/* * $Id: JGraphGraphFactory.java,v 1.12 2006/01/03 11:11:41 david Exp $ * Copyright (c) 2001-2006, Gaudenz Alder * Copyright (c) 2005-2006, David Benson * * All rights reserved. * * This file is licensed under the JGraph software license, a copy of which * will have been provided to you in the file LICENSE at the root of your * installation directory. If you are unable to locate this file please * contact JGraph sales for another copy. */ package com.jgraph.example; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Frame; import java.awt.GridLayout; import java.awt.Toolkit; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Hashtable; import java.util.Map; import java.util.Random; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.border.EmptyBorder; import org.jgraph.JGraph; import org.jgraph.graph.AttributeMap; import org.jgraph.graph.ConnectionSet; import org.jgraph.graph.DefaultEdge; import org.jgraph.graph.DefaultGraphCell; import org.jgraph.graph.DefaultGraphModel; import org.jgraph.graph.Edge; import org.jgraph.graph.GraphConstants; import org.jgraph.graph.GraphModel; import org.jgraph.graph.ParentMap; import org.jgraph.graph.Port; /** * A helper class that creates graphs. Currently supports tree graphs and a * random graph where all edges are connected at least once */ public class JGraphGraphFactory { public static final int FULLY_CONNECTED = 0; public static final int RANDOM_CONNECTED = 1; public static final int TREE = 2; public static final int FLOW = 3; /** * Shared <code>Random</code> */ private Random random = new Random(); /** * Number of vertices on current tree level being worked on */ private int numVerticesLevel; /** * Stores first unconnected edge available */ private int edgeIndex; /** * Stores first unconnected cell available in smaple tree, root cell never * is available */ private int vertexIndex; /** * Whether or not insert at performed directly on the model */ private boolean insertIntoModel = false; /** * Number of nodes for use as bean variable */ protected int numNodes = 36; /** * Number of edges for use as bean variable */ protected int numEdges = 36; /** * The maximum number of child nodes any parent in the tree graph can have */ protected int maxNodesPerTreeLevel = 2; protected FactoryConfigDialog dialog; /** * Default constructor */ public JGraphGraphFactory() { } /** * Entry method for inserting a sample graph * * @param graph * the JGraph to perform the insert on * @param graphType * which sample graph type is to be inserted * @param defaultVertexAttributes * the default attributes to use for vertices * @param defaultEdgeAttributes * the default attributes to use for edges */ public void insertGraph(JGraph graph, int graphType, Map defaultVertexAttributes, Map defaultEdgeAttributes) { if (dialog == null) { dialog = new FactoryConfigDialog(); } dialog.configureLayout(graph, graphType, defaultVertexAttributes, defaultEdgeAttributes); dialog.setModal(true); center(dialog); dialog.setVisible(true); } /** * clears the graph and inserts a random tree. The nodes are initially * placed a grid with the root node selected. The algorithm used is not * recursive as the number of nodes per level are not know at the size. A * DFS search would not work, since we don't know where the leaves are. * Cells are inserted over edges for clarity * * @param graph * the JGraph to perform the insert on * @param defaultVertexAttributes * the default attributes to use for vertices * @param defaultEdgeAttributes * the default attributes to use for edges * @return the root node of the tree */ public Object insertTreeSampleData(JGraph graph, Map defaultVertexAttributes, Map defaultEdgeAttributes) { // Create array big enough for all cells Object[] cells = new Object[numNodes * 2]; initialise(graph); numVerticesLevel = 1; edgeIndex = 0; vertexIndex = 1; int gridWidth = (int) Math.sqrt(numNodes); // Ensure arrows are present in edge attributes int arrow = GraphConstants.ARROW_CLASSIC; GraphConstants.setLineEnd(defaultEdgeAttributes, arrow); GraphConstants.setEndFill(defaultEdgeAttributes, true); // the cell occupy the first half of the cells array, i.e. from // 0 to numNodes-1, the edge the second half, from numNodes to // numNode*2-1 for (int i = 0; i < numNodes; i++) { Point2D cellPosition = calcCellPosition(i, gridWidth); DefaultGraphCell cell = createVertex(new Integer(i).toString(), cellPosition, defaultVertexAttributes); cells[i] = cell; } connectNextLevel(graph.getModel(), cells, defaultEdgeAttributes); Object[] cells2 = new Object[numNodes + numNodes - 1]; System.arraycopy(cells, numNodes, cells2, 0, numNodes - 1); System.arraycopy(cells, 0, cells2, numNodes - 1, numNodes); insertIntoGraph(graph, cells2); // Select the root cell graph.setSelectionCell(cells[0]); return cells[0]; } /** * clears the graph and inserts a random tree. The nodes are initially * placed a grid with the root node selected. The algorithm used is not * recursive as the number of nodes per level are not know at the size. A * DFS search would not work, since we don't know where the leaves are. * Cells are inserted over edges for clarity * * @param model * the model to perform the insert on * @param defaultVertexAttributes * the default attributes to use for vertices * @param defaultEdgeAttributes * the default attributes to use for edges * @return the root node of the tree */ public Object insertTreeSampleData(GraphModel model, Map defaultVertexAttributes, Map defaultEdgeAttributes) { // Create array big enough for all cells Object[] cells = new Object[numNodes * 2]; // Clear out the model Object[] roots = DefaultGraphModel.getRoots(model); Object[] descendants = DefaultGraphModel.getDescendants(model, roots).toArray(); model.remove(descendants); numVerticesLevel = 1; edgeIndex = 0; vertexIndex = 1; int gridWidth = (int) Math.sqrt(numNodes); // Ensure arrows are present in edge attributes int arrow = GraphConstants.ARROW_CLASSIC; GraphConstants.setLineEnd(defaultEdgeAttributes, arrow); GraphConstants.setEndFill(defaultEdgeAttributes, true); // the cell occupy the first half of the cells array, i.e. from // 0 to numNodes-1, the edge the second half, from numNodes to // numNode*2-1 for (int i = 0; i < numNodes; i++) { Point2D cellPosition = calcCellPosition(i, gridWidth); DefaultGraphCell cell = createVertex(new Integer(i).toString(), cellPosition, defaultVertexAttributes); cells[i] = cell; } connectNextLevel(model, cells, defaultEdgeAttributes); Object[] cells2 = new Object[numNodes + numNodes - 1]; System.arraycopy(cells, numNodes, cells2, 0, numNodes - 1); System.arraycopy(cells, 0, cells2, numNodes - 1, numNodes); JGraphGraphFactory.insert(model, cells2); return cells[0]; } /** * Takes all cells to be connected between one level and the next creates * * @param cells * @param defaultEdgeAttributes */ protected void connectNextLevel(GraphModel model, Object[] cells, Map defaultEdgeAttributes) { // If we've connected all vertices stop connecting if (vertexIndex < numNodes) { // Store the number of vertices on this level locally as the // variable is going to be reused int localNumVerticesLevel = numVerticesLevel; numVerticesLevel = 0; int localVertexCount = vertexIndex; // For each node in this level connect a random number of vertices for (int i = localVertexCount - localNumVerticesLevel; i < localVertexCount; i++) { connectChildrenVertices(model, cells, cells[i], defaultEdgeAttributes); } // Recurse connectNextLevel(model, cells, defaultEdgeAttributes); } } /** * Connects the next <code>numChildren</code> free vertices as targets * from the specified <code>parent</code> * * @param cells * @param parent * @param defaultEdgeAttributes */ protected void connectChildrenVertices(GraphModel model, Object[] cells, Object parent, Map defaultEdgeAttributes) { // If we've connected all vertices stop connecting if (vertexIndex < numNodes) { // Make a list of child cells first. We don't recurse straight down // to leaves since we don't how deep the tree is. Instead, each // level is connected at a time. Increasing maxNodesPerTreeLevel // makes the tree wider and shallower, decreasing makes it deeper // and narrower int numChildren = random.nextInt(maxNodesPerTreeLevel) + 1; Port parentPort; if (parent instanceof Port) { parentPort = (Port)parent; } else { parentPort = (Port)model.getChild(parent, 0); } for (int i = 0; i < numChildren; i++) { // If we've connected all vertices stop connecting if (vertexIndex < numNodes) { numVerticesLevel++; // Port of child i Port childPort; if (cells[vertexIndex] instanceof Port) { childPort = (Port)cells[vertexIndex++]; } else { childPort = (Port)model.getChild(cells[vertexIndex++], 0); } Edge edge = createEdge(defaultEdgeAttributes, parentPort, childPort); cells[(edgeIndex++) + numNodes] = edge; } } } } /** * clears the graph and inserts a random graph. The nodes are initially * placed a grid with no node selected. If there are at least as many edges * as nodes then all cells have at least one edge connected to them. * * @param graph * the JGraph instance to act upon * @param defaultVertexAttributes * the default attributes to use for vertices * @param defaultEdgeAttributes * the default attributes to use for edges */ public void insertConnectedGraphSampleData(JGraph graph, Map defaultVertexAttributes, Map defaultEdgeAttributes) { // Create array big enough for all cells Object[] cells = new DefaultGraphCell[numNodes + numEdges]; GraphModel model = graph.getModel(); initialise(graph); int gridWidth = (int) Math.sqrt(numNodes); for (int i = 0; i < numNodes; i++) { Point2D cellPosition = calcCellPosition(i, gridWidth); DefaultGraphCell cell = createVertex(new Integer(i).toString(), cellPosition, defaultVertexAttributes); cells[i] = cell; } // Connect every cell in turn to a random other for (int i = 0; i < Math.min(numNodes, numEdges); i++) { // Port of child i Port sourcePort; if (cells[i] instanceof Port) { sourcePort = (Port)cells[i]; } else { sourcePort = (Port)model.getChild(cells[i], 0); } // Select random other cell int node = random.nextInt(numNodes); if (numNodes > 1) { while (node == i) { node = random.nextInt(numNodes); } } Port targetPort; if (cells[node] instanceof Port) { targetPort = (Port)cells[node]; } else { targetPort = (Port)model.getChild(cells[node], 0); } Edge edge = createEdge(defaultEdgeAttributes, sourcePort, targetPort); cells[i + numNodes] = edge; } // Connect remaining edges randomly for (int i = numNodes; i < numEdges; i++) { int sourceNode = random.nextInt(numNodes); Port sourcePort; if (cells[sourceNode] instanceof Port) { sourcePort = (Port)cells[sourceNode]; } else { sourcePort = (Port)model.getChild(cells[sourceNode], 0); } // Select random other cell int targetNode = random.nextInt(numNodes); if (numNodes > 1) { while (targetNode == sourceNode) { targetNode = random.nextInt(numNodes); } } Port targetPort; if (cells[targetNode] instanceof Port) { targetPort = (Port)cells[targetNode]; } else { targetPort = (Port)model.getChild(cells[targetNode], 0); } Edge edge = createEdge(defaultEdgeAttributes, sourcePort, targetPort); cells[i + numNodes] = edge; } Object[] cells2 = new Object[numNodes + numEdges]; System.arraycopy(cells, numNodes, cells2, 0, numEdges); System.arraycopy(cells, 0, cells2, numEdges, numNodes); insertIntoGraph(graph, cells2); } /** * clears the graph and inserts a fully connected graph. The nodes are * initially placed a grid. There are the same number of cells and edges in * the graph, all cells have at least one edge connected to them. * * @param graph * the JGraph instance to act upon * @param defaultVertexAttributes * the default attributes to use for vertices * @param defaultEdgeAttributes * the default attributes to use for edges */ public void insertFullyConnectedGraphSampleData(JGraph graph, Map defaultVertexAttributes, Map defaultEdgeAttributes) { GraphModel model = graph.getModel(); // Calculate the number of edges int numEdges = ((numNodes - 1) * (numNodes)) / 2; // Create array big enough for all cells Object[] cells = new DefaultGraphCell[numNodes + numEdges]; initialise(graph); int gridWidth = (int) Math.sqrt(numNodes); for (int i = 0; i < numNodes; i++) { Point2D cellPosition = calcCellPosition(i, gridWidth); DefaultGraphCell cell = createVertex(new Integer(i).toString(), cellPosition, defaultVertexAttributes); cells[numEdges + i] = cell; } int cellCount = 0; // Connect every cell to each other for (int i = 0; i < numNodes; i++) { // Port of child i Port sourcePort; if (cells[numEdges + i] instanceof Port) { sourcePort = (Port)cells[numEdges + i]; } else { sourcePort = (Port)model.getChild(cells[numEdges + i], 0); } for (int j = i + 1; j < numNodes; j++) { Port targetPort; if (cells[numEdges + j] instanceof Port) { targetPort = (Port)cells[numEdges + j]; } else { targetPort = (Port)model.getChild(cells[numEdges + j], 0); } Edge edge = createEdge(defaultEdgeAttributes, sourcePort, targetPort); cells[cellCount++] = edge; } } insertIntoGraph(graph, cells); } /** * clears the graph and inserts a fully connected graph. The nodes are * initially placed a grid. There are the same number of cells and edges in * the graph, all cells have at least one edge connected to them. * * @param graph * the JGraph instance to act upon * @param defaultVertexAttributes * the default attributes to use for vertices * @param defaultEdgeAttributes * the default attributes to use for edges */ public void insertSampleFlowGraph(JGraph graph, Map defaultVertexAttributes, Map defaultEdgeAttributes) { } /** * Returns a point on a square grid given the index into the total number of * cells and the width of one line of the grid * * @param i * index of cell * @param gridWidth * width of each grid line * @return the position of the cell */ private Point2D calcCellPosition(int i, int gridWidth) { if (i != 0) { return new Point2D.Double(20 + (60 * (i % gridWidth)), 20 + (40 * (i / gridWidth))); } else { return new Point2D.Double(20, 20); } } /** * Method hook to create custom vertices * * @param userObject * the user object to pass to the cell * @return the new vertex instance */ protected DefaultGraphCell createVertex(Object userObject, Point2D position, Map defaultVertexAttributes) { AttributeMap attributes = new AttributeMap(defaultVertexAttributes); GraphConstants.setBounds(attributes, new Rectangle2D.Double(position .getX(), position.getY(), 40, 20)); DefaultGraphCell cell = new DefaultGraphCell(userObject, attributes); // Add a Port cell.addPort(); return cell; } /** * Method hook to create custom edges * * @return the new vertex instance */ protected Edge createEdge(Map defaultEdgeAttributes, Port sourcePort, Port targetPort) { AttributeMap edgeAttrib = null; if (defaultEdgeAttributes != null) { edgeAttrib = new AttributeMap(defaultEdgeAttributes); } else { edgeAttrib = new AttributeMap(6); } Edge edge = new DefaultEdge(null, edgeAttrib); edge.setSource(sourcePort); edge.setTarget(targetPort); return edge; } /** * Common initialization functionality * */ protected void initialise(JGraph graph) { // Remove all previous cells graph.getModel().remove(graph.getDescendants(graph.getRoots())); } /** * Common insert functionality */ protected void insertIntoGraph(JGraph graph, Object[] cells) { // For performance, don't select inserted cells boolean selectsAll = graph.getGraphLayoutCache() .isSelectsAllInsertedCells(); boolean selectsLocal = graph.getGraphLayoutCache() .isSelectsLocalInsertedCells(); graph.getGraphLayoutCache().setSelectsAllInsertedCells(false); graph.getGraphLayoutCache().setSelectsLocalInsertedCells(false); if (insertIntoModel) { graph.getModel().insert(cells, null, null, null, null); } else { graph.getGraphLayoutCache().insert(cells); } graph.getGraphLayoutCache().setSelectsAllInsertedCells(selectsAll); graph.getGraphLayoutCache().setSelectsLocalInsertedCells(selectsLocal); } /** * Inserts the specified cells into the graph model. This method is a * general implementation of cell insertion. If the source and target port * are null, then no connection set is created. The method uses the * attributes from the specified edge and the egdge's children to construct * the insert call. This example shows how to insert an edge with a special * arrow between two known vertices: * * <pre> * Object source = graph.getDefaultPortForCell(sourceVertex).getCell(); * Object target = graph.getDefaultPortForCell(targetVertex).getCell(); * DefaultEdge edge = new DefaultEdge("Hello, world!"); * edge.setSource(source); * edge.setTarget(target); * Map attrs = edge.getAttributes(); * GraphConstants.setLineEnd(attrs, GraphConstants.ARROW_TECHNICAL); * graph.getGraphLayoutCache().insert(edge); * </pre> */ public static void insert(GraphModel model, Object[] cells) { insert(model, cells, new Hashtable(), new ConnectionSet(), new ParentMap()); } /** * Variant of the insert method that allows to pass a default connection set * and parent map and nested map. */ public static void insert(GraphModel model, Object[] cells, Map nested, ConnectionSet cs, ParentMap pm) { if (cells != null) { if (nested == null) nested = new Hashtable(); if (cs == null) cs = new ConnectionSet(); if (pm == null) pm = new ParentMap(); for (int i = 0; i < cells.length; i++) { // Using the children of the vertex we construct the parent map. int childCount = model.getChildCount(cells[i]); for (int j = 0; j < childCount; j++) { Object child = model.getChild(cells[i], j); pm.addEntry(child, cells[i]); // And add their attributes to the nested map AttributeMap attrs = model.getAttributes(child); if (attrs != null) nested.put(child, attrs); } // A nested map with the vertex as key // and its attributes as the value // is required for the model. Map attrsTmp = (Map) nested.get(cells[i]); Map attrs = model.getAttributes(cells[i]); if (attrsTmp != null) attrs.putAll(attrsTmp); nested.put(cells[i], attrs); // Check if we have parameters for a connection set. Object sourcePort = model.getSource(cells[i]); if (sourcePort != null) cs.connect(cells[i], sourcePort, true); Object targetPort = model.getTarget(cells[i]); if (targetPort != null) cs.connect(cells[i], targetPort, false); } // Create an array with the parent and its children. cells = DefaultGraphModel.getDescendants(model, cells) .toArray(); // Finally call the insert method on the parent class. model.insert(cells, nested, cs, pm, null); } } /** * @return Returns the insertIntoModel. */ public boolean isInsertIntoModel() { return insertIntoModel; } /** * @param insertIntoModel * The insertIntoModel to set. */ public void setInsertIntoModel(boolean insertIntoModel) { this.insertIntoModel = insertIntoModel; } /** * @return Returns the numEdges. */ public int getNumEdges() { return numEdges; } /** * @param numEdges * The numEdges to set. */ public void setNumEdges(int numEdges) { if (numEdges < 1) { numEdges = 1; } else if (numEdges > 2000000) { numEdges = 2000000; } this.numEdges = numEdges; } /** * @return Returns the numNodes. */ public int getNumNodes() { return numNodes; } /** * @param numNodes * The numNodes to set. */ public void setNumNodes(int numNodes) { if (numNodes < 1) { numNodes = 1; } else if (numNodes > 2000000) { numNodes = 2000000; } this.numNodes = numNodes; } /** * @return Returns the maxNodesPerTreeLevel. */ public int getMaxNodesPerTreeLevel() { return maxNodesPerTreeLevel; } /** * @param maxNodesPerTreeLevel * The maxNodesPerTreeLevel to set. */ public void setMaxNodesPerTreeLevel(int maxNodesPerTreeLevel) { this.maxNodesPerTreeLevel = maxNodesPerTreeLevel; } public static void center(Window wnd) { Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frameSize = wnd.getSize(); wnd.setLocation(screenSize.width / 2 - (frameSize.width / 2), screenSize.height / 2 - (frameSize.height / 2)); } /** * Simple Dialog that configures how many nodes and edges the graph factory * is to create */ public class FactoryConfigDialog extends JDialog { protected boolean insertGraph = false; protected JGraph graph; protected int graphType; protected Map defaultVertexAttributes; protected Map defaultEdgeAttributes; protected JTextField maxTreeNodeChildren = new JTextField(); protected JTextField numNodes = new JTextField(); protected JTextField numEdges = new JTextField(); protected JCheckBox insertIntoModel = new JCheckBox(); public FactoryConfigDialog() { super((Frame) null, "Configure Sample Graph", true); JPanel panel = new JPanel(new GridLayout(4, 2, 4, 4)); panel.add(new JLabel("Max Child Nodes in Tree")); panel.add(maxTreeNodeChildren); panel.add(new JLabel("Number of nodes")); panel.add(numNodes); panel.add(new JLabel("Number of edges")); panel.add(numEdges); panel.add(new JLabel("Insert into model")); panel.add(insertIntoModel); JPanel panelBorder = new JPanel(); panelBorder.setBorder(new EmptyBorder(10, 10, 10, 10)); panelBorder.add(panel); JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory .createMatteBorder(1, 0, 0, 0, Color.GRAY), BorderFactory .createEmptyBorder(16, 8, 8, 8))); JButton applyButton = new JButton("Insert"); JButton closeButton = new JButton("Cancel"); buttonPanel.add(closeButton); buttonPanel.add(applyButton); getRootPane().setDefaultButton(applyButton); applyButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { applyValues(); if (graphType == TREE) { insertTreeSampleData(graph, defaultVertexAttributes, defaultEdgeAttributes); } else if (graphType == RANDOM_CONNECTED) { insertConnectedGraphSampleData(graph, defaultVertexAttributes, defaultEdgeAttributes); } else if (graphType == FULLY_CONNECTED) { insertFullyConnectedGraphSampleData(graph, defaultVertexAttributes, defaultEdgeAttributes); } else if (graphType == FLOW) { insertSampleFlowGraph(graph, defaultVertexAttributes, defaultEdgeAttributes); } setVisible(false); } }); closeButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { insertGraph = false; setVisible(false); } }); getContentPane().add(panelBorder, BorderLayout.CENTER); getContentPane().add(buttonPanel, BorderLayout.SOUTH); pack(); setResizable(false); // setLocationRelativeTo(parent); } public void configureLayout(JGraph graph, int graphType, Map defaultVertexAttributes, Map defaultEdgeAttributes) { this.graph = graph; this.graphType = graphType; this.defaultVertexAttributes = defaultVertexAttributes; this.defaultEdgeAttributes = defaultEdgeAttributes; maxTreeNodeChildren.setText(String .valueOf(getMaxNodesPerTreeLevel())); this.numNodes.setText(String.valueOf(getNumNodes())); this.numEdges.setText(String.valueOf(getNumEdges())); this.insertIntoModel.setSelected(isInsertIntoModel()); } protected void applyValues() { setMaxNodesPerTreeLevel(Integer.parseInt(maxTreeNodeChildren .getText())); setNumNodes(Integer.parseInt(this.numNodes.getText())); setNumEdges(Integer.parseInt(this.numEdges.getText())); setInsertIntoModel(this.insertIntoModel.isSelected()); } } }