1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/0b9bd1fdc765/
Changeset: 0b9bd1fdc765
User: Dave Bouvier
Date: 2013-05-21 18:15:17
Summary: Add an option to install and test every installable changeset revision of every repository returned by the tool shed API call. Default behavior changed to only install and test the most recent installable changeset revision of each repository.
Affected #: 1 file
diff -r 82f83d00dfd419c035c76b100ec0e88a10bb900c -r 0b9bd1fdc765d18d0831aa2ecbf419e16bbfd15b test/install_and_test_tool_shed_repositories/functional_tests.py
--- a/test/install_and_test_tool_shed_repositories/functional_tests.py
+++ b/test/install_and_test_tool_shed_repositories/functional_tests.py
@@ -225,13 +225,25 @@
url += '&%s' % params
return url
+def get_latest_downloadable_changeset_revision( url, name, owner ):
+ api_url_parts = [ 'api', 'repositories', 'get_ordered_installable_revisions' ]
+ params = urllib.urlencode( dict( name=name, owner=owner ) )
+ api_url = get_api_url( url, api_url_parts, params )
+ changeset_revisions = json_from_url( api_url )
+ if changeset_revisions:
+ return changeset_revisions[ -1 ]
+ else:
+ return '000000000000'
+
def get_repository_info_from_api( url, repository_info_dict ):
parts = [ 'api', 'repositories', repository_info_dict[ 'repository_id' ] ]
api_url = get_api_url( base=url, parts=parts )
extended_dict = json_from_url( api_url )
+ latest_changeset_revision = get_latest_downloadable_changeset_revision( url, extended_dict[ 'name' ], extended_dict[ 'owner' ] )
+ extended_dict[ 'latest_revision' ] = str( latest_changeset_revision )
return extended_dict
-def get_repositories_to_install( location, source='file', format='json' ):
+def get_repositories_to_install( tool_shed_url, latest_revision_only=True ):
'''
Get a list of repository info dicts to install. This method expects a json list of dicts with the following structure:
[
@@ -246,24 +258,49 @@
]
NOTE: If the tool shed URL specified in any dict is not present in the tool_sheds_conf.xml, the installation will fail.
'''
- if source == 'file':
- listing = file( location, 'r' ).read()
- elif source == 'url':
- assert tool_shed_api_key is not None, 'Cannot proceed without tool shed API key.'
- params = urllib.urlencode( dict( do_not_test='false',
- downloadable='true',
- malicious='false',
- includes_tools='true',
- skip_tool_test='false' ) )
- api_url = get_api_url( base=location, parts=[ 'repository_revisions' ], params=params )
- if format == 'json':
- return json_from_url( api_url )
+ assert tool_shed_api_key is not None, 'Cannot proceed without tool shed API key.'
+ params = urllib.urlencode( dict( do_not_test='false',
+ downloadable='true',
+ malicious='false',
+ includes_tools='true',
+ skip_tool_test='false' ) )
+ api_url = get_api_url( base=tool_shed_url, parts=[ 'repository_revisions' ], params=params )
+ base_repository_list = json_from_url( api_url )
+ known_repository_ids = {}
+ detailed_repository_list = []
+ for repository_to_install_dict in base_repository_list:
+ # We need to get some details from the tool shed API, such as repository name and owner, to pass on to the
+ # module that will generate the install methods.
+ repository_info_dict = get_repository_info_from_api( galaxy_tool_shed_url, repository_to_install_dict )
+ if repository_info_dict[ 'latest_revision' ] == '000000000000':
+ continue
+ owner = repository_info_dict[ 'owner' ]
+ name = repository_info_dict[ 'name' ]
+ changeset_revision = repository_to_install_dict[ 'changeset_revision' ]
+ repository_id = repository_to_install_dict[ 'repository_id' ]
+ # We are testing deprecated repositories, because it is possible that a deprecated repository contains valid
+ # and functionally correct tools that someone has previously installed. Deleted repositories have never been installed,
+ # and therefore do not need to be checked. If they are undeleted, this script will then test them the next time it runs.
+ if repository_info_dict[ 'deleted' ]:
+ log.info( "Skipping revision %s of repository id %s (%s/%s) since the repository is deleted...",
+ changeset_revision,
+ repository_id,
+ name,
+ owner )
+ continue
+ # Now merge the dict returned from /api/repository_revisions with the detailed dict we just retrieved.
+ if latest_revision_only:
+ if changeset_revision == repository_info_dict[ 'latest_revision' ]:
+ detailed_repository_list.append( dict( repository_info_dict.items() + repository_to_install_dict.items() ) )
+ else:
+ detailed_repository_list.append( dict( repository_info_dict.items() + repository_to_install_dict.items() ) )
+ repositories_tested = len( detailed_repository_list )
+ if latest_revision_only:
+ skipped_previous = ' and metadata revisions that are not the most recent'
else:
- raise AssertionError( 'Do not know how to handle source type %s.' % source )
- if format == 'json':
- return from_json_string( listing )
- else:
- raise AssertonError( 'Unknown format %s.' % format )
+ skipped_previous = ''
+ log.info( 'After removing deleted repositories%s from the list, %d remain to be tested.', skipped_previous, repositories_tested )
+ return detailed_repository_list
def get_tool_info_from_test_id( test_id ):
'''
@@ -286,13 +323,7 @@
return tool_test_results
def is_latest_downloadable_revision( url, repository_info_dict ):
- api_url_parts = [ 'api', 'repositories', 'get_ordered_installable_revisions' ]
- params = urllib.urlencode( dict( name=repository_info_dict[ 'name' ], owner=repository_info_dict[ 'owner' ] ) )
- api_url = get_api_url( url, api_url_parts, params )
- changeset_revisions = json_from_url( api_url )
- # The get_ordered_installable_revisions returns a list of changeset hashes, with the last hash in the list
- # being the most recent installable revision.
- latest_revision = changeset_revisions.pop()
+ latest_revision = get_latest_downloadable_changeset_revision( url, name=repository_info_dict[ 'name' ], owner=repository_info_dict[ 'owner' ] )
return str( repository_info_dict[ 'changeset_revision' ] ) == str( latest_revision )
def json_from_url( url ):
@@ -532,41 +563,25 @@
shed_tool_data_table_config=None,
persist=False )
# Initialize some variables for the summary that will be printed to stdout.
- repositories_tested = 0
repositories_passed = []
repositories_failed = []
repositories_failed_install = []
try:
- detailed_repository_list = []
# Get a list of repositories to test from the tool shed specified in the GALAXY_INSTALL_TEST_TOOL_SHED_URL environment variable.
log.info( "Retrieving repositories to install from the URL:\n%s\n", str( galaxy_tool_shed_url ) )
- repositories_to_install = get_repositories_to_install( galaxy_tool_shed_url, source='url' )
+ if '-check_all_revisions' not in sys.argv:
+ repositories_to_install = get_repositories_to_install( galaxy_tool_shed_url, latest_revision_only=True )
+ else:
+ repositories_to_install = get_repositories_to_install( galaxy_tool_shed_url, latest_revision_only=False )
log.info( "Retrieved %d repositories from the API.", len( repositories_to_install ) )
- for repository_to_install_dict in repositories_to_install:
- # We need to get some details from the tool shed API, such as repository name and owner, to pass on to the
- # module that will generate the install methods.
- repository_info_dict = get_repository_info_from_api( galaxy_tool_shed_url, repository_to_install_dict )
- # We are testing deprecated repositories, because it is possible that a deprecated repository contains valid
- # and functionally correct tools that someone has previously installed. Deleted repositories have never been installed,
- # and therefore do not need to be checked. If they are undeleted, this script will then test them the next time it runs.
- if repository_info_dict[ 'deleted' ]:
- log.info( "Skipping revision %s of repository id %s (%s/%s) since the repository is deleted...",
- repository_to_install_dict[ 'changeset_revision' ],
- repository_to_install_dict[ 'repository_id' ],
- repository_info_dict[ 'owner' ],
- repository_info_dict[ 'name' ] )
- continue
- # Now merge the dict returned from /api/repository_revisions with the detailed dict we just retrieved.
- detailed_repository_list.append( dict( repository_info_dict.items() + repository_to_install_dict.items() ) )
- repositories_tested = len( detailed_repository_list )
- log.info( 'After removing deleted repositories from the list, %d remain to be tested.', repositories_tested )
if '-list_repositories' in sys.argv:
log.info( "The API returned the following repositories, not counting deleted:" )
- for repository_info_dict in detailed_repository_list:
+ for repository_info_dict in repositories_to_install:
log.info( "%s owned by %s changeset revision %s",
repository_info_dict.get( 'name', None ),
repository_info_dict.get( 'owner', None ),
repository_info_dict.get( 'changeset_revision', None ) )
+ repositories_tested = len( repositories_to_install )
# This loop will iterate through the list of repositories generated by the above code, having already filtered out any
# that were marked as deleted. For each repository, it will generate a test method that will use Twill to install that
# repository into the embedded Galaxy application that was started up, selecting to install repository and tool
@@ -575,7 +590,7 @@
# it will record the result of the tests, and if any failed, the traceback and captured output of the tool that was run.
# After all tests have completed, the repository is uninstalled, so that the previous test cases don't interfere with
# the next repository's functional tests.
- for repository_info_dict in detailed_repository_list:
+ for repository_info_dict in repositories_to_install:
"""
Each repository_info_dict looks something like:
{
@@ -600,8 +615,8 @@
"""
repository_status = dict()
params = dict()
- repository_id = repository_info_dict.get( 'repository_id', None )
- changeset_revision = repository_info_dict.get( 'changeset_revision', None )
+ repository_id = str( repository_info_dict.get( 'repository_id', None ) )
+ changeset_revision = str( repository_info_dict.get( 'changeset_revision', None ) )
metadata_revision_id = repository_info_dict.get( 'id', None )
# Add the URL for the tool shed we're installing from, so the automated installation methods go to the right place.
repository_info_dict[ 'tool_shed_url' ] = galaxy_tool_shed_url
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.
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.