class Lutaml::Ea::Diagram::PathBuilder

routing algorithms.
diagram elements, supporting various connector types and
This class calculates SVG path data for connectors between
Path builder for connector rendering

def apply_offset(point, offsets, type)

def apply_offset(point, offsets, type)
  offset_x, offset_y = case type
                       when :source
                         [offsets[0], offsets[1]]
                       when :target
                         [offsets[2], offsets[3]]
                       else
                         [0, 0]
                       end
  [point[0] + offset_x, point[1] + offset_y]
end

def bezier_path

def bezier_path
  # Smooth curved path using Bezier curves
  x1, y1 = source_point
  x2, y2 = target_point
  # Control points for smooth curve
  cp1x = x1 + ((x2 - x1) * 0.3)
  cp1y = y1
  cp2x = x2 - ((x2 - x1) * 0.3)
  cp2y = y2
  "M #{x1},#{y1} C #{cp1x},#{cp1y} #{cp2x},#{cp2y} #{x2},#{y2}"
end

def build_path

Returns:
  • (String) - SVG path data
def build_path
  return straight_path if simple_connector?
  return waypoint_path if geometry_has_waypoints?
  case connector[:routing_type]
  when "orthogonal" then orthogonal_path
  when "bezier" then bezier_path
  else manhattan_path
  end
end

def calculate_element_center_point(element)

def calculate_element_center_point(element)
  # Calculate center point based on element bounds
  x = element[:x] || 0
  y = element[:y] || 0
  width = element[:width] || 120
  height = element[:height] || 80
  [x + (width / 2), y + (height / 2)]
end

def calculate_element_connection_point(element, type) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity

rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
def calculate_element_connection_point(element, type) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
  return [0, 0] unless element
  # Calculate connection point based on element bounds and
  # connector type
  x = element[:x] || 0
  y = element[:y] || 0
  width = element[:width] || 120
  height = element[:height] || 80
  point = case type
          when :source
            # Connect from right side for outgoing connectors
            [x + width, y + (height / 2)]
          when :target
            # Connect to left side for incoming connectors
            [x, y + (height / 2)]
          else
            [x + (width / 2), y + (height / 2)]
          end
  return point unless connector[:geometry]
  # Apply relative offsets if specified
  offsets = parse_geometry_offsets(connector[:geometry])
  apply_offset(point, offsets, type)
end

def calculate_end_point(geometry_data)

def calculate_end_point(geometry_data)
  calculate_start_end_point(geometry_data, :target)
end

def calculate_orthogonal_points # rubocop:disable Metrics/AbcSize,Metrics/MethodLength

rubocop:disable Metrics/AbcSize,Metrics/MethodLength
def calculate_orthogonal_points # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
  x1, y1 = source_point
  x2, y2 = target_point
  points = [[x1, y1]]
  # Determine direction based on relative positions
  if (x2 - x1).abs > (y2 - y1).abs
    # Horizontal first, then vertical
    points << [x1 + ((x2 - x1) / 2), y1]
    points << [x1 + ((x2 - x1) / 2), y2]
  else
    # Vertical first, then horizontal
    points << [x1, y1 + ((y2 - y1) / 2)]
    points << [x2, y1 + ((y2 - y1) / 2)]
  end
  points << [x2, y2]
  points
end

def calculate_start_end_point(geometry_data, type) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity

rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
def calculate_start_end_point(geometry_data, type) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
  point = if type == :source
            start_point
          else
            end_point
          end
  if point.nil? || !geometry_data[:has_relative_coords]
    return nil
  end
  offsets = [
    geometry_data[:source_offset_x] || 0,
    geometry_data[:source_offset_y] || 0,
    geometry_data[:target_offset_x] || 0,
    geometry_data[:target_offset_y] || 0,
  ]
  apply_offset(point, offsets, type)
end

def calculate_start_point(geometry_data)

def calculate_start_point(geometry_data)
  calculate_start_end_point(geometry_data, :source)
end

def end_point

def end_point
  return nil unless target_element
  calculate_element_center_point(target_element)
end

def geometry_has_waypoints?

def geometry_has_waypoints?
  return false unless connector[:geometry]
  geometry_data = parse_ea_geometry(connector[:geometry])
  geometry_data&.dig(:waypoints)&.any?
end

def initialize(connector, source_element = nil, target_element = nil)

def initialize(connector, source_element = nil, target_element = nil)
  @connector = connector
  @source_element = source_element
  @target_element = target_element
end

def manhattan_path # rubocop:disable Metrics/MethodLength

rubocop:disable Metrics/MethodLength
def manhattan_path # rubocop:disable Metrics/MethodLength
  # Manhattan distance routing with one bend
  x1, y1 = source_point
  x2, y2 = target_point
  # Calculate bend point (midpoint)
  bend_x = (x1 + x2) / 2
  bend_y = (y1 + y2) / 2
  # Choose bend direction to avoid elements
  if (x2 - x1).abs > (y2 - y1).abs
    # Horizontal bend
    "M #{x1},#{y1} L #{bend_x},#{y1} L #{bend_x},#{y2} L #{x2},#{y2}"
  else
    # Vertical bend
    "M #{x1},#{y1} L #{x1},#{bend_y} L #{x2},#{bend_y} L #{x2},#{y2}"
  end
end

def orthogonal_path

def orthogonal_path
  # Right-angle routing
  points = calculate_orthogonal_points
  path_from_points(points)
end

def path_from_points(points)

def path_from_points(points)
  return "" if points.empty?
  path = "M #{points[0][0]},#{points[0][1]}"
  points[1..].each do |point|
    path += " L #{point[0]},#{point[1]}"
  end
  path
end

def simple_connector?

def simple_connector?
  # Use straight line if both elements have direct coordinates
  connector[:source_x] && connector[:source_y] &&
    connector[:target_x] && connector[:target_y]
end

def source_point

def source_point
  if connector[:source_x] && connector[:source_y]
    [connector[:source_x], connector[:source_y]]
  else
    calculate_element_connection_point(source_element, :source)
  end
end

def start_point

def start_point
  return nil unless source_element
  calculate_element_center_point(source_element)
end

def straight_path

def straight_path
  x1 = connector[:source_x] || 0
  y1 = connector[:source_y] || 0
  x2 = connector[:target_x] || 100
  y2 = connector[:target_y] || 100
  "M #{x1},#{y1} L #{x2},#{y2}"
end

def target_point

def target_point
  if connector[:target_x] && connector[:target_y]
    [connector[:target_x], connector[:target_y]]
  else
    calculate_element_connection_point(target_element, :target)
  end
end

def waypoint_path

def waypoint_path
  geometry_data = parse_ea_geometry(connector[:geometry])
  points = []
  sp = source_point
  points << sp if sp
  geometry_data[:waypoints].each do |wp|
    points << [wp[:x], wp[:y]]
  end
  tp = target_point
  points << tp if tp
  path_from_points(points)
end