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