wolffd@0
|
1 classdef graphViz4MatlabNode < dynamicprops & hgsetget
|
wolffd@0
|
2 % This class represents a drawable node in an arbitrary graph.
|
wolffd@0
|
3 %
|
wolffd@0
|
4 % Public properties can be set using the standard Matlab set method as in
|
wolffd@0
|
5 % set(node,'curvature',[0,0],'linesytle','--','linecolor','g'). Call
|
wolffd@0
|
6 % node.redraw to redraw it after changing properties.
|
wolffd@0
|
7 %
|
wolffd@0
|
8 % Nodes are not really designed to live on their own, they should be
|
wolffd@0
|
9 % aggregated into a graph object responsible for layout.
|
wolffd@0
|
10 %
|
wolffd@0
|
11 % Matthew Dunham
|
wolffd@0
|
12 % University of British Columbia
|
wolffd@0
|
13 % http://www.cs.ubc.ca/~mdunham/
|
wolffd@0
|
14
|
wolffd@0
|
15 properties
|
wolffd@0
|
16 label; % displayed name for the node
|
wolffd@0
|
17 splitLabel; % label split in the middle
|
wolffd@0
|
18 useFullLabel = false; % if true, the full non-split label is used regardles of how long it is.
|
wolffd@0
|
19 description = ''; % text displayed when you click on the node
|
wolffd@0
|
20 curvature = [1 1]; % curvature of the node, [1,1] = circle
|
wolffd@0
|
21 lineStyle = '-'; % line style for the node
|
wolffd@0
|
22 lineWidth = 2; % line wdth for the node
|
wolffd@0
|
23 inedges = []; % indices of edges in
|
wolffd@0
|
24 outedges = []; % indices of edges out
|
wolffd@0
|
25 fontSize = 12; % The font size for the node
|
wolffd@0
|
26 showFullLabel = false; % If true, node labels are not split onto multiple lines regardelss of how long
|
wolffd@0
|
27 end
|
wolffd@0
|
28
|
wolffd@0
|
29 properties
|
wolffd@0
|
30 % Color properties
|
wolffd@0
|
31 lineColor = 'k'; % line color for the node
|
wolffd@0
|
32 selectedColor = [1 1 0.7]; % face color when selected with mouse
|
wolffd@0
|
33 faceColor = [1 1 0.8]; % face color when not shaded
|
wolffd@0
|
34 shadedColor = 'r'; % color when shaded, call shade() to shade
|
wolffd@0
|
35 textColor = 'k'; % label's text color
|
wolffd@0
|
36 containingGraph = []; % containing graph object
|
wolffd@0
|
37
|
wolffd@0
|
38 end
|
wolffd@0
|
39
|
wolffd@0
|
40 properties(GetAccess = 'public', SetAccess = 'protected')
|
wolffd@0
|
41 % Read only properties
|
wolffd@0
|
42 xpos = 0; % x-coordinate of node center relative to parent axes
|
wolffd@0
|
43 ypos = 0; % y-coordinate of node center relative to parent axes
|
wolffd@0
|
44 isvisible = false; % true iff the node is being displayed
|
wolffd@0
|
45 isshaded = false; % is the node shaded or not?
|
wolffd@0
|
46 width = 1; % width in data units
|
wolffd@0
|
47 height = 1; % height in data units
|
wolffd@0
|
48 end
|
wolffd@0
|
49
|
wolffd@0
|
50 properties(GetAccess = 'public', SetAccess = 'protected')
|
wolffd@0
|
51 % Handles to underlying Matlab graphics objects
|
wolffd@0
|
52 rechandle = []; % handle to the underlying rectangle object
|
wolffd@0
|
53 labelhandle = []; % handle to the underlying text object
|
wolffd@0
|
54 parent = []; % handle to the parent axes object
|
wolffd@0
|
55 isselected = false; % true iff, the node has been selected
|
wolffd@0
|
56 end
|
wolffd@0
|
57
|
wolffd@0
|
58 methods
|
wolffd@0
|
59
|
wolffd@0
|
60 function obj = graphViz4MatlabNode(label)
|
wolffd@0
|
61 % Node Constructor
|
wolffd@0
|
62 obj.label = label;
|
wolffd@0
|
63 obj.setSplitLabel(label);
|
wolffd@0
|
64
|
wolffd@0
|
65 end
|
wolffd@0
|
66
|
wolffd@0
|
67 function draw(obj,parent)
|
wolffd@0
|
68 % Draw the node on the specified parent axes. If no parent is
|
wolffd@0
|
69 % specified, the current axis is used.
|
wolffd@0
|
70 if(obj.isvisible)
|
wolffd@0
|
71 warning('GRAPHNODE:draw',['Node ',obj.label,' is already drawn, call redraw().']);
|
wolffd@0
|
72 return;
|
wolffd@0
|
73 end
|
wolffd@0
|
74 if(nargin < 2)
|
wolffd@0
|
75 if(isempty(obj.parent) || ~ishandle(obj.parent))
|
wolffd@0
|
76 obj.parent = gca;
|
wolffd@0
|
77 end
|
wolffd@0
|
78 else
|
wolffd@0
|
79 obj.parent = parent;
|
wolffd@0
|
80 end
|
wolffd@0
|
81 obj.drawNode();
|
wolffd@0
|
82 obj.setText();
|
wolffd@0
|
83 obj.isvisible = true;
|
wolffd@0
|
84 end
|
wolffd@0
|
85
|
wolffd@0
|
86 function redraw(obj)
|
wolffd@0
|
87 % Redraw the node, (must be called after node properties are
|
wolffd@0
|
88 % changed).
|
wolffd@0
|
89 if(obj.isvisible), obj.erase;end
|
wolffd@0
|
90 obj.draw();
|
wolffd@0
|
91 end
|
wolffd@0
|
92
|
wolffd@0
|
93 function erase(obj)
|
wolffd@0
|
94 % Erase the node but do not delete it
|
wolffd@0
|
95 if(~obj.isvisible)
|
wolffd@0
|
96 warning('GRAPHNODE:erase',['Node ',obj.label,' is already erased']);
|
wolffd@0
|
97 return;
|
wolffd@0
|
98 end
|
wolffd@0
|
99 delete(obj.rechandle);
|
wolffd@0
|
100 delete(obj.labelhandle);
|
wolffd@0
|
101 obj.rechandle = [];
|
wolffd@0
|
102 end
|
wolffd@0
|
103
|
wolffd@0
|
104 function shade(obj,color)
|
wolffd@0
|
105 % Shade the node the specified color. The default color is used if
|
wolffd@0
|
106 % none given.
|
wolffd@0
|
107 obj.isshaded = true;
|
wolffd@0
|
108 if(nargin == 2)
|
wolffd@0
|
109 obj.shadedColor = color;
|
wolffd@0
|
110 end
|
wolffd@0
|
111 if(obj.isvisible)
|
wolffd@0
|
112 set(obj.rechandle,'FaceColor',obj.shadedColor);
|
wolffd@0
|
113 end
|
wolffd@0
|
114 end
|
wolffd@0
|
115
|
wolffd@0
|
116 function unshade(obj)
|
wolffd@0
|
117 % Unshade the node
|
wolffd@0
|
118 obj.isshaded = false;
|
wolffd@0
|
119 if(obj.isvisible)
|
wolffd@0
|
120 set(obj.rechandle,'FaceColor',obj.faceColor);
|
wolffd@0
|
121 end
|
wolffd@0
|
122 end
|
wolffd@0
|
123
|
wolffd@0
|
124 function resize(obj,width,height)
|
wolffd@0
|
125 % Resize the node by the specified proportion
|
wolffd@0
|
126 if(nargin < 3)
|
wolffd@0
|
127 if(~isempty(obj.containingGraph))
|
wolffd@0
|
128 height = width;
|
wolffd@0
|
129 end
|
wolffd@0
|
130 end
|
wolffd@0
|
131 obj.width = width;
|
wolffd@0
|
132 obj.height = height;
|
wolffd@0
|
133 if(obj.isvisible), obj.redraw; end
|
wolffd@0
|
134 end
|
wolffd@0
|
135
|
wolffd@0
|
136 function move(obj,x,y)
|
wolffd@0
|
137 % Move the node's center to the new x,y coordinates, (relative to
|
wolffd@0
|
138 % the parent axes.)
|
wolffd@0
|
139 obj.xpos = x; obj.ypos = y;
|
wolffd@0
|
140 if(obj.isvisible),obj.redraw;end
|
wolffd@0
|
141 end
|
wolffd@0
|
142
|
wolffd@0
|
143 function select(obj)
|
wolffd@0
|
144 % Call this function to set the node in a selected state.
|
wolffd@0
|
145 obj.isselected = true;
|
wolffd@0
|
146 if(obj.isvisible)
|
wolffd@0
|
147 set(obj.rechandle,'faceColor',obj.selectedColor);
|
wolffd@0
|
148 end
|
wolffd@0
|
149 end
|
wolffd@0
|
150
|
wolffd@0
|
151
|
wolffd@0
|
152 function deselect(obj)
|
wolffd@0
|
153 % Call this function to deselect the node.
|
wolffd@0
|
154 obj.isselected = false;
|
wolffd@0
|
155 if(obj.isvisible)
|
wolffd@0
|
156 obj.redraw;
|
wolffd@0
|
157 end
|
wolffd@0
|
158 end
|
wolffd@0
|
159
|
wolffd@0
|
160 end % end of public methods
|
wolffd@0
|
161
|
wolffd@0
|
162 methods(Access = 'protected')
|
wolffd@0
|
163
|
wolffd@0
|
164 function nodePressed(obj,varargin)
|
wolffd@0
|
165 % This function is called whenever the node is pressed.
|
wolffd@0
|
166 if(~isempty(obj.containingGraph))
|
wolffd@0
|
167 obj.containingGraph.nodeSelected(obj);
|
wolffd@0
|
168 end
|
wolffd@0
|
169 end
|
wolffd@0
|
170
|
wolffd@0
|
171 function nodeDeleted(obj)
|
wolffd@0
|
172 % This function is called whenver the node is deleted, (perhaps
|
wolffd@0
|
173 % because the figure window was closed for instance).
|
wolffd@0
|
174 obj.isvisible = false;
|
wolffd@0
|
175 obj.parent = [];
|
wolffd@0
|
176 end
|
wolffd@0
|
177
|
wolffd@0
|
178 function drawNode(obj)
|
wolffd@0
|
179 % Draw the actual node
|
wolffd@0
|
180 recxpos = obj.xpos - obj.width/2;
|
wolffd@0
|
181 recypos = obj.ypos - obj.height/2;
|
wolffd@0
|
182 lineColor = obj.lineColor;
|
wolffd@0
|
183 lineWidth = obj.lineWidth;
|
wolffd@0
|
184 if(obj.isselected)
|
wolffd@0
|
185 color = obj.selectedColor;
|
wolffd@0
|
186 lineColor = 'r';
|
wolffd@0
|
187 lineWidth = 1.5*lineWidth;
|
wolffd@0
|
188 elseif(obj.isshaded)
|
wolffd@0
|
189 color = obj.shadedColor;
|
wolffd@0
|
190 else
|
wolffd@0
|
191 color = obj.faceColor;
|
wolffd@0
|
192 end
|
wolffd@0
|
193 obj.rechandle = rectangle(...
|
wolffd@0
|
194 'Parent' ,obj.parent ,...
|
wolffd@0
|
195 'Position' ,[recxpos,recypos,obj.width,obj.height] ,...
|
wolffd@0
|
196 'Curvature' ,obj.curvature ,...
|
wolffd@0
|
197 'LineWidth' , lineWidth ,...
|
wolffd@0
|
198 'LineStyle' ,obj.lineStyle ,...
|
wolffd@0
|
199 'EdgeColor' ,lineColor ,...
|
wolffd@0
|
200 'faceColor' ,color ,...
|
wolffd@0
|
201 'DisplayName' ,obj.label ,...
|
wolffd@0
|
202 'Tag' ,obj.label ,...
|
wolffd@0
|
203 'ButtonDownFcn',@obj.nodePressed ,...
|
wolffd@0
|
204 'UserData' ,obj ,...
|
wolffd@0
|
205 'DeleteFcn' ,@(varargin)obj.nodeDeleted() );
|
wolffd@0
|
206 end
|
wolffd@0
|
207
|
wolffd@0
|
208 function setText(obj)
|
wolffd@0
|
209 % Draw the node's label
|
wolffd@0
|
210 if((length(obj.label) < 10) || obj.useFullLabel || obj.showFullLabel)
|
wolffd@0
|
211 label = obj.label;
|
wolffd@0
|
212 else
|
wolffd@0
|
213 label = obj.splitLabel;
|
wolffd@0
|
214 end
|
wolffd@0
|
215 obj.labelhandle = text(obj.xpos,obj.ypos,label ,...
|
wolffd@0
|
216 'FontUnits' , 'points' ,...
|
wolffd@0
|
217 'HitTest' , 'off' ,...
|
wolffd@0
|
218 'FontWeight' , 'demi' ,...
|
wolffd@0
|
219 'Margin' , 0.01 ,...
|
wolffd@0
|
220 'HorizontalAlignment' , 'center' ,...
|
wolffd@0
|
221 'BackGroundColor' , 'none' ,...
|
wolffd@0
|
222 'Selected' , 'off' ,...
|
wolffd@0
|
223 'VerticalAlignment' , 'middle' ,...
|
wolffd@0
|
224 'LineStyle' , 'none' ,...
|
wolffd@0
|
225 'FontSize' , obj.fontSize ,...
|
wolffd@0
|
226 'Color' , obj.textColor );
|
wolffd@0
|
227 if(obj.useFullLabel)
|
wolffd@0
|
228 set(obj.labelhandle,'BackgroundColor',obj.selectedColor,'Margin',6,'EdgeColor','k','LineStyle','-');
|
wolffd@0
|
229 end
|
wolffd@0
|
230 end
|
wolffd@0
|
231
|
wolffd@0
|
232 function resizeText(obj)
|
wolffd@0
|
233 % Resize the text to fill the node (too slow for large graphs)
|
wolffd@0
|
234 fontsize = obj.maxFontSize;
|
wolffd@0
|
235 set(obj.labelhandle,'FontSize',fontsize);
|
wolffd@0
|
236 extent = get(obj.labelhandle,'Extent');
|
wolffd@0
|
237
|
wolffd@0
|
238 while((extent(1) < (obj.xpos - obj.width/2)) || (extent(2)+(extent(4)) > (obj.ypos + obj.height/2)))
|
wolffd@0
|
239 fontsize = 0.95*fontsize;
|
wolffd@0
|
240 set(obj.labelhandle,'FontSize',fontsize);
|
wolffd@0
|
241 extent = get(obj.labelhandle,'Extent');
|
wolffd@0
|
242 end
|
wolffd@0
|
243 end
|
wolffd@0
|
244
|
wolffd@0
|
245 function setSplitLabel(obj,label)
|
wolffd@0
|
246
|
wolffd@0
|
247 obj.splitLabel = splitString(label,8,10);
|
wolffd@0
|
248
|
wolffd@0
|
249 end
|
wolffd@0
|
250
|
wolffd@0
|
251 end % end of protected methods
|
wolffd@0
|
252
|
wolffd@0
|
253 end % end of graphnode class
|
wolffd@0
|
254
|
wolffd@0
|
255
|
wolffd@0
|
256
|
wolffd@0
|
257
|
wolffd@0
|
258
|
wolffd@0
|
259 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
wolffd@0
|
260 function S = splitString(varargin)
|
wolffd@0
|
261 % Split a string into multiple lines based on camel case.
|
wolffd@0
|
262 %
|
wolffd@0
|
263 % Inputs
|
wolffd@0
|
264 %
|
wolffd@0
|
265 % '-S' the string to split
|
wolffd@0
|
266 % '-minSize' does not split at all if length(S) < minSize
|
wolffd@0
|
267 % '-maxSize' splits no matter what, (even if no camel case change) if length(S) > maxSize
|
wolffd@0
|
268 % '-center' if true, [default], the string is center justified
|
wolffd@0
|
269 % '-cellMode' if true, a cell array of strings is returned, instead of a char array.
|
wolffd@0
|
270
|
wolffd@0
|
271 [S,minSize,maxSize,cellMode,center] = processArgs(varargin,'*+-S','','-minSize',8,'-maxSize',10,'-cellMode',false,'-center',true);
|
wolffd@0
|
272
|
wolffd@0
|
273 S = splitInTwo(S);
|
wolffd@0
|
274 if center
|
wolffd@0
|
275 S = strjust(S,'center');
|
wolffd@0
|
276 end
|
wolffd@0
|
277 if cellMode
|
wolffd@0
|
278 S = cellstr(S);
|
wolffd@0
|
279 end
|
wolffd@0
|
280
|
wolffd@0
|
281 function str = splitInTwo(str)
|
wolffd@0
|
282 % recursively split a string into two based on camel case
|
wolffd@0
|
283 isupper = isstrprop(str(2:end),'upper');
|
wolffd@0
|
284 if(size(str,2) >= minSize && any(isupper))
|
wolffd@0
|
285 first = find(isupper); first = first(1);
|
wolffd@0
|
286 top = str(1:first);
|
wolffd@0
|
287 bottom = str(first+1:end);
|
wolffd@0
|
288 str = strvcat(splitInTwo(top),splitInTwo(bottom)); %#ok
|
wolffd@0
|
289 elseif(size(str,2) > maxSize)
|
wolffd@0
|
290 top = [str(1:floor(length(str)/2)),'-'];
|
wolffd@0
|
291 bottom = str(floor(length(str)/2)+1:end);
|
wolffd@0
|
292 str = strvcat(splitInTwo(top),splitInTwo(bottom)); %#ok
|
wolffd@0
|
293 end
|
wolffd@0
|
294 end
|
wolffd@0
|
295 end
|
wolffd@0
|
296
|
wolffd@0
|
297
|
wolffd@0
|
298
|