Package org.gvt.layout

Source Code of org.gvt.layout.CoSELayout

package org.gvt.layout;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import org.eclipse.draw2d.geometry.*;
import org.gvt.model.CompoundModel;
import org.gvt.model.EdgeBendpoint;
import org.gvt.model.NodeModel;
import org.gvt.util.ChsGeometry;
import org.gvt.util.ChsTransform;

/**
* This class implements the overall layout process for the CoSE algorithm
* (Compound Spring Embedder by Dogrusoz et al).
*
* @author Ugur Dogrusoz
* @author Erhan Giral
* @author Cihan Kucukkececi
*
* Copyright: i-Vis Research Group, Bilkent University, 2007 - present
*/
public class CoSELayout extends AbstractLayout
{
// -----------------------------------------------------------------------------
// Section: Instance Variables
// -----------------------------------------------------------------------------
  /**
   * Ideal length of an edge
   */
  protected double idealEdgeLength = DEFAULT_EDGE_LENGTH;

  /**
   * Coefficient of ideal edge length, varies from phase to phase
   */
  private double idealEdgeLengthCoefficient;

  /**
   * Constant for calculating spring forces
   */
  protected double springConstant = DEFAULT_SPRING_STRENGTH;

  /**
   * Maximum distance at which a node may repulse another node
   */
  protected double repulsionRange;

  /**
   * Constant for calculating repulsion forces
   */
  protected double repulsionConstant = DEFAULT_REPULSION_STRENGTH;

  /**
   * Accuracy of layout iterations; higher accuracy means smaller time steps.
   */
  protected double accuracy;

  /**
   *
   * Constant for calculating gravitation forces
   */
  protected double gravityConstant = DEFAULT_GRAVITY_STRENGTH;

  /**
   * Gravitational constant for compound nodes
   */
  protected double compoundGravityConstant =
    DEFAULT_COMPOUND_GRAVITY_STRENGTH;

  /**
   * Maximum number of layout iterations allowed
   */
  protected int maxIteration = 4000;

  /**
   * Total number of iterations currently performed
   */
  protected int globalIteration;

  /**
   * Number of layout iterations that has not been animated (rendered)
   */
  protected int notAnimatedIterations;

  /**
   * Maximum iterations that each phase can perform
   */
  public int maxIterationsPhase1;
  public int maxIterationsPhase2;
  public int maxIterationsPhase3;

  /**
   * Threshold for convergence (calculated according to graph to be laid out)
   */
  public double totalDisplacementThreshold = 0;

  /**
   * Threshold for convergence per node
   */
  public double displacementThresholdPerNode = 0.5;

  /**
   * Current phase of the layout process
   */
  private int phase;

  /**
   * Tree reduction related variables; subgraphs that are trees are reduced
   * for efficiency purposes before layout starts and introduced back
   * gradually later on.
   */
  private Vector reducedTreeRoots;
  private Vector reducedNodes;
  private Vector deReducedNodes;
  private int noOfReducedNodes;

// -----------------------------------------------------------------------------
// Section: Constructors and Initializations
// -----------------------------------------------------------------------------
  /**
   * Passes parameters to super
   */
  public CoSELayout(CompoundModel rootModel)
  {
    super(rootModel);
  }

  /**
   * Passes parameters to super
   */
  public CoSELayout(List nodes, List edges)
  {
    super(nodes,edges);
  }

  public LNode createNewLNode(LGraphManager gm, NodeModel model)
  {
    return new CoSENode(gm);
  }

  public LNode createNewLNode(LGraphManager gm,
    NodeModel model,
    Point loc,
    Dimension size)
  {
    return new CoSENode(gm, loc, size);
  }

  public LEdge createNewLEdge(LNode source, LNode target)
  {
    return new CoSEEdge((CoSENode)source, (CoSENode)target);
  }

  public LGraph createNewLGraph(LNode rootNode, LGraphManager lGraphManager)
  {
    return new CoSEGraph((CoSENode)rootNode, lGraphManager);
  }

  public void initialize()
  {
    super.initialize();
    this.reducedTreeRoots = new Vector();
    this.reducedNodes = new Vector();
    this.deReducedNodes = new Vector();
  }

  /**
   * This method is used to set all layout parameters to default values.
   */
  public void initParameters()
  {
    super.initParameters();

    if (this.fromChisioModel)
    {
      LayoutOptionsPack.CoSE layoutOptionsPack =
        LayoutOptionsPack.getInstance().getCoSE();

      if (layoutOptionsPack.getIdealEdgeLength() < 10)
      {
        this.idealEdgeLength = DEFAULT_EDGE_LENGTH;
      }
      else
      {
        this.idealEdgeLength = layoutOptionsPack.getIdealEdgeLength();
      }

      this.springConstant =
        transform(layoutOptionsPack.getSpringStrength(),
          DEFAULT_SPRING_STRENGTH);
      this.repulsionConstant =
        transform(layoutOptionsPack.getRepulsionStrength(),
          DEFAULT_REPULSION_STRENGTH);
      this.gravityConstant =
        transform(layoutOptionsPack.getGravityStrength(),
          DEFAULT_GRAVITY_STRENGTH);
      this.compoundGravityConstant =
        transform(layoutOptionsPack.getCompoundGravityStrength(),
          DEFAULT_COMPOUND_GRAVITY_STRENGTH);
    }

    this.accuracy = 20;
    this.repulsionRange = this.idealEdgeLength * 5;

    if (this.layoutQuality == DEFAULT_QUALITY)
    {
      this.repulsionRange = this.idealEdgeLength * 5;
    }
    else if (this.layoutQuality == DRAFT_QUALITY)
    {
      this.repulsionRange = this.idealEdgeLength * 4;
      this.displacementThresholdPerNode += 0.30;
      this.maxIteration -= 2000;
    }
    else
    {
      this.repulsionRange = this.idealEdgeLength * 6;
      this.displacementThresholdPerNode -= 0.30;
      this.maxIteration += 2000;
    }

    this.globalIteration = 0;
    this.notAnimatedIterations = 0;
    this.maxIterationsPhase1 = this.maxIteration / 3;
    this.maxIterationsPhase2 =
      this.maxIterationsPhase3 = this.maxIteration;

    if (this.animationDuringLayout)
    {
      animationOnLayout = false;
    }

    CoSEGraph.setGraphMargin(CompoundModel.MARGIN_SIZE);
  }

  /**
   * This method estimates the initial node sizes during non-incremental
   * layout. A compound node can contain arbitrary number of nodes with an
   * arbitrary nesting hierarchy. Therefore size of a compound node is of
   * great importance as it defines the space in which its child graph should
   * be laid out in.
   */
  private void estimateInitialNodeSizes()
  {
    assert !this.incremental;
    this.lGraphManager.getRoot().getParent().getEstimatedSize();
  }

// -----------------------------------------------------------------------------
// Section: Layout!
// -----------------------------------------------------------------------------
  /**
   * This method performs layout on constructed l-level compound graph. If
   * "interactive" flag is false, then layout is assumed to be called for
   * test purposes.
   */
  public void layout()
  {
    boolean createBendsAsNeeded = LayoutOptionsPack.getInstance().
      getGeneral().isCreateBendsAsNeeded();

    if (createBendsAsNeeded)
    {
      this.createBendpoints();
    }

    if (!this.incremental)
    {
      ArrayList<ArrayList<LNode>> forest = this.getFlatForest();

      if (forest.size() > 0)
      // The graph associated with this layout is flat and a forest
      {
        this.positionNodesRadially(forest);
      }
      else
      // The graph associated with this layout is not flat or a forest
      {
        this.reduceTrees();
        this.estimateInitialNodeSizes();
        this.positionNodesRandomly();
      }
    }

    this.moveTowardsScreenCenter();
    this.doLayoutPhases();

    System.out.println("CoSE layout finished after " +
      this.globalIteration + " iterations");
  }

  /**
   * This method performs the actual layout on the l-level compound graph. An
   * update() needs to be called for changes to be propogated to the v-level
   * compound graph.
   */
  public void doLayoutPhases()
  {
    if (!this.incremental)
    {
      this.doPhase1();
      this.doPhase2();
      this.doPhase3();
    }
    else
    {
      this.doPhase3();
    }
  }

  /**
   * This is the first phase of our layout algorithm in which the skeleton of
   * the graph is laid out. This phase consists of two minor phases. These
   * minor phases share the total number of iterations reserved for this phase
   * equally. First phase applies only the spring forces on nodes, the second
   * phase applies both spring and repulsion forces.
   */
  private void doPhase1()
  {
    this.phase = PHASE_1;
    coolingFactor = 1.0;
    totalDisplacement = 0;
    this.globalIteration = 0;
    int iteration = 0;

    // Minor Phase 1, iterate over all nodes as long as max iters not hit
    this.idealEdgeLengthCoefficient =
      IDEAL_EDGE_LENGTH_COEFFICIENT_PHASE1_MINOR1;

    while (iteration < this.maxIterationsPhase1 / 2)
    {
      totalDisplacement = 0;

      this.applySpringForces();

      this.moveAllNodes();

      iteration++;
      this.globalIteration++;

      if (iteration % CONVERGENCE_CHECK_PERIOD == 0)
      {
        if (this.isConverged())
        {
          break;
        }
      }

      this.animate();
    }

    // Minor Phase 2
    iteration = 0;
    totalDisplacement = 0;
    this.idealEdgeLengthCoefficient =
      IDEAL_EDGE_LENGTH_COEFFICIENT_PHASE1_MINOR2;

    while (iteration < this.maxIterationsPhase1 / 2)
    {
      totalDisplacement = 0;

      this.applySpringForces();
      this.applyRepulsionForces();
      this.applyGravitationalForces();

      this.moveAllNodes();

      iteration++;
      this.globalIteration++;

      if (iteration % CONVERGENCE_CHECK_PERIOD == 0)
      {
        if (this.isConverged())
        {
          break;
        }
      }

      this.animate();
    }
  }

  /**
   * This is the phase where we grow trees back into our compound graph
   * gradually.
   */
  private void doPhase2()
  {
    this.phase = PHASE_2;
    coolingFactor = 1.0;
    this.idealEdgeLengthCoefficient =
      IDEAL_EDGE_LENGTH_COEFFICIENT_PHASE2;
    this.repulsionRange = this.repulsionRange * 0.85;

    this.growTrees();
  }

  /**
   * This is the phase for polishing layout.
   */
  private void doPhase3()
  {
    this.phase = PHASE_3;
    coolingFactor = 1.0;
    this.idealEdgeLengthCoefficient =
      IDEAL_EDGE_LENGTH_COEFFICIENT_PHASE3;
    this.repulsionRange = this.repulsionRange * 0.85;
    int iteration = 0;
    totalDisplacement = 1000;

    while (iteration < this.maxIterationsPhase3)
    {
      iteration++;
      this.globalIteration++;

      if (iteration % CONVERGENCE_CHECK_PERIOD == 0)
      {
        if (this.isConverged())
        {
          break;
        }

        // cool down is applied during this last phase only!
        coolingFactor = (this.maxIterationsPhase3 -
          iteration) / (double)this.maxIterationsPhase3;
      }

      totalDisplacement = 0;

      this.applySpringForces();
      this.applyRepulsionForces();
      this.applyGravitationalForces();
      this.moveAllNodes();

      this.animate();
    }
  }

  /**
   * The tree reduction operation identifies the *skeleton* of the target view
   * graph. The word skeleton means the strongly constrained nodes that are on
   * a cycle and/or end vertex of an inter-compartmental edge plus nodes that
   * are connecting such connected subgraphs. The skeleton of the graph will
   * be the exclusive target of the remaining operations in initialization and
   * first phase of layout.
   */
  private void reduceTrees()
  {
    this.reduceTrees(this.lGraphManager.getNodeList());

    LinkedList linkedList = new LinkedList();
    Iterator iterator = this.reducedNodes.iterator();
    CoSENode lNode;
    Object vNode;

    while (iterator.hasNext())
    {
      lNode = (CoSENode) iterator.next();
      vNode = this.viewMapLtoV.get(lNode);

      if (!linkedList.contains(vNode))
      {
        linkedList.add(vNode);
      }
    }

    this.animate();
  }

  /**
   * This method reduces the trees that are subgraphs of the graph defined by
   * the given list of nodes as described earlier.
   */
  private void reduceTrees(List nodeList)
  {
    this.noOfReducedNodes = 0;
    Iterator itr = nodeList.iterator();

    while (itr.hasNext())
    {
      CoSENode currentNode = (CoSENode) itr.next();

      if (currentNode.isReduced())
      {
        continue;
      }
      else if (currentNode.edges.size() == 0)
      // isolated node
      {
        // non-compound leaf node
        if (currentNode.getChild() == null &&
          currentNode.getOwner() == this.lGraphManager.getRoot())
        {
          this.reducedNodes.add(currentNode);
          currentNode.setReduced(true);
          this.noOfReducedNodes++;
          this.reducedTreeRoots.add(currentNode);
          currentNode.reducedTreeRoot = true;
        }
      }
      else if (currentNode.getNumberOFUnmarkedNeigbors() == 1)
      {
        do
        {
          if (currentNode.isReduced())
          {
            break;
          }

          // compound node or a part of a compound node
          if (currentNode.getChild() != null ||
            currentNode.getOwner() != this.lGraphManager.getRoot())
          {
            break;
          }

          this.reducedNodes.add(currentNode);
          currentNode.setReduced(true);
          this.noOfReducedNodes++;

          if (!this.reducedTreeRoots.contains(currentNode))
          {
            this.reducedTreeRoots.add(currentNode);
            currentNode.reducedTreeRoot = true;
          }

          Iterator edgeItr;
          CoSEEdge lEdge;
          CoSENode previousNode;
          edgeItr = currentNode.edges.iterator();

          while (edgeItr.hasNext())
          {
            lEdge = (CoSEEdge) edgeItr.next();

            if (!((CoSENode)lEdge.getOtherEnd(currentNode)).isReduced())
            {
              previousNode = currentNode;
              currentNode = (CoSENode) lEdge.getOtherEnd(currentNode);

              // remove all occurences
              while (this.reducedTreeRoots.contains(previousNode))
              {
                this.reducedTreeRoots.remove(previousNode);
                previousNode.reducedTreeRoot = false;
              }

              this.reducedTreeRoots.add(currentNode);
              currentNode.reducedTreeRoot = true;

              lEdge.reduced = true;
              break;
            }
          }

          if (currentNode.getNumberOFUnmarkedNeigbors() > 1)
          {
            break;
          }
        }
        while (true);
      }
      else // node cannot start a reduction
      {
        continue;
      }
    }
  }

  /**
   * This method gradually (level by level) grows the reduced trees back into
   * the compound graph while performing layout.
   */
  private void growTrees()
  {
    int iteration = 0;
    Vector tmp = new Vector();
    tmp.addAll(this.reducedTreeRoots);
    Vector evenLevels = tmp;
    Vector oddLevels = new Vector();

    while (!evenLevels.isEmpty())
    {
      while (!evenLevels.isEmpty())
      {
        CoSENode node = (CoSENode) evenLevels.remove(0);
        Iterator iter = node.edges.iterator();
        node.setReduced(false);
        this.noOfReducedNodes--;

        if (!this.deReducedNodes.contains(node))
        {
          this.deReducedNodes.add(node);
        }

        while (iter.hasNext())
        {
          CoSEEdge edge = (CoSEEdge) iter.next();

          if (edge.reduced)
          {
            edge.reduced = false;
            edge.growedFrom = node;
            CoSENode newNode = (CoSENode) edge.getOtherEnd(node);
            newNode.setReduced(false);
            this.noOfReducedNodes--;

            // Unhide

            LinkedList linkedList = new LinkedList();
            linkedList.add(this.viewMapLtoV.get(newNode));

            newNode.migrateTo(node);
            oddLevels.add(newNode);
          }
        }
      }

      while (iteration < TREE_GROWING_FREQUENCY)
      {
        this.applySpringForces();
        this.applyRepulsionForces();
        this.applyGravitationalForces();
        this.moveAllNodes();

        iteration++;
        this.animate();
      }

      this.globalIteration += iteration;
      iteration = 0;

      this.animate();

      while (!oddLevels.isEmpty())
      {
        CoSENode node = (CoSENode) oddLevels.remove(0);
        Iterator iter = node.edges.iterator();
        node.setReduced(false);
        this.noOfReducedNodes--;

        if (!this.deReducedNodes.contains(node))
        {
          this.deReducedNodes.add(node);
        }

        while (iter.hasNext())
        {
          CoSEEdge edge = (CoSEEdge) iter.next();
          if (edge.reduced)
          {
            edge.reduced = false;
            edge.growedFrom = node;
            CoSENode newNode = (CoSENode) edge.getOtherEnd(node);
            newNode.setReduced(false);
            this.noOfReducedNodes--;

            // Unhide

            LinkedList linkedList = new LinkedList();
            linkedList.add(this.viewMapLtoV.get(newNode));

            newNode.migrateTo(node);
            evenLevels.add(newNode);
          }
        }
      }

      while (iteration < TREE_GROWING_FREQUENCY)
      {
        this.applySpringForces();
        this.applyRepulsionForces();
        this.applyGravitationalForces();
        this.moveAllNodes();

        iteration++;

        this.animate();
      }

      this.globalIteration += iteration;

      this.animate();
    }
  }

  /**
   * This method applies the forces caused by the edges of the nodes.
   */
  private void applySpringForces()
  {
    // Calculated spring force and its components
    double springForce;
    double springForceX, springForceY;

    // Will be modified for inter-graph edges properly
    double idealLength;

    // Go over all edges and calculate spring forces for end nodes
    Object[] lEdges = this.getEdges();

    for (int i = 0; i < lEdges.length; i++)
    {
      CoSEEdge edge = (CoSEEdge) lEdges[i];

      // Ignore reduced edges
      if (edge.reduced)
      {
        continue;
      }

      CoSENode targetNode = (CoSENode) edge.target;
      CoSENode sourceNode = (CoSENode) edge.source;

      // Apply the ideal edge length coefficient
      idealLength = this.idealEdgeLength *
        this.idealEdgeLengthCoefficient * edge.getIdealLengthModifier();

      // Recalculate and initialize forces and length
      edge.recalculate();

      // Accuracy is used for time step accuracy. A higher accuracy
      // value means smaller incremental steps throughout the algorithm.
      springForce = this.springConstant *
        Math.abs(idealLength - edge.length) / this.accuracy;
//      springForce = this.springConstant *
//         Math.pow(idealLength - edge.length, 2) / (150 * this.accuracy);

      if (springForce > MAX_SPRING_FORCE)
      {
        springForce = MAX_SPRING_FORCE;
      }

      if (springForce < MIN_SPRING_FORCE)
      {
        springForce = MIN_SPRING_FORCE;
      }

      // Correct direction of force if not correct (edge is streched).
      if (idealLength < edge.length)
      {
        springForce = -springForce;
      }

      if (edge.overlapingTargetAndSource)
      {
        springForce = MAX_SPRING_FORCE;
        edge.overlapingTargetAndSource = false;
      }

      // Project force onto x and y axes, then apply forces on the nodes
      springForceX = springForce * (edge.lengthx / edge.length);
      springForceY = springForce * (edge.lengthy / edge.length);

      // Increment appropriate force components
      targetNode.springForceX += springForceX;
      targetNode.springForceY += springForceY;
      sourceNode.springForceX -= springForceX;
      sourceNode.springForceY -= springForceY;

      if (edge.isInterGraph)
      {
        if (targetNode.getOwner() != this.lGraphManager.getRoot())
        {
          ((CoSENode)targetNode.getOwner().getParent()).
            propogateIGEFtoParents(springForceX, springForceY);
        }

        if (sourceNode.getOwner() != this.lGraphManager.getRoot())
        {
          ((CoSENode)sourceNode.getOwner().getParent()).
            propogateIGEFtoParents(-springForceX, -springForceY);
        }
      }
    }
  }

  /**
   * This method applies gravitational forces on each node.
   */
  private void applyGravitationalForces()
  {
    CoSENode node;
    double ownerCenterX;
    double ownerCenterY;
    double distanceX;
    double distanceY;
    Object[] lNodes = this.getNodes();

    for (int i = 0; i < lNodes.length; i++)
    {
      node = (CoSENode) lNodes[i];

      assert node.gravitationForceX == 0 && node.gravitationForceY == 0;

      if (node.isReduced())
      {
        continue;
      }

      if (node.getOwner() == this.lGraphManager.getRoot())
      // in the root graph
      {
        distanceX = GRAVITY_CENTER_X - node.getCenterX();
        distanceY = GRAVITY_CENTER_Y - node.getCenterY();

        node.gravitationForceX = this.gravityConstant * distanceX;
        node.gravitationForceY = this.gravityConstant * distanceY;
      }
      else
      // inside a compound
      {
        ownerCenterX =
          (node.getOwner().getRight() + node.getOwner().getLeft()) / 2;
        ownerCenterY =
          (node.getOwner().getTop() + node.getOwner().getBottom()) / 2;
        distanceX = ownerCenterX - node.getCenterX();
        distanceY = ownerCenterY - node.getCenterY();

        node.gravitationForceX = this.compoundGravityConstant *
          this.gravityConstant * distanceX;
        node.gravitationForceY = this.compoundGravityConstant *
          this.gravityConstant * distanceY;
      }
    }
  }

  /**
   * This method applies node-to-node repulsion forces on the nodes.
   */
  private void applyRepulsionForces()
  {
    // Calculate repulsion forces for each node pair

    int i, j;
    CoSENode nodeA, nodeB;
    boolean overlap;
    double distanceX;
    double distanceY;
    double distanceSquared;
    double distance;
    double repulsionForce;
    double repulsionForceX;
    double repulsionForceY;
    double clipPointAx;
    double clipPointAy;
    double clipPointBx;
    double clipPointBy;
    Object[] lNodes = this.getNodes();

    for (i = 0; i < lNodes.length; i++)
    {
      nodeA = (CoSENode) lNodes[i];

      if (nodeA.isReduced())
      {
        continue;
      }

      for (j = i + 1; j < lNodes.length; j++)
      {
        nodeB = (CoSENode) lNodes[j];

        if (nodeB.isReduced())
        {
          continue;
        }

        // If both nodes are not members of the same graph, skip.
        if (nodeA.getOwner() != nodeB.getOwner())
        {
          continue;
        }

        clipPointAx = Double.MAX_VALUE;
        clipPointAy = Double.MAX_VALUE;
        clipPointBx = Double.MAX_VALUE;
        clipPointBy = Double.MAX_VALUE;

        overlap = false;

        Point cp =
          ChsGeometry.getIntersection(nodeA.getRect(),
            nodeB.getCenterX(), nodeB.getCenterY());       

        if (cp != null)
        {
          clipPointAx = cp.x;
          clipPointAy = cp.y;
        }

        cp =
          ChsGeometry.getIntersection(nodeB.getRect(),
            nodeA.getCenterX(), nodeA.getCenterY());

        if (cp != null)
        {
          clipPointBx = cp.x;
          clipPointBy = cp.y;
        }

        if (clipPointAx == Double.MAX_VALUE ||
          clipPointBx == Double.MAX_VALUE)
        {
          clipPointAx = nodeA.getCenterX();
          clipPointAy = nodeA.getCenterY();
          clipPointBx = nodeB.getCenterX();
          clipPointBy = nodeB.getCenterY();
          overlap = true;
        }

        if (nodeA.getRect().intersects(nodeB.getRect()))
        {
          clipPointAx = nodeA.getCenterX();
          clipPointAy = nodeA.getCenterY();
          clipPointBx = nodeB.getCenterX();
          clipPointBy = nodeB.getCenterY();
          overlap = true;
        }

        distanceX = clipPointBx - clipPointAx;
        distanceY = clipPointBy - clipPointAy;

        // Avoid division by 0

        if (distanceX == 0)
        {
          distanceX = Double.MIN_VALUE;
        }

        if (distanceY == 0)
        {
          distanceY = Double.MIN_VALUE;
        }

        // Repulsion forces depend on the square of the distance
        distanceSquared = distanceX * distanceX + distanceY * distanceY;

        repulsionForceX = 0.0;
        repulsionForceY = 0.0;

        // If we are too close, then we dont really want to calculate
        // the forces, since they are going to be huge!

        if (distanceSquared <= 1.0)
        {
          repulsionForceX = MAX_REPULSION_FORCE * sign(distanceX);
          repulsionForceY = MAX_REPULSION_FORCE * sign(distanceY);
        }
        else if (overlap)
        {
          distance = Math.sqrt(distanceSquared);
          repulsionForce = MAX_REPULSION_FORCE;
          repulsionForceX = repulsionForce * distanceX / distance;
          repulsionForceY = repulsionForce * distanceY / distance;
        }
        else if ((Math.abs(distanceX) <
            this.repulsionRange + (nodeA.rect.width + nodeB.rect.width) / 2 &&
          Math.abs(distanceY) <
            this.repulsionRange + (nodeA.rect.height + nodeB.rect.height) / 2))
        {
          distance = Math.sqrt(distanceSquared);

          if (this.phase == PHASE_3)
          {
            repulsionForce = 80 * this.repulsionConstant /
              (this.accuracy * distanceSquared);
          }
          else
          {
            repulsionForce = this.repulsionConstant /
              (this.accuracy * distance);
          }

          // Still too big of a force?
          if (repulsionForce > MAX_REPULSION_FORCE)
          {
            repulsionForce = MAX_REPULSION_FORCE;
          }

          repulsionForceX = repulsionForce * distanceX / distance;
          repulsionForceY = repulsionForce * distanceY / distance;
        }

        nodeA.repulsionForceX -= repulsionForceX;
        nodeA.repulsionForceY -= repulsionForceY;
        nodeB.repulsionForceX += repulsionForceX;
        nodeB.repulsionForceY += repulsionForceY;
      }
    }
  }

  /**
   * This method updates positions of each node at the end of an iteration.
   */
  private void moveAllNodes()
  {
    Object[] lNodes = this.getNodes();
    CoSENode node;

    for (int i = 0; i < lNodes.length; i++)
    {
      node = (CoSENode) lNodes[i];
      node.move();
    }
  }

  /**
   * This method inspects whether the graph has reached to a minima, based on
   * the phase that the layout manager is currently in. It returns true if
   * the layout seems to be oscillating as well.
   */
  private boolean isConverged()
  {
    boolean converged;

    if (this.phase == PHASE_1)
    {
      this.totalDisplacementThreshold =
        this.displacementThresholdPerNode *
          (this.getNodes().length - this.noOfReducedNodes);
    }
    else if (this.phase == PHASE_2)
    {
      // need to allow all reduced nodes to be grown!
      this.totalDisplacementThreshold = 0.0;
    }
    else if (this.phase == PHASE_3)
    {
      this.totalDisplacementThreshold =
        this.displacementThresholdPerNode * this.getNodes().length;
    }

    boolean oscilatting = false;

    if (this.globalIteration > this.maxIteration / 3)
    {
      oscilatting = Math.abs(totalDisplacement -
        oldTotalDisplacement) < 2;
    }

    converged = totalDisplacement <
      this.totalDisplacementThreshold;

    oldTotalDisplacement = totalDisplacement;

    return converged || oscilatting;
  }

  /**
   * This method updates the v-level compound graph coordinates and refresh
   * the display if corresponding flag is on.
   */
  private void animate()
  {
    if (this.animationDuringLayout && this.fromChisioModel)
    {
      if (this.notAnimatedIterations == this.animationPeriod)
      {
        this.update();

        this.notAnimatedIterations = 0;
      }
      else
      {
        this.notAnimatedIterations++;
      }
    }
  }

  private void createBendpoints()
  {
    List<LEdge> edges = new ArrayList(this.lGraphManager.getEdgeList());
    Set visited = new HashSet();

    for (int i = 0; i < edges.size(); i++)
    {
      LEdge edge = edges.get(i);

      if (!visited.contains(edge))
      {
        LNode source = edge.source;
        LNode target = edge.target;

        if (source == target)
        {
          edge.bendpoints.add(new EdgeBendpoint());
          edge.bendpoints.add(new EdgeBendpoint());
          this.createDummyNodesForBendpoints(edge);
          visited.add(edge);
        }
        else
        {
          List edgeList = source.getEdgeListToNode(target);
          edgeList.addAll(target.getEdgeListToNode(source));

          if (!visited.contains(edgeList.get(0)))
          {
            if (edgeList.size() > 1)
            {
              for(int k = 0; k < edgeList.size(); k++)
              {
                LEdge multiEdge = (LEdge)edgeList.get(k);
                multiEdge.bendpoints.add(new EdgeBendpoint());
                this.createDummyNodesForBendpoints(multiEdge);
              }
            }

            visited.addAll(edgeList);
          }
        }
      }

      if (visited.size() == edges.size())
      {
        break;
      }
    }
  }

  /**
   * This method performs initial positioning of given forest radially. The
   * final drawing should be centered at the gravitational center.
   */
  protected void positionNodesRadially(ArrayList<ArrayList<LNode>> forest)
  {
    // We tile the trees to a grid row by row; first tree starts at (0,0)
    Point currentStartingPoint = new Point(0, 0);
    int numberOfColumns = (int) Math.ceil(Math.sqrt(forest.size()));
    int height = 0;
    int currentY = 0;
    int currentX = 0;
    Point point = new Point(0, 0);

    for (int i = 0; i < forest.size(); i++)
    {
      if (i % numberOfColumns == 0)
      {
        // Start of a new row, make the x coordinate 0, increment the
        // y coordinate with the max height of the previous row
        currentX = 0;
        currentY = height;

        if (i !=0)
        {
          currentY += DEFAULT_COMPONENT_SEPERATION;
        }

        height = 0;
      }

      ArrayList<LNode> tree = forest.get(i);

      // Find the center of the tree
      LNode centerNode = AbstractLayout.findCenterOfTree(tree);

      // Set the staring point of the next tree
      currentStartingPoint.x = currentX;
      currentStartingPoint.y = currentY;

      // Do a radial layout starting with the center
      point =
        CoSELayout.radialLayout(tree, centerNode, currentStartingPoint);

      if (point.y > height)
      {
        height = point.y;
      }

      currentX = point.x + DEFAULT_COMPONENT_SEPERATION;
    }

    this.transform(
      new PrecisionPoint(GRAVITY_CENTER_X - point.x / 2,
        GRAVITY_CENTER_Y - point.y / 2));
  }

  /**
   * This method positions given nodes according to a simple radial layout
   * starting from the center node. The top-left of the final drawing is to be
   * at given location. It returns the bottom-right of the bounding rectangle
   * of the resulting tree drawing.
   */
  private static Point radialLayout(ArrayList<LNode> tree,
    LNode centerNode,
    Point startingPoint)
  {
    double radialSep = Math.max(maxDiagonalInTree(tree),
      CoSELayout.DEFAULT_RADIAL_SEPARATION);
    CoSELayout.branchRadialLayout(centerNode, null, 0, 359, 0, radialSep);
    Rectangle bounds = LGraph.calculateBounds(tree);

    ChsTransform chsTransform = new ChsTransform();
    chsTransform.setDeviceOrgX(bounds.getLeft().x);
    chsTransform.setDeviceOrgY(bounds.getTop().y);
    chsTransform.setWorldOrgX(startingPoint.x);
    chsTransform.setWorldOrgY(startingPoint.y);

    for (int i = 0; i < tree.size(); i++)
    {
      LNode node = tree.get(i);
      node.transform(chsTransform);
    }

    PrecisionPoint bottomRight =
      new PrecisionPoint(bounds.getBottomRight());

    return chsTransform.inverseTransformPoint(bottomRight);
  }

  /**
   * This method is recursively called for radial positioning of a node,
   * between the specified angles. Curent radial level is implied by the
   * distance given. Parent of this node in the tree is also needed.
   */
  private static void branchRadialLayout(LNode node,
    LNode parentOfNode,
    double startAngle, double endAngle,
    double distance, double radialSeparation)
  {
    // First, position this node by finding its angle.
    double halfInterval = ((endAngle - startAngle) + 1) / 2;

    if (halfInterval < 0)
    {
      halfInterval += 180;
    }

    double nodeAngle = (halfInterval + startAngle) % 360;
    double teta = (nodeAngle * ChsGeometry.TWO_PI) / 360;

    // Make polar to java cordinate conversion.
    double x = distance * Math.cos(teta);
    double y = distance * Math.sin(teta);

    node.setCenter(x, y);

    // Traverse all neighbors of this node and recursively call this
    // function.

    List<LEdge> neighborEdges = new LinkedList<LEdge>(node.getEdges());
    int childCount = neighborEdges.size();

    if (parentOfNode != null)
    {
      childCount--;
    }

    int branchCount = 0;

    int incEdgesCount = neighborEdges.size();
    int startIndex;

    List edges = node.getEdgesBetween(parentOfNode);

    // If there are multiple edges, prune them until there remains only one
    // edge.
    while (edges.size() > 1)
    {
      neighborEdges.remove(edges.remove(0));
      incEdgesCount--;
      childCount--;
    }

    if (parentOfNode != null)
    {
      assert edges.size() == 1;
      startIndex =
        (neighborEdges.indexOf(edges.get(0)) + 1) % incEdgesCount;
    }
    else
    {
      startIndex = 0;
    }

    double stepAngle = Math.abs(endAngle - startAngle) / childCount;

    for (int i = startIndex;
      branchCount != childCount ;
      i = (++i) % incEdgesCount)
    {
      LNode currentNeighbor =
        neighborEdges.get(i).getOtherEnd(node);

      // Don't back traverse to root node in current tree.
      if (currentNeighbor == parentOfNode)
      {
        continue;
      }

      double childStartAngle =
        (startAngle + branchCount * stepAngle) % 360;
      double childEndAngle = (childStartAngle + stepAngle) % 360;

      branchRadialLayout(currentNeighbor,
          node,
          childStartAngle, childEndAngle,
          distance + radialSeparation, radialSeparation);

      branchCount++;
    }
  }

  /**
   * This method finds the maximum diagonal length of the nodes in given tree.
   */
  private static double maxDiagonalInTree(ArrayList<LNode> tree)
  {
    double maxDiagonal = Double.MIN_VALUE;

    for (int i = 0; i < tree.size(); i++)
    {
      LNode node = tree.get(i);
      double diagonal = node.getDiagonal();

      if (diagonal > maxDiagonal)
      {
        maxDiagonal = diagonal;
      }
    }

    return maxDiagonal;
  }

// -----------------------------------------------------------------------------
// Section: Class Constants
// -----------------------------------------------------------------------------
  /**
   * Orientation enumeration list
   */
  public static final int RIGHT = 0;
  public static final int LEFT = 2;
  public static final int TOP = 3;
  public static final int BOTTOM = 1;

  /**
   * Phases of layout
   */
  public static final int PHASE_1 = 1;
  public static final int PHASE_2 = 2;
  public static final int PHASE_3 = 3;

  /**
   * Number of iterations that should be done in between convergence checks
   */
  public static final int CONVERGENCE_CHECK_PERIOD = 100;

  /**
   * Number of iterations that should be done in between growing trees
   */
  public static final int TREE_GROWING_FREQUENCY = 150;

  /**
   * Ideal edge length coefficient for different phases
   */
  public static final int IDEAL_EDGE_LENGTH_COEFFICIENT_PHASE1_MINOR1 = 2;
  public static final int IDEAL_EDGE_LENGTH_COEFFICIENT_PHASE1_MINOR2 = 1;
  public static final int IDEAL_EDGE_LENGTH_COEFFICIENT_PHASE2 = 1;
  public static final int IDEAL_EDGE_LENGTH_COEFFICIENT_PHASE3 = 1;

  /**
   * Default ideal edge length coefficient per level for intergraph edges
   */
  public static final double DEFAULT_PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = 0.0;

  /**
   * Minimum legth of an edge
   */
  public static final int MIN_EDGE_LENGTH = 1;

  /**
   * Interval that the spring force should be fit into
   */
  public static final double MAX_SPRING_FORCE = 5000;
  public static final double MIN_SPRING_FORCE = -MAX_SPRING_FORCE;

  /**
   * Maximum repulsion force that can be applied on a node
   */
  public static final double MAX_REPULSION_FORCE = 50;

  /**
   * User customizable layout options
   */

  // Layout property defaults
  public static final int DEFAULT_EDGE_LENGTH = 5;//60;
  public static final boolean DEFAULT_UNIFORM_LEAFS = false;
  public static final double DEFAULT_SPRING_STRENGTH = 1.0;
  public static final double DEFAULT_REPULSION_STRENGTH = 730; //1000;
  public static final double DEFAULT_GRAVITY_STRENGTH = 0.005;
  public static final double DEFAULT_COMPOUND_GRAVITY_STRENGTH = 2.8;//10;

  /**
   * Default distance between each level in radial layout
   */
  public static final double DEFAULT_RADIAL_SEPARATION = DEFAULT_EDGE_LENGTH;

  /**
   * Default separation of trees in a forest when tiled to a grid
   */
  public static final int DEFAULT_COMPONENT_SEPERATION = 60;

// -----------------------------------------------------------------------------
// Section: Class Variables
// -----------------------------------------------------------------------------
  /**
   * Ideal edge length coefficient per level for intergraph edges
   */
  public static double perLevelIdealEdgeLengthFactor =
    DEFAULT_PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR;

  /**
   * Factor used for cooling during only the last phase of layout; starts from
   * 1.0 and goes down towards zero as we approach maximum iterations.
   */
  public static double coolingFactor = 1.0;

  /**
   * Total displacement made in an iteration
   */
  public static double totalDisplacement = 0.0;

  /**
   * Total displacement made in the previous iteration
   */
  public static double oldTotalDisplacement = 0.0;
}
TOP

Related Classes of org.gvt.layout.CoSELayout

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.