From e57a0be83d3c2ac4aa6e76b29a2893eff6304155 Mon Sep 17 00:00:00 2001 From: corywang Date: Fri, 10 Apr 2015 11:01:51 -0400 Subject: [PATCH] Added checkboxes for multiple remove/export, reordering done by arrows, tooltip follows mouse --- VQI_GenomeBrowser.js | 193 ++++++++++++++++++++++++++++++------- VQI_GenomeBrowserDemo.html | 2 +- jquery.tipsy.js | 24 ++++- 3 files changed, 181 insertions(+), 38 deletions(-) diff --git a/VQI_GenomeBrowser.js b/VQI_GenomeBrowser.js index dbead89..e92f2b9 100755 --- a/VQI_GenomeBrowser.js +++ b/VQI_GenomeBrowser.js @@ -7,6 +7,10 @@ function VQI_GenomeBrowser(id) { var margin = 50; + var bufferSpace = 20; + + var trackHeight = 50; + var panExtent = [0, width]; var trackList = []; @@ -181,6 +185,20 @@ function VQI_GenomeBrowser(id) { }; this.makeFormForColocalization(); + var removeSelectedTracks = function(){ + var tracknames = [] + for (var i in trackList) { + if(graphRegion.selectAll("g").data([trackList[i]['name']], function(d){return d;}).select("#check").node().checked) + { + tracknames.push(trackList[i]['name']); + } + } + for (var i in tracknames) { + removeTrack(tracknames[i]); + } + reorderTracks(); + } + var removeTrack = function (removedTrackName) { //var removedTrackName = $("#" + divId + " #removeTrack").val(); if (removedTrackName != null) @@ -215,6 +233,19 @@ function VQI_GenomeBrowser(id) { }; this.makeFormForRemoveTrack();*/ + var exportSelectedTracks = function(){ + var tracknames = [] + for (var i in trackList) { + if(graphRegion.selectAll("g").data([trackList[i]['name']], function(d){return d;}).select("#check").node().checked) + { + tracknames.push(trackList[i]['name']); + } + } + for (var i in tracknames) { + exportTrack(tracknames[i]); + } + } + var exportTrack = function (track_name) { // alert("here"); @@ -244,6 +275,17 @@ function VQI_GenomeBrowser(id) { } this.makeFormForExportTrack();*/ + this.makeSelectionForm = function () { + var removeButton = ""; + var exportButton = ""; + var form = "
" + removeButton + exportButton + "
"; + $("#" + divId).append(form); + $("#" + divId + " #removetrackbutton").on("click", removeSelectedTracks.bind(this)); + $("#" + divId + " #exporttrackbutton").on("click", exportSelectedTracks.bind(this)); + }; + this.makeSelectionForm(); + + var self = this; this.svg = d3.select("#" + id) @@ -358,18 +400,19 @@ function VQI_GenomeBrowser(id) { var xMin = Number(min) > panExtent[0] ? Number(min) : panExtent[0]; var xMax = Number(max) < panExtent[1] ? Number(max) : panExtent[1]; - var distance = (panExtent[1] - panExtent[0])/10000 + 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] + distance]) + xScale.domain([panExtent[0], panExtent[0] + range]) .range([0,width]); - zoom.scaleExtent([1/10000, (panExtent[1] - panExtent[0]) / 50 / 10000]); + zoom.scaleExtent([1/initialZoom, (panExtent[1] - panExtent[0]) / 50 / initialZoom]); zoom.x(xScale).on("zoom", zoomed); - svg.call(zoom); + svg.call(zoom).on("dblclick.zoom", null); - var scale = (panExtent[1] - panExtent[0]) / (xMax - xMin) / 10000; + var scale = (panExtent[1] - panExtent[0]) / (xMax - xMin) / initialZoom; zoom.scale(scale); @@ -433,8 +476,6 @@ function VQI_GenomeBrowser(id) { var graph = function (chromosome, min, max) { - svg.attr("height", height + 2 * margin); - var self = this; chrom_curr = chromosome; @@ -483,12 +524,12 @@ function VQI_GenomeBrowser(id) { name = name || "track-" + (trackIndex + 1); if (type == 'cpg') { - initTrack(name, 50); + initTrack(name); addCpgTrack(thisData, name); data['type'] = 'cpg'; }else{ // if (type == 'bed') { - initTrack(name, 10); + initTrack(name); addBEDTrack(thisData, name); data['type'] = 'bed'; } @@ -499,28 +540,32 @@ function VQI_GenomeBrowser(id) { return data; } - var initTrack = function (name, fHeight){ - var bufferSpace = 20; + var reorderTracks = function (){ + svg.attr("height", height + 2 * margin + trackList.length * (trackHeight + bufferSpace)); + for(var i in trackList){ + graphRegion.selectAll("g").data([trackList[i]['name']], function(d){return d;}) + .attr("transform", "translate(" + 0 + "," + (margin + bufferSpace + i * (trackHeight + bufferSpace)) + ")"); + } + } - svg.attr("height", Number(svg.attr("height")) + fHeight + bufferSpace); - var thisY = Number(svg.attr("height")) - 2 * margin - fHeight / 2; + var initTrack = function (name){ graphRegion.selectAll("g").data([name], function(d){return d;}).enter().append("g"); var trackGroup = graphRegion.selectAll("g").data([name], function(d){return d;}) - .attr("transform", "translate(" + 0 + "," + thisY + ")"); + //.attr("transform", "translate(" + 0 + "," + thisY + ")"); trackGroup.selectAll("text").data([name], function(d){return d;}).enter().append("text") .attr("class", name) .attr("x", 0) - .attr("y", -fHeight / 2 - bufferSpace / 2) + .attr("y", -trackHeight / 2 - bufferSpace / 2) .attr("font-family", "sans-serif") .attr("font-size", "12px") .attr("fill", "red") .text(name); - if (trackGroup.select("rect.remove").empty()) + /* if (trackGroup.select("rect.remove").empty()) { trackGroup.append("rect") .attr("height", 10) @@ -540,26 +585,77 @@ function VQI_GenomeBrowser(id) { .attr("y", -5) .attr("class", "export") .on('click', function(){exportTrack(name);}); + }*/ + if (trackGroup.select(".checkbox").empty()) + { + trackGroup.append("foreignObject") + .attr("width", 50) + .attr("height", 20) + .attr("x", width) + .attr("y", -10) + .attr("class", "checkbox") + .append("xhtml:body") + .html("
") + .on("click", function(d, i){ + console.log(trackGroup.select("#check").node().checked); + xAxisSelection.call(xAxis);//I have no idea why it won't redraw the checkbox unless I call this + }); } - if (trackGroup.select("rect.drag").empty()) + if(trackGroup.select("path.up").empty()) + { + trackGroup.append("path") + .attr("transform", function(d) { return "translate(" + -20 + "," + -10 + ")" }) + .attr("d", d3.svg.symbol().type(["triangle-up"])) + .attr("class", "up") + .on('click', function(){ + var i = Number(getTrackIndexByName(name)); + if(i > 0) + { + var temp = trackList[i]; + trackList[i] = trackList[i-1]; + trackList[i-1] = temp; + } + reorderTracks(); + }); + } + + if(trackGroup.select("path.down").empty()) + { + trackGroup.append("path") + .attr("transform", function(d) { return "translate(" + -20 + "," + 10 + ")" }) + .attr("d", d3.svg.symbol().type(["triangle-down"])) + .attr("class", "down") + .on('click', function(){ + var i = Number(getTrackIndexByName(name)); + if(i < trackList.length - 1) + { + var temp = trackList[i]; + trackList[i] = trackList[i+1]; + trackList[i+1] = temp; + } + reorderTracks(); + }); + } + + /*if (trackGroup.select("rect.drag").empty()) { var drag = d3.behavior.drag() //.origin(function() { return {x: 0, y: d3.transform(trackGroup.attr("transform")).translate[1] }}) .on("drag", function(){ trackGroup = graphRegion.selectAll("g").data([name], function(d){return d;}) - .attr("transform", "translate(" + 0 + "," + Math.max(margin + fHeight/2 - bufferSpace/2, Math.min(svg.attr("height") - 2* margin, d3.transform(trackGroup.attr("transform")).translate[1] + d3.event.y)) + ")"); + .attr("transform", "translate(" + 0 + "," + Math.max(margin + trackHeight/2 - bufferSpace/2, Math.min(svg.attr("height") - 2* margin, d3.transform(trackGroup.attr("transform")).translate[1] + d3.event.y)) + ")"); //console.log(thisY + " " +d3.event.y); }); trackGroup.append("rect") - .attr("height", fHeight) + .attr("height", trackHeight) .attr("width", 10) .attr("x", -20) - .attr("y", -fHeight/2) + .attr("y", -trackHeight/2) .attr("class", "drag") .call(drag); - } + }*/ if (trackGroup.select("g.scalable").empty()) { @@ -639,6 +735,7 @@ function VQI_GenomeBrowser(id) { $(trackScalableGroup.selectAll('*')[0]).tipsy({ gravity: 'n', html: true, + follow: 'x', title: function () { var d = this.__data__; var name = (d.length > indexArray.name) ? d[indexArray.name] : ''; @@ -649,33 +746,46 @@ function VQI_GenomeBrowser(id) { var addBEDTrack = function (data, name) { - var fHeight = 10; + var trackHeight = 10; var trackScalableGroup = graphRegion.selectAll("g").data([name], function(d){return d;}).select("g.scalable"); //console.log(data[142]); - trackScalableGroup.selectAll("line") + trackScalableGroup.selectAll("rect") .data(data, function (d) { return d; }) .exit() .remove(); - trackScalableGroup.selectAll("line") + trackScalableGroup.selectAll("rect") .data(data, function (d) { return d; }) .enter() - .append("line"); + .append("rect"); // console.log(data[1]); - var tracks = trackScalableGroup.selectAll("line") + var tracks = trackScalableGroup.selectAll("rect") .data(data, function (d) { return d; }) - .attr("x1", function (d) { + .attr("x", function (d) { + return fullXScale(d[indexArray.start]); + }) + .attr("y", -1) + .attr("height", 2) + .attr("width", function (d) { + return fullXScale(d[indexArray.end]) - fullXScale(d[indexArray.start]); + }) + .style("fill-opacity", "1") + .style("stroke", "black") + .style("fill", "black") + .style("vector-effect", "non-scaling-stroke") + .attr("class", "scalable") + /*.attr("x1", function (d) { return fullXScale(d[indexArray.start]); }) .attr("y1", function (d) { @@ -689,7 +799,7 @@ function VQI_GenomeBrowser(id) { }) .style("stroke", "black") .style("stroke-width", "2px") - .attr("class", "scalable") + .attr("class", "scalable")*/ if (data[0].length >= indexArray.exonEnds) @@ -733,8 +843,8 @@ function VQI_GenomeBrowser(id) { .attr("x", function (d) { return fullXScale(d[indexArray.start]); }) - .attr("y", -fHeight / 2) - .attr("height", fHeight) + .attr("y", -trackHeight / 2) + .attr("height", trackHeight) .attr("width", function (d) { return fullXScale(d[indexArray.end]) - fullXScale(d[indexArray.start]); }) @@ -751,6 +861,7 @@ function VQI_GenomeBrowser(id) { $(trackScalableGroup.selectAll('*')[0]).tipsy({ gravity: 'n', html: true, + follow: 'x', title: function () { var d = this.__data__; var name = (d.length > indexArray.name) ? d[indexArray.name] : ''; @@ -759,11 +870,11 @@ function VQI_GenomeBrowser(id) { }); } - var addHeightTrack = function (data, name) { + /* 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 trackHeight = 100; + var thisY = Number(svg.attr("height")) - margin + trackHeight / 2 + bufferSpace; + svg.attr("height", Number(svg.attr("height")) + trackHeight + bufferSpace); var yScale = d3.scale.linear(); @@ -775,7 +886,7 @@ function VQI_GenomeBrowser(id) { }) + 1; yScale.domain([yMin, yMax]) - .range([thisY + fHeight / 2, thisY - fHeight / 2]); + .range([thisY + trackHeight / 2, thisY - trackHeight / 2]); var trackGroup = svg.select("g." + name); @@ -876,13 +987,14 @@ function VQI_GenomeBrowser(id) { .attr("y2", yScale(0)) .style("stroke", "black") .style("stroke-width", "2px") - } + }*/ this.addTrack = function (data, name, type) { // data['name'] = name; data = addOneTrack(data, name, type); trackList.push(data); + reorderTracks(); updateAllTracksSelectBoxes(); } @@ -1143,6 +1255,15 @@ function VQI_GenomeBrowser(id) { } }; + var getTrackIndexByName = function (name) { + for (var i in trackList) { + var thisTrack = trackList[i]; + if (thisTrack['name'] === name) { + return i; + } + } + }; + var updateAllTracksSelectBoxes = function () { var track1Select = $("#" + divId + " #track1"); updateSelectBoxWithTracks(track1Select.get(0)); diff --git a/VQI_GenomeBrowserDemo.html b/VQI_GenomeBrowserDemo.html index 47cb3f4..43ba4a5 100644 --- a/VQI_GenomeBrowserDemo.html +++ b/VQI_GenomeBrowserDemo.html @@ -29,7 +29,7 @@ var genomeFile = file_dir + "./hg19_refgene.txt"; obj.addTrackFile(genomeFile, "HG19", 'bed'); - var snpile = file_dir + "./Tile2contentsnpplotter.txt"; + var snpFile = file_dir + "./Tile2contentsnpplotter.txt"; obj.addTrackFile(snpFile, "Tile2 Content Snp", 'bed'); diff --git a/jquery.tipsy.js b/jquery.tipsy.js index 9fd564d..09d971f 100644 --- a/jquery.tipsy.js +++ b/jquery.tipsy.js @@ -196,6 +196,27 @@ setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn); } } + + function move(event) { + var tipsy = get(this); + tipsy.hoverState = 'in'; + if (options.follow == 'x') { + var arrow = $(tipsy.$tip).children('.tipsy-arrow'); + if (/^[^w]w$/.test(options.gravity) && arrow.position() != null) { + var x = event.pageX - ($(arrow).position().left+($(arrow).outerWidth()/2)); + } else if (/^[^e]e$/.test(options.gravity) && arrow.position() != null) { + var x = event.pageX - ($(arrow).position().left+($(arrow).outerWidth()/2)); + } else { + var x = event.pageX - ($(tipsy.$tip).outerWidth()/2); + } + $(tipsy.$tip).css('left', x); + } else if (options.follow == 'y') { + if (/^w|^e/.test(options.gravity) ) { + $(tipsy.$tip).css('top', event.pageY-($(tipsy.$tip).outerHeight()/2)); + } + } + + } function leave() { var tipsy = get(this); @@ -216,7 +237,8 @@ var binder = options.live ? 'live' : 'bind', eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus', eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur'; - this[binder](eventIn, enter)[binder](eventOut, leave); + eventMove = 'mousemove'; + this[binder](eventIn, enter)[binder](eventOut, leave)[binder](eventMove, move); } return this;