/* * JFLAP - Formal Languages and Automata Package * * * Susan H. Rodger * Computer Science Department * Duke University * August 27, 2009 * Copyright (c) 2002-2009 * All rights reserved. * JFLAP is open source software. Please see the LICENSE for terms. * */ package automata.graph.layout; import java.awt.geom.Point2D; import java.awt.Dimension; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Random; import java.util.Set; import automata.graph.Graph; import automata.graph.LayoutAlgorithm; /** * Implements the GEM algorithm, by Arne Frick, Andreas Ludwig, and Heiko * Mehldau in their 1994 paper. At present the rotation detection is not built * in, as forcing speedier convergence is totally unnecessary for our limited * applications. * * @author Thomas Finley */ public class GEMLayoutAlgorithm extends LayoutAlgorithm { /** * Default constructor. */ public GEMLayoutAlgorithm() { super(); } /** * Constructor allowing the user to customize certain values. The vertexDim * is not used in this algorithm, but it is here so the superclass constructor can * be used and for the LayoutAlgorithmFactory. * * @param pSize * value for size. * @param vDim * value for vertexDim. * @param vBuffer * value for vertexBuffer. */ public GEMLayoutAlgorithm(Dimension pSize, Dimension vDim, double vBuffer) { super(pSize, vDim, vBuffer); } public void layout(Graph graph, Set isovertices) { if (isovertices == null) isovertices = EMPTY_SET; Object[] vArray = graph.vertices(); int Rmax = 120 * (vArray.length - isovertices.size()); double Tglobal = Tmin + 1.0; // Determine an optimal edge length. With isovertices, we // want optimal length to be about average of existing edges // that will remain unchanged due to isovertex status. double optimalEdgeLength = OPTIMAL_EDGE_LENGTH; if (isovertices.size() > 0) { Object[] iso = isovertices.toArray(); int count = 0; double lengths = 0.0; for (int i = 0; i < iso.length; i++) for (int j = i + 1; j < iso.length; j++) { if (!graph.hasEdge(iso[i], iso[j])) continue; lengths += graph.pointForVertex(iso[i]).distance( graph.pointForVertex(iso[j])); count++; } if (count > 0) optimalEdgeLength = lengths / (double) count; } // The barycenter of the graph. double[] c = new double[] { 0.0, 0.0 }; // Initialize the record for each vertex. records = new HashMap(); for (int i = 0; i < vArray.length; i++) { Record r = new Record(); r.point = graph.pointForVertex(vArray[i]); // The barycenter will be updated. c[0] += r.point.getX(); c[1] += r.point.getY(); records.put(vArray[i], r); } // Iterate until done. ArrayList vertices = new ArrayList(); for (int i = 0; i < Rmax && Tglobal > Tmin; i++) { if (vertices.isEmpty()) { vertices = getMovableVertices(graph, isovertices); if (vertices.size() == 0) return; } // Choose a vertex V to update. int index = RANDOM.nextInt(vertices.size()); Object vertex = vertices.remove(index); Record record = (Record) records.get(vertex); Point2D point = graph.pointForVertex(vertex); // Compute the impulse of V. double Theta = graph.degree(vertex); Theta *= 1.0 + Theta / 2.0; double[] p = new double[] { (c[0] / graph.numberOfVertices() - point.getX()) * GRAVITATIONAL_CONSTANT * Theta, (c[1] / graph.numberOfVertices() - point.getY()) * GRAVITATIONAL_CONSTANT * Theta }; // Attraction to // BC. // Random disturbance. p[0] += RANDOM.nextDouble() * 10.0 - 5.0; p[1] += RANDOM.nextDouble() * 10.0 - 5.0; // Forces exerted by other nodes. for (int j = 0; j < vArray.length; j++) { if (vArray[j] == vertex) continue; Point2D otherPoint = graph.pointForVertex(vArray[j]); double[] delta = new double[] { point.getX() - otherPoint.getX(), point.getY() - otherPoint.getY() }; double D2 = delta[0] * delta[0] + delta[1] * delta[1]; double O2 = optimalEdgeLength * optimalEdgeLength; if (delta[0] != 0.0 || delta[1] != 0.0) { for (int k = 0; k < 2; k++) p[k] += delta[k] * O2 / D2; } if (!graph.hasEdge(vertex, vArray[j])) continue; for (int k = 0; k < 2; k++) p[k] -= delta[k] * D2 / (O2 * Theta); } // Adjust the position and temperature. if (p[0] != 0.0 || p[1] != 0.0) { double absp = Math.sqrt(Math.abs(p[0] * p[0] + p[1] * p[1])); for (int j = 0; j < 2; j++) p[j] *= record.temperature / absp; // update the position! graph.moveVertex(vertex, new Point2D.Double( point.getX() + p[0], point.getY() + p[1])); // update the barycenter c[0] += p[0]; c[1] += p[1]; } // Adjust the temperature. /* * if (record.lastImpulse[0] != 0.0 || record.lastImpulse[1] != 0.0) { * double beta = Math.atan2(p[0]-record.lastImpulse[0], * p[1]-record.lastImpulse[1]); if (Math.sin(beta) >= * Math.sin(Math.PI/2.0 + alphaR } */ // Paint the component... /* * if ((i+1) % (vArray.length - isovertices.size()) == 0) * component.paintImmediately(component.getBounds()); */ } //Finally, shift all points onto the screen. shiftOntoScreen(graph, size, vertexDim, true); } private static final Random RANDOM = new Random(); private Map records; private static final Set EMPTY_SET = new HashSet(); private static class Record { Point2D point = new Point2D.Double(); double[] lastImpulse = { 0.0, 0.0 }; double temperature = Tmin; double skew = 0.0; } private static final double Tmax = 256.0, Tmin = 3.0; private static final double OPTIMAL_EDGE_LENGTH = 100.0, GRAVITATIONAL_CONSTANT = 1.0 / 16.0; /* * private static final double alphaO */ }