Package com.mxgraph.layout.hierarchical.model

Source Code of com.mxgraph.layout.hierarchical.model.mxGraphHierarchyModel$CellVisitor

/*
* Copyright (c) 2005-2012, JGraph Ltd
*/
package com.mxgraph.layout.hierarchical.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
import com.mxgraph.view.mxGraph;

/**
* Internal model of a hierarchical graph. This model stores nodes and edges
* equivalent to the real graph nodes and edges, but also stores the rank of the
* cells, the order within the ranks and the new candidate locations of cells.
* The internal model also reverses edge direction were appropriate , ignores
* self-loop and groups parallels together under one edge object.
*/
public class mxGraphHierarchyModel
{
  /**
   * Stores the largest rank number allocated
   */
  public int maxRank;

  /**
   * Map from graph vertices to internal model nodes
   */
  protected Map<Object, mxGraphHierarchyNode> vertexMapper = null;

  /**
   * Map from graph edges to internal model edges
   */
  protected Map<Object, mxGraphHierarchyEdge> edgeMapper = null;

  /**
   * Mapping from rank number to actual rank
   */
  public Map<Integer, mxGraphHierarchyRank> ranks = null;

  /**
   * Store of roots of this hierarchy model, these are real graph cells, not
   * internal cells
   */
  public List<Object> roots;

  /**
   * The parent cell whose children are being laid out
   */
  public Object parent = null;

  /**
   * Count of the number of times the ancestor dfs has been used
   */
  protected int dfsCount = 0;

  /** High value to start source layering scan rank value from */
  private final int SOURCESCANSTARTRANK = 100000000;

  /**
   * Creates an internal ordered graph model using the vertices passed in. If
   * there are any, leftward edge need to be inverted in the internal model
   *
   * @param layout
   *            the enclosing layout object
   * @param vertices
   *            the vertices for this hierarchy
   */
  public mxGraphHierarchyModel(mxHierarchicalLayout layout,
      Object[] vertices, List<Object> roots, Object parent)
  {
    mxGraph graph = layout.getGraph();
    this.roots = roots;
    this.parent = parent;

    if (vertices == null)
    {
      vertices = graph.getChildVertices(parent);
    }

    // map of cells to internal cell needed for second run through
    // to setup the sink of edges correctly. Guess size by number
    // of edges is roughly same as number of vertices.
    vertexMapper = new Hashtable<Object, mxGraphHierarchyNode>(
        vertices.length);
    edgeMapper = new Hashtable<Object, mxGraphHierarchyEdge>(
        vertices.length);

    maxRank = SOURCESCANSTARTRANK;

    mxGraphHierarchyNode[] internalVertices = new mxGraphHierarchyNode[vertices.length];
    createInternalCells(layout, vertices, internalVertices);

    // Go through edges set their sink values. Also check the
    // ordering if and invert edges if necessary
    for (int i = 0; i < vertices.length; i++)
    {
      Collection<mxGraphHierarchyEdge> edges = internalVertices[i].connectsAsSource;
      Iterator<mxGraphHierarchyEdge> iter = edges.iterator();

      while (iter.hasNext())
      {
        mxGraphHierarchyEdge internalEdge = iter.next();
        Collection<Object> realEdges = internalEdge.edges;
        Iterator<Object> iter2 = realEdges.iterator();

        // Only need to process the first real edge, since
        // all the edges connect to the same other vertex
        if (iter2.hasNext())
        {
          Object realEdge = iter2.next();
          Object targetCell = graph.getView().getVisibleTerminal(
              realEdge, false);
          mxGraphHierarchyNode internalTargetCell = vertexMapper
              .get(targetCell);

          if (internalVertices[i] == internalTargetCell)
          {
            // The real edge is reversed relative to the internal edge
            targetCell = graph.getView().getVisibleTerminal(
                realEdge, true);
            internalTargetCell = vertexMapper.get(targetCell);
          }

          if (internalTargetCell != null
              && internalVertices[i] != internalTargetCell)
          {
            internalEdge.target = internalTargetCell;

            if (internalTargetCell.connectsAsTarget.size() == 0)
            {
              internalTargetCell.connectsAsTarget = new LinkedHashSet<mxGraphHierarchyEdge>(
                  4);
            }

            internalTargetCell.connectsAsTarget.add(internalEdge);
          }
        }
      }

      // Use the temp variable in the internal nodes to mark this
      // internal vertex as having been visited.
      internalVertices[i].temp[0] = 1;
    }
  }
 
  /**
   * Creates all edges in the internal model
   *
   * @param layout
   *            reference to the layout algorithm
   * @param vertices
   *            the vertices whom are to have an internal representation
   *            created
   * @param internalVertices
   *            the blank internal vertices to have their information filled
   *            in using the real vertices
   */
  protected void createInternalCells(mxHierarchicalLayout layout,
      Object[] vertices, mxGraphHierarchyNode[] internalVertices)
  {
    mxGraph graph = layout.getGraph();

    // Create internal edges
    for (int i = 0; i < vertices.length; i++)
    {
      internalVertices[i] = new mxGraphHierarchyNode(vertices[i]);
      vertexMapper.put(vertices[i], internalVertices[i]);

      // If the layout is deterministic, order the cells
      Object[] conns = layout.getEdges(vertices[i]);
      List<Object> outgoingCells = Arrays.asList(graph.getOpposites(
          conns, vertices[i]));
      internalVertices[i].connectsAsSource = new LinkedHashSet<mxGraphHierarchyEdge>(
          outgoingCells.size());

      // Create internal edges, but don't do any rank assignment yet
      // First use the information from the greedy cycle remover to
      // invert the leftward edges internally
      Iterator<Object> iter = outgoingCells.iterator();

      while (iter.hasNext())
      {
        // Don't add self-loops
        Object cell = iter.next();

        if (cell != vertices[i] && graph.getModel().isVertex(cell)
            && !layout.isVertexIgnored(cell))
        {
          // We process all edge between this source and its targets
          // If there are edges going both ways, we need to collect
          // them all into one internal edges to avoid looping problems
          // later. We assume this direction (source -> target) is the
          // natural direction if at least half the edges are going in
          // that direction.

          // The check below for edgeMapper.get(edges[0]) == null is
          // in case we've processed this the other way around
          // (target -> source) and the number of edges in each direction
          // are the same. All the graph edges will have been assigned to
          // an internal edge going the other way, so we don't want to
          // process them again
          Object[] undirectEdges = graph.getEdgesBetween(vertices[i],
              cell, false);
          Object[] directedEdges = graph.getEdgesBetween(vertices[i],
              cell, true);

          if (undirectEdges != null
              && undirectEdges.length > 0
              && (edgeMapper.get(undirectEdges[0]) == null)
              && (directedEdges.length * 2 >= undirectEdges.length))
          {

            ArrayList<Object> listEdges = new ArrayList<Object>(
                undirectEdges.length);

            for (int j = 0; j < undirectEdges.length; j++)
            {
              listEdges.add(undirectEdges[j]);
            }

            mxGraphHierarchyEdge internalEdge = new mxGraphHierarchyEdge(
                listEdges);
            Iterator<Object> iter2 = listEdges.iterator();

            while (iter2.hasNext())
            {
              Object edge = iter2.next();
              edgeMapper.put(edge, internalEdge);

              // Resets all point on the edge and disables the edge style
              // without deleting it from the cell style
              graph.resetEdge(edge);

              if (layout.isDisableEdgeStyle())
              {
                layout.setEdgeStyleEnabled(edge, false);
                layout.setOrthogonalEdge(edge, true);
              }
            }

            internalEdge.source = internalVertices[i];
            internalVertices[i].connectsAsSource.add(internalEdge);
          }
        }
      }

      // Ensure temp variable is cleared from any previous use
      internalVertices[i].temp[0] = 0;
    }
  }

  /**
   * Basic determination of minimum layer ranking by working from from sources
   * or sinks and working through each node in the relevant edge direction.
   * Starting at the sinks is basically a longest path layering algorithm.
   */
  public void initialRank()
  {
    Collection<mxGraphHierarchyNode> internalNodes = vertexMapper.values();
    LinkedList<mxGraphHierarchyNode> startNodes = new LinkedList<mxGraphHierarchyNode>();

    if (roots != null)
    {
      Iterator<Object> iter = roots.iterator();

      while (iter.hasNext())
      {
        mxGraphHierarchyNode internalNode = vertexMapper.get(iter
            .next());

        if (internalNode != null)
        {
          startNodes.add(internalNode);
        }
      }
    }

    Iterator<mxGraphHierarchyNode> iter = internalNodes.iterator();

    while (iter.hasNext())
    {
      mxGraphHierarchyNode internalNode = iter.next();
      // Mark the node as not having had a layer assigned
      internalNode.temp[0] = -1;
    }

    List<mxGraphHierarchyNode> startNodesCopy = new ArrayList<mxGraphHierarchyNode>(
        startNodes);

    while (!startNodes.isEmpty())
    {
      mxGraphHierarchyNode internalNode = startNodes.getFirst();
      Collection<mxGraphHierarchyEdge> layerDeterminingEdges;
      Collection<mxGraphHierarchyEdge> edgesToBeMarked;

      layerDeterminingEdges = internalNode.connectsAsTarget;
      edgesToBeMarked = internalNode.connectsAsSource;

      // flag to keep track of whether or not all layer determining
      // edges have been scanned
      boolean allEdgesScanned = true;

      // Work out the layer of this node from the layer determining
      // edges
      Iterator<mxGraphHierarchyEdge> iter2 = layerDeterminingEdges
          .iterator();

      // The minimum layer number of any node connected by one of
      // the layer determining edges variable. If we are starting
      // from sources, need to start at some huge value and
      // normalise down afterwards
      int minimumLayer = SOURCESCANSTARTRANK;

      while (allEdgesScanned && iter2.hasNext())
      {
        mxGraphHierarchyEdge internalEdge = iter2.next();

        if (internalEdge.temp[0] == 5270620)
        {
          // This edge has been scanned, get the layer of the
          // node on the other end
          mxGraphHierarchyNode otherNode = internalEdge.source;
          minimumLayer = Math.min(minimumLayer,
                otherNode.temp[0] - 1);
        }
        else
        {
          allEdgesScanned = false;
        }
      }

      // If all edge have been scanned, assign the layer, mark all
      // edges in the other direction and remove from the nodes list
      if (allEdgesScanned)
      {
        internalNode.temp[0] = minimumLayer;
        maxRank = Math.min(maxRank, minimumLayer);

        if (edgesToBeMarked != null)
        {
          Iterator<mxGraphHierarchyEdge> iter3 = edgesToBeMarked
              .iterator();

          while (iter3.hasNext())
          {
            mxGraphHierarchyEdge internalEdge = iter3.next();
            // Assign unique stamp ( y/m/d/h )
            internalEdge.temp[0] = 5270620;

            // Add node on other end of edge to LinkedList of
            // nodes to be analysed
            mxGraphHierarchyNode otherNode = internalEdge.target;

            // Only add node if it hasn't been assigned a layer
            if (otherNode.temp[0] == -1)
            {
              startNodes.addLast(otherNode);

              // Mark this other node as neither being
              // unassigned nor assigned so it isn't
              // added to this list again, but it's
              // layer isn't used in any calculation.
              otherNode.temp[0] = -2;
            }
          }
        }

        startNodes.removeFirst();
      }
      else
      {
        // Not all the edges have been scanned, get to the back of
        // the class and put the dunces cap on
        Object removedCell = startNodes.removeFirst();
        startNodes.addLast(internalNode);

        if (removedCell == internalNode && startNodes.size() == 1)
        {
          // This is an error condition, we can't get out of
          // this loop. It could happen for more than one node
          // but that's a lot harder to detect. Log the error
          // TODO make log comment
          break;
        }
      }
    }

    // Normalize the ranks down from their large starting value to place
    // at least 1 sink on layer 0
    iter = internalNodes.iterator();
    while (iter.hasNext())
    {
      mxGraphHierarchyNode internalNode = iter.next();
      // Mark the node as not having had a layer assigned
      internalNode.temp[0] -= maxRank;
    }
   
    // Tighten the roots as far as possible
    for (int i = 0; i < startNodesCopy.size(); i++)
    {
      mxGraphHierarchyNode internalNode = startNodesCopy.get(i);
      int currentMaxLayer = 0;
      Iterator<mxGraphHierarchyEdge> iter2 = internalNode.connectsAsSource
          .iterator();

      while (iter2.hasNext())
      {
        mxGraphHierarchyEdge internalEdge = iter2.next();
        mxGraphHierarchyNode otherNode = internalEdge.target;
        internalNode.temp[0] = Math.max(currentMaxLayer,
            otherNode.temp[0] + 1);
        currentMaxLayer = internalNode.temp[0];
      }
    }

    // Reset the maxRank to that which would be expected for a from-sink
    // scan
    maxRank = SOURCESCANSTARTRANK - maxRank;
  }

  /**
   * Fixes the layer assignments to the values stored in the nodes. Also needs
   * to create dummy nodes for edges that cross layers.
   */
  public void fixRanks()
  {
    final mxGraphHierarchyRank[] rankList = new mxGraphHierarchyRank[maxRank + 1];
    ranks = new LinkedHashMap<Integer, mxGraphHierarchyRank>(maxRank + 1);

    for (int i = 0; i < maxRank + 1; i++)
    {
      rankList[i] = new mxGraphHierarchyRank();
      ranks.put(new Integer(i), rankList[i]);
    }

    // Perform a DFS to obtain an initial ordering for each rank.
    // Without doing this you would end up having to process
    // crossings for a standard tree.
    mxGraphHierarchyNode[] rootsArray = null;

    if (roots != null)
    {
      Object[] oldRootsArray = roots.toArray();
      rootsArray = new mxGraphHierarchyNode[oldRootsArray.length];

      for (int i = 0; i < oldRootsArray.length; i++)
      {
        Object node = oldRootsArray[i];
        mxGraphHierarchyNode internalNode = vertexMapper.get(node);
        rootsArray[i] = internalNode;
      }
    }

    visit(new mxGraphHierarchyModel.CellVisitor()
    {
      public void visit(mxGraphHierarchyNode parent,
          mxGraphHierarchyNode cell,
          mxGraphHierarchyEdge connectingEdge, int layer, int seen)
      {
        mxGraphHierarchyNode node = cell;

        if (seen == 0 && node.maxRank < 0 && node.minRank < 0)
        {
          rankList[node.temp[0]].add(cell);
          node.maxRank = node.temp[0];
          node.minRank = node.temp[0];

          // Set temp[0] to the nodes position in the rank
          node.temp[0] = rankList[node.maxRank].size() - 1;
        }

        if (parent != null && connectingEdge != null)
        {
          int parentToCellRankDifference = (parent).maxRank
              - node.maxRank;

          if (parentToCellRankDifference > 1)
          {
            // There are ranks in between the parent and current cell
            mxGraphHierarchyEdge edge = connectingEdge;
            edge.maxRank = (parent).maxRank;
            edge.minRank = (cell).maxRank;
            edge.temp = new int[parentToCellRankDifference - 1];
            edge.x = new double[parentToCellRankDifference - 1];
            edge.y = new double[parentToCellRankDifference - 1];

            for (int i = edge.minRank + 1; i < edge.maxRank; i++)
            {
              // The connecting edge must be added to the
              // appropriate ranks
              rankList[i].add(edge);
              edge.setGeneralPurposeVariable(i,
                  rankList[i].size() - 1);
            }
          }
        }
      }
    }, rootsArray, false, null);
  }

  /**
   * A depth first search through the internal hierarchy model
   *
   * @param visitor
   *            the visitor pattern to be called for each node
   * @param trackAncestors
   *            whether or not the search is to keep track all nodes directly
   *            above this one in the search path
   */
  public void visit(CellVisitor visitor, mxGraphHierarchyNode[] dfsRoots,
      boolean trackAncestors, Set<mxGraphHierarchyNode> seenNodes)
  {
    // Run dfs through on all roots
    if (dfsRoots != null)
    {
      for (int i = 0; i < dfsRoots.length; i++)
      {
        mxGraphHierarchyNode internalNode = dfsRoots[i];

        if (internalNode != null)
        {
          if (seenNodes == null)
          {
            seenNodes = new HashSet<mxGraphHierarchyNode>();
          }

          if (trackAncestors)
          {
            // Set up hash code for root
            internalNode.hashCode = new int[2];
            internalNode.hashCode[0] = dfsCount;
            internalNode.hashCode[1] = i;
            dfs(null, internalNode, null, visitor, seenNodes,
                internalNode.hashCode, i, 0);
          }
          else
          {
            dfs(null, internalNode, null, visitor, seenNodes, 0);
          }
        }
      }

      dfsCount++;
    }
  }

  /**
   * Performs a depth first search on the internal hierarchy model
   *
   * @param parent
   *            the parent internal node of the current internal node
   * @param root
   *            the current internal node
   * @param connectingEdge
   *            the internal edge connecting the internal node and the parent
   *            internal node, if any
   * @param visitor
   *            the visitor pattern to be called for each node
   * @param seen
   *            a set of all nodes seen by this dfs a set of all of the
   *            ancestor node of the current node
   * @param layer
   *            the layer on the dfs tree ( not the same as the model ranks )
   */
  public void dfs(mxGraphHierarchyNode parent, mxGraphHierarchyNode root,
      mxGraphHierarchyEdge connectingEdge, CellVisitor visitor,
      Set<mxGraphHierarchyNode> seen, int layer)
  {
    if (root != null)
    {
      if (!seen.contains(root))
      {
        visitor.visit(parent, root, connectingEdge, layer, 0);
        seen.add(root);

        // Copy the connects as source list so that visitors
        // can change the original for edge direction inversions
        final Object[] outgoingEdges = root.connectsAsSource.toArray();

        for (int i = 0; i < outgoingEdges.length; i++)
        {
          mxGraphHierarchyEdge internalEdge = (mxGraphHierarchyEdge) outgoingEdges[i];
          mxGraphHierarchyNode targetNode = internalEdge.target;

          // Root check is O(|roots|)
          dfs(root, targetNode, internalEdge, visitor, seen,
              layer + 1);
        }
      }
      else
      {
        // Use the int field to indicate this node has been seen
        visitor.visit(parent, root, connectingEdge, layer, 1);
      }
    }
  }

  /**
   * Performs a depth first search on the internal hierarchy model. This dfs
   * extends the default version by keeping track of cells ancestors, but it
   * should be only used when necessary because of it can be computationally
   * intensive for deep searches.
   *
   * @param parent
   *            the parent internal node of the current internal node
   * @param root
   *            the current internal node
   * @param connectingEdge
   *            the internal edge connecting the internal node and the parent
   *            internal node, if any
   * @param visitor
   *            the visitor pattern to be called for each node
   * @param seen
   *            a set of all nodes seen by this dfs
   * @param ancestors
   *            the parent hash code
   * @param childHash
   *            the new hash code for this node
   * @param layer
   *            the layer on the dfs tree ( not the same as the model ranks )
   */
  public void dfs(mxGraphHierarchyNode parent, mxGraphHierarchyNode root,
      mxGraphHierarchyEdge connectingEdge, CellVisitor visitor,
      Set<mxGraphHierarchyNode> seen, int[] ancestors, int childHash,
      int layer)
  {
    // Explanation of custom hash set. Previously, the ancestors variable
    // was passed through the dfs as a HashSet. The ancestors were copied
    // into a new HashSet and when the new child was processed it was also
    // added to the set. If the current node was in its ancestor list it
    // meant there is a cycle in the graph and this information is passed
    // to the visitor.visit() in the seen parameter. The HashSet clone was
    // very expensive on CPU so a custom hash was developed using primitive
    // types. temp[] couldn't be used so hashCode[] was added to each node.
    // Each new child adds another int to the array, copying the prefix
    // from its parent. Child of the same parent add different ints (the
    // limit is therefore 2^32 children per parent...). If a node has a
    // child with the hashCode already set then the child code is compared
    // to the same portion of the current nodes array. If they match there
    // is a loop.
    // Note that the basic mechanism would only allow for 1 use of this
    // functionality, so the root nodes have two ints. The second int is
    // incremented through each node root and the first is incremented
    // through each run of the dfs algorithm (therefore the dfs is not
    // thread safe). The hash code of each node is set if not already set,
    // or if the first int does not match that of the current run.
    if (root != null)
    {
      if (parent != null)
      {
        // Form this nodes hash code if necessary, that is, if the
        // hashCode variable has not been initialized or if the
        // start of the parent hash code does not equal the start of
        // this nodes hash code, indicating the code was set on a
        // previous run of this dfs.
        if (root.hashCode == null
            || root.hashCode[0] != parent.hashCode[0])
        {
          int hashCodeLength = parent.hashCode.length + 1;
          root.hashCode = new int[hashCodeLength];
          System.arraycopy(parent.hashCode, 0, root.hashCode, 0,
              parent.hashCode.length);
          root.hashCode[hashCodeLength - 1] = childHash;
        }
      }

      if (!seen.contains(root))
      {
        visitor.visit(parent, root, connectingEdge, layer, 0);
        seen.add(root);
        // Copy the connects as source list so that visitors
        // can change the original for edge direction inversions
        final Object[] outgoingEdges = root.connectsAsSource.toArray();

        for (int i = 0; i < outgoingEdges.length; i++)
        {
          mxGraphHierarchyEdge internalEdge = (mxGraphHierarchyEdge) outgoingEdges[i];
          mxGraphHierarchyNode targetNode = internalEdge.target;

          // Root check is O(|roots|)
          dfs(root, targetNode, internalEdge, visitor, seen,
              root.hashCode, i, layer + 1);
        }
      }
      else
      {
        // Use the int field to indicate this node has been seen
        visitor.visit(parent, root, connectingEdge, layer, 1);
      }
    }
  }

  /**
   * Defines the interface that visitors use to perform operations upon the
   * graph information during depth first search (dfs) or other tree-traversal
   * strategies implemented by subclassers.
   */
  public interface CellVisitor
  {

    /**
     * The method within which the visitor will perform operations upon the
     * graph model
     *
     * @param parent
     *            the parent cell the current cell
     * @param cell
     *            the current cell visited
     * @param connectingEdge
     *            the edge that led the last cell visited to this cell
     * @param layer
     *            the current layer of the tree
     * @param seen
     *            an int indicating whether this cell has been seen
     *            previously
     */
    public void visit(mxGraphHierarchyNode parent,
        mxGraphHierarchyNode cell, mxGraphHierarchyEdge connectingEdge,
        int layer, int seen);
  }

  /**
   * @return Returns the vertexMapping.
   */
  public Map<Object, mxGraphHierarchyNode> getVertexMapper()
  {
    if (vertexMapper == null)
    {
      vertexMapper = new Hashtable<Object, mxGraphHierarchyNode>();
    }
    return vertexMapper;
  }

  /**
   * @param vertexMapping
   *            The vertexMapping to set.
   */
  public void setVertexMapper(Map<Object, mxGraphHierarchyNode> vertexMapping)
  {
    this.vertexMapper = vertexMapping;
  }

  /**
   * @return Returns the edgeMapper.
   */
  public Map<Object, mxGraphHierarchyEdge> getEdgeMapper()
  {
    return edgeMapper;
  }

  /**
   * @param edgeMapper
   *            The edgeMapper to set.
   */
  public void setEdgeMapper(Map<Object, mxGraphHierarchyEdge> edgeMapper)
  {
    this.edgeMapper = edgeMapper;
  }

  /**
   * @return Returns the dfsCount.
   */
  public int getDfsCount()
  {
    return dfsCount;
  }

  /**
   * @param dfsCount
   *            The dfsCount to set.
   */
  public void setDfsCount(int dfsCount)
  {
    this.dfsCount = dfsCount;
  }
}
TOP

Related Classes of com.mxgraph.layout.hierarchical.model.mxGraphHierarchyModel$CellVisitor

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.