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