Mercurial > hg > camir-aes2014
view toolboxes/graph_visualisation/graphViz4Matlab/graphViz4Matlab.m @ 0:e9a9cd732c1e tip
first hg version after svn
author | wolffd |
---|---|
date | Tue, 10 Feb 2015 15:05:51 +0000 |
parents | |
children |
line wrap: on
line source
classdef graphViz4Matlab < handle % Visualize a graph in a Matlab figure window by specifying an % adjacency matrix and optionally node labels, descriptions, colors and the % the layout algorithm. The best layout algorithms require that graphViz be % installed, available free at <http://www.graphviz.org>. % % Matthew Dunham % University of British Columbia % Last Updated: April 24, 2010 % Requires Matlab version 2008a or newer. % % Syntax (see more examples below): % graphViz4Matlab('-adjMat',adj,'-nodeLabels',labels,'-nodeColors',colors); % % Self loops are removed and not represented on the graph. % % Once the graph is displayed, there are several operations you can perform % with the mouse. % (1) Move a single node around. % (2) Draw a mouse box around several nodes and move them all together. % (3) Enter a description for a node by double clicking it. % (4) Shade the node by right clicking it % (5) Display a node's properties on the console by shift clicking it. % (6) Increase or decrease the font size % (7) Increase or decrease the node size % (8) Tighten the axes and relax the square aspect ratio. % (9) Ask the current layout algorithm to layout the nodes as though the % arrows were pointing the other way. This only affects some of the % layouts. % (10) Change the layout algorithm and refresh the graph % % Additionally, any operation you could do with a regular Matlab figure can % be done here, e.g. annotating or saving as a pdf. % % Options are specified via name value pairs in any order. % [] denote defaults. % % '-adjMat' [example matrix] The adjacency matrix % % '-layout' [Gvizlayout if graphViz installed, else Gridlayout] % A layout object, i.e. Gvizlayout | Gridlayout | Circlelayout % (See knownLayouts Property) % % '-nodeLabels' ['1':'n'] A cell array of labels for the nodes % % '-nodeDescriptions' [{}] Longer descriptions for the nodes, displayed when % double clicking on a node. % % '-nodeColors' ['c'] A cell array or n-by-3 matrix specifying colors % for the nodes. If fewer colors than nodes are specified, % the specified colors are reused and cycled through. % % '-undirected' [false] If true, no arrows are displayed. % % '-edgeColors' [] An n-by-3 cell array listing % {fromName,toName,color} for each row. You can % list only the n < numel(edges) edges you want to % color. If you do not label the nodes, graphViz4Matlab % uses '1','2','3', etc, in which case use these. % You can specify the text 'all' in place of toName, % to mean all nodes, i.e. {fromName,'all',color} % % % '-splitLabels' [true] If true, long node labels are split into % several rows % % '-doubleClickFn' (by default, double clicking a node brings up % an edit box for the node's description, but you % can pass in a custom function handle. The function % gets passed the node's label. % Examples: % % adj = rand(5,5) > 0.8; % labels = {'First','Second','Third','Fourth','Fifth'}; % colors = {'g','b'}; % will cycle through % s = graphViz4Matlab('-adjMat',adj,'-nodeLabels',labels,'-nodeColors',colors); % freeze(s); % convert to an image % % If you are only specifying an adjacency matrix, you can omit the % '-adjMat' name as in graphViz4Matlab(adj). % % Calling graphViz4Matlab without any parameters displays an example graph. % properties(GetAccess = 'public', SetAccess = 'private') % read only path = addpath(genpath(fileparts(which(mfilename)))); % automatically adds subdirectories to path graphVizPath = setupPath(); nnodes = 0; % The number of nodes nedges = 0; % The number of edges currentLayout= []; % The current layout object layouts = []; % List currently added layout objects adjMatrix = []; % The adjacency matrix isvisible = false; % True iff the graph is being displayed nodeArray = []; % The nodes edgeArray = []; % The edges fig = []; % The main window ax = []; % The main axes doubleClickFn = []; % function to execute when a user double clicks on a node, (must be a function handle that takes in the node name selectedNode = []; % The selected node, if any minNodeSize = []; % A minimum size for the nodes maxNodeSize = []; % A maximum size for the nodes undirected = false; % If undirected, arrows are not displayed flipped = false; % If true, layout is done as though edge directions were reversed. % (does not affect the logical layout). knownLayouts = {Gvizlayout ,... % add your own layout here or use Treelayout ,... % the addLayout() method Radiallayout,... Circularlayout,... Springlayout,... Circlelayout,... Gridlayout ,... Randlayout }; defaultEdgeColor = [0,0,0];%[20,43,140]/255; edgeColors; square = true; % amounts to a the call "axis square" splitLabels = true; end properties(GetAccess = 'private', SetAccess = 'private') % These values store the initial values not the current ones. nodeLabels = {}; nodeDescriptions = {}; nodeColors = {}; end properties(GetAccess = 'protected',SetAccess = 'protected') toolbar; % The button toolbar layoutButtons; % The layout buttons fontSize; % last calculated optimal font size selectedFontSize; % previousMouseLocation; % last mouse location relative to the axes groupSelectionMode = 0; % current stage in a group selection task groupSelectedNodes; % selected nodes in a group selection groupSelectedDims; % size of enclosing rectangle of selected nodes groupSelectedRect; % a bounding rectangle for the selected nodes end methods function obj = graphViz4Matlab(varargin) % graphViz4Matlab constructor if(~exist('processArgs','file')), error('Requires processArgs() function'); end obj.addKnownLayouts(); obj.processInputs(varargin{:}) obj.addNodes(); obj.addEdges(); obj.draw(); end function draw(obj) % Draw the graph if(obj.isvisible) obj.erase() end obj.createWindow(); obj.calculateMinMaxNodeSize(); obj.layoutNodes(); obj.displayGraph(); obj.isvisible = true; obj.paperCrop(); end function fig = freeze(obj) % Freeze the current image into a regular Matlab figure figure(obj.fig); print tmp.png -dpng -r300 fig = figure; image(imread('tmp.png')); axis off; delete tmp.png; close(obj.fig); end function redraw(obj) % Redraw the graph. (You could also call draw() again but then the % window is recreated as well and it doesn't look as nice). if(~obj.isvisible) obj.draw(); return; end cla(obj.ax); obj.clearGroupSelection(); obj.calculateMinMaxNodeSize(); obj.layoutNodes(); obj.displayGraph(); end function flip(obj,varargin) % Have the layout algorithms layout the graph as though the arrows % were pointing in the opposite direction. The node connectivity % remains the same and if node one pointed to node 2 before, it % still does after. This is useful for tree layout, for example to % turn the tree on its head. Calling it twice flips it back. obj.flipped = ~obj.flipped; if(obj.isvisible) obj.redraw(); end end function erase(obj) % Erase the graph but maintain the state so that it can be redrawn. if(obj.isvisible) obj.clearGroupSelection(); delete(obj.fig); obj.isvisible = false; end end function nodeSelected(obj,node) % This function is called by nodes when they are selected by the % mouse. It should not be called manually. if(obj.groupSelectionMode == 1) obj.groupSelectionStage1(); return; end if(~isempty(obj.selectedNode)) node.deselect(); obj.selectedNode = []; return; end switch get(obj.fig,'SelectionType') case 'normal' obj.singleClick(node); case 'open' obj.doubleClick(node); case 'alt' obj.rightClick(node); otherwise obj.shiftClick(node); end end function addLayout(obj,layout) % Let the graph know about a new layout you have created so that it % will be available via a toolbar button. The layout object must be % a descendant of the Abstractlayout class. This method does not have % to be called for existing layouts, nor does it need to be called % if you passed the new layout to the constructor or to the % setLayout() method. It will not add two layouts with the same % name property. if(~ismember(layout.name,fieldnames(obj.layouts))) if(layout.isavailable()) obj.layouts.(layout.name) = layout; if(obj.isvisible) obj.addButtons(); end else warning('graphViz4Matlab:layout','This layout is not available'); end end end function setLayout(obj,layout) % Set a new layout algorithm and refresh the graph. if(layout.isavailable()) obj.addLayout(layout); obj.currentLayout = obj.layouts.(layout.name); obj.redraw(); else warning('graphViz4Matlab:layout','Sorry, this layout is not available'); end end function squareAxes(obj,varargin) % Toggle the axes from square to normal and vice versa. obj.clearGroupSelection(); if(obj.square) axis(obj.ax,'normal'); obj.square = false; else axis(obj.ax,'square'); obj.square = true; end end function tightenAxes(obj,varargin) % Tighten the axes as much as possible. obj.clearGroupSelection(); xpos = vertcat(obj.nodeArray.xpos); ypos = vertcat(obj.nodeArray.ypos); r = obj.nodeArray(1).width/2; axis(obj.ax,[min(xpos)-r,max(xpos)+r,min(ypos)-r,max(ypos)+r]); axis normal; end end % end of public methods methods(Access = 'protected') function addKnownLayouts(obj) % Add all of the known layouts obj.layouts = struct; for i=1:numel(obj.knownLayouts) layout = obj.knownLayouts{i}; if(layout.isavailable()) obj.layouts.(layout.name) = layout; end end end function processInputs(obj,varargin) % Process the inputs and perform error checking labels = {'adj', 'adjMatrix', 'adjMat', 'layout', 'nodeLabels', 'nodeDescriptions', 'nodeColors', 'undirected', 'edgeColors', 'splitLabels', 'doubleClickFn'}; for i=1:numel(varargin) arg = varargin{i}; if ~ischar(arg), continue; end for j = 1:numel(labels) if strcmpi(arg, labels{i}); varargin{i} = ['-', arg]; end if strcmpi(arg, '-adj') || strcmpi(arg, '-adjMatrix') varargin{i} = '-adjMat'; end end end [adjMatrix, currentLayout, nodeLabels, nodeDescriptions, nodeColors,obj.undirected,obj.edgeColors,obj.splitLabels,obj.doubleClickFn] = processArgs(varargin,... '-adjMat' , [] ,... '-layout' , [] ,... '-nodeLabels' , {} ,... '-nodeDescriptions' , {} ,... '-nodeColors' , {} ,... '-undirected' , false ,... '-edgeColors' , [] ,... '-splitLabels' , true ,... '-doubleClickFn' , [] ); if(~isempty(currentLayout) && ~isavailable(currentLayout)) currentLayout = []; end if(isempty(adjMatrix)) adjMatrix = [0 0 0 0; 1 0 0 0; 1 1 0 0; 1 1 1 0]; % example graph end if(isempty(currentLayout)) fields = fieldnames(obj.layouts); currentLayout = obj.layouts.(fields{1}); else obj.addLayout(currentLayout); end obj.nnodes = size(adjMatrix,1); obj.adjMatrix = adjMatrix; if(isempty(nodeDescriptions)) nodeDescriptions = repmat({'Enter a description here...'},size(adjMatrix,1),1); end obj.nodeDescriptions = nodeDescriptions; obj.nodeColors = nodeColors; if(isempty(nodeLabels)) nodeLabels = cellfun(@(x)num2str(x),mat2cell(1:obj.nnodes,1,ones(1,obj.nnodes)),'UniformOutput',false); end obj.nodeLabels = nodeLabels; if(~isequal(numel(obj.nodeLabels),size(adjMatrix,1),size(adjMatrix,2))) error('graphViz4Matlab:dimMismatch','The number of labels must match the dimensions of adjmatrix.'); end obj.currentLayout = currentLayout; end function createWindow(obj) % Create the main window obj.fig = figure(floor(1000*rand) + 1000); set(obj.fig,'Name','graphViz4Matlab',... 'NumberTitle' ,'off',... 'Color','w' ,'Toolbar','none'); obj.createAxes(); ssize = get(0,'ScreenSize'); pos = [ssize(3)/2,50,-20+ssize(3)/2,ssize(4)-200]; set(obj.fig,'Position',pos); obj.setCallbacks(); obj.addButtons(); end function createAxes(obj) % Create the axes upon which the graph will be displayed. obj.ax = axes('Parent',obj.fig,'box','on','UserData','main'); outerpos = get(obj.ax,'OuterPosition'); axpos = outerpos; axpos(4) = 0.90; axpos(2) = 0.03; axis manual if(obj.square) axis square end set(obj.ax,'Position',axpos,'XTick',[],'YTick',[],'LineWidth',0.5); set(obj.ax,'ButtonDownFcn',@obj.axPressed); end function setCallbacks(obj) % Set the callback functions for the figure, i.e. functions that % will be called when the user performs various actions. set(obj.fig,'ResizeFcn' ,@obj.windowResized); set(obj.fig,'WindowButtonMotionFcn' ,@obj.mouseMoved); set(obj.fig,'WindowButtonUpFcn' ,@obj.buttonUp); set(obj.fig,'DeleteFcn' ,@obj.deleted); end function addNodes(obj) % Add all of the nodes to the graph structure, but don't display % them yet. obj.nodeArray = []; for i=1:obj.nnodes newnode = graphViz4MatlabNode(obj.nodeLabels{i}); newnode.containingGraph = obj; newnode.showFullLabel = ~obj.splitLabels; obj.nodeArray = [obj.nodeArray newnode]; end obj.addNodeDescriptions(obj.nodeDescriptions); obj.addNodeColors(obj.nodeColors); end function addNodeDescriptions(obj,nodeDescriptions) % Add any descriptions to the newly created nodes. if(~isempty(nodeDescriptions)) if(numel(nodeDescriptions) == 1) nodeDescriptions = repmat(nodeDescriptions,obj.nnodes,1); end for i=1:obj.nnodes obj.nodeArray(i).description = nodeDescriptions{i}; end end end function addNodeColors(obj,nodeColors) % Shade the nodes according to the specified colors. If too few % colors are specified, they are cycled through. if(~isempty(nodeColors)) if(~iscell(nodeColors)) nodeColors = mat2cell(nodeColors,ones(1,size(nodeColors,1)),size(nodeColors,2)); end if(size(nodeColors,2) > size(nodeColors,1)) nodeColors = nodeColors'; end if(numel(nodeColors) < obj.nnodes) nodeColors = repmat(nodeColors,ceil(obj.nnodes/numel(nodeColors)),1); nodeColors = nodeColors(1:obj.nnodes); end for i=1:obj.nnodes obj.nodeArray(i).shade(nodeColors{i}); end obj.nodeColors = nodeColors; end end function addEdges(obj) % Add all of the edges to the graph structure, but don't display % them yet. if(any(diag(obj.adjMatrix))) fprintf('\nRemoving Self Loops\n'); obj.adjMatrix = obj.adjMatrix - diag(diag(obj.adjMatrix)); end obj.edgeArray = struct('from',[],'to',[],'arrow',[]); counter = 1; for i=1:obj.nnodes for j=1:obj.nnodes if(obj.adjMatrix(i,j)) obj.edgeArray(counter) = struct('from',obj.nodeArray(i),'to',obj.nodeArray(j),'arrow',-1); obj.nodeArray(i).outedges = [obj.nodeArray(i).outedges,counter]; obj.nodeArray(j).inedges = [obj.nodeArray(j).inedges,counter]; counter = counter + 1; end end end obj.nedges = counter -1; end function calculateMinMaxNodeSize(obj) % calculates the maximum and minimum node sizes in data units SCREEN_PROPORTION_MAX = 1/10; SCREEN_PROPORTION_MIN = 1/35; units = get(0,'Units'); set(0,'Units','pixels'); screensize = get(0,'ScreenSize'); set(0,'Units',units); axunits = get(obj.ax,'Units'); set(obj.ax,'Units','pixels'); axsize = get(obj.ax,'Position'); set(obj.ax,'Units',axunits); if(screensize(3) < screensize(4)) dataUnitsPerPixel = abs(diff(xlim))/axsize(3); obj.minNodeSize = (SCREEN_PROPORTION_MIN*screensize(3))*dataUnitsPerPixel; obj.maxNodeSize = (SCREEN_PROPORTION_MAX*screensize(3))*dataUnitsPerPixel; else dataUnitsPerPixel = abs(diff(ylim))/axsize(4); obj.minNodeSize = (SCREEN_PROPORTION_MIN*screensize(4))*dataUnitsPerPixel; obj.maxNodeSize = (SCREEN_PROPORTION_MAX*screensize(4))*dataUnitsPerPixel; end end function layoutNodes(obj) % Layout the nodes and edges according to the current layout % algorithm. if(obj.flipped) adj = obj.adjMatrix'; else adj = obj.adjMatrix; end obj.currentLayout.dolayout(adj,obj.ax,obj.maxNodeSize); nodesize = obj.currentLayout.nodeSize(); locs = obj.currentLayout.centers(); for i=1:obj.nnodes node = obj.nodeArray(i); node.resize(nodesize); node.move(locs(i,1),locs(i,2)); end end function displayGraph(obj) % Display all of the nodes and edges. cla(obj.ax); obj.setFontSize(); for i=1:obj.nnodes node = obj.nodeArray(i); node.fontSize = obj.fontSize; node.draw(obj.ax); end displayEdges(obj); end function displayEdges(obj,indices) % Display or refresh the specified edges. If none specified, all % are refreshed. Currently only works with round nodes. figure(obj.fig); if(nargin < 2) indices = 1:obj.nedges; else indices = unique(indices); end for i=1:numel(indices) edge = obj.edgeArray(indices(i)); [X,Y,Xarrow,Yarrow] = obj.calcPositions(edge); if(ishandle(edge.arrow)) delete(edge.arrow) end hold on; edgeColor = obj.defaultEdgeColor; if ~isempty(obj.edgeColors) candidates = obj.edgeColors(findString(edge.from.label,obj.edgeColors(:,1)),:); if size(candidates,1)==1 && strcmpi(candidates(1,2),'all') edgeColor = candidates{1,3}; else edgeCol = candidates(findString(edge.to.label,candidates(:,2)),3); if ~isempty(edgeCol); edgeColor = edgeCol{1}; end end end edge.arrow = plot(X,Y,'LineWidth',2,'HitTest','off','Color',edgeColor); if(~obj.undirected) arrowHead = obj.displayArrowHead(X,Y,Xarrow,Yarrow,edgeColor); edge.arrow = [edge.arrow arrowHead]; end hold off; obj.edgeArray(indices(i)) = edge; end end function arrowHead = displayArrowHead(obj,X,Y,Xarrow,Yarrow,arrowColor) %#ok % Displays the arrow head given the appropriate coordinates % calculated via the calcPositions() function. arrowHead = patch('Faces' ,[1,2,3] ,... 'Vertices' ,[Xarrow(1) Yarrow(1); Xarrow(2) Yarrow(2) ;X(2) Y(2)],... 'FaceColor' ,arrowColor); end function [X,Y,Xarrow,Yarrow] = calcPositions(obj,edge) % Helper function for displayEdges() - calculates edge and arrow % start and end positions in data units. X = [edge.from.xpos edge.to.xpos]; Y = [edge.from.ypos edge.to.ypos]; ratio = (Y(2) - Y(1))/(X(2)-X(1)); if(isinf(ratio)) ratio = realmax; end % dx: x-distance from node1 center to perimeter in direction of node2 % dy: y-distance from node1 center to perimeter in direction of node2 % ddx: x-distance from node1 perimeter to base of arrow head % ddy: y-distance from node1 perimeter to base of arrow head % dpx: x-offset away from edge in perpendicular direction, for arrow head % dpy: y-offset away from edge in perpendicular direction, for arrow head arrowSize = obj.maxNodeSize/10; [dx,dy] = pol2cart(atan(ratio),edge.from.width/2); [ddx,ddy] = pol2cart(atan(ratio),arrowSize); ratio = 1/ratio; % now work out perpendicular directions. if(isinf(ratio)) ratio = realmax; end [dpx dpy] = pol2cart(atan(ratio),arrowSize/2); ddx = abs(ddx); ddy = abs(ddy); dpx = abs(dpx); dpy = abs(dpy); dx = abs(dx); dy = abs(dy); if(X(1) < X(2)) X(1) = X(1) + dx; X(2) = X(2) - dx; else X(1) = X(1) - dx; X(2) = X(2) + dx; end if(Y(1) < Y(2)) Y(1) = Y(1) + dy; Y(2) = Y(2) - dy; else Y(1) = Y(1) - dy; Y(2) = Y(2) + dy; end if(X(1) <= X(2) && Y(1) <= Y(2)) Xarrow(1) = X(2) - ddx - dpx; Xarrow(2) = X(2) - ddx + dpx; Yarrow(1) = Y(2) - ddy + dpy; Yarrow(2) = Y(2) - ddy - dpy; elseif(X(1) <= X(2) && Y(1) >= Y(2)) Xarrow(1) = X(2) - ddx - dpx; Xarrow(2) = X(2) - ddx + dpx; Yarrow(1) = Y(2) + ddy - dpy; Yarrow(2) = Y(2) + ddy + dpy; elseif(X(1) >= X(2) && Y(1) <= Y(2)) Xarrow(1) = X(2) + ddx - dpx; Xarrow(2) = X(2) + ddx + dpx; Yarrow(1) = Y(2) - ddy - dpy; Yarrow(2) = Y(2) - ddy + dpy; else % (X(1) >= (X(2) && Y(1) >= Y(2)) Xarrow(1) = X(2) + ddx - dpx; Xarrow(2) = X(2) + ddx + dpx; Yarrow(1) = Y(2) + ddy + dpy; Yarrow(2) = Y(2) + ddy - dpy; end end function addButtons(obj) % Add user interface buttons. if(~isempty(obj.toolbar)) if(ishandle(obj.toolbar)) delete(obj.toolbar); obj.toolbar = []; end end obj.toolbar = uitoolbar(obj.fig); % button icons load glicons; uipushtool(obj.toolbar,... 'ClickedCallback' ,@obj.decreaseFontSize,... 'TooltipString' ,'Decrease Font Size',... 'CData' ,icons.downblue); uipushtool(obj.toolbar,... 'ClickedCallback' ,@obj.increaseFontSize,... 'TooltipString' ,'Increase Font Size',... 'CData' ,icons.upblue); uipushtool(obj.toolbar,... 'ClickedCallback' ,@obj.tightenAxes,... 'TooltipString' ,'Tighten Axes',... 'CData' ,icons.expand); uipushtool(obj.toolbar,... 'ClickedCallback' ,@obj.flip,... 'TooltipString' ,'Flip/Reset Layout',... 'CData' , icons.flip); uipushtool(obj.toolbar,... 'ClickedCallback' ,@obj.shrinkNodes,... 'TooltipString' ,'Decrease Node Size',... 'CData' , icons.downdarkblue); uipushtool(obj.toolbar,... 'ClickedCallback' ,@obj.growNodes,... 'TooltipString' ,'Increase Node Size',... 'CData' , icons.updarkblue); if(~isempty(obj.layoutButtons)) for i=1:numel(obj.layoutButtons) if(ishandle(obj.layoutButtons(i))) delete(obj.layoutButtons(i)); end end obj.layoutButtons = []; end layoutNames = fieldnames(obj.layouts); for i=1:numel(layoutNames) layout = obj.layouts.(layoutNames{i}); layoutButton = uipushtool(obj.toolbar,... 'ClickedCallback', @obj.layoutButtonPushed,... 'TooltipString', layout.shortDescription,... 'UserData' , layoutNames{i},... 'Separator' , 'on',... 'CData' , layout.image); obj.layoutButtons = [obj.layoutButtons,layoutButton]; end end function setFontSize(obj) % fontsize = obj.maxFontSize; fontSize = 20; maxchars = size(char(obj.nodeLabels),2); width = obj.nodeArray(1).width; height = obj.nodeArray(1).height; xpos = -10; ypos = -10; t = text(xpos,ypos,repmat('g',1,maxchars),... 'FontUnits' , 'points' ,... 'Units' , 'data' ,... 'HorizontalAlignment' , 'center' ,... 'VerticalAlignment' , 'middle' ,... 'FontWeight' , 'demi' ,... 'LineStyle' , 'none' ,... 'Margin' , 0.01 ,... 'FontSize' , fontSize ,... 'Color' , 'w' ); extent = get(t,'Extent'); while(extent(3) > width || extent(4) > height) fontSize = fontSize - 1; if(fontSize < 2), break,end set(t,'FontSize',fontSize); extent = get(t,'Extent'); end obj.fontSize = fontSize; end function asp = aspectRatio(obj) % Return the current aspect ratio of the figure, width/height units = get(obj.ax,'Units'); set(obj.ax,'Units','pixels'); pos = get(obj.ax,'Position'); set(obj.ax,'Units',units); asp = (pos(3)/pos(4)); end function paperCrop(obj) % Make the papersize the same as the the figure size. This is % useful when saving as pdf. units = get(obj.fig,'Units'); set(obj.fig,'Units','inches'); pos = get(obj.fig,'Position'); set(obj.fig,'Units',units); set(obj.fig,'PaperPositionMode','auto','PaperSize',pos(3:4)); end %% % Callbacks function layoutButtonPushed(obj,buttonPushed,varargin) % Called when a layout button is pushed. name = get(buttonPushed,'UserData'); obj.currentLayout = obj.layouts.(name); axis square; obj.redraw; end function windowResized(obj,varargin) % This function is called whenever the window is resized. It % redraws the whole graph. if(obj.isvisible) obj.redraw; obj.paperCrop(); end end function mouseMoved(obj,varargin) % This function is called whenever the mouse moves within the % figure. if(obj.groupSelectionMode == 2) % Move all of the nodes & rectangle currentPoint = get(obj.ax,'CurrentPoint'); xlimits = get(obj.ax,'XLim'); ylimits = get(obj.ax,'YLim'); sdims = obj.groupSelectedDims; xdiff = currentPoint(1,1) - obj.previousMouseLocation(1,1); ydiff = currentPoint(1,2) - obj.previousMouseLocation(1,2); if(xdiff <=0) xdiff = max(xdiff,(xlimits(1)-sdims(1))); else xdiff = min(xdiff,xlimits(2)-sdims(2)); end if(ydiff <=0) ydiff = max(ydiff,(ylimits(1)-sdims(3))); else ydiff = min(ydiff,ylimits(2)-sdims(4)); end xnodepos = vertcat(obj.groupSelectedNodes.xpos) + xdiff; ynodepos = vertcat(obj.groupSelectedNodes.ypos) + ydiff; for i=1:numel(obj.groupSelectedNodes) obj.groupSelectedNodes(i).move(xnodepos(i),ynodepos(i)); end recpos = get(obj.groupSelectedRect,'Position'); recpos(1) = recpos(1) + xdiff; recpos(2) = recpos(2) + ydiff; obj.groupSelectedDims = [recpos(1),recpos(1)+recpos(3),recpos(2),recpos(2)+recpos(4)]; set(obj.groupSelectedRect,'Position',recpos); edges = [obj.groupSelectedNodes.inedges,obj.groupSelectedNodes.outedges]; obj.displayEdges(edges); obj.previousMouseLocation = currentPoint; else if(isempty(obj.selectedNode)), return,end currentPoint = get(obj.ax,'CurrentPoint'); x = currentPoint(1,1); y = currentPoint(1,2); xl = xlim + [obj.selectedNode.width,-obj.selectedNode.width]/2; yl = ylim + [obj.selectedNode.height,-obj.selectedNode.height]/2; x = min(max(xl(1),x),xl(2)); y = min(max(yl(1),y),yl(2)); obj.selectedNode.move(x,y); obj.displayEdges([obj.selectedNode.inedges,obj.selectedNode.outedges]); end end function buttonUp(obj,varargin) % This function executes when the mouse button is released. if(obj.groupSelectionMode == 2) obj.clearGroupSelection(); return; end if(isempty(obj.selectedNode)),return,end obj.selectedNode.deselect(); obj.selectedNode.useFullLabel = false; obj.selectedNode.fontSize = obj.selectedFontSize; obj.selectedNode.redraw(); obj.selectedNode = []; set(gcf,'Pointer','arrow'); end function axPressed(obj,varargin) % Called when the user selects the axes but not a node switch obj.groupSelectionMode case 0 % hasn't been selected yet xpos = vertcat(obj.nodeArray.xpos); ypos = vertcat(obj.nodeArray.ypos); p1 = get(obj.ax,'CurrentPoint'); rbbox; % returns after box drawn p2 = get(obj.ax,'CurrentPoint'); xleft = min(p1(1,1),p2(1,1)); xright = max(p1(1,1),p2(1,1)); ylower = min(p1(1,2),p2(1,2)); yupper = max(p1(1,2),p2(1,2)); selectedX = (xpos <= xright) & (xpos >= xleft); selectedY = (ypos <= yupper) & (ypos >= ylower); selected = selectedX & selectedY; if(~any(selected)),return,end obj.groupSelectionMode = 1; obj.groupSelectedNodes = obj.nodeArray(selected); for i=1:numel(obj.groupSelectedNodes) node = obj.groupSelectedNodes(i); node.select(); node.redraw(); end w = obj.groupSelectedNodes(1).width/2; h = obj.groupSelectedNodes(1).height/2; x = vertcat(obj.groupSelectedNodes.xpos); y = vertcat(obj.groupSelectedNodes.ypos); minx = min(x)-w; maxx = max(x)+w; miny = min(y)-h; maxy = max(y)+h; obj.groupSelectedDims = [minx,maxx,miny,maxy]; obj.groupSelectedRect = rectangle('Position',[minx,miny,maxx-minx,maxy-miny],'LineStyle','--','EdgeColor','r'); case 1 % nodes selected obj.groupSelectionStage1(); case 2 %not ever reached in this function obj.clearGroupSelection(); end end function groupSelectionStage1(obj) % Called after a group of nodes has been selected and the mouse % button has been pressed somewhere on the axes, (or on a node). p = get(obj.ax,'CurrentPoint'); obj.previousMouseLocation = p; dims = obj.groupSelectedDims; if(p(1,1) >= dims(1) && p(1,1) <= dims(2) && p(1,2) >= dims(3) && p(1,2) <=dims(4)) set(gcf,'Pointer','hand'); obj.groupSelectionMode = 2; else obj.clearGroupSelection(); end end function clearGroupSelection(obj) % Clear a group selection if(ishandle(obj.groupSelectedRect)) delete(obj.groupSelectedRect); end obj.groupSelectedRect = []; for i=1:numel(obj.groupSelectedNodes) obj.groupSelectedNodes(i).deselect(); end obj.groupSelectedNodes = []; obj.groupSelectedDims = []; obj.groupSelectionMode = 0; set(gcf,'Pointer','arrow'); end function deleted(obj,varargin) % Called when the figure is deleted by the user. obj.isvisible = false; obj.clearGroupSelection(); end function singleClick(obj,node) % Called when a user single clicks on a node. obj.selectedNode = node; node.select(); set(gcf,'Pointer','hand'); obj.selectedFontSize = node.fontSize; node.useFullLabel = true; node.fontSize = max(15,node.fontSize*1.5); node.redraw(); end function doubleClick(obj,node) % Called when a user double clicks on a node if isempty(obj.doubleClickFn) description = node.description; if(~iscell(description)) description = {description}; end answer = inputdlg('',node.label,4,description); if(~isempty(answer)) node.description = answer; end else obj.doubleClickFn(node.label); end end function rightClick(obj,node) %#ok % Called when a user right clicks on a node if(node.isshaded) node.unshade(); else node.shade(); end end function shiftClick(obj,node) %#ok % Called when a user shift clicks on a node display(node); end end methods % Callbacks that can be called by the user programmatically. function shrinkNodes(obj,varargin) % Shrink the nodes to 95% of their original size, (but not smaller % than a calculated minimum. obj.clearGroupSelection(); s = max(0.8*obj.nodeArray(1).width,obj.minNodeSize); obj.nodeArray(1).resize(s); obj.setFontSize(); for i=1:obj.nnodes node = obj.nodeArray(i); node.fontSize = obj.fontSize; node.resize(s); end obj.displayEdges(); end function growNodes(obj,varargin) % Grow the nodes to 1/0.95 times their original size, (but not % larger than a calculated maximum. obj.clearGroupSelection(); s = min(obj.nodeArray(1).width/0.8,1.5*obj.maxNodeSize); obj.nodeArray(1).resize(s); obj.setFontSize(); for i=1:obj.nnodes node = obj.nodeArray(i); node.fontSize = obj.fontSize; node.resize(s); end obj.displayEdges(); end function increaseFontSize(obj,varargin) % Increase the fontsize of all the nodes by 0.5 points. current = get(obj.nodeArray(1).labelhandle,'FontSize'); newsize = current + 1; for i=1:numel(obj.nodeArray) node = obj.nodeArray(i); node.fontSize = newsize; node.redraw(); end end function decreaseFontSize(obj,varargin) % Decrease the fontsize of all the nodes by 0.5 points. current = get(obj.nodeArray(1).labelhandle,'FontSize'); newsize = max(current - 1,1); for i=1:numel(obj.nodeArray) node = obj.nodeArray(i); node.fontSize = newsize; node.redraw(); end end function XY = getNodePositions(obj) % Return the current positions of the nodes. The bottom left % corner is [0 0] and the top right is [1 1]. Node positions % refer to the centre of a node. XY = zeros(obj.nnodes, 2); for i=1:obj.nnodes XY(i, 1) = obj.nodeArray(i).xpos; XY(i, 2) = obj.nodeArray(i).ypos; end end function setNodePositions(obj, XY) % Programmatically set the node positions % XY(i, 1) is the xposition of node i, XY(i, 2) is the yposition. for i=1:obj.nnodes obj.nodeArray(i).move(XY(i, 1), XY(i, 2)); end obj.displayGraph(); end function moveNode(obj, nodeIndex, xpos, ypos) % Programmatically set a node position. obj.nodeArray(nodeIndex).move(xpos, ypos); obj.displayGraph(); end end end