@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/REFERENCE.md ADDED
@@ -0,0 +1,2159 @@
1
+ # RenderEngine — Reference
2
+
3
+ One document for "what can I call and what does it take" — every chart/diagram entry point,
4
+ every shared option, every utility, and (the thing none of the older docs cover) the pattern for
5
+ feeding a chart a live, growing-then-capped data source.
6
+
7
+ This supersedes `README_COMPLETE.md`, `FEATURES_ADDED.md`, `FRAMEWORK_ALIGNMENT.md`,
8
+ `COMPATIBILITY_MATRIX.md`, and `DELIVERY_SUMMARY.md` as the day-to-day API reference — those five
9
+ predate most of the current chart-type set (some still say "8 chart types") and are kept only as
10
+ historical record; see `DOCUMENTATION_INDEX.md`. For *why* things are built the way they are —
11
+ the chart-type catalog pattern, the 3D camera split, per-chart design rationale — see
12
+ `ARCHITECTURE.md` and `CHART_TYPES_ROADMAP.md`. This doc is the *what/how*, not the *why*.
13
+
14
+ ## Setup
15
+
16
+ ```javascript
17
+ import { RenderEngine } from 'renderengine'; // or dist/renderengine.umd.js in a browser
18
+
19
+ const engine = new RenderEngine(document.getElementById('chart'), { rendererType: 'canvas' }); // or 'svg'
20
+ const headless = new RenderEngine(null); // test mode — every chart call no-ops safely
21
+ ```
22
+
23
+ - **Canvas** — higher raw point-count ceiling, no native DOM hit-testing (`hitTest()` does the
24
+ work manually).
25
+ - **SVG** — native tooltips/hit-testing, crisp at any zoom, automatic wheel-zoom and
26
+ drag-empty-space-to-pan on every SVG-rendered chart (`resetView()` clears it back to identity;
27
+ a 3D-orbit chart's own drag-to-rotate/wheel-to-zoom takes over instead — see below).
28
+ - Both renderers sit behind one primitive API (`rect`/`circle`/`path`/`text`/`line`/…) — every
29
+ `Chart` subclass is renderer-agnostic, so any `xChart()` call below works unchanged under either.
30
+
31
+ ## The rendering model (read this before the streaming section)
32
+
33
+ Every chart call — `barChart`, `lineChart`, all of them — starts with `this.renderer.clear()`
34
+ (via the `Chart.render()` base method) and redraws the entire chart from the `data` array you pass
35
+ in. There is no incremental/diff update, no "append one point" method, no built-in windowing.
36
+ Calling the same method again on the same container **replaces** what was there. This is deliberate
37
+ (it's what keeps every one of the 28 chart types simple and renderer-agnostic) but it means
38
+ "streaming"/"bounded window" behavior is entirely a caller-side pattern: keep your own array as the
39
+ source of truth, mutate *that*, then re-call the chart method. See "Dynamically adding data points
40
+ in a bounded window" below for the concrete pattern, straight from a real working implementation
41
+ in this repo.
42
+
43
+ ## Chart types
44
+
45
+ `src/chartCatalog.js` exports `CHART_CATALOG` (also reachable as `RenderEngine.CHART_CATALOG`) —
46
+ the single source of truth for this table; build a type-picker from it rather than hand-copying
47
+ this list (see `ARCHITECTURE.md`). `engine.renderChartType(key, data, options)` dispatches by
48
+ catalog `key` for you.
49
+
50
+ | Key | Method | Data shape | Notable options |
51
+ |---|---|---|---|
52
+ | `bar` | `barChart(data, opts)` | `Array<{label, value}>` | `colors`, `showLegend`, `barColor` — one of the types with numeric y-axis ticks/gridlines, see note below |
53
+ | `groupedBar` | `groupedBarChart(data, opts)` | multi-series `Array<{name, values:Array<{label,value}>}>` | `colors`, `legend`, `groupPadding`, `barPadding` |
54
+ | `line` | `lineChart(data, opts)` | `Array<{label, value, error?, errorPlus?, errorMinus?}>` or multi-series `Array<{name, values:[{label,value,error?,...}]}>` | `colors`, `legend`, `zAxis` (isometric depth per series); per-point `error` / `errorPlus` / `errorMinus` draw vertical error bars |
55
+ | `pie` | `pieChart(data, opts)` | `Array<{label, value}>` | `innerRadiusRatio` (>0 = doughnut), `colors`, `legend` |
56
+ | `donut` | `donutChart(data, opts)` | `Array<{label, value}>` | same as `pie` with `innerRadiusRatio` defaulting to 0.5 |
57
+ | `area` | `areaChart(data, opts)` | same as `line` | `stacked`, `colors`, `legend` |
58
+ | `stackedArea` | `stackedAreaChart(data, opts)` | multi-series `Array<{name, values:Array<{label,value}>}>` | same as `area` with `stacked:true` forced |
59
+ | `scatter` | `scatterChart(data, opts)` | `Array<{label, x, y, errorX?, errorY?, errorXMinus?, errorXPlus?, errorYMinus?, errorYPlus?}>` | `colorCol` (color-group by field, or a compiled formula — see below), `referenceLine`; symmetric or asymmetric x/y error bars |
60
+ | `bubble` | `bubbleChart(data, opts)` | `Array<{label, x, y, z}>` | `z` drives size; `colorCol`; `zAxis` (isometric depth) |
61
+ | `heatmap` | `heatmap(data, opts)` | `Array<{x, y, value}>` | `colorScale` (low→high stops), `xLabels`/`yLabels` |
62
+ | `calendarHeatmap` | `calendarHeatmap(data, opts)` | `Array<{date: string\|Date, value: number}>` | `colorScale`, `emptyColor`; one row per calendar day |
63
+ | `gantt` | `ganttChart(data, opts)` | `Array<{label, start, end, progress?, category?, color?}>` | `start`/`end` accept Date/ISO/number; `progress` 0–100 |
64
+ | `streamGraph` | `streamGraph(data, opts)` | multi-series `Array<{name, values:Array<{label,value}>}>` | silhouette baseline; `colors`, `legend` |
65
+ | `radialBar` | `radialBarChart(data, opts)` | `Array<{label, value}>` | angular categories with radial bar length; `colors`, `legend` |
66
+ | `populationPyramid` | `populationPyramid(data, opts)` | `Array<{label, left, right}>` | back-to-back horizontal bars; `leftColor`, `rightColor` |
67
+ | `sankey` | `sankeyDiagram(data, opts)` | `{nodes?: Array<{id,label?,color?}>, links: Array<{source,target,value}>}` | proportional-width flow links; `nodeWidth`, `linkOpacity` |
68
+ | `chord` | `chordDiagram(data, opts)` | same as `sankey` | nodes on a circle with curved chords of proportional thickness |
69
+ | `candlestick` | `candlestick(data, opts)` | `Array<{label, open, high, low, close}>` | `upColor`, `downColor`, `markers` (tooltip annotations) |
70
+ | `stackedBar` | `stackedBarChart(data, opts)` | `Array<{label, values:number[]}>` | `colors` (no built-in legend — a known gap) |
71
+ | `pipe` | `pipeChart(data, opts)` | `Array<{label, value, metric?}>` or multi-series | `metric` tapers line width; `legend` |
72
+ | `radar` | `radarChart(data, opts)` | `Array<{axis, value}>` or multi-series | `colors`, `legend` |
73
+ | `funnel` | `funnelChart(data, opts)` | `Array<{label, value}>` (largest first) | `colors` |
74
+ | `waterfall` | `waterfallChart(data, opts)` | `Array<{label, value, type?:'delta'|'total'}>` | `upColor`, `downColor`, `totalColor` |
75
+ | `histogram` | `histogram(values, opts)` | `number[]` (raw sample, binned internally) | bin count derived, not caller-set |
76
+ | `boxPlot` | `boxPlot(data, opts)` | `Array<{label, values:number[]}>` (raw per-group sample) | quartiles/whiskers computed internally |
77
+ | `density` | `densityChart(data, opts)` | same as `boxPlot` | Gaussian KDE overlay per group; `legend` |
78
+ | `violin` | `violinChart(data, opts)` | same as `boxPlot` | mirrored KDE silhouette per group |
79
+ | `qq` | `qqPlot(values, opts)` | `number[]` | delegates to `scatterChart`; fitted reference line |
80
+ | `lag` | `lagPlot(values, opts)` | `number[]` | `lag` (default 1); delegates to `scatterChart` |
81
+ | `treemap` | `treemapChart(data, opts)` | `Array<{label, value?, children?}>` (hierarchical) | squarified layout, palette by top-level branch |
82
+ | `sunburst` | `sunburstChart(data, opts)` | same hierarchical shape as `treemap` | nested rings by depth |
83
+ | `outline` | `outlineChart(data, opts)` | same hierarchical shape as `treemap`/`sunburst` | `onNodeClick`; collapsible indented list |
84
+ | `gauge-dial` | `dialGauge(value, opts)` | `number` (0–100) | `color`, `title` |
85
+ | `gauge-bar` | `barGauge(value, opts)` | `number` (0–100) | `color`, `title` |
86
+ | `bullet` | `bulletGauge(data, opts)` | `number` or `{value, target?, ranges?:Array<{max,label?,color?}>}` | `target`, `ranges`, `barColor`, `targetColor`, `min`, `max`, `title` |
87
+ | `bar3D` | `barChart3D(data, opts)` | `Array<{label, value, depth?}>` | `depthScale`; fixed-angle isometric, not a real camera |
88
+ | `bubble3D` | `bubbleChart3D(data, opts)` | `Array<{label, x, y, z, size?, category?}>` | real orbit camera — drag to rotate, wheel to zoom |
89
+ | `bar3DOrbit` | `barChart3DOrbit(data, opts)` | `Array<{x, z, value}>` (grid) | real orbit camera |
90
+ | `stackedBar3DOrbit` | `stackedBarChart3DOrbit(data, opts)` | `Array<{x, z, values:number[]}>` (grid) | real orbit camera |
91
+ | `wordCloud` | `wordCloud(data, opts)` | `Array<{text: string, value: number}>` | spiral placement + bounding-box collision; `minFontSize`, `maxFontSize`, `padding` |
92
+ | `parallelCoordinates` | `parallelCoordinates(data, opts)` | `Array<{label?, category?, [dim: number]}>` | one vertical axis per numeric dimension; category-driven colors; `dimensions` |
93
+ | `geoMap` | `geoMap(data, opts)` | `{features: Array<{id, name, value?, color?, geometry}>, bubbles?: Array<{lat, lon, value}>}` | equirectangular projection; `center`, `scale`, `colorScale`; see note below |
94
+
95
+ **`geoMap` note:** the `test/charts.html` demo uses deliberately rough, hand-drawn continental
96
+ polygons so the page needs no external map file. Real use should pass proper GeoJSON-like
97
+ `Polygon`/`MultiPolygon` features; the engine itself has no bundled world geometry and makes no
98
+ external network requests.
99
+
100
+ **Not in `CHART_CATALOG`** (diagram renderers invoked directly, not "chart a tabular result"
101
+ types — see `ARCHITECTURE.md`):
102
+
103
+ - `graphDiagram(nodes, edges, opts)` — force-directed node/edge diagram. `nodes:[{id,label,group?,shape?,size?}]`,
104
+ `edges:[{source,target,label?,weight?}]`. Key options: `onNodeClick`, `onEdgeClick`, `editable` (drag-to-connect,
105
+ right-click rename/delete/add), `contextMenuItems`/`edgeContextMenuItems`/`canvasContextMenuItems`
106
+ (replace the built-in right-click menus; support `{label, items:[...]}` submenus), `colors`, `nodeRadius`.
107
+ Dewey-style hierarchical `id`s (`"1.2.3"`) auto-infer parent/child edges in place of `edges`.
108
+ **Sizing:** a node's `size` (or, with `sizeBy: 'degree'`, its connection count — no `size` field needed)
109
+ scales its radius between `minRadius`/`maxRadius` (`sizeScale: 'linear'|'sqrt'`); an edge's `weight`
110
+ scales its stroke width between `minEdgeWidth`/`maxEdgeWidth` (`edgeWidthScale`) the same way. Either
111
+ is off by default — nothing changes size until some node/edge actually carries a value (or `sizeBy` is set).
112
+ **Edge routing:** `edgeRouting: 'straight'|'curved'|'orthogonal'|'polyline'` sets the diagram-wide default
113
+ (`'straight'` unless set); any edge can override it with its own `routing`. `'curved'` is a bezier that
114
+ leaves/arrives perpendicular to each node; `'orthogonal'` is a right-angle Manhattan connector;
115
+ `'polyline'` is a straight line unless another node sits in the way, in which case it steps around it with
116
+ one waypoint. **Ports:** an edge's `sourceSide`/`targetSide` (`'top'|'right'|'bottom'|'left'`, default
117
+ `'auto'`) pin where on the node it attaches — that point stays fixed to the node (moves with it) instead of
118
+ always sliding around to face whatever it's connected to, matching how `fromCol`/`toCol` table links already
119
+ anchor to a fixed side. Table-to-table FK edges (`fromCol`/`toCol` set, both ends `shape:'table'`) keep
120
+ their own fixed-column bezier regardless of `routing`/side options. The force-directed *node* layout
121
+ (springs pulling connected nodes together, mutual repulsion apart) is what `graphDiagram` already runs by
122
+ default — `edgeRouting` only changes how a connector is *drawn* between wherever the two nodes end up.
123
+ **Edge selection tolerance:** `edgeHitTolerance` (default 8 SVG units) widens the hit area around edge
124
+ lines for `hitTest()`, so thin or angled connections are easier to select, hover, or double-click for
125
+ `onEdgeDoubleClick`. The renderer's own pixel-exact hit is tried first; if it misses, the closest edge
126
+ within `edgeHitTolerance` is returned.
127
+ **Manual routing:** with `editable: true`, right-click an edge and choose "Add Waypoint Here" to drop a
128
+ bend point where you clicked (`_insertEdgeWaypoint`/its pure half `_edgeWaypointsWithInsertion`), drag a
129
+ bend point to move it, double-click a bend point to remove it — this builds `edge.waypoints` (`[{x,y}, ...]`),
130
+ which beats every `routing` style outright and threads the line straight through each point in order. Set
131
+ `edge.waypoints` yourself to hand in a route programmatically, or read it back via
132
+ `onEdgeWaypointsChange(edge, waypoints)`. (Adding a waypoint used to be double-click-the-line itself — moved
133
+ to the context menu once double-click was repurposed for `onEdgeDoubleClick`, see below.) The same edge
134
+ context menu also offers **Invert Direction**, which swaps `source`/`target` in place and fires
135
+ `onEdgeInvert(edge, oldSource, oldTarget)`.
136
+ **Undo/redo:** with `editable: true`, every model edit (move, resize, rename, add/delete node,
137
+ add/delete edge, invert edge, waypoint edits) is recorded in a `CommandHistory` bound to the diagram.
138
+ Press `Ctrl+Z` / `Ctrl+Y` (or `Ctrl+Shift+Z` for redo) to step through the stack. `historySize` sets
139
+ the maximum number of retained edits (default `200`), and `onHistoryChange(canUndo, canRedo)` fires
140
+ whenever the stack changes so a UI can enable/disable its own undo/redo buttons.
141
+ A host that re-renders the diagram passes its own `history` (a `CommandHistory`): edits are recorded
142
+ there, so undo outlives each render, and the host binds the undo keys itself (the diagram's own
143
+ `Ctrl+Z` handler stands down; `historySize`/`onHistoryChange` then belong to the host's history).
144
+ Recorded commands act on whichever diagram is on screen when they are undone, finding nodes and
145
+ edges by `id`. Every apply, undo and redo included, reports the result, so a host's copy stays in
146
+ step:
147
+
148
+ | Edit | Hook |
149
+ |---|---|
150
+ | move a node | `onNodeMove(nodeId, {x, y})` |
151
+ | resize a node | `onNodeResize(node)` (its `width`/`height`, or `radius` for a round shape) |
152
+ | add, move or delete a bend point | `onEdgeWaypointsChange(edge, waypoints)` |
153
+ | drag an end to pin its side | `onEdgeSideChange(edge, 'source' \| 'target', side)` (`undefined` = automatic again) |
154
+ | draw an edge | `onEdgeAdd(edge)` on add and redo, `onEdgeRemove(edge)` on undo |
155
+
156
+ An `onEdgeAdd` handler may give the new edge an `id`; the command finds it by that id after a
157
+ re-render.
158
+
159
+ ```javascript
160
+ const history = new CommandHistory();
161
+ const layout = { a: { x: 80, y: 80 }, b: { x: 300, y: 200 } };
162
+ const draw = () => engine.graphDiagram(
163
+ Object.entries(layout).map(([id, p]) => ({ id, name: id.toUpperCase(), ...p })),
164
+ [{ id: 'ab', source: 'a', target: 'b' }],
165
+ { editable: true, history, onNodeMove: (id, p) => { layout[id] = p; } },
166
+ );
167
+ draw();
168
+ // ...the user drags A; the page redraws...
169
+ draw();
170
+ history.undo(); // A goes back on screen, and onNodeMove puts layout.a back too
171
+ ```
172
+ **Grid:** `gridSize` (number, default `0`) snaps dragged and newly-added nodes to the nearest multiple;
173
+ `showGrid` (boolean, default `false`) draws a light grid behind the diagram. Both are plain `graphDiagram`
174
+ options, so they work for any editable diagram including ArchiMate. `GraphDiagram.snapToGrid(value, gridSize)`
175
+ is the pure helper if you need the same rounding rule elsewhere.
176
+ **Double-click:** dispatches to whatever's under the cursor — `onNodeDoubleClick(node)`/
177
+ `onEdgeDoubleClick(edge)` fire for a node/edge's line (nothing built-in happens without one; a typical wiring
178
+ opens the same dialog the right-click menu's "Show/Edit Properties…" does), `onCanvasDoubleClick(point)`
179
+ fires for empty canvas (SVG/world coordinates — no single object there to edit, so this is the natural place
180
+ for a diagram/view-level settings dialog instead). Double-clicking an existing waypoint handle still removes
181
+ it regardless of these — a distinct, precise gesture on a small routing control point, not "the edge" in
182
+ general. Every context-menu action (built-in or caller-supplied, via `contextMenuItems`/
183
+ `edgeContextMenuItems`/`canvasContextMenuItems`) now also receives the right-click's SVG/world point as a
184
+ third argument, `action(subject, engine, point)`.
185
+ **Edge tooltips:** hovering an edge shows a tooltip with its non-structural ("extra") data, the same
186
+ "only what isn't already visible on the diagram itself" convention a node's own hover tooltip uses
187
+ (`_edgeTooltipText`) — a bare `{source,target}` edge with nothing else set shows no tooltip at all. An
188
+ ArchiMate relationship's `kind` (extra data from `_archimateBuildEdge`'s point of view) always shows this
189
+ way.
190
+ **Manual resize:** `resizable: true` (or a node's own `node.resizable`, which overrides the diagram
191
+ default either way) shows a small handle on hover; dragging it sets `node.radius` — a hard override that
192
+ beats `sizeBy`/domain sizing, the same way explicit `node.x`/`node.y` already beat computed layout — or,
193
+ for a `shape:'table'` node, `node.width` (height still comes from column count). The handle's corner
194
+ defaults to `'top-right'`; `resizeCorner` changes it diagram-wide, `resizeCornerByShape: {shape: corner}`
195
+ per shape, and a node's own `node.resizeCorner` per node — each level overriding the one before it. See
196
+ `onNodeResize(node)`.
197
+ **Icons:** a node's `icon` (a short glyph/emoji string) or `iconUrl` (an image/SVG file or `data:` URI)
198
+ replaces the plain initials text centred on its shape (`iconPosition: 'center'`, the default) — `iconUrl`
199
+ wins if both are set; scales with the node's own radius, so it grows/shrinks along with manual resize.
200
+ `iconPosition: 'corner'` (diagram-wide, or a node's own `node.iconPosition`) instead draws it as a small
201
+ badge at a corner — same node > `iconCornerByShape` > diagram-wide `iconCorner` precedence as the resize
202
+ handle's corner, independently configurable (`archimateDiagram` uses this for its type-icon badge). Neither
203
+ mode applies to `shape:'person'` (already draws its own glyph) or `shape:'table'`.
204
+ **Box sizing/label position:** a `shape:'rect'` node's own `width`/`height` (either or both) override the
205
+ usual square-of-radius box, same idea as a table node's `node.width`. `labelPosition: 'inside'` (diagram-wide
206
+ default `'below'`, or a node's own `node.labelPosition`) left-aligns the label inside the box instead of
207
+ captioning it underneath, word-wrapped to as many lines as fit the box rather than one overflowing line
208
+ (`_wrapLabelLines`, measured with the renderer's `measureText(text, font)` — a 2D canvas context in the SVG and
209
+ Canvas renderers, an average-character estimate where there is none), keeping clear of a corner icon in a top
210
+ corner — meant for exactly this wider-than-tall case (`archimateDiagram` sets both). A node's own text style:
211
+ `textColor`, `fontFamily`, `fontSize` (`"12px"` or `"9pt"`), `fontWeight`, `fontStyle`; `strokeDasharray` dashes
212
+ its outline (an ArchiMate Grouping is drawn dashed). `options.edgesOnTop` draws lines over boxes instead of under
213
+ them, as ArchiMate tools do (`archimateDiagram` sets it), so a line into an element drawn inside another stays
214
+ visible.
215
+ **Label alignment:** `textAlign` (`'left'`|`'center'`|`'right'`) and `verticalAlign` (`'top'`|`'middle'`|`'bottom'`)
216
+ control the label's placement. Per-node values override diagram-wide `options.textAlign`/`options.verticalAlign`.
217
+ Defaults preserve existing behaviour: `labelPosition:'inside'` defaults to top-left; `'below'` defaults to
218
+ horizontal centre. `verticalAlign` only affects inside labels.
219
+ **Notes / annotations:** a node with `shape:'note'` renders as a yellow sticky note with a folded corner,
220
+ suppresses the centred icon/initials, and wraps its label inside. `archimateDiagram` accepts an `annotations`
221
+ option (array of `{id?, label/name, x, y, width?, height?}`) — each annotation becomes a note node on the
222
+ diagram but is not validated as an ArchiMate element, so notes are saved with the view without affecting the
223
+ model. Any plain `graphDiagram` can also use `shape:'note'` directly.
224
+ **Importable images:** a node with `shape:'image'` renders its `iconUrl` as an SVG `<image>` filling the node's
225
+ `width`/`height` (`preserveAspectRatio` defaults to slice; set `imagePreserveAspectRatio` on the node or
226
+ diagram options to change, e.g. `'xMidYMid meet'`). Image nodes suppress the usual centred icon/initials and
227
+ caption label. An optional `href` property turns the image into a clickable hyperlink (opened on click).
228
+ `test/charts.html` lets users add images via a file picker, drag-and-drop, or paste, and the node Properties
229
+ dialog exposes the link field. `RenderEngine.importArchiXML()` maps Archi `DiagramModelImage` elements to
230
+ `shape:'image'` and preserves their `iconUrl`/`href`.
231
+ **Label resolution** (`_resolveNodeLabel`, used everywhere a label renders — the box/caption, tooltip, table
232
+ header, keyword search): `label` may be a plain string (used as-is), a function `(node) => string` (called
233
+ at render time, the same "may be a function" convention `contextMenuItems`' own `label` already uses), or a
234
+ string with `{field}` placeholders (each replaced with that field's own value off the node — plain
235
+ substitution, not a parser, e.g. `'{name} ({type})'`). An empty/unset `label` falls back to `node.name`.
236
+ **More arrowheads/line styles:** beyond `'arrow'|'circle'|'tee'|'none'`, `arrowhead` (and a second,
237
+ independent `arrowheadStart` for the source end — e.g. a diamond at the "whole" end while the target end
238
+ carries none) also accepts `'diamond'|'diamond-hollow'|'triangle-hollow'|'open-arrow'`. `lineStyle:
239
+ 'solid'|'dashed'|'dotted'` generalizes the older boolean `dashed` (still honored when `lineStyle` isn't set).
240
+ **Node/edge properties:** `showPropertiesDialog(subject, engine, kind, {onSave})` and
241
+ `showDiagramPropertiesDialog(engine, {onSave})` are **shipped** (`src/propertiesDialog.js`, exported from the
242
+ package root) — wire them to `onNodeDoubleClick`/`onEdgeDoubleClick`/`onCanvasDoubleClick` or a context-menu
243
+ item and a diagram becomes editable. They used to exist only as ~400 lines inside `test/charts.html`, which
244
+ meant a consumer had to copy a demo to get double-click editing, so nobody did. `onSave(subject, changedKeys)`
245
+ fires after the subject is updated and the diagram re-rendered — that's where a consumer persists. The
246
+ decisions are pure and exported separately for reuse or testing: `propertyRowsFor` (which fields, which
247
+ section, which control), `coerceFieldValue` (read a string back as the type the field held), `derivedRefreshPlan`
248
+ (may a derived colour/icon be refreshed silently), `LAYER_CHOICES`, `NODE_SECTIONS`.
249
+ **Typed user-defined properties:** nodes and edges may carry `properties: [{name, type, value}]` where `type`
250
+ is `'string'`|`'integer'`|`'boolean'`|`'date'`. The Properties dialog shows these in their own section, lets
251
+ the user add/remove/edit them, and coerces values by type. `RenderEngine.NODE_STRUCTURAL_KEYS` and
252
+ `EDGE_STRUCTURAL_KEYS` include `'properties'` so they don't appear as untyped "extra data" tooltip lines.
253
+ A host that adds its own bookkeeping fields to the nodes and edges it hands in lists them in the
254
+ `internalKeys` option (an array); they are then left out of tooltips, the keyword filter and the
255
+ Properties dialog's "Other" section like the built-in fields. `graphDiagram.structuralKeys('node' | 'table' |
256
+ 'edge')` returns the combined set. ArchiMate passes `ArchimateView.PROJECTION_KEYS` (`elementId`,
257
+ `relationshipId`, `diagramInstanceId`, `visible`, `zIndex`, `viewObject`).
258
+ Archi native `.archimate` import/export round-trips properties: import converts flat `<property key="..." value="..."/>`
259
+ elements to typed `'string'` entries; export stringifies typed values back to flat key/value pairs.
260
+
261
+ There is still no built-in "Show/Edit Properties" **menu item** — `contextMenuItems`/
262
+ `edgeContextMenuItems` are plain hooks a caller wires up themselves; `RenderEngine.NODE_STRUCTURAL_KEYS`/
263
+ `EDGE_STRUCTURAL_KEYS` (used internally to keep built-in fields out of the auto tooltip's "extra data"
264
+ lines) are public statics for exactly this — see the `showPropertiesDialog` example and its use in the
265
+ Editable + Value Sizing demo in `test/charts.html` for a small dialog that lists every field on a clicked
266
+ node/edge (built-in and caller-added alike) as an editable input, and `showDiagramPropertiesDialog` (wired
267
+ to `onCanvasDoubleClick`/a canvas context-menu item in the same demos) for a diagram/view-level settings
268
+ dialog — `edgeRouting`, plus (for an ArchiMate diagram) the live `iconChoiceIndex` setting via
269
+ `_archimateSetIconChoiceIndex`. In that Properties dialog's own layer/type cascade (`test/charts.html`),
270
+ changing an ArchiMate element's `layer` clears and refocuses its `type` field — every official type belongs
271
+ to exactly one layer, so the old type essentially never still applies once the category changes.
272
+ - `graphDiagram3D(nodes, edges, opts)` — same data shape and sizing options, real orbit camera instead of
273
+ 2D force layout (node radius/edge width are additionally scaled by the camera's own perspective factor).
274
+ - `archimateDiagram(elements, relationships, opts)` — ArchiMate-notation diagram (Archi/archimatetool.com-style):
275
+ a thin transform over `graphDiagram` that builds rect nodes/edges with per-layer pastel fill, the label
276
+ inside the box, a small type-icon corner badge, and the 11 official relationship line/arrowhead notations,
277
+ then calls `graphDiagram` with them — every `graphDiagram` capability (drag, resize, routing, right-click
278
+ menus, editable add/rename/delete, ...) still applies.
279
+
280
+ **Archi native `.archimate` import/export:** `RenderEngine.importArchiXML(xml, viewId?)` parses an Archi
281
+ native XML file and returns `{ elements, relationships, options }` ready for `archimateDiagram()`. It maps
282
+ ArchiMate elements/relationships, view `DiagramObject` children (bounds, font, line thickness), and the
283
+ visual objects `DiagramModelNote`, `DiagramModelGroup`, `DiagramModelImage`, `DiagramModelReference`,
284
+ plus `SketchModelSticky`/`Actor` and `CanvasModelBlock`/`Sticky`/`Image`. Archi stores a nested visual's bounds
285
+ relative to its parent (a group, a group in a group, or an element drawn inside another element); import
286
+ turns them into positions on the view, so an element at `x="10"` inside a group at `x="100"` lands at 110.
287
+ A group inside a group keeps its `parentId`. On export, a view writes the groups drawn on it (those
288
+ with bounds in its `folderLayout`; `view.placedFolders`) nested as they are, each element inside the
289
+ group its folder is drawn as (`view.placedFolderOf(elementId)`, the nearest drawn ancestor), bounds
290
+ relative to the group. A folder that only exists in the model tree is not written as a group.
291
+ `model.allFolders()` lists every folder after its parent, read from `parentId`.
292
+ `RenderEngine.exportArchiXML(model, views)`
293
+ serializes back to Archi native XML, including element and relationship names, documentation and
294
+ properties. `views` are `{id, name, children}`; `fromArchimateView(view)` builds `children` from an
295
+ `ArchimateView`: one visual per instance, referencing the model element and relationship ids, with
296
+ bounds, bend points, and view-only notes and images.
297
+ The XML is what Archi 5.8 itself reads and writes (checked by opening our files in Archi and reading
298
+ back what it saved): all nine top-level folders in Archi's order (Archi fails to open a model with a
299
+ view but without them), elements as ArchiMate 3.2 classes (Archi knows no other: an ArchiMate 4
300
+ `process` in the application layer is written as `ApplicationProcess`, see `archimate32Type`),
301
+ relationships as `<Kind>Relationship` (a directed association is `AssociationRelationship
302
+ directed="true"`), groups and notes as `archimate:Group`/`archimate:Note` (a note's text in a
303
+ `<content>` element), each line naming its relationship as `archimateRelationship`, and each box
304
+ listing its incoming lines in `targetConnections` (without them Archi leaves some lines undrawn).
305
+ Import reads Archi's names and the older ones earlier versions of this module wrote.
306
+
307
+ **What an import keeps.** Checked against a real Archi model (canvas, sketch and ArchiMate views, nested
308
+ folders, styles, a legend, hints, report properties): Archi renders our export of it pixel-for-pixel as it
309
+ renders the original, and re-saving our export in Archi gives back the original model.
310
+ - **Positions.** The renderer places a node by its centre; Archi keeps a box's top-left corner. Import
311
+ centres each box (`x + width/2`), export takes the corner back (whole pixels, so a centre comes back
312
+ within half a pixel).
313
+ - **One instance per box.** Each box Archi draws is an instance with its own size and style, so the same
314
+ element twice on a view is two instances; each line joins the two instances it joins. A view shows what
315
+ Archi drew on it and nothing else (no "all elements", no lines Archi did not draw).
316
+ - **All views.** Views in sub-folders of Views, and canvas (`canvas:CanvasModel`) and sketch views: canvas
317
+ blocks and stickies and sketch stickies are view-only boxes with their text, colours and picture; a canvas
318
+ box without a border colour has no border; a sticky's text is centred vertically, as Archi draws it.
319
+ - **Style.** Archi's attributes `fillColor`, `lineColor`/`borderColor`, `fontColor`, `font` (SWT FontData:
320
+ name, size in points, bold/italic), `lineWidth`, `textAlignment` (centred when absent) and `textPosition`
321
+ become the instance's `appearance`.
322
+ - **Plain lines.** A line with no relationship (a note to an element, a canvas connection) is a
323
+ `ViewConnectionInstance` on the view, its look from Archi's `type` bits (solid/dashed/dotted; filled, hollow
324
+ or line arrows at either end). The validator leaves it alone.
325
+ - **Everything else, written back.** What the model does not use is kept on the item (`instance.archi`,
326
+ `view.archi`, `model.archi`, a concept's `archi`) and written back unchanged: other attributes (`hintTitle`,
327
+ `imagePosition`, `accessType`, `viewpoint`, a line's `locked`, ...), other child elements (`hintContent`, `notes`,
328
+ features such as a legend's), the original style (each attribute written as it was until its meaning is
329
+ changed), Archi's ids for boxes and lines, the stacking (document) order, a box's place inside another box
330
+ (canvas stickies in their blocks), the model-tree sub-folders (`archiFolder`), picture paths in the
331
+ archive, the model's id, `documentation` (Archi's purpose) and properties, and view properties (keys may
332
+ repeat). A copied view's boxes get new ids. Text keeps its spaces, and a CR is written as `&#xD;`.
333
+ - **Locks and groups.** A box's lock is the view's arrangement: Archi's `locked="true"` on import, and on
334
+ export where Archi can lock (canvas boxes); elsewhere (ArchiMate elements, notes, groups, images) Archi has
335
+ no lock, so it is the feature `renderengine.locked`, which Archi keeps. A logical group is the feature
336
+ `renderengine.group` (the group's id) on each member.
337
+ - **Canvases and templates.** A canvas (`canvas:CanvasModel`) is a `CanvasView`, its own kind of
338
+ `ArchimateView` (`view.constructor.kind === 'canvas'`; `ArchimateModel.registerViewType(archiViewType, Class)`
339
+ maps an Archi view type to a view class). A canvas kept to copy from is a template (`view.template`);
340
+ Archi has no such flag, so it is the view feature `renderengine.template` (`ARCHI_TEMPLATE_FEATURE`),
341
+ which Archi keeps. A copied view keeps its boxes inside their blocks.
342
+ - **Types Archi lacks.** Archi has a class for each ArchiMate 3.2 element type only, and will not open a
343
+ file naming another. An ArchiMate 4 type is written as its 3.2 type; any other (an IT icon type such as
344
+ `load-balancer`, or a custom type) as the nearest 3.2 type by layer and aspect (`archiElementType`,
345
+ `NEAREST_ARCHI_TYPE`: a technology device is a Node, a data store an Artifact, and so on), with its own type
346
+ kept as the feature `renderengine.type` and an Archi specialization named after it ("Load Balancer"), so
347
+ Archi users see it too. Import gives the type back and drops that specialization. An element Archi had
348
+ already given a specialization keeps its own.
349
+ - **No colour.** A box with no fill (`fillColor: 'none'`) is Archi's `alpha="0"`, and one with no outline
350
+ (`strokeColor: 'none'`) is Archi's `lineAlpha` feature at 0, both ways.
351
+ Bend points are written as Archi writes them, offsets from the centres of the two connected boxes
352
+ (`<bendpoint startX startY endX endY/>`, centres taken in whole pixels as Archi does). On import, bend
353
+ point i of n sits at `(1 - w)(sourceCentre + start) + w(targetCentre + end)` with `w = (i + 1)/(n + 1)`;
354
+ an `x`/`y` bend point from an earlier export is still read as a point on the view. An element saved
355
+ at Archi's default size (`width="-1"`) is 120 x 55 (a group 400 x 140, a note 185 x 80).
356
+
357
+ ```js
358
+ // Archi wrote <bendpoint startX="40" startY="13" endX="-160" endY="-87"/> on a line from a box at
359
+ // (0,0,120,55) to one at (200,100,120,55):
360
+ toRenderEngineModel(parseArchiModel(xml)).relationships[0].waypoints; // → [{ x: 100, y: 40 }]
361
+ ```
362
+ `toArchimateModel(parseArchiModel(xml))` reads all of that back, keeping notes and images on their
363
+ view (not as model elements) and the model's `name`.
364
+
365
+ **Archi files with pictures** are zip archives: the XML as `model.xml` and each picture under
366
+ `images/`, which `imagePath` names. `parseArchiFile(input)` takes a file as text, bytes or an
367
+ `ArrayBuffer`, plain or archived, and returns what `parseArchiModel` does with each archived picture
368
+ turned into a `data:` URL. `toArchiFile(model, views)` takes what `toArchiXML` does and returns
369
+ `{ data, archive, mimeType }`: plain XML when no visual has a `data:` picture, otherwise an archive
370
+ (bytes) with the pictures under `images/`, once each. `src/archimateArchive.js` owns the bridge;
371
+ `ZipArchive` (`src/zip/`, with a raw DEFLATE decoder and CRC-32, no dependencies) reads stored and
372
+ deflated entries, which is what Archi writes, and writes stored ones.
373
+
374
+ ```js
375
+ const file = toArchiFile(model, views); // { data, archive: true, mimeType: 'application/zip' }
376
+ const back = toArchimateModel(parseArchiFile(file.data));
377
+ ```
378
+
379
+ **Open Group Exchange Format** (ArchiMate Model Exchange File Format 3.1), the standard route to and
380
+ from other ArchiMate tools (Archi, Sparx EA, BiZZdesign, ...). `parseExchangeModel(xml)` gives what
381
+ `parseArchiModel` gives, and `toExchangeXML(model, views)` takes what `toArchiXML` takes, so both share
382
+ the Archi mapping; `toArchiExport(model)` gives an `ArchimateModel` as `[modelData, views]` for any of the
383
+ writers. `src/archimateExchangeFormat.js` owns what differs:
384
+ - **Positions.** A node's x/y and a bendpoint are on the view; Archi's are relative to the parent box and
385
+ offsets from the line's ends. The format allows no negative position, so a view reaching left of or
386
+ above 0 is moved right and down as a whole (as Archi exports).
387
+ - **Concepts.** Properties name a property definition (one per key). An Or junction is Archi's junction of
388
+ type "or"; an access relationship's `accessType` (Read, Access, ReadWrite; Write when absent) and an
389
+ influence's `modifier` are Archi's `accessType` and `strength`; `isDirected` is a directed association.
390
+ A type Archi lacks is written as its nearest 3.2 type, with its own kept in the property
391
+ `renderengine.type` (`EXCHANGE_TYPE_PROPERTY`), which import reads back.
392
+ - **Folders** are organizations: an item per Archi top-level folder, holding the sub-folders. On import an
393
+ outer item named as one of Archi's folders is that folder; any other outer item is a sub-folder.
394
+ - **Views.** Only ArchiMate diagrams: the format has no canvases or sketches, which are not written. A
395
+ group is a Container, a note (or picture, or view reference) a Label. A viewpoint is the format's name
396
+ ("Business Process Cooperation", `EXCHANGE_VIEWPOINTS`) and Archi's id (`business_process_cooperation`).
397
+ - **Style.** Colours are r/g/b with a 0-100 alpha (0: no fill or no line), the font a name, size and
398
+ `style` ("bold italic"). Only what a box or line sets itself is written; the reading tool draws the
399
+ rest with its own defaults.
400
+ - **Identifiers** must be xs:IDs: an id that is one is kept, any other (`1`, `a b`) is made one
401
+ (`id-1`, `id-a_b`) and every reference follows it.
402
+
403
+ Checked with Archi 5.8: Archi validates the files against the schema and opens them; a file Archi
404
+ exported comes back through `parseExchangeModel`, `toArchimateModel`, `toArchiExport` and
405
+ `toExchangeXML` identical to Archi's own import of it. Archi's importer ignores `lineWidth`.
406
+
407
+ ```js
408
+ const model = toArchimateModel(parseExchangeModel(xml)); // a file from Archi, Sparx EA, BiZZdesign, ...
409
+ const out = toExchangeXML(...toArchiExport(model)); // and back
410
+ ```
411
+
412
+ `src/archimateArchiFormat.js` owns the pure
413
+ mapping and the tiny dependency-free XML parser (uses `DOMParser` in browsers; under Node, a fallback
414
+ that decodes entities and character references, takes either quote, and skips comments and CDATA).
415
+ **CSV import/export:** `RenderEngine.exportModelCSV({elements, relationships})` returns `{ elementsCSV,
416
+ relationshipsCSV }` for spreadsheet editing; `RenderEngine.importModelCSV(elementsCSV, relationshipsCSV)`
417
+ parses them back into element/relationship objects. Columns are `id,name,type,layer,documentation,properties`
418
+ for elements and `id,source,target,kind,label,documentation,properties` for relationships. The `properties`
419
+ column holds the typed property array as JSON. `src/archimateCSV.js` owns the pure CSV helpers and is also
420
+ exported directly (`exportElementsCSV`, `importRelationshipsCSV`, etc.).
421
+
422
+ **`src/archimate.js`** owns the actual notation data — `ArchimateLayer`/`ArchimateElementType`/
423
+ `ArchimateRelationshipType` instances (all also re-exported from the package root, and as
424
+ `RenderEngine.ArchimateNotation`/`.ArchimateLayer`/etc. statics for convenience), queried through the
425
+ `ArchimateNotation` registry rather than archimateDiagram's own flat string-keyed maps — deliberately OO
426
+ because a type's layer is then a real object reference (`type.layer`), not a name in one lookup table and
427
+ the same name in a second, separate one that could silently drift apart. `ArchimateNotation.typesInLayer
428
+ (layerName)` is the query that relationship supports directly — the dependency a "type" control that must
429
+ narrow to match a chosen "layer" needs (see the cascading `layer`/`type` fields in the Properties dialog in
430
+ `test/charts.html`, which read exactly this both ways: picking a layer narrows type's own suggestions to
431
+ that layer's types; picking/typing a recognised type snaps layer to match it).
432
+
433
+ Every element type and relationship type also carries a short `description` for authoring UIs:
434
+ `ArchimateNotation.describe(typeName, version)` returns an element's description, and
435
+ `ArchimateNotation.describeRelationship(kind)` returns a relationship's description (both undefined for
436
+ unrecognised names). The Properties dialog in `test/charts.html` shows these in a "?" tooltip next to the
437
+ `type` and `kind` fields. `LLM_ARCHIMATE_GUIDE.md` packages the same catalog plus prompting guidance for
438
+ LLMs that generate diagram data.
439
+
440
+ **IT / infrastructure extension types.** `archimateDiagram` also recognises a set of practical IT concepts
441
+ that ArchiMate does not name directly: `firewall`, `laptop`, `server`, `database`, `table`, `file`, `folder`,
442
+ `container`, `pod`, `load-balancer`, `cloud-region`, and more (see `src/itIcons.js`/`RenderEngine.ITIconNotation`).
443
+ These resolve to the `technology` layer with the correct `aspect` (`active-structure` for devices/services,
444
+ `passive-structure` for data/files) and their own icon, so they behave like first-class ArchiMate elements in
445
+ diagrams, validation, and the construction UI. They are treated as extended/custom types, not official
446
+ ArchiMate, and the validator uses their aspect/layer for relationship advice.
447
+
448
+ `elements:[{id,label,layer?,type?,aspect?,icon?,iconUrl?}]` — `layer` (any string, not just the 7 official
449
+ names) picks the fill colour (`ArchimateLayer#color`, extendable via `opts.layerColors`), or derives from
450
+ `type` when `layer` is omitted: first checked against the full ~55 official ArchiMate element names
451
+ (`ArchimateNotation.elementType(type)?.layer` — most, e.g. `'goal'`/`'node'`/`'resource'`, do NOT start
452
+ with their own layer's name), then the prefix of `type` before its first `-` for anything not in that
453
+ catalog (`type:'business-my-widget'` → layer `'business'`, for ANY `'<layer>-whatever'` string, official
454
+ name or not). **Icons:** an element's own `icon`/`iconUrl` always wins; otherwise a recognised `type`'s own
455
+ representative icon (`ArchimateElementType#icon` — a glyph echoing the real pictogram's idea, e.g. Goal's
456
+ 🎯, Location's 📍, Application Component's 🧩 — not a redraw of the actual vector artwork); otherwise a
457
+ generic per-layer one (`ArchimateLayer#defaultIcon`) — either can be overridden diagram-wide via
458
+ `opts.defaultIcons`. **Badges are the notation's own glyphs, not emoji.** In the real notation a corner
459
+ badge IS the element's standalone glyph drawn small — a Business Process is a box with a little arrow in
460
+ the corner, and that same arrow alone is the element without its box. So the badge is rendered from the
461
+ type's own `notationShape` (as line art, via the generic `node.iconShape` graphDiagram field), one vector
462
+ glyph per type used at two sizes. Every official type in both catalogs has one, so ArchiMate rendering
463
+ never falls back to an emoji; `opts.iconSet` (`'glyph'|'emoji'`, default `'glyph'`) switches the whole
464
+ diagram to the Unicode stand-in set instead, and THAT is where `iconChoiceIndex`/`iconChoices` apply — the
465
+ standard glyph has no alternatives to choose between, the emoji do. **Icon choice sets:** a type doesn't offer just the one icon —
466
+ `ArchimateElementType#iconChoices`/`ArchimateNotation.iconChoices(typeName)` return a deduplicated list with
467
+ the official ArchiMate-standard glyph always at index 0, followed by a few generic aspect-wide alternatives
468
+ (`active-structure`/`behavior`/`passive-structure`/`motivation`/`other` — e.g. behavior types alternate
469
+ between ▶️/🔄/⚡) and finally the type's own layer default; an unrecognised/absent type falls back to
470
+ `GENERIC_ICON_CHOICES` (every aspect's alternatives pooled together). Which choice is used as an element's
471
+ *default* (when it sets no `icon` of its own) is a diagram-wide setting, `opts.iconChoiceIndex` (default
472
+ `0`, the standard icon) — an out-of-range index falls back to choice 0 rather than throwing. This is what a
473
+ "choice of icons per type, with the ArchiMate-standard one as the default" UI (a `<select>` of
474
+ `iconChoices(type)`, e.g. the Properties dialog's own `icon` field in `test/charts.html`) is built on; an
475
+ element's own explicit `icon` still always wins over any diagram-wide choice. `iconChoiceIndex` isn't just a
476
+ one-shot build option — `engine._archimateSetIconChoiceIndex(newIndex)` changes it LIVE after the diagram's
477
+ already drawn, re-deriving the icon for every node that's still on its old auto-derived default (same "was
478
+ it ever customised" check the Properties dialog's own layer/type-change handler uses) and leaving any
479
+ individually-picked icon alone; `test/charts.html`'s `showDiagramPropertiesDialog` (opened via
480
+ `onCanvasDoubleClick` or a canvas context-menu item) is a small UI for this. It's kept on `engine.
481
+ _archimateOptions`, not on `_chartState` — `archimateDiagram`'s own settings (`iconChoiceIndex`/
482
+ `layerColors`/`defaultIcons`/`relationshipStyles`) aren't part of `graphDiagram`'s flat `options` (they're
483
+ destructured out before its own options are built), and `_chartState` itself gets overwritten wholesale on
484
+ every `graphDiagram`/`_rerenderGraph` call, so anything meant to survive a redraw lives on the engine
485
+ instance instead. **Box corner style**
486
+ (`ArchimateElementType#cornerStyle`): every Motivation-layer
487
+ element is chamfered (all 4 corners cut at 45°) regardless of anything else; `'behavior'` aspect
488
+ (Process/Function/Service/Event/Interaction-like) gets rounded corners — an explicit `element.aspect` wins,
489
+ else a recognised `type`'s own aspect (so `type:'business-process'` is rounded with zero extra config);
490
+ everything else is sharp (correct for active/passive-structure elements). An element's own `cornerStyle`
491
+ (`'sharp'|'rounded'|'chamfered'`) overrides all of that directly — and works on any plain `graphDiagram`
492
+ `shape:'rect'` node too, not just via `archimateDiagram`.
493
+
494
+ `relationships:[{source,target,kind?}]` — `kind` (`composition`/`aggregation`/`assignment`/`realization`/
495
+ `specialization`/`serving`/`access`/`influence`/`triggering`/`flow`/`association`) picks the notation
496
+ (`ArchimateRelationshipType`, extendable via `opts.relationshipStyles`); an explicit `lineStyle`/
497
+ `arrowhead`/`arrowheadStart` on one relationship still overrides its kind's default.
498
+
499
+ **Standard alignment (advisory warnings, not blocking).** `archimateDiagram` validates the model against
500
+ the ArchiMate metamodel and warns where it deviates. `opts.invalidRelationships` says how an invalid
501
+ relationship shows: `'highlight'` (the default: a red dashed line and a tooltip), `'tooltip'` (the warning
502
+ on hover only) or `'off'` (nothing checked); `opts.validate: false` also turns checking off. The ArchiMate
503
+ editor takes it from the "Invalid relationships" setting, which a diagram, its model or the tool sets
504
+ (see the settings cascade below). Warnings are delivered three ways:
505
+ - `opts.onValidationWarning(warning)` is called once per warning during render;
506
+ - `engine._archimateWarnings` holds the full list after render;
507
+ - invalid relationships carry a tooltip explaining the issue, and with `'highlight'` a red dashed stroke.
508
+ Relationships between ArchiMate types are checked against the specification's own relationship table
509
+ (The Open Group, ArchiMate 3.2 Specification, Appendix B; `ArchimateValidator.specificationKinds`), an
510
+ ArchiMate 4 type looked up as its 3.2 type. `src/archimateRelationshipTable.js` holds the table, generated
511
+ by `scripts/generate-archimate-relationships.mjs` from the machine-readable copy Archi ships. Custom and IT
512
+ types, which the table does not name, fall back to `ArchimateValidator.RULES`: data, each rule naming its
513
+ `kinds` and constraining `fromAspect(s)`, `toAspect(s)`, `aspects`, `sameAspect`, `sameLayer`, `fromTypes`
514
+ or `toTypes`. Also checked: v4 Common Domain elements (`process`/`function`/`service`/`interaction`/`event`)
515
+ needing an explicit `layer`, and retired v3.2 types used under v4.
516
+
517
+ ```js
518
+ ArchimateValidator.allowedKinds({ type: 'business-actor' }, { type: 'business-role' }, '4');
519
+ // → ['assignment', 'serving', 'triggering', 'flow', 'association', 'association-directed']
520
+ ``` `RenderEngine.ArchimateValidator` exposes the same checks for construction UIs:
521
+ `ArchimateValidator.validateModel(elements, relationships, version)`,
522
+ `ArchimateValidator.validateRelationship(source, target, kind, version)`, and
523
+ `ArchimateValidator.allowedKinds(source, target, version)` — the latter powers the filtered relationship-kind
524
+ dropdown in `test/charts.html`'s Add Edge and Edge Properties dialogs.
525
+
526
+ **Specializations.** `RenderEngine.ArchimateSpecializationRegistry` (src/archimateSpecializations.js)
527
+ stores user-defined specializations (`{id, name, baseTypes[], icon?, color?, description?}`). Tag an
528
+ element with `specialization: '<id-or-name>'` and `archimateDiagram` applies the specialization's icon
529
+ and colour unless the element already has its own. `registry.serialize()` / `registry.deserialize(array)`
530
+ make persistence trivial; `test/charts.html` persists the catalog in `localStorage` and exposes a
531
+ "Specializations…" manage dialog.
532
+
533
+ **Colours, glyphs and line weights are read out of the spec's own artwork, not eyeballed.** The
534
+ specification PDF embeds its "Appendix A: Summary of Language Notation" figures as LOSSLESS PNGs, so the
535
+ palette below is the literal pixel value of each fill: Business `#FFFFAF`, Application `#AFFFFF`,
536
+ Technology and Physical both `#AFFFAF` (the spec draws them the same green), Strategy `#F5DEAA`,
537
+ Motivation `#CCCCFF`, Implementation & Migration `#FFE0E0`, Location `#FFBF80`. A few types carry a colour
538
+ their own layer doesn't share — Plateau/Gap are green (`#E0FFE0`) among the pink Implementation elements,
539
+ Grouping is unfilled — so `ArchimateElementType#ownColor` overrides the layer fill for those (element
540
+ `color` > `opts.layerColors` > `ownColor` > layer). Elements are drawn with a thin BLACK outline
541
+ (`strokeColor: '#000000'`, `strokeWidth: 1.2` — both plain graphDiagram node fields, defaulting to the
542
+ usual white 2px elsewhere), which is what the real notation uses; the white stroke graphDiagram uses to
543
+ separate overlapping blobs is a large part of why an ArchiMate diagram drawn with plain defaults reads as
544
+ much brighter and softer than the spec. The relationship notations were checked glyph-by-glyph against the
545
+ same appendix — all 11 kinds, plus the directed-association variant (`association-directed`, a single-barb
546
+ `open-arrow-half`) and both junction connector forms the figure shows alongside them.
547
+
548
+ **ArchiMate version: `opts.archimateVersion` (`'3.2'|'4'`, default `'4'`, `ArchimateNotation.versions()`).**
549
+ ArchiMate 4 retires 19 of 3.2's element types (`src/archimate.js`'s own doc comment has the full reasoning):
550
+ fifteen layer-specific behavior types (`business-process`/`application-process`/`technology-process`, and
551
+ the same 3-layer split for `function`/`service`/`interaction`/`event`) collapse into five layer-INDEPENDENT
552
+ "Common Domain" types (`process`/`function`/`service`/`interaction`/`event` — `ArchimateElementType#layer`
553
+ is `null` for these five; the real layer must come from the element's own `layer` field, not the type name);
554
+ four more (`contract`, `representation`, `constraint`, `gap`) are retired outright, each replaced by an
555
+ existing type. Under the `'4'` default, every element is first run through `ArchimateNotation.
556
+ migrateElementToV4` — a direct mirror of a jArchi "Migrate Model Elements from ArchiMate 3.2 to ArchiMate 4"
557
+ script's own two mapping tables — a no-op for any other type (most of the ~55, e.g. `business-actor`,
558
+ `application-component`, `goal`, `node`, are identical in both versions and all 11 relationship notations
559
+ are unchanged), so 3.2-authored `elements` data still renders correctly, auto-upgraded and tagged with its
560
+ own migration provenance as plain extra data: `ArchiMate4_Domain` (the original layer, e.g. `'Business'`)
561
+ + `MigratedFrom` (the original type) for a behavior consolidation, or `LegacyType`/`Type` + `MigratedFrom`
562
+ for a retired type — both visible in the hover tooltip/Properties dialog like any other custom field, same
563
+ as `kind` already is for a relationship. An element that already carries its own `layer` keeps it; one that
564
+ doesn't gets it filled in from the migration's own knowledge of where it used to live (needed since a
565
+ Common Domain type's own layer is `null` — without this, a migrated element with no explicit `layer` would
566
+ silently fall through to 'other'). Under `'3.2'`, no migration happens at all — every `ArchimateNotation`
567
+ lookup `_archimateBuildNode`/`_archimateBuildEdge` make (`elementType`, `elementTypeNames`, `typesInLayer`,
568
+ `iconChoices`, `elementLayer` — all take an optional `version` parameter, default `'4'`) resolves against
569
+ the original 3.2 catalog instead. See the ArchiMate Diagram demo's own version `<select>` in
570
+ `test/charts.html`, which re-runs `archimateDiagram` over the SAME `elements`/`relationships` for either
571
+ choice (not a live bidirectional toggle — a v4-migrated type like `'process'` was never a v3.2 catalog entry
572
+ to begin with, so round-tripping back through the ALREADY-migrated data wouldn't resolve correctly; picking
573
+ a version is a "how should this diagram be built" choice, made fresh from the original input each time).
574
+
575
+ **Distinct notation shapes: `opts.useDistinctShapes` (boolean, default `false`).** The plain box + corner
576
+ icon isn't the only ArchiMate notation — the spec's own Appendix A ("Summary of Language Notation") gives
577
+ many types a real distinct outline instead (Process = a plain arrow, Interface = a lollipop, Node = a 3D
578
+ cube, ...). `ArchimateElementType#notationShape` (`src/archimate.js`) names that shape (a `graphDiagram`
579
+ `shape` value — see `_nodeShapeEl`) for every type that has one; `undefined` for a type the spec only ever
580
+ shows as a plain box (Business Object, Work Package, Deliverable, ...), or whose real glyph is still too
581
+ compound to render cleanly as a single node shape here (System Software's box+circle is the one gap left).
582
+ 32 shapes implemented, verified against rendered output (not just the spec image) — see
583
+ `test/archimate.test.js`'s own `notationShape` describe block for the full type→shape mapping:
584
+ `arrow` (Process), `chevron` (Function, points up with a notched bottom — distinct from arrow, which points
585
+ right), `half-circle` (Interaction), `flag` (Event/Value Stream/Implementation Event), `pill` (Service, a
586
+ fully-rounded stadium box), `double-circle` (Collaboration), `lollipop` (Interface), `cylinder` (Role/
587
+ Stakeholder — a HORIZONTAL drum, left/right elliptical caps, not top/bottom), `cube` (Node, a real 3-face
588
+ isometric box), `target` (Goal/Outcome/Course of Action — a bullseye with a dark centre dot), `hexagon`
589
+ (Material — flat top/bottom, pointed left/right), `pin` (Location), `cloud` (Meaning), `document` (Artifact),
590
+ `parallelogram` (Requirement), `constraint` (Requirement's parallelogram plus its own internal divider
591
+ line), `gear` (Equipment — two overlapping cogs), `ellipse` (Value), `battery` (Resource), `component`
592
+ (Application Component — a box with two connector tabs on the left edge), `factory` (Facility), `device`
593
+ (Device — a monitor on a stand), `steps` (Capability — an ascending staircase), `wheel` (Driver — a
594
+ steering wheel), `magnifier` (Assessment), `stack` (Plateau — three bars), `gap-circle` (Gap — a circle
595
+ crossed by two lines), `principle` (Principle — a rounded square with "!"), `double-arrow`
596
+ (Communication/Distribution Network), `double-arrow-dashed` (Path), `junction` (a small solid dot, not the
597
+ plain default circle — no centred initials text), plus the existing `person` (Business Actor). Same
598
+ "representative, not pixel-perfect" calibration as the corner-badge icons — cross-checked against the spec,
599
+ a few curves/angles simplified for a clean SVG path, not a redraw of the actual vector artwork. When a
600
+ distinct shape is in use: the label moves below it (there's no "inside the box" for most of these,
601
+ `labelPosition` defaults to `'below'` instead of `'inside'`), and no default corner icon OR centred-initials
602
+ fallback is shown (the shape itself already says what the element is — every notationShape case sets
603
+ `suppressInner`) — an element's own explicit `icon`/`iconUrl` still wins regardless. `useDistinctShapes` is
604
+ a diagram-wide DEFAULT (off, so nothing changes for an existing diagram that hasn't opted in); an individual
605
+ element's own `useDistinctShape` (true/false) overrides it either way. A type with no implemented
606
+ `notationShape` always renders as the plain box, whichever way the setting goes. Every distinct shape also
607
+ honours an explicit `width`/`height` override the same way the plain box does (`_nodeSize` supports this for
608
+ any shape now, not just `'rect'`/`'table'` — and the generic `'below'` label offset and the plain
609
+ `'person'`/`'diamond'`/default-circle shapes were both fixed to size off the box's own smaller dimension
610
+ instead of `max(w,h)/2`, so a wide-but-short box no longer bleeds its label or an oversized circle into the
611
+ row below it in a dense grid). See the ArchiMate Diagram demo's own checkbox in `test/charts.html`, and the
612
+ dedicated **ArchiMate Notation Reference** demo right after it — every type the current version's catalog
613
+ has, one row per layer, built entirely from `ArchimateNotation.layers()`/`.typesInLayer()` rather than a
614
+ hand-picked sample, with the same box/shape toggle.
615
+
616
+ **Adding elements/relationships interactively:** archimateDiagram only enriches the `elements`/
617
+ `relationships` it's first called with — a node added afterward (e.g. via `editable`'s own "Add Node", or a
618
+ caller's own canvas action) doesn't automatically get archimateDiagram's layer colour/corner style/icon.
619
+ Use `engine._archimateBuildNode(element, opts)` and `engine._archimateBuildEdge(relationship, opts)` to
620
+ enrich a new item exactly the same way archimateDiagram does internally — see the ArchiMate Diagram demo's
621
+ `canvasContextMenuItems`/`onEdgeAdd` in `test/charts.html`. The built-in drag-to-connect gesture now acts as
622
+ a **magic connector** for ArchiMate diagrams: it picks the first relationship `kind` that
623
+ `ArchimateValidator.allowedKinds(source, target, version)` considers valid and immediately applies the
624
+ matching line/arrowhead style, falling back to a plain `{source,target}` edge only when no validator is
625
+ available. The right-click edge menu also offers **Invert Direction** to swap source/target in place.
626
+
627
+ See that same demo for all 11 relationship kinds, business/application/technology/motivation layers, a
628
+ chamfered Motivation goal, and rounded process/service boxes.
629
+
630
+ **Properties dialog fields are typed, not one-size-fits-all text boxes** (`test/charts.html`'s
631
+ `showPropertiesDialogUnsafe`/`addRow`): `'text'` (default) | `'textarea'` (`description`) | `'color'` (a
632
+ native `<input type=color>` swatch/picker for `color`, in place of a raw hex string) | `'select'` (a closed
633
+ list — `layer`, and `icon` sourced from `ArchimateNotation.iconChoices(subject.type)`) | `'select-editable'`
634
+ (an `<input list=datalist>` that still accepts free text — `type`, since archimateDiagram deliberately
635
+ doesn't enforce a closed type catalog). The dialog also groups a node's fields into an **Object** section
636
+ (`name`/`description`/`type` — the ArchiMate concept itself, the same regardless of how any one diagram
637
+ draws it) and a **Diagram** section (`label`/`layer`/`color`/`icon`/`group`/`size` — this view's own
638
+ presentation of it), with any remaining extra data under "Other"; this is a form-level grouping only —
639
+ `graphDiagram` itself still renders one flat node object, there's no separate model/view split under the
640
+ hood. The existing layer↔type cascade (`ArchimateNotation.typesInLayer`/`.elementType`) now also refreshes
641
+ `icon`'s own `<select>` options to the newly-chosen type's `iconChoices` whenever `type` changes, keeping the
642
+ current icon selected if it's still offered.
643
+ - `dataModelDiagram(entities, relationships, opts)` — conceptual/logical/physical entity-relationship diagram,
644
+ the same "thin transform over `graphDiagram`" shape as `archimateDiagram`. **`src/datamodel.js`** owns the
645
+ notation data (`DataModelLevel`/`NotationStyle`, queried through the `DataModelNotation` registry — also
646
+ re-exported from the package root and as `RenderEngine.DataModelNotation`), its own separate OO file/
647
+ registry for the same reason `archimate.js` is: this domain's vocabulary shouldn't live inside RenderEngine
648
+ itself.
649
+ **Three levels, one model — the cross-mapping.** An entity is ONE object: `{id, name, description?, x?, y?,
650
+ color?, conceptual?, logical?, physical?}` — `name`/`description` are shared across every level (the
651
+ entity's own identity, unchanged by fidelity), while `entity.logical.attributes`/`entity.physical.
652
+ attributes` (`[{name, type?, isPK?, isFK?}]`, physical's own sub-object also taking `tableName`) hold that
653
+ level's own detail — the same object/visual split `archimateDiagram` draws between an element and its
654
+ diagram, just spanning "levels" instead of "diagrams". `opts.level` (`'conceptual'|'logical'|'physical'`,
655
+ default `'logical'`) picks which one's on screen; an entity with no attributes at the current level
656
+ (typical for `'conceptual'`) always renders as a plain box, regardless of notation. Re-calling
657
+ `dataModelDiagram` with a different `level` over the SAME `entities`/`relationships` re-renders that same
658
+ model at different fidelity — there's only ever one model, never three separately-maintained diagrams that
659
+ could drift apart (see the Data Model demo in `test/charts.html`, whose Level/Notation `<select>`s just call
660
+ it again).
661
+ **Three notations, one cardinality vocabulary.** `relationships:[{source,target,name?,sourceCardinality?,
662
+ targetCardinality?,identifying?}]` — cardinality is stored once as a UML-style multiplicity string
663
+ (`'0..1'|'1'|'0..*'|'*'|'1..*'`, `DataModelNotation.cardinalities()`), and `opts.notation`
664
+ (`'chen'|'crowsfoot'|'uml'`, default `'crowsfoot'`) picks how each end renders it: **Chen** turns the
665
+ relationship itself into its own `shape:'diamond'` node with two plain edges (not one direct edge), each
666
+ labelled with a plain `'1'`/`'N'` text cardinality; **Crow's Foot** renders cardinality as an arrowhead-style
667
+ glyph at each entity end (`crowsfoot-one`/`-one-optional`/`-many`/`-many-mandatory` — new SVGRenderer marker
668
+ defs, fork for "many", tick for "one", hollow circle for "optional", usable as a plain edge `arrowhead`/
669
+ `arrowheadStart` outside dataModelDiagram too); **UML** shows the multiplicity string itself as a text label
670
+ near each end instead, via the new generic `edge.sourceLabel`/`edge.targetLabel` fields (independent of the
671
+ existing midpoint `edge.label` — see graphDiagram's own doc comment). `relationship.identifying === false`
672
+ renders a dashed line (a non-identifying relationship, in real ERD terms) regardless of notation;
673
+ unset/`true` stays solid. None of the three notations are pixel-perfect renditions of the real, full
674
+ notations (same calibration as `archimateDiagram`'s own icons) — see `src/datamodel.js`'s own doc comment.
675
+ - `railroadDiagram(diagramString)` — renders an EBNF grammar string as a railroad diagram. No node/edge
676
+ data at all.
677
+
678
+ **Y-axis ticks.** Most cartesian chart types only draw a bare axis line — no numeric tick marks
679
+ or gridlines. `barChart` and `groupedBarChart` are wired up to `_drawYAxisTicks` (a shared private
680
+ helper built on `niceTicks`/`formatValue`), which draws left-edge tick marks, value labels, and
681
+ light horizontal gridlines, and returns the "nice" (rounded-up) domain max the chart's own marks
682
+ should scale against instead of the raw data max — see `barChart`'s own doc comment in
683
+ `src/index.js` for why that matters (so the tallest bar actually reaches the top tick). Extending
684
+ this to `line`/`area`/`stackedBar`/etc. is the same three-line change: call
685
+ `this._drawYAxisTicks(x, y, width, height, rawMax)` before drawing marks, and scale against its
686
+ return value instead of the raw max — not done yet for the rest, tracked as a follow-up.
687
+
688
+ ## Common options
689
+
690
+ Not every method takes every option below — the table above calls out what's notable per type —
691
+ but these recur across most 2D chart types:
692
+
693
+ ```javascript
694
+ {
695
+ title: 'Chart Title',
696
+ xAxisLabel: 'X Axis', yAxisLabel: 'Y Axis',
697
+ colors: ['#5470c6', '#91cc75'], // palette, indexed by series/category/segment
698
+ x: 70, y: 60, width: 500, height: 300, // chart box (defaults from container size)
699
+ showLegend: true, // or `legend:` depending on method — see table above
700
+ }
701
+ ```
702
+
703
+ Data-resolution options (used with `register()`/`refreshChart()`, see below):
704
+
705
+ ```javascript
706
+ { dataPath: 'metrics.sales', dataMapper: (v) => v * 1.1 }
707
+ ```
708
+
709
+ ### `colorCol` — plain field, or a compiled formula
710
+
711
+ `scatterChart`/`bubbleChart`/`bubbleChart3D`'s `colorCol` names which field to color-group points
712
+ by (falling back to a `category`/`group` field if present, and to one flat color if neither is
713
+ given). It also accepts a **compiled formula** over the point's own fields, instead of just a field
714
+ name — e.g. bucketing points by whether a value crosses a threshold, without pre-computing a
715
+ derived field before calling into RenderEngine:
716
+
717
+ ```javascript
718
+ engine.scatterChart(data, {
719
+ colorCol: { formula: 'value > threshold ? 1 : 0', params: ['value', 'threshold'] },
720
+ });
721
+ ```
722
+
723
+ `params` are field names read off each data point, in the order the formula uses them. The formula
724
+ is a small, dependency-free scalar-expression language (`src/kernelExpr.js`) — arithmetic,
725
+ comparisons, `&&`/`||`/`!`, `? :`, and `abs floor ceil round sign sqrt exp log sin cos tanh min max
726
+ pow clamp` — compiled once per `colorCol` object with `new Function` from an AST the parser built,
727
+ never from the formula text directly, so a bad or malicious-looking formula string is refused at
728
+ parse time rather than reaching `eval`. Every distinct value the formula returns gets its own
729
+ palette color, exactly the way a plain field's distinct values already do.
730
+
731
+ ## Data & utility methods
732
+
733
+ ```javascript
734
+ engine.setGlobalData(data) // set a global object referenceable by dot-path
735
+ engine.resolvePath(path, mapper?) // e.g. resolvePath('metrics.sales.0.value')
736
+ engine.register(id, type, options) // remember a {type, options} pair for a container id
737
+ engine.refreshChart(id) // re-invoke it — resolves options.data or dataPath+dataMapper fresh each time
738
+ engine.niceTicks(maxVal, count=4) // readable axis tick values
739
+ engine.formatValue(v) // 1234567 -> "1.23M"
740
+ engine.bindTooltip(element, text)
741
+ engine.bindRichTooltip(element, {title, rows:[{k,v}]})
742
+ engine.adjustColor(hex, amount) // lighten/darken, used internally for 3D shading
743
+ ```
744
+
745
+ `register`/`refreshChart` is the registration mechanism `addDataPoint`/`addGraphNode` (next section)
746
+ build on.
747
+
748
+ ## Interactivity
749
+
750
+ ```javascript
751
+ engine.hitTest(x, y) // shape under a point (manual on Canvas, native on SVG)
752
+ engine.enterState(shape, 'hover') // apply a named visual state
753
+ engine.leaveState(shape, 'hover')
754
+ engine.resetView() // reset SVG pan/zoom or 3D orbit camera to identity
755
+ ```
756
+
757
+ SVG-rendered charts get wheel-zoom and drag-empty-space-to-pan for free; 3D-orbit charts
758
+ (`bubble3D`, `bar3DOrbit`, `stackedBar3DOrbit`, `graphDiagram3D`) get drag-to-rotate/wheel-to-zoom
759
+ instead — the two are mutually exclusive per chart, never combined.
760
+
761
+ ## Getting a chart out — clipboard and files
762
+
763
+ ```javascript
764
+ import { copyImage, downloadPng, downloadSvg, toPngBlob } from 'renderengine';
765
+
766
+ await copyImage(container, { name: 'sales-by-region' }); // → {via:'clipboard'} | {via:'download', filename}
767
+ await downloadPng(container, { scale: 2 });
768
+ downloadSvg(container);
769
+ const blob = await toPngBlob(container, { scale: 2, background: '#ffffff' });
770
+ ```
771
+
772
+ Pass the container, the `<svg>` or the `<canvas>` — the drawable is found either way. Options are
773
+ `scale` (default 2, retina-sharp), `background` (default `'#ffffff'`, `null` for transparent) and
774
+ `name` (the download's filename stem).
775
+
776
+ `copyImage` **falls back to downloading the PNG** where writing images to the clipboard is
777
+ unavailable — Firefox, and any page not on a secure context — so the button always does something.
778
+ The returned `via` says which happened, so a caller can tell the user the truth rather than claiming
779
+ a copy that did not occur.
780
+
781
+ Three things this handles that a hand-rolled `XMLSerializer` + `canvas` will get wrong, each
782
+ invisible until someone pastes the result somewhere:
783
+
784
+ | | |
785
+ |---|---|
786
+ | **No intrinsic size** | An SVG sized by CSS (`width: 100%`) has no `width` attribute, and an `Image` loading that markup is 0×0 — a blank paste. Explicit pixel dimensions are set from the laid-out box. |
787
+ | **Transparent background** | Diagrams draw no background, so a PNG pasted into anything dark shows dark-on-dark labels. A background rect goes in as the first child, behind the drawing. |
788
+ | **Namespace loss** | Markup without `xmlns` will not load as an image at all; `xlink:href` (markers, patterns) needs its own, added only when something uses it. |
789
+
790
+ Rasterizing is capped at `MAX_IMAGE_PIXELS` (32M). Over that, the **scale** is reduced rather than
791
+ the diagram's own dimensions — halving the scale still shows the whole diagram, whereas cropping or
792
+ shrinking it loses content or legibility. `imageSize()` and `prepareSvgMarkup()` are exported
793
+ separately, and are pure, if you want the decisions without the DOM.
794
+
795
+ **The exported image does not inherit the page's theme.** Serialized markup loaded as an image sees
796
+ no author stylesheet, so the drawing falls back to the presentation attributes the engine itself
797
+ emits — dark text and axes — rather than whatever the workspace's dark theme was overriding them
798
+ with. That is the right result for something being pasted into a document on white paper, but it
799
+ does mean a copy taken from a dark-themed app comes out light. To export a genuinely dark image,
800
+ set `background` to the dark colour *and* render the chart with explicitly dark-friendly colours
801
+ rather than relying on CSS.
802
+
803
+ ## Dynamically adding data points in a bounded window
804
+
805
+ `addDataPoint`/`addGraphNode` are real `RenderEngine` methods — the engine owns the append/trim
806
+ mechanics, not the caller. Internally they delegate to a small class hierarchy in
807
+ `src/datawindow/` (see `ARCHITECTURE.md`'s "Data windows — streaming" section for the full design);
808
+ this section is just how to call it.
809
+
810
+ ### 1. Register, then push
811
+
812
+ ```javascript
813
+ engine.register('live-chart', 'line', { data: initialSeries, title: 'Live feed' });
814
+
815
+ let tick = 0;
816
+ setInterval(() => {
817
+ engine.addDataPoint('live-chart', () => ({ label: `T${++tick}`, value: randomValue() }), { maxPoints: 50 });
818
+ }, 1500);
819
+ ```
820
+
821
+ - `register(id, catalogKeyOrMethodName, options)` — same registration mechanism `refreshChart` has
822
+ always used; `options.data` is the chart's current dataset.
823
+ - `addDataPoint(id, pointOrFactory, { maxPoints })` — appends one point (dropping the oldest once
824
+ past `maxPoints`; `0` = unbounded) and re-renders via `refreshChart`. `pointOrFactory` can be a
825
+ literal point, or a factory — required for multi-series charts (`line`/`area`/`pipe`), where it's
826
+ called once per series so each series can get its own value, and useful for shapes like
827
+ `candlestick` where the next point depends on the last one:
828
+
829
+ ```javascript
830
+ // candlestick — factory receives the current data array, so the next bar
831
+ // can continue from the previous bar's close:
832
+ engine.addDataPoint('live-candles', (data) => {
833
+ const last = data[data.length - 1];
834
+ const open = last ? last.close : 100;
835
+ const close = +(open + (Math.random() - 0.5) * 6).toFixed(2);
836
+ return { label: `T${++tick}`, open, high: Math.max(open, close) + 1, low: Math.min(open, close) - 1, close };
837
+ }, { maxPoints: 100 });
838
+ ```
839
+
840
+ Every `CHART_CATALOG` entry declares a `streamShape` that decides how `addDataPoint` treats its
841
+ data — `addDataPoint` is a **no-op** when it's `'none'`:
842
+
843
+ | `streamShape` | Chart types | Behavior |
844
+ |---|---|---|
845
+ | `flat` | `bar`, `pie`, `scatter`, `bubble`, `candlestick`, `stackedBar`, `funnel`, `waterfall`, `histogram`, `qq`, `lag`, `treemap`, `sunburst`, `outline`, `bar3D`, `bubble3D` | one new record appended to the outer array |
846
+ | `series` | `line`, `area`, `pipe` | every series' own `values` grows by one point |
847
+ | `grouped-sample` | `boxPlot`, `density`, `violin` | exactly ONE randomly-chosen group's `values` grows (a new sample observation belongs to one group, unlike a shared time axis) |
848
+ | `none` | `heatmap`, `radar` (fixed matrix/axis-set), `bar3DOrbit`/`stackedBar3DOrbit` (fixed grid), `gauge-dial`/`gauge-bar`/`bullet` (a value to replace, not a window) | `addDataPoint` no-ops — for gauges, just call `dialGauge(newValue)`/`barGauge(newValue)`/`bulletGauge(newValue)` again directly |
849
+
850
+ ### 2. Graphs — `addGraphNode`
851
+
852
+ `graphDiagram`/`graphDiagram3D` aren't `CHART_CATALOG` entries (see ARCHITECTURE.md), so they get
853
+ their own method rather than going through `streamShape`. Register with `{nodes, edges}` instead of
854
+ `{data}` — `refreshChart` detects the graph shape via `options.nodes`:
855
+
856
+ ```javascript
857
+ engine.register('live-graph', 'graphDiagram', { nodes: initialNodes, edges: initialEdges });
858
+
859
+ setInterval(() => {
860
+ const parent = engine.registry['live-graph'].options.nodes[Math.floor(Math.random() * n)];
861
+ engine.addGraphNode('live-graph', { id: `n${++tick}`, label: `Node ${tick}` }, [{ source: parent.id, target: `n${tick}` }], { maxNodes: 30 });
862
+ }, 1500);
863
+ ```
864
+
865
+ `addGraphNode(id, node, newEdges, { maxNodes })` drops the oldest nodes once past `maxNodes`, and
866
+ any edge left dangling off a removed node along with them.
867
+
868
+ ### Chained form — `engine[id].addGraphNode(...)`
869
+
870
+ Once an id is `register()`ed, `engine[id]` (or `engine.someId` when the id happens to be a valid
871
+ identifier — most DOM ids, e.g. `chart-container`, contain a hyphen, so bracket notation is what
872
+ you'll actually type) returns a handle scoped to that container, dropping the repeated `id` first
873
+ argument:
874
+
875
+ ```javascript
876
+ engine.register('live-graph', 'graphDiagram', { nodes: initialNodes, edges: initialEdges });
877
+
878
+ setInterval(() => {
879
+ const parent = engine.registry['live-graph'].options.nodes[Math.floor(Math.random() * n)];
880
+ engine['live-graph'].addGraphNode({ id: `n${++tick}`, label: `Node ${tick}` }, [{ source: parent.id, target: `n${tick}` }], { maxNodes: 30 });
881
+ }, 1500);
882
+ ```
883
+
884
+ Both forms work interchangeably — `engine.addGraphNode(id, ...)` and `engine[id].addGraphNode(...)`
885
+ call the same method underneath. The handle also exposes `register`/`refreshChart`/`addDataPoint`
886
+ the same way. It only resolves for an id already in `engine.registry` (i.e. `register()` must still
887
+ be called the normal way first) — an unregistered id falls through to a real, `undefined` property
888
+ instead of a handle, same as before this existed.
889
+
890
+ ### 3. If you never `register()` a container
891
+
892
+ `addDataPoint`/`addGraphNode` require a prior `register()` call (they look up the chart's type and
893
+ current data from the registry). For a one-off container you don't want to register, own the array
894
+ yourself and re-call the chart method directly — `createDataWindow(streamShape, data, {max})` (also
895
+ exported, see `src/datawindow/index.js`) gives you the same append/trim classes standalone:
896
+
897
+ ```javascript
898
+ import { createDataWindow } from 'renderengine';
899
+
900
+ const window = createDataWindow('flat', initialData, { max: 50 });
901
+ setInterval(() => {
902
+ const data = window.push(() => ({ label: `T${++tick}`, value: randomValue() }));
903
+ engine.lineChart(data, { title: 'Live feed' });
904
+ }, 1500);
905
+ ```
906
+
907
+ ### Performance guidance
908
+
909
+ - Every tick is a full `clear()` + redraw, not a diff — cheap for tens–low-hundreds of points per
910
+ series, but don't drive it faster than the container actually needs to look "live." 1–2 second
911
+ intervals read as real-time to a human; sub-100ms intervals mostly burn CPU redrawing pixels
912
+ nobody perceives changing.
913
+ - If your update source fires faster than you want to redraw (e.g. a WebSocket pushing every
914
+ message), buffer incoming points and flush `addDataPoint` on a `requestAnimationFrame` or fixed
915
+ interval instead of calling it per message.
916
+ - **Canvas** renderer for high point counts or high-frequency updates (5,000+ point ceiling vs
917
+ SVG's ~1,000+); **SVG** if you need native per-point tooltips/hit-testing more than raw throughput.
918
+ - `graphDiagram`'s force layout (`_layoutForce3D`, `iterations` default 150) re-settles the whole
919
+ graph on every redraw — fine for occasional `addGraphNode` calls, but don't drive it on a
920
+ sub-second timer with a large node count; pin node positions (`arrange`-style layouts, see
921
+ `ARCHITECTURE.md`) if you need cheap, jitter-free incremental adds.
922
+
923
+ See `examples/csv-chart-builder.html`'s "Live updates" panels (section 4 for charts, section 8 for
924
+ graphs) for a full working implementation of all of the above, including the per-shape synthetic
925
+ value generators (`LIVE_APPENDERS`) that decide *what* value to invent each tick — legitimately
926
+ demo-specific, unlike the append/trim mechanics above.
927
+
928
+ ## ArchiMate Diagram Editor (OO component)
929
+
930
+ **`examples/archimate.html` is the canvas editor now** (`ArchimateCanvasPart`, type `archimate-canvas`, below under
931
+ "Diagram templates and palette categories"); this section's graph-engine editor is kept in
932
+ `examples/archimate-legacy.html` until the canvas editor has replaced it.
933
+
934
+ **In a workbench: `ArchimateEditorPart`.** The legacy modeller is a workbench part (type
935
+ `archimate-editor`, workspace bundle) built on `EditorPart`: it takes the model as part of its JSON
936
+ spec, fills its pane, and sits in a `<ws-workspace>` beside any other parts. `examples/archimate-legacy.html`
937
+ is this, and nothing else:
938
+
939
+ ```javascript
940
+ const { el, registerBuiltinParts, ArchimateEditorPart } = RenderEngineWorkspace;
941
+ const ws = document.body.appendChild(el({ tag: 'ws-workspace', attrs: { label: 'ArchiMate Modeller', theme: 'light' } }));
942
+ registerBuiltinParts(ws);
943
+ ws.registerPart(ArchimateEditorPart);
944
+ ws.addParts([{ id: 'modeller', type: 'archimate-editor', title: 'Model', elements: [...], relationships: [...], views: [...] }]);
945
+ ws.registerPerspective('Modelling', { type: 'part', partId: 'modeller' });
946
+ ws.activatePerspective('Modelling');
947
+ ws.getPart('modeller').model; // the ArchimateModel (an import replaces it)
948
+ ```
949
+
950
+ Spec options: `elements`, `relationships`, `views` and `name` (as `ArchimateModel` takes them),
951
+ `features` (toolbar controls; all on unless set `false`) and `analysis` (`true` adds the analysis panel).
952
+ The part builds the `ArchimateModel` and hands it to `ArchimateDiagramEditor` (its `model` option), with
953
+ `fullPage: false` and region ids unique to the part, and `ARCHIMATE_SERVICES` (the editor's classes and
954
+ dialogs, imported directly) as its `workspace`.
955
+
956
+ **View settings, warnings and labels on the editor's canvas.** A host hands the editor the current view's
957
+ resolved settings as `viewSettings: () => settingsManager.getEffectiveSettings(view, modelId)`
958
+ (`editor.viewSettings()`, {} without one). The base editor draws the view's background, title band and
959
+ watermark round the drawing from them (`applyViewDecorations` on the element holding the `<svg>`; a view
960
+ without a background goes back to the page's own). `editor.edgeWarning(edge)` says what is wrong with a
961
+ line, `{ message, highlight }` or null: the message is the line's tooltip, and with `highlight` the line is
962
+ drawn red and dashed unless it has its own colour or dashes (class `edge-warning`). The ArchiMate canvas
963
+ editor warns about a relationship the ArchiMate rules do not allow for its version (`ArchimateValidator`), as
964
+ the view's `invalidRelationships` setting says: 'highlight' (the default), 'tooltip' or 'off'. A label
965
+ expression (`${name} (${type})` or `{name}`, on the element's `label` or the instance's `labelOverride`) is
966
+ resolved by the instance (`ElementInstance#getDisplayName`, through `LabelHelper.resolveNodeLabel`), so the
967
+ tree, the dialogs and both editors show the same text.
968
+
969
+ **Properties: the shape, and what it stands for.** The base editor's shape dialog (`editTemplateProperties`:
970
+ name, position, stacking, colours, text) and line dialog (label, sides, then only the style the line sets for
971
+ itself, `edge.styleProperties`) each end with a subclass's own section: `conceptFields(template)` and
972
+ `edgeConceptFields(edge)` give FormDialog fields, and `conceptCommand(template, values)` /
973
+ `edgeConceptCommand(edge, values)` the command applying them; the whole edit is one undo step. The ArchiMate
974
+ canvas editor's sections edit the model element (its own name, type, specialization among those registered for its
975
+ type, icon among its type's choices, documentation, typed properties) and the
976
+ relationship (kind, among those the rules allow and its own; documentation; typed properties), the same on every
977
+ view, through `EditViewItemCommand`; an empty box or list is unset, and a new type drops a stored layer. An
978
+ ArchiMate line's dashes and arrows come from its kind and a plain line's from its type, so neither is offered.
979
+
980
+ **Type pictures.** The model tree shows each element with its type's glyph in its layer colour, as the palette's tiles
981
+ do, and a folder of one type in the Layers tree too (`typeGlyph(typeName, { size, version })` in
982
+ `archimate/typeGlyph.js`, the one drawing both use; a 3.2 type under ArchiMate 4 is drawn as 3.2 draws it).
983
+ `TreeBuilder`'s `iconFor(node)` gives a node a picture in place of its `icon` text.
984
+
985
+ **Nesting.** In the ArchiMate canvas editor, dropping a box wholly inside an element's box nests it there (Archi's
986
+ nested elements): it moves with its parent, is raised above it, and an Archi file writes it inside it (whatever order
987
+ they were added in). Dragged out again, it is taken out. The move and the nesting are one undo step. On a canvas, a
988
+ block holds boxes the same way. Nesting one element in another then asks which relationship it stands for, among
989
+ Archi's nesting kinds (`NESTING_KINDS`: composition, aggregation, assignment, realization, specialization) that the
990
+ rules allow from parent to child, and none when the two already have one; **Add** puts it in the model, drawn as no
991
+ line (the nesting shows it), as its own undo step; **No relationship** adds none. The shared editor does the
992
+ finding: after a move, `DiagramEditor#nestingChanges(ids)` gives each moved shape's new parent (the innermost shape
993
+ that wholly holds it and `canHold` it), and a subclass supplies `parentOf`, `canHold`, `nestCommand`
994
+ (`NestInstanceCommand`) and `nested(changes)`; the template editor has no nesting. Model checks: **Check model…**
995
+ warns of an element drawn inside another with no such relationship (`nested-without-relationship`), and a
996
+ relationship a nesting shows is not reported as unused (`viewNestings(view)`, `nestingRelationships` in
997
+ `diagrams/nesting.js`).
998
+
999
+ **Format painter.** The toolbar's **🖌** turns it on (Archi's Format Painter): with a shape selected it holds that
1000
+ shape's format, otherwise the first shape clicked gives it; every shape clicked after gets it (one undo step each),
1001
+ and nothing is selected or moved meanwhile. **Esc** or 🖌 again turns it off. A shape's menu has **🖌 Copy format**
1002
+ and, once one is held, **Paste format** (to the selection, when the shape is part of it). A format is what
1003
+ `Template#format()` reads from the shape's `static formatKeys` (paint and text style here; an ArchiMate shape's or
1004
+ group's whole appearance), as the shape holds it: a colour it takes from elsewhere (an ArchiMate layer colour) is
1005
+ left unset, so the shape painted gets its own default. `DiagramEditor#copyFormat(id)`, `pasteFormat(ids)` (each
1006
+ shape takes the keys it has; a locked one stops it) and `setFormatPainter(on)`.
1007
+
1008
+ **Properties of several shapes at once.** With more than one shape selected, a selected shape's menu offers
1009
+ **Properties of N shapes** (double-clicking one opens it too): one dialog with the colours and text style
1010
+ (`appearanceFields(template)`, the same fields as one shape's dialog) in which every field starts on **Keep**,
1011
+ greyed out, showing the value the shapes share (or empty, with "Differs between them"). Untick Keep and what you
1012
+ set goes to every shape; the rest stays as each one has it. The whole edit is one undo step; a locked shape in the
1013
+ selection stops it. `DiagramEditor#editSelectionProperties(ids?)` opens it and
1014
+ `selectionPropertiesCommand(templates, values)` is the edit (a value left `undefined` changes nothing); a
1015
+ subclass adds its own section with `manyConceptFields(templates)` and `manyConceptCommand(templates, values)`.
1016
+ The ArchiMate canvas editor's, when every selected shape is an element: their **type**, their **specialization**
1017
+ when they share a type, and **Set property** (a name and value, set where an element has that property and added
1018
+ where it doesn't), from `manyElementsFields` and `manyElementsEditCommands` in `archimate/elementProperties.js`.
1019
+ `PropertyListEditor` (`PropertyListEditor.field(properties, { key })`) is the typed-property list both
1020
+ ArchiMate dialogs use. Both editors fill the specialization catalog the same way:
1021
+ `ArchimateSpecializationRegistry.loadFrom(store)` reads the saved catalog (`SPECIALIZATIONS_STORE`, this browser's),
1022
+ or registers the defaults and saves them.
1023
+
1024
+ **Boxes, the tree and the menus on the canvas.** Every ArchiMate view adds boxes besides elements through
1025
+ `view.addBox(kindKey, at)` (one undo step), from the kinds its class lists in `BOX_KINDS`: Archi's Note and Image
1026
+ on an ArchiMate view (`DIAGRAM_BOX_KINDS`), Block, Sticky and Image on a canvas (`CANVAS_BOX_KINDS`);
1027
+ `canvasBoxMenuItems(view, at)` offers them on right-click in either editor. The canvas editor's element menu adds
1028
+ **Generate View For This** and **Add Missing Relationships (n)** (`missingRelationshipsOf(boxId)`, drawn as one
1029
+ step by `addMissingRelationships`), and ⚙ Settings → **Model settings…** when it has a `settingsManager`. The base
1030
+ editor reports selection changes (`editor.onSelectionChange(({ templateId, edgeId }) => …)`, which returns an
1031
+ unsubscribe); the canvas part keeps the model tree in step with it both ways and shows a canvas's hint, or the
1032
+ selected box's. `SettingsManager#onSettingsChange` returns an unsubscribe too.
1033
+
1034
+ **Specializations and viewpoints of your own.** ⚙ Settings → **Specializations…** and **Viewpoints…**, in both
1035
+ editors, list the user's catalog with Edit and Delete and "+ New …". A specialization has a name, an icon, a
1036
+ colour, a description and the element types it applies to (none: every type); it is saved in
1037
+ `SPECIALIZATIONS_STORE` and offered on elements of those types (`specializationCatalogDialog({ version, store,
1038
+ onChange })`). A viewpoint has a name (not a standard one's), a description and the element types it allows (at
1039
+ least one); it is saved in `CUSTOM_VIEWPOINTS_STORE` (`CustomViewpoints.load()`, `save(list)`,
1040
+ `dialog(options)`) and offered after the standard viewpoints, where it fades or hides the rest and limits the
1041
+ palette like any other. There is no "Custom" placeholder any more. `CatalogDialog` is the list editor both use
1042
+ (`{ title, noun, items, label, fields, validate, save, remove, onChange }`; deleting asks first), and FormDialog's
1043
+ `checklist` field (`choices` `[[value, text, group]]`, reads as the chosen values) is the element type list,
1044
+ grouped by layer as the palette groups them (`elementTypeChoices(version)`, from `elementTypeGroups`).
1045
+
1046
+ **An element's menu in the model tree** (both editors; `elementMenuItems(element, host)`): **Properties…** edits the
1047
+ element itself, the same on every view (`editElementProperties`: its name, type, specialization, icon,
1048
+ documentation and typed properties, one undo step on the open view); **Show in View (n)** lists the views it is on
1049
+ and opens the chosen one with it selected; **Add to "…"**, when the open view lacks it, shows it there in the middle
1050
+ of what is in sight (`visibleCentre()`) with its relationships to what is shown; **Generate View For This**. The
1051
+ element fields are one definition (`elementPropertyFields`, `elementEditCommand`), which the canvas editor's shape
1052
+ dialog also uses for its Element section.
1053
+
1054
+ **Pictures, analysis and filters on the canvas.** A picture file dropped on a view, or pasted, is an image box
1055
+ there (`editor.addImage(dataURL, point)`, one undo step, selected; the base editor's `enableImageDrop`). With
1056
+ `analysis: true` in its spec the canvas part shows the selected element's analysis (views it is in, its
1057
+ relationships) under the palette, for an element chosen on the canvas or in the tree. The tree's own filters (its
1058
+ "Filters…" dialog: text, types, specializations, properties) dim what they leave out on the canvas too:
1059
+ `modelTree.matchingElementIds()` (null with no filter on) and `modelTree.onFilterChange(fn)` feed
1060
+ `editor.setElementFilter`. The analysis names an element's layer as the tree files it (`treeLayerOf`).
1061
+
1062
+ **Groups on the canvas.** An Archi group (a model folder with bounds on the view, `view.placedFolders`) is a
1063
+ `ViewGroupTemplate`: Archi's box with a name tab (`TabbedBoxGlyph`), placed by its centre while its layout entry
1064
+ (`view.folderLayout[id]`: top-left x/y, width, height, zIndex, `appearance`) keeps the corner, so the editor's
1065
+ move, resize, restack and paint commands edit the view and undo there; renaming it renames the folder. Its paint
1066
+ is imported into `appearance` and written back as boxes' is (a change written, the rest as the file had it). A
1067
+ line to a group is drawn. Moving a container moves what it holds, however deep: the base editor's
1068
+ `moversFor` adds `containedIn(id)` (none in the base) for each mover; the canvas editor's groups hold the
1069
+ elements in their folder, the notes and pictures Archi drew in them (`instance.archi.parent`) and the groups
1070
+ inside them, and a canvas block holds the boxes Archi drew in it. Deleting a group takes it off the view
1071
+ (`RemoveGroupFromViewCommand`); what it held stays.
1072
+
1073
+ **Derived relationships on the canvas.** With `nodeOptions.showDerived` (⚙ Settings → Show derived, off by
1074
+ default; the canvas part's spec `showDerived`), the ArchiMate canvas editor draws each relationship
1075
+ `DerivedRelationships.compute(model, { maxHops: 2 })` derives between two elements on the view, as a
1076
+ `DerivedEdge`: dotted, labelled with its chain ("composition→assignment"), with "Derived: …" as its tooltip. It
1077
+ is drawn only: `edge.editable` is false, so the editor neither selects, routes nor deletes it (deleting a box
1078
+ leaves it alone). Any `Edge` can say so (`editable`, default true) and add to its tooltip (`tooltip`).
1079
+
1080
+ **Selecting several shapes.** In the base editor (so the template editor and the ArchiMate canvas alike): drag a
1081
+ box on empty canvas to select the shapes wholly inside it (Ctrl or Cmd adds them to the selection;
1082
+ `editor.templatesInside(rect)`, `editor.selectTemplates(ids, { extend })`); Shift- or Ctrl-click adds one;
1083
+ **Ctrl+A** selects every shape that is drawn (`selectableIds()`). Whatever is selected acts together, each as one
1084
+ undo step: dragging any of them moves them all (with their groups and what they hold); the arrow keys nudge
1085
+ them a grid step, one pixel without a grid, ten times as far with Shift (`nudgeSelection(dx, dy)`); **Delete**
1086
+ removes them once confirmed, and every line joined to any of them once (`deleteSelection()`,
1087
+ `deleteTemplatesCommand(ids)`; a subclass gives the per-shape part, `removeTemplateCommand(id)`, and the step's
1088
+ name, `deleteLabel(ids)`); grouping, locking and the Arrange menu already took the selection. A fixed shape among
1089
+ them stops a move or a delete.
1090
+
1091
+ **One undo history per model.** As in Archi, a model has one undo stack, `model.history` (a `ModelHistory`),
1092
+ that all its views share (`view.history` is it): every edit on any view, and the model's own changes, in the order
1093
+ made. Adding a view (New, Duplicate, Generate View, a new canvas, a model template), renaming one and deleting one
1094
+ are undo steps too (`AddViewCommand`, `RenameViewCommand`, `RemoveViewCommand`), as are a view's name and
1095
+ documentation from its Properties…. Each step remembers the view in front when it was made, and undoing or redoing
1096
+ it brings that view to the front, so the change is seen (Ctrl+Z on one tab can undo an edit made on another).
1097
+ Unsaved changes follow the one history (`isDirty()`). A model a part has just made or opened starts with nothing
1098
+ to undo.
1099
+
1100
+ ```js
1101
+ const model = new DiagramModel();
1102
+ const a = model.createView('A'); // one undo step
1103
+ model.renameView(a.id, 'First'); // another
1104
+ model.history.undo(); // 'A' again
1105
+ model.history.undo(); // the view is gone
1106
+ ```
1107
+
1108
+ **Find and replace.** Every `EditorPart` (the ArchiMate canvas part and the template editor) has an **Edit** menu
1109
+ after File: **Undo**, **Redo** and **Find and replace…**, over the whole model (`model.search()`, a
1110
+ `ModelSearch`, as `checker()` and `report()` are). The dialog (`FindReplaceDialog`) lists what matches as you
1111
+ type, with **Match case**, **Whole words**, **Regular expression** (`$1` in the replacement is its first group)
1112
+ and **Look in** names, documentation and property values; choosing a match shows it (its view, then the item
1113
+ selected), and **Replace all** replaces every match as one undo step on the model's history. What is
1114
+ searched: the model's name, documentation and properties and each view's title (never emptied), documentation and
1115
+ properties (`ModelSearch`); in the template editor also each shape's name and line's label
1116
+ (`DiagramModelSearch`); in an ArchiMate model also every element's and relationship's name, documentation and
1117
+ properties, and the notes on each view (`ArchimateModelSearch`). A property whose value is a number or true/false is
1118
+ left out. A notation's search adds its own texts with `viewItemTargets(view)` and `conceptTargets()`.
1119
+
1120
+ ```js
1121
+ const search = model.search();
1122
+ search.find('customer', { wholeWord: true, fields: ['name'] }); // [{ kind: 'element', id, owner, fieldLabel: 'Name', text, count, target }, ...]
1123
+ const cmd = search.replaceCommand('(\\w+) DB', 'DB of $1', { regex: true }); // a ReplaceTextCommand, or null
1124
+ model.currentView.history.execute(cmd); // one undo step
1125
+ ```
1126
+
1127
+ **Transform names, rename a property, fit to text.** The **Edit** menu has **Transform names…**: Title Case,
1128
+ Sentence case, split camelCase (OrderService → Order Service), camelCase, remove extra spaces, UPPER or lower case,
1129
+ on every name in the model or on those containing some text (`NAME_TRANSFORMS`, `transformName(key, name)` in
1130
+ `diagramModel/nameTransforms.js`; `model.search().transformCommand(key, text)`), one undo step, a view never left
1131
+ without a name. **Rename property…** renames a property everywhere it is (the model, its views, and an ArchiMate
1132
+ model's elements and relationships), one undo step; where something has both names, the old value fills the new one
1133
+ only if that is empty (`search.renamePropertyCommand(from, to)`, `search.propertyNames()`, over
1134
+ `search.propertyHolders()`, which a notation's search extends). Several elements' Properties also has **Remove
1135
+ property** (one any of them has). A shape's menu has **Fit to text**: the shape (or the selection it is part of) sized
1136
+ to its label, keeping its top-left corner, one undo step (`DiagramEditor.textSize(text, fontPixels, {padding, min})`
1137
+ estimates it; `editor.textFit(template)` gives the room a shape needs, more for an ArchiMate element's badge).
1138
+
1139
+ **HTML report.** File → **Export HTML report** (every `EditorPart` whose model has `report()`: the ArchiMate
1140
+ canvas part and the template editor) saves the whole model as one HTML file that opens in any browser, with nothing
1141
+ else to load: a tree on the left (Elements by layer, Relationships by kind, Views), a search box that keeps what
1142
+ matches by name, type or documentation, and a page per item. A view's page has its picture, and clicking an element
1143
+ on it opens that element's page; an element's page has its type, layer, documentation, properties, relationships
1144
+ (each with the element at the other end) and the views it is on; a relationship's links its source and target. The
1145
+ address says which page (`#element:id`), so Back and links work.
1146
+
1147
+ The parts, each usable alone:
1148
+
1149
+ ```js
1150
+ import { ArchimateModel } from './src/charting/diagrams/ArchimateModel.js';
1151
+ import { reportHtml, reportFileName } from './src/charting/report/reportHtml.js';
1152
+
1153
+ const model = new ArchimateModel({
1154
+ name: 'Shop',
1155
+ elements: [{ id: 'a', name: 'Customer', type: 'business-actor' }, { id: 'b', name: 'Order app', type: 'application-component' }],
1156
+ relationships: [{ id: 'r', kind: 'serving', source: 'b', target: 'a' }],
1157
+ views: [{ id: 'v', name: 'Main', elementIds: ['a', 'b'] }],
1158
+ });
1159
+ const data = model.report().data(); // plain JSON: { title, home, sections, items }
1160
+ data.items['element:a'].lists; // [{ title: 'Relationships', rows: [{ key: 'relationship:r' }] }, { title: 'Views', ... }]
1161
+ const html = reportHtml(data); // the file's text
1162
+ reportFileName(data.title); // 'shop-report.html'
1163
+ ```
1164
+
1165
+ `model.report({ pictures, title })` is a `ModelReport` (`src/charting/diagramModel/`): the model's page and a page
1166
+ per view, for any `ViewSetModel`; `ArchimateModel` returns an `ArchimateModelReport`, which adds the elements and
1167
+ relationships. A subclass adds items with `conceptItems()`, tree sections with `conceptSections()`, and a view's
1168
+ facts and lists with `viewFacts(view)` / `viewLists(view)`. Pictures come from the editor:
1169
+ `DiagramEditor#viewPicture()` is the current view as SVG cropped to what is drawn (no grid, selection or zoom), with
1170
+ a box per shape whose `reportRef(template)` names its page (`{kind: 'element', id}` in the ArchiMate editor);
1171
+ `part.viewPictures()` shows each view in turn to collect them. The page is written from `el()` specs by
1172
+ `toHtml(spec)` (`src/workspace/dom.js`: the same spec, as escaped HTML text), and its script is `reportViewer`.
1173
+
1174
+ **The File menu, grouped.** Every editor part's File menu (`EditorPart#fileMenuItems`) is: **New** ▸ (a view or
1175
+ diagram, a canvas, from a template, a model), Open…, Save, Save As… (with a ModelsPart also Open… in a new tab and
1176
+ Close model), **Import** ▸ (each format that imports, then the part's own: an Archi template, CSV with mapping),
1177
+ **Export** ▸ (each format, the part's own, then the HTML report and the pictures), the part's own groups (the canvas
1178
+ part's **Templates** ▸), and **This view** ▸ (its properties, duplicate, delete). A group of one item is that item
1179
+ (named as the group says it: "Export HTML report"). A part adds to it with `newItems()`, `ownImportItems()`,
1180
+ `ownExportItems()` and `ownFileItems()`; `EditorPart.menuGroup(label, items, { prefix })` and
1181
+ `EditorPart.tidyMenu(items)` make the groups; `modelFileMenuItems(formats, { only: 'export' | 'import' })` gives
1182
+ one direction's items.
1183
+
1184
+ **Open, save, and unsaved changes.** An editor part with file formats (`EditorPart`, the ArchiMate canvas part) has
1185
+ **Open…**, **Save** and **Save As…** at the top of File (**Ctrl+O**, **Ctrl+S**, **Ctrl+Shift+S**), kept by its
1186
+ `part.file` (`ModelFile`). Open asks for a file and takes the first of the part's formats whose extension and
1187
+ contents fit (`format.acceptsName(name)`, `format.recognizes(data)`: a `.xml` is Archi's or the Exchange Format by its
1188
+ namespace); Save writes back to that file where the browser can (the File System Access API: Chrome and Edge),
1189
+ otherwise it is Save As, which asks where (or, with no save dialog in the browser, downloads) in the open file's
1190
+ format or the part's `saveFormat()` (Archi's for the canvas part). The part knows whether the model changed since it
1191
+ was opened or saved: `model.markSaved()` / `model.isDirty()` / `model.onEdit(fn)` (`ViewSetModel`), from the model's
1192
+ one history (`CommandHistory#stateToken`, the same whenever it is back in a state, so undoing back is clean again),
1193
+ views added, renamed or deleted included. `part.fileLabel` ("shop.archimate •") shows in the part's toolbar and the page
1194
+ title, and leaving the page with changes unsaved asks first. An import is not a file: Save then asks where.
1195
+ `file.open()` is `file.choose()` (ask for a file and read it as a model, opening it nowhere yet) then
1196
+ `file.adopt(chosen)`.
1197
+
1198
+ **Several models open at once.** A `ModelsPart` (type `models`, one of `BUILTIN_PARTS`) holds one editor part per
1199
+ model, each a tab of a work area (`WorkArea`), so each keeps its own file, unsaved state and undo history, and copy
1200
+ and paste go between them. Its spec names the editor part type and what to open: `{ id, type: 'models', editorType:
1201
+ 'archimate-canvas', models: [editor part spec, ...], newModel: editor part spec }`. An editor part it hosts
1202
+ (`part.modelsHost`) has **New model**, **Open… in a new tab** (with file formats: the file is read first, and the
1203
+ tab is made only for a model) and **Close model** at the top of File. A tab shows what the model is called (its file's name, else the model's name, as the toolbar does) and
1204
+ • with unsaved changes; closing one that has them asks first (its × too); the last one closed leaves a new, empty
1205
+ model; the page title follows the model in front. Keys an editor listens for on the page (undo, delete, Ctrl+S, ...)
1206
+ act only for the one on screen (`Part#isShown()`, `DiagramEditor#isShown()`). `examples/archimate.html` and the
1207
+ template editor's testbed host their editor this way:
1208
+
1209
+ ```js
1210
+ ws.addParts([{ id: 'models', type: 'models', editorType: 'archimate-canvas', newModel: { analysis: true },
1211
+ models: [{ id: 'modeller', title: 'Sample model', elements: [...], views: [...] }] }]);
1212
+ ws.registerPerspective('Modelling', { type: 'part', partId: 'models' });
1213
+ ```
1214
+
1215
+ **Align, distribute, match size, in every editor.** With two or more shapes selected, a shape's menu and the
1216
+ toolbar's **Arrange** menu (there always, greyed out with too few) offer **Align** (Left, Centre, Right, Top,
1217
+ Middle, Bottom), **Distribute** (Horizontally, Vertically; three or more) and **Match size** (Width, Height, Width
1218
+ and height). Align and match size follow the primary shape (the one selected last), as in Archi; distribute keeps
1219
+ the outermost two and makes the gaps between neighbours equal; match size keeps each shape's top-left corner. Each
1220
+ is one undo step; what a moved container holds moves with it; a locked shape stops it. `DiagramEditor` has
1221
+ `alignSelection(edge)`, `distributeSelection(direction)`, `matchSelectionSize(dimension)` and
1222
+ `alignMenuItems()`; where the shapes go is worked out from their boxes alone by `align`, `distribute` and
1223
+ `matchSize` (`src/charting/arrangement/alignment.js`), which return just the boxes that change:
1224
+
1225
+ ```js
1226
+ import { align, distribute, matchSize } from './src/charting/arrangement/alignment.js';
1227
+ const boxes = [{ id: 'a', left: 0, top: 0, width: 100, height: 50 }, { id: 'b', left: 150, top: 40, width: 60, height: 30 }, { id: 'c', left: 400, top: 10, width: 120, height: 80 }];
1228
+ align(boxes, 'top', boxes[1]); // [{ id: 'a', left: 0, top: 40 }, { id: 'c', left: 400, top: 40 }]
1229
+ distribute(boxes, 'horizontal'); // [{ id: 'b', left: 220, top: 40 }]
1230
+ matchSize(boxes, 'width', boxes[0]); // [{ id: 'b', width: 100, height: 30 }, { id: 'c', width: 100, height: 80 }]
1231
+ ```
1232
+
1233
+ **Import CSV with mapping.** File > **Import CSV with mapping…** (the ArchiMate canvas part) adds a spreadsheet's
1234
+ rows to the open model (it never replaces it): choose which column holds the name, the type (any case, a title, or
1235
+ CamelCase as Archi's elements.csv writes it; or one type for every row), an id, the documentation and which columns
1236
+ are properties, and optionally one whose cells name (or give the ids of) elements each row's element is related to,
1237
+ with the kind and direction. A row whose id, or name and type, is an element the model has updates its
1238
+ documentation and properties instead of making a second; rows naming the same element are one; a relationship the
1239
+ model has is not added twice, nor one the rules do not allow. The new elements can go on the open view, in a grid.
1240
+ One undo step; rows that could not be read (no name, an unknown type, an element to relate to that is not there)
1241
+ are listed after. The dialog suggests the columns from their names (Name, Type, ID, Documentation; the rest as
1242
+ properties), so Archi's elements.csv imports as it is. The CSV reader (`parseCsvTable`, `parseCsvRecords`,
1243
+ `guessDelimiter` in `src/csvTable.js`, which the CSV file format uses too) takes quoted cells with line breaks
1244
+ and commas, semicolons or tabs. `planCsvImport(table, mapping, model)` (`archimate/csvMapping.js`) plans it and
1245
+ `ImportRowsCommand` carries it out:
1246
+
1247
+ ```js
1248
+ const table = parseCsvTable(text);
1249
+ const plan = planCsvImport(table, { name: 'Name', type: 'Type', properties: ['Owner'], related: 'Uses', relatedKind: 'serving', relatedDirection: 'in' }, model);
1250
+ model.history.execute(new ImportRowsCommand(model, plan, { view: model.currentView })); // plan.problems: [{row, message}]
1251
+ ```
1252
+
1253
+ **draw.io export.** File > **Export draw.io (.drawio)** (the ArchiMate canvas part) saves every view as a page of one
1254
+ draw.io (diagrams.net) file, in draw.io's own ArchiMate 3 shapes: each box with its place, size, colours (its
1255
+ layer colour when it has none of its own) and text placement, a box nested in another as its child cell, notes,
1256
+ groups and canvas boxes; each line with its relationship's look (access by its access type) and its bendpoints. An
1257
+ element's or relationship's id, type, documentation and properties go on its cell's object, so draw.io keeps them.
1258
+ It is export only (`ModelFileFormat#imports` is false: no Import item, and Open… does not offer it).
1259
+ `toDrawioXML(model, { viewIds })` (`src/archimateDrawio.js`) writes it; `DRAWIO_ELEMENT_STYLES` and
1260
+ `DRAWIO_RELATIONSHIP_STYLES` are the type-to-shape tables.
1261
+
1262
+ **Roadmaps.** On the ArchiMate canvas, **View > Generate roadmap…** makes a new view of elements on a time line:
1263
+ each element with a start and an end date (by default its "Start Date" and "End Date" properties; ISO dates, a
1264
+ year and month, or day/month/year) is a bar from one to the other, under a header of years and months (short or
1265
+ full names), with month columns behind; bars that overlap in time go in lanes below each other. It takes the
1266
+ selected elements, or every element in the model with both dates; those without them are left out. The view comes
1267
+ to the front; one undo step. `model.generateRoadmap(elementIds, { startProperty, endProperty, name, monthNames })`
1268
+ returns `{ view, skipped }`; where everything goes is `roadmapPlan(items, options)` and dates are read by
1269
+ `parseDate(text)` (`src/charting/diagrams/roadmap.js`):
1270
+
1271
+ ```js
1272
+ const { view, skipped } = model.generateRoadmap(model.elements().map(e => e.id), { startProperty: 'From', endProperty: 'To' });
1273
+ ```
1274
+
1275
+ A note's own fill colour is drawn (it was always yellow); one without keeps the yellow sticky look.
1276
+
1277
+ **Add from text, in every editor.** Right-click the canvas → **Add from text…**: paste a list of names (from a
1278
+ document, a spreadsheet, a mail) and choose their kind, and a shape is made for each, placed in a grid from where
1279
+ you clicked, selected, one undo step. A name a line; a tab after it starts its documentation; blank lines are
1280
+ skipped and bullets and numbering ("- ", "• ", "1. ", "2) ") taken off. A first line `types=<kind>,<kind>,...`
1281
+ makes each tab-separated column that kind and each line a row of shapes (a kind by its key or its name, any case:
1282
+ `types=Business Actor,application-component`). The template editor makes its palette's shapes; the ArchiMate
1283
+ canvas makes new model elements with their boxes (the kinds its viewpoint allows; not on a canvas, whose boxes
1284
+ are not elements). `parseShapeText(text, {defaultType, resolveType})` and `placeShapes(parsed, at)`
1285
+ (`src/charting/textToShapes.js`) read and place the text; `DiagramEditor#addFromText(text, type, at)`,
1286
+ `shapeTypes()` and `createShapesCommand(items)`, which a subclass overrides to make its own kind of shape.
1287
+
1288
+ **Colour by, and legends, in every editor.** The **View** menu has **Colour by…** (a heat map): choose what to
1289
+ colour by, then a colour for each of its values (a soft palette by default, `DiagramEditor.COLOUR_PALETTE`; "No
1290
+ colour" leaves a value's shapes as they are), and whether to add a legend. Each shape takes its value's colour as
1291
+ its own fill, so the colours are kept like any other (an Archi file included), and one undo puts them back. The
1292
+ template editor colours by the kind of shape; the ArchiMate canvas by **Layer**, **Type**, **Specialization** or any
1293
+ **property** its elements have (notes and pictures have no value and stay as they are). A legend is a title and a
1294
+ box per value in its colour, in a row under the drawing (notes; on a canvas, stickies). **Add legend of the kinds
1295
+ shown** adds one for the kinds of shape (ArchiMate: the element types) in the colour of each's first shape. A
1296
+ locked shape keeps its colour. `colourKeys()`, `colourValue(template, key)`, `colourValues(key)`,
1297
+ `colourByCommand(key, colours, { legend })`, `kindLegendEntries()` and `legendCommand(entries, title)`, which a
1298
+ subclass overrides to say what its shapes are coloured by and what its legend boxes are:
1299
+
1300
+ ```js
1301
+ editor.colourByCommand('property:Lifecycle', { invest: '#98fb98', maintain: '#ffc300', retire: '#ffa07a' }, { legend: true });
1302
+ ```
1303
+
1304
+ **Layouts, in every editor.** The toolbar's **Arrange** menu has **Layout** (**Layout the view** with fewer than two
1305
+ shapes selected): **Grid**, **Circle**, **Tree, top down**, **Tree, left to right** (along the lines between the
1306
+ shapes: a parent centred over its children; a shape no line reaches is a root), **Force-directed** (lines pull,
1307
+ shapes push apart; the same input gives the same result) and **By kind, a row each** (the shape class in the
1308
+ template editor, the layer on the ArchiMate canvas). The shapes keep the top-left corner of the area they took;
1309
+ what a container holds goes with it; one undo step; a locked shape stops it. On the ArchiMate canvas, an element
1310
+ with parts on the view (the targets of its composition and aggregation relationships) has **Nest its parts inside**
1311
+ in its menu: each part nested in it, theirs in them however deep, in a grid below its label, the parent grown to
1312
+ hold them. `DiagramEditor#layoutShapes(kind, ids?)`, `layoutCategory(template)`, `partsOf(id)` and
1313
+ `nestPartsInside(id)`; where shapes go comes from `layout(kind, boxes, {edges, categoryOf, gap})` and
1314
+ `nestLayout(rootId, partsOf, boxOf)` (`src/charting/arrangement/layouts.js`), from the boxes alone:
1315
+
1316
+ ```js
1317
+ import { layout } from './src/charting/arrangement/layouts.js';
1318
+ const boxes = [{ id: 'a', left: 0, top: 0, width: 100, height: 50 }, { id: 'b', left: 0, top: 100, width: 100, height: 50 }, { id: 'c', left: 0, top: 200, width: 100, height: 50 }];
1319
+ layout('tree-down', boxes, { edges: [{ source: 'a', target: 'b' }, { source: 'a', target: 'c' }], gap: 40 });
1320
+ // [{ id: 'a', left: 70, top: 0 }, { id: 'b', left: 0, top: 110 }, { id: 'c', left: 140, top: 110 }]
1321
+ ```
1322
+
1323
+ **Copy and paste, in every editor.** **Ctrl+C** copies the selection, **Ctrl+X** cuts it (one undo step), **Ctrl+V**
1324
+ pastes (a little further along each time, selected), **Ctrl+D** duplicates without touching the clipboard; a shape's
1325
+ menu has Cut, Copy and Duplicate and the canvas menu **Paste here** (its top-left corner where you clicked). The
1326
+ clipboard is the page's (`DiagramClipboard`), so a copy pastes into another view, tab or model; an editor pastes only
1327
+ copies of its own kind (`static clipboardKind`: 'diagram' for the template editor, 'archimate' for ArchiMate views).
1328
+ An editor gives what a copy is (`copyPayload(ids)`) and how it is made again (`pasteCommand(payload, { dx, dy })`):
1329
+ the template editor copies each shape (its class and `serialize()`) and the lines between them; an ArchiMate view
1330
+ copies its items (`view.toJSON({ itemIds })`: boxes, the lines and relationships between them, the elements and
1331
+ relationships they show) and pastes them as new boxes for the **same** elements (`view.pasteCommand(data)`,
1332
+ `PlaceItemsCommand`), adding to the model what it lacks (a paste from another model) and to a view's element list
1333
+ what it does not list. Groups are not copied (they are folders).
1334
+
1335
+ **Mouse buttons, in every editor.** A left drag on empty canvas draws the selection box; the **right button drags
1336
+ the view** (a right-click that does not move still opens the menu), as do the middle button and Shift+left (Alt+left
1337
+ in the main ArchiMate editor). In the main ArchiMate editor (the graph engine) the box selects nodes too
1338
+ (`graph.selectNodes(ids, { extend })`, kept on the engine across redraws and marked with a glow), and dragging one of
1339
+ them moves them all as one undo step.
1340
+
1341
+ **Emphasis: highlight, viewpoint, filter.** A shape or line can be drawn as usual, dimmed (opacity 0.3),
1342
+ faded (0.2) or not at all (hidden), and only how it is drawn changes. Each reason is an `EmphasisRule`
1343
+ (`template(template, editor)` and `edge(edge, editor)` give `null`, `'dimmed'`, `'faded'` or `'hidden'`);
1344
+ `editor.emphasisRules()` lists the active ones, and the strongest level any of them gives wins
1345
+ (`templateEmphasis`, `edgeEmphasis`; a line to a hidden shape is hidden). A dimmed or faded item is drawn in
1346
+ a `<g opacity data-emphasis>` with its `data-template-id` or `data-edge-id`. The base editor's rule is the
1347
+ highlight: **View → Highlight…** (`editor.setHighlight(text)`, `HighlightRule`) dims every shape whose name
1348
+ does not contain the text, in any case, in the template editor and the ArchiMate canvas alike. The ArchiMate canvas
1349
+ editor adds `ViewpointRule`: with `nodeOptions.viewpoint` (⚙ Settings → Viewpoint), elements of types the
1350
+ viewpoint does not allow are faded, or hidden with `viewpointMode: 'hide'` (⚙ Settings → Outside the
1351
+ viewpoint), a line with one end outside is faded and one with both outside hidden; notes and canvas boxes are
1352
+ left alone. It also adds `ElementFilterRule` (`editor.setElementFilter(elementIds)`, null clears), which
1353
+ dims the model elements a filter did not keep. The palette then offers only the types the viewpoint allows
1354
+ (`new ArchimatePalette({ viewpoint })`).
1355
+
1356
+ ```js
1357
+ editor.setHighlight('order'); // the rest dimmed
1358
+ canvasEditor.nodeOptions = { ...canvasEditor.nodeOptions, viewpoint: 'Organization', viewpointMode: 'fade' };
1359
+ canvasEditor.templateEmphasis(appComponentShape); // 'faded'
1360
+ ```
1361
+
1362
+ **View menu, image export, model check, generated views.** These live in the `DiagramEditor` base, so the
1363
+ template editor (the testbed) and both ArchiMate editors have them. The **View** menu
1364
+ (`editor.viewMenuItems()`) has **Zoom to fit** (`editor.zoomToFit()`: everything drawn in sight, in the
1365
+ drawing's own units; `ViewFit` computes it, as a viewBox for the graph engine or a scale and pan for the
1366
+ template editor), **Reset zoom**, **Highlight…** (below), and, when the editor has a `model`, **Check model…**
1367
+ (`editor.showModelCheck()`). The checks are the model's own (`model.checker(options)`): a `ViewSetChecker` for
1368
+ any view set (empty views; an empty template is not one), and for an ArchiMate model an `ArchimateModelChecker`
1369
+ that adds relationships the specification does not allow, likely duplicates (elements of one type whose names
1370
+ have the same words, ignoring case, punctuation, word order and filler words such as "of" and "the":
1371
+ `nameKey(name)` and `duplicateGroups(elements)` in `diagrams/duplicates.js`), elements and relationships on no
1372
+ view, nested elements with no relationship, and names or documentation with unprintable characters (control
1373
+ characters, zero-width spaces; tabs and line breaks are fine in documentation). Each check is a method listed by
1374
+ `checks()`, so a subclass changes the set; choosing a finding shows it (`editor.revealFinding(finding)`: its view,
1375
+ its shape, or in the ArchiMate tree), and a finding can offer actions beside it (`editor.findingActions(finding)`).
1376
+ On the ArchiMate canvas, likely duplicates have **Merge…**: choose the element to keep (each choice says how many
1377
+ views and relationships use it) and the others are merged into it, one undo step (`model.mergeCommand(keepId,
1378
+ otherIds)`, a `MergeElementsCommand`). The kept element takes their relationships, their boxes on every view, the
1379
+ documentation and properties it lacks and a specialization if it has none; a relationship left joining it to
1380
+ itself, or the same as another, goes, its lines drawn as the other. The **File** menu (`EditorPart`'s, and the ArchiMate editor's) exports the current view
1381
+ as an image: PNG, SVG or a copy to the clipboard (`editor.exportImage('png' | 'svg' | 'copy')`, without the grid
1382
+ or selection; the ArchiMate editor's is off with `features.exportImage: false`). **Generate View For This**, on
1383
+ an element's menu in the diagram or the tree, makes a view of the element with everything related to it
1384
+ around it and every relationship between them (`model.generateView(elementIds, { name, related })`, Archi's
1385
+ "Generate View For").
1386
+
1387
+ ```js
1388
+ model.checker({ version: '3.2' }).run(); // [{ code, severity, kind, ids, message }, ...]
1389
+ model.generateView(['customer']); // a new current view: the customer and what it relates to
1390
+ ```
1391
+
1392
+ **Properties of an element, note or line.** Double-click it, or **Show/Edit Properties…** on its menu. The
1393
+ dialog's Object section is the concept (name, type, documentation as "description", specialization,
1394
+ properties), the same on every view; its Diagram section is how this view draws it: label, fill colour, line
1395
+ colour, text colour and icon, and a **Text** section from `TextStyle` (font, text size, bold, italic, alignment,
1396
+ position; the same fields the template editor shows). Bold and italic go to Archi in its font; Archi has no
1397
+ underline, so it is offered for template shapes only. Save
1398
+ keeps the changes where they live: the concept's in the model (a note's, image's or plain line's in the item
1399
+ itself), the rest on the view's instance, as one undo step on the view (`EditViewItemCommand`;
1400
+ `editor.editItemProperties(item, kind)` opens it, `viewItemChanges(kind, before, after)` sorts an edit). A
1401
+ line's colour is this view's. "No colour" (`none`) draws no fill or no outline; an element's icon then stays
1402
+ black.
1403
+
1404
+ **Views.** Right-click a view's tab, or its node in the model tree, for **Properties…**, **Duplicate**
1405
+ and **Delete** (the last view stays); the File menu starts with **New view…** and the current view's
1406
+ items. Double-clicking empty canvas, or the canvas menu's **View Properties…**, opens the same
1407
+ properties. `SettingsViewPropertiesDialog` edits the view's name (through `model.renameView`) and
1408
+ documentation, then the view's level of the settings cascade: routing, grid size and grid shown under
1409
+ Diagram, and background colour and image, a title band (text, position) and the watermark under
1410
+ Appearance. A cascading setting may inherit, and shows the value and the level it would come from. The diagram draws the view's resolved settings (background, title band, watermark).
1411
+ A filter (the tree's Filters…) is matched by model element, so it treats every view, a duplicate
1412
+ included, the same way, and never dims notes or images.
1413
+ A duplicate is independent: every element and relationship instance is copied with a new id, each
1414
+ relationship re-linked to the copied ends, with the element filter, viewpoint, documentation, view
1415
+ settings and folder layout; its undo history starts empty. View documentation round-trips through
1416
+ Archi export and import. The editor follows its model, so the same happens however a view is
1417
+ changed: `editor.model.duplicateView(id)` opens and shows the copy, deleting a view closes its tab.
1418
+
1419
+ **Canvases and templates.** As in Archi, canvases (`CanvasView`) have a **Canvases** folder of their own in
1420
+ the model tree, keeping Archi's sub-folders, and **Views** lists only the other views (`model.diagramViews()`,
1421
+ `model.canvases()`, `model.templates()`). The **Templates** folder holds the model's own templates (canvases
1422
+ marked `template`, saved in the `.archimate` file) and a **Library** of templates kept across models: a
1423
+ `CanvasTemplateLibrary` (a `TemplateLibrary`, the base any kind of view's library extends: entries, built-ins
1424
+ `DEFAULTS`, storage under `STORAGE_KEY`, change events), by default in this browser's storage (the editor's `templateLibrary` option takes
1425
+ another, or `null` for none). Right-click Canvases for **New canvas…** and **New canvas from template**; a
1426
+ canvas for **Save as model template…** and **Save to template library…**; a template for **New canvas from
1427
+ this**; Templates or Library for **Import Archi template…**, which reads Archi's `.archicanvas` and
1428
+ `.architemplate` files (a zip of a manifest, a thumbnail and the template's model, pictures included) into the
1429
+ library. The File menu offers the same. `CanvasTemplateMenu` builds these items, as `ViewMenu` does for views;
1430
+ its `askName`, `chooseFile` and `report` options replace the dialogs. A canvas made from a template gets new
1431
+ ids and the template's boxes and lines; the boxes become the canvas's background (a locked `BackgroundGroup`,
1432
+ behind everything added later; see "A background" under grouping and locking).
1433
+
1434
+ **Drawing on a canvas: Archi's palette, lines and hints.** On a canvas, right-click empty space for **Add
1435
+ Block**, **Add Sticky** and **Add Image…** (a picture file): the box is centred where you clicked, one undo step
1436
+ (`canvas.addBox(kind, at, { name, iconUrl })`; the kinds, with Archi's classes and looks, are `CANVAS_BOX_KINDS`,
1437
+ each a `CanvasBoxKind`; `canvasBoxMenuItems(view, at)` is the one menu definition both ArchiMate editors use).
1438
+ A line drawn between two boxes is a canvas connection (Archi's `CanvasModelConnection`), not a relationship:
1439
+ which a line is, is the view's rule (`view.linesAreRelationships(a, b)`: both ends model elements), so a line
1440
+ from a note on an ArchiMate view is a plain line too (`view.connect(a, b)` draws one, `view.removeRelationship(id)`
1441
+ takes it off; one undo step each). A **Hints** panel in the drawing's corner (`HintPanel`, folds to its header)
1442
+ shows the canvas's hint, and a box's own when it is clicked (`canvas.hint`, `canvas.hintFor(id)`; `ArchiHint`
1443
+ reads Archi's `hintTitle`/`hintContent` from any record and turns the HTML into plain text: it is never
1444
+ inserted as HTML). **Edit Hint…** on a box, and **Edit Canvas Hint…** on empty canvas, set a hint as a title and
1445
+ plain text, one undo step (`EditHintCommand`, on anything with an `archi` record; `ArchiHint.write` stores the
1446
+ text as escaped HTML paragraphs, as Archi keeps it; `hintMenuItems(view, itemId)` is the one menu definition). In
1447
+ the canvas editor, a canvas's side palette is Archi's (`CanvasPalette`, a kind of `ArchimatePalette`: Block,
1448
+ Sticky, Image, clicked or dragged in), and its plain lines are drawn and edited like any line (`ViewLineEdge`).
1449
+
1450
+ ```js
1451
+ const goal = canvas.addBox('block', { x: 300, y: 200 }, { name: 'Goals' });
1452
+ const idea = canvas.addBox('sticky', { x: 600, y: 200 }, { name: 'Idea' });
1453
+ canvas.connect(idea.id, goal.id); // a CanvasModelConnection
1454
+ canvas.hintFor(goal.id); // { title, content, text } or the canvas's
1455
+ ```
1456
+
1457
+ **Placing a template, and saving part of a canvas.** Right-click empty canvas for **Place template ▸** (the
1458
+ model's templates, then the library's): its boxes and lines go where you clicked, as their own locked
1459
+ background group, one undo step (`canvas.placeTemplate(source, { at, name })`, a `PlaceItemsCommand`), in front
1460
+ of any earlier background and behind everything else. Right-click a box for **Save as template ▸** (model or
1461
+ library): the box with its group (in the canvas editor, the selection with its groups), and the lines between
1462
+ them (`canvas.toJSON({ itemIds })`, `model.saveAsTemplate(viewId, title, { itemIds })`; `Arrangement#only(ids)`
1463
+ keeps their groups and locks). `CanvasTemplateMenu` builds these items (`placeTemplateItems(viewId, at)`,
1464
+ `saveAsTemplateItems(viewId, { itemIds })`) for both ArchiMate editors; the template editor's base offers the
1465
+ hooks (`shapeMenuExtras(id)`, `canvasMenuItems(at)` for empty canvas).
1466
+
1467
+ ```js
1468
+ canvas.placeTemplate(library.get('builtin-kanban').view, { at: { x: 400, y: 300 } }); // locked, behind
1469
+ model.saveAsTemplate(canvas.id, 'Lanes', { itemIds: [laneA, laneB] }); // a model template of two boxes
1470
+ ```
1471
+
1472
+ The library starts with built-in templates, so there is something to choose before anything is imported:
1473
+ SWOT analysis, PESTLE analysis, Stakeholder map (power and interest), Priority matrix (urgent and
1474
+ important), Retrospective, Kanban board and Architecture decision (`DEFAULT_CANVAS_TEMPLATES`). They are
1475
+ written for RenderEngine; each block carries a hint (Archi's `hintTitle`/`hintContent`), so a canvas made
1476
+ from one explains itself in Archi too. Built-ins come first, are marked `builtIn`, are not stored, and cannot
1477
+ be renamed or removed; `new CanvasTemplateLibrary({ defaults })` replaces them, and `defaults: []` gives none.
1478
+ `canvasTemplate({ id, name, description, blocks })` makes an entry from a list of blocks.
1479
+
1480
+ ```js
1481
+ const library = new CanvasTemplateLibrary(); // or { store: { load(fallback), save(value) } }
1482
+ model.newCanvasFromTemplate(library.get('builtin-swot').view, 'Our SWOT');
1483
+ const [bmc] = library.importArchiTemplate(bytes); // an .archicanvas file's bytes
1484
+ const canvas = model.newCanvasFromTemplate(bmc.view, 'Our business model'); // current view
1485
+ model.saveAsTemplate(canvas.id, 'Business model template'); // a model template; current view unchanged
1486
+ library.add({ view: canvas.toJSON(), name: 'Mine' }); // a library template
1487
+ ```
1488
+
1489
+ Archi's own canvas templates are not bundled (their content is licensed CC BY-SA); import them from an
1490
+ Archi install's `plugins/com.archimatetool.canvas_<version>/templates` folder.
1491
+
1492
+ **`ArchimateDiagramEditor`** is a reusable, self-contained class that encapsulates the complete lifecycle
1493
+ of an ArchiMate modeling UI: DOM structure creation, toolbar UI generation, model management, tree
1494
+ navigation, diagram rendering with styling, export/import, and control wiring. It is exported from both
1495
+ `RenderEngine` and `RenderEngineWorkspace`.
1496
+
1497
+ ### Self-contained design
1498
+
1499
+ The class owns **everything**: DOM structure, CSS styling, toolbar controls, event handling, and diagram
1500
+ rendering. The host HTML needs only:
1501
+ - One outer container div (e.g., `<div id="app"></div>`)
1502
+ - Model data (elements, relationships, views)
1503
+ - Feature configuration
1504
+
1505
+ ### Setup (with automatic toolbar)
1506
+
1507
+ ```javascript
1508
+ new RenderEngineWorkspace.ArchimateDiagramEditor({
1509
+ elements: [...],
1510
+ relationships: [...],
1511
+ views: [...],
1512
+ document,
1513
+ appContainerId: 'app', // Class generates toolbar, main, tree, diagram divs
1514
+ features: { // Toolbar is auto-generated based on these flags
1515
+ version: true,
1516
+ viewpoint: true,
1517
+ distinctShapes: true,
1518
+ showDerived: true,
1519
+ exportCSV: true,
1520
+ importCSV: true,
1521
+ exportArchimate: true,
1522
+ importArchimate: true,
1523
+ },
1524
+ workspace: window.RenderEngineWorkspace,
1525
+ }).render();
1526
+ ```
1527
+
1528
+ ### Setup (legacy: manual HTML + wireControls)
1529
+
1530
+ If you prefer to supply your own HTML with existing controls, omit `features` and use `wireControls()`:
1531
+
1532
+ ```javascript
1533
+ const editor = new RenderEngineWorkspace.ArchimateDiagramEditor({
1534
+ elements: [...],
1535
+ relationships: [...],
1536
+ views: [...],
1537
+ document,
1538
+ appContainerId: 'app', // or provide pre-made divs:
1539
+ toolbarContainerId: 'toolbar', // treeContainerId: 'tree',
1540
+ treeContainerId: 'tree', // diagramContainerId: 'diagram'
1541
+ diagramContainerId: 'diagram',
1542
+ workspace: window.RenderEngineWorkspace,
1543
+ });
1544
+
1545
+ // Wire controls for manually-created HTML
1546
+ editor.wireControls();
1547
+ editor.render();
1548
+ ```
1549
+
1550
+ ### Core methods
1551
+
1552
+ - **`render(options)`** — Render the current view's diagram with all options, styling, and position persistence. Called automatically on control changes if auto-toolbar is enabled.
1553
+ - **`savePositions()`** — Save element positions before a view switch.
1554
+ - **`exportCSV()`** — Export model to CSV format (elements + relationships). Returns a `Blob`.
1555
+ - **`importCSV(elementsCSV, relationshipsCSV)`** — Import the two CSV tables and replace the model: its elements and relationships, with one view of them all ("Imported elements"; a CSV holds no views). `importCSVText(text)` takes the one file `exportCSV()` writes.
1556
+ - **`replaceModel(model)`** — Edit another `ArchimateModel` in place of this one (every import ends here), closing the old views' tabs.
1557
+ - **`exportArchimate()`** — Export the model with every view as `.archimate` XML. Returns XML string (pictures stay `data:` URLs).
1558
+ - **`exportArchimateFile()`** — Export as Archi saves it: `toArchiFile`'s `{ data, archive, mimeType }`, a zip archive when a view holds pictures. The File menu's Export uses it.
1559
+ - **`importArchimate(file)`** — Import an Archi file, as XML text or as the file's bytes (plain or archived), and replace the entire model.
1560
+ - **`exportExchange()`** — Export the model as an Open Group Exchange Format file (XML text); its ArchiMate views are written, canvases and sketches are not. The File menu's "Export Exchange Format (.xml)" (feature `exportExchange`) downloads it.
1561
+ - **`importExchange(xml)`** — Import an Exchange Format file and replace the entire model (File menu, feature `importExchange`).
1562
+ - **`fileFormats`** (constructor option, default `ARCHIMATE_FILE_FORMATS`) — The file formats the File menu exports and imports; each is offered when its `features` flags are on (`export<featureName>`, `import<featureName>`).
1563
+ - **`wireControls(options)`** — Wire up control event listeners for manually-created HTML controls (version, viewpoint, distinct shapes, show derived, manage dialogs, filter). Auto-renders on change. Optional callbacks for custom handlers.
1564
+ - **`setupImageDragDrop()`** — Enable drag-drop and paste image support on diagram.
1565
+ - **`setDependencies(deps)`** — Inject dialog functions and custom callbacks (dependency injection).
1566
+ - **`saveSpecializations()`** — Persist specialization registry to localStorage.
1567
+ - **`getOption(controlId, defaultValue)`** — Read a control value from the DOM.
1568
+
1569
+ ### Properties (read-only)
1570
+
1571
+ - **`model`** — The `ArchimateModel` instance.
1572
+ - **`tree`** — The `ModelTree` navigator.
1573
+ - **`currentView`** — The active `ArchimateView`.
1574
+ - **`currentEngine`** — The active `RenderEngine` instance for the view.
1575
+ - **`diagrams`** — The `DiagramTabs` tab manager. `diagrams.containerFor(viewId)` is the element a view is
1576
+ drawn in. Nothing it builds carries an id (it uses the classes `diagram-area`, `diagram-tab-bar`, and
1577
+ `diagram-view` with `data-view-id`), and each `SVGRenderer` prefixes its line-end marker ids
1578
+ (`renderer.markerId('arrow')`, `renderer.markerUrl('arrow')` → `url(#re0-arrow)`), so several editors can
1579
+ share a page.
1580
+
1581
+ ### Styling & View Properties
1582
+
1583
+ The `render()` method applies:
1584
+ - **Background color** and **background image** to the diagram container.
1585
+ - **Title** (top or bottom, or hidden).
1586
+ - **Watermark** with three repeat modes: `'none'` (centered), `'repeat'` (diagonal), `'tile'` (canvas background).
1587
+
1588
+ All are properties of the current view's model object and persisted with the view.
1589
+
1590
+ All styling (page layout, containers, toolbar controls, tree nodes) is **generated by the class via CSS**—
1591
+ no stylesheets needed in host HTML.
1592
+
1593
+ ### Feature flags (auto-toolbar mode)
1594
+
1595
+ When a `features` config is provided, the class builds its toolbar from `menuBarItems()`, rendered by
1596
+ the shared `MenuBar` (see "Toolbars and dialogs" below):
1597
+
1598
+ - **`search`** — Regex-based element filter with magnifying glass icon (🔍) above the tree. Real-time filtering as user types. Accepts regex patterns (case-insensitive). Clear with backspace to restore full diagram. Also searches documentation with `{ searchDocs: true }` option.
1599
+ - **⚙ Settings** — a menu, always present, as in the template editor (`settingsMenuItems()`). It holds
1600
+ the notation settings below, each shown with its current value, then **Model settings…** (the model
1601
+ settings dialog, `showModelSettings()`: routing, grid, watermark).
1602
+ - **`version`** — Settings ▸ Version ▸ ArchiMate 4 / 3.2
1603
+ - **`viewpoint`** — Settings ▸ Viewpoint (every built-in and custom viewpoint) and ▸ Viewpoint mode (fade/hide)
1604
+ - **`distinctShapes`** — Settings ▸ Distinct shapes: on/off
1605
+ - **`showDerived`** — Settings ▸ Show derived: on/off
1606
+ - **`exportCSV`**, **`importCSV`**, **`exportArchimate`**, **`importArchimate`** — entries in the **File** menu,
1607
+ after the view items (New view…, Properties…, Duplicate, Delete)
1608
+
1609
+ An element's right-click menu orders it with the same **⬍ Order** submenu as the template editor
1610
+ (Bring to Front, Send to Back, Raise, Lower; `DiagramEditor.ORDER_OPERATIONS`), each one undo step on
1611
+ the view.
1612
+
1613
+ Set any to `false` to leave that setting out of the menu. Values live on the editor: `getOption(key)` / `setOption(key, value)`,
1614
+ keys `archimate-version`, `archimate-viewpoint`, `archimate-viewpoint-mode`, `archimate-distinct-shapes`,
1615
+ `archimate-show-derived`. Hosts that supply their own controls with those element ids (legacy
1616
+ `wireControls()` mode) are read from the DOM when the editor holds no value.
1617
+
1618
+ **Search feature examples:**
1619
+ ```javascript
1620
+ // Basic search by name
1621
+ setFilter('business') // Shows only business-layer elements
1622
+
1623
+ // Regex patterns
1624
+ setFilter('actor|process') // Shows actors and processes (alternation)
1625
+ setFilter('.*service') // Shows elements ending with 'service'
1626
+ setFilter('^Customer') // Shows elements starting with 'Customer'
1627
+
1628
+ // Include documentation in search
1629
+ setFilter('critical', { searchDocs: true })
1630
+ setFilter('urgent', { searchDocs: true })
1631
+
1632
+ // Clear filter
1633
+ setFilter('') // Restores full diagram
1634
+ ```
1635
+
1636
+ **Display behavior:**
1637
+ - **Diagram view:** Highlights matching elements with gold glow (others at 30% opacity)
1638
+ - **Tree panel:** Hides non-matching nodes (shows only matches)
1639
+ - **Combined effect:** Focus on matches while preserving diagram context
1640
+
1641
+ **Visual styles:**
1642
+ - **Diagram matches:** Gold glow + 110% brightness
1643
+ - **Diagram non-matches:** 30% opacity (visible but de-emphasized)
1644
+ - **Tree non-matches:** Hidden completely (CSS display: none)
1645
+
1646
+ ### Example: Minimal setup (recommended)
1647
+
1648
+ HTML: just an app container.
1649
+ ```html
1650
+ <!DOCTYPE html>
1651
+ <html>
1652
+ <head><meta charset="UTF-8"><title>ArchiMate</title></head>
1653
+ <body>
1654
+ <div id="app"></div>
1655
+ <script src="renderengine-workspace.umd.js"></script>
1656
+ <script>
1657
+ new window.RenderEngineWorkspace.ArchimateDiagramEditor({
1658
+ elements: [...],
1659
+ relationships: [...],
1660
+ views: [...],
1661
+ document,
1662
+ appContainerId: 'app',
1663
+ features: {
1664
+ version: true, viewpoint: true, distinctShapes: true,
1665
+ showDerived: true, exportCSV: true, importCSV: true,
1666
+ exportArchimate: true, importArchimate: true,
1667
+ },
1668
+ workspace: window.RenderEngineWorkspace,
1669
+ }).render();
1670
+ </script>
1671
+ </body>
1672
+ </html>
1673
+ ```
1674
+
1675
+ For a page of its own, `fullPage` (default `true`) fills the viewport; `false` fills the app container
1676
+ instead, as inside a workbench part. `model` hands in an `ArchimateModel` rather than building one from
1677
+ `elements`/`relationships`/`views`. See `examples/archimate-legacy.html` for the workbench host, and
1678
+ `test/charts.html` for integration with the workspace shell and dialog handlers.
1679
+
1680
+ ## Diagram templates and palette categories
1681
+
1682
+ `DiagramEditor` shapes are `Template` subclasses (`src/charting/templates/`). Geometry is
1683
+ centre-based — `(x, y)` is the centre of a `width × height` box — and every template accepts
1684
+ `{ x, y, width, height, rotation, zIndex, fillColor, fillOpacity, strokeColor, strokeWidth, textColor }`
1685
+ and a text style: `fontFamily`, `fontSize` (a bare number is points), `fontWeight: 'bold'`, `fontStyle: 'italic'`,
1686
+ `textDecoration: 'underline'`, `textAlign` ('left' | 'center' | 'right') and `textPosition` ('top' | 'middle' |
1687
+ 'bottom') (`TextStyle`; unset, the name is drawn at 12px at the shape's own label point). Unset sizes and
1688
+ colours come from the class's `static defaults`. A shape's properties dialog (double-click it) edits its name,
1689
+ place, colours and text style (Font, Text size, Bold, Italic, Underline, Alignment, Position) as one undo step;
1690
+ `serialize()` keeps the style. `TextStyle` is the one definition every editor uses: `fields(style)` and
1691
+ `fromValues(values)` for a FormDialog, `fromNode`/`toNode` for a graph-renderer node (whose position is
1692
+ `verticalAlign`), `svgAttributes(style)` and `block(style, box, lines)` for drawing (the graph renderer, the
1693
+ ArchiMate canvas shapes and the template shapes all place text with it).
1694
+
1695
+ ```js
1696
+ new RectangleTemplate('r1', 'Billing', { fontFamily: 'Georgia', fontSize: '14pt', fontWeight: 'bold', textAlign: 'left', textPosition: 'top' });
1697
+ ```
1698
+
1699
+ | Family | Classes |
1700
+ |---|---|
1701
+ | Boxes | `RectangleTemplate`, `RoundedRectangleTemplate`, `StadiumTemplate`, `PredefinedProcessTemplate`, `ClassTemplate`, `ComponentTemplate`, `SwimlaneTemplate`, `PackageTemplate`, `BoxTemplate` (3D) |
1702
+ | Round | `CircleTemplate`, `EllipseTemplate`, `FinalStateTemplate`, `CloudTemplate` |
1703
+ | Polygons | `DiamondTemplate`, `TriangleTemplate`, `PentagonTemplate`, `HexagonTemplate`, `OctagonTemplate`, `StarTemplate`, `ParallelogramTemplate`, `TrapezoidTemplate`, `ManualInputTemplate` |
1704
+ | Other | `CylinderTemplate`, `DocumentTemplate`, `NoteTemplate`, `DataStoreTemplate`, `ActorTemplate`, `LifelineTemplate` |
1705
+
1706
+ A template draws its outline in `shapeElements(doc)`, or in `points()` when it extends
1707
+ `PolygonTemplate`. The base class adds the group, rotation, label and editor wiring, and
1708
+ `renderPreview(size)` reuses the same outline for the palette tile.
1709
+
1710
+ **Edge endpoints.** `outline()` returns the visible outline as a polygon, with curves sampled
1711
+ (the default is the box). `boundaryPoint(dx, dy)` is where a ray from the centre in that
1712
+ on-screen direction meets the outline, rotation included. For concave outlines it uses the
1713
+ outermost crossing. A shape whose outline isn't its box overrides `outline()` to match what it
1714
+ draws. `EdgeGeometry.computeEdgePath` uses `info.boundaryPoint` whenever a node supplies one,
1715
+ for `'auto'` anchors and for pinned sides (`sourceSide`/`targetSide`). Nodes without one keep
1716
+ the `shape`-based model. `EdgeGeometry.endDirections(d)` returns the direction in which a path
1717
+ arrives at each end.
1718
+
1719
+ **Selecting and bending edges.** Selection is editor state (`selectedEdgeId` /
1720
+ `selectedTemplateId`). Selecting a shape deselects the edge, and clicking empty canvas clears both
1721
+ (`clearSelection()`). Only the selected edge shows handles: end dots, solid waypoints, and a
1722
+ hollow handle mid-segment. Click or drag a hollow handle, double-click the line, or right-click →
1723
+ *Add waypoint here* to add a bend. It is inserted into the nearest segment, so the route keeps
1724
+ its order. Drag a waypoint to move it. Double-click it or right-click → *Delete waypoint* to
1725
+ remove it, or use *Straighten* to remove all of them. Points snap to the grid while it is shown
1726
+ and are converted through pan/zoom. The programmatic forms are `addWaypoint(edgeId, {x, y})`,
1727
+ `removeWaypoint(edgeId, index)` and `clearWaypoints(edgeId)`. Each is one undo step.
1728
+
1729
+ **Reconnecting.** Drag either end dot of the selected edge onto another shape to move that
1730
+ end. Waypoints and style are kept, and that end's side pin resets to automatic. Dropping it on
1731
+ empty canvas, or on the shape at the other end, cancels. This is one undo step
1732
+ (`ReconnectEdgeCommand`).
1733
+
1734
+ **Line style levels.** A line's style resolves through `EdgeStyleCascade`: the line itself,
1735
+ then each level in `editor.edgeStyleLevels` (most specific first, `[{ key, label, style }]`),
1736
+ then the schema default. A value a level doesn't set is inherited. `EDGE_STYLE_PROPERTIES` is the
1737
+ single list of cascading properties:
1738
+
1739
+ | Key | Values (default first) |
1740
+ |---|---|
1741
+ | `routing` | `straight`, `orthogonal`, `curved`, `polyline` (bends around other shapes) |
1742
+ | `attach` | `floating` (outline, aimed at the other end), `sides` (middle of the facing side) |
1743
+ | `lineStyle` | `solid`, `dashed`, `dotted` (same dash patterns as `GraphDiagram`, via `LINE_STYLE_DASH`) |
1744
+ | `strokeColor`, `strokeWidth` | `#666666`, `2` |
1745
+ | `startMarker`, `endMarker` | `none`, or an `EdgeMarker` key |
1746
+
1747
+ `LINE_ONLY_PROPERTIES` lists `label`, `sourceSide` and `targetSide`; these never cascade. A
1748
+ pinned side beats `attach`. `editor.editLineStyle(levelKey)` opens the defaults dialog for one
1749
+ level, and the edge's *Properties* dialog edits the line. In both, every cascading field offers
1750
+ *Inherit*, labelled with the value and the level it would come from. Changes go through
1751
+ `SetStyleCommand` (one undo step; `undefined` = inherit). The editor never stores the levels
1752
+ itself, so the host decides what a level is and where it persists:
1753
+
1754
+ ```javascript
1755
+ new DiagramEditor({
1756
+ diagramContainerId: 'canvas',
1757
+ edgeStyleLevels: [
1758
+ { key: 'view', label: 'Diagram', style: view.edgeStyle }, // per diagram tab
1759
+ { key: 'model', label: 'Model', style: model.edgeStyle }, // shared by the model's diagrams
1760
+ { key: 'tool', label: 'Tool', style: toolDefaults }, // e.g. persisted in localStorage
1761
+ ],
1762
+ });
1763
+ ```
1764
+
1765
+ `MenuBar`'s Settings menu adds one *Line style: … defaults* entry per level.
1766
+
1767
+ **Edge markers.** `edge.startMarker` / `edge.endMarker` hold an `EdgeMarker` key:
1768
+ `arrow`, `openarrow`, `openarrowhalf`, `trianglehollow`, `diamond`, `diamondhollow`, `circle` or
1769
+ `crowsfoot` (`EDGE_MARKERS`). `getEdgeMarker(value)` ignores case and punctuation, so
1770
+ `"Crow's Foot"` resolves too, and so do ArchiMate's own arrowhead names (`diamond-hollow`,
1771
+ `open-arrow-half`, …). A marker's `shapes(tip, dir, size, color, background)` returns element
1772
+ descriptors whose tip is on the boundary and whose body extends back along the arriving
1773
+ segment, so nothing is hidden under the node; hollow markers are filled with `background`
1774
+ (default white) so the line does not show through them.
1775
+
1776
+ **Glyphs.** Every node shape the graph engine draws (`rect`, `circle`, `diamond`, `note`,
1777
+ `image`, `icon`, `person` and the ArchiMate notation shapes `pill`, `cube`, `chevron`, …) is a
1778
+ `Glyph` subclass in `src/charting/glyphs/`, one per file. `glyphFor(key)` returns it (an unknown key
1779
+ gives the circle) and `GLYPHS` maps every key. `glyph.parts(Glyph.context({R, size, color,
1780
+ cornerStyle, strokeColor, strokeWidth, icon, iconUrl}))` describes the shape centred on (0, 0) as
1781
+ `rect`/`circle`/`path`/`image`/`text`/`group` parts; `drawGlyph(renderer, parts)` draws them through
1782
+ a renderer. A glyph whose `ownsInterior` is true gets no centred icon or initials on top.
1783
+
1784
+ ```javascript
1785
+ const parts = glyphFor('cube').parts(Glyph.context({ R: 30, size: { w: 120, h: 55 }, color: '#AFFFAF', strokeColor: '#000', strokeWidth: 1.2 }));
1786
+ parts.children.length; // 3: front, top and side faces
1787
+ ```
1788
+
1789
+ `GlyphTemplate` is a template drawn by a glyph: a subclass names the glyph (`glyph()`) and its
1790
+ context, and the template draws its parts as SVG (`glyphToSvg`; `glyphElement(part)` gives the tag and
1791
+ attributes, by the SVG renderer's rules) and attaches lines to the glyph's `outline(ctx)`. Every glyph
1792
+ has an outline: its box by default, the real shape for round, skewed and pointed ones.
1793
+
1794
+ **ArchiMate in the template editor.** `ArchimateElementTemplate(instance, model, {nodeOptions})` and
1795
+ `ArchimateEdge(instance, view)` put an `ArchimateView` into a `DiagramEditor`. Neither keeps a copy:
1796
+ position, size, stacking and per-view paint are read from and written to the `ElementInstance`, the
1797
+ route, pinned sides and per-line style to the `RelationshipInstance`, so the editor's move, resize,
1798
+ waypoint, side and style commands edit the view and undo through `view.history`. A template looks as
1799
+ the graph engine draws the element (`ArchimateDiagram.buildNode`: layer colour, corner style, type
1800
+ glyph badge, or the full notation shape with `nodeOptions.useDistinctShapes`); notes and images
1801
+ (`ViewObjectInstance`) draw their own glyph. A line's dashes and end markers come from its
1802
+ relationship's kind and cannot be set on the line; routing, attach, colour and width can, and unset
1803
+ inherits. Its ends come from the instance (re-linking an end is a model edit, not a line edit).
1804
+
1805
+ ```javascript
1806
+ const view = model.currentView;
1807
+ const templates = new Map([...view.elementInstances.values()].map(i => [i.id, new ArchimateElementTemplate(i, model)]));
1808
+ const edges = new Map([...view.relationshipInstances.values()].map(i => [i.id, new ArchimateEdge(i, view)]));
1809
+ new DiagramEditor({ diagramContainerId: 'canvas', templates, edges, history: view.history }).render();
1810
+ ```
1811
+
1812
+ `ArchimateCanvasEditor({view, model, nodeOptions, chooseKind, ...DiagramEditor options})` is that
1813
+ editor as a class: it rebuilds its templates and lines from the view on every render (so an undone
1814
+ view edit shows at once), uses the view's history, and does through the view and model what stands
1815
+ for something else in ArchiMate, each as one undo step:
1816
+ - `addElement(type, point)`, and dropping an `ArchimatePalette` tile (`ARCHIMATE_TYPE_MIME`), creates a
1817
+ model element named after its type and shows it;
1818
+ - connecting two elements (`connect(sourceId, targetId)`) creates a relationship. Its kind is asked
1819
+ for through `chooseKind(kinds, from, to)` (a promise or value; null cancels) among
1820
+ `allowedKinds(sourceId, targetId)`, or without a chooser is the first of `KIND_PREFERENCE` that is
1821
+ allowed (the dependency kinds before the structural whole/part ones). Notes and images take none;
1822
+ - deleting an element takes it and its lines off the view; deleting a line takes the relationship
1823
+ off; the model keeps both.
1824
+
1825
+ `ArchimatePalette({version, onPick, document})` renders the element types grouped by layer
1826
+ (`ArchimatePalette.groups(version)`: the notation's layers, then ArchiMate 4's common behaviour types),
1827
+ each tile only its notation glyph in its layer colour, four to a row (`ArchimatePalette.TILE`; the name shows on hover); drag a tile onto the canvas or click it (`onPick`).
1828
+
1829
+ `ArchimateCanvasPart` (workspace bundle, type `archimate-canvas`; `examples/archimate.html`, and the testbed
1830
+ `test/archimate-canvas-testbed.html`) is the
1831
+ modeller on this canvas: toolbar (File with the view items,
1832
+ version, distinct shapes, zoom, connect, Settings), palette, one tab per view (Properties…, Duplicate,
1833
+ Delete) and the canvas; connecting opens a picker of the allowed kinds. The model tree is on the left, with a search box
1834
+ (the tree's text filter: it hides what it leaves out there and dims it on the canvas), and the palette on the right,
1835
+ as in Archi. File offers New view…, New canvas… and New canvas from template (`EditorPart#newItems()`), the open
1836
+ canvas saved as a template, and Import Archi template…. Dragging an element from the tree onto the canvas shows it there
1837
+ with its relationships to the elements already shown, one undo step (`editor.showElement(elementId, point)`,
1838
+ `ShowElementCommand`: only lines to or from the new box; redo adds the same lines). The File menu exports
1839
+ and imports CSV, `.archimate` and the Exchange Format; an import replaces the part's model
1840
+ (`part.replaceModel`) and the tree, canvas and menus follow it. Spec: `elements`, `relationships`,
1841
+ `views`, `name`, `archimateVersion`, `distinctShapes`, `viewpoint`, `viewpointMode`.
1842
+
1843
+ **File formats.** `ModelFileFormat` is one format a model is exported to and imported from: a subclass gives
1844
+ `label`, `accept`, `fileName`, `readsBytes` (import gets the file's bytes, not its text), `featureName`, and
1845
+ `export(model)` (`{ data, mimeType }`) and `import(data)` (a new model). `modelFileMenuItems(formats, { model,
1846
+ onImport, enabled })` makes the File menu's Export and Import items, one group per format: export downloads
1847
+ the file, import asks for one and hands the new model to `onImport` (a failure is reported and the model kept).
1848
+ `ARCHIMATE_FILE_FORMATS` are `ArchimateCsvFileFormat` (both tables in one file; import lays all the elements
1849
+ out in one view), `ArchiFileFormat` and `ExchangeFileFormat`; both ArchiMate editors read them.
1850
+
1851
+ ```js
1852
+ import { ARCHIMATE_FILE_FORMATS } from 'renderengine';
1853
+ const [csv] = ARCHIMATE_FILE_FORMATS;
1854
+ const { data } = csv.export(model); // '=== elements.csv ===\n...'
1855
+ const copy = csv.import(data); // a new ArchimateModel, with the view "Imported elements"
1856
+ ```
1857
+
1858
+ **Model tree layers.** The tree's Layers folder files an element under its layer as the diagram draws it
1859
+ (`treeLayerOf(element)`: its own `layer`, an IT type's, or its notation type's), in the order `TREE_LAYERS`;
1860
+ anything else (junction, grouping, location, an ArchiMate 4 common type) is under Other, as in Archi. A
1861
+ tree leaf dragged anywhere carries its element id as `MODEL_ELEMENT_MIME`.
1862
+
1863
+ An instance's `appearance` (`fillColor`, `fillOpacity`, `strokeColor`, `strokeWidth`, `textColor`, the text style
1864
+ keys `fontFamily`, `fontSize`, `fontWeight`, `fontStyle`, `textDecoration`; on a
1865
+ relationship instance the line style keys) is this occurrence's own paint; the graph engine draws it
1866
+ too. A template whose properties are computed reports what it holds itself through
1867
+ `ownProperty(key)`, which `SetPropertiesCommand` uses so undo restores "unset" rather than the
1868
+ computed value.
1869
+
1870
+ Each ArchiMate relationship type gives its notation as line style values:
1871
+ `ArchimateNotation.relationshipType('composition').edgeStyle()` is
1872
+ `{ lineStyle: 'solid', startMarker: 'diamond', endMarker: 'none' }`; arrowheads come back as marker keys
1873
+ (`realization` ends in `'trianglehollow'`).
1874
+
1875
+ `DIAGRAM_TYPES` (19 categories in the groups General, Process, UML, Data, Infrastructure,
1876
+ Business, Ideas) decides which templates the palette offers. `editor.initPaletteForDiagramType(container, key)`
1877
+ renders a grouped category picker above a grid of draggable tiles. `editor.setPaletteCategory(key)`
1878
+ switches category, and `onTemplateTypeChange(key)` (a constructor option) reports the switch.
1879
+ Unknown keys fall back to `'generic'` (Basic Shapes).
1880
+
1881
+ Adding a shape and a category (runs under Node — no DOM needed until render):
1882
+
1883
+ ```javascript
1884
+ import { PolygonTemplate, DIAGRAM_TYPES, getDiagramTypesByGroup } from './src/index.js';
1885
+
1886
+ class ChevronTemplate extends PolygonTemplate {
1887
+ static defaults = { width: 120, height: 50 };
1888
+ points() {
1889
+ const n = this.height / 2;
1890
+ return [[this.left, this.top], [this.right - n, this.top], [this.right, this.y],
1891
+ [this.right - n, this.bottom], [this.left, this.bottom], [this.left + n, this.y]];
1892
+ }
1893
+ }
1894
+
1895
+ DIAGRAM_TYPES['value-chain'] = {
1896
+ name: 'Value Chain', group: 'Business', description: 'Primary activities in sequence',
1897
+ templates: [{ id: 'activity', name: 'Activity', factory: (id, x, y) => new ChevronTemplate(id, 'Activity', { x, y }) }],
1898
+ };
1899
+
1900
+ console.log(DIAGRAM_TYPES['value-chain'].templates[0].factory('a1', 100, 50).points().length); // 6
1901
+ console.log(getDiagramTypesByGroup().find(g => g.group === 'Business').types.map(t => t.key));
1902
+ // [ 'organizational-chart', 'value-chain' ]
1903
+ ```
1904
+
1905
+ ### Diagram models and the editor part
1906
+
1907
+ `ViewSetModel` is the base of a model edited as several views: `views()`, `viewCount`, `getView`,
1908
+ `currentView`, and `createView`, `duplicateView`, `renameView`, `deleteView` (the last view can't be
1909
+ deleted; `canDeleteView` says so) and `switchTo`, each notifying `onChange` listeners. A subclass
1910
+ makes its own kind of view in `_newView(id, title)`; a view has an `id`, a `title` and
1911
+ `copy(id, title)`. `DiagramModel` and `ArchimateModel` both extend it.
1912
+
1913
+ `ViewMenu(model, {document, noun, editProperties})` turns those operations into menu items:
1914
+ `newViewItem()` (asks for a name) and `items(viewId)` (Rename…, Duplicate, and Delete after a
1915
+ confirmation when allowed). Given `editProperties(view)`, a properties dialog that includes the name,
1916
+ **Properties…** takes Rename's place. Tab strips, File menus and the ArchiMate model tree all use it,
1917
+ and both editors pass a properties dialog, so their tabs offer the same **Properties…**, **Duplicate**
1918
+ and **Delete**.
1919
+
1920
+ `ViewPropertiesDialog({model, view})` is the base of those dialogs: the view's name and documentation;
1921
+ a subclass adds `sections()` (more FormDialog fields) and `applySections(values)`.
1922
+ `SettingsViewPropertiesDialog` adds the view's level of the SettingsManager cascade (the ArchiMate
1923
+ editor's); `DiagramViewPropertiesDialog({cascade, levelKey, onStyleChange})` adds a diagram's
1924
+ line-style defaults, the `levelKey` level of an `EdgeStyleCascade`, applied as one undo step on the
1925
+ view's history (the template editor's). Both build their inheritable fields with
1926
+ `inheritableFields(properties, own, inherited)` (and read them back with `readInheritableFields`)
1927
+ from `SettingsCascade.js`, as `EdgeStyleDialog` does: each field shows its own value or offers
1928
+ "Inherit", labelled with the value and the level it would come from. `MODEL_SETTINGS_PROPERTIES`
1929
+ carries the labels, types and choices those forms use.
1930
+
1931
+ The SettingsManager cascade resolves each of those settings through a view's own settings, then its
1932
+ model's, then the tool's, then the default (`cascade(view, modelId)`; `getModelOverrides(modelId)` is
1933
+ what the model level itself sets, `getModelSettings(modelId)` the model's resolved values). Global
1934
+ Settings sets the tool's value and the model's (which may inherit the tool's); a view's Properties sets
1935
+ the diagram's (which may inherit the model's). "Invalid relationships" (`invalidRelationships`:
1936
+ `highlight`, `tooltip`, `off`) is set this way.
1937
+
1938
+ ```js
1939
+ settingsManager.saveToolSettings({ invalidRelationships: 'tooltip' }); // every model, unless it says otherwise
1940
+ settingsManager.saveViewSettings(view, { invalidRelationships: 'off' }); // this diagram only
1941
+ settingsManager.getEffectiveSettings(view, 'default').invalidRelationships; // → 'off'
1942
+ ```
1943
+
1944
+ `DiagramModel` holds a set of diagrams (`DiagramView`s) edited together. It adds the palette
1945
+ category they share (`templateType`) and the model-level line style (`edgeStyle`). Each `DiagramView` owns its `templates`
1946
+ and `edges` maps, a diagram-level `edgeStyle` and its own `CommandHistory`. `view.copy()` is
1947
+ independent: shapes and lines are copied, and the history starts empty.
1948
+
1949
+ Passing a view's maps and history to `DiagramEditor` binds the editor to that view: edits land
1950
+ in the view directly, and a rebuilt editor carries on with the same undo history:
1951
+
1952
+ ```javascript
1953
+ import { DiagramModel, DiagramEditor, RectangleTemplate } from './src/index.js';
1954
+
1955
+ const model = new DiagramModel({ templateType: 'flowchart' });
1956
+ const view = model.createView('Context');
1957
+ const editor = new DiagramEditor({
1958
+ enableKeyboardShortcuts: false, // no DOM needed for this example
1959
+ templates: view.templates, edges: view.edges, history: view.history,
1960
+ });
1961
+ editor.addTemplate(new RectangleTemplate('a', 'Box'));
1962
+ console.log(view.templates.has('a'), model.duplicateView(view.id).title); // true 'Context (copy)'
1963
+ ```
1964
+
1965
+ Call `editor.dispose()` before discarding an editor on a page that lives on. Its keyboard
1966
+ shortcuts and pan listeners are registered on `document`. Shortcuts ignore keys typed into
1967
+ form fields.
1968
+
1969
+ **Subclassing.** The undo shortcuts and every command use `editor.history`, an accessor a
1970
+ subclass can redirect. `ArchimateDiagramEditor` maps it to `currentView.history` (the model's one history), and hands the same history to the graph engine it draws with. Canvas edits
1971
+ (move, resize, bend points, pinned sides, drawing a relationship) are recorded there too, written
1972
+ back to the view through the hooks above, and undo across re-renders. A relationship drawn between
1973
+ two elements is created in the model and on the view, and its kind is asked for once; undo removes
1974
+ it from both. The line's right-click **Delete from View** takes one relationship instance off the
1975
+ view and leaves the model relationship.
1976
+
1977
+ **Views.** `DiagramEditorState` is the abstract base of every diagram view: its own `history` and
1978
+ the stacking operations below. A subclass says what stacks through `layerItems()`.
1979
+ `DiagramView` (the template editor's tabs) stacks its templates. `ArchimateView` stacks its element
1980
+ instances, and records adding and removing them as undo steps:
1981
+ `view.addToView(instance, { element })` shows an `ElementInstance` (creating the model element
1982
+ first when `element` is given, and widening `elementIds` to it), and `view.deleteElement(instanceId)`
1983
+ takes one off the view. Undo of either reverses all of it. `ArchimateModel.addElement(el)` and
1984
+ `removeElement(id)` are the model's side; position stays on the instance, so `addElement` does not
1985
+ keep an element's `x`/`y`.
1986
+
1987
+ Relationships work the same way. `view.missingRelationshipInstances(elementId)` lists the pairings
1988
+ the view could draw but doesn't (each instance of the element against each instance at the other
1989
+ end of its relationships), `view.addRelationshipsToView(instances)` adds them as one undo step, and
1990
+ `view.removeRelationship(instanceId)` takes one off. `ArchimateModel.addRelationship(rel)` and
1991
+ `removeRelationship(id)` are the model's side.
1992
+
1993
+ What a view draws per occurrence lives on the instance, so two occurrences of one element or
1994
+ relationship can differ:
1995
+
1996
+ | Instance | Field | Meaning |
1997
+ |---|---|---|
1998
+ | `ElementInstance` | `x`, `y`, `zIndex` | position and stacking |
1999
+ | `ElementInstance` | `size` | `{width, height}` or `{radius}` set on this view; `{}` = the element's own |
2000
+ | `RelationshipInstance` | `waypoints` | this line's bend points; `null` inherits the model relationship's, `[]` is straight |
2001
+ | `RelationshipInstance` | `sourceSide`, `targetSide` | pinned attachment side; `null` = automatic |
2002
+
2003
+ A note or image belongs to one view, not the model, so it is a `ViewObjectInstance` (a subclass of
2004
+ `ElementInstance`): it carries its own `content` (`{id, name, shape: 'note' | 'image', label, iconUrl,
2005
+ href, width, height}`), shows whatever the element filter says, and never appears in the model tree.
2006
+ A view also takes them as `viewObjects: [{content, x, y}]`, which is what an Archi import produces.
2007
+
2008
+ ```javascript
2009
+ view.addToView(new ViewObjectInstance({ id: 'n1', name: 'Remember', label: 'Remember', shape: 'note' }, 40, 300));
2010
+ model.getElement('n1'); // undefined: the note is the view's
2011
+ ```
2012
+
2013
+ A view's `edgeLayout` (`{relationshipId: {waypoints}}`, what an Archi import produces) is read once,
2014
+ at construction: it routes each instance of that relationship that has no route of its own.
2015
+
2016
+ ```javascript
2017
+ const model = new ArchimateModel({ elements: [{ id: 'a', name: 'A' }], views: [{ id: 'v', elementIds: ['a'] }] });
2018
+ const view = model.views()[0];
2019
+ view.addToView(new ElementInstance('note-1', 40, 60), { element: { id: 'note-1', name: 'Remember', shape: 'note' } });
2020
+ view.history.undo(); // the note leaves the view and the model; elementIds is ['a'] again
2021
+ ``` A subclass whose drawing engine owns zoom, pan and canvas clicks passes
2022
+ `enableCanvasInteractions: false`. One that draws into another SVG overrides `_canvasSvg()` so
2023
+ `_clientToDiagram` and `_snapToGrid` keep working.
2024
+
2025
+ **Stacking order.** `BringToFrontCommand`, `SendToBackCommand`, `RaiseCommand` and
2026
+ `LowerCommand` (all `ReorderCommand`s) work on any target with `layerItems()`: the editor's
2027
+ templates, or any view's `layerItems()` (`view.bringToFront(id)` and similar record one undo step
2028
+ on `view.history`). Raise and lower swap with the item actually drawn
2029
+ above or below, ties and gaps included. Each operation renumbers the stack to consecutive
2030
+ integers, and undo restores every previous value.
2031
+
2032
+ `EditorPart` (workspace bundle) is the base of the workbench parts that edit a `ViewSetModel`: a
2033
+ subclass builds its model from the part's spec in `createModel(spec)`, and gets `part.model`,
2034
+ `part.viewMenu` and `fileMenu()` (the view items, then `fileMenuItems()`: export and import in each of
2035
+ its `fileFormats()`, none by default, then the image export), and its editor disposed with the part. An
2036
+ import goes through `replaceModel(model)`, which a subclass follows in `modelReplaced(model)`.
2037
+ `DiagramEditorPart`, `ArchimateEditorPart` and `ArchimateCanvasPart` extend it.
2038
+
2039
+ `DiagramEditorPart` (workspace bundle) puts this together: `MenuBar` (File creates and deletes
2040
+ diagrams; Settings has the grid and line-style levels), the palette, a `ViewTabStrip` (right-click
2041
+ a tab for its **Properties…**: name, documentation and the diagram's line style; **Duplicate**;
2042
+ **Delete**) and the canvas. `part.edgeStyleLevels(view)` gives a diagram's line-style levels and
2043
+ `part.showViewProperties(view)` opens its properties. Spec options: `templateType`,
2044
+ `tabOrientation` (`'horizontal'`/`'vertical'`), `diagramTitle`, and `toolStyleKey`, the
2045
+ localStorage key for tool-level line style (`null` keeps it in memory only), saved through
2046
+ `LocalJsonStore`. Its levels are Diagram (`view.edgeStyle`), Model (`model.edgeStyle`) and Tool.
2047
+
2048
+ ```javascript
2049
+ const { DiagramEditorPart } = window.RenderEngineWorkspace;
2050
+ ws.registerPart(DiagramEditorPart);
2051
+ ws.addParts([{ id: 'editor', type: 'diagram-editor', title: 'Diagram Editor', templateType: 'flowchart' }]);
2052
+ ws.getPart('editor').model.currentView; // seed or inspect content before or after mounting
2053
+ ```
2054
+
2055
+ The workspace bundle includes its own copy of the engine, so a page can hold two copies of every
2056
+ class. Library code imports its collaborators directly rather than looking them up on
2057
+ `window.RenderEngine`, and `CommandHistory.execute` accepts any object with `execute()` and
2058
+ `undo()` rather than checking `instanceof Command`. A command built from either bundle therefore
2059
+ works in either bundle's history.
2060
+
2061
+ ### Toolbars and dialogs
2062
+
2063
+ `MenuBar` renders a row of toolbar items and has no controls of its own. Each editor contributes its
2064
+ items through `menuBarItems()`: the template editor gives zoom, connect mode and a ⚙ Settings menu (grid,
2065
+ grid size, line-style levels), and `ArchimateDiagramEditor` gives its File menu and a ⚙ Settings menu
2066
+ (notation and model settings). Each editor's ⚙ Settings menu reads `settingsMenuItems()`, which a
2067
+ subclass extends (ArchimateCanvasEditor puts version and distinct shapes in front of the grid and line
2068
+ styles). Every editor's shape menu orders with `orderMenuItem(id)`, the one Order submenu. A host can add more; `DiagramEditorPart` prepends its File menu (new, duplicate, rename, delete
2069
+ diagram). After an item acts, the bar re-renders. Items with live labels (the zoom %) also refresh on
2070
+ `editor.onRendered`.
2071
+
2072
+ | Item | Use |
2073
+ |---|---|
2074
+ | `ToolbarMenu(label, items)` | Button opening a `PopupMenu`; `items` may be a function, evaluated on open |
2075
+ | `ToolbarButton(label, action, { title, active })` | Action, or a toggle when `active()` is given |
2076
+ | `ToolbarSelect(label, choices, value, onChange)` | Labelled dropdown; `choices()` returns `[value, text, title?]` |
2077
+ | `ToolbarCheckbox(label, checked, onChange)` | Labelled switch |
2078
+ | `ToolbarText(text, style)` | Live text |
2079
+ | `ToolbarSeparator()` | Divider |
2080
+
2081
+ ```javascript
2082
+ new MenuBar({ container, editor, items: [
2083
+ new ToolbarMenu('File', [{ label: 'Export', action: exportIt }]),
2084
+ ...editor.menuBarItems(),
2085
+ ] }).render();
2086
+ ```
2087
+
2088
+ `FormDialog` is the one modal form: `fields` of type `text`, `textarea`, `number`, `color`, `select`
2089
+ or `checkbox`. A field with `inherit: { text, short? }` may be left unset; it then reads as `undefined` (a select
2090
+ offers `text` as its first choice; any other field, a checkbox too, gets a checkbox named `short`, default
2091
+ "Inherit", that greys it out). `FormDialog.many(fieldLists)` makes one form for several items from each one's
2092
+ fields: every field on "Keep" showing the value they share, read-only fields left out.
2093
+ `onApply(values)` receives every field; `onClose(applied)` is told once when the dialog closes, whether it
2094
+ was applied (false for Cancel, Escape or a click outside), so a caller waiting on a choice can settle.
2095
+ `EdgeStyleDialog`, the grid-size and diagram-name prompts are all `FormDialog`s. A dialog fits the window
2096
+ (at most 90% of its height): its fields scroll between the title and the buttons, which stay in view, and
2097
+ dragging the title moves it. A `textarea` scrolls inside its own box (at most 40% of the window) and can be
2098
+ dragged taller; a properties dialog shows `description`, `documentation`, `notes` and `tooltip` that way.
2099
+ A `color` field (`ColorField`) holds its value as text, `#rrggbb` or `none` for no colour (transparent, which a
2100
+ browser colour input cannot hold), beside the colour picker and a row of preset swatches: no colour, black,
2101
+ white, red, green, blue, yellow, orange, purple, grey and light grey (`ColorField.PRESETS`). A field's
2102
+ `presets` (`[{value, name}]`) replaces them, and `allowNone: false` leaves out "No colour".
2103
+
2104
+ ```js
2105
+ new FormDialog({ title: 'Box', fields: [
2106
+ { key: 'fill', label: 'Fill colour', type: 'color', value: '#ffffb5' }, // presets, none included
2107
+ { key: 'text', label: 'Text colour', type: 'color', value: '#000000', allowNone: false },
2108
+ { key: 'brand', label: 'Brand colour', type: 'color', presets: [{ value: '#0b5394', name: 'Brand blue' }] },
2109
+ ], onApply: (values) => console.log(values.fill) }).open(); // '#ffffb5', or 'none'
2110
+
2111
+ const fields = (t) => [{ key: 'fill', label: 'Fill colour', type: 'color', value: t.fill }];
2112
+ new FormDialog({ title: '2 boxes', fields: FormDialog.many([fields({ fill: '#ff0000' }), fields({ fill: '#ff0000' })]),
2113
+ onApply: (values) => console.log(values.fill) }).open(); // undefined while Keep is ticked
2114
+ ```
2115
+
2116
+ **Grouping and locking shapes.** A view's `arrangement` (`Arrangement`, on every `DiagramEditorState`, so a
2117
+ template diagram's `DiagramView` and an `ArchimateView` alike) says which items move as one (an
2118
+ `ItemGroup`: logical, it draws nothing but a dashed outline while a member is selected) and which are fixed in
2119
+ place (locked: not moved, resized, rotated, reordered, edited or deleted until unlocked; trying says so). A
2120
+ group can be locked as a whole; groups do not nest (grouping a member brings its group). Changes are one undo
2121
+ step each: `view.group(ids)`, `view.ungroup(id)`, `view.setLocked(ids, locked)`, or the commands
2122
+ `GroupCommand`, `UngroupCommand`, `LockCommand`. In the template editor (and ArchiMate's canvas editor, its
2123
+ subclass), Shift/Ctrl/⌘+click adds to the selection; dragging a selected shape moves the selection with its
2124
+ groups, as one step, and nothing moves if any of it is locked. In ArchiMate's graph editor, dragging a box
2125
+ moves its group with it, as one step. A shape's menu offers Group, Ungroup and Lock / Unlock
2126
+ (`arrangeMenuItems(arrangement, ids, act)`, the one definition every editor uses; Ungroup only once unlocked),
2127
+ as do Ctrl+G, Ctrl+Shift+G and Ctrl+L; a locked shape shows a lock and no resize or rotate handles.
2128
+
2129
+ **A background.** A kind of group is an `ItemGroup` subclass, kept by its JSON's `kind`. A `BackgroundGroup` is
2130
+ pinned behind everything else on its view: its members stack below every other item, so "Send to back" on
2131
+ anything else stops just in front of it, and its own members reorder only among themselves.
2132
+ `view.arrangeAsBackground(ids, { name })` (or the undoable `BackgroundCommand`) makes items one locked
2133
+ background group, behind the rest. A canvas made from a template gets its template's boxes this way, so the
2134
+ template stays at the back and holds still until it is unlocked (the shape's menu, **Unlock background**);
2135
+ unlocked, it moves as one; ungrouped, its boxes are ordinary boxes. Archi files keep it: each member carries
2136
+ the group (`renderengine.group`), its kind (`renderengine.groupKind`) and the lock.
2137
+
2138
+ ```js
2139
+ const groupId = view.group(['a', 'b']); // a and b move together
2140
+ view.setLocked(['a'], true); // the group is fixed in place
2141
+ view.arrangement.canMove(['b']); // → false
2142
+ view.history.undo(); // free again
2143
+ view.arrangeAsBackground(['t1', 't2']); // behind everything, locked, moved as one when unlocked
2144
+ ```
2145
+
2146
+ **Connecting and deleting in the template editor** are commands, one undo step each:
2147
+ `editor.connect(sourceId, targetId)` runs `connectCommand(sourceId, targetId)` (a new line), and
2148
+ `deleteTemplate(id)` / `deleteEdge(id)` run `deleteTemplateCommand(id)` (the shape and the lines joined
2149
+ to it) / `deleteEdgeCommand(id)`. A subclass whose shapes and lines stand for something else
2150
+ overrides those factories (ArchimateCanvasEditor does). A template class says whether the editor
2151
+ offers it a rotate handle (`static rotatable`, true by default; ArchiMate's templates are not). `pickFile(accept)` resolves with the chosen `File` (or `null`).
2152
+
2153
+ ## Where to go next
2154
+
2155
+ - **`ARCHITECTURE.md`** — module layout, the chart-type catalog mechanics, how to add a new chart type.
2156
+ - **`CHART_TYPES_ROADMAP.md`** — per-chart-type design rationale and what's deliberately deferred.
2157
+ - **`examples/csv-chart-builder.html`** — every chart type live, including the actual "Live updates"
2158
+ panel this doc's streaming section is drawn from; open it in a browser to see the pattern run.
2159
+ - **`test/charts.html`** — a plainer interactive demo of the core chart set.