sirena 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/.github/workflows/build_deploy.yml +59 -0
- data/.github/workflows/links.yml +85 -0
- data/.github/workflows/rake.yml +15 -0
- data/.github/workflows/release.yml +27 -0
- data/.gitignore +68 -0
- data/.rspec +3 -0
- data/.rubocop.yml +14 -0
- data/.rubocop_todo.yml +70 -0
- data/ARCHITECTURE.md +744 -0
- data/Gemfile +12 -0
- data/LICENSE +25 -0
- data/README.adoc +357 -0
- data/Rakefile +11 -0
- data/docs/.gitignore +1 -0
- data/docs/Gemfile +13 -0
- data/docs/_config.yml +182 -0
- data/docs/_diagram_types/architecture-diagram.adoc +314 -0
- data/docs/_diagram_types/block-diagram.adoc +345 -0
- data/docs/_diagram_types/c4-diagram.adoc +559 -0
- data/docs/_diagram_types/class-diagram.adoc +816 -0
- data/docs/_diagram_types/er-diagram.adoc +719 -0
- data/docs/_diagram_types/error-diagram.adoc +114 -0
- data/docs/_diagram_types/examples/flowchart-examples.adoc +29 -0
- data/docs/_diagram_types/flowchart.adoc +488 -0
- data/docs/_diagram_types/gantt-chart.adoc +502 -0
- data/docs/_diagram_types/git-graph.adoc +600 -0
- data/docs/_diagram_types/index.adoc +192 -0
- data/docs/_diagram_types/info-diagram.adoc +103 -0
- data/docs/_diagram_types/kanban-diagram.adoc +262 -0
- data/docs/_diagram_types/mindmap.adoc +603 -0
- data/docs/_diagram_types/packet-diagram.adoc +378 -0
- data/docs/_diagram_types/pie-chart.adoc +335 -0
- data/docs/_diagram_types/quadrant-chart.adoc +406 -0
- data/docs/_diagram_types/radar-chart.adoc +528 -0
- data/docs/_diagram_types/requirement-diagram.adoc +416 -0
- data/docs/_diagram_types/sankey-diagram.adoc +357 -0
- data/docs/_diagram_types/sequence-diagram.adoc +664 -0
- data/docs/_diagram_types/state-diagram.adoc +658 -0
- data/docs/_diagram_types/timeline.adoc +352 -0
- data/docs/_diagram_types/treemap-diagram.adoc +462 -0
- data/docs/_diagram_types/user-journey.adoc +602 -0
- data/docs/_features/index.adoc +129 -0
- data/docs/_guides/cli-reference.adoc +203 -0
- data/docs/_guides/index.adoc +56 -0
- data/docs/_guides/installation.adoc +100 -0
- data/docs/_guides/quick-start.adoc +132 -0
- data/docs/_pages/comparison.adoc +441 -0
- data/docs/_pages/compatibility.adoc +300 -0
- data/docs/_pages/index.adoc +39 -0
- data/docs/_references/index.adoc +103 -0
- data/docs/_tutorials/index.adoc +57 -0
- data/docs/index.adoc +166 -0
- data/docs/lychee.toml +54 -0
- data/examples/.gitignore +10 -0
- data/examples/README.adoc +196 -0
- data/examples/README.md +64 -0
- data/examples/architecture/01-basic-services.mmd +9 -0
- data/examples/architecture/01-basic-services.svg +37 -0
- data/examples/architecture/02-service-groups.mmd +16 -0
- data/examples/architecture/02-service-groups.svg +55 -0
- data/examples/architecture/README.adoc +79 -0
- data/examples/block/01-basic-blocks.mmd +13 -0
- data/examples/block/01-basic-blocks.svg +44 -0
- data/examples/block/02-block-shapes.mmd +13 -0
- data/examples/block/02-block-shapes.svg +47 -0
- data/examples/block/README.adoc +85 -0
- data/examples/c4/01-context-diagram.mmd +10 -0
- data/examples/c4/01-context-diagram.svg +45 -0
- data/examples/c4/02-container-diagram.mmd +24 -0
- data/examples/c4/02-container-diagram.svg +105 -0
- data/examples/c4/README.adoc +92 -0
- data/examples/class_diagram/01-basic-classes.mmd +61 -0
- data/examples/class_diagram/01-basic-classes.svg +117 -0
- data/examples/class_diagram/02-relationships.mmd +61 -0
- data/examples/class_diagram/02-relationships.svg +129 -0
- data/examples/class_diagram/README.adoc +93 -0
- data/examples/er_diagram/01-basic-entities.mmd +64 -0
- data/examples/er_diagram/01-basic-entities.svg +5 -0
- data/examples/er_diagram/02-cardinality.mmd +57 -0
- data/examples/er_diagram/02-cardinality.svg +125 -0
- data/examples/er_diagram/README.adoc +88 -0
- data/examples/error/01-basic-error.mmd +1 -0
- data/examples/error/01-basic-error.svg +13 -0
- data/examples/error/02-error-display.mmd +1 -0
- data/examples/error/02-error-display.svg +13 -0
- data/examples/error/README.adoc +71 -0
- data/examples/error_message_example.svg +13 -0
- data/examples/flowchart/00-original.mmd +13 -0
- data/examples/flowchart/00-original.svg +5 -0
- data/examples/flowchart/01-basic-flow.mmd +7 -0
- data/examples/flowchart/01-basic-flow.svg +52 -0
- data/examples/flowchart/01-basic-flow.yml +13 -0
- data/examples/flowchart/02*.svg +87 -0
- data/examples/flowchart/02-node-shapes.mmd +9 -0
- data/examples/flowchart/02-node-shapes.svg +33 -0
- data/examples/flowchart/03-edge-types.mmd +7 -0
- data/examples/flowchart/03-edge-types.svg +53 -0
- data/examples/flowchart/04-subgraphs.mmd +9 -0
- data/examples/flowchart/04-subgraphs.svg +33 -0
- data/examples/flowchart/05-styling.mmd +9 -0
- data/examples/flowchart/05-styling.svg +33 -0
- data/examples/flowchart/06-complex-flow.mmd +8 -0
- data/examples/flowchart/06-complex-flow.svg +59 -0
- data/examples/flowchart/README.adoc +167 -0
- data/examples/gantt/01-simple-timeline.* +14 -0
- data/examples/gantt/01-simple-timeline.mmd +6 -0
- data/examples/gantt/01-simple-timeline.svg +26 -0
- data/examples/gantt/02-task-dependencies.mmd +6 -0
- data/examples/gantt/02-task-dependencies.svg +26 -0
- data/examples/gantt/README.adoc +86 -0
- data/examples/git_graph/01-linear-history.mmd +12 -0
- data/examples/git_graph/01-linear-history.svg +26 -0
- data/examples/git_graph/02-branching.mmd +12 -0
- data/examples/git_graph/02-branching.svg +26 -0
- data/examples/git_graph/README.adoc +73 -0
- data/examples/info/02-showinfo.mmd +1 -0
- data/examples/info/02-showinfo.svg +10 -0
- data/examples/info/README.adoc +58 -0
- data/examples/info_showinfo_example.svg +10 -0
- data/examples/kanban/01-simple-board.mmd +8 -0
- data/examples/kanban/01-simple-board.svg +43 -0
- data/examples/kanban/02-workflow.mmd +8 -0
- data/examples/kanban/02-workflow.svg +43 -0
- data/examples/kanban/README.adoc +79 -0
- data/examples/mindmap/01-simple-tree.mmd +19 -0
- data/examples/mindmap/01-simple-tree.svg +61 -0
- data/examples/mindmap/02-knowledge-map.mmd +19 -0
- data/examples/mindmap/02-knowledge-map.svg +61 -0
- data/examples/mindmap/README.adoc +77 -0
- data/examples/packet/01-basic-packet.* +17 -0
- data/examples/packet/01-basic-packet.mmd +4 -0
- data/examples/packet/01-basic-packet.svg +82 -0
- data/examples/packet/README.adoc +58 -0
- data/examples/pie/01-simple-chart.mmd +5 -0
- data/examples/pie/01-simple-chart.svg +17 -0
- data/examples/pie/02-labeled-slices.mmd +6 -0
- data/examples/pie/02-labeled-slices.svg +19 -0
- data/examples/pie/README.adoc +75 -0
- data/examples/quadrant/01-basic-quadrant.mmd +13 -0
- data/examples/quadrant/01-basic-quadrant.svg +33 -0
- data/examples/quadrant/02-positioned-items.mmd +14 -0
- data/examples/quadrant/02-positioned-items.svg +35 -0
- data/examples/quadrant/README.adoc +84 -0
- data/examples/radar/01-simple-radar.* +5 -0
- data/examples/radar/01-simple-radar.mmd +3 -0
- data/examples/radar/01-simple-radar.svg +25 -0
- data/examples/radar/02-multiple-curves.mmd +4 -0
- data/examples/radar/02-multiple-curves.svg +43 -0
- data/examples/radar/README.adoc +75 -0
- data/examples/requirement/01-basic-requirements.mmd +23 -0
- data/examples/requirement/01-basic-requirements.svg +49 -0
- data/examples/requirement/02-risk-levels.mmd +23 -0
- data/examples/requirement/02-risk-levels.svg +49 -0
- data/examples/requirement/README.adoc +85 -0
- data/examples/sankey/01-simple-flow.mmd +7 -0
- data/examples/sankey/01-simple-flow.svg +34 -0
- data/examples/sankey/02-multi-stage.mmd +11 -0
- data/examples/sankey/02-multi-stage.svg +44 -0
- data/examples/sankey/README.adoc +74 -0
- data/examples/sequence/01-basic-sequence.mmd +27 -0
- data/examples/sequence/01-basic-sequence.svg +5 -0
- data/examples/sequence/02-activations.mmd +17 -0
- data/examples/sequence/02-activations.svg +78 -0
- data/examples/sequence/README.adoc +86 -0
- data/examples/state_diagram/01-simple-states.mmd +29 -0
- data/examples/state_diagram/01-simple-states.svg +5 -0
- data/examples/state_diagram/02-composite.mmd +19 -0
- data/examples/state_diagram/02-composite.svg +81 -0
- data/examples/state_diagram/README.adoc +90 -0
- data/examples/timeline/01-simple-timeline.mmd +11 -0
- data/examples/timeline/01-simple-timeline.svg +36 -0
- data/examples/timeline/02-periods.mmd +15 -0
- data/examples/timeline/02-periods.svg +47 -0
- data/examples/timeline/README.adoc +78 -0
- data/examples/treemap/01-basic-treemap.mmd +12 -0
- data/examples/treemap/01-basic-treemap.svg +59 -0
- data/examples/treemap/README.adoc +59 -0
- data/examples/user_journey/01-simple-journey.mmd +23 -0
- data/examples/user_journey/01-simple-journey.svg +5 -0
- data/examples/user_journey/02-multi-actor.mmd +18 -0
- data/examples/user_journey/02-multi-actor.svg +129 -0
- data/examples/user_journey/README.adoc +81 -0
- data/examples/xychart/01-line-chart.mmd +5 -0
- data/examples/xychart/01-line-chart.svg +43 -0
- data/examples/xychart/02-bar-chart.mmd +7 -0
- data/examples/xychart/02-bar-chart.svg +48 -0
- data/examples/xychart/README.adoc +80 -0
- data/exe/sirena +7 -0
- data/lib/sirena/cli.rb +138 -0
- data/lib/sirena/commands/batch.rb +117 -0
- data/lib/sirena/commands/render.rb +80 -0
- data/lib/sirena/commands/types.rb +29 -0
- data/lib/sirena/commands/version.rb +24 -0
- data/lib/sirena/diagram/architecture.rb +46 -0
- data/lib/sirena/diagram/base.rb +61 -0
- data/lib/sirena/diagram/block.rb +81 -0
- data/lib/sirena/diagram/c4.rb +328 -0
- data/lib/sirena/diagram/class_diagram.rb +385 -0
- data/lib/sirena/diagram/er_diagram.rb +238 -0
- data/lib/sirena/diagram/error.rb +38 -0
- data/lib/sirena/diagram/flowchart.rb +160 -0
- data/lib/sirena/diagram/gantt.rb +71 -0
- data/lib/sirena/diagram/git_graph.rb +36 -0
- data/lib/sirena/diagram/info.rb +38 -0
- data/lib/sirena/diagram/kanban.rb +178 -0
- data/lib/sirena/diagram/mindmap.rb +54 -0
- data/lib/sirena/diagram/packet.rb +79 -0
- data/lib/sirena/diagram/pie.rb +115 -0
- data/lib/sirena/diagram/quadrant.rb +138 -0
- data/lib/sirena/diagram/radar.rb +52 -0
- data/lib/sirena/diagram/requirement.rb +133 -0
- data/lib/sirena/diagram/sankey.rb +217 -0
- data/lib/sirena/diagram/sequence.rb +242 -0
- data/lib/sirena/diagram/state_diagram.rb +237 -0
- data/lib/sirena/diagram/timeline.rb +171 -0
- data/lib/sirena/diagram/treemap.rb +84 -0
- data/lib/sirena/diagram/user_journey.rb +149 -0
- data/lib/sirena/diagram/xy_chart.rb +76 -0
- data/lib/sirena/diagram.rb +8 -0
- data/lib/sirena/diagram_registry.rb +101 -0
- data/lib/sirena/engine.rb +292 -0
- data/lib/sirena/parser/architecture.rb +41 -0
- data/lib/sirena/parser/base.rb +41 -0
- data/lib/sirena/parser/block.rb +72 -0
- data/lib/sirena/parser/c4.rb +53 -0
- data/lib/sirena/parser/class_diagram.rb +63 -0
- data/lib/sirena/parser/er_diagram.rb +40 -0
- data/lib/sirena/parser/error.rb +49 -0
- data/lib/sirena/parser/flowchart.rb +71 -0
- data/lib/sirena/parser/gantt.rb +60 -0
- data/lib/sirena/parser/git_graph.rb +95 -0
- data/lib/sirena/parser/grammars/architecture.rb +145 -0
- data/lib/sirena/parser/grammars/block.rb +190 -0
- data/lib/sirena/parser/grammars/c4.rb +226 -0
- data/lib/sirena/parser/grammars/class_diagram.rb +284 -0
- data/lib/sirena/parser/grammars/common.rb +84 -0
- data/lib/sirena/parser/grammars/er_diagram.rb +114 -0
- data/lib/sirena/parser/grammars/error.rb +40 -0
- data/lib/sirena/parser/grammars/flowchart.rb +298 -0
- data/lib/sirena/parser/grammars/gantt.rb +252 -0
- data/lib/sirena/parser/grammars/git_graph.rb +167 -0
- data/lib/sirena/parser/grammars/info.rb +58 -0
- data/lib/sirena/parser/grammars/kanban.rb +83 -0
- data/lib/sirena/parser/grammars/mindmap.rb +115 -0
- data/lib/sirena/parser/grammars/packet.rb +73 -0
- data/lib/sirena/parser/grammars/pie.rb +128 -0
- data/lib/sirena/parser/grammars/quadrant.rb +199 -0
- data/lib/sirena/parser/grammars/radar.rb +150 -0
- data/lib/sirena/parser/grammars/requirement.rb +188 -0
- data/lib/sirena/parser/grammars/sankey.rb +104 -0
- data/lib/sirena/parser/grammars/sequence.rb +247 -0
- data/lib/sirena/parser/grammars/state_diagram.rb +172 -0
- data/lib/sirena/parser/grammars/timeline.rb +142 -0
- data/lib/sirena/parser/grammars/treemap.rb +120 -0
- data/lib/sirena/parser/grammars/xy_chart.rb +120 -0
- data/lib/sirena/parser/info.rb +49 -0
- data/lib/sirena/parser/kanban.rb +97 -0
- data/lib/sirena/parser/mindmap.rb +106 -0
- data/lib/sirena/parser/packet.rb +76 -0
- data/lib/sirena/parser/pie.rb +49 -0
- data/lib/sirena/parser/quadrant.rb +57 -0
- data/lib/sirena/parser/radar.rb +104 -0
- data/lib/sirena/parser/requirement.rb +70 -0
- data/lib/sirena/parser/sankey.rb +64 -0
- data/lib/sirena/parser/sequence.rb +51 -0
- data/lib/sirena/parser/state_diagram.rb +69 -0
- data/lib/sirena/parser/timeline.rb +57 -0
- data/lib/sirena/parser/transforms/architecture.rb +97 -0
- data/lib/sirena/parser/transforms/block.rb +254 -0
- data/lib/sirena/parser/transforms/c4.rb +347 -0
- data/lib/sirena/parser/transforms/class_diagram.rb +352 -0
- data/lib/sirena/parser/transforms/er_diagram.rb +169 -0
- data/lib/sirena/parser/transforms/error.rb +58 -0
- data/lib/sirena/parser/transforms/flowchart.rb +293 -0
- data/lib/sirena/parser/transforms/gantt.rb +215 -0
- data/lib/sirena/parser/transforms/git_graph.rb +160 -0
- data/lib/sirena/parser/transforms/info.rb +58 -0
- data/lib/sirena/parser/transforms/kanban.rb +176 -0
- data/lib/sirena/parser/transforms/mindmap.rb +227 -0
- data/lib/sirena/parser/transforms/packet.rb +63 -0
- data/lib/sirena/parser/transforms/pie.rb +143 -0
- data/lib/sirena/parser/transforms/quadrant.rb +177 -0
- data/lib/sirena/parser/transforms/radar.rb +126 -0
- data/lib/sirena/parser/transforms/requirement.rb +272 -0
- data/lib/sirena/parser/transforms/sankey.rb +122 -0
- data/lib/sirena/parser/transforms/sequence.rb +342 -0
- data/lib/sirena/parser/transforms/state_diagram.rb +292 -0
- data/lib/sirena/parser/transforms/timeline.rb +177 -0
- data/lib/sirena/parser/transforms/treemap.rb +81 -0
- data/lib/sirena/parser/transforms/xy_chart.rb +132 -0
- data/lib/sirena/parser/treemap.rb +98 -0
- data/lib/sirena/parser/user_journey.rb +120 -0
- data/lib/sirena/parser/xy_chart.rb +114 -0
- data/lib/sirena/parser.rb +8 -0
- data/lib/sirena/renderer/architecture.rb +251 -0
- data/lib/sirena/renderer/base.rb +251 -0
- data/lib/sirena/renderer/block.rb +286 -0
- data/lib/sirena/renderer/c4.rb +490 -0
- data/lib/sirena/renderer/class_diagram.rb +499 -0
- data/lib/sirena/renderer/er_diagram.rb +417 -0
- data/lib/sirena/renderer/error.rb +131 -0
- data/lib/sirena/renderer/flowchart.rb +301 -0
- data/lib/sirena/renderer/gantt.rb +331 -0
- data/lib/sirena/renderer/git_graph.rb +368 -0
- data/lib/sirena/renderer/info.rb +93 -0
- data/lib/sirena/renderer/kanban.rb +295 -0
- data/lib/sirena/renderer/mindmap.rb +396 -0
- data/lib/sirena/renderer/packet.rb +239 -0
- data/lib/sirena/renderer/pie.rb +235 -0
- data/lib/sirena/renderer/quadrant.rb +292 -0
- data/lib/sirena/renderer/radar.rb +323 -0
- data/lib/sirena/renderer/requirement.rb +371 -0
- data/lib/sirena/renderer/sankey.rb +255 -0
- data/lib/sirena/renderer/sequence.rb +424 -0
- data/lib/sirena/renderer/state_diagram.rb +328 -0
- data/lib/sirena/renderer/timeline.rb +304 -0
- data/lib/sirena/renderer/treemap.rb +152 -0
- data/lib/sirena/renderer/user_journey.rb +331 -0
- data/lib/sirena/renderer/xy_chart.rb +452 -0
- data/lib/sirena/renderer.rb +8 -0
- data/lib/sirena/svg/circle.rb +41 -0
- data/lib/sirena/svg/document.rb +103 -0
- data/lib/sirena/svg/element.rb +65 -0
- data/lib/sirena/svg/ellipse.rb +33 -0
- data/lib/sirena/svg/group.rb +71 -0
- data/lib/sirena/svg/line.rb +49 -0
- data/lib/sirena/svg/path.rb +76 -0
- data/lib/sirena/svg/polygon.rb +43 -0
- data/lib/sirena/svg/polyline.rb +35 -0
- data/lib/sirena/svg/rect.rb +57 -0
- data/lib/sirena/svg/style.rb +44 -0
- data/lib/sirena/svg/text.rb +72 -0
- data/lib/sirena/svg.rb +19 -0
- data/lib/sirena/text_measurement.rb +71 -0
- data/lib/sirena/theme/builtin/dark.yml +70 -0
- data/lib/sirena/theme/builtin/default.yml +80 -0
- data/lib/sirena/theme/builtin/high_contrast.yml +70 -0
- data/lib/sirena/theme/builtin/light.yml +70 -0
- data/lib/sirena/theme/color_palette.rb +48 -0
- data/lib/sirena/theme/effect_styles.rb +28 -0
- data/lib/sirena/theme/registry.rb +41 -0
- data/lib/sirena/theme/shape_styles.rb +28 -0
- data/lib/sirena/theme/spacing_config.rb +24 -0
- data/lib/sirena/theme/typography.rb +30 -0
- data/lib/sirena/theme.rb +69 -0
- data/lib/sirena/transform/architecture.rb +273 -0
- data/lib/sirena/transform/base.rb +199 -0
- data/lib/sirena/transform/block.rb +215 -0
- data/lib/sirena/transform/c4.rb +288 -0
- data/lib/sirena/transform/class_diagram.rb +296 -0
- data/lib/sirena/transform/er_diagram.rb +204 -0
- data/lib/sirena/transform/error.rb +39 -0
- data/lib/sirena/transform/flowchart.rb +161 -0
- data/lib/sirena/transform/gantt.rb +253 -0
- data/lib/sirena/transform/git_graph.rb +283 -0
- data/lib/sirena/transform/info.rb +39 -0
- data/lib/sirena/transform/kanban.rb +180 -0
- data/lib/sirena/transform/mindmap.rb +251 -0
- data/lib/sirena/transform/packet.rb +185 -0
- data/lib/sirena/transform/pie.rb +62 -0
- data/lib/sirena/transform/quadrant.rb +167 -0
- data/lib/sirena/transform/radar.rb +227 -0
- data/lib/sirena/transform/requirement.rb +233 -0
- data/lib/sirena/transform/sankey.rb +212 -0
- data/lib/sirena/transform/sequence.rb +143 -0
- data/lib/sirena/transform/state_diagram.rb +228 -0
- data/lib/sirena/transform/timeline.rb +139 -0
- data/lib/sirena/transform/treemap.rb +120 -0
- data/lib/sirena/transform/user_journey.rb +207 -0
- data/lib/sirena/transform/xy_chart.rb +273 -0
- data/lib/sirena/transform.rb +8 -0
- data/lib/sirena/version.rb +5 -0
- data/lib/sirena.rb +328 -0
- data/lib/tasks/benchmark.rake +532 -0
- data/lib/tasks/examples.rake +468 -0
- data/lib/tasks/generate_mermaid_fixtures.rake +363 -0
- data/lib/tasks/mermaid_fixtures.rake +46 -0
- data/scripts/extract_mermaid_tests.rb +493 -0
- data/scripts/rename_to_sirena.rb +73 -0
- data/sirena.gemspec +47 -0
- metadata +529 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Sirena
|
|
6
|
+
module Diagram
|
|
7
|
+
# Represents a treemap diagram showing hierarchical data
|
|
8
|
+
class TreemapDiagram < Base
|
|
9
|
+
attr_accessor :title, :root_nodes, :class_defs
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
super
|
|
13
|
+
@root_nodes = []
|
|
14
|
+
@class_defs = {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def type
|
|
18
|
+
:treemap
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Add a root-level node
|
|
22
|
+
def add_root_node(node)
|
|
23
|
+
@root_nodes << node
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Add a class definition
|
|
27
|
+
def add_class_def(name, styles)
|
|
28
|
+
@class_defs[name] = styles
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Calculate total value of all nodes
|
|
32
|
+
def total_value
|
|
33
|
+
@root_nodes.sum(&:total_value)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Represents a node in a treemap (can be a branch or leaf)
|
|
38
|
+
class TreemapNode
|
|
39
|
+
attr_accessor :label, :value, :children, :css_class, :parent
|
|
40
|
+
|
|
41
|
+
def initialize(label, value = nil)
|
|
42
|
+
@label = label
|
|
43
|
+
@value = value&.to_f
|
|
44
|
+
@children = []
|
|
45
|
+
@css_class = nil
|
|
46
|
+
@parent = nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Add a child node
|
|
50
|
+
def add_child(node)
|
|
51
|
+
node.parent = self
|
|
52
|
+
@children << node
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Check if this is a leaf node (has a value)
|
|
56
|
+
def leaf?
|
|
57
|
+
!@value.nil?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Check if this is a branch node (has children)
|
|
61
|
+
def branch?
|
|
62
|
+
!@children.empty?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Calculate total value including children
|
|
66
|
+
def total_value
|
|
67
|
+
if leaf?
|
|
68
|
+
@value
|
|
69
|
+
elsif branch?
|
|
70
|
+
@children.sum(&:total_value)
|
|
71
|
+
else
|
|
72
|
+
0.0
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Get the depth level of this node (root = 0)
|
|
77
|
+
def depth
|
|
78
|
+
return 0 unless @parent
|
|
79
|
+
|
|
80
|
+
1 + @parent.depth
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lutaml/model'
|
|
4
|
+
require_relative 'base'
|
|
5
|
+
|
|
6
|
+
module Sirena
|
|
7
|
+
module Diagram
|
|
8
|
+
# Represents a task in a user journey.
|
|
9
|
+
#
|
|
10
|
+
# A task has a name, score (1-5 indicating satisfaction), and a
|
|
11
|
+
# collection of actors involved in performing the task.
|
|
12
|
+
class JourneyTask < Lutaml::Model::Serializable
|
|
13
|
+
# Task name/description
|
|
14
|
+
attribute :name, :string
|
|
15
|
+
|
|
16
|
+
# Task satisfaction score (1-5 range)
|
|
17
|
+
attribute :score, :integer
|
|
18
|
+
|
|
19
|
+
# Collection of actor names involved in this task
|
|
20
|
+
attribute :actors, :string, collection: true, default: -> { [] }
|
|
21
|
+
|
|
22
|
+
# Validates the task has required fields.
|
|
23
|
+
#
|
|
24
|
+
# @return [Boolean] true if task is valid
|
|
25
|
+
def valid?
|
|
26
|
+
!name.nil? && !name.empty? &&
|
|
27
|
+
!score.nil? &&
|
|
28
|
+
score >= 1 && score <= 5 &&
|
|
29
|
+
!actors.nil? && !actors.empty?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns the color for this task based on score.
|
|
33
|
+
#
|
|
34
|
+
# @return [String] color indicator (:red, :yellow, :green)
|
|
35
|
+
def score_color
|
|
36
|
+
case score
|
|
37
|
+
when 1..2
|
|
38
|
+
:red
|
|
39
|
+
when 3
|
|
40
|
+
:yellow
|
|
41
|
+
when 4..5
|
|
42
|
+
:green
|
|
43
|
+
else
|
|
44
|
+
:yellow
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Represents a section grouping in a user journey.
|
|
50
|
+
#
|
|
51
|
+
# A section groups related tasks together, representing a phase
|
|
52
|
+
# or stage in the user journey.
|
|
53
|
+
class JourneySection < Lutaml::Model::Serializable
|
|
54
|
+
# Section name/title
|
|
55
|
+
attribute :name, :string
|
|
56
|
+
|
|
57
|
+
# Collection of tasks in this section
|
|
58
|
+
attribute :tasks, JourneyTask, collection: true,
|
|
59
|
+
default: -> { [] }
|
|
60
|
+
|
|
61
|
+
# Validates the section has required fields.
|
|
62
|
+
#
|
|
63
|
+
# @return [Boolean] true if section is valid
|
|
64
|
+
def valid?
|
|
65
|
+
!name.nil? && !name.empty? && tasks.all?(&:valid?)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# User Journey diagram model.
|
|
70
|
+
#
|
|
71
|
+
# Represents a complete user journey diagram showing the tasks users
|
|
72
|
+
# perform, grouped into sections, with satisfaction scores and actor
|
|
73
|
+
# involvement.
|
|
74
|
+
#
|
|
75
|
+
# @example Creating a simple user journey
|
|
76
|
+
# diagram = UserJourney.new
|
|
77
|
+
# diagram.title = "Customer Shopping Journey"
|
|
78
|
+
# section = JourneySection.new.tap do |s|
|
|
79
|
+
# s.name = "Shopping"
|
|
80
|
+
# end
|
|
81
|
+
# task = JourneyTask.new.tap do |t|
|
|
82
|
+
# t.name = "Browse products"
|
|
83
|
+
# t.score = 5
|
|
84
|
+
# t.actors = ["Customer"]
|
|
85
|
+
# end
|
|
86
|
+
# section.tasks << task
|
|
87
|
+
# diagram.sections << section
|
|
88
|
+
class UserJourney < Base
|
|
89
|
+
# Optional diagram title
|
|
90
|
+
attribute :title, :string
|
|
91
|
+
|
|
92
|
+
# Collection of journey sections
|
|
93
|
+
attribute :sections, JourneySection, collection: true,
|
|
94
|
+
default: -> { [] }
|
|
95
|
+
|
|
96
|
+
# Returns the diagram type identifier.
|
|
97
|
+
#
|
|
98
|
+
# @return [Symbol] :user_journey
|
|
99
|
+
def diagram_type
|
|
100
|
+
:user_journey
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Validates the user journey structure.
|
|
104
|
+
#
|
|
105
|
+
# A user journey is valid if:
|
|
106
|
+
# - It has at least one section
|
|
107
|
+
# - All sections are valid
|
|
108
|
+
# - All tasks within sections are valid
|
|
109
|
+
#
|
|
110
|
+
# @return [Boolean] true if user journey is valid
|
|
111
|
+
def valid?
|
|
112
|
+
return false if sections.nil? || sections.empty?
|
|
113
|
+
return false unless sections.all?(&:valid?)
|
|
114
|
+
|
|
115
|
+
true
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Returns all tasks across all sections.
|
|
119
|
+
#
|
|
120
|
+
# @return [Array<JourneyTask>] all tasks in the journey
|
|
121
|
+
def all_tasks
|
|
122
|
+
sections.flat_map(&:tasks)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Returns all unique actors involved in the journey.
|
|
126
|
+
#
|
|
127
|
+
# @return [Array<String>] unique actor names
|
|
128
|
+
def all_actors
|
|
129
|
+
all_tasks.flat_map(&:actors).uniq
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Finds tasks by score.
|
|
133
|
+
#
|
|
134
|
+
# @param score [Integer] the score to filter by (1-5)
|
|
135
|
+
# @return [Array<JourneyTask>] tasks with the given score
|
|
136
|
+
def tasks_by_score(score)
|
|
137
|
+
all_tasks.select { |t| t.score == score }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Finds tasks by actor.
|
|
141
|
+
#
|
|
142
|
+
# @param actor [String] the actor name
|
|
143
|
+
# @return [Array<JourneyTask>] tasks involving the actor
|
|
144
|
+
def tasks_by_actor(actor)
|
|
145
|
+
all_tasks.select { |t| t.actors.include?(actor) }
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sirena
|
|
4
|
+
module Diagram
|
|
5
|
+
# Represents an XY chart diagram (scatter/line/bar chart)
|
|
6
|
+
class XYChart < Base
|
|
7
|
+
attr_accessor :title, :x_axis, :y_axis, :datasets, :options
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
super
|
|
11
|
+
@datasets = []
|
|
12
|
+
@options = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def type
|
|
16
|
+
:xychart
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Add a dataset to the chart
|
|
20
|
+
def add_dataset(dataset)
|
|
21
|
+
@datasets << dataset
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Represents an axis configuration
|
|
26
|
+
class XYAxis
|
|
27
|
+
attr_accessor :label, :values, :min, :max, :type
|
|
28
|
+
|
|
29
|
+
def initialize
|
|
30
|
+
@type = :numeric # :numeric or :categorical
|
|
31
|
+
@values = []
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Check if axis has categorical values
|
|
35
|
+
def categorical?
|
|
36
|
+
@type == :categorical
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Check if axis has numeric range
|
|
40
|
+
def numeric?
|
|
41
|
+
@type == :numeric
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Get the range of the axis
|
|
45
|
+
def range
|
|
46
|
+
if numeric?
|
|
47
|
+
[@min || 0, @max || 100]
|
|
48
|
+
else
|
|
49
|
+
[0, values.length - 1]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Represents a dataset in an XY chart
|
|
55
|
+
class XYDataset
|
|
56
|
+
attr_accessor :id, :label, :chart_type, :values, :color
|
|
57
|
+
|
|
58
|
+
def initialize(id, label = nil, chart_type = :line)
|
|
59
|
+
@id = id
|
|
60
|
+
@label = label || id
|
|
61
|
+
@chart_type = chart_type # :line, :bar, :scatter
|
|
62
|
+
@values = []
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Add a value to the dataset
|
|
66
|
+
def add_value(value)
|
|
67
|
+
@values << value.to_f
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Set all values at once
|
|
71
|
+
def values=(vals)
|
|
72
|
+
@values = vals.map(&:to_f)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sirena
|
|
4
|
+
# Registry for diagram type handlers.
|
|
5
|
+
#
|
|
6
|
+
# This class implements the registry pattern to manage diagram type
|
|
7
|
+
# handlers without hardcoding type checks. Each diagram type registers
|
|
8
|
+
# its parser, transform, and renderer components, which can be retrieved
|
|
9
|
+
# dynamically during diagram processing.
|
|
10
|
+
#
|
|
11
|
+
# @example Registering a diagram type
|
|
12
|
+
# DiagramRegistry.register(
|
|
13
|
+
# :flowchart,
|
|
14
|
+
# parser: Parser::FlowchartGrammar,
|
|
15
|
+
# transform: Transform::FlowchartTransform,
|
|
16
|
+
# renderer: Renderer::FlowchartRenderer
|
|
17
|
+
# )
|
|
18
|
+
#
|
|
19
|
+
# @example Retrieving handlers for a type
|
|
20
|
+
# handlers = DiagramRegistry.get(:flowchart)
|
|
21
|
+
# # => { parser: Parser::FlowchartGrammar,
|
|
22
|
+
# # transform: Transform::FlowchartTransform,
|
|
23
|
+
# # renderer: Renderer::FlowchartRenderer }
|
|
24
|
+
#
|
|
25
|
+
# @example Listing registered types
|
|
26
|
+
# DiagramRegistry.types
|
|
27
|
+
# # => [:flowchart, :sequence, :class_diagram]
|
|
28
|
+
class DiagramRegistry
|
|
29
|
+
@handlers = {}
|
|
30
|
+
|
|
31
|
+
class << self
|
|
32
|
+
# Registers handlers for a diagram type.
|
|
33
|
+
#
|
|
34
|
+
# @param type [Symbol] the diagram type identifier
|
|
35
|
+
# @param parser [Class] the parser class for this diagram type
|
|
36
|
+
# @param transform [Class] the transform class for this diagram type
|
|
37
|
+
# @param renderer [Class] the renderer class for this diagram type
|
|
38
|
+
# @return [Hash] the registered handler hash
|
|
39
|
+
#
|
|
40
|
+
# @example Register a new diagram type
|
|
41
|
+
# DiagramRegistry.register(
|
|
42
|
+
# :flowchart,
|
|
43
|
+
# parser: Parser::FlowchartGrammar,
|
|
44
|
+
# transform: Transform::FlowchartTransform,
|
|
45
|
+
# renderer: Renderer::FlowchartRenderer
|
|
46
|
+
# )
|
|
47
|
+
def register(type, parser:, transform:, renderer:)
|
|
48
|
+
@handlers[type] = {
|
|
49
|
+
parser: parser,
|
|
50
|
+
transform: transform,
|
|
51
|
+
renderer: renderer
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Retrieves handlers for a diagram type.
|
|
56
|
+
#
|
|
57
|
+
# @param type [Symbol] the diagram type identifier
|
|
58
|
+
# @return [Hash, nil] hash with :parser, :transform, and :renderer
|
|
59
|
+
# keys, or nil if type not registered
|
|
60
|
+
#
|
|
61
|
+
# @example Get handlers for a type
|
|
62
|
+
# handlers = DiagramRegistry.get(:flowchart)
|
|
63
|
+
# parser_class = handlers[:parser]
|
|
64
|
+
def get(type)
|
|
65
|
+
@handlers[type]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Returns all registered diagram types.
|
|
69
|
+
#
|
|
70
|
+
# @return [Array<Symbol>] list of registered diagram type identifiers
|
|
71
|
+
#
|
|
72
|
+
# @example List all types
|
|
73
|
+
# DiagramRegistry.types
|
|
74
|
+
# # => [:flowchart, :sequence, :class_diagram]
|
|
75
|
+
def types
|
|
76
|
+
@handlers.keys
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Checks if a diagram type is registered.
|
|
80
|
+
#
|
|
81
|
+
# @param type [Symbol] the diagram type identifier
|
|
82
|
+
# @return [Boolean] true if type is registered
|
|
83
|
+
#
|
|
84
|
+
# @example Check if type is registered
|
|
85
|
+
# DiagramRegistry.registered?(:flowchart)
|
|
86
|
+
# # => true
|
|
87
|
+
def registered?(type)
|
|
88
|
+
@handlers.key?(type)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Clears all registered handlers.
|
|
92
|
+
#
|
|
93
|
+
# This method is primarily useful for testing purposes.
|
|
94
|
+
#
|
|
95
|
+
# @return [Hash] empty handler hash
|
|
96
|
+
def clear
|
|
97
|
+
@handlers = {}
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sirena
|
|
4
|
+
# Orchestrates the complete diagram rendering pipeline.
|
|
5
|
+
#
|
|
6
|
+
# The Engine class coordinates the parse → transform → layout → render
|
|
7
|
+
# pipeline for converting Mermaid source code into SVG output. It handles
|
|
8
|
+
# diagram type detection, retrieves appropriate handlers from the registry,
|
|
9
|
+
# and manages error handling throughout the process.
|
|
10
|
+
#
|
|
11
|
+
# @example Render a flowchart
|
|
12
|
+
# engine = Sirena::Engine.new
|
|
13
|
+
# svg = engine.render("graph TD\nA-->B")
|
|
14
|
+
# puts svg
|
|
15
|
+
#
|
|
16
|
+
# @example Render with options
|
|
17
|
+
# engine = Sirena::Engine.new
|
|
18
|
+
# svg = engine.render(source, verbose: true)
|
|
19
|
+
class Engine
|
|
20
|
+
# Error raised when diagram type cannot be detected
|
|
21
|
+
class DiagramTypeError < Error; end
|
|
22
|
+
|
|
23
|
+
# Error raised during pipeline execution
|
|
24
|
+
class PipelineError < Error; end
|
|
25
|
+
|
|
26
|
+
# Mapping of diagram syntax prefixes to diagram types
|
|
27
|
+
DIAGRAM_TYPE_PATTERNS = {
|
|
28
|
+
flowchart: /\A\s*(graph|flowchart)\s+/i,
|
|
29
|
+
sequence: /\A\s*sequenceDiagram/i,
|
|
30
|
+
class_diagram: /\A\s*classDiagram/i,
|
|
31
|
+
state_diagram: /\A\s*stateDiagram(-v2)?/i,
|
|
32
|
+
er_diagram: /\A\s*erDiagram/i,
|
|
33
|
+
user_journey: /\A\s*journey/i,
|
|
34
|
+
gantt: /\A\s*gantt\s/i,
|
|
35
|
+
pie: /\A\s*pie\s/i,
|
|
36
|
+
timeline: /\A\s*timeline(\s|$)/i,
|
|
37
|
+
quadrant: /\A\s*quadrantChart/i,
|
|
38
|
+
git_graph: /\A\s*gitGraph/i,
|
|
39
|
+
mindmap: /\A\s*mindmap/i,
|
|
40
|
+
kanban: /\A\s*kanban/i,
|
|
41
|
+
radar: /\A\s*radar-beta/i,
|
|
42
|
+
block: /\A\s*block-beta/i,
|
|
43
|
+
requirement: /\A\s*requirementDiagram/i,
|
|
44
|
+
xychart: /\A\s*xychart-beta/i,
|
|
45
|
+
architecture: /\A\s*architecture-beta/i,
|
|
46
|
+
sankey: /\A\s*sankey-beta/i,
|
|
47
|
+
packet: /\A\s*packet-beta/i,
|
|
48
|
+
treemap: /\A\s*treemap(-beta)?/i,
|
|
49
|
+
c4: /\A\s*(C4Context|C4Container|C4Component|C4Dynamic|C4Deployment|C4\s+diagram)/i,
|
|
50
|
+
info: /\A\s*info/i,
|
|
51
|
+
error: /\A\s*(error|Error)/i
|
|
52
|
+
}.freeze
|
|
53
|
+
|
|
54
|
+
attr_reader :verbose, :theme
|
|
55
|
+
|
|
56
|
+
# Creates a new Engine instance.
|
|
57
|
+
#
|
|
58
|
+
# @param verbose [Boolean] enable verbose output for debugging
|
|
59
|
+
# @param theme [String, Theme, Hash, nil] theme specification
|
|
60
|
+
def initialize(verbose: false, theme: nil)
|
|
61
|
+
@verbose = verbose
|
|
62
|
+
@theme = load_theme(theme)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Renders Mermaid source code to SVG.
|
|
66
|
+
#
|
|
67
|
+
# @param mermaid_source [String] Mermaid diagram source code
|
|
68
|
+
# @param options [Hash] rendering options
|
|
69
|
+
# @option options [Boolean] :verbose enable verbose output
|
|
70
|
+
# @option options [String, Theme, Hash, nil] :theme theme override
|
|
71
|
+
# @return [String] SVG XML string
|
|
72
|
+
# @raise [DiagramTypeError] if diagram type cannot be detected
|
|
73
|
+
# @raise [PipelineError] if any pipeline stage fails
|
|
74
|
+
def render(mermaid_source, options = {})
|
|
75
|
+
@verbose = options[:verbose] if options.key?(:verbose)
|
|
76
|
+
|
|
77
|
+
# Override theme if specified in options
|
|
78
|
+
theme = options[:theme] ? load_theme(options[:theme]) : @theme
|
|
79
|
+
|
|
80
|
+
log 'Starting render pipeline...'
|
|
81
|
+
|
|
82
|
+
# Detect diagram type
|
|
83
|
+
diagram_type = detect_diagram_type(mermaid_source)
|
|
84
|
+
log "Detected diagram type: #{diagram_type}"
|
|
85
|
+
|
|
86
|
+
# Retrieve handlers
|
|
87
|
+
handlers = retrieve_handlers(diagram_type)
|
|
88
|
+
log "Retrieved handlers for #{diagram_type}"
|
|
89
|
+
|
|
90
|
+
# Execute pipeline
|
|
91
|
+
diagram = parse_diagram(mermaid_source, handlers[:parser])
|
|
92
|
+
graph = transform_diagram(diagram, handlers[:transform])
|
|
93
|
+
laid_out_graph = layout_graph(graph)
|
|
94
|
+
svg_document = render_svg(laid_out_graph, handlers[:renderer], theme)
|
|
95
|
+
|
|
96
|
+
# Return XML string
|
|
97
|
+
svg_xml = svg_document.to_xml
|
|
98
|
+
log "Render complete, #{svg_xml.length} bytes"
|
|
99
|
+
|
|
100
|
+
svg_xml
|
|
101
|
+
rescue DiagramTypeError
|
|
102
|
+
# Re-raise diagram type errors without wrapping
|
|
103
|
+
raise
|
|
104
|
+
rescue StandardError => e
|
|
105
|
+
raise PipelineError,
|
|
106
|
+
"Rendering failed: #{e.message}\n#{e.backtrace.join("\n")}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
# Detects diagram type from source code syntax.
|
|
112
|
+
#
|
|
113
|
+
# @param source [String] Mermaid source code
|
|
114
|
+
# @return [Symbol] diagram type identifier
|
|
115
|
+
# @raise [DiagramTypeError] if type cannot be detected
|
|
116
|
+
def detect_diagram_type(source)
|
|
117
|
+
DIAGRAM_TYPE_PATTERNS.each do |type, pattern|
|
|
118
|
+
return type if source.match?(pattern)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
raise DiagramTypeError,
|
|
122
|
+
'Unable to detect diagram type from source. ' \
|
|
123
|
+
'Source must start with one of: graph, flowchart, ' \
|
|
124
|
+
'sequenceDiagram, classDiagram, stateDiagram, ' \
|
|
125
|
+
'erDiagram, journey, gantt, or pie'
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Retrieves handlers for a diagram type.
|
|
129
|
+
#
|
|
130
|
+
# @param type [Symbol] diagram type identifier
|
|
131
|
+
# @return [Hash] hash with :parser, :transform, :renderer keys
|
|
132
|
+
# @raise [DiagramTypeError] if type is not registered
|
|
133
|
+
def retrieve_handlers(type)
|
|
134
|
+
handlers = DiagramRegistry.get(type)
|
|
135
|
+
|
|
136
|
+
unless handlers
|
|
137
|
+
raise DiagramTypeError,
|
|
138
|
+
"No handlers registered for diagram type: #{type}"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
handlers
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Parses source code into diagram model.
|
|
145
|
+
#
|
|
146
|
+
# @param source [String] Mermaid source code
|
|
147
|
+
# @param parser_class [Class] parser class
|
|
148
|
+
# @return [Diagram::Base] parsed diagram model
|
|
149
|
+
def parse_diagram(source, parser_class)
|
|
150
|
+
log 'Parsing diagram...'
|
|
151
|
+
parser = parser_class.new
|
|
152
|
+
diagram = parser.parse(source)
|
|
153
|
+
log "Parse complete: #{diagram.class.name}"
|
|
154
|
+
diagram
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Transforms diagram model to graph structure.
|
|
158
|
+
#
|
|
159
|
+
# @param diagram [Diagram::Base] diagram model
|
|
160
|
+
# @param transform_class [Class] transform class
|
|
161
|
+
# @return [Object] graph structure
|
|
162
|
+
def transform_diagram(diagram, transform_class)
|
|
163
|
+
log 'Transforming diagram to graph...'
|
|
164
|
+
transform = transform_class.new
|
|
165
|
+
graph = transform.to_graph(diagram)
|
|
166
|
+
log 'Transform complete'
|
|
167
|
+
graph
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Computes layout for graph.
|
|
171
|
+
#
|
|
172
|
+
# Currently uses a simple fallback layout since elkrb may not be
|
|
173
|
+
# available. In the future, this will attempt to use elkrb for
|
|
174
|
+
# proper graph layout computation.
|
|
175
|
+
#
|
|
176
|
+
# @param graph [Object] graph structure
|
|
177
|
+
# @return [Object] graph with computed positions
|
|
178
|
+
def layout_graph(graph)
|
|
179
|
+
log 'Computing layout...'
|
|
180
|
+
|
|
181
|
+
# TODO: Attempt to use elkrb when available
|
|
182
|
+
# For now, use simple fallback positioning
|
|
183
|
+
apply_fallback_layout(graph)
|
|
184
|
+
|
|
185
|
+
log 'Layout complete (using fallback positioning)'
|
|
186
|
+
graph
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Applies simple grid-based fallback layout.
|
|
190
|
+
#
|
|
191
|
+
# This is a placeholder for actual elkrb layout. It arranges
|
|
192
|
+
# nodes in a simple grid pattern. Handles both object-based
|
|
193
|
+
# and hash-based graph structures.
|
|
194
|
+
#
|
|
195
|
+
# @param graph [Object, Hash] graph structure
|
|
196
|
+
# @return [Object, Hash] graph with positions
|
|
197
|
+
def apply_fallback_layout(graph)
|
|
198
|
+
# Handle hash-based graph structure (elkrb-compatible)
|
|
199
|
+
if graph.is_a?(Hash) && graph[:children]
|
|
200
|
+
apply_fallback_layout_to_hash(graph)
|
|
201
|
+
# Handle object-based graph structure
|
|
202
|
+
elsif graph.respond_to?(:nodes)
|
|
203
|
+
nodes = graph.nodes
|
|
204
|
+
nodes.each_with_index do |node, index|
|
|
205
|
+
next unless node.respond_to?(:x=) && node.respond_to?(:y=)
|
|
206
|
+
|
|
207
|
+
# Simple grid layout: 3 columns
|
|
208
|
+
col = index % 3
|
|
209
|
+
row = index / 3
|
|
210
|
+
|
|
211
|
+
node.x = 50 + (col * 200)
|
|
212
|
+
node.y = 50 + (row * 150)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
graph
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Applies fallback layout to hash-based graph structure.
|
|
220
|
+
#
|
|
221
|
+
# @param graph [Hash] graph hash with :children
|
|
222
|
+
# @return [Hash] graph with positions added
|
|
223
|
+
def apply_fallback_layout_to_hash(graph)
|
|
224
|
+
children = graph[:children] || []
|
|
225
|
+
|
|
226
|
+
# Apply layout to immediate children
|
|
227
|
+
children.each_with_index do |child, index|
|
|
228
|
+
# Skip if already has position
|
|
229
|
+
next if child[:x] && child[:y]
|
|
230
|
+
|
|
231
|
+
# Simple grid layout: 3 columns
|
|
232
|
+
col = index % 3
|
|
233
|
+
row = index / 3
|
|
234
|
+
|
|
235
|
+
child[:x] = 50 + (col * 250)
|
|
236
|
+
child[:y] = 50 + (row * 200)
|
|
237
|
+
|
|
238
|
+
# Recursively apply to nested children
|
|
239
|
+
apply_fallback_layout_to_hash(child) if child[:children]
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
graph
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Renders graph to SVG document.
|
|
246
|
+
#
|
|
247
|
+
# @param graph [Object] laid-out graph
|
|
248
|
+
# @param renderer_class [Class] renderer class
|
|
249
|
+
# @param theme [Theme] theme to use for rendering
|
|
250
|
+
# @return [Svg::Document] SVG document
|
|
251
|
+
def render_svg(graph, renderer_class, theme)
|
|
252
|
+
log 'Rendering to SVG...'
|
|
253
|
+
renderer = renderer_class.new(theme: theme)
|
|
254
|
+
svg = renderer.render(graph)
|
|
255
|
+
log 'SVG render complete'
|
|
256
|
+
svg
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Loads a theme from various specifications.
|
|
260
|
+
#
|
|
261
|
+
# @param theme_spec [String, Theme, Hash, nil] theme specification
|
|
262
|
+
# @return [Theme] loaded theme
|
|
263
|
+
def load_theme(theme_spec)
|
|
264
|
+
return Theme::Registry.get(:default) if theme_spec.nil?
|
|
265
|
+
|
|
266
|
+
case theme_spec
|
|
267
|
+
when String
|
|
268
|
+
# Could be theme name or path to file
|
|
269
|
+
if File.exist?(theme_spec)
|
|
270
|
+
Theme.load(theme_spec)
|
|
271
|
+
else
|
|
272
|
+
Theme::Registry.get(theme_spec.to_sym) ||
|
|
273
|
+
Theme::Registry.get(:default)
|
|
274
|
+
end
|
|
275
|
+
when Theme
|
|
276
|
+
theme_spec
|
|
277
|
+
when Hash
|
|
278
|
+
Theme.new(**theme_spec)
|
|
279
|
+
else
|
|
280
|
+
Theme::Registry.get(:default)
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Logs a message if verbose mode is enabled.
|
|
285
|
+
#
|
|
286
|
+
# @param message [String] message to log
|
|
287
|
+
# @return [void]
|
|
288
|
+
def log(message)
|
|
289
|
+
puts "[Sirena::Engine] #{message}" if verbose
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|