class Lutaml::UmlRepository::Presenters::DiagramPresenter
-
Generating SVG output via SvgRenderer
4. Creating a DiagramRenderer wrapper
3. Using StyleResolver to merge EA data + config + defaults
2. Converting EA coordinates to SVG format
1. Loading elements and connectors from the repository
Coordinates the entire diagram rendering pipeline by:
Presenter for UML Diagram elements
def build_connectors_data # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
-
(Array- Array of connector data for rendering)
def build_connectors_data # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity return [] unless element.diagram_links # Build elements index for quick lookup by XMI ID elements_map = build_elements_data .to_h do |elem| [elem[:id], elem] end # Build diagram objects map for EA internal ID lookup diagram_objects_map = {} element.diagram_objects&.each do |dobj| diagram_objects_map[extract_ea_id(dobj)] = dobj.object_xmi_id end element.diagram_links.filter_map do |diagram_link| # rubocop:disable Metrics/BlockLength # Skip hidden connectors next nil if diagram_link.hidden # Look up the actual connector in the repository connector = find_connector_by_xmi_id(diagram_link.connector_xmi_id) # Even if connector object not found, we can still render # Original diagram routing datausing geometry # The diagram_link contains all visual information needed connector_type = if connector determine_connector_type(connector) else # Default to association if connector object # not found "association" end # Parse source and target from diagram_link style # (contains SOID/EOID) style_data = parse_diagram_link_style(diagram_link.style) source_elem = nil target_elem = nil # Try to find elements using EA internal IDs from style if style_data[:soid] source_xmi_id = diagram_objects_map[style_data[:soid]] source_elem = elements_map[source_xmi_id] if source_xmi_id end if style_data[:eoid] target_xmi_id = diagram_objects_map[style_data[:eoid]] target_elem = elements_map[target_xmi_id] if target_xmi_id end # Fallback: try to find from connector object # if style parsing failed if (!source_elem || !target_elem) && connector source_elem ||= find_connector_source(connector, elements_map) target_elem ||= find_connector_target(connector, elements_map) end # Build connector data hash { id: diagram_link.connector_xmi_id, type: connector_type, geometry: diagram_link.geometry, style: diagram_link.style, # Source element for geometry calculation source_element: source_elem, # Target element for geometry calculation target_element: target_elem, # May be nil if not found element: connector, # Original diagram routing data diagram_link: diagram_link, } end end
def build_elements_data # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
-
(Array- Array of element data for rendering)
def build_elements_data # rubocop:disable Metrics/AbcSize,Metrics/MethodLength return [] unless element.diagram_objects element.diagram_objects.filter_map do |diagram_object| # Look up the actual element in the repository uml_element = find_element_by_xmi_id(diagram_object.object_xmi_id) next nil unless uml_element # Convert EA coordinates to SVG format coords = @layout_engine.convert_ea_coordinates(diagram_object) # Build element data hash { id: diagram_object.object_xmi_id, name: uml_element.name, type: determine_element_type(uml_element), x: coords[:x], y: coords[:y], width: coords[:width], height: coords[:height], stereotype: extract_stereotype(uml_element), attributes: extract_attributes(uml_element), operations: extract_operations(uml_element), element: uml_element, # Original UML element diagram_object: diagram_object, # Original diagram placement data } end end
def connectors
-
(Array- Array of connector data hashes)
def connectors build_connectors_data end
def determine_connector_type(connector)
-
(String)- Connector type
Parameters:
-
connector(Object) -- UML connector
def determine_connector_type(connector) case connector.class.name when /Generalization/ "generalization" when /Dependency/ "dependency" when /Realization/ "realization" else "association" end end
def determine_element_type(uml_element)
-
(String)- Element type
Parameters:
-
uml_element(Object) -- UML element
def determine_element_type(uml_element) case uml_element.class.name when /DataType/ "datatype" when /Enum/ "enum" when /Package/ "package" else "class" end end
def elements
-
(Array- Array of element data hashes)
def elements build_elements_data end
def extract_attributes(uml_element)
-
(Array- Array of attribute data)
Parameters:
-
uml_element(Object) -- UML element
def extract_attributes(uml_element) return [] unless uml_element.is_a?(Lutaml::Uml::Classifier) return [] unless uml_element.attributes uml_element.attributes.map do |attr| { name: attr.name, type: attr.type, visibility: attr.visibility, } end end
def extract_ea_id(diagram_object)
-
(String, nil)- EA internal ID (DUID from style)
Parameters:
-
diagram_object(Object) -- Diagram object
def extract_ea_id(diagram_object) return nil unless diagram_object.style # Parse DUID from style string # Style format: "NSL=0;LCol=-1;...;DUID=82C649C4;BCol=16764159;..." # Parse DUID from style string # Style format: "NSL=0;LCol=-1;...;DUID=82C649C4;BCol=16764159;..." style = diagram_object.style match = style.match(/DUID=([^;]+)/) return match[1] if match nil end
def extract_operations(uml_element)
-
(Array- Array of operation data)
Parameters:
-
uml_element(Object) -- UML element
def extract_operations(uml_element) return [] unless uml_element.is_a?(Lutaml::Uml::Classifier) return [] unless uml_element.operations uml_element.operations.map do |op| { name: op.name, visibility: op.visibility, return_type: op.return_type, parameters: extract_parameters(op), } end end
def extract_parameters(operation)
-
(Array- Array of parameter data)
Parameters:
-
operation(Object) -- UML operation
def extract_parameters(operation) return [] unless operation.owned_parameter operation.owned_parameter.map do |param| { name: param.name, type: param.type, } end end
def extract_stereotype(uml_element)
-
(String, nil)- Stereotype name
Parameters:
-
uml_element(Object) -- UML element
def extract_stereotype(uml_element) stereotype = uml_element.stereotype return nil unless stereotype && !stereotype.empty? stereotype.is_a?(Array) ? stereotype.first : stereotype end
def find_connector_by_xmi_id(xmi_id) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
-
(Object, nil)- UML connector or nil if not found
Parameters:
-
xmi_id(String) -- XMI identifier
def find_connector_by_xmi_id(xmi_id) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity return nil unless xmi_id && repository # Look in document-level associations index connector = repository.associations_index.find do |assoc| assoc.xmi_id == xmi_id end return connector if connector repository.classes_index.each do |klass| if klass.is_a?(Lutaml::Uml::Class) && klass.generalization gen = klass.generalization generalizations = gen.is_a?(Array) ? gen : [gen] generalizations.each do |g| return g if g.xmi_id == xmi_id end elsif (klass.is_a?(Lutaml::Uml::Class) || klass.is_a?(Lutaml::Uml::DataType)) && klass.associations assoc = klass.associations.find do |a| a.xmi_id == xmi_id end return assoc if assoc end end nil end
def find_connector_source(connector, elements_map) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
-
(Hash, nil)- Source element data
Parameters:
-
elements_map(Hash) -- Map of element ID to element data -
connector(Object) -- UML connector
def find_connector_source(connector, elements_map) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity source_id = case connector when Lutaml::Uml::Generalization nil when Lutaml::Uml::Dependency Array(connector.client).first when Lutaml::Uml::Association connector.owner_end || Array(connector.member_end).first end elements_map[source_id] end
def find_connector_target(connector, elements_map) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
-
(Hash, nil)- Target element data
Parameters:
-
elements_map(Hash) -- Map of element ID to element data -
connector(Object) -- UML connector
def find_connector_target(connector, elements_map) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity target_id = case connector when Lutaml::Uml::Generalization connector.general when Lutaml::Uml::Dependency Array(connector.supplier).first when Lutaml::Uml::Association ends = Array(connector.member_end) ends.size > 1 ? ends[1] : nil end elements_map[target_id] end
def find_element_by_xmi_id(xmi_id)
-
(Object, nil)- UML element or nil if not found
Parameters:
-
xmi_id(String) -- XMI identifier
def find_element_by_xmi_id(xmi_id) return nil unless xmi_id && repository # Try to find in classes index (includes classes, datatypes, enums) element = repository.classes_index.find { |cls| cls.xmi_id == xmi_id } return element if element # Try to find in packages index repository.packages_index.find { |pkg| pkg.xmi_id == xmi_id } end
def initialize(diagram, repository, options = {})
(**options)-
:config_path(String) -- Path to diagram configuration
Parameters:
-
options(Hash) -- Rendering options -
repository(Repository) -- Repository for looking up elements -
diagram(Lutaml::Uml::Diagram) -- The diagram to present
def initialize(diagram, repository, options = {}) super(diagram, repository) @config_path = options[:config_path] @layout_engine = Ea::Diagram::LayoutEngine.new end
def parse_diagram_link_style(style_string) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
-
(Hash)- Parsed style data
Parameters:
-
style_string(String) -- EA style string
def parse_diagram_link_style(style_string) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength return {} unless style_string data = {} style_string.split(";").each do |pair| next if pair.empty? key, value = pair.split("=", 2) next unless key && value case key.strip when "SOID" data[:soid] = value.strip when "EOID" data[:eoid] = value.strip end end data end
def svg_output(options = {}) # rubocop:disable Metrics/MethodLength
-
(String)- Complete SVG content
Options Hash:
(**options)-
:interactive(Boolean) -- -
:grid_visible(Boolean) -- Show grid -
:background_color(String) -- -
:padding(Integer) -- Padding around diagram
Parameters:
-
options(Hash) -- Rendering options
def svg_output(options = {}) # rubocop:disable Metrics/MethodLength # Build diagram data structure diagram_data = { name: element.name, elements: build_elements_data, connectors: build_connectors_data, } # Create diagram renderer wrapper diagram_renderer = DiagramRendererWrapper.new(diagram_data, @layout_engine) # Create SVG renderer with configuration renderer_options = options.merge(config_path: config_path) svg_renderer = Ea::Diagram::SvgRenderer.new(diagram_renderer, renderer_options) # Generate and return SVG svg_renderer.render end
def to_hash
def to_hash { type: "Diagram", name: element.name, diagram_type: element.diagram_type, package_name: element.package_name, elements_count: (element.diagram_objects || []).count, connectors_count: (element.diagram_links || []).count, } end
def to_table_row
def to_table_row { type: "Diagram", name: element.name || "(unnamed)", details: "#{element.diagram_type} " \ "- #{(element.diagram_objects || []).count} elements", } end
def to_text # rubocop:disable Metrics/AbcSize
Text output for diagram
def to_text # rubocop:disable Metrics/AbcSize lines = [] lines << "Diagram: #{element.name}" lines << ("=" * 50) lines << "" lines << "Type: #{element.diagram_type}" lines << "Package: #{element.package_name || 'Unknown'}" lines << "Elements: #{(element.diagram_objects || []).count}" lines << "Connectors: #{(element.diagram_links || []).count}" lines.join("\n") end