@peter.prib/renderengine 0.0.0-stage → 0.2.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 (451) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +203 -2
  3. package/REFERENCE.md +2198 -0
  4. package/dist/renderengine-workspace.umd.js +46905 -0
  5. package/dist/renderengine.umd.js +42223 -0
  6. package/examples/archimate.html +66 -0
  7. package/examples/diagram-editor.html +51 -0
  8. package/package.json +65 -4
  9. package/src/BaseRenderer.js +57 -0
  10. package/src/CanvasRenderer.js +194 -0
  11. package/src/Mat4.js +111 -0
  12. package/src/OrbitCamera.js +57 -0
  13. package/src/RendererFactory.js +50 -0
  14. package/src/SVGRenderer.js +439 -0
  15. package/src/archimate.js +723 -0
  16. package/src/archimateArchiFormat.js +1693 -0
  17. package/src/archimateArchiTypes.js +72 -0
  18. package/src/archimateArchive.js +140 -0
  19. package/src/archimateCSV.js +127 -0
  20. package/src/archimateDrawio.js +182 -0
  21. package/src/archimateExchangeFormat.js +546 -0
  22. package/src/archimateRelationshipTable.js +93 -0
  23. package/src/archimateSpecializations.js +161 -0
  24. package/src/chartCatalog.js +589 -0
  25. package/src/charting/ArchimateDiagramEditor.js +1624 -0
  26. package/src/charting/ArchimateSpecializationSelector.js +134 -0
  27. package/src/charting/Axis.js +68 -0
  28. package/src/charting/CameraSeeder.js +30 -0
  29. package/src/charting/CatalogDialog.js +92 -0
  30. package/src/charting/Chart.js +37 -0
  31. package/src/charting/Chart3D.js +99 -0
  32. package/src/charting/ChartBox.js +80 -0
  33. package/src/charting/ChartData.js +27 -0
  34. package/src/charting/ChartRegistry.js +76 -0
  35. package/src/charting/CheckboxTreeSelector.js +304 -0
  36. package/src/charting/ColorField.js +107 -0
  37. package/src/charting/ColorPalette.js +88 -0
  38. package/src/charting/CommandHistory.js +176 -0
  39. package/src/charting/DataFilter.js +52 -0
  40. package/src/charting/Diagram.js +36 -0
  41. package/src/charting/Diagram3D.js +29 -0
  42. package/src/charting/DiagramClipboard.js +31 -0
  43. package/src/charting/DiagramEditor.js +3330 -0
  44. package/src/charting/DiagramEditorCommand.js +110 -0
  45. package/src/charting/DiagramEditorCommands.js +237 -0
  46. package/src/charting/DiagramEditorEdgeCommands.js +142 -0
  47. package/src/charting/DiagramEditorState.js +98 -0
  48. package/src/charting/DiagramRegistry.js +86 -0
  49. package/src/charting/DiagramTypeRegistry.js +326 -0
  50. package/src/charting/DiagramViewPropertiesDialog.js +46 -0
  51. package/src/charting/Edge.js +113 -0
  52. package/src/charting/ErrorBars.js +37 -0
  53. package/src/charting/FilterSystem.js +285 -0
  54. package/src/charting/FindReplaceDialog.js +139 -0
  55. package/src/charting/FormDialog.js +385 -0
  56. package/src/charting/Gauge.js +58 -0
  57. package/src/charting/Hierarchy.js +175 -0
  58. package/src/charting/HintPanel.js +86 -0
  59. package/src/charting/ImageSelector.js +148 -0
  60. package/src/charting/InteractionManager.js +725 -0
  61. package/src/charting/Legend.js +58 -0
  62. package/src/charting/MenuBar.js +62 -0
  63. package/src/charting/ModelFileFormat.js +115 -0
  64. package/src/charting/OrbitControls.js +77 -0
  65. package/src/charting/Palette.js +119 -0
  66. package/src/charting/PopupMenu.js +117 -0
  67. package/src/charting/PropertiesPanel.js +155 -0
  68. package/src/charting/PropertyListEditor.js +96 -0
  69. package/src/charting/PropertyLog.js +92 -0
  70. package/src/charting/SettingsCascade.js +93 -0
  71. package/src/charting/SettingsDialog.js +53 -0
  72. package/src/charting/SettingsManager.js +179 -0
  73. package/src/charting/SettingsViewPropertiesDialog.js +58 -0
  74. package/src/charting/ShapePaths.js +27 -0
  75. package/src/charting/Statistics.js +134 -0
  76. package/src/charting/TemplateCatalog.js +59 -0
  77. package/src/charting/TemplateLibrary.js +103 -0
  78. package/src/charting/TextStyle.js +168 -0
  79. package/src/charting/ViewFit.js +55 -0
  80. package/src/charting/ViewMenu.js +70 -0
  81. package/src/charting/ViewPropertiesDialog.js +77 -0
  82. package/src/charting/ViewTabStrip.js +81 -0
  83. package/src/charting/archimate/ArchiFileFormat.js +32 -0
  84. package/src/charting/archimate/ArchimateCanvasEditor.js +815 -0
  85. package/src/charting/archimate/ArchimateCsvFileFormat.js +35 -0
  86. package/src/charting/archimate/ArchimateEdge.js +137 -0
  87. package/src/charting/archimate/ArchimateElementTemplate.js +195 -0
  88. package/src/charting/archimate/ArchimatePalette.js +102 -0
  89. package/src/charting/archimate/CsvImportDialog.js +105 -0
  90. package/src/charting/archimate/CustomViewpoints.js +74 -0
  91. package/src/charting/archimate/DerivedEdge.js +29 -0
  92. package/src/charting/archimate/DrawioFileFormat.js +24 -0
  93. package/src/charting/archimate/ElementFilterRule.js +21 -0
  94. package/src/charting/archimate/ExchangeFileFormat.js +28 -0
  95. package/src/charting/archimate/ViewGroupTemplate.js +137 -0
  96. package/src/charting/archimate/ViewLineEdge.js +84 -0
  97. package/src/charting/archimate/ViewpointRule.js +45 -0
  98. package/src/charting/archimate/archimateFileFormats.js +12 -0
  99. package/src/charting/archimate/archimateNames.js +5 -0
  100. package/src/charting/archimate/csvMapping.js +122 -0
  101. package/src/charting/archimate/elementMenuItems.js +32 -0
  102. package/src/charting/archimate/elementProperties.js +146 -0
  103. package/src/charting/archimate/elementTypeChoices.js +7 -0
  104. package/src/charting/archimate/elementTypeGroups.js +19 -0
  105. package/src/charting/archimate/index.js +17 -0
  106. package/src/charting/archimate/specializationCatalogDialog.js +64 -0
  107. package/src/charting/archimate/typeGlyph.js +29 -0
  108. package/src/charting/arrangement/Arrangement.js +112 -0
  109. package/src/charting/arrangement/ArrangementCommand.js +29 -0
  110. package/src/charting/arrangement/BackgroundCommand.js +41 -0
  111. package/src/charting/arrangement/BackgroundGroup.js +18 -0
  112. package/src/charting/arrangement/GroupCommand.js +46 -0
  113. package/src/charting/arrangement/ItemGroup.js +62 -0
  114. package/src/charting/arrangement/LockCommand.js +32 -0
  115. package/src/charting/arrangement/UngroupCommand.js +27 -0
  116. package/src/charting/arrangement/alignment.js +88 -0
  117. package/src/charting/arrangement/arrangeMenuItems.js +25 -0
  118. package/src/charting/arrangement/index.js +11 -0
  119. package/src/charting/arrangement/layouts.js +233 -0
  120. package/src/charting/canvas/ArchiHint.js +78 -0
  121. package/src/charting/canvas/CanvasBoxKind.js +39 -0
  122. package/src/charting/canvas/CanvasPalette.js +49 -0
  123. package/src/charting/canvas/CanvasTemplateLibrary.js +61 -0
  124. package/src/charting/canvas/CanvasTemplateMenu.js +151 -0
  125. package/src/charting/canvas/EditHintCommand.js +28 -0
  126. package/src/charting/canvas/canvasBoxKinds.js +27 -0
  127. package/src/charting/canvas/canvasBoxMenuItems.js +40 -0
  128. package/src/charting/canvas/defaultCanvasTemplates.js +186 -0
  129. package/src/charting/canvas/diagramBoxKinds.js +12 -0
  130. package/src/charting/canvas/hintMenuItems.js +49 -0
  131. package/src/charting/charts/ArcGauge.js +55 -0
  132. package/src/charting/charts/AreaChart.js +71 -0
  133. package/src/charting/charts/BarChart.js +80 -0
  134. package/src/charting/charts/BarChart3D.js +56 -0
  135. package/src/charting/charts/BarChart3DOrbit.js +53 -0
  136. package/src/charting/charts/BarChartBase.js +70 -0
  137. package/src/charting/charts/BarGauge.js +42 -0
  138. package/src/charting/charts/BoxPlotChart.js +41 -0
  139. package/src/charting/charts/BubbleChart.js +99 -0
  140. package/src/charting/charts/BubbleChart3D.js +78 -0
  141. package/src/charting/charts/BulletGauge.js +87 -0
  142. package/src/charting/charts/CalendarHeatmapChart.js +204 -0
  143. package/src/charting/charts/CandlestickChart.js +54 -0
  144. package/src/charting/charts/ChordDiagram.js +130 -0
  145. package/src/charting/charts/DensityChart.js +73 -0
  146. package/src/charting/charts/DialGauge.js +65 -0
  147. package/src/charting/charts/DonutChart.js +15 -0
  148. package/src/charting/charts/FunnelChart.js +47 -0
  149. package/src/charting/charts/GanttChart.js +201 -0
  150. package/src/charting/charts/GaugeChart.js +175 -0
  151. package/src/charting/charts/GeoMapChart.js +146 -0
  152. package/src/charting/charts/GroupedBarChart.js +88 -0
  153. package/src/charting/charts/GroupedDistributionChart.js +70 -0
  154. package/src/charting/charts/HeatmapChart.js +80 -0
  155. package/src/charting/charts/HierarchyChart.js +39 -0
  156. package/src/charting/charts/HistogramChart.js +45 -0
  157. package/src/charting/charts/KpiTile.js +72 -0
  158. package/src/charting/charts/LagPlotChart.js +41 -0
  159. package/src/charting/charts/LineChart.js +124 -0
  160. package/src/charting/charts/MultiRingGauge.js +54 -0
  161. package/src/charting/charts/OutlineChart.js +95 -0
  162. package/src/charting/charts/ParallelCoordinatesChart.js +130 -0
  163. package/src/charting/charts/PieChart.js +56 -0
  164. package/src/charting/charts/PipeChart.js +88 -0
  165. package/src/charting/charts/PopulationPyramidChart.js +106 -0
  166. package/src/charting/charts/QQPlotChart.js +64 -0
  167. package/src/charting/charts/RadarChart.js +76 -0
  168. package/src/charting/charts/RadialBarChart.js +96 -0
  169. package/src/charting/charts/RingGauge.js +23 -0
  170. package/src/charting/charts/SankeyDiagram.js +174 -0
  171. package/src/charting/charts/ScatterChart.js +104 -0
  172. package/src/charting/charts/StackedAreaChart.js +15 -0
  173. package/src/charting/charts/StackedBarChart.js +33 -0
  174. package/src/charting/charts/StackedBarChart3DOrbit.js +58 -0
  175. package/src/charting/charts/StreamGraphChart.js +131 -0
  176. package/src/charting/charts/SunburstChart.js +57 -0
  177. package/src/charting/charts/ThermometerGauge.js +43 -0
  178. package/src/charting/charts/TreemapChart.js +54 -0
  179. package/src/charting/charts/ViolinChart.js +37 -0
  180. package/src/charting/charts/WaterfallChart.js +66 -0
  181. package/src/charting/charts/WordCloudChart.js +104 -0
  182. package/src/charting/diagramModel/AddViewCommand.js +33 -0
  183. package/src/charting/diagramModel/DiagramModel.js +39 -0
  184. package/src/charting/diagramModel/DiagramModelSearch.js +22 -0
  185. package/src/charting/diagramModel/DiagramView.js +50 -0
  186. package/src/charting/diagramModel/ModelHistory.js +43 -0
  187. package/src/charting/diagramModel/ModelReport.js +153 -0
  188. package/src/charting/diagramModel/ModelSearch.js +223 -0
  189. package/src/charting/diagramModel/RemoveViewCommand.js +25 -0
  190. package/src/charting/diagramModel/RenameViewCommand.js +21 -0
  191. package/src/charting/diagramModel/ReplaceTextCommand.js +25 -0
  192. package/src/charting/diagramModel/ViewSetChecker.js +34 -0
  193. package/src/charting/diagramModel/ViewSetModel.js +243 -0
  194. package/src/charting/diagramModel/index.js +6 -0
  195. package/src/charting/diagramModel/nameTransforms.js +29 -0
  196. package/src/charting/diagrams/ArchimateCommands.js +141 -0
  197. package/src/charting/diagrams/ArchimateDiagram.js +263 -0
  198. package/src/charting/diagrams/ArchimateModel.js +1155 -0
  199. package/src/charting/diagrams/ArchimateModelChecker.js +125 -0
  200. package/src/charting/diagrams/ArchimateModelReport.js +128 -0
  201. package/src/charting/diagrams/ArchimateModelSearch.js +41 -0
  202. package/src/charting/diagrams/ArchimateValidator.js +357 -0
  203. package/src/charting/diagrams/CanvasView.js +112 -0
  204. package/src/charting/diagrams/ContextMenu.js +37 -0
  205. package/src/charting/diagrams/DataModelDiagram.js +86 -0
  206. package/src/charting/diagrams/DataModelRelationshipStrategy.js +38 -0
  207. package/src/charting/diagrams/DerivedRelationships.js +102 -0
  208. package/src/charting/diagrams/EbnfDiagram.js +39 -0
  209. package/src/charting/diagrams/EdgeGeometry.js +553 -0
  210. package/src/charting/diagrams/EditViewItemCommand.js +128 -0
  211. package/src/charting/diagrams/ElementInstance.js +157 -0
  212. package/src/charting/diagrams/GraphDiagram.js +1340 -0
  213. package/src/charting/diagrams/GraphDiagram3D.js +152 -0
  214. package/src/charting/diagrams/GraphMetrics.js +81 -0
  215. package/src/charting/diagrams/GridOverlay.js +137 -0
  216. package/src/charting/diagrams/ImportRowsCommand.js +62 -0
  217. package/src/charting/diagrams/LabelHelper.js +108 -0
  218. package/src/charting/diagrams/MergeElementsCommand.js +101 -0
  219. package/src/charting/diagrams/NestInstanceCommand.js +34 -0
  220. package/src/charting/diagrams/NodeGeometry.js +158 -0
  221. package/src/charting/diagrams/PlaceItemsCommand.js +77 -0
  222. package/src/charting/diagrams/RadialLayout.js +101 -0
  223. package/src/charting/diagrams/RelationshipInstance.js +93 -0
  224. package/src/charting/diagrams/RemoveGroupFromViewCommand.js +28 -0
  225. package/src/charting/diagrams/RemoveLineCommand.js +27 -0
  226. package/src/charting/diagrams/ShowElementCommand.js +43 -0
  227. package/src/charting/diagrams/SvgCoordinate.js +17 -0
  228. package/src/charting/diagrams/SvgZoom.js +127 -0
  229. package/src/charting/diagrams/Tooltip.js +62 -0
  230. package/src/charting/diagrams/ViewConnectionInstance.js +102 -0
  231. package/src/charting/diagrams/ViewObjectInstance.js +56 -0
  232. package/src/charting/diagrams/commands/AddEdgeCommand.js +34 -0
  233. package/src/charting/diagrams/commands/AddNodeCommand.js +28 -0
  234. package/src/charting/diagrams/commands/AddWaypointCommand.js +27 -0
  235. package/src/charting/diagrams/commands/CreateFolderCommand.js +27 -0
  236. package/src/charting/diagrams/commands/DeleteEdgeCommand.js +30 -0
  237. package/src/charting/diagrams/commands/DeleteFolderCommand.js +47 -0
  238. package/src/charting/diagrams/commands/DeleteNodeCommand.js +35 -0
  239. package/src/charting/diagrams/commands/DeleteWaypointCommand.js +24 -0
  240. package/src/charting/diagrams/commands/GraphCommand.js +65 -0
  241. package/src/charting/diagrams/commands/InvertEdgeCommand.js +24 -0
  242. package/src/charting/diagrams/commands/MoveNodeCommand.js +44 -0
  243. package/src/charting/diagrams/commands/MoveWaypointCommand.js +27 -0
  244. package/src/charting/diagrams/commands/RenameEdgeCommand.js +23 -0
  245. package/src/charting/diagrams/commands/RenameFolderCommand.js +31 -0
  246. package/src/charting/diagrams/commands/RenameNodeCommand.js +27 -0
  247. package/src/charting/diagrams/commands/ResizeNodeCommand.js +46 -0
  248. package/src/charting/diagrams/commands/UpdateEdgeSideCommand.js +40 -0
  249. package/src/charting/diagrams/commands/index.js +17 -0
  250. package/src/charting/diagrams/duplicates.js +49 -0
  251. package/src/charting/diagrams/nesting.js +30 -0
  252. package/src/charting/diagrams/roadmap.js +77 -0
  253. package/src/charting/edgeMarkers/ArrowMarker.js +15 -0
  254. package/src/charting/edgeMarkers/CircleMarker.js +13 -0
  255. package/src/charting/edgeMarkers/CrowsFootMarker.js +16 -0
  256. package/src/charting/edgeMarkers/DiamondMarker.js +15 -0
  257. package/src/charting/edgeMarkers/EdgeMarker.js +34 -0
  258. package/src/charting/edgeMarkers/HalfArrowMarker.js +8 -0
  259. package/src/charting/edgeMarkers/HollowDiamondMarker.js +18 -0
  260. package/src/charting/edgeMarkers/HollowTriangleMarker.js +18 -0
  261. package/src/charting/edgeMarkers/OpenArrowMarker.js +17 -0
  262. package/src/charting/edgeMarkers/index.js +27 -0
  263. package/src/charting/edgeStyle/EdgeStyleCascade.js +22 -0
  264. package/src/charting/edgeStyle/EdgeStyleDialog.js +51 -0
  265. package/src/charting/edgeStyle/EdgeStyleSchema.js +43 -0
  266. package/src/charting/edgeStyle/SetStyleCommand.js +12 -0
  267. package/src/charting/edgeStyle/index.js +4 -0
  268. package/src/charting/emphasis/EmphasisRule.js +42 -0
  269. package/src/charting/emphasis/HighlightRule.js +24 -0
  270. package/src/charting/gauges/ArcTrack.js +51 -0
  271. package/src/charting/gauges/GaugeAnimation.js +51 -0
  272. package/src/charting/gauges/GaugeBands.js +48 -0
  273. package/src/charting/gauges/GaugeScale.js +87 -0
  274. package/src/charting/gauges/GaugeTrack.js +44 -0
  275. package/src/charting/gauges/GaugeValue.js +61 -0
  276. package/src/charting/gauges/HorizontalTrack.js +14 -0
  277. package/src/charting/gauges/LinearTrack.js +29 -0
  278. package/src/charting/gauges/Sparkline.js +42 -0
  279. package/src/charting/gauges/VerticalTrack.js +17 -0
  280. package/src/charting/glyphs/ArrowGlyph.js +21 -0
  281. package/src/charting/glyphs/BatteryGlyph.js +18 -0
  282. package/src/charting/glyphs/ChevronGlyph.js +18 -0
  283. package/src/charting/glyphs/CircleGlyph.js +15 -0
  284. package/src/charting/glyphs/CloudGlyph.js +19 -0
  285. package/src/charting/glyphs/ComponentGlyph.js +18 -0
  286. package/src/charting/glyphs/ConstraintGlyph.js +24 -0
  287. package/src/charting/glyphs/ContractBoxGlyph.js +10 -0
  288. package/src/charting/glyphs/CubeGlyph.js +19 -0
  289. package/src/charting/glyphs/CylinderGlyph.js +19 -0
  290. package/src/charting/glyphs/DashedDoubleArrowGlyph.js +11 -0
  291. package/src/charting/glyphs/DeviceGlyph.js +17 -0
  292. package/src/charting/glyphs/DiamondGlyph.js +17 -0
  293. package/src/charting/glyphs/DocumentGlyph.js +17 -0
  294. package/src/charting/glyphs/DoubleArrowGlyph.js +23 -0
  295. package/src/charting/glyphs/DoubleCircleGlyph.js +24 -0
  296. package/src/charting/glyphs/EllipseGlyph.js +18 -0
  297. package/src/charting/glyphs/FactoryGlyph.js +20 -0
  298. package/src/charting/glyphs/FlagGlyph.js +16 -0
  299. package/src/charting/glyphs/GapCircleGlyph.js +17 -0
  300. package/src/charting/glyphs/GearGlyph.js +37 -0
  301. package/src/charting/glyphs/Glyph.js +98 -0
  302. package/src/charting/glyphs/GroupingBoxGlyph.js +13 -0
  303. package/src/charting/glyphs/HalfCircleGlyph.js +22 -0
  304. package/src/charting/glyphs/HexagonGlyph.js +28 -0
  305. package/src/charting/glyphs/IconGlyph.js +22 -0
  306. package/src/charting/glyphs/ImageGlyph.js +16 -0
  307. package/src/charting/glyphs/JunctionGlyph.js +17 -0
  308. package/src/charting/glyphs/LollipopGlyph.js +22 -0
  309. package/src/charting/glyphs/MagnifierGlyph.js +17 -0
  310. package/src/charting/glyphs/NoteGlyph.js +33 -0
  311. package/src/charting/glyphs/ObjectBoxGlyph.js +21 -0
  312. package/src/charting/glyphs/OrJunctionGlyph.js +17 -0
  313. package/src/charting/glyphs/ParallelogramGlyph.js +20 -0
  314. package/src/charting/glyphs/PersonGlyph.js +23 -0
  315. package/src/charting/glyphs/PillGlyph.js +23 -0
  316. package/src/charting/glyphs/PinGlyph.js +18 -0
  317. package/src/charting/glyphs/PrincipleGlyph.js +23 -0
  318. package/src/charting/glyphs/ProductBoxGlyph.js +16 -0
  319. package/src/charting/glyphs/RectGlyph.js +15 -0
  320. package/src/charting/glyphs/RoundedBoxGlyph.js +13 -0
  321. package/src/charting/glyphs/StackGlyph.js +14 -0
  322. package/src/charting/glyphs/StepsGlyph.js +20 -0
  323. package/src/charting/glyphs/StickFigureGlyph.js +32 -0
  324. package/src/charting/glyphs/SystemSoftwareBoxGlyph.js +16 -0
  325. package/src/charting/glyphs/TabbedBoxGlyph.js +37 -0
  326. package/src/charting/glyphs/TargetGlyph.js +23 -0
  327. package/src/charting/glyphs/WavyBoxGlyph.js +17 -0
  328. package/src/charting/glyphs/WheelGlyph.js +28 -0
  329. package/src/charting/glyphs/drawGlyph.js +28 -0
  330. package/src/charting/glyphs/glyphSvg.js +55 -0
  331. package/src/charting/glyphs/index.js +109 -0
  332. package/src/charting/modelTree/AnalysisPanel.js +159 -0
  333. package/src/charting/modelTree/DiagramTabs.js +217 -0
  334. package/src/charting/modelTree/ModelTree.js +164 -0
  335. package/src/charting/modelTree/ModelTreePanel.js +914 -0
  336. package/src/charting/modelTree/TreeBuilder.js +238 -0
  337. package/src/charting/modelTree/TreeModeRegistry.js +51 -0
  338. package/src/charting/modelTree/TreeViewMode.js +110 -0
  339. package/src/charting/modelTree/buildFilterTrees.js +138 -0
  340. package/src/charting/modelTree/modes/FlatTreeMode.js +46 -0
  341. package/src/charting/modelTree/modes/LayerGroupedTreeMode.js +62 -0
  342. package/src/charting/modelTree/modes/TypeGroupedTreeMode.js +60 -0
  343. package/src/charting/modelTree/treeLayers.js +11 -0
  344. package/src/charting/report/reportHtml.js +82 -0
  345. package/src/charting/report/reportViewer.js +118 -0
  346. package/src/charting/shapes/CircleShape.js +19 -0
  347. package/src/charting/shapes/PathShape.js +17 -0
  348. package/src/charting/shapes/RectShape.js +20 -0
  349. package/src/charting/shapes/Shape.js +20 -0
  350. package/src/charting/shapes/ShapeFactory.js +23 -0
  351. package/src/charting/storage/LocalJsonStore.js +43 -0
  352. package/src/charting/templates/ActorTemplate.js +36 -0
  353. package/src/charting/templates/BoxTemplate.js +27 -0
  354. package/src/charting/templates/CircleTemplate.js +18 -0
  355. package/src/charting/templates/ClassTemplate.js +20 -0
  356. package/src/charting/templates/CloudTemplate.js +43 -0
  357. package/src/charting/templates/ComponentTemplate.js +19 -0
  358. package/src/charting/templates/CylinderTemplate.js +30 -0
  359. package/src/charting/templates/DataStoreTemplate.js +16 -0
  360. package/src/charting/templates/DiamondTemplate.js +10 -0
  361. package/src/charting/templates/DocumentTemplate.js +32 -0
  362. package/src/charting/templates/EllipseTemplate.js +15 -0
  363. package/src/charting/templates/FinalStateTemplate.js +12 -0
  364. package/src/charting/templates/GlyphTemplate.js +47 -0
  365. package/src/charting/templates/HexagonTemplate.js +17 -0
  366. package/src/charting/templates/LifelineTemplate.js +20 -0
  367. package/src/charting/templates/ManualInputTemplate.js +12 -0
  368. package/src/charting/templates/NoteTemplate.js +23 -0
  369. package/src/charting/templates/OctagonTemplate.js +7 -0
  370. package/src/charting/templates/PackageTemplate.js +25 -0
  371. package/src/charting/templates/ParallelogramTemplate.js +11 -0
  372. package/src/charting/templates/PentagonTemplate.js +5 -0
  373. package/src/charting/templates/PolygonTemplate.js +18 -0
  374. package/src/charting/templates/PredefinedProcessTemplate.js +13 -0
  375. package/src/charting/templates/RectangleTemplate.js +28 -0
  376. package/src/charting/templates/RegularPolygonTemplate.js +21 -0
  377. package/src/charting/templates/RoundedRectangleTemplate.js +5 -0
  378. package/src/charting/templates/StadiumTemplate.js +8 -0
  379. package/src/charting/templates/StarTemplate.js +17 -0
  380. package/src/charting/templates/SwimlaneTemplate.js +18 -0
  381. package/src/charting/templates/Template.js +208 -0
  382. package/src/charting/templates/TrapezoidTemplate.js +11 -0
  383. package/src/charting/templates/TriangleTemplate.js +12 -0
  384. package/src/charting/templates/index.js +31 -0
  385. package/src/charting/templates/outline.js +59 -0
  386. package/src/charting/templates/svg.js +19 -0
  387. package/src/charting/textToShapes.js +61 -0
  388. package/src/charting/toolbar/ToolbarButton.js +38 -0
  389. package/src/charting/toolbar/ToolbarCheckbox.js +31 -0
  390. package/src/charting/toolbar/ToolbarItem.js +19 -0
  391. package/src/charting/toolbar/ToolbarMenu.js +36 -0
  392. package/src/charting/toolbar/ToolbarSelect.js +38 -0
  393. package/src/charting/toolbar/ToolbarSeparator.js +7 -0
  394. package/src/charting/toolbar/ToolbarText.js +22 -0
  395. package/src/charting/toolbar/index.js +8 -0
  396. package/src/charting/toolbar/pickFile.js +14 -0
  397. package/src/charting/viewDecorations.js +85 -0
  398. package/src/charting/zOrder.js +57 -0
  399. package/src/csvTable.js +61 -0
  400. package/src/datamodel.js +122 -0
  401. package/src/datawindow/BoundedWindow.js +36 -0
  402. package/src/datawindow/FlatDataWindow.js +34 -0
  403. package/src/datawindow/GraphDataWindow.js +40 -0
  404. package/src/datawindow/GroupedSampleDataWindow.js +16 -0
  405. package/src/datawindow/SeriesDataWindow.js +42 -0
  406. package/src/datawindow/index.js +31 -0
  407. package/src/diagramCatalog.js +30 -0
  408. package/src/ebnfDiagram.js +57 -0
  409. package/src/ebnfParser.js +129 -0
  410. package/src/exportImage.js +233 -0
  411. package/src/index.js +2636 -0
  412. package/src/itIcons.js +84 -0
  413. package/src/kernelExpr.js +243 -0
  414. package/src/propertiesDialog.js +463 -0
  415. package/src/railroadDiagram.js +571 -0
  416. package/src/treeModeCatalog.js +16 -0
  417. package/src/workspace/ContextBus.js +79 -0
  418. package/src/workspace/EditorShell.js +67 -0
  419. package/src/workspace/Part.js +163 -0
  420. package/src/workspace/PartFactory.js +49 -0
  421. package/src/workspace/PartRegistry.js +86 -0
  422. package/src/workspace/Workspace.js +321 -0
  423. package/src/workspace/archimateServices.js +33 -0
  424. package/src/workspace/dom.js +120 -0
  425. package/src/workspace/index.js +75 -0
  426. package/src/workspace/layout/GridNode.js +280 -0
  427. package/src/workspace/layout/LayoutNode.js +89 -0
  428. package/src/workspace/layout/PartNode.js +77 -0
  429. package/src/workspace/layout/SplitNode.js +114 -0
  430. package/src/workspace/layout/TabsNode.js +191 -0
  431. package/src/workspace/layout/index.js +24 -0
  432. package/src/workspace/parts/AdapterPart.js +33 -0
  433. package/src/workspace/parts/ArchimateCanvasPart.js +345 -0
  434. package/src/workspace/parts/ArchimateEditorPart.js +79 -0
  435. package/src/workspace/parts/ChartPart.js +115 -0
  436. package/src/workspace/parts/ContentPart.js +34 -0
  437. package/src/workspace/parts/DetailPart.js +46 -0
  438. package/src/workspace/parts/DiagramEditorPart.js +135 -0
  439. package/src/workspace/parts/EditorPart.js +388 -0
  440. package/src/workspace/parts/HtmlPart.js +25 -0
  441. package/src/workspace/parts/ListPart.js +57 -0
  442. package/src/workspace/parts/ModelFile.js +187 -0
  443. package/src/workspace/parts/ModelsPart.js +135 -0
  444. package/src/workspace/styles.js +258 -0
  445. package/src/workspace/themes.js +96 -0
  446. package/src/workspace/workArea.js +413 -0
  447. package/src/workspace/workAreaModel.js +197 -0
  448. package/src/workspace.js +16 -0
  449. package/src/zip/ZipArchive.js +160 -0
  450. package/src/zip/crc32.js +25 -0
  451. package/src/zip/inflate.js +159 -0
package/src/index.js ADDED
@@ -0,0 +1,2636 @@
1
+ import { RendererFactory } from './RendererFactory.js';
2
+ import { Mat4 } from './Mat4.js';
3
+ import { OrbitCamera } from './OrbitCamera.js';
4
+ import {
5
+ CHART_CATALOG, getChartCatalogEntry, chartRegistry,
6
+ AXIS_KIND, ENCODING_CHANNEL, COORD_SYSTEM, COLOR_MODE, CAMERA_MODE,
7
+ } from './chartCatalog.js';
8
+ import {
9
+ DIAGRAM_CATALOG, getDiagramCatalogEntry, diagramRegistry,
10
+ } from './diagramCatalog.js';
11
+ import { ChartBox, ChartLabels } from './charting/ChartBox.js';
12
+ import { Axis } from './charting/Axis.js';
13
+ import { Legend } from './charting/Legend.js';
14
+ import { ColorPalette } from './charting/ColorPalette.js';
15
+ import { ShapePaths } from './charting/ShapePaths.js';
16
+ import { BarChart } from './charting/charts/BarChart.js';
17
+ import { GroupedBarChart } from './charting/charts/GroupedBarChart.js';
18
+ import { LineChart } from './charting/charts/LineChart.js';
19
+ import { AreaChart } from './charting/charts/AreaChart.js';
20
+ import { StackedAreaChart } from './charting/charts/StackedAreaChart.js';
21
+ import { PieChart } from './charting/charts/PieChart.js';
22
+ import { DonutChart } from './charting/charts/DonutChart.js';
23
+ import { PipeChart } from './charting/charts/PipeChart.js';
24
+ import { StackedBarChart } from './charting/charts/StackedBarChart.js';
25
+ import { ScatterChart } from './charting/charts/ScatterChart.js';
26
+ import { BubbleChart } from './charting/charts/BubbleChart.js';
27
+ import { HistogramChart } from './charting/charts/HistogramChart.js';
28
+ import { BoxPlotChart } from './charting/charts/BoxPlotChart.js';
29
+ import { DensityChart } from './charting/charts/DensityChart.js';
30
+ import { ViolinChart } from './charting/charts/ViolinChart.js';
31
+ import { QQPlotChart } from './charting/charts/QQPlotChart.js';
32
+ import { LagPlotChart } from './charting/charts/LagPlotChart.js';
33
+ import { TreemapChart } from './charting/charts/TreemapChart.js';
34
+ import { SunburstChart } from './charting/charts/SunburstChart.js';
35
+ import { OutlineChart } from './charting/charts/OutlineChart.js';
36
+ import { RadarChart } from './charting/charts/RadarChart.js';
37
+ import { FunnelChart } from './charting/charts/FunnelChart.js';
38
+ import { WaterfallChart } from './charting/charts/WaterfallChart.js';
39
+ import { BarGauge } from './charting/charts/BarGauge.js';
40
+ import { BulletGauge } from './charting/charts/BulletGauge.js';
41
+ import { DialGauge } from './charting/charts/DialGauge.js';
42
+ import { ArcGauge } from './charting/charts/ArcGauge.js';
43
+ import { RingGauge } from './charting/charts/RingGauge.js';
44
+ import { MultiRingGauge } from './charting/charts/MultiRingGauge.js';
45
+ import { KpiTile } from './charting/charts/KpiTile.js';
46
+ import { ThermometerGauge } from './charting/charts/ThermometerGauge.js';
47
+ import { GaugeAnimation } from './charting/gauges/GaugeAnimation.js';
48
+ import { GaugeValue } from './charting/gauges/GaugeValue.js';
49
+ import { BarChart3D } from './charting/charts/BarChart3D.js';
50
+ import { BubbleChart3D } from './charting/charts/BubbleChart3D.js';
51
+ import { BarChart3DOrbit } from './charting/charts/BarChart3DOrbit.js';
52
+ import { StackedBarChart3DOrbit } from './charting/charts/StackedBarChart3DOrbit.js';
53
+ import { CalendarHeatmapChart } from './charting/charts/CalendarHeatmapChart.js';
54
+ import { GanttChart } from './charting/charts/GanttChart.js';
55
+ import { StreamGraphChart } from './charting/charts/StreamGraphChart.js';
56
+ import { RadialBarChart } from './charting/charts/RadialBarChart.js';
57
+ import { PopulationPyramidChart } from './charting/charts/PopulationPyramidChart.js';
58
+ import { SankeyDiagram } from './charting/charts/SankeyDiagram.js';
59
+ import { ChordDiagram } from './charting/charts/ChordDiagram.js';
60
+ import { WordCloudChart } from './charting/charts/WordCloudChart.js';
61
+ import { ParallelCoordinatesChart } from './charting/charts/ParallelCoordinatesChart.js';
62
+ import { GeoMapChart } from './charting/charts/GeoMapChart.js';
63
+ import { HeatmapChart } from './charting/charts/HeatmapChart.js';
64
+ import { CandlestickChart } from './charting/charts/CandlestickChart.js';
65
+ import { ShapeFactory } from './charting/shapes/ShapeFactory.js';
66
+ import { Statistics } from './charting/Statistics.js';
67
+ import { Chart3D } from './charting/Chart3D.js';
68
+ import { Hierarchy } from './charting/Hierarchy.js';
69
+ import { Gauge } from './charting/Gauge.js';
70
+ import { ChartData } from './charting/ChartData.js';
71
+ import { DataFilter } from './charting/DataFilter.js';
72
+ import { InteractionManager } from './charting/InteractionManager.js';
73
+ import { ebnfAstToDiagram } from './ebnfDiagram.js';
74
+ import { parseEbnf } from './ebnfParser.js';
75
+ import { Command, CompositeCommand, CommandHistory } from './charting/CommandHistory.js';
76
+ import { Diagram } from './charting/Diagram.js';
77
+ import { Diagram3D } from './charting/Diagram3D.js';
78
+ import { EbnfDiagram } from './charting/diagrams/EbnfDiagram.js';
79
+ import { GraphDiagram } from './charting/diagrams/GraphDiagram.js';
80
+ import { GraphDiagram3D } from './charting/diagrams/GraphDiagram3D.js';
81
+ import { GraphMetrics } from './charting/diagrams/GraphMetrics.js';
82
+ import { LabelHelper } from './charting/diagrams/LabelHelper.js';
83
+ import { NodeGeometry } from './charting/diagrams/NodeGeometry.js';
84
+ import { SvgCoordinate } from './charting/diagrams/SvgCoordinate.js';
85
+ import { EdgeGeometry } from './charting/diagrams/EdgeGeometry.js';
86
+ import { RadialLayout } from './charting/diagrams/RadialLayout.js';
87
+ import { DerivedRelationships } from './charting/diagrams/DerivedRelationships.js';
88
+ import { ArchimateDiagram } from './charting/diagrams/ArchimateDiagram.js';
89
+ import { DataModelDiagram } from './charting/diagrams/DataModelDiagram.js';
90
+ import { createDataWindow, GraphDataWindow } from './datawindow/index.js';
91
+ import { compileFormula } from './kernelExpr.js';
92
+ import { ArchimateNotation, ArchimateLayer, ArchimateElementType, ArchimateRelationshipType, ArchimateViewpoints } from './archimate.js';
93
+ import { ITIconNotation, ITElementType } from './itIcons.js';
94
+ import { ArchimateValidator } from './charting/diagrams/ArchimateValidator.js';
95
+ import { ArchimateSpecializationRegistry } from './archimateSpecializations.js';
96
+ import { ArchimateSpecializationSelector } from './charting/ArchimateSpecializationSelector.js';
97
+ import { ImageSelector } from './charting/ImageSelector.js';
98
+ import { CheckboxTreeSelector } from './charting/CheckboxTreeSelector.js';
99
+ import { SettingsManager, settingsManager } from './charting/SettingsManager.js';
100
+ import { showGlobalSettingsDialog } from './charting/SettingsDialog.js';
101
+ import { ArchimateModel, ArchimateView } from './charting/diagrams/ArchimateModel.js';
102
+ import { CanvasView } from './charting/diagrams/CanvasView.js';
103
+ import { PlaceItemsCommand } from './charting/diagrams/PlaceItemsCommand.js';
104
+ import { RemoveLineCommand } from './charting/diagrams/RemoveLineCommand.js';
105
+ import { CanvasBoxKind } from './charting/canvas/CanvasBoxKind.js';
106
+ import { CANVAS_BOX_KINDS, canvasBoxKind } from './charting/canvas/canvasBoxKinds.js';
107
+ import { canvasBoxMenuItems } from './charting/canvas/canvasBoxMenuItems.js';
108
+ import { ArchiHint } from './charting/canvas/ArchiHint.js';
109
+ import { HintPanel } from './charting/HintPanel.js';
110
+ import { EditHintCommand } from './charting/canvas/EditHintCommand.js';
111
+ import { hintMenuItems } from './charting/canvas/hintMenuItems.js';
112
+ import { CanvasPalette, CANVAS_BOX_MIME } from './charting/canvas/CanvasPalette.js';
113
+ import { ViewLineEdge } from './charting/archimate/ViewLineEdge.js';
114
+ import { ElementInstance } from './charting/diagrams/ElementInstance.js';
115
+ import { ViewObjectInstance } from './charting/diagrams/ViewObjectInstance.js';
116
+ import { Glyph, GLYPHS, glyphFor, drawGlyph } from './charting/glyphs/index.js';
117
+ import { glyphElement, glyphToSvg } from './charting/glyphs/glyphSvg.js';
118
+ import { GlyphTemplate } from './charting/templates/GlyphTemplate.js';
119
+ import { ArchimateElementTemplate, ArchimateEdge, ArchimateCanvasEditor, ArchimatePalette, KIND_PREFERENCE, ARCHIMATE_TYPE_MIME } from './charting/archimate/index.js';
120
+ import { RelationshipInstance } from './charting/diagrams/RelationshipInstance.js';
121
+ import { DiagramEditor } from './charting/DiagramEditor.js';
122
+ import { DiagramEditorState } from './charting/DiagramEditorState.js';
123
+ import { Template, PolygonTemplate, RegularPolygonTemplate, RectangleTemplate, RoundedRectangleTemplate, StadiumTemplate, PredefinedProcessTemplate, ClassTemplate, ComponentTemplate, SwimlaneTemplate, CircleTemplate, FinalStateTemplate, EllipseTemplate, DiamondTemplate, TriangleTemplate, PentagonTemplate, HexagonTemplate, OctagonTemplate, StarTemplate, ParallelogramTemplate, TrapezoidTemplate, ManualInputTemplate, CylinderTemplate, DocumentTemplate, NoteTemplate, CloudTemplate, ActorTemplate, PackageTemplate, BoxTemplate, DataStoreTemplate, LifelineTemplate } from './charting/templates/index.js';
124
+ import { TemplateCatalog } from './charting/TemplateCatalog.js';
125
+ import { DIAGRAM_TYPES, getTemplatesForDiagramType, getAllDiagramTypes, getDiagramTypesByGroup } from './charting/DiagramTypeRegistry.js';
126
+ import { EdgeMarker, ArrowMarker, DiamondMarker, CircleMarker, CrowsFootMarker, HollowDiamondMarker, HollowTriangleMarker, OpenArrowMarker, HalfArrowMarker, EDGE_MARKERS, getEdgeMarker } from './charting/edgeMarkers/index.js';
127
+ import { Palette } from './charting/Palette.js';
128
+ import { MoveElementCommand, BringToFrontCommand, SendToBackCommand, RaiseCommand, LowerCommand, CreateTemplateCommand, EditPropertyCommand, ResizeCommand, RotateCommand, SetPropertiesCommand, ReorderCommand } from './charting/DiagramEditorCommands.js';
129
+ import { Edge } from './charting/Edge.js';
130
+ import { AddEdgeCommand, DeleteEdgeCommand, ReconnectEdgeCommand, MoveWaypointCommand, AddWaypointCommand, DeleteWaypointCommand } from './charting/DiagramEditorEdgeCommands.js';
131
+ import { EDGE_STYLE_PROPERTIES, LINE_ONLY_PROPERTIES, LINE_STYLE_DASH, EdgeStyleCascade, SetStyleCommand, EdgeStyleDialog } from './charting/edgeStyle/index.js';
132
+ import { DiagramModel, DiagramView, ViewSetModel } from './charting/diagramModel/index.js';
133
+ import { ViewMenu } from './charting/ViewMenu.js';
134
+ import { CanvasTemplateLibrary } from './charting/canvas/CanvasTemplateLibrary.js';
135
+ import { CanvasTemplateMenu } from './charting/canvas/CanvasTemplateMenu.js';
136
+ import { TemplateLibrary } from './charting/TemplateLibrary.js';
137
+ import { Arrangement, ItemGroup, BackgroundGroup, GroupCommand, UngroupCommand, LockCommand, BackgroundCommand, arrangeMenuItems } from './charting/arrangement/index.js';
138
+ import { ViewPropertiesDialog } from './charting/ViewPropertiesDialog.js';
139
+ import { SettingsViewPropertiesDialog } from './charting/SettingsViewPropertiesDialog.js';
140
+ import { DiagramViewPropertiesDialog } from './charting/DiagramViewPropertiesDialog.js';
141
+ import { inheritableFields, readInheritableFields } from './charting/SettingsCascade.js';
142
+ import { LocalJsonStore } from './charting/storage/LocalJsonStore.js';
143
+ import { PopupMenu } from './charting/PopupMenu.js';
144
+ import { ViewTabStrip } from './charting/ViewTabStrip.js';
145
+ import { EditorShell } from './workspace/EditorShell.js';
146
+ import { FormDialog } from './charting/FormDialog.js';
147
+ import { ColorField } from './charting/ColorField.js';
148
+ import { TextStyle } from './charting/TextStyle.js';
149
+ import { ArchimateModelChecker } from './charting/diagrams/ArchimateModelChecker.js';
150
+ import { ViewSetChecker } from './charting/diagramModel/ViewSetChecker.js';
151
+ import { ViewFit } from './charting/ViewFit.js';
152
+ import { archiElementType, NEAREST_ARCHI_TYPE } from './archimateArchiTypes.js';
153
+ import { DEFAULT_CANVAS_TEMPLATES, canvasTemplate } from './charting/canvas/defaultCanvasTemplates.js';
154
+ import { EditViewItemCommand, viewItemChanges } from './charting/diagrams/EditViewItemCommand.js';
155
+ import { ToolbarItem, ToolbarButton, ToolbarMenu, ToolbarSelect, ToolbarCheckbox, ToolbarText, ToolbarSeparator, pickFile } from './charting/toolbar/index.js';
156
+ import { reorderedZ, stackingOrder } from './charting/zOrder.js';
157
+ import { PropertiesPanel } from './charting/PropertiesPanel.js';
158
+ import { MenuBar } from './charting/MenuBar.js';
159
+ import { ArchimateDiagramEditor } from './charting/ArchimateDiagramEditor.js';
160
+ import { ModelTree } from './charting/modelTree/ModelTree.js';
161
+ import { TreeModeRegistry } from './charting/modelTree/TreeModeRegistry.js';
162
+ import { TREE_MODE_CATALOG, treeModeRegistry, getTreeModeCatalogEntry } from './treeModeCatalog.js';
163
+ import { buildModelTreePanel } from './charting/modelTree/ModelTreePanel.js';
164
+ import { buildAnalysisPanel, buildAnalysisData } from './charting/modelTree/AnalysisPanel.js';
165
+ import { buildTypeCategoryTree, buildPropertyTree } from './charting/modelTree/buildFilterTrees.js';
166
+ import { DiagramTabs } from './charting/modelTree/DiagramTabs.js';
167
+ import { DataModelNotation, DataModelLevel, NotationStyle } from './datamodel.js';
168
+ import {
169
+ ARCHI_VISUAL_OBJECTS, ARCHI_VIEW_TYPES,
170
+ parseXML, parseArchiModel, toRenderEngineModel, toArchimateModel, toArchiXML, fromArchimateView, toArchiExport,
171
+ } from './archimateArchiFormat.js';
172
+ import { parseExchangeModel, toExchangeXML, EXCHANGE_NAMESPACE, EXCHANGE_TYPE_PROPERTY, EXCHANGE_VIEWPOINTS } from './archimateExchangeFormat.js';
173
+ import { ModelFileFormat, modelFileMenuItems, importFile } from './charting/ModelFileFormat.js';
174
+ import { ArchiFileFormat, ExchangeFileFormat, ArchimateCsvFileFormat, ARCHIMATE_FILE_FORMATS } from './charting/archimate/index.js';
175
+ import { ShowElementCommand } from './charting/diagrams/ShowElementCommand.js';
176
+ import { EmphasisRule, EMPHASIS_OPACITY, EMPHASIS_LEVELS, strongerEmphasis } from './charting/emphasis/EmphasisRule.js';
177
+ import { HighlightRule } from './charting/emphasis/HighlightRule.js';
178
+ import { CatalogDialog } from './charting/CatalogDialog.js';
179
+ import { CustomViewpoints, CUSTOM_VIEWPOINTS_STORE, specializationCatalogDialog, elementTypeChoices } from './charting/archimate/index.js';
180
+ import { ViewpointRule, ElementFilterRule } from './charting/archimate/index.js';
181
+ import { MODEL_ELEMENT_MIME, TREE_LAYERS, treeLayerOf } from './charting/modelTree/ModelTreePanel.js';
182
+ import { parseArchiFile, toArchiFile, ARCHI_MODEL_ENTRY } from './archimateArchive.js';
183
+ import { ZipArchive } from './zip/ZipArchive.js';
184
+ import {
185
+ exportElementsCSV, exportRelationshipsCSV, exportModelCSV,
186
+ importElementsCSV, importRelationshipsCSV, importModelCSV,
187
+ } from './archimateCSV.js';
188
+ // Safe to import here despite being browser-only in effect: every DOM/clipboard
189
+ // reference sits inside a function body, so loading it under node does nothing.
190
+ import {
191
+ copyImage, toPngBlob, downloadPng, downloadSvg, downloadBlob,
192
+ serializeSvg, prepareSvgMarkup, imageSize, imageFileName, findDrawable, MAX_IMAGE_PIXELS,
193
+ } from './exportImage.js';
194
+ // Same reasoning as exportImage: every DOM reference sits inside a function
195
+ // body, so loading this under node does nothing.
196
+ import {
197
+ showPropertiesDialog, showDiagramPropertiesDialog,
198
+ propertyRowsFor, coerceFieldValue, derivedRefreshPlan, LAYER_CHOICES, NODE_SECTIONS,
199
+ } from './propertiesDialog.js';
200
+
201
+ // NOTE: `src/workspace.js` is deliberately NOT re-exported here. It subclasses
202
+ // HTMLElement at module scope, so merely importing it throws outside a browser
203
+ // — and this module is the node entry point the test suite loads. It is a
204
+ // separate browser-only entry: import 'renderengine/src/workspace.js' directly.
205
+
206
+ export {
207
+ Mat4, OrbitCamera, CHART_CATALOG, getChartCatalogEntry, chartRegistry,
208
+ DIAGRAM_CATALOG, getDiagramCatalogEntry, diagramRegistry,
209
+ TREE_MODE_CATALOG, treeModeRegistry, getTreeModeCatalogEntry,
210
+ AXIS_KIND, ENCODING_CHANNEL, COORD_SYSTEM, COLOR_MODE, CAMERA_MODE,
211
+ Command, CompositeCommand, CommandHistory,
212
+ DiagramEditor, DiagramEditorState, Template, PolygonTemplate, RegularPolygonTemplate, RectangleTemplate, RoundedRectangleTemplate, StadiumTemplate, PredefinedProcessTemplate, ClassTemplate, ComponentTemplate, SwimlaneTemplate, CircleTemplate, FinalStateTemplate, EllipseTemplate, DiamondTemplate, TriangleTemplate, PentagonTemplate, HexagonTemplate, OctagonTemplate, StarTemplate, ParallelogramTemplate, TrapezoidTemplate, ManualInputTemplate, CylinderTemplate, DocumentTemplate, NoteTemplate, CloudTemplate, ActorTemplate, PackageTemplate, BoxTemplate, DataStoreTemplate, LifelineTemplate, TemplateCatalog, Palette, PropertiesPanel, MenuBar, MoveElementCommand, BringToFrontCommand, SendToBackCommand, RaiseCommand, LowerCommand, CreateTemplateCommand, EditPropertyCommand, ResizeCommand, RotateCommand, SetPropertiesCommand, ReorderCommand, DIAGRAM_TYPES, getTemplatesForDiagramType, getAllDiagramTypes, getDiagramTypesByGroup, EdgeMarker, ArrowMarker, DiamondMarker, CircleMarker, CrowsFootMarker, HollowDiamondMarker, HollowTriangleMarker, OpenArrowMarker, HalfArrowMarker, EDGE_MARKERS, getEdgeMarker, Edge, AddEdgeCommand, DeleteEdgeCommand, ReconnectEdgeCommand, EDGE_STYLE_PROPERTIES, LINE_ONLY_PROPERTIES, LINE_STYLE_DASH, EdgeStyleCascade, SetStyleCommand, EdgeStyleDialog, DiagramModel, DiagramView, ViewSetModel, ViewMenu, ViewPropertiesDialog, SettingsViewPropertiesDialog, DiagramViewPropertiesDialog, inheritableFields, readInheritableFields, LocalJsonStore, PopupMenu, ViewTabStrip, EditorShell, FormDialog, ToolbarItem, ToolbarButton, ToolbarMenu, ToolbarSelect, ToolbarCheckbox, ToolbarText, ToolbarSeparator, pickFile, reorderedZ, stackingOrder, MoveWaypointCommand, AddWaypointCommand, DeleteWaypointCommand,
213
+ ebnfAstToDiagram, parseEbnf,
214
+ createDataWindow, GraphDataWindow,
215
+ ArchimateNotation, ArchimateLayer, ArchimateElementType, ArchimateRelationshipType,
216
+ Glyph, GLYPHS, glyphFor, drawGlyph, glyphElement, glyphToSvg, GlyphTemplate, ArchimateElementTemplate, ArchimateEdge, ArchimateCanvasEditor, ArchimatePalette, KIND_PREFERENCE, ARCHIMATE_TYPE_MIME,
217
+ ArchimateModel, ArchimateView, CanvasView, PlaceItemsCommand, RemoveLineCommand, CanvasBoxKind, CANVAS_BOX_KINDS, canvasBoxKind, canvasBoxMenuItems, ArchiHint, HintPanel, EditHintCommand, hintMenuItems, CanvasPalette, CANVAS_BOX_MIME, ViewLineEdge, Arrangement, ItemGroup, BackgroundGroup, GroupCommand, UngroupCommand, LockCommand, BackgroundCommand, arrangeMenuItems, TemplateLibrary, CanvasTemplateLibrary, CanvasTemplateMenu, DEFAULT_CANVAS_TEMPLATES, canvasTemplate, EditViewItemCommand, viewItemChanges, ColorField, TextStyle, ArchimateModelChecker, ViewSetChecker, ViewFit, archiElementType, NEAREST_ARCHI_TYPE, ElementInstance, ViewObjectInstance, RelationshipInstance, ArchimateDiagramEditor,
218
+ ITIconNotation, ITElementType,
219
+ ArchimateValidator, ArchimateSpecializationSelector, ImageSelector, CheckboxTreeSelector, SettingsManager, settingsManager,
220
+ ModelTree, TreeModeRegistry,
221
+ RadialLayout, DerivedRelationships, EdgeGeometry,
222
+ buildModelTreePanel, buildAnalysisPanel, buildAnalysisData, buildTypeCategoryTree, buildPropertyTree, DiagramTabs,
223
+ DataModelNotation, DataModelLevel, NotationStyle,
224
+ exportElementsCSV, exportRelationshipsCSV, exportModelCSV,
225
+ importElementsCSV, importRelationshipsCSV, importModelCSV,
226
+ parseArchiModel, toArchimateModel, fromArchimateView, toArchiExport,
227
+ parseExchangeModel, toExchangeXML, EXCHANGE_NAMESPACE, EXCHANGE_TYPE_PROPERTY, EXCHANGE_VIEWPOINTS,
228
+ ModelFileFormat, modelFileMenuItems, importFile, ArchiFileFormat, ExchangeFileFormat, ArchimateCsvFileFormat, ARCHIMATE_FILE_FORMATS,
229
+ ShowElementCommand, MODEL_ELEMENT_MIME, TREE_LAYERS, treeLayerOf,
230
+ EmphasisRule, EMPHASIS_OPACITY, EMPHASIS_LEVELS, strongerEmphasis, HighlightRule, ViewpointRule, ElementFilterRule,
231
+ CatalogDialog, CustomViewpoints, CUSTOM_VIEWPOINTS_STORE, specializationCatalogDialog, elementTypeChoices,
232
+ parseArchiFile, toArchiFile, ARCHI_MODEL_ENTRY, ZipArchive,
233
+ copyImage, toPngBlob, downloadPng, downloadSvg, downloadBlob,
234
+ serializeSvg, prepareSvgMarkup, imageSize, imageFileName, findDrawable, MAX_IMAGE_PIXELS,
235
+ showPropertiesDialog, showDiagramPropertiesDialog, showGlobalSettingsDialog,
236
+ propertyRowsFor, coerceFieldValue, derivedRefreshPlan, LAYER_CHOICES, NODE_SECTIONS,
237
+ };
238
+
239
+ /**
240
+ * @class RenderEngine
241
+ * This class decides which renderer to use and provides a unified drawing API.
242
+ */
243
+ export class RenderEngine {
244
+ /** Layout constants for graphDiagram's `shape: 'table'` node type. */
245
+ static TABLE_NODE = NodeGeometry.TABLE_NODE;
246
+
247
+ /** ArchiMate standard-alignment validator (relationship validity, layer
248
+ * consistency) — exposed so callers building ArchiMate authoring UIs can
249
+ * filter relationship-kind pickers without duplicating rules. */
250
+ static ArchimateValidator = ArchimateValidator;
251
+
252
+ /** IT / infrastructure icon extension catalog — firewall, laptop, server,
253
+ * database, container, etc. These are not official ArchiMate types but are
254
+ * resolved by `archimateDiagram` and its construction UI as custom types
255
+ * with sensible layer/aspect/icon defaults. */
256
+ static ITIconNotation = ITIconNotation;
257
+ static ITElementType = ITElementType;
258
+
259
+ /** graphDiagram() node fields that are structural, not caller "extra data"
260
+ * — excluded from the auto-generated extra-data tooltip lines (see
261
+ * _nodeExtraDataLines). Table nodes have their own richer set since
262
+ * columns/rowCount/width are structural there, not extra data. */
263
+ static NODE_STRUCTURAL_KEYS = new Set([
264
+ 'id', 'name', 'label', 'x', 'y', 'shape', 'color', 'group', 'size', 'radius', 'width', 'height',
265
+ 'icon', 'iconUrl', 'iconShape', 'iconPosition', 'iconCorner', 'labelPosition', 'textAlign', 'verticalAlign',
266
+ 'resizable', 'resizeCorner', 'cornerStyle', 'strokeColor', 'strokeWidth', 'href', 'properties', 'specialization', 'useDistinctShape',
267
+ // How its text is drawn (TextStyle, in the node's names) and Archi's class for a canvas box: look, not data.
268
+ 'textColor', 'fontFamily', 'fontSize', 'fontWeight', 'fontStyle', 'textDecoration', 'archiVisualType',
269
+ ]);
270
+ static TABLE_NODE_STRUCTURAL_KEYS = new Set(['id', 'name', 'label', 'x', 'y', 'shape', 'color', 'group', 'columns', 'rowCount', 'width', 'resizable', 'resizeCorner']);
271
+ /** graphDiagram() edge fields that are structural, not caller "extra
272
+ * data" — same idea as NODE_STRUCTURAL_KEYS, for edges (see the
273
+ * Properties-panel example in test/charts.html, which uses this to show
274
+ * only a caller's own custom fields as editable). */
275
+ static EDGE_STRUCTURAL_KEYS = new Set([
276
+ 'source', 'target', 'label', 'sourceLabel', 'targetLabel', 'weight', 'color', 'dashed', 'lineStyle',
277
+ 'arrowhead', 'arrowheadStart', 'routing', 'sourceSide', 'targetSide', 'waypoints', 'fromCol', 'toCol',
278
+ 'kind', 'properties', 'dasharray', 'tooltip', '_archimateWarning',
279
+ ]);
280
+
281
+ /** The ArchiMate 3.2 notation registry (layers, the official element
282
+ * type catalog, relationship notations) archimateDiagram is built on —
283
+ * see src/archimate.js, which owns all of it; this is just a
284
+ * convenient static so callers already holding a RenderEngine instance
285
+ * (`engine.constructor.ArchimateNotation`) don't need a second import,
286
+ * the same reason graphDiagram's own NODE_STRUCTURAL_KEYS etc. live
287
+ * here as statics. */
288
+ static ArchimateNotation = ArchimateNotation;
289
+
290
+ /** ArchiMate viewpoints catalog and helpers — see src/archimate.js. */
291
+ static ArchimateViewpoints = ArchimateViewpoints;
292
+
293
+ /** Editable specialization catalog for ArchiMate — user-defined
294
+ * specializations linked to base types with their own icon/colour.
295
+ * See src/archimateSpecializations.js. */
296
+ static ArchimateSpecializationRegistry = ArchimateSpecializationRegistry;
297
+
298
+ /** Model tree view mode registry (byLayer, byType, flat) — exposed so
299
+ * callers building model tree UIs can populate mode pickers from the
300
+ * registry rather than hardcoding options. See src/treeModeCatalog.js. */
301
+ static treeModeRegistry = treeModeRegistry;
302
+
303
+ /** The data-modelling notation registry (levels, notation styles,
304
+ * cardinality vocabulary) dataModelDiagram is built on — see
305
+ * src/datamodel.js. Same convenience-static reasoning as
306
+ * ArchimateNotation above. */
307
+ static DataModelNotation = DataModelNotation;
308
+
309
+ /** outlineChart()'s node fields that are structural, not caller "extra
310
+ * data" — same idea as NODE_STRUCTURAL_KEYS above, for the
311
+ * treemapChart/sunburstChart/outlineChart shared hierarchy shape. */
312
+ static HIERARCHY_STRUCTURAL_KEYS = new Set(['label', 'value', 'children']);
313
+
314
+ /** Archi native `.archimate` XML visual-object type constants. */
315
+ static ARCHI_VISUAL_OBJECTS = ARCHI_VISUAL_OBJECTS;
316
+ static ARCHI_VIEW_TYPES = ARCHI_VIEW_TYPES;
317
+
318
+ /** Parse an Archi native `.archimate` XML string and return renderengine
319
+ * inputs ready for `archimateDiagram()`: `{ elements, relationships, options }`.
320
+ * `viewId` selects a specific view; defaults to the first view. */
321
+ static importArchiXML(xml, viewId) {
322
+ const archiModel = parseArchiModel(xml);
323
+ return toRenderEngineModel(archiModel, viewId);
324
+ }
325
+
326
+ /** Serialize renderengine model data back to an Archi native `.archimate`
327
+ * XML string. `model` is `{ id?, name?, version?, elements[], relationships[] }`
328
+ * and `views` is an array of Archi view descriptors. */
329
+ static exportArchiXML(model, views) {
330
+ return toArchiXML(model, views);
331
+ }
332
+
333
+ /** Export model elements/relationships to CSV strings for spreadsheet editing.
334
+ * Returns `{ elementsCSV, relationshipsCSV }`. */
335
+ static exportModelCSV(model) {
336
+ return exportModelCSV(model);
337
+ }
338
+
339
+ /** Import model elements/relationships from CSV strings. Returns
340
+ * `{ elements, relationships }` ready for `archimateDiagram()`. */
341
+ static importModelCSV(elementsCSV, relationshipsCSV) {
342
+ return importModelCSV(elementsCSV, relationshipsCSV);
343
+ }
344
+
345
+ /** Layout constants for outlineChart()'s indented rows. */
346
+ static OUTLINE_ROW_H = 22;
347
+ static OUTLINE_INDENT_W = 18;
348
+
349
+ /**
350
+ * The full list of chart types this engine supports, with a display
351
+ * label and the method to call for each — see chartCatalog.js. Also
352
+ * available as the standalone `CHART_CATALOG` export for consumers that
353
+ * only need the list (no RenderEngine instance) e.g. to populate a type
354
+ * picker before any query has run.
355
+ */
356
+ static CHART_CATALOG = CHART_CATALOG;
357
+
358
+ /**
359
+ * Object-oriented registry of chart types. Consumers can register custom
360
+ * chart types at runtime via `RenderEngine.CHART_REGISTRY.register(entry)`.
361
+ */
362
+ static CHART_REGISTRY = chartRegistry;
363
+
364
+ /**
365
+ * The full list of diagram types this engine supports, with the method to
366
+ * call for each — see diagramCatalog.js. Also available as the standalone
367
+ * `DIAGRAM_CATALOG` export.
368
+ */
369
+ static DIAGRAM_CATALOG = DIAGRAM_CATALOG;
370
+
371
+ /**
372
+ * Object-oriented registry of diagram types. Consumers can register custom
373
+ * diagram types at runtime via `RenderEngine.DIAGRAM_REGISTRY.register(entry)`.
374
+ */
375
+ static DIAGRAM_REGISTRY = diagramRegistry;
376
+
377
+ /**
378
+ * `get` trap backing the constructor's chained-access Proxy. Only falls
379
+ * through to a container handle when `prop` is neither a real
380
+ * property/method NOR already-registered (via `register()`) — instance
381
+ * properties like `renderer` are conditionally assigned (never set at
382
+ * all in container-less test mode), so an unset *real* property and an
383
+ * unused container id are otherwise indistinguishable from `in target`
384
+ * alone; requiring registry membership avoids that collision, and costs
385
+ * nothing since `addDataPoint`/`addGraphNode`/`refreshChart` already
386
+ * no-op for an id that was never registered anyway.
387
+ * @private
388
+ */
389
+ static _proxyHandler = {
390
+ get(target, prop, receiver) {
391
+ if (typeof prop === 'string' && !(prop in target) && target.registry && prop in target.registry) {
392
+ return target._containerHandle(prop);
393
+ }
394
+ return Reflect.get(target, prop, receiver);
395
+ },
396
+ };
397
+
398
+ /**
399
+ * Renders `data` as chart type `key` into this engine's container —
400
+ * looks up the method from CHART_CATALOG so a consumer's type-dispatch
401
+ * code doesn't need its own key→method mapping (only the data-shaping,
402
+ * which is inherently consumer-specific — see each entry's `dataShape`).
403
+ * @param {string} key A CHART_CATALOG key, e.g. 'bar', 'heatmap'.
404
+ * @param {*} data Shaped per that entry's `dataShape`.
405
+ * @param {object} [options]
406
+ * @throws {Error} if `key` isn't in CHART_CATALOG.
407
+ */
408
+ renderChartType(key, data, options = {}) {
409
+ const entry = RenderEngine.CHART_REGISTRY.get(key);
410
+ if (!entry) throw new Error(`Unknown chart type '${key}' — see RenderEngine.CHART_CATALOG for valid keys`);
411
+ return this[entry.method](data, options);
412
+ }
413
+
414
+ /**
415
+ * Renders a registered diagram type by key.
416
+ * @param {string} key A DIAGRAM_CATALOG key, e.g. 'graph', 'archimate'.
417
+ * @param {...*} args Passed through to the diagram method in the same order
418
+ * it expects (e.g. `nodes, edges, options` for 'graph').
419
+ * @throws {Error} if `key` isn't in DIAGRAM_CATALOG.
420
+ */
421
+ renderDiagramType(key, ...args) {
422
+ const entry = RenderEngine.DIAGRAM_REGISTRY.get(key);
423
+ if (!entry) throw new Error(`Unknown diagram type '${key}' — see RenderEngine.DIAGRAM_CATALOG for valid keys`);
424
+ return this[entry.method](...args);
425
+ }
426
+
427
+ /**
428
+ * @param {HTMLElement} container The container element for the chart.
429
+ * @param {object} [options]
430
+ * @param {'canvas' | 'svg'} [options.rendererType='canvas']
431
+ */
432
+ constructor(container, options = {}) {
433
+ if (container) {
434
+ const { rendererType = 'canvas' } = options;
435
+ const width = container.clientWidth;
436
+ const height = container.clientHeight;
437
+
438
+ this.container = container; // Store container for event listeners
439
+ this.renderer = RendererFactory.create(rendererType, container, width, height);
440
+ } else {
441
+ // Allow initialization without a container for testing
442
+ this.options = options;
443
+ console.log('RenderEngine initialized in test mode.');
444
+ }
445
+
446
+ if (this.container) {
447
+ this._interactions = new InteractionManager(this);
448
+ this._interactions.attach();
449
+ }
450
+
451
+ this._containerHandles = new Map();
452
+ // Lets `register`/`refreshChart`/`addDataPoint`/`addGraphNode` be
453
+ // called either the normal way (`engine.addGraphNode(id, ...)`) or
454
+ // scoped to a container id (`engine[id].addGraphNode(...)` — or
455
+ // `engine.someId.addGraphNode(...)` when `id` happens to be a valid
456
+ // identifier with no hyphens). Only property names that aren't
457
+ // already real properties/methods fall through to a container
458
+ // handle, so this never shadows anything on the class itself.
459
+ return new Proxy(this, RenderEngine._proxyHandler);
460
+ }
461
+
462
+ /**
463
+ * Returns (creating and caching on first use) the chained-access handle
464
+ * for container `id` — see the constructor's Proxy comment.
465
+ * @private
466
+ */
467
+ _containerHandle(id) {
468
+ if (!this._containerHandles.has(id)) {
469
+ const engine = this;
470
+ this._containerHandles.set(id, {
471
+ register: (type, options) => engine.register(id, type, options),
472
+ refreshChart: () => engine.refreshChart(id),
473
+ addDataPoint: (pointOrFactory, opts) => engine.addDataPoint(id, pointOrFactory, opts),
474
+ addGraphNode: (node, newEdges, opts) => engine.addGraphNode(id, node, newEdges, opts),
475
+ });
476
+ }
477
+ return this._containerHandles.get(id);
478
+ }
479
+
480
+ /**
481
+ * Convenience getter: exposes the current active diagram's _graph property
482
+ * (GraphDiagram uses this to store node positions, edges, and other state).
483
+ * Returns undefined if no active diagram or if the active diagram doesn't have _graph.
484
+ * @private
485
+ */
486
+ get _graph() {
487
+ return this._activeDiagram?._graph;
488
+ }
489
+
490
+ /**
491
+ * Default plot-area box for the bar/line/stackedBar/bubble/barChart3D
492
+ * family, sized to the actual renderer dimensions instead of a fixed
493
+ * constant. Reserves margin below the plot for the x-axis tick labels
494
+ * (drawn at y+height+20) and axis label (drawn at y+height+45) so they
495
+ * stay inside the container instead of being clipped (Canvas) or
496
+ * overflowing it (SVG) — a fixed height that assumes a specific
497
+ * container size cuts these off in any container it wasn't tuned for.
498
+ * @private
499
+ */
500
+ _defaultChartBox() {
501
+ return ChartBox.fromRenderer(this.renderer);
502
+ }
503
+
504
+ /**
505
+ * Draws a list of shape descriptors or Shape instances. Each shape is
506
+ * dispatched to the renderer through a concrete Shape subclass, so this
507
+ * method no longer switches on `shape.type`.
508
+ * @param {Array<object|Shape>} shapes An array of shape descriptions to draw.
509
+ */
510
+ draw(shapes) {
511
+ if (!this.renderer) return;
512
+ this.renderer.clear();
513
+ for (const shape of shapes) {
514
+ const s = ShapeFactory.fromObject(shape);
515
+ s.render(this.renderer);
516
+ }
517
+ }
518
+
519
+ /**
520
+ * Render a Chart subclass instance and make it the active chart for event
521
+ * dispatch. This replaces the old pattern of switching on `_chartState.type`
522
+ * in the global click listener.
523
+ * @private
524
+ */
525
+ _renderChart(chart) {
526
+ this._activeChart = chart;
527
+ this._activeDiagram = null;
528
+ chart.render();
529
+ }
530
+
531
+ /**
532
+ * Render a Diagram subclass instance and make it the active diagram for
533
+ * event dispatch.
534
+ * @private
535
+ */
536
+ _renderDiagram(diagram) {
537
+ this._activeDiagram = diagram;
538
+ this._activeChart = null;
539
+ diagram.render();
540
+ }
541
+
542
+ /**
543
+ * Renders a simple bar chart.
544
+ * @param {Array<{label: string, value: number}>} data The data to chart.
545
+ * @param {object} [options] Charting options.
546
+ * @param {number} [options.x=50] The x-coordinate of the chart origin.
547
+ * @param {number} [options.y=50] The y-coordinate of the chart origin.
548
+ * @param {number} [options.width=500] The width of the chart area.
549
+ * @param {number} [options.height=300] The height of the chart area.
550
+ * @param {string} [options.barColor='steelblue'] The color of the bars.
551
+ * @param {string} [options.title] The chart title.
552
+ * @param {string} [options.xAxisLabel] The label for the x-axis.
553
+ * @param {string} [options.yAxisLabel] The label for the y-axis.
554
+ * @param {string[]} [options.colors] Array of colors for the bars.
555
+ * @param {boolean} [options.showLegend=false] Whether to show the legend.
556
+ */
557
+ barChart(data, options = {}) {
558
+ if (!this.renderer) return;
559
+ this._chart3D = false; // leaving 3D mode — clears the SVG-zoom guard added for bubbleChart3D
560
+ // Store state for re-rendering and interactivity
561
+ this._chartState = { type: 'bar', data, options };
562
+ this._barDataFilter = this._barDataFilter || new DataFilter();
563
+ this._renderChart(new BarChart(this.renderer, data, options, this._barDataFilter));
564
+ return;
565
+ }
566
+
567
+ /**
568
+ * Renders a grouped (multi-set) bar chart.
569
+ * @param {Array<{name: string, values: Array<{label: string, value: number}>}>} data The data to chart.
570
+ * @param {object} [options] Charting options.
571
+ * @param {string[]} [options.colors] Series palette.
572
+ * @param {boolean} [options.legend=false] Show a series legend.
573
+ * @param {number} [options.groupPadding=0.2] Gap between category clusters as a fraction of cluster width.
574
+ * @param {number} [options.barPadding=0.05] Gap between bars inside a cluster as a fraction of bar width.
575
+ */
576
+ groupedBarChart(data, options = {}) {
577
+ if (!this.renderer) return;
578
+ this._chart3D = false;
579
+ this._chartState = { type: 'groupedBar', data, options };
580
+ this._renderChart(new GroupedBarChart(this.renderer, data, options));
581
+ }
582
+
583
+ /**
584
+ * Renders a simple line chart.
585
+ * @param {Array<{label: string, value: number}>} data The data to chart.
586
+ * @param {object} [options] Charting options.
587
+ */
588
+ lineChart(data, options = {}) {
589
+ if (!this.renderer) return;
590
+ this._chart3D = false;
591
+ this._chartState = { type: 'line', data, options };
592
+ this._renderChart(new LineChart(this.renderer, data, options));
593
+ }
594
+
595
+ /**
596
+ * Renders a multi-line chart where each line is a tapering filled ribbon
597
+ * ("pipe") instead of a uniform-width stroke — width at each point tracks
598
+ * a metric field. Neither SVG stroke-width nor Canvas lineWidth can vary
599
+ * along a single stroke, so this is drawn as a filled polygon: each
600
+ * point is offset perpendicular to the local line direction by
601
+ * ±width/2, and the upper/lower boundary points are connected into one
602
+ * closed path — the same `renderer.path()`/M...L...Z mechanism used
603
+ * everywhere else in this file.
604
+ * @param {Array<{label:string,value:number,metric?:number}>|Array<{name:string,values:Array<{label,value,metric}>}>} data
605
+ * @param {object} [options]
606
+ * @param {string} [options.metricCol='metric'] Field name driving pipe width.
607
+ * @param {number} [options.minWidth=4]
608
+ * @param {number} [options.maxWidth=28]
609
+ * @param {string[]} [options.colors]
610
+ * @param {boolean} [options.legend=false]
611
+ */
612
+ pipeChart(data, options = {}) {
613
+ if (!this.renderer) return;
614
+ this._chart3D = false; // leaving 3D mode — clears the SVG-zoom guard added for bubbleChart3D
615
+ this._chartState = { type: 'pipe', data, options };
616
+ this._renderChart(new PipeChart(this.renderer, data, options));
617
+ }
618
+
619
+ /**
620
+ * Renders a stacked bar chart.
621
+ * @param {Array<object>} data The data to chart. e.g. [{ label: 'Jan', values: [10, 20] }]
622
+ * @param {object} [options] Charting options.
623
+ */
624
+ stackedBarChart(data, options = {}) {
625
+ if (!this.renderer) return;
626
+ this._chart3D = false; // leaving 3D mode — clears the SVG-zoom guard added for bubbleChart3D
627
+ this._chartState = { type: 'stackedBar', data, options };
628
+ this._renderChart(new StackedBarChart(this.renderer, data, options));
629
+ }
630
+
631
+ // ── Color-scale helpers (heatmap) ───────────────────────────────────────
632
+
633
+ /**
634
+ * Maps a value within [vMin, vMax] to a color along an N-stop scale
635
+ * (linear interpolation between adjacent stops via ColorPalette).
636
+ * @private
637
+ */
638
+ _scaleColor(stops, vMin, vMax, v) {
639
+ return ColorPalette.scaleColor(stops, vMin, vMax, v);
640
+ }
641
+
642
+ /** Perceptual-ish luma check, used to pick readable text color on a colored cell/slice. @private */
643
+ _isDarkColor(hex) {
644
+ return ColorPalette.isDark(hex);
645
+ }
646
+
647
+ /**
648
+ * Renders a heatmap: a grid of cells, one per (x, y) category pair,
649
+ * shaded by `value` along a low→high color scale — the natural chart for
650
+ * a correlation matrix or any "intensity across two dimensions" dataset.
651
+ * @param {Array<{x: string, y: string, value: number}>} data
652
+ * @param {object} [options]
653
+ * @param {string[]} [options.xLabels] explicit x-axis category order (default: order of first appearance in data)
654
+ * @param {string[]} [options.yLabels] explicit y-axis category order (default: order of first appearance in data)
655
+ * @param {string[]} [options.colorScale=['#f7fbff','#4292c6','#08306b']] low-to-high color stops (2+ hex colors)
656
+ * @param {boolean} [options.showValues=false] draw the numeric value inside each cell
657
+ * @param {string} [options.title]
658
+ */
659
+ heatmap(data, options = {}) {
660
+ if (!this.renderer) return;
661
+ this._chart3D = false;
662
+ this._chartState = { type: 'heatmap', data, options };
663
+ this._renderChart(new HeatmapChart(this.renderer, data, options));
664
+ }
665
+
666
+ /**
667
+ * Renders a calendar heatmap: a year-at-a-glance grid of day cells, each
668
+ * shaded by its value — the classic GitHub-contribution-graph layout.
669
+ * @param {Array<{date: string|Date, value: number}>} data
670
+ * @param {object} [options]
671
+ * @param {number} [options.year] year to display (default current year)
672
+ * @param {string|Date} [options.startDate] explicit inclusive start
673
+ * @param {string|Date} [options.endDate] explicit inclusive end
674
+ * @param {string[]} [options.colorScale=['#ebedf0','#9be9a8','#40c463','#30a14e','#216e39']] low→high stops
675
+ * @param {string} [options.emptyColor='#ebedf0'] color for days with no data
676
+ * @param {number} [options.cellSize] pixel size of one day cell (auto if omitted)
677
+ * @param {number} [options.cellGap=2] gap between cells
678
+ * @param {boolean} [options.showValues=false] draw the numeric value inside each cell
679
+ * @param {boolean} [options.monthLabels=true] show month names above the grid
680
+ * @param {boolean} [options.weekdayLabels=true] show weekday names beside rows
681
+ * @param {0|1} [options.weekStart=0] 0 = Sunday, 1 = Monday
682
+ * @param {string} [options.title]
683
+ */
684
+ calendarHeatmap(data, options = {}) {
685
+ if (!this.renderer) return;
686
+ this._chart3D = false;
687
+ this._chartState = { type: 'calendarHeatmap', data, options };
688
+ this._renderChart(new CalendarHeatmapChart(this.renderer, data, options));
689
+ }
690
+
691
+ /**
692
+ * Renders a Gantt chart — horizontal bars on a time axis, one row per task.
693
+ * @param {Array<{label:string, start:string|Date|number, end:string|Date|number, progress?:number, category?:string, color?:string}>} data
694
+ * @param {object} [options]
695
+ * @param {number} [options.barHeight=24]
696
+ * @param {number} [options.barGap=12]
697
+ * @param {string[]} [options.colors] per-category palette
698
+ * @param {string} [options.axisLabel]
699
+ * @param {'auto'|'date'|'datetime'|'iso'} [options.dateFormat='auto']
700
+ * @param {boolean} [options.showProgress] default true if any task has progress
701
+ * @param {boolean} [options.showToday=true]
702
+ * @param {string} [options.title]
703
+ */
704
+ ganttChart(data, options = {}) {
705
+ if (!this.renderer) return;
706
+ this._chart3D = false;
707
+ this._chartState = { type: 'gantt', data, options };
708
+ this._renderChart(new GanttChart(this.renderer, data, options));
709
+ }
710
+
711
+ /**
712
+ * Renders a stream graph — a stacked area chart with a centered baseline,
713
+ * producing a flowing shape across time.
714
+ * @param {Array<{name: string, values: Array<{label: string, value: number}>}>} data
715
+ * @param {object} [options]
716
+ * @param {string[]} [options.colors]
717
+ * @param {boolean} [options.legend=true]
718
+ * @param {boolean} [options.smooth=true] cubic-bezier smoothing
719
+ * @param {number} [options.tension=0.5]
720
+ * @param {string} [options.title]
721
+ * @param {string} [options.xAxisLabel]
722
+ * @param {string} [options.yAxisLabel]
723
+ */
724
+ streamGraph(data, options = {}) {
725
+ if (!this.renderer) return;
726
+ this._chart3D = false;
727
+ this._chartState = { type: 'streamGraph', data, options };
728
+ this._renderChart(new StreamGraphChart(this.renderer, data, options));
729
+ }
730
+
731
+ /**
732
+ * Renders a radial bar chart (circular barplot) — bars radiating from a
733
+ * central point, one angular slice per category.
734
+ * @param {Array<{label:string, value:number}>} data
735
+ * @param {object} [options]
736
+ * @param {number} [options.cx]
737
+ * @param {number} [options.cy]
738
+ * @param {number} [options.radius]
739
+ * @param {number} [options.innerRadius]
740
+ * @param {number} [options.padAngle=0.02]
741
+ * @param {number} [options.startAngle=0]
742
+ * @param {number} [options.endAngle=2π]
743
+ * @param {string[]} [options.colors]
744
+ * @param {boolean} [options.showValues=false]
745
+ * @param {boolean} [options.legend=true]
746
+ * @param {string} [options.title]
747
+ */
748
+ radialBarChart(data, options = {}) {
749
+ if (!this.renderer) return;
750
+ this._chart3D = false;
751
+ this._chartState = { type: 'radialBar', data, options };
752
+ this._renderChart(new RadialBarChart(this.renderer, data, options));
753
+ }
754
+
755
+ /**
756
+ * Renders a population pyramid — back-to-back horizontal bars comparing
757
+ * two numeric values per category (commonly male/female by age group).
758
+ * @param {Array<{label:string, left:number, right:number}>} data
759
+ * @param {object} [options]
760
+ * @param {string} [options.leftLabel='Left']
761
+ * @param {string} [options.rightLabel='Right']
762
+ * @param {string} [options.leftColor='#4f8ef7']
763
+ * @param {string} [options.rightColor='#e91e63']
764
+ * @param {boolean} [options.showValues=false]
765
+ * @param {string} [options.title]
766
+ * @param {string} [options.xAxisLabel]
767
+ * @param {string} [options.yAxisLabel]
768
+ */
769
+ populationPyramid(data, options = {}) {
770
+ if (!this.renderer) return;
771
+ this._chart3D = false;
772
+ this._chartState = { type: 'populationPyramid', data, options };
773
+ this._renderChart(new PopulationPyramidChart(this.renderer, data, options));
774
+ }
775
+
776
+ /**
777
+ * Renders a Sankey diagram — flow links whose width is proportional to the
778
+ * value they carry, connecting source nodes to target nodes.
779
+ * @param {object} data
780
+ * @param {Array<{id:string, label?:string, color?:string}>} [data.nodes]
781
+ * @param {Array<{source:string, target:string, value:number}>} data.links
782
+ * @param {object} [options]
783
+ * @param {number} [options.nodeWidth=16]
784
+ * @param {number} [options.nodePadding=16]
785
+ * @param {number} [options.linkOpacity=0.45]
786
+ * @param {string[]} [options.colors]
787
+ * @param {string} [options.title]
788
+ */
789
+ sankeyDiagram(data, options = {}) {
790
+ if (!this.renderer) return;
791
+ this._chart3D = false;
792
+ this._chartState = { type: 'sankey', data, options };
793
+ this._renderChart(new SankeyDiagram(this.renderer, data, options));
794
+ }
795
+
796
+ /**
797
+ * Renders a chord diagram — nodes arranged around a circle, with curved
798
+ * links (chords) whose thickness is proportional to their value.
799
+ * @param {object} data
800
+ * @param {Array<{id:string, label?:string, color?:string}>} [data.nodes]
801
+ * @param {Array<{source:string, target:string, value:number}>} data.links
802
+ * @param {object} [options]
803
+ * @param {number} [options.radius]
804
+ * @param {number} [options.innerRadius]
805
+ * @param {number} [options.padAngle=0.04]
806
+ * @param {string[]} [options.colors]
807
+ * @param {string} [options.title]
808
+ */
809
+ chordDiagram(data, options = {}) {
810
+ if (!this.renderer) return;
811
+ this._chart3D = false;
812
+ this._chartState = { type: 'chord', data, options };
813
+ this._renderChart(new ChordDiagram(this.renderer, data, options));
814
+ }
815
+
816
+ /**
817
+ * Renders a word cloud — words sized by value and laid out with a simple
818
+ * spiral placement that avoids overlaps.
819
+ * @param {Array<{text:string, value:number}>} data
820
+ * @param {object} [options]
821
+ * @param {string} [options.title]
822
+ * @param {string[]} [options.colors]
823
+ * @param {number} [options.minFontSize=12]
824
+ * @param {number} [options.maxFontSize=48]
825
+ * @param {string} [options.fontFamily='sans-serif']
826
+ * @param {number} [options.padding=4]
827
+ */
828
+ wordCloud(data, options = {}) {
829
+ if (!this.renderer) return;
830
+ this._chart3D = false;
831
+ this._chartState = { type: 'wordCloud', data, options };
832
+ this._renderChart(new WordCloudChart(this.renderer, data, options));
833
+ }
834
+
835
+ /**
836
+ * Renders a parallel coordinates plot — one vertical axis per dimension,
837
+ * with each data row drawn as a polyline connecting its values.
838
+ * @param {Array<object>} data
839
+ * @param {object} [options]
840
+ * @param {string[]} [options.dimensions]
841
+ * @param {string[]} [options.colors]
842
+ * @param {number} [options.lineWidth=1.5]
843
+ * @param {number} [options.lineOpacity=0.6]
844
+ * @param {boolean} [options.showAxes=true]
845
+ * @param {boolean} [options.showLegend]
846
+ * @param {string} [options.title]
847
+ */
848
+ parallelCoordinates(data, options = {}) {
849
+ if (!this.renderer) return;
850
+ this._chart3D = false;
851
+ this._chartState = { type: 'parallelCoordinates', data, options };
852
+ this._renderChart(new ParallelCoordinatesChart(this.renderer, data, options));
853
+ }
854
+
855
+ /**
856
+ * Renders a simple geospatial map — equirectangular projection of
857
+ * GeoJSON-like Polygon/MultiPolygon features with optional choropleth
858
+ * fills and bubble markers.
859
+ * @param {object} data
860
+ * @param {Array<object>} data.features
861
+ * @param {Array<{lat:number, lon:number, value:number}>} [data.bubbles]
862
+ * @param {object} [options]
863
+ * @param {[number,number]} [options.center]
864
+ * @param {number} [options.scale]
865
+ * @param {string[]} [options.colorScale]
866
+ * @param {number} [options.bubbleMaxRadius=20]
867
+ * @param {string} [options.bubbleColor]
868
+ * @param {string} [options.title]
869
+ */
870
+ geoMap(data, options = {}) {
871
+ if (!this.renderer) return;
872
+ this._chart3D = false;
873
+ this._chartState = { type: 'geoMap', data, options };
874
+ this._renderChart(new GeoMapChart(this.renderer, data, options));
875
+ }
876
+
877
+ /**
878
+ * Renders a candlestick (OHLC) chart — one wick+body glyph per time
879
+ * step. The body is colored by direction (close >= open -> up-color,
880
+ * else down-color); the wick spans high-low, the body spans open-close.
881
+ * Optional `markers` overlay point-in-time annotations (e.g. dividend
882
+ * ex-dates, earnings dates) as small hover-tooltip circles above the
883
+ * chart — a click-to-expand version of this would need the same global
884
+ * hitTest-based click wiring `graphDiagram`'s `onNodeClick` uses, out of
885
+ * scope for this pass.
886
+ * @param {Array<{label:string, open:number, high:number, low:number, close:number}>} data
887
+ * @param {object} [options]
888
+ * @param {string} [options.upColor='#26a69a']
889
+ * @param {string} [options.downColor='#ef5350']
890
+ * @param {Array<{label:string, tooltip?:string}>} [options.markers]
891
+ * @param {string} [options.title]
892
+ */
893
+ candlestick(data, options = {}) {
894
+ if (!this.renderer) return;
895
+ this._chart3D = false;
896
+ this._chartState = { type: 'candlestick', data, options };
897
+ this._renderChart(new CandlestickChart(this.renderer, data, options));
898
+ }
899
+
900
+ /**
901
+ * Builds a path `d` string for a pie slice (rInner=0) or a doughnut/
902
+ * annular slice (rInner>0) spanning startAngle..endAngle (radians).
903
+ * @private
904
+ */
905
+ _donutSlicePath(cx, cy, rOuter, rInner, startAngle, endAngle) {
906
+ return ShapePaths.donutSlicePath(cx, cy, rOuter, rInner, startAngle, endAngle);
907
+ }
908
+
909
+ /**
910
+ * Renders a pie chart, or a doughnut chart when `innerRadiusRatio > 0`.
911
+ * @param {Array<{label:string, value:number}>} data
912
+ * @param {object} [options]
913
+ * @param {number} [options.innerRadiusRatio=0] 0 = pie; e.g. 0.6 = doughnut
914
+ * @param {string[]} [options.colors]
915
+ * @param {boolean} [options.legend=true]
916
+ * @param {string} [options.title]
917
+ */
918
+ pieChart(data, options = {}) {
919
+ if (!this.renderer) return;
920
+ this._chart3D = false;
921
+ this._chartState = { type: 'pie', data, options };
922
+ this._renderChart(new PieChart(this.renderer, data, options));
923
+ }
924
+
925
+ /**
926
+ * Renders a doughnut chart — a pie chart with a default inner radius.
927
+ * @param {Array<{label: string, value: number}>} data The data to chart.
928
+ * @param {object} [options] Same options as pieChart; innerRadiusRatio defaults to 0.5.
929
+ */
930
+ donutChart(data, options = {}) {
931
+ if (!this.renderer) return;
932
+ this._chart3D = false;
933
+ this._chartState = { type: 'donut', data, options };
934
+ this._renderChart(new DonutChart(this.renderer, data, options));
935
+ }
936
+
937
+ /**
938
+ * Renders an area chart — a line chart with the region below each series
939
+ * filled down to the baseline (or, with `stacked:true`, down to the
940
+ * previous series' band) to emphasize magnitude/cumulative volume rather
941
+ * than just trend shape.
942
+ * @param {Array<{label,value}>|Array<{name,values:Array<{label,value}>}>} data
943
+ * @param {object} [options]
944
+ * @param {boolean} [options.stacked=false]
945
+ * @param {string[]} [options.colors]
946
+ * @param {boolean} [options.legend=false]
947
+ */
948
+ areaChart(data, options = {}) {
949
+ if (!this.renderer) return;
950
+ this._chart3D = false;
951
+ this._chartState = { type: 'area', data, options };
952
+ this._renderChart(new AreaChart(this.renderer, data, options));
953
+ }
954
+
955
+ /**
956
+ * Renders a stacked area chart. Convenience wrapper around areaChart with
957
+ * `stacked: true`; accepts the same multi-series data shape.
958
+ * @param {Array<{name: string, values: Array<{label: string, value: number}>}>} data
959
+ * @param {object} [options]
960
+ */
961
+ stackedAreaChart(data, options = {}) {
962
+ if (!this.renderer) return;
963
+ this._chart3D = false;
964
+ this._chartState = { type: 'stackedArea', data, options };
965
+ this._renderChart(new StackedAreaChart(this.renderer, data, options));
966
+ }
967
+
968
+ /**
969
+ * Computes evenly-spaced bin edges/counts for a raw numeric sample —
970
+ * pure data transform, no rendering, DOM-free and unit-testable (same
971
+ * convention as `_tableNodeSize`/`_tableColumnY`).
972
+ * @private
973
+ */
974
+ _histogramBins(values, binCount) {
975
+ return Statistics.histogramBins(values, binCount);
976
+ }
977
+
978
+ /**
979
+ * Renders a histogram — frequency distribution of a raw numeric sample,
980
+ * binned into `binCount` evenly-spaced buckets (default: a Sturges'-rule
981
+ * estimate, ⌈log2(n)+1⌉, clamped to a sane 5-20 range).
982
+ * @param {number[]} values raw numeric sample (not pre-binned)
983
+ * @param {object} [options]
984
+ * @param {number} [options.binCount]
985
+ * @param {string} [options.barColor='steelblue']
986
+ */
987
+ histogram(values, options = {}) {
988
+ if (!this.renderer) return;
989
+ this._chart3D = false;
990
+ this._chartState = { type: 'histogram', data: values, options };
991
+ this._renderChart(new HistogramChart(this.renderer, values, options));
992
+ }
993
+
994
+ /**
995
+ * Inverse standard-normal CDF (the probit function) via Peter Acklam's
996
+ * well-known rational approximation (~1.15e-9 relative accuracy) — the
997
+ * "reference-distribution quantile function" qqPlot needs. Pure,
998
+ * DOM-free, unit-testable against known reference values.
999
+ * @private
1000
+ */
1001
+ _normalQuantile(p) {
1002
+ return Statistics.normalQuantile(p);
1003
+ }
1004
+
1005
+ /**
1006
+ * Pairs each sorted sample value with a theoretical normal quantile at
1007
+ * the standard `(i - 0.5)/n` plotting position — the data qqPlot
1008
+ * scatters. Pure, DOM-free, unit-testable. Both fields come out
1009
+ * already ascending (sample is sorted; _normalQuantile is monotonic).
1010
+ * @returns {Array<{theoretical: number, sample: number}>}
1011
+ * @private
1012
+ */
1013
+ _qqPoints(values) {
1014
+ return Statistics.qqPoints(values);
1015
+ }
1016
+
1017
+ /**
1018
+ * `[{x: values[i-lag], y: values[i]}]` for i = lag..length-1 — the data
1019
+ * lagPlot scatters, to visually check autocorrelation. Pure, DOM-free,
1020
+ * unit-testable.
1021
+ * @private
1022
+ */
1023
+ _lagPairs(values, lag = 1) {
1024
+ return Statistics.lagPairs(values, lag);
1025
+ }
1026
+
1027
+ /**
1028
+ * Linear-interpolation quantile (the common "R-7"/PERCENTILE.INC method)
1029
+ * over an already-sorted numeric array. Pure, DOM-free, unit-testable.
1030
+ * @private
1031
+ */
1032
+ _quantile(sorted, p) {
1033
+ return Statistics.quantile(sorted, p);
1034
+ }
1035
+
1036
+ /**
1037
+ * Computes box-and-whisker summary stats for a raw numeric sample:
1038
+ * quartiles, whiskers clamped to the standard 1.5×IQR Tukey fences (and
1039
+ * to the actual data range), and anything beyond the fences as an
1040
+ * outlier. Pure, DOM-free, unit-testable.
1041
+ * @private
1042
+ */
1043
+ _boxPlotStats(values) {
1044
+ return Statistics.boxPlotStats(values);
1045
+ }
1046
+
1047
+ /** Population standard deviation. Pure, DOM-free, unit-testable. @private */
1048
+ _stdev(values) {
1049
+ return Statistics.stdev(values);
1050
+ }
1051
+
1052
+ /**
1053
+ * Gaussian-kernel density estimate — a smoothed distribution curve from
1054
+ * a raw numeric sample, the shared math core behind densityChart and
1055
+ * violinChart. `bandwidth` defaults via Silverman's rule of thumb
1056
+ * (`1.06 * stdev * n^(-1/5)`, the standard heuristic, not bespoke);
1057
+ * `min`/`max` default to 3 bandwidths past the data's own extremes so
1058
+ * the curve tapers to ~0 rather than being cut off mid-slope. Pure,
1059
+ * DOM-free, unit-testable.
1060
+ * @returns {Array<{x: number, density: number}>}
1061
+ * @private
1062
+ */
1063
+ _kernelDensityEstimate(values, options = {}) {
1064
+ return Statistics.kernelDensityEstimate(values, options);
1065
+ }
1066
+
1067
+ /**
1068
+ * Renders a box-and-whisker plot — one box per group, summarizing a raw
1069
+ * numeric sample's spread (median, quartiles, whiskers, outliers)
1070
+ * instead of plotting every point.
1071
+ * @param {Array<{label:string, values:number[]}>} data
1072
+ * @param {object} [options]
1073
+ * @param {string} [options.boxColor='#5470c6']
1074
+ */
1075
+ boxPlot(data, options = {}) {
1076
+ if (!this.renderer) return;
1077
+ this._chart3D = false;
1078
+ this._chartState = { type: 'boxPlot', data, options };
1079
+ this._renderChart(new BoxPlotChart(this.renderer, data, options));
1080
+ }
1081
+
1082
+ /**
1083
+ * Renders overlaid distribution curves — one Gaussian-KDE curve per
1084
+ * group (_kernelDensityEstimate), sharing one value-axis so shapes are
1085
+ * visually comparable. Same top-level data shape as boxPlot (a caller
1086
+ * can hand the identical dataset to either) and the same multi-series
1087
+ * fill+topline+legend rendering pattern as areaChart, just with the
1088
+ * curve computed from a raw sample instead of literal category/value
1089
+ * points. Y is relative density (curve shape only) — no meaningful
1090
+ * absolute-number axis, so no numeric y-tick labels, same spirit as
1091
+ * radarChart's unlabeled radial scale.
1092
+ * @param {Array<{label:string, values:number[]}>} data
1093
+ * @param {object} [options]
1094
+ * @param {string[]} [options.colors]
1095
+ * @param {boolean} [options.legend=true]
1096
+ */
1097
+ densityChart(data, options = {}) {
1098
+ if (!this.renderer) return;
1099
+ this._chart3D = false;
1100
+ this._chartState = { type: 'density', data, options };
1101
+ this._renderChart(new DensityChart(this.renderer, data, options));
1102
+ }
1103
+
1104
+ /**
1105
+ * Renders a violin plot — one mirrored Gaussian-KDE silhouette per
1106
+ * group in its own x-slot, the same per-group layout math as boxPlot
1107
+ * (same top-level data shape too — a caller can hand the identical
1108
+ * dataset to either). Unlike boxPlot's single flat `boxColor`, each
1109
+ * violin gets its own palette color: a violin's shape is visually
1110
+ * larger/more prominent than a thin box glyph, so per-group color
1111
+ * materially helps distinguish many holdings at a glance (same
1112
+ * reasoning treemapChart/sunburstChart color per top-level group). A
1113
+ * thin median tick (reusing _quantile, like boxPlot's own) is drawn
1114
+ * for reference — no outlier dots, since showing the full distribution
1115
+ * shape including tails IS the point of a violin, unlike box plot's
1116
+ * separate Tukey-fence "outlier" concept.
1117
+ * @param {Array<{label:string, values:number[]}>} data
1118
+ * @param {object} [options]
1119
+ * @param {string[]} [options.colors]
1120
+ */
1121
+ violinChart(data, options = {}) {
1122
+ if (!this.renderer) return;
1123
+ this._chart3D = false;
1124
+ this._chartState = { type: 'violin', data, options };
1125
+ this._renderChart(new ViolinChart(this.renderer, data, options));
1126
+ }
1127
+
1128
+ /** Layout constant for treemapChart's branch-node header band. */
1129
+ static TREEMAP_HEADER_H = 16;
1130
+
1131
+ /**
1132
+ * A leaf's own value, or a branch's value computed as the sum of its
1133
+ * children (recursively — never required from the caller). Non-finite
1134
+ * or non-positive values can't be allocated area/angle and are treated
1135
+ * as 0 (filtered out by the caller doing the layout, not here). Pure —
1136
+ * directly unit-testable. Shared by treemapChart and sunburstChart —
1137
+ * both consume the same `Array<{label, value?, children?}>` shape.
1138
+ * @private
1139
+ */
1140
+ _hierarchyValue(node) {
1141
+ return Hierarchy.value(node);
1142
+ }
1143
+
1144
+ /**
1145
+ * Tree depth: 1 for a leaf, 1 + the deepest child for a branch. Pure.
1146
+ * Used by sunburstChart to size each ring so an arbitrarily deep tree
1147
+ * still fits within one fixed radius, rather than overflowing it.
1148
+ * @private
1149
+ */
1150
+ _hierarchyDepth(node) {
1151
+ return Hierarchy.depth(node);
1152
+ }
1153
+
1154
+ /**
1155
+ * The worst (largest) width:height aspect ratio among `row`'s
1156
+ * rectangles if laid out along a strip of the given `side` length —
1157
+ * the score squarify minimizes one row at a time. Pure. Assumes every
1158
+ * item's `.area` is already computed (see _squarify).
1159
+ * @private
1160
+ */
1161
+ _squarifyWorstRatio(row, side) {
1162
+ return Hierarchy.squarifyWorstRatio(row, side);
1163
+ }
1164
+
1165
+ /**
1166
+ * Squarified treemap (Bruls/Huizing/van Wijk): lays `items` (each
1167
+ * `{value, ref}`, pre-sorted descending by value by the caller) into
1168
+ * the rect `x,y,width,height`, area-proportional to value. Repeatedly
1169
+ * grows a "row" along the shorter side of the remaining space while
1170
+ * the worst aspect ratio in that row keeps improving, then flushes the
1171
+ * row and continues into what's left. Pure — operates on generic
1172
+ * `{value, ref}` pairs, not tied to the tree-node shape, so it's
1173
+ * directly testable with plain numbers.
1174
+ * @returns {Array<{ref: *, x: number, y: number, width: number, height: number}>}
1175
+ * @private
1176
+ */
1177
+ _squarify(items, x, y, width, height) {
1178
+ return Hierarchy.squarify(items, x, y, width, height);
1179
+ }
1180
+
1181
+ /**
1182
+ * Recursive layout over a treemapChart() node tree. Computes each
1183
+ * node's value (_hierarchyValue), squarifies this level (_squarify), and
1184
+ * for a branch reserves a fixed-height header band at the top of its
1185
+ * rect before recursing into the remaining content rect for its
1186
+ * children at depth+1. Returns a FLAT list covering every node in the
1187
+ * tree — a branch's own entry is pushed before its children are
1188
+ * recursed into (pre-order), so a caller painting in array order never
1189
+ * covers a node with its own descendant drawn first. Each entry also
1190
+ * carries `topAncestor` (the depth-0 node this one descends from, or
1191
+ * itself at depth 0) so a renderer can color every descendant off its
1192
+ * top-level group without re-walking the tree. Pure — no DOM/renderer
1193
+ * access.
1194
+ * @returns {Array<{node: object, x: number, y: number, width: number, height: number, depth: number, isLeaf: boolean, topAncestor: object}>}
1195
+ * @private
1196
+ */
1197
+ _treemapLayout(nodes, x, y, width, height, depth = 0, topAncestors = null) {
1198
+ return Hierarchy.treemapLayout(nodes, x, y, width, height, depth, topAncestors, RenderEngine.TREEMAP_HEADER_H);
1199
+ }
1200
+
1201
+ /**
1202
+ * Renders a treemap — rectangles nested/sized proportional to value
1203
+ * (squarified layout, see _treemapLayout), the standard chart for
1204
+ * "composition of a whole across a hierarchy" (e.g. disk usage by
1205
+ * folder, headcount by department/team).
1206
+ * @param {Array<{label: string, value?: number, children?: Array}>} data
1207
+ * A leaf has `value` and no `children`; a branch has `children` (its
1208
+ * value is the sum, computed automatically) and can nest to any depth.
1209
+ * @param {object} [options]
1210
+ * @param {string[]} [options.colors] Palette — one color per top-level node, descendants shaded lighter by depth.
1211
+ * @param {string} [options.title]
1212
+ */
1213
+ treemapChart(data, options = {}) {
1214
+ if (!this.renderer) return;
1215
+ this._chart3D = false;
1216
+ this._chartState = { type: 'treemap', data, options };
1217
+ this._renderChart(new TreemapChart(this.renderer, data, options));
1218
+ }
1219
+
1220
+ /**
1221
+ * Recursive angular layout over the same node tree treemapChart()
1222
+ * consumes — mirrors _treemapLayout's shape but partitions ANGLE
1223
+ * instead of area, one ring per depth. Unlike _treemapLayout (which
1224
+ * sorts descending for squarify's aspect-ratio heuristic), input order
1225
+ * is preserved — same convention as pieChart, and there's no
1226
+ * aspect-ratio concept for a polar arc that sorting would improve.
1227
+ * Returns a flat pre-order list, each entry carrying `topAncestor` (the
1228
+ * depth-0 node this one descends from) for render-time color lookup —
1229
+ * same pattern _treemapLayout established. Pure — no DOM/renderer
1230
+ * access.
1231
+ * @returns {Array<{node: object, cx: number, cy: number, rInner: number, rOuter: number, startAngle: number, endAngle: number, depth: number, isLeaf: boolean, topAncestor: object}>}
1232
+ * @private
1233
+ */
1234
+ _sunburstLayout(nodes, cx, cy, innerRadius, ringWidth, startAngle, endAngle, depth = 0, topAncestors = null) {
1235
+ return Hierarchy.sunburstLayout(nodes, cx, cy, innerRadius, ringWidth, startAngle, endAngle, depth, topAncestors);
1236
+ }
1237
+
1238
+ /**
1239
+ * Renders a sunburst — the same hierarchical composition treemapChart
1240
+ * shows as nested rectangles, drawn instead as nested rings (one ring
1241
+ * per depth), each ring's arcs sized by value like a pie chart. Shares
1242
+ * treemapChart's exact data contract (_hierarchyValue) and its
1243
+ * top-level-palette/depth-lightened coloring scheme; reuses
1244
+ * _donutSlicePath (built for pieChart's doughnut mode) for the actual
1245
+ * annular-sector geometry, since a sunburst ring IS a doughnut slice.
1246
+ * @param {Array<{label: string, value?: number, children?: Array}>} data
1247
+ * Same shape as treemapChart: a leaf has `value`, a branch has
1248
+ * `children` (value computed as their sum) and can nest to any depth.
1249
+ * @param {object} [options]
1250
+ * @param {number} [options.cx] @param {number} [options.cy] @param {number} [options.radius]
1251
+ * @param {number} [options.innerRadiusRatio=0] Fraction of `radius` left as a center hole, same semantics as pieChart.
1252
+ * @param {string[]} [options.colors] Palette — one color per top-level node, descendants shaded lighter by depth (like treemapChart).
1253
+ * @param {string} [options.title]
1254
+ */
1255
+ sunburstChart(data, options = {}) {
1256
+ if (!this.renderer) return;
1257
+ this._chart3D = false;
1258
+ this._chartState = { type: 'sunburst', data, options };
1259
+ this._renderChart(new SunburstChart(this.renderer, data, options));
1260
+ }
1261
+
1262
+ /**
1263
+ * Flat, pre-order list of *visible* rows for outlineChart() — a branch
1264
+ * present in `collapsed` (a Set of node object references, not ids —
1265
+ * this shape has no id field) contributes its own row but none of its
1266
+ * descendants'. Threads `topAncestor` through the recursion the same
1267
+ * way _treemapLayout/_sunburstLayout do, so the renderer can look up a
1268
+ * row's top-level accent color without re-walking the tree. Pure — no
1269
+ * DOM/renderer access, directly unit-testable.
1270
+ * @returns {Array<{node: object, depth: number, hasChildren: boolean, topAncestor: object}>}
1271
+ * @private
1272
+ */
1273
+ _outlineLayout(nodes, depth, collapsed, topAncestors = null) {
1274
+ return Hierarchy.outlineLayout(nodes, depth, collapsed, topAncestors);
1275
+ }
1276
+
1277
+ /**
1278
+ * A leaf row's trailing descriptive text — every field beyond
1279
+ * label/value/children, values only (no "key: " prefix, unlike
1280
+ * _nodeExtraDataLines' tooltip lines — a plain dash-joined fragment
1281
+ * reads better on an outline row than a labeled list), e.g. a table
1282
+ * row's "3 columns · 3 rows · hybrid" from
1283
+ * `{label:'users', columns:3, rows:3, mode:'hybrid'}`. Pure.
1284
+ * @private
1285
+ */
1286
+ _outlineExtraText(node) {
1287
+ return Hierarchy.outlineExtraText(node, RenderEngine.HIERARCHY_STRUCTURAL_KEYS);
1288
+ }
1289
+
1290
+ /** Re-invokes outlineChart() with its last data/options — same
1291
+ * "re-render after a click-driven state change" pattern
1292
+ * _rerenderGraph() uses for graphDiagram's own +/− badges.
1293
+ * @private */
1294
+ _rerenderOutline() {
1295
+ if (this._chartState?.type === 'outline') this.outlineChart(this._chartState.data, this._chartState.options);
1296
+ }
1297
+
1298
+ /**
1299
+ * Renders an outline / tree list — the same hierarchical composition
1300
+ * treemapChart/sunburstChart show as nested rectangles/rings, drawn
1301
+ * instead as an indented, collapsible list (a file-explorer or site-menu's
1302
+ * usual shape): one row per node, a branch's own value auto-summed from
1303
+ * its descendants (_hierarchyValue) shown as a trailing count, a leaf's
1304
+ * own extra fields (anything beyond label/value/children) shown as
1305
+ * trailing descriptive text instead. Any branch gets a +/− toggle;
1306
+ * clicking it collapses/expands just that subtree and re-renders —
1307
+ * collapse state (`this._outlineCollapsed`, a Set of node object
1308
+ * references) persists across re-renders of the *same* data array, the
1309
+ * same convention graphDiagram's hierarchical-key +/− badges use for
1310
+ * `_graphCollapsedNodes`. Content taller than the container scales down
1311
+ * to fit (top-anchored, not centered) rather than clipping or scrolling —
1312
+ * same "set viewBox to the content's real size" trick renderDiagram()
1313
+ * uses for a wide railroad diagram. SVG-only: the toggle/click
1314
+ * interactivity needs real per-row DOM elements, same reason
1315
+ * graphDiagram is SVG-only.
1316
+ * @param {Array<{label: string, value?: number, children?: Array}>} data
1317
+ * Same shape as treemapChart/sunburstChart: a leaf has `value` (or its
1318
+ * own extra fields), a branch has `children` (value computed as their
1319
+ * sum) and can nest to any depth. Any field beyond label/value/children
1320
+ * is extra descriptive data, not part of the structural contract.
1321
+ * @param {object} [options]
1322
+ * @param {string[]} [options.colors] Palette — one color per top-level node's row accent, descendants shaded lighter by depth (same scheme as treemapChart/sunburstChart).
1323
+ * @param {string} [options.title]
1324
+ * @param {function} [options.onNodeClick] Fired when a row's label (not its +/− toggle) is clicked.
1325
+ */
1326
+ outlineChart(data, options = {}) {
1327
+ if (!this.renderer || !this.renderer.supportsSceneGraph) {
1328
+ console.warn('Outline chart is only supported in SVG renderer.');
1329
+ return;
1330
+ }
1331
+ this._chart3D = false;
1332
+ this._chartState = { type: 'outline', data, options };
1333
+ this._outlineCollapsed = this._outlineCollapsed || new Set();
1334
+ this._renderChart(new OutlineChart(this.renderer, data, options, this._outlineCollapsed, () => this._rerenderOutline()));
1335
+ }
1336
+
1337
+ /**
1338
+ * Renders a radar/spider chart — one polygon per series across N shared
1339
+ * axes, each axis scaled independently by its own max value across all
1340
+ * series, so differently-scaled metrics (e.g. "latency ms" vs "score
1341
+ * 0-10") share one chart without one axis flattening the others.
1342
+ * @param {Array<{name:string, values:Array<{axis:string, value:number}>}>|Array<{axis:string,value:number}>} data
1343
+ * @param {object} [options]
1344
+ * @param {string[]} [options.colors]
1345
+ * @param {boolean} [options.legend=true]
1346
+ */
1347
+ radarChart(data, options = {}) {
1348
+ if (!this.renderer) return;
1349
+ this._chart3D = false;
1350
+ this._chartState = { type: 'radar', data, options };
1351
+ this._renderChart(new RadarChart(this.renderer, data, options));
1352
+ }
1353
+
1354
+ /**
1355
+ * Renders a funnel chart — one horizontal trapezoid band per stage,
1356
+ * width proportional to `value` relative to the largest stage, for
1357
+ * visualizing drop-off across a sequential process (e.g. a sales
1358
+ * pipeline). Each stage's bottom edge width comes from the *next*
1359
+ * stage's value, so the taper between adjacent bands is continuous.
1360
+ * @param {Array<{label:string, value:number}>} data ordered top-to-bottom (largest first, by convention)
1361
+ * @param {object} [options]
1362
+ * @param {string[]} [options.colors]
1363
+ * @param {string} [options.title]
1364
+ */
1365
+ funnelChart(data, options = {}) {
1366
+ if (!this.renderer) return;
1367
+ this._chart3D = false;
1368
+ this._chartState = { type: 'funnel', data, options };
1369
+ this._renderChart(new FunnelChart(this.renderer, data, options));
1370
+ }
1371
+
1372
+ /**
1373
+ * Renders a waterfall chart — running-total bars, each floating from the
1374
+ * previous cumulative value to the new one (a "delta" bar), except
1375
+ * entries marked `type:'total'` which draw full-height from zero (a
1376
+ * subtotal/grand-total checkpoint). Increases/decreases are colored
1377
+ * distinctly; thin connector lines between consecutive bars follow the
1378
+ * classic waterfall styling.
1379
+ * @param {Array<{label:string, value:number, type?:'delta'|'total'}>} data
1380
+ * @param {object} [options]
1381
+ * @param {string} [options.upColor='#26a69a']
1382
+ * @param {string} [options.downColor='#ef5350']
1383
+ * @param {string} [options.totalColor='#5470c6']
1384
+ */
1385
+ waterfallChart(data, options = {}) {
1386
+ if (!this.renderer) return;
1387
+ this._chart3D = false;
1388
+ this._chartState = { type: 'waterfall', data, options };
1389
+ this._renderChart(new WaterfallChart(this.renderer, data, options));
1390
+ }
1391
+
1392
+ /**
1393
+ * Reads whatever `colorCol` names for one data point — a plain field
1394
+ * name (unchanged behavior: `d[colorCol]`), or a compiled
1395
+ * `{formula, params}` expression evaluated over the point's own fields
1396
+ * (`kernelExpr.js`, the same small grammar sqlengine's compiled UDFs
1397
+ * use — see `documentation`/`tasks.md`). Shared by every chart with a
1398
+ * `colorCol` option (scatterChart/bubbleChart/bubbleChart3D) so a
1399
+ * formula means the same thing wherever it's written, and each distinct
1400
+ * value it returns gets its own palette color exactly as a plain
1401
+ * field's distinct values already do.
1402
+ * @private
1403
+ */
1404
+ _readColorCol(d, colorCol) {
1405
+ return ChartData.readColorCol(d, colorCol);
1406
+ }
1407
+
1408
+ /**
1409
+ * Renders a plain 2D scatter plot — x/y points at a fixed marker radius,
1410
+ * no size encoding (see bubbleChart() for that, which requires a `z`
1411
+ * field this doesn't). Optional per-point categorical coloring via
1412
+ * `colorCol`/`category`/`group`, same convention as bubbleChart. Uses a
1413
+ * real [min,max] domain per axis (not a 0-anchored one), so negative
1414
+ * values (e.g. investment returns, normal quantiles) plot correctly —
1415
+ * qqPlot/lagPlot both delegate their entire rendering here and need
1416
+ * exactly that.
1417
+ * @param {Array<{label: string, x: number, y: number}>} data The data to chart.
1418
+ * @param {object} [options] Charting options.
1419
+ * @param {boolean|{from:{x,y},to:{x,y}}} [options.referenceLine] A dashed
1420
+ * reference line: `true` draws identity (y=x) over the domains'
1421
+ * overlap (what lagPlot uses); an explicit `{from,to}` pair (data
1422
+ * space) draws exactly that segment (what qqPlot's fitted Q1/Q3 line
1423
+ * uses — not the same as identity).
1424
+ */
1425
+ scatterChart(data, options = {}) {
1426
+ if (!this.renderer) return;
1427
+ this._chart3D = false; // leaving 3D mode — clears the SVG-zoom guard added for bubbleChart3D
1428
+ this._chartState = { type: 'scatter', data, options };
1429
+ this._renderChart(new ScatterChart(this.renderer, data, options));
1430
+ }
1431
+
1432
+ /**
1433
+ * Renders a Q-Q (quantile-quantile) plot — tests whether a raw sample
1434
+ * is approximately normally distributed, the standard tail-risk/
1435
+ * fat-tails diagnostic for a return series before trusting a risk
1436
+ * model's normality assumption. Each point is (theoretical normal
1437
+ * quantile, sample quantile) via _qqPoints; a dashed reference line
1438
+ * fit through the sample's own Q1/Q3 (the standard qqline() convention
1439
+ * — not a plain y=x identity) shows what "perfectly normal" would look
1440
+ * like — points hugging the line are normal-ish, systematic curvature
1441
+ * (especially at the ends) signals fat tails/skew. Delegates its
1442
+ * entire rendering (points + reference line) to scatterChart via its
1443
+ * `referenceLine` option — no separate rendering code here.
1444
+ * @param {number[]} values Raw numeric sample.
1445
+ * @param {object} [options] Forwarded to scatterChart (title/axis labels/colors/box).
1446
+ */
1447
+ qqPlot(values, options = {}) {
1448
+ if (!this.renderer) return;
1449
+ this._chart3D = false;
1450
+ this._chartState = { type: 'qqPlot', data: values, options };
1451
+ this._renderChart(new QQPlotChart(this.renderer, values, options));
1452
+ }
1453
+
1454
+ /**
1455
+ * Renders a lag plot — value[t] against value[t-lag] (default lag=1),
1456
+ * the standard visual autocorrelation check: points scattered with no
1457
+ * structure suggest an uncorrelated (white-noise) series; a clear
1458
+ * diagonal trend (hugging the identity reference line) suggests real
1459
+ * serial dependence — worth checking before treating lagged values as
1460
+ * predictive features (e.g. a KAN model's input engineering). The pure
1461
+ * lag-pairing transform (_lagPairs) is the only genuinely new logic
1462
+ * here — rendering delegates entirely to scatterChart.
1463
+ * @param {number[]} values Raw ordered numeric series.
1464
+ * @param {object} [options] `lag` (default 1) plus everything scatterChart accepts.
1465
+ */
1466
+ lagPlot(values, options = {}) {
1467
+ if (!this.renderer) return;
1468
+ this._chart3D = false;
1469
+ this._chartState = { type: 'lagPlot', data: values, options };
1470
+ this._renderChart(new LagPlotChart(this.renderer, values, options));
1471
+ }
1472
+
1473
+ /**
1474
+ * Renders a bubble chart.
1475
+ * @param {Array<{label: string, x: number, y: number, z: number}>} data The data to chart.
1476
+ * @param {object} [options] Charting options.
1477
+ */
1478
+ bubbleChart(data, options = {}) {
1479
+ if (!this.renderer) return;
1480
+ this._chart3D = false; // leaving 3D mode — clears the SVG-zoom guard added for bubbleChart3D
1481
+ this._chartState = { type: 'bubble', data, options };
1482
+ this._renderChart(new BubbleChart(this.renderer, data, options));
1483
+ }
1484
+
1485
+ /**
1486
+ * Projects a normalized 3D point (each axis roughly in [-1, 1], centered
1487
+ * on the data's own origin) through a real orbit camera (OrbitCamera.js —
1488
+ * composed Mat4 rotation + a genuine perspective-projection matrix, not
1489
+ * the hand-tuned `distance/(distance+depth*0.6)` heuristic this used to
1490
+ * be) down to a 2D offset plus a depth value for z-sorting. Callers scale
1491
+ * sx/sy by the chart's own pixel radius and offset to its center — same
1492
+ * contract as before, so bubbleChart3D/_drawBox3D's `toScreen` closures
1493
+ * didn't need to change. `camera` is the plain {theta,phi,distance}
1494
+ * state `_camera3D`/`_addOrbit3DListeners` already drive; fovY/near/far
1495
+ * aren't part of that shared state (nothing currently varies them), so
1496
+ * OrbitCamera's own defaults apply. Square aspect (1) — callers already
1497
+ * scale sx and sy by the same single pixelRadius, i.e. they assume
1498
+ * uniform x/y scaling, so this preserves that.
1499
+ * @private
1500
+ */
1501
+ _project3D(x, y, z, camera) {
1502
+ return Chart3D.project(x, y, z, camera);
1503
+ }
1504
+
1505
+ /**
1506
+ * Renders a true 3D scatter/bubble chart — x/y/z are all spatial axes
1507
+ * (unlike bubbleChart(), where z encodes bubble *size*, not position) —
1508
+ * with an orbit camera you can drag to rotate and scroll to zoom (see
1509
+ * _addOrbit3DListeners). Distinct from bubbleChart({zAxis:true})'s fixed
1510
+ * isometric shift: this recomputes a real projection every frame and
1511
+ * z-sorts points back-to-front, so nearer points correctly occlude
1512
+ * farther ones as you rotate.
1513
+ * @param {Array<{label: string, x: number, y: number, z: number, size?: number, category?: string}>} data
1514
+ * @param {object} [options]
1515
+ * @param {string|{formula:string,params:string[]}} [options.colorCol] Field name to color points by
1516
+ * (auto-detects `category`/`group`), or a compiled formula over the point's own fields, e.g.
1517
+ * `{formula: 'value > threshold ? 1 : 0', params: ['value', 'threshold']}` — see `kernelExpr.js`.
1518
+ * @param {string[]} [options.colors] Palette for categorical coloring.
1519
+ * @param {string} [options.title]
1520
+ * @param {number} [options.initialTheta=-0.6] Starting azimuth (radians).
1521
+ * @param {number} [options.initialPhi=0.35] Starting elevation (radians).
1522
+ * @param {number} [options.initialDistance=2.2] Starting camera distance (zoom).
1523
+ */
1524
+ bubbleChart3D(data, options = {}) {
1525
+ if (!this.renderer) return;
1526
+
1527
+ const {
1528
+ initialTheta = -0.6, initialPhi = 0.35, initialDistance = 2.2,
1529
+ } = options;
1530
+
1531
+ // Camera state persists across redraws (drag/wheel) — only seed it
1532
+ // the first time this exact chart is drawn, not on every redraw.
1533
+ if (!this._camera3D) this._camera3D = { theta: initialTheta, phi: initialPhi, distance: initialDistance };
1534
+ this._initialCamera3D = { theta: initialTheta, phi: initialPhi, distance: initialDistance };
1535
+ this._chart3D = true;
1536
+ this._chartState = { type: 'bubble3d', data, options };
1537
+ this._chart3DRedraw = () => this.bubbleChart3D(data, { ...options, initialTheta, initialPhi, initialDistance });
1538
+
1539
+ this._renderChart(new BubbleChart3D(this.renderer, data, options, this._camera3D));
1540
+ }
1541
+
1542
+ /**
1543
+ * Draws one 3D box (a bar or one segment of a stacked bar) as three
1544
+ * visible faces — top, plus one X-side and one Z-side wall — through the
1545
+ * orbit camera. Full backface culling/lighting is out of scope here:
1546
+ * instead of testing every face, the X/Z side to draw is chosen by the
1547
+ * sign of the camera's approximate world-space direction
1548
+ * (sin(theta)/cos(theta) for a standard orbit-camera spherical→cartesian
1549
+ * position), which is cheap and looks correct across the normal drag
1550
+ * range even though it isn't exact culling. Bars don't interpenetrate,
1551
+ * so the caller only needs to z-sort *bars* (by center depth), not faces.
1552
+ * @returns {number} the box's average corner depth, for the caller's own
1553
+ * bar-level z-sort.
1554
+ * @private
1555
+ */
1556
+ _drawBox3D(x0, x1, y0, y1, z0, z1, color, toScreen, camera) {
1557
+ return Chart3D.drawBox(this.renderer, x0, x1, y0, y1, z0, z1, color, toScreen, camera);
1558
+ }
1559
+
1560
+ /**
1561
+ * Groups flat {x, z, ...} data into grid indices/labels for the two
1562
+ * orbit-camera bar chart variants — shared so barChart3DOrbit and
1563
+ * stackedBarChart3DOrbit bucket categories identically.
1564
+ * @private
1565
+ */
1566
+ _bucketGrid3D(data) {
1567
+ return Chart3D.bucketGrid(data);
1568
+ }
1569
+
1570
+ /**
1571
+ * Renders a grouped 3D bar chart across two categorical axes (x and z)
1572
+ * with a real orbit camera — drag to rotate, scroll to zoom (see
1573
+ * _addOrbit3DListeners). Distinct from barChart3D's isometric single-axis
1574
+ * bars: this is what "3D bar chart" usually means (a grid of bars, viewed
1575
+ * from any angle), not a fixed-tilt 2D chart with a depth shadow.
1576
+ * @param {Array<{x: string, z: string, value: number}>} data
1577
+ * @param {object} [options]
1578
+ * @param {string[]} [options.colors] Palette, indexed by z-group.
1579
+ * @param {string} [options.title]
1580
+ * @param {number} [options.initialTheta=-0.5] @param {number} [options.initialPhi=0.45] @param {number} [options.initialDistance=2.4]
1581
+ */
1582
+ barChart3DOrbit(data, options = {}) {
1583
+ if (!this.renderer) return;
1584
+
1585
+ const { initialTheta = -0.5, initialPhi = 0.45, initialDistance = 2.4 } = options;
1586
+
1587
+ if (!this._camera3D) this._camera3D = { theta: initialTheta, phi: initialPhi, distance: initialDistance };
1588
+ this._initialCamera3D = { theta: initialTheta, phi: initialPhi, distance: initialDistance };
1589
+ this._chart3D = true;
1590
+ this._chartState = { type: 'bar3dOrbit', data, options };
1591
+ this._chart3DRedraw = () => this.barChart3DOrbit(data, { ...options, initialTheta, initialPhi, initialDistance });
1592
+
1593
+ this._renderChart(new BarChart3DOrbit(this.renderer, data, options, this._camera3D));
1594
+ }
1595
+
1596
+ /**
1597
+ * Stacked-segment version of barChart3DOrbit: each (x, z) grid cell
1598
+ * holds a stack of `values`, each drawn as its own shorter box on top of
1599
+ * the previous segment (correct occlusion within one bar since segments
1600
+ * share x/z and only differ in y-extent — no special-casing needed
1601
+ * beyond the existing per-bar z-sort).
1602
+ * @param {Array<{x: string, z: string, values: number[]}>} data
1603
+ * @param {object} [options] Same as barChart3DOrbit, plus `colors` is
1604
+ * indexed by *segment index* (stack position) instead of z-group.
1605
+ */
1606
+ stackedBarChart3DOrbit(data, options = {}) {
1607
+ if (!this.renderer) return;
1608
+
1609
+ const { initialTheta = -0.5, initialPhi = 0.45, initialDistance = 2.4 } = options;
1610
+
1611
+ if (!this._camera3D) this._camera3D = { theta: initialTheta, phi: initialPhi, distance: initialDistance };
1612
+ this._initialCamera3D = { theta: initialTheta, phi: initialPhi, distance: initialDistance };
1613
+ this._chart3D = true;
1614
+ this._chartState = { type: 'stackedBar3dOrbit', data, options };
1615
+ this._chart3DRedraw = () => this.stackedBarChart3DOrbit(data, { ...options, initialTheta, initialPhi, initialDistance });
1616
+
1617
+ this._renderChart(new StackedBarChart3DOrbit(this.renderer, data, options, this._camera3D));
1618
+ }
1619
+
1620
+ /**
1621
+ * Renders a 3D bar chart (isometric projection).
1622
+ * @param {Array<{label: string, value: number, depth?: number}>} data The data to chart.
1623
+ * @param {object} [options] Charting options.
1624
+ */
1625
+ barChart3D(data, options = {}) {
1626
+ if (!this.renderer) return;
1627
+ this._chart3D = false; // leaving 3D mode — clears the SVG-zoom guard added for bubbleChart3D
1628
+ this._chartState = { type: 'bar3d', data, options };
1629
+ this._renderChart(new BarChart3D(this.renderer, data, options));
1630
+ }
1631
+
1632
+ /**
1633
+ * Helper function to lighten/darken a color for 3D effect.
1634
+ * @private
1635
+ */
1636
+ _lightenColor(color, amount) {
1637
+ return ColorPalette.lighten(color, amount);
1638
+ }
1639
+
1640
+ /** @private hex "#rrggbb" -> {r,g,b} */
1641
+ _hexToRgb(hex) {
1642
+ return ColorPalette.hexToRgb(hex);
1643
+ }
1644
+
1645
+ /** @private Linear-interpolates between two hex colors at t in [0,1]. */
1646
+ _interpolateColor(c1, c2, t) {
1647
+ return ColorPalette.interpolate(c1, c2, t);
1648
+ }
1649
+
1650
+ /**
1651
+ * Resolves a gauge `color` option to a single hex color for the given
1652
+ * 0-100 value. A plain string passes through unchanged. An array is
1653
+ * treated as a heat-map gradient, in either of two forms:
1654
+ * - plain hex strings, evenly spaced across 0-100:
1655
+ * `['#67e043', '#f9d939', '#f23c24']`
1656
+ * - `{stop, color}` pairs at explicit percentages (unevenly spaced
1657
+ * breakpoints, e.g. "green until 50, amber until 80, red above"):
1658
+ * `[{stop:0,color:'#67e043'},{stop:50,color:'#f9d939'},{stop:80,color:'#f23c24'}]`
1659
+ * @private
1660
+ */
1661
+ _resolveGaugeColor(color, percent) {
1662
+ return Gauge.resolveColor(color, percent);
1663
+ }
1664
+
1665
+ /**
1666
+ * Renders a bar gauge.
1667
+ * @param {number|{value:number, target?:number}} value The value to display (0-100 unless options.min/max are given).
1668
+ * @param {object} [options] Charting options.
1669
+ */
1670
+ barGauge(value, options = {}) {
1671
+ this._renderGauge(BarGauge, 'barGauge', value, options);
1672
+ }
1673
+
1674
+ /**
1675
+ * Renders a bullet graph: a primary bar compared against qualitative
1676
+ * performance ranges and a target marker.
1677
+ * @param {number|{value:number, target?:number, ranges?:Array<{max:number, label?, color?}>}} data The actual value, or an object with value/target/ranges.
1678
+ * @param {object} [options] Charting options.
1679
+ */
1680
+ bulletGauge(data, options = {}) {
1681
+ this._renderGauge(BulletGauge, 'bullet', data, options);
1682
+ }
1683
+
1684
+ /**
1685
+ * Renders a dial gauge.
1686
+ * @param {number|{value:number, target?:number}} value The value to display (0-100 unless options.min/max are given).
1687
+ * @param {object} [options] Charting options.
1688
+ */
1689
+ dialGauge(value, options = {}) {
1690
+ this._renderGauge(DialGauge, 'dialGauge', value, options);
1691
+ }
1692
+
1693
+ /**
1694
+ * Renders an arc gauge: a half circle (or any start angle and sweep) filled to the value.
1695
+ * @param {number|{value:number, target?:number}} value The value (0-100 unless options.min/max are given).
1696
+ * @param {object} [options] The gauge options (see REFERENCE.md, Gauges), cx, cy, r, thickness, startAngle, sweep, cap.
1697
+ */
1698
+ arcGauge(value, options = {}) {
1699
+ this._renderGauge(ArcGauge, 'arcGauge', value, options);
1700
+ }
1701
+
1702
+ /**
1703
+ * Renders a progress ring, its value in the middle.
1704
+ * @param {number|{value:number, target?:number}} value The value (0-100 unless options.min/max are given).
1705
+ * @param {object} [options] The gauge options, cx, cy, r, thickness.
1706
+ */
1707
+ ringGauge(value, options = {}) {
1708
+ this._renderGauge(RingGauge, 'ringGauge', value, options);
1709
+ }
1710
+
1711
+ /**
1712
+ * Renders several values as rings one inside the other, with a legend.
1713
+ * @param {Array<number|{value:number, label?:string, color?:string, min?:number, max?:number}>} values Outer ring first.
1714
+ * @param {object} [options] cx, cy, r, thickness, gap, colors, legend, min, max, format, units, title.
1715
+ */
1716
+ multiRingGauge(values, options = {}) {
1717
+ this._renderGauge(MultiRingGauge, 'multiRingGauge', values, options);
1718
+ }
1719
+
1720
+ /**
1721
+ * Renders a KPI tile: a figure, its change since the previous value, and a sparkline of its history.
1722
+ * @param {number|{value?:number, previous?:number, history?:number[]}} data
1723
+ * @param {object} [options] x, y, width, height, format, units, change ('percent'|'value'), upIsGood, bands, sparkColor, background, title.
1724
+ */
1725
+ kpiTile(data, options = {}) {
1726
+ this._renderGauge(KpiTile, 'kpiTile', data, options);
1727
+ }
1728
+
1729
+ /**
1730
+ * Renders a thermometer: a tube filled from a bulb up to the value.
1731
+ * @param {number|{value:number, target?:number}} value The value (0-100 unless options.min/max are given).
1732
+ * @param {object} [options] The gauge options, x, y, width, height, bulb.
1733
+ */
1734
+ thermometerGauge(value, options = {}) {
1735
+ this._renderGauge(ThermometerGauge, 'thermometerGauge', value, options);
1736
+ }
1737
+
1738
+ /**
1739
+ * Every gauge method draws through here. With `options.animate` (true:
1740
+ * 400ms, or milliseconds) a gauge of the same type already showing eases
1741
+ * from the value it shows to the new one; a newer call stops an older
1742
+ * animation and starts from where it got to.
1743
+ * @private
1744
+ */
1745
+ _renderGauge(GaugeType, type, data, options = {}) {
1746
+ if (!this.renderer) return;
1747
+ this._chart3D = false; // leaving 3D mode — clears the SVG-zoom guard added for bubbleChart3D
1748
+ this._chartState = { type, data, options };
1749
+ this._gaugeAnimation?.stop();
1750
+ const shown = this._gaugeShown?.type === type ? this._gaugeShown.data : null;
1751
+ const draw = (frame) => {
1752
+ this._gaugeShown = { type, data: frame };
1753
+ this._renderChart(new GaugeType(this.renderer, frame, options));
1754
+ };
1755
+ if (!options.animate || shown == null) {
1756
+ draw(data);
1757
+ return;
1758
+ }
1759
+ this._gaugeAnimation = new GaugeAnimation({
1760
+ from: GaugeValue.from(shown, options),
1761
+ to: GaugeValue.from(data, options),
1762
+ duration: options.animate === true ? 400 : Number(options.animate),
1763
+ draw,
1764
+ }).start();
1765
+ }
1766
+
1767
+ _arcPath(cx, cy, r, startAngle, endAngle) {
1768
+ return Gauge.arcPath(cx, cy, r, startAngle, endAngle);
1769
+ }
1770
+
1771
+ /**
1772
+ * Renders a simple network graph diagram. If any node's `id` is a
1773
+ * Dewey-style hierarchical key (digits joined by `.`, `-`, or `/`, e.g.
1774
+ * `"1.2.3"`, `"1-2-3"`) — see `_inferHierarchyEdges` — the parent-child
1775
+ * relationships implied by those keys replace `edges` entirely (except
1776
+ * "quasi-leaf" edges touching a non-Dewey-key node, which are kept and
1777
+ * attach that plain node as an extra child) and the diagram is laid out
1778
+ * as a top-down tree (`_computeTreeLayout`) instead of force-directed.
1779
+ * In that mode, any node with children gets a +/− badge to collapse or
1780
+ * expand its subtree, and a node's extra fields (beyond id/label/x/y/
1781
+ * shape/color/group/size) are auto-listed in its hover tooltip. Call
1782
+ * `filterGraphNodes(keyword)` at any time to live-filter which nodes are
1783
+ * shown by label/extra-data keyword (ancestors of a match stay visible
1784
+ * in hierarchical mode).
1785
+ * @param {Array<{id: string, label?: string|((node:object)=>string), name?: string, x: number, y: number, size?: number, icon?: string, iconUrl?: string}>} nodes
1786
+ * Array of node objects — `size` (a plain number) scales that node's radius, see `sizeBy` below. `icon`
1787
+ * (a short glyph/emoji string) or `iconUrl` (an image/SVG file or data URI) replaces the plain initials
1788
+ * text centred on the node's shape — `iconUrl` wins if both are set; neither applies to `shape:'person'`
1789
+ * (which already draws its own glyph) or `shape:'table'`. A `shape:'rect'` node's own `width`/`height`
1790
+ * override the usual square-of-radius box (see `options.labelPosition` for putting its label inside it,
1791
+ * word-wrapped and clipped to that box rather than a single overflowing line). Its own `cornerStyle`
1792
+ * (`'sharp'|'rounded'|'chamfered'`, default a plain small rx) picks the corner treatment — `archimateDiagram`
1793
+ * uses this for ArchiMate's own generic box shapes (_archimateCornerStyle), but it's a plain graphDiagram
1794
+ * node field, usable without archimateDiagram at all.
1795
+ * **Label resolution** (see `_resolveNodeLabel`, used everywhere a node's label is shown — the box/
1796
+ * caption text, tooltip, table header, keyword search): `label` may be a plain string (used as-is), a
1797
+ * function `(node) => string` (called at render time — the same "may be a function" convention
1798
+ * `contextMenuItems`' own `label` already uses), or a string containing `{field}` placeholders (each
1799
+ * replaced with that field's own value off the node — plain substitution, not a parser, e.g.
1800
+ * `'{name} ({type})'`). An empty/unset `label` falls back to `name`.
1801
+ * @param {Array<{source: string, target: string, weight?: number}>} edges Array of edge objects linking nodes by id — replaced by the inferred hierarchy if one is detected (quasi-leaf edges excepted, see above). `weight` scales that edge's stroke width, see `minEdgeWidth` below.
1802
+ * @param {object} [options] Rendering options.
1803
+ * @param {'degree'} [options.sizeBy] Size nodes by connection count (in + out edges) instead of requiring the
1804
+ * caller to pre-populate `node.size` — useful when "how connected is this node" IS the value worth showing.
1805
+ * @param {number} [options.minRadius] @param {number} [options.maxRadius] @param {'linear'|'sqrt'} [options.sizeScale]
1806
+ * Range/curve `node.size` (or degree, with `sizeBy:'degree'`) maps into — only takes effect once some node
1807
+ * has a finite size value; `sqrt` compresses outliers where the value scales with an area/count, not a length.
1808
+ * @param {number} [options.minEdgeWidth] @param {number} [options.maxEdgeWidth] @param {'linear'|'sqrt'} [options.edgeWidthScale]
1809
+ * Same shape as node sizing, for `edge.weight` → stroke width; only takes effect once some edge has a finite weight.
1810
+ * @param {'arrow'|'circle'|'tee'|'diamond'|'diamond-hollow'|'triangle-hollow'|'open-arrow'|'crowsfoot-one'|'crowsfoot-one-optional'|'crowsfoot-many'|'crowsfoot-many-mandatory'|'none'} [edge.arrowhead='arrow']
1811
+ * Glyph at the target end. `edge.arrowheadStart` is the same set, independently, for the SOURCE end (e.g.
1812
+ * ArchiMate composition's filled diamond there while the target end carries none — see archimateDiagram;
1813
+ * the `crowsfoot-*` glyphs are ERD cardinality notation, see dataModelDiagram).
1814
+ * @param {string} [edge.sourceLabel] @param {string} [edge.targetLabel] Independent text labels near each
1815
+ * end (e.g. a UML association's multiplicity, '0..1'/'1..*'/...) — distinct from the one midpoint
1816
+ * `edge.label` above (e.g. the relationship's own name); see dataModelDiagram's 'uml' notation.
1817
+ * @param {'solid'|'dashed'|'dotted'} [edge.lineStyle] Generalizes the older boolean `edge.dashed` (still
1818
+ * honored when `lineStyle` is unset) — 'dotted' is the case neither the old flag nor its fixed pattern covered.
1819
+ * @param {'straight'|'curved'|'orthogonal'|'polyline'} [options.edgeRouting='straight'] Default auto-routing
1820
+ * for edges that don't set their own `edge.routing`. `'straight'` is the original point-to-point line.
1821
+ * `'curved'` is a smooth bezier leaving/arriving along each end's own normal. `'orthogonal'` is a
1822
+ * right-angle (Manhattan) connector. `'polyline'` is a straight line that steps around one other node
1823
+ * with a single waypoint if it's directly in the way, otherwise it's the same as `'straight'`. An
1824
+ * individual edge can also carry `weight`/`color`/`label`/`dashed`/`arrowhead` (existing) plus
1825
+ * `routing` (overrides this default) and `sourceSide`/`targetSide` (`'top'|'right'|'bottom'|'left'`,
1826
+ * default `'auto'`) — a pinned side fixes *where on the node* the line attaches (it stays there as the
1827
+ * node moves) instead of always sliding around to face whatever it's connected to; force-directed
1828
+ * organic routing is what `graphDiagram`'s node layout already does by default (nodes/edges settle via
1829
+ * the same spring simulation) — see the class doc comment above for the tree-layout alternative.
1830
+ * When `editable`, any edge can also be hand-routed: right-click it and choose "Add Waypoint Here" to
1831
+ * drop a bend point where you clicked (`_insertEdgeWaypoint`/`_edgeWaypointsWithInsertion`), drag a bend
1832
+ * point to move it, double-click a bend point to remove it. That builds `edge.waypoints` (`[{x,y}, ...]`,
1833
+ * world/SVG coordinates) — set it directly to hand a route in instead, or read it back via
1834
+ * `onEdgeWaypointsChange(edge, waypoints)`. Waypoints beat every auto routing style outright, threading
1835
+ * the line through them in order.
1836
+ * @param {boolean} [options.resizable=false] Diagram-wide default for whether a node shows a resize
1837
+ * handle on hover — an individual node's own `node.resizable` (true/false) overrides this default
1838
+ * either way. Dragging the handle sets `node.radius` (a hard override, beating `sizeBy`/domain
1839
+ * sizing the same way an explicit `node.x`/`node.y` already beats computed layout) or, for a
1840
+ * `shape:'table'` node, `node.width` — see `onNodeResize(node)`.
1841
+ * @param {'top-left'|'top-right'|'bottom-left'|'bottom-right'} [options.resizeCorner='top-right']
1842
+ * Which corner the resize handle sits at, diagram-wide. `resizeCornerByShape: {shape: corner}`
1843
+ * overrides it per node shape, and a node's own `node.resizeCorner` overrides both.
1844
+ * @param {boolean} [options.edgesOnTop=false] Draw lines over boxes rather than under them (ArchiMate
1845
+ * tools do: a line into an element drawn inside another stays visible). Selection handles stay on top.
1846
+ * @param {'center'|'corner'} [options.iconPosition='center'] Where a node's `icon`/`iconUrl` (see the
1847
+ * nodes param above) sits — centred on the shape (replacing the initials) or a small badge at a corner
1848
+ * (`iconCorner`/`iconCornerByShape`/node's own `node.iconCorner` — same precedence as `resizeCorner`,
1849
+ * independently configurable). A node's own `node.iconPosition` overrides this default.
1850
+ * @param {'below'|'inside'} [options.labelPosition='below'] `'below'` is the original caption-under-the-
1851
+ * shape convention; `'inside'` left-aligns the label inside the box instead — meant for a `shape:'rect'`
1852
+ * node with its own `width`/`height` (a non-square override, same idea as a table node's `node.width`).
1853
+ * A node's own `node.labelPosition` overrides this default. See `archimateDiagram`, which sets both.
1854
+ * @param {Array<object>} [options.contextMenuItems] Replaces the built-in node right-click menu
1855
+ * (Pin/Unpin, Rename [editable only], Show Details, Hide, Delete). Each entry is either
1856
+ * `{separator: true}`, `{label, action(node, engine)}` (`label` may be a `label(node)`
1857
+ * function), or `{label, items: [...]}` for a hover pulldown/submenu of the same shape —
1858
+ * useful for a family of related actions (e.g. several layout choices) under one row.
1859
+ * @param {Array<object>} [options.edgeContextMenuItems] Same shape, replacing the built-in edge
1860
+ * menu (Rename/Delete) — only shown when `editable` is true.
1861
+ * @param {Array<object>} [options.canvasContextMenuItems] Same shape, replacing the built-in
1862
+ * canvas (empty-space) menu (Add Node). Unlike the edge menu, supplying this is enough on its
1863
+ * own to show a canvas-level menu even with `editable` left off — the built-in Add Node default
1864
+ * is genuinely an editing action so *that* stays editable-gated, but a caller providing their
1865
+ * own canvas items (e.g. re-arranging nodes — a view action, not an edit) clearly wants a menu
1866
+ * regardless. Every context-menu action — built-in or one of these — receives `(subject, engine,
1867
+ * point)`, `point` being the right-click location in SVG/world coordinates (`{x,y}`).
1868
+ * @param {(node:object)=>void} [options.onNodeDoubleClick] Double-click on a node calls this instead of
1869
+ * doing anything else — nothing built-in happens on its own (no default dialog), so a caller wanting
1870
+ * "double-click to edit" wires this to open their own properties UI (see the Properties dialog in
1871
+ * `test/charts.html`, opened this way there).
1872
+ * @param {(edge:object)=>void} [options.onEdgeDoubleClick] Same, for double-clicking an edge's line.
1873
+ * Double-clicking an existing waypoint handle still removes it regardless (a distinct, precise gesture
1874
+ * on a small routing control point, not "the edge" in general) — this fires for the line itself.
1875
+ * @param {(point:{x:number,y:number})=>void} [options.onCanvasDoubleClick] Double-clicking empty canvas
1876
+ * (no node/edge/waypoint under the cursor) calls this with the click point in SVG/world coordinates —
1877
+ * e.g. to open a diagram/view-level settings dialog, since there's no single node/edge object to edit.
1878
+ */ graphDiagram(nodes, edges, options = {}) {
1879
+ const { folders = [], folderLayout = {}, elementFolders = [] } = options;
1880
+
1881
+ // Build element-to-folder Map for quick lookup
1882
+ const elementFolderMap = new Map();
1883
+ for (const [elementId, folderId] of elementFolders) {
1884
+ elementFolderMap.set(elementId, folderId);
1885
+ }
1886
+
1887
+ // Pass elementFolderMap in options for rendering
1888
+ const optionsWithMap = { ...options, elementFolderMap };
1889
+
1890
+ this._chartState = { type: 'graph', nodes, edges, folders, options: optionsWithMap };
1891
+ this._chart3D = false;
1892
+ if (!this.renderer || !this.renderer.supportsSceneGraph) {
1893
+ console.warn('Advanced graph diagram is only supported in SVG renderer.');
1894
+ return;
1895
+ }
1896
+ this.renderer.clear();
1897
+ const diagram = new GraphDiagram(this.renderer, nodes, edges, optionsWithMap, this, folders);
1898
+ this._graphDiagram = diagram;
1899
+ this._renderDiagram(diagram);
1900
+ }
1901
+
1902
+ /**
1903
+ * ArchiMate-notation diagram — a thin transform over graphDiagram(),
1904
+ * *not* a separate renderer: it builds plain rect nodes/edges with
1905
+ * ArchiMate's own visual rules and calls graphDiagram() with them, so
1906
+ * every graphDiagram capability (drag, resize, routing, right-click
1907
+ * menus, editable add/rename/delete, ...) applies to the result exactly
1908
+ * as it would to any other graphDiagram.
1909
+ *
1910
+ * The actual notation data — layers, the official ~55-name element type
1911
+ * catalog, the 11 relationship types' line/arrowhead notation — lives in
1912
+ * `src/archimate.js` as real objects (ArchimateLayer/ArchimateElementType/
1913
+ * ArchimateRelationshipType), queried through the `ArchimateNotation`
1914
+ * registry (also `RenderEngine.ArchimateNotation`) rather than this
1915
+ * class's own flat lookup tables; an element's TYPE carries a genuine
1916
+ * reference to its LAYER object, not a name in one map and the same name
1917
+ * in a second one that could drift apart — see `ArchimateNotation.
1918
+ * typesInLayer(layer)` for the query that relationship supports directly
1919
+ * (e.g. a "type" dropdown that must narrow to match a chosen "layer" —
1920
+ * see the Properties/Add Element dialogs in test/charts.html).
1921
+ *
1922
+ * Matches Archi's (archimatetool.com) visual conventions structurally —
1923
+ * per-layer pastel fill, the label inside the box, a small type-icon
1924
+ * badge in a corner, official box corner shapes (chamfered/rounded/sharp
1925
+ * by layer and aspect), and the 11 official relationship line/arrowhead
1926
+ * notations (composition's filled diamond, aggregation's hollow diamond,
1927
+ * realization/specialization's hollow triangle, serving/access/
1928
+ * influence's open arrow, ...). Icons are representative glyphs echoing
1929
+ * each official pictogram's own idea, not a redraw of the real vector
1930
+ * artwork (ArchimateElementType#icon) — there's still no true per-type
1931
+ * icon CATALOG the way, say, a UML tool's stencil palette would have one.
1932
+ *
1933
+ * @param {Array<{id, label, layer?, type?, aspect?, icon?, iconUrl?, x?, y?, width?, height?, color?, cornerStyle?}>} elements
1934
+ * `layer` picks the fill colour (extendable/overridable via `options.layerColors`); if omitted,
1935
+ * derived from `type` (_archimateElementLayer/ArchimateNotation.elementLayer) — first checking
1936
+ * whether `type` is one of the ~55 official ArchiMate element names (most of these do NOT start
1937
+ * with their own layer's name — e.g. `type:'goal'` implies `layer:'motivation'`, `type:'node'`
1938
+ * implies `layer:'technology'` — so a plain prefix guess alone would get them wrong), then falling
1939
+ * back to the prefix of `type` before its first '-' for anything not in that catalog (so a custom
1940
+ * `type:'business-my-widget'` still implies `layer:'business'` with no extra config — this works
1941
+ * for ANY '<layer>-whatever' string, official name or not); `element.layer`/`type` both missing
1942
+ * falls back to 'other' (neutral grey). An explicit `element.color` always wins over the derived
1943
+ * layer colour, same as plain graphDiagram. The resolved `layer` (explicit or derived) is always
1944
+ * written back onto the built node — so it's present and editable even for an element that only
1945
+ * ever set `type`, not `layer` itself. An element's own `icon`/`iconUrl` always wins for its corner
1946
+ * badge; otherwise an official `type`'s own representative icon is used, then just a generic
1947
+ * per-layer one — either can be overridden diagram-wide via `options.defaultIcons`.
1948
+ * **Box corner style** (`_archimateCornerStyle`, matching the official notation's own generic
1949
+ * shapes): every Motivation-layer element is chamfered (all 4 corners cut) regardless of anything
1950
+ * else; `'behavior'` aspect (Process/Function/Service/Event/Interaction-like elements) gets rounded
1951
+ * corners — an explicit `aspect` wins, else an official `type`'s own aspect (so `type:'business-
1952
+ * process'` is rounded with zero extra config, same "official name already knows" idea as layer);
1953
+ * everything else is sharp (correct for active/passive-structure elements, the common case). An
1954
+ * element's own `cornerStyle` (`'sharp'|'rounded'|'chamfered'`) overrides all of that directly.
1955
+ * @param {Array<{source, target, kind?, label?}>} relationships
1956
+ * `kind` — one of composition/aggregation/assignment/realization/specialization/serving/
1957
+ * access/influence/triggering/flow/association (the 11 official ArchiMate relationship
1958
+ * types), or your own — picks the line style/arrowhead(s), extendable/overridable via
1959
+ * `options.relationshipStyles`; an explicit `lineStyle`/`dashed`/`arrowhead`/`arrowheadStart`
1960
+ * on any one relationship still overrides its kind's default, same as a plain graphDiagram
1961
+ * edge. An unrecognised/missing `kind` falls back to a plain solid association line rather
1962
+ * than being rejected.
1963
+ * @param {object} [options] Everything graphDiagram accepts (editable, resizable, onNodeClick,
1964
+ * contextMenuItems, ...), plus:
1965
+ * @param {object} [options.layerColors] Extra/overriding `layer -> color` entries.
1966
+ * @param {object} [options.relationshipStyles] Extra/overriding `kind -> {lineStyle,arrowhead,arrowheadStart}` entries.
1967
+ * @param {object} [options.defaultIcons] Extra/overriding `layer -> glyph` entries — wins even over an
1968
+ * official type's own icon (a deliberate diagram-wide override), used ONLY for an element that sets
1969
+ * neither `icon` nor `iconUrl` itself, so every element still shows some corner badge out of the box.
1970
+ * @param {number} [options.elementWidth=120] @param {number} [options.elementHeight=55]
1971
+ * Default box size for an element that doesn't set its own `width`/`height`.
1972
+ * @param {'3.2'|'4'} [options.archimateVersion='4'] Which ArchiMate version's element catalog/notation
1973
+ * applies (`ArchimateNotation.versions()`) — defaults to '4', the current standard. Under '4' (the
1974
+ * default), every element is first run through `ArchimateNotation.migrateElementToV4` (a no-op for
1975
+ * anything not one of the 19 retired/consolidated v3.2 types — see src/archimate.js's own doc
1976
+ * comment), so 3.2-authored data (e.g. `type:'business-process'`) still renders correctly, auto-
1977
+ * upgraded to its v4 equivalent (`type:'process'`) and tagged with its own migration provenance
1978
+ * (`ArchiMate4_Domain`/`MigratedFrom`, or `LegacyType`/`Type`+`MigratedFrom` for a retired type) —
1979
+ * both plain extra data, visible in the hover tooltip/Properties dialog like any other custom field.
1980
+ * Under '3.2', no migration happens and the element/relationship catalogs are the original ones.
1981
+ * @param {boolean} [options.useDistinctShapes=false] Diagram-wide default for whether an element with a
1982
+ * real ArchiMate notation shape (`ArchimateElementType#notationShape` — arrow for Process, chevron for
1983
+ * Function, cylinder for Role, cube for Node, ... — cross-checked against the spec's own Appendix A;
1984
+ * not every type has one) renders as that shape instead of the plain box + corner icon — an individual
1985
+ * element's own `useDistinctShape` (true/false) overrides this default either way. A type with no
1986
+ * implemented notationShape always renders as the plain box regardless.
1987
+ */
1988
+ archimateDiagram(elements, relationships, options = {}) {
1989
+ this._renderDiagram(new ArchimateDiagram(this.renderer, elements, relationships, options, this));
1990
+ }
1991
+
1992
+ /**
1993
+ * Live diagram-wide setting: which choice in each element type's own
1994
+ * `iconChoices` (src/archimate.js — choice 0 is always the ArchiMate-
1995
+ * standard glyph) is used as the DEFAULT icon for a node that doesn't
1996
+ * carry its own explicit `icon`/`iconUrl` — see archimateDiagram's own
1997
+ * `iconChoiceIndex` option, which only sets this once at build time.
1998
+ * Changing it here re-derives the icon for every node that hasn't been
1999
+ * individually customised (its current icon still matches what
2000
+ * auto-derivation gave it under the OLD index — the same "was it ever
2001
+ * customised" test the Properties dialog's own layer/type-change
2002
+ * handler uses in test/charts.html), leaving any node whose icon was
2003
+ * deliberately picked (via that dialog, or the original `elements` data)
2004
+ * untouched. No-op if this isn't (or is no longer) an ArchiMate diagram.
2005
+ */
2006
+ _archimateSetIconChoiceIndex(newIndex) {
2007
+ ArchimateDiagram.setIconChoiceIndex(this, newIndex);
2008
+ }
2009
+
2010
+ /** Pure. Maps a raw column/attribute type string (any casing/precision,
2011
+ * e.g. 'VARCHAR(255)', 'bigint', 'TIMESTAMPTZ') to one of the `shape:
2012
+ * 'table'` node's own colour-coded groups (see _buildTableNodeContent's
2013
+ * TYPE_COLORS) — 'UNKNOWN' (a neutral grey dot) for anything
2014
+ * unrecognised or unset, same fallback a real sqlengine-style table
2015
+ * node already uses for a column with no known type. */
2016
+ _dataModelTypeGroup(type) {
2017
+ return DataModelDiagram.typeGroup(type);
2018
+ }
2019
+
2020
+ /**
2021
+ * Pure. Builds one dataModelDiagram node from a raw `entity` at a given
2022
+ * level/notation — reusable directly, the same "the diagram only
2023
+ * enriches its own up-front input" reasoning as _archimateBuildNode/
2024
+ * _archimateBuildEdge (see their own comments) applies here too, for a
2025
+ * caller's own "add entity" UI. `entity.name`/`description` are shared
2026
+ * across every level (the entity's own identity); `entity[level]`
2027
+ * supplies that level's own `attributes` (and physical's own
2028
+ * `tableName`) — an entity with none at the current level (typical for
2029
+ * 'conceptual') always renders as a plain box, regardless of notation.
2030
+ * @param {object} entity {id, name, description?, x?, y?, color?,
2031
+ * conceptual?, logical?, physical?}
2032
+ * @param {{level: string, notation: NotationStyle}} opts
2033
+ */
2034
+ _dataModelBuildNode(entity, { level, notation }) {
2035
+ return DataModelDiagram.buildNode(entity, { level, notation });
2036
+ }
2037
+
2038
+ /**
2039
+ * Pure. Builds one dataModelDiagram edge from a raw `relationship` —
2040
+ * ONLY for a notation whose `relationshipAsNode` is false (Chen's "the
2041
+ * relationship IS its own diamond node, with two plain edges instead of
2042
+ * one" is handled directly in dataModelDiagram, since it touches both
2043
+ * the nodes and edges arrays, not just one edge). `relationship.
2044
+ * identifying === false` renders as a dashed line (a non-identifying
2045
+ * relationship, in real ERD terms) — unset/true stays solid.
2046
+ * @param {object} relationship {source, target, name?,
2047
+ * sourceCardinality?, targetCardinality?, identifying?}
2048
+ * @param {NotationStyle} notation
2049
+ */
2050
+ _dataModelBuildEdge(relationship, notation) {
2051
+ return DataModelDiagram.buildEdge(relationship, notation);
2052
+ }
2053
+
2054
+ /**
2055
+ * A conceptual/logical/physical data (entity-relationship) diagram — a
2056
+ * thin transform over graphDiagram, the same idea as archimateDiagram:
2057
+ * builds enriched nodes/edges, then calls graphDiagram with them. See
2058
+ * src/datamodel.js's own doc comment for the full reasoning; in short:
2059
+ *
2060
+ * **Three levels, one model — the cross-mapping.** `options.level`
2061
+ * (`'conceptual'|'logical'|'physical'`, default `'logical'`) picks which
2062
+ * of each entity's own `entity.conceptual`/`.logical`/`.physical`
2063
+ * sub-object supplies its attributes — `entity.name`/`.description` stay
2064
+ * shared across all three (the same object/visual split archimateDiagram
2065
+ * draws between an element and its diagram, just spanning "levels"
2066
+ * instead of "diagrams"). Re-calling this with a different `level` over
2067
+ * the SAME `entities`/`relationships` re-renders the same model at
2068
+ * different fidelity, so a conceptual entity, its logical attributes,
2069
+ * and its physical column types can never drift out of correspondence —
2070
+ * there's only ever one model, never three separately-maintained
2071
+ * diagrams.
2072
+ *
2073
+ * **Three notations.** `options.notation` (`'chen'|'crowsfoot'|'uml'`,
2074
+ * default `'crowsfoot'`) — Chen renders each relationship as its own
2075
+ * diamond-shaped node between two plain, text-labelled edges; Crow's
2076
+ * Foot renders cardinality as an arrowhead-style glyph at each entity
2077
+ * end; UML shows the cardinality (as a multiplicity string) as a text
2078
+ * label near each end instead (`edge.sourceLabel`/`targetLabel`).
2079
+ *
2080
+ * @param {Array<object>} entities See _dataModelBuildNode.
2081
+ * @param {Array<object>} relationships {source, target, name?,
2082
+ * sourceCardinality?, targetCardinality?, identifying?} — cardinality
2083
+ * is a UML multiplicity string (`DataModelNotation.cardinalities()`).
2084
+ * @param {object} [options] Everything graphDiagram accepts, plus:
2085
+ * @param {'conceptual'|'logical'|'physical'} [options.level='logical']
2086
+ * @param {'chen'|'crowsfoot'|'uml'} [options.notation='crowsfoot']
2087
+ */
2088
+ dataModelDiagram(entities, relationships, options = {}) {
2089
+ this._renderDiagram(new DataModelDiagram(this.renderer, entities, relationships, options, this));
2090
+ }
2091
+
2092
+ /**
2093
+ * A real 3D node-graph — nodes carry (or get simulated) x/y/z positions,
2094
+ * viewed through the same orbit camera as bubbleChart3D (drag to rotate,
2095
+ * scroll to zoom, double-click to reset — see _addOrbit3DListeners),
2096
+ * projected via OrbitCamera/Mat4 rather than 2D graphDiagram()'s SVG
2097
+ * viewBox pan/zoom. Nodes are simple point+label (matching bubbleChart3D's
2098
+ * dot style) — there is no per-node dragging in 3D (only camera orbit);
2099
+ * dragging a point in 2D screen space doesn't have a single obvious 3D
2100
+ * interpretation, so it's deliberately out of scope here.
2101
+ * @param {Array<{id:string, label?:string, x?:number, y?:number, z?:number, color?:string, group?:string, size?:number}>} nodes
2102
+ * @param {Array<{source:string, target:string, color?:string, weight?:number}>} edges
2103
+ * @param {object} [options]
2104
+ * @param {(node:object)=>void} [options.onNodeClick]
2105
+ * @param {number} [options.nodeRadius=8]
2106
+ * @param {'degree'} [options.sizeBy] Size nodes by connection count instead of `node.size`.
2107
+ * @param {number} [options.minRadius] @param {number} [options.maxRadius] @param {'linear'|'sqrt'} [options.sizeScale]
2108
+ * @param {number} [options.minEdgeWidth] @param {number} [options.maxEdgeWidth] @param {'linear'|'sqrt'} [options.edgeWidthScale]
2109
+ * Edge thickness from `edge.weight`, same scaling shape as node sizing.
2110
+ * @param {number} [options.initialTheta=-0.6] @param {number} [options.initialPhi=0.35] @param {number} [options.initialDistance=2.2]
2111
+ */
2112
+ graphDiagram3D(nodes, edges, options = {}) {
2113
+ const { initialTheta = -0.6, initialPhi = 0.35, initialDistance = 2.2 } = options;
2114
+ if (!this._camera3D) this._camera3D = { theta: initialTheta, phi: initialPhi, distance: initialDistance };
2115
+ this._initialCamera3D = { theta: initialTheta, phi: initialPhi, distance: initialDistance };
2116
+ this._chart3D = true;
2117
+ this._chart3DRedraw = () => this.graphDiagram3D(nodes, edges, options);
2118
+ this._renderDiagram(new GraphDiagram3D(this.renderer, nodes, edges, options, this._camera3D, this));
2119
+ }
2120
+
2121
+ /** Pure. Resolves a node's display label — delegates to LabelHelper. */
2122
+ _resolveNodeLabel(node) {
2123
+ return LabelHelper.resolveNodeLabel(node);
2124
+ }
2125
+
2126
+ /** Pure. Word-wraps label text — delegates to LabelHelper. */
2127
+ _wrapLabelLines(text, maxWidth, fontSize, maxHeight, lineHeight) {
2128
+ return LabelHelper.wrapLabelLines(text, maxWidth, fontSize, maxHeight, lineHeight);
2129
+ }
2130
+
2131
+ /** Pure. "key: value" lines for caller-supplied extra data — delegates to
2132
+ * LabelHelper. */
2133
+ _nodeExtraDataLines(node, knownKeys) {
2134
+ return LabelHelper.nodeExtraDataLines(node, knownKeys);
2135
+ }
2136
+
2137
+ /** Pure. Which ArchiMate layer an element belongs to — delegates to
2138
+ * ArchimateDiagram.elementLayer so the real logic lives with the diagram. */
2139
+ _archimateElementLayer(element, version) {
2140
+ return ArchimateDiagram.elementLayer(element, version);
2141
+ }
2142
+
2143
+ /** Pure. Fill colour for an ArchiMate `layer` name — delegates to
2144
+ * ArchimateDiagram.layerColor. */
2145
+ _archimateLayerColor(layer, layerColors, ownColor) {
2146
+ return ArchimateDiagram.layerColor(layer, layerColors, ownColor);
2147
+ }
2148
+
2149
+ /** Pure. Default corner-badge glyph for an ArchiMate element — delegates
2150
+ * to ArchimateDiagram.defaultIcon. */
2151
+ _archimateDefaultIcon(element, layer, defaultIcons, iconChoiceIndex = 0, version) {
2152
+ return ArchimateDiagram.defaultIcon(element, layer, defaultIcons, iconChoiceIndex, version);
2153
+ }
2154
+
2155
+ /** Pure. `{lineStyle, arrowhead, arrowheadStart}` notation for a
2156
+ * relationship `kind` — delegates to ArchimateDiagram.relationshipStyle. */
2157
+ _archimateRelationshipStyle(kind, relationshipStyles) {
2158
+ return ArchimateDiagram.relationshipStyle(kind, relationshipStyles);
2159
+ }
2160
+
2161
+ /** Builds one archimateDiagram node from a raw `element` — delegates to
2162
+ * ArchimateDiagram.buildNode so the diagram class owns the transform. */
2163
+ _archimateBuildNode(element, options = {}) {
2164
+ return ArchimateDiagram.buildNode(element, options);
2165
+ }
2166
+
2167
+ /** Pure. Builds one archimateDiagram edge from a raw `relationship` —
2168
+ * delegates to ArchimateDiagram.buildEdge. */
2169
+ _archimateBuildEdge(relationship, options = {}) {
2170
+ return ArchimateDiagram.buildEdge(relationship, options);
2171
+ }
2172
+
2173
+ /** Pure. Which corner style an ArchiMate `shape:'rect'` node renders —
2174
+ * delegates to ArchimateDiagram.cornerStyle. */
2175
+ _archimateCornerStyle(element, layer, version) {
2176
+ return ArchimateDiagram.cornerStyle(element, layer, version);
2177
+ }
2178
+
2179
+ /** Pure. SVG path `d` for a chamfered rectangle — delegates to NodeGeometry. */
2180
+ _chamferedRectPath(w, h, chamfer) {
2181
+ return NodeGeometry.chamferedRectPath(w, h, chamfer);
2182
+ }
2183
+
2184
+ /** Build the SVG element for a node's shape — delegates to NodeGeometry. */
2185
+ _nodeShapeEl(shape, R, color, size, cornerStyle, strokeColor = '#fff', strokeWidth = 2, iconUrl = null, icon = null) {
2186
+ return NodeGeometry.nodeShapeEl(this.renderer, shape, R, color, size, cornerStyle, strokeColor, strokeWidth, iconUrl, icon);
2187
+ }
2188
+
2189
+ /** Bounding box a node occupies — delegates to NodeGeometry.nodeSize. */
2190
+ _nodeSize(node, R, sizeDomain, sizeOptions) {
2191
+ return NodeGeometry.nodeSize(node, R, sizeDomain, sizeOptions);
2192
+ }
2193
+
2194
+ /** Pure. What a node's own size VALUE is, before it's mapped to a radius —
2195
+ * delegates to GraphMetrics.nodeSizeValue. */
2196
+ _nodeSizeValue(node, sizeBy, degreeMap) {
2197
+ return GraphMetrics.nodeSizeValue(node, sizeBy, degreeMap);
2198
+ }
2199
+
2200
+ /** Pure — no DOM/renderer access. {min,max} of every node's size VALUE —
2201
+ * delegates to GraphMetrics.computeSizeDomain. */
2202
+ _computeSizeDomain(nodes, { sizeBy, degreeMap } = {}) {
2203
+ return GraphMetrics.computeSizeDomain(nodes, { sizeBy, degreeMap });
2204
+ }
2205
+
2206
+ /** Pure. Maps a node's size value into [minRadius,maxRadius] — delegates to
2207
+ * GraphMetrics.effectiveNodeRadius. */
2208
+ _effectiveNodeRadius(node, R, sizeDomain, sizeOptions = {}) {
2209
+ return GraphMetrics.effectiveNodeRadius(node, R, sizeDomain, sizeOptions);
2210
+ }
2211
+
2212
+ /** Pure. Corner point of a box — delegates to NodeGeometry.cornerPoint. */
2213
+ _cornerPoint(pos, size, corner) {
2214
+ return NodeGeometry.cornerPoint(pos, size, corner);
2215
+ }
2216
+
2217
+ /** Pure. Generic 3-tier corner resolution — delegates to NodeGeometry. */
2218
+ _resolveNodeCorner(node, nodeKey, byShape, defaultCorner) {
2219
+ return NodeGeometry.resolveNodeCorner(node, nodeKey, byShape, defaultCorner);
2220
+ }
2221
+
2222
+ /** Pure. Resize-handle corner — delegates to NodeGeometry.nodeResizeCorner. */
2223
+ _nodeResizeCorner(node, resizeCornerByShape, defaultCorner = 'top-right') {
2224
+ return NodeGeometry.nodeResizeCorner(node, resizeCornerByShape, defaultCorner);
2225
+ }
2226
+
2227
+ /** Pure. Icon-badge corner — delegates to NodeGeometry.nodeIconCorner. */
2228
+ _nodeIconCorner(node, iconCornerByShape, defaultCorner = 'top-right') {
2229
+ return NodeGeometry.nodeIconCorner(node, iconCornerByShape, defaultCorner);
2230
+ }
2231
+
2232
+ /** Pure. Resize-handle drag result — delegates to NodeGeometry.resizedNodeValue. */
2233
+ _resizedNodeValue(node, corner, center, mousePos) {
2234
+ return NodeGeometry.resizedNodeValue(node, corner, center, mousePos);
2235
+ }
2236
+
2237
+ /** Pure — no DOM/renderer access. Map<nodeId, connectionCount> — delegates
2238
+ * to GraphMetrics.computeNodeDegrees. */
2239
+ _computeNodeDegrees(nodes, edges) {
2240
+ return GraphMetrics.computeNodeDegrees(nodes, edges);
2241
+ }
2242
+
2243
+ /** Pure. {min,max} of edge.weight across all edges — delegates to
2244
+ * GraphMetrics.computeEdgeWidthDomain. */
2245
+ _computeEdgeWidthDomain(edges) {
2246
+ return GraphMetrics.computeEdgeWidthDomain(edges);
2247
+ }
2248
+
2249
+ /** Pure. Maps edge.weight into [minEdgeWidth,maxEdgeWidth] — delegates to
2250
+ * GraphMetrics.effectiveEdgeWidth. */
2251
+ _effectiveEdgeWidth(edge, defaultWidth, widthDomain, widthOptions = {}) {
2252
+ return GraphMetrics.effectiveEdgeWidth(edge, defaultWidth, widthDomain, widthOptions);
2253
+ }
2254
+
2255
+ /** Pure — no DOM/renderer access. Table-node size — delegates to NodeGeometry. */
2256
+ _tableNodeSize(node) {
2257
+ return NodeGeometry.tableNodeSize(node);
2258
+ }
2259
+
2260
+ /** World-space Y of a named column's row within a table node — delegates to
2261
+ * NodeGeometry.tableColumnY. */
2262
+ _tableColumnY(node, posEntry, size, colName) {
2263
+ return NodeGeometry.tableColumnY(node, posEntry, size, colName);
2264
+ }
2265
+
2266
+ // Edge geometry helpers — delegated to EdgeGeometry.
2267
+ _edgeAnchor(pos, size, side, ux, uy, rectBoundary) {
2268
+ return EdgeGeometry.edgeAnchor(pos, size, side, ux, uy, rectBoundary);
2269
+ }
2270
+ _edgeSideOrientation(side) {
2271
+ return EdgeGeometry.edgeSideOrientation(side);
2272
+ }
2273
+ _curvedEdgePath(a1, a2) {
2274
+ return EdgeGeometry.curvedEdgePath(a1, a2);
2275
+ }
2276
+ _orthogonalEdgePath(a1, a2, sourceSide, targetSide) {
2277
+ return EdgeGeometry.orthogonalEdgePath(a1, a2, sourceSide, targetSide);
2278
+ }
2279
+ _segmentIntersectsCircle(a1, a2, circle) {
2280
+ return EdgeGeometry.segmentIntersectsCircle(a1, a2, circle);
2281
+ }
2282
+ _polylineEdgePath(a1, a2, obstacles = []) {
2283
+ return EdgeGeometry.polylineEdgePath(a1, a2, obstacles);
2284
+ }
2285
+ _nearestSegmentInsertIndex(points, x, y) {
2286
+ return EdgeGeometry.nearestSegmentInsertIndex(points, x, y);
2287
+ }
2288
+ _edgeWaypointsWithInsertion(edge, sourcePos, targetPos, point) {
2289
+ return EdgeGeometry.edgeWaypointsWithInsertion(edge, sourcePos, targetPos, point);
2290
+ }
2291
+ _pointToSegmentDist2(a, b, x, y) {
2292
+ return EdgeGeometry.pointToSegmentDist2(a, b, x, y);
2293
+ }
2294
+ _computeEdgePath(sourceInfo, sourcePos, sourceSize, targetInfo, targetPos, targetSize, edge, defaultRouting = 'straight', obstacles = []) {
2295
+ return EdgeGeometry.computeEdgePath(sourceInfo, sourcePos, sourceSize, targetInfo, targetPos, targetSize, edge, defaultRouting, obstacles);
2296
+ }
2297
+
2298
+ /** Draws a table-card node's header + one row per column into wrapper `g`. */
2299
+ _buildTableNodeContent(g, node) {
2300
+ return NodeGeometry.buildTableNodeContent(this.renderer, g, node);
2301
+ }
2302
+
2303
+ /**
2304
+ * Re-render the current graph diagram, if one is active. Exposed as a
2305
+ * convenience so consumer callbacks and demo dialogs can trigger a redraw
2306
+ * without reaching into `engine._graphDiagram` directly.
2307
+ *
2308
+ * Consumer code (e.g. test/charts.html's add-note/add-image dialogs) updates
2309
+ * `engine._chartState.nodes/edges/options` and then calls this method. The
2310
+ * active GraphDiagram instance keeps its own `_chartState`, so we push the
2311
+ * engine state into the diagram before re-rendering and pull the resulting
2312
+ * state back out, keeping the two references in sync.
2313
+ */
2314
+ _rerenderGraph() {
2315
+ if (!this._graphDiagram) return;
2316
+ this._graphDiagram._chartState = this._chartState;
2317
+ this._graphDiagram._rerenderGraph();
2318
+ this._chartState = this._graphDiagram._chartState;
2319
+ }
2320
+
2321
+ /**
2322
+ * Generate a unique node id from a label, using the current graph diagram's
2323
+ * node list (or the static helper if no diagram is active yet). Mirrors
2324
+ * GraphDiagram._generateNodeId so add-note / add-image dialogs don't need
2325
+ * to reach into `engine._graphDiagram`.
2326
+ */
2327
+ _generateNodeId(label) {
2328
+ if (this._graphDiagram) return this._graphDiagram._generateNodeId(label);
2329
+ return GraphDiagram.generateNodeId(label, (this._chartState?.nodes ?? []).map(n => n.id));
2330
+ }
2331
+
2332
+ /**
2333
+ * Renders a railroad diagram.
2334
+ * @param {Diagram} diagram A diagram object from railroadDiagram.js
2335
+ */
2336
+ railroadDiagram(diagram) {
2337
+ if (this.renderer) {
2338
+ this.renderer.clear();
2339
+ this.renderer.renderDiagram(diagram);
2340
+ }
2341
+ }
2342
+
2343
+ /**
2344
+ * Renders an EBNF grammar rule as a railroad (syntax) diagram. Accepts
2345
+ * either form:
2346
+ * - a raw EBNF string (e.g. `"DOUBLE ::= [0-9]+ ('.' [0-9]*)?"`) —
2347
+ * parsed via `parseEbnf` (ebnfParser.js); the verbatim input text is
2348
+ * also drawn in a caption band under the diagram.
2349
+ * - an already-parsed AST node — the bnfengine-shaped tree
2350
+ * (`{type:'Terminal'|'NonTerminal'|'Sequence'|'Choice'|'Optional'|
2351
+ * 'Repetition'|'Group', ...}` — see ebnfDiagram.js's own header)
2352
+ * converted via `ebnfAstToDiagram` before delegating to
2353
+ * `railroadDiagram()`. No caption is drawn for this form — there's no
2354
+ * source text to show.
2355
+ * @param {string|object} rule EBNF text, or a bnfengine-shaped AST node.
2356
+ * @param {object} [options]
2357
+ * @param {string} [options.captionBackground='#fdf6e3'] Caption band fill (text form only).
2358
+ */
2359
+ ebnfDiagram(rule, options = {}) {
2360
+ if (!this.renderer) return;
2361
+ this._renderDiagram(new EbnfDiagram(this.renderer, rule, options));
2362
+ }
2363
+
2364
+ /**
2365
+ * Performs a hit test at the given coordinates.
2366
+ * @param {number} x The x-coordinate.
2367
+ * @param {number} y The y-coordinate.
2368
+ * @returns {object | null} The shape object that was hit, or null.
2369
+ */
2370
+ hitTest(x, y) {
2371
+ const direct = this.renderer?.hitTest ? this.renderer.hitTest(x, y) : null;
2372
+ if (direct) return direct;
2373
+ // Pixel-exact SVG hitTest misses thin or angled edges. Fall back to a
2374
+ // distance-based search over the active graph diagram's edge paths.
2375
+ const svgPoint = this._clientToSvgPoint?.(x, y);
2376
+ if (!svgPoint || !this._graphDiagram) return null;
2377
+ const tolerance = this._chartState?.options?.edgeHitTolerance ?? 14;
2378
+ const edge = this._graphDiagram.findNearestEdge(svgPoint.x, svgPoint.y, tolerance);
2379
+ return edge ? { isEdge: true, edge } : null;
2380
+ }
2381
+
2382
+ /**
2383
+ * Live-filter the current graph diagram by keyword. Nodes whose label or
2384
+ * extra data does not match are hidden; edges touching hidden nodes are
2385
+ * hidden too. In hierarchical mode ancestors of a match stay visible.
2386
+ * Pass an empty string to clear the filter.
2387
+ * @param {string} keyword
2388
+ */
2389
+ filterGraphNodes(keyword) {
2390
+ this._graphDiagram?.filterGraphNodes?.(keyword);
2391
+ }
2392
+
2393
+ /** Screen (clientX/clientY) → SVG user-space coordinates, honoring the
2394
+ * current pan/zoom viewBox (_addZoomListeners). Shared by drag,
2395
+ * add-node, and edge-creation — all of which need a graph-space point,
2396
+ * not a raw screen pixel. */
2397
+ _clientToSvgPoint(clientX, clientY) {
2398
+ return SvgCoordinate.clientToSvgPoint(this.renderer?.svg, clientX, clientY);
2399
+ }
2400
+
2401
+ /**
2402
+ * Applies a named state to a shape.
2403
+ * @param {object} shape The shape object to apply the state to.
2404
+ * @param {string} stateName The name of the state (e.g., 'hover').
2405
+ */
2406
+ enterState(shape, stateName) {
2407
+ if (this.renderer?.enterState) {
2408
+ this.renderer.enterState(shape, stateName);
2409
+ }
2410
+ }
2411
+
2412
+ /**
2413
+ * Removes a named state from a shape.
2414
+ * @param {object} shape The shape object to remove the state from.
2415
+ * @param {string} stateName The name of the state (e.g., 'hover').
2416
+ */
2417
+ leaveState(shape, stateName) {
2418
+ if (this.renderer?.leaveState) {
2419
+ this.renderer.leaveState(shape, stateName);
2420
+ }
2421
+ }
2422
+
2423
+ // ──────────────────────────────────────────────────────────────────────────────
2424
+ // SVGEngine Compatibility Methods & Utilities
2425
+ // ──────────────────────────────────────────────────────────────────────────────
2426
+
2427
+ /**
2428
+ * Resolves a dot-notation path in the global data object.
2429
+ * Compatible with SVGEngine's _resolvePath pattern.
2430
+ * @param {string} path Dot-notation path (e.g., 'user.name', 'data.items.0.value')
2431
+ * @param {Function} [mapper] Optional function to transform the resolved value
2432
+ * @returns {*} The resolved value or undefined
2433
+ */
2434
+ resolvePath(path, mapper, globalData = this.globalData) {
2435
+ if (!path) return undefined;
2436
+ let val = path.split('.').reduce((p, c) => {
2437
+ if (p && typeof p === 'object' && c in p) {
2438
+ return p[c];
2439
+ } else if (Array.isArray(p) && !isNaN(c)) {
2440
+ return p[parseInt(c, 10)];
2441
+ }
2442
+ return undefined;
2443
+ }, globalData);
2444
+ return (mapper && val !== undefined) ? mapper(val, globalData) : val;
2445
+ }
2446
+
2447
+ /**
2448
+ * Sets global data that can be referenced in chart options via paths.
2449
+ * @param {object} data The global data object
2450
+ */
2451
+ setGlobalData(data) {
2452
+ this.globalData = data;
2453
+ }
2454
+
2455
+ /**
2456
+ * Generates nice round tick values for axis labels.
2457
+ * Ensures readable, evenly-spaced values on axes.
2458
+ * @param {number} maxVal The maximum value
2459
+ * @param {number} [count=4] Desired number of ticks
2460
+ * @returns {number[]} Array of tick values
2461
+ */
2462
+ niceTicks(maxVal, count = 4) {
2463
+ return Axis.niceTicks(maxVal, count);
2464
+ }
2465
+
2466
+ /**
2467
+ * Formats a value for display with appropriate units (M, k, etc.).
2468
+ * @param {number} v The value to format
2469
+ * @returns {string} Formatted value string
2470
+ */
2471
+ formatValue(v) {
2472
+ return Axis.formatValue(v);
2473
+ }
2474
+
2475
+ /**
2476
+ * Draws numeric y-axis tick marks + value labels (via niceTicks/
2477
+ * formatValue) along the left edge of a cartesian plot area, plus a
2478
+ * light horizontal gridline per tick. Shared by every chart method with
2479
+ * a numeric, zero-based y-axis — call it once, before drawing the
2480
+ * chart's own marks, so gridlines sit behind them.
2481
+ *
2482
+ * niceTicks rounds its top tick UP past the raw data max (e.g. a max
2483
+ * value of 82 becomes a top tick of 100) — so this returns that
2484
+ * "nice" max rather than the raw one passed in, and the caller MUST
2485
+ * scale its own marks (bar heights, line points, ...) against the
2486
+ * returned value, not the raw max, or the tallest mark won't reach the
2487
+ * axis's own top tick.
2488
+ * @param {number} x Left edge of the plot area (data-space origin).
2489
+ * @param {number} y Top edge of the plot area.
2490
+ * @param {number} width Plot area width.
2491
+ * @param {number} height Plot area height.
2492
+ * @param {number} rawMax The raw (un-rounded) data maximum.
2493
+ * @returns {number} The "nice" domain max the drawn ticks actually span.
2494
+ */
2495
+ _drawYAxisTicks(x, y, width, height, rawMax) {
2496
+ return new Axis().drawYAxis(this.renderer, { x, y, width, height }, rawMax);
2497
+ }
2498
+
2499
+ /**
2500
+ * Binds a tooltip to an element (for Canvas context).
2501
+ * Can be extended for use with interactive features.
2502
+ * @param {object} element The element or shape to bind tooltip to
2503
+ * @param {string} text The tooltip text
2504
+ */
2505
+ bindTooltip(element, text) {
2506
+ if (!text || !element) return;
2507
+ if (element.tooltip) {
2508
+ element.tooltip = text;
2509
+ } else if (element.setAttribute && typeof element.setAttribute === 'function') {
2510
+ element.setAttribute('title', text);
2511
+ }
2512
+ }
2513
+
2514
+ /**
2515
+ * Binds a rich tooltip with multiple rows to an element.
2516
+ * @param {object} element The element to bind tooltip to
2517
+ * @param {object} data Object with { title, rows: [{k, v}] }
2518
+ */
2519
+ bindRichTooltip(element, data) {
2520
+ if (!data || !element) return;
2521
+ const { title, rows = [] } = data;
2522
+ const text = `${title}\n${rows.map(r => `${r.k}: ${r.v}`).join('\n')}`;
2523
+ this.bindTooltip(element, text);
2524
+ }
2525
+
2526
+ /**
2527
+ * Lightens or darkens a color by a factor for shading effects.
2528
+ * Used internally for 3D effects.
2529
+ * @param {string} color Hex color string
2530
+ * @param {number} amount Amount to lighten (-1 to 1)
2531
+ * @returns {string} Modified hex color
2532
+ */
2533
+ adjustColor(color, amount) {
2534
+ return this._lightenColor(color, amount);
2535
+ }
2536
+
2537
+ /**
2538
+ * Registers a chart or diagram type for lazy rendering.
2539
+ * Compatible with SVGEngine's registry pattern. For a graph type
2540
+ * (`graphDiagram`/`graphDiagram3D`), pass `{nodes, edges, ...}` in
2541
+ * `options` instead of `{data}` — `refreshChart`/`addGraphNode` detect
2542
+ * the graph shape via `options.nodes`.
2543
+ * @param {string} id Chart container ID
2544
+ * @param {string} type A CHART_CATALOG key (e.g. 'bar', 'line'), a
2545
+ * DIAGRAM_CATALOG key (e.g. 'graph', 'archimate'), or — for
2546
+ * railroadDiagram, which isn't a registry entry — the real method name.
2547
+ * @param {object} [options] Chart/diagram options
2548
+ */
2549
+ register(id, type, options = {}) {
2550
+ if (!this.registry) this.registry = {};
2551
+ this.registry[id] = { type, options };
2552
+ }
2553
+
2554
+ /**
2555
+ * Refreshes a registered chart, re-resolving its data and re-invoking
2556
+ * its render method.
2557
+ * @param {string} id Chart container ID
2558
+ */
2559
+ refreshChart(id) {
2560
+ if (!this.registry || !this.registry[id]) return;
2561
+ const config = this.registry[id];
2562
+ const container = document.getElementById(id);
2563
+ if (!container) return;
2564
+
2565
+ // Catalog keys ('bar', 'heatmap', 'graph', 'archimate', ...) resolve
2566
+ // through the registries to their real method name; railroadDiagram
2567
+ // isn't a registry entry, so `type` already IS the method name there.
2568
+ const chartEntry = RenderEngine.CHART_REGISTRY.get(config.type);
2569
+ const diagramEntry = RenderEngine.DIAGRAM_REGISTRY.get(config.type);
2570
+ const methodName = chartEntry?.method ?? diagramEntry?.method ?? config.type;
2571
+ if (typeof this[methodName] !== 'function') return;
2572
+
2573
+ if (config.options.nodes !== undefined) {
2574
+ // Graph-shaped registration: two positional data args, not one.
2575
+ this[methodName](config.options.nodes, config.options.edges || [], config.options);
2576
+ } else {
2577
+ const data = config.options.data || this.resolvePath(config.options.dataPath, config.options.dataMapper);
2578
+ this[methodName](data, config.options);
2579
+ }
2580
+ }
2581
+
2582
+ /**
2583
+ * Appends one data point to a registered chart's data and re-renders it,
2584
+ * keeping at most `maxPoints` (oldest dropped first) — see
2585
+ * REFERENCE.md's "Dynamically adding data points in a bounded window".
2586
+ * Delegates the actual shape-aware append/trim to a `datawindow/`
2587
+ * class, selected via the chart's `CHART_CATALOG` `streamShape`; the
2588
+ * window instance is cached per `id` so repeated calls keep growing the
2589
+ * same bounded collection instead of re-deriving it from scratch.
2590
+ * @param {string} id A container id already passed to `register()`.
2591
+ * @param {*|function} pointOrFactory A literal point, or a factory (see
2592
+ * the `datawindow/` classes for the exact signature each streamShape
2593
+ * calls it with).
2594
+ * @param {{maxPoints?: number}} [options] `0` (default) = unbounded.
2595
+ */
2596
+ addDataPoint(id, pointOrFactory, { maxPoints = 0 } = {}) {
2597
+ const config = this.registry?.[id];
2598
+ const entry = config && RenderEngine.CHART_REGISTRY.get(config.type);
2599
+ if (!entry || entry.streamShape === 'none') return; // not registered, or a fixed-shape type (heatmap/radar/gauges/3D-orbit grids)
2600
+
2601
+ this._windows = this._windows || {};
2602
+ if (!this._windows[id]) {
2603
+ const current = config.options.data || this.resolvePath(config.options.dataPath, config.options.dataMapper);
2604
+ this._windows[id] = createDataWindow(entry.streamShape, current, { max: maxPoints });
2605
+ }
2606
+ this._windows[id].max = maxPoints; // let the cap change between calls, e.g. a live-editable "Max points" field
2607
+ config.options.data = this._windows[id].push(pointOrFactory);
2608
+ this.refreshChart(id);
2609
+ }
2610
+
2611
+ /**
2612
+ * Appends one node (and its edges) to a registered graph diagram and
2613
+ * re-renders it, keeping at most `maxNodes` — oldest nodes dropped
2614
+ * first, along with any edge left dangling off a removed node. See
2615
+ * `GraphDataWindow` (src/datawindow/GraphDataWindow.js).
2616
+ * @param {string} id A container id already `register()`ed with
2617
+ * `{nodes, edges}` in its options.
2618
+ * @param {object} node The new node, e.g. `{id, label}`.
2619
+ * @param {Array<object>} [newEdges] Edges connecting the new node, e.g. `[{source, target}]`.
2620
+ * @param {{maxNodes?: number}} [options] `0` (default) = unbounded.
2621
+ */
2622
+ addGraphNode(id, node, newEdges = [], { maxNodes = 0 } = {}) {
2623
+ const config = this.registry?.[id];
2624
+ if (!config || config.options.nodes === undefined) return; // not registered, or not registered graph-shaped
2625
+
2626
+ this._windows = this._windows || {};
2627
+ if (!this._windows[id]) {
2628
+ this._windows[id] = new GraphDataWindow({ nodes: config.options.nodes, edges: config.options.edges || [] }, { max: maxNodes });
2629
+ }
2630
+ this._windows[id].max = maxNodes;
2631
+ this._windows[id].push(node, newEdges);
2632
+ config.options.nodes = this._windows[id].nodes;
2633
+ config.options.edges = this._windows[id].edges;
2634
+ this.refreshChart(id);
2635
+ }
2636
+ }