/** * $Id: mxGraphGenerator.java,v 1.3 2012/11/21 14:16:01 mate Exp $ * Copyright (c) 2012, JGraph Ltd */ package com.mxgraph.analysis; import java.util.ArrayList; import com.mxgraph.costfunction.mxCostFunction; import com.mxgraph.costfunction.mxDoubleValCostFunction; import com.mxgraph.generatorfunction.mxGeneratorFunction; import com.mxgraph.generatorfunction.mxGeneratorRandomFunction; import com.mxgraph.model.mxGeometry; import com.mxgraph.model.mxIGraphModel; import com.mxgraph.view.mxCellState; import com.mxgraph.view.mxGraph; import com.mxgraph.view.mxGraphView; /** * @author Mate * */ public class mxGraphGenerator { // cost function class that implements mxICostFunction // private mxGeneratorFunction generatorFunction = new mxGeneratorRandomFunction(0,1,2); // private mxGeneratorFunction generatorFunction = new mxGeneratorConstFunction(1.5); // private mxGeneratorFunction generatorFunction = new mxGeneratorRandomIntFunction(0, 20); private mxGeneratorFunction generatorFunction = null; private mxCostFunction costFunction = null; public mxGraphGenerator(mxGeneratorFunction generatorFunction, mxCostFunction costFunction) { if (generatorFunction != null) { this.generatorFunction = generatorFunction; } if (costFunction != null) { this.costFunction = costFunction; } else { this.costFunction = new mxDoubleValCostFunction(); } }; /** * @param aGraph * @param numVertexes * @return a null graph */ public void getNullGraph(mxAnalysisGraph aGraph, int numVertices) { if (numVertices < 0) { throw new IllegalArgumentException(); } mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); for (int i = 0; i < numVertices; i++) { graph.insertVertex(parent, null, new Integer(i).toString(), i * 50, 0, 25, 25); } }; /** * @param aGraph * @param numVertices number of vertices * @return A complete graph that has <b>numVertices</b> number of vertices */ public void getCompleteGraph(mxAnalysisGraph aGraph, int numVertices) { if (numVertices < 0) { throw new IllegalArgumentException(); } mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); Object[] vertices = new Object[numVertices]; for (int i = 0; i < numVertices; i++) { vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), i * 50, 0, 25, 25); } for (int i = 0; i < numVertices; i++) { Object vertex1 = vertices[i]; for (int j = 0; j < numVertices; j++) { Object vertex2 = vertices[j]; if (vertex1 != vertex2 && !mxGraphStructure.areConnected(aGraph, vertex1, vertex2)) { graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertex1, vertex2); } } } }; /** * @param aGraph * @param numRows - number of rows in the grid graph * @param numColumns - number of columns in the grid graph * @return Returns a <b>numColumns</b> x <b>numRows</b> grid graph */ public void getGridGraph(mxAnalysisGraph aGraph, int numColumns, int numRows) { if (numColumns < 0 || numRows < 0) { throw new IllegalArgumentException(); } mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); int numVertices = numColumns * numRows; Object[] vertices = new Object[numVertices]; for (int i = 0; i < numVertices; i++) { vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25); } int vertexCount = 0; for (int j = 0; j < numRows; j++) { for (int i = 0; i < numColumns; i++) { Object currVertex = vertices[vertexCount]; if (i > 0) { // connect with previous x graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[vertexCount - 1], currVertex); } if (j > 0) { //connect with previous y graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[vertexCount - numColumns], currVertex); } vertexCount++; } } }; /** * Sets the physical spacing between vertices in a grid graph. This works for now only for a graph generated with mxGraphCreator.getGridGraph() only after creating the graph * @param aGraph * @param xSpacing - horizontal spacing between vertices * @param ySpacing - vertical spacing between vertices * @param numRows - number of rows in the grid graph * @param numColumns - number of columns in the grid graph */ public void setGridGraphSpacing(mxAnalysisGraph aGraph, double xSpacing, double ySpacing, int numColumns, int numRows) { mxGraph graph = aGraph.getGraph(); if (xSpacing < 0 || ySpacing < 0 || numColumns < 1 || numRows < 1) { throw new IllegalArgumentException(); } Object parent = graph.getDefaultParent(); Object[] vertices = aGraph.getChildVertices(parent); mxIGraphModel model = graph.getModel(); for (int i = 0; i < numRows; i++) { for (int j = 0; j < numColumns; j++) { Object currVertex = vertices[i * numColumns + j]; mxGeometry geometry = model.getGeometry(currVertex); geometry.setX(j * xSpacing); geometry.setY(i * ySpacing); } } }; /** * @param aGraph * @param numVerticesGroup1 number of vertices in group 1 * @param numVerticesGroup2 number of vertices in group 2 * @return a bipartite graph with group 1 containing <b>numVerticesGroup1</b> vertices and group 2 containing <b>numVerticesGroup2</b> */ public void getBipartiteGraph(mxAnalysisGraph aGraph, int numVerticesGroup1, int numVerticesGroup2) { if (numVerticesGroup1 < 0 || numVerticesGroup2 < 0) { throw new IllegalArgumentException(); } int numVertices = numVerticesGroup1 + numVerticesGroup2; mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); Object[] vertices = new Object[numVertices]; for (int i = 0; i < numVertices; i++) { vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25); } for (int i = 0; i < numVerticesGroup1; i++) { Object currVertex = vertices[i]; Object destVertex = vertices[getRandomInt(numVerticesGroup1, numVertices - 1)]; graph.insertEdge(parent, null, getNewEdgeValue(aGraph), currVertex, destVertex); } for (int j = 0; j < numVerticesGroup2; j++) { Object currVertex = vertices[numVerticesGroup1 + j]; int edgeNum = aGraph.getOpposites(aGraph.getEdges(currVertex, null, true, true, false, true), currVertex, true, true).length; if (edgeNum == 0) { Object destVertex = vertices[getRandomInt(0, numVerticesGroup1 - 1)]; graph.insertEdge(parent, null, getNewEdgeValue(aGraph), currVertex, destVertex); } } }; /** * Sets the physical spacing between vertices in a bipartite graph. This works for now only for a graph generated with mxGraphCreator.getBipartiteGraph() * only after creating the graph * @param aGraph * @param numVerticesGroup1 - number of vertices in group 1 * @param numVerticesGroup2 - number of vertices in group 2 * @param vertexSpacing - vertical spacing between vertices in the same group * @param groupSpacing - spacing between groups */ public void setBipartiteGraphSpacing(mxAnalysisGraph aGraph, int numVerticesGroup1, int numVerticesGroup2, double vertexSpacing, double groupSpacing) { if (numVerticesGroup1 < 0 || numVerticesGroup2 < 0) { throw new IllegalArgumentException(); } mxGraph graph = aGraph.getGraph(); double group1StartY = 0; double group2StartY = 0; Object parent = graph.getDefaultParent(); mxIGraphModel model = graph.getModel(); if (numVerticesGroup1 < numVerticesGroup2) { double centerYtimes2 = (numVerticesGroup2 * vertexSpacing); group1StartY = (centerYtimes2 - (numVerticesGroup1 * vertexSpacing)) / 2; } else { double centerYtimes2 = (numVerticesGroup1 * vertexSpacing); group2StartY = (centerYtimes2 - (numVerticesGroup2 * vertexSpacing)) / 2; } Object[] vertices = aGraph.getChildVertices(parent); // position vertexes for group 1 for (int i = 0; i < numVerticesGroup1; i++) { Object currVertex = vertices[i]; mxGeometry geometry = model.getGeometry(currVertex); geometry.setX(0); geometry.setY(group1StartY + i * vertexSpacing); } // position vertexes for group 2 for (int i = numVerticesGroup1; i < numVerticesGroup1 + numVerticesGroup2; i++) { Object currVertex = vertices[i]; mxGeometry geometry = model.getGeometry(currVertex); geometry.setX(groupSpacing); geometry.setY(group2StartY + (i - numVerticesGroup1) * vertexSpacing); } }; /** * @param aGraph * @param numVerticesGroup1 number of vertices in group 1 * @param numVerticesGroup2 number of vertices in group 2 * @return a bipartite graph with group 1 containing <b>numVerticesGroup1</b> vertices and group 2 containing <b>numVerticesGroup2</b> */ public void getCompleteBipartiteGraph(mxAnalysisGraph aGraph, int numVerticesGroup1, int numVerticesGroup2) { if (numVerticesGroup1 < 0 || numVerticesGroup2 < 0) { throw new IllegalArgumentException(); } int numVertices = numVerticesGroup1 + numVerticesGroup2; mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); Object[] vertices = new Object[numVertices]; for (int i = 0; i < numVertices; i++) { vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25); } for (int i = 0; i < numVerticesGroup1; i++) { for (int j = numVerticesGroup1; j < numVertices; j++) { Object currVertex = vertices[i]; Object destVertex = vertices[j]; graph.insertEdge(parent, null, getNewEdgeValue(aGraph), currVertex, destVertex); } } }; /** * @param aGraph * @param xDim * @param yDim * @return a knight graph of size <b>xDim</b> x <b>yDim</b> * Note that the minimum size is 3x3 */ public void getKnightGraph(mxAnalysisGraph aGraph, int xDim, int yDim) { if (xDim < 3 || yDim < 3) { throw new IllegalArgumentException(); } int numVertices = xDim * yDim; mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); Object[] vertices = new Object[numVertices]; for (int i = 0; i < numVertices; i++) { vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25); } //now we set up the starting conditions int[] currCoords = new int[2]; //the main loop for (int i = 0; i < (xDim * yDim); i++) { currCoords = getVertexGridCoords(xDim, yDim, i); Object[] neighborMoves = getKnightMoveVertexes(aGraph, xDim, yDim, currCoords[0], currCoords[1]); for (int j = 0; j < neighborMoves.length; j++) { // connect current with the possible move that has minimum number of its (possible moves) if (!mxGraphStructure.areConnected(aGraph, vertices[i], neighborMoves[j])) { graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[i], neighborMoves[j]); } // that vertex becomes the current vertex and we repeat until no possible moves } } }; /** * @param aGraph * @param xDim x dimension of chess-board, size starts from 1 * @param yDim y dimension of chess-board, size starts from 1 * @param xCoord x coordinate on the chess-board, coordinate starts from 1 * @param yCoord y coordinate on the chess-board, coordinate starts from 1 * @return a list of ALL vertexes which would be valid moves from the current position, regardless if they were visited or not * Note that both dimensions and both coordinates must be positive */ public Object[] getKnightMoveVertexes(mxAnalysisGraph aGraph, int xDim, int yDim, int xCoord, int yCoord) { if (xCoord > xDim || yCoord > yDim || xDim < 1 || yDim < 1 || xCoord < 1 || yCoord < 1) { throw new IllegalArgumentException(); } mxGraph graph = aGraph.getGraph(); Object[] vertices = aGraph.getChildVertices(graph.getDefaultParent()); //check all possible 8 locations //location 1 int currX = xCoord + 1; int currY = yCoord - 2; ArrayList<Object> possibleMoves = new ArrayList<Object>(); // check if in bounds Object currVertex; if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } //location 2 currX = xCoord + 2; currY = yCoord - 1; // check if in bounds if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } //location 3 currX = xCoord + 2; currY = yCoord + 1; // check if in bounds if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } //location 4 currX = xCoord + 1; currY = yCoord + 2; // check if in bounds if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } //location 5 currX = xCoord - 1; currY = yCoord + 2; // check if in bounds if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } //location 6 currX = xCoord - 2; currY = yCoord + 1; // check if in bounds if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } //location 7 currX = xCoord - 2; currY = yCoord - 1; // check if in bounds if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } //location 8 currX = xCoord - 1; currY = yCoord - 2; // check if in bounds if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } return possibleMoves.toArray(); }; /** * use this only with the grid graph, and various chess-board graphs, because of vertex ordering * @param xDim x dimension of chess-board, size starts from 1 * @param yDim y dimension of chess-board, size starts from 1 * @param value value of the vertex that needs coordinates returned * @return int[x,y] where x and y are the coordinates in the grid or chess-board * Note that both dimensions must be positive */ public int[] getVertexGridCoords(int xDim, int yDim, int value) { if (value > ((yDim * xDim) - 1) || xDim < 0 || yDim < 0 || value < 0) { throw new IllegalArgumentException(); } int yCoord = (int) Math.floor(value / xDim); int xCoord = (value - yCoord * xDim) + 1; yCoord += 1; int[] coords = new int[2]; coords[0] = xCoord; coords[1] = yCoord; return coords; }; /** * use this only with the grid graph and various chess-board graphs, because of vertex ordering * @param vertices * @param xDim x dimension of chess-board, size starts from 1 * @param yDim y dimension of chess-board, size starts from 1 * @param xCoord x coordinate on the chess-board, coordinate starts from 1 * @param yCoord y coordinate on the chess-board, coordinate starts from 1 * @return vertex on the desired coordinates. * Note that both dimensions and both coordinates must be positive */ private Object getVertexFromGrid(Object[] vertices, int xDim, int yDim, int xCoord, int yCoord) { if (xCoord > xDim || yCoord > yDim || xDim < 1 || yDim < 1 || xCoord < 1 || yCoord < 1) { throw new IllegalArgumentException(); } int value = (yCoord - 1) * xDim + xCoord - 1; return vertices[value]; }; /** * @param xDim * @param yDim * @param weights * Return a king graph of size <b>xDim</b> x <b>yDim</b> * Note that the minimum size is 4x4 */ public void getKingGraph(mxAnalysisGraph aGraph, int xDim, int yDim) { if (xDim < 2 || yDim < 2) { throw new IllegalArgumentException(); } int numVertices = xDim * yDim; mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); Object[] vertices = new Object[numVertices]; for (int i = 0; i < numVertices; i++) { vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25); } //now we set up the starting conditions int[] currCoords = new int[2]; //the main loop for (int i = 0; i < (xDim * yDim); i++) { currCoords = getVertexGridCoords(xDim, yDim, i); Object[] neighborMoves = getKingMoveVertexes(aGraph, xDim, yDim, currCoords[0], currCoords[1]); for (int j = 0; j < neighborMoves.length; j++) { // connect current with the possible move that has minimum number of its (possible moves) if (!mxGraphStructure.areConnected(aGraph, vertices[i], neighborMoves[j])) { graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[i], neighborMoves[j]); } // that vertex becomes the current vertex and we repeat until no possible moves } } }; /** * @param aGraph * @param xDim x dimension of the chessboard * @param yDim y dimension of the chessboard * @param xCoord the current x position of the king * @param yCoord the current y position of the king * @return list of all possible moves of a king from the specified position * Note that both dimensions and both coordinates must be positive */ public Object[] getKingMoveVertexes(mxAnalysisGraph aGraph, int xDim, int yDim, int xCoord, int yCoord) { if (xDim < 0 || yDim < 0 || xCoord < 0 || yCoord < 0) { throw new IllegalArgumentException(); } mxGraph graph = aGraph.getGraph(); Object[] vertices = aGraph.getChildVertices(graph.getDefaultParent()); //check all possible 8 locations //location 1 int currX = xCoord + 1; int currY = yCoord - 1; ArrayList<Object> possibleMoves = new ArrayList<Object>(); // check if in bounds Object currVertex; if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } //location 2 currX = xCoord + 1; currY = yCoord; // check if in bounds if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } //location 3 currX = xCoord + 1; currY = yCoord + 1; // check if in bounds if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } //location 4 currX = xCoord; currY = yCoord + 1; // check if in bounds if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } //location 5 currX = xCoord - 1; currY = yCoord + 1; // check if in bounds if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } //location 6 currX = xCoord - 1; currY = yCoord; // check if in bounds if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } //location 7 currX = xCoord - 1; currY = yCoord + 1; // check if in bounds if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } //location 8 currX = xCoord; currY = yCoord - 1; // check if in bounds if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY); possibleMoves.add(currVertex); } return possibleMoves.toArray(); }; /** * @param aGraph * Returns a Petersen graph */ public void getPetersenGraph(mxAnalysisGraph aGraph) { mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); Object[] vertices = new Object[10]; for (int i = 0; i < 10; i++) { vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25); } graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[0], vertices[2]); graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[0], vertices[8]); graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[0], vertices[9]); graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[1], vertices[2]); graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[1], vertices[5]); graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[1], vertices[7]); graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[2], vertices[4]); graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[3], vertices[4]); graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[3], vertices[7]); graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[3], vertices[9]); graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[4], vertices[6]); graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[5], vertices[6]); graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[5], vertices[9]); graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[6], vertices[8]); graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[7], vertices[8]); }; /** * @param aGraph * @param numVertices * Returns a path graph */ public void getPathGraph(mxAnalysisGraph aGraph, int numVertices) { if (numVertices < 0) { throw new IllegalArgumentException(); } mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); Object[] vertices = new Object[numVertices]; for (int i = 0; i < numVertices; i++) { vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25); } for (int i = 0; i < numVertices - 1; i++) { graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[i], vertices[i + 1]); } }; /** * Sets the physical spacing between vertices in a path graph. This works for now only for a graph generated with mxGraphCreator.getPathGraph() * only after creating the graph * @param aGraph * @param spacing */ public void setPathGraphSpacing(mxAnalysisGraph aGraph, double spacing) { if (spacing < 0) { throw new IllegalArgumentException(); } mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); Object[] vertices = aGraph.getChildVertices(parent); mxIGraphModel model = graph.getModel(); for (int i = 0; i < vertices.length; i++) { Object currVertex = vertices[i]; mxGeometry geometry = model.getGeometry(currVertex); geometry.setX(0); geometry.setY(i * spacing); } }; /** * @param aGraph * @param numVertices * Returns a star graph * Note that minimum vertex number is 4 */ public void getStarGraph(mxAnalysisGraph aGraph, int numVertices) { if (numVertices < 4) { throw new IllegalArgumentException(); } mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); Object[] vertices = new Object[numVertices]; for (int i = 0; i < numVertices; i++) { vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25); } int numVertexesInPerimeter = numVertices - 1; for (int i = 0; i < numVertexesInPerimeter; i++) { graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[numVertexesInPerimeter], vertices[i]); } }; /** * Sets the physical size of a star graph. This works for now only for a graph generated with mxGraphCreator.getStarGraph() and getWheelGraph() * @param aGraph * @param graphSize */ public void setStarGraphLayout(mxAnalysisGraph aGraph, double graphSize) { if (graphSize < 4) { throw new IllegalArgumentException(); } mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); Object[] vertices = aGraph.getChildVertices(parent); mxIGraphModel model = graph.getModel(); int vertexNum = vertices.length; double centerX = graphSize / 2f; double centerY = centerX; int numVertexesInPerimeter = vertexNum - 1; //create the circle for (int i = 0; i < numVertexesInPerimeter; i++) { //calc the position double x = 0; double y = 0; double currRatio = ((double) i / (double) numVertexesInPerimeter); currRatio = currRatio * 2; currRatio = currRatio * (double) Math.PI; x = Math.round(centerX + Math.round(graphSize * Math.sin(currRatio) / 2)); y = Math.round(centerY - Math.round(graphSize * Math.cos(currRatio) / 2)); Object currVertex = vertices[i]; mxGeometry geometry = model.getGeometry(currVertex); geometry.setX(x); geometry.setY(y); } mxGeometry geometry = model.getGeometry(vertices[vertexNum - 1]); geometry.setX(centerX); geometry.setY(centerY); }; /** * @param aGraph * @param numVertices * Returns a wheel graph. Note that numVertices has to be at least 4. */ public void getWheelGraph(mxAnalysisGraph aGraph, int numVertices) { if (numVertices < 4) { throw new IllegalArgumentException(); } mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); Object[] vertices = new Object[numVertices]; for (int i = 0; i < numVertices; i++) { vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25); } int numVerticesInPerimeter = numVertices - 1; for (int i = 0; i < numVerticesInPerimeter; i++) { graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[numVerticesInPerimeter], vertices[i]); if (i < numVerticesInPerimeter - 1) { graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[i], vertices[i + 1]); } else { graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[i], vertices[0]); } } }; /** * @param aGraph * @param numBranches number of branches (minimum >= 2) * @param branchSize number of vertices in a single branch (minimum >= 2) * Returns a friendship windmill graph (aka Dutch windmill) */ public void getFriendshipWindmillGraph(mxAnalysisGraph aGraph, int numBranches, int branchSize) { if (numBranches < 2 || branchSize < 2) { throw new IllegalArgumentException(); } mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); int numVertices = numBranches * branchSize + 1; Object[] vertices = new Object[numVertices]; int vertexCount = 0; for (int i = 0; i < numBranches; i++) { for (int j = 0; j < branchSize; j++) { vertices[vertexCount] = graph.insertVertex(parent, null, new Integer(vertexCount).toString(), 0, 0, 25, 25); vertexCount++; } } vertices[numVertices - 1] = graph.insertVertex(parent, null, new Integer(numVertices - 1).toString(), 0, 0, 25, 25); //make the connections for (int i = 0; i < numBranches; i++) { Object oldVertex = vertices[numVertices - 1]; for (int j = 0; j < branchSize; j++) { Object currVertex = vertices[i * (branchSize) + j]; graph.insertEdge(parent, null, getNewEdgeValue(aGraph), oldVertex, currVertex); oldVertex = currVertex; } Object currVertex = vertices[numVertices - 1]; graph.insertEdge(parent, null, getNewEdgeValue(aGraph), oldVertex, currVertex); } }; /** * @param aGraph * @param numBranches - number of branches (minimum >= 2) * @param branchSize - number of vertices in a single branch (minimum >= 2) * Returns a windmill graph */ public void getWindmillGraph(mxAnalysisGraph aGraph, int numBranches, int branchSize) { if (numBranches < 2 || branchSize < 2) { throw new IllegalArgumentException(); } mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); int numVertices = numBranches * branchSize + 1; Object[] vertices = new Object[numVertices]; int vertexCount = 0; for (int i = 0; i < numBranches; i++) { for (int j = 0; j < branchSize; j++) { vertices[vertexCount] = graph.insertVertex(parent, null, new Integer(vertexCount).toString(), 0, 0, 25, 25); vertexCount++; } } vertices[numVertices - 1] = graph.insertVertex(parent, null, new Integer(numVertices - 1).toString(), 0, 0, 25, 25); Object centerVertex = vertices[numVertices - 1]; //make the connections for (int i = 0; i < numBranches; i++) { for (int j = 0; j < branchSize; j++) { Object vertex1 = vertices[i * (branchSize) + j]; if (!mxGraphStructure.areConnected(aGraph, centerVertex, vertex1)) { graph.insertEdge(parent, null, getNewEdgeValue(aGraph), centerVertex, vertex1); } for (int k = 0; k < branchSize; k++) { Object vertex2 = vertices[i * (branchSize) + k]; if (j != k && !mxGraphStructure.areConnected(aGraph, vertex1, vertex2)) { graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertex1, vertex2); } } } } }; //NOTE the inside code is delicate, so please change it only if you know what you're doing /** * Sets the layout of a windmill graph. Use this method only for graphs generated with mxGraphGenerator.getWindmillGraph() and getFriendshitWindmillGraph() * @param aGraph * @param numBranches * @param numVerticesInBranch * @param graphSize */ public void setWindmillGraphLayout(mxAnalysisGraph aGraph, int numBranches, int numVerticesInBranch, double graphSize) { if (graphSize < 0 || numBranches < 2 || numVerticesInBranch < 1) { throw new IllegalArgumentException(); } mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); Object[] vertices = aGraph.getChildVertices(parent); mxIGraphModel model = graph.getModel(); int vertexNum = vertices.length; double centerX = graphSize / 2f; double centerY = centerX; boolean isBranchSizeEven = ((numVerticesInBranch) % 2 == 0); int middleIndex = (int) Math.ceil(numVerticesInBranch / 2f); //create the circle for (int i = 0; i < numBranches; i++) { for (int j = 0; j < numVerticesInBranch; j++) { double currSize = this.getRingSize(j + 1, numVerticesInBranch, graphSize); //calc the position double x = 0; double y = 0; int numVertexesInPerimeter = 0; double currRatio = 0; numVertexesInPerimeter = numBranches; // need to detect the 2 middle vertices for even sized branches if (isBranchSizeEven && j == middleIndex - 1) { //full size currRatio = ((double) i - (0.0005f * graphSize / Math.pow(numVerticesInBranch, 1))) / (double) numVertexesInPerimeter; } else if (isBranchSizeEven && j == middleIndex) { currRatio = ((double) i + (0.0005f * graphSize / Math.pow(numVerticesInBranch, 1))) / (double) numVertexesInPerimeter; } else if (!isBranchSizeEven && currSize == graphSize) { currRatio = ((double) i / (double) numVertexesInPerimeter); } else { if (j + 1 < middleIndex) { //before middle currRatio = ((double) (i - 1f / Math.pow(currSize, 0.25) + 0.00000000000015f * Math.pow(currSize, 4)) / (double) numVertexesInPerimeter); } else { //after middle currRatio = ((double) (i + 1f / Math.pow(currSize, 0.25) - 0.00000000000015f * Math.pow(currSize, 4)) / (double) numVertexesInPerimeter); } } currRatio = currRatio * 2; currRatio = currRatio * (double) Math.PI; x = Math.round(centerX + Math.round(currSize * Math.sin(currRatio) / 2)); y = Math.round(centerY - Math.round(currSize * Math.cos(currRatio) / 2)); //shoot int currIndex = i * (numVerticesInBranch) + j; Object currVertex = vertices[currIndex]; mxGeometry geometry = model.getGeometry(currVertex); geometry.setX(x); geometry.setY(y); } } //the center vertex is the last one Object currVertex = vertices[vertexNum - 1]; mxGeometry geometry = model.getGeometry(currVertex); geometry.setX(centerX); geometry.setY(centerY); }; /** * A helper function that calculates the ring size for a windmill graph, based on the index of a vertex in a brach and branch size. - for internal use * @param currVertex - starting from 1 * @param branchSize - starting from 1 * @param fullSize * @return ring size */ private double getRingSize(int currIndex, int branchSize, double fullSize) { if (currIndex < 1 || currIndex > branchSize || branchSize < 1 || fullSize < 0) { throw new IllegalArgumentException(); } int middleIndex = 0; boolean isBranchSizeEven = ((branchSize) % 2 == 0); middleIndex = (int) Math.ceil(branchSize / 2f); if (currIndex == middleIndex || (isBranchSizeEven && currIndex == middleIndex + 1)) { //full size return fullSize; } else if (currIndex >= middleIndex) { //after middle currIndex = branchSize - currIndex + 1; } return (((float) Math.pow(currIndex, 0.75) / (float) Math.pow(middleIndex, 0.75)) * fullSize); }; /** * Generates a random graph * @param aGraph * @param numNodes number of vertexes * @param numEdges number of edges (may be inaccurate if <b>forceConnected</b> is set to true * @param allowSelfLoops if true, there will be a chance that self loops will be generated too * @param allowMultipleEdges if true, there will be a chance that multiple edges will be generated (multiple edges between the same two vertices) * @param forceConnected if true the resulting graph will be always connected, but this may alter <b>numEdges</b> */ public void getSimpleRandomGraph(mxAnalysisGraph aGraph, int numNodes, int numEdges, boolean allowSelfLoops, boolean allowMultipleEdges, boolean forceConnected) { mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); Object[] vertices = new Object[numNodes]; for (int i = 0; i < numNodes; i++) { vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25); } for (int i = 0; i < numEdges; i++) { boolean goodPair = true; Object startVertex; Object endVertex; do { goodPair = true; startVertex = vertices[(int) Math.round(Math.random() * (vertices.length - 1))]; endVertex = vertices[(int) Math.round(Math.random() * (vertices.length - 1))]; if (!allowSelfLoops && startVertex.equals(endVertex)) { goodPair = false; } else if (!allowMultipleEdges && mxGraphStructure.areConnected(aGraph, startVertex, endVertex)) { goodPair = false; } } while (!goodPair); graph.insertEdge(parent, null, getNewEdgeValue(aGraph), startVertex, endVertex); } if (forceConnected) { mxGraphStructure.makeConnected(aGraph); } }; /** * Generates a random tree graph * @param aGraph * @param vertexCount */ public void getSimpleRandomTree(mxAnalysisGraph aGraph, int vertexCount) { int edgeCount = (int) Math.round(vertexCount * 2); this.getSimpleRandomGraph(aGraph, vertexCount, edgeCount, false, false, true); //still need to remove surplus edges Object[] vertices = aGraph.getChildVertices(aGraph.getGraph().getDefaultParent()); try { oneSpanningTree(aGraph, true, true); } catch (StructuralException e) { System.out.println(e); } try { mxGraphStructure.makeTreeDirected(aGraph, vertices[(int) Math.round(Math.random() * (vertices.length - 1))]); } catch (StructuralException e) { System.out.println(e); } }; /** * Creates a new edge value based on graph properties in mxAnalysisGraph. Used mostly when creating new edges during graph generation. * @param aGraph * @return */ public Double getNewEdgeValue(mxAnalysisGraph aGraph) { if (getGeneratorFunction() != null) { mxGraph graph = aGraph.getGraph(); return getGeneratorFunction().getCost(graph.getView().getState(graph.getDefaultParent())); } else { return null; } }; /** * @param graph * @param weighted if true, the edges will be weighted, otherwise all will have default value (1.0) * @param minWeight minimum edge weight if weighted * @param maxWeight maximum edge weight if weighted * @return a generator function */ public static mxGeneratorFunction getGeneratorFunction(mxGraph graph, boolean weighted, double minWeight, double maxWeight) { if (weighted) { return new mxGeneratorRandomFunction(minWeight, maxWeight, 2); } else { return null; } }; public mxGeneratorFunction getGeneratorFunction() { return this.generatorFunction; }; /** * @param minValue * @param maxValue * @return a random integer in the interval [minValue, maxValue] */ public int getRandomInt(int minValue, int maxValue) { if (minValue == maxValue) return minValue; if (minValue > maxValue) { int tmp = maxValue; maxValue = minValue; minValue = tmp; } int currValue = 0; currValue = minValue + (int) Math.round((Math.random() * (maxValue - minValue))); return currValue; }; /** * @param graph * @param forceConnected if true, an unconnected graph is made connected * @param forceSimple if true, a non-simple graph is made simple * Calculates one spanning tree of graph, which doesn't have to be but can be minimal * (this is faster than minimal spanning tree, so if you need any spanning tree, use this one) * Self loops and multiple edges are automatically removed! * Also, unconnected graphs are made connected! * @throws StructuralException the graph has to be simple (no self-loops and no multiple edges) */ public void oneSpanningTree(mxAnalysisGraph aGraph, boolean forceConnected, boolean forceSimple) throws StructuralException { mxGraph graph = aGraph.getGraph(); boolean isSimple = mxGraphStructure.isSimple(aGraph); boolean isConnected = mxGraphStructure.isConnected(aGraph); if (!isSimple) { if (forceSimple) { mxGraphStructure.makeSimple(aGraph); } else { throw new StructuralException("Graph is not simple."); } } if (!isConnected) { if (forceConnected) { mxGraphStructure.makeConnected(aGraph); } else { throw new StructuralException("Graph is not connected."); } } Object[] edges = aGraph.getChildEdges(graph.getDefaultParent()); int edgeCount = edges.length; for (int i = 0; i < edgeCount; i++) { Object currEdge = edges[i]; graph.removeCells(new Object[] { currEdge }); if (!mxGraphStructure.isConnected(aGraph)) { graph.addCell(currEdge); } } }; //TODO make a double check to avoid unnecessary cases of throwing an exception (if the algorithm can't work out a solution, try a mirrored strategy) /** * @param aGraph * @param xDim x dimension of the chessboard * @param yDim y dimension of the chessboard * @param startVertexValue vertex where the tour will start * @throws StructuralException not all size combinations are allowed, see wikipedia for a more detailed explanation * Returns a Knight's Tour graph */ public void getKnightTour(mxAnalysisGraph aGraph, int xDim, int yDim, int startVertexValue) throws StructuralException { if (xDim < 5 || yDim < 5) { throw new IllegalArgumentException(); } ArrayList<Object> resultPath = new ArrayList<Object>(); int vertexNum = xDim * yDim; mxGraph graph = aGraph.getGraph(); Object parent = graph.getDefaultParent(); int vertexCount = 0; for (int i = 0; i < vertexNum; i++) { graph.insertVertex(parent, null, new Integer(vertexCount).toString(), 0, 0, 25, 25); vertexCount++; } // we have the board set up Object[] vertices = aGraph.getChildVertices(parent); //now we set up the starting conditions int currValue = startVertexValue; int[] currCoords = new int[2]; Object oldMove = vertices[startVertexValue]; currCoords = getVertexGridCoords(xDim, yDim, startVertexValue); resultPath.add(oldMove); Object nextMove = getNextKnightMove(aGraph, xDim, yDim, currCoords[0], currCoords[1], resultPath); mxCostFunction costFunction = aGraph.getGenerator().getCostFunction(); mxGraphView view = graph.getView(); //the main loop while (nextMove != null) { // connect current with the possible move that has minimum number of its (possible moves) graph.insertEdge(parent, null, null, oldMove, nextMove); resultPath.add(nextMove); // that vertex becomes the current vertex and we repeat until no possible moves currValue = (int) costFunction.getCost(new mxCellState(view, nextMove, null)); currCoords = getVertexGridCoords(xDim, yDim, currValue); oldMove = nextMove; nextMove = getNextKnightMove(aGraph, xDim, yDim, currCoords[0], currCoords[1], resultPath); } if (resultPath.size() < vertexNum) { //the mirrored strategy should go here, instead of the exception //and the exception would be thrown only if the mirrored fails too throw new StructuralException("Could not generate a correct Knight tour with size " + xDim + " x " + yDim + "."); } }; /** * Helper function for Knights Tour - for internal use * @param aGraph * @param xDim * @param yDim * @param xCoord * @param yCoord * @param resultPath * @return */ private Object getNextKnightMove(mxAnalysisGraph aGraph, int xDim, int yDim, int xCoord, int yCoord, ArrayList<Object> resultPath) { Object[] possibleMoves = getKnightMoveVertexes(aGraph, xDim, yDim, xCoord, yCoord); //get the position with minimum possible moves int minMoveNum = 9; float biggestDistance = 0; Object currVertex = null; mxCostFunction costFunction = aGraph.getGenerator().getCostFunction(); mxGraphView view = aGraph.getGraph().getView(); for (int i = 0; i < possibleMoves.length; i++) { int currValue = (int) costFunction.getCost(new mxCellState(view, possibleMoves[i], null)); int[] currCoords = getVertexGridCoords(xDim, yDim, currValue); int currMoveNum = getPossibleKnightMoveCount(aGraph, xDim, yDim, currCoords[0], currCoords[1]); float currDistance = getDistanceFromGridCenter(xDim, yDim, currValue); if ((currMoveNum < minMoveNum || (currMoveNum == minMoveNum && currDistance > biggestDistance)) && !resultPath.contains(possibleMoves[i])) { biggestDistance = currDistance; minMoveNum = currMoveNum; currVertex = possibleMoves[i]; } } return currVertex; }; /** * Helper function for Knights Tour - for internal use * @param aGraph * @param xDim * @param yDim * @param xCoord * @param yCoord * @return */ private int getPossibleKnightMoveCount(mxAnalysisGraph aGraph, int xDim, int yDim, int xCoord, int yCoord) { //check all possible 8 locations //location 1 int currX = xCoord + 1; int currY = yCoord - 2; int possibleMoveCount = 0; Object parent = aGraph.getGraph().getDefaultParent(); Object[] vertices = aGraph.getChildVertices(parent); if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0) { possibleMoveCount++; } } //location 2 currX = xCoord + 2; currY = yCoord - 1; if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0) { possibleMoveCount++; } } //location 3 currX = xCoord + 2; currY = yCoord + 1; if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0) { possibleMoveCount++; } } //location 4 currX = xCoord + 1; currY = yCoord + 2; if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0) { possibleMoveCount++; } } //location 5 currX = xCoord - 1; currY = yCoord + 2; if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0) { possibleMoveCount++; } } //location 6 currX = xCoord - 2; currY = yCoord + 1; if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0) { possibleMoveCount++; } } //location 7 currX = xCoord - 2; currY = yCoord - 1; if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0) { possibleMoveCount++; } } //location 8 currX = xCoord - 1; currY = yCoord - 2; if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim) { if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0) { possibleMoveCount++; } } return possibleMoveCount; }; /** * Helper function for Knights Tour - for internal use * @param xDim * @param yDim * @param currValue * @return */ private float getDistanceFromGridCenter(int xDim, int yDim, int currValue) { float centerX = (xDim + 1) / 2f; float centerY = (yDim + 1) / 2f; int[] currCoords = getVertexGridCoords(xDim, yDim, currValue); float x = Math.abs(centerX - currCoords[0]); float y = Math.abs(centerY - currCoords[1]); return (float) Math.sqrt(x * x + y * y); } public mxCostFunction getCostFunction() { return costFunction; } public void setCostFunction(mxCostFunction costFunction) { this.costFunction = costFunction; }; };