Skip to content

Commit

Permalink
Merge branch 'release/0.10.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
abought committed Jan 15, 2020
2 parents ef66428 + c8f6b20 commit e3c5f8b
Show file tree
Hide file tree
Showing 22 changed files with 298 additions and 160 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
language: node_js
node_js:
- "lts/carbon"
- "lts/erbium"
cache:
yarn: true
directories:
Expand All @@ -15,7 +16,6 @@ branches:
- develop
notifications:
email:
- [email protected]
- [email protected]
slack:
secure: z9tSuzj4LCeJ8SWA0mnOkXPzbufXGlbDOGXSihPAahFVoiTWgL2biA7ZOlZrF3lUBKurJnbP/vOJRAdcA/p+4gymvUXjNJY1uUAxRPo0TVApzhlJAVlYGD/KRoALhbCKMj8Y949esbY1IEKb1/IbfFSCL51wFo2E3G6woSAL3MlH+nDP8WI0LjqjqckrrFgKq8PI5v1b0eSVVK5Guog8+UZYWZhP8A3dvpwBB1cteggby7kmEedQQu4VZ6fh2GbdkZJ4w2IZVFdgTx+nEQuElcnTFvLGiK1U9eR44aLOzJRvnvNxswgiQj2DkEf1Qf2+XvCi9Z4ckozfpqt0fgUIgIOM/7Gi2xmEQVjxTzlQhvhnokX2aT6IRokCS2e0ooHZwtF3bK37OKKrseCxIxYDihJa/Sqh5CkPQAd3J2HnVbGMH/WAaD5zivh213WjhQcZbk6F6UGwAsNO4VP06E1zSzS8E2+hWnKTWbgDHKuFAZvck1P08hQD97gBxA0H2M8PtbSMDuKABemletyvYt/V1yHU1fDSXGQl1gn43OpQPnV4Hc/Potsqc8GKYmqtVJZkmcmjgYHlFzRAhwwS7kjGsWFp8b6ValufvJqmSMVko2aXFiJXw6g+SKqIFUTw5LN8m3vdsofyMLCzXf7HX/sFJbm/IKXS7Kwqc7rPZUN6VR8=
9 changes: 6 additions & 3 deletions assets/js/app/Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -789,9 +789,11 @@ LocusZoom.Dashboard.Component.Button = function(parent) {
LocusZoom.Dashboard.Components.add('title', function(layout) {
LocusZoom.Dashboard.Component.apply(this, arguments);
this.show = function() {
this.div_selector = this.parent.selector.append('div')
.attr('class', 'lz-dashboard-title lz-dashboard-' + this.layout.position);
this.title_selector = this.div_selector.append('h3');
if (!this.div_selector) {
this.div_selector = this.parent.selector.append('div')
.attr('class', 'lz-dashboard-title lz-dashboard-' + this.layout.position);
this.title_selector = this.div_selector.append('h3');
}
return this.update();
};
this.update = function() {
Expand Down Expand Up @@ -847,6 +849,7 @@ LocusZoom.Dashboard.Components.add('region_scale', function(layout) {
* @augments LocusZoom.Dashboard.Component
* @param {string} [layout.button_html="Download Image"]
* @param {string} [layout.button_title="Download image of the current plot as locuszoom.svg"]
* @param {string} [layout.filename="locuszoom.svg"] The default filename to use when saving the image
*/
LocusZoom.Dashboard.Components.add('download', function(layout) {
LocusZoom.Dashboard.Component.apply(this, arguments);
Expand Down
74 changes: 40 additions & 34 deletions assets/js/app/Data.js
Original file line number Diff line number Diff line change
Expand Up @@ -1035,17 +1035,16 @@ LocusZoom.Data.GeneSource.prototype.normalizeResponse = function (data) { return
LocusZoom.Data.GeneSource.prototype.extractFields = function (data, fields, outnames, trans) { return data; };

/**
* Data Source for Gene Constraint Data, as fetched from the ExAC server (or compatible)
* Data Source for Gene Constraint Data, as fetched from the gnomAD server (or compatible)
*
* FIXME: The ExAc server has been decommissioned. This source is kept here to avoid breaking existing layouts; we may
* be able to restore this feature in the future once the gnomAD API is further developed
* In the past, this source used ExAC, which has been completely decommissioned. Since the old source referenced a
* server that no longer exists, this was redefined in 0.11.0 in a backwards-incompatible manner.
*
* @public
* @class
* @augments LocusZoom.Data.Source
*/
LocusZoom.Data.GeneConstraintSource = LocusZoom.Data.Source.extend(function(init) {
console.warn('The gene constraint source depends on a server (ExAC) that is no longer active. This information may not be displayed.');
this.parseInit(init);
}, 'GeneConstraintLZ');

Expand All @@ -1060,46 +1059,53 @@ LocusZoom.Data.GeneConstraintSource.prototype.getCacheKey = function(state, chai
};

LocusZoom.Data.GeneConstraintSource.prototype.fetchRequest = function(state, chain, fields) {
var geneids = [];
chain.body.forEach(function(gene) {
var gene_id = gene.gene_id;
if (gene_id.indexOf('.')) {
gene_id = gene_id.substr(0, gene_id.indexOf('.'));
}
geneids.push(gene_id);
var build = state.genome_build || this.params.build;
if (!build) {
throw new Error(['Data source', this.constructor.SOURCE_NAME, 'requires that you specify a genome_build'].join(' '));
}

var query = chain.body.map(function (gene) {
var gene_name = gene.gene_name;
// GraphQL alias names must match a specific set of allowed characters: https://stackoverflow.com/a/45757065/1422268
var alias = gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_');
// Each gene is a separate graphQL query, grouped into one request using aliases
return alias + ': gene(gene_symbol: "' + gene_name + '", reference_genome: ' + build + ') { gnomad_constraint { exp_syn obs_syn syn_z oe_syn oe_syn_lower oe_syn_upper exp_mis obs_mis mis_z oe_mis oe_mis_lower oe_mis_upper exp_lof obs_lof pLI oe_lof oe_lof_lower oe_lof_upper } } ';
});

if (!query.length) {
// If there are no genes, skip the network request
return Promise.resolve({ data: null });
}

query = '{' + query.join(' ') + ' }'; // GraphQL isn't quite JSON; items are separated by spaces but not commas
var url = this.getURL(state, chain, fields);
var body = 'geneids=' + encodeURIComponent(JSON.stringify(geneids));
var headers = {
'Content-Type': 'application/x-www-form-urlencoded'
};
// See: https://graphql.org/learn/serving-over-http/
var body = JSON.stringify({ query: query });
var headers = { 'Content-Type': 'application/json' };
return LocusZoom.createCORSPromise('POST', url, body, headers);
};

LocusZoom.Data.GeneConstraintSource.prototype.combineChainBody = function (data, chain, fields, outnames, trans) {
if (!data) {
return chain;
}
var constraint_fields = ['bp', 'exp_lof', 'exp_mis', 'exp_syn', 'lof_z', 'mis_z', 'mu_lof', 'mu_mis','mu_syn', 'n_exons', 'n_lof', 'n_mis', 'n_syn', 'pLI', 'syn_z'];
chain.body.forEach(function(gene, i) {
var gene_id = gene.gene_id;
if (gene_id.indexOf('.')) {
gene_id = gene_id.substr(0, gene_id.indexOf('.'));
}
constraint_fields.forEach(function(field) {
// Do not overwrite any fields defined in the original gene source
if (typeof chain.body[i][field] != 'undefined') { return; }
if (data[gene_id]) {
var val = data[gene_id][field];
if (typeof val == 'number' && val.toString().indexOf('.') !== -1) {
val = parseFloat(val.toFixed(2));

chain.body.forEach(function(gene) {
// Find payload keys that match gene names in this response
var alias = gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_'); // aliases are modified gene names
var constraint = data[alias] && data[alias]['gnomad_constraint']; // gnomad API has two ways of specifying missing data for a requested gene
if (constraint) {
// Add all fields from constraint data- do not override fields present in the gene source
Object.keys(constraint).forEach(function (key) {
var val = constraint[key];
if (typeof gene[key] === 'undefined') {
if (typeof val == 'number' && val.toString().indexOf('.') !== -1) {
val = parseFloat(val.toFixed(2));
}
gene[key] = val; // These two sources are both designed to bypass namespacing
}
chain.body[i][field] = val;
} else {
// If the gene did not come back in the response then set the same field with a null values
chain.body[i][field] = null;
}
});
});
}
});
return chain.body;
};
Expand Down
10 changes: 8 additions & 2 deletions assets/js/app/Layouts.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ LocusZoom.Layouts.add('tooltip', 'standard_genes', {
html: '<h4><strong><i>{{gene_name|htmlescape}}</i></strong></h4>'
+ 'Gene ID: <a href="https://useast.ensembl.org/homo_sapiens/Gene/Summary?g={{gene_id|htmlescape}}&db=core" target="_blank" rel="noopener">{{gene_id|htmlescape}}</a><br>'
+ 'Transcript ID: <strong>{{transcript_id|htmlescape}}</strong><br>'
+ '{{#if pLI}}<table>'
+ '<tr><th>Constraint</th><th>Expected variants</th><th>Observed variants</th><th>Const. Metric</th></tr>'
+ '<tr><td>Synonymous</td><td>{{exp_syn}}</td><td>{{obs_syn}}</td><td>z = {{syn_z}}<br>o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})</td></tr>'
+ '<tr><td>Missense</td><td>{{exp_mis}}</td><td>{{obs_mis}}</td><td>z = {{mis_z}}<br>o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})</td></tr>'
+ '<tr><td>pLoF</td><td>{{exp_lof}}</td><td>{{obs_lof}}</td><td>pLI = {{pLI}}<br>o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})</td></tr>'
+ '</table><br>{{/if}}'
+ '<a href="https://gnomad.broadinstitute.org/gene/{{gene_id|htmlescape}}" target="_blank" rel="noopener">More data on gnomAD</a>'
});

Expand Down Expand Up @@ -463,10 +469,10 @@ LocusZoom.Layouts.add('data_layer', 'phewas_pvalues', {
});

LocusZoom.Layouts.add('data_layer', 'genes', {
namespace: { 'gene': 'gene' },
namespace: { 'gene': 'gene', 'constraint': 'constraint' },
id: 'genes',
type: 'genes',
fields: ['{{namespace[gene]}}all'],
fields: ['{{namespace[gene]}}all', '{{namespace[constraint]}}all'],
id_field: 'gene_id',
behaviors: {
onmouseover: [
Expand Down
2 changes: 1 addition & 1 deletion assets/js/app/LocusZoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @namespace
*/
var LocusZoom = {
version: '0.10.0'
version: '0.10.1'
};

/**
Expand Down
114 changes: 58 additions & 56 deletions dist/locuszoom.app.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
} // ESTemplate: module content goes here
// ESTemplate: module content goes here
;
var LocusZoom = { version: '0.10.0' };
var LocusZoom = { version: '0.10.1' };
/**
* Populate a single element with a LocusZoom plot.
* selector can be a string for a DOM Query or a d3 selector.
Expand Down Expand Up @@ -1024,7 +1024,7 @@
'unselected'
]
},
html: '<h4><strong><i>{{gene_name|htmlescape}}</i></strong></h4>' + 'Gene ID: <a href="https://useast.ensembl.org/homo_sapiens/Gene/Summary?g={{gene_id|htmlescape}}&db=core" target="_blank" rel="noopener">{{gene_id|htmlescape}}</a><br>' + 'Transcript ID: <strong>{{transcript_id|htmlescape}}</strong><br>' + '<a href="https://gnomad.broadinstitute.org/gene/{{gene_id|htmlescape}}" target="_blank" rel="noopener">More data on gnomAD</a>'
html: '<h4><strong><i>{{gene_name|htmlescape}}</i></strong></h4>' + 'Gene ID: <a href="https://useast.ensembl.org/homo_sapiens/Gene/Summary?g={{gene_id|htmlescape}}&db=core" target="_blank" rel="noopener">{{gene_id|htmlescape}}</a><br>' + 'Transcript ID: <strong>{{transcript_id|htmlescape}}</strong><br>' + '{{#if pLI}}<table>' + '<tr><th>Constraint</th><th>Expected variants</th><th>Observed variants</th><th>Const. Metric</th></tr>' + '<tr><td>Synonymous</td><td>{{exp_syn}}</td><td>{{obs_syn}}</td><td>z = {{syn_z}}<br>o/e = {{oe_syn}} ({{oe_syn_lower}} - {{oe_syn_upper}})</td></tr>' + '<tr><td>Missense</td><td>{{exp_mis}}</td><td>{{obs_mis}}</td><td>z = {{mis_z}}<br>o/e = {{oe_mis}} ({{oe_mis_lower}} - {{oe_mis_upper}})</td></tr>' + '<tr><td>pLoF</td><td>{{exp_lof}}</td><td>{{obs_lof}}</td><td>pLI = {{pLI}}<br>o/e = {{oe_lof}} ({{oe_lof_lower}} - {{oe_lof_upper}})</td></tr>' + '</table><br>{{/if}}' + '<a href="https://gnomad.broadinstitute.org/gene/{{gene_id|htmlescape}}" target="_blank" rel="noopener">More data on gnomAD</a>'
});
LocusZoom.Layouts.add('tooltip', 'standard_intervals', {
namespace: { 'intervals': 'intervals' },
Expand Down Expand Up @@ -1357,10 +1357,16 @@
}
});
LocusZoom.Layouts.add('data_layer', 'genes', {
namespace: { 'gene': 'gene' },
namespace: {
'gene': 'gene',
'constraint': 'constraint'
},
id: 'genes',
type: 'genes',
fields: ['{{namespace[gene]}}all'],
fields: [
'{{namespace[gene]}}all',
'{{namespace[constraint]}}all'
],
id_field: 'gene_id',
behaviors: {
onmouseover: [{
Expand Down Expand Up @@ -7348,8 +7354,10 @@
LocusZoom.Dashboard.Components.add('title', function (layout) {
LocusZoom.Dashboard.Component.apply(this, arguments);
this.show = function () {
this.div_selector = this.parent.selector.append('div').attr('class', 'lz-dashboard-title lz-dashboard-' + this.layout.position);
this.title_selector = this.div_selector.append('h3');
if (!this.div_selector) {
this.div_selector = this.parent.selector.append('div').attr('class', 'lz-dashboard-title lz-dashboard-' + this.layout.position);
this.title_selector = this.div_selector.append('h3');
}
return this.update();
};
this.update = function () {
Expand Down Expand Up @@ -7411,6 +7419,7 @@
* @augments LocusZoom.Dashboard.Component
* @param {string} [layout.button_html="Download Image"]
* @param {string} [layout.button_title="Download image of the current plot as locuszoom.svg"]
* @param {string} [layout.filename="locuszoom.svg"] The default filename to use when saving the image
*/
LocusZoom.Dashboard.Components.add('download', function (layout) {
LocusZoom.Dashboard.Component.apply(this, arguments);
Expand Down Expand Up @@ -9337,17 +9346,16 @@
return data;
};
/**
* Data Source for Gene Constraint Data, as fetched from the ExAC server (or compatible)
* Data Source for Gene Constraint Data, as fetched from the gnomAD server (or compatible)
*
* FIXME: The ExAc server has been decommissioned. This source is kept here to avoid breaking existing layouts; we may
* be able to restore this feature in the future once the gnomAD API is further developed
* In the past, this source used ExAC, which has been completely decommissioned. Since the old source referenced a
* server that no longer exists, this was redefined in 0.11.0 in a backwards-incompatible manner.
*
* @public
* @class
* @augments LocusZoom.Data.Source
*/
LocusZoom.Data.GeneConstraintSource = LocusZoom.Data.Source.extend(function (init) {
console.warn('The gene constraint source depends on a server (ExAC) that is no longer active. This information may not be displayed.');
this.parseInit(init);
}, 'GeneConstraintLZ');
LocusZoom.Data.GeneConstraintSource.prototype.getURL = function () {
Expand All @@ -9360,61 +9368,55 @@
return this.url + JSON.stringify(state);
};
LocusZoom.Data.GeneConstraintSource.prototype.fetchRequest = function (state, chain, fields) {
var geneids = [];
chain.body.forEach(function (gene) {
var gene_id = gene.gene_id;
if (gene_id.indexOf('.')) {
gene_id = gene_id.substr(0, gene_id.indexOf('.'));
}
geneids.push(gene_id);
var build = state.genome_build || this.params.build;
if (!build) {
throw new Error([
'Data source',
this.constructor.SOURCE_NAME,
'requires that you specify a genome_build'
].join(' '));
}
var query = chain.body.map(function (gene) {
var gene_name = gene.gene_name;
// GraphQL alias names must match a specific set of allowed characters: https://stackoverflow.com/a/45757065/1422268
var alias = gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_');
// Each gene is a separate graphQL query, grouped into one request using aliases
return alias + ': gene(gene_symbol: "' + gene_name + '", reference_genome: ' + build + ') { gnomad_constraint { exp_syn obs_syn syn_z oe_syn oe_syn_lower oe_syn_upper exp_mis obs_mis mis_z oe_mis oe_mis_lower oe_mis_upper exp_lof obs_lof pLI oe_lof oe_lof_lower oe_lof_upper } } ';
});
if (!query.length) {
// If there are no genes, skip the network request
return Promise.resolve({ data: null });
}
query = '{' + query.join(' ') + ' }';
// GraphQL isn't quite JSON; items are separated by spaces but not commas
var url = this.getURL(state, chain, fields);
var body = 'geneids=' + encodeURIComponent(JSON.stringify(geneids));
var headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
// See: https://graphql.org/learn/serving-over-http/
var body = JSON.stringify({ query: query });
var headers = { 'Content-Type': 'application/json' };
return LocusZoom.createCORSPromise('POST', url, body, headers);
};
LocusZoom.Data.GeneConstraintSource.prototype.combineChainBody = function (data, chain, fields, outnames, trans) {
if (!data) {
return chain;
}
var constraint_fields = [
'bp',
'exp_lof',
'exp_mis',
'exp_syn',
'lof_z',
'mis_z',
'mu_lof',
'mu_mis',
'mu_syn',
'n_exons',
'n_lof',
'n_mis',
'n_syn',
'pLI',
'syn_z'
];
chain.body.forEach(function (gene, i) {
var gene_id = gene.gene_id;
if (gene_id.indexOf('.')) {
gene_id = gene_id.substr(0, gene_id.indexOf('.'));
}
constraint_fields.forEach(function (field) {
// Do not overwrite any fields defined in the original gene source
if (typeof chain.body[i][field] != 'undefined') {
return;
}
if (data[gene_id]) {
var val = data[gene_id][field];
if (typeof val == 'number' && val.toString().indexOf('.') !== -1) {
val = parseFloat(val.toFixed(2));
chain.body.forEach(function (gene) {
// Find payload keys that match gene names in this response
var alias = gene.gene_name.replace(/[^A-Za-z0-9_]/g, '_');
// aliases are modified gene names
var constraint = data[alias] && data[alias]['gnomad_constraint'];
// gnomad API has two ways of specifying missing data for a requested gene
if (constraint) {
// Add all fields from constraint data- do not override fields present in the gene source
Object.keys(constraint).forEach(function (key) {
var val = constraint[key];
if (typeof gene[key] === 'undefined') {
if (typeof val == 'number' && val.toString().indexOf('.') !== -1) {
val = parseFloat(val.toFixed(2));
}
gene[key] = val; // These two sources are both designed to bypass namespacing
}
chain.body[i][field] = val;
} else {
// If the gene did not come back in the response then set the same field with a null values
chain.body[i][field] = null;
}
});
});
}
});
return chain.body;
};
Expand Down
2 changes: 1 addition & 1 deletion dist/locuszoom.app.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/locuszoom.app.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/locuszoom.app.min.js.map

Large diffs are not rendered by default.

Loading

0 comments on commit e3c5f8b

Please sign in to comment.