Tag Archives: JGraph

JGraph Styles

I build a reference program to test some of the styles that JGraphX provides. This is the result of the program:

The full source code for this program follows:

package com.vainolo.jgraph;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import java.awt.BorderLayout;
import java.awt.Color;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxGraph;

public class JGraphLearning5 extends JFrame {
	private static final long serialVersionUID = -2398843485837905351L;
	private static final int X_SOURCE = 50, Y_SOURCE = 50 ,PAGE_WIDTH = 1300, 
		HORIZONTAL_STEP = 150, VERTICAL_STEP = 100, SHAPE_WIDTH = 50,
		SHAPE_HEIGHT = 60;

	public JGraphLearning5() {
		mxGraph graph = new mxGraph();
		mxGraphComponent graphComponent = new mxGraphComponent(graph);
		graphComponent.getViewport().setOpaque(true);
		graphComponent.getViewport().setBackground(Color.WHITE);
		
		int x = X_SOURCE, y = Y_SOURCE;
		for (JGraphShape shape : JGraphShape.values()) {
			graph.insertVertex(null, null, shape.mxShapeConstantValue, x, y, SHAPE_WIDTH, SHAPE_HEIGHT, mxConstants.STYLE_SHAPE+"="+shape.mxShapeConstantValue);
			x += HORIZONTAL_STEP;
			if(x > PAGE_WIDTH) { x = X_SOURCE; y += VERTICAL_STEP; }
		}
		// Some options for the shapes:
		x = X_SOURCE; y += VERTICAL_STEP;
		String styleShape = mxConstants.STYLE_SHAPE+"="+mxConstants.SHAPE_RECTANGLE+";"; 
		for(JGraphStyle style: JGraphStyle.values()) {
			graph.insertVertex(null, null, style.name(), x, y, SHAPE_WIDTH, SHAPE_HEIGHT, styleShape+style.mxStyle);
			x += HORIZONTAL_STEP;
			if(x > PAGE_WIDTH) { x = X_SOURCE; y += VERTICAL_STEP; }
		}
		
		styleShape = mxConstants.STYLE_SHAPE+"="+mxConstants.SHAPE_ELLIPSE+";"; 
		for(JGraphStyle style: JGraphStyle.values()) {
			graph.insertVertex(null, null, style.name(), x, y, SHAPE_WIDTH, SHAPE_HEIGHT, styleShape+style.mxStyle);
			x += HORIZONTAL_STEP;
			if(x > PAGE_WIDTH) { x = X_SOURCE; y += VERTICAL_STEP; }
		}

		getContentPane().setLayout(new BorderLayout(0,0));
		getContentPane().add(graphComponent, BorderLayout.CENTER);
	}

	public enum JGraphShape {
		RECTANGLE(mxConstants.SHAPE_RECTANGLE),
		ELLIPSE(mxConstants.SHAPE_ELLIPSE),
		DOUBLE_ELLIPSE(mxConstants.SHAPE_DOUBLE_ELLIPSE),
		RHOMBUS(mxConstants.SHAPE_RHOMBUS),
		LINE(mxConstants.SHAPE_LINE),
		IMAGE(mxConstants.SHAPE_IMAGE),
// doesn't work		ARROW(mxConstants.SHAPE_ARROW),
		CURVE(mxConstants.SHAPE_CURVE),
		LABEL(mxConstants.SHAPE_LABEL),
		CILINDER(mxConstants.SHAPE_CYLINDER),
		SWIMLANE(mxConstants.SHAPE_SWIMLANE),
		CONNECTOR(mxConstants.SHAPE_CONNECTOR),
		ACTOR(mxConstants.SHAPE_ACTOR),
		CLOUD(mxConstants.SHAPE_CLOUD),
		TRIANGLE(mxConstants.SHAPE_TRIANGLE),
		HEXAGON(mxConstants.SHAPE_HEXAGON);
		
		public String mxShapeConstantValue;

		JGraphShape(String mxShapeConstantValue) {
			this.mxShapeConstantValue = mxShapeConstantValue;
		}
	}
	
	public enum JGraphStyle {
		OPACITY(mxConstants.STYLE_OPACITY, 50.0),
		TEXT_OPACITY(mxConstants.STYLE_TEXT_OPACITY, 50.0),
		OVERFLOW_1(mxConstants.STYLE_OVERFLOW, "visible"),
		OVERFLOW_2(mxConstants.STYLE_OVERFLOW, "hidden"),
		OVERFLOW_3(mxConstants.STYLE_OVERFLOW, "fill"),
		ROTATION(mxConstants.STYLE_ROTATION, 45),
		FILLCOLOR(mxConstants.STYLE_FILLCOLOR, mxUtils.getHexColorString(Color.RED)),
		GRADIENTCOLOR(mxConstants.STYLE_GRADIENTCOLOR, mxUtils.getHexColorString(Color.BLUE)),
		GRADIENT_DIRECTION(mxConstants.STYLE_GRADIENT_DIRECTION, mxConstants.DIRECTION_EAST, mxConstants.STYLE_GRADIENTCOLOR, mxUtils.getHexColorString(Color.YELLOW)),
		STROKECOLOR(mxConstants.STYLE_STROKECOLOR, mxUtils.getHexColorString(Color.GREEN)),
		STROKEWIDTH(mxConstants.STYLE_STROKEWIDTH, 5),
		ALIGN(mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT),
		VERTICAL_ALIGN(mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_BOTTOM),
		LABEL_POSITION(mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_LEFT),
		VERTICAL_LABEL_POSITION(mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_BOTTOM),
		GLASS(mxConstants.STYLE_GLASS, 1),
		NOLABEL(mxConstants.STYLE_NOLABEL, 1),
		LABEL_BACKGROUNDCOLOR(mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, mxUtils.getHexColorString(Color.CYAN)),
		LABEL_BORDERCOLOR(mxConstants.STYLE_LABEL_BORDERCOLOR, mxUtils.getHexColorString(Color.PINK)),
		SHADOW(mxConstants.STYLE_SHADOW, true),
		DASHED(mxConstants.STYLE_DASHED, true),
		ROUNDED(mxConstants.STYLE_ROUNDED, true),
		HORIZONTAL(mxConstants.STYLE_HORIZONTAL, false),
		FONTCOLOR(mxConstants.STYLE_FONTCOLOR, mxUtils.getHexColorString(Color.ORANGE)),
		FONTFAMILY(mxConstants.STYLE_FONTFAMILY, "Times New Roman"),
		FONTSIZE(mxConstants.STYLE_FONTSIZE, 15),
		FONTSTYLE(mxConstants.STYLE_FONTSTYLE, mxConstants.FONT_BOLD),				
		;

		public String mxStyle;
		
		JGraphStyle(Object... values) {
			mxStyle = ""; 
			for (int i = 0; i < values.length; i++) {
				if(i%2==0) {
					mxStyle += values[i] + "=";
				} else {
					mxStyle += values[i] + ";";
				}
			}
		}
	}
	
	public static void main(String args[]) throws Exception {
		UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		SwingUtilities.invokeLater(new Runnable() {
			@Override public void run() {
				JGraphLearning5 jgl = new JGraphLearning5();
				jgl.setExtendedState(JFrame.MAXIMIZED_BOTH);
				jgl.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				jgl.setVisible(true);
			}
		});
	}
}

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);
	}
}

JGraph – Another Java Graph Framework

After some playing with JUNG (see examples 1, 2 and 3), I feel that it will be hard to use it for the application that I am writing… I need more edge versatility that the one provided by the simple API of Jung. So some more searching brought me to the JGraph framework.
JGraph seems to provide great visual flexibility and also includes graph editing properties, which are needed in my application.
As usual, the documentations of these open-source frameworks is not very user-friendly (there is a user manual, better than the one provided by most projects), but no step-by-step guide on how to use the framework. Since I am starting to learn it, I will try to produce it as I go along.
The first example (adapted from the example provided with the framework’s source code) creates a simple graph with two nodes and a vertex.

package com.vainolo.jgraph;
import javax.swing.JFrame;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.view.mxGraph;

/**
 * First examples of JGraphX - Creating a simple frame that
 * contains a graph component with two vertices and an edge connecting them.
 * @author vainolo
 */
public class JGraphXLearning1 extends JFrame {
	
	private static final long serialVersionUID = 196831535599934813L;

	public JGraphXLearning1() {
		super("JGraphXLearning1");
		mxGraph graph = new mxGraph();
		Object defaultParent = graph.getDefaultParent();
		graph.getModel().beginUpdate();
		Object v1 = graph.insertVertex(defaultParent, null, "Hello", 20, 20, 80, 30);
		Object v2 = graph.insertVertex(defaultParent, null, "World", 240, 150, 80, 30);
		graph.insertEdge(defaultParent, null, "Edge", v1, v2);
		graph.getModel().endUpdate();
		mxGraphComponent graphComponent = new mxGraphComponent(graph);
		getContentPane().add(graphComponent);
	}
	
	public static void main(String args[]) {
		JGraphXLearning1 frame = new JGraphXLearning1();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(400, 320);
		frame.setVisible(true);
	}
}

As you can see, the code is fairly simple. The JGraphXLearning1 class is a JFrame on which a mxGraphComponent is added. This component is the view of an mxGraph, which is the model of the graph.
The nice thing with this example is that it creates an editable graph, where the nodes can be moved, re-sized, the edge can be detached, attached again, you can add new edges between the nodes, change the text inside the nodes… A full-fledged editable graph in just 15 lines of code. Nice. Take a look:

Now we can start playing a bit with the code. Some useful model/view handling functions:

		graph.setCellsEditable(false);   // Want to edit the value of a cell in the graph?
		graph.setCellsMovable(false);    // Moving cells in the graph. Note that an edge is also a cell.
		graph.setCellsResizable(false);  // Inhibit cell re-sizing.
		graph.setCellsSelectable(false); // Now I can't even select the cells!!!
		graph.setEnabled(false); // Catch-All: no interaction with the graph.

		graphComponent.setConnectable(false); // Inhibit edge creation in the graph.

I searched the model for some access function to disable the creation of edges between vertices in the graph, but alas! This functionality is located in the view… strange since the re-size functions are in the model…

It’s late and I must go to sleep. In the next episode of “Vainolo learns JGraph”, I’ll start playing with node styles, and graph interaction. See you next time :-)