Class: Ea::Svg::EaEmitter::Markers

Inherits:
Object
  • Object
show all
Defined in:
lib/ea/svg/ea_emitter/markers.rb

Overview

Emits marker <polygon> and <path> elements per connector, matching EA's encoding rules. Returns an Array of Layer structs bucketed by marker style.

Layer style keys:

- :diamond_filled  → 4-pt <polygon>, black fill
- :triangle_open   → 3-pt <polygon>, white fill
- :arrow           → 3-pt <path>, no fill (shares style with
connector lines — same stroke, no fill)

Default mode (grouped: true) consolidates all markers of the same style into ONE <g>. Pass grouped: false for per-connector grouping.

Defined Under Namespace

Classes: Entry

Constant Summary collapse

DIAMOND_HALF_W =
5
DIAMOND_HALF_H =
10
TRI_HALF_BASE =
6
TRI_HEIGHT =
11
ARROW_HALF_BASE =
6
ARROW_HEIGHT =
11

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(diagram, model_index:, canvas: nil, grouped: true, stroke_width: 2) ⇒ Markers

Returns a new instance of Markers.



29
30
31
32
33
34
35
# File 'lib/ea/svg/ea_emitter/markers.rb', line 29

def initialize(diagram, model_index:, canvas: nil, grouped: true, stroke_width: 2)
  @diagram = diagram
  @model_index = model_index
  @canvas = canvas
  @grouped = grouped
  @stroke_width = stroke_width
end

Instance Attribute Details

#canvasObject (readonly)

Returns the value of attribute canvas.



27
28
29
# File 'lib/ea/svg/ea_emitter/markers.rb', line 27

def canvas
  @canvas
end

#diagramObject (readonly)

Returns the value of attribute diagram.



27
28
29
# File 'lib/ea/svg/ea_emitter/markers.rb', line 27

def diagram
  @diagram
end

#groupedObject (readonly)

Returns the value of attribute grouped.



27
28
29
# File 'lib/ea/svg/ea_emitter/markers.rb', line 27

def grouped
  @grouped
end

#model_indexObject (readonly)

Returns the value of attribute model_index.



27
28
29
# File 'lib/ea/svg/ea_emitter/markers.rb', line 27

def model_index
  @model_index
end

#stroke_widthObject (readonly)

Returns the value of attribute stroke_width.



27
28
29
# File 'lib/ea/svg/ea_emitter/markers.rb', line 27

def stroke_width
  @stroke_width
end

Instance Method Details

#arrow_path(tip, base) ⇒ Object



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/ea/svg/ea_emitter/markers.rb', line 358

def arrow_path(tip, base)
  tx, ty = translate_point(tip)
  bx, by = translate_point(base)
  ux, uy = unit_vector(tx, ty, bx, by)
  return nil unless ux

  back_x = tx - ux * ARROW_HEIGHT
  back_y = ty - uy * ARROW_HEIGHT
  w1_x = back_x + (-uy) * ARROW_HALF_BASE
  w1_y = back_y + ux * ARROW_HALF_BASE
  w2_x = back_x - (-uy) * ARROW_HALF_BASE
  w2_y = back_y - ux * ARROW_HALF_BASE
  d = "M #{Canvas.coord(w1_x)} #{Canvas.coord(w1_y)} L #{Canvas.coord(tx)} #{Canvas.coord(ty)} L #{Canvas.coord(w2_x)} #{Canvas.coord(w2_y)}"
  %(<path d="#{d}" shape-rendering="auto"/>)
end

#count_for(connector) ⇒ Object

Backwards-compatible API: count of marker bodies emitted.



63
64
65
66
67
68
# File 'lib/ea/svg/ea_emitter/markers.rb', line 63

def count_for(connector)
  return 0 if connector.hidden
  return 0 unless connector.waypoints&.any?(&:position)

  entries_for(connector).size
end

#diamond_polygon(tip, base) ⇒ Object



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/ea/svg/ea_emitter/markers.rb', line 317

def diamond_polygon(tip, base)
  tx, ty = translate_point(tip)
  bx, by = translate_point(base)
  ux, uy = unit_vector(tx, ty, bx, by)
  return nil unless ux

  right_x = tx - ux * DIAMOND_HALF_H + (-uy) * DIAMOND_HALF_W
  right_y = ty - uy * DIAMOND_HALF_H + ux * DIAMOND_HALF_W
  back_x = tx - ux * (DIAMOND_HALF_H * 2)
  back_y = ty - uy * (DIAMOND_HALF_H * 2)
  left_x = tx - ux * DIAMOND_HALF_H - (-uy) * DIAMOND_HALF_W
  left_y = ty - uy * DIAMOND_HALF_H - ux * DIAMOND_HALF_W
  pts = [
    Canvas.coord(tx), Canvas.coord(ty),
    Canvas.coord(right_x), Canvas.coord(right_y),
    Canvas.coord(back_x), Canvas.coord(back_y),
    Canvas.coord(left_x), Canvas.coord(left_y)
  ].join(" ")
  %(<polygon points="#{pts}" shape-rendering="auto"   style="fill-rule:evenodd;"/>)
end

#layersObject



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/ea/svg/ea_emitter/markers.rb', line 37

def layers
  entries = visible_connectors.flat_map { |c| entries_for(c) }
  return [] if entries.empty?

  if grouped
    entries.group_by(&:style_key).map do |key, grouped_entries|
      # EA emits ONE marker per connector regardless of
      # anchor overlap. Verified against plateau Bridge
      # diagram: 9 generalizations fanning from the same
      # parent anchor produce 9 distinct triangle polygons
      # at the same coordinates. The earlier "dedup by
      # anchor" hypothesis is disproven by reference SVGs.
      Layer.new(style_key: key,
                style: style_for(key),
                body: grouped_entries.map(&:body).join("\n  "))
    end
  else
    entries.map { |e| Layer.new(style_key: e.style_key, style: style_for(e.style_key), body: e.body) }
  end
end

#package_anchor_path(tip, base) ⇒ Object



382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/ea/svg/ea_emitter/markers.rb', line 382

def package_anchor_path(tip, base)
  tx, ty = translate_point(tip)
  bx, by = translate_point(base)
  ux, uy = unit_vector(tx, ty, bx, by)
  return nil unless ux

  back_x = tx - ux * PACKAGE_ANCHOR_H_BOTTOM
  back_y = ty - uy * PACKAGE_ANCHOR_H_BOTTOM
  tl_x = tx - ux * PACKAGE_ANCHOR_H_TOP + (-uy) * PACKAGE_ANCHOR_W
  tl_y = ty - uy * PACKAGE_ANCHOR_H_TOP + ux * PACKAGE_ANCHOR_W
  tr_x = tx - ux * PACKAGE_ANCHOR_H_TOP - (-uy) * PACKAGE_ANCHOR_W
  tr_y = ty - uy * PACKAGE_ANCHOR_H_TOP - ux * PACKAGE_ANCHOR_W
  bl_x = back_x + (-uy) * (PACKAGE_ANCHOR_W + 1)
  bl_y = back_y + ux * (PACKAGE_ANCHOR_W + 1)
  br_x = back_x - (-uy) * (PACKAGE_ANCHOR_W + 1)
  br_y = back_y - ux * (PACKAGE_ANCHOR_W + 1)
  d = "M #{Canvas.coord(tl_x)} #{Canvas.coord(tl_y)} " \
      "L #{Canvas.coord(tr_x)} #{Canvas.coord(tr_y)} " \
      "L #{Canvas.coord(br_x)} #{Canvas.coord(br_y)} " \
      "L #{Canvas.coord(bl_x)} #{Canvas.coord(bl_y)} " \
      "L #{Canvas.coord(tl_x)} #{Canvas.coord(tl_y)} Z"
  %(<path d="#{d}" shape-rendering="auto"/>)
end

#plus_path(anchor) ⇒ Object

Plus marker: two crossed line segments at the anchor. EA encodes as one with two sub-paths:

M cx cy-8 L cx cy+8    (vertical arm)
M cx+8 cy L cx-8 cy    (horizontal arm)


270
271
272
273
274
275
276
277
278
279
280
# File 'lib/ea/svg/ea_emitter/markers.rb', line 270

def plus_path(anchor)
  cx, cy = translate_point(anchor)
  cx_str = Canvas.coord(cx)
  cy_str = Canvas.coord(cy)
  up = Canvas.coord(cy - Marker::Plus::ARM_LENGTH)
  down = Canvas.coord(cy + Marker::Plus::ARM_LENGTH)
  left = Canvas.coord(cx - Marker::Plus::ARM_LENGTH)
  right = Canvas.coord(cx + Marker::Plus::ARM_LENGTH)
  d = "M #{cx_str} #{up} L #{cx_str} #{down} M #{right} #{cy_str} L #{left} #{cy_str}"
  %(<path d="#{d}" shape-rendering="auto"/>)
end

#renderObject



58
59
60
# File 'lib/ea/svg/ea_emitter/markers.rb', line 58

def render
  layers.map(&:to_svg).join("\n")
end

#triangle_polygon(tip, base) ⇒ Object



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/ea/svg/ea_emitter/markers.rb', line 338

def triangle_polygon(tip, base)
  tx, ty = translate_point(tip)
  bx, by = translate_point(base)
  ux, uy = unit_vector(tx, ty, bx, by)
  return nil unless ux

  back_x = tx - ux * TRI_HEIGHT
  back_y = ty - uy * TRI_HEIGHT
  w1_x = back_x + (-uy) * TRI_HALF_BASE
  w1_y = back_y + ux * TRI_HALF_BASE
  w2_x = back_x - (-uy) * TRI_HALF_BASE
  w2_y = back_y - ux * TRI_HALF_BASE
  pts = [
    Canvas.coord(tx), Canvas.coord(ty),
    Canvas.coord(w1_x), Canvas.coord(w1_y),
    Canvas.coord(w2_x), Canvas.coord(w2_y)
  ].join(" ")
  %(<polygon points="#{pts}" shape-rendering="auto"   style="fill-rule:evenodd;"/>)
end