@peter.prib/renderengine 0.0.0-stage → 0.2.0

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