Java, Eclipse and Web programming Tutorials

Eclipse Zest - Tutorial

Lars Vogel

Version 0.8

25.11.2009

Revision History
Revision 0.107.06.2009Lars Vogel
Created Article
Revision 0.216.06.2009Lars Vogel
First example
Revision 0.321.06.2009Lars Vogel
Added PDE Dependency Visualization
Revision 0.404.08.2009Lars Vogel
Minor corrections
Revision 0.524.09.2009Lars Vogel
Added Zest JFace
Revision 0.628.09.2009Lars Vogel
Added IGraphEntityContentProvider
Revision 0.729.09.2009Lars Vogel
Added Filter
Revision 0.825.11.2009Lars Vogel
Fixed typo

Eclipse Zest

Eclipse Zest is a visualization toolkit for graphs. This article explains how to create directly a Zest graph and how to use the JFace abstraction.

In this article Eclipse 3.5 (Eclipse Galileo) is used.


Table of Contents

1. Eclipse Zest
1.1. Overview
1.2. Components
1.3. Layout Manager
1.4. Filter
2. Installation
3. Your first Zest Project
3.1. Create Project
3.2. Add dependencies
3.3. View
3.4. Select layout manager via a command
4. Zest and JFace
5. Zest and JFace Example
5.1. Create Project
5.2. Model
5.3. Providers
5.4. View
5.5. Filter
6. Tips and Tricks
6.1. Disable that nodes can be moved manually
7. PDE Dependency Visualization
8. Thank you
9. Questions and Discussion
10. Links and Literature
10.1. Source Code
10.2. Eclipse Zest Resources
10.3. Other Resources

1. Eclipse Zest

1.1. Overview

Eclipse Zest is a visualization toolkit for graphs. It is based on SWT / Draw2D. Zest supports the viewer concept from JFace and therefore allows to separate the model from the graphical representation of the model.

This article assumes that you are already familiar with Eclipse RCP or Eclipes Plugin development. See Eclipse RCP or Eclipse Plugin Tutorial for an introduction to these topics.

1.2. Components

Eclipse Zest has the following components:

  • GraphNode - Node in the graph with the properties

  • GraphConnections - Arrow / Edge of the graph which connections to two nodes

  • GraphContainer - Use for a graph within a graph

  • Graph - holds the other elements (nodes, connections, container)

1.3. Layout Manager

Eclipse Zest provides graph layout managers. A graph layout manager determines how the nodes (and the arrows) of a graph are arranged on the screen.

The following layout managers are provided:

Table 1. Layout Manager

Layout Manager Description
TreeLayoutAlgorithmGraph is displayed in the form of a vertical tree
HorizontalTreeLayoutAlgorithmSimilar to TreeLayoutAlgorithm but layout is horizontal
RadialLayoutAlgorithmRoot is in the center, the others nodes are placed around this node
GridLayoutAlgorithm 
SpringLayoutAlgorithmLayout the graph so that all connections should have approx. the same length and that the edges overlap minimal
HorizontalShiftMoves overlapping nodes to the right
CompositeLayoutAlgorithmCombines other layout algorithms, for example HorizontalShift can be the second layout algorithm to move nodes which were still overlapping if another algorithm is used

1.4. Filter

You can also define filters (org.eclipse.zest.layouts.Filter) on the layout managers via the method setFilter(filter). This defines which nodes and connections should be displayed. The filter receives an LayoutItem, the actual graph element can be received with the method getGraphData().

2. Installation

Use the update manager of Eclipse to install the "Graphical Editing Framework Zest Visualization Toolkit". See using the Eclipse update manager for details.

Tip

You may have to unflag "Group items by category" to see Zest in the update manager. See Bug 280750

3. Your first Zest Project

3.1. Create Project

Create a new Eclipse RCP application "de.vogella.zest.first". Use the Eclipse RCP with a view as a template. See Eclipse RCP Tutorial for details.

3.2. Add dependencies

Add "org.eclipse.zest.core" and "org.eclipse.zest.layouts" as dependencies to your MANIFEST.MF.

3.3. View

Change the view to the following.

				
package de.vogella.zest.first;

import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.zest.core.widgets.Graph;
import org.eclipse.zest.core.widgets.GraphConnection;
import org.eclipse.zest.core.widgets.GraphNode;
import org.eclipse.zest.core.widgets.ZestStyles;
import org.eclipse.zest.layouts.LayoutStyles;
import org.eclipse.zest.layouts.algorithms.SpringLayoutAlgorithm;
import org.eclipse.zest.layouts.algorithms.TreeLayoutAlgorithm;

public class View extends ViewPart {
	public static final String ID = "de.vogella.zest.first.view";
	private Graph graph;
	private int layout = 1;

	public void createPartControl(Composite parent) {
		// Graph will hold all other objects
		graph = new Graph(parent, SWT.NONE);
		// Now a few nodes
		GraphNode node1 = new GraphNode(graph, SWT.NONE, "Jim");
		GraphNode node2 = new GraphNode(graph, SWT.NONE, "Jack");
		GraphNode node3 = new GraphNode(graph, SWT.NONE, "Joe");
		GraphNode node4 = new GraphNode(graph, SWT.NONE, "Bill");
		// Lets have a directed connection
		new GraphConnection(graph, ZestStyles.CONNECTIONS_DIRECTED, node1,
				node2);
		// Lets have a dotted graph connection
		new GraphConnection(graph, ZestStyles.CONNECTIONS_DOT, node2, node3);
		// Standard connection
		new GraphConnection(graph, SWT.NONE, node3, node1);
		// Change line color and line width
		GraphConnection graphConnection = new GraphConnection(graph, SWT.NONE,
				node1, node4);
		graphConnection.changeLineColor(parent.getDisplay().getSystemColor(
				SWT.COLOR_GREEN));
		// Also set a text
		graphConnection.setText("This is a text");
		graphConnection.setHighlightColor(parent.getDisplay().getSystemColor(
				SWT.COLOR_RED));
		graphConnection.setLineWidth(3);
		graphConnection.addListener(SWT.SELECTED, new Listener() {

			@Override
			public void handleEvent(Event event) {
				System.out.println("Selected");
			}

		});
		graph.setLayoutAlgorithm(new SpringLayoutAlgorithm(
				LayoutStyles.NO_LAYOUT_NODE_RESIZING), true);

	}

	public void setLayoutManager() {
		switch (layout) {
		case 1:
			graph.setLayoutAlgorithm(new TreeLayoutAlgorithm(
					LayoutStyles.NO_LAYOUT_NODE_RESIZING), true);
			layout++;
			break;
		case 2:
			graph.setLayoutAlgorithm(new SpringLayoutAlgorithm(
					LayoutStyles.NO_LAYOUT_NODE_RESIZING), true);
			layout = 1;
			break;

		}

	}

	/**
	 * Passing the focus request to the viewer's control.
	 */
	public void setFocus() {
	}
}
			

This coding creates a simple graph and connects its elements.

3.4. Select layout manager via a command

Create a command with the following default handler "de.vogella.zest.first.handler.ChangeLayout" which will change the layout for the graph. See Eclipse Commands on details how to create a command.

				
package de.vogella.zest.first.handler;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.handlers.HandlerUtil;

import de.vogella.zest.first.View;

public class ChangeLayout extends AbstractHandler {

	@Override
	public Object execute(ExecutionEvent event) throws ExecutionException {
		IViewPart findView = HandlerUtil.getActiveWorkbenchWindow(event)
				.getActivePage().findView("de.vogella.zest.first.view");
		View view = (View) findView;
		view.setLayoutManager();
		return null;
	}

}

			

Assign the command to the menu.

3.4.1. Run your application

Run your application, if you select your command the layout of your view should change.

4. Zest and JFace

JFace provides viewers to encapsulate the data from the presentation. For an introduction to JFace viewer please see Eclipse JFace TableViewer .

A JFace viewer requires a content provider and a label provider.

Zest provides as a viewer the class "GraphViewer".

Content provider in Zest are either based on the connections or on the nodes. Standard Content providers are:

Table 2. Zest JFace Content Provider

Content ProviderDescription
IGraphContentProviderBased on the connections. The connections contain the information which nodes they refer to. Cannot display nodes without connections.
IGraphEntityContentProviderBased on the Node which contain the information about which relationship they have. These relationship are available in the label provider as EntityConnectionData objects.
IGraphEntityRelationshipContentProvider Node based, the content provider defines getRelationShips(sourceNode, destinationNode) which determines the connections. The advantages compared with IGraphEntityContentProvider is that you decide which objects you return.

As label provider Zest can use the standard JFace interface ILabelProvider (implemented for example by the class LabelProvider) or the Zest specific IEntityStyleProvider.

5. Zest and JFace Example

5.1. Create Project

Create a new Eclipse RCP application "de.vogella.zest.jface". Use the Eclipse RCP with a view as a template. See Eclipse RCP Tutorial for details.

Add "org.eclipse.zest.core" and "org.eclipse.zest.layouts" as dependencies to your MANIFEST.MF.

Change the Perspective.java to the following (we don't want a standalone view).

				
package de.vogella.zest.jface;

import org.eclipse.ui.IPageLayout;
import org.eclipse.ui.IPerspectiveFactory;

public class Perspective implements IPerspectiveFactory {

	public void createInitialLayout(IPageLayout layout) {
		String editorArea = layout.getEditorArea();
		layout.setEditorAreaVisible(false);
		layout.setFixed(true);
		
		layout.addView(View.ID, IPageLayout.LEFT, 1.0f, editorArea);
	}

}

			

5.2. Model

Create the following model.

				
package de.vogella.zest.jface.model;

public class MyNode {
	private final String id;
	private final  String name;
	
	public MyNode(String id, String name) {
		this.id = id;
		this.name = name;
	}
	public String getId() {
		return id;
	}
	public String getName() {
		return name;
	}

	
}

			

				
package de.vogella.zest.jface.model;

public class MyConnection {
	final String id; 
	final String label; 
	final MyNode source;
	final MyNode destination;
	
	public MyConnection(String id, String label, MyNode source, MyNode destination) {
		this.id = id;
		this.label = label;
		this.source = source;
		this.destination = destination;
	}

	public String getLabel() {
		return label;
	}
	
	public MyNode getSource() {
		return source;
	}
	public MyNode getDestination() {
		return destination;
	}
	
}

			

Tip

Please note that the model can be anything as long as you can logically convert it into a connected Graph.

Also build this class which provides an instance of the data model.

				
package de.vogella.zest.jface.model;

import java.util.ArrayList;
import java.util.List;

public class NodeModelContentProvider {
	private List<MyConnection> connections;
	private List<MyNode> nodes;

	public NodeModelContentProvider() {
		// Image here a fancy DB access
		// Now create a few nodes
		nodes = new ArrayList<MyNode>();
		MyNode node = new MyNode("1", "Hamburg");
		nodes.add(node);
		node = new MyNode("2", "Frankfurt");
		nodes.add(node);
		node = new MyNode("3", "Berlin");
		nodes.add(node);
		node = new MyNode("4", "Munich");
		nodes.add(node);
		node = new MyNode("5", "Eppelheim");
		nodes.add(node);
		node = new MyNode("6", "Ahrensboek"); 
		nodes.add(node);

		connections = new ArrayList<MyConnection>();
		MyConnection connect = new MyConnection("1", "1", nodes.get(0), nodes.get(1));
		connections.add(connect);
		connect = new MyConnection("2", "2", nodes.get(0), nodes.get(4));
		connections.add(connect);
		connect = new MyConnection("3", "3", nodes.get(2), nodes.get(1));
		connections.add(connect);
		connect = new MyConnection("4", "3", nodes.get(1), nodes.get(3));
		connections.add(connect);
		
		// Some information stored differently
		
		for (MyConnection connection : connections) {
			connection.getSource().getConnectedTo().add(connection.getDestination());
		}
	}
	
	
	public List<MyNode> getNodes() {
		return nodes;
	}
}

			

5.3. Providers

Create the following content and label providers.

				
package de.vogella.zest.jface.zestviewer;

import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.zest.core.viewers.IGraphEntityContentProvider;

import de.vogella.zest.jface.model.MyNode;

public class ZestNodeContentProvider extends ArrayContentProvider  implements IGraphEntityContentProvider {

	@Override
	public Object[] getConnectedTo(Object entity) {
		if (entity instanceof MyNode) {
			MyNode node = (MyNode) entity;
			return node.getConnectedTo().toArray();
		}
		throw new RuntimeException("Type not supported");
	}
}

			

				
package de.vogella.zest.jface.zestviewer;

import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.zest.core.viewers.EntityConnectionData;

import de.vogella.zest.jface.model.MyConnection;
import de.vogella.zest.jface.model.MyNode;

public class ZestLabelProvider extends LabelProvider {
	@Override
	public String getText(Object element) {
		if (element instanceof MyNode){
			MyNode myNode = (MyNode) element;
			return myNode.getName();
		}
		if (element instanceof MyConnection){
			MyConnection myConnection = (MyConnection) element;
			return myConnection.getLabel();
		}
		
		if (element instanceof EntityConnectionData){
			return "";
		}
		throw new RuntimeException("Wrong type: " + element.getClass().toString() );
	}
}

			

5.4. View

Change the view to the following.

				
package de.vogella.zest.jface;

import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.zest.core.viewers.AbstractZoomableViewer;
import org.eclipse.zest.core.viewers.GraphViewer;
import org.eclipse.zest.core.viewers.IZoomableWorkbenchPart;
import org.eclipse.zest.core.viewers.ZoomContributionViewItem;
import org.eclipse.zest.layouts.LayoutAlgorithm;
import org.eclipse.zest.layouts.LayoutStyles;
import org.eclipse.zest.layouts.algorithms.TreeLayoutAlgorithm;

import de.vogella.zest.jface.model.NodeModelContentProvider;
import de.vogella.zest.jface.zestviewer.ZestLabelProvider;
import de.vogella.zest.jface.zestviewer.ZestNodeContentProvider;

public class View extends ViewPart implements IZoomableWorkbenchPart {
	public static final String ID = "de.vogella.zest.jface.view";
	private GraphViewer viewer;

	public void createPartControl(Composite parent) {
		viewer = new GraphViewer(parent, SWT.BORDER);
		viewer.setContentProvider(new ZestNodeContentProvider());
		viewer.setLabelProvider(new ZestLabelProvider());
		NodeModelContentProvider model = new NodeModelContentProvider();
		viewer.setInput(model.getNodes());
		LayoutAlgorithm layout = setLayout();
		viewer.setLayoutAlgorithm(layout, true);
		viewer.applyLayout();
		fillToolBar();
	}

	private LayoutAlgorithm setLayout(){
		LayoutAlgorithm layout;
//		layout = new SpringLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING);	
		layout = new TreeLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING);
//		layout = new GridLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING);
//		layout = new HorizontalTreeLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING);
//		layout = new RadialLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING);
		// We will filter on the nodes
		layout.setFilter(new NodeFilter());
		return layout; 
		
	}

	/**
	 * Passing the focus request to the viewer's control.
	 */
	public void setFocus() {
	}
	
	private void fillToolBar() {
		ZoomContributionViewItem toolbarZoomContributionViewItem = new ZoomContributionViewItem(this);
		IActionBars bars = getViewSite().getActionBars();
		bars.getMenuManager().add(toolbarZoomContributionViewItem);

	}

	@Override
	public AbstractZoomableViewer getZoomableViewer() {
		return viewer;
	}
}

			

The result should look like the following.

5.5. Filter

You can define a filter on the viewer via setFilters();

For example define the following filter.

				
package de.vogella.zest.jface.zestviewer;

import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;

import de.vogella.zest.jface.model.MyNode;

public class NodeFilter extends ViewerFilter {

	@Override
	public boolean select(Viewer viewer, Object parentElement, Object element) {
		
		if (element instanceof MyNode) {
			MyNode node = (MyNode) element;
			return node.getName().toLowerCase().contains("a");
			
		}
		return true; 
	}

}

			

Apply the filter to the view to filter all elements.

				
package de.vogella.zest.jface;

import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.zest.core.viewers.AbstractZoomableViewer;
import org.eclipse.zest.core.viewers.GraphViewer;
import org.eclipse.zest.core.viewers.IZoomableWorkbenchPart;
import org.eclipse.zest.core.viewers.ZoomContributionViewItem;
import org.eclipse.zest.layouts.LayoutAlgorithm;
import org.eclipse.zest.layouts.LayoutStyles;
import org.eclipse.zest.layouts.algorithms.TreeLayoutAlgorithm;

import de.vogella.zest.jface.model.NodeModelContentProvider;
import de.vogella.zest.jface.zestviewer.NodeFilter;
import de.vogella.zest.jface.zestviewer.ZestLabelProvider;
import de.vogella.zest.jface.zestviewer.ZestNodeContentProvider;

public class View extends ViewPart implements IZoomableWorkbenchPart {
	public static final String ID = "de.vogella.zest.jface.view";
	private GraphViewer viewer;

	public void createPartControl(Composite parent) {
		viewer = new GraphViewer(parent, SWT.BORDER);
		viewer.setContentProvider(new ZestNodeContentProvider());
		viewer.setLabelProvider(new ZestLabelProvider());
		NodeModelContentProvider model = new NodeModelContentProvider();
		viewer.setInput(model.getNodes());
		LayoutAlgorithm layout = setLayout();
		viewer.setLayoutAlgorithm(layout, true);
		viewer.applyLayout();
		NodeFilter filter = new NodeFilter();
		ViewerFilter[] filters = new ViewerFilter[1];
		filters[0]= filter; 
		viewer.setFilters(filters);
		fillToolBar();
	}

	private LayoutAlgorithm setLayout(){
		LayoutAlgorithm layout;
//		layout = new SpringLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING);	
		layout = new TreeLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING);
//		layout = new GridLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING);
//		layout = new HorizontalTreeLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING);
//		layout = new RadialLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING);
		// We will filter on the nodes
//		layout.setFilter(new NodeFilter());
		return layout; 
		
	}

	/**
	 * Passing the focus request to the viewer's control.
	 */
	public void setFocus() {
	}
	
	private void fillToolBar() {
		ZoomContributionViewItem toolbarZoomContributionViewItem = new ZoomContributionViewItem(this);
		IActionBars bars = getViewSite().getActionBars();
		bars.getMenuManager().add(toolbarZoomContributionViewItem);

	}

	@Override
	public AbstractZoomableViewer getZoomableViewer() {
		return viewer;
	}
}

			

Tip

You can also define a filter on the layout so that certain elements are ignore then calculating the layout. Method isObjectFiltered(LayoutItem item). Use item.getGraphData() to get the underlying object (GraphNode or GraphConnection).

6. Tips and Tricks

6.1. Disable that nodes can be moved manually

Per default the user can move the nodes in Zest. To disable this you have to extend the Graph.

				
package de.vogella.zest.movenodes.graph;

import org.eclipse.draw2d.SWTEventDispatcher;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.zest.core.widgets.Graph;

public class NonMovableGraph extends Graph {

	public NonMovableGraph(Composite parent, int style) {
		super(parent, style);
		this.getLightweightSystem().setEventDispatcher(
				new SWTEventDispatcher() {
					public void dispatchMouseMoved(
							org.eclipse.swt.events.MouseEvent me) {
						// Doing nothing
					}
				});
	}

}

			

The usage is demonstrated in project "de.vogella.zest.movenodes".

7. PDE Dependency Visualization

A good and extensive example for the usage of Zest for visualization of plug-in dependencies is the PDE Incubator Dependency Visualization. See here to see how to get the source code.

In case you are not familiar with Eclipse plugin development you can use this tutorial to learn about Eclipse Plugin Development

8. Thank you

Thank you for practicing with this tutorial.

Please note that I maintain this website in my private time. If you like the information I'm providing please help me by donating.

9. Questions and Discussion

For questions and discussion around this article please use the www.vogella.de Google Group. Also if you note an error in this article please post the error and if possible the correction to the Group.

I believe the following is a very good guideline for asking questions in general and also for the Google group How To Ask Questions The Smart Way.

10. Links and Literature

10.1. Source Code

http://www.vogella.de/code/codeeclipse.html Source Code of Examples