Class: Ea::Diagram::PathBuilder

Inherits:
Object
  • Object
show all
Includes:
Util
Defined in:
lib/ea/diagram/path_builder.rb

Overview

Path builder for connector rendering

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Util

#parse_ea_geometry, #parse_geometry_offsets, #style_to_css

Constructor Details

#initialize(connector, source_element = nil, target_element = nil) ⇒ PathBuilder

Returns a new instance of PathBuilder.



15
16
17
18
19
# File 'lib/ea/diagram/path_builder.rb', line 15

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

Instance Attribute Details

#connectorObject (readonly)

Returns the value of attribute connector.



13
14
15
# File 'lib/ea/diagram/path_builder.rb', line 13

def connector
  @connector
end

#source_elementObject (readonly)

Returns the value of attribute source_element.



13
14
15
# File 'lib/ea/diagram/path_builder.rb', line 13

def source_element
  @source_element
end

#target_elementObject (readonly)

Returns the value of attribute target_element.



13
14
15
# File 'lib/ea/diagram/path_builder.rb', line 13

def target_element
  @target_element
end

Instance Method Details

#apply_offset(point, offsets, type) ⇒ Object



188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/ea/diagram/path_builder.rb', line 188

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

#bezier_pathObject



75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/ea/diagram/path_builder.rb', line 75

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

#build_pathString

Build SVG path data for the connector

Returns:

  • (String)

    SVG path data



23
24
25
26
27
28
29
30
31
32
# File 'lib/ea/diagram/path_builder.rb', line 23

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

#calculate_element_connection_point(element, type) ⇒ Object

rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/ea/diagram/path_builder.rb', line 160

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

#calculate_orthogonal_pointsObject

rubocop:disable Metrics/AbcSize,Metrics/MethodLength



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/ea/diagram/path_builder.rb', line 89

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

#geometry_has_waypoints?Boolean

Returns:

  • (Boolean)


120
121
122
123
124
125
# File 'lib/ea/diagram/path_builder.rb', line 120

def geometry_has_waypoints?
  return false unless connector[:geometry]

  geometry_data = parse_ea_geometry(connector[:geometry])
  geometry_data&.dig(:waypoints)&.any?
end

#manhattan_pathObject

rubocop:disable Metrics/MethodLength



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/ea/diagram/path_builder.rb', line 56

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

#orthogonal_pathObject



50
51
52
53
54
# File 'lib/ea/diagram/path_builder.rb', line 50

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

#path_from_points(points) ⇒ Object



110
111
112
113
114
115
116
117
118
# File 'lib/ea/diagram/path_builder.rb', line 110

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

#simple_connector?Boolean

Returns:

  • (Boolean)


35
36
37
38
39
# File 'lib/ea/diagram/path_builder.rb', line 35

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

#source_pointObject



144
145
146
147
148
149
150
# File 'lib/ea/diagram/path_builder.rb', line 144

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

#straight_pathObject



41
42
43
44
45
46
47
48
# File 'lib/ea/diagram/path_builder.rb', line 41

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

#target_pointObject



152
153
154
155
156
157
158
# File 'lib/ea/diagram/path_builder.rb', line 152

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

#waypoint_pathObject



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/ea/diagram/path_builder.rb', line 127

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