@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.
- package/LICENSE +21 -0
- package/README.md +203 -2
- package/REFERENCE.md +2198 -0
- package/dist/renderengine-workspace.umd.js +46905 -0
- package/dist/renderengine.umd.js +42223 -0
- package/examples/archimate.html +66 -0
- package/examples/diagram-editor.html +51 -0
- package/package.json +65 -4
- package/src/BaseRenderer.js +57 -0
- package/src/CanvasRenderer.js +194 -0
- package/src/Mat4.js +111 -0
- package/src/OrbitCamera.js +57 -0
- package/src/RendererFactory.js +50 -0
- package/src/SVGRenderer.js +439 -0
- package/src/archimate.js +723 -0
- package/src/archimateArchiFormat.js +1693 -0
- package/src/archimateArchiTypes.js +72 -0
- package/src/archimateArchive.js +140 -0
- package/src/archimateCSV.js +127 -0
- package/src/archimateDrawio.js +182 -0
- package/src/archimateExchangeFormat.js +546 -0
- package/src/archimateRelationshipTable.js +93 -0
- package/src/archimateSpecializations.js +161 -0
- package/src/chartCatalog.js +589 -0
- package/src/charting/ArchimateDiagramEditor.js +1624 -0
- package/src/charting/ArchimateSpecializationSelector.js +134 -0
- package/src/charting/Axis.js +68 -0
- package/src/charting/CameraSeeder.js +30 -0
- package/src/charting/CatalogDialog.js +92 -0
- package/src/charting/Chart.js +37 -0
- package/src/charting/Chart3D.js +99 -0
- package/src/charting/ChartBox.js +80 -0
- package/src/charting/ChartData.js +27 -0
- package/src/charting/ChartRegistry.js +76 -0
- package/src/charting/CheckboxTreeSelector.js +304 -0
- package/src/charting/ColorField.js +107 -0
- package/src/charting/ColorPalette.js +88 -0
- package/src/charting/CommandHistory.js +176 -0
- package/src/charting/DataFilter.js +52 -0
- package/src/charting/Diagram.js +36 -0
- package/src/charting/Diagram3D.js +29 -0
- package/src/charting/DiagramClipboard.js +31 -0
- package/src/charting/DiagramEditor.js +3330 -0
- package/src/charting/DiagramEditorCommand.js +110 -0
- package/src/charting/DiagramEditorCommands.js +237 -0
- package/src/charting/DiagramEditorEdgeCommands.js +142 -0
- package/src/charting/DiagramEditorState.js +98 -0
- package/src/charting/DiagramRegistry.js +86 -0
- package/src/charting/DiagramTypeRegistry.js +326 -0
- package/src/charting/DiagramViewPropertiesDialog.js +46 -0
- package/src/charting/Edge.js +113 -0
- package/src/charting/ErrorBars.js +37 -0
- package/src/charting/FilterSystem.js +285 -0
- package/src/charting/FindReplaceDialog.js +139 -0
- package/src/charting/FormDialog.js +385 -0
- package/src/charting/Gauge.js +58 -0
- package/src/charting/Hierarchy.js +175 -0
- package/src/charting/HintPanel.js +86 -0
- package/src/charting/ImageSelector.js +148 -0
- package/src/charting/InteractionManager.js +725 -0
- package/src/charting/Legend.js +58 -0
- package/src/charting/MenuBar.js +62 -0
- package/src/charting/ModelFileFormat.js +115 -0
- package/src/charting/OrbitControls.js +77 -0
- package/src/charting/Palette.js +119 -0
- package/src/charting/PopupMenu.js +117 -0
- package/src/charting/PropertiesPanel.js +155 -0
- package/src/charting/PropertyListEditor.js +96 -0
- package/src/charting/PropertyLog.js +92 -0
- package/src/charting/SettingsCascade.js +93 -0
- package/src/charting/SettingsDialog.js +53 -0
- package/src/charting/SettingsManager.js +179 -0
- package/src/charting/SettingsViewPropertiesDialog.js +58 -0
- package/src/charting/ShapePaths.js +27 -0
- package/src/charting/Statistics.js +134 -0
- package/src/charting/TemplateCatalog.js +59 -0
- package/src/charting/TemplateLibrary.js +103 -0
- package/src/charting/TextStyle.js +168 -0
- package/src/charting/ViewFit.js +55 -0
- package/src/charting/ViewMenu.js +70 -0
- package/src/charting/ViewPropertiesDialog.js +77 -0
- package/src/charting/ViewTabStrip.js +81 -0
- package/src/charting/archimate/ArchiFileFormat.js +32 -0
- package/src/charting/archimate/ArchimateCanvasEditor.js +815 -0
- package/src/charting/archimate/ArchimateCsvFileFormat.js +35 -0
- package/src/charting/archimate/ArchimateEdge.js +137 -0
- package/src/charting/archimate/ArchimateElementTemplate.js +195 -0
- package/src/charting/archimate/ArchimatePalette.js +102 -0
- package/src/charting/archimate/CsvImportDialog.js +105 -0
- package/src/charting/archimate/CustomViewpoints.js +74 -0
- package/src/charting/archimate/DerivedEdge.js +29 -0
- package/src/charting/archimate/DrawioFileFormat.js +24 -0
- package/src/charting/archimate/ElementFilterRule.js +21 -0
- package/src/charting/archimate/ExchangeFileFormat.js +28 -0
- package/src/charting/archimate/ViewGroupTemplate.js +137 -0
- package/src/charting/archimate/ViewLineEdge.js +84 -0
- package/src/charting/archimate/ViewpointRule.js +45 -0
- package/src/charting/archimate/archimateFileFormats.js +12 -0
- package/src/charting/archimate/archimateNames.js +5 -0
- package/src/charting/archimate/csvMapping.js +122 -0
- package/src/charting/archimate/elementMenuItems.js +32 -0
- package/src/charting/archimate/elementProperties.js +146 -0
- package/src/charting/archimate/elementTypeChoices.js +7 -0
- package/src/charting/archimate/elementTypeGroups.js +19 -0
- package/src/charting/archimate/index.js +17 -0
- package/src/charting/archimate/specializationCatalogDialog.js +64 -0
- package/src/charting/archimate/typeGlyph.js +29 -0
- package/src/charting/arrangement/Arrangement.js +112 -0
- package/src/charting/arrangement/ArrangementCommand.js +29 -0
- package/src/charting/arrangement/BackgroundCommand.js +41 -0
- package/src/charting/arrangement/BackgroundGroup.js +18 -0
- package/src/charting/arrangement/GroupCommand.js +46 -0
- package/src/charting/arrangement/ItemGroup.js +62 -0
- package/src/charting/arrangement/LockCommand.js +32 -0
- package/src/charting/arrangement/UngroupCommand.js +27 -0
- package/src/charting/arrangement/alignment.js +88 -0
- package/src/charting/arrangement/arrangeMenuItems.js +25 -0
- package/src/charting/arrangement/index.js +11 -0
- package/src/charting/arrangement/layouts.js +233 -0
- package/src/charting/canvas/ArchiHint.js +78 -0
- package/src/charting/canvas/CanvasBoxKind.js +39 -0
- package/src/charting/canvas/CanvasPalette.js +49 -0
- package/src/charting/canvas/CanvasTemplateLibrary.js +61 -0
- package/src/charting/canvas/CanvasTemplateMenu.js +151 -0
- package/src/charting/canvas/EditHintCommand.js +28 -0
- package/src/charting/canvas/canvasBoxKinds.js +27 -0
- package/src/charting/canvas/canvasBoxMenuItems.js +40 -0
- package/src/charting/canvas/defaultCanvasTemplates.js +186 -0
- package/src/charting/canvas/diagramBoxKinds.js +12 -0
- package/src/charting/canvas/hintMenuItems.js +49 -0
- package/src/charting/charts/ArcGauge.js +55 -0
- package/src/charting/charts/AreaChart.js +71 -0
- package/src/charting/charts/BarChart.js +80 -0
- package/src/charting/charts/BarChart3D.js +56 -0
- package/src/charting/charts/BarChart3DOrbit.js +53 -0
- package/src/charting/charts/BarChartBase.js +70 -0
- package/src/charting/charts/BarGauge.js +42 -0
- package/src/charting/charts/BoxPlotChart.js +41 -0
- package/src/charting/charts/BubbleChart.js +99 -0
- package/src/charting/charts/BubbleChart3D.js +78 -0
- package/src/charting/charts/BulletGauge.js +87 -0
- package/src/charting/charts/CalendarHeatmapChart.js +204 -0
- package/src/charting/charts/CandlestickChart.js +54 -0
- package/src/charting/charts/ChordDiagram.js +130 -0
- package/src/charting/charts/DensityChart.js +73 -0
- package/src/charting/charts/DialGauge.js +65 -0
- package/src/charting/charts/DonutChart.js +15 -0
- package/src/charting/charts/FunnelChart.js +47 -0
- package/src/charting/charts/GanttChart.js +201 -0
- package/src/charting/charts/GaugeChart.js +175 -0
- package/src/charting/charts/GeoMapChart.js +146 -0
- package/src/charting/charts/GroupedBarChart.js +88 -0
- package/src/charting/charts/GroupedDistributionChart.js +70 -0
- package/src/charting/charts/HeatmapChart.js +80 -0
- package/src/charting/charts/HierarchyChart.js +39 -0
- package/src/charting/charts/HistogramChart.js +45 -0
- package/src/charting/charts/KpiTile.js +72 -0
- package/src/charting/charts/LagPlotChart.js +41 -0
- package/src/charting/charts/LineChart.js +124 -0
- package/src/charting/charts/MultiRingGauge.js +54 -0
- package/src/charting/charts/OutlineChart.js +95 -0
- package/src/charting/charts/ParallelCoordinatesChart.js +130 -0
- package/src/charting/charts/PieChart.js +56 -0
- package/src/charting/charts/PipeChart.js +88 -0
- package/src/charting/charts/PopulationPyramidChart.js +106 -0
- package/src/charting/charts/QQPlotChart.js +64 -0
- package/src/charting/charts/RadarChart.js +76 -0
- package/src/charting/charts/RadialBarChart.js +96 -0
- package/src/charting/charts/RingGauge.js +23 -0
- package/src/charting/charts/SankeyDiagram.js +174 -0
- package/src/charting/charts/ScatterChart.js +104 -0
- package/src/charting/charts/StackedAreaChart.js +15 -0
- package/src/charting/charts/StackedBarChart.js +33 -0
- package/src/charting/charts/StackedBarChart3DOrbit.js +58 -0
- package/src/charting/charts/StreamGraphChart.js +131 -0
- package/src/charting/charts/SunburstChart.js +57 -0
- package/src/charting/charts/ThermometerGauge.js +43 -0
- package/src/charting/charts/TreemapChart.js +54 -0
- package/src/charting/charts/ViolinChart.js +37 -0
- package/src/charting/charts/WaterfallChart.js +66 -0
- package/src/charting/charts/WordCloudChart.js +104 -0
- package/src/charting/diagramModel/AddViewCommand.js +33 -0
- package/src/charting/diagramModel/DiagramModel.js +39 -0
- package/src/charting/diagramModel/DiagramModelSearch.js +22 -0
- package/src/charting/diagramModel/DiagramView.js +50 -0
- package/src/charting/diagramModel/ModelHistory.js +43 -0
- package/src/charting/diagramModel/ModelReport.js +153 -0
- package/src/charting/diagramModel/ModelSearch.js +223 -0
- package/src/charting/diagramModel/RemoveViewCommand.js +25 -0
- package/src/charting/diagramModel/RenameViewCommand.js +21 -0
- package/src/charting/diagramModel/ReplaceTextCommand.js +25 -0
- package/src/charting/diagramModel/ViewSetChecker.js +34 -0
- package/src/charting/diagramModel/ViewSetModel.js +243 -0
- package/src/charting/diagramModel/index.js +6 -0
- package/src/charting/diagramModel/nameTransforms.js +29 -0
- package/src/charting/diagrams/ArchimateCommands.js +141 -0
- package/src/charting/diagrams/ArchimateDiagram.js +263 -0
- package/src/charting/diagrams/ArchimateModel.js +1155 -0
- package/src/charting/diagrams/ArchimateModelChecker.js +125 -0
- package/src/charting/diagrams/ArchimateModelReport.js +128 -0
- package/src/charting/diagrams/ArchimateModelSearch.js +41 -0
- package/src/charting/diagrams/ArchimateValidator.js +357 -0
- package/src/charting/diagrams/CanvasView.js +112 -0
- package/src/charting/diagrams/ContextMenu.js +37 -0
- package/src/charting/diagrams/DataModelDiagram.js +86 -0
- package/src/charting/diagrams/DataModelRelationshipStrategy.js +38 -0
- package/src/charting/diagrams/DerivedRelationships.js +102 -0
- package/src/charting/diagrams/EbnfDiagram.js +39 -0
- package/src/charting/diagrams/EdgeGeometry.js +553 -0
- package/src/charting/diagrams/EditViewItemCommand.js +128 -0
- package/src/charting/diagrams/ElementInstance.js +157 -0
- package/src/charting/diagrams/GraphDiagram.js +1340 -0
- package/src/charting/diagrams/GraphDiagram3D.js +152 -0
- package/src/charting/diagrams/GraphMetrics.js +81 -0
- package/src/charting/diagrams/GridOverlay.js +137 -0
- package/src/charting/diagrams/ImportRowsCommand.js +62 -0
- package/src/charting/diagrams/LabelHelper.js +108 -0
- package/src/charting/diagrams/MergeElementsCommand.js +101 -0
- package/src/charting/diagrams/NestInstanceCommand.js +34 -0
- package/src/charting/diagrams/NodeGeometry.js +158 -0
- package/src/charting/diagrams/PlaceItemsCommand.js +77 -0
- package/src/charting/diagrams/RadialLayout.js +101 -0
- package/src/charting/diagrams/RelationshipInstance.js +93 -0
- package/src/charting/diagrams/RemoveGroupFromViewCommand.js +28 -0
- package/src/charting/diagrams/RemoveLineCommand.js +27 -0
- package/src/charting/diagrams/ShowElementCommand.js +43 -0
- package/src/charting/diagrams/SvgCoordinate.js +17 -0
- package/src/charting/diagrams/SvgZoom.js +127 -0
- package/src/charting/diagrams/Tooltip.js +62 -0
- package/src/charting/diagrams/ViewConnectionInstance.js +102 -0
- package/src/charting/diagrams/ViewObjectInstance.js +56 -0
- package/src/charting/diagrams/commands/AddEdgeCommand.js +34 -0
- package/src/charting/diagrams/commands/AddNodeCommand.js +28 -0
- package/src/charting/diagrams/commands/AddWaypointCommand.js +27 -0
- package/src/charting/diagrams/commands/CreateFolderCommand.js +27 -0
- package/src/charting/diagrams/commands/DeleteEdgeCommand.js +30 -0
- package/src/charting/diagrams/commands/DeleteFolderCommand.js +47 -0
- package/src/charting/diagrams/commands/DeleteNodeCommand.js +35 -0
- package/src/charting/diagrams/commands/DeleteWaypointCommand.js +24 -0
- package/src/charting/diagrams/commands/GraphCommand.js +65 -0
- package/src/charting/diagrams/commands/InvertEdgeCommand.js +24 -0
- package/src/charting/diagrams/commands/MoveNodeCommand.js +44 -0
- package/src/charting/diagrams/commands/MoveWaypointCommand.js +27 -0
- package/src/charting/diagrams/commands/RenameEdgeCommand.js +23 -0
- package/src/charting/diagrams/commands/RenameFolderCommand.js +31 -0
- package/src/charting/diagrams/commands/RenameNodeCommand.js +27 -0
- package/src/charting/diagrams/commands/ResizeNodeCommand.js +46 -0
- package/src/charting/diagrams/commands/UpdateEdgeSideCommand.js +40 -0
- package/src/charting/diagrams/commands/index.js +17 -0
- package/src/charting/diagrams/duplicates.js +49 -0
- package/src/charting/diagrams/nesting.js +30 -0
- package/src/charting/diagrams/roadmap.js +77 -0
- package/src/charting/edgeMarkers/ArrowMarker.js +15 -0
- package/src/charting/edgeMarkers/CircleMarker.js +13 -0
- package/src/charting/edgeMarkers/CrowsFootMarker.js +16 -0
- package/src/charting/edgeMarkers/DiamondMarker.js +15 -0
- package/src/charting/edgeMarkers/EdgeMarker.js +34 -0
- package/src/charting/edgeMarkers/HalfArrowMarker.js +8 -0
- package/src/charting/edgeMarkers/HollowDiamondMarker.js +18 -0
- package/src/charting/edgeMarkers/HollowTriangleMarker.js +18 -0
- package/src/charting/edgeMarkers/OpenArrowMarker.js +17 -0
- package/src/charting/edgeMarkers/index.js +27 -0
- package/src/charting/edgeStyle/EdgeStyleCascade.js +22 -0
- package/src/charting/edgeStyle/EdgeStyleDialog.js +51 -0
- package/src/charting/edgeStyle/EdgeStyleSchema.js +43 -0
- package/src/charting/edgeStyle/SetStyleCommand.js +12 -0
- package/src/charting/edgeStyle/index.js +4 -0
- package/src/charting/emphasis/EmphasisRule.js +42 -0
- package/src/charting/emphasis/HighlightRule.js +24 -0
- package/src/charting/gauges/ArcTrack.js +51 -0
- package/src/charting/gauges/GaugeAnimation.js +51 -0
- package/src/charting/gauges/GaugeBands.js +48 -0
- package/src/charting/gauges/GaugeScale.js +87 -0
- package/src/charting/gauges/GaugeTrack.js +44 -0
- package/src/charting/gauges/GaugeValue.js +61 -0
- package/src/charting/gauges/HorizontalTrack.js +14 -0
- package/src/charting/gauges/LinearTrack.js +29 -0
- package/src/charting/gauges/Sparkline.js +42 -0
- package/src/charting/gauges/VerticalTrack.js +17 -0
- package/src/charting/glyphs/ArrowGlyph.js +21 -0
- package/src/charting/glyphs/BatteryGlyph.js +18 -0
- package/src/charting/glyphs/ChevronGlyph.js +18 -0
- package/src/charting/glyphs/CircleGlyph.js +15 -0
- package/src/charting/glyphs/CloudGlyph.js +19 -0
- package/src/charting/glyphs/ComponentGlyph.js +18 -0
- package/src/charting/glyphs/ConstraintGlyph.js +24 -0
- package/src/charting/glyphs/ContractBoxGlyph.js +10 -0
- package/src/charting/glyphs/CubeGlyph.js +19 -0
- package/src/charting/glyphs/CylinderGlyph.js +19 -0
- package/src/charting/glyphs/DashedDoubleArrowGlyph.js +11 -0
- package/src/charting/glyphs/DeviceGlyph.js +17 -0
- package/src/charting/glyphs/DiamondGlyph.js +17 -0
- package/src/charting/glyphs/DocumentGlyph.js +17 -0
- package/src/charting/glyphs/DoubleArrowGlyph.js +23 -0
- package/src/charting/glyphs/DoubleCircleGlyph.js +24 -0
- package/src/charting/glyphs/EllipseGlyph.js +18 -0
- package/src/charting/glyphs/FactoryGlyph.js +20 -0
- package/src/charting/glyphs/FlagGlyph.js +16 -0
- package/src/charting/glyphs/GapCircleGlyph.js +17 -0
- package/src/charting/glyphs/GearGlyph.js +37 -0
- package/src/charting/glyphs/Glyph.js +98 -0
- package/src/charting/glyphs/GroupingBoxGlyph.js +13 -0
- package/src/charting/glyphs/HalfCircleGlyph.js +22 -0
- package/src/charting/glyphs/HexagonGlyph.js +28 -0
- package/src/charting/glyphs/IconGlyph.js +22 -0
- package/src/charting/glyphs/ImageGlyph.js +16 -0
- package/src/charting/glyphs/JunctionGlyph.js +17 -0
- package/src/charting/glyphs/LollipopGlyph.js +22 -0
- package/src/charting/glyphs/MagnifierGlyph.js +17 -0
- package/src/charting/glyphs/NoteGlyph.js +33 -0
- package/src/charting/glyphs/ObjectBoxGlyph.js +21 -0
- package/src/charting/glyphs/OrJunctionGlyph.js +17 -0
- package/src/charting/glyphs/ParallelogramGlyph.js +20 -0
- package/src/charting/glyphs/PersonGlyph.js +23 -0
- package/src/charting/glyphs/PillGlyph.js +23 -0
- package/src/charting/glyphs/PinGlyph.js +18 -0
- package/src/charting/glyphs/PrincipleGlyph.js +23 -0
- package/src/charting/glyphs/ProductBoxGlyph.js +16 -0
- package/src/charting/glyphs/RectGlyph.js +15 -0
- package/src/charting/glyphs/RoundedBoxGlyph.js +13 -0
- package/src/charting/glyphs/StackGlyph.js +14 -0
- package/src/charting/glyphs/StepsGlyph.js +20 -0
- package/src/charting/glyphs/StickFigureGlyph.js +32 -0
- package/src/charting/glyphs/SystemSoftwareBoxGlyph.js +16 -0
- package/src/charting/glyphs/TabbedBoxGlyph.js +37 -0
- package/src/charting/glyphs/TargetGlyph.js +23 -0
- package/src/charting/glyphs/WavyBoxGlyph.js +17 -0
- package/src/charting/glyphs/WheelGlyph.js +28 -0
- package/src/charting/glyphs/drawGlyph.js +28 -0
- package/src/charting/glyphs/glyphSvg.js +55 -0
- package/src/charting/glyphs/index.js +109 -0
- package/src/charting/modelTree/AnalysisPanel.js +159 -0
- package/src/charting/modelTree/DiagramTabs.js +217 -0
- package/src/charting/modelTree/ModelTree.js +164 -0
- package/src/charting/modelTree/ModelTreePanel.js +914 -0
- package/src/charting/modelTree/TreeBuilder.js +238 -0
- package/src/charting/modelTree/TreeModeRegistry.js +51 -0
- package/src/charting/modelTree/TreeViewMode.js +110 -0
- package/src/charting/modelTree/buildFilterTrees.js +138 -0
- package/src/charting/modelTree/modes/FlatTreeMode.js +46 -0
- package/src/charting/modelTree/modes/LayerGroupedTreeMode.js +62 -0
- package/src/charting/modelTree/modes/TypeGroupedTreeMode.js +60 -0
- package/src/charting/modelTree/treeLayers.js +11 -0
- package/src/charting/report/reportHtml.js +82 -0
- package/src/charting/report/reportViewer.js +118 -0
- package/src/charting/shapes/CircleShape.js +19 -0
- package/src/charting/shapes/PathShape.js +17 -0
- package/src/charting/shapes/RectShape.js +20 -0
- package/src/charting/shapes/Shape.js +20 -0
- package/src/charting/shapes/ShapeFactory.js +23 -0
- package/src/charting/storage/LocalJsonStore.js +43 -0
- package/src/charting/templates/ActorTemplate.js +36 -0
- package/src/charting/templates/BoxTemplate.js +27 -0
- package/src/charting/templates/CircleTemplate.js +18 -0
- package/src/charting/templates/ClassTemplate.js +20 -0
- package/src/charting/templates/CloudTemplate.js +43 -0
- package/src/charting/templates/ComponentTemplate.js +19 -0
- package/src/charting/templates/CylinderTemplate.js +30 -0
- package/src/charting/templates/DataStoreTemplate.js +16 -0
- package/src/charting/templates/DiamondTemplate.js +10 -0
- package/src/charting/templates/DocumentTemplate.js +32 -0
- package/src/charting/templates/EllipseTemplate.js +15 -0
- package/src/charting/templates/FinalStateTemplate.js +12 -0
- package/src/charting/templates/GlyphTemplate.js +47 -0
- package/src/charting/templates/HexagonTemplate.js +17 -0
- package/src/charting/templates/LifelineTemplate.js +20 -0
- package/src/charting/templates/ManualInputTemplate.js +12 -0
- package/src/charting/templates/NoteTemplate.js +23 -0
- package/src/charting/templates/OctagonTemplate.js +7 -0
- package/src/charting/templates/PackageTemplate.js +25 -0
- package/src/charting/templates/ParallelogramTemplate.js +11 -0
- package/src/charting/templates/PentagonTemplate.js +5 -0
- package/src/charting/templates/PolygonTemplate.js +18 -0
- package/src/charting/templates/PredefinedProcessTemplate.js +13 -0
- package/src/charting/templates/RectangleTemplate.js +28 -0
- package/src/charting/templates/RegularPolygonTemplate.js +21 -0
- package/src/charting/templates/RoundedRectangleTemplate.js +5 -0
- package/src/charting/templates/StadiumTemplate.js +8 -0
- package/src/charting/templates/StarTemplate.js +17 -0
- package/src/charting/templates/SwimlaneTemplate.js +18 -0
- package/src/charting/templates/Template.js +208 -0
- package/src/charting/templates/TrapezoidTemplate.js +11 -0
- package/src/charting/templates/TriangleTemplate.js +12 -0
- package/src/charting/templates/index.js +31 -0
- package/src/charting/templates/outline.js +59 -0
- package/src/charting/templates/svg.js +19 -0
- package/src/charting/textToShapes.js +61 -0
- package/src/charting/toolbar/ToolbarButton.js +38 -0
- package/src/charting/toolbar/ToolbarCheckbox.js +31 -0
- package/src/charting/toolbar/ToolbarItem.js +19 -0
- package/src/charting/toolbar/ToolbarMenu.js +36 -0
- package/src/charting/toolbar/ToolbarSelect.js +38 -0
- package/src/charting/toolbar/ToolbarSeparator.js +7 -0
- package/src/charting/toolbar/ToolbarText.js +22 -0
- package/src/charting/toolbar/index.js +8 -0
- package/src/charting/toolbar/pickFile.js +14 -0
- package/src/charting/viewDecorations.js +85 -0
- package/src/charting/zOrder.js +57 -0
- package/src/csvTable.js +61 -0
- package/src/datamodel.js +122 -0
- package/src/datawindow/BoundedWindow.js +36 -0
- package/src/datawindow/FlatDataWindow.js +34 -0
- package/src/datawindow/GraphDataWindow.js +40 -0
- package/src/datawindow/GroupedSampleDataWindow.js +16 -0
- package/src/datawindow/SeriesDataWindow.js +42 -0
- package/src/datawindow/index.js +31 -0
- package/src/diagramCatalog.js +30 -0
- package/src/ebnfDiagram.js +57 -0
- package/src/ebnfParser.js +129 -0
- package/src/exportImage.js +233 -0
- package/src/index.js +2636 -0
- package/src/itIcons.js +84 -0
- package/src/kernelExpr.js +243 -0
- package/src/propertiesDialog.js +463 -0
- package/src/railroadDiagram.js +571 -0
- package/src/treeModeCatalog.js +16 -0
- package/src/workspace/ContextBus.js +79 -0
- package/src/workspace/EditorShell.js +67 -0
- package/src/workspace/Part.js +163 -0
- package/src/workspace/PartFactory.js +49 -0
- package/src/workspace/PartRegistry.js +86 -0
- package/src/workspace/Workspace.js +321 -0
- package/src/workspace/archimateServices.js +33 -0
- package/src/workspace/dom.js +120 -0
- package/src/workspace/index.js +75 -0
- package/src/workspace/layout/GridNode.js +280 -0
- package/src/workspace/layout/LayoutNode.js +89 -0
- package/src/workspace/layout/PartNode.js +77 -0
- package/src/workspace/layout/SplitNode.js +114 -0
- package/src/workspace/layout/TabsNode.js +191 -0
- package/src/workspace/layout/index.js +24 -0
- package/src/workspace/parts/AdapterPart.js +33 -0
- package/src/workspace/parts/ArchimateCanvasPart.js +345 -0
- package/src/workspace/parts/ArchimateEditorPart.js +79 -0
- package/src/workspace/parts/ChartPart.js +115 -0
- package/src/workspace/parts/ContentPart.js +34 -0
- package/src/workspace/parts/DetailPart.js +46 -0
- package/src/workspace/parts/DiagramEditorPart.js +135 -0
- package/src/workspace/parts/EditorPart.js +388 -0
- package/src/workspace/parts/HtmlPart.js +25 -0
- package/src/workspace/parts/ListPart.js +57 -0
- package/src/workspace/parts/ModelFile.js +187 -0
- package/src/workspace/parts/ModelsPart.js +135 -0
- package/src/workspace/styles.js +258 -0
- package/src/workspace/themes.js +96 -0
- package/src/workspace/workArea.js +413 -0
- package/src/workspace/workAreaModel.js +197 -0
- package/src/workspace.js +16 -0
- package/src/zip/ZipArchive.js +160 -0
- package/src/zip/crc32.js +25 -0
- 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 `
`.
|
|
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.
|