etassia 0.1.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 +7 -0
- data/CHANGELOG.md +140 -0
- data/LICENSE +25 -0
- data/README.adoc +436 -0
- data/exe/etassia +7 -0
- data/lib/etassia/cli.rb +187 -0
- data/lib/etassia/commands/batch.rb +210 -0
- data/lib/etassia/commands/notation_loader.rb +22 -0
- data/lib/etassia/commands/render.rb +98 -0
- data/lib/etassia/commands/types.rb +33 -0
- data/lib/etassia/commands/version.rb +24 -0
- data/lib/etassia/corpus_type_names.rb +52 -0
- data/lib/etassia/diagram/architecture.rb +108 -0
- data/lib/etassia/diagram/base.rb +61 -0
- data/lib/etassia/diagram/block.rb +102 -0
- data/lib/etassia/diagram/c4.rb +337 -0
- data/lib/etassia/diagram/class_diagram.rb +466 -0
- data/lib/etassia/diagram/class_namespace.rb +17 -0
- data/lib/etassia/diagram/class_note.rb +17 -0
- data/lib/etassia/diagram/containment.rb +107 -0
- data/lib/etassia/diagram/er_diagram.rb +317 -0
- data/lib/etassia/diagram/error.rb +38 -0
- data/lib/etassia/diagram/flowchart.rb +419 -0
- data/lib/etassia/diagram/flowchart_label_text.rb +63 -0
- data/lib/etassia/diagram/gantt.rb +94 -0
- data/lib/etassia/diagram/generic_text.rb +30 -0
- data/lib/etassia/diagram/git_graph.rb +57 -0
- data/lib/etassia/diagram/info.rb +38 -0
- data/lib/etassia/diagram/kanban.rb +198 -0
- data/lib/etassia/diagram/mindmap.rb +71 -0
- data/lib/etassia/diagram/packet.rb +93 -0
- data/lib/etassia/diagram/pie.rb +115 -0
- data/lib/etassia/diagram/quadrant.rb +135 -0
- data/lib/etassia/diagram/radar.rb +65 -0
- data/lib/etassia/diagram/requirement.rb +168 -0
- data/lib/etassia/diagram/sankey.rb +217 -0
- data/lib/etassia/diagram/sequence.rb +299 -0
- data/lib/etassia/diagram/sequence_box.rb +18 -0
- data/lib/etassia/diagram/sequence_frame.rb +37 -0
- data/lib/etassia/diagram/sequence_frame_section.rb +23 -0
- data/lib/etassia/diagram/sequence_text.rb +69 -0
- data/lib/etassia/diagram/sequence_wrap.rb +23 -0
- data/lib/etassia/diagram/state_diagram.rb +282 -0
- data/lib/etassia/diagram/timeline.rb +183 -0
- data/lib/etassia/diagram/treemap.rb +95 -0
- data/lib/etassia/diagram/user_journey.rb +153 -0
- data/lib/etassia/diagram/xy_chart.rb +90 -0
- data/lib/etassia/diagram.rb +8 -0
- data/lib/etassia/diagram_registry.rb +34 -0
- data/lib/etassia/engine.rb +334 -0
- data/lib/etassia/error/diagram_type_error.rb +10 -0
- data/lib/etassia/error/layout_error.rb +12 -0
- data/lib/etassia/error/notation_registration_error.rb +10 -0
- data/lib/etassia/error/parse_error.rb +11 -0
- data/lib/etassia/error/pipeline_error.rb +10 -0
- data/lib/etassia/error/render_error.rb +12 -0
- data/lib/etassia/error/score_error.rb +12 -0
- data/lib/etassia/error.rb +10 -0
- data/lib/etassia/error_report.rb +69 -0
- data/lib/etassia/ir/data.rb +39 -0
- data/lib/etassia/ir/data_value.rb +35 -0
- data/lib/etassia/ir/dimension.rb +10 -0
- data/lib/etassia/ir/edge.rb +34 -0
- data/lib/etassia/ir/graph.rb +28 -0
- data/lib/etassia/ir/integrity.rb +80 -0
- data/lib/etassia/ir/item.rb +46 -0
- data/lib/etassia/ir/model.rb +56 -0
- data/lib/etassia/ir/node.rb +10 -0
- data/lib/etassia/ir/placement.rb +47 -0
- data/lib/etassia/ir/prepositioned.rb +25 -0
- data/lib/etassia/ir/prepositioned_item.rb +26 -0
- data/lib/etassia/ir/property_set.rb +43 -0
- data/lib/etassia/ir/scalar.rb +25 -0
- data/lib/etassia/ir/series.rb +10 -0
- data/lib/etassia/ir.rb +17 -0
- data/lib/etassia/js_math.rb +511 -0
- data/lib/etassia/js_number.rb +64 -0
- data/lib/etassia/layout/architecture.rb +1472 -0
- data/lib/etassia/layout/base.rb +276 -0
- data/lib/etassia/layout/block.rb +738 -0
- data/lib/etassia/layout/c4.rb +876 -0
- data/lib/etassia/layout/c4_boundary_metrics.rb +35 -0
- data/lib/etassia/layout/c4_bounds.rb +106 -0
- data/lib/etassia/layout/c4_intersection.rb +77 -0
- data/lib/etassia/layout/c4_placement.rb +135 -0
- data/lib/etassia/layout/c4_shape_metrics.rb +97 -0
- data/lib/etassia/layout/c4_shape_outline.rb +67 -0
- data/lib/etassia/layout/c4_stereotype.rb +30 -0
- data/lib/etassia/layout/c4_text.rb +54 -0
- data/lib/etassia/layout/class_cell_placement.rb +148 -0
- data/lib/etassia/layout/class_diagram.rb +1116 -0
- data/lib/etassia/layout/class_diagram_namespaces.rb +26 -0
- data/lib/etassia/layout/class_diagram_notes.rb +26 -0
- data/lib/etassia/layout/class_name_advances.rb +31 -0
- data/lib/etassia/layout/class_namespace_box.rb +21 -0
- data/lib/etassia/layout/class_namespace_nodes.rb +197 -0
- data/lib/etassia/layout/class_namespace_placement.rb +71 -0
- data/lib/etassia/layout/class_note.rb +44 -0
- data/lib/etassia/layout/class_note_nodes.rb +84 -0
- data/lib/etassia/layout/elk_notices.rb +48 -0
- data/lib/etassia/layout/elk_placement.rb +197 -0
- data/lib/etassia/layout/er_diagram.rb +1002 -0
- data/lib/etassia/layout/er_diagram_adjacency.rb +20 -0
- data/lib/etassia/layout/er_diagram_alignment.rb +72 -0
- data/lib/etassia/layout/er_diagram_compaction.rb +85 -0
- data/lib/etassia/layout/er_diagram_conflicts.rb +70 -0
- data/lib/etassia/layout/er_diagram_coordinates.rb +104 -0
- data/lib/etassia/layout/er_diagram_entity_size.rb +82 -0
- data/lib/etassia/layout/er_diagram_layering.rb +109 -0
- data/lib/etassia/layout/er_diagram_ordering.rb +143 -0
- data/lib/etassia/layout/er_diagram_placement.rb +163 -0
- data/lib/etassia/layout/er_diagram_placement_node.rb +22 -0
- data/lib/etassia/layout/error.rb +133 -0
- data/lib/etassia/layout/flowchart/brandes_koepf.rb +208 -0
- data/lib/etassia/layout/flowchart/cluster_reach.rb +70 -0
- data/lib/etassia/layout/flowchart/component_packing.rb +129 -0
- data/lib/etassia/layout/flowchart/directed_placement.rb +428 -0
- data/lib/etassia/layout/flowchart/edge_chains.rb +111 -0
- data/lib/etassia/layout/flowchart/edge_order.rb +34 -0
- data/lib/etassia/layout/flowchart/edge_router.rb +533 -0
- data/lib/etassia/layout/flowchart/label_backdrop_geometry.rb +62 -0
- data/lib/etassia/layout/flowchart/layer_spacing.rb +85 -0
- data/lib/etassia/layout/flowchart/level_edges.rb +74 -0
- data/lib/etassia/layout/flowchart/level_frame.rb +47 -0
- data/lib/etassia/layout/flowchart/minimum_rank_spacing.rb +74 -0
- data/lib/etassia/layout/flowchart/row_centering.rb +229 -0
- data/lib/etassia/layout/flowchart.rb +1290 -0
- data/lib/etassia/layout/gantt.rb +814 -0
- data/lib/etassia/layout/gantt_duration.rb +49 -0
- data/lib/etassia/layout/gantt_exclusions.rb +74 -0
- data/lib/etassia/layout/gantt_ticks.rb +165 -0
- data/lib/etassia/layout/git_graph.rb +1075 -0
- data/lib/etassia/layout/grid.rb +165 -0
- data/lib/etassia/layout/info.rb +113 -0
- data/lib/etassia/layout/kanban.rb +452 -0
- data/lib/etassia/layout/kanban_card_text.rb +95 -0
- data/lib/etassia/layout/mindmap.rb +425 -0
- data/lib/etassia/layout/mindmap_cose.rb +81 -0
- data/lib/etassia/layout/mindmap_cose_body.rb +93 -0
- data/lib/etassia/layout/mindmap_cose_geometry.rb +190 -0
- data/lib/etassia/layout/mindmap_cose_grid.rb +96 -0
- data/lib/etassia/layout/mindmap_cose_link.rb +42 -0
- data/lib/etassia/layout/mindmap_cose_relaxation.rb +166 -0
- data/lib/etassia/layout/mindmap_cose_seed.rb +93 -0
- data/lib/etassia/layout/mindmap_cose_wedge.rb +37 -0
- data/lib/etassia/layout/mindmap_node_size.rb +130 -0
- data/lib/etassia/layout/packet.rb +285 -0
- data/lib/etassia/layout/pie.rb +305 -0
- data/lib/etassia/layout/pie_legend.rb +64 -0
- data/lib/etassia/layout/pie_legend_entry.rb +21 -0
- data/lib/etassia/layout/quadrant.rb +402 -0
- data/lib/etassia/layout/radar.rb +442 -0
- data/lib/etassia/layout/requirement.rb +765 -0
- data/lib/etassia/layout/sankey.rb +501 -0
- data/lib/etassia/layout/scene.rb +18 -0
- data/lib/etassia/layout/sequence/actor_gaps.rb +151 -0
- data/lib/etassia/layout/sequence/box_shape.rb +23 -0
- data/lib/etassia/layout/sequence/creations.rb +73 -0
- data/lib/etassia/layout/sequence/frame_divider.rb +22 -0
- data/lib/etassia/layout/sequence/frame_placement.rb +424 -0
- data/lib/etassia/layout/sequence/frame_reader.rb +75 -0
- data/lib/etassia/layout/sequence/frame_shape.rb +45 -0
- data/lib/etassia/layout/sequence/geometry.rb +24 -0
- data/lib/etassia/layout/sequence/message_rows.rb +165 -0
- data/lib/etassia/layout/sequence/note.rb +16 -0
- data/lib/etassia/layout/sequence/note_box.rb +114 -0
- data/lib/etassia/layout/sequence/note_placement.rb +223 -0
- data/lib/etassia/layout/sequence/text_width.rb +69 -0
- data/lib/etassia/layout/sequence/text_wrap.rb +93 -0
- data/lib/etassia/layout/sequence.rb +936 -0
- data/lib/etassia/layout/state_diagram.rb +1277 -0
- data/lib/etassia/layout/timeline.rb +165 -0
- data/lib/etassia/layout/timeline_card.rb +142 -0
- data/lib/etassia/layout/timeline_flow.rb +164 -0
- data/lib/etassia/layout/timeline_line.rb +33 -0
- data/lib/etassia/layout/timeline_rounding.rb +19 -0
- data/lib/etassia/layout/timeline_text.rb +105 -0
- data/lib/etassia/layout/treemap.rb +303 -0
- data/lib/etassia/layout/treemap_placement.rb +52 -0
- data/lib/etassia/layout/treemap_squarify.rb +108 -0
- data/lib/etassia/layout/user_journey.rb +300 -0
- data/lib/etassia/layout/user_journey_face.rb +33 -0
- data/lib/etassia/layout/user_journey_face_geometry.rb +80 -0
- data/lib/etassia/layout/user_journey_geometry.rb +336 -0
- data/lib/etassia/layout/user_journey_legend.rb +84 -0
- data/lib/etassia/layout/xy_chart.rb +620 -0
- data/lib/etassia/layout.rb +19 -0
- data/lib/etassia/lint_debt_scoreboard.rb +219 -0
- data/lib/etassia/markdown_text/emphasis_simulator.rb +373 -0
- data/lib/etassia/markdown_text.rb +393 -0
- data/lib/etassia/notation/builtin.rb +39 -0
- data/lib/etassia/notation/entry.rb +15 -0
- data/lib/etassia/notation/mermaid/class_diagram_config.rb +42 -0
- data/lib/etassia/notation/mermaid/git_graph_config.rb +46 -0
- data/lib/etassia/notation/mermaid/ir_adapter.rb +27 -0
- data/lib/etassia/notation/mermaid/ir_adapters/architecture.rb +196 -0
- data/lib/etassia/notation/mermaid/ir_adapters/block.rb +138 -0
- data/lib/etassia/notation/mermaid/ir_adapters/c4.rb +258 -0
- data/lib/etassia/notation/mermaid/ir_adapters/class_diagram.rb +257 -0
- data/lib/etassia/notation/mermaid/ir_adapters/er_diagram.rb +220 -0
- data/lib/etassia/notation/mermaid/ir_adapters/error.rb +32 -0
- data/lib/etassia/notation/mermaid/ir_adapters/flowchart.rb +208 -0
- data/lib/etassia/notation/mermaid/ir_adapters/gantt.rb +236 -0
- data/lib/etassia/notation/mermaid/ir_adapters/git_graph.rb +186 -0
- data/lib/etassia/notation/mermaid/ir_adapters/info.rb +34 -0
- data/lib/etassia/notation/mermaid/ir_adapters/kanban.rb +110 -0
- data/lib/etassia/notation/mermaid/ir_adapters/mindmap.rb +76 -0
- data/lib/etassia/notation/mermaid/ir_adapters/packet.rb +51 -0
- data/lib/etassia/notation/mermaid/ir_adapters/pie.rb +71 -0
- data/lib/etassia/notation/mermaid/ir_adapters/quadrant.rb +116 -0
- data/lib/etassia/notation/mermaid/ir_adapters/radar.rb +142 -0
- data/lib/etassia/notation/mermaid/ir_adapters/requirement.rb +237 -0
- data/lib/etassia/notation/mermaid/ir_adapters/sankey.rb +74 -0
- data/lib/etassia/notation/mermaid/ir_adapters/sequence.rb +304 -0
- data/lib/etassia/notation/mermaid/ir_adapters/state_diagram.rb +225 -0
- data/lib/etassia/notation/mermaid/ir_adapters/timeline.rb +93 -0
- data/lib/etassia/notation/mermaid/ir_adapters/treemap.rb +143 -0
- data/lib/etassia/notation/mermaid/ir_adapters/user_journey.rb +127 -0
- data/lib/etassia/notation/mermaid/ir_adapters/xychart.rb +131 -0
- data/lib/etassia/notation/mermaid.rb +399 -0
- data/lib/etassia/notation/parsed.rb +31 -0
- data/lib/etassia/notation/plantuml/activity/arrow_head.rb +45 -0
- data/lib/etassia/notation/plantuml/activity/arrow_style.rb +53 -0
- data/lib/etassia/notation/plantuml/activity/bar_columns.rb +75 -0
- data/lib/etassia/notation/plantuml/activity/box.rb +60 -0
- data/lib/etassia/notation/plantuml/activity/builder.rb +77 -0
- data/lib/etassia/notation/plantuml/activity/canvas.rb +117 -0
- data/lib/etassia/notation/plantuml/activity/column_centres.rb +151 -0
- data/lib/etassia/notation/plantuml/activity/columns.rb +224 -0
- data/lib/etassia/notation/plantuml/activity/diagram.rb +35 -0
- data/lib/etassia/notation/plantuml/activity/edge.rb +35 -0
- data/lib/etassia/notation/plantuml/activity/edge_marks.rb +68 -0
- data/lib/etassia/notation/plantuml/activity/exits.rb +61 -0
- data/lib/etassia/notation/plantuml/activity/frame_marks.rb +116 -0
- data/lib/etassia/notation/plantuml/activity/lane_steps.rb +82 -0
- data/lib/etassia/notation/plantuml/activity/lane_titles.rb +46 -0
- data/lib/etassia/notation/plantuml/activity/layout.rb +50 -0
- data/lib/etassia/notation/plantuml/activity/legacy_parser.rb +181 -0
- data/lib/etassia/notation/plantuml/activity/legacy_row_gaps.rb +65 -0
- data/lib/etassia/notation/plantuml/activity/mark.rb +23 -0
- data/lib/etassia/notation/plantuml/activity/marks.rb +62 -0
- data/lib/etassia/notation/plantuml/activity/metrics.rb +169 -0
- data/lib/etassia/notation/plantuml/activity/new_parser/blocks.rb +166 -0
- data/lib/etassia/notation/plantuml/activity/new_parser.rb +177 -0
- data/lib/etassia/notation/plantuml/activity/node.rb +50 -0
- data/lib/etassia/notation/plantuml/activity/node_marks.rb +174 -0
- data/lib/etassia/notation/plantuml/activity/note.rb +27 -0
- data/lib/etassia/notation/plantuml/activity/parser.rb +51 -0
- data/lib/etassia/notation/plantuml/activity/placement.rb +327 -0
- data/lib/etassia/notation/plantuml/activity/ranking.rb +142 -0
- data/lib/etassia/notation/plantuml/activity/reach.rb +58 -0
- data/lib/etassia/notation/plantuml/activity/renderer.rb +220 -0
- data/lib/etassia/notation/plantuml/activity/route.rb +22 -0
- data/lib/etassia/notation/plantuml/activity/router.rb +308 -0
- data/lib/etassia/notation/plantuml/activity/row_gaps.rb +105 -0
- data/lib/etassia/notation/plantuml/activity/row_spread.rb +59 -0
- data/lib/etassia/notation/plantuml/activity/scene.rb +20 -0
- data/lib/etassia/notation/plantuml/activity/text.rb +36 -0
- data/lib/etassia/notation/plantuml/activity.rb +50 -0
- data/lib/etassia/notation/plantuml/arrow.rb +135 -0
- data/lib/etassia/notation/plantuml/caption.rb +50 -0
- data/lib/etassia/notation/plantuml/caption_layout.rb +97 -0
- data/lib/etassia/notation/plantuml/caption_rows.rb +67 -0
- data/lib/etassia/notation/plantuml/class_header.rb +178 -0
- data/lib/etassia/notation/plantuml/class_name.rb +51 -0
- data/lib/etassia/notation/plantuml/component.rb +37 -0
- data/lib/etassia/notation/plantuml/creole.rb +278 -0
- data/lib/etassia/notation/plantuml/creole_icons.rb +33 -0
- data/lib/etassia/notation/plantuml/diagram.rb +60 -0
- data/lib/etassia/notation/plantuml/diagram_builder.rb +442 -0
- data/lib/etassia/notation/plantuml/diagram_name.rb +51 -0
- data/lib/etassia/notation/plantuml/directives.rb +33 -0
- data/lib/etassia/notation/plantuml/drawing/box.rb +28 -0
- data/lib/etassia/notation/plantuml/drawing/face.rb +39 -0
- data/lib/etassia/notation/plantuml/drawing/pen.rb +105 -0
- data/lib/etassia/notation/plantuml/drawing/renderer.rb +36 -0
- data/lib/etassia/notation/plantuml/drawing/scene.rb +28 -0
- data/lib/etassia/notation/plantuml/drawing.rb +19 -0
- data/lib/etassia/notation/plantuml/ebnf/blocks.rb +272 -0
- data/lib/etassia/notation/plantuml/ebnf/layout.rb +117 -0
- data/lib/etassia/notation/plantuml/ebnf/model.rb +22 -0
- data/lib/etassia/notation/plantuml/ebnf/reader.rb +131 -0
- data/lib/etassia/notation/plantuml/ebnf.rb +34 -0
- data/lib/etassia/notation/plantuml/end_texts.rb +78 -0
- data/lib/etassia/notation/plantuml/entity.rb +54 -0
- data/lib/etassia/notation/plantuml/further.rb +38 -0
- data/lib/etassia/notation/plantuml/gantt/layout.rb +269 -0
- data/lib/etassia/notation/plantuml/gantt/reader.rb +210 -0
- data/lib/etassia/notation/plantuml/gantt/renderer.rb +81 -0
- data/lib/etassia/notation/plantuml/gantt.rb +34 -0
- data/lib/etassia/notation/plantuml/include/blocks.rb +66 -0
- data/lib/etassia/notation/plantuml/include/finder.rb +61 -0
- data/lib/etassia/notation/plantuml/include.rb +78 -0
- data/lib/etassia/notation/plantuml/ir_adapter/annotations.rb +40 -0
- data/lib/etassia/notation/plantuml/ir_adapter/links.rb +91 -0
- data/lib/etassia/notation/plantuml/ir_adapter/sink.rb +49 -0
- data/lib/etassia/notation/plantuml/ir_adapter/structure.rb +86 -0
- data/lib/etassia/notation/plantuml/ir_adapter.rb +32 -0
- data/lib/etassia/notation/plantuml/ir_reader/annotations.rb +51 -0
- data/lib/etassia/notation/plantuml/ir_reader/index.rb +49 -0
- data/lib/etassia/notation/plantuml/ir_reader/links.rb +133 -0
- data/lib/etassia/notation/plantuml/ir_reader/structure.rb +83 -0
- data/lib/etassia/notation/plantuml/ir_reader.rb +42 -0
- data/lib/etassia/notation/plantuml/junction.rb +20 -0
- data/lib/etassia/notation/plantuml/klass.rb +41 -0
- data/lib/etassia/notation/plantuml/label_clearance.rb +67 -0
- data/lib/etassia/notation/plantuml/layout.rb +2806 -0
- data/lib/etassia/notation/plantuml/member.rb +37 -0
- data/lib/etassia/notation/plantuml/member_icon.rb +87 -0
- data/lib/etassia/notation/plantuml/member_icon_elements.rb +91 -0
- data/lib/etassia/notation/plantuml/member_pitch.rb +76 -0
- data/lib/etassia/notation/plantuml/member_rule.rb +48 -0
- data/lib/etassia/notation/plantuml/network/grid.rb +170 -0
- data/lib/etassia/notation/plantuml/network/layout.rb +158 -0
- data/lib/etassia/notation/plantuml/network/model.rb +22 -0
- data/lib/etassia/notation/plantuml/network/placement.rb +64 -0
- data/lib/etassia/notation/plantuml/network/reader.rb +126 -0
- data/lib/etassia/notation/plantuml/network/shapes.rb +109 -0
- data/lib/etassia/notation/plantuml/network.rb +37 -0
- data/lib/etassia/notation/plantuml/node_graph/archimate.rb +49 -0
- data/lib/etassia/notation/plantuml/node_graph/box_reader.rb +86 -0
- data/lib/etassia/notation/plantuml/node_graph/cloud.rb +73 -0
- data/lib/etassia/notation/plantuml/node_graph/crows_foot.rb +85 -0
- data/lib/etassia/notation/plantuml/node_graph/curve.rb +66 -0
- data/lib/etassia/notation/plantuml/node_graph/data_reader.rb +77 -0
- data/lib/etassia/notation/plantuml/node_graph/data_shape.rb +39 -0
- data/lib/etassia/notation/plantuml/node_graph/detour.rb +98 -0
- data/lib/etassia/notation/plantuml/node_graph/edge.rb +26 -0
- data/lib/etassia/notation/plantuml/node_graph/entity_metrics.rb +49 -0
- data/lib/etassia/notation/plantuml/node_graph/entity_reader.rb +65 -0
- data/lib/etassia/notation/plantuml/node_graph/entity_shape.rb +111 -0
- data/lib/etassia/notation/plantuml/node_graph/figures.rb +92 -0
- data/lib/etassia/notation/plantuml/node_graph/frame_ports.rb +73 -0
- data/lib/etassia/notation/plantuml/node_graph/graph.rb +40 -0
- data/lib/etassia/notation/plantuml/node_graph/heads.rb +89 -0
- data/lib/etassia/notation/plantuml/node_graph/label_fan.rb +76 -0
- data/lib/etassia/notation/plantuml/node_graph/label_placer.rb +310 -0
- data/lib/etassia/notation/plantuml/node_graph/label_waypoint.rb +47 -0
- data/lib/etassia/notation/plantuml/node_graph/layout.rb +293 -0
- data/lib/etassia/notation/plantuml/node_graph/link.rb +78 -0
- data/lib/etassia/notation/plantuml/node_graph/macros.rb +39 -0
- data/lib/etassia/notation/plantuml/node_graph/metrics.rb +113 -0
- data/lib/etassia/notation/plantuml/node_graph/node.rb +44 -0
- data/lib/etassia/notation/plantuml/node_graph/note_tail.rb +58 -0
- data/lib/etassia/notation/plantuml/node_graph/packer.rb +126 -0
- data/lib/etassia/notation/plantuml/node_graph/parser.rb +309 -0
- data/lib/etassia/notation/plantuml/node_graph/paths.rb +48 -0
- data/lib/etassia/notation/plantuml/node_graph/ranking.rb +231 -0
- data/lib/etassia/notation/plantuml/node_graph/reader.rb +53 -0
- data/lib/etassia/notation/plantuml/node_graph/references.rb +38 -0
- data/lib/etassia/notation/plantuml/node_graph/renderer.rb +160 -0
- data/lib/etassia/notation/plantuml/node_graph/scene.rb +22 -0
- data/lib/etassia/notation/plantuml/node_graph/shapes.rb +302 -0
- data/lib/etassia/notation/plantuml/node_graph/skipped_ranks.rb +61 -0
- data/lib/etassia/notation/plantuml/node_graph/tab.rb +68 -0
- data/lib/etassia/notation/plantuml/node_graph/usecase_ellipse.rb +100 -0
- data/lib/etassia/notation/plantuml/node_graph.rb +38 -0
- data/lib/etassia/notation/plantuml/note.rb +27 -0
- data/lib/etassia/notation/plantuml/note_layout.rb +257 -0
- data/lib/etassia/notation/plantuml/note_outline.rb +68 -0
- data/lib/etassia/notation/plantuml/objects/map_rows.rb +46 -0
- data/lib/etassia/notation/plantuml/objects/reader.rb +208 -0
- data/lib/etassia/notation/plantuml/objects.rb +39 -0
- data/lib/etassia/notation/plantuml/package.rb +45 -0
- data/lib/etassia/notation/plantuml/package_color.rb +32 -0
- data/lib/etassia/notation/plantuml/package_frames.rb +178 -0
- data/lib/etassia/notation/plantuml/parser.rb +622 -0
- data/lib/etassia/notation/plantuml/portion_visibility.rb +91 -0
- data/lib/etassia/notation/plantuml/qualified_association_layout.rb +332 -0
- data/lib/etassia/notation/plantuml/qualifier_box.rb +78 -0
- data/lib/etassia/notation/plantuml/rank_chain.rb +527 -0
- data/lib/etassia/notation/plantuml/rank_hierarchy.rb +347 -0
- data/lib/etassia/notation/plantuml/relation.rb +103 -0
- data/lib/etassia/notation/plantuml/relation_label_separator.rb +199 -0
- data/lib/etassia/notation/plantuml/relation_path_points.rb +84 -0
- data/lib/etassia/notation/plantuml/renderer.rb +490 -0
- data/lib/etassia/notation/plantuml/scene.rb +134 -0
- data/lib/etassia/notation/plantuml/segment_box.rb +85 -0
- data/lib/etassia/notation/plantuml/sequence/activation.rb +28 -0
- data/lib/etassia/notation/plantuml/sequence/appearance.rb +71 -0
- data/lib/etassia/notation/plantuml/sequence/arrow_end.rb +23 -0
- data/lib/etassia/notation/plantuml/sequence/arrow_marks.rb +111 -0
- data/lib/etassia/notation/plantuml/sequence/arrow_style.rb +42 -0
- data/lib/etassia/notation/plantuml/sequence/arrow_syntax.rb +122 -0
- data/lib/etassia/notation/plantuml/sequence/bar_tracker.rb +59 -0
- data/lib/etassia/notation/plantuml/sequence/box.rb +23 -0
- data/lib/etassia/notation/plantuml/sequence/chrome.rb +60 -0
- data/lib/etassia/notation/plantuml/sequence/chrome_rows.rb +184 -0
- data/lib/etassia/notation/plantuml/sequence/create.rb +34 -0
- data/lib/etassia/notation/plantuml/sequence/delay.rb +19 -0
- data/lib/etassia/notation/plantuml/sequence/destroy.rb +20 -0
- data/lib/etassia/notation/plantuml/sequence/diagram.rb +86 -0
- data/lib/etassia/notation/plantuml/sequence/divider.rb +19 -0
- data/lib/etassia/notation/plantuml/sequence/edge.rb +53 -0
- data/lib/etassia/notation/plantuml/sequence/embedded.rb +77 -0
- data/lib/etassia/notation/plantuml/sequence/fill.rb +56 -0
- data/lib/etassia/notation/plantuml/sequence/fragment.rb +28 -0
- data/lib/etassia/notation/plantuml/sequence/fragment_shape.rb +138 -0
- data/lib/etassia/notation/plantuml/sequence/head_style.rb +43 -0
- data/lib/etassia/notation/plantuml/sequence/ir_adapter/blocks.rb +73 -0
- data/lib/etassia/notation/plantuml/sequence/ir_adapter/fills.rb +24 -0
- data/lib/etassia/notation/plantuml/sequence/ir_adapter/items.rb +48 -0
- data/lib/etassia/notation/plantuml/sequence/ir_adapter/messages.rb +62 -0
- data/lib/etassia/notation/plantuml/sequence/ir_adapter/participants.rb +42 -0
- data/lib/etassia/notation/plantuml/sequence/ir_adapter/settings.rb +54 -0
- data/lib/etassia/notation/plantuml/sequence/ir_adapter.rb +38 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader/blocks.rb +77 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader/fills.rb +27 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader/index.rb +36 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader/items.rb +35 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader/messages.rb +70 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader/participants.rb +42 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader/settings.rb +74 -0
- data/lib/etassia/notation/plantuml/sequence/ir_reader.rb +38 -0
- data/lib/etassia/notation/plantuml/sequence/keyword_defaults.rb +28 -0
- data/lib/etassia/notation/plantuml/sequence/layout.rb +1567 -0
- data/lib/etassia/notation/plantuml/sequence/message.rb +97 -0
- data/lib/etassia/notation/plantuml/sequence/message_wrap.rb +77 -0
- data/lib/etassia/notation/plantuml/sequence/note.rb +61 -0
- data/lib/etassia/notation/plantuml/sequence/note_fill.rb +35 -0
- data/lib/etassia/notation/plantuml/sequence/note_geometry.rb +126 -0
- data/lib/etassia/notation/plantuml/sequence/note_shape.rb +51 -0
- data/lib/etassia/notation/plantuml/sequence/numbered_label.rb +57 -0
- data/lib/etassia/notation/plantuml/sequence/numbering.rb +49 -0
- data/lib/etassia/notation/plantuml/sequence/outline.rb +204 -0
- data/lib/etassia/notation/plantuml/sequence/page_break.rb +17 -0
- data/lib/etassia/notation/plantuml/sequence/paint.rb +79 -0
- data/lib/etassia/notation/plantuml/sequence/parallel_message.rb +19 -0
- data/lib/etassia/notation/plantuml/sequence/parser.rb +844 -0
- data/lib/etassia/notation/plantuml/sequence/participant.rb +27 -0
- data/lib/etassia/notation/plantuml/sequence/picture.rb +29 -0
- data/lib/etassia/notation/plantuml/sequence/ref.rb +24 -0
- data/lib/etassia/notation/plantuml/sequence/ref_shape.rb +74 -0
- data/lib/etassia/notation/plantuml/sequence/refusals.rb +47 -0
- data/lib/etassia/notation/plantuml/sequence/renderer.rb +501 -0
- data/lib/etassia/notation/plantuml/sequence/scene.rb +200 -0
- data/lib/etassia/notation/plantuml/sequence/skin.rb +76 -0
- data/lib/etassia/notation/plantuml/sequence/spacing.rb +28 -0
- data/lib/etassia/notation/plantuml/sequence/style.rb +151 -0
- data/lib/etassia/notation/plantuml/sequence/tab_size.rb +42 -0
- data/lib/etassia/notation/plantuml/sequence/walker.rb +1190 -0
- data/lib/etassia/notation/plantuml/sequence.rb +47 -0
- data/lib/etassia/notation/plantuml/source_lines.rb +33 -0
- data/lib/etassia/notation/plantuml/state/axis_solver.rb +272 -0
- data/lib/etassia/notation/plantuml/state/cluster_padding.rb +75 -0
- data/lib/etassia/notation/plantuml/state/curve.rb +73 -0
- data/lib/etassia/notation/plantuml/state/dot_axis.rb +248 -0
- data/lib/etassia/notation/plantuml/state/extent.rb +42 -0
- data/lib/etassia/notation/plantuml/state/hierarchy.rb +355 -0
- data/lib/etassia/notation/plantuml/state/label_clearance.rb +148 -0
- data/lib/etassia/notation/plantuml/state/label_reach.rb +139 -0
- data/lib/etassia/notation/plantuml/state/layout.rb +173 -0
- data/lib/etassia/notation/plantuml/state/level_edges.rb +113 -0
- data/lib/etassia/notation/plantuml/state/look.rb +36 -0
- data/lib/etassia/notation/plantuml/state/note_shape.rb +73 -0
- data/lib/etassia/notation/plantuml/state/plane.rb +85 -0
- data/lib/etassia/notation/plantuml/state/ranking.rb +181 -0
- data/lib/etassia/notation/plantuml/state/reader.rb +310 -0
- data/lib/etassia/notation/plantuml/state/renderer.rb +253 -0
- data/lib/etassia/notation/plantuml/state/route_detour.rb +115 -0
- data/lib/etassia/notation/plantuml/state/route_geometry.rb +186 -0
- data/lib/etassia/notation/plantuml/state/routes.rb +196 -0
- data/lib/etassia/notation/plantuml/state/row.rb +88 -0
- data/lib/etassia/notation/plantuml/state/rows.rb +50 -0
- data/lib/etassia/notation/plantuml/state/stretch.rb +71 -0
- data/lib/etassia/notation/plantuml/state.rb +62 -0
- data/lib/etassia/notation/plantuml/stereotype_head.rb +58 -0
- data/lib/etassia/notation/plantuml/style/bound.rb +34 -0
- data/lib/etassia/notation/plantuml/style/colour.rb +41 -0
- data/lib/etassia/notation/plantuml/style/colour_names.rb +89 -0
- data/lib/etassia/notation/plantuml/style/defaults.rb +33 -0
- data/lib/etassia/notation/plantuml/style/inline.rb +56 -0
- data/lib/etassia/notation/plantuml/style/reader.rb +242 -0
- data/lib/etassia/notation/plantuml/style/sheet.rb +103 -0
- data/lib/etassia/notation/plantuml/style.rb +32 -0
- data/lib/etassia/notation/plantuml/style_reader.rb +160 -0
- data/lib/etassia/notation/plantuml/style_sheet.rb +27 -0
- data/lib/etassia/notation/plantuml/styled_relation_curve.rb +71 -0
- data/lib/etassia/notation/plantuml/timing/arrows.rb +81 -0
- data/lib/etassia/notation/plantuml/timing/axis.rb +100 -0
- data/lib/etassia/notation/plantuml/timing/layout.rb +172 -0
- data/lib/etassia/notation/plantuml/timing/model.rb +34 -0
- data/lib/etassia/notation/plantuml/timing/reader.rb +173 -0
- data/lib/etassia/notation/plantuml/timing/signals.rb +328 -0
- data/lib/etassia/notation/plantuml/timing.rb +38 -0
- data/lib/etassia/notation/plantuml/tree/layout.rb +276 -0
- data/lib/etassia/notation/plantuml/tree/mindmap_layout.rb +157 -0
- data/lib/etassia/notation/plantuml/tree/reader.rb +186 -0
- data/lib/etassia/notation/plantuml/tree/renderer.rb +67 -0
- data/lib/etassia/notation/plantuml/tree/role.rb +57 -0
- data/lib/etassia/notation/plantuml/tree/rules.rb +156 -0
- data/lib/etassia/notation/plantuml/tree/wbs_layout.rb +164 -0
- data/lib/etassia/notation/plantuml/tree.rb +41 -0
- data/lib/etassia/notation/plantuml/unsupported_construct_error.rb +39 -0
- data/lib/etassia/notation/plantuml/unsupported_constructs.rb +103 -0
- data/lib/etassia/notation/plantuml/usecase.rb +36 -0
- data/lib/etassia/notation/plantuml.rb +132 -0
- data/lib/etassia/notation/plantuml_data/boxes/frames.rb +101 -0
- data/lib/etassia/notation/plantuml_data/boxes/graph_layout.rb +348 -0
- data/lib/etassia/notation/plantuml_data/boxes/heads.rb +65 -0
- data/lib/etassia/notation/plantuml_data/boxes/highlights.rb +70 -0
- data/lib/etassia/notation/plantuml_data/boxes/inline_rows.rb +99 -0
- data/lib/etassia/notation/plantuml_data/boxes/json_reader.rb +24 -0
- data/lib/etassia/notation/plantuml_data/boxes/link_text.rb +94 -0
- data/lib/etassia/notation/plantuml_data/boxes/marks.rb +76 -0
- data/lib/etassia/notation/plantuml_data/boxes/ornaments.rb +130 -0
- data/lib/etassia/notation/plantuml_data/boxes/renderer.rb +95 -0
- data/lib/etassia/notation/plantuml_data/boxes/row_alignment.rb +58 -0
- data/lib/etassia/notation/plantuml_data/boxes/row_bend.rb +43 -0
- data/lib/etassia/notation/plantuml_data/boxes/scene.rb +27 -0
- data/lib/etassia/notation/plantuml_data/boxes/table.rb +56 -0
- data/lib/etassia/notation/plantuml_data/boxes/table_box.rb +355 -0
- data/lib/etassia/notation/plantuml_data/boxes/text.rb +101 -0
- data/lib/etassia/notation/plantuml_data/boxes/tree_builder.rb +82 -0
- data/lib/etassia/notation/plantuml_data/boxes/tree_layout.rb +82 -0
- data/lib/etassia/notation/plantuml_data/boxes/yaml_reader.rb +105 -0
- data/lib/etassia/notation/plantuml_data/openiconic.LICENSE +6 -0
- data/lib/etassia/notation/plantuml_data/openiconic.json +1 -0
- data/lib/etassia/notation/plantuml_data.rb +84 -0
- data/lib/etassia/notation/plantuml_salt/block.rb +80 -0
- data/lib/etassia/notation/plantuml_salt/box.rb +30 -0
- data/lib/etassia/notation/plantuml_salt/font_metrics.rb +28 -0
- data/lib/etassia/notation/plantuml_salt/grid_marks.rb +66 -0
- data/lib/etassia/notation/plantuml_salt/group_marks.rb +77 -0
- data/lib/etassia/notation/plantuml_salt/layout.rb +206 -0
- data/lib/etassia/notation/plantuml_salt/mark.rb +36 -0
- data/lib/etassia/notation/plantuml_salt/markup.rb +57 -0
- data/lib/etassia/notation/plantuml_salt/parser.rb +239 -0
- data/lib/etassia/notation/plantuml_salt/renderer.rb +138 -0
- data/lib/etassia/notation/plantuml_salt/row.rb +19 -0
- data/lib/etassia/notation/plantuml_salt/run.rb +34 -0
- data/lib/etassia/notation/plantuml_salt/scene.rb +23 -0
- data/lib/etassia/notation/plantuml_salt/scroll_marks.rb +94 -0
- data/lib/etassia/notation/plantuml_salt/tab_marks.rb +74 -0
- data/lib/etassia/notation/plantuml_salt/tree_marks.rb +127 -0
- data/lib/etassia/notation/plantuml_salt/widget.rb +40 -0
- data/lib/etassia/notation/plantuml_salt/widget_marks.rb +319 -0
- data/lib/etassia/notation/plantuml_salt.rb +66 -0
- data/lib/etassia/notation/plugin_failure.rb +42 -0
- data/lib/etassia/notation.rb +289 -0
- data/lib/etassia/parser/architecture.rb +25 -0
- data/lib/etassia/parser/atoms/delimited_run.rb +82 -0
- data/lib/etassia/parser/atoms/greedy_run.rb +116 -0
- data/lib/etassia/parser/atoms/joined.rb +47 -0
- data/lib/etassia/parser/atoms/trimmed_run.rb +47 -0
- data/lib/etassia/parser/base.rb +258 -0
- data/lib/etassia/parser/block.rb +32 -0
- data/lib/etassia/parser/builders/architecture.rb +135 -0
- data/lib/etassia/parser/builders/block.rb +288 -0
- data/lib/etassia/parser/builders/c4.rb +275 -0
- data/lib/etassia/parser/builders/capture_string.rb +17 -0
- data/lib/etassia/parser/builders/class_diagram.rb +539 -0
- data/lib/etassia/parser/builders/er_diagram.rb +308 -0
- data/lib/etassia/parser/builders/error.rb +51 -0
- data/lib/etassia/parser/builders/flowchart.rb +1150 -0
- data/lib/etassia/parser/builders/gantt.rb +267 -0
- data/lib/etassia/parser/builders/git_graph.rb +301 -0
- data/lib/etassia/parser/builders/info.rb +57 -0
- data/lib/etassia/parser/builders/kanban.rb +411 -0
- data/lib/etassia/parser/builders/mindmap.rb +271 -0
- data/lib/etassia/parser/builders/packet.rb +85 -0
- data/lib/etassia/parser/builders/pie.rb +105 -0
- data/lib/etassia/parser/builders/quadrant.rb +165 -0
- data/lib/etassia/parser/builders/radar.rb +157 -0
- data/lib/etassia/parser/builders/requirement.rb +230 -0
- data/lib/etassia/parser/builders/sankey.rb +142 -0
- data/lib/etassia/parser/builders/sequence.rb +431 -0
- data/lib/etassia/parser/builders/sequence_blocks.rb +117 -0
- data/lib/etassia/parser/builders/sequence_box_title.rb +57 -0
- data/lib/etassia/parser/builders/state_diagram.rb +364 -0
- data/lib/etassia/parser/builders/timeline.rb +133 -0
- data/lib/etassia/parser/builders/treemap.rb +138 -0
- data/lib/etassia/parser/builders/xy_chart.rb +148 -0
- data/lib/etassia/parser/c4.rb +37 -0
- data/lib/etassia/parser/class_diagram.rb +58 -0
- data/lib/etassia/parser/class_namespaces.rb +62 -0
- data/lib/etassia/parser/class_notes.rb +54 -0
- data/lib/etassia/parser/er_diagram.rb +28 -0
- data/lib/etassia/parser/error.rb +30 -0
- data/lib/etassia/parser/flowchart.rb +87 -0
- data/lib/etassia/parser/gantt.rb +43 -0
- data/lib/etassia/parser/git_graph.rb +106 -0
- data/lib/etassia/parser/grammars/architecture.rb +165 -0
- data/lib/etassia/parser/grammars/block.rb +190 -0
- data/lib/etassia/parser/grammars/c4.rb +229 -0
- data/lib/etassia/parser/grammars/class_diagram.rb +606 -0
- data/lib/etassia/parser/grammars/common.rb +107 -0
- data/lib/etassia/parser/grammars/er_diagram.rb +232 -0
- data/lib/etassia/parser/grammars/error.rb +32 -0
- data/lib/etassia/parser/grammars/flowchart.rb +1722 -0
- data/lib/etassia/parser/grammars/gantt.rb +198 -0
- data/lib/etassia/parser/grammars/git_graph.rb +234 -0
- data/lib/etassia/parser/grammars/info.rb +53 -0
- data/lib/etassia/parser/grammars/kanban.rb +417 -0
- data/lib/etassia/parser/grammars/mermaid_unicode_text.rb +108 -0
- data/lib/etassia/parser/grammars/mindmap.rb +220 -0
- data/lib/etassia/parser/grammars/packet.rb +71 -0
- data/lib/etassia/parser/grammars/pie.rb +128 -0
- data/lib/etassia/parser/grammars/quadrant.rb +202 -0
- data/lib/etassia/parser/grammars/radar.rb +158 -0
- data/lib/etassia/parser/grammars/requirement.rb +258 -0
- data/lib/etassia/parser/grammars/sankey.rb +104 -0
- data/lib/etassia/parser/grammars/sequence.rb +940 -0
- data/lib/etassia/parser/grammars/state_diagram.rb +382 -0
- data/lib/etassia/parser/grammars/timeline.rb +153 -0
- data/lib/etassia/parser/grammars/treemap.rb +122 -0
- data/lib/etassia/parser/grammars/xy_chart.rb +119 -0
- data/lib/etassia/parser/info.rb +32 -0
- data/lib/etassia/parser/kanban.rb +131 -0
- data/lib/etassia/parser/mermaid_shapes.rb +106 -0
- data/lib/etassia/parser/metadata_yaml.rb +452 -0
- data/lib/etassia/parser/mindmap.rb +92 -0
- data/lib/etassia/parser/native/builders/architecture.rb +103 -0
- data/lib/etassia/parser/native/builders/block.rb +68 -0
- data/lib/etassia/parser/native/builders/c4.rb +16 -0
- data/lib/etassia/parser/native/builders/class_diagram.rb +63 -0
- data/lib/etassia/parser/native/builders/er_diagram.rb +47 -0
- data/lib/etassia/parser/native/builders/flowchart.rb +58 -0
- data/lib/etassia/parser/native/builders/gantt.rb +35 -0
- data/lib/etassia/parser/native/builders/gitgraph.rb +35 -0
- data/lib/etassia/parser/native/builders/info.rb +30 -0
- data/lib/etassia/parser/native/builders/kanban.rb +37 -0
- data/lib/etassia/parser/native/builders/mindmap.rb +26 -0
- data/lib/etassia/parser/native/builders/packet.rb +63 -0
- data/lib/etassia/parser/native/builders/pie.rb +82 -0
- data/lib/etassia/parser/native/builders/quadrant.rb +27 -0
- data/lib/etassia/parser/native/builders/radar.rb +84 -0
- data/lib/etassia/parser/native/builders/requirement.rb +25 -0
- data/lib/etassia/parser/native/builders/sankey.rb +18 -0
- data/lib/etassia/parser/native/builders/sequence.rb +48 -0
- data/lib/etassia/parser/native/builders/state_diagram.rb +61 -0
- data/lib/etassia/parser/native/builders/timeline.rb +46 -0
- data/lib/etassia/parser/native/builders/treemap.rb +60 -0
- data/lib/etassia/parser/native/builders/user_journey.rb +83 -0
- data/lib/etassia/parser/native/builders/xy_chart.rb +94 -0
- data/lib/etassia/parser/native/declined.rb +12 -0
- data/lib/etassia/parser/native/mermaid.json +1 -0
- data/lib/etassia/parser/native/text.rb +31 -0
- data/lib/etassia/parser/native.rb +99 -0
- data/lib/etassia/parser/packet.rb +56 -0
- data/lib/etassia/parser/pie.rb +32 -0
- data/lib/etassia/parser/quadrant.rb +77 -0
- data/lib/etassia/parser/radar.rb +99 -0
- data/lib/etassia/parser/requirement.rb +32 -0
- data/lib/etassia/parser/sankey.rb +47 -0
- data/lib/etassia/parser/sequence.rb +34 -0
- data/lib/etassia/parser/state_diagram.rb +29 -0
- data/lib/etassia/parser/timeline.rb +40 -0
- data/lib/etassia/parser/treemap.rb +25 -0
- data/lib/etassia/parser/user_journey.rb +253 -0
- data/lib/etassia/parser/xy_chart.rb +98 -0
- data/lib/etassia/parser.rb +18 -0
- data/lib/etassia/renderer/architecture.rb +232 -0
- data/lib/etassia/renderer/architecture_edge_router.rb +379 -0
- data/lib/etassia/renderer/base.rb +295 -0
- data/lib/etassia/renderer/block.rb +157 -0
- data/lib/etassia/renderer/c4.rb +294 -0
- data/lib/etassia/renderer/class_diagram.rb +671 -0
- data/lib/etassia/renderer/er_diagram.rb +842 -0
- data/lib/etassia/renderer/error.rb +131 -0
- data/lib/etassia/renderer/flowchart.rb +315 -0
- data/lib/etassia/renderer/gantt.rb +177 -0
- data/lib/etassia/renderer/git_graph.rb +224 -0
- data/lib/etassia/renderer/info.rb +59 -0
- data/lib/etassia/renderer/kanban.rb +157 -0
- data/lib/etassia/renderer/line_break_text.rb +56 -0
- data/lib/etassia/renderer/markdown_text.rb +106 -0
- data/lib/etassia/renderer/mindmap.rb +290 -0
- data/lib/etassia/renderer/packet.rb +87 -0
- data/lib/etassia/renderer/pie.rb +113 -0
- data/lib/etassia/renderer/quadrant.rb +189 -0
- data/lib/etassia/renderer/radar.rb +191 -0
- data/lib/etassia/renderer/requirement.rb +209 -0
- data/lib/etassia/renderer/sankey.rb +114 -0
- data/lib/etassia/renderer/sequence/frame_drawing.rb +169 -0
- data/lib/etassia/renderer/sequence/note_drawing.rb +69 -0
- data/lib/etassia/renderer/sequence.rb +490 -0
- data/lib/etassia/renderer/state_diagram.rb +202 -0
- data/lib/etassia/renderer/timeline.rb +134 -0
- data/lib/etassia/renderer/timeline_palette.rb +18 -0
- data/lib/etassia/renderer/treemap/section_total.rb +33 -0
- data/lib/etassia/renderer/treemap.rb +128 -0
- data/lib/etassia/renderer/user_journey.rb +209 -0
- data/lib/etassia/renderer/xy_chart.rb +180 -0
- data/lib/etassia/renderer.rb +22 -0
- data/lib/etassia/runtime.rb +125 -0
- data/lib/etassia/source/frontmatter.rb +501 -0
- data/lib/etassia/source.rb +224 -0
- data/lib/etassia/svg/arrowhead.rb +205 -0
- data/lib/etassia/svg/circle.rb +19 -0
- data/lib/etassia/svg/document.rb +106 -0
- data/lib/etassia/svg/element.rb +209 -0
- data/lib/etassia/svg/ellipse.rb +23 -0
- data/lib/etassia/svg/escaping.rb +149 -0
- data/lib/etassia/svg/group.rb +64 -0
- data/lib/etassia/svg/line.rb +21 -0
- data/lib/etassia/svg/numbers.rb +53 -0
- data/lib/etassia/svg/path.rb +67 -0
- data/lib/etassia/svg/path_geometry.rb +378 -0
- data/lib/etassia/svg/polygon.rb +25 -0
- data/lib/etassia/svg/polyline.rb +28 -0
- data/lib/etassia/svg/rect.rb +34 -0
- data/lib/etassia/svg/style.rb +50 -0
- data/lib/etassia/svg/text.rb +181 -0
- data/lib/etassia/svg/tspan.rb +48 -0
- data/lib/etassia/svg.rb +38 -0
- data/lib/etassia/text_measurement/arial_advances.rb +122 -0
- data/lib/etassia/text_measurement/dejavu_advances.rb +111 -0
- data/lib/etassia/text_measurement/times_advances.rb +64 -0
- data/lib/etassia/text_measurement/trebuchet_advances.rb +53 -0
- data/lib/etassia/text_measurement/trebuchet_kerning.rb +29 -0
- data/lib/etassia/text_measurement.rb +198 -0
- data/lib/etassia/theme/builtin/dark.yml +229 -0
- data/lib/etassia/theme/builtin/default.yml +239 -0
- data/lib/etassia/theme/builtin/high_contrast.yml +231 -0
- data/lib/etassia/theme/builtin/light.yml +229 -0
- data/lib/etassia/theme/color_palette.rb +97 -0
- data/lib/etassia/theme/completion.rb +50 -0
- data/lib/etassia/theme/effect_styles.rb +34 -0
- data/lib/etassia/theme/registry.rb +45 -0
- data/lib/etassia/theme/shape_styles.rb +34 -0
- data/lib/etassia/theme/spacing_config.rb +30 -0
- data/lib/etassia/theme/typography.rb +40 -0
- data/lib/etassia/theme.rb +73 -0
- data/lib/etassia/version.rb +5 -0
- data/lib/etassia.rb +40 -0
- metadata +865 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../js_math"
|
|
4
|
+
|
|
5
|
+
module Etassia
|
|
6
|
+
module Layout
|
|
7
|
+
# Works out where an edge's line runs between two boxes.
|
|
8
|
+
#
|
|
9
|
+
# This is geometry only: it takes two laid-out boxes and gives back
|
|
10
|
+
# the points the line passes through. Nothing here knows about SVG,
|
|
11
|
+
# and Flowchart turns the points into a path.
|
|
12
|
+
#
|
|
13
|
+
# The maths exists because of clusters. A node is painted OVER the
|
|
14
|
+
# line, so a line drawn from centre to centre looks right without
|
|
15
|
+
# any trimming. A cluster is painted BEHIND the line, so the stretch
|
|
16
|
+
# from its centre out to its border stays visible across the inside
|
|
17
|
+
# of the box. Every rule below is about keeping that stretch off the
|
|
18
|
+
# page while the line still touches the box it belongs to.
|
|
19
|
+
#
|
|
20
|
+
# One point shape throughout: `{ x: <number>, y: <number> }`, which
|
|
21
|
+
# is the shape ELK bend points already have. `ends_of` is the single
|
|
22
|
+
# place it becomes anything else.
|
|
23
|
+
#
|
|
24
|
+
# @example Route an edge
|
|
25
|
+
# points, label_ends = FlowchartEdgeRouter.new.route(
|
|
26
|
+
# source, target, bends
|
|
27
|
+
# )
|
|
28
|
+
class FlowchartEdgeRouter
|
|
29
|
+
# Rounded the way mermaid paints a cluster. The Scene carries the same
|
|
30
|
+
# radius, and an endpoint on a corner has to land on it.
|
|
31
|
+
CLUSTER_CORNER = 5
|
|
32
|
+
|
|
33
|
+
# Two boxes that touch add up to exactly one run between their
|
|
34
|
+
# centres, and the arithmetic lands a hair under it. Without the
|
|
35
|
+
# slack they get trimmed to the same point and the edge is drawn
|
|
36
|
+
# zero units long.
|
|
37
|
+
TOUCHING = 1e-9
|
|
38
|
+
|
|
39
|
+
# Routes are written to two decimal places after trimming. Centres
|
|
40
|
+
# less than one output unit apart cannot provide a useful straight run.
|
|
41
|
+
COINCIDENT = 0.01
|
|
42
|
+
|
|
43
|
+
# Trimmed endpoints are written to two decimals, so a coordinate can
|
|
44
|
+
# sit half a hundredth away from the unrounded side it reached.
|
|
45
|
+
ROUTE_EPSILON = 0.01
|
|
46
|
+
|
|
47
|
+
# How far a degenerate loop reaches past the borders it leaves.
|
|
48
|
+
# Flowchart's page sizing adds 40 past the drawn maxima, and the
|
|
49
|
+
# Scene framing keeps the origin at zero and adds another 40
|
|
50
|
+
# extents, leaving 80 units there. A reach beyond that needs the
|
|
51
|
+
# page to grow with it.
|
|
52
|
+
LOOP_REACH = 20
|
|
53
|
+
|
|
54
|
+
# The marker Layout::Flowchart puts on a subgraph.
|
|
55
|
+
# Layout::Grid#cluster? makes the same test: change one and
|
|
56
|
+
# change the others, or the layers stop agreeing about what a box
|
|
57
|
+
# is.
|
|
58
|
+
#
|
|
59
|
+
# @param box [Hash] a laid-out box
|
|
60
|
+
# @return [Boolean] true when the box is a cluster
|
|
61
|
+
def self.cluster?(box)
|
|
62
|
+
box.dig(:metadata, :cluster) == true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# The exact centre of a laid-out box. Flowchart aims routes, heads and
|
|
66
|
+
# outlines at this same point, so keep one copy: two drifted
|
|
67
|
+
# apart once and put routed ends half a pixel off their heads.
|
|
68
|
+
#
|
|
69
|
+
# @param box [Hash] a laid-out box
|
|
70
|
+
# @return [Hash] :x and :y of the centre
|
|
71
|
+
def self.centre(box)
|
|
72
|
+
{ x: (box[:x] || 0) + ((box[:width] || 100) / 2.0),
|
|
73
|
+
y: (box[:y] || 0) + ((box[:height] || 50) / 2.0) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# The two ends of a run, flattened for the path and label builders.
|
|
77
|
+
#
|
|
78
|
+
# @param points [Array<Hash>] the points of a run
|
|
79
|
+
# @return [Array<Numeric>] source x, source y, target x, target y
|
|
80
|
+
def self.ends_of(points)
|
|
81
|
+
first = points.first
|
|
82
|
+
last = points.last
|
|
83
|
+
[first[:x], first[:y], last[:x], last[:y]]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Routes one edge.
|
|
87
|
+
#
|
|
88
|
+
# @param source [Hash] the laid-out box the edge leaves
|
|
89
|
+
# @param target [Hash] the laid-out box the edge reaches
|
|
90
|
+
# @param bends [Array<Hash>, nil] bend points the layout supplied
|
|
91
|
+
# @return [Array(Array<Hash>, Array<Numeric>)] the points of the
|
|
92
|
+
# run, and the ends of the stretch the label sits on
|
|
93
|
+
def route(source, target, bends = nil)
|
|
94
|
+
points = initial_route(source, target)
|
|
95
|
+
clamp_route(points, source, target, bends)
|
|
96
|
+
label_points = points.length > 2 ? points.slice(1, 2) : points
|
|
97
|
+
[points, self.class.ends_of(label_points)]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def initial_route(source, target)
|
|
103
|
+
points = coincident_loop(source, target) unless
|
|
104
|
+
exposed_cluster_overlap?(source, target)
|
|
105
|
+
points || trimmed_route(source, target)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def clamp_route(points, source, target, bends)
|
|
109
|
+
source_neighbour = bends&.first || points[1]
|
|
110
|
+
target_neighbour = bends&.last || points[-2]
|
|
111
|
+
points[0] = clamp_cluster_corner(points[0], source_neighbour, source)
|
|
112
|
+
points[-1] = clamp_cluster_corner(points[-1], target_neighbour, target)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def cluster?(box)
|
|
116
|
+
self.class.cluster?(box)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Nodes and unmarked containers stay unchanged. A cluster is painted
|
|
120
|
+
# with integer rectangle coordinates, so its endpoint is moved from the
|
|
121
|
+
# layout box onto that exact rounded outline.
|
|
122
|
+
def clamp_cluster_corner(point, neighbour, box)
|
|
123
|
+
return point unless cluster?(box)
|
|
124
|
+
|
|
125
|
+
x_side, y_side = cluster_sides(point, box)
|
|
126
|
+
return point unless x_side || y_side
|
|
127
|
+
|
|
128
|
+
outline = painted_cluster_outline(box)
|
|
129
|
+
return point if outline.values_at(:rx, :ry).any?(&:zero?)
|
|
130
|
+
|
|
131
|
+
painted_cluster_contact(point, neighbour, outline, x_side, y_side)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def painted_cluster_contact(point, neighbour, outline, x_side, y_side)
|
|
135
|
+
if x_side && y_side
|
|
136
|
+
return rounded_corner(neighbour, outline, x_side, y_side)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
clamp_cluster_side(point, outline, x_side, y_side)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def cluster_sides(point, box)
|
|
143
|
+
[edge_side(point[:x], [box[:x] || 0, right_of(box)]),
|
|
144
|
+
edge_side(point[:y], [box[:y] || 0, bottom_of(box)])]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def edge_side(coordinate, edges)
|
|
148
|
+
side = edges.each_index.min_by do |index|
|
|
149
|
+
(coordinate - edges[index]).abs
|
|
150
|
+
end
|
|
151
|
+
side if (coordinate - edges[side]).abs <= ROUTE_EPSILON
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def painted_cluster_outline(box)
|
|
155
|
+
x, y, width, height = painted_cluster_dimensions(box)
|
|
156
|
+
{ xs: [x, x + width], ys: [y, y + height],
|
|
157
|
+
rx: [CLUSTER_CORNER, width / 2.0].min,
|
|
158
|
+
ry: [CLUSTER_CORNER, height / 2.0].min }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def painted_cluster_dimensions(box)
|
|
162
|
+
%i[x y width height].map { |key| (box[key] || 0).to_i.to_f }
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def clamp_cluster_side(point, outline, x_side, y_side)
|
|
166
|
+
if x_side
|
|
167
|
+
clamp_vertical_side(point, outline, x_side)
|
|
168
|
+
else
|
|
169
|
+
clamp_horizontal_side(point, outline, y_side)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def clamp_vertical_side(point, outline, side)
|
|
174
|
+
xs, ys, ry = outline.values_at(:xs, :ys, :ry)
|
|
175
|
+
point.merge(x: xs[side],
|
|
176
|
+
y: point[:y].clamp(ys.first + ry, ys.last - ry))
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def clamp_horizontal_side(point, outline, side)
|
|
180
|
+
xs, ys, rx = outline.values_at(:xs, :ys, :rx)
|
|
181
|
+
point.merge(x: point[:x].clamp(xs.first + rx, xs.last - rx),
|
|
182
|
+
y: ys[side])
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def rounded_corner(neighbour, outline, x_side, y_side)
|
|
186
|
+
corner = corner_point(outline, x_side, y_side)
|
|
187
|
+
centre = corner_centre(outline, x_side, y_side)
|
|
188
|
+
direction = { x: corner[:x] - neighbour[:x],
|
|
189
|
+
y: corner[:y] - neighbour[:y] }
|
|
190
|
+
step = ellipse_entry(
|
|
191
|
+
corner, centre, direction, outline[:rx], outline[:ry]
|
|
192
|
+
)
|
|
193
|
+
step ? corner_at_step(corner, direction, step) : corner
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def corner_point(outline, x_side, y_side)
|
|
197
|
+
xs, ys = outline.values_at(:xs, :ys)
|
|
198
|
+
{ x: xs[x_side], y: ys[y_side] }
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def corner_centre(outline, x_side, y_side)
|
|
202
|
+
xs, ys, rx, ry = outline.values_at(:xs, :ys, :rx, :ry)
|
|
203
|
+
{ x: x_side.zero? ? xs.first + rx : xs.last - rx,
|
|
204
|
+
y: y_side.zero? ? ys.first + ry : ys.last - ry }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def corner_at_step(corner, direction, step)
|
|
208
|
+
corner.merge(x: (corner[:x] + (direction[:x] * step)).round(2),
|
|
209
|
+
y: (corner[:y] + (direction[:y] * step)).round(2))
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def ellipse_entry(corner, centre, direction, radius_x, radius_y)
|
|
213
|
+
x, y = normalized_corner(corner, centre, radius_x, radius_y)
|
|
214
|
+
dx, dy = ellipse_direction(direction, radius_x, radius_y)
|
|
215
|
+
a = (dx**2) + (dy**2)
|
|
216
|
+
return nil if a.zero?
|
|
217
|
+
|
|
218
|
+
b = 2 * ((x * dx) + (y * dy))
|
|
219
|
+
smallest_quadratic_root(a, b)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def normalized_corner(corner, centre, radius_x, radius_y)
|
|
223
|
+
[(corner[:x] - centre[:x]) / radius_x,
|
|
224
|
+
(corner[:y] - centre[:y]) / radius_y]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def ellipse_direction(direction, radius_x, radius_y)
|
|
228
|
+
[direction[:x] / radius_x, direction[:y] / radius_y]
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def smallest_quadratic_root(quad_coeff, linear_coeff)
|
|
232
|
+
discriminant = (linear_coeff**2) - (4 * quad_coeff)
|
|
233
|
+
return nil if discriminant.negative?
|
|
234
|
+
|
|
235
|
+
quadratic_roots(quad_coeff, linear_coeff, discriminant)
|
|
236
|
+
.select { |value| value >= 0 }.min
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def quadratic_roots(quad_coeff, linear_coeff, discriminant)
|
|
240
|
+
[(-linear_coeff - Math.sqrt(discriminant)) / (2 * quad_coeff),
|
|
241
|
+
(-linear_coeff + Math.sqrt(discriminant)) / (2 * quad_coeff)]
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# A straight run needs a direction. Self-edges and distinct boxes
|
|
245
|
+
# with the same centre have none, so route from the source's top to
|
|
246
|
+
# the target's bottom with two bends beyond both boxes.
|
|
247
|
+
def coincident_loop(source, target)
|
|
248
|
+
source_centre = FlowchartEdgeRouter.centre(source)
|
|
249
|
+
target_centre = FlowchartEdgeRouter.centre(target)
|
|
250
|
+
return nil unless near?(source_centre, target_centre)
|
|
251
|
+
|
|
252
|
+
coincident_loop_points(source, target, source_centre, target_centre)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def coincident_loop_points(source, target, source_centre, target_centre)
|
|
256
|
+
start, finish = loop_endpoints(
|
|
257
|
+
source, target, source_centre, target_centre
|
|
258
|
+
)
|
|
259
|
+
out = [right_of(source), right_of(target)].max + LOOP_REACH
|
|
260
|
+
bend_y = finish[:y]
|
|
261
|
+
bend_y += LOOP_REACH if near?(start, finish)
|
|
262
|
+
rounded_points([start, { x: out, y: start[:y] },
|
|
263
|
+
{ x: out, y: bend_y }, finish])
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def loop_endpoints(source, target, source_centre, target_centre)
|
|
267
|
+
source_top, = vertical_outline(source, source_centre[:y])
|
|
268
|
+
_, target_bottom = vertical_outline(target, target_centre[:y])
|
|
269
|
+
[{ x: source_centre[:x], y: source_top },
|
|
270
|
+
{ x: target_centre[:x], y: target_bottom }]
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def near?(first, second)
|
|
274
|
+
JsMath.hypot(first[:x] - second[:x],
|
|
275
|
+
first[:y] - second[:y]) < COINCIDENT
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def right_of(box)
|
|
279
|
+
(box[:x] || 0) + (box[:width] || 0)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def bottom_of(box)
|
|
283
|
+
(box[:y] || 0) + (box[:height] || 0)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Circle nodes use the smaller box dimension as their radius, so
|
|
287
|
+
# their vertical outline may sit inside the box bounds.
|
|
288
|
+
def vertical_outline(box, centre_y)
|
|
289
|
+
y = box[:y] || 0
|
|
290
|
+
height = box[:height] || 0
|
|
291
|
+
shape = box.dig(:metadata, :shape)
|
|
292
|
+
return [y, y + height] unless %w[circle double_circle].include?(shape)
|
|
293
|
+
|
|
294
|
+
radius = [box[:width] || 0, height].min / 2
|
|
295
|
+
[centre_y - radius, centre_y + radius]
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# The points of the ordinary run: from centre to centre, with each
|
|
299
|
+
# end pulled back to the border of the box it leaves when that box
|
|
300
|
+
# is a cluster.
|
|
301
|
+
def trimmed_route(source, target)
|
|
302
|
+
from = FlowchartEdgeRouter.centre(source)
|
|
303
|
+
to = FlowchartEdgeRouter.centre(target)
|
|
304
|
+
out = step_out(source, from, to)
|
|
305
|
+
back = step_out(target, to, from)
|
|
306
|
+
trimmed_points(source, target, from, to, [out, back])
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def trimmed_points(source, target, from, to, steps)
|
|
310
|
+
if exposed_cluster_overlap?(source, target)
|
|
311
|
+
return exterior_route(source, target, from, to)
|
|
312
|
+
end
|
|
313
|
+
if enclosing_steps?(steps)
|
|
314
|
+
return enclosing_route(source, target, from, to)
|
|
315
|
+
end
|
|
316
|
+
if overlapping_steps?(steps)
|
|
317
|
+
return exterior_route(source, target, from, to)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
clipped_route(source, target, from, to, steps)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def enclosing_steps?(steps)
|
|
324
|
+
out, back = steps
|
|
325
|
+
out >= 1.0 - TOUCHING || back >= 1.0 - TOUCHING
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def overlapping_steps?(steps)
|
|
329
|
+
out, back = steps
|
|
330
|
+
out + back >= 1.0 - TOUCHING
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def clipped_route(source, target, from, to, steps)
|
|
334
|
+
out, back = steps
|
|
335
|
+
route = [along(from, to, out), along(to, from, back)]
|
|
336
|
+
if route.first == route.last
|
|
337
|
+
return exterior_route(source, target, from, to)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
route
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def exposed_cluster_overlap?(source, target)
|
|
344
|
+
return false unless cluster?(source) && cluster?(target)
|
|
345
|
+
if contains_box?(source, target) || contains_box?(target, source)
|
|
346
|
+
return false
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
overlapping_x?(source, target) && overlapping_y?(source, target)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def overlapping_x?(source, target)
|
|
353
|
+
(source[:x] || 0) <= right_of(target) &&
|
|
354
|
+
right_of(source) >= (target[:x] || 0)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def overlapping_y?(source, target)
|
|
358
|
+
(source[:y] || 0) <= bottom_of(target) &&
|
|
359
|
+
bottom_of(source) >= (target[:y] || 0)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def contains_box?(outer, inner)
|
|
363
|
+
contains_x?(outer, inner) && contains_y?(outer, inner)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def contains_x?(outer, inner)
|
|
367
|
+
(outer[:x] || 0) <= (inner[:x] || 0) &&
|
|
368
|
+
right_of(outer) >= right_of(inner)
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def contains_y?(outer, inner)
|
|
372
|
+
(outer[:y] || 0) <= (inner[:y] || 0) &&
|
|
373
|
+
bottom_of(outer) >= bottom_of(inner)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def enclosing_route(source, target, from, to)
|
|
377
|
+
source_back = step_out(source, from, mirror(from, to))
|
|
378
|
+
target_back = step_out(target, to, mirror(to, from))
|
|
379
|
+
|
|
380
|
+
[along(from, to, -source_back), along(to, from, -target_back)]
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# The point as far the other side of `from` as `to` is this side.
|
|
384
|
+
def mirror(from, to)
|
|
385
|
+
{ x: (2 * from[:x]) - to[:x], y: (2 * from[:y]) - to[:y] }
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# Corners on the sides facing away from the other box expose both
|
|
389
|
+
# ends. Prefer the corridor matching the centre direction, but unequal
|
|
390
|
+
# projections can put that corner inside the other box, so only use a
|
|
391
|
+
# candidate whose actual segments stay outside both open faces.
|
|
392
|
+
def exterior_route(source, target, from, to)
|
|
393
|
+
candidates = exterior_candidates(source, target, from, to)
|
|
394
|
+
rounded = candidates.map { |points| rounded_points(points) }
|
|
395
|
+
clear = rounded.find { |route| clear_route?(route, source, target) }
|
|
396
|
+
clear || rounded.first
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def exterior_candidates(source, target, from, to)
|
|
400
|
+
bottom = bottom_exterior_route(source, target, from, to)
|
|
401
|
+
right = right_exterior_route(source, target, from, to)
|
|
402
|
+
sideways = (to[:x] - from[:x]).abs >= (to[:y] - from[:y]).abs
|
|
403
|
+
sideways ? [bottom, right] : [right, bottom]
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def rounded_points(points)
|
|
407
|
+
points.map do |point|
|
|
408
|
+
{ x: point[:x].round(2), y: point[:y].round(2) }
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def clear_route?(points, source, target)
|
|
413
|
+
points.each_cons(2).none? do |from, to|
|
|
414
|
+
[source, target].any? { |box| crosses_face?(box, from, to) }
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def crosses_face?(box, from, to)
|
|
419
|
+
if from[:x] == to[:x]
|
|
420
|
+
crosses_vertical_face?(box, from, to)
|
|
421
|
+
else
|
|
422
|
+
crosses_horizontal_face?(box, from, to)
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def crosses_vertical_face?(box, from, to)
|
|
427
|
+
low, high = [from[:y], to[:y]].minmax
|
|
428
|
+
inside_x?(box, from[:x]) && overlaps_y_face?(box, low, high)
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def crosses_horizontal_face?(box, from, to)
|
|
432
|
+
low, high = [from[:x], to[:x]].minmax
|
|
433
|
+
inside_y?(box, from[:y]) && overlaps_x_face?(box, low, high)
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def inside_x?(box, coordinate)
|
|
437
|
+
coordinate > (box[:x] || 0) && coordinate < right_of(box)
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def inside_y?(box, coordinate)
|
|
441
|
+
coordinate > (box[:y] || 0) && coordinate < bottom_of(box)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def overlaps_x_face?(box, low, high)
|
|
445
|
+
low < right_of(box) && high > (box[:x] || 0)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def overlaps_y_face?(box, low, high)
|
|
449
|
+
low < bottom_of(box) && high > (box[:y] || 0)
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def bottom_exterior_route(source, target, from, to)
|
|
453
|
+
rightwards = to[:x] > from[:x]
|
|
454
|
+
source_x = rightwards ? source[:x] || 0 : right_of(source)
|
|
455
|
+
target_x = rightwards ? right_of(target) : target[:x] || 0
|
|
456
|
+
source_y = bottom_of(source)
|
|
457
|
+
target_y = bottom_of(target)
|
|
458
|
+
outside_y = [source_y, target_y].max + LOOP_REACH
|
|
459
|
+
|
|
460
|
+
[{ x: source_x, y: source_y }, { x: source_x, y: outside_y },
|
|
461
|
+
{ x: target_x, y: outside_y }, { x: target_x, y: target_y }]
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def right_exterior_route(source, target, from, to)
|
|
465
|
+
downwards = to[:y] > from[:y]
|
|
466
|
+
source_x = right_of(source)
|
|
467
|
+
target_x = right_of(target)
|
|
468
|
+
source_y = downwards ? source[:y] || 0 : bottom_of(source)
|
|
469
|
+
target_y = downwards ? bottom_of(target) : target[:y] || 0
|
|
470
|
+
outside_x = [source_x, target_x].max + LOOP_REACH
|
|
471
|
+
|
|
472
|
+
[{ x: source_x, y: source_y }, { x: outside_x, y: source_y },
|
|
473
|
+
{ x: outside_x, y: target_y }, { x: target_x, y: target_y }]
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
# How far along the ray to the other centre this box's border sits.
|
|
477
|
+
# Values above one mean the other centre lies inside this box. A node
|
|
478
|
+
# is not trimmed, so it stays where it is.
|
|
479
|
+
#
|
|
480
|
+
# Measured off the sides themselves rather than from half the
|
|
481
|
+
# width: the centre above is the one this flowchart has always
|
|
482
|
+
# used, and its integer division puts it half a unit off centre on
|
|
483
|
+
# an odd-width box. Halves either side of that would miss the side
|
|
484
|
+
# by the same half unit.
|
|
485
|
+
def step_out(box, from, to)
|
|
486
|
+
return 0.0 unless cluster?(box)
|
|
487
|
+
|
|
488
|
+
reach = border_steps(box, from, to)
|
|
489
|
+
reach.empty? ? 0.0 : reach.min.clamp(0.0, Float::INFINITY)
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def border_steps(box, from, to)
|
|
493
|
+
[axis_step(box, from[:x], to[:x], :x, :width),
|
|
494
|
+
axis_step(box, from[:y], to[:y], :y, :height)].compact
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def axis_step(box, origin, target, origin_key, size_key)
|
|
498
|
+
delta = target - origin
|
|
499
|
+
return if delta.zero?
|
|
500
|
+
|
|
501
|
+
box_side(origin, delta, box, origin_key, size_key)
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def box_side(from, delta, box, origin_key, size_key)
|
|
505
|
+
side(from, delta, box[origin_key] || 0, box[size_key] || 0)
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
# The side the run is heading for, as a fraction of the whole run.
|
|
509
|
+
def side(from, delta, near, size)
|
|
510
|
+
edge = delta.positive? ? near + size : near
|
|
511
|
+
(edge - from) / delta.to_f
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
# An end that was not trimmed comes back exactly as it went in. A
|
|
515
|
+
# box measured from text has a fractional centre, and touching
|
|
516
|
+
# that moved every ordinary edge.
|
|
517
|
+
#
|
|
518
|
+
# A trimmed one is rounded, because the arithmetic lands on
|
|
519
|
+
# seventeen digits behind the point and this output goes into a
|
|
520
|
+
# document. Two decimals is finer than anything anyone draws.
|
|
521
|
+
def along(from, to, step)
|
|
522
|
+
return from if step.zero?
|
|
523
|
+
|
|
524
|
+
{ x: along_coordinate(from, to, step, :x),
|
|
525
|
+
y: along_coordinate(from, to, step, :y) }
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def along_coordinate(from, to, step, axis)
|
|
529
|
+
(from[axis] + ((to[axis] - from[axis]) * step)).round(2)
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Etassia
|
|
4
|
+
module Layout
|
|
5
|
+
class Flowchart < Base
|
|
6
|
+
# Measured bounds behind an edge label's alphabetic baseline.
|
|
7
|
+
# Paint belongs to the renderer; these coordinates belong to the Scene.
|
|
8
|
+
class LabelBackdropGeometry < Lutaml::Model::Serializable
|
|
9
|
+
attribute :x, :float
|
|
10
|
+
attribute :y, :float
|
|
11
|
+
attribute :width, :float
|
|
12
|
+
attribute :height, :float
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def for_label(label, font_size:)
|
|
16
|
+
measured = measured_label(label, font_size)
|
|
17
|
+
new(**bounds(measured))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# Legacy callers may omit metrics; measure without changing their
|
|
23
|
+
# label or selecting a typography default inside the geometry layer.
|
|
24
|
+
def measured_label(label, font_size)
|
|
25
|
+
size = measured_size(label, font_size)
|
|
26
|
+
label.dup.tap do |measured|
|
|
27
|
+
measured.font_size = font_size
|
|
28
|
+
measured.width = size[:width]
|
|
29
|
+
measured.height = size[:height]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def measured_size(label, font_size)
|
|
34
|
+
TextMeasurement.measure(
|
|
35
|
+
label.text,
|
|
36
|
+
font_size: font_size,
|
|
37
|
+
width: label.width,
|
|
38
|
+
height: label.height,
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def bounds(label)
|
|
43
|
+
padding = label.font_size / 8.0
|
|
44
|
+
{ x: label.x - (label.width / 2.0) - padding,
|
|
45
|
+
y: top(label) - padding,
|
|
46
|
+
**padded_size(label, padding) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def padded_size(label, padding)
|
|
50
|
+
{ width: label.width + (padding * 2),
|
|
51
|
+
height: label.height + (padding * 2) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def top(label)
|
|
55
|
+
label.y - (label.height / 2.0) -
|
|
56
|
+
(Flowchart::MIDDLE_BASELINE_EMS * label.font_size)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Etassia
|
|
4
|
+
module Layout
|
|
5
|
+
class Flowchart < Base
|
|
6
|
+
# Opens room between the layers of a laid-out level for edge labels,
|
|
7
|
+
# the way dagre gives a label its own rank: a gap grows by the tallest
|
|
8
|
+
# label on an edge that crosses it. Works on a level laid out downward,
|
|
9
|
+
# so a label's room is its size along the flow, `:height` there.
|
|
10
|
+
class LayerSpacing
|
|
11
|
+
# The line box mmdc gives a label is 1.5 times its text height.
|
|
12
|
+
LINE_HEIGHT = 1.5
|
|
13
|
+
|
|
14
|
+
# @param flat [Hash] a level whose boxes elkrb has placed
|
|
15
|
+
# @param label_axis [Symbol] :height, or :width for a turned level
|
|
16
|
+
def self.apply(flat, label_axis: :height)
|
|
17
|
+
new(flat, label_axis).apply
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(flat, label_axis)
|
|
21
|
+
@flat = flat
|
|
22
|
+
@label_axis = label_axis
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def apply
|
|
26
|
+
@tops = @flat[:children].map { |box| box[:y] }.uniq.sort
|
|
27
|
+
@shifts = shifts
|
|
28
|
+
return @flat if @shifts.all?(&:zero?)
|
|
29
|
+
|
|
30
|
+
@flat[:children].each { |box| box[:y] += shift_at(box[:y]) }
|
|
31
|
+
@flat
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# How far each layer moves down: the room of every gap above it.
|
|
37
|
+
def shifts
|
|
38
|
+
rooms = gap_rooms
|
|
39
|
+
total = 0.0
|
|
40
|
+
@tops.each_index.map do |index|
|
|
41
|
+
total += rooms[index - 1] if index.positive?
|
|
42
|
+
total
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def gap_rooms
|
|
47
|
+
rooms = Array.new(@tops.length, 0.0)
|
|
48
|
+
(@flat[:edges] || []).each do |edge|
|
|
49
|
+
gap = gap_of(edge)
|
|
50
|
+
next unless gap
|
|
51
|
+
|
|
52
|
+
rooms[gap] = [rooms[gap], room_of(edge)].max
|
|
53
|
+
end
|
|
54
|
+
rooms
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The gap an edge's label sits in: the one after the layer halfway
|
|
58
|
+
# along the edge.
|
|
59
|
+
def gap_of(edge)
|
|
60
|
+
from, to = [layer_of(edge[:sources].first),
|
|
61
|
+
layer_of(edge[:targets].first)].sort
|
|
62
|
+
return if to.zero? || from == to
|
|
63
|
+
|
|
64
|
+
(from + to - 1) / 2
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def layer_of(box_id)
|
|
68
|
+
box = @flat[:children].find { |child| child[:id] == box_id }
|
|
69
|
+
@tops.index(box[:y])
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def room_of(edge)
|
|
73
|
+
size = (edge[:labels] || []).filter_map { |l| l[@label_axis] }.max
|
|
74
|
+
return 0.0 unless size
|
|
75
|
+
|
|
76
|
+
@label_axis == :height ? size * LINE_HEIGHT : size
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def shift_at(top)
|
|
80
|
+
@shifts[@tops.index(top)]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|