2 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/d8b3dc823978/ Changeset: d8b3dc823978 Branch: next-stable User: carlfeberhard Date: 2013-05-21 17:15:10 Summary: Fixes to phyloviz Affected #: 6 files diff -r 9bb23c38aeec1f26ec1c22fbcad8a098fd6ae015 -r d8b3dc823978835f0b2105635990f310fa619c1c lib/galaxy/visualization/data_providers/phyloviz/__init__.py --- a/lib/galaxy/visualization/data_providers/phyloviz/__init__.py +++ b/lib/galaxy/visualization/data_providers/phyloviz/__init__.py @@ -1,3 +1,4 @@ + """ Data providers code for PhyloViz """ from galaxy.visualization.data_providers.basic import BaseDataProvider @@ -40,4 +41,3 @@ rval[ "msg"] = parseMsg return rval - diff -r 9bb23c38aeec1f26ec1c22fbcad8a098fd6ae015 -r d8b3dc823978835f0b2105635990f310fa619c1c lib/galaxy/visualization/data_providers/phyloviz/newickparser.py --- a/lib/galaxy/visualization/data_providers/phyloviz/newickparser.py +++ b/lib/galaxy/visualization/data_providers/phyloviz/newickparser.py @@ -77,8 +77,6 @@ childrenNodes += [node] return childrenNodes - - def _mapName(self, newickString, nameMap): """ Necessary to replace names of terms inside nexus representation @@ -108,9 +106,9 @@ newString += newickString[start:] return newString - def parseNode(self, string, depth): - """ Recursive method for parsing newick string, works by stripping down the string into substring + """ + Recursive method for parsing newick string, works by stripping down the string into substring of newick contained with brackers, which is used to call itself. Eg ... ( A, B, (D, E)C, F, G ) ... diff -r 9bb23c38aeec1f26ec1c22fbcad8a098fd6ae015 -r d8b3dc823978835f0b2105635990f310fa619c1c lib/galaxy/visualization/data_providers/registry.py --- a/lib/galaxy/visualization/data_providers/registry.py +++ b/lib/galaxy/visualization/data_providers/registry.py @@ -53,7 +53,7 @@ elif isinstance( original_dataset.datatype, Tabular ): data_provider_class = ColumnDataProvider elif isinstance( original_dataset.datatype, ( Nexus, Newick, Phyloxml ) ): - data_provider_class = genome.PhylovizDataProvider + data_provider_class = PhylovizDataProvider data_provider = data_provider_class( original_dataset=original_dataset ) diff -r 9bb23c38aeec1f26ec1c22fbcad8a098fd6ae015 -r d8b3dc823978835f0b2105635990f310fa619c1c static/scripts/packed/viz/phyloviz.js --- a/static/scripts/packed/viz/phyloviz.js +++ b/static/scripts/packed/viz/phyloviz.js @@ -1,1 +1,1 @@ -define(["libs/d3","viz/visualization","mvc/data"],function(m,f,g){var l=Backbone.View.extend({className:"UserMenuBase",isAcceptableValue:function(r,p,n){var o=this,s=r.val(),t=r.attr("displayLabel")||r.attr("id").replace("phyloViz","");function q(u){return !isNaN(parseFloat(u))&&isFinite(u)}if(!q(s)){alert(t+" is not a number!");return false}if(s>n){alert(t+" is too large.");return false}else{if(s<p){alert(t+" is too small.");return false}}return true},hasIllegalJsonCharacters:function(n){if(n.val().search(/"|'|\\/)!==-1){alert("Named fields cannot contain these illegal characters: double quote(\"), single guote('), or back slash(\\). ");return true}return false}});function h(){var w=this,r=m.layout.hierarchy().sort(null).value(null),v=360,q="Linear",u=18,s=200,t=0,p=0.5,n=50;w.leafHeight=function(x){if(typeof x==="undefined"){return u}else{u=x;return w}};w.layoutMode=function(x){if(typeof x==="undefined"){return q}else{q=x;return w}};w.layoutAngle=function(x){if(typeof x==="undefined"){return v}if(isNaN(x)||x<0||x>360){return w}else{v=x;return w}};w.separation=function(x){if(typeof x==="undefined"){return s}else{s=x;return w}};w.links=function(x){return m.layout.tree().links(x)};w.nodes=function(A,y){var z=r.call(w,A,y),x=[],C=0,B=0;z.forEach(function(D){var E=D.data;E.depth=D.depth;C=E.depth>C?E.depth:C;x.push(E)});x.forEach(function(D){if(!D.children){B+=1;D.depth=C}});u=q==="Circular"?v/B:u;t=0;o(x[0],C,u,null);return x};function o(B,D,A,z){var y=B.children,x=0;var C=B.dist||p;C=C>1?1:C;B.dist=C;if(z!==null){B.y0=z.y0+C*s}else{B.y0=n}if(!y){B.x0=t++*A}else{y.forEach(function(E){E.parent=B;x+=o(E,D,A,B)});B.x0=x/y.length}B.x=B.x0;B.y=B.y0;return B.x0}return w}var b=f.Visualization.extend({defaults:{layout:"Linear",separation:250,leafHeight:18,type:"phyloviz",title:"Title",scaleFactor:1,translate:[0,0],fontSize:12,selectedNode:null,nodeAttrChangedTime:0},initialize:function(n){this.set("dataset",new g.Dataset({id:n.dataset_id}))},root:{},toggle:function(n){if(typeof n==="undefined"){return}if(n.children){n._children=n.children;n.children=null}else{n.children=n._children;n._children=null}},toggleAll:function(n){if(n.children&&n.children.length!==0){n.children.forEach(this.toggleAll);toggle(n)}},getData:function(){return this.root},save:function(){var n=this.root;o(n);this.set("root",n);function o(q){delete q.parent;if(q._selected){delete q._selected}if(q.children){q.children.forEach(o)}if(q._children){q._children.forEach(o)}}var p=jQuery.extend(true,{},this.attributes);p.selectedNode=null;show_message("Saving to Galaxy","progress");return $.ajax({url:this.url(),type:"POST",dataType:"json",data:{vis_json:JSON.stringify(p)},success:function(q){var r=q.url.split("id=")[1].split("&")[0],s="/visualization?id="+r;window.history.pushState({},"",s+window.location.hash);hide_modal()}})}});var d=Backbone.View.extend({defaults:{nodeRadius:4.5},stdInit:function(o){var n=this;n.model.on("change:separation change:leafHeight change:fontSize change:nodeAttrChangedTime",n.updateAndRender,n);n.vis=o.vis;n.i=0;n.maxDepth=-1;n.width=o.width;n.height=o.height},updateAndRender:function(p){var o=m.select(".vis"),n=this;p=p||n.model.root;n.renderNodes(p);n.renderLinks(p);n.addTooltips()},renderLinks:function(n){var w=this;var o=w.diagonal;var p=w.duration;var r=w.layoutMode;var t=w.vis.selectAll("g.completeLink").data(w.tree.links(w.nodes),function(x){return x.target.id});var v=function(x){x.pos0=x.source.y0+" "+x.source.x0;x.pos1=x.source.y0+" "+x.target.x0;x.pos2=x.target.y0+" "+x.target.x0};var u=t.enter().insert("svg:g","g.node").attr("class","completeLink");u.append("svg:path").attr("class","link").attr("d",function(x){v(x);return"M "+x.pos0+" L "+x.pos1});var s=t.transition().duration(500);s.select("path.link").attr("d",function(x){v(x);return"M "+x.pos0+" L "+x.pos1+" L "+x.pos2});var q=t.exit().remove()},selectNode:function(o){var n=this;m.selectAll("g.node").classed("selectedHighlight",function(p){if(o.id===p.id){if(o._selected){delete o._selected;return false}else{o._selected=true;return true}}return false});n.model.set("selectedNode",o);$("#phyloVizSelectedNodeName").val(o.name);$("#phyloVizSelectedNodeDist").val(o.dist);$("#phyloVizSelectedNodeAnnotation").val(o.annotation||"")},addTooltips:function(){$(".bs-tooltip").remove();$(".node").attr("data-original-title",function(){var o=this.__data__,n=o.annotation||"None";return o?(o.name?o.name+"<br/>":"")+"Dist: "+o.dist+" <br/>Annotation: "+n:""}).tooltip({placement:"top",trigger:"hover"})}});var a=d.extend({initialize:function(o){var n=this;n.margins=o.margins;n.layoutMode="Linear";n.stdInit(o);n.layout();n.updateAndRender(n.model.root)},layout:function(){var n=this;n.tree=new h().layoutMode("Linear");n.diagonal=m.svg.diagonal().projection(function(o){return[o.y,o.x]})},renderNodes:function(n){var u=this,v=u.model.get("fontSize")+"px";u.tree.separation(u.model.get("separation")).leafHeight(u.model.get("leafHeight"));var q=500,o=u.tree.separation(u.model.get("separation")).nodes(u.model.root);var p=u.vis.selectAll("g.node").data(o,function(w){return w.name+w.id||(w.id=++u.i)});u.nodes=o;u.duration=q;var r=p.enter().append("svg:g").attr("class","node").on("dblclick",function(){m.event.stopPropagation()}).on("click",function(w){if(m.event.altKey){u.selectNode(w)}else{if(w.children&&w.children.length===0){return}u.model.toggle(w);u.updateAndRender(w)}});r.attr("transform",function(w){return"translate("+n.y0+","+n.x0+")"});r.append("svg:circle").attr("r",0.000001).style("fill",function(w){return w._children?"lightsteelblue":"#fff"});r.append("svg:text").attr("class","nodeLabel").attr("x",function(w){return w.children||w._children?-10:10}).attr("dy",".35em").attr("text-anchor",function(w){return w.children||w._children?"end":"start"}).style("fill-opacity",0.000001);var s=p.transition().duration(q);s.attr("transform",function(w){return"translate("+w.y+","+w.x+")"});s.select("circle").attr("r",u.defaults.nodeRadius).style("fill",function(w){return w._children?"lightsteelblue":"#fff"});s.select("text").style("fill-opacity",1).style("font-size",v).text(function(w){return w.name});var t=p.exit().transition().duration(q).remove();t.select("circle").attr("r",0.000001);t.select("text").style("fill-opacity",0.000001);o.forEach(function(w){w.x0=w.x;w.y0=w.y})}});var j=Backbone.View.extend({className:"phyloviz",initialize:function(o){var n=this;n.MIN_SCALE=0.05;n.MAX_SCALE=5;n.MAX_DISPLACEMENT=500;n.margins=[10,60,10,80];n.width=$("#PhyloViz").width();n.height=$("#PhyloViz").height();n.radius=n.width;n.data=o.data;$(window).resize(function(){n.width=$("#PhyloViz").width();n.height=$("#PhyloViz").height();n.render()});n.phyloTree=new b(o.config);n.phyloTree.root=n.data;n.zoomFunc=m.behavior.zoom().scaleExtent([n.MIN_SCALE,n.MAX_SCALE]);n.zoomFunc.translate(n.phyloTree.get("translate"));n.zoomFunc.scale(n.phyloTree.get("scaleFactor"));n.navMenu=new c(n);n.settingsMenu=new i({phyloTree:n.phyloTree});n.nodeSelectionView=new e({phyloTree:n.phyloTree});n.search=new k();setTimeout(function(){n.zoomAndPan()},1000)},render:function(){var o=this;$("#PhyloViz").empty();o.mainSVG=m.select("#PhyloViz").append("svg:svg").attr("width",o.width).attr("height",o.height).attr("pointer-events","all").call(o.zoomFunc.on("zoom",function(){o.zoomAndPan()}));o.boundingRect=o.mainSVG.append("svg:rect").attr("class","boundingRect").attr("width",o.width).attr("height",o.height).attr("stroke","black").attr("fill","white");o.vis=o.mainSVG.append("svg:g").attr("class","vis");o.layoutOptions={model:o.phyloTree,width:o.width,height:o.height,vis:o.vis,margins:o.margins};$("#title").text("Phylogenetic Tree from "+o.phyloTree.get("title")+":");var n=new a(o.layoutOptions)},zoomAndPan:function(n){var t,p;if(typeof n!=="undefined"){t=n.zoom;p=n.translate}var w=this,r=w.zoomFunc.scale(),v=w.zoomFunc.translate(),s="",u="";switch(t){case"reset":r=1;v=[0,0];break;case"+":r*=1.1;break;case"-":r*=0.9;break;default:if(typeof t==="number"){r=t}else{if(m.event!==null){r=m.event.scale}}}if(r<w.MIN_SCALE||r>w.MAX_SCALE){return}w.zoomFunc.scale(r);s="translate("+w.margins[3]+","+w.margins[0]+") scale("+r+")";if(m.event!==null){u="translate("+m.event.translate+")"}else{if(typeof p!=="undefined"){var q=p.split(",")[0];var o=p.split(",")[1];if(!isNaN(q)&&!isNaN(o)){v=[v[0]+parseFloat(q),v[1]+parseFloat(o)]}}w.zoomFunc.translate(v);u="translate("+v+")"}w.phyloTree.set("scaleFactor",r);w.phyloTree.set("translate",v);w.vis.attr("transform",u+s)},reloadViz:function(){var n=this,o=$("#phylovizNexSelector :selected").val();$.getJSON(n.phyloTree.get("dataset").url(),{tree_index:o,data_type:"raw_data"},function(p){n.data=p.data;n.config=p;n.render()})}});var c=Backbone.View.extend({initialize:function(o){var n=this;n.phylovizView=o;$("#panelHeaderRightBtns").empty();$("#phyloVizNavBtns").empty();$("#phylovizNexSelector").off();n.initNavBtns();n.initRightHeaderBtns();$("#phylovizNexSelector").off().on("change",function(){n.phylovizView.reloadViz()})},initRightHeaderBtns:function(){var n=this;rightMenu=create_icon_buttons_menu([{icon_class:"gear",title:"PhyloViz Settings",on_click:function(){$("#SettingsMenu").show();n.settingsMenu.updateUI()}},{icon_class:"disk",title:"Save visualization",on_click:function(){var o=$("#phylovizNexSelector option:selected").text();if(o){n.phylovizView.phyloTree.set("title",o)}n.phylovizView.phyloTree.save()}},{icon_class:"chevron-expand",title:"Search / Edit Nodes",on_click:function(){$("#nodeSelectionView").show()}},{icon_class:"information",title:"Phyloviz Help",on_click:function(){window.open("http://wiki.g2.bx.psu.edu/Learn/Visualization/PhylogeneticTree")}}],{tooltip_config:{placement:"bottom"}});$("#panelHeaderRightBtns").append(rightMenu.$el)},initNavBtns:function(){var n=this,o=create_icon_buttons_menu([{icon_class:"zoom-in",title:"Zoom in",on_click:function(){n.phylovizView.zoomAndPan({zoom:"+"})}},{icon_class:"zoom-out",title:"Zoom out",on_click:function(){n.phylovizView.zoomAndPan({zoom:"-"})}},{icon_class:"arrow-circle",title:"Reset Zoom/Pan",on_click:function(){n.phylovizView.zoomAndPan({zoom:"reset"})}}],{tooltip_config:{placement:"bottom"}});$("#phyloVizNavBtns").append(o.$el)}});var i=l.extend({className:"Settings",initialize:function(o){var n=this;n.phyloTree=o.phyloTree;n.el=$("#SettingsMenu");n.inputs={separation:$("#phyloVizTreeSeparation"),leafHeight:$("#phyloVizTreeLeafHeight"),fontSize:$("#phyloVizTreeFontSize")};$("#settingsCloseBtn").off().on("click",function(){n.el.hide()});$("#phylovizResetSettingsBtn").off().on("click",function(){n.resetToDefaults()});$("#phylovizApplySettingsBtn").off().on("click",function(){n.apply()})},apply:function(){var n=this;if(!n.isAcceptableValue(n.inputs.separation,50,2500)||!n.isAcceptableValue(n.inputs.leafHeight,5,30)||!n.isAcceptableValue(n.inputs.fontSize,5,20)){return}$.each(n.inputs,function(o,p){n.phyloTree.set(o,p.val())})},updateUI:function(){var n=this;$.each(n.inputs,function(o,p){p.val(n.phyloTree.get(o))})},resetToDefaults:function(){$(".bs-tooltip").remove();var n=this;$.each(n.phyloTree.defaults,function(o,p){n.phyloTree.set(o,p)});n.updateUI()},render:function(){}});var e=l.extend({className:"Settings",initialize:function(o){var n=this;n.el=$("#nodeSelectionView");n.phyloTree=o.phyloTree;n.UI={enableEdit:$("#phylovizEditNodesCheck"),saveChanges:$("#phylovizNodeSaveChanges"),cancelChanges:$("#phylovizNodeCancelChanges"),name:$("#phyloVizSelectedNodeName"),dist:$("#phyloVizSelectedNodeDist"),annotation:$("#phyloVizSelectedNodeAnnotation")};n.valuesOfConcern={name:null,dist:null,annotation:null};$("#nodeSelCloseBtn").off().on("click",function(){n.el.hide()});n.UI.saveChanges.off().on("click",function(){n.updateNodes()});n.UI.cancelChanges.off().on("click",function(){n.cancelChanges()});(function(p){p.fn.enable=function(q){return p(this).each(function(){if(q){p(this).removeAttr("disabled")}else{p(this).attr("disabled","disabled")}})}})(jQuery);n.UI.enableEdit.off().on("click",function(){n.toggleUI()})},toggleUI:function(){var n=this,o=n.UI.enableEdit.is(":checked");if(!o){n.cancelChanges()}$.each(n.valuesOfConcern,function(p,q){n.UI[p].enable(o)});if(o){n.UI.saveChanges.show();n.UI.cancelChanges.show()}else{n.UI.saveChanges.hide();n.UI.cancelChanges.hide()}},cancelChanges:function(){var n=this,o=n.phyloTree.get("selectedNode");if(o){$.each(n.valuesOfConcern,function(p,q){n.UI[p].val(o[p])})}},updateNodes:function(){var n=this,o=n.phyloTree.get("selectedNode");if(o){if(!n.isAcceptableValue(n.UI.dist,0,1)||n.hasIllegalJsonCharacters(n.UI.name)||n.hasIllegalJsonCharacters(n.UI.annotation)){return}$.each(n.valuesOfConcern,function(p,q){(o[p])=n.UI[p].val()});n.phyloTree.set("nodeAttrChangedTime",new Date())}else{alert("No node selected")}}});var k=l.extend({initialize:function(){var n=this;$("#phyloVizSearchBtn").on("click",function(){var p=$("#phyloVizSearchTerm"),q=$("#phyloVizSearchCondition").val().split("-"),o=q[0],r=q[1];n.hasIllegalJsonCharacters(p);if(o==="dist"){n.isAcceptableValue(p,0,1)}n.searchTree(o,r,p.val())})},searchTree:function(n,p,o){m.selectAll("g.node").classed("searchHighlight",function(r){var q=r[n];if(typeof q!=="undefined"&&q!==null){if(n==="dist"){switch(p){case"greaterEqual":return q>=+o;case"lesserEqual":return q<=+o;default:return}}else{if(n==="name"||n==="annotation"){return q.toLowerCase().indexOf(o.toLowerCase())!==-1}}}})}});return{PhylovizView:j}}); \ No newline at end of file +define(["libs/d3","viz/visualization","mvc/data"],function(m,f,g){var l=Backbone.View.extend({className:"UserMenuBase",isAcceptableValue:function(q,o,n){var r=q.val(),s=q.attr("displayLabel")||q.attr("id").replace("phyloViz","");function p(t){return !isNaN(parseFloat(t))&&isFinite(t)}if(!p(r)){alert(s+" is not a number!");return false}if(r>n){alert(s+" is too large.");return false}else{if(r<o){alert(s+" is too small.");return false}}return true},hasIllegalJsonCharacters:function(n){if(n.val().search(/"|'|\\/)!==-1){alert("Named fields cannot contain these illegal characters: double quote(\"), single guote('), or back slash(\\). ");return true}return false}});function h(){var w=this,r=m.layout.hierarchy().sort(null).value(null),v=360,q="Linear",u=18,s=200,t=0,p=0.5,n=50;w.leafHeight=function(x){if(typeof x==="undefined"){return u}else{u=x;return w}};w.layoutMode=function(x){if(typeof x==="undefined"){return q}else{q=x;return w}};w.layoutAngle=function(x){if(typeof x==="undefined"){return v}if(isNaN(x)||x<0||x>360){return w}else{v=x;return w}};w.separation=function(x){if(typeof x==="undefined"){return s}else{s=x;return w}};w.links=function(x){return m.layout.tree().links(x)};w.nodes=function(A,y){if(toString.call(A)==="[object Array]"){A=A[0]}var z=r.call(w,A,y),x=[],C=0,B=0;window._d=A;window._nodes=z;z.forEach(function(D){C=D.depth>C?D.depth:C;x.push(D)});x.forEach(function(D){if(!D.children){B+=1;D.depth=C}});u=q==="Circular"?v/B:u;t=0;o(x[0],C,u,null);return x};function o(B,D,A,z){var y=B.children,x=0;var C=B.dist||p;C=C>1?1:C;B.dist=C;if(z!==null){B.y0=z.y0+C*s}else{B.y0=n}if(!y){B.x0=t*A;t+=1}else{y.forEach(function(E){E.parent=B;x+=o(E,D,A,B)});B.x0=x/y.length}B.x=B.x0;B.y=B.y0;return B.x0}return w}var b=f.Visualization.extend({defaults:{layout:"Linear",separation:250,leafHeight:18,type:"phyloviz",title:"Title",scaleFactor:1,translate:[0,0],fontSize:12,selectedNode:null,nodeAttrChangedTime:0},initialize:function(n){this.set("dataset",new g.Dataset({id:n.dataset_id}))},root:{},toggle:function(n){if(typeof n==="undefined"){return}if(n.children){n._children=n.children;n.children=null}else{n.children=n._children;n._children=null}},toggleAll:function(n){if(n.children&&n.children.length!==0){n.children.forEach(this.toggleAll);toggle(n)}},getData:function(){return this.root},save:function(){var n=this.root;o(n);this.set("root",n);function o(q){delete q.parent;if(q._selected){delete q._selected}if(q.children){q.children.forEach(o)}if(q._children){q._children.forEach(o)}}var p=jQuery.extend(true,{},this.attributes);p.selectedNode=null;show_message("Saving to Galaxy","progress");return $.ajax({url:this.url(),type:"POST",dataType:"json",data:{vis_json:JSON.stringify(p)},success:function(q){var r=q.url.split("id=")[1].split("&")[0],s="/visualization?id="+r;window.history.pushState({},"",s+window.location.hash);hide_modal()}})}});var d=Backbone.View.extend({defaults:{nodeRadius:4.5},stdInit:function(o){var n=this;n.model.on("change:separation change:leafHeight change:fontSize change:nodeAttrChangedTime",n.updateAndRender,n);n.vis=o.vis;n.i=0;n.maxDepth=-1;n.width=o.width;n.height=o.height},updateAndRender:function(p){var o=m.select(".vis"),n=this;p=p||n.model.root;n.renderNodes(p);n.renderLinks(p);n.addTooltips()},renderLinks:function(n){var w=this;var o=w.diagonal;var p=w.duration;var r=w.layoutMode;var t=w.vis.selectAll("g.completeLink").data(w.tree.links(w.nodes),function(x){return x.target.id});var v=function(x){x.pos0=x.source.y0+" "+x.source.x0;x.pos1=x.source.y0+" "+x.target.x0;x.pos2=x.target.y0+" "+x.target.x0};var u=t.enter().insert("svg:g","g.node").attr("class","completeLink");u.append("svg:path").attr("class","link").attr("d",function(x){v(x);return"M "+x.pos0+" L "+x.pos1});var s=t.transition().duration(500);s.select("path.link").attr("d",function(x){v(x);return"M "+x.pos0+" L "+x.pos1+" L "+x.pos2});var q=t.exit().remove()},selectNode:function(o){var n=this;m.selectAll("g.node").classed("selectedHighlight",function(p){if(o.id===p.id){if(o._selected){delete o._selected;return false}else{o._selected=true;return true}}return false});n.model.set("selectedNode",o);$("#phyloVizSelectedNodeName").val(o.name);$("#phyloVizSelectedNodeDist").val(o.dist);$("#phyloVizSelectedNodeAnnotation").val(o.annotation||"")},addTooltips:function(){$(".bs-tooltip").remove();$(".node").attr("data-original-title",function(){var o=this.__data__,n=o.annotation||"None";return o?(o.name?o.name+"<br/>":"")+"Dist: "+o.dist+" <br/>Annotation: "+n:""}).tooltip({placement:"top",trigger:"hover"})}});var a=d.extend({initialize:function(o){var n=this;n.margins=o.margins;n.layoutMode="Linear";n.stdInit(o);n.layout();n.updateAndRender(n.model.root)},layout:function(){var n=this;n.tree=new h().layoutMode("Linear");n.diagonal=m.svg.diagonal().projection(function(o){return[o.y,o.x]})},renderNodes:function(n){var u=this,v=u.model.get("fontSize")+"px";u.tree.separation(u.model.get("separation")).leafHeight(u.model.get("leafHeight"));var q=500,o=u.tree.separation(u.model.get("separation")).nodes(u.model.root);var p=u.vis.selectAll("g.node").data(o,function(w){return w.name+w.id||(w.id=++u.i)});u.nodes=o;u.duration=q;var r=p.enter().append("svg:g").attr("class","node").on("dblclick",function(){m.event.stopPropagation()}).on("click",function(w){if(m.event.altKey){u.selectNode(w)}else{if(w.children&&w.children.length===0){return}u.model.toggle(w);u.updateAndRender(w)}});console.debug("source:",n);if(toString.call(n)==="[object Array]"){n=n[0]}r.attr("transform",function(w){return"translate("+n.y0+","+n.x0+")"});r.append("svg:circle").attr("r",0.000001).style("fill",function(w){return w._children?"lightsteelblue":"#fff"});r.append("svg:text").attr("class","nodeLabel").attr("x",function(w){return w.children||w._children?-10:10}).attr("dy",".35em").attr("text-anchor",function(w){return w.children||w._children?"end":"start"}).style("fill-opacity",0.000001);var s=p.transition().duration(q);s.attr("transform",function(w){return"translate("+w.y+","+w.x+")"});s.select("circle").attr("r",u.defaults.nodeRadius).style("fill",function(w){return w._children?"lightsteelblue":"#fff"});s.select("text").style("fill-opacity",1).style("font-size",v).text(function(w){return w.name});var t=p.exit().transition().duration(q).remove();t.select("circle").attr("r",0.000001);t.select("text").style("fill-opacity",0.000001);o.forEach(function(w){w.x0=w.x;w.y0=w.y})}});var j=Backbone.View.extend({className:"phyloviz",initialize:function(o){var n=this;n.MIN_SCALE=0.05;n.MAX_SCALE=5;n.MAX_DISPLACEMENT=500;n.margins=[10,60,10,80];n.width=$("#PhyloViz").width();n.height=$("#PhyloViz").height();n.radius=n.width;n.data=o.data;$(window).resize(function(){n.width=$("#PhyloViz").width();n.height=$("#PhyloViz").height();n.render()});n.phyloTree=new b(o.config);n.phyloTree.root=n.data;n.zoomFunc=m.behavior.zoom().scaleExtent([n.MIN_SCALE,n.MAX_SCALE]);n.zoomFunc.translate(n.phyloTree.get("translate"));n.zoomFunc.scale(n.phyloTree.get("scaleFactor"));n.navMenu=new c(n);n.settingsMenu=new i({phyloTree:n.phyloTree});n.nodeSelectionView=new e({phyloTree:n.phyloTree});n.search=new k();setTimeout(function(){n.zoomAndPan()},1000)},render:function(){var o=this;$("#PhyloViz").empty();o.mainSVG=m.select("#PhyloViz").append("svg:svg").attr("width",o.width).attr("height",o.height).attr("pointer-events","all").call(o.zoomFunc.on("zoom",function(){o.zoomAndPan()}));o.boundingRect=o.mainSVG.append("svg:rect").attr("class","boundingRect").attr("width",o.width).attr("height",o.height).attr("stroke","black").attr("fill","white");o.vis=o.mainSVG.append("svg:g").attr("class","vis");o.layoutOptions={model:o.phyloTree,width:o.width,height:o.height,vis:o.vis,margins:o.margins};$("#title").text("Phylogenetic Tree from "+o.phyloTree.get("title")+":");var n=new a(o.layoutOptions)},zoomAndPan:function(n){var t,p;if(typeof n!=="undefined"){t=n.zoom;p=n.translate}var w=this,r=w.zoomFunc.scale(),v=w.zoomFunc.translate(),s="",u="";switch(t){case"reset":r=1;v=[0,0];break;case"+":r*=1.1;break;case"-":r*=0.9;break;default:if(typeof t==="number"){r=t}else{if(m.event!==null){r=m.event.scale}}}if(r<w.MIN_SCALE||r>w.MAX_SCALE){return}w.zoomFunc.scale(r);s="translate("+w.margins[3]+","+w.margins[0]+") scale("+r+")";if(m.event!==null){u="translate("+m.event.translate+")"}else{if(typeof p!=="undefined"){var q=p.split(",")[0];var o=p.split(",")[1];if(!isNaN(q)&&!isNaN(o)){v=[v[0]+parseFloat(q),v[1]+parseFloat(o)]}}w.zoomFunc.translate(v);u="translate("+v+")"}w.phyloTree.set("scaleFactor",r);w.phyloTree.set("translate",v);w.vis.attr("transform",u+s)},reloadViz:function(){var n=this,o=$("#phylovizNexSelector :selected").val();$.getJSON(n.phyloTree.get("dataset").url(),{tree_index:o,data_type:"raw_data"},function(p){n.data=p.data;n.config=p;n.render()})}});var c=Backbone.View.extend({initialize:function(o){var n=this;n.phylovizView=o;$("#panelHeaderRightBtns").empty();$("#phyloVizNavBtns").empty();$("#phylovizNexSelector").off();n.initNavBtns();n.initRightHeaderBtns();$("#phylovizNexSelector").off().on("change",function(){n.phylovizView.reloadViz()})},initRightHeaderBtns:function(){var n=this;rightMenu=create_icon_buttons_menu([{icon_class:"gear",title:"PhyloViz Settings",on_click:function(){$("#SettingsMenu").show();n.settingsMenu.updateUI()}},{icon_class:"disk",title:"Save visualization",on_click:function(){var o=$("#phylovizNexSelector option:selected").text();if(o){n.phylovizView.phyloTree.set("title",o)}n.phylovizView.phyloTree.save()}},{icon_class:"chevron-expand",title:"Search / Edit Nodes",on_click:function(){$("#nodeSelectionView").show()}},{icon_class:"information",title:"Phyloviz Help",on_click:function(){window.open("http://wiki.g2.bx.psu.edu/Learn/Visualization/PhylogeneticTree")}}],{tooltip_config:{placement:"bottom"}});$("#panelHeaderRightBtns").append(rightMenu.$el)},initNavBtns:function(){var n=this,o=create_icon_buttons_menu([{icon_class:"zoom-in",title:"Zoom in",on_click:function(){n.phylovizView.zoomAndPan({zoom:"+"})}},{icon_class:"zoom-out",title:"Zoom out",on_click:function(){n.phylovizView.zoomAndPan({zoom:"-"})}},{icon_class:"arrow-circle",title:"Reset Zoom/Pan",on_click:function(){n.phylovizView.zoomAndPan({zoom:"reset"})}}],{tooltip_config:{placement:"bottom"}});$("#phyloVizNavBtns").append(o.$el)}});var i=l.extend({className:"Settings",initialize:function(o){var n=this;n.phyloTree=o.phyloTree;n.el=$("#SettingsMenu");n.inputs={separation:$("#phyloVizTreeSeparation"),leafHeight:$("#phyloVizTreeLeafHeight"),fontSize:$("#phyloVizTreeFontSize")};$("#settingsCloseBtn").off().on("click",function(){n.el.hide()});$("#phylovizResetSettingsBtn").off().on("click",function(){n.resetToDefaults()});$("#phylovizApplySettingsBtn").off().on("click",function(){n.apply()})},apply:function(){var n=this;if(!n.isAcceptableValue(n.inputs.separation,50,2500)||!n.isAcceptableValue(n.inputs.leafHeight,5,30)||!n.isAcceptableValue(n.inputs.fontSize,5,20)){return}$.each(n.inputs,function(o,p){n.phyloTree.set(o,p.val())})},updateUI:function(){var n=this;$.each(n.inputs,function(o,p){p.val(n.phyloTree.get(o))})},resetToDefaults:function(){$(".bs-tooltip").remove();var n=this;$.each(n.phyloTree.defaults,function(o,p){n.phyloTree.set(o,p)});n.updateUI()},render:function(){}});var e=l.extend({className:"Settings",initialize:function(o){var n=this;n.el=$("#nodeSelectionView");n.phyloTree=o.phyloTree;n.UI={enableEdit:$("#phylovizEditNodesCheck"),saveChanges:$("#phylovizNodeSaveChanges"),cancelChanges:$("#phylovizNodeCancelChanges"),name:$("#phyloVizSelectedNodeName"),dist:$("#phyloVizSelectedNodeDist"),annotation:$("#phyloVizSelectedNodeAnnotation")};n.valuesOfConcern={name:null,dist:null,annotation:null};$("#nodeSelCloseBtn").off().on("click",function(){n.el.hide()});n.UI.saveChanges.off().on("click",function(){n.updateNodes()});n.UI.cancelChanges.off().on("click",function(){n.cancelChanges()});(function(p){p.fn.enable=function(q){return p(this).each(function(){if(q){p(this).removeAttr("disabled")}else{p(this).attr("disabled","disabled")}})}})(jQuery);n.UI.enableEdit.off().on("click",function(){n.toggleUI()})},toggleUI:function(){var n=this,o=n.UI.enableEdit.is(":checked");if(!o){n.cancelChanges()}$.each(n.valuesOfConcern,function(p,q){n.UI[p].enable(o)});if(o){n.UI.saveChanges.show();n.UI.cancelChanges.show()}else{n.UI.saveChanges.hide();n.UI.cancelChanges.hide()}},cancelChanges:function(){var n=this,o=n.phyloTree.get("selectedNode");if(o){$.each(n.valuesOfConcern,function(p,q){n.UI[p].val(o[p])})}},updateNodes:function(){var n=this,o=n.phyloTree.get("selectedNode");if(o){if(!n.isAcceptableValue(n.UI.dist,0,1)||n.hasIllegalJsonCharacters(n.UI.name)||n.hasIllegalJsonCharacters(n.UI.annotation)){return}$.each(n.valuesOfConcern,function(p,q){(o[p])=n.UI[p].val()});n.phyloTree.set("nodeAttrChangedTime",new Date())}else{alert("No node selected")}}});var k=l.extend({initialize:function(){var n=this;$("#phyloVizSearchBtn").on("click",function(){var p=$("#phyloVizSearchTerm"),q=$("#phyloVizSearchCondition").val().split("-"),o=q[0],r=q[1];n.hasIllegalJsonCharacters(p);if(o==="dist"){n.isAcceptableValue(p,0,1)}n.searchTree(o,r,p.val())})},searchTree:function(n,p,o){m.selectAll("g.node").classed("searchHighlight",function(r){var q=r[n];if(typeof q!=="undefined"&&q!==null){if(n==="dist"){switch(p){case"greaterEqual":return q>=+o;case"lesserEqual":return q<=+o;default:return}}else{if(n==="name"||n==="annotation"){return q.toLowerCase().indexOf(o.toLowerCase())!==-1}}}})}});return{PhylovizView:j}}); \ No newline at end of file diff -r 9bb23c38aeec1f26ec1c22fbcad8a098fd6ae015 -r d8b3dc823978835f0b2105635990f310fa619c1c static/scripts/viz/phyloviz.js --- a/static/scripts/viz/phyloviz.js +++ b/static/scripts/viz/phyloviz.js @@ -1,18 +1,18 @@ define(['libs/d3', 'viz/visualization', 'mvc/data'], function(d3, visualization_mod, data_mod) { +/** + * Base class of any menus that takes in user interaction. Contains checking methods. + */ var UserMenuBase = Backbone.View.extend({ - /** - * Base class of any menus that takes in user interaction. Contains checking methods. - */ className: 'UserMenuBase', + /** + * Check if an input value is a number and falls within max min. + */ isAcceptableValue : function ($inputKey, min, max) { - /** - * Check if an input value is a number and falls within max min. - */ - var self = this, - value = $inputKey.val(), + //TODO: use better feedback than alert + var value = $inputKey.val(), fieldName = $inputKey.attr("displayLabel") || $inputKey.attr("id").replace("phyloViz", ""); function isNumeric(n) { @@ -34,12 +34,13 @@ return true; }, + /** + * Check if any user string inputs has illegal characters that json cannot accept + */ hasIllegalJsonCharacters : function($inputKey) { - /** - * Check if any user string inputs has illegal characters that json cannot accept - */ if ($inputKey.val().search(/"|'|\\/) !== -1){ - alert("Named fields cannot contain these illegal characters: double quote(\"), single guote(\'), or back slash(\\). "); + alert("Named fields cannot contain these illegal characters: " + + "double quote(\"), single guote(\'), or back slash(\\). "); return true; } return false; @@ -47,12 +48,12 @@ }); +/** + * -- Custom Layout call for phyloViz to suit the needs of a phylogenetic tree. + * -- Specifically: 1) Nodes have a display display of (= evo dist X depth separation) from their parent + * 2) Nodes must appear in other after they have expand and contracted + */ function PhyloTreeLayout() { - /** - * -- Custom Layout call for phyloViz to suit the needs of a phylogenetic tree. - * -- Specifically: 1) Nodes have a display display of (= evo dist X depth separation) from their parent - * 2) Nodes must appear in other after they have expand and contracted - */ var self = this, hierarchy = d3.layout.hierarchy().sort(null).value(null), @@ -75,9 +76,11 @@ else { layoutMode = mode; return self;} }; - self.layoutAngle = function(angle) { // changes the layout angle of the display, which is really changing the height + // changes the layout angle of the display, which is really changing the height + self.layoutAngle = function(angle) { if (typeof angle === "undefined"){ return height; } - if (isNaN(angle) || angle < 0 || angle > 360) { return self; } // to use default if the user puts in strange values + // to use default if the user puts in strange values + if (isNaN(angle) || angle < 0 || angle > 360) { return self; } else { height = angle; return self;} }; @@ -92,19 +95,28 @@ // -- Custom method for laying out phylogeny tree in a linear fashion self.nodes = function (d, i) { - var _nodes = hierarchy.call(self, d, i), // self is to find the depth of all the nodes, assumes root is passed in + //TODO: newick and phyloxml return arrays. where should this go (client (here, else), server)? + if( toString.call( d ) === '[object Array]' ){ + // if d is an array, replate with the first object (newick, phyloxml) + d = d[0]; + } + // self is to find the depth of all the nodes, assumes root is passed in + var _nodes = hierarchy.call(self, d, i), nodes = [], maxDepth = 0, numLeaves = 0; + //console.debug( JSON.stringify( _nodes, null, 2 ) ) + window._d = d; + window._nodes = _nodes; + //TODO: remove dbl-touch loop // changing from hierarchy's custom format for data to usable format - _nodes.forEach(function (_node){ - var node = _node.data; - node.depth = _node.depth; + _nodes.forEach(function (node){ maxDepth = node.depth > maxDepth ? node.depth : maxDepth; //finding max depth of tree nodes.push(node); }); - // counting the number of leaf nodes and assigning max depth to nodes that do not have children to flush all the leave nodes + // counting the number of leaf nodes and assigning max depth + // to nodes that do not have children to flush all the leave nodes nodes.forEach(function(node){ if ( !node.children ) { //&& !node._children numLeaves += 1; @@ -120,15 +132,16 @@ }; + /** + * -- Function with side effect of adding x0, y0 to all child; take in the root as starting point + * assuming that the leave nodes would be sorted in presented order + * horizontal(y0) is calculated according to (= evo dist X depth separation) from their parent + * vertical (x0) - if leave node: find its order in all of the leave node === node.id, + * then multiply by verticalSeparation + * - if parent node: is place in the mid point all of its children nodes + * -- The layout will first calculate the y0 field going towards the leaves, and x0 when returning + */ function layout (node, maxDepth, vertSeparation, parent) { - /** - * -- Function with side effect of adding x0, y0 to all child; take in the root as starting point - * assuming that the leave nodes would be sorted in presented order - * horizontal(y0) is calculated according to (= evo dist X depth separation) from their parent - * vertical (x0) - if leave node: find its order in all of the leave node === node.id, then multiply by verticalSeparation - * - if parent node: is place in the mid point all of its children nodes - * -- The layout will first calculate the y0 field going towards the leaves, and x0 when returning - */ var children = node.children, sumChildVertSeparation = 0; @@ -145,7 +158,8 @@ // if a node have no children, we will treat it as a leaf and start laying it out first if (!children) { - node.x0 = leafIndex++ * vertSeparation; + node.x0 = leafIndex * vertSeparation; + leafIndex += 1; } else { // if it has children, we will visit all its children and calculate its position from its children children.forEach( function (child) { @@ -189,12 +203,12 @@ root : {}, // Root has to be its own independent object because it is not part of the viz_config + /** + * Mechanism to expand or contract a single node. Expanded nodes have a children list, while for + * contracted nodes the list is stored in _children. Nodes with their children data stored in _children will not + * have their children rendered. + */ toggle : function (d) { - /** - * Mechanism to expand or contract a single node. Expanded nodes have a children list, while for - * contracted nodes the list is stored in _children. Nodes with their children data stored in _children will not have their - * children rendered. - */ if(typeof d === "undefined") {return ;} if (d.children ) { d._children = d.children; @@ -205,29 +219,29 @@ } }, + /** + * Contracts the phylotree to a single node by repeatedly calling itself to place all the list + * of children under _children. + */ toggleAll : function(d) { - /** - * Contracts the phylotree to a single node by repeatedly calling itself to place all the list - * of children under _children. - */ if (d.children && d.children.length !== 0) { d.children.forEach(this.toggleAll); toggle(d); } }, + /** + * Return the data of the tree. Used for preserving state. + */ getData : function (){ - /** - * Return the data of the tree. Used for preserving state. - */ return this.root; }, + /** + * Overriding the default save mechanism to do some clean of circular reference of the + * phyloTree and to include phyloTree in the saved json + */ save: function() { - /** - * Overriding the default save mechanism to do some clean of circular reference of the - * phyloTree and to include phyloTree in the saved json - */ var root = this.root; cleanTree(root); this.set("root", root); @@ -240,7 +254,7 @@ if (node._selected){ delete node._selected;} if (node.children) { - node.children.forEach(cleanTree); + node.children.forEach(cleanTree); } if (node._children) { node._children.forEach(cleanTree); @@ -270,26 +284,24 @@ }); - +// -- Views -- /** - * -- Views -- + * Stores the default variable for setting up the visualization */ var PhylovizLayoutBase = Backbone.View.extend({ - /** - * Stores the default variable for setting up the visualization - */ defaults : { nodeRadius : 4.5 // radius of each node in the diagram }, + /** + * Common initialization in layouts + */ stdInit : function (options) { - /** - * Common initialization in layouts - */ var self = this; - self.model.on("change:separation change:leafHeight change:fontSize change:nodeAttrChangedTime", self.updateAndRender, self); + self.model.on("change:separation change:leafHeight change:fontSize change:nodeAttrChangedTime", + self.updateAndRender, self); self.vis = options.vis; self.i = 0; @@ -300,11 +312,11 @@ }, + /** + * Updates the visualization whenever there are changes in the expansion and contraction of nodes + * AND possibly when the tree is edited. + */ updateAndRender : function(source) { - /** - * Updates the visualization whenever there are changes in the expansion and contraction of nodes - * AND possibly when the tree is edited. - */ var vis = d3.select(".vis"), self = this; source = source || self.model.root; @@ -315,10 +327,10 @@ }, + /** + * Renders the links for the visualization. + */ renderLinks : function(source) { - /** - * Renders the links for the visualization. - */ var self = this; var diagonal = self.diagonal; var duration = self.duration; @@ -327,15 +339,17 @@ .data(self.tree.links(self.nodes), function(d) { return d.target.id; }); var calcalateLinePos = function(d) { - d.pos0 = d.source.y0 + " " + d.source.x0; // position of the source node <=> starting location of the line drawn - d.pos1 = d.source.y0 + " " + d.target.x0; // position where the line makes a right angle bend - d.pos2 = d.target.y0 + " " + d.target.x0; // point where the horizontal line becomes a dotted line + // position of the source node <=> starting location of the line drawn + d.pos0 = d.source.y0 + " " + d.source.x0; + // position where the line makes a right angle bend + d.pos1 = d.source.y0 + " " + d.target.x0; + // point where the horizontal line becomes a dotted line + d.pos2 = d.target.y0 + " " + d.target.x0; }; var linkEnter = link.enter().insert("svg:g","g.node") .attr("class", "completeLink"); - linkEnter.append("svg:path") .attr("class", "link") .attr("d", function(d) { @@ -357,10 +371,10 @@ // User Interaction methods below + /** + * Displays the information for editing + */ selectNode : function(node){ - /** - * Displays the information for editting - */ var self = this; d3.selectAll("g.node") .classed("selectedHighlight", function(d){ @@ -382,11 +396,11 @@ $("#phyloVizSelectedNodeAnnotation").val(node.annotation || ""); }, + /** + * Creates bootstrap tooltip for the visualization. Has to be called repeatedly due to newly generated + * enterNodes + */ addTooltips : function (){ - /** - * Creates bootstrap tooltip for the visualization. Has to be called repeatedly due to newly generated - * enterNodes - */ $(".bs-tooltip").remove(); //clean up tooltip, just in case its listeners are removed by d3 $(".node") .attr("data-original-title", function(){ @@ -400,13 +414,11 @@ }); - - +/** + * Linea layout class of Phyloviz, is responsible for rendering the nodes + * calls PhyloTreeLayout to determine the positions of the nodes + */ var PhylovizLinearView = PhylovizLayoutBase.extend({ - /** - * Linea layout class of Phyloviz, is responsible for rendering the nodes - * calls PhyloTreeLayout to determine the positions of the nodes - */ initialize : function(options){ // Default values of linear layout var self = this; @@ -419,23 +431,21 @@ self.updateAndRender(self.model.root); }, + /** + * Creates the basic layout of a linear tree by precalculating fixed values. + * One of calculations are also made here + */ layout : function() { - /** - * Creates the basic layout of a linear tree by precalculating fixed values. - * One of calculations are also made here - */ - var self = this; - self.tree = new PhyloTreeLayout().layoutMode("Linear"); self.diagonal = d3.svg.diagonal() .projection(function(d) { return [d.y, d.x ]; }); }, + /** + * Renders the nodes base on Linear layout. + */ renderNodes : function (source) { - /** - * Renders the nodes base on Linear layout. - */ var self = this, fontSize = self.model.get("fontSize") + "px"; @@ -446,7 +456,9 @@ nodes = self.tree.separation(self.model.get("separation")).nodes(self.model.root); var node = self.vis.selectAll("g.node") - .data(nodes, function(d) { return d.name + d.id || (d.id = ++self.i); }); + .data(nodes, function(d) { + return d.name + d.id || (d.id = ++self.i); + }); // These variables has to be passed into update links which are in the base methods self.nodes = nodes; @@ -466,7 +478,12 @@ self.updateAndRender(d); // re-render the tree } }); - + console.debug( 'source:', source ) + //TODO: newick and phyloxml return arrays. where should this go (client (here, else), server)? + if( toString.call( source ) === '[object Array]' ){ + // if d is an array, replate with the first object (newick, phyloxml) + source = source[0]; + } nodeEnter.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; }); nodeEnter.append("svg:circle") @@ -557,8 +574,8 @@ self.nodeSelectionView = new NodeSelectionView({phyloTree : self.phyloTree}); self.search = new PhyloVizSearch(); - - setTimeout(function(){ // using settimeout to call the zoomAndPan function according to the stored attributes in viz_config + // using settimeout to call the zoomAndPan function according to the stored attributes in viz_config + setTimeout(function(){ self.zoomAndPan(); }, 1000); }, @@ -603,11 +620,11 @@ var linearView = new PhylovizLinearView(self.layoutOptions); }, + /** + * Function to zoom and pan the svg element which the entire tree is contained within + * Uses d3.zoom events, and extend them to allow manual updates and keeping states in model + */ zoomAndPan : function(event){ - /** - * Function to zoom and pan the svg element which the entire tree is contained within - * Uses d3.zoom events, and extend them to allow manual updates and keeping states in model - */ var zoomParams, translateParams; if (typeof event !== "undefined") { @@ -659,21 +676,26 @@ self.phyloTree.set("scaleFactor", scaleFactor); self.phyloTree.set("translate", translationCoor); - self.vis.attr("transform", translateStatement + zoomStatement); //refers to the view that we are actually zooming + //refers to the view that we are actually zooming + self.vis.attr("transform", translateStatement + zoomStatement); }, + /** + * Primes the Ajax URL to load another Nexus tree + */ reloadViz : function() { - /** - * Primes the Ajax URL to load another Nexus tree - */ var self = this, treeIndex = $("#phylovizNexSelector :selected").val(); - $.getJSON(self.phyloTree.get("dataset").url(), { tree_index: treeIndex, data_type: 'raw_data' }, function(packedJson){ - self.data = packedJson.data; - self.config = packedJson; - self.render(); - }); + $.getJSON(self.phyloTree.get("dataset").url(), { + tree_index: treeIndex, + data_type: 'raw_data' + }, + function(packedJson){ + self.data = packedJson.data; + self.config = packedJson; + self.render(); + }); } }); @@ -768,10 +790,10 @@ $("#phylovizApplySettingsBtn").off().on("click", function() { self.apply(); }); }, + /** + * Applying user values to phylotree model. + */ apply : function(){ - /** - * Applying user values to phylotree model. - */ var self = this; if (!self.isAcceptableValue(self.inputs.separation, 50, 2500) || !self.isAcceptableValue(self.inputs.leafHeight, 5, 30) || @@ -782,19 +804,19 @@ self.phyloTree.set(key, $input.val()); }); }, + /** + * Called to update the values input to that stored in the model + */ updateUI : function(){ - /** - * Called to update the values input to that stored in the model - */ var self = this; $.each(self.inputs, function(key, $input){ $input.val(self.phyloTree.get(key)); }); }, + /** + * Resets the value of the phyloTree model to its default + */ resetToDefaults : function(){ - /** - * Resets the value of the phyloTree model to its default - */ $(".bs-tooltip").remove(); // just in case the tool tip was not removed var self = this; $.each(self.phyloTree.defaults, function(key, value) { @@ -810,10 +832,11 @@ }); +/** + * View for inspecting node properties and editing them + */ var NodeSelectionView = UserMenuBase.extend({ - /** - * View for inspecting node properties and editing them - */ + className: 'Settings', initialize : function (options){ @@ -830,11 +853,12 @@ annotation : $("#phyloVizSelectedNodeAnnotation") }; + // temporarily stores the values in case user change their mind self.valuesOfConcern = { name : null, dist : null, annotation : null - }; // temporarily stores the values in case user change their mind + }; //init UI buttons $("#nodeSelCloseBtn").off().on("click", function() { self.el.hide(); }); @@ -859,10 +883,10 @@ }); }, + /** + * For turning on and off the child elements + */ toggleUI : function(){ - /** - * For turning on and off the child elements - */ var self = this, checked = self.UI.enableEdit.is(':checked'); @@ -881,10 +905,10 @@ }, + /** + * Reverting to previous values in case user change their minds + */ cancelChanges : function() { - /** - * Reverting to previous values in case user change their minds - */ var self = this, node = self.phyloTree.get("selectedNode"); if (node){ @@ -894,10 +918,10 @@ } }, + /** + * Changing the data in the underlying tree with user-specified values + */ updateNodes : function (){ - /** - * Changing the data in the underlying tree with user-specified values - */ var self = this, node = self.phyloTree.get("selectedNode"); if (node){ @@ -914,17 +938,15 @@ alert("No node selected"); } } - - }); +/** + * Initializes the search panel on phyloviz and handles its user interaction + * It allows user to search the entire free based on some qualifer, like dist <= val. + */ var PhyloVizSearch = UserMenuBase.extend({ - /** - * Initializes the search panel on phyloviz and handles its user interaction - * It allows user to search the entire free based on some qualifer, like dist <= val. - */ initialize : function () { var self = this; @@ -942,10 +964,10 @@ }); }, + /** + * Searches the entire tree and will highlight the nodes that match the condition in green + */ searchTree : function (attr, condition, val){ - /** - * Searches the entire tree and will highlight the nodes that match the condition in green - */ d3.selectAll("g.node") .classed("searchHighlight", function(d){ var attrVal = d[attr]; @@ -972,4 +994,4 @@ PhylovizView: PhylovizView }; -}); \ No newline at end of file +}); diff -r 9bb23c38aeec1f26ec1c22fbcad8a098fd6ae015 -r d8b3dc823978835f0b2105635990f310fa619c1c templates/webapps/galaxy/visualization/phyloviz.mako --- a/templates/webapps/galaxy/visualization/phyloviz.mako +++ b/templates/webapps/galaxy/visualization/phyloviz.mako @@ -81,7 +81,7 @@ position: fixed; ## Borrowed from galaxy modal_dialogues - background-color: white; + background-color: white; border: 1px solid #999; border: 1px solid rgba(0, 0, 0, 0.3); -webkit-border-radius: 6px; @@ -157,9 +157,9 @@ // -- Initialization code |--> phyloviz = new phyloviz_mod.PhylovizView({ - data: data, - layout : "Linear", - config : config + data : data, + layout : "Linear", + config : config }); // -- Render viz. -- @@ -169,7 +169,9 @@ $(function firstVizLoad(){ // calls when viz is loaded for the first time var config = JSON.parse( '${ h.to_json_string( config )}'); + window.config = config; var data = JSON.parse('${h.to_json_string(data)}'); + window.data = data; initPhyloViz(data, config); }); }); @@ -189,7 +191,6 @@ <div style="clear: both"></div></div> - <div id="phyloVizNavContainer"><div id="phyloVizNav"> %if config["ext"] == "nex" and not config["saved_visualization"]: @@ -208,10 +209,7 @@ <div class="navControl"><p> | Alt+click to select nodes</p></div> - - </div> - </div> ## Node Selection Menu @@ -284,11 +282,6 @@ </div></div> - - - - - <div class="Panel" id="FloatingMenu" style="display: None;"><h2>PhyloViz (<a onclick="displayHelp()" href="javascript:void(0);">?</a>)</h2> @@ -300,14 +293,12 @@ <div class="hint">4. Minimap: Currently displays an exact but scaled down replicate of the tree, orange bounding box is correct for linear only<br/> Can be switched on or off</div><div class="hint">5. Changing Layouts: Able to change between circular and linear layouts.</div> - </div><h5>Scaling & Rotation:</h5><button id="phylovizZoomInBtn" class="" > + </button><button id="phylovizZoomOutBtn" class="" > - </button> - <h5>Translation:</h5><button id="phylovizTranslateUpBtn" > Up </button><button id="phylovizTranslateDownBtn" > Down </button> @@ -315,8 +306,6 @@ <button id="phylovizTranslateLeftBtn" > Left </button><button id="phylovizTranslateRightBtn" > Right </button> - - <h5>Others:</h5><button id="phylovizResetBtn" > Reset Zoom/Translate </button><button id="phylovizSaveBtn" > Save vizualization </button> https://bitbucket.org/galaxy/galaxy-central/commits/82f83d00dfd4/ Changeset: 82f83d00dfd4 User: carlfeberhard Date: 2013-05-21 17:16:05 Summary: merge next-stable Affected #: 6 files diff -r 137dc44f21782c771879af1febfe962a2ea9ae6a -r 82f83d00dfd419c035c76b100ec0e88a10bb900c lib/galaxy/visualization/data_providers/phyloviz/__init__.py --- a/lib/galaxy/visualization/data_providers/phyloviz/__init__.py +++ b/lib/galaxy/visualization/data_providers/phyloviz/__init__.py @@ -1,3 +1,4 @@ + """ Data providers code for PhyloViz """ from galaxy.visualization.data_providers.basic import BaseDataProvider @@ -40,4 +41,3 @@ rval[ "msg"] = parseMsg return rval - diff -r 137dc44f21782c771879af1febfe962a2ea9ae6a -r 82f83d00dfd419c035c76b100ec0e88a10bb900c lib/galaxy/visualization/data_providers/phyloviz/newickparser.py --- a/lib/galaxy/visualization/data_providers/phyloviz/newickparser.py +++ b/lib/galaxy/visualization/data_providers/phyloviz/newickparser.py @@ -77,8 +77,6 @@ childrenNodes += [node] return childrenNodes - - def _mapName(self, newickString, nameMap): """ Necessary to replace names of terms inside nexus representation @@ -108,9 +106,9 @@ newString += newickString[start:] return newString - def parseNode(self, string, depth): - """ Recursive method for parsing newick string, works by stripping down the string into substring + """ + Recursive method for parsing newick string, works by stripping down the string into substring of newick contained with brackers, which is used to call itself. Eg ... ( A, B, (D, E)C, F, G ) ... diff -r 137dc44f21782c771879af1febfe962a2ea9ae6a -r 82f83d00dfd419c035c76b100ec0e88a10bb900c lib/galaxy/visualization/data_providers/registry.py --- a/lib/galaxy/visualization/data_providers/registry.py +++ b/lib/galaxy/visualization/data_providers/registry.py @@ -53,7 +53,7 @@ elif isinstance( original_dataset.datatype, Tabular ): data_provider_class = ColumnDataProvider elif isinstance( original_dataset.datatype, ( Nexus, Newick, Phyloxml ) ): - data_provider_class = genome.PhylovizDataProvider + data_provider_class = PhylovizDataProvider data_provider = data_provider_class( original_dataset=original_dataset ) diff -r 137dc44f21782c771879af1febfe962a2ea9ae6a -r 82f83d00dfd419c035c76b100ec0e88a10bb900c static/scripts/packed/viz/phyloviz.js --- a/static/scripts/packed/viz/phyloviz.js +++ b/static/scripts/packed/viz/phyloviz.js @@ -1,1 +1,1 @@ -define(["libs/d3","viz/visualization","mvc/data"],function(m,f,g){var l=Backbone.View.extend({className:"UserMenuBase",isAcceptableValue:function(r,p,n){var o=this,s=r.val(),t=r.attr("displayLabel")||r.attr("id").replace("phyloViz","");function q(u){return !isNaN(parseFloat(u))&&isFinite(u)}if(!q(s)){alert(t+" is not a number!");return false}if(s>n){alert(t+" is too large.");return false}else{if(s<p){alert(t+" is too small.");return false}}return true},hasIllegalJsonCharacters:function(n){if(n.val().search(/"|'|\\/)!==-1){alert("Named fields cannot contain these illegal characters: double quote(\"), single guote('), or back slash(\\). ");return true}return false}});function h(){var w=this,r=m.layout.hierarchy().sort(null).value(null),v=360,q="Linear",u=18,s=200,t=0,p=0.5,n=50;w.leafHeight=function(x){if(typeof x==="undefined"){return u}else{u=x;return w}};w.layoutMode=function(x){if(typeof x==="undefined"){return q}else{q=x;return w}};w.layoutAngle=function(x){if(typeof x==="undefined"){return v}if(isNaN(x)||x<0||x>360){return w}else{v=x;return w}};w.separation=function(x){if(typeof x==="undefined"){return s}else{s=x;return w}};w.links=function(x){return m.layout.tree().links(x)};w.nodes=function(A,y){var z=r.call(w,A,y),x=[],C=0,B=0;z.forEach(function(D){var E=D.data;E.depth=D.depth;C=E.depth>C?E.depth:C;x.push(E)});x.forEach(function(D){if(!D.children){B+=1;D.depth=C}});u=q==="Circular"?v/B:u;t=0;o(x[0],C,u,null);return x};function o(B,D,A,z){var y=B.children,x=0;var C=B.dist||p;C=C>1?1:C;B.dist=C;if(z!==null){B.y0=z.y0+C*s}else{B.y0=n}if(!y){B.x0=t++*A}else{y.forEach(function(E){E.parent=B;x+=o(E,D,A,B)});B.x0=x/y.length}B.x=B.x0;B.y=B.y0;return B.x0}return w}var b=f.Visualization.extend({defaults:{layout:"Linear",separation:250,leafHeight:18,type:"phyloviz",title:"Title",scaleFactor:1,translate:[0,0],fontSize:12,selectedNode:null,nodeAttrChangedTime:0},initialize:function(n){this.set("dataset",new g.Dataset({id:n.dataset_id}))},root:{},toggle:function(n){if(typeof n==="undefined"){return}if(n.children){n._children=n.children;n.children=null}else{n.children=n._children;n._children=null}},toggleAll:function(n){if(n.children&&n.children.length!==0){n.children.forEach(this.toggleAll);toggle(n)}},getData:function(){return this.root},save:function(){var n=this.root;o(n);this.set("root",n);function o(q){delete q.parent;if(q._selected){delete q._selected}if(q.children){q.children.forEach(o)}if(q._children){q._children.forEach(o)}}var p=jQuery.extend(true,{},this.attributes);p.selectedNode=null;show_message("Saving to Galaxy","progress");return $.ajax({url:this.url(),type:"POST",dataType:"json",data:{vis_json:JSON.stringify(p)},success:function(q){var r=q.url.split("id=")[1].split("&")[0],s="/visualization?id="+r;window.history.pushState({},"",s+window.location.hash);hide_modal()}})}});var d=Backbone.View.extend({defaults:{nodeRadius:4.5},stdInit:function(o){var n=this;n.model.on("change:separation change:leafHeight change:fontSize change:nodeAttrChangedTime",n.updateAndRender,n);n.vis=o.vis;n.i=0;n.maxDepth=-1;n.width=o.width;n.height=o.height},updateAndRender:function(p){var o=m.select(".vis"),n=this;p=p||n.model.root;n.renderNodes(p);n.renderLinks(p);n.addTooltips()},renderLinks:function(n){var w=this;var o=w.diagonal;var p=w.duration;var r=w.layoutMode;var t=w.vis.selectAll("g.completeLink").data(w.tree.links(w.nodes),function(x){return x.target.id});var v=function(x){x.pos0=x.source.y0+" "+x.source.x0;x.pos1=x.source.y0+" "+x.target.x0;x.pos2=x.target.y0+" "+x.target.x0};var u=t.enter().insert("svg:g","g.node").attr("class","completeLink");u.append("svg:path").attr("class","link").attr("d",function(x){v(x);return"M "+x.pos0+" L "+x.pos1});var s=t.transition().duration(500);s.select("path.link").attr("d",function(x){v(x);return"M "+x.pos0+" L "+x.pos1+" L "+x.pos2});var q=t.exit().remove()},selectNode:function(o){var n=this;m.selectAll("g.node").classed("selectedHighlight",function(p){if(o.id===p.id){if(o._selected){delete o._selected;return false}else{o._selected=true;return true}}return false});n.model.set("selectedNode",o);$("#phyloVizSelectedNodeName").val(o.name);$("#phyloVizSelectedNodeDist").val(o.dist);$("#phyloVizSelectedNodeAnnotation").val(o.annotation||"")},addTooltips:function(){$(".bs-tooltip").remove();$(".node").attr("data-original-title",function(){var o=this.__data__,n=o.annotation||"None";return o?(o.name?o.name+"<br/>":"")+"Dist: "+o.dist+" <br/>Annotation: "+n:""}).tooltip({placement:"top",trigger:"hover"})}});var a=d.extend({initialize:function(o){var n=this;n.margins=o.margins;n.layoutMode="Linear";n.stdInit(o);n.layout();n.updateAndRender(n.model.root)},layout:function(){var n=this;n.tree=new h().layoutMode("Linear");n.diagonal=m.svg.diagonal().projection(function(o){return[o.y,o.x]})},renderNodes:function(n){var u=this,v=u.model.get("fontSize")+"px";u.tree.separation(u.model.get("separation")).leafHeight(u.model.get("leafHeight"));var q=500,o=u.tree.separation(u.model.get("separation")).nodes(u.model.root);var p=u.vis.selectAll("g.node").data(o,function(w){return w.name+w.id||(w.id=++u.i)});u.nodes=o;u.duration=q;var r=p.enter().append("svg:g").attr("class","node").on("dblclick",function(){m.event.stopPropagation()}).on("click",function(w){if(m.event.altKey){u.selectNode(w)}else{if(w.children&&w.children.length===0){return}u.model.toggle(w);u.updateAndRender(w)}});r.attr("transform",function(w){return"translate("+n.y0+","+n.x0+")"});r.append("svg:circle").attr("r",0.000001).style("fill",function(w){return w._children?"lightsteelblue":"#fff"});r.append("svg:text").attr("class","nodeLabel").attr("x",function(w){return w.children||w._children?-10:10}).attr("dy",".35em").attr("text-anchor",function(w){return w.children||w._children?"end":"start"}).style("fill-opacity",0.000001);var s=p.transition().duration(q);s.attr("transform",function(w){return"translate("+w.y+","+w.x+")"});s.select("circle").attr("r",u.defaults.nodeRadius).style("fill",function(w){return w._children?"lightsteelblue":"#fff"});s.select("text").style("fill-opacity",1).style("font-size",v).text(function(w){return w.name});var t=p.exit().transition().duration(q).remove();t.select("circle").attr("r",0.000001);t.select("text").style("fill-opacity",0.000001);o.forEach(function(w){w.x0=w.x;w.y0=w.y})}});var j=Backbone.View.extend({className:"phyloviz",initialize:function(o){var n=this;n.MIN_SCALE=0.05;n.MAX_SCALE=5;n.MAX_DISPLACEMENT=500;n.margins=[10,60,10,80];n.width=$("#PhyloViz").width();n.height=$("#PhyloViz").height();n.radius=n.width;n.data=o.data;$(window).resize(function(){n.width=$("#PhyloViz").width();n.height=$("#PhyloViz").height();n.render()});n.phyloTree=new b(o.config);n.phyloTree.root=n.data;n.zoomFunc=m.behavior.zoom().scaleExtent([n.MIN_SCALE,n.MAX_SCALE]);n.zoomFunc.translate(n.phyloTree.get("translate"));n.zoomFunc.scale(n.phyloTree.get("scaleFactor"));n.navMenu=new c(n);n.settingsMenu=new i({phyloTree:n.phyloTree});n.nodeSelectionView=new e({phyloTree:n.phyloTree});n.search=new k();setTimeout(function(){n.zoomAndPan()},1000)},render:function(){var o=this;$("#PhyloViz").empty();o.mainSVG=m.select("#PhyloViz").append("svg:svg").attr("width",o.width).attr("height",o.height).attr("pointer-events","all").call(o.zoomFunc.on("zoom",function(){o.zoomAndPan()}));o.boundingRect=o.mainSVG.append("svg:rect").attr("class","boundingRect").attr("width",o.width).attr("height",o.height).attr("stroke","black").attr("fill","white");o.vis=o.mainSVG.append("svg:g").attr("class","vis");o.layoutOptions={model:o.phyloTree,width:o.width,height:o.height,vis:o.vis,margins:o.margins};$("#title").text("Phylogenetic Tree from "+o.phyloTree.get("title")+":");var n=new a(o.layoutOptions)},zoomAndPan:function(n){var t,p;if(typeof n!=="undefined"){t=n.zoom;p=n.translate}var w=this,r=w.zoomFunc.scale(),v=w.zoomFunc.translate(),s="",u="";switch(t){case"reset":r=1;v=[0,0];break;case"+":r*=1.1;break;case"-":r*=0.9;break;default:if(typeof t==="number"){r=t}else{if(m.event!==null){r=m.event.scale}}}if(r<w.MIN_SCALE||r>w.MAX_SCALE){return}w.zoomFunc.scale(r);s="translate("+w.margins[3]+","+w.margins[0]+") scale("+r+")";if(m.event!==null){u="translate("+m.event.translate+")"}else{if(typeof p!=="undefined"){var q=p.split(",")[0];var o=p.split(",")[1];if(!isNaN(q)&&!isNaN(o)){v=[v[0]+parseFloat(q),v[1]+parseFloat(o)]}}w.zoomFunc.translate(v);u="translate("+v+")"}w.phyloTree.set("scaleFactor",r);w.phyloTree.set("translate",v);w.vis.attr("transform",u+s)},reloadViz:function(){var n=this,o=$("#phylovizNexSelector :selected").val();$.getJSON(n.phyloTree.get("dataset").url(),{tree_index:o,data_type:"raw_data"},function(p){n.data=p.data;n.config=p;n.render()})}});var c=Backbone.View.extend({initialize:function(o){var n=this;n.phylovizView=o;$("#panelHeaderRightBtns").empty();$("#phyloVizNavBtns").empty();$("#phylovizNexSelector").off();n.initNavBtns();n.initRightHeaderBtns();$("#phylovizNexSelector").off().on("change",function(){n.phylovizView.reloadViz()})},initRightHeaderBtns:function(){var n=this;rightMenu=create_icon_buttons_menu([{icon_class:"gear",title:"PhyloViz Settings",on_click:function(){$("#SettingsMenu").show();n.settingsMenu.updateUI()}},{icon_class:"disk",title:"Save visualization",on_click:function(){var o=$("#phylovizNexSelector option:selected").text();if(o){n.phylovizView.phyloTree.set("title",o)}n.phylovizView.phyloTree.save()}},{icon_class:"chevron-expand",title:"Search / Edit Nodes",on_click:function(){$("#nodeSelectionView").show()}},{icon_class:"information",title:"Phyloviz Help",on_click:function(){window.open("http://wiki.g2.bx.psu.edu/Learn/Visualization/PhylogeneticTree")}}],{tooltip_config:{placement:"bottom"}});$("#panelHeaderRightBtns").append(rightMenu.$el)},initNavBtns:function(){var n=this,o=create_icon_buttons_menu([{icon_class:"zoom-in",title:"Zoom in",on_click:function(){n.phylovizView.zoomAndPan({zoom:"+"})}},{icon_class:"zoom-out",title:"Zoom out",on_click:function(){n.phylovizView.zoomAndPan({zoom:"-"})}},{icon_class:"arrow-circle",title:"Reset Zoom/Pan",on_click:function(){n.phylovizView.zoomAndPan({zoom:"reset"})}}],{tooltip_config:{placement:"bottom"}});$("#phyloVizNavBtns").append(o.$el)}});var i=l.extend({className:"Settings",initialize:function(o){var n=this;n.phyloTree=o.phyloTree;n.el=$("#SettingsMenu");n.inputs={separation:$("#phyloVizTreeSeparation"),leafHeight:$("#phyloVizTreeLeafHeight"),fontSize:$("#phyloVizTreeFontSize")};$("#settingsCloseBtn").off().on("click",function(){n.el.hide()});$("#phylovizResetSettingsBtn").off().on("click",function(){n.resetToDefaults()});$("#phylovizApplySettingsBtn").off().on("click",function(){n.apply()})},apply:function(){var n=this;if(!n.isAcceptableValue(n.inputs.separation,50,2500)||!n.isAcceptableValue(n.inputs.leafHeight,5,30)||!n.isAcceptableValue(n.inputs.fontSize,5,20)){return}$.each(n.inputs,function(o,p){n.phyloTree.set(o,p.val())})},updateUI:function(){var n=this;$.each(n.inputs,function(o,p){p.val(n.phyloTree.get(o))})},resetToDefaults:function(){$(".bs-tooltip").remove();var n=this;$.each(n.phyloTree.defaults,function(o,p){n.phyloTree.set(o,p)});n.updateUI()},render:function(){}});var e=l.extend({className:"Settings",initialize:function(o){var n=this;n.el=$("#nodeSelectionView");n.phyloTree=o.phyloTree;n.UI={enableEdit:$("#phylovizEditNodesCheck"),saveChanges:$("#phylovizNodeSaveChanges"),cancelChanges:$("#phylovizNodeCancelChanges"),name:$("#phyloVizSelectedNodeName"),dist:$("#phyloVizSelectedNodeDist"),annotation:$("#phyloVizSelectedNodeAnnotation")};n.valuesOfConcern={name:null,dist:null,annotation:null};$("#nodeSelCloseBtn").off().on("click",function(){n.el.hide()});n.UI.saveChanges.off().on("click",function(){n.updateNodes()});n.UI.cancelChanges.off().on("click",function(){n.cancelChanges()});(function(p){p.fn.enable=function(q){return p(this).each(function(){if(q){p(this).removeAttr("disabled")}else{p(this).attr("disabled","disabled")}})}})(jQuery);n.UI.enableEdit.off().on("click",function(){n.toggleUI()})},toggleUI:function(){var n=this,o=n.UI.enableEdit.is(":checked");if(!o){n.cancelChanges()}$.each(n.valuesOfConcern,function(p,q){n.UI[p].enable(o)});if(o){n.UI.saveChanges.show();n.UI.cancelChanges.show()}else{n.UI.saveChanges.hide();n.UI.cancelChanges.hide()}},cancelChanges:function(){var n=this,o=n.phyloTree.get("selectedNode");if(o){$.each(n.valuesOfConcern,function(p,q){n.UI[p].val(o[p])})}},updateNodes:function(){var n=this,o=n.phyloTree.get("selectedNode");if(o){if(!n.isAcceptableValue(n.UI.dist,0,1)||n.hasIllegalJsonCharacters(n.UI.name)||n.hasIllegalJsonCharacters(n.UI.annotation)){return}$.each(n.valuesOfConcern,function(p,q){(o[p])=n.UI[p].val()});n.phyloTree.set("nodeAttrChangedTime",new Date())}else{alert("No node selected")}}});var k=l.extend({initialize:function(){var n=this;$("#phyloVizSearchBtn").on("click",function(){var p=$("#phyloVizSearchTerm"),q=$("#phyloVizSearchCondition").val().split("-"),o=q[0],r=q[1];n.hasIllegalJsonCharacters(p);if(o==="dist"){n.isAcceptableValue(p,0,1)}n.searchTree(o,r,p.val())})},searchTree:function(n,p,o){m.selectAll("g.node").classed("searchHighlight",function(r){var q=r[n];if(typeof q!=="undefined"&&q!==null){if(n==="dist"){switch(p){case"greaterEqual":return q>=+o;case"lesserEqual":return q<=+o;default:return}}else{if(n==="name"||n==="annotation"){return q.toLowerCase().indexOf(o.toLowerCase())!==-1}}}})}});return{PhylovizView:j}}); \ No newline at end of file +define(["libs/d3","viz/visualization","mvc/data"],function(m,f,g){var l=Backbone.View.extend({className:"UserMenuBase",isAcceptableValue:function(q,o,n){var r=q.val(),s=q.attr("displayLabel")||q.attr("id").replace("phyloViz","");function p(t){return !isNaN(parseFloat(t))&&isFinite(t)}if(!p(r)){alert(s+" is not a number!");return false}if(r>n){alert(s+" is too large.");return false}else{if(r<o){alert(s+" is too small.");return false}}return true},hasIllegalJsonCharacters:function(n){if(n.val().search(/"|'|\\/)!==-1){alert("Named fields cannot contain these illegal characters: double quote(\"), single guote('), or back slash(\\). ");return true}return false}});function h(){var w=this,r=m.layout.hierarchy().sort(null).value(null),v=360,q="Linear",u=18,s=200,t=0,p=0.5,n=50;w.leafHeight=function(x){if(typeof x==="undefined"){return u}else{u=x;return w}};w.layoutMode=function(x){if(typeof x==="undefined"){return q}else{q=x;return w}};w.layoutAngle=function(x){if(typeof x==="undefined"){return v}if(isNaN(x)||x<0||x>360){return w}else{v=x;return w}};w.separation=function(x){if(typeof x==="undefined"){return s}else{s=x;return w}};w.links=function(x){return m.layout.tree().links(x)};w.nodes=function(A,y){if(toString.call(A)==="[object Array]"){A=A[0]}var z=r.call(w,A,y),x=[],C=0,B=0;window._d=A;window._nodes=z;z.forEach(function(D){C=D.depth>C?D.depth:C;x.push(D)});x.forEach(function(D){if(!D.children){B+=1;D.depth=C}});u=q==="Circular"?v/B:u;t=0;o(x[0],C,u,null);return x};function o(B,D,A,z){var y=B.children,x=0;var C=B.dist||p;C=C>1?1:C;B.dist=C;if(z!==null){B.y0=z.y0+C*s}else{B.y0=n}if(!y){B.x0=t*A;t+=1}else{y.forEach(function(E){E.parent=B;x+=o(E,D,A,B)});B.x0=x/y.length}B.x=B.x0;B.y=B.y0;return B.x0}return w}var b=f.Visualization.extend({defaults:{layout:"Linear",separation:250,leafHeight:18,type:"phyloviz",title:"Title",scaleFactor:1,translate:[0,0],fontSize:12,selectedNode:null,nodeAttrChangedTime:0},initialize:function(n){this.set("dataset",new g.Dataset({id:n.dataset_id}))},root:{},toggle:function(n){if(typeof n==="undefined"){return}if(n.children){n._children=n.children;n.children=null}else{n.children=n._children;n._children=null}},toggleAll:function(n){if(n.children&&n.children.length!==0){n.children.forEach(this.toggleAll);toggle(n)}},getData:function(){return this.root},save:function(){var n=this.root;o(n);this.set("root",n);function o(q){delete q.parent;if(q._selected){delete q._selected}if(q.children){q.children.forEach(o)}if(q._children){q._children.forEach(o)}}var p=jQuery.extend(true,{},this.attributes);p.selectedNode=null;show_message("Saving to Galaxy","progress");return $.ajax({url:this.url(),type:"POST",dataType:"json",data:{vis_json:JSON.stringify(p)},success:function(q){var r=q.url.split("id=")[1].split("&")[0],s="/visualization?id="+r;window.history.pushState({},"",s+window.location.hash);hide_modal()}})}});var d=Backbone.View.extend({defaults:{nodeRadius:4.5},stdInit:function(o){var n=this;n.model.on("change:separation change:leafHeight change:fontSize change:nodeAttrChangedTime",n.updateAndRender,n);n.vis=o.vis;n.i=0;n.maxDepth=-1;n.width=o.width;n.height=o.height},updateAndRender:function(p){var o=m.select(".vis"),n=this;p=p||n.model.root;n.renderNodes(p);n.renderLinks(p);n.addTooltips()},renderLinks:function(n){var w=this;var o=w.diagonal;var p=w.duration;var r=w.layoutMode;var t=w.vis.selectAll("g.completeLink").data(w.tree.links(w.nodes),function(x){return x.target.id});var v=function(x){x.pos0=x.source.y0+" "+x.source.x0;x.pos1=x.source.y0+" "+x.target.x0;x.pos2=x.target.y0+" "+x.target.x0};var u=t.enter().insert("svg:g","g.node").attr("class","completeLink");u.append("svg:path").attr("class","link").attr("d",function(x){v(x);return"M "+x.pos0+" L "+x.pos1});var s=t.transition().duration(500);s.select("path.link").attr("d",function(x){v(x);return"M "+x.pos0+" L "+x.pos1+" L "+x.pos2});var q=t.exit().remove()},selectNode:function(o){var n=this;m.selectAll("g.node").classed("selectedHighlight",function(p){if(o.id===p.id){if(o._selected){delete o._selected;return false}else{o._selected=true;return true}}return false});n.model.set("selectedNode",o);$("#phyloVizSelectedNodeName").val(o.name);$("#phyloVizSelectedNodeDist").val(o.dist);$("#phyloVizSelectedNodeAnnotation").val(o.annotation||"")},addTooltips:function(){$(".bs-tooltip").remove();$(".node").attr("data-original-title",function(){var o=this.__data__,n=o.annotation||"None";return o?(o.name?o.name+"<br/>":"")+"Dist: "+o.dist+" <br/>Annotation: "+n:""}).tooltip({placement:"top",trigger:"hover"})}});var a=d.extend({initialize:function(o){var n=this;n.margins=o.margins;n.layoutMode="Linear";n.stdInit(o);n.layout();n.updateAndRender(n.model.root)},layout:function(){var n=this;n.tree=new h().layoutMode("Linear");n.diagonal=m.svg.diagonal().projection(function(o){return[o.y,o.x]})},renderNodes:function(n){var u=this,v=u.model.get("fontSize")+"px";u.tree.separation(u.model.get("separation")).leafHeight(u.model.get("leafHeight"));var q=500,o=u.tree.separation(u.model.get("separation")).nodes(u.model.root);var p=u.vis.selectAll("g.node").data(o,function(w){return w.name+w.id||(w.id=++u.i)});u.nodes=o;u.duration=q;var r=p.enter().append("svg:g").attr("class","node").on("dblclick",function(){m.event.stopPropagation()}).on("click",function(w){if(m.event.altKey){u.selectNode(w)}else{if(w.children&&w.children.length===0){return}u.model.toggle(w);u.updateAndRender(w)}});console.debug("source:",n);if(toString.call(n)==="[object Array]"){n=n[0]}r.attr("transform",function(w){return"translate("+n.y0+","+n.x0+")"});r.append("svg:circle").attr("r",0.000001).style("fill",function(w){return w._children?"lightsteelblue":"#fff"});r.append("svg:text").attr("class","nodeLabel").attr("x",function(w){return w.children||w._children?-10:10}).attr("dy",".35em").attr("text-anchor",function(w){return w.children||w._children?"end":"start"}).style("fill-opacity",0.000001);var s=p.transition().duration(q);s.attr("transform",function(w){return"translate("+w.y+","+w.x+")"});s.select("circle").attr("r",u.defaults.nodeRadius).style("fill",function(w){return w._children?"lightsteelblue":"#fff"});s.select("text").style("fill-opacity",1).style("font-size",v).text(function(w){return w.name});var t=p.exit().transition().duration(q).remove();t.select("circle").attr("r",0.000001);t.select("text").style("fill-opacity",0.000001);o.forEach(function(w){w.x0=w.x;w.y0=w.y})}});var j=Backbone.View.extend({className:"phyloviz",initialize:function(o){var n=this;n.MIN_SCALE=0.05;n.MAX_SCALE=5;n.MAX_DISPLACEMENT=500;n.margins=[10,60,10,80];n.width=$("#PhyloViz").width();n.height=$("#PhyloViz").height();n.radius=n.width;n.data=o.data;$(window).resize(function(){n.width=$("#PhyloViz").width();n.height=$("#PhyloViz").height();n.render()});n.phyloTree=new b(o.config);n.phyloTree.root=n.data;n.zoomFunc=m.behavior.zoom().scaleExtent([n.MIN_SCALE,n.MAX_SCALE]);n.zoomFunc.translate(n.phyloTree.get("translate"));n.zoomFunc.scale(n.phyloTree.get("scaleFactor"));n.navMenu=new c(n);n.settingsMenu=new i({phyloTree:n.phyloTree});n.nodeSelectionView=new e({phyloTree:n.phyloTree});n.search=new k();setTimeout(function(){n.zoomAndPan()},1000)},render:function(){var o=this;$("#PhyloViz").empty();o.mainSVG=m.select("#PhyloViz").append("svg:svg").attr("width",o.width).attr("height",o.height).attr("pointer-events","all").call(o.zoomFunc.on("zoom",function(){o.zoomAndPan()}));o.boundingRect=o.mainSVG.append("svg:rect").attr("class","boundingRect").attr("width",o.width).attr("height",o.height).attr("stroke","black").attr("fill","white");o.vis=o.mainSVG.append("svg:g").attr("class","vis");o.layoutOptions={model:o.phyloTree,width:o.width,height:o.height,vis:o.vis,margins:o.margins};$("#title").text("Phylogenetic Tree from "+o.phyloTree.get("title")+":");var n=new a(o.layoutOptions)},zoomAndPan:function(n){var t,p;if(typeof n!=="undefined"){t=n.zoom;p=n.translate}var w=this,r=w.zoomFunc.scale(),v=w.zoomFunc.translate(),s="",u="";switch(t){case"reset":r=1;v=[0,0];break;case"+":r*=1.1;break;case"-":r*=0.9;break;default:if(typeof t==="number"){r=t}else{if(m.event!==null){r=m.event.scale}}}if(r<w.MIN_SCALE||r>w.MAX_SCALE){return}w.zoomFunc.scale(r);s="translate("+w.margins[3]+","+w.margins[0]+") scale("+r+")";if(m.event!==null){u="translate("+m.event.translate+")"}else{if(typeof p!=="undefined"){var q=p.split(",")[0];var o=p.split(",")[1];if(!isNaN(q)&&!isNaN(o)){v=[v[0]+parseFloat(q),v[1]+parseFloat(o)]}}w.zoomFunc.translate(v);u="translate("+v+")"}w.phyloTree.set("scaleFactor",r);w.phyloTree.set("translate",v);w.vis.attr("transform",u+s)},reloadViz:function(){var n=this,o=$("#phylovizNexSelector :selected").val();$.getJSON(n.phyloTree.get("dataset").url(),{tree_index:o,data_type:"raw_data"},function(p){n.data=p.data;n.config=p;n.render()})}});var c=Backbone.View.extend({initialize:function(o){var n=this;n.phylovizView=o;$("#panelHeaderRightBtns").empty();$("#phyloVizNavBtns").empty();$("#phylovizNexSelector").off();n.initNavBtns();n.initRightHeaderBtns();$("#phylovizNexSelector").off().on("change",function(){n.phylovizView.reloadViz()})},initRightHeaderBtns:function(){var n=this;rightMenu=create_icon_buttons_menu([{icon_class:"gear",title:"PhyloViz Settings",on_click:function(){$("#SettingsMenu").show();n.settingsMenu.updateUI()}},{icon_class:"disk",title:"Save visualization",on_click:function(){var o=$("#phylovizNexSelector option:selected").text();if(o){n.phylovizView.phyloTree.set("title",o)}n.phylovizView.phyloTree.save()}},{icon_class:"chevron-expand",title:"Search / Edit Nodes",on_click:function(){$("#nodeSelectionView").show()}},{icon_class:"information",title:"Phyloviz Help",on_click:function(){window.open("http://wiki.g2.bx.psu.edu/Learn/Visualization/PhylogeneticTree")}}],{tooltip_config:{placement:"bottom"}});$("#panelHeaderRightBtns").append(rightMenu.$el)},initNavBtns:function(){var n=this,o=create_icon_buttons_menu([{icon_class:"zoom-in",title:"Zoom in",on_click:function(){n.phylovizView.zoomAndPan({zoom:"+"})}},{icon_class:"zoom-out",title:"Zoom out",on_click:function(){n.phylovizView.zoomAndPan({zoom:"-"})}},{icon_class:"arrow-circle",title:"Reset Zoom/Pan",on_click:function(){n.phylovizView.zoomAndPan({zoom:"reset"})}}],{tooltip_config:{placement:"bottom"}});$("#phyloVizNavBtns").append(o.$el)}});var i=l.extend({className:"Settings",initialize:function(o){var n=this;n.phyloTree=o.phyloTree;n.el=$("#SettingsMenu");n.inputs={separation:$("#phyloVizTreeSeparation"),leafHeight:$("#phyloVizTreeLeafHeight"),fontSize:$("#phyloVizTreeFontSize")};$("#settingsCloseBtn").off().on("click",function(){n.el.hide()});$("#phylovizResetSettingsBtn").off().on("click",function(){n.resetToDefaults()});$("#phylovizApplySettingsBtn").off().on("click",function(){n.apply()})},apply:function(){var n=this;if(!n.isAcceptableValue(n.inputs.separation,50,2500)||!n.isAcceptableValue(n.inputs.leafHeight,5,30)||!n.isAcceptableValue(n.inputs.fontSize,5,20)){return}$.each(n.inputs,function(o,p){n.phyloTree.set(o,p.val())})},updateUI:function(){var n=this;$.each(n.inputs,function(o,p){p.val(n.phyloTree.get(o))})},resetToDefaults:function(){$(".bs-tooltip").remove();var n=this;$.each(n.phyloTree.defaults,function(o,p){n.phyloTree.set(o,p)});n.updateUI()},render:function(){}});var e=l.extend({className:"Settings",initialize:function(o){var n=this;n.el=$("#nodeSelectionView");n.phyloTree=o.phyloTree;n.UI={enableEdit:$("#phylovizEditNodesCheck"),saveChanges:$("#phylovizNodeSaveChanges"),cancelChanges:$("#phylovizNodeCancelChanges"),name:$("#phyloVizSelectedNodeName"),dist:$("#phyloVizSelectedNodeDist"),annotation:$("#phyloVizSelectedNodeAnnotation")};n.valuesOfConcern={name:null,dist:null,annotation:null};$("#nodeSelCloseBtn").off().on("click",function(){n.el.hide()});n.UI.saveChanges.off().on("click",function(){n.updateNodes()});n.UI.cancelChanges.off().on("click",function(){n.cancelChanges()});(function(p){p.fn.enable=function(q){return p(this).each(function(){if(q){p(this).removeAttr("disabled")}else{p(this).attr("disabled","disabled")}})}})(jQuery);n.UI.enableEdit.off().on("click",function(){n.toggleUI()})},toggleUI:function(){var n=this,o=n.UI.enableEdit.is(":checked");if(!o){n.cancelChanges()}$.each(n.valuesOfConcern,function(p,q){n.UI[p].enable(o)});if(o){n.UI.saveChanges.show();n.UI.cancelChanges.show()}else{n.UI.saveChanges.hide();n.UI.cancelChanges.hide()}},cancelChanges:function(){var n=this,o=n.phyloTree.get("selectedNode");if(o){$.each(n.valuesOfConcern,function(p,q){n.UI[p].val(o[p])})}},updateNodes:function(){var n=this,o=n.phyloTree.get("selectedNode");if(o){if(!n.isAcceptableValue(n.UI.dist,0,1)||n.hasIllegalJsonCharacters(n.UI.name)||n.hasIllegalJsonCharacters(n.UI.annotation)){return}$.each(n.valuesOfConcern,function(p,q){(o[p])=n.UI[p].val()});n.phyloTree.set("nodeAttrChangedTime",new Date())}else{alert("No node selected")}}});var k=l.extend({initialize:function(){var n=this;$("#phyloVizSearchBtn").on("click",function(){var p=$("#phyloVizSearchTerm"),q=$("#phyloVizSearchCondition").val().split("-"),o=q[0],r=q[1];n.hasIllegalJsonCharacters(p);if(o==="dist"){n.isAcceptableValue(p,0,1)}n.searchTree(o,r,p.val())})},searchTree:function(n,p,o){m.selectAll("g.node").classed("searchHighlight",function(r){var q=r[n];if(typeof q!=="undefined"&&q!==null){if(n==="dist"){switch(p){case"greaterEqual":return q>=+o;case"lesserEqual":return q<=+o;default:return}}else{if(n==="name"||n==="annotation"){return q.toLowerCase().indexOf(o.toLowerCase())!==-1}}}})}});return{PhylovizView:j}}); \ No newline at end of file diff -r 137dc44f21782c771879af1febfe962a2ea9ae6a -r 82f83d00dfd419c035c76b100ec0e88a10bb900c static/scripts/viz/phyloviz.js --- a/static/scripts/viz/phyloviz.js +++ b/static/scripts/viz/phyloviz.js @@ -1,18 +1,18 @@ define(['libs/d3', 'viz/visualization', 'mvc/data'], function(d3, visualization_mod, data_mod) { +/** + * Base class of any menus that takes in user interaction. Contains checking methods. + */ var UserMenuBase = Backbone.View.extend({ - /** - * Base class of any menus that takes in user interaction. Contains checking methods. - */ className: 'UserMenuBase', + /** + * Check if an input value is a number and falls within max min. + */ isAcceptableValue : function ($inputKey, min, max) { - /** - * Check if an input value is a number and falls within max min. - */ - var self = this, - value = $inputKey.val(), + //TODO: use better feedback than alert + var value = $inputKey.val(), fieldName = $inputKey.attr("displayLabel") || $inputKey.attr("id").replace("phyloViz", ""); function isNumeric(n) { @@ -34,12 +34,13 @@ return true; }, + /** + * Check if any user string inputs has illegal characters that json cannot accept + */ hasIllegalJsonCharacters : function($inputKey) { - /** - * Check if any user string inputs has illegal characters that json cannot accept - */ if ($inputKey.val().search(/"|'|\\/) !== -1){ - alert("Named fields cannot contain these illegal characters: double quote(\"), single guote(\'), or back slash(\\). "); + alert("Named fields cannot contain these illegal characters: " + + "double quote(\"), single guote(\'), or back slash(\\). "); return true; } return false; @@ -47,12 +48,12 @@ }); +/** + * -- Custom Layout call for phyloViz to suit the needs of a phylogenetic tree. + * -- Specifically: 1) Nodes have a display display of (= evo dist X depth separation) from their parent + * 2) Nodes must appear in other after they have expand and contracted + */ function PhyloTreeLayout() { - /** - * -- Custom Layout call for phyloViz to suit the needs of a phylogenetic tree. - * -- Specifically: 1) Nodes have a display display of (= evo dist X depth separation) from their parent - * 2) Nodes must appear in other after they have expand and contracted - */ var self = this, hierarchy = d3.layout.hierarchy().sort(null).value(null), @@ -75,9 +76,11 @@ else { layoutMode = mode; return self;} }; - self.layoutAngle = function(angle) { // changes the layout angle of the display, which is really changing the height + // changes the layout angle of the display, which is really changing the height + self.layoutAngle = function(angle) { if (typeof angle === "undefined"){ return height; } - if (isNaN(angle) || angle < 0 || angle > 360) { return self; } // to use default if the user puts in strange values + // to use default if the user puts in strange values + if (isNaN(angle) || angle < 0 || angle > 360) { return self; } else { height = angle; return self;} }; @@ -92,19 +95,28 @@ // -- Custom method for laying out phylogeny tree in a linear fashion self.nodes = function (d, i) { - var _nodes = hierarchy.call(self, d, i), // self is to find the depth of all the nodes, assumes root is passed in + //TODO: newick and phyloxml return arrays. where should this go (client (here, else), server)? + if( toString.call( d ) === '[object Array]' ){ + // if d is an array, replate with the first object (newick, phyloxml) + d = d[0]; + } + // self is to find the depth of all the nodes, assumes root is passed in + var _nodes = hierarchy.call(self, d, i), nodes = [], maxDepth = 0, numLeaves = 0; + //console.debug( JSON.stringify( _nodes, null, 2 ) ) + window._d = d; + window._nodes = _nodes; + //TODO: remove dbl-touch loop // changing from hierarchy's custom format for data to usable format - _nodes.forEach(function (_node){ - var node = _node.data; - node.depth = _node.depth; + _nodes.forEach(function (node){ maxDepth = node.depth > maxDepth ? node.depth : maxDepth; //finding max depth of tree nodes.push(node); }); - // counting the number of leaf nodes and assigning max depth to nodes that do not have children to flush all the leave nodes + // counting the number of leaf nodes and assigning max depth + // to nodes that do not have children to flush all the leave nodes nodes.forEach(function(node){ if ( !node.children ) { //&& !node._children numLeaves += 1; @@ -120,15 +132,16 @@ }; + /** + * -- Function with side effect of adding x0, y0 to all child; take in the root as starting point + * assuming that the leave nodes would be sorted in presented order + * horizontal(y0) is calculated according to (= evo dist X depth separation) from their parent + * vertical (x0) - if leave node: find its order in all of the leave node === node.id, + * then multiply by verticalSeparation + * - if parent node: is place in the mid point all of its children nodes + * -- The layout will first calculate the y0 field going towards the leaves, and x0 when returning + */ function layout (node, maxDepth, vertSeparation, parent) { - /** - * -- Function with side effect of adding x0, y0 to all child; take in the root as starting point - * assuming that the leave nodes would be sorted in presented order - * horizontal(y0) is calculated according to (= evo dist X depth separation) from their parent - * vertical (x0) - if leave node: find its order in all of the leave node === node.id, then multiply by verticalSeparation - * - if parent node: is place in the mid point all of its children nodes - * -- The layout will first calculate the y0 field going towards the leaves, and x0 when returning - */ var children = node.children, sumChildVertSeparation = 0; @@ -145,7 +158,8 @@ // if a node have no children, we will treat it as a leaf and start laying it out first if (!children) { - node.x0 = leafIndex++ * vertSeparation; + node.x0 = leafIndex * vertSeparation; + leafIndex += 1; } else { // if it has children, we will visit all its children and calculate its position from its children children.forEach( function (child) { @@ -189,12 +203,12 @@ root : {}, // Root has to be its own independent object because it is not part of the viz_config + /** + * Mechanism to expand or contract a single node. Expanded nodes have a children list, while for + * contracted nodes the list is stored in _children. Nodes with their children data stored in _children will not + * have their children rendered. + */ toggle : function (d) { - /** - * Mechanism to expand or contract a single node. Expanded nodes have a children list, while for - * contracted nodes the list is stored in _children. Nodes with their children data stored in _children will not have their - * children rendered. - */ if(typeof d === "undefined") {return ;} if (d.children ) { d._children = d.children; @@ -205,29 +219,29 @@ } }, + /** + * Contracts the phylotree to a single node by repeatedly calling itself to place all the list + * of children under _children. + */ toggleAll : function(d) { - /** - * Contracts the phylotree to a single node by repeatedly calling itself to place all the list - * of children under _children. - */ if (d.children && d.children.length !== 0) { d.children.forEach(this.toggleAll); toggle(d); } }, + /** + * Return the data of the tree. Used for preserving state. + */ getData : function (){ - /** - * Return the data of the tree. Used for preserving state. - */ return this.root; }, + /** + * Overriding the default save mechanism to do some clean of circular reference of the + * phyloTree and to include phyloTree in the saved json + */ save: function() { - /** - * Overriding the default save mechanism to do some clean of circular reference of the - * phyloTree and to include phyloTree in the saved json - */ var root = this.root; cleanTree(root); this.set("root", root); @@ -240,7 +254,7 @@ if (node._selected){ delete node._selected;} if (node.children) { - node.children.forEach(cleanTree); + node.children.forEach(cleanTree); } if (node._children) { node._children.forEach(cleanTree); @@ -270,26 +284,24 @@ }); - +// -- Views -- /** - * -- Views -- + * Stores the default variable for setting up the visualization */ var PhylovizLayoutBase = Backbone.View.extend({ - /** - * Stores the default variable for setting up the visualization - */ defaults : { nodeRadius : 4.5 // radius of each node in the diagram }, + /** + * Common initialization in layouts + */ stdInit : function (options) { - /** - * Common initialization in layouts - */ var self = this; - self.model.on("change:separation change:leafHeight change:fontSize change:nodeAttrChangedTime", self.updateAndRender, self); + self.model.on("change:separation change:leafHeight change:fontSize change:nodeAttrChangedTime", + self.updateAndRender, self); self.vis = options.vis; self.i = 0; @@ -300,11 +312,11 @@ }, + /** + * Updates the visualization whenever there are changes in the expansion and contraction of nodes + * AND possibly when the tree is edited. + */ updateAndRender : function(source) { - /** - * Updates the visualization whenever there are changes in the expansion and contraction of nodes - * AND possibly when the tree is edited. - */ var vis = d3.select(".vis"), self = this; source = source || self.model.root; @@ -315,10 +327,10 @@ }, + /** + * Renders the links for the visualization. + */ renderLinks : function(source) { - /** - * Renders the links for the visualization. - */ var self = this; var diagonal = self.diagonal; var duration = self.duration; @@ -327,15 +339,17 @@ .data(self.tree.links(self.nodes), function(d) { return d.target.id; }); var calcalateLinePos = function(d) { - d.pos0 = d.source.y0 + " " + d.source.x0; // position of the source node <=> starting location of the line drawn - d.pos1 = d.source.y0 + " " + d.target.x0; // position where the line makes a right angle bend - d.pos2 = d.target.y0 + " " + d.target.x0; // point where the horizontal line becomes a dotted line + // position of the source node <=> starting location of the line drawn + d.pos0 = d.source.y0 + " " + d.source.x0; + // position where the line makes a right angle bend + d.pos1 = d.source.y0 + " " + d.target.x0; + // point where the horizontal line becomes a dotted line + d.pos2 = d.target.y0 + " " + d.target.x0; }; var linkEnter = link.enter().insert("svg:g","g.node") .attr("class", "completeLink"); - linkEnter.append("svg:path") .attr("class", "link") .attr("d", function(d) { @@ -357,10 +371,10 @@ // User Interaction methods below + /** + * Displays the information for editing + */ selectNode : function(node){ - /** - * Displays the information for editting - */ var self = this; d3.selectAll("g.node") .classed("selectedHighlight", function(d){ @@ -382,11 +396,11 @@ $("#phyloVizSelectedNodeAnnotation").val(node.annotation || ""); }, + /** + * Creates bootstrap tooltip for the visualization. Has to be called repeatedly due to newly generated + * enterNodes + */ addTooltips : function (){ - /** - * Creates bootstrap tooltip for the visualization. Has to be called repeatedly due to newly generated - * enterNodes - */ $(".bs-tooltip").remove(); //clean up tooltip, just in case its listeners are removed by d3 $(".node") .attr("data-original-title", function(){ @@ -400,13 +414,11 @@ }); - - +/** + * Linea layout class of Phyloviz, is responsible for rendering the nodes + * calls PhyloTreeLayout to determine the positions of the nodes + */ var PhylovizLinearView = PhylovizLayoutBase.extend({ - /** - * Linea layout class of Phyloviz, is responsible for rendering the nodes - * calls PhyloTreeLayout to determine the positions of the nodes - */ initialize : function(options){ // Default values of linear layout var self = this; @@ -419,23 +431,21 @@ self.updateAndRender(self.model.root); }, + /** + * Creates the basic layout of a linear tree by precalculating fixed values. + * One of calculations are also made here + */ layout : function() { - /** - * Creates the basic layout of a linear tree by precalculating fixed values. - * One of calculations are also made here - */ - var self = this; - self.tree = new PhyloTreeLayout().layoutMode("Linear"); self.diagonal = d3.svg.diagonal() .projection(function(d) { return [d.y, d.x ]; }); }, + /** + * Renders the nodes base on Linear layout. + */ renderNodes : function (source) { - /** - * Renders the nodes base on Linear layout. - */ var self = this, fontSize = self.model.get("fontSize") + "px"; @@ -446,7 +456,9 @@ nodes = self.tree.separation(self.model.get("separation")).nodes(self.model.root); var node = self.vis.selectAll("g.node") - .data(nodes, function(d) { return d.name + d.id || (d.id = ++self.i); }); + .data(nodes, function(d) { + return d.name + d.id || (d.id = ++self.i); + }); // These variables has to be passed into update links which are in the base methods self.nodes = nodes; @@ -466,7 +478,12 @@ self.updateAndRender(d); // re-render the tree } }); - + console.debug( 'source:', source ) + //TODO: newick and phyloxml return arrays. where should this go (client (here, else), server)? + if( toString.call( source ) === '[object Array]' ){ + // if d is an array, replate with the first object (newick, phyloxml) + source = source[0]; + } nodeEnter.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; }); nodeEnter.append("svg:circle") @@ -557,8 +574,8 @@ self.nodeSelectionView = new NodeSelectionView({phyloTree : self.phyloTree}); self.search = new PhyloVizSearch(); - - setTimeout(function(){ // using settimeout to call the zoomAndPan function according to the stored attributes in viz_config + // using settimeout to call the zoomAndPan function according to the stored attributes in viz_config + setTimeout(function(){ self.zoomAndPan(); }, 1000); }, @@ -603,11 +620,11 @@ var linearView = new PhylovizLinearView(self.layoutOptions); }, + /** + * Function to zoom and pan the svg element which the entire tree is contained within + * Uses d3.zoom events, and extend them to allow manual updates and keeping states in model + */ zoomAndPan : function(event){ - /** - * Function to zoom and pan the svg element which the entire tree is contained within - * Uses d3.zoom events, and extend them to allow manual updates and keeping states in model - */ var zoomParams, translateParams; if (typeof event !== "undefined") { @@ -659,21 +676,26 @@ self.phyloTree.set("scaleFactor", scaleFactor); self.phyloTree.set("translate", translationCoor); - self.vis.attr("transform", translateStatement + zoomStatement); //refers to the view that we are actually zooming + //refers to the view that we are actually zooming + self.vis.attr("transform", translateStatement + zoomStatement); }, + /** + * Primes the Ajax URL to load another Nexus tree + */ reloadViz : function() { - /** - * Primes the Ajax URL to load another Nexus tree - */ var self = this, treeIndex = $("#phylovizNexSelector :selected").val(); - $.getJSON(self.phyloTree.get("dataset").url(), { tree_index: treeIndex, data_type: 'raw_data' }, function(packedJson){ - self.data = packedJson.data; - self.config = packedJson; - self.render(); - }); + $.getJSON(self.phyloTree.get("dataset").url(), { + tree_index: treeIndex, + data_type: 'raw_data' + }, + function(packedJson){ + self.data = packedJson.data; + self.config = packedJson; + self.render(); + }); } }); @@ -768,10 +790,10 @@ $("#phylovizApplySettingsBtn").off().on("click", function() { self.apply(); }); }, + /** + * Applying user values to phylotree model. + */ apply : function(){ - /** - * Applying user values to phylotree model. - */ var self = this; if (!self.isAcceptableValue(self.inputs.separation, 50, 2500) || !self.isAcceptableValue(self.inputs.leafHeight, 5, 30) || @@ -782,19 +804,19 @@ self.phyloTree.set(key, $input.val()); }); }, + /** + * Called to update the values input to that stored in the model + */ updateUI : function(){ - /** - * Called to update the values input to that stored in the model - */ var self = this; $.each(self.inputs, function(key, $input){ $input.val(self.phyloTree.get(key)); }); }, + /** + * Resets the value of the phyloTree model to its default + */ resetToDefaults : function(){ - /** - * Resets the value of the phyloTree model to its default - */ $(".bs-tooltip").remove(); // just in case the tool tip was not removed var self = this; $.each(self.phyloTree.defaults, function(key, value) { @@ -810,10 +832,11 @@ }); +/** + * View for inspecting node properties and editing them + */ var NodeSelectionView = UserMenuBase.extend({ - /** - * View for inspecting node properties and editing them - */ + className: 'Settings', initialize : function (options){ @@ -830,11 +853,12 @@ annotation : $("#phyloVizSelectedNodeAnnotation") }; + // temporarily stores the values in case user change their mind self.valuesOfConcern = { name : null, dist : null, annotation : null - }; // temporarily stores the values in case user change their mind + }; //init UI buttons $("#nodeSelCloseBtn").off().on("click", function() { self.el.hide(); }); @@ -859,10 +883,10 @@ }); }, + /** + * For turning on and off the child elements + */ toggleUI : function(){ - /** - * For turning on and off the child elements - */ var self = this, checked = self.UI.enableEdit.is(':checked'); @@ -881,10 +905,10 @@ }, + /** + * Reverting to previous values in case user change their minds + */ cancelChanges : function() { - /** - * Reverting to previous values in case user change their minds - */ var self = this, node = self.phyloTree.get("selectedNode"); if (node){ @@ -894,10 +918,10 @@ } }, + /** + * Changing the data in the underlying tree with user-specified values + */ updateNodes : function (){ - /** - * Changing the data in the underlying tree with user-specified values - */ var self = this, node = self.phyloTree.get("selectedNode"); if (node){ @@ -914,17 +938,15 @@ alert("No node selected"); } } - - }); +/** + * Initializes the search panel on phyloviz and handles its user interaction + * It allows user to search the entire free based on some qualifer, like dist <= val. + */ var PhyloVizSearch = UserMenuBase.extend({ - /** - * Initializes the search panel on phyloviz and handles its user interaction - * It allows user to search the entire free based on some qualifer, like dist <= val. - */ initialize : function () { var self = this; @@ -942,10 +964,10 @@ }); }, + /** + * Searches the entire tree and will highlight the nodes that match the condition in green + */ searchTree : function (attr, condition, val){ - /** - * Searches the entire tree and will highlight the nodes that match the condition in green - */ d3.selectAll("g.node") .classed("searchHighlight", function(d){ var attrVal = d[attr]; @@ -972,4 +994,4 @@ PhylovizView: PhylovizView }; -}); \ No newline at end of file +}); diff -r 137dc44f21782c771879af1febfe962a2ea9ae6a -r 82f83d00dfd419c035c76b100ec0e88a10bb900c templates/webapps/galaxy/visualization/phyloviz.mako --- a/templates/webapps/galaxy/visualization/phyloviz.mako +++ b/templates/webapps/galaxy/visualization/phyloviz.mako @@ -81,7 +81,7 @@ position: fixed; ## Borrowed from galaxy modal_dialogues - background-color: white; + background-color: white; border: 1px solid #999; border: 1px solid rgba(0, 0, 0, 0.3); -webkit-border-radius: 6px; @@ -157,9 +157,9 @@ // -- Initialization code |--> phyloviz = new phyloviz_mod.PhylovizView({ - data: data, - layout : "Linear", - config : config + data : data, + layout : "Linear", + config : config }); // -- Render viz. -- @@ -169,7 +169,9 @@ $(function firstVizLoad(){ // calls when viz is loaded for the first time var config = JSON.parse( '${ h.to_json_string( config )}'); + window.config = config; var data = JSON.parse('${h.to_json_string(data)}'); + window.data = data; initPhyloViz(data, config); }); }); @@ -189,7 +191,6 @@ <div style="clear: both"></div></div> - <div id="phyloVizNavContainer"><div id="phyloVizNav"> %if config["ext"] == "nex" and not config["saved_visualization"]: @@ -208,10 +209,7 @@ <div class="navControl"><p> | Alt+click to select nodes</p></div> - - </div> - </div> ## Node Selection Menu @@ -284,11 +282,6 @@ </div></div> - - - - - <div class="Panel" id="FloatingMenu" style="display: None;"><h2>PhyloViz (<a onclick="displayHelp()" href="javascript:void(0);">?</a>)</h2> @@ -300,14 +293,12 @@ <div class="hint">4. Minimap: Currently displays an exact but scaled down replicate of the tree, orange bounding box is correct for linear only<br/> Can be switched on or off</div><div class="hint">5. Changing Layouts: Able to change between circular and linear layouts.</div> - </div><h5>Scaling & Rotation:</h5><button id="phylovizZoomInBtn" class="" > + </button><button id="phylovizZoomOutBtn" class="" > - </button> - <h5>Translation:</h5><button id="phylovizTranslateUpBtn" > Up </button><button id="phylovizTranslateDownBtn" > Down </button> @@ -315,8 +306,6 @@ <button id="phylovizTranslateLeftBtn" > Left </button><button id="phylovizTranslateRightBtn" > Right </button> - - <h5>Others:</h5><button id="phylovizResetBtn" > Reset Zoom/Translate </button><button id="phylovizSaveBtn" > Save vizualization </button> Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.