UNPKG

cttv.genome

Version:

Lightweight genome browser for CTTV targets based on TnT Genome

746 lines (651 loc) 25.7 kB
//var ensembl_rest_api = require("tnt.ensembl"); var nav = require("./navigation.js"); var browser_tooltips = require("./tooltips.js"); var aggregation = require("./aggregation.js"); var RSVP = require('rsvp'); var apijs = require("tnt.api"); var biotypes = require("./biotypes.js"); var cttvRestApi = require("cttv.api"); var pipelines = require("./pipelines.js"); var cttv_genome_browser = function() { "use strict"; // Options for the widget var conf = { links_prefix: "https://www.targetvalidation.org", show_links: true, show_snps: true, show_nav: true, cttvRestApi: cttvRestApi().prefix("https://www.targetvalidation.org/api/"), efo: undefined }; var tracks = {}; // Public apif var myApi; var navTheme = nav(); // var show_links = true; // var efo; var snp_new_legend; // Divs var snp_legend_div; var navDiv; var snpColors = { TargetDisease: "#FF0000", // red Target: "#3e9999", // blue Disease: "#FFD400", // orange Other: "#cccccc" // grey }; var ensemblRestApi; // div_ids to display different elements // They have to be set dynamically because the IDs contain the div_id of the main element containing the plug-in var div_id; var geneTrackHeight = 0; var gBrowser; var gBrowserTheme = function(gB, div) { // Set the different #ids for the html elements (needs to be lively because they depend on the div_id) set_div_id(div); gBrowser = gB; gB.zoom_in(150); ensemblRestApi = tnt.board.track.data.genome.ensembl; //ensemblRestApi = gB.rest(); // If the nav is shown or not navTheme .show_options(conf.show_nav); // tooltips var tooltips = browser_tooltips() .cttvRestApi (conf.cttvRestApi) .ensemblRestApi (ensemblRestApi) .prefix (conf.links_prefix) .view (gB); // Transcript data var mixedData = tnt.board.track.data.genome.gene(); var gene_updater = mixedData.retriever(); mixedData.retriever (function (loc) { return gene_updater(loc) .then (function (fullGenes) { var genes = []; for (var i=0; i<fullGenes.length; i++) { var gene = fullGenes[i]; if (gene.id !== gB.gene()) { gene.key = gene.id; gene.isGene = true; gene.exons = [{ start: gene.start, end: gene.end, coding: true, offset: 0, isGene: true }]; genes.push(gene); } } var url = ensemblRestApi.url() .endpoint("/lookup/id/:id") .parameters({ id: gB.gene(), expand: true }); // var url = ensemblRestApi.url.gene({ // id: gB.gene(), // expand: true // }); return ensemblRestApi.call(url) .then (function (resp) { var g = resp.body; var tss = tnt.board.track.data.genome.transcript().gene2Transcripts(g); for (var i=0; i<tss.length; i++) { var ts = tss[i]; if (overlaps([loc.from, loc.to], [ts.start, ts.end])) { genes.push(ts); } } // genes = genes.concat(tss); genes.map(gene_color); setupLegend(genes); return genes; }); }); }); var overlaps = function (ref, feat) { if (ref[0] < feat[0] && ref[1] > feat[1]) { // feat inside return true; } if (ref[0] > feat[0] && ref[1] < feat[1]) { // inside -- right return true; } if (ref[0] > feat[0] && ref[1] > feat[1]) { // inside -- left return true; } if (ref[1] > feat[0] && ref[1] < feat[1]) { // feat expands both sides return true; } return false; }; var getBiotypes = function (genes) { // get the biotypes: var biotypes_array = genes.map(function(e){ return biotypes.legend[e.biotype]; }); // also the ones for the transcript of the matching gene var transcript_biotypes = genes.filter (function (e2) { if (e2.gene) { return e2.gene.id === gB.gene(); } return e2.id === gB.gene(); //return e2.gene.id === gB.gene(); }).map (function (e) { return biotypes.legend[e.biotype]; //return biotypes.legend[e.transcript.biotype]; }); biotypes_array = biotypes_array.concat(transcript_biotypes); var biotypes_hash = {}; for (var i=0; i<biotypes_array.length; i++) { biotypes_hash[biotypes_array[i]] = 1; } var curr_biotypes = []; for (var p in biotypes_hash) { if (biotypes_hash.hasOwnProperty(p)) { var b = {}; b.label = p; b.color = biotypes.color[p]; curr_biotypes.push(b); } } myApi.get("biotypes", curr_biotypes); return curr_biotypes; }; var setupLegend = function (genes) { var curr_biotypes = getBiotypes(genes); var biotype_legend = gene_legend_div.selectAll(".tnt_biotype_legend") .data(curr_biotypes, function(d){ return d.label; }); var new_legend = biotype_legend .enter() .append("div") .attr("class", "tnt_biotype_legend") .style("display", "inline"); new_legend .append("div") .style("display", "inline-block") .style("margin", "0px 5px 0px 15px") .style("width", "10px") .style("height", "10px") .style("border", "1px solid #000") .style("background", function(d){ return d.color; }); new_legend .append("text") .text(function(d){return d.label;}); biotype_legend .exit() .remove(); }; // TRACKS! // ClinVar track var regionEnsemblPromise = function (loc) { var regionUrl = ensemblRestApi.url() .endpoint("overlap/region/:species/:region") .parameters({ species: loc.species, region: (loc.chr + ":" + loc.from + "-" + loc.to), feature: ["gene"] }); // var regionUrl = ensemblRestApi.url.region ({ // species: loc.species, // chr: loc.chr, // from: loc.from, // to: loc.to, // features: ["gene"] // }); return ensemblRestApi.call(regionUrl) .then (function (resp) { return resp.body; }); }; var clinvar_updater = tnt.board.track.data.async() .retriever (function (loc) { return regionEnsemblPromise(loc) .then (function (genes) { var allGenesPromises = []; var geneIds = []; for (var i=0; i<genes.length; i++) { geneIds.push(genes[i].id); } var p = pipelines() .ensemblRestApi (ensemblRestApi) .cttvRestApi (conf.cttvRestApi) .rare(geneIds, conf.efo); allGenesPromises.push(p); return RSVP.all(allGenesPromises); }) .then (function (resps) { var flattenedSNPs = []; for (var i=0; i<resps.length; i++) { var resp = resps[i]; for (var snp in resp.snps) { if (resp.snps.hasOwnProperty(snp) && resp.snps[snp].pos) { flattenedSNPs.push (resp.snps[snp]); } } } return flattenedSNPs; }); }); var foreground_color = function (d) { // highlight means same disease if (d.highlight && (gB.gene() === d.target.geneid)) { return snpColors.TargetDisease; } else if (d.highlight) { return snpColors.Disease; } else if (gB.gene() === d.target.geneid) { return snpColors.Target; } return snpColors.Other; }; var clinvar_display = tnt.board.track.feature.pin() .domain([0.3, 1.2]) .color (foreground_color) .index(function (d) { return d.name; }) .on("click", tooltips.snp) .layout(tnt.board.track.layout() .elements(function(elems) { aggregation(elems, clinvar_display.scale(), gB.gene()); }) ); var clinvar_track = tnt.board.track() .label("Mutations in rare diseases") .height(60) .color("white") .display(clinvar_display) .data (clinvar_updater); // Async Gwas updater for ALL genes var gwas_updater = tnt.board.track.data.async() .retriever (function (loc) { return regionEnsemblPromise(loc) .then (function (genes) { var allGenesPromises = []; var geneIds = []; for (var i=0; i<genes.length; i++) { geneIds.push(genes[i].id); } var p = pipelines() .ensemblRestApi (ensemblRestApi) .cttvRestApi (conf.cttvRestApi) .common(geneIds, conf.efo); allGenesPromises.push(p); return RSVP.all(allGenesPromises); }) .then (function (resps) { var flattenedSNPs = []; for (var i=0; i<resps.length; i++) { var resp = resps[i]; for (var snp in resp.snps) { if (resp.snps.hasOwnProperty(snp) && resp.snps[snp].pos) { flattenedSNPs.push(resp.snps[snp]); } } } return flattenedSNPs; }); }); // Gwas track var gwas_display = tnt.board.track.feature.pin() .domain([0.3,1.2]) .index(function (d) { return d.name; }) .color (foreground_color) .on("click", tooltips.snp) .layout(tnt.board.track.layout() .elements(function (elems) { aggregation(elems, clinvar_display.scale()); }) ); //var gwas_guider = gwas_display.guider(); // gwas_display.guider (function (width) { // var track = this; // var p0_offset = 16.11; // var p05_offset = 43.88 // // // pvalue 0 // track.g // .append("line") // .attr("x1", 0) // .attr("y1", p0_offset) // //.attr("y1", y_offset) // .attr("x2", width) // .attr("y2", p0_offset) // //.attr("y2", y_offset) // .attr("stroke", "lightgrey"); // track.g // .append("text") // .attr("x", width - 50) // .attr("y", p0_offset + 10) // .attr("font-size", 10) // .attr("fill", "lightgrey") // .text("pvalue 0"); // pvalue 0.5 // track.g // .append("line") // .attr("x1", 0) // .attr("y1", p05_offset) // .attr("x2", width) // .attr("y2", p05_offset) // .attr("stroke", "lightgrey") // track.g // .append("text") // .attr("x", width - 50) // .attr("y", p05_offset + 10) // .attr("font-size", 10) // .attr("fill", "lightgrey") // .text("pvalue 0.5"); // continue with rest of guider //gwas_guider.call(track, width); //}); var gwas_track = tnt.board.track() .label("Mutations in common diseases") .height(60) .color("white") .display(gwas_display) .data (gwas_updater); // Aux track for label var transcript_label_track = tnt.board.track() .label ("Genes / Transcripts") .height(20) .color ("white") .display(tnt.board.track.feature.block()) .data(tnt.board.track.data.sync() .retriever (function () { return []; }) ); // Transcript / Gene track var transcript_track = tnt.board.track() .height(geneTrackHeight) .color("white") .display(tnt.board.track.feature.genome.transcript() .color (function (t) { return t.featureColor; }) .on("click", tooltips.gene) ) // .data(transcript_data); .data(mixedData); // Update the track based on the number of needed slots for the genes transcript_track.display().layout() .keep_slots(false) .fixed_slot_type("expanded") .on_layout_run (function (types, current) { var needed_height = types.expanded.needed_slots * types.expanded.slot_height; if (needed_height !== geneTrackHeight) { if (needed_height < 200) { // Minimum of 200 geneTrackHeight = 200; } else { geneTrackHeight = needed_height; } geneTrackHeight = needed_height; transcript_track.height(needed_height); gB.tracks(gB.tracks()); // reorder re-computes track heights } }); // Sequence track var sequence_track = tnt.board.track() .label ("sequence") .height(30) .color("white") .display(tnt.board.track.feature.genome.sequence()) .data(tnt.board.track.data.genome.sequence() .limit(200) ); gBrowserTheme.start(); // The order of the elements are: Nav div // genome browser div // legend div // nav div navDiv = d3.select(div) .append("div") .style("width", "950px"); gBrowser(div); // The legend for the gene colors var gene_legend_div = d3.select(div) .append("div") .attr("class", "tnt_legend_div"); gene_legend_div .append("text") .attr("class", "tnt_legend_header") .text("Gene legend:"); d3.selectAll("tnt_biotype") .data(transcript_track.data().elements()); // The legen for the snps colors snp_legend_div = d3.select(div) .append("div") .attr("class", "tnt_legend_div"); snp_legend_div .append("text") .attr("class", "tnt_legend_header") .text("Variants legend:"); if (conf.show_snps) { tracks.common_snps = gwas_track; gBrowser.add_track(gwas_track); tracks.rare_snps = clinvar_track; gBrowser.add_track(clinvar_track); } tracks.gene = transcript_track; gBrowser .add_track(sequence_track) .add_track(transcript_label_track) .add_track(transcript_track); }; ///*********************//// /// RENDERING FUNCTIONS //// ///*********************//// // API // DATA var start = function () { var geneUrl = ensemblRestApi.url() .endpoint("lookup/id/:id") .parameters({ id: gBrowser.gene() }); // var geneUrl = ensemblRestApi.url.gene ({ // id: gB.gene() // }); var genePromise = ensemblRestApi.call(geneUrl) .then (function (resp) { return resp.body; }); var diseasePromise; if (conf.efo) { var efoUrl = conf.cttvRestApi.url.disease({ code: conf.efo }); diseasePromise = conf.cttvRestApi.call(efoUrl) .then (function (resp) { return resp.body; }); } // // SNPs ClinVar var snpsClinvarPromise; if (conf.show_snps) { snpsClinvarPromise = pipelines() .ensemblRestApi (ensemblRestApi) .cttvRestApi (conf.cttvRestApi) .rare (gBrowser.gene()); } else { snpsClinvarPromise = new Promise (function (res, rej) { res({}); }); } // // SNP GWASs var snpsGwasPromise; if (conf.show_snps) { snpsGwasPromise = pipelines() .ensemblRestApi (ensemblRestApi) .cttvRestApi (conf.cttvRestApi) .common (gBrowser.gene()); } else { snpsGwasPromise = new Promise (function (resolve, reject) { resolve({}); }); } RSVP.all ([genePromise, snpsGwasPromise, snpsClinvarPromise, diseasePromise]) .then (function (resps) { var disease = resps[3]; var gene = resps[0]; fillSNPLegend (gene, disease); var gene_extent = [gene.start, gene.end]; var gwas_extent = resps[1].extent; var clinvar_extent = resps[2].extent; var gwasLength = gwas_extent ? (gwas_extent[1] - gwas_extent[0]) : 0; var clinvarLength = clinvar_extent ? (clinvar_extent[1] - clinvar_extent[0]) : 0; var geneLength = gene_extent[1] - gene_extent[0]; // var gwasStart = gwas_extent ? (~~(gwas_extent[0] - (gwasLength/5))) : Infinity; var gwasEnd = gwas_extent ? (~~(gwas_extent[1] + (gwasLength/5))) : -Infinity; var clinvarStart = clinvar_extent ? (~~(clinvar_extent[0] - (clinvarLength/5))) : Infinity; var clinvarEnd = clinvar_extent ? (~~(clinvar_extent[1] + (clinvarLength/5))) : -Infinity; var geneStart = ~~(gene_extent[0] - (geneLength/5)); var geneEnd = ~~(gene_extent[1] + (geneLength/5)); // var start = d3.min([gwasStart||Infinity, geneStart, clinvarStart||Infinity]); var end = d3.max([gwasEnd||0, geneEnd, clinvarEnd||0]); // // var zoomOut = (gene.end - gene.start) + 100; // gB.zoom_out(zoomOut); // We can finally start! gBrowser.chr(gene.seq_region_name); navTheme.orig ({ from : start, to : end }); gBrowser.start({from: start, to: end}); // Navigation // Needs to be positioned here because it needs to know the chromosome navTheme (gBrowser, navDiv.node()); }); }; var fillSNPLegend = function (gene, disease) { var snp_legend_data = []; if (disease) { snp_legend_data.push({ label: "Mutation in " + gene.display_name + " associated with " + disease.label, color: snpColors.TargetDisease }); snp_legend_data.push({ label: "Mutation associated with " + disease.label + " in other genes", color: snpColors.Disease }); } snp_legend_data.push({ label: "Variant in " + gene.display_name, color: snpColors.Target }); snp_legend_data.push({ label: "Other Variant", color: snpColors.Other }); snp_new_legend = snp_legend_div.selectAll(".tnt_snp_legend") .data(snp_legend_data) .enter() .append("div") .attr("class", "tnt_snp_legend"); snp_new_legend .append("div") .attr("class", "tnt_legend_item") .style("display", "inline-block") .style("margin", "0px 5px 0px 15px") .style("width", "10px") .style("height", "10px") .style("border", "1px solid #000") .style("border-radius", "5px") .style("background", function(d){ return d.color; }); snp_new_legend .append("text") .text(function(d) { return d.label; }); // Links div var links_pane = snp_legend_div .append("div") .attr("class", "tnt_links_pane") .style("display", function() { if (conf.show_links) { return "block"; } else { return "none"; } }); // ensembl links_pane .append("span") .text("Open region in Ensembl"); var ensemblLoc = links_pane .append("i") .attr("title", "open region in ensembl") .attr("class", "cttvGenomeBrowserIcon fa fa-external-link") .on("click", function () { var link = buildEnsemblLink(); window.open(link, "_blank"); }); links_pane .append("span") .style("margin-left", "30px") .text("See " + gene.display_name + " in Ensembl"); links_pane .append("i") .attr("title", "See " + gene + " in Ensembl") .attr("class", "cttvGenomeBrowserIcon fa fa-external-link") .on("click", function () { var link = buildEnsemblGeneLink(); window.open(link, "_blank"); }) }; myApi = apijs(gBrowserTheme) .getset(conf) .method('start', start) .method ("track", function (name) { switch (name) { case "gene": return tracks.gene; case "common_snps": return tracks.common_snps; case "rare_snps": return tracks.rare_snps; } return; // No track returned }); var set_div_id = function(div) { div_id = d3.select(div).attr("id"); }; ///*********************//// /// UTILITY METHODS //// ///*********************//// // Private methods var buildEnsemblLink = function() { var url = "http://www.ensembl.org/" + gBrowser.species() + "/Location/View?r=" + gBrowser.chr() + "%3A" + gBrowser.from() + "-" + gBrowser.to(); return url; }; var buildEnsemblGeneLink = function () { var url = "http://www.ensembl.org/" + gBrowser.species() + "/Gene/Summary?db=core&g=" + gBrowser.gene(); return url; }; function gene_color (transcript) { var biotype = transcript.biotype; var color = biotypes.color[biotypes.legend[biotype]]; transcript.featureColor = color; // colors must be set in the exons too for (var i=0; i<transcript.exons.length; i++) { transcript.exons[i].featureColor = color; } } // Public methods /** <strong>buildEnsemblGeneLink</strong> returns the Ensembl url pointing to the gene summary of the given gene @param {String} gene The Ensembl gene id. Should be a valid ID of the form ENSGXXXXXXXXX" @returns {String} The Ensembl URL for the given gene */ return gBrowserTheme; }; module.exports = exports = cttv_genome_browser;