@peter.prib/renderengine 0.0.0-stage → 0.1.0

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