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,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lutaml/model'
|
|
4
|
+
require_relative 'base'
|
|
5
|
+
|
|
6
|
+
module Sirena
|
|
7
|
+
module Diagram
|
|
8
|
+
# Represents a slice in a pie chart.
|
|
9
|
+
#
|
|
10
|
+
# A slice represents a data point with a label and numeric value.
|
|
11
|
+
class PieSlice < Lutaml::Model::Serializable
|
|
12
|
+
# Label/name for this slice
|
|
13
|
+
attribute :label, :string
|
|
14
|
+
|
|
15
|
+
# Numeric value for this slice (can be integer or decimal)
|
|
16
|
+
attribute :value, :float
|
|
17
|
+
|
|
18
|
+
# Validates the slice has required attributes.
|
|
19
|
+
#
|
|
20
|
+
# @return [Boolean] true if slice is valid
|
|
21
|
+
def valid?
|
|
22
|
+
!label.nil? && !label.empty? && !value.nil?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Calculate percentage of total.
|
|
26
|
+
#
|
|
27
|
+
# @param total [Float] the total value of all slices
|
|
28
|
+
# @return [Float] percentage (0-100)
|
|
29
|
+
def percentage(total)
|
|
30
|
+
return 0.0 if total.zero?
|
|
31
|
+
|
|
32
|
+
(value.to_f / total) * 100.0
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Pie chart diagram model.
|
|
37
|
+
#
|
|
38
|
+
# Represents a complete pie chart with labeled slices showing
|
|
39
|
+
# proportional data distribution. Pie charts are useful for showing
|
|
40
|
+
# part-to-whole relationships.
|
|
41
|
+
#
|
|
42
|
+
# @example Creating a simple pie chart
|
|
43
|
+
# pie = Pie.new
|
|
44
|
+
# pie.title = 'Sales Report'
|
|
45
|
+
# pie.slices << PieSlice.new(label: 'Apples', value: 42.5)
|
|
46
|
+
# pie.slices << PieSlice.new(label: 'Oranges', value: 30.2)
|
|
47
|
+
# pie.slices << PieSlice.new(label: 'Bananas', value: 27.3)
|
|
48
|
+
class Pie < Base
|
|
49
|
+
# Collection of data slices in the pie chart
|
|
50
|
+
attribute :slices, PieSlice, collection: true, default: -> { [] }
|
|
51
|
+
|
|
52
|
+
# Whether to show data values on the chart
|
|
53
|
+
attribute :show_data, :boolean, default: -> { false }
|
|
54
|
+
|
|
55
|
+
# Accessibility title (for screen readers)
|
|
56
|
+
attribute :acc_title, :string
|
|
57
|
+
|
|
58
|
+
# Accessibility description (for screen readers)
|
|
59
|
+
attribute :acc_description, :string
|
|
60
|
+
|
|
61
|
+
# Returns the diagram type identifier.
|
|
62
|
+
#
|
|
63
|
+
# @return [Symbol] :pie
|
|
64
|
+
def diagram_type
|
|
65
|
+
:pie
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Validates the pie chart structure.
|
|
69
|
+
#
|
|
70
|
+
# A pie chart is valid if:
|
|
71
|
+
# - All slices (if any) are valid
|
|
72
|
+
# - All slice values are numeric
|
|
73
|
+
#
|
|
74
|
+
# Note: Empty pie charts are allowed for parsing tests
|
|
75
|
+
#
|
|
76
|
+
# @return [Boolean] true if pie chart is valid
|
|
77
|
+
def valid?
|
|
78
|
+
return true if slices.nil? || slices.empty?
|
|
79
|
+
|
|
80
|
+
slices.all?(&:valid?)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Calculate total value of all slices.
|
|
84
|
+
#
|
|
85
|
+
# @return [Float] sum of all slice values
|
|
86
|
+
def total_value
|
|
87
|
+
slices.sum(&:value)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Get slices with their calculated percentages.
|
|
91
|
+
#
|
|
92
|
+
# @return [Array<Hash>] array of hashes with :slice and :percentage
|
|
93
|
+
def slices_with_percentages
|
|
94
|
+
total = total_value
|
|
95
|
+
slices.map do |slice|
|
|
96
|
+
{
|
|
97
|
+
slice: slice,
|
|
98
|
+
percentage: slice.percentage(total)
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Calculate angle in degrees for a slice.
|
|
104
|
+
#
|
|
105
|
+
# @param slice [PieSlice] the slice to calculate angle for
|
|
106
|
+
# @return [Float] angle in degrees (0-360)
|
|
107
|
+
def slice_angle(slice)
|
|
108
|
+
total = total_value
|
|
109
|
+
return 0.0 if total.zero?
|
|
110
|
+
|
|
111
|
+
(slice.value / total) * 360.0
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lutaml/model'
|
|
4
|
+
require_relative 'base'
|
|
5
|
+
|
|
6
|
+
module Sirena
|
|
7
|
+
module Diagram
|
|
8
|
+
# Represents a data point in a quadrant chart.
|
|
9
|
+
#
|
|
10
|
+
# A point has a label, normalized x/y coordinates (0-1 range),
|
|
11
|
+
# and optional styling parameters.
|
|
12
|
+
class QuadrantPoint < Lutaml::Model::Serializable
|
|
13
|
+
# Label/name for this point
|
|
14
|
+
attribute :label, :string
|
|
15
|
+
|
|
16
|
+
# X coordinate (normalized 0-1)
|
|
17
|
+
attribute :x, :float
|
|
18
|
+
|
|
19
|
+
# Y coordinate (normalized 0-1)
|
|
20
|
+
attribute :y, :float
|
|
21
|
+
|
|
22
|
+
# Optional radius for the point
|
|
23
|
+
attribute :radius, :float
|
|
24
|
+
|
|
25
|
+
# Optional fill color
|
|
26
|
+
attribute :color, :string
|
|
27
|
+
|
|
28
|
+
# Optional stroke color
|
|
29
|
+
attribute :stroke_color, :string
|
|
30
|
+
|
|
31
|
+
# Optional stroke width
|
|
32
|
+
attribute :stroke_width, :float
|
|
33
|
+
|
|
34
|
+
# Validates the point has required attributes.
|
|
35
|
+
#
|
|
36
|
+
# @return [Boolean] true if point is valid
|
|
37
|
+
def valid?
|
|
38
|
+
!label.nil? && !label.empty? &&
|
|
39
|
+
!x.nil? && !y.nil? &&
|
|
40
|
+
x >= 0.0 && x <= 1.0 &&
|
|
41
|
+
y >= 0.0 && y <= 1.0
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Determine which quadrant this point is in (1-4).
|
|
45
|
+
#
|
|
46
|
+
# Quadrants are numbered:
|
|
47
|
+
# - Quadrant 1: top-right (x >= 0.5, y >= 0.5)
|
|
48
|
+
# - Quadrant 2: top-left (x < 0.5, y >= 0.5)
|
|
49
|
+
# - Quadrant 3: bottom-left (x < 0.5, y < 0.5)
|
|
50
|
+
# - Quadrant 4: bottom-right (x >= 0.5, y < 0.5)
|
|
51
|
+
#
|
|
52
|
+
# @return [Integer] quadrant number (1-4)
|
|
53
|
+
def quadrant
|
|
54
|
+
if x >= 0.5 && y >= 0.5
|
|
55
|
+
1
|
|
56
|
+
elsif x < 0.5 && y >= 0.5
|
|
57
|
+
2
|
|
58
|
+
elsif x < 0.5 && y < 0.5
|
|
59
|
+
3
|
|
60
|
+
else
|
|
61
|
+
4
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Quadrant chart diagram model.
|
|
67
|
+
#
|
|
68
|
+
# Represents a 2x2 quadrant chart for visualizing data points
|
|
69
|
+
# in a two-dimensional space. Useful for categorizing items
|
|
70
|
+
# based on two independent variables.
|
|
71
|
+
#
|
|
72
|
+
# @example Creating a simple quadrant chart
|
|
73
|
+
# chart = QuadrantChart.new
|
|
74
|
+
# chart.title = 'Product Analysis'
|
|
75
|
+
# chart.x_axis_left = 'Low Cost'
|
|
76
|
+
# chart.x_axis_right = 'High Cost'
|
|
77
|
+
# chart.y_axis_bottom = 'Low Value'
|
|
78
|
+
# chart.y_axis_top = 'High Value'
|
|
79
|
+
# chart.quadrant_1_label = 'Invest'
|
|
80
|
+
# chart.points << QuadrantPoint.new(label: 'Product A', x: 0.3, y: 0.7)
|
|
81
|
+
class QuadrantChart < Base
|
|
82
|
+
# X-axis label for the left side
|
|
83
|
+
attribute :x_axis_left, :string
|
|
84
|
+
|
|
85
|
+
# X-axis label for the right side
|
|
86
|
+
attribute :x_axis_right, :string
|
|
87
|
+
|
|
88
|
+
# Y-axis label for the bottom
|
|
89
|
+
attribute :y_axis_bottom, :string
|
|
90
|
+
|
|
91
|
+
# Y-axis label for the top
|
|
92
|
+
attribute :y_axis_top, :string
|
|
93
|
+
|
|
94
|
+
# Label for quadrant 1 (top-right)
|
|
95
|
+
attribute :quadrant_1_label, :string
|
|
96
|
+
|
|
97
|
+
# Label for quadrant 2 (top-left)
|
|
98
|
+
attribute :quadrant_2_label, :string
|
|
99
|
+
|
|
100
|
+
# Label for quadrant 3 (bottom-left)
|
|
101
|
+
attribute :quadrant_3_label, :string
|
|
102
|
+
|
|
103
|
+
# Label for quadrant 4 (bottom-right)
|
|
104
|
+
attribute :quadrant_4_label, :string
|
|
105
|
+
|
|
106
|
+
# Collection of data points in the chart
|
|
107
|
+
attribute :points, QuadrantPoint, collection: true, default: -> { [] }
|
|
108
|
+
|
|
109
|
+
# Returns the diagram type identifier.
|
|
110
|
+
#
|
|
111
|
+
# @return [Symbol] :quadrant
|
|
112
|
+
def diagram_type
|
|
113
|
+
:quadrant
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Validates the quadrant chart structure.
|
|
117
|
+
#
|
|
118
|
+
# A quadrant chart is valid if:
|
|
119
|
+
# - All points (if any) are valid
|
|
120
|
+
#
|
|
121
|
+
# Note: Axis labels are optional for minimal diagrams
|
|
122
|
+
#
|
|
123
|
+
# @return [Boolean] true if chart is valid
|
|
124
|
+
def valid?
|
|
125
|
+
return false unless points.all?(&:valid?)
|
|
126
|
+
|
|
127
|
+
true
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Get points grouped by quadrant.
|
|
131
|
+
#
|
|
132
|
+
# @return [Hash<Integer, Array<QuadrantPoint>>] points by quadrant
|
|
133
|
+
def points_by_quadrant
|
|
134
|
+
points.group_by(&:quadrant)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sirena
|
|
4
|
+
module Diagram
|
|
5
|
+
# Represents a radar/spider chart diagram
|
|
6
|
+
class RadarChart < Base
|
|
7
|
+
attr_accessor :title, :acc_title, :acc_descr, :axes, :curves, :options
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
super
|
|
11
|
+
@axes = []
|
|
12
|
+
@curves = []
|
|
13
|
+
@options = {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def type
|
|
17
|
+
:radar
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Represents an axis in a radar chart
|
|
22
|
+
class RadarAxis
|
|
23
|
+
attr_accessor :id, :label
|
|
24
|
+
|
|
25
|
+
def initialize(id, label = nil)
|
|
26
|
+
@id = id
|
|
27
|
+
@label = label || id
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Represents a data curve/dataset in a radar chart
|
|
32
|
+
class RadarCurve
|
|
33
|
+
attr_accessor :id, :label, :values
|
|
34
|
+
|
|
35
|
+
def initialize(id, label = nil)
|
|
36
|
+
@id = id
|
|
37
|
+
@label = label || id
|
|
38
|
+
@values = {}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Add a value for an axis
|
|
42
|
+
def add_value(axis_id, value)
|
|
43
|
+
@values[axis_id] = value.to_f
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Get value for an axis
|
|
47
|
+
def value_for(axis_id)
|
|
48
|
+
@values[axis_id] || 0.0
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Sirena
|
|
6
|
+
module Diagram
|
|
7
|
+
# Represents a requirement in the diagram
|
|
8
|
+
class Requirement < Lutaml::Model::Serializable
|
|
9
|
+
attribute :name, :string
|
|
10
|
+
attribute :type, :string, default: -> { "requirement" }
|
|
11
|
+
attribute :id, :string
|
|
12
|
+
attribute :text, :string
|
|
13
|
+
attribute :risk, :string # Low, Medium, High
|
|
14
|
+
attribute :verifymethod, :string # Analysis, Inspection, Test, Demonstration
|
|
15
|
+
attribute :classes, :string, collection: true, default: -> { [] }
|
|
16
|
+
|
|
17
|
+
def add_class(class_name)
|
|
18
|
+
classes << class_name
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Represents an element in the diagram
|
|
23
|
+
class RequirementElement < Lutaml::Model::Serializable
|
|
24
|
+
attribute :name, :string
|
|
25
|
+
attribute :type, :string
|
|
26
|
+
attribute :docref, :string
|
|
27
|
+
attribute :classes, :string, collection: true, default: -> { [] }
|
|
28
|
+
|
|
29
|
+
def add_class(class_name)
|
|
30
|
+
classes << class_name
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Represents a relationship between requirements/elements
|
|
35
|
+
class RequirementRelationship < Lutaml::Model::Serializable
|
|
36
|
+
attribute :source, :string
|
|
37
|
+
attribute :target, :string
|
|
38
|
+
attribute :type, :string # contains, copies, derives, satisfies, verifies, refines, traces
|
|
39
|
+
|
|
40
|
+
VALID_TYPES = %w[
|
|
41
|
+
contains
|
|
42
|
+
copies
|
|
43
|
+
derives
|
|
44
|
+
satisfies
|
|
45
|
+
verifies
|
|
46
|
+
refines
|
|
47
|
+
traces
|
|
48
|
+
].freeze
|
|
49
|
+
|
|
50
|
+
def valid?
|
|
51
|
+
VALID_TYPES.include?(type)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Represents styling for a requirement or element
|
|
56
|
+
class RequirementStyle < Lutaml::Model::Serializable
|
|
57
|
+
attribute :target_ids, :string, collection: true, default: -> { [] }
|
|
58
|
+
attribute :fill, :string
|
|
59
|
+
attribute :stroke, :string
|
|
60
|
+
attribute :stroke_width, :string
|
|
61
|
+
attribute :properties, :string, collection: true, default: -> { [] }
|
|
62
|
+
|
|
63
|
+
def add_target(id)
|
|
64
|
+
target_ids << id
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def add_property(property)
|
|
68
|
+
properties << property
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Represents a CSS class definition
|
|
73
|
+
class RequirementClass < Lutaml::Model::Serializable
|
|
74
|
+
attribute :name, :string
|
|
75
|
+
attribute :fill, :string
|
|
76
|
+
attribute :stroke, :string
|
|
77
|
+
attribute :stroke_width, :string
|
|
78
|
+
attribute :properties, :string, collection: true, default: -> { [] }
|
|
79
|
+
|
|
80
|
+
def add_property(property)
|
|
81
|
+
properties << property
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Represents a class assignment to requirements/elements
|
|
86
|
+
class RequirementClassAssignment < Lutaml::Model::Serializable
|
|
87
|
+
attribute :target_ids, :string, collection: true, default: -> { [] }
|
|
88
|
+
attribute :class_names, :string, collection: true, default: -> { [] }
|
|
89
|
+
|
|
90
|
+
def add_target(id)
|
|
91
|
+
target_ids << id
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def add_class(class_name)
|
|
95
|
+
class_names << class_name
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Represents a Mermaid requirement diagram
|
|
100
|
+
class RequirementDiagram < Lutaml::Model::Serializable
|
|
101
|
+
attribute :requirements, Requirement, collection: true, default: -> { [] }
|
|
102
|
+
attribute :elements, RequirementElement, collection: true, default: -> { [] }
|
|
103
|
+
attribute :relationships, RequirementRelationship, collection: true, default: -> { [] }
|
|
104
|
+
attribute :styles, RequirementStyle, collection: true, default: -> { [] }
|
|
105
|
+
attribute :classes, RequirementClass, collection: true, default: -> { [] }
|
|
106
|
+
attribute :class_assignments, RequirementClassAssignment, collection: true, default: -> { [] }
|
|
107
|
+
|
|
108
|
+
def add_requirement(requirement)
|
|
109
|
+
requirements << requirement
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def add_element(element)
|
|
113
|
+
elements << element
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def add_relationship(relationship)
|
|
117
|
+
relationships << relationship
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def add_style(style)
|
|
121
|
+
styles << style
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def add_class(klass)
|
|
125
|
+
classes << klass
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def add_class_assignment(assignment)
|
|
129
|
+
class_assignments << assignment
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
require_relative "base"
|
|
5
|
+
|
|
6
|
+
module Sirena
|
|
7
|
+
module Diagram
|
|
8
|
+
# Represents a node in a Sankey diagram.
|
|
9
|
+
#
|
|
10
|
+
# A node can be a source, target, or intermediate point in the flow.
|
|
11
|
+
# Nodes are automatically discovered from flow definitions.
|
|
12
|
+
class SankeyNode < Lutaml::Model::Serializable
|
|
13
|
+
# Unique identifier for the node
|
|
14
|
+
attribute :id, :string
|
|
15
|
+
|
|
16
|
+
# Display label for the node (defaults to id if not set)
|
|
17
|
+
attribute :label, :string
|
|
18
|
+
|
|
19
|
+
def initialize(id = nil, label = nil)
|
|
20
|
+
super()
|
|
21
|
+
@id = id
|
|
22
|
+
@label = label || id
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Validates the node has required attributes.
|
|
26
|
+
#
|
|
27
|
+
# @return [Boolean] true if node is valid
|
|
28
|
+
def valid?
|
|
29
|
+
!id.nil? && !id.empty?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Get display label, falling back to id.
|
|
33
|
+
#
|
|
34
|
+
# @return [String] the label to display
|
|
35
|
+
def display_label
|
|
36
|
+
label && !label.empty? ? label : id
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Represents a flow/link between two nodes in a Sankey diagram.
|
|
41
|
+
#
|
|
42
|
+
# A flow connects a source node to a target node with a value
|
|
43
|
+
# that determines the width of the flow arrow.
|
|
44
|
+
class SankeyFlow < Lutaml::Model::Serializable
|
|
45
|
+
# Source node identifier
|
|
46
|
+
attribute :source, :string
|
|
47
|
+
|
|
48
|
+
# Target node identifier
|
|
49
|
+
attribute :target, :string
|
|
50
|
+
|
|
51
|
+
# Flow value (determines arrow width)
|
|
52
|
+
attribute :value, :float
|
|
53
|
+
|
|
54
|
+
# Optional label for the flow
|
|
55
|
+
attribute :label, :string
|
|
56
|
+
|
|
57
|
+
def initialize(source = nil, target = nil, value = 0.0)
|
|
58
|
+
super()
|
|
59
|
+
@source = source
|
|
60
|
+
@target = target
|
|
61
|
+
@value = value.to_f
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Validates the flow has required attributes.
|
|
65
|
+
#
|
|
66
|
+
# @return [Boolean] true if flow is valid
|
|
67
|
+
def valid?
|
|
68
|
+
!source.nil? && !source.empty? &&
|
|
69
|
+
!target.nil? && !target.empty? &&
|
|
70
|
+
value > 0
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Check if this is a self-loop.
|
|
74
|
+
#
|
|
75
|
+
# @return [Boolean] true if source equals target
|
|
76
|
+
def self_loop?
|
|
77
|
+
source == target
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Sankey diagram model.
|
|
82
|
+
#
|
|
83
|
+
# Represents a Sankey diagram showing flows between nodes where
|
|
84
|
+
# the width of arrows is proportional to flow values. Used for
|
|
85
|
+
# visualizing energy flows, material flows, cost distributions, etc.
|
|
86
|
+
#
|
|
87
|
+
# @example Creating a Sankey diagram
|
|
88
|
+
# sankey = SankeyDiagram.new
|
|
89
|
+
# sankey.title = 'Energy Flow'
|
|
90
|
+
#
|
|
91
|
+
# flow1 = SankeyFlow.new('Source', 'Process', 100)
|
|
92
|
+
# flow2 = SankeyFlow.new('Process', 'Output', 70)
|
|
93
|
+
# flow3 = SankeyFlow.new('Process', 'Waste', 30)
|
|
94
|
+
# sankey.flows << flow1 << flow2 << flow3
|
|
95
|
+
class SankeyDiagram < Base
|
|
96
|
+
# Collection of explicitly defined nodes (optional)
|
|
97
|
+
attribute :nodes, SankeyNode, collection: true,
|
|
98
|
+
default: -> { [] }
|
|
99
|
+
|
|
100
|
+
# Collection of flows between nodes
|
|
101
|
+
attribute :flows, SankeyFlow, collection: true,
|
|
102
|
+
default: -> { [] }
|
|
103
|
+
|
|
104
|
+
# Accessibility title (for screen readers)
|
|
105
|
+
attribute :acc_title, :string
|
|
106
|
+
|
|
107
|
+
# Accessibility description (for screen readers)
|
|
108
|
+
attribute :acc_description, :string
|
|
109
|
+
|
|
110
|
+
def initialize
|
|
111
|
+
@nodes = []
|
|
112
|
+
@flows = []
|
|
113
|
+
super
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Returns the diagram type identifier.
|
|
117
|
+
#
|
|
118
|
+
# @return [Symbol] :sankey
|
|
119
|
+
def diagram_type
|
|
120
|
+
:sankey
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Validates the diagram structure.
|
|
124
|
+
#
|
|
125
|
+
# A Sankey diagram is valid if it has at least one flow.
|
|
126
|
+
#
|
|
127
|
+
# @return [Boolean] true if diagram is valid
|
|
128
|
+
def valid?
|
|
129
|
+
!flows.empty? && flows.all?(&:valid?)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Get all unique node IDs from flows and explicit nodes.
|
|
133
|
+
#
|
|
134
|
+
# @return [Array<String>] array of unique node IDs
|
|
135
|
+
def all_node_ids
|
|
136
|
+
node_ids_from_flows = flows.flat_map { |f| [f.source, f.target] }
|
|
137
|
+
explicit_node_ids = nodes.map(&:id)
|
|
138
|
+
(node_ids_from_flows + explicit_node_ids).uniq
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Get or create node by ID.
|
|
142
|
+
#
|
|
143
|
+
# @param id [String] node identifier
|
|
144
|
+
# @return [SankeyNode] the node
|
|
145
|
+
def node_by_id(id)
|
|
146
|
+
nodes.find { |n| n.id == id } || SankeyNode.new(id)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Get all flows from a specific node.
|
|
150
|
+
#
|
|
151
|
+
# @param node_id [String] the source node ID
|
|
152
|
+
# @return [Array<SankeyFlow>] flows from this node
|
|
153
|
+
def flows_from(node_id)
|
|
154
|
+
flows.select { |f| f.source == node_id }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Get all flows to a specific node.
|
|
158
|
+
#
|
|
159
|
+
# @param node_id [String] the target node ID
|
|
160
|
+
# @return [Array<SankeyFlow>] flows to this node
|
|
161
|
+
def flows_to(node_id)
|
|
162
|
+
flows.select { |f| f.target == node_id }
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Calculate total outflow from a node.
|
|
166
|
+
#
|
|
167
|
+
# @param node_id [String] the node ID
|
|
168
|
+
# @return [Float] sum of all outgoing flow values
|
|
169
|
+
def total_outflow(node_id)
|
|
170
|
+
flows_from(node_id).sum(&:value)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Calculate total inflow to a node.
|
|
174
|
+
#
|
|
175
|
+
# @param node_id [String] the node ID
|
|
176
|
+
# @return [Float] sum of all incoming flow values
|
|
177
|
+
def total_inflow(node_id)
|
|
178
|
+
flows_to(node_id).sum(&:value)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Get source nodes (nodes with no inflow).
|
|
182
|
+
#
|
|
183
|
+
# @return [Array<String>] source node IDs
|
|
184
|
+
def source_nodes
|
|
185
|
+
all_node_ids.select { |id| total_inflow(id).zero? }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Get sink nodes (nodes with no outflow).
|
|
189
|
+
#
|
|
190
|
+
# @return [Array<String>] sink node IDs
|
|
191
|
+
def sink_nodes
|
|
192
|
+
all_node_ids.select { |id| total_outflow(id).zero? }
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Get total flow value in the diagram.
|
|
196
|
+
#
|
|
197
|
+
# @return [Float] sum of all flow values
|
|
198
|
+
def total_flow
|
|
199
|
+
flows.sum(&:value)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Get maximum flow value.
|
|
203
|
+
#
|
|
204
|
+
# @return [Float] maximum single flow value
|
|
205
|
+
def max_flow
|
|
206
|
+
flows.map(&:value).max || 0.0
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Get minimum flow value.
|
|
210
|
+
#
|
|
211
|
+
# @return [Float] minimum single flow value
|
|
212
|
+
def min_flow
|
|
213
|
+
flows.map(&:value).min || 0.0
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|