diff --git a/VQI_GenomeBrowser.js b/VQI_GenomeBrowser.js index c27de60..82f64d8 100755 --- a/VQI_GenomeBrowser.js +++ b/VQI_GenomeBrowser.js @@ -3,7 +3,7 @@ function VQI_GenomeBrowser(id) { var width = 1000; - var height = 100; + var height = 20; var margin = 50; @@ -55,14 +55,14 @@ function VQI_GenomeBrowser(id) { var chrom_end = Number(navigate[2].trim()); graph(chrom, chrom_start, chrom_end); }; - this.makeNavigationForm = function () { - var navigateBox = ""; + /*this.makeNavigationForm = function () { + var navigateBox = ""; var navigateButton = ""; var form = ""; $("#" + divId).append(form); $("#" + divId + " #navigatebutton").on("click", navigateToRegion.bind(this)); }; - this.makeNavigationForm(); + this.makeNavigationForm();*/ var browseToGene = function () { var geneName = $("#" + divId + " #geneName").val(); @@ -91,14 +91,17 @@ function VQI_GenomeBrowser(id) { }; - this.makeFormForGeneSearch = function () { + this.makeNavigationForm = function () { + var navigateBox = ""; + var navigateButton = ""; var geneText = "Gene: "; var geneSearchButton = ""; - var form = "
" + geneText + geneSearchButton + "
"; + var form = ""; $("#" + divId).append(form); $("#" + divId + " #genesearchbutton").on("click", browseToGene.bind(this)); + $("#" + divId + " #navigatebutton").on("click", navigateToRegion.bind(this)); }; - this.makeFormForGeneSearch(); + this.makeNavigationForm(); var colocalize = function () { var track1Name = $("#" + divId + " #track1").val(); @@ -204,6 +207,33 @@ function VQI_GenomeBrowser(id) { }; this.makeFormForRemoveTrack(); + var exportTrack = function(){ + + var outputFile = window.prompt("What do you want to name your output file (Note: This won't have any effect on Safari)") || 'export'; + outputFile = outputFile.replace('.csv','') + '.txt' + + var track = this.getTrackByName($("#" + divId + " #exportTrack").val()); + + exportTrackToText(track, outputFile); + + } + this.makeFormForExportTrack = function () { + var exportTrackSelect = "Export: "; + + var exportButton = "Export"; + var form = "
" + exportTrackSelect + exportButton + "
"; + $("#" + divId).append(form); + $("#" + divId + " #exporttrackbutton").on("click", exportTrack.bind(this)); + } + this.makeFormForExportTrack(); + var self = this; this.svg = d3.select("#" + id) @@ -214,6 +244,8 @@ function VQI_GenomeBrowser(id) { var svg = this.svg; + var graphRegion = svg.append("g").attr("transform", "translate(" + margin + "," + margin + ")"); + //set up scales and axis var fullXScale = d3.scale.linear(); @@ -226,29 +258,19 @@ function VQI_GenomeBrowser(id) { .ticks(10); //x-axis - var xAxisSelection = svg.append("g") + var xAxisSelection = graphRegion.append("g") .call(xAxis) .attr("class", "axis") - .attr("transform", "translate(0," + (0 + margin) + ")"); // .attr("transform", "translate(0," + (height + margin) + ")"); - //Just a line - svg.append("line") - .attr("x1", margin) - .attr("y1", height / 2 + margin + 20) - .attr("x2", width + margin) - .attr("y2", height / 2 + margin + 20) - .style("stroke", "blue"); - var zoom = d3.behavior.zoom(); - var clipPath = svg.append("clipPath") + var clipPath = graphRegion.append("clipPath") .attr("id", "clip") .append("rect") - .attr("x", margin) - .attr("y", margin) .attr("width", width) - .attr("height", height); + .attr("height", height) + .attr("y", -margin); //to make this work with all the transformations. //set up tool tips @@ -283,8 +305,13 @@ function VQI_GenomeBrowser(id) { //console.log(d3.event.scale); //svg.selectAll("g.scalable").attr("transform", "translate(" + d3.event.translate[0] + ",0)scale(" + d3.event.scale + ",1)"); + //fast, scales as a group, but doesn't draw at large zooms svg.selectAll("g.scalable").attr("transform", "translate(" + zoom.translate()[0]+",0)scale(" + zoom.scale() + ",1)"); + //slow, scales elements individually, draws at large zooms + //svg.selectAll("g.scalable").selectAll("*").attr("transform", "translate(" + zoom.translate()[0]+",0)scale(" + zoom.scale() + ",1)"); + + xAxisSelection.call(xAxis); d3.selectAll(".axis path, .axis line").style({ @@ -295,14 +322,15 @@ function VQI_GenomeBrowser(id) { d3.selectAll(".axis text").style({ "font-family": "sans-serif", "font-size": "11px"}); + + $("#" + divId + " #navigate").val(chrom_curr + ":" + Math.round(xScale.domain()[0]) + "-" + Math.round(xScale.domain()[1])); }; var panLimit = function() { var divisor = (width) / ((xScale.domain()[1]-xScale.domain()[0])*zoom.scale()); - var diff = margin - zoom.scale()* margin; - minX = -(((xScale.domain()[0]-xScale.domain()[1])*zoom.scale())+(panExtent[1]-(panExtent[1]-(width/divisor)))) + diff, - maxX = -(((xScale.domain()[0]-xScale.domain()[1]))+(panExtent[1]-panExtent[0]))*divisor*zoom.scale() + diff; + minX = -(((xScale.domain()[0]-xScale.domain()[1])*zoom.scale())+(panExtent[1]-(panExtent[1]-(width/divisor)))), + maxX = -(((xScale.domain()[0]-xScale.domain()[1]))+(panExtent[1]-panExtent[0]))*divisor*zoom.scale(); tx = xScale.domain()[0] < panExtent[0] ? minX : @@ -322,7 +350,7 @@ function VQI_GenomeBrowser(id) { var xMax = Number(max) < panExtent[1] ? Number(max) : panExtent[1]; xScale.domain([panExtent[0], panExtent[1]]) - .range([margin, width + margin]); + .range([0, width]); zoom.scaleExtent([1,(panExtent[1]-panExtent[0])/50]); @@ -337,9 +365,8 @@ function VQI_GenomeBrowser(id) { //translates scale with regards to the middle of xMin and xMax var divisor = (width) / ((xScale.domain()[1]-xScale.domain()[0])*scale); - var diff = margin - scale* margin; - var minX = -(((xScale.domain()[0]-xScale.domain()[1])*scale)+(panExtent[1]-(panExtent[1]-(width/divisor)))) + diff; - var maxX = -(((xScale.domain()[0]-xScale.domain()[1]))+(panExtent[1]-panExtent[0]))*divisor*scale + diff; + var minX = -(((xScale.domain()[0]-xScale.domain()[1])*scale)+(panExtent[1]-(panExtent[1]-(width/divisor)))); + var maxX = -(((xScale.domain()[0]-xScale.domain()[1]))+(panExtent[1]-panExtent[0]))*divisor*scale; var midScale = d3.scale.linear() .domain([panExtent[0] + (xMax - xMin) / 2, panExtent[1] - (xMax - xMin) / 2]) @@ -356,31 +383,53 @@ function VQI_GenomeBrowser(id) { // maxNumber.property("value", xMax); } + var setPanExtent = function(min, max){ + + var mins = []; + var maxs = []; + + if(min != null && max != null) + { + mins.push(Number(min)); + maxs.push(Number(max)); + } + + for (var i in trackList) { + mins.push(d3.min(trackList[i], function (d) { + return Number(d[indexArray.start]) + })); + maxs.push(d3.max(trackList[i], function (d) { + return Number(d[indexArray.end]) + })); + } + + min = d3.min(mins); + max = d3.max(maxs); + + + panExtent[0] = isNaN(min) ? 0 : min; + panExtent[1] = isNaN(max) ? width : max; + + fullXScale = d3.scale.linear() + .domain(panExtent) + .range([0, width]); + } + var graph = function (chromosome, min, max) { + //Only need to set bounds if current chromosome is already graphed + if(chromosome == chrom_curr) + { + setBounds(min, max); + return; + } + svg.attr("height", height + 2 * margin); - var indexList = indexArray; - //Graphs the current chromosome. If min and max are specified, graph only in that range. - //Otherwise, graphs the whole chromosome var self = this; - var currentData; - chrom_curr = chromosome; - currentData = genomeData.filter(function (d) { - return d[indexList.chr] == chromosome - }); - - panExtent[0] = d3.min(currentData, function (d) { - return d[indexList.start] - }); - panExtent[1] = d3.max(currentData, function (d) { - return d[indexList.end] - }); - - //Gets relevant data and calculates min/max if needed if (min == null || max == null) { setBounds(panExtent[0],panExtent[1]); } @@ -388,80 +437,140 @@ function VQI_GenomeBrowser(id) { setBounds(Number(min),Number(max)); } - fullXScale = d3.scale.linear() - .domain(panExtent) - .range([margin, width + margin]); + for (var i in trackList) { + addOneTrack(trackList[i], trackList[i]['name'], trackList[i]['type'], i); + } + } + + var addOneTrack = function (data, name, type, i) { + + i = Number(i); + var trackIndex; + if (!isNaN(i)) { + trackIndex = i; + } else { + trackIndex = trackList.length; + } + + //check if min and max of whole graph needs to be readjusted + var min = d3.min(data, function (d) { + return Number(d[indexArray.start]) + }); + var max = d3.max(data, function (d) { + return Number(d[indexArray.start]) + }); + if(min < panExtent[0] || max > panExtent[1]) + { + setPanExtent(min, max); + graph(chrom_curr); + } + + var thisData = data.filter(function (d) { + return (d[indexArray.chr] === chrom_curr); + }); + + var trackCount = trackIndex + 1; + //name of track + name = name || "track-" + (trackIndex + 1); + + if(type == 'cpg'){ + addCpgTrack(thisData, name); + data['type'] = 'cpg'; + } + if(type == 'bed'){ + addBEDTrack(thisData, name); + data['type'] = 'bed'; + } + + data['name'] = name; + clipPath.attr("height", Number(svg.attr("height"))); + return data; + } + + var addCpgTrack = function(data, name){ - if(svg.select("g.genes").empty()) + var bufferSpace = 20; + var fHeight = 50; + svg.attr("height", Number(svg.attr("height")) + fHeight + bufferSpace); + var thisY = Number(svg.attr("height")) - 2 * margin - fHeight / 2; + + if(graphRegion.select("g." + name).empty()) { - svg.append("g") - .attr("class", "genes") + graphRegion.append("g") + .attr("class", name) } - var geneGroup = svg.select("g.genes"); + var trackGroup = graphRegion.select("g." + name) + .attr("transform", "translate(" + 0 + "," + thisY + ")"); - if(geneGroup.select("text.genes").empty()) + if(trackGroup.select("text." + name).empty()) { - geneGroup.append("text") - .attr("class", "genes") + trackGroup.append("text") + .attr("class", name) .attr("x", 0) - .attr("y", height / 2 + margin - 5) + .attr("y", -fHeight / 2 - bufferSpace/2) .attr("font-family", "sans-serif") .attr("font-size", "12px") .attr("fill", "red") - .text(chromosome); + .text(name); } - geneGroup.select("text.genes") - .text(chromosome); - if(geneGroup.select("g.scalable").empty()) + //Just a line + trackGroup.append("line") + .attr("x1", 0) + .attr("y1", 0) + .attr("x2", width) + .attr("y2", 0) + .style("stroke", "blue"); + + if(trackGroup.select("g.scalable").empty()) { - geneGroup.append("g") + trackGroup.append("g") .attr("clip-path", "url(#clip)") .append("g") .attr("class", "scalable"); } - var geneScalableGroup = geneGroup.select("g.scalable"); + var trackScalableGroup = trackGroup.select("g.scalable"); - geneScalableGroup.selectAll("rect") - .data(currentData, function (d) {return d;}) + trackScalableGroup.selectAll("rect") + .data(data, function (d) {return d;}) .exit() .remove(); - geneScalableGroup.selectAll("rect") - .data(currentData, function (d) {return d;}) + trackScalableGroup.selectAll("rect") + .data(data, function (d) {return d;}) .enter() .append("rect"); - geneScalableGroup.selectAll("rect") + trackScalableGroup.selectAll("rect") .style("fill-opacity", ".4") .style("stroke", function (d) { - var type = d[indexList.type]; + var type = d[indexArray.type]; return type == "cpg" ? "red" : type == "shore" ? "green" : "yellow" }) .style("fill", function (d) { - var type = d[indexList.type]; + var type = d[indexArray.type]; return type == "cpg" ? "red" : type == "shore" ? "green" : "yellow" }) .style("vector-effect", "non-scaling-stroke") //.attr("class", "scalable") .attr("height", function (d) { - var type = d[indexList.type]; + var type = d[indexArray.type]; return type == "cpg" ? 40 : type == "shore" ? 20 : 15 }) .attr("width", function (d) { - return fullXScale(d[indexList.end]) - fullXScale(d[indexList.start]) + return fullXScale(d[indexArray.end]) - fullXScale(d[indexArray.start]) }) .attr("x", function (d) { - return fullXScale(d[indexList.start]) + return fullXScale(d[indexArray.start]) }) .attr("y", function (d) { - var type = d[indexList.type]; - var offset = type == "cpg" ? 0 : type == "shore" ? 10 : 12.5; - return height / 2 + margin + offset + var type = d[indexArray.type]; + var offset = type == "cpg" ? -20 : type == "shore" ? -10 : -7.5; + return offset;//height / 2 + margin + offset }); - /*if(geneScalableGroup.select("rect").empty()) + /*if(trackScalableGroup.select("rect").empty()) { - geneScalableGroup.append("rect") + trackScalableGroup.append("rect") .style("fill-opacity", ".4") .style("stroke", "red") .style("fill", "red") @@ -471,14 +580,7 @@ function VQI_GenomeBrowser(id) { .attr("x", fullXScale(1000000)) .attr("y", height / 2 + margin); }*/ - - for (var i in trackList) { - addOneTrack(trackList[i], trackList[i]['name'], i); - } - - clipPath.attr("height", Number(svg.attr("height"))); - - $(geneScalableGroup.selectAll('*')[0]).tipsy({ + $(trackScalableGroup.selectAll('*')[0]).tipsy({ gravity: 'n', html: true, title: function () { @@ -489,54 +591,31 @@ function VQI_GenomeBrowser(id) { }); } - var addOneTrack = function (data, name, i) { - - i = Number(i); - var trackIndex; - if (!isNaN(i)) { - trackIndex = i; - } else { - trackIndex = trackList.length; - } - - - var index = indexArray; - var thisData = data.filter(function (d) { - return (d[index.chr] === chrom_curr); - }); - - var trackCount = trackIndex + 1; - //name of track - name = name || "track-" + (trackIndex + 1); - - var bufferSpace = 10; + var addBEDTrack = function(data, name){ + var bufferSpace = 20; var fHeight = 10; - var thisY = Number(svg.attr("height")) - margin + fHeight / 2 + bufferSpace; svg.attr("height", Number(svg.attr("height")) + fHeight + bufferSpace); + var thisY = Number(svg.attr("height")) - 2 * margin - fHeight / 2; - if(svg.select("g." + name).empty()) + if(graphRegion.select("g." + name).empty()) { - svg.append("g") + graphRegion.append("g") .attr("class", name) - //.attr("clip-path", "url(#clip)"); } - var trackGroup = svg.select("g." + name); + var trackGroup = graphRegion.select("g." + name) + .attr("transform", "translate(" + 0 + "," + thisY + ")"); - if (!i) { - - if(trackGroup.select("text." + name).empty()) - { - trackGroup.append("text") - .attr("class", name) - .attr("x", 0) - .attr("y", thisY - fHeight / 2 - bufferSpace / 2) - .attr("font-family", "sans-serif") - .attr("font-size", "12px") - .attr("fill", "red") - } - trackGroup.select("text." + name) + if(trackGroup.select("text." + name).empty()) + { + trackGroup.append("text") + .attr("class", name) + .attr("x", 0) + .attr("y", -fHeight / 2 - bufferSpace / 2) + .attr("font-family", "sans-serif") + .attr("font-size", "12px") + .attr("fill", "red") .text(name); - } + } if(trackGroup.select("g.scalable").empty()) { @@ -548,41 +627,47 @@ function VQI_GenomeBrowser(id) { var trackScalableGroup = trackGroup.select("g.scalable"); trackScalableGroup.selectAll("line") - .data(thisData, function (d){return d;}) + .data(data, function (d) {return d;}) + .exit() + .remove(); + + trackScalableGroup.selectAll("line") + .data(data, function (d){return d;}) .enter() .append("line"); var tracks = trackScalableGroup.selectAll("line") - .data(thisData, function (d){return d;}) + .data(data, function (d){return d;}) .attr("x1", function (d) { - return fullXScale(d[index.start]); + return fullXScale(d[indexArray.start]); }) .attr("y1", function (d) { - return thisY; + return 0; }) .attr("x2", function (d) { - return fullXScale(d[index.end]); + return fullXScale(d[indexArray.end]); }) .attr("y2", function (d) { - return thisY; + return 0; }) .style("stroke", "black") .style("stroke-width", "2px") .attr("class", "scalable") - if(thisData[0].length >= indexArray.exonEnds) + if(data[0].length >= indexArray.exonEnds) { var exons = []; - $.each(thisData, function (index, value) { + $.each(data, function (index, value) { var exonStarts = value[indexArray.exonStarts].slice(1,value[indexArray.exonStarts].length - 2).split(","); var exonEnds = value[indexArray.exonEnds].slice(1,value[indexArray.exonEnds].length - 2).split(","); for(var i = 0; i < exonStarts.length; i++) { - var exon = exonStarts.slice(0); + var exon = []; + exon[indexArray.chr] = value[indexArray.chr]; exon[indexArray.name] = value[indexArray.name] + " Exon " + (i+1) + "/" + exonStarts.length; exon[indexArray.start] = parseInt(exonStarts[i]); exon[indexArray.end] = parseInt(exonEnds[i]); @@ -590,6 +675,13 @@ function VQI_GenomeBrowser(id) { } }); + console.log(exons[1]); + + trackScalableGroup.selectAll("rect") + .data(data, function (d) {return d;}) + .exit() + .remove(); + trackScalableGroup.selectAll("rect") .data(exons, function (d) {return d;}) .enter() @@ -597,12 +689,12 @@ function VQI_GenomeBrowser(id) { trackScalableGroup.selectAll("rect") .data(exons, function (d) {return d;}) .attr("x", function (d) { - return fullXScale(d[index.start]); + return fullXScale(d[indexArray.start]); }) - .attr("y", thisY - fHeight / 2) + .attr("y", -fHeight / 2) .attr("height", fHeight) .attr("width", function (d) { - return fullXScale(d[index.end]) - fullXScale(d[index.start]); + return fullXScale(d[indexArray.end]) - fullXScale(d[indexArray.start]); }) .style("fill-opacity", ".8") .style("stroke", "gray") @@ -612,9 +704,7 @@ function VQI_GenomeBrowser(id) { } - data['name'] = name; - - addHeightTrack(thisData, name); + //addHeightTrack(thisData, name); $(trackScalableGroup.selectAll('*')[0]).tipsy({ gravity: 'n', @@ -625,10 +715,6 @@ function VQI_GenomeBrowser(id) { return name + " (" + d[indexArray.chr] + " : " + d[indexArray.start] + " - " + d[indexArray.end] + ")"; } }); - - return data; - - } var addHeightTrack = function(data, name){ @@ -682,6 +768,11 @@ function VQI_GenomeBrowser(id) { var lines = data.filter(function (d){return d[indexArray.score] == "";}); var rects = data.filter(function (d){return !isNaN(d[indexArray.score]) && d[indexArray.score] != "";}); + trackHeightGroup.selectAll("rect") + .data(rects, function (d) {return d;}) + .exit() + .remove(); + trackHeightGroup.selectAll("rect") .data(rects, function(d){return d;}) .enter() @@ -705,6 +796,11 @@ function VQI_GenomeBrowser(id) { .style("stroke", "gray") .style("fill", "gray") + trackHeightGroup.selectAll("line") + .data(lines, function (d) {return d;}) + .exit() + .remove(); + trackHeightGroup.selectAll("line") .data(lines, function(d){return d;}) .enter() @@ -724,18 +820,18 @@ function VQI_GenomeBrowser(id) { .style("stroke-width", "2px") } - this.addTrack = function (data, name) { + this.addTrack = function (data, name, type) { // data['name'] = name; - data = addOneTrack(data, name); + data = addOneTrack(data, name, type); trackList.push(data); updateAllTracksSelectBoxes(); } - this.addTrackFile = function (dataFile, name, header) { + this.addTrackFile = function (dataFile, name, type, header) { $.get(dataFile, function (data) { var dataRows = thisObj.parseTrackFile(data, header); - thisObj.addTrack(dataRows, name); + thisObj.addTrack(dataRows, name, type); }); } @@ -748,7 +844,7 @@ function VQI_GenomeBrowser(id) { return genomeData; } - var submitFile = function () { + /*var submitFile = function () { genomeData = []; chromosomes = []; var file_dir = "./saved-data/"; @@ -824,7 +920,7 @@ function VQI_GenomeBrowser(id) { } d3.select("#" + id + " #submit").on("click", submitFile.bind(this)); -// submitFile(); +// submitFile();*/ this.loadCPGFiles = function (cpg, shoer, shelf) { genomeData = []; @@ -890,7 +986,7 @@ function VQI_GenomeBrowser(id) { // shelveData.push({"chromosome": value[0], "start": value[3], "end": value[4], "type": "shelve"}); }); genomeData = genomeData.concat(shelveData); - graph("chr1"); + thisObj.addTrack(genomeData, "cpg", 'cpg'); }); } @@ -995,6 +1091,8 @@ function VQI_GenomeBrowser(id) { updateSelectBoxWithTracks(track2Select.get(0)); var removeTrackSelect = $("#" + divId + " #removeTrack"); updateSelectBoxWithTracks(removeTrackSelect.get(0)); + var exportTrackSelect = $("#" + divId + " #exportTrack"); + updateSelectBoxWithTracks(exportTrackSelect.get(0)); } var updateSelectBoxWithTracks = function (selectBox) { selectBox = selectBox; @@ -1008,6 +1106,33 @@ function VQI_GenomeBrowser(id) { }; + var exportTrackToText = function(data, filename) { + + temp = []; + + //console.log(data[1].join("\t")); + + for(i in data) + { + if(data[i].constructor === Array) + { + var row = data[i].join("\t"); + temp.push(row); + } + } + + var txt = temp.join("\n"); + + // Data URI + var txtData = 'data:text/csv;charset=utf-8,' + encodeURIComponent(txt); + + //var encodedUri = encodeURI(csvContent); + window.open(txtData); + + /*$("#" + divId + " #exporttrackbutton").attr("href", txtData); + $("#" + divId + " #exporttrackbutton").attr("download", filename);*/ + } + // this.makeFormForColocalization(); } diff --git a/VQI_GenomeBrowserDemo.html b/VQI_GenomeBrowserDemo.html index 786ab7f..d675621 100644 --- a/VQI_GenomeBrowserDemo.html +++ b/VQI_GenomeBrowserDemo.html @@ -26,7 +26,7 @@ obj.loadCPGFiles(cpg, shoer, shelf); var genomeFile = file_dir + "./hg19_refgene.txt"; - obj.addTrackFile(genomeFile, "HG19"); + obj.addTrackFile(genomeFile, "HG19", 'bed');