use of org.knime.workbench.ui.layout.Graph.Edge in project knime-core by knime.
the class SimpleLayeredLayouter method doLayout.
/**
* computes an hierarchical layout of the given graph. If nodes are fixed by
* means of the given map, they will end up on the first or last layer,
* ordered by their original y-coordinate. Precondition: g must be a
* directed acyclic graph!
*
* @param g the graph to perform layout on
* @param fixedNodes node map containing true if the respective node should
* be fixed (only sources and sinks allowed)
* @throws RuntimeException
*/
public void doLayout(final Graph g, final Map<Node, Boolean> fixedNodes) throws RuntimeException {
// create lists for fixed sources and sinks if necessary
ArrayList<Node> fixedSources = null;
ArrayList<Node> fixedSinks = null;
if (fixedNodes != null) {
fixedSources = new ArrayList<Graph.Node>();
fixedSinks = new ArrayList<Graph.Node>();
for (Node n : g.nodes()) {
if (fixedNodes.get(n)) {
if (n.inDegree() == 0) {
fixedSources.add(n);
} else if (n.outDegree() == 0) {
fixedSinks.add(n);
}
}
}
if (fixedSources.isEmpty()) {
fixedSources = null;
}
if (fixedSinks.isEmpty()) {
fixedSinks = null;
}
}
// get layering of the graph
Map<Node, Integer> nodeLayer = g.createIntNodeMap();
ArrayList<ArrayList<Node>> layers = Layerer.assignLayers(g, nodeLayer, fixedSources, fixedSinks);
// add dummy vertices for edges spanning several layers
ArrayList<Edge> hiddenEdges = new ArrayList<Graph.Edge>();
ArrayList<Node> dummyNodes = new ArrayList<Graph.Node>();
ArrayList<Edge> dummyEdges = new ArrayList<Graph.Edge>();
HashMap<Edge, ArrayList<Node>> hiddenEdgeToDummyVertices = new HashMap<Graph.Edge, ArrayList<Node>>();
for (Edge e : g.edges()) {
int startLayer = nodeLayer.get(e.source()).intValue();
int endLayer = nodeLayer.get(e.target()).intValue();
int span = endLayer - startLayer;
if (span > 1) {
hiddenEdges.add(e);
}
}
// concurrent modification due to iterator
for (Edge e : hiddenEdges) {
// list for this edges dummy nodes
ArrayList<Node> eDummyNodes = new ArrayList<Graph.Node>();
int startLayer = nodeLayer.get(e.source()).intValue();
int endLayer = nodeLayer.get(e.target()).intValue();
int span = endLayer - startLayer;
Node last = e.source();
for (int i = 1; i < span; i++) {
Node current = g.createNode("bend " + e + ", " + i, startLayer + i, g.getY(last));
// add dummy to its layer
nodeLayer.put(current, startLayer + i);
layers.get(startLayer + i).add(current);
// add dummy edge to graph
Edge dEdge = g.createEdge(last, current);
dummyEdges.add(dEdge);
// add dummy vertex to the list of dummies for the original edge
eDummyNodes.add(current);
// proceed
last = current;
}
// add last dummy edge
g.createEdge(last, e.target());
// store list of dummy nodes for original edge
hiddenEdgeToDummyVertices.put(e, eDummyNodes);
// add this edges dummy Nodes to the list of all dummy nodes
dummyNodes.addAll(eDummyNodes);
}
// remove hidden edges
for (Edge e : hiddenEdges) {
g.removeEdge(e);
}
// set initial coordinates by layer
int layer = 0;
for (ArrayList<Node> currentLayer : layers) {
// sort first and last layer by y-coordinate if fixed
if (layer == 0 && fixedSources != null) {
Collections.sort(currentLayer, new Util.NodeByYComparator(g));
} else if (layer == layers.size() - 1 && fixedSinks != null) {
Collections.sort(currentLayer, new Util.NodeByYComparator(g));
} else {
// here the ordering is shuffled, could also be done several
// times in the crossing minimization phase.
// I.e., every execution of the algorithm potentially yields
// another result!
Collections.shuffle(currentLayer, m_rnd);
// ordering could also be initialized by the current ordering
// from y-coordinates.
// Collections.sort(currentLayer, new
// Util.NodeByYComparator(g));
}
// set coordinates from 0,1,...,size of layer
int verticalCoord = 0;
for (Node n : currentLayer) {
g.setCoordinates(n, layer, verticalCoord);
verticalCoord++;
}
layer++;
}
/* Do crossing minimization */
CrossingMinimizer cm = new CrossingMinimizer(g, layers, fixedSources, fixedSinks);
cm.run();
/* Do vertical placement */
VerticalCoordinateAssigner vca = new VerticalCoordinateAssigner(g, layers, dummyNodes, dummyEdges);
vca.setBalanceBranchings(m_balanceBranching);
vca.run();
/*
* Reinsert hidden edges with bendpoints, and remove dummy nodes and
* edges
*/
for (Edge hEdge : hiddenEdges) {
Edge e = g.reinsert(hEdge);
for (Node n : hiddenEdgeToDummyVertices.get(hEdge)) {
g.addBend(e, g.getX(n), g.getY(n));
// also removes dummy edges!
g.removeNode(n);
}
}
// clean up unnecessary bend-points
g.cleanBends();
}
use of org.knime.workbench.ui.layout.Graph.Edge in project knime-core by knime.
the class VerticalCoordinateAssigner method markConflicts.
/*
* Functions needed for first phase
*/
/**
* mark conflicting edges.
*/
private void markConflicts() {
if (m_layers.size() < 4) {
// no conflicts possible since there cannot be any inner segments
return;
}
// next-to-last and last layer
for (int i = 1; i < m_layers.size() - 2; i++) {
int k0 = 0;
int l = 0;
for (int l1 = 0; l1 < m_layers.get(i + 1).size(); l1++) {
Node vl1 = m_layers.get(i + 1).get(l1);
Edge innerSegment = getInnerSegmentIncidentTo(vl1);
if (l1 == m_layers.get(i + 1).size() - 1 || innerSegment != null) {
int k1 = m_layers.get(i).size() - 1;
if (innerSegment != null) {
k1 = m_layers.get(i).indexOf(innerSegment.opposite(vl1));
}
while (l <= l1) {
Node vl = m_layers.get(i + 1).get(l);
for (Edge e : m_g.inEdges(vl)) {
Node vk = e.opposite(vl);
int k = m_layers.get(i).indexOf(vk);
if (k < k0 || k > k1) {
m_marked.put(e, true);
}
}
l++;
}
k0 = k1;
}
}
}
}
use of org.knime.workbench.ui.layout.Graph.Edge in project knime-core by knime.
the class VerticalCoordinateAssigner method getNeighbors.
/**
* get either left or right neighbors of a node, sorted by their current
* y-coordinate.
*
* @param n
* @param left true if left neighbors should be returned, false otherwise
* @return
*/
private ArrayList<Node> getNeighbors(final Node n, final boolean left) {
ArrayList<Node> neighbors = new ArrayList<Graph.Node>();
Iterable<Edge> incidentEdges;
if (left) {
incidentEdges = m_g.inEdges(n);
} else {
incidentEdges = m_g.outEdges(n);
}
for (Edge e : incidentEdges) {
neighbors.add(e.opposite(n));
}
// sort by order in layer
Collections.sort(neighbors, new Util.NodeByYComparator(m_g));
return neighbors;
}
use of org.knime.workbench.ui.layout.Graph.Edge in project knime-core by knime.
the class Layerer method updateSources.
/**
* check the outgoing edges of a given node n for becoming a new source
* after n is processed.
*
* @param g
* @param n
* @param sources
* @param residualDegree
*/
private static void updateSources(final Graph g, Node n, ArrayList<Node> sources, Map<Node, Integer> residualDegree) {
for (Edge e : g.outEdges(n)) {
Node t = e.target();
int newDegree = residualDegree.get(t).intValue() - 1;
residualDegree.put(t, newDegree);
if (newDegree == 0)
sources.add(t);
}
}
use of org.knime.workbench.ui.layout.Graph.Edge in project knime-core by knime.
the class LayoutManager method doLayout.
/**
* @param nodes the nodes that should be laid out. If null, all nodes of the
* workflow manager passed to the constructor are laid out.
*/
public void doLayout(final Collection<NodeContainerUI> nodes) {
int X_STRETCH = 100;
int Y_STRETCH = 120;
if (WorkflowEditor.getActiveEditorSnapToGrid()) {
if (WorkflowEditor.getActiveEditorGridX() >= 70) {
X_STRETCH = WorkflowEditor.getActiveEditorGridX();
} else {
X_STRETCH = WorkflowEditor.getActiveEditorGridXOffset(X_STRETCH);
}
Y_STRETCH = WorkflowEditor.getActiveEditorGridYOffset(Y_STRETCH);
}
// add all nodes that should be laid out to the graph
Collection<NodeContainerUI> allNodes = nodes;
if (allNodes == null || allNodes.size() <= 1) {
allNodes = m_wfm.getNodeContainers();
}
// keep the left upper corner of the node cluster.
// Nodes laid out are placed right and below
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
// add all nodes that are to be laid out
for (NodeContainerUI nc : allNodes) {
Node gNode = createGraphNodeForNC(nc);
m_workbenchToGraphNodes.put(nc, gNode);
NodeUIInformation ui = nc.getUIInformation();
minX = (ui.getBounds()[0] < minX) ? ui.getBounds()[0] : minX;
minY = (ui.getBounds()[1] < minY) ? ui.getBounds()[1] : minY;
if (WorkflowEditor.getActiveEditorSnapToGrid()) {
Point nextGridLocation = WorkflowEditor.getActiveEditorNextGridLocation(new Point(minX, minY));
minX = nextGridLocation.x;
minY = nextGridLocation.y;
}
}
// find all connections that connect from/to our nodes,
// keep a flag that states: isClusterInternal
HashMap<ConnectionContainerUI, Boolean> allConns = new HashMap<ConnectionContainerUI, Boolean>();
for (ConnectionContainerUI conn : m_wfm.getConnectionContainers()) {
Node src = null;
if (!conn.getSource().equals(m_wfm.getID())) {
// if it's not a meta node incoming connection
src = m_workbenchToGraphNodes.get(m_wfm.getNodeContainer(conn.getSource()));
}
Node dest = null;
if (!conn.getDest().equals(m_wfm.getID())) {
// if it is not a meta node outgoing connection
dest = m_workbenchToGraphNodes.get(m_wfm.getNodeContainer(conn.getDest()));
}
boolean isInternal = (src != null && dest != null);
// if at least one node is auto laid out we need the connection
if (src != null || dest != null) {
allConns.put(conn, isInternal);
}
}
// Add all connections (internal and leading in/out the cluster)
// to the graph
Edge gEdge;
for (ConnectionContainerUI conn : allConns.keySet()) {
Node srcGraphNode;
Node destGraphNode;
if (conn.getSource().equals(m_wfm.getID())) {
// it connects to a meta node input port:
int portIdx = conn.getSourcePort();
srcGraphNode = m_workbenchWFMInports.get(portIdx);
if (srcGraphNode == null) {
srcGraphNode = m_g.createNode("Incoming " + portIdx, 0, portIdx * Y_STRETCH);
m_workbenchWFMInports.put(portIdx, srcGraphNode);
}
} else {
NodeContainerUI s = m_wfm.getNodeContainer(conn.getSource());
srcGraphNode = m_workbenchToGraphNodes.get(s);
if (srcGraphNode == null) {
// then it connects to an "outside" node
srcGraphNode = m_workbenchIncomingNodes.get(s);
if (srcGraphNode == null) {
srcGraphNode = createGraphNodeForNC(s);
m_workbenchIncomingNodes.put(s, srcGraphNode);
}
}
// else it is a connection inside the layout cluster
}
if (conn.getDest().equals(m_wfm.getID())) {
// it connects to a meta node output port
int portIdx = conn.getDestPort();
destGraphNode = m_workbenchWFMOutports.get(portIdx);
if (destGraphNode == null) {
destGraphNode = m_g.createNode("Outgoing " + portIdx, 250, portIdx * Y_STRETCH);
m_workbenchWFMOutports.put(portIdx, destGraphNode);
}
} else {
NodeContainerUI d = m_wfm.getNodeContainer(conn.getDest());
destGraphNode = m_workbenchToGraphNodes.get(d);
if (destGraphNode == null) {
// then it connects to an "outside" node
destGraphNode = m_workbenchOutgoingNodes.get(d);
if (destGraphNode == null) {
destGraphNode = createGraphNodeForNC(d);
m_workbenchOutgoingNodes.put(d, destGraphNode);
}
}
// else it is a connection within the layout cluster
}
gEdge = m_g.createEdge(srcGraphNode, destGraphNode);
if (gEdge != null) {
m_workbenchToGraphEdges.put(conn, gEdge);
m_parallelConns.put(gEdge, new LinkedList<ConnectionContainerUI>(Collections.singletonList(conn)));
} else {
// a connection between these node already exists in the graph
Edge graphEdge = srcGraphNode.getEdge(destGraphNode);
assert graphEdge != null;
// add the connection to list of parallel connections.
m_parallelConns.get(graphEdge).add(conn);
}
}
// AFTER creating all nodes, mark the incoming/outgoing nodes as fixed
boolean anchorsExist = false;
Map<Node, Boolean> anchorNodes = m_g.createBoolNodeMap();
for (Node n : m_workbenchIncomingNodes.values()) {
anchorsExist = true;
anchorNodes.put(n, Boolean.TRUE);
}
for (Node n : m_workbenchOutgoingNodes.values()) {
anchorsExist = true;
anchorNodes.put(n, Boolean.TRUE);
}
for (Node n : m_workbenchWFMInports.values()) {
anchorsExist = true;
anchorNodes.put(n, Boolean.TRUE);
}
for (Node n : m_workbenchWFMOutports.values()) {
anchorsExist = true;
anchorNodes.put(n, Boolean.TRUE);
}
SimpleLayeredLayouter layouter = new SimpleLayeredLayouter(m_initPlacementSeed);
layouter.setBalanceBranchings(!WorkflowEditor.getActiveEditorSnapToGrid());
if (anchorsExist) {
layouter.doLayout(m_g, anchorNodes);
} else {
layouter.doLayout(m_g, null);
}
// preserver the old stuff for undoers
m_oldBendpoints = new HashMap<ConnectionID, ConnectionUIInformation>();
m_oldCoordinates = new HashMap<NodeID, NodeUIInformation>();
// transfer new coordinates back to nodes
// with fixed nodes (lots of) the new coordinates of the nodes may not
// start at 0.
double coordOffsetX = Integer.MAX_VALUE;
double coordOffsetY = Integer.MAX_VALUE;
for (NodeContainerUI nc : allNodes) {
Node gNode = m_workbenchToGraphNodes.get(nc);
coordOffsetX = Math.min(coordOffsetX, m_g.getX(gNode));
coordOffsetY = Math.min(coordOffsetY, m_g.getY(gNode));
}
for (NodeContainerUI nc : allNodes) {
NodeUIInformation uiInfo = nc.getUIInformation();
if (uiInfo != null) {
Node gNode = m_workbenchToGraphNodes.get(nc);
int[] b = uiInfo.getBounds();
int x = (int) Math.round((m_g.getX(gNode) - coordOffsetX) * X_STRETCH) + minX;
int y = (int) Math.round((m_g.getY(gNode) - coordOffsetY) * Y_STRETCH) + minY;
NodeUIInformation newCoord = NodeUIInformation.builder().setNodeLocation(x, y, b[2], b[3]).setHasAbsoluteCoordinates(uiInfo.hasAbsoluteCoordinates()).setSnapToGrid(WorkflowEditor.getActiveEditorSnapToGrid()).build();
LOGGER.debug("Node " + nc + " gets auto-layout coordinates " + newCoord);
// save old coordinates for undo
m_oldCoordinates.put(nc.getID(), uiInfo);
// triggers gui update
nc.setUIInformation(newCoord);
}
}
// delete old bendpoints - transfer new ones
for (ConnectionContainerUI conn : allConns.keySet()) {
// store old bendpoint for undo
ConnectionUIInformation ui = conn.getUIInfo();
if (ui != null) {
m_oldBendpoints.put(conn.getID(), ui);
} else {
m_oldBendpoints.put(conn.getID(), null);
}
ConnectionUIInformation.Builder newUIBuilder = ConnectionUIInformation.builder();
Edge e = m_workbenchToGraphEdges.get(conn);
if (e == null) {
// a parallel connection not represented by the edge
continue;
}
List<ConnectionContainerUI> conns = m_parallelConns.get(e);
assert conns.size() > 0;
// that is how we created it!
assert conns.get(0) == conn;
ArrayList<Point2D> newBends = m_g.bends(e);
if (newBends != null && !newBends.isEmpty()) {
// half the node icon size...
int extraX = 16;
int extraY = 24;
for (int i = 0; i < newBends.size(); i++) {
Point2D b = newBends.get(i);
newUIBuilder.addBendpoint((int) Math.round((b.getX() - coordOffsetX) * X_STRETCH) + extraX + minX, (int) Math.round((b.getY() - coordOffsetY) * Y_STRETCH) + extraY + minY, i);
}
}
ConnectionUIInformation newUI = newUIBuilder.build();
conn.setUIInfo(newUI);
// compute bendpoints for parallel connections (slightly offset)
for (int i = 1; i < conns.size(); i++) {
// idx 0 == conn!
ConnectionContainerUI parConn = conns.get(i);
// destination port determines offset
int yOffset = (parConn.getDestPort() - conn.getDestPort()) * 10;
ConnectionUIInformation parUI = ConnectionUIInformation.builder(newUI).translate(new int[] { 0, yOffset }).build();
parConn.setUIInfo(parUI);
}
}
}
Aggregations