All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.praxislive.ide.pxr.graph.scene.PraxisGraphLayout Maven / Gradle / Ivy

The newest version!
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2024 Neil C Smith.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 3 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 3 for more details.
 *
 * You should have received a copy of the GNU General Public License version 3
 * along with this work; if not, see http://www.gnu.org/licenses/
 *
 *
 * Please visit https://www.praxislive.org if you need additional information or
 * have any questions.
 *
 *
 * This file incorporates code from Apache NetBeans Visual Library, covered by
 * the following terms :
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.praxislive.ide.pxr.graph.scene;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.Stack;
import java.util.TreeSet;
import org.netbeans.api.visual.graph.layout.GraphLayout;
import org.netbeans.api.visual.graph.layout.UniversalGraph;
import org.netbeans.api.visual.widget.Widget;

/**
 *
 */
class PraxisGraphLayout extends GraphLayout> {

    public static final boolean TRACE = false;
    public static final boolean CHECK = false;    // Iterations
    public static final int SWEEP_ITERATIONS = 3;
    public static final int CROSSING_ITERATIONS = 3;    // Options default settings 
    public static final int DUMMY_WIDTH = 10;
    public static final int X_OFFSET = 30;
    public static final int LAYER_OFFSET = 40;    // Options
    private int dummyWidth;
    private int yOffset;
    private int layerOffset;
    private int layerCount;    // Variables
    private PraxisGraphScene graph;
    private List nodes;
    private Collection nodesSubset = null;
    private HashMap vertexToLayoutNode;
    private Set> reversedLinks;
    private List[] layers;
    private boolean animate = false;
    private boolean invert = true;

    public PraxisGraphLayout(boolean animate,
            boolean inverted, int xOffset, int layerOffset) {

        dummyWidth = DUMMY_WIDTH;

        // scene is not used yet. It will be used when the container agnostic feature
        // is put into the NBVL
        this.animate = animate;

        if (xOffset > 0) {
            this.yOffset = xOffset;
        } else {
            this.yOffset = X_OFFSET;
        }

        if (layerOffset > 0) {
            this.layerOffset = layerOffset;
        } else {
            this.layerOffset = LAYER_OFFSET;
        }
        
        this.invert = inverted;
    }

    public PraxisGraphLayout(boolean animate, boolean inverted) {
        this(animate, inverted, X_OFFSET, LAYER_OFFSET);
    }

    public PraxisGraphLayout(boolean animate) {
        this(animate, false);
    }

    public PraxisGraphLayout() {
        this(false);
    }

    private class LayoutNode {

        public int x;
        public int y;
        public int width;
        public int height;
        public int layer = -1;
        public int xOffset;
        public int yOffset;
        public int bottomYOffset;
        public N vertex; // Only used for non-dummy nodes, otherwise null
        public List preds = new ArrayList();
        public List succs = new ArrayList();
        public int pos = -1; // Position within layer
        public float crossingNumber;

        public String toString() {
            return "Node " + vertex;
        }
    }

    private class LayoutEdge {

        public LayoutNode from;
        public LayoutNode to;
        public int relativeFrom;
        public int relativeTo;
        public EdgeID link;
        private int indexFrom;
        private int indexTo;
    }

    private abstract class AlgorithmPart {

        public void start() {
            if (CHECK) {
                preCheck();
            }

            long start = 0;
            if (TRACE) {
                System.out.println("##################################################");
                System.out.println("Starting part " + this.getClass().getName());
                start = System.currentTimeMillis();
            }
            
            run();
            
            if (TRACE) {
                System.out.println("Timing for " + this.getClass().getName() + " is " + (System.currentTimeMillis() - start));
                printStatistics();
            }

            if (CHECK) {
                postCheck();
            }
        }

        protected abstract void run();

        protected void printStatistics() {
        }

        protected void postCheck() {
        }

        protected void preCheck() {
        }
    }

    @Override
    protected void performGraphLayout(UniversalGraph> graph) {

        this.graph = (PraxisGraphScene) graph.getScene();

        vertexToLayoutNode = new HashMap();
        reversedLinks = new HashSet>();
        nodes = new ArrayList();

        // #############################################################
        // Step 1: Build up data structure
        new BuildDatastructure().start();

        // #############################################################
        // STEP 2: Reverse edges, handle backedges
        new ReverseEdges().start();

        // #############################################################
        // STEP 3: Assign layers
        new AssignLayers().start();

        // #############################################################
        // STEP 4: Create dummy nodes
        new CreateDummyNodes().start();

        // #############################################################
        // STEP 5: Crossing Reduction
        new CrossingReduction().start();

        // #############################################################
        // STEP 7: Assign X coordinates
        //new AssignXCoordinates().start();
        new AssignYCoordinates().start();

        // #############################################################
        // STEP 6: Assign Y coordinates
        new AssignXCoordinates().start();

        // #############################################################
        // STEP 8: Write back to interface
        new WriteResult().start();
    }

    @Override
    protected void performNodesLayout(UniversalGraph> arg0, Collection arg1) {

        this.nodesSubset = arg1;
        this.performGraphLayout(arg0);

    }

    private class BuildDatastructure extends AlgorithmPart {

        protected void run() {
            // Set up nodes
            Collection vertices;
            if (nodesSubset == null) {
                vertices = graph.getNodes();
            } else {
                vertices = nodesSubset;
            }
            for (N v : vertices) {
                LayoutNode node = new LayoutNode();
                Widget w = graph.findWidget(v);
                assert w != null;
                Rectangle r = w.getBounds();
                if (r == null) {
                    r = w.getPreferredBounds();
                }
                Dimension size = r.getSize();
                node.width = (int) size.getWidth();
                node.height = (int) size.getHeight();
                node.vertex = v;
                nodes.add(node);
                vertexToLayoutNode.put(v, node);
            }

            // Set up edges
            Collection> links = graph.getEdges();
            for (EdgeID l : links) {
                LayoutEdge edge = new LayoutEdge();
                assert vertexToLayoutNode.containsKey(graph.getEdgeSource(l).getParent());
                assert vertexToLayoutNode.containsKey(graph.getEdgeTarget(l).getParent());

                if (invert) {
                    edge.to = vertexToLayoutNode.get(graph.getEdgeSource(l).getParent());
                    edge.from = vertexToLayoutNode.get(graph.getEdgeTarget(l).getParent());
                } else {
                    edge.from = vertexToLayoutNode.get(graph.getEdgeSource(l).getParent());
                    edge.to = vertexToLayoutNode.get(graph.getEdgeTarget(l).getParent());
                }
                
                Widget w = graph.findWidget(graph.getEdgeSource(l).getParent());

                assert w != null;
                Rectangle r = w.getBounds();
                if (r == null) {
                    r = w.getPreferredBounds();
                }
                Dimension size = r.getSize();
                edge.relativeFrom = size.height / 2;
                edge.indexFrom = new ArrayList>(graph.getNodePins(graph.getEdgeSource(l).getParent())).indexOf(graph.getEdgeSource(l));
                w = graph.findWidget(graph.getEdgeTarget(l).getParent());
                assert w != null;
                r = w.getBounds();
                if (r == null) {
                    r = w.getPreferredBounds();
                }
                size = r.getSize();
                edge.relativeTo = size.height / 2;
                edge.indexTo = new ArrayList>(graph.getNodePins(graph.getEdgeTarget(l).getParent())).indexOf(graph.getEdgeTarget(l));
                edge.link = l;

                edge.from.succs.add(edge);
                edge.to.preds.add(edge);

            }
        }

        public void postCheck() {

            assert vertexToLayoutNode.keySet().size() == nodes.size();
            assert nodes.size() == graph.getNodes().size();

            for (N v : graph.getNodes()) {

                LayoutNode node = vertexToLayoutNode.get(v);
                assert node != null;

                for (LayoutEdge e : node.succs) {
                    assert e.from == node;
                }

                for (LayoutEdge e : node.preds) {
                    assert e.to == node;
                }
            }
        }
    }

    private class ReverseEdges extends AlgorithmPart {

        private HashSet visited;
        private HashSet active;

        protected void run() {

            // Remove self-edges, TODO: Special treatment
            for (LayoutNode node : nodes) {
                ArrayList succs = new ArrayList(node.succs);
                for (LayoutEdge e : succs) {
                    assert e.from == node;
                    if (e.to == node) {
                        node.succs.remove(e);
                        node.preds.remove(e);
                    }
                }
            }

            // Start DFS and reverse back edges
            visited = new HashSet();
            active = new HashSet();
            for (LayoutNode node : nodes) {
                DFS(node);
            }
        }

        private void DFS(LayoutNode startNode) {
            if (visited.contains(startNode)) {
                return;
            }
            Stack stack = new Stack();
            stack.push(startNode);

            while (!stack.empty()) {
                LayoutNode node = stack.pop();

                if (visited.contains(node)) {
                    // Node no longer active
                    active.remove(node);
                    continue;
                }

                // Repush immediately to know when no longer active
                stack.push(node);
                visited.add(node);
                active.add(node);

                ArrayList succs = new ArrayList(node.succs);
                for (LayoutEdge e : succs) {
                    if (active.contains(e.to)) {
                        assert visited.contains(e.to);
                        // Encountered back edge
                        reverseEdge(e);
                    } else if (!visited.contains(e.to)) {
                        stack.push(e.to);
                    }
                }
            }
        }

        private void reverseAllInputs(LayoutNode node) {
            for (LayoutEdge e : node.preds) {
                assert !reversedLinks.contains(e.link);
                reversedLinks.add(e.link);
                node.succs.add(e);
                e.from.preds.add(e);
                e.from.succs.remove(e);
                int oldRelativeFrom = e.relativeFrom;
                int oldRelativeTo = e.relativeTo;
                e.to = e.from;
                e.from = node;
                e.relativeFrom = oldRelativeTo;
                e.relativeTo = oldRelativeFrom;
            }
            node.preds.clear();
        }

        private void reverseEdge(LayoutEdge e) {
            assert !reversedLinks.contains(e.link);
            reversedLinks.add(e.link);

            LayoutNode oldFrom = e.from;
            LayoutNode oldTo = e.to;
            int oldRelativeFrom = e.relativeFrom;
            int oldRelativeTo = e.relativeTo;

            e.from = oldTo;
            e.to = oldFrom;
            e.relativeFrom = oldRelativeTo;
            e.relativeTo = oldRelativeFrom;

            oldFrom.succs.remove(e);
            oldFrom.preds.add(e);
            oldTo.preds.remove(e);
            oldTo.succs.add(e);
        }

        public void postCheck() {

            for (LayoutNode n : nodes) {

                Queue queue = new LinkedList();
                for (LayoutEdge e : n.succs) {
                    LayoutNode s = e.to;
                    queue.add(s);
                    visited.add(s);
                }

                HashSet visited = new HashSet();
                while (!queue.isEmpty()) {
                    LayoutNode curNode = queue.remove();

                    for (LayoutEdge e : curNode.succs) {
                        assert e.to != n;
                        if (!visited.contains(e.to)) {
                            queue.add(e.to);
                            visited.add(e.to);
                        }
                    }
                }
            }
        }
    }

    private class AssignLayers extends AlgorithmPart {

        public void preCheck() {
            for (LayoutNode n : nodes) {
                assert n.layer == -1;
            }
        }

        protected void run() {
            HashSet set = new HashSet();
            for (LayoutNode n : nodes) {
                if (n.preds.size() == 0) {
                    set.add(n);
                    n.layer = 0;
                }
            }

            int z = 1;
            HashSet newSet = new HashSet();
            HashSet failed = new HashSet();
            while (!set.isEmpty()) {

                newSet.clear();
                failed.clear();

                for (LayoutNode n : set) {

                    for (LayoutEdge se : n.succs) {
                        LayoutNode s = se.to;
                        if (!newSet.contains(s) && !failed.contains(s)) {
                            boolean ok = true;
                            for (LayoutEdge pe : s.preds) {
                                LayoutNode p = pe.from;
                                if (p.layer == -1) {
                                    ok = false;
                                    break;
                                }
                            }

                            if (ok) {
                                newSet.add(s);
                            } else {
                                failed.add(s);
                            }
                        }
                    }

                }

                for (LayoutNode n : newSet) {
                    n.layer = z;
                }

                // Swap sets
                HashSet tmp = set;
                set = newSet;
                newSet = tmp;
                z += 1;
            }

            optimize(set);

            layerCount = z - 1;
        }

        public void optimize(HashSet set) {

            for (LayoutNode n : set) {
                if (n.preds.size() == 0 && n.succs.size() > 0) {
                    int minLayer = n.succs.get(0).to.layer;
                    for (LayoutEdge e : n.succs) {
                        minLayer = Math.min(minLayer, e.to.layer);
                    }

                    n.layer = minLayer - 1;
                }
            }

        }

        public void printStatistics() {
            //for(LayoutNode n : nodes) {
            //	System.out.println(n + " on layer " + n.layer);
            //}
        }

        public void postCheck() {
            for (LayoutNode n : nodes) {
                assert n.layer >= 0;
                assert n.layer < layerCount;
                for (LayoutEdge e : n.succs) {
                    assert e.from.layer < e.to.layer;
                }
            }
        }
    }

    private class CreateDummyNodes extends AlgorithmPart {

        private int oldNodeCount;

        protected void preCheck() {
            for (LayoutNode n : nodes) {
                for (LayoutEdge e : n.succs) {
                    assert e.from != null;
                    assert e.from == n;
                    assert e.from.layer < e.to.layer;
                }

                for (LayoutEdge e : n.preds) {
                    assert e.to != null;
                    assert e.to == n;
                }
            }
        }

        protected void run() {
            oldNodeCount = nodes.size();
            ArrayList currentNodes = new ArrayList(nodes);
            for (LayoutNode n : currentNodes) {
                for (LayoutEdge e : n.succs) {
                    processSingleEdge(e);
                }
            }
        }

        private void processSingleEdge(LayoutEdge e) {
            LayoutNode n = e.from;
            if (e.to.layer > n.layer + 1) {
                LayoutEdge last = e;
                for (int i = n.layer + 1; i < last.to.layer; i++) {
                    last = addBetween(last, i);
                }
            }
        }

        private LayoutEdge addBetween(LayoutEdge e, int layer) {
            LayoutNode n = new LayoutNode();
            n.width = 0;
            n.height = dummyWidth;
            n.layer = layer;
            n.preds.add(e);
            nodes.add(n);
            LayoutEdge result = new LayoutEdge();
            n.succs.add(result);
            result.from = n;
            result.relativeFrom = n.height / 2;
            result.to = e.to;
            result.relativeTo = e.relativeTo;
            e.relativeTo = n.height / 2;
            e.to.preds.remove(e);
            e.to.preds.add(result);
            e.to = n;
            return result;
        }

        public void printStatistics() {
            System.out.println("Dummy nodes created: " + (nodes.size() - oldNodeCount));
        }

        public void postCheck() {
            ArrayList currentNodes = new ArrayList(nodes);
            for (LayoutNode n : currentNodes) {
                for (LayoutEdge e : n.succs) {
                    assert e.from.layer == e.to.layer - 1;
                }
            }

            for (int i = 0; i < layers.length; i++) {
                assert layers[i].size() > 0;
                for (LayoutNode n : layers[i]) {
                    assert n.layer == i;
                }
            }
        }
    }
    private Comparator crossingNodeComparator = new Comparator() {

        public int compare(LayoutNode n1, LayoutNode n2) {
            float f = n1.crossingNumber - n2.crossingNumber;
            if (f < 0) {
                return -1;
            } else if (f > 0) {
                return 1;
            } else {
                return 0;
            }
        }
    };

    private class CrossingReduction extends AlgorithmPart {

        public void preCheck() {
            for (LayoutNode n : nodes) {
                assert n.layer < layerCount;
            }
        }

        protected void run() {

            layers = new List[layerCount];

            for (int i = 0; i < layerCount; i++) {
                layers[i] = new ArrayList();
            }


            // Generate initial ordering
            HashSet visited = new HashSet();
            for (LayoutNode n : nodes) {
                if (n.layer == 0) {
                    layers[0].add(n);
                    visited.add(n);
                } else if (n.preds.size() == 0) {
                    layers[n.layer].add(n);
                    visited.add(n);
                }
            }

            for (int i = 0; i < layers.length - 1; i++) {
                for (LayoutNode n : layers[i]) {
                    for (LayoutEdge e : n.succs) {
                        if (!visited.contains(e.to)) {
                            visited.add(e.to);
                            layers[i + 1].add(e.to);
                        }
                    }
                }
            }


            updatePositions();
//
            // Optimize
            for (int i = 0; i < CROSSING_ITERATIONS; i++) {
                upSweep();
                downSweep();
            }
        }

        private void updatePositions() {

            for (int i = 0; i < layers.length; i++) {
                int z = 0;
                for (LayoutNode n : layers[i]) {
                    n.pos = z;
                    z++;
                }
            }
        }

        private void downSweep() {

            // Downsweep
            for (int i = 1; i < layerCount; i++) {

                for (LayoutNode n : layers[i]) {

                    float sum = 0.0f;
                    for (LayoutEdge e : n.preds) {
                        float cur = e.from.pos;
                        if (e.from.height != 0 && e.relativeFrom != 0) {
                            cur += (float) e.relativeFrom / (float) (e.from.height);
                        }

                        sum += cur;
                    }

                    if (n.preds.size() > 0) {
                        sum /= n.preds.size();
                        n.crossingNumber = sum;
                    //if(n.vertex == null) n.crossingNumber += layers[i].size();
                    } else {
                        n.crossingNumber = Integer.MAX_VALUE;
                    }
                }

                Collections.sort(layers[i], crossingNodeComparator);

                int z = 0;
                for (LayoutNode n : layers[i]) {
                    n.pos = z;
                    z++;
                }
            }
        }

        private void upSweep() {
            // Upsweep
            for (int i = layerCount - 1; i >= 0; i--) {

                for (LayoutNode n : layers[i]) {

                    float sum = 0.0f;
                    for (LayoutEdge e : n.succs) {
                        float cur = e.to.pos;
                        if (e.to.height != 0 && e.relativeTo != 0) {
                            cur += ((float) e.relativeTo - ((float)(e.indexTo * 20))) / (float) (e.to.height);
                        }

                        sum += cur;
                    }

                    if (n.succs.size() > 0) {
                        sum /= n.succs.size();
                        n.crossingNumber = sum;
                    //if(n.vertex == null) n.crossingNumber += layers[i].size();
                    } else {
                        n.crossingNumber = Integer.MAX_VALUE;
                    }

                }

                Collections.sort(layers[i], crossingNodeComparator);

                int z = 0;
                for (LayoutNode n : layers[i]) {
                    n.pos = z;
                    z++;
                }
            }
        }

        private int evaluate() {
            // TODO: Implement efficient evaluate / crossing min
            return 0;
        }

        public void postCheck() {

            HashSet visited = new HashSet();
            for (int i = 0; i < layers.length; i++) {
                for (LayoutNode n : layers[i]) {
                    assert !visited.contains(n);
                    assert n.layer == i;
                    visited.add(n);
                }
            }

        }
    }
    private final Comparator nodePositionComparator = new Comparator() {

        @Override
        public int compare(LayoutNode n1, LayoutNode n2) {
            int res = n1.pos - n2.pos;
            if (res == 0) {
                res = n1.toString().compareTo(n2.toString());
//                if (res == 0) {
//                    res = System.identityHashCode(n1) - System.identityHashCode(n2);
//                }
            }
            return res;
        }
    };
    private final Comparator nodeProcessingDownComparator = new Comparator() {

        @Override
        public int compare(LayoutNode n1, LayoutNode n2) {
            if (n1.vertex == null && n2.vertex == null) {
                int res = n1.toString().compareTo(n2.toString());
//                if (res == 0) {
//                    res = System.identityHashCode(n1) - System.identityHashCode(n2);
//                }
                return res;
            }
            if (n1.vertex == null) {
                return -1;
            }
            if (n2.vertex == null) {
                return 1;
            }
            int res = n1.preds.size() - n2.preds.size();
            if (res == 0 && !n1.preds.isEmpty()) {
                res = res = n1.toString().compareTo(n2.toString());
//                if (res == 0) {
//                    res = System.identityHashCode(n1) - System.identityHashCode(n2);
//                }
            }
            return res;
        }
    };
    private final Comparator nodeProcessingUpComparator = new Comparator() {

        @Override
        public int compare(LayoutNode n1, LayoutNode n2) {
            if (n1.vertex == null && n2.vertex == null) {
                int res = n1.toString().compareTo(n2.toString());
//                if (res == 0) {
//                    res = System.identityHashCode(n1) - System.identityHashCode(n2);
//                }
                return res;
            }
            if (n1.vertex == null) {
                return -1;
            }
            if (n2.vertex == null) {
                return 1;
            }
            int res = n1.succs.size() - n2.succs.size();
            if (res == 0) {
                res = n1.toString().compareTo(n2.toString());
//                if (res == 0) {
//                    res = System.identityHashCode(n1) - System.identityHashCode(n2);
//                }
            }
            return res;
        }
    };

    private class AssignYCoordinates extends AlgorithmPart {

        private ArrayList[] space;
        private ArrayList[] downProcessingOrder;
        private ArrayList[] upProcessingOrder;

        private void initialPositions() {
            for (LayoutNode n : nodes) {
                n.y = space[n.layer].get(n.pos);
            }
        }

        protected void run() {

            space = new ArrayList[layers.length];
            downProcessingOrder = new ArrayList[layers.length];
            upProcessingOrder = new ArrayList[layers.length];

            for (int i = 0; i < layers.length; i++) {
                space[i] = new ArrayList();
                downProcessingOrder[i] = new ArrayList();
                upProcessingOrder[i] = new ArrayList();

                int curY = 0;
                for (LayoutNode n : layers[i]) {
                    space[i].add(curY);
                    curY += n.height + yOffset;
                    downProcessingOrder[i].add(n);
                    upProcessingOrder[i].add(n);
                }

                Collections.sort(downProcessingOrder[i], nodeProcessingDownComparator);
                Collections.sort(upProcessingOrder[i], nodeProcessingUpComparator);
            }

            initialPositions();
            for (int i = 0; i < SWEEP_ITERATIONS; i++) {
//                sweepDown();
//                sweepUp();
//                sweepDown();
            }
//
//            sweepDown();
//            sweepUp();
        }

        private int calculateOptimalDown(LayoutNode n) {

            List values = new ArrayList();
            if (n.preds.size() == 0) {
                return n.y;
            }
            for (LayoutEdge e : n.preds) {
                int cur = e.from.y + e.relativeFrom - e.relativeTo;
                values.add(cur);
            }
            return median(values);
        }

        private int calculateOptimalUp(LayoutNode n) {

            List values = new ArrayList();
            if (n.succs.size() == 0) {
                return n.y;
            }
            for (LayoutEdge e : n.succs) {
                int cur = e.to.y + e.relativeTo - e.relativeFrom;
                values.add(cur);
            }
            return median(values);
        }

        private int median(List values) {
            Collections.sort(values);
            if (values.size() % 2 == 0) {
                return (values.get(values.size() / 2 - 1) + values.get(values.size() / 2)) / 2;
            } else {
                return values.get(values.size() / 2);
            }
        }

        private void sweepUp() {
            for (int i = layers.length - 2; i >= 0; i--) {
                NodeColumn r = new NodeColumn(space[i]);
                for (LayoutNode n : upProcessingOrder[i]) {
                    int optimal = calculateOptimalUp(n);
                    r.insert(n, optimal);
                }
            }
        }

        private void sweepDown() {
            for (int i = 1; i < layers.length; i++) {
                NodeColumn r = new NodeColumn(space[i]);
                for (LayoutNode n : downProcessingOrder[i]) {
                    int optimal = calculateOptimalDown(n);
                    r.insert(n, optimal);
                }
            }
        }
    }

    private class NodeColumn {

        private TreeSet treeSet;
        private ArrayList space;

        public NodeColumn(ArrayList space) {
            treeSet = new TreeSet(nodePositionComparator);
            this.space = space;
        }

        public int offset(LayoutNode n1, LayoutNode n2) {
            int v1 = space.get(n1.pos) + n1.height;
            int v2 = space.get(n2.pos);
            return v2 - v1;
        }

        public void insert(LayoutNode n, int pos) {

            SortedSet headSet = treeSet.headSet(n);
            SortedSet tailSet = treeSet.tailSet(n);

            LayoutNode topNeighbour = null;
            int minY = Integer.MIN_VALUE;
            if (!headSet.isEmpty()) {
                topNeighbour = headSet.last();
                minY = topNeighbour.y + topNeighbour.height + offset(topNeighbour, n);
            }

            LayoutNode bottomNeighbour = null;
            int maxY = Integer.MAX_VALUE;
            if (!tailSet.isEmpty()) {
                bottomNeighbour = tailSet.first();
                maxY = bottomNeighbour.y - offset(n, bottomNeighbour) - n.height;
            }

            assert minY <= maxY;

            if (pos >= minY && pos <= maxY) {
                n.y = pos;
            } else if (Math.abs((long) pos - (long) minY) < Math.abs((long) pos - (long) maxY)) {
                assert minY != Integer.MIN_VALUE;
                n.y = minY;
            } else {
                assert maxY != Integer.MAX_VALUE;
                n.y = maxY;
            }

            treeSet.add(n);
        }
    }

    private class AssignXCoordinates extends AlgorithmPart {

        protected void run() {
            int curX = 0;
            //maxLayerHeight = new int[layers.length];
            for (int i = 0; i < layers.length; i++) {
                int maxWidth = 0;
                int baseLine = 0;
                int bottomBaseLine = 0;
                for (LayoutNode n : layers[i]) {
                    maxWidth = Math.max(maxWidth, n.width - n.xOffset - n.bottomYOffset);
                    baseLine = Math.max(baseLine, n.xOffset);
//                    bottomBaseLine = Math.max(bottomBaseLine, n.bottomYOffset);
                }

                for (LayoutNode n : layers[i]) {
                    if (n.vertex == null) {
                        // Dummy node => set height to line height
                        n.x = curX;
                        n.width = maxWidth + baseLine + bottomBaseLine;
                    } else {
                        n.x = curX + baseLine + (maxWidth - (n.width - n.xOffset - n.bottomYOffset)) / 2 - n.xOffset;
                    }
                }

                curX += maxWidth + baseLine + bottomBaseLine;
                curX += layerOffset;
            }
        }
    }

    private class WriteResult extends AlgorithmPart {

        private int pointCount;

        protected void run() {

            HashMap vertexPositions = new HashMap();
            HashMap, List> linkPositions = new HashMap, List>();
            for (N v : graph.getNodes()) {
                LayoutNode n = vertexToLayoutNode.get(v);
                assert !vertexPositions.containsKey(v);
                vertexPositions.put(v, new Point(n.x + n.xOffset, n.y + n.yOffset));
            }

//            for (LayoutNode n : nodes) {
//
//                for (LayoutEdge e : n.succs) {
//                    if (e.link != null) {
//                        E link = e.link;
//                        ArrayList points = new ArrayList();
//
//                        Point p = new Point(e.from.x + e.relativeFrom, e.from.y + e.from.height - e.from.bottomYOffset);
//                        points.add(p);
//
//                        LayoutNode cur = e.to;
//                        LayoutNode other = e.from;
//                        LayoutEdge curEdge = e;
//                        while (cur.vertex == null && cur.succs.size() != 0) {
//                            //if(points.size() > 1 && points.get(points.size() -1).x == cur.x + cur.width/2 && points.get(points.size() - 2).x == cur.x + cur.width/2) {
//                            //	points.remove(points.size() - 1);
//                            //}
//
//                            points.add(new Point(cur.x + cur.width / 2, cur.y));
//                            //if(points.size() > 1 && points.get(points.size() -1).x == cur.x + cur.width/2 && points.get(points.size() - 2).x == cur.x + cur.width/2) {
//                            //	points.remove(points.size() - 1);
//                            //	}
//                            points.add(new Point(cur.x + cur.width / 2, cur.y + cur.height));
//                            if (cur.succs.size() == 0) {
//                                break;
//                            }
//                            assert cur.succs.size() == 1;
//                            curEdge = cur.succs.get(0);
//                            cur = curEdge.to;
//                        }
//
//                        p = new Point(cur.x + curEdge.relativeTo, cur.y + cur.yOffset);
//                        points.add(p);
//
//                        if (reversedLinks.contains(link)) {
//                            Collections.reverse(points);
//                        }
//
//                        linkPositions.put(e.link, points);
//
//                        // No longer needed!
//                        e.link = null;
//                    }
//                }
//            }

            int minX = Integer.MAX_VALUE;
            int minY = Integer.MAX_VALUE;
            for (N v : vertexPositions.keySet()) {
                Point p = vertexPositions.get(v);
                minX = Math.min(minX, p.x);
                minY = Math.min(minY, p.y);
            }

//            for (E l : linkPositions.keySet()) {
//                List points = linkPositions.get(l);
//                for (Point p : points) {
//                    if (p != null) {
//                        minX = Math.min(minX, p.x);
//                        minY = Math.min(minY, p.y);
//                    }
//                }
//
//            }

            for (N v : vertexPositions.keySet()) {
                Point p = vertexPositions.get(v);
                p.x -= minX;
                p.y -= minY;
                p.x += 50;
                p.y += 50;
                Widget w = graph.findWidget(v);
                if (animate) {
                    graph.getSceneAnimator().animatePreferredLocation(w, p);
                } else {
                    w.setPreferredLocation(p);
                }
            }

//            for (E l : linkPositions.keySet()) {
//                List points = linkPositions.get(l);
//
//                for (Point p : points) {
//                    if (p != null) {
//                        p.x -= minX;
//                        p.y -= minY;
//                    }
//                }
//
//                //Kris - this is a hack to reverse the order of the control points
//                // that were created by the algorithm. This is used when the graph
//                // is inverted.
//                if (invert && points.size() > 3) {
//                    int numPoints = points.size();
//                    ArrayList invertedPoints = new ArrayList(numPoints);
//
//                    invertedPoints.add(points.get(0));
//
//                    for (int i = numPoints - 2; i > 0; i--) {
//                        invertedPoints.add(points.get(i));
//                    }
//
//                    invertedPoints.add(points.get(numPoints - 1));
//
//                    points = invertedPoints;
//                }
//
//                Widget w = graph.getScene().findWidget(l);
//                if (w instanceof ConnectionWidget) {
//                    ConnectionWidget cw = (ConnectionWidget) w;
//                    cw.setControlPoints(points, true);
//                }
//
//            }
            
            graph.getScene().validate();
            graph.getScene().repaint();
            graph.getScene().revalidate();
        }

        protected void printStatistics() {
            System.out.println("Number of nodes: " + nodes.size());
            int edgeCount = 0;
            for (LayoutNode n : nodes) {
                edgeCount += n.succs.size();
            }
            System.out.println("Number of edges: " + edgeCount);
            System.out.println("Number of points: " + pointCount);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy