diff --git a/VQI_GenomeBrowser.js b/VQI_GenomeBrowser.js index 5008c84..bd1cfe8 100755 --- a/VQI_GenomeBrowser.js +++ b/VQI_GenomeBrowser.js @@ -13,6 +13,8 @@ function VQI_GenomeBrowser(id, serviceURL) { var panExtent = [0, width]; + var initialZoom = 1 //defines what will be considered a zoom scale of 1 + var trackList = []; var genomeData = []; @@ -24,7 +26,6 @@ function VQI_GenomeBrowser(id, serviceURL) { var testdata; var isready = false; - var zoomOnly = false; var waitTime; var loadCount = 0; @@ -390,7 +391,7 @@ function VQI_GenomeBrowser(id, serviceURL) { .append("svg") .attr("width", width + 2 * margin) .attr("height", height + 2 * margin) - .style("border", "1px solid black"); + .style("border", "1px solid black") var svg = this.svg; @@ -400,6 +401,8 @@ function VQI_GenomeBrowser(id, serviceURL) { var xScale = d3.scale.linear(); + var fullXScale = d3.scale.linear(); + var xAxis = d3.svg.axis() .scale(xScale) .orient("bottom") @@ -412,6 +415,67 @@ function VQI_GenomeBrowser(id, serviceURL) { var zoom = d3.behavior.zoom(); + + var rubberbanding = false; + var rubberbandingX; + svg.on("mousedown", function() { + svg.selectAll("rect.selection").remove(); + if(d3.event.shiftKey){ + d3.event.preventDefault(); + zoom.on("zoom",null); + svg.call(zoom); + rubberbanding = true; + var p = d3.mouse(this); + // create rubber band selector + + if(p[0] > margin && p[0] < margin + width) + { + svg.append( "rect") + .attr({ + class : "selection", + x : p[0], + y : 0, + width : 0, + height : height + 2 * margin + trackList.length * (trackHeight + bufferSpace) + }) + .style("fill-opacity", ".2") + .on("mousedown", navigateToRegion); + } + rubberbandingX = p[0]; + } + }) + .on("mousemove", function() { + // rubber band selector + var s = svg.select("rect.selection"); + + if(!s.empty() && rubberbanding) { + d3.event.preventDefault(); + // get cursor coordinate + var p = d3.mouse(this); + // get rubber band selector coordinate and size + newX = Math.min(Math.max(p[0] , margin), width + margin); + var c = { + x : Math.min(newX, rubberbandingX), + width : Math.abs(newX - rubberbandingX) + }; + + s.attr(c); + } + }) + .on("mouseup", function() { + // remove rubber band selector + zoom.on("zoom",zoomed); + svg.call(zoom).on("dblclick.zoom", null); + rubberbanding = false; + + var s = svg.select("rect.selection"); + if(!s.empty()){ + $("#" + divId + " #navigate").val(chrom_curr + ":" + + Math.round(xScale.invert(parseInt(s.attr("x"))-margin)) + "-" + + Math.round(xScale.invert(parseInt(s.attr("x")) + parseInt(s.attr("width")) - margin))); + } + }); + var clipPath = graphRegion.append("clipPath") .attr("id", "clip") .append("rect") @@ -421,6 +485,8 @@ function VQI_GenomeBrowser(id, serviceURL) { var zoomed = function () { + svg.selectAll("rect.selection").remove(); + zoom.translate([panLimit(), 0]); for(i in trackList) @@ -440,13 +506,12 @@ function VQI_GenomeBrowser(id, serviceURL) { "font-family": "sans-serif", "font-size": "11px"}); - if (! zoomOnly){ - if (waitTime != undefined) - { - clearTimeout(waitTime); - } - waitTime = setTimeout(updateTrack, 100); - } + if (waitTime != undefined) + { + clearTimeout(waitTime); + } + waitTime = setTimeout(updateTrack, 100); + $("#" + divId + " #navigate").val(chrom_curr + ":" + Math.round(xScale.domain()[0]) + "-" + Math.round(xScale.domain()[1])); }; @@ -521,25 +586,24 @@ function VQI_GenomeBrowser(id, serviceURL) { }; var setBounds = function (min, max) { - panExtent[0] = 0; var xMin = Number(min) > panExtent[0] ? Number(min) : panExtent[0]; var xMax = Number(max) < panExtent[1] ? Number(max) : panExtent[1]; - var initialZoom = 10000 //defines what will be considered a zoom scale of 1 var range = (panExtent[1] - panExtent[0]) / initialZoom - xScale.domain([panExtent[0], panExtent[0] + range]) .range([0, width]); zoom.scaleExtent([1 / initialZoom, (panExtent[1] - panExtent[0]) / 50 / initialZoom]); + var prevTranslate = zoom.translate(); + var prevScale = zoom.scale(); + zoom.x(xScale).on("zoom", zoomed); svg.call(zoom).on("dblclick.zoom", null); var scale = (panExtent[1] - panExtent[0]) / (xMax - xMin) / initialZoom; - zoom.scale(scale); - + zoom.scale(scale) //translates scale with regards to the middle of xMin and xMax var divisor = (width) / ((xScale.domain()[1] - xScale.domain()[0]) * scale); @@ -552,12 +616,18 @@ function VQI_GenomeBrowser(id, serviceURL) { var zoomWidth = midScale((xMax + xMin) / 2); - zoom.translate([zoomWidth, 0]) - .scale(scale); + //transitions start from the previous transition, so I make a transition that first zooms + //to the current view in no time, then another transition that zooms to the desired view + svg.transition() + .duration(0) + .call(zoom.translate(prevTranslate).scale(prevScale).event) + .each("end", function(){ + svg.transition() + .duration(1000) + .call(zoom.scale(scale).translate([zoomWidth, 0]).event) + }) - zoomOnly = true; zoomed(); - zoomOnly = false; } var setPanExtent = function (min, max) { @@ -586,7 +656,11 @@ function VQI_GenomeBrowser(id, serviceURL) { panExtent[0] = isNaN(min) ? 0 : min; panExtent[1] = isNaN(max) ? width : max; - var distance = (panExtent[1] - panExtent[0]) / 10000 + var distance = (panExtent[1] - panExtent[0]) / initialZoom; + + fullXScale = d3.scale.linear() + .domain([panExtent[0], panExtent[0] + distance]) + .range([0, width]); } var graph = function (chromosome, min, max) { @@ -647,7 +721,7 @@ function VQI_GenomeBrowser(id, serviceURL) { var reorderTracks = function () { svg.attr("height", height + 2 * margin + trackList.length * (trackHeight + bufferSpace)); for (var i in trackList) { - trackList[i].group.attr("transform", "translate(" + 0 + "," + (margin + bufferSpace + i * (trackHeight + bufferSpace)) + ")"); + trackList[i].group.transition().attr("transform", "translate(" + 0 + "," + (margin + bufferSpace + i * (trackHeight + bufferSpace)) + ")"); } } @@ -1031,9 +1105,19 @@ function VQI_GenomeBrowser(id, serviceURL) { var shelveFileHandle = tempHandles[1]; var shoreFileHandle = tempHandles[2]; var data; + var redrawCutoff = 1000; + + var getScale = function(){ + if(zoom.scale() < redrawCutoff) + return fullXScale; + else + return xScale; + } var drawTrack = function(data) { + var thisScale = getScale(); + trackGroup.selectAll("rect") .data(data, function (d) { return d; @@ -1065,10 +1149,10 @@ function VQI_GenomeBrowser(id, serviceURL) { return type == "cpg" ? 40 : type == "shore" ? 20 : 15 }) .attr("width", function (d) { - return xScale(d[indexArray.end]) - xScale(d[indexArray.start]) + return thisScale(d[indexArray.end]) - thisScale(d[indexArray.start]) }) .attr("x", function (d) { - return xScale(d[indexArray.start]) + return thisScale(d[indexArray.start]) }) .attr("y", function (d) { var type = d[indexArray.type]; @@ -1179,14 +1263,35 @@ function VQI_GenomeBrowser(id, serviceURL) { }); } + var previousScale = 1; this.zoomed = function() { - trackGroup.selectAll("rect") - .attr("width", function (d) { - return xScale(d[indexArray.end]) - xScale(d[indexArray.start]) - }) - .attr("x", function (d) { - return xScale(d[indexArray.start]) - }); + var thisScale = getScale(); + if(zoom.scale() < redrawCutoff) + { + if(previousScale > redrawCutoff) + { + trackGroup.selectAll("rect") + .attr("width", function (d) { + return thisScale(d[indexArray.end]) - thisScale(d[indexArray.start]) + }) + .attr("x", function (d) { + return thisScale(d[indexArray.start]) + }); + } + trackGroup.attr("transform", "translate(" + zoom.translate()[0] + ",0)scale(" + zoom.scale() + ",1)"); + } + else + { + trackGroup.attr("transform", null); + trackGroup.selectAll("rect") + .attr("width", function (d) { + return thisScale(d[indexArray.end]) - thisScale(d[indexArray.start]) + }) + .attr("x", function (d) { + return thisScale(d[indexArray.start]) + }); + } + previousScale = zoom.scale(); } this.getData = function(){ @@ -1201,6 +1306,14 @@ function VQI_GenomeBrowser(id, serviceURL) { this.fileHandle = fileHandle; var tempHandles = fileHandle.split(","); var data; + var redrawCutoff = 1000; + + var getScale = function(){ + if(zoom.scale() < redrawCutoff) + return fullXScale; + else + return xScale; + } var drawTrack = function(data, exons) { @@ -1208,6 +1321,8 @@ function VQI_GenomeBrowser(id, serviceURL) { return; } + var thisScale = getScale(); + var trackHeight = 10; trackGroup.selectAll("rect") @@ -1229,14 +1344,14 @@ function VQI_GenomeBrowser(id, serviceURL) { return d; }) .attr("x", function (d) { - return xScale(d[indexArray.start]); + return thisScale(d[indexArray.start]); }) .attr("y", function (d) { return d[indexArray.score] > 0 ? -10 : d[indexArray.score] < 0 ? 0 : -5; }) .attr("height", 10) .attr("width", function (d) { - return xScale(d[indexArray.end]) - xScale(d[indexArray.start]); + return thisScale(d[indexArray.end]) - thisScale(d[indexArray.start]); }) .style("fill-opacity", "1") .style("stroke", function (d) { @@ -1271,12 +1386,12 @@ function VQI_GenomeBrowser(id, serviceURL) { return d; }) .attr("x", function (d) { - return xScale(d[indexArray.start]); + return thisScale(d[indexArray.start]); }) .attr("y", -10) .attr("height", 20) .attr("width", function (d) { - return xScale(d[indexArray.end]) - xScale(d[indexArray.start]); + return thisScale(d[indexArray.end]) - thisScale(d[indexArray.start]); }) .style("fill-opacity", ".8") .style("stroke", "black") @@ -1383,17 +1498,39 @@ function VQI_GenomeBrowser(id, serviceURL) { } ).error(function (req, status, error) { $("body").append(status + ": " + error); - }); + }); + this.zoomed(); } + var previousScale = 1; this.zoomed = function() { - trackGroup.selectAll("rect") - .attr("width", function (d) { - return xScale(d[indexArray.end]) - xScale(d[indexArray.start]) - }) - .attr("x", function (d) { - return xScale(d[indexArray.start]) - }); + var thisScale = getScale(); + if(zoom.scale() < redrawCutoff) + { + if(previousScale > redrawCutoff) + { + trackGroup.selectAll("rect") + .attr("width", function (d) { + return thisScale(d[indexArray.end]) - thisScale(d[indexArray.start]) + }) + .attr("x", function (d) { + return thisScale(d[indexArray.start]) + }); + } + trackGroup.attr("transform", "translate(" + zoom.translate()[0] + ",0)scale(" + zoom.scale() + ",1)"); + } + else + { + trackGroup.attr("transform", null); + trackGroup.selectAll("rect") + .attr("width", function (d) { + return thisScale(d[indexArray.end]) - thisScale(d[indexArray.start]) + }) + .attr("x", function (d) { + return thisScale(d[indexArray.start]) + }); + } + previousScale = zoom.scale(); } this.getData = function(){ @@ -1480,7 +1617,7 @@ function VQI_GenomeBrowser(id, serviceURL) { this.updateTrack = function(){ if(faFile) { - if( zoom.scale() >= (panExtent[1] - panExtent[0]) / 100 / 10000) + if( zoom.scale() >= (panExtent[1] - panExtent[0]) / 100/ initialZoom) { var offset; for(var i = 0; i < faiData.length; i++)