ea 0.6.136 → 0.6.137
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/lib/ea/sources/xmi/diagram_builder/association_class_metadata.rb +127 -0
- data/lib/ea/sources/xmi/diagram_builder.rb +85 -7
- data/lib/ea/sources/xmi/extension_elements.rb +5 -4
- data/lib/ea/sources/xmi/relationship_builder.rb +20 -2
- data/lib/ea/sources/xmi/stored_route.rb +28 -1
- data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +3 -2
- data/lib/ea/svg/ea_emitter/connector_redock.rb +2 -1
- data/lib/ea/svg/ea_emitter/element/tagged_value_renderer.rb +10 -5
- data/lib/ea/svg/ea_emitter/label/end_label.rb +1 -1
- data/lib/ea/theme/definition.rb +2 -2
- data/lib/ea/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 86d6c89e9ac852772ebf6ee8eca6f4a08550af81c7f48908655c94b4bc8ab18c
|
|
4
|
+
data.tar.gz: 6ebe1a0e0647909564a7a403730e4041a8eb639ded4479deebbc19db6f3e8e5a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 91e45ba11088734c4ad3998c124945a4a80fc1001e7f3384fd69c02087e7578fcb2d2d1b8c3a6e1b183e26b03c7debd00fc1bdebe216337cb55855573f85f157
|
|
7
|
+
data.tar.gz: 6edd840e00dbf74517daad8bd65e2c28d0cec57ad1c02a15016417327d7c3e7dbd1bacfa22c25f6d62d60af632b4d6812110d31523c642a670b386bb86f1a448
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Sources
|
|
7
|
+
module Xmi
|
|
8
|
+
class DiagramBuilder
|
|
9
|
+
# EA stores the association-class reference, connector subtype and proxy
|
|
10
|
+
# classifiers in extension attributes omitted by the XMI model gem.
|
|
11
|
+
class AssociationClassMetadata
|
|
12
|
+
def initialize(xmi_path)
|
|
13
|
+
@xmi_path = xmi_path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def for(connector, placed_subjects:, waypoints:)
|
|
17
|
+
return unless valid_tether?(connector, waypoints)
|
|
18
|
+
|
|
19
|
+
attached_id = connector_data.dig(connector.idref, :association_class)
|
|
20
|
+
return unless attached_id && class_ids.include?(attached_id)
|
|
21
|
+
return unless placed_subjects.any? { |subject| guid_key(subject) == guid_key(attached_id) }
|
|
22
|
+
|
|
23
|
+
attached_id
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def valid_tether?(connector, waypoints)
|
|
29
|
+
@xmi_path && connector && association_class?(connector) &&
|
|
30
|
+
proxy_pair?(connector) && coincident_route?(waypoints)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def association_class?(connector)
|
|
34
|
+
connector.properties&.ea_type == "Association" &&
|
|
35
|
+
connector_data.dig(connector.idref, :subtype) == "Class" &&
|
|
36
|
+
connector_data.dig(connector.idref, :private_data).to_s.match?(/\A[1-9]\d*\z/)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def proxy_pair?(connector)
|
|
40
|
+
endpoints = [connector.source, connector.target]
|
|
41
|
+
return false unless proxy_endpoints?(endpoints)
|
|
42
|
+
|
|
43
|
+
classifiers = endpoints.map { |endpoint| proxy_classifiers[endpoint.idref] }
|
|
44
|
+
matching_classifiers?(classifiers)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def proxy_endpoints?(endpoints)
|
|
48
|
+
endpoints.all? { |endpoint| endpoint&.model&.type == "ProxyConnector" }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def matching_classifiers?(classifiers)
|
|
52
|
+
classifiers.none? { |classifier| classifier.to_s.empty? } && classifiers.uniq.one?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def coincident_route?(waypoints)
|
|
56
|
+
first = waypoints&.first&.position
|
|
57
|
+
last = waypoints&.last&.position
|
|
58
|
+
first && last && first.x == last.x && first.y == last.y
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def connector_data
|
|
62
|
+
@connector_data ||= read_metadata.fetch(:connectors)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def proxy_classifiers
|
|
66
|
+
@proxy_classifiers ||= read_metadata.fetch(:proxies)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def class_ids
|
|
70
|
+
@class_ids ||= read_metadata.fetch(:classes)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def read_metadata
|
|
74
|
+
@read_metadata ||= scan_metadata
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def scan_metadata
|
|
78
|
+
data = { connectors: {}, proxies: {}, classes: [] }
|
|
79
|
+
File.open(@xmi_path) do |file|
|
|
80
|
+
Nokogiri::XML::Reader(file).each { |node| read_node(node, data) }
|
|
81
|
+
end
|
|
82
|
+
data
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def read_node(node, data)
|
|
86
|
+
return unless node.node_type == Nokogiri::XML::Reader::TYPE_ELEMENT
|
|
87
|
+
|
|
88
|
+
read_element(node, data) if node.local_name == "element"
|
|
89
|
+
read_connector(node, data) if node.local_name == "connector"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def read_element(node, data)
|
|
93
|
+
element = Nokogiri::XML(node.outer_xml).root
|
|
94
|
+
id = attr(element, "idref")
|
|
95
|
+
return unless id
|
|
96
|
+
|
|
97
|
+
type = attr(element, "type")
|
|
98
|
+
data[:classes] << id if type == "uml:Class"
|
|
99
|
+
data[:proxies][id] = attr(element, "classifier") if type == "uml:ProxyConnector"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def read_connector(node, data)
|
|
103
|
+
connector = Nokogiri::XML(node.outer_xml).root
|
|
104
|
+
id = attr(connector, "idref")
|
|
105
|
+
return unless id
|
|
106
|
+
|
|
107
|
+
extended = connector.at_xpath("./*[local-name()='extendedProperties']")
|
|
108
|
+
properties = connector.at_xpath("./*[local-name()='properties']")
|
|
109
|
+
data[:connectors][id] = {
|
|
110
|
+
subtype: attr(properties, "subtype"),
|
|
111
|
+
association_class: attr(extended, "associationclass"),
|
|
112
|
+
private_data: attr(extended, "privatedata1")
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def attr(node, name)
|
|
117
|
+
node&.attribute_nodes&.find { |attribute| attribute.name == name }&.value
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def guid_key(subject)
|
|
121
|
+
subject&.sub(/\AEA(?:ID|PK)_/, "")&.upcase
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -10,6 +10,7 @@ module Ea
|
|
|
10
10
|
# the UML-DI owned_element bounds otherwise.
|
|
11
11
|
class DiagramBuilder
|
|
12
12
|
autoload :ConnectorMetadata, "ea/sources/xmi/diagram_builder/connector_metadata"
|
|
13
|
+
autoload :AssociationClassMetadata, "ea/sources/xmi/diagram_builder/association_class_metadata"
|
|
13
14
|
autoload :FontSettings, "ea/sources/xmi/diagram_builder/font_settings"
|
|
14
15
|
autoload :StereotypeProfiles, "ea/sources/xmi/diagram_builder/stereotype_profiles"
|
|
15
16
|
autoload :UmldiEdges, "ea/sources/xmi/diagram_builder/umldi_edges"
|
|
@@ -44,7 +45,7 @@ module Ea
|
|
|
44
45
|
|
|
45
46
|
def build_one(ext_diagram)
|
|
46
47
|
id = IdNormalizer.from_xmi_id(ext_diagram.id)
|
|
47
|
-
ext_elements = ExtensionElements.new(ext_diagram)
|
|
48
|
+
ext_elements = ExtensionElements.new(ext_diagram, connector_subjects: connector_index)
|
|
48
49
|
placed_elements = ext_elements.placed_elements
|
|
49
50
|
placed_connectors = ext_elements.placed_connectors
|
|
50
51
|
props = ext_diagram.properties
|
|
@@ -130,6 +131,7 @@ module Ea
|
|
|
130
131
|
line_width: style.line_width,
|
|
131
132
|
font_family: font[:family],
|
|
132
133
|
font_size: font[:size],
|
|
134
|
+
font_size_unit: "pt",
|
|
133
135
|
font_logical_height: font[:logical_height],
|
|
134
136
|
font_size_inherited: ExtensionStyleParser.split_pairs(style.raw.to_s)["fontsz"] == "0",
|
|
135
137
|
font_bold: style.bold,
|
|
@@ -140,10 +142,32 @@ module Ea
|
|
|
140
142
|
z_order: placed.seqno,
|
|
141
143
|
duid: style.duid,
|
|
142
144
|
umldi_keyword: umldi_keywords[placed.subject],
|
|
143
|
-
style: (
|
|
145
|
+
style: element_style(placed.subject)
|
|
144
146
|
)
|
|
145
147
|
end
|
|
146
148
|
|
|
149
|
+
# XMI preserves UML member order in ownedAttribute. Carry that
|
|
150
|
+
# provenance to the renderer so it does not replace it with a name sort.
|
|
151
|
+
def element_style(subject)
|
|
152
|
+
style = (@stereotype_profiles ||= StereotypeProfiles.new(root)).for(subject)
|
|
153
|
+
return style unless source_ordered_classifiers.include?(subject)
|
|
154
|
+
|
|
155
|
+
style.merge(stored_attribute_order: true)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def source_ordered_classifiers
|
|
159
|
+
@source_ordered_classifiers ||= begin
|
|
160
|
+
ids = []
|
|
161
|
+
visit = lambda do |element|
|
|
162
|
+
ids << element.id if TypeClassifierMap.classifier_type?(element.type)
|
|
163
|
+
element.packaged_element.to_a.each { |child| visit.call(child) }
|
|
164
|
+
element.nested_classifier.to_a.each { |child| visit.call(child) }
|
|
165
|
+
end
|
|
166
|
+
root.model&.packaged_element.to_a.each { |element| visit.call(element) }
|
|
167
|
+
ids
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
147
171
|
def build_bounds(left, top, right, bottom)
|
|
148
172
|
return nil unless left && top && right && bottom
|
|
149
173
|
|
|
@@ -160,16 +184,33 @@ module Ea
|
|
|
160
184
|
by_subject = placed_elements.group_by { |pe| guid_key(pe.subject) }
|
|
161
185
|
|
|
162
186
|
placed_connectors.each_with_index.map do |placed, index|
|
|
163
|
-
build_connector(placed, by_duid, by_subject, owner_id, index)
|
|
187
|
+
connector = build_connector(placed, by_duid, by_subject, owner_id, index)
|
|
188
|
+
mark_association_class(connector, placed, placed_elements)
|
|
164
189
|
end
|
|
165
190
|
end
|
|
166
191
|
|
|
192
|
+
def mark_association_class(connector, placed, placed_elements)
|
|
193
|
+
reference = association_class_metadata.for(
|
|
194
|
+
connector_index[placed.subject],
|
|
195
|
+
placed_subjects: placed_elements.map(&:subject),
|
|
196
|
+
waypoints: connector.waypoints
|
|
197
|
+
)
|
|
198
|
+
if reference
|
|
199
|
+
connector.style[:association_class_ref] = reference
|
|
200
|
+
connector.source_anchor = connector.waypoints.first.position
|
|
201
|
+
end
|
|
202
|
+
connector
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def association_class_metadata
|
|
206
|
+
@association_class_metadata ||= AssociationClassMetadata.new(xmi_path)
|
|
207
|
+
end
|
|
208
|
+
|
|
167
209
|
def build_connector(placed, by_duid, by_subject, owner_id, rank)
|
|
168
210
|
geom = placed.geometry
|
|
169
211
|
style = placed.style
|
|
170
|
-
edge_source, edge_target =
|
|
171
|
-
source =
|
|
172
|
-
target = by_duid[style.eoid] || sole_placement(by_subject, edge_target)
|
|
212
|
+
edge_source, edge_target = connector_ends(owner_id, placed)
|
|
213
|
+
source, target = placed_ends(style, by_duid, by_subject, edge_source, edge_target)
|
|
173
214
|
ext_meta = connector_meta(placed.subject)
|
|
174
215
|
route = connector_route(placed, source, target, owner_id, ext_meta[:ea_type])
|
|
175
216
|
Ea::Model::DiagramConnector.new(
|
|
@@ -197,6 +238,23 @@ module Ea
|
|
|
197
238
|
)
|
|
198
239
|
end
|
|
199
240
|
|
|
241
|
+
# The saved SOID/EOID can name the model connector's ends in reverse
|
|
242
|
+
# order; the model connector decides which placement is the source.
|
|
243
|
+
def placed_ends(style, by_duid, by_subject, edge_source, edge_target)
|
|
244
|
+
source = by_duid[style.soid] || sole_placement(by_subject, edge_source)
|
|
245
|
+
target = by_duid[style.eoid] || sole_placement(by_subject, edge_target)
|
|
246
|
+
return [source, target] unless source && target &&
|
|
247
|
+
reversed_ends?(source, target, edge_source, edge_target)
|
|
248
|
+
|
|
249
|
+
[target, source]
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def reversed_ends?(source, target, edge_source, edge_target)
|
|
253
|
+
guid_key(edge_source) != guid_key(edge_target) &&
|
|
254
|
+
guid_key(source.subject) == guid_key(edge_target) &&
|
|
255
|
+
guid_key(target.subject) == guid_key(edge_source)
|
|
256
|
+
end
|
|
257
|
+
|
|
200
258
|
def connector_meta(subject_id)
|
|
201
259
|
connector = connector_index[subject_id]
|
|
202
260
|
return { ea_type: nil, direction: nil } unless connector
|
|
@@ -242,7 +300,18 @@ module Ea
|
|
|
242
300
|
points = native || compute_waypoints(placed.geometry, source, target,
|
|
243
301
|
style: placed.style, connector_type: type)
|
|
244
302
|
native ||= umldi_waypoints(owner_id, placed) if points.empty?
|
|
245
|
-
{ waypoints: native || points, style: native
|
|
303
|
+
{ waypoints: native || points, style: connector_route_style(placed, native) }
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def connector_route_style(placed, native)
|
|
307
|
+
route_style = native ? { umldi_route: true } : {}
|
|
308
|
+
# Label seats use EA's saved routing mode on every route source;
|
|
309
|
+
# umldi_route still marks UMLDI waypoints as authoritative.
|
|
310
|
+
route_style.merge!(Qea::DiagramStyleParser.parse(placed.style.raw).slice(:mode, :lwidth, :tree))
|
|
311
|
+
# Source-known links without DUIDs use classifier endpoint metadata,
|
|
312
|
+
# not a saved label box that might refer to the opposite end.
|
|
313
|
+
route_style[:explicit_end_roles] = true unless placed.style.soid && placed.style.eoid
|
|
314
|
+
route_style
|
|
246
315
|
end
|
|
247
316
|
|
|
248
317
|
# EA's unique exported edge supplies bendless routes and recovers
|
|
@@ -254,6 +323,15 @@ module Ea
|
|
|
254
323
|
route&.map { |x, y| Ea::Model::Waypoint.new(position: Ea::Model::Point.new(x: x, y: y)) }
|
|
255
324
|
end
|
|
256
325
|
|
|
326
|
+
def connector_ends(diagram_id, placed)
|
|
327
|
+
umldi_ends(diagram_id, placed) || extension_ends(placed.subject)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def extension_ends(subject)
|
|
331
|
+
connector = connector_index[subject]
|
|
332
|
+
[connector&.source&.idref, connector&.target&.idref]
|
|
333
|
+
end
|
|
334
|
+
|
|
257
335
|
def umldi_ends(diagram_id, placed)
|
|
258
336
|
return unless xmi_path
|
|
259
337
|
|
|
@@ -12,8 +12,9 @@ module Ea
|
|
|
12
12
|
class ExtensionElements
|
|
13
13
|
attr_reader :diagram
|
|
14
14
|
|
|
15
|
-
def initialize(diagram)
|
|
15
|
+
def initialize(diagram, connector_subjects: {})
|
|
16
16
|
@diagram = diagram
|
|
17
|
+
@connector_subjects = connector_subjects
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def placed_elements
|
|
@@ -53,9 +54,9 @@ module Ea
|
|
|
53
54
|
|
|
54
55
|
placement = ExtensionGeometryParser.parse(row.geometry)
|
|
55
56
|
style = ExtensionStyleParser.parse(row.style)
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
return nil unless style.soid && style.eoid
|
|
57
|
+
# Some EA placements omit both DUIDs. The extension connector
|
|
58
|
+
# index still identifies these rows without treating shapes as links.
|
|
59
|
+
return nil unless (style.soid && style.eoid) || @connector_subjects.key?(row.subject)
|
|
59
60
|
|
|
60
61
|
PlacedConnector.new(
|
|
61
62
|
subject: row.subject,
|
|
@@ -45,12 +45,30 @@ module Ea
|
|
|
45
45
|
def package_import_relationships(built)
|
|
46
46
|
built_ids = built.to_set(&:id)
|
|
47
47
|
connector_index.filter_map do |id, connector|
|
|
48
|
-
next if built_ids.include?(id)
|
|
48
|
+
next if built_ids.include?(id)
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
extension_import_relationship(id, connector)
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
def extension_import_relationship(id, connector)
|
|
55
|
+
case connector.properties&.ea_type
|
|
56
|
+
when "Package" then package_import_relationship(id, connector)
|
|
57
|
+
when "Dependency"
|
|
58
|
+
dependency_import_relationship(id, connector) if connector_stereotype(connector) == "import"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# EA exports elementImport outside packagedElement, so the model walk
|
|
63
|
+
# misses it; the extension retains the importing/imported endpoints.
|
|
64
|
+
def dependency_import_relationship(id, connector)
|
|
65
|
+
Ea::Model::Dependency.new(
|
|
66
|
+
id: id, stereotype: "import",
|
|
67
|
+
client_id: connector.source&.idref,
|
|
68
|
+
supplier_id: connector.target&.idref
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
54
72
|
def connector_stereotype(connector)
|
|
55
73
|
connector&.xrefs&.value.to_s[CONNECTOR_STEREOTYPE]
|
|
56
74
|
end
|
|
@@ -64,10 +64,37 @@ module Ea
|
|
|
64
64
|
|
|
65
65
|
def bent_points(src, bends, tgt, link, connector, source, target)
|
|
66
66
|
points = [src, *bends, tgt]
|
|
67
|
-
|
|
67
|
+
if connector.connector_type == "Generalization"
|
|
68
|
+
preserve_side_entry = authored_lateral_side_entry?(points, bends, link, source, target)
|
|
69
|
+
redock_parent_above(points, link, source, target)
|
|
70
|
+
points[-1] = [points[-1][0], points[1][1]] if preserve_side_entry
|
|
71
|
+
end
|
|
68
72
|
points
|
|
69
73
|
end
|
|
70
74
|
|
|
75
|
+
# A single LH tree bend can be the authored side-entry level itself.
|
|
76
|
+
# Parent-above redocking normally moves a Generalization to the target
|
|
77
|
+
# bottom, which destroys that horizontal leg and disagrees with QEA.
|
|
78
|
+
def authored_lateral_side_entry?(points, bends, link, source, target)
|
|
79
|
+
return false unless bends.one? && points.size == 3 && lateral_side_entry_style?(link) &&
|
|
80
|
+
target.y + target.height <= source.y
|
|
81
|
+
|
|
82
|
+
level_on_target_side?(points[1], target)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def lateral_side_entry_style?(link)
|
|
86
|
+
style = Qea::DiagramStyleParser.parse(link.style)
|
|
87
|
+
style[:mode] == "3" && style[:tree] == "LH" && style[:lwidth] == "2"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def within_width?(x, bounds)
|
|
91
|
+
x.between?(bounds.x, bounds.x + bounds.width)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def level_on_target_side?(bend, target)
|
|
95
|
+
bend[1].between?(target.y, target.y + target.height) && !within_width?(bend[0], target)
|
|
96
|
+
end
|
|
97
|
+
|
|
71
98
|
def finish(points, link, connector, source, target, bends, ports, offsets, edge)
|
|
72
99
|
tree_route = stored_tree_route(points, link, connector, source, target, ports)
|
|
73
100
|
if tree_route
|
|
@@ -94,8 +94,9 @@ module Ea
|
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
def content_x_offset(theme, size, size_unit, family)
|
|
97
|
-
# EA
|
|
98
|
-
|
|
97
|
+
# EA seats literal names at the attribute-name content x, with the
|
|
98
|
+
# attribute row point-size increment.
|
|
99
|
+
offset = theme.attribute_content_x_offset || 26
|
|
99
100
|
offset += 2 * (size - 7) if size_unit == "pt"
|
|
100
101
|
offset -= 1 if Ea::Fonts::Metrics.compact_compartment_font?(size, family: family, unit: size_unit)
|
|
101
102
|
offset
|
|
@@ -19,11 +19,12 @@ module Ea
|
|
|
19
19
|
# self-loop, an authoritative UMLDI route, or no drawn bounds for either end).
|
|
20
20
|
def pairs_for(connector, diagram, model_index, bounds_map)
|
|
21
21
|
return nil unless bounds_map
|
|
22
|
-
return nil if connector.style.to_h[:umldi_route] == true
|
|
23
22
|
|
|
24
23
|
tether = association_class_pairs(connector, bounds_map)
|
|
25
24
|
return tether if tether
|
|
26
25
|
|
|
26
|
+
return nil if connector.style.to_h[:umldi_route] == true
|
|
27
|
+
|
|
27
28
|
points = waypoint_pairs(connector)
|
|
28
29
|
lateral = lateral_tree?(connector, points)
|
|
29
30
|
trunk = trunk_tree?(connector, points)
|
|
@@ -19,10 +19,10 @@ module Ea
|
|
|
19
19
|
def self.render(tagged_values, bounds:, first_y:, family:, size:, fill: "#000000",
|
|
20
20
|
size_unit: "px", logical_height: nil)
|
|
21
21
|
line_h = line_height(size, family, size_unit, logical_height)
|
|
22
|
-
text_blocks = [build_text(
|
|
22
|
+
text_blocks = [build_text(header_x(bounds, family, size, size_unit, logical_height),
|
|
23
23
|
first_y, HEADER_TEXT, family, size, fill,
|
|
24
24
|
style: "italic", size_unit: size_unit, logical_height: logical_height)]
|
|
25
|
-
tagged_values.each_with_index do |tv, idx|
|
|
25
|
+
ordered_values(tagged_values).each_with_index do |tv, idx|
|
|
26
26
|
text_blocks << build_text(bounds.x + LINE_X_OFFSET, first_y + ((idx + 1) * line_h),
|
|
27
27
|
"#{tv.key} = #{tv.value}", family, size, fill,
|
|
28
28
|
size_unit: size_unit, logical_height: logical_height)
|
|
@@ -30,6 +30,11 @@ module Ea
|
|
|
30
30
|
%(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def self.ordered_values(tagged_values)
|
|
34
|
+
tagged_values.sort_by { |tv| tv.key.to_s.downcase }
|
|
35
|
+
end
|
|
36
|
+
private_class_method :ordered_values
|
|
37
|
+
|
|
33
38
|
def self.build_text(x, y, content, family, size, fill, style: "normal",
|
|
34
39
|
size_unit: "px", logical_height: nil)
|
|
35
40
|
TextRenderer.new(content: content, x: x, y: y,
|
|
@@ -48,7 +53,7 @@ module Ea
|
|
|
48
53
|
private_class_method :line_height
|
|
49
54
|
|
|
50
55
|
# Center the "tags" header text within the bounds width.
|
|
51
|
-
def self.
|
|
56
|
+
def self.header_x(bounds, family, size, size_unit, logical_height)
|
|
52
57
|
# QEA point captions center on the same integer width emitted in SVG.
|
|
53
58
|
# Retain the legacy pixel API's placement.
|
|
54
59
|
text_width = if size_unit == "pt"
|
|
@@ -60,9 +65,9 @@ module Ea
|
|
|
60
65
|
HEADER_TEXT.length * (size / 2)
|
|
61
66
|
end
|
|
62
67
|
centered = ((bounds.width - text_width) / 2.0).floor
|
|
63
|
-
[HEADER_X_OFFSET, centered].max
|
|
68
|
+
bounds.x + [HEADER_X_OFFSET, centered].max
|
|
64
69
|
end
|
|
65
|
-
private_class_method :
|
|
70
|
+
private_class_method :header_x
|
|
66
71
|
end
|
|
67
72
|
end
|
|
68
73
|
end
|
|
@@ -66,7 +66,7 @@ module Ea
|
|
|
66
66
|
if role_empty?(role) && mult_empty?(mult)
|
|
67
67
|
other_from_prop, other_role, other_mult = role_and_multiplicity(connector,
|
|
68
68
|
opposite(end_kind))
|
|
69
|
-
if stored_labels
|
|
69
|
+
if stored_labels || connector.style.to_h[:explicit_end_roles]
|
|
70
70
|
return [] if role_empty?(other_role) && mult_empty?(other_mult)
|
|
71
71
|
|
|
72
72
|
return own_stereotype_only(text_box, anchor, connector,
|
data/lib/ea/theme/definition.rb
CHANGED
|
@@ -138,8 +138,8 @@ module Ea
|
|
|
138
138
|
@label_font_size_unit || font_size_unit
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
-
#
|
|
142
|
-
#
|
|
141
|
+
# Content x of attribute and enum-literal names; a theme's own
|
|
142
|
+
# attribute_spec overrides the inherited seat.
|
|
143
143
|
def attribute_content_x_offset
|
|
144
144
|
@attribute_spec_override ? attribute_spec.content_x_offset : 22
|
|
145
145
|
end
|
data/lib/ea/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ea
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.137
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -852,6 +852,7 @@ files:
|
|
|
852
852
|
- lib/ea/sources/xmi/annotation_builder.rb
|
|
853
853
|
- lib/ea/sources/xmi/classifier_builder.rb
|
|
854
854
|
- lib/ea/sources/xmi/diagram_builder.rb
|
|
855
|
+
- lib/ea/sources/xmi/diagram_builder/association_class_metadata.rb
|
|
855
856
|
- lib/ea/sources/xmi/diagram_builder/connector_metadata.rb
|
|
856
857
|
- lib/ea/sources/xmi/diagram_builder/font_settings.rb
|
|
857
858
|
- lib/ea/sources/xmi/diagram_builder/stereotype_profiles.rb
|