Daniel@0: classdef graphViz4MatlabNode < dynamicprops & hgsetget Daniel@0: % This class represents a drawable node in an arbitrary graph. Daniel@0: % Daniel@0: % Public properties can be set using the standard Matlab set method as in Daniel@0: % set(node,'curvature',[0,0],'linesytle','--','linecolor','g'). Call Daniel@0: % node.redraw to redraw it after changing properties. Daniel@0: % Daniel@0: % Nodes are not really designed to live on their own, they should be Daniel@0: % aggregated into a graph object responsible for layout. Daniel@0: % Daniel@0: % Matthew Dunham Daniel@0: % University of British Columbia Daniel@0: % http://www.cs.ubc.ca/~mdunham/ Daniel@0: Daniel@0: properties Daniel@0: label; % displayed name for the node Daniel@0: splitLabel; % label split in the middle Daniel@0: useFullLabel = false; % if true, the full non-split label is used regardles of how long it is. Daniel@0: description = ''; % text displayed when you click on the node Daniel@0: curvature = [1 1]; % curvature of the node, [1,1] = circle Daniel@0: lineStyle = '-'; % line style for the node Daniel@0: lineWidth = 2; % line wdth for the node Daniel@0: inedges = []; % indices of edges in Daniel@0: outedges = []; % indices of edges out Daniel@0: fontSize = 12; % The font size for the node Daniel@0: showFullLabel = false; % If true, node labels are not split onto multiple lines regardelss of how long Daniel@0: end Daniel@0: Daniel@0: properties Daniel@0: % Color properties Daniel@0: lineColor = 'k'; % line color for the node Daniel@0: selectedColor = [1 1 0.7]; % face color when selected with mouse Daniel@0: faceColor = [1 1 0.8]; % face color when not shaded Daniel@0: shadedColor = 'r'; % color when shaded, call shade() to shade Daniel@0: textColor = 'k'; % label's text color Daniel@0: containingGraph = []; % containing graph object Daniel@0: Daniel@0: end Daniel@0: Daniel@0: properties(GetAccess = 'public', SetAccess = 'protected') Daniel@0: % Read only properties Daniel@0: xpos = 0; % x-coordinate of node center relative to parent axes Daniel@0: ypos = 0; % y-coordinate of node center relative to parent axes Daniel@0: isvisible = false; % true iff the node is being displayed Daniel@0: isshaded = false; % is the node shaded or not? Daniel@0: width = 1; % width in data units Daniel@0: height = 1; % height in data units Daniel@0: end Daniel@0: Daniel@0: properties(GetAccess = 'public', SetAccess = 'protected') Daniel@0: % Handles to underlying Matlab graphics objects Daniel@0: rechandle = []; % handle to the underlying rectangle object Daniel@0: labelhandle = []; % handle to the underlying text object Daniel@0: parent = []; % handle to the parent axes object Daniel@0: isselected = false; % true iff, the node has been selected Daniel@0: end Daniel@0: Daniel@0: methods Daniel@0: Daniel@0: function obj = graphViz4MatlabNode(label) Daniel@0: % Node Constructor Daniel@0: obj.label = label; Daniel@0: obj.setSplitLabel(label); Daniel@0: Daniel@0: end Daniel@0: Daniel@0: function draw(obj,parent) Daniel@0: % Draw the node on the specified parent axes. If no parent is Daniel@0: % specified, the current axis is used. Daniel@0: if(obj.isvisible) Daniel@0: warning('GRAPHNODE:draw',['Node ',obj.label,' is already drawn, call redraw().']); Daniel@0: return; Daniel@0: end Daniel@0: if(nargin < 2) Daniel@0: if(isempty(obj.parent) || ~ishandle(obj.parent)) Daniel@0: obj.parent = gca; Daniel@0: end Daniel@0: else Daniel@0: obj.parent = parent; Daniel@0: end Daniel@0: obj.drawNode(); Daniel@0: obj.setText(); Daniel@0: obj.isvisible = true; Daniel@0: end Daniel@0: Daniel@0: function redraw(obj) Daniel@0: % Redraw the node, (must be called after node properties are Daniel@0: % changed). Daniel@0: if(obj.isvisible), obj.erase;end Daniel@0: obj.draw(); Daniel@0: end Daniel@0: Daniel@0: function erase(obj) Daniel@0: % Erase the node but do not delete it Daniel@0: if(~obj.isvisible) Daniel@0: warning('GRAPHNODE:erase',['Node ',obj.label,' is already erased']); Daniel@0: return; Daniel@0: end Daniel@0: delete(obj.rechandle); Daniel@0: delete(obj.labelhandle); Daniel@0: obj.rechandle = []; Daniel@0: end Daniel@0: Daniel@0: function shade(obj,color) Daniel@0: % Shade the node the specified color. The default color is used if Daniel@0: % none given. Daniel@0: obj.isshaded = true; Daniel@0: if(nargin == 2) Daniel@0: obj.shadedColor = color; Daniel@0: end Daniel@0: if(obj.isvisible) Daniel@0: set(obj.rechandle,'FaceColor',obj.shadedColor); Daniel@0: end Daniel@0: end Daniel@0: Daniel@0: function unshade(obj) Daniel@0: % Unshade the node Daniel@0: obj.isshaded = false; Daniel@0: if(obj.isvisible) Daniel@0: set(obj.rechandle,'FaceColor',obj.faceColor); Daniel@0: end Daniel@0: end Daniel@0: Daniel@0: function resize(obj,width,height) Daniel@0: % Resize the node by the specified proportion Daniel@0: if(nargin < 3) Daniel@0: if(~isempty(obj.containingGraph)) Daniel@0: height = width; Daniel@0: end Daniel@0: end Daniel@0: obj.width = width; Daniel@0: obj.height = height; Daniel@0: if(obj.isvisible), obj.redraw; end Daniel@0: end Daniel@0: Daniel@0: function move(obj,x,y) Daniel@0: % Move the node's center to the new x,y coordinates, (relative to Daniel@0: % the parent axes.) Daniel@0: obj.xpos = x; obj.ypos = y; Daniel@0: if(obj.isvisible),obj.redraw;end Daniel@0: end Daniel@0: Daniel@0: function select(obj) Daniel@0: % Call this function to set the node in a selected state. Daniel@0: obj.isselected = true; Daniel@0: if(obj.isvisible) Daniel@0: set(obj.rechandle,'faceColor',obj.selectedColor); Daniel@0: end Daniel@0: end Daniel@0: Daniel@0: Daniel@0: function deselect(obj) Daniel@0: % Call this function to deselect the node. Daniel@0: obj.isselected = false; Daniel@0: if(obj.isvisible) Daniel@0: obj.redraw; Daniel@0: end Daniel@0: end Daniel@0: Daniel@0: end % end of public methods Daniel@0: Daniel@0: methods(Access = 'protected') Daniel@0: Daniel@0: function nodePressed(obj,varargin) Daniel@0: % This function is called whenever the node is pressed. Daniel@0: if(~isempty(obj.containingGraph)) Daniel@0: obj.containingGraph.nodeSelected(obj); Daniel@0: end Daniel@0: end Daniel@0: Daniel@0: function nodeDeleted(obj) Daniel@0: % This function is called whenver the node is deleted, (perhaps Daniel@0: % because the figure window was closed for instance). Daniel@0: obj.isvisible = false; Daniel@0: obj.parent = []; Daniel@0: end Daniel@0: Daniel@0: function drawNode(obj) Daniel@0: % Draw the actual node Daniel@0: recxpos = obj.xpos - obj.width/2; Daniel@0: recypos = obj.ypos - obj.height/2; Daniel@0: lineColor = obj.lineColor; Daniel@0: lineWidth = obj.lineWidth; Daniel@0: if(obj.isselected) Daniel@0: color = obj.selectedColor; Daniel@0: lineColor = 'r'; Daniel@0: lineWidth = 1.5*lineWidth; Daniel@0: elseif(obj.isshaded) Daniel@0: color = obj.shadedColor; Daniel@0: else Daniel@0: color = obj.faceColor; Daniel@0: end Daniel@0: obj.rechandle = rectangle(... Daniel@0: 'Parent' ,obj.parent ,... Daniel@0: 'Position' ,[recxpos,recypos,obj.width,obj.height] ,... Daniel@0: 'Curvature' ,obj.curvature ,... Daniel@0: 'LineWidth' , lineWidth ,... Daniel@0: 'LineStyle' ,obj.lineStyle ,... Daniel@0: 'EdgeColor' ,lineColor ,... Daniel@0: 'faceColor' ,color ,... Daniel@0: 'DisplayName' ,obj.label ,... Daniel@0: 'Tag' ,obj.label ,... Daniel@0: 'ButtonDownFcn',@obj.nodePressed ,... Daniel@0: 'UserData' ,obj ,... Daniel@0: 'DeleteFcn' ,@(varargin)obj.nodeDeleted() ); Daniel@0: end Daniel@0: Daniel@0: function setText(obj) Daniel@0: % Draw the node's label Daniel@0: if((length(obj.label) < 10) || obj.useFullLabel || obj.showFullLabel) Daniel@0: label = obj.label; Daniel@0: else Daniel@0: label = obj.splitLabel; Daniel@0: end Daniel@0: obj.labelhandle = text(obj.xpos,obj.ypos,label ,... Daniel@0: 'FontUnits' , 'points' ,... Daniel@0: 'HitTest' , 'off' ,... Daniel@0: 'FontWeight' , 'demi' ,... Daniel@0: 'Margin' , 0.01 ,... Daniel@0: 'HorizontalAlignment' , 'center' ,... Daniel@0: 'BackGroundColor' , 'none' ,... Daniel@0: 'Selected' , 'off' ,... Daniel@0: 'VerticalAlignment' , 'middle' ,... Daniel@0: 'LineStyle' , 'none' ,... Daniel@0: 'FontSize' , obj.fontSize ,... Daniel@0: 'Color' , obj.textColor ); Daniel@0: if(obj.useFullLabel) Daniel@0: set(obj.labelhandle,'BackgroundColor',obj.selectedColor,'Margin',6,'EdgeColor','k','LineStyle','-'); Daniel@0: end Daniel@0: end Daniel@0: Daniel@0: function resizeText(obj) Daniel@0: % Resize the text to fill the node (too slow for large graphs) Daniel@0: fontsize = obj.maxFontSize; Daniel@0: set(obj.labelhandle,'FontSize',fontsize); Daniel@0: extent = get(obj.labelhandle,'Extent'); Daniel@0: Daniel@0: while((extent(1) < (obj.xpos - obj.width/2)) || (extent(2)+(extent(4)) > (obj.ypos + obj.height/2))) Daniel@0: fontsize = 0.95*fontsize; Daniel@0: set(obj.labelhandle,'FontSize',fontsize); Daniel@0: extent = get(obj.labelhandle,'Extent'); Daniel@0: end Daniel@0: end Daniel@0: Daniel@0: function setSplitLabel(obj,label) Daniel@0: Daniel@0: obj.splitLabel = splitString(label,8,10); Daniel@0: Daniel@0: end Daniel@0: Daniel@0: end % end of protected methods Daniel@0: Daniel@0: end % end of graphnode class Daniel@0: Daniel@0: Daniel@0: Daniel@0: Daniel@0: Daniel@0: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Daniel@0: function S = splitString(varargin) Daniel@0: % Split a string into multiple lines based on camel case. Daniel@0: % Daniel@0: % Inputs Daniel@0: % Daniel@0: % '-S' the string to split Daniel@0: % '-minSize' does not split at all if length(S) < minSize Daniel@0: % '-maxSize' splits no matter what, (even if no camel case change) if length(S) > maxSize Daniel@0: % '-center' if true, [default], the string is center justified Daniel@0: % '-cellMode' if true, a cell array of strings is returned, instead of a char array. Daniel@0: Daniel@0: [S,minSize,maxSize,cellMode,center] = processArgs(varargin,'*+-S','','-minSize',8,'-maxSize',10,'-cellMode',false,'-center',true); Daniel@0: Daniel@0: S = splitInTwo(S); Daniel@0: if center Daniel@0: S = strjust(S,'center'); Daniel@0: end Daniel@0: if cellMode Daniel@0: S = cellstr(S); Daniel@0: end Daniel@0: Daniel@0: function str = splitInTwo(str) Daniel@0: % recursively split a string into two based on camel case Daniel@0: isupper = isstrprop(str(2:end),'upper'); Daniel@0: if(size(str,2) >= minSize && any(isupper)) Daniel@0: first = find(isupper); first = first(1); Daniel@0: top = str(1:first); Daniel@0: bottom = str(first+1:end); Daniel@0: str = strvcat(splitInTwo(top),splitInTwo(bottom)); %#ok Daniel@0: elseif(size(str,2) > maxSize) Daniel@0: top = [str(1:floor(length(str)/2)),'-']; Daniel@0: bottom = str(floor(length(str)/2)+1:end); Daniel@0: str = strvcat(splitInTwo(top),splitInTwo(bottom)); %#ok Daniel@0: end Daniel@0: end Daniel@0: end Daniel@0: Daniel@0: Daniel@0: