| Java, Eclipse and Web programming Tutorials |
Version 0.8
Copyright © 2009 Lars Vogel
25.11.2009
| Revision History | ||
|---|---|---|
| Revision 0.1 | 07.06.2009 | Lars Vogel |
| Created Article | ||
| Revision 0.2 | 16.06.2009 | Lars Vogel |
| First example | ||
| Revision 0.3 | 21.06.2009 | Lars Vogel |
| Added PDE Dependency Visualization | ||
| Revision 0.4 | 04.08.2009 | Lars Vogel |
| Minor corrections | ||
| Revision 0.5 | 24.09.2009 | Lars Vogel |
| Added Zest JFace | ||
| Revision 0.6 | 28.09.2009 | Lars Vogel |
| Added IGraphEntityContentProvider | ||
| Revision 0.7 | 29.09.2009 | Lars Vogel |
| Added Filter | ||
| Revision 0.8 | 25.11.2009 | Lars Vogel |
| Fixed typo | ||
Table of Contents
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.
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)
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 |
|---|---|
| TreeLayoutAlgorithm | Graph is displayed in the form of a vertical tree |
| HorizontalTreeLayoutAlgorithm | Similar to TreeLayoutAlgorithm but layout is horizontal |
| RadialLayoutAlgorithm | Root is in the center, the others nodes are placed around this node |
| GridLayoutAlgorithm | |
| SpringLayoutAlgorithm | Layout the graph so that all connections should have approx. the same length and that the edges overlap minimal |
| HorizontalShift | Moves overlapping nodes to the right |
| CompositeLayoutAlgorithm | Combines other layout algorithms, for example HorizontalShift can be the second layout algorithm to move nodes which were still overlapping if another algorithm is used |
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().
Use the update manager of Eclipse to install the "Graphical Editing Framework Zest Visualization Toolkit". See using the Eclipse update manager for details.
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.
Add "org.eclipse.zest.core" and "org.eclipse.zest.layouts" as dependencies to your MANIFEST.MF.
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.
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.
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 Provider | Description |
|---|---|
| IGraphContentProvider | Based on the connections. The connections contain the information which nodes they refer to. Cannot display nodes without connections. |
| IGraphEntityContentProvider | Based 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.
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);
}
}
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;
}
}
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;
}
}
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() );
}
}
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.

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;
}
}
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".
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
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.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.
http://www.vogella.de/code/codeeclipse.html Source Code of Examples
http://www.eclipse.org/gef/zest/ Eclipse Zest Homepage
http://wiki.eclipse.org/index.php/GEF_Zest_Visualization Eclipse Zest Wiki
http://www.eclipse.org/gef/zest/snippets.php Eclipse Zest Snippets