
Apparently, the above vitamins are "are formulated with a "problem specific" philosophy in mind". I like that idea. I'm liking it especially well this evening. In fact, I guess you could say that I myself have a new zest for life. As I so subtly implied in a recent post my Zest experience has been generally very specific to my set of problems, but with a few specific problems of it's own. Ian Bull was kind enough to respond with an update of stuff they've been working on, including the work that Mateusz Matela and other's have been doing on Zest 2.0. I had to check that out, because I've been getting frustrated with my own attempts to set algorithm against algorithm in a desperate attempt to wrangle what I wanted out of Zest as it is. Take a look at the above post for all the sordid details. But you can ignore all that nonsense about soap -- I must have been having a bad no-hair day. Ba-dum-bum.
As I was walking home yesterday, I thought about the work that I'd put into figuring out the right way to do dynamic resizing. I'd be really embarrassed to mention how much time that I've put into that, so I won't. I thought about the whole sunk costs thing, and decided that what I would do is actually write my own tree layout algorithm. Good idea right? No, really bad idea. Good way to suck up a whole week. So instead, let's grab the latest Zest efforts and see what we can do with it.
It was actually a bit difficult to track down the info. Got to update those wiki pages guys! :) You can find everything at:
http://eclipse-soc.googlecode.com/svn/trunk/2009-treeviewsforzest/**
Once I'd grabbed the latest source, the conversion of my existing code was pretty minimal and obvious. The API is as advertised -- "same Zest you know and love, but now, with less information hiding!". I spent a bunch of time mucking around with trying to get my current frankenstein setup working with the new API. For an hour or two I wrestled with LayoutContext and other stuff, but every attempt to manually set the size of the root figure was thwarted. Somehow that sneaky Zest was able to set itself back to where it wanted to be. (Question: How many different kinds of bounds and resizing models does one platform really need?) But then I thought, what the heck, let's just see if we can get this stuff to work like I want right out of the box.
Then I looked a little closer at the actual layout code and found something interesting:
void internalApplyLayout() {
TreeNode superRoot = treeObserver.getSuperRoot();
bounds = context.getBounds();
if (direction == TOP_DOWN || direction == BOTTOM_UP) {
leafSize = bounds.width / superRoot.numOfLeaves;
layerSize = bounds.height / superRoot.height;
} else {
leafSize = bounds.height / superRoot.numOfLeaves;
layerSize = bounds.width / superRoot.height;
}
int leafCountSoFar = 0;
for (Iterator iterator = superRoot.getChildren().iterator(); iterator.hasNext();) {
TreeNode rootInfo = (TreeNode) iterator.next();
computePositionRecursively(rootInfo, leafCountSoFar);
leafCountSoFar = leafCountSoFar + rootInfo.numOfLeaves;
}
}
Hmm...I wonder what would happen if I changed those leaf and layer sizes so that instead of using a size relative to the pre-determined bounds, I just set them to the size of the space I want my figures to take up? It worked! Just like that, and much slicker updates as well.
And one really great bonus. I had complained on the GEF forum that the tree layouts weren't ideal -- they could be a lot more compact.
Here's a visualization using the Metascape visual editor of an AMF agent behavior using the old Zest and one using 2.0. Note how the nodes near the tree roots were splayed out far more than they need to be. The new version is perfect. The different streams of actions are interleaved beautifully. To restate the obvious, the best thing about using an API is that when someone improves something you get all the benefit with no work. Nice job guys!
![]() | ![]() |
So this was one of those moments of triumph mixed with the regret of all of the time wasted on pointless pursuits. I ditched a couple of hundred lines of laboriously hand-"crafted" code, which is always a tiny bit painful, but ultimately getting rid of chunks of code while preserving the same or better functionality is the best smell of all. Next step was to clean things up and try to generalize it a bit.
Here's the code:
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.zest.layouts.algorithms.TreeLayoutAlgorithm;
import org.eclipse.zest.layouts.interfaces.EntityLayout;
/**
*
* @author mparker
*
*/
public class FreeflowTreeLayoutAlgorithm extends TreeLayoutAlgorithm {
private Dimension preferredNodeSize = null;
/**
* Create a Tree Layout with option for fixed size nodes. If preferredNodeSize is not null, the layout will size the
* container to the ideal space to just contain all nodes of fixed size without any overlap. Otherwise, the
* algorithm will size for the container's available space as defined in the super class.
*
* @param direction
* @param preferredNodeSize the size to make each node, may be null, in which case the algorithm will use base class
* behavior
*/
public FreeflowTreeLayoutAlgorithm(int direction, Dimension preferredNodeSize) {
super(direction);
this.preferredNodeSize = preferredNodeSize;
}
protected void updateLeafAndLayerSizes() {
if (preferredNodeSize != null) {
setLeafSize(preferredNodeSize.preciseWidth());
setLayerSize(preferredNodeSize.preciseHeight());
} else {
super.updateLeafAndLayerSizes();
}
}
/**
* A noop. We don't want to have the entities positions change with respect to their idea locations.
*
* @param entities
* @see org.eclipse.zest.layouts.algorithms.TreeLayoutAlgorithm#scaleEntities(org.eclipse.zest.layouts.interfaces.EntityLayout[])
*/
protected void scaleEntities(EntityLayout[] entities) {
}
/**
* @param preferredNodeSize the preferredNodeSize to set
*/
public void setPreferredNodeSize(Dimension preferredNodeSize) {
this.preferredNodeSize = preferredNodeSize;
}
}
Pathetically simple, just how we like it. This did require some minor surgery on the underlying TreeLayoutAlgorithm class. All we're doing here is exposing a few more bits of the API. But unfortunately I can't just grab this one class and make my own changes because it is still coupled with some Zest stuff that's nailed to the floor. I've appended the patch (see below) and I'll pass it on to the Zest team. It looks like they've accomplished a lot over the summer and I'm anxious to see this stuff graduate to the Eclipse project proper.
Index: src/org/eclipse/zest/layouts/algorithms/TreeLayoutAlgorithm.java
===================================================================
--- src/org/eclipse/zest/layouts/algorithms/TreeLayoutAlgorithm.java (revision 170)
+++ src/org/eclipse/zest/layouts/algorithms/TreeLayoutAlgorithm.java (working copy)
@@ -57,6 +57,7 @@
private boolean resize = false;
+
private LayoutContext context;
private DisplayIndependentRectangle bounds;
@@ -72,6 +73,7 @@
setDirection(direction);
}
+
public int getDirection() {
return direction;
}
@@ -121,6 +123,10 @@
AlgorithmHelper.maximizeSizes(entities);
}
+ scaleEntities(entities);
+ }
+
+ protected void scaleEntities(EntityLayout[] entities) {
DisplayIndependentRectangle bounds2 = new DisplayIndependentRectangle(bounds);
AlgorithmHelper.fitWithinBounds(entities, bounds2, resize);
}
@@ -128,6 +134,17 @@
void internalApplyLayout() {
TreeNode superRoot = treeObserver.getSuperRoot();
bounds = context.getBounds();
+ updateLeafAndLayerSizes();
+ int leafCountSoFar = 0;
+ for (Iterator iterator = superRoot.getChildren().iterator(); iterator.hasNext();) {
+ TreeNode rootInfo = (TreeNode) iterator.next();
+ computePositionRecursively(rootInfo, leafCountSoFar);
+ leafCountSoFar = leafCountSoFar + rootInfo.numOfLeaves;
+ }
+ }
+
+ protected void updateLeafAndLayerSizes() {
+ TreeNode superRoot = treeObserver.getSuperRoot();
if (direction == TOP_DOWN || direction == BOTTOM_UP) {
leafSize = bounds.width / superRoot.numOfLeaves;
layerSize = bounds.height / superRoot.height;
@@ -135,12 +152,6 @@
leafSize = bounds.height / superRoot.numOfLeaves;
layerSize = bounds.width / superRoot.height;
}
- int leafCountSoFar = 0;
- for (Iterator iterator = superRoot.getChildren().iterator(); iterator.hasNext();) {
- TreeNode rootInfo = (TreeNode) iterator.next();
- computePositionRecursively(rootInfo, leafCountSoFar);
- leafCountSoFar = leafCountSoFar + rootInfo.numOfLeaves;
- }
}
/**
@@ -171,4 +182,12 @@
relativePosition += childInfo.numOfLeaves;
}
}
+
+ protected void setLeafSize(double leafSize) {
+ this.leafSize = leafSize;
+ }
+
+ protected void setLayerSize(double layerSize) {
+ this.layerSize = layerSize;
+ }
}





Thank you for posting, I was not aware of progress made on the tree layouts. I've had similar layout issues in my application.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteLooks very cool. I really would like to see the Zest 2.0 merged into the Eclipse cvs tree.
ReplyDeleteI did a merge of this code into our modified Zest 1.1.x plugins today just to evaluate. Layout seems to be handled better overall with the 2.0 changes. Some of our graphs are pretty "tangled up" with lots of cross-connections, but even then there's less node overlap and off-screen placement, even with long label text, placement seems to make better overall use of space, and layout also takes zoom level into account.
ReplyDeleteReally looking forward to seeing this move forward.