From 2c0ec7445c7a7d3be77498bec4484468061f4f44 Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Thu, 14 Nov 2024 17:14:28 +0100 Subject: [PATCH 01/30] JDK-8314512 IGV: clean up hierarchical layout code --- .../com/sun/hotspot/igv/data/InputNode.java | 5 - .../java/com/sun/hotspot/igv/graph/Block.java | 44 +- .../hotspot/igv/graph/BlockConnection.java | 5 - .../com/sun/hotspot/igv/graph/Diagram.java | 40 +- .../com/sun/hotspot/igv/graph/Figure.java | 72 +- .../hotspot/igv/graph/FigureConnection.java | 15 +- .../com/sun/hotspot/igv/graph/InputSlot.java | 2 +- .../com/sun/hotspot/igv/graph/OutputSlot.java | 2 +- .../java/com/sun/hotspot/igv/graph/Slot.java | 24 +- .../igv/hierarchicallayout/ClusterEdge.java | 4 - .../ClusterIngoingConnection.java | 4 - .../ClusterInputSlotNode.java | 5 +- .../igv/hierarchicallayout/ClusterNode.java | 32 +- .../ClusterOutgoingConnection.java | 4 - .../ClusterOutputSlotNode.java | 5 +- .../HierarchicalCFGLayoutManager.java | 146 +- .../HierarchicalClusterLayoutManager.java | 90 +- .../HierarchicalLayoutManager.java | 2042 +++++------------ .../HierarchicalStableLayoutManager.java | 12 +- .../InterClusterConnection.java | 4 - .../igv/hierarchicallayout/LayoutEdge.java | 103 +- .../igv/hierarchicallayout/LayoutGraph.java | 583 +++++ .../igv/hierarchicallayout/LayoutLayer.java | 194 ++ .../hierarchicallayout}/LayoutManager.java | 17 +- .../igv/hierarchicallayout/LayoutNode.java | 403 +++- .../LinearLayoutManager.java | 70 - .../igv/hierarchicallayout/Timing.java | 67 - .../com/sun/hotspot/igv/layout/Cluster.java | 13 +- .../sun/hotspot/igv/layout/LayoutGraph.java | 205 -- .../java/com/sun/hotspot/igv/layout/Link.java | 2 - .../com/sun/hotspot/igv/util/Statistics.java | 40 - .../sun/hotspot/igv/view/DiagramScene.java | 506 ++-- .../hotspot/igv/view/EditorTopComponent.java | 11 - .../hotspot/igv/view/widgets/BlockWidget.java | 19 +- .../igv/view/widgets/FigureWidget.java | 113 +- .../igv/view/widgets/InputSlotWidget.java | 14 +- .../hotspot/igv/view/widgets/LineWidget.java | 174 +- .../igv/view/widgets/OutputSlotWidget.java | 19 +- .../hotspot/igv/view/widgets/SlotWidget.java | 33 +- 39 files changed, 2552 insertions(+), 2591 deletions(-) create mode 100644 src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java create mode 100644 src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java rename src/utils/IdealGraphVisualizer/{Layout/src/main/java/com/sun/hotspot/igv/layout => HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout}/LayoutManager.java (68%) delete mode 100644 src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LinearLayoutManager.java delete mode 100644 src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/Timing.java delete mode 100644 src/utils/IdealGraphVisualizer/Layout/src/main/java/com/sun/hotspot/igv/layout/LayoutGraph.java delete mode 100644 src/utils/IdealGraphVisualizer/Util/src/main/java/com/sun/hotspot/igv/util/Statistics.java diff --git a/src/utils/IdealGraphVisualizer/Data/src/main/java/com/sun/hotspot/igv/data/InputNode.java b/src/utils/IdealGraphVisualizer/Data/src/main/java/com/sun/hotspot/igv/data/InputNode.java index f2e1ad16ea691..62b7aa4c03455 100644 --- a/src/utils/IdealGraphVisualizer/Data/src/main/java/com/sun/hotspot/igv/data/InputNode.java +++ b/src/utils/IdealGraphVisualizer/Data/src/main/java/com/sun/hotspot/igv/data/InputNode.java @@ -63,11 +63,6 @@ public boolean equals(Object obj) { Objects.equals(getProperties(), other.getProperties()); } - @Override - public int hashCode() { - return Objects.hash(id, getProperties()); - } - @Override public String toString() { return "Node " + id + " " + getProperties().toString(); diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java index 483ca9d40f040..011fd8239b157 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java @@ -24,11 +24,12 @@ package com.sun.hotspot.igv.graph; import com.sun.hotspot.igv.data.InputBlock; +import com.sun.hotspot.igv.data.InputNode; import com.sun.hotspot.igv.layout.Cluster; -import java.awt.Dimension; +import com.sun.hotspot.igv.layout.Vertex; +import java.awt.Point; import java.awt.Rectangle; -import java.util.HashSet; -import java.util.Set; +import java.util.*; /** * @@ -36,9 +37,9 @@ */ public class Block implements Cluster { - private InputBlock inputBlock; + protected final InputBlock inputBlock; private Rectangle bounds; - private Diagram diagram; + private final Diagram diagram; public Block(InputBlock inputBlock, Diagram diagram) { this.inputBlock = inputBlock; @@ -59,14 +60,31 @@ public Set getSuccessors() { return succs; } - public Dimension getNodeOffset() { - return new Dimension(0, -Figure.getVerticalOffset()); + public List getVertices() { + List vertices = new ArrayList<>(); + for (InputNode inputNode : inputBlock.getNodes()) { + vertices.add(diagram.getFigure(inputNode)); + } + return vertices; } public void setBounds(Rectangle r) { this.bounds = r; } + @Override + public void setPosition(Point p) { + if (bounds != null) { + bounds.setLocation(p); + } + } + + @Override + public Point getPosition() { + return bounds.getLocation(); + } + + @Override public Rectangle getBounds() { return bounds; } @@ -79,5 +97,17 @@ public int compareTo(Cluster o) { public String toString() { return inputBlock.getName(); } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + Block other = (Block) obj; + return inputBlock.equals(other.inputBlock); + } + + @Override + public int hashCode() { + return inputBlock.hashCode(); + } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java index 84b7a4ec09f45..4648734ee80db 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java @@ -85,11 +85,6 @@ public Block getToCluster() { return destinationBlock; } - @Override - public boolean isVIP() { - return true; - } - @Override public List getControlPoints() { return controlPoints; diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Diagram.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Diagram.java index 191996f2ee9f8..fca51f4b53dbf 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Diagram.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Diagram.java @@ -34,7 +34,7 @@ */ public class Diagram { - private List
figures; + private final Map figures; private final Map blocks; private final String nodeText; private final String shortNodeText; @@ -63,7 +63,7 @@ public Diagram(InputGraph graph, String nodeText, String shortNodeText, this.nodeText = nodeText; this.shortNodeText = shortNodeText; this.tinyNodeText = tinyNodeText; - this.figures = new ArrayList<>(); + this.figures = new LinkedHashMap<>(); this.blocks = new LinkedHashMap<>(8); this.blockConnections = new HashSet<>(); this.cfg = false; @@ -81,7 +81,7 @@ public Diagram(InputGraph graph, String nodeText, String shortNodeText, f.getProperties().add(n.getProperties()); f.setBlock(blocks.get(graph.getBlock(n))); figureHash.put(n.getId(), f); - this.figures.add(f); + this.figures.put(n, f); } for (InputEdge e : graph.getEdges()) { @@ -113,7 +113,7 @@ public Diagram(InputGraph graph, String nodeText, String shortNodeText, } } - for (Figure f : figures) { + for (Figure f : figures.values()) { int i = 0; for (InputSlot inputSlot : f.getInputSlots()) { inputSlot.setOriginalIndex(i); @@ -133,6 +133,11 @@ public Block getBlock(InputBlock b) { return blocks.get(b); } + public Figure getFigure(InputNode n) { + assert figures.containsKey(n); + return figures.get(n); + } + public boolean hasBlock(InputBlock b) { return blocks.containsKey(b); } @@ -158,7 +163,7 @@ public Collection getInputBlocks() { } public List
getFigures() { - return Collections.unmodifiableList(figures); + return Collections.unmodifiableList(new ArrayList<>(figures.values())); } public FigureConnection createConnection(InputSlot inputSlot, OutputSlot outputSlot, String label) { @@ -167,7 +172,7 @@ public FigureConnection createConnection(InputSlot inputSlot, OutputSlot outputS return new FigureConnection(inputSlot, outputSlot, label); } - public void removeAllBlocks(Set blocksToRemove) { + public void removeAllBlocks(Collection blocksToRemove) { Set
figuresToRemove = new HashSet<>(); for (Block b : blocksToRemove) { for (Figure f : getFigures()) { @@ -182,18 +187,12 @@ public void removeAllBlocks(Set blocksToRemove) { } } - public void removeAllFigures(Set
figuresToRemove) { + public void removeAllFigures(Collection
figuresToRemove) { for (Figure f : figuresToRemove) { freeFigure(f); - } + figures.remove(f.getInputNode()); - ArrayList
newFigures = new ArrayList<>(); - for (Figure f : this.figures) { - if (!figuresToRemove.contains(f)) { - newFigures.add(f); - } } - figures = newFigures; } private void freeFigure(Figure succ) { @@ -215,15 +214,14 @@ private void freeFigure(Figure succ) { } - public void removeFigure(Figure succ) { - assert this.figures.contains(succ); - freeFigure(succ); - this.figures.remove(succ); + public void removeFigure(Figure figure) { + freeFigure(figure); + this.figures.remove(figure.getInputNode()); } public Set getConnections() { Set connections = new HashSet<>(); - for (Figure f : figures) { + for (Figure f : figures.values()) { for (InputSlot s : f.getInputSlots()) { connections.addAll(s.getConnections()); } @@ -246,7 +244,7 @@ public void printStatistics() { System.out.println("============================================================="); System.out.println("Diagram statistics"); - List
tmpFigures = getFigures(); + Collection
tmpFigures = getFigures(); Set connections = getConnections(); System.out.println("Number of figures: " + tmpFigures.size()); @@ -278,7 +276,7 @@ public int compare(Figure a, Figure b) { } public Figure getRootFigure() { - Properties.PropertySelector
selector = new Properties.PropertySelector<>(figures); + Properties.PropertySelector
selector = new Properties.PropertySelector<>(figures.values()); Figure root = selector.selectSingle(new Properties.StringPropertyMatcher("name", "Root")); if (root == null) { root = selector.selectSingle(new Properties.StringPropertyMatcher("name", "Start")); diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java index f02b4657cea63..4c7d01a4cebb8 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java @@ -33,13 +33,11 @@ public class Figure extends Properties.Entity implements Vertex { - public static final int INSET = 8; public static final int SLOT_WIDTH = 10; - public static final int OVERLAPPING = 6; - public static final int SLOT_START = 4; - public static final int SLOT_OFFSET = 8; - public static final int TOP_CFG_HEIGHT = 7; - public static final int BOTTOM_CFG_HEIGHT = 6; + public static final int SLOT_HEIGHT = 10; + public static final int BORDER = 1; + public static final int PADDING = 4; + public static final int SLOT_OFFSET = 16; public static final int WARNING_WIDTH = 16; public static final double BOLD_LINE_FACTOR = 1.06; protected List inputSlots; @@ -67,23 +65,16 @@ public int getHeight() { } private void updateHeight() { - String nodeText = diagram.getNodeText(); - int lines = nodeText.split("\n").length; - if (hasInputList() && lines > 1) { - lines++; - } - if (getProperties().get("extra_label") != null) { - lines++; - } - heightCash = lines * metrics.getHeight() + INSET; - if (diagram.isCFG()) { - if (hasNamedInputSlot()) { - heightCash += TOP_CFG_HEIGHT; - } - if (hasNamedOutputSlot()) { - heightCash += BOTTOM_CFG_HEIGHT; - } + heightCash = getLines().length * metrics.getHeight() + 2 * PADDING; + heightCash += getSlotsHeight(); + } + + public int getSlotsHeight() { + int slotHeight = 0; + if (hasNamedInputSlot() || hasNamedOutputSlot()) { + slotHeight += diagram.isCFG() ? 2 * Figure.SLOT_HEIGHT : Figure.SLOT_HEIGHT; } + return slotHeight; } public static List getAllBefore(List inputList, T tIn) { @@ -117,19 +108,20 @@ public void setWidth(int width) { } private void updateWidth() { - int max = 0; - for (String s : getLines()) { - int cur = metrics.stringWidth(s); - if (cur > max) { - max = cur; - } + widthCash = 0; + for (String s : getLines()) { + int cur = metrics.stringWidth(s); + if (cur > widthCash) { + widthCash = cur; } - widthCash = (int)(max * BOLD_LINE_FACTOR) + INSET; - if (getWarning() != null) { - widthCash += WARNING_WIDTH; - } - widthCash = Math.max(widthCash, Figure.getSlotsWidth(inputSlots)); - widthCash = Math.max(widthCash, Figure.getSlotsWidth(outputSlots)); + } + widthCash += 2 * PADDING; + if (getWarning() != null) { + widthCash += WARNING_WIDTH + PADDING; + } + widthCash = Math.max(widthCash, Figure.getSlotsWidth(inputSlots)); + widthCash = Math.max(widthCash, Figure.getSlotsWidth(outputSlots)); + widthCash = (int)(widthCash * BOLD_LINE_FACTOR); } protected Figure(Diagram diagram, int id, InputNode node) { @@ -231,10 +223,9 @@ public InputNode getInputNode() { return inputNode; } - public InputSlot createInputSlot() { + public void createInputSlot() { InputSlot slot = new InputSlot(this, -1); inputSlots.add(slot); - return slot; } public void removeSlot(Slot s) { @@ -341,7 +332,6 @@ public void updateLines() { } inputLabel = nodeTinyLabel; } - assert(inputLabel != null); int gapSize = is.gapSize(); if (gapSize == 1) { inputs.add("_"); @@ -379,8 +369,8 @@ public void updateLines() { @Override public Dimension getSize() { - int width = Math.max(getWidth(), Figure.SLOT_WIDTH * (Math.max(inputSlots.size(), outputSlots.size()) + 1)); - int height = getHeight() + (diagram.isCFG() ? 0 : 2 * Figure.SLOT_WIDTH - 2 * Figure.OVERLAPPING); + int width = getWidth(); + int height = getHeight(); return new Dimension(width, height); } @@ -402,10 +392,6 @@ public String toString() { return idString; } - public static int getVerticalOffset() { - return Figure.SLOT_WIDTH - Figure.OVERLAPPING; - } - public Cluster getCluster() { return block; } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java index 186a6b63f1760..e26c618f49e3a 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java @@ -41,7 +41,7 @@ public class FigureConnection implements Connection { private Color color; private ConnectionStyle style; private List controlPoints; - private String label; + private final String label; protected FigureConnection(InputSlot inputSlot, OutputSlot outputSlot, String label) { this.inputSlot = inputSlot; @@ -121,6 +121,14 @@ public Port getFrom() { return outputSlot; } + public Figure getFromFigure() { + return outputSlot.getFigure(); + } + + public Figure getToFigure() { + return inputSlot.getFigure(); + } + @Override public Cluster getFromCluster() { return getFrom().getVertex().getCluster(); @@ -136,11 +144,6 @@ public Cluster getToCluster() { return getTo().getVertex().getCluster(); } - @Override - public boolean isVIP() { - return style == ConnectionStyle.BOLD; - } - @Override public List getControlPoints() { return controlPoints; diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java index 71c29aa8db11f..2ba127cc8f707 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java @@ -72,7 +72,7 @@ public Point getRelativePosition() { int gap = getFigure().getWidth() - Figure.getSlotsWidth(getFigure().getInputSlots()); double gapRatio = (double)gap / (double)(getFigure().getInputSlots().size() + 1); int gapAmount = (int)((getPosition() + 1)*gapRatio); - return new Point(gapAmount + Figure.getSlotsWidth(Figure.getAllBefore(getFigure().getInputSlots(), this)) + getWidth()/2, -Figure.SLOT_START); + return new Point(gapAmount + Figure.getSlotsWidth(Figure.getAllBefore(getFigure().getInputSlots(), this)) + getWidth()/2, 0); } @Override diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java index 7b1f9cd1a64d7..a2555ce36584b 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java @@ -54,7 +54,7 @@ public Point getRelativePosition() { } double gapRatio = (double)gap / (double)(getFigure().getOutputSlots().size() + 1); int gapAmount = (int)((getPosition() + 1)*gapRatio); - return new Point(gapAmount + Figure.getSlotsWidth(Figure.getAllBefore(getFigure().getOutputSlots(), this)) + getWidth()/2, Figure.SLOT_START); + return new Point(gapAmount + Figure.getSlotsWidth(Figure.getAllBefore(getFigure().getOutputSlots(), this)) + getWidth()/2, 0); } @Override diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java index f5c0b2c2ff499..1ddc145bb89f9 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java @@ -45,14 +45,13 @@ */ public abstract class Slot implements Port, Source.Provider, Properties.Provider { - private int wantedIndex; - private Source source; + private final int wantedIndex; + private final Source source; protected List connections; - private InputNode associatedNode; private Color color; private String text; private String shortName; - private Figure figure; + private final Figure figure; protected Slot(Figure figure, int wantedIndex) { this.figure = figure; @@ -80,13 +79,10 @@ public Properties getProperties() { } public static final Comparator slotIndexComparator = Comparator.comparingInt(o -> o.wantedIndex); - public void setAssociatedNode(InputNode node) { - associatedNode = node; - } - public int getWidth() { - if (shortName == null || shortName.length() <= 1) { - return Figure.SLOT_WIDTH; + assert shortName != null; + if (shortName.isEmpty()) { + return 0; } else { BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); Graphics g = image.getGraphics(); @@ -96,8 +92,8 @@ public int getWidth() { } } - public int getWantedIndex() { - return wantedIndex; + public int getHeight() { + return Figure.SLOT_HEIGHT; } @Override @@ -111,7 +107,6 @@ public String getText() { public void setShortName(String s) { assert s != null; -// assert s.length() <= 2; this.shortName = s; } @@ -138,7 +133,7 @@ public String getToolTipText() { } public boolean shouldShowName() { - return getShortName() != null && getShortName().length() > 0; + return getShortName() != null && !getShortName().isEmpty(); } public boolean hasSourceNodes() { @@ -153,7 +148,6 @@ public void setText(String s) { } public Figure getFigure() { - assert figure != null; return figure; } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterEdge.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterEdge.java index a7266ebd5fa2a..166d14b419abd 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterEdge.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterEdge.java @@ -70,10 +70,6 @@ public List getControlPoints() { return points; } - public boolean isVIP() { - return false; - } - @Override public String toString() { return from + "->" + to; diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterIngoingConnection.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterIngoingConnection.java index 4d97b06d3c310..70c34e71ca9c7 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterIngoingConnection.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterIngoingConnection.java @@ -70,8 +70,4 @@ public void setControlPoints(List p) { public List getControlPoints() { return controlPoints; } - - public boolean isVIP() { - return false; - } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterInputSlotNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterInputSlotNode.java index 22c5abda75cde..da6842c791ce8 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterInputSlotNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterInputSlotNode.java @@ -54,7 +54,6 @@ public ClusterInputSlotNode(ClusterNode n, String id) { n.addSubNode(this); final Vertex thisNode = this; - final ClusterNode thisBlockNode = blockNode; outputSlot = new Port() { @@ -76,13 +75,13 @@ public String toString() { public Point getRelativePosition() { Point p = new Point(thisNode.getPosition()); - p.x += blockNode.getBorder(); + p.x += ClusterNode.PADDING; p.y = 0; return p; } public Vertex getVertex() { - return thisBlockNode; + return blockNode; } @Override diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterNode.java index 80151e69d0657..382fec9b651fa 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterNode.java @@ -29,6 +29,7 @@ import com.sun.hotspot.igv.layout.Vertex; import java.awt.Dimension; import java.awt.Point; +import java.awt.Rectangle; import java.util.*; /** @@ -37,6 +38,7 @@ */ public class ClusterNode implements Vertex { + public static final int PADDING = 8; private Cluster cluster; private Port inputSlot; private final Set subNodes; @@ -45,21 +47,15 @@ public class ClusterNode implements Vertex { private final Set subEdges; private boolean root; private final String name; - private final int border; - private final Dimension nodeOffset; private final int headerVerticalSpace; private final Dimension emptySize; - public ClusterNode(Cluster cluster, String name, int border, - Dimension nodeOffset, int headerVerticalSpace, - Dimension emptySize) { + public ClusterNode(Cluster cluster, String name, int headerVerticalSpace, Dimension emptySize) { this.subNodes = new HashSet<>(); this.subEdges = new HashSet<>(); this.cluster = cluster; this.position = new Point(0, 0); this.name = name; - this.border = border; - this.nodeOffset = nodeOffset; this.headerVerticalSpace = headerVerticalSpace; this.emptySize = emptySize; if (emptySize.width > 0 || emptySize.height > 0) { @@ -67,8 +63,8 @@ public ClusterNode(Cluster cluster, String name, int border, } } - public ClusterNode(Cluster cluster, String name) { - this(cluster, name, 20, new Dimension(0, 0), 0, new Dimension(0, 0)); + public void updateClusterBounds() { + cluster.setBounds(new Rectangle(position, size)); } public String getName() { @@ -145,22 +141,22 @@ private void calculateSize() { // Normalize coordinates for (Vertex n : subNodes) { - n.setPosition(new Point(n.getPosition().x - minX + nodeOffset.width, - n.getPosition().y - minY + nodeOffset.height + headerVerticalSpace)); + n.setPosition(new Point(n.getPosition().x - minX, + n.getPosition().y - minY + headerVerticalSpace)); } for (Link l : subEdges) { List points = new ArrayList<>(l.getControlPoints()); for (Point p : points) { p.x -= minX; - p.y -= minY; + p.y = p.y - minY + headerVerticalSpace; } l.setControlPoints(points); } - size.width += 2 * border; - size.height += 2 * border; + size.width += 2 * PADDING; + size.height += 2 * PADDING; } public Port getInputSlot() { @@ -181,7 +177,7 @@ public void setPosition(Point pos) { this.position = pos; for (Vertex n : subNodes) { Point cur = new Point(n.getPosition()); - cur.translate(pos.x + border, pos.y + border); + cur.translate(pos.x + PADDING, pos.y + PADDING); n.setPosition(cur); } @@ -191,7 +187,7 @@ public void setPosition(Point pos) { for (Point p : arr) { if (p != null) { Point p2 = new Point(p); - p2.translate(pos.x + border, pos.y + border); + p2.translate(pos.x + PADDING, pos.y + PADDING); newArr.add(p2); } else { newArr.add(null); @@ -218,10 +214,6 @@ public boolean isRoot() { return root; } - public int getBorder() { - return border; - } - public int compareTo(Vertex o) { return toString().compareTo(o.toString()); } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutgoingConnection.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutgoingConnection.java index b63ef4bb1eee3..a5267020a0e72 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutgoingConnection.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutgoingConnection.java @@ -69,8 +69,4 @@ public void setControlPoints(List p) { public List getControlPoints() { return intermediatePoints; } - - public boolean isVIP() { - return false; - } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutputSlotNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutputSlotNode.java index 2073efc9fcfa0..b1eb6b0578b2a 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutputSlotNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutputSlotNode.java @@ -63,7 +63,6 @@ public ClusterOutputSlotNode(ClusterNode n, String id) { n.addSubNode(this); final Vertex thisNode = this; - final ClusterNode thisBlockNode = blockNode; inputSlot = new Port() { @@ -85,13 +84,13 @@ public String toString() { public Point getRelativePosition() { Point p = new Point(thisNode.getPosition()); - p.x += blockNode.getBorder(); + p.x += ClusterNode.PADDING; p.y = 0; return p; } public Vertex getVertex() { - return thisBlockNode; + return blockNode; } @Override diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java index 3d4e8f87b273d..baa4bbb458534 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java @@ -23,22 +23,20 @@ */ package com.sun.hotspot.igv.hierarchicallayout; -import com.sun.hotspot.igv.layout.LayoutManager; import com.sun.hotspot.igv.layout.*; import java.awt.*; import java.util.*; -public class HierarchicalCFGLayoutManager implements LayoutManager { +public class HierarchicalCFGLayoutManager extends LayoutManager { - private static final int BLOCK_BORDER = 5; private final FontMetrics fontMetrics; - // Lays out nodes within a single cluster (basic block). - private LayoutManager subManager; - // Lays out clusters in the CFG. - private LayoutManager manager; - private Set clusters; + private final HierarchicalLayoutManager manager; + private final Set clusters; + private final Set clusterLinks; - public HierarchicalCFGLayoutManager() { + public HierarchicalCFGLayoutManager(Set clusterLinks, Set clusters) { + this.clusterLinks = clusterLinks; + this.clusters = clusters; // Anticipate block label sizes to dimension blocks appropriately. Canvas canvas = new Canvas(); Font font = new Font("Arial", Font.BOLD, 14); @@ -59,95 +57,101 @@ public void setManager(LayoutManager manager) { this.manager = manager; } - public void setClusters(Set clusters) { - this.clusters = clusters; + @Override + public void setCutEdges(boolean enable) { + manager.setCutEdges(enable); } - public void doLayout(LayoutGraph graph, Set importantLinks) { - doLayout(graph); + Map clusterNodesMap; + Map clusterEdgesMap; + + private static void doLinearLayout(ClusterNode clusterNode) { + Cluster cluster = clusterNode.getCluster(); + LayoutGraph graph = new LayoutGraph(clusterNode.getSubEdges(), clusterNode.getSubNodes()); + int curY = 0; + for (Vertex vertex : cluster.getVertices()) { + if (graph.containsVertex(vertex)) { + vertex.setPosition(new Point(0, curY)); + curY += vertex.getSize().height; + } + } + clusterNode.updateSize(); } public void doLayout(LayoutGraph graph) { - // Create cluster-level nodes and edges. - Map clusterNode = createClusterNodes(graph); - Set clusterEdges = createClusterEdges(clusterNode); - markRootClusters(clusterEdges); + clusterNodesMap = createClusterNodes(graph.getVertices()); + assert clusterNodesMap.size() == clusters.size(); + clusterEdgesMap = createClusterEdges(clusterNodesMap); + assert clusterEdgesMap.size() == clusterLinks.size(); // Compute layout for each cluster. - for (Cluster c : clusters) { - ClusterNode n = clusterNode.get(c); - subManager.doLayout(new LayoutGraph(n.getSubEdges(), n.getSubNodes()), new HashSet<>()); - n.updateSize(); + for (ClusterNode clusterNode : clusterNodesMap.values()) { + doLinearLayout(clusterNode); + } + + // mark root nodes + LayoutGraph clusterGraph = new LayoutGraph(clusterEdgesMap.values(), clusterNodesMap.values()); + for (Vertex rootVertex : clusterGraph.findRootVertices()) { + assert rootVertex instanceof ClusterNode; + ((ClusterNode) rootVertex).setRoot(true); } // Compute inter-cluster layout. - manager.doLayout(new LayoutGraph(clusterEdges, new HashSet<>(clusterNode.values())), new HashSet<>()); + manager.doLayout(clusterGraph); // Write back results. - writeBackClusterBounds(clusterNode); - writeBackClusterEdgePoints(graph, clusterEdges); + writeBackClusterBounds(); + writeBackClusterEdgePoints(); } - private Map createClusterNodes(LayoutGraph graph) { - Map clusterNode = new HashMap<>(); - for (Cluster c : clusters) { - String blockLabel = "B" + c; - Dimension emptySize = new Dimension(fontMetrics.stringWidth(blockLabel) + BLOCK_BORDER * 2, - fontMetrics.getHeight() + BLOCK_BORDER); - ClusterNode cn = new ClusterNode(c, c.toString(), BLOCK_BORDER, c.getNodeOffset(), - fontMetrics.getHeight(), emptySize); - clusterNode.put(c, cn); + private Map createClusterNodes(SortedSet vertices) { + Map clusterNodes = new HashMap<>(); + for (Cluster cluster : clusters) { + String blockLabel = "B" + cluster; + Dimension emptySize = new Dimension(fontMetrics.stringWidth(blockLabel) + ClusterNode.PADDING, + fontMetrics.getHeight() + ClusterNode.PADDING); + ClusterNode clusterNode = new ClusterNode(cluster, cluster.toString(), fontMetrics.getHeight(), emptySize); + clusterNodes.put(cluster, clusterNode); } - for (Vertex v : graph.getVertices()) { - Cluster c = v.getCluster(); - assert c != null : "Cluster of vertex " + v + " is null!"; - clusterNode.get(c).addSubNode(v); + for (Vertex vertex : vertices) { + Cluster cluster = vertex.getCluster(); + clusterNodes.get(cluster).addSubNode(vertex); } - return clusterNode; + return clusterNodes; } - private Set createClusterEdges(Map clusterNode) { - Set clusterEdges = new HashSet<>(); - for (Cluster c : clusters) { - ClusterNode start = clusterNode.get(c); - for (Cluster succ : c.getSuccessors()) { - ClusterNode end = clusterNode.get(succ); - if (end != null) { - ClusterEdge e = new ClusterEdge(start, end); - clusterEdges.add(e); - } - } - } - return clusterEdges; - } + private Map createClusterEdges(Map clusterNodes) { + Map clusterEdges = new HashMap<>(); - private void markRootClusters(Set clusterEdges) { - Set roots = new LayoutGraph(clusterEdges).findRootVertices(); - for (Vertex v : roots) { - assert v instanceof ClusterNode; - ((ClusterNode) v).setRoot(true); + for (Link clusterLink : clusterLinks) { + ClusterNode fromClusterNode = clusterNodes.get(clusterLink.getFromCluster()); + ClusterNode toClusterNode = clusterNodes.get(clusterLink.getToCluster()); + assert fromClusterNode != null; + assert toClusterNode != null; + clusterEdges.put(clusterLink, new ClusterEdge(fromClusterNode, toClusterNode)); } + + return clusterEdges; } - private void writeBackClusterBounds(Map clusterNode) { - for (Cluster c : clusters) { - ClusterNode n = clusterNode.get(c); - c.setBounds(new Rectangle(n.getPosition(), n.getSize())); + private void writeBackClusterBounds() { + assert clusterNodesMap.size() == clusters.size(); + for (ClusterNode clusterNode : clusterNodesMap.values()) { + clusterNode.updateClusterBounds(); } } - private void writeBackClusterEdgePoints(LayoutGraph graph, Set clusterEdges) { - // Map from "primitive" cluster edges to their input links. - Map, Link> inputLink = new HashMap<>(); - for (Link l : graph.getLinks()) { - inputLink.put(new AbstractMap.SimpleEntry<>(l.getFromCluster(), l.getToCluster()), l); - } - for (ClusterEdge ce : clusterEdges) { - assert (ce.getControlPoints() != null); - Link l = inputLink.get(new AbstractMap.SimpleEntry<>(ce.getFromCluster(), ce.getToCluster())); - l.setControlPoints(ce.getControlPoints()); + private void writeBackClusterEdgePoints() { + assert clusterEdgesMap.size() == clusterLinks.size(); + for (Link clusterLink : clusterLinks) { + ClusterEdge clusterEdge = clusterEdgesMap.get(clusterLink); + if (clusterEdge.getControlPoints() != null) { + clusterLink.setControlPoints(clusterEdge.getControlPoints()); + } else { + clusterLink.setControlPoints(new ArrayList<>()); + } } } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java index 1d103585e58ba..9dba4d03c8bd5 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java @@ -24,23 +24,22 @@ package com.sun.hotspot.igv.hierarchicallayout; import com.sun.hotspot.igv.layout.*; -import java.awt.Point; -import java.awt.Rectangle; +import java.awt.*; import java.util.*; +import java.util.List; /** * * @author Thomas Wuerthinger */ -public class HierarchicalClusterLayoutManager implements LayoutManager { +public class HierarchicalClusterLayoutManager extends LayoutManager { - private HierarchicalLayoutManager.Combine combine; - private LayoutManager subManager = new HierarchicalLayoutManager(combine); - private LayoutManager manager = new HierarchicalLayoutManager(combine); - private static final boolean TRACE = false; + private final LayoutManager subManager; + private final LayoutManager manager; - public HierarchicalClusterLayoutManager(HierarchicalLayoutManager.Combine combine) { - this.combine = combine; + public HierarchicalClusterLayoutManager() { + this.manager = new HierarchicalLayoutManager(); + this.subManager = new HierarchicalLayoutManager(); } @Override @@ -62,9 +61,6 @@ public void setManager(LayoutManager manager) { } public void doLayout(LayoutGraph graph) { - - assert graph.verify(); - HashMap> listsConnection = new HashMap<>(); HashMap> clusterInputSlotHash = new HashMap<>(); HashMap> clusterOutputSlotHash = new HashMap<>(); @@ -79,22 +75,35 @@ public void doLayout(LayoutGraph graph) { HashMap linkClusterIngoingConnection = new HashMap<>(); Set clusterNodeSet = new HashSet<>(); - Set cluster = graph.getClusters(); + Set clusters = new TreeSet<>(); + for (Vertex v : graph.getVertices()) { + if (v.getCluster() != null) { + clusters.add(v.getCluster()); + } + } + int z = 0; - for (Cluster c : cluster) { + for (Cluster c : clusters) { listsConnection.put(c, new ArrayList<>()); clusterInputSlotHash.put(c, new HashMap<>()); clusterOutputSlotHash.put(c, new HashMap<>()); clusterOutputSlotSet.put(c, new TreeSet<>()); clusterInputSlotSet.put(c, new TreeSet<>()); - ClusterNode cn = new ClusterNode(c, "" + z); + + String blockLabel = "B" + c; + Canvas canvas = new Canvas(); + FontMetrics fontMetrics = canvas.getFontMetrics(TITLE_FONT); + Dimension emptySize = new Dimension(fontMetrics.stringWidth(blockLabel) + ClusterNode.PADDING * 2, + fontMetrics.getHeight() + ClusterNode.PADDING * 2); + ClusterNode cn = new ClusterNode(c, "" + z, fontMetrics.getHeight(), emptySize); + clusterNodes.put(c, cn); clusterNodeSet.add(cn); z++; } // Add cluster edges - for (Cluster c : cluster) { + for (Cluster c : clusters) { ClusterNode start = clusterNodes.get(c); @@ -123,13 +132,6 @@ public void doLayout(LayoutGraph graph) { Cluster fromCluster = fromVertex.getCluster(); Cluster toCluster = toVertex.getCluster(); - Port samePort = null; - if (combine == HierarchicalLayoutManager.Combine.SAME_INPUTS) { - samePort = toPort; - } else if (combine == HierarchicalLayoutManager.Combine.SAME_OUTPUTS) { - samePort = fromPort; - } - assert listsConnection.containsKey(fromCluster); assert listsConnection.containsKey(toCluster); @@ -137,23 +139,19 @@ public void doLayout(LayoutGraph graph) { listsConnection.get(fromCluster).add(l); clusterNodes.get(fromCluster).addSubEdge(l); } else { - ClusterInputSlotNode inputSlotNode = null; - ClusterOutputSlotNode outputSlotNode = null; + ClusterInputSlotNode inputSlotNode; + ClusterOutputSlotNode outputSlotNode; - if (samePort != null) { - outputSlotNode = clusterOutputSlotHash.get(fromCluster).get(samePort); - inputSlotNode = clusterInputSlotHash.get(toCluster).get(samePort); - } + outputSlotNode = clusterOutputSlotHash.get(fromCluster).get(fromPort); + inputSlotNode = clusterInputSlotHash.get(toCluster).get(fromPort); if (outputSlotNode == null) { - outputSlotNode = new ClusterOutputSlotNode(clusterNodes.get(fromCluster), "Out " + fromCluster.toString() + " " + samePort); + outputSlotNode = new ClusterOutputSlotNode(clusterNodes.get(fromCluster), "Out " + fromCluster.toString() + " " + fromPort); clusterOutputSlotSet.get(fromCluster).add(outputSlotNode); ClusterOutgoingConnection conn = new ClusterOutgoingConnection(outputSlotNode, l); outputSlotNode.setOutgoingConnection(conn); clusterNodes.get(fromCluster).addSubEdge(conn); - if (samePort != null) { - clusterOutputSlotHash.get(fromCluster).put(samePort, outputSlotNode); - } + clusterOutputSlotHash.get(fromCluster).put(fromPort, outputSlotNode); linkClusterOutgoingConnection.put(l, conn); } else { @@ -161,13 +159,13 @@ public void doLayout(LayoutGraph graph) { } if (inputSlotNode == null) { - inputSlotNode = new ClusterInputSlotNode(clusterNodes.get(toCluster), "In " + toCluster.toString() + " " + samePort); + inputSlotNode = new ClusterInputSlotNode(clusterNodes.get(toCluster), "In " + toCluster.toString() + " " + fromPort); clusterInputSlotSet.get(toCluster).add(inputSlotNode); } ClusterIngoingConnection conn = new ClusterIngoingConnection(inputSlotNode, l); clusterNodes.get(toCluster).addSubEdge(conn); - clusterInputSlotHash.get(toCluster).put(samePort, inputSlotNode); + clusterInputSlotHash.get(toCluster).put(fromPort, inputSlotNode); linkClusterIngoingConnection.put(l, conn); @@ -178,16 +176,9 @@ public void doLayout(LayoutGraph graph) { } } - Timing t = null; - - if (TRACE) { - t = new Timing("Child timing"); - t.start(); - } - - for (Cluster c : cluster) { + for (Cluster c : clusters) { ClusterNode n = clusterNodes.get(c); - subManager.doLayout(new LayoutGraph(n.getSubEdges(), n.getSubNodes()), new HashSet<>()); + subManager.doLayout(new LayoutGraph(n.getSubEdges(), n.getSubNodes())); n.updateSize(); } @@ -197,20 +188,13 @@ public void doLayout(LayoutGraph graph) { ((ClusterNode) v).setRoot(true); } - manager.doLayout(new LayoutGraph(clusterEdges, clusterNodeSet), interClusterEdges); + manager.doLayout(new LayoutGraph(clusterEdges, clusterNodeSet)); - for (Cluster c : cluster) { + for (Cluster c : clusters) { ClusterNode n = clusterNodes.get(c); c.setBounds(new Rectangle(n.getPosition(), n.getSize())); } - // TODO: handle case where blocks are not fully connected - - if (TRACE) { - t.stop(); - t.print(); - } - for (Link l : graph.getLinks()) { if (linkInterClusterConnection.containsKey(l)) { diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java index 996fd8955775a..69a1251fd0e73 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,117 +23,23 @@ */ package com.sun.hotspot.igv.hierarchicallayout; -import com.sun.hotspot.igv.layout.LayoutGraph; -import com.sun.hotspot.igv.layout.LayoutManager; +import static com.sun.hotspot.igv.hierarchicallayout.LayoutEdge.LAYOUT_EDGE_LAYER_COMPARATOR; +import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.*; import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Vertex; -import com.sun.hotspot.igv.util.Statistics; -import java.awt.Dimension; import java.awt.Point; import java.util.*; -/** - * - * @author Thomas Wuerthinger - */ -public class HierarchicalLayoutManager implements LayoutManager { - - public static final boolean TRACE = false; - public static final boolean CHECK = false; - public static final int SWEEP_ITERATIONS = 1; - public static final int CROSSING_ITERATIONS = 2; - public static final int DUMMY_HEIGHT = 1; - public static final int DUMMY_WIDTH = 1; - public static final int X_OFFSET = 8; - public static final int LAYER_OFFSET = 8; - public static final int MAX_LAYER_LENGTH = -1; - public static final int MIN_LAYER_DIFFERENCE = 1; - public static final int VIP_BONUS = 10; - - public enum Combine { - - NONE, - SAME_INPUTS, - SAME_OUTPUTS - } - // Options - private final Combine combine; - private final int dummyWidth; - private final int dummyHeight; - private int xOffset; - private int layerOffset; - private int maxLayerLength; - private int minLayerDifference; - private boolean layoutSelfEdges; - // Algorithm global datastructures - private Set reversedLinks; - private Set selfEdges; - private List nodes; - private HashMap vertexToLayoutNode; - private HashMap> reversedLinkStartPoints; - private HashMap> reversedLinkEndPoints; - private HashMap> splitStartPoints; - private HashMap> splitEndPoints; - private LayoutGraph graph; - private List[] layers; - private int layerCount; - private Set importantLinks; - private final Set linksToFollow; - - private abstract static 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() { - } - } +public class HierarchicalLayoutManager extends LayoutManager { - public HierarchicalLayoutManager(Combine b) { - this.combine = b; - this.dummyWidth = DUMMY_WIDTH; - this.dummyHeight = DUMMY_HEIGHT; - this.xOffset = X_OFFSET; - this.layerOffset = LAYER_OFFSET; - this.maxLayerLength = MAX_LAYER_LENGTH; - this.minLayerDifference = MIN_LAYER_DIFFERENCE; - this.layoutSelfEdges = false; - this.linksToFollow = new HashSet<>(); + public HierarchicalLayoutManager() { + setCutEdges(false); } - public void setXOffset(int xOffset) { - this.xOffset = xOffset; - } - - public void setLayerOffset(int layerOffset) { - this.layerOffset = layerOffset; + @Override + public void setCutEdges(boolean enable) { + maxLayerLength = enable ? 10 : -1; } @Override @@ -141,1582 +47,724 @@ public void setCutEdges(boolean enable) { maxLayerLength = enable ? 10 : -1; } - public void setMinLayerDifference(int v) { - minLayerDifference = v; - } + // STEP 2: Assign layers and create dummy nodes + LayerManager.apply(layoutGraph, maxLayerLength); - public void setLayoutSelfEdges(boolean layoutSelfEdges) { - this.layoutSelfEdges = layoutSelfEdges; - } + // STEP 3: Crossing Reduction + CrossingReduction.apply(layoutGraph); - public List getNodes() { - return nodes; - } + // STEP 4: Assign X coordinates + AssignXCoordinates.apply(layoutGraph); - // Remove self-edges, possibly saving them into the selfEdges set. - private void removeSelfEdges(boolean save) { - for (LayoutNode node : nodes) { - for (LayoutEdge e : new ArrayList<>(node.succs)) { - if (e.to == node) { - if (save) { - selfEdges.add(e); - } - node.succs.remove(e); - node.preds.remove(e); - } - } - } + // STEP 5: Write back to interface + WriteResult.apply(layoutGraph); } - @Override - public void doLayout(LayoutGraph graph) { - doLayout(graph, new HashSet<>()); + static private class ReverseEdges { - } + static public void apply(LayoutGraph graph) { + removeSelfEdges(graph); + reverseRootInputs(graph); + depthFirstSearch(graph); - @Override - public void doLayout(LayoutGraph graph, Set importantLinks) { - - this.importantLinks = importantLinks; - this.graph = graph; - - vertexToLayoutNode = new HashMap<>(); - reversedLinks = new HashSet<>(); - selfEdges = new HashSet<>(); - reversedLinkStartPoints = new HashMap<>(); - reversedLinkEndPoints = new HashMap<>(); - nodes = new ArrayList<>(); - splitStartPoints = new HashMap<>(); - splitEndPoints = new HashMap<>(); - - // ############################################################# - // Step 1: Build up data structure - new BuildDatastructure().start(); - - if (!layoutSelfEdges) { - // Remove self-edges from the beginning. - removeSelfEdges(false); + for (LayoutNode node : graph.getLayoutNodes()) { + node.computeReversedLinkPoints(); + } } - // ############################################################# - // STEP 2: Reverse edges, handle backedges - new ReverseEdges().start(); - - for (LayoutNode n : nodes) { - ArrayList tmpArr = new ArrayList<>(); - for (LayoutEdge e : n.succs) { - if (importantLinks.contains(e.link)) { - tmpArr.add(e); + private static void removeSelfEdges(LayoutGraph graph) { + for (LayoutNode node : graph.getLayoutNodes()) { + Iterator edgeIterator = node.getSuccs().iterator(); + while (edgeIterator.hasNext()) { + LayoutEdge edge = edgeIterator.next(); + if (edge.getTo() == node) { + edgeIterator.remove(); + node.getPreds().remove(edge); + } } } + } - for (LayoutEdge e : tmpArr) { - e.from.succs.remove(e); - e.to.preds.remove(e); + private static void reverseRootInputs(LayoutGraph graph) { + for (LayoutNode node : graph.getLayoutNodes()) { + if (node.getVertex().isRoot()) { + for (LayoutEdge predEdge : new ArrayList<>(node.getPreds())) { + reverseEdge(predEdge); + } + } } } - // Hide self-edges from the layout algorithm and save them for later. - removeSelfEdges(true); - - // ############################################################# - // STEP 3: Assign layers - new AssignLayers().start(); + public static void reverseEdge(LayoutEdge edge) { + edge.reverse(); - // ############################################################# - // STEP 4: Create dummy nodes - new CreateDummyNodes().start(); + LayoutNode fromNode = edge.getFrom(); + LayoutNode toNode = edge.getTo(); + int relativeFrom = edge.getRelativeFromX(); + int relativeTo = edge.getRelativeToX(); - // ############################################################# - // STEP 5: Crossing Reduction - new CrossingReduction().start(); + edge.setFrom(toNode); + edge.setTo(fromNode); + edge.setRelativeFromX(relativeTo); + edge.setRelativeToX(relativeFrom); - // ############################################################# - // STEP 7: Assign X coordinates - new AssignXCoordinates().start(); - - // ############################################################# - // STEP 6: Assign Y coordinates - new AssignYCoordinates().start(); - - // Put saved self-edges back so that they are assigned points. - for (LayoutEdge e : selfEdges) { - e.from.succs.add(e); - e.to.preds.add(e); + fromNode.getSuccs().remove(edge); + fromNode.getPreds().add(edge); + toNode.getPreds().remove(edge); + toNode.getSuccs().add(edge); } - // ############################################################# - // STEP 8: Write back to interface - new WriteResult().start(); - } - - private class WriteResult extends AlgorithmPart { - - private int pointCount; - - @Override - protected void run() { - - HashMap vertexPositions = new HashMap<>(); - HashMap> linkPositions = new HashMap<>(); - for (Vertex v : graph.getVertices()) { - 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.preds) { - if (e.link != null && !linkPositions.containsKey(e.link)) { - ArrayList points = new ArrayList<>(); - - Point p = new Point(e.to.x + e.relativeTo, e.to.y + e.to.yOffset + e.link.getTo().getRelativePosition().y); - points.add(p); - if (e.to.inOffsets.containsKey(e.relativeTo)) { - points.add(new Point(p.x, p.y + e.to.inOffsets.get(e.relativeTo) + e.link.getTo().getRelativePosition().y)); - } - - LayoutNode cur = e.from; - LayoutNode other = e.to; - LayoutEdge curEdge = e; - while (cur.vertex == null && cur.preds.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 + cur.height)); - 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)); - assert cur.preds.size() == 1; - curEdge = cur.preds.get(0); - cur = curEdge.from; - } - - p = new Point(cur.x + curEdge.relativeFrom, cur.y + cur.height - cur.bottomYOffset + (curEdge.link == null ? 0 : curEdge.link.getFrom().getRelativePosition().y)); - if (curEdge.from.outOffsets.containsKey(curEdge.relativeFrom)) { - points.add(new Point(p.x, p.y + curEdge.from.outOffsets.get(curEdge.relativeFrom) + (curEdge.link == null ? 0 : curEdge.link.getFrom().getRelativePosition().y))); - } - points.add(p); - - Collections.reverse(points); - - if (cur.vertex == null && cur.preds.size() == 0) { - - if (reversedLinkEndPoints.containsKey(e.link)) { - for (Point p1 : reversedLinkEndPoints.get(e.link)) { - points.add(new Point(p1.x + e.to.x, p1.y + e.to.y)); - } - } + private static void depthFirstSearch(LayoutGraph graph) { + Set visited = new HashSet<>(); + Set active = new HashSet<>(); - if (splitStartPoints.containsKey(e.link)) { - points.add(0, null); - points.addAll(0, splitStartPoints.get(e.link)); + for (LayoutNode startNode : graph.getLayoutNodes()) { + Deque stack = new ArrayDeque<>(); + stack.push(startNode); - //checkPoints(points); - if (reversedLinks.contains(e.link)) { - Collections.reverse(points); - } - assert !linkPositions.containsKey(e.link); - linkPositions.put(e.link, points); - } else { - splitEndPoints.put(e.link, points); - } + while (!stack.isEmpty()) { + LayoutNode node = stack.pop(); - } else { - if (reversedLinks.contains(e.link)) { - Collections.reverse(points); - if (selfEdges.contains(e)) { - // For self edges, it is enough with the - // start and end points computed by ReverseEdges. - points.clear(); - } - } - if (reversedLinkStartPoints.containsKey(e.link)) { - for (Point p1 : reversedLinkStartPoints.get(e.link)) { - points.add(new Point(p1.x + cur.x, p1.y + cur.y)); - } - } + if (visited.contains(node)) { + active.remove(node); + continue; + } - if (reversedLinkEndPoints.containsKey(e.link)) { - for (Point p1 : reversedLinkEndPoints.get(e.link)) { - points.add(0, new Point(p1.x + other.x, p1.y + other.y)); - } - } + stack.push(node); + visited.add(node); + active.add(node); - assert !linkPositions.containsKey(e.link); - linkPositions.put(e.link, points); + for (LayoutEdge edge : new ArrayList<>(node.getSuccs())) { + LayoutNode successor = edge.getTo(); + if (active.contains(successor)) { + reverseEdge(edge); + } else if (!visited.contains(successor)) { + stack.push(successor); } - pointCount += points.size(); } } + } + } + } - for (LayoutEdge e : n.succs) { - if (e.link != null && !linkPositions.containsKey(e.link)) { - ArrayList points = new ArrayList<>(); - Point p = new Point(e.from.x + e.relativeFrom, e.from.y + e.from.height - e.from.bottomYOffset + e.link.getFrom().getRelativePosition().y); - points.add(p); - if (e.from.outOffsets.containsKey(e.relativeFrom)) { - Point pOffset = new Point(p.x, p.y + e.from.outOffsets.get(e.relativeFrom) + - e.link.getFrom().getRelativePosition().y + e.from.yOffset); - if (!pOffset.equals(p)) { - points.add(pOffset); - } - } - - LayoutNode cur = e.to; - LayoutNode other = e.from; - LayoutEdge curEdge = e; - while (cur.vertex == null && !cur.succs.isEmpty()) { - 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.isEmpty()) { - 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 + ((curEdge.link == null) ? 0 : curEdge.link.getTo().getRelativePosition().y)); - points.add(p); - if (curEdge.to.inOffsets.containsKey(curEdge.relativeTo)) { - points.add(new Point(p.x, p.y + curEdge.to.inOffsets.get(curEdge.relativeTo) + ((curEdge.link == null) ? 0 : curEdge.link.getTo().getRelativePosition().y))); - } - if (cur.succs.isEmpty() && cur.vertex == null) { - if (reversedLinkStartPoints.containsKey(e.link)) { - for (Point p1 : reversedLinkStartPoints.get(e.link)) { - points.add(0, new Point(p1.x + other.x, p1.y + other.y)); - } - } + static private class LayerManager { - if (splitEndPoints.containsKey(e.link)) { - points.add(null); - points.addAll(splitEndPoints.get(e.link)); + private static void assignLayerDownwards(LayoutGraph graph) { + ArrayList workingList = new ArrayList<>(); - if (reversedLinks.contains(e.link)) { - Collections.reverse(points); - } - assert !linkPositions.containsKey(e.link); - linkPositions.put(e.link, points); - } else { - splitStartPoints.put(e.link, points); - } - } else { + // add all root nodes to layer 0 + for (LayoutNode node : graph.getLayoutNodes()) { + if (!node.hasPreds()) { + workingList.add(node); + node.setLayer(0); + } + } - if (reversedLinkStartPoints.containsKey(e.link)) { - for (Point p1 : reversedLinkStartPoints.get(e.link)) { - points.add(0, new Point(p1.x + other.x + other.xOffset, p1.y + other.y)); - } - } - if (reversedLinkEndPoints.containsKey(e.link)) { - for (Point p1 : reversedLinkEndPoints.get(e.link)) { - points.add(new Point(p1.x + cur.x + cur.xOffset, p1.y + cur.y)); + // assign layers downwards starting from roots + int layer = 1; + while (!workingList.isEmpty()) { + ArrayList newWorkingList = new ArrayList<>(); + for (LayoutNode node : workingList) { + for (LayoutEdge succEdge : node.getSuccs()) { + LayoutNode succNode = succEdge.getTo(); + if (succNode.getLayer() == -1) { + // This node was not assigned before. + boolean assignedPred = true; + for (LayoutEdge predEdge : succNode.getPreds()) { + LayoutNode predNode = predEdge.getFrom(); + if (predNode.getLayer() == -1 || predNode.getLayer() >= layer) { + // This now has an unscheduled successor or a successor that was scheduled only in this round. + assignedPred = false; + break; } } - if (reversedLinks.contains(e.link)) { - Collections.reverse(points); + if (assignedPred) { + // This successor node can be assigned. + succNode.setLayer(layer); + newWorkingList.add(succNode); } - assert !linkPositions.containsKey(e.link); - linkPositions.put(e.link, points); } - - pointCount += points.size(); } } + workingList = newWorkingList; + layer++; } - int minX = Integer.MAX_VALUE; - int minY = Integer.MAX_VALUE; - for (Vertex v : vertexPositions.keySet()) { - Point p = vertexPositions.get(v); - minX = Math.min(minX, p.x); - minY = Math.min(minY, p.y); - } - - for (Link 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); - } - } - + int layerCount = layer - 1; + for (LayoutNode n : graph.getLayoutNodes()) { + n.setLayer((layerCount - 1 - n.getLayer())); } + } - for (Vertex v : vertexPositions.keySet()) { - Point p = vertexPositions.get(v); - p.x -= minX; - p.y -= minY; - v.setPosition(p); - } + private static void assignLayerUpwards(LayoutGraph graph) { + ArrayList workingList = new ArrayList<>(); + // add all leaves to working list, reset layer of non-leave nodes + for (LayoutNode node : graph.getLayoutNodes()) { + if (!node.hasSuccs()) { + workingList.add(node); + } else { + node.setLayer(-1); + } + } + + // assign layer upwards starting from leaves + // sinks non-leave nodes down as much as possible + int layer = 1; + while (!workingList.isEmpty()) { + ArrayList newWorkingList = new ArrayList<>(); + for (LayoutNode node : workingList) { + if (node.getLayer() < layer) { + for (LayoutEdge predEdge : node.getPreds()) { + LayoutNode predNode = predEdge.getFrom(); + if (predNode.getLayer() == -1) { + // This node was not assigned before. + boolean assignedSucc = true; + for (LayoutEdge succEdge : predNode.getSuccs()) { + LayoutNode succNode = succEdge.getTo(); + if (succNode.getLayer() == -1 || succNode.getLayer() >= layer) { + // This now has an unscheduled successor or a successor that was scheduled only in this round. + assignedSucc = false; + break; + } + } - for (Link l : linkPositions.keySet()) { - List points = linkPositions.get(l); - for (Point p : points) { - if (p != null) { - p.x -= minX; - p.y -= minY; + if (assignedSucc) { + // This predecessor node can be assigned. + predNode.setLayer(layer); + newWorkingList.add(predNode); + } + } + } + } else { + newWorkingList.add(node); } } - l.setControlPoints(points); + workingList = newWorkingList; + layer++; } - } - @Override - protected void printStatistics() { - System.out.println("Number of nodes: " + nodes.size()); - int edgeCount = 0; - for (LayoutNode n : nodes) { - edgeCount += n.succs.size(); + int layerCount = layer - 1; + for (LayoutNode n : graph.getLayoutNodes()) { + n.setLayer((layerCount - 1 - n.getLayer())); } - System.out.println("Number of edges: " + edgeCount); - System.out.println("Number of points: " + pointCount); - } - } - public static final Comparator nodePositionComparator = Comparator.comparingInt(n -> n.pos); - public static final Comparator nodeProcessingDownComparator = (n1, n2) -> { - int n1VIP = 0; - for (LayoutEdge e : n1.preds) { - if (e.vip) { - n1VIP++; - } - } - int n2VIP = 0; - for (LayoutEdge e : n2.preds) { - if (e.vip) { - n2VIP++; - } - } - if (n1VIP != n2VIP) { - return n2VIP - n1VIP; + graph.initLayers(layerCount); } - if (n1.vertex == null) { - if (n2.vertex == null) { - return 0; - } - return -1; - } - if (n2.vertex == null) { - return 1; - } - return n1.preds.size() - n2.preds.size(); - }; - public static final Comparator nodeProcessingUpComparator = (n1, n2) -> { - int n1VIP = 0; - for (LayoutEdge e : n1.succs) { - if (e.vip) { - n1VIP++; - } - } - int n2VIP = 0; - for (LayoutEdge e : n2.succs) { - if (e.vip) { - n2VIP++; - } - } - if (n1VIP != n2VIP) { - return n2VIP - n1VIP; - } - if (n1.vertex == null) { - if (n2.vertex == null) { - return 0; - } - return -1; - } - if (n2.vertex == null) { - return 1; - } - return n1.succs.size() - n2.succs.size(); - }; - private class AssignXCoordinates extends AlgorithmPart { - - private ArrayList[] space; - private ArrayList[] downProcessingOrder; - private ArrayList[] upProcessingOrder; - - private void initialPositions() { - for (LayoutNode n : nodes) { - n.x = space[n.layer].get(n.pos); - } - } - @SuppressWarnings("unchecked") - private void createArrays() { - space = new ArrayList[layers.length]; - downProcessingOrder = new ArrayList[layers.length]; - upProcessingOrder = new ArrayList[layers.length]; + static private void assignLayers(LayoutGraph graph) { + assignLayerDownwards(graph); + assignLayerUpwards(graph); } - @Override - protected void run() { - createArrays(); - - for (int i = 0; i < layers.length; i++) { - space[i] = new ArrayList<>(); - downProcessingOrder[i] = new ArrayList<>(); - upProcessingOrder[i] = new ArrayList<>(); - - int curX = 0; - for (LayoutNode n : layers[i]) { - space[i].add(curX); - curX += n.width + xOffset; - downProcessingOrder[i].add(n); - upProcessingOrder[i].add(n); - } - - downProcessingOrder[i].sort(nodeProcessingDownComparator); - upProcessingOrder[i].sort(nodeProcessingUpComparator); - } - - initialPositions(); - for (int i = 0; i < SWEEP_ITERATIONS; i++) { - sweepDown(); - adjustSpace(); - sweepUp(); - adjustSpace(); - } + static private void createDummyNodes(LayoutGraph graph, int maxLayerLength) { + List layoutNodes = new ArrayList<>(graph.getLayoutNodes()); + layoutNodes.sort(LAYOUT_NODE_DEGREE_COMPARATOR); - sweepDown(); - adjustSpace(); - sweepUp(); - } - - private void adjustSpace() { - for (int i = 0; i < layers.length; i++) { - for (LayoutNode n : layers[i]) { - space[i].add(n.x); + // Generate initial ordering + HashSet visited = new HashSet<>(); + for (LayoutNode layoutNode : layoutNodes) { + if (layoutNode.getLayer() == 0) { + graph.getLayer(0).add(layoutNode); + visited.add(layoutNode); + } else if (!layoutNode.hasPreds()) { + graph.getLayer(layoutNode.getLayer()).add(layoutNode); + visited.add(layoutNode); } } - } - private int calculateOptimalDown(LayoutNode n) { - int size = n.preds.size(); - if (size == 0) { - return n.x; - } - int vipCount = 0; - for (LayoutEdge e : n.preds) { - if (e.vip) { - vipCount++; - } + for (LayoutNode layoutNode : layoutNodes) { + createDummiesForNodeSuccessor(graph, layoutNode, maxLayerLength); } - if (vipCount == 0) { - int[] values = new int[size]; - for (int i = 0; i < size; i++) { - LayoutEdge e = n.preds.get(i); - values[i] = e.from.x + e.relativeFrom - e.relativeTo; - } - return Statistics.median(values); - } else { - int z = 0; - int[] values = new int[vipCount]; - for (int i = 0; i < size; i++) { - LayoutEdge e = n.preds.get(i); - if (e.vip) { - values[z++] = e.from.x + e.relativeFrom - e.relativeTo; + for (int i = 0; i < graph.getLayerCount() - 1; i++) { + for (LayoutNode n : graph.getLayer(i)) { + for (LayoutEdge e : n.getSuccs()) { + if (e.getTo().isDummy()) continue; + if (!visited.contains(e.getTo())) { + visited.add(e.getTo()); + graph.getLayer(i + 1).add(e.getTo()); + e.getTo().setLayer(i + 1); + } } } - return Statistics.median(values); } } - private int calculateOptimalBoth(LayoutNode n) { - if (n.preds.size() == n.succs.size()) { - return n.x; - } - - int[] values = new int[n.preds.size() + n.succs.size()]; - int i = 0; + static private void createDummiesForNodeSuccessor(LayoutGraph graph, LayoutNode layoutNode, int maxLayerLength) { + HashMap> portsToUnprocessedEdges = new HashMap<>(); + ArrayList succs = new ArrayList<>(layoutNode.getSuccs()); + HashMap portToTopNode = new HashMap<>(); + HashMap> portToBottomNodeMapping = new HashMap<>(); + for (LayoutEdge succEdge : succs) { + int startPort = succEdge.getRelativeFromX(); + LayoutNode fromNode = succEdge.getFrom(); + LayoutNode toNode = succEdge.getTo(); - for (LayoutEdge e : n.preds) { - values[i] = e.from.x + e.relativeFrom - e.relativeTo; - i++; - } - - for (LayoutEdge e : n.succs) { - values[i] = e.to.x + e.relativeTo - e.relativeFrom; - i++; - } + // edge is longer than one layer => needs dummy nodes + if (fromNode.getLayer() != toNode.getLayer() - 1) { + // the edge needs to be cut + if (maxLayerLength != -1 && toNode.getLayer() - fromNode.getLayer() > maxLayerLength) { + // remove the succEdge before replacing it + toNode.getPreds().remove(succEdge); + fromNode.getSuccs().remove(succEdge); - return Statistics.median(values); - } - - private int calculateOptimalUp(LayoutNode n) { - int size = n.succs.size(); - if (size == 0) { - return n.x; - } - int[] values = new int[size]; - for (int i = 0; i < size; i++) { - LayoutEdge e = n.succs.get(i); - values[i] = e.to.x + e.relativeTo - e.relativeFrom; - if (e.vip) { - return values[i]; - } - } - return Statistics.median(values); - } + LayoutNode topCutNode = portToTopNode.get(startPort); + if (topCutNode == null) { + topCutNode = new LayoutNode(); + topCutNode.setLayer(fromNode.getLayer() + 1); + graph.addNodeToLayer(topCutNode, topCutNode.getLayer()); + portToTopNode.put(startPort, topCutNode); + portToBottomNodeMapping.put(startPort, new HashMap<>()); + } + LayoutEdge edgeToTopCut = new LayoutEdge(fromNode, topCutNode, succEdge.getRelativeFromX(), topCutNode.getWidth() / 2, succEdge.getLink()); + if (succEdge.isReversed()) edgeToTopCut.reverse(); + fromNode.getSuccs().add(edgeToTopCut); + topCutNode.getPreds().add(edgeToTopCut); + + HashMap layerToBottomNode = portToBottomNodeMapping.get(startPort); + LayoutNode bottomCutNode = layerToBottomNode.get(toNode.getLayer()); + if (bottomCutNode == null) { + bottomCutNode = new LayoutNode(); + bottomCutNode.setLayer(toNode.getLayer() - 1); + graph.addNodeToLayer(bottomCutNode, bottomCutNode.getLayer()); + layerToBottomNode.put(toNode.getLayer(), bottomCutNode); + } + LayoutEdge bottomEdge = new LayoutEdge(bottomCutNode, toNode, bottomCutNode.getWidth() / 2, succEdge.getRelativeToX(), succEdge.getLink()); + if (succEdge.isReversed()) bottomEdge.reverse(); + toNode.getPreds().add(bottomEdge); + bottomCutNode.getSuccs().add(bottomEdge); + + } else { // the edge is not cut, but needs dummy nodes + portsToUnprocessedEdges.putIfAbsent(startPort, new ArrayList<>()); + portsToUnprocessedEdges.get(startPort).add(succEdge); + } + } + } + + for (Map.Entry> portToUnprocessedEdges : portsToUnprocessedEdges.entrySet()) { + Integer startPort = portToUnprocessedEdges.getKey(); + List unprocessedEdges = portToUnprocessedEdges.getValue(); + unprocessedEdges.sort(LAYOUT_EDGE_LAYER_COMPARATOR); + + if (unprocessedEdges.size() == 1) { + // process a single edge + LayoutEdge singleEdge = unprocessedEdges.get(0); + LayoutNode fromNode = singleEdge.getFrom(); + if (singleEdge.getTo().getLayer() > fromNode.getLayer() + 1) { + LayoutEdge previousEdge = singleEdge; + for (int i = fromNode.getLayer() + 1; i < previousEdge.getTo().getLayer(); i++) { + LayoutNode dummyNode = new LayoutNode(); + dummyNode.setLayer(i); + dummyNode.getPreds().add(previousEdge); + graph.addNodeToLayer(dummyNode, dummyNode.getLayer()); + LayoutEdge dummyEdge = new LayoutEdge(dummyNode, previousEdge.getTo(), dummyNode.getWidth() / 2, previousEdge.getRelativeToX(), singleEdge.getLink()); + if (previousEdge.isReversed()) dummyEdge.reverse(); + dummyNode.getSuccs().add(dummyEdge); + previousEdge.setRelativeToX(dummyNode.getWidth() / 2); + previousEdge.getTo().getPreds().remove(previousEdge); + previousEdge.getTo().getPreds().add(dummyEdge); + previousEdge.setTo(dummyNode); + previousEdge = dummyEdge; + } + } + } else { + int lastLayer = unprocessedEdges.get(unprocessedEdges.size() - 1).getTo().getLayer(); + int dummyCnt = lastLayer - layoutNode.getLayer() - 1; + LayoutEdge[] newDummyEdges = new LayoutEdge[dummyCnt]; + LayoutNode[] newDummyNodes = new LayoutNode[dummyCnt]; - private void sweepUp() { - for (int i = layers.length - 1; i >= 0; i--) { - NodeRow r = new NodeRow(space[i]); - for (LayoutNode n : upProcessingOrder[i]) { - int optimal = calculateOptimalUp(n); - r.insert(n, optimal); + newDummyNodes[0] = new LayoutNode(); + newDummyNodes[0].setLayer(layoutNode.getLayer() + 1); + newDummyEdges[0] = new LayoutEdge(layoutNode, newDummyNodes[0], startPort, newDummyNodes[0].getWidth() / 2, null); + newDummyNodes[0].getPreds().add(newDummyEdges[0]); + layoutNode.getSuccs().add(newDummyEdges[0]); + for (int j = 1; j < dummyCnt; j++) { + newDummyNodes[j] = new LayoutNode(); + newDummyNodes[j].setLayer(layoutNode.getLayer() + j + 1); + newDummyEdges[j] = new LayoutEdge(newDummyNodes[j - 1], newDummyNodes[j], null); + newDummyNodes[j].getPreds().add(newDummyEdges[j]); + newDummyNodes[j - 1].getSuccs().add(newDummyEdges[j]); + } + for (LayoutEdge unprocessedEdge : unprocessedEdges) { + LayoutNode anchorNode = newDummyNodes[unprocessedEdge.getTo().getLayer() - layoutNode.getLayer() - 2]; + anchorNode.getSuccs().add(unprocessedEdge); + unprocessedEdge.setFrom(anchorNode); + unprocessedEdge.setRelativeFromX(anchorNode.getWidth() / 2); + layoutNode.getSuccs().remove(unprocessedEdge); + } + for (LayoutNode dummyNode : newDummyNodes) { + graph.addNodeToLayer(dummyNode, dummyNode.getLayer()); + } } } } - private void sweepDown() { - for (int i = 1; i < layers.length; i++) { - NodeRow r = new NodeRow(space[i]); - for (LayoutNode n : downProcessingOrder[i]) { - int optimal = calculateOptimalDown(n); - r.insert(n, optimal); - } - } + static public void apply(LayoutGraph graph, int maxLayerLength) { + assignLayers(graph); + createDummyNodes(graph, maxLayerLength); + graph.updatePositions(); } } - public static class NodeRow { - - private final TreeSet treeSet; - private final ArrayList space; - - public NodeRow(ArrayList space) { - treeSet = new TreeSet<>(nodePositionComparator); - this.space = space; - } - - public int offset(LayoutNode n1, LayoutNode n2) { - int v1 = space.get(n1.pos) + n1.width; - int v2 = space.get(n2.pos); - return v2 - v1; - } - - public void insert(LayoutNode n, int pos) { - - SortedSet headSet = treeSet.headSet(n); - - LayoutNode leftNeighbor; - int minX = Integer.MIN_VALUE; - if (!headSet.isEmpty()) { - leftNeighbor = headSet.last(); - minX = leftNeighbor.x + leftNeighbor.width + offset(leftNeighbor, n); - } + private static class CrossingReduction { - if (pos < minX) { - n.x = minX; - } else { - - LayoutNode rightNeighbor; - SortedSet tailSet = treeSet.tailSet(n); - int maxX = Integer.MAX_VALUE; - if (!tailSet.isEmpty()) { - rightNeighbor = tailSet.first(); - maxX = rightNeighbor.x - offset(n, rightNeighbor) - n.width; - } - - n.x = Math.min(pos, maxX); - - assert minX <= maxX : minX + " vs " + maxX; + public static void apply(LayoutGraph graph) { + for (int i = 0; i < CROSSING_ITERATIONS; i++) { + downSweep(graph); + upSweep(graph); } - - treeSet.add(n); + downSweep(graph); + graph.updatePositions(); } - } - private static final Comparator crossingNodeComparator = Comparator.comparingInt(n -> n.crossingNumber); - private class CrossingReduction extends AlgorithmPart { - - @Override - public void preCheck() { - for (LayoutNode n : nodes) { - assert n.layer < layerCount; + private static void doAveragePositions(LayoutLayer layer) { + for (LayoutNode node : layer) { + node.setWeightedPosition(node.averagePosition()); } - } - - @SuppressWarnings("unchecked") - private void createLayers() { - layers = new List[layerCount]; - - for (int i = 0; i < layerCount; i++) { - layers[i] = new ArrayList<>(); + layer.sort(CROSSING_NODE_COMPARATOR); + int x = 0; + for (LayoutNode n : layer) { + n.setWeightedPosition(x); + x += n.getOuterWidth() + NODE_OFFSET; } } - @Override - protected void run() { - createLayers(); - - // 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.isEmpty()) { - layers[n.layer].add(n); - visited.add(n); + private static void doMedianPositions(LayoutLayer layer, boolean usePred) { + for (LayoutNode node : layer) { + int size = usePred ? node.getPreds().size() : node.getSuccs().size(); + if (size == 0) continue; + float[] values = new float[size]; + for (int j = 0; j < size; j++) { + LayoutNode predNode = usePred ? node.getPreds().get(j).getFrom() : node.getSuccs().get(j).getTo(); + values[j] = predNode.getWeightedPosition(); } - } - - 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); - if (!nodes.contains(e.to)) { - nodes.add(e.to); - } - } - } + Arrays.sort(values); + if (values.length % 2 == 0) { + node.setWeightedPosition((values[size / 2 - 1] + values[size / 2]) / 2); + } else { + node.setWeightedPosition(values[size / 2]); } } - - updatePositions(); - - initX(); - - // Optimize - for (int i = 0; i < CROSSING_ITERATIONS; i++) { - downSweep(); - upSweep(); + layer.sort(CROSSING_NODE_COMPARATOR); + int x = 0; + for (LayoutNode n : layer) { + n.setWeightedPosition(x); + x += n.getOuterWidth() + NODE_OFFSET; } - downSweep(); } - private void initX() { - - for (int i = 0; i < layers.length; i++) { - updateXOfLayer(i); + private static void placeLeavesAndRoots(LayoutLayer layer, boolean usePred) { + // Nodes that have no adjacent nodes on the neighboring layer: + // leave fixed in their current positions with non-fixed nodes sorted into the remaining positions + for (int j = 0; j < layer.size(); j++) { + LayoutNode node = layer.get(j); + if (usePred ? !node.hasPreds() : !node.hasSuccs()) { + float prevWeight = (j > 0) ? layer.get(j - 1).getWeightedPosition() : 0; + float nextWeight = (j < layer.size() - 1) ? layer.get(j + 1).getWeightedPosition() : 0; + node.setWeightedPosition((prevWeight + nextWeight) / 2); + } } - } - - private void updateXOfLayer(int index) { + layer.sort(CROSSING_NODE_COMPARATOR); int x = 0; - - for (LayoutNode n : layers[index]) { - n.x = x; - x += n.width + xOffset; + for (LayoutNode n : layer) { + n.setWeightedPosition(x); + x += n.getOuterWidth() + NODE_OFFSET; } } - private void updatePositions() { - for (List layer : layers) { - int z = 0; - for (LayoutNode n : layer) { - n.pos = z; - z++; - } + private static void downSweep(LayoutGraph graph) { + for (int i = 0; i < graph.getLayerCount(); i++) { + doAveragePositions(graph.getLayer(i)); } - } - - private void downSweep() { - - // Downsweep - for (int i = 1; i < layerCount; i++) { - - for (LayoutNode n : layers[i]) { - n.crossingNumber = 0; - } - - for (LayoutNode n : layers[i]) { - - int sum = 0; - int count = 0; - for (LayoutEdge e : n.preds) { - int cur = e.from.x + e.relativeFrom; - int factor = 1; - if (e.vip) { - factor = VIP_BONUS; - } - sum += cur * factor; - count += factor; - } - - if (count > 0) { - sum /= count; - n.crossingNumber = sum; - } - } - - updateCrossingNumbers(i, true); - layers[i].sort(crossingNodeComparator); - updateXOfLayer(i); - - int z = 0; - for (LayoutNode n : layers[i]) { - n.pos = z; - z++; - } + for (int i = 1; i < graph.getLayerCount(); i++) { + doMedianPositions(graph.getLayer(i), true); + placeLeavesAndRoots(graph.getLayer(i), true); } } - private void updateCrossingNumbers(int index, boolean down) { - for (int i = 0; i < layers[index].size(); i++) { - LayoutNode n = layers[index].get(i); - LayoutNode prev = null; - if (i > 0) { - prev = layers[index].get(i - 1); - } - LayoutNode next = null; - if (i < layers[index].size() - 1) { - next = layers[index].get(i + 1); - } - - boolean cond = n.succs.isEmpty(); - if (down) { - cond = n.preds.isEmpty(); - } - - if (cond) { - - if (prev != null && next != null) { - n.crossingNumber = (prev.crossingNumber + next.crossingNumber) / 2; - } else if (prev != null) { - n.crossingNumber = prev.crossingNumber; - } else if (next != null) { - n.crossingNumber = next.crossingNumber; - } - } + private static void upSweep(LayoutGraph graph) { + for (int i = graph.getLayerCount() - 1; i >= 0; i--) { + doAveragePositions(graph.getLayer(i)); + } + for (int i = graph.getLayerCount() - 2; i >= 0; i--) { + doMedianPositions(graph.getLayer(i), false); + placeLeavesAndRoots(graph.getLayer(i), false); } } + } - private void upSweep() { - // Upsweep - for (int i = layerCount - 2; i >= 0; i--) { - - for (LayoutNode n : layers[i]) { - n.crossingNumber = 0; - } - - for (LayoutNode n : layers[i]) { - - int count = 0; - int sum = 0; - for (LayoutEdge e : n.succs) { - int cur = e.to.x + e.relativeTo; - int factor = 1; - if (e.vip) { - factor = VIP_BONUS; - } - sum += cur * factor; - count += factor; - } - - if (count > 0) { - sum /= count; - n.crossingNumber = sum; - } - - } - - updateCrossingNumbers(i, false); - layers[i].sort(crossingNodeComparator); - updateXOfLayer(i); - - int z = 0; - for (LayoutNode n : layers[i]) { - n.pos = z; - z++; + private static class AssignXCoordinates { + + static int[][] space; + static LayoutNode[][] downProcessingOrder; + static LayoutNode[][] upProcessingOrder; + + static private void createArrays(LayoutGraph graph) { + space = new int[graph.getLayerCount()][]; + downProcessingOrder = new LayoutNode[graph.getLayerCount()][]; + upProcessingOrder = new LayoutNode[graph.getLayerCount()][]; + for (int i = 0; i < graph.getLayerCount(); i++) { + LayoutLayer layer = graph.getLayer(i); + space[i] = new int[layer.size()]; + downProcessingOrder[i] = new LayoutNode[layer.size()]; + upProcessingOrder[i] = new LayoutNode[layer.size()]; + int curX = 0; + for (int j = 0; j < layer.size(); j++) { + space[i][j] = curX; + LayoutNode node = layer.get(j); + curX += node.getOuterWidth() + NODE_OFFSET; + downProcessingOrder[i][j] = node; + upProcessingOrder[i][j] = node; } + Arrays.sort(downProcessingOrder[i], NODE_PROCESSING_DOWN_COMPARATOR); + Arrays.sort(upProcessingOrder[i], NODE_PROCESSING_UP_COMPARATOR); } } - @Override - 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); - } + static private void initialPositions(LayoutGraph graph) { + for (LayoutNode layoutNode : graph.getLayoutNodes()) { + layoutNode.setX(space[layoutNode.getLayer()][layoutNode.getPos()]); + } + for (LayoutNode dummyNode : graph.getDummyNodes()) { + dummyNode.setX(space[dummyNode.getLayer()][dummyNode.getPos()]); } - } - } - - private class AssignYCoordinates extends AlgorithmPart { - - @Override - protected void run() { - int curY = 0; - - for (List layer : layers) { - int maxHeight = 0; - int baseLine = 0; - int bottomBaseLine = 0; - for (LayoutNode n : layer) { - maxHeight = Math.max(maxHeight, n.height - n.yOffset - n.bottomYOffset); - baseLine = Math.max(baseLine, n.yOffset); - bottomBaseLine = Math.max(bottomBaseLine, n.bottomYOffset); - } - - int maxXOffset = 0; - for (LayoutNode n : layer) { - if (n.vertex == null) { - // Dummy node - n.y = curY; - n.height = maxHeight + baseLine + bottomBaseLine; - - } else { - n.y = curY + baseLine + (maxHeight - (n.height - n.yOffset - n.bottomYOffset)) / 2 - n.yOffset; - } - for (LayoutEdge e : n.succs) { - int curXOffset = Math.abs(n.x - e.to.x); - maxXOffset = Math.max(curXOffset, maxXOffset); - } - } - - curY += maxHeight + baseLine + bottomBaseLine; - curY += layerOffset + ((int) (Math.sqrt(maxXOffset) * 1.5)); + static private void apply(LayoutGraph graph) { + createArrays(graph); + initialPositions(graph); + for (int i = 0; i < SWEEP_ITERATIONS; i++) { + sweepDown(graph); + sweepUp(graph); } + graph.optimizeBackEdgeCrossings(); + graph.straightenEdges(); } - } - private class CreateDummyNodes extends AlgorithmPart { - - private int oldNodeCount; - - @Override - 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; + static private void processRow(int[] space, LayoutNode[] processingOrder) { + Arrays.sort(processingOrder, DUMMY_NODES_THEN_OPTIMAL_X); + TreeSet treeSet = new TreeSet<>(NODE_POS_COMPARATOR); + for (LayoutNode node : processingOrder) { + int minX = Integer.MIN_VALUE; + SortedSet headSet = treeSet.headSet(node, false); + if (!headSet.isEmpty()) { + LayoutNode leftNeighbor = headSet.last(); + minX = leftNeighbor.getOuterLeft() + space[node.getPos()] - space[leftNeighbor.getPos()]; } - for (LayoutEdge e : n.preds) { - assert e.to != null; - assert e.to == n; + int maxX = Integer.MAX_VALUE; + SortedSet tailSet = treeSet.tailSet(node, false); + if (!tailSet.isEmpty()) { + LayoutNode rightNeighbor = tailSet.first(); + maxX = rightNeighbor.getOuterLeft() + space[node.getPos()] - space[rightNeighbor.getPos()]; } - } - } - - @Override - protected void run() { - oldNodeCount = nodes.size(); - - if (combine == Combine.SAME_OUTPUTS) { - - Comparator comparator = Comparator.comparingInt(e -> e.to.layer); - HashMap> portHash = new HashMap<>(); - ArrayList currentNodes = new ArrayList<>(nodes); - for (LayoutNode n : currentNodes) { - portHash.clear(); - - ArrayList succs = new ArrayList<>(n.succs); - HashMap topNodeHash = new HashMap<>(); - HashMap> bottomNodeHash = new HashMap<>(); - for (LayoutEdge e : succs) { - assert e.from.layer < e.to.layer; - if (e.from.layer != e.to.layer - 1) { - if (maxLayerLength != -1 && e.to.layer - e.from.layer > maxLayerLength) { - assert maxLayerLength > 2; - e.to.preds.remove(e); - e.from.succs.remove(e); - - LayoutEdge topEdge; - - if (combine == Combine.SAME_OUTPUTS && topNodeHash.containsKey(e.relativeFrom)) { - LayoutNode topNode = topNodeHash.get(e.relativeFrom); - topEdge = new LayoutEdge(); - topEdge.relativeFrom = e.relativeFrom; - topEdge.from = e.from; - topEdge.relativeTo = topNode.width / 2; - topEdge.to = topNode; - topEdge.link = e.link; - topEdge.vip = e.vip; - e.from.succs.add(topEdge); - topNode.preds.add(topEdge); - } else { - - LayoutNode topNode = new LayoutNode(); - topNode.layer = e.from.layer + 1; - topNode.width = DUMMY_WIDTH; - topNode.height = DUMMY_HEIGHT; - nodes.add(topNode); - topEdge = new LayoutEdge(); - topEdge.relativeFrom = e.relativeFrom; - topEdge.from = e.from; - topEdge.relativeTo = 0; - topEdge.to = topNode; - topEdge.link = e.link; - topEdge.vip = e.vip; - e.from.succs.add(topEdge); - topNode.preds.add(topEdge); - topNodeHash.put(e.relativeFrom, topNode); - bottomNodeHash.put(e.relativeFrom, new HashMap<>()); - } - - HashMap hash = bottomNodeHash.get(e.relativeFrom); - - LayoutNode bottomNode; - if (hash.containsKey(e.to.layer)) { - bottomNode = hash.get(e.to.layer); - } else { - - bottomNode = new LayoutNode(); - bottomNode.layer = e.to.layer - 1; - bottomNode.width = DUMMY_WIDTH; - bottomNode.height = DUMMY_HEIGHT; - nodes.add(bottomNode); - hash.put(e.to.layer, bottomNode); - } - - LayoutEdge bottomEdge = new LayoutEdge(); - bottomEdge.relativeTo = e.relativeTo; - bottomEdge.to = e.to; - bottomEdge.relativeFrom = bottomNode.width / 2; - bottomEdge.from = bottomNode; - bottomEdge.link = e.link; - bottomEdge.vip = e.vip; - e.to.preds.add(bottomEdge); - bottomNode.succs.add(bottomEdge); - - } else { - Integer i = e.relativeFrom; - if (!portHash.containsKey(i)) { - portHash.put(i, new ArrayList<>()); - } - portHash.get(i).add(e); - } - } - } - - succs = new ArrayList<>(n.succs); - for (LayoutEdge e : succs) { - - Integer i = e.relativeFrom; - if (portHash.containsKey(i)) { - - List list = portHash.get(i); - list.sort(comparator); - - if (list.size() == 1) { - processSingleEdge(list.get(0)); - } else { - - int maxLayer = list.get(0).to.layer; - for (LayoutEdge curEdge : list) { - maxLayer = Math.max(maxLayer, curEdge.to.layer); - } - - int cnt = maxLayer - n.layer - 1; - LayoutEdge[] edges = new LayoutEdge[cnt]; - LayoutNode[] nodes = new LayoutNode[cnt]; - edges[0] = new LayoutEdge(); - edges[0].from = n; - edges[0].relativeFrom = i; - edges[0].vip = e.vip; - n.succs.add(edges[0]); - - nodes[0] = new LayoutNode(); - nodes[0].width = dummyWidth; - nodes[0].height = dummyHeight; - nodes[0].layer = n.layer + 1; - nodes[0].preds.add(edges[0]); - edges[0].to = nodes[0]; - edges[0].relativeTo = nodes[0].width / 2; - for (int j = 1; j < cnt; j++) { - edges[j] = new LayoutEdge(); - edges[j].vip = e.vip; - edges[j].from = nodes[j - 1]; - edges[j].relativeFrom = nodes[j - 1].width / 2; - nodes[j - 1].succs.add(edges[j]); - nodes[j] = new LayoutNode(); - nodes[j].width = dummyWidth; - nodes[j].height = dummyHeight; - nodes[j].layer = n.layer + j + 1; - nodes[j].preds.add(edges[j]); - edges[j].to = nodes[j]; - edges[j].relativeTo = nodes[j].width / 2; - } - - for (LayoutEdge curEdge : list) { - assert curEdge.to.layer - n.layer - 2 >= 0; - assert curEdge.to.layer - n.layer - 2 < cnt; - LayoutNode anchor = nodes[curEdge.to.layer - n.layer - 2]; - anchor.succs.add(curEdge); - curEdge.from = anchor; - curEdge.relativeFrom = anchor.width / 2; - n.succs.remove(curEdge); - } - - } - portHash.remove(i); - } - } - } - } else if (combine == Combine.SAME_INPUTS) { - throw new UnsupportedOperationException("Currently not supported"); - } else { - ArrayList currentNodes = new ArrayList<>(nodes); - for (LayoutNode n : currentNodes) { - for (LayoutEdge e : List.copyOf(n.succs)) { - processSingleEdge(e); - } - } + node.setX(Math.min(Math.max(node.getOptimalX(), minX), maxX)); + treeSet.add(node); } } - private void processSingleEdge(LayoutEdge e) { - LayoutNode n = e.to; - if (e.to.layer - 1 > e.from.layer) { - LayoutEdge last = e; - for (int i = n.layer - 1; i > last.from.layer; i--) { - last = addBetween(last, i); + static private void sweepUp(LayoutGraph graph) { + for (int i = graph.getLayerCount() - 2; i >= 0; i--) { + for (LayoutNode node : upProcessingOrder[i]) { + node.setOptimalX(node.calculateOptimalPositionUp()); } + processRow(space[i], upProcessingOrder[i]); } } - private LayoutEdge addBetween(LayoutEdge e, int layer) { - LayoutNode n = new LayoutNode(); - n.width = DUMMY_WIDTH; - n.height = DUMMY_HEIGHT; - n.layer = layer; - n.succs.add(e); - nodes.add(n); - LayoutEdge result = new LayoutEdge(); - result.vip = e.vip; - n.preds.add(result); - result.to = n; - result.relativeTo = n.width / 2; - result.from = e.from; - result.relativeFrom = e.relativeFrom; - result.link = e.link; - e.relativeFrom = n.width / 2; - e.from.succs.remove(e); - e.from.succs.add(result); - e.from = n; - return result; - } - - @Override - public void printStatistics() { - System.out.println("Dummy nodes created: " + (nodes.size() - oldNodeCount)); - } - - @Override - 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; + static private void sweepDown(LayoutGraph graph) { + for (int i = 1; i < graph.getLayerCount(); i++) { + for (LayoutNode node : downProcessingOrder[i]) { + node.setOptimalX(node.calculateOptimalPositionDown()); } + processRow(space[i], downProcessingOrder[i]); } } } - private class AssignLayers extends AlgorithmPart { - - @Override - public void preCheck() { - for (LayoutNode n : nodes) { - assert n.layer == -1; - } - } - - @Override - protected void run() { - assignLayerDownwards(); - assignLayerUpwards(); - } - - private void assignLayerDownwards() { - ArrayList hull = new ArrayList<>(); - for (LayoutNode n : nodes) { - if (n.preds.isEmpty()) { - hull.add(n); - n.layer = 0; - } - } + private static class WriteResult { - int z = minLayerDifference; - while (!hull.isEmpty()) { - ArrayList newSet = new ArrayList<>(); - for (LayoutNode n : hull) { - for (LayoutEdge se : n.succs) { - LayoutNode s = se.to; - if (s.layer != -1) { - // This node was assigned before. - } else { - boolean unassignedPred = false; - for (LayoutEdge pe : s.preds) { - LayoutNode p = pe.from; - if (p.layer == -1 || p.layer >= z) { - // This now has an unscheduled successor or a successor that was scheduled only in this round. - unassignedPred = true; - break; - } - } + private static HashMap> computeLinkPositions(LayoutGraph graph) { + HashMap> linkToSplitEndPoints = new HashMap<>(); + HashMap> linkPositions = new HashMap<>(); - if (unassignedPred) { - // This successor node can not yet be assigned. - } else { - s.layer = z; - newSet.add(s); - } + for (LayoutNode layoutNode : graph.getLayoutNodes()) { + for (LayoutEdge predEdge : layoutNode.getPreds()) { + LayoutNode fromNode = predEdge.getFrom(); + LayoutNode toNode = predEdge.getTo(); + + ArrayList linkPoints = new ArrayList<>(); + // input edge stub + linkPoints.add(new Point(predEdge.getEndX(), toNode.getTop())); + linkPoints.add(new Point(predEdge.getEndX(), graph.getLayer(toNode.getLayer()).getTop() - LAYER_OFFSET)); + + LayoutEdge curEdge = predEdge; + while (fromNode.isDummy() && fromNode.hasPreds()) { + linkPoints.add(new Point(fromNode.getCenterX(), graph.getLayer(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); + linkPoints.add(new Point(fromNode.getCenterX(), graph.getLayer(fromNode.getLayer()).getTop() - LAYER_OFFSET)); + curEdge = fromNode.getPreds().get(0); + fromNode = curEdge.getFrom(); + } + linkPoints.add(new Point(curEdge.getStartX(), graph.getLayer(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); + // output edge stub + linkPoints.add(new Point(curEdge.getStartX(), fromNode.getBottom())); + + if (predEdge.isReversed()) { + for (Point relativeEnd : toNode.getReversedLinkEndPoints().get(predEdge.getLink())) { + Point endPoint = new Point(toNode.getLeft() + relativeEnd.x, toNode.getTop() + relativeEnd.y); + linkPoints.add(0, endPoint); } - } - } - - hull = newSet; - z += minLayerDifference; - } - - layerCount = z - minLayerDifference; - for (LayoutNode n : nodes) { - n.layer = (layerCount - 1 - n.layer); - } - } - - private void assignLayerUpwards() { - ArrayList hull = new ArrayList<>(); - for (LayoutNode n : nodes) { - if (n.succs.isEmpty()) { - hull.add(n); - } else { - n.layer = -1; - } - } - - int z = minLayerDifference; - while (!hull.isEmpty()) { - ArrayList newSet = new ArrayList<>(); - for (LayoutNode n : hull) { - if (n.layer < z) { - for (LayoutEdge se : n.preds) { - LayoutNode s = se.from; - if (s.layer != -1) { - // This node was assigned before. - } else { - boolean unassignedSucc = false; - for (LayoutEdge pe : s.succs) { - LayoutNode p = pe.to; - if (p.layer == -1 || p.layer >= z) { - // This now has an unscheduled successor or a successor that was scheduled only in this round. - unassignedSucc = true; - break; - } - } - if (unassignedSucc) { - // This predecessor node can not yet be assigned. - } else { - s.layer = z; - newSet.add(s); + if (!fromNode.isDummy()) { + if (fromNode.getReversedLinkStartPoints().containsKey(predEdge.getLink())) { + for (Point relativeStart : fromNode.getReversedLinkStartPoints().get(predEdge.getLink())) { + Point startPoint = new Point(fromNode.getLeft() + relativeStart.x, fromNode.getTop() + relativeStart.y); + linkPoints.add(startPoint); } } } } else { - newSet.add(n); + Collections.reverse(linkPoints); } - } - - hull = newSet; - z += minLayerDifference; - } - - layerCount = z - minLayerDifference; - - for (LayoutNode n : nodes) { - n.layer = (layerCount - 1 - n.layer); - } - } - - @Override - 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 ReverseEdges extends AlgorithmPart { - private HashSet visited; - private HashSet active; - - @Override - protected void run() { - - // Reverse inputs of roots - for (LayoutNode node : nodes) { - if (node.vertex.isRoot()) { - boolean ok = true; - for (LayoutEdge e : node.preds) { - if (e.from.vertex.isRoot()) { - ok = false; - break; + if (fromNode.isDummy()) { + if (predEdge.isReversed()) { + Collections.reverse(linkPoints); } - } - if (ok) { - reverseAllInputs(node); + linkToSplitEndPoints.put(predEdge.getLink(), linkPoints); + + } else { + linkPositions.put(predEdge.getLink(), linkPoints); } } } - // Start DFS and reverse back edges - visited = new HashSet<>(); - active = new HashSet<>(); - for (LayoutNode node : nodes) { - DFS(node); - } - - for (LayoutNode node : nodes) { + for (LayoutNode layoutNode : graph.getLayoutNodes()) { + for (LayoutEdge succEdge : layoutNode.getSuccs()) { + if (succEdge.getLink() == null) continue; - SortedSet reversedDown = new TreeSet<>(); + LayoutNode fromNode = succEdge.getFrom(); + LayoutNode toNode = succEdge.getTo(); - boolean hasSelfEdge = false; - for (LayoutEdge e : node.succs) { - if (reversedLinks.contains(e.link)) { - reversedDown.add(e.relativeFrom); - if (e.from == e.to) { - hasSelfEdge = true; - } - } - } - - // Whether the node has non-self reversed edges going downwards. - // If so, reversed edges going upwards are drawn to the left. - boolean hasReversedDown = - reversedDown.size() > 0 && - !(reversedDown.size() == 1 && hasSelfEdge); - - SortedSet reversedUp = null; - if (hasReversedDown) { - reversedUp = new TreeSet<>(); - } else { - reversedUp = new TreeSet<>(Collections.reverseOrder()); - } + ArrayList linkPoints = new ArrayList<>(); + linkPoints.add(new Point(succEdge.getStartX(), fromNode.getBottom())); + linkPoints.add(new Point(succEdge.getStartX(), graph.getLayer(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); - for (LayoutEdge e : node.preds) { - if (reversedLinks.contains(e.link)) { - reversedUp.add(e.relativeTo); + LayoutEdge curEdge = succEdge; + while (toNode.isDummy() && toNode.hasSuccs()) { + linkPoints.add(new Point(toNode.getCenterX(), graph.getLayer(toNode.getLayer()).getTop() - LAYER_OFFSET)); + linkPoints.add(new Point(toNode.getCenterX(), graph.getLayer(toNode.getLayer()).getBottom() + LAYER_OFFSET)); + curEdge = toNode.getSuccs().get(0); + toNode = curEdge.getTo(); } - } + linkPoints.add(new Point(curEdge.getEndX(), graph.getLayer(toNode.getLayer()).getTop() - LAYER_OFFSET)); + linkPoints.add(new Point(curEdge.getEndX(), toNode.getTop())); - final int offset = xOffset + DUMMY_WIDTH; + if (succEdge.isReversed()) { + Collections.reverse(linkPoints); - int curY = 0; - int curWidth = node.width + reversedDown.size() * offset; - for (int pos : reversedDown) { - ArrayList reversedSuccs = new ArrayList<>(); - for (LayoutEdge e : node.succs) { - if (e.relativeFrom == pos && reversedLinks.contains(e.link)) { - reversedSuccs.add(e); - e.relativeFrom = curWidth; + if (fromNode.getReversedLinkStartPoints().containsKey(succEdge.getLink())) { + for (Point relativeStart : fromNode.getReversedLinkStartPoints().get(succEdge.getLink())) { + Point startPoint = new Point(fromNode.getLeft() + relativeStart.x, fromNode.getTop() + relativeStart.y); + linkPoints.add(startPoint); + } } - } - - ArrayList startPoints = new ArrayList<>(); - startPoints.add(new Point(curWidth, curY)); - startPoints.add(new Point(pos, curY)); - startPoints.add(new Point(pos, reversedDown.size() * offset)); - for (LayoutEdge e : reversedSuccs) { - reversedLinkStartPoints.put(e.link, startPoints); - } - - node.inOffsets.put(pos, -curY); - curY += offset; - node.height += offset; - node.yOffset += offset; - curWidth -= offset; - } - int widthFactor = reversedDown.size(); - if (hasSelfEdge) { - widthFactor--; - } - node.width += widthFactor * offset; - - int curX = 0; - int minX = 0; - if (hasReversedDown) { - minX = -offset * reversedUp.size(); - } - - int oldNodeHeight = node.height; - for (int pos : reversedUp) { - ArrayList reversedPreds = new ArrayList<>(); - for (LayoutEdge e : node.preds) { - if (e.relativeTo == pos && reversedLinks.contains(e.link)) { - if (hasReversedDown) { - e.relativeTo = curX - offset; - } else { - e.relativeTo = node.width + offset; + if (!toNode.isDummy()) { + if (toNode.getReversedLinkEndPoints().containsKey(succEdge.getLink())) { + for (Point relativeEnd : toNode.getReversedLinkEndPoints().get(succEdge.getLink())) { + Point endPoint = new Point(toNode.getLeft() + relativeEnd.x, toNode.getTop() + relativeEnd.y); + linkPoints.add(0, endPoint); + } } - - reversedPreds.add(e); } } - node.height += offset; - ArrayList endPoints = new ArrayList<>(); - node.width += offset; - if (hasReversedDown) { - curX -= offset; - endPoints.add(new Point(curX, node.height)); - } else { - curX += offset; - endPoints.add(new Point(node.width, node.height)); - } - - node.outOffsets.put(pos - minX, curX); - curX += offset; - node.bottomYOffset += offset; - - endPoints.add(new Point(pos, node.height)); - endPoints.add(new Point(pos, oldNodeHeight)); - for (LayoutEdge e : reversedPreds) { - reversedLinkEndPoints.put(e.link, endPoints); - } - } - - if (minX < 0) { - for (LayoutEdge e : node.preds) { - e.relativeTo -= minX; - } - - for (LayoutEdge e : node.succs) { - e.relativeFrom -= minX; + if (linkToSplitEndPoints.containsKey(succEdge.getLink())) { + if (succEdge.isReversed()) { + Collections.reverse(linkPoints); + } + linkPoints.add(null); + linkPoints.addAll(linkToSplitEndPoints.get(succEdge.getLink())); + if (succEdge.isReversed()) { + Collections.reverse(linkPoints); + } } - - node.xOffset = -minX; - node.width += -minX; + linkPositions.put(succEdge.getLink(), linkPoints); } } + return linkPositions; } - private void DFS(LayoutNode startNode) { - if (visited.contains(startNode)) { - return; - } - - Stack stack = new Stack<>(); - stack.push(startNode); + public static void apply(LayoutGraph graph) { + // Assign Y coordinates + graph.positionLayers(); - while (!stack.empty()) { - LayoutNode node = stack.pop(); + int minX = Integer.MAX_VALUE; + int minY = Integer.MAX_VALUE; - if (visited.contains(node)) { - // Node no longer active - active.remove(node); - continue; - } + HashMap> linkPositions = computeLinkPositions(graph); - // 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) && (linksToFollow.size() == 0 || linksToFollow.contains(e.link))) { - stack.push(e.to); + for (List points : linkPositions.values()) { + for (Point point : points) { + if (point != null) { + minX = Math.min(minX, point.x); + minY = Math.min(minY, point.y); } } } - } - 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; + for (LayoutNode layoutNode : graph.getLayoutNodes()) { + minX = Math.min(minX, layoutNode.getX()); + minY = Math.min(minY, layoutNode.getY()); } - 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); - } - - @Override - public void postCheck() { - - for (LayoutNode n : nodes) { - - HashSet curVisited = new HashSet<>(); - Queue queue = new LinkedList<>(); - for (LayoutEdge e : n.succs) { - LayoutNode s = e.to; - queue.add(s); - curVisited.add(s); - } - while (!queue.isEmpty()) { - LayoutNode curNode = queue.remove(); - - for (LayoutEdge e : curNode.succs) { - assert e.to != n; - if (!curVisited.contains(e.to)) { - queue.add(e.to); - curVisited.add(e.to); - } - } - } + for (LayoutNode dummyNode : graph.getDummyNodes()) { + minX = Math.min(minX, dummyNode.getX()); + minY = Math.min(minY, dummyNode.getY()); } - } - } - private final Comparator linkComparator = (l1, l2) -> { - if (l1.isVIP() && !l2.isVIP()) { - return -1; - } - if (!l1.isVIP() && l2.isVIP()) { - return 1; - } - - int result = l1.getFrom().getVertex().compareTo(l2.getFrom().getVertex()); - if (result != 0) { - return result; - } - result = l1.getTo().getVertex().compareTo(l2.getTo().getVertex()); - if (result != 0) { - return result; - } - result = l1.getFrom().getRelativePosition().x - l2.getFrom().getRelativePosition().x; - if (result != 0) { - return result; - } - result = l1.getTo().getRelativePosition().x - l2.getTo().getRelativePosition().x; - return result; - }; - - private class BuildDatastructure extends AlgorithmPart { - - @Override - protected void run() { - // Set up nodes - List vertices = new ArrayList<>(graph.getVertices()); - // Order roots first to create more natural layer assignments. - vertices.sort((Vertex a, Vertex b) -> - a.isRoot() == b.isRoot() ? a.compareTo(b) : Boolean.compare(b.isRoot(), a.isRoot())); - - for (Vertex v : vertices) { - LayoutNode node = new LayoutNode(); - Dimension size = v.getSize(); - node.width = (int) size.getWidth(); - node.height = (int) size.getHeight(); - node.vertex = v; - nodes.add(node); - vertexToLayoutNode.put(v, node); + for (LayoutLayer layer : graph.getLayers()) { + minY = Math.min(minY, layer.getTop()); } - // Set up edges - List links = new ArrayList<>(graph.getLinks()); - links.sort(linkComparator); - for (Link l : links) { - LayoutEdge edge = new LayoutEdge(); - assert vertexToLayoutNode.containsKey(l.getFrom().getVertex()); - assert vertexToLayoutNode.containsKey(l.getTo().getVertex()); - edge.from = vertexToLayoutNode.get(l.getFrom().getVertex()); - edge.to = vertexToLayoutNode.get(l.getTo().getVertex()); - edge.relativeFrom = l.getFrom().getRelativePosition().x; - edge.relativeTo = l.getTo().getRelativePosition().x; - edge.link = l; - edge.vip = l.isVIP(); - edge.from.succs.add(edge); - edge.to.preds.add(edge); + for (LayoutNode layoutNode : graph.getLayoutNodes()) { + layoutNode.setX(layoutNode.getX() - minX); + layoutNode.setY(layoutNode.getY() - minY); } - - for (Link l : importantLinks) { - if (!vertexToLayoutNode.containsKey(l.getFrom().getVertex()) - || vertexToLayoutNode.containsKey(l.getTo().getVertex())) { - continue; - } - LayoutNode from = vertexToLayoutNode.get(l.getFrom().getVertex()); - LayoutNode to = vertexToLayoutNode.get(l.getTo().getVertex()); - for (LayoutEdge e : from.succs) { - if (e.to == to) { - linksToFollow.add(e.link); - } - } + for (LayoutNode dummyNode : graph.getDummyNodes()) { + dummyNode.setX(dummyNode.getX() - minX); + dummyNode.setY(dummyNode.getY() - minY); } - } - - @Override - public void postCheck() { - assert vertexToLayoutNode.keySet().size() == nodes.size(); - assert nodes.size() == graph.getVertices().size(); - - for (Vertex v : graph.getVertices()) { - - LayoutNode node = vertexToLayoutNode.get(v); - assert node != null; + for (LayoutLayer layer : graph.getLayers()) { + layer.shiftTop(-minY); + } - for (LayoutEdge e : node.succs) { - assert e.from == node; - } + // Shift vertices by minX/minY + for (LayoutNode layoutNode : graph.getLayoutNodes()) { + Vertex vertex = layoutNode.getVertex(); + vertex.setPosition(new Point(layoutNode.getLeft(), layoutNode.getTop())); + } - for (LayoutEdge e : node.preds) { - assert e.to == node; + // shift links by minX/minY + for (Map.Entry> entry : linkPositions.entrySet()) { + Link link = entry.getKey(); + List points = entry.getValue(); + for (Point p : points) { + if (p != null) { + p.x -= minX; + p.y -= minY; + } } + // write points back to links + link.setControlPoints(points); } } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java index 2d76d15394cd7..2922e2c192e89 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java @@ -23,15 +23,13 @@ */ package com.sun.hotspot.igv.hierarchicallayout; -import com.sun.hotspot.igv.layout.LayoutGraph; import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Vertex; -import com.sun.hotspot.igv.util.Statistics; import java.awt.Dimension; import java.awt.Point; import java.util.*; -public class HierarchicalStableLayoutManager { +public class HierarchicalStableLayoutManager extends LayoutManager{ public static final int DUMMY_HEIGHT = 1; public static final int DUMMY_WIDTH = 1; @@ -65,6 +63,14 @@ public void doLayout(LayoutGraph layoutGraph) { } + public void setCutEdges(boolean cutEdges) { + } + + @Override + public void doLayout(LayoutGraph graph) { + + } + enum Action { ADD, REMOVE diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/InterClusterConnection.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/InterClusterConnection.java index 0ff9a7dd04cd3..4112ac997b8f1 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/InterClusterConnection.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/InterClusterConnection.java @@ -74,8 +74,4 @@ public List getControlPoints() { public String toString() { return "InterClusterConnection[from=" + getFrom() + ", to=" + getTo() + "]"; } - - public boolean isVIP() { - return false; - } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java index 081135a2abfb2..eba424936531b 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java @@ -25,20 +25,99 @@ package com.sun.hotspot.igv.hierarchicallayout; import com.sun.hotspot.igv.layout.Link; +import java.util.Comparator; public class LayoutEdge { - public LayoutNode from; - public LayoutNode to; - // Horizontal distance relative to start of 'from'. - public int relativeFrom; - // Horizontal distance relative to start of 'to'. - public int relativeTo; - public Link link; - public boolean vip; + public static final Comparator LAYOUT_EDGE_LAYER_COMPARATOR = Comparator.comparingInt(e -> e.getTo().getLayer()); - @Override - public String toString() { - return "Edge " + from + ", " + to; - } + private LayoutNode from; + private LayoutNode to; + // Horizontal distance relative to start of 'from'. + private int relativeFromX; + // Horizontal distance relative to start of 'to'. + private int relativeToX; + private Link link; + private boolean isReversed; + + public int getStartX() { + return relativeFromX + from.getLeft(); + } + + public int getEndX() { + return relativeToX + to.getLeft(); + } + + public LayoutEdge(LayoutNode from, LayoutNode to, Link link) { + this.from = from; + this.to = to; + this.link = link; + this.isReversed = false; + } + + public LayoutEdge(LayoutNode from, LayoutNode to, int relativeFromX, int relativeToX, Link link) { + this(from, to, link); + this.relativeFromX = relativeFromX; + this.relativeToX = relativeToX; + } + + public void reverse() { + isReversed = !isReversed; + } + + public boolean isReversed() { + return isReversed; + } + + @Override + public String toString() { + return "Edge " + from + ", " + to; + } + + public LayoutNode getFrom() { + return from; + } + + public void setFrom(LayoutNode from) { + this.from = from; + } + + public LayoutNode getTo() { + return to; + } + + public void setTo(LayoutNode to) { + this.to = to; + } + + public int getFromX() { + return from.getX() + getRelativeFromX(); + } + + public int getToX() { + return to.getX() + getRelativeToX(); + } + + public int getRelativeFromX() { + return relativeFromX; + } + public void setRelativeFromX(int relativeFromX) { + this.relativeFromX = relativeFromX; + } + + public int getRelativeToX() { + return relativeToX; + } + + public void setRelativeToX(int relativeToX) { + this.relativeToX = relativeToX; + } + + public Link getLink() { + return link; + } + + public void setLink(Link link) { + this.link = link; } +} diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java new file mode 100644 index 0000000000000..e6a072263a243 --- /dev/null +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java @@ -0,0 +1,583 @@ +/* + * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 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 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.sun.hotspot.igv.hierarchicallayout; + +import com.sun.hotspot.igv.layout.Link; +import com.sun.hotspot.igv.layout.Port; +import com.sun.hotspot.igv.layout.Vertex; +import java.util.*; +import java.util.stream.Collectors; + +import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.NODE_POS_COMPARATOR; + +/** + * + * @author Thomas Wuerthinger + */ +public class LayoutGraph { + + public static final Comparator LINK_COMPARATOR = + Comparator.comparing((Link l) -> l.getFrom().getVertex()) + .thenComparing(l -> l.getTo().getVertex()) + .thenComparingInt(l -> l.getFrom().getRelativePosition().x) + .thenComparingInt(l -> l.getTo().getRelativePosition().x); + + private final Set links; + private final SortedSet vertices; + private final HashMap> inputPorts; + private final HashMap> outputPorts; + private final HashMap> portLinks; + + private final List dummyNodes; + private final LinkedHashMap vertexToLayoutNode; + + private List layers; + + public LayoutGraph(Set links) { + this(links, new HashSet<>()); + } + + public void initLayers(int layerCount) { + layers = new ArrayList<>(layerCount); + for (int i = 0; i < layerCount; i++) { + layers.add(new LayoutLayer()); + } + } + + public List getDummyNodes() { + return Collections.unmodifiableList(dummyNodes); + } + + private LayoutLayer createNewLayer(int layerNr) { + LayoutLayer layer = new LayoutLayer(); + layers.add(layerNr, layer); + + // update layer field in nodes below layerNr + for (int l = layerNr + 1; l < getLayerCount(); l++) { + for (LayoutNode layoutNode : getLayer(l)) { + layoutNode.setLayer(l); + } + } + return layer; + } + + private void deleteLayer(int layerNr) { + layers.remove(layerNr); + + // Update the layer field in nodes below the deleted layer + for (int l = layerNr; l < getLayerCount(); l++) { + for (LayoutNode layoutNode : getLayer(l)) { + layoutNode.setLayer(l); + } + } + } + + + // check that NO neighbors of node are in a given layer + // otherwise insert a new layer + // return the layerNr where the node can now be safely inserted + public int insertNewLayerIfNeeded(LayoutNode node, int layerNr) { + for (Link inputLink : getInputLinks(node.getVertex())) { + if (inputLink.getFrom().getVertex() == inputLink.getTo().getVertex()) continue; + LayoutNode fromNode = getLayoutNode(inputLink.getFrom().getVertex()); + if (fromNode.getLayer() == layerNr) { + moveExpandLayerDown(layerNr + 1); + return layerNr + 1; + } + } + for (Link outputLink : getOutputLinks(node.getVertex())) { + if (outputLink.getFrom().getVertex() == outputLink.getTo().getVertex()) continue; + LayoutNode toNode = getLayoutNode(outputLink.getTo().getVertex()); + if (toNode.getLayer() == layerNr) { + moveExpandLayerDown(layerNr); + return layerNr; + } + } + return layerNr; + + } + + // inserts a new layer at layerNr + // inserts dummy nodes acoring to layerNr - 1 + // moves the layer from previous layerNr to layerNr + 1 + private void moveExpandLayerDown(int layerNr) { + LayoutLayer newLayer = createNewLayer(layerNr); + + if (layerNr == 0) return; + LayoutLayer layerAbove = getLayer(layerNr - 1); + + for (LayoutNode fromNode : layerAbove) { + int fromX = fromNode.getX(); + Map> successorsByX = fromNode.groupSuccessorsByX(); + fromNode.getSuccs().clear(); + + for (Map.Entry> entry : successorsByX.entrySet()) { + Integer relativeFromX = entry.getKey(); + List edges = entry.getValue(); + LayoutNode dummyNode = new LayoutNode(); + dummyNode.setX(fromX + relativeFromX); + dummyNode.setLayer(layerNr); + dummyNode.getSuccs().addAll(edges); + LayoutEdge dummyEdge = new LayoutEdge(fromNode, dummyNode, relativeFromX, 0, edges.get(0).getLink()); + if (edges.get(0).isReversed()) dummyEdge.reverse(); + + fromNode.getSuccs().add(dummyEdge); + dummyNode.getPreds().add(dummyEdge); + for (LayoutEdge edge : edges) { + edge.setFrom(dummyNode); + } + addNodeToLayer(dummyNode, layerNr); + } + } + + newLayer.sortNodesByXAndSetPositions(); + } + + public List getLayers() { + return Collections.unmodifiableList(layers); + } + + public int getLayerCount() { + return layers.size(); + } + + public Collection getLayoutNodes() { + return vertexToLayoutNode.values(); + } + + public LayoutNode getLayoutNode(Vertex vertex) { + return vertexToLayoutNode.get(vertex); + } + + public LayoutGraph(Collection links, Collection additionalVertices) { + this.links = new HashSet<>(links); + + vertices = new TreeSet<>(additionalVertices); + portLinks = new HashMap<>(links.size()); + inputPorts = new HashMap<>(links.size()); + outputPorts = new HashMap<>(links.size()); + + for (Link link : links) { + assert link.getFrom() != null; + assert link.getTo() != null; + Port fromPort = link.getFrom(); + Port toPort = link.getTo(); + Vertex fromVertex = fromPort.getVertex(); + Vertex toVertex = toPort.getVertex(); + + vertices.add(fromVertex); + vertices.add(toVertex); + + outputPorts.computeIfAbsent(fromVertex, k -> new HashSet<>()).add(fromPort); + inputPorts.computeIfAbsent(toVertex, k -> new HashSet<>()).add(toPort); + + portLinks.computeIfAbsent(fromPort, k -> new HashSet<>()).add(link); + portLinks.computeIfAbsent(toPort, k -> new HashSet<>()).add(link); + } + + // cleanup + vertexToLayoutNode = new LinkedHashMap<>(); + dummyNodes = new ArrayList<>(); + + + // Set up nodes + for (Vertex v : getVertices()) { + LayoutNode node = new LayoutNode(v); + vertexToLayoutNode.put(v, node); + } + + // Set up edges + List sortedLinks = new ArrayList<>(links); + sortedLinks.sort(LINK_COMPARATOR); + for (Link link : links) { + createLayoutEdge(link); + } + } + + public void addNodeToLayer(LayoutNode node, int layerNumber) { + node.setLayer(layerNumber); + getLayer(layerNumber).add(node); + + // Register node in the appropriate collection based on its type + registerNode(node); + } + + private void registerNode(LayoutNode node) { + if (node.isDummy()) { + dummyNodes.add(node); + } else { + vertexToLayoutNode.put(node.getVertex(), node); + } + } + + + public void removeNode(LayoutNode node) { + int layer = node.getLayer(); + layers.get(layer).remove(node); + layers.get(layer).updateLayerPositions(); + // Remove node from graph layout + if (node.isDummy()) { + dummyNodes.remove(node); + } else { + vertexToLayoutNode.remove(node.getVertex()); + } + } + + public void updatePositions() { + for (LayoutLayer layer : layers) { + layer.updateLayerPositions(); + } + } + + public LayoutEdge createLayoutEdge(Link link) { + LayoutEdge edge = new LayoutEdge( + vertexToLayoutNode.get(link.getFrom().getVertex()), + vertexToLayoutNode.get(link.getTo().getVertex()), + link.getFrom().getRelativePosition().x, + link.getTo().getRelativePosition().x, + link); + edge.getFrom().getSuccs().add(edge); + edge.getTo().getPreds().add(edge); + return edge; + } + + public Set getLinks() { + return links; + } + + public SortedSet getVertices() { + return vertices; + } + + public boolean containsVertex(Vertex vertex) { + return vertices.contains(vertex); + } + + public Set findRootVertices() { + return vertices.stream() + .filter(v -> inputPorts.getOrDefault(v, Collections.emptySet()).isEmpty()) + .collect(Collectors.toSet()); + } + + public Set getInputLinks(Vertex vertex) { + Set inputLinks = new HashSet<>(); + for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { + inputLinks.addAll(portLinks.getOrDefault(inputPort, Collections.emptySet())); + } + return inputLinks; + } + + public Set getOutputLinks(Vertex vertex) { + Set outputLinks = new HashSet<>(); + for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) { + outputLinks.addAll(portLinks.getOrDefault(outputPort, Collections.emptySet())); + } + return outputLinks; + } + + private Set getAllLinks(Vertex vertex) { + Set allLinks = new HashSet<>(); + + for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { + allLinks.addAll(portLinks.getOrDefault(inputPort, Collections.emptySet())); + } + + for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) { + allLinks.addAll(portLinks.getOrDefault(outputPort, Collections.emptySet())); + } + + return allLinks; + } + + private void removeEdges(LayoutNode movedNode) { + for (Link inputLink : getAllLinks(movedNode.getVertex())) { + Vertex from = inputLink.getFrom().getVertex(); + Vertex to = inputLink.getTo().getVertex(); + LayoutNode toNode = getLayoutNode(to); + LayoutNode fromNode = getLayoutNode(from); + + if (toNode.getLayer() < fromNode.getLayer()) { + // Reversed edge + toNode = fromNode; + toNode.getReversedLinkEndPoints().remove(inputLink); + fromNode.getReversedLinkStartPoints().remove(inputLink); + } + + // Remove preds-edges bottom up, starting at "to" node + // Cannot start from "from" node since there might be joint edges + List toNodePredsEdges = List.copyOf(toNode.getPreds()); + for (LayoutEdge edge : toNodePredsEdges) { + LayoutNode predNode = edge.getFrom(); + LayoutEdge edgeToRemove; + + if (edge.getLink() != null && edge.getLink().equals(inputLink)) { + toNode.getPreds().remove(edge); + edgeToRemove = edge; + } else { + // Wrong edge, look at next + continue; + } + + if (!predNode.isDummy() && predNode.getVertex().equals(from)) { + // No dummy nodes inbetween 'from' and 'to' vertex + predNode.getSuccs().remove(edgeToRemove); + break; + } else { + // Must remove edges between dummy nodes + boolean found = true; + LayoutNode succNode = toNode; + while (predNode.isDummy() && found) { + found = false; + + if (predNode.getSuccs().size() <= 1 && predNode.getPreds().size() <= 1) { + // Dummy node used only for this link, remove if not already removed + removeNode(predNode); + } else { + // anchor node, should not be removed + break; + } + + if (predNode.getPreds().size() == 1) { + predNode.getSuccs().remove(edgeToRemove); + succNode = predNode; + edgeToRemove = predNode.getPreds().get(0); + predNode = edgeToRemove.getFrom(); + found = true; + } + } + + predNode.getSuccs().remove(edgeToRemove); + succNode.getPreds().remove(edgeToRemove); + } + break; + } + } + + // remove link connected to movedNode + for (Link link : getLinks()) { + if (link.getTo().getVertex() == movedNode.getVertex()) { + link.setControlPoints(new ArrayList<>()); + movedNode.getReversedLinkStartPoints().remove(link); + } else if (link.getFrom().getVertex() == movedNode.getVertex()) { + link.setControlPoints(new ArrayList<>()); + movedNode.getReversedLinkEndPoints().remove(link); + } + } + + movedNode.initSize(); + } + + public void removeNodeAndEdges(LayoutNode node) { + removeEdges(node); + removeNode(node); + } + + + public LayoutLayer getLayer(int layerNr) { + return layers.get(layerNr); + } + + public int findLayer(int y) { + int optimalLayer = -1; + int minDistance = Integer.MAX_VALUE; + for (int l = 0; l < getLayerCount(); l++) { + // Check if y is within this layer's bounds + if (y >= getLayer(l).getTop() && y <= getLayer(l).getBottom()) { + return l; + } + + int distance = Math.abs(getLayer(l).getCenter() - y); + if (distance < minDistance) { + minDistance = distance; + optimalLayer = l; + } + } + return optimalLayer; + } + + public void positionLayers() { + int currentY = 0; + for (LayoutLayer layer : getLayers()) { + layer.setTop(currentY); + + // Calculate the maximum layer height and set it for the layer + int maxLayerHeight = layer.calculateMaxLayerHeight(); + layer.setHeight(maxLayerHeight); + + // Center nodes vertically within the layer + layer.centerNodesVertically(); + + // Update currentY to account for the padded bottom of this layer + currentY += layer.calculateScalePaddedBottom(); + } + } + + public void optimizeBackEdgeCrossings() { + for (LayoutNode node : getLayoutNodes()) { + if (node.getReversedLinkStartPoints().isEmpty() && node.getReversedLinkEndPoints().isEmpty()) continue; + node.computeReversedLinkPoints(); + } + } + + public void removeEmptyLayers() { + int i = 0; + while (i < getLayerCount()) { + LayoutLayer layer = getLayer(i); + if (layer.isDummyLayer()) { + removeEmptyLayer(i); + } else { + i++; // Move to the next layer only if no removal occurred + } + } + } + + private void removeEmptyLayer(int layerNr) { + LayoutLayer layer = getLayer(layerNr); + if (!layer.isDummyLayer()) return; + + for (LayoutNode dummyNode : layer) { + if (dummyNode.getSuccs().isEmpty()) { + dummyNode.setLayer(layerNr + 1); + getLayer(layerNr + 1).add(dummyNode); + dummyNode.setX(dummyNode.calculateOptimalPositionDown()); + getLayer(layerNr + 1).sortNodesByXAndSetPositions(); + continue; + } else if (dummyNode.getPreds().isEmpty()) { + dummyNode.setLayer(layerNr - 1); + dummyNode.setX(dummyNode.calculateOptimalPositionUp()); + getLayer(layerNr - 1).add(dummyNode); + getLayer(layerNr - 1).sortNodesByXAndSetPositions(); + continue; + } + LayoutEdge layoutEdge = dummyNode.getPreds().get(0); + + // remove the layoutEdge + LayoutNode fromNode = layoutEdge.getFrom(); + fromNode.getSuccs().remove(layoutEdge); + + List successorEdges = dummyNode.getSuccs(); + for (LayoutEdge successorEdge : successorEdges) { + successorEdge.setRelativeFromX(layoutEdge.getRelativeFromX()); + successorEdge.setFrom(fromNode); + fromNode.getSuccs().add(successorEdge); + } + dummyNode.getPreds().clear(); + dummyNode.getSuccs().clear(); + dummyNodes.remove(dummyNode); + } + + deleteLayer(layerNr); + } + + /** + * Repositions the given LayoutNode to the specified x-coordinate within its layer, + * ensuring no overlap with adjacent nodes and maintaining a minimum NODE_OFFSET distance. + * + * @param layoutNode The LayoutNode to be repositioned. + * @param newX The desired new x-coordinate for the layoutNode. + */ + private void repositionLayoutNodeX(LayoutNode layoutNode, int newX) { + int currentX = layoutNode.getX(); + + // Early exit if the desired position is the same as the current position + if (newX == currentX) { + return; + } + + LayoutLayer layer = getLayer(layoutNode.getLayer()); + if (newX > currentX) { + layer.attemptMoveRight(layoutNode, newX); + } else { + layer.attemptMoveLeft(layoutNode, newX); + } + } + + /** + * Aligns the x-coordinate of a single dummy successor node for the given LayoutNode. + * If the node has exactly one successor and that successor is a dummy node, + * this method sets the dummy node's x-coordinate to either the node's x-coordinate + * (if the node is a dummy) or to the starting x-coordinate of the connecting edge. + * + * @param node The LayoutNode whose single dummy successor needs to be aligned. + */ + private void alignSingleSuccessorDummyNodeX(LayoutNode node) { + // Retrieve the list of successor edges + List successors = node.getSuccs(); + + // Proceed only if there is exactly one successor + if (successors.size() != 1) { + return; + } + + LayoutEdge successorEdge = successors.get(0); + LayoutNode successorNode = successorEdge.getTo(); + + // Proceed only if the successor node is a dummy node + if (!successorNode.isDummy()) { + return; + } + + // Determine the target x-coordinate based on whether the current node is a dummy + int targetX = node.isDummy() ? node.getX() : successorEdge.getStartX(); + + // Align the successor dummy node to the target x-coordinate + repositionLayoutNodeX(successorNode, targetX); + } + + /** + * Aligns the x-coordinates of dummy successor nodes within the specified layer. + * Performs alignment in both forward and backward directions to ensure consistency. + * + * @param layer The LayoutLayer whose nodes' dummy successors need alignment. + */ + private void alignLayerDummySuccessors(LayoutLayer layer) { + // Forward pass: Align dummy successors from the first node to the last. + for (LayoutNode node : layer) { + alignSingleSuccessorDummyNodeX(node); + } + + // Backward pass: Align dummy successors from the last node to the first. + for (int i = layer.size() - 1; i >= 0; i--) { + LayoutNode node = layer.get(i); + alignSingleSuccessorDummyNodeX(node); + } + } + + /** + * Aligns the x-coordinates of dummy successor nodes across all layers. + * Performs alignment in both forward and backward directions for comprehensive coverage. + */ + public void straightenEdges() { + // Forward pass: Align dummy successors from the first layer to the last. + for (int i = 0; i < getLayerCount(); i++) { + alignLayerDummySuccessors(getLayer(i)); + } + + // Backward pass: Align dummy successors from the last layer to the first. + for (int i = getLayerCount() - 1; i >= 0; i--) { + alignLayerDummySuccessors(getLayer(i)); + } + } + +} diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java new file mode 100644 index 0000000000000..dc530dd31d7e5 --- /dev/null +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 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 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.sun.hotspot.igv.hierarchicallayout; + +import static com.sun.hotspot.igv.hierarchicallayout.LayoutManager.*; +import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.NODE_X_COMPARATOR; +import java.util.ArrayList; +import java.util.Collection; + +public class LayoutLayer extends ArrayList { + + private int height = 0; + private int y = 0; + + @Override + public boolean addAll(Collection c) { + c.forEach(this::updateHeight); + return super.addAll(c); + } + + private void updateHeight(LayoutNode n) { + height = Math.max(height, n.getOuterHeight()); + } + + @Override + public boolean add(LayoutNode n) { + updateHeight(n); + return super.add(n); + } + + public int calculateMaxLayerHeight() { + int maxLayerHeight = 0; + for (LayoutNode layoutNode : this) { + if (!layoutNode.isDummy()) { + // Center the node by setting equal top and bottom margins + int offset = Math.max(layoutNode.getTopMargin(), layoutNode.getBottomMargin()); + layoutNode.setTopMargin(offset); + layoutNode.setBottomMargin(offset); + } + maxLayerHeight = Math.max(maxLayerHeight, layoutNode.getOuterHeight()); + } + return maxLayerHeight; + } + + public int calculateScalePaddedBottom() { + int maxXOffset = 0; + + for (LayoutNode layoutNode : this) { + for (LayoutEdge succEdge : layoutNode.getSuccs()) { + maxXOffset = Math.max(Math.abs(succEdge.getStartX() - succEdge.getEndX()), maxXOffset); + } + } + + int scalePaddedBottom = this.getHeight(); + scalePaddedBottom += (int) (SCALE_LAYER_PADDING * Math.max((int) (Math.sqrt(maxXOffset) * 2), LAYER_OFFSET * 3)); + return scalePaddedBottom; + } + + public void centerNodesVertically() { + for (LayoutNode layoutNode : this) { + int centeredY = getTop() + (getHeight() - layoutNode.getOuterHeight()) / 2; + layoutNode.setY(centeredY); + } + } + + public void setTop(int top) { + y = top; + } + + public void shiftTop(int shift) { + y += shift; + } + + public int getTop() { + return y; + } + + public int getCenter() { + return y + height / 2; + } + + public int getBottom() { + return y + height; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + // Layer contains no non-dummy nodes + public boolean isDummyLayer() { + for (LayoutNode node : this) { + if (!node.isDummy()) { + return false; + } + } + return true; + } + + public void sortNodesByXAndSetPositions() { + if (this.isEmpty()) return; + + // Sort nodes in the layer increasingly by x + this.sort(NODE_X_COMPARATOR); + + int pos = 0; + int minX = this.get(0).getX(); // Starting X position for the first node + + for (LayoutNode node : this) { + node.setPos(pos); + pos++; + + // Set the X position of the node to at least minX, ensuring spacing + int x = Math.max(node.getX(), minX); + node.setX(x); + + // Update minX for the next node based on the current node's outer width and offset + minX = x + node.getOuterWidth() + NODE_OFFSET; + } + } + + public void updateLayerPositions() { + int pos = 0; + for (LayoutNode layoutNode : this) { + layoutNode.setPos(pos); + pos++; + } + } + + public void attemptMoveRight(LayoutNode layoutNode, int newX) { + int currentX = layoutNode.getX(); + int shiftAmount = newX - currentX; + int rightPos = layoutNode.getPos() + 1; + + if (rightPos < size()) { + // There is a right neighbor + LayoutNode rightNeighbor = get(rightPos); + int proposedRightEdge = layoutNode.getRight() + shiftAmount; + int requiredLeftEdge = rightNeighbor.getOuterLeft() - NODE_OFFSET; + + if (proposedRightEdge <= requiredLeftEdge) { + layoutNode.setX(newX); + } + } else { + // No right neighbor; safe to move freely to the right + layoutNode.setX(newX); + } + } + + public void attemptMoveLeft(LayoutNode layoutNode, int newX) { + int currentX = layoutNode.getX(); + int shiftAmount = currentX - newX; + int leftPos = layoutNode.getPos() - 1; + + if (leftPos >= 0) { + // There is a left neighbor + LayoutNode leftNeighbor = get(leftPos); + int proposedLeftEdge = layoutNode.getLeft() - shiftAmount; + int requiredRightEdge = leftNeighbor.getOuterRight() + NODE_OFFSET; + + if (requiredRightEdge <= proposedLeftEdge) { + layoutNode.setX(newX); + } + } else { + // No left neighbor; safe to move freely to the left + layoutNode.setX(newX); + } + } +} diff --git a/src/utils/IdealGraphVisualizer/Layout/src/main/java/com/sun/hotspot/igv/layout/LayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutManager.java similarity index 68% rename from src/utils/IdealGraphVisualizer/Layout/src/main/java/com/sun/hotspot/igv/layout/LayoutManager.java rename to src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutManager.java index a74ef280ebeb3..be88f567eb6e1 100644 --- a/src/utils/IdealGraphVisualizer/Layout/src/main/java/com/sun/hotspot/igv/layout/LayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutManager.java @@ -21,19 +21,28 @@ * questions. * */ -package com.sun.hotspot.igv.layout; +package com.sun.hotspot.igv.hierarchicallayout; -import java.util.Set; +import java.awt.Font; /** * * @author Thomas Wuerthinger */ -public interface LayoutManager { +public abstract class LayoutManager { void setCutEdges(boolean enable); void doLayout(LayoutGraph graph); - void doLayout(LayoutGraph graph, Set importantLinks); + public abstract void setCutEdges(boolean enable); + + public static final Font TITLE_FONT = new Font("Arial", Font.BOLD, 14); + public static final int SWEEP_ITERATIONS = 1; + public static final int CROSSING_ITERATIONS = 1; + public static final int NODE_OFFSET = 8; + public static final int LAYER_OFFSET = 8; + public static final double SCALE_LAYER_PADDING = 1.5; + + public abstract void doLayout(LayoutGraph graph); } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java index 848ada1b77d36..931ef1f2024ea 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java @@ -23,31 +23,398 @@ */ package com.sun.hotspot.igv.hierarchicallayout; +import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Vertex; +import java.awt.Dimension; +import java.awt.Point; import java.util.*; +import static com.sun.hotspot.igv.hierarchicallayout.LayoutManager.NODE_OFFSET; + public 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 Vertex vertex; // Only used for non-dummy nodes, otherwise null + public static final Comparator LAYOUT_NODE_DEGREE_COMPARATOR = Comparator.comparingInt(LayoutNode::getDegree); + public static final Comparator NODE_POS_COMPARATOR = Comparator.comparingInt(LayoutNode::getPos); + public static final Comparator NODE_X_COMPARATOR = Comparator.comparingInt(LayoutNode::getX); + public static final Comparator CROSSING_NODE_COMPARATOR = Comparator.comparingDouble(LayoutNode::getWeightedPosition); + public static final Comparator DUMMY_NODES_FIRST = Comparator.comparing(LayoutNode::isDummy).reversed(); + public static final Comparator NODE_PROCESSING_DOWN_COMPARATOR = DUMMY_NODES_FIRST.thenComparingInt(LayoutNode::getOutDegree); + public static final Comparator NODE_PROCESSING_UP_COMPARATOR = DUMMY_NODES_FIRST.thenComparing(LayoutNode::getInDegree); + public static final Comparator DUMMY_NODES_THEN_OPTIMAL_X = DUMMY_NODES_FIRST.thenComparing(LayoutNode::getOptimalX); + + public static final int DUMMY_HEIGHT = 1; + public static final int DUMMY_WIDTH = 1; + + private int layer = -1; + private int optimal_x; + private int x; + private int y; + private int width; + private int height; + private int topMargin; + private int bottomMargin; + private int rightMargin; + private int leftMargin; + + private final Vertex vertex; // Only used for non-dummy nodes, otherwise null + + private final List preds = new ArrayList<>(); + private final List succs = new ArrayList<>(); + private final HashMap> reversedLinkStartPoints = new HashMap<>(); + private final HashMap> reversedLinkEndPoints = new HashMap<>(); + private int pos = -1; // Position within layer + + private float weightedPosition = 0; + + public LayoutNode(Vertex v) { + vertex = v; + initSize(); + } + + public void initSize() { + if (vertex == null) { + height = DUMMY_HEIGHT; + width = DUMMY_WIDTH; + } else { + Dimension size = vertex.getSize(); + height = size.height; + width = size.width; + } + setTopMargin(0); + setBottomMargin(0); + setLeftMargin(0); + setRightMargin(0); + } + + public int calculateOptimalPositionDown() { + int numPreds = preds.size(); + if (numPreds == 0) { + return getX(); + } + + List positions = new ArrayList<>(numPreds); + for (LayoutEdge edge : preds) { + positions.add(edge.getStartX() - edge.getRelativeToX()); + } + + Collections.sort(positions); + int midIndex = numPreds / 2; + return (numPreds % 2 == 0) + ? (positions.get(midIndex - 1) + positions.get(midIndex)) / 2 + : positions.get(midIndex); + } + + public int calculateOptimalPositionUp() { + int numSuccs = succs.size(); + if (numSuccs == 0) { + return getX(); + } + + List positions = new ArrayList<>(numSuccs); + for (LayoutEdge edge : succs) { + positions.add(edge.getEndX() - edge.getRelativeFromX()); + } + + Collections.sort(positions); + int midIndex = numSuccs / 2; + return (numSuccs % 2 == 0) + ? (positions.get(midIndex - 1) + positions.get(midIndex)) / 2 + : positions.get(midIndex); + } + + public LayoutNode() { + this(null); + } + + public int getOutDegree() { + return succs.size(); + } + + public int getInDegree() { + return preds.size(); + } + + public int getDegree() { + return preds.size() + succs.size(); + } + + public float averagePosition() { + float totalWeightedPosition = 0; + float totalWeight = 0; + + for (LayoutEdge predEdge : preds) { + LayoutNode predNode = predEdge.getFrom(); + int weight = predNode.getDegree(); + totalWeightedPosition += weight * predEdge.getStartX(); + totalWeight += weight; + } + for (LayoutEdge succEdge : succs) { + LayoutNode succNode = succEdge.getTo(); + int weight = succNode.getDegree(); + totalWeightedPosition += weight * succEdge.getEndX(); + totalWeight += weight; + } + + // Calculate the (weighted) average position for the node based on neighbor positions and weights (degree) + return totalWeight > 0 ? totalWeightedPosition / totalWeight : 0; + } + + public int getLeft() { + return x + leftMargin; + } + + public int getOuterLeft() { + return x; + } + + public int getOuterWidth() { + return leftMargin + width + rightMargin; + } + + public int getOuterHeight() { + return topMargin + height + bottomMargin; + } + + public int getRight() { + return x + leftMargin + width; + } + + public int getOuterRight() { + return x + leftMargin + width + rightMargin; + } + + public int getCenterX() { + return x + leftMargin + (width / 2); + } + + public int getTop() { + return y + topMargin; + } + + public int getBottom() { + return y + topMargin + height; + } + + public boolean isDummy() { + return vertex == null; + } + @Override + public String toString() { + if (vertex != null) { + return vertex.toString(); + } else { + return "dummy"; + } + } + + public int getOptimalX() { + return optimal_x; + } + + public void setOptimalX(int optimal_x) { + this.optimal_x = optimal_x; + } + + public int getX() { + return x; + } + + public void setX(int x) { + this.x = x; + } + + public int getY() { + return y; + } + + public void setY(int y) { + this.y = y; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public int getLayer() { + return layer; + } + + public void setLayer(int layer) { + this.layer = layer; + } + + public int getLeftMargin() { + return leftMargin; + } + + public void setLeftMargin(int leftMargin) { + this.leftMargin = leftMargin; + } + + public int getTopMargin() { + return topMargin; + } + + public void setTopMargin(int topMargin) { + this.topMargin = topMargin; + } + + public int getRightMargin() { + return rightMargin; + } + + public void setRightMargin(int rightMargin) { + this.rightMargin = rightMargin; + } + + public int getBottomMargin() { + return bottomMargin; + } + + public void setBottomMargin(int bottomMargin) { + this.bottomMargin = bottomMargin; + } + + public Vertex getVertex() { + return vertex; + } + + public List getPreds() { + return preds; + } + + public boolean hasPreds() { + return !preds.isEmpty(); + } + + public boolean hasSuccs() { + return !succs.isEmpty(); + } + + public List getSuccs() { + return succs; + } + + public Map> groupSuccessorsByX() { + Map> result = new HashMap<>(); + for (LayoutEdge succEdge : succs) { + result.computeIfAbsent(succEdge.getRelativeFromX(), k -> new ArrayList<>()).add(succEdge); + } + return result; + } + + public HashMap> getReversedLinkStartPoints() { + return reversedLinkStartPoints; + } + + public HashMap> getReversedLinkEndPoints() { + return reversedLinkEndPoints; + } + + public int getPos() { + return pos; + } + + public void setPos(int pos) { + this.pos = pos; + } - public List preds = new ArrayList<>(); - public List succs = new ArrayList<>(); - public HashMap outOffsets = new HashMap<>(); - public HashMap inOffsets = new HashMap<>(); - public int pos = -1; // Position within layer + public float getWeightedPosition() { + return weightedPosition; + } - public int crossingNumber; + public void setWeightedPosition(float weightedPosition) { + this.weightedPosition = weightedPosition; + } - @Override - public String toString() { - return "Node " + vertex; + private void computeReversedStartPoints() { + TreeMap> sortedDownMap = new TreeMap<>(Collections.reverseOrder()); + for (LayoutEdge succEdge : getSuccs()) { + if (succEdge.isReversed()) { + succEdge.setRelativeFromX(succEdge.getLink().getTo().getRelativePosition().x); + sortedDownMap.putIfAbsent(succEdge.getRelativeFromX(), new ArrayList<>()); + sortedDownMap.get(succEdge.getRelativeFromX()).add(succEdge); + } } + + int offset = NODE_OFFSET + LayoutNode.DUMMY_WIDTH; + int currentX = getWidth(); + int startY = 0; + int currentY = 0; + for (Map.Entry> entry : sortedDownMap.entrySet()) { + int startX = entry.getKey(); + ArrayList reversedSuccs = entry.getValue(); + + currentX += offset; + currentY -= offset; + setTopMargin(getTopMargin() + offset); + + ArrayList startPoints = new ArrayList<>(); + startPoints.add(new Point(currentX, currentY)); + startPoints.add(new Point(startX, currentY)); + startPoints.add(new Point(startX, startY)); + for (LayoutEdge revEdge : reversedSuccs) { + revEdge.setRelativeFromX(currentX); + getReversedLinkStartPoints().put(revEdge.getLink(), startPoints); + } + } + setLeftMargin(getLeftMargin()); + setRightMargin(getRightMargin() + (sortedDownMap.size() * offset)); + } + + private void computeReversedEndPoints() { + TreeMap> sortedUpMap = new TreeMap<>(Collections.reverseOrder()); + for (LayoutEdge predEdge : getPreds()) { + if (predEdge.isReversed()) { + predEdge.setRelativeToX(predEdge.getLink().getFrom().getRelativePosition().x); + sortedUpMap.putIfAbsent(predEdge.getRelativeToX(), new ArrayList<>()); + sortedUpMap.get(predEdge.getRelativeToX()).add(predEdge); + } + } + + int offset = NODE_OFFSET + LayoutNode.DUMMY_WIDTH; + int currentX = getWidth(); + int startY = getHeight(); + int currentY = getHeight(); + for (Map.Entry> entry : sortedUpMap.entrySet()) { + int startX = entry.getKey(); + ArrayList reversedPreds = entry.getValue(); + + currentX += offset; + currentY += offset; + setBottomMargin(getBottomMargin() + offset); + + ArrayList endPoints = new ArrayList<>(); + endPoints.add(new Point(currentX, currentY)); + endPoints.add(new Point(startX, currentY)); + endPoints.add(new Point(startX, startY)); + for (LayoutEdge revEdge : reversedPreds) { + revEdge.setRelativeToX(currentX); + getReversedLinkEndPoints().put(revEdge.getLink(), endPoints); + } + } + setLeftMargin(getLeftMargin()); + setRightMargin(getRightMargin() + (sortedUpMap.size() * offset)); + } + + public void computeReversedLinkPoints() { + initSize(); + getReversedLinkStartPoints().clear(); + getReversedLinkEndPoints().clear(); + + computeReversedStartPoints(); + computeReversedEndPoints(); } +} diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LinearLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LinearLayoutManager.java deleted file mode 100644 index 69d53ebc9fa62..0000000000000 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LinearLayoutManager.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 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 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ -package com.sun.hotspot.igv.hierarchicallayout; - -import com.sun.hotspot.igv.layout.LayoutGraph; -import com.sun.hotspot.igv.layout.LayoutManager; -import com.sun.hotspot.igv.layout.Link; -import com.sun.hotspot.igv.layout.Vertex; -import java.awt.Point; -import java.util.*; - -public class LinearLayoutManager implements LayoutManager { - - // Ranking determining the vertical node ordering. - private final Map vertexRank; - - public LinearLayoutManager(Map vertexRank) { - this.vertexRank = vertexRank; - } - - @Override - public void setCutEdges(boolean enable) {} - - @Override - public void doLayout(LayoutGraph graph) { - doLayout(graph, new HashSet<>()); - } - - @Override - public void doLayout(LayoutGraph graph, Set importantLinks) { - - assert (graph.getLinks().isEmpty()); - - // Sort vertices according to given rank. - List vertices = new ArrayList<>(graph.getVertices()); - vertices.sort(Comparator.comparingInt((Vertex v) -> vertexRank.getOrDefault(v, Integer.MAX_VALUE))); - - // Assign vertical coordinates in rank order. - assignVerticalCoordinates(vertices); - } - - private void assignVerticalCoordinates(List vertices) { - int curY = 0; - for (Vertex v : vertices) { - v.setPosition(new Point(0, curY)); - curY += v.getSize().getHeight(); - } - } -} diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/Timing.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/Timing.java deleted file mode 100644 index 080516df12035..0000000000000 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/Timing.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 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 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ -package com.sun.hotspot.igv.hierarchicallayout; - -/** - * - * @author Thomas Wuerthinger - */ -public class Timing { - - private long lastValue; - private long sum; - private final String name; - - public Timing(String name) { - this.name = name; - } - - @Override - public String toString() { - long val = sum; - if (lastValue != 0) { - // Timer running - long newValue = System.nanoTime(); - val += (newValue - lastValue); - } - return "Timing for " + name + " is: " + val / 1000000 + " ms"; - } - - public void print() { - System.out.println(); - } - - public void start() { - lastValue = System.nanoTime(); - } - - public void stop() { - if (lastValue == 0) { - throw new IllegalStateException("You must call start before stop"); - } - long newValue = System.nanoTime(); - sum += newValue - lastValue; - lastValue = 0; - } -} diff --git a/src/utils/IdealGraphVisualizer/Layout/src/main/java/com/sun/hotspot/igv/layout/Cluster.java b/src/utils/IdealGraphVisualizer/Layout/src/main/java/com/sun/hotspot/igv/layout/Cluster.java index e6938cb2f1910..b0c21fedf302c 100644 --- a/src/utils/IdealGraphVisualizer/Layout/src/main/java/com/sun/hotspot/igv/layout/Cluster.java +++ b/src/utils/IdealGraphVisualizer/Layout/src/main/java/com/sun/hotspot/igv/layout/Cluster.java @@ -23,8 +23,9 @@ */ package com.sun.hotspot.igv.layout; -import java.awt.Dimension; +import java.awt.Point; import java.awt.Rectangle; +import java.util.List; import java.util.Set; /** @@ -35,7 +36,13 @@ public interface Cluster extends Comparable { void setBounds(Rectangle r); - Set getSuccessors(); + void setPosition(Point p); + + Point getPosition(); + + Rectangle getBounds(); - Dimension getNodeOffset(); + List getVertices(); + + Set getSuccessors(); } diff --git a/src/utils/IdealGraphVisualizer/Layout/src/main/java/com/sun/hotspot/igv/layout/LayoutGraph.java b/src/utils/IdealGraphVisualizer/Layout/src/main/java/com/sun/hotspot/igv/layout/LayoutGraph.java deleted file mode 100644 index ad358cba29914..0000000000000 --- a/src/utils/IdealGraphVisualizer/Layout/src/main/java/com/sun/hotspot/igv/layout/LayoutGraph.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 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 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ -package com.sun.hotspot.igv.layout; - -import java.util.*; - -/** - * - * @author Thomas Wuerthinger - */ -public class LayoutGraph { - - private final Set links; - private final SortedSet vertices; - private final HashMap> inputPorts; - private final HashMap> outputPorts; - private final HashMap> portLinks; - - public LayoutGraph(Set links) { - this(links, new HashSet<>()); - } - - public LayoutGraph(Set links, Set additionalVertices) { - this.links = links; - assert verify(); - - vertices = new TreeSet<>(); - portLinks = new HashMap<>(links.size()); - inputPorts = new HashMap<>(links.size()); - outputPorts = new HashMap<>(links.size()); - - for (Link l : links) { - if (l.getFrom() == null || l.getTo() == null) { - continue; - } - Port p = l.getFrom(); - Port p2 = l.getTo(); - Vertex v1 = p.getVertex(); - Vertex v2 = p2.getVertex(); - - if (!vertices.contains(v1)) { - - outputPorts.put(v1, new HashSet<>(1)); - inputPorts.put(v1, new HashSet<>(3)); - vertices.add(v1); - assert vertices.contains(v1); - } - - if (!vertices.contains(v2)) { - vertices.add(v2); - assert vertices.contains(v2); - outputPorts.put(v2, new HashSet<>(1)); - inputPorts.put(v2, new HashSet<>(3)); - } - - if (!portLinks.containsKey(p)) { - HashSet hashSet = new HashSet<>(3); - portLinks.put(p, hashSet); - } - - if (!portLinks.containsKey(p2)) { - portLinks.put(p2, new HashSet<>(3)); - } - - outputPorts.get(v1).add(p); - inputPorts.get(v2).add(p2); - - portLinks.get(p).add(l); - portLinks.get(p2).add(l); - } - - for (Vertex v : additionalVertices) { - if (!vertices.contains(v)) { - outputPorts.put(v, new HashSet<>(1)); - inputPorts.put(v, new HashSet<>(3)); - vertices.add(v); - } - } - } - - public Set getInputPorts(Vertex v) { - return this.inputPorts.get(v); - } - - public Set getOutputPorts(Vertex v) { - return this.outputPorts.get(v); - } - - public Set getPortLinks(Port p) { - return portLinks.get(p); - } - - public Set getLinks() { - return links; - } - - public boolean verify() { - return true; - } - - public SortedSet getVertices() { - return vertices; - } - - private void markNotRoot(Set notRootSet, Vertex v, Vertex startingVertex) { - - if (notRootSet.contains(v)) { - return; - } - if (v != startingVertex) { - notRootSet.add(v); - } - Set outPorts = getOutputPorts(v); - for (Port p : outPorts) { - Set portLinks = getPortLinks(p); - for (Link l : portLinks) { - Port other = l.getTo(); - Vertex otherVertex = other.getVertex(); - if (otherVertex != startingVertex) { - markNotRoot(notRootSet, otherVertex, startingVertex); - } - } - } - } - - // Returns a set of vertices with the following properties: - // - All Vertices in the set startingRoots are elements of the set. - // - When starting a DFS at every vertex in the set, every vertex of the - // whole graph is visited. - public Set findRootVertices(Set startingRoots) { - - Set notRootSet = new HashSet<>(); - for (Vertex v : startingRoots) { - if (!notRootSet.contains(v)) { - markNotRoot(notRootSet, v, v); - } - } - - Set tmpVertices = getVertices(); - for (Vertex v : tmpVertices) { - if (!notRootSet.contains(v)) { - if (this.getInputPorts(v).size() == 0) { - markNotRoot(notRootSet, v, v); - } - } - } - - for (Vertex v : tmpVertices) { - if (!notRootSet.contains(v)) { - markNotRoot(notRootSet, v, v); - } - } - - Set result = new HashSet<>(); - for (Vertex v : tmpVertices) { - if (!notRootSet.contains(v)) { - result.add(v); - } - } - assert tmpVertices.size() == 0 || result.size() > 0; - return result; - } - - public Set findRootVertices() { - return findRootVertices(new HashSet<>()); - } - - public SortedSet getClusters() { - - SortedSet clusters = new TreeSet<>(); - for (Vertex v : getVertices()) { - if (v.getCluster() != null) { - clusters.add(v.getCluster()); - } - } - - return clusters; - } - - @Override - public String toString() { - return "LayoutGraph(" + vertices + ", " + links + ", " + getClusters() + ")"; - } -} diff --git a/src/utils/IdealGraphVisualizer/Layout/src/main/java/com/sun/hotspot/igv/layout/Link.java b/src/utils/IdealGraphVisualizer/Layout/src/main/java/com/sun/hotspot/igv/layout/Link.java index 681ef7af33a6a..c568531145a06 100644 --- a/src/utils/IdealGraphVisualizer/Layout/src/main/java/com/sun/hotspot/igv/layout/Link.java +++ b/src/utils/IdealGraphVisualizer/Layout/src/main/java/com/sun/hotspot/igv/layout/Link.java @@ -40,8 +40,6 @@ public interface Link { Cluster getToCluster(); - boolean isVIP(); - List getControlPoints(); void setControlPoints(List list); diff --git a/src/utils/IdealGraphVisualizer/Util/src/main/java/com/sun/hotspot/igv/util/Statistics.java b/src/utils/IdealGraphVisualizer/Util/src/main/java/com/sun/hotspot/igv/util/Statistics.java deleted file mode 100644 index b40ce7919df3e..0000000000000 --- a/src/utils/IdealGraphVisualizer/Util/src/main/java/com/sun/hotspot/igv/util/Statistics.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.sun.hotspot.igv.util; - -import java.util.Arrays; - -public class Statistics { - - public static int median(int[] values) { - Arrays.sort(values); - if (values.length % 2 == 0) { - return (values[values.length / 2 - 1] + values[values.length / 2]) / 2; - } else { - return values[values.length / 2]; - } - } -} diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java index 3f7fec7450508..1c2b9383b02b3 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java @@ -27,7 +27,8 @@ import com.sun.hotspot.igv.data.*; import com.sun.hotspot.igv.graph.*; import com.sun.hotspot.igv.hierarchicallayout.*; -import com.sun.hotspot.igv.layout.LayoutGraph; +import com.sun.hotspot.igv.layout.Cluster; +import com.sun.hotspot.igv.hierarchicallayout.LayoutGraph; import com.sun.hotspot.igv.selectioncoordinator.SelectionCoordinator; import com.sun.hotspot.igv.util.ColorIcon; import com.sun.hotspot.igv.util.DoubleClickAction; @@ -49,8 +50,6 @@ import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import org.netbeans.api.visual.action.*; -import org.netbeans.api.visual.animator.AnimatorEvent; -import org.netbeans.api.visual.animator.AnimatorListener; import org.netbeans.api.visual.layout.LayoutFactory; import org.netbeans.api.visual.model.*; import org.netbeans.api.visual.widget.LayerWidget; @@ -101,7 +100,6 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl public static final float ZOOM_MAX_FACTOR = 4.0f; public static final float ZOOM_MIN_FACTOR = 0.25f; public static final float ZOOM_INCREMENT = 1.5f; - public static final int SLOT_OFFSET = 8; public static final int ANIMATION_LIMIT = 40; @SuppressWarnings("unchecked") @@ -329,7 +327,10 @@ public void select(Widget widget, Point localLocation, boolean invertSelection) addChild(blockLayer); connectionLayer = new LayerWidget(this); - addChild(connectionLayer); + LayerWidget paddedLayer = new LayerWidget(this); + paddedLayer.setBorder(BorderFactory.createEmptyBorder(50, 50, 50, 50)); // Adds 50px padding on all sides + paddedLayer.addChild(connectionLayer); + addChild(paddedLayer); mainLayer = new LayerWidget(this); addChild(mainLayer); @@ -357,6 +358,7 @@ public void select(Widget widget, Point localLocation, boolean invertSelection) rectangle.height *= -1; } + clearSelectedNodes(); Set selectedObjects = new HashSet<>(); for (Figure f : getModel().getDiagram().getFigures()) { FigureWidget w = getWidget(f); @@ -478,9 +480,6 @@ public void focusChanged(ObjectSceneEvent arg0, Object arg1, Object arg2) { }; addObjectSceneListener(selectionChangedListener, ObjectSceneEventType.OBJECT_SELECTION_CHANGED, ObjectSceneEventType.OBJECT_HIGHLIGHTING_CHANGED, ObjectSceneEventType.OBJECT_HOVER_CHANGED); - this.model = model; - modelState = new ModelState(model); - model.getDiagramChangedEvent().addListener(m -> update()); model.getGraphChangedEvent().addListener(m -> graphChanged()); model.getHiddenNodesChangedEvent().addListener(m -> hiddenNodesChanged()); @@ -498,6 +497,9 @@ public void ancestorResized(HierarchyEvent e) { }); hierarchicalStableLayoutManager = new HierarchicalStableLayoutManager(); + + this.model = model; + modelState = new ModelState(model); } @Override @@ -554,9 +556,11 @@ public void actionPerformed(ActionEvent e) { } private void clearObjects() { - Collection objects = new ArrayList<>(getObjects()); - for (Object o : objects) { - removeObject(o); + Set objectSet = new HashSet<>(getObjects()); + for (Object object : objectSet) { + if (isObject(object)) { + removeObject(object); + } } } @@ -569,19 +573,19 @@ private void updateFigureTexts() { private void updateFigureWidths() { if (getModel().getShowCFG()) { - Map maxWidth = new HashMap<>(); - for (InputBlock inputBlock : getModel().getDiagram().getInputBlocks()) { - maxWidth.put(inputBlock, 10); + Map maxWidth = new HashMap<>(); + for (Block block : getModel().getDiagram().getBlocks()) { + maxWidth.put(block, 10); } for (Figure figure : getModel().getDiagram().getFigures()) { // Compute max node width in each block. - if (figure.getWidth() > maxWidth.get(figure.getBlock().getInputBlock())) { - maxWidth.put(figure.getBlock().getInputBlock(), figure.getWidth()); + if (figure.getWidth() > maxWidth.get(figure.getBlock())) { + maxWidth.put(figure.getBlock(), figure.getWidth()); } } for (Figure figure : getModel().getDiagram().getFigures()) { // Set all nodes' width to the maximum width in the blocks? - figure.setWidth(maxWidth.get(figure.getBlock().getInputBlock())); + figure.setWidth(maxWidth.get(figure.getBlock())); } } } @@ -598,7 +602,7 @@ private void rebuildMainLayer() { mainLayer.addChild(figureWidget); for (InputSlot inputSlot : figure.getInputSlots()) { - SlotWidget slotWidget = new InputSlotWidget(inputSlot, this, figureWidget, figureWidget); + SlotWidget slotWidget = new InputSlotWidget(inputSlot, this, figureWidget); slotWidget.getActions().addAction(new DoubleClickAction(slotWidget)); slotWidget.getActions().addAction(hoverAction); slotWidget.getActions().addAction(selectAction); @@ -606,7 +610,7 @@ private void rebuildMainLayer() { } for (OutputSlot outputSlot : figure.getOutputSlots()) { - SlotWidget slotWidget = new OutputSlotWidget(outputSlot, this, figureWidget, figureWidget); + SlotWidget slotWidget = new OutputSlotWidget(outputSlot, this, figureWidget); slotWidget.getActions().addAction(new DoubleClickAction(slotWidget)); slotWidget.getActions().addAction(hoverAction); slotWidget.getActions().addAction(selectAction); @@ -618,11 +622,11 @@ private void rebuildMainLayer() { private void rebuildBlockLayer() { blockLayer.removeChildren(); if (getModel().getShowBlocks() || getModel().getShowCFG()) { - for (InputBlock inputBlock : getModel().getDiagram().getInputBlocks()) { - BlockWidget blockWidget = new BlockWidget(this, inputBlock); + for (Block block : getModel().getDiagram().getBlocks()) { + BlockWidget blockWidget = new BlockWidget(this, block); blockWidget.getActions().addAction(new DoubleClickAction(blockWidget)); blockWidget.setVisible(false); - addObject(inputBlock, blockWidget); + addObject(block, blockWidget); blockLayer.addChild(blockWidget); } } @@ -636,8 +640,41 @@ private void update() { rebuildMainLayer(); rebuildBlockLayer(); relayout(); - setFigureSelection(model.getSelectedFigures()); + rebuilding = false; + } + + private void hiddenNodesChanged() { + relayout(); + addUndo(); + } + + private void relayout() { + rebuilding = true; + Set oldVisibleFigureWidgets = getVisibleFigureWidgets(); + Set oldVisibleBlockWidgets = getVisibleBlockWidgets(); + + updateVisibleFigureWidgets(); + updateNodeHull(); + updateVisibleBlockWidgets(); + validateAll(); + + Set
visibleFigures = getVisibleFigures(); + Set visibleConnections = getVisibleConnections(); + if (getModel().getShowStableSea()) { + doStableSeaLayout(visibleFigures, visibleConnections); + } else if (getModel().getShowSea()) { + doSeaLayout(visibleFigures, visibleConnections); + } else if (getModel().getShowBlocks()) { + doClusteredLayout(visibleConnections); + } else if (getModel().getShowCFG()) { + doCFGLayout(visibleFigures, visibleConnections); + } + rebuildConnectionLayer(); + + updateFigureWidgetLocations(oldVisibleFigureWidgets); + updateBlockWidgetBounds(oldVisibleBlockWidgets); validateAll(); + setFigureSelection(model.getSelectedFigures()); centerSelectedFigures(); rebuilding = false; } @@ -663,7 +700,7 @@ private void centerRootNode() { Rectangle bounds = rootWidget.getBounds(); if (bounds != null) { Point location = rootWidget.getLocation(); - centerRectangle(new Rectangle(location.x, location.y, bounds.width, bounds.height), false); + centerRectangle(new Rectangle(location.x, location.y, bounds.width, bounds.height)); } } } @@ -671,112 +708,50 @@ private void centerRootNode() { } } - private void hiddenNodesChanged() { - relayout(); - addUndo(); - } - protected boolean isRebuilding() { return rebuilding; } - private boolean isVisible(Connection c) { + public boolean isVisibleBlockConnection(BlockConnection blockConnection) { + Widget w1 = getWidget(blockConnection.getFromCluster()); + Widget w2 = getWidget(blockConnection.getToCluster()); + return w1.isVisible() && w2.isVisible(); + } + + private boolean isVisibleFigureConnection(FigureConnection figureConnection) { // Generally, a connection is visible if its source and destination // widgets are visible. An exception is Figure connections in the CFG // view, which are never shown. - if (getModel().getShowCFG() && c instanceof FigureConnection) { + if (getModel().getShowCFG()) { return false; } - Widget w1, w2; - if (c instanceof BlockConnection) { - w1 = getWidget(((Block)c.getFromCluster()).getInputBlock()); - w2 = getWidget(((Block)c.getToCluster()).getInputBlock()); - } else { - assert (c instanceof FigureConnection); - w1 = getWidget(c.getFrom().getVertex()); - w2 = getWidget(c.getTo().getVertex()); - } + Widget w1 = getWidget(figureConnection.getFrom().getVertex()); + Widget w2 = getWidget(figureConnection.getTo().getVertex()); return w1.isVisible() && w2.isVisible(); } - private void doStableSeaLayout(HashSet
visibleFigures, HashSet visibleConnections) { - boolean enable = model.getCutEdges(); - boolean previous = hierarchicalStableLayoutManager.getCutEdges(); - hierarchicalStableLayoutManager.setCutEdges(enable); - if (enable != previous) { - hierarchicalStableLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); - } else { - hierarchicalStableLayoutManager.updateLayout(visibleFigures, visibleConnections); - } + private void doStableSeaLayout(Set
visibleFigures, Set visibleConnections) { + hierarchicalStableLayoutManager.setCutEdges(model.getCutEdges()); + hierarchicalStableLayoutManager.updateLayout(visibleFigures, visibleConnections); } - private void doSeaLayout(HashSet
figures, HashSet edges) { - HierarchicalLayoutManager manager = new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS); - manager.setCutEdges(model.getCutEdges()); - manager.doLayout(new LayoutGraph(edges, figures)); - hierarchicalStableLayoutManager.setShouldRedrawLayout(true); - } - - private void doClusteredLayout(HashSet edges) { - HierarchicalClusterLayoutManager m = new HierarchicalClusterLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS); - HierarchicalLayoutManager manager = new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS); - manager.setCutEdges(model.getCutEdges()); - manager.setMinLayerDifference(3); - m.setManager(manager); - m.setSubManager(new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS)); - m.doLayout(new LayoutGraph(edges)); - } - - private void doCFGLayout(HashSet
figures, HashSet edges) { - Diagram diagram = getModel().getDiagram(); - HierarchicalCFGLayoutManager m = new HierarchicalCFGLayoutManager(); - HierarchicalLayoutManager manager = new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS); - manager.setCutEdges(model.getCutEdges()); - manager.setMinLayerDifference(1); - manager.setLayoutSelfEdges(true); - manager.setXOffset(25); - manager.setLayerOffset(25); - m.setManager(manager); - Map nodeFig = new HashMap<>(); - for (Figure f : figures) { - InputNode n = f.getInputNode(); - if (n != null) { - nodeFig.put(n, f); - } - } - // Compute global ranking among figures given by in-block order. If - // needed, this could be cached as long as it is computed for all the - // figures in the model, not just the visible ones. - Map figureRank = new HashMap<>(figures.size()); - int r = 0; - for (InputBlock b : diagram.getInputBlocks()) { - for (InputNode n : b.getNodes()) { - Figure f = nodeFig.get(n); - if (f != null) { - figureRank.put(f, r); - r++; - } - } - } - // Add visible connections for CFG edges. - for (BlockConnection c : diagram.getBlockConnections()) { - if (isVisible(c)) { - edges.add(c); - } - } - m.setSubManager(new LinearLayoutManager(figureRank)); - Set visibleBlocks = new HashSet<>(); - for (Block b : diagram.getBlocks()) { - BlockWidget w = getWidget(b.getInputBlock()); - if (w.isVisible()) { - visibleBlocks.add(b); - } - } - m.setClusters(new HashSet<>(visibleBlocks)); - m.doLayout(new LayoutGraph(edges, figures)); + private void doSeaLayout(Set
visibleFigures, Set visibleConnections) { + HierarchicalLayoutManager seaLayoutManager = new HierarchicalLayoutManager(); + seaLayoutManager.setCutEdges(model.getCutEdges()); + seaLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); } + private void doClusteredLayout(Set visibleConnections) { + HierarchicalClusterLayoutManager clusterLayoutManager = new HierarchicalClusterLayoutManager(); + clusterLayoutManager.setCutEdges(model.getCutEdges()); + clusterLayoutManager.doLayout(new LayoutGraph(visibleConnections, new HashSet<>())); + } + private void doCFGLayout(Set
visibleFigures, Set visibleConnections) { + HierarchicalCFGLayoutManager cfgLayoutManager = new HierarchicalCFGLayoutManager(getVisibleBlockConnections(), getVisibleBlocks()); + cfgLayoutManager.setCutEdges(model.getCutEdges()); + cfgLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); + } private boolean shouldAnimate() { int visibleFigureCount = 0; @@ -790,47 +765,37 @@ private boolean shouldAnimate() { private final Point specialNullPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE); - private void processOutputSlot(OutputSlot outputSlot, List connections, int controlPointIndex, Point lastPoint, LineWidget predecessor) { - Map> pointMap = new HashMap<>(connections.size()); - for (Connection connection : connections) { - if (!isVisible(connection)) { - continue; - } - List controlPoints = connection.getControlPoints(); - if (controlPointIndex >= controlPoints.size()) { - continue; - } + private void processOutputSlot(OutputSlot outputSlot, List connections, int controlPointIndex, Point lastPoint, LineWidget predecessor) { + Map> pointMap = new HashMap<>(connections.size()); - Point currentPoint = controlPoints.get(controlPointIndex); - if (currentPoint == null) { // Long connection, has been cut vertically. - currentPoint = specialNullPoint; - } else if (connection.hasSlots()) { - if (controlPointIndex == 0 && !outputSlot.shouldShowName()) { - currentPoint = new Point(currentPoint.x, currentPoint.y - SLOT_OFFSET); - } else if (controlPointIndex == controlPoints.size() - 1 && - !((Slot)connection.getTo()).shouldShowName()) { - currentPoint = new Point(currentPoint.x, currentPoint.y + SLOT_OFFSET); + for (FigureConnection connection : connections) { + if (isVisibleFigureConnection(connection)) { + List controlPoints = connection.getControlPoints(); + if (controlPointIndex < controlPoints.size()) { + Point currentPoint = controlPoints.get(controlPointIndex); + if (currentPoint == null) { // Long connection, has been cut vertically. + currentPoint = specialNullPoint; + } else { + currentPoint = new Point(currentPoint.x, currentPoint.y); + } + if (pointMap.containsKey(currentPoint)) { + pointMap.get(currentPoint).add(connection); + } else { + pointMap.put(currentPoint, new ArrayList<>(Collections.singletonList(connection))); + } } } - - if (pointMap.containsKey(currentPoint)) { - pointMap.get(currentPoint).add(connection); - } else { - List newList = new ArrayList<>(2); - newList.add(connection); - pointMap.put(currentPoint, newList); - } } for (Point currentPoint : pointMap.keySet()) { - List connectionList = pointMap.get(currentPoint); + List connectionList = pointMap.get(currentPoint); boolean isBold = false; boolean isDashed = true; boolean isVisible = true; - for (Connection c : connectionList) { + for (FigureConnection c : connectionList) { if (c.getStyle() == Connection.ConnectionStyle.BOLD) { isBold = true; } else if (c.getStyle() == Connection.ConnectionStyle.INVISIBLE) { @@ -848,6 +813,14 @@ private void processOutputSlot(OutputSlot outputSlot, List connectio newPredecessor = new LineWidget(this, outputSlot, connectionList, src, dest, predecessor, isBold, isDashed); newPredecessor.setVisible(isVisible); + if (predecessor == null) { + if (outputSlotToLineWidget.containsKey(outputSlot)) { + outputSlotToLineWidget.get(outputSlot).add(newPredecessor); + } else { + outputSlotToLineWidget.put(outputSlot, new HashSet<>(Collections.singleton(newPredecessor))); + } + } + connectionLayer.addChild(newPredecessor); addObject(new ConnectionSet(connectionList), newPredecessor); newPredecessor.getActions().addAction(hoverAction); @@ -855,6 +828,89 @@ private void processOutputSlot(OutputSlot outputSlot, List connectio processOutputSlot(outputSlot, connectionList, controlPointIndex + 1, currentPoint, newPredecessor); } + + if (pointMap.isEmpty()) { + for (FigureConnection connection : connections) { + if (isVisibleFigureConnection(connection)) { + InputSlot inputSlot = connection.getInputSlot(); + if (inputSlotToLineWidget.containsKey(inputSlot)) { + inputSlotToLineWidget.get(inputSlot).add(predecessor); + } else { + inputSlotToLineWidget.put(inputSlot, new HashSet<>(Collections.singleton(predecessor))); + } + } + } + } + } + + private void processFreeForm(OutputSlot outputSlot, List connections) { + for (FigureConnection connection : connections) { + if (isVisibleFigureConnection(connection)) { + boolean isBold = false; + boolean isDashed = true; + boolean isVisible = true; + if (connection.getStyle() == Connection.ConnectionStyle.BOLD) { + isBold = true; + } else if (connection.getStyle() == Connection.ConnectionStyle.INVISIBLE) { + isVisible = false; + } + if (connection.getStyle() != Connection.ConnectionStyle.DASHED) { + isDashed = false; + } + + + List controlPoints = connection.getControlPoints(); + if (controlPoints.size() <= 2) continue; + Point firstPoint = controlPoints.get(0); // First point + Point lastPoint = controlPoints.get(controlPoints.size() - 1); // Last point + List connectionList = new ArrayList<>(Collections.singleton(connection)); + LineWidget line = new LineWidget(this, outputSlot, connectionList, firstPoint, lastPoint, null, isBold, isDashed); + line.setFromControlYOffset(50); + line.setToControlYOffset(-50); + line.setVisible(isVisible); + connectionLayer.addChild(line); + + addObject(new ConnectionSet(connectionList), line); + line.getActions().addAction(hoverAction); + + if (outputSlotToLineWidget.containsKey(outputSlot)) { + outputSlotToLineWidget.get(outputSlot).add(line); + } else { + outputSlotToLineWidget.put(outputSlot, new HashSet<>(Collections.singleton(line))); + } + + InputSlot inputSlot = connection.getInputSlot(); + if (inputSlotToLineWidget.containsKey(inputSlot)) { + inputSlotToLineWidget.get(inputSlot).add(line); + } else { + inputSlotToLineWidget.put(inputSlot, new HashSet<>(Collections.singleton(line))); + } + } + } + } + + + private void processBlockConnection(BlockConnection blockConnection) { + boolean isDashed = blockConnection.getStyle() == Connection.ConnectionStyle.DASHED; + boolean isBold = blockConnection.getStyle() == Connection.ConnectionStyle.BOLD; + boolean isVisible = blockConnection.getStyle() != Connection.ConnectionStyle.INVISIBLE; + Point lastPoint = null; + LineWidget predecessor = null; + for (Point currentPoint : blockConnection.getControlPoints()) { + if (currentPoint == null) { // Long connection, has been cut vertically. + currentPoint = specialNullPoint; + } else if (lastPoint != specialNullPoint && lastPoint != null) { + List connectionList = Collections.singletonList(blockConnection); + Point src = new Point(lastPoint); + Point dest = new Point(currentPoint); + predecessor = new LineWidget(this, null, connectionList, src, dest, predecessor, isBold, isDashed); + predecessor.setVisible(isVisible); + connectionLayer.addChild(predecessor); + addObject(new ConnectionSet(connectionList), predecessor); + predecessor.getActions().addAction(hoverAction); + } + lastPoint = currentPoint; + } } @Override @@ -871,14 +927,10 @@ public void handleDoubleClick(Widget w, WidgetAction.WidgetMouseEvent e) { private class ConnectionSet { - private Set connections; - - public ConnectionSet(Collection connections) { - connections = new HashSet<>(connections); - } + private Collection connections; - public Set getConnectionSet() { - return Collections.unmodifiableSet(connections); + public ConnectionSet(Collection connections) { + this.connections = connections; } } @@ -888,9 +940,9 @@ public Lookup getLookup() { } private void gotoBlock(final Block block) { - BlockWidget bw = getWidget(block.getInputBlock()); + BlockWidget bw = getWidget(block); if (bw != null) { - centerRectangle(bw.getBounds(), true); + centerRectangle(bw.getBounds()); } } @@ -937,28 +989,26 @@ public void centerSelectedFigures() { } } if (overallRect != null) { - centerRectangle(overallRect, true); + centerRectangle(overallRect); } } - private void centerRectangle(Rectangle r, boolean zoomToFit) { + private void centerRectangle(Rectangle r) { Rectangle rect = convertSceneToView(r); Rectangle viewRect = scrollPane.getViewport().getViewRect(); - if (zoomToFit) { - double factor = Math.min(viewRect.getWidth() / rect.getWidth(), viewRect.getHeight() / rect.getHeight()); - double zoomFactor = getZoomFactor(); - double newZoomFactor = zoomFactor * factor; - if (factor < 1.0 || zoomFactor < 1.0) { - newZoomFactor = Math.min(1.0, newZoomFactor); - centredZoom(newZoomFactor, null); - factor = newZoomFactor / zoomFactor; - rect.x *= factor; - rect.y *= factor; - rect.width *= factor; - rect.height *= factor; - } - } + double factor = Math.min(viewRect.getWidth() / rect.getWidth(), viewRect.getHeight() / rect.getHeight()); + double zoomFactor = getZoomFactor(); + double newZoomFactor = zoomFactor * factor; + if (factor < 1.0 || zoomFactor < 1.0) { + newZoomFactor = Math.min(1.0, newZoomFactor); + centredZoom(newZoomFactor, null); + factor = newZoomFactor / zoomFactor; + rect.x *= factor; + rect.y *= factor; + rect.width *= factor; + rect.height *= factor; + } viewRect.x = rect.x + rect.width / 2 - viewRect.width / 2; viewRect.y = rect.y + rect.height / 2 - viewRect.height / 2; // Ensure to be within area @@ -1004,18 +1054,20 @@ public void componentShowing() { } private void rebuildConnectionLayer() { + outputSlotToLineWidget.clear(); + inputSlotToLineWidget.clear(); connectionLayer.removeChildren(); for (Figure figure : getModel().getDiagram().getFigures()) { for (OutputSlot outputSlot : figure.getOutputSlots()) { - List connectionList = new ArrayList<>(outputSlot.getConnections()); + List connectionList = new ArrayList<>(outputSlot.getConnections()); processOutputSlot(outputSlot, connectionList, 0, null, null); } } if (getModel().getShowCFG()) { for (BlockConnection blockConnection : getModel().getDiagram().getBlockConnections()) { - if (isVisible(blockConnection)) { - processOutputSlot(null, Collections.singletonList(blockConnection), 0, null, null); + if (isVisibleBlockConnection(blockConnection)) { + processBlockConnection(blockConnection); } } } @@ -1035,8 +1087,8 @@ private Set getVisibleFigureWidgets() { private Set getVisibleBlockWidgets() { Set visibleBlockWidgets = new HashSet<>(); if (getModel().getShowBlocks() || getModel().getShowCFG()) { - for (InputBlock inputBlock : getModel().getDiagram().getInputBlocks()) { - BlockWidget blockWidget = getWidget(inputBlock); + for (Block block : getModel().getDiagram().getBlocks()) { + BlockWidget blockWidget = getWidget(block); if (blockWidget.isVisible()) { visibleBlockWidgets.add(blockWidget); } @@ -1085,16 +1137,16 @@ private void updateNodeHull() { private void updateVisibleBlockWidgets() { if (getModel().getShowBlocks() || getModel().getShowCFG()) { - Set visibleBlocks = new HashSet<>(); + Set visibleBlocks = new HashSet<>(); for (Figure figure : getModel().getDiagram().getFigures()) { FigureWidget figureWidget = getWidget(figure); if (figureWidget.isVisible()) { - visibleBlocks.add(figure.getBlock().getInputBlock()); + visibleBlocks.add(figure.getBlock()); } } if (getModel().getShowCFG() && getModel().getShowEmptyBlocks()) { // Add remaining blocks. - visibleBlocks.addAll(getModel().getDiagram().getInputBlocks()); + visibleBlocks.addAll(getModel().getDiagram().getBlocks()); } if (getModel().getShowCFG()) { // Blockless figures and artificial blocks are hidden in this view. @@ -1106,19 +1158,18 @@ private void updateVisibleBlockWidgets() { } } - for (InputBlock inputBlock : getModel().getDiagram().getInputBlocks()) { + for (Block block : getModel().getDiagram().getBlocks()) { // A block is visible if it is marked as such, except for // artificial or null blocks in the CFG view. - boolean visibleAfter = visibleBlocks.contains(inputBlock) && - !(getModel().getShowCFG() && (inputBlock.isArtificial() || inputBlock.getNodes().isEmpty())); - - BlockWidget blockWidget = getWidget(inputBlock); + boolean visibleAfter = visibleBlocks.contains(block) && + !(getModel().getShowCFG() && (block.getInputBlock().isArtificial() || block.getInputBlock().getNodes().isEmpty())); + BlockWidget blockWidget = getWidget(block); blockWidget.setVisible(visibleAfter); } } } - private HashSet
getVisibleFigures() { + private Set
getVisibleFigures() { HashSet
visibleFigures = new HashSet<>(); for (Figure figure : getModel().getDiagram().getFigures()) { FigureWidget figureWidget = getWidget(figure); @@ -1129,10 +1180,31 @@ private HashSet
getVisibleFigures() { return visibleFigures; } + private Set getVisibleBlocks() { + Set visibleBlocks = new HashSet<>(); + for (Block b : getModel().getDiagram().getBlocks()) { + BlockWidget w = getWidget(b); + if (w.isVisible()) { + visibleBlocks.add(b); + } + } + return visibleBlocks; + } + + private Set getVisibleBlockConnections() { + Set clusterLinks = new HashSet<>(); + for (BlockConnection c : getModel().getDiagram().getBlockConnections()) { + if (isVisibleBlockConnection(c)) { + clusterLinks.add(c); + } + } + return clusterLinks; + } + private HashSet getVisibleConnections() { HashSet visibleConnections = new HashSet<>(); - for (Connection connection : getModel().getDiagram().getConnections()) { - if (isVisible(connection)) { + for (FigureConnection connection : getModel().getDiagram().getConnections()) { + if (isVisibleFigureConnection(connection)) { visibleConnections.add(connection); } } @@ -1158,7 +1230,7 @@ private void updateBlockWidgetBounds(Set oldVisibleBlockWidgets) { if (getModel().getShowBlocks() || getModel().getShowCFG()) { boolean doAnimation = shouldAnimate(); for (Block block : getModel().getDiagram().getBlocks()) { - BlockWidget blockWidget = getWidget(block.getInputBlock()); + BlockWidget blockWidget = getWidget(block); if (blockWidget != null && blockWidget.isVisible()) { Rectangle bounds = new Rectangle(block.getBounds()); if (doAnimation && oldVisibleBlockWidgets.contains(blockWidget)) { @@ -1171,65 +1243,8 @@ private void updateBlockWidgetBounds(Set oldVisibleBlockWidgets) { } } - private void centerSingleSelectedFigure() { - if (model.getSelectedFigures().size() == 1) { - if (getSceneAnimator().getPreferredLocationAnimator().isRunning()) { - getSceneAnimator().getPreferredLocationAnimator().addAnimatorListener(new AnimatorListener() { - @Override - public void animatorStarted(AnimatorEvent animatorEvent) {} - - @Override - public void animatorReset(AnimatorEvent animatorEvent) {} - - @Override - public void animatorFinished(AnimatorEvent animatorEvent) { - getSceneAnimator().getPreferredLocationAnimator().removeAnimatorListener(this); - } - - @Override - public void animatorPreTick(AnimatorEvent animatorEvent) {} - - @Override - public void animatorPostTick(AnimatorEvent animatorEvent) { - validateAll(); - centerSelectedFigures(); - } - }); - } else { - centerSelectedFigures(); - } - } - } - - private void relayout() { - rebuilding = true; - Set oldVisibleFigureWidgets = getVisibleFigureWidgets(); - Set oldVisibleBlockWidgets = getVisibleBlockWidgets(); - - updateVisibleFigureWidgets(); - updateNodeHull(); - updateVisibleBlockWidgets(); - - HashSet
visibleFigures = getVisibleFigures(); - HashSet visibleConnections = getVisibleConnections(); - if (getModel().getShowStableSea()) { - doStableSeaLayout(visibleFigures, visibleConnections); - } else if (getModel().getShowSea()) { - doSeaLayout(visibleFigures, visibleConnections); - } else if (getModel().getShowBlocks()) { - doClusteredLayout(visibleConnections); - } else if (getModel().getShowCFG()) { - doCFGLayout(visibleFigures, visibleConnections); - } - rebuildConnectionLayer(); - - updateFigureWidgetLocations(oldVisibleFigureWidgets); - updateBlockWidgetBounds(oldVisibleBlockWidgets); - validateAll(); - - centerSingleSelectedFigure(); - rebuilding = false; - } + Map> outputSlotToLineWidget = new HashMap<>(); + Map> inputSlotToLineWidget = new HashMap<>(); public JPopupMenu createPopupMenu() { JPopupMenu menu = new JPopupMenu(); @@ -1295,6 +1310,7 @@ private static class ModelState { public final int firstPos; public final int secondPos; + public ModelState(DiagramViewModel model) { hiddenNodes = new HashSet<>(model.getHiddenNodes()); firstPos = model.getFirstPosition(); @@ -1303,11 +1319,11 @@ public ModelState(DiagramViewModel model) { } private void addUndo() { - ModelState newModelState = new ModelState(model); if (undoRedoEnabled) { + ModelState newModelState = new ModelState(model); DiagramUndoRedo undoRedo = new DiagramUndoRedo(this, getScrollPosition(), modelState, newModelState); getUndoRedoManager().undoableEditHappened(new UndoableEditEvent(this, undoRedo)); + modelState = newModelState; } - modelState = newModelState; } } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java index d69047c8c3e65..56c83517d2f39 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java @@ -196,17 +196,6 @@ public void mouseMoved(MouseEvent e) {} layoutButtons.add(cfgLayoutButton); toolBar.add(cfgLayoutButton); - diagramViewModel.getGraphChangedEvent().addListener(model -> { - // HierarchicalStableLayoutManager is not reliable for difference graphs - boolean isDiffGraph = model.getGraph().isDiffGraph(); - // deactivate HierarchicalStableLayoutManager for difference graphs - stableSeaLayoutButton.setEnabled(!isDiffGraph); - if (stableSeaLayoutButton.isSelected() && isDiffGraph) { - // fallback to HierarchicalLayoutManager for difference graphs - seaLayoutButton.setSelected(true); - } - }); - toolBar.addSeparator(); toolBar.add(new JToggleButton(new PredSuccAction(diagramViewModel.getShowNodeHull()))); toolBar.add(new JToggleButton(new ShowEmptyBlocksAction(cfgLayoutAction, diagramViewModel.getShowEmptyBlocks()))); diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/BlockWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/BlockWidget.java index b7dcf7bc0d80d..af9fa27814132 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/BlockWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/BlockWidget.java @@ -23,6 +23,7 @@ */ package com.sun.hotspot.igv.view.widgets; +import com.sun.hotspot.igv.graph.Block; import com.sun.hotspot.igv.data.InputBlock; import com.sun.hotspot.igv.data.services.InputGraphProvider; import com.sun.hotspot.igv.util.DoubleClickHandler; @@ -44,11 +45,11 @@ public class BlockWidget extends Widget implements DoubleClickHandler { public static final Color BACKGROUND_COLOR = new Color(235, 235, 255); private static final Font TITLE_FONT = new Font("Arial", Font.BOLD, 14); public static final Color TITLE_COLOR = new Color(42, 42, 171); - private final InputBlock blockNode; + private final Block block; - public BlockWidget(Scene scene, InputBlock blockNode) { + public BlockWidget(Scene scene, Block block) { super(scene); - this.blockNode = blockNode; + this.block = block; this.setBackground(BACKGROUND_COLOR); this.setOpaque(true); this.setCheckClipping(true); @@ -71,7 +72,7 @@ protected void paintWidget() { g.setColor(TITLE_COLOR); g.setFont(TITLE_FONT); - String s = "B" + blockNode.getName(); + String s = "B" + getBlockNode().getName(); Rectangle2D r1 = g.getFontMetrics().getStringBounds(s, g); g.drawString(s, r.x + 5, r.y + (int) r1.getHeight()); g.setStroke(old); @@ -83,10 +84,18 @@ private void addToSelection(BlockWidget blockWidget, boolean additiveSelection) if (!additiveSelection) { graphProvider.clearSelectedNodes(); } - graphProvider.addSelectedNodes(blockWidget.blockNode.getNodes(), false); + graphProvider.addSelectedNodes(blockWidget.getBlockNode().getNodes(), false); } } + public void updatePosition() { + setPreferredLocation(block.getPosition()); + } + + public InputBlock getBlockNode() { + return block.getInputBlock(); + } + private int getModifierMask () { return Utilities.isMac() ? MouseEvent.META_DOWN_MASK : MouseEvent.CTRL_DOWN_MASK; } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java index bbdf08dc8b84b..c44e598f53632 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java @@ -26,12 +26,15 @@ import com.sun.hotspot.igv.data.Properties; import com.sun.hotspot.igv.graph.Diagram; import com.sun.hotspot.igv.graph.Figure; +import com.sun.hotspot.igv.graph.Slot; import com.sun.hotspot.igv.util.DoubleClickAction; import com.sun.hotspot.igv.util.DoubleClickHandler; import com.sun.hotspot.igv.util.PropertiesConverter; import com.sun.hotspot.igv.util.PropertiesSheet; import com.sun.hotspot.igv.view.DiagramScene; import java.awt.*; +import java.awt.geom.Path2D; +import java.awt.geom.RoundRectangle2D; import java.util.ArrayList; import java.util.HashSet; import java.util.Set; @@ -39,6 +42,8 @@ import javax.swing.BorderFactory; import javax.swing.JMenu; import javax.swing.JPopupMenu; +import javax.swing.border.Border; +import javax.swing.border.LineBorder; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import org.netbeans.api.visual.action.PopupMenuProvider; @@ -67,8 +72,6 @@ public class FigureWidget extends Widget implements Properties.Provider, PopupMe private ArrayList labelWidgets; private DiagramScene diagramScene; private boolean boundary; - private final Node node; - private Widget dummyTop; private static final Image warningSign = ImageUtilities.loadImage("com/sun/hotspot/igv/view/images/warning.png"); public void setBoundary(boolean b) { @@ -117,34 +120,17 @@ public FigureWidget(final Figure f, DiagramScene scene) { this.setCheckClipping(true); this.diagramScene = scene; - Widget outer = new Widget(scene); - outer.setBackground(f.getColor()); - outer.setLayout(LayoutFactory.createOverlayLayout()); - middleWidget = new Widget(scene); - SerialAlignment textAlign = scene.getModel().getShowCFG() ? - LayoutFactory.SerialAlignment.LEFT_TOP : - LayoutFactory.SerialAlignment.CENTER; - middleWidget.setLayout(LayoutFactory.createVerticalFlowLayout(textAlign, 0)); + middleWidget.setPreferredBounds(new Rectangle(0, 0, f.getWidth(), f.getHeight())); + middleWidget.setLayout(LayoutFactory.createHorizontalFlowLayout(SerialAlignment.CENTER, 0)); middleWidget.setOpaque(true); middleWidget.getActions().addAction(new DoubleClickAction(this)); middleWidget.setCheckClipping(false); - - dummyTop = new Widget(scene); - int extraTopHeight = - getFigure().getDiagram().isCFG() && getFigure().hasNamedInputSlot() ? - Figure.TOP_CFG_HEIGHT : 0; - dummyTop.setMinimumSize(new Dimension(Figure.INSET / 2, 1 + extraTopHeight)); - middleWidget.addChild(dummyTop); - - // This widget includes the node text and possibly a warning sign to the right. - Widget nodeInfoWidget = new Widget(scene); - nodeInfoWidget.setLayout(LayoutFactory.createAbsoluteLayout()); - middleWidget.addChild(nodeInfoWidget); + this.addChild(middleWidget); Widget textWidget = new Widget(scene); - textWidget.setLayout(LayoutFactory.createVerticalFlowLayout(textAlign, 0)); - nodeInfoWidget.addChild(textWidget); + textWidget.setLayout(LayoutFactory.createVerticalFlowLayout(SerialAlignment.CENTER, 0)); + middleWidget.addChild(textWidget); String[] strings = figure.getLines(); labelWidgets = new ArrayList<>(strings.length); @@ -157,32 +143,32 @@ public FigureWidget(final Figure f, DiagramScene scene) { lw.setFont(Diagram.FONT); lw.setAlignment(LabelWidget.Alignment.CENTER); lw.setVerticalAlignment(LabelWidget.VerticalAlignment.CENTER); - lw.setBorder(BorderFactory.createEmptyBorder()); lw.setCheckClipping(false); } formatExtraLabel(false); refreshColor(); + for (int i=1; i < labelWidgets.size(); i++) { + labelWidgets.get(i).setFont(Diagram.FONT.deriveFont(Font.ITALIC)); + labelWidgets.get(i).setForeground(Color.DARK_GRAY); + } + + + int textHeight = f.getHeight() - 2 * Figure.PADDING - f.getSlotsHeight(); if (getFigure().getWarning() != null) { ImageWidget warningWidget = new ImageWidget(scene, warningSign); - Point warningLocation = new Point(getFigure().getWidth() - Figure.WARNING_WIDTH - Figure.INSET / 2, 0); - warningWidget.setPreferredLocation(warningLocation); warningWidget.setToolTipText(getFigure().getWarning()); - nodeInfoWidget.addChild(warningWidget); + middleWidget.addChild(warningWidget); + int textWidth = f.getWidth() - 4 * Figure.BORDER; + textWidth -= Figure.WARNING_WIDTH + Figure.PADDING; + textWidget.setPreferredBounds(new Rectangle(0, 0, textWidth, textHeight)); + } else { + int textWidth = f.getWidth() - 4 * Figure.BORDER; + textWidget.setPreferredBounds(new Rectangle(0, 0, textWidth, textHeight)); } - Widget dummyBottom = new Widget(scene); - int extraBottomHeight = - getFigure().getDiagram().isCFG() && getFigure().hasNamedOutputSlot() ? - Figure.BOTTOM_CFG_HEIGHT : 0; - dummyBottom.setMinimumSize(new Dimension(Figure.INSET / 2, 1 + extraBottomHeight)); - middleWidget.addChild(dummyBottom); - - middleWidget.setPreferredBounds(new Rectangle(0, Figure.getVerticalOffset(), f.getWidth(), f.getHeight())); - this.addChild(middleWidget); - // Initialize node for property sheet - node = new AbstractNode(Children.LEAF) { + Node node = new AbstractNode(Children.LEAF) { @Override protected Sheet createSheet() { @@ -196,6 +182,42 @@ protected Sheet createSheet() { this.setToolTipText(PropertiesConverter.convertToHTML(f.getProperties())); } + public void updatePosition() { + setPreferredLocation(figure.getPosition()); + } + + public int getFigureHeight() { + return middleWidget.getPreferredBounds().height; + } + + public static class RoundedBorder extends LineBorder { + + final float RADIUS = 3f; + + public RoundedBorder(Color color, int thickness) { + super(color, thickness); + } + + @Override + public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { + if ((this.thickness > 0) && (g instanceof Graphics2D)) { + Graphics2D g2d = (Graphics2D) g; + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + Color oldColor = g2d.getColor(); + g2d.setColor(this.lineColor); + int offs = this.thickness; + int size = offs + offs; + Shape outer = new RoundRectangle2D.Float(x, y, width, height, RADIUS, RADIUS); + Shape inner = new RoundRectangle2D.Float(x + offs, y + offs, width - size, height - size, RADIUS, RADIUS); + Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD); + path.append(outer, false); + path.append(inner, false); + g2d.fill(path); + g2d.setColor(oldColor); + } + } + } + public void refreshColor() { middleWidget.setBackground(figure.getColor()); for (LabelWidget lw : labelWidgets) { @@ -208,19 +230,22 @@ protected void notifyStateChanged(ObjectState previousState, ObjectState state) super.notifyStateChanged(previousState, state); Font font = Diagram.FONT; - int thickness = 1; + Color borderColor = Color.BLACK; + Color innerBorderColor = getFigure().getColor(); if (state.isSelected()) { font = Diagram.BOLD_FONT; - thickness = 2; + innerBorderColor = Color.BLACK; } - Color borderColor = Color.BLACK; - Color innerBorderColor = getFigure().getColor(); if (state.isHighlighted()) { innerBorderColor = borderColor = Color.BLUE; } - middleWidget.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(borderColor, thickness), BorderFactory.createLineBorder(innerBorderColor, 1))); + Border innerBorder = new RoundedBorder(borderColor, Figure.BORDER); + Border outerBorder = new RoundedBorder(innerBorderColor, Figure.BORDER); + Border roundedBorder = BorderFactory.createCompoundBorder(innerBorder, outerBorder); + middleWidget.setBorder(roundedBorder); + for (LabelWidget labelWidget : labelWidgets) { labelWidget.setFont(font); } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/InputSlotWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/InputSlotWidget.java index 42bc7bb099fee..86ae608d371d3 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/InputSlotWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/InputSlotWidget.java @@ -33,7 +33,6 @@ import java.awt.Font; import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; -import java.util.List; import org.netbeans.api.visual.widget.Widget; /** @@ -45,8 +44,8 @@ public class InputSlotWidget extends SlotWidget { private InputSlot inputSlot; private DiagramScene scene; - public InputSlotWidget(InputSlot slot, DiagramScene scene, Widget parent, FigureWidget fw) { - super(slot, scene, parent, fw); + public InputSlotWidget(InputSlot slot, DiagramScene scene, FigureWidget fw) { + super(slot, scene, fw); inputSlot = slot; this.scene = scene; } @@ -55,17 +54,10 @@ public InputSlot getInputSlot() { return inputSlot; } - @Override - protected int calculateSlotWidth() { - List slots = getSlot().getFigure().getInputSlots(); - assert slots.contains(getSlot()); - return calculateWidth(slots.size()); - } - @Override protected int yOffset() { return getFigureWidget().getFigure().getDiagram().isCFG() ? - calculateClientArea().height - 1 : Figure.SLOT_START; + calculateClientArea().height / 2 : 0; } @Override diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java index 61b71453915b9..ff7e955137aa8 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java @@ -27,11 +27,10 @@ import com.sun.hotspot.igv.graph.Connection; import com.sun.hotspot.igv.graph.Figure; import com.sun.hotspot.igv.graph.OutputSlot; -import com.sun.hotspot.igv.layout.Vertex; import com.sun.hotspot.igv.util.StringUtils; import com.sun.hotspot.igv.view.DiagramScene; -import com.sun.hotspot.igv.view.actions.CustomSelectAction; import java.awt.*; +import java.awt.geom.CubicCurve2D; import java.awt.geom.Line2D; import java.util.ArrayList; import java.util.HashSet; @@ -42,7 +41,6 @@ import javax.swing.event.PopupMenuListener; import org.netbeans.api.visual.action.ActionFactory; import org.netbeans.api.visual.action.PopupMenuProvider; -import org.netbeans.api.visual.action.SelectProvider; import org.netbeans.api.visual.model.ObjectState; import org.netbeans.api.visual.widget.Widget; @@ -61,10 +59,10 @@ public class LineWidget extends Widget implements PopupMenuProvider { private static final double ZOOM_FACTOR = 0.1; private final OutputSlot outputSlot; private final DiagramScene scene; - private final List connections; - private final Point from; - private final Point to; - private final Rectangle clientArea; + private final List connections; + private Point from; + private Point to; + private Rectangle clientArea; private final LineWidget predecessor; private final List successors; private boolean highlighted; @@ -72,7 +70,7 @@ public class LineWidget extends Widget implements PopupMenuProvider { private final boolean isBold; private final boolean isDashed; - public LineWidget(DiagramScene scene, OutputSlot s, List connections, Point from, Point to, LineWidget predecessor, boolean isBold, boolean isDashed) { + public LineWidget(DiagramScene scene, OutputSlot s, List connections, Point from, Point to, LineWidget predecessor, boolean isBold, boolean isDashed) { super(scene); this.scene = scene; this.outputSlot = s; @@ -88,10 +86,52 @@ public LineWidget(DiagramScene scene, OutputSlot s, List connections this.isBold = isBold; this.isDashed = isDashed; + computeClientArea(); + + Color color = Color.BLACK; + if (!connections.isEmpty()) { + color = connections.get(0).getColor(); + } + setToolTipText("" + generateToolTipText(this.connections) + ""); + + setCheckClipping(false); + + getActions().addAction(ActionFactory.createPopupMenuAction(this)); + setBackground(color); + } + + + public Point getClientAreaLocation() { + return clientArea.getLocation(); + } + + private void computeClientArea() { int minX = from.x; int minY = from.y; int maxX = to.x; int maxY = to.y; + + if (fromControlYOffset != 0 && toControlYOffset != 0) { + // Adjust the bounding box to accommodate control points for curves + if (from.y < to.y) { // non-reversed edges + minY = Math.min(minY, from.y + fromControlYOffset); + maxY = Math.max(maxY, to.y + toControlYOffset); + } else { // reversed edges + if (from.x - to.x > 0) { + minX = Math.min(minX, from.x - 150); + maxX = Math.max(maxX, to.x + 150); + minY = Math.min(minY, from.y + fromControlYOffset); + maxY = Math.max(maxY, to.y + toControlYOffset); + } else { + minX = Math.min(minX, from.x + 150); + maxX = Math.max(maxX, to.x - 150); + minY = Math.min(minY, from.y + fromControlYOffset); + maxY = Math.max(maxY, to.y + toControlYOffset); + } + } + } + + // Ensure min and max values are correct if (minX > maxX) { int tmp = minX; minX = maxX; @@ -104,53 +144,42 @@ public LineWidget(DiagramScene scene, OutputSlot s, List connections maxY = tmp; } + // Set client area to include the curve and add a BORDER for extra space clientArea = new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1); clientArea.grow(BORDER, BORDER); + } - Color color = Color.BLACK; - if (connections.size() > 0) { - color = connections.get(0).getColor(); + private String generateToolTipText(List conn) { + StringBuilder sb = new StringBuilder(); + for (Connection c : conn) { + sb.append(StringUtils.escapeHTML(c.getToolTipText())); + sb.append("
"); } - setToolTipText("" + generateToolTipText(this.connections) + ""); + return sb.toString(); + } - setCheckClipping(true); - getActions().addAction(ActionFactory.createPopupMenuAction(this)); - setBackground(color); - - getActions().addAction(new CustomSelectAction(new SelectProvider() { + public void setFrom(Point from) { + this.from = from; + computeClientArea(); + } - @Override - public boolean isAimingAllowed(Widget widget, Point localLocation, boolean invertSelection) { - return true; - } + public void setTo(Point to) { + this.to= to; + computeClientArea(); + } - @Override - public boolean isSelectionAllowed(Widget widget, Point localLocation, boolean invertSelection) { - return true; - } + private int fromControlYOffset; + private int toControlYOffset; - @Override - public void select(Widget widget, Point localLocation, boolean invertSelection) { - Set vertexSet = new HashSet<>(); - for (Connection connection : connections) { - if (connection.hasSlots()) { - vertexSet.add(connection.getTo().getVertex()); - vertexSet.add(connection.getFrom().getVertex()); - } - } - scene.userSelectionSuggested(vertexSet, invertSelection); - } - })); + public void setFromControlYOffset(int fromControlYOffset) { + this.fromControlYOffset = fromControlYOffset; + computeClientArea(); } - private String generateToolTipText(List conn) { - StringBuilder sb = new StringBuilder(); - for (Connection c : conn) { - sb.append(StringUtils.escapeHTML(c.getToolTipText())); - sb.append("
"); - } - return sb.toString(); + public void setToControlYOffset(int toControlYOffset) { + this.toControlYOffset = toControlYOffset; + computeClientArea(); } public Point getFrom() { @@ -176,7 +205,7 @@ protected void paintWidget() { return; } - Graphics2D g = getScene().getGraphics(); + Graphics2D g = this.getGraphics(); g.setPaint(this.getBackground()); float width = 1.0f; @@ -195,10 +224,44 @@ protected void paintWidget() { BasicStroke.JOIN_MITER, 10, dashPattern, 0)); } else { - g.setStroke(new BasicStroke(width)); + g.setStroke(new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER)); } - g.drawLine(from.x, from.y, to.x, to.y); + // Define S-shaped curve with control points + if (fromControlYOffset != 0 && toControlYOffset != 0) { + if (from.y < to.y) { // non-reversed edges + if (Math.abs(from.x - to.x) > 10) { + CubicCurve2D.Float sShape = new CubicCurve2D.Float(); + sShape.setCurve(from.x, from.y, + from.x, from.y + fromControlYOffset, + to.x, to.y + toControlYOffset, + to.x, to.y); + g.draw(sShape); + } else { + g.drawLine(from.x, from.y, to.x, to.y); + } + } else { // reverse edges + if (from.x - to.x > 0) { + CubicCurve2D.Float sShape = new CubicCurve2D.Float(); + sShape.setCurve(from.x, from.y, + from.x - 150, from.y + fromControlYOffset, + to.x + 150, to.y + toControlYOffset, + to.x, to.y); + g.draw(sShape); + } else { + // add x offset + CubicCurve2D.Float sShape = new CubicCurve2D.Float(); + sShape.setCurve(from.x, from.y, + from.x + 150, from.y + fromControlYOffset, + to.x - 150, to.y + toControlYOffset, + to.x, to.y); + g.draw(sShape); + } + } + } else { + // Fallback to straight line if control points are not set + g.drawLine(from.x, from.y, to.x, to.y); + } boolean sameFrom = false; boolean sameTo = successors.isEmpty(); @@ -234,6 +297,7 @@ protected void paintWidget() { 3); } g.setStroke(oldStroke); + super.paintWidget(); } private void setPopupVisible(boolean b) { @@ -257,6 +321,14 @@ protected void notifyStateChanged(ObjectState previousState, ObjectState state) } } + public LineWidget getPredecessor() { + return predecessor; + } + + public List getSuccessors() { + return successors; + } + private void highlightPredecessors(boolean enable) { LineWidget predecessorLineWidget = predecessor; while (predecessorLineWidget != null) { @@ -314,6 +386,13 @@ private void popupVisibleSuccessors(boolean b) { } } + public Figure getFromFigure() { + if (outputSlot != null) { + return outputSlot.getFigure(); + } + return null; + } + @Override public JPopupMenu getPopupMenu(Widget widget, Point localLocation) { JPopupMenu menu = new JPopupMenu(); @@ -350,4 +429,5 @@ public void popupMenuCanceled(PopupMenuEvent e) { return menu; } + } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/OutputSlotWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/OutputSlotWidget.java index f1a03715d22e3..d74986ad7e66f 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/OutputSlotWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/OutputSlotWidget.java @@ -23,11 +23,8 @@ */ package com.sun.hotspot.igv.view.widgets; -import com.sun.hotspot.igv.graph.Figure; import com.sun.hotspot.igv.graph.OutputSlot; import com.sun.hotspot.igv.view.DiagramScene; -import java.util.List; -import org.netbeans.api.visual.widget.Widget; /** * @@ -37,8 +34,8 @@ public class OutputSlotWidget extends SlotWidget { private OutputSlot outputSlot; - public OutputSlotWidget(OutputSlot slot, DiagramScene scene, Widget parent, FigureWidget fw) { - super(slot, scene, parent, fw); + public OutputSlotWidget(OutputSlot slot, DiagramScene scene, FigureWidget fw) { + super(slot, scene, fw); outputSlot = slot; } @@ -46,18 +43,10 @@ public OutputSlot getOutputSlot() { return outputSlot; } - @Override - protected int calculateSlotWidth() { - List slots = getSlot().getFigure().getOutputSlots(); - assert slots.contains(getSlot()); - return calculateWidth(slots.size()); - - } - @Override protected int yOffset() { int overlap = getFigureWidget().getFigure().getDiagram().isCFG() ? - calculateClientArea().height : Figure.SLOT_START; - return getSlot().getFigure().getHeight() - overlap; + calculateClientArea().height / 2 : 0; + return getFigureWidget().getFigureHeight() - overlap; } } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/SlotWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/SlotWidget.java index f6f7fa6b80bf9..d16d4e9cb1ecf 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/SlotWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/SlotWidget.java @@ -25,7 +25,6 @@ import com.sun.hotspot.igv.graph.Diagram; import com.sun.hotspot.igv.graph.Figure; -import com.sun.hotspot.igv.graph.OutputSlot; import com.sun.hotspot.igv.graph.Slot; import com.sun.hotspot.igv.util.DoubleClickHandler; import com.sun.hotspot.igv.view.DiagramScene; @@ -50,7 +49,7 @@ public abstract class SlotWidget extends Widget implements DoubleClickHandler { protected static double ZOOM_FACTOR = 0.6; private DiagramScene diagramScene; - public SlotWidget(Slot slot, DiagramScene scene, Widget parent, FigureWidget fw) { + public SlotWidget(Slot slot, DiagramScene scene, FigureWidget fw) { super(scene); this.diagramScene = scene; this.slot = slot; @@ -60,12 +59,14 @@ public SlotWidget(Slot slot, DiagramScene scene, Widget parent, FigureWidget fw) } // No clipping, to let input slots draw gap markers outside their bounds. this.setCheckClipping(false); - parent.addChild(this); - - Point p = slot.getRelativePosition(); - p.x -= this.calculateClientArea().width / 2; - p.y += yOffset(); - this.setPreferredLocation(p); + fw.addChild(this); + if (slot.shouldShowName()) { + Point p = slot.getRelativePosition(); + p.x -= slot.getWidth() / 2; + p.y -= slot.getHeight() / 2; + p.y += yOffset(); // TODO use fw + this.setPreferredLocation(p); + } } @Override @@ -140,30 +141,18 @@ protected void paintWidget() { g.setColor(Color.BLACK); } int r = 2; - if (slot instanceof OutputSlot) { - g.fillOval(w / 2 - r, Figure.SLOT_WIDTH - Figure.SLOT_START - r, 2 * r, 2 * r); - } else { - g.fillOval(w / 2 - r, Figure.SLOT_START - r, 2 * r, 2 * r); - } - } else { - // Do not paint a slot with connections. + g.fillOval(w / 2 - r, h / 2 - r, 2 * r, 2 * r); } } } @Override protected Rectangle calculateClientArea() { - return new Rectangle(0, 0, slot.getWidth(), Figure.SLOT_WIDTH); + return new Rectangle(0, 0, slot.getWidth(), slot.getHeight()); } - protected abstract int calculateSlotWidth(); - protected abstract int yOffset(); - protected int calculateWidth(int count) { - return getFigureWidget().getFigure().getWidth() / count; - } - @Override public void handleDoubleClick(Widget w, WidgetAction.WidgetMouseEvent e) { Set hiddenNodes = new HashSet<>(diagramScene.getModel().getHiddenNodes()); From 2f4e3fd57fbd393879a0ccee3585c3dff3cbcfbb Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Thu, 14 Nov 2024 22:04:13 +0100 Subject: [PATCH 02/30] AllSoFar --- .../com/sun/hotspot/igv/data/InputNode.java | 24 + .../java/com/sun/hotspot/igv/graph/Block.java | 3 +- .../hotspot/igv/graph/BlockConnection.java | 32 + .../com/sun/hotspot/igv/graph/Figure.java | 38 +- .../hotspot/igv/graph/FigureConnection.java | 20 +- .../com/sun/hotspot/igv/graph/InputSlot.java | 14 + .../com/sun/hotspot/igv/graph/OutputSlot.java | 13 + .../java/com/sun/hotspot/igv/graph/Slot.java | 23 +- .../igv/hierarchicallayout/ClusterEdge.java | 9 +- .../ClusterIngoingConnection.java | 11 +- .../ClusterInputSlotNode.java | 22 +- .../igv/hierarchicallayout/ClusterNode.java | 21 +- .../ClusterOutgoingConnection.java | 11 +- .../ClusterOutputSlotNode.java | 47 +- .../FreeInteractiveLayoutManager.java | 324 +++++ .../HierarchicalCFGLayoutManager.java | 36 +- .../HierarchicalClusterLayoutManager.java | 82 +- .../HierarchicalLayoutManager.java | 573 +++++---- .../HierarchicalStableLayoutManager.java | 885 ++++++-------- .../InterClusterConnection.java | 9 +- .../igv/hierarchicallayout/LayoutEdge.java | 140 ++- .../igv/hierarchicallayout/LayoutGraph.java | 1083 +++++++++++++---- .../igv/hierarchicallayout/LayoutLayer.java | 183 ++- .../igv/hierarchicallayout/LayoutManager.java | 11 +- .../igv/hierarchicallayout/LayoutMover.java | 55 + .../igv/hierarchicallayout/LayoutNode.java | 445 ++++--- .../sun/hotspot/igv/settings/Settings.java | 1 + .../sun/hotspot/igv/view/DiagramScene.java | 309 ++++- .../hotspot/igv/view/DiagramViewModel.java | 13 + .../hotspot/igv/view/EditorTopComponent.java | 6 + .../hotspot/igv/view/actions/ColorAction.java | 27 +- .../view/actions/EnableFreeLayoutAction.java | 49 + .../igv/view/widgets/FigureWidget.java | 1 + .../hotspot/igv/view/widgets/LineWidget.java | 33 +- .../sun/hotspot/igv/view/images/dynamic.png | Bin 0 -> 2218 bytes 35 files changed, 3169 insertions(+), 1384 deletions(-) create mode 100644 src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java create mode 100644 src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java create mode 100644 src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java create mode 100644 src/utils/IdealGraphVisualizer/View/src/main/resources/com/sun/hotspot/igv/view/images/dynamic.png diff --git a/src/utils/IdealGraphVisualizer/Data/src/main/java/com/sun/hotspot/igv/data/InputNode.java b/src/utils/IdealGraphVisualizer/Data/src/main/java/com/sun/hotspot/igv/data/InputNode.java index 62b7aa4c03455..48a000bf7b9f5 100644 --- a/src/utils/IdealGraphVisualizer/Data/src/main/java/com/sun/hotspot/igv/data/InputNode.java +++ b/src/utils/IdealGraphVisualizer/Data/src/main/java/com/sun/hotspot/igv/data/InputNode.java @@ -23,6 +23,7 @@ */ package com.sun.hotspot.igv.data; +import java.awt.Color; import java.util.Objects; /** @@ -67,4 +68,27 @@ public boolean equals(Object obj) { public String toString() { return "Node " + id + " " + getProperties().toString(); } + + public void setCustomColor(Color color) { + if (color != null) { + String hexColor = String.format("#%08X", color.getRGB()); + getProperties().setProperty("color", hexColor); + } else { + getProperties().setProperty("color", null); + } + } + + public Color getCustomColor() { + String hexColor = getProperties().get("color"); + if (hexColor != null) { + try { + String hex = hexColor.startsWith("#") ? hexColor.substring(1) : hexColor; + int argb = (int) Long.parseLong(hex, 16); + return new Color(argb, true); + } catch (Exception ignored) { + return null; + } + } + return null; + } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java index 011fd8239b157..bca39d6b338c4 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java @@ -100,7 +100,8 @@ public String toString() { @Override public boolean equals(Object obj) { - if (obj == null) return false; + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; Block other = (Block) obj; return inputBlock.equals(other.inputBlock); } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java index 4648734ee80db..3c5dfb0bb9376 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java @@ -27,6 +27,7 @@ import java.awt.Color; import java.awt.Point; import java.util.List; +import java.util.Objects; public class BlockConnection implements Connection { @@ -99,4 +100,35 @@ public void setControlPoints(List list) { public boolean hasSlots() { return false; } + + /** + * Determines equality based on sourceBlock, destinationBlock, and label. + * + * @param o the object to compare with + * @return true if equal, false otherwise + */ + @Override + public boolean equals(Object o) { + // Reference equality check + if (this == o) return true; + + // Type check + if (!(o instanceof BlockConnection)) return false; + + // Cast and field comparisons + BlockConnection that = (BlockConnection) o; + return Objects.equals(this.sourceBlock, that.sourceBlock) && + Objects.equals(this.destinationBlock, that.destinationBlock) && + Objects.equals(this.label, that.label); + } + + /** + * Generates hash code based on sourceBlock, destinationBlock, and label. + * + * @return the hash code + */ + @Override + public int hashCode() { + return Objects.hash(sourceBlock, destinationBlock, label); + } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java index 4c7d01a4cebb8..bdfa466dced88 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java @@ -148,7 +148,12 @@ public void setColor(Color color) { } public Color getColor() { - return color; + Color customColor = inputNode.getCustomColor(); + if (customColor != null) { + return customColor; + } else { + return color; + } } public void setWarning(String warning) { @@ -374,19 +379,6 @@ public Dimension getSize() { return new Dimension(width, height); } - @Override - public boolean equals(Object o) { - if (!(o instanceof Figure)) { - return false; - } - return getInputNode().equals(((Figure) o).getInputNode()); - } - - @Override - public int hashCode() { - return getInputNode().hashCode(); - } - @Override public String toString() { return idString; @@ -409,4 +401,22 @@ public boolean isRoot() { public int compareTo(Vertex f) { return toString().compareTo(f.toString()); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Figure other)) { + return false; + } + return Objects.equals(this.getInputNode(), other.getInputNode()); + } + + @Override + public int hashCode() { + return Objects.hash(getInputNode()); + } + + public void setCustomColor(Color color) { + inputNode.setCustomColor(color); + } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java index e26c618f49e3a..526881f4318f4 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java @@ -24,11 +24,13 @@ package com.sun.hotspot.igv.graph; import com.sun.hotspot.igv.layout.Cluster; +import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Port; import java.awt.Color; import java.awt.Point; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * @@ -161,15 +163,19 @@ public boolean hasSlots() { @Override public boolean equals(Object o) { - if (!(o instanceof FigureConnection)) { - return false; - } + if (this == o) return true; // Reference equality check + if (!(o instanceof FigureConnection)) return false; // Type check + + FigureConnection that = (FigureConnection) o; - return getInputSlot().getFigure().equals(((FigureConnection)o).getInputSlot().getFigure()) - && getOutputSlot().getFigure().equals(((FigureConnection)o).getOutputSlot().getFigure()) - && getInputSlot().getPosition() == ((FigureConnection)o).getInputSlot().getPosition() - && getOutputSlot().getPosition() == ((FigureConnection) o).getOutputSlot().getPosition(); + // Compare source and target ports using their own equals() methods + return Objects.equals(this.outputSlot, that.outputSlot) && + Objects.equals(this.inputSlot, that.inputSlot); } + @Override + public int hashCode() { + return Objects.hash(outputSlot, inputSlot); + } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java index 2ba127cc8f707..e0f5ae73b6d42 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java @@ -25,6 +25,7 @@ import java.awt.Point; import java.util.List; +import java.util.Objects; /** * @@ -84,4 +85,17 @@ public String getToolTipText() { public String toString() { return "InputSlot[figure=" + this.getFigure().toString() + ", position=" + getPosition() + "]"; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof InputSlot that)) return false; + if (!super.equals(o)) return false; + return this.originalIndex == that.originalIndex; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), originalIndex); + } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java index a2555ce36584b..14d1f1d52cd54 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java @@ -24,6 +24,7 @@ package com.sun.hotspot.igv.graph; import java.awt.Point; +import java.util.Objects; /** * @@ -61,4 +62,16 @@ public Point getRelativePosition() { public String toString() { return "OutputSlot[figure=" + this.getFigure().toString() + ", position=" + getPosition() + "]"; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode()); + } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java index 1ddc145bb89f9..3aa739d0cb979 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java @@ -34,10 +34,7 @@ import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.image.BufferedImage; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.util.*; /** * @@ -178,5 +175,23 @@ public Vertex getVertex() { public abstract int getPosition(); public abstract void setPosition(int position); + + @Override + public boolean equals(Object o) { + // Reference equality check + if (this == o) return true; + + // Type check + if (!(o instanceof Slot other)) return false; + + // Compare immutable and identity-defining fields + return this.wantedIndex == other.wantedIndex && + Objects.equals(this.figure, other.figure); + } + + @Override + public int hashCode() { + return Objects.hash(figure, wantedIndex); + } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterEdge.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterEdge.java index 166d14b419abd..97ca9b99ad917 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterEdge.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterEdge.java @@ -30,7 +30,6 @@ import java.util.List; /** - * * @author Thomas Wuerthinger */ public class ClusterEdge implements Link { @@ -62,14 +61,14 @@ public Cluster getToCluster() { return to.getCluster(); } - public void setControlPoints(List p) { - this.points = p; - } - public List getControlPoints() { return points; } + public void setControlPoints(List p) { + this.points = p; + } + @Override public String toString() { return from + "->" + to; diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterIngoingConnection.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterIngoingConnection.java index 70c34e71ca9c7..29a5b7dfa272c 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterIngoingConnection.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterIngoingConnection.java @@ -31,14 +31,13 @@ import java.util.List; /** - * * @author Thomas Wuerthinger */ public class ClusterIngoingConnection implements Link { - private List controlPoints; private final Port inputSlot; private final Port outputSlot; + private List controlPoints; public ClusterIngoingConnection(ClusterInputSlotNode inputSlotNode, Link c) { this.controlPoints = new ArrayList<>(); @@ -63,11 +62,11 @@ public Cluster getToCluster() { return null; } - public void setControlPoints(List p) { - this.controlPoints = p; - } - public List getControlPoints() { return controlPoints; } + + public void setControlPoints(List p) { + this.controlPoints = p; + } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterInputSlotNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterInputSlotNode.java index da6842c791ce8..6c27d54409943 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterInputSlotNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterInputSlotNode.java @@ -30,22 +30,15 @@ import java.awt.Point; /** - * * @author Thomas Wuerthinger */ public class ClusterInputSlotNode implements Vertex { - private Point position; private final Port inputSlot; private final Port outputSlot; private final ClusterNode blockNode; - private final String id; - - @Override - public String toString() { - return id; - } + private Point position; public ClusterInputSlotNode(ClusterNode n, String id) { this.blockNode = n; @@ -91,6 +84,11 @@ public String toString() { }; } + @Override + public String toString() { + return id; + } + public Port getInputSlot() { return inputSlot; } @@ -103,14 +101,14 @@ public Dimension getSize() { return new Dimension(0, 0); } - public void setPosition(Point p) { - this.position = p; - } - public Point getPosition() { return position; } + public void setPosition(Point p) { + this.position = p; + } + public Cluster getCluster() { return null; } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterNode.java index 382fec9b651fa..068278f5b41bd 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterNode.java @@ -33,22 +33,21 @@ import java.util.*; /** - * * @author Thomas Wuerthinger */ public class ClusterNode implements Vertex { public static final int PADDING = 8; - private Cluster cluster; - private Port inputSlot; private final Set subNodes; - private Dimension size; - private Point position; private final Set subEdges; - private boolean root; private final String name; private final int headerVerticalSpace; private final Dimension emptySize; + private Cluster cluster; + private Port inputSlot; + private Dimension size; + private Point position; + private boolean root; public ClusterNode(Cluster cluster, String name, int headerVerticalSpace, Dimension emptySize) { this.subNodes = new HashSet<>(); @@ -142,7 +141,7 @@ private void calculateSize() { // Normalize coordinates for (Vertex n : subNodes) { n.setPosition(new Point(n.getPosition().x - minX, - n.getPosition().y - minY + headerVerticalSpace)); + n.getPosition().y - minY + headerVerticalSpace)); } for (Link l : subEdges) { @@ -206,14 +205,14 @@ public void setCluster(Cluster c) { cluster = c; } - public void setRoot(boolean b) { - root = b; - } - public boolean isRoot() { return root; } + public void setRoot(boolean b) { + root = b; + } + public int compareTo(Vertex o) { return toString().compareTo(o.toString()); } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutgoingConnection.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutgoingConnection.java index a5267020a0e72..360532b7dca00 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutgoingConnection.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutgoingConnection.java @@ -31,14 +31,13 @@ import java.util.List; /** - * * @author Thomas Wuerthinger */ public class ClusterOutgoingConnection implements Link { - private List intermediatePoints; private final Port inputSlot; private final Port outputSlot; + private List intermediatePoints; public ClusterOutgoingConnection(ClusterOutputSlotNode outputSlotNode, Link c) { this.intermediatePoints = new ArrayList<>(); @@ -62,11 +61,11 @@ public Cluster getToCluster() { return null; } - public void setControlPoints(List p) { - this.intermediatePoints = p; - } - public List getControlPoints() { return intermediatePoints; } + + public void setControlPoints(List p) { + this.intermediatePoints = p; + } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutputSlotNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutputSlotNode.java index b1eb6b0578b2a..edd913f345b16 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutputSlotNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutputSlotNode.java @@ -30,31 +30,17 @@ import java.awt.Point; /** - * * @author Thomas Wuerthinger */ public class ClusterOutputSlotNode implements Vertex { - private Point position; private final Port inputSlot; private final Port outputSlot; private final ClusterNode blockNode; + private final String id; + private Point position; private Cluster cluster; private ClusterOutgoingConnection conn; - private final String id; - - public void setOutgoingConnection(ClusterOutgoingConnection c) { - this.conn = c; - } - - public ClusterOutgoingConnection getOutgoingConnection() { - return conn; - } - - @Override - public String toString() { - return id; - } public ClusterOutputSlotNode(ClusterNode n, String id) { this.blockNode = n; @@ -100,18 +86,31 @@ public String toString() { }; } - public Dimension getSize() { - return new Dimension(0, 0); + public ClusterOutgoingConnection getOutgoingConnection() { + return conn; } - public void setPosition(Point p) { - this.position = p; + public void setOutgoingConnection(ClusterOutgoingConnection c) { + this.conn = c; + } + + @Override + public String toString() { + return id; + } + + public Dimension getSize() { + return new Dimension(0, 0); } public Point getPosition() { return position; } + public void setPosition(Point p) { + this.position = p; + } + public Port getInputSlot() { return inputSlot; } @@ -120,14 +119,14 @@ public Port getOutputSlot() { return outputSlot; } - public void setCluster(Cluster c) { - cluster = c; - } - public Cluster getCluster() { return cluster; } + public void setCluster(Cluster c) { + cluster = c; + } + public boolean isRoot() { return false; } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java new file mode 100644 index 0000000000000..d221d78f8620c --- /dev/null +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 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 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.sun.hotspot.igv.hierarchicallayout; + +import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.LAYOUT_NODE_DEGREE_COMPARATOR; +import com.sun.hotspot.igv.layout.Link; +import com.sun.hotspot.igv.layout.Vertex; +import java.awt.Point; +import java.util.*; + +public class FreeInteractiveLayoutManager extends LayoutManager implements LayoutMover { + + private boolean cutEdges = false; + + private static final int LINE_OFFSET = 10; + + private Map layoutNodes; + + private LayoutGraph prevGraph; + + private final Random random = new Random(42); + + // Constants for offsets and displacements + private static final int MAX_OFFSET_AROUND_NEIGHBOR = 200; // Max offset for random positioning around a neighbor + private static final int MAX_OFFSET_AROUND_ORIGIN = 200; // Max offset for random positioning around origin + private static final int DISPLACEMENT_RANGE_BARYCENTER = 100; // Displacement range for barycenter calculation + private static final int DISPLACEMENT_RANGE_SINGLE = 200; + + // Create a comparator to sort nodes by the number of unassigned neighbors + private final Comparator LeastUnassignedNeighborsComparator = Comparator.comparingInt(node -> { + Vertex vertex = node.getVertex(); + int unassignedNeighbors = 0; + for (Vertex neighborVertex : prevGraph.getNeighborVertices(vertex)) { + if (!layoutNodes.containsKey(neighborVertex)) { + unassignedNeighbors++; + } + } + return unassignedNeighbors; + }); + + public FreeInteractiveLayoutManager() { + this.cutEdges = false; + this.layoutNodes = new HashMap<>(); + this.prevGraph = null; + } + + @Override + public void moveLink(Point linkPos, int shiftX) {} + + @Override + public void moveVertices(Set movedVertices) { + for (Vertex v : movedVertices) { + moveVertex(v); + } + } + + @Override + public void moveVertex(Vertex vertex) { + assert prevGraph.containsVertex(vertex); + LayoutNode layoutNode = layoutNodes.get(vertex); + layoutNode.setX(vertex.getPosition().x); + layoutNode.setY(vertex.getPosition().y); + for (Link link : prevGraph.getAllLinks(vertex)) { + setLinkControlPoints(link); + } + } + + @Override + public boolean isFreeForm() { + return true; + } + + public void setCutEdges(boolean enable) { + this.cutEdges = enable; + } + + @Override + public void doLayout(LayoutGraph graph) { + prevGraph = graph; + if (layoutNodes.isEmpty()) { + HierarchicalLayoutManager manager = new HierarchicalLayoutManager(); + manager.doLayout(graph); + for (LayoutNode node : graph.getLayoutNodes()) { + node.initSize(); + layoutNodes.put(node.getVertex(), node); + } + graph.clearLayout(); + } else { + // add new vertices to layoutNodes, x/y from barycenter + List newLayoutNodes = new ArrayList<>(); + + // Set up layout nodes for each vertex + for (Vertex vertex : prevGraph.getVertices()) { + if (!layoutNodes.containsKey(vertex)) { + LayoutNode addedNode = new LayoutNode(vertex); + addedNode.initSize(); + newLayoutNodes.add(addedNode); + } + } + + positionNewLayoutNodes(newLayoutNodes); + } + + // Write back vertices + for (Vertex vertex : prevGraph.getVertices()) { + LayoutNode layoutNode = layoutNodes.get(vertex); + vertex.setPosition(new Point(layoutNode.getLeft(), layoutNode.getTop())); + } + + // Write back links + for (Link link : prevGraph.getLinks()) { + setLinkControlPoints(link); + } + } + + public void positionNewLayoutNodes(List newLayoutNodes) { + Random random = new Random(); + + // First pass: Initial positioning based on unassigned neighbors + newLayoutNodes.sort(LeastUnassignedNeighborsComparator); + + for (LayoutNode node : newLayoutNodes) { + Vertex vertex = node.getVertex(); + + // Gather assigned neighbors + List assignedNeighbors = new ArrayList<>(); + for (Vertex neighborVertex : prevGraph.getNeighborVertices(vertex)) { + if (layoutNodes.containsKey(neighborVertex)) { + assignedNeighbors.add(layoutNodes.get(neighborVertex)); + } + } + + if (!assignedNeighbors.isEmpty()) { + if (assignedNeighbors.size() == 1) { + // Single neighbor: position around the neighbor + setPositionAroundSingleNode(node, assignedNeighbors.get(0), DISPLACEMENT_RANGE_SINGLE); + } else { + // Multiple neighbors: Calculate barycenter with displacement + calculateBarycenterWithDisplacement(node, assignedNeighbors, DISPLACEMENT_RANGE_BARYCENTER); + } + } else { + // No neighbors: Position randomly around (0, 0) + setRandomPositionAroundOrigin(node, random); + } + + // Add the new node to the layout + layoutNodes.put(vertex, node); + } + + // Second pass: Refine positions based on neighbor degree + newLayoutNodes.sort(LAYOUT_NODE_DEGREE_COMPARATOR.reversed()); + + // Collect all nodes (existing and new) + Collection allNodes = layoutNodes.values(); + + for (LayoutNode node : newLayoutNodes) { + Vertex vertex = node.getVertex(); + + // Gather assigned neighbors + List assignedNeighbors = new ArrayList<>(); + for (Vertex neighborVertex : prevGraph.getNeighborVertices(vertex)) { + if (layoutNodes.containsKey(neighborVertex)) { + assignedNeighbors.add(layoutNodes.get(neighborVertex)); + } + } + + if (!assignedNeighbors.isEmpty()) { + // Refine position based on force-based method + applyForceBasedAdjustment(node, assignedNeighbors, allNodes); + } + + // Ensure node's position remains updated in the layout + layoutNodes.put(vertex, node); + } + } + + private void applyForceBasedAdjustment(LayoutNode node, List assignedNeighbors, Collection allNodes) { + // Constants for force-based adjustment + final int ITERATIONS = 50; + final double REPULSION_CONSTANT = 1000; + final double SPRING_CONSTANT = 0.2; + final double DAMPING = 0.8; + final double IDEAL_LENGTH = 100; + + double posX = node.getX(); + double posY = node.getY(); + double dx = 0, dy = 0; // Displacement + + for (int i = 0; i < ITERATIONS; i++) { + double netForceX = 0; + double netForceY = 0; + + // Repulsive forces from all other nodes + for (LayoutNode otherNode : allNodes) { + if (otherNode == node) continue; // Skip self + + double deltaX = posX - otherNode.getX(); + double deltaY = posY - otherNode.getY(); + double distanceSquared = deltaX * deltaX + deltaY * deltaY + 0.01; // Prevent division by zero + double distance = Math.sqrt(distanceSquared); + + // Repulsive force (Coulomb's law) + double repulsiveForce = REPULSION_CONSTANT / distanceSquared; + netForceX += (deltaX / distance) * repulsiveForce; + netForceY += (deltaY / distance) * repulsiveForce; + } + + // Attractive forces to assigned neighbors + for (LayoutNode neighbor : assignedNeighbors) { + double deltaX = neighbor.getX() - posX; + double deltaY = neighbor.getY() - posY; + double distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY + 0.01); // Prevent division by zero + + // Attractive force (Hooke's law) + double displacement = distance - IDEAL_LENGTH; + double attractiveForce = SPRING_CONSTANT * displacement; + netForceX += (deltaX / distance) * attractiveForce; + netForceY += (deltaY / distance) * attractiveForce; + } + + // Update displacement with damping + dx = (dx + netForceX) * DAMPING; + dy = (dy + netForceY) * DAMPING; + + // Update node position + posX += dx; + posY += dy; + } + + // Set final position + node.setX((int) posX); + node.setY((int) posY); + } + + + // Utility method: position around a given node + private void setPositionAroundSingleNode(LayoutNode node, LayoutNode neighbor, int displacement) { + boolean neighborIsPredecessor = prevGraph.isPredecessorVertex(node.getVertex(), neighbor.getVertex()); + boolean neighborIsSuccessor = prevGraph.isSuccessorVertex(node.getVertex(), neighbor.getVertex()); + + int shiftY = 0; + if (neighborIsPredecessor) { + shiftY = displacement; + } else if (neighborIsSuccessor) { + shiftY = -displacement; + } + assert shiftY != 0; + + + int randomY = neighbor.getY() + random.nextInt(MAX_OFFSET_AROUND_NEIGHBOR + 1) + shiftY; + int randomX = neighbor.getX() + random.nextInt(MAX_OFFSET_AROUND_NEIGHBOR + 1); + node.setX(randomX); + node.setY(randomY); + } + + // Utility method: Random position around origin + private void setRandomPositionAroundOrigin(LayoutNode node, Random random) { + int randomX = random.nextInt(MAX_OFFSET_AROUND_ORIGIN + 1); + int randomY = random.nextInt(MAX_OFFSET_AROUND_ORIGIN + 1); + node.setX(randomX); + node.setY(randomY); + } + + // Utility method: Calculate barycenter with displacement + private void calculateBarycenterWithDisplacement(LayoutNode node, List neighbors, int displacementRange) { + double barycenterX = 0, barycenterY = 0; + for (LayoutNode neighbor : neighbors) { + barycenterX += neighbor.getX(); + barycenterY += neighbor.getY(); + } + barycenterX /= neighbors.size(); + barycenterY /= neighbors.size(); + + // Add random displacement for slight separation + int displacementX = random.nextInt(displacementRange + 1); + int displacementY = random.nextInt(displacementRange + 1); + node.setX((int) barycenterX + displacementX); + node.setY((int) barycenterY + displacementY); + } + + private void setLinkControlPoints(Link link) { + if (link.getFrom().getVertex() == link.getTo().getVertex()) return; + + LayoutNode from = layoutNodes.get(link.getFrom().getVertex()); + LayoutNode to = layoutNodes.get(link.getTo().getVertex()); + int relativeFromX = link.getFrom().getRelativePosition().x; + int relativeToX = link.getTo().getRelativePosition().x; + + int startX = from.getLeft() + relativeFromX; + int startY = from.getBottom(); + int endX = to.getLeft() + relativeToX; + int endY = to.getTop(); + + Point startPoint = new Point(startX, startY); + Point endPoint = new Point(endX, endY); + List line = new ArrayList<>(); + line.add(startPoint); + line.add(new Point(startPoint.x, startPoint.y + LINE_OFFSET)); + line.add(new Point(endPoint.x, endPoint.y - LINE_OFFSET)); + line.add(endPoint); + link.setControlPoints(line); + } +} diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java index baa4bbb458534..a27ab9cf01e61 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java @@ -23,7 +23,9 @@ */ package com.sun.hotspot.igv.hierarchicallayout; -import com.sun.hotspot.igv.layout.*; +import com.sun.hotspot.igv.layout.Cluster; +import com.sun.hotspot.igv.layout.Link; +import com.sun.hotspot.igv.layout.Vertex; import java.awt.*; import java.util.*; @@ -33,6 +35,8 @@ public class HierarchicalCFGLayoutManager extends LayoutManager { private final HierarchicalLayoutManager manager; private final Set clusters; private final Set clusterLinks; + Map clusterNodesMap; + Map clusterEdgesMap; public HierarchicalCFGLayoutManager(Set clusterLinks, Set clusters) { this.clusterLinks = clusterLinks; @@ -41,30 +45,9 @@ public HierarchicalCFGLayoutManager(Set clusterLinks, Set clusterNodesMap; - Map clusterEdgesMap; - private static void doLinearLayout(ClusterNode clusterNode) { Cluster cluster = clusterNode.getCluster(); LayoutGraph graph = new LayoutGraph(clusterNode.getSubEdges(), clusterNode.getSubNodes()); @@ -78,6 +61,11 @@ private static void doLinearLayout(ClusterNode clusterNode) { clusterNode.updateSize(); } + @Override + public void setCutEdges(boolean enable) { + manager.setCutEdges(enable); + } + public void doLayout(LayoutGraph graph) { // Create cluster-level nodes and edges. clusterNodesMap = createClusterNodes(graph.getVertices()); @@ -110,7 +98,7 @@ private Map createClusterNodes(SortedSet vertices) for (Cluster cluster : clusters) { String blockLabel = "B" + cluster; Dimension emptySize = new Dimension(fontMetrics.stringWidth(blockLabel) + ClusterNode.PADDING, - fontMetrics.getHeight() + ClusterNode.PADDING); + fontMetrics.getHeight() + ClusterNode.PADDING); ClusterNode clusterNode = new ClusterNode(cluster, cluster.toString(), fontMetrics.getHeight(), emptySize); clusterNodes.put(cluster, clusterNode); } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java index 9dba4d03c8bd5..4bff737a8d9f3 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java @@ -23,49 +23,73 @@ */ package com.sun.hotspot.igv.hierarchicallayout; -import com.sun.hotspot.igv.layout.*; +import com.sun.hotspot.igv.layout.Cluster; +import com.sun.hotspot.igv.layout.Link; +import com.sun.hotspot.igv.layout.Port; +import com.sun.hotspot.igv.layout.Vertex; import java.awt.*; -import java.util.*; import java.util.List; +import java.util.*; /** - * * @author Thomas Wuerthinger */ public class HierarchicalClusterLayoutManager extends LayoutManager { - private final LayoutManager subManager; private final LayoutManager manager; + private final HashMap layoutMovers; + private final HashMap clusterNodes; + public HierarchicalClusterLayoutManager() { - this.manager = new HierarchicalLayoutManager(); - this.subManager = new HierarchicalLayoutManager(); + this.manager = new HierarchicalLayoutManager(); + this.layoutMovers = new HashMap<>(); + this.clusterNodes = new HashMap<>(); } @Override public void setCutEdges(boolean enable) { - subManager.setCutEdges(enable); manager.setCutEdges(enable); } - public void doLayout(LayoutGraph graph, Set importantLinks) { - doLayout(graph); - } + public LayoutMover getLayoutMover() { + return new LayoutMover() { + @Override + public void moveLink(Point linkPos, int shiftX) { - public void setSubManager(LayoutManager manager) { - this.subManager = manager; - } + } + + @Override + public void moveVertices(Set movedVertices) { - public void setManager(LayoutManager manager) { - this.manager = manager; + } + + @Override + public void moveVertex(Vertex movedVertex) { + Cluster cluster = movedVertex.getCluster(); + LayoutMover layoutMover = layoutMovers.get(cluster); + if (layoutMover != null) { + layoutMover.moveVertex(movedVertex); + ClusterNode n = clusterNodes.get(cluster); + n.updateSize(); + cluster.setBounds(new Rectangle(n.getPosition(), n.getSize())); + } + } + + @Override + public boolean isFreeForm() { + return false; + } + }; } public void doLayout(LayoutGraph graph) { + layoutMovers.clear(); + clusterNodes.clear(); HashMap> listsConnection = new HashMap<>(); HashMap> clusterInputSlotHash = new HashMap<>(); HashMap> clusterOutputSlotHash = new HashMap<>(); - HashMap clusterNodes = new HashMap<>(); HashMap> clusterInputSlotSet = new HashMap<>(); HashMap> clusterOutputSlotSet = new HashMap<>(); Set clusterEdges = new HashSet<>(); @@ -102,21 +126,6 @@ public void doLayout(LayoutGraph graph) { z++; } - // Add cluster edges - for (Cluster c : clusters) { - - ClusterNode start = clusterNodes.get(c); - - for (Cluster succ : c.getSuccessors()) { - ClusterNode end = clusterNodes.get(succ); - if (end != null && start != end) { - ClusterEdge e = new ClusterEdge(start, end); - clusterEdges.add(e); - interClusterEdges.add(e); - } - } - } - for (Vertex v : graph.getVertices()) { Cluster c = v.getCluster(); assert c != null : "Cluster of vertex " + v + " is null!"; @@ -143,7 +152,7 @@ public void doLayout(LayoutGraph graph) { ClusterOutputSlotNode outputSlotNode; outputSlotNode = clusterOutputSlotHash.get(fromCluster).get(fromPort); - inputSlotNode = clusterInputSlotHash.get(toCluster).get(fromPort); + inputSlotNode = clusterInputSlotHash.get(toCluster).get(toPort); // TODO: fix? if (outputSlotNode == null) { outputSlotNode = new ClusterOutputSlotNode(clusterNodes.get(fromCluster), "Out " + fromCluster.toString() + " " + fromPort); @@ -159,7 +168,10 @@ public void doLayout(LayoutGraph graph) { } if (inputSlotNode == null) { - inputSlotNode = new ClusterInputSlotNode(clusterNodes.get(toCluster), "In " + toCluster.toString() + " " + fromPort); + inputSlotNode = new ClusterInputSlotNode( + clusterNodes.get(toCluster), + "In " + toCluster.toString() + " " + toPort // Use toPort here + ); clusterInputSlotSet.get(toCluster).add(inputSlotNode); } @@ -178,11 +190,13 @@ public void doLayout(LayoutGraph graph) { for (Cluster c : clusters) { ClusterNode n = clusterNodes.get(c); + HierarchicalLayoutManager subManager = new HierarchicalLayoutManager(); subManager.doLayout(new LayoutGraph(n.getSubEdges(), n.getSubNodes())); n.updateSize(); + layoutMovers.put(c, subManager); } - Set roots = new LayoutGraph(interClusterEdges).findRootVertices(); + Set roots = new LayoutGraph(interClusterEdges, new HashSet<>()).findRootVertices(); for (Vertex v : roots) { assert v instanceof ClusterNode; ((ClusterNode) v).setRoot(true); diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java index 69a1251fd0e73..854aa7ebb4798 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java @@ -23,15 +23,16 @@ */ package com.sun.hotspot.igv.hierarchicallayout; -import static com.sun.hotspot.igv.hierarchicallayout.LayoutEdge.LAYOUT_EDGE_LAYER_COMPARATOR; import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.*; import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Vertex; import java.awt.Point; import java.util.*; +public class HierarchicalLayoutManager extends LayoutManager implements LayoutMover { -public class HierarchicalLayoutManager extends LayoutManager { + int maxLayerLength; + private LayoutGraph graph; public HierarchicalLayoutManager() { setCutEdges(false); @@ -43,9 +44,11 @@ public void setCutEdges(boolean enable) { } @Override - public void setCutEdges(boolean enable) { - maxLayerLength = enable ? 10 : -1; - } + public void doLayout(LayoutGraph layoutGraph) { + layoutGraph.initializeLayout(); + + // STEP 1: Remove self edges and reverse edges + ReverseEdges.apply(layoutGraph); // STEP 2: Assign layers and create dummy nodes LayerManager.apply(layoutGraph, maxLayerLength); @@ -58,9 +61,77 @@ public void setCutEdges(boolean enable) { // STEP 5: Write back to interface WriteResult.apply(layoutGraph); + + graph = layoutGraph; } - static private class ReverseEdges { + + + @Override + public void moveLink(Point linkPos, int shiftX) { + int layerNr = graph.findLayer(linkPos.y); + for (LayoutNode node : graph.getLayer(layerNr)) { + if (node.isDummy() && linkPos.x == node.getX()) { + LayoutLayer layer = graph.getLayer(layerNr); + if (layer.contains(node)) { + node.setX(linkPos.x + shiftX); + layer.sortNodesByX(); + break; + } + } + } + writeBack(); + } + + @Override + public void moveVertices(Set movedVertices) { + for (Vertex vertex : movedVertices) { + moveVertex(vertex); + } + writeBack(); + } + + private void writeBack() { + graph.optimizeBackEdgeCrossings(); + graph.straightenEdges(); + WriteResult.apply(graph); + } + + @Override + public void moveVertex(Vertex movedVertex) { + Point newLoc = movedVertex.getPosition(); + LayoutNode movedNode = graph.getLayoutNode(movedVertex); + assert !movedNode.isDummy(); + + int layerNr = graph.findLayer(newLoc.y + movedNode.getOuterHeight() / 2); + if (movedNode.getLayer() == layerNr) { // we move the node in the same layer + LayoutLayer layer = graph.getLayer(layerNr); + if (layer.contains(movedNode)) { + movedNode.setX(newLoc.x); + layer.sortNodesByX(); + } + } else { // only remove edges if we moved the node to a new layer + if (maxLayerLength > 0) return; // TODO: not implemented + graph.removeNodeAndEdges(movedNode); + layerNr = graph.insertNewLayerIfNeeded(movedNode, layerNr); + graph.addNodeToLayer(movedNode, layerNr); + movedNode.setX(newLoc.x); + graph.getLayer(layerNr).sortNodesByX(); + graph.removeEmptyLayers(); + graph.addEdges(movedNode, maxLayerLength); + } + } + + @Override + public boolean isFreeForm() { + return false; + } + + public List getNodes() { + return graph.getAllNodes(); + } + + public static class ReverseEdges { static public void apply(LayoutGraph graph) { removeSelfEdges(graph); @@ -68,27 +139,32 @@ static public void apply(LayoutGraph graph) { depthFirstSearch(graph); for (LayoutNode node : graph.getLayoutNodes()) { - node.computeReversedLinkPoints(); + node.computeReversedLinkPoints(false); } } private static void removeSelfEdges(LayoutGraph graph) { for (LayoutNode node : graph.getLayoutNodes()) { - Iterator edgeIterator = node.getSuccs().iterator(); - while (edgeIterator.hasNext()) { - LayoutEdge edge = edgeIterator.next(); + // Collect self-edges first to avoid concurrent modification + List selfEdges = new ArrayList<>(); + for (LayoutEdge edge : node.getSuccessors()) { if (edge.getTo() == node) { - edgeIterator.remove(); - node.getPreds().remove(edge); + selfEdges.add(edge); } } + + // Remove each self-edge + for (LayoutEdge edge : selfEdges) { + node.removeSuccessor(edge); + node.removePredecessor(edge); + } } } private static void reverseRootInputs(LayoutGraph graph) { for (LayoutNode node : graph.getLayoutNodes()) { if (node.getVertex().isRoot()) { - for (LayoutEdge predEdge : new ArrayList<>(node.getPreds())) { + for (LayoutEdge predEdge : new ArrayList<>(node.getPredecessors())) { reverseEdge(predEdge); } } @@ -108,10 +184,10 @@ public static void reverseEdge(LayoutEdge edge) { edge.setRelativeFromX(relativeTo); edge.setRelativeToX(relativeFrom); - fromNode.getSuccs().remove(edge); - fromNode.getPreds().add(edge); - toNode.getPreds().remove(edge); - toNode.getSuccs().add(edge); + fromNode.removeSuccessor(edge); + fromNode.addPredecessor(edge); + toNode.removePredecessor(edge); + toNode.addSuccessor(edge); } private static void depthFirstSearch(LayoutGraph graph) { @@ -134,7 +210,7 @@ private static void depthFirstSearch(LayoutGraph graph) { visited.add(node); active.add(node); - for (LayoutEdge edge : new ArrayList<>(node.getSuccs())) { + for (LayoutEdge edge : new ArrayList<>(node.getSuccessors())) { LayoutNode successor = edge.getTo(); if (active.contains(successor)) { reverseEdge(edge); @@ -147,15 +223,14 @@ private static void depthFirstSearch(LayoutGraph graph) { } } - - static private class LayerManager { + public static class LayerManager { private static void assignLayerDownwards(LayoutGraph graph) { ArrayList workingList = new ArrayList<>(); // add all root nodes to layer 0 for (LayoutNode node : graph.getLayoutNodes()) { - if (!node.hasPreds()) { + if (!node.hasPredecessors()) { workingList.add(node); node.setLayer(0); } @@ -166,12 +241,12 @@ private static void assignLayerDownwards(LayoutGraph graph) { while (!workingList.isEmpty()) { ArrayList newWorkingList = new ArrayList<>(); for (LayoutNode node : workingList) { - for (LayoutEdge succEdge : node.getSuccs()) { + for (LayoutEdge succEdge : node.getSuccessors()) { LayoutNode succNode = succEdge.getTo(); if (succNode.getLayer() == -1) { // This node was not assigned before. boolean assignedPred = true; - for (LayoutEdge predEdge : succNode.getPreds()) { + for (LayoutEdge predEdge : succNode.getPredecessors()) { LayoutNode predNode = predEdge.getFrom(); if (predNode.getLayer() == -1 || predNode.getLayer() >= layer) { // This now has an unscheduled successor or a successor that was scheduled only in this round. @@ -201,7 +276,7 @@ private static void assignLayerUpwards(LayoutGraph graph) { ArrayList workingList = new ArrayList<>(); // add all leaves to working list, reset layer of non-leave nodes for (LayoutNode node : graph.getLayoutNodes()) { - if (!node.hasSuccs()) { + if (!node.hasSuccessors()) { workingList.add(node); } else { node.setLayer(-1); @@ -215,12 +290,12 @@ private static void assignLayerUpwards(LayoutGraph graph) { ArrayList newWorkingList = new ArrayList<>(); for (LayoutNode node : workingList) { if (node.getLayer() < layer) { - for (LayoutEdge predEdge : node.getPreds()) { + for (LayoutEdge predEdge : node.getPredecessors()) { LayoutNode predNode = predEdge.getFrom(); if (predNode.getLayer() == -1) { // This node was not assigned before. boolean assignedSucc = true; - for (LayoutEdge succEdge : predNode.getSuccs()) { + for (LayoutEdge succEdge : predNode.getSuccessors()) { LayoutNode succNode = succEdge.getTo(); if (succNode.getLayer() == -1 || succNode.getLayer() >= layer) { // This now has an unscheduled successor or a successor that was scheduled only in this round. @@ -269,19 +344,19 @@ static private void createDummyNodes(LayoutGraph graph, int maxLayerLength) { if (layoutNode.getLayer() == 0) { graph.getLayer(0).add(layoutNode); visited.add(layoutNode); - } else if (!layoutNode.hasPreds()) { + } else if (!layoutNode.hasPredecessors()) { graph.getLayer(layoutNode.getLayer()).add(layoutNode); visited.add(layoutNode); } } for (LayoutNode layoutNode : layoutNodes) { - createDummiesForNodeSuccessor(graph, layoutNode, maxLayerLength); + graph.createDummiesForNodeSuccessor(layoutNode, maxLayerLength); } for (int i = 0; i < graph.getLayerCount() - 1; i++) { for (LayoutNode n : graph.getLayer(i)) { - for (LayoutEdge e : n.getSuccs()) { + for (LayoutEdge e : n.getSuccessors()) { if (e.getTo().isDummy()) continue; if (!visited.contains(e.getTo())) { visited.add(e.getTo()); @@ -293,115 +368,6 @@ static private void createDummyNodes(LayoutGraph graph, int maxLayerLength) { } } - static private void createDummiesForNodeSuccessor(LayoutGraph graph, LayoutNode layoutNode, int maxLayerLength) { - HashMap> portsToUnprocessedEdges = new HashMap<>(); - ArrayList succs = new ArrayList<>(layoutNode.getSuccs()); - HashMap portToTopNode = new HashMap<>(); - HashMap> portToBottomNodeMapping = new HashMap<>(); - for (LayoutEdge succEdge : succs) { - int startPort = succEdge.getRelativeFromX(); - LayoutNode fromNode = succEdge.getFrom(); - LayoutNode toNode = succEdge.getTo(); - - // edge is longer than one layer => needs dummy nodes - if (fromNode.getLayer() != toNode.getLayer() - 1) { - // the edge needs to be cut - if (maxLayerLength != -1 && toNode.getLayer() - fromNode.getLayer() > maxLayerLength) { - // remove the succEdge before replacing it - toNode.getPreds().remove(succEdge); - fromNode.getSuccs().remove(succEdge); - - LayoutNode topCutNode = portToTopNode.get(startPort); - if (topCutNode == null) { - topCutNode = new LayoutNode(); - topCutNode.setLayer(fromNode.getLayer() + 1); - graph.addNodeToLayer(topCutNode, topCutNode.getLayer()); - portToTopNode.put(startPort, topCutNode); - portToBottomNodeMapping.put(startPort, new HashMap<>()); - } - LayoutEdge edgeToTopCut = new LayoutEdge(fromNode, topCutNode, succEdge.getRelativeFromX(), topCutNode.getWidth() / 2, succEdge.getLink()); - if (succEdge.isReversed()) edgeToTopCut.reverse(); - fromNode.getSuccs().add(edgeToTopCut); - topCutNode.getPreds().add(edgeToTopCut); - - HashMap layerToBottomNode = portToBottomNodeMapping.get(startPort); - LayoutNode bottomCutNode = layerToBottomNode.get(toNode.getLayer()); - if (bottomCutNode == null) { - bottomCutNode = new LayoutNode(); - bottomCutNode.setLayer(toNode.getLayer() - 1); - graph.addNodeToLayer(bottomCutNode, bottomCutNode.getLayer()); - layerToBottomNode.put(toNode.getLayer(), bottomCutNode); - } - LayoutEdge bottomEdge = new LayoutEdge(bottomCutNode, toNode, bottomCutNode.getWidth() / 2, succEdge.getRelativeToX(), succEdge.getLink()); - if (succEdge.isReversed()) bottomEdge.reverse(); - toNode.getPreds().add(bottomEdge); - bottomCutNode.getSuccs().add(bottomEdge); - - } else { // the edge is not cut, but needs dummy nodes - portsToUnprocessedEdges.putIfAbsent(startPort, new ArrayList<>()); - portsToUnprocessedEdges.get(startPort).add(succEdge); - } - } - } - - for (Map.Entry> portToUnprocessedEdges : portsToUnprocessedEdges.entrySet()) { - Integer startPort = portToUnprocessedEdges.getKey(); - List unprocessedEdges = portToUnprocessedEdges.getValue(); - unprocessedEdges.sort(LAYOUT_EDGE_LAYER_COMPARATOR); - - if (unprocessedEdges.size() == 1) { - // process a single edge - LayoutEdge singleEdge = unprocessedEdges.get(0); - LayoutNode fromNode = singleEdge.getFrom(); - if (singleEdge.getTo().getLayer() > fromNode.getLayer() + 1) { - LayoutEdge previousEdge = singleEdge; - for (int i = fromNode.getLayer() + 1; i < previousEdge.getTo().getLayer(); i++) { - LayoutNode dummyNode = new LayoutNode(); - dummyNode.setLayer(i); - dummyNode.getPreds().add(previousEdge); - graph.addNodeToLayer(dummyNode, dummyNode.getLayer()); - LayoutEdge dummyEdge = new LayoutEdge(dummyNode, previousEdge.getTo(), dummyNode.getWidth() / 2, previousEdge.getRelativeToX(), singleEdge.getLink()); - if (previousEdge.isReversed()) dummyEdge.reverse(); - dummyNode.getSuccs().add(dummyEdge); - previousEdge.setRelativeToX(dummyNode.getWidth() / 2); - previousEdge.getTo().getPreds().remove(previousEdge); - previousEdge.getTo().getPreds().add(dummyEdge); - previousEdge.setTo(dummyNode); - previousEdge = dummyEdge; - } - } - } else { - int lastLayer = unprocessedEdges.get(unprocessedEdges.size() - 1).getTo().getLayer(); - int dummyCnt = lastLayer - layoutNode.getLayer() - 1; - LayoutEdge[] newDummyEdges = new LayoutEdge[dummyCnt]; - LayoutNode[] newDummyNodes = new LayoutNode[dummyCnt]; - - newDummyNodes[0] = new LayoutNode(); - newDummyNodes[0].setLayer(layoutNode.getLayer() + 1); - newDummyEdges[0] = new LayoutEdge(layoutNode, newDummyNodes[0], startPort, newDummyNodes[0].getWidth() / 2, null); - newDummyNodes[0].getPreds().add(newDummyEdges[0]); - layoutNode.getSuccs().add(newDummyEdges[0]); - for (int j = 1; j < dummyCnt; j++) { - newDummyNodes[j] = new LayoutNode(); - newDummyNodes[j].setLayer(layoutNode.getLayer() + j + 1); - newDummyEdges[j] = new LayoutEdge(newDummyNodes[j - 1], newDummyNodes[j], null); - newDummyNodes[j].getPreds().add(newDummyEdges[j]); - newDummyNodes[j - 1].getSuccs().add(newDummyEdges[j]); - } - for (LayoutEdge unprocessedEdge : unprocessedEdges) { - LayoutNode anchorNode = newDummyNodes[unprocessedEdge.getTo().getLayer() - layoutNode.getLayer() - 2]; - anchorNode.getSuccs().add(unprocessedEdge); - unprocessedEdge.setFrom(anchorNode); - unprocessedEdge.setRelativeFromX(anchorNode.getWidth() / 2); - layoutNode.getSuccs().remove(unprocessedEdge); - } - for (LayoutNode dummyNode : newDummyNodes) { - graph.addNodeToLayer(dummyNode, dummyNode.getLayer()); - } - } - } - } - static public void apply(LayoutGraph graph, int maxLayerLength) { assignLayers(graph); createDummyNodes(graph, maxLayerLength); @@ -409,209 +375,280 @@ static public void apply(LayoutGraph graph, int maxLayerLength) { } } - private static class CrossingReduction { + static class CrossingReduction { public static void apply(LayoutGraph graph) { + for (int i = 0; i < graph.getLayerCount(); i++) { + graph.getLayer(i).updateNodeIndices(); + graph.getLayer(i).initXPositions(); + } + for (int i = 0; i < CROSSING_ITERATIONS; i++) { downSweep(graph); upSweep(graph); } downSweep(graph); - graph.updatePositions(); } - private static void doAveragePositions(LayoutLayer layer) { - for (LayoutNode node : layer) { - node.setWeightedPosition(node.averagePosition()); - } - layer.sort(CROSSING_NODE_COMPARATOR); - int x = 0; - for (LayoutNode n : layer) { - n.setWeightedPosition(x); - x += n.getOuterWidth() + NODE_OFFSET; + private static void downSweep(LayoutGraph graph) { + + for (int i = 1; i < graph.getLayerCount(); i++) { + for (LayoutNode n : graph.getLayer(i)) { + n.setCrossingNumber(0); + } + for (LayoutNode n : graph.getLayer(i)) { + int sum = 0; + int count = 0; + for (LayoutEdge e : n.getPredecessors()) { + sum += e.getStartX(); + count++; + } + + if (count > 0) { + sum /= count; + n.setCrossingNumber(sum); + } + } + updateCrossingNumbers(graph.getLayer(i), true); + graph.getLayer(i).sort(NODE_CROSSING_COMPARATOR); + graph.getLayer(i).initXPositions(); + graph.getLayer(i).updateNodeIndices(); } } - private static void doMedianPositions(LayoutLayer layer, boolean usePred) { - for (LayoutNode node : layer) { - int size = usePred ? node.getPreds().size() : node.getSuccs().size(); - if (size == 0) continue; - float[] values = new float[size]; - for (int j = 0; j < size; j++) { - LayoutNode predNode = usePred ? node.getPreds().get(j).getFrom() : node.getSuccs().get(j).getTo(); - values[j] = predNode.getWeightedPosition(); + private static void updateCrossingNumbers(LayoutLayer layer, boolean down) { + for (int i = 0; i < layer.size(); i++) { + LayoutNode n = layer.get(i); + LayoutNode prev = null; + if (i > 0) { + prev = layer.get(i - 1); } - Arrays.sort(values); - if (values.length % 2 == 0) { - node.setWeightedPosition((values[size / 2 - 1] + values[size / 2]) / 2); - } else { - node.setWeightedPosition(values[size / 2]); + LayoutNode next = null; + if (i < layer.size() - 1) { + next = layer.get(i + 1); + } + boolean cond = !n.hasSuccessors(); + if (down) { + cond = !n.hasPredecessors(); + } + if (cond) { + if (prev != null && next != null) { + n.setCrossingNumber((prev.getCrossingNumber() + next.getCrossingNumber()) / 2); + } else if (prev != null) { + n.setCrossingNumber(prev.getCrossingNumber()); + } else if (next != null) { + n.setCrossingNumber(next.getCrossingNumber()); + } } - } - layer.sort(CROSSING_NODE_COMPARATOR); - int x = 0; - for (LayoutNode n : layer) { - n.setWeightedPosition(x); - x += n.getOuterWidth() + NODE_OFFSET; } } - private static void placeLeavesAndRoots(LayoutLayer layer, boolean usePred) { - // Nodes that have no adjacent nodes on the neighboring layer: - // leave fixed in their current positions with non-fixed nodes sorted into the remaining positions - for (int j = 0; j < layer.size(); j++) { - LayoutNode node = layer.get(j); - if (usePred ? !node.hasPreds() : !node.hasSuccs()) { - float prevWeight = (j > 0) ? layer.get(j - 1).getWeightedPosition() : 0; - float nextWeight = (j < layer.size() - 1) ? layer.get(j + 1).getWeightedPosition() : 0; - node.setWeightedPosition((prevWeight + nextWeight) / 2); + private static void upSweep(LayoutGraph graph) { + for (int i = graph.getLayerCount() - 2; i >= 0; i--) { + for (LayoutNode n : graph.getLayer(i)) { + n.setCrossingNumber(0); } - } - layer.sort(CROSSING_NODE_COMPARATOR); - int x = 0; - for (LayoutNode n : layer) { - n.setWeightedPosition(x); - x += n.getOuterWidth() + NODE_OFFSET; + for (LayoutNode n : graph.getLayer(i)) { + int count = 0; + int sum = 0; + for (LayoutEdge e : n.getSuccessors()) { + sum += e.getEndX(); + count++; + } + if (count > 0) { + sum /= count; + n.setCrossingNumber(sum); + } + + } + updateCrossingNumbers(graph.getLayer(i), false); + graph.getLayer(i).sort(NODE_CROSSING_COMPARATOR); + graph.getLayer(i).initXPositions(); + graph.getLayer(i).updateNodeIndices(); } } + } - private static void downSweep(LayoutGraph graph) { - for (int i = 0; i < graph.getLayerCount(); i++) { - doAveragePositions(graph.getLayer(i)); + static class AssignXCoordinates { + + private static List> space; + private static List> downProcessingOrder; + private static List> upProcessingOrder; + + private static final Comparator nodeProcessingDownComparator = (n1, n2) -> { + if (n1.isDummy()) { + if (n2.isDummy()) { + return 0; + } + return -1; } - for (int i = 1; i < graph.getLayerCount(); i++) { - doMedianPositions(graph.getLayer(i), true); - placeLeavesAndRoots(graph.getLayer(i), true); + if (n2.isDummy()) { + return 1; } - } + return n1.getInDegree() - n2.getInDegree(); + }; - private static void upSweep(LayoutGraph graph) { - for (int i = graph.getLayerCount() - 1; i >= 0; i--) { - doAveragePositions(graph.getLayer(i)); + private static final Comparator nodeProcessingUpComparator = (n1, n2) -> { + if (n1.isDummy()) { + if (n2.isDummy()) { + return 0; + } + return -1; } - for (int i = graph.getLayerCount() - 2; i >= 0; i--) { - doMedianPositions(graph.getLayer(i), false); - placeLeavesAndRoots(graph.getLayer(i), false); + if (n2.isDummy()) { + return 1; } - } - } + return n1.getOutDegree() - n2.getOutDegree(); + }; - private static class AssignXCoordinates { - - static int[][] space; - static LayoutNode[][] downProcessingOrder; - static LayoutNode[][] upProcessingOrder; + public static void apply(LayoutGraph graph) { + space = new ArrayList<>(graph.getLayerCount()); + downProcessingOrder = new ArrayList<>(graph.getLayerCount()); + upProcessingOrder = new ArrayList<>(graph.getLayerCount()); - static private void createArrays(LayoutGraph graph) { - space = new int[graph.getLayerCount()][]; - downProcessingOrder = new LayoutNode[graph.getLayerCount()][]; - upProcessingOrder = new LayoutNode[graph.getLayerCount()][]; for (int i = 0; i < graph.getLayerCount(); i++) { - LayoutLayer layer = graph.getLayer(i); - space[i] = new int[layer.size()]; - downProcessingOrder[i] = new LayoutNode[layer.size()]; - upProcessingOrder[i] = new LayoutNode[layer.size()]; + // Add a new empty list for each layer + space.add(new ArrayList<>()); + downProcessingOrder.add(new ArrayList<>()); + upProcessingOrder.add(new ArrayList<>()); + int curX = 0; - for (int j = 0; j < layer.size(); j++) { - space[i][j] = curX; - LayoutNode node = layer.get(j); - curX += node.getOuterWidth() + NODE_OFFSET; - downProcessingOrder[i][j] = node; - upProcessingOrder[i][j] = node; + for (LayoutNode n : graph.getLayer(i)) { + // Add the current position to space and increment curX + space.get(i).add(curX); + curX += n.getOuterWidth() + NODE_OFFSET; + + // Add the current node to processing orders + downProcessingOrder.get(i).add(n); + upProcessingOrder.get(i).add(n); } - Arrays.sort(downProcessingOrder[i], NODE_PROCESSING_DOWN_COMPARATOR); - Arrays.sort(upProcessingOrder[i], NODE_PROCESSING_UP_COMPARATOR); + + // Sort the processing orders + downProcessingOrder.get(i).sort(nodeProcessingDownComparator); + upProcessingOrder.get(i).sort(nodeProcessingUpComparator); } - } - static private void initialPositions(LayoutGraph graph) { - for (LayoutNode layoutNode : graph.getLayoutNodes()) { - layoutNode.setX(space[layoutNode.getLayer()][layoutNode.getPos()]); + for (LayoutNode n : graph.getLayoutNodes()) { + n.setX(space.get(n.getLayer()).get(n.getPos())); } - for (LayoutNode dummyNode : graph.getDummyNodes()) { - dummyNode.setX(space[dummyNode.getLayer()][dummyNode.getPos()]); + + for (LayoutNode n : graph.getDummyNodes()) { + n.setX(space.get(n.getLayer()).get(n.getPos())); } - } - static private void apply(LayoutGraph graph) { - createArrays(graph); - initialPositions(graph); for (int i = 0; i < SWEEP_ITERATIONS; i++) { sweepDown(graph); + adjustSpace(graph); sweepUp(graph); + adjustSpace(graph); } - graph.optimizeBackEdgeCrossings(); - graph.straightenEdges(); + + sweepDown(graph); + adjustSpace(graph); + sweepUp(graph); } - static private void processRow(int[] space, LayoutNode[] processingOrder) { - Arrays.sort(processingOrder, DUMMY_NODES_THEN_OPTIMAL_X); - TreeSet treeSet = new TreeSet<>(NODE_POS_COMPARATOR); - for (LayoutNode node : processingOrder) { - int minX = Integer.MIN_VALUE; - SortedSet headSet = treeSet.headSet(node, false); - if (!headSet.isEmpty()) { - LayoutNode leftNeighbor = headSet.last(); - minX = leftNeighbor.getOuterLeft() + space[node.getPos()] - space[leftNeighbor.getPos()]; + private static void adjustSpace(LayoutGraph graph) { + for (int i = 0; i < graph.getLayerCount(); i++) { + for (LayoutNode n : graph.getLayer(i)) { + space.get(i).add(n.getX()); } + } + } - int maxX = Integer.MAX_VALUE; - SortedSet tailSet = treeSet.tailSet(node, false); - if (!tailSet.isEmpty()) { - LayoutNode rightNeighbor = tailSet.first(); - maxX = rightNeighbor.getOuterLeft() + space[node.getPos()] - space[rightNeighbor.getPos()]; + private static void sweepUp(LayoutGraph graph) { + for (int i = graph.getLayerCount() - 1; i >= 0; i--) { + NodeRow r = new NodeRow(space.get(i)); + for (LayoutNode n : upProcessingOrder.get(i)) { + int optimal = n.calculateOptimalXFromSuccessors(true); + r.insert(n, optimal); } - - node.setX(Math.min(Math.max(node.getOptimalX(), minX), maxX)); - treeSet.add(node); } } - static private void sweepUp(LayoutGraph graph) { - for (int i = graph.getLayerCount() - 2; i >= 0; i--) { - for (LayoutNode node : upProcessingOrder[i]) { - node.setOptimalX(node.calculateOptimalPositionUp()); + private static void sweepDown(LayoutGraph graph) { + for (int i = 1; i < graph.getLayerCount(); i++) { + NodeRow r = new NodeRow(space.get(i)); + for (LayoutNode n : downProcessingOrder.get(i)) { + int optimal = n.calculateOptimalXFromPredecessors(true); + r.insert(n, optimal); } - processRow(space[i], upProcessingOrder[i]); } } + } + + public static class NodeRow { + + private final TreeSet treeSet; + private final ArrayList space; + + public NodeRow(ArrayList space) { + treeSet = new TreeSet<>(NODE_POS_COMPARATOR); + this.space = space; + } - static private void sweepDown(LayoutGraph graph) { - for (int i = 1; i < graph.getLayerCount(); i++) { - for (LayoutNode node : downProcessingOrder[i]) { - node.setOptimalX(node.calculateOptimalPositionDown()); + public int offset(LayoutNode n1, LayoutNode n2) { + int v1 = space.get(n1.getPos()) + n1.getOuterWidth(); + int v2 = space.get(n2.getPos()); + return v2 - v1; + } + + public void insert(LayoutNode n, int pos) { + + SortedSet headSet = treeSet.headSet(n); + + LayoutNode leftNeighbor; + int minX = Integer.MIN_VALUE; + if (!headSet.isEmpty()) { + leftNeighbor = headSet.last(); + minX = leftNeighbor.getOuterRight() + offset(leftNeighbor, n); + } + + if (pos < minX) { + n.setX(minX); + } else { + + LayoutNode rightNeighbor; + SortedSet tailSet = treeSet.tailSet(n); + int maxX = Integer.MAX_VALUE; + if (!tailSet.isEmpty()) { + rightNeighbor = tailSet.first(); + maxX = rightNeighbor.getX() - offset(n, rightNeighbor) - n.getOuterWidth(); } - processRow(space[i], downProcessingOrder[i]); + + n.setX(Math.min(pos, maxX)); } + + treeSet.add(n); } } - private static class WriteResult { + public static class WriteResult { private static HashMap> computeLinkPositions(LayoutGraph graph) { HashMap> linkToSplitEndPoints = new HashMap<>(); HashMap> linkPositions = new HashMap<>(); for (LayoutNode layoutNode : graph.getLayoutNodes()) { - for (LayoutEdge predEdge : layoutNode.getPreds()) { + for (LayoutEdge predEdge : layoutNode.getPredecessors()) { LayoutNode fromNode = predEdge.getFrom(); LayoutNode toNode = predEdge.getTo(); ArrayList linkPoints = new ArrayList<>(); // input edge stub - linkPoints.add(new Point(predEdge.getEndX(), toNode.getTop())); + linkPoints.add(new Point(predEdge.getEndX(), predEdge.getEndY())); linkPoints.add(new Point(predEdge.getEndX(), graph.getLayer(toNode.getLayer()).getTop() - LAYER_OFFSET)); LayoutEdge curEdge = predEdge; - while (fromNode.isDummy() && fromNode.hasPreds()) { + while (fromNode.isDummy() && fromNode.hasPredecessors()) { linkPoints.add(new Point(fromNode.getCenterX(), graph.getLayer(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); linkPoints.add(new Point(fromNode.getCenterX(), graph.getLayer(fromNode.getLayer()).getTop() - LAYER_OFFSET)); - curEdge = fromNode.getPreds().get(0); + curEdge = fromNode.getPredecessors().get(0); fromNode = curEdge.getFrom(); } linkPoints.add(new Point(curEdge.getStartX(), graph.getLayer(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); // output edge stub - linkPoints.add(new Point(curEdge.getStartX(), fromNode.getBottom())); + linkPoints.add(new Point(curEdge.getStartX(), curEdge.getStartY())); if (predEdge.isReversed()) { for (Point relativeEnd : toNode.getReversedLinkEndPoints().get(predEdge.getLink())) { @@ -644,7 +681,7 @@ private static HashMap> computeLinkPositions(LayoutGraph graph } for (LayoutNode layoutNode : graph.getLayoutNodes()) { - for (LayoutEdge succEdge : layoutNode.getSuccs()) { + for (LayoutEdge succEdge : layoutNode.getSuccessors()) { if (succEdge.getLink() == null) continue; LayoutNode fromNode = succEdge.getFrom(); @@ -655,10 +692,10 @@ private static HashMap> computeLinkPositions(LayoutGraph graph linkPoints.add(new Point(succEdge.getStartX(), graph.getLayer(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); LayoutEdge curEdge = succEdge; - while (toNode.isDummy() && toNode.hasSuccs()) { + while (toNode.isDummy() && toNode.hasSuccessors()) { linkPoints.add(new Point(toNode.getCenterX(), graph.getLayer(toNode.getLayer()).getTop() - LAYER_OFFSET)); linkPoints.add(new Point(toNode.getCenterX(), graph.getLayer(toNode.getLayer()).getBottom() + LAYER_OFFSET)); - curEdge = toNode.getSuccs().get(0); + curEdge = toNode.getSuccessors().get(0); toNode = curEdge.getTo(); } linkPoints.add(new Point(curEdge.getEndX(), graph.getLayer(toNode.getLayer()).getTop() - LAYER_OFFSET)); @@ -743,7 +780,7 @@ public static void apply(LayoutGraph graph) { } for (LayoutLayer layer : graph.getLayers()) { - layer.shiftTop(-minY); + layer.moveLayerVertically(-minY); } // Shift vertices by minX/minY diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java index 2922e2c192e89..afd692e84464f 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,27 +23,23 @@ */ package com.sun.hotspot.igv.hierarchicallayout; +import static com.sun.hotspot.igv.hierarchicallayout.LayoutManager.LAYER_OFFSET; +import static com.sun.hotspot.igv.hierarchicallayout.LayoutManager.NODE_OFFSET; import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Vertex; import java.awt.Dimension; import java.awt.Point; import java.util.*; -public class HierarchicalStableLayoutManager extends LayoutManager{ +public class HierarchicalStableLayoutManager { - public static final int DUMMY_HEIGHT = 1; - public static final int DUMMY_WIDTH = 1; - public static final int X_OFFSET = 8; - public static final int LAYER_OFFSET = 8; // Algorithm global data structures private Set currentVertices; private Set currentLinks; private Set reversedLinks; private List nodes; private final HashMap vertexToLayoutNode; - private HashMap> reversedLinkStartPoints; - private HashMap> reversedLinkEndPoints; - private HashMap> layers; + private HashMap layers; private final HierarchicalLayoutManager manager; private HashMap vertexToAction; @@ -63,14 +59,6 @@ public void doLayout(LayoutGraph layoutGraph) { } - public void setCutEdges(boolean cutEdges) { - } - - @Override - public void doLayout(LayoutGraph graph) { - - } - enum Action { ADD, REMOVE @@ -100,7 +88,7 @@ public LinkAction(Link link, Action action) { public HierarchicalStableLayoutManager() { oldVertices = new HashSet<>(); oldLinks = new HashSet<>(); - manager = new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS); + manager = new HierarchicalLayoutManager(); vertexToLayoutNode = new HashMap<>(); nodes = new ArrayList<>(); } @@ -115,26 +103,48 @@ public boolean getCutEdges() { } private int calculateOptimalBoth(LayoutNode n) { - if (n.preds.isEmpty() && n.succs.isEmpty()) { - return n.x; + if (n.getPredecessors().isEmpty() && n.getSuccessors().isEmpty()) { + return n.getX(); } - int[] values = new int[n.preds.size() + n.succs.size()]; + int[] values = new int[n.getPredecessors().size() + n.getSuccessors().size()]; int i = 0; - for (LayoutEdge e : n.preds) { - values[i] = e.from.x + e.relativeFrom - e.relativeTo; + for (LayoutEdge e : n.getPredecessors()) { + values[i] = e.getFrom().getX() + e.getRelativeFromX() - e.getRelativeToX(); i++; } - for (LayoutEdge e : n.succs) { - values[i] = e.to.x + e.relativeTo - e.relativeFrom; + for (LayoutEdge e : n.getSuccessors()) { + values[i] = e.getTo().getX() + e.getRelativeToX() - e.getRelativeFromX(); i++; } - return Statistics.median(values); + return median(values); + } + + public static int median(int[] values) { + Arrays.sort(values); + if (values.length % 2 == 0) { + return (values[values.length / 2 - 1] + values[values.length / 2]) / 2; + } else { + return values[values.length / 2]; + } } + public static final Comparator nodeProcessingUpComparator = (n1, n2) -> { + if (n1.getVertex() == null) { + if (n2.getVertex() == null) { + return 0; + } + return -1; + } + if (n2.getVertex() == null) { + return 1; + } + return n1.getSuccessors().size() - n2.getSuccessors().size(); + }; + /** * Adjust the X-coordinates of the nodes in the given layer, as a new node has * been inserted at that layer @@ -144,16 +154,16 @@ private void adjustXCoordinates(int layer) { ArrayList space = new ArrayList<>(); List nodeProcessingOrder = new ArrayList<>(); - nodes.sort(HierarchicalLayoutManager.nodePositionComparator); + nodes.sort(Comparator.comparingInt(LayoutNode::getPos)); int curX = 0; for (LayoutNode n : nodes) { space.add(curX); - curX += n.width + X_OFFSET; + curX += n.getOuterWidth() + NODE_OFFSET; nodeProcessingOrder.add(n); } - nodeProcessingOrder.sort(HierarchicalLayoutManager.nodeProcessingUpComparator); + nodeProcessingOrder.sort(nodeProcessingUpComparator); HierarchicalLayoutManager.NodeRow r = new HierarchicalLayoutManager.NodeRow(space); for (LayoutNode n : nodeProcessingOrder) { int optimal = calculateOptimalBoth(n); @@ -163,8 +173,8 @@ private void adjustXCoordinates(int layer) { private void ensureNeighborEdgeConsistency() { for (LayoutNode n : nodes) { - n.succs.removeIf(e -> !nodes.contains(e.to)); - n.preds.removeIf(e -> !nodes.contains(e.from)); + n.getSuccessorsRaw().removeIf(e -> !nodes.contains(e.getTo())); + n.getPredecessorsRaw().removeIf(e -> !nodes.contains(e.getFrom())); } } @@ -254,8 +264,8 @@ private void findInitialReversedLinks() { for (Link link : oldLinks) { for (Link l : currentLinks) { if (l.equals(link)) { - if (vertexToLayoutNode.get(l.getFrom().getVertex()).layer > vertexToLayoutNode - .get(l.getTo().getVertex()).layer) { + if (vertexToLayoutNode.get(l.getFrom().getVertex()).getLayer() > vertexToLayoutNode + .get(l.getTo().getVertex()).getLayer()) { // Link is reversed reversedLinks.add(l); updateReversedLinkPositions(l); @@ -271,7 +281,7 @@ private void updateReversedLinkPositions(Link link) { // Correct direction, is reversed link assert fromNode != null && toNode != null; assert nodes.contains(fromNode) && nodes.contains(toNode); - assert fromNode.layer > toNode.layer; + assert fromNode.getLayer() > toNode.getLayer(); assert reversedLinks.contains(link); updateNodeWithReversedEdges(fromNode); @@ -279,138 +289,7 @@ private void updateReversedLinkPositions(Link link) { } private void updateNodeWithReversedEdges(LayoutNode node) { - // Reset node data in case there were previous reversed edges - node.width = (int) node.vertex.getSize().getWidth(); - node.height = (int) node.vertex.getSize().getHeight(); - node.yOffset = 0; - node.bottomYOffset = 0; - node.xOffset = 0; - node.inOffsets.clear(); - node.outOffsets.clear(); - - SortedSet reversedDown = new TreeSet<>(); - - // Reset relativeFrom for all succ edges - for (LayoutEdge e : node.succs) { - if (e.link == null) { - continue; - } - e.relativeFrom = e.link.getFrom().getRelativePosition().x; - if (reversedLinks.contains(e.link)) { - e.relativeFrom = e.link.getTo().getRelativePosition().x; - reversedDown.add(e.relativeFrom); - } - } - - // Whether the node has non-self reversed edges going downwards. - // If so, reversed edges going upwards are drawn to the left. - boolean hasReversedDown = !reversedDown.isEmpty(); - - SortedSet reversedUp; - if (hasReversedDown) { - reversedUp = new TreeSet<>(); - } else { - reversedUp = new TreeSet<>(Collections.reverseOrder()); - } - - // Reset relativeTo for all pred edges - for (LayoutEdge e : node.preds) { - if (e.link == null) { - continue; - } - e.relativeTo = e.link.getTo().getRelativePosition().x; - if (reversedLinks.contains(e.link)) { - e.relativeTo = e.link.getFrom().getRelativePosition().x; - reversedUp.add(e.relativeTo); - } - } - - final int offset = X_OFFSET + DUMMY_WIDTH; - - int curY = 0; - int curWidth = node.width + reversedDown.size() * offset; - for (int pos : reversedDown) { - ArrayList reversedSuccs = new ArrayList<>(); - for (LayoutEdge e : node.succs) { - if (e.relativeFrom == pos && reversedLinks.contains(e.link)) { - reversedSuccs.add(e); - e.relativeFrom = curWidth; - } - } - - ArrayList startPoints = new ArrayList<>(); - startPoints.add(new Point(curWidth, curY)); - startPoints.add(new Point(pos, curY)); - startPoints.add(new Point(pos, reversedDown.size() * offset)); - for (LayoutEdge e : reversedSuccs) { - reversedLinkStartPoints.put(e.link, startPoints); - } - - node.inOffsets.put(pos, -curY); - curY += offset; - node.height += offset; - node.yOffset += offset; - curWidth -= offset; - } - - int widthFactor = reversedDown.size(); - node.width += widthFactor * offset; - - int curX = 0; - int minX = 0; - if (hasReversedDown) { - minX = -offset * reversedUp.size(); - } - - int oldNodeHeight = node.height; - for (int pos : reversedUp) { - ArrayList reversedPreds = new ArrayList<>(); - for (LayoutEdge e : node.preds) { - if (e.relativeTo == pos && reversedLinks.contains(e.link)) { - if (hasReversedDown) { - e.relativeTo = curX - offset; - } else { - e.relativeTo = node.width + offset; - } - - reversedPreds.add(e); - } - } - node.height += offset; - ArrayList endPoints = new ArrayList<>(); - - node.width += offset; - if (hasReversedDown) { - curX -= offset; - endPoints.add(new Point(curX, node.height)); - } else { - curX += offset; - endPoints.add(new Point(node.width, node.height)); - } - - node.outOffsets.put(pos - minX, curX); - curX += offset; - node.bottomYOffset += offset; - - endPoints.add(new Point(pos, node.height)); - endPoints.add(new Point(pos, oldNodeHeight)); - for (LayoutEdge e : reversedPreds) { - reversedLinkEndPoints.put(e.link, endPoints); - } - } - - if (minX < 0) { - for (LayoutEdge e : node.preds) { - e.relativeTo -= minX; - } - - for (LayoutEdge e : node.succs) { - e.relativeFrom -= minX; - } - - node.xOffset = -minX; - node.width -= minX; - } + node.computeReversedLinkPoints(false); } /** @@ -424,8 +303,6 @@ public void updateLayout(Set vertices, Set lin currentVertices = vertices; currentLinks = links; reversedLinks = new HashSet<>(); - reversedLinkStartPoints = new HashMap<>(); - reversedLinkEndPoints = new HashMap<>(); vertexActions = new LinkedList<>(); linkActions = new LinkedList<>(); vertexToAction = new HashMap<>(); @@ -436,12 +313,12 @@ public void updateLayout(Set vertices, Set lin // If the layout is too messy it should be redrawn using the static algorithm, // currently HierarchicalLayoutManager manager.doLayout(new LayoutGraph(links, vertices)); - nodes = manager.getNodes(); + nodes = new ArrayList<>(manager.getNodes()); shouldRedrawLayout = false; } else { generateActions(); - new BuildDatastructure().run(); + new BuildDatastructures().run(); findInitialReversedLinks(); @@ -477,7 +354,7 @@ private void run() { } } - private class BuildDatastructure { + private class BuildDatastructures { // In case there are changes in the node size, its layer must be updated Set layersToUpdate = new HashSet<>(); @@ -488,53 +365,51 @@ private class BuildDatastructure { */ private void updateNodeObjects() { for (LayoutNode node : nodes) { - if (node.vertex != null) { + if (node.getVertex() != null) { for (Vertex vertex : currentVertices) { - if (vertex.equals(node.vertex)) { + if (vertex.equals(node.getVertex())) { Dimension size = vertex.getSize(); - if (node.width < (int) size.getWidth()) { - layersToUpdate.add(node.layer); + if (node.getOuterWidth() < (int) size.getWidth()) { + layersToUpdate.add(node.getLayer()); } - node.width = (int) size.getWidth(); - node.height = (int) size.getHeight(); - node.vertex = vertex; + node.initSize(); + node.setVertex(vertex); } } - vertexToLayoutNode.put(node.vertex, node); + vertexToLayoutNode.put(node.getVertex(), node); } else { - node.height = DUMMY_HEIGHT; - node.width = DUMMY_WIDTH; + node.initSize(); } - for (LayoutEdge edge : node.preds) { - if (edge.link != null) { + for (LayoutEdge edge : node.getPredecessors()) { + if (edge.getLink() != null) { for (Link link : currentLinks) { - if (link.equals(edge.link)) { - edge.link = link; - if (link.getTo().getVertex().equals(edge.from.vertex)) { + if (link.equals(edge.getLink())) { + edge.setLink(link); + if (link.getTo().getVertex().equals(edge.getFrom().getVertex())) { // reversed link - edge.relativeFrom = link.getTo().getRelativePosition().x; - edge.relativeTo = link.getFrom().getRelativePosition().x; + edge.setRelativeFromX(link.getTo().getRelativePosition().x); + edge.setRelativeToX(link.getFrom().getRelativePosition().x); } else { - edge.relativeFrom = link.getFrom().getRelativePosition().x; - edge.relativeTo = link.getTo().getRelativePosition().x; + edge.setRelativeFromX(link.getFrom().getRelativePosition().x); + edge.setRelativeToX(link.getTo().getRelativePosition().x); } break; } } } } - for (LayoutEdge edge : node.succs) { - if (edge.link != null) { + for (LayoutEdge edge : node.getSuccessors()) { + if (edge.getLink() != null) { for (Link link : currentLinks) { - if (link.equals(edge.link)) { - edge.link = link; - if (link.getTo().getVertex().equals(edge.from.vertex)) { + if (link.equals(edge.getLink())) { + edge.setLink(link); + if (link.getTo().getVertex().equals(edge.getFrom().getVertex())) { // reversed link - edge.relativeFrom = link.getTo().getRelativePosition().x; - edge.relativeTo = link.getFrom().getRelativePosition().x; + edge.setRelativeFromX(link.getTo().getRelativePosition().x); + edge.setRelativeToX(link.getFrom().getRelativePosition().x); } else { - edge.relativeFrom = link.getFrom().getRelativePosition().x; - edge.relativeTo = link.getTo().getRelativePosition().x; + edge.setRelativeFromX(link.getFrom().getRelativePosition().x); + edge.setRelativeToX(link.getTo().getRelativePosition().x); } break; } @@ -550,14 +425,14 @@ private void updateNodeObjects() { private void storeNodeLayers() { layers = new HashMap<>(); for (LayoutNode node : nodes) { - if (!layers.containsKey(node.layer)) { - layers.put(node.layer, new ArrayList<>()); + if (!layers.containsKey(node.getLayer())) { + layers.put(node.getLayer(), new LayoutLayer()); } - layers.get(node.layer).add(node); + layers.get(node.getLayer()).add(node); } for (int i = 0; i < layers.keySet().size(); i++) { if (!layers.containsKey(i)) { - layers.put(i, new ArrayList<>()); + layers.put(i, new LayoutLayer()); } } } @@ -584,7 +459,7 @@ private int optimalPosition(LayoutNode node, int layer) { assert layers.containsKey(layer); List layerNodes = layers.get(layer); - layerNodes.sort(HierarchicalLayoutManager.nodePositionComparator); + layerNodes.sort(Comparator.comparingInt(LayoutNode::getPos)); int edgeCrossings = Integer.MAX_VALUE; int optimalPos = -1; @@ -592,9 +467,9 @@ private int optimalPosition(LayoutNode node, int layer) { for (int i = 0; i < layerNodes.size() + 1; i++) { int xCoord; if (i == 0) { - xCoord = layerNodes.get(i).x - node.width - 1; + xCoord = layerNodes.get(i).getX() - node.getOuterWidth() - 1; } else { - xCoord = layerNodes.get(i - 1).x + layerNodes.get(i - 1).width + 1; + xCoord = layerNodes.get(i - 1).getX() + layerNodes.get(i - 1).getOuterWidth() + 1; } int currentCrossings = 0; @@ -602,28 +477,28 @@ private int optimalPosition(LayoutNode node, int layer) { if (layers.containsKey(layer - 1)) { List predNodes = layers.get(layer - 1); // For each link with an end point in vertex, check how many edges cross it - for (LayoutEdge edge : node.preds) { - if (edge.from.layer == layer - 1) { - int fromNodeXCoord = edge.from.x; - if (edge.from.vertex != null) { - fromNodeXCoord += edge.relativeFrom; + for (LayoutEdge edge : node.getPredecessors()) { + if (edge.getFrom().getLayer() == layer - 1) { + int fromNodeXCoord = edge.getFrom().getX(); + if (edge.getFrom().getVertex() != null) { + fromNodeXCoord += edge.getRelativeFromX(); } int toNodeXCoord = xCoord; - if (node.vertex != null) { - toNodeXCoord += edge.relativeTo; + if (node.getVertex() != null) { + toNodeXCoord += edge.getRelativeToX(); } for (LayoutNode n : predNodes) { - for (LayoutEdge e : n.succs) { - if (e.to == null) { + for (LayoutEdge e : n.getSuccessors()) { + if (e.getTo() == null) { continue; } - int compFromXCoord = e.from.x; - if (e.from.vertex != null) { - compFromXCoord += e.relativeFrom; + int compFromXCoord = e.getFrom().getX(); + if (e.getFrom().getVertex() != null) { + compFromXCoord += e.getRelativeFromX(); } - int compToXCoord = e.to.x; - if (e.to.vertex != null) { - compToXCoord += e.relativeTo; + int compToXCoord = e.getTo().getX(); + if (e.getTo().getVertex() != null) { + compToXCoord += e.getRelativeToX(); } if ((fromNodeXCoord > compFromXCoord && toNodeXCoord < compToXCoord) || (fromNodeXCoord < compFromXCoord @@ -639,28 +514,28 @@ private int optimalPosition(LayoutNode node, int layer) { if (layers.containsKey(layer + 1)) { List succsNodes = layers.get(layer + 1); // For each link with an end point in vertex, check how many edges cross it - for (LayoutEdge edge : node.succs) { - if (edge.to.layer == layer + 1) { - int toNodeXCoord = edge.to.x; - if (edge.to.vertex != null) { - toNodeXCoord += edge.relativeTo; + for (LayoutEdge edge : node.getSuccessors()) { + if (edge.getTo().getLayer() == layer + 1) { + int toNodeXCoord = edge.getTo().getX(); + if (edge.getTo().getVertex() != null) { + toNodeXCoord += edge.getRelativeToX(); } int fromNodeXCoord = xCoord; - if (node.vertex != null) { - fromNodeXCoord += edge.relativeFrom; + if (node.getVertex() != null) { + fromNodeXCoord += edge.getRelativeFromX(); } for (LayoutNode n : succsNodes) { - for (LayoutEdge e : n.preds) { - if (e.from == null) { + for (LayoutEdge e : n.getPredecessors()) { + if (e.getFrom() == null) { continue; } - int compFromXCoord = e.from.x; - if (e.from.vertex != null) { - compFromXCoord += e.relativeFrom; + int compFromXCoord = e.getFrom().getX(); + if (e.getFrom().getVertex() != null) { + compFromXCoord += e.getRelativeFromX(); } - int compToXCoord = e.to.x; - if (e.to.vertex != null) { - compToXCoord += e.relativeTo; + int compToXCoord = e.getTo().getX(); + if (e.getTo().getVertex() != null) { + compToXCoord += e.getRelativeToX(); } if ((fromNodeXCoord > compFromXCoord && toNodeXCoord < compToXCoord) || (fromNodeXCoord < compFromXCoord @@ -688,18 +563,18 @@ private int optimalPosition(LayoutNode node, int layer) { private void insertNode(LayoutNode node, int layer) { assert layers.containsKey(layer) || layer == 0; - node.layer = layer; - List layerNodes = layers.getOrDefault(layer, new ArrayList()); + node.setLayer(layer); + LayoutLayer layerNodes = layers.getOrDefault(layer, new LayoutLayer()); if (layerNodes.isEmpty()) { - node.pos = 0; + node.setPos(0); } else { - node.pos = optimalPosition(node, layer); + node.setPos(optimalPosition(node, layer)); } for (LayoutNode n : layerNodes) { - if (n.pos >= node.pos) { - n.pos += 1; + if (n.getPos() >= node.getPos()) { + n.setPos(n.getPos() + 1); } } layerNodes.add(node); @@ -708,18 +583,18 @@ private void insertNode(LayoutNode node, int layer) { if (!nodes.contains(node)) { nodes.add(node); } - if (node.vertex != null) { - vertexToLayoutNode.put(node.vertex, node); + if (node.getVertex() != null) { + vertexToLayoutNode.put(node.getVertex(), node); } adjustXCoordinates(layer); } private void processSingleEdge(LayoutEdge e) { - LayoutNode n = e.to; - if (e.to.layer - 1 > e.from.layer) { + LayoutNode n = e.getTo(); + if (e.getTo().getLayer() - 1 > e.getFrom().getLayer()) { LayoutEdge last = e; - for (int i = n.layer - 1; i > last.from.layer; i--) { + for (int i = n.getLayer() - 1; i > last.getFrom().getLayer(); i--) { last = addBetween(last, i); } } @@ -727,34 +602,26 @@ private void processSingleEdge(LayoutEdge e) { private LayoutEdge addBetween(LayoutEdge e, int layer) { LayoutNode n = new LayoutNode(); - n.width = DUMMY_WIDTH; - n.height = DUMMY_HEIGHT; - n.succs.add(e); - LayoutEdge result = new LayoutEdge(); - result.vip = e.vip; - n.preds.add(result); - result.to = n; - result.relativeTo = n.width / 2; - result.from = e.from; - result.relativeFrom = e.relativeFrom; - result.link = e.link; - e.relativeFrom = n.width / 2; - e.from.succs.remove(e); - e.from.succs.add(result); - e.from = n; + n.addSuccessor(e); + LayoutEdge result = new LayoutEdge(e.getFrom(), n, e.getRelativeFromX(), n.getOuterWidth() / 2, e.getLink()); + n.addPredecessor(result); + e.setRelativeFromX(n.getOuterWidth() / 2); + e.getFrom().removeSuccessor(e); + e.getFrom().addSuccessor(result); + e.setFrom(n); insertNode(n, layer); return result; } private void insertDummyNodes(LayoutEdge edge) { - LayoutNode from = edge.from; - LayoutNode to = edge.to; + LayoutNode from = edge.getFrom(); + LayoutNode to = edge.getTo(); boolean hasEdgeFromSamePort = false; - LayoutEdge edgeFromSamePort = new LayoutEdge(); + LayoutEdge edgeFromSamePort = null; - for (LayoutEdge e : edge.from.succs) { - if (e.relativeFrom == edge.relativeFrom && e.to.vertex == null) { + for (LayoutEdge e : edge.getFrom().getSuccessors()) { + if (e.getRelativeFromX() == edge.getRelativeFromX() && e.getTo().getVertex() == null) { edgeFromSamePort = e; hasEdgeFromSamePort = true; break; @@ -766,16 +633,16 @@ private void insertDummyNodes(LayoutEdge edge) { } else { LayoutEdge curEdge = edgeFromSamePort; boolean newEdge = true; - while (curEdge.to.layer < to.layer - 1 && curEdge.to.vertex == null && newEdge) { + while (curEdge.getTo().getLayer() < to.getLayer() - 1 && curEdge.getTo().getVertex() == null && newEdge) { // Traverse down the chain of dummy nodes linking together the edges originating // from the same port newEdge = false; - if (curEdge.to.succs.size() == 1) { - curEdge = curEdge.to.succs.get(0); + if (curEdge.getTo().getSuccessors().size() == 1) { + curEdge = curEdge.getTo().getSuccessors().get(0); newEdge = true; } else { - for (LayoutEdge e : curEdge.to.succs) { - if (e.to.vertex == null) { + for (LayoutEdge e : curEdge.getTo().getSuccessors()) { + if (e.getTo().getVertex() == null) { curEdge = e; newEdge = true; break; @@ -785,27 +652,27 @@ private void insertDummyNodes(LayoutEdge edge) { } LayoutNode prevDummy; - if (curEdge.to.vertex != null) { - prevDummy = curEdge.from; + if (curEdge.getTo().getVertex() != null) { + prevDummy = curEdge.getFrom(); } else { - prevDummy = curEdge.to; + prevDummy = curEdge.getTo(); } - edge.from = prevDummy; - edge.relativeFrom = prevDummy.width / 2; - from.succs.remove(edge); - prevDummy.succs.add(edge); + edge.setFrom(prevDummy); + edge.setRelativeFromX(prevDummy.getOuterWidth() / 2); + from.removeSuccessor(edge); + prevDummy.addSuccessor(edge); processSingleEdge(edge); } } private boolean canMoveNodeUp(LayoutNode node) { - if (node.layer == 0) { + if (node.getLayer() == 0) { return false; } - int newLayer = node.layer - 1; - for (LayoutEdge e : node.preds) { - if (e.from.vertex != null && e.from.layer == newLayer) { + int newLayer = node.getLayer() - 1; + for (LayoutEdge e : node.getPredecessors()) { + if (e.getFrom().getVertex() != null && e.getFrom().getLayer() == newLayer) { return false; } } @@ -813,12 +680,12 @@ private boolean canMoveNodeUp(LayoutNode node) { } private boolean canMoveNodeDown(LayoutNode node) { - if (node.layer == layers.keySet().size() - 1) { + if (node.getLayer() == layers.keySet().size() - 1) { return false; } - int newLayer = node.layer + 1; - for (LayoutEdge e : node.succs) { - if (e.to.vertex != null && e.to.layer == newLayer) { + int newLayer = node.getLayer() + 1; + for (LayoutEdge e : node.getSuccessors()) { + if (e.getTo().getVertex() != null && e.getTo().getLayer() == newLayer) { return false; } } @@ -828,23 +695,23 @@ private boolean canMoveNodeDown(LayoutNode node) { private void moveNodeUp(LayoutNode node) { assert canMoveNodeUp(node); - List previousPredEdges = List.copyOf(node.preds); + List previousPredEdges = List.copyOf(node.getPredecessors()); for (LayoutEdge edge : previousPredEdges) { - LayoutNode predNode = edge.from; - assert predNode.vertex == null; - for (LayoutEdge e : predNode.preds) { - e.to = edge.to; - e.relativeTo = edge.relativeTo; - node.preds.add(e); - node.preds.remove(edge); + LayoutNode predNode = edge.getFrom(); + assert predNode.getVertex() == null; + for (LayoutEdge e : predNode.getPredecessors()) { + e.setTo(edge.getTo()); + e.setRelativeToX(edge.getRelativeToX()); + node.addPredecessor(e); + node.removePredecessor(edge); } removeNodeWithoutRemovingLayer(predNode); } removeNodeWithoutRemovingLayer(node); - insertNode(node, node.layer - 1); + insertNode(node, node.getLayer() - 1); - for (LayoutEdge edge : List.copyOf(node.succs)) { + for (LayoutEdge edge : List.copyOf(node.getSuccessors())) { processSingleEdge(edge); } } @@ -852,23 +719,23 @@ private void moveNodeUp(LayoutNode node) { private void moveNodeDown(LayoutNode node) { assert canMoveNodeDown(node); - List previousSuccEdges = List.copyOf(node.succs); + List previousSuccEdges = List.copyOf(node.getSuccessors()); for (LayoutEdge edge : previousSuccEdges) { - LayoutNode succNode = edge.to; - assert succNode.vertex == null; - for (LayoutEdge e : succNode.succs) { - e.from = edge.from; - e.relativeFrom = edge.relativeFrom; - node.succs.add(e); - node.succs.remove(edge); + LayoutNode succNode = edge.getTo(); + assert succNode.getVertex() == null; + for (LayoutEdge e : succNode.getSuccessors()) { + e.setFrom(edge.getFrom()); + e.setRelativeFromX(edge.getRelativeFromX()); + node.addSuccessor(e); + node.removeSuccessor(edge); } removeNodeWithoutRemovingLayer(succNode); } removeNodeWithoutRemovingLayer(node); - insertNode(node, node.layer + 1); + insertNode(node, node.getLayer() + 1); - for (LayoutEdge edge : List.copyOf(node.preds)) { + for (LayoutEdge edge : List.copyOf(node.getPredecessors())) { processSingleEdge(edge); } } @@ -890,25 +757,25 @@ private void handleNeighborNodesOnSameLayer(LayoutNode from, LayoutNode to) { * remaining layers numbers */ private void expandNewLayerBeneath(LayoutNode node) { - int layer = node.layer + 1; + int layer = node.getLayer() + 1; // Move all necessary layers down one step for (int i = layers.size() - 1; i >= layer; i--) { - List list = layers.get(i); + LayoutLayer list = layers.get(i); for (LayoutNode n : list) { - n.layer = i + 1; + n.setLayer(i + 1); } layers.remove(i); layers.put(i + 1, list); } // Create new empty layer - List l = new ArrayList<>(); + LayoutLayer l = new LayoutLayer(); layers.put(layer, l); assert layers.get(layer).isEmpty(); for (LayoutNode n : nodes) { - assert n.layer != layer; + assert n.getLayer() != layer; } // Add dummy nodes for edges going across new layer. One for each port on the @@ -917,35 +784,28 @@ private void expandNewLayerBeneath(LayoutNode node) { for (LayoutNode n : predLayer) { HashMap> portHashes = new HashMap<>(); - for (LayoutEdge e : n.succs) { - if (!portHashes.containsKey(e.relativeFrom)) { - portHashes.put(e.relativeFrom, new ArrayList<>()); + for (LayoutEdge e : n.getSuccessors()) { + if (!portHashes.containsKey(e.getRelativeFromX())) { + portHashes.put(e.getRelativeFromX(), new ArrayList<>()); } - portHashes.get(e.relativeFrom).add(e); + portHashes.get(e.getRelativeFromX()).add(e); } for (Integer i : portHashes.keySet()) { List edges = portHashes.get(i); LayoutNode dummy = new LayoutNode(); - dummy.width = DUMMY_WIDTH; - dummy.height = DUMMY_HEIGHT; - - LayoutEdge newEdge = new LayoutEdge(); - newEdge.from = n; - newEdge.relativeFrom = i; - newEdge.to = dummy; - newEdge.relativeTo = dummy.width / 2; - newEdge.link = edges.get(0).link; // issue? - n.succs.add(newEdge); - dummy.preds.add(newEdge); + + LayoutEdge newEdge = new LayoutEdge(n, dummy, i, dummy.getOuterWidth() / 2, edges.get(0).getLink()); + n.addSuccessor(newEdge); + dummy.addPredecessor(newEdge); for (LayoutEdge e : edges) { - e.from = dummy; - e.relativeFrom = dummy.width / 2; - n.succs.remove(e); - dummy.succs.add(e); - assert e.to.layer == layer + 1; + e.setFrom(dummy); + e.setRelativeFromX(dummy.getOuterWidth() / 2); + n.removeSuccessor(e); + dummy.addSuccessor(e); + assert e.getTo().getLayer() == layer + 1; } insertNode(dummy, layer); @@ -955,7 +815,7 @@ private void expandNewLayerBeneath(LayoutNode node) { // Move node to new layer moveNodeDown(node); assert layers.get(layer).contains(node); - assert node.layer == layer; + assert node.getLayer() == layer; } private void applyAddLinkAction(Link l) { @@ -968,18 +828,12 @@ private void applyAddLinkAction(Link l) { return; } - if (toNode.layer == fromNode.layer) { + if (toNode.getLayer() == fromNode.getLayer()) { handleNeighborNodesOnSameLayer(fromNode, toNode); } - LayoutEdge edge = new LayoutEdge(); - edge.link = l; - edge.from = fromNode; - edge.relativeFrom = l.getFrom().getRelativePosition().x; - edge.to = toNode; - edge.relativeTo = l.getTo().getRelativePosition().x; - - boolean reversedLink = fromNode.layer > toNode.layer; + LayoutEdge edge = new LayoutEdge(fromNode, toNode, l.getFrom().getRelativePosition().x, l.getTo().getRelativePosition().x, l); + boolean reversedLink = fromNode.getLayer() > toNode.getLayer(); if (reversedLink) { // Reversed link reversedLinks.add(l); @@ -988,23 +842,23 @@ private void applyAddLinkAction(Link l) { fromNode = toNode; toNode = temp; - int oldRelativeFrom = edge.relativeFrom; - int oldRelativeTo = edge.relativeTo; + int oldRelativeFrom = edge.getRelativeFromX(); + int oldRelativeTo = edge.getRelativeToX(); - edge.from = fromNode; - edge.to = toNode; - edge.relativeFrom = oldRelativeTo; - edge.relativeTo = oldRelativeFrom; + edge.setFrom(fromNode); + edge.setTo(toNode); + edge.setRelativeFromX(oldRelativeTo); + edge.setRelativeToX(oldRelativeFrom); } - fromNode.succs.add(edge); - toNode.preds.add(edge); + fromNode.addSuccessor(edge); + toNode.addPredecessor(edge); if (reversedLink) { updateReversedLinkPositions(l); } - if (fromNode.layer != toNode.layer - 1) { + if (fromNode.getLayer() != toNode.getLayer() - 1) { // Edge span multiple layers - must insert dummy nodes insertDummyNodes(edge); } @@ -1038,20 +892,20 @@ private int optimalLayer(Vertex vertex, List links) { LayoutNode fromNode = vertexToLayoutNode.get(link.getFrom().getVertex()); LayoutNode toNode = vertexToLayoutNode.get(link.getTo().getVertex()); if (link.getTo().getVertex().equals(vertex) && fromNode != null) { - if (fromNode.layer > i) { + if (fromNode.getLayer() > i) { curReversedEdges += 1; - } else if (fromNode.layer == i) { + } else if (fromNode.getLayer() == i) { curNeighborsOnSameLayer += 1; } - curTotalEdgeLength += Math.abs(fromNode.layer - i); + curTotalEdgeLength += Math.abs(fromNode.getLayer() - i); } if (link.getFrom().getVertex().equals(vertex) && toNode != null) { - if (toNode.layer < i) { + if (toNode.getLayer() < i) { curReversedEdges += 1; - } else if (toNode.layer == i) { + } else if (toNode.getLayer() == i) { curNeighborsOnSameLayer += 1; } - curTotalEdgeLength += Math.abs(i - toNode.layer); + curTotalEdgeLength += Math.abs(i - toNode.getLayer()); } } @@ -1072,12 +926,7 @@ private int optimalLayer(Vertex vertex, List links) { } private void applyAddVertexAction(VertexAction action) { - LayoutNode node = new LayoutNode(); - Dimension size = action.vertex.getSize(); - node.width = (int) size.getWidth(); - node.height = (int) size.getHeight(); - node.vertex = action.vertex; - + LayoutNode node = new LayoutNode(action.vertex); List links = new ArrayList<>(); for (LinkAction a : action.linkActions) { links.add(a.link); @@ -1087,26 +936,19 @@ private void applyAddVertexAction(VertexAction action) { // Temporarily add the links so that the node insertion accounts for edge // crossings for (Link l : links) { - LayoutEdge e = new LayoutEdge(); if (l.getTo().getVertex().equals(action.vertex) && nodes.contains(vertexToLayoutNode.get(l.getFrom().getVertex()))) { - e.to = node; - e.from = vertexToLayoutNode.get(l.getFrom().getVertex()); - e.relativeFrom = l.getFrom().getRelativePosition().x; - e.relativeTo = l.getTo().getRelativePosition().x; - node.preds.add(e); + LayoutEdge e = new LayoutEdge(vertexToLayoutNode.get(l.getFrom().getVertex()), node, l.getFrom().getRelativePosition().x, l.getTo().getRelativePosition().x, null); + node.addPredecessor(e); } else if (l.getFrom().getVertex().equals(action.vertex) && nodes.contains(vertexToLayoutNode.get(l.getTo().getVertex()))) { - e.from = node; - e.to = vertexToLayoutNode.get(l.getTo().getVertex()); - e.relativeFrom = l.getFrom().getRelativePosition().x; - e.relativeTo = l.getTo().getRelativePosition().x; - node.succs.add(e); + LayoutEdge e = new LayoutEdge(node, vertexToLayoutNode.get(l.getTo().getVertex()), l.getFrom().getRelativePosition().x, l.getTo().getRelativePosition().x, null); + node.addSuccessor(e); } } insertNode(node, layer); - node.succs.clear(); - node.preds.clear(); + node.clearSuccessors(); + node.clearPredecessors(); // Add associated edges for (LinkAction a : action.linkActions) { @@ -1122,44 +964,39 @@ private void applyRemoveLinkAction(Link l) { LayoutNode toNode = vertexToLayoutNode.get(to); LayoutNode fromNode = vertexToLayoutNode.get(from); - if (toNode.layer < fromNode.layer) { + if (toNode.getLayer() < fromNode.getLayer()) { // Reversed edge - LayoutNode temp = toNode; toNode = fromNode; - fromNode = temp; - reversedLinks.remove(l); - reversedLinkEndPoints.remove(l); - reversedLinkStartPoints.remove(l); } // Remove preds-edges bottom up, starting at "to" node // Cannot start from "from" node since there might be joint edges - List toNodePredsEdges = List.copyOf(toNode.preds); + List toNodePredsEdges = List.copyOf(toNode.getPredecessors()); for (LayoutEdge edge : toNodePredsEdges) { - LayoutNode n = edge.from; + LayoutNode n = edge.getFrom(); LayoutEdge edgeToRemove; - if (edge.link != null && edge.link.equals(l)) { - toNode.preds.remove(edge); + if (edge.getLink() != null && edge.getLink().equals(l)) { + toNode.removePredecessor(edge); edgeToRemove = edge; } else { // Wrong edge, look at next continue; } - if (n.vertex != null && n.vertex.equals(from)) { + if (n.getVertex() != null && n.getVertex().equals(from)) { // No dummy nodes inbetween 'from' and 'to' vertex - n.succs.remove(edgeToRemove); + n.removeSuccessor(edgeToRemove); break; } else { // Must remove edges between dummy nodes boolean found = true; LayoutNode prev = toNode; - while (n.vertex == null && found) { + while (n.getVertex() == null && found) { found = false; - if (n.succs.size() <= 1 && n.preds.size() <= 1) { + if (n.getSuccessors().size() <= 1 && n.getPredecessors().size() <= 1) { // Dummy node used only for this link, remove if not already removed if (nodes.contains(n)) { removeNode(n); @@ -1169,17 +1006,17 @@ private void applyRemoveLinkAction(Link l) { break; } - if (n.preds.size() == 1) { - n.succs.remove(edgeToRemove); + if (n.getPredecessors().size() == 1) { + n.removeSuccessor(edgeToRemove); prev = n; - edgeToRemove = n.preds.get(0); - n = edgeToRemove.from; + edgeToRemove = n.getPredecessors().get(0); + n = edgeToRemove.getFrom(); found = true; } } - n.succs.remove(edgeToRemove); - prev.preds.remove(edgeToRemove); + n.removeSuccessor(edgeToRemove); + prev.removePredecessor(edgeToRemove); } break; } @@ -1198,19 +1035,19 @@ private void removeNode(LayoutNode node) { if (!nodes.contains(node)) { return; } - int layer = node.layer; - int pos = node.pos; - List remainingLayerNodes = layers.get(layer); + int layer = node.getLayer(); + int pos = node.getPos(); + LayoutLayer remainingLayerNodes = layers.get(layer); assert remainingLayerNodes.contains(node); remainingLayerNodes.remove(node); // Update position of remaining nodes on the same layer boolean onlyDummiesLeft = true; for (LayoutNode n : remainingLayerNodes) { - if (n.pos > pos) { - n.pos -= 1; + if (n.getPos() > pos) { + n.setPos(n.getPos() - 1); } - if (n.vertex != null || n.preds.size() > 1) { + if (n.getVertex() != null || n.getPredecessors().size() > 1) { onlyDummiesLeft = false; } } @@ -1218,22 +1055,22 @@ private void removeNode(LayoutNode node) { if (onlyDummiesLeft && shouldRemoveEmptyLayers) { layers.remove(layer); for (int i = layer + 1; i <= layers.size(); i++) { - List list = layers.get(i); + LayoutLayer list = layers.get(i); layers.remove(i); layers.put(i - 1, list); for (LayoutNode n : list) { - n.layer -= 1; + n.setLayer(n.getLayer() - 1); } } for (LayoutNode n : remainingLayerNodes) { - if (n.preds.size() == 1) { - LayoutEdge predEdge = n.preds.get(0); - LayoutNode fromNode = predEdge.from; - fromNode.succs.remove(predEdge); - for (LayoutEdge e : n.succs) { - e.from = fromNode; - e.relativeFrom = predEdge.relativeFrom; - fromNode.succs.add(e); + if (n.getPredecessors().size() == 1) { + LayoutEdge predEdge = n.getPredecessors().get(0); + LayoutNode fromNode = predEdge.getFrom(); + fromNode.removeSuccessor(predEdge); + for (LayoutEdge e : n.getSuccessors()) { + e.setFrom(fromNode); + e.setRelativeFromX(predEdge.getRelativeFromX()); + fromNode.addSuccessor(e); } } nodes.remove(n); @@ -1292,12 +1129,12 @@ void run() { Set layoutedLinks = new HashSet<>(); Set layoutedNodes = new HashSet<>(); for (LayoutNode n : nodes) { - for (LayoutEdge e : n.preds) { - if (e.link != null) { - layoutedLinks.add(e.link); + for (LayoutEdge e : n.getPredecessors()) { + if (e.getLink() != null) { + layoutedLinks.add(e.getLink()); } } - if (n.vertex != null) { + if (n.getVertex() != null) { layoutedNodes.add(n); } } @@ -1320,135 +1157,151 @@ void run() { private class AssignYCoordinates { void run() { + int currentY = 0; + for (int i = 0; i < layers.size(); i++) { + LayoutLayer layer = layers.get(i); + layer.setTop(currentY); - // Reset all values before assigning y-coordinates - for (LayoutNode n : nodes) { - if (n.vertex != null) { - updateNodeWithReversedEdges(n); - } else { - n.height = DUMMY_HEIGHT; - } - n.y = 0; + // Calculate the maximum layer height and set it for the layer + int maxLayerHeight = layer.calculateMaxLayerHeight(); + layer.setHeight(maxLayerHeight); + + // Center nodes vertically within the layer + layer.centerNodesVertically(); + + // Update currentY to account for the padded bottom of this layer + currentY += layer.calculatePaddedHeight(); } + } + } - int curY = 0; + private class WriteResult { - for (int i = 0; i < layers.size(); i++) { - List layer = layers.get(i); - int maxHeight = 0; - int baseLine = 0; - int bottomBaseLine = 0; - for (LayoutNode n : layer) { - maxHeight = Math.max(maxHeight, n.height - n.yOffset - n.bottomYOffset); - baseLine = Math.max(baseLine, n.yOffset); - bottomBaseLine = Math.max(bottomBaseLine, n.bottomYOffset); - } + private HashMap> computeLinkPositions() { + HashMap> linkToSplitEndPoints = new HashMap<>(); + HashMap> linkPositions = new HashMap<>(); - int maxXOffset = 0; - for (LayoutNode n : layer) { - if (n.vertex == null) { - // Dummy node - n.y = curY; - n.height = maxHeight + baseLine + bottomBaseLine; + for (LayoutNode layoutNode : nodes) { + if (layoutNode.isDummy()) continue; + for (LayoutEdge predEdge : layoutNode.getPredecessors()) { + LayoutNode fromNode = predEdge.getFrom(); + LayoutNode toNode = predEdge.getTo(); + + ArrayList linkPoints = new ArrayList<>(); + // input edge stub + linkPoints.add(new Point(predEdge.getEndX(), predEdge.getEndY())); + linkPoints.add(new Point(predEdge.getEndX(), layers.get(toNode.getLayer()).getTop() - LAYER_OFFSET)); + + LayoutEdge curEdge = predEdge; + while (fromNode.isDummy() && fromNode.hasPredecessors()) { + linkPoints.add(new Point(fromNode.getCenterX(), layers.get(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); + linkPoints.add(new Point(fromNode.getCenterX(), layers.get(fromNode.getLayer()).getTop() - LAYER_OFFSET)); + curEdge = fromNode.getPredecessors().get(0); + fromNode = curEdge.getFrom(); + } + linkPoints.add(new Point(curEdge.getStartX(), layers.get(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); + // output edge stub + linkPoints.add(new Point(curEdge.getStartX(), curEdge.getStartY())); + + if (predEdge.isReversed()) { + for (Point relativeEnd : toNode.getReversedLinkEndPoints().get(predEdge.getLink())) { + Point endPoint = new Point(toNode.getLeft() + relativeEnd.x, toNode.getTop() + relativeEnd.y); + linkPoints.add(0, endPoint); + } + if (!fromNode.isDummy()) { + if (fromNode.getReversedLinkStartPoints().containsKey(predEdge.getLink())) { + for (Point relativeStart : fromNode.getReversedLinkStartPoints().get(predEdge.getLink())) { + Point startPoint = new Point(fromNode.getLeft() + relativeStart.x, fromNode.getTop() + relativeStart.y); + linkPoints.add(startPoint); + } + } + } } else { - n.y = curY + baseLine + (maxHeight - (n.height - n.yOffset - n.bottomYOffset)) / 2 - n.yOffset; + Collections.reverse(linkPoints); } - for (LayoutEdge e : n.succs) { - int curXOffset = Math.abs(n.x - e.to.x); - maxXOffset = Math.max(curXOffset, maxXOffset); + if (fromNode.isDummy()) { + if (predEdge.isReversed()) { + Collections.reverse(linkPoints); + } + linkToSplitEndPoints.put(predEdge.getLink(), linkPoints); + + } else { + linkPositions.put(predEdge.getLink(), linkPoints); } } - - curY += maxHeight + baseLine + bottomBaseLine; - curY += LAYER_OFFSET + ((int) (Math.sqrt(maxXOffset) * 1.5)); } - } - } - - private class WriteResult { - private List edgePoints(LayoutEdge e) { - ArrayList points = new ArrayList<>(); + for (LayoutNode layoutNode : nodes) { + if (layoutNode.isDummy()) continue; + for (LayoutEdge succEdge : layoutNode.getSuccessors()) { + if (succEdge.getLink() == null) continue; - Point p = new Point(e.to.x + e.relativeTo, - e.to.y + e.to.yOffset + e.link.getTo().getRelativePosition().y); - points.add(p); - if (e.to.inOffsets.containsKey(e.relativeTo)) { - points.add(new Point(p.x, - p.y + e.to.inOffsets.get(e.relativeTo) + e.link.getTo().getRelativePosition().y)); - } + LayoutNode fromNode = succEdge.getFrom(); + LayoutNode toNode = succEdge.getTo(); - LayoutNode cur = e.from; - LayoutEdge curEdge = e; - while (cur.vertex == null && !cur.preds.isEmpty()) { - 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) { - // On the same vertical line, can remove previous point - points.remove(points.size() - 1); - } - // Top of the dummy node - points.add(new Point(cur.x + cur.width / 2, cur.y + cur.height)); - 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); - } - // Bottom of the dummy node - points.add(new Point(cur.x + cur.width / 2, cur.y)); - assert cur.preds.size() == 1; - curEdge = cur.preds.get(0); - cur = curEdge.from; - } + ArrayList linkPoints = new ArrayList<>(); + linkPoints.add(new Point(succEdge.getStartX(), fromNode.getBottom())); + linkPoints.add(new Point(succEdge.getStartX(), layers.get(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); - p = new Point(cur.x + curEdge.relativeFrom, cur.y + cur.height - cur.bottomYOffset - + (curEdge.link == null ? 0 : curEdge.link.getFrom().getRelativePosition().y)); - if (curEdge.from.outOffsets.containsKey(curEdge.relativeFrom)) { - points.add(new Point(p.x, p.y + curEdge.from.outOffsets.get(curEdge.relativeFrom) - + (curEdge.link == null ? 0 : curEdge.link.getFrom().getRelativePosition().y))); - } - points.add(p); + LayoutEdge curEdge = succEdge; + while (toNode.isDummy() && toNode.hasSuccessors()) { + linkPoints.add(new Point(toNode.getCenterX(), layers.get(toNode.getLayer()).getTop() - LAYER_OFFSET)); + linkPoints.add(new Point(toNode.getCenterX(), layers.get(toNode.getLayer()).getBottom() + LAYER_OFFSET)); + curEdge = toNode.getSuccessors().get(0); + toNode = curEdge.getTo(); + } + linkPoints.add(new Point(curEdge.getEndX(), layers.get(toNode.getLayer()).getTop() - LAYER_OFFSET)); + linkPoints.add(new Point(curEdge.getEndX(), toNode.getTop())); - Collections.reverse(points); + if (succEdge.isReversed()) { + Collections.reverse(linkPoints); - if (reversedLinks.contains(e.link)) { - Collections.reverse(points); + if (fromNode.getReversedLinkStartPoints().containsKey(succEdge.getLink())) { + for (Point relativeStart : fromNode.getReversedLinkStartPoints().get(succEdge.getLink())) { + Point startPoint = new Point(fromNode.getLeft() + relativeStart.x, fromNode.getTop() + relativeStart.y); + linkPoints.add(startPoint); + } + } - assert reversedLinkStartPoints.containsKey(e.link); - for (Point p1 : reversedLinkStartPoints.get(e.link)) { - points.add(new Point(p1.x + cur.x, p1.y + cur.y)); - } + if (!toNode.isDummy()) { + if (toNode.getReversedLinkEndPoints().containsKey(succEdge.getLink())) { + for (Point relativeEnd : toNode.getReversedLinkEndPoints().get(succEdge.getLink())) { + Point endPoint = new Point(toNode.getLeft() + relativeEnd.x, toNode.getTop() + relativeEnd.y); + linkPoints.add(0, endPoint); + } + } + } + } - assert reversedLinkEndPoints.containsKey(e.link); - for (Point p1 : reversedLinkEndPoints.get(e.link)) { - points.add(0, new Point(p1.x + e.to.x, p1.y + e.to.y)); + if (linkToSplitEndPoints.containsKey(succEdge.getLink())) { + if (succEdge.isReversed()) { + Collections.reverse(linkPoints); + } + linkPoints.add(null); + linkPoints.addAll(linkToSplitEndPoints.get(succEdge.getLink())); + if (succEdge.isReversed()) { + Collections.reverse(linkPoints); + } + } + linkPositions.put(succEdge.getLink(), linkPoints); } } - return points; + return linkPositions; } + void run() { HashMap vertexPositions = new HashMap<>(); - HashMap> linkPositions = new HashMap<>(); + HashMap> linkPositions = computeLinkPositions(); - for (LayoutNode n : nodes) { - if (n.vertex != null) { - assert !vertexPositions.containsKey(n.vertex); - vertexPositions.put(n.vertex, new Point(n.x + n.xOffset, n.y + n.yOffset)); - } else { - continue; - } - - // All edges can be drawn from bottom up, the links are stored in the preds list - // of each node - for (LayoutEdge e : n.preds) { - if (e.link != null && !linkPositions.containsKey(e.link)) { - List points = edgePoints(e); - assert !linkPositions.containsKey(e.link); - linkPositions.put(e.link, points); - } + for (LayoutNode n : nodes) { + if (n.getVertex() != null) { + assert !vertexPositions.containsKey(n.getVertex()); + vertexPositions.put(n.getVertex(), new Point(n.getLeft(), n.getTop())); } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/InterClusterConnection.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/InterClusterConnection.java index 4112ac997b8f1..eaa2789b1d937 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/InterClusterConnection.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/InterClusterConnection.java @@ -31,7 +31,6 @@ import java.util.List; /** - * * @author Thomas Wuerthinger */ public class InterClusterConnection implements Link { @@ -62,14 +61,14 @@ public Cluster getToCluster() { return null; } - public void setControlPoints(List p) { - this.intermediatePoints = p; - } - public List getControlPoints() { return intermediatePoints; } + public void setControlPoints(List p) { + this.intermediatePoints = p; + } + @Override public String toString() { return "InterClusterConnection[from=" + getFrom() + ", to=" + getTo() + "]"; diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java index eba424936531b..e0aa527acab57 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java @@ -25,29 +25,31 @@ package com.sun.hotspot.igv.hierarchicallayout; import com.sun.hotspot.igv.layout.Link; -import java.util.Comparator; +/** + * Represents an edge in the layout graph between two nodes (LayoutNode). + * Contains information about the source and target nodes, relative positions, + * and whether the edge has been reversed (used for back edges in hierarchical layouts). + */ public class LayoutEdge { - public static final Comparator LAYOUT_EDGE_LAYER_COMPARATOR = Comparator.comparingInt(e -> e.getTo().getLayer()); - + private Link link; private LayoutNode from; private LayoutNode to; - // Horizontal distance relative to start of 'from'. + // Horizontal distance relative to the start of 'from' node. private int relativeFromX; - // Horizontal distance relative to start of 'to'. + // Horizontal distance relative to the start of 'to' node. private int relativeToX; - private Link link; private boolean isReversed; - public int getStartX() { - return relativeFromX + from.getLeft(); - } - - public int getEndX() { - return relativeToX + to.getLeft(); - } - + /** + * Constructs a LayoutEdge between two nodes with the specified link. + * The relative positions are set to zero by default. + * + * @param from The source LayoutNode. + * @param to The target LayoutNode. + * @param link The Link associated with this edge. + */ public LayoutEdge(LayoutNode from, LayoutNode to, Link link) { this.from = from; this.to = to; @@ -55,16 +57,70 @@ public LayoutEdge(LayoutNode from, LayoutNode to, Link link) { this.isReversed = false; } + /** + * Constructs a LayoutEdge between two nodes with specified relative positions and link. + * + * @param from The source LayoutNode. + * @param to The target LayoutNode. + * @param relativeFromX The horizontal distance relative to the start of 'from' node. + * @param relativeToX The horizontal distance relative to the start of 'to' node. + * @param link The Link associated with this edge. + */ public LayoutEdge(LayoutNode from, LayoutNode to, int relativeFromX, int relativeToX, Link link) { this(from, to, link); this.relativeFromX = relativeFromX; this.relativeToX = relativeToX; } + /** + * Gets the absolute x-coordinate of the starting point of the edge. + * + * @return The x-coordinate of the edge's starting point. + */ + public int getStartX() { + return relativeFromX + from.getLeft(); + } + + /** + * Gets the absolute y-coordinate of the starting point of the edge. + * + * @return The y-coordinate of the edge's starting point. + */ + public int getStartY() { + return from.getBottom(); + } + + /** + * Gets the absolute x-coordinate of the ending point of the edge. + * + * @return The x-coordinate of the edge's ending point. + */ + public int getEndX() { + return relativeToX + to.getLeft(); + } + + /** + * Gets the absolute y-coordinate of the ending point of the edge. + * + * @return The y-coordinate of the edge's ending point. + */ + public int getEndY() { + return to.getTop(); + } + + /** + * Reverses the direction of the edge. + * Marks the edge as reversed, which is used to represent back edges in hierarchical layouts. + */ public void reverse() { isReversed = !isReversed; } + /** + * Checks if the edge is reversed. + * + * @return True if the edge is reversed; false otherwise. + */ public boolean isReversed() { return isReversed; } @@ -74,45 +130,101 @@ public String toString() { return "Edge " + from + ", " + to; } + /** + * Gets the source node of the edge. + * + * @return The source LayoutNode. + */ public LayoutNode getFrom() { return from; } + /** + * Sets the source node of the edge. + * + * @param from The LayoutNode to set as the source. + */ public void setFrom(LayoutNode from) { this.from = from; } + /** + * Gets the target node of the edge. + * + * @return The target LayoutNode. + */ public LayoutNode getTo() { return to; } + /** + * Sets the target node of the edge. + * + * @param to The LayoutNode to set as the target. + */ public void setTo(LayoutNode to) { this.to = to; } + /** + * Gets the absolute x-coordinate of the source node's connection point for this edge. + * + * @return The x-coordinate of the source node's connection point. + */ public int getFromX() { return from.getX() + getRelativeFromX(); } + /** + * Gets the absolute x-coordinate of the target node's connection point for this edge. + * + * @return The x-coordinate of the target node's connection point. + */ public int getToX() { return to.getX() + getRelativeToX(); } + /** + * Gets the relative horizontal position from the source node's left boundary to the edge's starting point. + * + * @return The relative x-coordinate from the source node. + */ public int getRelativeFromX() { return relativeFromX; } + + /** + * Sets the relative horizontal position from the source node's left boundary to the edge's starting point. + * + * @param relativeFromX The relative x-coordinate to set. + */ public void setRelativeFromX(int relativeFromX) { this.relativeFromX = relativeFromX; } + /** + * Gets the relative horizontal position from the target node's left boundary to the edge's ending point. + * + * @return The relative x-coordinate to the target node. + */ public int getRelativeToX() { return relativeToX; } + /** + * Sets the relative horizontal position from the target node's left boundary to the edge's ending point. + * + * @param relativeToX The relative x-coordinate to set. + */ public void setRelativeToX(int relativeToX) { this.relativeToX = relativeToX; } + /** + * Gets the Link associated with this edge. + * + * @return The Link object. + */ public Link getLink() { return link; } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java index e6a072263a243..5dbe1d0177e06 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,18 +23,13 @@ */ package com.sun.hotspot.igv.hierarchicallayout; +import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.NODE_POS_COMPARATOR; import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Port; import com.sun.hotspot.igv.layout.Vertex; import java.util.*; import java.util.stream.Collectors; -import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.NODE_POS_COMPARATOR; - -/** - * - * @author Thomas Wuerthinger - */ public class LayoutGraph { public static final Comparator LINK_COMPARATOR = @@ -43,32 +38,220 @@ public class LayoutGraph { .thenComparingInt(l -> l.getFrom().getRelativePosition().x) .thenComparingInt(l -> l.getTo().getRelativePosition().x); - private final Set links; + // Registered Graph Components: Links, Vertices, and Port Mappings + private final Set links; private final SortedSet vertices; - private final HashMap> inputPorts; - private final HashMap> outputPorts; - private final HashMap> portLinks; + private final LinkedHashMap> inputPorts; + private final LinkedHashMap> outputPorts; + private final LinkedHashMap> portLinks; + // Layout Management: LayoutNodes and LayoutLayers + private final LinkedHashMap layoutNodes; private final List dummyNodes; - private final LinkedHashMap vertexToLayoutNode; + private final List layers; + + /** + * Constructs a new LayoutGraph using the provided collection of links and additional vertices. + * Initializes the graph layout structure with the given links and includes any additional vertices. + * + * @param links The collection of links that represent the edges of the graph. + * @param additionalVertices The collection of additional vertices to be included in the graph. + */ + public LayoutGraph(Collection links, Collection additionalVertices) { + this.links = new HashSet<>(links); + vertices = new TreeSet<>(additionalVertices); + portLinks = new LinkedHashMap<>(links.size()); + inputPorts = new LinkedHashMap<>(links.size()); + outputPorts = new LinkedHashMap<>(links.size()); + + for (Link link : links) { + assert link.getFrom() != null; + assert link.getTo() != null; + Port fromPort = link.getFrom(); + Port toPort = link.getTo(); + Vertex fromVertex = fromPort.getVertex(); + Vertex toVertex = toPort.getVertex(); + + vertices.add(fromVertex); + vertices.add(toVertex); - private List layers; + outputPorts.computeIfAbsent(fromVertex, k -> new HashSet<>()).add(fromPort); + inputPorts.computeIfAbsent(toVertex, k -> new HashSet<>()).add(toPort); + + portLinks.computeIfAbsent(fromPort, k -> new HashSet<>()).add(link); + portLinks.computeIfAbsent(toPort, k -> new HashSet<>()).add(link); + } - public LayoutGraph(Set links) { - this(links, new HashSet<>()); + // cleanup + layoutNodes = new LinkedHashMap<>(); + dummyNodes = new ArrayList<>(); + layers = new ArrayList<>(); + } + + public void clearLayout() { + layoutNodes.clear(); + dummyNodes.clear(); + layers.clear(); } + /** + * Initializes or resets the layout structures by clearing existing nodes, dummy nodes, and layers. + * It then sets up the layout nodes for each vertex and creates layout edges based on the sorted links. + */ + public void initializeLayout() { + // Reset layout structures + clearLayout(); + + // Set up layout nodes for each vertex + for (Vertex vertex : getVertices()) { + createLayoutNode(vertex); + } + + // Set up layout edges in a sorted order for reproducibility + List sortedLinks = new ArrayList<>(links); + sortedLinks.sort(LINK_COMPARATOR); + for (Link link : sortedLinks) { + createLayoutEdge(link); + } + } + + /** + * Initializes the layers of the graph with the specified number of empty layers. + * + * @param layerCount The number of layers to initialize. + */ public void initLayers(int layerCount) { - layers = new ArrayList<>(layerCount); + layers.clear(); for (int i = 0; i < layerCount; i++) { layers.add(new LayoutLayer()); } } + /** + * Removes a link from the graph. + * + * @param link The Link to be removed. + */ + public void removeLink(Link link) { + if (!links.contains(link)) { + return; + } + + Port fromPort = link.getFrom(); + Port toPort = link.getTo(); + Vertex fromVertex = fromPort.getVertex(); + Vertex toVertex = toPort.getVertex(); + + // Remove from links set + links.remove(link); + + // Update portLinks + Set fromPortLinks = portLinks.get(fromPort); + if (fromPortLinks != null) { + fromPortLinks.remove(link); + if (fromPortLinks.isEmpty()) { + portLinks.remove(fromPort); + } + } + + Set toPortLinks = portLinks.get(toPort); + if (toPortLinks != null) { + toPortLinks.remove(link); + if (toPortLinks.isEmpty()) { + portLinks.remove(toPort); + } + } + + // Update inputPorts and outputPorts + Set fromVertexOutputPorts = outputPorts.get(fromVertex); + if (fromVertexOutputPorts != null) { + fromVertexOutputPorts.remove(fromPort); + if (fromVertexOutputPorts.isEmpty()) { + outputPorts.remove(fromVertex); + } + } + + Set toVertexInputPorts = inputPorts.get(toVertex); + if (toVertexInputPorts != null) { + toVertexInputPorts.remove(toPort); + if (toVertexInputPorts.isEmpty()) { + inputPorts.remove(toVertex); + } + } + + // Remove corresponding LayoutEdge + removeEdge(link); + } + + /** + * Removes a vertex and all associated links from the graph. + * + * @param vertex The Vertex to be removed. + * @throws IllegalArgumentException if the vertex does not exist in the graph. + */ + public void removeVertex(Vertex vertex) { + assert vertices.contains(vertex) : "Vertex does not exist in the graph"; + + // Remove all associated links + List associatedLinks = getAllLinks(vertex); + for (Link link : associatedLinks) { + removeLink(link); + } + + // Remove from vertices set + vertices.remove(vertex); + + // Remove from inputPorts and outputPorts + inputPorts.remove(vertex); + outputPorts.remove(vertex); + + // Remove LayoutNode + LayoutNode node = layoutNodes.get(vertex); + if (node != null) { + removeNodeAndEdges(node); + } + + updatePositions(); + } + + /** + * Retrieves an unmodifiable list of dummy nodes in the graph. + * + * @return An unmodifiable list containing all dummy nodes in the graph. + */ public List getDummyNodes() { return Collections.unmodifiableList(dummyNodes); } + /** + * Retrieves a collection of all layout nodes in the graph. + * + * @return A collection containing all LayoutNodes. + */ + public Collection getLayoutNodes() { + return Collections.unmodifiableCollection(layoutNodes.values()); + } + + /** + * Retrieves a combined list of all nodes in the graph, + * including both layout nodes and dummy nodes. + * + * @return An unmodifiable list containing all nodes in the graph. + */ + public List getAllNodes() { + List allNodes = new ArrayList<>(); + allNodes.addAll(layoutNodes.values()); + allNodes.addAll(dummyNodes); + return Collections.unmodifiableList(allNodes); + } + + /** + * Creates a new layer at the specified index in the layers list. + * Adjusts the layer numbers of existing nodes in layers below the inserted layer. + * + * @param layerNr The index at which to insert the new layer. + * @return The newly created LayoutLayer. + */ private LayoutLayer createNewLayer(int layerNr) { LayoutLayer layer = new LayoutLayer(); layers.add(layerNr, layer); @@ -82,6 +265,12 @@ private LayoutLayer createNewLayer(int layerNr) { return layer; } + /** + * Deletes the layer at the specified index. + * Adjusts the layer numbers of existing nodes in layers below the deleted layer. + * + * @param layerNr The index of the layer to delete. + */ private void deleteLayer(int layerNr) { layers.remove(layerNr); @@ -93,10 +282,15 @@ private void deleteLayer(int layerNr) { } } - - // check that NO neighbors of node are in a given layer - // otherwise insert a new layer - // return the layerNr where the node can now be safely inserted + /** + * Ensures that no neighboring nodes of the specified node are in the same layer. + * If any neighbor is found in the specified layer, inserts a new layer to avoid conflicts. + * Returns the adjusted layer number where the node can be safely inserted. + * + * @param node The LayoutNode to check and possibly reposition. + * @param layerNr The proposed layer number for the node. + * @return The layer number where the node can be safely inserted after adjustments. + */ public int insertNewLayerIfNeeded(LayoutNode node, int layerNr) { for (Link inputLink : getInputLinks(node.getVertex())) { if (inputLink.getFrom().getVertex() == inputLink.getTo().getVertex()) continue; @@ -118,11 +312,14 @@ public int insertNewLayerIfNeeded(LayoutNode node, int layerNr) { } - // inserts a new layer at layerNr - // inserts dummy nodes acoring to layerNr - 1 - // moves the layer from previous layerNr to layerNr + 1 + /** + * Inserts a new layer at the specified index and adjusts nodes and edges accordingly. + * Moves existing nodes and their successors down to accommodate the new layer. + * + * @param layerNr The index at which to insert the new layer. + */ private void moveExpandLayerDown(int layerNr) { - LayoutLayer newLayer = createNewLayer(layerNr); + LayoutLayer newLayer = createNewLayer(layerNr); if (layerNr == 0) return; LayoutLayer layerAbove = getLayer(layerNr - 1); @@ -130,7 +327,7 @@ private void moveExpandLayerDown(int layerNr) { for (LayoutNode fromNode : layerAbove) { int fromX = fromNode.getX(); Map> successorsByX = fromNode.groupSuccessorsByX(); - fromNode.getSuccs().clear(); + fromNode.clearSuccessors(); for (Map.Entry> entry : successorsByX.entrySet()) { Integer relativeFromX = entry.getKey(); @@ -138,166 +335,245 @@ private void moveExpandLayerDown(int layerNr) { LayoutNode dummyNode = new LayoutNode(); dummyNode.setX(fromX + relativeFromX); dummyNode.setLayer(layerNr); - dummyNode.getSuccs().addAll(edges); + for (LayoutEdge edge : edges) { + dummyNode.addSuccessor(edge); + } LayoutEdge dummyEdge = new LayoutEdge(fromNode, dummyNode, relativeFromX, 0, edges.get(0).getLink()); if (edges.get(0).isReversed()) dummyEdge.reverse(); - fromNode.getSuccs().add(dummyEdge); - dummyNode.getPreds().add(dummyEdge); + fromNode.addSuccessor(dummyEdge); + dummyNode.addPredecessor(dummyEdge); for (LayoutEdge edge : edges) { edge.setFrom(dummyNode); } - addNodeToLayer(dummyNode, layerNr); + addDummyToLayer(dummyNode, layerNr); } } - newLayer.sortNodesByXAndSetPositions(); + newLayer.sortNodesByX(); } + /** + * Retrieves an unmodifiable list of all layers in the graph. + * + * @return An unmodifiable list containing all layers. + */ public List getLayers() { return Collections.unmodifiableList(layers); } + /** + * Returns the total number of layers in the graph. + * + * @return The number of layers. + */ public int getLayerCount() { return layers.size(); } - public Collection getLayoutNodes() { - return vertexToLayoutNode.values(); - } - + /** + * Retrieves the LayoutNode associated with the specified Vertex. + * + * @param vertex The vertex whose LayoutNode is to be retrieved. + * @return The LayoutNode corresponding to the given vertex, or null if not found. + */ public LayoutNode getLayoutNode(Vertex vertex) { - return vertexToLayoutNode.get(vertex); - } - - public LayoutGraph(Collection links, Collection additionalVertices) { - this.links = new HashSet<>(links); - - vertices = new TreeSet<>(additionalVertices); - portLinks = new HashMap<>(links.size()); - inputPorts = new HashMap<>(links.size()); - outputPorts = new HashMap<>(links.size()); - - for (Link link : links) { - assert link.getFrom() != null; - assert link.getTo() != null; - Port fromPort = link.getFrom(); - Port toPort = link.getTo(); - Vertex fromVertex = fromPort.getVertex(); - Vertex toVertex = toPort.getVertex(); - - vertices.add(fromVertex); - vertices.add(toVertex); - - outputPorts.computeIfAbsent(fromVertex, k -> new HashSet<>()).add(fromPort); - inputPorts.computeIfAbsent(toVertex, k -> new HashSet<>()).add(toPort); - - portLinks.computeIfAbsent(fromPort, k -> new HashSet<>()).add(link); - portLinks.computeIfAbsent(toPort, k -> new HashSet<>()).add(link); - } - - // cleanup - vertexToLayoutNode = new LinkedHashMap<>(); - dummyNodes = new ArrayList<>(); - - - // Set up nodes - for (Vertex v : getVertices()) { - LayoutNode node = new LayoutNode(v); - vertexToLayoutNode.put(v, node); - } - - // Set up edges - List sortedLinks = new ArrayList<>(links); - sortedLinks.sort(LINK_COMPARATOR); - for (Link link : links) { - createLayoutEdge(link); - } + return layoutNodes.get(vertex); } + /** + * Adds a LayoutNode to the specified layer and registers it in the graph. + * + * @param node The LayoutNode to add to the layer. + * @param layerNumber The index of the layer to which the node will be added. + */ public void addNodeToLayer(LayoutNode node, int layerNumber) { + assert !node.isDummy(); node.setLayer(layerNumber); getLayer(layerNumber).add(node); - - // Register node in the appropriate collection based on its type - registerNode(node); - } - - private void registerNode(LayoutNode node) { - if (node.isDummy()) { - dummyNodes.add(node); - } else { - vertexToLayoutNode.put(node.getVertex(), node); + if (!layoutNodes.containsKey(node.getVertex())) { + layoutNodes.put(node.getVertex(), node); } } - - public void removeNode(LayoutNode node) { - int layer = node.getLayer(); - layers.get(layer).remove(node); - layers.get(layer).updateLayerPositions(); - // Remove node from graph layout - if (node.isDummy()) { - dummyNodes.remove(node); - } else { - vertexToLayoutNode.remove(node.getVertex()); - } + /** + * Adds a LayoutNode to the specified layer and registers it in the graph. + * + * @param node The LayoutNode to add to the layer. + * @param layerNumber The index of the layer to which the node will be added. + */ + public void addDummyToLayer(LayoutNode node, int layerNumber) { + assert node.isDummy(); + node.setLayer(layerNumber); + getLayer(layerNumber).add(node); + dummyNodes.add(node); } + /** + * Updates the positions of all nodes in each layer. + * Should be called after changes to node positions or layer compositions. + */ public void updatePositions() { for (LayoutLayer layer : layers) { - layer.updateLayerPositions(); + layer.updateNodeIndices(); } } + // Create and register LayoutNode + public LayoutNode createLayoutNode(Vertex vertex) { + if (!vertices.contains(vertex)) { + throw new IllegalArgumentException("Vertex does not exist in the graph: " + vertex); + } + LayoutNode node = new LayoutNode(vertex); + layoutNodes.put(vertex, node); + return node; + } + + /** + * Creates a LayoutEdge based on the given Link and connects it to the corresponding LayoutNodes. + * + * @param link The Link representing the edge in the graph. + * @return The newly created LayoutEdge. + */ public LayoutEdge createLayoutEdge(Link link) { LayoutEdge edge = new LayoutEdge( - vertexToLayoutNode.get(link.getFrom().getVertex()), - vertexToLayoutNode.get(link.getTo().getVertex()), + layoutNodes.get(link.getFrom().getVertex()), + layoutNodes.get(link.getTo().getVertex()), link.getFrom().getRelativePosition().x, link.getTo().getRelativePosition().x, link); - edge.getFrom().getSuccs().add(edge); - edge.getTo().getPreds().add(edge); + edge.getFrom().addSuccessor(edge); + edge.getTo().addPredecessor(edge); return edge; } - public Set getLinks() { + /** + * Retrieves the set of all links (edges) in the graph. + * + * @return A set containing all links in the graph. + */ + public Set getLinks() { return links; } + /** + * Retrieves the set of all vertices in the graph, sorted in natural order. + * + * @return A sorted set of all vertices in the graph. + */ public SortedSet getVertices() { return vertices; } + /** + * Checks whether the graph contains the specified vertex. + * + * @param vertex The vertex to check for presence in the graph. + * @return True if the vertex is present, false otherwise. + */ public boolean containsVertex(Vertex vertex) { return vertices.contains(vertex); } + /** + * Finds all root vertices in the graph (vertices with no incoming links). + * + * @return A set of root vertices. + */ public Set findRootVertices() { return vertices.stream() .filter(v -> inputPorts.getOrDefault(v, Collections.emptySet()).isEmpty()) .collect(Collectors.toSet()); } - public Set getInputLinks(Vertex vertex) { - Set inputLinks = new HashSet<>(); + /** + * Retrieves all incoming links to the specified vertex. + * + * @param vertex The vertex whose incoming links are to be retrieved. + * @return A set of links that are incoming to the vertex. + */ + public List getInputLinks(Vertex vertex) { + List inputLinks = new ArrayList<>(); for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { inputLinks.addAll(portLinks.getOrDefault(inputPort, Collections.emptySet())); } return inputLinks; } - public Set getOutputLinks(Vertex vertex) { - Set outputLinks = new HashSet<>(); + /** + * Retrieves all outgoing links from the specified vertex. + * + * @param vertex The vertex whose outgoing links are to be retrieved. + * @return A set of links that are outgoing from the vertex. + */ + public List getOutputLinks(Vertex vertex) { + List outputLinks = new ArrayList<>(); for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) { outputLinks.addAll(portLinks.getOrDefault(outputPort, Collections.emptySet())); } return outputLinks; } - private Set getAllLinks(Vertex vertex) { - Set allLinks = new HashSet<>(); + /** + * Checks if the given predecessorVertex is a direct predecessor of the specified vertex. + * + * @param vertex The vertex to check for predecessors. + * @param predecessorVertex The vertex to verify as a predecessor of the given vertex. + * @return true if predecessorVertex is a direct predecessor of vertex, false otherwise. + */ + public boolean isPredecessorVertex(Vertex vertex, Vertex predecessorVertex) { + for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { + for (Link inputLink : portLinks.getOrDefault(inputPort, Collections.emptySet())) { + Vertex fromVertex = inputLink.getFrom().getVertex(); + if (fromVertex.equals(predecessorVertex)) { + return true; + } + } + } + return false; + } + + /** + * Checks if the given successorVertex is a direct successor of the specified vertex. + * + * @param vertex The vertex to check for successors. + * @param successorVertex The vertex to verify as a successor of the given vertex. + * @return true if successorVertex is a direct successor of vertex, false otherwise. + */ + public boolean isSuccessorVertex(Vertex vertex, Vertex successorVertex) { + for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) { + for (Link outputLink : portLinks.getOrDefault(outputPort, Collections.emptySet())) { + Vertex toVertex = outputLink.getTo().getVertex(); + if (toVertex.equals(successorVertex)) { + return true; + } + } + } + return false; + } + + + public List getNeighborVertices(Vertex vertex) { + List neighborVertices = new ArrayList<>(); + for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { + for (Link inputLink : portLinks.getOrDefault(inputPort, Collections.emptySet())) { + Vertex fromVertex = inputLink.getFrom().getVertex(); + assert fromVertex != null; + neighborVertices.add(fromVertex); + } + } + for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) { + for (Link outputLink : portLinks.getOrDefault(outputPort, Collections.emptySet())) { + Vertex toVertex = outputLink.getTo().getVertex(); + assert toVertex != null; + neighborVertices.add(toVertex); + } + } + return neighborVertices; + } + + public List getAllLinks(Vertex vertex) { + List allLinks = new ArrayList<>(); for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { allLinks.addAll(portLinks.getOrDefault(inputPort, Collections.emptySet())); @@ -310,94 +586,123 @@ private Set getAllLinks(Vertex vertex) { return allLinks; } - private void removeEdges(LayoutNode movedNode) { - for (Link inputLink : getAllLinks(movedNode.getVertex())) { - Vertex from = inputLink.getFrom().getVertex(); - Vertex to = inputLink.getTo().getVertex(); - LayoutNode toNode = getLayoutNode(to); - LayoutNode fromNode = getLayoutNode(from); + /** + * Removes the specified LayoutNode and all its connected edges from the graph. + * + * @param node The LayoutNode to remove along with its edges. + */ + public void removeNodeAndEdges(LayoutNode node) { + assert !node.isDummy(); + removeEdges(node); // a node can only be removed together with its edges + int layer = node.getLayer(); + layers.get(layer).remove(node); + layers.get(layer).updateNodeIndices(); + layoutNodes.remove(node.getVertex()); + } - if (toNode.getLayer() < fromNode.getLayer()) { - // Reversed edge - toNode = fromNode; - toNode.getReversedLinkEndPoints().remove(inputLink); - fromNode.getReversedLinkStartPoints().remove(inputLink); - } - // Remove preds-edges bottom up, starting at "to" node - // Cannot start from "from" node since there might be joint edges - List toNodePredsEdges = List.copyOf(toNode.getPreds()); - for (LayoutEdge edge : toNodePredsEdges) { - LayoutNode predNode = edge.getFrom(); - LayoutEdge edgeToRemove; + /** + * Removes all edges connected to the specified LayoutNode. + * Handles the removal of associated dummy nodes if they are no longer needed. + * Updates the graph structure accordingly after node movement. + * + * @param node The LayoutNode whose connected edges are to be removed. + */ + public void removeEdges(LayoutNode node) { + assert !node.isDummy(); + for (Link link : getAllLinks(node.getVertex())) { + removeEdge(link); + } + } - if (edge.getLink() != null && edge.getLink().equals(inputLink)) { - toNode.getPreds().remove(edge); - edgeToRemove = edge; - } else { - // Wrong edge, look at next - continue; - } + public void removeEdge(Link link) { + Vertex from = link.getFrom().getVertex(); + Vertex to = link.getTo().getVertex(); + LayoutNode toNode = getLayoutNode(to); + LayoutNode fromNode = getLayoutNode(from); - if (!predNode.isDummy() && predNode.getVertex().equals(from)) { - // No dummy nodes inbetween 'from' and 'to' vertex - predNode.getSuccs().remove(edgeToRemove); - break; - } else { - // Must remove edges between dummy nodes - boolean found = true; - LayoutNode succNode = toNode; - while (predNode.isDummy() && found) { - found = false; - - if (predNode.getSuccs().size() <= 1 && predNode.getPreds().size() <= 1) { - // Dummy node used only for this link, remove if not already removed - removeNode(predNode); - } else { - // anchor node, should not be removed - break; - } + if (toNode.getLayer() < fromNode.getLayer()) { + // Reversed edge + toNode = fromNode; + } - if (predNode.getPreds().size() == 1) { - predNode.getSuccs().remove(edgeToRemove); - succNode = predNode; - edgeToRemove = predNode.getPreds().get(0); - predNode = edgeToRemove.getFrom(); - found = true; - } + // Remove preds-edges bottom up, starting at "to" node + // Cannot start from "from" node since there might be joint edges + List toNodePredsEdges = List.copyOf(toNode.getPredecessors()); + for (LayoutEdge edge : toNodePredsEdges) { + LayoutNode predNode = edge.getFrom(); + LayoutEdge edgeToRemove; + + if (edge.getLink() != null && edge.getLink().equals(link)) { + toNode.removePredecessor(edge); + edgeToRemove = edge; + } else { + // Wrong edge, look at next + continue; + } + + if (!predNode.isDummy() && predNode.getVertex().equals(from)) { + // No dummy nodes inbetween 'from' and 'to' vertex + predNode.removeSuccessor(edgeToRemove); + break; + } else { + // Must remove edges between dummy nodes + boolean found = true; + LayoutNode succNode = toNode; + while (predNode.isDummy() && found) { + found = false; + + if (predNode.getSuccessors().size() <= 1 && predNode.getPredecessors().size() <= 1) { + // Dummy node used only for this link, remove if not already removed + assert predNode.isDummy(); + int layer = predNode.getLayer(); + layers.get(layer).remove(predNode); + layers.get(layer).updateNodeIndices(); + dummyNodes.remove(predNode); + } else { + // anchor node, should not be removed + break; } - predNode.getSuccs().remove(edgeToRemove); - succNode.getPreds().remove(edgeToRemove); + if (predNode.getPredecessors().size() == 1) { + predNode.removeSuccessor(edgeToRemove); + succNode = predNode; + edgeToRemove = predNode.getPredecessors().get(0); + predNode = edgeToRemove.getFrom(); + found = true; + } } - break; - } - } - // remove link connected to movedNode - for (Link link : getLinks()) { - if (link.getTo().getVertex() == movedNode.getVertex()) { - link.setControlPoints(new ArrayList<>()); - movedNode.getReversedLinkStartPoints().remove(link); - } else if (link.getFrom().getVertex() == movedNode.getVertex()) { - link.setControlPoints(new ArrayList<>()); - movedNode.getReversedLinkEndPoints().remove(link); + predNode.removeSuccessor(edgeToRemove); + succNode.removePredecessor(edgeToRemove); } + break; } - movedNode.initSize(); - } - - public void removeNodeAndEdges(LayoutNode node) { - removeEdges(node); - removeNode(node); + if (fromNode.getReversedLinkStartPoints().containsKey(link)) { + fromNode.computeReversedLinkPoints(false); + } + if (toNode.getReversedLinkStartPoints().containsKey(link)) { + toNode.computeReversedLinkPoints(false); + } } - + /** + * Retrieves the LayoutLayer at the specified index. + * + * @param layerNr The index of the layer to retrieve. + * @return The LayoutLayer at the specified index. + */ public LayoutLayer getLayer(int layerNr) { return layers.get(layerNr); } + /** + * Finds the layer closest to the given y-coordinate. + * + * @param y the y-coordinate to check + * @return the index of the optimal layer, or -1 if no layers are found + */ public int findLayer(int y) { int optimalLayer = -1; int minDistance = Integer.MAX_VALUE; @@ -416,6 +721,10 @@ public int findLayer(int y) { return optimalLayer; } + /** + * Positions the layers vertically, calculating their heights and setting their positions. + * Centers the nodes within each layer vertically. + */ public void positionLayers() { int currentY = 0; for (LayoutLayer layer : getLayers()) { @@ -429,22 +738,28 @@ public void positionLayers() { layer.centerNodesVertically(); // Update currentY to account for the padded bottom of this layer - currentY += layer.calculateScalePaddedBottom(); + currentY += layer.calculatePaddedHeight(); } } + /** + * Optimizes routing of reversed (back) edges to reduce crossings. + */ public void optimizeBackEdgeCrossings() { for (LayoutNode node : getLayoutNodes()) { - if (node.getReversedLinkStartPoints().isEmpty() && node.getReversedLinkEndPoints().isEmpty()) continue; - node.computeReversedLinkPoints(); + node.optimizeBackEdgeCrossing(); } } + /** + * Removes empty layers from the graph. + * Iteratively checks for and removes layers that contain only dummy nodes. + */ public void removeEmptyLayers() { int i = 0; while (i < getLayerCount()) { LayoutLayer layer = getLayer(i); - if (layer.isDummyLayer()) { + if (layer.containsOnlyDummyNodes()) { removeEmptyLayer(i); } else { i++; // Move to the next layer only if no removal occurred @@ -452,38 +767,44 @@ public void removeEmptyLayers() { } } + /** + * Removes the layer at the specified index if it is empty or contains only dummy nodes. + * Adjusts the positions of nodes and edges accordingly. + * + * @param layerNr The index of the layer to remove. + */ private void removeEmptyLayer(int layerNr) { LayoutLayer layer = getLayer(layerNr); - if (!layer.isDummyLayer()) return; + if (!layer.containsOnlyDummyNodes()) return; for (LayoutNode dummyNode : layer) { - if (dummyNode.getSuccs().isEmpty()) { + if (dummyNode.getSuccessors().isEmpty()) { dummyNode.setLayer(layerNr + 1); getLayer(layerNr + 1).add(dummyNode); - dummyNode.setX(dummyNode.calculateOptimalPositionDown()); - getLayer(layerNr + 1).sortNodesByXAndSetPositions(); + dummyNode.setX(dummyNode.calculateOptimalXFromPredecessors(true)); + getLayer(layerNr + 1).sortNodesByX(); continue; - } else if (dummyNode.getPreds().isEmpty()) { + } else if (dummyNode.getPredecessors().isEmpty()) { dummyNode.setLayer(layerNr - 1); - dummyNode.setX(dummyNode.calculateOptimalPositionUp()); + dummyNode.setX(dummyNode.calculateOptimalXFromSuccessors(true)); getLayer(layerNr - 1).add(dummyNode); - getLayer(layerNr - 1).sortNodesByXAndSetPositions(); + getLayer(layerNr - 1).sortNodesByX(); continue; } - LayoutEdge layoutEdge = dummyNode.getPreds().get(0); + LayoutEdge layoutEdge = dummyNode.getPredecessors().get(0); // remove the layoutEdge LayoutNode fromNode = layoutEdge.getFrom(); - fromNode.getSuccs().remove(layoutEdge); + fromNode.removeSuccessor(layoutEdge); - List successorEdges = dummyNode.getSuccs(); + List successorEdges = dummyNode.getSuccessors(); for (LayoutEdge successorEdge : successorEdges) { successorEdge.setRelativeFromX(layoutEdge.getRelativeFromX()); successorEdge.setFrom(fromNode); - fromNode.getSuccs().add(successorEdge); + fromNode.addSuccessor(successorEdge); } - dummyNode.getPreds().clear(); - dummyNode.getSuccs().clear(); + dummyNode.clearPredecessors(); + dummyNode.clearSuccessors(); dummyNodes.remove(dummyNode); } @@ -491,11 +812,11 @@ private void removeEmptyLayer(int layerNr) { } /** - * Repositions the given LayoutNode to the specified x-coordinate within its layer, - * ensuring no overlap with adjacent nodes and maintaining a minimum NODE_OFFSET distance. + * Repositions the specified LayoutNode horizontally within its layer to the new x-coordinate. + * Ensures no overlap with adjacent nodes and maintains minimum spacing. * - * @param layoutNode The LayoutNode to be repositioned. - * @param newX The desired new x-coordinate for the layoutNode. + * @param layoutNode The LayoutNode to reposition. + * @param newX The new x-coordinate to set for the node. */ private void repositionLayoutNodeX(LayoutNode layoutNode, int newX) { int currentX = layoutNode.getX(); @@ -507,23 +828,22 @@ private void repositionLayoutNodeX(LayoutNode layoutNode, int newX) { LayoutLayer layer = getLayer(layoutNode.getLayer()); if (newX > currentX) { - layer.attemptMoveRight(layoutNode, newX); + layer.tryShiftNodeRight(layoutNode, newX); } else { - layer.attemptMoveLeft(layoutNode, newX); + layer.tryShiftNodeLeft(layoutNode, newX); } } /** * Aligns the x-coordinate of a single dummy successor node for the given LayoutNode. * If the node has exactly one successor and that successor is a dummy node, - * this method sets the dummy node's x-coordinate to either the node's x-coordinate - * (if the node is a dummy) or to the starting x-coordinate of the connecting edge. + * sets the dummy node's x-coordinate to align with the current node or the edge's starting point. * - * @param node The LayoutNode whose single dummy successor needs to be aligned. + * @param node The LayoutNode whose dummy successor is to be aligned. */ private void alignSingleSuccessorDummyNodeX(LayoutNode node) { // Retrieve the list of successor edges - List successors = node.getSuccs(); + List successors = node.getSuccessors(); // Proceed only if there is exactly one successor if (successors.size() != 1) { @@ -565,8 +885,8 @@ private void alignLayerDummySuccessors(LayoutLayer layer) { } /** - * Aligns the x-coordinates of dummy successor nodes across all layers. - * Performs alignment in both forward and backward directions for comprehensive coverage. + * Straightens edges in the graph by aligning dummy nodes to reduce bends. + * Processes all layers to align dummy successor nodes. */ public void straightenEdges() { // Forward pass: Align dummy successors from the first layer to the last. @@ -580,4 +900,343 @@ public void straightenEdges() { } } + /** + * Calculates the optimal horizontal position (index) for the specified node within the given layer, + * aiming to minimize the number of edge crossings. + * + * @param node The node to position. + * @param layerNr The index of the layer in which to position the node. + * @return The optimal position index within the layer for the node. + */ + private int optimalPosition(LayoutNode node, int layerNr) { + getLayer(layerNr).sort(NODE_POS_COMPARATOR); + int edgeCrossings = Integer.MAX_VALUE; + int optimalPos = -1; + + // Try each possible position in the layerNr + for (int i = 0; i < getLayer(layerNr).size() + 1; i++) { + int xCoord; + if (i == 0) { + xCoord = getLayer(layerNr).get(i).getX() - node.getWidth() - 1; + } else { + xCoord = getLayer(layerNr).get(i - 1).getX() + getLayer(layerNr).get(i - 1).getWidth() + 1; + } + + int currentCrossings = 0; + + if (0 <= layerNr - 1) { + // For each link with an end point in vertex, check how many edges cross it + for (LayoutEdge edge : node.getPredecessors()) { + if (edge.getFrom().getLayer() == layerNr - 1) { + int fromNodeXCoord = edge.getFromX(); + int toNodeXCoord = xCoord; + if (!node.isDummy()) { + toNodeXCoord += edge.getRelativeToX(); + } + for (LayoutNode n : getLayer(layerNr - 1)) { + for (LayoutEdge e : n.getSuccessors()) { + if (e.getTo() == null) { + continue; + } + int compFromXCoord = e.getFromX(); + int compToXCoord = e.getToX(); + if ((fromNodeXCoord > compFromXCoord && toNodeXCoord < compToXCoord) + || (fromNodeXCoord < compFromXCoord + && toNodeXCoord > compToXCoord)) { + currentCrossings += 1; + } + } + } + } + } + } + // Edge crossings across current layerNr and layerNr below + if (layerNr + 1 < getLayerCount()) { + // For each link with an end point in vertex, check how many edges cross it + for (LayoutEdge edge : node.getSuccessors()) { + if (edge.getTo().getLayer() == layerNr + 1) { + int toNodeXCoord = edge.getToX(); + int fromNodeXCoord = xCoord; + if (!node.isDummy()) { + fromNodeXCoord += edge.getRelativeFromX(); + } + for (LayoutNode n : getLayer(layerNr + 1)) { + for (LayoutEdge e : n.getPredecessors()) { + if (e.getFrom() == null) { + continue; + } + int compFromXCoord = e.getFromX(); + int compToXCoord = e.getToX(); + if ((fromNodeXCoord > compFromXCoord && toNodeXCoord < compToXCoord) + || (fromNodeXCoord < compFromXCoord + && toNodeXCoord > compToXCoord)) { + currentCrossings += 1; + } + } + } + } + } + } + if (currentCrossings <= edgeCrossings) { + edgeCrossings = currentCrossings; + optimalPos = i; + } + } + return optimalPos; + } + + /** + * Creates layout edges for the specified node and reverses edges as needed. + * Reverses edges that go from lower to higher layers to maintain proper layering. + * + * @param node The LayoutNode for which to create and reverse edges. + */ + public void createAndReverseLayoutEdges(LayoutNode node) { + List nodeLinks = new ArrayList<>(getInputLinks(node.getVertex())); + nodeLinks.addAll(getOutputLinks(node.getVertex())); + nodeLinks.sort(LINK_COMPARATOR); + + List reversedLayoutNodes = new ArrayList<>(); + for (Link link : nodeLinks) { + if (link.getFrom().getVertex() == link.getTo().getVertex()) continue; + LayoutEdge layoutEdge = createLayoutEdge(link); + + LayoutNode fromNode = layoutEdge.getFrom(); + LayoutNode toNode = layoutEdge.getTo(); + + if (fromNode.getLayer() > toNode.getLayer()) { + HierarchicalLayoutManager.ReverseEdges.reverseEdge(layoutEdge); + reversedLayoutNodes.add(fromNode); + reversedLayoutNodes.add(toNode); + } + } + + // ReverseEdges + for (LayoutNode layoutNode : reversedLayoutNodes) { + layoutNode.computeReversedLinkPoints(false); + } + } + + /** + * Inserts dummy nodes along the edges from predecessors of the specified node, + * for edges that span more than one layer. + * + * @param layoutNode The node for which to create predecessor dummy nodes. + */ + public void createDummiesForNodePredecessor(LayoutNode layoutNode) { + for (LayoutEdge predEdge : layoutNode.getPredecessors()) { + LayoutNode fromNode = predEdge.getFrom(); + LayoutNode toNode = predEdge.getTo(); + if (Math.abs(toNode.getLayer() - fromNode.getLayer()) <= 1) continue; + + boolean hasEdgeFromSamePort = false; + LayoutEdge edgeFromSamePort = new LayoutEdge(fromNode, toNode, predEdge.getLink()); + if (predEdge.isReversed()) edgeFromSamePort.reverse(); + + for (LayoutEdge succEdge : fromNode.getSuccessors()) { + if (succEdge.getRelativeFromX() == predEdge.getRelativeFromX() && succEdge.getTo().isDummy()) { + edgeFromSamePort = succEdge; + hasEdgeFromSamePort = true; + break; + } + } + + if (hasEdgeFromSamePort) { + LayoutEdge curEdge = edgeFromSamePort; + boolean newEdge = true; + while (curEdge.getTo().getLayer() < toNode.getLayer() - 1 && curEdge.getTo().isDummy() && newEdge) { + // Traverse down the chain of dummy nodes linking together the edges originating + // from the same port + newEdge = false; + if (curEdge.getTo().getSuccessors().size() == 1) { + curEdge = curEdge.getTo().getSuccessors().get(0); + newEdge = true; + } else { + for (LayoutEdge e : curEdge.getTo().getSuccessors()) { + if (e.getTo().isDummy()) { + curEdge = e; + newEdge = true; + break; + } + } + } + } + + LayoutNode prevDummy; + if (!curEdge.getTo().isDummy()) { + prevDummy = curEdge.getFrom(); + } else { + prevDummy = curEdge.getTo(); + } + + predEdge.setFrom(prevDummy); + predEdge.setRelativeFromX(prevDummy.getWidth() / 2); + fromNode.removeSuccessor(predEdge); + prevDummy.addSuccessor(predEdge); + } + + LayoutNode layoutNode1 = predEdge.getTo(); + if (predEdge.getTo().getLayer() - 1 > predEdge.getFrom().getLayer()) { + LayoutEdge prevEdge = predEdge; + for (int l = layoutNode1.getLayer() - 1; l > prevEdge.getFrom().getLayer(); l--) { + LayoutNode dummyNode = new LayoutNode(); + dummyNode.addSuccessor(prevEdge); + LayoutEdge result = new LayoutEdge(prevEdge.getFrom(), dummyNode, prevEdge.getRelativeFromX(), 0, prevEdge.getLink()); + if (prevEdge.isReversed()) result.reverse(); + dummyNode.addPredecessor(result); + prevEdge.setRelativeFromX(0); + prevEdge.getFrom().removeSuccessor(prevEdge); + prevEdge.getFrom().addSuccessor(result); + prevEdge.setFrom(dummyNode); + dummyNode.setLayer(l); + List layerNodes = getLayer(l); + if (layerNodes.isEmpty()) { + dummyNode.setPos(0); + } else { + dummyNode.setPos(optimalPosition(dummyNode, l)); + } + for (LayoutNode n : layerNodes) { + if (n.getPos() >= dummyNode.getPos()) { + n.setPos(n.getPos() + 1); + } + } + addDummyToLayer(dummyNode, l); + prevEdge = dummyNode.getPredecessors().get(0); + } + } + } + } + + /** + * Inserts dummy nodes along the edges to successors of the specified node, + * for edges that span more than one layer. + * Can limit the maximum length of layers an edge spans using maxLayerLength. + * + * @param layoutNode The node for which to create successor dummy nodes. + * @param maxLayerLength The maximum number of layers an edge can span without splitting it + */ + public void createDummiesForNodeSuccessor(LayoutNode layoutNode, int maxLayerLength) { + LinkedHashMap> portsToUnprocessedEdges = new LinkedHashMap<>(); + ArrayList succs = new ArrayList<>(layoutNode.getSuccessors()); + LinkedHashMap portToTopNode = new LinkedHashMap<>(); + LinkedHashMap> portToBottomNodeMapping = new LinkedHashMap<>(); + for (LayoutEdge succEdge : succs) { + int startPort = succEdge.getRelativeFromX(); + LayoutNode fromNode = succEdge.getFrom(); + LayoutNode toNode = succEdge.getTo(); + + // edge is longer than one layer => needs dummy nodes + if (fromNode.getLayer() != toNode.getLayer() - 1) { + // the edge needs to be cut + if (maxLayerLength != -1 && toNode.getLayer() - fromNode.getLayer() > maxLayerLength) { + // remove the succEdge before replacing it + toNode.removePredecessor(succEdge); + fromNode.removeSuccessor(succEdge); + + LayoutNode topCutNode = portToTopNode.get(startPort); + if (topCutNode == null) { + topCutNode = new LayoutNode(); + topCutNode.setLayer(fromNode.getLayer() + 1); + addDummyToLayer(topCutNode, topCutNode.getLayer()); + portToTopNode.put(startPort, topCutNode); + portToBottomNodeMapping.put(startPort, new LinkedHashMap<>()); + } + LayoutEdge edgeToTopCut = new LayoutEdge(fromNode, topCutNode, succEdge.getRelativeFromX(), topCutNode.getWidth() / 2, succEdge.getLink()); + if (succEdge.isReversed()) edgeToTopCut.reverse(); + fromNode.addSuccessor(edgeToTopCut); + topCutNode.addPredecessor(edgeToTopCut); + + LinkedHashMap layerToBottomNode = portToBottomNodeMapping.get(startPort); + LayoutNode bottomCutNode = layerToBottomNode.get(toNode.getLayer()); + if (bottomCutNode == null) { + bottomCutNode = new LayoutNode(); + bottomCutNode.setLayer(toNode.getLayer() - 1); + addDummyToLayer(bottomCutNode, bottomCutNode.getLayer()); + layerToBottomNode.put(toNode.getLayer(), bottomCutNode); + } + LayoutEdge bottomEdge = new LayoutEdge(bottomCutNode, toNode, bottomCutNode.getWidth() / 2, succEdge.getRelativeToX(), succEdge.getLink()); + if (succEdge.isReversed()) bottomEdge.reverse(); + toNode.addPredecessor(bottomEdge); + bottomCutNode.addSuccessor(bottomEdge); + + } else { // the edge is not cut, but needs dummy nodes + portsToUnprocessedEdges.putIfAbsent(startPort, new ArrayList<>()); + portsToUnprocessedEdges.get(startPort).add(succEdge); + } + } + } + + for (Map.Entry> portToUnprocessedEdges : portsToUnprocessedEdges.entrySet()) { + Integer startPort = portToUnprocessedEdges.getKey(); + List unprocessedEdges = portToUnprocessedEdges.getValue(); + unprocessedEdges.sort(Comparator.comparingInt(e -> e.getTo().getLayer())); + + if (unprocessedEdges.size() == 1) { + // process a single edge + LayoutEdge singleEdge = unprocessedEdges.get(0); + LayoutNode fromNode = singleEdge.getFrom(); + if (singleEdge.getTo().getLayer() > fromNode.getLayer() + 1) { + LayoutEdge previousEdge = singleEdge; + for (int i = fromNode.getLayer() + 1; i < previousEdge.getTo().getLayer(); i++) { + LayoutNode dummyNode = new LayoutNode(); + dummyNode.setLayer(i); + dummyNode.addPredecessor(previousEdge); + addDummyToLayer(dummyNode, dummyNode.getLayer()); + LayoutEdge dummyEdge = new LayoutEdge(dummyNode, previousEdge.getTo(), dummyNode.getWidth() / 2, previousEdge.getRelativeToX(), singleEdge.getLink()); + if (previousEdge.isReversed()) dummyEdge.reverse(); + dummyNode.addSuccessor(dummyEdge); + previousEdge.setRelativeToX(dummyNode.getWidth() / 2); + previousEdge.getTo().removePredecessor(previousEdge); + previousEdge.getTo().addPredecessor(dummyEdge); + previousEdge.setTo(dummyNode); + previousEdge = dummyEdge; + } + } + } else { + int lastLayer = unprocessedEdges.get(unprocessedEdges.size() - 1).getTo().getLayer(); + int dummyCnt = lastLayer - layoutNode.getLayer() - 1; + LayoutEdge[] newDummyEdges = new LayoutEdge[dummyCnt]; + LayoutNode[] newDummyNodes = new LayoutNode[dummyCnt]; + + newDummyNodes[0] = new LayoutNode(); + newDummyNodes[0].setLayer(layoutNode.getLayer() + 1); + newDummyEdges[0] = new LayoutEdge(layoutNode, newDummyNodes[0], startPort, newDummyNodes[0].getWidth() / 2, null); + newDummyNodes[0].addPredecessor(newDummyEdges[0]); + layoutNode.addSuccessor(newDummyEdges[0]); + for (int j = 1; j < dummyCnt; j++) { + newDummyNodes[j] = new LayoutNode(); + newDummyNodes[j].setLayer(layoutNode.getLayer() + j + 1); + newDummyEdges[j] = new LayoutEdge(newDummyNodes[j - 1], newDummyNodes[j], null); + newDummyNodes[j].addPredecessor(newDummyEdges[j]); + newDummyNodes[j - 1].addSuccessor(newDummyEdges[j]); + } + for (LayoutEdge unprocessedEdge : unprocessedEdges) { + LayoutNode anchorNode = newDummyNodes[unprocessedEdge.getTo().getLayer() - layoutNode.getLayer() - 2]; + anchorNode.addSuccessor(unprocessedEdge); + unprocessedEdge.setFrom(anchorNode); + unprocessedEdge.setRelativeFromX(anchorNode.getWidth() / 2); + layoutNode.removeSuccessor(unprocessedEdge); + } + for (LayoutNode dummyNode : newDummyNodes) { + addDummyToLayer(dummyNode, dummyNode.getLayer()); + } + } + } + } + + /** + * Adds edges connected to the specified node, including any necessary dummy nodes. + * Handles edge reversal, dummy node insertion for both predecessors and successors, + * and updates node positions accordingly. + * + * @param node The LayoutNode to which edges will be added. + * @param maxLayerLength The maximum number of layers an edge can span without splitting it + */ + public void addEdges(LayoutNode node, int maxLayerLength) { + assert !node.isDummy(); + createAndReverseLayoutEdges(node); + createDummiesForNodeSuccessor(node, maxLayerLength); + createDummiesForNodePredecessor(node); + updatePositions(); + } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java index dc530dd31d7e5..4aea3294a6917 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java @@ -28,46 +28,82 @@ import java.util.ArrayList; import java.util.Collection; +/** + * Represents a layer in a hierarchical graph layout. + * Each LayoutLayer contains a collection of LayoutNodes positioned at the same vertical level. + * Provides methods to manage the nodes within the layer, including positioning, sorting, + * and adjusting the layout to minimize overlaps and improve visual clarity. + */ public class LayoutLayer extends ArrayList { private int height = 0; private int y = 0; + /** + * Adds all LayoutNodes from the specified collection to this layer. + * Updates the layer's height based on the nodes added. + * + * @param c The collection of LayoutNodes to be added. + * @return true if this layer changed as a result of the call. + */ @Override public boolean addAll(Collection c) { - c.forEach(this::updateHeight); + c.forEach(this::updateLayerHeight); return super.addAll(c); } - private void updateHeight(LayoutNode n) { - height = Math.max(height, n.getOuterHeight()); - } - + /** + * Adds a single LayoutNode to this layer. + * Updates the layer's height based on the node added. + * + * @param n The LayoutNode to be added. + * @return true if the node was added successfully. + */ @Override public boolean add(LayoutNode n) { - updateHeight(n); + updateLayerHeight(n); return super.add(n); } + /** + * Updates the layer's height if the outer height of the given node exceeds the current height. + * + * @param n The LayoutNode whose height is to be considered. + */ + private void updateLayerHeight(LayoutNode n) { + height = Math.max(height, n.getOuterHeight()); + } + + /** + * Calculates and returns the maximum height among the nodes in this layer, including their margins. + * Adjusts the top and bottom margins of non-dummy nodes to be equal, effectively centering them vertically. + * + * @return The maximum outer height of nodes in this layer. + */ public int calculateMaxLayerHeight() { int maxLayerHeight = 0; for (LayoutNode layoutNode : this) { if (!layoutNode.isDummy()) { // Center the node by setting equal top and bottom margins - int offset = Math.max(layoutNode.getTopMargin(), layoutNode.getBottomMargin()); - layoutNode.setTopMargin(offset); - layoutNode.setBottomMargin(offset); + layoutNode.centerNode(); } maxLayerHeight = Math.max(maxLayerHeight, layoutNode.getOuterHeight()); } return maxLayerHeight; } - public int calculateScalePaddedBottom() { + /** + * Calculates and returns the total height of this layer, including additional padding + * based on the maximum horizontal offset among the edges of its nodes. + * This padding helps in scaling the layer vertically to accommodate edge bends and crossings. + * + * @return The total padded height of the layer. + */ + public int calculatePaddedHeight() { int maxXOffset = 0; for (LayoutNode layoutNode : this) { - for (LayoutEdge succEdge : layoutNode.getSuccs()) { + for (LayoutEdge succEdge : layoutNode.getSuccessors()) { maxXOffset = Math.max(Math.abs(succEdge.getStartX() - succEdge.getEndX()), maxXOffset); } } @@ -77,6 +113,10 @@ public int calculateScalePaddedBottom() { return scalePaddedBottom; } + /** + * Centers all nodes in this layer vertically within the layer's assigned space. + * Adjusts each node's Y-coordinate so that it is centered based on the layer's top and height. + */ public void centerNodesVertically() { for (LayoutNode layoutNode : this) { int centeredY = getTop() + (getHeight() - layoutNode.getOuterHeight()) / 2; @@ -84,36 +124,71 @@ public void centerNodesVertically() { } } - public void setTop(int top) { - y = top; - } - - public void shiftTop(int shift) { + /** + * Shifts the top Y-coordinate of this layer by the specified amount. + * Useful for moving the entire layer up or down. + * + * @param shift The amount to shift the layer's top position. Positive values move it down. + */ + public void moveLayerVertically(int shift) { y += shift; } + /** + * Gets the top Y-coordinate of this layer. + * + * @return The Y-coordinate representing the top of the layer. + */ public int getTop() { return y; } + /** + * Sets the top Y-coordinate of this layer. + * + * @param top The Y-coordinate representing the top of the layer. + */ + public void setTop(int top) { + y = top; + } + public int getCenter() { return y + height / 2; } + /** + * Gets the bottom Y-coordinate of this layer. + * + * @return The Y-coordinate representing the bottom of the layer. + */ public int getBottom() { return y + height; } + /** + * Gets the height of this layer. + * + * @return The height of the layer. + */ public int getHeight() { return height; } + /** + * Sets the height of this layer. + * + * @param height The height to set for the layer. + */ public void setHeight(int height) { this.height = height; } - // Layer contains no non-dummy nodes - public boolean isDummyLayer() { + /** + * Checks if this layer contains only dummy nodes. + * + * @return true if all nodes in the layer are dummy nodes; false otherwise. + */ + public boolean containsOnlyDummyNodes() { for (LayoutNode node : this) { if (!node.isDummy()) { return false; @@ -122,29 +197,55 @@ public boolean isDummyLayer() { return true; } - public void sortNodesByXAndSetPositions() { - if (this.isEmpty()) return; + /** + * Sorts the nodes in this layer by their X-coordinate in increasing order. + * Assigns position indices to nodes based on the sorted order. + * Adjusts the X-coordinates of nodes to ensure minimum spacing between them. + */ + public void sortNodesByX() { + if (isEmpty()) return; - // Sort nodes in the layer increasingly by x - this.sort(NODE_X_COMPARATOR); + sort(NODE_X_COMPARATOR); // Sort nodes in the layer increasingly by x - int pos = 0; - int minX = this.get(0).getX(); // Starting X position for the first node + updateNodeIndices(); + updateMinXSpacing(false); + } - for (LayoutNode node : this) { - node.setPos(pos); - pos++; + /** + * Ensures nodes have minimum horizontal spacing by adjusting their X positions. + * + * @param startFromZero if true, starts positioning from X = 0; otherwise, uses the first node's current X. + */ + public void updateMinXSpacing(boolean startFromZero) { + if (isEmpty()) { + return; // No nodes to adjust. + } + + int minX = startFromZero ? 0 : this.get(0).getX(); - // Set the X position of the node to at least minX, ensuring spacing + for (LayoutNode node : this) { int x = Math.max(node.getX(), minX); node.setX(x); - - // Update minX for the next node based on the current node's outer width and offset minX = x + node.getOuterWidth() + NODE_OFFSET; } } - public void updateLayerPositions() { + /** + * Initializes nodes' X positions with spacing. + */ + public void initXPositions() { + int curX = 0; + for (LayoutNode node : this) { + node.setX(curX); + curX += node.getOuterWidth() + NODE_OFFSET; + } + } + + /** + * Updates the position indices of the nodes in this layer based on their order in the list. + * Useful after nodes have been added or removed to ensure position indices are consistent. + */ + public void updateNodeIndices() { int pos = 0; for (LayoutNode layoutNode : this) { layoutNode.setPos(pos); @@ -152,7 +253,15 @@ public void updateLayerPositions() { } } - public void attemptMoveRight(LayoutNode layoutNode, int newX) { + /** + * Attempts to move the specified node to the right within the layer to the given X-coordinate. + * Ensures that the node does not overlap with its right neighbor by checking required spacing. + * If movement is possible without causing overlap, the node's X-coordinate is updated. + * + * @param layoutNode The node to move. + * @param newX The desired new X-coordinate for the node. + */ + public void tryShiftNodeRight(LayoutNode layoutNode, int newX) { int currentX = layoutNode.getX(); int shiftAmount = newX - currentX; int rightPos = layoutNode.getPos() + 1; @@ -172,7 +281,15 @@ public void attemptMoveRight(LayoutNode layoutNode, int newX) { } } - public void attemptMoveLeft(LayoutNode layoutNode, int newX) { + /** + * Attempts to move the specified node to the left within the layer to the given X-coordinate. + * Ensures that the node does not overlap with its left neighbor by checking required spacing. + * If movement is possible without causing overlap, the node's X-coordinate is updated. + * + * @param layoutNode The node to move. + * @param newX The desired new X-coordinate for the node. + */ + public void tryShiftNodeLeft(LayoutNode layoutNode, int newX) { int currentX = layoutNode.getX(); int shiftAmount = currentX - newX; int leftPos = layoutNode.getPos() - 1; diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutManager.java index be88f567eb6e1..d6f605e4674cb 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutManager.java @@ -26,23 +26,18 @@ import java.awt.Font; /** - * * @author Thomas Wuerthinger */ public abstract class LayoutManager { - void setCutEdges(boolean enable); - - void doLayout(LayoutGraph graph); - - public abstract void setCutEdges(boolean enable); - public static final Font TITLE_FONT = new Font("Arial", Font.BOLD, 14); public static final int SWEEP_ITERATIONS = 1; - public static final int CROSSING_ITERATIONS = 1; + public static final int CROSSING_ITERATIONS = 2; public static final int NODE_OFFSET = 8; public static final int LAYER_OFFSET = 8; public static final double SCALE_LAYER_PADDING = 1.5; + public abstract void setCutEdges(boolean enable); + public abstract void doLayout(LayoutGraph graph); } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java new file mode 100644 index 0000000000000..48599d78d7580 --- /dev/null +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 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 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.sun.hotspot.igv.hierarchicallayout; + +import com.sun.hotspot.igv.layout.Vertex; +import java.awt.Point; +import java.util.Set; + +public interface LayoutMover { + /** + * Moves a link by shifting its position along the X-axis. + * + * @param linkPos The current position of the link. + * @param shiftX The amount to shift the link along the X-axis. + */ + void moveLink(Point linkPos, int shiftX); + + /** + * Moves a set of vertices. + * + * @param movedVertices A set of vertices to be moved. + */ + void moveVertices(Set movedVertices); + + /** + * Moves a single vertex. + * + * @param movedVertex The vertex to be moved. + */ + void moveVertex(Vertex movedVertex); + + boolean isFreeForm(); +} + diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java index 931ef1f2024ea..7568484740e30 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java @@ -23,30 +23,37 @@ */ package com.sun.hotspot.igv.hierarchicallayout; +import static com.sun.hotspot.igv.hierarchicallayout.LayoutManager.NODE_OFFSET; import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Vertex; import java.awt.Dimension; import java.awt.Point; import java.util.*; -import static com.sun.hotspot.igv.hierarchicallayout.LayoutManager.NODE_OFFSET; - +/** + * Represents a node in a hierarchical graph layout. + * A LayoutNode can be either an actual vertex from the graph or a dummy node inserted during the layout process. + * It stores layout-related properties such as position, size, margins, and connections to predecessor and successor nodes. + */ public class LayoutNode { + // Comparator constants for sorting LayoutNodes in various ways public static final Comparator LAYOUT_NODE_DEGREE_COMPARATOR = Comparator.comparingInt(LayoutNode::getDegree); public static final Comparator NODE_POS_COMPARATOR = Comparator.comparingInt(LayoutNode::getPos); public static final Comparator NODE_X_COMPARATOR = Comparator.comparingInt(LayoutNode::getX); - public static final Comparator CROSSING_NODE_COMPARATOR = Comparator.comparingDouble(LayoutNode::getWeightedPosition); - public static final Comparator DUMMY_NODES_FIRST = Comparator.comparing(LayoutNode::isDummy).reversed(); - public static final Comparator NODE_PROCESSING_DOWN_COMPARATOR = DUMMY_NODES_FIRST.thenComparingInt(LayoutNode::getOutDegree); - public static final Comparator NODE_PROCESSING_UP_COMPARATOR = DUMMY_NODES_FIRST.thenComparing(LayoutNode::getInDegree); - public static final Comparator DUMMY_NODES_THEN_OPTIMAL_X = DUMMY_NODES_FIRST.thenComparing(LayoutNode::getOptimalX); + public static final Comparator NODE_CROSSING_COMPARATOR = Comparator.comparingInt(LayoutNode::getCrossingNumber); + + // Default dimensions for dummy nodes public static final int DUMMY_HEIGHT = 1; public static final int DUMMY_WIDTH = 1; - + private Vertex vertex; // Associated graph vertex; null for dummy nodes + private final List preds = new ArrayList<>(); // Incoming edges + private final List succs = new ArrayList<>(); // Outgoing edges + private final HashMap> reversedLinkStartPoints = new HashMap<>(); // Start points of reversed edges + private final HashMap> reversedLinkEndPoints = new HashMap<>(); // End points of reversed edges + // Layout properties private int layer = -1; - private int optimal_x; private int x; private int y; private int width; @@ -55,22 +62,34 @@ public class LayoutNode { private int bottomMargin; private int rightMargin; private int leftMargin; + private int pos = -1; // Position within its layer + private boolean reverseLeft = false; + private int crossingNumber = 0; - private final Vertex vertex; // Only used for non-dummy nodes, otherwise null - - private final List preds = new ArrayList<>(); - private final List succs = new ArrayList<>(); - private final HashMap> reversedLinkStartPoints = new HashMap<>(); - private final HashMap> reversedLinkEndPoints = new HashMap<>(); - private int pos = -1; // Position within layer - - private float weightedPosition = 0; + /** + * Constructs a LayoutNode associated with the given Vertex. + * Initializes the node's size based on the vertex's dimensions. + * + * @param v The Vertex associated with this LayoutNode. If null, the node is a dummy node. + */ public LayoutNode(Vertex v) { vertex = v; initSize(); } + /** + * Constructs a dummy LayoutNode + */ + public LayoutNode() { + this(null); + } + + /** + * Initializes the size and margins of the node. + * If the node represents a real vertex, it uses the vertex's size. + * Dummy nodes use default dimensions. + */ public void initSize() { if (vertex == null) { height = DUMMY_HEIGHT; @@ -80,124 +99,216 @@ public void initSize() { height = size.height; width = size.width; } - setTopMargin(0); - setBottomMargin(0); - setLeftMargin(0); - setRightMargin(0); + topMargin = 0; + bottomMargin = 0; + leftMargin = 0; + rightMargin = 0; } - public int calculateOptimalPositionDown() { + public int getCrossingNumber() { + return crossingNumber; + } + + public void setCrossingNumber(int crossingNumber) { + this.crossingNumber = crossingNumber; + } + + public int calculateOptimalXFromPredecessors(boolean useMedian) { int numPreds = preds.size(); + + // If there are no predecessors, retain the current x position if (numPreds == 0) { return getX(); } + // Collect the x positions from all predecessor edges List positions = new ArrayList<>(numPreds); for (LayoutEdge edge : preds) { positions.add(edge.getStartX() - edge.getRelativeToX()); } - Collections.sort(positions); - int midIndex = numPreds / 2; - return (numPreds % 2 == 0) - ? (positions.get(midIndex - 1) + positions.get(midIndex)) / 2 - : positions.get(midIndex); + if (useMedian) { + // Calculate the median position + Collections.sort(positions); + int midIndex = numPreds / 2; + + if (numPreds % 2 == 0) { + // Even number of predecessors: average the two middle values + return (positions.get(midIndex - 1) + positions.get(midIndex)) / 2; + } else { + // Odd number of predecessors: take the middle value + return positions.get(midIndex); + } + } else { + // Calculate the average position + long sum = 0; + for (int pos : positions) { + sum += pos; + } + // Integer division is used; adjust as needed for rounding + return (int) (sum / numPreds); + } } - public int calculateOptimalPositionUp() { + + public int calculateOptimalXFromSuccessors(boolean useMedian) { int numSuccs = succs.size(); + + // If there are no successors, retain the current x position if (numSuccs == 0) { return getX(); } + // Collect the x positions from all successor edges List positions = new ArrayList<>(numSuccs); for (LayoutEdge edge : succs) { positions.add(edge.getEndX() - edge.getRelativeFromX()); } - Collections.sort(positions); - int midIndex = numSuccs / 2; - return (numSuccs % 2 == 0) - ? (positions.get(midIndex - 1) + positions.get(midIndex)) / 2 - : positions.get(midIndex); - } - - public LayoutNode() { - this(null); + if (useMedian) { + // Calculate the median position + Collections.sort(positions); + int midIndex = numSuccs / 2; + + if (numSuccs % 2 == 0) { + // Even number of successors: average the two middle values + return (positions.get(midIndex - 1) + positions.get(midIndex)) / 2; + } else { + // Odd number of successors: take the middle value + return positions.get(midIndex); + } + } else { + // Calculate the average position + long sum = 0; + for (int pos : positions) { + sum += pos; + } + // Integer division is used; adjust as needed for rounding + return (int) (sum / numSuccs); + } } + /** + * Calculates the node's out-degree (number of outgoing edges). + * + * @return The out-degree of the node. + */ public int getOutDegree() { return succs.size(); } + /** + * Calculates the node's in-degree (number of incoming edges). + * + * @return The in-degree of the node. + */ public int getInDegree() { return preds.size(); } + /** + * Calculates the total degree of the node (sum of in-degree and out-degree). + * + * @return The total degree of the node. + */ public int getDegree() { return preds.size() + succs.size(); } - public float averagePosition() { - float totalWeightedPosition = 0; - float totalWeight = 0; - - for (LayoutEdge predEdge : preds) { - LayoutNode predNode = predEdge.getFrom(); - int weight = predNode.getDegree(); - totalWeightedPosition += weight * predEdge.getStartX(); - totalWeight += weight; - } - for (LayoutEdge succEdge : succs) { - LayoutNode succNode = succEdge.getTo(); - int weight = succNode.getDegree(); - totalWeightedPosition += weight * succEdge.getEndX(); - totalWeight += weight; - } - - // Calculate the (weighted) average position for the node based on neighbor positions and weights (degree) - return totalWeight > 0 ? totalWeightedPosition / totalWeight : 0; - } - + /** + * Gets the left boundary (excluding left margin) of the node. + * + * @return The x-coordinate of the left boundary. + */ public int getLeft() { return x + leftMargin; } + /** + * Gets the outer left boundary (including left margin) of the node. + * + * @return The x-coordinate of the outer left boundary. + */ public int getOuterLeft() { return x; } + /** + * Gets the total width of the node, including left and right margins. + * + * @return The total outer width. + */ public int getOuterWidth() { return leftMargin + width + rightMargin; } + /** + * Gets the total height of the node, including top and bottom margins. + * + * @return The total outer height. + */ public int getOuterHeight() { return topMargin + height + bottomMargin; } + public int getHeight() { + return height; + } + + /** + * Gets the right boundary (excluding right margin) of the node. + * + * @return The x-coordinate of the right boundary. + */ public int getRight() { return x + leftMargin + width; } + /** + * Gets the outer right boundary (including right margin) of the node. + * + * @return The x-coordinate of the outer right boundary. + */ public int getOuterRight() { return x + leftMargin + width + rightMargin; } + /** + * Gets the horizontal center point of the node. + * + * @return The x-coordinate of the center. + */ public int getCenterX() { return x + leftMargin + (width / 2); } + /** + * Gets the top boundary (excluding top margin) of the node. + * + * @return The y-coordinate of the top boundary. + */ public int getTop() { return y + topMargin; } + /** + * Gets the bottom boundary (excluding bottom margin) of the node. + * + * @return The y-coordinate of the bottom boundary. + */ public int getBottom() { return y + topMargin + height; } + /** + * Checks if the node is a dummy node. + * + * @return True if the node is a dummy node; false otherwise. + */ public boolean isDummy() { return vertex == null; } + @Override public String toString() { if (vertex != null) { @@ -207,14 +318,6 @@ public String toString() { } } - public int getOptimalX() { - return optimal_x; - } - - public void setOptimalX(int optimal_x) { - this.optimal_x = optimal_x; - } - public int getX() { return x; } @@ -235,18 +338,6 @@ public int getWidth() { return width; } - public void setWidth(int width) { - this.width = width; - } - - public int getHeight() { - return height; - } - - public void setHeight(int height) { - this.height = height; - } - public int getLayer() { return layer; } @@ -255,72 +346,78 @@ public void setLayer(int layer) { this.layer = layer; } - public int getLeftMargin() { - return leftMargin; + /** + * Centers the node by setting equal top and bottom margins. + * The larger of the two margins is applied to both. + */ + public void centerNode() { + int offset = Math.max(topMargin, bottomMargin); + topMargin = offset; + bottomMargin = offset; } - public void setLeftMargin(int leftMargin) { - this.leftMargin = leftMargin; + public Vertex getVertex() { + return vertex; } - public int getTopMargin() { - return topMargin; + public void setVertex(Vertex vertex) { + this.vertex = vertex; } - public void setTopMargin(int topMargin) { - this.topMargin = topMargin; + public boolean hasPredecessors() { + return !preds.isEmpty(); } - public int getRightMargin() { - return rightMargin; + public boolean hasSuccessors() { + return !succs.isEmpty(); } - public void setRightMargin(int rightMargin) { - this.rightMargin = rightMargin; + public void clearSuccessors() { + succs.clear(); } - public int getBottomMargin() { - return bottomMargin; + public void clearPredecessors() { + preds.clear(); } - public void setBottomMargin(int bottomMargin) { - this.bottomMargin = bottomMargin; + public List getSuccessors() { + return Collections.unmodifiableList(succs); } - public Vertex getVertex() { - return vertex; + public List getSuccessorsRaw() { + return succs; } - public List getPreds() { + public List getPredecessors() { + return Collections.unmodifiableList(preds); + } + + public List getPredecessorsRaw() { return preds; } - public boolean hasPreds() { - return !preds.isEmpty(); + public void addSuccessor(LayoutEdge successor) { + succs.add(successor); } - public boolean hasSuccs() { - return !succs.isEmpty(); + public void removeSuccessor(LayoutEdge successor) { + succs.remove(successor); } - public List getSuccs() { - return succs; + public void addPredecessor(LayoutEdge predecessor) { + preds.add(predecessor); } - public Map> groupSuccessorsByX() { - Map> result = new HashMap<>(); - for (LayoutEdge succEdge : succs) { - result.computeIfAbsent(succEdge.getRelativeFromX(), k -> new ArrayList<>()).add(succEdge); - } - return result; + public void removePredecessor(LayoutEdge predecessor) { + preds.remove(predecessor); } - public HashMap> getReversedLinkStartPoints() { - return reversedLinkStartPoints; + public Map> getReversedLinkStartPoints() { + return Collections.unmodifiableMap(reversedLinkStartPoints); } - public HashMap> getReversedLinkEndPoints() { - return reversedLinkEndPoints; + public Map> getReversedLinkEndPoints() { + return Collections.unmodifiableMap(reversedLinkEndPoints); } public int getPos() { @@ -331,17 +428,53 @@ public void setPos(int pos) { this.pos = pos; } - public float getWeightedPosition() { - return weightedPosition; + /** + * Groups the successor edges by their relative x-coordinate from the current node. + * + * @return A map of relative x-coordinate to list of successor edges. + */ + public Map> groupSuccessorsByX() { + Map> result = new HashMap<>(); + for (LayoutEdge succEdge : succs) { + result.computeIfAbsent(succEdge.getRelativeFromX(), k -> new ArrayList<>()).add(succEdge); + } + return result; } - public void setWeightedPosition(float weightedPosition) { - this.weightedPosition = weightedPosition; + private int getBackedgeCrossingScore() { + int score = 0; + for (LayoutEdge predEdge : preds) { + if (predEdge.isReversed()) { + List points = reversedLinkEndPoints.get(predEdge.getLink()); + if (points != null) { + int x0 = points.get(points.size() - 1).x; + int xn = points.get(0).x; + int startPoint = predEdge.getStartX(); + int endPoint = predEdge.getEndX(); + int win = (x0 < xn) ? (startPoint - endPoint) : (endPoint - startPoint); + score += win; + } + } + } + for (LayoutEdge succEdge : succs) { + if (succEdge.isReversed()) { + List points = reversedLinkStartPoints.get(succEdge.getLink()); + if (points != null) { + int x0 = points.get(points.size() - 1).x; + int xn = points.get(0).x; + int startPoint = succEdge.getStartX(); + int endPoint = succEdge.getEndX(); + int win = (x0 > xn) ? (startPoint - endPoint) : (endPoint - startPoint); + score += win; + } + } + } + return score; } - private void computeReversedStartPoints() { - TreeMap> sortedDownMap = new TreeMap<>(Collections.reverseOrder()); - for (LayoutEdge succEdge : getSuccs()) { + private boolean computeReversedStartPoints(boolean left) { + TreeMap> sortedDownMap = left ? new TreeMap<>() : new TreeMap<>(Collections.reverseOrder()); + for (LayoutEdge succEdge : succs) { if (succEdge.isReversed()) { succEdge.setRelativeFromX(succEdge.getLink().getTo().getRelativePosition().x); sortedDownMap.putIfAbsent(succEdge.getRelativeFromX(), new ArrayList<>()); @@ -350,16 +483,17 @@ private void computeReversedStartPoints() { } int offset = NODE_OFFSET + LayoutNode.DUMMY_WIDTH; - int currentX = getWidth(); + int offsetX = left ? -offset : offset; + int currentX = left ? 0 : width; int startY = 0; int currentY = 0; for (Map.Entry> entry : sortedDownMap.entrySet()) { int startX = entry.getKey(); ArrayList reversedSuccs = entry.getValue(); - currentX += offset; + currentX += offsetX; currentY -= offset; - setTopMargin(getTopMargin() + offset); + topMargin += offset; ArrayList startPoints = new ArrayList<>(); startPoints.add(new Point(currentX, currentY)); @@ -367,16 +501,20 @@ private void computeReversedStartPoints() { startPoints.add(new Point(startX, startY)); for (LayoutEdge revEdge : reversedSuccs) { revEdge.setRelativeFromX(currentX); - getReversedLinkStartPoints().put(revEdge.getLink(), startPoints); + reversedLinkStartPoints.put(revEdge.getLink(), startPoints); } } - setLeftMargin(getLeftMargin()); - setRightMargin(getRightMargin() + (sortedDownMap.size() * offset)); + if (left) { + leftMargin += sortedDownMap.size() * offset; + } else { + rightMargin += sortedDownMap.size() * offset; + } + return !sortedDownMap.isEmpty(); } - private void computeReversedEndPoints() { - TreeMap> sortedUpMap = new TreeMap<>(Collections.reverseOrder()); - for (LayoutEdge predEdge : getPreds()) { + private boolean computeReversedEndPoints(boolean left) { + TreeMap> sortedUpMap = left ? new TreeMap<>() : new TreeMap<>(Collections.reverseOrder()); + for (LayoutEdge predEdge : preds) { if (predEdge.isReversed()) { predEdge.setRelativeToX(predEdge.getLink().getFrom().getRelativePosition().x); sortedUpMap.putIfAbsent(predEdge.getRelativeToX(), new ArrayList<>()); @@ -385,16 +523,17 @@ private void computeReversedEndPoints() { } int offset = NODE_OFFSET + LayoutNode.DUMMY_WIDTH; - int currentX = getWidth(); - int startY = getHeight(); - int currentY = getHeight(); + int offsetX = left ? -offset : offset; + int currentX = left ? 0 : getWidth(); + int startY = height; + int currentY = height; for (Map.Entry> entry : sortedUpMap.entrySet()) { int startX = entry.getKey(); ArrayList reversedPreds = entry.getValue(); - currentX += offset; + currentX += offsetX; currentY += offset; - setBottomMargin(getBottomMargin() + offset); + bottomMargin += offset; ArrayList endPoints = new ArrayList<>(); endPoints.add(new Point(currentX, currentY)); @@ -402,19 +541,39 @@ private void computeReversedEndPoints() { endPoints.add(new Point(startX, startY)); for (LayoutEdge revEdge : reversedPreds) { revEdge.setRelativeToX(currentX); - getReversedLinkEndPoints().put(revEdge.getLink(), endPoints); + reversedLinkEndPoints.put(revEdge.getLink(), endPoints); } } - setLeftMargin(getLeftMargin()); - setRightMargin(getRightMargin() + (sortedUpMap.size() * offset)); + if (left) { + leftMargin += sortedUpMap.size() * offset; + } else { + rightMargin += sortedUpMap.size() * offset; + } + + return !sortedUpMap.isEmpty(); } - public void computeReversedLinkPoints() { + public void computeReversedLinkPoints(boolean reverseLeft) { + this.reverseLeft = reverseLeft; initSize(); - getReversedLinkStartPoints().clear(); - getReversedLinkEndPoints().clear(); + reversedLinkStartPoints.clear(); + reversedLinkEndPoints.clear(); + + boolean hasReversedDown = computeReversedStartPoints(reverseLeft); + boolean hasReversedUP = computeReversedEndPoints(hasReversedDown != reverseLeft); + } - computeReversedStartPoints(); - computeReversedEndPoints(); + public boolean isReverseRight() { + return !reverseLeft; + } + + public void optimizeBackEdgeCrossing() { + if (reversedLinkStartPoints.isEmpty() && reversedLinkEndPoints.isEmpty()) return; + int orig_score = getBackedgeCrossingScore(); + computeReversedLinkPoints(isReverseRight()); + int reverse_score = getBackedgeCrossingScore(); + if (orig_score > reverse_score) { + computeReversedLinkPoints(isReverseRight()); + } } } diff --git a/src/utils/IdealGraphVisualizer/Settings/src/main/java/com/sun/hotspot/igv/settings/Settings.java b/src/utils/IdealGraphVisualizer/Settings/src/main/java/com/sun/hotspot/igv/settings/Settings.java index 41af4a1d26282..480fb43ea5b8d 100644 --- a/src/utils/IdealGraphVisualizer/Settings/src/main/java/com/sun/hotspot/igv/settings/Settings.java +++ b/src/utils/IdealGraphVisualizer/Settings/src/main/java/com/sun/hotspot/igv/settings/Settings.java @@ -37,6 +37,7 @@ public static class DefaultView { public static final int CLUSTERED_SEA_OF_NODES = 1; public static final int CONTROL_FLOW_GRAPH = 2; public static final int STABLE_SEA_OF_NODES = 3; + public static final int INTERACTIVE_FREE_NODES = 4; } public static final String NODE_TEXT = "nodeText"; diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java index 1c2b9383b02b3..74f687689633b 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java @@ -45,6 +45,7 @@ import javax.swing.*; import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS; import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS; +import javax.swing.border.Border; import javax.swing.event.UndoableEditEvent; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.CannotRedoException; @@ -80,10 +81,17 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl private final LayerWidget mainLayer; private final LayerWidget blockLayer; private final LayerWidget connectionLayer; + private final Widget shadowWidget; + private final Widget pointerWidget; private final DiagramViewModel model; private ModelState modelState; private boolean rebuilding; + + private final FreeInteractiveLayoutManager freeInteractiveLayoutManager; private final HierarchicalStableLayoutManager hierarchicalStableLayoutManager; + private HierarchicalLayoutManager seaLayoutManager; + private LayoutMover layoutMover; + /** * The alpha level of partially visible figures. @@ -93,7 +101,7 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl /** * The offset of the graph to the border of the window showing it. */ - public static final int BORDER_SIZE = 100; + public static final int BORDER_SIZE = 50; public static final int UNDOREDO_LIMIT = 100; public static final int SCROLL_UNIT_INCREMENT = 80; public static final int SCROLL_BLOCK_INCREMENT = 400; @@ -222,7 +230,7 @@ public void filteredChanged(SelectionCoordinator coordinator) { public void colorSelectedFigures(Color color) { for (Figure figure : model.getSelectedFigures()) { - figure.setColor(color); + figure.setCustomColor(color); FigureWidget figureWidget = getWidget(figure); if (figureWidget != null) { figureWidget.refreshColor(); @@ -323,19 +331,26 @@ public void select(Widget widget, Point localLocation, boolean invertSelection) getActions().addAction(selectAction); + Border emptyBorder = BorderFactory.createEmptyBorder(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE); + blockLayer = new LayerWidget(this); + blockLayer.setBorder(emptyBorder); addChild(blockLayer); connectionLayer = new LayerWidget(this); - LayerWidget paddedLayer = new LayerWidget(this); - paddedLayer.setBorder(BorderFactory.createEmptyBorder(50, 50, 50, 50)); // Adds 50px padding on all sides - paddedLayer.addChild(connectionLayer); - addChild(paddedLayer); + connectionLayer.setBorder(emptyBorder); + addChild(connectionLayer); mainLayer = new LayerWidget(this); + mainLayer.setBorder(emptyBorder); addChild(mainLayer); - setBorder(BorderFactory.createLineBorder(Color.white, BORDER_SIZE)); + pointerWidget = new Widget(DiagramScene.this); + addChild(pointerWidget); + + shadowWidget = new Widget(DiagramScene.this); + addChild(shadowWidget); + setLayout(LayoutFactory.createAbsoluteLayout()); getActions().addAction(mouseZoomAction); getActions().addAction(ActionFactory.createPopupMenuAction((widget, localLocation) -> createPopupMenu())); @@ -496,7 +511,9 @@ public void ancestorResized(HierarchyEvent e) { } }); + freeInteractiveLayoutManager = new FreeInteractiveLayoutManager(); hierarchicalStableLayoutManager = new HierarchicalStableLayoutManager(); + seaLayoutManager = new HierarchicalLayoutManager(); this.model = model; modelState = new ModelState(model); @@ -590,6 +607,166 @@ private void updateFigureWidths() { } } + private MoveProvider getFigureMoveProvider() { + return new MoveProvider() { + + private boolean hasMoved = false; // Flag to track movement + private int startLayerY; + + private void setFigureShadow(Figure f) { + FigureWidget fw = getWidget(f); + Color c = f.getColor(); + Border border = new FigureWidget.RoundedBorder(new Color(0,0,0, 50), 1); + shadowWidget.setBorder(border); + shadowWidget.setBackground(new Color(c.getRed(), c.getGreen(), c.getBlue(), 50)); + shadowWidget.setPreferredLocation(fw.getPreferredLocation()); + shadowWidget.setPreferredSize(f.getSize()); + shadowWidget.setVisible(true); + shadowWidget.setOpaque(true); + shadowWidget.revalidate(); + shadowWidget.repaint(); + } + + private void setMovePointer(Figure f) { + Border border = new FigureWidget.RoundedBorder(Color.RED, 1); + pointerWidget.setBorder(border); + pointerWidget.setBackground(Color.RED); + pointerWidget.setPreferredBounds(new Rectangle(0, 0, 3, f.getSize().height)); + pointerWidget.setVisible(false); + pointerWidget.setOpaque(true); + } + + + @Override + public void movementStarted(Widget widget) { + if (layoutMover == null) return; // Do nothing if layoutMover is not available + + widget.bringToFront(); + startLayerY = widget.getLocation().y; + hasMoved = false; // Reset the movement flag + if (layoutMover.isFreeForm()) return; + Set
selectedFigures = model.getSelectedFigures(); + if (selectedFigures.size() == 1) { + Figure selectedFigure = selectedFigures.iterator().next(); + setFigureShadow(selectedFigure); + setMovePointer(selectedFigure); + } + } + + @Override + public void movementFinished(Widget widget) { + shadowWidget.setVisible(false); + pointerWidget.setVisible(false); + if (layoutMover == null || !hasMoved) return; // Do nothing if layoutMover is not available or no movement occurred + rebuilding = true; + + Set
movedFigures = new HashSet<>(model.getSelectedFigures()); + for (Figure figure : movedFigures) { + FigureWidget fw = getWidget(figure); + figure.setPosition(new Point(fw.getLocation().x, fw.getLocation().y)); + } + + layoutMover.moveVertices(movedFigures); + rebuildConnectionLayer(); + + for (FigureWidget fw : getVisibleFigureWidgets()) { + fw.updatePosition(); + } + + validateAll(); + addUndo(); + rebuilding = false; + } + + private static final int MAGNET_SIZE = 5; + + private int magnetToStartLayerY(Widget widget, Point location) { + int shiftY = location.y - widget.getLocation().y; + if (Math.abs(location.y - startLayerY) <= MAGNET_SIZE) { + if (Math.abs(widget.getLocation().y - startLayerY) > MAGNET_SIZE) { + shiftY = startLayerY - widget.getLocation().y; + } else { + shiftY = 0; + } + } + return shiftY; + } + + @Override + public Point getOriginalLocation(Widget widget) { + if (layoutMover == null) return widget.getLocation(); // default behavior + return ActionFactory.createDefaultMoveProvider().getOriginalLocation(widget); + } + + @Override + public void setNewLocation(Widget widget, Point location) { + if (layoutMover == null) return; // Do nothing if layoutMover is not available + hasMoved = true; // Mark that a movement occurred + + int shiftX = location.x - widget.getLocation().x; + int shiftY; + if (layoutMover.isFreeForm()) { + shiftY = location.y - widget.getLocation().y; + } else { + shiftY = magnetToStartLayerY(widget, location); + } + List
selectedFigures = new ArrayList<>( model.getSelectedFigures()); + selectedFigures.sort(Comparator.comparingInt(f -> f.getPosition().x)); + for (Figure figure : selectedFigures) { + FigureWidget fw = getWidget(figure); + for (InputSlot inputSlot : figure.getInputSlots()) { + if (inputSlotToLineWidget.containsKey(inputSlot)) { + for (LineWidget lw : inputSlotToLineWidget.get(inputSlot)) { + Point toPt = lw.getTo(); + int xTo = toPt.x + shiftX; + int yTo = toPt.y + shiftY; + lw.setTo(new Point(xTo, yTo)); + if (!layoutMover.isFreeForm()) { + Point fromPt = lw.getFrom(); + lw.setFrom(new Point(fromPt.x + shiftX, fromPt.y)); + LineWidget pred = lw.getPredecessor(); + pred.setTo(new Point(pred.getTo().x + shiftX, pred.getTo().y)); + pred.revalidate(); + } + lw.revalidate(); + } + } + } + for (OutputSlot outputSlot : figure.getOutputSlots()) { + if (outputSlotToLineWidget.containsKey(outputSlot)) { + for (LineWidget lw : outputSlotToLineWidget.get(outputSlot)) { + Point fromPt = lw.getFrom(); + int xFrom = fromPt.x + shiftX; + int yFrom = fromPt.y + shiftY; + lw.setFrom(new Point(xFrom, yFrom)); + if (!layoutMover.isFreeForm()) { + Point toPt = lw.getTo(); + lw.setTo(new Point(toPt.x + shiftX, toPt.y)); + for (LineWidget succ : lw.getSuccessors()) { + succ.setFrom(new Point(succ.getFrom().x + shiftX, succ.getFrom().y)); + succ.revalidate(); + } + } + lw.revalidate(); + } + } + } + Point newLocation = new Point(fw.getLocation().x + shiftX, fw.getLocation().y + shiftY); + ActionFactory.createDefaultMoveProvider().setNewLocation(fw, newLocation); + } + + if (selectedFigures.size() == 1 && !layoutMover.isFreeForm()) { + FigureWidget fw = getWidget(selectedFigures.iterator().next()); + pointerWidget.setVisible(true); + Point newLocation = new Point(fw.getLocation().x + shiftX -3, fw.getLocation().y + shiftY); + ActionFactory.createDefaultMoveProvider().setNewLocation(pointerWidget, newLocation); + } + connectionLayer.revalidate(); + connectionLayer.repaint(); + } + }; + } + private void rebuildMainLayer() { mainLayer.removeChildren(); for (Figure figure : getModel().getDiagram().getFigures()) { @@ -598,6 +775,7 @@ private void rebuildMainLayer() { figureWidget.getActions().addAction(ActionFactory.createPopupMenuAction(figureWidget)); figureWidget.getActions().addAction(selectAction); figureWidget.getActions().addAction(hoverAction); + figureWidget.getActions().addAction(ActionFactory.createMoveAction(null, getFigureMoveProvider())); addObject(figure, figureWidget); mainLayer.addChild(figureWidget); @@ -660,12 +838,15 @@ private void relayout() { Set
visibleFigures = getVisibleFigures(); Set visibleConnections = getVisibleConnections(); - if (getModel().getShowStableSea()) { + if (getModel().getShowFreeInteractive()) { + doFreeInteractiveLayout(visibleFigures, visibleConnections); + } + else if (getModel().getShowStableSea()) { doStableSeaLayout(visibleFigures, visibleConnections); } else if (getModel().getShowSea()) { doSeaLayout(visibleFigures, visibleConnections); } else if (getModel().getShowBlocks()) { - doClusteredLayout(visibleConnections); + doClusteredLayout(visibleFigures, visibleConnections); } else if (getModel().getShowCFG()) { doCFGLayout(visibleFigures, visibleConnections); } @@ -730,24 +911,40 @@ private boolean isVisibleFigureConnection(FigureConnection figureConnection) { return w1.isVisible() && w2.isVisible(); } + private void doFreeInteractiveLayout(Set
visibleFigures, Set visibleConnections) { + layoutMover = freeInteractiveLayoutManager; + freeInteractiveLayoutManager.setCutEdges(model.getCutEdges()); + freeInteractiveLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); + } + private void doStableSeaLayout(Set
visibleFigures, Set visibleConnections) { - hierarchicalStableLayoutManager.setCutEdges(model.getCutEdges()); - hierarchicalStableLayoutManager.updateLayout(visibleFigures, visibleConnections); + layoutMover = null; + boolean enable = model.getCutEdges(); + boolean previous = hierarchicalStableLayoutManager.getCutEdges(); + hierarchicalStableLayoutManager.setCutEdges(enable); + if (enable != previous) { + hierarchicalStableLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); + } else { + hierarchicalStableLayoutManager.updateLayout(visibleFigures, visibleConnections); + } } private void doSeaLayout(Set
visibleFigures, Set visibleConnections) { - HierarchicalLayoutManager seaLayoutManager = new HierarchicalLayoutManager(); + seaLayoutManager = new HierarchicalLayoutManager(); + layoutMover = seaLayoutManager; seaLayoutManager.setCutEdges(model.getCutEdges()); seaLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); } - private void doClusteredLayout(Set visibleConnections) { + private void doClusteredLayout(Set
visibleFigures, Set visibleConnections) { + layoutMover = null; HierarchicalClusterLayoutManager clusterLayoutManager = new HierarchicalClusterLayoutManager(); clusterLayoutManager.setCutEdges(model.getCutEdges()); - clusterLayoutManager.doLayout(new LayoutGraph(visibleConnections, new HashSet<>())); + clusterLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); } private void doCFGLayout(Set
visibleFigures, Set visibleConnections) { + layoutMover = null; HierarchicalCFGLayoutManager cfgLayoutManager = new HierarchicalCFGLayoutManager(getVisibleBlockConnections(), getVisibleBlocks()); cfgLayoutManager.setCutEdges(model.getCutEdges()); cfgLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); @@ -766,6 +963,83 @@ private boolean shouldAnimate() { private final Point specialNullPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE); + private MoveProvider getFigureConnectionMoveProvider() { + return new MoveProvider() { + + Point startLocation; + Point originalPosition; + + @Override + public void movementStarted(Widget widget) { + if (layoutMover == null) return; // Do nothing if layoutMover is not available + LineWidget lw = (LineWidget) widget; + startLocation = lw.getClientAreaLocation(); + originalPosition = lw.getFrom(); + } + + @Override + public void movementFinished(Widget widget) { + if (layoutMover == null) return; // Do nothing if layoutMover is not available + LineWidget lineWidget = (LineWidget) widget; + if (lineWidget.getPredecessor() == null) return; + if (lineWidget.getSuccessors().isEmpty()) return; + if (lineWidget.getFrom().x != lineWidget.getTo().x) return; + + int shiftX = lineWidget.getClientAreaLocation().x - startLocation.x; + if (shiftX == 0) return; + + rebuilding = true; + layoutMover.moveLink(originalPosition, shiftX); + rebuildConnectionLayer(); + for (FigureWidget fw : getVisibleFigureWidgets()) { + fw.updatePosition(); + } + validateAll(); + addUndo(); + rebuilding = false; + } + + @Override + public Point getOriginalLocation(Widget widget) { + if (layoutMover == null) return widget.getLocation(); // default behavior + LineWidget lineWidget = (LineWidget) widget; + return lineWidget.getClientAreaLocation(); + } + + @Override + public void setNewLocation(Widget widget, Point location) { + if (layoutMover == null) return; // Do nothing if layoutMover is not available + LineWidget lineWidget = (LineWidget) widget; + if (lineWidget.getPredecessor() == null) return; + if (lineWidget.getSuccessors().isEmpty()) return; + if (lineWidget.getFrom().x != lineWidget.getTo().x) return; + + int shiftX = location.x - lineWidget.getClientAreaLocation().x; + if (shiftX == 0) return; + + Point oldFrom = lineWidget.getFrom(); + Point newFrom = new Point(oldFrom.x + shiftX, oldFrom.y); + + Point oldTo = lineWidget.getTo(); + Point newTo = new Point(oldTo.x + shiftX, oldTo.y); + + lineWidget.setTo(newTo); + lineWidget.setFrom(newFrom); + lineWidget.revalidate(); + + LineWidget predecessor = lineWidget.getPredecessor(); + Point toPt = predecessor.getTo(); + predecessor.setTo(new Point(toPt.x + shiftX, toPt.y)); + predecessor.revalidate(); + + for (LineWidget successor : lineWidget.getSuccessors()) { + Point fromPt = successor.getFrom(); + successor.setFrom(new Point(fromPt.x + shiftX, fromPt.y)); + successor.revalidate(); + } + } + }; + } private void processOutputSlot(OutputSlot outputSlot, List connections, int controlPointIndex, Point lastPoint, LineWidget predecessor) { Map> pointMap = new HashMap<>(connections.size()); @@ -824,6 +1098,7 @@ private void processOutputSlot(OutputSlot outputSlot, List con connectionLayer.addChild(newPredecessor); addObject(new ConnectionSet(connectionList), newPredecessor); newPredecessor.getActions().addAction(hoverAction); + newPredecessor.getActions().addAction(ActionFactory.createMoveAction(null, getFigureConnectionMoveProvider())); } processOutputSlot(outputSlot, connectionList, controlPointIndex + 1, currentPoint, newPredecessor); @@ -1060,7 +1335,11 @@ private void rebuildConnectionLayer() { for (Figure figure : getModel().getDiagram().getFigures()) { for (OutputSlot outputSlot : figure.getOutputSlots()) { List connectionList = new ArrayList<>(outputSlot.getConnections()); - processOutputSlot(outputSlot, connectionList, 0, null, null); + if (layoutMover != null && layoutMover.isFreeForm()) { + processFreeForm(outputSlot, connectionList); + } else { + processOutputSlot(outputSlot, connectionList, 0, null, null); + } } } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewModel.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewModel.java index 3081825025fe2..ffee8064e50cc 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewModel.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewModel.java @@ -63,6 +63,7 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene private final ChangedEvent selectedNodesChangedEvent = new ChangedEvent<>(this); private final ChangedEvent hiddenNodesChangedEvent = new ChangedEvent<>(this); private ChangedListener titleChangedListener = g -> {}; + private boolean showFreeInteractive; private boolean showStableSea; private boolean showSea; private boolean showBlocks; @@ -104,6 +105,17 @@ public void setGlobalSelection(boolean enable, boolean fire) { } } + public boolean getShowFreeInteractive() { + return showFreeInteractive; + } + + public void setShowFreeInteractive(boolean enable) { + showFreeInteractive = enable; + if (enable) { + diagramChangedEvent.fire(); + } + } + public boolean getShowStableSea() { return showStableSea; } @@ -224,6 +236,7 @@ public DiagramViewModel(InputGraph graph) { globalSelection = GlobalSelectionAction.get(GlobalSelectionAction.class).isSelected(); cutEdges = CutEdgesAction.get(CutEdgesAction.class).isSelected(); + showFreeInteractive = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.INTERACTIVE_FREE_NODES; showStableSea = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.STABLE_SEA_OF_NODES; showSea = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.SEA_OF_NODES; showBlocks = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.CLUSTERED_SEA_OF_NODES; diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java index 56c83517d2f39..ff51b8928f676 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java @@ -140,6 +140,7 @@ public EditorTopComponent(DiagramViewModel diagramViewModel) { cardLayout = new CardLayout(); centerPanel = new JPanel(); centerPanel.setLayout(cardLayout); + centerPanel.setOpaque(true); centerPanel.setBackground(Color.WHITE); satelliteComponent = scene.createSatelliteView(); satelliteComponent.setSize(200, 200); @@ -175,6 +176,11 @@ public void mouseMoved(MouseEvent e) {} toolBar.addSeparator(); ButtonGroup layoutButtons = new ButtonGroup(); + JToggleButton freeInteractiveLayoutButton = new JToggleButton(new EnableFreeLayoutAction(this)); + freeInteractiveLayoutButton.setSelected(diagramViewModel.getShowFreeInteractive()); + layoutButtons.add(freeInteractiveLayoutButton); + toolBar.add(freeInteractiveLayoutButton); + JToggleButton stableSeaLayoutButton = new JToggleButton(new EnableStableSeaLayoutAction(this)); stableSeaLayoutButton.setSelected(diagramViewModel.getShowStableSea()); layoutButtons.add(stableSeaLayoutButton); diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ColorAction.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ColorAction.java index 7a38587eb3855..b0d27d8be04d7 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ColorAction.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ColorAction.java @@ -82,6 +82,7 @@ public String getName() { private static final JLabel selectedColorLabel = new JLabel("Preview"); private static final JColorChooser colorChooser = new JColorChooser(Color.WHITE); + private static final Color NO_COLOR = new Color(0, 0, 0, 0); public ColorAction() { initializeComponents(); @@ -89,8 +90,8 @@ public ColorAction() { private void initializeComponents() { selectedColorLabel.setPreferredSize(new Dimension(3 * 32, 32)); - selectedColorLabel.setOpaque(true); - selectedColorLabel.setBackground(Color.WHITE); + selectedColorLabel.setOpaque(false); // Allow transparency + selectedColorLabel.setBackground(NO_COLOR); // Set transparent background selectedColorLabel.setForeground(Color.BLACK); // Set text color selectedColorLabel.setHorizontalAlignment(SwingConstants.CENTER); // Center the text @@ -100,6 +101,7 @@ private void initializeComponents() { Color selectedColor = colorChooser.getColor(); if (selectedColor != null) { selectedColorLabel.setBackground(selectedColor); + selectedColorLabel.setOpaque(selectedColor.getAlpha() != 0); selectedColorLabel.setForeground(FigureWidget.getTextColor(selectedColor)); } }); @@ -118,10 +120,26 @@ private void initializeComponents() { colorButton.setPreferredSize(new Dimension(16, 16)); colorButton.addActionListener(e -> { selectedColorLabel.setBackground(color); + selectedColorLabel.setOpaque(color.getAlpha() != 0); selectedColorLabel.setForeground(FigureWidget.getTextColor(color)); }); colorsPanel.add(colorButton); } + + // Add "No Color" button + JButton noColorButton = new JButton("No Color"); + noColorButton.setOpaque(false); + noColorButton.setContentAreaFilled(false); + noColorButton.setBorderPainted(true); + noColorButton.setPreferredSize(new Dimension(60, 16)); + noColorButton.addActionListener(e -> { + selectedColorLabel.setBackground(NO_COLOR); + selectedColorLabel.setOpaque(false); + selectedColorLabel.setForeground(Color.BLACK); + }); + colorsPanel.add(noColorButton); + + // Add the preview label colorsPanel.add(selectedColorLabel, 0); colorsPanel.revalidate(); colorsPanel.repaint(); @@ -148,9 +166,10 @@ public void performAction(DiagramViewModel model) { dialogLoc = dialogHolder[0].getLocation(); // OK button action Color selectedColor = selectedColorLabel.getBackground(); - if (selectedColor != null) { - editor.colorSelectedFigures(selectedColor); + if (selectedColor.equals(NO_COLOR)) { + selectedColor = null; } + editor.colorSelectedFigures(selectedColor); }, null // Cancel button action ); diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java new file mode 100644 index 0000000000000..201fc51e15e9e --- /dev/null +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 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 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.sun.hotspot.igv.view.actions; + +import com.sun.hotspot.igv.view.EditorTopComponent; +import java.beans.PropertyChangeEvent; + +public class EnableFreeLayoutAction extends EnableLayoutAction { + + public EnableFreeLayoutAction(EditorTopComponent etc) { + super(etc); + } + + @Override + protected String iconResource() { + return "com/sun/hotspot/igv/view/images/dynamic.png"; + } + + @Override + protected String getDescription() { + return "Show dynamic free layout"; + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + editor.getModel().setShowFreeInteractive(this.isSelected()); + } +} diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java index c44e598f53632..91cc55ce74fc9 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java @@ -268,6 +268,7 @@ public Figure getFigure() { @Override protected void paintChildren() { + refreshColor(); Composite oldComposite = null; if (boundary) { oldComposite = getScene().getGraphics().getComposite(); diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java index ff7e955137aa8..4ad93684ed660 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java @@ -32,10 +32,8 @@ import java.awt.*; import java.awt.geom.CubicCurve2D; import java.awt.geom.Line2D; -import java.util.ArrayList; -import java.util.HashSet; +import java.util.*; import java.util.List; -import java.util.Set; import javax.swing.JPopupMenu; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; @@ -69,6 +67,8 @@ public class LineWidget extends Widget implements PopupMenuProvider { private boolean popupVisible; private final boolean isBold; private final boolean isDashed; + private int fromControlYOffset; + private int toControlYOffset; public LineWidget(DiagramScene scene, OutputSlot s, List connections, Point from, Point to, LineWidget predecessor, boolean isBold, boolean isDashed) { super(scene); @@ -118,13 +118,13 @@ private void computeClientArea() { maxY = Math.max(maxY, to.y + toControlYOffset); } else { // reversed edges if (from.x - to.x > 0) { - minX = Math.min(minX, from.x - 150); - maxX = Math.max(maxX, to.x + 150); + minX = Math.min(minX, from.x); + maxX = Math.max(maxX, to.x); minY = Math.min(minY, from.y + fromControlYOffset); maxY = Math.max(maxY, to.y + toControlYOffset); } else { - minX = Math.min(minX, from.x + 150); - maxX = Math.max(maxX, to.x - 150); + minX = Math.min(minX, from.x); + maxX = Math.max(maxX, to.x); minY = Math.min(minY, from.y + fromControlYOffset); maxY = Math.max(maxY, to.y + toControlYOffset); } @@ -169,9 +169,6 @@ public void setTo(Point to) { computeClientArea(); } - private int fromControlYOffset; - private int toControlYOffset; - public void setFromControlYOffset(int fromControlYOffset) { this.fromControlYOffset = fromControlYOffset; computeClientArea(); @@ -190,6 +187,14 @@ public Point getTo() { return to; } + public LineWidget getPredecessor() { + return predecessor; + } + + public List getSuccessors() { + return Collections.unmodifiableList(successors); + } + private void addSuccessor(LineWidget widget) { this.successors.add(widget); } @@ -321,14 +326,6 @@ protected void notifyStateChanged(ObjectState previousState, ObjectState state) } } - public LineWidget getPredecessor() { - return predecessor; - } - - public List getSuccessors() { - return successors; - } - private void highlightPredecessors(boolean enable) { LineWidget predecessorLineWidget = predecessor; while (predecessorLineWidget != null) { diff --git a/src/utils/IdealGraphVisualizer/View/src/main/resources/com/sun/hotspot/igv/view/images/dynamic.png b/src/utils/IdealGraphVisualizer/View/src/main/resources/com/sun/hotspot/igv/view/images/dynamic.png new file mode 100644 index 0000000000000000000000000000000000000000..781b98176f8657d705976bf1ceb15e78a07f8098 GIT binary patch literal 2218 zcmZ{l4Ny}@5P&a${A2)8X;r8a!>EXmmmfh0Nh&4^!GZx|8`Nq;^1xR@UM6{w0OBu- z$oPj+9I^N}PKAzCDB@s6l&X*sDdJci1Vqpxh1LQirJ&M#Awob%a=Fd!e!I7KZ!ejW zh!7{L8x;WHBn}lt!P$iz_O|fbqirkUA0>V&3oULTR+{(%|#X@Z`wq^P+wes$<|dgyVCY?L}G@s zbJ5&#HaohYdk^hJ_wHF)=?8l<#pnBv zq~wQg?X4~CEkxYrt!x{-K6m!#1q~gC)y-XN+hd0mLG`SF-si6p?o}C^2i<#^M{74o zQ2WZ#TN6nBgm3WBcZl?ejfyzr7p#uJs|`X{lmSy8buD()JhW zZ<=@@jsid(P zEGB~$005iGW^q~Y!=yoh!{qw00I<&92kam-7mlVgR-+kQ&RCDh&<28|g<>(BKhEk9 zw(SHoIA}s+bhCycZjyuI;gi}8fX!)4DkEg!UvL$8B15jkV^M}7QA5T9JOdYsi6|kb z84^`$9oN98n>@HsCe;X?W^y4C_;eX8M2Krqnm>cdVA2It8jZ%&D&x3OqSa;`{^HZO z5QK({AW2C{j3f>N*Ty5Pz`#I+$wt_0U+CehOI8zdgRffWITGYU91*HhXfX|e;c6Ng zS00P&2|k@pCK`Q>tP?e0W0}-Cb6K!JgtQ$ zsTfVA(Q8$vjZi8ORD~v@YC;D&7BX5tERRR7oGZjtxHeiNSD*qG&s2=1YHTM)yd{_c zRmnscjHrXVCSduSnTdu;!I-H)3&TWkoetFs@kFu&!AFmwIk}~e1rIl$YeI0X6vtHp z%g*2-6Ri`#a9oMSC5vFQ3CJGuWx)?q`k{wLa0?#`nmi)T;#S*FGIr*CwvVGp-;{a5 z#}SQNA&o7ZQy@E$wR!hLuaar9lGy+Hy5p-4`Y4T^E}lOg%n7yu>l$rqBIWMqtV3BX zO<%4c08#e~b-z_>>(36`57$OhKP~Iubwzimu#oEAQ`B|wbt?T(u7>#;m%UJEa?g9%|qB#<=!(gq)#xOIk9+ Date: Wed, 27 Nov 2024 00:20:09 +0100 Subject: [PATCH 03/30] Revert "AllSoFar" This reverts commit 2f4e3fd57fbd393879a0ccee3585c3dff3cbcfbb. --- .../com/sun/hotspot/igv/data/InputNode.java | 24 - .../java/com/sun/hotspot/igv/graph/Block.java | 3 +- .../hotspot/igv/graph/BlockConnection.java | 32 - .../com/sun/hotspot/igv/graph/Figure.java | 38 +- .../hotspot/igv/graph/FigureConnection.java | 20 +- .../com/sun/hotspot/igv/graph/InputSlot.java | 14 - .../com/sun/hotspot/igv/graph/OutputSlot.java | 13 - .../java/com/sun/hotspot/igv/graph/Slot.java | 23 +- .../igv/hierarchicallayout/ClusterEdge.java | 9 +- .../ClusterIngoingConnection.java | 11 +- .../ClusterInputSlotNode.java | 22 +- .../igv/hierarchicallayout/ClusterNode.java | 21 +- .../ClusterOutgoingConnection.java | 11 +- .../ClusterOutputSlotNode.java | 47 +- .../FreeInteractiveLayoutManager.java | 324 ----- .../HierarchicalCFGLayoutManager.java | 36 +- .../HierarchicalClusterLayoutManager.java | 82 +- .../HierarchicalLayoutManager.java | 573 ++++----- .../HierarchicalStableLayoutManager.java | 885 ++++++++------ .../InterClusterConnection.java | 9 +- .../igv/hierarchicallayout/LayoutEdge.java | 140 +-- .../igv/hierarchicallayout/LayoutGraph.java | 1083 ++++------------- .../igv/hierarchicallayout/LayoutLayer.java | 183 +-- .../igv/hierarchicallayout/LayoutManager.java | 11 +- .../igv/hierarchicallayout/LayoutMover.java | 55 - .../igv/hierarchicallayout/LayoutNode.java | 445 +++---- .../sun/hotspot/igv/settings/Settings.java | 1 - .../sun/hotspot/igv/view/DiagramScene.java | 309 +---- .../hotspot/igv/view/DiagramViewModel.java | 13 - .../hotspot/igv/view/EditorTopComponent.java | 6 - .../hotspot/igv/view/actions/ColorAction.java | 27 +- .../view/actions/EnableFreeLayoutAction.java | 49 - .../igv/view/widgets/FigureWidget.java | 1 - .../hotspot/igv/view/widgets/LineWidget.java | 33 +- .../sun/hotspot/igv/view/images/dynamic.png | Bin 2218 -> 0 bytes 35 files changed, 1384 insertions(+), 3169 deletions(-) delete mode 100644 src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java delete mode 100644 src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java delete mode 100644 src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java delete mode 100644 src/utils/IdealGraphVisualizer/View/src/main/resources/com/sun/hotspot/igv/view/images/dynamic.png diff --git a/src/utils/IdealGraphVisualizer/Data/src/main/java/com/sun/hotspot/igv/data/InputNode.java b/src/utils/IdealGraphVisualizer/Data/src/main/java/com/sun/hotspot/igv/data/InputNode.java index 48a000bf7b9f5..62b7aa4c03455 100644 --- a/src/utils/IdealGraphVisualizer/Data/src/main/java/com/sun/hotspot/igv/data/InputNode.java +++ b/src/utils/IdealGraphVisualizer/Data/src/main/java/com/sun/hotspot/igv/data/InputNode.java @@ -23,7 +23,6 @@ */ package com.sun.hotspot.igv.data; -import java.awt.Color; import java.util.Objects; /** @@ -68,27 +67,4 @@ public boolean equals(Object obj) { public String toString() { return "Node " + id + " " + getProperties().toString(); } - - public void setCustomColor(Color color) { - if (color != null) { - String hexColor = String.format("#%08X", color.getRGB()); - getProperties().setProperty("color", hexColor); - } else { - getProperties().setProperty("color", null); - } - } - - public Color getCustomColor() { - String hexColor = getProperties().get("color"); - if (hexColor != null) { - try { - String hex = hexColor.startsWith("#") ? hexColor.substring(1) : hexColor; - int argb = (int) Long.parseLong(hex, 16); - return new Color(argb, true); - } catch (Exception ignored) { - return null; - } - } - return null; - } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java index bca39d6b338c4..011fd8239b157 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java @@ -100,8 +100,7 @@ public String toString() { @Override public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null || getClass() != obj.getClass()) return false; + if (obj == null) return false; Block other = (Block) obj; return inputBlock.equals(other.inputBlock); } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java index 3c5dfb0bb9376..4648734ee80db 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java @@ -27,7 +27,6 @@ import java.awt.Color; import java.awt.Point; import java.util.List; -import java.util.Objects; public class BlockConnection implements Connection { @@ -100,35 +99,4 @@ public void setControlPoints(List list) { public boolean hasSlots() { return false; } - - /** - * Determines equality based on sourceBlock, destinationBlock, and label. - * - * @param o the object to compare with - * @return true if equal, false otherwise - */ - @Override - public boolean equals(Object o) { - // Reference equality check - if (this == o) return true; - - // Type check - if (!(o instanceof BlockConnection)) return false; - - // Cast and field comparisons - BlockConnection that = (BlockConnection) o; - return Objects.equals(this.sourceBlock, that.sourceBlock) && - Objects.equals(this.destinationBlock, that.destinationBlock) && - Objects.equals(this.label, that.label); - } - - /** - * Generates hash code based on sourceBlock, destinationBlock, and label. - * - * @return the hash code - */ - @Override - public int hashCode() { - return Objects.hash(sourceBlock, destinationBlock, label); - } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java index bdfa466dced88..4c7d01a4cebb8 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java @@ -148,12 +148,7 @@ public void setColor(Color color) { } public Color getColor() { - Color customColor = inputNode.getCustomColor(); - if (customColor != null) { - return customColor; - } else { - return color; - } + return color; } public void setWarning(String warning) { @@ -379,6 +374,19 @@ public Dimension getSize() { return new Dimension(width, height); } + @Override + public boolean equals(Object o) { + if (!(o instanceof Figure)) { + return false; + } + return getInputNode().equals(((Figure) o).getInputNode()); + } + + @Override + public int hashCode() { + return getInputNode().hashCode(); + } + @Override public String toString() { return idString; @@ -401,22 +409,4 @@ public boolean isRoot() { public int compareTo(Vertex f) { return toString().compareTo(f.toString()); } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Figure other)) { - return false; - } - return Objects.equals(this.getInputNode(), other.getInputNode()); - } - - @Override - public int hashCode() { - return Objects.hash(getInputNode()); - } - - public void setCustomColor(Color color) { - inputNode.setCustomColor(color); - } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java index 526881f4318f4..e26c618f49e3a 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java @@ -24,13 +24,11 @@ package com.sun.hotspot.igv.graph; import com.sun.hotspot.igv.layout.Cluster; -import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Port; import java.awt.Color; import java.awt.Point; import java.util.ArrayList; import java.util.List; -import java.util.Objects; /** * @@ -163,19 +161,15 @@ public boolean hasSlots() { @Override public boolean equals(Object o) { - if (this == o) return true; // Reference equality check - if (!(o instanceof FigureConnection)) return false; // Type check - - FigureConnection that = (FigureConnection) o; + if (!(o instanceof FigureConnection)) { + return false; + } - // Compare source and target ports using their own equals() methods - return Objects.equals(this.outputSlot, that.outputSlot) && - Objects.equals(this.inputSlot, that.inputSlot); + return getInputSlot().getFigure().equals(((FigureConnection)o).getInputSlot().getFigure()) + && getOutputSlot().getFigure().equals(((FigureConnection)o).getOutputSlot().getFigure()) + && getInputSlot().getPosition() == ((FigureConnection)o).getInputSlot().getPosition() + && getOutputSlot().getPosition() == ((FigureConnection) o).getOutputSlot().getPosition(); } - @Override - public int hashCode() { - return Objects.hash(outputSlot, inputSlot); - } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java index e0f5ae73b6d42..2ba127cc8f707 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java @@ -25,7 +25,6 @@ import java.awt.Point; import java.util.List; -import java.util.Objects; /** * @@ -85,17 +84,4 @@ public String getToolTipText() { public String toString() { return "InputSlot[figure=" + this.getFigure().toString() + ", position=" + getPosition() + "]"; } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof InputSlot that)) return false; - if (!super.equals(o)) return false; - return this.originalIndex == that.originalIndex; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), originalIndex); - } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java index 14d1f1d52cd54..a2555ce36584b 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java @@ -24,7 +24,6 @@ package com.sun.hotspot.igv.graph; import java.awt.Point; -import java.util.Objects; /** * @@ -62,16 +61,4 @@ public Point getRelativePosition() { public String toString() { return "OutputSlot[figure=" + this.getFigure().toString() + ", position=" + getPosition() + "]"; } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - return super.equals(o); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode()); - } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java index 3aa739d0cb979..1ddc145bb89f9 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java @@ -34,7 +34,10 @@ import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.image.BufferedImage; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; /** * @@ -175,23 +178,5 @@ public Vertex getVertex() { public abstract int getPosition(); public abstract void setPosition(int position); - - @Override - public boolean equals(Object o) { - // Reference equality check - if (this == o) return true; - - // Type check - if (!(o instanceof Slot other)) return false; - - // Compare immutable and identity-defining fields - return this.wantedIndex == other.wantedIndex && - Objects.equals(this.figure, other.figure); - } - - @Override - public int hashCode() { - return Objects.hash(figure, wantedIndex); - } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterEdge.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterEdge.java index 97ca9b99ad917..166d14b419abd 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterEdge.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterEdge.java @@ -30,6 +30,7 @@ import java.util.List; /** + * * @author Thomas Wuerthinger */ public class ClusterEdge implements Link { @@ -61,14 +62,14 @@ public Cluster getToCluster() { return to.getCluster(); } - public List getControlPoints() { - return points; - } - public void setControlPoints(List p) { this.points = p; } + public List getControlPoints() { + return points; + } + @Override public String toString() { return from + "->" + to; diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterIngoingConnection.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterIngoingConnection.java index 29a5b7dfa272c..70c34e71ca9c7 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterIngoingConnection.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterIngoingConnection.java @@ -31,13 +31,14 @@ import java.util.List; /** + * * @author Thomas Wuerthinger */ public class ClusterIngoingConnection implements Link { + private List controlPoints; private final Port inputSlot; private final Port outputSlot; - private List controlPoints; public ClusterIngoingConnection(ClusterInputSlotNode inputSlotNode, Link c) { this.controlPoints = new ArrayList<>(); @@ -62,11 +63,11 @@ public Cluster getToCluster() { return null; } - public List getControlPoints() { - return controlPoints; - } - public void setControlPoints(List p) { this.controlPoints = p; } + + public List getControlPoints() { + return controlPoints; + } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterInputSlotNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterInputSlotNode.java index 6c27d54409943..da6842c791ce8 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterInputSlotNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterInputSlotNode.java @@ -30,15 +30,22 @@ import java.awt.Point; /** + * * @author Thomas Wuerthinger */ public class ClusterInputSlotNode implements Vertex { + private Point position; private final Port inputSlot; private final Port outputSlot; private final ClusterNode blockNode; + private final String id; - private Point position; + + @Override + public String toString() { + return id; + } public ClusterInputSlotNode(ClusterNode n, String id) { this.blockNode = n; @@ -84,11 +91,6 @@ public String toString() { }; } - @Override - public String toString() { - return id; - } - public Port getInputSlot() { return inputSlot; } @@ -101,14 +103,14 @@ public Dimension getSize() { return new Dimension(0, 0); } - public Point getPosition() { - return position; - } - public void setPosition(Point p) { this.position = p; } + public Point getPosition() { + return position; + } + public Cluster getCluster() { return null; } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterNode.java index 068278f5b41bd..382fec9b651fa 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterNode.java @@ -33,21 +33,22 @@ import java.util.*; /** + * * @author Thomas Wuerthinger */ public class ClusterNode implements Vertex { public static final int PADDING = 8; - private final Set subNodes; - private final Set subEdges; - private final String name; - private final int headerVerticalSpace; - private final Dimension emptySize; private Cluster cluster; private Port inputSlot; + private final Set subNodes; private Dimension size; private Point position; + private final Set subEdges; private boolean root; + private final String name; + private final int headerVerticalSpace; + private final Dimension emptySize; public ClusterNode(Cluster cluster, String name, int headerVerticalSpace, Dimension emptySize) { this.subNodes = new HashSet<>(); @@ -141,7 +142,7 @@ private void calculateSize() { // Normalize coordinates for (Vertex n : subNodes) { n.setPosition(new Point(n.getPosition().x - minX, - n.getPosition().y - minY + headerVerticalSpace)); + n.getPosition().y - minY + headerVerticalSpace)); } for (Link l : subEdges) { @@ -205,14 +206,14 @@ public void setCluster(Cluster c) { cluster = c; } - public boolean isRoot() { - return root; - } - public void setRoot(boolean b) { root = b; } + public boolean isRoot() { + return root; + } + public int compareTo(Vertex o) { return toString().compareTo(o.toString()); } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutgoingConnection.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutgoingConnection.java index 360532b7dca00..a5267020a0e72 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutgoingConnection.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutgoingConnection.java @@ -31,13 +31,14 @@ import java.util.List; /** + * * @author Thomas Wuerthinger */ public class ClusterOutgoingConnection implements Link { + private List intermediatePoints; private final Port inputSlot; private final Port outputSlot; - private List intermediatePoints; public ClusterOutgoingConnection(ClusterOutputSlotNode outputSlotNode, Link c) { this.intermediatePoints = new ArrayList<>(); @@ -61,11 +62,11 @@ public Cluster getToCluster() { return null; } - public List getControlPoints() { - return intermediatePoints; - } - public void setControlPoints(List p) { this.intermediatePoints = p; } + + public List getControlPoints() { + return intermediatePoints; + } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutputSlotNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutputSlotNode.java index edd913f345b16..b1eb6b0578b2a 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutputSlotNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/ClusterOutputSlotNode.java @@ -30,17 +30,31 @@ import java.awt.Point; /** + * * @author Thomas Wuerthinger */ public class ClusterOutputSlotNode implements Vertex { + private Point position; private final Port inputSlot; private final Port outputSlot; private final ClusterNode blockNode; - private final String id; - private Point position; private Cluster cluster; private ClusterOutgoingConnection conn; + private final String id; + + public void setOutgoingConnection(ClusterOutgoingConnection c) { + this.conn = c; + } + + public ClusterOutgoingConnection getOutgoingConnection() { + return conn; + } + + @Override + public String toString() { + return id; + } public ClusterOutputSlotNode(ClusterNode n, String id) { this.blockNode = n; @@ -86,31 +100,18 @@ public String toString() { }; } - public ClusterOutgoingConnection getOutgoingConnection() { - return conn; - } - - public void setOutgoingConnection(ClusterOutgoingConnection c) { - this.conn = c; - } - - @Override - public String toString() { - return id; - } - public Dimension getSize() { return new Dimension(0, 0); } - public Point getPosition() { - return position; - } - public void setPosition(Point p) { this.position = p; } + public Point getPosition() { + return position; + } + public Port getInputSlot() { return inputSlot; } @@ -119,14 +120,14 @@ public Port getOutputSlot() { return outputSlot; } - public Cluster getCluster() { - return cluster; - } - public void setCluster(Cluster c) { cluster = c; } + public Cluster getCluster() { + return cluster; + } + public boolean isRoot() { return false; } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java deleted file mode 100644 index d221d78f8620c..0000000000000 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 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 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ -package com.sun.hotspot.igv.hierarchicallayout; - -import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.LAYOUT_NODE_DEGREE_COMPARATOR; -import com.sun.hotspot.igv.layout.Link; -import com.sun.hotspot.igv.layout.Vertex; -import java.awt.Point; -import java.util.*; - -public class FreeInteractiveLayoutManager extends LayoutManager implements LayoutMover { - - private boolean cutEdges = false; - - private static final int LINE_OFFSET = 10; - - private Map layoutNodes; - - private LayoutGraph prevGraph; - - private final Random random = new Random(42); - - // Constants for offsets and displacements - private static final int MAX_OFFSET_AROUND_NEIGHBOR = 200; // Max offset for random positioning around a neighbor - private static final int MAX_OFFSET_AROUND_ORIGIN = 200; // Max offset for random positioning around origin - private static final int DISPLACEMENT_RANGE_BARYCENTER = 100; // Displacement range for barycenter calculation - private static final int DISPLACEMENT_RANGE_SINGLE = 200; - - // Create a comparator to sort nodes by the number of unassigned neighbors - private final Comparator LeastUnassignedNeighborsComparator = Comparator.comparingInt(node -> { - Vertex vertex = node.getVertex(); - int unassignedNeighbors = 0; - for (Vertex neighborVertex : prevGraph.getNeighborVertices(vertex)) { - if (!layoutNodes.containsKey(neighborVertex)) { - unassignedNeighbors++; - } - } - return unassignedNeighbors; - }); - - public FreeInteractiveLayoutManager() { - this.cutEdges = false; - this.layoutNodes = new HashMap<>(); - this.prevGraph = null; - } - - @Override - public void moveLink(Point linkPos, int shiftX) {} - - @Override - public void moveVertices(Set movedVertices) { - for (Vertex v : movedVertices) { - moveVertex(v); - } - } - - @Override - public void moveVertex(Vertex vertex) { - assert prevGraph.containsVertex(vertex); - LayoutNode layoutNode = layoutNodes.get(vertex); - layoutNode.setX(vertex.getPosition().x); - layoutNode.setY(vertex.getPosition().y); - for (Link link : prevGraph.getAllLinks(vertex)) { - setLinkControlPoints(link); - } - } - - @Override - public boolean isFreeForm() { - return true; - } - - public void setCutEdges(boolean enable) { - this.cutEdges = enable; - } - - @Override - public void doLayout(LayoutGraph graph) { - prevGraph = graph; - if (layoutNodes.isEmpty()) { - HierarchicalLayoutManager manager = new HierarchicalLayoutManager(); - manager.doLayout(graph); - for (LayoutNode node : graph.getLayoutNodes()) { - node.initSize(); - layoutNodes.put(node.getVertex(), node); - } - graph.clearLayout(); - } else { - // add new vertices to layoutNodes, x/y from barycenter - List newLayoutNodes = new ArrayList<>(); - - // Set up layout nodes for each vertex - for (Vertex vertex : prevGraph.getVertices()) { - if (!layoutNodes.containsKey(vertex)) { - LayoutNode addedNode = new LayoutNode(vertex); - addedNode.initSize(); - newLayoutNodes.add(addedNode); - } - } - - positionNewLayoutNodes(newLayoutNodes); - } - - // Write back vertices - for (Vertex vertex : prevGraph.getVertices()) { - LayoutNode layoutNode = layoutNodes.get(vertex); - vertex.setPosition(new Point(layoutNode.getLeft(), layoutNode.getTop())); - } - - // Write back links - for (Link link : prevGraph.getLinks()) { - setLinkControlPoints(link); - } - } - - public void positionNewLayoutNodes(List newLayoutNodes) { - Random random = new Random(); - - // First pass: Initial positioning based on unassigned neighbors - newLayoutNodes.sort(LeastUnassignedNeighborsComparator); - - for (LayoutNode node : newLayoutNodes) { - Vertex vertex = node.getVertex(); - - // Gather assigned neighbors - List assignedNeighbors = new ArrayList<>(); - for (Vertex neighborVertex : prevGraph.getNeighborVertices(vertex)) { - if (layoutNodes.containsKey(neighborVertex)) { - assignedNeighbors.add(layoutNodes.get(neighborVertex)); - } - } - - if (!assignedNeighbors.isEmpty()) { - if (assignedNeighbors.size() == 1) { - // Single neighbor: position around the neighbor - setPositionAroundSingleNode(node, assignedNeighbors.get(0), DISPLACEMENT_RANGE_SINGLE); - } else { - // Multiple neighbors: Calculate barycenter with displacement - calculateBarycenterWithDisplacement(node, assignedNeighbors, DISPLACEMENT_RANGE_BARYCENTER); - } - } else { - // No neighbors: Position randomly around (0, 0) - setRandomPositionAroundOrigin(node, random); - } - - // Add the new node to the layout - layoutNodes.put(vertex, node); - } - - // Second pass: Refine positions based on neighbor degree - newLayoutNodes.sort(LAYOUT_NODE_DEGREE_COMPARATOR.reversed()); - - // Collect all nodes (existing and new) - Collection allNodes = layoutNodes.values(); - - for (LayoutNode node : newLayoutNodes) { - Vertex vertex = node.getVertex(); - - // Gather assigned neighbors - List assignedNeighbors = new ArrayList<>(); - for (Vertex neighborVertex : prevGraph.getNeighborVertices(vertex)) { - if (layoutNodes.containsKey(neighborVertex)) { - assignedNeighbors.add(layoutNodes.get(neighborVertex)); - } - } - - if (!assignedNeighbors.isEmpty()) { - // Refine position based on force-based method - applyForceBasedAdjustment(node, assignedNeighbors, allNodes); - } - - // Ensure node's position remains updated in the layout - layoutNodes.put(vertex, node); - } - } - - private void applyForceBasedAdjustment(LayoutNode node, List assignedNeighbors, Collection allNodes) { - // Constants for force-based adjustment - final int ITERATIONS = 50; - final double REPULSION_CONSTANT = 1000; - final double SPRING_CONSTANT = 0.2; - final double DAMPING = 0.8; - final double IDEAL_LENGTH = 100; - - double posX = node.getX(); - double posY = node.getY(); - double dx = 0, dy = 0; // Displacement - - for (int i = 0; i < ITERATIONS; i++) { - double netForceX = 0; - double netForceY = 0; - - // Repulsive forces from all other nodes - for (LayoutNode otherNode : allNodes) { - if (otherNode == node) continue; // Skip self - - double deltaX = posX - otherNode.getX(); - double deltaY = posY - otherNode.getY(); - double distanceSquared = deltaX * deltaX + deltaY * deltaY + 0.01; // Prevent division by zero - double distance = Math.sqrt(distanceSquared); - - // Repulsive force (Coulomb's law) - double repulsiveForce = REPULSION_CONSTANT / distanceSquared; - netForceX += (deltaX / distance) * repulsiveForce; - netForceY += (deltaY / distance) * repulsiveForce; - } - - // Attractive forces to assigned neighbors - for (LayoutNode neighbor : assignedNeighbors) { - double deltaX = neighbor.getX() - posX; - double deltaY = neighbor.getY() - posY; - double distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY + 0.01); // Prevent division by zero - - // Attractive force (Hooke's law) - double displacement = distance - IDEAL_LENGTH; - double attractiveForce = SPRING_CONSTANT * displacement; - netForceX += (deltaX / distance) * attractiveForce; - netForceY += (deltaY / distance) * attractiveForce; - } - - // Update displacement with damping - dx = (dx + netForceX) * DAMPING; - dy = (dy + netForceY) * DAMPING; - - // Update node position - posX += dx; - posY += dy; - } - - // Set final position - node.setX((int) posX); - node.setY((int) posY); - } - - - // Utility method: position around a given node - private void setPositionAroundSingleNode(LayoutNode node, LayoutNode neighbor, int displacement) { - boolean neighborIsPredecessor = prevGraph.isPredecessorVertex(node.getVertex(), neighbor.getVertex()); - boolean neighborIsSuccessor = prevGraph.isSuccessorVertex(node.getVertex(), neighbor.getVertex()); - - int shiftY = 0; - if (neighborIsPredecessor) { - shiftY = displacement; - } else if (neighborIsSuccessor) { - shiftY = -displacement; - } - assert shiftY != 0; - - - int randomY = neighbor.getY() + random.nextInt(MAX_OFFSET_AROUND_NEIGHBOR + 1) + shiftY; - int randomX = neighbor.getX() + random.nextInt(MAX_OFFSET_AROUND_NEIGHBOR + 1); - node.setX(randomX); - node.setY(randomY); - } - - // Utility method: Random position around origin - private void setRandomPositionAroundOrigin(LayoutNode node, Random random) { - int randomX = random.nextInt(MAX_OFFSET_AROUND_ORIGIN + 1); - int randomY = random.nextInt(MAX_OFFSET_AROUND_ORIGIN + 1); - node.setX(randomX); - node.setY(randomY); - } - - // Utility method: Calculate barycenter with displacement - private void calculateBarycenterWithDisplacement(LayoutNode node, List neighbors, int displacementRange) { - double barycenterX = 0, barycenterY = 0; - for (LayoutNode neighbor : neighbors) { - barycenterX += neighbor.getX(); - barycenterY += neighbor.getY(); - } - barycenterX /= neighbors.size(); - barycenterY /= neighbors.size(); - - // Add random displacement for slight separation - int displacementX = random.nextInt(displacementRange + 1); - int displacementY = random.nextInt(displacementRange + 1); - node.setX((int) barycenterX + displacementX); - node.setY((int) barycenterY + displacementY); - } - - private void setLinkControlPoints(Link link) { - if (link.getFrom().getVertex() == link.getTo().getVertex()) return; - - LayoutNode from = layoutNodes.get(link.getFrom().getVertex()); - LayoutNode to = layoutNodes.get(link.getTo().getVertex()); - int relativeFromX = link.getFrom().getRelativePosition().x; - int relativeToX = link.getTo().getRelativePosition().x; - - int startX = from.getLeft() + relativeFromX; - int startY = from.getBottom(); - int endX = to.getLeft() + relativeToX; - int endY = to.getTop(); - - Point startPoint = new Point(startX, startY); - Point endPoint = new Point(endX, endY); - List line = new ArrayList<>(); - line.add(startPoint); - line.add(new Point(startPoint.x, startPoint.y + LINE_OFFSET)); - line.add(new Point(endPoint.x, endPoint.y - LINE_OFFSET)); - line.add(endPoint); - link.setControlPoints(line); - } -} diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java index a27ab9cf01e61..baa4bbb458534 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java @@ -23,9 +23,7 @@ */ package com.sun.hotspot.igv.hierarchicallayout; -import com.sun.hotspot.igv.layout.Cluster; -import com.sun.hotspot.igv.layout.Link; -import com.sun.hotspot.igv.layout.Vertex; +import com.sun.hotspot.igv.layout.*; import java.awt.*; import java.util.*; @@ -35,8 +33,6 @@ public class HierarchicalCFGLayoutManager extends LayoutManager { private final HierarchicalLayoutManager manager; private final Set clusters; private final Set clusterLinks; - Map clusterNodesMap; - Map clusterEdgesMap; public HierarchicalCFGLayoutManager(Set clusterLinks, Set clusters) { this.clusterLinks = clusterLinks; @@ -45,9 +41,30 @@ public HierarchicalCFGLayoutManager(Set clusterLinks, Set clusterNodesMap; + Map clusterEdgesMap; + private static void doLinearLayout(ClusterNode clusterNode) { Cluster cluster = clusterNode.getCluster(); LayoutGraph graph = new LayoutGraph(clusterNode.getSubEdges(), clusterNode.getSubNodes()); @@ -61,11 +78,6 @@ private static void doLinearLayout(ClusterNode clusterNode) { clusterNode.updateSize(); } - @Override - public void setCutEdges(boolean enable) { - manager.setCutEdges(enable); - } - public void doLayout(LayoutGraph graph) { // Create cluster-level nodes and edges. clusterNodesMap = createClusterNodes(graph.getVertices()); @@ -98,7 +110,7 @@ private Map createClusterNodes(SortedSet vertices) for (Cluster cluster : clusters) { String blockLabel = "B" + cluster; Dimension emptySize = new Dimension(fontMetrics.stringWidth(blockLabel) + ClusterNode.PADDING, - fontMetrics.getHeight() + ClusterNode.PADDING); + fontMetrics.getHeight() + ClusterNode.PADDING); ClusterNode clusterNode = new ClusterNode(cluster, cluster.toString(), fontMetrics.getHeight(), emptySize); clusterNodes.put(cluster, clusterNode); } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java index 4bff737a8d9f3..9dba4d03c8bd5 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java @@ -23,73 +23,49 @@ */ package com.sun.hotspot.igv.hierarchicallayout; -import com.sun.hotspot.igv.layout.Cluster; -import com.sun.hotspot.igv.layout.Link; -import com.sun.hotspot.igv.layout.Port; -import com.sun.hotspot.igv.layout.Vertex; +import com.sun.hotspot.igv.layout.*; import java.awt.*; -import java.util.List; import java.util.*; +import java.util.List; /** + * * @author Thomas Wuerthinger */ public class HierarchicalClusterLayoutManager extends LayoutManager { + private final LayoutManager subManager; private final LayoutManager manager; - private final HashMap layoutMovers; - private final HashMap clusterNodes; - public HierarchicalClusterLayoutManager() { - this.manager = new HierarchicalLayoutManager(); - this.layoutMovers = new HashMap<>(); - this.clusterNodes = new HashMap<>(); + this.manager = new HierarchicalLayoutManager(); + this.subManager = new HierarchicalLayoutManager(); } @Override public void setCutEdges(boolean enable) { + subManager.setCutEdges(enable); manager.setCutEdges(enable); } - public LayoutMover getLayoutMover() { - return new LayoutMover() { - @Override - public void moveLink(Point linkPos, int shiftX) { - - } - - @Override - public void moveVertices(Set movedVertices) { - - } + public void doLayout(LayoutGraph graph, Set importantLinks) { + doLayout(graph); + } - @Override - public void moveVertex(Vertex movedVertex) { - Cluster cluster = movedVertex.getCluster(); - LayoutMover layoutMover = layoutMovers.get(cluster); - if (layoutMover != null) { - layoutMover.moveVertex(movedVertex); - ClusterNode n = clusterNodes.get(cluster); - n.updateSize(); - cluster.setBounds(new Rectangle(n.getPosition(), n.getSize())); - } - } + public void setSubManager(LayoutManager manager) { + this.subManager = manager; + } - @Override - public boolean isFreeForm() { - return false; - } - }; + public void setManager(LayoutManager manager) { + this.manager = manager; } public void doLayout(LayoutGraph graph) { - layoutMovers.clear(); - clusterNodes.clear(); HashMap> listsConnection = new HashMap<>(); HashMap> clusterInputSlotHash = new HashMap<>(); HashMap> clusterOutputSlotHash = new HashMap<>(); + HashMap clusterNodes = new HashMap<>(); HashMap> clusterInputSlotSet = new HashMap<>(); HashMap> clusterOutputSlotSet = new HashMap<>(); Set clusterEdges = new HashSet<>(); @@ -126,6 +102,21 @@ public void doLayout(LayoutGraph graph) { z++; } + // Add cluster edges + for (Cluster c : clusters) { + + ClusterNode start = clusterNodes.get(c); + + for (Cluster succ : c.getSuccessors()) { + ClusterNode end = clusterNodes.get(succ); + if (end != null && start != end) { + ClusterEdge e = new ClusterEdge(start, end); + clusterEdges.add(e); + interClusterEdges.add(e); + } + } + } + for (Vertex v : graph.getVertices()) { Cluster c = v.getCluster(); assert c != null : "Cluster of vertex " + v + " is null!"; @@ -152,7 +143,7 @@ public void doLayout(LayoutGraph graph) { ClusterOutputSlotNode outputSlotNode; outputSlotNode = clusterOutputSlotHash.get(fromCluster).get(fromPort); - inputSlotNode = clusterInputSlotHash.get(toCluster).get(toPort); // TODO: fix? + inputSlotNode = clusterInputSlotHash.get(toCluster).get(fromPort); if (outputSlotNode == null) { outputSlotNode = new ClusterOutputSlotNode(clusterNodes.get(fromCluster), "Out " + fromCluster.toString() + " " + fromPort); @@ -168,10 +159,7 @@ public void doLayout(LayoutGraph graph) { } if (inputSlotNode == null) { - inputSlotNode = new ClusterInputSlotNode( - clusterNodes.get(toCluster), - "In " + toCluster.toString() + " " + toPort // Use toPort here - ); + inputSlotNode = new ClusterInputSlotNode(clusterNodes.get(toCluster), "In " + toCluster.toString() + " " + fromPort); clusterInputSlotSet.get(toCluster).add(inputSlotNode); } @@ -190,13 +178,11 @@ public void doLayout(LayoutGraph graph) { for (Cluster c : clusters) { ClusterNode n = clusterNodes.get(c); - HierarchicalLayoutManager subManager = new HierarchicalLayoutManager(); subManager.doLayout(new LayoutGraph(n.getSubEdges(), n.getSubNodes())); n.updateSize(); - layoutMovers.put(c, subManager); } - Set roots = new LayoutGraph(interClusterEdges, new HashSet<>()).findRootVertices(); + Set roots = new LayoutGraph(interClusterEdges).findRootVertices(); for (Vertex v : roots) { assert v instanceof ClusterNode; ((ClusterNode) v).setRoot(true); diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java index 854aa7ebb4798..69a1251fd0e73 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java @@ -23,16 +23,15 @@ */ package com.sun.hotspot.igv.hierarchicallayout; +import static com.sun.hotspot.igv.hierarchicallayout.LayoutEdge.LAYOUT_EDGE_LAYER_COMPARATOR; import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.*; import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Vertex; import java.awt.Point; import java.util.*; -public class HierarchicalLayoutManager extends LayoutManager implements LayoutMover { - int maxLayerLength; - private LayoutGraph graph; +public class HierarchicalLayoutManager extends LayoutManager { public HierarchicalLayoutManager() { setCutEdges(false); @@ -44,11 +43,9 @@ public void setCutEdges(boolean enable) { } @Override - public void doLayout(LayoutGraph layoutGraph) { - layoutGraph.initializeLayout(); - - // STEP 1: Remove self edges and reverse edges - ReverseEdges.apply(layoutGraph); + public void setCutEdges(boolean enable) { + maxLayerLength = enable ? 10 : -1; + } // STEP 2: Assign layers and create dummy nodes LayerManager.apply(layoutGraph, maxLayerLength); @@ -61,77 +58,9 @@ public void doLayout(LayoutGraph layoutGraph) { // STEP 5: Write back to interface WriteResult.apply(layoutGraph); - - graph = layoutGraph; } - - - @Override - public void moveLink(Point linkPos, int shiftX) { - int layerNr = graph.findLayer(linkPos.y); - for (LayoutNode node : graph.getLayer(layerNr)) { - if (node.isDummy() && linkPos.x == node.getX()) { - LayoutLayer layer = graph.getLayer(layerNr); - if (layer.contains(node)) { - node.setX(linkPos.x + shiftX); - layer.sortNodesByX(); - break; - } - } - } - writeBack(); - } - - @Override - public void moveVertices(Set movedVertices) { - for (Vertex vertex : movedVertices) { - moveVertex(vertex); - } - writeBack(); - } - - private void writeBack() { - graph.optimizeBackEdgeCrossings(); - graph.straightenEdges(); - WriteResult.apply(graph); - } - - @Override - public void moveVertex(Vertex movedVertex) { - Point newLoc = movedVertex.getPosition(); - LayoutNode movedNode = graph.getLayoutNode(movedVertex); - assert !movedNode.isDummy(); - - int layerNr = graph.findLayer(newLoc.y + movedNode.getOuterHeight() / 2); - if (movedNode.getLayer() == layerNr) { // we move the node in the same layer - LayoutLayer layer = graph.getLayer(layerNr); - if (layer.contains(movedNode)) { - movedNode.setX(newLoc.x); - layer.sortNodesByX(); - } - } else { // only remove edges if we moved the node to a new layer - if (maxLayerLength > 0) return; // TODO: not implemented - graph.removeNodeAndEdges(movedNode); - layerNr = graph.insertNewLayerIfNeeded(movedNode, layerNr); - graph.addNodeToLayer(movedNode, layerNr); - movedNode.setX(newLoc.x); - graph.getLayer(layerNr).sortNodesByX(); - graph.removeEmptyLayers(); - graph.addEdges(movedNode, maxLayerLength); - } - } - - @Override - public boolean isFreeForm() { - return false; - } - - public List getNodes() { - return graph.getAllNodes(); - } - - public static class ReverseEdges { + static private class ReverseEdges { static public void apply(LayoutGraph graph) { removeSelfEdges(graph); @@ -139,32 +68,27 @@ static public void apply(LayoutGraph graph) { depthFirstSearch(graph); for (LayoutNode node : graph.getLayoutNodes()) { - node.computeReversedLinkPoints(false); + node.computeReversedLinkPoints(); } } private static void removeSelfEdges(LayoutGraph graph) { for (LayoutNode node : graph.getLayoutNodes()) { - // Collect self-edges first to avoid concurrent modification - List selfEdges = new ArrayList<>(); - for (LayoutEdge edge : node.getSuccessors()) { + Iterator edgeIterator = node.getSuccs().iterator(); + while (edgeIterator.hasNext()) { + LayoutEdge edge = edgeIterator.next(); if (edge.getTo() == node) { - selfEdges.add(edge); + edgeIterator.remove(); + node.getPreds().remove(edge); } } - - // Remove each self-edge - for (LayoutEdge edge : selfEdges) { - node.removeSuccessor(edge); - node.removePredecessor(edge); - } } } private static void reverseRootInputs(LayoutGraph graph) { for (LayoutNode node : graph.getLayoutNodes()) { if (node.getVertex().isRoot()) { - for (LayoutEdge predEdge : new ArrayList<>(node.getPredecessors())) { + for (LayoutEdge predEdge : new ArrayList<>(node.getPreds())) { reverseEdge(predEdge); } } @@ -184,10 +108,10 @@ public static void reverseEdge(LayoutEdge edge) { edge.setRelativeFromX(relativeTo); edge.setRelativeToX(relativeFrom); - fromNode.removeSuccessor(edge); - fromNode.addPredecessor(edge); - toNode.removePredecessor(edge); - toNode.addSuccessor(edge); + fromNode.getSuccs().remove(edge); + fromNode.getPreds().add(edge); + toNode.getPreds().remove(edge); + toNode.getSuccs().add(edge); } private static void depthFirstSearch(LayoutGraph graph) { @@ -210,7 +134,7 @@ private static void depthFirstSearch(LayoutGraph graph) { visited.add(node); active.add(node); - for (LayoutEdge edge : new ArrayList<>(node.getSuccessors())) { + for (LayoutEdge edge : new ArrayList<>(node.getSuccs())) { LayoutNode successor = edge.getTo(); if (active.contains(successor)) { reverseEdge(edge); @@ -223,14 +147,15 @@ private static void depthFirstSearch(LayoutGraph graph) { } } - public static class LayerManager { + + static private class LayerManager { private static void assignLayerDownwards(LayoutGraph graph) { ArrayList workingList = new ArrayList<>(); // add all root nodes to layer 0 for (LayoutNode node : graph.getLayoutNodes()) { - if (!node.hasPredecessors()) { + if (!node.hasPreds()) { workingList.add(node); node.setLayer(0); } @@ -241,12 +166,12 @@ private static void assignLayerDownwards(LayoutGraph graph) { while (!workingList.isEmpty()) { ArrayList newWorkingList = new ArrayList<>(); for (LayoutNode node : workingList) { - for (LayoutEdge succEdge : node.getSuccessors()) { + for (LayoutEdge succEdge : node.getSuccs()) { LayoutNode succNode = succEdge.getTo(); if (succNode.getLayer() == -1) { // This node was not assigned before. boolean assignedPred = true; - for (LayoutEdge predEdge : succNode.getPredecessors()) { + for (LayoutEdge predEdge : succNode.getPreds()) { LayoutNode predNode = predEdge.getFrom(); if (predNode.getLayer() == -1 || predNode.getLayer() >= layer) { // This now has an unscheduled successor or a successor that was scheduled only in this round. @@ -276,7 +201,7 @@ private static void assignLayerUpwards(LayoutGraph graph) { ArrayList workingList = new ArrayList<>(); // add all leaves to working list, reset layer of non-leave nodes for (LayoutNode node : graph.getLayoutNodes()) { - if (!node.hasSuccessors()) { + if (!node.hasSuccs()) { workingList.add(node); } else { node.setLayer(-1); @@ -290,12 +215,12 @@ private static void assignLayerUpwards(LayoutGraph graph) { ArrayList newWorkingList = new ArrayList<>(); for (LayoutNode node : workingList) { if (node.getLayer() < layer) { - for (LayoutEdge predEdge : node.getPredecessors()) { + for (LayoutEdge predEdge : node.getPreds()) { LayoutNode predNode = predEdge.getFrom(); if (predNode.getLayer() == -1) { // This node was not assigned before. boolean assignedSucc = true; - for (LayoutEdge succEdge : predNode.getSuccessors()) { + for (LayoutEdge succEdge : predNode.getSuccs()) { LayoutNode succNode = succEdge.getTo(); if (succNode.getLayer() == -1 || succNode.getLayer() >= layer) { // This now has an unscheduled successor or a successor that was scheduled only in this round. @@ -344,19 +269,19 @@ static private void createDummyNodes(LayoutGraph graph, int maxLayerLength) { if (layoutNode.getLayer() == 0) { graph.getLayer(0).add(layoutNode); visited.add(layoutNode); - } else if (!layoutNode.hasPredecessors()) { + } else if (!layoutNode.hasPreds()) { graph.getLayer(layoutNode.getLayer()).add(layoutNode); visited.add(layoutNode); } } for (LayoutNode layoutNode : layoutNodes) { - graph.createDummiesForNodeSuccessor(layoutNode, maxLayerLength); + createDummiesForNodeSuccessor(graph, layoutNode, maxLayerLength); } for (int i = 0; i < graph.getLayerCount() - 1; i++) { for (LayoutNode n : graph.getLayer(i)) { - for (LayoutEdge e : n.getSuccessors()) { + for (LayoutEdge e : n.getSuccs()) { if (e.getTo().isDummy()) continue; if (!visited.contains(e.getTo())) { visited.add(e.getTo()); @@ -368,6 +293,115 @@ static private void createDummyNodes(LayoutGraph graph, int maxLayerLength) { } } + static private void createDummiesForNodeSuccessor(LayoutGraph graph, LayoutNode layoutNode, int maxLayerLength) { + HashMap> portsToUnprocessedEdges = new HashMap<>(); + ArrayList succs = new ArrayList<>(layoutNode.getSuccs()); + HashMap portToTopNode = new HashMap<>(); + HashMap> portToBottomNodeMapping = new HashMap<>(); + for (LayoutEdge succEdge : succs) { + int startPort = succEdge.getRelativeFromX(); + LayoutNode fromNode = succEdge.getFrom(); + LayoutNode toNode = succEdge.getTo(); + + // edge is longer than one layer => needs dummy nodes + if (fromNode.getLayer() != toNode.getLayer() - 1) { + // the edge needs to be cut + if (maxLayerLength != -1 && toNode.getLayer() - fromNode.getLayer() > maxLayerLength) { + // remove the succEdge before replacing it + toNode.getPreds().remove(succEdge); + fromNode.getSuccs().remove(succEdge); + + LayoutNode topCutNode = portToTopNode.get(startPort); + if (topCutNode == null) { + topCutNode = new LayoutNode(); + topCutNode.setLayer(fromNode.getLayer() + 1); + graph.addNodeToLayer(topCutNode, topCutNode.getLayer()); + portToTopNode.put(startPort, topCutNode); + portToBottomNodeMapping.put(startPort, new HashMap<>()); + } + LayoutEdge edgeToTopCut = new LayoutEdge(fromNode, topCutNode, succEdge.getRelativeFromX(), topCutNode.getWidth() / 2, succEdge.getLink()); + if (succEdge.isReversed()) edgeToTopCut.reverse(); + fromNode.getSuccs().add(edgeToTopCut); + topCutNode.getPreds().add(edgeToTopCut); + + HashMap layerToBottomNode = portToBottomNodeMapping.get(startPort); + LayoutNode bottomCutNode = layerToBottomNode.get(toNode.getLayer()); + if (bottomCutNode == null) { + bottomCutNode = new LayoutNode(); + bottomCutNode.setLayer(toNode.getLayer() - 1); + graph.addNodeToLayer(bottomCutNode, bottomCutNode.getLayer()); + layerToBottomNode.put(toNode.getLayer(), bottomCutNode); + } + LayoutEdge bottomEdge = new LayoutEdge(bottomCutNode, toNode, bottomCutNode.getWidth() / 2, succEdge.getRelativeToX(), succEdge.getLink()); + if (succEdge.isReversed()) bottomEdge.reverse(); + toNode.getPreds().add(bottomEdge); + bottomCutNode.getSuccs().add(bottomEdge); + + } else { // the edge is not cut, but needs dummy nodes + portsToUnprocessedEdges.putIfAbsent(startPort, new ArrayList<>()); + portsToUnprocessedEdges.get(startPort).add(succEdge); + } + } + } + + for (Map.Entry> portToUnprocessedEdges : portsToUnprocessedEdges.entrySet()) { + Integer startPort = portToUnprocessedEdges.getKey(); + List unprocessedEdges = portToUnprocessedEdges.getValue(); + unprocessedEdges.sort(LAYOUT_EDGE_LAYER_COMPARATOR); + + if (unprocessedEdges.size() == 1) { + // process a single edge + LayoutEdge singleEdge = unprocessedEdges.get(0); + LayoutNode fromNode = singleEdge.getFrom(); + if (singleEdge.getTo().getLayer() > fromNode.getLayer() + 1) { + LayoutEdge previousEdge = singleEdge; + for (int i = fromNode.getLayer() + 1; i < previousEdge.getTo().getLayer(); i++) { + LayoutNode dummyNode = new LayoutNode(); + dummyNode.setLayer(i); + dummyNode.getPreds().add(previousEdge); + graph.addNodeToLayer(dummyNode, dummyNode.getLayer()); + LayoutEdge dummyEdge = new LayoutEdge(dummyNode, previousEdge.getTo(), dummyNode.getWidth() / 2, previousEdge.getRelativeToX(), singleEdge.getLink()); + if (previousEdge.isReversed()) dummyEdge.reverse(); + dummyNode.getSuccs().add(dummyEdge); + previousEdge.setRelativeToX(dummyNode.getWidth() / 2); + previousEdge.getTo().getPreds().remove(previousEdge); + previousEdge.getTo().getPreds().add(dummyEdge); + previousEdge.setTo(dummyNode); + previousEdge = dummyEdge; + } + } + } else { + int lastLayer = unprocessedEdges.get(unprocessedEdges.size() - 1).getTo().getLayer(); + int dummyCnt = lastLayer - layoutNode.getLayer() - 1; + LayoutEdge[] newDummyEdges = new LayoutEdge[dummyCnt]; + LayoutNode[] newDummyNodes = new LayoutNode[dummyCnt]; + + newDummyNodes[0] = new LayoutNode(); + newDummyNodes[0].setLayer(layoutNode.getLayer() + 1); + newDummyEdges[0] = new LayoutEdge(layoutNode, newDummyNodes[0], startPort, newDummyNodes[0].getWidth() / 2, null); + newDummyNodes[0].getPreds().add(newDummyEdges[0]); + layoutNode.getSuccs().add(newDummyEdges[0]); + for (int j = 1; j < dummyCnt; j++) { + newDummyNodes[j] = new LayoutNode(); + newDummyNodes[j].setLayer(layoutNode.getLayer() + j + 1); + newDummyEdges[j] = new LayoutEdge(newDummyNodes[j - 1], newDummyNodes[j], null); + newDummyNodes[j].getPreds().add(newDummyEdges[j]); + newDummyNodes[j - 1].getSuccs().add(newDummyEdges[j]); + } + for (LayoutEdge unprocessedEdge : unprocessedEdges) { + LayoutNode anchorNode = newDummyNodes[unprocessedEdge.getTo().getLayer() - layoutNode.getLayer() - 2]; + anchorNode.getSuccs().add(unprocessedEdge); + unprocessedEdge.setFrom(anchorNode); + unprocessedEdge.setRelativeFromX(anchorNode.getWidth() / 2); + layoutNode.getSuccs().remove(unprocessedEdge); + } + for (LayoutNode dummyNode : newDummyNodes) { + graph.addNodeToLayer(dummyNode, dummyNode.getLayer()); + } + } + } + } + static public void apply(LayoutGraph graph, int maxLayerLength) { assignLayers(graph); createDummyNodes(graph, maxLayerLength); @@ -375,280 +409,209 @@ static public void apply(LayoutGraph graph, int maxLayerLength) { } } - static class CrossingReduction { + private static class CrossingReduction { public static void apply(LayoutGraph graph) { - for (int i = 0; i < graph.getLayerCount(); i++) { - graph.getLayer(i).updateNodeIndices(); - graph.getLayer(i).initXPositions(); - } - for (int i = 0; i < CROSSING_ITERATIONS; i++) { downSweep(graph); upSweep(graph); } downSweep(graph); + graph.updatePositions(); } - private static void downSweep(LayoutGraph graph) { - - for (int i = 1; i < graph.getLayerCount(); i++) { - for (LayoutNode n : graph.getLayer(i)) { - n.setCrossingNumber(0); - } - for (LayoutNode n : graph.getLayer(i)) { - int sum = 0; - int count = 0; - for (LayoutEdge e : n.getPredecessors()) { - sum += e.getStartX(); - count++; - } - - if (count > 0) { - sum /= count; - n.setCrossingNumber(sum); - } - } - updateCrossingNumbers(graph.getLayer(i), true); - graph.getLayer(i).sort(NODE_CROSSING_COMPARATOR); - graph.getLayer(i).initXPositions(); - graph.getLayer(i).updateNodeIndices(); + private static void doAveragePositions(LayoutLayer layer) { + for (LayoutNode node : layer) { + node.setWeightedPosition(node.averagePosition()); + } + layer.sort(CROSSING_NODE_COMPARATOR); + int x = 0; + for (LayoutNode n : layer) { + n.setWeightedPosition(x); + x += n.getOuterWidth() + NODE_OFFSET; } } - private static void updateCrossingNumbers(LayoutLayer layer, boolean down) { - for (int i = 0; i < layer.size(); i++) { - LayoutNode n = layer.get(i); - LayoutNode prev = null; - if (i > 0) { - prev = layer.get(i - 1); - } - LayoutNode next = null; - if (i < layer.size() - 1) { - next = layer.get(i + 1); + private static void doMedianPositions(LayoutLayer layer, boolean usePred) { + for (LayoutNode node : layer) { + int size = usePred ? node.getPreds().size() : node.getSuccs().size(); + if (size == 0) continue; + float[] values = new float[size]; + for (int j = 0; j < size; j++) { + LayoutNode predNode = usePred ? node.getPreds().get(j).getFrom() : node.getSuccs().get(j).getTo(); + values[j] = predNode.getWeightedPosition(); } - boolean cond = !n.hasSuccessors(); - if (down) { - cond = !n.hasPredecessors(); - } - if (cond) { - if (prev != null && next != null) { - n.setCrossingNumber((prev.getCrossingNumber() + next.getCrossingNumber()) / 2); - } else if (prev != null) { - n.setCrossingNumber(prev.getCrossingNumber()); - } else if (next != null) { - n.setCrossingNumber(next.getCrossingNumber()); - } + Arrays.sort(values); + if (values.length % 2 == 0) { + node.setWeightedPosition((values[size / 2 - 1] + values[size / 2]) / 2); + } else { + node.setWeightedPosition(values[size / 2]); } } + layer.sort(CROSSING_NODE_COMPARATOR); + int x = 0; + for (LayoutNode n : layer) { + n.setWeightedPosition(x); + x += n.getOuterWidth() + NODE_OFFSET; + } } - private static void upSweep(LayoutGraph graph) { - for (int i = graph.getLayerCount() - 2; i >= 0; i--) { - for (LayoutNode n : graph.getLayer(i)) { - n.setCrossingNumber(0); + private static void placeLeavesAndRoots(LayoutLayer layer, boolean usePred) { + // Nodes that have no adjacent nodes on the neighboring layer: + // leave fixed in their current positions with non-fixed nodes sorted into the remaining positions + for (int j = 0; j < layer.size(); j++) { + LayoutNode node = layer.get(j); + if (usePred ? !node.hasPreds() : !node.hasSuccs()) { + float prevWeight = (j > 0) ? layer.get(j - 1).getWeightedPosition() : 0; + float nextWeight = (j < layer.size() - 1) ? layer.get(j + 1).getWeightedPosition() : 0; + node.setWeightedPosition((prevWeight + nextWeight) / 2); } - for (LayoutNode n : graph.getLayer(i)) { - int count = 0; - int sum = 0; - for (LayoutEdge e : n.getSuccessors()) { - sum += e.getEndX(); - count++; - } - if (count > 0) { - sum /= count; - n.setCrossingNumber(sum); - } - - } - updateCrossingNumbers(graph.getLayer(i), false); - graph.getLayer(i).sort(NODE_CROSSING_COMPARATOR); - graph.getLayer(i).initXPositions(); - graph.getLayer(i).updateNodeIndices(); + } + layer.sort(CROSSING_NODE_COMPARATOR); + int x = 0; + for (LayoutNode n : layer) { + n.setWeightedPosition(x); + x += n.getOuterWidth() + NODE_OFFSET; } } - } - - static class AssignXCoordinates { - private static List> space; - private static List> downProcessingOrder; - private static List> upProcessingOrder; - - private static final Comparator nodeProcessingDownComparator = (n1, n2) -> { - if (n1.isDummy()) { - if (n2.isDummy()) { - return 0; - } - return -1; + private static void downSweep(LayoutGraph graph) { + for (int i = 0; i < graph.getLayerCount(); i++) { + doAveragePositions(graph.getLayer(i)); } - if (n2.isDummy()) { - return 1; + for (int i = 1; i < graph.getLayerCount(); i++) { + doMedianPositions(graph.getLayer(i), true); + placeLeavesAndRoots(graph.getLayer(i), true); } - return n1.getInDegree() - n2.getInDegree(); - }; + } - private static final Comparator nodeProcessingUpComparator = (n1, n2) -> { - if (n1.isDummy()) { - if (n2.isDummy()) { - return 0; - } - return -1; + private static void upSweep(LayoutGraph graph) { + for (int i = graph.getLayerCount() - 1; i >= 0; i--) { + doAveragePositions(graph.getLayer(i)); } - if (n2.isDummy()) { - return 1; + for (int i = graph.getLayerCount() - 2; i >= 0; i--) { + doMedianPositions(graph.getLayer(i), false); + placeLeavesAndRoots(graph.getLayer(i), false); } - return n1.getOutDegree() - n2.getOutDegree(); - }; + } + } - public static void apply(LayoutGraph graph) { - space = new ArrayList<>(graph.getLayerCount()); - downProcessingOrder = new ArrayList<>(graph.getLayerCount()); - upProcessingOrder = new ArrayList<>(graph.getLayerCount()); + private static class AssignXCoordinates { - for (int i = 0; i < graph.getLayerCount(); i++) { - // Add a new empty list for each layer - space.add(new ArrayList<>()); - downProcessingOrder.add(new ArrayList<>()); - upProcessingOrder.add(new ArrayList<>()); + static int[][] space; + static LayoutNode[][] downProcessingOrder; + static LayoutNode[][] upProcessingOrder; + static private void createArrays(LayoutGraph graph) { + space = new int[graph.getLayerCount()][]; + downProcessingOrder = new LayoutNode[graph.getLayerCount()][]; + upProcessingOrder = new LayoutNode[graph.getLayerCount()][]; + for (int i = 0; i < graph.getLayerCount(); i++) { + LayoutLayer layer = graph.getLayer(i); + space[i] = new int[layer.size()]; + downProcessingOrder[i] = new LayoutNode[layer.size()]; + upProcessingOrder[i] = new LayoutNode[layer.size()]; int curX = 0; - for (LayoutNode n : graph.getLayer(i)) { - // Add the current position to space and increment curX - space.get(i).add(curX); - curX += n.getOuterWidth() + NODE_OFFSET; - - // Add the current node to processing orders - downProcessingOrder.get(i).add(n); - upProcessingOrder.get(i).add(n); + for (int j = 0; j < layer.size(); j++) { + space[i][j] = curX; + LayoutNode node = layer.get(j); + curX += node.getOuterWidth() + NODE_OFFSET; + downProcessingOrder[i][j] = node; + upProcessingOrder[i][j] = node; } - - // Sort the processing orders - downProcessingOrder.get(i).sort(nodeProcessingDownComparator); - upProcessingOrder.get(i).sort(nodeProcessingUpComparator); + Arrays.sort(downProcessingOrder[i], NODE_PROCESSING_DOWN_COMPARATOR); + Arrays.sort(upProcessingOrder[i], NODE_PROCESSING_UP_COMPARATOR); } + } - for (LayoutNode n : graph.getLayoutNodes()) { - n.setX(space.get(n.getLayer()).get(n.getPos())); + static private void initialPositions(LayoutGraph graph) { + for (LayoutNode layoutNode : graph.getLayoutNodes()) { + layoutNode.setX(space[layoutNode.getLayer()][layoutNode.getPos()]); } - - for (LayoutNode n : graph.getDummyNodes()) { - n.setX(space.get(n.getLayer()).get(n.getPos())); + for (LayoutNode dummyNode : graph.getDummyNodes()) { + dummyNode.setX(space[dummyNode.getLayer()][dummyNode.getPos()]); } + } + static private void apply(LayoutGraph graph) { + createArrays(graph); + initialPositions(graph); for (int i = 0; i < SWEEP_ITERATIONS; i++) { sweepDown(graph); - adjustSpace(graph); sweepUp(graph); - adjustSpace(graph); } - - sweepDown(graph); - adjustSpace(graph); - sweepUp(graph); + graph.optimizeBackEdgeCrossings(); + graph.straightenEdges(); } - private static void adjustSpace(LayoutGraph graph) { - for (int i = 0; i < graph.getLayerCount(); i++) { - for (LayoutNode n : graph.getLayer(i)) { - space.get(i).add(n.getX()); + static private void processRow(int[] space, LayoutNode[] processingOrder) { + Arrays.sort(processingOrder, DUMMY_NODES_THEN_OPTIMAL_X); + TreeSet treeSet = new TreeSet<>(NODE_POS_COMPARATOR); + for (LayoutNode node : processingOrder) { + int minX = Integer.MIN_VALUE; + SortedSet headSet = treeSet.headSet(node, false); + if (!headSet.isEmpty()) { + LayoutNode leftNeighbor = headSet.last(); + minX = leftNeighbor.getOuterLeft() + space[node.getPos()] - space[leftNeighbor.getPos()]; } - } - } - private static void sweepUp(LayoutGraph graph) { - for (int i = graph.getLayerCount() - 1; i >= 0; i--) { - NodeRow r = new NodeRow(space.get(i)); - for (LayoutNode n : upProcessingOrder.get(i)) { - int optimal = n.calculateOptimalXFromSuccessors(true); - r.insert(n, optimal); + int maxX = Integer.MAX_VALUE; + SortedSet tailSet = treeSet.tailSet(node, false); + if (!tailSet.isEmpty()) { + LayoutNode rightNeighbor = tailSet.first(); + maxX = rightNeighbor.getOuterLeft() + space[node.getPos()] - space[rightNeighbor.getPos()]; } + + node.setX(Math.min(Math.max(node.getOptimalX(), minX), maxX)); + treeSet.add(node); } } - private static void sweepDown(LayoutGraph graph) { - for (int i = 1; i < graph.getLayerCount(); i++) { - NodeRow r = new NodeRow(space.get(i)); - for (LayoutNode n : downProcessingOrder.get(i)) { - int optimal = n.calculateOptimalXFromPredecessors(true); - r.insert(n, optimal); + static private void sweepUp(LayoutGraph graph) { + for (int i = graph.getLayerCount() - 2; i >= 0; i--) { + for (LayoutNode node : upProcessingOrder[i]) { + node.setOptimalX(node.calculateOptimalPositionUp()); } + processRow(space[i], upProcessingOrder[i]); } } - } - - public static class NodeRow { - - private final TreeSet treeSet; - private final ArrayList space; - - public NodeRow(ArrayList space) { - treeSet = new TreeSet<>(NODE_POS_COMPARATOR); - this.space = space; - } - - public int offset(LayoutNode n1, LayoutNode n2) { - int v1 = space.get(n1.getPos()) + n1.getOuterWidth(); - int v2 = space.get(n2.getPos()); - return v2 - v1; - } - - public void insert(LayoutNode n, int pos) { - - SortedSet headSet = treeSet.headSet(n); - LayoutNode leftNeighbor; - int minX = Integer.MIN_VALUE; - if (!headSet.isEmpty()) { - leftNeighbor = headSet.last(); - minX = leftNeighbor.getOuterRight() + offset(leftNeighbor, n); - } - - if (pos < minX) { - n.setX(minX); - } else { - - LayoutNode rightNeighbor; - SortedSet tailSet = treeSet.tailSet(n); - int maxX = Integer.MAX_VALUE; - if (!tailSet.isEmpty()) { - rightNeighbor = tailSet.first(); - maxX = rightNeighbor.getX() - offset(n, rightNeighbor) - n.getOuterWidth(); + static private void sweepDown(LayoutGraph graph) { + for (int i = 1; i < graph.getLayerCount(); i++) { + for (LayoutNode node : downProcessingOrder[i]) { + node.setOptimalX(node.calculateOptimalPositionDown()); } - - n.setX(Math.min(pos, maxX)); + processRow(space[i], downProcessingOrder[i]); } - - treeSet.add(n); } } - public static class WriteResult { + private static class WriteResult { private static HashMap> computeLinkPositions(LayoutGraph graph) { HashMap> linkToSplitEndPoints = new HashMap<>(); HashMap> linkPositions = new HashMap<>(); for (LayoutNode layoutNode : graph.getLayoutNodes()) { - for (LayoutEdge predEdge : layoutNode.getPredecessors()) { + for (LayoutEdge predEdge : layoutNode.getPreds()) { LayoutNode fromNode = predEdge.getFrom(); LayoutNode toNode = predEdge.getTo(); ArrayList linkPoints = new ArrayList<>(); // input edge stub - linkPoints.add(new Point(predEdge.getEndX(), predEdge.getEndY())); + linkPoints.add(new Point(predEdge.getEndX(), toNode.getTop())); linkPoints.add(new Point(predEdge.getEndX(), graph.getLayer(toNode.getLayer()).getTop() - LAYER_OFFSET)); LayoutEdge curEdge = predEdge; - while (fromNode.isDummy() && fromNode.hasPredecessors()) { + while (fromNode.isDummy() && fromNode.hasPreds()) { linkPoints.add(new Point(fromNode.getCenterX(), graph.getLayer(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); linkPoints.add(new Point(fromNode.getCenterX(), graph.getLayer(fromNode.getLayer()).getTop() - LAYER_OFFSET)); - curEdge = fromNode.getPredecessors().get(0); + curEdge = fromNode.getPreds().get(0); fromNode = curEdge.getFrom(); } linkPoints.add(new Point(curEdge.getStartX(), graph.getLayer(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); // output edge stub - linkPoints.add(new Point(curEdge.getStartX(), curEdge.getStartY())); + linkPoints.add(new Point(curEdge.getStartX(), fromNode.getBottom())); if (predEdge.isReversed()) { for (Point relativeEnd : toNode.getReversedLinkEndPoints().get(predEdge.getLink())) { @@ -681,7 +644,7 @@ private static HashMap> computeLinkPositions(LayoutGraph graph } for (LayoutNode layoutNode : graph.getLayoutNodes()) { - for (LayoutEdge succEdge : layoutNode.getSuccessors()) { + for (LayoutEdge succEdge : layoutNode.getSuccs()) { if (succEdge.getLink() == null) continue; LayoutNode fromNode = succEdge.getFrom(); @@ -692,10 +655,10 @@ private static HashMap> computeLinkPositions(LayoutGraph graph linkPoints.add(new Point(succEdge.getStartX(), graph.getLayer(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); LayoutEdge curEdge = succEdge; - while (toNode.isDummy() && toNode.hasSuccessors()) { + while (toNode.isDummy() && toNode.hasSuccs()) { linkPoints.add(new Point(toNode.getCenterX(), graph.getLayer(toNode.getLayer()).getTop() - LAYER_OFFSET)); linkPoints.add(new Point(toNode.getCenterX(), graph.getLayer(toNode.getLayer()).getBottom() + LAYER_OFFSET)); - curEdge = toNode.getSuccessors().get(0); + curEdge = toNode.getSuccs().get(0); toNode = curEdge.getTo(); } linkPoints.add(new Point(curEdge.getEndX(), graph.getLayer(toNode.getLayer()).getTop() - LAYER_OFFSET)); @@ -780,7 +743,7 @@ public static void apply(LayoutGraph graph) { } for (LayoutLayer layer : graph.getLayers()) { - layer.moveLayerVertically(-minY); + layer.shiftTop(-minY); } // Shift vertices by minX/minY diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java index afd692e84464f..2922e2c192e89 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,23 +23,27 @@ */ package com.sun.hotspot.igv.hierarchicallayout; -import static com.sun.hotspot.igv.hierarchicallayout.LayoutManager.LAYER_OFFSET; -import static com.sun.hotspot.igv.hierarchicallayout.LayoutManager.NODE_OFFSET; import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Vertex; import java.awt.Dimension; import java.awt.Point; import java.util.*; -public class HierarchicalStableLayoutManager { +public class HierarchicalStableLayoutManager extends LayoutManager{ + public static final int DUMMY_HEIGHT = 1; + public static final int DUMMY_WIDTH = 1; + public static final int X_OFFSET = 8; + public static final int LAYER_OFFSET = 8; // Algorithm global data structures private Set currentVertices; private Set currentLinks; private Set reversedLinks; private List nodes; private final HashMap vertexToLayoutNode; - private HashMap layers; + private HashMap> reversedLinkStartPoints; + private HashMap> reversedLinkEndPoints; + private HashMap> layers; private final HierarchicalLayoutManager manager; private HashMap vertexToAction; @@ -59,6 +63,14 @@ public void doLayout(LayoutGraph layoutGraph) { } + public void setCutEdges(boolean cutEdges) { + } + + @Override + public void doLayout(LayoutGraph graph) { + + } + enum Action { ADD, REMOVE @@ -88,7 +100,7 @@ public LinkAction(Link link, Action action) { public HierarchicalStableLayoutManager() { oldVertices = new HashSet<>(); oldLinks = new HashSet<>(); - manager = new HierarchicalLayoutManager(); + manager = new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS); vertexToLayoutNode = new HashMap<>(); nodes = new ArrayList<>(); } @@ -103,48 +115,26 @@ public boolean getCutEdges() { } private int calculateOptimalBoth(LayoutNode n) { - if (n.getPredecessors().isEmpty() && n.getSuccessors().isEmpty()) { - return n.getX(); + if (n.preds.isEmpty() && n.succs.isEmpty()) { + return n.x; } - int[] values = new int[n.getPredecessors().size() + n.getSuccessors().size()]; + int[] values = new int[n.preds.size() + n.succs.size()]; int i = 0; - for (LayoutEdge e : n.getPredecessors()) { - values[i] = e.getFrom().getX() + e.getRelativeFromX() - e.getRelativeToX(); + for (LayoutEdge e : n.preds) { + values[i] = e.from.x + e.relativeFrom - e.relativeTo; i++; } - for (LayoutEdge e : n.getSuccessors()) { - values[i] = e.getTo().getX() + e.getRelativeToX() - e.getRelativeFromX(); + for (LayoutEdge e : n.succs) { + values[i] = e.to.x + e.relativeTo - e.relativeFrom; i++; } - return median(values); - } - - public static int median(int[] values) { - Arrays.sort(values); - if (values.length % 2 == 0) { - return (values[values.length / 2 - 1] + values[values.length / 2]) / 2; - } else { - return values[values.length / 2]; - } + return Statistics.median(values); } - public static final Comparator nodeProcessingUpComparator = (n1, n2) -> { - if (n1.getVertex() == null) { - if (n2.getVertex() == null) { - return 0; - } - return -1; - } - if (n2.getVertex() == null) { - return 1; - } - return n1.getSuccessors().size() - n2.getSuccessors().size(); - }; - /** * Adjust the X-coordinates of the nodes in the given layer, as a new node has * been inserted at that layer @@ -154,16 +144,16 @@ private void adjustXCoordinates(int layer) { ArrayList space = new ArrayList<>(); List nodeProcessingOrder = new ArrayList<>(); - nodes.sort(Comparator.comparingInt(LayoutNode::getPos)); + nodes.sort(HierarchicalLayoutManager.nodePositionComparator); int curX = 0; for (LayoutNode n : nodes) { space.add(curX); - curX += n.getOuterWidth() + NODE_OFFSET; + curX += n.width + X_OFFSET; nodeProcessingOrder.add(n); } - nodeProcessingOrder.sort(nodeProcessingUpComparator); + nodeProcessingOrder.sort(HierarchicalLayoutManager.nodeProcessingUpComparator); HierarchicalLayoutManager.NodeRow r = new HierarchicalLayoutManager.NodeRow(space); for (LayoutNode n : nodeProcessingOrder) { int optimal = calculateOptimalBoth(n); @@ -173,8 +163,8 @@ private void adjustXCoordinates(int layer) { private void ensureNeighborEdgeConsistency() { for (LayoutNode n : nodes) { - n.getSuccessorsRaw().removeIf(e -> !nodes.contains(e.getTo())); - n.getPredecessorsRaw().removeIf(e -> !nodes.contains(e.getFrom())); + n.succs.removeIf(e -> !nodes.contains(e.to)); + n.preds.removeIf(e -> !nodes.contains(e.from)); } } @@ -264,8 +254,8 @@ private void findInitialReversedLinks() { for (Link link : oldLinks) { for (Link l : currentLinks) { if (l.equals(link)) { - if (vertexToLayoutNode.get(l.getFrom().getVertex()).getLayer() > vertexToLayoutNode - .get(l.getTo().getVertex()).getLayer()) { + if (vertexToLayoutNode.get(l.getFrom().getVertex()).layer > vertexToLayoutNode + .get(l.getTo().getVertex()).layer) { // Link is reversed reversedLinks.add(l); updateReversedLinkPositions(l); @@ -281,7 +271,7 @@ private void updateReversedLinkPositions(Link link) { // Correct direction, is reversed link assert fromNode != null && toNode != null; assert nodes.contains(fromNode) && nodes.contains(toNode); - assert fromNode.getLayer() > toNode.getLayer(); + assert fromNode.layer > toNode.layer; assert reversedLinks.contains(link); updateNodeWithReversedEdges(fromNode); @@ -289,7 +279,138 @@ private void updateReversedLinkPositions(Link link) { } private void updateNodeWithReversedEdges(LayoutNode node) { - node.computeReversedLinkPoints(false); + // Reset node data in case there were previous reversed edges + node.width = (int) node.vertex.getSize().getWidth(); + node.height = (int) node.vertex.getSize().getHeight(); + node.yOffset = 0; + node.bottomYOffset = 0; + node.xOffset = 0; + node.inOffsets.clear(); + node.outOffsets.clear(); + + SortedSet reversedDown = new TreeSet<>(); + + // Reset relativeFrom for all succ edges + for (LayoutEdge e : node.succs) { + if (e.link == null) { + continue; + } + e.relativeFrom = e.link.getFrom().getRelativePosition().x; + if (reversedLinks.contains(e.link)) { + e.relativeFrom = e.link.getTo().getRelativePosition().x; + reversedDown.add(e.relativeFrom); + } + } + + // Whether the node has non-self reversed edges going downwards. + // If so, reversed edges going upwards are drawn to the left. + boolean hasReversedDown = !reversedDown.isEmpty(); + + SortedSet reversedUp; + if (hasReversedDown) { + reversedUp = new TreeSet<>(); + } else { + reversedUp = new TreeSet<>(Collections.reverseOrder()); + } + + // Reset relativeTo for all pred edges + for (LayoutEdge e : node.preds) { + if (e.link == null) { + continue; + } + e.relativeTo = e.link.getTo().getRelativePosition().x; + if (reversedLinks.contains(e.link)) { + e.relativeTo = e.link.getFrom().getRelativePosition().x; + reversedUp.add(e.relativeTo); + } + } + + final int offset = X_OFFSET + DUMMY_WIDTH; + + int curY = 0; + int curWidth = node.width + reversedDown.size() * offset; + for (int pos : reversedDown) { + ArrayList reversedSuccs = new ArrayList<>(); + for (LayoutEdge e : node.succs) { + if (e.relativeFrom == pos && reversedLinks.contains(e.link)) { + reversedSuccs.add(e); + e.relativeFrom = curWidth; + } + } + + ArrayList startPoints = new ArrayList<>(); + startPoints.add(new Point(curWidth, curY)); + startPoints.add(new Point(pos, curY)); + startPoints.add(new Point(pos, reversedDown.size() * offset)); + for (LayoutEdge e : reversedSuccs) { + reversedLinkStartPoints.put(e.link, startPoints); + } + + node.inOffsets.put(pos, -curY); + curY += offset; + node.height += offset; + node.yOffset += offset; + curWidth -= offset; + } + + int widthFactor = reversedDown.size(); + node.width += widthFactor * offset; + + int curX = 0; + int minX = 0; + if (hasReversedDown) { + minX = -offset * reversedUp.size(); + } + + int oldNodeHeight = node.height; + for (int pos : reversedUp) { + ArrayList reversedPreds = new ArrayList<>(); + for (LayoutEdge e : node.preds) { + if (e.relativeTo == pos && reversedLinks.contains(e.link)) { + if (hasReversedDown) { + e.relativeTo = curX - offset; + } else { + e.relativeTo = node.width + offset; + } + + reversedPreds.add(e); + } + } + node.height += offset; + ArrayList endPoints = new ArrayList<>(); + + node.width += offset; + if (hasReversedDown) { + curX -= offset; + endPoints.add(new Point(curX, node.height)); + } else { + curX += offset; + endPoints.add(new Point(node.width, node.height)); + } + + node.outOffsets.put(pos - minX, curX); + curX += offset; + node.bottomYOffset += offset; + + endPoints.add(new Point(pos, node.height)); + endPoints.add(new Point(pos, oldNodeHeight)); + for (LayoutEdge e : reversedPreds) { + reversedLinkEndPoints.put(e.link, endPoints); + } + } + + if (minX < 0) { + for (LayoutEdge e : node.preds) { + e.relativeTo -= minX; + } + + for (LayoutEdge e : node.succs) { + e.relativeFrom -= minX; + } + + node.xOffset = -minX; + node.width -= minX; + } } /** @@ -303,6 +424,8 @@ public void updateLayout(Set vertices, Set lin currentVertices = vertices; currentLinks = links; reversedLinks = new HashSet<>(); + reversedLinkStartPoints = new HashMap<>(); + reversedLinkEndPoints = new HashMap<>(); vertexActions = new LinkedList<>(); linkActions = new LinkedList<>(); vertexToAction = new HashMap<>(); @@ -313,12 +436,12 @@ public void updateLayout(Set vertices, Set lin // If the layout is too messy it should be redrawn using the static algorithm, // currently HierarchicalLayoutManager manager.doLayout(new LayoutGraph(links, vertices)); - nodes = new ArrayList<>(manager.getNodes()); + nodes = manager.getNodes(); shouldRedrawLayout = false; } else { generateActions(); - new BuildDatastructures().run(); + new BuildDatastructure().run(); findInitialReversedLinks(); @@ -354,7 +477,7 @@ private void run() { } } - private class BuildDatastructures { + private class BuildDatastructure { // In case there are changes in the node size, its layer must be updated Set layersToUpdate = new HashSet<>(); @@ -365,51 +488,53 @@ private class BuildDatastructures { */ private void updateNodeObjects() { for (LayoutNode node : nodes) { - if (node.getVertex() != null) { + if (node.vertex != null) { for (Vertex vertex : currentVertices) { - if (vertex.equals(node.getVertex())) { + if (vertex.equals(node.vertex)) { Dimension size = vertex.getSize(); - if (node.getOuterWidth() < (int) size.getWidth()) { - layersToUpdate.add(node.getLayer()); + if (node.width < (int) size.getWidth()) { + layersToUpdate.add(node.layer); } - node.initSize(); - node.setVertex(vertex); + node.width = (int) size.getWidth(); + node.height = (int) size.getHeight(); + node.vertex = vertex; } } - vertexToLayoutNode.put(node.getVertex(), node); + vertexToLayoutNode.put(node.vertex, node); } else { - node.initSize(); + node.height = DUMMY_HEIGHT; + node.width = DUMMY_WIDTH; } - for (LayoutEdge edge : node.getPredecessors()) { - if (edge.getLink() != null) { + for (LayoutEdge edge : node.preds) { + if (edge.link != null) { for (Link link : currentLinks) { - if (link.equals(edge.getLink())) { - edge.setLink(link); - if (link.getTo().getVertex().equals(edge.getFrom().getVertex())) { + if (link.equals(edge.link)) { + edge.link = link; + if (link.getTo().getVertex().equals(edge.from.vertex)) { // reversed link - edge.setRelativeFromX(link.getTo().getRelativePosition().x); - edge.setRelativeToX(link.getFrom().getRelativePosition().x); + edge.relativeFrom = link.getTo().getRelativePosition().x; + edge.relativeTo = link.getFrom().getRelativePosition().x; } else { - edge.setRelativeFromX(link.getFrom().getRelativePosition().x); - edge.setRelativeToX(link.getTo().getRelativePosition().x); + edge.relativeFrom = link.getFrom().getRelativePosition().x; + edge.relativeTo = link.getTo().getRelativePosition().x; } break; } } } } - for (LayoutEdge edge : node.getSuccessors()) { - if (edge.getLink() != null) { + for (LayoutEdge edge : node.succs) { + if (edge.link != null) { for (Link link : currentLinks) { - if (link.equals(edge.getLink())) { - edge.setLink(link); - if (link.getTo().getVertex().equals(edge.getFrom().getVertex())) { + if (link.equals(edge.link)) { + edge.link = link; + if (link.getTo().getVertex().equals(edge.from.vertex)) { // reversed link - edge.setRelativeFromX(link.getTo().getRelativePosition().x); - edge.setRelativeToX(link.getFrom().getRelativePosition().x); + edge.relativeFrom = link.getTo().getRelativePosition().x; + edge.relativeTo = link.getFrom().getRelativePosition().x; } else { - edge.setRelativeFromX(link.getFrom().getRelativePosition().x); - edge.setRelativeToX(link.getTo().getRelativePosition().x); + edge.relativeFrom = link.getFrom().getRelativePosition().x; + edge.relativeTo = link.getTo().getRelativePosition().x; } break; } @@ -425,14 +550,14 @@ private void updateNodeObjects() { private void storeNodeLayers() { layers = new HashMap<>(); for (LayoutNode node : nodes) { - if (!layers.containsKey(node.getLayer())) { - layers.put(node.getLayer(), new LayoutLayer()); + if (!layers.containsKey(node.layer)) { + layers.put(node.layer, new ArrayList<>()); } - layers.get(node.getLayer()).add(node); + layers.get(node.layer).add(node); } for (int i = 0; i < layers.keySet().size(); i++) { if (!layers.containsKey(i)) { - layers.put(i, new LayoutLayer()); + layers.put(i, new ArrayList<>()); } } } @@ -459,7 +584,7 @@ private int optimalPosition(LayoutNode node, int layer) { assert layers.containsKey(layer); List layerNodes = layers.get(layer); - layerNodes.sort(Comparator.comparingInt(LayoutNode::getPos)); + layerNodes.sort(HierarchicalLayoutManager.nodePositionComparator); int edgeCrossings = Integer.MAX_VALUE; int optimalPos = -1; @@ -467,9 +592,9 @@ private int optimalPosition(LayoutNode node, int layer) { for (int i = 0; i < layerNodes.size() + 1; i++) { int xCoord; if (i == 0) { - xCoord = layerNodes.get(i).getX() - node.getOuterWidth() - 1; + xCoord = layerNodes.get(i).x - node.width - 1; } else { - xCoord = layerNodes.get(i - 1).getX() + layerNodes.get(i - 1).getOuterWidth() + 1; + xCoord = layerNodes.get(i - 1).x + layerNodes.get(i - 1).width + 1; } int currentCrossings = 0; @@ -477,28 +602,28 @@ private int optimalPosition(LayoutNode node, int layer) { if (layers.containsKey(layer - 1)) { List predNodes = layers.get(layer - 1); // For each link with an end point in vertex, check how many edges cross it - for (LayoutEdge edge : node.getPredecessors()) { - if (edge.getFrom().getLayer() == layer - 1) { - int fromNodeXCoord = edge.getFrom().getX(); - if (edge.getFrom().getVertex() != null) { - fromNodeXCoord += edge.getRelativeFromX(); + for (LayoutEdge edge : node.preds) { + if (edge.from.layer == layer - 1) { + int fromNodeXCoord = edge.from.x; + if (edge.from.vertex != null) { + fromNodeXCoord += edge.relativeFrom; } int toNodeXCoord = xCoord; - if (node.getVertex() != null) { - toNodeXCoord += edge.getRelativeToX(); + if (node.vertex != null) { + toNodeXCoord += edge.relativeTo; } for (LayoutNode n : predNodes) { - for (LayoutEdge e : n.getSuccessors()) { - if (e.getTo() == null) { + for (LayoutEdge e : n.succs) { + if (e.to == null) { continue; } - int compFromXCoord = e.getFrom().getX(); - if (e.getFrom().getVertex() != null) { - compFromXCoord += e.getRelativeFromX(); + int compFromXCoord = e.from.x; + if (e.from.vertex != null) { + compFromXCoord += e.relativeFrom; } - int compToXCoord = e.getTo().getX(); - if (e.getTo().getVertex() != null) { - compToXCoord += e.getRelativeToX(); + int compToXCoord = e.to.x; + if (e.to.vertex != null) { + compToXCoord += e.relativeTo; } if ((fromNodeXCoord > compFromXCoord && toNodeXCoord < compToXCoord) || (fromNodeXCoord < compFromXCoord @@ -514,28 +639,28 @@ private int optimalPosition(LayoutNode node, int layer) { if (layers.containsKey(layer + 1)) { List succsNodes = layers.get(layer + 1); // For each link with an end point in vertex, check how many edges cross it - for (LayoutEdge edge : node.getSuccessors()) { - if (edge.getTo().getLayer() == layer + 1) { - int toNodeXCoord = edge.getTo().getX(); - if (edge.getTo().getVertex() != null) { - toNodeXCoord += edge.getRelativeToX(); + for (LayoutEdge edge : node.succs) { + if (edge.to.layer == layer + 1) { + int toNodeXCoord = edge.to.x; + if (edge.to.vertex != null) { + toNodeXCoord += edge.relativeTo; } int fromNodeXCoord = xCoord; - if (node.getVertex() != null) { - fromNodeXCoord += edge.getRelativeFromX(); + if (node.vertex != null) { + fromNodeXCoord += edge.relativeFrom; } for (LayoutNode n : succsNodes) { - for (LayoutEdge e : n.getPredecessors()) { - if (e.getFrom() == null) { + for (LayoutEdge e : n.preds) { + if (e.from == null) { continue; } - int compFromXCoord = e.getFrom().getX(); - if (e.getFrom().getVertex() != null) { - compFromXCoord += e.getRelativeFromX(); + int compFromXCoord = e.from.x; + if (e.from.vertex != null) { + compFromXCoord += e.relativeFrom; } - int compToXCoord = e.getTo().getX(); - if (e.getTo().getVertex() != null) { - compToXCoord += e.getRelativeToX(); + int compToXCoord = e.to.x; + if (e.to.vertex != null) { + compToXCoord += e.relativeTo; } if ((fromNodeXCoord > compFromXCoord && toNodeXCoord < compToXCoord) || (fromNodeXCoord < compFromXCoord @@ -563,18 +688,18 @@ private int optimalPosition(LayoutNode node, int layer) { private void insertNode(LayoutNode node, int layer) { assert layers.containsKey(layer) || layer == 0; - node.setLayer(layer); - LayoutLayer layerNodes = layers.getOrDefault(layer, new LayoutLayer()); + node.layer = layer; + List layerNodes = layers.getOrDefault(layer, new ArrayList()); if (layerNodes.isEmpty()) { - node.setPos(0); + node.pos = 0; } else { - node.setPos(optimalPosition(node, layer)); + node.pos = optimalPosition(node, layer); } for (LayoutNode n : layerNodes) { - if (n.getPos() >= node.getPos()) { - n.setPos(n.getPos() + 1); + if (n.pos >= node.pos) { + n.pos += 1; } } layerNodes.add(node); @@ -583,18 +708,18 @@ private void insertNode(LayoutNode node, int layer) { if (!nodes.contains(node)) { nodes.add(node); } - if (node.getVertex() != null) { - vertexToLayoutNode.put(node.getVertex(), node); + if (node.vertex != null) { + vertexToLayoutNode.put(node.vertex, node); } adjustXCoordinates(layer); } private void processSingleEdge(LayoutEdge e) { - LayoutNode n = e.getTo(); - if (e.getTo().getLayer() - 1 > e.getFrom().getLayer()) { + LayoutNode n = e.to; + if (e.to.layer - 1 > e.from.layer) { LayoutEdge last = e; - for (int i = n.getLayer() - 1; i > last.getFrom().getLayer(); i--) { + for (int i = n.layer - 1; i > last.from.layer; i--) { last = addBetween(last, i); } } @@ -602,26 +727,34 @@ private void processSingleEdge(LayoutEdge e) { private LayoutEdge addBetween(LayoutEdge e, int layer) { LayoutNode n = new LayoutNode(); - n.addSuccessor(e); - LayoutEdge result = new LayoutEdge(e.getFrom(), n, e.getRelativeFromX(), n.getOuterWidth() / 2, e.getLink()); - n.addPredecessor(result); - e.setRelativeFromX(n.getOuterWidth() / 2); - e.getFrom().removeSuccessor(e); - e.getFrom().addSuccessor(result); - e.setFrom(n); + n.width = DUMMY_WIDTH; + n.height = DUMMY_HEIGHT; + n.succs.add(e); + LayoutEdge result = new LayoutEdge(); + result.vip = e.vip; + n.preds.add(result); + result.to = n; + result.relativeTo = n.width / 2; + result.from = e.from; + result.relativeFrom = e.relativeFrom; + result.link = e.link; + e.relativeFrom = n.width / 2; + e.from.succs.remove(e); + e.from.succs.add(result); + e.from = n; insertNode(n, layer); return result; } private void insertDummyNodes(LayoutEdge edge) { - LayoutNode from = edge.getFrom(); - LayoutNode to = edge.getTo(); + LayoutNode from = edge.from; + LayoutNode to = edge.to; boolean hasEdgeFromSamePort = false; - LayoutEdge edgeFromSamePort = null; + LayoutEdge edgeFromSamePort = new LayoutEdge(); - for (LayoutEdge e : edge.getFrom().getSuccessors()) { - if (e.getRelativeFromX() == edge.getRelativeFromX() && e.getTo().getVertex() == null) { + for (LayoutEdge e : edge.from.succs) { + if (e.relativeFrom == edge.relativeFrom && e.to.vertex == null) { edgeFromSamePort = e; hasEdgeFromSamePort = true; break; @@ -633,16 +766,16 @@ private void insertDummyNodes(LayoutEdge edge) { } else { LayoutEdge curEdge = edgeFromSamePort; boolean newEdge = true; - while (curEdge.getTo().getLayer() < to.getLayer() - 1 && curEdge.getTo().getVertex() == null && newEdge) { + while (curEdge.to.layer < to.layer - 1 && curEdge.to.vertex == null && newEdge) { // Traverse down the chain of dummy nodes linking together the edges originating // from the same port newEdge = false; - if (curEdge.getTo().getSuccessors().size() == 1) { - curEdge = curEdge.getTo().getSuccessors().get(0); + if (curEdge.to.succs.size() == 1) { + curEdge = curEdge.to.succs.get(0); newEdge = true; } else { - for (LayoutEdge e : curEdge.getTo().getSuccessors()) { - if (e.getTo().getVertex() == null) { + for (LayoutEdge e : curEdge.to.succs) { + if (e.to.vertex == null) { curEdge = e; newEdge = true; break; @@ -652,27 +785,27 @@ private void insertDummyNodes(LayoutEdge edge) { } LayoutNode prevDummy; - if (curEdge.getTo().getVertex() != null) { - prevDummy = curEdge.getFrom(); + if (curEdge.to.vertex != null) { + prevDummy = curEdge.from; } else { - prevDummy = curEdge.getTo(); + prevDummy = curEdge.to; } - edge.setFrom(prevDummy); - edge.setRelativeFromX(prevDummy.getOuterWidth() / 2); - from.removeSuccessor(edge); - prevDummy.addSuccessor(edge); + edge.from = prevDummy; + edge.relativeFrom = prevDummy.width / 2; + from.succs.remove(edge); + prevDummy.succs.add(edge); processSingleEdge(edge); } } private boolean canMoveNodeUp(LayoutNode node) { - if (node.getLayer() == 0) { + if (node.layer == 0) { return false; } - int newLayer = node.getLayer() - 1; - for (LayoutEdge e : node.getPredecessors()) { - if (e.getFrom().getVertex() != null && e.getFrom().getLayer() == newLayer) { + int newLayer = node.layer - 1; + for (LayoutEdge e : node.preds) { + if (e.from.vertex != null && e.from.layer == newLayer) { return false; } } @@ -680,12 +813,12 @@ private boolean canMoveNodeUp(LayoutNode node) { } private boolean canMoveNodeDown(LayoutNode node) { - if (node.getLayer() == layers.keySet().size() - 1) { + if (node.layer == layers.keySet().size() - 1) { return false; } - int newLayer = node.getLayer() + 1; - for (LayoutEdge e : node.getSuccessors()) { - if (e.getTo().getVertex() != null && e.getTo().getLayer() == newLayer) { + int newLayer = node.layer + 1; + for (LayoutEdge e : node.succs) { + if (e.to.vertex != null && e.to.layer == newLayer) { return false; } } @@ -695,23 +828,23 @@ private boolean canMoveNodeDown(LayoutNode node) { private void moveNodeUp(LayoutNode node) { assert canMoveNodeUp(node); - List previousPredEdges = List.copyOf(node.getPredecessors()); + List previousPredEdges = List.copyOf(node.preds); for (LayoutEdge edge : previousPredEdges) { - LayoutNode predNode = edge.getFrom(); - assert predNode.getVertex() == null; - for (LayoutEdge e : predNode.getPredecessors()) { - e.setTo(edge.getTo()); - e.setRelativeToX(edge.getRelativeToX()); - node.addPredecessor(e); - node.removePredecessor(edge); + LayoutNode predNode = edge.from; + assert predNode.vertex == null; + for (LayoutEdge e : predNode.preds) { + e.to = edge.to; + e.relativeTo = edge.relativeTo; + node.preds.add(e); + node.preds.remove(edge); } removeNodeWithoutRemovingLayer(predNode); } removeNodeWithoutRemovingLayer(node); - insertNode(node, node.getLayer() - 1); + insertNode(node, node.layer - 1); - for (LayoutEdge edge : List.copyOf(node.getSuccessors())) { + for (LayoutEdge edge : List.copyOf(node.succs)) { processSingleEdge(edge); } } @@ -719,23 +852,23 @@ private void moveNodeUp(LayoutNode node) { private void moveNodeDown(LayoutNode node) { assert canMoveNodeDown(node); - List previousSuccEdges = List.copyOf(node.getSuccessors()); + List previousSuccEdges = List.copyOf(node.succs); for (LayoutEdge edge : previousSuccEdges) { - LayoutNode succNode = edge.getTo(); - assert succNode.getVertex() == null; - for (LayoutEdge e : succNode.getSuccessors()) { - e.setFrom(edge.getFrom()); - e.setRelativeFromX(edge.getRelativeFromX()); - node.addSuccessor(e); - node.removeSuccessor(edge); + LayoutNode succNode = edge.to; + assert succNode.vertex == null; + for (LayoutEdge e : succNode.succs) { + e.from = edge.from; + e.relativeFrom = edge.relativeFrom; + node.succs.add(e); + node.succs.remove(edge); } removeNodeWithoutRemovingLayer(succNode); } removeNodeWithoutRemovingLayer(node); - insertNode(node, node.getLayer() + 1); + insertNode(node, node.layer + 1); - for (LayoutEdge edge : List.copyOf(node.getPredecessors())) { + for (LayoutEdge edge : List.copyOf(node.preds)) { processSingleEdge(edge); } } @@ -757,25 +890,25 @@ private void handleNeighborNodesOnSameLayer(LayoutNode from, LayoutNode to) { * remaining layers numbers */ private void expandNewLayerBeneath(LayoutNode node) { - int layer = node.getLayer() + 1; + int layer = node.layer + 1; // Move all necessary layers down one step for (int i = layers.size() - 1; i >= layer; i--) { - LayoutLayer list = layers.get(i); + List list = layers.get(i); for (LayoutNode n : list) { - n.setLayer(i + 1); + n.layer = i + 1; } layers.remove(i); layers.put(i + 1, list); } // Create new empty layer - LayoutLayer l = new LayoutLayer(); + List l = new ArrayList<>(); layers.put(layer, l); assert layers.get(layer).isEmpty(); for (LayoutNode n : nodes) { - assert n.getLayer() != layer; + assert n.layer != layer; } // Add dummy nodes for edges going across new layer. One for each port on the @@ -784,28 +917,35 @@ private void expandNewLayerBeneath(LayoutNode node) { for (LayoutNode n : predLayer) { HashMap> portHashes = new HashMap<>(); - for (LayoutEdge e : n.getSuccessors()) { - if (!portHashes.containsKey(e.getRelativeFromX())) { - portHashes.put(e.getRelativeFromX(), new ArrayList<>()); + for (LayoutEdge e : n.succs) { + if (!portHashes.containsKey(e.relativeFrom)) { + portHashes.put(e.relativeFrom, new ArrayList<>()); } - portHashes.get(e.getRelativeFromX()).add(e); + portHashes.get(e.relativeFrom).add(e); } for (Integer i : portHashes.keySet()) { List edges = portHashes.get(i); LayoutNode dummy = new LayoutNode(); - - LayoutEdge newEdge = new LayoutEdge(n, dummy, i, dummy.getOuterWidth() / 2, edges.get(0).getLink()); - n.addSuccessor(newEdge); - dummy.addPredecessor(newEdge); + dummy.width = DUMMY_WIDTH; + dummy.height = DUMMY_HEIGHT; + + LayoutEdge newEdge = new LayoutEdge(); + newEdge.from = n; + newEdge.relativeFrom = i; + newEdge.to = dummy; + newEdge.relativeTo = dummy.width / 2; + newEdge.link = edges.get(0).link; // issue? + n.succs.add(newEdge); + dummy.preds.add(newEdge); for (LayoutEdge e : edges) { - e.setFrom(dummy); - e.setRelativeFromX(dummy.getOuterWidth() / 2); - n.removeSuccessor(e); - dummy.addSuccessor(e); - assert e.getTo().getLayer() == layer + 1; + e.from = dummy; + e.relativeFrom = dummy.width / 2; + n.succs.remove(e); + dummy.succs.add(e); + assert e.to.layer == layer + 1; } insertNode(dummy, layer); @@ -815,7 +955,7 @@ private void expandNewLayerBeneath(LayoutNode node) { // Move node to new layer moveNodeDown(node); assert layers.get(layer).contains(node); - assert node.getLayer() == layer; + assert node.layer == layer; } private void applyAddLinkAction(Link l) { @@ -828,12 +968,18 @@ private void applyAddLinkAction(Link l) { return; } - if (toNode.getLayer() == fromNode.getLayer()) { + if (toNode.layer == fromNode.layer) { handleNeighborNodesOnSameLayer(fromNode, toNode); } - LayoutEdge edge = new LayoutEdge(fromNode, toNode, l.getFrom().getRelativePosition().x, l.getTo().getRelativePosition().x, l); - boolean reversedLink = fromNode.getLayer() > toNode.getLayer(); + LayoutEdge edge = new LayoutEdge(); + edge.link = l; + edge.from = fromNode; + edge.relativeFrom = l.getFrom().getRelativePosition().x; + edge.to = toNode; + edge.relativeTo = l.getTo().getRelativePosition().x; + + boolean reversedLink = fromNode.layer > toNode.layer; if (reversedLink) { // Reversed link reversedLinks.add(l); @@ -842,23 +988,23 @@ private void applyAddLinkAction(Link l) { fromNode = toNode; toNode = temp; - int oldRelativeFrom = edge.getRelativeFromX(); - int oldRelativeTo = edge.getRelativeToX(); + int oldRelativeFrom = edge.relativeFrom; + int oldRelativeTo = edge.relativeTo; - edge.setFrom(fromNode); - edge.setTo(toNode); - edge.setRelativeFromX(oldRelativeTo); - edge.setRelativeToX(oldRelativeFrom); + edge.from = fromNode; + edge.to = toNode; + edge.relativeFrom = oldRelativeTo; + edge.relativeTo = oldRelativeFrom; } - fromNode.addSuccessor(edge); - toNode.addPredecessor(edge); + fromNode.succs.add(edge); + toNode.preds.add(edge); if (reversedLink) { updateReversedLinkPositions(l); } - if (fromNode.getLayer() != toNode.getLayer() - 1) { + if (fromNode.layer != toNode.layer - 1) { // Edge span multiple layers - must insert dummy nodes insertDummyNodes(edge); } @@ -892,20 +1038,20 @@ private int optimalLayer(Vertex vertex, List links) { LayoutNode fromNode = vertexToLayoutNode.get(link.getFrom().getVertex()); LayoutNode toNode = vertexToLayoutNode.get(link.getTo().getVertex()); if (link.getTo().getVertex().equals(vertex) && fromNode != null) { - if (fromNode.getLayer() > i) { + if (fromNode.layer > i) { curReversedEdges += 1; - } else if (fromNode.getLayer() == i) { + } else if (fromNode.layer == i) { curNeighborsOnSameLayer += 1; } - curTotalEdgeLength += Math.abs(fromNode.getLayer() - i); + curTotalEdgeLength += Math.abs(fromNode.layer - i); } if (link.getFrom().getVertex().equals(vertex) && toNode != null) { - if (toNode.getLayer() < i) { + if (toNode.layer < i) { curReversedEdges += 1; - } else if (toNode.getLayer() == i) { + } else if (toNode.layer == i) { curNeighborsOnSameLayer += 1; } - curTotalEdgeLength += Math.abs(i - toNode.getLayer()); + curTotalEdgeLength += Math.abs(i - toNode.layer); } } @@ -926,7 +1072,12 @@ private int optimalLayer(Vertex vertex, List links) { } private void applyAddVertexAction(VertexAction action) { - LayoutNode node = new LayoutNode(action.vertex); + LayoutNode node = new LayoutNode(); + Dimension size = action.vertex.getSize(); + node.width = (int) size.getWidth(); + node.height = (int) size.getHeight(); + node.vertex = action.vertex; + List links = new ArrayList<>(); for (LinkAction a : action.linkActions) { links.add(a.link); @@ -936,19 +1087,26 @@ private void applyAddVertexAction(VertexAction action) { // Temporarily add the links so that the node insertion accounts for edge // crossings for (Link l : links) { + LayoutEdge e = new LayoutEdge(); if (l.getTo().getVertex().equals(action.vertex) && nodes.contains(vertexToLayoutNode.get(l.getFrom().getVertex()))) { - LayoutEdge e = new LayoutEdge(vertexToLayoutNode.get(l.getFrom().getVertex()), node, l.getFrom().getRelativePosition().x, l.getTo().getRelativePosition().x, null); - node.addPredecessor(e); + e.to = node; + e.from = vertexToLayoutNode.get(l.getFrom().getVertex()); + e.relativeFrom = l.getFrom().getRelativePosition().x; + e.relativeTo = l.getTo().getRelativePosition().x; + node.preds.add(e); } else if (l.getFrom().getVertex().equals(action.vertex) && nodes.contains(vertexToLayoutNode.get(l.getTo().getVertex()))) { - LayoutEdge e = new LayoutEdge(node, vertexToLayoutNode.get(l.getTo().getVertex()), l.getFrom().getRelativePosition().x, l.getTo().getRelativePosition().x, null); - node.addSuccessor(e); + e.from = node; + e.to = vertexToLayoutNode.get(l.getTo().getVertex()); + e.relativeFrom = l.getFrom().getRelativePosition().x; + e.relativeTo = l.getTo().getRelativePosition().x; + node.succs.add(e); } } insertNode(node, layer); - node.clearSuccessors(); - node.clearPredecessors(); + node.succs.clear(); + node.preds.clear(); // Add associated edges for (LinkAction a : action.linkActions) { @@ -964,39 +1122,44 @@ private void applyRemoveLinkAction(Link l) { LayoutNode toNode = vertexToLayoutNode.get(to); LayoutNode fromNode = vertexToLayoutNode.get(from); - if (toNode.getLayer() < fromNode.getLayer()) { + if (toNode.layer < fromNode.layer) { // Reversed edge + LayoutNode temp = toNode; toNode = fromNode; + fromNode = temp; + reversedLinks.remove(l); + reversedLinkEndPoints.remove(l); + reversedLinkStartPoints.remove(l); } // Remove preds-edges bottom up, starting at "to" node // Cannot start from "from" node since there might be joint edges - List toNodePredsEdges = List.copyOf(toNode.getPredecessors()); + List toNodePredsEdges = List.copyOf(toNode.preds); for (LayoutEdge edge : toNodePredsEdges) { - LayoutNode n = edge.getFrom(); + LayoutNode n = edge.from; LayoutEdge edgeToRemove; - if (edge.getLink() != null && edge.getLink().equals(l)) { - toNode.removePredecessor(edge); + if (edge.link != null && edge.link.equals(l)) { + toNode.preds.remove(edge); edgeToRemove = edge; } else { // Wrong edge, look at next continue; } - if (n.getVertex() != null && n.getVertex().equals(from)) { + if (n.vertex != null && n.vertex.equals(from)) { // No dummy nodes inbetween 'from' and 'to' vertex - n.removeSuccessor(edgeToRemove); + n.succs.remove(edgeToRemove); break; } else { // Must remove edges between dummy nodes boolean found = true; LayoutNode prev = toNode; - while (n.getVertex() == null && found) { + while (n.vertex == null && found) { found = false; - if (n.getSuccessors().size() <= 1 && n.getPredecessors().size() <= 1) { + if (n.succs.size() <= 1 && n.preds.size() <= 1) { // Dummy node used only for this link, remove if not already removed if (nodes.contains(n)) { removeNode(n); @@ -1006,17 +1169,17 @@ private void applyRemoveLinkAction(Link l) { break; } - if (n.getPredecessors().size() == 1) { - n.removeSuccessor(edgeToRemove); + if (n.preds.size() == 1) { + n.succs.remove(edgeToRemove); prev = n; - edgeToRemove = n.getPredecessors().get(0); - n = edgeToRemove.getFrom(); + edgeToRemove = n.preds.get(0); + n = edgeToRemove.from; found = true; } } - n.removeSuccessor(edgeToRemove); - prev.removePredecessor(edgeToRemove); + n.succs.remove(edgeToRemove); + prev.preds.remove(edgeToRemove); } break; } @@ -1035,19 +1198,19 @@ private void removeNode(LayoutNode node) { if (!nodes.contains(node)) { return; } - int layer = node.getLayer(); - int pos = node.getPos(); - LayoutLayer remainingLayerNodes = layers.get(layer); + int layer = node.layer; + int pos = node.pos; + List remainingLayerNodes = layers.get(layer); assert remainingLayerNodes.contains(node); remainingLayerNodes.remove(node); // Update position of remaining nodes on the same layer boolean onlyDummiesLeft = true; for (LayoutNode n : remainingLayerNodes) { - if (n.getPos() > pos) { - n.setPos(n.getPos() - 1); + if (n.pos > pos) { + n.pos -= 1; } - if (n.getVertex() != null || n.getPredecessors().size() > 1) { + if (n.vertex != null || n.preds.size() > 1) { onlyDummiesLeft = false; } } @@ -1055,22 +1218,22 @@ private void removeNode(LayoutNode node) { if (onlyDummiesLeft && shouldRemoveEmptyLayers) { layers.remove(layer); for (int i = layer + 1; i <= layers.size(); i++) { - LayoutLayer list = layers.get(i); + List list = layers.get(i); layers.remove(i); layers.put(i - 1, list); for (LayoutNode n : list) { - n.setLayer(n.getLayer() - 1); + n.layer -= 1; } } for (LayoutNode n : remainingLayerNodes) { - if (n.getPredecessors().size() == 1) { - LayoutEdge predEdge = n.getPredecessors().get(0); - LayoutNode fromNode = predEdge.getFrom(); - fromNode.removeSuccessor(predEdge); - for (LayoutEdge e : n.getSuccessors()) { - e.setFrom(fromNode); - e.setRelativeFromX(predEdge.getRelativeFromX()); - fromNode.addSuccessor(e); + if (n.preds.size() == 1) { + LayoutEdge predEdge = n.preds.get(0); + LayoutNode fromNode = predEdge.from; + fromNode.succs.remove(predEdge); + for (LayoutEdge e : n.succs) { + e.from = fromNode; + e.relativeFrom = predEdge.relativeFrom; + fromNode.succs.add(e); } } nodes.remove(n); @@ -1129,12 +1292,12 @@ void run() { Set layoutedLinks = new HashSet<>(); Set layoutedNodes = new HashSet<>(); for (LayoutNode n : nodes) { - for (LayoutEdge e : n.getPredecessors()) { - if (e.getLink() != null) { - layoutedLinks.add(e.getLink()); + for (LayoutEdge e : n.preds) { + if (e.link != null) { + layoutedLinks.add(e.link); } } - if (n.getVertex() != null) { + if (n.vertex != null) { layoutedNodes.add(n); } } @@ -1157,151 +1320,135 @@ void run() { private class AssignYCoordinates { void run() { - int currentY = 0; - for (int i = 0; i < layers.size(); i++) { - LayoutLayer layer = layers.get(i); - layer.setTop(currentY); - // Calculate the maximum layer height and set it for the layer - int maxLayerHeight = layer.calculateMaxLayerHeight(); - layer.setHeight(maxLayerHeight); - - // Center nodes vertically within the layer - layer.centerNodesVertically(); - - // Update currentY to account for the padded bottom of this layer - currentY += layer.calculatePaddedHeight(); + // Reset all values before assigning y-coordinates + for (LayoutNode n : nodes) { + if (n.vertex != null) { + updateNodeWithReversedEdges(n); + } else { + n.height = DUMMY_HEIGHT; + } + n.y = 0; } - } - } - private class WriteResult { + int curY = 0; - private HashMap> computeLinkPositions() { - HashMap> linkToSplitEndPoints = new HashMap<>(); - HashMap> linkPositions = new HashMap<>(); + for (int i = 0; i < layers.size(); i++) { + List layer = layers.get(i); + int maxHeight = 0; + int baseLine = 0; + int bottomBaseLine = 0; + for (LayoutNode n : layer) { + maxHeight = Math.max(maxHeight, n.height - n.yOffset - n.bottomYOffset); + baseLine = Math.max(baseLine, n.yOffset); + bottomBaseLine = Math.max(bottomBaseLine, n.bottomYOffset); + } - for (LayoutNode layoutNode : nodes) { - if (layoutNode.isDummy()) continue; - for (LayoutEdge predEdge : layoutNode.getPredecessors()) { - LayoutNode fromNode = predEdge.getFrom(); - LayoutNode toNode = predEdge.getTo(); - - ArrayList linkPoints = new ArrayList<>(); - // input edge stub - linkPoints.add(new Point(predEdge.getEndX(), predEdge.getEndY())); - linkPoints.add(new Point(predEdge.getEndX(), layers.get(toNode.getLayer()).getTop() - LAYER_OFFSET)); - - LayoutEdge curEdge = predEdge; - while (fromNode.isDummy() && fromNode.hasPredecessors()) { - linkPoints.add(new Point(fromNode.getCenterX(), layers.get(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); - linkPoints.add(new Point(fromNode.getCenterX(), layers.get(fromNode.getLayer()).getTop() - LAYER_OFFSET)); - curEdge = fromNode.getPredecessors().get(0); - fromNode = curEdge.getFrom(); - } - linkPoints.add(new Point(curEdge.getStartX(), layers.get(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); - // output edge stub - linkPoints.add(new Point(curEdge.getStartX(), curEdge.getStartY())); - - if (predEdge.isReversed()) { - for (Point relativeEnd : toNode.getReversedLinkEndPoints().get(predEdge.getLink())) { - Point endPoint = new Point(toNode.getLeft() + relativeEnd.x, toNode.getTop() + relativeEnd.y); - linkPoints.add(0, endPoint); - } + int maxXOffset = 0; + for (LayoutNode n : layer) { + if (n.vertex == null) { + // Dummy node + n.y = curY; + n.height = maxHeight + baseLine + bottomBaseLine; - if (!fromNode.isDummy()) { - if (fromNode.getReversedLinkStartPoints().containsKey(predEdge.getLink())) { - for (Point relativeStart : fromNode.getReversedLinkStartPoints().get(predEdge.getLink())) { - Point startPoint = new Point(fromNode.getLeft() + relativeStart.x, fromNode.getTop() + relativeStart.y); - linkPoints.add(startPoint); - } - } - } } else { - Collections.reverse(linkPoints); + n.y = curY + baseLine + (maxHeight - (n.height - n.yOffset - n.bottomYOffset)) / 2 - n.yOffset; } - if (fromNode.isDummy()) { - if (predEdge.isReversed()) { - Collections.reverse(linkPoints); - } - linkToSplitEndPoints.put(predEdge.getLink(), linkPoints); - - } else { - linkPositions.put(predEdge.getLink(), linkPoints); + for (LayoutEdge e : n.succs) { + int curXOffset = Math.abs(n.x - e.to.x); + maxXOffset = Math.max(curXOffset, maxXOffset); } } + + curY += maxHeight + baseLine + bottomBaseLine; + curY += LAYER_OFFSET + ((int) (Math.sqrt(maxXOffset) * 1.5)); } + } + } - for (LayoutNode layoutNode : nodes) { - if (layoutNode.isDummy()) continue; - for (LayoutEdge succEdge : layoutNode.getSuccessors()) { - if (succEdge.getLink() == null) continue; + private class WriteResult { - LayoutNode fromNode = succEdge.getFrom(); - LayoutNode toNode = succEdge.getTo(); + private List edgePoints(LayoutEdge e) { + ArrayList points = new ArrayList<>(); - ArrayList linkPoints = new ArrayList<>(); - linkPoints.add(new Point(succEdge.getStartX(), fromNode.getBottom())); - linkPoints.add(new Point(succEdge.getStartX(), layers.get(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); + Point p = new Point(e.to.x + e.relativeTo, + e.to.y + e.to.yOffset + e.link.getTo().getRelativePosition().y); + points.add(p); + if (e.to.inOffsets.containsKey(e.relativeTo)) { + points.add(new Point(p.x, + p.y + e.to.inOffsets.get(e.relativeTo) + e.link.getTo().getRelativePosition().y)); + } - LayoutEdge curEdge = succEdge; - while (toNode.isDummy() && toNode.hasSuccessors()) { - linkPoints.add(new Point(toNode.getCenterX(), layers.get(toNode.getLayer()).getTop() - LAYER_OFFSET)); - linkPoints.add(new Point(toNode.getCenterX(), layers.get(toNode.getLayer()).getBottom() + LAYER_OFFSET)); - curEdge = toNode.getSuccessors().get(0); - toNode = curEdge.getTo(); - } - linkPoints.add(new Point(curEdge.getEndX(), layers.get(toNode.getLayer()).getTop() - LAYER_OFFSET)); - linkPoints.add(new Point(curEdge.getEndX(), toNode.getTop())); + LayoutNode cur = e.from; + LayoutEdge curEdge = e; + while (cur.vertex == null && !cur.preds.isEmpty()) { + 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) { + // On the same vertical line, can remove previous point + points.remove(points.size() - 1); + } + // Top of the dummy node + points.add(new Point(cur.x + cur.width / 2, cur.y + cur.height)); + 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); + } + // Bottom of the dummy node + points.add(new Point(cur.x + cur.width / 2, cur.y)); + assert cur.preds.size() == 1; + curEdge = cur.preds.get(0); + cur = curEdge.from; + } - if (succEdge.isReversed()) { - Collections.reverse(linkPoints); + p = new Point(cur.x + curEdge.relativeFrom, cur.y + cur.height - cur.bottomYOffset + + (curEdge.link == null ? 0 : curEdge.link.getFrom().getRelativePosition().y)); + if (curEdge.from.outOffsets.containsKey(curEdge.relativeFrom)) { + points.add(new Point(p.x, p.y + curEdge.from.outOffsets.get(curEdge.relativeFrom) + + (curEdge.link == null ? 0 : curEdge.link.getFrom().getRelativePosition().y))); + } + points.add(p); - if (fromNode.getReversedLinkStartPoints().containsKey(succEdge.getLink())) { - for (Point relativeStart : fromNode.getReversedLinkStartPoints().get(succEdge.getLink())) { - Point startPoint = new Point(fromNode.getLeft() + relativeStart.x, fromNode.getTop() + relativeStart.y); - linkPoints.add(startPoint); - } - } + Collections.reverse(points); - if (!toNode.isDummy()) { - if (toNode.getReversedLinkEndPoints().containsKey(succEdge.getLink())) { - for (Point relativeEnd : toNode.getReversedLinkEndPoints().get(succEdge.getLink())) { - Point endPoint = new Point(toNode.getLeft() + relativeEnd.x, toNode.getTop() + relativeEnd.y); - linkPoints.add(0, endPoint); - } - } - } - } + if (reversedLinks.contains(e.link)) { + Collections.reverse(points); - if (linkToSplitEndPoints.containsKey(succEdge.getLink())) { - if (succEdge.isReversed()) { - Collections.reverse(linkPoints); - } - linkPoints.add(null); - linkPoints.addAll(linkToSplitEndPoints.get(succEdge.getLink())); - if (succEdge.isReversed()) { - Collections.reverse(linkPoints); - } - } - linkPositions.put(succEdge.getLink(), linkPoints); + assert reversedLinkStartPoints.containsKey(e.link); + for (Point p1 : reversedLinkStartPoints.get(e.link)) { + points.add(new Point(p1.x + cur.x, p1.y + cur.y)); + } + + assert reversedLinkEndPoints.containsKey(e.link); + for (Point p1 : reversedLinkEndPoints.get(e.link)) { + points.add(0, new Point(p1.x + e.to.x, p1.y + e.to.y)); } } - return linkPositions; + return points; } - void run() { HashMap vertexPositions = new HashMap<>(); - HashMap> linkPositions = computeLinkPositions(); - + HashMap> linkPositions = new HashMap<>(); for (LayoutNode n : nodes) { - if (n.getVertex() != null) { - assert !vertexPositions.containsKey(n.getVertex()); - vertexPositions.put(n.getVertex(), new Point(n.getLeft(), n.getTop())); + + if (n.vertex != null) { + assert !vertexPositions.containsKey(n.vertex); + vertexPositions.put(n.vertex, new Point(n.x + n.xOffset, n.y + n.yOffset)); + } else { + continue; + } + + // All edges can be drawn from bottom up, the links are stored in the preds list + // of each node + for (LayoutEdge e : n.preds) { + if (e.link != null && !linkPositions.containsKey(e.link)) { + List points = edgePoints(e); + assert !linkPositions.containsKey(e.link); + linkPositions.put(e.link, points); + } } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/InterClusterConnection.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/InterClusterConnection.java index eaa2789b1d937..4112ac997b8f1 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/InterClusterConnection.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/InterClusterConnection.java @@ -31,6 +31,7 @@ import java.util.List; /** + * * @author Thomas Wuerthinger */ public class InterClusterConnection implements Link { @@ -61,14 +62,14 @@ public Cluster getToCluster() { return null; } - public List getControlPoints() { - return intermediatePoints; - } - public void setControlPoints(List p) { this.intermediatePoints = p; } + public List getControlPoints() { + return intermediatePoints; + } + @Override public String toString() { return "InterClusterConnection[from=" + getFrom() + ", to=" + getTo() + "]"; diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java index e0aa527acab57..eba424936531b 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java @@ -25,31 +25,29 @@ package com.sun.hotspot.igv.hierarchicallayout; import com.sun.hotspot.igv.layout.Link; +import java.util.Comparator; -/** - * Represents an edge in the layout graph between two nodes (LayoutNode). - * Contains information about the source and target nodes, relative positions, - * and whether the edge has been reversed (used for back edges in hierarchical layouts). - */ public class LayoutEdge { - private Link link; + public static final Comparator LAYOUT_EDGE_LAYER_COMPARATOR = Comparator.comparingInt(e -> e.getTo().getLayer()); + private LayoutNode from; private LayoutNode to; - // Horizontal distance relative to the start of 'from' node. + // Horizontal distance relative to start of 'from'. private int relativeFromX; - // Horizontal distance relative to the start of 'to' node. + // Horizontal distance relative to start of 'to'. private int relativeToX; + private Link link; private boolean isReversed; - /** - * Constructs a LayoutEdge between two nodes with the specified link. - * The relative positions are set to zero by default. - * - * @param from The source LayoutNode. - * @param to The target LayoutNode. - * @param link The Link associated with this edge. - */ + public int getStartX() { + return relativeFromX + from.getLeft(); + } + + public int getEndX() { + return relativeToX + to.getLeft(); + } + public LayoutEdge(LayoutNode from, LayoutNode to, Link link) { this.from = from; this.to = to; @@ -57,70 +55,16 @@ public LayoutEdge(LayoutNode from, LayoutNode to, Link link) { this.isReversed = false; } - /** - * Constructs a LayoutEdge between two nodes with specified relative positions and link. - * - * @param from The source LayoutNode. - * @param to The target LayoutNode. - * @param relativeFromX The horizontal distance relative to the start of 'from' node. - * @param relativeToX The horizontal distance relative to the start of 'to' node. - * @param link The Link associated with this edge. - */ public LayoutEdge(LayoutNode from, LayoutNode to, int relativeFromX, int relativeToX, Link link) { this(from, to, link); this.relativeFromX = relativeFromX; this.relativeToX = relativeToX; } - /** - * Gets the absolute x-coordinate of the starting point of the edge. - * - * @return The x-coordinate of the edge's starting point. - */ - public int getStartX() { - return relativeFromX + from.getLeft(); - } - - /** - * Gets the absolute y-coordinate of the starting point of the edge. - * - * @return The y-coordinate of the edge's starting point. - */ - public int getStartY() { - return from.getBottom(); - } - - /** - * Gets the absolute x-coordinate of the ending point of the edge. - * - * @return The x-coordinate of the edge's ending point. - */ - public int getEndX() { - return relativeToX + to.getLeft(); - } - - /** - * Gets the absolute y-coordinate of the ending point of the edge. - * - * @return The y-coordinate of the edge's ending point. - */ - public int getEndY() { - return to.getTop(); - } - - /** - * Reverses the direction of the edge. - * Marks the edge as reversed, which is used to represent back edges in hierarchical layouts. - */ public void reverse() { isReversed = !isReversed; } - /** - * Checks if the edge is reversed. - * - * @return True if the edge is reversed; false otherwise. - */ public boolean isReversed() { return isReversed; } @@ -130,101 +74,45 @@ public String toString() { return "Edge " + from + ", " + to; } - /** - * Gets the source node of the edge. - * - * @return The source LayoutNode. - */ public LayoutNode getFrom() { return from; } - /** - * Sets the source node of the edge. - * - * @param from The LayoutNode to set as the source. - */ public void setFrom(LayoutNode from) { this.from = from; } - /** - * Gets the target node of the edge. - * - * @return The target LayoutNode. - */ public LayoutNode getTo() { return to; } - /** - * Sets the target node of the edge. - * - * @param to The LayoutNode to set as the target. - */ public void setTo(LayoutNode to) { this.to = to; } - /** - * Gets the absolute x-coordinate of the source node's connection point for this edge. - * - * @return The x-coordinate of the source node's connection point. - */ public int getFromX() { return from.getX() + getRelativeFromX(); } - /** - * Gets the absolute x-coordinate of the target node's connection point for this edge. - * - * @return The x-coordinate of the target node's connection point. - */ public int getToX() { return to.getX() + getRelativeToX(); } - /** - * Gets the relative horizontal position from the source node's left boundary to the edge's starting point. - * - * @return The relative x-coordinate from the source node. - */ public int getRelativeFromX() { return relativeFromX; } - - /** - * Sets the relative horizontal position from the source node's left boundary to the edge's starting point. - * - * @param relativeFromX The relative x-coordinate to set. - */ public void setRelativeFromX(int relativeFromX) { this.relativeFromX = relativeFromX; } - /** - * Gets the relative horizontal position from the target node's left boundary to the edge's ending point. - * - * @return The relative x-coordinate to the target node. - */ public int getRelativeToX() { return relativeToX; } - /** - * Sets the relative horizontal position from the target node's left boundary to the edge's ending point. - * - * @param relativeToX The relative x-coordinate to set. - */ public void setRelativeToX(int relativeToX) { this.relativeToX = relativeToX; } - /** - * Gets the Link associated with this edge. - * - * @return The Link object. - */ public Link getLink() { return link; } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java index 5dbe1d0177e06..e6a072263a243 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,13 +23,18 @@ */ package com.sun.hotspot.igv.hierarchicallayout; -import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.NODE_POS_COMPARATOR; import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Port; import com.sun.hotspot.igv.layout.Vertex; import java.util.*; import java.util.stream.Collectors; +import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.NODE_POS_COMPARATOR; + +/** + * + * @author Thomas Wuerthinger + */ public class LayoutGraph { public static final Comparator LINK_COMPARATOR = @@ -38,220 +43,32 @@ public class LayoutGraph { .thenComparingInt(l -> l.getFrom().getRelativePosition().x) .thenComparingInt(l -> l.getTo().getRelativePosition().x); - // Registered Graph Components: Links, Vertices, and Port Mappings - private final Set links; + private final Set links; private final SortedSet vertices; - private final LinkedHashMap> inputPorts; - private final LinkedHashMap> outputPorts; - private final LinkedHashMap> portLinks; + private final HashMap> inputPorts; + private final HashMap> outputPorts; + private final HashMap> portLinks; - // Layout Management: LayoutNodes and LayoutLayers - private final LinkedHashMap layoutNodes; private final List dummyNodes; - private final List layers; - - /** - * Constructs a new LayoutGraph using the provided collection of links and additional vertices. - * Initializes the graph layout structure with the given links and includes any additional vertices. - * - * @param links The collection of links that represent the edges of the graph. - * @param additionalVertices The collection of additional vertices to be included in the graph. - */ - public LayoutGraph(Collection links, Collection additionalVertices) { - this.links = new HashSet<>(links); - vertices = new TreeSet<>(additionalVertices); - portLinks = new LinkedHashMap<>(links.size()); - inputPorts = new LinkedHashMap<>(links.size()); - outputPorts = new LinkedHashMap<>(links.size()); - - for (Link link : links) { - assert link.getFrom() != null; - assert link.getTo() != null; - Port fromPort = link.getFrom(); - Port toPort = link.getTo(); - Vertex fromVertex = fromPort.getVertex(); - Vertex toVertex = toPort.getVertex(); - - vertices.add(fromVertex); - vertices.add(toVertex); + private final LinkedHashMap vertexToLayoutNode; - outputPorts.computeIfAbsent(fromVertex, k -> new HashSet<>()).add(fromPort); - inputPorts.computeIfAbsent(toVertex, k -> new HashSet<>()).add(toPort); - - portLinks.computeIfAbsent(fromPort, k -> new HashSet<>()).add(link); - portLinks.computeIfAbsent(toPort, k -> new HashSet<>()).add(link); - } + private List layers; - // cleanup - layoutNodes = new LinkedHashMap<>(); - dummyNodes = new ArrayList<>(); - layers = new ArrayList<>(); - } - - public void clearLayout() { - layoutNodes.clear(); - dummyNodes.clear(); - layers.clear(); + public LayoutGraph(Set links) { + this(links, new HashSet<>()); } - /** - * Initializes or resets the layout structures by clearing existing nodes, dummy nodes, and layers. - * It then sets up the layout nodes for each vertex and creates layout edges based on the sorted links. - */ - public void initializeLayout() { - // Reset layout structures - clearLayout(); - - // Set up layout nodes for each vertex - for (Vertex vertex : getVertices()) { - createLayoutNode(vertex); - } - - // Set up layout edges in a sorted order for reproducibility - List sortedLinks = new ArrayList<>(links); - sortedLinks.sort(LINK_COMPARATOR); - for (Link link : sortedLinks) { - createLayoutEdge(link); - } - } - - /** - * Initializes the layers of the graph with the specified number of empty layers. - * - * @param layerCount The number of layers to initialize. - */ public void initLayers(int layerCount) { - layers.clear(); + layers = new ArrayList<>(layerCount); for (int i = 0; i < layerCount; i++) { layers.add(new LayoutLayer()); } } - /** - * Removes a link from the graph. - * - * @param link The Link to be removed. - */ - public void removeLink(Link link) { - if (!links.contains(link)) { - return; - } - - Port fromPort = link.getFrom(); - Port toPort = link.getTo(); - Vertex fromVertex = fromPort.getVertex(); - Vertex toVertex = toPort.getVertex(); - - // Remove from links set - links.remove(link); - - // Update portLinks - Set fromPortLinks = portLinks.get(fromPort); - if (fromPortLinks != null) { - fromPortLinks.remove(link); - if (fromPortLinks.isEmpty()) { - portLinks.remove(fromPort); - } - } - - Set toPortLinks = portLinks.get(toPort); - if (toPortLinks != null) { - toPortLinks.remove(link); - if (toPortLinks.isEmpty()) { - portLinks.remove(toPort); - } - } - - // Update inputPorts and outputPorts - Set fromVertexOutputPorts = outputPorts.get(fromVertex); - if (fromVertexOutputPorts != null) { - fromVertexOutputPorts.remove(fromPort); - if (fromVertexOutputPorts.isEmpty()) { - outputPorts.remove(fromVertex); - } - } - - Set toVertexInputPorts = inputPorts.get(toVertex); - if (toVertexInputPorts != null) { - toVertexInputPorts.remove(toPort); - if (toVertexInputPorts.isEmpty()) { - inputPorts.remove(toVertex); - } - } - - // Remove corresponding LayoutEdge - removeEdge(link); - } - - /** - * Removes a vertex and all associated links from the graph. - * - * @param vertex The Vertex to be removed. - * @throws IllegalArgumentException if the vertex does not exist in the graph. - */ - public void removeVertex(Vertex vertex) { - assert vertices.contains(vertex) : "Vertex does not exist in the graph"; - - // Remove all associated links - List associatedLinks = getAllLinks(vertex); - for (Link link : associatedLinks) { - removeLink(link); - } - - // Remove from vertices set - vertices.remove(vertex); - - // Remove from inputPorts and outputPorts - inputPorts.remove(vertex); - outputPorts.remove(vertex); - - // Remove LayoutNode - LayoutNode node = layoutNodes.get(vertex); - if (node != null) { - removeNodeAndEdges(node); - } - - updatePositions(); - } - - /** - * Retrieves an unmodifiable list of dummy nodes in the graph. - * - * @return An unmodifiable list containing all dummy nodes in the graph. - */ public List getDummyNodes() { return Collections.unmodifiableList(dummyNodes); } - /** - * Retrieves a collection of all layout nodes in the graph. - * - * @return A collection containing all LayoutNodes. - */ - public Collection getLayoutNodes() { - return Collections.unmodifiableCollection(layoutNodes.values()); - } - - /** - * Retrieves a combined list of all nodes in the graph, - * including both layout nodes and dummy nodes. - * - * @return An unmodifiable list containing all nodes in the graph. - */ - public List getAllNodes() { - List allNodes = new ArrayList<>(); - allNodes.addAll(layoutNodes.values()); - allNodes.addAll(dummyNodes); - return Collections.unmodifiableList(allNodes); - } - - /** - * Creates a new layer at the specified index in the layers list. - * Adjusts the layer numbers of existing nodes in layers below the inserted layer. - * - * @param layerNr The index at which to insert the new layer. - * @return The newly created LayoutLayer. - */ private LayoutLayer createNewLayer(int layerNr) { LayoutLayer layer = new LayoutLayer(); layers.add(layerNr, layer); @@ -265,12 +82,6 @@ private LayoutLayer createNewLayer(int layerNr) { return layer; } - /** - * Deletes the layer at the specified index. - * Adjusts the layer numbers of existing nodes in layers below the deleted layer. - * - * @param layerNr The index of the layer to delete. - */ private void deleteLayer(int layerNr) { layers.remove(layerNr); @@ -282,15 +93,10 @@ private void deleteLayer(int layerNr) { } } - /** - * Ensures that no neighboring nodes of the specified node are in the same layer. - * If any neighbor is found in the specified layer, inserts a new layer to avoid conflicts. - * Returns the adjusted layer number where the node can be safely inserted. - * - * @param node The LayoutNode to check and possibly reposition. - * @param layerNr The proposed layer number for the node. - * @return The layer number where the node can be safely inserted after adjustments. - */ + + // check that NO neighbors of node are in a given layer + // otherwise insert a new layer + // return the layerNr where the node can now be safely inserted public int insertNewLayerIfNeeded(LayoutNode node, int layerNr) { for (Link inputLink : getInputLinks(node.getVertex())) { if (inputLink.getFrom().getVertex() == inputLink.getTo().getVertex()) continue; @@ -312,14 +118,11 @@ public int insertNewLayerIfNeeded(LayoutNode node, int layerNr) { } - /** - * Inserts a new layer at the specified index and adjusts nodes and edges accordingly. - * Moves existing nodes and their successors down to accommodate the new layer. - * - * @param layerNr The index at which to insert the new layer. - */ + // inserts a new layer at layerNr + // inserts dummy nodes acoring to layerNr - 1 + // moves the layer from previous layerNr to layerNr + 1 private void moveExpandLayerDown(int layerNr) { - LayoutLayer newLayer = createNewLayer(layerNr); + LayoutLayer newLayer = createNewLayer(layerNr); if (layerNr == 0) return; LayoutLayer layerAbove = getLayer(layerNr - 1); @@ -327,7 +130,7 @@ private void moveExpandLayerDown(int layerNr) { for (LayoutNode fromNode : layerAbove) { int fromX = fromNode.getX(); Map> successorsByX = fromNode.groupSuccessorsByX(); - fromNode.clearSuccessors(); + fromNode.getSuccs().clear(); for (Map.Entry> entry : successorsByX.entrySet()) { Integer relativeFromX = entry.getKey(); @@ -335,245 +138,166 @@ private void moveExpandLayerDown(int layerNr) { LayoutNode dummyNode = new LayoutNode(); dummyNode.setX(fromX + relativeFromX); dummyNode.setLayer(layerNr); - for (LayoutEdge edge : edges) { - dummyNode.addSuccessor(edge); - } + dummyNode.getSuccs().addAll(edges); LayoutEdge dummyEdge = new LayoutEdge(fromNode, dummyNode, relativeFromX, 0, edges.get(0).getLink()); if (edges.get(0).isReversed()) dummyEdge.reverse(); - fromNode.addSuccessor(dummyEdge); - dummyNode.addPredecessor(dummyEdge); + fromNode.getSuccs().add(dummyEdge); + dummyNode.getPreds().add(dummyEdge); for (LayoutEdge edge : edges) { edge.setFrom(dummyNode); } - addDummyToLayer(dummyNode, layerNr); + addNodeToLayer(dummyNode, layerNr); } } - newLayer.sortNodesByX(); + newLayer.sortNodesByXAndSetPositions(); } - /** - * Retrieves an unmodifiable list of all layers in the graph. - * - * @return An unmodifiable list containing all layers. - */ public List getLayers() { return Collections.unmodifiableList(layers); } - /** - * Returns the total number of layers in the graph. - * - * @return The number of layers. - */ public int getLayerCount() { return layers.size(); } - /** - * Retrieves the LayoutNode associated with the specified Vertex. - * - * @param vertex The vertex whose LayoutNode is to be retrieved. - * @return The LayoutNode corresponding to the given vertex, or null if not found. - */ + public Collection getLayoutNodes() { + return vertexToLayoutNode.values(); + } + public LayoutNode getLayoutNode(Vertex vertex) { - return layoutNodes.get(vertex); + return vertexToLayoutNode.get(vertex); } - /** - * Adds a LayoutNode to the specified layer and registers it in the graph. - * - * @param node The LayoutNode to add to the layer. - * @param layerNumber The index of the layer to which the node will be added. - */ - public void addNodeToLayer(LayoutNode node, int layerNumber) { - assert !node.isDummy(); - node.setLayer(layerNumber); - getLayer(layerNumber).add(node); - if (!layoutNodes.containsKey(node.getVertex())) { - layoutNodes.put(node.getVertex(), node); + public LayoutGraph(Collection links, Collection additionalVertices) { + this.links = new HashSet<>(links); + + vertices = new TreeSet<>(additionalVertices); + portLinks = new HashMap<>(links.size()); + inputPorts = new HashMap<>(links.size()); + outputPorts = new HashMap<>(links.size()); + + for (Link link : links) { + assert link.getFrom() != null; + assert link.getTo() != null; + Port fromPort = link.getFrom(); + Port toPort = link.getTo(); + Vertex fromVertex = fromPort.getVertex(); + Vertex toVertex = toPort.getVertex(); + + vertices.add(fromVertex); + vertices.add(toVertex); + + outputPorts.computeIfAbsent(fromVertex, k -> new HashSet<>()).add(fromPort); + inputPorts.computeIfAbsent(toVertex, k -> new HashSet<>()).add(toPort); + + portLinks.computeIfAbsent(fromPort, k -> new HashSet<>()).add(link); + portLinks.computeIfAbsent(toPort, k -> new HashSet<>()).add(link); + } + + // cleanup + vertexToLayoutNode = new LinkedHashMap<>(); + dummyNodes = new ArrayList<>(); + + + // Set up nodes + for (Vertex v : getVertices()) { + LayoutNode node = new LayoutNode(v); + vertexToLayoutNode.put(v, node); + } + + // Set up edges + List sortedLinks = new ArrayList<>(links); + sortedLinks.sort(LINK_COMPARATOR); + for (Link link : links) { + createLayoutEdge(link); } } - /** - * Adds a LayoutNode to the specified layer and registers it in the graph. - * - * @param node The LayoutNode to add to the layer. - * @param layerNumber The index of the layer to which the node will be added. - */ - public void addDummyToLayer(LayoutNode node, int layerNumber) { - assert node.isDummy(); + public void addNodeToLayer(LayoutNode node, int layerNumber) { node.setLayer(layerNumber); getLayer(layerNumber).add(node); - dummyNodes.add(node); + + // Register node in the appropriate collection based on its type + registerNode(node); } - /** - * Updates the positions of all nodes in each layer. - * Should be called after changes to node positions or layer compositions. - */ - public void updatePositions() { - for (LayoutLayer layer : layers) { - layer.updateNodeIndices(); + private void registerNode(LayoutNode node) { + if (node.isDummy()) { + dummyNodes.add(node); + } else { + vertexToLayoutNode.put(node.getVertex(), node); } } - // Create and register LayoutNode - public LayoutNode createLayoutNode(Vertex vertex) { - if (!vertices.contains(vertex)) { - throw new IllegalArgumentException("Vertex does not exist in the graph: " + vertex); + + public void removeNode(LayoutNode node) { + int layer = node.getLayer(); + layers.get(layer).remove(node); + layers.get(layer).updateLayerPositions(); + // Remove node from graph layout + if (node.isDummy()) { + dummyNodes.remove(node); + } else { + vertexToLayoutNode.remove(node.getVertex()); + } + } + + public void updatePositions() { + for (LayoutLayer layer : layers) { + layer.updateLayerPositions(); } - LayoutNode node = new LayoutNode(vertex); - layoutNodes.put(vertex, node); - return node; } - /** - * Creates a LayoutEdge based on the given Link and connects it to the corresponding LayoutNodes. - * - * @param link The Link representing the edge in the graph. - * @return The newly created LayoutEdge. - */ public LayoutEdge createLayoutEdge(Link link) { LayoutEdge edge = new LayoutEdge( - layoutNodes.get(link.getFrom().getVertex()), - layoutNodes.get(link.getTo().getVertex()), + vertexToLayoutNode.get(link.getFrom().getVertex()), + vertexToLayoutNode.get(link.getTo().getVertex()), link.getFrom().getRelativePosition().x, link.getTo().getRelativePosition().x, link); - edge.getFrom().addSuccessor(edge); - edge.getTo().addPredecessor(edge); + edge.getFrom().getSuccs().add(edge); + edge.getTo().getPreds().add(edge); return edge; } - /** - * Retrieves the set of all links (edges) in the graph. - * - * @return A set containing all links in the graph. - */ - public Set getLinks() { + public Set getLinks() { return links; } - /** - * Retrieves the set of all vertices in the graph, sorted in natural order. - * - * @return A sorted set of all vertices in the graph. - */ public SortedSet getVertices() { return vertices; } - /** - * Checks whether the graph contains the specified vertex. - * - * @param vertex The vertex to check for presence in the graph. - * @return True if the vertex is present, false otherwise. - */ public boolean containsVertex(Vertex vertex) { return vertices.contains(vertex); } - /** - * Finds all root vertices in the graph (vertices with no incoming links). - * - * @return A set of root vertices. - */ public Set findRootVertices() { return vertices.stream() .filter(v -> inputPorts.getOrDefault(v, Collections.emptySet()).isEmpty()) .collect(Collectors.toSet()); } - /** - * Retrieves all incoming links to the specified vertex. - * - * @param vertex The vertex whose incoming links are to be retrieved. - * @return A set of links that are incoming to the vertex. - */ - public List getInputLinks(Vertex vertex) { - List inputLinks = new ArrayList<>(); + public Set getInputLinks(Vertex vertex) { + Set inputLinks = new HashSet<>(); for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { inputLinks.addAll(portLinks.getOrDefault(inputPort, Collections.emptySet())); } return inputLinks; } - /** - * Retrieves all outgoing links from the specified vertex. - * - * @param vertex The vertex whose outgoing links are to be retrieved. - * @return A set of links that are outgoing from the vertex. - */ - public List getOutputLinks(Vertex vertex) { - List outputLinks = new ArrayList<>(); + public Set getOutputLinks(Vertex vertex) { + Set outputLinks = new HashSet<>(); for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) { outputLinks.addAll(portLinks.getOrDefault(outputPort, Collections.emptySet())); } return outputLinks; } - /** - * Checks if the given predecessorVertex is a direct predecessor of the specified vertex. - * - * @param vertex The vertex to check for predecessors. - * @param predecessorVertex The vertex to verify as a predecessor of the given vertex. - * @return true if predecessorVertex is a direct predecessor of vertex, false otherwise. - */ - public boolean isPredecessorVertex(Vertex vertex, Vertex predecessorVertex) { - for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { - for (Link inputLink : portLinks.getOrDefault(inputPort, Collections.emptySet())) { - Vertex fromVertex = inputLink.getFrom().getVertex(); - if (fromVertex.equals(predecessorVertex)) { - return true; - } - } - } - return false; - } - - /** - * Checks if the given successorVertex is a direct successor of the specified vertex. - * - * @param vertex The vertex to check for successors. - * @param successorVertex The vertex to verify as a successor of the given vertex. - * @return true if successorVertex is a direct successor of vertex, false otherwise. - */ - public boolean isSuccessorVertex(Vertex vertex, Vertex successorVertex) { - for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) { - for (Link outputLink : portLinks.getOrDefault(outputPort, Collections.emptySet())) { - Vertex toVertex = outputLink.getTo().getVertex(); - if (toVertex.equals(successorVertex)) { - return true; - } - } - } - return false; - } - - - public List getNeighborVertices(Vertex vertex) { - List neighborVertices = new ArrayList<>(); - for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { - for (Link inputLink : portLinks.getOrDefault(inputPort, Collections.emptySet())) { - Vertex fromVertex = inputLink.getFrom().getVertex(); - assert fromVertex != null; - neighborVertices.add(fromVertex); - } - } - for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) { - for (Link outputLink : portLinks.getOrDefault(outputPort, Collections.emptySet())) { - Vertex toVertex = outputLink.getTo().getVertex(); - assert toVertex != null; - neighborVertices.add(toVertex); - } - } - return neighborVertices; - } - - public List getAllLinks(Vertex vertex) { - List allLinks = new ArrayList<>(); + private Set getAllLinks(Vertex vertex) { + Set allLinks = new HashSet<>(); for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { allLinks.addAll(portLinks.getOrDefault(inputPort, Collections.emptySet())); @@ -586,123 +310,94 @@ public List getAllLinks(Vertex vertex) { return allLinks; } - /** - * Removes the specified LayoutNode and all its connected edges from the graph. - * - * @param node The LayoutNode to remove along with its edges. - */ - public void removeNodeAndEdges(LayoutNode node) { - assert !node.isDummy(); - removeEdges(node); // a node can only be removed together with its edges - int layer = node.getLayer(); - layers.get(layer).remove(node); - layers.get(layer).updateNodeIndices(); - layoutNodes.remove(node.getVertex()); - } - + private void removeEdges(LayoutNode movedNode) { + for (Link inputLink : getAllLinks(movedNode.getVertex())) { + Vertex from = inputLink.getFrom().getVertex(); + Vertex to = inputLink.getTo().getVertex(); + LayoutNode toNode = getLayoutNode(to); + LayoutNode fromNode = getLayoutNode(from); - /** - * Removes all edges connected to the specified LayoutNode. - * Handles the removal of associated dummy nodes if they are no longer needed. - * Updates the graph structure accordingly after node movement. - * - * @param node The LayoutNode whose connected edges are to be removed. - */ - public void removeEdges(LayoutNode node) { - assert !node.isDummy(); - for (Link link : getAllLinks(node.getVertex())) { - removeEdge(link); - } - } - - public void removeEdge(Link link) { - Vertex from = link.getFrom().getVertex(); - Vertex to = link.getTo().getVertex(); - LayoutNode toNode = getLayoutNode(to); - LayoutNode fromNode = getLayoutNode(from); + if (toNode.getLayer() < fromNode.getLayer()) { + // Reversed edge + toNode = fromNode; + toNode.getReversedLinkEndPoints().remove(inputLink); + fromNode.getReversedLinkStartPoints().remove(inputLink); + } - if (toNode.getLayer() < fromNode.getLayer()) { - // Reversed edge - toNode = fromNode; - } + // Remove preds-edges bottom up, starting at "to" node + // Cannot start from "from" node since there might be joint edges + List toNodePredsEdges = List.copyOf(toNode.getPreds()); + for (LayoutEdge edge : toNodePredsEdges) { + LayoutNode predNode = edge.getFrom(); + LayoutEdge edgeToRemove; - // Remove preds-edges bottom up, starting at "to" node - // Cannot start from "from" node since there might be joint edges - List toNodePredsEdges = List.copyOf(toNode.getPredecessors()); - for (LayoutEdge edge : toNodePredsEdges) { - LayoutNode predNode = edge.getFrom(); - LayoutEdge edgeToRemove; + if (edge.getLink() != null && edge.getLink().equals(inputLink)) { + toNode.getPreds().remove(edge); + edgeToRemove = edge; + } else { + // Wrong edge, look at next + continue; + } - if (edge.getLink() != null && edge.getLink().equals(link)) { - toNode.removePredecessor(edge); - edgeToRemove = edge; - } else { - // Wrong edge, look at next - continue; - } + if (!predNode.isDummy() && predNode.getVertex().equals(from)) { + // No dummy nodes inbetween 'from' and 'to' vertex + predNode.getSuccs().remove(edgeToRemove); + break; + } else { + // Must remove edges between dummy nodes + boolean found = true; + LayoutNode succNode = toNode; + while (predNode.isDummy() && found) { + found = false; + + if (predNode.getSuccs().size() <= 1 && predNode.getPreds().size() <= 1) { + // Dummy node used only for this link, remove if not already removed + removeNode(predNode); + } else { + // anchor node, should not be removed + break; + } - if (!predNode.isDummy() && predNode.getVertex().equals(from)) { - // No dummy nodes inbetween 'from' and 'to' vertex - predNode.removeSuccessor(edgeToRemove); - break; - } else { - // Must remove edges between dummy nodes - boolean found = true; - LayoutNode succNode = toNode; - while (predNode.isDummy() && found) { - found = false; - - if (predNode.getSuccessors().size() <= 1 && predNode.getPredecessors().size() <= 1) { - // Dummy node used only for this link, remove if not already removed - assert predNode.isDummy(); - int layer = predNode.getLayer(); - layers.get(layer).remove(predNode); - layers.get(layer).updateNodeIndices(); - dummyNodes.remove(predNode); - } else { - // anchor node, should not be removed - break; + if (predNode.getPreds().size() == 1) { + predNode.getSuccs().remove(edgeToRemove); + succNode = predNode; + edgeToRemove = predNode.getPreds().get(0); + predNode = edgeToRemove.getFrom(); + found = true; + } } - if (predNode.getPredecessors().size() == 1) { - predNode.removeSuccessor(edgeToRemove); - succNode = predNode; - edgeToRemove = predNode.getPredecessors().get(0); - predNode = edgeToRemove.getFrom(); - found = true; - } + predNode.getSuccs().remove(edgeToRemove); + succNode.getPreds().remove(edgeToRemove); } - - predNode.removeSuccessor(edgeToRemove); - succNode.removePredecessor(edgeToRemove); + break; } - break; } - if (fromNode.getReversedLinkStartPoints().containsKey(link)) { - fromNode.computeReversedLinkPoints(false); - } - if (toNode.getReversedLinkStartPoints().containsKey(link)) { - toNode.computeReversedLinkPoints(false); + // remove link connected to movedNode + for (Link link : getLinks()) { + if (link.getTo().getVertex() == movedNode.getVertex()) { + link.setControlPoints(new ArrayList<>()); + movedNode.getReversedLinkStartPoints().remove(link); + } else if (link.getFrom().getVertex() == movedNode.getVertex()) { + link.setControlPoints(new ArrayList<>()); + movedNode.getReversedLinkEndPoints().remove(link); + } } + + movedNode.initSize(); } - /** - * Retrieves the LayoutLayer at the specified index. - * - * @param layerNr The index of the layer to retrieve. - * @return The LayoutLayer at the specified index. - */ + public void removeNodeAndEdges(LayoutNode node) { + removeEdges(node); + removeNode(node); + } + + public LayoutLayer getLayer(int layerNr) { return layers.get(layerNr); } - /** - * Finds the layer closest to the given y-coordinate. - * - * @param y the y-coordinate to check - * @return the index of the optimal layer, or -1 if no layers are found - */ public int findLayer(int y) { int optimalLayer = -1; int minDistance = Integer.MAX_VALUE; @@ -721,10 +416,6 @@ public int findLayer(int y) { return optimalLayer; } - /** - * Positions the layers vertically, calculating their heights and setting their positions. - * Centers the nodes within each layer vertically. - */ public void positionLayers() { int currentY = 0; for (LayoutLayer layer : getLayers()) { @@ -738,28 +429,22 @@ public void positionLayers() { layer.centerNodesVertically(); // Update currentY to account for the padded bottom of this layer - currentY += layer.calculatePaddedHeight(); + currentY += layer.calculateScalePaddedBottom(); } } - /** - * Optimizes routing of reversed (back) edges to reduce crossings. - */ public void optimizeBackEdgeCrossings() { for (LayoutNode node : getLayoutNodes()) { - node.optimizeBackEdgeCrossing(); + if (node.getReversedLinkStartPoints().isEmpty() && node.getReversedLinkEndPoints().isEmpty()) continue; + node.computeReversedLinkPoints(); } } - /** - * Removes empty layers from the graph. - * Iteratively checks for and removes layers that contain only dummy nodes. - */ public void removeEmptyLayers() { int i = 0; while (i < getLayerCount()) { LayoutLayer layer = getLayer(i); - if (layer.containsOnlyDummyNodes()) { + if (layer.isDummyLayer()) { removeEmptyLayer(i); } else { i++; // Move to the next layer only if no removal occurred @@ -767,44 +452,38 @@ public void removeEmptyLayers() { } } - /** - * Removes the layer at the specified index if it is empty or contains only dummy nodes. - * Adjusts the positions of nodes and edges accordingly. - * - * @param layerNr The index of the layer to remove. - */ private void removeEmptyLayer(int layerNr) { LayoutLayer layer = getLayer(layerNr); - if (!layer.containsOnlyDummyNodes()) return; + if (!layer.isDummyLayer()) return; for (LayoutNode dummyNode : layer) { - if (dummyNode.getSuccessors().isEmpty()) { + if (dummyNode.getSuccs().isEmpty()) { dummyNode.setLayer(layerNr + 1); getLayer(layerNr + 1).add(dummyNode); - dummyNode.setX(dummyNode.calculateOptimalXFromPredecessors(true)); - getLayer(layerNr + 1).sortNodesByX(); + dummyNode.setX(dummyNode.calculateOptimalPositionDown()); + getLayer(layerNr + 1).sortNodesByXAndSetPositions(); continue; - } else if (dummyNode.getPredecessors().isEmpty()) { + } else if (dummyNode.getPreds().isEmpty()) { dummyNode.setLayer(layerNr - 1); - dummyNode.setX(dummyNode.calculateOptimalXFromSuccessors(true)); + dummyNode.setX(dummyNode.calculateOptimalPositionUp()); getLayer(layerNr - 1).add(dummyNode); - getLayer(layerNr - 1).sortNodesByX(); + getLayer(layerNr - 1).sortNodesByXAndSetPositions(); continue; } - LayoutEdge layoutEdge = dummyNode.getPredecessors().get(0); + LayoutEdge layoutEdge = dummyNode.getPreds().get(0); // remove the layoutEdge LayoutNode fromNode = layoutEdge.getFrom(); - fromNode.removeSuccessor(layoutEdge); + fromNode.getSuccs().remove(layoutEdge); - List successorEdges = dummyNode.getSuccessors(); + List successorEdges = dummyNode.getSuccs(); for (LayoutEdge successorEdge : successorEdges) { successorEdge.setRelativeFromX(layoutEdge.getRelativeFromX()); successorEdge.setFrom(fromNode); - fromNode.addSuccessor(successorEdge); + fromNode.getSuccs().add(successorEdge); } - dummyNode.clearPredecessors(); - dummyNode.clearSuccessors(); + dummyNode.getPreds().clear(); + dummyNode.getSuccs().clear(); dummyNodes.remove(dummyNode); } @@ -812,11 +491,11 @@ private void removeEmptyLayer(int layerNr) { } /** - * Repositions the specified LayoutNode horizontally within its layer to the new x-coordinate. - * Ensures no overlap with adjacent nodes and maintains minimum spacing. + * Repositions the given LayoutNode to the specified x-coordinate within its layer, + * ensuring no overlap with adjacent nodes and maintaining a minimum NODE_OFFSET distance. * - * @param layoutNode The LayoutNode to reposition. - * @param newX The new x-coordinate to set for the node. + * @param layoutNode The LayoutNode to be repositioned. + * @param newX The desired new x-coordinate for the layoutNode. */ private void repositionLayoutNodeX(LayoutNode layoutNode, int newX) { int currentX = layoutNode.getX(); @@ -828,22 +507,23 @@ private void repositionLayoutNodeX(LayoutNode layoutNode, int newX) { LayoutLayer layer = getLayer(layoutNode.getLayer()); if (newX > currentX) { - layer.tryShiftNodeRight(layoutNode, newX); + layer.attemptMoveRight(layoutNode, newX); } else { - layer.tryShiftNodeLeft(layoutNode, newX); + layer.attemptMoveLeft(layoutNode, newX); } } /** * Aligns the x-coordinate of a single dummy successor node for the given LayoutNode. * If the node has exactly one successor and that successor is a dummy node, - * sets the dummy node's x-coordinate to align with the current node or the edge's starting point. + * this method sets the dummy node's x-coordinate to either the node's x-coordinate + * (if the node is a dummy) or to the starting x-coordinate of the connecting edge. * - * @param node The LayoutNode whose dummy successor is to be aligned. + * @param node The LayoutNode whose single dummy successor needs to be aligned. */ private void alignSingleSuccessorDummyNodeX(LayoutNode node) { // Retrieve the list of successor edges - List successors = node.getSuccessors(); + List successors = node.getSuccs(); // Proceed only if there is exactly one successor if (successors.size() != 1) { @@ -885,8 +565,8 @@ private void alignLayerDummySuccessors(LayoutLayer layer) { } /** - * Straightens edges in the graph by aligning dummy nodes to reduce bends. - * Processes all layers to align dummy successor nodes. + * Aligns the x-coordinates of dummy successor nodes across all layers. + * Performs alignment in both forward and backward directions for comprehensive coverage. */ public void straightenEdges() { // Forward pass: Align dummy successors from the first layer to the last. @@ -900,343 +580,4 @@ public void straightenEdges() { } } - /** - * Calculates the optimal horizontal position (index) for the specified node within the given layer, - * aiming to minimize the number of edge crossings. - * - * @param node The node to position. - * @param layerNr The index of the layer in which to position the node. - * @return The optimal position index within the layer for the node. - */ - private int optimalPosition(LayoutNode node, int layerNr) { - getLayer(layerNr).sort(NODE_POS_COMPARATOR); - int edgeCrossings = Integer.MAX_VALUE; - int optimalPos = -1; - - // Try each possible position in the layerNr - for (int i = 0; i < getLayer(layerNr).size() + 1; i++) { - int xCoord; - if (i == 0) { - xCoord = getLayer(layerNr).get(i).getX() - node.getWidth() - 1; - } else { - xCoord = getLayer(layerNr).get(i - 1).getX() + getLayer(layerNr).get(i - 1).getWidth() + 1; - } - - int currentCrossings = 0; - - if (0 <= layerNr - 1) { - // For each link with an end point in vertex, check how many edges cross it - for (LayoutEdge edge : node.getPredecessors()) { - if (edge.getFrom().getLayer() == layerNr - 1) { - int fromNodeXCoord = edge.getFromX(); - int toNodeXCoord = xCoord; - if (!node.isDummy()) { - toNodeXCoord += edge.getRelativeToX(); - } - for (LayoutNode n : getLayer(layerNr - 1)) { - for (LayoutEdge e : n.getSuccessors()) { - if (e.getTo() == null) { - continue; - } - int compFromXCoord = e.getFromX(); - int compToXCoord = e.getToX(); - if ((fromNodeXCoord > compFromXCoord && toNodeXCoord < compToXCoord) - || (fromNodeXCoord < compFromXCoord - && toNodeXCoord > compToXCoord)) { - currentCrossings += 1; - } - } - } - } - } - } - // Edge crossings across current layerNr and layerNr below - if (layerNr + 1 < getLayerCount()) { - // For each link with an end point in vertex, check how many edges cross it - for (LayoutEdge edge : node.getSuccessors()) { - if (edge.getTo().getLayer() == layerNr + 1) { - int toNodeXCoord = edge.getToX(); - int fromNodeXCoord = xCoord; - if (!node.isDummy()) { - fromNodeXCoord += edge.getRelativeFromX(); - } - for (LayoutNode n : getLayer(layerNr + 1)) { - for (LayoutEdge e : n.getPredecessors()) { - if (e.getFrom() == null) { - continue; - } - int compFromXCoord = e.getFromX(); - int compToXCoord = e.getToX(); - if ((fromNodeXCoord > compFromXCoord && toNodeXCoord < compToXCoord) - || (fromNodeXCoord < compFromXCoord - && toNodeXCoord > compToXCoord)) { - currentCrossings += 1; - } - } - } - } - } - } - if (currentCrossings <= edgeCrossings) { - edgeCrossings = currentCrossings; - optimalPos = i; - } - } - return optimalPos; - } - - /** - * Creates layout edges for the specified node and reverses edges as needed. - * Reverses edges that go from lower to higher layers to maintain proper layering. - * - * @param node The LayoutNode for which to create and reverse edges. - */ - public void createAndReverseLayoutEdges(LayoutNode node) { - List nodeLinks = new ArrayList<>(getInputLinks(node.getVertex())); - nodeLinks.addAll(getOutputLinks(node.getVertex())); - nodeLinks.sort(LINK_COMPARATOR); - - List reversedLayoutNodes = new ArrayList<>(); - for (Link link : nodeLinks) { - if (link.getFrom().getVertex() == link.getTo().getVertex()) continue; - LayoutEdge layoutEdge = createLayoutEdge(link); - - LayoutNode fromNode = layoutEdge.getFrom(); - LayoutNode toNode = layoutEdge.getTo(); - - if (fromNode.getLayer() > toNode.getLayer()) { - HierarchicalLayoutManager.ReverseEdges.reverseEdge(layoutEdge); - reversedLayoutNodes.add(fromNode); - reversedLayoutNodes.add(toNode); - } - } - - // ReverseEdges - for (LayoutNode layoutNode : reversedLayoutNodes) { - layoutNode.computeReversedLinkPoints(false); - } - } - - /** - * Inserts dummy nodes along the edges from predecessors of the specified node, - * for edges that span more than one layer. - * - * @param layoutNode The node for which to create predecessor dummy nodes. - */ - public void createDummiesForNodePredecessor(LayoutNode layoutNode) { - for (LayoutEdge predEdge : layoutNode.getPredecessors()) { - LayoutNode fromNode = predEdge.getFrom(); - LayoutNode toNode = predEdge.getTo(); - if (Math.abs(toNode.getLayer() - fromNode.getLayer()) <= 1) continue; - - boolean hasEdgeFromSamePort = false; - LayoutEdge edgeFromSamePort = new LayoutEdge(fromNode, toNode, predEdge.getLink()); - if (predEdge.isReversed()) edgeFromSamePort.reverse(); - - for (LayoutEdge succEdge : fromNode.getSuccessors()) { - if (succEdge.getRelativeFromX() == predEdge.getRelativeFromX() && succEdge.getTo().isDummy()) { - edgeFromSamePort = succEdge; - hasEdgeFromSamePort = true; - break; - } - } - - if (hasEdgeFromSamePort) { - LayoutEdge curEdge = edgeFromSamePort; - boolean newEdge = true; - while (curEdge.getTo().getLayer() < toNode.getLayer() - 1 && curEdge.getTo().isDummy() && newEdge) { - // Traverse down the chain of dummy nodes linking together the edges originating - // from the same port - newEdge = false; - if (curEdge.getTo().getSuccessors().size() == 1) { - curEdge = curEdge.getTo().getSuccessors().get(0); - newEdge = true; - } else { - for (LayoutEdge e : curEdge.getTo().getSuccessors()) { - if (e.getTo().isDummy()) { - curEdge = e; - newEdge = true; - break; - } - } - } - } - - LayoutNode prevDummy; - if (!curEdge.getTo().isDummy()) { - prevDummy = curEdge.getFrom(); - } else { - prevDummy = curEdge.getTo(); - } - - predEdge.setFrom(prevDummy); - predEdge.setRelativeFromX(prevDummy.getWidth() / 2); - fromNode.removeSuccessor(predEdge); - prevDummy.addSuccessor(predEdge); - } - - LayoutNode layoutNode1 = predEdge.getTo(); - if (predEdge.getTo().getLayer() - 1 > predEdge.getFrom().getLayer()) { - LayoutEdge prevEdge = predEdge; - for (int l = layoutNode1.getLayer() - 1; l > prevEdge.getFrom().getLayer(); l--) { - LayoutNode dummyNode = new LayoutNode(); - dummyNode.addSuccessor(prevEdge); - LayoutEdge result = new LayoutEdge(prevEdge.getFrom(), dummyNode, prevEdge.getRelativeFromX(), 0, prevEdge.getLink()); - if (prevEdge.isReversed()) result.reverse(); - dummyNode.addPredecessor(result); - prevEdge.setRelativeFromX(0); - prevEdge.getFrom().removeSuccessor(prevEdge); - prevEdge.getFrom().addSuccessor(result); - prevEdge.setFrom(dummyNode); - dummyNode.setLayer(l); - List layerNodes = getLayer(l); - if (layerNodes.isEmpty()) { - dummyNode.setPos(0); - } else { - dummyNode.setPos(optimalPosition(dummyNode, l)); - } - for (LayoutNode n : layerNodes) { - if (n.getPos() >= dummyNode.getPos()) { - n.setPos(n.getPos() + 1); - } - } - addDummyToLayer(dummyNode, l); - prevEdge = dummyNode.getPredecessors().get(0); - } - } - } - } - - /** - * Inserts dummy nodes along the edges to successors of the specified node, - * for edges that span more than one layer. - * Can limit the maximum length of layers an edge spans using maxLayerLength. - * - * @param layoutNode The node for which to create successor dummy nodes. - * @param maxLayerLength The maximum number of layers an edge can span without splitting it - */ - public void createDummiesForNodeSuccessor(LayoutNode layoutNode, int maxLayerLength) { - LinkedHashMap> portsToUnprocessedEdges = new LinkedHashMap<>(); - ArrayList succs = new ArrayList<>(layoutNode.getSuccessors()); - LinkedHashMap portToTopNode = new LinkedHashMap<>(); - LinkedHashMap> portToBottomNodeMapping = new LinkedHashMap<>(); - for (LayoutEdge succEdge : succs) { - int startPort = succEdge.getRelativeFromX(); - LayoutNode fromNode = succEdge.getFrom(); - LayoutNode toNode = succEdge.getTo(); - - // edge is longer than one layer => needs dummy nodes - if (fromNode.getLayer() != toNode.getLayer() - 1) { - // the edge needs to be cut - if (maxLayerLength != -1 && toNode.getLayer() - fromNode.getLayer() > maxLayerLength) { - // remove the succEdge before replacing it - toNode.removePredecessor(succEdge); - fromNode.removeSuccessor(succEdge); - - LayoutNode topCutNode = portToTopNode.get(startPort); - if (topCutNode == null) { - topCutNode = new LayoutNode(); - topCutNode.setLayer(fromNode.getLayer() + 1); - addDummyToLayer(topCutNode, topCutNode.getLayer()); - portToTopNode.put(startPort, topCutNode); - portToBottomNodeMapping.put(startPort, new LinkedHashMap<>()); - } - LayoutEdge edgeToTopCut = new LayoutEdge(fromNode, topCutNode, succEdge.getRelativeFromX(), topCutNode.getWidth() / 2, succEdge.getLink()); - if (succEdge.isReversed()) edgeToTopCut.reverse(); - fromNode.addSuccessor(edgeToTopCut); - topCutNode.addPredecessor(edgeToTopCut); - - LinkedHashMap layerToBottomNode = portToBottomNodeMapping.get(startPort); - LayoutNode bottomCutNode = layerToBottomNode.get(toNode.getLayer()); - if (bottomCutNode == null) { - bottomCutNode = new LayoutNode(); - bottomCutNode.setLayer(toNode.getLayer() - 1); - addDummyToLayer(bottomCutNode, bottomCutNode.getLayer()); - layerToBottomNode.put(toNode.getLayer(), bottomCutNode); - } - LayoutEdge bottomEdge = new LayoutEdge(bottomCutNode, toNode, bottomCutNode.getWidth() / 2, succEdge.getRelativeToX(), succEdge.getLink()); - if (succEdge.isReversed()) bottomEdge.reverse(); - toNode.addPredecessor(bottomEdge); - bottomCutNode.addSuccessor(bottomEdge); - - } else { // the edge is not cut, but needs dummy nodes - portsToUnprocessedEdges.putIfAbsent(startPort, new ArrayList<>()); - portsToUnprocessedEdges.get(startPort).add(succEdge); - } - } - } - - for (Map.Entry> portToUnprocessedEdges : portsToUnprocessedEdges.entrySet()) { - Integer startPort = portToUnprocessedEdges.getKey(); - List unprocessedEdges = portToUnprocessedEdges.getValue(); - unprocessedEdges.sort(Comparator.comparingInt(e -> e.getTo().getLayer())); - - if (unprocessedEdges.size() == 1) { - // process a single edge - LayoutEdge singleEdge = unprocessedEdges.get(0); - LayoutNode fromNode = singleEdge.getFrom(); - if (singleEdge.getTo().getLayer() > fromNode.getLayer() + 1) { - LayoutEdge previousEdge = singleEdge; - for (int i = fromNode.getLayer() + 1; i < previousEdge.getTo().getLayer(); i++) { - LayoutNode dummyNode = new LayoutNode(); - dummyNode.setLayer(i); - dummyNode.addPredecessor(previousEdge); - addDummyToLayer(dummyNode, dummyNode.getLayer()); - LayoutEdge dummyEdge = new LayoutEdge(dummyNode, previousEdge.getTo(), dummyNode.getWidth() / 2, previousEdge.getRelativeToX(), singleEdge.getLink()); - if (previousEdge.isReversed()) dummyEdge.reverse(); - dummyNode.addSuccessor(dummyEdge); - previousEdge.setRelativeToX(dummyNode.getWidth() / 2); - previousEdge.getTo().removePredecessor(previousEdge); - previousEdge.getTo().addPredecessor(dummyEdge); - previousEdge.setTo(dummyNode); - previousEdge = dummyEdge; - } - } - } else { - int lastLayer = unprocessedEdges.get(unprocessedEdges.size() - 1).getTo().getLayer(); - int dummyCnt = lastLayer - layoutNode.getLayer() - 1; - LayoutEdge[] newDummyEdges = new LayoutEdge[dummyCnt]; - LayoutNode[] newDummyNodes = new LayoutNode[dummyCnt]; - - newDummyNodes[0] = new LayoutNode(); - newDummyNodes[0].setLayer(layoutNode.getLayer() + 1); - newDummyEdges[0] = new LayoutEdge(layoutNode, newDummyNodes[0], startPort, newDummyNodes[0].getWidth() / 2, null); - newDummyNodes[0].addPredecessor(newDummyEdges[0]); - layoutNode.addSuccessor(newDummyEdges[0]); - for (int j = 1; j < dummyCnt; j++) { - newDummyNodes[j] = new LayoutNode(); - newDummyNodes[j].setLayer(layoutNode.getLayer() + j + 1); - newDummyEdges[j] = new LayoutEdge(newDummyNodes[j - 1], newDummyNodes[j], null); - newDummyNodes[j].addPredecessor(newDummyEdges[j]); - newDummyNodes[j - 1].addSuccessor(newDummyEdges[j]); - } - for (LayoutEdge unprocessedEdge : unprocessedEdges) { - LayoutNode anchorNode = newDummyNodes[unprocessedEdge.getTo().getLayer() - layoutNode.getLayer() - 2]; - anchorNode.addSuccessor(unprocessedEdge); - unprocessedEdge.setFrom(anchorNode); - unprocessedEdge.setRelativeFromX(anchorNode.getWidth() / 2); - layoutNode.removeSuccessor(unprocessedEdge); - } - for (LayoutNode dummyNode : newDummyNodes) { - addDummyToLayer(dummyNode, dummyNode.getLayer()); - } - } - } - } - - /** - * Adds edges connected to the specified node, including any necessary dummy nodes. - * Handles edge reversal, dummy node insertion for both predecessors and successors, - * and updates node positions accordingly. - * - * @param node The LayoutNode to which edges will be added. - * @param maxLayerLength The maximum number of layers an edge can span without splitting it - */ - public void addEdges(LayoutNode node, int maxLayerLength) { - assert !node.isDummy(); - createAndReverseLayoutEdges(node); - createDummiesForNodeSuccessor(node, maxLayerLength); - createDummiesForNodePredecessor(node); - updatePositions(); - } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java index 4aea3294a6917..dc530dd31d7e5 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java @@ -28,82 +28,46 @@ import java.util.ArrayList; import java.util.Collection; -/** - * Represents a layer in a hierarchical graph layout. - * Each LayoutLayer contains a collection of LayoutNodes positioned at the same vertical level. - * Provides methods to manage the nodes within the layer, including positioning, sorting, - * and adjusting the layout to minimize overlaps and improve visual clarity. - */ public class LayoutLayer extends ArrayList { private int height = 0; private int y = 0; - /** - * Adds all LayoutNodes from the specified collection to this layer. - * Updates the layer's height based on the nodes added. - * - * @param c The collection of LayoutNodes to be added. - * @return true if this layer changed as a result of the call. - */ @Override public boolean addAll(Collection c) { - c.forEach(this::updateLayerHeight); + c.forEach(this::updateHeight); return super.addAll(c); } - /** - * Adds a single LayoutNode to this layer. - * Updates the layer's height based on the node added. - * - * @param n The LayoutNode to be added. - * @return true if the node was added successfully. - */ + private void updateHeight(LayoutNode n) { + height = Math.max(height, n.getOuterHeight()); + } + @Override public boolean add(LayoutNode n) { - updateLayerHeight(n); + updateHeight(n); return super.add(n); } - /** - * Updates the layer's height if the outer height of the given node exceeds the current height. - * - * @param n The LayoutNode whose height is to be considered. - */ - private void updateLayerHeight(LayoutNode n) { - height = Math.max(height, n.getOuterHeight()); - } - - /** - * Calculates and returns the maximum height among the nodes in this layer, including their margins. - * Adjusts the top and bottom margins of non-dummy nodes to be equal, effectively centering them vertically. - * - * @return The maximum outer height of nodes in this layer. - */ public int calculateMaxLayerHeight() { int maxLayerHeight = 0; for (LayoutNode layoutNode : this) { if (!layoutNode.isDummy()) { // Center the node by setting equal top and bottom margins - layoutNode.centerNode(); + int offset = Math.max(layoutNode.getTopMargin(), layoutNode.getBottomMargin()); + layoutNode.setTopMargin(offset); + layoutNode.setBottomMargin(offset); } maxLayerHeight = Math.max(maxLayerHeight, layoutNode.getOuterHeight()); } return maxLayerHeight; } - /** - * Calculates and returns the total height of this layer, including additional padding - * based on the maximum horizontal offset among the edges of its nodes. - * This padding helps in scaling the layer vertically to accommodate edge bends and crossings. - * - * @return The total padded height of the layer. - */ - public int calculatePaddedHeight() { + public int calculateScalePaddedBottom() { int maxXOffset = 0; for (LayoutNode layoutNode : this) { - for (LayoutEdge succEdge : layoutNode.getSuccessors()) { + for (LayoutEdge succEdge : layoutNode.getSuccs()) { maxXOffset = Math.max(Math.abs(succEdge.getStartX() - succEdge.getEndX()), maxXOffset); } } @@ -113,10 +77,6 @@ public int calculatePaddedHeight() { return scalePaddedBottom; } - /** - * Centers all nodes in this layer vertically within the layer's assigned space. - * Adjusts each node's Y-coordinate so that it is centered based on the layer's top and height. - */ public void centerNodesVertically() { for (LayoutNode layoutNode : this) { int centeredY = getTop() + (getHeight() - layoutNode.getOuterHeight()) / 2; @@ -124,71 +84,36 @@ public void centerNodesVertically() { } } - /** - * Shifts the top Y-coordinate of this layer by the specified amount. - * Useful for moving the entire layer up or down. - * - * @param shift The amount to shift the layer's top position. Positive values move it down. - */ - public void moveLayerVertically(int shift) { + public void setTop(int top) { + y = top; + } + + public void shiftTop(int shift) { y += shift; } - /** - * Gets the top Y-coordinate of this layer. - * - * @return The Y-coordinate representing the top of the layer. - */ public int getTop() { return y; } - /** - * Sets the top Y-coordinate of this layer. - * - * @param top The Y-coordinate representing the top of the layer. - */ - public void setTop(int top) { - y = top; - } - public int getCenter() { return y + height / 2; } - /** - * Gets the bottom Y-coordinate of this layer. - * - * @return The Y-coordinate representing the bottom of the layer. - */ public int getBottom() { return y + height; } - /** - * Gets the height of this layer. - * - * @return The height of the layer. - */ public int getHeight() { return height; } - /** - * Sets the height of this layer. - * - * @param height The height to set for the layer. - */ public void setHeight(int height) { this.height = height; } - /** - * Checks if this layer contains only dummy nodes. - * - * @return true if all nodes in the layer are dummy nodes; false otherwise. - */ - public boolean containsOnlyDummyNodes() { + // Layer contains no non-dummy nodes + public boolean isDummyLayer() { for (LayoutNode node : this) { if (!node.isDummy()) { return false; @@ -197,55 +122,29 @@ public boolean containsOnlyDummyNodes() { return true; } - /** - * Sorts the nodes in this layer by their X-coordinate in increasing order. - * Assigns position indices to nodes based on the sorted order. - * Adjusts the X-coordinates of nodes to ensure minimum spacing between them. - */ - public void sortNodesByX() { - if (isEmpty()) return; - - sort(NODE_X_COMPARATOR); // Sort nodes in the layer increasingly by x + public void sortNodesByXAndSetPositions() { + if (this.isEmpty()) return; - updateNodeIndices(); - updateMinXSpacing(false); - } + // Sort nodes in the layer increasingly by x + this.sort(NODE_X_COMPARATOR); - /** - * Ensures nodes have minimum horizontal spacing by adjusting their X positions. - * - * @param startFromZero if true, starts positioning from X = 0; otherwise, uses the first node's current X. - */ - public void updateMinXSpacing(boolean startFromZero) { - if (isEmpty()) { - return; // No nodes to adjust. - } - - int minX = startFromZero ? 0 : this.get(0).getX(); + int pos = 0; + int minX = this.get(0).getX(); // Starting X position for the first node for (LayoutNode node : this) { + node.setPos(pos); + pos++; + + // Set the X position of the node to at least minX, ensuring spacing int x = Math.max(node.getX(), minX); node.setX(x); - minX = x + node.getOuterWidth() + NODE_OFFSET; - } - } - /** - * Initializes nodes' X positions with spacing. - */ - public void initXPositions() { - int curX = 0; - for (LayoutNode node : this) { - node.setX(curX); - curX += node.getOuterWidth() + NODE_OFFSET; + // Update minX for the next node based on the current node's outer width and offset + minX = x + node.getOuterWidth() + NODE_OFFSET; } } - /** - * Updates the position indices of the nodes in this layer based on their order in the list. - * Useful after nodes have been added or removed to ensure position indices are consistent. - */ - public void updateNodeIndices() { + public void updateLayerPositions() { int pos = 0; for (LayoutNode layoutNode : this) { layoutNode.setPos(pos); @@ -253,15 +152,7 @@ public void updateNodeIndices() { } } - /** - * Attempts to move the specified node to the right within the layer to the given X-coordinate. - * Ensures that the node does not overlap with its right neighbor by checking required spacing. - * If movement is possible without causing overlap, the node's X-coordinate is updated. - * - * @param layoutNode The node to move. - * @param newX The desired new X-coordinate for the node. - */ - public void tryShiftNodeRight(LayoutNode layoutNode, int newX) { + public void attemptMoveRight(LayoutNode layoutNode, int newX) { int currentX = layoutNode.getX(); int shiftAmount = newX - currentX; int rightPos = layoutNode.getPos() + 1; @@ -281,15 +172,7 @@ public void tryShiftNodeRight(LayoutNode layoutNode, int newX) { } } - /** - * Attempts to move the specified node to the left within the layer to the given X-coordinate. - * Ensures that the node does not overlap with its left neighbor by checking required spacing. - * If movement is possible without causing overlap, the node's X-coordinate is updated. - * - * @param layoutNode The node to move. - * @param newX The desired new X-coordinate for the node. - */ - public void tryShiftNodeLeft(LayoutNode layoutNode, int newX) { + public void attemptMoveLeft(LayoutNode layoutNode, int newX) { int currentX = layoutNode.getX(); int shiftAmount = currentX - newX; int leftPos = layoutNode.getPos() - 1; diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutManager.java index d6f605e4674cb..be88f567eb6e1 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutManager.java @@ -26,18 +26,23 @@ import java.awt.Font; /** + * * @author Thomas Wuerthinger */ public abstract class LayoutManager { + void setCutEdges(boolean enable); + + void doLayout(LayoutGraph graph); + + public abstract void setCutEdges(boolean enable); + public static final Font TITLE_FONT = new Font("Arial", Font.BOLD, 14); public static final int SWEEP_ITERATIONS = 1; - public static final int CROSSING_ITERATIONS = 2; + public static final int CROSSING_ITERATIONS = 1; public static final int NODE_OFFSET = 8; public static final int LAYER_OFFSET = 8; public static final double SCALE_LAYER_PADDING = 1.5; - public abstract void setCutEdges(boolean enable); - public abstract void doLayout(LayoutGraph graph); } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java deleted file mode 100644 index 48599d78d7580..0000000000000 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 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 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ -package com.sun.hotspot.igv.hierarchicallayout; - -import com.sun.hotspot.igv.layout.Vertex; -import java.awt.Point; -import java.util.Set; - -public interface LayoutMover { - /** - * Moves a link by shifting its position along the X-axis. - * - * @param linkPos The current position of the link. - * @param shiftX The amount to shift the link along the X-axis. - */ - void moveLink(Point linkPos, int shiftX); - - /** - * Moves a set of vertices. - * - * @param movedVertices A set of vertices to be moved. - */ - void moveVertices(Set movedVertices); - - /** - * Moves a single vertex. - * - * @param movedVertex The vertex to be moved. - */ - void moveVertex(Vertex movedVertex); - - boolean isFreeForm(); -} - diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java index 7568484740e30..931ef1f2024ea 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java @@ -23,37 +23,30 @@ */ package com.sun.hotspot.igv.hierarchicallayout; -import static com.sun.hotspot.igv.hierarchicallayout.LayoutManager.NODE_OFFSET; import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Vertex; import java.awt.Dimension; import java.awt.Point; import java.util.*; -/** - * Represents a node in a hierarchical graph layout. - * A LayoutNode can be either an actual vertex from the graph or a dummy node inserted during the layout process. - * It stores layout-related properties such as position, size, margins, and connections to predecessor and successor nodes. - */ +import static com.sun.hotspot.igv.hierarchicallayout.LayoutManager.NODE_OFFSET; + public class LayoutNode { - // Comparator constants for sorting LayoutNodes in various ways public static final Comparator LAYOUT_NODE_DEGREE_COMPARATOR = Comparator.comparingInt(LayoutNode::getDegree); public static final Comparator NODE_POS_COMPARATOR = Comparator.comparingInt(LayoutNode::getPos); public static final Comparator NODE_X_COMPARATOR = Comparator.comparingInt(LayoutNode::getX); - public static final Comparator NODE_CROSSING_COMPARATOR = Comparator.comparingInt(LayoutNode::getCrossingNumber); + public static final Comparator CROSSING_NODE_COMPARATOR = Comparator.comparingDouble(LayoutNode::getWeightedPosition); + public static final Comparator DUMMY_NODES_FIRST = Comparator.comparing(LayoutNode::isDummy).reversed(); + public static final Comparator NODE_PROCESSING_DOWN_COMPARATOR = DUMMY_NODES_FIRST.thenComparingInt(LayoutNode::getOutDegree); + public static final Comparator NODE_PROCESSING_UP_COMPARATOR = DUMMY_NODES_FIRST.thenComparing(LayoutNode::getInDegree); + public static final Comparator DUMMY_NODES_THEN_OPTIMAL_X = DUMMY_NODES_FIRST.thenComparing(LayoutNode::getOptimalX); - - // Default dimensions for dummy nodes public static final int DUMMY_HEIGHT = 1; public static final int DUMMY_WIDTH = 1; - private Vertex vertex; // Associated graph vertex; null for dummy nodes - private final List preds = new ArrayList<>(); // Incoming edges - private final List succs = new ArrayList<>(); // Outgoing edges - private final HashMap> reversedLinkStartPoints = new HashMap<>(); // Start points of reversed edges - private final HashMap> reversedLinkEndPoints = new HashMap<>(); // End points of reversed edges - // Layout properties + private int layer = -1; + private int optimal_x; private int x; private int y; private int width; @@ -62,34 +55,22 @@ public class LayoutNode { private int bottomMargin; private int rightMargin; private int leftMargin; - private int pos = -1; // Position within its layer - private boolean reverseLeft = false; - private int crossingNumber = 0; + private final Vertex vertex; // Only used for non-dummy nodes, otherwise null + + private final List preds = new ArrayList<>(); + private final List succs = new ArrayList<>(); + private final HashMap> reversedLinkStartPoints = new HashMap<>(); + private final HashMap> reversedLinkEndPoints = new HashMap<>(); + private int pos = -1; // Position within layer + + private float weightedPosition = 0; - /** - * Constructs a LayoutNode associated with the given Vertex. - * Initializes the node's size based on the vertex's dimensions. - * - * @param v The Vertex associated with this LayoutNode. If null, the node is a dummy node. - */ public LayoutNode(Vertex v) { vertex = v; initSize(); } - /** - * Constructs a dummy LayoutNode - */ - public LayoutNode() { - this(null); - } - - /** - * Initializes the size and margins of the node. - * If the node represents a real vertex, it uses the vertex's size. - * Dummy nodes use default dimensions. - */ public void initSize() { if (vertex == null) { height = DUMMY_HEIGHT; @@ -99,216 +80,124 @@ public void initSize() { height = size.height; width = size.width; } - topMargin = 0; - bottomMargin = 0; - leftMargin = 0; - rightMargin = 0; + setTopMargin(0); + setBottomMargin(0); + setLeftMargin(0); + setRightMargin(0); } - public int getCrossingNumber() { - return crossingNumber; - } - - public void setCrossingNumber(int crossingNumber) { - this.crossingNumber = crossingNumber; - } - - public int calculateOptimalXFromPredecessors(boolean useMedian) { + public int calculateOptimalPositionDown() { int numPreds = preds.size(); - - // If there are no predecessors, retain the current x position if (numPreds == 0) { return getX(); } - // Collect the x positions from all predecessor edges List positions = new ArrayList<>(numPreds); for (LayoutEdge edge : preds) { positions.add(edge.getStartX() - edge.getRelativeToX()); } - if (useMedian) { - // Calculate the median position - Collections.sort(positions); - int midIndex = numPreds / 2; - - if (numPreds % 2 == 0) { - // Even number of predecessors: average the two middle values - return (positions.get(midIndex - 1) + positions.get(midIndex)) / 2; - } else { - // Odd number of predecessors: take the middle value - return positions.get(midIndex); - } - } else { - // Calculate the average position - long sum = 0; - for (int pos : positions) { - sum += pos; - } - // Integer division is used; adjust as needed for rounding - return (int) (sum / numPreds); - } + Collections.sort(positions); + int midIndex = numPreds / 2; + return (numPreds % 2 == 0) + ? (positions.get(midIndex - 1) + positions.get(midIndex)) / 2 + : positions.get(midIndex); } - - public int calculateOptimalXFromSuccessors(boolean useMedian) { + public int calculateOptimalPositionUp() { int numSuccs = succs.size(); - - // If there are no successors, retain the current x position if (numSuccs == 0) { return getX(); } - // Collect the x positions from all successor edges List positions = new ArrayList<>(numSuccs); for (LayoutEdge edge : succs) { positions.add(edge.getEndX() - edge.getRelativeFromX()); } - if (useMedian) { - // Calculate the median position - Collections.sort(positions); - int midIndex = numSuccs / 2; - - if (numSuccs % 2 == 0) { - // Even number of successors: average the two middle values - return (positions.get(midIndex - 1) + positions.get(midIndex)) / 2; - } else { - // Odd number of successors: take the middle value - return positions.get(midIndex); - } - } else { - // Calculate the average position - long sum = 0; - for (int pos : positions) { - sum += pos; - } - // Integer division is used; adjust as needed for rounding - return (int) (sum / numSuccs); - } + Collections.sort(positions); + int midIndex = numSuccs / 2; + return (numSuccs % 2 == 0) + ? (positions.get(midIndex - 1) + positions.get(midIndex)) / 2 + : positions.get(midIndex); + } + + public LayoutNode() { + this(null); } - /** - * Calculates the node's out-degree (number of outgoing edges). - * - * @return The out-degree of the node. - */ public int getOutDegree() { return succs.size(); } - /** - * Calculates the node's in-degree (number of incoming edges). - * - * @return The in-degree of the node. - */ public int getInDegree() { return preds.size(); } - /** - * Calculates the total degree of the node (sum of in-degree and out-degree). - * - * @return The total degree of the node. - */ public int getDegree() { return preds.size() + succs.size(); } - /** - * Gets the left boundary (excluding left margin) of the node. - * - * @return The x-coordinate of the left boundary. - */ + public float averagePosition() { + float totalWeightedPosition = 0; + float totalWeight = 0; + + for (LayoutEdge predEdge : preds) { + LayoutNode predNode = predEdge.getFrom(); + int weight = predNode.getDegree(); + totalWeightedPosition += weight * predEdge.getStartX(); + totalWeight += weight; + } + for (LayoutEdge succEdge : succs) { + LayoutNode succNode = succEdge.getTo(); + int weight = succNode.getDegree(); + totalWeightedPosition += weight * succEdge.getEndX(); + totalWeight += weight; + } + + // Calculate the (weighted) average position for the node based on neighbor positions and weights (degree) + return totalWeight > 0 ? totalWeightedPosition / totalWeight : 0; + } + public int getLeft() { return x + leftMargin; } - /** - * Gets the outer left boundary (including left margin) of the node. - * - * @return The x-coordinate of the outer left boundary. - */ public int getOuterLeft() { return x; } - /** - * Gets the total width of the node, including left and right margins. - * - * @return The total outer width. - */ public int getOuterWidth() { return leftMargin + width + rightMargin; } - /** - * Gets the total height of the node, including top and bottom margins. - * - * @return The total outer height. - */ public int getOuterHeight() { return topMargin + height + bottomMargin; } - public int getHeight() { - return height; - } - - /** - * Gets the right boundary (excluding right margin) of the node. - * - * @return The x-coordinate of the right boundary. - */ public int getRight() { return x + leftMargin + width; } - /** - * Gets the outer right boundary (including right margin) of the node. - * - * @return The x-coordinate of the outer right boundary. - */ public int getOuterRight() { return x + leftMargin + width + rightMargin; } - /** - * Gets the horizontal center point of the node. - * - * @return The x-coordinate of the center. - */ public int getCenterX() { return x + leftMargin + (width / 2); } - /** - * Gets the top boundary (excluding top margin) of the node. - * - * @return The y-coordinate of the top boundary. - */ public int getTop() { return y + topMargin; } - /** - * Gets the bottom boundary (excluding bottom margin) of the node. - * - * @return The y-coordinate of the bottom boundary. - */ public int getBottom() { return y + topMargin + height; } - /** - * Checks if the node is a dummy node. - * - * @return True if the node is a dummy node; false otherwise. - */ public boolean isDummy() { return vertex == null; } - @Override public String toString() { if (vertex != null) { @@ -318,6 +207,14 @@ public String toString() { } } + public int getOptimalX() { + return optimal_x; + } + + public void setOptimalX(int optimal_x) { + this.optimal_x = optimal_x; + } + public int getX() { return x; } @@ -338,6 +235,18 @@ public int getWidth() { return width; } + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + public int getLayer() { return layer; } @@ -346,78 +255,72 @@ public void setLayer(int layer) { this.layer = layer; } - /** - * Centers the node by setting equal top and bottom margins. - * The larger of the two margins is applied to both. - */ - public void centerNode() { - int offset = Math.max(topMargin, bottomMargin); - topMargin = offset; - bottomMargin = offset; + public int getLeftMargin() { + return leftMargin; } - public Vertex getVertex() { - return vertex; - } - - public void setVertex(Vertex vertex) { - this.vertex = vertex; + public void setLeftMargin(int leftMargin) { + this.leftMargin = leftMargin; } - public boolean hasPredecessors() { - return !preds.isEmpty(); + public int getTopMargin() { + return topMargin; } - public boolean hasSuccessors() { - return !succs.isEmpty(); + public void setTopMargin(int topMargin) { + this.topMargin = topMargin; } - public void clearSuccessors() { - succs.clear(); + public int getRightMargin() { + return rightMargin; } - public void clearPredecessors() { - preds.clear(); + public void setRightMargin(int rightMargin) { + this.rightMargin = rightMargin; } - public List getSuccessors() { - return Collections.unmodifiableList(succs); + public int getBottomMargin() { + return bottomMargin; } - public List getSuccessorsRaw() { - return succs; + public void setBottomMargin(int bottomMargin) { + this.bottomMargin = bottomMargin; } - public List getPredecessors() { - return Collections.unmodifiableList(preds); + public Vertex getVertex() { + return vertex; } - public List getPredecessorsRaw() { + public List getPreds() { return preds; } - public void addSuccessor(LayoutEdge successor) { - succs.add(successor); + public boolean hasPreds() { + return !preds.isEmpty(); } - public void removeSuccessor(LayoutEdge successor) { - succs.remove(successor); + public boolean hasSuccs() { + return !succs.isEmpty(); } - public void addPredecessor(LayoutEdge predecessor) { - preds.add(predecessor); + public List getSuccs() { + return succs; } - public void removePredecessor(LayoutEdge predecessor) { - preds.remove(predecessor); + public Map> groupSuccessorsByX() { + Map> result = new HashMap<>(); + for (LayoutEdge succEdge : succs) { + result.computeIfAbsent(succEdge.getRelativeFromX(), k -> new ArrayList<>()).add(succEdge); + } + return result; } - public Map> getReversedLinkStartPoints() { - return Collections.unmodifiableMap(reversedLinkStartPoints); + public HashMap> getReversedLinkStartPoints() { + return reversedLinkStartPoints; } - public Map> getReversedLinkEndPoints() { - return Collections.unmodifiableMap(reversedLinkEndPoints); + public HashMap> getReversedLinkEndPoints() { + return reversedLinkEndPoints; } public int getPos() { @@ -428,53 +331,17 @@ public void setPos(int pos) { this.pos = pos; } - /** - * Groups the successor edges by their relative x-coordinate from the current node. - * - * @return A map of relative x-coordinate to list of successor edges. - */ - public Map> groupSuccessorsByX() { - Map> result = new HashMap<>(); - for (LayoutEdge succEdge : succs) { - result.computeIfAbsent(succEdge.getRelativeFromX(), k -> new ArrayList<>()).add(succEdge); - } - return result; + public float getWeightedPosition() { + return weightedPosition; } - private int getBackedgeCrossingScore() { - int score = 0; - for (LayoutEdge predEdge : preds) { - if (predEdge.isReversed()) { - List points = reversedLinkEndPoints.get(predEdge.getLink()); - if (points != null) { - int x0 = points.get(points.size() - 1).x; - int xn = points.get(0).x; - int startPoint = predEdge.getStartX(); - int endPoint = predEdge.getEndX(); - int win = (x0 < xn) ? (startPoint - endPoint) : (endPoint - startPoint); - score += win; - } - } - } - for (LayoutEdge succEdge : succs) { - if (succEdge.isReversed()) { - List points = reversedLinkStartPoints.get(succEdge.getLink()); - if (points != null) { - int x0 = points.get(points.size() - 1).x; - int xn = points.get(0).x; - int startPoint = succEdge.getStartX(); - int endPoint = succEdge.getEndX(); - int win = (x0 > xn) ? (startPoint - endPoint) : (endPoint - startPoint); - score += win; - } - } - } - return score; + public void setWeightedPosition(float weightedPosition) { + this.weightedPosition = weightedPosition; } - private boolean computeReversedStartPoints(boolean left) { - TreeMap> sortedDownMap = left ? new TreeMap<>() : new TreeMap<>(Collections.reverseOrder()); - for (LayoutEdge succEdge : succs) { + private void computeReversedStartPoints() { + TreeMap> sortedDownMap = new TreeMap<>(Collections.reverseOrder()); + for (LayoutEdge succEdge : getSuccs()) { if (succEdge.isReversed()) { succEdge.setRelativeFromX(succEdge.getLink().getTo().getRelativePosition().x); sortedDownMap.putIfAbsent(succEdge.getRelativeFromX(), new ArrayList<>()); @@ -483,17 +350,16 @@ private boolean computeReversedStartPoints(boolean left) { } int offset = NODE_OFFSET + LayoutNode.DUMMY_WIDTH; - int offsetX = left ? -offset : offset; - int currentX = left ? 0 : width; + int currentX = getWidth(); int startY = 0; int currentY = 0; for (Map.Entry> entry : sortedDownMap.entrySet()) { int startX = entry.getKey(); ArrayList reversedSuccs = entry.getValue(); - currentX += offsetX; + currentX += offset; currentY -= offset; - topMargin += offset; + setTopMargin(getTopMargin() + offset); ArrayList startPoints = new ArrayList<>(); startPoints.add(new Point(currentX, currentY)); @@ -501,20 +367,16 @@ private boolean computeReversedStartPoints(boolean left) { startPoints.add(new Point(startX, startY)); for (LayoutEdge revEdge : reversedSuccs) { revEdge.setRelativeFromX(currentX); - reversedLinkStartPoints.put(revEdge.getLink(), startPoints); + getReversedLinkStartPoints().put(revEdge.getLink(), startPoints); } } - if (left) { - leftMargin += sortedDownMap.size() * offset; - } else { - rightMargin += sortedDownMap.size() * offset; - } - return !sortedDownMap.isEmpty(); + setLeftMargin(getLeftMargin()); + setRightMargin(getRightMargin() + (sortedDownMap.size() * offset)); } - private boolean computeReversedEndPoints(boolean left) { - TreeMap> sortedUpMap = left ? new TreeMap<>() : new TreeMap<>(Collections.reverseOrder()); - for (LayoutEdge predEdge : preds) { + private void computeReversedEndPoints() { + TreeMap> sortedUpMap = new TreeMap<>(Collections.reverseOrder()); + for (LayoutEdge predEdge : getPreds()) { if (predEdge.isReversed()) { predEdge.setRelativeToX(predEdge.getLink().getFrom().getRelativePosition().x); sortedUpMap.putIfAbsent(predEdge.getRelativeToX(), new ArrayList<>()); @@ -523,17 +385,16 @@ private boolean computeReversedEndPoints(boolean left) { } int offset = NODE_OFFSET + LayoutNode.DUMMY_WIDTH; - int offsetX = left ? -offset : offset; - int currentX = left ? 0 : getWidth(); - int startY = height; - int currentY = height; + int currentX = getWidth(); + int startY = getHeight(); + int currentY = getHeight(); for (Map.Entry> entry : sortedUpMap.entrySet()) { int startX = entry.getKey(); ArrayList reversedPreds = entry.getValue(); - currentX += offsetX; + currentX += offset; currentY += offset; - bottomMargin += offset; + setBottomMargin(getBottomMargin() + offset); ArrayList endPoints = new ArrayList<>(); endPoints.add(new Point(currentX, currentY)); @@ -541,39 +402,19 @@ private boolean computeReversedEndPoints(boolean left) { endPoints.add(new Point(startX, startY)); for (LayoutEdge revEdge : reversedPreds) { revEdge.setRelativeToX(currentX); - reversedLinkEndPoints.put(revEdge.getLink(), endPoints); + getReversedLinkEndPoints().put(revEdge.getLink(), endPoints); } } - if (left) { - leftMargin += sortedUpMap.size() * offset; - } else { - rightMargin += sortedUpMap.size() * offset; - } - - return !sortedUpMap.isEmpty(); + setLeftMargin(getLeftMargin()); + setRightMargin(getRightMargin() + (sortedUpMap.size() * offset)); } - public void computeReversedLinkPoints(boolean reverseLeft) { - this.reverseLeft = reverseLeft; + public void computeReversedLinkPoints() { initSize(); - reversedLinkStartPoints.clear(); - reversedLinkEndPoints.clear(); - - boolean hasReversedDown = computeReversedStartPoints(reverseLeft); - boolean hasReversedUP = computeReversedEndPoints(hasReversedDown != reverseLeft); - } + getReversedLinkStartPoints().clear(); + getReversedLinkEndPoints().clear(); - public boolean isReverseRight() { - return !reverseLeft; - } - - public void optimizeBackEdgeCrossing() { - if (reversedLinkStartPoints.isEmpty() && reversedLinkEndPoints.isEmpty()) return; - int orig_score = getBackedgeCrossingScore(); - computeReversedLinkPoints(isReverseRight()); - int reverse_score = getBackedgeCrossingScore(); - if (orig_score > reverse_score) { - computeReversedLinkPoints(isReverseRight()); - } + computeReversedStartPoints(); + computeReversedEndPoints(); } } diff --git a/src/utils/IdealGraphVisualizer/Settings/src/main/java/com/sun/hotspot/igv/settings/Settings.java b/src/utils/IdealGraphVisualizer/Settings/src/main/java/com/sun/hotspot/igv/settings/Settings.java index 480fb43ea5b8d..41af4a1d26282 100644 --- a/src/utils/IdealGraphVisualizer/Settings/src/main/java/com/sun/hotspot/igv/settings/Settings.java +++ b/src/utils/IdealGraphVisualizer/Settings/src/main/java/com/sun/hotspot/igv/settings/Settings.java @@ -37,7 +37,6 @@ public static class DefaultView { public static final int CLUSTERED_SEA_OF_NODES = 1; public static final int CONTROL_FLOW_GRAPH = 2; public static final int STABLE_SEA_OF_NODES = 3; - public static final int INTERACTIVE_FREE_NODES = 4; } public static final String NODE_TEXT = "nodeText"; diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java index 74f687689633b..1c2b9383b02b3 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java @@ -45,7 +45,6 @@ import javax.swing.*; import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS; import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS; -import javax.swing.border.Border; import javax.swing.event.UndoableEditEvent; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.CannotRedoException; @@ -81,17 +80,10 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl private final LayerWidget mainLayer; private final LayerWidget blockLayer; private final LayerWidget connectionLayer; - private final Widget shadowWidget; - private final Widget pointerWidget; private final DiagramViewModel model; private ModelState modelState; private boolean rebuilding; - - private final FreeInteractiveLayoutManager freeInteractiveLayoutManager; private final HierarchicalStableLayoutManager hierarchicalStableLayoutManager; - private HierarchicalLayoutManager seaLayoutManager; - private LayoutMover layoutMover; - /** * The alpha level of partially visible figures. @@ -101,7 +93,7 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl /** * The offset of the graph to the border of the window showing it. */ - public static final int BORDER_SIZE = 50; + public static final int BORDER_SIZE = 100; public static final int UNDOREDO_LIMIT = 100; public static final int SCROLL_UNIT_INCREMENT = 80; public static final int SCROLL_BLOCK_INCREMENT = 400; @@ -230,7 +222,7 @@ public void filteredChanged(SelectionCoordinator coordinator) { public void colorSelectedFigures(Color color) { for (Figure figure : model.getSelectedFigures()) { - figure.setCustomColor(color); + figure.setColor(color); FigureWidget figureWidget = getWidget(figure); if (figureWidget != null) { figureWidget.refreshColor(); @@ -331,26 +323,19 @@ public void select(Widget widget, Point localLocation, boolean invertSelection) getActions().addAction(selectAction); - Border emptyBorder = BorderFactory.createEmptyBorder(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE); - blockLayer = new LayerWidget(this); - blockLayer.setBorder(emptyBorder); addChild(blockLayer); connectionLayer = new LayerWidget(this); - connectionLayer.setBorder(emptyBorder); - addChild(connectionLayer); + LayerWidget paddedLayer = new LayerWidget(this); + paddedLayer.setBorder(BorderFactory.createEmptyBorder(50, 50, 50, 50)); // Adds 50px padding on all sides + paddedLayer.addChild(connectionLayer); + addChild(paddedLayer); mainLayer = new LayerWidget(this); - mainLayer.setBorder(emptyBorder); addChild(mainLayer); - pointerWidget = new Widget(DiagramScene.this); - addChild(pointerWidget); - - shadowWidget = new Widget(DiagramScene.this); - addChild(shadowWidget); - + setBorder(BorderFactory.createLineBorder(Color.white, BORDER_SIZE)); setLayout(LayoutFactory.createAbsoluteLayout()); getActions().addAction(mouseZoomAction); getActions().addAction(ActionFactory.createPopupMenuAction((widget, localLocation) -> createPopupMenu())); @@ -511,9 +496,7 @@ public void ancestorResized(HierarchyEvent e) { } }); - freeInteractiveLayoutManager = new FreeInteractiveLayoutManager(); hierarchicalStableLayoutManager = new HierarchicalStableLayoutManager(); - seaLayoutManager = new HierarchicalLayoutManager(); this.model = model; modelState = new ModelState(model); @@ -607,166 +590,6 @@ private void updateFigureWidths() { } } - private MoveProvider getFigureMoveProvider() { - return new MoveProvider() { - - private boolean hasMoved = false; // Flag to track movement - private int startLayerY; - - private void setFigureShadow(Figure f) { - FigureWidget fw = getWidget(f); - Color c = f.getColor(); - Border border = new FigureWidget.RoundedBorder(new Color(0,0,0, 50), 1); - shadowWidget.setBorder(border); - shadowWidget.setBackground(new Color(c.getRed(), c.getGreen(), c.getBlue(), 50)); - shadowWidget.setPreferredLocation(fw.getPreferredLocation()); - shadowWidget.setPreferredSize(f.getSize()); - shadowWidget.setVisible(true); - shadowWidget.setOpaque(true); - shadowWidget.revalidate(); - shadowWidget.repaint(); - } - - private void setMovePointer(Figure f) { - Border border = new FigureWidget.RoundedBorder(Color.RED, 1); - pointerWidget.setBorder(border); - pointerWidget.setBackground(Color.RED); - pointerWidget.setPreferredBounds(new Rectangle(0, 0, 3, f.getSize().height)); - pointerWidget.setVisible(false); - pointerWidget.setOpaque(true); - } - - - @Override - public void movementStarted(Widget widget) { - if (layoutMover == null) return; // Do nothing if layoutMover is not available - - widget.bringToFront(); - startLayerY = widget.getLocation().y; - hasMoved = false; // Reset the movement flag - if (layoutMover.isFreeForm()) return; - Set
selectedFigures = model.getSelectedFigures(); - if (selectedFigures.size() == 1) { - Figure selectedFigure = selectedFigures.iterator().next(); - setFigureShadow(selectedFigure); - setMovePointer(selectedFigure); - } - } - - @Override - public void movementFinished(Widget widget) { - shadowWidget.setVisible(false); - pointerWidget.setVisible(false); - if (layoutMover == null || !hasMoved) return; // Do nothing if layoutMover is not available or no movement occurred - rebuilding = true; - - Set
movedFigures = new HashSet<>(model.getSelectedFigures()); - for (Figure figure : movedFigures) { - FigureWidget fw = getWidget(figure); - figure.setPosition(new Point(fw.getLocation().x, fw.getLocation().y)); - } - - layoutMover.moveVertices(movedFigures); - rebuildConnectionLayer(); - - for (FigureWidget fw : getVisibleFigureWidgets()) { - fw.updatePosition(); - } - - validateAll(); - addUndo(); - rebuilding = false; - } - - private static final int MAGNET_SIZE = 5; - - private int magnetToStartLayerY(Widget widget, Point location) { - int shiftY = location.y - widget.getLocation().y; - if (Math.abs(location.y - startLayerY) <= MAGNET_SIZE) { - if (Math.abs(widget.getLocation().y - startLayerY) > MAGNET_SIZE) { - shiftY = startLayerY - widget.getLocation().y; - } else { - shiftY = 0; - } - } - return shiftY; - } - - @Override - public Point getOriginalLocation(Widget widget) { - if (layoutMover == null) return widget.getLocation(); // default behavior - return ActionFactory.createDefaultMoveProvider().getOriginalLocation(widget); - } - - @Override - public void setNewLocation(Widget widget, Point location) { - if (layoutMover == null) return; // Do nothing if layoutMover is not available - hasMoved = true; // Mark that a movement occurred - - int shiftX = location.x - widget.getLocation().x; - int shiftY; - if (layoutMover.isFreeForm()) { - shiftY = location.y - widget.getLocation().y; - } else { - shiftY = magnetToStartLayerY(widget, location); - } - List
selectedFigures = new ArrayList<>( model.getSelectedFigures()); - selectedFigures.sort(Comparator.comparingInt(f -> f.getPosition().x)); - for (Figure figure : selectedFigures) { - FigureWidget fw = getWidget(figure); - for (InputSlot inputSlot : figure.getInputSlots()) { - if (inputSlotToLineWidget.containsKey(inputSlot)) { - for (LineWidget lw : inputSlotToLineWidget.get(inputSlot)) { - Point toPt = lw.getTo(); - int xTo = toPt.x + shiftX; - int yTo = toPt.y + shiftY; - lw.setTo(new Point(xTo, yTo)); - if (!layoutMover.isFreeForm()) { - Point fromPt = lw.getFrom(); - lw.setFrom(new Point(fromPt.x + shiftX, fromPt.y)); - LineWidget pred = lw.getPredecessor(); - pred.setTo(new Point(pred.getTo().x + shiftX, pred.getTo().y)); - pred.revalidate(); - } - lw.revalidate(); - } - } - } - for (OutputSlot outputSlot : figure.getOutputSlots()) { - if (outputSlotToLineWidget.containsKey(outputSlot)) { - for (LineWidget lw : outputSlotToLineWidget.get(outputSlot)) { - Point fromPt = lw.getFrom(); - int xFrom = fromPt.x + shiftX; - int yFrom = fromPt.y + shiftY; - lw.setFrom(new Point(xFrom, yFrom)); - if (!layoutMover.isFreeForm()) { - Point toPt = lw.getTo(); - lw.setTo(new Point(toPt.x + shiftX, toPt.y)); - for (LineWidget succ : lw.getSuccessors()) { - succ.setFrom(new Point(succ.getFrom().x + shiftX, succ.getFrom().y)); - succ.revalidate(); - } - } - lw.revalidate(); - } - } - } - Point newLocation = new Point(fw.getLocation().x + shiftX, fw.getLocation().y + shiftY); - ActionFactory.createDefaultMoveProvider().setNewLocation(fw, newLocation); - } - - if (selectedFigures.size() == 1 && !layoutMover.isFreeForm()) { - FigureWidget fw = getWidget(selectedFigures.iterator().next()); - pointerWidget.setVisible(true); - Point newLocation = new Point(fw.getLocation().x + shiftX -3, fw.getLocation().y + shiftY); - ActionFactory.createDefaultMoveProvider().setNewLocation(pointerWidget, newLocation); - } - connectionLayer.revalidate(); - connectionLayer.repaint(); - } - }; - } - private void rebuildMainLayer() { mainLayer.removeChildren(); for (Figure figure : getModel().getDiagram().getFigures()) { @@ -775,7 +598,6 @@ private void rebuildMainLayer() { figureWidget.getActions().addAction(ActionFactory.createPopupMenuAction(figureWidget)); figureWidget.getActions().addAction(selectAction); figureWidget.getActions().addAction(hoverAction); - figureWidget.getActions().addAction(ActionFactory.createMoveAction(null, getFigureMoveProvider())); addObject(figure, figureWidget); mainLayer.addChild(figureWidget); @@ -838,15 +660,12 @@ private void relayout() { Set
visibleFigures = getVisibleFigures(); Set visibleConnections = getVisibleConnections(); - if (getModel().getShowFreeInteractive()) { - doFreeInteractiveLayout(visibleFigures, visibleConnections); - } - else if (getModel().getShowStableSea()) { + if (getModel().getShowStableSea()) { doStableSeaLayout(visibleFigures, visibleConnections); } else if (getModel().getShowSea()) { doSeaLayout(visibleFigures, visibleConnections); } else if (getModel().getShowBlocks()) { - doClusteredLayout(visibleFigures, visibleConnections); + doClusteredLayout(visibleConnections); } else if (getModel().getShowCFG()) { doCFGLayout(visibleFigures, visibleConnections); } @@ -911,40 +730,24 @@ private boolean isVisibleFigureConnection(FigureConnection figureConnection) { return w1.isVisible() && w2.isVisible(); } - private void doFreeInteractiveLayout(Set
visibleFigures, Set visibleConnections) { - layoutMover = freeInteractiveLayoutManager; - freeInteractiveLayoutManager.setCutEdges(model.getCutEdges()); - freeInteractiveLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); - } - private void doStableSeaLayout(Set
visibleFigures, Set visibleConnections) { - layoutMover = null; - boolean enable = model.getCutEdges(); - boolean previous = hierarchicalStableLayoutManager.getCutEdges(); - hierarchicalStableLayoutManager.setCutEdges(enable); - if (enable != previous) { - hierarchicalStableLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); - } else { - hierarchicalStableLayoutManager.updateLayout(visibleFigures, visibleConnections); - } + hierarchicalStableLayoutManager.setCutEdges(model.getCutEdges()); + hierarchicalStableLayoutManager.updateLayout(visibleFigures, visibleConnections); } private void doSeaLayout(Set
visibleFigures, Set visibleConnections) { - seaLayoutManager = new HierarchicalLayoutManager(); - layoutMover = seaLayoutManager; + HierarchicalLayoutManager seaLayoutManager = new HierarchicalLayoutManager(); seaLayoutManager.setCutEdges(model.getCutEdges()); seaLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); } - private void doClusteredLayout(Set
visibleFigures, Set visibleConnections) { - layoutMover = null; + private void doClusteredLayout(Set visibleConnections) { HierarchicalClusterLayoutManager clusterLayoutManager = new HierarchicalClusterLayoutManager(); clusterLayoutManager.setCutEdges(model.getCutEdges()); - clusterLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); + clusterLayoutManager.doLayout(new LayoutGraph(visibleConnections, new HashSet<>())); } private void doCFGLayout(Set
visibleFigures, Set visibleConnections) { - layoutMover = null; HierarchicalCFGLayoutManager cfgLayoutManager = new HierarchicalCFGLayoutManager(getVisibleBlockConnections(), getVisibleBlocks()); cfgLayoutManager.setCutEdges(model.getCutEdges()); cfgLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); @@ -963,83 +766,6 @@ private boolean shouldAnimate() { private final Point specialNullPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE); - private MoveProvider getFigureConnectionMoveProvider() { - return new MoveProvider() { - - Point startLocation; - Point originalPosition; - - @Override - public void movementStarted(Widget widget) { - if (layoutMover == null) return; // Do nothing if layoutMover is not available - LineWidget lw = (LineWidget) widget; - startLocation = lw.getClientAreaLocation(); - originalPosition = lw.getFrom(); - } - - @Override - public void movementFinished(Widget widget) { - if (layoutMover == null) return; // Do nothing if layoutMover is not available - LineWidget lineWidget = (LineWidget) widget; - if (lineWidget.getPredecessor() == null) return; - if (lineWidget.getSuccessors().isEmpty()) return; - if (lineWidget.getFrom().x != lineWidget.getTo().x) return; - - int shiftX = lineWidget.getClientAreaLocation().x - startLocation.x; - if (shiftX == 0) return; - - rebuilding = true; - layoutMover.moveLink(originalPosition, shiftX); - rebuildConnectionLayer(); - for (FigureWidget fw : getVisibleFigureWidgets()) { - fw.updatePosition(); - } - validateAll(); - addUndo(); - rebuilding = false; - } - - @Override - public Point getOriginalLocation(Widget widget) { - if (layoutMover == null) return widget.getLocation(); // default behavior - LineWidget lineWidget = (LineWidget) widget; - return lineWidget.getClientAreaLocation(); - } - - @Override - public void setNewLocation(Widget widget, Point location) { - if (layoutMover == null) return; // Do nothing if layoutMover is not available - LineWidget lineWidget = (LineWidget) widget; - if (lineWidget.getPredecessor() == null) return; - if (lineWidget.getSuccessors().isEmpty()) return; - if (lineWidget.getFrom().x != lineWidget.getTo().x) return; - - int shiftX = location.x - lineWidget.getClientAreaLocation().x; - if (shiftX == 0) return; - - Point oldFrom = lineWidget.getFrom(); - Point newFrom = new Point(oldFrom.x + shiftX, oldFrom.y); - - Point oldTo = lineWidget.getTo(); - Point newTo = new Point(oldTo.x + shiftX, oldTo.y); - - lineWidget.setTo(newTo); - lineWidget.setFrom(newFrom); - lineWidget.revalidate(); - - LineWidget predecessor = lineWidget.getPredecessor(); - Point toPt = predecessor.getTo(); - predecessor.setTo(new Point(toPt.x + shiftX, toPt.y)); - predecessor.revalidate(); - - for (LineWidget successor : lineWidget.getSuccessors()) { - Point fromPt = successor.getFrom(); - successor.setFrom(new Point(fromPt.x + shiftX, fromPt.y)); - successor.revalidate(); - } - } - }; - } private void processOutputSlot(OutputSlot outputSlot, List connections, int controlPointIndex, Point lastPoint, LineWidget predecessor) { Map> pointMap = new HashMap<>(connections.size()); @@ -1098,7 +824,6 @@ private void processOutputSlot(OutputSlot outputSlot, List con connectionLayer.addChild(newPredecessor); addObject(new ConnectionSet(connectionList), newPredecessor); newPredecessor.getActions().addAction(hoverAction); - newPredecessor.getActions().addAction(ActionFactory.createMoveAction(null, getFigureConnectionMoveProvider())); } processOutputSlot(outputSlot, connectionList, controlPointIndex + 1, currentPoint, newPredecessor); @@ -1335,11 +1060,7 @@ private void rebuildConnectionLayer() { for (Figure figure : getModel().getDiagram().getFigures()) { for (OutputSlot outputSlot : figure.getOutputSlots()) { List connectionList = new ArrayList<>(outputSlot.getConnections()); - if (layoutMover != null && layoutMover.isFreeForm()) { - processFreeForm(outputSlot, connectionList); - } else { - processOutputSlot(outputSlot, connectionList, 0, null, null); - } + processOutputSlot(outputSlot, connectionList, 0, null, null); } } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewModel.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewModel.java index ffee8064e50cc..3081825025fe2 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewModel.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewModel.java @@ -63,7 +63,6 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene private final ChangedEvent selectedNodesChangedEvent = new ChangedEvent<>(this); private final ChangedEvent hiddenNodesChangedEvent = new ChangedEvent<>(this); private ChangedListener titleChangedListener = g -> {}; - private boolean showFreeInteractive; private boolean showStableSea; private boolean showSea; private boolean showBlocks; @@ -105,17 +104,6 @@ public void setGlobalSelection(boolean enable, boolean fire) { } } - public boolean getShowFreeInteractive() { - return showFreeInteractive; - } - - public void setShowFreeInteractive(boolean enable) { - showFreeInteractive = enable; - if (enable) { - diagramChangedEvent.fire(); - } - } - public boolean getShowStableSea() { return showStableSea; } @@ -236,7 +224,6 @@ public DiagramViewModel(InputGraph graph) { globalSelection = GlobalSelectionAction.get(GlobalSelectionAction.class).isSelected(); cutEdges = CutEdgesAction.get(CutEdgesAction.class).isSelected(); - showFreeInteractive = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.INTERACTIVE_FREE_NODES; showStableSea = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.STABLE_SEA_OF_NODES; showSea = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.SEA_OF_NODES; showBlocks = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.CLUSTERED_SEA_OF_NODES; diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java index ff51b8928f676..56c83517d2f39 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java @@ -140,7 +140,6 @@ public EditorTopComponent(DiagramViewModel diagramViewModel) { cardLayout = new CardLayout(); centerPanel = new JPanel(); centerPanel.setLayout(cardLayout); - centerPanel.setOpaque(true); centerPanel.setBackground(Color.WHITE); satelliteComponent = scene.createSatelliteView(); satelliteComponent.setSize(200, 200); @@ -176,11 +175,6 @@ public void mouseMoved(MouseEvent e) {} toolBar.addSeparator(); ButtonGroup layoutButtons = new ButtonGroup(); - JToggleButton freeInteractiveLayoutButton = new JToggleButton(new EnableFreeLayoutAction(this)); - freeInteractiveLayoutButton.setSelected(diagramViewModel.getShowFreeInteractive()); - layoutButtons.add(freeInteractiveLayoutButton); - toolBar.add(freeInteractiveLayoutButton); - JToggleButton stableSeaLayoutButton = new JToggleButton(new EnableStableSeaLayoutAction(this)); stableSeaLayoutButton.setSelected(diagramViewModel.getShowStableSea()); layoutButtons.add(stableSeaLayoutButton); diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ColorAction.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ColorAction.java index b0d27d8be04d7..7a38587eb3855 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ColorAction.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ColorAction.java @@ -82,7 +82,6 @@ public String getName() { private static final JLabel selectedColorLabel = new JLabel("Preview"); private static final JColorChooser colorChooser = new JColorChooser(Color.WHITE); - private static final Color NO_COLOR = new Color(0, 0, 0, 0); public ColorAction() { initializeComponents(); @@ -90,8 +89,8 @@ public ColorAction() { private void initializeComponents() { selectedColorLabel.setPreferredSize(new Dimension(3 * 32, 32)); - selectedColorLabel.setOpaque(false); // Allow transparency - selectedColorLabel.setBackground(NO_COLOR); // Set transparent background + selectedColorLabel.setOpaque(true); + selectedColorLabel.setBackground(Color.WHITE); selectedColorLabel.setForeground(Color.BLACK); // Set text color selectedColorLabel.setHorizontalAlignment(SwingConstants.CENTER); // Center the text @@ -101,7 +100,6 @@ private void initializeComponents() { Color selectedColor = colorChooser.getColor(); if (selectedColor != null) { selectedColorLabel.setBackground(selectedColor); - selectedColorLabel.setOpaque(selectedColor.getAlpha() != 0); selectedColorLabel.setForeground(FigureWidget.getTextColor(selectedColor)); } }); @@ -120,26 +118,10 @@ private void initializeComponents() { colorButton.setPreferredSize(new Dimension(16, 16)); colorButton.addActionListener(e -> { selectedColorLabel.setBackground(color); - selectedColorLabel.setOpaque(color.getAlpha() != 0); selectedColorLabel.setForeground(FigureWidget.getTextColor(color)); }); colorsPanel.add(colorButton); } - - // Add "No Color" button - JButton noColorButton = new JButton("No Color"); - noColorButton.setOpaque(false); - noColorButton.setContentAreaFilled(false); - noColorButton.setBorderPainted(true); - noColorButton.setPreferredSize(new Dimension(60, 16)); - noColorButton.addActionListener(e -> { - selectedColorLabel.setBackground(NO_COLOR); - selectedColorLabel.setOpaque(false); - selectedColorLabel.setForeground(Color.BLACK); - }); - colorsPanel.add(noColorButton); - - // Add the preview label colorsPanel.add(selectedColorLabel, 0); colorsPanel.revalidate(); colorsPanel.repaint(); @@ -166,10 +148,9 @@ public void performAction(DiagramViewModel model) { dialogLoc = dialogHolder[0].getLocation(); // OK button action Color selectedColor = selectedColorLabel.getBackground(); - if (selectedColor.equals(NO_COLOR)) { - selectedColor = null; + if (selectedColor != null) { + editor.colorSelectedFigures(selectedColor); } - editor.colorSelectedFigures(selectedColor); }, null // Cancel button action ); diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java deleted file mode 100644 index 201fc51e15e9e..0000000000000 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 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 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ -package com.sun.hotspot.igv.view.actions; - -import com.sun.hotspot.igv.view.EditorTopComponent; -import java.beans.PropertyChangeEvent; - -public class EnableFreeLayoutAction extends EnableLayoutAction { - - public EnableFreeLayoutAction(EditorTopComponent etc) { - super(etc); - } - - @Override - protected String iconResource() { - return "com/sun/hotspot/igv/view/images/dynamic.png"; - } - - @Override - protected String getDescription() { - return "Show dynamic free layout"; - } - - @Override - public void propertyChange(PropertyChangeEvent evt) { - editor.getModel().setShowFreeInteractive(this.isSelected()); - } -} diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java index 91cc55ce74fc9..c44e598f53632 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java @@ -268,7 +268,6 @@ public Figure getFigure() { @Override protected void paintChildren() { - refreshColor(); Composite oldComposite = null; if (boundary) { oldComposite = getScene().getGraphics().getComposite(); diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java index 4ad93684ed660..ff7e955137aa8 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java @@ -32,8 +32,10 @@ import java.awt.*; import java.awt.geom.CubicCurve2D; import java.awt.geom.Line2D; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.swing.JPopupMenu; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; @@ -67,8 +69,6 @@ public class LineWidget extends Widget implements PopupMenuProvider { private boolean popupVisible; private final boolean isBold; private final boolean isDashed; - private int fromControlYOffset; - private int toControlYOffset; public LineWidget(DiagramScene scene, OutputSlot s, List connections, Point from, Point to, LineWidget predecessor, boolean isBold, boolean isDashed) { super(scene); @@ -118,13 +118,13 @@ private void computeClientArea() { maxY = Math.max(maxY, to.y + toControlYOffset); } else { // reversed edges if (from.x - to.x > 0) { - minX = Math.min(minX, from.x); - maxX = Math.max(maxX, to.x); + minX = Math.min(minX, from.x - 150); + maxX = Math.max(maxX, to.x + 150); minY = Math.min(minY, from.y + fromControlYOffset); maxY = Math.max(maxY, to.y + toControlYOffset); } else { - minX = Math.min(minX, from.x); - maxX = Math.max(maxX, to.x); + minX = Math.min(minX, from.x + 150); + maxX = Math.max(maxX, to.x - 150); minY = Math.min(minY, from.y + fromControlYOffset); maxY = Math.max(maxY, to.y + toControlYOffset); } @@ -169,6 +169,9 @@ public void setTo(Point to) { computeClientArea(); } + private int fromControlYOffset; + private int toControlYOffset; + public void setFromControlYOffset(int fromControlYOffset) { this.fromControlYOffset = fromControlYOffset; computeClientArea(); @@ -187,14 +190,6 @@ public Point getTo() { return to; } - public LineWidget getPredecessor() { - return predecessor; - } - - public List getSuccessors() { - return Collections.unmodifiableList(successors); - } - private void addSuccessor(LineWidget widget) { this.successors.add(widget); } @@ -326,6 +321,14 @@ protected void notifyStateChanged(ObjectState previousState, ObjectState state) } } + public LineWidget getPredecessor() { + return predecessor; + } + + public List getSuccessors() { + return successors; + } + private void highlightPredecessors(boolean enable) { LineWidget predecessorLineWidget = predecessor; while (predecessorLineWidget != null) { diff --git a/src/utils/IdealGraphVisualizer/View/src/main/resources/com/sun/hotspot/igv/view/images/dynamic.png b/src/utils/IdealGraphVisualizer/View/src/main/resources/com/sun/hotspot/igv/view/images/dynamic.png deleted file mode 100644 index 781b98176f8657d705976bf1ceb15e78a07f8098..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2218 zcmZ{l4Ny}@5P&a${A2)8X;r8a!>EXmmmfh0Nh&4^!GZx|8`Nq;^1xR@UM6{w0OBu- z$oPj+9I^N}PKAzCDB@s6l&X*sDdJci1Vqpxh1LQirJ&M#Awob%a=Fd!e!I7KZ!ejW zh!7{L8x;WHBn}lt!P$iz_O|fbqirkUA0>V&3oULTR+{(%|#X@Z`wq^P+wes$<|dgyVCY?L}G@s zbJ5&#HaohYdk^hJ_wHF)=?8l<#pnBv zq~wQg?X4~CEkxYrt!x{-K6m!#1q~gC)y-XN+hd0mLG`SF-si6p?o}C^2i<#^M{74o zQ2WZ#TN6nBgm3WBcZl?ejfyzr7p#uJs|`X{lmSy8buD()JhW zZ<=@@jsid(P zEGB~$005iGW^q~Y!=yoh!{qw00I<&92kam-7mlVgR-+kQ&RCDh&<28|g<>(BKhEk9 zw(SHoIA}s+bhCycZjyuI;gi}8fX!)4DkEg!UvL$8B15jkV^M}7QA5T9JOdYsi6|kb z84^`$9oN98n>@HsCe;X?W^y4C_;eX8M2Krqnm>cdVA2It8jZ%&D&x3OqSa;`{^HZO z5QK({AW2C{j3f>N*Ty5Pz`#I+$wt_0U+CehOI8zdgRffWITGYU91*HhXfX|e;c6Ng zS00P&2|k@pCK`Q>tP?e0W0}-Cb6K!JgtQ$ zsTfVA(Q8$vjZi8ORD~v@YC;D&7BX5tERRR7oGZjtxHeiNSD*qG&s2=1YHTM)yd{_c zRmnscjHrXVCSduSnTdu;!I-H)3&TWkoetFs@kFu&!AFmwIk}~e1rIl$YeI0X6vtHp z%g*2-6Ri`#a9oMSC5vFQ3CJGuWx)?q`k{wLa0?#`nmi)T;#S*FGIr*CwvVGp-;{a5 z#}SQNA&o7ZQy@E$wR!hLuaar9lGy+Hy5p-4`Y4T^E}lOg%n7yu>l$rqBIWMqtV3BX zO<%4c08#e~b-z_>>(36`57$OhKP~Iubwzimu#oEAQ`B|wbt?T(u7>#;m%UJEa?g9%|qB#<=!(gq)#xOIk9+ Date: Wed, 27 Nov 2024 00:54:05 +0100 Subject: [PATCH 04/30] 8314512: IGV: clean up hierarchical layout code --- .../HierarchicalCFGLayoutManager.java | 26 +- .../HierarchicalClusterLayoutManager.java | 48 +- .../HierarchicalLayoutManager.java | 513 +++++----- .../HierarchicalStableLayoutManager.java | 885 ++++++++---------- .../igv/hierarchicallayout/LayoutEdge.java | 134 ++- .../igv/hierarchicallayout/LayoutGraph.java | 707 ++++++-------- .../igv/hierarchicallayout/LayoutLayer.java | 186 ++-- .../igv/hierarchicallayout/LayoutManager.java | 10 +- .../igv/hierarchicallayout/LayoutNode.java | 389 +++++--- .../sun/hotspot/igv/view/DiagramScene.java | 85 +- .../hotspot/igv/view/EditorTopComponent.java | 1 + 11 files changed, 1379 insertions(+), 1605 deletions(-) diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java index baa4bbb458534..9d9af88d3c0d8 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalCFGLayoutManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,9 @@ */ package com.sun.hotspot.igv.hierarchicallayout; -import com.sun.hotspot.igv.layout.*; +import com.sun.hotspot.igv.layout.Cluster; +import com.sun.hotspot.igv.layout.Link; +import com.sun.hotspot.igv.layout.Vertex; import java.awt.*; import java.util.*; @@ -33,6 +35,8 @@ public class HierarchicalCFGLayoutManager extends LayoutManager { private final HierarchicalLayoutManager manager; private final Set clusters; private final Set clusterLinks; + Map clusterNodesMap; + Map clusterEdgesMap; public HierarchicalCFGLayoutManager(Set clusterLinks, Set clusters) { this.clusterLinks = clusterLinks; @@ -41,20 +45,7 @@ public HierarchicalCFGLayoutManager(Set clusterLinks, Set clusterNodesMap; - Map clusterEdgesMap; - private static void doLinearLayout(ClusterNode clusterNode) { Cluster cluster = clusterNode.getCluster(); LayoutGraph graph = new LayoutGraph(clusterNode.getSubEdges(), clusterNode.getSubNodes()); diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java index 9dba4d03c8bd5..ac3d1846d3c4f 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalClusterLayoutManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,38 +34,27 @@ */ public class HierarchicalClusterLayoutManager extends LayoutManager { - private final LayoutManager subManager; private final LayoutManager manager; + private final HashMap clusterNodes; + public HierarchicalClusterLayoutManager() { - this.manager = new HierarchicalLayoutManager(); - this.subManager = new HierarchicalLayoutManager(); + this.manager = new HierarchicalLayoutManager(); + this.clusterNodes = new HashMap<>(); } @Override public void setCutEdges(boolean enable) { - subManager.setCutEdges(enable); manager.setCutEdges(enable); } - public void doLayout(LayoutGraph graph, Set importantLinks) { - doLayout(graph); - } - - public void setSubManager(LayoutManager manager) { - this.subManager = manager; - } - - public void setManager(LayoutManager manager) { - this.manager = manager; - } public void doLayout(LayoutGraph graph) { + clusterNodes.clear(); HashMap> listsConnection = new HashMap<>(); HashMap> clusterInputSlotHash = new HashMap<>(); HashMap> clusterOutputSlotHash = new HashMap<>(); - HashMap clusterNodes = new HashMap<>(); HashMap> clusterInputSlotSet = new HashMap<>(); HashMap> clusterOutputSlotSet = new HashMap<>(); Set clusterEdges = new HashSet<>(); @@ -102,21 +91,6 @@ public void doLayout(LayoutGraph graph) { z++; } - // Add cluster edges - for (Cluster c : clusters) { - - ClusterNode start = clusterNodes.get(c); - - for (Cluster succ : c.getSuccessors()) { - ClusterNode end = clusterNodes.get(succ); - if (end != null && start != end) { - ClusterEdge e = new ClusterEdge(start, end); - clusterEdges.add(e); - interClusterEdges.add(e); - } - } - } - for (Vertex v : graph.getVertices()) { Cluster c = v.getCluster(); assert c != null : "Cluster of vertex " + v + " is null!"; @@ -143,7 +117,7 @@ public void doLayout(LayoutGraph graph) { ClusterOutputSlotNode outputSlotNode; outputSlotNode = clusterOutputSlotHash.get(fromCluster).get(fromPort); - inputSlotNode = clusterInputSlotHash.get(toCluster).get(fromPort); + inputSlotNode = clusterInputSlotHash.get(toCluster).get(toPort); if (outputSlotNode == null) { outputSlotNode = new ClusterOutputSlotNode(clusterNodes.get(fromCluster), "Out " + fromCluster.toString() + " " + fromPort); @@ -159,7 +133,10 @@ public void doLayout(LayoutGraph graph) { } if (inputSlotNode == null) { - inputSlotNode = new ClusterInputSlotNode(clusterNodes.get(toCluster), "In " + toCluster.toString() + " " + fromPort); + inputSlotNode = new ClusterInputSlotNode( + clusterNodes.get(toCluster), + "In " + toCluster.toString() + " " + toPort // Use toPort here + ); clusterInputSlotSet.get(toCluster).add(inputSlotNode); } @@ -178,11 +155,12 @@ public void doLayout(LayoutGraph graph) { for (Cluster c : clusters) { ClusterNode n = clusterNodes.get(c); + HierarchicalLayoutManager subManager = new HierarchicalLayoutManager(); subManager.doLayout(new LayoutGraph(n.getSubEdges(), n.getSubNodes())); n.updateSize(); } - Set roots = new LayoutGraph(interClusterEdges).findRootVertices(); + Set roots = new LayoutGraph(interClusterEdges, new HashSet<>()).findRootVertices(); for (Vertex v : roots) { assert v instanceof ClusterNode; ((ClusterNode) v).setRoot(true); diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java index 69a1251fd0e73..3aa4c2ef7e1d5 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,16 +23,17 @@ */ package com.sun.hotspot.igv.hierarchicallayout; -import static com.sun.hotspot.igv.hierarchicallayout.LayoutEdge.LAYOUT_EDGE_LAYER_COMPARATOR; import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.*; import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Vertex; import java.awt.Point; import java.util.*; - public class HierarchicalLayoutManager extends LayoutManager { + int maxLayerLength; + private LayoutGraph graph; + public HierarchicalLayoutManager() { setCutEdges(false); } @@ -43,9 +44,11 @@ public void setCutEdges(boolean enable) { } @Override - public void setCutEdges(boolean enable) { - maxLayerLength = enable ? 10 : -1; - } + public void doLayout(LayoutGraph layoutGraph) { + layoutGraph.initializeLayout(); + + // STEP 1: Remove self edges and reverse edges + ReverseEdges.apply(layoutGraph); // STEP 2: Assign layers and create dummy nodes LayerManager.apply(layoutGraph, maxLayerLength); @@ -58,9 +61,15 @@ public void setCutEdges(boolean enable) { // STEP 5: Write back to interface WriteResult.apply(layoutGraph); + + graph = layoutGraph; } - static private class ReverseEdges { + public List getNodes() { + return graph.getAllNodes(); + } + + public static class ReverseEdges { static public void apply(LayoutGraph graph) { removeSelfEdges(graph); @@ -68,27 +77,32 @@ static public void apply(LayoutGraph graph) { depthFirstSearch(graph); for (LayoutNode node : graph.getLayoutNodes()) { - node.computeReversedLinkPoints(); + node.computeReversedLinkPoints(false); } } private static void removeSelfEdges(LayoutGraph graph) { for (LayoutNode node : graph.getLayoutNodes()) { - Iterator edgeIterator = node.getSuccs().iterator(); - while (edgeIterator.hasNext()) { - LayoutEdge edge = edgeIterator.next(); + // Collect self-edges first to avoid concurrent modification + List selfEdges = new ArrayList<>(); + for (LayoutEdge edge : node.getSuccessors()) { if (edge.getTo() == node) { - edgeIterator.remove(); - node.getPreds().remove(edge); + selfEdges.add(edge); } } + + // Remove each self-edge + for (LayoutEdge edge : selfEdges) { + node.removeSuccessor(edge); + node.removePredecessor(edge); + } } } private static void reverseRootInputs(LayoutGraph graph) { for (LayoutNode node : graph.getLayoutNodes()) { if (node.getVertex().isRoot()) { - for (LayoutEdge predEdge : new ArrayList<>(node.getPreds())) { + for (LayoutEdge predEdge : new ArrayList<>(node.getPredecessors())) { reverseEdge(predEdge); } } @@ -108,10 +122,10 @@ public static void reverseEdge(LayoutEdge edge) { edge.setRelativeFromX(relativeTo); edge.setRelativeToX(relativeFrom); - fromNode.getSuccs().remove(edge); - fromNode.getPreds().add(edge); - toNode.getPreds().remove(edge); - toNode.getSuccs().add(edge); + fromNode.removeSuccessor(edge); + fromNode.addPredecessor(edge); + toNode.removePredecessor(edge); + toNode.addSuccessor(edge); } private static void depthFirstSearch(LayoutGraph graph) { @@ -134,7 +148,7 @@ private static void depthFirstSearch(LayoutGraph graph) { visited.add(node); active.add(node); - for (LayoutEdge edge : new ArrayList<>(node.getSuccs())) { + for (LayoutEdge edge : new ArrayList<>(node.getSuccessors())) { LayoutNode successor = edge.getTo(); if (active.contains(successor)) { reverseEdge(edge); @@ -147,15 +161,14 @@ private static void depthFirstSearch(LayoutGraph graph) { } } - - static private class LayerManager { + public static class LayerManager { private static void assignLayerDownwards(LayoutGraph graph) { ArrayList workingList = new ArrayList<>(); // add all root nodes to layer 0 for (LayoutNode node : graph.getLayoutNodes()) { - if (!node.hasPreds()) { + if (!node.hasPredecessors()) { workingList.add(node); node.setLayer(0); } @@ -166,12 +179,12 @@ private static void assignLayerDownwards(LayoutGraph graph) { while (!workingList.isEmpty()) { ArrayList newWorkingList = new ArrayList<>(); for (LayoutNode node : workingList) { - for (LayoutEdge succEdge : node.getSuccs()) { + for (LayoutEdge succEdge : node.getSuccessors()) { LayoutNode succNode = succEdge.getTo(); if (succNode.getLayer() == -1) { // This node was not assigned before. boolean assignedPred = true; - for (LayoutEdge predEdge : succNode.getPreds()) { + for (LayoutEdge predEdge : succNode.getPredecessors()) { LayoutNode predNode = predEdge.getFrom(); if (predNode.getLayer() == -1 || predNode.getLayer() >= layer) { // This now has an unscheduled successor or a successor that was scheduled only in this round. @@ -201,7 +214,7 @@ private static void assignLayerUpwards(LayoutGraph graph) { ArrayList workingList = new ArrayList<>(); // add all leaves to working list, reset layer of non-leave nodes for (LayoutNode node : graph.getLayoutNodes()) { - if (!node.hasSuccs()) { + if (!node.hasSuccessors()) { workingList.add(node); } else { node.setLayer(-1); @@ -215,12 +228,12 @@ private static void assignLayerUpwards(LayoutGraph graph) { ArrayList newWorkingList = new ArrayList<>(); for (LayoutNode node : workingList) { if (node.getLayer() < layer) { - for (LayoutEdge predEdge : node.getPreds()) { + for (LayoutEdge predEdge : node.getPredecessors()) { LayoutNode predNode = predEdge.getFrom(); if (predNode.getLayer() == -1) { // This node was not assigned before. boolean assignedSucc = true; - for (LayoutEdge succEdge : predNode.getSuccs()) { + for (LayoutEdge succEdge : predNode.getSuccessors()) { LayoutNode succNode = succEdge.getTo(); if (succNode.getLayer() == -1 || succNode.getLayer() >= layer) { // This now has an unscheduled successor or a successor that was scheduled only in this round. @@ -269,19 +282,19 @@ static private void createDummyNodes(LayoutGraph graph, int maxLayerLength) { if (layoutNode.getLayer() == 0) { graph.getLayer(0).add(layoutNode); visited.add(layoutNode); - } else if (!layoutNode.hasPreds()) { + } else if (!layoutNode.hasPredecessors()) { graph.getLayer(layoutNode.getLayer()).add(layoutNode); visited.add(layoutNode); } } for (LayoutNode layoutNode : layoutNodes) { - createDummiesForNodeSuccessor(graph, layoutNode, maxLayerLength); + graph.createDummiesForNodeSuccessor(layoutNode, maxLayerLength); } for (int i = 0; i < graph.getLayerCount() - 1; i++) { for (LayoutNode n : graph.getLayer(i)) { - for (LayoutEdge e : n.getSuccs()) { + for (LayoutEdge e : n.getSuccessors()) { if (e.getTo().isDummy()) continue; if (!visited.contains(e.getTo())) { visited.add(e.getTo()); @@ -293,115 +306,6 @@ static private void createDummyNodes(LayoutGraph graph, int maxLayerLength) { } } - static private void createDummiesForNodeSuccessor(LayoutGraph graph, LayoutNode layoutNode, int maxLayerLength) { - HashMap> portsToUnprocessedEdges = new HashMap<>(); - ArrayList succs = new ArrayList<>(layoutNode.getSuccs()); - HashMap portToTopNode = new HashMap<>(); - HashMap> portToBottomNodeMapping = new HashMap<>(); - for (LayoutEdge succEdge : succs) { - int startPort = succEdge.getRelativeFromX(); - LayoutNode fromNode = succEdge.getFrom(); - LayoutNode toNode = succEdge.getTo(); - - // edge is longer than one layer => needs dummy nodes - if (fromNode.getLayer() != toNode.getLayer() - 1) { - // the edge needs to be cut - if (maxLayerLength != -1 && toNode.getLayer() - fromNode.getLayer() > maxLayerLength) { - // remove the succEdge before replacing it - toNode.getPreds().remove(succEdge); - fromNode.getSuccs().remove(succEdge); - - LayoutNode topCutNode = portToTopNode.get(startPort); - if (topCutNode == null) { - topCutNode = new LayoutNode(); - topCutNode.setLayer(fromNode.getLayer() + 1); - graph.addNodeToLayer(topCutNode, topCutNode.getLayer()); - portToTopNode.put(startPort, topCutNode); - portToBottomNodeMapping.put(startPort, new HashMap<>()); - } - LayoutEdge edgeToTopCut = new LayoutEdge(fromNode, topCutNode, succEdge.getRelativeFromX(), topCutNode.getWidth() / 2, succEdge.getLink()); - if (succEdge.isReversed()) edgeToTopCut.reverse(); - fromNode.getSuccs().add(edgeToTopCut); - topCutNode.getPreds().add(edgeToTopCut); - - HashMap layerToBottomNode = portToBottomNodeMapping.get(startPort); - LayoutNode bottomCutNode = layerToBottomNode.get(toNode.getLayer()); - if (bottomCutNode == null) { - bottomCutNode = new LayoutNode(); - bottomCutNode.setLayer(toNode.getLayer() - 1); - graph.addNodeToLayer(bottomCutNode, bottomCutNode.getLayer()); - layerToBottomNode.put(toNode.getLayer(), bottomCutNode); - } - LayoutEdge bottomEdge = new LayoutEdge(bottomCutNode, toNode, bottomCutNode.getWidth() / 2, succEdge.getRelativeToX(), succEdge.getLink()); - if (succEdge.isReversed()) bottomEdge.reverse(); - toNode.getPreds().add(bottomEdge); - bottomCutNode.getSuccs().add(bottomEdge); - - } else { // the edge is not cut, but needs dummy nodes - portsToUnprocessedEdges.putIfAbsent(startPort, new ArrayList<>()); - portsToUnprocessedEdges.get(startPort).add(succEdge); - } - } - } - - for (Map.Entry> portToUnprocessedEdges : portsToUnprocessedEdges.entrySet()) { - Integer startPort = portToUnprocessedEdges.getKey(); - List unprocessedEdges = portToUnprocessedEdges.getValue(); - unprocessedEdges.sort(LAYOUT_EDGE_LAYER_COMPARATOR); - - if (unprocessedEdges.size() == 1) { - // process a single edge - LayoutEdge singleEdge = unprocessedEdges.get(0); - LayoutNode fromNode = singleEdge.getFrom(); - if (singleEdge.getTo().getLayer() > fromNode.getLayer() + 1) { - LayoutEdge previousEdge = singleEdge; - for (int i = fromNode.getLayer() + 1; i < previousEdge.getTo().getLayer(); i++) { - LayoutNode dummyNode = new LayoutNode(); - dummyNode.setLayer(i); - dummyNode.getPreds().add(previousEdge); - graph.addNodeToLayer(dummyNode, dummyNode.getLayer()); - LayoutEdge dummyEdge = new LayoutEdge(dummyNode, previousEdge.getTo(), dummyNode.getWidth() / 2, previousEdge.getRelativeToX(), singleEdge.getLink()); - if (previousEdge.isReversed()) dummyEdge.reverse(); - dummyNode.getSuccs().add(dummyEdge); - previousEdge.setRelativeToX(dummyNode.getWidth() / 2); - previousEdge.getTo().getPreds().remove(previousEdge); - previousEdge.getTo().getPreds().add(dummyEdge); - previousEdge.setTo(dummyNode); - previousEdge = dummyEdge; - } - } - } else { - int lastLayer = unprocessedEdges.get(unprocessedEdges.size() - 1).getTo().getLayer(); - int dummyCnt = lastLayer - layoutNode.getLayer() - 1; - LayoutEdge[] newDummyEdges = new LayoutEdge[dummyCnt]; - LayoutNode[] newDummyNodes = new LayoutNode[dummyCnt]; - - newDummyNodes[0] = new LayoutNode(); - newDummyNodes[0].setLayer(layoutNode.getLayer() + 1); - newDummyEdges[0] = new LayoutEdge(layoutNode, newDummyNodes[0], startPort, newDummyNodes[0].getWidth() / 2, null); - newDummyNodes[0].getPreds().add(newDummyEdges[0]); - layoutNode.getSuccs().add(newDummyEdges[0]); - for (int j = 1; j < dummyCnt; j++) { - newDummyNodes[j] = new LayoutNode(); - newDummyNodes[j].setLayer(layoutNode.getLayer() + j + 1); - newDummyEdges[j] = new LayoutEdge(newDummyNodes[j - 1], newDummyNodes[j], null); - newDummyNodes[j].getPreds().add(newDummyEdges[j]); - newDummyNodes[j - 1].getSuccs().add(newDummyEdges[j]); - } - for (LayoutEdge unprocessedEdge : unprocessedEdges) { - LayoutNode anchorNode = newDummyNodes[unprocessedEdge.getTo().getLayer() - layoutNode.getLayer() - 2]; - anchorNode.getSuccs().add(unprocessedEdge); - unprocessedEdge.setFrom(anchorNode); - unprocessedEdge.setRelativeFromX(anchorNode.getWidth() / 2); - layoutNode.getSuccs().remove(unprocessedEdge); - } - for (LayoutNode dummyNode : newDummyNodes) { - graph.addNodeToLayer(dummyNode, dummyNode.getLayer()); - } - } - } - } - static public void apply(LayoutGraph graph, int maxLayerLength) { assignLayers(graph); createDummyNodes(graph, maxLayerLength); @@ -409,209 +313,280 @@ static public void apply(LayoutGraph graph, int maxLayerLength) { } } - private static class CrossingReduction { + static class CrossingReduction { public static void apply(LayoutGraph graph) { + for (int i = 0; i < graph.getLayerCount(); i++) { + graph.getLayer(i).updateNodeIndices(); + graph.getLayer(i).initXPositions(); + } + for (int i = 0; i < CROSSING_ITERATIONS; i++) { downSweep(graph); upSweep(graph); } downSweep(graph); - graph.updatePositions(); } - private static void doAveragePositions(LayoutLayer layer) { - for (LayoutNode node : layer) { - node.setWeightedPosition(node.averagePosition()); - } - layer.sort(CROSSING_NODE_COMPARATOR); - int x = 0; - for (LayoutNode n : layer) { - n.setWeightedPosition(x); - x += n.getOuterWidth() + NODE_OFFSET; + private static void downSweep(LayoutGraph graph) { + + for (int i = 1; i < graph.getLayerCount(); i++) { + for (LayoutNode n : graph.getLayer(i)) { + n.setCrossingNumber(0); + } + for (LayoutNode n : graph.getLayer(i)) { + int sum = 0; + int count = 0; + for (LayoutEdge e : n.getPredecessors()) { + sum += e.getStartX(); + count++; + } + + if (count > 0) { + sum /= count; + n.setCrossingNumber(sum); + } + } + updateCrossingNumbers(graph.getLayer(i), true); + graph.getLayer(i).sort(NODE_CROSSING_COMPARATOR); + graph.getLayer(i).initXPositions(); + graph.getLayer(i).updateNodeIndices(); } } - private static void doMedianPositions(LayoutLayer layer, boolean usePred) { - for (LayoutNode node : layer) { - int size = usePred ? node.getPreds().size() : node.getSuccs().size(); - if (size == 0) continue; - float[] values = new float[size]; - for (int j = 0; j < size; j++) { - LayoutNode predNode = usePred ? node.getPreds().get(j).getFrom() : node.getSuccs().get(j).getTo(); - values[j] = predNode.getWeightedPosition(); + private static void updateCrossingNumbers(LayoutLayer layer, boolean down) { + for (int i = 0; i < layer.size(); i++) { + LayoutNode n = layer.get(i); + LayoutNode prev = null; + if (i > 0) { + prev = layer.get(i - 1); } - Arrays.sort(values); - if (values.length % 2 == 0) { - node.setWeightedPosition((values[size / 2 - 1] + values[size / 2]) / 2); - } else { - node.setWeightedPosition(values[size / 2]); + LayoutNode next = null; + if (i < layer.size() - 1) { + next = layer.get(i + 1); + } + boolean cond = !n.hasSuccessors(); + if (down) { + cond = !n.hasPredecessors(); + } + if (cond) { + if (prev != null && next != null) { + n.setCrossingNumber((prev.getCrossingNumber() + next.getCrossingNumber()) / 2); + } else if (prev != null) { + n.setCrossingNumber(prev.getCrossingNumber()); + } else if (next != null) { + n.setCrossingNumber(next.getCrossingNumber()); + } } - } - layer.sort(CROSSING_NODE_COMPARATOR); - int x = 0; - for (LayoutNode n : layer) { - n.setWeightedPosition(x); - x += n.getOuterWidth() + NODE_OFFSET; } } - private static void placeLeavesAndRoots(LayoutLayer layer, boolean usePred) { - // Nodes that have no adjacent nodes on the neighboring layer: - // leave fixed in their current positions with non-fixed nodes sorted into the remaining positions - for (int j = 0; j < layer.size(); j++) { - LayoutNode node = layer.get(j); - if (usePred ? !node.hasPreds() : !node.hasSuccs()) { - float prevWeight = (j > 0) ? layer.get(j - 1).getWeightedPosition() : 0; - float nextWeight = (j < layer.size() - 1) ? layer.get(j + 1).getWeightedPosition() : 0; - node.setWeightedPosition((prevWeight + nextWeight) / 2); + private static void upSweep(LayoutGraph graph) { + for (int i = graph.getLayerCount() - 2; i >= 0; i--) { + for (LayoutNode n : graph.getLayer(i)) { + n.setCrossingNumber(0); } - } - layer.sort(CROSSING_NODE_COMPARATOR); - int x = 0; - for (LayoutNode n : layer) { - n.setWeightedPosition(x); - x += n.getOuterWidth() + NODE_OFFSET; + for (LayoutNode n : graph.getLayer(i)) { + int count = 0; + int sum = 0; + for (LayoutEdge e : n.getSuccessors()) { + sum += e.getEndX(); + count++; + } + if (count > 0) { + sum /= count; + n.setCrossingNumber(sum); + } + + } + updateCrossingNumbers(graph.getLayer(i), false); + graph.getLayer(i).sort(NODE_CROSSING_COMPARATOR); + graph.getLayer(i).initXPositions(); + graph.getLayer(i).updateNodeIndices(); } } + } - private static void downSweep(LayoutGraph graph) { - for (int i = 0; i < graph.getLayerCount(); i++) { - doAveragePositions(graph.getLayer(i)); + static class AssignXCoordinates { + + private static List> space; + private static List> downProcessingOrder; + private static List> upProcessingOrder; + + private static final Comparator nodeProcessingDownComparator = (n1, n2) -> { + if (n1.isDummy()) { + if (n2.isDummy()) { + return 0; + } + return -1; } - for (int i = 1; i < graph.getLayerCount(); i++) { - doMedianPositions(graph.getLayer(i), true); - placeLeavesAndRoots(graph.getLayer(i), true); + if (n2.isDummy()) { + return 1; } - } + return n1.getInDegree() - n2.getInDegree(); + }; - private static void upSweep(LayoutGraph graph) { - for (int i = graph.getLayerCount() - 1; i >= 0; i--) { - doAveragePositions(graph.getLayer(i)); + private static final Comparator nodeProcessingUpComparator = (n1, n2) -> { + if (n1.isDummy()) { + if (n2.isDummy()) { + return 0; + } + return -1; } - for (int i = graph.getLayerCount() - 2; i >= 0; i--) { - doMedianPositions(graph.getLayer(i), false); - placeLeavesAndRoots(graph.getLayer(i), false); + if (n2.isDummy()) { + return 1; } - } - } - - private static class AssignXCoordinates { + return n1.getOutDegree() - n2.getOutDegree(); + }; - static int[][] space; - static LayoutNode[][] downProcessingOrder; - static LayoutNode[][] upProcessingOrder; + public static void apply(LayoutGraph graph) { + space = new ArrayList<>(graph.getLayerCount()); + downProcessingOrder = new ArrayList<>(graph.getLayerCount()); + upProcessingOrder = new ArrayList<>(graph.getLayerCount()); - static private void createArrays(LayoutGraph graph) { - space = new int[graph.getLayerCount()][]; - downProcessingOrder = new LayoutNode[graph.getLayerCount()][]; - upProcessingOrder = new LayoutNode[graph.getLayerCount()][]; for (int i = 0; i < graph.getLayerCount(); i++) { - LayoutLayer layer = graph.getLayer(i); - space[i] = new int[layer.size()]; - downProcessingOrder[i] = new LayoutNode[layer.size()]; - upProcessingOrder[i] = new LayoutNode[layer.size()]; + // Add a new empty list for each layer + space.add(new ArrayList<>()); + downProcessingOrder.add(new ArrayList<>()); + upProcessingOrder.add(new ArrayList<>()); + int curX = 0; - for (int j = 0; j < layer.size(); j++) { - space[i][j] = curX; - LayoutNode node = layer.get(j); - curX += node.getOuterWidth() + NODE_OFFSET; - downProcessingOrder[i][j] = node; - upProcessingOrder[i][j] = node; + for (LayoutNode n : graph.getLayer(i)) { + // Add the current position to space and increment curX + space.get(i).add(curX); + curX += n.getOuterWidth() + NODE_OFFSET; + + // Add the current node to processing orders + downProcessingOrder.get(i).add(n); + upProcessingOrder.get(i).add(n); } - Arrays.sort(downProcessingOrder[i], NODE_PROCESSING_DOWN_COMPARATOR); - Arrays.sort(upProcessingOrder[i], NODE_PROCESSING_UP_COMPARATOR); + + // Sort the processing orders + downProcessingOrder.get(i).sort(nodeProcessingDownComparator); + upProcessingOrder.get(i).sort(nodeProcessingUpComparator); } - } - static private void initialPositions(LayoutGraph graph) { - for (LayoutNode layoutNode : graph.getLayoutNodes()) { - layoutNode.setX(space[layoutNode.getLayer()][layoutNode.getPos()]); + for (LayoutNode n : graph.getLayoutNodes()) { + n.setX(space.get(n.getLayer()).get(n.getPos())); } - for (LayoutNode dummyNode : graph.getDummyNodes()) { - dummyNode.setX(space[dummyNode.getLayer()][dummyNode.getPos()]); + + for (LayoutNode n : graph.getDummyNodes()) { + n.setX(space.get(n.getLayer()).get(n.getPos())); } - } - static private void apply(LayoutGraph graph) { - createArrays(graph); - initialPositions(graph); for (int i = 0; i < SWEEP_ITERATIONS; i++) { sweepDown(graph); + adjustSpace(graph); sweepUp(graph); + adjustSpace(graph); } - graph.optimizeBackEdgeCrossings(); - graph.straightenEdges(); + + sweepDown(graph); + adjustSpace(graph); + sweepUp(graph); } - static private void processRow(int[] space, LayoutNode[] processingOrder) { - Arrays.sort(processingOrder, DUMMY_NODES_THEN_OPTIMAL_X); - TreeSet treeSet = new TreeSet<>(NODE_POS_COMPARATOR); - for (LayoutNode node : processingOrder) { - int minX = Integer.MIN_VALUE; - SortedSet headSet = treeSet.headSet(node, false); - if (!headSet.isEmpty()) { - LayoutNode leftNeighbor = headSet.last(); - minX = leftNeighbor.getOuterLeft() + space[node.getPos()] - space[leftNeighbor.getPos()]; + private static void adjustSpace(LayoutGraph graph) { + for (int i = 0; i < graph.getLayerCount(); i++) { + for (LayoutNode n : graph.getLayer(i)) { + space.get(i).add(n.getX()); } + } + } - int maxX = Integer.MAX_VALUE; - SortedSet tailSet = treeSet.tailSet(node, false); - if (!tailSet.isEmpty()) { - LayoutNode rightNeighbor = tailSet.first(); - maxX = rightNeighbor.getOuterLeft() + space[node.getPos()] - space[rightNeighbor.getPos()]; + private static void sweepUp(LayoutGraph graph) { + for (int i = graph.getLayerCount() - 1; i >= 0; i--) { + NodeRow r = new NodeRow(space.get(i)); + for (LayoutNode n : upProcessingOrder.get(i)) { + int optimal = n.calculateOptimalXFromSuccessors(true); + r.insert(n, optimal); } - - node.setX(Math.min(Math.max(node.getOptimalX(), minX), maxX)); - treeSet.add(node); } } - static private void sweepUp(LayoutGraph graph) { - for (int i = graph.getLayerCount() - 2; i >= 0; i--) { - for (LayoutNode node : upProcessingOrder[i]) { - node.setOptimalX(node.calculateOptimalPositionUp()); + private static void sweepDown(LayoutGraph graph) { + for (int i = 1; i < graph.getLayerCount(); i++) { + NodeRow r = new NodeRow(space.get(i)); + for (LayoutNode n : downProcessingOrder.get(i)) { + int optimal = n.calculateOptimalXFromPredecessors(true); + r.insert(n, optimal); } - processRow(space[i], upProcessingOrder[i]); } } + } - static private void sweepDown(LayoutGraph graph) { - for (int i = 1; i < graph.getLayerCount(); i++) { - for (LayoutNode node : downProcessingOrder[i]) { - node.setOptimalX(node.calculateOptimalPositionDown()); + public static class NodeRow { + + private final TreeSet treeSet; + private final ArrayList space; + + public NodeRow(ArrayList space) { + treeSet = new TreeSet<>(NODE_POS_COMPARATOR); + this.space = space; + } + + public int offset(LayoutNode n1, LayoutNode n2) { + int v1 = space.get(n1.getPos()) + n1.getOuterWidth(); + int v2 = space.get(n2.getPos()); + return v2 - v1; + } + + public void insert(LayoutNode n, int pos) { + + SortedSet headSet = treeSet.headSet(n); + + LayoutNode leftNeighbor; + int minX = Integer.MIN_VALUE; + if (!headSet.isEmpty()) { + leftNeighbor = headSet.last(); + minX = leftNeighbor.getOuterRight() + offset(leftNeighbor, n); + } + + if (pos < minX) { + n.setX(minX); + } else { + + LayoutNode rightNeighbor; + SortedSet tailSet = treeSet.tailSet(n); + int maxX = Integer.MAX_VALUE; + if (!tailSet.isEmpty()) { + rightNeighbor = tailSet.first(); + maxX = rightNeighbor.getX() - offset(n, rightNeighbor) - n.getOuterWidth(); } - processRow(space[i], downProcessingOrder[i]); + + n.setX(Math.min(pos, maxX)); } + + treeSet.add(n); } } - private static class WriteResult { + public static class WriteResult { private static HashMap> computeLinkPositions(LayoutGraph graph) { HashMap> linkToSplitEndPoints = new HashMap<>(); HashMap> linkPositions = new HashMap<>(); for (LayoutNode layoutNode : graph.getLayoutNodes()) { - for (LayoutEdge predEdge : layoutNode.getPreds()) { + for (LayoutEdge predEdge : layoutNode.getPredecessors()) { LayoutNode fromNode = predEdge.getFrom(); LayoutNode toNode = predEdge.getTo(); ArrayList linkPoints = new ArrayList<>(); // input edge stub - linkPoints.add(new Point(predEdge.getEndX(), toNode.getTop())); + linkPoints.add(new Point(predEdge.getEndX(), predEdge.getEndY())); linkPoints.add(new Point(predEdge.getEndX(), graph.getLayer(toNode.getLayer()).getTop() - LAYER_OFFSET)); LayoutEdge curEdge = predEdge; - while (fromNode.isDummy() && fromNode.hasPreds()) { + while (fromNode.isDummy() && fromNode.hasPredecessors()) { linkPoints.add(new Point(fromNode.getCenterX(), graph.getLayer(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); linkPoints.add(new Point(fromNode.getCenterX(), graph.getLayer(fromNode.getLayer()).getTop() - LAYER_OFFSET)); - curEdge = fromNode.getPreds().get(0); + curEdge = fromNode.getPredecessors().get(0); fromNode = curEdge.getFrom(); } linkPoints.add(new Point(curEdge.getStartX(), graph.getLayer(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); // output edge stub - linkPoints.add(new Point(curEdge.getStartX(), fromNode.getBottom())); + linkPoints.add(new Point(curEdge.getStartX(), curEdge.getStartY())); if (predEdge.isReversed()) { for (Point relativeEnd : toNode.getReversedLinkEndPoints().get(predEdge.getLink())) { @@ -644,7 +619,7 @@ private static HashMap> computeLinkPositions(LayoutGraph graph } for (LayoutNode layoutNode : graph.getLayoutNodes()) { - for (LayoutEdge succEdge : layoutNode.getSuccs()) { + for (LayoutEdge succEdge : layoutNode.getSuccessors()) { if (succEdge.getLink() == null) continue; LayoutNode fromNode = succEdge.getFrom(); @@ -655,10 +630,10 @@ private static HashMap> computeLinkPositions(LayoutGraph graph linkPoints.add(new Point(succEdge.getStartX(), graph.getLayer(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); LayoutEdge curEdge = succEdge; - while (toNode.isDummy() && toNode.hasSuccs()) { + while (toNode.isDummy() && toNode.hasSuccessors()) { linkPoints.add(new Point(toNode.getCenterX(), graph.getLayer(toNode.getLayer()).getTop() - LAYER_OFFSET)); linkPoints.add(new Point(toNode.getCenterX(), graph.getLayer(toNode.getLayer()).getBottom() + LAYER_OFFSET)); - curEdge = toNode.getSuccs().get(0); + curEdge = toNode.getSuccessors().get(0); toNode = curEdge.getTo(); } linkPoints.add(new Point(curEdge.getEndX(), graph.getLayer(toNode.getLayer()).getTop() - LAYER_OFFSET)); @@ -743,7 +718,7 @@ public static void apply(LayoutGraph graph) { } for (LayoutLayer layer : graph.getLayers()) { - layer.shiftTop(-minY); + layer.moveLayerVertically(-minY); } // Shift vertices by minX/minY diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java index 2922e2c192e89..afd692e84464f 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalStableLayoutManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,27 +23,23 @@ */ package com.sun.hotspot.igv.hierarchicallayout; +import static com.sun.hotspot.igv.hierarchicallayout.LayoutManager.LAYER_OFFSET; +import static com.sun.hotspot.igv.hierarchicallayout.LayoutManager.NODE_OFFSET; import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Vertex; import java.awt.Dimension; import java.awt.Point; import java.util.*; -public class HierarchicalStableLayoutManager extends LayoutManager{ +public class HierarchicalStableLayoutManager { - public static final int DUMMY_HEIGHT = 1; - public static final int DUMMY_WIDTH = 1; - public static final int X_OFFSET = 8; - public static final int LAYER_OFFSET = 8; // Algorithm global data structures private Set currentVertices; private Set currentLinks; private Set reversedLinks; private List nodes; private final HashMap vertexToLayoutNode; - private HashMap> reversedLinkStartPoints; - private HashMap> reversedLinkEndPoints; - private HashMap> layers; + private HashMap layers; private final HierarchicalLayoutManager manager; private HashMap vertexToAction; @@ -63,14 +59,6 @@ public void doLayout(LayoutGraph layoutGraph) { } - public void setCutEdges(boolean cutEdges) { - } - - @Override - public void doLayout(LayoutGraph graph) { - - } - enum Action { ADD, REMOVE @@ -100,7 +88,7 @@ public LinkAction(Link link, Action action) { public HierarchicalStableLayoutManager() { oldVertices = new HashSet<>(); oldLinks = new HashSet<>(); - manager = new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS); + manager = new HierarchicalLayoutManager(); vertexToLayoutNode = new HashMap<>(); nodes = new ArrayList<>(); } @@ -115,26 +103,48 @@ public boolean getCutEdges() { } private int calculateOptimalBoth(LayoutNode n) { - if (n.preds.isEmpty() && n.succs.isEmpty()) { - return n.x; + if (n.getPredecessors().isEmpty() && n.getSuccessors().isEmpty()) { + return n.getX(); } - int[] values = new int[n.preds.size() + n.succs.size()]; + int[] values = new int[n.getPredecessors().size() + n.getSuccessors().size()]; int i = 0; - for (LayoutEdge e : n.preds) { - values[i] = e.from.x + e.relativeFrom - e.relativeTo; + for (LayoutEdge e : n.getPredecessors()) { + values[i] = e.getFrom().getX() + e.getRelativeFromX() - e.getRelativeToX(); i++; } - for (LayoutEdge e : n.succs) { - values[i] = e.to.x + e.relativeTo - e.relativeFrom; + for (LayoutEdge e : n.getSuccessors()) { + values[i] = e.getTo().getX() + e.getRelativeToX() - e.getRelativeFromX(); i++; } - return Statistics.median(values); + return median(values); + } + + public static int median(int[] values) { + Arrays.sort(values); + if (values.length % 2 == 0) { + return (values[values.length / 2 - 1] + values[values.length / 2]) / 2; + } else { + return values[values.length / 2]; + } } + public static final Comparator nodeProcessingUpComparator = (n1, n2) -> { + if (n1.getVertex() == null) { + if (n2.getVertex() == null) { + return 0; + } + return -1; + } + if (n2.getVertex() == null) { + return 1; + } + return n1.getSuccessors().size() - n2.getSuccessors().size(); + }; + /** * Adjust the X-coordinates of the nodes in the given layer, as a new node has * been inserted at that layer @@ -144,16 +154,16 @@ private void adjustXCoordinates(int layer) { ArrayList space = new ArrayList<>(); List nodeProcessingOrder = new ArrayList<>(); - nodes.sort(HierarchicalLayoutManager.nodePositionComparator); + nodes.sort(Comparator.comparingInt(LayoutNode::getPos)); int curX = 0; for (LayoutNode n : nodes) { space.add(curX); - curX += n.width + X_OFFSET; + curX += n.getOuterWidth() + NODE_OFFSET; nodeProcessingOrder.add(n); } - nodeProcessingOrder.sort(HierarchicalLayoutManager.nodeProcessingUpComparator); + nodeProcessingOrder.sort(nodeProcessingUpComparator); HierarchicalLayoutManager.NodeRow r = new HierarchicalLayoutManager.NodeRow(space); for (LayoutNode n : nodeProcessingOrder) { int optimal = calculateOptimalBoth(n); @@ -163,8 +173,8 @@ private void adjustXCoordinates(int layer) { private void ensureNeighborEdgeConsistency() { for (LayoutNode n : nodes) { - n.succs.removeIf(e -> !nodes.contains(e.to)); - n.preds.removeIf(e -> !nodes.contains(e.from)); + n.getSuccessorsRaw().removeIf(e -> !nodes.contains(e.getTo())); + n.getPredecessorsRaw().removeIf(e -> !nodes.contains(e.getFrom())); } } @@ -254,8 +264,8 @@ private void findInitialReversedLinks() { for (Link link : oldLinks) { for (Link l : currentLinks) { if (l.equals(link)) { - if (vertexToLayoutNode.get(l.getFrom().getVertex()).layer > vertexToLayoutNode - .get(l.getTo().getVertex()).layer) { + if (vertexToLayoutNode.get(l.getFrom().getVertex()).getLayer() > vertexToLayoutNode + .get(l.getTo().getVertex()).getLayer()) { // Link is reversed reversedLinks.add(l); updateReversedLinkPositions(l); @@ -271,7 +281,7 @@ private void updateReversedLinkPositions(Link link) { // Correct direction, is reversed link assert fromNode != null && toNode != null; assert nodes.contains(fromNode) && nodes.contains(toNode); - assert fromNode.layer > toNode.layer; + assert fromNode.getLayer() > toNode.getLayer(); assert reversedLinks.contains(link); updateNodeWithReversedEdges(fromNode); @@ -279,138 +289,7 @@ private void updateReversedLinkPositions(Link link) { } private void updateNodeWithReversedEdges(LayoutNode node) { - // Reset node data in case there were previous reversed edges - node.width = (int) node.vertex.getSize().getWidth(); - node.height = (int) node.vertex.getSize().getHeight(); - node.yOffset = 0; - node.bottomYOffset = 0; - node.xOffset = 0; - node.inOffsets.clear(); - node.outOffsets.clear(); - - SortedSet reversedDown = new TreeSet<>(); - - // Reset relativeFrom for all succ edges - for (LayoutEdge e : node.succs) { - if (e.link == null) { - continue; - } - e.relativeFrom = e.link.getFrom().getRelativePosition().x; - if (reversedLinks.contains(e.link)) { - e.relativeFrom = e.link.getTo().getRelativePosition().x; - reversedDown.add(e.relativeFrom); - } - } - - // Whether the node has non-self reversed edges going downwards. - // If so, reversed edges going upwards are drawn to the left. - boolean hasReversedDown = !reversedDown.isEmpty(); - - SortedSet reversedUp; - if (hasReversedDown) { - reversedUp = new TreeSet<>(); - } else { - reversedUp = new TreeSet<>(Collections.reverseOrder()); - } - - // Reset relativeTo for all pred edges - for (LayoutEdge e : node.preds) { - if (e.link == null) { - continue; - } - e.relativeTo = e.link.getTo().getRelativePosition().x; - if (reversedLinks.contains(e.link)) { - e.relativeTo = e.link.getFrom().getRelativePosition().x; - reversedUp.add(e.relativeTo); - } - } - - final int offset = X_OFFSET + DUMMY_WIDTH; - - int curY = 0; - int curWidth = node.width + reversedDown.size() * offset; - for (int pos : reversedDown) { - ArrayList reversedSuccs = new ArrayList<>(); - for (LayoutEdge e : node.succs) { - if (e.relativeFrom == pos && reversedLinks.contains(e.link)) { - reversedSuccs.add(e); - e.relativeFrom = curWidth; - } - } - - ArrayList startPoints = new ArrayList<>(); - startPoints.add(new Point(curWidth, curY)); - startPoints.add(new Point(pos, curY)); - startPoints.add(new Point(pos, reversedDown.size() * offset)); - for (LayoutEdge e : reversedSuccs) { - reversedLinkStartPoints.put(e.link, startPoints); - } - - node.inOffsets.put(pos, -curY); - curY += offset; - node.height += offset; - node.yOffset += offset; - curWidth -= offset; - } - - int widthFactor = reversedDown.size(); - node.width += widthFactor * offset; - - int curX = 0; - int minX = 0; - if (hasReversedDown) { - minX = -offset * reversedUp.size(); - } - - int oldNodeHeight = node.height; - for (int pos : reversedUp) { - ArrayList reversedPreds = new ArrayList<>(); - for (LayoutEdge e : node.preds) { - if (e.relativeTo == pos && reversedLinks.contains(e.link)) { - if (hasReversedDown) { - e.relativeTo = curX - offset; - } else { - e.relativeTo = node.width + offset; - } - - reversedPreds.add(e); - } - } - node.height += offset; - ArrayList endPoints = new ArrayList<>(); - - node.width += offset; - if (hasReversedDown) { - curX -= offset; - endPoints.add(new Point(curX, node.height)); - } else { - curX += offset; - endPoints.add(new Point(node.width, node.height)); - } - - node.outOffsets.put(pos - minX, curX); - curX += offset; - node.bottomYOffset += offset; - - endPoints.add(new Point(pos, node.height)); - endPoints.add(new Point(pos, oldNodeHeight)); - for (LayoutEdge e : reversedPreds) { - reversedLinkEndPoints.put(e.link, endPoints); - } - } - - if (minX < 0) { - for (LayoutEdge e : node.preds) { - e.relativeTo -= minX; - } - - for (LayoutEdge e : node.succs) { - e.relativeFrom -= minX; - } - - node.xOffset = -minX; - node.width -= minX; - } + node.computeReversedLinkPoints(false); } /** @@ -424,8 +303,6 @@ public void updateLayout(Set vertices, Set lin currentVertices = vertices; currentLinks = links; reversedLinks = new HashSet<>(); - reversedLinkStartPoints = new HashMap<>(); - reversedLinkEndPoints = new HashMap<>(); vertexActions = new LinkedList<>(); linkActions = new LinkedList<>(); vertexToAction = new HashMap<>(); @@ -436,12 +313,12 @@ public void updateLayout(Set vertices, Set lin // If the layout is too messy it should be redrawn using the static algorithm, // currently HierarchicalLayoutManager manager.doLayout(new LayoutGraph(links, vertices)); - nodes = manager.getNodes(); + nodes = new ArrayList<>(manager.getNodes()); shouldRedrawLayout = false; } else { generateActions(); - new BuildDatastructure().run(); + new BuildDatastructures().run(); findInitialReversedLinks(); @@ -477,7 +354,7 @@ private void run() { } } - private class BuildDatastructure { + private class BuildDatastructures { // In case there are changes in the node size, its layer must be updated Set layersToUpdate = new HashSet<>(); @@ -488,53 +365,51 @@ private class BuildDatastructure { */ private void updateNodeObjects() { for (LayoutNode node : nodes) { - if (node.vertex != null) { + if (node.getVertex() != null) { for (Vertex vertex : currentVertices) { - if (vertex.equals(node.vertex)) { + if (vertex.equals(node.getVertex())) { Dimension size = vertex.getSize(); - if (node.width < (int) size.getWidth()) { - layersToUpdate.add(node.layer); + if (node.getOuterWidth() < (int) size.getWidth()) { + layersToUpdate.add(node.getLayer()); } - node.width = (int) size.getWidth(); - node.height = (int) size.getHeight(); - node.vertex = vertex; + node.initSize(); + node.setVertex(vertex); } } - vertexToLayoutNode.put(node.vertex, node); + vertexToLayoutNode.put(node.getVertex(), node); } else { - node.height = DUMMY_HEIGHT; - node.width = DUMMY_WIDTH; + node.initSize(); } - for (LayoutEdge edge : node.preds) { - if (edge.link != null) { + for (LayoutEdge edge : node.getPredecessors()) { + if (edge.getLink() != null) { for (Link link : currentLinks) { - if (link.equals(edge.link)) { - edge.link = link; - if (link.getTo().getVertex().equals(edge.from.vertex)) { + if (link.equals(edge.getLink())) { + edge.setLink(link); + if (link.getTo().getVertex().equals(edge.getFrom().getVertex())) { // reversed link - edge.relativeFrom = link.getTo().getRelativePosition().x; - edge.relativeTo = link.getFrom().getRelativePosition().x; + edge.setRelativeFromX(link.getTo().getRelativePosition().x); + edge.setRelativeToX(link.getFrom().getRelativePosition().x); } else { - edge.relativeFrom = link.getFrom().getRelativePosition().x; - edge.relativeTo = link.getTo().getRelativePosition().x; + edge.setRelativeFromX(link.getFrom().getRelativePosition().x); + edge.setRelativeToX(link.getTo().getRelativePosition().x); } break; } } } } - for (LayoutEdge edge : node.succs) { - if (edge.link != null) { + for (LayoutEdge edge : node.getSuccessors()) { + if (edge.getLink() != null) { for (Link link : currentLinks) { - if (link.equals(edge.link)) { - edge.link = link; - if (link.getTo().getVertex().equals(edge.from.vertex)) { + if (link.equals(edge.getLink())) { + edge.setLink(link); + if (link.getTo().getVertex().equals(edge.getFrom().getVertex())) { // reversed link - edge.relativeFrom = link.getTo().getRelativePosition().x; - edge.relativeTo = link.getFrom().getRelativePosition().x; + edge.setRelativeFromX(link.getTo().getRelativePosition().x); + edge.setRelativeToX(link.getFrom().getRelativePosition().x); } else { - edge.relativeFrom = link.getFrom().getRelativePosition().x; - edge.relativeTo = link.getTo().getRelativePosition().x; + edge.setRelativeFromX(link.getFrom().getRelativePosition().x); + edge.setRelativeToX(link.getTo().getRelativePosition().x); } break; } @@ -550,14 +425,14 @@ private void updateNodeObjects() { private void storeNodeLayers() { layers = new HashMap<>(); for (LayoutNode node : nodes) { - if (!layers.containsKey(node.layer)) { - layers.put(node.layer, new ArrayList<>()); + if (!layers.containsKey(node.getLayer())) { + layers.put(node.getLayer(), new LayoutLayer()); } - layers.get(node.layer).add(node); + layers.get(node.getLayer()).add(node); } for (int i = 0; i < layers.keySet().size(); i++) { if (!layers.containsKey(i)) { - layers.put(i, new ArrayList<>()); + layers.put(i, new LayoutLayer()); } } } @@ -584,7 +459,7 @@ private int optimalPosition(LayoutNode node, int layer) { assert layers.containsKey(layer); List layerNodes = layers.get(layer); - layerNodes.sort(HierarchicalLayoutManager.nodePositionComparator); + layerNodes.sort(Comparator.comparingInt(LayoutNode::getPos)); int edgeCrossings = Integer.MAX_VALUE; int optimalPos = -1; @@ -592,9 +467,9 @@ private int optimalPosition(LayoutNode node, int layer) { for (int i = 0; i < layerNodes.size() + 1; i++) { int xCoord; if (i == 0) { - xCoord = layerNodes.get(i).x - node.width - 1; + xCoord = layerNodes.get(i).getX() - node.getOuterWidth() - 1; } else { - xCoord = layerNodes.get(i - 1).x + layerNodes.get(i - 1).width + 1; + xCoord = layerNodes.get(i - 1).getX() + layerNodes.get(i - 1).getOuterWidth() + 1; } int currentCrossings = 0; @@ -602,28 +477,28 @@ private int optimalPosition(LayoutNode node, int layer) { if (layers.containsKey(layer - 1)) { List predNodes = layers.get(layer - 1); // For each link with an end point in vertex, check how many edges cross it - for (LayoutEdge edge : node.preds) { - if (edge.from.layer == layer - 1) { - int fromNodeXCoord = edge.from.x; - if (edge.from.vertex != null) { - fromNodeXCoord += edge.relativeFrom; + for (LayoutEdge edge : node.getPredecessors()) { + if (edge.getFrom().getLayer() == layer - 1) { + int fromNodeXCoord = edge.getFrom().getX(); + if (edge.getFrom().getVertex() != null) { + fromNodeXCoord += edge.getRelativeFromX(); } int toNodeXCoord = xCoord; - if (node.vertex != null) { - toNodeXCoord += edge.relativeTo; + if (node.getVertex() != null) { + toNodeXCoord += edge.getRelativeToX(); } for (LayoutNode n : predNodes) { - for (LayoutEdge e : n.succs) { - if (e.to == null) { + for (LayoutEdge e : n.getSuccessors()) { + if (e.getTo() == null) { continue; } - int compFromXCoord = e.from.x; - if (e.from.vertex != null) { - compFromXCoord += e.relativeFrom; + int compFromXCoord = e.getFrom().getX(); + if (e.getFrom().getVertex() != null) { + compFromXCoord += e.getRelativeFromX(); } - int compToXCoord = e.to.x; - if (e.to.vertex != null) { - compToXCoord += e.relativeTo; + int compToXCoord = e.getTo().getX(); + if (e.getTo().getVertex() != null) { + compToXCoord += e.getRelativeToX(); } if ((fromNodeXCoord > compFromXCoord && toNodeXCoord < compToXCoord) || (fromNodeXCoord < compFromXCoord @@ -639,28 +514,28 @@ private int optimalPosition(LayoutNode node, int layer) { if (layers.containsKey(layer + 1)) { List succsNodes = layers.get(layer + 1); // For each link with an end point in vertex, check how many edges cross it - for (LayoutEdge edge : node.succs) { - if (edge.to.layer == layer + 1) { - int toNodeXCoord = edge.to.x; - if (edge.to.vertex != null) { - toNodeXCoord += edge.relativeTo; + for (LayoutEdge edge : node.getSuccessors()) { + if (edge.getTo().getLayer() == layer + 1) { + int toNodeXCoord = edge.getTo().getX(); + if (edge.getTo().getVertex() != null) { + toNodeXCoord += edge.getRelativeToX(); } int fromNodeXCoord = xCoord; - if (node.vertex != null) { - fromNodeXCoord += edge.relativeFrom; + if (node.getVertex() != null) { + fromNodeXCoord += edge.getRelativeFromX(); } for (LayoutNode n : succsNodes) { - for (LayoutEdge e : n.preds) { - if (e.from == null) { + for (LayoutEdge e : n.getPredecessors()) { + if (e.getFrom() == null) { continue; } - int compFromXCoord = e.from.x; - if (e.from.vertex != null) { - compFromXCoord += e.relativeFrom; + int compFromXCoord = e.getFrom().getX(); + if (e.getFrom().getVertex() != null) { + compFromXCoord += e.getRelativeFromX(); } - int compToXCoord = e.to.x; - if (e.to.vertex != null) { - compToXCoord += e.relativeTo; + int compToXCoord = e.getTo().getX(); + if (e.getTo().getVertex() != null) { + compToXCoord += e.getRelativeToX(); } if ((fromNodeXCoord > compFromXCoord && toNodeXCoord < compToXCoord) || (fromNodeXCoord < compFromXCoord @@ -688,18 +563,18 @@ private int optimalPosition(LayoutNode node, int layer) { private void insertNode(LayoutNode node, int layer) { assert layers.containsKey(layer) || layer == 0; - node.layer = layer; - List layerNodes = layers.getOrDefault(layer, new ArrayList()); + node.setLayer(layer); + LayoutLayer layerNodes = layers.getOrDefault(layer, new LayoutLayer()); if (layerNodes.isEmpty()) { - node.pos = 0; + node.setPos(0); } else { - node.pos = optimalPosition(node, layer); + node.setPos(optimalPosition(node, layer)); } for (LayoutNode n : layerNodes) { - if (n.pos >= node.pos) { - n.pos += 1; + if (n.getPos() >= node.getPos()) { + n.setPos(n.getPos() + 1); } } layerNodes.add(node); @@ -708,18 +583,18 @@ private void insertNode(LayoutNode node, int layer) { if (!nodes.contains(node)) { nodes.add(node); } - if (node.vertex != null) { - vertexToLayoutNode.put(node.vertex, node); + if (node.getVertex() != null) { + vertexToLayoutNode.put(node.getVertex(), node); } adjustXCoordinates(layer); } private void processSingleEdge(LayoutEdge e) { - LayoutNode n = e.to; - if (e.to.layer - 1 > e.from.layer) { + LayoutNode n = e.getTo(); + if (e.getTo().getLayer() - 1 > e.getFrom().getLayer()) { LayoutEdge last = e; - for (int i = n.layer - 1; i > last.from.layer; i--) { + for (int i = n.getLayer() - 1; i > last.getFrom().getLayer(); i--) { last = addBetween(last, i); } } @@ -727,34 +602,26 @@ private void processSingleEdge(LayoutEdge e) { private LayoutEdge addBetween(LayoutEdge e, int layer) { LayoutNode n = new LayoutNode(); - n.width = DUMMY_WIDTH; - n.height = DUMMY_HEIGHT; - n.succs.add(e); - LayoutEdge result = new LayoutEdge(); - result.vip = e.vip; - n.preds.add(result); - result.to = n; - result.relativeTo = n.width / 2; - result.from = e.from; - result.relativeFrom = e.relativeFrom; - result.link = e.link; - e.relativeFrom = n.width / 2; - e.from.succs.remove(e); - e.from.succs.add(result); - e.from = n; + n.addSuccessor(e); + LayoutEdge result = new LayoutEdge(e.getFrom(), n, e.getRelativeFromX(), n.getOuterWidth() / 2, e.getLink()); + n.addPredecessor(result); + e.setRelativeFromX(n.getOuterWidth() / 2); + e.getFrom().removeSuccessor(e); + e.getFrom().addSuccessor(result); + e.setFrom(n); insertNode(n, layer); return result; } private void insertDummyNodes(LayoutEdge edge) { - LayoutNode from = edge.from; - LayoutNode to = edge.to; + LayoutNode from = edge.getFrom(); + LayoutNode to = edge.getTo(); boolean hasEdgeFromSamePort = false; - LayoutEdge edgeFromSamePort = new LayoutEdge(); + LayoutEdge edgeFromSamePort = null; - for (LayoutEdge e : edge.from.succs) { - if (e.relativeFrom == edge.relativeFrom && e.to.vertex == null) { + for (LayoutEdge e : edge.getFrom().getSuccessors()) { + if (e.getRelativeFromX() == edge.getRelativeFromX() && e.getTo().getVertex() == null) { edgeFromSamePort = e; hasEdgeFromSamePort = true; break; @@ -766,16 +633,16 @@ private void insertDummyNodes(LayoutEdge edge) { } else { LayoutEdge curEdge = edgeFromSamePort; boolean newEdge = true; - while (curEdge.to.layer < to.layer - 1 && curEdge.to.vertex == null && newEdge) { + while (curEdge.getTo().getLayer() < to.getLayer() - 1 && curEdge.getTo().getVertex() == null && newEdge) { // Traverse down the chain of dummy nodes linking together the edges originating // from the same port newEdge = false; - if (curEdge.to.succs.size() == 1) { - curEdge = curEdge.to.succs.get(0); + if (curEdge.getTo().getSuccessors().size() == 1) { + curEdge = curEdge.getTo().getSuccessors().get(0); newEdge = true; } else { - for (LayoutEdge e : curEdge.to.succs) { - if (e.to.vertex == null) { + for (LayoutEdge e : curEdge.getTo().getSuccessors()) { + if (e.getTo().getVertex() == null) { curEdge = e; newEdge = true; break; @@ -785,27 +652,27 @@ private void insertDummyNodes(LayoutEdge edge) { } LayoutNode prevDummy; - if (curEdge.to.vertex != null) { - prevDummy = curEdge.from; + if (curEdge.getTo().getVertex() != null) { + prevDummy = curEdge.getFrom(); } else { - prevDummy = curEdge.to; + prevDummy = curEdge.getTo(); } - edge.from = prevDummy; - edge.relativeFrom = prevDummy.width / 2; - from.succs.remove(edge); - prevDummy.succs.add(edge); + edge.setFrom(prevDummy); + edge.setRelativeFromX(prevDummy.getOuterWidth() / 2); + from.removeSuccessor(edge); + prevDummy.addSuccessor(edge); processSingleEdge(edge); } } private boolean canMoveNodeUp(LayoutNode node) { - if (node.layer == 0) { + if (node.getLayer() == 0) { return false; } - int newLayer = node.layer - 1; - for (LayoutEdge e : node.preds) { - if (e.from.vertex != null && e.from.layer == newLayer) { + int newLayer = node.getLayer() - 1; + for (LayoutEdge e : node.getPredecessors()) { + if (e.getFrom().getVertex() != null && e.getFrom().getLayer() == newLayer) { return false; } } @@ -813,12 +680,12 @@ private boolean canMoveNodeUp(LayoutNode node) { } private boolean canMoveNodeDown(LayoutNode node) { - if (node.layer == layers.keySet().size() - 1) { + if (node.getLayer() == layers.keySet().size() - 1) { return false; } - int newLayer = node.layer + 1; - for (LayoutEdge e : node.succs) { - if (e.to.vertex != null && e.to.layer == newLayer) { + int newLayer = node.getLayer() + 1; + for (LayoutEdge e : node.getSuccessors()) { + if (e.getTo().getVertex() != null && e.getTo().getLayer() == newLayer) { return false; } } @@ -828,23 +695,23 @@ private boolean canMoveNodeDown(LayoutNode node) { private void moveNodeUp(LayoutNode node) { assert canMoveNodeUp(node); - List previousPredEdges = List.copyOf(node.preds); + List previousPredEdges = List.copyOf(node.getPredecessors()); for (LayoutEdge edge : previousPredEdges) { - LayoutNode predNode = edge.from; - assert predNode.vertex == null; - for (LayoutEdge e : predNode.preds) { - e.to = edge.to; - e.relativeTo = edge.relativeTo; - node.preds.add(e); - node.preds.remove(edge); + LayoutNode predNode = edge.getFrom(); + assert predNode.getVertex() == null; + for (LayoutEdge e : predNode.getPredecessors()) { + e.setTo(edge.getTo()); + e.setRelativeToX(edge.getRelativeToX()); + node.addPredecessor(e); + node.removePredecessor(edge); } removeNodeWithoutRemovingLayer(predNode); } removeNodeWithoutRemovingLayer(node); - insertNode(node, node.layer - 1); + insertNode(node, node.getLayer() - 1); - for (LayoutEdge edge : List.copyOf(node.succs)) { + for (LayoutEdge edge : List.copyOf(node.getSuccessors())) { processSingleEdge(edge); } } @@ -852,23 +719,23 @@ private void moveNodeUp(LayoutNode node) { private void moveNodeDown(LayoutNode node) { assert canMoveNodeDown(node); - List previousSuccEdges = List.copyOf(node.succs); + List previousSuccEdges = List.copyOf(node.getSuccessors()); for (LayoutEdge edge : previousSuccEdges) { - LayoutNode succNode = edge.to; - assert succNode.vertex == null; - for (LayoutEdge e : succNode.succs) { - e.from = edge.from; - e.relativeFrom = edge.relativeFrom; - node.succs.add(e); - node.succs.remove(edge); + LayoutNode succNode = edge.getTo(); + assert succNode.getVertex() == null; + for (LayoutEdge e : succNode.getSuccessors()) { + e.setFrom(edge.getFrom()); + e.setRelativeFromX(edge.getRelativeFromX()); + node.addSuccessor(e); + node.removeSuccessor(edge); } removeNodeWithoutRemovingLayer(succNode); } removeNodeWithoutRemovingLayer(node); - insertNode(node, node.layer + 1); + insertNode(node, node.getLayer() + 1); - for (LayoutEdge edge : List.copyOf(node.preds)) { + for (LayoutEdge edge : List.copyOf(node.getPredecessors())) { processSingleEdge(edge); } } @@ -890,25 +757,25 @@ private void handleNeighborNodesOnSameLayer(LayoutNode from, LayoutNode to) { * remaining layers numbers */ private void expandNewLayerBeneath(LayoutNode node) { - int layer = node.layer + 1; + int layer = node.getLayer() + 1; // Move all necessary layers down one step for (int i = layers.size() - 1; i >= layer; i--) { - List list = layers.get(i); + LayoutLayer list = layers.get(i); for (LayoutNode n : list) { - n.layer = i + 1; + n.setLayer(i + 1); } layers.remove(i); layers.put(i + 1, list); } // Create new empty layer - List l = new ArrayList<>(); + LayoutLayer l = new LayoutLayer(); layers.put(layer, l); assert layers.get(layer).isEmpty(); for (LayoutNode n : nodes) { - assert n.layer != layer; + assert n.getLayer() != layer; } // Add dummy nodes for edges going across new layer. One for each port on the @@ -917,35 +784,28 @@ private void expandNewLayerBeneath(LayoutNode node) { for (LayoutNode n : predLayer) { HashMap> portHashes = new HashMap<>(); - for (LayoutEdge e : n.succs) { - if (!portHashes.containsKey(e.relativeFrom)) { - portHashes.put(e.relativeFrom, new ArrayList<>()); + for (LayoutEdge e : n.getSuccessors()) { + if (!portHashes.containsKey(e.getRelativeFromX())) { + portHashes.put(e.getRelativeFromX(), new ArrayList<>()); } - portHashes.get(e.relativeFrom).add(e); + portHashes.get(e.getRelativeFromX()).add(e); } for (Integer i : portHashes.keySet()) { List edges = portHashes.get(i); LayoutNode dummy = new LayoutNode(); - dummy.width = DUMMY_WIDTH; - dummy.height = DUMMY_HEIGHT; - - LayoutEdge newEdge = new LayoutEdge(); - newEdge.from = n; - newEdge.relativeFrom = i; - newEdge.to = dummy; - newEdge.relativeTo = dummy.width / 2; - newEdge.link = edges.get(0).link; // issue? - n.succs.add(newEdge); - dummy.preds.add(newEdge); + + LayoutEdge newEdge = new LayoutEdge(n, dummy, i, dummy.getOuterWidth() / 2, edges.get(0).getLink()); + n.addSuccessor(newEdge); + dummy.addPredecessor(newEdge); for (LayoutEdge e : edges) { - e.from = dummy; - e.relativeFrom = dummy.width / 2; - n.succs.remove(e); - dummy.succs.add(e); - assert e.to.layer == layer + 1; + e.setFrom(dummy); + e.setRelativeFromX(dummy.getOuterWidth() / 2); + n.removeSuccessor(e); + dummy.addSuccessor(e); + assert e.getTo().getLayer() == layer + 1; } insertNode(dummy, layer); @@ -955,7 +815,7 @@ private void expandNewLayerBeneath(LayoutNode node) { // Move node to new layer moveNodeDown(node); assert layers.get(layer).contains(node); - assert node.layer == layer; + assert node.getLayer() == layer; } private void applyAddLinkAction(Link l) { @@ -968,18 +828,12 @@ private void applyAddLinkAction(Link l) { return; } - if (toNode.layer == fromNode.layer) { + if (toNode.getLayer() == fromNode.getLayer()) { handleNeighborNodesOnSameLayer(fromNode, toNode); } - LayoutEdge edge = new LayoutEdge(); - edge.link = l; - edge.from = fromNode; - edge.relativeFrom = l.getFrom().getRelativePosition().x; - edge.to = toNode; - edge.relativeTo = l.getTo().getRelativePosition().x; - - boolean reversedLink = fromNode.layer > toNode.layer; + LayoutEdge edge = new LayoutEdge(fromNode, toNode, l.getFrom().getRelativePosition().x, l.getTo().getRelativePosition().x, l); + boolean reversedLink = fromNode.getLayer() > toNode.getLayer(); if (reversedLink) { // Reversed link reversedLinks.add(l); @@ -988,23 +842,23 @@ private void applyAddLinkAction(Link l) { fromNode = toNode; toNode = temp; - int oldRelativeFrom = edge.relativeFrom; - int oldRelativeTo = edge.relativeTo; + int oldRelativeFrom = edge.getRelativeFromX(); + int oldRelativeTo = edge.getRelativeToX(); - edge.from = fromNode; - edge.to = toNode; - edge.relativeFrom = oldRelativeTo; - edge.relativeTo = oldRelativeFrom; + edge.setFrom(fromNode); + edge.setTo(toNode); + edge.setRelativeFromX(oldRelativeTo); + edge.setRelativeToX(oldRelativeFrom); } - fromNode.succs.add(edge); - toNode.preds.add(edge); + fromNode.addSuccessor(edge); + toNode.addPredecessor(edge); if (reversedLink) { updateReversedLinkPositions(l); } - if (fromNode.layer != toNode.layer - 1) { + if (fromNode.getLayer() != toNode.getLayer() - 1) { // Edge span multiple layers - must insert dummy nodes insertDummyNodes(edge); } @@ -1038,20 +892,20 @@ private int optimalLayer(Vertex vertex, List links) { LayoutNode fromNode = vertexToLayoutNode.get(link.getFrom().getVertex()); LayoutNode toNode = vertexToLayoutNode.get(link.getTo().getVertex()); if (link.getTo().getVertex().equals(vertex) && fromNode != null) { - if (fromNode.layer > i) { + if (fromNode.getLayer() > i) { curReversedEdges += 1; - } else if (fromNode.layer == i) { + } else if (fromNode.getLayer() == i) { curNeighborsOnSameLayer += 1; } - curTotalEdgeLength += Math.abs(fromNode.layer - i); + curTotalEdgeLength += Math.abs(fromNode.getLayer() - i); } if (link.getFrom().getVertex().equals(vertex) && toNode != null) { - if (toNode.layer < i) { + if (toNode.getLayer() < i) { curReversedEdges += 1; - } else if (toNode.layer == i) { + } else if (toNode.getLayer() == i) { curNeighborsOnSameLayer += 1; } - curTotalEdgeLength += Math.abs(i - toNode.layer); + curTotalEdgeLength += Math.abs(i - toNode.getLayer()); } } @@ -1072,12 +926,7 @@ private int optimalLayer(Vertex vertex, List links) { } private void applyAddVertexAction(VertexAction action) { - LayoutNode node = new LayoutNode(); - Dimension size = action.vertex.getSize(); - node.width = (int) size.getWidth(); - node.height = (int) size.getHeight(); - node.vertex = action.vertex; - + LayoutNode node = new LayoutNode(action.vertex); List links = new ArrayList<>(); for (LinkAction a : action.linkActions) { links.add(a.link); @@ -1087,26 +936,19 @@ private void applyAddVertexAction(VertexAction action) { // Temporarily add the links so that the node insertion accounts for edge // crossings for (Link l : links) { - LayoutEdge e = new LayoutEdge(); if (l.getTo().getVertex().equals(action.vertex) && nodes.contains(vertexToLayoutNode.get(l.getFrom().getVertex()))) { - e.to = node; - e.from = vertexToLayoutNode.get(l.getFrom().getVertex()); - e.relativeFrom = l.getFrom().getRelativePosition().x; - e.relativeTo = l.getTo().getRelativePosition().x; - node.preds.add(e); + LayoutEdge e = new LayoutEdge(vertexToLayoutNode.get(l.getFrom().getVertex()), node, l.getFrom().getRelativePosition().x, l.getTo().getRelativePosition().x, null); + node.addPredecessor(e); } else if (l.getFrom().getVertex().equals(action.vertex) && nodes.contains(vertexToLayoutNode.get(l.getTo().getVertex()))) { - e.from = node; - e.to = vertexToLayoutNode.get(l.getTo().getVertex()); - e.relativeFrom = l.getFrom().getRelativePosition().x; - e.relativeTo = l.getTo().getRelativePosition().x; - node.succs.add(e); + LayoutEdge e = new LayoutEdge(node, vertexToLayoutNode.get(l.getTo().getVertex()), l.getFrom().getRelativePosition().x, l.getTo().getRelativePosition().x, null); + node.addSuccessor(e); } } insertNode(node, layer); - node.succs.clear(); - node.preds.clear(); + node.clearSuccessors(); + node.clearPredecessors(); // Add associated edges for (LinkAction a : action.linkActions) { @@ -1122,44 +964,39 @@ private void applyRemoveLinkAction(Link l) { LayoutNode toNode = vertexToLayoutNode.get(to); LayoutNode fromNode = vertexToLayoutNode.get(from); - if (toNode.layer < fromNode.layer) { + if (toNode.getLayer() < fromNode.getLayer()) { // Reversed edge - LayoutNode temp = toNode; toNode = fromNode; - fromNode = temp; - reversedLinks.remove(l); - reversedLinkEndPoints.remove(l); - reversedLinkStartPoints.remove(l); } // Remove preds-edges bottom up, starting at "to" node // Cannot start from "from" node since there might be joint edges - List toNodePredsEdges = List.copyOf(toNode.preds); + List toNodePredsEdges = List.copyOf(toNode.getPredecessors()); for (LayoutEdge edge : toNodePredsEdges) { - LayoutNode n = edge.from; + LayoutNode n = edge.getFrom(); LayoutEdge edgeToRemove; - if (edge.link != null && edge.link.equals(l)) { - toNode.preds.remove(edge); + if (edge.getLink() != null && edge.getLink().equals(l)) { + toNode.removePredecessor(edge); edgeToRemove = edge; } else { // Wrong edge, look at next continue; } - if (n.vertex != null && n.vertex.equals(from)) { + if (n.getVertex() != null && n.getVertex().equals(from)) { // No dummy nodes inbetween 'from' and 'to' vertex - n.succs.remove(edgeToRemove); + n.removeSuccessor(edgeToRemove); break; } else { // Must remove edges between dummy nodes boolean found = true; LayoutNode prev = toNode; - while (n.vertex == null && found) { + while (n.getVertex() == null && found) { found = false; - if (n.succs.size() <= 1 && n.preds.size() <= 1) { + if (n.getSuccessors().size() <= 1 && n.getPredecessors().size() <= 1) { // Dummy node used only for this link, remove if not already removed if (nodes.contains(n)) { removeNode(n); @@ -1169,17 +1006,17 @@ private void applyRemoveLinkAction(Link l) { break; } - if (n.preds.size() == 1) { - n.succs.remove(edgeToRemove); + if (n.getPredecessors().size() == 1) { + n.removeSuccessor(edgeToRemove); prev = n; - edgeToRemove = n.preds.get(0); - n = edgeToRemove.from; + edgeToRemove = n.getPredecessors().get(0); + n = edgeToRemove.getFrom(); found = true; } } - n.succs.remove(edgeToRemove); - prev.preds.remove(edgeToRemove); + n.removeSuccessor(edgeToRemove); + prev.removePredecessor(edgeToRemove); } break; } @@ -1198,19 +1035,19 @@ private void removeNode(LayoutNode node) { if (!nodes.contains(node)) { return; } - int layer = node.layer; - int pos = node.pos; - List remainingLayerNodes = layers.get(layer); + int layer = node.getLayer(); + int pos = node.getPos(); + LayoutLayer remainingLayerNodes = layers.get(layer); assert remainingLayerNodes.contains(node); remainingLayerNodes.remove(node); // Update position of remaining nodes on the same layer boolean onlyDummiesLeft = true; for (LayoutNode n : remainingLayerNodes) { - if (n.pos > pos) { - n.pos -= 1; + if (n.getPos() > pos) { + n.setPos(n.getPos() - 1); } - if (n.vertex != null || n.preds.size() > 1) { + if (n.getVertex() != null || n.getPredecessors().size() > 1) { onlyDummiesLeft = false; } } @@ -1218,22 +1055,22 @@ private void removeNode(LayoutNode node) { if (onlyDummiesLeft && shouldRemoveEmptyLayers) { layers.remove(layer); for (int i = layer + 1; i <= layers.size(); i++) { - List list = layers.get(i); + LayoutLayer list = layers.get(i); layers.remove(i); layers.put(i - 1, list); for (LayoutNode n : list) { - n.layer -= 1; + n.setLayer(n.getLayer() - 1); } } for (LayoutNode n : remainingLayerNodes) { - if (n.preds.size() == 1) { - LayoutEdge predEdge = n.preds.get(0); - LayoutNode fromNode = predEdge.from; - fromNode.succs.remove(predEdge); - for (LayoutEdge e : n.succs) { - e.from = fromNode; - e.relativeFrom = predEdge.relativeFrom; - fromNode.succs.add(e); + if (n.getPredecessors().size() == 1) { + LayoutEdge predEdge = n.getPredecessors().get(0); + LayoutNode fromNode = predEdge.getFrom(); + fromNode.removeSuccessor(predEdge); + for (LayoutEdge e : n.getSuccessors()) { + e.setFrom(fromNode); + e.setRelativeFromX(predEdge.getRelativeFromX()); + fromNode.addSuccessor(e); } } nodes.remove(n); @@ -1292,12 +1129,12 @@ void run() { Set layoutedLinks = new HashSet<>(); Set layoutedNodes = new HashSet<>(); for (LayoutNode n : nodes) { - for (LayoutEdge e : n.preds) { - if (e.link != null) { - layoutedLinks.add(e.link); + for (LayoutEdge e : n.getPredecessors()) { + if (e.getLink() != null) { + layoutedLinks.add(e.getLink()); } } - if (n.vertex != null) { + if (n.getVertex() != null) { layoutedNodes.add(n); } } @@ -1320,135 +1157,151 @@ void run() { private class AssignYCoordinates { void run() { + int currentY = 0; + for (int i = 0; i < layers.size(); i++) { + LayoutLayer layer = layers.get(i); + layer.setTop(currentY); - // Reset all values before assigning y-coordinates - for (LayoutNode n : nodes) { - if (n.vertex != null) { - updateNodeWithReversedEdges(n); - } else { - n.height = DUMMY_HEIGHT; - } - n.y = 0; + // Calculate the maximum layer height and set it for the layer + int maxLayerHeight = layer.calculateMaxLayerHeight(); + layer.setHeight(maxLayerHeight); + + // Center nodes vertically within the layer + layer.centerNodesVertically(); + + // Update currentY to account for the padded bottom of this layer + currentY += layer.calculatePaddedHeight(); } + } + } - int curY = 0; + private class WriteResult { - for (int i = 0; i < layers.size(); i++) { - List layer = layers.get(i); - int maxHeight = 0; - int baseLine = 0; - int bottomBaseLine = 0; - for (LayoutNode n : layer) { - maxHeight = Math.max(maxHeight, n.height - n.yOffset - n.bottomYOffset); - baseLine = Math.max(baseLine, n.yOffset); - bottomBaseLine = Math.max(bottomBaseLine, n.bottomYOffset); - } + private HashMap> computeLinkPositions() { + HashMap> linkToSplitEndPoints = new HashMap<>(); + HashMap> linkPositions = new HashMap<>(); - int maxXOffset = 0; - for (LayoutNode n : layer) { - if (n.vertex == null) { - // Dummy node - n.y = curY; - n.height = maxHeight + baseLine + bottomBaseLine; + for (LayoutNode layoutNode : nodes) { + if (layoutNode.isDummy()) continue; + for (LayoutEdge predEdge : layoutNode.getPredecessors()) { + LayoutNode fromNode = predEdge.getFrom(); + LayoutNode toNode = predEdge.getTo(); + + ArrayList linkPoints = new ArrayList<>(); + // input edge stub + linkPoints.add(new Point(predEdge.getEndX(), predEdge.getEndY())); + linkPoints.add(new Point(predEdge.getEndX(), layers.get(toNode.getLayer()).getTop() - LAYER_OFFSET)); + + LayoutEdge curEdge = predEdge; + while (fromNode.isDummy() && fromNode.hasPredecessors()) { + linkPoints.add(new Point(fromNode.getCenterX(), layers.get(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); + linkPoints.add(new Point(fromNode.getCenterX(), layers.get(fromNode.getLayer()).getTop() - LAYER_OFFSET)); + curEdge = fromNode.getPredecessors().get(0); + fromNode = curEdge.getFrom(); + } + linkPoints.add(new Point(curEdge.getStartX(), layers.get(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); + // output edge stub + linkPoints.add(new Point(curEdge.getStartX(), curEdge.getStartY())); + + if (predEdge.isReversed()) { + for (Point relativeEnd : toNode.getReversedLinkEndPoints().get(predEdge.getLink())) { + Point endPoint = new Point(toNode.getLeft() + relativeEnd.x, toNode.getTop() + relativeEnd.y); + linkPoints.add(0, endPoint); + } + if (!fromNode.isDummy()) { + if (fromNode.getReversedLinkStartPoints().containsKey(predEdge.getLink())) { + for (Point relativeStart : fromNode.getReversedLinkStartPoints().get(predEdge.getLink())) { + Point startPoint = new Point(fromNode.getLeft() + relativeStart.x, fromNode.getTop() + relativeStart.y); + linkPoints.add(startPoint); + } + } + } } else { - n.y = curY + baseLine + (maxHeight - (n.height - n.yOffset - n.bottomYOffset)) / 2 - n.yOffset; + Collections.reverse(linkPoints); } - for (LayoutEdge e : n.succs) { - int curXOffset = Math.abs(n.x - e.to.x); - maxXOffset = Math.max(curXOffset, maxXOffset); + if (fromNode.isDummy()) { + if (predEdge.isReversed()) { + Collections.reverse(linkPoints); + } + linkToSplitEndPoints.put(predEdge.getLink(), linkPoints); + + } else { + linkPositions.put(predEdge.getLink(), linkPoints); } } - - curY += maxHeight + baseLine + bottomBaseLine; - curY += LAYER_OFFSET + ((int) (Math.sqrt(maxXOffset) * 1.5)); } - } - } - - private class WriteResult { - private List edgePoints(LayoutEdge e) { - ArrayList points = new ArrayList<>(); + for (LayoutNode layoutNode : nodes) { + if (layoutNode.isDummy()) continue; + for (LayoutEdge succEdge : layoutNode.getSuccessors()) { + if (succEdge.getLink() == null) continue; - Point p = new Point(e.to.x + e.relativeTo, - e.to.y + e.to.yOffset + e.link.getTo().getRelativePosition().y); - points.add(p); - if (e.to.inOffsets.containsKey(e.relativeTo)) { - points.add(new Point(p.x, - p.y + e.to.inOffsets.get(e.relativeTo) + e.link.getTo().getRelativePosition().y)); - } + LayoutNode fromNode = succEdge.getFrom(); + LayoutNode toNode = succEdge.getTo(); - LayoutNode cur = e.from; - LayoutEdge curEdge = e; - while (cur.vertex == null && !cur.preds.isEmpty()) { - 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) { - // On the same vertical line, can remove previous point - points.remove(points.size() - 1); - } - // Top of the dummy node - points.add(new Point(cur.x + cur.width / 2, cur.y + cur.height)); - 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); - } - // Bottom of the dummy node - points.add(new Point(cur.x + cur.width / 2, cur.y)); - assert cur.preds.size() == 1; - curEdge = cur.preds.get(0); - cur = curEdge.from; - } + ArrayList linkPoints = new ArrayList<>(); + linkPoints.add(new Point(succEdge.getStartX(), fromNode.getBottom())); + linkPoints.add(new Point(succEdge.getStartX(), layers.get(fromNode.getLayer()).getBottom() + LAYER_OFFSET)); - p = new Point(cur.x + curEdge.relativeFrom, cur.y + cur.height - cur.bottomYOffset - + (curEdge.link == null ? 0 : curEdge.link.getFrom().getRelativePosition().y)); - if (curEdge.from.outOffsets.containsKey(curEdge.relativeFrom)) { - points.add(new Point(p.x, p.y + curEdge.from.outOffsets.get(curEdge.relativeFrom) - + (curEdge.link == null ? 0 : curEdge.link.getFrom().getRelativePosition().y))); - } - points.add(p); + LayoutEdge curEdge = succEdge; + while (toNode.isDummy() && toNode.hasSuccessors()) { + linkPoints.add(new Point(toNode.getCenterX(), layers.get(toNode.getLayer()).getTop() - LAYER_OFFSET)); + linkPoints.add(new Point(toNode.getCenterX(), layers.get(toNode.getLayer()).getBottom() + LAYER_OFFSET)); + curEdge = toNode.getSuccessors().get(0); + toNode = curEdge.getTo(); + } + linkPoints.add(new Point(curEdge.getEndX(), layers.get(toNode.getLayer()).getTop() - LAYER_OFFSET)); + linkPoints.add(new Point(curEdge.getEndX(), toNode.getTop())); - Collections.reverse(points); + if (succEdge.isReversed()) { + Collections.reverse(linkPoints); - if (reversedLinks.contains(e.link)) { - Collections.reverse(points); + if (fromNode.getReversedLinkStartPoints().containsKey(succEdge.getLink())) { + for (Point relativeStart : fromNode.getReversedLinkStartPoints().get(succEdge.getLink())) { + Point startPoint = new Point(fromNode.getLeft() + relativeStart.x, fromNode.getTop() + relativeStart.y); + linkPoints.add(startPoint); + } + } - assert reversedLinkStartPoints.containsKey(e.link); - for (Point p1 : reversedLinkStartPoints.get(e.link)) { - points.add(new Point(p1.x + cur.x, p1.y + cur.y)); - } + if (!toNode.isDummy()) { + if (toNode.getReversedLinkEndPoints().containsKey(succEdge.getLink())) { + for (Point relativeEnd : toNode.getReversedLinkEndPoints().get(succEdge.getLink())) { + Point endPoint = new Point(toNode.getLeft() + relativeEnd.x, toNode.getTop() + relativeEnd.y); + linkPoints.add(0, endPoint); + } + } + } + } - assert reversedLinkEndPoints.containsKey(e.link); - for (Point p1 : reversedLinkEndPoints.get(e.link)) { - points.add(0, new Point(p1.x + e.to.x, p1.y + e.to.y)); + if (linkToSplitEndPoints.containsKey(succEdge.getLink())) { + if (succEdge.isReversed()) { + Collections.reverse(linkPoints); + } + linkPoints.add(null); + linkPoints.addAll(linkToSplitEndPoints.get(succEdge.getLink())); + if (succEdge.isReversed()) { + Collections.reverse(linkPoints); + } + } + linkPositions.put(succEdge.getLink(), linkPoints); } } - return points; + return linkPositions; } + void run() { HashMap vertexPositions = new HashMap<>(); - HashMap> linkPositions = new HashMap<>(); + HashMap> linkPositions = computeLinkPositions(); - for (LayoutNode n : nodes) { - if (n.vertex != null) { - assert !vertexPositions.containsKey(n.vertex); - vertexPositions.put(n.vertex, new Point(n.x + n.xOffset, n.y + n.yOffset)); - } else { - continue; - } - - // All edges can be drawn from bottom up, the links are stored in the preds list - // of each node - for (LayoutEdge e : n.preds) { - if (e.link != null && !linkPositions.containsKey(e.link)) { - List points = edgePoints(e); - assert !linkPositions.containsKey(e.link); - linkPositions.put(e.link, points); - } + for (LayoutNode n : nodes) { + if (n.getVertex() != null) { + assert !vertexPositions.containsKey(n.getVertex()); + vertexPositions.put(n.getVertex(), new Point(n.getLeft(), n.getTop())); } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java index eba424936531b..eebfa8c31e73b 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,14 @@ package com.sun.hotspot.igv.hierarchicallayout; import com.sun.hotspot.igv.layout.Link; -import java.util.Comparator; +/** + * Represents an edge in the layout graph between two nodes (LayoutNode). + * Contains information about the source and target nodes, relative positions, + * and whether the edge has been reversed (used for back edges in hierarchical layouts). + */ public class LayoutEdge { - public static final Comparator LAYOUT_EDGE_LAYER_COMPARATOR = Comparator.comparingInt(e -> e.getTo().getLayer()); - private LayoutNode from; private LayoutNode to; // Horizontal distance relative to start of 'from'. @@ -40,14 +42,14 @@ public class LayoutEdge { private Link link; private boolean isReversed; - public int getStartX() { - return relativeFromX + from.getLeft(); - } - - public int getEndX() { - return relativeToX + to.getLeft(); - } - + /** + * Constructs a LayoutEdge between two nodes with the specified link. + * The relative positions are set to zero by default. + * + * @param from The source LayoutNode. + * @param to The target LayoutNode. + * @param link The Link associated with this edge. + */ public LayoutEdge(LayoutNode from, LayoutNode to, Link link) { this.from = from; this.to = to; @@ -55,16 +57,70 @@ public LayoutEdge(LayoutNode from, LayoutNode to, Link link) { this.isReversed = false; } + /** + * Constructs a LayoutEdge between two nodes with specified relative positions and link. + * + * @param from The source LayoutNode. + * @param to The target LayoutNode. + * @param relativeFromX The horizontal distance relative to the start of 'from' node. + * @param relativeToX The horizontal distance relative to the start of 'to' node. + * @param link The Link associated with this edge. + */ public LayoutEdge(LayoutNode from, LayoutNode to, int relativeFromX, int relativeToX, Link link) { this(from, to, link); this.relativeFromX = relativeFromX; this.relativeToX = relativeToX; } + /** + * Gets the absolute x-coordinate of the starting point of the edge. + * + * @return The x-coordinate of the edge's starting point. + */ + public int getStartX() { + return relativeFromX + from.getLeft(); + } + + /** + * Gets the absolute y-coordinate of the starting point of the edge. + * + * @return The y-coordinate of the edge's starting point. + */ + public int getStartY() { + return from.getBottom(); + } + + /** + * Gets the absolute x-coordinate of the ending point of the edge. + * + * @return The x-coordinate of the edge's ending point. + */ + public int getEndX() { + return relativeToX + to.getLeft(); + } + + /** + * Gets the absolute y-coordinate of the ending point of the edge. + * + * @return The y-coordinate of the edge's ending point. + */ + public int getEndY() { + return to.getTop(); + } + + /** + * Reverses the direction of the edge. + * Marks the edge as reversed, which is used to represent back edges in hierarchical layouts. + */ public void reverse() { isReversed = !isReversed; } + /** + * Checks if the edge is reversed. + * + * @return True if the edge is reversed; false otherwise. + */ public boolean isReversed() { return isReversed; } @@ -74,45 +130,83 @@ public String toString() { return "Edge " + from + ", " + to; } + /** + * Gets the source node of the edge. + * + * @return The source LayoutNode. + */ public LayoutNode getFrom() { return from; } + /** + * Sets the source node of the edge. + * + * @param from The LayoutNode to set as the source. + */ public void setFrom(LayoutNode from) { this.from = from; } + /** + * Gets the target node of the edge. + * + * @return The target LayoutNode. + */ public LayoutNode getTo() { return to; } + /** + * Sets the target node of the edge. + * + * @param to The LayoutNode to set as the target. + */ public void setTo(LayoutNode to) { this.to = to; } - public int getFromX() { - return from.getX() + getRelativeFromX(); - } - - public int getToX() { - return to.getX() + getRelativeToX(); - } - + /** + * Gets the relative horizontal position from the source node's left boundary to the edge's starting point. + * + * @return The relative x-coordinate from the source node. + */ public int getRelativeFromX() { return relativeFromX; } + + /** + * Sets the relative horizontal position from the source node's left boundary to the edge's starting point. + * + * @param relativeFromX The relative x-coordinate to set. + */ public void setRelativeFromX(int relativeFromX) { this.relativeFromX = relativeFromX; } + /** + * Gets the relative horizontal position from the target node's left boundary to the edge's ending point. + * + * @return The relative x-coordinate to the target node. + */ public int getRelativeToX() { return relativeToX; } + /** + * Sets the relative horizontal position from the target node's left boundary to the edge's ending point. + * + * @param relativeToX The relative x-coordinate to set. + */ public void setRelativeToX(int relativeToX) { this.relativeToX = relativeToX; } + /** + * Gets the Link associated with this edge. + * + * @return The Link object. + */ public Link getLink() { return link; } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java index e6a072263a243..7ac83d39d52c2 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,7 +29,6 @@ import java.util.*; import java.util.stream.Collectors; -import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.NODE_POS_COMPARATOR; /** * @@ -43,140 +42,29 @@ public class LayoutGraph { .thenComparingInt(l -> l.getFrom().getRelativePosition().x) .thenComparingInt(l -> l.getTo().getRelativePosition().x); - private final Set links; + // Registered Graph Components: Links, Vertices, and Port Mappings + private final Set links; private final SortedSet vertices; - private final HashMap> inputPorts; - private final HashMap> outputPorts; - private final HashMap> portLinks; + private final LinkedHashMap> inputPorts; + // Layout Management: LayoutNodes and LayoutLayers + private final LinkedHashMap layoutNodes; private final List dummyNodes; - private final LinkedHashMap vertexToLayoutNode; - - private List layers; - - public LayoutGraph(Set links) { - this(links, new HashSet<>()); - } - - public void initLayers(int layerCount) { - layers = new ArrayList<>(layerCount); - for (int i = 0; i < layerCount; i++) { - layers.add(new LayoutLayer()); - } - } - - public List getDummyNodes() { - return Collections.unmodifiableList(dummyNodes); - } - - private LayoutLayer createNewLayer(int layerNr) { - LayoutLayer layer = new LayoutLayer(); - layers.add(layerNr, layer); - - // update layer field in nodes below layerNr - for (int l = layerNr + 1; l < getLayerCount(); l++) { - for (LayoutNode layoutNode : getLayer(l)) { - layoutNode.setLayer(l); - } - } - return layer; - } - - private void deleteLayer(int layerNr) { - layers.remove(layerNr); - - // Update the layer field in nodes below the deleted layer - for (int l = layerNr; l < getLayerCount(); l++) { - for (LayoutNode layoutNode : getLayer(l)) { - layoutNode.setLayer(l); - } - } - } - - - // check that NO neighbors of node are in a given layer - // otherwise insert a new layer - // return the layerNr where the node can now be safely inserted - public int insertNewLayerIfNeeded(LayoutNode node, int layerNr) { - for (Link inputLink : getInputLinks(node.getVertex())) { - if (inputLink.getFrom().getVertex() == inputLink.getTo().getVertex()) continue; - LayoutNode fromNode = getLayoutNode(inputLink.getFrom().getVertex()); - if (fromNode.getLayer() == layerNr) { - moveExpandLayerDown(layerNr + 1); - return layerNr + 1; - } - } - for (Link outputLink : getOutputLinks(node.getVertex())) { - if (outputLink.getFrom().getVertex() == outputLink.getTo().getVertex()) continue; - LayoutNode toNode = getLayoutNode(outputLink.getTo().getVertex()); - if (toNode.getLayer() == layerNr) { - moveExpandLayerDown(layerNr); - return layerNr; - } - } - return layerNr; - - } - - // inserts a new layer at layerNr - // inserts dummy nodes acoring to layerNr - 1 - // moves the layer from previous layerNr to layerNr + 1 - private void moveExpandLayerDown(int layerNr) { - LayoutLayer newLayer = createNewLayer(layerNr); - - if (layerNr == 0) return; - LayoutLayer layerAbove = getLayer(layerNr - 1); - - for (LayoutNode fromNode : layerAbove) { - int fromX = fromNode.getX(); - Map> successorsByX = fromNode.groupSuccessorsByX(); - fromNode.getSuccs().clear(); - - for (Map.Entry> entry : successorsByX.entrySet()) { - Integer relativeFromX = entry.getKey(); - List edges = entry.getValue(); - LayoutNode dummyNode = new LayoutNode(); - dummyNode.setX(fromX + relativeFromX); - dummyNode.setLayer(layerNr); - dummyNode.getSuccs().addAll(edges); - LayoutEdge dummyEdge = new LayoutEdge(fromNode, dummyNode, relativeFromX, 0, edges.get(0).getLink()); - if (edges.get(0).isReversed()) dummyEdge.reverse(); - - fromNode.getSuccs().add(dummyEdge); - dummyNode.getPreds().add(dummyEdge); - for (LayoutEdge edge : edges) { - edge.setFrom(dummyNode); - } - addNodeToLayer(dummyNode, layerNr); - } - } - - newLayer.sortNodesByXAndSetPositions(); - } - - public List getLayers() { - return Collections.unmodifiableList(layers); - } - - public int getLayerCount() { - return layers.size(); - } - - public Collection getLayoutNodes() { - return vertexToLayoutNode.values(); - } - - public LayoutNode getLayoutNode(Vertex vertex) { - return vertexToLayoutNode.get(vertex); - } + private final List layers; + /** + * Constructs a new LayoutGraph using the provided collection of links and additional vertices. + * Initializes the graph layout structure with the given links and includes any additional vertices. + * + * @param links The collection of links that represent the edges of the graph. + * @param additionalVertices The collection of additional vertices to be included in the graph. + */ public LayoutGraph(Collection links, Collection additionalVertices) { this.links = new HashSet<>(links); - vertices = new TreeSet<>(additionalVertices); - portLinks = new HashMap<>(links.size()); - inputPorts = new HashMap<>(links.size()); - outputPorts = new HashMap<>(links.size()); + LinkedHashMap> portLinks = new LinkedHashMap<>(links.size()); + inputPorts = new LinkedHashMap<>(links.size()); + LinkedHashMap> outputPorts = new LinkedHashMap<>(links.size()); for (Link link : links) { assert link.getFrom() != null; @@ -197,225 +85,203 @@ public LayoutGraph(Collection links, Collection(); + layoutNodes = new LinkedHashMap<>(); dummyNodes = new ArrayList<>(); + layers = new ArrayList<>(); + } + public void clearLayout() { + layoutNodes.clear(); + dummyNodes.clear(); + layers.clear(); + } - // Set up nodes - for (Vertex v : getVertices()) { - LayoutNode node = new LayoutNode(v); - vertexToLayoutNode.put(v, node); + /** + * Initializes or resets the layout structures by clearing existing nodes, dummy nodes, and layers. + * It then sets up the layout nodes for each vertex and creates layout edges based on the sorted links. + */ + public void initializeLayout() { + // Reset layout structures + clearLayout(); + + // Set up layout nodes for each vertex + for (Vertex vertex : getVertices()) { + createLayoutNode(vertex); } - // Set up edges + // Set up layout edges in a sorted order for reproducibility List sortedLinks = new ArrayList<>(links); sortedLinks.sort(LINK_COMPARATOR); - for (Link link : links) { + for (Link link : sortedLinks) { createLayoutEdge(link); } } - public void addNodeToLayer(LayoutNode node, int layerNumber) { - node.setLayer(layerNumber); - getLayer(layerNumber).add(node); + /** + * Initializes the layers of the graph with the specified number of empty layers. + * + * @param layerCount The number of layers to initialize. + */ + public void initLayers(int layerCount) { + layers.clear(); + for (int i = 0; i < layerCount; i++) { + layers.add(new LayoutLayer()); + } + } - // Register node in the appropriate collection based on its type - registerNode(node); + /** + * Retrieves an unmodifiable list of dummy nodes in the graph. + * + * @return An unmodifiable list containing all dummy nodes in the graph. + */ + public List getDummyNodes() { + return Collections.unmodifiableList(dummyNodes); } - private void registerNode(LayoutNode node) { - if (node.isDummy()) { - dummyNodes.add(node); - } else { - vertexToLayoutNode.put(node.getVertex(), node); - } + /** + * Retrieves a collection of all layout nodes in the graph. + * + * @return A collection containing all LayoutNodes. + */ + public Collection getLayoutNodes() { + return Collections.unmodifiableCollection(layoutNodes.values()); } + /** + * Retrieves a combined list of all nodes in the graph, + * including both layout nodes and dummy nodes. + * + * @return An unmodifiable list containing all nodes in the graph. + */ + public List getAllNodes() { + List allNodes = new ArrayList<>(); + allNodes.addAll(layoutNodes.values()); + allNodes.addAll(dummyNodes); + return Collections.unmodifiableList(allNodes); + } - public void removeNode(LayoutNode node) { - int layer = node.getLayer(); - layers.get(layer).remove(node); - layers.get(layer).updateLayerPositions(); - // Remove node from graph layout - if (node.isDummy()) { - dummyNodes.remove(node); - } else { - vertexToLayoutNode.remove(node.getVertex()); - } + /** + * Retrieves an unmodifiable list of all layers in the graph. + * + * @return An unmodifiable list containing all layers. + */ + public List getLayers() { + return Collections.unmodifiableList(layers); + } + + /** + * Returns the total number of layers in the graph. + * + * @return The number of layers. + */ + public int getLayerCount() { + return layers.size(); } + /** + * Adds a LayoutNode to the specified layer and registers it in the graph. + * + * @param node The LayoutNode to add to the layer. + * @param layerNumber The index of the layer to which the node will be added. + */ + public void addDummyToLayer(LayoutNode node, int layerNumber) { + assert node.isDummy(); + node.setLayer(layerNumber); + getLayer(layerNumber).add(node); + dummyNodes.add(node); + } + + /** + * Updates the positions of all nodes in each layer. + * Should be called after changes to node positions or layer compositions. + */ public void updatePositions() { for (LayoutLayer layer : layers) { - layer.updateLayerPositions(); + layer.updateNodeIndices(); + } + } + + // Create and register LayoutNode + public LayoutNode createLayoutNode(Vertex vertex) { + if (!vertices.contains(vertex)) { + throw new IllegalArgumentException("Vertex does not exist in the graph: " + vertex); } + LayoutNode node = new LayoutNode(vertex); + layoutNodes.put(vertex, node); + return node; } + /** + * Creates a LayoutEdge based on the given Link and connects it to the corresponding LayoutNodes. + * + * @param link The Link representing the edge in the graph. + * @return The newly created LayoutEdge. + */ public LayoutEdge createLayoutEdge(Link link) { LayoutEdge edge = new LayoutEdge( - vertexToLayoutNode.get(link.getFrom().getVertex()), - vertexToLayoutNode.get(link.getTo().getVertex()), + layoutNodes.get(link.getFrom().getVertex()), + layoutNodes.get(link.getTo().getVertex()), link.getFrom().getRelativePosition().x, link.getTo().getRelativePosition().x, link); - edge.getFrom().getSuccs().add(edge); - edge.getTo().getPreds().add(edge); + edge.getFrom().addSuccessor(edge); + edge.getTo().addPredecessor(edge); return edge; } - public Set getLinks() { + /** + * Retrieves the set of all links (edges) in the graph. + * + * @return A set containing all links in the graph. + */ + public Set getLinks() { return links; } + /** + * Retrieves the set of all vertices in the graph, sorted in natural order. + * + * @return A sorted set of all vertices in the graph. + */ public SortedSet getVertices() { return vertices; } + /** + * Checks whether the graph contains the specified vertex. + * + * @param vertex The vertex to check for presence in the graph. + * @return True if the vertex is present, false otherwise. + */ public boolean containsVertex(Vertex vertex) { return vertices.contains(vertex); } + /** + * Finds all root vertices in the graph (vertices with no incoming links). + * + * @return A set of root vertices. + */ public Set findRootVertices() { return vertices.stream() .filter(v -> inputPorts.getOrDefault(v, Collections.emptySet()).isEmpty()) .collect(Collectors.toSet()); } - public Set getInputLinks(Vertex vertex) { - Set inputLinks = new HashSet<>(); - for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { - inputLinks.addAll(portLinks.getOrDefault(inputPort, Collections.emptySet())); - } - return inputLinks; - } - - public Set getOutputLinks(Vertex vertex) { - Set outputLinks = new HashSet<>(); - for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) { - outputLinks.addAll(portLinks.getOrDefault(outputPort, Collections.emptySet())); - } - return outputLinks; - } - - private Set getAllLinks(Vertex vertex) { - Set allLinks = new HashSet<>(); - - for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { - allLinks.addAll(portLinks.getOrDefault(inputPort, Collections.emptySet())); - } - - for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) { - allLinks.addAll(portLinks.getOrDefault(outputPort, Collections.emptySet())); - } - - return allLinks; - } - - private void removeEdges(LayoutNode movedNode) { - for (Link inputLink : getAllLinks(movedNode.getVertex())) { - Vertex from = inputLink.getFrom().getVertex(); - Vertex to = inputLink.getTo().getVertex(); - LayoutNode toNode = getLayoutNode(to); - LayoutNode fromNode = getLayoutNode(from); - - if (toNode.getLayer() < fromNode.getLayer()) { - // Reversed edge - toNode = fromNode; - toNode.getReversedLinkEndPoints().remove(inputLink); - fromNode.getReversedLinkStartPoints().remove(inputLink); - } - - // Remove preds-edges bottom up, starting at "to" node - // Cannot start from "from" node since there might be joint edges - List toNodePredsEdges = List.copyOf(toNode.getPreds()); - for (LayoutEdge edge : toNodePredsEdges) { - LayoutNode predNode = edge.getFrom(); - LayoutEdge edgeToRemove; - - if (edge.getLink() != null && edge.getLink().equals(inputLink)) { - toNode.getPreds().remove(edge); - edgeToRemove = edge; - } else { - // Wrong edge, look at next - continue; - } - - if (!predNode.isDummy() && predNode.getVertex().equals(from)) { - // No dummy nodes inbetween 'from' and 'to' vertex - predNode.getSuccs().remove(edgeToRemove); - break; - } else { - // Must remove edges between dummy nodes - boolean found = true; - LayoutNode succNode = toNode; - while (predNode.isDummy() && found) { - found = false; - - if (predNode.getSuccs().size() <= 1 && predNode.getPreds().size() <= 1) { - // Dummy node used only for this link, remove if not already removed - removeNode(predNode); - } else { - // anchor node, should not be removed - break; - } - - if (predNode.getPreds().size() == 1) { - predNode.getSuccs().remove(edgeToRemove); - succNode = predNode; - edgeToRemove = predNode.getPreds().get(0); - predNode = edgeToRemove.getFrom(); - found = true; - } - } - - predNode.getSuccs().remove(edgeToRemove); - succNode.getPreds().remove(edgeToRemove); - } - break; - } - } - - // remove link connected to movedNode - for (Link link : getLinks()) { - if (link.getTo().getVertex() == movedNode.getVertex()) { - link.setControlPoints(new ArrayList<>()); - movedNode.getReversedLinkStartPoints().remove(link); - } else if (link.getFrom().getVertex() == movedNode.getVertex()) { - link.setControlPoints(new ArrayList<>()); - movedNode.getReversedLinkEndPoints().remove(link); - } - } - - movedNode.initSize(); - } - - public void removeNodeAndEdges(LayoutNode node) { - removeEdges(node); - removeNode(node); - } - - + /** + * Retrieves the LayoutLayer at the specified index. + * + * @param layerNr The index of the layer to retrieve. + * @return The LayoutLayer at the specified index. + */ public LayoutLayer getLayer(int layerNr) { return layers.get(layerNr); } - public int findLayer(int y) { - int optimalLayer = -1; - int minDistance = Integer.MAX_VALUE; - for (int l = 0; l < getLayerCount(); l++) { - // Check if y is within this layer's bounds - if (y >= getLayer(l).getTop() && y <= getLayer(l).getBottom()) { - return l; - } - - int distance = Math.abs(getLayer(l).getCenter() - y); - if (distance < minDistance) { - minDistance = distance; - optimalLayer = l; - } - } - return optimalLayer; - } - + /** + * Positions the layers vertically, calculating their heights and setting their positions. + * Centers the nodes within each layer vertically. + */ public void positionLayers() { int currentY = 0; for (LayoutLayer layer : getLayers()) { @@ -429,155 +295,124 @@ public void positionLayers() { layer.centerNodesVertically(); // Update currentY to account for the padded bottom of this layer - currentY += layer.calculateScalePaddedBottom(); - } - } - - public void optimizeBackEdgeCrossings() { - for (LayoutNode node : getLayoutNodes()) { - if (node.getReversedLinkStartPoints().isEmpty() && node.getReversedLinkEndPoints().isEmpty()) continue; - node.computeReversedLinkPoints(); - } - } - - public void removeEmptyLayers() { - int i = 0; - while (i < getLayerCount()) { - LayoutLayer layer = getLayer(i); - if (layer.isDummyLayer()) { - removeEmptyLayer(i); - } else { - i++; // Move to the next layer only if no removal occurred - } - } - } - - private void removeEmptyLayer(int layerNr) { - LayoutLayer layer = getLayer(layerNr); - if (!layer.isDummyLayer()) return; - - for (LayoutNode dummyNode : layer) { - if (dummyNode.getSuccs().isEmpty()) { - dummyNode.setLayer(layerNr + 1); - getLayer(layerNr + 1).add(dummyNode); - dummyNode.setX(dummyNode.calculateOptimalPositionDown()); - getLayer(layerNr + 1).sortNodesByXAndSetPositions(); - continue; - } else if (dummyNode.getPreds().isEmpty()) { - dummyNode.setLayer(layerNr - 1); - dummyNode.setX(dummyNode.calculateOptimalPositionUp()); - getLayer(layerNr - 1).add(dummyNode); - getLayer(layerNr - 1).sortNodesByXAndSetPositions(); - continue; - } - LayoutEdge layoutEdge = dummyNode.getPreds().get(0); - - // remove the layoutEdge - LayoutNode fromNode = layoutEdge.getFrom(); - fromNode.getSuccs().remove(layoutEdge); - - List successorEdges = dummyNode.getSuccs(); - for (LayoutEdge successorEdge : successorEdges) { - successorEdge.setRelativeFromX(layoutEdge.getRelativeFromX()); - successorEdge.setFrom(fromNode); - fromNode.getSuccs().add(successorEdge); - } - dummyNode.getPreds().clear(); - dummyNode.getSuccs().clear(); - dummyNodes.remove(dummyNode); - } - - deleteLayer(layerNr); - } - - /** - * Repositions the given LayoutNode to the specified x-coordinate within its layer, - * ensuring no overlap with adjacent nodes and maintaining a minimum NODE_OFFSET distance. - * - * @param layoutNode The LayoutNode to be repositioned. - * @param newX The desired new x-coordinate for the layoutNode. - */ - private void repositionLayoutNodeX(LayoutNode layoutNode, int newX) { - int currentX = layoutNode.getX(); - - // Early exit if the desired position is the same as the current position - if (newX == currentX) { - return; - } - - LayoutLayer layer = getLayer(layoutNode.getLayer()); - if (newX > currentX) { - layer.attemptMoveRight(layoutNode, newX); - } else { - layer.attemptMoveLeft(layoutNode, newX); + currentY += layer.calculatePaddedHeight(); } } /** - * Aligns the x-coordinate of a single dummy successor node for the given LayoutNode. - * If the node has exactly one successor and that successor is a dummy node, - * this method sets the dummy node's x-coordinate to either the node's x-coordinate - * (if the node is a dummy) or to the starting x-coordinate of the connecting edge. + * Inserts dummy nodes along the edges to successors of the specified node, + * for edges that span more than one layer. + * Can limit the maximum length of layers an edge spans using maxLayerLength. * - * @param node The LayoutNode whose single dummy successor needs to be aligned. + * @param layoutNode The node for which to create successor dummy nodes. + * @param maxLayerLength The maximum number of layers an edge can span without splitting it */ - private void alignSingleSuccessorDummyNodeX(LayoutNode node) { - // Retrieve the list of successor edges - List successors = node.getSuccs(); - - // Proceed only if there is exactly one successor - if (successors.size() != 1) { - return; - } - - LayoutEdge successorEdge = successors.get(0); - LayoutNode successorNode = successorEdge.getTo(); - - // Proceed only if the successor node is a dummy node - if (!successorNode.isDummy()) { - return; - } - - // Determine the target x-coordinate based on whether the current node is a dummy - int targetX = node.isDummy() ? node.getX() : successorEdge.getStartX(); - - // Align the successor dummy node to the target x-coordinate - repositionLayoutNodeX(successorNode, targetX); - } - - /** - * Aligns the x-coordinates of dummy successor nodes within the specified layer. - * Performs alignment in both forward and backward directions to ensure consistency. - * - * @param layer The LayoutLayer whose nodes' dummy successors need alignment. - */ - private void alignLayerDummySuccessors(LayoutLayer layer) { - // Forward pass: Align dummy successors from the first node to the last. - for (LayoutNode node : layer) { - alignSingleSuccessorDummyNodeX(node); - } - - // Backward pass: Align dummy successors from the last node to the first. - for (int i = layer.size() - 1; i >= 0; i--) { - LayoutNode node = layer.get(i); - alignSingleSuccessorDummyNodeX(node); - } - } - - /** - * Aligns the x-coordinates of dummy successor nodes across all layers. - * Performs alignment in both forward and backward directions for comprehensive coverage. - */ - public void straightenEdges() { - // Forward pass: Align dummy successors from the first layer to the last. - for (int i = 0; i < getLayerCount(); i++) { - alignLayerDummySuccessors(getLayer(i)); + public void createDummiesForNodeSuccessor(LayoutNode layoutNode, int maxLayerLength) { + LinkedHashMap> portsToUnprocessedEdges = new LinkedHashMap<>(); + ArrayList succs = new ArrayList<>(layoutNode.getSuccessors()); + LinkedHashMap portToTopNode = new LinkedHashMap<>(); + LinkedHashMap> portToBottomNodeMapping = new LinkedHashMap<>(); + for (LayoutEdge succEdge : succs) { + int startPort = succEdge.getRelativeFromX(); + LayoutNode fromNode = succEdge.getFrom(); + LayoutNode toNode = succEdge.getTo(); + + // edge is longer than one layer => needs dummy nodes + if (fromNode.getLayer() != toNode.getLayer() - 1) { + // the edge needs to be cut + if (maxLayerLength != -1 && toNode.getLayer() - fromNode.getLayer() > maxLayerLength) { + // remove the succEdge before replacing it + toNode.removePredecessor(succEdge); + fromNode.removeSuccessor(succEdge); + + LayoutNode topCutNode = portToTopNode.get(startPort); + if (topCutNode == null) { + topCutNode = new LayoutNode(); + topCutNode.setLayer(fromNode.getLayer() + 1); + addDummyToLayer(topCutNode, topCutNode.getLayer()); + portToTopNode.put(startPort, topCutNode); + portToBottomNodeMapping.put(startPort, new LinkedHashMap<>()); + } + LayoutEdge edgeToTopCut = new LayoutEdge(fromNode, topCutNode, succEdge.getRelativeFromX(), topCutNode.getWidth() / 2, succEdge.getLink()); + if (succEdge.isReversed()) edgeToTopCut.reverse(); + fromNode.addSuccessor(edgeToTopCut); + topCutNode.addPredecessor(edgeToTopCut); + + LinkedHashMap layerToBottomNode = portToBottomNodeMapping.get(startPort); + LayoutNode bottomCutNode = layerToBottomNode.get(toNode.getLayer()); + if (bottomCutNode == null) { + bottomCutNode = new LayoutNode(); + bottomCutNode.setLayer(toNode.getLayer() - 1); + addDummyToLayer(bottomCutNode, bottomCutNode.getLayer()); + layerToBottomNode.put(toNode.getLayer(), bottomCutNode); + } + LayoutEdge bottomEdge = new LayoutEdge(bottomCutNode, toNode, bottomCutNode.getWidth() / 2, succEdge.getRelativeToX(), succEdge.getLink()); + if (succEdge.isReversed()) bottomEdge.reverse(); + toNode.addPredecessor(bottomEdge); + bottomCutNode.addSuccessor(bottomEdge); + + } else { // the edge is not cut, but needs dummy nodes + portsToUnprocessedEdges.putIfAbsent(startPort, new ArrayList<>()); + portsToUnprocessedEdges.get(startPort).add(succEdge); + } + } } - // Backward pass: Align dummy successors from the last layer to the first. - for (int i = getLayerCount() - 1; i >= 0; i--) { - alignLayerDummySuccessors(getLayer(i)); + for (Map.Entry> portToUnprocessedEdges : portsToUnprocessedEdges.entrySet()) { + Integer startPort = portToUnprocessedEdges.getKey(); + List unprocessedEdges = portToUnprocessedEdges.getValue(); + unprocessedEdges.sort(Comparator.comparingInt(e -> e.getTo().getLayer())); + + if (unprocessedEdges.size() == 1) { + // process a single edge + LayoutEdge singleEdge = unprocessedEdges.get(0); + LayoutNode fromNode = singleEdge.getFrom(); + if (singleEdge.getTo().getLayer() > fromNode.getLayer() + 1) { + LayoutEdge previousEdge = singleEdge; + for (int i = fromNode.getLayer() + 1; i < previousEdge.getTo().getLayer(); i++) { + LayoutNode dummyNode = new LayoutNode(); + dummyNode.setLayer(i); + dummyNode.addPredecessor(previousEdge); + addDummyToLayer(dummyNode, dummyNode.getLayer()); + LayoutEdge dummyEdge = new LayoutEdge(dummyNode, previousEdge.getTo(), dummyNode.getWidth() / 2, previousEdge.getRelativeToX(), singleEdge.getLink()); + if (previousEdge.isReversed()) dummyEdge.reverse(); + dummyNode.addSuccessor(dummyEdge); + previousEdge.setRelativeToX(dummyNode.getWidth() / 2); + previousEdge.getTo().removePredecessor(previousEdge); + previousEdge.getTo().addPredecessor(dummyEdge); + previousEdge.setTo(dummyNode); + previousEdge = dummyEdge; + } + } + } else { + int lastLayer = unprocessedEdges.get(unprocessedEdges.size() - 1).getTo().getLayer(); + int dummyCnt = lastLayer - layoutNode.getLayer() - 1; + LayoutEdge[] newDummyEdges = new LayoutEdge[dummyCnt]; + LayoutNode[] newDummyNodes = new LayoutNode[dummyCnt]; + + newDummyNodes[0] = new LayoutNode(); + newDummyNodes[0].setLayer(layoutNode.getLayer() + 1); + newDummyEdges[0] = new LayoutEdge(layoutNode, newDummyNodes[0], startPort, newDummyNodes[0].getWidth() / 2, null); + newDummyNodes[0].addPredecessor(newDummyEdges[0]); + layoutNode.addSuccessor(newDummyEdges[0]); + for (int j = 1; j < dummyCnt; j++) { + newDummyNodes[j] = new LayoutNode(); + newDummyNodes[j].setLayer(layoutNode.getLayer() + j + 1); + newDummyEdges[j] = new LayoutEdge(newDummyNodes[j - 1], newDummyNodes[j], null); + newDummyNodes[j].addPredecessor(newDummyEdges[j]); + newDummyNodes[j - 1].addSuccessor(newDummyEdges[j]); + } + for (LayoutEdge unprocessedEdge : unprocessedEdges) { + LayoutNode anchorNode = newDummyNodes[unprocessedEdge.getTo().getLayer() - layoutNode.getLayer() - 2]; + anchorNode.addSuccessor(unprocessedEdge); + unprocessedEdge.setFrom(anchorNode); + unprocessedEdge.setRelativeFromX(anchorNode.getWidth() / 2); + layoutNode.removeSuccessor(unprocessedEdge); + } + for (LayoutNode dummyNode : newDummyNodes) { + addDummyToLayer(dummyNode, dummyNode.getLayer()); + } + } } } - } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java index dc530dd31d7e5..6f477b63f2df9 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,46 +28,82 @@ import java.util.ArrayList; import java.util.Collection; +/** + * Represents a layer in a hierarchical graph layout. + * Each LayoutLayer contains a collection of LayoutNodes positioned at the same vertical level. + * Provides methods to manage the nodes within the layer, including positioning, sorting, + * and adjusting the layout to minimize overlaps and improve visual clarity. + */ public class LayoutLayer extends ArrayList { private int height = 0; private int y = 0; + /** + * Adds all LayoutNodes from the specified collection to this layer. + * Updates the layer's height based on the nodes added. + * + * @param c The collection of LayoutNodes to be added. + * @return true if this layer changed as a result of the call. + */ @Override public boolean addAll(Collection c) { - c.forEach(this::updateHeight); + c.forEach(this::updateLayerHeight); return super.addAll(c); } - private void updateHeight(LayoutNode n) { - height = Math.max(height, n.getOuterHeight()); - } - + /** + * Adds a single LayoutNode to this layer. + * Updates the layer's height based on the node added. + * + * @param n The LayoutNode to be added. + * @return true if the node was added successfully. + */ @Override public boolean add(LayoutNode n) { - updateHeight(n); + updateLayerHeight(n); return super.add(n); } + /** + * Updates the layer's height if the outer height of the given node exceeds the current height. + * + * @param n The LayoutNode whose height is to be considered. + */ + private void updateLayerHeight(LayoutNode n) { + height = Math.max(height, n.getOuterHeight()); + } + + /** + * Calculates and returns the maximum height among the nodes in this layer, including their margins. + * Adjusts the top and bottom margins of non-dummy nodes to be equal, effectively centering them vertically. + * + * @return The maximum outer height of nodes in this layer. + */ public int calculateMaxLayerHeight() { int maxLayerHeight = 0; for (LayoutNode layoutNode : this) { if (!layoutNode.isDummy()) { // Center the node by setting equal top and bottom margins - int offset = Math.max(layoutNode.getTopMargin(), layoutNode.getBottomMargin()); - layoutNode.setTopMargin(offset); - layoutNode.setBottomMargin(offset); + layoutNode.centerNode(); } maxLayerHeight = Math.max(maxLayerHeight, layoutNode.getOuterHeight()); } return maxLayerHeight; } - public int calculateScalePaddedBottom() { + /** + * Calculates and returns the total height of this layer, including additional padding + * based on the maximum horizontal offset among the edges of its nodes. + * This padding helps in scaling the layer vertically to accommodate edge bends and crossings. + * + * @return The total padded height of the layer. + */ + public int calculatePaddedHeight() { int maxXOffset = 0; for (LayoutNode layoutNode : this) { - for (LayoutEdge succEdge : layoutNode.getSuccs()) { + for (LayoutEdge succEdge : layoutNode.getSuccessors()) { maxXOffset = Math.max(Math.abs(succEdge.getStartX() - succEdge.getEndX()), maxXOffset); } } @@ -77,6 +113,10 @@ public int calculateScalePaddedBottom() { return scalePaddedBottom; } + /** + * Centers all nodes in this layer vertically within the layer's assigned space. + * Adjusts each node's Y-coordinate so that it is centered based on the layer's top and height. + */ public void centerNodesVertically() { for (LayoutNode layoutNode : this) { int centeredY = getTop() + (getHeight() - layoutNode.getOuterHeight()) / 2; @@ -84,111 +124,81 @@ public void centerNodesVertically() { } } - public void setTop(int top) { - y = top; - } - - public void shiftTop(int shift) { + /** + * Shifts the top Y-coordinate of this layer by the specified amount. + * Useful for moving the entire layer up or down. + * + * @param shift The amount to shift the layer's top position. Positive values move it down. + */ + public void moveLayerVertically(int shift) { y += shift; } + /** + * Gets the top Y-coordinate of this layer. + * + * @return The Y-coordinate representing the top of the layer. + */ public int getTop() { return y; } - public int getCenter() { - return y + height / 2; + /** + * Sets the top Y-coordinate of this layer. + * + * @param top The Y-coordinate representing the top of the layer. + */ + public void setTop(int top) { + y = top; } + /** + * Gets the bottom Y-coordinate of this layer. + * + * @return The Y-coordinate representing the bottom of the layer. + */ public int getBottom() { return y + height; } + /** + * Gets the height of this layer. + * + * @return The height of the layer. + */ public int getHeight() { return height; } + /** + * Sets the height of this layer. + * + * @param height The height to set for the layer. + */ public void setHeight(int height) { this.height = height; } - // Layer contains no non-dummy nodes - public boolean isDummyLayer() { + /** + * Initializes nodes' X positions with spacing. + */ + public void initXPositions() { + int curX = 0; for (LayoutNode node : this) { - if (!node.isDummy()) { - return false; - } + node.setX(curX); + curX += node.getOuterWidth() + NODE_OFFSET; } - return true; } - public void sortNodesByXAndSetPositions() { - if (this.isEmpty()) return; - - // Sort nodes in the layer increasingly by x - this.sort(NODE_X_COMPARATOR); - - int pos = 0; - int minX = this.get(0).getX(); // Starting X position for the first node - - for (LayoutNode node : this) { - node.setPos(pos); - pos++; - - // Set the X position of the node to at least minX, ensuring spacing - int x = Math.max(node.getX(), minX); - node.setX(x); - - // Update minX for the next node based on the current node's outer width and offset - minX = x + node.getOuterWidth() + NODE_OFFSET; - } - } - - public void updateLayerPositions() { + /** + * Updates the position indices of the nodes in this layer based on their order in the list. + * Useful after nodes have been added or removed to ensure position indices are consistent. + */ + public void updateNodeIndices() { int pos = 0; for (LayoutNode layoutNode : this) { layoutNode.setPos(pos); pos++; } } - - public void attemptMoveRight(LayoutNode layoutNode, int newX) { - int currentX = layoutNode.getX(); - int shiftAmount = newX - currentX; - int rightPos = layoutNode.getPos() + 1; - - if (rightPos < size()) { - // There is a right neighbor - LayoutNode rightNeighbor = get(rightPos); - int proposedRightEdge = layoutNode.getRight() + shiftAmount; - int requiredLeftEdge = rightNeighbor.getOuterLeft() - NODE_OFFSET; - - if (proposedRightEdge <= requiredLeftEdge) { - layoutNode.setX(newX); - } - } else { - // No right neighbor; safe to move freely to the right - layoutNode.setX(newX); - } - } - - public void attemptMoveLeft(LayoutNode layoutNode, int newX) { - int currentX = layoutNode.getX(); - int shiftAmount = currentX - newX; - int leftPos = layoutNode.getPos() - 1; - - if (leftPos >= 0) { - // There is a left neighbor - LayoutNode leftNeighbor = get(leftPos); - int proposedLeftEdge = layoutNode.getLeft() - shiftAmount; - int requiredRightEdge = leftNeighbor.getOuterRight() + NODE_OFFSET; - - if (requiredRightEdge <= proposedLeftEdge) { - layoutNode.setX(newX); - } - } else { - // No left neighbor; safe to move freely to the left - layoutNode.setX(newX); - } - } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutManager.java index be88f567eb6e1..a7ad1a3a6f12b 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,12 +31,6 @@ */ public abstract class LayoutManager { - void setCutEdges(boolean enable); - - void doLayout(LayoutGraph graph); - - public abstract void setCutEdges(boolean enable); - public static final Font TITLE_FONT = new Font("Arial", Font.BOLD, 14); public static final int SWEEP_ITERATIONS = 1; public static final int CROSSING_ITERATIONS = 1; @@ -44,5 +38,7 @@ public abstract class LayoutManager { public static final int LAYER_OFFSET = 8; public static final double SCALE_LAYER_PADDING = 1.5; + public abstract void setCutEdges(boolean enable); + public abstract void doLayout(LayoutGraph graph); } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java index 931ef1f2024ea..639ca7af6778f 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,30 +23,37 @@ */ package com.sun.hotspot.igv.hierarchicallayout; +import static com.sun.hotspot.igv.hierarchicallayout.LayoutManager.NODE_OFFSET; import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Vertex; import java.awt.Dimension; import java.awt.Point; import java.util.*; -import static com.sun.hotspot.igv.hierarchicallayout.LayoutManager.NODE_OFFSET; - +/** + * Represents a node in a hierarchical graph layout. + * A LayoutNode can be either an actual vertex from the graph or a dummy node inserted during the layout process. + * It stores layout-related properties such as position, size, margins, and connections to predecessor and successor nodes. + */ public class LayoutNode { + // Comparator constants for sorting LayoutNodes in various ways public static final Comparator LAYOUT_NODE_DEGREE_COMPARATOR = Comparator.comparingInt(LayoutNode::getDegree); public static final Comparator NODE_POS_COMPARATOR = Comparator.comparingInt(LayoutNode::getPos); public static final Comparator NODE_X_COMPARATOR = Comparator.comparingInt(LayoutNode::getX); - public static final Comparator CROSSING_NODE_COMPARATOR = Comparator.comparingDouble(LayoutNode::getWeightedPosition); - public static final Comparator DUMMY_NODES_FIRST = Comparator.comparing(LayoutNode::isDummy).reversed(); - public static final Comparator NODE_PROCESSING_DOWN_COMPARATOR = DUMMY_NODES_FIRST.thenComparingInt(LayoutNode::getOutDegree); - public static final Comparator NODE_PROCESSING_UP_COMPARATOR = DUMMY_NODES_FIRST.thenComparing(LayoutNode::getInDegree); - public static final Comparator DUMMY_NODES_THEN_OPTIMAL_X = DUMMY_NODES_FIRST.thenComparing(LayoutNode::getOptimalX); + public static final Comparator NODE_CROSSING_COMPARATOR = Comparator.comparingInt(LayoutNode::getCrossingNumber); + + // Default dimensions for dummy nodes public static final int DUMMY_HEIGHT = 1; public static final int DUMMY_WIDTH = 1; - + private Vertex vertex; // Associated graph vertex; null for dummy nodes + private final List preds = new ArrayList<>(); // Incoming edges + private final List succs = new ArrayList<>(); // Outgoing edges + private final HashMap> reversedLinkStartPoints = new HashMap<>(); // Start points of reversed edges + private final HashMap> reversedLinkEndPoints = new HashMap<>(); // End points of reversed edges + // Layout properties private int layer = -1; - private int optimal_x; private int x; private int y; private int width; @@ -55,22 +62,33 @@ public class LayoutNode { private int bottomMargin; private int rightMargin; private int leftMargin; + private int pos = -1; // Position within its layer + private int crossingNumber = 0; - private final Vertex vertex; // Only used for non-dummy nodes, otherwise null - - private final List preds = new ArrayList<>(); - private final List succs = new ArrayList<>(); - private final HashMap> reversedLinkStartPoints = new HashMap<>(); - private final HashMap> reversedLinkEndPoints = new HashMap<>(); - private int pos = -1; // Position within layer - - private float weightedPosition = 0; + /** + * Constructs a LayoutNode associated with the given Vertex. + * Initializes the node's size based on the vertex's dimensions. + * + * @param v The Vertex associated with this LayoutNode. If null, the node is a dummy node. + */ public LayoutNode(Vertex v) { vertex = v; initSize(); } + /** + * Constructs a dummy LayoutNode + */ + public LayoutNode() { + this(null); + } + + /** + * Initializes the size and margins of the node. + * If the node represents a real vertex, it uses the vertex's size. + * Dummy nodes use default dimensions. + */ public void initSize() { if (vertex == null) { height = DUMMY_HEIGHT; @@ -80,124 +98,198 @@ public void initSize() { height = size.height; width = size.width; } - setTopMargin(0); - setBottomMargin(0); - setLeftMargin(0); - setRightMargin(0); + topMargin = 0; + bottomMargin = 0; + leftMargin = 0; + rightMargin = 0; + } + + public int getCrossingNumber() { + return crossingNumber; + } + + public void setCrossingNumber(int crossingNumber) { + this.crossingNumber = crossingNumber; } - public int calculateOptimalPositionDown() { + public int calculateOptimalXFromPredecessors(boolean useMedian) { int numPreds = preds.size(); + + // If there are no predecessors, retain the current x position if (numPreds == 0) { return getX(); } + // Collect the x positions from all predecessor edges List positions = new ArrayList<>(numPreds); for (LayoutEdge edge : preds) { positions.add(edge.getStartX() - edge.getRelativeToX()); } - Collections.sort(positions); - int midIndex = numPreds / 2; - return (numPreds % 2 == 0) - ? (positions.get(midIndex - 1) + positions.get(midIndex)) / 2 - : positions.get(midIndex); + if (useMedian) { + // Calculate the median position + Collections.sort(positions); + int midIndex = numPreds / 2; + + if (numPreds % 2 == 0) { + // Even number of predecessors: average the two middle values + return (positions.get(midIndex - 1) + positions.get(midIndex)) / 2; + } else { + // Odd number of predecessors: take the middle value + return positions.get(midIndex); + } + } else { + // Calculate the average position + long sum = 0; + for (int pos : positions) { + sum += pos; + } + // Integer division is used; adjust as needed for rounding + return (int) (sum / numPreds); + } } - public int calculateOptimalPositionUp() { + + public int calculateOptimalXFromSuccessors(boolean useMedian) { int numSuccs = succs.size(); + + // If there are no successors, retain the current x position if (numSuccs == 0) { return getX(); } + // Collect the x positions from all successor edges List positions = new ArrayList<>(numSuccs); for (LayoutEdge edge : succs) { positions.add(edge.getEndX() - edge.getRelativeFromX()); } - Collections.sort(positions); - int midIndex = numSuccs / 2; - return (numSuccs % 2 == 0) - ? (positions.get(midIndex - 1) + positions.get(midIndex)) / 2 - : positions.get(midIndex); - } - - public LayoutNode() { - this(null); + if (useMedian) { + // Calculate the median position + Collections.sort(positions); + int midIndex = numSuccs / 2; + + if (numSuccs % 2 == 0) { + // Even number of successors: average the two middle values + return (positions.get(midIndex - 1) + positions.get(midIndex)) / 2; + } else { + // Odd number of successors: take the middle value + return positions.get(midIndex); + } + } else { + // Calculate the average position + long sum = 0; + for (int pos : positions) { + sum += pos; + } + // Integer division is used; adjust as needed for rounding + return (int) (sum / numSuccs); + } } + /** + * Calculates the node's out-degree (number of outgoing edges). + * + * @return The out-degree of the node. + */ public int getOutDegree() { return succs.size(); } + /** + * Calculates the node's in-degree (number of incoming edges). + * + * @return The in-degree of the node. + */ public int getInDegree() { return preds.size(); } + /** + * Calculates the total degree of the node (sum of in-degree and out-degree). + * + * @return The total degree of the node. + */ public int getDegree() { return preds.size() + succs.size(); } - public float averagePosition() { - float totalWeightedPosition = 0; - float totalWeight = 0; - - for (LayoutEdge predEdge : preds) { - LayoutNode predNode = predEdge.getFrom(); - int weight = predNode.getDegree(); - totalWeightedPosition += weight * predEdge.getStartX(); - totalWeight += weight; - } - for (LayoutEdge succEdge : succs) { - LayoutNode succNode = succEdge.getTo(); - int weight = succNode.getDegree(); - totalWeightedPosition += weight * succEdge.getEndX(); - totalWeight += weight; - } - - // Calculate the (weighted) average position for the node based on neighbor positions and weights (degree) - return totalWeight > 0 ? totalWeightedPosition / totalWeight : 0; - } - + /** + * Gets the left boundary (excluding left margin) of the node. + * + * @return The x-coordinate of the left boundary. + */ public int getLeft() { return x + leftMargin; } - public int getOuterLeft() { - return x; - } - + /** + * Gets the total width of the node, including left and right margins. + * + * @return The total outer width. + */ public int getOuterWidth() { return leftMargin + width + rightMargin; } + /** + * Gets the total height of the node, including top and bottom margins. + * + * @return The total outer height. + */ public int getOuterHeight() { return topMargin + height + bottomMargin; } - public int getRight() { - return x + leftMargin + width; + public int getHeight() { + return height; } + /** + * Gets the outer right boundary (including right margin) of the node. + * + * @return The x-coordinate of the outer right boundary. + */ public int getOuterRight() { return x + leftMargin + width + rightMargin; } + /** + * Gets the horizontal center point of the node. + * + * @return The x-coordinate of the center. + */ public int getCenterX() { return x + leftMargin + (width / 2); } + /** + * Gets the top boundary (excluding top margin) of the node. + * + * @return The y-coordinate of the top boundary. + */ public int getTop() { return y + topMargin; } + /** + * Gets the bottom boundary (excluding bottom margin) of the node. + * + * @return The y-coordinate of the bottom boundary. + */ public int getBottom() { return y + topMargin + height; } + /** + * Checks if the node is a dummy node. + * + * @return True if the node is a dummy node; false otherwise. + */ public boolean isDummy() { return vertex == null; } + @Override public String toString() { if (vertex != null) { @@ -207,14 +299,6 @@ public String toString() { } } - public int getOptimalX() { - return optimal_x; - } - - public void setOptimalX(int optimal_x) { - this.optimal_x = optimal_x; - } - public int getX() { return x; } @@ -235,18 +319,6 @@ public int getWidth() { return width; } - public void setWidth(int width) { - this.width = width; - } - - public int getHeight() { - return height; - } - - public void setHeight(int height) { - this.height = height; - } - public int getLayer() { return layer; } @@ -255,72 +327,78 @@ public void setLayer(int layer) { this.layer = layer; } - public int getLeftMargin() { - return leftMargin; + /** + * Centers the node by setting equal top and bottom margins. + * The larger of the two margins is applied to both. + */ + public void centerNode() { + int offset = Math.max(topMargin, bottomMargin); + topMargin = offset; + bottomMargin = offset; } - public void setLeftMargin(int leftMargin) { - this.leftMargin = leftMargin; + public Vertex getVertex() { + return vertex; } - public int getTopMargin() { - return topMargin; + public void setVertex(Vertex vertex) { + this.vertex = vertex; } - public void setTopMargin(int topMargin) { - this.topMargin = topMargin; + public boolean hasPredecessors() { + return !preds.isEmpty(); } - public int getRightMargin() { - return rightMargin; + public boolean hasSuccessors() { + return !succs.isEmpty(); } - public void setRightMargin(int rightMargin) { - this.rightMargin = rightMargin; + public void clearSuccessors() { + succs.clear(); } - public int getBottomMargin() { - return bottomMargin; + public void clearPredecessors() { + preds.clear(); } - public void setBottomMargin(int bottomMargin) { - this.bottomMargin = bottomMargin; + public List getSuccessors() { + return Collections.unmodifiableList(succs); } - public Vertex getVertex() { - return vertex; + public List getSuccessorsRaw() { + return succs; } - public List getPreds() { + public List getPredecessors() { + return Collections.unmodifiableList(preds); + } + + public List getPredecessorsRaw() { return preds; } - public boolean hasPreds() { - return !preds.isEmpty(); + public void addSuccessor(LayoutEdge successor) { + succs.add(successor); } - public boolean hasSuccs() { - return !succs.isEmpty(); + public void removeSuccessor(LayoutEdge successor) { + succs.remove(successor); } - public List getSuccs() { - return succs; + public void addPredecessor(LayoutEdge predecessor) { + preds.add(predecessor); } - public Map> groupSuccessorsByX() { - Map> result = new HashMap<>(); - for (LayoutEdge succEdge : succs) { - result.computeIfAbsent(succEdge.getRelativeFromX(), k -> new ArrayList<>()).add(succEdge); - } - return result; + public void removePredecessor(LayoutEdge predecessor) { + preds.remove(predecessor); } - public HashMap> getReversedLinkStartPoints() { - return reversedLinkStartPoints; + public Map> getReversedLinkStartPoints() { + return Collections.unmodifiableMap(reversedLinkStartPoints); } - public HashMap> getReversedLinkEndPoints() { - return reversedLinkEndPoints; + public Map> getReversedLinkEndPoints() { + return Collections.unmodifiableMap(reversedLinkEndPoints); } public int getPos() { @@ -331,17 +409,9 @@ public void setPos(int pos) { this.pos = pos; } - public float getWeightedPosition() { - return weightedPosition; - } - - public void setWeightedPosition(float weightedPosition) { - this.weightedPosition = weightedPosition; - } - - private void computeReversedStartPoints() { - TreeMap> sortedDownMap = new TreeMap<>(Collections.reverseOrder()); - for (LayoutEdge succEdge : getSuccs()) { + private boolean computeReversedStartPoints(boolean left) { + TreeMap> sortedDownMap = left ? new TreeMap<>() : new TreeMap<>(Collections.reverseOrder()); + for (LayoutEdge succEdge : succs) { if (succEdge.isReversed()) { succEdge.setRelativeFromX(succEdge.getLink().getTo().getRelativePosition().x); sortedDownMap.putIfAbsent(succEdge.getRelativeFromX(), new ArrayList<>()); @@ -350,16 +420,17 @@ private void computeReversedStartPoints() { } int offset = NODE_OFFSET + LayoutNode.DUMMY_WIDTH; - int currentX = getWidth(); + int offsetX = left ? -offset : offset; + int currentX = left ? 0 : width; int startY = 0; int currentY = 0; for (Map.Entry> entry : sortedDownMap.entrySet()) { int startX = entry.getKey(); ArrayList reversedSuccs = entry.getValue(); - currentX += offset; + currentX += offsetX; currentY -= offset; - setTopMargin(getTopMargin() + offset); + topMargin += offset; ArrayList startPoints = new ArrayList<>(); startPoints.add(new Point(currentX, currentY)); @@ -367,16 +438,20 @@ private void computeReversedStartPoints() { startPoints.add(new Point(startX, startY)); for (LayoutEdge revEdge : reversedSuccs) { revEdge.setRelativeFromX(currentX); - getReversedLinkStartPoints().put(revEdge.getLink(), startPoints); + reversedLinkStartPoints.put(revEdge.getLink(), startPoints); } } - setLeftMargin(getLeftMargin()); - setRightMargin(getRightMargin() + (sortedDownMap.size() * offset)); + if (left) { + leftMargin += sortedDownMap.size() * offset; + } else { + rightMargin += sortedDownMap.size() * offset; + } + return !sortedDownMap.isEmpty(); } - private void computeReversedEndPoints() { - TreeMap> sortedUpMap = new TreeMap<>(Collections.reverseOrder()); - for (LayoutEdge predEdge : getPreds()) { + private boolean computeReversedEndPoints(boolean left) { + TreeMap> sortedUpMap = left ? new TreeMap<>() : new TreeMap<>(Collections.reverseOrder()); + for (LayoutEdge predEdge : preds) { if (predEdge.isReversed()) { predEdge.setRelativeToX(predEdge.getLink().getFrom().getRelativePosition().x); sortedUpMap.putIfAbsent(predEdge.getRelativeToX(), new ArrayList<>()); @@ -385,16 +460,17 @@ private void computeReversedEndPoints() { } int offset = NODE_OFFSET + LayoutNode.DUMMY_WIDTH; - int currentX = getWidth(); - int startY = getHeight(); - int currentY = getHeight(); + int offsetX = left ? -offset : offset; + int currentX = left ? 0 : getWidth(); + int startY = height; + int currentY = height; for (Map.Entry> entry : sortedUpMap.entrySet()) { int startX = entry.getKey(); ArrayList reversedPreds = entry.getValue(); - currentX += offset; + currentX += offsetX; currentY += offset; - setBottomMargin(getBottomMargin() + offset); + bottomMargin += offset; ArrayList endPoints = new ArrayList<>(); endPoints.add(new Point(currentX, currentY)); @@ -402,19 +478,24 @@ private void computeReversedEndPoints() { endPoints.add(new Point(startX, startY)); for (LayoutEdge revEdge : reversedPreds) { revEdge.setRelativeToX(currentX); - getReversedLinkEndPoints().put(revEdge.getLink(), endPoints); + reversedLinkEndPoints.put(revEdge.getLink(), endPoints); } } - setLeftMargin(getLeftMargin()); - setRightMargin(getRightMargin() + (sortedUpMap.size() * offset)); + if (left) { + leftMargin += sortedUpMap.size() * offset; + } else { + rightMargin += sortedUpMap.size() * offset; + } + + return !sortedUpMap.isEmpty(); } - public void computeReversedLinkPoints() { + public void computeReversedLinkPoints(boolean reverseLeft) { initSize(); - getReversedLinkStartPoints().clear(); - getReversedLinkEndPoints().clear(); + reversedLinkStartPoints.clear(); + reversedLinkEndPoints.clear(); - computeReversedStartPoints(); - computeReversedEndPoints(); + boolean hasReversedDown = computeReversedStartPoints(reverseLeft); + computeReversedEndPoints(hasReversedDown != reverseLeft); } } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java index 1c2b9383b02b3..0a0146cba8e4b 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java @@ -45,6 +45,7 @@ import javax.swing.*; import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS; import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS; +import javax.swing.border.Border; import javax.swing.event.UndoableEditEvent; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.CannotRedoException; @@ -83,7 +84,10 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl private final DiagramViewModel model; private ModelState modelState; private boolean rebuilding; + private final HierarchicalStableLayoutManager hierarchicalStableLayoutManager; + private HierarchicalLayoutManager seaLayoutManager; + /** * The alpha level of partially visible figures. @@ -93,7 +97,7 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl /** * The offset of the graph to the border of the window showing it. */ - public static final int BORDER_SIZE = 100; + public static final int BORDER_SIZE = 50; public static final int UNDOREDO_LIMIT = 100; public static final int SCROLL_UNIT_INCREMENT = 80; public static final int SCROLL_BLOCK_INCREMENT = 400; @@ -323,19 +327,20 @@ public void select(Widget widget, Point localLocation, boolean invertSelection) getActions().addAction(selectAction); + Border emptyBorder = BorderFactory.createEmptyBorder(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE); + blockLayer = new LayerWidget(this); + blockLayer.setBorder(emptyBorder); addChild(blockLayer); connectionLayer = new LayerWidget(this); - LayerWidget paddedLayer = new LayerWidget(this); - paddedLayer.setBorder(BorderFactory.createEmptyBorder(50, 50, 50, 50)); // Adds 50px padding on all sides - paddedLayer.addChild(connectionLayer); - addChild(paddedLayer); + connectionLayer.setBorder(emptyBorder); + addChild(connectionLayer); mainLayer = new LayerWidget(this); + mainLayer.setBorder(emptyBorder); addChild(mainLayer); - setBorder(BorderFactory.createLineBorder(Color.white, BORDER_SIZE)); setLayout(LayoutFactory.createAbsoluteLayout()); getActions().addAction(mouseZoomAction); getActions().addAction(ActionFactory.createPopupMenuAction((widget, localLocation) -> createPopupMenu())); @@ -497,6 +502,7 @@ public void ancestorResized(HierarchyEvent e) { }); hierarchicalStableLayoutManager = new HierarchicalStableLayoutManager(); + seaLayoutManager = new HierarchicalLayoutManager(); this.model = model; modelState = new ModelState(model); @@ -665,7 +671,7 @@ private void relayout() { } else if (getModel().getShowSea()) { doSeaLayout(visibleFigures, visibleConnections); } else if (getModel().getShowBlocks()) { - doClusteredLayout(visibleConnections); + doClusteredLayout(visibleFigures, visibleConnections); } else if (getModel().getShowCFG()) { doCFGLayout(visibleFigures, visibleConnections); } @@ -731,20 +737,26 @@ private boolean isVisibleFigureConnection(FigureConnection figureConnection) { } private void doStableSeaLayout(Set
visibleFigures, Set visibleConnections) { - hierarchicalStableLayoutManager.setCutEdges(model.getCutEdges()); - hierarchicalStableLayoutManager.updateLayout(visibleFigures, visibleConnections); + boolean enable = model.getCutEdges(); + boolean previous = hierarchicalStableLayoutManager.getCutEdges(); + hierarchicalStableLayoutManager.setCutEdges(enable); + if (enable != previous) { + hierarchicalStableLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); + } else { + hierarchicalStableLayoutManager.updateLayout(visibleFigures, visibleConnections); + } } private void doSeaLayout(Set
visibleFigures, Set visibleConnections) { - HierarchicalLayoutManager seaLayoutManager = new HierarchicalLayoutManager(); + seaLayoutManager = new HierarchicalLayoutManager(); seaLayoutManager.setCutEdges(model.getCutEdges()); seaLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); } - private void doClusteredLayout(Set visibleConnections) { + private void doClusteredLayout(Set
visibleFigures, Set visibleConnections) { HierarchicalClusterLayoutManager clusterLayoutManager = new HierarchicalClusterLayoutManager(); clusterLayoutManager.setCutEdges(model.getCutEdges()); - clusterLayoutManager.doLayout(new LayoutGraph(visibleConnections, new HashSet<>())); + clusterLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); } private void doCFGLayout(Set
visibleFigures, Set visibleConnections) { @@ -765,8 +777,6 @@ private boolean shouldAnimate() { private final Point specialNullPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE); - - private void processOutputSlot(OutputSlot outputSlot, List connections, int controlPointIndex, Point lastPoint, LineWidget predecessor) { Map> pointMap = new HashMap<>(connections.size()); @@ -843,53 +853,6 @@ private void processOutputSlot(OutputSlot outputSlot, List con } } - private void processFreeForm(OutputSlot outputSlot, List connections) { - for (FigureConnection connection : connections) { - if (isVisibleFigureConnection(connection)) { - boolean isBold = false; - boolean isDashed = true; - boolean isVisible = true; - if (connection.getStyle() == Connection.ConnectionStyle.BOLD) { - isBold = true; - } else if (connection.getStyle() == Connection.ConnectionStyle.INVISIBLE) { - isVisible = false; - } - if (connection.getStyle() != Connection.ConnectionStyle.DASHED) { - isDashed = false; - } - - - List controlPoints = connection.getControlPoints(); - if (controlPoints.size() <= 2) continue; - Point firstPoint = controlPoints.get(0); // First point - Point lastPoint = controlPoints.get(controlPoints.size() - 1); // Last point - List connectionList = new ArrayList<>(Collections.singleton(connection)); - LineWidget line = new LineWidget(this, outputSlot, connectionList, firstPoint, lastPoint, null, isBold, isDashed); - line.setFromControlYOffset(50); - line.setToControlYOffset(-50); - line.setVisible(isVisible); - connectionLayer.addChild(line); - - addObject(new ConnectionSet(connectionList), line); - line.getActions().addAction(hoverAction); - - if (outputSlotToLineWidget.containsKey(outputSlot)) { - outputSlotToLineWidget.get(outputSlot).add(line); - } else { - outputSlotToLineWidget.put(outputSlot, new HashSet<>(Collections.singleton(line))); - } - - InputSlot inputSlot = connection.getInputSlot(); - if (inputSlotToLineWidget.containsKey(inputSlot)) { - inputSlotToLineWidget.get(inputSlot).add(line); - } else { - inputSlotToLineWidget.put(inputSlot, new HashSet<>(Collections.singleton(line))); - } - } - } - } - - private void processBlockConnection(BlockConnection blockConnection) { boolean isDashed = blockConnection.getStyle() == Connection.ConnectionStyle.DASHED; boolean isBold = blockConnection.getStyle() == Connection.ConnectionStyle.BOLD; diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java index 56c83517d2f39..d067925fb87b7 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java @@ -140,6 +140,7 @@ public EditorTopComponent(DiagramViewModel diagramViewModel) { cardLayout = new CardLayout(); centerPanel = new JPanel(); centerPanel.setLayout(cardLayout); + centerPanel.setOpaque(true); centerPanel.setBackground(Color.WHITE); satelliteComponent = scene.createSatelliteView(); satelliteComponent.setSize(200, 200); From 7bfdeefd4cb4fceefa83c5038da41e3af6ec94cf Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Wed, 27 Nov 2024 11:11:36 +0100 Subject: [PATCH 05/30] Node labels in the CFG view left aligned --- .../java/com/sun/hotspot/igv/view/widgets/FigureWidget.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java index c44e598f53632..4cf2e86c046f1 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java @@ -129,7 +129,10 @@ public FigureWidget(final Figure f, DiagramScene scene) { this.addChild(middleWidget); Widget textWidget = new Widget(scene); - textWidget.setLayout(LayoutFactory.createVerticalFlowLayout(SerialAlignment.CENTER, 0)); + SerialAlignment textAlign = scene.getModel().getShowCFG() ? + LayoutFactory.SerialAlignment.LEFT_TOP : + LayoutFactory.SerialAlignment.CENTER; + textWidget.setLayout(LayoutFactory.createVerticalFlowLayout(textAlign, 0)); middleWidget.addChild(textWidget); String[] strings = figure.getLines(); From b8265876d072a08336b5272cb9d20d44beae724e Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Wed, 27 Nov 2024 11:11:57 +0100 Subject: [PATCH 06/30] Fix crash: missing Figure after filter applied --- .../Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java | 4 +++- .../src/main/java/com/sun/hotspot/igv/graph/Diagram.java | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java index 011fd8239b157..d127e1357e765 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java @@ -63,7 +63,9 @@ public Set getSuccessors() { public List getVertices() { List vertices = new ArrayList<>(); for (InputNode inputNode : inputBlock.getNodes()) { - vertices.add(diagram.getFigure(inputNode)); + if (diagram.hasFigure(inputNode)) { + vertices.add(diagram.getFigure(inputNode)); + } } return vertices; } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Diagram.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Diagram.java index fca51f4b53dbf..03d3cf3002c8a 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Diagram.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Diagram.java @@ -133,6 +133,10 @@ public Block getBlock(InputBlock b) { return blocks.get(b); } + public boolean hasFigure(InputNode n) { + return figures.containsKey(n); + } + public Figure getFigure(InputNode n) { assert figures.containsKey(n); return figures.get(n); From f01593f823e4b8ca799cdaad57d6bd9dd1ee4ac9 Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Wed, 27 Nov 2024 15:13:45 +0100 Subject: [PATCH 07/30] cached --- .../java/com/sun/hotspot/igv/view/widgets/LineWidget.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java index ff7e955137aa8..2b105ab33644a 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java @@ -69,6 +69,7 @@ public class LineWidget extends Widget implements PopupMenuProvider { private boolean popupVisible; private final boolean isBold; private final boolean isDashed; + private boolean needToInitToolTipText = true; public LineWidget(DiagramScene scene, OutputSlot s, List connections, Point from, Point to, LineWidget predecessor, boolean isBold, boolean isDashed) { super(scene); @@ -92,7 +93,6 @@ public LineWidget(DiagramScene scene, OutputSlot s, List c if (!connections.isEmpty()) { color = connections.get(0).getColor(); } - setToolTipText("" + generateToolTipText(this.connections) + ""); setCheckClipping(false); @@ -318,6 +318,10 @@ protected void notifyStateChanged(ObjectState previousState, ObjectState state) setHighlighted(enableHighlighting); recursiveHighlightSuccessors(enableHighlighting); highlightVertices(enableHighlighting); + if (enableHighlighting && needToInitToolTipText) { + setToolTipText("" + generateToolTipText(this.connections) + ""); + needToInitToolTipText = false; // Ensure it's only set once + } } } From f045e1d6155e4ab5450d737acac561d25396ad24 Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Wed, 27 Nov 2024 15:29:00 +0100 Subject: [PATCH 08/30] remove dead code in LineWidget --- .../hotspot/igv/view/widgets/LineWidget.java | 113 +----------------- 1 file changed, 6 insertions(+), 107 deletions(-) diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java index 2b105ab33644a..d587bf208b41d 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,12 +30,9 @@ import com.sun.hotspot.igv.util.StringUtils; import com.sun.hotspot.igv.view.DiagramScene; import java.awt.*; -import java.awt.geom.CubicCurve2D; import java.awt.geom.Line2D; -import java.util.ArrayList; -import java.util.HashSet; +import java.util.*; import java.util.List; -import java.util.Set; import javax.swing.JPopupMenu; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; @@ -60,8 +57,8 @@ public class LineWidget extends Widget implements PopupMenuProvider { private final OutputSlot outputSlot; private final DiagramScene scene; private final List connections; - private Point from; - private Point to; + private final Point from; + private final Point to; private Rectangle clientArea; private final LineWidget predecessor; private final List successors; @@ -75,7 +72,7 @@ public LineWidget(DiagramScene scene, OutputSlot s, List c super(scene); this.scene = scene; this.outputSlot = s; - this.connections = connections; + this.connections = Collections.unmodifiableList(connections); this.from = from; this.to = to; this.predecessor = predecessor; @@ -100,37 +97,12 @@ public LineWidget(DiagramScene scene, OutputSlot s, List c setBackground(color); } - - public Point getClientAreaLocation() { - return clientArea.getLocation(); - } - private void computeClientArea() { int minX = from.x; int minY = from.y; int maxX = to.x; int maxY = to.y; - if (fromControlYOffset != 0 && toControlYOffset != 0) { - // Adjust the bounding box to accommodate control points for curves - if (from.y < to.y) { // non-reversed edges - minY = Math.min(minY, from.y + fromControlYOffset); - maxY = Math.max(maxY, to.y + toControlYOffset); - } else { // reversed edges - if (from.x - to.x > 0) { - minX = Math.min(minX, from.x - 150); - maxX = Math.max(maxX, to.x + 150); - minY = Math.min(minY, from.y + fromControlYOffset); - maxY = Math.max(maxY, to.y + toControlYOffset); - } else { - minX = Math.min(minX, from.x + 150); - maxX = Math.max(maxX, to.x - 150); - minY = Math.min(minY, from.y + fromControlYOffset); - maxY = Math.max(maxY, to.y + toControlYOffset); - } - } - } - // Ensure min and max values are correct if (minX > maxX) { int tmp = minX; @@ -158,30 +130,6 @@ private String generateToolTipText(List conn) { return sb.toString(); } - - public void setFrom(Point from) { - this.from = from; - computeClientArea(); - } - - public void setTo(Point to) { - this.to= to; - computeClientArea(); - } - - private int fromControlYOffset; - private int toControlYOffset; - - public void setFromControlYOffset(int fromControlYOffset) { - this.fromControlYOffset = fromControlYOffset; - computeClientArea(); - } - - public void setToControlYOffset(int toControlYOffset) { - this.toControlYOffset = toControlYOffset; - computeClientArea(); - } - public Point getFrom() { return from; } @@ -227,41 +175,7 @@ protected void paintWidget() { g.setStroke(new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER)); } - // Define S-shaped curve with control points - if (fromControlYOffset != 0 && toControlYOffset != 0) { - if (from.y < to.y) { // non-reversed edges - if (Math.abs(from.x - to.x) > 10) { - CubicCurve2D.Float sShape = new CubicCurve2D.Float(); - sShape.setCurve(from.x, from.y, - from.x, from.y + fromControlYOffset, - to.x, to.y + toControlYOffset, - to.x, to.y); - g.draw(sShape); - } else { - g.drawLine(from.x, from.y, to.x, to.y); - } - } else { // reverse edges - if (from.x - to.x > 0) { - CubicCurve2D.Float sShape = new CubicCurve2D.Float(); - sShape.setCurve(from.x, from.y, - from.x - 150, from.y + fromControlYOffset, - to.x + 150, to.y + toControlYOffset, - to.x, to.y); - g.draw(sShape); - } else { - // add x offset - CubicCurve2D.Float sShape = new CubicCurve2D.Float(); - sShape.setCurve(from.x, from.y, - from.x + 150, from.y + fromControlYOffset, - to.x - 150, to.y + toControlYOffset, - to.x, to.y); - g.draw(sShape); - } - } - } else { - // Fallback to straight line if control points are not set - g.drawLine(from.x, from.y, to.x, to.y); - } + g.drawLine(from.x, from.y, to.x, to.y); boolean sameFrom = false; boolean sameTo = successors.isEmpty(); @@ -325,14 +239,6 @@ protected void notifyStateChanged(ObjectState previousState, ObjectState state) } } - public LineWidget getPredecessor() { - return predecessor; - } - - public List getSuccessors() { - return successors; - } - private void highlightPredecessors(boolean enable) { LineWidget predecessorLineWidget = predecessor; while (predecessorLineWidget != null) { @@ -390,13 +296,6 @@ private void popupVisibleSuccessors(boolean b) { } } - public Figure getFromFigure() { - if (outputSlot != null) { - return outputSlot.getFigure(); - } - return null; - } - @Override public JPopupMenu getPopupMenu(Widget widget, Point localLocation) { JPopupMenu menu = new JPopupMenu(); From 7617a53b58e519ffdc5d3c8471a1c1f155438cc2 Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Wed, 27 Nov 2024 16:17:30 +0100 Subject: [PATCH 09/30] batch add connectionLayer.addChildren(newWidgets); --- .../main/java/com/sun/hotspot/igv/view/DiagramScene.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java index 0a0146cba8e4b..297c7d9b99818 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java @@ -831,7 +831,7 @@ private void processOutputSlot(OutputSlot outputSlot, List con } } - connectionLayer.addChild(newPredecessor); + newWidgets.add(newPredecessor); addObject(new ConnectionSet(connectionList), newPredecessor); newPredecessor.getActions().addAction(hoverAction); } @@ -1016,10 +1016,13 @@ public void componentShowing() { SelectionCoordinator.getInstance().getSelectedChangedEvent().addListener(selectedCoordinatorListener); } + private final ArrayList newWidgets = new ArrayList<>(); + private void rebuildConnectionLayer() { outputSlotToLineWidget.clear(); inputSlotToLineWidget.clear(); connectionLayer.removeChildren(); + newWidgets.clear(); for (Figure figure : getModel().getDiagram().getFigures()) { for (OutputSlot outputSlot : figure.getOutputSlots()) { List connectionList = new ArrayList<>(outputSlot.getConnections()); @@ -1034,6 +1037,9 @@ private void rebuildConnectionLayer() { } } } + + connectionLayer.addChildren(newWidgets); + newWidgets.clear(); } private Set getVisibleFigureWidgets() { From f11f43d086e4d451bc7f4eb2a9d8ea7f01e03697 Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Wed, 27 Nov 2024 16:17:41 +0100 Subject: [PATCH 10/30] run IGV without asserts --- src/utils/IdealGraphVisualizer/igv.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 src/utils/IdealGraphVisualizer/igv.sh diff --git a/src/utils/IdealGraphVisualizer/igv.sh b/src/utils/IdealGraphVisualizer/igv.sh old mode 100644 new mode 100755 index 4d931e2815ce1..3a0599bb6f64e --- a/src/utils/IdealGraphVisualizer/igv.sh +++ b/src/utils/IdealGraphVisualizer/igv.sh @@ -20,4 +20,4 @@ # or visit www.oracle.com if you need additional information or have any # questions. -mvn --batch-mode -f application/pom.xml nbm:run-platform >.igv.log 2>&1 +mvn --batch-mode -f application/pom.xml nbm:run-platform -Dnetbeans.run.params="-J-da" >.igv.log 2>&1 From ffa021dcaa00803c53e7391b9ceb24629a5b99bd Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Wed, 27 Nov 2024 16:44:20 +0100 Subject: [PATCH 11/30] update Figure height calculation for Slots --- .../main/java/com/sun/hotspot/igv/graph/Figure.java | 9 +++++++-- .../sun/hotspot/igv/view/widgets/FigureWidget.java | 12 ++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java index 4c7d01a4cebb8..b91ca8d5f2b38 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java @@ -71,8 +71,13 @@ private void updateHeight() { public int getSlotsHeight() { int slotHeight = 0; - if (hasNamedInputSlot() || hasNamedOutputSlot()) { - slotHeight += diagram.isCFG() ? 2 * Figure.SLOT_HEIGHT : Figure.SLOT_HEIGHT; + if (diagram.isCFG()) { + if (hasNamedInputSlot()) { + slotHeight += Figure.SLOT_HEIGHT; + } + if (hasNamedOutputSlot()) { + slotHeight += Figure.SLOT_HEIGHT; + } } return slotHeight; } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java index 4cf2e86c046f1..6b9b8e548d108 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java @@ -67,10 +67,10 @@ public class FigureWidget extends Widget implements Properties.Provider, PopupMenuProvider, DoubleClickHandler { private static final double LABEL_ZOOM_FACTOR = 0.3; - private Figure figure; - private Widget middleWidget; - private ArrayList labelWidgets; - private DiagramScene diagramScene; + private final Figure figure; + private final Widget middleWidget; + private final ArrayList labelWidgets; + private final DiagramScene diagramScene; private boolean boundary; private static final Image warningSign = ImageUtilities.loadImage("com/sun/hotspot/igv/view/images/warning.png"); @@ -185,10 +185,6 @@ protected Sheet createSheet() { this.setToolTipText(PropertiesConverter.convertToHTML(f.getProperties())); } - public void updatePosition() { - setPreferredLocation(figure.getPosition()); - } - public int getFigureHeight() { return middleWidget.getPreferredBounds().height; } From ad4d07614b9a50e9037f619c771c2d824fd41643 Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Wed, 27 Nov 2024 18:29:08 +0100 Subject: [PATCH 12/30] remove executability of igv.sh --- src/utils/IdealGraphVisualizer/igv.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 src/utils/IdealGraphVisualizer/igv.sh diff --git a/src/utils/IdealGraphVisualizer/igv.sh b/src/utils/IdealGraphVisualizer/igv.sh old mode 100755 new mode 100644 From 2f1157efe8d8d3c12b261bf459deaf1cd970166b Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Thu, 28 Nov 2024 09:30:20 +0100 Subject: [PATCH 13/30] fixed graph objects equality --- .../java/com/sun/hotspot/igv/graph/Block.java | 3 ++- .../sun/hotspot/igv/graph/BlockConnection.java | 16 ++++++++++++++++ .../java/com/sun/hotspot/igv/graph/Figure.java | 7 ++++--- .../hotspot/igv/graph/FigureConnection.java | 17 +++++++++-------- .../com/sun/hotspot/igv/graph/InputSlot.java | 14 ++++++++++++++ .../com/sun/hotspot/igv/graph/OutputSlot.java | 13 +++++++++++++ .../java/com/sun/hotspot/igv/graph/Slot.java | 18 ++++++++++++++---- 7 files changed, 72 insertions(+), 16 deletions(-) diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java index d127e1357e765..e5a3f9eeac068 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Block.java @@ -102,7 +102,8 @@ public String toString() { @Override public boolean equals(Object obj) { - if (obj == null) return false; + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; Block other = (Block) obj; return inputBlock.equals(other.inputBlock); } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java index 4648734ee80db..1ec3b2da266db 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/BlockConnection.java @@ -27,6 +27,7 @@ import java.awt.Color; import java.awt.Point; import java.util.List; +import java.util.Objects; public class BlockConnection implements Connection { @@ -99,4 +100,19 @@ public void setControlPoints(List list) { public boolean hasSlots() { return false; } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BlockConnection that)) return false; + return Objects.equals(this.sourceBlock, that.sourceBlock) && + Objects.equals(this.destinationBlock, that.destinationBlock) && + Objects.equals(this.label, that.label); + } + + @Override + public int hashCode() { + return Objects.hash(sourceBlock, destinationBlock, label); + } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java index b91ca8d5f2b38..2b111d00f4364 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Figure.java @@ -381,15 +381,16 @@ public Dimension getSize() { @Override public boolean equals(Object o) { - if (!(o instanceof Figure)) { + if (this == o) return true; + if (!(o instanceof Figure other)) { return false; } - return getInputNode().equals(((Figure) o).getInputNode()); + return Objects.equals(this.getInputNode(), other.getInputNode()); } @Override public int hashCode() { - return getInputNode().hashCode(); + return Objects.hash(getInputNode()); } @Override diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java index e26c618f49e3a..679b1aa300671 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/FigureConnection.java @@ -29,6 +29,7 @@ import java.awt.Point; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * @@ -161,15 +162,15 @@ public boolean hasSlots() { @Override public boolean equals(Object o) { - if (!(o instanceof FigureConnection)) { - return false; - } - - return getInputSlot().getFigure().equals(((FigureConnection)o).getInputSlot().getFigure()) - && getOutputSlot().getFigure().equals(((FigureConnection)o).getOutputSlot().getFigure()) - && getInputSlot().getPosition() == ((FigureConnection)o).getInputSlot().getPosition() - && getOutputSlot().getPosition() == ((FigureConnection) o).getOutputSlot().getPosition(); + if (this == o) return true; + if (!(o instanceof FigureConnection that)) return false; + return Objects.equals(this.outputSlot, that.outputSlot) && + Objects.equals(this.inputSlot, that.inputSlot); } + @Override + public int hashCode() { + return Objects.hash(outputSlot, inputSlot); + } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java index 2ba127cc8f707..e0f5ae73b6d42 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java @@ -25,6 +25,7 @@ import java.awt.Point; import java.util.List; +import java.util.Objects; /** * @@ -84,4 +85,17 @@ public String getToolTipText() { public String toString() { return "InputSlot[figure=" + this.getFigure().toString() + ", position=" + getPosition() + "]"; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof InputSlot that)) return false; + if (!super.equals(o)) return false; + return this.originalIndex == that.originalIndex; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), originalIndex); + } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java index a2555ce36584b..14d1f1d52cd54 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java @@ -24,6 +24,7 @@ package com.sun.hotspot.igv.graph; import java.awt.Point; +import java.util.Objects; /** * @@ -61,4 +62,16 @@ public Point getRelativePosition() { public String toString() { return "OutputSlot[figure=" + this.getFigure().toString() + ", position=" + getPosition() + "]"; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode()); + } } diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java index 1ddc145bb89f9..df85e8db99f21 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java @@ -34,10 +34,7 @@ import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.image.BufferedImage; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.util.*; /** * @@ -178,5 +175,18 @@ public Vertex getVertex() { public abstract int getPosition(); public abstract void setPosition(int position); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Slot other)) return false; + return this.wantedIndex == other.wantedIndex && + Objects.equals(this.figure, other.figure); + } + + @Override + public int hashCode() { + return Objects.hash(figure, wantedIndex); + } } From c08d99e9746382a9db423479e42eef00d7b0126e Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Thu, 28 Nov 2024 10:27:08 +0100 Subject: [PATCH 14/30] revert copyright changes --- .../igv/hierarchicallayout/HierarchicalLayoutManager.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java index 3aa4c2ef7e1d5..58c40c1a083aa 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,10 @@ import java.awt.Point; import java.util.*; +/** + * + * @author Thomas Wuerthinger + */ public class HierarchicalLayoutManager extends LayoutManager { int maxLayerLength; From 648057c6391594ef0118d05edfe6176bf8f4e0c5 Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Thu, 28 Nov 2024 09:25:08 +0100 Subject: [PATCH 15/30] 8343705: IGV: Interactive Node Moving in Hierarchical Layout --- .../HierarchicalLayoutManager.java | 58 +- .../igv/hierarchicallayout/LayoutEdge.java | 18 + .../igv/hierarchicallayout/LayoutGraph.java | 696 +++++++++++++++++- .../igv/hierarchicallayout/LayoutLayer.java | 107 +++ .../igv/hierarchicallayout/LayoutMover.java | 53 ++ .../igv/hierarchicallayout/LayoutNode.java | 80 +- .../sun/hotspot/igv/view/DiagramScene.java | 268 ++++++- .../igv/view/widgets/FigureWidget.java | 4 + .../hotspot/igv/view/widgets/LineWidget.java | 26 +- 9 files changed, 1297 insertions(+), 13 deletions(-) create mode 100644 src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java index 3aa4c2ef7e1d5..ed19d17fbbfff 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java @@ -29,7 +29,7 @@ import java.awt.Point; import java.util.*; -public class HierarchicalLayoutManager extends LayoutManager { +public class HierarchicalLayoutManager extends LayoutManager implements LayoutMover { int maxLayerLength; private LayoutGraph graph; @@ -65,6 +65,62 @@ public void doLayout(LayoutGraph layoutGraph) { graph = layoutGraph; } + @Override + public void moveLink(Point linkPos, int shiftX) { + int layerNr = graph.findLayer(linkPos.y); + for (LayoutNode node : graph.getLayer(layerNr)) { + if (node.isDummy() && linkPos.x == node.getX()) { + LayoutLayer layer = graph.getLayer(layerNr); + if (layer.contains(node)) { + node.setX(linkPos.x + shiftX); + layer.sortNodesByX(); + break; + } + } + } + writeBack(); + } + + @Override + public void moveVertices(Set movedVertices) { + for (Vertex vertex : movedVertices) { + moveVertex(vertex); + } + writeBack(); + } + + private void writeBack() { + graph.optimizeBackEdgeCrossings(); + graph.updateLayerMinXSpacing(); + graph.straightenEdges(); + WriteResult.apply(graph); + } + + @Override + public void moveVertex(Vertex movedVertex) { + Point newLoc = movedVertex.getPosition(); + LayoutNode movedNode = graph.getLayoutNode(movedVertex); + assert !movedNode.isDummy(); + + int layerNr = graph.findLayer(newLoc.y + movedNode.getOuterHeight() / 2); + if (movedNode.getLayer() == layerNr) { // we move the node in the same layer + LayoutLayer layer = graph.getLayer(layerNr); + if (layer.contains(movedNode)) { + movedNode.setX(newLoc.x); + layer.sortNodesByX(); + } + } else { // only remove edges if we moved the node to a new layer + if (maxLayerLength > 0) return; // TODO: not implemented + graph.removeNodeAndEdges(movedNode); + layerNr = graph.insertNewLayerIfNeeded(movedNode, layerNr); + graph.addNodeToLayer(movedNode, layerNr); + movedNode.setX(newLoc.x); + graph.getLayer(layerNr).sortNodesByX(); + graph.removeEmptyLayers(); + graph.addEdges(movedNode, maxLayerLength); + } + } + public List getNodes() { return graph.getAllNodes(); } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java index eebfa8c31e73b..74affaad4c725 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java @@ -166,6 +166,24 @@ public void setTo(LayoutNode to) { this.to = to; } + /** + * Gets the absolute x-coordinate of the source node's connection point for this edge. + * + * @return The x-coordinate of the source node's connection point. + */ + public int getFromX() { + return from.getX() + getRelativeFromX(); + } + + /** + * Gets the absolute x-coordinate of the target node's connection point for this edge. + * + * @return The x-coordinate of the target node's connection point. + */ + public int getToX() { + return to.getX() + getRelativeToX(); + } + /** * Gets the relative horizontal position from the source node's left boundary to the edge's starting point. * diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java index 7ac83d39d52c2..763b8ffad0515 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java @@ -23,6 +23,7 @@ */ package com.sun.hotspot.igv.hierarchicallayout; +import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.NODE_POS_COMPARATOR; import com.sun.hotspot.igv.layout.Link; import com.sun.hotspot.igv.layout.Port; import com.sun.hotspot.igv.layout.Vertex; @@ -46,6 +47,8 @@ public class LayoutGraph { private final Set links; private final SortedSet vertices; private final LinkedHashMap> inputPorts; + private final LinkedHashMap> outputPorts; + private final LinkedHashMap> portLinks; // Layout Management: LayoutNodes and LayoutLayers private final LinkedHashMap layoutNodes; @@ -62,9 +65,9 @@ public class LayoutGraph { public LayoutGraph(Collection links, Collection additionalVertices) { this.links = new HashSet<>(links); vertices = new TreeSet<>(additionalVertices); - LinkedHashMap> portLinks = new LinkedHashMap<>(links.size()); + portLinks = new LinkedHashMap<>(links.size()); inputPorts = new LinkedHashMap<>(links.size()); - LinkedHashMap> outputPorts = new LinkedHashMap<>(links.size()); + outputPorts = new LinkedHashMap<>(links.size()); for (Link link : links) { assert link.getFrom() != null; @@ -160,6 +163,114 @@ public List getAllNodes() { return Collections.unmodifiableList(allNodes); } + /** + * Creates a new layer at the specified index in the layers list. + * Adjusts the layer numbers of existing nodes in layers below the inserted layer. + * + * @param layerNr The index at which to insert the new layer. + * @return The newly created LayoutLayer. + */ + private LayoutLayer createNewLayer(int layerNr) { + LayoutLayer layer = new LayoutLayer(); + layers.add(layerNr, layer); + + // update layer field in nodes below layerNr + for (int l = layerNr + 1; l < getLayerCount(); l++) { + for (LayoutNode layoutNode : getLayer(l)) { + layoutNode.setLayer(l); + } + } + return layer; + } + + /** + * Deletes the layer at the specified index. + * Adjusts the layer numbers of existing nodes in layers below the deleted layer. + * + * @param layerNr The index of the layer to delete. + */ + private void deleteLayer(int layerNr) { + layers.remove(layerNr); + + // Update the layer field in nodes below the deleted layer + for (int l = layerNr; l < getLayerCount(); l++) { + for (LayoutNode layoutNode : getLayer(l)) { + layoutNode.setLayer(l); + } + } + } + + /** + * Ensures that no neighboring nodes of the specified node are in the same layer. + * If any neighbor is found in the specified layer, inserts a new layer to avoid conflicts. + * Returns the adjusted layer number where the node can be safely inserted. + * + * @param node The LayoutNode to check and possibly reposition. + * @param layerNr The proposed layer number for the node. + * @return The layer number where the node can be safely inserted after adjustments. + */ + public int insertNewLayerIfNeeded(LayoutNode node, int layerNr) { + for (Link inputLink : getInputLinks(node.getVertex())) { + if (inputLink.getFrom().getVertex() == inputLink.getTo().getVertex()) continue; + LayoutNode fromNode = getLayoutNode(inputLink.getFrom().getVertex()); + if (fromNode.getLayer() == layerNr) { + moveExpandLayerDown(layerNr + 1); + return layerNr + 1; + } + } + for (Link outputLink : getOutputLinks(node.getVertex())) { + if (outputLink.getFrom().getVertex() == outputLink.getTo().getVertex()) continue; + LayoutNode toNode = getLayoutNode(outputLink.getTo().getVertex()); + if (toNode.getLayer() == layerNr) { + moveExpandLayerDown(layerNr); + return layerNr; + } + } + return layerNr; + + } + + /** + * Inserts a new layer at the specified index and adjusts nodes and edges accordingly. + * Moves existing nodes and their successors down to accommodate the new layer. + * + * @param layerNr The index at which to insert the new layer. + */ + private void moveExpandLayerDown(int layerNr) { + LayoutLayer newLayer = createNewLayer(layerNr); + + if (layerNr == 0) return; + LayoutLayer layerAbove = getLayer(layerNr - 1); + + for (LayoutNode fromNode : layerAbove) { + int fromX = fromNode.getX(); + Map> successorsByX = fromNode.groupSuccessorsByX(); + fromNode.clearSuccessors(); + + for (Map.Entry> entry : successorsByX.entrySet()) { + Integer relativeFromX = entry.getKey(); + List edges = entry.getValue(); + LayoutNode dummyNode = new LayoutNode(); + dummyNode.setX(fromX + relativeFromX); + dummyNode.setLayer(layerNr); + for (LayoutEdge edge : edges) { + dummyNode.addSuccessor(edge); + } + LayoutEdge dummyEdge = new LayoutEdge(fromNode, dummyNode, relativeFromX, 0, edges.get(0).getLink()); + if (edges.get(0).isReversed()) dummyEdge.reverse(); + + fromNode.addSuccessor(dummyEdge); + dummyNode.addPredecessor(dummyEdge); + for (LayoutEdge edge : edges) { + edge.setFrom(dummyNode); + } + addDummyToLayer(dummyNode, layerNr); + } + } + + newLayer.sortNodesByX(); + } + /** * Retrieves an unmodifiable list of all layers in the graph. * @@ -178,6 +289,31 @@ public int getLayerCount() { return layers.size(); } + /** + * Retrieves the LayoutNode associated with the specified Vertex. + * + * @param vertex The vertex whose LayoutNode is to be retrieved. + * @return The LayoutNode corresponding to the given vertex, or null if not found. + */ + public LayoutNode getLayoutNode(Vertex vertex) { + return layoutNodes.get(vertex); + } + + /** + * Adds a LayoutNode to the specified layer and registers it in the graph. + * + * @param node The LayoutNode to add to the layer. + * @param layerNumber The index of the layer to which the node will be added. + */ + public void addNodeToLayer(LayoutNode node, int layerNumber) { + assert !node.isDummy(); + node.setLayer(layerNumber); + getLayer(layerNumber).add(node); + if (!layoutNodes.containsKey(node.getVertex())) { + layoutNodes.put(node.getVertex(), node); + } + } + /** * Adds a LayoutNode to the specified layer and registers it in the graph. * @@ -268,6 +404,148 @@ public Set findRootVertices() { .collect(Collectors.toSet()); } + /** + * Retrieves all incoming links to the specified vertex. + * + * @param vertex The vertex whose incoming links are to be retrieved. + * @return A set of links that are incoming to the vertex. + */ + public List getInputLinks(Vertex vertex) { + List inputLinks = new ArrayList<>(); + for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { + inputLinks.addAll(portLinks.getOrDefault(inputPort, Collections.emptySet())); + } + return inputLinks; + } + + /** + * Retrieves all outgoing links from the specified vertex. + * + * @param vertex The vertex whose outgoing links are to be retrieved. + * @return A set of links that are outgoing from the vertex. + */ + public List getOutputLinks(Vertex vertex) { + List outputLinks = new ArrayList<>(); + for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) { + outputLinks.addAll(portLinks.getOrDefault(outputPort, Collections.emptySet())); + } + return outputLinks; + } + + public List getAllLinks(Vertex vertex) { + List allLinks = new ArrayList<>(); + + for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { + allLinks.addAll(portLinks.getOrDefault(inputPort, Collections.emptySet())); + } + + for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) { + allLinks.addAll(portLinks.getOrDefault(outputPort, Collections.emptySet())); + } + + return allLinks; + } + + /** + * Removes the specified LayoutNode and all its connected edges from the graph. + * + * @param node The LayoutNode to remove along with its edges. + */ + public void removeNodeAndEdges(LayoutNode node) { + assert !node.isDummy(); + removeEdges(node); // a node can only be removed together with its edges + int layer = node.getLayer(); + layers.get(layer).remove(node); + layers.get(layer).updateNodeIndices(); + layoutNodes.remove(node.getVertex()); + } + + /** + * Removes all edges connected to the specified LayoutNode. + * Handles the removal of associated dummy nodes if they are no longer needed. + * Updates the graph structure accordingly after node movement. + * + * @param node The LayoutNode whose connected edges are to be removed. + */ + public void removeEdges(LayoutNode node) { + assert !node.isDummy(); + for (Link link : getAllLinks(node.getVertex())) { + removeEdge(link); + } + } + + public void removeEdge(Link link) { + Vertex from = link.getFrom().getVertex(); + Vertex to = link.getTo().getVertex(); + LayoutNode toNode = getLayoutNode(to); + LayoutNode fromNode = getLayoutNode(from); + + if (toNode.getLayer() < fromNode.getLayer()) { + // Reversed edge + toNode = fromNode; + } + + // Remove preds-edges bottom up, starting at "to" node + // Cannot start from "from" node since there might be joint edges + List toNodePredsEdges = List.copyOf(toNode.getPredecessors()); + for (LayoutEdge edge : toNodePredsEdges) { + LayoutNode predNode = edge.getFrom(); + LayoutEdge edgeToRemove; + + if (edge.getLink() != null && edge.getLink().equals(link)) { + toNode.removePredecessor(edge); + edgeToRemove = edge; + } else { + // Wrong edge, look at next + continue; + } + + if (!predNode.isDummy() && predNode.getVertex().equals(from)) { + // No dummy nodes inbetween 'from' and 'to' vertex + predNode.removeSuccessor(edgeToRemove); + break; + } else { + // Must remove edges between dummy nodes + boolean found = true; + LayoutNode succNode = toNode; + while (predNode.isDummy() && found) { + found = false; + + if (predNode.getSuccessors().size() <= 1 && predNode.getPredecessors().size() <= 1) { + // Dummy node used only for this link, remove if not already removed + assert predNode.isDummy(); + int layer = predNode.getLayer(); + layers.get(layer).remove(predNode); + layers.get(layer).updateNodeIndices(); + dummyNodes.remove(predNode); + } else { + // anchor node, should not be removed + break; + } + + if (predNode.getPredecessors().size() == 1) { + predNode.removeSuccessor(edgeToRemove); + succNode = predNode; + edgeToRemove = predNode.getPredecessors().get(0); + predNode = edgeToRemove.getFrom(); + found = true; + } + } + + predNode.removeSuccessor(edgeToRemove); + succNode.removePredecessor(edgeToRemove); + } + break; + } + + if (fromNode.getReversedLinkStartPoints().containsKey(link)) { + fromNode.computeReversedLinkPoints(false); + } + if (toNode.getReversedLinkStartPoints().containsKey(link)) { + toNode.computeReversedLinkPoints(false); + } + } + /** * Retrieves the LayoutLayer at the specified index. * @@ -278,6 +556,30 @@ public LayoutLayer getLayer(int layerNr) { return layers.get(layerNr); } + /** + * Finds the layer closest to the given y-coordinate. + * + * @param y the y-coordinate to check + * @return the index of the optimal layer, or -1 if no layers are found + */ + public int findLayer(int y) { + int optimalLayer = -1; + int minDistance = Integer.MAX_VALUE; + for (int l = 0; l < getLayerCount(); l++) { + // Check if y is within this layer's bounds + if (y >= getLayer(l).getTop() && y <= getLayer(l).getBottom()) { + return l; + } + + int distance = Math.abs(getLayer(l).getCenter() - y); + if (distance < minDistance) { + minDistance = distance; + optimalLayer = l; + } + } + return optimalLayer; + } + /** * Positions the layers vertically, calculating their heights and setting their positions. * Centers the nodes within each layer vertically. @@ -299,6 +601,380 @@ public void positionLayers() { } } + /** + * Optimizes routing of reversed (back) edges to reduce crossings. + */ + public void optimizeBackEdgeCrossings() { + for (LayoutNode node : getLayoutNodes()) { + node.optimizeBackEdgeCrossing(); + } + } + + /** + * Removes empty layers from the graph. + * Iteratively checks for and removes layers that contain only dummy nodes. + */ + public void removeEmptyLayers() { + int i = 0; + while (i < getLayerCount()) { + LayoutLayer layer = getLayer(i); + if (layer.containsOnlyDummyNodes()) { + removeEmptyLayer(i); + } else { + i++; // Move to the next layer only if no removal occurred + } + } + } + + /** + * Removes the layer at the specified index if it is empty or contains only dummy nodes. + * Adjusts the positions of nodes and edges accordingly. + * + * @param layerNr The index of the layer to remove. + */ + private void removeEmptyLayer(int layerNr) { + LayoutLayer layer = getLayer(layerNr); + if (!layer.containsOnlyDummyNodes()) return; + + for (LayoutNode dummyNode : layer) { + if (dummyNode.getSuccessors().isEmpty()) { + dummyNode.setLayer(layerNr + 1); + getLayer(layerNr + 1).add(dummyNode); + dummyNode.setX(dummyNode.calculateOptimalXFromPredecessors(true)); + getLayer(layerNr + 1).sortNodesByX(); + continue; + } else if (dummyNode.getPredecessors().isEmpty()) { + dummyNode.setLayer(layerNr - 1); + dummyNode.setX(dummyNode.calculateOptimalXFromSuccessors(true)); + getLayer(layerNr - 1).add(dummyNode); + getLayer(layerNr - 1).sortNodesByX(); + continue; + } + LayoutEdge layoutEdge = dummyNode.getPredecessors().get(0); + + // remove the layoutEdge + LayoutNode fromNode = layoutEdge.getFrom(); + fromNode.removeSuccessor(layoutEdge); + + List successorEdges = dummyNode.getSuccessors(); + for (LayoutEdge successorEdge : successorEdges) { + successorEdge.setRelativeFromX(layoutEdge.getRelativeFromX()); + successorEdge.setFrom(fromNode); + fromNode.addSuccessor(successorEdge); + } + dummyNode.clearPredecessors(); + dummyNode.clearSuccessors(); + dummyNodes.remove(dummyNode); + } + + deleteLayer(layerNr); + } + + /** + * Repositions the specified LayoutNode horizontally within its layer to the new x-coordinate. + * Ensures no overlap with adjacent nodes and maintains minimum spacing. + * + * @param layoutNode The LayoutNode to reposition. + * @param newX The new x-coordinate to set for the node. + */ + private void repositionLayoutNodeX(LayoutNode layoutNode, int newX) { + int currentX = layoutNode.getX(); + + // Early exit if the desired position is the same as the current position + if (newX == currentX) { + return; + } + + LayoutLayer layer = getLayer(layoutNode.getLayer()); + if (newX > currentX) { + layer.tryShiftNodeRight(layoutNode, newX); + } else { + layer.tryShiftNodeLeft(layoutNode, newX); + } + } + + /** + * Aligns the x-coordinate of a single dummy successor node for the given LayoutNode. + * If the node has exactly one successor and that successor is a dummy node, + * sets the dummy node's x-coordinate to align with the current node or the edge's starting point. + * + * @param node The LayoutNode whose dummy successor is to be aligned. + */ + private void alignSingleSuccessorDummyNodeX(LayoutNode node) { + // Retrieve the list of successor edges + List successors = node.getSuccessors(); + + // Proceed only if there is exactly one successor + if (successors.size() != 1) { + return; + } + + LayoutEdge successorEdge = successors.get(0); + LayoutNode successorNode = successorEdge.getTo(); + + // Proceed only if the successor node is a dummy node + if (!successorNode.isDummy()) { + return; + } + + // Determine the target x-coordinate based on whether the current node is a dummy + int targetX = node.isDummy() ? node.getX() : successorEdge.getStartX(); + + // Align the successor dummy node to the target x-coordinate + repositionLayoutNodeX(successorNode, targetX); + } + + /** + * Aligns the x-coordinates of dummy successor nodes within the specified layer. + * Performs alignment in both forward and backward directions to ensure consistency. + * + * @param layer The LayoutLayer whose nodes' dummy successors need alignment. + */ + private void alignLayerDummySuccessors(LayoutLayer layer) { + // Forward pass: Align dummy successors from the first node to the last. + for (LayoutNode node : layer) { + alignSingleSuccessorDummyNodeX(node); + } + + // Backward pass: Align dummy successors from the last node to the first. + for (int i = layer.size() - 1; i >= 0; i--) { + LayoutNode node = layer.get(i); + alignSingleSuccessorDummyNodeX(node); + } + } + + /** + * Straightens edges in the graph by aligning dummy nodes to reduce bends. + * Processes all layers to align dummy successor nodes. + */ + public void straightenEdges() { + // Forward pass: Align dummy successors from the first layer to the last. + for (int i = 0; i < getLayerCount(); i++) { + alignLayerDummySuccessors(getLayer(i)); + } + + // Backward pass: Align dummy successors from the last layer to the first. + for (int i = getLayerCount() - 1; i >= 0; i--) { + alignLayerDummySuccessors(getLayer(i)); + } + } + + /** + * Updates the minimum X spacing for all layers in the graph. + */ + public void updateLayerMinXSpacing() { + for (LayoutLayer layer : this.getLayers()) { + layer.updateMinXSpacing(false); + } + } + + /** + * Calculates the optimal horizontal position (index) for the specified node within the given layer, + * aiming to minimize the number of edge crossings. + * + * @param node The node to position. + * @param layerNr The index of the layer in which to position the node. + * @return The optimal position index within the layer for the node. + */ + private int optimalPosition(LayoutNode node, int layerNr) { + getLayer(layerNr).sort(NODE_POS_COMPARATOR); + int edgeCrossings = Integer.MAX_VALUE; + int optimalPos = -1; + + // Try each possible position in the layerNr + for (int i = 0; i < getLayer(layerNr).size() + 1; i++) { + int xCoord; + if (i == 0) { + xCoord = getLayer(layerNr).get(i).getX() - node.getWidth() - 1; + } else { + xCoord = getLayer(layerNr).get(i - 1).getX() + getLayer(layerNr).get(i - 1).getWidth() + 1; + } + + int currentCrossings = 0; + + if (0 <= layerNr - 1) { + // For each link with an end point in vertex, check how many edges cross it + for (LayoutEdge edge : node.getPredecessors()) { + if (edge.getFrom().getLayer() == layerNr - 1) { + int fromNodeXCoord = edge.getFromX(); + int toNodeXCoord = xCoord; + if (!node.isDummy()) { + toNodeXCoord += edge.getRelativeToX(); + } + for (LayoutNode n : getLayer(layerNr - 1)) { + for (LayoutEdge e : n.getSuccessors()) { + if (e.getTo() == null) { + continue; + } + int compFromXCoord = e.getFromX(); + int compToXCoord = e.getToX(); + if ((fromNodeXCoord > compFromXCoord && toNodeXCoord < compToXCoord) + || (fromNodeXCoord < compFromXCoord + && toNodeXCoord > compToXCoord)) { + currentCrossings += 1; + } + } + } + } + } + } + // Edge crossings across current layerNr and layerNr below + if (layerNr + 1 < getLayerCount()) { + // For each link with an end point in vertex, check how many edges cross it + for (LayoutEdge edge : node.getSuccessors()) { + if (edge.getTo().getLayer() == layerNr + 1) { + int toNodeXCoord = edge.getToX(); + int fromNodeXCoord = xCoord; + if (!node.isDummy()) { + fromNodeXCoord += edge.getRelativeFromX(); + } + for (LayoutNode n : getLayer(layerNr + 1)) { + for (LayoutEdge e : n.getPredecessors()) { + if (e.getFrom() == null) { + continue; + } + int compFromXCoord = e.getFromX(); + int compToXCoord = e.getToX(); + if ((fromNodeXCoord > compFromXCoord && toNodeXCoord < compToXCoord) + || (fromNodeXCoord < compFromXCoord + && toNodeXCoord > compToXCoord)) { + currentCrossings += 1; + } + } + } + } + } + } + if (currentCrossings <= edgeCrossings) { + edgeCrossings = currentCrossings; + optimalPos = i; + } + } + return optimalPos; + } + + /** + * Creates layout edges for the specified node and reverses edges as needed. + * Reverses edges that go from lower to higher layers to maintain proper layering. + * + * @param node The LayoutNode for which to create and reverse edges. + */ + public void createAndReverseLayoutEdges(LayoutNode node) { + List nodeLinks = new ArrayList<>(getInputLinks(node.getVertex())); + nodeLinks.addAll(getOutputLinks(node.getVertex())); + nodeLinks.sort(LINK_COMPARATOR); + + List reversedLayoutNodes = new ArrayList<>(); + for (Link link : nodeLinks) { + if (link.getFrom().getVertex() == link.getTo().getVertex()) continue; + LayoutEdge layoutEdge = createLayoutEdge(link); + + LayoutNode fromNode = layoutEdge.getFrom(); + LayoutNode toNode = layoutEdge.getTo(); + + if (fromNode.getLayer() > toNode.getLayer()) { + HierarchicalLayoutManager.ReverseEdges.reverseEdge(layoutEdge); + reversedLayoutNodes.add(fromNode); + reversedLayoutNodes.add(toNode); + } + } + + // ReverseEdges + for (LayoutNode layoutNode : reversedLayoutNodes) { + layoutNode.computeReversedLinkPoints(false); + } + } + + /** + * Inserts dummy nodes along the edges from predecessors of the specified node, + * for edges that span more than one layer. + * + * @param layoutNode The node for which to create predecessor dummy nodes. + */ + public void createDummiesForNodePredecessor(LayoutNode layoutNode) { + for (LayoutEdge predEdge : layoutNode.getPredecessors()) { + LayoutNode fromNode = predEdge.getFrom(); + LayoutNode toNode = predEdge.getTo(); + if (Math.abs(toNode.getLayer() - fromNode.getLayer()) <= 1) continue; + + boolean hasEdgeFromSamePort = false; + LayoutEdge edgeFromSamePort = new LayoutEdge(fromNode, toNode, predEdge.getLink()); + if (predEdge.isReversed()) edgeFromSamePort.reverse(); + + for (LayoutEdge succEdge : fromNode.getSuccessors()) { + if (succEdge.getRelativeFromX() == predEdge.getRelativeFromX() && succEdge.getTo().isDummy()) { + edgeFromSamePort = succEdge; + hasEdgeFromSamePort = true; + break; + } + } + + if (hasEdgeFromSamePort) { + LayoutEdge curEdge = edgeFromSamePort; + boolean newEdge = true; + while (curEdge.getTo().getLayer() < toNode.getLayer() - 1 && curEdge.getTo().isDummy() && newEdge) { + // Traverse down the chain of dummy nodes linking together the edges originating + // from the same port + newEdge = false; + if (curEdge.getTo().getSuccessors().size() == 1) { + curEdge = curEdge.getTo().getSuccessors().get(0); + newEdge = true; + } else { + for (LayoutEdge e : curEdge.getTo().getSuccessors()) { + if (e.getTo().isDummy()) { + curEdge = e; + newEdge = true; + break; + } + } + } + } + + LayoutNode prevDummy; + if (!curEdge.getTo().isDummy()) { + prevDummy = curEdge.getFrom(); + } else { + prevDummy = curEdge.getTo(); + } + + predEdge.setFrom(prevDummy); + predEdge.setRelativeFromX(prevDummy.getWidth() / 2); + fromNode.removeSuccessor(predEdge); + prevDummy.addSuccessor(predEdge); + } + + LayoutNode layoutNode1 = predEdge.getTo(); + if (predEdge.getTo().getLayer() - 1 > predEdge.getFrom().getLayer()) { + LayoutEdge prevEdge = predEdge; + for (int l = layoutNode1.getLayer() - 1; l > prevEdge.getFrom().getLayer(); l--) { + LayoutNode dummyNode = new LayoutNode(); + dummyNode.addSuccessor(prevEdge); + LayoutEdge result = new LayoutEdge(prevEdge.getFrom(), dummyNode, prevEdge.getRelativeFromX(), 0, prevEdge.getLink()); + if (prevEdge.isReversed()) result.reverse(); + dummyNode.addPredecessor(result); + prevEdge.setRelativeFromX(0); + prevEdge.getFrom().removeSuccessor(prevEdge); + prevEdge.getFrom().addSuccessor(result); + prevEdge.setFrom(dummyNode); + dummyNode.setLayer(l); + List layerNodes = getLayer(l); + if (layerNodes.isEmpty()) { + dummyNode.setPos(0); + } else { + dummyNode.setPos(optimalPosition(dummyNode, l)); + } + for (LayoutNode n : layerNodes) { + if (n.getPos() >= dummyNode.getPos()) { + n.setPos(n.getPos() + 1); + } + } + addDummyToLayer(dummyNode, l); + prevEdge = dummyNode.getPredecessors().get(0); + } + } + } + } + /** * Inserts dummy nodes along the edges to successors of the specified node, * for edges that span more than one layer. @@ -415,4 +1091,20 @@ public void createDummiesForNodeSuccessor(LayoutNode layoutNode, int maxLayerLen } } } + + /** + * Adds edges connected to the specified node, including any necessary dummy nodes. + * Handles edge reversal, dummy node insertion for both predecessors and successors, + * and updates node positions accordingly. + * + * @param node The LayoutNode to which edges will be added. + * @param maxLayerLength The maximum number of layers an edge can span without splitting it + */ + public void addEdges(LayoutNode node, int maxLayerLength) { + assert !node.isDummy(); + createAndReverseLayoutEdges(node); + createDummiesForNodeSuccessor(node, maxLayerLength); + createDummiesForNodePredecessor(node); + updatePositions(); + } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java index 6f477b63f2df9..58c0c15890671 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java @@ -152,6 +152,10 @@ public void setTop(int top) { y = top; } + public int getCenter() { + return y + height / 2; + } + /** * Gets the bottom Y-coordinate of this layer. * @@ -179,6 +183,53 @@ public void setHeight(int height) { this.height = height; } + /** + * Checks if this layer contains only dummy nodes. + * + * @return true if all nodes in the layer are dummy nodes; false otherwise. + */ + public boolean containsOnlyDummyNodes() { + for (LayoutNode node : this) { + if (!node.isDummy()) { + return false; + } + } + return true; + } + + /** + * Sorts the nodes in this layer by their X-coordinate in increasing order. + * Assigns position indices to nodes based on the sorted order. + * Adjusts the X-coordinates of nodes to ensure minimum spacing between them. + */ + public void sortNodesByX() { + if (isEmpty()) return; + + sort(NODE_X_COMPARATOR); // Sort nodes in the layer increasingly by x + + updateNodeIndices(); + updateMinXSpacing(false); + } + + /** + * Ensures nodes have minimum horizontal spacing by adjusting their X positions. + * + * @param startFromZero if true, starts positioning from X = 0; otherwise, uses the first node's current X. + */ + public void updateMinXSpacing(boolean startFromZero) { + if (isEmpty()) { + return; // No nodes to adjust. + } + + int minX = startFromZero ? 0 : this.get(0).getX(); + + for (LayoutNode node : this) { + int x = Math.max(node.getX(), minX); + node.setX(x); + minX = x + node.getOuterWidth() + NODE_OFFSET; + } + } + /** * Initializes nodes' X positions with spacing. */ @@ -201,4 +252,60 @@ public void updateNodeIndices() { pos++; } } + + /** + * Attempts to move the specified node to the right within the layer to the given X-coordinate. + * Ensures that the node does not overlap with its right neighbor by checking required spacing. + * If movement is possible without causing overlap, the node's X-coordinate is updated. + * + * @param layoutNode The node to move. + * @param newX The desired new X-coordinate for the node. + */ + public void tryShiftNodeRight(LayoutNode layoutNode, int newX) { + int currentX = layoutNode.getX(); + int shiftAmount = newX - currentX; + int rightPos = layoutNode.getPos() + 1; + + if (rightPos < size()) { + // There is a right neighbor + LayoutNode rightNeighbor = get(rightPos); + int proposedRightEdge = layoutNode.getRight() + shiftAmount; + int requiredLeftEdge = rightNeighbor.getOuterLeft() - NODE_OFFSET; + + if (proposedRightEdge <= requiredLeftEdge) { + layoutNode.setX(newX); + } + } else { + // No right neighbor; safe to move freely to the right + layoutNode.setX(newX); + } + } + + /** + * Attempts to move the specified node to the left within the layer to the given X-coordinate. + * Ensures that the node does not overlap with its left neighbor by checking required spacing. + * If movement is possible without causing overlap, the node's X-coordinate is updated. + * + * @param layoutNode The node to move. + * @param newX The desired new X-coordinate for the node. + */ + public void tryShiftNodeLeft(LayoutNode layoutNode, int newX) { + int currentX = layoutNode.getX(); + int shiftAmount = currentX - newX; + int leftPos = layoutNode.getPos() - 1; + + if (leftPos >= 0) { + // There is a left neighbor + LayoutNode leftNeighbor = get(leftPos); + int proposedLeftEdge = layoutNode.getLeft() - shiftAmount; + int requiredRightEdge = leftNeighbor.getOuterRight() + NODE_OFFSET; + + if (requiredRightEdge <= proposedLeftEdge) { + layoutNode.setX(newX); + } + } else { + // No left neighbor; safe to move freely to the left + layoutNode.setX(newX); + } + } } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java new file mode 100644 index 0000000000000..19f24f1f7e69c --- /dev/null +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 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 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.sun.hotspot.igv.hierarchicallayout; + +import com.sun.hotspot.igv.layout.Vertex; +import java.awt.Point; +import java.util.Set; + +public interface LayoutMover { + /** + * Moves a link by shifting its position along the X-axis. + * + * @param linkPos The current position of the link. + * @param shiftX The amount to shift the link along the X-axis. + */ + void moveLink(Point linkPos, int shiftX); + + /** + * Moves a set of vertices. + * + * @param movedVertices A set of vertices to be moved. + */ + void moveVertices(Set movedVertices); + + /** + * Moves a single vertex. + * + * @param movedVertex The vertex to be moved. + */ + void moveVertex(Vertex movedVertex); +} + diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java index 639ca7af6778f..747297a1bd2a6 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java @@ -63,6 +63,7 @@ public class LayoutNode { private int rightMargin; private int leftMargin; private int pos = -1; // Position within its layer + private boolean reverseLeft = false; private int crossingNumber = 0; @@ -223,6 +224,15 @@ public int getLeft() { return x + leftMargin; } + /** + * Gets the outer left boundary (including left margin) of the node. + * + * @return The x-coordinate of the outer left boundary. + */ + public int getOuterLeft() { + return x; + } + /** * Gets the total width of the node, including left and right margins. * @@ -245,6 +255,15 @@ public int getHeight() { return height; } + /** + * Gets the right boundary (excluding right margin) of the node. + * + * @return The x-coordinate of the right boundary. + */ + public int getRight() { + return x + leftMargin + width; + } + /** * Gets the outer right boundary (including right margin) of the node. * @@ -409,6 +428,50 @@ public void setPos(int pos) { this.pos = pos; } + /** + * Groups the successor edges by their relative x-coordinate from the current node. + * + * @return A map of relative x-coordinate to list of successor edges. + */ + public Map> groupSuccessorsByX() { + Map> result = new HashMap<>(); + for (LayoutEdge succEdge : succs) { + result.computeIfAbsent(succEdge.getRelativeFromX(), k -> new ArrayList<>()).add(succEdge); + } + return result; + } + + private int getBackedgeCrossingScore() { + int score = 0; + for (LayoutEdge predEdge : preds) { + if (predEdge.isReversed()) { + List points = reversedLinkEndPoints.get(predEdge.getLink()); + if (points != null) { + int x0 = points.get(points.size() - 1).x; + int xn = points.get(0).x; + int startPoint = predEdge.getStartX(); + int endPoint = predEdge.getEndX(); + int win = (x0 < xn) ? (startPoint - endPoint) : (endPoint - startPoint); + score += win; + } + } + } + for (LayoutEdge succEdge : succs) { + if (succEdge.isReversed()) { + List points = reversedLinkStartPoints.get(succEdge.getLink()); + if (points != null) { + int x0 = points.get(points.size() - 1).x; + int xn = points.get(0).x; + int startPoint = succEdge.getStartX(); + int endPoint = succEdge.getEndX(); + int win = (x0 > xn) ? (startPoint - endPoint) : (endPoint - startPoint); + score += win; + } + } + } + return score; + } + private boolean computeReversedStartPoints(boolean left) { TreeMap> sortedDownMap = left ? new TreeMap<>() : new TreeMap<>(Collections.reverseOrder()); for (LayoutEdge succEdge : succs) { @@ -491,11 +554,26 @@ private boolean computeReversedEndPoints(boolean left) { } public void computeReversedLinkPoints(boolean reverseLeft) { + this.reverseLeft = reverseLeft; initSize(); reversedLinkStartPoints.clear(); reversedLinkEndPoints.clear(); boolean hasReversedDown = computeReversedStartPoints(reverseLeft); - computeReversedEndPoints(hasReversedDown != reverseLeft); + boolean hasReversedUP = computeReversedEndPoints(hasReversedDown != reverseLeft); + } + + public boolean isReverseRight() { + return !reverseLeft; + } + + public void optimizeBackEdgeCrossing() { + if (reversedLinkStartPoints.isEmpty() && reversedLinkEndPoints.isEmpty()) return; + int orig_score = getBackedgeCrossingScore(); + computeReversedLinkPoints(isReverseRight()); + int reverse_score = getBackedgeCrossingScore(); + if (orig_score > reverse_score) { + computeReversedLinkPoints(isReverseRight()); + } } } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java index 297c7d9b99818..fd57ab632c6d1 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java @@ -81,12 +81,17 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl private final LayerWidget mainLayer; private final LayerWidget blockLayer; private final LayerWidget connectionLayer; + private final Widget shadowWidget; + private final Widget pointerWidget; private final DiagramViewModel model; private ModelState modelState; private boolean rebuilding; + private final Map> outputSlotToLineWidget = new HashMap<>(); + private final Map> inputSlotToLineWidget = new HashMap<>(); private final HierarchicalStableLayoutManager hierarchicalStableLayoutManager; private HierarchicalLayoutManager seaLayoutManager; + private LayoutMover layoutMover; /** @@ -341,6 +346,12 @@ public void select(Widget widget, Point localLocation, boolean invertSelection) mainLayer.setBorder(emptyBorder); addChild(mainLayer); + pointerWidget = new Widget(DiagramScene.this); + addChild(pointerWidget); + + shadowWidget = new Widget(DiagramScene.this); + addChild(shadowWidget); + setLayout(LayoutFactory.createAbsoluteLayout()); getActions().addAction(mouseZoomAction); getActions().addAction(ActionFactory.createPopupMenuAction((widget, localLocation) -> createPopupMenu())); @@ -596,6 +607,163 @@ private void updateFigureWidths() { } } + private MoveProvider getFigureMoveProvider() { + return new MoveProvider() { + + private boolean hasMoved = false; // Flag to track movement + private int startLayerY; + + private void setFigureShadow(Figure f) { + FigureWidget fw = getWidget(f); + Color c = f.getColor(); + Border border = new FigureWidget.RoundedBorder(new Color(0,0,0, 50), 1); + shadowWidget.setBorder(border); + shadowWidget.setBackground(new Color(c.getRed(), c.getGreen(), c.getBlue(), 50)); + shadowWidget.setPreferredLocation(fw.getPreferredLocation()); + shadowWidget.setPreferredSize(f.getSize()); + shadowWidget.setVisible(true); + shadowWidget.setOpaque(true); + shadowWidget.revalidate(); + shadowWidget.repaint(); + } + + private void setMovePointer(Figure f) { + Border border = new FigureWidget.RoundedBorder(Color.RED, 1); + pointerWidget.setBorder(border); + pointerWidget.setBackground(Color.RED); + pointerWidget.setPreferredBounds(new Rectangle(0, 0, 3, f.getSize().height)); + pointerWidget.setVisible(false); + pointerWidget.setOpaque(true); + } + + + @Override + public void movementStarted(Widget widget) { + if (layoutMover == null) return; // Do nothing if layoutMover is not available + + widget.bringToFront(); + startLayerY = widget.getLocation().y; + hasMoved = false; // Reset the movement flag + Set
selectedFigures = model.getSelectedFigures(); + if (selectedFigures.size() == 1) { + Figure selectedFigure = selectedFigures.iterator().next(); + setFigureShadow(selectedFigure); + setMovePointer(selectedFigure); + } + } + + @Override + public void movementFinished(Widget widget) { + shadowWidget.setVisible(false); + pointerWidget.setVisible(false); + if (layoutMover == null || !hasMoved) return; // Do nothing if layoutMover is not available or no movement occurred + rebuilding = true; + + Set
movedFigures = new HashSet<>(model.getSelectedFigures()); + for (Figure figure : movedFigures) { + FigureWidget fw = getWidget(figure); + figure.setPosition(new Point(fw.getLocation().x, fw.getLocation().y)); + } + + layoutMover.moveVertices(movedFigures); + rebuildConnectionLayer(); + + for (FigureWidget fw : getVisibleFigureWidgets()) { + fw.updatePosition(); + } + + validateAll(); + addUndo(); + rebuilding = false; + } + + private static final int MAGNET_SIZE = 5; + + private int magnetToStartLayerY(Widget widget, Point location) { + int shiftY = location.y - widget.getLocation().y; + if (Math.abs(location.y - startLayerY) <= MAGNET_SIZE) { + if (Math.abs(widget.getLocation().y - startLayerY) > MAGNET_SIZE) { + shiftY = startLayerY - widget.getLocation().y; + } else { + shiftY = 0; + } + } + return shiftY; + } + + @Override + public Point getOriginalLocation(Widget widget) { + if (layoutMover == null) return widget.getLocation(); // default behavior + return ActionFactory.createDefaultMoveProvider().getOriginalLocation(widget); + } + + @Override + public void setNewLocation(Widget widget, Point location) { + if (layoutMover == null) return; // Do nothing if layoutMover is not available + hasMoved = true; // Mark that a movement occurred + + int shiftX = location.x - widget.getLocation().x; + int shiftY = magnetToStartLayerY(widget, location); + + List
selectedFigures = new ArrayList<>( model.getSelectedFigures()); + selectedFigures.sort(Comparator.comparingInt(f -> f.getPosition().x)); + for (Figure figure : selectedFigures) { + FigureWidget fw = getWidget(figure); + for (InputSlot inputSlot : figure.getInputSlots()) { + assert inputSlot != null; + if (inputSlotToLineWidget.containsKey(inputSlot)) { + for (LineWidget lw : inputSlotToLineWidget.get(inputSlot)) { + assert lw != null; + Point toPt = lw.getTo(); + Point fromPt = lw.getFrom(); + if (toPt != null && fromPt != null) { + int xTo = toPt.x + shiftX; + int yTo = toPt.y + shiftY; + lw.setTo(new Point(xTo, yTo)); + lw.setFrom(new Point(fromPt.x + shiftX, fromPt.y)); + LineWidget pred = lw.getPredecessor(); + pred.setTo(new Point(pred.getTo().x + shiftX, pred.getTo().y)); + pred.revalidate(); + lw.revalidate(); + } + } + } + } + for (OutputSlot outputSlot : figure.getOutputSlots()) { + assert outputSlot != null; + if (outputSlotToLineWidget.containsKey(outputSlot)) { + for (LineWidget lw : outputSlotToLineWidget.get(outputSlot)) { + assert lw != null; + Point fromPt = lw.getFrom(); + Point toPt = lw.getTo(); + if (toPt != null && fromPt != null) { + int xFrom = fromPt.x + shiftX; + int yFrom = fromPt.y + shiftY; + lw.setFrom(new Point(xFrom, yFrom)); + lw.setTo(new Point(toPt.x + shiftX, toPt.y)); + for (LineWidget succ : lw.getSuccessors()) { + succ.setFrom(new Point(succ.getFrom().x + shiftX, succ.getFrom().y)); + succ.revalidate(); + } + lw.revalidate(); + } + } + } + } + Point newLocation = new Point(fw.getLocation().x + shiftX, fw.getLocation().y + shiftY); + ActionFactory.createDefaultMoveProvider().setNewLocation(fw, newLocation); + } + + FigureWidget fw = getWidget(selectedFigures.iterator().next()); + pointerWidget.setVisible(true); + Point newLocation = new Point(fw.getLocation().x + shiftX -3, fw.getLocation().y + shiftY); + ActionFactory.createDefaultMoveProvider().setNewLocation(pointerWidget, newLocation); + connectionLayer.revalidate(); + connectionLayer.repaint(); + } + }; + } + private void rebuildMainLayer() { mainLayer.removeChildren(); for (Figure figure : getModel().getDiagram().getFigures()) { @@ -604,6 +772,7 @@ private void rebuildMainLayer() { figureWidget.getActions().addAction(ActionFactory.createPopupMenuAction(figureWidget)); figureWidget.getActions().addAction(selectAction); figureWidget.getActions().addAction(hoverAction); + figureWidget.getActions().addAction(ActionFactory.createMoveAction(null, getFigureMoveProvider())); addObject(figure, figureWidget); mainLayer.addChild(figureWidget); @@ -737,6 +906,7 @@ private boolean isVisibleFigureConnection(FigureConnection figureConnection) { } private void doStableSeaLayout(Set
visibleFigures, Set visibleConnections) { + layoutMover = null; boolean enable = model.getCutEdges(); boolean previous = hierarchicalStableLayoutManager.getCutEdges(); hierarchicalStableLayoutManager.setCutEdges(enable); @@ -749,17 +919,20 @@ private void doStableSeaLayout(Set
visibleFigures, Set visib private void doSeaLayout(Set
visibleFigures, Set visibleConnections) { seaLayoutManager = new HierarchicalLayoutManager(); + layoutMover = seaLayoutManager; seaLayoutManager.setCutEdges(model.getCutEdges()); seaLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); } private void doClusteredLayout(Set
visibleFigures, Set visibleConnections) { + layoutMover = null; HierarchicalClusterLayoutManager clusterLayoutManager = new HierarchicalClusterLayoutManager(); clusterLayoutManager.setCutEdges(model.getCutEdges()); clusterLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); } private void doCFGLayout(Set
visibleFigures, Set visibleConnections) { + layoutMover = null; HierarchicalCFGLayoutManager cfgLayoutManager = new HierarchicalCFGLayoutManager(getVisibleBlockConnections(), getVisibleBlocks()); cfgLayoutManager.setCutEdges(model.getCutEdges()); cfgLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); @@ -777,6 +950,85 @@ private boolean shouldAnimate() { private final Point specialNullPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE); + + private MoveProvider getFigureConnectionMoveProvider() { + return new MoveProvider() { + + Point startLocation; + Point originalPosition; + + @Override + public void movementStarted(Widget widget) { + if (layoutMover == null) return; // Do nothing if layoutMover is not available + LineWidget lw = (LineWidget) widget; + startLocation = lw.getClientAreaLocation(); + originalPosition = lw.getFrom(); + } + + @Override + public void movementFinished(Widget widget) { + if (layoutMover == null) return; // Do nothing if layoutMover is not available + LineWidget lineWidget = (LineWidget) widget; + if (lineWidget.getPredecessor() == null) return; + if (lineWidget.getSuccessors().isEmpty()) return; + if (lineWidget.getFrom().x != lineWidget.getTo().x) return; + + int shiftX = lineWidget.getClientAreaLocation().x - startLocation.x; + if (shiftX == 0) return; + + rebuilding = true; + layoutMover.moveLink(originalPosition, shiftX); + rebuildConnectionLayer(); + for (FigureWidget fw : getVisibleFigureWidgets()) { + fw.updatePosition(); + } + validateAll(); + addUndo(); + rebuilding = false; + } + + @Override + public Point getOriginalLocation(Widget widget) { + if (layoutMover == null) return widget.getLocation(); // default behavior + LineWidget lineWidget = (LineWidget) widget; + return lineWidget.getClientAreaLocation(); + } + + @Override + public void setNewLocation(Widget widget, Point location) { + if (layoutMover == null) return; // Do nothing if layoutMover is not available + LineWidget lineWidget = (LineWidget) widget; + if (lineWidget.getPredecessor() == null) return; + if (lineWidget.getSuccessors().isEmpty()) return; + if (lineWidget.getFrom().x != lineWidget.getTo().x) return; + + int shiftX = location.x - lineWidget.getClientAreaLocation().x; + if (shiftX == 0) return; + + Point oldFrom = lineWidget.getFrom(); + Point newFrom = new Point(oldFrom.x + shiftX, oldFrom.y); + + Point oldTo = lineWidget.getTo(); + Point newTo = new Point(oldTo.x + shiftX, oldTo.y); + + lineWidget.setTo(newTo); + lineWidget.setFrom(newFrom); + lineWidget.revalidate(); + + LineWidget predecessor = lineWidget.getPredecessor(); + Point toPt = predecessor.getTo(); + predecessor.setTo(new Point(toPt.x + shiftX, toPt.y)); + predecessor.revalidate(); + + for (LineWidget successor : lineWidget.getSuccessors()) { + Point fromPt = successor.getFrom(); + successor.setFrom(new Point(fromPt.x + shiftX, fromPt.y)); + successor.revalidate(); + } + } + }; + } + private void processOutputSlot(OutputSlot outputSlot, List connections, int controlPointIndex, Point lastPoint, LineWidget predecessor) { Map> pointMap = new HashMap<>(connections.size()); @@ -824,6 +1076,7 @@ private void processOutputSlot(OutputSlot outputSlot, List con newPredecessor.setVisible(isVisible); if (predecessor == null) { + assert outputSlot != null; if (outputSlotToLineWidget.containsKey(outputSlot)) { outputSlotToLineWidget.get(outputSlot).add(newPredecessor); } else { @@ -834,6 +1087,7 @@ private void processOutputSlot(OutputSlot outputSlot, List con newWidgets.add(newPredecessor); addObject(new ConnectionSet(connectionList), newPredecessor); newPredecessor.getActions().addAction(hoverAction); + newPredecessor.getActions().addAction(ActionFactory.createMoveAction(null, getFigureConnectionMoveProvider())); } processOutputSlot(outputSlot, connectionList, controlPointIndex + 1, currentPoint, newPredecessor); @@ -843,10 +1097,13 @@ private void processOutputSlot(OutputSlot outputSlot, List con for (FigureConnection connection : connections) { if (isVisibleFigureConnection(connection)) { InputSlot inputSlot = connection.getInputSlot(); - if (inputSlotToLineWidget.containsKey(inputSlot)) { - inputSlotToLineWidget.get(inputSlot).add(predecessor); - } else { - inputSlotToLineWidget.put(inputSlot, new HashSet<>(Collections.singleton(predecessor))); + if (predecessor != null) { + assert inputSlot != null; + if (inputSlotToLineWidget.containsKey(inputSlot)) { + inputSlotToLineWidget.get(inputSlot).add(predecessor); + } else { + inputSlotToLineWidget.put(inputSlot, new HashSet<>(Collections.singleton(predecessor))); + } } } } @@ -1212,9 +1469,6 @@ private void updateBlockWidgetBounds(Set oldVisibleBlockWidgets) { } } - Map> outputSlotToLineWidget = new HashMap<>(); - Map> inputSlotToLineWidget = new HashMap<>(); - public JPopupMenu createPopupMenu() { JPopupMenu menu = new JPopupMenu(); diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java index 6b9b8e548d108..24780d4d7d29c 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java @@ -185,6 +185,10 @@ protected Sheet createSheet() { this.setToolTipText(PropertiesConverter.convertToHTML(f.getProperties())); } + public void updatePosition() { + setPreferredLocation(figure.getPosition()); + } + public int getFigureHeight() { return middleWidget.getPreferredBounds().height; } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java index d587bf208b41d..3cce782e4373f 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java @@ -57,8 +57,8 @@ public class LineWidget extends Widget implements PopupMenuProvider { private final OutputSlot outputSlot; private final DiagramScene scene; private final List connections; - private final Point from; - private final Point to; + private Point from; + private Point to; private Rectangle clientArea; private final LineWidget predecessor; private final List successors; @@ -97,6 +97,10 @@ public LineWidget(DiagramScene scene, OutputSlot s, List c setBackground(color); } + public Point getClientAreaLocation() { + return clientArea.getLocation(); + } + private void computeClientArea() { int minX = from.x; int minY = from.y; @@ -130,6 +134,16 @@ private String generateToolTipText(List conn) { return sb.toString(); } + public void setFrom(Point from) { + this.from = from; + computeClientArea(); + } + + public void setTo(Point to) { + this.to= to; + computeClientArea(); + } + public Point getFrom() { return from; } @@ -138,6 +152,14 @@ public Point getTo() { return to; } + public LineWidget getPredecessor() { + return predecessor; + } + + public List getSuccessors() { + return Collections.unmodifiableList(successors); + } + private void addSuccessor(LineWidget widget) { this.successors.add(widget); } From 4f7ca8ee5ae510f0e3614677494a9ed92a843daf Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Thu, 28 Nov 2024 14:31:54 +0100 Subject: [PATCH 16/30] JDK-8345041 IGV: Free Placement Mode in IGV Layout fix --- .../FreeInteractiveLayoutManager.java | 324 ++++++++++++++++++ .../HierarchicalLayoutManager.java | 5 + .../igv/hierarchicallayout/LayoutGraph.java | 57 +++ .../igv/hierarchicallayout/LayoutMover.java | 2 + .../sun/hotspot/igv/settings/Settings.java | 1 + .../sun/hotspot/igv/view/DiagramScene.java | 105 +++++- .../hotspot/igv/view/DiagramViewModel.java | 15 +- .../hotspot/igv/view/EditorTopComponent.java | 5 + .../view/actions/EnableFreeLayoutAction.java | 49 +++ .../hotspot/igv/view/widgets/LineWidget.java | 69 +++- .../sun/hotspot/igv/view/images/dynamic.png | Bin 0 -> 2218 bytes 11 files changed, 612 insertions(+), 20 deletions(-) create mode 100644 src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java create mode 100644 src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java create mode 100644 src/utils/IdealGraphVisualizer/View/src/main/resources/com/sun/hotspot/igv/view/images/dynamic.png diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java new file mode 100644 index 0000000000000..d221d78f8620c --- /dev/null +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 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 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.sun.hotspot.igv.hierarchicallayout; + +import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.LAYOUT_NODE_DEGREE_COMPARATOR; +import com.sun.hotspot.igv.layout.Link; +import com.sun.hotspot.igv.layout.Vertex; +import java.awt.Point; +import java.util.*; + +public class FreeInteractiveLayoutManager extends LayoutManager implements LayoutMover { + + private boolean cutEdges = false; + + private static final int LINE_OFFSET = 10; + + private Map layoutNodes; + + private LayoutGraph prevGraph; + + private final Random random = new Random(42); + + // Constants for offsets and displacements + private static final int MAX_OFFSET_AROUND_NEIGHBOR = 200; // Max offset for random positioning around a neighbor + private static final int MAX_OFFSET_AROUND_ORIGIN = 200; // Max offset for random positioning around origin + private static final int DISPLACEMENT_RANGE_BARYCENTER = 100; // Displacement range for barycenter calculation + private static final int DISPLACEMENT_RANGE_SINGLE = 200; + + // Create a comparator to sort nodes by the number of unassigned neighbors + private final Comparator LeastUnassignedNeighborsComparator = Comparator.comparingInt(node -> { + Vertex vertex = node.getVertex(); + int unassignedNeighbors = 0; + for (Vertex neighborVertex : prevGraph.getNeighborVertices(vertex)) { + if (!layoutNodes.containsKey(neighborVertex)) { + unassignedNeighbors++; + } + } + return unassignedNeighbors; + }); + + public FreeInteractiveLayoutManager() { + this.cutEdges = false; + this.layoutNodes = new HashMap<>(); + this.prevGraph = null; + } + + @Override + public void moveLink(Point linkPos, int shiftX) {} + + @Override + public void moveVertices(Set movedVertices) { + for (Vertex v : movedVertices) { + moveVertex(v); + } + } + + @Override + public void moveVertex(Vertex vertex) { + assert prevGraph.containsVertex(vertex); + LayoutNode layoutNode = layoutNodes.get(vertex); + layoutNode.setX(vertex.getPosition().x); + layoutNode.setY(vertex.getPosition().y); + for (Link link : prevGraph.getAllLinks(vertex)) { + setLinkControlPoints(link); + } + } + + @Override + public boolean isFreeForm() { + return true; + } + + public void setCutEdges(boolean enable) { + this.cutEdges = enable; + } + + @Override + public void doLayout(LayoutGraph graph) { + prevGraph = graph; + if (layoutNodes.isEmpty()) { + HierarchicalLayoutManager manager = new HierarchicalLayoutManager(); + manager.doLayout(graph); + for (LayoutNode node : graph.getLayoutNodes()) { + node.initSize(); + layoutNodes.put(node.getVertex(), node); + } + graph.clearLayout(); + } else { + // add new vertices to layoutNodes, x/y from barycenter + List newLayoutNodes = new ArrayList<>(); + + // Set up layout nodes for each vertex + for (Vertex vertex : prevGraph.getVertices()) { + if (!layoutNodes.containsKey(vertex)) { + LayoutNode addedNode = new LayoutNode(vertex); + addedNode.initSize(); + newLayoutNodes.add(addedNode); + } + } + + positionNewLayoutNodes(newLayoutNodes); + } + + // Write back vertices + for (Vertex vertex : prevGraph.getVertices()) { + LayoutNode layoutNode = layoutNodes.get(vertex); + vertex.setPosition(new Point(layoutNode.getLeft(), layoutNode.getTop())); + } + + // Write back links + for (Link link : prevGraph.getLinks()) { + setLinkControlPoints(link); + } + } + + public void positionNewLayoutNodes(List newLayoutNodes) { + Random random = new Random(); + + // First pass: Initial positioning based on unassigned neighbors + newLayoutNodes.sort(LeastUnassignedNeighborsComparator); + + for (LayoutNode node : newLayoutNodes) { + Vertex vertex = node.getVertex(); + + // Gather assigned neighbors + List assignedNeighbors = new ArrayList<>(); + for (Vertex neighborVertex : prevGraph.getNeighborVertices(vertex)) { + if (layoutNodes.containsKey(neighborVertex)) { + assignedNeighbors.add(layoutNodes.get(neighborVertex)); + } + } + + if (!assignedNeighbors.isEmpty()) { + if (assignedNeighbors.size() == 1) { + // Single neighbor: position around the neighbor + setPositionAroundSingleNode(node, assignedNeighbors.get(0), DISPLACEMENT_RANGE_SINGLE); + } else { + // Multiple neighbors: Calculate barycenter with displacement + calculateBarycenterWithDisplacement(node, assignedNeighbors, DISPLACEMENT_RANGE_BARYCENTER); + } + } else { + // No neighbors: Position randomly around (0, 0) + setRandomPositionAroundOrigin(node, random); + } + + // Add the new node to the layout + layoutNodes.put(vertex, node); + } + + // Second pass: Refine positions based on neighbor degree + newLayoutNodes.sort(LAYOUT_NODE_DEGREE_COMPARATOR.reversed()); + + // Collect all nodes (existing and new) + Collection allNodes = layoutNodes.values(); + + for (LayoutNode node : newLayoutNodes) { + Vertex vertex = node.getVertex(); + + // Gather assigned neighbors + List assignedNeighbors = new ArrayList<>(); + for (Vertex neighborVertex : prevGraph.getNeighborVertices(vertex)) { + if (layoutNodes.containsKey(neighborVertex)) { + assignedNeighbors.add(layoutNodes.get(neighborVertex)); + } + } + + if (!assignedNeighbors.isEmpty()) { + // Refine position based on force-based method + applyForceBasedAdjustment(node, assignedNeighbors, allNodes); + } + + // Ensure node's position remains updated in the layout + layoutNodes.put(vertex, node); + } + } + + private void applyForceBasedAdjustment(LayoutNode node, List assignedNeighbors, Collection allNodes) { + // Constants for force-based adjustment + final int ITERATIONS = 50; + final double REPULSION_CONSTANT = 1000; + final double SPRING_CONSTANT = 0.2; + final double DAMPING = 0.8; + final double IDEAL_LENGTH = 100; + + double posX = node.getX(); + double posY = node.getY(); + double dx = 0, dy = 0; // Displacement + + for (int i = 0; i < ITERATIONS; i++) { + double netForceX = 0; + double netForceY = 0; + + // Repulsive forces from all other nodes + for (LayoutNode otherNode : allNodes) { + if (otherNode == node) continue; // Skip self + + double deltaX = posX - otherNode.getX(); + double deltaY = posY - otherNode.getY(); + double distanceSquared = deltaX * deltaX + deltaY * deltaY + 0.01; // Prevent division by zero + double distance = Math.sqrt(distanceSquared); + + // Repulsive force (Coulomb's law) + double repulsiveForce = REPULSION_CONSTANT / distanceSquared; + netForceX += (deltaX / distance) * repulsiveForce; + netForceY += (deltaY / distance) * repulsiveForce; + } + + // Attractive forces to assigned neighbors + for (LayoutNode neighbor : assignedNeighbors) { + double deltaX = neighbor.getX() - posX; + double deltaY = neighbor.getY() - posY; + double distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY + 0.01); // Prevent division by zero + + // Attractive force (Hooke's law) + double displacement = distance - IDEAL_LENGTH; + double attractiveForce = SPRING_CONSTANT * displacement; + netForceX += (deltaX / distance) * attractiveForce; + netForceY += (deltaY / distance) * attractiveForce; + } + + // Update displacement with damping + dx = (dx + netForceX) * DAMPING; + dy = (dy + netForceY) * DAMPING; + + // Update node position + posX += dx; + posY += dy; + } + + // Set final position + node.setX((int) posX); + node.setY((int) posY); + } + + + // Utility method: position around a given node + private void setPositionAroundSingleNode(LayoutNode node, LayoutNode neighbor, int displacement) { + boolean neighborIsPredecessor = prevGraph.isPredecessorVertex(node.getVertex(), neighbor.getVertex()); + boolean neighborIsSuccessor = prevGraph.isSuccessorVertex(node.getVertex(), neighbor.getVertex()); + + int shiftY = 0; + if (neighborIsPredecessor) { + shiftY = displacement; + } else if (neighborIsSuccessor) { + shiftY = -displacement; + } + assert shiftY != 0; + + + int randomY = neighbor.getY() + random.nextInt(MAX_OFFSET_AROUND_NEIGHBOR + 1) + shiftY; + int randomX = neighbor.getX() + random.nextInt(MAX_OFFSET_AROUND_NEIGHBOR + 1); + node.setX(randomX); + node.setY(randomY); + } + + // Utility method: Random position around origin + private void setRandomPositionAroundOrigin(LayoutNode node, Random random) { + int randomX = random.nextInt(MAX_OFFSET_AROUND_ORIGIN + 1); + int randomY = random.nextInt(MAX_OFFSET_AROUND_ORIGIN + 1); + node.setX(randomX); + node.setY(randomY); + } + + // Utility method: Calculate barycenter with displacement + private void calculateBarycenterWithDisplacement(LayoutNode node, List neighbors, int displacementRange) { + double barycenterX = 0, barycenterY = 0; + for (LayoutNode neighbor : neighbors) { + barycenterX += neighbor.getX(); + barycenterY += neighbor.getY(); + } + barycenterX /= neighbors.size(); + barycenterY /= neighbors.size(); + + // Add random displacement for slight separation + int displacementX = random.nextInt(displacementRange + 1); + int displacementY = random.nextInt(displacementRange + 1); + node.setX((int) barycenterX + displacementX); + node.setY((int) barycenterY + displacementY); + } + + private void setLinkControlPoints(Link link) { + if (link.getFrom().getVertex() == link.getTo().getVertex()) return; + + LayoutNode from = layoutNodes.get(link.getFrom().getVertex()); + LayoutNode to = layoutNodes.get(link.getTo().getVertex()); + int relativeFromX = link.getFrom().getRelativePosition().x; + int relativeToX = link.getTo().getRelativePosition().x; + + int startX = from.getLeft() + relativeFromX; + int startY = from.getBottom(); + int endX = to.getLeft() + relativeToX; + int endY = to.getTop(); + + Point startPoint = new Point(startX, startY); + Point endPoint = new Point(endX, endY); + List line = new ArrayList<>(); + line.add(startPoint); + line.add(new Point(startPoint.x, startPoint.y + LINE_OFFSET)); + line.add(new Point(endPoint.x, endPoint.y - LINE_OFFSET)); + line.add(endPoint); + link.setControlPoints(line); + } +} diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java index a4c63a0132180..76ac119741fb6 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java @@ -125,6 +125,11 @@ public void moveVertex(Vertex movedVertex) { } } + @Override + public boolean isFreeForm() { + return false; + } + public List getNodes() { return graph.getAllNodes(); } diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java index 763b8ffad0515..fcc37bcdd79a0 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java @@ -432,6 +432,63 @@ public List getOutputLinks(Vertex vertex) { return outputLinks; } + /** + * Checks if the given predecessorVertex is a direct predecessor of the specified vertex. + * + * @param vertex The vertex to check for predecessors. + * @param predecessorVertex The vertex to verify as a predecessor of the given vertex. + * @return true if predecessorVertex is a direct predecessor of vertex, false otherwise. + */ + public boolean isPredecessorVertex(Vertex vertex, Vertex predecessorVertex) { + for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { + for (Link inputLink : portLinks.getOrDefault(inputPort, Collections.emptySet())) { + Vertex fromVertex = inputLink.getFrom().getVertex(); + if (fromVertex.equals(predecessorVertex)) { + return true; + } + } + } + return false; + } + + /** + * Checks if the given successorVertex is a direct successor of the specified vertex. + * + * @param vertex The vertex to check for successors. + * @param successorVertex The vertex to verify as a successor of the given vertex. + * @return true if successorVertex is a direct successor of vertex, false otherwise. + */ + public boolean isSuccessorVertex(Vertex vertex, Vertex successorVertex) { + for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) { + for (Link outputLink : portLinks.getOrDefault(outputPort, Collections.emptySet())) { + Vertex toVertex = outputLink.getTo().getVertex(); + if (toVertex.equals(successorVertex)) { + return true; + } + } + } + return false; + } + + public List getNeighborVertices(Vertex vertex) { + List neighborVertices = new ArrayList<>(); + for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) { + for (Link inputLink : portLinks.getOrDefault(inputPort, Collections.emptySet())) { + Vertex fromVertex = inputLink.getFrom().getVertex(); + assert fromVertex != null; + neighborVertices.add(fromVertex); + } + } + for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) { + for (Link outputLink : portLinks.getOrDefault(outputPort, Collections.emptySet())) { + Vertex toVertex = outputLink.getTo().getVertex(); + assert toVertex != null; + neighborVertices.add(toVertex); + } + } + return neighborVertices; + } + public List getAllLinks(Vertex vertex) { List allLinks = new ArrayList<>(); diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java index 19f24f1f7e69c..48599d78d7580 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java @@ -49,5 +49,7 @@ public interface LayoutMover { * @param movedVertex The vertex to be moved. */ void moveVertex(Vertex movedVertex); + + boolean isFreeForm(); } diff --git a/src/utils/IdealGraphVisualizer/Settings/src/main/java/com/sun/hotspot/igv/settings/Settings.java b/src/utils/IdealGraphVisualizer/Settings/src/main/java/com/sun/hotspot/igv/settings/Settings.java index 41af4a1d26282..480fb43ea5b8d 100644 --- a/src/utils/IdealGraphVisualizer/Settings/src/main/java/com/sun/hotspot/igv/settings/Settings.java +++ b/src/utils/IdealGraphVisualizer/Settings/src/main/java/com/sun/hotspot/igv/settings/Settings.java @@ -37,6 +37,7 @@ public static class DefaultView { public static final int CLUSTERED_SEA_OF_NODES = 1; public static final int CONTROL_FLOW_GRAPH = 2; public static final int STABLE_SEA_OF_NODES = 3; + public static final int INTERACTIVE_FREE_NODES = 4; } public static final String NODE_TEXT = "nodeText"; diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java index fd57ab632c6d1..7dcacb24ba51c 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java @@ -89,6 +89,7 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl private final Map> outputSlotToLineWidget = new HashMap<>(); private final Map> inputSlotToLineWidget = new HashMap<>(); + private final FreeInteractiveLayoutManager freeInteractiveLayoutManager; private final HierarchicalStableLayoutManager hierarchicalStableLayoutManager; private HierarchicalLayoutManager seaLayoutManager; private LayoutMover layoutMover; @@ -512,6 +513,7 @@ public void ancestorResized(HierarchyEvent e) { } }); + freeInteractiveLayoutManager = new FreeInteractiveLayoutManager(); hierarchicalStableLayoutManager = new HierarchicalStableLayoutManager(); seaLayoutManager = new HierarchicalLayoutManager(); @@ -644,6 +646,7 @@ public void movementStarted(Widget widget) { widget.bringToFront(); startLayerY = widget.getLocation().y; hasMoved = false; // Reset the movement flag + if (layoutMover.isFreeForm()) return; Set
selectedFigures = model.getSelectedFigures(); if (selectedFigures.size() == 1) { Figure selectedFigure = selectedFigures.iterator().next(); @@ -703,8 +706,12 @@ public void setNewLocation(Widget widget, Point location) { hasMoved = true; // Mark that a movement occurred int shiftX = location.x - widget.getLocation().x; - int shiftY = magnetToStartLayerY(widget, location); - + int shiftY; + if (layoutMover.isFreeForm()) { + shiftY = location.y - widget.getLocation().y; + } else { + shiftY = magnetToStartLayerY(widget, location); + } List
selectedFigures = new ArrayList<>( model.getSelectedFigures()); selectedFigures.sort(Comparator.comparingInt(f -> f.getPosition().x)); for (Figure figure : selectedFigures) { @@ -715,11 +722,11 @@ public void setNewLocation(Widget widget, Point location) { for (LineWidget lw : inputSlotToLineWidget.get(inputSlot)) { assert lw != null; Point toPt = lw.getTo(); - Point fromPt = lw.getFrom(); - if (toPt != null && fromPt != null) { - int xTo = toPt.x + shiftX; - int yTo = toPt.y + shiftY; - lw.setTo(new Point(xTo, yTo)); + int xTo = toPt.x + shiftX; + int yTo = toPt.y + shiftY; + lw.setTo(new Point(xTo, yTo)); + if (!layoutMover.isFreeForm()) { + Point fromPt = lw.getFrom(); lw.setFrom(new Point(fromPt.x + shiftX, fromPt.y)); LineWidget pred = lw.getPredecessor(); pred.setTo(new Point(pred.getTo().x + shiftX, pred.getTo().y)); @@ -735,11 +742,11 @@ public void setNewLocation(Widget widget, Point location) { for (LineWidget lw : outputSlotToLineWidget.get(outputSlot)) { assert lw != null; Point fromPt = lw.getFrom(); - Point toPt = lw.getTo(); - if (toPt != null && fromPt != null) { - int xFrom = fromPt.x + shiftX; - int yFrom = fromPt.y + shiftY; - lw.setFrom(new Point(xFrom, yFrom)); + int xFrom = fromPt.x + shiftX; + int yFrom = fromPt.y + shiftY; + lw.setFrom(new Point(xFrom, yFrom)); + if (!layoutMover.isFreeForm()) { + Point toPt = lw.getTo(); lw.setTo(new Point(toPt.x + shiftX, toPt.y)); for (LineWidget succ : lw.getSuccessors()) { succ.setFrom(new Point(succ.getFrom().x + shiftX, succ.getFrom().y)); @@ -754,10 +761,12 @@ public void setNewLocation(Widget widget, Point location) { ActionFactory.createDefaultMoveProvider().setNewLocation(fw, newLocation); } - FigureWidget fw = getWidget(selectedFigures.iterator().next()); - pointerWidget.setVisible(true); - Point newLocation = new Point(fw.getLocation().x + shiftX -3, fw.getLocation().y + shiftY); - ActionFactory.createDefaultMoveProvider().setNewLocation(pointerWidget, newLocation); + if (selectedFigures.size() == 1 && !layoutMover.isFreeForm()) { + FigureWidget fw = getWidget(selectedFigures.iterator().next()); + pointerWidget.setVisible(true); + Point newLocation = new Point(fw.getLocation().x + shiftX -3, fw.getLocation().y + shiftY); + ActionFactory.createDefaultMoveProvider().setNewLocation(pointerWidget, newLocation); + } connectionLayer.revalidate(); connectionLayer.repaint(); } @@ -835,7 +844,10 @@ private void relayout() { Set
visibleFigures = getVisibleFigures(); Set visibleConnections = getVisibleConnections(); - if (getModel().getShowStableSea()) { + if (getModel().getShowFreeInteractive()) { + doFreeInteractiveLayout(visibleFigures, visibleConnections); + } + else if (getModel().getShowStableSea()) { doStableSeaLayout(visibleFigures, visibleConnections); } else if (getModel().getShowSea()) { doSeaLayout(visibleFigures, visibleConnections); @@ -905,6 +917,12 @@ private boolean isVisibleFigureConnection(FigureConnection figureConnection) { return w1.isVisible() && w2.isVisible(); } + private void doFreeInteractiveLayout(Set
visibleFigures, Set visibleConnections) { + layoutMover = freeInteractiveLayoutManager; + freeInteractiveLayoutManager.setCutEdges(model.getCutEdges()); + freeInteractiveLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures)); + } + private void doStableSeaLayout(Set
visibleFigures, Set visibleConnections) { layoutMover = null; boolean enable = model.getCutEdges(); @@ -1110,6 +1128,53 @@ private void processOutputSlot(OutputSlot outputSlot, List con } } + private void processFreeForm(OutputSlot outputSlot, List connections) { + for (FigureConnection connection : connections) { + if (isVisibleFigureConnection(connection)) { + boolean isBold = false; + boolean isDashed = true; + boolean isVisible = true; + if (connection.getStyle() == Connection.ConnectionStyle.BOLD) { + isBold = true; + } else if (connection.getStyle() == Connection.ConnectionStyle.INVISIBLE) { + isVisible = false; + } + if (connection.getStyle() != Connection.ConnectionStyle.DASHED) { + isDashed = false; + } + + + List controlPoints = connection.getControlPoints(); + if (controlPoints.size() <= 2) continue; + Point firstPoint = controlPoints.get(0); // First point + Point lastPoint = controlPoints.get(controlPoints.size() - 1); // Last point + List connectionList = new ArrayList<>(Collections.singleton(connection)); + LineWidget line = new LineWidget(this, outputSlot, connectionList, firstPoint, lastPoint, null, isBold, isDashed); + line.setFromControlYOffset(50); + line.setToControlYOffset(-50); + line.setVisible(isVisible); + connectionLayer.addChild(line); + + addObject(new ConnectionSet(connectionList), line); + line.getActions().addAction(hoverAction); + + if (outputSlotToLineWidget.containsKey(outputSlot)) { + outputSlotToLineWidget.get(outputSlot).add(line); + } else { + outputSlotToLineWidget.put(outputSlot, new HashSet<>(Collections.singleton(line))); + } + + InputSlot inputSlot = connection.getInputSlot(); + if (inputSlotToLineWidget.containsKey(inputSlot)) { + inputSlotToLineWidget.get(inputSlot).add(line); + } else { + inputSlotToLineWidget.put(inputSlot, new HashSet<>(Collections.singleton(line))); + } + } + } + } + + private void processBlockConnection(BlockConnection blockConnection) { boolean isDashed = blockConnection.getStyle() == Connection.ConnectionStyle.DASHED; boolean isBold = blockConnection.getStyle() == Connection.ConnectionStyle.BOLD; @@ -1283,7 +1348,11 @@ private void rebuildConnectionLayer() { for (Figure figure : getModel().getDiagram().getFigures()) { for (OutputSlot outputSlot : figure.getOutputSlots()) { List connectionList = new ArrayList<>(outputSlot.getConnections()); - processOutputSlot(outputSlot, connectionList, 0, null, null); + if (layoutMover != null && layoutMover.isFreeForm()) { + processFreeForm(outputSlot, connectionList); + } else { + processOutputSlot(outputSlot, connectionList, 0, null, null); + } } } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewModel.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewModel.java index 3081825025fe2..2f06a257c7bb1 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewModel.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewModel.java @@ -63,6 +63,7 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene private final ChangedEvent selectedNodesChangedEvent = new ChangedEvent<>(this); private final ChangedEvent hiddenNodesChangedEvent = new ChangedEvent<>(this); private ChangedListener titleChangedListener = g -> {}; + private boolean showFreeInteractive; private boolean showStableSea; private boolean showSea; private boolean showBlocks; @@ -104,6 +105,17 @@ public void setGlobalSelection(boolean enable, boolean fire) { } } + public boolean getShowFreeInteractive() { + return showFreeInteractive; + } + + public void setShowFreeInteractive(boolean enable) { + showFreeInteractive = enable; + if (enable) { + diagramChangedEvent.fire(); + } + } + public boolean getShowStableSea() { return showStableSea; } @@ -224,6 +236,7 @@ public DiagramViewModel(InputGraph graph) { globalSelection = GlobalSelectionAction.get(GlobalSelectionAction.class).isSelected(); cutEdges = CutEdgesAction.get(CutEdgesAction.class).isSelected(); + showFreeInteractive = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.INTERACTIVE_FREE_NODES; showStableSea = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.STABLE_SEA_OF_NODES; showSea = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.SEA_OF_NODES; showBlocks = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.CLUSTERED_SEA_OF_NODES; @@ -266,7 +279,7 @@ public void setSelectedNodes(Set nodes) { for (String ignored : getPositions()) { colors.add(Color.black); } - if (nodes.size() >= 1) { + if (!nodes.isEmpty()) { for (Integer id : nodes) { if (id < 0) { id = -id; diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java index d067925fb87b7..ff51b8928f676 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java @@ -176,6 +176,11 @@ public void mouseMoved(MouseEvent e) {} toolBar.addSeparator(); ButtonGroup layoutButtons = new ButtonGroup(); + JToggleButton freeInteractiveLayoutButton = new JToggleButton(new EnableFreeLayoutAction(this)); + freeInteractiveLayoutButton.setSelected(diagramViewModel.getShowFreeInteractive()); + layoutButtons.add(freeInteractiveLayoutButton); + toolBar.add(freeInteractiveLayoutButton); + JToggleButton stableSeaLayoutButton = new JToggleButton(new EnableStableSeaLayoutAction(this)); stableSeaLayoutButton.setSelected(diagramViewModel.getShowStableSea()); layoutButtons.add(stableSeaLayoutButton); diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java new file mode 100644 index 0000000000000..201fc51e15e9e --- /dev/null +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 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 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.sun.hotspot.igv.view.actions; + +import com.sun.hotspot.igv.view.EditorTopComponent; +import java.beans.PropertyChangeEvent; + +public class EnableFreeLayoutAction extends EnableLayoutAction { + + public EnableFreeLayoutAction(EditorTopComponent etc) { + super(etc); + } + + @Override + protected String iconResource() { + return "com/sun/hotspot/igv/view/images/dynamic.png"; + } + + @Override + protected String getDescription() { + return "Show dynamic free layout"; + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + editor.getModel().setShowFreeInteractive(this.isSelected()); + } +} diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java index 3cce782e4373f..ee94f7a36744b 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java @@ -30,6 +30,7 @@ import com.sun.hotspot.igv.util.StringUtils; import com.sun.hotspot.igv.view.DiagramScene; import java.awt.*; +import java.awt.geom.CubicCurve2D; import java.awt.geom.Line2D; import java.util.*; import java.util.List; @@ -67,6 +68,8 @@ public class LineWidget extends Widget implements PopupMenuProvider { private final boolean isBold; private final boolean isDashed; private boolean needToInitToolTipText = true; + private int fromControlYOffset; + private int toControlYOffset; public LineWidget(DiagramScene scene, OutputSlot s, List connections, Point from, Point to, LineWidget predecessor, boolean isBold, boolean isDashed) { super(scene); @@ -107,6 +110,26 @@ private void computeClientArea() { int maxX = to.x; int maxY = to.y; + if (fromControlYOffset != 0 && toControlYOffset != 0) { + // Adjust the bounding box to accommodate control points for curves + if (from.y < to.y) { // non-reversed edges + minY = Math.min(minY, from.y + fromControlYOffset); + maxY = Math.max(maxY, to.y + toControlYOffset); + } else { // reversed edges + if (from.x - to.x > 0) { + minX = Math.min(minX, from.x); + maxX = Math.max(maxX, to.x); + minY = Math.min(minY, from.y + fromControlYOffset); + maxY = Math.max(maxY, to.y + toControlYOffset); + } else { + minX = Math.min(minX, from.x); + maxX = Math.max(maxX, to.x); + minY = Math.min(minY, from.y + fromControlYOffset); + maxY = Math.max(maxY, to.y + toControlYOffset); + } + } + } + // Ensure min and max values are correct if (minX > maxX) { int tmp = minX; @@ -144,6 +167,16 @@ public void setTo(Point to) { computeClientArea(); } + public void setFromControlYOffset(int fromControlYOffset) { + this.fromControlYOffset = fromControlYOffset; + computeClientArea(); + } + + public void setToControlYOffset(int toControlYOffset) { + this.toControlYOffset = toControlYOffset; + computeClientArea(); + } + public Point getFrom() { return from; } @@ -197,7 +230,41 @@ protected void paintWidget() { g.setStroke(new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER)); } - g.drawLine(from.x, from.y, to.x, to.y); + // Define S-shaped curve with control points + if (fromControlYOffset != 0 && toControlYOffset != 0) { + if (from.y < to.y) { // non-reversed edges + if (Math.abs(from.x - to.x) > 10) { + CubicCurve2D.Float sShape = new CubicCurve2D.Float(); + sShape.setCurve(from.x, from.y, + from.x, from.y + fromControlYOffset, + to.x, to.y + toControlYOffset, + to.x, to.y); + g.draw(sShape); + } else { + g.drawLine(from.x, from.y, to.x, to.y); + } + } else { // reverse edges + if (from.x - to.x > 0) { + CubicCurve2D.Float sShape = new CubicCurve2D.Float(); + sShape.setCurve(from.x, from.y, + from.x - 150, from.y + fromControlYOffset, + to.x + 150, to.y + toControlYOffset, + to.x, to.y); + g.draw(sShape); + } else { + // add x offset + CubicCurve2D.Float sShape = new CubicCurve2D.Float(); + sShape.setCurve(from.x, from.y, + from.x + 150, from.y + fromControlYOffset, + to.x - 150, to.y + toControlYOffset, + to.x, to.y); + g.draw(sShape); + } + } + } else { + // Fallback to straight line if control points are not set + g.drawLine(from.x, from.y, to.x, to.y); + } boolean sameFrom = false; boolean sameTo = successors.isEmpty(); diff --git a/src/utils/IdealGraphVisualizer/View/src/main/resources/com/sun/hotspot/igv/view/images/dynamic.png b/src/utils/IdealGraphVisualizer/View/src/main/resources/com/sun/hotspot/igv/view/images/dynamic.png new file mode 100644 index 0000000000000000000000000000000000000000..781b98176f8657d705976bf1ceb15e78a07f8098 GIT binary patch literal 2218 zcmZ{l4Ny}@5P&a${A2)8X;r8a!>EXmmmfh0Nh&4^!GZx|8`Nq;^1xR@UM6{w0OBu- z$oPj+9I^N}PKAzCDB@s6l&X*sDdJci1Vqpxh1LQirJ&M#Awob%a=Fd!e!I7KZ!ejW zh!7{L8x;WHBn}lt!P$iz_O|fbqirkUA0>V&3oULTR+{(%|#X@Z`wq^P+wes$<|dgyVCY?L}G@s zbJ5&#HaohYdk^hJ_wHF)=?8l<#pnBv zq~wQg?X4~CEkxYrt!x{-K6m!#1q~gC)y-XN+hd0mLG`SF-si6p?o}C^2i<#^M{74o zQ2WZ#TN6nBgm3WBcZl?ejfyzr7p#uJs|`X{lmSy8buD()JhW zZ<=@@jsid(P zEGB~$005iGW^q~Y!=yoh!{qw00I<&92kam-7mlVgR-+kQ&RCDh&<28|g<>(BKhEk9 zw(SHoIA}s+bhCycZjyuI;gi}8fX!)4DkEg!UvL$8B15jkV^M}7QA5T9JOdYsi6|kb z84^`$9oN98n>@HsCe;X?W^y4C_;eX8M2Krqnm>cdVA2It8jZ%&D&x3OqSa;`{^HZO z5QK({AW2C{j3f>N*Ty5Pz`#I+$wt_0U+CehOI8zdgRffWITGYU91*HhXfX|e;c6Ng zS00P&2|k@pCK`Q>tP?e0W0}-Cb6K!JgtQ$ zsTfVA(Q8$vjZi8ORD~v@YC;D&7BX5tERRR7oGZjtxHeiNSD*qG&s2=1YHTM)yd{_c zRmnscjHrXVCSduSnTdu;!I-H)3&TWkoetFs@kFu&!AFmwIk}~e1rIl$YeI0X6vtHp z%g*2-6Ri`#a9oMSC5vFQ3CJGuWx)?q`k{wLa0?#`nmi)T;#S*FGIr*CwvVGp-;{a5 z#}SQNA&o7ZQy@E$wR!hLuaar9lGy+Hy5p-4`Y4T^E}lOg%n7yu>l$rqBIWMqtV3BX zO<%4c08#e~b-z_>>(36`57$OhKP~Iubwzimu#oEAQ`B|wbt?T(u7>#;m%UJEa?g9%|qB#<=!(gq)#xOIk9+ Date: Fri, 29 Nov 2024 17:47:12 +0100 Subject: [PATCH 17/30] missing import after merge --- .../java/com/sun/hotspot/igv/view/widgets/LineWidget.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java index 0e3a182e4d91e..01ebb8ed9e31f 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java @@ -27,6 +27,7 @@ import com.sun.hotspot.igv.graph.Connection; import com.sun.hotspot.igv.graph.Figure; import com.sun.hotspot.igv.graph.OutputSlot; +import com.sun.hotspot.igv.layout.Vertex; import com.sun.hotspot.igv.util.StringUtils; import com.sun.hotspot.igv.view.DiagramScene; import java.awt.*; @@ -37,8 +38,11 @@ import javax.swing.JPopupMenu; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; + +import com.sun.hotspot.igv.view.actions.CustomSelectAction; import org.netbeans.api.visual.action.ActionFactory; import org.netbeans.api.visual.action.PopupMenuProvider; +import org.netbeans.api.visual.action.SelectProvider; import org.netbeans.api.visual.model.ObjectState; import org.netbeans.api.visual.widget.Widget; From 02c182bf6b08ba73072fd918c36cb3913f51ceb6 Mon Sep 17 00:00:00 2001 From: Toby Holenstein Date: Fri, 29 Nov 2024 17:47:53 +0100 Subject: [PATCH 18/30] Update InputSlot.java --- .../java/com/sun/hotspot/igv/graph/InputSlot.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java index 1bc4446822a31..192f24f2f8588 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/InputSlot.java @@ -85,17 +85,4 @@ public String getToolTipText() { public String toString() { return "InputSlot[figure=" + this.getFigure().toString() + ", position=" + getPosition() + "]"; } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof InputSlot that)) return false; - if (!super.equals(o)) return false; - return this.originalIndex == that.originalIndex; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), originalIndex); - } } From 10519219bc6e72e26d1cad0060dc644f9b493819 Mon Sep 17 00:00:00 2001 From: Toby Holenstein Date: Fri, 29 Nov 2024 17:48:15 +0100 Subject: [PATCH 19/30] Update OutputSlot.java --- .../java/com/sun/hotspot/igv/graph/OutputSlot.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java index cbae2541a2f79..a9e17a5582740 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/OutputSlot.java @@ -62,16 +62,4 @@ public Point getRelativePosition() { public String toString() { return "OutputSlot[figure=" + this.getFigure().toString() + ", position=" + getPosition() + "]"; } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - return super.equals(o); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode()); - } } From bf081c5bff6ca8ebb7d5369419aa7bfb577c3d47 Mon Sep 17 00:00:00 2001 From: Toby Holenstein Date: Fri, 29 Nov 2024 17:48:37 +0100 Subject: [PATCH 20/30] Update Slot.java --- .../main/java/com/sun/hotspot/igv/graph/Slot.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java index b723a6a3d037f..eb86d3dda880f 100644 --- a/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java +++ b/src/utils/IdealGraphVisualizer/Graph/src/main/java/com/sun/hotspot/igv/graph/Slot.java @@ -175,18 +175,5 @@ public Vertex getVertex() { public abstract int getPosition(); public abstract void setPosition(int position); - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Slot other)) return false; - return this.wantedIndex == other.wantedIndex && - Objects.equals(this.figure, other.figure); - } - - @Override - public int hashCode() { - return Objects.hash(figure, wantedIndex); - } } From 227c32041f45f60a3054449328044c065e6e56c6 Mon Sep 17 00:00:00 2001 From: Toby Holenstein Date: Fri, 29 Nov 2024 17:49:24 +0100 Subject: [PATCH 21/30] Update HierarchicalLayoutManager.java --- .../igv/hierarchicallayout/HierarchicalLayoutManager.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java index 4574f2379e7d4..561184ba8c2ed 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java @@ -37,7 +37,6 @@ public class HierarchicalLayoutManager extends LayoutManager implements LayoutMo int maxLayerLength; private LayoutGraph graph; - private boolean layoutSelfEdges = false; public HierarchicalLayoutManager() { From 43fb361264b8f6a16dbcd82d11b074a77f5e5dbd Mon Sep 17 00:00:00 2001 From: Toby Holenstein Date: Fri, 29 Nov 2024 17:49:57 +0100 Subject: [PATCH 22/30] Update DiagramScene.java --- .../src/main/java/com/sun/hotspot/igv/view/DiagramScene.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java index 3e242c527181b..da1733524d2a1 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java @@ -94,7 +94,6 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl private HierarchicalLayoutManager seaLayoutManager; private LayoutMover layoutMover; - /** * The alpha level of partially visible figures. */ From 1d79e109a5b8ed254e2f1216f7a97a28c08e13d6 Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Fri, 29 Nov 2024 18:00:28 +0100 Subject: [PATCH 23/30] finish merge of master --- .../sun/hotspot/igv/view/DiagramScene.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java index da1733524d2a1..56bc6da2ba0da 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java @@ -723,10 +723,14 @@ public void setNewLocation(Widget widget, Point location) { assert lw != null; Point toPt = lw.getTo(); Point fromPt = lw.getFrom(); - if (!layoutMover.isFreeForm() && toPt != null && fromPt != null) { - int xTo = toPt.x + shiftX; - int yTo = toPt.y + shiftY; - lw.setTo(new Point(xTo, yTo)); + Point toPt = lw.getTo(); + if (toPt == null || fromPt == null) { + continue; + } + int xTo = toPt.x + shiftX; + int yTo = toPt.y + shiftY; + lw.setTo(new Point(xTo, yTo)); + if (!layoutMover.isFreeForm()) { lw.setFrom(new Point(fromPt.x + shiftX, fromPt.y)); LineWidget pred = lw.getPredecessor(); pred.setTo(new Point(pred.getTo().x + shiftX, pred.getTo().y)); @@ -743,10 +747,14 @@ public void setNewLocation(Widget widget, Point location) { assert lw != null; Point fromPt = lw.getFrom(); Point toPt = lw.getTo(); - if (!layoutMover.isFreeForm() && toPt != null && fromPt != null) { - int xFrom = fromPt.x + shiftX; - int yFrom = fromPt.y + shiftY; lw.setFrom(new Point(xFrom, yFrom)); + if (toPt == null || fromPt == null) { + continue; + } + int xFrom = fromPt.x + shiftX; + int yFrom = fromPt.y + shiftY; + lw.setFrom(new Point(xFrom, yFrom)); + if (!layoutMover.isFreeForm()) { lw.setTo(new Point(toPt.x + shiftX, toPt.y)); for (LineWidget succ : lw.getSuccessors()) { succ.setFrom(new Point(succ.getFrom().x + shiftX, succ.getFrom().y)); From 5b6923d485998099b1a52ecb3ec30d78a92eb022 Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Fri, 29 Nov 2024 18:01:15 +0100 Subject: [PATCH 24/30] missing --- .../src/main/java/com/sun/hotspot/igv/view/DiagramScene.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java index 56bc6da2ba0da..99ae3380bb39d 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java @@ -721,7 +721,6 @@ public void setNewLocation(Widget widget, Point location) { if (inputSlotToLineWidget.containsKey(inputSlot)) { for (LineWidget lw : inputSlotToLineWidget.get(inputSlot)) { assert lw != null; - Point toPt = lw.getTo(); Point fromPt = lw.getFrom(); Point toPt = lw.getTo(); if (toPt == null || fromPt == null) { @@ -747,7 +746,6 @@ public void setNewLocation(Widget widget, Point location) { assert lw != null; Point fromPt = lw.getFrom(); Point toPt = lw.getTo(); - lw.setFrom(new Point(xFrom, yFrom)); if (toPt == null || fromPt == null) { continue; } From 66ad81f388e7cf5b1b1bb02037e3951cc33cfb1b Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Fri, 29 Nov 2024 18:14:50 +0100 Subject: [PATCH 25/30] prevent divison by zero --- .../FreeInteractiveLayoutManager.java | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java index d221d78f8620c..bff981e684a75 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java @@ -135,8 +135,6 @@ public void doLayout(LayoutGraph graph) { } public void positionNewLayoutNodes(List newLayoutNodes) { - Random random = new Random(); - // First pass: Initial positioning based on unassigned neighbors newLayoutNodes.sort(LeastUnassignedNeighborsComparator); @@ -195,13 +193,26 @@ public void positionNewLayoutNodes(List newLayoutNodes) { } } + /** + * Applies a force-based adjustment to the position of a given layout node + * based on repulsive forces from all other nodes and attractive forces from its assigned neighbors. + *

+ * This method simulates a physical system where nodes repel each other to maintain spacing + * and are pulled towards their neighbors to maintain connectivity. The forces are calculated + * using Coulomb's law for repulsion and Hooke's law for attraction. The system iterates for + * a fixed number of iterations to stabilize the position of the node. + * + * @param node The node whose position is being adjusted. + * @param assignedNeighbors A list of neighboring nodes that attract this node. + * @param allNodes A collection of all nodes in the layout, used for repulsive forces. + */ private void applyForceBasedAdjustment(LayoutNode node, List assignedNeighbors, Collection allNodes) { // Constants for force-based adjustment - final int ITERATIONS = 50; - final double REPULSION_CONSTANT = 1000; - final double SPRING_CONSTANT = 0.2; - final double DAMPING = 0.8; - final double IDEAL_LENGTH = 100; + final int ITERATIONS = 50; // Number of simulation iterations. + final double REPULSION_CONSTANT = 1000; // Magnitude of repulsive forces. + final double SPRING_CONSTANT = 0.2; // Strength of attractive forces to neighbors. + final double DAMPING = 0.8; // Damping factor to reduce displacement and ensure convergence. + final double IDEAL_LENGTH = 100; // Ideal distance between a node and its neighbors. double posX = node.getX(); double posY = node.getY(); @@ -217,9 +228,17 @@ private void applyForceBasedAdjustment(LayoutNode node, List assigne double deltaX = posX - otherNode.getX(); double deltaY = posY - otherNode.getY(); - double distanceSquared = deltaX * deltaX + deltaY * deltaY + 0.01; // Prevent division by zero + double distanceSquared = deltaX * deltaX + deltaY * deltaY; double distance = Math.sqrt(distanceSquared); + // If distance is zero, add small random noise to deltaX and deltaY + if (distance == 0) { + deltaX = random.nextDouble() * 0.1 - 0.05; // Random value between -0.05 and 0.05 + deltaY = random.nextDouble() * 0.1 - 0.05; + distanceSquared = deltaX * deltaX + deltaY * deltaY; + distance = Math.sqrt(distanceSquared); + } + // Repulsive force (Coulomb's law) double repulsiveForce = REPULSION_CONSTANT / distanceSquared; netForceX += (deltaX / distance) * repulsiveForce; @@ -230,7 +249,14 @@ private void applyForceBasedAdjustment(LayoutNode node, List assigne for (LayoutNode neighbor : assignedNeighbors) { double deltaX = neighbor.getX() - posX; double deltaY = neighbor.getY() - posY; - double distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY + 0.01); // Prevent division by zero + double distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + + // If distance is zero, add small random noise to deltaX and deltaY + if (distance == 0) { + deltaX = random.nextDouble() * 0.1 - 0.05; // Random value between -0.05 and 0.05 + deltaY = random.nextDouble() * 0.1 - 0.05; + distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + } // Attractive force (Hooke's law) double displacement = distance - IDEAL_LENGTH; @@ -253,7 +279,6 @@ private void applyForceBasedAdjustment(LayoutNode node, List assigne node.setY((int) posY); } - // Utility method: position around a given node private void setPositionAroundSingleNode(LayoutNode node, LayoutNode neighbor, int displacement) { boolean neighborIsPredecessor = prevGraph.isPredecessorVertex(node.getVertex(), neighbor.getVertex()); @@ -267,7 +292,6 @@ private void setPositionAroundSingleNode(LayoutNode node, LayoutNode neighbor, i } assert shiftY != 0; - int randomY = neighbor.getY() + random.nextInt(MAX_OFFSET_AROUND_NEIGHBOR + 1) + shiftY; int randomX = neighbor.getX() + random.nextInt(MAX_OFFSET_AROUND_NEIGHBOR + 1); node.setX(randomX); From 2847b60fe4a2ef721dd7f6b6e779100345696be8 Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Fri, 29 Nov 2024 23:57:49 +0100 Subject: [PATCH 26/30] layoutNode.setVertex(vertex) and LayoutNode updateSize() --- .../FreeInteractiveLayoutManager.java | 5 +++++ .../igv/hierarchicallayout/LayoutNode.java | 16 ++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java index bff981e684a75..daf64bffbca7c 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java @@ -125,6 +125,7 @@ public void doLayout(LayoutGraph graph) { // Write back vertices for (Vertex vertex : prevGraph.getVertices()) { LayoutNode layoutNode = layoutNodes.get(vertex); + layoutNode.setVertex(vertex); vertex.setPosition(new Point(layoutNode.getLeft(), layoutNode.getTop())); } @@ -327,7 +328,11 @@ private void setLinkControlPoints(Link link) { if (link.getFrom().getVertex() == link.getTo().getVertex()) return; LayoutNode from = layoutNodes.get(link.getFrom().getVertex()); + from.setVertex(link.getFrom().getVertex()); + from.updateSize(); LayoutNode to = layoutNodes.get(link.getTo().getVertex()); + to.setVertex(link.getTo().getVertex()); + to.updateSize(); int relativeFromX = link.getFrom().getRelativePosition().x; int relativeToX = link.getTo().getRelativePosition().x; diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java index 9ad0c70b912eb..2816115080e17 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java @@ -107,12 +107,7 @@ public LayoutNode() { this(null); } - /** - * Initializes the size and margins of the node. - * If the node represents a real vertex, it uses the vertex's size. - * Dummy nodes use default dimensions. - */ - public void initSize() { + public void updateSize() { if (vertex == null) { height = DUMMY_HEIGHT; width = DUMMY_WIDTH; @@ -121,6 +116,15 @@ public void initSize() { height = size.height; width = size.width; } + } + + /** + * Initializes the size and margins of the node. + * If the node represents a real vertex, it uses the vertex's size. + * Dummy nodes use default dimensions. + */ + public void initSize() { + updateSize(); topMargin = 0; bottomMargin = 0; leftMargin = 0; From d1843e650e200b4ac1f8c5ba0156d5176be0e404 Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Sat, 30 Nov 2024 00:04:11 +0100 Subject: [PATCH 27/30] function comment added for setLinkControlPoints --- .../FreeInteractiveLayoutManager.java | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java index daf64bffbca7c..107717a621033 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java @@ -324,30 +324,34 @@ private void calculateBarycenterWithDisplacement(LayoutNode node, List + * Calculates the start and end points, applies offsets for curvature, and updates + * the link's control points. + * + * @param link The link to process. + */ private void setLinkControlPoints(Link link) { - if (link.getFrom().getVertex() == link.getTo().getVertex()) return; + if (link.getFrom().getVertex() == link.getTo().getVertex()) return; // Skip self-links LayoutNode from = layoutNodes.get(link.getFrom().getVertex()); from.setVertex(link.getFrom().getVertex()); from.updateSize(); + LayoutNode to = layoutNodes.get(link.getTo().getVertex()); to.setVertex(link.getTo().getVertex()); to.updateSize(); - int relativeFromX = link.getFrom().getRelativePosition().x; - int relativeToX = link.getTo().getRelativePosition().x; - - int startX = from.getLeft() + relativeFromX; - int startY = from.getBottom(); - int endX = to.getLeft() + relativeToX; - int endY = to.getTop(); - - Point startPoint = new Point(startX, startY); - Point endPoint = new Point(endX, endY); - List line = new ArrayList<>(); - line.add(startPoint); - line.add(new Point(startPoint.x, startPoint.y + LINE_OFFSET)); - line.add(new Point(endPoint.x, endPoint.y - LINE_OFFSET)); - line.add(endPoint); - link.setControlPoints(line); + + Point startPoint = new Point(from.getLeft() + link.getFrom().getRelativePosition().x, from.getBottom()); + Point endPoint = new Point(to.getLeft() + link.getTo().getRelativePosition().x, to.getTop()); + + List controlPoints = new ArrayList<>(); + controlPoints.add(startPoint); + controlPoints.add(new Point(startPoint.x, startPoint.y + LINE_OFFSET)); + controlPoints.add(new Point(endPoint.x, endPoint.y - LINE_OFFSET)); + controlPoints.add(endPoint); + + link.setControlPoints(controlPoints); } } From ce9720d3b18073de8f3f65dfe04444f5029e50d7 Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Sat, 30 Nov 2024 00:05:59 +0100 Subject: [PATCH 28/30] trailing whitespace --- .../igv/hierarchicallayout/HierarchicalLayoutManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java index 561184ba8c2ed..cb9487ae828fa 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java @@ -52,7 +52,7 @@ public void setLayoutSelfEdges(boolean layoutSelfEdges) { public void setCutEdges(boolean enable) { maxLayerLength = enable ? 10 : -1; } - + @Override public boolean isFreeForm() { return false; From 14d20181e50dc98d905b345a96ded8802c49b479 Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Sat, 30 Nov 2024 01:08:03 +0100 Subject: [PATCH 29/30] make applyForceBasedAdjustment more numerical stable --- .../FreeInteractiveLayoutManager.java | 57 ++++++++++++++----- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java index 107717a621033..7aec57ec2a092 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java @@ -209,11 +209,13 @@ public void positionNewLayoutNodes(List newLayoutNodes) { */ private void applyForceBasedAdjustment(LayoutNode node, List assignedNeighbors, Collection allNodes) { // Constants for force-based adjustment - final int ITERATIONS = 50; // Number of simulation iterations. - final double REPULSION_CONSTANT = 1000; // Magnitude of repulsive forces. - final double SPRING_CONSTANT = 0.2; // Strength of attractive forces to neighbors. - final double DAMPING = 0.8; // Damping factor to reduce displacement and ensure convergence. - final double IDEAL_LENGTH = 100; // Ideal distance between a node and its neighbors. + final int ITERATIONS = 50; // Number of simulation iterations + final double REPULSION_CONSTANT = 1000; // Magnitude of repulsive forces (Coulomb's law) + final double SPRING_CONSTANT = 0.2; // Strength of attractive forces to neighbors (Hooke's law) + final double DAMPING = 0.8; // Factor to reduce displacement and ensure stability + final double IDEAL_LENGTH = 100; // Desired distance between a node and its neighbors + final double MAX_FORCE = 1000; // Upper limit for the magnitude of applied forces + final double CONVERGENCE_THRESHOLD = 0.01; // Force threshold for stopping early double posX = node.getX(); double posY = node.getY(); @@ -232,9 +234,9 @@ private void applyForceBasedAdjustment(LayoutNode node, List assigne double distanceSquared = deltaX * deltaX + deltaY * deltaY; double distance = Math.sqrt(distanceSquared); - // If distance is zero, add small random noise to deltaX and deltaY - if (distance == 0) { - deltaX = random.nextDouble() * 0.1 - 0.05; // Random value between -0.05 and 0.05 + // Avoid division by zero by introducing a minimum distance + if (distance < 1e-6) { + deltaX = random.nextDouble() * 0.1 - 0.05; deltaY = random.nextDouble() * 0.1 - 0.05; distanceSquared = deltaX * deltaX + deltaY * deltaY; distance = Math.sqrt(distanceSquared); @@ -242,6 +244,10 @@ private void applyForceBasedAdjustment(LayoutNode node, List assigne // Repulsive force (Coulomb's law) double repulsiveForce = REPULSION_CONSTANT / distanceSquared; + + // Normalize force to prevent large displacements + if (repulsiveForce > MAX_FORCE) repulsiveForce = MAX_FORCE; + netForceX += (deltaX / distance) * repulsiveForce; netForceY += (deltaY / distance) * repulsiveForce; } @@ -252,9 +258,8 @@ private void applyForceBasedAdjustment(LayoutNode node, List assigne double deltaY = neighbor.getY() - posY; double distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); - // If distance is zero, add small random noise to deltaX and deltaY - if (distance == 0) { - deltaX = random.nextDouble() * 0.1 - 0.05; // Random value between -0.05 and 0.05 + if (distance < 1e-6) { + deltaX = random.nextDouble() * 0.1 - 0.05; deltaY = random.nextDouble() * 0.1 - 0.05; distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); } @@ -262,22 +267,44 @@ private void applyForceBasedAdjustment(LayoutNode node, List assigne // Attractive force (Hooke's law) double displacement = distance - IDEAL_LENGTH; double attractiveForce = SPRING_CONSTANT * displacement; + + if (attractiveForce > MAX_FORCE) attractiveForce = MAX_FORCE; + netForceX += (deltaX / distance) * attractiveForce; netForceY += (deltaY / distance) * attractiveForce; } - // Update displacement with damping + // Apply damping and update displacement dx = (dx + netForceX) * DAMPING; dy = (dy + netForceY) * DAMPING; - // Update node position + // Scale displacement if it's too large + double displacementMagnitude = Math.sqrt(dx * dx + dy * dy); + if (displacementMagnitude > MAX_FORCE) { + dx *= MAX_FORCE / displacementMagnitude; + dy *= MAX_FORCE / displacementMagnitude; + } + + // Update position posX += dx; posY += dy; + + // Stop early if the net force is negligible + if (Math.abs(netForceX) < CONVERGENCE_THRESHOLD && Math.abs(netForceY) < CONVERGENCE_THRESHOLD) { + break; + } + + // Validate position to avoid invalid or extreme values + if (Double.isNaN(posX) || Double.isInfinite(posX) || Double.isNaN(posY) || Double.isInfinite(posY)) { + posX = node.getX(); // Reset to original position + posY = node.getY(); + break; + } } // Set final position - node.setX((int) posX); - node.setY((int) posY); + node.setX((int) Math.round(posX)); + node.setY((int) Math.round(posY)); } // Utility method: position around a given node From f8d699aff1e2a290ff1e739b9df9bdc89aa8cfde Mon Sep 17 00:00:00 2001 From: Toby Holenstein Date: Thu, 12 Dec 2024 21:56:48 +0100 Subject: [PATCH 30/30] Update LayoutGraph.java --- .../com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java index d24ecc06ef28d..956d72924b991 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java @@ -169,7 +169,7 @@ public Collection getLayoutNodes() { * Retrieves a combined list of all nodes in the graph, * including both layout nodes and dummy nodes. * - * @return An unmodifiable list containing all nodes in the graph. + * @return An unmodifiable list containing all nodes in the graph */ public List getAllNodes() { List allNodes = new ArrayList<>();