getVisualPortPosition method

Offset getVisualPortPosition(
  1. String portId, {
  2. required Size portSize,
  3. NodeShape? shape,
})

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 port
  • portSize - 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,
      );
  }
}