Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ploting a nested cell as a tree using treeplot: MATLAB

I have a complex cell that represents a tree structure:

CellArray = {1,1,1,{1,1,1,{1,1,{1,{1 1 1 1 1 1 1 1}, 1,1}, 1,1},1,1,1},1,1,1,{1,1,1,1}};

I want to plot the representative tree from it by using treeplot(p), but I'm not sure how to construct the array p for it to display correctly.

like image 994
Zander Avatar asked Jan 04 '23 14:01

Zander


1 Answers

We can create a recursive function, which explores your cell array and creates a tree pointer array (as described in the docs) to each node's parent.

This function takes a cell array (like the one in your question) which contains either scalars or nested cell arrays.


treebuilder logic:

  1. If an item is a scalar, assign it a parent node number, increment node number by 1
  2. If an item is a cell array, run treebuilder on that cell, returning the maximum node number which was reached (along with the generated sub-tree).
  3. Recursive function because of step 2, so repeat until every element done

Function:

function treearray = getTreeArray(cellarray)
    % initialise the array construction from node 0
    treearray = [0, treebuilder(cellarray, 1)]; 
    % recursive tree building function, pass it a cell array and root node
    function [out, node] = treebuilder(cellarray, rnode)
        % Set up variables to be populated whilst looping
        out = []; 
        % Start node off at root node
        node = rnode;
        % Loop over cell array elements, either recurse or add node
        for ii = 1:numel(cellarray)
            tb = []; node = node + 1;
            if iscell(cellarray{ii})
                [tb, node] = treebuilder(cellarray{ii}, node);
            end
            out = [out, rnode, tb];   
        end
    end
end

Usage with simple example

Here is a more simple example than yours, so we can check logic works easily.

myCellArray = {1 1 {1 1 1 {1 1 1}}};
% This cell array has 3 levels: 
% - 3 child nodes (2,3,4) of the root node (1) 
% - Last node on the first level (4) has 4 children:
%     - 4 child nodes on second level (5,6,7,8) 
%     - Last node on the first level (8) has 3 children:
%         - 3 child nodes on third level (9,10,11)
myTreeArray = getTreeArray(myCellArray);
% Output, we see the corresponding nodes as listed above:
% [0 1 1 1 4 4 4 4 8 8 8]
treeplot(myTreeArray)

simple


Your cell array

I think this works as expected, note you don't have to define the myCellArray or myTreeArray variables:

treeplot(getTreeArray({1,1,1,{1,1,1,{1,1,{1,{1 1 1 1 1 1 1 1}, 1,1}, 1,1},1,1,1},1,1,1,{1,1,1,1}}))

Here is the output image, showing that the algorithm can cope with the more complicated tree. Speed doesn't seem too bad either, although displaying extremely complicated trees will be fairly redundant anyway!


Edit: Labelling the nodes

You can label the nodes by getting their position using treelayout and keeping track of the values as you encounter them when building the tree array. The function should be tweaked for this "keeping track" like so:

function [treearray, nodevals] = getTreeArray(cellarray)
    % initialise the array construction from node 0
    [nodes, ~, nodevals] = treebuilder(cellarray, 1); 
    treearray = [0, nodes];
    % recursive tree building function, pass it a cell array and root node
    function [out, node, nodevals] = treebuilder(cellarray, rnode)
        % Set up variables to be populated whilst looping
        out = []; nodevals = {};
        % Start node off at root node
        node = rnode;
        % Loop over cell array elements, either recurse or add node
        for ii = 1:numel(cellarray)
            node = node + 1;
            if iscell(cellarray{ii})
                [tb, node, nv] = treebuilder(cellarray{ii}, node);
                out = [out, rnode, tb];  
                nodevals = [nodevals, nv];
            else
                out = [out, rnode];
                nodevals = [nodevals, {node; cellarray{ii}}];
            end 
        end
    end
end

Note: You could use a similar adaptation to keep track of the node number instead of the node value if you wanted to number each node on the plot.

I have used a cell array here so that you could have text or numerical values on each node. If you only ever want numerical values, it may shorten the post-formatting to store nodevals in a matrix instead.

Then to plot this you can use

% Run the tree building script above
[treearray, nodevals] = getTreeArray(myCellArray);
% Plot
treeplot(treearray);
% Get the position of each node on the plot  
[x,y] = treelayout(treearray);
% Get the indices of the nodes which have values stored
nodeidx = cell2mat(nodevals(1,:));
% Get the labels (values) corresponding to those nodes. Must be strings in cell array
labels = cellfun(@num2str, nodevals(2,:), 'uniformoutput', 0);
% Add labels, with a vertical offset to the y coords so that labels don't sit on nodes
text(x(nodeidx), y(nodeidx) - 0.03, labels);

Example output for the cell myCellArray = {{17, 99.9}, 50}, where I've chosen those numbers to make it clear they're not the actual "node number"!

enter image description here

like image 116
Wolfie Avatar answered Jan 21 '23 13:01

Wolfie