wolffd@0
|
1 classdef graphViz4Matlab < handle
|
wolffd@0
|
2 % Visualize a graph in a Matlab figure window by specifying an
|
wolffd@0
|
3 % adjacency matrix and optionally node labels, descriptions, colors and the
|
wolffd@0
|
4 % the layout algorithm. The best layout algorithms require that graphViz be
|
wolffd@0
|
5 % installed, available free at <http://www.graphviz.org>.
|
wolffd@0
|
6 %
|
wolffd@0
|
7 % Matthew Dunham
|
wolffd@0
|
8 % University of British Columbia
|
wolffd@0
|
9 % Last Updated: April 24, 2010
|
wolffd@0
|
10 % Requires Matlab version 2008a or newer.
|
wolffd@0
|
11 %
|
wolffd@0
|
12 % Syntax (see more examples below):
|
wolffd@0
|
13 % graphViz4Matlab('-adjMat',adj,'-nodeLabels',labels,'-nodeColors',colors);
|
wolffd@0
|
14 %
|
wolffd@0
|
15 % Self loops are removed and not represented on the graph.
|
wolffd@0
|
16 %
|
wolffd@0
|
17 % Once the graph is displayed, there are several operations you can perform
|
wolffd@0
|
18 % with the mouse.
|
wolffd@0
|
19 % (1) Move a single node around.
|
wolffd@0
|
20 % (2) Draw a mouse box around several nodes and move them all together.
|
wolffd@0
|
21 % (3) Enter a description for a node by double clicking it.
|
wolffd@0
|
22 % (4) Shade the node by right clicking it
|
wolffd@0
|
23 % (5) Display a node's properties on the console by shift clicking it.
|
wolffd@0
|
24 % (6) Increase or decrease the font size
|
wolffd@0
|
25 % (7) Increase or decrease the node size
|
wolffd@0
|
26 % (8) Tighten the axes and relax the square aspect ratio.
|
wolffd@0
|
27 % (9) Ask the current layout algorithm to layout the nodes as though the
|
wolffd@0
|
28 % arrows were pointing the other way. This only affects some of the
|
wolffd@0
|
29 % layouts.
|
wolffd@0
|
30 % (10) Change the layout algorithm and refresh the graph
|
wolffd@0
|
31 %
|
wolffd@0
|
32 % Additionally, any operation you could do with a regular Matlab figure can
|
wolffd@0
|
33 % be done here, e.g. annotating or saving as a pdf.
|
wolffd@0
|
34 %
|
wolffd@0
|
35 % Options are specified via name value pairs in any order.
|
wolffd@0
|
36 % [] denote defaults.
|
wolffd@0
|
37 %
|
wolffd@0
|
38 % '-adjMat' [example matrix] The adjacency matrix
|
wolffd@0
|
39 %
|
wolffd@0
|
40 % '-layout' [Gvizlayout if graphViz installed, else Gridlayout]
|
wolffd@0
|
41 % A layout object, i.e. Gvizlayout | Gridlayout | Circlelayout
|
wolffd@0
|
42 % (See knownLayouts Property)
|
wolffd@0
|
43 %
|
wolffd@0
|
44 % '-nodeLabels' ['1':'n'] A cell array of labels for the nodes
|
wolffd@0
|
45 %
|
wolffd@0
|
46 % '-nodeDescriptions' [{}] Longer descriptions for the nodes, displayed when
|
wolffd@0
|
47 % double clicking on a node.
|
wolffd@0
|
48 %
|
wolffd@0
|
49 % '-nodeColors' ['c'] A cell array or n-by-3 matrix specifying colors
|
wolffd@0
|
50 % for the nodes. If fewer colors than nodes are specified,
|
wolffd@0
|
51 % the specified colors are reused and cycled through.
|
wolffd@0
|
52 %
|
wolffd@0
|
53 % '-undirected' [false] If true, no arrows are displayed.
|
wolffd@0
|
54 %
|
wolffd@0
|
55 % '-edgeColors' [] An n-by-3 cell array listing
|
wolffd@0
|
56 % {fromName,toName,color} for each row. You can
|
wolffd@0
|
57 % list only the n < numel(edges) edges you want to
|
wolffd@0
|
58 % color. If you do not label the nodes, graphViz4Matlab
|
wolffd@0
|
59 % uses '1','2','3', etc, in which case use these.
|
wolffd@0
|
60 % You can specify the text 'all' in place of toName,
|
wolffd@0
|
61 % to mean all nodes, i.e. {fromName,'all',color}
|
wolffd@0
|
62 %
|
wolffd@0
|
63 %
|
wolffd@0
|
64 % '-splitLabels' [true] If true, long node labels are split into
|
wolffd@0
|
65 % several rows
|
wolffd@0
|
66 %
|
wolffd@0
|
67 % '-doubleClickFn' (by default, double clicking a node brings up
|
wolffd@0
|
68 % an edit box for the node's description, but you
|
wolffd@0
|
69 % can pass in a custom function handle. The function
|
wolffd@0
|
70 % gets passed the node's label.
|
wolffd@0
|
71 % Examples:
|
wolffd@0
|
72 %
|
wolffd@0
|
73 % adj = rand(5,5) > 0.8;
|
wolffd@0
|
74 % labels = {'First','Second','Third','Fourth','Fifth'};
|
wolffd@0
|
75 % colors = {'g','b'}; % will cycle through
|
wolffd@0
|
76 % s = graphViz4Matlab('-adjMat',adj,'-nodeLabels',labels,'-nodeColors',colors);
|
wolffd@0
|
77 % freeze(s); % convert to an image
|
wolffd@0
|
78 %
|
wolffd@0
|
79 % If you are only specifying an adjacency matrix, you can omit the
|
wolffd@0
|
80 % '-adjMat' name as in graphViz4Matlab(adj).
|
wolffd@0
|
81 %
|
wolffd@0
|
82 % Calling graphViz4Matlab without any parameters displays an example graph.
|
wolffd@0
|
83 %
|
wolffd@0
|
84
|
wolffd@0
|
85
|
wolffd@0
|
86 properties(GetAccess = 'public', SetAccess = 'private')
|
wolffd@0
|
87 % read only
|
wolffd@0
|
88 path = addpath(genpath(fileparts(which(mfilename)))); % automatically adds subdirectories to path
|
wolffd@0
|
89 graphVizPath = setupPath();
|
wolffd@0
|
90 nnodes = 0; % The number of nodes
|
wolffd@0
|
91 nedges = 0; % The number of edges
|
wolffd@0
|
92 currentLayout= []; % The current layout object
|
wolffd@0
|
93 layouts = []; % List currently added layout objects
|
wolffd@0
|
94 adjMatrix = []; % The adjacency matrix
|
wolffd@0
|
95 isvisible = false; % True iff the graph is being displayed
|
wolffd@0
|
96 nodeArray = []; % The nodes
|
wolffd@0
|
97 edgeArray = []; % The edges
|
wolffd@0
|
98 fig = []; % The main window
|
wolffd@0
|
99 ax = []; % The main axes
|
wolffd@0
|
100 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
|
101 selectedNode = []; % The selected node, if any
|
wolffd@0
|
102 minNodeSize = []; % A minimum size for the nodes
|
wolffd@0
|
103 maxNodeSize = []; % A maximum size for the nodes
|
wolffd@0
|
104 undirected = false; % If undirected, arrows are not displayed
|
wolffd@0
|
105 flipped = false; % If true, layout is done as though edge directions were reversed.
|
wolffd@0
|
106 % (does not affect the logical layout).
|
wolffd@0
|
107 knownLayouts = {Gvizlayout ,... % add your own layout here or use
|
wolffd@0
|
108 Treelayout ,... % the addLayout() method
|
wolffd@0
|
109 Radiallayout,...
|
wolffd@0
|
110 Circularlayout,...
|
wolffd@0
|
111 Springlayout,...
|
wolffd@0
|
112 Circlelayout,...
|
wolffd@0
|
113 Gridlayout ,...
|
wolffd@0
|
114 Randlayout };
|
wolffd@0
|
115 defaultEdgeColor = [0,0,0];%[20,43,140]/255;
|
wolffd@0
|
116 edgeColors;
|
wolffd@0
|
117 square = true; % amounts to a the call "axis square"
|
wolffd@0
|
118 splitLabels = true;
|
wolffd@0
|
119 end
|
wolffd@0
|
120
|
wolffd@0
|
121 properties(GetAccess = 'private', SetAccess = 'private')
|
wolffd@0
|
122 % These values store the initial values not the current ones.
|
wolffd@0
|
123 nodeLabels = {};
|
wolffd@0
|
124 nodeDescriptions = {};
|
wolffd@0
|
125 nodeColors = {};
|
wolffd@0
|
126 end
|
wolffd@0
|
127
|
wolffd@0
|
128 properties(GetAccess = 'protected',SetAccess = 'protected')
|
wolffd@0
|
129 toolbar; % The button toolbar
|
wolffd@0
|
130 layoutButtons; % The layout buttons
|
wolffd@0
|
131 fontSize; % last calculated optimal font size
|
wolffd@0
|
132 selectedFontSize; %
|
wolffd@0
|
133
|
wolffd@0
|
134 previousMouseLocation; % last mouse location relative to the axes
|
wolffd@0
|
135 groupSelectionMode = 0; % current stage in a group selection task
|
wolffd@0
|
136 groupSelectedNodes; % selected nodes in a group selection
|
wolffd@0
|
137 groupSelectedDims; % size of enclosing rectangle of selected nodes
|
wolffd@0
|
138 groupSelectedRect; % a bounding rectangle for the selected nodes
|
wolffd@0
|
139 end
|
wolffd@0
|
140
|
wolffd@0
|
141 methods
|
wolffd@0
|
142
|
wolffd@0
|
143 function obj = graphViz4Matlab(varargin)
|
wolffd@0
|
144 % graphViz4Matlab constructor
|
wolffd@0
|
145 if(~exist('processArgs','file')), error('Requires processArgs() function'); end
|
wolffd@0
|
146 obj.addKnownLayouts();
|
wolffd@0
|
147 obj.processInputs(varargin{:})
|
wolffd@0
|
148 obj.addNodes();
|
wolffd@0
|
149 obj.addEdges();
|
wolffd@0
|
150 obj.draw();
|
wolffd@0
|
151 end
|
wolffd@0
|
152
|
wolffd@0
|
153 function draw(obj)
|
wolffd@0
|
154 % Draw the graph
|
wolffd@0
|
155 if(obj.isvisible)
|
wolffd@0
|
156 obj.erase()
|
wolffd@0
|
157 end
|
wolffd@0
|
158 obj.createWindow();
|
wolffd@0
|
159 obj.calculateMinMaxNodeSize();
|
wolffd@0
|
160 obj.layoutNodes();
|
wolffd@0
|
161 obj.displayGraph();
|
wolffd@0
|
162 obj.isvisible = true;
|
wolffd@0
|
163 obj.paperCrop();
|
wolffd@0
|
164 end
|
wolffd@0
|
165
|
wolffd@0
|
166 function fig = freeze(obj)
|
wolffd@0
|
167 % Freeze the current image into a regular Matlab figure
|
wolffd@0
|
168 figure(obj.fig);
|
wolffd@0
|
169 print tmp.png -dpng -r300
|
wolffd@0
|
170 fig = figure;
|
wolffd@0
|
171 image(imread('tmp.png'));
|
wolffd@0
|
172 axis off;
|
wolffd@0
|
173 delete tmp.png;
|
wolffd@0
|
174 close(obj.fig);
|
wolffd@0
|
175 end
|
wolffd@0
|
176
|
wolffd@0
|
177 function redraw(obj)
|
wolffd@0
|
178 % Redraw the graph. (You could also call draw() again but then the
|
wolffd@0
|
179 % window is recreated as well and it doesn't look as nice).
|
wolffd@0
|
180 if(~obj.isvisible)
|
wolffd@0
|
181 obj.draw();
|
wolffd@0
|
182 return;
|
wolffd@0
|
183 end
|
wolffd@0
|
184 cla(obj.ax);
|
wolffd@0
|
185 obj.clearGroupSelection();
|
wolffd@0
|
186 obj.calculateMinMaxNodeSize();
|
wolffd@0
|
187 obj.layoutNodes();
|
wolffd@0
|
188 obj.displayGraph();
|
wolffd@0
|
189 end
|
wolffd@0
|
190
|
wolffd@0
|
191 function flip(obj,varargin)
|
wolffd@0
|
192 % Have the layout algorithms layout the graph as though the arrows
|
wolffd@0
|
193 % were pointing in the opposite direction. The node connectivity
|
wolffd@0
|
194 % remains the same and if node one pointed to node 2 before, it
|
wolffd@0
|
195 % still does after. This is useful for tree layout, for example to
|
wolffd@0
|
196 % turn the tree on its head. Calling it twice flips it back.
|
wolffd@0
|
197 obj.flipped = ~obj.flipped;
|
wolffd@0
|
198 if(obj.isvisible)
|
wolffd@0
|
199 obj.redraw();
|
wolffd@0
|
200 end
|
wolffd@0
|
201 end
|
wolffd@0
|
202
|
wolffd@0
|
203 function erase(obj)
|
wolffd@0
|
204 % Erase the graph but maintain the state so that it can be redrawn.
|
wolffd@0
|
205 if(obj.isvisible)
|
wolffd@0
|
206 obj.clearGroupSelection();
|
wolffd@0
|
207 delete(obj.fig);
|
wolffd@0
|
208 obj.isvisible = false;
|
wolffd@0
|
209
|
wolffd@0
|
210 end
|
wolffd@0
|
211 end
|
wolffd@0
|
212
|
wolffd@0
|
213 function nodeSelected(obj,node)
|
wolffd@0
|
214 % This function is called by nodes when they are selected by the
|
wolffd@0
|
215 % mouse. It should not be called manually.
|
wolffd@0
|
216 if(obj.groupSelectionMode == 1)
|
wolffd@0
|
217 obj.groupSelectionStage1();
|
wolffd@0
|
218 return;
|
wolffd@0
|
219 end
|
wolffd@0
|
220 if(~isempty(obj.selectedNode))
|
wolffd@0
|
221 node.deselect();
|
wolffd@0
|
222 obj.selectedNode = [];
|
wolffd@0
|
223 return;
|
wolffd@0
|
224 end
|
wolffd@0
|
225 switch get(obj.fig,'SelectionType')
|
wolffd@0
|
226 case 'normal'
|
wolffd@0
|
227 obj.singleClick(node);
|
wolffd@0
|
228 case 'open'
|
wolffd@0
|
229 obj.doubleClick(node);
|
wolffd@0
|
230 case 'alt'
|
wolffd@0
|
231 obj.rightClick(node);
|
wolffd@0
|
232 otherwise
|
wolffd@0
|
233 obj.shiftClick(node);
|
wolffd@0
|
234 end
|
wolffd@0
|
235 end
|
wolffd@0
|
236
|
wolffd@0
|
237 function addLayout(obj,layout)
|
wolffd@0
|
238 % Let the graph know about a new layout you have created so that it
|
wolffd@0
|
239 % will be available via a toolbar button. The layout object must be
|
wolffd@0
|
240 % a descendant of the Abstractlayout class. This method does not have
|
wolffd@0
|
241 % to be called for existing layouts, nor does it need to be called
|
wolffd@0
|
242 % if you passed the new layout to the constructor or to the
|
wolffd@0
|
243 % setLayout() method. It will not add two layouts with the same
|
wolffd@0
|
244 % name property.
|
wolffd@0
|
245 if(~ismember(layout.name,fieldnames(obj.layouts)))
|
wolffd@0
|
246 if(layout.isavailable())
|
wolffd@0
|
247 obj.layouts.(layout.name) = layout;
|
wolffd@0
|
248 if(obj.isvisible)
|
wolffd@0
|
249 obj.addButtons();
|
wolffd@0
|
250 end
|
wolffd@0
|
251 else
|
wolffd@0
|
252 warning('graphViz4Matlab:layout','This layout is not available');
|
wolffd@0
|
253 end
|
wolffd@0
|
254 end
|
wolffd@0
|
255 end
|
wolffd@0
|
256
|
wolffd@0
|
257 function setLayout(obj,layout)
|
wolffd@0
|
258 % Set a new layout algorithm and refresh the graph.
|
wolffd@0
|
259 if(layout.isavailable())
|
wolffd@0
|
260 obj.addLayout(layout);
|
wolffd@0
|
261 obj.currentLayout = obj.layouts.(layout.name);
|
wolffd@0
|
262 obj.redraw();
|
wolffd@0
|
263 else
|
wolffd@0
|
264 warning('graphViz4Matlab:layout','Sorry, this layout is not available');
|
wolffd@0
|
265 end
|
wolffd@0
|
266 end
|
wolffd@0
|
267
|
wolffd@0
|
268 function squareAxes(obj,varargin)
|
wolffd@0
|
269 % Toggle the axes from square to normal and vice versa.
|
wolffd@0
|
270 obj.clearGroupSelection();
|
wolffd@0
|
271 if(obj.square)
|
wolffd@0
|
272 axis(obj.ax,'normal');
|
wolffd@0
|
273 obj.square = false;
|
wolffd@0
|
274 else
|
wolffd@0
|
275 axis(obj.ax,'square');
|
wolffd@0
|
276 obj.square = true;
|
wolffd@0
|
277 end
|
wolffd@0
|
278
|
wolffd@0
|
279 end
|
wolffd@0
|
280
|
wolffd@0
|
281 function tightenAxes(obj,varargin)
|
wolffd@0
|
282 % Tighten the axes as much as possible.
|
wolffd@0
|
283 obj.clearGroupSelection();
|
wolffd@0
|
284 xpos = vertcat(obj.nodeArray.xpos);
|
wolffd@0
|
285 ypos = vertcat(obj.nodeArray.ypos);
|
wolffd@0
|
286 r = obj.nodeArray(1).width/2;
|
wolffd@0
|
287 axis(obj.ax,[min(xpos)-r,max(xpos)+r,min(ypos)-r,max(ypos)+r]);
|
wolffd@0
|
288 axis normal;
|
wolffd@0
|
289 end
|
wolffd@0
|
290
|
wolffd@0
|
291
|
wolffd@0
|
292
|
wolffd@0
|
293
|
wolffd@0
|
294 end % end of public methods
|
wolffd@0
|
295
|
wolffd@0
|
296
|
wolffd@0
|
297 methods(Access = 'protected')
|
wolffd@0
|
298
|
wolffd@0
|
299 function addKnownLayouts(obj)
|
wolffd@0
|
300 % Add all of the known layouts
|
wolffd@0
|
301 obj.layouts = struct;
|
wolffd@0
|
302 for i=1:numel(obj.knownLayouts)
|
wolffd@0
|
303 layout = obj.knownLayouts{i};
|
wolffd@0
|
304 if(layout.isavailable())
|
wolffd@0
|
305 obj.layouts.(layout.name) = layout;
|
wolffd@0
|
306 end
|
wolffd@0
|
307 end
|
wolffd@0
|
308 end
|
wolffd@0
|
309
|
wolffd@0
|
310 function processInputs(obj,varargin)
|
wolffd@0
|
311 % Process the inputs and perform error checking
|
wolffd@0
|
312 labels = {'adj', 'adjMatrix', 'adjMat', 'layout', 'nodeLabels', 'nodeDescriptions', 'nodeColors', 'undirected', 'edgeColors', 'splitLabels', 'doubleClickFn'};
|
wolffd@0
|
313 for i=1:numel(varargin)
|
wolffd@0
|
314 arg = varargin{i};
|
wolffd@0
|
315 if ~ischar(arg), continue; end
|
wolffd@0
|
316 for j = 1:numel(labels)
|
wolffd@0
|
317 if strcmpi(arg, labels{i});
|
wolffd@0
|
318 varargin{i} = ['-', arg];
|
wolffd@0
|
319 end
|
wolffd@0
|
320 if strcmpi(arg, '-adj') || strcmpi(arg, '-adjMatrix')
|
wolffd@0
|
321 varargin{i} = '-adjMat';
|
wolffd@0
|
322 end
|
wolffd@0
|
323 end
|
wolffd@0
|
324 end
|
wolffd@0
|
325
|
wolffd@0
|
326 [adjMatrix, currentLayout, nodeLabels, nodeDescriptions, nodeColors,obj.undirected,obj.edgeColors,obj.splitLabels,obj.doubleClickFn] = processArgs(varargin,...
|
wolffd@0
|
327 '-adjMat' , [] ,...
|
wolffd@0
|
328 '-layout' , [] ,...
|
wolffd@0
|
329 '-nodeLabels' , {} ,...
|
wolffd@0
|
330 '-nodeDescriptions' , {} ,...
|
wolffd@0
|
331 '-nodeColors' , {} ,...
|
wolffd@0
|
332 '-undirected' , false ,...
|
wolffd@0
|
333 '-edgeColors' , [] ,...
|
wolffd@0
|
334 '-splitLabels' , true ,...
|
wolffd@0
|
335 '-doubleClickFn' , [] );
|
wolffd@0
|
336
|
wolffd@0
|
337
|
wolffd@0
|
338 if(~isempty(currentLayout) && ~isavailable(currentLayout))
|
wolffd@0
|
339 currentLayout = [];
|
wolffd@0
|
340 end
|
wolffd@0
|
341 if(isempty(adjMatrix))
|
wolffd@0
|
342 adjMatrix = [0 0 0 0; 1 0 0 0; 1 1 0 0; 1 1 1 0]; % example graph
|
wolffd@0
|
343 end
|
wolffd@0
|
344
|
wolffd@0
|
345 if(isempty(currentLayout))
|
wolffd@0
|
346 fields = fieldnames(obj.layouts);
|
wolffd@0
|
347 currentLayout = obj.layouts.(fields{1});
|
wolffd@0
|
348 else
|
wolffd@0
|
349 obj.addLayout(currentLayout);
|
wolffd@0
|
350 end
|
wolffd@0
|
351 obj.nnodes = size(adjMatrix,1);
|
wolffd@0
|
352 obj.adjMatrix = adjMatrix;
|
wolffd@0
|
353 if(isempty(nodeDescriptions))
|
wolffd@0
|
354 nodeDescriptions = repmat({'Enter a description here...'},size(adjMatrix,1),1);
|
wolffd@0
|
355 end
|
wolffd@0
|
356 obj.nodeDescriptions = nodeDescriptions;
|
wolffd@0
|
357 obj.nodeColors = nodeColors;
|
wolffd@0
|
358
|
wolffd@0
|
359 if(isempty(nodeLabels))
|
wolffd@0
|
360 nodeLabels = cellfun(@(x)num2str(x),mat2cell(1:obj.nnodes,1,ones(1,obj.nnodes)),'UniformOutput',false);
|
wolffd@0
|
361 end
|
wolffd@0
|
362
|
wolffd@0
|
363 obj.nodeLabels = nodeLabels;
|
wolffd@0
|
364 if(~isequal(numel(obj.nodeLabels),size(adjMatrix,1),size(adjMatrix,2)))
|
wolffd@0
|
365 error('graphViz4Matlab:dimMismatch','The number of labels must match the dimensions of adjmatrix.');
|
wolffd@0
|
366 end
|
wolffd@0
|
367 obj.currentLayout = currentLayout;
|
wolffd@0
|
368 end
|
wolffd@0
|
369
|
wolffd@0
|
370 function createWindow(obj)
|
wolffd@0
|
371 % Create the main window
|
wolffd@0
|
372 obj.fig = figure(floor(1000*rand) + 1000);
|
wolffd@0
|
373 set(obj.fig,'Name','graphViz4Matlab',...
|
wolffd@0
|
374 'NumberTitle' ,'off',...
|
wolffd@0
|
375 'Color','w' ,'Toolbar','none');
|
wolffd@0
|
376 obj.createAxes();
|
wolffd@0
|
377 ssize = get(0,'ScreenSize');
|
wolffd@0
|
378 pos = [ssize(3)/2,50,-20+ssize(3)/2,ssize(4)-200];
|
wolffd@0
|
379 set(obj.fig,'Position',pos);
|
wolffd@0
|
380 obj.setCallbacks();
|
wolffd@0
|
381 obj.addButtons();
|
wolffd@0
|
382
|
wolffd@0
|
383 end
|
wolffd@0
|
384
|
wolffd@0
|
385 function createAxes(obj)
|
wolffd@0
|
386 % Create the axes upon which the graph will be displayed.
|
wolffd@0
|
387 obj.ax = axes('Parent',obj.fig,'box','on','UserData','main');
|
wolffd@0
|
388 outerpos = get(obj.ax,'OuterPosition');
|
wolffd@0
|
389 axpos = outerpos;
|
wolffd@0
|
390 axpos(4) = 0.90;
|
wolffd@0
|
391 axpos(2) = 0.03;
|
wolffd@0
|
392 axis manual
|
wolffd@0
|
393 if(obj.square)
|
wolffd@0
|
394 axis square
|
wolffd@0
|
395 end
|
wolffd@0
|
396 set(obj.ax,'Position',axpos,'XTick',[],'YTick',[],'LineWidth',0.5);
|
wolffd@0
|
397 set(obj.ax,'ButtonDownFcn',@obj.axPressed);
|
wolffd@0
|
398 end
|
wolffd@0
|
399
|
wolffd@0
|
400
|
wolffd@0
|
401
|
wolffd@0
|
402 function setCallbacks(obj)
|
wolffd@0
|
403 % Set the callback functions for the figure, i.e. functions that
|
wolffd@0
|
404 % will be called when the user performs various actions.
|
wolffd@0
|
405 set(obj.fig,'ResizeFcn' ,@obj.windowResized);
|
wolffd@0
|
406 set(obj.fig,'WindowButtonMotionFcn' ,@obj.mouseMoved);
|
wolffd@0
|
407 set(obj.fig,'WindowButtonUpFcn' ,@obj.buttonUp);
|
wolffd@0
|
408 set(obj.fig,'DeleteFcn' ,@obj.deleted);
|
wolffd@0
|
409 end
|
wolffd@0
|
410
|
wolffd@0
|
411 function addNodes(obj)
|
wolffd@0
|
412 % Add all of the nodes to the graph structure, but don't display
|
wolffd@0
|
413 % them yet.
|
wolffd@0
|
414 obj.nodeArray = [];
|
wolffd@0
|
415 for i=1:obj.nnodes
|
wolffd@0
|
416 newnode = graphViz4MatlabNode(obj.nodeLabels{i});
|
wolffd@0
|
417 newnode.containingGraph = obj;
|
wolffd@0
|
418 newnode.showFullLabel = ~obj.splitLabels;
|
wolffd@0
|
419 obj.nodeArray = [obj.nodeArray newnode];
|
wolffd@0
|
420 end
|
wolffd@0
|
421 obj.addNodeDescriptions(obj.nodeDescriptions);
|
wolffd@0
|
422 obj.addNodeColors(obj.nodeColors);
|
wolffd@0
|
423 end
|
wolffd@0
|
424
|
wolffd@0
|
425 function addNodeDescriptions(obj,nodeDescriptions)
|
wolffd@0
|
426 % Add any descriptions to the newly created nodes.
|
wolffd@0
|
427 if(~isempty(nodeDescriptions))
|
wolffd@0
|
428 if(numel(nodeDescriptions) == 1)
|
wolffd@0
|
429 nodeDescriptions = repmat(nodeDescriptions,obj.nnodes,1);
|
wolffd@0
|
430 end
|
wolffd@0
|
431 for i=1:obj.nnodes
|
wolffd@0
|
432 obj.nodeArray(i).description = nodeDescriptions{i};
|
wolffd@0
|
433 end
|
wolffd@0
|
434 end
|
wolffd@0
|
435 end
|
wolffd@0
|
436
|
wolffd@0
|
437 function addNodeColors(obj,nodeColors)
|
wolffd@0
|
438 % Shade the nodes according to the specified colors. If too few
|
wolffd@0
|
439 % colors are specified, they are cycled through.
|
wolffd@0
|
440 if(~isempty(nodeColors))
|
wolffd@0
|
441 if(~iscell(nodeColors))
|
wolffd@0
|
442 nodeColors = mat2cell(nodeColors,ones(1,size(nodeColors,1)),size(nodeColors,2));
|
wolffd@0
|
443 end
|
wolffd@0
|
444 if(size(nodeColors,2) > size(nodeColors,1))
|
wolffd@0
|
445 nodeColors = nodeColors';
|
wolffd@0
|
446 end
|
wolffd@0
|
447 if(numel(nodeColors) < obj.nnodes)
|
wolffd@0
|
448 nodeColors = repmat(nodeColors,ceil(obj.nnodes/numel(nodeColors)),1);
|
wolffd@0
|
449 nodeColors = nodeColors(1:obj.nnodes);
|
wolffd@0
|
450 end
|
wolffd@0
|
451 for i=1:obj.nnodes
|
wolffd@0
|
452 obj.nodeArray(i).shade(nodeColors{i});
|
wolffd@0
|
453 end
|
wolffd@0
|
454 obj.nodeColors = nodeColors;
|
wolffd@0
|
455 end
|
wolffd@0
|
456 end
|
wolffd@0
|
457
|
wolffd@0
|
458 function addEdges(obj)
|
wolffd@0
|
459 % Add all of the edges to the graph structure, but don't display
|
wolffd@0
|
460 % them yet.
|
wolffd@0
|
461 if(any(diag(obj.adjMatrix)))
|
wolffd@0
|
462 fprintf('\nRemoving Self Loops\n');
|
wolffd@0
|
463 obj.adjMatrix = obj.adjMatrix - diag(diag(obj.adjMatrix));
|
wolffd@0
|
464 end
|
wolffd@0
|
465 obj.edgeArray = struct('from',[],'to',[],'arrow',[]);
|
wolffd@0
|
466 counter = 1;
|
wolffd@0
|
467 for i=1:obj.nnodes
|
wolffd@0
|
468 for j=1:obj.nnodes
|
wolffd@0
|
469 if(obj.adjMatrix(i,j))
|
wolffd@0
|
470 obj.edgeArray(counter) = struct('from',obj.nodeArray(i),'to',obj.nodeArray(j),'arrow',-1);
|
wolffd@0
|
471 obj.nodeArray(i).outedges = [obj.nodeArray(i).outedges,counter];
|
wolffd@0
|
472 obj.nodeArray(j).inedges = [obj.nodeArray(j).inedges,counter];
|
wolffd@0
|
473 counter = counter + 1;
|
wolffd@0
|
474 end
|
wolffd@0
|
475 end
|
wolffd@0
|
476 end
|
wolffd@0
|
477 obj.nedges = counter -1;
|
wolffd@0
|
478 end
|
wolffd@0
|
479
|
wolffd@0
|
480 function calculateMinMaxNodeSize(obj)
|
wolffd@0
|
481 % calculates the maximum and minimum node sizes in data units
|
wolffd@0
|
482 SCREEN_PROPORTION_MAX = 1/10;
|
wolffd@0
|
483 SCREEN_PROPORTION_MIN = 1/35;
|
wolffd@0
|
484 units = get(0,'Units');
|
wolffd@0
|
485 set(0,'Units','pixels');
|
wolffd@0
|
486 screensize = get(0,'ScreenSize');
|
wolffd@0
|
487 set(0,'Units',units);
|
wolffd@0
|
488 axunits = get(obj.ax,'Units');
|
wolffd@0
|
489 set(obj.ax,'Units','pixels');
|
wolffd@0
|
490 axsize = get(obj.ax,'Position');
|
wolffd@0
|
491 set(obj.ax,'Units',axunits);
|
wolffd@0
|
492 if(screensize(3) < screensize(4))
|
wolffd@0
|
493 dataUnitsPerPixel = abs(diff(xlim))/axsize(3);
|
wolffd@0
|
494 obj.minNodeSize = (SCREEN_PROPORTION_MIN*screensize(3))*dataUnitsPerPixel;
|
wolffd@0
|
495 obj.maxNodeSize = (SCREEN_PROPORTION_MAX*screensize(3))*dataUnitsPerPixel;
|
wolffd@0
|
496 else
|
wolffd@0
|
497 dataUnitsPerPixel = abs(diff(ylim))/axsize(4);
|
wolffd@0
|
498 obj.minNodeSize = (SCREEN_PROPORTION_MIN*screensize(4))*dataUnitsPerPixel;
|
wolffd@0
|
499 obj.maxNodeSize = (SCREEN_PROPORTION_MAX*screensize(4))*dataUnitsPerPixel;
|
wolffd@0
|
500 end
|
wolffd@0
|
501 end
|
wolffd@0
|
502
|
wolffd@0
|
503 function layoutNodes(obj)
|
wolffd@0
|
504 % Layout the nodes and edges according to the current layout
|
wolffd@0
|
505 % algorithm.
|
wolffd@0
|
506 if(obj.flipped)
|
wolffd@0
|
507 adj = obj.adjMatrix';
|
wolffd@0
|
508 else
|
wolffd@0
|
509 adj = obj.adjMatrix;
|
wolffd@0
|
510 end
|
wolffd@0
|
511 obj.currentLayout.dolayout(adj,obj.ax,obj.maxNodeSize);
|
wolffd@0
|
512 nodesize = obj.currentLayout.nodeSize();
|
wolffd@0
|
513 locs = obj.currentLayout.centers();
|
wolffd@0
|
514 for i=1:obj.nnodes
|
wolffd@0
|
515 node = obj.nodeArray(i);
|
wolffd@0
|
516 node.resize(nodesize);
|
wolffd@0
|
517 node.move(locs(i,1),locs(i,2));
|
wolffd@0
|
518 end
|
wolffd@0
|
519 end
|
wolffd@0
|
520
|
wolffd@0
|
521 function displayGraph(obj)
|
wolffd@0
|
522 % Display all of the nodes and edges.
|
wolffd@0
|
523 cla(obj.ax);
|
wolffd@0
|
524 obj.setFontSize();
|
wolffd@0
|
525 for i=1:obj.nnodes
|
wolffd@0
|
526 node = obj.nodeArray(i);
|
wolffd@0
|
527 node.fontSize = obj.fontSize;
|
wolffd@0
|
528 node.draw(obj.ax);
|
wolffd@0
|
529 end
|
wolffd@0
|
530 displayEdges(obj);
|
wolffd@0
|
531 end
|
wolffd@0
|
532
|
wolffd@0
|
533 function displayEdges(obj,indices)
|
wolffd@0
|
534 % Display or refresh the specified edges. If none specified, all
|
wolffd@0
|
535 % are refreshed. Currently only works with round nodes.
|
wolffd@0
|
536 figure(obj.fig);
|
wolffd@0
|
537 if(nargin < 2)
|
wolffd@0
|
538 indices = 1:obj.nedges;
|
wolffd@0
|
539 else
|
wolffd@0
|
540 indices = unique(indices);
|
wolffd@0
|
541 end
|
wolffd@0
|
542 for i=1:numel(indices)
|
wolffd@0
|
543 edge = obj.edgeArray(indices(i));
|
wolffd@0
|
544 [X,Y,Xarrow,Yarrow] = obj.calcPositions(edge);
|
wolffd@0
|
545 if(ishandle(edge.arrow))
|
wolffd@0
|
546 delete(edge.arrow)
|
wolffd@0
|
547 end
|
wolffd@0
|
548 hold on;
|
wolffd@0
|
549 edgeColor = obj.defaultEdgeColor;
|
wolffd@0
|
550 if ~isempty(obj.edgeColors)
|
wolffd@0
|
551 candidates = obj.edgeColors(findString(edge.from.label,obj.edgeColors(:,1)),:);
|
wolffd@0
|
552 if size(candidates,1)==1 && strcmpi(candidates(1,2),'all')
|
wolffd@0
|
553 edgeColor = candidates{1,3};
|
wolffd@0
|
554 else
|
wolffd@0
|
555 edgeCol = candidates(findString(edge.to.label,candidates(:,2)),3);
|
wolffd@0
|
556 if ~isempty(edgeCol); edgeColor = edgeCol{1}; end
|
wolffd@0
|
557 end
|
wolffd@0
|
558 end
|
wolffd@0
|
559 edge.arrow = plot(X,Y,'LineWidth',2,'HitTest','off','Color',edgeColor);
|
wolffd@0
|
560 if(~obj.undirected)
|
wolffd@0
|
561 arrowHead = obj.displayArrowHead(X,Y,Xarrow,Yarrow,edgeColor);
|
wolffd@0
|
562 edge.arrow = [edge.arrow arrowHead];
|
wolffd@0
|
563 end
|
wolffd@0
|
564 hold off;
|
wolffd@0
|
565 obj.edgeArray(indices(i)) = edge;
|
wolffd@0
|
566 end
|
wolffd@0
|
567 end
|
wolffd@0
|
568
|
wolffd@0
|
569 function arrowHead = displayArrowHead(obj,X,Y,Xarrow,Yarrow,arrowColor) %#ok
|
wolffd@0
|
570 % Displays the arrow head given the appropriate coordinates
|
wolffd@0
|
571 % calculated via the calcPositions() function.
|
wolffd@0
|
572
|
wolffd@0
|
573 arrowHead = patch('Faces' ,[1,2,3] ,...
|
wolffd@0
|
574 'Vertices' ,[Xarrow(1) Yarrow(1); Xarrow(2) Yarrow(2) ;X(2) Y(2)],...
|
wolffd@0
|
575 'FaceColor' ,arrowColor);
|
wolffd@0
|
576 end
|
wolffd@0
|
577
|
wolffd@0
|
578 function [X,Y,Xarrow,Yarrow] = calcPositions(obj,edge)
|
wolffd@0
|
579 % Helper function for displayEdges() - calculates edge and arrow
|
wolffd@0
|
580 % start and end positions in data units.
|
wolffd@0
|
581 X = [edge.from.xpos edge.to.xpos];
|
wolffd@0
|
582 Y = [edge.from.ypos edge.to.ypos];
|
wolffd@0
|
583 ratio = (Y(2) - Y(1))/(X(2)-X(1));
|
wolffd@0
|
584 if(isinf(ratio))
|
wolffd@0
|
585 ratio = realmax;
|
wolffd@0
|
586 end
|
wolffd@0
|
587 % dx: x-distance from node1 center to perimeter in direction of node2
|
wolffd@0
|
588 % dy: y-distance from node1 center to perimeter in direction of node2
|
wolffd@0
|
589 % ddx: x-distance from node1 perimeter to base of arrow head
|
wolffd@0
|
590 % ddy: y-distance from node1 perimeter to base of arrow head
|
wolffd@0
|
591 % dpx: x-offset away from edge in perpendicular direction, for arrow head
|
wolffd@0
|
592 % dpy: y-offset away from edge in perpendicular direction, for arrow head
|
wolffd@0
|
593
|
wolffd@0
|
594 arrowSize = obj.maxNodeSize/10;
|
wolffd@0
|
595 [dx,dy] = pol2cart(atan(ratio),edge.from.width/2);
|
wolffd@0
|
596 [ddx,ddy] = pol2cart(atan(ratio),arrowSize);
|
wolffd@0
|
597 ratio = 1/ratio; % now work out perpendicular directions.
|
wolffd@0
|
598 if(isinf(ratio))
|
wolffd@0
|
599 ratio = realmax;
|
wolffd@0
|
600 end
|
wolffd@0
|
601 [dpx dpy] = pol2cart(atan(ratio),arrowSize/2);
|
wolffd@0
|
602 ddx = abs(ddx); ddy = abs(ddy); dpx = abs(dpx); dpy = abs(dpy);
|
wolffd@0
|
603 dx = abs(dx); dy = abs(dy);
|
wolffd@0
|
604 if(X(1) < X(2))
|
wolffd@0
|
605 X(1) = X(1) + dx; X(2) = X(2) - dx;
|
wolffd@0
|
606 else
|
wolffd@0
|
607 X(1) = X(1) - dx; X(2) = X(2) + dx;
|
wolffd@0
|
608 end
|
wolffd@0
|
609 if(Y(1) < Y(2))
|
wolffd@0
|
610 Y(1) = Y(1) + dy; Y(2) = Y(2) - dy;
|
wolffd@0
|
611 else
|
wolffd@0
|
612 Y(1) = Y(1) - dy; Y(2) = Y(2) + dy;
|
wolffd@0
|
613 end
|
wolffd@0
|
614 if(X(1) <= X(2) && Y(1) <= Y(2))
|
wolffd@0
|
615 Xarrow(1) = X(2) - ddx - dpx; Xarrow(2) = X(2) - ddx + dpx;
|
wolffd@0
|
616 Yarrow(1) = Y(2) - ddy + dpy; Yarrow(2) = Y(2) - ddy - dpy;
|
wolffd@0
|
617 elseif(X(1) <= X(2) && Y(1) >= Y(2))
|
wolffd@0
|
618 Xarrow(1) = X(2) - ddx - dpx; Xarrow(2) = X(2) - ddx + dpx;
|
wolffd@0
|
619 Yarrow(1) = Y(2) + ddy - dpy; Yarrow(2) = Y(2) + ddy + dpy;
|
wolffd@0
|
620 elseif(X(1) >= X(2) && Y(1) <= Y(2))
|
wolffd@0
|
621 Xarrow(1) = X(2) + ddx - dpx; Xarrow(2) = X(2) + ddx + dpx;
|
wolffd@0
|
622 Yarrow(1) = Y(2) - ddy - dpy; Yarrow(2) = Y(2) - ddy + dpy;
|
wolffd@0
|
623 else % (X(1) >= (X(2) && Y(1) >= Y(2))
|
wolffd@0
|
624 Xarrow(1) = X(2) + ddx - dpx; Xarrow(2) = X(2) + ddx + dpx;
|
wolffd@0
|
625 Yarrow(1) = Y(2) + ddy + dpy; Yarrow(2) = Y(2) + ddy - dpy;
|
wolffd@0
|
626 end
|
wolffd@0
|
627 end
|
wolffd@0
|
628
|
wolffd@0
|
629 function addButtons(obj)
|
wolffd@0
|
630 % Add user interface buttons.
|
wolffd@0
|
631 if(~isempty(obj.toolbar))
|
wolffd@0
|
632 if(ishandle(obj.toolbar))
|
wolffd@0
|
633 delete(obj.toolbar);
|
wolffd@0
|
634 obj.toolbar = [];
|
wolffd@0
|
635 end
|
wolffd@0
|
636 end
|
wolffd@0
|
637 obj.toolbar = uitoolbar(obj.fig);
|
wolffd@0
|
638
|
wolffd@0
|
639 % button icons
|
wolffd@0
|
640 load glicons;
|
wolffd@0
|
641
|
wolffd@0
|
642 uipushtool(obj.toolbar,...
|
wolffd@0
|
643 'ClickedCallback' ,@obj.decreaseFontSize,...
|
wolffd@0
|
644 'TooltipString' ,'Decrease Font Size',...
|
wolffd@0
|
645 'CData' ,icons.downblue);
|
wolffd@0
|
646
|
wolffd@0
|
647 uipushtool(obj.toolbar,...
|
wolffd@0
|
648 'ClickedCallback' ,@obj.increaseFontSize,...
|
wolffd@0
|
649 'TooltipString' ,'Increase Font Size',...
|
wolffd@0
|
650 'CData' ,icons.upblue);
|
wolffd@0
|
651
|
wolffd@0
|
652 uipushtool(obj.toolbar,...
|
wolffd@0
|
653 'ClickedCallback' ,@obj.tightenAxes,...
|
wolffd@0
|
654 'TooltipString' ,'Tighten Axes',...
|
wolffd@0
|
655 'CData' ,icons.expand);
|
wolffd@0
|
656
|
wolffd@0
|
657 uipushtool(obj.toolbar,...
|
wolffd@0
|
658 'ClickedCallback' ,@obj.flip,...
|
wolffd@0
|
659 'TooltipString' ,'Flip/Reset Layout',...
|
wolffd@0
|
660 'CData' , icons.flip);
|
wolffd@0
|
661
|
wolffd@0
|
662 uipushtool(obj.toolbar,...
|
wolffd@0
|
663 'ClickedCallback' ,@obj.shrinkNodes,...
|
wolffd@0
|
664 'TooltipString' ,'Decrease Node Size',...
|
wolffd@0
|
665 'CData' , icons.downdarkblue);
|
wolffd@0
|
666
|
wolffd@0
|
667 uipushtool(obj.toolbar,...
|
wolffd@0
|
668 'ClickedCallback' ,@obj.growNodes,...
|
wolffd@0
|
669 'TooltipString' ,'Increase Node Size',...
|
wolffd@0
|
670 'CData' , icons.updarkblue);
|
wolffd@0
|
671
|
wolffd@0
|
672 if(~isempty(obj.layoutButtons))
|
wolffd@0
|
673 for i=1:numel(obj.layoutButtons)
|
wolffd@0
|
674 if(ishandle(obj.layoutButtons(i)))
|
wolffd@0
|
675 delete(obj.layoutButtons(i));
|
wolffd@0
|
676 end
|
wolffd@0
|
677 end
|
wolffd@0
|
678 obj.layoutButtons = [];
|
wolffd@0
|
679 end
|
wolffd@0
|
680
|
wolffd@0
|
681 layoutNames = fieldnames(obj.layouts);
|
wolffd@0
|
682 for i=1:numel(layoutNames)
|
wolffd@0
|
683 layout = obj.layouts.(layoutNames{i});
|
wolffd@0
|
684 layoutButton = uipushtool(obj.toolbar,...
|
wolffd@0
|
685 'ClickedCallback', @obj.layoutButtonPushed,...
|
wolffd@0
|
686 'TooltipString', layout.shortDescription,...
|
wolffd@0
|
687 'UserData' , layoutNames{i},...
|
wolffd@0
|
688 'Separator' , 'on',...
|
wolffd@0
|
689 'CData' , layout.image);
|
wolffd@0
|
690 obj.layoutButtons = [obj.layoutButtons,layoutButton];
|
wolffd@0
|
691 end
|
wolffd@0
|
692 end
|
wolffd@0
|
693
|
wolffd@0
|
694 function setFontSize(obj)
|
wolffd@0
|
695 % fontsize = obj.maxFontSize;
|
wolffd@0
|
696 fontSize = 20;
|
wolffd@0
|
697 maxchars = size(char(obj.nodeLabels),2);
|
wolffd@0
|
698 width = obj.nodeArray(1).width;
|
wolffd@0
|
699 height = obj.nodeArray(1).height;
|
wolffd@0
|
700 xpos = -10; ypos = -10;
|
wolffd@0
|
701 t = text(xpos,ypos,repmat('g',1,maxchars),...
|
wolffd@0
|
702 'FontUnits' , 'points' ,...
|
wolffd@0
|
703 'Units' , 'data' ,...
|
wolffd@0
|
704 'HorizontalAlignment' , 'center' ,...
|
wolffd@0
|
705 'VerticalAlignment' , 'middle' ,...
|
wolffd@0
|
706 'FontWeight' , 'demi' ,...
|
wolffd@0
|
707 'LineStyle' , 'none' ,...
|
wolffd@0
|
708 'Margin' , 0.01 ,...
|
wolffd@0
|
709 'FontSize' , fontSize ,...
|
wolffd@0
|
710 'Color' , 'w' );
|
wolffd@0
|
711
|
wolffd@0
|
712 extent = get(t,'Extent');
|
wolffd@0
|
713
|
wolffd@0
|
714 while(extent(3) > width || extent(4) > height)
|
wolffd@0
|
715 fontSize = fontSize - 1;
|
wolffd@0
|
716 if(fontSize < 2), break,end
|
wolffd@0
|
717 set(t,'FontSize',fontSize);
|
wolffd@0
|
718 extent = get(t,'Extent');
|
wolffd@0
|
719 end
|
wolffd@0
|
720 obj.fontSize = fontSize;
|
wolffd@0
|
721
|
wolffd@0
|
722 end
|
wolffd@0
|
723
|
wolffd@0
|
724 function asp = aspectRatio(obj)
|
wolffd@0
|
725 % Return the current aspect ratio of the figure, width/height
|
wolffd@0
|
726
|
wolffd@0
|
727 units = get(obj.ax,'Units');
|
wolffd@0
|
728 set(obj.ax,'Units','pixels');
|
wolffd@0
|
729 pos = get(obj.ax,'Position');
|
wolffd@0
|
730 set(obj.ax,'Units',units);
|
wolffd@0
|
731 asp = (pos(3)/pos(4));
|
wolffd@0
|
732
|
wolffd@0
|
733 end
|
wolffd@0
|
734
|
wolffd@0
|
735 function paperCrop(obj)
|
wolffd@0
|
736 % Make the papersize the same as the the figure size. This is
|
wolffd@0
|
737 % useful when saving as pdf.
|
wolffd@0
|
738 units = get(obj.fig,'Units');
|
wolffd@0
|
739 set(obj.fig,'Units','inches');
|
wolffd@0
|
740 pos = get(obj.fig,'Position');
|
wolffd@0
|
741 set(obj.fig,'Units',units);
|
wolffd@0
|
742 set(obj.fig,'PaperPositionMode','auto','PaperSize',pos(3:4));
|
wolffd@0
|
743 end
|
wolffd@0
|
744 %%
|
wolffd@0
|
745 % Callbacks
|
wolffd@0
|
746
|
wolffd@0
|
747 function layoutButtonPushed(obj,buttonPushed,varargin)
|
wolffd@0
|
748 % Called when a layout button is pushed.
|
wolffd@0
|
749 name = get(buttonPushed,'UserData');
|
wolffd@0
|
750 obj.currentLayout = obj.layouts.(name);
|
wolffd@0
|
751 axis square;
|
wolffd@0
|
752 obj.redraw;
|
wolffd@0
|
753 end
|
wolffd@0
|
754
|
wolffd@0
|
755 function windowResized(obj,varargin)
|
wolffd@0
|
756 % This function is called whenever the window is resized. It
|
wolffd@0
|
757 % redraws the whole graph.
|
wolffd@0
|
758 if(obj.isvisible)
|
wolffd@0
|
759 obj.redraw;
|
wolffd@0
|
760 obj.paperCrop();
|
wolffd@0
|
761 end
|
wolffd@0
|
762
|
wolffd@0
|
763 end
|
wolffd@0
|
764
|
wolffd@0
|
765 function mouseMoved(obj,varargin)
|
wolffd@0
|
766 % This function is called whenever the mouse moves within the
|
wolffd@0
|
767 % figure.
|
wolffd@0
|
768 if(obj.groupSelectionMode == 2)
|
wolffd@0
|
769 % Move all of the nodes & rectangle
|
wolffd@0
|
770 currentPoint = get(obj.ax,'CurrentPoint');
|
wolffd@0
|
771 xlimits = get(obj.ax,'XLim');
|
wolffd@0
|
772 ylimits = get(obj.ax,'YLim');
|
wolffd@0
|
773 sdims = obj.groupSelectedDims;
|
wolffd@0
|
774 xdiff = currentPoint(1,1) - obj.previousMouseLocation(1,1);
|
wolffd@0
|
775 ydiff = currentPoint(1,2) - obj.previousMouseLocation(1,2);
|
wolffd@0
|
776
|
wolffd@0
|
777 if(xdiff <=0)
|
wolffd@0
|
778 xdiff = max(xdiff,(xlimits(1)-sdims(1)));
|
wolffd@0
|
779 else
|
wolffd@0
|
780 xdiff = min(xdiff,xlimits(2)-sdims(2));
|
wolffd@0
|
781 end
|
wolffd@0
|
782 if(ydiff <=0)
|
wolffd@0
|
783 ydiff = max(ydiff,(ylimits(1)-sdims(3)));
|
wolffd@0
|
784 else
|
wolffd@0
|
785 ydiff = min(ydiff,ylimits(2)-sdims(4));
|
wolffd@0
|
786 end
|
wolffd@0
|
787 xnodepos = vertcat(obj.groupSelectedNodes.xpos) + xdiff;
|
wolffd@0
|
788 ynodepos = vertcat(obj.groupSelectedNodes.ypos) + ydiff;
|
wolffd@0
|
789 for i=1:numel(obj.groupSelectedNodes)
|
wolffd@0
|
790 obj.groupSelectedNodes(i).move(xnodepos(i),ynodepos(i));
|
wolffd@0
|
791 end
|
wolffd@0
|
792 recpos = get(obj.groupSelectedRect,'Position');
|
wolffd@0
|
793 recpos(1) = recpos(1) + xdiff;
|
wolffd@0
|
794 recpos(2) = recpos(2) + ydiff;
|
wolffd@0
|
795 obj.groupSelectedDims = [recpos(1),recpos(1)+recpos(3),recpos(2),recpos(2)+recpos(4)];
|
wolffd@0
|
796 set(obj.groupSelectedRect,'Position',recpos);
|
wolffd@0
|
797 edges = [obj.groupSelectedNodes.inedges,obj.groupSelectedNodes.outedges];
|
wolffd@0
|
798 obj.displayEdges(edges);
|
wolffd@0
|
799 obj.previousMouseLocation = currentPoint;
|
wolffd@0
|
800 else
|
wolffd@0
|
801 if(isempty(obj.selectedNode)), return,end
|
wolffd@0
|
802 currentPoint = get(obj.ax,'CurrentPoint');
|
wolffd@0
|
803 x = currentPoint(1,1); y = currentPoint(1,2);
|
wolffd@0
|
804 xl = xlim + [obj.selectedNode.width,-obj.selectedNode.width]/2;
|
wolffd@0
|
805 yl = ylim + [obj.selectedNode.height,-obj.selectedNode.height]/2;
|
wolffd@0
|
806 x = min(max(xl(1),x),xl(2));
|
wolffd@0
|
807 y = min(max(yl(1),y),yl(2));
|
wolffd@0
|
808 obj.selectedNode.move(x,y);
|
wolffd@0
|
809 obj.displayEdges([obj.selectedNode.inedges,obj.selectedNode.outedges]);
|
wolffd@0
|
810 end
|
wolffd@0
|
811 end
|
wolffd@0
|
812
|
wolffd@0
|
813 function buttonUp(obj,varargin)
|
wolffd@0
|
814 % This function executes when the mouse button is released.
|
wolffd@0
|
815 if(obj.groupSelectionMode == 2)
|
wolffd@0
|
816 obj.clearGroupSelection();
|
wolffd@0
|
817 return;
|
wolffd@0
|
818 end
|
wolffd@0
|
819 if(isempty(obj.selectedNode)),return,end
|
wolffd@0
|
820 obj.selectedNode.deselect();
|
wolffd@0
|
821
|
wolffd@0
|
822 obj.selectedNode.useFullLabel = false;
|
wolffd@0
|
823 obj.selectedNode.fontSize = obj.selectedFontSize;
|
wolffd@0
|
824 obj.selectedNode.redraw();
|
wolffd@0
|
825 obj.selectedNode = [];
|
wolffd@0
|
826 set(gcf,'Pointer','arrow');
|
wolffd@0
|
827 end
|
wolffd@0
|
828
|
wolffd@0
|
829 function axPressed(obj,varargin)
|
wolffd@0
|
830 % Called when the user selects the axes but not a node
|
wolffd@0
|
831 switch obj.groupSelectionMode
|
wolffd@0
|
832 case 0 % hasn't been selected yet
|
wolffd@0
|
833 xpos = vertcat(obj.nodeArray.xpos);
|
wolffd@0
|
834 ypos = vertcat(obj.nodeArray.ypos);
|
wolffd@0
|
835 p1 = get(obj.ax,'CurrentPoint');
|
wolffd@0
|
836 rbbox; % returns after box drawn
|
wolffd@0
|
837 p2 = get(obj.ax,'CurrentPoint');
|
wolffd@0
|
838 xleft = min(p1(1,1),p2(1,1));
|
wolffd@0
|
839 xright = max(p1(1,1),p2(1,1));
|
wolffd@0
|
840 ylower = min(p1(1,2),p2(1,2));
|
wolffd@0
|
841 yupper = max(p1(1,2),p2(1,2));
|
wolffd@0
|
842 selectedX = (xpos <= xright) & (xpos >= xleft);
|
wolffd@0
|
843 selectedY = (ypos <= yupper) & (ypos >= ylower);
|
wolffd@0
|
844 selected = selectedX & selectedY;
|
wolffd@0
|
845 if(~any(selected)),return,end
|
wolffd@0
|
846 obj.groupSelectionMode = 1;
|
wolffd@0
|
847 obj.groupSelectedNodes = obj.nodeArray(selected);
|
wolffd@0
|
848 for i=1:numel(obj.groupSelectedNodes)
|
wolffd@0
|
849 node = obj.groupSelectedNodes(i);
|
wolffd@0
|
850 node.select();
|
wolffd@0
|
851 node.redraw();
|
wolffd@0
|
852 end
|
wolffd@0
|
853
|
wolffd@0
|
854 w = obj.groupSelectedNodes(1).width/2;
|
wolffd@0
|
855 h = obj.groupSelectedNodes(1).height/2;
|
wolffd@0
|
856 x = vertcat(obj.groupSelectedNodes.xpos);
|
wolffd@0
|
857 y = vertcat(obj.groupSelectedNodes.ypos);
|
wolffd@0
|
858 minx = min(x)-w; maxx = max(x)+w; miny = min(y)-h; maxy = max(y)+h;
|
wolffd@0
|
859 obj.groupSelectedDims = [minx,maxx,miny,maxy];
|
wolffd@0
|
860 obj.groupSelectedRect = rectangle('Position',[minx,miny,maxx-minx,maxy-miny],'LineStyle','--','EdgeColor','r');
|
wolffd@0
|
861 case 1 % nodes selected
|
wolffd@0
|
862 obj.groupSelectionStage1();
|
wolffd@0
|
863 case 2 %not ever reached in this function
|
wolffd@0
|
864 obj.clearGroupSelection();
|
wolffd@0
|
865 end
|
wolffd@0
|
866 end
|
wolffd@0
|
867
|
wolffd@0
|
868 function groupSelectionStage1(obj)
|
wolffd@0
|
869 % Called after a group of nodes has been selected and the mouse
|
wolffd@0
|
870 % button has been pressed somewhere on the axes, (or on a node).
|
wolffd@0
|
871 p = get(obj.ax,'CurrentPoint');
|
wolffd@0
|
872 obj.previousMouseLocation = p;
|
wolffd@0
|
873 dims = obj.groupSelectedDims;
|
wolffd@0
|
874 if(p(1,1) >= dims(1) && p(1,1) <= dims(2) && p(1,2) >= dims(3) && p(1,2) <=dims(4))
|
wolffd@0
|
875 set(gcf,'Pointer','hand');
|
wolffd@0
|
876 obj.groupSelectionMode = 2;
|
wolffd@0
|
877 else
|
wolffd@0
|
878 obj.clearGroupSelection();
|
wolffd@0
|
879 end
|
wolffd@0
|
880 end
|
wolffd@0
|
881
|
wolffd@0
|
882 function clearGroupSelection(obj)
|
wolffd@0
|
883 % Clear a group selection
|
wolffd@0
|
884 if(ishandle(obj.groupSelectedRect))
|
wolffd@0
|
885 delete(obj.groupSelectedRect);
|
wolffd@0
|
886 end
|
wolffd@0
|
887 obj.groupSelectedRect = [];
|
wolffd@0
|
888 for i=1:numel(obj.groupSelectedNodes)
|
wolffd@0
|
889 obj.groupSelectedNodes(i).deselect();
|
wolffd@0
|
890 end
|
wolffd@0
|
891 obj.groupSelectedNodes = [];
|
wolffd@0
|
892 obj.groupSelectedDims = [];
|
wolffd@0
|
893 obj.groupSelectionMode = 0;
|
wolffd@0
|
894 set(gcf,'Pointer','arrow');
|
wolffd@0
|
895 end
|
wolffd@0
|
896
|
wolffd@0
|
897 function deleted(obj,varargin)
|
wolffd@0
|
898 % Called when the figure is deleted by the user.
|
wolffd@0
|
899 obj.isvisible = false;
|
wolffd@0
|
900 obj.clearGroupSelection();
|
wolffd@0
|
901 end
|
wolffd@0
|
902
|
wolffd@0
|
903 function singleClick(obj,node)
|
wolffd@0
|
904 % Called when a user single clicks on a node.
|
wolffd@0
|
905 obj.selectedNode = node;
|
wolffd@0
|
906 node.select();
|
wolffd@0
|
907 set(gcf,'Pointer','hand');
|
wolffd@0
|
908 obj.selectedFontSize = node.fontSize;
|
wolffd@0
|
909 node.useFullLabel = true;
|
wolffd@0
|
910 node.fontSize = max(15,node.fontSize*1.5);
|
wolffd@0
|
911 node.redraw();
|
wolffd@0
|
912 end
|
wolffd@0
|
913
|
wolffd@0
|
914 function doubleClick(obj,node)
|
wolffd@0
|
915 % Called when a user double clicks on a node
|
wolffd@0
|
916 if isempty(obj.doubleClickFn)
|
wolffd@0
|
917 description = node.description;
|
wolffd@0
|
918 if(~iscell(description))
|
wolffd@0
|
919 description = {description};
|
wolffd@0
|
920 end
|
wolffd@0
|
921 answer = inputdlg('',node.label,4,description);
|
wolffd@0
|
922 if(~isempty(answer))
|
wolffd@0
|
923 node.description = answer;
|
wolffd@0
|
924 end
|
wolffd@0
|
925 else
|
wolffd@0
|
926 obj.doubleClickFn(node.label);
|
wolffd@0
|
927 end
|
wolffd@0
|
928 end
|
wolffd@0
|
929
|
wolffd@0
|
930 function rightClick(obj,node) %#ok
|
wolffd@0
|
931 % Called when a user right clicks on a node
|
wolffd@0
|
932 if(node.isshaded)
|
wolffd@0
|
933 node.unshade();
|
wolffd@0
|
934 else
|
wolffd@0
|
935 node.shade();
|
wolffd@0
|
936 end
|
wolffd@0
|
937 end
|
wolffd@0
|
938
|
wolffd@0
|
939 function shiftClick(obj,node) %#ok
|
wolffd@0
|
940 % Called when a user shift clicks on a node
|
wolffd@0
|
941 display(node);
|
wolffd@0
|
942 end
|
wolffd@0
|
943
|
wolffd@0
|
944
|
wolffd@0
|
945 end
|
wolffd@0
|
946
|
wolffd@0
|
947 methods
|
wolffd@0
|
948 % Callbacks that can be called by the user programmatically.
|
wolffd@0
|
949 function shrinkNodes(obj,varargin)
|
wolffd@0
|
950 % Shrink the nodes to 95% of their original size, (but not smaller
|
wolffd@0
|
951 % than a calculated minimum.
|
wolffd@0
|
952 obj.clearGroupSelection();
|
wolffd@0
|
953 s = max(0.8*obj.nodeArray(1).width,obj.minNodeSize);
|
wolffd@0
|
954 obj.nodeArray(1).resize(s);
|
wolffd@0
|
955 obj.setFontSize();
|
wolffd@0
|
956 for i=1:obj.nnodes
|
wolffd@0
|
957 node = obj.nodeArray(i);
|
wolffd@0
|
958 node.fontSize = obj.fontSize;
|
wolffd@0
|
959 node.resize(s);
|
wolffd@0
|
960 end
|
wolffd@0
|
961 obj.displayEdges();
|
wolffd@0
|
962 end
|
wolffd@0
|
963
|
wolffd@0
|
964 function growNodes(obj,varargin)
|
wolffd@0
|
965 % Grow the nodes to 1/0.95 times their original size, (but not
|
wolffd@0
|
966 % larger than a calculated maximum.
|
wolffd@0
|
967 obj.clearGroupSelection();
|
wolffd@0
|
968 s = min(obj.nodeArray(1).width/0.8,1.5*obj.maxNodeSize);
|
wolffd@0
|
969 obj.nodeArray(1).resize(s);
|
wolffd@0
|
970 obj.setFontSize();
|
wolffd@0
|
971 for i=1:obj.nnodes
|
wolffd@0
|
972 node = obj.nodeArray(i);
|
wolffd@0
|
973 node.fontSize = obj.fontSize;
|
wolffd@0
|
974 node.resize(s);
|
wolffd@0
|
975 end
|
wolffd@0
|
976 obj.displayEdges();
|
wolffd@0
|
977 end
|
wolffd@0
|
978
|
wolffd@0
|
979 function increaseFontSize(obj,varargin)
|
wolffd@0
|
980 % Increase the fontsize of all the nodes by 0.5 points.
|
wolffd@0
|
981 current = get(obj.nodeArray(1).labelhandle,'FontSize');
|
wolffd@0
|
982 newsize = current + 1;
|
wolffd@0
|
983 for i=1:numel(obj.nodeArray)
|
wolffd@0
|
984 node = obj.nodeArray(i);
|
wolffd@0
|
985 node.fontSize = newsize;
|
wolffd@0
|
986 node.redraw();
|
wolffd@0
|
987 end
|
wolffd@0
|
988 end
|
wolffd@0
|
989
|
wolffd@0
|
990 function decreaseFontSize(obj,varargin)
|
wolffd@0
|
991 % Decrease the fontsize of all the nodes by 0.5 points.
|
wolffd@0
|
992 current = get(obj.nodeArray(1).labelhandle,'FontSize');
|
wolffd@0
|
993 newsize = max(current - 1,1);
|
wolffd@0
|
994 for i=1:numel(obj.nodeArray)
|
wolffd@0
|
995 node = obj.nodeArray(i);
|
wolffd@0
|
996 node.fontSize = newsize;
|
wolffd@0
|
997 node.redraw();
|
wolffd@0
|
998 end
|
wolffd@0
|
999 end
|
wolffd@0
|
1000
|
wolffd@0
|
1001 function XY = getNodePositions(obj)
|
wolffd@0
|
1002 % Return the current positions of the nodes. The bottom left
|
wolffd@0
|
1003 % corner is [0 0] and the top right is [1 1]. Node positions
|
wolffd@0
|
1004 % refer to the centre of a node.
|
wolffd@0
|
1005 XY = zeros(obj.nnodes, 2);
|
wolffd@0
|
1006 for i=1:obj.nnodes
|
wolffd@0
|
1007 XY(i, 1) = obj.nodeArray(i).xpos;
|
wolffd@0
|
1008 XY(i, 2) = obj.nodeArray(i).ypos;
|
wolffd@0
|
1009 end
|
wolffd@0
|
1010 end
|
wolffd@0
|
1011
|
wolffd@0
|
1012 function setNodePositions(obj, XY)
|
wolffd@0
|
1013 % Programmatically set the node positions
|
wolffd@0
|
1014 % XY(i, 1) is the xposition of node i, XY(i, 2) is the yposition.
|
wolffd@0
|
1015 for i=1:obj.nnodes
|
wolffd@0
|
1016 obj.nodeArray(i).move(XY(i, 1), XY(i, 2));
|
wolffd@0
|
1017 end
|
wolffd@0
|
1018 obj.displayGraph();
|
wolffd@0
|
1019 end
|
wolffd@0
|
1020
|
wolffd@0
|
1021 function moveNode(obj, nodeIndex, xpos, ypos)
|
wolffd@0
|
1022 % Programmatically set a node position.
|
wolffd@0
|
1023 obj.nodeArray(nodeIndex).move(xpos, ypos);
|
wolffd@0
|
1024 obj.displayGraph();
|
wolffd@0
|
1025 end
|
wolffd@0
|
1026
|
wolffd@0
|
1027 end
|
wolffd@0
|
1028 end
|