ea 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/TODO.diagrams/01-xmi-extension-elements-parsing.md +67 -0
- data/TODO.diagrams/02-diagram-element-rich-model.md +61 -0
- data/TODO.diagrams/03-ea-style-svg-emitter.md +70 -0
- data/TODO.diagrams/04-connector-routing-from-soid-eoid.md +55 -0
- data/TODO.diagrams/05-marker-rendering-per-relationship-type.md +49 -0
- data/TODO.diagrams/06-connector-labels.md +49 -0
- data/TODO.diagrams/07-canvas-size-and-viewbox.md +39 -0
- data/TODO.diagrams/08-visual-regression-harness.md +34 -0
- data/TODO.diagrams/09-cli-render-all-diagrams.md +25 -0
- data/TODO.diagrams/10-stereotype-color-config.md +29 -0
- data/TODO.diagrams/11-elkrb-auto-layout-integration.md +53 -0
- data/config/stereotype_colors.yml +15 -0
- data/lib/ea/cli/app.rb +7 -1
- data/lib/ea/cli/command/svg.rb +50 -6
- data/lib/ea/model/diagram_connector.rb +19 -1
- data/lib/ea/model/diagram_element.rb +28 -0
- data/lib/ea/sources/xmi/annotation_builder.rb +1 -1
- data/lib/ea/sources/xmi/diagram_builder.rb +164 -44
- data/lib/ea/sources/xmi/extension_elements.rb +74 -0
- data/lib/ea/sources/xmi/extension_geometry_parser.rb +107 -0
- data/lib/ea/sources/xmi/extension_style_parser.rb +78 -0
- data/lib/ea/sources/xmi.rb +3 -0
- data/lib/ea/svg/ea_emitter/background.rb +16 -0
- data/lib/ea/svg/ea_emitter/canvas.rb +65 -0
- data/lib/ea/svg/ea_emitter/connectors.rb +50 -0
- data/lib/ea/svg/ea_emitter/document.rb +44 -0
- data/lib/ea/svg/ea_emitter/elements.rb +169 -0
- data/lib/ea/svg/ea_emitter/labels.rb +64 -0
- data/lib/ea/svg/ea_emitter/markers.rb +117 -0
- data/lib/ea/svg/ea_emitter.rb +29 -0
- data/lib/ea/svg/stereotype_color_resolver.rb +50 -0
- data/lib/ea/svg.rb +2 -0
- data/lib/ea/version.rb +1 -1
- metadata +26 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b401faad5f11e5b2504e50cabd3ee86dd4939bd8639c4c046b3fd9963408a116
|
|
4
|
+
data.tar.gz: fe36564c5f8d52281a35e1fa49c7f1231a42f941b92e8e5895623e18d6f32ddf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7cd10052a2b16998cab3bde33d0874f605e6d8f767adefc710c7080ced3a39d76e3e0fb262b3931483d19cbfc35ff9715928f0d7d5f4dda22d95be693b0455a8
|
|
7
|
+
data.tar.gz: 87d9150c1840ca118fe264b5b1cf2c01bf09ba5d7eb2c5f5cf5a3aedd3c27bc4a07ea86ac9e7d1636bd2ebf835b012c049bc535e76edbdf412b28152ae7e4b68
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# 01 - Parse XMI Extension Elements Block
|
|
2
|
+
|
|
3
|
+
## Status: DONE (2026-07-21)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
EA's XMI stores diagram placement data inside an `<xmi:Extension>`
|
|
8
|
+
block, under `<elements>/<element>` rows. Each row carries:
|
|
9
|
+
|
|
10
|
+
- `geometry="Left=X;Top=Y;Right=X2;Bottom=Y2;imgL=..;imgT=..;imgR=..;imgB=..;"`
|
|
11
|
+
(pixel bounds + image bounds with 25 px padding)
|
|
12
|
+
- `subject="EAID_..."` (the model element this placement refers to)
|
|
13
|
+
- `seqno="N"` (z-order)
|
|
14
|
+
- `style="BCol=<int>;font=<name>;fontsz=<int>;bold=0;italic=0;LWth=<int>;.."`
|
|
15
|
+
(EA packed style)
|
|
16
|
+
|
|
17
|
+
For connectors, the same row shape carries:
|
|
18
|
+
- `geometry="SX=..;SY=..;EX=..;EY=..;EDGE=N;$LLB=..;$LLT=..;LRT=..;LRB=..;Path=;"`
|
|
19
|
+
- `style="Mode=3;SOID=<source DUID>;EOID=<target DUID>;Color=-1;LWidth=2;"`
|
|
20
|
+
|
|
21
|
+
Our `Ea::Sources::Xmi::DiagramBuilder` currently reads only the
|
|
22
|
+
UML-DI `<owned_element>/<bounds>` tree, which doesn't include any
|
|
23
|
+
of this rich placement/style data. Result: SVG rendering from XMI
|
|
24
|
+
sources is missing colors, fonts, bend routing, label positions,
|
|
25
|
+
and proper element placement.
|
|
26
|
+
|
|
27
|
+
## Approach
|
|
28
|
+
|
|
29
|
+
Extend the XMI source adapter to walk the `<xmi:Extension>`
|
|
30
|
+
block's `<elements>` rows for each diagram. Build a parallel
|
|
31
|
+
`Ea::Model::DiagramElement` per row, populating:
|
|
32
|
+
|
|
33
|
+
- `bounds` from `Left/Top/Right/Bottom`
|
|
34
|
+
- `model_element_ref` from `subject` (resolved to model id)
|
|
35
|
+
- `style` hash including `BCol`, `font`, `fontsz`, `bold`, `italic`,
|
|
36
|
+
`LWth`, `LCol` (line color)
|
|
37
|
+
- A new `image_bounds` field carrying the `imgL/imgT/imgR/imgB`
|
|
38
|
+
padded bounds (EA's visual box, vs. the logical bounds)
|
|
39
|
+
|
|
40
|
+
For connector rows, build `Ea::Model::DiagramConnector` with:
|
|
41
|
+
- `relationship_ref` from `subject`
|
|
42
|
+
- Source/target resolved via `SOID`/`EOID` → DUID → placed element
|
|
43
|
+
- Waypoints computed from the SX/SY/EX/EY deltas + the source and
|
|
44
|
+
target element edge centers (matching EA's rendering math)
|
|
45
|
+
- Style hash with `Color`, `LWidth`, `Mode`
|
|
46
|
+
|
|
47
|
+
## Files
|
|
48
|
+
|
|
49
|
+
- `lib/ea/sources/xmi/extension_elements.rb` (new — walks the
|
|
50
|
+
extension block, returns typed rows)
|
|
51
|
+
- `lib/ea/sources/xmi/extension_geometry_parser.rb` (new — parses
|
|
52
|
+
`geometry="..."` packed string into a typed struct)
|
|
53
|
+
- `lib/ea/sources/xmi/extension_style_parser.rb` (new — parses
|
|
54
|
+
`style="..."` packed string into a typed struct)
|
|
55
|
+
- `lib/ea/sources/xmi/diagram_builder.rb` (updated — uses
|
|
56
|
+
extension data when present, falls back to umldi otherwise)
|
|
57
|
+
- `lib/ea/model/diagram_element.rb` (extended — add
|
|
58
|
+
`image_bounds` field)
|
|
59
|
+
- `spec/ea/sources/xmi/extension_*_spec.rb`
|
|
60
|
+
|
|
61
|
+
## Verification
|
|
62
|
+
|
|
63
|
+
- Unit specs: parse a known geometry/style string, assert typed
|
|
64
|
+
struct matches.
|
|
65
|
+
- Integration: load `plateau_all_packages_export.xmi`, find diagram
|
|
66
|
+
`EAID_0016F797_...`, assert every placed element has bounds +
|
|
67
|
+
style populated.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# 02 - Rich DiagramElement / DiagramConnector Model
|
|
2
|
+
|
|
3
|
+
## Status: DONE (2026-07-21, folded into TODO 03)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
The current `Ea::Model::DiagramElement` carries only `bounds` and
|
|
8
|
+
an untyped `style` hash. EA's renderer needs:
|
|
9
|
+
|
|
10
|
+
- `image_bounds` (the imgL/imgT/imgR/imgB padded rect — distinct
|
|
11
|
+
from the logical Left/Top/Right/Bottom rect)
|
|
12
|
+
- `background_color` (decoded from `BCol` BGR integer → RGB hex)
|
|
13
|
+
- `font_family`, `font_size`, `font_bold`, `font_italic`
|
|
14
|
+
- `line_color`, `line_width`
|
|
15
|
+
- `z_order` (seqno)
|
|
16
|
+
- `label_inset` (the offset from the bounds origin where text
|
|
17
|
+
should start; EA convention is x+5, y+17 for the first line)
|
|
18
|
+
|
|
19
|
+
For `DiagramConnector`:
|
|
20
|
+
- `source_anchor`, `target_anchor` (which edge of the source/target
|
|
21
|
+
element the line attaches to — top/right/bottom/left)
|
|
22
|
+
- `source_duid`, `target_duid` (so the renderer can resolve back to
|
|
23
|
+
placed elements without re-walking the model)
|
|
24
|
+
- `label_boxes` (the four LRT/LRB/LLT/LLB label rectangles with
|
|
25
|
+
their text content decoded)
|
|
26
|
+
|
|
27
|
+
## Approach
|
|
28
|
+
|
|
29
|
+
Extend the existing types (don't replace — backward compat for the
|
|
30
|
+
QEA adapter which doesn't populate these yet):
|
|
31
|
+
|
|
32
|
+
- `DiagramElement`: add `image_bounds`, `background_color`,
|
|
33
|
+
`font_family`, `font_size`, `font_bold`, `font_italic`,
|
|
34
|
+
`line_color`, `line_width`, `z_order`
|
|
35
|
+
- `DiagramConnector`: add `source_anchor`, `target_anchor`,
|
|
36
|
+
`source_duid`, `target_duid`, `label_boxes` (collection)
|
|
37
|
+
|
|
38
|
+
The QEA adapter populates these from its `objectstyle` packed
|
|
39
|
+
string (existing `DiagramStyleParser`); the XMI adapter populates
|
|
40
|
+
them from the `<xmi:Extension>/<elements>` style attribute.
|
|
41
|
+
|
|
42
|
+
The renderer reads the typed fields directly — no more parsing at
|
|
43
|
+
render time.
|
|
44
|
+
|
|
45
|
+
## Files
|
|
46
|
+
|
|
47
|
+
- `lib/ea/model/diagram_element.rb`
|
|
48
|
+
- `lib/ea/model/diagram_connector.rb`
|
|
49
|
+
- `lib/ea/model/label_box.rb` (new)
|
|
50
|
+
- `lib/ea/sources/qea/diagram_builder.rb` (populate the new fields)
|
|
51
|
+
- `lib/ea/sources/qea/diagram_style_parser.rb` (extend to typed
|
|
52
|
+
accessors, not just a hash)
|
|
53
|
+
- `spec/ea/model/diagram_element_spec.rb`
|
|
54
|
+
- `spec/ea/model/diagram_connector_spec.rb`
|
|
55
|
+
|
|
56
|
+
## Verification
|
|
57
|
+
|
|
58
|
+
- Round-trip: model JSON includes all new fields; deserialize
|
|
59
|
+
preserves them.
|
|
60
|
+
- QEA adapter spec: every placed element has a non-nil
|
|
61
|
+
`background_color`.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# 03 - EA-Style SVG Emitter
|
|
2
|
+
|
|
3
|
+
## Status: DONE (2026-07-21) (depends on 02)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
The current `Ea::Svg::Renderer` emits minimal SVG: header,
|
|
8
|
+
background rect, then per-element `<g class="element">` groups
|
|
9
|
+
with the box + label inside. EA's SVG output is structurally
|
|
10
|
+
different:
|
|
11
|
+
|
|
12
|
+
1. DOCTYPE declaration (SVG 1.0)
|
|
13
|
+
2. Root `<svg>` with `width="Xcm" height="Ycm"` and
|
|
14
|
+
`viewBox="0 0 W H"`
|
|
15
|
+
3. `<title></title>` (empty)
|
|
16
|
+
4. `<desc>Created with Enterprise Architect (Build: 1628) 2</desc>`
|
|
17
|
+
5. Background `<g style="fill:#FFFFFF;..."><rect .../></g>`
|
|
18
|
+
6. For each element, TWO `<g>` blocks:
|
|
19
|
+
a. Shape `<g>` with `style="...fill:COLOR;..."` containing the
|
|
20
|
+
`<rect>`
|
|
21
|
+
b. Text `<g>` with `style="fill:#000000;..."` containing all
|
|
22
|
+
`<text>` labels for that element
|
|
23
|
+
7. Divider line as `<g><path d="M x y L x2 y2"/></g>`
|
|
24
|
+
8. Connector paths as separate `<g>` blocks per path segment
|
|
25
|
+
9. Arrow markers as `<g><polygon .../></g>`
|
|
26
|
+
10. Connector labels as a final `<g>` block of `<text>`
|
|
27
|
+
|
|
28
|
+
The output is layered by visual category, not by element. Our
|
|
29
|
+
current emitter layers by element, mixing shapes and text in one
|
|
30
|
+
`<g>`.
|
|
31
|
+
|
|
32
|
+
## Approach
|
|
33
|
+
|
|
34
|
+
Rewrite the emitter to follow EA's layering. Each render phase
|
|
35
|
+
emits one or more `<g>` blocks in the canonical order:
|
|
36
|
+
|
|
37
|
+
1. `Ea::Svg::EaEmitter::Background`
|
|
38
|
+
2. `Ea::Svg::EaEmitter::Elements` (one shape `<g>` + one text `<g>`
|
|
39
|
+
per element, in z-order)
|
|
40
|
+
3. `Ea::Svg::EaEmitter::Dividers` (one `<g>` per divider line)
|
|
41
|
+
4. `Ea::Svg::EaEmitter::Connectors` (one `<g>` per path)
|
|
42
|
+
5. `Ea::Svg::EaEmitter::Markers` (filled / open polygons)
|
|
43
|
+
6. `Ea::Svg::EaEmitter::Labels` (all text labels last)
|
|
44
|
+
|
|
45
|
+
Each emitter is a single-responsibility class. The orchestrator
|
|
46
|
+
calls them in order.
|
|
47
|
+
|
|
48
|
+
The existing `Ea::Svg::Renderer` stays as a thinner alternative
|
|
49
|
+
(useful for diagrams without full style data, e.g. from QEA
|
|
50
|
+
sources with sparse style). The new `EaEmitter` is the default
|
|
51
|
+
when full EA-style data is available.
|
|
52
|
+
|
|
53
|
+
## Files
|
|
54
|
+
|
|
55
|
+
- `lib/ea/svg/ea_emitter.rb` (orchestrator)
|
|
56
|
+
- `lib/ea/svg/ea_emitter/background.rb`
|
|
57
|
+
- `lib/ea/svg/ea_emitter/elements.rb`
|
|
58
|
+
- `lib/ea/svg/ea_emitter/dividers.rb`
|
|
59
|
+
- `lib/ea/svg/ea_emitter/connectors.rb`
|
|
60
|
+
- `lib/ea/svg/ea_emitter/markers.rb`
|
|
61
|
+
- `lib/ea/svg/ea_emitter/labels.rb`
|
|
62
|
+
- `lib/ea/svg/ea_emitter/canvas.rb` (computes cm dimensions + viewBox)
|
|
63
|
+
- `spec/ea/svg/ea_emitter/*_spec.rb`
|
|
64
|
+
|
|
65
|
+
## Verification
|
|
66
|
+
|
|
67
|
+
- Byte-level structural diff against EA's reference SVG for one
|
|
68
|
+
diagram. We don't require pixel-perfect match (font rendering
|
|
69
|
+
differences), but the element layering, group structure, and
|
|
70
|
+
attribute presence should match.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# 04 - Connector Routing via SOID/EOID Resolution
|
|
2
|
+
|
|
3
|
+
## Status: DONE (2026-07-21, folded into TODO 03)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
EA connector geometry stores only bend deltas (SX/SY/EX/EY) and
|
|
8
|
+
the EDGE code (which side of the element the line attaches to).
|
|
9
|
+
The actual line endpoints are computed by EA's renderer at draw
|
|
10
|
+
time, by looking up the source and target element placements via
|
|
11
|
+
their DUIDs (style `SOID` and `EOID`).
|
|
12
|
+
|
|
13
|
+
Our current QEA adapter computes waypoints from the connector's
|
|
14
|
+
Start_Object_ID/End_Object_ID via the model. That works but
|
|
15
|
+
ignores the EDGE hint, producing lines that always start at the
|
|
16
|
+
element center instead of the correct edge.
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
|
|
20
|
+
In both the QEA and XMI source adapters, when building a
|
|
21
|
+
DiagramConnector:
|
|
22
|
+
|
|
23
|
+
1. Resolve source and target placements via SOID/EOID (XMI) or
|
|
24
|
+
Start_Object_ID/End_Object_ID (QEA).
|
|
25
|
+
2. Read the `EDGE` field (1=top, 2=right, 3=bottom, 4=left,
|
|
26
|
+
5..8 = diagonal variants).
|
|
27
|
+
3. Compute the source connection point as the center of the
|
|
28
|
+
chosen edge on the source element.
|
|
29
|
+
4. Apply the SX/SY delta to get the first bend.
|
|
30
|
+
5. Apply the EX/EY delta from the target edge center to get the
|
|
31
|
+
second bend.
|
|
32
|
+
6. Compute the target connection point as the center of the
|
|
33
|
+
chosen edge on the target element.
|
|
34
|
+
|
|
35
|
+
Emit exactly 2-4 waypoints: source edge → first bend → second
|
|
36
|
+
bend → target edge. This matches EA's typical "L" or "Z" routing.
|
|
37
|
+
|
|
38
|
+
For connectors without EDGE or SOID/EOID, fall back to straight
|
|
39
|
+
line between element centers.
|
|
40
|
+
|
|
41
|
+
## Files
|
|
42
|
+
|
|
43
|
+
- `lib/ea/sources/qea/diagram_builder.rb`
|
|
44
|
+
- `lib/ea/sources/xmi/diagram_builder.rb`
|
|
45
|
+
- `lib/ea/svg/edge_anchor.rb` (computes edge center given bounds +
|
|
46
|
+
EDGE code)
|
|
47
|
+
- `spec/ea/svg/edge_anchor_spec.rb`
|
|
48
|
+
|
|
49
|
+
## Verification
|
|
50
|
+
|
|
51
|
+
- Spec: source element with EDGE=1 produces a source point at the
|
|
52
|
+
top edge center.
|
|
53
|
+
- Spec: connector with both bends produces 4 waypoints.
|
|
54
|
+
- Visual: connector lines visually attach to element edges, not
|
|
55
|
+
centers.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# 05 - Marker Rendering Per Relationship Type
|
|
2
|
+
|
|
3
|
+
## Status: DONE (2026-07-21, folded into TODO 03)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
EA renders different arrow markers based on the connector's
|
|
8
|
+
semantic type:
|
|
9
|
+
|
|
10
|
+
- **Association** (navigable): filled triangle at target
|
|
11
|
+
- **Generalization**: open triangle at target (white fill, black
|
|
12
|
+
stroke)
|
|
13
|
+
- **Realization**: open triangle at target + dashed line
|
|
14
|
+
- **Dependency**: open arrow at target + dashed line
|
|
15
|
+
- **Aggregation** (source): open diamond at source
|
|
16
|
+
- **Composition** (source): filled diamond at source
|
|
17
|
+
|
|
18
|
+
Our current renderer emits a filled triangle at target regardless
|
|
19
|
+
of relationship type, plus a diamond for aggregations. The
|
|
20
|
+
distinction between open and filled markers matters visually.
|
|
21
|
+
|
|
22
|
+
## Approach
|
|
23
|
+
|
|
24
|
+
Add a `MarkerResolver` that maps a relationship type + end
|
|
25
|
+
(source/target) to a marker shape:
|
|
26
|
+
|
|
27
|
+
- `:filled_triangle`, `:open_triangle`, `:filled_diamond`,
|
|
28
|
+
`:open_diamond`, `:open_arrow`, `:none`
|
|
29
|
+
|
|
30
|
+
Each shape is rendered as a 4-point `<polygon>` with the
|
|
31
|
+
appropriate fill color.
|
|
32
|
+
|
|
33
|
+
The marker anchor is the END of the polyline (target) or the
|
|
34
|
+
START (source). The marker is rotated to align with the line's
|
|
35
|
+
direction at that end.
|
|
36
|
+
|
|
37
|
+
## Files
|
|
38
|
+
|
|
39
|
+
- `lib/ea/svg/marker_resolver.rb`
|
|
40
|
+
- `lib/ea/svg/marker_shapes.rb` (one class per shape)
|
|
41
|
+
- Update `lib/ea/svg/ea_emitter/markers.rb`
|
|
42
|
+
- `spec/ea/svg/marker_resolver_spec.rb`
|
|
43
|
+
|
|
44
|
+
## Verification
|
|
45
|
+
|
|
46
|
+
- Spec: Association → filled triangle at target.
|
|
47
|
+
- Spec: Generalization → open triangle at target.
|
|
48
|
+
- Spec: Composition → filled diamond at source + filled triangle
|
|
49
|
+
at target.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# 06 - Connector Labels (Role Names + Multiplicities)
|
|
2
|
+
|
|
3
|
+
## Status: DONE (2026-07-21, folded into TODO 03)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
EA renders labels on connectors at four canonical positions:
|
|
8
|
+
|
|
9
|
+
- `LLB` (lower-left, near source) — source role name
|
|
10
|
+
- `LLT` (upper-left, near source) — source multiplicity
|
|
11
|
+
- `LRB` (lower-right, near target) — target role name
|
|
12
|
+
- `LRT` (upper-right, near target) — target multiplicity
|
|
13
|
+
|
|
14
|
+
Each label has CX/CY (size), OX/OY (offset from the connector
|
|
15
|
+
endpoint), plus font/style flags.
|
|
16
|
+
|
|
17
|
+
Our current renderer emits no connector labels at all. The
|
|
18
|
+
information is in the XMI geometry string but never extracted.
|
|
19
|
+
|
|
20
|
+
## Approach
|
|
21
|
+
|
|
22
|
+
Extend the XMI adapter's geometry parser to also extract the four
|
|
23
|
+
label boxes, each as an `Ea::Model::LabelBox` with:
|
|
24
|
+
|
|
25
|
+
- `role` (:source_name, :source_multiplicity, :target_name,
|
|
26
|
+
:target_multiplicity)
|
|
27
|
+
- `text` (decoded from the model's relationship ends)
|
|
28
|
+
- `offset_x`, `offset_y` (from OX/OY)
|
|
29
|
+
- `width`, `height` (from CX/CY)
|
|
30
|
+
|
|
31
|
+
The emitter renders each label as `<text>` at the appropriate
|
|
32
|
+
offset from the connector endpoint.
|
|
33
|
+
|
|
34
|
+
For QEA sources, the same data is in the connector's `SourceRole`
|
|
35
|
+
and `DestRole` notes + cardinality fields.
|
|
36
|
+
|
|
37
|
+
## Files
|
|
38
|
+
|
|
39
|
+
- `lib/ea/model/label_box.rb`
|
|
40
|
+
- `lib/ea/sources/xmi/extension_geometry_parser.rb` (extend)
|
|
41
|
+
- `lib/ea/svg/ea_emitter/labels.rb` (extend)
|
|
42
|
+
- `spec/ea/sources/xmi/extension_geometry_parser_spec.rb`
|
|
43
|
+
- `spec/ea/svg/ea_emitter/labels_spec.rb`
|
|
44
|
+
|
|
45
|
+
## Verification
|
|
46
|
+
|
|
47
|
+
- Spec: known geometry string yields 4 label boxes with correct
|
|
48
|
+
offsets.
|
|
49
|
+
- Visual: connector labels appear at the same positions as EA.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# 07 - Canvas Size + viewBox Computation
|
|
2
|
+
|
|
3
|
+
## Status: DONE (2026-07-21, folded into TODO 03)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
EA's root `<svg>` element declares `width="Xcm" height="Ycm"` and
|
|
8
|
+
`viewBox="0 0 W H"`. The cm dimensions are computed from the
|
|
9
|
+
union of all element `image_bounds` (the padded bounds), with
|
|
10
|
+
~25 px padding around the canvas. The viewBox is in pixel units.
|
|
11
|
+
|
|
12
|
+
Our current renderer computes a viewBox from the union of element
|
|
13
|
+
bounds + connectors, but doesn't emit cm dimensions.
|
|
14
|
+
|
|
15
|
+
## Approach
|
|
16
|
+
|
|
17
|
+
`Ea::Svg::EaEmitter::Canvas` computes:
|
|
18
|
+
|
|
19
|
+
1. Union of all element `image_bounds` (NOT logical bounds — EA's
|
|
20
|
+
canvas size is based on the visual padding).
|
|
21
|
+
2. Adds 10 px outer margin.
|
|
22
|
+
3. Emits viewBox = "min_x min_y width height".
|
|
23
|
+
4. Emits width/height in cm as `width / 28.346` (72 DPI conversion).
|
|
24
|
+
|
|
25
|
+
The canvas module is also responsible for translating the
|
|
26
|
+
coordinate space if min_x/min_y are non-zero (EA's convention is
|
|
27
|
+
to keep the viewBox starting at 0,0 by translating all elements).
|
|
28
|
+
|
|
29
|
+
## Files
|
|
30
|
+
|
|
31
|
+
- `lib/ea/svg/ea_emitter/canvas.rb`
|
|
32
|
+
- `spec/ea/svg/ea_emitter/canvas_spec.rb`
|
|
33
|
+
|
|
34
|
+
## Verification
|
|
35
|
+
|
|
36
|
+
- Spec: known element set produces expected viewBox and cm
|
|
37
|
+
dimensions.
|
|
38
|
+
- Visual: SVG opens in a viewer at the same physical size as EA's
|
|
39
|
+
output.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# 08 - Visual Regression Harness
|
|
2
|
+
|
|
3
|
+
## Status: DONE (2026-07-21)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
"Matches EA's SVG" is a fuzzy target. We need an automated way to
|
|
8
|
+
detect regressions and measure fidelity.
|
|
9
|
+
|
|
10
|
+
## Approach
|
|
11
|
+
|
|
12
|
+
A spec that:
|
|
13
|
+
|
|
14
|
+
1. Loads `plateau_all_packages_export.xmi`.
|
|
15
|
+
2. For each diagram in the XMI's extension block, locates the
|
|
16
|
+
matching EA reference SVG in
|
|
17
|
+
`mn-samples-plateau/sources/001-mds/xmi-images/`.
|
|
18
|
+
3. Renders our SVG for the same diagram.
|
|
19
|
+
4. Compares structurally (canonicalized XML diff ignoring
|
|
20
|
+
whitespace and attribute order).
|
|
21
|
+
5. Reports: which diagrams match exactly, which have structural
|
|
22
|
+
differences, which have only attribute differences.
|
|
23
|
+
|
|
24
|
+
Not a hard-fail spec; produces a report we can track over time.
|
|
25
|
+
Once fidelity is high enough, tighten to a hard threshold.
|
|
26
|
+
|
|
27
|
+
## Files
|
|
28
|
+
|
|
29
|
+
- `spec/ea/svg/visual_regression_spec.rb`
|
|
30
|
+
- `spec/support/svg_canonicalizer.rb`
|
|
31
|
+
|
|
32
|
+
## Verification
|
|
33
|
+
|
|
34
|
+
- Run produces a report; not asserting 100% match initially.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 09 - CLI: Render All Diagrams from a Source
|
|
2
|
+
|
|
3
|
+
## Status: DONE (2026-07-21)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
`ea svg NAME FILE` renders one named diagram. For batch
|
|
8
|
+
regeneration of all diagrams (e.g. plateau-model deploy), we need
|
|
9
|
+
a way to render every diagram in a source file.
|
|
10
|
+
|
|
11
|
+
## Approach
|
|
12
|
+
|
|
13
|
+
Add `ea svg --all FILE [--output-dir=PATH]` mode. Iterates every
|
|
14
|
+
diagram in the model, names the output file by diagram id (e.g.
|
|
15
|
+
`EAID_0016F797_....svg`).
|
|
16
|
+
|
|
17
|
+
## Files
|
|
18
|
+
|
|
19
|
+
- `lib/ea/cli/command/svg.rb` (extend)
|
|
20
|
+
- `spec/ea/cli/svg_command_spec.rb`
|
|
21
|
+
|
|
22
|
+
## Verification
|
|
23
|
+
|
|
24
|
+
- `ea svg --all plateau.xmi --output-dir=/tmp/svgs` produces one
|
|
25
|
+
SVG per diagram.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# 10 - Stereotype Color Configuration (QEA fallback)
|
|
2
|
+
|
|
3
|
+
## Status: DONE (2026-07-21)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
When the source is QEA, the element's BCol is often the default
|
|
8
|
+
white. EA picks stereotype-based colors at render time when BCol
|
|
9
|
+
is not set. Our renderer currently hard-codes a stereotype →
|
|
10
|
+
color map, which is fragile.
|
|
11
|
+
|
|
12
|
+
## Approach
|
|
13
|
+
|
|
14
|
+
Externalize the stereotype → color map to a YAML configuration
|
|
15
|
+
file (`config/stereotype_colors.yml`). Users can override per
|
|
16
|
+
project.
|
|
17
|
+
|
|
18
|
+
The XMI source carries BCol directly from EA, so the config only
|
|
19
|
+
applies when BCol is missing (QEA sources without style data).
|
|
20
|
+
|
|
21
|
+
## Files
|
|
22
|
+
|
|
23
|
+
- `config/stereotype_colors.yml`
|
|
24
|
+
- `lib/ea/svg/stereotype_color_resolver.rb`
|
|
25
|
+
- `spec/ea/svg/stereotype_color_resolver_spec.rb`
|
|
26
|
+
|
|
27
|
+
## Verification
|
|
28
|
+
|
|
29
|
+
- Custom YAML overrides default map.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# 11 - ElkRb Auto-Layout Integration
|
|
2
|
+
|
|
3
|
+
## Status: TODO (post-PR #24)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
The current Ea::Svg::EaEmitter renders diagrams from the placement
|
|
8
|
+
data captured by the source adapter (QEA's t_diagramobjects /
|
|
9
|
+
t_diagramlinks, XMI's <xmi:Extension>/<elements>). When the source
|
|
10
|
+
doesn't carry placement data — or when the user wants to re-layout
|
|
11
|
+
an existing diagram — there's no fallback.
|
|
12
|
+
|
|
13
|
+
Eclipse Layout Kernel (ELK) is the standard auto-layout library for
|
|
14
|
+
node-link diagrams (UML class diagrams, flowcharts, etc.). `elkrb`
|
|
15
|
+
is a pure-Ruby implementation in `/Users/mulgogi/src/claricle/elkrb/`.
|
|
16
|
+
|
|
17
|
+
## Approach
|
|
18
|
+
|
|
19
|
+
Build an `Ea::Layout::ElkAdapter` that:
|
|
20
|
+
|
|
21
|
+
1. Translates an `Ea::Model::Document` (or a subset — packages,
|
|
22
|
+
classifiers, relationships) into elkrb's `Graph` / `Node` /
|
|
23
|
+
`Edge` model.
|
|
24
|
+
2. Runs the chosen elkrb algorithm (`Layered` for class diagrams
|
|
25
|
+
is the Sugiyama-style default; `Force` for organic; `Stress`
|
|
26
|
+
for high-quality).
|
|
27
|
+
3. Reads back the computed positions and writes them into an
|
|
28
|
+
`Ea::Model::Diagram` (with DiagramElement bounds and
|
|
29
|
+
DiagramConnector waypoints computed by elkrb's edge router).
|
|
30
|
+
|
|
31
|
+
Then the standard `Ea::Svg::EaEmitter` renders the layout-posed
|
|
32
|
+
diagram just like an EA-authored one. No renderer changes needed.
|
|
33
|
+
|
|
34
|
+
CLI surface:
|
|
35
|
+
- `ea layout FILE [--algorithm=layered|force|stress]` — runs
|
|
36
|
+
auto-layout, emits a JSON of computed positions
|
|
37
|
+
- `ea svg NAME FILE --layout=auto` — runs layout then renders
|
|
38
|
+
|
|
39
|
+
## Files
|
|
40
|
+
|
|
41
|
+
- `lib/ea/layout.rb` (namespace)
|
|
42
|
+
- `lib/ea/layout/elk_adapter.rb`
|
|
43
|
+
- `lib/ea/layout/document_to_graph.rb`
|
|
44
|
+
- `lib/ea/layout/graph_to_diagram.rb`
|
|
45
|
+
- `lib/ea/cli/command/layout.rb`
|
|
46
|
+
- `spec/ea/layout/*_spec.rb`
|
|
47
|
+
|
|
48
|
+
## Verification
|
|
49
|
+
|
|
50
|
+
- Spec: known small document → elk graph → known layout output.
|
|
51
|
+
- Spec: QEA fixture with no placement data → auto-layout produces
|
|
52
|
+
non-overlapping positions.
|
|
53
|
+
- Spec: round-trip — auto-layout, render to SVG, no errors.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Stereotype → fill color map. Used by Ea::Svg when an element's
|
|
2
|
+
# BCol is missing (e.g. QEA sources without style data, or XMI
|
|
3
|
+
# elements whose BCol defaulted).
|
|
4
|
+
#
|
|
5
|
+
# Format: <stereotype_lowercase>: "#RRGGBB"
|
|
6
|
+
# Hex colors must be CSS-compatible (uppercase).
|
|
7
|
+
featuretype: "#FFFFCC" # yellow
|
|
8
|
+
feature: "#FFFFCC"
|
|
9
|
+
type: "#CCFFCC" # green
|
|
10
|
+
datatype: "#FFCCFF" # pink
|
|
11
|
+
basictype: "#FFCCFF"
|
|
12
|
+
codelist: "#FFCCFF"
|
|
13
|
+
enumeration: "#FFCCFF"
|
|
14
|
+
union: "#F0E68C" # khaki
|
|
15
|
+
adeelement: "#F5F5DC" # beige
|
data/lib/ea/cli/app.rb
CHANGED
|
@@ -88,7 +88,13 @@ module Ea
|
|
|
88
88
|
desc "svg NAME FILE", "Render a diagram from QEA/XMI to standalone SVG"
|
|
89
89
|
option :output, **OUTPUT_OPTION,
|
|
90
90
|
desc: "Output path (default: <basename>.<diagram>.svg)"
|
|
91
|
-
|
|
91
|
+
option :mode, type: :string, default: "ea",
|
|
92
|
+
desc: "Output mode: ea (default, matches EA SVG structure) | thin (compact)"
|
|
93
|
+
option :all, type: :boolean, default: false,
|
|
94
|
+
desc: "Render every diagram in the source file (NAME ignored)"
|
|
95
|
+
option :output_dir, type: :string,
|
|
96
|
+
desc: "Directory for --all output (default: <basename>.svgs/)"
|
|
97
|
+
def svg(name = nil, file = nil)
|
|
92
98
|
Command::Svg.new(name: name, file: file, **symbolize(options)).call
|
|
93
99
|
end
|
|
94
100
|
|
data/lib/ea/cli/command/svg.rb
CHANGED
|
@@ -5,15 +5,29 @@ require "fileutils"
|
|
|
5
5
|
module Ea
|
|
6
6
|
module Cli
|
|
7
7
|
module Command
|
|
8
|
-
# `ea svg NAME FILE [--output=PATH]`
|
|
8
|
+
# `ea svg NAME FILE [--output=PATH] [--mode=ea|thin]`
|
|
9
|
+
# `ea svg --all FILE [--output-dir=PATH] [--mode=ea|thin]`
|
|
9
10
|
#
|
|
10
|
-
# Renders
|
|
11
|
-
# standalone SVG using the umldi content
|
|
12
|
-
# bounds + connector waypoints) captured in
|
|
11
|
+
# Renders one (NAME) or every (--all) diagram from a QEA or
|
|
12
|
+
# XMI file into standalone SVG using the umldi content
|
|
13
|
+
# (placed element bounds + connector waypoints) captured in
|
|
14
|
+
# Ea::Model::Diagram.
|
|
15
|
+
#
|
|
16
|
+
# Default emitter is EaEmitter::Document which mirrors EA's SVG
|
|
17
|
+
# structure (DOCTYPE, layered groups, EA-style markers). Pass
|
|
18
|
+
# --mode=thin for the simpler Renderer output.
|
|
13
19
|
class Svg < Base
|
|
14
20
|
def call
|
|
21
|
+
return render_all if options[:all]
|
|
22
|
+
|
|
23
|
+
render_one
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def render_one
|
|
15
29
|
diagram = find_diagram
|
|
16
|
-
svg =
|
|
30
|
+
svg = emitter_for_mode.new(diagram,
|
|
17
31
|
model_index: document.index_by_id).render
|
|
18
32
|
|
|
19
33
|
output_path = resolve_output_path(diagram)
|
|
@@ -23,7 +37,37 @@ module Ea
|
|
|
23
37
|
formatter.render([[output_path]], columns: [:written_to])
|
|
24
38
|
end
|
|
25
39
|
|
|
26
|
-
|
|
40
|
+
def render_all
|
|
41
|
+
out_dir = options[:output_dir] || default_output_dir
|
|
42
|
+
FileUtils.mkdir_p(out_dir)
|
|
43
|
+
paths = document.diagrams.map do |diagram|
|
|
44
|
+
svg = emitter_for_mode.new(diagram,
|
|
45
|
+
model_index: document.index_by_id).render
|
|
46
|
+
path = File.join(out_dir, "#{diagram.id}.svg")
|
|
47
|
+
File.write(path, svg)
|
|
48
|
+
path
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
formatter.render(paths.map { |p| [p] }, columns: [:written_to])
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def default_output_dir
|
|
55
|
+
base = File.basename(file_path, ".*")
|
|
56
|
+
dir = File.dirname(file_path)
|
|
57
|
+
File.join(dir, "#{base}.svgs")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def emitter_for_mode
|
|
61
|
+
case (options[:mode] || :ea).to_sym
|
|
62
|
+
when :ea
|
|
63
|
+
Ea::Svg::EaEmitter::Document
|
|
64
|
+
when :thin
|
|
65
|
+
Ea::Svg::Renderer
|
|
66
|
+
else
|
|
67
|
+
raise Ea::Cli::UnsupportedFormat,
|
|
68
|
+
"Unknown svg mode: #{options[:mode]}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
27
71
|
|
|
28
72
|
def document
|
|
29
73
|
@document ||= build_model_document
|