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,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "grammars/xy_chart"
|
|
5
|
+
require_relative "transforms/xy_chart"
|
|
6
|
+
require_relative "../diagram/xy_chart"
|
|
7
|
+
|
|
8
|
+
module Sirena
|
|
9
|
+
module Parser
|
|
10
|
+
# XY Chart parser for Mermaid xychart-beta diagram syntax.
|
|
11
|
+
#
|
|
12
|
+
# Uses Parslet grammar-based parsing to handle XY chart syntax
|
|
13
|
+
# with axes and multiple datasets.
|
|
14
|
+
#
|
|
15
|
+
# Parses XY charts with support for:
|
|
16
|
+
# - Title
|
|
17
|
+
# - X-axis with categorical or numeric values
|
|
18
|
+
# - Y-axis with range
|
|
19
|
+
# - Multiple datasets (line, bar, or named)
|
|
20
|
+
#
|
|
21
|
+
# @example Parse a simple XY chart
|
|
22
|
+
# parser = XYChartParser.new
|
|
23
|
+
# source = <<~MERMAID
|
|
24
|
+
# xychart-beta
|
|
25
|
+
# title "Sales Revenue"
|
|
26
|
+
# x-axis [jan, feb, mar]
|
|
27
|
+
# y-axis 0 --> 100
|
|
28
|
+
# line [5, 10, 15]
|
|
29
|
+
# MERMAID
|
|
30
|
+
# diagram = parser.parse(source)
|
|
31
|
+
class XYChartParser < Base
|
|
32
|
+
# Parses XY chart diagram source into an XYChart model.
|
|
33
|
+
#
|
|
34
|
+
# @param source [String] the Mermaid XY chart diagram source
|
|
35
|
+
# @return [Diagram::XYChart] the parsed XY chart
|
|
36
|
+
# @raise [ParseError] if syntax is invalid
|
|
37
|
+
def parse(source)
|
|
38
|
+
grammar = Grammars::XYChart.new
|
|
39
|
+
|
|
40
|
+
begin
|
|
41
|
+
parse_tree = grammar.parse(source)
|
|
42
|
+
rescue Parslet::ParseFailed => e
|
|
43
|
+
raise ParseError, "Syntax error at #{e.parse_failure_cause.pos}: " \
|
|
44
|
+
"#{e.parse_failure_cause}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Transform parse tree to intermediate representation
|
|
48
|
+
transform = Transforms::XYChart.new
|
|
49
|
+
result = transform.apply(parse_tree)
|
|
50
|
+
|
|
51
|
+
# Create the diagram model
|
|
52
|
+
create_diagram(result)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def create_diagram(result)
|
|
58
|
+
diagram = Diagram::XYChart.new
|
|
59
|
+
diagram.title = result[:title]
|
|
60
|
+
|
|
61
|
+
# Create X-axis
|
|
62
|
+
if result[:x_axis]
|
|
63
|
+
diagram.x_axis = create_x_axis(result[:x_axis])
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Create Y-axis
|
|
67
|
+
if result[:y_axis]
|
|
68
|
+
diagram.y_axis = create_y_axis(result[:y_axis])
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Create datasets
|
|
72
|
+
result[:datasets].each_with_index do |dataset_data, idx|
|
|
73
|
+
dataset = Diagram::XYDataset.new(
|
|
74
|
+
"dataset_#{idx}",
|
|
75
|
+
dataset_data[:label],
|
|
76
|
+
dataset_data[:chart_type]
|
|
77
|
+
)
|
|
78
|
+
dataset.values = dataset_data[:values]
|
|
79
|
+
diagram.add_dataset(dataset)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
diagram
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def create_x_axis(axis_data)
|
|
86
|
+
axis = Diagram::XYAxis.new
|
|
87
|
+
axis.label = axis_data[:label]
|
|
88
|
+
|
|
89
|
+
# Extract values and determine if categorical or numeric
|
|
90
|
+
values = axis_data[:values]
|
|
91
|
+
|
|
92
|
+
# Check if all values are numeric
|
|
93
|
+
if values.all? { |v| v.is_a?(Numeric) }
|
|
94
|
+
axis.type = :numeric
|
|
95
|
+
axis.values = values
|
|
96
|
+
else
|
|
97
|
+
axis.type = :categorical
|
|
98
|
+
axis.values = values.map(&:to_s)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
axis
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def create_y_axis(axis_data)
|
|
105
|
+
axis = Diagram::XYAxis.new
|
|
106
|
+
axis.label = axis_data[:label]
|
|
107
|
+
axis.type = :numeric
|
|
108
|
+
axis.min = axis_data[:min]
|
|
109
|
+
axis.max = axis_data[:max]
|
|
110
|
+
axis
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Sirena
|
|
6
|
+
module Renderer
|
|
7
|
+
# Architecture diagram renderer for converting positioned layouts to SVG
|
|
8
|
+
class ArchitectureRenderer < Base
|
|
9
|
+
# Icon mappings for common service types
|
|
10
|
+
ICON_GLYPHS = {
|
|
11
|
+
"database" => "⬢",
|
|
12
|
+
"server" => "▦",
|
|
13
|
+
"disk" => "◎",
|
|
14
|
+
"cloud" => "☁",
|
|
15
|
+
"internet" => "◈",
|
|
16
|
+
"browser" => "⊞",
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
# Renders a positioned layout to SVG
|
|
20
|
+
#
|
|
21
|
+
# @param layout [Hash] positioned layout with service positions
|
|
22
|
+
# @return [Svg::Document] the rendered SVG document
|
|
23
|
+
def render(layout)
|
|
24
|
+
svg = create_document_from_layout(layout)
|
|
25
|
+
|
|
26
|
+
# Render groups first (as backgrounds)
|
|
27
|
+
render_groups(layout, svg) if layout[:groups]
|
|
28
|
+
|
|
29
|
+
# Render edges (connections)
|
|
30
|
+
render_edges(layout, svg) if layout[:edges]
|
|
31
|
+
|
|
32
|
+
# Render services on top
|
|
33
|
+
render_services(layout, svg) if layout[:services]
|
|
34
|
+
|
|
35
|
+
svg
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
protected
|
|
39
|
+
|
|
40
|
+
def create_document_from_layout(layout)
|
|
41
|
+
width = layout[:width] || 800
|
|
42
|
+
height = layout[:height] || 600
|
|
43
|
+
|
|
44
|
+
Svg::Document.new(width: width, height: height)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def render_groups(layout, svg)
|
|
48
|
+
# Render groups in order (parent groups before children)
|
|
49
|
+
layout[:groups].each do |group_id, group_info|
|
|
50
|
+
render_group(group_info, svg)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def render_group(group_info, svg)
|
|
55
|
+
group = group_info[:group]
|
|
56
|
+
x = group_info[:x]
|
|
57
|
+
y = group_info[:y]
|
|
58
|
+
width = group_info[:width]
|
|
59
|
+
height = group_info[:height]
|
|
60
|
+
|
|
61
|
+
# Create group element
|
|
62
|
+
g = Svg::Group.new.tap do |elem|
|
|
63
|
+
elem.id = "group-#{group.id}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Draw group boundary
|
|
67
|
+
boundary = Svg::Rect.new.tap do |rect|
|
|
68
|
+
rect.x = x
|
|
69
|
+
rect.y = y
|
|
70
|
+
rect.width = width
|
|
71
|
+
rect.height = height
|
|
72
|
+
rect.fill = theme_color(:group_background) || "#f0f0f0"
|
|
73
|
+
rect.fill_opacity = "0.3"
|
|
74
|
+
rect.stroke = theme_color(:border_color) || "#999"
|
|
75
|
+
rect.stroke_width = "2"
|
|
76
|
+
rect.stroke_dasharray = "5,5"
|
|
77
|
+
rect.rx = "8"
|
|
78
|
+
rect.ry = "8"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
g.children << boundary
|
|
82
|
+
|
|
83
|
+
# Draw group label
|
|
84
|
+
if group.label
|
|
85
|
+
label = Svg::Text.new.tap do |text|
|
|
86
|
+
text.x = x + 10
|
|
87
|
+
text.y = y + 20
|
|
88
|
+
text.content = group.label
|
|
89
|
+
text.font_size = "14"
|
|
90
|
+
text.font_weight = "bold"
|
|
91
|
+
text.fill = theme_color(:text_color) || "#333"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
g.children << label
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Draw group icon if present
|
|
98
|
+
if group.icon
|
|
99
|
+
icon_text = Svg::Text.new.tap do |text|
|
|
100
|
+
text.x = x + width - 30
|
|
101
|
+
text.y = y + 25
|
|
102
|
+
text.content = icon_glyph(group.icon)
|
|
103
|
+
text.font_size = "20"
|
|
104
|
+
text.fill = theme_color(:text_color) || "#666"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
g.children << icon_text
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
svg << g
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def render_services(layout, svg)
|
|
114
|
+
layout[:services].each do |service_id, service_info|
|
|
115
|
+
render_service(service_info, svg)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def render_service(service_info, svg)
|
|
120
|
+
service = service_info[:service]
|
|
121
|
+
x = service_info[:x]
|
|
122
|
+
y = service_info[:y]
|
|
123
|
+
width = service_info[:width]
|
|
124
|
+
height = service_info[:height]
|
|
125
|
+
|
|
126
|
+
# Create service group
|
|
127
|
+
g = Svg::Group.new.tap do |elem|
|
|
128
|
+
elem.id = "service-#{service.id}"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Draw service box
|
|
132
|
+
box = Svg::Rect.new.tap do |rect|
|
|
133
|
+
rect.x = x
|
|
134
|
+
rect.y = y
|
|
135
|
+
rect.width = width
|
|
136
|
+
rect.height = height
|
|
137
|
+
apply_theme_to_node(rect)
|
|
138
|
+
rect.rx = "5"
|
|
139
|
+
rect.ry = "5"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
g.children << box
|
|
143
|
+
|
|
144
|
+
# Draw service icon
|
|
145
|
+
if service.icon
|
|
146
|
+
icon = Svg::Text.new.tap do |text|
|
|
147
|
+
text.x = x + width / 2
|
|
148
|
+
text.y = y + height / 3
|
|
149
|
+
text.content = icon_glyph(service.icon)
|
|
150
|
+
text.font_size = "24"
|
|
151
|
+
text.text_anchor = "middle"
|
|
152
|
+
text.dominant_baseline = "middle"
|
|
153
|
+
text.fill = theme_color(:text_color) || "#666"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
g.children << icon
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Draw service label
|
|
160
|
+
if service.label
|
|
161
|
+
label = Svg::Text.new.tap do |text|
|
|
162
|
+
text.x = x + width / 2
|
|
163
|
+
text.y = y + height * 2 / 3
|
|
164
|
+
text.content = service.label
|
|
165
|
+
apply_theme_to_text(text)
|
|
166
|
+
text.text_anchor = "middle"
|
|
167
|
+
text.dominant_baseline = "middle"
|
|
168
|
+
text.font_size = "12"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
g.children << label
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
svg << g
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def render_edges(layout, svg)
|
|
178
|
+
layout[:edges].each do |edge_info|
|
|
179
|
+
render_edge(edge_info, svg)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def render_edge(edge_info, svg)
|
|
184
|
+
edge = edge_info[:edge]
|
|
185
|
+
from_x = edge_info[:from_x]
|
|
186
|
+
from_y = edge_info[:from_y]
|
|
187
|
+
to_x = edge_info[:to_x]
|
|
188
|
+
to_y = edge_info[:to_y]
|
|
189
|
+
|
|
190
|
+
# Create edge group
|
|
191
|
+
g = Svg::Group.new.tap do |elem|
|
|
192
|
+
elem.id = "edge-#{edge.from_id}-#{edge.to_id}"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Calculate path
|
|
196
|
+
path_data = calculate_edge_path(from_x, from_y, to_x, to_y)
|
|
197
|
+
|
|
198
|
+
# Draw connection line
|
|
199
|
+
path = Svg::Path.new.tap do |p|
|
|
200
|
+
p.d = path_data
|
|
201
|
+
p.fill = "none"
|
|
202
|
+
apply_theme_to_edge(p)
|
|
203
|
+
p.marker_end = "url(#arrowhead)"
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
g.children << path
|
|
207
|
+
|
|
208
|
+
# Draw edge label if present
|
|
209
|
+
if edge.label && !edge.label.empty?
|
|
210
|
+
mid_x = (from_x + to_x) / 2
|
|
211
|
+
mid_y = (from_y + to_y) / 2
|
|
212
|
+
|
|
213
|
+
label = Svg::Text.new.tap do |text|
|
|
214
|
+
text.x = mid_x
|
|
215
|
+
text.y = mid_y - 5
|
|
216
|
+
text.content = edge.label
|
|
217
|
+
text.font_size = "10"
|
|
218
|
+
text.text_anchor = "middle"
|
|
219
|
+
text.fill = theme_color(:text_color) || "#666"
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
g.children << label
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
svg << g
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def calculate_edge_path(from_x, from_y, to_x, to_y)
|
|
229
|
+
# Use simple straight line for now
|
|
230
|
+
# Could be enhanced with bezier curves or orthogonal routing
|
|
231
|
+
"M #{from_x} #{from_y} L #{to_x} #{to_y}"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def icon_glyph(icon_name)
|
|
235
|
+
# Extract base icon name (remove prefixes like "logos:" or "fa:")
|
|
236
|
+
base_name = icon_name.split(":").last
|
|
237
|
+
|
|
238
|
+
# Map to glyph or use first letter as fallback
|
|
239
|
+
ICON_GLYPHS[base_name] || base_name[0].upcase
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def calculate_width(layout)
|
|
243
|
+
layout[:width] || 800
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def calculate_height(layout)
|
|
247
|
+
layout[:height] || 600
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sirena
|
|
4
|
+
module Renderer
|
|
5
|
+
# Abstract base class for diagram renderers.
|
|
6
|
+
#
|
|
7
|
+
# Renderers convert laid-out graph structures (with computed positions)
|
|
8
|
+
# into SVG documents using the Svg builder classes. Each diagram type has
|
|
9
|
+
# its own renderer that maps graph elements to appropriate SVG shapes.
|
|
10
|
+
#
|
|
11
|
+
# The renderer implements the template method pattern, defining the
|
|
12
|
+
# overall rendering pipeline while allowing subclasses to customize
|
|
13
|
+
# specific rendering steps.
|
|
14
|
+
#
|
|
15
|
+
# @example Define a custom renderer
|
|
16
|
+
# class FlowchartRenderer < Renderer::Base
|
|
17
|
+
# def render(graph)
|
|
18
|
+
# svg = create_document(graph)
|
|
19
|
+
# render_nodes(graph, svg)
|
|
20
|
+
# render_edges(graph, svg)
|
|
21
|
+
# svg
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# protected
|
|
25
|
+
#
|
|
26
|
+
# def render_nodes(graph, svg)
|
|
27
|
+
# # Render graph nodes as SVG shapes
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# def render_edges(graph, svg)
|
|
31
|
+
# # Render graph edges as SVG paths
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# @abstract Subclass and implement rendering methods
|
|
36
|
+
class Base
|
|
37
|
+
attr_accessor :theme
|
|
38
|
+
|
|
39
|
+
# Creates a new renderer instance.
|
|
40
|
+
#
|
|
41
|
+
# @param theme [Theme, nil] theme to use for rendering
|
|
42
|
+
def initialize(theme: nil)
|
|
43
|
+
@theme = theme || Theme::Registry.get(:default)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Renders a laid-out graph to an SVG document.
|
|
47
|
+
#
|
|
48
|
+
# This method should be overridden by subclasses to implement
|
|
49
|
+
# diagram-specific rendering logic.
|
|
50
|
+
#
|
|
51
|
+
# @param graph [Object] the laid-out elkrb graph with positions
|
|
52
|
+
# @return [Svg::Document] the rendered SVG document
|
|
53
|
+
# @raise [NotImplementedError] if not implemented by subclass
|
|
54
|
+
def render(graph)
|
|
55
|
+
raise NotImplementedError,
|
|
56
|
+
"#{self.class} must implement #render(graph)"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
protected
|
|
60
|
+
|
|
61
|
+
# Creates an SVG document with appropriate dimensions.
|
|
62
|
+
#
|
|
63
|
+
# @param graph [Object] the graph to get dimensions from
|
|
64
|
+
# @param padding [Numeric] padding around the diagram
|
|
65
|
+
# @return [Svg::Document] new SVG document
|
|
66
|
+
def create_document(graph, padding: 20)
|
|
67
|
+
width = calculate_width(graph) + (padding * 2)
|
|
68
|
+
height = calculate_height(graph) + (padding * 2)
|
|
69
|
+
|
|
70
|
+
Svg::Document.new.tap do |doc|
|
|
71
|
+
doc.width = width
|
|
72
|
+
doc.height = height
|
|
73
|
+
doc.view_box = "0 0 #{width} #{height}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Calculates the total width needed for the diagram.
|
|
78
|
+
#
|
|
79
|
+
# Subclasses should override this to compute width from graph.
|
|
80
|
+
#
|
|
81
|
+
# @param graph [Object] the graph
|
|
82
|
+
# @return [Numeric] the width in pixels
|
|
83
|
+
def calculate_width(_graph)
|
|
84
|
+
800 # Default width
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Calculates the total height needed for the diagram.
|
|
88
|
+
#
|
|
89
|
+
# Subclasses should override this to compute height from graph.
|
|
90
|
+
#
|
|
91
|
+
# @param graph [Object] the graph
|
|
92
|
+
# @return [Numeric] the height in pixels
|
|
93
|
+
def calculate_height(_graph)
|
|
94
|
+
600 # Default height
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Helper methods for accessing theme properties
|
|
98
|
+
|
|
99
|
+
# Gets a color property from the theme.
|
|
100
|
+
#
|
|
101
|
+
# @param property [Symbol] property name
|
|
102
|
+
# @return [String, nil] color value
|
|
103
|
+
def theme_color(property)
|
|
104
|
+
theme&.colors&.send(property)
|
|
105
|
+
rescue NoMethodError
|
|
106
|
+
nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Gets a typography property from the theme.
|
|
110
|
+
#
|
|
111
|
+
# @param property [Symbol] property name
|
|
112
|
+
# @return [String, Float, nil] typography value
|
|
113
|
+
def theme_typography(property)
|
|
114
|
+
theme&.typography&.send(property)
|
|
115
|
+
rescue NoMethodError
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Gets a shape property from the theme.
|
|
120
|
+
#
|
|
121
|
+
# @param property [Symbol] property name
|
|
122
|
+
# @return [Float, String, nil] shape value
|
|
123
|
+
def theme_shape(property)
|
|
124
|
+
theme&.shapes&.send(property)
|
|
125
|
+
rescue NoMethodError
|
|
126
|
+
nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Gets a spacing property from the theme.
|
|
130
|
+
#
|
|
131
|
+
# @param property [Symbol] property name
|
|
132
|
+
# @return [Float, nil] spacing value
|
|
133
|
+
def theme_spacing(property)
|
|
134
|
+
theme&.spacing&.send(property)
|
|
135
|
+
rescue NoMethodError
|
|
136
|
+
nil
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Gets an effect property from the theme.
|
|
140
|
+
#
|
|
141
|
+
# @param property [Symbol] property name
|
|
142
|
+
# @return [Boolean, Float, String, nil] effect value
|
|
143
|
+
def theme_effect(property)
|
|
144
|
+
theme&.effects&.send(property)
|
|
145
|
+
rescue NoMethodError
|
|
146
|
+
nil
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Applies theme styles to a node element.
|
|
150
|
+
#
|
|
151
|
+
# @param element [Svg::Element] SVG element
|
|
152
|
+
# @return [void]
|
|
153
|
+
def apply_theme_to_node(element)
|
|
154
|
+
element.fill = theme_color(:node_fill) if theme_color(:node_fill)
|
|
155
|
+
element.stroke = theme_color(:node_stroke) if theme_color(:node_stroke)
|
|
156
|
+
if theme_shape(:stroke_width)
|
|
157
|
+
element.stroke_width = theme_shape(:stroke_width).to_s
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Applies theme styles to an edge element.
|
|
162
|
+
#
|
|
163
|
+
# @param element [Svg::Element] SVG element
|
|
164
|
+
# @return [void]
|
|
165
|
+
def apply_theme_to_edge(element)
|
|
166
|
+
element.stroke = theme_color(:edge_stroke) if theme_color(:edge_stroke)
|
|
167
|
+
if theme_shape(:stroke_width)
|
|
168
|
+
element.stroke_width = theme_shape(:stroke_width).to_s
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Applies theme styles to a text element.
|
|
173
|
+
#
|
|
174
|
+
# @param element [Svg::Text] SVG text element
|
|
175
|
+
# @return [void]
|
|
176
|
+
def apply_theme_to_text(element)
|
|
177
|
+
element.fill = theme_color(:label_text) if theme_color(:label_text)
|
|
178
|
+
if theme_typography(:font_family)
|
|
179
|
+
element.font_family = theme_typography(:font_family)
|
|
180
|
+
end
|
|
181
|
+
if theme_typography(:font_size_normal)
|
|
182
|
+
element.font_size = theme_typography(:font_size_normal).to_s
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Creates default style for SVG elements.
|
|
187
|
+
#
|
|
188
|
+
# @param element_type [Symbol] type of element (:node, :edge, :text)
|
|
189
|
+
# @return [Svg::Style] style object
|
|
190
|
+
def default_style(element_type)
|
|
191
|
+
case element_type
|
|
192
|
+
when :node
|
|
193
|
+
Svg::Style.new.tap do |style|
|
|
194
|
+
style.fill = theme_color(:node_fill) || '#ffffff'
|
|
195
|
+
style.stroke = theme_color(:node_stroke) || '#000000'
|
|
196
|
+
style.stroke_width =
|
|
197
|
+
(theme_shape(:stroke_width) || 2).to_s
|
|
198
|
+
end
|
|
199
|
+
when :edge
|
|
200
|
+
Svg::Style.new.tap do |style|
|
|
201
|
+
style.fill = 'none'
|
|
202
|
+
style.stroke = theme_color(:edge_stroke) || '#000000'
|
|
203
|
+
style.stroke_width =
|
|
204
|
+
(theme_shape(:stroke_width) || 2).to_s
|
|
205
|
+
end
|
|
206
|
+
when :text
|
|
207
|
+
Svg::Style.new.tap do |style|
|
|
208
|
+
style.fill = theme_color(:label_text) || '#000000'
|
|
209
|
+
style.font_family =
|
|
210
|
+
theme_typography(:font_family) || 'Arial, sans-serif'
|
|
211
|
+
style.font_size =
|
|
212
|
+
(theme_typography(:font_size_normal) || 14).to_s
|
|
213
|
+
style.text_anchor = 'middle'
|
|
214
|
+
end
|
|
215
|
+
else
|
|
216
|
+
Svg::Style.new
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Creates an SVG path data string from bend points.
|
|
221
|
+
#
|
|
222
|
+
# @param start_point [Hash] hash with :x and :y keys
|
|
223
|
+
# @param end_point [Hash] hash with :x and :y keys
|
|
224
|
+
# @param bend_points [Array<Hash>] array of hashes with :x and :y
|
|
225
|
+
# @return [String] SVG path data string
|
|
226
|
+
def create_path_data(start_point, end_point, bend_points = [])
|
|
227
|
+
points = [start_point] + bend_points + [end_point]
|
|
228
|
+
path_parts = ["M #{points[0][:x]} #{points[0][:y]}"]
|
|
229
|
+
|
|
230
|
+
points[1..].each do |point|
|
|
231
|
+
path_parts << "L #{point[:x]} #{point[:y]}"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
path_parts.join(' ')
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Adds an arrow marker to the SVG document.
|
|
238
|
+
#
|
|
239
|
+
# @param svg [Svg::Document] the SVG document
|
|
240
|
+
# @param id [String] unique identifier for the marker
|
|
241
|
+
# @return [void]
|
|
242
|
+
def add_arrow_marker(svg, id: 'arrow')
|
|
243
|
+
# This is a placeholder for actual marker implementation
|
|
244
|
+
# Subclasses can implement this to add arrow markers
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Error raised during rendering.
|
|
249
|
+
class RenderError < StandardError; end
|
|
250
|
+
end
|
|
251
|
+
end
|