diff --git a/VQI_GenomeBrowser.js b/VQI_GenomeBrowser.js index 9c07db7..c27de60 100755 --- a/VQI_GenomeBrowser.js +++ b/VQI_GenomeBrowser.js @@ -48,9 +48,6 @@ function VQI_GenomeBrowser(id) { // }); var navigateToRegion = function () { - /*var chrom = $("#" + divId + " #chrom").val(); - var chrom_start = $("#" + divId + " #chrom_start").val(); - var chrom_end = $("#" + divId + " #chrom_end").val();*/ var navigate = $("#" + divId + " #navigate").val(); navigate = navigate.split(/[:-]/); var chrom = navigate[0].trim(); @@ -59,9 +56,6 @@ function VQI_GenomeBrowser(id) { graph(chrom, chrom_start, chrom_end); }; this.makeNavigationForm = function () { - /*var chromSelectBox = ""; - var chromStartBox = ""; - var chromEndBox = "";*/ var navigateBox = ""; var navigateButton = ""; var form = ""; @@ -178,16 +172,17 @@ function VQI_GenomeBrowser(id) { var removeTrack = function () { var removedTrackName = $("#" + divId + " #removeTrack").val(); - - for (var i in trackList) { - var thisTrack = trackList[i]; - if (thisTrack['name'] === removedTrackName) { - trackList[i]["selections"].remove(); - trackList.splice(i,1); - break; + if(removedTrackName != null) + { + for (var i in trackList) { + var thisTrack = trackList[i]; + if (thisTrack['name'] === removedTrackName) { + svg.select("g." + trackList[i]['name']).remove(); + trackList.splice(i,1); + break; + } } } - updateAllTracksSelectBoxes(); graph(chrom_curr, xScale.domain()[0], xScale.domain()[1]); }; @@ -221,6 +216,8 @@ function VQI_GenomeBrowser(id) { //set up scales and axis + var fullXScale = d3.scale.linear(); + var xScale = d3.scale.linear(); var xAxis = d3.svg.axis() @@ -245,6 +242,14 @@ function VQI_GenomeBrowser(id) { var zoom = d3.behavior.zoom(); + var clipPath = svg.append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("x", margin) + .attr("y", margin) + .attr("width", width) + .attr("height", height); + //set up tool tips /*var tip = d3.tip() @@ -274,36 +279,12 @@ function VQI_GenomeBrowser(id) { var zoomed = function () { - - var t = zoom.translate(); - //console.log(t[0]); zoom.translate([panLimit(), 0]); + //console.log(d3.event.scale); + //svg.selectAll("g.scalable").attr("transform", "translate(" + d3.event.translate[0] + ",0)scale(" + d3.event.scale + ",1)"); - svg.selectAll("rect") - .attr("width", function (d) { - return xScale(d[indexArray.end]) - xScale(d[indexArray.start]) - }) - .attr("x", function (d) { - return xScale(d[indexArray.start]) - }) + svg.selectAll("g.scalable").attr("transform", "translate(" + zoom.translate()[0]+",0)scale(" + zoom.scale() + ",1)"); - for (var i in trackList) { - - //Tried to select only the lines in the selection - var trackLines = trackList[i]["selections"].filter(function(d){ - return d[indexArray.name].indexOf("/") == -1; - }); - - trackLines.attr("x1", function (d) { - return xScale(d[indexArray.start]); - }) - .attr("x2", function (d) { - return xScale(d[indexArray.end]); - }) - } - - // minNumber.property("value", xScale.domain()[0]); - //maxNumber.property("value", xScale.domain()[1]); xAxisSelection.call(xAxis); d3.selectAll(".axis path, .axis line").style({ @@ -386,28 +367,6 @@ function VQI_GenomeBrowser(id) { var currentData; - //Populates drop down list with different chromosomes -// select.selectAll("option") -// .data(chromosomes) -// .enter() -// .append("option") -// .attr("value", function (d) { -// return d; -// }) -// .text(function (d) { -// return d; -// }); - - /*d3.select("#chrom").selectAll("option") - .data(chromosomes) - .enter() - .append("option") - .attr("value", function (d) { - return d; - }) - .text(function (d) { - return d; - });*/ chrom_curr = chromosome; currentData = genomeData.filter(function (d) { @@ -422,44 +381,58 @@ function VQI_GenomeBrowser(id) { }); //Gets relevant data and calculates min/max if needed - if (min == null || max == null) { - - - d3.select("#chrom_start").property("value", panExtent[0]); - d3.select("#chrom_end").property("value", panExtent[1]); + if (min == null || max == null) { setBounds(panExtent[0],panExtent[1]); } else { - /* currentData = genomeData.filter(function (d) { - return d[indexList.chr] == chromosome;// && d[indexList.start] >= min && d[indexList.end] <= max - });*/ setBounds(Number(min),Number(max)); } - svg.selectAll("text") - .data(["cpg"], function (d) {return d;}) - .enter() - .append("text") - .attr("x", 0) - .attr("y", height / 2 + margin) - .attr("font-family", "sans-serif") - .attr("font-size", "12px") - .attr("fill", "red") - svg.selectAll("text") - .data(["cpg"], function (d) {return d;}) + fullXScale = d3.scale.linear() + .domain(panExtent) + .range([margin, width + margin]); + + if(svg.select("g.genes").empty()) + { + svg.append("g") + .attr("class", "genes") + } + var geneGroup = svg.select("g.genes"); + + if(geneGroup.select("text.genes").empty()) + { + geneGroup.append("text") + .attr("class", "genes") + .attr("x", 0) + .attr("y", height / 2 + margin - 5) + .attr("font-family", "sans-serif") + .attr("font-size", "12px") + .attr("fill", "red") + .text(chromosome); + } + geneGroup.select("text.genes") .text(chromosome); - svg.selectAll("rect") - .data(currentData) + if(geneGroup.select("g.scalable").empty()) + { + geneGroup.append("g") + .attr("clip-path", "url(#clip)") + .append("g") + .attr("class", "scalable"); + } + var geneScalableGroup = geneGroup.select("g.scalable"); + + geneScalableGroup.selectAll("rect") + .data(currentData, function (d) {return d;}) .exit() .remove(); - svg.selectAll("rect") - .data(currentData) + geneScalableGroup.selectAll("rect") + .data(currentData, function (d) {return d;}) .enter() .append("rect"); - svg.selectAll("rect") + geneScalableGroup.selectAll("rect") .style("fill-opacity", ".4") .style("stroke", function (d) { var type = d[indexList.type]; @@ -469,27 +442,51 @@ function VQI_GenomeBrowser(id) { var type = d[indexList.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]; return type == "cpg" ? 40 : type == "shore" ? 20 : 15 }) .attr("width", function (d) { - return xScale(d[indexList.end]) - xScale(d[indexList.start]) + return fullXScale(d[indexList.end]) - fullXScale(d[indexList.start]) }) .attr("x", function (d) { - return xScale(d[indexList.start]) + return fullXScale(d[indexList.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 - }) - //.on('mouseover', tip.show) - //.on('mouseout', tip.hide); + }); + /*if(geneScalableGroup.select("rect").empty()) + { + geneScalableGroup.append("rect") + .style("fill-opacity", ".4") + .style("stroke", "red") + .style("fill", "red") + .style("vector-effect", "non-scaling-stroke") + .attr("height", 40) + .attr("width", fullXScale(2000000) - fullXScale(1000000)) + .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({ + gravity: 'n', + html: true, + title: function () { + var d = this.__data__; + var name = (d.length > indexArray.name) ? d[indexArray.name] : ''; + return name + " (" + d[indexArray.chr] + " : " + d[indexArray.start] + " - " + d[indexArray.end] + ")"; + } + }); } var addOneTrack = function (data, name, i) { @@ -512,123 +509,114 @@ function VQI_GenomeBrowser(id) { //name of track name = name || "track-" + (trackIndex + 1); - var ySpace = 50 * trackCount; + var bufferSpace = 10; var fHeight = 10; - svg.attr("height", height + 2 * margin + ySpace); - var thisY = height / 2 + margin + 20 + ySpace; - - //empty selection - var trackSelections = d3.selectAll(".empty"); + var thisY = Number(svg.attr("height")) - margin + fHeight / 2 + bufferSpace; + svg.attr("height", Number(svg.attr("height")) + fHeight + bufferSpace); + + if(svg.select("g." + name).empty()) + { + svg.append("g") + .attr("class", name) + //.attr("clip-path", "url(#clip)"); + } + var trackGroup = svg.select("g." + name); if (!i) { - /*svg.append("line") - .attr("x1", margin) - .attr("y1", height / 2 + margin + 20 + ySpace) - .attr("x2", width + margin) - .attr("y2", height / 2 + margin + 20 + ySpace) - .style("stroke-width", 1) - .style("stroke", "black");*/ - svg.selectAll("text") - .data([name], function (d) {return d;}) - .enter() - .append("text"); - - var text = svg.selectAll("text") - .data([name], function (d) {return d;}); - trackSelections[0].push(text.node()); - - text.attr("x", 0) - .attr("y", thisY - 10) - .attr("font-family", "sans-serif") - .attr("font-size", "12px") - .attr("fill", "red") + 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) .text(name); } -// test.addTrack({start:20000000, end:20000100}) - svg.selectAll("line") + + if(trackGroup.select("g.scalable").empty()) + { + trackGroup.append("g") + .attr("clip-path", "url(#clip)") + .append("g") + .attr("class", "scalable"); + } + var trackScalableGroup = trackGroup.select("g.scalable"); + + trackScalableGroup.selectAll("line") .data(thisData, function (d){return d;}) .enter() .append("line"); - var tracks = svg.selectAll("line") + + var tracks = trackScalableGroup.selectAll("line") .data(thisData, function (d){return d;}) - var lineAttributes = tracks .attr("x1", function (d) { - return xScale(d[index.start]); + return fullXScale(d[index.start]); }) .attr("y1", function (d) { return thisY; }) .attr("x2", function (d) { - return xScale(d[index.end]); + return fullXScale(d[index.end]); }) .attr("y2", function (d) { -// return 50; return thisY; }) .style("stroke", "black") .style("stroke-width", "2px") + .attr("class", "scalable") - trackSelections[0] = trackSelections[0].concat(tracks[0]); - var exons = []; - $.each(thisData, function (index, value) { + if(thisData[0].length >= indexArray.exonEnds) + { + var exons = []; + $.each(thisData, 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(","); + 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); - exon[indexArray.name] = value[indexArray.name] + " Exon " + (i+1) + "/" + exonStarts.length; - exon[indexArray.start] = parseInt(exonStarts[i]); - exon[indexArray.end] = parseInt(exonEnds[i]); - exons.push(exon); - } - }); + for(var i = 0; i < exonStarts.length; i++) + { + var exon = exonStarts.slice(0); + exon[indexArray.name] = value[indexArray.name] + " Exon " + (i+1) + "/" + exonStarts.length; + exon[indexArray.start] = parseInt(exonStarts[i]); + exon[indexArray.end] = parseInt(exonEnds[i]); + exons.push(exon); + } + }); - svg.selectAll("rect") - .data(exons, function (d) {return d;}) - .enter() - .append("rect"); - var exonRect = svg.selectAll("rect") - .data(exons, function (d) {return d;}); - var exonRectAttributes = exonRect - .attr("x", function (d) { - return xScale(d[index.start]); - }) - .attr("y", function (d) { + trackScalableGroup.selectAll("rect") + .data(exons, function (d) {return d;}) + .enter() + .append("rect"); + trackScalableGroup.selectAll("rect") + .data(exons, function (d) {return d;}) + .attr("x", function (d) { + return fullXScale(d[index.start]); + }) + .attr("y", thisY - fHeight / 2) + .attr("height", fHeight) + .attr("width", function (d) { + return fullXScale(d[index.end]) - fullXScale(d[index.start]); + }) + .style("fill-opacity", ".8") + .style("stroke", "gray") + .style("fill", "gray") + .style("vector-effect", "non-scaling-stroke") + .attr("class", "scalable") - return thisY - fHeight / 2; - }) - .attr("height", function (d) { - return fHeight; - }) - .attr("width", function (d) { -// return 50; - return xScale(d[index.end]) - xScale(d[index.start]); - }) - .style("fill-opacity", ".8") - .style("stroke", "gray") - .style("fill", "gray"); + } - //.style("fill", "green") - //.on('mouseover', tip.show) - //.on('mouseout', tip.hide); + data['name'] = name; - trackSelections[0] = trackSelections[0].concat(exonRect[0]); + addHeightTrack(thisData, name); - $('svg rect').tipsy({ - gravity: 'n', - html: true, - title: function () { - var d = this.__data__; - var name = (d.length > indexArray.name) ? d[indexArray.name] : ''; - return name + " (" + d[indexArray.chr] + " : " + d[indexArray.start] + " - " + d[indexArray.end] + ")"; - } - }); - $(tracks[0]).tipsy({ + $(trackScalableGroup.selectAll('*')[0]).tipsy({ gravity: 'n', html: true, title: function () { @@ -638,13 +626,104 @@ function VQI_GenomeBrowser(id) { } }); - data['name'] = name; - data['selections'] = trackSelections; - return data; } + + var addHeightTrack = function(data, name){ + var bufferSpace = 10; + var fHeight = 100; + var thisY = Number(svg.attr("height")) - margin + fHeight / 2 + bufferSpace; + svg.attr("height", Number(svg.attr("height")) + fHeight + bufferSpace); + + var yScale = d3.scale.linear(); + + var yMin = d3.min(data, function (d) { + return isNaN(d[indexArray.score]) ? 0 : Number(d[indexArray.score]) + }) - 1; + var yMax = d3.max(data, function (d) { + return isNaN(d[indexArray.score]) ? 0 : Number(d[indexArray.score]) + }) + 1; + + yScale.domain([yMin,yMax]) + .range([thisY + fHeight/2, thisY - fHeight/2]); + + var trackGroup = svg.select("g." + name); + + var yAxis = d3.svg.axis() + .scale(yScale) + .orient("left") + .ticks(5); + + var yAxisSelection = trackGroup.append("g") + .call(yAxis) + .attr("class", "axis " + name) + .attr("transform", "translate(" + margin + "," + 0 + ")"); + + d3.selectAll(".axis path, .axis line").style({ + "fill": "none", + "stroke": "black", + "shape-rendering": "crispEdges"}); + + d3.selectAll(".axis text").style({ + "font-family": "sans-serif", + "font-size": "11px"}); + + var trackScalableGroup = trackGroup.select("g.scalable"); + + if(trackScalableGroup.select("g.height").empty()) + { + trackScalableGroup.append("g") + .attr("class", "height") + } + var trackHeightGroup = svg.select("g.height"); + + 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;}) + .enter() + .append("rect"); + + trackHeightGroup.selectAll("rect") + .data(rects, function(d){return d;}) + .attr("x", function (d) { + return fullXScale(Number(d[indexArray.start])); + }) + .attr("y", function (d) { + return d < 0 ? yScale(0) : yScale(d[indexArray.score]); + }) + .attr("height", function (d) { + return Math.abs(yScale(d[indexArray.score]) - yScale(0)); + }) + .attr("width", function (d) { + return fullXScale(Number(d[indexArray.end])) - fullXScale(Number(d[indexArray.start])); + }) + .style("fill-opacity", ".8") + .style("stroke", "gray") + .style("fill", "gray") + + trackHeightGroup.selectAll("line") + .data(lines, function(d){return d;}) + .enter() + .append("line"); + + trackHeightGroup.selectAll("line") + .data(lines, function(d){return d;}) + .attr("x1", function (d) { + return fullXScale(d[indexArray.start]); + }) + .attr("y1",yScale(0)) + .attr("x2", function (d) { + return fullXScale(d[indexArray.end]); + }) + .attr("y2", yScale(0)) + .style("stroke", "black") + .style("stroke-width", "2px") + } + this.addTrack = function (data, name) { // data['name'] = name; data = addOneTrack(data, name);