Skip to content

Layout

Diagram layout engine for positioning devices and routing wires.

New in v0.15.0: The layout system has been refactored into a modular package structure for better maintainability:

  • layout.engine - Main LayoutEngine orchestrator
  • layout.positioning - DevicePositioner for multi-tier device placement
  • layout.routing - WireRouter for smooth Bezier curve routing
  • layout.sizing - CanvasSizer for calculating canvas dimensions
  • layout.types - LayoutConfig, LayoutResult, and constants
  • layout.utils - Utility functions (e.g., create_bezier_path)

The public API remains unchanged - import from pinviz.layout as before.

layout

Layout engine for positioning components and routing wires.

This package provides modular layout functionality split into focused components: - types: Layout data types and configuration - engine: Main LayoutEngine orchestrator - positioning: Device positioning logic - routing: Wire routing logic - sizing: Canvas size calculation - utils: Utility functions (create_bezier_path)

For backward compatibility, all public classes are re-exported at the package level.

CanvasSizer

CanvasSizer(config: LayoutConfig, board_margin_top: float)

Calculates canvas dimensions to fit all diagram components.

Determines the minimum canvas size needed to display the board, all devices, all wire paths, and optional legend/GPIO diagram without clipping or overlap.

Initialize canvas sizer.

PARAMETER DESCRIPTION
config

Layout configuration parameters

TYPE: LayoutConfig

board_margin_top

Top margin for the board

TYPE: float

Source code in src/pinviz/layout/sizing.py
def __init__(self, config: LayoutConfig, board_margin_top: float):
    """
    Initialize canvas sizer.

    Args:
        config: Layout configuration parameters
        board_margin_top: Top margin for the board
    """
    self.config = config
    self.board_margin_top = board_margin_top

calculate_canvas_size

calculate_canvas_size(
    diagram: Diagram, routed_wires: list[RoutedWire]
) -> tuple[float, float]

Calculate required canvas size to fit all components.

Determines the minimum canvas dimensions needed to display the board, all devices, all wire paths, and optional legend/GPIO diagram without clipping or overlap. Accounts for multi-tier device layouts.

PARAMETER DESCRIPTION
diagram

The diagram containing board, devices, and configuration

TYPE: Diagram

routed_wires

List of wires with calculated routing paths

TYPE: list[RoutedWire]

RETURNS DESCRIPTION
tuple[float, float]

Tuple of (canvas_width, canvas_height) in SVG units

Note

Adds extra margin for the legend and GPIO reference diagram if enabled.

Source code in src/pinviz/layout/sizing.py
def calculate_canvas_size(
    self, diagram: Diagram, routed_wires: list[RoutedWire]
) -> tuple[float, float]:
    """
    Calculate required canvas size to fit all components.

    Determines the minimum canvas dimensions needed to display the board,
    all devices, all wire paths, and optional legend/GPIO diagram without
    clipping or overlap. Accounts for multi-tier device layouts.

    Args:
        diagram: The diagram containing board, devices, and configuration
        routed_wires: List of wires with calculated routing paths

    Returns:
        Tuple of (canvas_width, canvas_height) in SVG units

    Note:
        Adds extra margin for the legend and GPIO reference diagram if enabled.
    """
    # Start with board dimensions
    max_x = self.config.board_margin_left + diagram.board.width
    max_y = self.board_margin_top + diagram.board.height

    # Find rightmost device across all tiers
    for device in diagram.devices:
        device_right = device.position.x + device.width
        device_bottom = device.position.y + device.height
        max_x = max(max_x, device_right)
        max_y = max(max_y, device_bottom)

    # Check wire paths
    for wire in routed_wires:
        for point in wire.path_points:
            max_x = max(max_x, point.x)
            max_y = max(max_y, point.y)

    # Add uniform padding around all content
    canvas_width = max_x + self.config.canvas_padding
    canvas_height = max_y + self.config.canvas_padding

    # Add extra space for device specifications table if needed
    # Table is positioned below the bottommost element (device or board)
    if diagram.show_legend:
        devices_with_specs = [d for d in diagram.devices if d.description]
        if devices_with_specs:
            # Find the bottommost element
            board_bottom = self.board_margin_top + diagram.board.height
            device_bottom = max_y  # Already calculated above from devices
            max_bottom = max(board_bottom, device_bottom)

            # Table position: below bottommost element + margin
            table_y = max_bottom + self.config.specs_table_top_margin

            # Table height: header + rows (varies with multiline descriptions)
            # Use realistic estimate matching render_svg.py base row height
            header_height = TABLE_LAYOUT.HEADER_HEIGHT
            base_row_height = TABLE_LAYOUT.BASE_ROW_HEIGHT
            table_height = header_height + (len(devices_with_specs) * base_row_height)
            table_bottom = table_y + table_height

            # Ensure canvas is tall enough for the table
            canvas_height = max(canvas_height, table_bottom + self.config.canvas_padding)

    # Apply min/max bounds
    original_width = canvas_width
    original_height = canvas_height

    canvas_width = max(
        self.config.min_canvas_width, min(canvas_width, self.config.max_canvas_width)
    )
    canvas_height = max(
        self.config.min_canvas_height, min(canvas_height, self.config.max_canvas_height)
    )

    # Log warnings if clamped
    if (
        canvas_width == self.config.max_canvas_width
        and original_width > self.config.max_canvas_width
    ):
        logger.warning(
            f"Canvas width clamped to {canvas_width}px (requested: {original_width:.0f}px). "
            "Diagram may be too wide. Consider reducing device count or tier spacing."
        )

    if (
        canvas_height == self.config.max_canvas_height
        and original_height > self.config.max_canvas_height
    ):
        logger.warning(
            f"Canvas height clamped to {canvas_height}px (requested: {original_height:.0f}px). "
            "Diagram may be too tall. Consider reducing device count or vertical spacing."
        )

    return canvas_width, canvas_height

DevicePositioner

DevicePositioner(
    config: LayoutConfig, board_margin_top: float
)

Positions devices across horizontal tiers based on connection depth.

Handles multi-tier layout where devices are arranged in columns (tiers) based on their hierarchical level in the connection graph. Within each tier, devices are stacked vertically with smart positioning based on their pin connections.

Initialize device positioner.

PARAMETER DESCRIPTION
config

Layout configuration parameters

TYPE: LayoutConfig

board_margin_top

Top margin for the board

TYPE: float

Source code in src/pinviz/layout/positioning.py
def __init__(self, config: LayoutConfig, board_margin_top: float):
    """
    Initialize device positioner.

    Args:
        config: Layout configuration parameters
        board_margin_top: Top margin for the board
    """
    self.config = config
    self.board_margin_top = board_margin_top

position_devices

position_devices(diagram: Diagram) -> None

Position all devices in the diagram across horizontal tiers.

Calculates device levels from connection graph, assigns X positions for each tier, then positions devices vertically within each tier.

PARAMETER DESCRIPTION
diagram

The diagram containing devices and connections

TYPE: Diagram

Note

This method mutates the position attribute of each device.

Source code in src/pinviz/layout/positioning.py
def position_devices(self, diagram: Diagram) -> None:
    """
    Position all devices in the diagram across horizontal tiers.

    Calculates device levels from connection graph, assigns X positions
    for each tier, then positions devices vertically within each tier.

    Args:
        diagram: The diagram containing devices and connections

    Note:
        This method mutates the position attribute of each device.
    """
    # Calculate device levels from connection graph
    device_levels = self._calculate_device_levels(diagram)

    # Calculate X position for each tier
    tier_positions = self._calculate_tier_positions(device_levels, diagram.devices)

    # Group devices by level
    devices_by_level: dict[int, list[Device]] = {}
    for device in diagram.devices:
        level = device_levels.get(device.name, 0)
        if level not in devices_by_level:
            devices_by_level[level] = []
        devices_by_level[level].append(device)

    # Position devices within each tier
    for level, devices_at_level in devices_by_level.items():
        tier_x = tier_positions[level]

        # Set X positions first
        for device in devices_at_level:
            device.position = Point(tier_x, 0)  # Y will be set below

        # Apply smart vertical positioning (works for all board types)
        self._position_devices_vertically_smart(devices_at_level, diagram)

LayoutConfig dataclass

LayoutConfig(
    board_margin_left: float = 40.0,
    board_margin_top_base: float = 40.0,
    title_height: float = 40.0,
    title_margin: float = 50.0,
    device_area_left: float = 450.0,
    device_spacing_vertical: float = 20.0,
    device_margin_top: float = 60.0,
    rail_offset: float = 40.0,
    wire_spacing: float = 8.0,
    bundle_spacing: float = 4.0,
    corner_radius: float = 5.0,
    canvas_padding: float = 40.0,
    legend_margin: float = 20.0,
    legend_width: float = 150.0,
    legend_height: float = 120.0,
    pin_number_y_offset: float = 12.0,
    gpio_diagram_width: float = 125.0,
    gpio_diagram_margin: float = 40.0,
    specs_table_top_margin: float = 30.0,
    tier_spacing: float = 200.0,
    min_canvas_width: float = 400.0,
    min_canvas_height: float = 300.0,
    max_canvas_width: float = 5000.0,
    max_canvas_height: float = 3000.0,
    warn_connections: int = 30,
    warn_devices: int = 20,
    max_connections: int | None = None,
    max_devices: int | None = None,
)

Configuration parameters for diagram layout.

Controls spacing, margins, and visual parameters for the diagram layout engine. All measurements are in SVG units (typically pixels).

ATTRIBUTE DESCRIPTION
board_margin_left

Left margin before board (default: 40.0)

TYPE: float

board_margin_top_base

Base top margin before board (default: 80.0)

TYPE: float

title_height

Height reserved for title text (default: 40.0)

TYPE: float

title_margin

Margin below title before wires can start (default: 50.0)

TYPE: float

device_area_left

X position where devices start (default: 450.0)

TYPE: float

device_spacing_vertical

Vertical space between stacked devices (default: 20.0)

TYPE: float

device_margin_top

Top margin for first device (default: 60.0)

TYPE: float

rail_offset

Horizontal distance from board to wire routing rail (default: 40.0)

TYPE: float

wire_spacing

Minimum vertical spacing between parallel wires (default: 8.0)

TYPE: float

bundle_spacing

Spacing between wire bundles (default: 4.0)

TYPE: float

corner_radius

Radius for wire corner rounding (default: 5.0)

TYPE: float

canvas_padding

Uniform padding around all content (default: 40.0)

TYPE: float

legend_margin

Margin around legend box (default: 20.0)

TYPE: float

legend_width

Width of legend box (default: 150.0)

TYPE: float

legend_height

Height of legend box (default: 120.0)

TYPE: float

pin_number_y_offset

Vertical offset for pin number circles (default: 12.0)

TYPE: float

gpio_diagram_width

Width of GPIO reference diagram (default: 125.0)

TYPE: float

gpio_diagram_margin

Margin around GPIO reference diagram (default: 40.0)

TYPE: float

specs_table_top_margin

Margin above specs table from bottom element (default: 30.0)

TYPE: float

tier_spacing

Horizontal spacing between device tiers (default: 200.0)

TYPE: float

min_canvas_width

Minimum canvas width (default: 400.0)

TYPE: float

min_canvas_height

Minimum canvas height (default: 300.0)

TYPE: float

max_canvas_width

Maximum canvas width (default: 5000.0)

TYPE: float

max_canvas_height

Maximum canvas height (default: 3000.0)

TYPE: float

warn_connections

Threshold for connection count warning (default: 30)

TYPE: int

warn_devices

Threshold for device count warning (default: 20)

TYPE: int

max_connections

Hard limit for connection count, None = unlimited (default: None)

TYPE: int | None

max_devices

Hard limit for device count, None = unlimited (default: None)

TYPE: int | None

get_board_margin_top

get_board_margin_top(show_title: bool) -> float

Calculate actual board top margin based on whether title is shown.

Source code in src/pinviz/layout/types.py
def get_board_margin_top(self, show_title: bool) -> float:
    """Calculate actual board top margin based on whether title is shown."""
    if show_title:
        return self.board_margin_top_base + self.title_height + self.title_margin
    return self.board_margin_top_base

LayoutConstants dataclass

LayoutConstants(
    Y_POSITION_TOLERANCE: float = 50.0,
    FROM_Y_POSITION_TOLERANCE: float = 100.0,
    RAIL_SPACING_MULTIPLIER: float = 3.0,
    VERTICAL_SPACING_MULTIPLIER: float = 4.5,
    MIN_SEPARATION_MULTIPLIER: float = 1.5,
    SAMPLE_POSITIONS: tuple[float, ...] = (
        0.0,
        0.25,
        0.5,
        0.75,
        1.0,
    ),
    CONFLICT_ADJUSTMENT_DIVISOR: float = 2.0,
    MAX_ADJUSTMENT: float = 50.0,
    STRAIGHT_SEGMENT_LENGTH: float = 15.0,
    WIRE_PIN_EXTENSION: float = 2.0,
    SIMILAR_Y_THRESHOLD: float = 50.0,
    GENTLE_ARC_CTRL1_RAIL_RATIO: float = 0.3,
    GENTLE_ARC_CTRL1_START_RATIO: float = 0.7,
    GENTLE_ARC_CTRL1_OFFSET_RATIO: float = 0.8,
    GENTLE_ARC_CTRL2_RAIL_RATIO: float = 0.7,
    GENTLE_ARC_CTRL2_END_RATIO: float = 0.3,
    GENTLE_ARC_CTRL2_OFFSET_RATIO: float = 0.3,
    S_CURVE_CTRL1_RATIO: float = 0.4,
    S_CURVE_CTRL1_OFFSET_RATIO: float = 0.9,
    S_CURVE_CTRL2_RATIO: float = 0.4,
    S_CURVE_CTRL2_OFFSET_RATIO: float = 0.3,
)

Algorithm constants for wire routing and path calculation.

These constants control the behavior of the wire routing algorithm, including grouping, spacing, and curve generation. They are separate from LayoutConfig as they represent algorithmic tuning parameters rather than user-configurable layout settings.

LayoutEngine

LayoutEngine(config: LayoutConfig | None = None)

Calculate positions and wire routing for diagram components.

The layout engine orchestrates the algorithmic placement of devices and routing of wires between board pins and device pins. It coordinates three specialized components: - DevicePositioner: Handles device placement across tiers - WireRouter: Routes wires with smooth curves - CanvasSizer: Calculates canvas dimensions

Wire routing features
  • Automatic offset for parallel wires from the same pin
  • Rounded corners for professional appearance
  • Multiple routing styles (orthogonal, curved, mixed)
  • Optimized path calculation to minimize overlaps

Initialize layout engine with optional configuration.

PARAMETER DESCRIPTION
config

Layout configuration parameters. If None, uses default LayoutConfig.

TYPE: LayoutConfig | None DEFAULT: None

Source code in src/pinviz/layout/engine.py
def __init__(self, config: LayoutConfig | None = None):
    """
    Initialize layout engine with optional configuration.

    Args:
        config: Layout configuration parameters. If None, uses default LayoutConfig.
    """
    self.config = config or LayoutConfig()

layout_diagram

layout_diagram(diagram: Diagram) -> LayoutResult

Calculate layout for a complete diagram.

Returns a LayoutResult containing all layout information including canvas dimensions, device positions, and routed wires. This immutable result can be passed to the renderer without further diagram mutation.

PARAMETER DESCRIPTION
diagram

The diagram to layout

TYPE: Diagram

RETURNS DESCRIPTION
LayoutResult

LayoutResult with complete layout information

Note

For backward compatibility, this method still mutates device.position on the diagram's devices. Future versions will remove this mutation.

Source code in src/pinviz/layout/engine.py
def layout_diagram(self, diagram: Diagram) -> LayoutResult:
    """
    Calculate layout for a complete diagram.

    Returns a LayoutResult containing all layout information including
    canvas dimensions, device positions, and routed wires. This immutable
    result can be passed to the renderer without further diagram mutation.

    Args:
        diagram: The diagram to layout

    Returns:
        LayoutResult with complete layout information

    Note:
        For backward compatibility, this method still mutates device.position
        on the diagram's devices. Future versions will remove this mutation.
    """
    # Check complexity and log warnings/errors
    self._check_complexity(diagram)

    # Calculate actual board margin based on whether title is shown
    board_margin_top = self.config.get_board_margin_top(diagram.show_title)

    # Position devices across multiple tiers based on connection depth
    # NOTE: This still mutates diagram.devices[].position for backward compatibility
    positioner = DevicePositioner(self.config, board_margin_top)
    positioner.position_devices(diagram)

    # Route all wires
    router = WireRouter(self.config, self.config.board_margin_left, board_margin_top)
    routed_wires = router.route_wires(diagram)

    # Calculate canvas size
    sizer = CanvasSizer(self.config, board_margin_top)
    canvas_width, canvas_height = sizer.calculate_canvas_size(diagram, routed_wires)

    # Collect device positions into immutable mapping
    device_positions = {
        device.name: Point(device.position.x, device.position.y) for device in diagram.devices
    }

    # Calculate board position
    board_position = Point(self.config.board_margin_left, board_margin_top)

    # Validate layout and log warnings
    validation_issues = self.validate_layout(diagram, canvas_width, canvas_height)
    wire_clearance_issues = self._validate_wire_clearance(
        diagram, routed_wires, board_margin_top
    )
    all_issues = validation_issues + wire_clearance_issues
    for issue in all_issues:
        log.warning("layout_validation_issue", issue=issue)

    # Return immutable layout result
    return LayoutResult(
        canvas_width=canvas_width,
        canvas_height=canvas_height,
        board_position=board_position,
        device_positions=device_positions,
        routed_wires=routed_wires,
        board_margin_top=board_margin_top,
    )

validate_layout

validate_layout(
    diagram: Diagram,
    canvas_width: float,
    canvas_height: float,
) -> list[str]

Validate calculated layout for issues.

Checks for: - Device overlaps - Devices positioned at negative coordinates - Devices extending beyond canvas bounds

PARAMETER DESCRIPTION
diagram

The diagram with positioned devices

TYPE: Diagram

canvas_width

Canvas width

TYPE: float

canvas_height

Canvas height

TYPE: float

RETURNS DESCRIPTION
list[str]

List of validation warnings/errors (empty if no issues)

Source code in src/pinviz/layout/engine.py
def validate_layout(
    self, diagram: Diagram, canvas_width: float, canvas_height: float
) -> list[str]:
    """
    Validate calculated layout for issues.

    Checks for:
    - Device overlaps
    - Devices positioned at negative coordinates
    - Devices extending beyond canvas bounds

    Args:
        diagram: The diagram with positioned devices
        canvas_width: Canvas width
        canvas_height: Canvas height

    Returns:
        List of validation warnings/errors (empty if no issues)
    """
    issues = []

    # Check for device overlaps
    for i, dev1 in enumerate(diagram.devices):
        pos1 = dev1.position
        rect1 = (pos1.x, pos1.y, pos1.x + dev1.width, pos1.y + dev1.height)

        for dev2 in diagram.devices[i + 1 :]:
            pos2 = dev2.position
            rect2 = (pos2.x, pos2.y, pos2.x + dev2.width, pos2.y + dev2.height)

            if self._rectangles_overlap(rect1, rect2):
                issues.append(f"Devices '{dev1.name}' and '{dev2.name}' overlap")

    # Check for out-of-bounds devices
    for device in diagram.devices:
        pos = device.position
        if pos.x < 0 or pos.y < 0:
            issues.append(f"Device '{device.name}' positioned at negative coordinates")

        if pos.x + device.width > canvas_width:
            issues.append(f"Device '{device.name}' extends beyond canvas width")

        if pos.y + device.height > canvas_height:
            issues.append(f"Device '{device.name}' extends beyond canvas height")

    return issues

LayoutResult dataclass

LayoutResult(
    canvas_width: float,
    canvas_height: float,
    board_position: Point,
    device_positions: dict[str, Point],
    routed_wires: list[RoutedWire],
    board_margin_top: float,
)

Complete layout information for a diagram.

Contains all calculated layout data including canvas dimensions, positioned devices, and routed wires. This is the immutable output of the layout engine that gets passed to the renderer.

This decouples layout calculation from rendering, enabling: - Independent testing of layout logic - Alternative layout algorithms - Layout result caching - Thread-safe parallel rendering

ATTRIBUTE DESCRIPTION
canvas_width

Calculated canvas width in SVG units

TYPE: float

canvas_height

Calculated canvas height in SVG units

TYPE: float

board_position

Absolute position of the board on canvas

TYPE: Point

device_positions

Mapping of device names to their absolute positions

TYPE: dict[str, Point]

routed_wires

List of wires with calculated routing paths

TYPE: list[RoutedWire]

board_margin_top

Top margin of the board (needed for pin positioning)

TYPE: float

RoutedWire dataclass

RoutedWire(
    connection: Connection,
    path_points: list[Point],
    color: str,
    from_pin_pos: Point,
    to_pin_pos: Point,
)

A wire connection with calculated routing path.

Contains the complete routing information for a wire, including all waypoints along its path. This is the result of the layout engine's wire routing algorithm.

ATTRIBUTE DESCRIPTION
connection

The original connection specification

TYPE: Connection

path_points

List of points defining the wire path (min 2 points)

TYPE: list[Point]

color

Wire color as hex code (from connection or auto-assigned)

TYPE: str

from_pin_pos

Absolute position of source pin on board

TYPE: Point

to_pin_pos

Absolute position of destination pin on device

TYPE: Point

WireData dataclass

WireData(
    connection: Connection,
    from_pos: Point,
    to_pos: Point,
    color: str,
    device: Device,
    source_device: Device | None = None,
    is_source_right_side: bool = False,
    is_target_right_side: bool = False,
)

Intermediate wire data collected during routing.

Stores all information needed to route a single wire before path calculation. Used internally by the layout engine during the wire routing algorithm.

ATTRIBUTE DESCRIPTION
connection

The original connection specification

TYPE: Connection

from_pos

Absolute position of source pin on board

TYPE: Point

to_pos

Absolute position of destination pin on device

TYPE: Point

color

Wire color as hex code (from connection or auto-assigned)

TYPE: str

device

The target device for this wire

TYPE: Device

source_device

The source device (None for board-to-device connections)

TYPE: Device | None

is_source_right_side

True if source pin is on right side of device

TYPE: bool

is_target_right_side

True if target pin is on right side of device

TYPE: bool

WireRouter

WireRouter(
    config: LayoutConfig,
    board_margin_left: float,
    board_margin_top: float,
)

Routes wires between board pins and device pins.

Implements device-based routing lanes where each device gets its own vertical "rail" for routing wires. This prevents wire crossings and maintains visual clarity similar to Fritzing diagrams.

Features: - Automatic offset for parallel wires from the same pin - Smooth Bezier curves for professional appearance - Conflict detection and resolution - Support for device-to-device connections

Initialize wire router.

PARAMETER DESCRIPTION
config

Layout configuration parameters

TYPE: LayoutConfig

board_margin_left

Left margin for the board

TYPE: float

board_margin_top

Top margin for the board

TYPE: float

Source code in src/pinviz/layout/routing.py
def __init__(self, config: LayoutConfig, board_margin_left: float, board_margin_top: float):
    """
    Initialize wire router.

    Args:
        config: Layout configuration parameters
        board_margin_left: Left margin for the board
        board_margin_top: Top margin for the board
    """
    self.config = config
    self.constants = LayoutConstants()
    self.board_margin_left = board_margin_left
    self.board_margin_top = board_margin_top

route_wires

route_wires(diagram: Diagram) -> list[RoutedWire]

Route all wires using device-based routing lanes to prevent crossings.

This is the main wire routing orchestration method. It coordinates the multi-step routing algorithm: 1. Collect wire data (pins, positions, colors) 2. Sort wires for optimal visual flow 3. Group wires by starting position for offset calculation 4. Assign routing rails to each device 5. Calculate initial wire paths with offsets 6. Detect and resolve any remaining conflicts 7. Generate final routed wires

Strategy: - Assign each device a vertical routing zone based on its Y position - Wires to the same device route through that device's zone - Wires to different devices use different zones, preventing crossings - Similar to Fritzing's approach where wires don't cross

PARAMETER DESCRIPTION
diagram

The diagram containing all connections, board, and devices

TYPE: Diagram

RETURNS DESCRIPTION
list[RoutedWire]

List of RoutedWire objects with calculated paths

Source code in src/pinviz/layout/routing.py
def route_wires(self, diagram: Diagram) -> list[RoutedWire]:
    """
    Route all wires using device-based routing lanes to prevent crossings.

    This is the main wire routing orchestration method. It coordinates the
    multi-step routing algorithm:
    1. Collect wire data (pins, positions, colors)
    2. Sort wires for optimal visual flow
    3. Group wires by starting position for offset calculation
    4. Assign routing rails to each device
    5. Calculate initial wire paths with offsets
    6. Detect and resolve any remaining conflicts
    7. Generate final routed wires

    Strategy:
    - Assign each device a vertical routing zone based on its Y position
    - Wires to the same device route through that device's zone
    - Wires to different devices use different zones, preventing crossings
    - Similar to Fritzing's approach where wires don't cross

    Args:
        diagram: The diagram containing all connections, board, and devices

    Returns:
        List of RoutedWire objects with calculated paths
    """
    # Step 1: Collect wire data from all connections
    wire_data = self._collect_wire_data(diagram)

    # Sort wires by starting Y position first, then by target device
    # This groups wires from nearby pins together for better visual flow
    wire_data.sort(key=lambda w: (w.from_pos.y, w.device.position.y, w.to_pos.y))

    # Step 2: Group wires by starting Y position for vertical offset calculation
    y_groups = self._group_wires_by_position(wire_data)

    # Step 3: Assign rail positions for each device
    device_to_base_rail, wire_count_per_device = self._assign_rail_positions(
        wire_data, diagram.board.width
    )

    # Step 4: Calculate initial wire paths with offsets
    initial_wires = self._calculate_initial_wire_paths(
        wire_data, y_groups, device_to_base_rail, wire_count_per_device
    )

    # Step 5: Detect and resolve any overlapping wire paths
    y_offset_adjustments = self._detect_and_resolve_overlaps(initial_wires)

    # Step 6: Generate final routed wires with all adjustments applied
    routed_wires = self._generate_final_routed_wires(initial_wires, y_offset_adjustments)

    return routed_wires

create_bezier_path

create_bezier_path(
    points: list[Point], corner_radius: float = 5.0
) -> str

Create an SVG path string with smooth Bezier curves.

Creates organic, flowing curves through the points using cubic Bezier curves, similar to the classic Fritzing diagram style.

PARAMETER DESCRIPTION
points

List of points defining the path (including control points)

TYPE: list[Point]

corner_radius

Not used, kept for API compatibility

TYPE: float DEFAULT: 5.0

RETURNS DESCRIPTION
str

SVG path d attribute string with smooth curves

Source code in src/pinviz/layout/utils.py
def create_bezier_path(points: list[Point], corner_radius: float = 5.0) -> str:
    """
    Create an SVG path string with smooth Bezier curves.

    Creates organic, flowing curves through the points using cubic Bezier curves,
    similar to the classic Fritzing diagram style.

    Args:
        points: List of points defining the path (including control points)
        corner_radius: Not used, kept for API compatibility

    Returns:
        SVG path d attribute string with smooth curves
    """
    if len(points) < 2:
        return ""

    # Start at first point
    path_parts = [f"M {points[0].x:.2f},{points[0].y:.2f}"]

    if len(points) == 2:
        # Simple line
        path_parts.append(f"L {points[1].x:.2f},{points[1].y:.2f}")
    elif len(points) == 3:
        # Quadratic Bezier through middle point
        path_parts.append(
            f"Q {points[1].x:.2f},{points[1].y:.2f} {points[2].x:.2f},{points[2].y:.2f}"
        )
    elif len(points) == 4:
        # Smooth cubic Bezier using middle two points as control points
        path_parts.append(
            f"C {points[1].x:.2f},{points[1].y:.2f} "
            f"{points[2].x:.2f},{points[2].y:.2f} "
            f"{points[3].x:.2f},{points[3].y:.2f}"
        )
    elif len(points) == 5:
        # Cubic Bezier curve followed by straight line into pin
        # This ensures the wire visually connects directly into the device pin
        # points[0] = start, points[1] = ctrl1, points[2] = ctrl2
        # points[3] = connection point, points[4] = pin center

        # Smooth cubic Bezier using middle two points as control points
        path_parts.append(
            f"C {points[1].x:.2f},{points[1].y:.2f} "
            f"{points[2].x:.2f},{points[2].y:.2f} "
            f"{points[3].x:.2f},{points[3].y:.2f}"
        )

        # Straight line segment into the pin for clear visual connection
        path_parts.append(f"L {points[4].x:.2f},{points[4].y:.2f}")
    else:
        # Many points - create smooth curve through all
        for i in range(1, len(points)):
            if i == len(points) - 1:
                # Last segment - simple curve
                prev = points[i - 1]
                curr = points[i]
                # Create smooth approach to final point
                cx = prev.x + (curr.x - prev.x) * 0.5
                path_parts.append(f"Q {cx:.2f},{curr.y:.2f} {curr.x:.2f},{curr.y:.2f}")
            else:
                # Use current point as control, next as target
                curr = points[i]
                next_pt = points[i + 1]
                path_parts.append(f"Q {curr.x:.2f},{curr.y:.2f} {next_pt.x:.2f},{next_pt.y:.2f}")
                i += 1  # Skip next point since we used it

    return " ".join(path_parts)