/** * This helper method calculates the repulsive forces acting on a node from all the other nodes in * the graph. BIG O( number of nodes ) * * <p>There is a repulsive force between every nodes depending on the distance separating them and * their size. * * @param current node to calculate forces for. */ private void calculateNodeRepulsiveForces(Node current) { Iterator<Node> inner = controller.getNodeIterator(); int current_radius, n_radius, dx, dy; Node n; current_radius = (int) Math.sqrt(Math.pow(current.getWidth(), 2) + Math.pow(current.getHeight(), 2)); while (inner.hasNext()) { n = inner.next(); if (n != current) { dx = current.getX() - n.getX(); dy = current.getY() - n.getY(); n_radius = (int) Math.sqrt(Math.pow(n.getWidth(), 2) + Math.pow(n.getHeight(), 2)); // only repel if nodes are connected or within diameter * MAX_REPEL_DISTANCE // if (Math.sqrt(dx * dx + dy * dy) < (Math.max(current_radius, n_radius) * // MAX_REPEL_MULTIPLIER)) { double l = (dx * dx + dy * dy) + .1; if (l > 0) { // current.setVX(current.getVX() + dx * (current_radius * SPACING) / l); // current.setVY(current.getVY() + dy * (current_radius * SPACING) / l); current.accelx += (dx * REPULSION / (l)) / current.weight; current.accely += (dy * REPULSION / (l)) / current.weight; // current.accelx += (dx * SPACING / (l * .5)) / current.weight; // current.accely += (dy * SPACING / (l * .5)) / current.weight; // n.accelx += (dx * SPACING / (l * -0.5)) / n.weight; // n.accely += (dy * SPACING / (l * -0.5)) / n.weight; } // } } } }
/** * This helper calculates all the attractive forces onto a node. Attractive forces from from the * edges pulling nodes towards each other, this will love through the edges of this node. * * @param current Node to calculate forces for. */ private void calculateNodeAttractiveForce(Node current) { // get all the edges of the current node ArrayList<Edge> nodeEdgeList = current.getEdgeList(); // of each of this node's edge do attactive forces Iterator<Edge> nodeEdges = nodeEdgeList.iterator(); /// int numOfEdgesWeight = 10 * (int) Math.round(nodeEdgeList.size() + SPACING); // Loop through edges and find edges containing current node while (nodeEdges.hasNext()) { Edge e = nodeEdges.next(); double edgeStrength = e.getStrength(); Node n; int dx, dy; double sign = 1.0; if (current == e.getNode1()) { n = e.getNode2(); dx = current.getX() - n.getX(); dy = current.getY() - n.getY(); } else { n = e.getNode1(); dx = current.getX() - n.getX(); dy = current.getY() - n.getY(); sign = -1.0; } double distance = Math.sqrt(dx * dx + dy * dy) + .1; // multiply by the strength of edge // current.setVX(current.getVX() - dx / numOfEdgesWeight * edgeStrength); // current.setVY(current.getVY() - dy / numOfEdgesWeight * edgeStrength); current.accelx += sign * (dx * STIFFNESS * (SPRING_LENGTH - distance)) / current.weight; current.accely += sign * (dy * STIFFNESS * (SPRING_LENGTH - distance)) / current.weight; // current.accelx += sign * (dx * (e.getStrength() + 1) * (SPRING_LENGTH - distance) * 0.5) / // current.weight; // current.accely += sign * (dy * (e.getStrength() + 1) * (SPRING_LENGTH - distance) * 0.5) / // current.weight; // n.accelx += sign * (dx * (e.getStrength() + 1) * (SPRING_LENGTH - distance) * 0.5) / // n.weight; // n.accely += sign * (dy * (e.getStrength() + 1) * (SPRING_LENGTH - distance) * 0.5) / // n.weight; } }
/** * The graph was resize so the algorithm must be adapted to the new size. Then mark dirty. * * @param w new width * @param h new height */ private void setGraphSize(int w, int h) { int oldWidth = width; int oldHeight = height; double relativeWidth, relativeHeight; int newX, newY; width = w; height = h; // Move the pinned nodes to adjust to the new size ArrayList<Node> pinnedNodes = controller.getGraphPinned(); if (pinnedNodes != null) { for (Node n : pinnedNodes) { relativeWidth = (double) n.getX() / oldWidth; relativeHeight = (double) n.getY() / oldHeight; newX = (int) Math.round(width * relativeWidth); newY = (int) Math.round(height * relativeHeight); n.setX(newX); n.setY(newY); } } }
/** * Layout core logic. * * <p>This iterates over all the nodes and determines their velocities based on a force directed * layout approach, performs collision detection/avoidance and then ultimately their new positions * as they move. */ private void doLayout() { double maxVelocity = 100; double numOfIterations = 0; double step = 100; double skip = step; int counter = 1; boolean dividerChanged = false; // active = true; while (maxVelocity > 1 && active && (numOfIterations < MAX_NUM_OF_ITERATIONS)) { counter++; maxVelocity = 0; numOfIterations++; Iterator<Node> outer = controller.getNodeIterator(); startTime = System.currentTimeMillis(); double systemMovement = 0; double kineticEnergy = 0; /* Iterate over all nodes and calculate velocity */ while (outer.hasNext()) { Node current = outer.next(); current.resetVelocity(); current.resetAcceleration(); /* * If current node is not pinned or selected, * perform force directed layout calculations here. */ if (!((current == controller.getGraphSelected()) || current.isPinned())) { calculateNodeAttractiveForce(current); calculateNodeRepulsiveForces(current); // applyNodeFriction(current); // if (IS_COOLING) { // calculateNodeCooling(current, numOfIterations); // } // calculate velocities current.setVX(current.getVX() + current.accelx * TIMESTEP * DAMPING); current.setVY(current.getVY() + current.accely * TIMESTEP * DAMPING); double speedSquared = current.getVX() * current.getVX() + current.getVY() * current.getVY(); kineticEnergy += 0.5 * current.weight * speedSquared; // cap the velocity if (current.getVX() >= 0) { current.setVX(Math.min(current.getVX(), MAX_DIST_PER_MOVE)); } else { current.setVX(Math.max(current.getVX(), -MAX_DIST_PER_MOVE)); } if (current.getVY() >= 0) { current.setVY(Math.min(current.getVY(), MAX_DIST_PER_MOVE)); } else { current.setVY(Math.max(current.getVY(), -MAX_DIST_PER_MOVE)); } // System.out.println("vy "+ current.getVY() + ", vx " + current.getVX()); } } /* Iterate over all nodes move them after all the velocities are set */ outer = controller.getNodeIterator(); while (outer.hasNext()) { Node current = outer.next(); int xStart = current.getX(); int yStart = current.getY(); /* update node position */ int xEnd = current.getX() + (int) current.getVX(); int yEnd = current.getY() + (int) current.getVY(); int dX = Math.abs(xStart - xEnd); int dY = Math.abs(yStart - yEnd); // systemMovement += dX; // systemMovement += dY; // if(dX + dY > 6) { controller.moveNode(current, xEnd, yEnd); // } // controller.moveNode(current, Math.min(current.getX() + (int) // current.getVX(),MAX_DIST_PER_MOVE), Math.min(current.getY() + (int) current.getVY(), // MAX_DIST_PER_MOVE)); /* compute maxVelocity for layout iteration */ maxVelocity = Math.max( maxVelocity, Math.sqrt(Math.pow(current.getVX(), 2) + Math.pow(current.getVY(), 2))); } if (kineticEnergy < ENERGY_THRESHOLD) { numOfIterations = MAX_NUM_OF_ITERATIONS; } /* Make animation slower */ try { Thread.sleep(sleepInterval); } catch (InterruptedException ex) { } /* Calculate how long the iteration took */ stopTime = System.currentTimeMillis(); long duration = stopTime - startTime; if ((duration > MAX_ITERATION_TIME) && (sleepInterval > 5)) { sleepInterval -= 5; } else if (duration < MIN_ITERATION_TIME) { sleepInterval += 5; } if (numOfIterations > skip) { skip += step; System.out.print('.'); } } /* We've reached a stable layout, hold on for now */ stable = true; // System.out.println("ran in " + numOfIterations + " iterations."); }