diff --git a/assets/docs/layout_tutorial.md b/assets/docs/layout_tutorial.md index 99d4f244..4d3e1865 100644 --- a/assets/docs/layout_tutorial.md +++ b/assets/docs/layout_tutorial.md @@ -70,23 +70,23 @@ are defined. Consider the `standard_association` plot: LocusZoom.Layouts.add('plot', 'standard_association', { state: {}, width: 800, - height: 450, - responsive_resize: 'width_only', + responsive_resize: true, min_region_scale: 20000, max_region_scale: 1000000, dashboard: LocusZoom.Layouts.get('dashboard', 'standard_plot', { unnamespaced: true }), panels: [ - LocusZoom.Layouts.get('panel', 'association', { unnamespaced: true, proportional_height: 0.5 }), - LocusZoom.Layouts.get('panel', 'genes', { unnamespaced: true, proportional_height: 0.5 }) + LocusZoom.Layouts.get('panel', 'association', { unnamespaced: true, height: 225 }), + LocusZoom.Layouts.get('panel', 'genes', { unnamespaced: true, 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, -we control how the total space is allocated between two panels (`proportional_height`). The actual details of what to -render are defined as nested layouts (association and genes panels), and the registry also contains predefined options -for this piece- `LocusZoom.Layouts.get(...)` returns a JSON object. +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 this piece- +`LocusZoom.Layouts.get(...)` returns a JSON object. Although the layout could be defined as a single giant object (top-down view of everything at once), defining it in terms of reusable building blocks (bottom up) makes it much easier to read and see boundaries. @@ -309,23 +309,22 @@ data_sources // 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 - height: 1200, // The default plot was sized for two panels. Make sure to allocate enough room for the extra panel. - responsive_resize: 'width_only', // This makes height calculations more reliable. Will become the default in the future. + 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". - proportional_height: 0.33, + height: 400, id: 'assoc_study1', // Give each panel a unique ID title: { text: 'Study 1' }, }), LocusZoom.Layouts.get('panel', 'association', { namespace: { assoc: 'assoc_study2' }, - proportional_height: 0.33, // Modifies the default so that all three panels get enough space + 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, proportional_height: 0.33 }) // "unnamespaced" when using as a generic building block + LocusZoom.Layouts.get('panel', 'genes', { unnamespaced: true, height: 400 }) // "unnamespaced" when using as a generic building block ] }); ``` @@ -347,8 +346,7 @@ Object.keys(layer_data[0]); // See the field names used by the data for a single ### 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; - dynamic panels do not require specifying `proportional_height` etc. + first render. New panels can be added dynamically! When doing so, the plot will grow to accommodate the new panel. ```js // This creates a new configuration object diff --git a/dist/ext/lz-aggregation-tests.min.js b/dist/ext/lz-aggregation-tests.min.js index fca6840d..2b8086c7 100644 --- a/dist/ext/lz-aggregation-tests.min.js +++ b/dist/ext/lz-aggregation-tests.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.13.0-beta.3 */ -var LzAggregationTests=function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},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=12)}({12:function(e,t,r){"use strict";r.r(t);var n=r(4),o=r(2);function s(e){const t=e.Adapters.get("BaseAdapter"),r=e.Adapters.get("ConnectorSource");class s extends o.BaseApiAdapter{getURL(e,t,r){const n=e.aggregation_tests||{};t.header||(t.header={}),t.header.aggregation_genoset_id=n.genoset_id||null,t.header.aggregation_genoset_build=n.genoset_build||null,t.header.aggregation_phenoset_id=n.phenoset_id||null,t.header.aggregation_pheno=n.pheno||null,t.header.aggregation_calcs=n.calcs||{};const o=n.masks||[];return t.header.aggregation_masks=o,t.header.aggregation_mask_ids=o.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 n=this.getURL(e,t,r),o=this.getCacheKey(e,t,r);return fetch(n,{method:"POST",body:o,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 r=n.helpers.parsePortalJSON(e);let o=r[0];const s=r[1];o=o.byMask(t.header.aggregation_mask_ids);const i=t.header.aggregation_calcs;if(!i||0===Object.keys(i).length)return{variants:[],groups:[],results:[]};return new n.helpers.PortalTestRunner(o,s,i).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("AggregationTestSourceLZ",s),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 r{_getRequiredSources(){return["gene_ns","aggregation_ns"]}combineChainBody(e,t){const r=this._source_name_mapping.aggregation_ns,n=this._source_name_mapping.gene_ns,o=t.discrete[r],s=t.discrete[n],i={};return o.groups.forEach((function(e){Object.prototype.hasOwnProperty.call(i,e.group)||(i[e.group]=[]),i[e.group].push(e.pvalue)})),s.forEach((function(e){const t=e.gene_name,r=i[t];r&&(e.aggregation_best_pvalue=Math.min.apply(null,r))})),s}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(s),t.default=s},2:function(e,t,r){"use strict";function n(e,t,r){if(t&&r||!t&&!r)throw new Error(e+' must provide a parameter specifying either "build" or "source". It should not specify both.');if(t&&!["GRCh37","GRCh38"].includes(t))throw new Error(e+" must specify a valid genome build number")}r.r(t),r.d(t,"BaseAdapter",(function(){return o})),r.d(t,"BaseApiAdapter",(function(){return s})),r.d(t,"AssociationLZ",(function(){return i})),r.d(t,"ConnectorSource",(function(){return f})),r.d(t,"GeneConstraintLZ",(function(){return d})),r.d(t,"GeneLZ",(function(){return c})),r.d(t,"GwasCatalogLZ",(function(){return u})),r.d(t,"LDServer",(function(){return a})),r.d(t,"PheWASLZ",(function(){return p})),r.d(t,"RecombLZ",(function(){return l})),r.d(t,"StaticSource",(function(){return h}));class o{constructor(e){this._enableCache=!0,this._cachedKey=null,this.__dependentSource=!1,this.parseInit(e)}parseInit(e){this.params=e.params||{}}getCacheKey(e,t,r){return this.getURL(e,t,r)}getURL(e,t,r){return this.url}fetchRequest(e,t,r){const n=this.getURL(e,t,r);return fetch(n).then(e=>{if(!e.ok)throw new Error(e.statusText);return e.text()})}getRequest(e,t,r){let n;const o=this.getCacheKey(e,t,r);return this._enableCache&&void 0!==o&&o===this._cachedKey?n=Promise.resolve(this._cachedResponse):(n=this.fetchRequest(e,t,r),this._enableCache&&(this._cachedKey=o,this._cachedResponse=n)),n}normalizeResponse(e){if(Array.isArray(e))return e;const t=Object.keys(e),r=e[t[0]].length;if(!t.every((function(t){return e[t].length===r})))throw new Error(this.constructor.name+" expects a response in which all arrays of data are the same length");const n=[],o=Object.keys(e);for(let t=0;tPromise.resolve(this.annotateData(e,t))).then(e=>Promise.resolve(this.extractFields(e,r,n,o))).then(e=>(t.discrete[s]=e,Promise.resolve(this.combineChainBody(e,t,r,n,o)))).then(e=>({header:t.header||{},discrete:t.discrete,body:e}))}getData(e,t,r,n){if(this.preGetData){const o=this.preGetData(e,t,r,n);this.pre&&(e=o.state||e,t=o.fields||t,r=o.outnames||r,n=o.trans||n)}return o=>this.__dependentSource&&o&&o.body&&!o.body.length?Promise.resolve(o):this.getRequest(e,o,t).then(e=>this.parseResponse(e,o,t,r,n))}}class s extends o{parseInit(e){if(super.parseInit(e),this.url=e.url,!this.url)throw new Error("Source not initialized with required URL")}}class i extends s{preGetData(e,t,r,n){return[this.params.id_field||"id","position"].forEach((function(e){t.includes(e)||(t.unshift(e),r.unshift(e),n.unshift(null))})),{fields:t,outnames:r,trans:n}}getURL(e,t,r){const n=t.header.analysis||this.params.source||this.params.analysis;if(void 0===n)throw new Error("Association source must specify an analysis ID to plot");return`${this.url}results/?filter=analysis in ${n} and chromosome in '${e.chr}' and position ge ${e.start} and position le ${e.end}`}normalizeResponse(e){return e=super.normalizeResponse(e),this.params&&this.params.sort&&e.length&&e[0].position&&e.sort((function(e,t){return e.position-t.position})),e}}class a extends s{constructor(e){super(e),this.__dependentSource=!0}preGetData(e,t){if(t.length>1&&(2!==t.length||!t.includes("isrefvar")))throw new Error("LD does not know how to get all fields: "+t.join(", "))}findMergeFields(e){let t={id:this.params.id_field,position:this.params.position_field,pvalue:this.params.pvalue_field,_names_:null};if(e&&e.body&&e.body.length>0){const n=Object.keys(e.body[0]),o=(r=n,function(){const e=arguments;for(let t=0;tt}:function(e,t){return e{if(!e.ok)throw new Error(e.statusText);return e.text()}).then((function(e){return e=JSON.parse(e),Object.keys(e.data).forEach((function(t){o.data[t]=(o.data[t]||[]).concat(e.data[t])})),e.next?s(e.next):o}))};return s(n)}}class u extends s{constructor(e){super(e),this.__dependentSource=!0}getURL(e,t,r){const o=e.genome_build||this.params.build;n(this.constructor.name,o,null);const s="GRCh38"===o?5:6,i=this.params.source||s;return`${this.url}?format=objects&sort=pos&filter=id eq ${i} and chrom eq '${e.chr}' and pos ge ${e.start} and pos le ${e.end}`}findMergeFields(e){const t=Object.keys(e).find((function(e){return e.match(/\b(position|pos)\b/i)}));if(!t)throw new Error("Could not find data to align with GWAS catalog results");return{pos:t}}extractFields(e,t,r,n){return e}combineChainBody(e,t,r,n,o){if(!e.length)return t.body;const s=n[r.indexOf("log_pvalue")];function i(e,t,r,n,o){const i=e.n_catalog_matches||0;if(e.n_catalog_matches=i+1,!(e[s]&&e[s]>t.log_pvalue))for(let s=0;se.ok?e.text():[]).catch(e=>[])}combineChainBody(e,t,r,n,o){return e?(t.body.forEach((function(t){const r="_"+t.gene_name.replace(/[^A-Za-z0-9_]/g,"_"),n=e[r]&&e[r].gnomad_constraint;n&&Object.keys(n).forEach((function(e){let r=n[e];void 0===t[e]&&("number"==typeof r&&r.toString().includes(".")&&(r=parseFloat(r.toFixed(2))),t[e]=r)}))})),t.body):t}}class l extends s{getURL(e,t,r){const o=e.genome_build||this.params.build;let s=this.params.source;return n(this.constructor.SOURCE_NAME,o,s),o&&(s="GRCh38"===o?16:15),`${this.url}?filter=id in ${s} and chromosome eq '${e.chr}' and position le ${e.end} and position ge ${e.start}`}}class h extends o{parseInit(e){this._data=e}getRequest(e,t,r){return Promise.resolve(this._data)}}class p extends s{getURL(e,t,r){const n=(e.genome_build?[e.genome_build]:null)||this.params.build;if(!n||!Array.isArray(n)||!n.length)throw new Error(["Data source",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(e.variant),"'&format=objects&",n.map((function(e){return"build="+encodeURIComponent(e)})).join("&")].join("")}}class f extends o{constructor(e){if(super(e),!e||!e.sources)throw new Error("Connectors must specify the data they require as init.sources = {internal_name: chain_source_id}} pairs");this._source_name_mapping=e.sources;const t=Object.keys(e.sources);this._getRequiredSources().forEach(e=>{if(!t.includes(e))throw new Error(`Configuration for ${this.constructor.name} must specify a source ID corresponding to ${e}`)})}parseInit(){}getRequest(e,t,r){return Object.keys(this._source_name_mapping).forEach(e=>{const r=this._source_name_mapping[e];if(t.discrete&&!t.discrete[r])throw new Error(`${this.constructor.name} cannot be used before loading required data for: ${r}`)}),Promise.resolve(t.body||[])}parseResponse(e,t,r,n,o){return Promise.resolve(this.combineChainBody(e,t,r,n,o)).then((function(e){return{header:t.header||{},discrete:t.discrete||{},body:e}}))}combineChainBody(e,t){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")}}},4:function(e,t){e.exports=raremetal}}).default; +/*! Locuszoom 0.13.0-beta.4 */ +var LzAggregationTests=function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},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=12)}({12:function(e,t,r){"use strict";r.r(t);var n=r(4),o=r(2);function s(e){const t=e.Adapters.get("BaseAdapter"),r=e.Adapters.get("ConnectorSource");class s extends o.BaseApiAdapter{getURL(e,t,r){const n=e.aggregation_tests||{};t.header||(t.header={}),t.header.aggregation_genoset_id=n.genoset_id||null,t.header.aggregation_genoset_build=n.genoset_build||null,t.header.aggregation_phenoset_id=n.phenoset_id||null,t.header.aggregation_pheno=n.pheno||null,t.header.aggregation_calcs=n.calcs||{};const o=n.masks||[];return t.header.aggregation_masks=o,t.header.aggregation_mask_ids=o.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 n=this.getURL(e,t,r),o=this.getCacheKey(e,t,r);return fetch(n,{method:"POST",body:o,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 r=n.helpers.parsePortalJSON(e);let o=r[0];const s=r[1];o=o.byMask(t.header.aggregation_mask_ids);const i=t.header.aggregation_calcs;if(!i||0===Object.keys(i).length)return{variants:[],groups:[],results:[]};return new n.helpers.PortalTestRunner(o,s,i).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("AggregationTestSourceLZ",s),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 r{_getRequiredSources(){return["gene_ns","aggregation_ns"]}combineChainBody(e,t){const r=this._source_name_mapping.aggregation_ns,n=this._source_name_mapping.gene_ns,o=t.discrete[r],s=t.discrete[n],i={};return o.groups.forEach((function(e){Object.prototype.hasOwnProperty.call(i,e.group)||(i[e.group]=[]),i[e.group].push(e.pvalue)})),s.forEach((function(e){const t=e.gene_name,r=i[t];r&&(e.aggregation_best_pvalue=Math.min.apply(null,r))})),s}})}"undefined"!=typeof LocusZoom&&LocusZoom.use(s),t.default=s},2:function(e,t,r){"use strict";function n(e,t,r){if(t&&r||!t&&!r)throw new Error(e+' must provide a parameter specifying either "build" or "source". It should not specify both.');if(t&&!["GRCh37","GRCh38"].includes(t))throw new Error(e+" must specify a valid genome build number")}r.r(t),r.d(t,"BaseAdapter",(function(){return o})),r.d(t,"BaseApiAdapter",(function(){return s})),r.d(t,"AssociationLZ",(function(){return i})),r.d(t,"ConnectorSource",(function(){return f})),r.d(t,"GeneConstraintLZ",(function(){return d})),r.d(t,"GeneLZ",(function(){return c})),r.d(t,"GwasCatalogLZ",(function(){return u})),r.d(t,"LDServer",(function(){return a})),r.d(t,"PheWASLZ",(function(){return p})),r.d(t,"RecombLZ",(function(){return l})),r.d(t,"StaticSource",(function(){return h}));class o{constructor(e){this._enableCache=!0,this._cachedKey=null,this._cache_pos_start=null,this._cache_pos_end=null,this.__dependentSource=!1,this.parseInit(e)}parseInit(e){this.params=e.params||{}}getCacheKey(e,t,r){this.getURL(e,t,r);const n=e.chr,{_cache_pos_start:o,_cache_pos_end:s}=this;return o&&e.start>=o&&s&&e.end<=s?`${n}_${o}_${s}`:`${e.chr}_${e.start}_${e.end}`}getURL(e,t,r){return this.url}fetchRequest(e,t,r){const n=this.getURL(e,t,r);return fetch(n).then(e=>{if(!e.ok)throw new Error(e.statusText);return e.text()})}getRequest(e,t,r){let n;const o=this.getCacheKey(e,t,r);return this._enableCache&&void 0!==o&&o===this._cachedKey?n=Promise.resolve(this._cachedResponse):(n=this.fetchRequest(e,t,r),this._enableCache&&(this._cachedKey=o,this._cache_pos_start=e.start,this._cache_pos_end=e.end,this._cachedResponse=n)),n}normalizeResponse(e){if(Array.isArray(e))return e;const t=Object.keys(e),r=e[t[0]].length;if(!t.every((function(t){return e[t].length===r})))throw new Error(this.constructor.name+" expects a response in which all arrays of data are the same length");const n=[],o=Object.keys(e);for(let t=0;tPromise.resolve(this.annotateData(e,t))).then(e=>Promise.resolve(this.extractFields(e,r,n,o))).then(e=>(t.discrete[s]=e,Promise.resolve(this.combineChainBody(e,t,r,n,o)))).then(e=>({header:t.header||{},discrete:t.discrete,body:e}))}getData(e,t,r,n){if(this.preGetData){const o=this.preGetData(e,t,r,n);this.pre&&(e=o.state||e,t=o.fields||t,r=o.outnames||r,n=o.trans||n)}return o=>this.__dependentSource&&o&&o.body&&!o.body.length?Promise.resolve(o):this.getRequest(e,o,t).then(e=>this.parseResponse(e,o,t,r,n))}}class s extends o{parseInit(e){if(super.parseInit(e),this.url=e.url,!this.url)throw new Error("Source not initialized with required URL")}}class i extends s{preGetData(e,t,r,n){return[this.params.id_field||"id","position"].forEach((function(e){t.includes(e)||(t.unshift(e),r.unshift(e),n.unshift(null))})),{fields:t,outnames:r,trans:n}}getURL(e,t,r){const n=t.header.analysis||this.params.source||this.params.analysis;if(void 0===n)throw new Error("Association source must specify an analysis ID to plot");return`${this.url}results/?filter=analysis in ${n} and chromosome in '${e.chr}' and position ge ${e.start} and position le ${e.end}`}normalizeResponse(e){return e=super.normalizeResponse(e),this.params&&this.params.sort&&e.length&&e[0].position&&e.sort((function(e,t){return e.position-t.position})),e}}class a extends s{constructor(e){super(e),this.__dependentSource=!0}preGetData(e,t){if(t.length>1&&(2!==t.length||!t.includes("isrefvar")))throw new Error("LD does not know how to get all fields: "+t.join(", "))}findMergeFields(e){let t={id:this.params.id_field,position:this.params.position_field,pvalue:this.params.pvalue_field,_names_:null};if(e&&e.body&&e.body.length>0){const n=Object.keys(e.body[0]),o=(r=n,function(){const e=arguments;for(let t=0;tt}:function(e,t){return e{if(!e.ok)throw new Error(e.statusText);return e.text()}).then((function(e){return e=JSON.parse(e),Object.keys(e.data).forEach((function(t){o.data[t]=(o.data[t]||[]).concat(e.data[t])})),e.next?s(e.next):o}))};return s(n)}}class u extends s{constructor(e){super(e),this.__dependentSource=!0}getURL(e,t,r){const o=e.genome_build||this.params.build;n(this.constructor.name,o,null);const s="GRCh38"===o?5:6,i=this.params.source||s;return`${this.url}?format=objects&sort=pos&filter=id eq ${i} and chrom eq '${e.chr}' and pos ge ${e.start} and pos le ${e.end}`}findMergeFields(e){const t=Object.keys(e).find((function(e){return e.match(/\b(position|pos)\b/i)}));if(!t)throw new Error("Could not find data to align with GWAS catalog results");return{pos:t}}extractFields(e,t,r,n){return e}combineChainBody(e,t,r,n,o){if(!e.length)return t.body;const s=n[r.indexOf("log_pvalue")];function i(e,t,r,n,o){const i=e.n_catalog_matches||0;if(e.n_catalog_matches=i+1,!(e[s]&&e[s]>t.log_pvalue))for(let s=0;se.ok?e.text():[]).catch(e=>[])}combineChainBody(e,t,r,n,o){return e?(t.body.forEach((function(t){const r="_"+t.gene_name.replace(/[^A-Za-z0-9_]/g,"_"),n=e[r]&&e[r].gnomad_constraint;n&&Object.keys(n).forEach((function(e){let r=n[e];void 0===t[e]&&("number"==typeof r&&r.toString().includes(".")&&(r=parseFloat(r.toFixed(2))),t[e]=r)}))})),t.body):t}}class l extends s{getURL(e,t,r){const o=e.genome_build||this.params.build;let s=this.params.source;return n(this.constructor.SOURCE_NAME,o,s),o&&(s="GRCh38"===o?16:15),`${this.url}?filter=id in ${s} and chromosome eq '${e.chr}' and position le ${e.end} and position ge ${e.start}`}}class h extends o{parseInit(e){this._data=e}getRequest(e,t,r){return Promise.resolve(this._data)}}class p extends s{getURL(e,t,r){const n=(e.genome_build?[e.genome_build]:null)||this.params.build;if(!n||!Array.isArray(n)||!n.length)throw new Error(["Data source",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(e.variant),"'&format=objects&",n.map((function(e){return"build="+encodeURIComponent(e)})).join("&")].join("")}getCacheKey(e,t,r){return this.getURL(e,t,r)}}class f extends o{constructor(e){if(super(e),!e||!e.sources)throw new Error("Connectors must specify the data they require as init.sources = {internal_name: chain_source_id}} pairs");this._source_name_mapping=e.sources;const t=Object.keys(e.sources);this._getRequiredSources().forEach(e=>{if(!t.includes(e))throw new Error(`Configuration for ${this.constructor.name} must specify a source ID corresponding to ${e}`)})}parseInit(){}getRequest(e,t,r){return Object.keys(this._source_name_mapping).forEach(e=>{const r=this._source_name_mapping[e];if(t.discrete&&!t.discrete[r])throw new Error(`${this.constructor.name} cannot be used before loading required data for: ${r}`)}),Promise.resolve(t.body||[])}parseResponse(e,t,r,n,o){return Promise.resolve(this.combineChainBody(e,t,r,n,o)).then((function(e){return{header:t.header||{},discrete:t.discrete||{},body:e}}))}combineChainBody(e,t){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")}}},4:function(e,t){e.exports=raremetal}}).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 fb155f3c..b483025e 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]/./esm/ext/lz-aggregation-tests.js","webpack://[name]/./esm/data/adapters.js","webpack://[name]/external \"raremetal\""],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","install","LocusZoom","BaseAdapter","Adapters","ConnectorSource","AggregationTestSource","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","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","parsePortalJSON","byMask","keys","length","results","PortalTestRunner","toJSON","res","mask_id_to_desc","reduce","acc","val","description","data","forEach","group","mask_name","mask","catch","e","console","add","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","validateBuildSource","class_name","build","source","includes","_enableCache","_cachedKey","__dependentSource","params","req","cacheKey","_cachedResponse","fetchRequest","Array","isArray","N","every","record","j","outnames","trans","fieldFound","k","output_record","v","source_id","normalizeResponse","standardized","annotateData","extractFields","one_source_body","combineChainBody","new_body","preGetData","pre","getRequest","parseResponse","BaseApiAdapter","AssociationLZ","id_field","x","unshift","analysis","LDServer","join","dataFields","id","position_field","pvalue_field","_names_","names","nameMatch","arr","regexes","regex","id_match","obj","isrefvarin","isrefvarout","ldin","ldout","refVar","findRequestedFields","ldrefvar","findMergeFields","columns","pval_field","cmp","test","extremeVal","extremeIdx","findExtremeValue","genome_build","ld_source","population","ld_pop","getRefvar","original","pos","ref","alt","encodeURIComponent","reqFields","corrField","rsquare","left","right","lfield","rfield","position2","leftJoin","refvar","idfield","outrefname","outldname","tagRefVariant","combined","chainRequests","payload","concat","next","GwasCatalogLZ","build_option","default_source","posMatch","find","decider_out","indexOf","n_matches","fn","outn","chainNames","catNames","GeneLZ","GeneConstraintLZ","unique_gene_names","query","replace","err","alias","constraint","toString","parseFloat","toFixed","RecombLZ","StaticSource","_data","PheWASLZ","sources","specified_ids","_getRequiredSources","chain_source_id","raremetal"],"mappings":"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","file":"ext/lz-aggregation-tests.min.js","sourcesContent":[" \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, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\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\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 12);\n","/** @module */\n/*\n * LocusZoom extensions used to calculate and render aggregation test results. Because these calculations depend on an\n * external library, the special data sources are defined here, rather than in LocusZoom core code.\n *\n * The page must incorporate and load all libraries before this file can be used, including:\n * - Vendor assets\n * - LocusZoom\n * - raremetal.js (available via NPM or a related CDN)\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';\nimport {BaseApiAdapter} from '../data/adapters';\n\n\nfunction install (LocusZoom) {\n /**\n * Data Source that calculates gene or region-based tests based on provided data\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 * @public\n */\n const BaseAdapter = LocusZoom.Adapters.get('BaseAdapter');\n const ConnectorSource = LocusZoom.Adapters.get('ConnectorSource');\n\n class AggregationTestSource 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 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 * @public\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', AggregationTestSource);\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","/**\n * Define standard data adapters used to retrieve data (usually from REST APIs)\n * @module\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/**\n * Base class for LocusZoom data sources (any). See also: BaseApiAdapter\n * @public\n */\nclass BaseAdapter {\n constructor(config) {\n /**\n * Whether this source should enable caching\n * @member {Boolean}\n */\n this._enableCache = true;\n this._cachedKey = null;\n\n /**\n * Whether this data source 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 * @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 data source. 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 /** @member {Object} */\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 URL.\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|undefined}\n */\n getCacheKey(state, chain, fields) {\n return this.getURL(state, chain, fields);\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 */\n getRequest(state, chain, fields) {\n let req;\n const cacheKey = this.getCacheKey(state, chain, fields);\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._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 sources 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 * @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 source, 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 sources 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 source. (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 source for LocusZoom data sources that receive their data over the web. Adds default config parameters\n * (and potentially other behavior) that are relevant to URL-based requests.\n */\nclass BaseApiAdapter extends BaseAdapter {\n parseInit(config) {\n super.parseInit(config);\n\n /** @member {String} */\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 * Data Source for Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a requesr\n * @public\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 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 normalizeResponse (data) {\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 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\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.\n *\n * In older versions of LocusZoom, this was known as \"LDServer\". A prior source (targeted at older APIs) has been removed.\n */\nclass LDServer extends BaseApiAdapter {\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 normalizeResponse (data) {\n // The LD API payload does not obey standard format conventions; do not try to transform it.\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 * @returns {*|string} The marker id (expected to be in `chr:pos_ref/alt` format) of the reference variant\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 return refVar;\n }\n\n getURL(state, chain, fields) {\n // Accept the following params in this.params:\n // - method (r, rsquare, cov)\n // - source (aka panel)\n // - population (ALL, AFR, EUR, etc)\n // - build\n // The LD source/pop can be overridden from plot.state for dynamic layouts\n const build = state.genome_build || this.params.build || 'GRCh37';\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 let refVar = this.getRefvar(state, chain, fields);\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 refVar = `${chrom}:${pos}`;\n if (ref && alt) {\n refVar += `_${ref}/${alt}`;\n }\n // Preserve the user-provided variant spec for use when matching to assoc data\n chain.header.ldrefvar = original;\n\n return [\n this.url, 'genome_builds/', build, '/references/', source, '/populations/', population, '/variants',\n '?correlation=', method,\n '&variant=', encodeURIComponent(refVar),\n '&chrom=', encodeURIComponent(state.chr),\n '&start=', encodeURIComponent(state.start),\n '&stop=', encodeURIComponent(state.end),\n ].join('');\n }\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 fetchRequest(state, chain, fields) {\n // The API is paginated, but we need all of the data to render a plot. Depaginate and combine where appropriate.\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 * Data source for GWAS catalogs of known variants\n * @public\n * @class\n * @param {Object|String} init Configuration (URL or object)\n * @param {Object} [init.params] Optional configuration parameters\n * @param {Number} [init.params.source=2] The ID of the chosen catalog. Defaults to EBI GWAS catalog, GRCh37\n * @param {('strict'|'loose')} [init.params.match_type='strict'] Whether to match on exact variant, or just position.\n */\nclass GwasCatalogLZ extends BaseApiAdapter {\n constructor(config) {\n super(config);\n this.__dependentSource = true;\n }\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_option = state.genome_build || this.params.build;\n validateBuildSource(this.constructor.name, build_option, null); // Source can override build- not mutually exclusive\n\n // Most of our annotations will respect genome build before any other option.\n // But there can be more than one GWAS catalog version available in the same API, for the same build- an\n // explicit config option will always take\n // precedence.\n // See: http://portaldev.sph.umich.edu/api/v1/annotation/gwascatalog/?format=objects\n const default_source = (build_option === 'GRCh38') ? 5 : 6; // EBI GWAS catalog\n const source = this.params.source || default_source;\n return `${this.url }?format=objects&sort=pos&filter=id eq ${source} and chrom eq '${state.chr}' and pos ge ${state.start} and pos le ${state.end}`;\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 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 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 * Data Source for Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format)\n * @public\n */\nclass GeneLZ extends BaseApiAdapter {\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 (build) {\n // If build specified, we auto-select the best current portaldev API dataset for that build\n // If build is not specified, we use the exact source ID provided by the user.\n // See: https://portaldev.sph.umich.edu/api/v1/annotation/genes/sources/?format=objects\n source = (build === 'GRCh38') ? 4 : 5;\n }\n return `${this.url}?filter=source in ${source} and chrom eq '${state.chr}' and start le ${state.end} and end ge ${state.start}`;\n }\n\n normalizeResponse(data) {\n // Genes have 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 return data;\n }\n\n extractFields(data, fields, outnames, trans) {\n return data;\n }\n}\n\n/**\n * Data Source for Gene Constraint Data, as fetched from the gnomAD server (or compatible)\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.\n *\n * @public\n*/\nclass GeneConstraintLZ extends BaseApiAdapter {\n constructor(config) {\n super(config);\n this.__dependentSource = true;\n }\n getURL() {\n // GraphQL API: request details are encoded in the body, not the URL\n return this.url;\n }\n getCacheKey(state, chain, fields) {\n const build = state.genome_build || this.params.build;\n // GraphQL API: request not defined solely by the URL\n // Gather the state params that govern constraint query for a given region.\n return `${this.url} ${state.chr} ${state.start} ${state.end} ${build}`;\n }\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(`Data source ${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) {\n // If there are no genes, skip the network request\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 // FIXME: The gnomAD API sometimes has temporary CORS changes that temporarily break the genes track\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 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 * Data Source for Recombination Rate Data, as fetched from the LocusZoom API server (or compatible)\n * @public\n */\nclass RecombLZ extends BaseApiAdapter {\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 (build) { // If build specified, choose a known Portal API dataset IDs (build 37/38)\n source = (build === 'GRCh38') ? 16 : 15;\n }\n return `${this.url}?filter=id in ${source} and chromosome eq '${state.chr}' and position le ${state.end} and position ge ${state.start}`;\n }\n}\n\n/**\n * Data Source for static blobs of data as raw JS objects. This does not perform additional parsing, which is required\n * for some sources (eg when joining 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.\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 * @public\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 getRequest(state, chain, fields) {\n return Promise.resolve(this._data);\n }\n}\n\n\n/**\n * Data source for PheWAS data retrieved from a LocusZoom/PortalDev compatible API\n * @public\n * @param {String[]} init.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(['Data source', 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\n\n/**\n * Base class for \"connectors\"- this is meant to be subclassed, rather than used directly.\n *\n * A connector is a source 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 * @param {Object} init Configuration for this source\n * @param {Object} init.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 * @type {*|Function}\n */\nclass ConnectorSource extends BaseAdapter {\n constructor(config) {\n super(config);\n\n if (!config || !config.sources) {\n throw new Error('Connectors must specify the data they require as init.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 */\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","module.exports = raremetal;"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/./esm/ext/lz-aggregation-tests.js","webpack://[name]/./esm/data/adapters.js","webpack://[name]/external \"raremetal\""],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","install","LocusZoom","BaseAdapter","Adapters","ConnectorSource","AggregationTestSource","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","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","parsePortalJSON","byMask","keys","length","results","PortalTestRunner","toJSON","res","mask_id_to_desc","reduce","acc","val","description","data","forEach","group","mask_name","mask","catch","e","console","add","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","validateBuildSource","class_name","build","source","includes","_enableCache","_cachedKey","_cache_pos_start","_cache_pos_end","__dependentSource","params","cache_pos_chr","req","cacheKey","_cachedResponse","fetchRequest","Array","isArray","N","every","record","j","outnames","trans","fieldFound","k","output_record","v","source_id","normalizeResponse","standardized","annotateData","extractFields","one_source_body","combineChainBody","new_body","preGetData","pre","getRequest","parseResponse","BaseApiAdapter","AssociationLZ","id_field","x","unshift","analysis","LDServer","join","dataFields","id","position_field","pvalue_field","_names_","names","nameMatch","arr","regexes","regex","id_match","obj","isrefvarin","isrefvarout","ldin","ldout","refVar","findRequestedFields","ldrefvar","findMergeFields","columns","pval_field","cmp","test","extremeVal","extremeIdx","findExtremeValue","original","pos","ref","alt","refVar_formatted","genome_build","ld_source","population","ld_pop","refVar_raw","getRefvar","encodeURIComponent","base","_","reqFields","corrField","rsquare","left","right","lfield","rfield","position2","leftJoin","refvar","idfield","outrefname","outldname","tagRefVariant","combined","chainRequests","payload","concat","next","GwasCatalogLZ","build_option","default_source","posMatch","find","decider_out","indexOf","n_matches","fn","outn","chainNames","catNames","GeneLZ","GeneConstraintLZ","unique_gene_names","query","replace","err","alias","constraint","toString","parseFloat","toFixed","RecombLZ","StaticSource","_data","PheWASLZ","sources","specified_ids","_getRequiredSources","chain_source_id","raremetal"],"mappings":"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","file":"ext/lz-aggregation-tests.min.js","sourcesContent":[" \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, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\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\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 12);\n","/** @module */\n/*\n * LocusZoom extensions used to calculate and render aggregation test results. Because these calculations depend on an\n * external library, the special data sources are defined here, rather than in LocusZoom core code.\n *\n * The page must incorporate and load all libraries before this file can be used, including:\n * - Vendor assets\n * - LocusZoom\n * - raremetal.js (available via NPM or a related CDN)\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';\nimport {BaseApiAdapter} from '../data/adapters';\n\n\nfunction install (LocusZoom) {\n /**\n * Data Source that calculates gene or region-based tests based on provided data\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 * @public\n */\n const BaseAdapter = LocusZoom.Adapters.get('BaseAdapter');\n const ConnectorSource = LocusZoom.Adapters.get('ConnectorSource');\n\n class AggregationTestSource 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 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 * @public\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', AggregationTestSource);\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","/**\n * Define standard data adapters used to retrieve data (usually from REST APIs)\n * @module\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/**\n * Base class for LocusZoom data sources (any). See also: BaseApiAdapter\n * @public\n */\nclass BaseAdapter {\n constructor(config) {\n /**\n * Whether this source should enable caching\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 data source 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 * @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 data source. 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 /** @member {Object} */\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 data source 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 data sources 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 sources 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 * @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 source, 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 sources 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 source. (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 source for LocusZoom data sources that receive their data over the web. Adds default config parameters\n * (and potentially other behavior) that are relevant to URL-based requests.\n */\nclass BaseApiAdapter extends BaseAdapter {\n parseInit(config) {\n super.parseInit(config);\n\n /** @member {String} */\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 * Data Source for Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a requesr\n * @public\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 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 normalizeResponse (data) {\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 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\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.\n *\n * In older versions of LocusZoom, this was known as \"LDServer\". A prior source (targeted at older APIs) has been removed.\n */\nclass LDServer extends BaseApiAdapter {\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 normalizeResponse (data) {\n // The LD API payload does not obey standard format conventions; do not try to transform it.\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 * @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 getURL(state, chain, fields) {\n // Accept the following params in this.params:\n // - method (r, rsquare, cov)\n // - source (aka panel)\n // - population (ALL, AFR, EUR, etc)\n // - build\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 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 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 fetchRequest(state, chain, fields) {\n // The API is paginated, but we need all of the data to render a plot. Depaginate and combine where appropriate.\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 * Data source for GWAS catalogs of known variants\n * @public\n * @class\n * @param {Object|String} init Configuration (URL or object)\n * @param {Object} [init.params] Optional configuration parameters\n * @param {Number} [init.params.source=2] The ID of the chosen catalog. Defaults to EBI GWAS catalog, GRCh37\n * @param {('strict'|'loose')} [init.params.match_type='strict'] Whether to match on exact variant, or just position.\n */\nclass GwasCatalogLZ extends BaseApiAdapter {\n constructor(config) {\n super(config);\n this.__dependentSource = true;\n }\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_option = state.genome_build || this.params.build;\n validateBuildSource(this.constructor.name, build_option, null); // Source can override build- not mutually exclusive\n\n // Most of our annotations will respect genome build before any other option.\n // But there can be more than one GWAS catalog version available in the same API, for the same build- an\n // explicit config option will always take\n // precedence.\n // See: http://portaldev.sph.umich.edu/api/v1/annotation/gwascatalog/?format=objects\n const default_source = (build_option === 'GRCh38') ? 5 : 6; // EBI GWAS catalog\n const source = this.params.source || default_source;\n return `${this.url }?format=objects&sort=pos&filter=id eq ${source} and chrom eq '${state.chr}' and pos ge ${state.start} and pos le ${state.end}`;\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 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 * Data Source for Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format)\n * @public\n */\nclass GeneLZ extends BaseApiAdapter {\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 (build) {\n // If build specified, we auto-select the best current portaldev API dataset for that build\n // If build is not specified, we use the exact source ID provided by the user.\n // See: https://portaldev.sph.umich.edu/api/v1/annotation/genes/sources/?format=objects\n source = (build === 'GRCh38') ? 4 : 5;\n }\n return `${this.url}?filter=source in ${source} and chrom eq '${state.chr}' and start le ${state.end} and end ge ${state.start}`;\n }\n\n normalizeResponse(data) {\n // Genes have 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 return data;\n }\n\n extractFields(data, fields, outnames, trans) {\n return data;\n }\n}\n\n/**\n * Data Source for Gene Constraint Data, as fetched from the gnomAD server (or compatible)\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.\n *\n * @public\n*/\nclass GeneConstraintLZ extends BaseApiAdapter {\n constructor(config) {\n super(config);\n this.__dependentSource = true;\n }\n getURL() {\n // GraphQL API: request details are encoded in the body, not the URL\n return this.url;\n }\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(`Data source ${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) {\n // If there are no genes, skip the network request\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 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 * Data Source for Recombination Rate Data, as fetched from the LocusZoom API server (or compatible)\n * @public\n */\nclass RecombLZ extends BaseApiAdapter {\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 (build) { // If build specified, choose a known Portal API dataset IDs (build 37/38)\n source = (build === 'GRCh38') ? 16 : 15;\n }\n return `${this.url}?filter=id in ${source} and chromosome eq '${state.chr}' and position le ${state.end} and position ge ${state.start}`;\n }\n}\n\n/**\n * Data Source for static blobs of data as raw JS objects. This does not perform additional parsing, which is required\n * for some sources (eg when joining 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.\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 * @public\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 getRequest(state, chain, fields) {\n return Promise.resolve(this._data);\n }\n}\n\n\n/**\n * Data source for PheWAS data retrieved from a LocusZoom/PortalDev compatible API\n * @public\n * @param {String[]} init.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(['Data source', 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/**\n * Base class for \"connectors\"- this is meant to be subclassed, rather than used directly.\n *\n * A connector is a source 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 * @param {Object} init Configuration for this source\n * @param {Object} init.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 * @type {*|Function}\n */\nclass ConnectorSource extends BaseAdapter {\n constructor(config) {\n super(config);\n\n if (!config || !config.sources) {\n throw new Error('Connectors must specify the data they require as init.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 */\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","module.exports = raremetal;"],"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 ef224835..ed167cad 100644 --- a/dist/ext/lz-credible-sets.min.js +++ b/dist/ext/lz-credible-sets.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.13.0-beta.3 */ -var LzCredibleSets=function(e){var t={};function a(s){if(t[s])return t[s].exports;var o=t[s]={i:s,l:!1,exports:{}};return e[s].call(o.exports,o,o.exports,a),o.l=!0,o.exports}return a.m=e,a.c=t,a.d=function(e,t,s){a.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:s})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,t){if(1&t&&(e=a(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var s=Object.create(null);if(a.r(s),Object.defineProperty(s,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)a.d(s,o,function(t){return e[t]}.bind(null,o));return s},a.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(t,"a",t),t},a.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},a.p="",a(a.s=10)}({10:function(e,t,a){"use strict";a.r(t);var s=a(3);function o(e){const t=e.Adapters.get("BaseAdapter");e.Adapters.add("CredibleSetLZ",class extends t{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,t,a){return[e.credible_set_threshold||this.params.threshold,e.chr,e.start,e.end].join("_")}fetchRequest(e,t){if(!t.body.length)return Promise.resolve([]);const a=this,o=e.credible_set_threshold||this.params.threshold;if(void 0===t.body[0][a.params.fields.log_pvalue])throw new Error("Credible set source could not locate the required fields from a previous request.");const i=t.body.map(e=>e[a.params.fields.log_pvalue]);if(!i.some(e=>e>=a.params.significance_threshold))return Promise.resolve([]);const r=[];try{const e=s.scoring.bayesFactors(i),a=s.scoring.normalizeProbabilities(e),n=s.marking.findCredibleSet(a,o),c=s.marking.rescaleCredibleSet(n),l=s.marking.markBoolean(n);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}}"}),e.Layouts.add("data_layer","association_credible_set",function(){const t=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 t.color.unshift({field:"lz_highlight_match",scale_function:"if",parameters:{field_value:!0,then:"#FFf000"}}),t}()),e.Layouts.add("data_layer","annotation_credible_set",{namespace:{assoc:"assoc",credset:"credset"},id:"annotationcredibleset",type:"annotation_track",id_field:"{{namespace[assoc]}}variant",x_axis:{field:"{{namespace[assoc]}}position"},color:[{field:"lz_highlight_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("panel","annotation_credible_set",{id:"annotationcredibleset",title:{text:"SNPs in 95% credible set",x:50,style:{"font-size":"14px"}},width:800,height:45,min_height:45,proportional_width:1,margin:{top:25,right:50,bottom:0,left:50},inner_border:"rgb(210, 210, 210)",toolbar:e.Layouts.get("toolbar","standard_panel",{unnamespaced:!0}),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","association_credible_set",function(){const t=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 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:"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"}]}}]}),t}()),e.Layouts.add("plot","association_credible_set",{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})]})}"undefined"!=typeof LocusZoom&&LocusZoom.use(o),t.default=o},3:function(e,t){e.exports=gwasCredibleSets}}).default; +/*! Locuszoom 0.13.0-beta.4 */ +var LzCredibleSets=function(e){var t={};function a(s){if(t[s])return t[s].exports;var o=t[s]={i:s,l:!1,exports:{}};return e[s].call(o.exports,o,o.exports,a),o.l=!0,o.exports}return a.m=e,a.c=t,a.d=function(e,t,s){a.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:s})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,t){if(1&t&&(e=a(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var s=Object.create(null);if(a.r(s),Object.defineProperty(s,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)a.d(s,o,function(t){return e[t]}.bind(null,o));return s},a.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(t,"a",t),t},a.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},a.p="",a(a.s=10)}({10:function(e,t,a){"use strict";a.r(t);var s=a(3);function o(e){const t=e.Adapters.get("BaseAdapter");e.Adapters.add("CredibleSetLZ",class extends t{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,t,a){return[e.credible_set_threshold||this.params.threshold,e.chr,e.start,e.end].join("_")}fetchRequest(e,t){if(!t.body.length)return Promise.resolve([]);const a=this,o=e.credible_set_threshold||this.params.threshold;if(void 0===t.body[0][a.params.fields.log_pvalue])throw new Error("Credible set source could not locate the required fields from a previous request.");const r=t.body.map(e=>e[a.params.fields.log_pvalue]);if(!r.some(e=>e>=a.params.significance_threshold))return Promise.resolve([]);const n=[];try{const e=s.scoring.bayesFactors(r),a=s.scoring.normalizeProbabilities(e),i=s.marking.findCredibleSet(a,o),c=s.marking.rescaleCredibleSet(i),l=s.marking.markBoolean(i);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}}"}),e.Layouts.add("data_layer","association_credible_set",function(){const t=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 t.color.unshift({field:"lz_is_match",scale_function:"if",parameters:{field_value:!0,then:"#FFf000"}}),t}()),e.Layouts.add("data_layer","annotation_credible_set",{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("panel","annotation_credible_set",{id:"annotationcredibleset",title:{text:"SNPs in 95% credible set",x:50,style:{"font-size":"14px"}},min_height:45,height:45,margin:{top:25,right:50,bottom:0,left:50},inner_border:"rgb(210, 210, 210)",toolbar:e.Layouts.get("toolbar","standard_panel",{unnamespaced:!0}),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","association_credible_set",function(){const t=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 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:"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"}]}}]}),t}()),e.Layouts.add("plot","association_credible_set",{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})]})}"undefined"!=typeof LocusZoom&&LocusZoom.use(o),t.default=o},3:function(e,t){e.exports=gwasCredibleSets}}).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 8d0fa475..97a2ebea 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]/./esm/ext/lz-credible-sets.js","webpack://[name]/external \"gwasCredibleSets\""],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","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","bayesFactors","posteriorProbabilities","normalizeProbabilities","credibleSet","findCredibleSet","credSetScaled","rescaleCredibleSet","credSetBool","markBoolean","push","posterior_prob","contrib_fraction","is_member","e","console","error","data","outnames","trans","src","dest","keys","forEach","attr","Layouts","unnamespaced","html","namespace","closable","show","or","hide","and","base","id","fill_opacity","tooltip","match","send","receive","color","unshift","field","scale_function","parameters","field_value","then","type","id_field","x_axis","filters","operator","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip_positioning","title","text","x","style","width","height","min_height","proportional_width","margin","top","right","bottom","left","inner_border","toolbar","interaction","drag_background_to_pan","scroll_to_zoom","x_linked","data_layers","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","responsive_resize","min_region_scale","max_region_scale","panels","use","gwasCredibleSets"],"mappings":"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","file":"ext/lz-credible-sets.min.js","sourcesContent":[" \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, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\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\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 10);\n","/**\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 The page must incorporate and load all libraries before this file can be used, including:\n - Vendor assets\n - LocusZoom\n - gwas-credible-sets (available via NPM or a related CDN)\n @module\n*/\n\nimport {marking, scoring} from 'gwas-credible-sets';\n\nfunction install (LocusZoom) {\n const BaseAdapter = LocusZoom.Adapters.get('BaseAdapter');\n /**\n * Custom data source that calculates the 95% credible set based on provided 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.\n *\n * @param {Object} init.params\n * @param {Object} init.params.fields\n * @param {String} init.params.fields.log_pvalue The name of the field containing -log10 pvalue information\n * @param {Number} [init.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} [init.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 class CredibleSetLZ extends BaseAdapter {\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\n LocusZoom.Adapters.add('CredibleSetLZ', CredibleSetLZ);\n\n // Add related layouts to the central global registry\n LocusZoom.Layouts.add('tooltip', 'association_credible_set', 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', 'annotation_credible_set', {\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\n LocusZoom.Layouts.add('data_layer', 'association_credible_set', 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_highlight_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\n LocusZoom.Layouts.add('data_layer', 'annotation_credible_set', {\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_highlight_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\n LocusZoom.Layouts.add('panel', 'annotation_credible_set', {\n id: 'annotationcredibleset',\n title: { text: 'SNPs in 95% credible set', x: 50, style: { 'font-size': '14px' } },\n width: 800,\n height: 45,\n min_height: 45,\n proportional_width: 1,\n margin: { top: 25, right: 50, bottom: 0, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_panel', { unnamespaced: true }),\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\n LocusZoom.Layouts.add('panel', 'association_credible_set', 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\n LocusZoom.Layouts.add('plot', 'association_credible_set', {\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\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","module.exports = gwasCredibleSets;"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/./esm/ext/lz-credible-sets.js","webpack://[name]/external \"gwasCredibleSets\""],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","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","bayesFactors","posteriorProbabilities","normalizeProbabilities","credibleSet","findCredibleSet","credSetScaled","rescaleCredibleSet","credSetBool","markBoolean","push","posterior_prob","contrib_fraction","is_member","e","console","error","data","outnames","trans","src","dest","keys","forEach","attr","Layouts","unnamespaced","html","namespace","closable","show","or","hide","and","base","id","fill_opacity","tooltip","match","send","receive","color","unshift","field","scale_function","parameters","field_value","then","type","id_field","x_axis","filters","operator","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip_positioning","title","text","x","style","min_height","height","margin","top","right","bottom","left","inner_border","toolbar","interaction","drag_background_to_pan","scroll_to_zoom","x_linked","data_layers","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","width","responsive_resize","min_region_scale","max_region_scale","panels","use","gwasCredibleSets"],"mappings":";+BACE,IAAIA,EAAmB,GAGvB,SAASC,EAAoBC,GAG5B,GAAGF,EAAiBE,GACnB,OAAOF,EAAiBE,GAAUC,QAGnC,IAAIC,EAASJ,EAAiBE,GAAY,CACzCG,EAAGH,EACHI,GAAG,EACHH,QAAS,IAUV,OANAI,EAAQL,GAAUM,KAAKJ,EAAOD,QAASC,EAAQA,EAAOD,QAASF,GAG/DG,EAAOE,GAAI,EAGJF,EAAOD,QA0Df,OArDAF,EAAoBQ,EAAIF,EAGxBN,EAAoBS,EAAIV,EAGxBC,EAAoBU,EAAI,SAASR,EAASS,EAAMC,GAC3CZ,EAAoBa,EAAEX,EAASS,IAClCG,OAAOC,eAAeb,EAASS,EAAM,CAAEK,YAAY,EAAMC,IAAKL,KAKhEZ,EAAoBkB,EAAI,SAAShB,GACX,oBAAXiB,QAA0BA,OAAOC,aAC1CN,OAAOC,eAAeb,EAASiB,OAAOC,YAAa,CAAEC,MAAO,WAE7DP,OAAOC,eAAeb,EAAS,aAAc,CAAEmB,OAAO,KAQvDrB,EAAoBsB,EAAI,SAASD,EAAOE,GAEvC,GADU,EAAPA,IAAUF,EAAQrB,EAAoBqB,IAC/B,EAAPE,EAAU,OAAOF,EACpB,GAAW,EAAPE,GAA8B,iBAAVF,GAAsBA,GAASA,EAAMG,WAAY,OAAOH,EAChF,IAAII,EAAKX,OAAOY,OAAO,MAGvB,GAFA1B,EAAoBkB,EAAEO,GACtBX,OAAOC,eAAeU,EAAI,UAAW,CAAET,YAAY,EAAMK,MAAOA,IACtD,EAAPE,GAA4B,iBAATF,EAAmB,IAAI,IAAIM,KAAON,EAAOrB,EAAoBU,EAAEe,EAAIE,EAAK,SAASA,GAAO,OAAON,EAAMM,IAAQC,KAAK,KAAMD,IAC9I,OAAOF,GAIRzB,EAAoB6B,EAAI,SAAS1B,GAChC,IAAIS,EAAST,GAAUA,EAAOqB,WAC7B,WAAwB,OAAOrB,EAAgB,SAC/C,WAA8B,OAAOA,GAEtC,OADAH,EAAoBU,EAAEE,EAAQ,IAAKA,GAC5BA,GAIRZ,EAAoBa,EAAI,SAASiB,EAAQC,GAAY,OAAOjB,OAAOkB,UAAUC,eAAe1B,KAAKuB,EAAQC,IAGzG/B,EAAoBkC,EAAI,GAIjBlC,EAAoBA,EAAoBmC,EAAI,I,kCClFrD,kBAaA,SAASC,EAASC,GACd,MAAMC,EAAcD,EAAUE,SAAStB,IAAI,eAuG3CoB,EAAUE,SAASC,IAAI,gBAvFvB,cAA4BF,EACxB,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,OAASjC,OAAOuC,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,IAAKC,GAASA,EAAKH,EAAKpB,OAAOC,OAAOC,aAEnE,IAAKmB,EAAUG,KAAMC,GAAQA,GAAOL,EAAKpB,OAAOQ,wBAG5C,OAAOU,QAAQC,QAAQ,IAG3B,MAAMO,EAAe,GACrB,IACI,MAAMC,EAAS,UAAQC,aAAaP,GAC9BQ,EAAyB,UAAQC,uBAAuBH,GAIxDI,EAAc,UAAQC,gBAAgBH,EAAwBtB,GAC9D0B,EAAgB,UAAQC,mBAAmBH,GAC3CI,EAAc,UAAQC,YAAYL,GAGxC,IAAK,IAAI1E,EAAI,EAAGA,EAAIqD,EAAMM,KAAKC,OAAQ5D,IACnCqE,EAAaW,KAAK,CACdC,eAAgBT,EAAuBxE,GACvCkF,iBAAkBN,EAAc5E,GAChCmF,UAAWL,EAAY9E,KAGjC,MAAOoF,GAELC,QAAQC,MAAMF,GAElB,OAAOvB,QAAQC,QAAQO,GAG3B,iBAAiBkB,EAAMlC,EAAOT,EAAQ4C,EAAUC,GAE5C,GAAIpC,EAAMM,KAAKC,QAAU2B,EAAK3B,OAC1B,IAAK,IAAI5D,EAAI,EAAGA,EAAIuF,EAAK3B,OAAQ5D,IAAK,CAClC,MAAM0F,EAAMH,EAAKvF,GACX2F,EAAOtC,EAAMM,KAAK3D,GACxBU,OAAOkF,KAAKF,GAAKG,SAAQ,SAAUC,GAC/BH,EAAKG,GAAQJ,EAAII,MAI7B,OAAOzC,EAAMM,QAQrB1B,EAAU8D,QAAQ3D,IAAI,UAAW,2BAA4B,WAEzD,MAAMnC,EAAIgC,EAAU8D,QAAQlF,IAAI,UAAW,uBAAwB,CAAEmF,cAAc,IAEnF,OADA/F,EAAEgG,MAAQ,iKACHhG,EAJkD,IAO7DgC,EAAU8D,QAAQ3D,IAAI,UAAW,0BAA2B,CACxD8D,UAAW,CAAE,MAAS,QAAS,QAAW,WAC1CC,UAAU,EACVC,KAAM,CAAEC,GAAI,CAAC,cAAe,aAC5BC,KAAM,CAAEC,IAAK,CAAC,gBAAiB,eAC/BN,KAAM,8TAKVhE,EAAU8D,QAAQ3D,IAAI,aAAc,2BAA4B,WAC5D,MAAMoE,EAAOvE,EAAU8D,QAAQlF,IAAI,aAAc,sBAAuB,CACpEmF,cAAc,EACdS,GAAI,yBACJP,UAAW,CAAE,MAAS,QAAS,QAAW,UAAW,GAAM,MAC3DQ,aAAc,GACdC,QAAS1E,EAAU8D,QAAQlF,IAAI,UAAW,2BAA4B,CAAEmF,cAAc,IACtFpD,OAAQ,CACJ,8BAA+B,+BAC/B,iCAAkC,kDAClC,iCACA,uCAAwC,yCACxC,kCACA,yBAA0B,6BAE9BgE,MAAO,CAAEC,KAAM,8BAA+BC,QAAS,iCAU3D,OARAN,EAAKO,MAAMC,QAAQ,CACfC,MAAO,cACPC,eAAgB,KAChBC,WAAY,CACRC,aAAa,EACbC,KAAM,aAGPb,EAzBqD,IA4BhEvE,EAAU8D,QAAQ3D,IAAI,aAAc,0BAA2B,CAC3D8D,UAAW,CAAE,MAAS,QAAS,QAAW,WAC1CO,GAAI,wBACJa,KAAM,mBACNC,SAAU,8BACVC,OAAQ,CACJP,MAAO,gCAEXF,MAAO,CACH,CACIE,MAAO,cACPC,eAAgB,KAChBC,WAAY,CACRC,aAAa,EACbC,KAAM,YAGd,WAEJzE,OAAQ,CAAC,8BAA+B,+BAAgC,iCAAkC,uCAAwC,yCAA0C,mCAC5LgE,MAAO,CAAEC,KAAM,8BAA+BC,QAAS,+BACvDW,QAAS,CAEL,CAAER,MAAO,kCAAmCS,SAAU,IAAKzG,OAAO,IAEtE0G,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,QAAS1E,EAAU8D,QAAQlF,IAAI,UAAW,0BAA2B,CAAEmF,cAAc,IACrFmC,oBAAqB,QAGzBlG,EAAU8D,QAAQ3D,IAAI,QAAS,0BAA2B,CACtDqE,GAAI,wBACJ2B,MAAO,CAAEC,KAAM,2BAA4BC,EAAG,GAAIC,MAAO,CAAE,YAAa,SACxEC,WAAY,GACZC,OAAQ,GACRC,OAAQ,CAAEC,IAAK,GAAIC,MAAO,GAAIC,OAAQ,EAAGC,KAAM,IAC/CC,aAAc,qBACdC,QAAS/G,EAAU8D,QAAQlF,IAAI,UAAW,iBAAkB,CAAEmF,cAAc,IAC5EiD,YAAa,CACTC,wBAAwB,EACxBC,gBAAgB,EAChBC,UAAU,GAEdC,YAAa,CACTpH,EAAU8D,QAAQlF,IAAI,aAAc,0BAA2B,CAAEmF,cAAc,OAIvF/D,EAAU8D,QAAQ3D,IAAI,QAAS,2BAA4B,WACvD,MAAMnC,EAAIgC,EAAU8D,QAAQlF,IAAI,QAAS,cAAe,CACpDmF,cAAc,EACdS,GAAI,0BACJP,UAAW,CAAE,MAAS,QAAS,QAAW,WAC1CmD,YAAa,CACTpH,EAAU8D,QAAQlF,IAAI,aAAc,eAAgB,CAAEmF,cAAc,IACpE/D,EAAU8D,QAAQlF,IAAI,aAAc,cAAe,CAAEmF,cAAc,IACnE/D,EAAU8D,QAAQlF,IAAI,aAAc,2BAA4B,CAAEmF,cAAc,OAqGxF,OAjGA/F,EAAE+I,QAAQM,QAAQtE,KACd,CACIsC,KAAM,kBACNiC,SAAU,QACVxC,MAAO,OAEPyC,YAAa,qBACbC,aAAc,uCACdC,WAAY,yBACZC,4BAA6B,mCAE7BC,QAAS,CACL,CAEIC,aAAc,6BACdC,QAAS,CACLC,YAAa,SACbC,WAAY,GACZjD,MAAO,CACHE,MAAO,kCACPC,eAAgB,KAChBC,WAAY,CACRC,aAAa,EACbC,KAAM,UACN4C,KAAM,YAGdC,OAAQ,CACJ,CACIC,MAAO,SACPpD,MAAO,UACPqD,KAAM,GACNC,MAAO,kBACPC,MAAO,yBAEX,CACIH,MAAO,SACPpD,MAAO,UACPqD,KAAM,GACNC,MAAO,sBACPC,MAAO,4BAKvB,CAEIT,aAAc,8CACdC,QAAS,CACLC,YAAa,SACbC,WAAY,GACZjD,MAAO,CACH,CACIE,MAAO,yCACPC,eAAgB,KAChBC,WAAY,CACRC,YAAa,EACbC,KAAM,YAGd,CACIH,eAAgB,cAChBD,MAAO,yCACPE,WAAY,CACRoD,OAAQ,CAAC,EAAG,GACZC,OAAQ,CAAC,UAAW,cAIhCN,OAAQ,CACJ,CACIC,MAAO,SACPpD,MAAO,UACPqD,KAAM,GACNC,MAAO,kBACPC,MAAO,yBAEX,CACIH,MAAO,SACPpD,MAAO,UACPqD,KAAM,GACNC,MAAO,oBACPC,MAAO,yBAEX,CACIH,MAAO,SACPpD,MAAO,UACPqD,KAAM,GACNC,MAAO,oBACPC,MAAO,+BAQ5BrK,EA7GgD,IAgH3DgC,EAAU8D,QAAQ3D,IAAI,OAAQ,2BAA4B,CACtDgB,MAAO,GACPqH,MAAO,IACPhC,OAAQ,IACRiC,mBAAmB,EACnBC,iBAAkB,IAClBC,iBAAkB,IAClB5B,QAAS/G,EAAU8D,QAAQlF,IAAI,UAAW,uBAAwB,CAAEmF,cAAc,IAClF6E,OAAQ,CACJ5I,EAAU8D,QAAQlF,IAAI,QAAS,2BAA4B,CAAEmF,cAAc,IAC3E/D,EAAU8D,QAAQlF,IAAI,QAAS,0BAA2B,CAAEmF,cAAc,IAC1E/D,EAAU8D,QAAQlF,IAAI,QAAS,QAAS,CAAEmF,cAAc,OAO3C,oBAAd/D,WAGPA,UAAU6I,IAAI9I,GAIH,a,gBC3WfjC,EAAOD,QAAUiL,oB","file":"ext/lz-credible-sets.min.js","sourcesContent":[" \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, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\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\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 10);\n","/**\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 The page must incorporate and load all libraries before this file can be used, including:\n - Vendor assets\n - LocusZoom\n - gwas-credible-sets (available via NPM or a related CDN)\n @module\n*/\n\nimport {marking, scoring} from 'gwas-credible-sets';\n\nfunction install (LocusZoom) {\n const BaseAdapter = LocusZoom.Adapters.get('BaseAdapter');\n /**\n * Custom data source that calculates the 95% credible set based on provided 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.\n *\n * @param {Object} init.params\n * @param {Object} init.params.fields\n * @param {String} init.params.fields.log_pvalue The name of the field containing -log10 pvalue information\n * @param {Number} [init.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} [init.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 class CredibleSetLZ extends BaseAdapter {\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\n LocusZoom.Adapters.add('CredibleSetLZ', CredibleSetLZ);\n\n // Add related layouts to the central global registry\n LocusZoom.Layouts.add('tooltip', 'association_credible_set', 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', 'annotation_credible_set', {\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\n LocusZoom.Layouts.add('data_layer', 'association_credible_set', 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\n LocusZoom.Layouts.add('data_layer', 'annotation_credible_set', {\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\n LocusZoom.Layouts.add('panel', 'annotation_credible_set', {\n id: 'annotationcredibleset',\n title: { text: 'SNPs in 95% credible set', x: 50, style: { 'font-size': '14px' } },\n min_height: 45,\n height: 45,\n margin: { top: 25, right: 50, bottom: 0, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: LocusZoom.Layouts.get('toolbar', 'standard_panel', { unnamespaced: true }),\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\n LocusZoom.Layouts.add('panel', 'association_credible_set', 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\n LocusZoom.Layouts.add('plot', 'association_credible_set', {\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\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","module.exports = gwasCredibleSets;"],"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 f5867644..23e58e81 100644 --- a/dist/ext/lz-dynamic-urls.min.js +++ b/dist/ext/lz-dynamic-urls.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.13.0-beta.3 */ +/*! Locuszoom 0.13.0-beta.4 */ var LzDynamicUrls=function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=6)}({6:function(t,e,n){"use strict";function r(t){const e={};if(t){const n=("?"===t[0]?t.substr(1):t).split("&");for(let t=0;tt[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 a=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=i.symbol().size((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e)).type((t,e)=>{const a=this.resolveScalableParameter(this.layout.point_shape,t,e),s="symbol"+(a.charAt(0).toUpperCase()+a.slice(1));return i[s]||null});a.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(a).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),a.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 a=this.layout.confidence_intervals;if(a&&this.layout.fields.includes(a.start_field)&&this.layout.fields.includes(a.end_field)){const e=t=>+t[a.start_field],s=t=>+t[a.end_field];return[i.min(t,e),i.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(s),e.default=s}}).default; //# sourceMappingURL=lz-forest-track.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 32f833b6..ab62ed58 100644 --- a/dist/ext/lz-intervals-track.min.js +++ b/dist/ext/lz-intervals-track.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.13.0-beta.3 */ -var LzIntervalsTrack=function(t){var e={};function a(r){if(e[r])return e[r].exports;var s=e[r]={i:r,l:!1,exports:{}};return t[r].call(s.exports,s,s.exports,a),s.l=!0,s.exports}return a.m=t,a.c=e,a.d=function(t,e,r){a.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},a.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},a.t=function(t,e){if(1&e&&(t=a(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(a.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var s in t)a.d(r,s,function(e){return t[e]}.bind(null,s));return r},a.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return a.d(e,"a",e),e},a.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},a.p="",a(a.s=9)}({0:function(t,e){t.exports=d3},9:function(t,e,a){"use strict";a.r(e);var r=a(0);const s=Symbol.for("lzXCS"),i=Symbol.for("lzYCS"),n=Symbol.for("lzXCE"),l=Symbol.for("lzYCE");function o(t){const e=t.Adapters.get("BaseApiAdapter"),a=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",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},u={id:"intervals",width:1e3,height:50,min_width:500,min_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]},_={state:{},width:800,height:550,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",{unnamespaced:!0,width:800,proportional_height:225/570}),Object.assign({unnamespaced:!0,proportional_height:120/570},u),t.Layouts.get("panel","genes",{unnamespaced:!0,width:800,proportional_height:225/570})]};t.Adapters.add("IntervalLZ",class extends e{getURL(t,e,a){const r=`?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}${r}`}}),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 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[s]=e(t[a]),t[n]=e(t[r]),t[i]=g*this.getTrackHeight()+o,t[l]=t[i]+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,n=e.legend&&e.legend.length;if(!!i^!!n)throw new Error("To use a manually specified color scheme, both color and legend options must be set.");const l=e.color.find((function(t){return t.scale_function&&"to_rgb"===t.scale_function})),o=l&&l.field,c=this._generateCategoriesFromData(this.data,o);if(!i&&!n){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 a=this._applyFilters(e);this._statusnodes_group.selectAll("rect").remove();const l=this._statusnodes_group.selectAll("rect").data(r.range(t.length));if(this.layout.split_tracks){const t=this.getTrackHeight();l.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(l).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)}l.exit().remove();const o=this._datanodes_group.selectAll("rect").data(a,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[i]).attr("width",t=>t[n]-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[n],y_min:t.data[i],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=>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",g),t.Layouts.add("data_layer","intervals",h),t.Layouts.add("panel","intervals",u),t.Layouts.add("plot","interval_association",_),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 a(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(o),e.default=o}}).default; +/*! Locuszoom 0.13.0-beta.4 */ +var LzIntervalsTrack=function(t){var e={};function a(r){if(e[r])return e[r].exports;var s=e[r]={i:r,l:!1,exports:{}};return t[r].call(s.exports,s,s.exports,a),s.l=!0,s.exports}return a.m=t,a.c=e,a.d=function(t,e,r){a.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},a.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},a.t=function(t,e){if(1&e&&(t=a(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(a.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var s in t)a.d(r,s,function(e){return t[e]}.bind(null,s));return r},a.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return a.d(e,"a",e),e},a.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},a.p="",a(a.s=9)}({0:function(t,e){t.exports=d3},9:function(t,e,a){"use strict";a.r(e);var r=a(0);const s=Symbol.for("lzXCS"),i=Symbol.for("lzYCS"),n=Symbol.for("lzXCE"),l=Symbol.for("lzYCE");function o(t){const e=t.Adapters.get("BaseApiAdapter"),a=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}}"},u={namespace:{intervals:"intervals"},id:"intervals",type:"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",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:[u]},h={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 r=`?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}${r}`}}),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 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[s]=e(t[a]),t[n]=e(t[r]),t[i]=g*this.getTrackHeight()+o,t[l]=t[i]+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,n=e.legend&&e.legend.length;if(!!i^!!n)throw new Error("To use a manually specified color scheme, both color and legend options must be set.");const l=e.color.find((function(t){return t.scale_function&&"to_rgb"===t.scale_function})),o=l&&l.field,c=this._generateCategoriesFromData(this.data,o);if(!i&&!n){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 a=this._applyFilters(e);this._statusnodes_group.selectAll("rect").remove();const l=this._statusnodes_group.selectAll("rect").data(r.range(t.length));if(this.layout.split_tracks){const t=this.getTrackHeight();l.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(l).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)}l.exit().remove();const o=this._datanodes_group.selectAll("rect").data(a,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[i]).attr("width",t=>t[n]-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[n],y_min:t.data[i],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=>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",g),t.Layouts.add("data_layer","intervals",u),t.Layouts.add("panel","intervals",_),t.Layouts.add("plot","interval_association",h),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 a(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(o),e.default=o}}).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 60f849b6..d1b418d5 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]/external \"d3\"","webpack://[name]/./esm/ext/lz-intervals-track.js"],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","d3","XCS","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","fields","id_field","field","scale_function","parameters","categories","values","null_value","legend","behaviors","onmouseover","action","status","onmouseout","onclick","exclusive","onshiftclick","tooltip","intervals_panel_layout","width","height","min_width","min_height","margin","top","right","bottom","left","toolbar","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","responsive_resize","min_region_scale","max_region_scale","panels","proportional_height","assign","add","chain","query","header","bedtracksource","this","params","source","chr","end","start","url","layout","merge","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","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","getElementStatusNodeId","cliparea","exit","data_nodes","getElementId","resolveScalableParameter","applyBehaviors","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","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":[" \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, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\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\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 9);\n","module.exports = d3;","/**\nInterval annotation track (for chromatin state, etc). Useful for BED file data with non-overlapping intervals.\nThis is not part of the core LocusZoom library, but can be included as a standalone file.\n\nThe page must incorporate and load all libraries before this file can be used, including:\n - Vendor assets\n - LocusZoom\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 * Data Source for Interval Annotation Data (e.g. BED Tracks), as fetched from the LocusZoom API server (or compatible)\n * @public\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 * Button to toggle split tracks\n */\n class ToggleSplitTracks extends _BaseWidget {\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 * 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 * @function 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 */\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\n /**\n * Intervals Data Layer\n * Implements a data layer that will render interval annotation tracks (intervals must provide start and end values)\n */\n const BaseLayer = LocusZoom.DataLayers.get('BaseDataLayer');\n class LzIntervalsTrack extends BaseLayer {\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 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 const intervals_layer_layout = {\n namespace: { 'intervals': 'intervals' },\n id: 'intervals',\n type: '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 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 const intervals_panel_layout = {\n id: 'intervals',\n width: 1000,\n height: 50,\n min_width: 500,\n min_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 const intervals_plot_layout = {\n state: {},\n width: 800,\n height: 550,\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 unnamespaced: true,\n width: 800,\n proportional_height: (225 / 570),\n }),\n Object.assign(\n { unnamespaced: true, proportional_height: (120 / 570) },\n intervals_panel_layout\n ),\n LocusZoom.Layouts.get('panel', 'genes', { unnamespaced: true, width: 800, proportional_height: (225 / 570) }),\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]/external \"d3\"","webpack://[name]/./esm/ext/lz-intervals-track.js"],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","d3","XCS","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","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","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","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","getElementStatusNodeId","cliparea","exit","data_nodes","getElementId","resolveScalableParameter","applyBehaviors","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","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":[" \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, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\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\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 9);\n","module.exports = d3;","/**\nInterval annotation track (for chromatin state, etc). Useful for BED file data with non-overlapping intervals.\nThis is not part of the core LocusZoom library, but can be included as a standalone file.\n\nThe page must incorporate and load all libraries before this file can be used, including:\n - Vendor assets\n - LocusZoom\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 * Data Source for Interval Annotation Data (e.g. BED Tracks), as fetched from the LocusZoom API server (or compatible)\n * @public\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 * Button to toggle split tracks\n */\n class ToggleSplitTracks extends _BaseWidget {\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 * 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 * @function 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 */\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\n /**\n * Intervals Data Layer\n * Implements a data layer that will render interval annotation tracks (intervals must provide start and end values)\n */\n const BaseLayer = LocusZoom.DataLayers.get('BaseDataLayer');\n class LzIntervalsTrack extends BaseLayer {\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 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 const intervals_layer_layout = {\n namespace: { 'intervals': 'intervals' },\n id: 'intervals',\n type: '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 const intervals_panel_layout = {\n id: '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 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 diff --git a/dist/ext/lz-tabix-source.min.js b/dist/ext/lz-tabix-source.min.js index 1743b402..f0c75197 100644 --- a/dist/ext/lz-tabix-source.min.js +++ b/dist/ext/lz-tabix-source.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.13.0-beta.3 */ -var LzTabix=function(e){var r={};function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}return t.m=e,t.c=r,t.d=function(e,r,n){t.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:n})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,r){if(1&r&&(e=t(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(t.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var o in e)t.d(n,o,function(r){return e[r]}.bind(null,o));return n},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,"a",r),r},t.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},t.p="",t(t.s=11)}({11:function(e,r,t){"use strict";t.r(r);var n=t(5),o=t.n(n);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=o.a.urlReader(this.url_data,this.url_tbi).catch((function(){throw new Error("Failed to create a tabix reader from the provided URL")}))}getCacheKey(e){return[e.chr,e.start,e.end,this._overfetch].join("_")}fetchRequest(e){return new Promise((r,t)=>{const n=e.start,o=e.end,i=this._overfetch*(o-n),a=e.start-i,u=e.end+i;this._reader_promise.then(n=>{n.fetch(e.chr,a,u,(function(e,n){n&&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),r.default=i},5:function(e,r){e.exports=tabix}}).default; +/*! Locuszoom 0.13.0-beta.4 */ +var LzTabix=function(e){var r={};function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}return t.m=e,t.c=r,t.d=function(e,r,n){t.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:n})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,r){if(1&r&&(e=t(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(t.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var o in e)t.d(n,o,function(r){return e[r]}.bind(null,o));return n},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,"a",r),r},t.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},t.p="",t(t.s=11)}({11:function(e,r,t){"use strict";t.r(r);var n=t(5),o=t.n(n);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=o.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 n=e.start,o=e.end,i=this._overfetch*(o-n),a=e.start-i,u=e.end+i;this._reader_promise.then(n=>{n.fetch(e.chr,a,u,(function(e,n){n&&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),r.default=i},5:function(e,r){e.exports=tabix}}).default; //# sourceMappingURL=lz-tabix-source.min.js.map \ No newline at end of file diff --git a/dist/ext/lz-tabix-source.min.js.map b/dist/ext/lz-tabix-source.min.js.map index 2ba87b41..c73415ef 100644 --- a/dist/ext/lz-tabix-source.min.js.map +++ b/dist/ext/lz-tabix-source.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/./esm/ext/lz-tabix-source.js","webpack://[name]/external \"tabix\""],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","install","LocusZoom","BaseAdapter","Adapters","add","init","parser_func","url_data","Error","this","parser","url_tbi","params","_overfetch","overfetch","_reader_promise","urlReader","catch","state","chr","start","end","join","Promise","resolve","reject","region_start","region_end","extra_amount","then","reader","fetch","data","err","map","use","tabix"],"mappings":"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","file":"ext/lz-tabix-source.min.js","sourcesContent":[" \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, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\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\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 11);\n","/**\nA datasource that fetches data from a remote Tabix file, instead of a RESTful API.\nRequires a generic user-specified parser.\n\nThe 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 @module\n*/\nimport tabix from 'tabix-reader';\n\n\nfunction install(LocusZoom) {\n const BaseAdapter = LocusZoom.Adapters.get('BaseAdapter');\n\n /**\n * Custom data source that loads data from a remote Tabix file (if the file host has been configured with proper\n * CORS and Range header support- most hosts do not do that by default).\n *\n * @param {Object} init.params\n * @param {Object} init.params.fields\n * @param {String} init.params.fields.log_pvalue The name of the field containing pvalue information\n * @param {Number} [init.params.threshold=0.95] The credible set threshold (eg 95%)\n */\n class TabixUrlSource extends BaseAdapter {\n /**\n * @param {Object} init\n * @param {function} init.parser_func A function that parses a single line of text and returns (usually) a\n * structured object of data fields\n * @param {string} init.url_data The URL for the bgzipped and tabix-indexed file\n * @param {string} [init.url_tbi] The URL for the tabix index. Defaults to `url_data` + '.tbi'\n * @param {Object} [init.params]\n * @param {number} [init.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.\n */\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 getCacheKey(state /*, chain, fields*/) {\n // In generic form, Tabix queries are based on chr, start, and end. The cache is thus controlled by the query,\n // not the URL\n return [state.chr, state.start, state.end, this._overfetch].join('_');\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","module.exports = tabix;"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/./esm/ext/lz-tabix-source.js","webpack://[name]/external \"tabix\""],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","install","LocusZoom","BaseAdapter","Adapters","add","init","parser_func","url_data","Error","this","parser","url_tbi","params","_overfetch","overfetch","_reader_promise","urlReader","catch","state","Promise","resolve","reject","region_start","start","region_end","end","extra_amount","then","reader","fetch","chr","data","err","map","use","tabix"],"mappings":"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","file":"ext/lz-tabix-source.min.js","sourcesContent":[" \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, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\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\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 11);\n","/**\nA datasource that fetches data from a remote Tabix file, instead of a RESTful API.\nRequires a generic user-specified parser.\n\nThe 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 @module\n*/\nimport tabix from 'tabix-reader';\n\n\nfunction install(LocusZoom) {\n const BaseAdapter = LocusZoom.Adapters.get('BaseAdapter');\n\n /**\n * Custom data source that loads data from a remote Tabix file (if the file host has been configured with proper\n * CORS and Range header support- most hosts do not do that by default).\n *\n * @param {Object} init.params\n * @param {Object} init.params.fields\n * @param {String} init.params.fields.log_pvalue The name of the field containing pvalue information\n * @param {Number} [init.params.threshold=0.95] The credible set threshold (eg 95%)\n */\n class TabixUrlSource extends BaseAdapter {\n /**\n * @param {Object} init\n * @param {function} init.parser_func A function that parses a single line of text and returns (usually) a\n * structured object of data fields\n * @param {string} init.url_data The URL for the bgzipped and tabix-indexed file\n * @param {string} [init.url_tbi] The URL for the tabix index. Defaults to `url_data` + '.tbi'\n * @param {Object} [init.params]\n * @param {number} [init.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.\n */\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","module.exports = tabix;"],"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 af09d4c3..c16956f1 100644 --- a/dist/ext/lz-widget-addons.min.js +++ b/dist/ext/lz-widget-addons.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.13.0-beta.3 */ +/*! Locuszoom 0.13.0-beta.4 */ var LzWidgetAddons=function(t){var e={};function o(a){if(e[a])return e[a].exports;var n=e[a]={i:a,l:!1,exports:{}};return t[a].call(n.exports,n,n.exports,o),n.l=!0,n.exports}return o.m=t,o.c=e,o.d=function(t,e,a){o.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:a})},o.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},o.t=function(t,e){if(1&e&&(t=o(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var a=Object.create(null);if(o.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)o.d(a,n,function(e){return t[e]}.bind(null,n));return a},o.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(e,"a",e),e},o.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},o.p="",o(o.s=7)}([function(t,e){t.exports=d3},function(t,e,o){"use strict";o.d(e,"a",(function(){return r})),o.d(e,"b",(function(){return i})),o.d(e,"c",(function(){return s})),o.d(e,"d",(function(){return u}));var a=o(0);const n=Math.sqrt(3),l={draw(t,e){const o=-Math.sqrt(e/(3*n));t.moveTo(0,2*-o),t.lineTo(-n*o,o),t.lineTo(n*o,o),t.closePath()}};function r(t,e,o){if(e?"string"==typeof e&&(e={default:e}):e={default:""},"string"==typeof t){const a=/\{\{namespace(\[[A-Za-z_0-9]+\]|)\}\}/g;let n,l,r,s;const i=[];for(;null!==(n=a.exec(t));)l=n[0],r=n[1].length?n[1].replace(/(\[|\])/g,""):null,s=o,null!=e&&"object"==typeof e&&void 0!==e[r]&&(s=e[r]+(e[r].length?":":"")),i.push({base:l,namespace:s});for(let e in i)t=t.replace(i[e].base,i[e].namespace)}else if("object"==typeof t&&null!=t){if(void 0!==t.namespace){e=s(e,"string"==typeof t.namespace?{default:t.namespace}:t.namespace)}let a,n;for(let l in t)"namespace"!==l&&(a=r(t[l],e,o),n=r(l,e,o),l!==n&&delete t[l],t[n]=a)}return t}function s(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 o in e){if(!Object.prototype.hasOwnProperty.call(e,o))continue;let a=null===t[o]?"undefined":typeof t[o],n=typeof e[o];if("object"===a&&Array.isArray(t[o])&&(a="array"),"object"===n&&Array.isArray(e[o])&&(n="array"),"function"===a||"function"===n)throw new Error("LocusZoom.Layouts.merge encountered an unsupported property type");"undefined"!==a?"object"!==a||"object"!==n||(t[o]=s(t[o],e[o])):t[o]=i(e[o])}return t}function i(t){return JSON.parse(JSON.stringify(t))}function u(t){if(!t)return null;if("triangledown"===t)return l;const e="symbol"+(t.charAt(0).toUpperCase()+t.slice(1));return a[e]||null}},,,,,,function(t,e,o){"use strict";o.r(e);var a=o(1);const n=["highlight","select","fade","hide"],l=["highlighted","selected","faded","hidden"],r=["unhighlight","deselect","unfade","show"];function s(t){const e=t.Widgets.get("_Button"),o=t.Widgets.get("BaseWidget");const s=function(){const e=t.Layouts.get("tooltip","standard_association",{unnamespaced:!0});return e.html+='Condition on Variant
',e}(),i=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 o{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,o=Object(a.b)(t);"object"==typeof t&&"string"!=typeof o.html&&(o.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(),n=e.append("tr");n.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("×"),n.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 o{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 a=this.parent_panel.data_layers[e],s="string"!=typeof a.layout.name?a.id:a.layout.name,i=t.append("tr");i.append("td").html(s),this.layout.statuses.forEach(t=>{const e=l.indexOf(t),o=n[e];let s,u,d;a.global_statuses[t]?(s=r[e],u=`un${o}AllElements`,d="-highlighted"):(s=n[e],u=o+"AllElements",d=""),i.append("td").append("a").attr("class",`lz-toolbar-button lz-toolbar-button-${this.layout.color}${d}`).style("margin-left","0em").on("click",()=>{a[u](),this.button.menu.populate()}).html(s)});const u=0===o,d=o===this.parent_panel.data_layer_ids_by_z_index.length-1,p=i.append("td");p.append("a").attr("class",`lz-toolbar-button lz-toolbar-button-group-start lz-toolbar-button-${this.layout.color}${d?"-disabled":""}`).style("margin-left","0em").on("click",()=>{a.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}${u?"-disabled":""}`).style("margin-left","0em").on("click",()=>{a.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 ${s} layer? This cannot be undone.`)&&a.parent.removeDataLayer(e),this.button.menu.populate())).html("×").attr("title","Remove layer")}),this}),this.button.show()),this}}),t.Layouts.add("tooltip","covariates_model_association",s),t.Layouts.add("toolbar","covariates_model_plot",i)}"undefined"!=typeof LocusZoom&&LocusZoom.use(s),e.default=s}]).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 93765b29..de6d2525 100644 --- a/dist/locuszoom.app.min.js +++ b/dist/locuszoom.app.min.js @@ -1,3 +1,3 @@ -/*! Locuszoom 0.13.0-beta.3 */ -var LocusZoom=function(t){var e={};function i(s){if(e[s])return e[s].exports;var a=e[s]={i:s,l:!1,exports:{}};return t[s].call(a.exports,a,a.exports,i),a.l=!0,a.exports}return i.m=t,i.c=e,i.d=function(t,e,s){i.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:s})},i.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var s=Object.create(null);if(i.r(s),Object.defineProperty(s,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var a in t)i.d(s,a,function(e){return t[e]}.bind(null,a));return s},i.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return i.d(e,"a",e),e},i.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},i.p="",i(i.s=13)}({0:function(t,e){t.exports=d3},1:function(t,e,i){"use strict";i.d(e,"a",(function(){return o})),i.d(e,"b",(function(){return l})),i.d(e,"c",(function(){return r})),i.d(e,"d",(function(){return h}));var s=i(0);const a=Math.sqrt(3),n={draw(t,e){const i=-Math.sqrt(e/(3*a));t.moveTo(0,2*-i),t.lineTo(-a*i,i),t.lineTo(a*i,i),t.closePath()}};function o(t,e,i){if(e?"string"==typeof e&&(e={default:e}):e={default:""},"string"==typeof t){const s=/\{\{namespace(\[[A-Za-z_0-9]+\]|)\}\}/g;let a,n,o,r;const l=[];for(;null!==(a=s.exec(t));)n=a[0],o=a[1].length?a[1].replace(/(\[|\])/g,""):null,r=i,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=r(e,"string"==typeof t.namespace?{default:t.namespace}:t.namespace)}let s,a;for(let n in t)"namespace"!==n&&(s=o(t[n],e,i),a=o(n,e,i),n!==a&&delete t[n],t[a]=s)}return t}function r(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 i in e){if(!Object.prototype.hasOwnProperty.call(e,i))continue;let s=null===t[i]?"undefined":typeof t[i],a=typeof e[i];if("object"===s&&Array.isArray(t[i])&&(s="array"),"object"===a&&Array.isArray(e[i])&&(a="array"),"function"===s||"function"===a)throw new Error("LocusZoom.Layouts.merge encountered an unsupported property type");"undefined"!==s?"object"!==s||"object"!==a||(t[i]=r(t[i],e[i])):t[i]=l(e[i])}return t}function l(t){return JSON.parse(JSON.stringify(t))}function h(t){if(!t)return null;if("triangledown"===t)return n;const e="symbol"+(t.charAt(0).toUpperCase()+t.slice(1));return s[e]||null}},13:function(t,e,i){"use strict";i.r(e);var s={};i.r(s),i.d(s,"log10",(function(){return g})),i.d(s,"neglog10",(function(){return y})),i.d(s,"logtoscinotation",(function(){return m})),i.d(s,"scinotation",(function(){return f})),i.d(s,"htmlescape",(function(){return b})),i.d(s,"urlencode",(function(){return x}));var a={};i.r(a),i.d(a,"BaseWidget",(function(){return S})),i.d(a,"_Button",(function(){return j})),i.d(a,"dimensions",(function(){return T})),i.d(a,"display_options",(function(){return H})),i.d(a,"download",(function(){return P})),i.d(a,"download_png",(function(){return D})),i.d(a,"filter_field",(function(){return L})),i.d(a,"menu",(function(){return U})),i.d(a,"move_panel_down",(function(){return C})),i.d(a,"move_panel_up",(function(){return I})),i.d(a,"region_scale",(function(){return A})),i.d(a,"resize_to_data",(function(){return q})),i.d(a,"set_state",(function(){return Z})),i.d(a,"shift_region",(function(){return B})),i.d(a,"remove_panel",(function(){return R})),i.d(a,"title",(function(){return $})),i.d(a,"toggle_legend",(function(){return G})),i.d(a,"zoom_region",(function(){return F}));var n={};i.r(n),i.d(n,"categorical_bin",(function(){return rt})),i.d(n,"if_value",(function(){return nt})),i.d(n,"interpolate",(function(){return ht})),i.d(n,"numerical_bin",(function(){return ot})),i.d(n,"ordinal_cycle",(function(){return lt}));var o={};i.r(o),i.d(o,"BaseDataLayer",(function(){return pt})),i.d(o,"annotation_track",(function(){return gt})),i.d(o,"arcs",(function(){return mt})),i.d(o,"genes",(function(){return bt})),i.d(o,"line",(function(){return vt})),i.d(o,"orthogonal_line",(function(){return zt})),i.d(o,"scatter",(function(){return Et})),i.d(o,"category_scatter",(function(){return Mt}));var r={};i.r(r),i.d(r,"tooltip",(function(){return ne})),i.d(r,"toolbar_widgets",(function(){return oe})),i.d(r,"toolbar",(function(){return re})),i.d(r,"data_layer",(function(){return le})),i.d(r,"panel",(function(){return he})),i.d(r,"plot",(function(){return ce}));class l{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,i=!1){if(!i&&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 h extends l{create(t,...e){return new(this.get(t))(...e)}extend(t,e,i){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 s=this.get(t);class a extends s{}return Object.assign(a.prototype,i,s),this.add(e,a),a}}var c=i(2);const d=new h;for(let[t,e]of Object.entries(c))d.add(t,e);d.add("StaticJSON",c.StaticSource),d.add("LDLZ2",c.LDServer);var u=d,p=i(0);const _={verbs:["highlight","select","fade","hide"],adjectives:["highlighted","selected","faded","hidden"]};function g(t){return isNaN(t)||t<=0?null:Math.log(t)/Math.LN10}function y(t){return isNaN(t)||t<=0?null:-Math.log(t)/Math.LN10}function m(t){if(isNaN(t))return"NaN";if(0===t)return"1";const e=Math.ceil(t),i=e-t,s=Math.pow(10,i);return 1===e?(s/10).toFixed(4):2===e?(s/100).toFixed(3):`${s.toFixed(2)} × 10^-${e}`}function f(t){if(isNaN(t))return"NaN";if(0===t)return"0";const e=Math.abs(t);let i;return i=e>1?Math.ceil(Math.log(e)/Math.LN10):Math.floor(Math.log(e)/Math.LN10),Math.abs(i)<=3?t.toFixed(3):t.toExponential(2).replace("+","").replace("e"," × 10^")}function b(t){return t?(t=""+t).replace(/['"<>&`]/g,(function(t){switch(t){case"'":return"'";case'"':return""";case"<":return"<";case">":return">";case"&":return"&";case"`":return"`"}})):""}function x(t){return encodeURIComponent(t)}const v=new class extends l{_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(s))v.add(t,e);var w=v;class z{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]=w.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 i=null;void 0!==t[`${this.namespace}:${this.name}`]?i=t[`${this.namespace}:${this.name}`]:void 0!==t[this.name]?i=t[this.name]:e&&void 0!==e[this.full_name]&&(i=e[this.full_name]),t[this.full_name]=this._applyTransformations(i)}return t[this.full_name]}}var k=i(1);var E=class{constructor(t){this._sources=t}__split_requests(t){var e={},i=/^(?:([^:]+):)?([^:|]*)(\|.+)*$/;return t.forEach((function(t){var s=i.exec(t),a=s[1]||"base",n=s[2],o=w.get(s[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 i=this.__split_requests(e),s=Object.keys(i).map(e=>{if(!this._sources.get(e))throw new Error(`Datasource for namespace ${e} not found`);return this._sources.get(e).getData(t,i[e].fields,i[e].outnames,i[e].trans)}),a=Promise.resolve({header:{},body:[],discrete:{}}),n=0;n(this.curtain.showing||(this.curtain.selector=p.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&&O(this.curtain.selector,e);const i=this._getPageOrigin();return this.curtain.selector.style("top",i.y+"px").style("left",i.x+"px").style("width",this.layout.width+"px").style("height",this.layout.height+"px"),this.curtain.content_selector.style("max-width",this.layout.width-40+"px").style("max-height",this.layout.height-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 N(){return{showing:!1,selector:null,content_selector:null,progress_selector:null,cancel_selector:null,show:t=>(this.loader.showing||(this.loader.selector=p.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 i=this._getPageOrigin(),s=this.loader.selector.node().getBoundingClientRect();return this.loader.selector.style("top",i.y+this.layout.height-s.height-6+"px").style("left",i.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 O(t,e){e=e||{};for(let[i,s]of Object.entries(e))t.style(i,s)}class S{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&&O(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 j{constructor(t){if(!(t instanceof S))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=p.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,i=this.parent_plot.getContainerOffset(),s=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+s.height+6,l=Math.max(t.x+this.parent_svg.layout.width-n.width-3,t.x+3)):(r=a.bottom+e+3-i.top,l=Math.max(a.left+a.width-n.width-i.left,t.x+3));const h=Math.max(this.parent_svg.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,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(O,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 $ extends S{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 T extends S{update(){const t=this.parent_plot.layout.width.toString().includes(".")?this.parent_plot.layout.width.toFixed(2):this.parent_plot.layout.width,e=this.parent_plot.layout.height.toString().includes(".")?this.parent_plot.layout.height.toFixed(2):this.parent_plot.layout.height;return this.selector.html(`${t}px × ${e}px`),this.layout.class&&this.selector.attr("class",this.layout.class),this.layout.style&&O(this.selector,this.layout.style),this}}class A extends S{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(it(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&&O(this.selector,this.layout.style),this}}class L extends S{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._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}}_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 i;return()=>{clearTimeout(i),i=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 P extends S{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"}update(){return this.button||(this.button=new j(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)),this}_getCSS(t){const e=/^svg\.lz-locuszoom\s*/;let i="";for(let t=0;t{let e=this.parent_plot.svg.node().cloneNode(!0);e.setAttribute("xlink","http://www.w3.org/1999/xlink"),e=p.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*+p.select(this).attr("dy").substring(-2).slice(0,-2);p.select(this).attr("dy",t)}));const i=new XMLSerializer;e=e.node();const[s,a]=this._getDimensions();e.setAttribute("width",s),e.setAttribute("height",a),this._appendCSS(this._getCSS(e),e),t(i.serializeToString(e))})}_getBlobUrl(){return this._generateSVG().then(t=>{const e=new Blob([t],{type:"image/svg+xml"});return URL.createObjectURL(e)})}}class D extends P{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"}_getBlobUrl(){return super._getBlobUrl().then(t=>{const e=document.createElement("canvas"),i=e.getContext("2d"),[s,a]=this._getDimensions();return e.width=s,e.height=a,new Promise((n,o)=>{const r=new Image;r.onload=()=>{i.drawImage(r,0,0,s,a),URL.revokeObjectURL(t),e.toBlob(t=>{n(URL.createObjectURL(t))})},r.src=t})})}}class R extends S{update(){return this.button||(this.button=new j(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),p.select(t.parent.svg.node().parentNode).on(`mouseover.${t.getBaseId()}.toolbar`,null),p.select(t.parent.svg.node().parentNode).on(`mouseout.${t.getBaseId()}.toolbar`,null),t.parent.removePanel(t.id)}),this.button.show()),this}}class I extends S{update(){if(this.button){const t=0===this.parent_panel.layout.y_index;return this.button.disable(t),this}return this.button=new j(this).setColor(this.layout.color).setHtml("▴").setTitle("Move panel up").setOnclick(()=>{this.parent_panel.moveUp(),this.update()}),this.button.show(),this.update()}}class C extends S{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 j(this).setColor(this.layout.color).setHtml("▾").setTitle("Move panel down").setOnclick(()=>{this.parent_panel.moveDown(),this.update()}),this.button.show(),this.update()}}class B extends S{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?"+":"-"}${it(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 j(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 F extends S{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 j(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 i=Math.floor((e-t)/2);this.parent_plot.applyState({start:Math.max(this.parent_plot.state.start-i,1),end:this.parent_plot.state.end+i})}),this.button.show(),this}}class U extends S{update(){return this.button||(this.button=new j(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 q extends S{update(){return this.button||(this.button=new j(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 G extends S{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 j(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 H extends S{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);const i=t.fields_whitelist||["color","fill_opacity","filters","label","legend","point_shape","point_size","tooltip","tooltip_positioning"],s=this.parent_panel.data_layers[t.layer_name];if(!s)throw new Error(`Display options could not locate the specified layer_name: '${t.layer_name}'`);const a=s.layout,n={};i.forEach(t=>{const e=a[t];void 0!==e&&(n[t]=Object(k.b)(e))}),this._selected_item="default",this.button=new j(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",()=>{i.forEach(t=>{const e=void 0!==o[t];s.layout[t]=e?o[t]:n[t]}),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 Z extends S{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._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 j(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 i=this.button.menu.inner_selector.append("table"),s=(s,a,n)=>{const o=i.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:""))}),o.append("td").append("label").style("font-weight","normal").attr("for",r).text(s)};return t.options.forEach((t,e)=>s(t.display_name,t.value,e)),this})}update(){return this.button.show(),this}}const K=new h;for(let[t,e]of Object.entries(a))K.add(t,e);var J=K;class V{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=J.create(t.type,t,this);this.widgets.push(e)}catch(t){console.warn("Failed to create widget"),console.error(t)}}),"panel"===this.type&&p.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=p.select(this.parent.svg.node().parentNode).insert("div",":first-child");break;case"panel":this.selector=p.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",i=t.x.toString()+"px",s=(this.parent.layout.width-4).toString()+"px";this.selector.style("position","absolute").style("top",e).style("left",i).style("width",s)}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 Y={orientation:"vertical",origin:{x:0,y:0},width:10,height:10,padding:5,label_size:12,hidden:!1};class W{constructor(t){return this.parent=t,this.id=this.parent.getBaseId()+".legend",this.parent.layout.legend=Object(k.c)(this.parent.layout.legend||{},Y),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,i=t,s=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}, ${i})`),o=+a.label_size||+this.layout.label_size||12;let r=0,l=o/2+t/2;s=Math.max(s,o+t);const h=a.shape||"",c=Object(k.d)(h);if("line"===h){const e=+a.length||16,i=o/4+t/2;n.append("path").attr("class",a.class||"").attr("d",`M0,${i}L${e},${i}`).call(O,a.style||{}),r=e+t}else if("rect"===h){const e=+a.width||16,i=+a.height||e;n.append("rect").attr("class",a.class||"").attr("width",e).attr("height",i).attr("fill",a.color||{}).call(O,a.style||{}),r=e+t,s=Math.max(s,i+t)}else if(c){const e=+a.size||40,i=Math.ceil(Math.sqrt(e/Math.PI));n.append("path").attr("class",a.class||"").attr("d",p.symbol().size(e).type(c)).attr("transform",`translate(${i}, ${i+t/2})`).attr("fill",a.color||{}).call(O,a.style||{}),r=2*i+t,l=Math.max(2*i+t/2,l),s=Math.max(s,2*i+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)i+=d.height+t,s=0;else{const a=this.layout.origin.x+e+d.width;e>t&&a>this.parent.layout.width&&(i+=s,e=t,n.attr("transform",`translate(${e}, ${i})`)),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.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 X={title:{text:"",style:{},x:10,y:22},y_index:null,width:0,height:0,origin:{x:0,y:null},min_width:1,min_height:1,proportional_width:null,proportional_height:null,proportional_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 Q{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=Object(k.c)(t||{},X),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={layout_changed:[],data_requested:[],data_rendered:[],element_clicked:[],element_selection:[],match_requested:[]},this.initializeLayout()}on(t,e){if(!Array.isArray(this.event_hooks[t]))throw new Error("Unable to register event hook, invalid event: "+t.toString());if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this.event_hooks[t].push(e),e}off(t,e){const i=this.event_hooks[t];if(!Array.isArray(i))throw new Error("Unable to remove event hook, invalid event: "+t.toString());if(void 0===e)this.event_hooks[t]=[];else{const t=i.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");i.splice(t,1)}return this}emit(t,e,i){if(i=i||!1,!Array.isArray(this.event_hooks[t]))throw new Error("LocusZoom attempted to throw an invalid event: "+t.toString());"boolean"==typeof e&&2===arguments.length&&(i=e,e=null);const s={sourceID:this.getBaseId(),target:this,data:e||null};return this.event_hooks[t].forEach(t=>{t.call(this,s)}),i&&this.parent&&this.parent.emit(t,s),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=Object(k.c)(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(O,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=Ot.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 i=null;return this.layout.data_layers.forEach((t,s)=>{t.id===e.id&&(i=s)}),null===i&&(i=this.layout.data_layers.push(this.data_layers[e.id].layout)-1),this.data_layers[e.id].layout_idx=i,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.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.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 i=Math.pow(-10,e),s=Math.pow(-10,-e),a=Math.pow(10,-e),n=Math.pow(10,e);return t===1/0&&(t=n),t===-1/0&&(t=i),0===t&&(t=a),t>0&&(t=Math.max(Math.min(t,n),a)),t<0&&(t=Math.max(Math.min(t,s),i)),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 i,s=null;if(this.parent.interaction.zooming&&"function"==typeof this.x_scale){const t=Math.abs(this.x_extent[1]-this.x_extent[0]),s=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(s*(1/a));a<1&&!isNaN(this.parent.layout.max_region_scale)?a=1/(Math.min(n,this.parent.layout.max_region_scale)/s):a>1&&!isNaN(this.parent.layout.min_region_scale)&&(a=1/(Math.max(n,this.parent.layout.min_region_scale)/s));const o=Math.floor(t*a);i=this.parent.interaction.zooming.center-this.layout.margin.left-this.layout.origin.x;const r=i/this.layout.cliparea.width,l=Math.max(Math.floor(this.x_scale.invert(e.x_shifted[0])-(o-s)*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":p.event&&p.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):(i=this.parent.interaction.dragging.start_x-this.layout.margin.left-this.layout.origin.x,s=t(i/(i+this.parent.interaction.dragging.dragged_x),3),e.x_shifted[0]=0,e.x_shifted[1]=Math.max(this.layout.cliparea.width*(1/s),1));break;case"y1_tick":case"y2_tick":{const a=`y${this.parent.interaction.dragging.method[1]}_shifted`;p.event&&p.event.shiftKey?(e[a][0]=this.layout.cliparea.height+this.parent.interaction.dragging.dragged_y,e[a][1]=+this.parent.interaction.dragging.dragged_y):(i=this.layout.cliparea.height-(this.parent.interaction.dragging.start_y-this.layout.margin.top-this.layout.origin.y),s=t(i/(i-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/s))}}}if(["x","y1","y2"].forEach(t=>{this[t+"_extent"]&&(this[t+"_scale"]=p.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"]=p.scaleLinear().domain(this[t+"_extent"]).range(e[t]),this.renderAxis(t))}),this.layout.interaction.scroll_to_zoom){const t=()=>{if(!p.event.shiftKey&&!p.event.altKey)return void(this.parent._canInteract(this.id)&&this.loader.show("Press [SHIFT] or [ALT] while scrolling to zoom").hide(1e3));if(p.event.preventDefault(),!this.parent._canInteract(this.id))return;const t=p.mouse(this.svg.container.node()),e=Math.max(-1,Math.min(1,p.event.wheelDelta||-p.event.detail||-p.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}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(){if(0===this.layout.width&&null===this.layout.proportional_width&&(this.layout.proportional_width=1),0===this.layout.height&&null===this.layout.proportional_height){const t=Object.keys(this.parent.panels).length;this.layout.proportional_height=t>0?1/t:1}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.layout.width=Math.max(Math.round(+t),this.layout.min_width),this.layout.height=Math.max(Math.round(+e),this.layout.min_height)):(null!==this.layout.proportional_width&&(this.layout.width=Math.max(this.layout.proportional_width*this.parent.layout.width,this.layout.min_width)),null!==this.layout.proportional_height&&(this.layout.height=Math.max(this.layout.proportional_height*this.parent.layout.height,this.layout.min_height))),this.layout.cliparea.width=Math.max(this.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.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,i,s){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(i)&&i>=0&&(this.layout.margin.bottom=Math.max(Math.round(+i),0)),!isNaN(s)&&s>=0&&(this.layout.margin.left=Math.max(Math.round(+s),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.layout.width&&(a=Math.floor((this.layout.margin.left+this.layout.margin.right-this.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.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.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=M.call(this),this.loader=N.call(this),this.layout.show_loading_indicator&&this.addBasicLoader(!1),this.toolbar=new V(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 W(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(p.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(i=>{i!==this.id&&this.parent.panels[i].layout.interaction[t+"_linked"]&&e.push(i)}),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=p.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"]=p.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 i=this,s={position:e.position};return this.data_layer_ids_by_z_index.reduce((e,a)=>{const n=i.data_layers[a];return e.concat(n.getTicks(t,s))},[]).map(t=>{let i={};return i=Object(k.c)(i,e),Object(k.c)(i,t)})}}return this[t+"_extent"]?function(t,e,i){(void 0===i||isNaN(parseInt(i)))&&(i=5);const s=(i=+i)/3,a=Math.abs(t[0]-t[1]);let n=a/i;Math.log(a)/Math.LN10<-2&&(n=.75*Math.max(Math.abs(a))/s);const o=Math.pow(10,Math.floor(Math.log(n)/Math.LN10));let r=0;o<1&&0!==o&&(r=Math.abs(Math.round(Math.log(o)/Math.LN10)));let l=o;2*o-n<1.5*(n-l)&&(l=2*o,5*o-n<2.75*(n-l)&&(l=5*o,10*o-n<1.5*(n-l)&&(l=10*o)));let h=[],c=parseFloat((Math.floor(t[0]/l)*l).toFixed(r));for(;c0&&(c=parseFloat(c.toFixed(r)));h.push(c),(void 0===e||-1===["low","high","both","neither"].indexOf(e))&&(e="neither");"low"!==e&&"both"!==e||h[0]t[1]&&h.pop();return h}(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 i={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.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 s=(t=>{for(let e=0;eit(t,6));else{let e=this[t+"_ticks"].map(e=>e[t.substr(0,1)]);this[t+"_axis"].tickValues(e).tickFormat((e,i)=>this[t+"_ticks"][i].text)}if(this.svg[t+"_axis"].attr("transform",i[t].position).call(this[t+"_axis"]),!s){const e=p.selectAll(`g#${this.getBaseId().replace(".","\\.")}\\.${t}_axis g.tick`),i=this;e.each((function(e,s){const a=p.select(this).select("text");i[t+"_ticks"][s].style&&O(a,i[t+"_ticks"][s].style),i[t+"_ticks"][s].transform&&a.attr("transform",i[t+"_ticks"][s].transform)}))}const n=this.layout.axes[t].label||null;return null!==n&&(this.svg[t+"_axis_label"].attr("x",i[t].label_x).attr("y",i[t].label_y).text(at(this.state,n)).attr("fill","currentColor"),null!==i[t].label_rotate&&this.svg[t+"_axis_label"].attr("transform",`rotate(${i[t].label_rotate} ${i[t].label_x}, ${i[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`,i=function(){"function"==typeof p.select(this).node().focus&&p.select(this).node().focus();let s="x"===t?"ew-resize":"ns-resize";p.event&&p.event.shiftKey&&(s="move"),p.select(this).style("font-weight","bold").style("cursor",s).on("keydown"+e,i).on("keyup"+e,i)};this.svg.container.selectAll(`.lz-axis.lz-${t} .tick text`).attr("tabindex",0).on("mouseover"+e,i).on("mouseout"+e,(function(){p.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 i=this.data_layers[e].getAbsoluteDataHeight();+i&&(t=null===t?+i:Math.max(t,+i))}),+t&&(t+=+this.layout.margin.top+ +this.layout.margin.bottom,this.setDimensions(this.layout.width,t),this.parent.setDimensions(),this.parent.panel_ids_by_y_index.forEach(t=>{this.parent.panels[t].layout.proportional_height=null}),this.parent.positionPanels())}setAllElementStatus(t,e){this.data_layer_ids_by_z_index.forEach(i=>{this.data_layers[i].setAllElementStatus(t,e)})}}_.verbs.forEach((t,e)=>{const i=_.adjectives[e],s="un"+t;Q.prototype[t+"AllElements"]=function(){return this.setAllElementStatus(i,!0),this},Q.prototype[s+"AllElements"]=function(){return this.setAllElementStatus(i,!1),this}});const tt={state:{},width:1,height:1,min_width:1,min_height:1,responsive_resize:!1,panels:[],toolbar:{widgets:[]},panel_boundaries:!0,mouse_guide:!0};class et{constructor(t,e,i){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=i,Object(k.c)(this.layout,tt),this._base_layout=Object(k.b)(this.layout),this.state=this.layout.state,this.lzd=new E(e),this._external_listeners=new Map,this.event_hooks={layout_changed:[],data_requested:[],data_rendered:[],element_clicked:[],element_selection:[],match_requested:[],panel_removed:[],region_changed:[],state_changed:[]},this.interaction={},this.initializeLayout()}on(t,e){if(!Array.isArray(this.event_hooks[t]))throw new Error("Unable to register event hook, invalid event: "+t.toString());if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this.event_hooks[t].push(e),e}off(t,e){const i=this.event_hooks[t];if(!Array.isArray(i))throw new Error("Unable to remove event hook, invalid event: "+t.toString());if(void 0===e)this.event_hooks[t]=[];else{const t=i.indexOf(e);if(-1===t)throw new Error("The specified event listener is not registered and therefore cannot be removed");i.splice(t,1)}return this}emit(t,e){if(!Array.isArray(this.event_hooks[t]))throw new Error("LocusZoom attempted to throw an invalid event: "+t.toString());const i=this.getBaseId();return this.event_hooks[t].forEach(t=>{let s;s=e&&e.sourceID?e:{sourceID:i,target:this,data:e||null},t.call(this,s)}),this}addPanel(t){if("object"!=typeof t)throw new Error("Invalid panel layout");const e=new Q(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 i=null;return this.layout.panels.forEach((t,s)=>{t.id===e.id&&(i=s)}),null===i&&(i=this.layout.panels.push(this.panels[e.id].layout)-1),this.panels[e.id].layout_idx=i,this.initialized&&(this.positionPanels(),this.panels[e.id].initialize(),this.panels[e.id].reMap(),this.setDimensions(this.layout.width,this.layout.height)),this.panels[e.id]}clearPanelData(t,e){let i;return e=e||"wipe",i=t?[t]:Object.keys(this.panels),i.forEach(t=>{this.panels[t].data_layer_ids_by_z_index.forEach(i=>{const s=this.panels[t].data_layers[i];s.destroyAllTooltips(),delete s.layer_state,delete this.layout.state[s.state_id],"reset"===e&&s._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.layout.min_height=this._base_layout.min_height,this.layout.min_width=this._base_layout.min_width,this.positionPanels(),this.setDimensions(this.layout.width,this.layout.height)),this.emit("panel_removed",t),this}refresh(){return this.applyState()}subscribeToData(t,e,i){const s=(i=i||{}).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(i.discrete?t.discrete:t.body,this)).catch(s)}catch(t){s(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 i in t)e[i]=t[i];e=function(t,e){e=e||{};let i,s=!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,i=0;else if(isNaN(t.start)||isNaN(t.end))a=t.start||t.end,i=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),i=t.end-t.start,i<0){const e=t.start;t.end=t.start,t.start=e,i=t.end-t.start}a<0&&(t.start=1,t.end=1,i=0)}s=!0}return!isNaN(e.min_region_scale)&&s&&ie.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:i,end:s}=this.state;Object.keys(t).some(t=>["chr","start","end"].includes(t))&&this.emit("region_changed",{chr:e,start:i,end:s}),this.loading_data=!1})}trackExternalListener(t,e,i){this._external_listeners.has(t)||this._external_listeners.set(t,new Map);const s=this._external_listeners.get(t),a=s.get(e)||[];a.includes(i)||a.push(i),s.set(e,a)}destroy(){for(let[t,e]of this._external_listeners.entries())for(let[i,s]of e)for(let e of s)t.removeEventListener(i,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,i=document.documentElement.scrollTop||document.body.scrollTop,s=this.svg.node();for(;null!==s.parentNode;)if(s=s.parentNode,s!==document&&"static"!==p.select(s).style("position")){e=-1*s.getBoundingClientRect().left,i=-1*s.getBoundingClientRect().top;break}return{x:e+t.left,y:i+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}sumProportional(t){if("height"!==t&&"width"!==t)throw new Error("Bad dimension value passed to sumProportional");let e=0;for(let i in this.panels)this.panels[i].layout["proportional_"+t]||(this.panels[i].layout["proportional_"+t]=1/Object.keys(this.panels).length),e+=this.panels[i].layout["proportional_"+t];return e}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(isNaN(this.layout.height)||this.layout.height<=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){let i,s=parseFloat(this.layout.min_width)||0,a=parseFloat(this.layout.min_height)||0;for(i in this.panels)s=Math.max(s,this.panels[i].layout.min_width),parseFloat(this.panels[i].layout.min_height)>0&&parseFloat(this.panels[i].layout.proportional_height)>0&&(a=Math.max(a,this.panels[i].layout.min_height/this.panels[i].layout.proportional_height));if(this.layout.min_width=Math.max(s,1),this.layout.min_height=Math.max(a,1),p.select(this.svg.node().parentNode).style("min-width",this.layout.min_width+"px").style("min-height",this.layout.min_height+"px"),!isNaN(t)&&t>=0&&!isNaN(e)&&e>=0){this.layout.width=Math.max(Math.round(+t),this.layout.min_width),this.layout.height=Math.max(Math.round(+e),this.layout.min_height),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.layout.width,s=this.panels[t].layout.proportional_height*this.layout.height;this.panels[t].setDimensions(e,s),this.panels[t].setOrigin(0,i),this.panels[t].layout.proportional_origin.x=0,this.panels[t].layout.proportional_origin.y=i/this.layout.height,i+=s,this.panels[t].toolbar.update()})}else if(Object.keys(this.panels).length){for(i in this.layout.width=0,this.layout.height=0,this.panels)this.layout.width=Math.max(this.panels[i].layout.width,this.layout.width),this.layout.height+=this.panels[i].layout.height;this.layout.width=Math.max(this.layout.width,this.layout.min_width),this.layout.height=Math.max(this.layout.height,this.layout.min_height)}return null!==this.svg&&(this.svg.attr("viewBox",`0 0 ${this.layout.width} ${this.layout.height}`),this.svg.attr("width",this.layout.width).attr("height",this.layout.height)),this.initialized&&(this.panel_boundaries.position(),this.toolbar.update(),this.curtain.update(),this.loader.update()),this.emit("layout_changed")}positionPanels(){let t;const e={left:0,right:0};for(t in this.panels)null===this.panels[t].layout.proportional_height&&(this.panels[t].layout.proportional_height=this.panels[t].layout.height/this.layout.height),null===this.panels[t].layout.proportional_width&&(this.panels[t].layout.proportional_width=1),this.panels[t].layout.interaction.x_linked&&(e.left=Math.max(e.left,this.panels[t].layout.margin.left),e.right=Math.max(e.right,this.panels[t].layout.margin.right));const i=this.sumProportional("height");if(!i)return this;const s=1/i;for(t in this.panels)this.panels[t].layout.proportional_height*=s;let a=0;this.panel_ids_by_y_index.forEach(t=>{if(this.panels[t].setOrigin(0,a),this.panels[t].layout.proportional_origin.x=0,a+=this.panels[t].layout.height,this.panels[t].layout.interaction.x_linked){const i=Math.max(e.left-this.panels[t].layout.margin.left,0)+Math.max(e.right-this.panels[t].layout.margin.right,0);this.panels[t].layout.width+=i,this.panels[t].layout.margin.left=e.left,this.panels[t].layout.margin.right=e.right,this.panels[t].layout.cliparea.origin.x=e.left}});const n=a;return this.panel_ids_by_y_index.forEach(t=>{this.panels[t].layout.proportional_origin.y=this.panels[t].layout.origin.y/n}),this.setDimensions(),this.panel_ids_by_y_index.forEach(t=>{this.panels[t].setDimensions(this.layout.width*this.panels[t].layout.proportional_width,this.layout.height*this.panels[t].layout.proportional_height)}),this}initialize(){if(this.layout.responsive_resize&&p.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),i=t.append("rect").attr("class","lz-mouse_guide-horizontal").attr("y",-1);this.mouse_guide={svg:t,vertical:e,horizontal:i}}this.curtain=M.call(this),this.loader=N.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 i=p.select(this.parent.svg.node().parentNode).insert("div",".lz-data_layer-tooltip").attr("class","lz-panel-boundary").attr("title","Resize panel");i.append("span");const s=p.drag();s.on("start",()=>{this.dragging=!0}),s.on("end",()=>{this.dragging=!1}),s.on("drag",()=>{const t=this.parent.panels[this.parent.panel_ids_by_y_index[e]],i=t.layout.height;t.setDimensions(t.layout.width,t.layout.height+p.event.dy);const s=t.layout.height-i,a=this.parent.layout.height+s;this.parent.panel_ids_by_y_index.forEach((t,i)=>{const n=this.parent.panels[this.parent.panel_ids_by_y_index[i]];n.layout.proportional_height=n.layout.height/a,i>e&&(n.setOrigin(n.layout.origin.x,n.layout.origin.y+s),n.toolbar.position())}),this.parent.positionPanels(),this.position()}),i.call(s),this.parent.panel_boundaries.selectors.push(i)});const t=p.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=p.drag();e.on("start",()=>{this.dragging=!0}),e.on("end",()=>{this.dragging=!1}),e.on("drag",()=>{this.parent.setDimensions(this.parent.layout.width+p.event.dx,this.parent.layout.height+p.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,i)=>{const s=this.parent.panels[this.parent.panel_ids_by_y_index[i]]._getPageOrigin(),a=t.x,n=s.y+this.parent.panels[this.parent.panel_ids_by_y_index[i]].layout.height-12,o=this.parent.layout.width-1;e.style("top",n+"px").style("left",a+"px").style("width",o+"px"),e.select("span").style("width",o+"px")});return this.corner_selector.style("top",t.y+this.parent.layout.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&&p.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 V(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)},i=()=>{const t=p.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`,i)}const e=()=>{this.stopDrag()},i=()=>{if(this.interaction.dragging){const t=p.mouse(this.svg.node());p.event&&p.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,i).on("touchmove"+t,i);const s=p.select("body").node();s&&(s.addEventListener("mouseup",e),s.addEventListener("touchend",e),this.trackExternalListener(s,"mouseup",e),this.trackExternalListener(s,"touchend",e)),this.on("match_requested",t=>{const e=t.data,i=e.active?e.value:null;this.applyState({lz_match_value:i})}),this.initialized=!0;const a=this.svg.node().getBoundingClientRect(),n=a.width?a.width:this.layout.width,o=a.height?a.height:this.layout.height;return this.setDimensions(n,o),this}startDrag(t,e){t=t||null;let i=null;switch(e=e||null){case"background":case"x_tick":i="x";break;case"y1_tick":i="y1";break;case"y2_tick":i="y2"}if(!(t instanceof Q&&i&&this._canInteract()))return this.stopDrag();const s=p.mouse(this.svg.node());return this.interaction={panel_id:t.id,linked_panel_ids:t.getLinkedPanelIds(i),dragging:{method:e,start_x:s[0],start_y:s[1],dragged_x:0,dragged_y:0,axis:i}},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,i,s)=>{t.data_layer_ids_by_z_index.forEach(a=>{const n=t.data_layers[a].layout[e+"_axis"];n.axis===i&&(n.floor=s[0],n.ceiling=s[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 i=parseInt(this.interaction.dragging.method[1]);e("y",i,t[`y${i}_extent`])}}return this.interaction={},this.svg.style("cursor",null),this}}function it(t,e,i){const s={0:"",3:"K",6:"M",9:"G"};if(i=i||!1,isNaN(e)||null===e){const i=Math.log(t)/Math.LN10;e=Math.min(Math.max(i-i%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 i&&void 0!==s[e]&&(r+=` ${s[e]}b`),r}function st(t){let e=t.toUpperCase();e=e.replace(/,/g,"");const i=/([KMG])[B]*$/,s=i.exec(e);let a=1;return s&&(a="M"===s[1]?1e6:"G"===s[1]?1e9:1e3,e=e.replace(i,"")),e=Number(e)*a,e}function at(t,e){if("object"!=typeof t)throw new Error("invalid arguments: data is not an object");if("string"!=typeof e)throw new Error("invalid arguments: html is not a string");const i=[],s=/{{(?:(#if )?([A-Za-z0-9_:|]+)|(\/if))}}/;for(;e.length>0;){const t=s.exec(e);t?0!==t.index?(i.push({text:e.slice(0,t.index)}),e=e.slice(t.index)):"#if "===t[1]?(i.push({condition:t[2]}),e=e.slice(t[0].length)):t[2]?(i.push({variable:t[2]}),e=e.slice(t[0].length)):"/if"===t[3]?(i.push({close:"if"}),e=e.slice(t[0].length)):(console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(e)} and previous tokens are ${JSON.stringify(i)} and current regex match is ${JSON.stringify([t[1],t[2],t[3]])}`),e=e.slice(t[0].length)):(i.push({text:e}),e="")}const a=function(){const t=i.shift();if(void 0!==t.text||t.variable)return t;if(t.condition){for(t.then=[];i.length>0;){if("if"===i[0].close){i.shift();break}t.then.push(a())}return t}return console.error("Error making tooltip AST due to unknown token "+JSON.stringify(t)),{text:""}},n=[];for(;i.length>0;)n.push(a());const o=function(e){return Object.prototype.hasOwnProperty.call(o.cache,e)||(o.cache[e]=new z(e).resolve(t)),o.cache[e]};o.cache={};const r=function(t){if(void 0!==t.text)return t.text;if(t.variable){try{const e=o(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{const e=o(t.condition);if(e||0===e)return t.then.map(r).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(r).join("")}const nt=(t,e)=>void 0===e||t.field_value!==e?void 0!==t.else?t.else:null:t.then,ot=(t,e)=>{const i=t.breaks||[],s=t.values||[];if(null==e||isNaN(+e))return t.null_value?t.null_value:null;const a=i.reduce((function(t,i){return+e=t&&+evoid 0!==e&&t.categories.includes(e)?t.values[t.categories.indexOf(e)]:t.null_value?t.null_value:null,lt=(t,e,i)=>{var s=t.values;return s[i%s.length]},ht=(t,e)=>{var i=t.breaks||[],s=t.values||[],a=t.null_value?t.null_value:null;if(i.length<2||i.length!==s.length)return a;if(null==e||isNaN(+e))return a;if(+e<=t.breaks[0])return s[0];if(+e>=t.breaks[t.breaks.length-1])return s[i.length-1];{var n=null;if(i.forEach((function(t,s){s&&i[s-1]<=+e&&i[s]>=+e&&(n=s)})),null===n)return a;const t=(+e-i[n-1])/(i[n]-i[n-1]);return isFinite(t)?p.interpolate(s[n-1],s[n])(t):a}},ct=new l;for(let[t,e]of Object.entries(n))ct.add(t,e);ct.add("if",nt);var dt=ct;const ut={type:"",filters:null,fields:[],x_axis:{},y_axis:{},tooltip_positioning:"horizontal"};class pt{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=Object(k.c)(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=Object(k.b)(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,i){const s=this.getElementId(t);return this.layer_state.extra_fields[s]||(this.layer_state.extra_fields[s]={}),this.layer_state.extra_fields[s][e]=i,this}setFilter(t){this._filter_func=t}_getDataExtent(t,e){return t=t||this.data,p.extent(t,t=>+new z(e.field).resolve(t))}getElementId(t){const e=Symbol.for("lzID");if(t[e])return t[e];const i=this.layout.id_field||"id";if(void 0===t[i])throw new Error("Unable to generate element ID");const s=t[i].toString().replace(/\W/g,""),a=`${this.getBaseId()}-${s}`.replace(/([:.[\],])/g,"_");return t[e]=a,a}getElementStatusNodeId(t){return null}getElementById(t){const e=p.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=this.parent_plot.state.lz_match_value;return this.data.forEach((i,s)=>{t&&null!=e&&(i.lz_highlight_match=i[t]===e),i.toHTML=()=>{const t=this.layout.id_field||"id";let e="";return i[t]&&(e=i[t].toString()),e},i.getDataLayer=()=>this,i.getPanel=()=>this.parent||null,i.getPlot=()=>{const t=this.parent;return t?t.parent:null},i.deselect=()=>{this.getDataLayer().unselectElement(this)}}),this.applyCustomDataMethods(),this}applyCustomDataMethods(){return this}resolveScalableParameter(t,e,i){let s=null;if(Array.isArray(t)){let a=0;for(;null===s&&ac-(p+x)?"top":"bottom"):"horizontal"===v&&(x=0,v=u<=o.width/2?"left":"right"),"top"===v||"bottom"===v){const t=Math.max(h.width/2-u,0),e=Math.max(h.width/2+u-d,0);g=l.x+u-h.width/2-e+t,f=l.x+u-g-7,"top"===v?(_=l.y+p-(x+h.height+8),y="down",m=h.height-1):(_=l.y+p+x+8,y="up",m=-8)}else{if("left"!==v&&"right"!==v)throw new Error("Unrecognized placement value");"left"===v?(g=l.x+u+b+8,y="left",f=-8):(g=l.x+u-h.width-b-8,y="right",f=h.width-1),p-h.height/2<=0?(_=l.y+p-10.5-6,m=6):p+h.height/2>=c?(_=l.y+p+7+6-h.height,m=h.height-14-6):(_=l.y+p-h.height/2,m=h.height/2-7)}return t.selector.style("left",g+"px").style("top",_+"px"),t.arrow||(t.arrow=t.selector.append("div").style("position","absolute")),t.arrow.attr("class","lz-data_layer-tooltip-arrow_"+y).style("left",f+"px").style("top",m+"px"),this}filter(t,e,i,s){const a=(t,e)=>{const{field:i,operator:s,value:a}=e,n=this.layer_state.extra_fields[this.getElementId(t)],o=new z(i).resolve(t,n);return{"=":(t,e)=>t===e,"!=":(t,e)=>t!=e,"<":(t,e)=>tt<=e,">":(t,e)=>t>e,">=":(t,e)=>t>=e,"%":(t,e)=>t%e,in:(t,e)=>e&&e.includes(t),match:(t,e)=>t&&t.includes(e)}[s](o,a)};let n=!0;return t.forEach(t=>{a(e,t)||(n=!1)}),n}getElementAnnotation(t,e){const i=this.getElementId(t),s=this.layer_state.extra_fields[i];return s&&s[e]}_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;_.adjectives.forEach(t=>{e[t]=e[t]||[]}),e.has_tooltip=e.has_tooltip||[],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:p.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.push(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(t,this.layout.tooltip.html)),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 i;if(i="string"==typeof t?t:this.getElementId(t),this.tooltips[i]&&("object"==typeof this.tooltips[i].selector&&this.tooltips[i].selector.remove(),delete this.tooltips[i]),!e){const t=this.layer_state.status_flags.has_tooltip,e=t.indexOf(i);t.splice(e,1)}return this}destroyAllTooltips(){for(let t in this.tooltips)this.destroyTooltip(t,!0);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],i=this._getTooltipPosition(e);if(!i)return null;this._drawTooltip(e,this.layout.tooltip_positioning,i.x_min,i.x_max,i.y_min,i.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 i=this.getElementId(t),s=(t,e,i)=>{let a=null;if("object"!=typeof t||null===t)return null;if(Array.isArray(e))i=i||"and",a=1===e.length?t[e[0]]:e.reduce((e,s)=>"and"===i?t[e]&&t[s]:"or"===i?t[e]||t[s]:null);else{if("object"!=typeof e)return!1;{let n;for(let o in e)n=s(t,e[o],o),null===a?a=n:"and"===i?a=a&&n:"or"===i&&(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={};_.adjectives.forEach(t=>{const e="un"+t;r[t]=o.status_flags[t].includes(i),r[e]=!r[t]});const l=s(r,a),h=s(r,n),c=o.status_flags.has_tooltip.includes(i);return!l||!e&&!c||h?this.destroyTooltip(t):this.createTooltip(t),this}setElementStatus(t,e,i,s){if("has_tooltip"===t)return this;let a;void 0===i&&(i=!0);try{a=this.getElementId(e)}catch(t){return this}s&&this.setAllElementStatus(t,!i),p.select("#"+a).classed(`lz-data_layer-${this.layout.type}-${t}`,i);const n=this.getElementStatusNodeId(e);null!==n&&p.select("#"+n).classed(`lz-data_layer-${this.layout.type}-statusnode-${t}`,i);const o=this.layer_state.status_flags[t].indexOf(a),r=-1===o;i&&r&&this.layer_state.status_flags[t].push(a),i||r||this.layer_state.status_flags[t].splice(o,1),this.showOrHideTooltip(e,r),r&&this.parent.emit("layout_changed",!0);const l="selected"===t;!l||!r&&i||this.parent.emit("element_selection",{element:e,active:i},!0);const h=this.layout.match&&this.layout.match.send;return l&&h&&(r||!i)&&this.parent.emit("match_requested",{value:e[h],active:i},!0),this}setAllElementStatus(t,e){if(void 0===t||!_.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{this.layer_state.status_flags[t].slice().forEach(e=>{const i=this.getElementById(e);"object"==typeof i&&null!==i&&this.setElementStatus(t,i,!1)}),this.layer_state.status_flags[t]=[]}return this.global_statuses[t]=e,this}applyBehaviors(t){"object"==typeof this.layout.behaviors&&Object.keys(this.layout.behaviors).forEach(e=>{const i=/(click|mouseover|mouseout)/.exec(e);i&&t.on(`${i[0]}.${e}`,this.executeBehaviors(e,this.layout.behaviors[e]))})}executeBehaviors(t,e){const i=t.includes("ctrl"),s=t.includes("shift"),a=this;return function(t){t=t||p.select(p.event.target).datum(),i===!!p.event.ctrlKey&&s===!!p.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 i=a.layer_state.status_flags[e.status].includes(a.getElementId(t)),s=e.exclusive&&!i;a.setElementStatus(e.status,t,!i,s);break;case"link":if("string"==typeof e.href){const i=at(t,e.href);"string"==typeof e.target?window.open(i,e.target):window.location.href=i}}})}}_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 i in t)Object.prototype.hasOwnProperty.call(t,i)&&Array.isArray(t[i])&&t[i].forEach(t=>{try{this.setElementStatus(i,this.getElementById(t),!0)}catch(t){console.warn(`Unable to apply state: ${e.state_id}, ${i}`),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})}}_.verbs.forEach((t,e)=>{const i=_.adjectives[e],s="un"+t;pt.prototype[t+"Element"]=function(t,e){return e=void 0!==e&&!!e,this.setElementStatus(i,t,!0,e),this},pt.prototype[s+"Element"]=function(t,e){return e=void 0!==e&&!!e,this.setElementStatus(i,t,!1,e),this},pt.prototype[t+"AllElements"]=function(){return this.setAllElementStatus(i,!0),this},pt.prototype[s+"AllElements"]=function(){return this.setAllElementStatus(i,!1),this}});const _t={color:"#000000",filters:null,tooltip_positioning:"vertical",hitarea_width:8};class gt extends pt{constructor(t){if(!Array.isArray(t.filters))throw new Error("Annotation track must specify array of filters for selecting points to annotate");Object(k.c)(t,_t),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]),i=(e,i)=>{const s=this.parent.x_scale(e[this.layout.x_axis.field]);let a=s-this.layout.hitarea_width/2;if(i>=1){const e=t[i-1],n=this.parent.x_scale(e[this.layout.x_axis.field]);a=Math.max(a,(s+n)/2)}return[a,s]};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)=>i(t,e)[0]).attr("width",(t,e)=>{const s=i(t,e);return s[1]-s[0]+this.layout.hitarea_width/2});const s=this._visible_lines_group.selectAll("rect.lz-data_layer-"+this.layout.type).data(t,t=>t[this.layout.id_field]);s.enter().append("rect").attr("class","lz-data_layer-"+this.layout.type).merge(s).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)),s.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),e.exit().remove()}_getTooltipPosition(t){const e=this.parent,i=e.layout.height-(e.layout.margin.top+e.layout.margin.bottom),s=e.x_scale(t.data[this.layout.x_axis.field]),a=i/2;return{x_min:s-1,x_max:s+1,y_min:a-e.layout.margin.top,y_max:a+e.layout.margin.bottom}}}const yt={color:"seagreen",hitarea_width:"10px",style:{fill:"none","stroke-width":"1px","stroke-opacity":"100%"},tooltip_positioning:"top"};class mt extends pt{constructor(t){t=Object(k.c)(t,yt),super(...arguments)}render(){const t=this.layout,e=this.parent.x_scale,i=this.parent[`y${t.y_axis.axis}_scale`],s=this._applyFilters();function a(s){const a=s[t.x_axis.field1],n=s[t.x_axis.field2],o=(a+n)/2,r=[[e(a),i(0)],[e(o),i(s[t.y_axis.field])],[e(n),i(0)]];return p.line().x(t=>t[0]).y(t=>t[1]).curve(p.curveNatural)(r)}const n=this.svg.group.selectAll("path.lz-data_layer-arcs-hitarea").data(s,t=>this.getElementId(t)),o=this.svg.group.selectAll("path.lz-data_layer-arcs").data(s,t=>this.getElementId(t));return this.svg.group.call(O,t.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",t.hitarea_width).style("stroke-opacity",0).style("stroke","transparent").attr("d",t=>a(t)),o.enter().append("path").attr("class","lz-data_layer-arcs").merge(o).attr("id",t=>this.getElementId(t)).attr("stroke",(t,e)=>this.resolveScalableParameter(this.layout.color,t,e)).attr("d",(t,e)=>a(t)),o.exit().remove(),n.exit().remove(),this.svg.group.call(this.applyBehaviors.bind(this)),this}_getTooltipPosition(t){const e=this.parent,i=this.layout,s=t.data[i.x_axis.field1],a=t.data[i.x_axis.field2],n=e[`y${i.y_axis.axis}_scale`];return{x_min:e.x_scale(Math.min(s,a)),x_max:e.x_scale(Math.max(s,a)),y_min:n(t.data[i.y_axis.field]),y_max:n(0)}}}const ft={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 bt extends pt{constructor(t){t=Object(k.c)(t,ft),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 i=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+"→"),s=i.node().getBBox().width;return i.remove(),s}catch(t){return 0}};return this.tracks=1,this.gene_track_index={1:[]},t.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 i=1;for(;null===t.track;){let e=!1;this.gene_track_index[i].map(i=>{if(!e){const s=Math.min(i.display_range.start,t.display_range.start);Math.max(i.display_range.end,t.display_range.end)-sthis.tracks&&(this.tracks=i,this.gene_track_index[i]=[])):(t.track=i,this.gene_track_index[i].push(t))}t.parent=this,t.transcripts.map((e,i)=>{t.transcripts[i].parent=t,t.transcripts[i].exons.map((e,s)=>t.transcripts[i].exons[s].parent=t.transcripts[i])})}),this}render(){const t=this,e=this._applyFilters();let i;this.assignTracks(e);const s=this.svg.group.selectAll("g.lz-data_layer-genes").data(e,t=>t.gene_name);s.enter().append("g").attr("class","lz-data_layer-genes").merge(s).attr("id",t=>this.getElementId(t)).each((function(e){const s=e.parent,a=p.select(this).selectAll("rect.lz-data_layer-genes.lz-data_layer-genes-statusnode").data([e],t=>s.getElementStatusNodeId(t));i=s.getTrackHeight()-s.layout.track_vertical_spacing,a.enter().append("rect").attr("class","lz-data_layer-genes lz-data_layer-genes-statusnode").merge(a).attr("id",t=>s.getElementStatusNodeId(t)).attr("rx",s.layout.bounding_box_padding).attr("ry",s.layout.bounding_box_padding).attr("width",t=>t.display_range.width).attr("height",i).attr("x",t=>t.display_range.start).attr("y",t=>(t.track-1)*s.getTrackHeight()),a.exit().remove();const n=p.select(this).selectAll("rect.lz-data_layer-genes.lz-boundary").data([e],t=>t.gene_name+"_boundary");i=1,n.enter().append("rect").attr("class","lz-data_layer-genes lz-boundary").merge(n).attr("width",t=>s.parent.x_scale(t.end)-s.parent.x_scale(t.start)).attr("height",i).attr("x",t=>s.parent.x_scale(t.start)).attr("y",t=>(t.track-1)*s.getTrackHeight()+s.layout.bounding_box_padding+s.layout.label_font_size+s.layout.label_exon_spacing+Math.max(s.layout.exon_height,3)/2).style("fill",(e,i)=>t.resolveScalableParameter(t.layout.color,e,i)).style("stroke",(e,i)=>t.resolveScalableParameter(t.layout.stroke,e,i)),n.exit().remove();const o=p.select(this).selectAll("text.lz-data_layer-genes.lz-label").data([e],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",e.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+s.layout.bounding_box_padding:"end"===t.display_range.text_anchor?t.display_range.end-s.layout.bounding_box_padding:void 0).attr("y",t=>(t.track-1)*s.getTrackHeight()+s.layout.bounding_box_padding+s.layout.label_font_size),o.exit().remove();const r=p.select(this).selectAll("rect.lz-data_layer-genes.lz-exon").data(e.transcripts[e.parent.transcript_idx].exons,t=>t.exon_id);i=s.layout.exon_height,r.enter().append("rect").attr("class","lz-data_layer-genes lz-exon").merge(r).style("fill",(e,i)=>t.resolveScalableParameter(t.layout.color,e.parent.parent,i)).style("stroke",(e,i)=>t.resolveScalableParameter(t.layout.stroke,e.parent.parent,i)).attr("width",t=>s.parent.x_scale(t.end)-s.parent.x_scale(t.start)).attr("height",i).attr("x",t=>s.parent.x_scale(t.start)).attr("y",()=>(e.track-1)*s.getTrackHeight()+s.layout.bounding_box_padding+s.layout.label_font_size+s.layout.label_exon_spacing),r.exit().remove();const l=p.select(this).selectAll("rect.lz-data_layer-genes.lz-clickarea").data([e],t=>t.gene_name+"_clickarea");i=s.getTrackHeight()-s.layout.track_vertical_spacing,l.enter().append("rect").attr("class","lz-data_layer-genes lz-clickarea").merge(l).attr("id",t=>s.getElementId(t)+"_clickarea").attr("rx",s.layout.bounding_box_padding).attr("ry",s.layout.bounding_box_padding).attr("width",t=>t.display_range.width).attr("height",i).attr("x",t=>t.display_range.start).attr("y",t=>(t.track-1)*s.getTrackHeight()),l.exit().remove()})),s.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),i=p.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:i.y,y_max:i.y+i.height}}}const xt={style:{fill:"none","stroke-width":"2px"},interpolate:"curveLinear",x_axis:{field:"x"},y_axis:{field:"y",axis:1},hitarea_width:5};class vt extends pt{constructor(t){if((t=Object(k.c)(t,xt)).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,i=this.layout.y_axis.field,s=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]);let a;this.path=s.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?p.area().x(t=>+n(t[e])).y0(+o(0)).y1(t=>+o(t[i])):p.line().x(t=>+n(t[e])).y(t=>+o(t[i])).curve(p[this.layout.interpolate]),s.merge(this.path).attr("d",a).call(O,this.layout.style),s.exit().remove()}setElementStatus(t,e,i){return this.setAllElementStatus(t,i)}setAllElementStatus(t,e){if(void 0===t||!_.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 i="lz-data_layer-line";return Object.keys(this.global_statuses).forEach(t=>{this.global_statuses[t]&&(i+=" lz-data_layer-line-"+t)}),this.path.attr("class",i),this.parent.emit("layout_changed",!0),this}}const wt={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 zt extends pt{constructor(t){t=Object(k.c)(t,wt),["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`,i=`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[i][0]},{x:this.layout.offset,y:t[i][1]}]}const s=this.svg.group.selectAll("path.lz-data_layer-line").data([this.data]),a=[t.layout.cliparea.height,0],n=p.line().x((e,i)=>{const s=+t.x_scale(e.x);return isNaN(s)?t.x_range[i]:s}).y((i,s)=>{const n=+t[e](i.y);return isNaN(n)?a[s]:n});this.path=s.enter().append("path").attr("class","lz-data_layer-line").merge(s).attr("d",n).call(O,this.layout.style).call(this.applyBehaviors.bind(this)),s.exit().remove()}_getTooltipPosition(t){try{const t=p.mouse(this.svg.container.node()),e=t[0],i=t[1];return{x_min:e-1,x_max:e+1,y_min:i-1,y_max:i+1}}catch(t){return null}}}const kt={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 Et extends pt{constructor(t){(t=Object(k.c)(t,kt)).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]),i=`y${this.layout.y_axis.axis}_scale`,s=this.parent[i](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:s-n,y_max:s+n}}flip_labels(){const t=this,e=t.resolveScalableParameter(t.layout.point_size,{}),i=t.layout.label.spacing,s=Boolean(t.layout.label.lines),a=2*i,n=this.parent.layout.width-this.parent.layout.margin.left-this.parent.layout.margin.right-2*i,o=(t,a)=>{const n=+t.attr("x"),o=2*i+2*Math.sqrt(e);let r,l;s&&(r=+a.attr("x2"),l=i+2*Math.sqrt(e)),"start"===t.style("text-anchor")?(t.style("text-anchor","end"),t.attr("x",n-o),s&&a.attr("x2",r-l)):(t.style("text-anchor","start"),t.attr("x",n+o),s&&a.attr("x2",r+l))};t.label_texts.each((function(e,a){const r=p.select(this);if(+r.attr("x")+r.node().getBoundingClientRect().width+i>n){const e=s?p.select(t.label_lines.nodes()[a]):null;o(r,e)}})),t.label_texts.each((function(e,n){const r=p.select(this);if("end"===r.style("text-anchor"))return;let l=+r.attr("x");const h=r.node().getBoundingClientRect(),c=s?p.select(t.label_lines.nodes()[n]):null;t.label_texts.each((function(){const t=p.select(this).node().getBoundingClientRect();h.leftt.left&&h.topt.top&&(o(r,c),l=+r.attr("x"),l-h.width-il.left&&r.topl.top))return;i=!0;const h=o.attr("y"),c=.5*(r.topg?(y=d-+n,d=+n,u-=y):u+l.height/2>g&&(y=u-+h,u=+h,d-=y),a.attr("y",d),o.attr("y",u)}))})),i){if(t.layout.label.lines){const e=t.label_texts.nodes();t.label_lines.attr("y2",(t,i)=>p.select(e[i]).attr("y"))}this.seperate_iterations<150&&setTimeout(()=>{this.separate_labels()},1)}}render(){const t=this,e=this.parent.x_scale,i=this.parent[`y${this.layout.y_axis.axis}_scale`],s=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=i(t[this.layout.y_axis.field]);isNaN(n)&&(n=-1e3),isNaN(o)&&(o=-1e3),t[s]=n,t[a]=o}),this.layout.coalesce.active&&n.length>this.layout.coalesce.max_points){let{x_min:t,x_max:s,y_min:a,y_max:o,x_gap:r,y_gap:l}=this.layout.coalesce;n=function(t,e,i,s,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,i){c=t,d=e,u.push(i)}return t.forEach(t=>{const g=t[l],y=t[h],m=g>=e&&g<=i&&y>=a&&y<=n;if(t.lz_highlight_match||!m)p(),r.push(t);else if(null===c)_(g,y,t);else{Math.abs(g-c)<=s&&Math.abs(y-d)<=o?u.push(t):(p(),_(g,y,t))}}),p(),r}(n,isFinite(t)?e(+t):-1/0,isFinite(s)?e(+s):1/0,r,isFinite(o)?i(+o):-1/0,isFinite(a)?i(+a):1/0,l)}if(this.layout.label){let e;const i=t.layout.label.filters||[];if(i.length){const t=this.filter.bind(this,i);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(e,t.layout.label.text||"")).attr("x",e=>e[s]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing).attr("y",t=>t[a]).attr("text-anchor","start").call(O,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[s]).attr("y1",t=>t[a]).attr("x2",e=>e[s]+Math.sqrt(t.resolveScalableParameter(t.layout.point_size,e))+t.layout.label.spacing/2).attr("y2",t=>t[a]).call(O,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=p.symbol().size((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e)).type((t,e)=>Object(k.d)(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[s]}, ${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=p.select(p.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");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_plot.applyState({ldrefvar:e})}}class Mt extends Et{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 i=this.data.sort((t,i)=>{const s=t[e],a=i[e],n="string"==typeof s?s.toLowerCase():s,o="string"==typeof a?a.toLowerCase():a;return n===o?0:n{e[t]=e[t]||i}),i}_generateCategoryBounds(){const t=this.layout.x_axis.category_field,e=this.layout.x_axis.field||"x",i={};this.data.forEach(s=>{const a=s[t],n=s[e],o=i[a]||[n,n];i[a]=[Math.min(o[0],n),Math.max(o[1],n)]});const s=Object.keys(i);return this._setDynamicColorScheme(s),i}_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,i=this._getColorScale(this._base_layout).parameters;if(i.categories.length&&i.values.length){const s={};i.categories.forEach(t=>{s[t]=1}),t.every(t=>Object.prototype.hasOwnProperty.call(s,t))?e.categories=i.categories:e.categories=t}else e.categories=t;let s;for(s=i.values.length?i.values:["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"];s.length{const o=s[t];let r;switch(i){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 Nt=new h;for(let[t,e]of Object.entries(o))Nt.add(t,e);var Ot=Nt;const St={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 Make LD Reference
'},jt=function(){const t=Object(k.b)(St);return t.html+="Toggle label",t}(),$t={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'},Tt={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'},At={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}}"},Lt={id:"significance",type:"orthogonal_line",orientation:"horizontal",offset:7.301},Pt={namespace:{recomb:"recomb"},id:"recombrate",type:"line",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}},Dt={namespace:{assoc:"assoc",ld:"ld"},id:"associationpvalues",type:"scatter",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:["#357ebd","#46b8da","#5cb85c","#eea236","#d43f3a"]}},"#B8B8B8"],legend:[{shape:"diamond",color:"#9632b8",size:40,label:"LD Ref Var",class:"lz-data_layer-scatter"},{shape:"circle",color:"#d43f3a",size:40,label:"1.0 > r² ≥ 0.8",class:"lz-data_layer-scatter"},{shape:"circle",color:"#eea236",size:40,label:"0.8 > r² ≥ 0.6",class:"lz-data_layer-scatter"},{shape:"circle",color:"#5cb85c",size:40,label:"0.6 > r² ≥ 0.4",class:"lz-data_layer-scatter"},{shape:"circle",color:"#46b8da",size:40,label:"0.4 > r² ≥ 0.2",class:"lz-data_layer-scatter"},{shape:"circle",color:"#357ebd",size:40,label:"0.2 > r² ≥ 0.0",class:"lz-data_layer-scatter"},{shape:"circle",color:"#B8B8B8",size:40,label:"no r² data",class:"lz-data_layer-scatter"}],label:null,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",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:Object(k.b)(St)},Rt={namespace:{access:"access"},id:"coaccessibility",type:"arcs",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_highlight_match",scale_function:"if",parameters:{field_value:!0,then:"#ff0000"}},{field:"lz_highlight_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:Object(k.b)(At)},It=function(){let t=Object(k.b)(Dt);return t=Object(k.c)({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}(),Ct={namespace:{phewas:"phewas"},id:"phewaspvalues",type:"category_scatter",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"}}},Bt={namespace:{gene:"gene",constraint:"constraint"},id:"genes",type:"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:Object(k.b)($t)},Ft=Object(k.c)({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"]}]},Object(k.b)(Bt)),Ut={namespace:{assoc:"assoc",catalog:"catalog"},id:"annotation_catalog",type:"annotation_track",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:7.301}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:Object(k.b)(Tt),tooltip_positioning:"top"},qt={type:"set_state",position:"right",color:"blue",button_html:"LD Population: ",show_selected:!0,button_title:"Select LD Population: ",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"}]},Gt={type:"display_options",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}}]},Ht={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"}}]},Zt={widgets:[{type:"title",title:"LocusZoom",subtitle:'v0.13.0-beta.3',position:"left"},{type:"download",position:"right",group_position:"end"},{type:"download_png",position:"right",group_position:"start"}]},Kt=function(){const t=Object(k.b)(Zt);return t.widgets.push(Object(k.b)(qt)),t}(),Jt=function(){const t=Object(k.b)(Zt);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}(),Vt={id:"association",width:800,height:225,min_width:400,min_height:200,proportional_width:1,margin:{top:35,right:50,bottom:40,left:50},inner_border:"rgb(210, 210, 210)",toolbar:function(){const t=Object(k.b)(Ht);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:[Object(k.b)(Lt),Object(k.b)(Pt),Object(k.b)(Dt)]},Yt={id:"coaccessibility",width:800,height:225,min_width:400,min_height:100,proportional_width:1,margin:{top:35,right:50,bottom:40,left:50},inner_border:"rgb(210, 210, 210)",toolbar:Object(k.b)(Ht),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:[Object(k.b)(Rt)]},Wt=function(){let t=Object(k.b)(Vt);return t=Object(k.c)({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:7.301},{field:"{{namespace[ld]}}state",operator:">",value:.4}],style:{"font-size":"10px","font-weight":"bold",fill:"#333333"}}}}]}),t.data_layers=[Object(k.b)(Lt),Object(k.b)(Pt),Object(k.b)(It)],t}(),Xt={id:"genes",width:800,height:225,min_width:400,min_height:112.5,proportional_width:1,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=Object(k.b)(Ht);return t.widgets.push({type:"resize_to_data",position:"right",button_html:"Resize"},Object(k.b)(Gt)),t}(),data_layers:[Object(k.b)(Ft)]},Qt={id:"phewas",width:800,height:300,min_width:800,min_height:300,proportional_width:1,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:[Object(k.b)(Lt),Object(k.b)(Ct)]},te={id:"annotationcatalog",width:800,height:45,min_height:45,proportional_width:1,margin:{top:25,right:50,bottom:0,left:50},inner_border:"rgb(210, 210, 210)",toolbar:Object(k.b)(Ht),interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[Object(k.b)(Ut)]},ee={state:{},width:800,height:450,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Object(k.b)(Kt),panels:[Object(k.c)({proportional_height:.5},Object(k.b)(Vt)),Object(k.c)({proportional_height:.5},Object(k.b)(Xt))]},ie={state:{},width:800,height:500,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Object(k.b)(Kt),panels:[Object(k.b)(te),Object(k.b)(Wt),Object(k.b)(Xt)]},se={width:800,height:600,min_width:800,min_height:600,responsive_resize:!0,toolbar:Object(k.b)(Zt),panels:[Object(k.c)({proportional_height:.5},Object(k.b)(Qt)),Object(k.c)({proportional_height:.5,margin:{bottom:40},axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:32,tick_format:"region",extent:"state"}}},Object(k.b)(Xt))],mouse_guide:!1},ae={state:{},width:800,height:450,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Object(k.b)(Zt),panels:[Object.assign({proportional_height:.4},Object(k.b)(Yt)),function(){const t=Object.assign({proportional_height:.6},Object(k.b)(Xt)),e=t.data_layers[0];e.match={send:"gene_name",receive:"gene_name"};const i=[{field:"lz_highlight_match",scale_function:"if",parameters:{field_value:!0,then:"#ff0000"}},{field:"lz_highlight_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},"#363696"];return e.color=i,e.stroke=i,t}()]},ne={standard_association:St,standard_association_with_label:jt,standard_genes:$t,catalog_variant:Tt,coaccessibility:At},oe={ldlz2_pop_selector:qt,gene_selector_menu:Gt},re={standard_panel:Ht,standard_plot:Zt,standard_association:Kt,region_nav_plot:Jt},le={significance:Lt,recomb_rate:Pt,association_pvalues:Dt,coaccessibility:Rt,association_pvalues_catalog:It,phewas_pvalues:Ct,genes:Bt,genes_filtered:Ft,annotation_catalog:Ut},he={association:Vt,coaccessibility:Yt,association_catalog:Wt,genes:Xt,phewas:Qt,annotation_catalog:te},ce={standard_association:ee,association_catalog:ie,standard_phewas:se,coaccessibility:ae};const de=new class extends l{get(t,e,i={}){if(!t||!e)throw new Error("Must specify both the type and name for the layout desired. See .list() for available options");let s=super.get(t).get(e);if(s=Object(k.c)(i,s),s.unnamespaced)return delete s.unnamespaced,Object(k.b)(s);let a="";"string"==typeof s.namespace?a=s.namespace:"object"==typeof s.namespace&&Object.keys(s.namespace).length&&(a=void 0!==s.namespace.default?s.namespace.default:s.namespace[Object.keys(s.namespace)[0]].toString()),a+=a.length?":":"";const n=Object(k.a)(s,s.namespace,a);return Object(k.b)(n)}add(t,e,i,s=!1){if(!(t&&e&&i))throw new Error("To add a layout, type, name, and item must all be specified");if("object"!=typeof i)throw new Error("The configuration to be added must be an object");this.has(t)||super.add(t,new l);const a=Object(k.b)(i);return super.get(t).add(e,a,s)}list(t){if(!t){let t={};for(let[e,i]of this._items)t[e]=i.list();return t}return super.get(t).list()}merge(t,e){return Object(k.c)(t,e)}};for(let[t,e]of Object.entries(r))for(let[i,s]of Object.entries(e))de.add(t,i,s);var ue=de;const pe={version:"0.13.0-beta.3",populate:function(t,e,i){if(void 0===t)throw new Error("LocusZoom.populate selector not defined");let s;return p.select(t).html(""),p.select(t).call((function(t){if(void 0===t.node().id){let e=0;for(;!p.select("#lz-"+e).empty();)e++;t.attr("id","#lz-"+e)}if(s=new et(t.node().id,e,i),s.container=t.node(),void 0!==t.node().dataset&&void 0!==t.node().dataset.region){const e=function(t){let e=/^(\w+):([\d,.]+[kmgbKMGB]*)([-+])([\d,.]+[kmgbKMGB]*)$/.exec(t);if(e){if("+"===e[3]){const t=st(e[2]),i=st(e[4]);return{chr:e[1],start:t-i,end:t+i}}return{chr:e[1],start:st(e[2]),end:st(e[4])}}if(e=/^(\w+):([\d,.]+[kmgbKMGB]*)$/.exec(t),e)return{chr:e[1],position:st(e[2])};return null}(t.node().dataset.region);Object.keys(e).forEach((function(t){s.state[t]=e[t]}))}s.svg=p.select("div#"+s.id).append("svg").attr("version","1.1").attr("xmlns","http://www.w3.org/2000/svg").attr("id",s.id+"_svg").attr("class","lz-locuszoom").call(O,s.layout.style),s.setDimensions(),s.positionPanels(),s.initialize(),e&&s.refresh()})),s},DataSources:class extends l{constructor(t){super(),this._registry=t||u}add(t,e,i=!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,i]=e;e=this._registry.create(t,i)}return e.source_id=t,super.add(t,e,i),this}},Adapters:u,DataLayers:Ot,Layouts:ue,ScaleFunctions:dt,TransformationFunctions:w,Widgets:J,get KnownDataSources(){return console.warn('Deprecation warning: KnownDataSources has been renamed to "Adapters"'),u}},_e=[];pe.use=function(t,...e){if(!_e.includes(t)){if(e.unshift(pe),"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)}_e.push(t)}};e.default=pe},2:function(t,e,i){"use strict";function s(t,e,i){if(e&&i||!e&&!i)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")}i.r(e),i.d(e,"BaseAdapter",(function(){return a})),i.d(e,"BaseApiAdapter",(function(){return n})),i.d(e,"AssociationLZ",(function(){return o})),i.d(e,"ConnectorSource",(function(){return _})),i.d(e,"GeneConstraintLZ",(function(){return c})),i.d(e,"GeneLZ",(function(){return h})),i.d(e,"GwasCatalogLZ",(function(){return l})),i.d(e,"LDServer",(function(){return r})),i.d(e,"PheWASLZ",(function(){return p})),i.d(e,"RecombLZ",(function(){return d})),i.d(e,"StaticSource",(function(){return u}));class a{constructor(t){this._enableCache=!0,this._cachedKey=null,this.__dependentSource=!1,this.parseInit(t)}parseInit(t){this.params=t.params||{}}getCacheKey(t,e,i){return this.getURL(t,e,i)}getURL(t,e,i){return this.url}fetchRequest(t,e,i){const s=this.getURL(t,e,i);return fetch(s).then(t=>{if(!t.ok)throw new Error(t.statusText);return t.text()})}getRequest(t,e,i){let s;const a=this.getCacheKey(t,e,i);return this._enableCache&&void 0!==a&&a===this._cachedKey?s=Promise.resolve(this._cachedResponse):(s=this.fetchRequest(t,e,i),this._enableCache&&(this._cachedKey=a,this._cachedResponse=s)),s}normalizeResponse(t){if(Array.isArray(t))return t;const e=Object.keys(t),i=t[e[0]].length;if(!e.every((function(e){return t[e].length===i})))throw new Error(this.constructor.name+" expects a response in which all arrays of data are the same length");const s=[],a=Object.keys(t);for(let e=0;ePromise.resolve(this.annotateData(t,e))).then(t=>Promise.resolve(this.extractFields(t,i,s,a))).then(t=>(e.discrete[n]=t,Promise.resolve(this.combineChainBody(t,e,i,s,a)))).then(t=>({header:e.header||{},discrete:e.discrete,body:t}))}getData(t,e,i,s){if(this.preGetData){const a=this.preGetData(t,e,i,s);this.pre&&(t=a.state||t,e=a.fields||e,i=a.outnames||i,s=a.trans||s)}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,i,s))}}class n extends a{parseInit(t){if(super.parseInit(t),this.url=t.url,!this.url)throw new Error("Source not initialized with required URL")}}class o extends n{preGetData(t,e,i,s){return[this.params.id_field||"id","position"].forEach((function(t){e.includes(t)||(e.unshift(t),i.unshift(t),s.unshift(null))})),{fields:e,outnames:i,trans:s}}getURL(t,e,i){const s=e.header.analysis||this.params.source||this.params.analysis;if(void 0===s)throw new Error("Association source must specify an analysis ID to plot");return`${this.url}results/?filter=analysis in ${s} 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 r extends n{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 s=Object.keys(t.body[0]),a=(i=s,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(s)}}class l extends n{constructor(t){super(t),this.__dependentSource=!0}getURL(t,e,i){const a=t.genome_build||this.params.build;s(this.constructor.name,a,null);const n="GRCh38"===a?5:6,o=this.params.source||n;return`${this.url}?format=objects&sort=pos&filter=id eq ${o} and chrom eq '${t.chr}' and pos ge ${t.start} and pos le ${t.end}`}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,i,s){return t}combineChainBody(t,e,i,s,a){if(!t.length)return e.body;const n=s[i.indexOf("log_pvalue")];function o(t,e,i,s,a){const o=t.n_catalog_matches||0;if(t.n_catalog_matches=o+1,!(t[n]&&t[n]>e.log_pvalue))for(let n=0;nt.ok?t.text():[]).catch(t=>[])}combineChainBody(t,e,i,s,a){return t?(e.body.forEach((function(e){const i="_"+e.gene_name.replace(/[^A-Za-z0-9_]/g,"_"),s=t[i]&&t[i].gnomad_constraint;s&&Object.keys(s).forEach((function(t){let i=s[t];void 0===e[t]&&("number"==typeof i&&i.toString().includes(".")&&(i=parseFloat(i.toFixed(2))),e[t]=i)}))})),e.body):e}}class d extends n{getURL(t,e,i){const a=t.genome_build||this.params.build;let n=this.params.source;return s(this.constructor.SOURCE_NAME,a,n),a&&(n="GRCh38"===a?16:15),`${this.url}?filter=id in ${n} and chromosome eq '${t.chr}' and position le ${t.end} and position ge ${t.start}`}}class u extends a{parseInit(t){this._data=t}getRequest(t,e,i){return Promise.resolve(this._data)}}class p extends n{getURL(t,e,i){const s=(t.genome_build?[t.genome_build]:null)||this.params.build;if(!s||!Array.isArray(s)||!s.length)throw new Error(["Data source",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&",s.map((function(t){return"build="+encodeURIComponent(t)})).join("&")].join("")}}class _ extends a{constructor(t){if(super(t),!t||!t.sources)throw new Error("Connectors must specify the data they require as init.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,i){return Object.keys(this._source_name_mapping).forEach(t=>{const i=this._source_name_mapping[t];if(e.discrete&&!e.discrete[i])throw new Error(`${this.constructor.name} cannot be used before loading required data for: ${i}`)}),Promise.resolve(e.body||[])}parseResponse(t,e,i,s,a){return Promise.resolve(this.combineChainBody(t,e,i,s,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")}}}}).default; +/*! Locuszoom 0.13.0-beta.4 */ +var LocusZoom=function(t){var e={};function s(i){if(e[i])return e[i].exports;var a=e[i]={i:i,l:!1,exports:{}};return t[i].call(a.exports,a,a.exports,s),a.l=!0,a.exports}return s.m=t,s.c=e,s.d=function(t,e,i){s.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},s.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},s.t=function(t,e){if(1&e&&(t=s(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(s.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var a in t)s.d(i,a,function(e){return t[e]}.bind(null,a));return i},s.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return s.d(e,"a",e),e},s.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},s.p="",s(s.s=13)}({0:function(t,e){t.exports=d3},1:function(t,e,s){"use strict";s.d(e,"a",(function(){return o})),s.d(e,"b",(function(){return l})),s.d(e,"c",(function(){return r})),s.d(e,"d",(function(){return h}));var i=s(0);const a=Math.sqrt(3),n={draw(t,e){const s=-Math.sqrt(e/(3*a));t.moveTo(0,2*-s),t.lineTo(-a*s,s),t.lineTo(a*s,s),t.closePath()}};function o(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=r(e,"string"==typeof t.namespace?{default:t.namespace}:t.namespace)}let i,a;for(let n in t)"namespace"!==n&&(i=o(t[n],e,s),a=o(n,e,s),n!==a&&delete t[n],t[a]=i)}return t}function r(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]=r(t[s],e[s])):t[s]=l(e[s])}return t}function l(t){return JSON.parse(JSON.stringify(t))}function h(t){if(!t)return null;if("triangledown"===t)return n;const e="symbol"+(t.charAt(0).toUpperCase()+t.slice(1));return i[e]||null}},13:function(t,e,s){"use strict";s.r(e);var i={};s.r(i),s.d(i,"log10",(function(){return g})),s.d(i,"neglog10",(function(){return y})),s.d(i,"logtoscinotation",(function(){return f})),s.d(i,"scinotation",(function(){return m})),s.d(i,"htmlescape",(function(){return b})),s.d(i,"urlencode",(function(){return x}));var a={};s.r(a),s.d(a,"BaseWidget",(function(){return O})),s.d(a,"_Button",(function(){return $})),s.d(a,"display_options",(function(){return G})),s.d(a,"download",(function(){return L})),s.d(a,"download_png",(function(){return D})),s.d(a,"filter_field",(function(){return T})),s.d(a,"menu",(function(){return U})),s.d(a,"move_panel_down",(function(){return C})),s.d(a,"move_panel_up",(function(){return R})),s.d(a,"region_scale",(function(){return A})),s.d(a,"resize_to_data",(function(){return F})),s.d(a,"set_state",(function(){return H})),s.d(a,"shift_region",(function(){return I})),s.d(a,"remove_panel",(function(){return P})),s.d(a,"title",(function(){return j})),s.d(a,"toggle_legend",(function(){return q})),s.d(a,"zoom_region",(function(){return B}));var n={};s.r(n),s.d(n,"categorical_bin",(function(){return lt})),s.d(n,"stable_choice",(function(){return ct})),s.d(n,"if_value",(function(){return ot})),s.d(n,"interpolate",(function(){return dt})),s.d(n,"numerical_bin",(function(){return rt})),s.d(n,"ordinal_cycle",(function(){return ht}));var o={};s.r(o),s.d(o,"BaseDataLayer",(function(){return gt})),s.d(o,"annotation_track",(function(){return ft})),s.d(o,"arcs",(function(){return bt})),s.d(o,"genes",(function(){return vt})),s.d(o,"line",(function(){return zt})),s.d(o,"orthogonal_line",(function(){return Et})),s.d(o,"scatter",(function(){return Nt})),s.d(o,"category_scatter",(function(){return St}));var r={};s.r(r),s.d(r,"tooltip",(function(){return re})),s.d(r,"toolbar_widgets",(function(){return le})),s.d(r,"toolbar",(function(){return he})),s.d(r,"data_layer",(function(){return ce})),s.d(r,"panel",(function(){return de})),s.d(r,"plot",(function(){return ue}));class l{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 h extends l{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}}var c=s(2);const d=new h;for(let[t,e]of Object.entries(c))d.add(t,e);d.add("StaticJSON",c.StaticSource),d.add("LDLZ2",c.LDServer);var u=d,p=s(0);const _={verbs:["highlight","select","fade","hide"],adjectives:["highlighted","selected","faded","hidden"]};function g(t){return isNaN(t)||t<=0?null:Math.log(t)/Math.LN10}function y(t){return isNaN(t)||t<=0?null:-Math.log(t)/Math.LN10}function f(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 m(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 b(t){return t?(t=""+t).replace(/['"<>&`]/g,(function(t){switch(t){case"'":return"'";case'"':return""";case"<":return"<";case">":return">";case"&":return"&";case"`":return"`"}})):""}function x(t){return encodeURIComponent(t)}const v=new class extends l{_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))v.add(t,e);var w=v;class z{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]=w.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]}}var k=s(1);var E=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=w.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=p.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&&S(this.curtain.selector,e);const s=this._getPageOrigin();return this.curtain.selector.style("top",s.y+"px").style("left",s.x+"px").style("width",this.parent_plot.layout.width+"px").style("height",this.layout.height+"px"),this.curtain.content_selector.style("max-width",this.parent_plot.layout.width-40+"px").style("max-height",this.layout.height-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 N(){return{showing:!1,selector:null,content_selector:null,progress_selector:null,cancel_selector:null,show:t=>(this.loader.showing||(this.loader.selector=p.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 S(t,e){e=e||{};for(let[s,i]of Object.entries(e))t.style(s,i)}class O{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&&S(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 ${constructor(t){if(!(t instanceof O))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=p.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,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(S,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 j extends O{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 A extends O{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(et(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&&S(this.selector,this.layout.style),this}}class T extends O{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._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}}_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 L extends O{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"}update(){return this.button||(this.button=new $(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)),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=p.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*+p.select(this).attr("dy").substring(-2).slice(0,-2);p.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 D extends L{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"}_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 P extends O{update(){return this.button||(this.button=new $(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),p.select(t.parent.svg.node().parentNode).on(`mouseover.${t.getBaseId()}.toolbar`,null),p.select(t.parent.svg.node().parentNode).on(`mouseout.${t.getBaseId()}.toolbar`,null),t.parent.removePanel(t.id)}),this.button.show()),this}}class R extends O{update(){if(this.button){const t=0===this.parent_panel.layout.y_index;return this.button.disable(t),this}return this.button=new $(this).setColor(this.layout.color).setHtml("▴").setTitle("Move panel up").setOnclick(()=>{this.parent_panel.moveUp(),this.update()}),this.button.show(),this.update()}}class C extends O{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 $(this).setColor(this.layout.color).setHtml("▾").setTitle("Move panel down").setOnclick(()=>{this.parent_panel.moveDown(),this.update()}),this.button.show(),this.update()}}class I extends O{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?"+":"-"}${et(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 $(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 B extends O{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 $(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 U extends O{update(){return this.button||(this.button=new $(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 F extends O{update(){return this.button||(this.button=new $(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 q extends O{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 $(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 G extends O{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);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]=Object(k.b)(e))}),this._selected_item="default",this.button=new $(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._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 H extends O{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._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 $(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:""))}),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 Z=new h;for(let[t,e]of Object.entries(a))Z.add(t,e);var K=Z;class J{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=K.create(t.type,t,this);this.widgets.push(e)}catch(t){console.warn("Failed to create widget"),console.error(t)}}),"panel"===this.type&&p.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=p.select(this.parent.svg.node().parentNode).insert("div",":first-child");break;case"panel":this.selector=p.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 V={orientation:"vertical",origin:{x:0,y:0},width:10,height:10,padding:5,label_size:12,hidden:!1};class Y{constructor(t){return this.parent=t,this.id=this.parent.getBaseId()+".legend",this.parent.layout.legend=Object(k.c)(this.parent.layout.legend||{},V),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=Object(k.d)(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(S,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(S,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",p.symbol().size(e).type(c)).attr("transform",`translate(${s}, ${s+t/2})`).attr("fill",a.color||{}).call(S,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 W={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 X{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=Object(k.c)(t||{},W),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={layout_changed:[],data_requested:[],data_rendered:[],element_clicked:[],element_selection:[],match_requested:[]},this.initializeLayout()}on(t,e){if(!Array.isArray(this.event_hooks[t]))throw new Error("Unable to register event hook, invalid event: "+t.toString());if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this.event_hooks[t].push(e),e}off(t,e){const s=this.event_hooks[t];if(!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,!Array.isArray(this.event_hooks[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].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=Object(k.c)(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(S,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=$t.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":p.event&&p.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`;p.event&&p.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"]=p.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"]=p.scaleLinear().domain(this[t+"_extent"]).range(e[t]),this.renderAxis(t))}),this.layout.interaction.scroll_to_zoom){const t=()=>{if(!p.event.shiftKey&&!p.event.altKey)return void(this.parent._canInteract(this.id)&&this.loader.show("Press [SHIFT] or [ALT] while scrolling to zoom").hide(1e3));if(p.event.preventDefault(),!this.parent._canInteract(this.id))return;const t=p.mouse(this.svg.container.node()),e=Math.max(-1,Math.min(1,p.event.wheelDelta||-p.event.detail||-p.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}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=M.call(this),this.loader=N.call(this),this.layout.show_loading_indicator&&this.addBasicLoader(!1),this.toolbar=new J(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 Y(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(p.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=p.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"]=p.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=Object(k.c)(s,e),Object(k.c)(s,t)})}}return this[t+"_extent"]?function(t,e,s){(void 0===s||isNaN(parseInt(s)))&&(s=5);const i=(s=+s)/3,a=Math.abs(t[0]-t[1]);let n=a/s;Math.log(a)/Math.LN10<-2&&(n=.75*Math.max(Math.abs(a))/i);const o=Math.pow(10,Math.floor(Math.log(n)/Math.LN10));let r=0;o<1&&0!==o&&(r=Math.abs(Math.round(Math.log(o)/Math.LN10)));let l=o;2*o-n<1.5*(n-l)&&(l=2*o,5*o-n<2.75*(n-l)&&(l=5*o,10*o-n<1.5*(n-l)&&(l=10*o)));let h=[],c=parseFloat((Math.floor(t[0]/l)*l).toFixed(r));for(;c0&&(c=parseFloat(c.toFixed(r)));h.push(c),(void 0===e||-1===["low","high","both","neither"].indexOf(e))&&(e="neither");"low"!==e&&"both"!==e||h[0]t[1]&&h.pop();return h}(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;eet(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=p.selectAll(`g#${this.getBaseId().replace(".","\\.")}\\.${t}_axis g.tick`),s=this;e.each((function(e,i){const a=p.select(this).select("text");s[t+"_ticks"][i].style&&S(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(it(this.state,n)).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 p.select(this).node().focus&&p.select(this).node().focus();let i="x"===t?"ew-resize":"ns-resize";p.event&&p.event.shiftKey&&(i="move"),p.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(){p.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)})}}_.verbs.forEach((t,e)=>{const s=_.adjectives[e],i="un"+t;X.prototype[t+"AllElements"]=function(){return this.setAllElementStatus(s,!0),this},X.prototype[i+"AllElements"]=function(){return this.setAllElementStatus(s,!1),this}});const Q={state:{},width:800,min_width:400,responsive_resize:!1,panels:[],toolbar:{widgets:[]},panel_boundaries:!0,mouse_guide:!0};class tt{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,Object(k.c)(this.layout,Q),this._base_layout=Object(k.b)(this.layout),this.state=this.layout.state,this.lzd=new E(e),this._external_listeners=new Map,this.event_hooks={layout_changed:[],data_requested:[],data_rendered:[],element_clicked:[],element_selection:[],match_requested:[],panel_removed:[],region_changed:[],state_changed:[]},this.interaction={},this.initializeLayout()}on(t,e){if(!Array.isArray(this.event_hooks[t]))throw new Error("Unable to register event hook, invalid event: "+t.toString());if("function"!=typeof e)throw new Error("Unable to register event hook, invalid hook function passed");return this.event_hooks[t].push(e),e}off(t,e){const s=this.event_hooks[t];if(!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){if(!Array.isArray(this.event_hooks[t]))throw new Error("LocusZoom attempted to throw an invalid event: "+t.toString());const s=this.getBaseId();return this.event_hooks[t].forEach(t=>{let i;i=e&&e.sourceID?e:{sourceID:s,target:this,data:e||null},t.call(this,i)}),this}addPanel(t){if("object"!=typeof t)throw new Error("Invalid panel layout");const e=new X(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!isNaN(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"!==p.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&&p.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=M.call(this),this.loader=N.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=p.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=p.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+p.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=p.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=p.drag();e.on("start",()=>{this.dragging=!0}),e.on("end",()=>{this.dragging=!1}),e.on("drag",()=>{this.parent.setDimensions(this.parent.layout.width+p.event.dx,this.parent._total_height+p.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&&p.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 J(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=p.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=p.mouse(this.svg.node());p.event&&p.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=p.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;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 X&&s&&this._canInteract()))return this.stopDrag();const i=p.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 et(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 st(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 it(t,e){if("object"!=typeof t)throw new Error("invalid arguments: data is not an object");if("string"!=typeof e)throw new Error("invalid arguments: html is not a string");const s=[],i=/{{(?:(#if )?([A-Za-z0-9_:|]+)|(\/if))}}/;for(;e.length>0;){const t=i.exec(e);t?0!==t.index?(s.push({text:e.slice(0,t.index)}),e=e.slice(t.index)):"#if "===t[1]?(s.push({condition:t[2]}),e=e.slice(t[0].length)):t[2]?(s.push({variable:t[2]}),e=e.slice(t[0].length)):"/if"===t[3]?(s.push({close:"if"}),e=e.slice(t[0].length)):(console.error(`Error tokenizing tooltip when remaining template is ${JSON.stringify(e)} and previous tokens are ${JSON.stringify(s)} and current regex match is ${JSON.stringify([t[1],t[2],t[3]])}`),e=e.slice(t[0].length)):(s.push({text:e}),e="")}const a=function(){const t=s.shift();if(void 0!==t.text||t.variable)return t;if(t.condition){for(t.then=[];s.length>0;){if("if"===s[0].close){s.shift();break}t.then.push(a())}return t}return console.error("Error making tooltip AST due to unknown token "+JSON.stringify(t)),{text:""}},n=[];for(;s.length>0;)n.push(a());const o=function(e){return Object.prototype.hasOwnProperty.call(o.cache,e)||(o.cache[e]=new z(e).resolve(t)),o.cache[e]};o.cache={};const r=function(t){if(void 0!==t.text)return t.text;if(t.variable){try{const e=o(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{const e=o(t.condition);if(e||0===e)return t.then.map(r).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(r).join("")}const at=new l;at.add("=",(t,e)=>t===e),at.add("!=",(t,e)=>t!=e),at.add("<",(t,e)=>tt<=e),at.add(">",(t,e)=>t>e),at.add(">=",(t,e)=>t>=e),at.add("%",(t,e)=>t%e),at.add("in",(t,e)=>e&&e.includes(t)),at.add("match",(t,e)=>t&&t.includes(e));var nt=at;const ot=(t,e)=>void 0===e||t.field_value!==e?void 0!==t.else?t.else:null:t.then,rt=(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,ht=(t,e,s)=>{const i=t.values;return i[s%i.length]};let ct=(t,e,s)=>{const i=t.values,a=t._cache=t._cache||new Map,n=t.max_cache_size||500;if(a.size>=n&&a.clear(),a.has(e))return a.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 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)?p.interpolate(i[n-1],i[n])(t):a}},ut=new l;for(let[t,e]of Object.entries(n))ut.add(t,e);ut.add("if",ot);var pt=ut;const _t={type:"",filters:null,match:{},fields:[],x_axis:{},y_axis:{},tooltip_positioning:"horizontal"};class gt{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=Object(k.c)(t||{},_t),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=Object(k.b)(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,p.extent(t,t=>+new z(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=p.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=nt.get(this.layout.match&&this.layout.match.operator||"="),s=this.parent_plot.state.lz_match_value,i=t?new z(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),f="down",m=c.height-1):(g=h.y+_+v+8,f="up",m=-8)}else{if("left"!==w&&"right"!==w)throw new Error("Unrecognized placement value");"left"===w?(y=h.x+p+x+8,f="left",b=-8):(y=h.x+p-c.width-x-8,f="right",b=c.width-1),_-c.height/2<=0?(g=h.y+_-10.5-6,m=6):_+c.height/2>=d?(g=h.y+_+7+6-c.height,m=c.height-14-6):(g=h.y+_-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=nt.get(i),r=this.layer_state.extra_fields[this.getElementId(e)];o(new z(s).resolve(e,r),n)||(a=!1)}),a}getElementAnnotation(t,e){const s=this.getElementId(t),i=this.layer_state.extra_fields[s];return i&&i[e]}_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;_.adjectives.forEach(t=>{e[t]=e[t]||[]}),e.has_tooltip=e.has_tooltip||[],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:p.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.push(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(it(t,this.layout.tooltip.html)),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){const t=this.layer_state.status_flags.has_tooltip,e=t.indexOf(s);t.splice(e,1)}return this}destroyAllTooltips(){for(let t in this.tooltips)this.destroyTooltip(t,!0);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={};_.adjectives.forEach(t=>{const e="un"+t;r[t]=o.status_flags[t].includes(s),r[e]=!r[t]});const l=i(r,a),h=i(r,n),c=o.status_flags.has_tooltip.includes(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),p.select("#"+a).classed(`lz-data_layer-${this.layout.type}-${t}`,s);const n=this.getElementStatusNodeId(e);null!==n&&p.select("#"+n).classed(`lz-data_layer-${this.layout.type}-statusnode-${t}`,s);const o=this.layer_state.status_flags[t].indexOf(a),r=-1===o;s&&r&&this.layer_state.status_flags[t].push(a),s||r||this.layer_state.status_flags[t].splice(o,1),this.showOrHideTooltip(e,r),r&&this.parent.emit("layout_changed",!0);const l="selected"===t;!l||!r&&s||this.parent.emit("element_selection",{element:e,active:s},!0);const h=this.layout.match&&this.layout.match.send;return!l||void 0===h||!r&&s||this.parent.emit("match_requested",{value:new z(h).resolve(e),active:s},!0),this}setAllElementStatus(t,e){if(void 0===t||!_.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{this.layer_state.status_flags[t].slice().forEach(e=>{const s=this.getElementById(e);"object"==typeof s&&null!==s&&this.setElementStatus(t,s,!1)}),this.layer_state.status_flags[t]=[]}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||p.select(p.event.target).datum(),s===!!p.event.ctrlKey&&i===!!p.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].includes(a.getElementId(t)),i=e.exclusive&&!s;a.setElementStatus(e.status,t,!s,i);break;case"link":if("string"==typeof e.href){const s=it(t,e.href);"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)&&Array.isArray(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})}}_.verbs.forEach((t,e)=>{const s=_.adjectives[e],i="un"+t;gt.prototype[t+"Element"]=function(t,e){return e=void 0!==e&&!!e,this.setElementStatus(s,t,!0,e),this},gt.prototype[i+"Element"]=function(t,e){return e=void 0!==e&&!!e,this.setElementStatus(s,t,!1,e),this},gt.prototype[t+"AllElements"]=function(){return this.setAllElementStatus(s,!0),this},gt.prototype[i+"AllElements"]=function(){return this.setAllElementStatus(s,!1),this}});const yt={color:"#000000",filters:null,tooltip_positioning:"vertical",hitarea_width:8};class ft extends gt{constructor(t){if(!Array.isArray(t.filters))throw new Error("Annotation track must specify array of filters for selecting points to annotate");Object(k.c)(t,yt),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 mt={color:"seagreen",hitarea_width:"10px",style:{fill:"none","stroke-width":"1px","stroke-opacity":"100%"},tooltip_positioning:"top"};class bt extends gt{constructor(t){t=Object(k.c)(t,mt),super(...arguments)}render(){const t=this.layout,e=this.parent.x_scale,s=this.parent[`y${t.y_axis.axis}_scale`],i=this._applyFilters();function a(i){const a=i[t.x_axis.field1],n=i[t.x_axis.field2],o=(a+n)/2,r=[[e(a),s(0)],[e(o),s(i[t.y_axis.field])],[e(n),s(0)]];return p.line().x(t=>t[0]).y(t=>t[1]).curve(p.curveNatural)(r)}const n=this.svg.group.selectAll("path.lz-data_layer-arcs-hitarea").data(i,t=>this.getElementId(t)),o=this.svg.group.selectAll("path.lz-data_layer-arcs").data(i,t=>this.getElementId(t));return this.svg.group.call(S,t.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",t.hitarea_width).style("stroke-opacity",0).style("stroke","transparent").attr("d",t=>a(t)),o.enter().append("path").attr("class","lz-data_layer-arcs").merge(o).attr("id",t=>this.getElementId(t)).attr("stroke",(t,e)=>this.resolveScalableParameter(this.layout.color,t,e)).attr("d",(t,e)=>a(t)),o.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],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 xt={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 vt extends gt{constructor(t){t=Object(k.c)(t,xt),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=p.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=p.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=p.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=p.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=p.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=p.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 zt extends gt{constructor(t){if((t=Object(k.c)(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?p.area().x(t=>+n(t[e])).y0(+o(0)).y1(t=>+o(t[s])):p.line().x(t=>+n(t[e])).y(t=>+o(t[s])).curve(p[this.layout.interpolate]),i.merge(this.path).attr("d",a).call(S,this.layout.style),i.exit().remove()}setElementStatus(t,e,s){return this.setAllElementStatus(t,s)}setAllElementStatus(t,e){if(void 0===t||!_.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 kt={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 Et extends gt{constructor(t){t=Object(k.c)(t,kt),["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=p.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(S,this.layout.style).call(this.applyBehaviors.bind(this)),i.exit().remove()}_getTooltipPosition(t){try{const t=p.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 Mt={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 Nt extends gt{constructor(t){(t=Object(k.c)(t,Mt)).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=p.select(this);if(+r.attr("x")+r.node().getBoundingClientRect().width+s>n){const e=i?p.select(t.label_lines.nodes()[a]):null;o(r,e)}})),t.label_texts.each((function(e,n){const r=p.select(this);if("end"===r.style("text-anchor"))return;let l=+r.attr("x");const h=r.node().getBoundingClientRect(),c=i?p.select(t.label_lines.nodes()[n]):null;t.label_texts.each((function(){const t=p.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.topg?(y=d-+n,d=+n,u-=y):u+l.height/2>g&&(y=u-+h,u=+h,d-=y),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)=>p.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],f=g>=e&&g<=s&&y>=a&&y<=n;if(t.lz_is_match||!f)p(),r.push(t);else if(null===c)_(g,y,t);else{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=>it(e,t.layout.label.text||"")).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(S,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(S,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=p.symbol().size((t,e)=>this.resolveScalableParameter(this.layout.point_size,t,e)).type((t,e)=>Object(k.d)(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=p.select(p.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");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_plot.applyState({ldrefvar:e})}}class St extends Nt{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 Ot=new h;for(let[t,e]of Object.entries(o))Ot.add(t,e);var $t=Ot;const jt={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 Make LD Reference
'},At=function(){const t=Object(k.b)(jt);return t.html+="Toggle label",t}(),Tt={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'},Lt={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'},Dt={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}}"},Pt={id:"significance",type:"orthogonal_line",orientation:"horizontal",offset:7.301},Rt={namespace:{recomb:"recomb"},id:"recombrate",type:"line",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}},Ct={namespace:{assoc:"assoc",ld:"ld"},id:"associationpvalues",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",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:["#357ebd","#46b8da","#5cb85c","#eea236","#d43f3a"]}},"#B8B8B8"],legend:[{shape:"diamond",color:"#9632b8",size:40,label:"LD Ref Var",class:"lz-data_layer-scatter"},{shape:"circle",color:"#d43f3a",size:40,label:"1.0 > r² ≥ 0.8",class:"lz-data_layer-scatter"},{shape:"circle",color:"#eea236",size:40,label:"0.8 > r² ≥ 0.6",class:"lz-data_layer-scatter"},{shape:"circle",color:"#5cb85c",size:40,label:"0.6 > r² ≥ 0.4",class:"lz-data_layer-scatter"},{shape:"circle",color:"#46b8da",size:40,label:"0.4 > r² ≥ 0.2",class:"lz-data_layer-scatter"},{shape:"circle",color:"#357ebd",size:40,label:"0.2 > r² ≥ 0.0",class:"lz-data_layer-scatter"},{shape:"circle",color:"#B8B8B8",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:Object(k.b)(jt)},It={namespace:{access:"access"},id:"coaccessibility",type:"arcs",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:"#ff0000"}},{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:Object(k.b)(Dt)},Bt=function(){let t=Object(k.b)(Ct);return t=Object(k.c)({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}(),Ut={namespace:{phewas:"phewas"},id:"phewaspvalues",type:"category_scatter",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"}}},Ft={namespace:{gene:"gene",constraint:"constraint"},id:"genes",type:"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:Object(k.b)(Tt)},qt=Object(k.c)({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"]}]},Object(k.b)(Ft)),Gt={namespace:{assoc:"assoc",catalog:"catalog"},id:"annotation_catalog",type:"annotation_track",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:7.301}],behaviors:{onmouseover:[{action:"set",status:"highlighted"}],onmouseout:[{action:"unset",status:"highlighted"}],onclick:[{action:"toggle",status:"selected",exclusive:!0}]},tooltip:Object(k.b)(Lt),tooltip_positioning:"top"},Ht={type:"set_state",position:"right",color:"blue",button_html:"LD Population: ",show_selected:!0,button_title:"Select LD Population: ",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"}]},Zt={type:"display_options",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}}]},Kt={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"}}]},Jt={widgets:[{type:"title",title:"LocusZoom",subtitle:'v0.13.0-beta.4',position:"left"},{type:"download",position:"right",group_position:"end"},{type:"download_png",position:"right",group_position:"start"}]},Vt=function(){const t=Object(k.b)(Jt);return t.widgets.push(Object(k.b)(Ht)),t}(),Yt=function(){const t=Object(k.b)(Jt);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}(),Wt={id:"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=Object(k.b)(Kt);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:[Object(k.b)(Pt),Object(k.b)(Rt),Object(k.b)(Ct)]},Xt={id:"coaccessibility",min_height:150,height:180,margin:{top:35,right:50,bottom:40,left:50},inner_border:"rgb(210, 210, 210)",toolbar:Object(k.b)(Kt),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:[Object(k.b)(It)]},Qt=function(){let t=Object(k.b)(Wt);return t=Object(k.c)({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:7.301},{field:"{{namespace[ld]}}state",operator:">",value:.4}],style:{"font-size":"10px","font-weight":"bold",fill:"#333333"}}}}]}),t.data_layers=[Object(k.b)(Pt),Object(k.b)(Rt),Object(k.b)(Bt)],t}(),te={id:"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=Object(k.b)(Kt);return t.widgets.push({type:"resize_to_data",position:"right",button_html:"Resize"},Object(k.b)(Zt)),t}(),data_layers:[Object(k.b)(qt)]},ee={id:"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:[Object(k.b)(Pt),Object(k.b)(Ut)]},se={id:"annotationcatalog",min_height:45,height:45,margin:{top:25,right:50,bottom:0,left:50},inner_border:"rgb(210, 210, 210)",toolbar:Object(k.b)(Kt),interaction:{drag_background_to_pan:!0,scroll_to_zoom:!0,x_linked:!0},data_layers:[Object(k.b)(Gt)]},ie={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Vt,panels:[Object(k.b)(Wt),Object(k.b)(te)]},ae={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Vt,panels:[se,Qt,te]},ne={width:800,responsive_resize:!0,toolbar:Jt,panels:[Object(k.b)(ee),Object(k.c)({height:300,margin:{bottom:40},axes:{x:{label:"Chromosome {{chr}} (Mb)",label_offset:32,tick_format:"region",extent:"state"}}},Object(k.b)(te))],mouse_guide:!1},oe={state:{},width:800,responsive_resize:!0,min_region_scale:2e4,max_region_scale:1e6,toolbar:Object(k.b)(Jt),panels:[Object(k.b)(Xt),function(){const t=Object.assign({height:270},Object(k.b)(te)),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:"#ff0000"}},{field:"lz_is_match",scale_function:"if",parameters:{field_value:!1,then:"#EAE6E6"}},"#363696"];return e.color=s,e.stroke=s,t}()]},re={standard_association:jt,standard_association_with_label:At,standard_genes:Tt,catalog_variant:Lt,coaccessibility:Dt},le={ldlz2_pop_selector:Ht,gene_selector_menu:Zt},he={standard_panel:Kt,standard_plot:Jt,standard_association:Vt,region_nav_plot:Yt},ce={significance:Pt,recomb_rate:Rt,association_pvalues:Ct,coaccessibility:It,association_pvalues_catalog:Bt,phewas_pvalues:Ut,genes:Ft,genes_filtered:qt,annotation_catalog:Gt},de={association:Wt,coaccessibility:Xt,association_catalog:Qt,genes:te,phewas:ee,annotation_catalog:se},ue={standard_association:ie,association_catalog:ae,standard_phewas:ne,coaccessibility:oe};const pe=new class extends l{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=Object(k.c)(s,i),i.unnamespaced)return delete i.unnamespaced,Object(k.b)(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?":":"";const n=Object(k.a)(i,i.namespace,a);return Object(k.b)(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 l);const a=Object(k.b)(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 Object(k.c)(t,e)}};for(let[t,e]of Object.entries(r))for(let[s,i]of Object.entries(e))pe.add(t,s,i);var _e=pe;const ge={version:"0.13.0-beta.4",populate:function(t,e,s){if(void 0===t)throw new Error("LocusZoom.populate selector not defined");let i;return p.select(t).html(""),p.select(t).call((function(t){if(void 0===t.node().id){let e=0;for(;!p.select("#lz-"+e).empty();)e++;t.attr("id","#lz-"+e)}if(i=new tt(t.node().id,e,s),i.container=t.node(),void 0!==t.node().dataset&&void 0!==t.node().dataset.region){const e=function(t){let e=/^(\w+):([\d,.]+[kmgbKMGB]*)([-+])([\d,.]+[kmgbKMGB]*)$/.exec(t);if(e){if("+"===e[3]){const t=st(e[2]),s=st(e[4]);return{chr:e[1],start:t-s,end:t+s}}return{chr:e[1],start:st(e[2]),end:st(e[4])}}if(e=/^(\w+):([\d,.]+[kmgbKMGB]*)$/.exec(t),e)return{chr:e[1],position:st(e[2])};return null}(t.node().dataset.region);Object.keys(e).forEach((function(t){i.state[t]=e[t]}))}i.svg=p.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(S,i.layout.style),i.setDimensions(),i.positionPanels(),i.initialize(),e&&i.refresh()})),i},DataSources:class extends l{constructor(t){super(),this._registry=t||u}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:u,DataLayers:$t,Layouts:_e,MatchFunctions:nt,ScaleFunctions:pt,TransformationFunctions:w,Widgets:K,get KnownDataSources(){return console.warn('Deprecation warning: KnownDataSources has been renamed to "Adapters"'),u}},ye=[];ge.use=function(t,...e){if(!ye.includes(t)){if(e.unshift(ge),"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)}ye.push(t)}};e.default=ge},2:function(t,e,s){"use strict";function i(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")}s.r(e),s.d(e,"BaseAdapter",(function(){return a})),s.d(e,"BaseApiAdapter",(function(){return n})),s.d(e,"AssociationLZ",(function(){return o})),s.d(e,"ConnectorSource",(function(){return _})),s.d(e,"GeneConstraintLZ",(function(){return c})),s.d(e,"GeneLZ",(function(){return h})),s.d(e,"GwasCatalogLZ",(function(){return l})),s.d(e,"LDServer",(function(){return r})),s.d(e,"PheWASLZ",(function(){return p})),s.d(e,"RecombLZ",(function(){return d})),s.d(e,"StaticSource",(function(){return u}));class a{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 n extends a{parseInit(t){if(super.parseInit(t),this.url=t.url,!this.url)throw new Error("Source not initialized with required URL")}}class o extends n{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 r extends n{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 l extends n{constructor(t){super(t),this.__dependentSource=!0}getURL(t,e,s){const a=t.genome_build||this.params.build;i(this.constructor.name,a,null);const n="GRCh38"===a?5:6,o=this.params.source||n;return`${this.url}?format=objects&sort=pos&filter=id eq ${o} and chrom eq '${t.chr}' and pos ge ${t.start} and pos le ${t.end}`}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=i[s.indexOf("log_pvalue")];function o(t,e,s,i,a){const o=t.n_catalog_matches||0;if(t.n_catalog_matches=o+1,!(t[n]&&t[n]>e.log_pvalue))for(let n=0;nt.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 d extends n{getURL(t,e,s){const a=t.genome_build||this.params.build;let n=this.params.source;return i(this.constructor.SOURCE_NAME,a,n),a&&(n="GRCh38"===a?16:15),`${this.url}?filter=id in ${n} and chromosome eq '${t.chr}' and position le ${t.end} and position ge ${t.start}`}}class u extends a{parseInit(t){this._data=t}getRequest(t,e,s){return Promise.resolve(this._data)}}class p extends n{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(["Data source",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 _ extends a{constructor(t){if(super(t),!t||!t.sources)throw new Error("Connectors must specify the data they require as init.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")}}}}).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 41cc52d9..54bd5565 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]/external \"d3\"","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/registry/base.js","webpack://[name]/./esm/registry/adapters.js","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/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/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/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/index.js","webpack://[name]/./esm/version.js","webpack://[name]/./esm/data/sources.js","webpack://[name]/./esm/data/adapters.js"],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","d3","sqrt3","Math","sqrt","triangledown","context","size","y","moveTo","lineTo","closePath","applyNamespaces","element","namespace","default_namespace","default","re","match","base","resolved_namespace","replace","exec","length","push","merge","namespaced_element","namespaced_property","custom_layout","default_layout","Error","custom_type","default_type","Array","isArray","deepCopy","item","JSON","parse","stringify","nameToSymbol","shape","factory_name","charAt","toUpperCase","slice","RegistryBase","this","_items","Map","has","override","set","delete","from","keys","ClassRegistry","args","parent_name","source_name","overrides","console","warn","arguments","sub","assign","add","type","entries","adapters","STATUSES","verbs","adjectives","log10","isNaN","log","LN10","neglog10","logtoscinotation","exp","ceil","diff","pow","toFixed","scinotation","abs","floor","toExponential","htmlescape","urlencode","encodeURIComponent","template_string","funcs","map","super","substring","reduce","acc","func","_collectTransforms","field","parts","full_name","transformations","split","forEach","transform","transforms","val","data","extra","_applyTransformations","sources","_sources","fields","requests","raw","trans","outnames","state","__split_requests","request_handles","getData","ret","Promise","resolve","header","body","discrete","then","generateCurtain","showing","selector","content_selector","hide_delay","show","content","css","curtain","parent_plot","svg","node","parentNode","insert","attr","id","append","html","on","hide","update","clearTimeout","applyStyles","page_origin","_getPageOrigin","style","x","layout","width","height","delay","setTimeout","remove","generateLoader","progress_selector","cancel_selector","loader","percent","loader_boundrect","getBoundingClientRect","min","max","animate","classed","setPercentCompleted","selection","styles","prop","parent","color","parent_panel","parent_svg","button","persist","position","group_position","includes","initialize","status","menu","shouldPersist","force","destroy","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","left","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","toString","getClass","preUpdate","postUpdate","Title","div_selector","title_selector","subtitle","display_width","display_height","class","start","end","positionIntToString","_data_layer","data_layers","layer_name","_field","_field_display_html","field_display_html","_operator","operator","_filter_id","_data_type","data_type","_value_selector","filters","result","find","index","indexOf","_getTarget","splice","_clearFilter","Number","text","input_size","timer","apply","debounce","_getValue","_setFilter","render","_filename","filename","_button_html","button_html","_button_title","button_title","setColor","setHtml","setTitle","setOnMouseover","_getBlobUrl","url","old","URL","revokeObjectURL","setOnMouseout","root","ancestor_pattern","extractedCSSText","styleSheets","cssRules","e","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","suppress_confirm","confirm","panel","toolbar","removePanel","MovePanelUp","is_at_top","y_index","disable","moveUp","MovePanelDown","is_at_bottom","panel_ids_by_y_index","moveDown","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","allowed_fields","fields_whitelist","dataLayer","dataLayerLayout","defaultConfig","configSlot","undefined","_selected_item","uniqueID","random","table","menuLayout","renderRow","display_name","display_options","row_id","row","radioId","field_name","has_option","defaultName","default_config_display_name","options","display","SetState","state_field","show_selected","new_state","widgets","hide_timeout","dashboard","components","widget","error","panel_boundaries","dragging","interaction","orientation","origin","padding","label_size","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_width","min_height","proportional_width","proportional_height","proportional_origin","margin","right","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","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","emit","parseFloat","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","method","dragged_x","shiftKey","start_x","y_shifted","dragged_y","start_y","domain","renderAxis","zoom_handler","altKey","_canInteract","preventDefault","coords","wheelDelta","detail","deltaY","getLinkedPanelIds","data_layer_id","draw","show_immediately","plot_origin","panel_count","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","sort","applyPanelYIndexesToPanelLayouts","positionPanels","reMap","message","all","catch","decoupled","concat","getAxisExtent","extent","ticks","baseTickConfig","self","config","nextLayer","getTicks","itemConfig","clip_range","target_tick_count","parseInt","min_n","base_toFixed","unit","pop","prettyTicks","canRender","axis_params","label_offset","label_rotate","generateTicks","ticksAreAllNumbers","axis_factory","tickPadding","tickValues","tick_format","tickFormat","substr","tick_selector","parseFields","tick_mouseover","focus","cursor","target_height","dh","getAbsoluteDataHeight","toggle","verb","adjective","antiverb","responsive_resize","mouse_guide","datasource","remap_promises","_base_layout","lzd","_external_listeners","panel_layout","panelId","panelsList","pid","layer","layer_state","_setDefaultState","clearPanelData","success_callback","opts","error_callback","onerror","err","listener","new_data","state_changes","mods","chr","attempted_scale","validated_region","attempted_midpoint","temp","_updateStatePosition","loading_data","applyAllElementStatus","some","event_name","tracker","registered_events","listeners","removeEventListener","lastElementChild","removeChild","outerHTML","bounding_client_rect","x_offset","scrollLeft","y_offset","offset","offsetParent","offsetTop","offsetLeft","dimension","total","clientRect","resize_listener","rescaleSVG","window","addEventListener","trackExternalListener","load_listener","addPanel","panel_width","panel_height","x_linked_margins","total_proportional_height","sumProportional","proportional_adjustment","calculated_plot_height","mouse_guide_svg","mouse_guide_vertical_svg","mouse_guide_horizontal_svg","vertical","horizontal","selectors","corner_selector","panel_idx","panel_resize_drag","this_panel","original_panel_height","panel_height_change","new_calculated_plot_height","loop_panel_id","loop_panel_idx","loop_panel","corner_drag","dx","plot_page_origin","panel_page_origin","mouseout_mouse_guide","mousemove_mouse_guide","mouseup","stopDrag","mousemove","body_node","to_send","active","lz_match_value","client_rect","overrideAxisLayout","axis_number","axis_layout","ceiling","lower_buffer","upper_buffer","min_extent","y_axis_number","pos","suffix","exp_symbols","0","3","6","9","places_exp","min_exp","places","positionStringToInt","suffixre","mult","tokens","regex","condition","variable","close","astify","token","shift","ast","cache","render_node","join","if_value","parameters","input","field_value","else","numerical_bin","breaks","values","null_value","threshold","prev","curr","categorical_bin","categories","ordinal_cycle","interpolate","nullval","upper_idx","brk","normalized_input","isFinite","tooltip_positioning","_base_id","_filter_func","tooltip","tooltips","global_statuses","resortDataLayers","getElementId","extra_fields","axis_config","id_key","for","id_field","element_id","empty","field_to_match","receive","broadcast_value","lz_highlight_match","toHTML","getDataLayer","getPanel","getPlot","deselect","unselectElement","applyCustomDataMethods","element_data","data_index","resolveScalableParameter","scale_function","scalable","f","axis_name","data_extent","_getDataExtent","original_extent_span","range_min","range_max","y_scale","y_extent","x_min","x_max","y_min","y_max","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","array","test","filter","a","b","status_flags","updateTooltip","positionTooltip","closable","destroyTooltip","element_or_id","temporary","label_mark_position","_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","element_status_idx","added_status","showOrHideTooltip","is_selected","value_to_broadcast","send","setElementStatus","getElementById","behaviors","event_match","executeBehaviors","requiredKeyStates","datum","ctrlKey","behavior","action","current_status_boolean","href","open","location","panel_origin","positionAllTooltips","applyDataMethods","hitarea_width","_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","_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","transcript_idx","tracks","gene_track_index","1","_getLabelWidth","gene_name","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","gene","bboxes","getTrackHeight","boundaries","labels","strand","exon_id","clickareas","gene_bbox_id","gene_bbox","x_field","y_field","path","y0","path_class","global_status","default_orthogonal_layout","default_y","point_size","point_shape","coalesce","max_points","x_gap","y_gap","fill_opacity","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","ref","ldrefvar","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","every","colors","categoryBounds","knownCategories","knownColors","xPos","_prepareData","_generateCategoryBounds","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","standard_phewas","unnamespaced","contents","list","LocusZoom","version","plot","iterator","dataset","region","parsed_state","parsePositionQuery","refresh","DataSources","registry","_registry","source_id","Adapters","DataLayers","Layouts","ScaleFunctions","TransformationFunctions","Widgets","INSTALLED_PLUGINS","use","plugin","unshift","install","validateBuildSource","class_name","build","source","BaseAdapter","_enableCache","_cachedKey","__dependentSource","parseInit","params","chain","getURL","fetch","response","ok","statusText","req","cacheKey","getCacheKey","_cachedResponse","fetchRequest","N","constructor","records","record","j","fieldFound","k","output_record","v","resp","json","normalizeResponse","standardized","annotateData","extractFields","one_source_body","combineChainBody","new_body","preGetData","pre","getRequest","parseResponse","BaseApiAdapter","AssociationLZ","analysis","LDServer","dataFields","position_field","pvalue","pvalue_field","_names_","names","nameMatch","arr","regexes","id_match","RegExp","obj","isrefvarin","isrefvarout","ldin","ldout","refVar","findRequestedFields","findMergeFields","columns","pval_field","cmp","extremeVal","extremeIdx","findExtremeValue","genome_build","ld_source","population","ld_pop","getRefvar","original","chrom","alt","reqFields","corrField","rsquare","lfield","rfield","position2","leftJoin","refvar","idfield","outrefname","outldname","tagRefVariant","combined","chainRequests","payload","next","GwasCatalogLZ","build_option","default_source","posMatch","decider_out","n_matches","fn","outn","chainNames","catNames","GeneLZ","GeneConstraintLZ","unique_gene_names","query","headers","alias","constraint","RecombLZ","SOURCE_NAME","StaticSource","_data","PheWASLZ","variant","ConnectorSource","_source_name_mapping","specified_ids","_getRequiredSources","chain_source_id"],"mappings":"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","file":"locuszoom.app.min.js","sourcesContent":[" \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, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\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\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 13);\n","module.exports = d3;","/**\n * Utilities for modifying or working with layout objects\n * @module\n */\nimport * as d3 from 'd3';\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 * @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\nexport { applyNamespaces, deepCopy, merge, nameToSymbol };\n","/** @module */\n\n/**\n * Base class for all registries\n *\n * LocusZoom is plugin-extensible, and layouts are string-based and JSON serializable. This is achieved through the use\n * of a central registry that holds a reference to each possible feature.\n *\n * Each registry has some syntactical sugar, with common elements are defined in a base class\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 */\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 * A registry of known data sources. Can be used to find sources by name, either from predefined\n * classes, or plugins.\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\n\nimport * as adapters from '../data/adapters';\n\n\n// KnownDataSources is a basic registry with no special behavior.\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\nregistry.add('StaticJSON', adapters.StaticSource);\nregistry.add('LDLZ2', adapters.LDServer);\n\n\nexport default registry;\n","/**\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 value from the API. For example, a template or axis label\n * can convert from pvalue to -log10pvalue\n * @module\n */\n\n/**\n * Return the log10 of a value. Can be composed for, eg, loglog plots.\n * @param 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 * @function neglog10\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 * @function logtoscinotation\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 * @function scinotation\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\n * @function htmlescape\n * @param {String} value HTML-escape the provided value\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 * URL-encode the provided text, eg for constructing hyperlinks\n * @function urlencode\n * @param {String} value\n */\nexport function urlencode (value) {\n return encodeURIComponent(value);\n}\n","/**\n * @module\n * @private\n */\nimport {RegistryBase} from './base';\nimport * as transforms from '../helpers/transforms';\n\n/**\n * Registry of transformation functions that may be applied to template values.\n * Provides syntactic sugar atop a standard registry.\n * @private\n */\nclass TransformationFunctions extends RegistryBase {\n _collectTransforms(template_string) {\n // Helper function that turns a sequence of function names into a single callable\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\nconst registry = new TransformationFunctions();\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 { TransformationFunctions as _TransformationFunctions };\n","/** @module */\nimport 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 * @module\n * @private\n */\nimport { 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 kind of data source.\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 this.curtain.selector\n .style('top', `${page_origin.y}px`)\n .style('left', `${page_origin.x}px`)\n .style('width', `${this.layout.width}px`)\n .style('height', `${this.layout.height}px`);\n this.curtain.content_selector\n .style('max-width', `${this.layout.width - 40}px`)\n .style('max-height', `${this.layout.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 /* Uncomment this code when a functional cancel button can be shown\n var cancel_boundrect = this.loader.cancel_selector.node().getBoundingClientRect();\n this.loader.content_selector.style({\n \"padding-right\": (cancel_boundrect.width + padding) + \"px\"\n });\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\n\nexport { applyStyles, debounce, generateCurtain, generateLoader };\n","/** @module */\nimport * as d3 from 'd3';\n\nimport {positionIntToString} from '../../helpers/display';\nimport {applyStyles, debounce} from '../../helpers/common';\nimport {deepCopy} from '../../helpers/layouts';\n\n// FIXME: Button creation should occur in the constructors, not in update functions\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 * @param {Object} layout A JSON-serializable object of layout configuration parameters\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 {Toolbar} parent The toolbar that contains this widget\n */\nclass BaseWidget {\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.\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_svg.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_svg.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, 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 title formatting\n * @param {object} layout\n * @param {string} layout.title Text to render\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 * Renders text to display the current dimensions of the plot. Automatically updated as plot dimensions change\n */\nclass Dimensions extends BaseWidget {\n update() {\n const display_width = !this.parent_plot.layout.width.toString().includes('.') ? this.parent_plot.layout.width : this.parent_plot.layout.width.toFixed(2);\n const display_height = !this.parent_plot.layout.height.toString().includes('.') ? this.parent_plot.layout.height : this.parent_plot.layout.height.toFixed(2);\n this.selector.html(`${display_width}px × ${display_height}px`);\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 * Display the current scale of the genome region displayed in the plot, as defined by the difference between\n * `state.end` and `state.start`.\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\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] The number of characters to allow in the text field\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 */\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._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 /** Set the filter based on a provided value */\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 }\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 * Button to export current plot to an SVG image\n */\nclass DownloadSVG extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Download SVG\"]\n * @param {string} [layout.button_title=\"Download image of the current plot as locuszoom.svg\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\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 }\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 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 */\nclass DownloadPNG extends DownloadSVG {\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 }\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 * @param {Boolean} [layout.suppress_confirm=false] If true, removes the panel without prompting user for confirmation\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 */\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 */\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 * @param {object} layout\n * @param {number} [layout.step=50000] The stepsize to change the region by\n * @param {string} [layout.button_html]\n * @param {string} [layout.button_title]\n */\nclass ShiftRegion extends BaseWidget {\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 * @param {object} layout\n * @param {number} [layout.step=0.2] The amount to zoom in by (where 1 indicates 100%)\n */\nclass ZoomRegion extends BaseWidget {\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\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\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 * @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 * @param {string} [layout.button_html=\"Resize to Data\"]\n * @param {string} [layout.button_title]\n */\nclass ResizeToData 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 || '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 */\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 * 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 *\n * @param {object} layout\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.layer_name Specify the datalayer that this button should affect\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 * @typedef {{display_name: string, display: Object}} DisplayOptionsButtonConfigField\n * @param {DisplayOptionsButtonConfigField[]} layout.options Specify a label and set of layout directives associated\n * with this `display` option. Display field should include all changes to datalayer presentation options.\n */\nclass DisplayOptions extends BaseWidget {\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\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._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 * 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 source can use it to change LD reference population (for all panels) after render\n *\n * @param {object} layout\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\n * @param {string} [layout.state_field] The name of the field in plot.state that will be set by this button\n * @typedef {{display_name: string, value: *}} SetStateOptionsConfigField\n * @param {SetStateOptionsConfigField[]} layout.options Specify human labels and associated values for the dropdown menu\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 /**\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 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 Dimensions as dimensions,\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","/**\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\nimport * as widgets from '../components/toolbar/widgets';\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","/** @module */\nimport 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.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","/** @module */\nimport * as d3 from 'd3';\nimport {applyStyles} from '../helpers/common';\nimport {merge, nameToSymbol} from '../helpers/layouts';\n\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.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.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","/** @module */\nimport * 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 * @static\n * @type {Object}\n */\nconst default_layout = {\n title: { text: '', style: {}, x: 10, y: 22 },\n y_index: null,\n width: 0,\n height: 0,\n origin: { x: 0, y: null },\n min_width: 1,\n min_height: 1,\n proportional_width: null,\n proportional_height: null,\n proportional_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, // On by default as of LZ 0.13\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 {Object} layout\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 * @protected\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 * @protected\n * @member {Object}\n */\n this.event_hooks = {\n 'layout_changed': [],\n 'data_requested': [],\n 'data_rendered': [],\n 'element_clicked': [],\n 'element_selection': [],\n 'match_requested': [], // A data layer is attempting to highlight matching points (internal use only)\n };\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 * The following panel-level events are currently supported:\n * - `layout_changed` - context: panel - Any aspect of the panel's layout (including dimensions or state) has changed.\n * - `data_requested` - context: panel - A request for new data from any data source used in the panel has been made.\n * - `data_rendered` - context: panel - Data from a request has been received and rendered in the panel.\n * - `element_clicked` - context: panel - A data element in any of the panel's data layers has been clicked.\n * - `element_selection` - context: panel - Triggered when an element changes \"selection\" status, and identifies\n * whether the element is being selected or deselected.\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. The this context bound to each event hook function is dependent on the type of event, as\n * denoted above. For example, when data_requested is emitted the context for this in the event hook will be the\n * panel itself, but when element_clicked is emitted the context for this in the event hook will be the element\n * that was clicked.\n *\n * @public\n * @param {String} event The name of the event (as defined in `event_hooks`)\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' || !Array.isArray(this.event_hooks[event])) {\n throw new Error(`Unable to register event hook, invalid event: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\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 * @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' || !Array.isArray(this.event_hooks[event])) {\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 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 if (bubble && this.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 * @public\n * @param {object} layout\n * @returns {*}\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.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.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 return this;\n }\n\n /**\n * Add a \"basic\" loader to a panel\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 * @public\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\n // If the layout is missing BOTH width and proportional width then set the proportional width to 1.\n // This will default the panel to taking up the full width of the plot.\n if (this.layout.width === 0 && this.layout.proportional_width === null) {\n this.layout.proportional_width = 1;\n }\n\n // If the layout is missing BOTH height and proportional height then set the proportional height to\n // an equal share of the plot's current height.\n if (this.layout.height === 0 && this.layout.proportional_height === null) {\n const panel_count = Object.keys(this.parent.panels).length;\n if (panel_count > 0) {\n this.layout.proportional_height = (1 / panel_count);\n } else {\n this.layout.proportional_height = 1;\n }\n }\n\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.layout.width = Math.max(Math.round(+width), this.layout.min_width);\n this.layout.height = Math.max(Math.round(+height), this.layout.min_height);\n }\n } else {\n if (this.layout.proportional_width !== null) {\n this.layout.width = Math.max(this.layout.proportional_width * this.parent.layout.width, this.layout.min_width);\n }\n if (this.layout.proportional_height !== null) {\n this.layout.height = Math.max(this.layout.proportional_height * this.parent.layout.height, this.layout.min_height);\n }\n }\n this.layout.cliparea.width = Math.max(this.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.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 (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.layout.width) {\n extra = Math.floor(((this.layout.margin.left + this.layout.margin.right) - this.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.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.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 /** @member {Object} */\n this.curtain = generateCurtain.call(this);\n /** @member {Object} */\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 * @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 /** @member {Element} */\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 * @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 * @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\n return this;\n\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.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(this.state, label))\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.layout.width, target_height);\n this.parent.setDimensions();\n this.parent.panel_ids_by_y_index.forEach((id) => {\n this.parent.panels[id].layout.proportional_height = null;\n });\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 */\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 {Object} data\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 * Since this is only an existence check, **variables with a value of 0 will be evaluated as true**.\n * This can be used with namespaced values, `{{#if assoc:field}}`; any dynamic namespacing will be applied when the\n * layout is first retrieved.\n * @returns {string}\n */\nfunction parseFields(data, html) {\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_:|]+)|(\\/if))}}/;\n while (html.length > 0) {\n const m = regex.exec(html);\n if (!m) {\n tokens.push({text: html}); html = '';\n } else if (m.index !== 0) {\n tokens.push({text: html.slice(0, m.index)}); html = html.slice(m.index);\n } else if (m[1] === '#if ') {\n tokens.push({condition: m[2]}); html = html.slice(m[0].length);\n } else if (m[2]) {\n tokens.push({variable: m[2]}); html = html.slice(m[0].length);\n } else if (m[3] === '/if') {\n tokens.push({close: 'if'}); 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 token.then = [];\n while (tokens.length > 0) {\n if (tokens[0].close === 'if') {\n tokens.shift();\n break;\n }\n token.then.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);\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 || condition === 0) {\n return node.then.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 * @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 {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","/** @module */\nimport * 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 * @protected\n * @static\n * @type {Object}\n */\nconst default_layout = {\n state: {},\n width: 1,\n height: 1,\n min_width: 1,\n min_height: 1,\n responsive_resize: false, // Allowed values: false, \"width_only\" (synonym for true)\n panels: [],\n toolbar: {\n widgets: [],\n },\n panel_boundaries: true,\n mouse_guide: true,\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 * @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 (!isNaN(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 (!isNaN(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 A JSON-serializable object of layout configuration parameters\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 * @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 * @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 * @protected\n * @member {Object}\n */\n this.event_hooks = {\n 'layout_changed': [], // Many rerendering operations, including dimensions changed, element highlighted, or rerender on chanegd data. Caution: Direct layout mutations might not be captured by this event.\n 'data_requested': [], // A request has been made for new data from any data source used in the plot\n 'data_rendered': [], // Data from a request has been received and rendered in the plot\n 'element_clicked': [], // Select or unselect\n 'element_selection': [], // Element becomes active (only)\n 'match_requested': [], // A data layer is attempting to highlight matching points (internal use only)\n 'panel_removed': [], // A panel has been removed (eg via the \"x\" button in plot)\n 'region_changed': [], // The viewing region (chr/start/end) has been changed\n 'state_changed': [], // Only triggered when a state change causes rerender\n };\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 * The following plot-level events are currently supported:\n * - `layout_changed` - context: plot - Any aspect of the plot's layout (including dimensions or state) has changed.\n * - `data_requested` - context: plot - A request for new data from any data source used in the plot has been made.\n * - `data_rendered` - context: plot - Data from a request has been received and rendered in the plot.\n * - `element_clicked` - context: plot - A data element in any of the plot's data layers has been clicked.\n * - `element_selection` - context: plot - Triggered when an element changes \"selection\" status, and identifies\n * whether the element is being selected or deselected.\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. The this context bound to each event hook function is dependent on the type of event, as\n * denoted above. For example, when data_requested is emitted the context for this in the event hook will be the\n * plot itself, but when element_clicked is emitted the context for this in the event hook will be the element\n * that was clicked.\n *\n * @public\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n if (typeof 'event' != 'string' || !Array.isArray(this.event_hooks[event])) {\n throw new Error(`Unable to register event hook, invalid event: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\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 {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 * @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 if (typeof 'event' != 'string' || !Array.isArray(this.event_hooks[event])) {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n }\n const sourceID = this.getBaseId();\n this.event_hooks[event].forEach((hookToRun) => {\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 // 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 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.layout.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 * @public\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 * @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 // Allow the plot to shrink when panels are removed, by forcing it to recalculate min dimensions from scratch\n this.layout.min_height = this._base_layout.min_height;\n this.layout.min_width = this._base_layout.min_width;\n\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.layout.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 * @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 source 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 state_changes\n * @returns {Promise} A promise that resolves when all data fetch and update operations are complete\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 /**\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\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 * Helper method to sum the proportional dimensions of panels, a value that's checked often as panels are added/removed\n * @private\n * @param {('Height'|'Width')} dimension\n * @returns {number}\n */\n sumProportional(dimension) {\n if (dimension !== 'height' && dimension !== 'width') {\n throw new Error('Bad dimension value passed to sumProportional');\n }\n let total = 0;\n for (let id in this.panels) {\n // Ensure every panel contributing to the sum has a non-zero proportional dimension\n if (!this.panels[id].layout[`proportional_${dimension}`]) {\n this.panels[id].layout[`proportional_${dimension}`] = 1 / Object.keys(this.panels).length;\n }\n total += this.panels[id].layout[`proportional_${dimension}`];\n }\n return total;\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 if (isNaN(this.layout.height) || this.layout.height <= 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.\n * @private\n * @param {Number} [width] If provided and larger than minimum size, set plot to this width\n * @param {Number} [height] If provided and larger than minimum size, set plot to this height\n * @returns {Plot}\n */\n setDimensions(width, height) {\n\n let id;\n\n // Update minimum allowable width and height by aggregating minimums from panels, then apply minimums to containing element.\n let min_width = parseFloat(this.layout.min_width) || 0;\n let min_height = parseFloat(this.layout.min_height) || 0;\n for (id in this.panels) {\n min_width = Math.max(min_width, this.panels[id].layout.min_width);\n if (parseFloat(this.panels[id].layout.min_height) > 0 && parseFloat(this.panels[id].layout.proportional_height) > 0) {\n min_height = Math.max(min_height, (this.panels[id].layout.min_height / this.panels[id].layout.proportional_height));\n }\n }\n this.layout.min_width = Math.max(min_width, 1);\n this.layout.min_height = Math.max(min_height, 1);\n d3.select(this.svg.node().parentNode)\n .style('min-width', `${this.layout.min_width}px`)\n .style('min-height', `${this.layout.min_height}px`);\n\n // If width and height arguments were passed then adjust them against plot minimums if necessary.\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 this.layout.width = Math.max(Math.round(+width), this.layout.min_width);\n this.layout.height = Math.max(Math.round(+height), this.layout.min_height);\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_width = this.layout.width;\n const panel_height = this.panels[panel_id].layout.proportional_height * this.layout.height;\n this.panels[panel_id].setDimensions(panel_width, panel_height);\n this.panels[panel_id].setOrigin(0, y_offset);\n this.panels[panel_id].layout.proportional_origin.x = 0;\n this.panels[panel_id].layout.proportional_origin.y = y_offset / this.layout.height;\n y_offset += panel_height;\n this.panels[panel_id].toolbar.update();\n });\n } else if (Object.keys(this.panels).length) {\n // If width and height arguments were NOT passed (and panels exist) then determine the plot dimensions\n // by making it conform to panel dimensions, assuming panels are already positioned correctly.\n this.layout.width = 0;\n this.layout.height = 0;\n for (id in this.panels) {\n this.layout.width = Math.max(this.panels[id].layout.width, this.layout.width);\n this.layout.height += this.panels[id].layout.height;\n }\n this.layout.width = Math.max(this.layout.width, this.layout.min_width);\n this.layout.height = Math.max(this.layout.height, this.layout.min_height);\n }\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} ${this.layout.height}`);\n\n this.svg\n .attr('width', this.layout.width)\n .attr('height', this.layout.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\n let id;\n\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 (id in this.panels) {\n if (this.panels[id].layout.proportional_height === null) {\n this.panels[id].layout.proportional_height = this.panels[id].layout.height / this.layout.height;\n }\n if (this.panels[id].layout.proportional_width === null) {\n this.panels[id].layout.proportional_width = 1;\n }\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 // Sum the proportional heights and then adjust all proportionally so that the sum is exactly 1\n const total_proportional_height = this.sumProportional('height');\n if (!total_proportional_height) {\n return this;\n }\n const proportional_adjustment = 1 / total_proportional_height;\n for (id in this.panels) {\n this.panels[id].layout.proportional_height *= proportional_adjustment;\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 this.panels[panel_id].setOrigin(0, y_offset);\n this.panels[panel_id].layout.proportional_origin.x = 0;\n y_offset += this.panels[panel_id].layout.height;\n if (this.panels[panel_id].layout.interaction.x_linked) {\n const delta = Math.max(x_linked_margins.left - this.panels[panel_id].layout.margin.left, 0)\n + Math.max(x_linked_margins.right - this.panels[panel_id].layout.margin.right, 0);\n this.panels[panel_id].layout.width += delta;\n this.panels[panel_id].layout.margin.left = x_linked_margins.left;\n this.panels[panel_id].layout.margin.right = x_linked_margins.right;\n this.panels[panel_id].layout.cliparea.origin.x = x_linked_margins.left;\n }\n });\n const calculated_plot_height = y_offset;\n this.panel_ids_by_y_index.forEach((panel_id) => {\n this.panels[panel_id].layout.proportional_origin.y = this.panels[panel_id].layout.origin.y / calculated_plot_height;\n });\n\n // Update dimensions on the plot to accommodate repositioned panels\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 this.panels[panel_id].setDimensions(\n this.layout.width * this.panels[panel_id].layout.proportional_width,\n this.layout.height * this.panels[panel_id].layout.proportional_height\n );\n });\n\n return this;\n\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_panel.layout.width, this_panel.layout.height + d3.event.dy);\n const panel_height_change = this_panel.layout.height - original_panel_height;\n const new_calculated_plot_height = this.parent.layout.height + panel_height_change;\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 loop_panel.layout.proportional_height = loop_panel.layout.height / new_calculated_plot_height;\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.layout.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_page_origin = this.parent.panels[this.parent.panel_ids_by_y_index[panel_idx]]._getPageOrigin();\n const left = plot_page_origin.x;\n const top = panel_page_origin.y + this.parent.panels[this.parent.panel_ids_by_y_index[panel_idx]].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.layout.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 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.layout.height;\n this.setDimensions(width, height);\n\n return this;\n\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\nexport {Plot as default};\n\n// Only for testing\nexport { _updateStatePosition };\n","/**\n * Define functions used by Scalable Layout Directives.\n *\n * These \"scaling functions\" are used during rendering to return output (eg color) based on input value\n * @module\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 * @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 * @param {Object} parameters\n * @param {Array} parameters.values A list of option values\n * @return {*}\n */\nconst ordinal_cycle = (parameters, value, index) => {\n var options = parameters.values;\n return options[index % options.length];\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, if_value, interpolate, numerical_bin, ordinal_cycle };\n","/**\n * @module\n * @private\n */\nimport {RegistryBase} from './base';\nimport * as scalable from '../helpers/scalable';\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","/** @module */\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 scalable from '../../registry/scalable';\n\n\n/**\n * A basic description of keys expected in a layout. Not intended to be directly used or modified by an end user.\n * @protected\n * @type {{type: string, fields: Array, x_axis: {}, y_axis: {}}}\n */\nconst default_layout = {\n type: '',\n filters: null, // Can be an array of {field, operator, value} entries\n fields: [], // A list of fields required for this data layer; determines output of `extractFields`\n x_axis: {}, // Axis options vary based on data layer type\n y_axis: {}, // Axis options vary based on data layer type\n tooltip_positioning: 'horizontal', // Where to draw tooltips relative to the point. Can be \"vertical\" or \"horizontal\"\n};\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 * @param {Object} layout A JSON-serializable object describing the layout for this layer\n * @param {Panel|null} parent Where this layout is used\n*/\nclass BaseDataLayer {\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 */\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 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 * @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\n * @param func\n */\n setFilter(func) {\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 seperate 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 * @protected\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 * @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.\n * @protected\n * @returns {BaseDataLayer}\n */\n applyDataMethods() {\n const field_to_match = (this.layout.match && this.layout.match.receive);\n const broadcast_value = this.parent_plot.state.lz_match_value;\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_highlight_match = (item[field_to_match] === 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 * @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 * @protected\n * @param {Array|Number|String|Object} 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 (layout, element_data, data_index) {\n let ret = null;\n if (Array.isArray(layout)) {\n let idx = 0;\n while (ret === null && idx < layout.length) {\n ret = this.resolveScalableParameter(layout[idx], element_data, data_index);\n idx++;\n }\n } else {\n switch (typeof layout) {\n case 'number':\n case 'string':\n ret = layout;\n break;\n case 'object':\n if (layout.scale_function) {\n const func = scalable.get(layout.scale_function);\n if (layout.field) {\n const f = new Field(layout.field);\n let extra;\n try {\n extra = this.layer_state && this.layer_state.extra_fields[this.getElementId(element_data)];\n } catch (e) {\n extra = null;\n }\n ret = func(layout.parameters || {}, f.resolve(element_data, extra), data_index);\n } else {\n ret = func(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 * @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 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 = panel_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 <= panel_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 set criteria\n *\n * Typically this is used with array.filter (the first argument is curried, `filter.bind(this, options)`\n * @protected\n * @param {Object[]} filters A list of filter entries: {field, value, operator} describing each filter.\n * Operator must be from a list of built-in operators\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(filters, item, index, array) {\n const test = (element, filter) => {\n const {field, operator, value: target} = filter;\n const operators = {\n '=': (a, b) => a === b,\n // eslint-disable-next-line eqeqeq\n '!=': (a, b) => a != b, // For absence of a value, deliberately allow weak comparisons (eg undefined/null)\n '<': (a, b) => a < b,\n '<=': (a, b) => a <= b,\n '>': (a, b) => a > b,\n '>=': (a, b) => a >= b,\n '%': (a, b) => a % b,\n 'in': (a, b) => b && b.includes(a), // works for strings or arrays\n 'match': (a, b) => a && a.includes(b),\n };\n const extra = this.layer_state.extra_fields[this.getElementId(element)];\n const field_value = (new Field(field)).resolve(element, extra);\n return operators[operator](field_value, target);\n };\n\n let match = true;\n filters.forEach((filter) => {\n if (!test(item, filter)) {\n match = false;\n }\n });\n return 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\n * @return {*}\n */\n getElementAnnotation (element, key) {\n const id = this.getElementId(element);\n const extra = this.layer_state.extra_fields[id];\n return extra && extra[key];\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] || [];\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'] || [];\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'].push(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(d, this.layout.tooltip.html));\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 state = this.layer_state.status_flags['has_tooltip'];\n const label_mark_position = state.indexOf(id);\n state.splice(label_mark_position, 1);\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() {\n for (let id in this.tooltips) {\n this.destroyTooltip(id, true);\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].includes(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'].includes(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 *\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 element_status_idx = this.layer_state.status_flags[status].indexOf(element_id);\n const added_status = (element_status_idx === -1); // On a re-render, existing statuses will be reapplied.\n if (active && added_status) {\n this.layer_state.status_flags[status].push(element_id);\n }\n if (!active && !added_status) {\n this.layer_state.status_flags[status].splice(element_status_idx, 1);\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 && value_to_broadcast && (added_status || !active)) {\n this.parent.emit(\n 'match_requested',\n { value: element[value_to_broadcast], 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 = this.layer_state.status_flags[status].slice();\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] = [];\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].includes(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(element, behavior.href);\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 if (Array.isArray(status_flags[property])) {\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) {\n if (typeof exclusive == 'undefined') {\n exclusive = false;\n } else {\n exclusive = !!exclusive;\n }\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","/** @module */\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\nconst default_layout = {\n color: '#000000',\n filters: null,\n tooltip_positioning: 'vertical', // Allowed values: top, middle, bottom\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 color by membership in a group, alongside information in other panels\n *\n */\nclass AnnotationTrack extends BaseDataLayer {\n /*\n * @param {Object} layout\n * @param {Object|String} [layout.color]\n * @param {Object[]} layout.filters An array of filter entries specifying which points to draw annotations for.\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 _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","/**\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 * @module\n */\nimport * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {applyStyles} from '../../helpers/common';\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\nclass Arcs extends BaseDataLayer {\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","/** @module */\nimport * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\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*/\nclass Genes extends BaseDataLayer {\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 data.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 source 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 });\n return this;\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 const track_data = this._applyFilters();\n 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","/** @module */\nimport * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {STATUSES} from '../constants';\nimport {applyStyles} from '../../helpers/common';\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.\n*/\nclass Line extends BaseDataLayer {\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 * @returns {Line}\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\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 * Implements a horizontal or vertical line given an orientation and an offset in the layout\n * Does not require a data source\n*/\nclass OrthogonalLine extends BaseDataLayer {\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\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","/** @module */\nimport * 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\nconst default_layout = {\n point_size: 40,\n point_shape: 'circle',\n tooltip_positioning: 'horizontal',\n color: '#888888',\n coalesce: {\n // Options to control whether and how to combine adjacent insignificant (\"within region of interest\") points\n // to improve rendering performance?\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 // Expressed in units of px apart. For circles, area 40 = radius ~3.5; aim for ~1 diameter distance.\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 * Scatter Data Layer\n * Implements a standard scatter plot\n */\nclass Scatter extends BaseDataLayer {\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.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(d, data_layer.layout.label.text || ''))\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 // Method to set a passed element as the LD reference in the plot-level state\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_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 to be\n * determined dynamically when data is first loaded.\n *\n */\nclass CategoryScatter extends Scatter {\n constructor(layout) {\n super(...arguments);\n /**\n * Define category names and extents (boundaries) for plotting.\n * @member {Object.} Category names and extents, in the form {category_name: [min_x, max_x]}\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*/\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_highlight_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\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 base layouts used to populate the LZ registry\n * @module\n * @private\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 Make LD Reference
`,\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 += `Toggle 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 from a data source\n */\n\nconst significance_layer = {\n id: 'significance',\n type: 'orthogonal_line',\n orientation: 'horizontal',\n offset: LZ_SIG_THRESHOLD_LOGP,\n};\n\nconst recomb_rate_layer = {\n namespace: { 'recomb': 'recomb' },\n id: 'recombrate',\n type: 'line',\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\nconst association_pvalues_layer = {\n namespace: { 'assoc': 'assoc', 'ld': 'ld' },\n id: 'associationpvalues',\n type: 'scatter',\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 values: ['#357ebd', '#46b8da', '#5cb85c', '#eea236', '#d43f3a'],\n },\n },\n '#B8B8B8',\n ],\n legend: [\n { shape: 'diamond', color: '#9632b8', size: 40, label: 'LD Ref Var', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: '#d43f3a', size: 40, label: '1.0 > r² ≥ 0.8', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: '#eea236', size: 40, label: '0.8 > r² ≥ 0.6', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: '#5cb85c', size: 40, label: '0.6 > r² ≥ 0.4', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: '#46b8da', size: 40, label: '0.4 > r² ≥ 0.2', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: '#357ebd', size: 40, label: '0.2 > r² ≥ 0.0', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: '#B8B8B8', size: 40, label: 'no r² data', class: 'lz-data_layer-scatter' },\n ],\n label: null,\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 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\nconst coaccessibility_layer = {\n namespace: { 'access': 'access' },\n id: 'coaccessibility',\n type: 'arcs',\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_highlight_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#ff0000',\n },\n },\n {\n field: 'lz_highlight_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\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\nconst phewas_pvalues_layer = {\n namespace: { 'phewas': 'phewas' },\n id: 'phewaspvalues',\n type: 'category_scatter',\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\nconst genes_layer = {\n namespace: { 'gene': 'gene', 'constraint': 'constraint' },\n id: 'genes',\n type: '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\nconst genes_layer_filtered = merge({\n // By default this layer doesn't show everything. Often used in tandem with a panel-level toolbar \"show all\" button.\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\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 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 */\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 position: 'right',\n color: 'blue',\n button_html: 'LD Population: ',\n show_selected: true,\n button_title: 'Select LD Population: ',\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\nconst gene_selector_menu = {\n type: 'display_options',\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 */\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\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\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\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\nconst association_panel = {\n id: 'association',\n width: 800,\n height: 225,\n min_width: 400,\n min_height: 200,\n proportional_width: 1,\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\nconst coaccessibility_panel = {\n id: 'coaccessibility',\n width: 800,\n height: 225,\n min_width: 400,\n min_height: 100,\n proportional_width: 1,\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\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\nconst genes_panel = {\n id: 'genes',\n width: 800,\n height: 225,\n min_width: 400,\n min_height: 112.5,\n proportional_width: 1,\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\nconst phewas_panel = {\n id: 'phewas',\n width: 800,\n height: 300,\n min_width: 800,\n min_height: 300,\n proportional_width: 1,\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\nconst annotation_catalog_panel = {\n id: 'annotationcatalog',\n width: 800,\n height: 45,\n min_height: 45,\n proportional_width: 1,\n margin: { top: 25, right: 50, bottom: 0, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\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\nconst standard_association_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: deepCopy(standard_association_toolbar),\n panels: [\n merge({ proportional_height: 0.5}, deepCopy(association_panel)),\n merge({ proportional_height: 0.5}, deepCopy(genes_panel)),\n ],\n};\n\nconst association_catalog_plot = {\n state: {},\n width: 800,\n height: 500,\n responsive_resize: true,\n min_region_scale: 20000,\n max_region_scale: 1000000,\n toolbar: deepCopy(standard_association_toolbar),\n panels: [\n deepCopy(annotation_catalog_panel),\n deepCopy(association_catalog_panel),\n deepCopy(genes_panel),\n ],\n};\n\nconst standard_phewas_plot = {\n width: 800,\n height: 600,\n min_width: 800,\n min_height: 600,\n responsive_resize: true,\n toolbar: deepCopy(standard_plot_toolbar),\n panels: [\n merge({proportional_height: 0.5}, deepCopy(phewas_panel)),\n merge({\n proportional_height: 0.5,\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\nconst coaccessibility_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: deepCopy(standard_plot_toolbar),\n panels: [\n Object.assign(\n { proportional_height: 0.4 },\n deepCopy(coaccessibility_panel)\n ),\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 { proportional_height: 0.6 },\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_highlight_match', // Special field name whose presence triggers custom rendering\n scale_function: 'if',\n parameters: {\n field_value: true,\n then: '#ff0000',\n },\n },\n {\n field: 'lz_highlight_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","/**\n * @module\n * @private\n */\nimport {RegistryBase} from './base';\nimport {applyNamespaces, deepCopy, merge} 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 */\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\n * @param {String} name\n * @param {Object} item\n * @param {boolean} override\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 widget type.\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 */\n merge(custom_layout, default_layout) {\n return merge(custom_layout, default_layout);\n }\n}\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 * Compatibility layer: expose symbols via UMD module to match the old LocusZoom API\n * A library using this file will need to load `locuszoom.css` separately.\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 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 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 */\n\n\nconst INSTALLED_PLUGINS = [];\n\n/**\n *\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","export default '0.13.0-beta.3';\n","/** @module */\nimport {RegistryBase} from '../registry/base';\nimport { ADAPTERS } from '../registry';\n\n/**\n * Create and coordinate an ensemble of (namespaced) data source instances\n * This is the mechanism by which users create data sources for a specific plot, and should be considered part of the\n * public interface for LocusZoom.\n *\n * @public\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 * Define standard data adapters used to retrieve data (usually from REST APIs)\n * @module\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/**\n * Base class for LocusZoom data sources (any). See also: BaseApiAdapter\n * @public\n */\nclass BaseAdapter {\n constructor(config) {\n /**\n * Whether this source should enable caching\n * @member {Boolean}\n */\n this._enableCache = true;\n this._cachedKey = null;\n\n /**\n * Whether this data source 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 * @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 data source. 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 /** @member {Object} */\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 URL.\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|undefined}\n */\n getCacheKey(state, chain, fields) {\n return this.getURL(state, chain, fields);\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 */\n getRequest(state, chain, fields) {\n let req;\n const cacheKey = this.getCacheKey(state, chain, fields);\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._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 sources 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 * @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 source, 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 sources 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 source. (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 source for LocusZoom data sources that receive their data over the web. Adds default config parameters\n * (and potentially other behavior) that are relevant to URL-based requests.\n */\nclass BaseApiAdapter extends BaseAdapter {\n parseInit(config) {\n super.parseInit(config);\n\n /** @member {String} */\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 * Data Source for Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a requesr\n * @public\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 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 normalizeResponse (data) {\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 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\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.\n *\n * In older versions of LocusZoom, this was known as \"LDServer\". A prior source (targeted at older APIs) has been removed.\n */\nclass LDServer extends BaseApiAdapter {\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 normalizeResponse (data) {\n // The LD API payload does not obey standard format conventions; do not try to transform it.\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 * @returns {*|string} The marker id (expected to be in `chr:pos_ref/alt` format) of the reference variant\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 return refVar;\n }\n\n getURL(state, chain, fields) {\n // Accept the following params in this.params:\n // - method (r, rsquare, cov)\n // - source (aka panel)\n // - population (ALL, AFR, EUR, etc)\n // - build\n // The LD source/pop can be overridden from plot.state for dynamic layouts\n const build = state.genome_build || this.params.build || 'GRCh37';\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 let refVar = this.getRefvar(state, chain, fields);\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 refVar = `${chrom}:${pos}`;\n if (ref && alt) {\n refVar += `_${ref}/${alt}`;\n }\n // Preserve the user-provided variant spec for use when matching to assoc data\n chain.header.ldrefvar = original;\n\n return [\n this.url, 'genome_builds/', build, '/references/', source, '/populations/', population, '/variants',\n '?correlation=', method,\n '&variant=', encodeURIComponent(refVar),\n '&chrom=', encodeURIComponent(state.chr),\n '&start=', encodeURIComponent(state.start),\n '&stop=', encodeURIComponent(state.end),\n ].join('');\n }\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 fetchRequest(state, chain, fields) {\n // The API is paginated, but we need all of the data to render a plot. Depaginate and combine where appropriate.\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 * Data source for GWAS catalogs of known variants\n * @public\n * @class\n * @param {Object|String} init Configuration (URL or object)\n * @param {Object} [init.params] Optional configuration parameters\n * @param {Number} [init.params.source=2] The ID of the chosen catalog. Defaults to EBI GWAS catalog, GRCh37\n * @param {('strict'|'loose')} [init.params.match_type='strict'] Whether to match on exact variant, or just position.\n */\nclass GwasCatalogLZ extends BaseApiAdapter {\n constructor(config) {\n super(config);\n this.__dependentSource = true;\n }\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_option = state.genome_build || this.params.build;\n validateBuildSource(this.constructor.name, build_option, null); // Source can override build- not mutually exclusive\n\n // Most of our annotations will respect genome build before any other option.\n // But there can be more than one GWAS catalog version available in the same API, for the same build- an\n // explicit config option will always take\n // precedence.\n // See: http://portaldev.sph.umich.edu/api/v1/annotation/gwascatalog/?format=objects\n const default_source = (build_option === 'GRCh38') ? 5 : 6; // EBI GWAS catalog\n const source = this.params.source || default_source;\n return `${this.url }?format=objects&sort=pos&filter=id eq ${source} and chrom eq '${state.chr}' and pos ge ${state.start} and pos le ${state.end}`;\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 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 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 * Data Source for Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format)\n * @public\n */\nclass GeneLZ extends BaseApiAdapter {\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 (build) {\n // If build specified, we auto-select the best current portaldev API dataset for that build\n // If build is not specified, we use the exact source ID provided by the user.\n // See: https://portaldev.sph.umich.edu/api/v1/annotation/genes/sources/?format=objects\n source = (build === 'GRCh38') ? 4 : 5;\n }\n return `${this.url}?filter=source in ${source} and chrom eq '${state.chr}' and start le ${state.end} and end ge ${state.start}`;\n }\n\n normalizeResponse(data) {\n // Genes have 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 return data;\n }\n\n extractFields(data, fields, outnames, trans) {\n return data;\n }\n}\n\n/**\n * Data Source for Gene Constraint Data, as fetched from the gnomAD server (or compatible)\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.\n *\n * @public\n*/\nclass GeneConstraintLZ extends BaseApiAdapter {\n constructor(config) {\n super(config);\n this.__dependentSource = true;\n }\n getURL() {\n // GraphQL API: request details are encoded in the body, not the URL\n return this.url;\n }\n getCacheKey(state, chain, fields) {\n const build = state.genome_build || this.params.build;\n // GraphQL API: request not defined solely by the URL\n // Gather the state params that govern constraint query for a given region.\n return `${this.url} ${state.chr} ${state.start} ${state.end} ${build}`;\n }\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(`Data source ${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) {\n // If there are no genes, skip the network request\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 // FIXME: The gnomAD API sometimes has temporary CORS changes that temporarily break the genes track\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 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 * Data Source for Recombination Rate Data, as fetched from the LocusZoom API server (or compatible)\n * @public\n */\nclass RecombLZ extends BaseApiAdapter {\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 (build) { // If build specified, choose a known Portal API dataset IDs (build 37/38)\n source = (build === 'GRCh38') ? 16 : 15;\n }\n return `${this.url}?filter=id in ${source} and chromosome eq '${state.chr}' and position le ${state.end} and position ge ${state.start}`;\n }\n}\n\n/**\n * Data Source for static blobs of data as raw JS objects. This does not perform additional parsing, which is required\n * for some sources (eg when joining 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.\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 * @public\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 getRequest(state, chain, fields) {\n return Promise.resolve(this._data);\n }\n}\n\n\n/**\n * Data source for PheWAS data retrieved from a LocusZoom/PortalDev compatible API\n * @public\n * @param {String[]} init.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(['Data source', 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\n\n/**\n * Base class for \"connectors\"- this is meant to be subclassed, rather than used directly.\n *\n * A connector is a source 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 * @param {Object} init Configuration for this source\n * @param {Object} init.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 * @type {*|Function}\n */\nclass ConnectorSource extends BaseAdapter {\n constructor(config) {\n super(config);\n\n if (!config || !config.sources) {\n throw new Error('Connectors must specify the data they require as init.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 */\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"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://[name]/webpack/bootstrap","webpack://[name]/external \"d3\"","webpack://[name]/./esm/helpers/layouts.js","webpack://[name]/./esm/registry/base.js","webpack://[name]/./esm/registry/adapters.js","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/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/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/index.js","webpack://[name]/./esm/version.js","webpack://[name]/./esm/data/sources.js","webpack://[name]/./esm/data/adapters.js"],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","d3","sqrt3","Math","sqrt","triangledown","context","size","y","moveTo","lineTo","closePath","applyNamespaces","element","namespace","default_namespace","default","re","match","base","resolved_namespace","replace","exec","length","push","merge","namespaced_element","namespaced_property","custom_layout","default_layout","Error","custom_type","default_type","Array","isArray","deepCopy","item","JSON","parse","stringify","nameToSymbol","shape","factory_name","charAt","toUpperCase","slice","RegistryBase","this","_items","Map","has","override","set","delete","from","keys","ClassRegistry","args","parent_name","source_name","overrides","console","warn","arguments","sub","assign","add","type","entries","adapters","STATUSES","verbs","adjectives","log10","isNaN","log","LN10","neglog10","logtoscinotation","exp","ceil","diff","pow","toFixed","scinotation","abs","floor","toExponential","htmlescape","urlencode","encodeURIComponent","template_string","funcs","map","super","substring","reduce","acc","func","_collectTransforms","field","parts","full_name","transformations","split","forEach","transform","transforms","val","data","extra","_applyTransformations","sources","_sources","fields","requests","raw","trans","outnames","state","__split_requests","request_handles","getData","ret","Promise","resolve","header","body","discrete","then","generateCurtain","showing","selector","content_selector","hide_delay","show","content","css","curtain","parent_plot","svg","node","parentNode","insert","attr","id","append","html","on","hide","update","clearTimeout","applyStyles","page_origin","_getPageOrigin","style","x","layout","width","height","delay","setTimeout","remove","generateLoader","progress_selector","cancel_selector","loader","percent","loader_boundrect","getBoundingClientRect","min","max","animate","classed","setPercentCompleted","selection","styles","prop","parent","color","parent_panel","parent_svg","button","persist","position","group_position","includes","initialize","status","menu","shouldPersist","force","destroy","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","left","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","toString","getClass","preUpdate","postUpdate","Title","div_selector","title_selector","subtitle","start","end","positionIntToString","class","_data_layer","data_layers","layer_name","_field","_field_display_html","field_display_html","_operator","operator","_filter_id","_data_type","data_type","_value_selector","filters","result","find","index","indexOf","_getTarget","splice","_clearFilter","Number","text","input_size","timer","apply","debounce","_getValue","_setFilter","render","_filename","filename","_button_html","button_html","_button_title","button_title","setColor","setHtml","setTitle","setOnMouseover","_getBlobUrl","url","old","URL","revokeObjectURL","setOnMouseout","root","ancestor_pattern","extractedCSSText","styleSheets","cssRules","e","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","suppress_confirm","confirm","panel","toolbar","removePanel","MovePanelUp","is_at_top","y_index","disable","moveUp","MovePanelDown","is_at_bottom","panel_ids_by_y_index","moveDown","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","allowed_fields","fields_whitelist","dataLayer","dataLayerLayout","defaultConfig","configSlot","undefined","_selected_item","uniqueID","random","table","menuLayout","renderRow","display_name","display_options","row_id","row","radioId","field_name","has_option","defaultName","default_config_display_name","options","display","SetState","state_field","show_selected","new_state","widgets","hide_timeout","dashboard","components","widget","error","panel_boundaries","dragging","interaction","orientation","origin","padding","label_size","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","right","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","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","emit","parseFloat","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","method","dragged_x","shiftKey","start_x","y_shifted","dragged_y","start_y","domain","renderAxis","zoom_handler","altKey","_canInteract","preventDefault","coords","wheelDelta","detail","deltaY","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","sort","applyPanelYIndexesToPanelLayouts","positionPanels","reMap","message","all","catch","decoupled","concat","getAxisExtent","extent","ticks","baseTickConfig","self","config","nextLayer","getTicks","itemConfig","clip_range","target_tick_count","parseInt","min_n","base_toFixed","unit","pop","prettyTicks","canRender","axis_params","label_offset","label_rotate","generateTicks","ticksAreAllNumbers","axis_factory","tickPadding","tickValues","tick_format","tickFormat","substr","tick_selector","parseFields","tick_mouseover","focus","cursor","target_height","dh","getAbsoluteDataHeight","toggle","verb","adjective","antiverb","min_width","responsive_resize","mouse_guide","datasource","remap_promises","_base_layout","lzd","_external_listeners","panel_layout","_total_height","panelId","panelsList","pid","layer","layer_state","_setDefaultState","clearPanelData","success_callback","opts","error_callback","onerror","err","listener","new_data","state_changes","mods","chr","attempted_scale","validated_region","attempted_midpoint","temp","_updateStatePosition","loading_data","applyAllElementStatus","some","event_name","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","selectors","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","dx","plot_page_origin","panel_page_origin","mouseout_mouse_guide","mousemove_mouse_guide","mouseup","stopDrag","mousemove","body_node","to_send","active","lz_match_value","client_rect","overrideAxisLayout","axis_number","axis_layout","ceiling","lower_buffer","upper_buffer","min_extent","y_axis_number","pos","suffix","exp_symbols","0","3","6","9","places_exp","min_exp","places","positionStringToInt","suffixre","mult","tokens","regex","condition","variable","close","astify","token","shift","ast","cache","render_node","join","a","b","if_value","parameters","input","field_value","else","numerical_bin","breaks","values","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_positioning","_base_id","_filter_func","tooltip","tooltips","global_statuses","resortDataLayers","getElementId","extra_fields","axis_config","id_key","for","id_field","element_id","empty","field_to_match","receive","match_function","broadcast_value","field_resolver","lz_is_match","toHTML","getDataLayer","getPanel","getPlot","deselect","unselectElement","applyCustomDataMethods","element_data","data_index","resolveScalableParameter","scale_function","f","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","filter","test_func","status_flags","updateTooltip","positionTooltip","closable","destroyTooltip","element_or_id","temporary","label_mark_position","_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","element_status_idx","added_status","showOrHideTooltip","is_selected","value_to_broadcast","send","setElementStatus","getElementById","behaviors","event_match","executeBehaviors","requiredKeyStates","datum","ctrlKey","behavior","action","current_status_boolean","href","open","location","panel_origin","positionAllTooltips","applyDataMethods","hitarea_width","_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","_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","transcript_idx","tracks","gene_track_index","1","_getLabelWidth","gene_name","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","gene","bboxes","getTrackHeight","boundaries","labels","strand","exon_id","clickareas","gene_bbox_id","gene_bbox","x_field","y_field","path","y0","path_class","global_status","default_orthogonal_layout","default_y","point_size","point_shape","coalesce","max_points","x_gap","y_gap","fill_opacity","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","ref","ldrefvar","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","every","colors","categoryBounds","knownCategories","knownColors","xPos","_prepareData","_generateCategoryBounds","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","standard_phewas","unnamespaced","contents","list","LocusZoom","version","plot","iterator","dataset","region","parsed_state","parsePositionQuery","refresh","DataSources","registry","_registry","source_id","Adapters","DataLayers","Layouts","MatchFunctions","ScaleFunctions","TransformationFunctions","Widgets","INSTALLED_PLUGINS","use","plugin","unshift","install","validateBuildSource","class_name","build","source","BaseAdapter","_enableCache","_cachedKey","_cache_pos_start","_cache_pos_end","__dependentSource","parseInit","params","chain","getURL","cache_pos_chr","fetch","response","ok","statusText","req","cacheKey","getCacheKey","_cachedResponse","fetchRequest","N","constructor","records","record","j","fieldFound","k","output_record","v","resp","json","normalizeResponse","standardized","annotateData","extractFields","one_source_body","combineChainBody","new_body","preGetData","pre","getRequest","parseResponse","BaseApiAdapter","AssociationLZ","analysis","LDServer","dataFields","position_field","pvalue","pvalue_field","_names_","names","nameMatch","arr","regexes","id_match","RegExp","obj","isrefvarin","isrefvarout","ldin","ldout","refVar","findRequestedFields","findMergeFields","columns","pval_field","cmp","test","extremeVal","extremeIdx","findExtremeValue","original","chrom","alt","refVar_formatted","genome_build","ld_source","population","ld_pop","refVar_raw","getRefvar","_","reqFields","corrField","rsquare","lfield","rfield","position2","leftJoin","refvar","idfield","outrefname","outldname","tagRefVariant","combined","chainRequests","payload","next","GwasCatalogLZ","build_option","default_source","posMatch","decider_out","n_matches","fn","outn","chainNames","catNames","GeneLZ","GeneConstraintLZ","unique_gene_names","query","headers","alias","constraint","RecombLZ","SOURCE_NAME","StaticSource","_data","PheWASLZ","variant","ConnectorSource","_source_name_mapping","specified_ids","_getRequiredSources","chain_source_id"],"mappings":"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","file":"locuszoom.app.min.js","sourcesContent":[" \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, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\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\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 13);\n","module.exports = d3;","/**\n * Utilities for modifying or working with layout objects\n * @module\n */\nimport * as d3 from 'd3';\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 * @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\nexport { applyNamespaces, deepCopy, merge, nameToSymbol };\n","/** @module */\n\n/**\n * Base class for all registries\n *\n * LocusZoom is plugin-extensible, and layouts are string-based and JSON serializable. This is achieved through the use\n * of a central registry that holds a reference to each possible feature.\n *\n * Each registry has some syntactical sugar, with common elements are defined in a base class\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 */\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 * A registry of known data sources. Can be used to find sources by name, either from predefined\n * classes, or plugins.\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\n\nimport * as adapters from '../data/adapters';\n\n\n// KnownDataSources is a basic registry with no special behavior.\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\nregistry.add('StaticJSON', adapters.StaticSource);\nregistry.add('LDLZ2', adapters.LDServer);\n\n\nexport default registry;\n","/**\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 value from the API. For example, a template or axis label\n * can convert from pvalue to -log10pvalue\n * @module\n */\n\n/**\n * Return the log10 of a value. Can be composed for, eg, loglog plots.\n * @param 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 * @function neglog10\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 * @function logtoscinotation\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 * @function scinotation\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\n * @function htmlescape\n * @param {String} value HTML-escape the provided value\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 * URL-encode the provided text, eg for constructing hyperlinks\n * @function urlencode\n * @param {String} value\n */\nexport function urlencode (value) {\n return encodeURIComponent(value);\n}\n","/**\n * @module\n * @private\n */\nimport {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 * @private\n */\nclass TransformationFunctions extends RegistryBase {\n _collectTransforms(template_string) {\n // Helper function that turns a sequence of function names into a single callable\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\nconst registry = new TransformationFunctions();\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 { TransformationFunctions as _TransformationFunctions };\n","/** @module */\nimport 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 * @module\n * @private\n */\nimport { 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 kind of data source.\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 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', `${this.layout.height}px`);\n this.curtain.content_selector\n .style('max-width', `${this.parent_plot.layout.width - 40}px`)\n .style('max-height', `${this.layout.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","/** @module */\nimport * as d3 from 'd3';\n\nimport {positionIntToString} from '../../helpers/display';\nimport {applyStyles, debounce} from '../../helpers/common';\nimport {deepCopy} from '../../helpers/layouts';\n\n// FIXME: Button creation should occur in the constructors, not in update functions\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 * @param {Object} layout A JSON-serializable object of layout configuration parameters\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 {Toolbar} parent The toolbar that contains this widget\n */\nclass BaseWidget {\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.\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, 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 title formatting\n * @param {object} layout\n * @param {string} layout.title Text to render\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`.\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\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] The number of characters to allow in the text field\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 */\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._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 /** Set the filter based on a provided value */\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 }\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 * Button to export current plot to an SVG image\n */\nclass DownloadSVG extends BaseWidget {\n /**\n * @param {string} [layout.button_html=\"Download SVG\"]\n * @param {string} [layout.button_title=\"Download image of the current plot as locuszoom.svg\"]\n * @param {string} [layout.filename=\"locuszoom.svg\"] The default filename to use when saving the image\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 }\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 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 */\nclass DownloadPNG extends DownloadSVG {\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 }\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 * @param {Boolean} [layout.suppress_confirm=false] If true, removes the panel without prompting user for confirmation\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 */\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 */\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 * @param {object} layout\n * @param {number} [layout.step=50000] The stepsize to change the region by\n * @param {string} [layout.button_html]\n * @param {string} [layout.button_title]\n */\nclass ShiftRegion extends BaseWidget {\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 * @param {object} layout\n * @param {number} [layout.step=0.2] The amount to zoom in by (where 1 indicates 100%)\n */\nclass ZoomRegion extends BaseWidget {\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\n * NOTE: Trusts content exactly as given. XSS prevention is the responsibility of the implementer.\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 * @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 * @param {string} [layout.button_html=\"Resize to Data\"]\n * @param {string} [layout.button_title]\n */\nclass ResizeToData 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 || '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 */\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 * 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 *\n * @param {object} layout\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.layer_name Specify the datalayer that this button should affect\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 * @typedef {{display_name: string, display: Object}} DisplayOptionsButtonConfigField\n * @param {DisplayOptionsButtonConfigField[]} layout.options Specify a label and set of layout directives associated\n * with this `display` option. Display field should include all changes to datalayer presentation options.\n */\nclass DisplayOptions extends BaseWidget {\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\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._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 * 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 source can use it to change LD reference population (for all panels) after render\n *\n * @param {object} layout\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\n * @param {string} [layout.state_field] The name of the field in plot.state that will be set by this button\n * @typedef {{display_name: string, value: *}} SetStateOptionsConfigField\n * @param {SetStateOptionsConfigField[]} layout.options Specify human labels and associated values for the dropdown menu\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 /**\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 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","/**\n * @module\n * @private\n */\nimport {ClassRegistry} from './base';\nimport * as widgets from '../components/toolbar/widgets';\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","/** @module */\nimport 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","/** @module */\nimport * as d3 from 'd3';\nimport {applyStyles} from '../helpers/common';\nimport {merge, nameToSymbol} from '../helpers/layouts';\n\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","/** @module */\nimport * 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 * @static\n * @type {Object}\n */\nconst default_layout = {\n title: { text: '', style: {}, x: 10, y: 22 },\n y_index: null,\n min_height: 1, // When resizing, do not allow height to go below this value\n height: 1, // The actual height allocated to the panel (>= min_height)\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, // On by default as of LZ 0.13\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 {Object} layout\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 * @protected\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 * @protected\n * @member {Object}\n */\n this.event_hooks = {\n 'layout_changed': [],\n 'data_requested': [],\n 'data_rendered': [],\n 'element_clicked': [],\n 'element_selection': [],\n 'match_requested': [], // A data layer is attempting to highlight matching points (internal use only)\n };\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 * The following panel-level events are currently supported:\n * - `layout_changed` - context: panel - Any aspect of the panel's layout (including dimensions or state) has changed.\n * - `data_requested` - context: panel - A request for new data from any data source used in the panel has been made.\n * - `data_rendered` - context: panel - Data from a request has been received and rendered in the panel.\n * - `element_clicked` - context: panel - A data element in any of the panel's data layers has been clicked.\n * - `element_selection` - context: panel - Triggered when an element changes \"selection\" status, and identifies\n * whether the element is being selected or deselected.\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. The this context bound to each event hook function is dependent on the type of event, as\n * denoted above. For example, when data_requested is emitted the context for this in the event hook will be the\n * panel itself, but when element_clicked is emitted the context for this in the event hook will be the element\n * that was clicked.\n *\n * @public\n * @param {String} event The name of the event (as defined in `event_hooks`)\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' || !Array.isArray(this.event_hooks[event])) {\n throw new Error(`Unable to register event hook, invalid event: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\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 * @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' || !Array.isArray(this.event_hooks[event])) {\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 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 if (bubble && this.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 * @public\n * @param {object} layout\n * @returns {*}\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 return this;\n }\n\n /**\n * Add a \"basic\" loader to a panel\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 * @public\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 /** @member {Object} */\n this.curtain = generateCurtain.call(this);\n /** @member {Object} */\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 * @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 /** @member {Element} */\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 * @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 * @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\n return this;\n\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(this.state, label))\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 */\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 {Object} data\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 * Since this is only an existence check, **variables with a value of 0 will be evaluated as true**.\n * This can be used with namespaced values, `{{#if assoc:field}}`; any dynamic namespacing will be applied when the\n * layout is first retrieved.\n * @returns {string}\n */\nfunction parseFields(data, html) {\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_:|]+)|(\\/if))}}/;\n while (html.length > 0) {\n const m = regex.exec(html);\n if (!m) {\n tokens.push({text: html}); html = '';\n } else if (m.index !== 0) {\n tokens.push({text: html.slice(0, m.index)}); html = html.slice(m.index);\n } else if (m[1] === '#if ') {\n tokens.push({condition: m[2]}); html = html.slice(m[0].length);\n } else if (m[2]) {\n tokens.push({variable: m[2]}); html = html.slice(m[0].length);\n } else if (m[3] === '/if') {\n tokens.push({close: 'if'}); 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 token.then = [];\n while (tokens.length > 0) {\n if (tokens[0].close === 'if') {\n tokens.shift();\n break;\n }\n token.then.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);\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 || condition === 0) {\n return node.then.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 * @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 {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","/** @module */\nimport * 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 * @protected\n * @static\n * @type {Object}\n */\nconst default_layout = {\n state: {},\n width: 800,\n min_width: 400,\n responsive_resize: false, // Allowed values: false, \"width_only\" (synonym for true)\n panels: [],\n toolbar: {\n widgets: [],\n },\n panel_boundaries: true,\n mouse_guide: true,\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 * @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 (!isNaN(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 (!isNaN(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 A JSON-serializable object of layout configuration parameters\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 * @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 * @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 * @protected\n * @member {Object}\n */\n this.event_hooks = {\n 'layout_changed': [], // Many rerendering operations, including dimensions changed, element highlighted, or rerender on chanegd data. Caution: Direct layout mutations might not be captured by this event.\n 'data_requested': [], // A request has been made for new data from any data source used in the plot\n 'data_rendered': [], // Data from a request has been received and rendered in the plot\n 'element_clicked': [], // Select or unselect\n 'element_selection': [], // Element becomes active (only)\n 'match_requested': [], // A data layer is attempting to highlight matching points (internal use only)\n 'panel_removed': [], // A panel has been removed (eg via the \"x\" button in plot)\n 'region_changed': [], // The viewing region (chr/start/end) has been changed\n 'state_changed': [], // Only triggered when a state change causes rerender\n };\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 * The following plot-level events are currently supported:\n * - `layout_changed` - context: plot - Any aspect of the plot's layout (including dimensions or state) has changed.\n * - `data_requested` - context: plot - A request for new data from any data source used in the plot has been made.\n * - `data_rendered` - context: plot - Data from a request has been received and rendered in the plot.\n * - `element_clicked` - context: plot - A data element in any of the plot's data layers has been clicked.\n * - `element_selection` - context: plot - Triggered when an element changes \"selection\" status, and identifies\n * whether the element is being selected or deselected.\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. The this context bound to each event hook function is dependent on the type of event, as\n * denoted above. For example, when data_requested is emitted the context for this in the event hook will be the\n * plot itself, but when element_clicked is emitted the context for this in the event hook will be the element\n * that was clicked.\n *\n * @public\n * @param {String} event The name of an event (as defined in `event_hooks`)\n * @param {eventCallback} hook\n * @returns {function} The registered event listener\n */\n on(event, hook) {\n if (typeof 'event' != 'string' || !Array.isArray(this.event_hooks[event])) {\n throw new Error(`Unable to register event hook, invalid event: ${event.toString()}`);\n }\n if (typeof hook != 'function') {\n throw new Error('Unable to register event hook, invalid hook function passed');\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 {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 * @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 if (typeof 'event' != 'string' || !Array.isArray(this.event_hooks[event])) {\n throw new Error(`LocusZoom attempted to throw an invalid event: ${event.toString()}`);\n }\n const sourceID = this.getBaseId();\n this.event_hooks[event].forEach((hookToRun) => {\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 // 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 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 * @public\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 * @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 * @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 source 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 state_changes\n * @returns {Promise} A promise that resolves when all data fetch and update operations are complete\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 /**\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\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 */\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 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 * All \"matcher\" functions have the call signature (item_value, target_value) => {boolean}\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 */\nimport {RegistryBase} from './base';\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\nregistry.add('=', (a, b) => a === b);\n// eslint-disable-next-line eqeqeq\nregistry.add('!=', (a, b) => a != b); // For absence of a value, deliberately allow weak comparisons (eg undefined/null)\nregistry.add('<', (a, b) => a < b);\nregistry.add('<=', (a, b) => a <= b);\nregistry.add('>', (a, b) => a > b);\nregistry.add('>=', (a, b) => a >= b);\nregistry.add('%', (a, b) => a % b);\nregistry.add('in', (a, b) => b && b.includes(a)); // works for strings or arrays: \"item value for gene type is one of the allowed categories of interest\"\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 * Define functions used by Scalable Layout Directives.\n *\n * These \"scaling functions\" are used during rendering to return output (eg color) based on input value\n * @module\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 * @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 data sources 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 option values\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 const options = parameters.values;\n // Each place the function gets used has its own parameters object. This function thus memoizes per usage\n // (\"association point color\") rather than globally\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 32 bit integer to be within the range of options allowed\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\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","/** @module */\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 * A basic description of keys expected in a layout. Not intended to be directly used or modified by an end user.\n * @protected\n * @type {{type: string, fields: Array, x_axis: {}, y_axis: {}}}\n */\nconst default_layout = {\n type: '',\n filters: null, // Can be an array of {field, operator, value} entries\n match: {}, // Object with 3 keys, all optional: { send: fieldname_to_send, receive: fieldname_to_compare, operator: name_of_match_function}\n fields: [], // A list of fields required for this data layer; determines output of `extractFields`\n x_axis: {}, // Axis options vary based on data layer type\n y_axis: {}, // Axis options vary based on data layer type\n tooltip_positioning: 'horizontal', // Where to draw tooltips relative to the point. Can be \"vertical\" or \"horizontal\"\n};\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 * @param {Object} layout A JSON-serializable object describing the layout for this layer\n * @param {Panel|null} parent Where this layout is used\n*/\nclass BaseDataLayer {\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 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 * @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 FilterFunctions 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 seperate 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 * @protected\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 * @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 * @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 * @protected\n * @param {Array|Number|String|Object} 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 (layout, element_data, data_index) {\n let ret = null;\n if (Array.isArray(layout)) {\n let idx = 0;\n while (ret === null && idx < layout.length) {\n ret = this.resolveScalableParameter(layout[idx], element_data, data_index);\n idx++;\n }\n } else {\n switch (typeof layout) {\n case 'number':\n case 'string':\n ret = layout;\n break;\n case 'object':\n if (layout.scale_function) {\n const func = SCALABLE.get(layout.scale_function);\n if (layout.field) {\n const f = new Field(layout.field);\n let extra;\n try {\n extra = this.layer_state && this.layer_state.extra_fields[this.getElementId(element_data)];\n } catch (e) {\n extra = null;\n }\n ret = func(layout.parameters || {}, f.resolve(element_data, extra), data_index);\n } else {\n ret = func(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 * @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 set criteria\n *\n * Typically this is used with array.filter (the first argument is curried, `this.filter.bind(this, options)`\n * @protected\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\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 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 const extra = this.layer_state.extra_fields[this.getElementId(item)];\n const field_value = (new Field(field)).resolve(item, extra);\n if (!test_func(field_value, target)) {\n match = false;\n }\n });\n return 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\n * @return {*}\n */\n getElementAnnotation (element, key) {\n const id = this.getElementId(element);\n const extra = this.layer_state.extra_fields[id];\n return extra && extra[key];\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] || [];\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'] || [];\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'].push(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(d, this.layout.tooltip.html));\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 state = this.layer_state.status_flags['has_tooltip'];\n const label_mark_position = state.indexOf(id);\n state.splice(label_mark_position, 1);\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() {\n for (let id in this.tooltips) {\n this.destroyTooltip(id, true);\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].includes(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'].includes(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 *\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 element_status_idx = this.layer_state.status_flags[status].indexOf(element_id);\n const added_status = (element_status_idx === -1); // On a re-render, existing statuses will be reapplied.\n if (active && added_status) {\n this.layer_state.status_flags[status].push(element_id);\n }\n if (!active && !added_status) {\n this.layer_state.status_flags[status].splice(element_status_idx, 1);\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 = this.layer_state.status_flags[status].slice();\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] = [];\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].includes(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(element, behavior.href);\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 if (Array.isArray(status_flags[property])) {\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) {\n if (typeof exclusive == 'undefined') {\n exclusive = false;\n } else {\n exclusive = !!exclusive;\n }\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","/** @module */\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\n\nconst default_layout = {\n color: '#000000',\n filters: null,\n tooltip_positioning: 'vertical', // Allowed values: top, middle, bottom\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 color by membership in a group, alongside information in other panels\n *\n */\nclass AnnotationTrack extends BaseDataLayer {\n /*\n * @param {Object} layout\n * @param {Object|String} [layout.color]\n * @param {Object[]} layout.filters An array of filter entries specifying which points to draw annotations for.\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 _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","/**\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 * @module\n */\nimport * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {applyStyles} from '../../helpers/common';\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\nclass Arcs extends BaseDataLayer {\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","/** @module */\nimport * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\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*/\nclass Genes extends BaseDataLayer {\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 source 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","/** @module */\nimport * as d3 from 'd3';\n\nimport BaseDataLayer from './base';\nimport {merge} from '../../helpers/layouts';\nimport {STATUSES} from '../constants';\nimport {applyStyles} from '../../helpers/common';\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.\n*/\nclass Line extends BaseDataLayer {\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 * @returns {Line}\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\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 * Implements a horizontal or vertical line given an orientation and an offset in the layout\n * Does not require a data source\n*/\nclass OrthogonalLine extends BaseDataLayer {\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\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","/** @module */\nimport * 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\nconst default_layout = {\n point_size: 40,\n point_shape: 'circle',\n tooltip_positioning: 'horizontal',\n color: '#888888',\n coalesce: {\n // Options to control whether and how to combine adjacent insignificant (\"within region of interest\") points\n // to improve rendering performance?\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 // Expressed in units of px apart. For circles, area 40 = radius ~3.5; aim for ~1 diameter distance.\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 * Scatter Data Layer\n * Implements a standard scatter plot\n */\nclass Scatter extends BaseDataLayer {\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(d, data_layer.layout.label.text || ''))\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 // Method to set a passed element as the LD reference in the plot-level state\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_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 to be\n * determined dynamically when data is first loaded.\n *\n */\nclass CategoryScatter extends Scatter {\n constructor(layout) {\n super(...arguments);\n /**\n * Define category names and extents (boundaries) for plotting.\n * @member {Object.} Category names and extents, in the form {category_name: [min_x, max_x]}\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*/\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\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 base layouts used to populate the LZ registry\n * @module\n * @private\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 Make LD Reference
`,\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 += `Toggle 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 from a data source\n */\n\nconst significance_layer = {\n id: 'significance',\n type: 'orthogonal_line',\n orientation: 'horizontal',\n offset: LZ_SIG_THRESHOLD_LOGP,\n};\n\nconst recomb_rate_layer = {\n namespace: { 'recomb': 'recomb' },\n id: 'recombrate',\n type: 'line',\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\nconst association_pvalues_layer = {\n namespace: { 'assoc': 'assoc', 'ld': 'ld' },\n id: 'associationpvalues',\n type: 'scatter',\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 values: ['#357ebd', '#46b8da', '#5cb85c', '#eea236', '#d43f3a'],\n },\n },\n '#B8B8B8',\n ],\n legend: [\n { shape: 'diamond', color: '#9632b8', size: 40, label: 'LD Ref Var', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: '#d43f3a', size: 40, label: '1.0 > r² ≥ 0.8', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: '#eea236', size: 40, label: '0.8 > r² ≥ 0.6', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: '#5cb85c', size: 40, label: '0.6 > r² ≥ 0.4', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: '#46b8da', size: 40, label: '0.4 > r² ≥ 0.2', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: '#357ebd', size: 40, label: '0.2 > r² ≥ 0.0', class: 'lz-data_layer-scatter' },\n { shape: 'circle', color: '#B8B8B8', 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\nconst coaccessibility_layer = {\n namespace: { 'access': 'access' },\n id: 'coaccessibility',\n type: 'arcs',\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: '#ff0000',\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\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\nconst phewas_pvalues_layer = {\n namespace: { 'phewas': 'phewas' },\n id: 'phewaspvalues',\n type: 'category_scatter',\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\nconst genes_layer = {\n namespace: { 'gene': 'gene', 'constraint': 'constraint' },\n id: 'genes',\n type: '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\nconst genes_layer_filtered = merge({\n // By default this layer doesn't show everything. Often used in tandem with a panel-level toolbar \"show all\" button.\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\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 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 */\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 position: 'right',\n color: 'blue',\n button_html: 'LD Population: ',\n show_selected: true,\n button_title: 'Select LD Population: ',\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\nconst gene_selector_menu = {\n type: 'display_options',\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 */\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\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\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\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\nconst association_panel = {\n id: '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\nconst coaccessibility_panel = {\n id: '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\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\nconst genes_panel = {\n id: '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\nconst phewas_panel = {\n id: '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\nconst annotation_catalog_panel = {\n id: 'annotationcatalog',\n min_height: 45,\n height: 45,\n margin: { top: 25, right: 50, bottom: 0, left: 50 },\n inner_border: 'rgb(210, 210, 210)',\n toolbar: deepCopy(standard_panel_toolbar),\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\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\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\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\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: '#ff0000',\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","/**\n * @module\n * @private\n */\nimport {RegistryBase} from './base';\nimport {applyNamespaces, deepCopy, merge} 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 */\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\n * @param {String} name\n * @param {Object} item\n * @param {boolean} override\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 widget type.\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 */\n merge(custom_layout, default_layout) {\n return merge(custom_layout, default_layout);\n }\n}\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 * Compatibility layer: expose symbols via UMD module to match the old LocusZoom API\n * A library using this file will need to load `locuszoom.css` separately.\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 */\n\n\nconst INSTALLED_PLUGINS = [];\n\n/**\n *\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","export default '0.13.0-beta.4';\n","/** @module */\nimport {RegistryBase} from '../registry/base';\nimport { ADAPTERS } from '../registry';\n\n/**\n * Create and coordinate an ensemble of (namespaced) data source instances\n * This is the mechanism by which users create data sources for a specific plot, and should be considered part of the\n * public interface for LocusZoom.\n *\n * @public\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 * Define standard data adapters used to retrieve data (usually from REST APIs)\n * @module\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/**\n * Base class for LocusZoom data sources (any). See also: BaseApiAdapter\n * @public\n */\nclass BaseAdapter {\n constructor(config) {\n /**\n * Whether this source should enable caching\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 data source 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 * @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 data source. 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 /** @member {Object} */\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 data source 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 data sources 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 sources 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 * @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 source, 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 sources 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 source. (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 source for LocusZoom data sources that receive their data over the web. Adds default config parameters\n * (and potentially other behavior) that are relevant to URL-based requests.\n */\nclass BaseApiAdapter extends BaseAdapter {\n parseInit(config) {\n super.parseInit(config);\n\n /** @member {String} */\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 * Data Source for Association Data from the LocusZoom/ Portaldev API (or compatible). Defines how to make a requesr\n * @public\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 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 normalizeResponse (data) {\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 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\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.\n *\n * In older versions of LocusZoom, this was known as \"LDServer\". A prior source (targeted at older APIs) has been removed.\n */\nclass LDServer extends BaseApiAdapter {\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 normalizeResponse (data) {\n // The LD API payload does not obey standard format conventions; do not try to transform it.\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 * @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 getURL(state, chain, fields) {\n // Accept the following params in this.params:\n // - method (r, rsquare, cov)\n // - source (aka panel)\n // - population (ALL, AFR, EUR, etc)\n // - build\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 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 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 fetchRequest(state, chain, fields) {\n // The API is paginated, but we need all of the data to render a plot. Depaginate and combine where appropriate.\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 * Data source for GWAS catalogs of known variants\n * @public\n * @class\n * @param {Object|String} init Configuration (URL or object)\n * @param {Object} [init.params] Optional configuration parameters\n * @param {Number} [init.params.source=2] The ID of the chosen catalog. Defaults to EBI GWAS catalog, GRCh37\n * @param {('strict'|'loose')} [init.params.match_type='strict'] Whether to match on exact variant, or just position.\n */\nclass GwasCatalogLZ extends BaseApiAdapter {\n constructor(config) {\n super(config);\n this.__dependentSource = true;\n }\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_option = state.genome_build || this.params.build;\n validateBuildSource(this.constructor.name, build_option, null); // Source can override build- not mutually exclusive\n\n // Most of our annotations will respect genome build before any other option.\n // But there can be more than one GWAS catalog version available in the same API, for the same build- an\n // explicit config option will always take\n // precedence.\n // See: http://portaldev.sph.umich.edu/api/v1/annotation/gwascatalog/?format=objects\n const default_source = (build_option === 'GRCh38') ? 5 : 6; // EBI GWAS catalog\n const source = this.params.source || default_source;\n return `${this.url }?format=objects&sort=pos&filter=id eq ${source} and chrom eq '${state.chr}' and pos ge ${state.start} and pos le ${state.end}`;\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 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 * Data Source for Gene Data, as fetched from the LocusZoom/Portaldev API server (or compatible format)\n * @public\n */\nclass GeneLZ extends BaseApiAdapter {\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 (build) {\n // If build specified, we auto-select the best current portaldev API dataset for that build\n // If build is not specified, we use the exact source ID provided by the user.\n // See: https://portaldev.sph.umich.edu/api/v1/annotation/genes/sources/?format=objects\n source = (build === 'GRCh38') ? 4 : 5;\n }\n return `${this.url}?filter=source in ${source} and chrom eq '${state.chr}' and start le ${state.end} and end ge ${state.start}`;\n }\n\n normalizeResponse(data) {\n // Genes have 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 return data;\n }\n\n extractFields(data, fields, outnames, trans) {\n return data;\n }\n}\n\n/**\n * Data Source for Gene Constraint Data, as fetched from the gnomAD server (or compatible)\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.\n *\n * @public\n*/\nclass GeneConstraintLZ extends BaseApiAdapter {\n constructor(config) {\n super(config);\n this.__dependentSource = true;\n }\n getURL() {\n // GraphQL API: request details are encoded in the body, not the URL\n return this.url;\n }\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(`Data source ${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) {\n // If there are no genes, skip the network request\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 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 * Data Source for Recombination Rate Data, as fetched from the LocusZoom API server (or compatible)\n * @public\n */\nclass RecombLZ extends BaseApiAdapter {\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 (build) { // If build specified, choose a known Portal API dataset IDs (build 37/38)\n source = (build === 'GRCh38') ? 16 : 15;\n }\n return `${this.url}?filter=id in ${source} and chromosome eq '${state.chr}' and position le ${state.end} and position ge ${state.start}`;\n }\n}\n\n/**\n * Data Source for static blobs of data as raw JS objects. This does not perform additional parsing, which is required\n * for some sources (eg when joining 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.\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 * @public\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 getRequest(state, chain, fields) {\n return Promise.resolve(this._data);\n }\n}\n\n\n/**\n * Data source for PheWAS data retrieved from a LocusZoom/PortalDev compatible API\n * @public\n * @param {String[]} init.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(['Data source', 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/**\n * Base class for \"connectors\"- this is meant to be subclassed, rather than used directly.\n *\n * A connector is a source 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 * @param {Object} init Configuration for this source\n * @param {Object} init.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 * @type {*|Function}\n */\nclass ConnectorSource extends BaseAdapter {\n constructor(config) {\n super(config);\n\n if (!config || !config.sources) {\n throw new Error('Connectors must specify the data they require as init.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 */\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"],"sourceRoot":""} \ No newline at end of file diff --git a/docs/wiki/data-layer.md b/docs/wiki/data-layer.md index 0dfa2102..5d1e8466 100644 --- a/docs/wiki/data-layer.md +++ b/docs/wiki/data-layer.md @@ -25,7 +25,7 @@ These directives are commonly implemented across several or all data layer types * **`match`** - *Object* (advanced interactive feature) If present, this feature allows different data layers to communicate when a point is clicked. For example, a user can click a point in one layer (an association plot), and see matching points for the same variant change color in other panels/data layers (eg annotation tracks, or other association studies plotted alongside for comparison). When a point is clicked, one part of the plot "broadcasts" a specified field value from that point (a field name in `match.send`), and every other layer looks for points with a matching field value (the field name in `match.receive` for each layer). For performance reasons, currently it is only possible to match one value at a time across all layers in the plot. This feature may change based on user feedback. If a given `match` directive is omitted, the layer will not `send` or `receive` match events. - * The match directive only asks whether a match exists. It works by marking matching points with a custom synthetic field `lz_highlight_match`. You can change the display of matching items in any data layer, by using whatever existing layout directives that layer supports: eg "if field value is true, then color is red". `color: { field: 'lz_highlight_match', scale_function: 'if', parameters: { field_value: true, then: '#FFf000' } }` + * The match directive only asks whether a match exists. It works by marking matching points with a custom synthetic field `lz_is_match`. You can change the display of matching items in any data layer, by using whatever existing layout directives that layer supports: eg "if field value is true, then color is red". `color: { field: 'lz_is_match', scale_function: 'if', parameters: { field_value: true, then: '#FFf000' } }` * **`fill_opacity`** - *Number _or_ Object, Scalable* A number representing the opacity level to apply to the fills of objects for elements created in the data layer. Opacity numbers range from `0` for transparent to `1` for opaque. Defaults to `1` for most data layers that support this directive. diff --git a/docs/wiki/panel.md b/docs/wiki/panel.md index da51735b..6c00905b 100644 --- a/docs/wiki/panel.md +++ b/docs/wiki/panel.md @@ -138,13 +138,7 @@ var panel_layout = { Discrete width, in pixels, of the panel. Subject to being limited by **`panel.min_width`**. * **`height`** - *Number* - Discrete height, in pixels, of the panel. Subject to being limited by **`panel.min_height`**. - -* **`min_width`** - *Number* - Minimum discrete width, in pixels, allowable for the panel. Able influence the **`min_width`** value on the parent layout. - -* **`min_height`** - *Number* - Minimum discrete height, in pixels, allowable for the panel. Able influence the **`min_height`** value on the parent layout. + Discrete height, in pixels, of the panel. * **`origin`** - *Object* An object that defines where the panel will be positioned in the plot (by the panel's top left corner) expressed as discrete pixel coordinates. @@ -156,21 +150,6 @@ var panel_layout = { Y-offset, in pixels, for the top-left corner of the panel (relative to the LocusZoom plot). **NOTE:** SVG y values go from the top down, so the SVG origin of (0,0) is in the top left corner. -* **`proportional_width`** - *Number* - Width of the panel expressed as a proportion of the available width of the LocusZoom plot. Value should be a number in the range of `0.0` to `1.0`, with `1.0` meaning 100%. - -* **`proportional_height`** - *Number* - Height of the panel expressed as a proportion of the available height of the LocusZoom plot. Value should be a number in the range of `0.0` to `1.0`, with `1.0` meaning 100%. - -* **`proportional_origin`** - *Object* - An object that defines where the panel will be positioned in the plot (by the panel's top left corner) expressed as proportions of the plot's dimensions. - - * **`proportional_origin.x`** - *Number* - X-offset, as a proportion of the plot's width, for the top-left corner of the panel (for example, 50% would be expressed as `0.5`). - - * **`proportional_origin.y`** - *Number* - Y-offset, as a proportion of the plot's height, for the top-left corner of the panel (for example, 25% would be expressed as `0.25`). - * **`margin`** - *Object* An object that defines the margins between a panel's boundaries and its content. diff --git a/docs/wiki/plot.md b/docs/wiki/plot.md index eaa5f2d0..b10146fc 100644 --- a/docs/wiki/plot.md +++ b/docs/wiki/plot.md @@ -148,9 +148,6 @@ var layout = { * **`min_width`** - *Number* Minimum discrete width, in pixels, allowable for the LocusZoom plot. May automatically increase to the greatest **`panels.{$panel_id}.min_width`** for each **`$panel_id`**. -* **`min_height`** - *Number* - Minimum discrete height, in pixels, allowable for the LocusZoom plot. May automatically increase based on **`panels.{$panel_id}.min_height`** and **`panels.{$panel_id}.proportional_height`** for each **`$panel_id`**. - * **`min_region_scale`** - *Number* Minimum allowable domain, in bases, for the x dimension of any panels whose x dimension resembles a genomic region (e.g. connected to `state.start` and `state.end`). Enforced by all actions that can reduce the x domain of a child panel, including zoom interactions. Effective default is 1. diff --git a/esm/components/data_layer/base.js b/esm/components/data_layer/base.js index cc43c157..54a5aa57 100644 --- a/esm/components/data_layer/base.js +++ b/esm/components/data_layer/base.js @@ -5,7 +5,8 @@ import {STATUSES} from '../constants'; import Field from '../../data/field'; import {parseFields} from '../../helpers/display'; import {deepCopy, merge} from '../../helpers/layouts'; -import scalable from '../../registry/scalable'; +import MATCHERS from '../../registry/matchers'; +import SCALABLE from '../../registry/scalable'; /** @@ -16,6 +17,7 @@ import scalable from '../../registry/scalable'; const default_layout = { type: '', filters: null, // Can be an array of {field, operator, value} entries + match: {}, // Object with 3 keys, all optional: { send: fieldname_to_send, receive: fieldname_to_compare, operator: name_of_match_function} fields: [], // A list of fields required for this data layer; determines output of `extractFields` x_axis: {}, // Axis options vary based on data layer type y_axis: {}, // Axis options vary based on data layer type @@ -93,6 +95,7 @@ class BaseDataLayer { * A user-provided function used to filter data for display. If provided, this will override any declarative * options in `layout.filters` * @private + * @deprecated */ this._filter_func = null; @@ -105,7 +108,9 @@ class BaseDataLayer { } /** - * Values in the layout object may change during rendering etc. Retain a copy of the original data layer state + * Values in the layout object may change during rendering etc. Retain a copy of the original data layer state. + * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is + * loaded: it contains the "defaults", not just the result of a calculated value. * @protected * @member {Object} */ @@ -216,10 +221,13 @@ class BaseDataLayer { } /** - * Select a filter function to be applied to the data + * Select a filter function to be applied to the data. DEPRECATED: Please use the FilterFunctions registry + * and reference via declarative filters. * @param func + * @deprecated */ setFilter(func) { + 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 = func; } @@ -302,21 +310,27 @@ class BaseDataLayer { /** * Basic method to apply arbitrary methods and properties to data elements. - * This is called on all data immediately after being fetched. + * This is called on all data immediately after being fetched. (requires reMap, not just re-render) + * + * Allowing a data element to access its parent enables interactive functionality, such as tooltips that modify + * the parent plot. This is also used for system-derived fields like "matching" behavior". + * * @protected * @returns {BaseDataLayer} */ applyDataMethods() { const field_to_match = (this.layout.match && this.layout.match.receive); + const match_function = MATCHERS.get(this.layout.match && this.layout.match.operator || '='); 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; 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_highlight_match = (item[field_to_match] === broadcast_value); + item.lz_is_match = (match_function(field_resolver.resolve(item), broadcast_value)); } item.toHTML = () => { @@ -384,7 +398,7 @@ class BaseDataLayer { break; case 'object': if (layout.scale_function) { - const func = scalable.get(layout.scale_function); + const func = SCALABLE.get(layout.scale_function); if (layout.field) { const f = new Field(layout.field); let extra; @@ -539,6 +553,7 @@ class BaseDataLayer { */ _drawTooltip(tooltip, position, x_min, x_max, y_min, y_max) { const panel_layout = this.parent.layout; + const plot_layout = this.parent_plot.layout; const layer_layout = this.layout; // Tooltip position params: as defined in the default stylesheet, used in calculations @@ -551,7 +566,7 @@ class BaseDataLayer { const page_origin = this._getPageOrigin(); const tooltip_box = tooltip.selector.node().getBoundingClientRect(); const data_layer_height = panel_layout.height - (panel_layout.margin.top + panel_layout.margin.bottom); - const data_layer_width = panel_layout.width - (panel_layout.margin.left + panel_layout.margin.right); + const data_layer_width = plot_layout.width - (panel_layout.margin.left + panel_layout.margin.right); // Clip the edges of the datum to the available plot area x_min = Math.max(x_min, 0); @@ -582,7 +597,7 @@ class BaseDataLayer { } else if (placement === 'horizontal') { // Auto select whether to position to the left of the item, or to the right y_offset = 0; - if (x_center <= panel_layout.width / 2) { + if (x_center <= plot_layout.width / 2) { placement = 'left'; } else { placement = 'right'; @@ -650,38 +665,24 @@ class BaseDataLayer { /** * Determine whether a given data element matches set criteria * - * Typically this is used with array.filter (the first argument is curried, `filter.bind(this, options)` + * Typically this is used with array.filter (the first argument is curried, `this.filter.bind(this, options)` * @protected - * @param {Object[]} filters A list of filter entries: {field, value, operator} describing each filter. + * @param {Object[]} filter_rules A list of rule entries: {field, value, operator} describing each filter. * Operator must be from a list of built-in operators * @param {Object} item * @param {Number} index * @param {Array} array * @returns {Boolean} Whether the specified item is a match */ - filter(filters, item, index, array) { - const test = (element, filter) => { + filter(filter_rules, item, index, array) { + let match = true; + filter_rules.forEach((filter) => { // Try each filter on this item, in sequence const {field, operator, value: target} = filter; - const operators = { - '=': (a, b) => a === b, - // eslint-disable-next-line eqeqeq - '!=': (a, b) => a != b, // For absence of a value, deliberately allow weak comparisons (eg undefined/null) - '<': (a, b) => a < b, - '<=': (a, b) => a <= b, - '>': (a, b) => a > b, - '>=': (a, b) => a >= b, - '%': (a, b) => a % b, - 'in': (a, b) => b && b.includes(a), // works for strings or arrays - 'match': (a, b) => a && a.includes(b), - }; - const extra = this.layer_state.extra_fields[this.getElementId(element)]; - const field_value = (new Field(field)).resolve(element, extra); - return operators[operator](field_value, target); - }; + const test_func = MATCHERS.get(operator); - let match = true; - filters.forEach((filter) => { - if (!test(item, filter)) { + const extra = this.layer_state.extra_fields[this.getElementId(item)]; + const field_value = (new Field(field)).resolve(item, extra); + if (!test_func(field_value, target)) { match = false; } }); @@ -1129,10 +1130,11 @@ class BaseDataLayer { } const value_to_broadcast = (this.layout.match && this.layout.match.send); - if (is_selected && value_to_broadcast && (added_status || !active)) { + if (is_selected && (typeof value_to_broadcast !== 'undefined') && (added_status || !active)) { this.parent.emit( + // The broadcast value can use transforms to "clean up value before sending broadcasting" 'match_requested', - { value: element[value_to_broadcast], active: active }, + { value: new Field(value_to_broadcast).resolve(element), active: active }, true ); } diff --git a/esm/components/data_layer/genes.js b/esm/components/data_layer/genes.js index 2842f74d..3aba1b54 100644 --- a/esm/components/data_layer/genes.js +++ b/esm/components/data_layer/genes.js @@ -101,103 +101,107 @@ class Genes extends BaseDataLayer { this.tracks = 1; this.gene_track_index = { 1: [] }; - data.map((item) => { - // If necessary, split combined gene id / version fields into discrete fields. - // NOTE: this may be an issue with CSG's genes data source that may eventually be solved upstream. - if (item.gene_id && item.gene_id.indexOf('.')) { - const split = item.gene_id.split('.'); - item.gene_id = split[0]; - item.gene_version = split[1]; - } + return data + // Filter out any genes that are fully outside the region of interest. This allows us to use cached data + // when zooming in, without breaking the layout by allocating space for genes that are not visible. + .filter((item) => !(item.end < this.state.start) && !(item.start > this.state.end)) + .map((item) => { + // If necessary, split combined gene id / version fields into discrete fields. + // NOTE: this may be an issue with CSG's genes data source that may eventually be solved upstream. + if (item.gene_id && item.gene_id.indexOf('.')) { + const split = item.gene_id.split('.'); + item.gene_id = split[0]; + item.gene_version = split[1]; + } - // Stash the transcript ID on the parent gene - item.transcript_id = item.transcripts[this.transcript_idx].transcript_id; - - // Determine display range start and end, based on minimum allowable gene display width, bounded by what we can see - // (range: values in terms of pixels on the screen) - item.display_range = { - start: this.parent.x_scale(Math.max(item.start, this.state.start)), - end: this.parent.x_scale(Math.min(item.end, this.state.end)), - }; - item.display_range.label_width = _getLabelWidth(item.gene_name, this.layout.label_font_size); - item.display_range.width = item.display_range.end - item.display_range.start; - // Determine label text anchor (default to middle) - item.display_range.text_anchor = 'middle'; - if (item.display_range.width < item.display_range.label_width) { - if (item.start < this.state.start) { - item.display_range.end = item.display_range.start - + item.display_range.label_width - + this.layout.label_font_size; - item.display_range.text_anchor = 'start'; - } else if (item.end > this.state.end) { - item.display_range.start = item.display_range.end - - item.display_range.label_width - - this.layout.label_font_size; - item.display_range.text_anchor = 'end'; - } else { - const centered_margin = ((item.display_range.label_width - item.display_range.width) / 2) - + this.layout.label_font_size; - if ((item.display_range.start - centered_margin) < this.parent.x_scale(this.state.start)) { - item.display_range.start = this.parent.x_scale(this.state.start); - item.display_range.end = item.display_range.start + item.display_range.label_width; + // Stash the transcript ID on the parent gene + item.transcript_id = item.transcripts[this.transcript_idx].transcript_id; + + // Determine display range start and end, based on minimum allowable gene display width, bounded by what we can see + // (range: values in terms of pixels on the screen) + item.display_range = { + start: this.parent.x_scale(Math.max(item.start, this.state.start)), + end: this.parent.x_scale(Math.min(item.end, this.state.end)), + }; + item.display_range.label_width = _getLabelWidth(item.gene_name, this.layout.label_font_size); + item.display_range.width = item.display_range.end - item.display_range.start; + // Determine label text anchor (default to middle) + item.display_range.text_anchor = 'middle'; + if (item.display_range.width < item.display_range.label_width) { + if (item.start < this.state.start) { + item.display_range.end = item.display_range.start + + item.display_range.label_width + + this.layout.label_font_size; item.display_range.text_anchor = 'start'; - } else if ((item.display_range.end + centered_margin) > this.parent.x_scale(this.state.end)) { - item.display_range.end = this.parent.x_scale(this.state.end); - item.display_range.start = item.display_range.end - item.display_range.label_width; + } else if (item.end > this.state.end) { + item.display_range.start = item.display_range.end + - item.display_range.label_width + - this.layout.label_font_size; item.display_range.text_anchor = 'end'; } else { - item.display_range.start -= centered_margin; - item.display_range.end += centered_margin; + const centered_margin = ((item.display_range.label_width - item.display_range.width) / 2) + + this.layout.label_font_size; + if ((item.display_range.start - centered_margin) < this.parent.x_scale(this.state.start)) { + item.display_range.start = this.parent.x_scale(this.state.start); + item.display_range.end = item.display_range.start + item.display_range.label_width; + item.display_range.text_anchor = 'start'; + } else if ((item.display_range.end + centered_margin) > this.parent.x_scale(this.state.end)) { + item.display_range.end = this.parent.x_scale(this.state.end); + item.display_range.start = item.display_range.end - item.display_range.label_width; + item.display_range.text_anchor = 'end'; + } else { + item.display_range.start -= centered_margin; + item.display_range.end += centered_margin; + } } + item.display_range.width = item.display_range.end - item.display_range.start; } - item.display_range.width = item.display_range.end - item.display_range.start; - } - // Add bounding box padding to the calculated display range start, end, and width - item.display_range.start -= this.layout.bounding_box_padding; - item.display_range.end += this.layout.bounding_box_padding; - item.display_range.width += 2 * this.layout.bounding_box_padding; - // Convert and stash display range values into domain values - // (domain: values in terms of the data set, e.g. megabases) - item.display_domain = { - start: this.parent.x_scale.invert(item.display_range.start), - end: this.parent.x_scale.invert(item.display_range.end), - }; - item.display_domain.width = item.display_domain.end - item.display_domain.start; - - // Using display range/domain data generated above cast each gene to tracks such that none overlap - item.track = null; - let potential_track = 1; - while (item.track === null) { - let collision_on_potential_track = false; - this.gene_track_index[potential_track].map((placed_gene) => { + // Add bounding box padding to the calculated display range start, end, and width + item.display_range.start -= this.layout.bounding_box_padding; + item.display_range.end += this.layout.bounding_box_padding; + item.display_range.width += 2 * this.layout.bounding_box_padding; + // Convert and stash display range values into domain values + // (domain: values in terms of the data set, e.g. megabases) + item.display_domain = { + start: this.parent.x_scale.invert(item.display_range.start), + end: this.parent.x_scale.invert(item.display_range.end), + }; + item.display_domain.width = item.display_domain.end - item.display_domain.start; + + // Using display range/domain data generated above cast each gene to tracks such that none overlap + item.track = null; + let potential_track = 1; + while (item.track === null) { + let collision_on_potential_track = false; + this.gene_track_index[potential_track].map((placed_gene) => { + if (!collision_on_potential_track) { + const min_start = Math.min(placed_gene.display_range.start, item.display_range.start); + const max_end = Math.max(placed_gene.display_range.end, item.display_range.end); + if ((max_end - min_start) < (placed_gene.display_range.width + item.display_range.width)) { + collision_on_potential_track = true; + } + } + }); if (!collision_on_potential_track) { - const min_start = Math.min(placed_gene.display_range.start, item.display_range.start); - const max_end = Math.max(placed_gene.display_range.end, item.display_range.end); - if ((max_end - min_start) < (placed_gene.display_range.width + item.display_range.width)) { - collision_on_potential_track = true; + item.track = potential_track; + this.gene_track_index[potential_track].push(item); + } else { + potential_track++; + if (potential_track > this.tracks) { + this.tracks = potential_track; + this.gene_track_index[potential_track] = []; } } - }); - if (!collision_on_potential_track) { - item.track = potential_track; - this.gene_track_index[potential_track].push(item); - } else { - potential_track++; - if (potential_track > this.tracks) { - this.tracks = potential_track; - this.gene_track_index[potential_track] = []; - } } - } - // Stash parent references on all genes, transcripts, and exons - item.parent = this; - item.transcripts.map((d, t) => { - item.transcripts[t].parent = item; - item.transcripts[t].exons.map((d, e) => item.transcripts[t].exons[e].parent = item.transcripts[t]); + // Stash parent references on all genes, transcripts, and exons + item.parent = this; + item.transcripts.map((d, t) => { + item.transcripts[t].parent = item; + item.transcripts[t].exons.map((d, e) => item.transcripts[t].exons[e].parent = item.transcripts[t]); + }); + return item; }); - }); - return this; } /** @@ -206,8 +210,8 @@ class Genes extends BaseDataLayer { render() { const self = this; // Apply filters to only render a specified set of points - const track_data = this._applyFilters(); - this.assignTracks(track_data); + let track_data = this._applyFilters(); + track_data = this.assignTracks(track_data); let height; // Render gene groups diff --git a/esm/components/data_layer/scatter.js b/esm/components/data_layer/scatter.js index 190b1692..fea2630d 100644 --- a/esm/components/data_layer/scatter.js +++ b/esm/components/data_layer/scatter.js @@ -73,7 +73,7 @@ class Scatter extends BaseDataLayer { const spacing = data_layer.layout.label.spacing; const handle_lines = Boolean(data_layer.layout.label.lines); const min_x = 2 * spacing; - const max_x = this.parent.layout.width - this.parent.layout.margin.left - this.parent.layout.margin.right - (2 * spacing); + const max_x = this.parent_plot.layout.width - this.parent.layout.margin.left - this.parent.layout.margin.right - (2 * spacing); const flip = (dn, dnl) => { const dnx = +dn.attr('x'); diff --git a/esm/components/legend.js b/esm/components/legend.js index 7144674e..e0b7f692 100644 --- a/esm/components/legend.js +++ b/esm/components/legend.js @@ -166,7 +166,7 @@ class Legend { // Ensure this element does not exceed the panel width // (E.g. drop to the next line if it does, but only if it's not the only element on this line) const right_x = this.layout.origin.x + x + bcr.width; - if (x > padding && right_x > this.parent.layout.width) { + if (x > padding && right_x > this.parent.parent.layout.width) { y += line_height; x = padding; selector.attr('transform', `translate(${x}, ${y})`); @@ -209,7 +209,7 @@ class Legend { this.layout.origin.y = this.parent.layout.height - bcr.height - +this.layout.pad_from_bottom; } if (!isNaN(+this.layout.pad_from_right)) { - this.layout.origin.x = this.parent.layout.width - bcr.width - +this.layout.pad_from_right; + this.layout.origin.x = this.parent.parent.layout.width - bcr.width - +this.layout.pad_from_right; } this.selector.attr('transform', `translate(${this.layout.origin.x}, ${this.layout.origin.y})`); } diff --git a/esm/components/panel.js b/esm/components/panel.js index 3c2cfcb0..e956baaf 100644 --- a/esm/components/panel.js +++ b/esm/components/panel.js @@ -18,14 +18,9 @@ import data_layers from '../registry/data_layers'; const default_layout = { title: { text: '', style: {}, x: 10, y: 22 }, y_index: null, - width: 0, - height: 0, + min_height: 1, // When resizing, do not allow height to go below this value + height: 1, // The actual height allocated to the panel (>= min_height) origin: { x: 0, y: null }, - min_width: 1, - min_height: 1, - proportional_width: null, - proportional_height: null, - proportional_origin: { x: 0, y: null }, margin: { top: 0, right: 0, bottom: 0, left: 0 }, background_click: 'clear_selections', toolbar: { @@ -507,14 +502,14 @@ class Panel { // Set size on the clip rect this.svg.clipRect - .attr('width', this.layout.width) + .attr('width', this.parent_plot.layout.width) .attr('height', this.layout.height); // Set and position the inner border, style if necessary this.inner_border .attr('x', this.layout.margin.left) .attr('y', this.layout.margin.top) - .attr('width', this.layout.width - (this.layout.margin.left + this.layout.margin.right)) + .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)); if (this.layout.inner_border) { this.inner_border @@ -784,24 +779,6 @@ class Panel { * @returns {Panel} */ initializeLayout() { - - // If the layout is missing BOTH width and proportional width then set the proportional width to 1. - // This will default the panel to taking up the full width of the plot. - if (this.layout.width === 0 && this.layout.proportional_width === null) { - this.layout.proportional_width = 1; - } - - // If the layout is missing BOTH height and proportional height then set the proportional height to - // an equal share of the plot's current height. - if (this.layout.height === 0 && this.layout.proportional_height === null) { - const panel_count = Object.keys(this.parent.panels).length; - if (panel_count > 0) { - this.layout.proportional_height = (1 / panel_count); - } else { - this.layout.proportional_height = 1; - } - } - // Set panel dimensions, origin, and margin this.setDimensions(); this.setOrigin(); @@ -845,22 +822,16 @@ class Panel { setDimensions(width, height) { if (typeof width != 'undefined' && typeof height != 'undefined') { if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) { - this.layout.width = Math.max(Math.round(+width), this.layout.min_width); + 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); } - } else { - if (this.layout.proportional_width !== null) { - this.layout.width = Math.max(this.layout.proportional_width * this.parent.layout.width, this.layout.min_width); - } - if (this.layout.proportional_height !== null) { - this.layout.height = Math.max(this.layout.proportional_height * this.parent.layout.height, this.layout.min_height); - } } - this.layout.cliparea.width = Math.max(this.layout.width - (this.layout.margin.left + this.layout.margin.right), 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); if (this.svg.clipRect) { this.svg.clipRect - .attr('width', this.layout.width) + .attr('width', this.parent.layout.width) .attr('height', this.layout.height); } if (this.initialized) { @@ -919,20 +890,21 @@ class Panel { if (!isNaN(left) && left >= 0) { this.layout.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 (this.layout.margin.left + this.layout.margin.right > this.layout.width) { - extra = Math.floor(((this.layout.margin.left + this.layout.margin.right) - this.layout.width) / 2); + 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; } ['top', 'right', 'bottom', 'left'].forEach((m) => { this.layout.margin[m] = Math.max(this.layout.margin[m], 0); }); - this.layout.cliparea.width = Math.max(this.layout.width - (this.layout.margin.left + this.layout.margin.right), 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; @@ -962,7 +934,7 @@ class Panel { const clipPath = this.svg.container.append('clipPath') .attr('id', `${base_id}.clip`); this.svg.clipRect = clipPath.append('rect') - .attr('width', this.layout.width) + .attr('width', this.parent_plot.layout.width) .attr('height', this.layout.height); // Append svg group for rendering all panel child elements, clipped by the clip path @@ -1301,7 +1273,7 @@ class Panel { label_rotate: -90, }, y2: { - position: `translate(${this.layout.width - this.layout.margin.right}, ${this.layout.margin.top})`, + position: `translate(${this.parent_plot.layout.width - this.layout.margin.right}, ${this.layout.margin.top})`, orientation: 'right', label_x: (this.layout.axes[axis].label_offset || 0), label_y: this.layout.cliparea.height / 2, @@ -1451,11 +1423,8 @@ class Panel { if (+target_height) { target_height += +this.layout.margin.top + +this.layout.margin.bottom; // FIXME: plot.setDimensions calls panel.setDimensions (though without arguments) - this.setDimensions(this.layout.width, target_height); + this.setDimensions(this.parent_plot.layout.width, target_height); this.parent.setDimensions(); - this.parent.panel_ids_by_y_index.forEach((id) => { - this.parent.panels[id].layout.proportional_height = null; - }); this.parent.positionPanels(); } } diff --git a/esm/components/plot.js b/esm/components/plot.js index d5df0fcf..3dfa8428 100644 --- a/esm/components/plot.js +++ b/esm/components/plot.js @@ -16,10 +16,8 @@ import {generateCurtain, generateLoader} from '../helpers/common'; */ const default_layout = { state: {}, - width: 1, - height: 1, - min_width: 1, - min_height: 1, + width: 800, + min_width: 400, responsive_resize: false, // Allowed values: false, "width_only" (synonym for true) panels: [], toolbar: { @@ -175,7 +173,9 @@ class Plot { merge(this.layout, default_layout); // TODO: evaluate how the default layout is applied /** - * Values in the layout object may change during rendering etc. Retain a copy of the original plot options + * Values in the layout object may change during rendering etc. Retain a copy of the original plot options. + * This is useful for, eg, dynamically generated color schemes that need to start from scratch when new data is + * loaded: it contains the "defaults", not just the result of a calculated value. * @protected * @member {Object} */ @@ -392,7 +392,7 @@ class Plot { this.panels[panel.id].reMap(); // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip // positioning. TODO: make this additional call unnecessary. - this.setDimensions(this.layout.width, this.layout.height); + this.setDimensions(this.layout.width, this._total_height); } return this.panels[panel.id]; } @@ -477,14 +477,10 @@ class Plot { // Call positionPanels() to keep panels from overlapping and ensure filling all available vertical space if (this.initialized) { - // Allow the plot to shrink when panels are removed, by forcing it to recalculate min dimensions from scratch - this.layout.min_height = this._base_layout.min_height; - this.layout.min_width = this._base_layout.min_width; - this.positionPanels(); // An extra call to setDimensions with existing discrete dimensions fixes some rounding errors with tooltip // positioning. TODO: make this additional call unnecessary. - this.setDimensions(this.layout.width, this.layout.height); + this.setDimensions(this.layout.width, this._total_height); } this.emit('panel_removed', id); @@ -765,27 +761,6 @@ class Plot { return this.id; } - /** - * Helper method to sum the proportional dimensions of panels, a value that's checked often as panels are added/removed - * @private - * @param {('Height'|'Width')} dimension - * @returns {number} - */ - sumProportional(dimension) { - if (dimension !== 'height' && dimension !== 'width') { - throw new Error('Bad dimension value passed to sumProportional'); - } - let total = 0; - for (let id in this.panels) { - // Ensure every panel contributing to the sum has a non-zero proportional dimension - if (!this.panels[id].layout[`proportional_${dimension}`]) { - this.panels[id].layout[`proportional_${dimension}`] = 1 / Object.keys(this.panels).length; - } - total += this.panels[id].layout[`proportional_${dimension}`]; - } - return total; - } - /** * Resize the plot to fit the bounding container * @private @@ -808,9 +783,6 @@ class Plot { if (isNaN(this.layout.width) || this.layout.width <= 0) { throw new Error('Plot layout parameter `width` must be a positive number'); } - if (isNaN(this.layout.height) || this.layout.height <= 0) { - throw new Error('Plot layout parameter `width` must be a positive number'); - } // 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; @@ -840,36 +812,21 @@ class Plot { * Set the dimensions for a plot, and ensure that panels are sized and positioned correctly. * * If dimensions are provided, resizes each panel proportionally to match the new plot dimensions. Otherwise, - * calculates the appropriate plot dimensions based on all panels. + * calculates the appropriate plot dimensions based on all panels, and ensures that panels are placed and + * rendered in the correct relative positions. * @private - * @param {Number} [width] If provided and larger than minimum size, set plot to this width - * @param {Number} [height] If provided and larger than minimum size, set plot to this height + * @param {Number} [width] If provided and larger than minimum allowed size, set plot to this width + * @param {Number} [height] If provided and larger than minimum allowed size, set plot to this height * @returns {Plot} */ setDimensions(width, height) { - - let id; - - // Update minimum allowable width and height by aggregating minimums from panels, then apply minimums to containing element. - let min_width = parseFloat(this.layout.min_width) || 0; - let min_height = parseFloat(this.layout.min_height) || 0; - for (id in this.panels) { - min_width = Math.max(min_width, this.panels[id].layout.min_width); - if (parseFloat(this.panels[id].layout.min_height) > 0 && parseFloat(this.panels[id].layout.proportional_height) > 0) { - min_height = Math.max(min_height, (this.panels[id].layout.min_height / this.panels[id].layout.proportional_height)); - } - } - this.layout.min_width = Math.max(min_width, 1); - this.layout.min_height = Math.max(min_height, 1); - d3.select(this.svg.node().parentNode) - .style('min-width', `${this.layout.min_width}px`) - .style('min-height', `${this.layout.min_height}px`); - - // If width and height arguments were passed then adjust them against plot minimums if necessary. + // If width and height arguments were passed, then adjust plot dimensions to fit all panels // Then resize the plot and proportionally resize panels to fit inside the new plot dimensions. if (!isNaN(width) && width >= 0 && !isNaN(height) && height >= 0) { + // Resize operations may ask for a different amount of space than that used by panels. + const height_scaling_factor = height / this._total_height; + this.layout.width = Math.max(Math.round(+width), this.layout.min_width); - this.layout.height = Math.max(Math.round(+height), this.layout.min_height); // Override discrete values if resizing responsively if (this.layout.responsive_resize) { // All resize modes will affect width @@ -880,36 +837,28 @@ class Plot { // Resize/reposition panels to fit, update proportional origins if necessary let y_offset = 0; this.panel_ids_by_y_index.forEach((panel_id) => { + const panel = this.panels[panel_id]; const panel_width = this.layout.width; - const panel_height = this.panels[panel_id].layout.proportional_height * this.layout.height; - this.panels[panel_id].setDimensions(panel_width, panel_height); - this.panels[panel_id].setOrigin(0, y_offset); - this.panels[panel_id].layout.proportional_origin.x = 0; - this.panels[panel_id].layout.proportional_origin.y = y_offset / this.layout.height; + // In this block, we are passing explicit dimensions that might require rescaling all panels at once + const panel_height = panel.layout.height * height_scaling_factor; + panel.setDimensions(panel_width, panel_height); + panel.setOrigin(0, y_offset); y_offset += panel_height; - this.panels[panel_id].toolbar.update(); + panel.toolbar.update(); }); - } else if (Object.keys(this.panels).length) { - // If width and height arguments were NOT passed (and panels exist) then determine the plot dimensions - // by making it conform to panel dimensions, assuming panels are already positioned correctly. - this.layout.width = 0; - this.layout.height = 0; - for (id in this.panels) { - this.layout.width = Math.max(this.panels[id].layout.width, this.layout.width); - this.layout.height += this.panels[id].layout.height; - } - this.layout.width = Math.max(this.layout.width, this.layout.min_width); - this.layout.height = Math.max(this.layout.height, this.layout.min_height); } + // Set the plot height to the sum of all panels (using the "real" height values accounting for panel.min_height) + const final_height = this._total_height; + // Apply layout width and height as discrete values or viewbox values if (this.svg !== null) { // The viewBox must always be specified in order for "save as image" button to work - this.svg.attr('viewBox', `0 0 ${this.layout.width} ${this.layout.height}`); + this.svg.attr('viewBox', `0 0 ${this.layout.width} ${final_height}`); this.svg .attr('width', this.layout.width) - .attr('height', this.layout.height); + .attr('height', final_height); } // If the plot has been initialized then trigger some necessary render functions @@ -931,9 +880,6 @@ class Plot { * @private */ positionPanels() { - - let id; - // We want to enforce that all x-linked panels have consistent horizontal margins // (to ensure that aligned items stay aligned despite inconsistent initial layout parameters) // NOTE: This assumes panels have consistent widths already. That should probably be enforced too! @@ -942,63 +888,44 @@ 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 (id in this.panels) { - if (this.panels[id].layout.proportional_height === null) { - this.panels[id].layout.proportional_height = this.panels[id].layout.height / this.layout.height; - } - if (this.panels[id].layout.proportional_width === null) { - this.panels[id].layout.proportional_width = 1; - } + 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); } } - // Sum the proportional heights and then adjust all proportionally so that the sum is exactly 1 - const total_proportional_height = this.sumProportional('height'); - if (!total_proportional_height) { - return this; - } - const proportional_adjustment = 1 / total_proportional_height; - for (id in this.panels) { - this.panels[id].layout.proportional_height *= proportional_adjustment; - } - // 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.panels[panel_id].setOrigin(0, y_offset); - this.panels[panel_id].layout.proportional_origin.x = 0; + const panel = this.panels[panel_id]; + panel.setOrigin(0, y_offset); y_offset += this.panels[panel_id].layout.height; - if (this.panels[panel_id].layout.interaction.x_linked) { - const delta = Math.max(x_linked_margins.left - this.panels[panel_id].layout.margin.left, 0) - + Math.max(x_linked_margins.right - this.panels[panel_id].layout.margin.right, 0); - this.panels[panel_id].layout.width += delta; - this.panels[panel_id].layout.margin.left = x_linked_margins.left; - this.panels[panel_id].layout.margin.right = x_linked_margins.right; - this.panels[panel_id].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; } }); - const calculated_plot_height = y_offset; - this.panel_ids_by_y_index.forEach((panel_id) => { - this.panels[panel_id].layout.proportional_origin.y = this.panels[panel_id].layout.origin.y / calculated_plot_height; - }); - // Update dimensions on the plot to accommodate repositioned panels + // Update dimensions on the plot to accommodate repositioned panels (eg when resizing one panel, + // also must update the plot dimensions) 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.panels[panel_id].setDimensions( - this.layout.width * this.panels[panel_id].layout.proportional_width, - this.layout.height * this.panels[panel_id].layout.proportional_height + const panel = this.panels[panel_id]; + panel.setDimensions( + this.layout.width, + panel.layout.height ); }); return this; - } /** @@ -1065,15 +992,13 @@ class Plot { // 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 original_panel_height = this_panel.layout.height; - this_panel.setDimensions(this_panel.layout.width, this_panel.layout.height + d3.event.dy); + 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; - const new_calculated_plot_height = this.parent.layout.height + panel_height_change; // 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]]; - loop_panel.layout.proportional_height = loop_panel.layout.height / new_calculated_plot_height; 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(); @@ -1107,7 +1032,7 @@ class Plot { this.dragging = false; }); corner_drag.on('drag', () => { - this.parent.setDimensions(this.parent.layout.width + d3.event.dx, this.parent.layout.height + d3.event.dy); + 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; @@ -1121,9 +1046,10 @@ class Plot { // Position panel boundaries const plot_page_origin = this.parent._getPageOrigin(); this.selectors.forEach((selector, panel_idx) => { - const panel_page_origin = this.parent.panels[this.parent.panel_ids_by_y_index[panel_idx]]._getPageOrigin(); + 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 + this.parent.panels[this.parent.panel_ids_by_y_index[panel_idx]].layout.height - 12; + const top = panel_page_origin.y + panel.layout.height - 12; const width = this.parent.layout.width - 1; selector .style('top', `${top}px`) @@ -1136,7 +1062,7 @@ class Plot { const corner_padding = 10; const corner_size = 16; this.corner_selector - .style('top', `${plot_page_origin.y + this.parent.layout.height - corner_padding - corner_size}px`) + .style('top', `${plot_page_origin.y + this.parent._total_height - corner_padding - corner_size}px`) .style('left', `${plot_page_origin.x + this.parent.layout.width - corner_padding - corner_size}px`); return this; }, @@ -1245,11 +1171,10 @@ class Plot { // positioning. TODO: make this additional call unnecessary. const client_rect = this.svg.node().getBoundingClientRect(); const width = client_rect.width ? client_rect.width : this.layout.width; - const height = client_rect.height ? client_rect.height : this.layout.height; + const height = client_rect.height ? client_rect.height : this._total_height; this.setDimensions(width, height); return this; - } /** @@ -1358,6 +1283,11 @@ class Plot { return this; } + + get _total_height() { + // The plot height is a calculated property, derived from the sum of its panel layout objects + return this.layout.panels.reduce((acc, item) => item.height + acc, 0); + } } export {Plot as default}; diff --git a/esm/components/toolbar/index.js b/esm/components/toolbar/index.js index 72df33cd..0b781c14 100644 --- a/esm/components/toolbar/index.js +++ b/esm/components/toolbar/index.js @@ -169,7 +169,7 @@ class Toolbar { const page_origin = this.parent._getPageOrigin(); const top = `${(page_origin.y + 3.5).toString()}px`; const left = `${page_origin.x.toString()}px`; - const width = `${(this.parent.layout.width - 4).toString()}px`; + const width = `${(this.parent_plot.layout.width - 4).toString()}px`; this.selector .style('position', 'absolute') .style('top', top) diff --git a/esm/components/toolbar/widgets.js b/esm/components/toolbar/widgets.js index 6daf1cad..79075720 100644 --- a/esm/components/toolbar/widgets.js +++ b/esm/components/toolbar/widgets.js @@ -312,12 +312,12 @@ class Button { let left; if (this.parent_toolbar.type === 'panel') { top = (page_origin.y + toolbar_client_rect.height + (2 * padding)); - left = Math.max(page_origin.x + this.parent_svg.layout.width - menu_client_rect.width - padding, page_origin.x + padding); + left = Math.max(page_origin.x + this.parent_plot.layout.width - menu_client_rect.width - padding, page_origin.x + padding); } else { top = button_client_rect.bottom + page_scroll_top + padding - container_offset.top; left = Math.max(button_client_rect.left + button_client_rect.width - menu_client_rect.width - container_offset.left, page_origin.x + padding); } - const base_max_width = Math.max(this.parent_svg.layout.width - (2 * padding) - scrollbar_padding, scrollbar_padding); + const base_max_width = Math.max(this.parent_plot.layout.width - (2 * padding) - scrollbar_padding, scrollbar_padding); const container_max_width = base_max_width; const content_max_width = (base_max_width - (4 * padding)); const base_max_height = Math.max(this.parent_svg.layout.height - (10 * padding) - menu_height_padding, menu_height_padding); @@ -658,24 +658,6 @@ class Title extends BaseWidget { } } -/** - * Renders text to display the current dimensions of the plot. Automatically updated as plot dimensions change - */ -class Dimensions extends BaseWidget { - update() { - const display_width = !this.parent_plot.layout.width.toString().includes('.') ? this.parent_plot.layout.width : this.parent_plot.layout.width.toFixed(2); - const display_height = !this.parent_plot.layout.height.toString().includes('.') ? this.parent_plot.layout.height : this.parent_plot.layout.height.toFixed(2); - this.selector.html(`${display_width}px × ${display_height}px`); - if (this.layout.class) { - this.selector.attr('class', this.layout.class); - } - if (this.layout.style) { - applyStyles(this.selector, this.layout.style); - } - return this; - } -} - /** * Display the current scale of the genome region displayed in the plot, as defined by the difference between * `state.end` and `state.start`. @@ -1500,7 +1482,6 @@ class SetState extends BaseWidget { export { BaseWidget, // This is used to create subclasses 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.. - Dimensions as dimensions, DisplayOptions as display_options, DownloadSVG as download, DownloadPNG as download_png, diff --git a/esm/data/adapters.js b/esm/data/adapters.js index 1a0c9222..fd1110e9 100644 --- a/esm/data/adapters.js +++ b/esm/data/adapters.js @@ -28,6 +28,11 @@ class BaseAdapter { 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 data source type is dependent on previous requests- for example, the LD source cannot annotate * association data if no data was found for that region. @@ -54,16 +59,34 @@ class BaseAdapter { /** * 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 URL. + * 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|undefined} + * @returns {String} */ getCacheKey(state, chain, fields) { - return this.getURL(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 data source 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 data sources 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}`; + } } /** @@ -99,16 +122,20 @@ class BaseAdapter { * 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; } } @@ -468,7 +495,9 @@ class LDServer extends BaseApiAdapter { /** * 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 - * @returns {*|string} The marker id (expected to be in `chr:pos_ref/alt` format) of the reference variant + * @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) { @@ -517,7 +546,23 @@ class LDServer extends BaseApiAdapter { } refVar = chain.body[findExtremeValue(chain.body, keys.pvalue)][keys.id]; } - return refVar; + // 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]; } getURL(state, chain, fields) { @@ -527,7 +572,7 @@ class LDServer extends BaseApiAdapter { // - population (ALL, AFR, EUR, etc) // - build // The LD source/pop can be overridden from plot.state for dynamic layouts - const build = state.genome_build || this.params.build || 'GRCh37'; + 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'; @@ -539,35 +584,29 @@ class LDServer extends BaseApiAdapter { validateBuildSource(this.constructor.name, build, null); // LD doesn't need to validate `source` option - let refVar = this.getRefvar(state, chain, fields); - // 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); + const [refVar_formatted, refVar_raw] = this.getRefvar(state, chain, fields); - 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. - refVar = `${chrom}:${pos}`; - if (ref && alt) { - refVar += `_${ref}/${alt}`; - } // Preserve the user-provided variant spec for use when matching to assoc data - chain.header.ldrefvar = original; + chain.header.ldrefvar = refVar_raw; return [ this.url, 'genome_builds/', build, '/references/', source, '/populations/', population, '/variants', '?correlation=', method, - '&variant=', encodeURIComponent(refVar), + '&variant=', encodeURIComponent(refVar_formatted), '&chrom=', encodeURIComponent(state.chr), '&start=', encodeURIComponent(state.start), '&stop=', encodeURIComponent(state.end), ].join(''); } + 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}`; + } + combineChainBody(data, chain, fields, outnames, trans) { let keys = this.findMergeFields(chain); let reqFields = this.findRequestedFields(fields, outnames); @@ -688,7 +727,7 @@ class GwasCatalogLZ extends BaseApiAdapter { return chain.body; } - // TODO: Better reuse options in the future. This source is very specifically tied to the PortalDev API, where + // 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)]; @@ -784,12 +823,6 @@ class GeneConstraintLZ extends BaseApiAdapter { // GraphQL API: request details are encoded in the body, not the URL return this.url; } - getCacheKey(state, chain, fields) { - const build = state.genome_build || this.params.build; - // GraphQL API: request not defined solely by the URL - // Gather the state params that govern constraint query for a given region. - return `${this.url} ${state.chr} ${state.start} ${state.end} ${build}`; - } normalizeResponse(data) { return data; @@ -828,7 +861,7 @@ class GeneConstraintLZ extends BaseApiAdapter { const body = JSON.stringify({ query: query }); const headers = { 'Content-Type': 'application/json' }; - // FIXME: The gnomAD API sometimes has temporary CORS changes that temporarily break the genes track + // Note: The gnomAD API sometimes fails randomly. // If request blocked, return a fake "no data" signal so the genes track can still render w/o constraint info return fetch(url, { method: 'POST', body, headers }).then((response) => { if (!response.ok) { @@ -924,6 +957,11 @@ class PheWASLZ extends BaseApiAdapter { ]; 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); + } } diff --git a/esm/ext/lz-credible-sets.js b/esm/ext/lz-credible-sets.js index af06cbc3..842e2266 100644 --- a/esm/ext/lz-credible-sets.js +++ b/esm/ext/lz-credible-sets.js @@ -153,7 +153,7 @@ function install (LocusZoom) { match: { send: '{{namespace[assoc]}}variant', receive: '{{namespace[assoc]}}variant' }, }); base.color.unshift({ - field: 'lz_highlight_match', // Special field name whose presence triggers custom rendering + field: 'lz_is_match', // Special field name whose presence triggers custom rendering scale_function: 'if', parameters: { field_value: true, @@ -173,7 +173,7 @@ function install (LocusZoom) { }, color: [ { - field: 'lz_highlight_match', // Special field name whose presence triggers custom rendering + field: 'lz_is_match', // Special field name whose presence triggers custom rendering scale_function: 'if', parameters: { field_value: true, @@ -209,10 +209,8 @@ function install (LocusZoom) { LocusZoom.Layouts.add('panel', 'annotation_credible_set', { id: 'annotationcredibleset', title: { text: 'SNPs in 95% credible set', x: 50, style: { 'font-size': '14px' } }, - width: 800, - height: 45, min_height: 45, - proportional_width: 1, + height: 45, margin: { top: 25, right: 50, bottom: 0, left: 50 }, inner_border: 'rgb(210, 210, 210)', toolbar: LocusZoom.Layouts.get('toolbar', 'standard_panel', { unnamespaced: true }), diff --git a/esm/ext/lz-intervals-track.js b/esm/ext/lz-intervals-track.js index f0045a22..f56c885f 100644 --- a/esm/ext/lz-intervals-track.js +++ b/esm/ext/lz-intervals-track.js @@ -557,6 +557,7 @@ function install (LocusZoom) { scale_function: 'to_rgb', }, { + // TODO: Consider changing this to stable_choice in the future, for more stable coloring field: '{{namespace[intervals]}}state_name', scale_function: 'categorical_bin', parameters: { @@ -587,10 +588,8 @@ function install (LocusZoom) { const intervals_panel_layout = { id: 'intervals', - width: 1000, - height: 50, - min_width: 500, min_height: 50, + height: 50, margin: { top: 25, right: 150, bottom: 5, left: 50 }, toolbar: (function () { const l = LocusZoom.Layouts.get('toolbar', 'standard_panel', { unnamespaced: true }); @@ -619,22 +618,14 @@ function install (LocusZoom) { const intervals_plot_layout = { state: {}, width: 800, - height: 550, responsive_resize: true, min_region_scale: 20000, max_region_scale: 1000000, toolbar: LocusZoom.Layouts.get('toolbar', 'standard_association', { unnamespaced: true }), panels: [ - LocusZoom.Layouts.get('panel', 'association', { - unnamespaced: true, - width: 800, - proportional_height: (225 / 570), - }), - Object.assign( - { unnamespaced: true, proportional_height: (120 / 570) }, - intervals_panel_layout - ), - LocusZoom.Layouts.get('panel', 'genes', { unnamespaced: true, width: 800, proportional_height: (225 / 570) }), + LocusZoom.Layouts.get('panel', 'association'), + LocusZoom.Layouts.merge({ unnamespaced: true, min_height: 120, height: 120 }, intervals_panel_layout), + LocusZoom.Layouts.get('panel', 'genes'), ], }; diff --git a/esm/ext/lz-tabix-source.js b/esm/ext/lz-tabix-source.js index 66a69857..c560608b 100644 --- a/esm/ext/lz-tabix-source.js +++ b/esm/ext/lz-tabix-source.js @@ -64,12 +64,6 @@ function install(LocusZoom) { }); } - getCacheKey(state /*, chain, fields*/) { - // In generic form, Tabix queries are based on chr, start, and end. The cache is thus controlled by the query, - // not the URL - return [state.chr, state.start, state.end, this._overfetch].join('_'); - } - fetchRequest(state /*, chain, fields */) { return new Promise((resolve, reject) => { // Ensure that the reader is fully created (and index available), then make a query diff --git a/esm/helpers/common.js b/esm/helpers/common.js index 05208a48..e82a3b2f 100644 --- a/esm/helpers/common.js +++ b/esm/helpers/common.js @@ -64,10 +64,10 @@ function generateCurtain() { this.curtain.selector .style('top', `${page_origin.y}px`) .style('left', `${page_origin.x}px`) - .style('width', `${this.layout.width}px`) + .style('width', `${this.parent_plot.layout.width}px`) .style('height', `${this.layout.height}px`); this.curtain.content_selector - .style('max-width', `${this.layout.width - 40}px`) + .style('max-width', `${this.parent_plot.layout.width - 40}px`) .style('max-height', `${this.layout.height - 40}px`); // Apply content if provided if (typeof content == 'string') { @@ -165,12 +165,7 @@ function generateLoader() { this.loader.selector .style('top', `${page_origin.y + this.layout.height - loader_boundrect.height - padding}px`) .style('left', `${page_origin.x + padding }px`); - /* Uncomment this code when a functional cancel button can be shown - var cancel_boundrect = this.loader.cancel_selector.node().getBoundingClientRect(); - this.loader.content_selector.style({ - "padding-right": (cancel_boundrect.width + padding) + "px" - }); - */ + // Apply percent if provided if (typeof percent == 'number') { this.loader.progress_selector @@ -254,5 +249,4 @@ function debounce(func, delay = 500) { }; } - export { applyStyles, debounce, generateCurtain, generateLoader }; diff --git a/esm/helpers/render.js b/esm/helpers/render.js index 2c8f99f8..a0df2a0d 100644 --- a/esm/helpers/render.js +++ b/esm/helpers/render.js @@ -51,7 +51,7 @@ function coalesce_scatter_points (data, x_min, x_max, x_gap, y_min, y_max, y_gap const y = item[ycs]; const in_combine_region = (x >= x_min && x <= x_max && y >= y_min && y <= y_max); - if (item.lz_highlight_match || !in_combine_region) { + if (item.lz_is_match || !in_combine_region) { // If an item is marked as interesting in some way, always render it explicitly // (and coalesce the preceding points if a run was in progress, to preserve ordering) _combine(); diff --git a/esm/helpers/scalable.js b/esm/helpers/scalable.js index 2e2f3719..a74daf28 100644 --- a/esm/helpers/scalable.js +++ b/esm/helpers/scalable.js @@ -84,15 +84,71 @@ const categorical_bin = (parameters, value) => { /** * Cycle through a set of options, so that the each element in a set of data receives a value different than the * element before it. For example: "use this palette of 10 colors to visually distinguish 100 adjacent items" + * This is useful when ADJACENT items must be guaranteed to yield a different result, but it leads to unstable color + * 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. * @param {Object} parameters * @param {Array} parameters.values A list of option values * @return {*} */ const ordinal_cycle = (parameters, value, index) => { - var options = parameters.values; + const options = parameters.values; return options[index % options.length]; }; +/** + * A scale function that auto-chooses something (like color) from a preset scheme, and makes the same choice every + * time given the same value, regardless of ordering or what other data is in the region + * + * This is useful when categories must be stable (same color, every time). But sometimes it will assign adjacent values + * the same color due to hash collisions. + * + * For performance reasons, this is memoized once per instance. Eg, each scalable color parameter has its own cache. + * This function is therefore slightly less amenable to layout mutations like "changing the options after scaling + * function is used", but this is not expected to be a common use case. + * + * CAVEAT: Some data sources 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. + * @param parameters + * @param {Array} parameters.values A list of option values + * @param {Number} [parameters.max_cache_size=500] The maximum number of values to cache. This option is mostly used + * for unit testing, because stable choice is intended for datasets with a relatively limited number of + * discrete categories. + * @param value + * @param index + */ +let stable_choice = (parameters, value, index) => { + const options = parameters.values; + // Each place the function gets used has its own parameters object. This function thus memoizes per usage + // ("association point color") rather than globally + const cache = parameters._cache = parameters._cache || new Map(); + const max_cache_size = parameters.max_cache_size || 500; + + if (cache.size >= max_cache_size) { + // Prevent cache from growing out of control (eg as user moves between regions a lot) + cache.clear(); + } + if (cache.has(value)) { + return cache.get(value); + } + + // Simple JS hashcode implementation, from: + // https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript + let hash = 0; + value = String(value); + for (let i = 0; i < value.length; i++) { + let chr = value.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; // Convert to 32bit integer + } + // Convert 32 bit integer to be within the range of options allowed + const result = options[Math.abs(hash) % options.length]; + cache.set(value, result); + return result; +}; + /** * Function for continuous interpolation of numerical values along a gradient with arbitrarily many break points. * @function interpolate @@ -144,4 +200,4 @@ const interpolate = (parameters, input) => { }; -export { categorical_bin, if_value, interpolate, numerical_bin, ordinal_cycle }; +export { categorical_bin, stable_choice, if_value, interpolate, numerical_bin, ordinal_cycle }; diff --git a/esm/index.js b/esm/index.js index e03a1fd7..94583134 100644 --- a/esm/index.js +++ b/esm/index.js @@ -12,6 +12,7 @@ import { DATA_LAYERS as DataLayers, WIDGETS as Widgets, LAYOUTS as Layouts, + MATCHERS as MatchFunctions, SCALABLE as ScaleFunctions, TRANSFORMS as TransformationFunctions, } from './registry'; @@ -26,6 +27,7 @@ const LocusZoom = { Adapters, DataLayers, Layouts, + MatchFunctions, ScaleFunctions, TransformationFunctions, Widgets, diff --git a/esm/layouts/index.js b/esm/layouts/index.js index 41b7e64a..9325679c 100644 --- a/esm/layouts/index.js +++ b/esm/layouts/index.js @@ -117,6 +117,8 @@ const association_pvalues_layer = { namespace: { 'assoc': 'assoc', 'ld': 'ld' }, id: 'associationpvalues', 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', coalesce: { active: true, }, @@ -167,8 +169,6 @@ const association_pvalues_layer = { { shape: 'circle', color: '#B8B8B8', size: 40, label: 'no r² data', class: 'lz-data_layer-scatter' }, ], label: null, - 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', z_index: 2, x_axis: { field: '{{namespace[assoc]}}position', @@ -206,7 +206,7 @@ const coaccessibility_layer = { ], color: [ { - field: 'lz_highlight_match', // Special field name whose presence triggers custom rendering + field: 'lz_is_match', // Special field name whose presence triggers custom rendering scale_function: 'if', parameters: { field_value: true, @@ -214,7 +214,7 @@ const coaccessibility_layer = { }, }, { - field: 'lz_highlight_match', // Special field name whose presence triggers custom rendering + field: 'lz_is_match', // Special field name whose presence triggers custom rendering scale_function: 'if', parameters: { field_value: false, @@ -567,11 +567,8 @@ const region_nav_plot_toolbar = function () { const association_panel = { id: 'association', - width: 800, - height: 225, - min_width: 400, min_height: 200, - proportional_width: 1, + height: 225, margin: { top: 35, right: 50, bottom: 40, left: 50 }, inner_border: 'rgb(210, 210, 210)', toolbar: (function () { @@ -620,11 +617,8 @@ const association_panel = { const coaccessibility_panel = { id: 'coaccessibility', - width: 800, - height: 225, - min_width: 400, - min_height: 100, - proportional_width: 1, + min_height: 150, + height: 180, margin: { top: 35, right: 50, bottom: 40, left: 50 }, inner_border: 'rgb(210, 210, 210)', toolbar: deepCopy(standard_panel_toolbar), @@ -713,11 +707,8 @@ const association_catalog_panel = function () { const genes_panel = { id: 'genes', - width: 800, + min_height: 150, height: 225, - min_width: 400, - min_height: 112.5, - proportional_width: 1, margin: { top: 20, right: 50, bottom: 20, left: 50 }, axes: {}, interaction: { @@ -744,11 +735,8 @@ const genes_panel = { const phewas_panel = { id: 'phewas', - width: 800, - height: 300, - min_width: 800, min_height: 300, - proportional_width: 1, + height: 300, margin: { top: 20, right: 50, bottom: 120, left: 50 }, inner_border: 'rgb(210, 210, 210)', axes: { @@ -776,10 +764,8 @@ const phewas_panel = { const annotation_catalog_panel = { id: 'annotationcatalog', - width: 800, - height: 45, min_height: 45, - proportional_width: 1, + height: 45, margin: { top: 25, right: 50, bottom: 0, left: 50 }, inner_border: 'rgb(210, 210, 210)', toolbar: deepCopy(standard_panel_toolbar), @@ -800,43 +786,38 @@ const annotation_catalog_panel = { const standard_association_plot = { state: {}, width: 800, - height: 450, responsive_resize: true, min_region_scale: 20000, max_region_scale: 1000000, - toolbar: deepCopy(standard_association_toolbar), + toolbar: standard_association_toolbar, panels: [ - merge({ proportional_height: 0.5}, deepCopy(association_panel)), - merge({ proportional_height: 0.5}, deepCopy(genes_panel)), + deepCopy(association_panel), + deepCopy(genes_panel), ], }; const association_catalog_plot = { state: {}, width: 800, - height: 500, responsive_resize: true, min_region_scale: 20000, max_region_scale: 1000000, - toolbar: deepCopy(standard_association_toolbar), + toolbar: standard_association_toolbar, panels: [ - deepCopy(annotation_catalog_panel), - deepCopy(association_catalog_panel), - deepCopy(genes_panel), + annotation_catalog_panel, + association_catalog_panel, + genes_panel, ], }; const standard_phewas_plot = { width: 800, - height: 600, - min_width: 800, - min_height: 600, responsive_resize: true, - toolbar: deepCopy(standard_plot_toolbar), + toolbar: standard_plot_toolbar, panels: [ - merge({proportional_height: 0.5}, deepCopy(phewas_panel)), + deepCopy(phewas_panel), merge({ - proportional_height: 0.5, + height: 300, margin: { bottom: 40 }, axes: { x: { @@ -854,28 +835,24 @@ const standard_phewas_plot = { const coaccessibility_plot = { state: {}, width: 800, - height: 450, responsive_resize: true, min_region_scale: 20000, max_region_scale: 1000000, toolbar: deepCopy(standard_plot_toolbar), panels: [ - Object.assign( - { proportional_height: 0.4 }, - deepCopy(coaccessibility_panel) - ), + deepCopy(coaccessibility_panel), function () { // Take the default genes panel, and add a custom feature to highlight gene tracks based on short name // This is a companion to the "match" directive in the coaccessibility panel const base = Object.assign( - { proportional_height: 0.6 }, + { height: 270 }, deepCopy(genes_panel) ); const layer = base.data_layers[0]; layer.match = { send: 'gene_name', receive: 'gene_name' }; const color_config = [ { - field: 'lz_highlight_match', // Special field name whose presence triggers custom rendering + field: 'lz_is_match', // Special field name whose presence triggers custom rendering scale_function: 'if', parameters: { field_value: true, @@ -883,7 +860,7 @@ const coaccessibility_plot = { }, }, { - field: 'lz_highlight_match', // Special field name whose presence triggers custom rendering + field: 'lz_is_match', // Special field name whose presence triggers custom rendering scale_function: 'if', parameters: { field_value: false, diff --git a/esm/registry/index.js b/esm/registry/index.js index 4d10648a..371770f0 100644 --- a/esm/registry/index.js +++ b/esm/registry/index.js @@ -6,8 +6,14 @@ import ADAPTERS from './adapters'; import DATA_LAYERS from './data_layers'; import LAYOUTS from './layouts'; +import MATCHERS from './matchers'; import SCALABLE from './scalable'; import TRANSFORMS from './transforms'; import WIDGETS from './widgets'; -export { ADAPTERS, DATA_LAYERS, LAYOUTS, SCALABLE, TRANSFORMS, WIDGETS }; +export { + // Base classes and reusable components + ADAPTERS, DATA_LAYERS, LAYOUTS, WIDGETS, + // User defined functions for injecting custom behavior into layout directives + MATCHERS, SCALABLE, TRANSFORMS, +}; diff --git a/esm/registry/matchers.js b/esm/registry/matchers.js new file mode 100644 index 00000000..77a5fb41 --- /dev/null +++ b/esm/registry/matchers.js @@ -0,0 +1,30 @@ +/** + * "Match" test functions used to compare two values for filtering (what to render) and matching + * (comparison and finding related points across data layers) + * + * All "matcher" functions have the call signature (item_value, target_value) => {boolean} + * Both filtering and matching depend on asking "is this field interesting to me", which is inherently a problem of + * making comparisons. The registry allows any arbitrary function (with a field value as the first argument), but that + * function doesn't have to use either argument. + * + */ +import {RegistryBase} from './base'; + +const registry = new RegistryBase(); + +// Most of the filter syntax uses things that are JS reserved operators. Instead of exporting symbols from another +// module, just define and register them here. + +registry.add('=', (a, b) => a === b); +// eslint-disable-next-line eqeqeq +registry.add('!=', (a, b) => a != b); // For absence of a value, deliberately allow weak comparisons (eg undefined/null) +registry.add('<', (a, b) => a < b); +registry.add('<=', (a, b) => a <= b); +registry.add('>', (a, b) => a > b); +registry.add('>=', (a, b) => a >= b); +registry.add('%', (a, b) => a % b); +registry.add('in', (a, b) => b && b.includes(a)); // works for strings or arrays: "item value for gene type is one of the allowed categories of interest" +registry.add('match', (a, b) => a && a.includes(b)); // useful for text search: "find all gene names that contain the user-entered value HLA" + + +export default registry; diff --git a/esm/registry/scalable.js b/esm/registry/scalable.js index 09ae8af2..653c811e 100644 --- a/esm/registry/scalable.js +++ b/esm/registry/scalable.js @@ -1,4 +1,8 @@ /** + * Functions that control "scalable" layout directives: given a value (like a number) return another value + * (like a color, size, or shape) that governs how something is displayed + * + * All scale functions have the call signature `(layout_parameters, input) => result|null` * @module * @private */ diff --git a/esm/registry/transforms.js b/esm/registry/transforms.js index 82be99e4..b497877e 100644 --- a/esm/registry/transforms.js +++ b/esm/registry/transforms.js @@ -6,7 +6,7 @@ import {RegistryBase} from './base'; import * as transforms from '../helpers/transforms'; /** - * Registry of transformation functions that may be applied to template values. + * Registry of transformation functions that may be applied to template values to control how values are rendered. * Provides syntactic sugar atop a standard registry. * @private */ diff --git a/esm/version.js b/esm/version.js index cc7771c1..db3b0c8a 100644 --- a/esm/version.js +++ b/esm/version.js @@ -1 +1 @@ -export default '0.13.0-beta.3'; +export default '0.13.0-beta.4'; diff --git a/examples/gwas_catalog.html b/examples/gwas_catalog.html index 5b30ff34..96ddb5b6 100644 --- a/examples/gwas_catalog.html +++ b/examples/gwas_catalog.html @@ -113,7 +113,7 @@

Top Hits

var layout_assoc_layer = layout.panels[1].data_layers[2]; layout_assoc_layer.match = { send: 'assoc:position', receive: 'assoc:position' }; layout_assoc_layer.color.unshift({ - field: 'lz_highlight_match', // Special field name whose presence triggers custom rendering + field: 'lz_is_match', // Special field name whose presence triggers custom rendering scale_function: 'if', parameters: { field_value: true, @@ -125,7 +125,7 @@

Top Hits

layout_annotation_track.match = { send: 'catalog:pos', receive: 'catalog:pos' }; layout_annotation_track.color = [ { - field: 'lz_highlight_match', // Special field name whose presence triggers custom rendering + field: 'lz_is_match', // Special field name whose presence triggers custom rendering scale_function: 'if', parameters: { field_value: true, diff --git a/examples/multiple_phenotypes_layered.html b/examples/multiple_phenotypes_layered.html index 1102634c..4b8193cd 100644 --- a/examples/multiple_phenotypes_layered.html +++ b/examples/multiple_phenotypes_layered.html @@ -88,7 +88,6 @@

Top Hits

}); layout = { width: 800, - height: 500, responsive_resize: true, panels: [ LocusZoom.Layouts.get("panel", "association", association_panel_mods), diff --git a/examples/phewas_forest.html b/examples/phewas_forest.html index 20d0e6d1..6062180f 100644 --- a/examples/phewas_forest.html +++ b/examples/phewas_forest.html @@ -59,15 +59,14 @@
< return home
// Define a reusable layout, and then retrieve it so that the namespaces get filled in LocusZoom.Layouts.add("plot", "phewas_forest", { + min_width: 400, width: 800, - height: 800, responsive_resize: true, panels: [ { id: "phewasforest", - width: 800, + min_height: 400, height: 800, - proportional_width: 1, margin: { top: 35, right: 220, bottom: 50, left: 20 }, inner_border: "rgba(210, 210, 210, 0.85)", toolbar: { diff --git a/index.html b/index.html index cb319ccc..aa8d1189 100644 --- a/index.html +++ b/index.html @@ -76,7 +76,7 @@

Get LocusZoom.js

CSS
@@ -96,7 +96,7 @@
Dependencies
Javascript
diff --git a/package-lock.json b/package-lock.json index 3d6b280c..aa7f91b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "locuszoom", - "version": "0.13.0-beta.3", + "version": "0.13.0-beta.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 18411c54..75b41ca5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "locuszoom", - "version": "0.13.0-beta.3", + "version": "0.13.0-beta.4", "main": "dist/locuszoom.app.min.js", "module": "esm/index.js", "sideEffects": true, diff --git a/test/unit/components/test_datalayer.js b/test/unit/components/test_datalayer.js index 5b0e9755..131a5151 100644 --- a/test/unit/components/test_datalayer.js +++ b/test/unit/components/test_datalayer.js @@ -2,7 +2,7 @@ import {assert} from 'chai'; import * as d3 from 'd3'; import sinon from 'sinon'; -import { SCALABLE } from '../../../esm/registry'; +import { MATCHERS, SCALABLE } from '../../../esm/registry'; import BaseDataLayer from '../../../esm/components/data_layer/base'; import {populate} from '../../../esm/helpers/display'; import DataSources from '../../../esm/data'; @@ -17,10 +17,10 @@ describe('LocusZoom.DataLayer', function () { beforeEach(function () { const layout = { width: 800, - height: 400, panels: [ { - id: 'panel0', width: 800, proportional_width: 1, height: 400, proportional_height: 1, + id: 'panel0', + height: 400, data_layers: [ { id: 'layerA', type: 'line' }, { id: 'layerB', type: 'line' }, @@ -829,6 +829,41 @@ describe('LocusZoom.DataLayer', function () { assert.deepEqual(result, [{ a: 'exact' }]); }); + it('can work with user-specified filters', function () { + MATCHERS.add('near_to', (a, b) => a < (b + 100) && a > (b - 100)); + const layer = new BaseDataLayer({id_field: 'a'}); + const options = [{ field: 'a', operator: 'near_to', value: 200 }]; + const data = [{ a: 50 }, { a: 200 }, { a: 250 }]; + + const result = data.filter(layer.filter.bind(layer, options)); + assert.equal(result.length, 2); + assert.deepEqual(result, [{ a: 200 }, {a: 250 }]); + + MATCHERS.remove('near_to'); + }); + + it('can use transformed field values with filter rules', function() { + const layer = new BaseDataLayer({id_field: 'a'}); + const options = [{ field: 'a|scinotation', operator: '=', value: '1.000' }]; + const data = [{ a: 1 }, { a: 0 }, { a: 1 }]; + + const result = data.filter(layer.filter.bind(layer, options)); + assert.equal(result.length, 2); + // Note that transform results are cached, so they will show up in the internal representation of the data + // after fetching + assert.deepEqual(result, [{ a: 1, 'a|scinotation': '1.000' }, { a: 1, 'a|scinotation': '1.000' }]); + }); + + it('throws an error when an unrecognized filter is specified', function () { + const layer = new BaseDataLayer({id_field: 'a'}); + const options = [{ field: 'a', operator: 'doesnotexist', value: 200 }]; + const data = [{ a: 50 }, { a: 200 }, { a: 250 }]; + + assert.throws(() => { + data.filter(layer.filter.bind(layer, options)); + }, 'Item not found: doesnotexist'); + }); + describe('interaction with data fetching', function () { beforeEach(function () { this.plot = null; diff --git a/test/unit/components/test_panel.js b/test/unit/components/test_panel.js index 38c4ac24..48aa0037 100644 --- a/test/unit/components/test_panel.js +++ b/test/unit/components/test_panel.js @@ -66,31 +66,15 @@ describe('Panel', function() { it('should allow changing dimensions', function() { // TODO: What, exactly, is this testing? this.association_panel.setDimensions(840, 560); - assert.equal(this.association_panel.layout.width, 840); assert.equal(this.association_panel.layout.height, 560); this.association_panel.setDimensions(9000, -50); - assert.equal(this.association_panel.layout.width, 840); assert.equal(this.association_panel.layout.height, 560); this.association_panel.setDimensions('q', 942); - assert.equal(this.association_panel.layout.width, 840); assert.equal(this.association_panel.layout.height, 560); }); - it('should enforce minimum dimensions', function() { - assert.ok(this.association_panel.layout.width >= this.association_panel.layout.min_width); - assert.ok(this.association_panel.layout.height >= this.association_panel.layout.min_height); - - this.association_panel.setDimensions(this.association_panel.layout.min_width / 2, 0); - assert.ok(this.association_panel.layout.width >= this.association_panel.layout.min_width); - assert.ok(this.association_panel.layout.height >= this.association_panel.layout.min_height); - - this.association_panel.setDimensions(0, this.association_panel.layout.min_height / 2); - assert.ok(this.association_panel.layout.width >= this.association_panel.layout.min_width); - assert.ok(this.association_panel.layout.height >= this.association_panel.layout.min_height); - }); - it('should allow setting origin irrespective of plot dimensions', function() { this.plot.setDimensions(500, 600); this.association_panel.setOrigin(20, 50); @@ -119,7 +103,7 @@ describe('Panel', function() { assert.equal(this.association_panel.layout.margin.left, 4); assert.equal(this.association_panel.layout.cliparea.origin.x, 4); assert.equal(this.association_panel.layout.cliparea.origin.y, 1); - assert.equal(this.association_panel.layout.cliparea.width, this.association_panel.layout.width - (2 + 4)); + assert.equal(this.association_panel.layout.cliparea.width, this.association_panel.parent.layout.width - (2 + 4)); assert.equal(this.association_panel.layout.cliparea.height, this.association_panel.layout.height - (1 + 3)); this.association_panel.setMargin(0, '12', -17, {foo: 'bar'}); @@ -130,7 +114,7 @@ describe('Panel', function() { assert.equal(this.association_panel.layout.cliparea.origin.x, 4); assert.equal(this.association_panel.layout.cliparea.origin.y, 0); - assert.equal(this.association_panel.layout.cliparea.width, this.association_panel.layout.width - (12 + 4)); + assert.equal(this.association_panel.layout.cliparea.width, this.plot.layout.width - (12 + 4)); assert.equal(this.association_panel.layout.cliparea.height, this.association_panel.layout.height - (0 + 3)); }); @@ -180,9 +164,8 @@ describe('Panel', function() { beforeEach(function() { const layout = { width: 800, - height: 400, panels: [ - { id: 'panel0', width: 800, proportional_width: 1, height: 400, proportional_height: 1 }, + { id: 'panel0', height: 400 }, ], }; d3.select('body').append('div').attr('id', 'plot'); @@ -232,17 +215,8 @@ describe('Panel', function() { const datasources = new DataSources(); this.layout = { width: 100, - height: 100, - min_width: 100, - min_height: 100, resizable: false, - panels: [ - { - id: 'test', - width: 100, - height: 100, - }, - ], + panels: [{ id: 'test', height: 100 }], }; d3.select('body').append('div').attr('id', 'plot'); this.plot = populate('#plot', datasources, this.layout); @@ -339,11 +313,9 @@ describe('Panel', function() { .add('static', ['StaticJSON', [{ id: 'a', x: 1, y: 2 }, { id: 'b', x: 3, y: 4 }, { id: 'c', x: 5, y: 6 }] ]); this.layout = { width: 100, - height: 100, panels: [ { id: 'p', - width: 100, height: 100, axes: { x: { label: 'x' }, @@ -384,7 +356,6 @@ describe('Panel', function() { d3.select('body').append('div').attr('id', 'plot'); const layout = { width: 100, - height: 100, panels: [ { id: 'p1', interaction: { x_linked: true } }, { id: 'p2', interaction: { y1_linked: true } }, diff --git a/test/unit/components/test_plot.js b/test/unit/components/test_plot.js index 010566e3..42847616 100644 --- a/test/unit/components/test_plot.js +++ b/test/unit/components/test_plot.js @@ -14,9 +14,6 @@ describe('LocusZoom.Plot', function() { beforeEach(function() { const layout = { width: 100, - height: 100, - min_width: 1, - min_height: 1, panels: [], }; d3.select('body').append('div').attr('id', 'plot'); @@ -46,70 +43,62 @@ describe('LocusZoom.Plot', function() { 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' }); - const panelB = this.plot.addPanel({ id: 'panelB', foo: 'baz' }); + const panelA = this.plot.addPanel({ id: 'panelA', foo: 'bar', height: 10 }); + const panelB = this.plot.addPanel({ id: 'panelB', foo: 'baz', height: 20 }); assert.hasAnyKeys(this.plot.panels, ['panelA']); assert.equal(this.plot.panels[panelA.id].id, panelA.id); assert.equal(this.plot.layout.panels.length, 2); + assert.equal(this.plot._total_height, 30, 'Initial height is sum of the two panels'); + this.plot.removePanel('panelA'); assert.doesNotHaveAnyKeys(this.plot.panels, ['panelA']); assert.equal(this.plot.layout.panels.length, 1); assert.equal(this.plot.layout.panels[0].id, 'panelB'); assert.equal(this.plot.panels[panelB.id].layout_idx, 0); + + assert.equal(this.plot._total_height, 20, 'Final height is the space requested by the remaining single panel'); }); - it('should allow setting dimensions, bounded by layout minimums', function() { + it('should allow setting dimensions', function() { this.plot.setDimensions(563, 681); assert.equal(this.plot.layout.width, 563); - assert.equal(this.plot.layout.height, 681); + // Tests for awkward API choice: invalid numbers can be provided but are quietly ignored. this.plot.setDimensions(1320.3, -50); assert.equal(this.plot.layout.width, 563); - assert.equal(this.plot.layout.height, 681); this.plot.setDimensions('q', 0); - assert.equal(this.plot.layout.width, 563); - assert.equal(this.plot.layout.height, 681); - - this.plot.setDimensions(1, 1); - assert.equal(this.plot.layout.width, this.plot.layout.min_width); - assert.equal(this.plot.layout.height, this.plot.layout.min_height); - }); - it('should enforce minimum dimensions based on its panels', function() { - this.plot.addPanel({ id: 'p1', width: 50, height: 30, min_width: 50, min_height: 30 }); - this.plot.addPanel({ id: 'p2', width: 20, height: 10, min_width: 20, min_height: 10 }); - this.plot.setDimensions(1, 1); - assert.equal(this.plot.layout.min_width, 50); - assert.equal(this.plot.layout.min_height, 40); - assert.equal(this.plot.layout.width, this.plot.layout.min_width); - assert.equal(this.plot.layout.height, this.plot.layout.min_height); - }); - it('should allow for responsively positioning panels using a proportional dimensions', function() { - const responsive_layout = LAYOUTS.get('plot', 'standard_association', { - responsive_resize: true, - panels: [ - { id: 'positions', proportional_width: 1, proportional_height: 0.6, min_height: 60 }, - { id: 'genes', proportional_width: 1, proportional_height: 0.4, min_height: 40 }, - ], - }); - responsive_layout.state = { chr: '1', start: 1, end: 100000 }; - this.plot = populate('#plot', null, responsive_layout); - assert.equal(this.plot.layout.panels[0].height / this.plot.layout.height, 0.6); - assert.equal(this.plot.layout.panels[1].height / this.plot.layout.height, 0.4); - this.plot.setDimensions(2000); - assert.equal(this.plot.layout.panels[0].height / this.plot.layout.height, 0.6); - assert.equal(this.plot.layout.panels[1].height / this.plot.layout.height, 0.4); - this.plot.setDimensions(900, 900); - assert.equal(this.plot.layout.panels[0].height / this.plot.layout.height, 0.6); - assert.equal(this.plot.layout.panels[1].height / this.plot.layout.height, 0.4); + 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'); + + const panelA = this.plot.addPanel({ id: 'panelA', height: 100 }); + const panelB = this.plot.addPanel({ id: 'panelB', height: 200 }); + + this.plot.setDimensions(100, 600); + + assert.equal(this.plot._total_height, 600, 'Plot is set to the specified height'); + + 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'); + + const panelA = this.plot.addPanel({ id: 'panelA', min_height: 50, height: 100 }); + const panelB = this.plot.addPanel({ id: 'panelB', min_height: 100, height: 200 }); + this.plot.setDimensions(100, 100); - assert.equal(this.plot.layout.panels[0].height / this.plot.layout.height, 0.6); - assert.equal(this.plot.layout.panels[1].height / this.plot.layout.height, 0.4); + + assert.equal(this.plot._total_height, 150, 'Plot cannot be smaller than min_heights'); + + 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, - height: 500, panels: [ LAYOUTS.get('panel', 'association', { margin: { left: 200 } }), LAYOUTS.get('panel', 'association', { id: 'assoc2', margin: { right: 300 } }), @@ -122,21 +111,14 @@ describe('LocusZoom.Plot', function() { 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].width, this.plot.layout.panels[0].width); assert.equal(this.plot.layout.panels[0].origin.x, this.plot.layout.panels[0].origin.x); }); it('should not allow for a non-numerical / non-positive predefined dimensions', function() { assert.throws(() => { - populate('#plot', null, { width: 0, height: 0 }); + populate('#plot', null, { width: 0 }); }); assert.throws(() => { - populate('#plot', null, { width: 20, height: -20 }); - }); - assert.throws(() => { - populate('#plot', null, { width: 'foo', height: 40 }); - }); - assert.throws(() => { - populate('#plot', null, { width: 60, height: [1, 2] }); + populate('#plot', null, { width: 'foo' }); }); }); }); @@ -160,84 +142,60 @@ describe('LocusZoom.Plot', function() { const datasources = new DataSources(); const layout = { width: 100, - height: 100, min_width: 100, - min_height: 100, panels: [], }; d3.select('body').append('div').attr('id', 'plot'); this.plot = populate('#plot', datasources, layout); }); - it('Should adjust the size of the panel if a single panel is added that does not completely fill min_height', function() { - const panelA = { id: 'panelA', width: 100, height: 50 }; + 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); const svg = d3.select('#plot svg'); assert.equal(this.plot.layout.width, 100); - assert.equal(this.plot.layout.height, 100); assert.equal((+svg.attr('width')), 100); - assert.equal((+svg.attr('height')), 100); - assert.equal(this.plot.panels.panelA.layout.width, 100); - assert.equal(this.plot.panels.panelA.layout.height, 100); - assert.equal(this.plot.panels.panelA.layout.proportional_height, 1); - assert.equal(this.plot.panels.panelA.layout.proportional_origin.y, 0); + assert.equal((+svg.attr('height')), 50); + assert.equal(this.plot.panels.panelA.layout.height, 50); assert.equal(this.plot.panels.panelA.layout.origin.y, 0); - assert.equal(this.plot.sumProportional('height'), 1); }); 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', width: 100, height: 60 }; - const panelB = { id: 'panelB', width: 100, height: 60 }; + const panelA = { id: 'panelA', height: 60 }; + const panelB = { id: 'panelB', height: 60 }; this.plot.addPanel(panelA); this.plot.addPanel(panelB); const svg = d3.select('#plot svg'); assert.equal(this.plot.layout.width, 100); - assert.equal(this.plot.layout.height, 160); assert.equal((+svg.attr('width')), 100); - assert.equal((+svg.attr('height')), 160); - assert.equal(this.plot.panels.panelA.layout.width, 100); - assert.equal(this.plot.panels.panelA.layout.height, 100); - assert.equal(this.plot.panels.panelA.layout.proportional_height, 0.625); - assert.equal(this.plot.panels.panelA.layout.proportional_origin.y, 0); + assert.equal((+svg.attr('height')), 120); + assert.equal(this.plot.panels.panelA.layout.height, 60); assert.equal(this.plot.panels.panelA.layout.origin.y, 0); - assert.equal(this.plot.panels.panelB.layout.width, 100); assert.equal(this.plot.panels.panelB.layout.height, 60); - assert.equal(this.plot.panels.panelB.layout.proportional_height, 0.375); - assert.equal(this.plot.panels.panelB.layout.proportional_origin.y, 0.625); - assert.equal(this.plot.panels.panelB.layout.origin.y, 100); - assert.equal(this.plot.sumProportional('height'), 1); + assert.equal(this.plot.panels.panelB.layout.origin.y, 60); }); it('Should resize the plot as panels are removed', function() { - const panelA = { id: 'panelA', width: 100, height: 60 }; - const panelB = { id: 'panelB', width: 100, height: 60 }; + const panelA = { id: 'panelA', height: 60 }; + const panelB = { id: 'panelB', height: 60 }; this.plot.addPanel(panelA); this.plot.addPanel(panelB); this.plot.removePanel('panelA'); const svg = d3.select('#plot svg'); assert.equal(this.plot.layout.width, 100); - assert.equal(this.plot.layout.height, 100); assert.equal((+svg.attr('width')), 100); - assert.equal((+svg.attr('height')), 100); - assert.equal(this.plot.panels.panelB.layout.width, 100); - assert.equal(this.plot.panels.panelB.layout.height, 100); - assert.equal(this.plot.panels.panelB.layout.proportional_height, 1); - assert.equal(this.plot.panels.panelB.layout.proportional_origin.y, 0); + assert.equal((+svg.attr('height')), 60); + assert.equal(this.plot.panels.panelB.layout.height, 60); assert.equal(this.plot.panels.panelB.layout.origin.y, 0); - assert.equal(this.plot.sumProportional('height'), 1); }); it('Should resize the plot as panels are removed, when panels specify min_height', function() { - // Regression test for a reported edge case where `min_height` caused resizing logic to work differently - // Small hack; resize the plot after it was created - this.plot.layout.min_height = this.plot._base_layout.min_height = 600; - this.plot.layout.height = this.plot._base_layout.height = 600; - this.plot.layout.width = this.plot._base_layout.width = 800; - this.plot.layout.min_width = this.plot._base_layout.min_width = 800; - this.plot.layout.responsive_resize = this.plot._base_layout.responsive_resize = true; + this.plot.layout.height = 600; + this.plot.layout.width = 800; + this.plot.layout.responsive_resize = true; // These numbers are based on a real plot. Expected behavior is chosen to match behavior of a layout where // panels had no min_height specified. - const panelA = { id: 'panelA', width: 800, height: 300, min_height: 300 }; - const panelB = { id: 'panelB', width: 800, height: 50, min_height: 50 }; - const panelC = { id: 'panelC', width: 800, height: 225, min_height: 112.5 }; + const panelA = { id: 'panelA', height: 300 }; + const panelB = { id: 'panelB', height: 50 }; + const panelC = { id: 'panelC', height: 225 }; // Set up the scenario this.plot.addPanel(panelA); @@ -246,19 +204,11 @@ describe('LocusZoom.Plot', function() { this.plot.removePanel('panelC'); // Check dimensions. Some checks are approximate due to floating point rounding issues. - assert.equal(this.plot.layout.width, 800, 'Plot width is incorrect'); - assert.approximately(this.plot.layout.height, 650, 0.000001, 'Plot height is incorrect'); - - assert.equal(this.plot.panels.panelB.layout.width, 800, 'Panel B width is incorrect'); - assert.equal(this.plot.panels.panelB.layout.height, 50, 'Panel B height is incorrect'); + assert.equal(this.plot.panels.panelB.layout.height, 50, 'Panel B height matches layout value'); // When the overall plot specifies a min_height larger than any panel, the final resizing of the panels // must respect that setting. Thus, panels A and B will not have the same relative proportions // after panel C is removed. - assert.approximately(this.plot.panels.panelA.layout.proportional_height, 600 / 650, 0.000001, 'Panel A proportional height is incorrect'); - assert.approximately(this.plot.panels.panelB.layout.proportional_height, 50 / 650, 0.000001, 'Panel B proportional height is incorrect'); - assert.equal(this.plot.panels.panelB.layout.proportional_origin.y, 1 - 50 / 650, 'Panel B proportional_origin.y is incorrect'); - assert.equal(this.plot.panels.panelB.layout.origin.y, 600, 'Panel B origin.y is incorrect'); - assert.approximately(this.plot.sumProportional('height'), 1, 0.000001, 'Total height should be 1'); + 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 @@ -266,9 +216,9 @@ describe('LocusZoom.Plot', function() { // min_height is larger than any one panel, the space actually gets reallocated, and the remaining // panels stretch or shrink. // This test ensures that we will see the desired behavior when plot min_height isn't dominant. - const panelA = { id: 'panelA', width: 800, height: 300, min_height: 300 }; - const panelB = { id: 'panelB', width: 800, height: 50, min_height: 50 }; - const panelC = { id: 'panelC', width: 800, height: 225, min_height: 112.5 }; + const panelA = { id: 'panelA', height: 300 }; + const panelB = { id: 'panelB', height: 50 }; + const panelC = { id: 'panelC', height: 225 }; // Set up the scenario this.plot.addPanel(panelA); @@ -276,47 +226,44 @@ describe('LocusZoom.Plot', function() { this.plot.addPanel(panelC); // Verify that panel A and B adopt a specific proportion (when 3 panels are present) - assert.equal(this.plot.panels.panelA.layout.height, 300, 'Panel A height is incorrect (before)'); - assert.equal(this.plot.panels.panelB.layout.height, 50, 'Panel B height is incorrect (before)'); + assert.equal(this.plot.panels.panelA.layout.height, 300, 'Panel A height matches layout value (before)'); + assert.equal(this.plot.panels.panelB.layout.height, 50, 'Panel B height matches layout value (before)'); this.plot.removePanel('panelC'); // Check dimensions. Some checks are approximate due to floating point rounding issues. - assert.equal(this.plot.layout.width, 800, 'Plot width is incorrect'); - assert.equal(this.plot.layout.height, 350, 'Plot height is incorrect'); - // Panels A and B will have the same size and relative proportions after resize as before - assert.equal(this.plot.panels.panelA.layout.height, 300, 'Panel A height is incorrect (after)'); - assert.equal(this.plot.panels.panelB.layout.height, 50, 'Panel B height is incorrect (after)'); - assert.equal(this.plot.panels.panelB.layout.width, 800, 'Panel B width is incorrect'); - - assert.approximately(this.plot.panels.panelA.layout.proportional_height, 300 / 350, 0.000001, 'Panel A proportional height is incorrect'); - assert.approximately(this.plot.panels.panelB.layout.proportional_height, 50 / 350, 0.000001, 'Panel B proportional height is incorrect'); - assert.approximately(this.plot.panels.panelB.layout.proportional_origin.y, 1 - 50 / 350, 0.000001, 'Panel B proportional_origin.y is incorrect'); - assert.equal(this.plot.panels.panelB.layout.origin.y, 300, 'Panel B origin.y is incorrect'); - assert.approximately(this.plot.sumProportional('height'), 1, 0.000001, 'Total proportional height should be 1'); + assert.equal(this.plot.panels.panelA.layout.height, 300, 'Panel A height matches layout (after)'); + 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', width: 100, height: 60 }; - const panelB = { id: 'panelB', idth: 100, height: 60 }; + const panelA = { id: 'panelA', height: 60 }; + const panelB = { id: 'panelB', height: 61 }; this.plot.addPanel(panelA); this.plot.addPanel(panelB); - const panelC = { id: 'panelC', width: 100, height: 60, y_index: 1 }; + const panelC = { id: 'panelC', height: 62, y_index: 1 }; // In between A and B this.plot.addPanel(panelC); assert.equal(this.plot.panels.panelA.layout.y_index, 0); assert.equal(this.plot.panels.panelB.layout.y_index, 2); assert.equal(this.plot.panels.panelC.layout.y_index, 1); assert.deepEqual(this.plot.panel_ids_by_y_index, ['panelA', 'panelC', 'panelB']); + + assert.deepEqual(panelA.height, 60, 'panel A height matches layout value'); + assert.deepEqual(panelB.height, 61, 'panel B height matches layout value'); + assert.deepEqual(panelC.height, 62, 'panel C height matches layout value'); + + 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', width: 100, height: 60 }; - const panelB = { id: 'panelB', width: 100, height: 60 }; - const panelC = { id: 'panelC', width: 100, height: 60 }; - const panelD = { id: 'panelD', width: 100, height: 60 }; + const panelA = { id: 'panelA', height: 60 }; + const panelB = { id: 'panelB', height: 60 }; + const panelC = { id: 'panelC', height: 60 }; + const panelD = { id: 'panelD', height: 60 }; this.plot.addPanel(panelA); this.plot.addPanel(panelB); this.plot.addPanel(panelC); this.plot.addPanel(panelD); - const panelE = { id: 'panelE', width: 100, height: 60, y_index: -1 }; + const panelE = { id: 'panelE', height: 60, y_index: -1 }; this.plot.addPanel(panelE); assert.equal(this.plot.panels.panelA.layout.y_index, 0); assert.equal(this.plot.panels.panelB.layout.y_index, 1); @@ -332,9 +279,6 @@ describe('LocusZoom.Plot', function() { const datasources = new DataSources(); const layout = { width: 100, - height: 100, - min_width: 100, - min_height: 100, panels: [], }; d3.select('body').append('div').attr('id', 'plot'); @@ -409,7 +353,7 @@ describe('LocusZoom.Plot', function() { describe('State and Requests', function() { beforeEach(function() { this.datasources = new DataSources(); - this.layout = { width: 100, height: 100 }; + this.layout = { width: 100 }; d3.select('body').append('div').attr('id', 'plot'); }); afterEach(function() { @@ -621,16 +565,14 @@ describe('LocusZoom.Plot', function() { delete this.plot; }); + const get_matches = function (data) { + return data.map((item) => item.lz_is_match); + }; + it('notifies all layers to find matches when an event fires', function () { // This is the end result of triggering a match event, and lets us test after render promise complete const plot = this.plot; return this.plot.applyState({ lz_match_value: 0 }).then(function() { - - const get_matches = function (data) { - return data.map(function (item) { - return item.lz_highlight_match; - }); - }; const d1 = get_matches(plot.panels.p.data_layers.d1.data); const d2 = get_matches(plot.panels.p.data_layers.d2.data); const d3 = get_matches(plot.panels.p.data_layers.d3.data); @@ -640,6 +582,60 @@ describe('LocusZoom.Plot', function() { assert.deepEqual(d3, [false, false], 'layer 3 saw match event but no values matched'); }); }); + + it('allows matching rules to use transforms (on send)', function (done) { + const layer1 = this.plot.panels.p.data_layers.d1; + layer1.layout.match.send = 's:x|scinotation'; + + // We can validate the send rule by checking what value gets broadcast + layer1.parent.on('match_requested', (event) => { + assert.equal(event.data.value, '0', 'The item value was converted from a number to a string before being broadcast to other panels'); + done(); + }); + + // Trigger the match event on datapoint 1 from layer 1 + layer1.setElementStatus('selected', { 's:id': 'a', 's:x': 0, 's:y': false }, true, true); + }); + + it('allows matching rules to use transforms (on receive)', function () { + const layer1 = this.plot.panels.p.data_layers.d1; + layer1.layout.match.receive = 's:x|scinotation'; + return this.plot.applyState({lz_match_value: '1.000'}).then(() => { + const matches = get_matches(layer1.data); + assert.deepEqual(matches, [false, true], 'The broadcast value is compared to the modified value for a data item'); + }); + }); + + it('allows matching rules to use any match operators from the registry', function () { + const layer1 = this.plot.panels.p.data_layers.d1; + layer1.layout.match.operator = '>='; + return this.plot.applyState({lz_match_value: 0.5}).then(() => { + const matches = get_matches(layer1.data); + assert.deepEqual(matches, [false, true], 'Can match on a rule other than exact match'); + }); + }); + + it('allows a match event to trigger filtering behavior', function () { + // Example use case: click a data layer element, and other layers update to only render items that match that point + // Originally, match functionality was only used to change HOW something was shown. This allows + // matching to also change IF something is shown. Here we capture that this works, but to be honest, + // it's not often recommended- the user could get into a situation where they hid all their points, and + // have no way to "reset/clear" the match rule to show things again. + const layer1 = this.plot.panels.p.data_layers.d1; + // In a real use, we could trigger "only filter on match" with a custom function that returned true + // if EITHER a match occurred, OR lz_is_match was undefined (eg, no match had been attempted). + layer1.layout.filters = [{field: 'lz_is_match', operator: '=', value: true}]; + return this.plot.applyState({lz_match_value: 1}).then(() => { + // layer.data contains everything; select only filtered data elements + const filtered = layer1._applyFilters(); + assert.equal(filtered.length, 1, 'Only one data item is shown'); + assert.equal( + filtered[0]['s:id'], + 'b', + 'This item displayed is the one that satisfies matching rules' + ); + }); + }); }); }); diff --git a/test/unit/components/test_toolbar.js b/test/unit/components/test_toolbar.js index 86877eab..f421c959 100644 --- a/test/unit/components/test_toolbar.js +++ b/test/unit/components/test_toolbar.js @@ -12,7 +12,7 @@ describe('LocusZoom.Toolbar', function() { var layout = { toolbar: { widgets: [ - { type: 'dimensions' }, + { type: 'title', title: 'LocusZoom' }, ], }, panels: [ @@ -85,7 +85,7 @@ describe('LocusZoom.Toolbar', function() { var datasources = new DataSources(); var layout = { panels: [ - { id: 'test', width: 100, height: 100 }, + { id: 'test', height: 100 }, ], }; d3.select('body').append('div').attr('id', 'plot'); @@ -98,11 +98,11 @@ describe('LocusZoom.Toolbar', function() { var layout = { toolbar: { widgets: [ - { type: 'dimensions' }, + { type: 'title', title: 'LocusZoom' }, ], }, panels: [ - { id: 'test', width: 100, height: 100 }, + { id: 'test', height: 100 }, ], }; d3.select('body').append('div').attr('id', 'plot'); diff --git a/test/unit/components/test_widgets.js b/test/unit/components/test_widgets.js index 00dfb048..188ad0bd 100644 --- a/test/unit/components/test_widgets.js +++ b/test/unit/components/test_widgets.js @@ -5,35 +5,6 @@ import {assert} from 'chai'; describe('Toolbar widgets', function () { - describe('Dimensions Widget', function() { - beforeEach(function() { - var datasources = new DataSources(); - var layout = { - toolbar: { - widgets: [ - { type: 'dimensions' }, - ], - }, - panels: [ - { id: 'test', width: 100, height: 100 }, - ], - }; - d3.select('body').append('div').attr('id', 'plot'); - this.plot = populate('#plot', datasources, layout); - }); - afterEach(function() { - d3.select('#plot').remove(); - this.plot = null; - }); - it('Should show initial plot dimensions', function() { - assert.equal(this.plot.toolbar.widgets[0].selector.html(), '100px × 100px'); - }); - it('Should show updated plot dimensions automatically as dimensions change', function() { - this.plot.setDimensions(220, 330); - assert.equal(this.plot.toolbar.widgets[0].selector.html(), '220px × 330px'); - }); - }); - describe('Region Scale Widget', function() { beforeEach(function() { var datasources = new DataSources(); diff --git a/test/unit/data/test_adapters.js b/test/unit/data/test_adapters.js index 2d0251be..c32a8e31 100644 --- a/test/unit/data/test_adapters.js +++ b/test/unit/data/test_adapters.js @@ -54,6 +54,70 @@ describe('Data adapters', function () { }); }); + 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(); + }); + }); + describe('Source.parseResponse', function () { // Parse response is a wrapper for a set of helper methods. Test them individually, and combined. afterEach(function () { @@ -416,46 +480,46 @@ describe('Data adapters', function () { 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: 'Something' }, + const [ref, _] = source.getRefvar( + { ldrefvar: '12:100_A/C' }, { header: {}, body: [{ id: 'a', pvalue: 0 }] }, ['ldrefvar', 'state'] ); - assert.equal(ref, 'Something'); + 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( + const [ref, _] = source.getRefvar( {}, { header: {}, body: [ - { id: 'a', pvalue: 0.5 }, - { id: 'b', pvalue: 0.05 }, - { id: 'c', pvalue: 0.1 }, + { 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, 'b'); + 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( + const [ref, _] = source.getRefvar( {}, { header: {}, body: [ - { id: 'a', log_pvalue: 10 }, - { id: 'b', log_pvalue: 50 }, - { id: 'c', log_pvalue: 7 }, + { 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, 'b'); + assert.equal(ref, '12:100_A/B'); }); it('correctly identifies the variant-marker field', function () { diff --git a/test/unit/ext/test_ext_intervals-track.js b/test/unit/ext/test_ext_intervals-track.js index ac591172..86da1cc2 100644 --- a/test/unit/ext/test_ext_intervals-track.js +++ b/test/unit/ext/test_ext_intervals-track.js @@ -138,7 +138,7 @@ describe('Interval annotation track', function () { const layout = { panels: [ LAYOUTS.get('panel', 'intervals') ], state: { chr: 'X', start: 1, end: 500 }, - width: 800, height: 550, + width: 800, }; layout.panels[0].data_layers[0].split_tracks = true; diff --git a/test/unit/helpers/test_render.js b/test/unit/helpers/test_render.js index fa17195b..6e3f6f0f 100644 --- a/test/unit/helpers/test_render.js +++ b/test/unit/helpers/test_render.js @@ -119,9 +119,9 @@ describe('Coordinate coalescing', function () { it('always adds points that are explicitly marked significant', function () { const sample_data = _addSymbols([ - { x: 0, y: 0.5, lz_highlight_match: true }, - { x: 1, y: 0.9, lz_highlight_match: true }, - { x: 2, y: 0.999, lz_highlight_match: true }, + { x: 0, y: 0.5, lz_is_match: true }, + { x: 1, y: 0.9, lz_is_match: true }, + { x: 2, y: 0.999, lz_is_match: true }, ]); const actual = coalesce_scatter_points( sample_data, diff --git a/test/unit/test_scalable.js b/test/unit/test_scalable.js index 9b16e898..256b7bb1 100644 --- a/test/unit/test_scalable.js +++ b/test/unit/test_scalable.js @@ -1,6 +1,6 @@ import { assert } from 'chai'; -import {categorical_bin, if_value, interpolate, numerical_bin, ordinal_cycle} from '../../esm/helpers/scalable'; +import {categorical_bin, if_value, stable_choice, interpolate, numerical_bin, ordinal_cycle} from '../../esm/helpers/scalable'; describe('Scale Functions', function() { describe('if', function() { @@ -77,6 +77,65 @@ describe('Scale Functions', function() { assert.equal(categorical_bin(parameters), 'null_value'); }); }); + describe('stable_choice', function () { + it('will choose the same options from a set given the same value, regardless of data order', function () { + const parameters = { values: Array.from({length: 20}, (x, i) => i) }; + assert.equal(stable_choice(parameters, 'MODERN_MAJOR_GENERAL', 0), 14); + assert.equal(stable_choice(parameters, 'MODERN_MAJOR_GENERAL', 99), 14); + }); + it('the number of options will influence the outcome', function () { + // This produces the same option from the same hash, BUT ONLY GIVEN THE SAME OPTIONS. + // We actually check HASH % LENGTH. So adding one new option to the array will choose a different result. + let parameters = { values: Array.from({length: 20}, (x, i) => i) }; + assert.equal(stable_choice(parameters, 'MODERN_MAJOR_GENERAL', 0), 14, 'Shorter options array makes one choice'); + + parameters = { values: Array.from({length: 50}, (x, i) => i) }; + assert.equal(stable_choice(parameters, 'MODERN_MAJOR_GENERAL', 0), 24, 'Longer options array = different choice for same input value'); + }); + it('handles various datatypes', function () { + const parameters = { values: Array.from({length: 20}, (x, i) => i) }; + // Look, user data is messy, ok? Sometimes values just... wander off... in otherwise good data + assert.equal(stable_choice(parameters, undefined), 4, 'Should handle undefined'); + assert.equal(stable_choice(parameters, null), 3, 'Should handle null'); + // "Normal" types of data (including edge cases) + assert.equal(stable_choice(parameters, true), 18, 'Should handle booleans'); + assert.equal(stable_choice(parameters, 12), 9, 'Should handle integers'); + assert.equal(stable_choice(parameters, Infinity), 16, 'Should handle infinity'); + assert.equal(stable_choice(parameters, NaN), 3, 'Should handle NaNs'); + assert.equal(stable_choice(parameters, 'Hi!'), 0, 'Should handle strings'); + assert.equal(stable_choice(parameters, ''), 0, 'Should handle empty strings'); + assert.equal(stable_choice(parameters, 'a🐙b'), 17, 'Should handle strings with unicode'); + // Container types with the same string representation will receive the same hash. + // Oh good, you know that two arrays aren't === in JS. Congrats on your CS degree but not relevant here. + assert.equal(stable_choice(parameters, ['a', 'b', 'c']), 2, 'Should handle arrays that serialize to a string'); + // Document that the string representation of an object is useless for comparison (always [object Object]) + assert.equal(stable_choice(parameters, {}), 8, 'An empty object will yield a hash value...'); + assert.equal(stable_choice(parameters, { afield: 'avalue' }), 8, '...same value returned regardless of object contents'); + }); + it('memoizes repeated calls', function () { + const parameters = { values: ['a', 'b', 'c'] }; + assert.equal(stable_choice(parameters, 'avalue'), 'c', 'First call returns expected result'); + assert.exists(parameters._cache, 'A cache is created on the parameters object'); + assert.hasAllKeys(parameters._cache, ['avalue'], 'Result is cached'); + + // To test that we're returning a cached value, we'll reach in and modify the cache + parameters._cache.set('avalue', 42); + assert.equal(stable_choice(parameters, 'avalue'), 42, 'Second call uses the cached result'); + }); + it('allows setting a max cache size, after which all old keys are evicted', function () { + const parameters = { values: ['a', 'b', 'c'], max_cache_size: 2 }; + + stable_choice(parameters, 1); + stable_choice(parameters, 2); + assert.hasAllKeys(parameters._cache, ['1', '2'], 'First two calls are cached'); + + stable_choice(parameters, 3); + assert.hasAllKeys(parameters._cache, ['3'], 'At third call, prior two keys are evicted.'); + + stable_choice(parameters, 4); + assert.hasAllKeys(parameters._cache, ['3', '4'], 'Fourth call is added to cache'); + }); + }); describe('ordinal_cycle', function () { before(function () { this.options = { values: ['a', 'b', 'c'] };