1618 lines
66 KiB
Matlab
1618 lines
66 KiB
Matlab
function View(tr,sel,propsForFigure)
|
|
%VIEW views a phylogenetic tree in phytreetool.
|
|
%
|
|
% VIEW(TREE) shows a phylogenetic tree object. The significant distances
|
|
% between branches and nodes are in horizontal direction, vertical
|
|
% coordinates are accommodated only for display purposes. Tree
|
|
% Edit/Analysis tools are accessible through the mouse left/right buttons
|
|
% and also using the 'Tree' menu.
|
|
%
|
|
% VIEW(TREE,SEL) starts the viewer with an initial selection of nodes
|
|
% specified by SEL. SEL can be a logical array of any of the following
|
|
% sizes: [NUMLEAVES+NUMBRANCHES x 1], [NUMLEAVES x 1], or [NUMBRANCHES x
|
|
% 1]. SEL may also be a list of indices.
|
|
%
|
|
% Examples:
|
|
%
|
|
% tr = phytreeread('pf00002.tree')
|
|
% view(tr)
|
|
%
|
|
% See also PHYTREE, PHYTREE/PLOT, PHYTREEREAD, PHYTREETOOL, SEQLINKAGE,
|
|
% SEQNEIGHJOIN.
|
|
|
|
% Copyright 2003-2006 The MathWorks, Inc.
|
|
% $Revision: 1.1.6.19.2.1 $ $Author: batserve $ $Date: 2006/07/24 13:54:28 $
|
|
|
|
if numel(tr)~=1
|
|
error('Bioinfo:phytree:view:NoMultielementArrays',...
|
|
'Phylogenetic tree must be an 1-by-1 object.');
|
|
end
|
|
|
|
tr = doBasicCalculations(tr);
|
|
|
|
nodeIndex = 1:tr.numLabels;
|
|
leafIndex = 1:tr.numLeaves;
|
|
branchIndex = tr.numLeaves+1:tr.numLabels;
|
|
|
|
% check empty names
|
|
for ind = nodeIndex
|
|
if isempty(tr.names{ind})
|
|
if ind > tr.numLeaves
|
|
tr.names{ind} = ['Branch ' num2str(ind-tr.numLeaves)];
|
|
else
|
|
tr.names{ind} = ['Leaf ' num2str(ind)];
|
|
end
|
|
end
|
|
end
|
|
|
|
% initial drawing
|
|
if nargin<3
|
|
propsForFigure.Name = ['Phylogenetic Tree Tool ' getphytreetoolnumber];
|
|
end
|
|
propsForFigure.PruneWarning = getacceptedwarningfromothertools;
|
|
fig = figure('Renderer','ZBuffer','Name',propsForFigure.Name,...
|
|
'NumberTitle','off','IntegerHandle','off','tag','PhyTreeTool');
|
|
setappdata(fig,'propsForFigure',propsForFigure)
|
|
setappdata(fig,'backupTree',tr)
|
|
tr.ha = axes; hold on;
|
|
set(tr.ha,'Position',[.05 .05 .7 .9],'YTick',leafIndex,'FontSize',9,'Ydir','reverse',...
|
|
'YAxisLocation','Right','YTickLabel',char(tr.names{leafIndex}))
|
|
tr.hlines = plot( ...
|
|
tr.x([nodeIndex;repmat([tr.par(1:tr.numLabels-1) tr.numLabels],2,1)]),...
|
|
tr.y([repmat(nodeIndex,2,1);[tr.par(1:tr.numLabels-1) tr.numLabels]]),...
|
|
'-k');
|
|
tr.hpathline = plot(1,1,'--r','LineWidth',2,'Visible','off');
|
|
tr.hdragbox = plot(1,1,':k','LineWidth',1,'Visible','off');
|
|
tr.hdots(1,1) = plot(tr.x(branchIndex),tr.y(branchIndex),'o',...
|
|
'MarkerSize',5,'MarkerEdgeColor','k','MarkerFaceColor','b');
|
|
tr.hdots(1,2) = plot(tr.x(leafIndex),tr.y(leafIndex),'square',...
|
|
'MarkerSize',4,'MarkerEdgeColor','k','MarkerFaceColor','w');
|
|
tr.hseldots(1,1) = plot(tr.x(branchIndex),tr.y(branchIndex),'o',...
|
|
'MarkerSize',5,'MarkerEdgeColor','r','MarkerFaceColor','r');
|
|
tr.hseldots(1,2) = plot(tr.x(leafIndex),tr.y(leafIndex),'square',...
|
|
'MarkerSize',4,'MarkerEdgeColor','r','MarkerFaceColor','r');
|
|
tr.hldots(1,1) = plot(tr.x(branchIndex),tr.y(branchIndex),'o',...
|
|
'MarkerSize',5,'MarkerEdgeColor',[.5 .5 .5],...
|
|
'MarkerFaceColor',[.6 .6 1]);
|
|
tr.hldots(1,2) = plot(tr.x(leafIndex),tr.y(leafIndex),'square',...
|
|
'MarkerSize',4,'MarkerEdgeColor',[.5 .5 .5],...
|
|
'MarkerFaceColor','w');
|
|
set(tr.hldots(1),'Xdata',[],'Ydata',[])
|
|
set(tr.hldots(2),'Xdata',[],'Ydata',[])
|
|
tr.axhold = plot([-eps -eps],[0 0],'.','MarkerSize',eps,'Color','w');
|
|
tr.datatip = text(0,1,1,'k','Tag','TreeTag','BackgroundColor',[1 1 .93],...
|
|
'Color', [0 0 0],'EdgeColor', [0.8 0.8 0.8],...
|
|
'VerticalAlignment','Top','Clipping','off',...
|
|
'Visible','off','Fontsize',8,'Interpreter','none');
|
|
|
|
if nargin == 1 || isempty(sel)
|
|
tr.selected = false(tr.numLabels,1); % selected nodes
|
|
else
|
|
% validate sel
|
|
if islogical(sel)
|
|
if numel(sel)==tr.numLabels
|
|
sel = sel(:)==true;
|
|
elseif numel(sel)==tr.numLeaves
|
|
sel = [sel(:);false(tr.numBranches,1)];
|
|
elseif numel(sel)==tr.numBranches
|
|
sel = [false(tr.numLeaves,1);sel(:)];
|
|
else
|
|
close(fig)
|
|
error('Bioinfo:phytree:view:IncorrectLogical',...
|
|
'Logical vector must have the same number of elements as nodes in the Phylogenetic Tree');
|
|
end
|
|
elseif isnumeric(sel) && isreal(sel) && all(sel>=1) && all(sel<=tr.numLabels)
|
|
tem(tr.numLabels)=false;
|
|
tem(floor(sel))=true;
|
|
sel=tem(:);
|
|
else
|
|
close(fig)
|
|
error('Bioinfo:phytree:view:IncorrectTypeofArguments',...
|
|
'Invalid value for NODES');
|
|
end
|
|
tr.selected =sel;
|
|
end
|
|
|
|
% save more figure data needed for the gui functionality
|
|
tr.activeNodes = true(tr.numLabels,1); % active nodes
|
|
tr.activeBranches = true(tr.numBranches,1); % active Branches
|
|
tr.sel2root = false(tr.numLabels,1); % path sel-node to root
|
|
tr.editMode = 'Select'; % initial edit mode
|
|
tr.indicativeMode = false; % data-tip flag
|
|
tr.lastThresholdValue = []; % remembers last cut
|
|
|
|
% create uicontrols (will appear as needed, initially invisible)
|
|
tr.editBox = uicontrol(fig,'Background',[1 1 1],'style','edit',...
|
|
'visible','off','callback',@doneRenaming);
|
|
tr.slider = uicontrol(fig,'style','slider','SliderStep',[.1 .1],...
|
|
'visible','off','callback',@sliderCallback);
|
|
tr.slidertx = uicontrol(fig,'style','text','visible','off');
|
|
tr.sliderok = uicontrol(fig,'style','pushbutton','visible','off',...
|
|
'string','OK','callback',@doThresholdCut);
|
|
|
|
% setup callback for click over nodes
|
|
set([tr.hseldots,tr.hdots,tr.hldots],'ButtonDownFcn',@toggleNode)
|
|
% setup figure callback functions
|
|
set(fig,'WindowButtonDownFcn',@mouseClickOnFigure);
|
|
set(fig,'WindowButtonUpFcn',@mouseRelease);
|
|
set(fig,'WindowButtonMotionFcn',@localWindowButtonMotion);
|
|
|
|
% setup UIMenus, context menus and toolbar
|
|
tr.hToggleUIMenu = makePhyTreeViewerUIMenus(fig);
|
|
tr.hToggleToolbar = makePhyTreeViewerToolbar(fig);
|
|
[tr.hToggleContextMenu,tr.hAxisContextMenu,tr.hDotsContextMenu] = ...
|
|
makePhyTreeViewerContextMenus(fig);
|
|
% activate Context Menus
|
|
set(tr.ha,'UIContextMenu',tr.hAxisContextMenu);
|
|
set([tr.hdots tr.hldots tr.hseldots],'UIContextMenu',tr.hDotsContextMenu);
|
|
|
|
set(fig,'UserData',tr) % save figure data
|
|
|
|
correctFigureSize(fig, 15 * tr.numLeaves); % resize figure if needed
|
|
setupYLabelsListeners; % listeners for YLabels
|
|
updateTree(fig,[],[]) % updates figure after all initializations
|
|
set(gca,'xLim',[0 max(tr.x)] + max(tr.x) * [-.1 .05]);
|
|
tr.yLim = get(tr.ha,'Ylim');tr.xLim = get(tr.ha,'Xlim');
|
|
set(fig,'UserData',tr) % save figure data
|
|
toolsmenufcn(fig,'PanY') % set zoom mode to vertical constraining
|
|
toolsmenufcn(fig,'ZoomY') % set pan mode to vertical constraining
|
|
set(fig,'HandleVisibility','callback') % after all init, make it invisible
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function ylabelsListener(hSrc,event,hf,ha) %#ok
|
|
% Auto sizes the ylabels
|
|
ratio = max(get(hf,'Position').*[0 0 0 1])/diff(get(ha,'YLim'));
|
|
set(ha,'Fontsize',min(9,ceil(ratio/1.7))); % the gold formula
|
|
% Also verify if we need to re-position the slidebar of threshold cut
|
|
tr=get(hf,'Userdata');
|
|
if any(strcmp(tr.editMode,{'Distance to Leaves','Distance to Root'}))
|
|
wS = get(hf,'Position'); % window dimensions
|
|
aP = get(tr.ha,'Position'); % axes position
|
|
set(tr.slider, 'Position',[aP(1)*wS(3) wS(4)-20 aP(3)*wS(3) 20])
|
|
set(tr.slidertx,'Position',[sum(aP([1 3]))*wS(3) wS(4)-20 60 20])
|
|
set(tr.sliderok,'Position',[sum(aP([1 3]))*wS(3)+60 wS(4)-20 30 20])
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function mouseClickOnFigure(h,varargin)
|
|
% This callback function is activated when a mouse button is pressed in any
|
|
% location of the figure and under any of my edit modes
|
|
tr = get(gcbf,'Userdata');
|
|
switch tr.editMode
|
|
case 'Renaming'; doneRenaming(h,varargin);
|
|
case 'Distance to Leaves'; cancelThresholdCut(h,varargin);
|
|
case 'Distance to Root'; cancelThresholdCut(h,varargin);
|
|
case 'Select';
|
|
switch get(gcbf,'SelectionType')
|
|
case {'normal','extend'}
|
|
tr = get(gcbf,'userdata');
|
|
cp = get(tr.ha,'CurrentPoint');
|
|
xPos = cp(1,1); yPos = cp(1,2);
|
|
set(tr.hdragbox,'Visible','on',...
|
|
'Xdata',repmat(xPos,5,1),'Ydata',repmat(yPos,5,1))
|
|
case 'open'
|
|
autoFit(h)
|
|
end
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function toggleNode(h,varargin)
|
|
% This callback function is activated when a mouse button is pressed over
|
|
% any of the displayed nodes under any of my edit modes
|
|
hideActiveIndicators(h,varargin)
|
|
tr = get(gcbf,'Userdata');
|
|
switch get(gcbf,'SelectionType')
|
|
case 'normal'
|
|
switch tr.editMode
|
|
case 'Select'; selectNode(h,varargin);
|
|
case 'Inspect'; inspectNode(h,varargin);
|
|
case 'Collapse/Expand'; collapseExpand(h,varargin);
|
|
case 'Rotate Branch'; rotateBranch(h,varargin);
|
|
case 'Rename'; renameNode(h,varargin);
|
|
case 'Renaming'; doneRenaming(h,varargin);
|
|
case 'Prune'; pruneTree(h,varargin);
|
|
case 'Distance to Leaves'; cancelThresholdCut(h,varargin);
|
|
case 'Distance to Root'; cancelThresholdCut(h,varargin);
|
|
end
|
|
case 'extend'
|
|
switch tr.editMode
|
|
case 'Select'; selectNode(h,varargin);
|
|
end
|
|
case 'alt'
|
|
case 'open'
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function changeEditMode(h,varargin) %#ok
|
|
% Callback function to change the edit mode, this function is
|
|
% called from the toolbar, the context menu or the uimenu.
|
|
tr = get(gcbf,'Userdata');
|
|
myModes = {'Inspect','Collapse/Expand','Rotate Branch','Rename','Prune'};
|
|
|
|
% first, disable any present edit mode
|
|
switch tr.editMode
|
|
case {myModes{:},'Select'};
|
|
disableMyContextMenus(h)
|
|
disableMyWindowButtonActions(h)
|
|
ind = strmatch(tr.editMode,myModes); %#ok
|
|
set(tr.hToggleToolbar(ind), 'State','off')
|
|
set(tr.hToggleUIMenu(ind), 'Checked','off')
|
|
set(tr.hToggleContextMenu(ind),'Checked','off')
|
|
%case '&Zoom In'; toolsmenufcn(gcbf,'ZoomIn');
|
|
case '&Zoom In'; zoom(gcbf,'off')
|
|
%case 'Zoom &Out'; toolsmenufcn(gcbf,'ZoomOut');
|
|
case 'Zoom &Out'; zoom(gcbf,'off')
|
|
%case '&Pan'; toolsmenufcn(gcbf,'Pan');
|
|
case '&Pan'; pan(gcbf,'off');
|
|
case {'Distance to Leaves','Distance to Root'}
|
|
enableAllUI(h)
|
|
disableMyWindowButtonActions(h)
|
|
end
|
|
|
|
% depending on the caller instance, determine the new edit mode
|
|
switch get(h,'Type')
|
|
case 'uimenu'; newEditMode = get(h,'Label');
|
|
case 'uitoggletool'
|
|
newEditMode = get(h,'Tag');
|
|
switch newEditMode
|
|
case 'Exploration.ZoomIn'; newEditMode = '&Zoom In';
|
|
case 'Exploration.ZoomOut'; newEditMode = 'Zoom &Out';
|
|
case 'Exploration.Pan'; newEditMode = '&Pan';
|
|
end
|
|
otherwise; newEditMode = 'Select';
|
|
end
|
|
%disp( [tr.editMode ' --> ' newEditMode] )
|
|
% if new mode is the same then we are toggling off
|
|
if strcmp(newEditMode,tr.editMode)
|
|
newEditMode = 'Select';
|
|
end
|
|
|
|
% if changing to Prune, verify the warnign has been accepted
|
|
if strcmp(newEditMode,'Prune')
|
|
propsForFigure = getappdata(gcbf,'propsForFigure');
|
|
if isequal(propsForFigure.PruneWarning,'NotDone')
|
|
warndlg(['Pruning nodes cannot be undone. Before continuing,',...
|
|
' you may want to export the current tree to a new tool.'],...
|
|
'Warning','modal')
|
|
setacceptedwarningtoothertools
|
|
end
|
|
end
|
|
|
|
switch newEditMode
|
|
case '&Zoom In'; toolsmenufcn(gcbf,'ZoomIn');
|
|
case 'Zoom &Out'; toolsmenufcn(gcbf,'ZoomOut');
|
|
%case '&Pan'; toolsmenufcn(gcbf,'Pan');
|
|
case '&Pan'; pan(gcbf,'on');
|
|
case myModes; enableMyContextMenus(h)
|
|
enableMyWindowButtonActions(h)
|
|
ind = strmatch(newEditMode,myModes); %#ok
|
|
set(tr.hToggleToolbar(ind), 'State','on')
|
|
set(tr.hToggleUIMenu(ind), 'Checked','on')
|
|
set(tr.hToggleContextMenu(ind),'Checked','on')
|
|
case 'Select'; enableMyContextMenus(h)
|
|
enableMyWindowButtonActions(h)
|
|
case {'Distance to Leaves','Distance to Root'}
|
|
disableAllUI(h)
|
|
enableMyWindowButtonActions(h)
|
|
set(gcbf,'WindowButtonMotionFcn',[])
|
|
set(gcbf,'WindowButtonUpFcn',[])
|
|
set([tr.hseldots,tr.hdots,tr.hldots],'ButtonDownFcn',[])
|
|
end
|
|
|
|
switch newEditMode
|
|
case 'Inspect'; if sum(tr.selected(:)) ~= 1
|
|
tr.selected(:) = false;
|
|
tr.selected(end) = true;
|
|
end
|
|
otherwise
|
|
end
|
|
|
|
tr.sel2root = path2root(tr, tr.selected);
|
|
tr.editMode = newEditMode;
|
|
set(gcbf,'userdata',tr)
|
|
updateTree(gcbf,[],[])
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function hideActiveIndicators(h,varargin) %#ok
|
|
tr = get(gcbf,'userdata');
|
|
set([tr.hpathline,tr.datatip],'visible','off')
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function disableAllUI(h,varargin) %#ok
|
|
hw = findall(gcbf,'Type','uimenu','Parent',gcbf);
|
|
gw = findall(gcbf,'Type','UIToggleTool');
|
|
set([hw;gw],'Enable','off')
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function enableAllUI(h,varargin) %#ok
|
|
hw = findall(gcbf,'Type','uimenu','Parent',gcbf);
|
|
gw = findall(gcbf,'Type','UIToggleTool');
|
|
set([hw;gw],'Enable','on')
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function disableMyContextMenus(h,varargin) %#ok
|
|
tr = get(gcbf,'userdata');
|
|
set(tr.ha,'UIContextMenu',[]);
|
|
set([tr.hdots tr.hseldots],'UIContextMenu',[]);
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function enableMyContextMenus(h,varargin) %#ok
|
|
tr = get(gcbf,'userdata');
|
|
set(tr.ha,'UIContextMenu',tr.hAxisContextMenu);
|
|
set([tr.hdots tr.hldots tr.hseldots],'UIContextMenu',tr.hDotsContextMenu);
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function disableMyWindowButtonActions(h,varargin) %#ok
|
|
set(gcbf,'WindowButtonDownFcn',[]);
|
|
set(gcbf,'WindowButtonUpFcn',[])
|
|
set(gcbf,'WindowButtonMotionFcn',[]);
|
|
tr = get(gcbf,'userdata');
|
|
set([tr.hseldots,tr.hdots,tr.hldots],'ButtonDownFcn',[])
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function enableMyWindowButtonActions(h,varargin) %#ok
|
|
set(gcbf,'WindowButtonDownFcn',@mouseClickOnFigure);
|
|
set(gcbf,'WindowButtonUpFcn',@mouseRelease);
|
|
set(gcbf,'WindowButtonMotionFcn',@localWindowButtonMotion);
|
|
tr = get(gcbf,'userdata');
|
|
set([tr.hseldots,tr.hdots,tr.hldots],'ButtonDownFcn',@toggleNode)
|
|
set(gcbf,'KeyPressFcn',[]);
|
|
set(gcbf,'Pointer','arrow');
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function localWindowButtonMotion(h,varargin) %#ok
|
|
% Callback function activated when moving over the axes, checks location of
|
|
% the mouse and puts datatip if over an active node.
|
|
|
|
tr = get(h,'userdata');
|
|
% set a virtual grid to get the point
|
|
xThres=diff(get(tr.ha,'Xlim'))/100;
|
|
yThres=diff(get(tr.ha,'Ylim'))/100;
|
|
cp = get(tr.ha,'CurrentPoint');
|
|
xPos = cp(1,1); yPos = cp(1,2);
|
|
hp = tr.x<(xPos+xThres) & tr.x>(xPos-xThres) & ...
|
|
tr.y<(yPos+yThres) & tr.y>(yPos-yThres);
|
|
hp = find (hp & tr.activeNodes);
|
|
|
|
% shortcut out when dragging a box in select mode
|
|
if strcmp(get(tr.hdragbox,'Visible'),'on')
|
|
xdata = get(tr.hdragbox,'XData');xdata([3,4]) = xPos;
|
|
ydata = get(tr.hdragbox,'YData');ydata([2,3]) = yPos;
|
|
set(tr.hdragbox,'XData',xdata,'YData',ydata)
|
|
% shortcut out when turning off 'indicative' mode
|
|
elseif tr.indicativeMode && isempty(hp) %&& isempty(tr.highligth)
|
|
set([tr.datatip tr.hpathline],'visible','off')
|
|
set(tr.hlines,'color','black')
|
|
set(tr.hldots(1),'Xdata',[],'Ydata',[])
|
|
set(tr.hldots(2),'Xdata',[],'Ydata',[])
|
|
% turn on or update 'indicative' mode
|
|
elseif numel(hp) % && isempty(tr.highligth)
|
|
% find leaves (children) below this branch
|
|
children = false(1,tr.numLabels);
|
|
children(hp(1)) = true;
|
|
for ind = hp(1)-tr.numLeaves:-1:1
|
|
if children(ind+tr.numLeaves)
|
|
children(tr.tree(ind,:))=true;
|
|
end
|
|
end
|
|
|
|
% find and draw path to selected
|
|
if strcmp(tr.editMode,'Inspect')
|
|
[pathA,pathB] = path2sel(tr,hp(1));
|
|
dis2sel = tr.x(find(pathA,1))+tr.x(find(pathB,1))...
|
|
-2*tr.x(find(pathA,1,'last'));
|
|
if any(pathB)
|
|
xx = [tr.x(pathA);NaN;tr.x(pathB)];
|
|
yy = [tr.y(pathA);NaN;tr.y(pathB)];
|
|
hh=zeros(2*numel(xx),1); hh(1:2:end)=1; hh=cumsum(hh);
|
|
set(tr.hpathline,'XData',xx(hh(2:end)),...
|
|
'YData',yy(hh(1:end-1)),'Visible','on');
|
|
end
|
|
end
|
|
|
|
% place text
|
|
name = [tr.names{hp(1)} ' '];
|
|
name(name=='_')=' ';
|
|
children(hp(1)) = false;
|
|
numChil = sum(children(1:tr.numLeaves));
|
|
childrenNames = char(tr.names(children(1:tr.numLeaves)));
|
|
childrenNames(childrenNames=='_')=' ';
|
|
childrenNames=[repmat(' ',size(childrenNames,1),1) childrenNames];
|
|
switch tr.editMode
|
|
case 'Inspect'
|
|
if numChil
|
|
set(tr.datatip,'string',char( [ {name; ...
|
|
['Dist to parent: ' num2str(tr.dist(hp(1)))];...
|
|
['Dist to root: ' num2str(tr.x(hp(1))-tr.x(end))];...
|
|
['Path length: ' num2str(dis2sel)];...
|
|
['Samples: ' num2str(numChil)]};...
|
|
mat2cell(childrenNames,ones(size(childrenNames,1),1),...
|
|
size(childrenNames,2))]))
|
|
extraLines = 5;
|
|
else
|
|
set(tr.datatip,'string',char( {name; ...
|
|
['Dist to parent: ' num2str(tr.dist(hp(1)))];...
|
|
['Dist to root: ' num2str(tr.x(hp(1))-tr.x(end))];...
|
|
['Path length: ' num2str(dis2sel)]}))
|
|
extraLines = 4;
|
|
end
|
|
case {'Collapse/Expand','Rotate Branch','Rename','Prune','Select'}
|
|
if numChil
|
|
set(tr.datatip,'string',char([
|
|
{[name ' (' num2str(numChil) ' samples)']};...
|
|
mat2cell(childrenNames,ones(size(childrenNames,1),1),...
|
|
size(childrenNames,2))]))
|
|
else
|
|
set(tr.datatip,'string', name)
|
|
end
|
|
extraLines = 1;
|
|
otherwise % all other modes
|
|
end
|
|
|
|
%compute some values before adjusting data tip
|
|
fp = get(gcbf,'Position'); % fig position in points
|
|
fh = fp(4);%fw = fp(3); % fig size (height & width) in points
|
|
ap = get(tr.ha,'Position'); % axis position normalized
|
|
yl = ylim(tr.ha); yl = yl - ...
|
|
[ap(2) ap(2)+ap(4)-1]*diff(yl)/ap(4); % fig height limits in axis units
|
|
xl = xlim(tr.ha); xl = xl - ...
|
|
[ap(1) ap(1)+ap(3)-1]*diff(xl)/ap(3); % fig width limits in axis units
|
|
yPosPt = (-yPos -4*yThres + yl(2))*fh/diff(yl); % datatip position in pts
|
|
reqPt = (numChil+extraLines)*14+2; % required datatip height in pts
|
|
% adjust if other fontsize is used
|
|
|
|
%adjust string of datatip if it will not fit (i.e. remove names)
|
|
if reqPt > fh
|
|
str = get(tr.datatip,'String');
|
|
set(tr.datatip,'String',str(1:extraLines,:));
|
|
reqPt = extraLines*14+2;
|
|
end
|
|
|
|
%adjust vertical position of datatip just below cp
|
|
topEdge = yl(2)-min(fh,max(yPosPt,reqPt))*diff(yl)/fh;
|
|
switch tr.editMode
|
|
case {'Collapse/Expand','Rotate Branch','Prune'}
|
|
% datatip usually to the left of cp to see shadowing of branches
|
|
datatipExtent = get(tr.datatip,'Extent');
|
|
datatipWidth = datatipExtent(3);
|
|
rightEdge = max(xPos-3*xThres,xl(1)+datatipWidth);
|
|
% is the datatip over cp ?
|
|
if rightEdge>xPos && topEdge<yPos
|
|
% then try to put it above cp
|
|
topEdge = yPos - 3 * yThres - reqPt*diff(yl)/fh;
|
|
% does datatip fit above cp ?
|
|
if topEdge<yl(1)
|
|
% then adjust string by removing names of species
|
|
str = get(tr.datatip,'String');
|
|
set(tr.datatip,'String',str(1:extraLines,:));
|
|
reqPt = extraLines*14+2;
|
|
topEdge = yl(2)-min(fh,max(yPosPt,reqPt))*diff(yl)/fh;
|
|
end
|
|
end
|
|
set(tr.datatip,'Position',[rightEdge-datatipWidth,topEdge,1])
|
|
set(tr.datatip,'horizontalalignment','left')
|
|
case {'Inspect','Rename','Select'}
|
|
% datatip usually to the right of cp to minimize problems on the
|
|
% left edge
|
|
set(tr.datatip,'Position',[xPos+3*xThres,topEdge,1])
|
|
set(tr.datatip,'horizontalalignment','left')
|
|
otherwise
|
|
end
|
|
|
|
switch tr.editMode
|
|
case {'Collapse/Expand','Rotate Branch','Prune'}
|
|
% de-color branches to rotate or collapse
|
|
uncoloredNodes = false(1,tr.numLabels);
|
|
uncoloredNodes(hp(1)) = true;
|
|
for ind = hp(1)-tr.numLeaves:-1:1
|
|
if uncoloredNodes(ind+tr.numLeaves)
|
|
uncoloredNodes(tr.tree(ind,:))=true;
|
|
end
|
|
end
|
|
if ~strcmp(tr.editMode,'Prune')
|
|
uncoloredNodes(hp(1)) = false;
|
|
end
|
|
uncoloredNodes = uncoloredNodes & tr.activeNodes';
|
|
set(tr.hlines(uncoloredNodes),'color',[.87 .87 .87])
|
|
ind=find(uncoloredNodes(tr.numLeaves+1:tr.numLabels))+tr.numLeaves;
|
|
set(tr.hldots(1),'Xdata',tr.x(ind),'Ydata',tr.y(ind))
|
|
ind=find(uncoloredNodes(1:tr.numLeaves));
|
|
set(tr.hldots(2),'Xdata',tr.x(ind),'Ydata',tr.y(ind))
|
|
otherwise
|
|
end
|
|
|
|
set(tr.datatip,'Visible','on')
|
|
tr.indicativeMode = true;
|
|
set(gcbf,'userdata',tr)
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function mouseRelease(h,varargin) %#ok
|
|
tr = get(gcbf,'userdata');
|
|
xdata = get(tr.hdragbox,'Xdata');
|
|
ydata = get(tr.hdragbox,'Ydata');
|
|
hp = (tr.x<max(xdata) & tr.x>min(xdata) & ...
|
|
tr.y<max(ydata) & tr.y>min(ydata) & tr.activeNodes) ;
|
|
if (strcmp(get(gcbf,'SelectionType'),'normal') && ...
|
|
strcmp(get(tr.hdragbox,'visible'),'on'))
|
|
tr.selected(:) = false;
|
|
end
|
|
tr.selected(hp) = true;
|
|
tr.sel2root = path2root(tr,tr.selected);
|
|
set(tr.hdragbox,'Visible','off')
|
|
set(gcbf,'userdata',tr)
|
|
updateTree(gcbf,[],[])
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function selectNode(h,varargin) %#ok
|
|
% Callback function to select a Node.
|
|
% Entry points: from 1) the dots context menu or 2) toggle node
|
|
tr = get(gcbf,'userdata');
|
|
% set a virtual grid to get the point
|
|
xThres=diff(get(tr.ha,'Xlim'))/100;
|
|
yThres=diff(get(tr.ha,'Ylim'))/100;
|
|
cp = get(tr.ha,'CurrentPoint');
|
|
xPos = cp(1,1); yPos = cp(1,2);
|
|
hp = find(tr.x<(xPos+xThres) & tr.x>(xPos-xThres) & ...
|
|
tr.y<(yPos+yThres) & tr.y>(yPos-yThres)) ;
|
|
|
|
if numel(hp)
|
|
set(tr.hdragbox,'visible','off')
|
|
temp = tr.selected(hp(1));
|
|
switch get(gcbf,'SelectionType')
|
|
case 'normal'; tr.selected(:) = false;
|
|
case 'alt'; if ~temp
|
|
tr.selected(:) = false;
|
|
end
|
|
temp=false;
|
|
end
|
|
tr.selected(hp(1)) = ~temp;
|
|
tr.sel2root = path2root(tr,tr.selected);
|
|
set(gcbf,'userdata',tr)
|
|
updateTree(gcbf,[],[])
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function inspectNode(h,varargin) %#ok
|
|
% Callback function to inspect the reference Node.
|
|
% Entry points: from 1) the dots context menu or 2) toggle node
|
|
tr = get(gcbf,'userdata');
|
|
% set a virtual grid to get the point
|
|
xThres=diff(get(tr.ha,'Xlim'))/100;
|
|
yThres=diff(get(tr.ha,'Ylim'))/100;
|
|
cp = get(tr.ha,'CurrentPoint');
|
|
xPos = cp(1,1); yPos = cp(1,2);
|
|
hp = find(tr.x<(xPos+xThres) & tr.x>(xPos-xThres) & ...
|
|
tr.y<(yPos+yThres) & tr.y>(yPos-yThres)) ;
|
|
|
|
if numel(hp)
|
|
temp = tr.selected(hp(1));
|
|
tr.selected(:) = false;
|
|
tr.selected(hp(1)) = ~temp;
|
|
tr.sel2root = path2root(tr,tr.selected);
|
|
set(gcbf,'userdata',tr)
|
|
updateTree(gcbf,[],[])
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function collapseExpand(h,varargin) %#ok
|
|
% Callback function to Collapse/Expand a branch.
|
|
% Entry points: from 1) the dots context menu or 2) toggle node
|
|
|
|
tr = get(gcbf,'userdata');
|
|
if strcmp(get(h,'Type'),'uimenu') % come from a context menu
|
|
hp = find(tr.selected(tr.numLeaves+1:tr.numLabels));
|
|
else
|
|
% set a virtual grid to get the point
|
|
xThres=diff(get(tr.ha,'Xlim'))/100;
|
|
yThres=diff(get(tr.ha,'Ylim'))/100;
|
|
cp = get(tr.ha,'CurrentPoint');
|
|
xPos = cp(1,1); yPos = cp(1,2);
|
|
hp = find(tr.x<(xPos+xThres) & tr.x>(xPos-xThres) & ...
|
|
tr.y<(yPos+yThres) & tr.y>(yPos-yThres)) ;
|
|
hp=hp(hp>tr.numLeaves)-tr.numLeaves;
|
|
if numel(hp)
|
|
hp=hp(1); %just in case it picked two points
|
|
end
|
|
end
|
|
|
|
if numel(hp)
|
|
for ind = 1:numel(hp)
|
|
tr.activeBranches(hp(ind))=~tr.activeBranches(hp(ind));
|
|
activeBranches=find(tr.activeBranches)';
|
|
% find active nodes by expanding active Branches
|
|
tr.activeNodes(:)=false;
|
|
tr.activeNodes(tr.numLabels,1)=true;
|
|
for k = activeBranches(end:-1:1)
|
|
tr.activeNodes(tr.tree(k,:))=tr.activeNodes(k+tr.numLeaves);
|
|
end
|
|
tr.selected(:) = false;
|
|
tr.selected(hp(ind)+tr.numLeaves) = true;
|
|
end
|
|
tr.sel2root = path2root(tr,tr.selected);
|
|
set(gcbf,'userdata',tr)
|
|
updateTree(gcbf,[],hp(end)+tr.numLeaves)
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function rotateBranch(h,varargin) %#ok
|
|
% Callback function to rotate a branch reordering the leaves.
|
|
% Entry points: from 1) the dots context menu or 2) toggle node
|
|
tr = get(gcbf,'userdata');
|
|
if strcmp(get(h,'Type'),'uimenu') % come from a context menu
|
|
hp = find(tr.selected(tr.numLeaves+1:tr.numLabels));
|
|
else
|
|
% set a virtual grid to get the point
|
|
xThres=diff(get(tr.ha,'Xlim'))/100;
|
|
yThres=diff(get(tr.ha,'Ylim'))/100;
|
|
cp = get(tr.ha,'CurrentPoint');
|
|
xPos = cp(1,1); yPos = cp(1,2);
|
|
hp = find(tr.x<(xPos+xThres) & tr.x>(xPos-xThres) & ...
|
|
tr.y<(yPos+yThres) & tr.y>(yPos-yThres)) ;
|
|
hp=hp(hp>tr.numLeaves)-tr.numLeaves;
|
|
if numel(hp)
|
|
hp=hp(1); %just in case it picked two points
|
|
end
|
|
end
|
|
if numel(hp)
|
|
for ind = 1:numel(hp)
|
|
%find Leaves for every child
|
|
childrenA = false(1,tr.numLabels);
|
|
childrenA(tr.tree(hp(ind),1)) = true;
|
|
for k = tr.tree(hp(ind),1)-tr.numLeaves:-1:1
|
|
if childrenA(k+tr.numLeaves)
|
|
childrenA(tr.tree(k,:))=true;
|
|
end
|
|
end
|
|
childrenB = false(1,tr.numLabels);
|
|
childrenB(tr.tree(hp(ind),2)) = true;
|
|
for k = tr.tree(hp(ind),2)-tr.numLeaves:-1:1
|
|
if childrenB(k+tr.numLeaves)
|
|
childrenB(tr.tree(k,:))=true;
|
|
end
|
|
end
|
|
permuta = 1:tr.numLabels;
|
|
chA = find(childrenA(1:tr.numLeaves));
|
|
chB = find(childrenB(1:tr.numLeaves));
|
|
if chA(1)<chB(1)
|
|
permuta([chA chB])=[chB chA];
|
|
else
|
|
permuta([chB chA])=[chA chB];
|
|
end
|
|
ipermuta = zeros(1,tr.numLabels);
|
|
ipermuta(permuta)=1:tr.numLabels;
|
|
tr.names = tr.names(permuta);
|
|
tr.dist = tr.dist(permuta);
|
|
tr.tree = ipermuta(tr.tree);
|
|
tr.par = tr.par(permuta(1:end-1));
|
|
tr.selected = tr.selected(permuta);
|
|
tr.activeNodes = tr.activeNodes(permuta);
|
|
tr.sel2root = tr.sel2root(permuta);
|
|
end
|
|
set(gcbf,'userdata',tr)
|
|
updateTree(gcbf,[],[])
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function renameNode(h,varargin) %#ok
|
|
% Renames a Node. Puts an uicontrol to input the new name.
|
|
% Entry points: from 1) the dots context menu or 2) toggle node
|
|
tr = get(gcbf,'userdata');
|
|
Xlim=get(tr.ha,'Xlim');Ylim=get(tr.ha,'Ylim');
|
|
aPos=get(tr.ha,'Position');
|
|
% set a virtual grid to get the point
|
|
xThres=diff(Xlim)/100;
|
|
yThres=diff(Ylim)/100;
|
|
cp = get(tr.ha,'CurrentPoint');
|
|
xPos = cp(1,1); yPos = cp(1,2);
|
|
hp = find(tr.x<(xPos+xThres) & tr.x>(xPos-xThres) & ...
|
|
tr.y<(yPos+yThres) & tr.y>(yPos-yThres)) ;
|
|
if numel(hp)
|
|
tr.previousMode = tr.editMode; tr.editMode = 'Renaming';
|
|
xBoxPos = (.02+(xPos-Xlim(1))/diff(Xlim))*aPos(3)+aPos(1);
|
|
yBoxPos = (.02+(Ylim(2)-yPos)/diff(Ylim))*aPos(4)+aPos(2);
|
|
position=get(gcbf,'position');
|
|
position=[position(3)*xBoxPos position(4)*yBoxPos 150 20];
|
|
set(tr.editBox,'position',position);
|
|
set(tr.editBox,'Visible','on','string',tr.names{hp(1)},'Value',hp(1))
|
|
disableAllUI(h)
|
|
disableMyContextMenus(h)
|
|
set(gcbf,'WindowButtonMotionFcn',[]); % disable windows mouse motion
|
|
set(gcbf,'userdata',tr)
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function doneRenaming(h,varargin) %#ok
|
|
% Output helper function to abandon the "Renaming" mode
|
|
tr = get(gcbf,'userdata');
|
|
tr.editMode = tr.previousMode;
|
|
tr.names{get(tr.editBox,'Value')} = get(tr.editBox,'String');
|
|
set(tr.editBox,'Visible','off')
|
|
set(gcbf,'userdata',tr)
|
|
updateTree(gcbf,[],[]);
|
|
enableAllUI(h)
|
|
enableMyContextMenus(h)
|
|
enableMyWindowButtonActions(h)
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function pruneTree(h,varargin) %#ok
|
|
% Callback function to prune the tree, this function is complex because not
|
|
% only the basic structure is updated but all the other handles are
|
|
% also updated to contain the new tree.
|
|
|
|
% if changing to Prune, verify the warning has been accepted
|
|
propsForFigure = getappdata(gcbf,'propsForFigure');
|
|
if isequal(propsForFigure.PruneWarning,'NotDone')
|
|
warndlg(['Pruning nodes cannot be undone. Before continuing,',...
|
|
' you may want to export the current tree to a new tool.'],...
|
|
'Warning','modal')
|
|
setacceptedwarningtoothertools
|
|
return % do not do this pruning
|
|
end
|
|
|
|
tr = get(gcbf,'userdata');
|
|
if strcmp(get(h,'Type'),'uimenu') % comes from a context menu
|
|
hp = find(tr.selected);
|
|
else
|
|
tr = get(gcbf,'userdata');
|
|
Xlim=get(tr.ha,'Xlim');Ylim=get(tr.ha,'Ylim');
|
|
% aPos=get(tr.ha,'Position');
|
|
% set a virtual grid to get the point
|
|
xThres=diff(Xlim)/100;
|
|
yThres=diff(Ylim)/100;
|
|
cp = get(tr.ha,'CurrentPoint');
|
|
xPos = cp(1,1); yPos = cp(1,2);
|
|
hp = find(tr.x<(xPos+xThres) & tr.x>(xPos-xThres) & ...
|
|
tr.y<(yPos+yThres) & tr.y>(yPos-yThres));
|
|
hp=hp(1); %just in case it picked two points
|
|
end
|
|
|
|
hp(hp==tr.numLabels)=[]; %cannot delete the root
|
|
|
|
while numel(hp)
|
|
%find all nodes to purge (i.e. all descendants)
|
|
children = false(1,tr.numLabels);
|
|
children(hp(1)) = true;
|
|
for k = hp(1)-tr.numLeaves:-1:1
|
|
if children(k+tr.numLeaves)
|
|
children(tr.tree(k,:))=true;
|
|
end
|
|
end
|
|
mypar = tr.par(hp(1)); % parent
|
|
if mypar < tr.numLabels % my parent is NOT the root
|
|
% connect brother to granparent
|
|
mygrpar = tr.par(mypar); % grandparent
|
|
myuncle = setxor(tr.tree(mygrpar-tr.numLeaves,:),mypar); % uncle
|
|
mybro = setxor(tr.tree(mypar-tr.numLeaves,:),hp(1)); % brother
|
|
tr.tree(mygrpar-tr.numLeaves,:) = [myuncle mybro];
|
|
tr.dist(mybro) = tr.dist(mybro) + tr.dist(mypar);
|
|
temp = get(tr.hlines(mygrpar-tr.numLeaves),'Xdata');
|
|
temp([1 4])=tr.x(tr.tree(mygrpar-tr.numLeaves,:));
|
|
set(tr.hlines(mygrpar-tr.numLeaves),'Xdata',temp);
|
|
highlight = [mybro,mygrpar];
|
|
else % if my parent is the root, now I am the new root
|
|
temp=cell2mat(get(tr.hlines,'Xdata'))-tr.dist(end);
|
|
for k = 1:tr.numBranches
|
|
set(tr.hlines(k),'Xdata',temp(k,:));
|
|
end
|
|
highlight = setxor(tr.tree(mypar-tr.numLeaves,:),hp(1));
|
|
end
|
|
children(mypar) = true; %also delete my par
|
|
% find indexes to change tree
|
|
permuta = 1:tr.numLabels;
|
|
permuta(children) = [];
|
|
ipermuta = zeros(1,tr.numLabels);
|
|
ipermuta(permuta) = 1:length(permuta);
|
|
permutaBranches = permuta(permuta>tr.numLeaves)-tr.numLeaves;
|
|
% update all tree structure fields
|
|
tr.names = tr.names(permuta);
|
|
tr.dist = tr.dist(permuta);
|
|
tr.tree = tr.tree(permutaBranches,:);
|
|
tr.tree = ipermuta(tr.tree);
|
|
if isempty(tr.tree)
|
|
return;
|
|
end % one leaf, no branches !
|
|
tr = doBasicCalculations(tr);
|
|
hlines = tr.hlines;
|
|
tr.hlines = tr.hlines(permuta);
|
|
delete(setxor(hlines,tr.hlines));
|
|
tr.activeNodes = tr.activeNodes(permuta);
|
|
tr.activeBranches = tr.activeBranches(permutaBranches);
|
|
tr.selected = tr.selected(permuta);
|
|
tr.selected(:) = false;
|
|
tr.selected(ipermuta(highlight)) = true;
|
|
tr.sel2root = path2root(tr,tr.selected);
|
|
% update the vector with nodes to prune (node index has changed)
|
|
hp=ipermuta(hp);
|
|
hp(1)=[];
|
|
hp(hp==0)=[];
|
|
set(gcbf,'userdata',tr)
|
|
updateTree(gcbf,[],[])
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function colorDown(h,varargin) %#ok
|
|
% Color Down a branch.
|
|
% Entry points: from 1) the dots context menu or 2) toggle node
|
|
disp('Color Down a branch. Not implemented YET !!!')
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function findNode(h,varargin) %#ok
|
|
treefig = gcbf;
|
|
tr = get(treefig,'userdata');
|
|
s = inputdlg('Regular Expression to match ?','Find Leaf/Branch',1);
|
|
if ~isempty(s)
|
|
hc=regexpi(regexprep(tr.names,'_',' '),s);
|
|
h = false(1,tr.numLabels);
|
|
for ind = 1:tr.numLabels
|
|
if ~isempty(hc{ind})
|
|
h(ind)=true;
|
|
end
|
|
end
|
|
hf = find(h);
|
|
for ind = 1:length(hf)
|
|
while ~tr.activeNodes(hf(ind))
|
|
hf(ind)=tr.par(hf(ind));
|
|
end
|
|
end
|
|
tr.selected(:) = false;
|
|
tr.selected(hf) = true;
|
|
tr.sel2root = path2root(tr,tr.selected); % update path to root
|
|
set(treefig,'Userdata',tr);
|
|
updateTree(treefig,[],[])
|
|
|
|
% if selected are out of current view then fit the tree
|
|
if (any(min(ylim(tr.ha))>tr.y(tr.selected)) || ...
|
|
any(max(ylim(tr.ha))<tr.y(tr.selected)))
|
|
autoFit(h)
|
|
end
|
|
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function thresholdCut(h,varargin) %#ok
|
|
% sets the slider uicontrol and wait for the threshold cut to be entered.
|
|
expandAll(h);
|
|
autoFit(h);
|
|
changeEditMode(h) % turns off any mode
|
|
tr = get(gcbf,'userdata');
|
|
wS = get(gcbf,'Position'); % window dimensions
|
|
aP = get(tr.ha,'Position'); % axes position
|
|
set(tr.slider, 'Position',[aP(1)*wS(3) wS(4)-20 aP(3)*wS(3) 20])
|
|
set(tr.slidertx,'Position',[sum(aP([1 3]))*wS(3) wS(4)-20 60 20])
|
|
set(tr.sliderok,'Position',[sum(aP([1 3]))*wS(3)+60 wS(4)-20 30 20])
|
|
set([tr.slider tr.slidertx tr.sliderok],'visible','on')
|
|
if isempty(tr.lastThresholdValue)
|
|
set(tr.slider,'max',max(tr.x),'value',max(tr.x)*.75)
|
|
else
|
|
set(tr.slider,'max',max(tr.x),'value',tr.lastThresholdValue)
|
|
end
|
|
set(tr.slider,'TooltipString',get(h,'Label'))
|
|
sliderCallback(h,varargin)
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function sliderCallback(h,varargin) %#ok
|
|
% this helper function serves both sliders (from root and from leaves), it
|
|
% hides selected nodes based on the current threshold cut
|
|
tr = get(gcbf,'userdata');
|
|
Value = get(tr.slider,'Value');
|
|
switch get(tr.slider,'TooltipString')
|
|
case 'Distance to Leaves'
|
|
tr.tocollapse = tr.dist2Leaf<(max(tr.x)-Value);
|
|
set(tr.slidertx,'String',num2str(max(tr.x)-Value))
|
|
case 'Distance to Root'
|
|
tr.tocollapse = tr.x >= Value;
|
|
set(tr.slidertx,'String',num2str(Value))
|
|
end
|
|
set(gcbf,'Userdata',tr);
|
|
toshow = [tr.tocollapse(tr.par(1:tr.numLabels-1));0]&tr.tocollapse;
|
|
mask = (1:tr.numLabels)'>tr.numLeaves;
|
|
% update light lines
|
|
set(tr.hlines(~toshow),'color','k')
|
|
set(tr.hlines(toshow),'color',[.87 .87 .87])
|
|
% update light dots
|
|
set(tr.hldots(1),'Ydata',tr.y(toshow&mask&tr.activeNodes),...
|
|
'Xdata',tr.x(toshow&mask&tr.activeNodes))
|
|
set(tr.hldots(2),'Ydata',tr.y(toshow&~mask&tr.activeNodes),...
|
|
'Xdata',tr.x(toshow&~mask&tr.activeNodes))
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function doThresholdCut(h,varargin) %#ok
|
|
% this helper function inactivates nodes based on the threshold cut selected
|
|
% with the slider. Entry point: the only way to get into this function is
|
|
% by the 'OK' uicontrol next to the slider.
|
|
tr = get(gcbf,'userdata');
|
|
tr.activeBranches = tr.activeBranches & ~tr.tocollapse(tr.numLeaves+1:tr.numLabels);
|
|
% find active nodes by expanding active Branches
|
|
activeBranches=find(tr.activeBranches)';
|
|
tr.activeNodes(:)=false;
|
|
tr.activeNodes(tr.numLabels,1)=true;
|
|
for ind = activeBranches(end:-1:1)
|
|
tr.activeNodes(tr.tree(ind,:))=tr.activeNodes(ind+tr.numLeaves);
|
|
end
|
|
tr.lastThresholdValue = get(tr.slider,'Value');
|
|
set([tr.slider,tr.slidertx,tr.sliderok],'Visible','off')
|
|
tr.selected(:) = false;
|
|
tr.sel2root = path2root(tr,tr.selected);
|
|
set(gcbf,'userdata',tr)
|
|
changeEditMode(h);
|
|
updateTree(gcbf,[],[]);
|
|
autoFit(h)
|
|
enableAllUI(h)
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function cancelThresholdCut(h,varargin) %#ok
|
|
% this helper function cancels the threshold cut mode and returns to the
|
|
% 'select' mode. Entry point: the ways to get into this function is
|
|
% by the 'CANCEL' uicontrol next to the slider (does not exist yet) or by
|
|
% mouse click over the axes diring the slider mode.
|
|
tr = get(gcbf,'userdata');
|
|
set([tr.slider,tr.slidertx,tr.sliderok],'Visible','off')
|
|
changeEditMode(h);
|
|
updateTree(gcbf,[],[]);
|
|
enableAllUI(h)
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function expandAll(h,varargin) %#ok
|
|
% Callback function to expand all hidden nodes
|
|
tr = get(gcbf,'userdata');
|
|
x=[0 inf];
|
|
[dump,anchor]=min(abs((mean(ylim)-tr.y))+x(1+tr.activeNodes)'); %#ok
|
|
tr.activeBranches(:) = true;
|
|
tr.activeNodes(:) = true;
|
|
set(gcbf,'Userdata',tr);
|
|
updateTree(gcbf,[],anchor)
|
|
autoFit(h)
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% Callback function to save tree
|
|
function saveNewick(h,varargin) %#ok
|
|
tr = get(gcbf,'userdata');
|
|
newtr.tree = tr.tree;
|
|
newtr.dist = tr.dist;
|
|
newtr.names = tr.names;
|
|
phytreewrite(phytree(newtr),'GUI',true);
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% Callback function to restore the original tree
|
|
function restoreTree(h,varargin) %#ok
|
|
tr = getappdata(gcbf,'backupTree');
|
|
view(phytree(tr))
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% Callback function to load tree
|
|
function loadNewick(h,varargin) %#ok
|
|
if strcmp(get(h,'Type'),'uimenu') % if caller is the uimenu then needs
|
|
figtoclose = gcbf;
|
|
tr = phytreeread; % to pick a file
|
|
|
|
else % if not, caller is the callback from get workspace var
|
|
tr=[];
|
|
pfig = get(h,'Parent');
|
|
if strcmp(get(h,'string'),'Import') || ...
|
|
(strcmp(get(h,'style'),'listbox') && strcmp(get(gcbf,'SelectionType'),'open'))
|
|
hp = get(pfig,'Userdata');
|
|
figtoclose = hp(4);
|
|
ops = get(hp(1),'string');
|
|
if ~isempty(ops)
|
|
tr = evalin('base',ops(get(hp(1),'value'),:));
|
|
end
|
|
close(pfig);
|
|
elseif strcmp(get(h,'string'),'Cancel')
|
|
close(pfig);
|
|
end
|
|
end
|
|
if ~isempty(tr)
|
|
propsForFigure = getappdata(figtoclose,'propsForFigure');
|
|
view(tr,[],propsForFigure);
|
|
close(figtoclose)
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% Callback function to close tree
|
|
function closeNewick(h,varargin) %#ok
|
|
close(gcbf)
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% Callback function to copy the tree to a figure
|
|
function doPublishFigure(h,varargin,AllNodes,callerIsContextMenu) %#ok
|
|
|
|
pfig = get(h,'Parent');
|
|
if strcmp(get(h,'string'),'Cancel')
|
|
close(pfig);
|
|
return;
|
|
end
|
|
|
|
% get options from the publishdlg window and close it
|
|
hp = get(pfig,'Userdata');
|
|
vfig = hp(7);
|
|
va = get(hp(1:6),'Value');
|
|
va = [va{:}];
|
|
switch find(va(1:3))
|
|
case 1; args = {'type','square'};
|
|
case 2; args = {'type','angular'};
|
|
case 3; args = {'type','radial'};
|
|
end
|
|
args = {args{:},'bra',va(4),'lea',va(5),'ter',va(6)};
|
|
close(pfig);
|
|
|
|
% now select the branch to publish based on selected points
|
|
tr = get(vfig,'userdata');
|
|
if ~callerIsContextMenu & ~tr.selected %#ok % if called from the uimenu and
|
|
tr.selected(end) = true; % nothing is selected pick the root
|
|
end
|
|
selected = find(tr.selected);
|
|
commonpath = true(tr.numLabels,1);
|
|
for ind = 1:numel(selected)
|
|
commonpath = commonpath & path2root(tr,selected(ind));
|
|
end
|
|
branchtoexp = find(commonpath,1);
|
|
tr.selected(:) = false;
|
|
tr.selected(branchtoexp) = true;
|
|
set(vfig,'userdata',tr)
|
|
updateTree(vfig,[],[]);
|
|
|
|
hp = branchtoexp;
|
|
%find all nodes to export (i.e. all descendants)
|
|
children = false(1,tr.numLabels);
|
|
children(hp) = true;
|
|
if AllNodes
|
|
for ind = hp-tr.numLeaves:-1:1
|
|
if children(ind+tr.numLeaves)
|
|
children(tr.tree(ind,:))=true;
|
|
end
|
|
end
|
|
permuta = find(children);
|
|
else
|
|
for ind = hp-tr.numLeaves:-1:1
|
|
if children(ind+tr.numLeaves)
|
|
children(tr.tree(ind,:))=tr.activeNodes(tr.tree(ind,:));
|
|
end
|
|
end
|
|
braToLea = find(~tr.activeNodes(tr.tree(:,1)))+tr.numLeaves;
|
|
expBran = find(children(tr.numLeaves+1:end)) + tr.numLeaves;
|
|
permuta = [find(children(1:tr.numLeaves)) ...
|
|
intersect(expBran,braToLea)];
|
|
[dump,hs] = sort(tr.y(permuta)); %#ok
|
|
permuta = [permuta(hs) setdiff(expBran,braToLea)];
|
|
end
|
|
|
|
if sum(children)>1 % enough leaves to export ?
|
|
newtr = phytree;
|
|
ipermuta(permuta) = 1:length(permuta);
|
|
numLeaves = (ipermuta(end) + 1)/2;
|
|
newtr.tree = ipermuta(tr.tree(permuta(numLeaves+1:end)-tr.numLeaves,:));
|
|
newtr.dist = tr.dist(permuta);
|
|
newtr.names = tr.names(permuta);
|
|
plot(newtr,true(length(newtr.tree),1),args{:})
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% Callback function for autofit
|
|
function autoFit(h,varargin) %#ok
|
|
tr = get(gcbf,'userdata');
|
|
set(tr.ha,'Ylim',[min(tr.y(tr.activeNodes))-1,max(tr.y(tr.activeNodes))+1]);
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% Callback function to Reset view
|
|
function myResetView(h,varargin) %#ok
|
|
tr = get(gcbf,'userdata');
|
|
set(tr.ha,'Ylim',tr.yLim);
|
|
set(tr.ha,'Xlim',tr.xLim);
|
|
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% Common export function
|
|
function exportSubtree(h,varargin,AllNodes,ToWS,callerIsContextMenu) %#ok
|
|
|
|
tr = get(gcbf,'userdata');
|
|
% if called from the uimenu and nothing is selected pick the root
|
|
if ~callerIsContextMenu & ~tr.selected %#ok
|
|
tr.selected(end) = true;
|
|
end
|
|
|
|
selected = find(tr.selected);
|
|
commonpath = true(tr.numLabels,1);
|
|
for ind = 1:numel(selected)
|
|
commonpath = commonpath & path2root(tr,selected(ind));
|
|
end
|
|
branchtoexp = find(commonpath,1);
|
|
tr.selected(:) = false;
|
|
tr.selected(branchtoexp) = true;
|
|
|
|
set(gcbf,'userdata',tr)
|
|
updateTree(gcbf,[],[]);
|
|
doExport(branchtoexp,AllNodes,ToWS)
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% Common export function after having selected a point
|
|
function doExport(hp,AllNodes,ToWS)
|
|
|
|
tr = get(gcbf,'userdata');
|
|
|
|
%find all nodes to export (i.e. all descendants)
|
|
children = false(1,tr.numLabels);
|
|
children(hp) = true;
|
|
if AllNodes
|
|
for ind = hp-tr.numLeaves:-1:1
|
|
if children(ind+tr.numLeaves)
|
|
children(tr.tree(ind,:))=true;
|
|
end
|
|
end
|
|
permuta = find(children);
|
|
else
|
|
for ind = hp-tr.numLeaves:-1:1
|
|
if children(ind+tr.numLeaves)
|
|
children(tr.tree(ind,:))=tr.activeNodes(tr.tree(ind,:));
|
|
end
|
|
end
|
|
braToLea = find(~tr.activeNodes(tr.tree(:,1)))+tr.numLeaves;
|
|
expBran = find(children(tr.numLeaves+1:end)) + tr.numLeaves;
|
|
permuta = [find(children(1:tr.numLeaves)) ...
|
|
intersect(expBran,braToLea)];
|
|
[dump,hs] = sort(tr.y(permuta)); %#ok
|
|
permuta = [permuta(hs) setdiff(expBran,braToLea)];
|
|
end
|
|
if sum(children)>1 % enough leaves to export ?
|
|
newtr=phytree;
|
|
ipermuta(permuta) = 1:length(permuta);
|
|
numLeaves = (ipermuta(end) + 1)/2;
|
|
newtr.tree = ipermuta(tr.tree(permuta(numLeaves+1:end)-tr.numLeaves,:));
|
|
newtr.dist = tr.dist(permuta);
|
|
newtr.names = tr.names(permuta);
|
|
if ToWS % export to workspace ?
|
|
s = inputdlg('Workspace variable name ?','Export to Workspace',1);
|
|
while ~(isempty(s) || isvarname(s{1}) || isempty(s{1}))
|
|
s = inputdlg('Not a valid variable name, type a MATLAB variable name ?','Export to Workspace',1);
|
|
end
|
|
if ~(isempty(s) || isempty(s{1}))
|
|
assignin('base',s{1},newtr)
|
|
end
|
|
else % no, then export to other viewer
|
|
view(newtr);
|
|
end
|
|
end
|
|
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function updateTree(h,highLight,anchor) %#ok
|
|
% Redraws the tree depending on the active Branches
|
|
% rather than erase and redraw, we only change specific fields in hlines
|
|
% and hdots.
|
|
|
|
tr = get(h,'userdata');
|
|
activeBranches=find(tr.activeBranches)';
|
|
oldPos = tr.y(anchor);
|
|
|
|
% propagate last leaf
|
|
lastleaf = 1:tr.numLabels;
|
|
for ind = tr.numBranches:-1:1
|
|
if ~tr.activeNodes(tr.tree(ind,1))
|
|
lastleaf(tr.tree(ind,:))=lastleaf(ind+tr.numLeaves);
|
|
end
|
|
end
|
|
|
|
% find x coordinates of branches
|
|
tr.x = tr.dist;
|
|
for ind = tr.numBranches:-1:1
|
|
tr.x(tr.tree(ind,:)) = tr.x(tr.tree(ind,:)) + tr.x(ind+tr.numLeaves);
|
|
end
|
|
|
|
% find y coordinates of branches
|
|
dummy = lastleaf([true,diff(lastleaf(1:tr.numLeaves))~=0]);
|
|
tr.y=zeros(tr.numLabels,1);
|
|
tr.y(dummy)=1:length(dummy);
|
|
for ind = activeBranches
|
|
tr.y(ind+tr.numLeaves) = mean(tr.y(tr.tree(ind,:)));
|
|
end
|
|
|
|
% update right labels
|
|
todis = tr.names(dummy);
|
|
set(tr.ha,'ytick',1:length(dummy),'yticklabel',todis)
|
|
|
|
% show only active branches
|
|
set(tr.hlines,'Visible','off')
|
|
set(tr.hlines(tr.activeNodes),'Visible','on')
|
|
|
|
% update coordinates in lines
|
|
for ind = 1:tr.numLabels-1
|
|
set(tr.hlines(ind),'Ydata',tr.y([ind,ind,tr.par(ind)]))
|
|
set(tr.hlines(ind),'Xdata',tr.x([ind,tr.par([ind ind])]))
|
|
end
|
|
set(tr.hlines(tr.numLabels),'Ydata',tr.y(tr.numLabels)*[1 1 1])
|
|
set(tr.hlines(tr.numLabels),'Xdata',tr.x(tr.numLabels)*[1 1 1])
|
|
|
|
% update dots
|
|
mask = false(tr.numLabels,1); mask(1:tr.numLeaves) = true;
|
|
set(tr.hdots(1),'Ydata',tr.y(tr.activeNodes&~mask),'Xdata',tr.x(tr.activeNodes&~mask))
|
|
set(tr.hdots(2),'Ydata',tr.y(tr.activeNodes&mask),'Xdata',tr.x(tr.activeNodes&mask))
|
|
|
|
% update red dots
|
|
set(tr.hseldots(1),'Ydata',tr.y(tr.activeNodes&~mask&tr.selected),'Xdata',tr.x(tr.activeNodes&~mask&tr.selected))
|
|
set(tr.hseldots(2),'Ydata',tr.y(tr.activeNodes&mask&tr.selected),'Xdata',tr.x(tr.activeNodes&mask&tr.selected))
|
|
|
|
% set the axis holders
|
|
set(tr.axhold,'Ydata',[0.5,max(tr.y(tr.activeNodes))+0.5])
|
|
if numel(oldPos)
|
|
set(tr.ha,'ylim',get(tr.ha,'ylim')+tr.y(anchor)-oldPos);
|
|
else
|
|
set(tr.ha,'ylim',get(tr.ha,'ylim')) % just touch 'YLim' such that the listener is triggered
|
|
end
|
|
|
|
% turn on indicative modes
|
|
tr.indicativeMode = false;
|
|
set([tr.datatip tr.hpathline],'visible','off')
|
|
set(tr.hlines,'color','black')
|
|
set(tr.hldots(1),'Xdata',[],'Ydata',[])
|
|
set(tr.hldots(2),'Xdata',[],'Ydata',[])
|
|
|
|
% save figure data
|
|
set(h,'Userdata',tr)
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function path2r = path2root(tr,from)
|
|
% helper function, finds path to root
|
|
path2r = false(tr.numLabels,1);
|
|
if (numel(from)~=1 && sum(from)~=1)
|
|
return;
|
|
end
|
|
path2r(from) = true;
|
|
temp = find(path2r);
|
|
if numel(temp)
|
|
while temp~=tr.numLabels;
|
|
temp = tr.par(temp);
|
|
path2r(temp) = true;
|
|
end
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function [pathA,pathB] = path2sel(tr,from)
|
|
% helper function, finds path to selected node
|
|
path2rt = path2root(tr,from);
|
|
commonPath = tr.sel2root & path2rt;
|
|
commonPath(find(commonPath,1)) = false;
|
|
pathB = tr.sel2root & ~commonPath;
|
|
pathA = path2rt & ~commonPath;
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function tr = doBasicCalculations(tr)
|
|
% helper function to compute and find some features of the tree
|
|
tr = struct(tr);
|
|
tr.numBranches = size(tr.tree,1);
|
|
tr.numLeaves = tr.numBranches + 1;
|
|
tr.numLabels = tr.numBranches + tr.numLeaves;
|
|
|
|
% obtain parents for every node
|
|
tr.par(tr.tree(:)) = tr.numLeaves + [1:tr.numBranches 1:tr.numBranches];
|
|
|
|
% calculate the distance to the closest leaf for every node
|
|
% needed for fast threshold cut
|
|
tr.dist2Leaf = zeros(tr.numLabels,1);
|
|
for ind = 1:tr.numBranches
|
|
tr.dist2Leaf(ind+tr.numLeaves) = ...
|
|
min(tr.dist2Leaf(tr.tree(ind,:))+tr.dist(tr.tree(ind,:)));
|
|
end
|
|
|
|
% calculate drawing coordinates for the tree: x coordinated will never
|
|
% change, but y coordinates may change depending on the active branches and
|
|
% nodes.
|
|
tr.x = tr.dist; tr.y=[1:tr.numLeaves zeros(1,tr.numBranches)]';
|
|
for ind = tr.numBranches:-1:1
|
|
tr.x(tr.tree(ind,:)) = tr.x(tr.tree(ind,:)) + tr.x(ind+tr.numLeaves);
|
|
end
|
|
for ind =1:tr.numBranches
|
|
tr.y(ind+tr.numLeaves) = mean(tr.y(tr.tree(ind,:)));
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function correctFigureSize(fig,recommendedHeight)
|
|
% helper function to increase initial figure size depending on the screen &
|
|
% tree sizes
|
|
screenSize = diff(reshape(get(0,'ScreenSize'),2,2),[],2)-100;
|
|
% 100 gives extra space for the figure header and win toolbar
|
|
position = get(fig,'Position');
|
|
if recommendedHeight > position(4)
|
|
if recommendedHeight < sum(position([2 4]))
|
|
position(2) = sum(position([2 4])) - recommendedHeight;
|
|
position(4) = recommendedHeight;
|
|
elseif recommendedHeight < screenSize(2)
|
|
position(2) = 30;
|
|
position(4) = recommendedHeight;
|
|
else
|
|
position(2) = 30;
|
|
position(4) = screenSize(2);
|
|
end
|
|
set(fig,'Position',position)
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function hToggleToolbar = makePhyTreeViewerToolbar(fig)
|
|
% helper function to set the toolbar
|
|
%
|
|
% hToggleToolbar contains handles to easy change the state on/off when
|
|
% changing modes
|
|
|
|
oldSH = get(0,'ShowHiddenHandles');
|
|
set(0,'ShowHiddenHandles','on')
|
|
|
|
set(fig,'toolbar','figure') % needs to update because uicontrols turn it off
|
|
|
|
% Fix toolbar options, we keep: ZoomIn,ZoomOut,Pan
|
|
hw = findall(fig,'type','uitoolbar');
|
|
hf = get(hw,'Children');
|
|
h1 = findall(hf,'Tag','Exploration.Pan');
|
|
h2 = findall(hf,'Tag','Exploration.ZoomOut');
|
|
h3 = findall(hf,'Tag','Exploration.ZoomIn');
|
|
delete(setxor(hf,[h1,h2,h3]))
|
|
set([h1 h2 h3],'Separator','off','clickedCallback',@changeEditMode);
|
|
|
|
% load icons
|
|
load(fullfile(matlabroot,'toolbox','bioinfo','bioinfo','@phytree','phytreeicons'))
|
|
|
|
h4 = uitoggletool('ToolTip','Inspect Tool Mode','separator','on',...
|
|
'Tag','Inspect', 'CData',icons(:,:,1:3)); %#ok
|
|
h5 = uitoggletool('ToolTip','Collapse/Expand Branch Mode',...
|
|
'Tag','Collapse/Expand','CData',icons(:,:,4:6)); %#ok
|
|
h6 = uitoggletool('ToolTip','Rotate Branch Mode',...
|
|
'Tag','Rotate Branch', 'CData',icons(:,:,7:9)); %#ok
|
|
h7 = uitoggletool('ToolTip','Rename Leaf/Branch Mode',...
|
|
'Tag','Rename', 'CData',icons(:,:,10:12)); %#ok
|
|
h8 = uitoggletool('ToolTip','Prune (delete) Leaf/Branch Mode',...
|
|
'Tag','Prune', 'CData',icons(:,:,13:15)); %#ok
|
|
set([h4 h5 h6 h7 h8],'clickedCallback',@changeEditMode,'state','off',...
|
|
'Serializable','off','HandleVisibility','off');
|
|
hToggleToolbar = [h4 h5 h6 h7 h8 h1 h2 h3];
|
|
set(0,'ShowHiddenHandles',oldSH)
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function hToggleUIMenu = makePhyTreeViewerUIMenus(fig)
|
|
% helper function to set UI menus
|
|
%
|
|
% hToggleUIMenu contains handles to easy check on/off modes in the UI menu
|
|
|
|
oldSH = get(0,'ShowHiddenHandles');
|
|
set(0,'ShowHiddenHandles','on')
|
|
|
|
% delete figure menus not used
|
|
h1 = findall(fig,'Type','uimenu', 'Label','&Edit');
|
|
h2 = findall(fig,'Type','uimenu', 'Label','&View');
|
|
h3 = findall(fig,'Type','uimenu', 'Label','&Insert');
|
|
h4 = findall(fig,'Type','uimenu', 'Label','&Desktop');
|
|
delete([h1,h2,h3,h4])
|
|
|
|
% Repair "File" menu
|
|
hw = findall(fig,'Type','uimenu', 'Label','&File');
|
|
hf = get(hw,'children');
|
|
h1 = findall(hw,'Label','Expo&rt Setup...');
|
|
h3 = findall(hw,'Label','Print Pre&view...');
|
|
h4 = findall(hw,'Label','&Print...');
|
|
delete(setxor(hf,[h1,h3,h4]))
|
|
uimenu(hw,'Label','New Tool...', 'Position',1,'Callback','phytreetool')
|
|
uimenu(hw,'Label','Open...', 'Position',2,'Callback',@loadNewick)
|
|
uimenu(hw,'Label','Import from Workspace...','Position',3,'Callback',@importfromwsdlg)
|
|
uimenu(hw,'Label','Open Original in New Tool','Position',4,'Callback',@restoreTree)
|
|
uimenu(hw,'Label','Save As...', 'Position',5,'Callback',@saveNewick,'Separator','on')
|
|
item0 = uimenu(hw ,'Label','Print to Figure','Position',6);
|
|
uimenu(item0,'Label','With Hidden Nodes...','Callback',{@publishdlg,1,0});
|
|
uimenu(item0,'Label','Only Displayed...', 'Callback',{@publishdlg,0,0});
|
|
item1 = uimenu(hw,'Label','Export to New Tool','Position',7);
|
|
uimenu(item1,'Label','With Hidden Nodes...','Callback',{@exportSubtree,1,0,0});
|
|
uimenu(item1,'Label','Only Displayed...', 'Callback',{@exportSubtree,0,0,0});
|
|
item2 = uimenu(hw,'Label','Export to Workspace','Position',8);
|
|
uimenu(item2,'Label','With Hidden Nodes...','Callback',{@exportSubtree,1,1,0});
|
|
uimenu(item2,'Label','Only Displayed...', 'Callback',{@exportSubtree,0,1,0});
|
|
uimenu(hw,'Label','Exit','Separator','on','Position',12,'Callback',@closeNewick)
|
|
set(h1,'Separator','on')
|
|
|
|
% Repair "Tools" menu
|
|
hw = findall(fig,'Type','uimenu','Label','&Tools');
|
|
hf = get(hw,'children');
|
|
h1 = findall(hw,'Tag','figMenuZoomIn'); set(h1,'Callback',{@changeEditMode,gcbo});
|
|
h2 = findall(hw,'Tag','figMenuZoomOut'); set(h2,'Callback',{@changeEditMode,gcbo});
|
|
h3 = findall(hw,'Tag','figMenuPan'); set(h3,'Callback',{@changeEditMode,gcbo});
|
|
h4 = findall(hw,'Tag','figMenuResetView'); set(h4,'Callback',{@myResetView,gcbo});
|
|
h5 = findall(hw,'Tag','figMenuOptions');
|
|
set([h1,h4],'separator','off')
|
|
delete(setxor(hf,[h1,h2,h3,h4,h5]))
|
|
delete(findall(h5,'Tag','figMenuOptionsDatatip'))
|
|
delete(findall(h5,'Tag','figMenuOptionsDataBar'))
|
|
h6 = uimenu(hw,'Label','Inspect','Position',1,'Callback',@changeEditMode);
|
|
h7 = uimenu(hw,'Label','Collapse/Expand','Position',2,...
|
|
'Callback',@changeEditMode);
|
|
h8 = uimenu(hw,'Label','Rotate Branch','Position',3,...
|
|
'Callback',@changeEditMode);
|
|
h9 = uimenu(hw,'Label','Rename','Position',4, 'Callback',@changeEditMode);
|
|
h10 = uimenu(hw,'Label','Prune','Position',5, 'Callback',@changeEditMode);
|
|
item3 = uimenu(hw,'Label','Threshold Collapse','Position',9,'Separator','on');
|
|
uimenu(item3,'Label','Distance to Leaves', 'Callback',@thresholdCut);
|
|
uimenu(item3,'Label','Distance to Root', 'Callback',@thresholdCut);
|
|
uimenu(hw,'Label','Expand All','Position',10,'Callback',@expandAll);
|
|
uimenu(hw,'Label','Find Leaf/Branch...','Position',11,'Callback',@findNode);
|
|
uimenu(hw,'Label','Fit to Window','Position',12,'Separator','on',...
|
|
'Callback',@autoFit);
|
|
set(h1,'Separator','on')
|
|
|
|
% Repair "Help" menu
|
|
hw = findall(fig,'Type','uimenu','Label','&Help');
|
|
delete(get(hw,'children'));
|
|
uimenu(hw,'Label','Bioinformatics Toolbox Help','Position',1,'Callback',...
|
|
'web([docroot ''/toolbox/bioinfo/bioinfo_product_page.html''])')
|
|
uimenu(hw,'Label','Phylogenetic Tree Tool Help','Position',2,'Callback',...
|
|
['helpview(fullfile(docroot,''toolbox'',''bioinfo'', ''bioinfo.map'')' ...
|
|
',''phytreetool_reference'')' ] )
|
|
uimenu(hw,'Label','Demos','Position',3,'Separator','on',...
|
|
'Callback','demo(''toolbox'',''bioinfo'')')
|
|
tlbx = ver('bioinfo');
|
|
mailstr = ['web(''mailto:bioinfofeedback@mathworks.com?subject=',...
|
|
'Feedback%20for%20Phytreetool%20in%20Bioinformatics',...
|
|
'%20Toolbox%20',tlbx(1).Version,''')'];
|
|
uimenu(hw,'Label','Send Feedback','Position',4,'Separator','on',...
|
|
'Callback',mailstr);
|
|
set(0,'ShowHiddenHandles',oldSH)
|
|
hToggleUIMenu = [h6 h7 h8 h9 h10 h1 h2 h3];
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function [hToggleContextMenu,hAxisContextMenu,hDotsContextMenu] = ...
|
|
makePhyTreeViewerContextMenus(fig) %#ok
|
|
% helper function to set context menus
|
|
%
|
|
% hToggleContextMenu contains handles to easy check on/off modes in the
|
|
% Context Menu
|
|
% hAxisContextMenu contains handle to the Axis Context Menu to used to
|
|
% reactivate it
|
|
% hDotsContextMenu contains handle to the Dots Context Menu to used to
|
|
% reactivate it
|
|
|
|
% set up context menu for the axes (when mouse is not over a node)
|
|
hcm1 = uicontextmenu('Callback',@hideActiveIndicators);
|
|
|
|
h1 = uimenu(hcm1,'Label','Inspect', 'Callback',@changeEditMode);
|
|
h2 = uimenu(hcm1,'Label','Collapse/Expand','Callback',@changeEditMode);
|
|
h3 = uimenu(hcm1,'Label','Rotate Branch', 'Callback',@changeEditMode);
|
|
h4 = uimenu(hcm1,'Label','Rename', 'Callback',@changeEditMode);
|
|
h5 = uimenu(hcm1,'Label','Prune', 'Callback',@changeEditMode);
|
|
item4 = uimenu(hcm1 , 'Label', 'Threshold Collapse','Separator','on');
|
|
uimenu(item4,'Label','Distance to Leaves', 'Callback',@thresholdCut);
|
|
uimenu(item4,'Label','Distance to Root', 'Callback',@thresholdCut);
|
|
uimenu(hcm1,'Label','Expand All','Callback',@expandAll);
|
|
uimenu(hcm1 ,'Label','Find Leaf/Branch...', 'Callback',@findNode);
|
|
uimenu(hcm1 ,'Label','Fit to Window','Separator','on','Callback',@autoFit);
|
|
uimenu(hcm1 ,'Label','Reset to Original View', 'Callback',@myResetView);
|
|
|
|
% context menu for dots (when mouse over a dot and right mouse button pressed)
|
|
hcm2 = uicontextmenu('Callback',@enableOptionsContextMenu);
|
|
uimenu(hcm2,'Label','Collapse/Expand', 'Callback',@collapseExpand);
|
|
uimenu(hcm2,'Label','Rotate Branch', 'Callback',@rotateBranch);
|
|
uimenu(hcm2,'Label','Rename', 'Callback',@renameNode);
|
|
uimenu(hcm2,'Label','Prune', 'Callback',@pruneTree);
|
|
item0 = uimenu(hcm2,'Label','Print to Figure','Separator','on');
|
|
uimenu(item0,'Label','With Hidden Nodes...','Callback',{@publishdlg,1,1});
|
|
uimenu(item0,'Label','Only Displayed...', 'Callback',{@publishdlg,0,1});
|
|
item1 = uimenu(hcm2,'Label','Export to New Tool');
|
|
uimenu(item1,'Label','With Hidden Nodes...','Callback',{@exportSubtree,1,0,1});
|
|
uimenu(item1,'Label','Only Displayed...', 'Callback',{@exportSubtree,0,0,1});
|
|
item2 = uimenu(hcm2,'Label','Export to Workspace');
|
|
uimenu(item2,'Label','With Hidden Nodes...','Callback',{@exportSubtree,1,1,1});
|
|
uimenu(item2,'Label','Only Displayed...', 'Callback',{@exportSubtree,0,1,1});
|
|
%uimenu(hcm2,'Label','Color Down', 'Callback',@colorDown);
|
|
|
|
% save context menus in my data structure to later restore them if desired
|
|
hToggleContextMenu = [h1 h2 h3 h4 h5];
|
|
hAxisContextMenu = hcm1;
|
|
hDotsContextMenu = hcm2;
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function enableOptionsContextMenu(h,varargin)
|
|
hideActiveIndicators
|
|
selectNode(h,varargin)
|
|
tr = get(gcbf,'Userdata');
|
|
hc = get(tr.hDotsContextMenu,'Children');
|
|
set(hc,'Enable','on')
|
|
if sum(tr.selected)~=1
|
|
set(hc(5),'Enable','off');
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function setupYLabelsListeners
|
|
% helper function to setsup listeners for the ylables, so we can detect if
|
|
% we would need to change the fontsize
|
|
hgp = findpackage('hg');
|
|
axesC = findclass(hgp,'axes');
|
|
figureC = findclass(hgp,'figure');
|
|
% listens when the Ylim of axes has changed
|
|
YLimListener = handle.listener(gca,axesC.findprop('YLim'),...
|
|
'PropertyPostSet',{@ylabelsListener,gcf,gca});
|
|
% listens when Position of Figure has changed
|
|
PositionListener = handle.listener(gcf,figureC.findprop('Position'),...
|
|
'PropertyPostSet',{@ylabelsListener,gcf,gca});
|
|
% store the listeners
|
|
setappdata(gcf,'PhyTreeListeners',[YLimListener, PositionListener]);
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function publishdlg(h,varargin,AllNodes,callerIsContextMenu) %#ok
|
|
% dialog window to select options for publishing
|
|
vfig = gcbf;
|
|
c = get(0,'ScreenSize')*[1 0;0 1;.5 0;0 .5];
|
|
fig = figure('WindowStyle','modal','Color',[0.831373 0.815686 0.784314],...
|
|
'Position',[c-[150 100] 300 200],'Resize','off','NumberTitle','off',...
|
|
'Name','Print Phylogenetic Tree to Figure','IntegerHandle','off' );
|
|
h1=uibuttongroup;h2=uibuttongroup;
|
|
set(h1,'Position',[.08 .35 .35 .55],'Title','Rendering Type','backgroundcolor',[0.831373 0.815686 0.784314])
|
|
set(h2,'Position',[.52 .35 .39 .55],'Title','Display Labels','backgroundcolor',[0.831373 0.815686 0.784314])
|
|
ui1=uicontrol(h1,'style','radiobutton','Position',[5 70 90 20],'string','Square','value',1,'backgroundcolor',[0.831373 0.815686 0.784314]);
|
|
ui2=uicontrol(h1,'style','radiobutton','Position',[5 40 90 20],'string','Angular','backgroundcolor',[0.831373 0.815686 0.784314]);
|
|
ui3=uicontrol(h1,'style','radiobutton','Position',[5 10 90 20],'string','Radial','backgroundcolor',[0.831373 0.815686 0.784314]);
|
|
ui4=uicontrol(h2,'style','checkbox','Position',[5 70 109 20],'string','Branch Nodes','backgroundcolor',[0.831373 0.815686 0.784314]);
|
|
ui5=uicontrol(h2,'style','checkbox','Position',[5 40 109 20],'string','Leaf Nodes','backgroundcolor',[0.831373 0.815686 0.784314]);
|
|
ui6=uicontrol(h2,'style','checkbox','Position',[5 10 109 20],'string','Terminal Nodes','value',1,'backgroundcolor',[0.831373 0.815686 0.784314]);
|
|
uicontrol(fig,'style','pushbutton','Position',[70 20 60 30],'string','Print','Callback',{@doPublishFigure,AllNodes,callerIsContextMenu});
|
|
uicontrol(fig,'style','pushbutton','Position',[155 20 60 30],'string','Cancel','Callback',{@doPublishFigure,AllNodes,callerIsContextMenu});
|
|
set(fig,'Userdata',[ui1 ui2 ui3 ui4 ui5 ui6 vfig]);
|
|
set(h1,'SelectionChangeFcn',{@toggleCheckBoxs,ui3,ui6})
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function toggleCheckBoxs(h,event,h3,h6) %#ok
|
|
if get(h3,'value')
|
|
set(h6,'enable','off')
|
|
else
|
|
set(h6,'enable','on')
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function importfromwsdlg(h,varargin) %#ok
|
|
% dialog window to select variable from workspace
|
|
mvars = evalin('base','whos');
|
|
mvars = mvars(strmatch('phytree',{mvars(:).class})); %#ok
|
|
vfig = gcbf;
|
|
c = get(0,'ScreenSize')*[1 0;0 1;.5 0;0 .5];
|
|
fig = figure('WindowStyle','modal','Color',[0.831373 0.815686 0.784314],...
|
|
'Position',[c-[80 100] 160 220],'Resize','off','NumberTitle','off',...
|
|
'Name','Get Phytree Object','IntegerHandle','off' );
|
|
ui1=uicontrol(fig,'style','list','Position',[22 70 120 120],'string',char(mvars(:).name),'backgroundcolor','w','Callback',@loadNewick);
|
|
ui2=uicontrol(fig,'style','pushbutton','Position',[15 20 60 30],'string','Import','Callback',@loadNewick);
|
|
ui3=uicontrol(fig,'style','pushbutton','Position',[90 20 60 30],'string','Cancel','Callback',@loadNewick);
|
|
uicontrol(fig,'style','text','Position',[20 190 140 20],'string','Select phytree object:','Horizontal','left','backgroundcolor',[0.831373 0.815686 0.784314])
|
|
set(fig,'Userdata',[ui1 ui2 ui3 vfig]);
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function n = getphytreetoolnumber()
|
|
% Computes the index number for this particular tool
|
|
|
|
% first, finds the used numbers so far
|
|
allFigs = findall(0,'tag','PhyTreeTool');
|
|
usedNumbers = zeros(1,numel(allFigs)+1);
|
|
baseName = 'Phylogenetic Tree Tool ';
|
|
baseLen = length(baseName);
|
|
for i = 1:numel(allFigs)
|
|
str = get(allFigs(i),'Name');
|
|
usedNumbers(i) = str2double(str(baseLen:end));
|
|
end
|
|
|
|
% This is how we find the next index. The rule is that we find the lowest
|
|
% integer value (non-zero and positive) not yet prescribed to a phytree
|
|
% tool, This is the same way MATLAB figures behave.
|
|
n = num2str(min(setdiff(1:(max(usedNumbers)+1),usedNumbers)));
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function n = getacceptedwarningfromothertools(cfig) %#ok
|
|
% Finds out if the pruning warning has been accepted
|
|
% first, finds the used numbers so far
|
|
allFigs = findall(0,'tag','PhyTreeTool');
|
|
n = 'NotDone';
|
|
for i = 1:numel(allFigs)
|
|
propsForFigure = getappdata(allFigs(i),'propsForFigure');
|
|
if isequal(propsForFigure.PruneWarning,'Done')
|
|
n = 'Done';
|
|
end
|
|
end
|
|
|
|
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
function setacceptedwarningtoothertools()
|
|
% Set the pruning warning to all the other open tools as 'Done'
|
|
allFigs = findall(0,'tag','PhyTreeTool');
|
|
for i = 1:numel(allFigs)
|
|
propsForFigure = getappdata(allFigs(i),'propsForFigure');
|
|
propsForFigure.PruneWarning = 'Done';
|
|
setappdata(allFigs(i),'propsForFigure',propsForFigure);
|
|
end
|
|
|