Skip to content

Month: April 2011

Another day with JGraph – Styles and Constrained Children

Today I spent some more time playing with JGraph. The application I am creating requires three basic shapes: a rectangle, an ellipse and a roundtangle (rounded rectangle). All of these shapes are supported in JGraph using the styles mechanism, descibed here in the user manual. A very useful class that must be read and re-read for creating styles is mxConstants which contains many definitions for the style customizations and their possible values.
For example, creating a style for an ellipse looks like this (using a bluish color):

Hashtable<String, Object> style = new Hashtable<String, Object>();
style.put(mxConstants.STYLE_FILLCOLOR, mxUtils.getHexColorString(Color.WHITE));
style.put(mxConstants.STYLE_STROKEWIDTH, 1.5);
style.put(mxConstants.STYLE_STROKECOLOR, mxUtils.getHexColorString(new Color(0, 0, 170)));
style.put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_ELLIPSE);
style.put(mxConstants.STYLE_PERIMETER, mxConstants.PERIMETER_ELLIPSE);

and the style for a rountangle looks like this (using a brownish color):

Hashtable<String, Object> style = new Hashtable<String, Object>();
style.put(mxConstants.STYLE_FILLCOLOR, mxUtils.getHexColorString(Color.WHITE));
style.put(mxConstants.STYLE_STROKEWIDTH, 1.5);
style.put(mxConstants.STYLE_STROKECOLOR, mxUtils.getHexColorString(new Color(0, 0, 170)));
style.put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_ELLIPSE);
style.put(mxConstants.STYLE_PERIMETER, mxConstants.PERIMETER_ELLIPSE);

After the style is created it must be added to the graph’s stylesheet. The following adds a style created above giving it the name MyStyle, using it to create a new vertex using this style:

mxGraph graph = new mxGraph();
mxStylesheet stylesheet = graph.getStylesheet();
stylesheet.putCellStyle("MyStyle", style);
graph.insertVertex(defaultParent, null, "Hello", 20, 20, 80, 30, "MyStyle");

An interesting style that I had to add to the styles was the mxConstants.STYLE_PERIMETER style. What happened is that after creating an ellipse style and adding a link starting at a vertex with this style, the link did not start at the border of the ellipse but some space outside, in the bounding rectangle of the ellipse. This style setting fixes this.
Another nice feature in JGraph is the hierarchical definition of the graph, where all entities in the graph (called cells in JGraph) can have a parent that contains it. This is a feature that I definitely need, and is easy to use. The first parameter in the insertVertex and insertEdge functions of the mxGraph defines the parent of the inserted node. In my previous post, I used a defaultParent when creating new cells. Now I use an existing cell as the parent, using the following code:

Object v1 = graph.insertVertex(defaultParent, null, "Process", 20, 20, 80, 30, "process");
Object v2 = graph.insertVertex(defaultParent, null, "Object", 240, 150, 80, 60, "object");
Object v3 = graph.insertVertex(v2, null, "State", 10, 35, 30, 20, "state");

And creating the following graph:

In the default JGraph implementation, the inner vertex can be moved outside the boundaries of the containing vertex, which is not something that is allowed in my application. Reading the mxGraph javadoc some possible functions seemed to make this happen: setConstrainChildren and setDefaultOverlap, but they seemed to do nothing. A short search in the internet produced this solution, where the mxGraphComponent is extended as follows (since I never allow for drag-and-drop outside a parent):

mxGraphComponent graphComponent = new mxGraphComponent(graph) {
	private static final long serialVersionUID = 1821677322838455152L;
	@Override public mxGraphHandler createGraphHandler() {
		return new mxGraphHandler(this) {
			@Override protected boolean shouldRemoveCellFromParent(Object parent, Object[] cells, MouseEvent e) {
				return false;
			}
		};
	}
}; 

It will be interesting to investigate what previously mentioned function do, but the solution works. Long live programming by coincidence. The full code of the developed example is shown below. See you next time.

package com.vainolo.jgraph;

import java.awt.Color;
import java.awt.event.MouseEvent;
import java.util.Hashtable;
import javax.swing.JFrame;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.handler.mxGraphHandler;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxStylesheet;

/**
 * Second JGraph self-tutorial.
 * This class creates a three-node graph, with two first class vertex and one
 * vertex that is child of a first class vertex.
 * Styles are implemented for each one of the vertices.
 * The {@link mxGraphComponent} class is extended so that the inner vertex cannot be
 * dragged outside the parent vertex.
 * 
 * @author vainolo
 *
 */
public class JGraphLearning2 extends JFrame {
	private static final long serialVersionUID = -3363276407804479930L;
	/**
	 * Create all required styles.
	 * Create a three node graph with an inner node.
	 * Extend the {@link mxGraphComponent} so that inner vertex cannot be dragged
	 * outside their parent.
	 */
	public JGraphLearning2() {
		super("JGraphXLearning2");
		mxGraph graph = new mxGraph();
		configureGraph(graph);
		Object defaultParent = graph.getDefaultParent();
		mxStylesheet stylesheet = graph.getStylesheet();
		stylesheet.putCellStyle("process", createProcessStyle());
		stylesheet.putCellStyle("object", createObjectStyle());
		stylesheet.putCellStyle("state", createStateStyle());
		stylesheet.putCellStyle("agent", createAgentLinkStyle());
		graph.getModel().beginUpdate();
		Object v1 = graph.insertVertex(defaultParent, null, "Process", 20, 20, 80, 30, "process");
		Object v2 = graph.insertVertex(defaultParent, null, "Object", 240, 150, 80, 60, "object");
		Object v3 = graph.insertVertex(v2, null, "State", 10, 35, 30, 20, "state");
		graph.insertEdge(defaultParent, null, "", v1, v2, "agent");
		graph.getModel().endUpdate();
		mxGraphComponent graphComponent = new mxGraphComponent(graph) {
			private static final long serialVersionUID = 1821677322838455152L;
			@Override public mxGraphHandler createGraphHandler() {
				return new mxGraphHandler(this) {
					@Override protected boolean shouldRemoveCellFromParent(Object parent, Object[] cells, MouseEvent e) {
						return false;
					}
					
				};
			}
		}; 
		configureGraphComponent(graphComponent);
		getContentPane().add(graphComponent);
	}
	
	/**
	 * General graph settings.
	 * @param graph the graph to configure.
	 */
	private void configureGraph(mxGraph graph) {
		graph.setEnabled(false);
		graph.setCellsResizable(true);
		graph.setConstrainChildren(true);
		graph.setExtendParents(true);
		graph.setExtendParentsOnAdd(true);
		graph.setDefaultOverlap(0);
	}
	
	/**
	 * General graph component settings.
	 * @param graphComponent
	 */
	private void configureGraphComponent(mxGraphComponent graphComponent) {
		graphComponent.getViewport().setOpaque(true);
		graphComponent.getViewport().setBackground(Color.WHITE);
		graphComponent.setConnectable(false);
	}	
	
	/**
	 * Create a new style for process vertices.
	 * @return the created style.
	 */
	private Hashtable<String,Object> createProcessStyle() {
		Hashtable<String, Object> style = new Hashtable<String, Object>();
		style.put(mxConstants.STYLE_FILLCOLOR, mxUtils.getHexColorString(Color.WHITE));
		style.put(mxConstants.STYLE_STROKEWIDTH, 1.5);
		style.put(mxConstants.STYLE_STROKECOLOR, mxUtils.getHexColorString(new Color(0, 0, 170)));
		style.put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_ELLIPSE);
		style.put(mxConstants.STYLE_PERIMETER, mxConstants.PERIMETER_ELLIPSE);
		return style;
	}
	
	/**
	 * Create a new style for object vertices.
	 * @return the created style.
	 */
	private Hashtable<String, Object> createObjectStyle() {
		Hashtable<String, Object> style = new Hashtable<String, Object>();
		style.put(mxConstants.STYLE_FILLCOLOR, mxUtils.getHexColorString(Color.WHITE));
		style.put(mxConstants.STYLE_STROKECOLOR, mxUtils.getHexColorString(new Color(0, 110, 0)));
		style.put(mxConstants.STYLE_STROKEWIDTH, 1.5);
		style.put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_RECTANGLE);
		style.put(mxConstants.STYLE_PERIMETER, mxConstants.PERIMETER_RECTANGLE);
		return style;
	}
	
	/**
	 * Create a new style for state vertices.
	 * @return the created style.
	 */
	private Hashtable<String, Object> createStateStyle() {
		Hashtable<String, Object> style = new Hashtable<String, Object>();
		style.put(mxConstants.STYLE_FILLCOLOR, mxUtils.getHexColorString(Color.WHITE));
		style.put(mxConstants.STYLE_STROKECOLOR, mxUtils.getHexColorString(new Color(91, 91, 0)));
		style.put(mxConstants.STYLE_STROKEWIDTH, 1.5);		
		style.put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_RECTANGLE);
		style.put(mxConstants.STYLE_PERIMETER, mxConstants.PERIMETER_RECTANGLE);
		style.put(mxConstants.STYLE_ROUNDED, true);
		return style;
	}

	/**
	 * Create a new style for agent links.
	 * @return the created style.
	 */
	private Hashtable<String, Object> createAgentLinkStyle() {
		Hashtable<String, Object> style = new Hashtable<String, Object>();
		style.put(mxConstants.STYLE_STROKECOLOR, mxUtils.getHexColorString(Color.BLACK));		
		style.put(mxConstants.STYLE_ENDARROW, mxConstants.ARROW_OVAL);
		return style;
	}
	
	/**
	 * Execute the program.
	 * @param args ignored.
	 */
	public static void main(String args[]) {
		JGraphLearning2 frame = new JGraphLearning2();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(400, 320);
		frame.setVisible(true);
	}
}