wolffd@0: classdef Gvizlayout < Abstractlayout wolffd@0: % Use the graphVIZ package to determine the optimal layout for a graph. wolffd@0: % wolffd@0: % Matthew Dunham wolffd@0: % University of British Columbia wolffd@0: % http://www.cs.ubc.ca/~mdunham/ wolffd@0: properties wolffd@0: xmin; % The left most point on the graph axis in data units wolffd@0: xmax; % The right most point on the graph axis in data units wolffd@0: ymin; % The bottom most point on the graph axis in data units wolffd@0: ymax; % The top most point on the graph axis in data units wolffd@0: adjMatrix; % The adjacency matrix wolffd@0: maxNodeSize; % The maximum diameter of a node in data units wolffd@0: image; % An image for the button that will lanuch this layout wolffd@0: name; % A unique name for instances of this class. wolffd@0: shortDescription; % A short descriptions used in tooltips. wolffd@0: nodeSize; % The calculated node size, call dolayout() before accessing wolffd@0: centers; % The calculated node centers in an n-by-2 matrix wolffd@0: end wolffd@0: wolffd@0: properties(GetAccess = 'protected', SetAccess = 'protected') wolffd@0: layoutFile = 'layout.dot'; wolffd@0: adjFile = 'adjmat.dot'; wolffd@0: end wolffd@0: wolffd@0: methods wolffd@0: wolffd@0: function obj = Gvizlayout(name) wolffd@0: % constructor wolffd@0: if(nargin < 1) wolffd@0: obj.name = 'Gvizlayout'; wolffd@0: else wolffd@0: obj.name = name; wolffd@0: end wolffd@0: load glicons; wolffd@0: obj.image = icons.energy; wolffd@0: obj.shortDescription = 'Minimum Energy Layout (GraphViz)'; wolffd@0: end wolffd@0: wolffd@0: function available = isavailable(obj) wolffd@0: % Make sure graphViz is available. wolffd@0: available = Gvizlayout.queryGviz('neato'); wolffd@0: if(not(available)) wolffd@0: fprintf('Please install or upgrade graphViz\n'); wolffd@0: end wolffd@0: end wolffd@0: wolffd@0: wolffd@0: end wolffd@0: wolffd@0: wolffd@0: methods(Access = 'protected') wolffd@0: wolffd@0: function calcLayout(obj) wolffd@0: % Have graphViz calculate the layout wolffd@0: obj.writeDOTfile(); wolffd@0: obj.callGraphViz(); wolffd@0: obj.readLayout(); wolffd@0: obj.cleanup(); wolffd@0: end wolffd@0: wolffd@0: function writeDOTfile(obj) wolffd@0: % Write the adjacency matrix into a dot file that graphViz can wolffd@0: % understand. wolffd@0: fid = fopen('adjmat.dot','w'); wolffd@0: fprintf(fid,'digraph G {\ncenter = 1;\nsize="10,10";\n'); wolffd@0: n = size(obj.adjMatrix,1); wolffd@0: for i=1:n, fprintf(fid,'%d;\n',i); end wolffd@0: edgetxt = ' -> '; wolffd@0: for i=1:n wolffd@0: for j=1:n wolffd@0: if(obj.adjMatrix(i,j)) wolffd@0: fprintf(fid,'%d%s%d;\n',i,edgetxt,j); wolffd@0: end wolffd@0: end wolffd@0: end wolffd@0: fprintf(fid,'}'); wolffd@0: fclose(fid); wolffd@0: end wolffd@0: wolffd@0: function callGraphViz(obj) wolffd@0: % Call GraphViz to determine an optimal layout. Write this layout in wolffd@0: % layout.dot for later parsing. wolffd@0: err = system(['neato -Tdot -Gmaxiter=5000 -Gstart=7 -o ',obj.layoutFile,' ',obj.adjFile]); wolffd@0: if(err),error('Sorry, unknown GraphViz failure, try another layout'); end wolffd@0: end wolffd@0: wolffd@0: function readLayout(obj) wolffd@0: % Parse the layout.dot file for the graphViz node locations and wolffd@0: % dimensions. wolffd@0: fid = fopen(obj.layoutFile,'r'); wolffd@0: text = textscan(fid,'%s','delimiter','\n'); wolffd@0: fclose(fid); wolffd@0: text = text{:}; wolffd@0: [start,dims] = strtok(text{cellfun(@(x)~isempty(x),strfind(text,'graph [bb="'))},'"'); wolffd@0: dims = textscan(strtok(dims,'"'),'%n','delimiter',','); wolffd@0: dims = dims{:}'; wolffd@0: text(cellfun(@(x)~isempty(x),strfind(text,' -> ')))=[]; % delete edge info, we don't need it wolffd@0: text(cellfun(@(x)isempty(x),strfind(text,'pos')))=[]; % keep only positions wolffd@0: [start,remaining] = strtok(text,'"'); wolffd@0: [locations,remaining] = strtok(remaining,'"'); wolffd@0: locations = cellfun(@(str)textscan(str,'%n','delimiter',','),locations,'UniformOutput',false); wolffd@0: locations = [locations{:}]; wolffd@0: locations = [locations{:}]'; wolffd@0: obj.scaleLocations(locations,dims); wolffd@0: end wolffd@0: wolffd@0: function scaleLocations(obj,locations,graphVizDims) wolffd@0: % Scale the graphViz node locations to the smartgraph dimensions and wolffd@0: % set the node size. wolffd@0: dims = graphVizDims; loc = locations; wolffd@0: loc(:,1) = (loc(:,1)-dims(1)) ./ (dims(3)-dims(1))*(obj.xmax - obj.xmin) + obj.xmin; wolffd@0: loc(:,2) = (loc(:,2)-dims(2)) ./ (dims(4)-dims(2))*(obj.ymax - obj.ymin) + obj.ymin; wolffd@0: obj.centers = loc; wolffd@0: wolffd@0: a = min(abs(loc(:,1) - obj.xmin)); wolffd@0: b = min(abs(loc(:,1) - obj.xmax)); wolffd@0: c = min(abs(loc(:,2) - obj.ymin)); wolffd@0: d = min(abs(loc(:,2) - obj.ymax)); wolffd@0: obj.nodeSize = min(2*min([a,b,c,d]),obj.maxNodeSize); wolffd@0: wolffd@0: end wolffd@0: wolffd@0: function cleanup(obj) wolffd@0: % delete the temporary files. wolffd@0: delete(obj.adjFile); wolffd@0: delete(obj.layoutFile); wolffd@0: end wolffd@0: wolffd@0: end wolffd@0: wolffd@0: methods(Access = 'protected',Static = true) wolffd@0: wolffd@0: function available = queryGviz(name) wolffd@0: err = system([name,' -V']); wolffd@0: available = ~err; wolffd@0: end wolffd@0: wolffd@0: end wolffd@0: wolffd@0: wolffd@0: end % end of graphVIZlayout class