8309463: IGV: Dynamic graph layout algorithm

Reviewed-by: tholenstein, rcastanedalo, chagedorn
This commit is contained in:
Emmy 2023-08-29 09:52:41 +00:00 committed by Tobias Holenstein
parent 1d0250709a
commit 5cc64cc27a
14 changed files with 1767 additions and 90 deletions

View File

@ -384,6 +384,19 @@ public class Figure extends Properties.Entity implements Vertex {
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;

View File

@ -156,5 +156,17 @@ public class FigureConnection implements Connection {
return true;
}
@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();
}
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
@ -51,6 +51,11 @@
<artifactId>Layout</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.sun.hotspot.igv</groupId>
<artifactId>Util</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 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
@ -27,6 +27,7 @@ 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 com.sun.hotspot.igv.util.Statistics;
import java.awt.Dimension;
import java.awt.Point;
import java.util.*;
@ -79,49 +80,6 @@ public class HierarchicalLayoutManager implements LayoutManager {
private Set<? extends Link> importantLinks;
private final Set<Link> linksToFollow;
private class LayoutNode {
public int x;
public int y;
public int width;
public int height;
public int layer = -1;
public int xOffset;
public int yOffset;
public int bottomYOffset;
public Vertex vertex; // Only used for non-dummy nodes, otherwise null
public List<LayoutEdge> preds = new ArrayList<>();
public List<LayoutEdge> succs = new ArrayList<>();
public HashMap<Integer, Integer> outOffsets = new HashMap<>();
public HashMap<Integer, Integer> inOffsets = new HashMap<>();
public int pos = -1; // Position within layer
public int crossingNumber;
@Override
public String toString() {
return "Node " + vertex;
}
}
private 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;
@Override
public String toString() {
return "Edge " + from + ", " + to;
}
}
private abstract static class AlgorithmPart {
public void start() {
@ -190,6 +148,10 @@ public class HierarchicalLayoutManager implements LayoutManager {
this.layoutSelfEdges = layoutSelfEdges;
}
public List<LayoutNode> getNodes() {
return nodes;
}
// Remove self-edges, possibly saving them into the selfEdges set.
private void removeSelfEdges(boolean save) {
for (LayoutNode node : nodes) {
@ -305,7 +267,7 @@ public class HierarchicalLayoutManager implements LayoutManager {
for (LayoutNode n : nodes) {
for (LayoutEdge e : n.preds) {
if (e.link != null) {
if (e.link != null && !linkPositions.containsKey(e.link)) {
ArrayList<Point> points = new ArrayList<>();
Point p = new Point(e.to.x + e.relativeTo, e.to.y + e.to.yOffset + e.link.getTo().getRelativePosition().y);
@ -386,14 +348,11 @@ public class HierarchicalLayoutManager implements LayoutManager {
linkPositions.put(e.link, points);
}
pointCount += points.size();
// No longer needed!
e.link = null;
}
}
for (LayoutEdge e : n.succs) {
if (e.link != null) {
if (e.link != null && !linkPositions.containsKey(e.link)) {
ArrayList<Point> 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);
@ -470,7 +429,6 @@ public class HierarchicalLayoutManager implements LayoutManager {
}
pointCount += points.size();
e.link = null;
}
}
}
@ -526,8 +484,8 @@ public class HierarchicalLayoutManager implements LayoutManager {
}
}
private static final Comparator<LayoutNode> nodePositionComparator = Comparator.comparingInt(n -> n.pos);
private static final Comparator<LayoutNode> nodeProcessingDownComparator = (n1, n2) -> {
public static final Comparator<LayoutNode> nodePositionComparator = Comparator.comparingInt(n -> n.pos);
public static final Comparator<LayoutNode> nodeProcessingDownComparator = (n1, n2) -> {
int n1VIP = 0;
for (LayoutEdge e : n1.preds) {
if (e.vip) {
@ -554,7 +512,7 @@ public class HierarchicalLayoutManager implements LayoutManager {
}
return n1.preds.size() - n2.preds.size();
};
private static final Comparator<LayoutNode> nodeProcessingUpComparator = (n1, n2) -> {
public static final Comparator<LayoutNode> nodeProcessingUpComparator = (n1, n2) -> {
int n1VIP = 0;
for (LayoutEdge e : n1.succs) {
if (e.vip) {
@ -661,7 +619,7 @@ public class HierarchicalLayoutManager implements LayoutManager {
LayoutEdge e = n.preds.get(i);
values[i] = e.from.x + e.relativeFrom - e.relativeTo;
}
return median(values);
return Statistics.median(values);
} else {
int z = 0;
int[] values = new int[vipCount];
@ -671,7 +629,7 @@ public class HierarchicalLayoutManager implements LayoutManager {
values[z++] = e.from.x + e.relativeFrom - e.relativeTo;
}
}
return median(values);
return Statistics.median(values);
}
}
@ -693,7 +651,7 @@ public class HierarchicalLayoutManager implements LayoutManager {
i++;
}
return median(values);
return Statistics.median(values);
}
private int calculateOptimalUp(LayoutNode n) {
@ -709,16 +667,7 @@ public class HierarchicalLayoutManager implements LayoutManager {
return values[i];
}
}
return median(values);
}
private 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);
}
private void sweepUp() {
@ -742,7 +691,7 @@ public class HierarchicalLayoutManager implements LayoutManager {
}
}
private static class NodeRow {
public static class NodeRow {
private final TreeSet<LayoutNode> treeSet;
private final ArrayList<Integer> space;
@ -831,6 +780,9 @@ public class HierarchicalLayoutManager implements LayoutManager {
if (!visited.contains(e.to)) {
visited.add(e.to);
layers[i + 1].add(e.to);
if (!nodes.contains(e.to)) {
nodes.add(e.to);
}
}
}
}
@ -1221,7 +1173,7 @@ public class HierarchicalLayoutManager implements LayoutManager {
} else {
ArrayList<LayoutNode> currentNodes = new ArrayList<>(nodes);
for (LayoutNode n : currentNodes) {
for (LayoutEdge e : n.succs) {
for (LayoutEdge e : List.copyOf(n.succs)) {
processSingleEdge(e);
}
}
@ -1229,10 +1181,10 @@ public class HierarchicalLayoutManager implements LayoutManager {
}
private void processSingleEdge(LayoutEdge e) {
LayoutNode n = e.from;
if (e.to.layer > n.layer + 1) {
LayoutNode n = e.to;
if (e.to.layer - 1 > e.from.layer) {
LayoutEdge last = e;
for (int i = n.layer + 1; i < last.to.layer; i++) {
for (int i = n.layer - 1; i > last.from.layer; i--) {
last = addBetween(last, i);
}
}
@ -1240,22 +1192,23 @@ public class HierarchicalLayoutManager implements LayoutManager {
private LayoutEdge addBetween(LayoutEdge e, int layer) {
LayoutNode n = new LayoutNode();
n.width = dummyWidth;
n.height = dummyHeight;
n.width = DUMMY_WIDTH;
n.height = DUMMY_HEIGHT;
n.layer = layer;
n.preds.add(e);
n.succs.add(e);
nodes.add(n);
LayoutEdge result = new LayoutEdge();
result.vip = e.vip;
n.succs.add(result);
result.from = n;
result.relativeFrom = n.width / 2;
result.to = e.to;
result.relativeTo = e.relativeTo;
e.relativeTo = n.width / 2;
e.to.preds.remove(e);
e.to.preds.add(result);
e.to = n;
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;
}

View File

@ -0,0 +1,44 @@
/*
* 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 com.sun.hotspot.igv.layout.Link;
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;
@Override
public String toString() {
return "Edge " + from + ", " + to;
}
}

View File

@ -0,0 +1,53 @@
/*
* 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 com.sun.hotspot.igv.layout.Vertex;
import java.util.*;
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 List<LayoutEdge> preds = new ArrayList<>();
public List<LayoutEdge> succs = new ArrayList<>();
public HashMap<Integer, Integer> outOffsets = new HashMap<>();
public HashMap<Integer, Integer> inOffsets = new HashMap<>();
public int pos = -1; // Position within layer
public int crossingNumber;
@Override
public String toString() {
return "Node " + vertex;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 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
@ -36,6 +36,7 @@ public class Settings {
public static final int SEA_OF_NODES = 0;
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 String NODE_TEXT = "nodeText";

View File

@ -0,0 +1,40 @@
/*
* 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];
}
}
}

View File

@ -26,10 +26,7 @@ package com.sun.hotspot.igv.view;
import com.sun.hotspot.igv.data.Properties;
import com.sun.hotspot.igv.data.*;
import com.sun.hotspot.igv.graph.*;
import com.sun.hotspot.igv.hierarchicallayout.HierarchicalCFGLayoutManager;
import com.sun.hotspot.igv.hierarchicallayout.HierarchicalClusterLayoutManager;
import com.sun.hotspot.igv.hierarchicallayout.HierarchicalLayoutManager;
import com.sun.hotspot.igv.hierarchicallayout.LinearLayoutManager;
import com.sun.hotspot.igv.hierarchicallayout.*;
import com.sun.hotspot.igv.layout.LayoutGraph;
import com.sun.hotspot.igv.selectioncoordinator.SelectionCoordinator;
import com.sun.hotspot.igv.util.ColorIcon;
@ -87,6 +84,7 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
private final DiagramViewModel model;
private ModelState modelState;
private boolean rebuilding;
private final HierarchicalStableLayoutManager hierarchicalStableLayoutManager;
/**
* The alpha level of partially visible figures.
@ -487,6 +485,8 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
}
}
});
hierarchicalStableLayoutManager = new HierarchicalStableLayoutManager();
}
@Override
@ -688,10 +688,15 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
return w1.isVisible() && w2.isVisible();
}
private void doStableSeaLayout(HashSet<Figure> visibleFigures, HashSet<Connection> visibleConnections) {
hierarchicalStableLayoutManager.updateLayout(visibleFigures, visibleConnections);
}
private void doSeaLayout(HashSet<Figure> figures, HashSet<Connection> edges) {
HierarchicalLayoutManager manager = new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS);
manager.setMaxLayerLength(10);
manager.doLayout(new LayoutGraph(edges, figures));
hierarchicalStableLayoutManager.setShouldRedrawLayout(true);
}
private void doClusteredLayout(HashSet<Connection> edges) {
@ -1187,7 +1192,9 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
HashSet<Figure> visibleFigures = getVisibleFigures();
HashSet<Connection> visibleConnections = getVisibleConnections();
if (getModel().getShowSea()) {
if (getModel().getShowStableSea()) {
doStableSeaLayout(visibleFigures, visibleConnections);
} else if (getModel().getShowSea()) {
doSeaLayout(visibleFigures, visibleConnections);
} else if (getModel().getShowBlocks()) {
doClusteredLayout(visibleConnections);

View File

@ -62,6 +62,7 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
private final ChangedEvent<DiagramViewModel> selectedNodesChangedEvent = new ChangedEvent<>(this);
private final ChangedEvent<DiagramViewModel> hiddenNodesChangedEvent = new ChangedEvent<>(this);
private ChangedListener<InputGraph> titleChangedListener = g -> {};
private boolean showStableSea;
private boolean showSea;
private boolean showBlocks;
private boolean showCFG;
@ -90,6 +91,17 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
}
}
public boolean getShowStableSea() {
return showStableSea;
}
public void setShowStableSea(boolean enable) {
showStableSea = enable;
if (enable) {
diagramChangedEvent.fire();
}
}
public boolean getShowSea() {
return showSea;
}
@ -216,6 +228,7 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
filtersOrder = provider.getAllFiltersOrdered();
globalSelection = GlobalSelectionAction.get(GlobalSelectionAction.class).isSelected();
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;
showCFG = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.CONTROL_FLOW_GRAPH;

View File

@ -175,6 +175,11 @@ public final class EditorTopComponent extends TopComponent implements TopCompone
toolBar.addSeparator();
ButtonGroup layoutButtons = new ButtonGroup();
JToggleButton stableSeaLayoutButton = new JToggleButton(new EnableStableSeaLayoutAction(this));
stableSeaLayoutButton.setSelected(diagramViewModel.getShowStableSea());
layoutButtons.add(stableSeaLayoutButton);
toolBar.add(stableSeaLayoutButton);
JToggleButton seaLayoutButton = new JToggleButton(new EnableSeaLayoutAction(this));
seaLayoutButton.setSelected(diagramViewModel.getShowSea());
layoutButtons.add(seaLayoutButton);
@ -191,6 +196,17 @@ public final class EditorTopComponent extends TopComponent implements TopCompone
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())));

View File

@ -0,0 +1,49 @@
/*
* 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.view.actions;
import com.sun.hotspot.igv.view.EditorTopComponent;
import java.beans.PropertyChangeEvent;
public class EnableStableSeaLayoutAction extends EnableLayoutAction {
public EnableStableSeaLayoutAction(EditorTopComponent etc) {
super(etc);
}
@Override
protected String iconResource() {
return "com/sun/hotspot/igv/view/images/stable_sea.png";
}
@Override
protected String getDescription() {
return "Show stable sea of nodes";
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
editor.getModel().setShowStableSea(this.isSelected());
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB