getVisualPortPosition method
Gets the visual position where a port should be rendered within the node container.
This calculates the local position of a port within the node's coordinate space. The port's outer edge aligns with the node/shape boundary, and the port extends inward. For example, a left port's left edge aligns with the node's left edge.
Parameters:
portId- The unique identifier of the portportSize- The size of the port widget (width x height)shape- Optional shape for anchor-based positioning
Returns the Offset where the port widget's top-left corner should be positioned.
Shaped nodes: Ports positioned at shape boundary anchors.
- Anchor gives the exact point on the shape boundary (e.g., diamond's left tip)
- Port edge aligns with anchor, port extends inward
- port.offset adjusts from this base position (usually Offset.zero)
Rectangular nodes: Ports positioned using absolute offset.
- port.offset.dy = vertical position for left/right ports
- port.offset.dx = horizontal position for top/bottom ports
- Port edge aligns with node edge, port extends inward
Throws ArgumentError if no port with the given portId is found.
Implementation
Offset getVisualPortPosition(
String portId, {
required Size portSize,
NodeShape? shape,
}) {
final port = [
...inputPorts,
...outputPorts,
].cast<Port?>().firstWhere((p) => p?.id == portId, orElse: () => null);
if (port == null) {
throw ArgumentError('Port $portId not found');
}
// Get anchor position from shape (full node size) or default edge centers
final Offset anchorOffset;
if (shape != null) {
final anchors = shape.getPortAnchors(size.value);
final anchor = anchors.firstWhere(
(a) => a.position == port.position,
orElse: () => _fallbackAnchor(port.position),
);
anchorOffset = anchor.offset;
} else {
anchorOffset = _fallbackAnchor(port.position).offset;
}
// Port positioning:
// - Port outer edge aligns with shape/node boundary at anchor point
// - Port extends INWARD from the boundary
// - Port is centered on the perpendicular axis
// - port.offset provides adjustment from this base position
//
// For shaped nodes: anchor.dy/dx gives the center position, port.offset adjusts
// For rectangular nodes: port.offset gives the center position directly
switch (port.position) {
case PortPosition.left:
// Port left edge at anchor x (shape/node left boundary)
// Port centered vertically at anchor y (shaped) or offset.dy (rectangular)
final baseY = shape != null ? anchorOffset.dy : port.offset.dy;
return Offset(
anchorOffset.dx + port.offset.dx,
baseY - portSize.height / 2 + (shape != null ? port.offset.dy : 0),
);
case PortPosition.right:
// Port right edge at anchor x (shape/node right boundary)
// Port centered vertically at anchor y (shaped) or offset.dy (rectangular)
final baseY = shape != null ? anchorOffset.dy : port.offset.dy;
return Offset(
anchorOffset.dx - portSize.width + port.offset.dx,
baseY - portSize.height / 2 + (shape != null ? port.offset.dy : 0),
);
case PortPosition.top:
// Port top edge at anchor y (shape/node top boundary)
// Port centered horizontally at anchor x (shaped) or offset.dx (rectangular)
final baseX = shape != null ? anchorOffset.dx : port.offset.dx;
return Offset(
baseX - portSize.width / 2 + (shape != null ? port.offset.dx : 0),
anchorOffset.dy + port.offset.dy,
);
case PortPosition.bottom:
// Port bottom edge at anchor y (shape/node bottom boundary)
// Port centered horizontally at anchor x (shaped) or offset.dx (rectangular)
final baseX = shape != null ? anchorOffset.dx : port.offset.dx;
return Offset(
baseX - portSize.width / 2 + (shape != null ? port.offset.dx : 0),
anchorOffset.dy - portSize.height + port.offset.dy,
);
}
}