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