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.
Files changed (382) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/build_deploy.yml +59 -0
  3. data/.github/workflows/links.yml +85 -0
  4. data/.github/workflows/rake.yml +15 -0
  5. data/.github/workflows/release.yml +27 -0
  6. data/.gitignore +68 -0
  7. data/.rspec +3 -0
  8. data/.rubocop.yml +14 -0
  9. data/.rubocop_todo.yml +70 -0
  10. data/ARCHITECTURE.md +744 -0
  11. data/Gemfile +12 -0
  12. data/LICENSE +25 -0
  13. data/README.adoc +357 -0
  14. data/Rakefile +11 -0
  15. data/docs/.gitignore +1 -0
  16. data/docs/Gemfile +13 -0
  17. data/docs/_config.yml +182 -0
  18. data/docs/_diagram_types/architecture-diagram.adoc +314 -0
  19. data/docs/_diagram_types/block-diagram.adoc +345 -0
  20. data/docs/_diagram_types/c4-diagram.adoc +559 -0
  21. data/docs/_diagram_types/class-diagram.adoc +816 -0
  22. data/docs/_diagram_types/er-diagram.adoc +719 -0
  23. data/docs/_diagram_types/error-diagram.adoc +114 -0
  24. data/docs/_diagram_types/examples/flowchart-examples.adoc +29 -0
  25. data/docs/_diagram_types/flowchart.adoc +488 -0
  26. data/docs/_diagram_types/gantt-chart.adoc +502 -0
  27. data/docs/_diagram_types/git-graph.adoc +600 -0
  28. data/docs/_diagram_types/index.adoc +192 -0
  29. data/docs/_diagram_types/info-diagram.adoc +103 -0
  30. data/docs/_diagram_types/kanban-diagram.adoc +262 -0
  31. data/docs/_diagram_types/mindmap.adoc +603 -0
  32. data/docs/_diagram_types/packet-diagram.adoc +378 -0
  33. data/docs/_diagram_types/pie-chart.adoc +335 -0
  34. data/docs/_diagram_types/quadrant-chart.adoc +406 -0
  35. data/docs/_diagram_types/radar-chart.adoc +528 -0
  36. data/docs/_diagram_types/requirement-diagram.adoc +416 -0
  37. data/docs/_diagram_types/sankey-diagram.adoc +357 -0
  38. data/docs/_diagram_types/sequence-diagram.adoc +664 -0
  39. data/docs/_diagram_types/state-diagram.adoc +658 -0
  40. data/docs/_diagram_types/timeline.adoc +352 -0
  41. data/docs/_diagram_types/treemap-diagram.adoc +462 -0
  42. data/docs/_diagram_types/user-journey.adoc +602 -0
  43. data/docs/_features/index.adoc +129 -0
  44. data/docs/_guides/cli-reference.adoc +203 -0
  45. data/docs/_guides/index.adoc +56 -0
  46. data/docs/_guides/installation.adoc +100 -0
  47. data/docs/_guides/quick-start.adoc +132 -0
  48. data/docs/_pages/comparison.adoc +441 -0
  49. data/docs/_pages/compatibility.adoc +300 -0
  50. data/docs/_pages/index.adoc +39 -0
  51. data/docs/_references/index.adoc +103 -0
  52. data/docs/_tutorials/index.adoc +57 -0
  53. data/docs/index.adoc +166 -0
  54. data/docs/lychee.toml +54 -0
  55. data/examples/.gitignore +10 -0
  56. data/examples/README.adoc +196 -0
  57. data/examples/README.md +64 -0
  58. data/examples/architecture/01-basic-services.mmd +9 -0
  59. data/examples/architecture/01-basic-services.svg +37 -0
  60. data/examples/architecture/02-service-groups.mmd +16 -0
  61. data/examples/architecture/02-service-groups.svg +55 -0
  62. data/examples/architecture/README.adoc +79 -0
  63. data/examples/block/01-basic-blocks.mmd +13 -0
  64. data/examples/block/01-basic-blocks.svg +44 -0
  65. data/examples/block/02-block-shapes.mmd +13 -0
  66. data/examples/block/02-block-shapes.svg +47 -0
  67. data/examples/block/README.adoc +85 -0
  68. data/examples/c4/01-context-diagram.mmd +10 -0
  69. data/examples/c4/01-context-diagram.svg +45 -0
  70. data/examples/c4/02-container-diagram.mmd +24 -0
  71. data/examples/c4/02-container-diagram.svg +105 -0
  72. data/examples/c4/README.adoc +92 -0
  73. data/examples/class_diagram/01-basic-classes.mmd +61 -0
  74. data/examples/class_diagram/01-basic-classes.svg +117 -0
  75. data/examples/class_diagram/02-relationships.mmd +61 -0
  76. data/examples/class_diagram/02-relationships.svg +129 -0
  77. data/examples/class_diagram/README.adoc +93 -0
  78. data/examples/er_diagram/01-basic-entities.mmd +64 -0
  79. data/examples/er_diagram/01-basic-entities.svg +5 -0
  80. data/examples/er_diagram/02-cardinality.mmd +57 -0
  81. data/examples/er_diagram/02-cardinality.svg +125 -0
  82. data/examples/er_diagram/README.adoc +88 -0
  83. data/examples/error/01-basic-error.mmd +1 -0
  84. data/examples/error/01-basic-error.svg +13 -0
  85. data/examples/error/02-error-display.mmd +1 -0
  86. data/examples/error/02-error-display.svg +13 -0
  87. data/examples/error/README.adoc +71 -0
  88. data/examples/error_message_example.svg +13 -0
  89. data/examples/flowchart/00-original.mmd +13 -0
  90. data/examples/flowchart/00-original.svg +5 -0
  91. data/examples/flowchart/01-basic-flow.mmd +7 -0
  92. data/examples/flowchart/01-basic-flow.svg +52 -0
  93. data/examples/flowchart/01-basic-flow.yml +13 -0
  94. data/examples/flowchart/02*.svg +87 -0
  95. data/examples/flowchart/02-node-shapes.mmd +9 -0
  96. data/examples/flowchart/02-node-shapes.svg +33 -0
  97. data/examples/flowchart/03-edge-types.mmd +7 -0
  98. data/examples/flowchart/03-edge-types.svg +53 -0
  99. data/examples/flowchart/04-subgraphs.mmd +9 -0
  100. data/examples/flowchart/04-subgraphs.svg +33 -0
  101. data/examples/flowchart/05-styling.mmd +9 -0
  102. data/examples/flowchart/05-styling.svg +33 -0
  103. data/examples/flowchart/06-complex-flow.mmd +8 -0
  104. data/examples/flowchart/06-complex-flow.svg +59 -0
  105. data/examples/flowchart/README.adoc +167 -0
  106. data/examples/gantt/01-simple-timeline.* +14 -0
  107. data/examples/gantt/01-simple-timeline.mmd +6 -0
  108. data/examples/gantt/01-simple-timeline.svg +26 -0
  109. data/examples/gantt/02-task-dependencies.mmd +6 -0
  110. data/examples/gantt/02-task-dependencies.svg +26 -0
  111. data/examples/gantt/README.adoc +86 -0
  112. data/examples/git_graph/01-linear-history.mmd +12 -0
  113. data/examples/git_graph/01-linear-history.svg +26 -0
  114. data/examples/git_graph/02-branching.mmd +12 -0
  115. data/examples/git_graph/02-branching.svg +26 -0
  116. data/examples/git_graph/README.adoc +73 -0
  117. data/examples/info/02-showinfo.mmd +1 -0
  118. data/examples/info/02-showinfo.svg +10 -0
  119. data/examples/info/README.adoc +58 -0
  120. data/examples/info_showinfo_example.svg +10 -0
  121. data/examples/kanban/01-simple-board.mmd +8 -0
  122. data/examples/kanban/01-simple-board.svg +43 -0
  123. data/examples/kanban/02-workflow.mmd +8 -0
  124. data/examples/kanban/02-workflow.svg +43 -0
  125. data/examples/kanban/README.adoc +79 -0
  126. data/examples/mindmap/01-simple-tree.mmd +19 -0
  127. data/examples/mindmap/01-simple-tree.svg +61 -0
  128. data/examples/mindmap/02-knowledge-map.mmd +19 -0
  129. data/examples/mindmap/02-knowledge-map.svg +61 -0
  130. data/examples/mindmap/README.adoc +77 -0
  131. data/examples/packet/01-basic-packet.* +17 -0
  132. data/examples/packet/01-basic-packet.mmd +4 -0
  133. data/examples/packet/01-basic-packet.svg +82 -0
  134. data/examples/packet/README.adoc +58 -0
  135. data/examples/pie/01-simple-chart.mmd +5 -0
  136. data/examples/pie/01-simple-chart.svg +17 -0
  137. data/examples/pie/02-labeled-slices.mmd +6 -0
  138. data/examples/pie/02-labeled-slices.svg +19 -0
  139. data/examples/pie/README.adoc +75 -0
  140. data/examples/quadrant/01-basic-quadrant.mmd +13 -0
  141. data/examples/quadrant/01-basic-quadrant.svg +33 -0
  142. data/examples/quadrant/02-positioned-items.mmd +14 -0
  143. data/examples/quadrant/02-positioned-items.svg +35 -0
  144. data/examples/quadrant/README.adoc +84 -0
  145. data/examples/radar/01-simple-radar.* +5 -0
  146. data/examples/radar/01-simple-radar.mmd +3 -0
  147. data/examples/radar/01-simple-radar.svg +25 -0
  148. data/examples/radar/02-multiple-curves.mmd +4 -0
  149. data/examples/radar/02-multiple-curves.svg +43 -0
  150. data/examples/radar/README.adoc +75 -0
  151. data/examples/requirement/01-basic-requirements.mmd +23 -0
  152. data/examples/requirement/01-basic-requirements.svg +49 -0
  153. data/examples/requirement/02-risk-levels.mmd +23 -0
  154. data/examples/requirement/02-risk-levels.svg +49 -0
  155. data/examples/requirement/README.adoc +85 -0
  156. data/examples/sankey/01-simple-flow.mmd +7 -0
  157. data/examples/sankey/01-simple-flow.svg +34 -0
  158. data/examples/sankey/02-multi-stage.mmd +11 -0
  159. data/examples/sankey/02-multi-stage.svg +44 -0
  160. data/examples/sankey/README.adoc +74 -0
  161. data/examples/sequence/01-basic-sequence.mmd +27 -0
  162. data/examples/sequence/01-basic-sequence.svg +5 -0
  163. data/examples/sequence/02-activations.mmd +17 -0
  164. data/examples/sequence/02-activations.svg +78 -0
  165. data/examples/sequence/README.adoc +86 -0
  166. data/examples/state_diagram/01-simple-states.mmd +29 -0
  167. data/examples/state_diagram/01-simple-states.svg +5 -0
  168. data/examples/state_diagram/02-composite.mmd +19 -0
  169. data/examples/state_diagram/02-composite.svg +81 -0
  170. data/examples/state_diagram/README.adoc +90 -0
  171. data/examples/timeline/01-simple-timeline.mmd +11 -0
  172. data/examples/timeline/01-simple-timeline.svg +36 -0
  173. data/examples/timeline/02-periods.mmd +15 -0
  174. data/examples/timeline/02-periods.svg +47 -0
  175. data/examples/timeline/README.adoc +78 -0
  176. data/examples/treemap/01-basic-treemap.mmd +12 -0
  177. data/examples/treemap/01-basic-treemap.svg +59 -0
  178. data/examples/treemap/README.adoc +59 -0
  179. data/examples/user_journey/01-simple-journey.mmd +23 -0
  180. data/examples/user_journey/01-simple-journey.svg +5 -0
  181. data/examples/user_journey/02-multi-actor.mmd +18 -0
  182. data/examples/user_journey/02-multi-actor.svg +129 -0
  183. data/examples/user_journey/README.adoc +81 -0
  184. data/examples/xychart/01-line-chart.mmd +5 -0
  185. data/examples/xychart/01-line-chart.svg +43 -0
  186. data/examples/xychart/02-bar-chart.mmd +7 -0
  187. data/examples/xychart/02-bar-chart.svg +48 -0
  188. data/examples/xychart/README.adoc +80 -0
  189. data/exe/sirena +7 -0
  190. data/lib/sirena/cli.rb +138 -0
  191. data/lib/sirena/commands/batch.rb +117 -0
  192. data/lib/sirena/commands/render.rb +80 -0
  193. data/lib/sirena/commands/types.rb +29 -0
  194. data/lib/sirena/commands/version.rb +24 -0
  195. data/lib/sirena/diagram/architecture.rb +46 -0
  196. data/lib/sirena/diagram/base.rb +61 -0
  197. data/lib/sirena/diagram/block.rb +81 -0
  198. data/lib/sirena/diagram/c4.rb +328 -0
  199. data/lib/sirena/diagram/class_diagram.rb +385 -0
  200. data/lib/sirena/diagram/er_diagram.rb +238 -0
  201. data/lib/sirena/diagram/error.rb +38 -0
  202. data/lib/sirena/diagram/flowchart.rb +160 -0
  203. data/lib/sirena/diagram/gantt.rb +71 -0
  204. data/lib/sirena/diagram/git_graph.rb +36 -0
  205. data/lib/sirena/diagram/info.rb +38 -0
  206. data/lib/sirena/diagram/kanban.rb +178 -0
  207. data/lib/sirena/diagram/mindmap.rb +54 -0
  208. data/lib/sirena/diagram/packet.rb +79 -0
  209. data/lib/sirena/diagram/pie.rb +115 -0
  210. data/lib/sirena/diagram/quadrant.rb +138 -0
  211. data/lib/sirena/diagram/radar.rb +52 -0
  212. data/lib/sirena/diagram/requirement.rb +133 -0
  213. data/lib/sirena/diagram/sankey.rb +217 -0
  214. data/lib/sirena/diagram/sequence.rb +242 -0
  215. data/lib/sirena/diagram/state_diagram.rb +237 -0
  216. data/lib/sirena/diagram/timeline.rb +171 -0
  217. data/lib/sirena/diagram/treemap.rb +84 -0
  218. data/lib/sirena/diagram/user_journey.rb +149 -0
  219. data/lib/sirena/diagram/xy_chart.rb +76 -0
  220. data/lib/sirena/diagram.rb +8 -0
  221. data/lib/sirena/diagram_registry.rb +101 -0
  222. data/lib/sirena/engine.rb +292 -0
  223. data/lib/sirena/parser/architecture.rb +41 -0
  224. data/lib/sirena/parser/base.rb +41 -0
  225. data/lib/sirena/parser/block.rb +72 -0
  226. data/lib/sirena/parser/c4.rb +53 -0
  227. data/lib/sirena/parser/class_diagram.rb +63 -0
  228. data/lib/sirena/parser/er_diagram.rb +40 -0
  229. data/lib/sirena/parser/error.rb +49 -0
  230. data/lib/sirena/parser/flowchart.rb +71 -0
  231. data/lib/sirena/parser/gantt.rb +60 -0
  232. data/lib/sirena/parser/git_graph.rb +95 -0
  233. data/lib/sirena/parser/grammars/architecture.rb +145 -0
  234. data/lib/sirena/parser/grammars/block.rb +190 -0
  235. data/lib/sirena/parser/grammars/c4.rb +226 -0
  236. data/lib/sirena/parser/grammars/class_diagram.rb +284 -0
  237. data/lib/sirena/parser/grammars/common.rb +84 -0
  238. data/lib/sirena/parser/grammars/er_diagram.rb +114 -0
  239. data/lib/sirena/parser/grammars/error.rb +40 -0
  240. data/lib/sirena/parser/grammars/flowchart.rb +298 -0
  241. data/lib/sirena/parser/grammars/gantt.rb +252 -0
  242. data/lib/sirena/parser/grammars/git_graph.rb +167 -0
  243. data/lib/sirena/parser/grammars/info.rb +58 -0
  244. data/lib/sirena/parser/grammars/kanban.rb +83 -0
  245. data/lib/sirena/parser/grammars/mindmap.rb +115 -0
  246. data/lib/sirena/parser/grammars/packet.rb +73 -0
  247. data/lib/sirena/parser/grammars/pie.rb +128 -0
  248. data/lib/sirena/parser/grammars/quadrant.rb +199 -0
  249. data/lib/sirena/parser/grammars/radar.rb +150 -0
  250. data/lib/sirena/parser/grammars/requirement.rb +188 -0
  251. data/lib/sirena/parser/grammars/sankey.rb +104 -0
  252. data/lib/sirena/parser/grammars/sequence.rb +247 -0
  253. data/lib/sirena/parser/grammars/state_diagram.rb +172 -0
  254. data/lib/sirena/parser/grammars/timeline.rb +142 -0
  255. data/lib/sirena/parser/grammars/treemap.rb +120 -0
  256. data/lib/sirena/parser/grammars/xy_chart.rb +120 -0
  257. data/lib/sirena/parser/info.rb +49 -0
  258. data/lib/sirena/parser/kanban.rb +97 -0
  259. data/lib/sirena/parser/mindmap.rb +106 -0
  260. data/lib/sirena/parser/packet.rb +76 -0
  261. data/lib/sirena/parser/pie.rb +49 -0
  262. data/lib/sirena/parser/quadrant.rb +57 -0
  263. data/lib/sirena/parser/radar.rb +104 -0
  264. data/lib/sirena/parser/requirement.rb +70 -0
  265. data/lib/sirena/parser/sankey.rb +64 -0
  266. data/lib/sirena/parser/sequence.rb +51 -0
  267. data/lib/sirena/parser/state_diagram.rb +69 -0
  268. data/lib/sirena/parser/timeline.rb +57 -0
  269. data/lib/sirena/parser/transforms/architecture.rb +97 -0
  270. data/lib/sirena/parser/transforms/block.rb +254 -0
  271. data/lib/sirena/parser/transforms/c4.rb +347 -0
  272. data/lib/sirena/parser/transforms/class_diagram.rb +352 -0
  273. data/lib/sirena/parser/transforms/er_diagram.rb +169 -0
  274. data/lib/sirena/parser/transforms/error.rb +58 -0
  275. data/lib/sirena/parser/transforms/flowchart.rb +293 -0
  276. data/lib/sirena/parser/transforms/gantt.rb +215 -0
  277. data/lib/sirena/parser/transforms/git_graph.rb +160 -0
  278. data/lib/sirena/parser/transforms/info.rb +58 -0
  279. data/lib/sirena/parser/transforms/kanban.rb +176 -0
  280. data/lib/sirena/parser/transforms/mindmap.rb +227 -0
  281. data/lib/sirena/parser/transforms/packet.rb +63 -0
  282. data/lib/sirena/parser/transforms/pie.rb +143 -0
  283. data/lib/sirena/parser/transforms/quadrant.rb +177 -0
  284. data/lib/sirena/parser/transforms/radar.rb +126 -0
  285. data/lib/sirena/parser/transforms/requirement.rb +272 -0
  286. data/lib/sirena/parser/transforms/sankey.rb +122 -0
  287. data/lib/sirena/parser/transforms/sequence.rb +342 -0
  288. data/lib/sirena/parser/transforms/state_diagram.rb +292 -0
  289. data/lib/sirena/parser/transforms/timeline.rb +177 -0
  290. data/lib/sirena/parser/transforms/treemap.rb +81 -0
  291. data/lib/sirena/parser/transforms/xy_chart.rb +132 -0
  292. data/lib/sirena/parser/treemap.rb +98 -0
  293. data/lib/sirena/parser/user_journey.rb +120 -0
  294. data/lib/sirena/parser/xy_chart.rb +114 -0
  295. data/lib/sirena/parser.rb +8 -0
  296. data/lib/sirena/renderer/architecture.rb +251 -0
  297. data/lib/sirena/renderer/base.rb +251 -0
  298. data/lib/sirena/renderer/block.rb +286 -0
  299. data/lib/sirena/renderer/c4.rb +490 -0
  300. data/lib/sirena/renderer/class_diagram.rb +499 -0
  301. data/lib/sirena/renderer/er_diagram.rb +417 -0
  302. data/lib/sirena/renderer/error.rb +131 -0
  303. data/lib/sirena/renderer/flowchart.rb +301 -0
  304. data/lib/sirena/renderer/gantt.rb +331 -0
  305. data/lib/sirena/renderer/git_graph.rb +368 -0
  306. data/lib/sirena/renderer/info.rb +93 -0
  307. data/lib/sirena/renderer/kanban.rb +295 -0
  308. data/lib/sirena/renderer/mindmap.rb +396 -0
  309. data/lib/sirena/renderer/packet.rb +239 -0
  310. data/lib/sirena/renderer/pie.rb +235 -0
  311. data/lib/sirena/renderer/quadrant.rb +292 -0
  312. data/lib/sirena/renderer/radar.rb +323 -0
  313. data/lib/sirena/renderer/requirement.rb +371 -0
  314. data/lib/sirena/renderer/sankey.rb +255 -0
  315. data/lib/sirena/renderer/sequence.rb +424 -0
  316. data/lib/sirena/renderer/state_diagram.rb +328 -0
  317. data/lib/sirena/renderer/timeline.rb +304 -0
  318. data/lib/sirena/renderer/treemap.rb +152 -0
  319. data/lib/sirena/renderer/user_journey.rb +331 -0
  320. data/lib/sirena/renderer/xy_chart.rb +452 -0
  321. data/lib/sirena/renderer.rb +8 -0
  322. data/lib/sirena/svg/circle.rb +41 -0
  323. data/lib/sirena/svg/document.rb +103 -0
  324. data/lib/sirena/svg/element.rb +65 -0
  325. data/lib/sirena/svg/ellipse.rb +33 -0
  326. data/lib/sirena/svg/group.rb +71 -0
  327. data/lib/sirena/svg/line.rb +49 -0
  328. data/lib/sirena/svg/path.rb +76 -0
  329. data/lib/sirena/svg/polygon.rb +43 -0
  330. data/lib/sirena/svg/polyline.rb +35 -0
  331. data/lib/sirena/svg/rect.rb +57 -0
  332. data/lib/sirena/svg/style.rb +44 -0
  333. data/lib/sirena/svg/text.rb +72 -0
  334. data/lib/sirena/svg.rb +19 -0
  335. data/lib/sirena/text_measurement.rb +71 -0
  336. data/lib/sirena/theme/builtin/dark.yml +70 -0
  337. data/lib/sirena/theme/builtin/default.yml +80 -0
  338. data/lib/sirena/theme/builtin/high_contrast.yml +70 -0
  339. data/lib/sirena/theme/builtin/light.yml +70 -0
  340. data/lib/sirena/theme/color_palette.rb +48 -0
  341. data/lib/sirena/theme/effect_styles.rb +28 -0
  342. data/lib/sirena/theme/registry.rb +41 -0
  343. data/lib/sirena/theme/shape_styles.rb +28 -0
  344. data/lib/sirena/theme/spacing_config.rb +24 -0
  345. data/lib/sirena/theme/typography.rb +30 -0
  346. data/lib/sirena/theme.rb +69 -0
  347. data/lib/sirena/transform/architecture.rb +273 -0
  348. data/lib/sirena/transform/base.rb +199 -0
  349. data/lib/sirena/transform/block.rb +215 -0
  350. data/lib/sirena/transform/c4.rb +288 -0
  351. data/lib/sirena/transform/class_diagram.rb +296 -0
  352. data/lib/sirena/transform/er_diagram.rb +204 -0
  353. data/lib/sirena/transform/error.rb +39 -0
  354. data/lib/sirena/transform/flowchart.rb +161 -0
  355. data/lib/sirena/transform/gantt.rb +253 -0
  356. data/lib/sirena/transform/git_graph.rb +283 -0
  357. data/lib/sirena/transform/info.rb +39 -0
  358. data/lib/sirena/transform/kanban.rb +180 -0
  359. data/lib/sirena/transform/mindmap.rb +251 -0
  360. data/lib/sirena/transform/packet.rb +185 -0
  361. data/lib/sirena/transform/pie.rb +62 -0
  362. data/lib/sirena/transform/quadrant.rb +167 -0
  363. data/lib/sirena/transform/radar.rb +227 -0
  364. data/lib/sirena/transform/requirement.rb +233 -0
  365. data/lib/sirena/transform/sankey.rb +212 -0
  366. data/lib/sirena/transform/sequence.rb +143 -0
  367. data/lib/sirena/transform/state_diagram.rb +228 -0
  368. data/lib/sirena/transform/timeline.rb +139 -0
  369. data/lib/sirena/transform/treemap.rb +120 -0
  370. data/lib/sirena/transform/user_journey.rb +207 -0
  371. data/lib/sirena/transform/xy_chart.rb +273 -0
  372. data/lib/sirena/transform.rb +8 -0
  373. data/lib/sirena/version.rb +5 -0
  374. data/lib/sirena.rb +328 -0
  375. data/lib/tasks/benchmark.rake +532 -0
  376. data/lib/tasks/examples.rake +468 -0
  377. data/lib/tasks/generate_mermaid_fixtures.rake +363 -0
  378. data/lib/tasks/mermaid_fixtures.rake +46 -0
  379. data/scripts/extract_mermaid_tests.rb +493 -0
  380. data/scripts/rename_to_sirena.rb +73 -0
  381. data/sirena.gemspec +47 -0
  382. metadata +529 -0
@@ -0,0 +1,292 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative '../svg/document'
5
+ require_relative '../svg/rect'
6
+ require_relative '../svg/line'
7
+ require_relative '../svg/circle'
8
+ require_relative '../svg/text'
9
+ require_relative '../svg/group'
10
+
11
+ module Sirena
12
+ module Renderer
13
+ # Quadrant chart renderer for converting quadrant diagrams to SVG.
14
+ #
15
+ # Converts a QuadrantChart diagram model into SVG with a 2x2 grid,
16
+ # axis labels, quadrant labels, and data points.
17
+ #
18
+ # @example Render a quadrant chart
19
+ # renderer = QuadrantRenderer.new
20
+ # svg = renderer.render(quadrant_graph)
21
+ class QuadrantRenderer < Base
22
+ # Quadrant colors (can be overridden by theme)
23
+ QUADRANT_COLORS = {
24
+ 1 => '#e3f2fd', # Light blue
25
+ 2 => '#fff3e0', # Light orange
26
+ 3 => '#f3e5f5', # Light purple
27
+ 4 => '#e8f5e9' # Light green
28
+ }.freeze
29
+
30
+ # Renders a quadrant chart diagram to SVG.
31
+ #
32
+ # @param graph [Hash] the quadrant chart graph structure from transform
33
+ # @return [Svg::Document] the rendered SVG document
34
+ def render(graph)
35
+ svg = create_document(graph)
36
+
37
+ # Render title if present
38
+ render_title(graph, svg) if graph[:title]
39
+
40
+ # Render quadrant grid
41
+ render_quadrants(graph, svg)
42
+
43
+ # Render axes
44
+ render_axes(graph, svg)
45
+
46
+ # Render axis labels
47
+ render_axis_labels(graph, svg)
48
+
49
+ # Render quadrant labels
50
+ render_quadrant_labels(graph, svg)
51
+
52
+ # Render data points
53
+ render_points(graph, svg)
54
+
55
+ svg
56
+ end
57
+
58
+ protected
59
+
60
+ def create_document(graph)
61
+ dims = graph[:dimensions]
62
+
63
+ Svg::Document.new.tap do |doc|
64
+ doc.width = dims[:width]
65
+ doc.height = dims[:height]
66
+ doc.view_box = "0 0 #{dims[:width]} #{dims[:height]}"
67
+ end
68
+ end
69
+
70
+ def render_title(graph, svg)
71
+ dims = graph[:dimensions]
72
+
73
+ title_text = Svg::Text.new.tap do |t|
74
+ t.x = dims[:width] / 2
75
+ t.y = 30
76
+ t.content = graph[:title]
77
+ t.fill = theme_color(:label_text) || '#000000'
78
+ t.font_family = theme_typography(:font_family) ||
79
+ 'Arial, sans-serif'
80
+ t.font_size = (theme_typography(:font_size_large) || 18).to_s
81
+ t.text_anchor = 'middle'
82
+ t.font_weight = 'bold'
83
+ end
84
+ svg << title_text
85
+ end
86
+
87
+ def render_quadrants(graph, svg)
88
+ quadrants = graph[:quadrants]
89
+
90
+ # Render each quadrant background
91
+ quadrants.each do |_key, quadrant|
92
+ bounds = quadrant[:bounds]
93
+ color = get_quadrant_color(quadrant[:number])
94
+
95
+ rect = Svg::Rect.new.tap do |r|
96
+ r.x = bounds[:x]
97
+ r.y = bounds[:y]
98
+ r.width = bounds[:width]
99
+ r.height = bounds[:height]
100
+ r.fill = color
101
+ r.stroke = theme_color(:grid_line) || '#cccccc'
102
+ r.stroke_width = '1'
103
+ r.opacity = '0.3'
104
+ end
105
+ svg << rect
106
+ end
107
+ end
108
+
109
+ def render_axes(graph, svg)
110
+ dims = graph[:dimensions]
111
+
112
+ # Vertical axis (center)
113
+ center_x = dims[:chart_x] + (dims[:chart_width] / 2)
114
+ vertical_line = Svg::Line.new.tap do |line|
115
+ line.x1 = center_x
116
+ line.y1 = dims[:chart_y]
117
+ line.x2 = center_x
118
+ line.y2 = dims[:chart_y] + dims[:chart_height]
119
+ line.stroke = theme_color(:grid_line) || '#666666'
120
+ line.stroke_width = '2'
121
+ end
122
+ svg << vertical_line
123
+
124
+ # Horizontal axis (center)
125
+ center_y = dims[:chart_y] + (dims[:chart_height] / 2)
126
+ horizontal_line = Svg::Line.new.tap do |line|
127
+ line.x1 = dims[:chart_x]
128
+ line.y1 = center_y
129
+ line.x2 = dims[:chart_x] + dims[:chart_width]
130
+ line.y2 = center_y
131
+ line.stroke = theme_color(:grid_line) || '#666666'
132
+ line.stroke_width = '2'
133
+ end
134
+ svg << horizontal_line
135
+ end
136
+
137
+ def render_axis_labels(graph, svg)
138
+ dims = graph[:dimensions]
139
+ axes = graph[:axes]
140
+
141
+ # X-axis left label
142
+ render_axis_label(
143
+ axes[:x_left],
144
+ dims[:chart_x] - 10,
145
+ dims[:chart_y] + dims[:chart_height] + 30,
146
+ 'end',
147
+ svg
148
+ )
149
+
150
+ # X-axis right label
151
+ render_axis_label(
152
+ axes[:x_right],
153
+ dims[:chart_x] + dims[:chart_width] + 10,
154
+ dims[:chart_y] + dims[:chart_height] + 30,
155
+ 'start',
156
+ svg
157
+ )
158
+
159
+ # Y-axis bottom label
160
+ render_axis_label(
161
+ axes[:y_bottom],
162
+ dims[:chart_x] - 30,
163
+ dims[:chart_y] + dims[:chart_height] + 10,
164
+ 'middle',
165
+ svg
166
+ )
167
+
168
+ # Y-axis top label
169
+ render_axis_label(
170
+ axes[:y_top],
171
+ dims[:chart_x] - 30,
172
+ dims[:chart_y] - 10,
173
+ 'middle',
174
+ svg
175
+ )
176
+ end
177
+
178
+ def render_axis_label(text, x, y, anchor, svg)
179
+ return unless text
180
+
181
+ label = Svg::Text.new.tap do |t|
182
+ t.x = x
183
+ t.y = y
184
+ t.content = text
185
+ t.fill = theme_color(:label_text) || '#666666'
186
+ t.font_family = theme_typography(:font_family) ||
187
+ 'Arial, sans-serif'
188
+ t.font_size = (theme_typography(:font_size_small) || 12).to_s
189
+ t.text_anchor = anchor
190
+ end
191
+ svg << label
192
+ end
193
+
194
+ def render_quadrant_labels(graph, svg)
195
+ quadrants = graph[:quadrants]
196
+
197
+ quadrants.each do |_key, quadrant|
198
+ next unless quadrant[:label]
199
+
200
+ bounds = quadrant[:bounds]
201
+
202
+ # Calculate center position for label
203
+ label_x = bounds[:x] + (bounds[:width] / 2)
204
+ label_y = bounds[:y] + 20
205
+
206
+ label = Svg::Text.new.tap do |t|
207
+ t.x = label_x
208
+ t.y = label_y
209
+ t.content = quadrant[:label]
210
+ t.fill = theme_color(:label_text) || '#333333'
211
+ t.font_family = theme_typography(:font_family) ||
212
+ 'Arial, sans-serif'
213
+ t.font_size = (theme_typography(:font_size_normal) || 14).to_s
214
+ t.text_anchor = 'middle'
215
+ t.font_weight = 'bold'
216
+ t.opacity = '0.7'
217
+ end
218
+ svg << label
219
+ end
220
+ end
221
+
222
+ def render_points(graph, svg)
223
+ points = graph[:points] || []
224
+
225
+ points.each do |point|
226
+ render_point(point, svg)
227
+ render_point_label(point, svg)
228
+ end
229
+ end
230
+
231
+ def render_point(point, svg)
232
+ radius = point[:radius] || 6
233
+ color = point[:color] || get_point_color(point[:quadrant])
234
+ stroke_color = point[:stroke_color] ||
235
+ theme_color(:node_stroke) ||
236
+ '#ffffff'
237
+ stroke_width = point[:stroke_width] || 2
238
+
239
+ circle = Svg::Circle.new.tap do |c|
240
+ c.cx = point[:svg_x]
241
+ c.cy = point[:svg_y]
242
+ c.r = radius
243
+ c.fill = color
244
+ c.stroke = stroke_color
245
+ c.stroke_width = stroke_width.to_s
246
+ c.id = point[:id]
247
+ end
248
+ svg << circle
249
+ end
250
+
251
+ def render_point_label(point, svg)
252
+ # Position label slightly above and to the right of point
253
+ label_x = point[:svg_x] + 10
254
+ label_y = point[:svg_y] - 10
255
+
256
+ label = Svg::Text.new.tap do |t|
257
+ t.x = label_x
258
+ t.y = label_y
259
+ t.content = point[:label]
260
+ t.fill = theme_color(:label_text) || '#000000'
261
+ t.font_family = theme_typography(:font_family) ||
262
+ 'Arial, sans-serif'
263
+ t.font_size = (theme_typography(:font_size_small) || 11).to_s
264
+ t.text_anchor = 'start'
265
+ end
266
+ svg << label
267
+ end
268
+
269
+ def get_quadrant_color(quadrant_number)
270
+ # Try to get from theme first
271
+ color_key = "quadrant_#{quadrant_number}".to_sym
272
+ theme_color(color_key) || QUADRANT_COLORS[quadrant_number]
273
+ end
274
+
275
+ def get_point_color(quadrant_number)
276
+ # Use darker version of quadrant color for points
277
+ case quadrant_number
278
+ when 1
279
+ theme_color(:primary) || '#2196f3'
280
+ when 2
281
+ theme_color(:secondary) || '#ff9800'
282
+ when 3
283
+ theme_color(:accent) || '#9c27b0'
284
+ when 4
285
+ theme_color(:success) || '#4caf50'
286
+ else
287
+ theme_color(:primary) || '#2196f3'
288
+ end
289
+ end
290
+ end
291
+ end
292
+ end
@@ -0,0 +1,323 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../svg/document"
4
+ require_relative "../svg/circle"
5
+ require_relative "../svg/line"
6
+ require_relative "../svg/polygon"
7
+ require_relative "../svg/text"
8
+ require_relative "../svg/group"
9
+
10
+ module Sirena
11
+ module Renderer
12
+ # Renders a Radar Chart layout to SVG.
13
+ #
14
+ # The renderer converts the positioned layout structure from
15
+ # Transform::Radar into an SVG visualization showing:
16
+ # - Circular or polygonal grid
17
+ # - Radial axes from center
18
+ # - Axis labels
19
+ # - Data polygons for each curve/dataset
20
+ # - Legend
21
+ #
22
+ # @example Render a radar chart
23
+ # renderer = Renderer::Radar.new(theme: my_theme)
24
+ # svg = renderer.render(layout)
25
+ class Radar < Base
26
+ # Default colors for datasets (cycling)
27
+ DEFAULT_COLORS = %w[
28
+ #2563eb #7c3aed #db2777 #ea580c #ca8a04
29
+ #16a34a #0891b2 #4f46e5 #c026d3 #dc2626
30
+ ].freeze
31
+
32
+ # Renders the layout structure to SVG.
33
+ #
34
+ # @param layout [Hash] layout data from Transform::Radar
35
+ # @return [Svg::Document] rendered SVG document
36
+ def render(layout)
37
+ svg = create_document_from_layout(layout)
38
+
39
+ # Store center and radius for rendering
40
+ @center_x = layout[:center_x]
41
+ @center_y = layout[:center_y]
42
+ @radius = layout[:radius]
43
+
44
+ # Render in order: grid, axes, curves, labels, legend
45
+ render_grid(layout, svg)
46
+ render_axes(layout, svg)
47
+ render_curves(layout, svg)
48
+ render_axis_labels(layout, svg)
49
+ render_legend(layout, svg) if should_show_legend?(layout)
50
+
51
+ svg
52
+ end
53
+
54
+ protected
55
+
56
+ # Creates an SVG document with dimensions from layout.
57
+ #
58
+ # @param layout [Hash] layout data
59
+ # @return [Svg::Document] new SVG document
60
+ def create_document_from_layout(layout)
61
+ Svg::Document.new.tap do |doc|
62
+ doc.width = layout[:width]
63
+ doc.height = layout[:height]
64
+ doc.view_box = "0 0 #{doc.width} #{doc.height}"
65
+ end
66
+ end
67
+
68
+ # Determines if legend should be shown.
69
+ #
70
+ # @param layout [Hash] layout data
71
+ # @return [Boolean] true if legend should be shown
72
+ def should_show_legend?(layout)
73
+ # Check diagram options, default to true
74
+ layout.dig(:options, :show_legend) != false
75
+ end
76
+
77
+ # Renders the grid (circular or polygonal).
78
+ #
79
+ # @param layout [Hash] layout data
80
+ # @param svg [Svg::Document] SVG document
81
+ # @return [void]
82
+ def render_grid(layout, svg)
83
+ layout[:grid_circles].each do |grid|
84
+ render_grid_circle(grid, svg)
85
+ end
86
+ end
87
+
88
+ # Renders a single grid circle.
89
+ #
90
+ # @param grid [Hash] grid circle data
91
+ # @param svg [Svg::Document] SVG document
92
+ # @return [void]
93
+ def render_grid_circle(grid, svg)
94
+ circle = Svg::Circle.new.tap do |c|
95
+ c.cx = @center_x
96
+ c.cy = @center_y
97
+ c.r = grid[:radius]
98
+ c.fill = "none"
99
+ c.stroke = theme_color(:grid_line) || "#e5e7eb"
100
+ c.stroke_width = "1"
101
+ end
102
+
103
+ svg.add_element(circle)
104
+ end
105
+
106
+ # Renders all axes radiating from center.
107
+ #
108
+ # @param layout [Hash] layout data
109
+ # @param svg [Svg::Document] SVG document
110
+ # @return [void]
111
+ def render_axes(layout, svg)
112
+ layout[:axes].each do |axis|
113
+ render_axis_line(axis, svg)
114
+ end
115
+ end
116
+
117
+ # Renders a single axis line.
118
+ #
119
+ # @param axis [Hash] axis data
120
+ # @param svg [Svg::Document] SVG document
121
+ # @return [void]
122
+ def render_axis_line(axis, svg)
123
+ line = Svg::Line.new.tap do |l|
124
+ l.x1 = @center_x
125
+ l.y1 = @center_y
126
+ l.x2 = @center_x + axis[:end_x]
127
+ l.y2 = @center_y + axis[:end_y]
128
+ l.stroke = theme_color(:axis_line) || "#9ca3af"
129
+ l.stroke_width = "1"
130
+ end
131
+
132
+ svg.add_element(line)
133
+ end
134
+
135
+ # Renders axis labels.
136
+ #
137
+ # @param layout [Hash] layout data
138
+ # @param svg [Svg::Document] SVG document
139
+ # @return [void]
140
+ def render_axis_labels(layout, svg)
141
+ layout[:axes].each do |axis|
142
+ render_axis_label(axis, svg)
143
+ end
144
+ end
145
+
146
+ # Renders a single axis label.
147
+ #
148
+ # @param axis [Hash] axis data
149
+ # @param svg [Svg::Document] SVG document
150
+ # @return [void]
151
+ def render_axis_label(axis, svg)
152
+ x = @center_x + axis[:label_x]
153
+ y = @center_y + axis[:label_y]
154
+
155
+ text = Svg::Text.new.tap do |t|
156
+ t.x = x
157
+ t.y = y
158
+ t.text_anchor = calculate_text_anchor(axis[:angle_degrees])
159
+ t.dominant_baseline = calculate_dominant_baseline(axis[:angle_degrees])
160
+ t.fill = theme_color(:label_text) || "#000000"
161
+ t.font_size = (theme_typography(:font_size_normal) || 12).to_s
162
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
163
+ t.font_weight = "bold"
164
+ t.content = axis[:label]
165
+ end
166
+
167
+ svg.add_element(text)
168
+ end
169
+
170
+ # Calculates text anchor based on angle.
171
+ #
172
+ # @param angle [Numeric] angle in degrees
173
+ # @return [String] text anchor value
174
+ def calculate_text_anchor(angle)
175
+ # Normalize angle to 0-360
176
+ normalized = angle % 360
177
+ normalized += 360 if normalized < 0
178
+
179
+ if normalized > 45 && normalized < 135
180
+ "start"
181
+ elsif normalized > 225 && normalized < 315
182
+ "end"
183
+ else
184
+ "middle"
185
+ end
186
+ end
187
+
188
+ # Calculates dominant baseline based on angle.
189
+ #
190
+ # @param angle [Numeric] angle in degrees
191
+ # @return [String] dominant baseline value
192
+ def calculate_dominant_baseline(angle)
193
+ # Normalize angle to 0-360
194
+ normalized = angle % 360
195
+ normalized += 360 if normalized < 0
196
+
197
+ if normalized > 135 && normalized < 225
198
+ "hanging"
199
+ elsif normalized < 45 || normalized > 315
200
+ "auto"
201
+ else
202
+ "middle"
203
+ end
204
+ end
205
+
206
+ # Renders all data curves as polygons.
207
+ #
208
+ # @param layout [Hash] layout data
209
+ # @param svg [Svg::Document] SVG document
210
+ # @return [void]
211
+ def render_curves(layout, svg)
212
+ layout[:curves].each_with_index do |curve, idx|
213
+ color = get_curve_color(idx)
214
+ render_curve_polygon(curve, color, svg)
215
+ end
216
+ end
217
+
218
+ # Gets color for a curve based on index.
219
+ #
220
+ # @param index [Integer] curve index
221
+ # @return [String] color value
222
+ def get_curve_color(index)
223
+ DEFAULT_COLORS[index % DEFAULT_COLORS.length]
224
+ end
225
+
226
+ # Renders a single curve as a polygon.
227
+ #
228
+ # @param curve [Hash] curve data
229
+ # @param color [String] fill color
230
+ # @param svg [Svg::Document] SVG document
231
+ # @return [void]
232
+ def render_curve_polygon(curve, color, svg)
233
+ return if curve[:points].empty?
234
+
235
+ # Build points array for polygon
236
+ points = curve[:points].map do |point|
237
+ x = @center_x + point[:x]
238
+ y = @center_y + point[:y]
239
+ "#{x},#{y}"
240
+ end.join(" ")
241
+
242
+ polygon = Svg::Polygon.new.tap do |p|
243
+ p.points = points
244
+ p.fill = color
245
+ p.fill_opacity = "0.3"
246
+ p.stroke = color
247
+ p.stroke_width = "2"
248
+ end
249
+
250
+ svg.add_element(polygon)
251
+
252
+ # Render data points as small circles
253
+ render_curve_points(curve, color, svg)
254
+ end
255
+
256
+ # Renders data points for a curve.
257
+ #
258
+ # @param curve [Hash] curve data
259
+ # @param color [String] point color
260
+ # @param svg [Svg::Document] SVG document
261
+ # @return [void]
262
+ def render_curve_points(curve, color, svg)
263
+ curve[:points].each do |point|
264
+ x = @center_x + point[:x]
265
+ y = @center_y + point[:y]
266
+
267
+ circle = Svg::Circle.new.tap do |c|
268
+ c.cx = x
269
+ c.cy = y
270
+ c.r = 3
271
+ c.fill = color
272
+ c.stroke = theme_color(:background) || "#ffffff"
273
+ c.stroke_width = "1"
274
+ end
275
+
276
+ svg.add_element(circle)
277
+ end
278
+ end
279
+
280
+ # Renders legend for all curves.
281
+ #
282
+ # @param layout [Hash] layout data
283
+ # @param svg [Svg::Document] SVG document
284
+ # @return [void]
285
+ def render_legend(layout, svg)
286
+ return if layout[:curves].empty?
287
+
288
+ legend_x = 20
289
+ legend_y = layout[:height] - 40
290
+ item_height = 20
291
+
292
+ layout[:curves].each_with_index do |curve, idx|
293
+ color = get_curve_color(idx)
294
+ y_offset = idx * item_height
295
+
296
+ # Color box
297
+ box = Svg::Circle.new.tap do |c|
298
+ c.cx = legend_x
299
+ c.cy = legend_y + y_offset
300
+ c.r = 5
301
+ c.fill = color
302
+ c.stroke = "none"
303
+ end
304
+
305
+ svg.add_element(box)
306
+
307
+ # Label
308
+ text = Svg::Text.new.tap do |t|
309
+ t.x = legend_x + 15
310
+ t.y = legend_y + y_offset + 4
311
+ t.text_anchor = "start"
312
+ t.fill = theme_color(:label_text) || "#000000"
313
+ t.font_size = (theme_typography(:font_size_small) || 10).to_s
314
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
315
+ t.content = curve[:label]
316
+ end
317
+
318
+ svg.add_element(text)
319
+ end
320
+ end
321
+ end
322
+ end
323
+ end