Skip to content

Commit

Permalink
#1750 Setting input port positions for nodes with multiple inputs (#1751
Browse files Browse the repository at this point in the history
)
  • Loading branch information
tomlyn authored Mar 18, 2024
1 parent 5538f79 commit 401073a
Show file tree
Hide file tree
Showing 9 changed files with 377 additions and 234 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import SetLinksStyleAction from "../command-actions/setLinksStyleAction.js";
import UpdateLinkAction from "../command-actions/updateLinkAction.js";
import LabelUtil from "./label-util.js";
import Logger from "../logging/canvas-logger.js";
import CanvasUtils from "./common-canvas-utils";
import ObjectModel from "../object-model/object-model.js";
import SizeAndPositionObjectsAction from "../command-actions/sizeAndPositionObjectsAction.js";
import getContextMenuDefiniton from "./canvas-controller-menu-utils.js";
Expand Down Expand Up @@ -127,7 +128,10 @@ export default class CanvasController {

setCanvasConfig(config) {
this.logger.log("Setting Canvas Config");
// TODO - Remove these next three lines in next major release.
const correctConfig = this.correctTypo(config);
correctConfig.enableNodeLayout =
CanvasUtils.convertPortPosInfo(correctConfig.enableNodeLayout);
this.objectModel.openPaletteIfNecessary(config);
this.objectModel.setCanvasConfig(correctConfig);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2023 Elyra Authors
* Copyright 2017-2024 Elyra Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,7 +18,7 @@
// objects stored in redux and also the copy of canvas objects maintained by
// the CanvasRender objects.

import { get, has, set } from "lodash";
import { get, has, isNumber, set } from "lodash";
import { ASSOCIATION_LINK, ASSOC_STRAIGHT, COMMENT_LINK, NODE_LINK,
LINK_TYPE_STRAIGHT, SUPER_NODE, NORTH, SOUTH, EAST, WEST }
from "../common-canvas/constants/canvas-constants.js";
Expand Down Expand Up @@ -1246,4 +1246,66 @@ export default class CanvasUtils {
}
return null;
}

// Convert now deprecated layout fields to the port positions arrays. The layout fields are
// expected to provided in pairs. For example: inputPortTopPosX and inputPortTopPosY. However,
// if they are not and only one of the pair is provided the functions in svg-canvas-utils.nodes.js
// that calculate a x, y coordinate for the port will default to 0 if a value provided is undefined.
// TODO - Remove this in a future major release.
static convertPortPosInfo(layout) {
const newLayout = layout;

if (!layout) {
return newLayout;
}

// If custom fields exist for input ports, write the values into the
// inputPortPositions array and delete the redundant fields.
if (isNumber(newLayout.inputPortTopPosX) || isNumber(newLayout.inputPortTopPosY)) {
newLayout.inputPortPositions = [
{ x_pos: newLayout.inputPortTopPosX, y_pos: newLayout.inputPortTopPosY, pos: "topLeft" }
];
delete newLayout.inputPortTopPosX;
delete newLayout.inputPortTopPosY;

} else if (isNumber(newLayout.inputPortBottomPosX) || isNumber(newLayout.inputPortBottomPosY)) {
newLayout.inputPortPositions = [
{ x_pos: newLayout.inputPortBottomPosX, y_pos: newLayout.inputPortBottomPosY, pos: "bottomLeft" }
];
delete newLayout.inputPortBottomPosX;
delete newLayout.inputPortBottomPosY;

} else if (isNumber(newLayout.inputPortLeftPosX) || isNumber(newLayout.inputPortLeftPosY)) {
newLayout.inputPortPositions = [
{ x_pos: newLayout.inputPortLeftPosX, y_pos: newLayout.inputPortLeftPosY, pos: "topLeft" }
];
delete newLayout.inputPortLeftPosX;
delete newLayout.inputPortLeftPosY;
}

// If custom fields exist for output ports, write the values into the
// outputPortPositions array and delete the redundant fields.
if (isNumber(newLayout.outputPortTopPosX) || isNumber(newLayout.outputPortTopPosY)) {
newLayout.outputPortPositions = [
{ x_pos: newLayout.outputPortTopPosX, y_pos: newLayout.outputPortTopPosY, pos: "topLeft" }
];
delete newLayout.outputPortTopPosX;
delete newLayout.outputPortTopPosY;

} else if (isNumber(newLayout.outputPortBottomPosX) || isNumber(newLayout.outputPortBottomPosY)) {
newLayout.outputPortPositions = [
{ x_pos: newLayout.outputPortBottomPosX, y_pos: newLayout.outputPortBottomPosY, pos: "bottomLeft" }
];
delete newLayout.outputPortBottomPosX;
delete newLayout.outputPortBottomPosY;

} else if (isNumber(newLayout.outputPortRightPosX) || isNumber(newLayout.outputPortRightPosY)) {
newLayout.outputPortPositions = [
{ x_pos: newLayout.outputPortRightPosX, y_pos: newLayout.outputPortRightPosY, pos: "topRight" }
];
delete newLayout.outputPortRightPosX;
delete newLayout.outputPortRightPosY;
}
return newLayout;
}
}
213 changes: 140 additions & 73 deletions canvas_modules/common-canvas/src/common-canvas/svg-canvas-renderer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2023 Elyra Authors
* Copyright 2017-2024 Elyra Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -3384,135 +3384,202 @@ export default class SVGCanvasRenderer {
}

setPortPositionsForNode(node) {
if (this.canvasLayout.linkDirection === LINK_DIR_TOP_BOTTOM) {
this.setPortPositionsVertical(node, node.inputs, node.inputPortsWidth, node.layout.inputPortTopPosX, node.layout.inputPortTopPosY);
this.setPortPositionsVertical(node, node.outputs, node.outputPortsWidth, node.layout.outputPortBottomPosX, this.getOutputPortBottomPosY(node));
} else if (this.canvasLayout.linkDirection === LINK_DIR_BOTTOM_TOP) {
this.setPortPositionsVertical(node, node.inputs, node.inputPortsWidth, node.layout.inputPortBottomPosX, this.getInputPortBottomPosY(node));
this.setPortPositionsVertical(node, node.outputs, node.outputPortsWidth, node.layout.outputPortTopPosX, node.layout.outputPortTopPosY);
if (this.canvasLayout.linkDirection === LINK_DIR_TOP_BOTTOM ||
this.canvasLayout.linkDirection === LINK_DIR_BOTTOM_TOP) {
this.setPortPositionsVertical(
node, node.inputs, node.inputPortsWidth,
node.layout.inputPortPositions,
node.layout.inputPortAutoPosition);
this.setPortPositionsVertical(
node, node.outputs, node.outputPortsWidth,
node.layout.outputPortPositions,
node.layout.outputPortAutoPosition,
this.config.enableSingleOutputPortDisplay);

} else {
this.setPortPositionsLeftRight(node, node.inputs, node.inputPortsHeight, node.layout.inputPortLeftPosX, node.layout.inputPortLeftPosY);
this.setPortPositionsLeftRight(node, node.outputs, node.outputPortsHeight,
this.nodeUtils.getNodeOutputPortRightPosX(node),
this.nodeUtils.getNodeOutputPortRightPosY(node),
this.setPortPositionsHoriz(
node, node.inputs, node.inputPortsHeight,
node.layout.inputPortPositions,
node.layout.inputPortAutoPosition);
this.setPortPositionsHoriz(
node, node.outputs, node.outputPortsHeight,
node.layout.outputPortPositions,
node.layout.outputPortAutoPosition,
this.config.enableSingleOutputPortDisplay);
}
}

getOutputPortBottomPosY(node) {
return node.height + node.layout.outputPortBottomPosY;
}

getInputPortBottomPosY(node) {
return node.height + node.layout.inputPortBottomPosY;
}

setPortPositionsVertical(data, ports, portsWidth, xPos, yPos) {
setPortPositionsVertical(node, ports, portsWidth, portPositions, autoPosition, displaySinglePort = false) {
if (ports && ports.length > 0) {
if (data.width <= data.layout.defaultNodeWidth &&
const xPos = this.nodeUtils.getNodePortPosX(portPositions[0], node);
const yPos = this.nodeUtils.getNodePortPosY(portPositions[0], node);

if (node.width <= node.layout.defaultNodeWidth &&
ports.length === 1) {
ports[0].cx = xPos;
ports[0].cy = yPos;
} else {
let xPosition = 0;
// If we are only going to display a single port, we set all the
// port positions to be the same as if there is only one port.
if (displaySinglePort) {
this.setPortPositionsVerticalDisplaySingle(node, ports, xPos, yPos);

if (CanvasUtils.isExpandedSupernode(data)) {
const widthSvgArea = data.width - (2 * this.canvasLayout.supernodeSVGAreaPadding);
const remainingSpace = widthSvgArea - portsWidth;
xPosition = (2 * this.canvasLayout.supernodeSVGAreaPadding) + (remainingSpace / 2);
} else if (autoPosition || CanvasUtils.isExpandedSupernode(node)) {
this.setPortPositionsVerticalAuto(node, ports, portsWidth, yPos);

} else if (portsWidth < data.width) {
xPosition = (data.width - portsWidth) / 2;
} else {
this.setPortPositionsCustom(ports, portPositions, node, xPos, yPos);
}
}
}
}

xPosition += data.layout.portArcOffset;
// If only a single port is to be displayed, this methods sets the x and y
// coordinates of all the ports to the same values appropriately for either
// regular nodes or expanded supernodes.
setPortPositionsVerticalDisplaySingle(node, ports, xPos, yPos) {
let xPosition = 0;
if (CanvasUtils.isExpandedSupernode(node)) {
const widthSvgArea = node.width - (2 * this.canvasLayout.supernodeSVGAreaPadding);
xPosition = widthSvgArea / 2;

// Sub-flow binding node ports need to be spaced by the inverse of the
// zoom amount so that, after zoomToFit on the in-place sub-flow the
// binding node ports line up with those on the supernode. This is only
// necessary with binding nodes with mutiple ports.
let multiplier = 1;
if (CanvasUtils.isSuperBindingNode(data)) {
multiplier = 1 / this.zoomUtils.getZoomScale();
}
} else {
xPosition = xPos;
}

ports.forEach((p) => {
xPosition += (data.layout.portArcRadius * multiplier);
p.cx = xPosition;
p.cy = yPos;
xPosition += ((data.layout.portArcRadius + data.layout.portArcSpacing) * multiplier);
});
}
ports.forEach((p) => {
p.cx = xPosition;
p.cy = yPos;
});
}

// Sets the ports x and y coordinates for regular and expanded supernodes
// when all ports are displayed in a normal manner (as opposed to when a
// single port is displayed).
setPortPositionsVerticalAuto(node, ports, portsWidth, yPos) {
let xPosition = 0;

if (CanvasUtils.isExpandedSupernode(node)) {
const widthSvgArea = node.width - (2 * this.canvasLayout.supernodeSVGAreaPadding);
const remainingSpace = widthSvgArea - portsWidth;
xPosition = this.canvasLayout.supernodeSVGAreaPadding + (remainingSpace / 2);

} else if (portsWidth < node.width) {
xPosition = (node.width - portsWidth) / 2;
}

xPosition += node.layout.portArcOffset;

// Sub-flow binding node ports need to be spaced by the inverse of the
// zoom amount so that, after zoomToFit on the in-place sub-flow the
// binding node ports line up with those on the supernode. This is only
// necessary with binding nodes with mutiple ports.
let multiplier = 1;
if (CanvasUtils.isSuperBindingNode(node)) {
multiplier = 1 / this.zoomUtils.getZoomScale();
}
ports.forEach((p) => {
xPosition += (node.layout.portArcRadius * multiplier);
p.cx = xPosition;
p.cy = yPos;
xPosition += ((node.layout.portArcRadius + node.layout.portArcSpacing) * multiplier);
});
}

setPortPositionsLeftRight(data, ports, portsHeight, xPos, yPos, displaySinglePort = false) {
setPortPositionsHoriz(node, ports, portsHeight, portPositions, autoPosition, displaySinglePort = false) {
if (ports && ports.length > 0) {
if (data.height <= data.layout.defaultNodeHeight &&
const xPos = this.nodeUtils.getNodePortPosX(portPositions[0], node);
const yPos = this.nodeUtils.getNodePortPosY(portPositions[0], node);

if (node.height <= node.layout.defaultNodeHeight &&
ports.length === 1) {
ports[0].cx = xPos;
ports[0].cy = yPos;

} else {
// If we are only going to display a single port, we can set all the
// If we are only going to display a single port, we set all the
// port positions to be the same as if there is only one port.
if (displaySinglePort) {
this.setPortPositionsLeftRightSinglePort(data, ports, xPos, yPos);
this.setPortPositionsHorizDisplaySingle(node, ports, xPos, yPos);

} else if (autoPosition || CanvasUtils.isExpandedSupernode(node)) {
this.setPortPositionsHorizAuto(node, ports, portsHeight, xPos);

} else {
this.setPortPositionsLeftRightAllPorts(data, ports, portsHeight, xPos, yPos);
this.setPortPositionsCustom(ports, portPositions, node, xPos, yPos);
}
}
}
}

// If only a single port is to be displayed, this methods sets the x and y
// coordinates of all the ports to the same values appropriately for either
// regular nodes or expanded supernodes.
setPortPositionsHorizDisplaySingle(node, ports, xPos, yPos) {
let yPosition = 0;
if (CanvasUtils.isExpandedSupernode(node)) {
const heightSvgArea = node.height - this.canvasLayout.supernodeTopAreaHeight - this.canvasLayout.supernodeSVGAreaPadding;
yPosition = this.canvasLayout.supernodeTopAreaHeight + (heightSvgArea / 2);

} else {
yPosition = yPos;
}

ports.forEach((p) => {
p.cx = xPos;
p.cy = yPosition;
});
}

// Sets the ports x and y coordinates for regular and expanded supernodes
// when all ports are displayed in a normal manner (as opposed to when a
// single port is displayed).
setPortPositionsLeftRightAllPorts(data, ports, portsHeight, xPos, yPos) {
setPortPositionsHorizAuto(node, ports, portsHeight, xPos) {
let yPosition = 0;

if (CanvasUtils.isExpandedSupernode(data)) {
const heightSvgArea = data.height - this.canvasLayout.supernodeTopAreaHeight - this.canvasLayout.supernodeSVGAreaPadding;
if (CanvasUtils.isExpandedSupernode(node)) {
const heightSvgArea = node.height - this.canvasLayout.supernodeTopAreaHeight - this.canvasLayout.supernodeSVGAreaPadding;
const remainingSpace = heightSvgArea - portsHeight;
yPosition = this.canvasLayout.supernodeTopAreaHeight + this.canvasLayout.supernodeSVGAreaPadding + (remainingSpace / 2);
yPosition = this.canvasLayout.supernodeTopAreaHeight + (remainingSpace / 2);

} else if (portsHeight < data.height) {
yPosition = (data.height - portsHeight) / 2;
} else if (portsHeight < node.height) {
yPosition = (node.height - portsHeight) / 2;
}

yPosition += data.layout.portArcOffset;
yPosition += node.layout.portArcOffset;

// Sub-flow binding node ports need to be spaced by the inverse of the
// zoom amount so that, after zoomToFit on the in-place sub-flow the
// binding node ports line up with those on the supernode. This is only
// necessary with binding nodes with mutiple ports.
let multiplier = 1;
if (CanvasUtils.isSuperBindingNode(data)) {
if (CanvasUtils.isSuperBindingNode(node)) {
multiplier = 1 / this.zoomUtils.getZoomScale();
}
ports.forEach((p) => {
yPosition += (data.layout.portArcRadius * multiplier);
yPosition += (node.layout.portArcRadius * multiplier);
p.cx = xPos;
p.cy = yPosition;
yPosition += ((data.layout.portArcRadius + data.layout.portArcSpacing) * multiplier);
yPosition += ((node.layout.portArcRadius + node.layout.portArcSpacing) * multiplier);
});
}

// If only a single port is to be displayed, this methods sets the x and y
// coordinates of all the ports to the same values appropriately for either
// regular nodes or expanded supernodes.
setPortPositionsLeftRightSinglePort(data, ports, xPos, yPos) {
let yPosition = 0;
if (CanvasUtils.isExpandedSupernode(data)) {
const heightSvgArea = data.height - this.canvasLayout.supernodeTopAreaHeight - this.canvasLayout.supernodeSVGAreaPadding;
yPosition = this.canvasLayout.supernodeTopAreaHeight + (heightSvgArea / 2);
// Sets the node's port positions based on the custom positions provided
// by the application in the portPositions array.
setPortPositionsCustom(ports, portPositions, node, zerothX, zerothY) {
let xPos = zerothX;
let yPos = zerothY;

} else {
yPosition = yPos;
}

ports.forEach((p) => {
ports.forEach((p, i) => {
// No need to recalculate the zeroth position AND if there are more
// ports than portPositions just use the last port position for all
// subsequent ports.
if (i > 0 && i < portPositions.length) {
xPos = this.nodeUtils.getNodePortPosX(portPositions[i], node);
yPos = this.nodeUtils.getNodePortPosY(portPositions[i], node);
}
p.cx = xPos;
p.cy = yPosition;
p.cy = yPos;
});
}

Expand Down
Loading

0 comments on commit 401073a

Please sign in to comment.