package org.f2o.absurdum.puck.gui.graph; import java.awt.Point; import java.awt.geom.Point2D; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.Random; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.f2o.absurdum.puck.gui.panels.ArrowPanel; import org.f2o.absurdum.puck.gui.panels.GraphElementPanel; import org.f2o.absurdum.puck.gui.panels.PathPanel; import org.f2o.absurdum.puck.i18n.UIMessages; public class GraphArranger { private static double GRAVITATIONAL_CONSTANT = 0.005; private static int MAX_BUMP = 10; private static int IDEAL_PATH_LENGTH = 80; private static Random random = new Random(); private static double REPULSION_DISTANCE = 80; private static double MAX_ACCEL = 5.0; private void applyForce ( Node firstNode , Node secondNode , Point2D.Double tl1 , Point2D.Double tl2 , Point2D.Double center1 , Point2D.Double center2 , boolean repulse ) { double x1 = tl1.x; double y1 = tl1.y; double x2 = tl2.x; double y2 = tl2.y; double xCenter1 = center1.x; double yCenter1 = center1.y; double xCenter2 = center2.x; double yCenter2 = center2.y; //we assume constant density, mass proportional to volume int mass1 = firstNode.getBounds().height * firstNode.getBounds().width; int mass2 = secondNode.getBounds().height * secondNode.getBounds().width; if ( xCenter1 == yCenter1 && xCenter2 == yCenter2 ) //nodes in exactly the same position: we bump one of them (the second one) randomly { x2 += random.nextInt(MAX_BUMP*2+1)-MAX_BUMP; y2 += random.nextInt(MAX_BUMP*2+1)-MAX_BUMP; } else { System.err.println("TL: (" + x1 +"," + y1+") (" + x2 + "," + y2 + ")"); System.err.println("CN: (" + xCenter1 +"," + yCenter1+") (" + xCenter2 + "," + yCenter2 + ")"); //repulsion on x axis. It's not really acceleration, we don't keep speed (we assume huge drag) double xDistance = (double) Math.abs(xCenter1-xCenter2); if ( xDistance != 0.0 ) { double xRepulsionForce = (((double)mass1)*((double)mass2)*GRAVITATIONAL_CONSTANT)/(xDistance*xDistance); double xAccel1 = Math.max( xRepulsionForce / mass1 , MAX_ACCEL ); double xAccel2 = Math.max( xRepulsionForce / mass2 , MAX_ACCEL ); System.err.println("x-dist " + xDistance + ", rep. force " + xRepulsionForce); if (!repulse) { xAccel1 = -xAccel1; xAccel2 = -xAccel2; } //attract if ( xCenter1 > xCenter2 ) { x1 += xAccel1 ; x2 -= xAccel2; } else { x1 -= xAccel1 ; x2 += xAccel2; } } //repulsion on y axis. It's not really acceleration, we don't keep speed (we assume huge drag) double yDistance = (double) Math.abs(yCenter1-yCenter2); if ( yDistance != 0.0 ) { double yRepulsionForce = (((double)mass1)*((double)mass2)*GRAVITATIONAL_CONSTANT)/(yDistance*yDistance); double yAccel1 = Math.max( yRepulsionForce / mass1 , MAX_ACCEL ); double yAccel2 = Math.max( yRepulsionForce / mass2 , MAX_ACCEL ); System.err.println("y-dist " + yDistance + ", rep. force " + yRepulsionForce); if (!repulse) { yAccel1 = -yAccel1; yAccel2 = -yAccel2; } //attract if ( yCenter1 > yCenter2 ) { y1 += yAccel1 ; y2 -= yAccel2; } else { y1 -= yAccel1 ; y2 += yAccel2; } } } System.err.println("N1 Old: " + tl1.x + ", " + tl1.y + " -> New: " + x1 + ", " + y1 ); System.err.println("N2 Old: " + tl2.x + ", " + tl2.y + " -> New: " + x2 + ", " + y2 ); //update node position tl1.x = x1; tl2.x = x2; tl1.y = y1; tl2.y = y2; //update the center information center1.x = tl1.x + ((double)firstNode.getBounds().width/2); center2.x = tl2.x + ((double)secondNode.getBounds().width/2); center1.y = tl1.y + ((double)firstNode.getBounds().height/2); center2.y = tl2.y + ((double)secondNode.getBounds().height/2); } public void arrangeIter ( GraphEditingPanel gep , List nodes , List topLeftCoords , List centerCoords ) { //apply repulsion forces for ( int i = 0 ; i < nodes.size() ; i++ ) { for ( int j = i+1 ; j < nodes.size() ; j++ ) { //a pair of different nodes. There is a repulsion. Node firstNode = (Node) nodes.get(i); Node secondNode = (Node) nodes.get(j); Point2D.Double tl1 = (Point2D.Double) topLeftCoords.get(i); Point2D.Double tl2 = (Point2D.Double) topLeftCoords.get(j); Point2D.Double center1 = (Point2D.Double) centerCoords.get(i); Point2D.Double center2 = (Point2D.Double) centerCoords.get(j); double nodeDist = center1.distance(center2); if ( nodeDist < REPULSION_DISTANCE ) applyForce(firstNode,secondNode,tl1,tl2,center1,center2,true); else if ( nodeDist > 4 * REPULSION_DISTANCE ) //attract at large distances applyForce(firstNode,secondNode,tl1,tl2,center1,center2,false); } } //apply path direction forces for ( int i = 0 ; i < nodes.size() ; i++ ) { Node theNode = (Node) nodes.get(i); List arrows = theNode.getArrows(); for ( int j = 0 ; j < arrows.size() ; j++ ) { Arrow theArrow = (Arrow) arrows.get(j); GraphElementPanel panel = theArrow.getAssociatedPanel(); Node destinationNode = theArrow.getDestination(); if ( destinationNode != null ) { if ( panel instanceof PathPanel ) { PathPanel thePanel = (PathPanel) panel; String direction = thePanel.getDirectionString(); if ( direction != null ) { int destinationIndex = nodes.indexOf(destinationNode); if ( destinationIndex >= 0 ) { Point2D.Double destTl = (Point2D.Double) topLeftCoords.get(destinationIndex); Point2D.Double destCenter = (Point2D.Double) centerCoords.get(destinationIndex); Point2D.Double srcTl = (Point2D.Double) topLeftCoords.get(i); Point2D.Double srcCenter = (Point2D.Double) centerCoords.get(i); applyPathDirectionForces ( theNode , destinationNode , srcTl , destTl , srcCenter , destCenter , direction ); } } } else { int destinationIndex = nodes.indexOf(destinationNode); if ( destinationIndex >= 0 ) { Point2D.Double destTl = (Point2D.Double) topLeftCoords.get(destinationIndex); Point2D.Double destCenter = (Point2D.Double) centerCoords.get(destinationIndex); Point2D.Double srcTl = (Point2D.Double) topLeftCoords.get(i); Point2D.Double srcCenter = (Point2D.Double) centerCoords.get(i); double nodeDist = srcTl.distance(destTl); if ( nodeDist > 2*REPULSION_DISTANCE ) //attract connected nodes somewhat //nah, not good result ;// applyForce(theNode,destinationNode,srcTl,destTl,srcCenter,destCenter,false); } //if destinationIndex >=0 } //i not instance of path panel } //if destination node not null } //for all arrows } //for all nodes //actually move nodes to the new coordinates for ( int i = 0 ; i < nodes.size() ; i++ ) { Node node = (Node) nodes.get(i); Point2D.Double topLeft = (Point2D.Double) topLeftCoords.get(i); node.setLocation( (int)Math.round(topLeft.x) , (int)Math.round(topLeft.y) ); } } private void applyPathDirectionForces ( Node source , Node destination , Point2D.Double sourceTopLeft , Point2D.Double destinationTopLeft , Point2D.Double sourceCenter , Point2D.Double destinationCenter , String directionString ) { //destination node coordinates double x = destinationTopLeft.x; double y = destinationTopLeft.y; //destination node center double xCenter = destinationCenter.x; double yCenter = destinationCenter.y; double mass = destination.getBounds().height * destination.getBounds().width; //coordinates where the destination node will be attracted double xIdealDestination; double yIdealDestination; if ( directionString.equals(UIMessages.getInstance().getMessage("dir.n")) ) { xIdealDestination = sourceCenter.x; yIdealDestination = sourceCenter.y - IDEAL_PATH_LENGTH; } else if ( directionString.equals(UIMessages.getInstance().getMessage("dir.s")) ) { xIdealDestination = sourceCenter.x; yIdealDestination = sourceCenter.y + IDEAL_PATH_LENGTH; } else if ( directionString.equals(UIMessages.getInstance().getMessage("dir.w")) ) { xIdealDestination = sourceCenter.x - IDEAL_PATH_LENGTH; yIdealDestination = sourceCenter.y; } else if ( directionString.equals(UIMessages.getInstance().getMessage("dir.e")) ) { xIdealDestination = sourceCenter.x + IDEAL_PATH_LENGTH; yIdealDestination = sourceCenter.y; } else return; //attraction on x axis. It's not really acceleration, we don't keep speed (we assume huge draft) double xDistance = (double) Math.abs(xCenter-xIdealDestination); if ( xDistance != 0.0 ) { double xAttractionForce = (((double)mass)*((double)mass)*GRAVITATIONAL_CONSTANT)/(xDistance*xDistance); double xAccel = Math.max( xAttractionForce / mass , MAX_ACCEL ); if ( xCenter < xIdealDestination ) { x += xAccel; } else { x -= xAccel; } } //attraction on y axis. It's not really acceleration, we don't keep speed (we assume huge draft) double yDistance = (double) Math.abs(yCenter-yIdealDestination); if ( yDistance != 0.0 ) { double yAttractionForce = (((double)mass)*((double)mass)*GRAVITATIONAL_CONSTANT)/(yDistance*yDistance); double yAccel = Math.max( yAttractionForce / mass , MAX_ACCEL ); if ( yCenter < yIdealDestination ) { y += yAccel; } else { y -= yAccel; } } //update node position destinationTopLeft.x = x; destinationTopLeft.y = y; //update the center information destinationCenter.x = destinationTopLeft.x + ((double)destination.getBounds().width/2); destinationCenter.y = destinationTopLeft.y + ((double)destination.getBounds().height/2); } public void arrange ( final GraphEditingPanel gep , int iters ) { List nodes = gep.getNodes(); List topLeftCoords = new ArrayList(); //node coordinates as double List centerCoords = new ArrayList(); //node coordinates as double //fill coordinate list for ( int i = 0 ; i < nodes.size() ; i++ ) { Node node = (Node) nodes.get(i); Point2D.Double topLeft = new Point2D.Double( node.getBounds().x , node.getBounds().y ); Point2D.Double center = new Point2D.Double( node.getBounds().getCenterX() , node.getBounds().getCenterY() ); topLeftCoords.add(topLeft); centerCoords.add(center); } for ( int i = 0 ; i < iters ; i++ ) { arrangeIter(gep , nodes , topLeftCoords , centerCoords); /* try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { */ gep.repaint(); //if ( i % 50 == 0 ) JOptionPane.showConfirmDialog(gep, "UH UH MOOVAN"); /* } } ); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } */ } } }