/** {@inheritDoc} */
  public IntermediateProcessingConfiguration getIntermediateProcessingConfiguration(
      final LGraph graph) {

    if (graph
        .getProperty(InternalProperties.GRAPH_PROPERTIES)
        .contains(GraphProperties.EXTERNAL_PORTS)) {
      return HIERARCHY_PROCESSING_ADDITIONS;
    } else {
      return null;
    }
  }
예제 #2
0
  /** @param graph the {@link LGraph} for which to record the spacing values. */
  public Spacings(final LGraph graph) {

    nodeSpacing = graph.getProperty(InternalProperties.SPACING);
    inLayerSpacingFactor = graph.getProperty(Properties.OBJ_SPACING_IN_LAYER_FACTOR);
    edgeEdgeSpacing = nodeSpacing * graph.getProperty(Properties.EDGE_SPACING_FACTOR);
    edgeNodeSpacing = nodeSpacing * graph.getProperty(Properties.EDGE_NODE_SPACING_FACTOR);
    portSpacing = graph.getProperty(InternalProperties.PORT_SPACING);
    externalPortSpacing = graph.getProperty(InternalProperties.PORT_SPACING);
    labelSpacing = graph.getProperty(LayoutOptions.LABEL_SPACING);

    // pre calculate the spacings between pairs of node types
    int n = NodeType.values().length;
    nodeTypeSpacings = new float[n][n];
    precalculateNodeTypeSpacings();
  }
  /** {@inheritDoc} */
  public void process(final LGraph layeredGraph, final IElkProgressMonitor monitor) {
    monitor.begin("Linear segments node placement", 1);

    spacings = layeredGraph.getProperty(InternalProperties.SPACINGS);

    // sort the linear segments of the layered graph
    sortLinearSegments(layeredGraph);

    // create an unbalanced placement from the sorted segments
    createUnbalancedPlacement(layeredGraph);

    // balance the placement
    balancePlacement(layeredGraph);

    // post-process the placement for small corrections
    postProcess(layeredGraph);

    // release the created resources
    linearSegments = null;
    spacings = null;
    monitor.done();
  }
  /** {@inheritDoc} */
  public void process(final LGraph graph, final IElkProgressMonitor progressMonitor) {
    progressMonitor.begin("Greedy switch crossing reduction", 1);

    greedySwitchType = graph.getProperty(Properties.GREEDY_SWITCH_TYPE);

    int layerCount = graph.getLayers().size();
    if (layerCount < 2 || greedySwitchType == GreedySwitchType.OFF) {
      progressMonitor.done();
      return;
    }

    initialize(graph);

    if (greedySwitchType.useBestOfUpOrDown()) {
      compareSweepingUpwardOrDownward();
    } else {
      sweepOneSidedOrTwoSided();
    }

    setAsGraph(bestNodeOrder);

    progressMonitor.done();
  }
  /**
   * Balance the initial placement by force-based movement of regions. First perform
   * <em>pendulum</em> iterations, where only one direction of edges is considered, then
   * <em>rubber</em> iterations, where both incoming and outgoing edges are considered. In each
   * iteration first determine the <em>deflection</em> of each linear segment, i.e. the optimal
   * position delta that leads to a balanced placement with respect to its adjacent segments. Then
   * merge regions that touch each other, building mean values of the involved deflections, and
   * finally apply the resulting deflection values to all segments. The iterations stop when no
   * further improvement is done.
   *
   * @param layeredGraph a layered graph
   */
  private void balancePlacement(final LGraph layeredGraph) {
    double deflectionDampening =
        layeredGraph.getProperty(Properties.LINEAR_SEGMENTS_DEFLECTION_DAMPENING).doubleValue();

    // Determine a suitable number of pendulum iterations
    int thoroughness = layeredGraph.getProperty(Properties.THOROUGHNESS);
    int pendulumIters = PENDULUM_ITERS;
    int finalIters = FINAL_ITERS;
    double threshold = THRESHOLD_FACTOR / thoroughness;

    // Iterate the balancing
    boolean ready = false;
    Mode mode = Mode.FORW_PENDULUM;
    double lastTotalDeflection = Integer.MAX_VALUE;
    do {

      // Calculate force for every linear segment
      boolean incoming = mode != Mode.BACKW_PENDULUM;
      boolean outgoing = mode != Mode.FORW_PENDULUM;
      double totalDeflection = 0;
      for (LinearSegment segment : linearSegments) {
        segment.refSegment = null;
        calcDeflection(segment, incoming, outgoing, deflectionDampening);
        totalDeflection += Math.abs(segment.deflection);
      }

      // Merge linear segments to form regions
      boolean merged;
      do {
        merged = mergeRegions(layeredGraph);
      } while (merged);

      // Move the nodes according to the deflection value of their region
      for (LinearSegment segment : linearSegments) {
        double deflection = segment.region().deflection;
        if (deflection != 0) {
          for (LNode node : segment.nodes) {
            node.getPosition().y += deflection;
          }
        }
      }

      // Update the balancing mode
      if (mode == Mode.FORW_PENDULUM || mode == Mode.BACKW_PENDULUM) {
        pendulumIters--;
        if (pendulumIters <= 0
            && (totalDeflection < lastTotalDeflection || -pendulumIters > thoroughness)) {
          mode = Mode.RUBBER;
          lastTotalDeflection = Integer.MAX_VALUE;
        } else if (mode == Mode.FORW_PENDULUM) {
          mode = Mode.BACKW_PENDULUM;
          lastTotalDeflection = totalDeflection;
        } else {
          mode = Mode.FORW_PENDULUM;
          lastTotalDeflection = totalDeflection;
        }
      } else {
        ready =
            totalDeflection >= lastTotalDeflection
                || lastTotalDeflection - totalDeflection < threshold;
        lastTotalDeflection = totalDeflection;
        if (ready) {
          finalIters--;
        }
      }
    } while (!(ready && finalIters <= 0));
  }
  /**
   * Fills the dependency graph with dependencies. If a dependency would introduce a cycle, the
   * offending linear segment is split into two linear segments.
   *
   * @param layeredGraph the layered graph.
   * @param segmentList the list of segments. Updated to include the newly created linear segments.
   * @param outgoingList the lists of outgoing dependencies for each segment. This essentially
   *     encodes the edges of the dependency graph.
   * @param incomingCountList the number of incoming dependencies for each segment.
   */
  private void createDependencyGraphEdges(
      final LGraph layeredGraph,
      final List<LinearSegment> segmentList,
      final List<List<LinearSegment>> outgoingList,
      final List<Integer> incomingCountList) {

    /*
     * There's some <scaryVoice> faaaancy </scaryVoice> stuff going on here. Basically, we go
     * through all the layers, from left to right. In each layer, we go through all the nodes.
     * For each node, we retrieve the linear segment it's part of and add a dependency to the
     * next node's linear segment. So far so good.
     *
     * This works perfectly fine as long as we assume that the relative order of linear segments
     * doesn't change from one layer to the next. However, since the introduction of north /
     * south port dummies, it can. We now have to avoid creating cycles in the dependency graph.
     * This is done by remembering the indices of each linear segment in the previous layer.
     * When we encounter a segment x, we check if there is a segment y that came before x in the
     * previous layer. (that would introduce a cycle) If that's the case, we split x at the
     * current layer, resulting in two segments, x1 and x2, x2 starting at the current layer.
     * Now, we proceed as usual, adding a dependency from x2 to y. But we have avoided a cycle
     * because y does not depend on x2, but on x1.
     */

    int nextLinearSegmentID = segmentList.size();
    int layerIndex = 0;
    for (Layer layer : layeredGraph) {
      List<LNode> nodes = layer.getNodes();
      if (nodes.isEmpty()) {
        // Ignore empty layers
        continue;
      }

      Iterator<LNode> nodeIter = nodes.iterator();
      int indexInLayer = 0;

      // We carry the previous node with us for dependency management
      LNode previousNode = null;

      // Get the layer's first node
      LNode currentNode = nodeIter.next();
      LinearSegment currentSegment = null;

      while (currentNode != null) {
        // Get the current node's segment
        currentSegment = segmentList.get(currentNode.id);

        /*
         * Check if we have a cycle. That's the case if the following holds: - The current
         * segment appeared in the previous layer as well. - In the previous layer, we find
         * a segment after the current segment that appears before the current segment in
         * the current layer.
         */
        if (currentSegment.indexInLastLayer >= 0) {
          LinearSegment cycleSegment = null;
          Iterator<LNode> cycleNodesIter = layer.getNodes().listIterator(indexInLayer + 1);
          while (cycleNodesIter.hasNext()) {
            LNode cycleNode = cycleNodesIter.next();
            cycleSegment = segmentList.get(cycleNode.id);

            if (cycleSegment.lastLayer == currentSegment.lastLayer
                && cycleSegment.indexInLastLayer < currentSegment.indexInLastLayer) {

              break;
            } else {
              cycleSegment = null;
            }
          }

          // If we have found a cycle segment, we need to split the current linear segment
          if (cycleSegment != null) {
            // Update the current segment before it's split
            if (previousNode != null) {
              incomingCountList.set(currentNode.id, incomingCountList.get(currentNode.id) - 1);
              outgoingList.get(previousNode.id).remove(currentSegment);
            }

            currentSegment = currentSegment.split(currentNode, nextLinearSegmentID++);
            segmentList.add(currentSegment);
            outgoingList.add(new ArrayList<LinearSegment>());

            if (previousNode != null) {
              outgoingList.get(previousNode.id).add(currentSegment);
              incomingCountList.add(1);
            } else {
              incomingCountList.add(0);
            }
          }
        }

        // Now add a dependency to the next node, if any
        LNode nextNode = null;
        if (nodeIter.hasNext()) {
          nextNode = nodeIter.next();
          LinearSegment nextSegment = segmentList.get(nextNode.id);

          outgoingList.get(currentNode.id).add(nextSegment);
          incomingCountList.set(nextNode.id, incomingCountList.get(nextNode.id) + 1);
        }

        // Update segment's layer information
        currentSegment.lastLayer = layerIndex;
        currentSegment.indexInLastLayer = indexInLayer++;

        // Cycle nodes
        previousNode = currentNode;
        currentNode = nextNode;
      }

      layerIndex++;
    }

    // Write debug output graph
    if (layeredGraph.getProperty(LayoutOptions.DEBUG_MODE)) {
      DebugUtil.writeDebugGraph(layeredGraph, segmentList, outgoingList);
    }
  }
  /** {@inheritDoc} */
  public void process(final LGraph layeredGraph, final IElkProgressMonitor monitor) {
    monitor.begin("Spline SelfLoop positioning", 1);

    /** Stores which loop placement strategy to choose. */
    final SelfLoopPlacement loopPlacement =
        layeredGraph.getProperty(Properties.SPLINE_SELF_LOOP_PLACEMENT);

    ////////////////////////////////////////////////////////
    // There are two main jobs to be done:
    // 1) Find a loop-side for each component.
    // 2) Position ports in the correct order around the node.
    ////////////////////////////////////////////////////////

    // Process all nodes on all layers.
    for (final Layer layer : layeredGraph) {
      for (final LNode node : layer) {

        // Read self-loops components.
        final List<ConnectedSelfLoopComponent> components =
            node.getProperty(InternalProperties.SPLINE_SELFLOOP_COMPONENTS);

        // Components to be distributed by the placement strategy.
        final List<ConnectedSelfLoopComponent> componentsToBePlaced = Lists.newArrayList();

        for (final ConnectedSelfLoopComponent component : components) {
          // Re-Add all hidden edges to their ports.
          component.unhideEdges();

          if (component.getConstrainedPorts().isEmpty()) {
            // If there is no constraint on any port, we have to process this component later
            componentsToBePlaced.add(component);
          } else {
            // If there is at least one port with a constraint to it's port-side,
            // we will set the port- and loop-sides by this constraint. (Job 1)
            setPortSideByConstraint(component);
            if (!component.getNonLoopPorts().isEmpty()) {
              // Position and re-add all ports to the node, that are part of a component
              // with at least one non-loop edge. (Job 2)
              addComponentWithNonLoopEdges(component);
            }
          }
        }

        // Now we have to find a loop-side (job 1) for the remaining components. They are all
        // stored in componentsToBePlaced. All these components don't have a port with a
        // constraint on it's portSide, so we can arrange them accordingly to the cosen strategy.
        switch (loopPlacement) {
          case EQUALLY_DISTRIBUTED:
            setPortSideSpreadEqually(componentsToBePlaced, node);
            break;
          case NORTH_SEQUENCE:
            for (final ConnectedSelfLoopComponent component : componentsToBePlaced) {
              component.setLoopSide(LoopSide.N, true);
            }
            break;
          case NORTH_STACKED:
            for (final ConnectedSelfLoopComponent component : componentsToBePlaced) {
              component.setLoopSide(LoopSide.N, true);
            }
            break;
          default:
            // Unknown strategy chosen.
            assert false;
            break;
        }

        // Position and re-add all ports to the node.
        // This is job 2)
        switch (loopPlacement) {
          case EQUALLY_DISTRIBUTED:
          case NORTH_STACKED:
            portStackedPositioning(components);
            break;
          case NORTH_SEQUENCE:
            portLinedPositioning(components);
            break;
          default:
            break;
        }
      }
    }
    monitor.done();
  }