mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-29 12:38:24 +00:00
8345041: IGV: Free Placement Mode in IGV Layout
Reviewed-by: chagedorn, epeter, rcastanedalo
This commit is contained in:
parent
8b22517cb0
commit
e5f0c19084
@ -0,0 +1,384 @@
|
||||
/*
|
||||
* 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<Vertex, LayoutNode> 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<LayoutNode> 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<? extends Vertex> 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<LayoutNode> 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);
|
||||
layoutNode.setVertex(vertex);
|
||||
vertex.setPosition(new Point(layoutNode.getLeft(), layoutNode.getTop()));
|
||||
}
|
||||
|
||||
// Write back links
|
||||
for (Link link : prevGraph.getLinks()) {
|
||||
setLinkControlPoints(link);
|
||||
}
|
||||
}
|
||||
|
||||
public void positionNewLayoutNodes(List<LayoutNode> newLayoutNodes) {
|
||||
// First pass: Initial positioning based on unassigned neighbors
|
||||
newLayoutNodes.sort(LeastUnassignedNeighborsComparator);
|
||||
|
||||
for (LayoutNode node : newLayoutNodes) {
|
||||
Vertex vertex = node.getVertex();
|
||||
|
||||
// Gather assigned neighbors
|
||||
List<LayoutNode> 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<LayoutNode> allNodes = layoutNodes.values();
|
||||
|
||||
for (LayoutNode node : newLayoutNodes) {
|
||||
Vertex vertex = node.getVertex();
|
||||
|
||||
// Gather assigned neighbors
|
||||
List<LayoutNode> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
* 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<LayoutNode> assignedNeighbors, Collection<LayoutNode> allNodes) {
|
||||
// Constants for force-based adjustment
|
||||
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();
|
||||
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;
|
||||
double distance = Math.sqrt(distanceSquared);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Apply damping and update displacement
|
||||
dx = (dx + netForceX) * DAMPING;
|
||||
dy = (dy + netForceY) * DAMPING;
|
||||
|
||||
// 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) Math.round(posX));
|
||||
node.setY((int) Math.round(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<LayoutNode> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets control points for a given link based on its start and end layout nodes.
|
||||
* <p>
|
||||
* 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; // 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();
|
||||
|
||||
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<Point> 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);
|
||||
}
|
||||
}
|
||||
@ -53,6 +53,11 @@ public class HierarchicalLayoutManager extends LayoutManager implements LayoutMo
|
||||
maxLayerLength = enable ? 10 : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFreeForm() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doLayout(LayoutGraph layoutGraph) {
|
||||
layoutGraph.initializeLayout();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 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
|
||||
@ -169,7 +169,7 @@ public class LayoutGraph {
|
||||
* 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<LayoutNode> getAllNodes() {
|
||||
List<LayoutNode> allNodes = new ArrayList<>();
|
||||
@ -447,6 +447,63 @@ public class LayoutGraph {
|
||||
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<Vertex> getNeighborVertices(Vertex vertex) {
|
||||
List<Vertex> 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<Link> getAllLinks(Vertex vertex) {
|
||||
List<Link> allLinks = new ArrayList<>();
|
||||
|
||||
|
||||
@ -49,5 +49,7 @@ public interface LayoutMover {
|
||||
* @param movedVertex The vertex to be moved.
|
||||
*/
|
||||
void moveVertex(Vertex movedVertex);
|
||||
|
||||
boolean isFreeForm();
|
||||
}
|
||||
|
||||
|
||||
@ -107,12 +107,7 @@ public class 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 class LayoutNode {
|
||||
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;
|
||||
|
||||
@ -37,6 +37,7 @@ public class Settings {
|
||||
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";
|
||||
|
||||
@ -89,6 +89,7 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
|
||||
|
||||
private final Map<OutputSlot, Set<LineWidget>> outputSlotToLineWidget = new HashMap<>();
|
||||
private final Map<InputSlot, Set<LineWidget>> inputSlotToLineWidget = new HashMap<>();
|
||||
private final FreeInteractiveLayoutManager freeInteractiveLayoutManager;
|
||||
private final HierarchicalStableLayoutManager hierarchicalStableLayoutManager;
|
||||
private HierarchicalLayoutManager seaLayoutManager;
|
||||
private LayoutMover layoutMover;
|
||||
@ -511,6 +512,7 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
|
||||
}
|
||||
});
|
||||
|
||||
freeInteractiveLayoutManager = new FreeInteractiveLayoutManager();
|
||||
hierarchicalStableLayoutManager = new HierarchicalStableLayoutManager();
|
||||
seaLayoutManager = new HierarchicalLayoutManager();
|
||||
|
||||
@ -643,6 +645,8 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
|
||||
widget.bringToFront();
|
||||
startLayerY = widget.getLocation().y;
|
||||
hasMoved = false; // Reset the movement flag
|
||||
if (layoutMover.isFreeForm()) return;
|
||||
|
||||
Set<Figure> selectedFigures = model.getSelectedFigures();
|
||||
if (selectedFigures.size() == 1) {
|
||||
Figure selectedFigure = selectedFigures.iterator().next();
|
||||
@ -702,8 +706,12 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
|
||||
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<Figure> selectedFigures = new ArrayList<>( model.getSelectedFigures());
|
||||
selectedFigures.sort(Comparator.comparingInt(f -> f.getPosition().x));
|
||||
for (Figure figure : selectedFigures) {
|
||||
@ -713,12 +721,15 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
|
||||
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));
|
||||
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));
|
||||
@ -735,10 +746,13 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
|
||||
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));
|
||||
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));
|
||||
@ -753,10 +767,12 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
|
||||
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();
|
||||
}
|
||||
@ -834,7 +850,9 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
|
||||
|
||||
Set<Figure> visibleFigures = getVisibleFigures();
|
||||
Set<Connection> 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);
|
||||
@ -904,6 +922,12 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
|
||||
return w1.isVisible() && w2.isVisible();
|
||||
}
|
||||
|
||||
private void doFreeInteractiveLayout(Set<Figure> visibleFigures, Set<Connection> visibleConnections) {
|
||||
layoutMover = freeInteractiveLayoutManager;
|
||||
freeInteractiveLayoutManager.setCutEdges(model.getCutEdges());
|
||||
freeInteractiveLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures));
|
||||
}
|
||||
|
||||
private void doStableSeaLayout(Set<Figure> visibleFigures, Set<Connection> visibleConnections) {
|
||||
layoutMover = null;
|
||||
boolean enable = model.getCutEdges();
|
||||
@ -1108,6 +1132,52 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
|
||||
}
|
||||
}
|
||||
|
||||
private void processFreeForm(OutputSlot outputSlot, List<FigureConnection> 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<Point> 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<FigureConnection> 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;
|
||||
@ -1281,7 +1351,11 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
|
||||
for (Figure figure : getModel().getDiagram().getFigures()) {
|
||||
for (OutputSlot outputSlot : figure.getOutputSlots()) {
|
||||
List<FigureConnection> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -63,6 +63,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 showFreeInteractive;
|
||||
private boolean showStableSea;
|
||||
private boolean showSea;
|
||||
private boolean showBlocks;
|
||||
@ -104,6 +105,17 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
|
||||
}
|
||||
}
|
||||
|
||||
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 class DiagramViewModel extends RangeSliderModel implements ChangedListene
|
||||
|
||||
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 class DiagramViewModel extends RangeSliderModel implements ChangedListene
|
||||
for (String ignored : getPositions()) {
|
||||
colors.add(Color.black);
|
||||
}
|
||||
if (nodes.size() >= 1) {
|
||||
if (!nodes.isEmpty()) {
|
||||
for (Integer id : nodes) {
|
||||
if (id < 0) {
|
||||
id = -id;
|
||||
|
||||
@ -176,6 +176,11 @@ public final class EditorTopComponent extends TopComponent implements TopCompone
|
||||
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);
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -30,14 +30,16 @@ 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.*;
|
||||
import java.util.List;
|
||||
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;
|
||||
@ -70,6 +72,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<? extends Connection> connections, Point from, Point to, LineWidget predecessor, boolean isBold, boolean isDashed) {
|
||||
super(scene);
|
||||
@ -172,6 +176,16 @@ public class LineWidget extends Widget implements PopupMenuProvider {
|
||||
computeClientArea();
|
||||
}
|
||||
|
||||
public void setFromControlYOffset(int fromControlYOffset) {
|
||||
this.fromControlYOffset = fromControlYOffset;
|
||||
computeClientArea();
|
||||
}
|
||||
|
||||
public void setToControlYOffset(int toControlYOffset) {
|
||||
this.toControlYOffset = toControlYOffset;
|
||||
computeClientArea();
|
||||
}
|
||||
|
||||
public Point getFrom() {
|
||||
return from;
|
||||
}
|
||||
@ -225,7 +239,41 @@ public class LineWidget extends Widget implements PopupMenuProvider {
|
||||
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();
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
Loading…
x
Reference in New Issue
Block a user