Slow Tree Expansion

Hi,

I use a JTree to display a file system. For user convenience, there are buttons to expand/collapse the complete tree in a recursive manner. However, when the tree reaches a certain size, the expansion/collapsing takes too much time.

On an AMD X2 3800EE it takes for 5031 nodes about 1.5 sec to expand the tree and about 0.5 sec to collapse it. (see code below)

However, it takes for 31,057 nodes between 70 and 120 sec to expand the tree and about 27 sec to collapse it (replace the interval [10,20] in makeTree() below with [10,30]).

Any idea how to speed up the expansion/collapsing is highly appreciated! Thanx in advance!

import java.awt.* ;

import java.awt.event.* ;

import javax.swing.* ;

import javax.swing.tree.* ;

import java.util.* ;

class Test

{

staticboolean collapsed =true ;

static Random random =new Random(0) ;

publicstaticvoid main(String[] args)

{

// create a random tree (with a constant seed)

DefaultMutableTreeNode root = makeTree(0,10,20) ;

// collect all tree nodes for later expansion/collapsing

final Collection<DefaultMutableTreeNode> nodes =new LinkedList<DefaultMutableTreeNode>() ;

collect(nodes, root) ;

System.out.println("Nodes created: " + nodes.size()) ;

// GUI: display the tree plus a button to expand/collapse

final JTree tree =new JTree(root,false) ;

tree.setShowsRootHandles(true) ;

JButton button =new JButton("Toggle") ;

button.addActionListener(new ActionListener(){

publicvoid actionPerformed(ActionEvent event){ toggle(tree,nodes) ;}}) ;

JFrame frame =new JFrame() ;

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.add(new JScrollPane(tree), BorderLayout.CENTER) ;

frame.add(button, BorderLayout.SOUTH) ;

frame.pack() ;

frame.setVisible(true) ;

}

static DefaultMutableTreeNode makeTree(int index,int min,int max)

{

DefaultMutableTreeNode node =new DefaultMutableTreeNode(index,true) ;

int n = min + random.nextInt(max - min) ;

for (int i=0 ; i<n ; ++i){

node.add(makeTree(i, 2*min/3, 2*max/3)) ;

}

return node ;

}

staticvoid collect(Collection><DefaultMutableTreeNode> nodes, DefaultMutableTreeNode node)

{

for (Enumeration e=node.children() ; e.hasMoreElements() ; ){

collect(nodes, (DefaultMutableTreeNode)e.nextElement()) ;

}

nodes.add(node) ;

}

// expand/collapse the tree and display the real time in nano seconds

staticvoid toggle(JTree tree, Collection<DefaultMutableTreeNode> nodes)

{

long ns = System.nanoTime() ;

for (DefaultMutableTreeNode node : nodes){

if (node.isLeaf())

continue ;

TreePath path =new TreePath(node.getPath()) ;

if (collapsed)

tree.expandPath(path) ;

else

tree.collapsePath(path) ;

}

collapsed = !collapsed ;

System.out.println("Toggled! " + (System.nanoTime() - ns)) ;

}

}

[5464 byte] By [Crauda] at [2007-11-27 4:06:28]
# 1

In most cases, having a tree with 31,057 nodes isn't the best idea anyways. I mean, as a user, having 31,057 nodes in a tree sounds daunting at best. How will I find the data I am looking for in an organized way?

Here are my suggestions.

Can you filter the data prior to building the tree so only the important information is shown to the user? This is usually the route you want to take when dealing with such large amount of information.

But... if you MUST have so many nodes on a tree there are 2 options.

1. Only load the 1st level... or possibly 1st and 2nd level... nodes then load only the subtrees when the user expands them. If this option is available, it would look the cleanest and give you the best response time. This of course assumes with 31,057 nodes, you have amazing organization, therefore you must have many tree levels to organize. You can load a lot of nodes pretty quickly even at the time of expansion... so try that out if you can.

2. If you didn't organize that amount of data cleanly, then you might want to consider a dynamic loading tree. I wrote one up awhile back (see code below).. so see if it fits your needs.

Anyways, a GUI redesign sounds better than either of the implementations but I have no idea what it is you are working on! So... good luck!

-Js

import java.awt.Dimension;

import java.awt.Rectangle;

import java.awt.event.AdjustmentEvent;

import java.awt.event.AdjustmentListener;

import javax.swing.JFrame;

import javax.swing.JScrollPane;

import javax.swing.JTree;

import javax.swing.event.TreeExpansionEvent;

import javax.swing.event.TreeWillExpandListener;

import javax.swing.tree.DefaultMutableTreeNode;

import javax.swing.tree.DefaultTreeModel;

import javax.swing.tree.ExpandVetoException;

public class DynamicTreeLoading extends JFrame

{

//STATICS

private static final int NUM_OF_NODES = 20000;

private static final int SCROLLPANE_WIDTH = 200;

private static final int SCROLLPANE_HEIGHT = 500;

//DATA

private int currentNodeCount = 0;

private DefaultMutableTreeNode rootNode;

//GUI

private JScrollPane scrollPane;

private JTree tree;

public DynamicTreeLoading()

{

this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

this.setContentPane(getScrollPane());

this.pack();

initTree();

this.setVisible(true);

}

private void initTree()

{

//Find out how many nodes we should start with (+ 1 so we know we are off the page)

Rectangle singleNodeRect = getTree().getRowBounds(0);

int viewPortHeight = getScrollPane().getViewport().getHeight();

int numOfStartNodes = (viewPortHeight / singleNodeRect.height) + 1;

//Add the nodes

DefaultTreeModel tm = (DefaultTreeModel)getTree().getModel();

for(int i=0; i<numOfStartNodes; i++)

{

tm.insertNodeInto(new DefaultMutableTreeNode("Node " + i), getRootNode(), getRootNode().getChildCount());

}

//Make sure our count is correct so we know how many we currently have added

currentNodeCount = numOfStartNodes;

//Make sure the tree starts expanded

getTree().expandRow(0);

}

private JScrollPane getScrollPane()

{

if(scrollPane == null)

{

scrollPane = new JScrollPane(getTree());

scrollPane.setPreferredSize(new Dimension(SCROLLPANE_WIDTH, SCROLLPANE_HEIGHT));

scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener()

{

public void adjustmentValueChanged(AdjustmentEvent e)

{

Rectangle singleNodeRect = getTree().getRowBounds(0);

Rectangle visibleRect = getTree().getVisibleRect();

//If we are SUPPOSED TO SHOW more nodes than we currently have... add them to the tree

int supposedNodeCount = (int)((visibleRect.getY()+visibleRect.getHeight()) / singleNodeRect.getHeight()) + 1;

if(supposedNodeCount > currentNodeCount)

{

DefaultTreeModel tm = (DefaultTreeModel)getTree().getModel();

for(int i=currentNodeCount+1; i<=supposedNodeCount; i++)

{

tm.insertNodeInto(new DefaultMutableTreeNode("Node " + i), getRootNode(), getRootNode().getChildCount());

}

currentNodeCount = supposedNodeCount;

}

}

});

}

return scrollPane;

}

private JTree getTree()

{

if(tree == null)

{

tree = new JTree(getRootNode());

tree.addTreeWillExpandListener(new TreeWillExpandListener()

{

public void treeWillExpand(TreeExpansionEvent evt) throws ExpandVetoException {}

public void treeWillCollapse(TreeExpansionEvent evt) throws ExpandVetoException

{

// Don't allow Root to collapse

if(evt.getPath().getParentPath() == null)

{

throw new ExpandVetoException(evt);

}

}

});

//Set your JTree to be as big as its GOING to be so the scrollbar looks correct

//There is (NUM_OF_NODES + 1) because we can't forget about the root node

Rectangle singleNodeRect = tree.getRowBounds(0);

tree.setPreferredSize(new Dimension(tree.getPreferredSize().width, singleNodeRect.height*(NUM_OF_NODES+1)));

}

return tree;

}

private DefaultMutableTreeNode getRootNode()

{

if(rootNode == null)

{

rootNode = new DefaultMutableTreeNode("root");

}

return rootNode;

}

public static void main(String args[])

{

new DynamicTreeLoading();

}

}

JSnakea at 2007-7-12 9:11:36 > top of Java-index,Desktop,Core GUI APIs...
# 2

Thanks for your detailed answer! :-)

It forced me to think again about my model:

The tree represents a directory structure. And there are file systems with a few thousands, maybe ten thousands, directories. However, I don't think there is problem for a user to manage this. The tree structure and the alphabetic order are quite enough to guide you thru the tree.

I made the experience that expanding/collapsing of single directory nodes is really annoying, so I introduced the recursive one. And I was really surprised how slow it is (it blocked the complete application). Traversal thru a JTree is about 100 times faster than extending each node. And since I can't see the technical reason, I thought I didn't use the API the right way?!

Anyways, since I want to keep the feature, I'll simply introduce a progress bar, so the user can manage their next break. :-)

Maybe, as an option, I'll take your suggestion to expand only 2 or 3 depth levels of the tree.

thank you!

Crauda at 2007-7-12 9:11:36 > top of Java-index,Desktop,Core GUI APIs...
# 3

You realize this is why they use an updating JList in most directory structure GUI's. I mean take a look at JFileChooser or most file choosers for that matter. Having bread-crumbs to remember where you've been.. or a drop down to show you the path history... is the way to go.

IT keeps it nice and simple for the user... and also keeps you from having to ever figure out all 31,057 nodes at once.

-Js

JSnakea at 2007-7-12 9:11:36 > top of Java-index,Desktop,Core GUI APIs...