From f019974a316ee01a42a8405f432cffbf8b2f1ccf Mon Sep 17 00:00:00 2001 From: Michael Baur Date: Wed, 13 Dec 2023 11:34:27 +0100 Subject: [PATCH] yFiles for HTML 2.6.0.3 demos --- demos/.prettierignore | 4 +- demos/.prettierrc.json | 6 +- demos/README.html | 4 +- demos/analysis/clustering/ClusteringDemo.js | 30 +- demos/analysis/clustering/ClusteringDemo.ts | 28 +- demos/analysis/clustering/DemoVisuals.js | 4 +- demos/analysis/clustering/DemoVisuals.ts | 14 +- .../analysis/clustering/DendrogramSupport.js | 30 +- .../analysis/clustering/DendrogramSupport.ts | 30 +- demos/analysis/clustering/VoronoiDiagram.js | 30 +- demos/analysis/clustering/VoronoiDiagram.ts | 30 +- demos/analysis/clustering/index.html | 2 +- .../CriticalPathAnalysisDemo.js | 4 +- .../CriticalPathAnalysisDemo.ts | 4 +- .../CriticalPathHelper.js | 20 +- .../CriticalPathHelper.ts | 14 +- .../analysis/criticalpathanalysis/index.html | 2 +- .../intersection-detection/DemoVisuals.js | 7 - .../intersection-detection/DemoVisuals.ts | 7 - .../IntersectionDetectionDemo.js | 28 +- .../IntersectionDetectionDemo.ts | 28 +- .../intersection-detection/TooltipHelper.js | 2 +- .../intersection-detection/TooltipHelper.ts | 2 +- .../intersection-detection/index.html | 2 +- demos/analysis/networkflows/DemoStyles.js | 6 +- demos/analysis/networkflows/DemoStyles.ts | 9 +- .../analysis/networkflows/NetworkFlowsDemo.js | 86 +- .../analysis/networkflows/NetworkFlowsDemo.ts | 82 +- demos/analysis/networkflows/index.html | 2 +- .../analysis/transitivity/TransitivityDemo.js | 26 +- .../analysis/transitivity/TransitivityDemo.ts | 26 +- demos/analysis/transitivity/index.html | 2 +- .../accessibility/AccessibilityDemo.js | 20 +- .../accessibility/AccessibilityDemo.ts | 20 +- .../accessibility/index.html | 2 +- .../background-image/BackgroundImageDemo.js | 16 +- .../background-image/BackgroundImageDemo.ts | 16 +- .../background-image/index.html | 2 +- .../BuildingGraphFromDataDemo.js | 12 +- .../building-graph-from-data/index.html | 2 +- .../BuildingSwimlanesFromDataDemo.js | 12 +- .../building-swimlanes-from-data/index.html | 2 +- .../ComplexHighlightDecoratorDemo.js | 18 +- .../ComplexHighlightDecoratorDemo.ts | 18 +- .../complex-highlight-decorator/index.html | 2 +- .../custom-graphml/CustomGraphMLDemo.js | 18 +- .../custom-graphml/CustomGraphMLDemo.ts | 16 +- .../custom-graphml/index.html | 2 +- .../drag-and-drop/SimpleDragAndDropDemo.js | 24 +- .../drag-and-drop/SimpleDragAndDropDemo.ts | 16 +- .../drag-and-drop/index.html | 2 +- .../external-links/ExternalLinksDemo.js | 20 +- .../external-links/ExternalLinksDemo.ts | 18 +- .../external-links/LinkItemHoverInputMode.js | 2 +- .../external-links/index.html | 2 +- .../file-operations/index.html | 2 +- .../file-operations/json-support.js | 40 +- .../file-operations/json-support.ts | 38 +- .../FilteringWithFoldingDemo.js | 32 +- .../FilteringWithFoldingDemo.ts | 28 +- .../filtering-with-folding/index.html | 2 +- .../filtering/FilteringDemo.js | 32 +- .../filtering/FilteringDemo.ts | 28 +- .../application-features/filtering/index.html | 2 +- .../folding/FoldingDemo.js | 16 +- .../folding/FoldingDemo.ts | 16 +- demos/application-features/folding/index.html | 2 +- .../graph-copy/GraphCopyDemo.js | 18 +- .../graph-copy/GraphCopyDemo.ts | 16 +- .../graph-copy/index.html | 2 +- .../graph-decorator/GraphDecoratorDemo.js | 18 +- .../graph-decorator/GraphDecoratorDemo.ts | 16 +- .../graph-decorator/index.html | 2 +- .../graph-search/GraphSearchDemo.js | 16 +- .../graph-search/GraphSearchDemo.ts | 14 +- .../graph-search/index.html | 2 +- .../grid-snapping/GridSnappingDemo.js | 18 +- .../grid-snapping/GridSnappingDemo.ts | 16 +- .../grid-snapping/index.html | 2 +- .../input-output/InputOutputDemo.js | 16 +- .../input-output/InputOutputDemo.ts | 16 +- .../input-output/index.html | 2 +- .../InteractiveAggregationDemo.js | 44 +- .../InteractiveAggregationDemo.ts | 28 +- .../interactiveaggregation/index.html | 2 +- .../label-text-wrapping/index.html | 2 +- .../native-listeners/NativeListenersDemo.js | 16 +- .../native-listeners/NativeListenersDemo.ts | 16 +- .../native-listeners/NodeStyleDecorator.js | 2 +- .../native-listeners/index.html | 2 +- .../orthogonal-edges/OrthogonalEdgesDemo.js | 16 +- .../orthogonal-edges/OrthogonalEdgesDemo.ts | 16 +- .../orthogonal-edges/index.html | 2 +- .../overview/OverviewComponentDemo.js | 16 +- .../overview/OverviewComponentDemo.ts | 16 +- .../application-features/overview/index.html | 2 +- .../RectangularIndicatorDemo.js | 16 +- .../RectangularIndicatorDemo.ts | 16 +- .../rectangular-indicator/index.html | 2 +- .../rotatablenodes/RotatableNodeLabels.js | 8 +- .../rotatablenodes/RotatableNodeLabels.ts | 8 +- .../rotatablenodes/RotatableNodes.js | 35 +- .../rotatablenodes/RotatableNodes.ts | 40 +- .../rotatablenodes/RotatableNodesDemo.js | 24 +- .../rotatablenodes/RotatableNodesDemo.ts | 22 +- .../rotatablenodes/RotatablePorts.js | 7 - .../rotatablenodes/RotatablePorts.ts | 7 - .../rotatablenodes/RotatedNodeLayoutStage.js | 26 +- .../rotatablenodes/RotatedNodeLayoutStage.ts | 25 +- .../RotationAwareGroupBoundsCalculator.js | 2 +- .../RotationAwareGroupBoundsCalculator.ts | 2 +- .../rotatablenodes/index.html | 2 +- .../SimpleHighlightDecoratorDemo.js | 16 +- .../SimpleHighlightDecoratorDemo.ts | 16 +- .../simple-highlight-decorator/index.html | 2 +- .../smart-click-navigation/index.html | 2 +- .../snapping/SnappingDemo.js | 16 +- .../snapping/SnappingDemo.ts | 16 +- .../application-features/snapping/index.html | 2 +- .../SubdivideEdgeDropInputMode.js | 6 +- .../SubdivideEdgeDropInputMode.ts | 6 +- .../subdivide-edges/SubdivideEdgesDemo.js | 8 +- .../subdivide-edges/SubdivideEdgesDemo.ts | 8 +- .../subdivide-edges/index.html | 2 +- .../tableeditor/DragAndDropSupport.js | 4 +- .../tableeditor/DragAndDropSupport.ts | 4 +- .../tableeditor/TableEditorDemo.js | 10 +- .../tableeditor/TableEditorDemo.ts | 10 +- .../tableeditor/index.html | 2 +- .../theming/ThemingDemo.js | 20 +- .../theming/ThemingDemo.ts | 20 +- demos/application-features/theming/index.html | 2 +- .../tooltips/TooltipsDemo.js | 20 +- .../tooltips/TooltipsDemo.ts | 20 +- .../application-features/tooltips/index.html | 2 +- .../webgl-rendering/WebGLRenderingDemo.js | 4 +- .../webgl-rendering/WebGLRenderingDemo.ts | 2 +- .../webgl-rendering/index.html | 2 +- .../EditAdjacencyNodeSourceDialog.js | 2 +- .../EditAdjacencyNodeSourceDialog.ts | 2 +- .../adjacencygraphbuilder/ModelClasses.js | 6 +- .../adjacencygraphbuilder/SchemaComponent.js | 25 +- .../adjacencygraphbuilder/SchemaComponent.ts | 13 +- .../adjacencygraphbuilder/index.html | 2 +- .../graphbuilder/EditSourceDialog.js | 2 +- .../graphbuilder/EditSourceDialog.ts | 2 +- .../graphbuilder/GraphBuilderDemo.js | 8 +- .../graphbuilder/GraphBuilderDemo.ts | 8 +- .../databinding/graphbuilder/ModelClasses.js | 14 +- .../databinding/graphbuilder/ModelClasses.ts | 8 +- .../graphbuilder/SourcesListBox.js | 5 +- .../graphbuilder/SourcesListBox.ts | 5 +- demos/databinding/graphbuilder/index.html | 20 +- .../AdjacencyGraphBuilder.js | 8 +- .../AdjacencyGraphBuilder.ts | 13 +- .../index.html | 2 +- .../port-aware-graph-builder/GraphBuilder.js | 16 +- .../port-aware-graph-builder/GraphBuilder.ts | 16 +- .../port-aware-graph-builder/index.html | 2 +- .../port-aware-tree-builder/TreeBuilder.js | 8 +- .../port-aware-tree-builder/TreeBuilder.ts | 8 +- .../port-aware-tree-builder/index.html | 2 +- .../SimpleGraphBuilderDemo.js | 20 +- .../SimpleGraphBuilderDemo.ts | 14 +- .../simplegraphbuilder/data-view.css | 4 +- .../databinding/simplegraphbuilder/index.html | 2 +- .../treebuilder/EditTreeNodeSourceDialog.js | 2 +- .../treebuilder/EditTreeNodeSourceDialog.ts | 2 +- demos/databinding/treebuilder/ModelClasses.js | 6 +- .../treebuilder/SchemaComponent.js | 23 +- .../treebuilder/SchemaComponent.ts | 15 +- demos/databinding/treebuilder/index.html | 2 +- .../button-input-mode/ButtonInputMode.js | 6 +- .../button-input-mode/ButtonInputMode.ts | 6 +- .../button-input-mode/ButtonInputModeDemo.js | 8 +- .../button-input-mode/ButtonInputModeDemo.ts | 8 +- .../OffsetLabelModelWrapper.js | 2 +- .../OffsetLabelModelWrapper.ts | 2 +- demos/input/button-input-mode/index.html | 6 +- .../ClearAreaLayoutHelper.js | 18 +- .../ClearAreaLayoutHelper.ts | 14 +- .../ComponentDragAndDropDemo.js | 30 +- .../ComponentDragAndDropDemo.ts | 20 +- demos/input/componentdraganddrop/index.html | 2 +- demos/input/contextmenu/ContextMenuDemo.js | 2 +- demos/input/contextmenu/ContextMenuDemo.ts | 2 +- demos/input/contextmenu/index.html | 2 +- .../CustomDragAndDropDemo.js | 6 +- .../CustomDragAndDropDemo.ts | 6 +- demos/input/custom-drag-and-drop/index.html | 2 +- .../ArrowNodeStyleAngleHandle.js | 6 +- .../ArrowNodeStyleAngleHandle.ts | 11 +- .../ArrowNodeStyleShaftRatioHandle.ts | 5 +- .../CustomHandleProviderDemo.js | 4 +- .../CustomHandleProviderDemo.ts | 4 +- demos/input/custom-handle-provider/index.html | 2 +- .../CustomEdgeCreationDemo.js | 12 +- .../CustomEdgeCreationDemo.ts | 12 +- demos/input/customedgecreation/index.html | 2 +- .../customlabelmodel/CustomNodeLabelModel.js | 2 +- .../customlabelmodel/CustomNodeLabelModel.ts | 7 +- demos/input/customlabelmodel/index.html | 2 +- .../CustomNodePortLocationModel.js | 2 +- .../CustomNodePortLocationModel.ts | 2 +- .../customportmodel/CustomPortModelDemo.js | 2 +- demos/input/customportmodel/index.html | 2 +- .../AdditionalSnapLineMoveInputMode.js | 2 +- .../AdditionalSnapLineMoveInputMode.ts | 2 +- .../AdditionalSnapLineVisualCreator.ts | 5 +- .../customsnapping/CustomSnappingDemo.js | 8 +- .../customsnapping/CustomSnappingDemo.ts | 8 +- .../OrthogonalLabelSnapLineProviderWrapper.js | 4 +- .../OrthogonalLabelSnapLineProviderWrapper.ts | 4 +- demos/input/customsnapping/index.html | 2 +- .../DragFromComponentDemo.js | 10 +- .../DragFromComponentDemo.ts | 10 +- .../drag-from-component/NodeDragInputMode.js | 10 +- .../drag-from-component/NodeDragInputMode.ts | 10 +- demos/input/drag-from-component/index.html | 2 +- demos/input/draganddrop/DragAndDropDemo.js | 22 +- demos/input/draganddrop/DragAndDropDemo.ts | 18 +- .../draganddrop/NativeDragAndDropPanel.js | 8 +- .../draganddrop/NativeDragAndDropPanel.ts | 8 +- demos/input/draganddrop/index.html | 2 +- .../BlueEdgePortCandidateProvider.js | 8 +- .../BlueEdgePortCandidateProvider.ts | 8 +- ...geReconnectionPortCandidateProviderDemo.js | 4 +- ...geReconnectionPortCandidateProviderDemo.ts | 4 +- .../GreenEdgePortCandidateProvider.js | 2 +- .../GreenEdgePortCandidateProvider.ts | 2 +- .../OrangeEdgePortCandidateProvider.js | 4 +- .../OrangeEdgePortCandidateProvider.ts | 4 +- demos/input/edgereconnection/index.html | 2 +- .../GraphDragAndDropDemo.js | 6 +- .../GraphDragAndDropDemo.ts | 4 +- .../graph-drag-and-drop/GraphDropInputMode.js | 14 +- .../graph-drag-and-drop/GraphDropInputMode.ts | 10 +- demos/input/graph-drag-and-drop/index.html | 2 +- .../InteractiveGraphRestructuringDemo.js | 10 +- .../InteractiveGraphRestructuringDemo.ts | 4 +- .../RelocateSubtreeLayoutHelper.js | 28 +- .../RelocateSubtreeLayoutHelper.ts | 8 +- .../interactivegraphrestructuring/Subtree.js | 4 +- .../SubtreePositionHandler.js | 8 +- .../SubtreePositionHandler.ts | 4 +- .../interactivegraphrestructuring/index.html | 2 +- demos/input/labelediting/LabelEditingDemo.js | 24 +- demos/input/labelediting/LabelEditingDemo.ts | 24 +- demos/input/labelediting/index.html | 2 +- .../LabelHandleProviderDemo.js | 10 +- .../LabelHandleProviderDemo.ts | 2 +- .../labelhandleprovider/LabelResizeHandle.ts | 5 +- demos/input/labelhandleprovider/index.html | 2 +- .../lassoselection/LassoSelectionDemo.js | 18 +- .../lassoselection/LassoSelectionDemo.ts | 18 +- demos/input/lassoselection/index.html | 2 +- .../magnifying-glass/MagnifyingGlassDemo.js | 11 +- .../magnifying-glass/MagnifyingGlassDemo.ts | 26 +- demos/input/magnifying-glass/index.html | 2 +- demos/input/marquee-node-creation/index.html | 2 +- .../MoveUnselectedNodesDemo.js | 2 +- .../MoveUnselectedNodesDemo.ts | 7 +- demos/input/moveunselectednodes/index.html | 2 +- .../NodeSelectionResizingDemo.js | 16 +- .../NodeSelectionResizingDemo.ts | 10 +- .../NodeSelectionResizingInputMode.js | 22 +- .../NodeSelectionResizingInputMode.ts | 16 +- demos/input/nodeselectionresizing/index.html | 2 +- .../BlueBendCreator.js | 2 +- .../BlueBendCreator.ts | 2 +- .../OrthogonalEdgeEditingCustomizationDemo.js | 18 +- .../OrthogonalEdgeEditingCustomizationDemo.ts | 18 +- .../index.html | 2 +- .../BluePortCandidateProvider.js | 2 +- .../BluePortCandidateProvider.ts | 2 +- .../GreenPortCandidateProvider.js | 2 +- .../GreenPortCandidateProvider.ts | 2 +- .../PortCandidateProviderDemo.js | 6 +- .../PortCandidateProviderDemo.ts | 6 +- demos/input/portcandidateprovider/index.html | 2 +- .../LimitedRectangleDescriptor.js | 7 - .../LimitedRectangleDescriptor.ts | 7 - demos/input/positionhandler/index.html | 2 +- demos/input/reparenthandler/index.html | 2 +- .../ReshapeHandleProviderConfigurationDemo.js | 6 +- .../reshapehandleconfiguration/index.html | 2 +- .../ReshapeHandleProviderDemo.js | 4 +- .../ReshapeHandleProviderDemo.ts | 4 +- demos/input/reshapehandleprovider/index.html | 2 +- .../RestrictedEditingDemo.js | 2 +- .../RestrictedEditingDemo.ts | 2 +- demos/input/restricted-editing/index.html | 2 +- .../singleselection/SingleSelectionDemo.js | 8 +- .../singleselection/SingleSelectionDemo.ts | 8 +- demos/input/singleselection/index.html | 2 +- .../SizeConstraintProviderDemo.js | 2 +- .../SizeConstraintProviderDemo.ts | 2 +- demos/input/sizeconstraintprovider/index.html | 2 +- .../touchcustomization/DialContextMenu.js | 14 +- .../touchcustomization/DialContextMenu.ts | 10 +- .../input/touchcustomization/TouchEditor.css | 12 +- .../touchcustomization/TouchEditorDemo.js | 24 +- .../touchcustomization/TouchEditorDemo.ts | 22 +- demos/input/touchcustomization/index.html | 2 +- .../ValidBeginCursorsDemo.js | 16 +- .../ValidBeginCursorsDemo.ts | 16 +- demos/input/valid-begin-cursors/index.html | 2 +- demos/layout-features/cactus/Cactus.js | 2 +- demos/layout-features/cactus/Cactus.ts | 2 +- .../cactus/SampleApplication.js | 10 +- .../cactus/SampleApplication.ts | 2 +- demos/layout-features/cactus/index.html | 2 +- .../compact-disk-groups/CompactDiskGroups.js | 2 +- .../compact-disk-groups/CompactDiskGroups.ts | 2 +- .../compact-disk-groups/SampleApplication.js | 6 +- .../compact-disk-groups/SampleApplication.ts | 2 +- .../compact-disk-groups/index.html | 2 +- .../CompactTabularLayout.js | 4 +- .../CompactTabularLayout.ts | 4 +- .../compact-tabular-layout/index.html | 2 +- .../edge-router-buses/EdgeRouterBuses.js | 6 +- .../edge-router-buses/EdgeRouterBuses.ts | 6 +- .../edge-router-buses/index.html | 2 +- .../EdgeRouterIncremental.js | 2 +- .../EdgeRouterIncremental.ts | 2 +- .../edge-router-incremental/index.html | 2 +- .../layout-features/edge-router/EdgeRouter.js | 10 +- .../layout-features/edge-router/EdgeRouter.ts | 6 +- demos/layout-features/edge-router/index.html | 2 +- .../hierarchic-compact-groups/index.html | 2 +- .../HierarchicConstraints.js | 4 +- .../HierarchicConstraints.ts | 4 +- .../hierarchic-constraints/index.html | 2 +- .../HierarchicEdgeGrouping.js | 4 +- .../hierarchic-edge-grouping/index.html | 2 +- .../HierarchicEdgeLabeling.js | 2 +- .../HierarchicEdgeLabeling.ts | 2 +- .../hierarchic-edge-labeling/index.html | 2 +- .../HierarchicGivenLayering.js | 2 +- .../HierarchicGivenLayering.ts | 2 +- .../hierarchic-given-layering/index.html | 2 +- .../HierarchicIncremental.js | 4 +- .../HierarchicIncremental.ts | 4 +- .../hierarchic-incremental/index.html | 2 +- .../HierarchicLayerConstraints.js | 8 +- .../HierarchicLayerConstraints.ts | 8 +- .../hierarchic-layer-constraints/index.html | 2 +- .../HierarchicNodeAlignment.js | 4 +- .../HierarchicNodeAlignment.ts | 2 +- .../hierarchic-node-alignment/index.html | 2 +- .../HierarchicPortcandidateSet.js | 2 +- .../HierarchicPortcandidateSet.ts | 2 +- .../hierarchic-portcandidate-set/index.html | 2 +- .../HierarchicSequenceConstraints.js | 2 +- .../HierarchicSequenceConstraints.ts | 2 +- .../index.html | 2 +- .../layout-features/hierarchic/Hierarchic.js | 8 +- .../layout-features/hierarchic/Hierarchic.ts | 8 +- demos/layout-features/hierarchic/index.html | 2 +- .../organic-constraints/SampleApplication.js | 2 +- .../organic-constraints/SampleApplication.ts | 2 +- .../organic-constraints/index.html | 2 +- .../OrganicEdgeLabeling.js | 4 +- .../organic-edge-labeling/index.html | 2 +- .../organic-incremental/OrganicIncremental.js | 2 +- .../organic-incremental/OrganicIncremental.ts | 2 +- .../organic-incremental/index.html | 2 +- .../organic-substructures/index.html | 2 +- demos/layout-features/organic/index.html | 2 +- .../layout-features/orthogonal/Orthogonal.js | 6 +- .../layout-features/orthogonal/Orthogonal.ts | 6 +- demos/layout-features/orthogonal/index.html | 2 +- .../RecursiveGroupLayout.js | 8 +- .../RecursiveGroupLayout.ts | 8 +- .../recursive-group-layout/index.html | 2 +- .../tree-node-placers/TreeNodePlacers.js | 2 +- .../tree-node-placers/TreeNodePlacers.ts | 2 +- .../tree-node-placers/index.html | 2 +- demos/layout-features/tree/Tree.js | 2 +- demos/layout-features/tree/Tree.ts | 2 +- demos/layout-features/tree/index.html | 2 +- demos/layout/arc-diagram/index.html | 2 +- .../boundary-labeling/BoundaryLabelingDemo.js | 2 +- .../boundary-labeling/BoundaryLabelingDemo.ts | 2 +- .../boundary-labeling/configure-layout.js | 18 +- .../boundary-labeling/configure-layout.ts | 16 +- demos/layout/boundary-labeling/index.html | 2 +- demos/layout/busrouting/BusRoutingDemo.js | 4 +- demos/layout/busrouting/BusRoutingDemo.ts | 2 +- demos/layout/busrouting/index.html | 2 +- .../layout/busstructures/BusStructuresDemo.js | 15 +- .../layout/busstructures/BusStructuresDemo.ts | 15 +- demos/layout/busstructures/index.html | 2 +- .../CircularSubstructuresDemo.js | 2 +- .../layout/circular-substructures/index.html | 2 +- .../circular-substructures/types-popup.js | 2 +- .../circular-substructures/types-popup.ts | 2 +- .../clearmarqueearea/ClearAreaLayoutHelper.js | 2 +- .../clearmarqueearea/ClearMarqueeAreaDemo.js | 10 +- .../clearmarqueearea/ClearMarqueeAreaDemo.ts | 4 +- demos/layout/clearmarqueearea/index.html | 2 +- .../ClearAreaLayoutHelper.js | 6 +- .../ClearRectangleAreaDemo.js | 10 +- .../ClearRectangleAreaDemo.ts | 4 +- demos/layout/clearrectanglearea/index.html | 2 +- .../layout/criticalpaths/CriticalPathsDemo.js | 16 +- .../layout/criticalpaths/CriticalPathsDemo.ts | 16 +- demos/layout/criticalpaths/PriorityPanel.js | 4 +- demos/layout/criticalpaths/PriorityPanel.ts | 4 +- demos/layout/criticalpaths/index.html | 2 +- .../custom-layout-stage/AlignmentStage.js | 12 +- .../custom-layout-stage/AlignmentStage.ts | 11 +- .../CustomLayoutStageDemo.js | 12 +- .../CustomLayoutStageDemo.ts | 12 +- .../MoveNodesAsideStage.js | 2 +- .../MoveNodesAsideStage.ts | 2 +- demos/layout/custom-layout-stage/index.html | 2 +- demos/layout/edgebundling/DemoStyles.js | 4 +- demos/layout/edgebundling/DemoStyles.ts | 4 +- demos/layout/edgebundling/EdgeBundlingDemo.js | 20 +- demos/layout/edgebundling/EdgeBundlingDemo.ts | 18 +- demos/layout/edgebundling/index.html | 2 +- demos/layout/edgegrouping/EdgeGroupingDemo.js | 20 +- demos/layout/edgegrouping/EdgeGroupingDemo.ts | 12 +- demos/layout/edgegrouping/index.html | 2 +- .../EdgeLabelPlacementDemo.js | 23 +- .../EdgeLabelPlacementDemo.ts | 20 +- demos/layout/edgelabelplacement/index.html | 2 +- .../EdgeRouterGroupingDemo.js | 24 +- .../EdgeRouterGroupingDemo.ts | 24 +- demos/layout/edgeroutergrouping/index.html | 2 +- demos/layout/familytree/FamilyTreeDemo.js | 24 +- demos/layout/familytree/FamilyTreeDemo.ts | 22 +- demos/layout/familytree/index.html | 2 +- .../fillarealayout/FillAreaLayoutDemo.js | 12 +- .../fillarealayout/FillAreaLayoutDemo.ts | 6 +- demos/layout/fillarealayout/index.html | 2 +- .../ExpandCollapseNavigationHandler.js | 24 +- .../ExpandCollapseNavigationHandler.ts | 24 +- .../FoldingWithLayoutDemo.js | 10 +- .../FoldingWithLayoutDemo.ts | 8 +- demos/layout/foldingwithlayout/index.html | 2 +- .../height-profile/configure-highlight.js | 2 +- .../height-profile/configure-highlight.ts | 2 +- .../layout/height-profile/configure-layout.js | 2 +- .../layout/height-profile/configure-layout.ts | 2 +- demos/layout/height-profile/draw-axis.js | 4 +- demos/layout/height-profile/draw-axis.ts | 4 +- demos/layout/height-profile/index.html | 2 +- demos/layout/height-profile/scale-data.js | 6 +- demos/layout/height-profile/scale-data.ts | 6 +- demos/layout/height-profile/styles.js | 12 +- .../HierarchicNestingDemo.js | 4 +- .../HierarchicNestingDemo.ts | 4 +- demos/layout/hierarchic-nesting/index.html | 2 +- .../interactive-hierarchic-nesting-layout.js | 18 +- .../interactive-hierarchic-nesting-layout.ts | 14 +- .../InteractiveHierarchicDemo.js | 20 +- .../InteractiveHierarchicDemo.ts | 20 +- .../interactive-hierarchic/LayerVisual.js | 12 +- .../interactive-hierarchic/LayerVisual.ts | 12 +- .../layout/interactive-hierarchic/index.html | 2 +- .../InteractiveEdgeRoutingDemo.js | 4 +- .../layout/interactiveedgerouting/index.html | 2 +- demos/layout/interactiveorganic/DemoStyles.js | 2 +- demos/layout/interactiveorganic/DemoStyles.ts | 2 +- .../InteractiveOrganicDemo.js | 10 +- .../InteractiveOrganicDemo.ts | 10 +- demos/layout/interactiveorganic/index.html | 2 +- .../layerconstraints/LayerConstraintsDemo.js | 12 +- demos/layout/layerconstraints/index.html | 2 +- demos/layout/mazerouting/MazeRoutingDemo.js | 30 +- demos/layout/mazerouting/MazeRoutingDemo.ts | 26 +- .../mazerouting/PolylineEdgeRouterConfig.js | 4 +- .../mazerouting/PolylineEdgeRouterConfig.ts | 4 +- demos/layout/mazerouting/index.html | 2 +- .../MetabolicPathwaysDemo.js | 8 +- .../MetabolicPathwaysDemo.ts | 6 +- .../configure-krebs-cycle-layout.js | 48 +- .../configure-krebs-cycle-layout.ts | 44 +- .../configure-pentose-layout.js | 48 +- .../configure-pentose-layout.ts | 42 +- demos/layout/metabolic-pathways/index.html | 2 +- demos/layout/metabolic-pathways/styles.js | 6 +- demos/layout/metabolic-pathways/styles.ts | 6 +- demos/layout/multipage/MultiPageDemo.js | 26 +- demos/layout/multipage/MultiPageDemo.ts | 16 +- .../multipage/MultiPageIGraphBuilder.js | 18 +- .../multipage/MultiPageIGraphBuilder.ts | 18 +- demos/layout/multipage/index.html | 2 +- .../node-alignment/NodeAlignmentDemo.js | 10 +- .../node-alignment/NodeAlignmentDemo.ts | 10 +- demos/layout/node-alignment/index.html | 2 +- .../NodeLabelPlacementDemo.js | 10 +- .../NodeLabelPlacementDemo.ts | 10 +- demos/layout/nodelabelplacement/index.html | 2 +- .../nodeoverlapavoiding/LayoutHelper.js | 24 +- .../nodeoverlapavoiding/LayoutHelper.ts | 16 +- .../NodeOverlapAvoidingDemo.js | 14 +- .../NodeOverlapAvoidingDemo.ts | 2 +- demos/layout/nodeoverlapavoiding/index.html | 2 +- demos/layout/nodetypes/NodeTypesDemo.js | 4 +- demos/layout/nodetypes/NodeTypesDemo.ts | 2 +- demos/layout/nodetypes/index.html | 2 +- .../OrganicSubstructuresDemo.js | 4 +- .../OrganicSubstructuresDemo.ts | 4 +- demos/layout/organic-substructures/index.html | 2 +- .../resources/parallel.graphml | 2 +- demos/layout/partial/PartialLayoutDemo.js | 12 +- demos/layout/partial/PartialLayoutDemo.ts | 8 +- demos/layout/partial/index.html | 2 +- .../layout/partitiongrid/PartitionGridDemo.js | 8 +- .../layout/partitiongrid/PartitionGridDemo.ts | 6 +- .../PartitionGridVisualCreator.ts | 5 +- demos/layout/partitiongrid/index.html | 2 +- demos/layout/sankey/SankeyDemo.js | 2 +- demos/layout/sankey/edge-thickness.js | 4 +- demos/layout/sankey/edge-thickness.ts | 4 +- demos/layout/sankey/index.html | 2 +- .../sankey/interaction/configure-highlight.js | 6 +- .../sankey/interaction/configure-highlight.ts | 4 +- demos/layout/sankey/node-popup.js | 8 +- demos/layout/sankey/node-popup.ts | 2 +- demos/layout/sankey/sankey-layout.js | 8 +- demos/layout/sankey/styles-support.js | 4 +- demos/layout/sankey/styles-support.ts | 2 +- .../SequenceConstraintsDemo.js | 12 +- .../SequenceConstraintsDemo.ts | 2 +- demos/layout/sequenceconstraints/index.html | 2 +- .../SimplePartitionGridDemo.js | 8 +- .../SimplePartitionGridDemo.ts | 6 +- demos/layout/simplepartitiongrid/index.html | 2 +- demos/layout/splitedges/ContextMenuSupport.js | 54 +- demos/layout/splitedges/ContextMenuSupport.ts | 52 +- demos/layout/splitedges/SplitEdgesDemo.js | 28 +- demos/layout/splitedges/SplitEdgesDemo.ts | 24 +- demos/layout/splitedges/index.html | 2 +- .../layout/subcomponents/SubcomponentsDemo.js | 18 +- .../layout/subcomponents/SubcomponentsDemo.ts | 16 +- demos/layout/subcomponents/index.html | 2 +- .../tabular-groups/TabularGroupsDemo.js | 16 +- .../tabular-groups/TabularGroupsDemo.ts | 2 +- demos/layout/tabular-groups/index.html | 2 +- demos/layout/tree/CreateTreeEdgeInputMode.js | 2 +- demos/layout/tree/CreateTreeEdgeInputMode.ts | 2 +- demos/layout/tree/NodePlacerPanel.js | 22 +- demos/layout/tree/NodePlacerPanel.ts | 22 +- demos/layout/tree/TreeLayoutConfigurations.js | 6 +- demos/layout/tree/TreeLayoutConfigurations.ts | 6 +- demos/layout/tree/TreeLayoutDemo.js | 14 +- demos/layout/tree/TreeLayoutDemo.ts | 14 +- demos/layout/tree/index.html | 2 +- demos/layout/treemap/TreeMapDemo.js | 20 +- demos/layout/treemap/TreeMapDemo.ts | 20 +- demos/layout/treemap/index.html | 2 +- .../without-view/LayoutWithoutViewDemo.js | 4 +- .../without-view/LayoutWithoutViewDemo.ts | 4 +- demos/layout/without-view/index.html | 2 +- demos/loading/amdloading/AmdLoadingDemo.js | 142 --- demos/loading/amdloading/README.md | 28 - demos/loading/basic-module-loading/index.html | 3 +- demos/loading/nodejs/NodeJSDemo.js | 2 +- demos/loading/nodejs/NodeJSDemo.ts | 2 +- demos/loading/nodejs/README.html | 2 +- demos/loading/nodejs/index.html | 2 +- demos/loading/nodejs/server/package.json | 9 +- demos/loading/nodejs/server/server.js | 95 -- demos/loading/nodejs/server/server.mjs | 68 ++ demos/loading/parcel/README.html | 2 +- demos/loading/parcel/index.html | 2 +- demos/loading/parcel/package.json | 8 +- .../src/web-worker-client-message-handler.ts | 12 +- .../src/web-worker-server-message-handler.ts | 6 +- demos/loading/rollupjs/README.html | 2 +- demos/loading/rollupjs/index.html | 2 +- demos/loading/rollupjs/package.json | 18 +- demos/loading/rollupjs/src/LayoutWorker.js | 2 +- demos/loading/rollupjs/src/RollupJsDemo.js | 4 +- demos/loading/scriptloading/README.md | 20 - .../scriptloading/ScriptLoadingDemo.js | 113 --- demos/loading/scriptloading/index.html | 146 --- demos/loading/vite/README.html | 2 +- demos/loading/vite/index.html | 2 +- demos/loading/vite/package.json | 8 +- .../vite/src/message-handler-main-thread.ts | 8 +- .../vite/src/message-handler-worker-thread.ts | 6 +- demos/loading/web-dev-server/README.html | 2 +- demos/loading/web-dev-server/package.json | 4 +- demos/loading/web-dev-server/src/index.html | 2 +- demos/loading/webpack-lazy-layout/README.html | 2 +- .../webpack-lazy-layout/index.template.html | 2 +- .../loading/webpack-lazy-layout/package.json | 12 +- demos/loading/webpack-lazy-yfiles/README.html | 2 +- .../webpack-lazy-yfiles/index.template.html | 2 +- .../loading/webpack-lazy-yfiles/package.json | 12 +- .../src/diagram-component.ts | 2 +- demos/loading/webpack/README.html | 10 +- demos/loading/webpack/README.md | 6 +- demos/loading/webpack/index.template.html | 9 +- demos/loading/webpack/package.json | 12 +- demos/loading/webworker-modules/README.md | 2 +- .../webworker-modules/WebWorkerModulesDemo.js | 8 +- .../webworker-modules/WebWorkerModulesDemo.ts | 4 +- .../loading/webworker-modules/WorkerLayout.js | 2 +- .../loading/webworker-modules/WorkerLayout.ts | 2 +- demos/loading/webworker-modules/index.html | 9 +- demos/loading/webworker-umd/README.md | 25 - demos/loading/webworker-umd/WebWorkerDemo.js | 918 ------------------ demos/loading/webworker-umd/index.html | 174 ---- demos/loading/webworker-webpack/README.html | 2 +- .../webworker-webpack/index.template.html | 2 +- demos/loading/webworker-webpack/package.json | 10 +- .../src/WebWorkerWebpackDemo.ts | 2 +- .../webworker-webpack/src/WorkerLayout.ts | 2 +- demos/package.json | 31 +- demos/resources/README.html | 2 +- .../apply-local-storage-variables.js | 2 +- demos/resources/demo-error.js | 12 +- demos/resources/demo-error.ts | 4 +- demos/resources/demo-ui/element-utils.js | 14 +- demos/resources/demo-ui/element-utils.ts | 14 +- demos/resources/demo-ui/sidebars.js | 10 +- demos/resources/demo-ui/sidebars.ts | 8 +- demos/resources/demo-ui/toolbar.js | 12 +- demos/resources/demo-ui/toolbar.ts | 8 +- demos/resources/fetch-license.js | 8 +- demos/resources/fetch-license.ts | 10 +- demos/resources/filesystem-warning.js | 3 +- demos/resources/image/amdloading.png | Bin 27049 -> 0 bytes demos/resources/image/export-pdf.png | Bin 9563 -> 0 bytes demos/resources/image/export-png.png | Bin 9942 -> 0 bytes demos/resources/image/export-print.png | Bin 9484 -> 0 bytes demos/resources/image/export-svg.png | Bin 11844 -> 0 bytes demos/resources/image/export.png | Bin 7480 -> 0 bytes demos/resources/image/home-automation.png | Bin 0 -> 7649 bytes demos/resources/image/imageexport.png | Bin 0 -> 20726 bytes demos/resources/image/pdfexport.png | Bin 0 -> 15913 bytes demos/resources/image/printing.png | Bin 5923 -> 22289 bytes demos/resources/image/scriptloading.png | Bin 75218 -> 0 bytes demos/resources/image/svgexport.png | Bin 0 -> 18804 bytes demos/resources/image/webworkerumd.png | Bin 45393 -> 0 bytes demos/resources/package.json | 5 +- demos/resources/readme-demo-data.js | 63 +- demos/resources/readme-demo-support.js | 198 ++-- demos/resources/style/demo-option-editor.css | 25 +- demos/resources/style/demo.css | 173 +++- demos/resources/style/tutorial.css | 198 +++- demos/showcase/bpmn/BpmnEditorDemo.js | 14 +- demos/showcase/bpmn/BpmnEditorDemo.ts | 10 +- demos/showcase/bpmn/BpmnLayout.js | 14 +- demos/showcase/bpmn/BpmnLayout.ts | 14 +- demos/showcase/bpmn/BpmnLayoutData.js | 40 +- demos/showcase/bpmn/BpmnLayoutData.ts | 18 +- demos/showcase/bpmn/BpmnPopupSupport.js | 4 +- demos/showcase/bpmn/BpmnPopupSupport.ts | 4 +- demos/showcase/bpmn/bpmn-di.js | 85 +- demos/showcase/bpmn/bpmn-di.ts | 80 +- demos/showcase/bpmn/bpmn-view.js | 62 +- demos/showcase/bpmn/bpmn-view.ts | 60 +- demos/showcase/bpmn/index.html | 2 +- .../HighlightSupport.js | 4 +- .../HighlightSupport.ts | 4 +- .../NonRibbonChordDiagramDemo.js | 14 +- .../NonRibbonChordDiagramDemo.ts | 10 +- .../chord-diagram-non-ribbon/index.html | 6 +- .../chord-diagram/ChordDiagramDemo.js | 20 +- .../chord-diagram/ChordDiagramDemo.ts | 18 +- .../chord-diagram/ChordDiagramLayout.js | 14 +- .../chord-diagram/ChordDiagramLayout.ts | 14 +- .../chord-diagram/CircleSegmentNodeStyle.ts | 5 +- demos/showcase/chord-diagram/index.html | 2 +- .../company-ownership/CompanyOwnershipDemo.js | 14 +- .../company-ownership/CompanyOwnershipDemo.ts | 24 +- .../CompanyOwnershipSearch.js | 4 +- .../CompanyOwnershipSearch.ts | 4 +- .../company-ownership/CompanyStructureView.js | 49 +- .../company-ownership/CompanyStructureView.ts | 35 +- .../TogglePortButtonSupport.js | 2 +- .../company-ownership/configure-highlight.js | 10 +- .../company-ownership/configure-highlight.ts | 10 +- .../company-ownership/configure-layout.js | 12 +- .../company-ownership/configure-layout.ts | 4 +- demos/showcase/company-ownership/index.html | 2 +- .../prepare-smooth-animation.js | 2 +- .../prepare-smooth-animation.ts | 2 +- .../styles/CustomShapeNodeStyle.ts | 6 +- .../styles/TableNodeStyle.js | 4 +- .../decision-tree-component/DecisionTree.js | 22 +- .../decision-tree-component/DecisionTree.ts | 20 +- .../decision-tree-component.js | 4 +- .../decision-tree-component.ts | 4 +- .../editor-component/context-menu.js | 2 +- .../editor-component/context-menu.ts | 2 +- .../editor-component/editor-component.js | 8 +- .../editor-component/editor-component.ts | 8 +- demos/showcase/decisiontree/index.html | 2 +- .../switch-components-button.js | 2 +- .../switch-components-button.ts | 2 +- demos/showcase/flowchart/index.html | 2 +- .../flowchart/interaction/drag-and-drop.js | 2 +- .../flowchart/interaction/drag-and-drop.ts | 2 +- .../flowchart/layout/FlowchartLayout.js | 135 ++- .../flowchart/layout/FlowchartLayout.ts | 120 ++- .../flowchart/layout/FlowchartLayoutData.js | 10 +- .../flowchart/model/load-flowchart.js | 4 +- .../flowchart/option-panel/option-panel.css | 3 +- .../frauddetection/FraudDetectionDemo.js | 20 +- .../frauddetection/FraudDetectionDemo.ts | 14 +- demos/showcase/frauddetection/entity-data.js | 4 +- demos/showcase/frauddetection/entity-data.ts | 2 +- .../fraud-detection/fraud-components.js | 27 +- .../fraud-detection/fraud-components.ts | 22 +- .../fraud-detection/fraud-detection.js | 12 +- .../fraud-detection/fraud-detection.ts | 12 +- .../fraud-detection/inspection-view.js | 32 +- .../fraud-detection/inspection-view.ts | 30 +- demos/showcase/frauddetection/index.html | 2 +- .../frauddetection/interactive-layout.js | 18 +- .../frauddetection/interactive-layout.ts | 18 +- .../frauddetection/properties-view.js | 2 +- .../styles/ConnectionEdgeStyle.js | 2 +- .../styles/ConnectionEdgeStyle.ts | 2 +- .../styles/initialize-webgl-styles.js | 4 +- .../styles/initialize-webgl-styles.ts | 4 +- .../frauddetection/timeline/Styling.js | 2 +- .../frauddetection/timeline/Styling.ts | 2 +- .../timeline/TimeframeRectangle.ts | 10 +- .../frauddetection/timeline/Timeline.js | 30 +- .../frauddetection/timeline/Timeline.ts | 28 +- .../timeline/bucket-aggregation.js | 6 +- .../timeline/bucket-aggregation.ts | 6 +- .../timeline/timeline-layout.js | 6 +- .../timeline/timeline-layout.ts | 9 +- .../graph-wizard-for-flowchart/Actions.js | 40 +- .../graph-wizard-for-flowchart/Actions.ts | 38 +- .../FlowchartConfiguration.js | 36 +- .../FlowchartConfiguration.ts | 31 +- .../GraphWizardInputMode.js | 48 +- .../GraphWizardInputMode.ts | 41 +- .../Preconditions.js | 10 +- .../Preconditions.ts | 10 +- .../WizardAction.js | 8 +- .../WizardAction.ts | 6 +- .../graph-wizard-for-flowchart/index.html | 2 +- .../resources/graph-creation-wizard.css | 8 +- .../graphanalysis/GraphAnalysisDemo.js | 12 +- .../graphanalysis/GraphAnalysisDemo.ts | 12 +- .../graphanalysis/algorithms/algorithms.js | 18 +- .../graphanalysis/algorithms/algorithms.ts | 18 +- .../graphanalysis/algorithms/centrality.js | 22 +- .../graphanalysis/algorithms/centrality.ts | 22 +- .../graphanalysis/algorithms/connectivity.js | 26 +- .../graphanalysis/algorithms/connectivity.ts | 26 +- .../graphanalysis/algorithms/cycles.js | 6 +- .../graphanalysis/algorithms/cycles.ts | 6 +- .../algorithms/minimum-spanning-tree.js | 4 +- .../algorithms/minimum-spanning-tree.ts | 4 +- .../graphanalysis/algorithms/paths.js | 20 +- .../graphanalysis/algorithms/paths.ts | 20 +- .../graphanalysis/algorithms/substructures.js | 20 +- .../graphanalysis/algorithms/substructures.ts | 20 +- demos/showcase/graphanalysis/index.html | 2 +- .../graphanalysis/layout/CentralityStage.js | 16 +- .../graphanalysis/layout/CentralityStage.ts | 15 +- demos/showcase/graphanalysis/layout/layout.js | 4 +- demos/showcase/graphanalysis/layout/layout.ts | 2 +- .../ui/ComponentSwitchingInputMode.js | 2 +- .../ui/ComponentSwitchingInputMode.ts | 2 +- .../showcase/graphanalysis/ui/context-menu.js | 12 +- .../showcase/graphanalysis/ui/context-menu.ts | 12 +- .../ui/graph-structure-information.js | 14 +- .../ui/graph-structure-information.ts | 14 +- demos/showcase/graphanalysis/ui/ui-utils.js | 10 +- demos/showcase/graphanalysis/ui/ui-utils.ts | 10 +- .../home-automation/FlowEdge/FlowEdge.js | 231 +++++ .../home-automation/FlowEdge/FlowEdge.ts | 222 +++++ .../home-automation/FlowEdge/FlowEdgeStyle.js | 236 +++++ .../home-automation/FlowEdge/FlowEdgeStyle.ts | 197 ++++ .../FlowPortRelocationHandleProvider.js | 153 +++ .../FlowPortRelocationHandleProvider.ts | 141 +++ .../home-automation/FlowEdge/flowEdge.css | 104 ++ ...owEdgeReconnectionPortCandidateProvider.js | 123 +++ ...owEdgeReconnectionPortCandidateProvider.ts | 113 +++ .../home-automation/FlowNode/FlowNode.js | 216 +++++ .../home-automation/FlowNode/FlowNode.ts | 200 ++++ .../home-automation/FlowNode/FlowNodePort.js | 74 ++ .../home-automation/FlowNode/FlowNodePort.ts | 67 ++ .../FlowNode/FlowNodePortCandidateProvider.js | 105 ++ .../FlowNode/FlowNodePortCandidateProvider.ts | 97 ++ .../FlowNode/FlowNodePortStyle.js | 144 +++ .../FlowNode/FlowNodePortStyle.ts | 118 +++ .../home-automation/FlowNode/FlowNodeStyle.js | 617 ++++++++++++ .../home-automation/FlowNode/FlowNodeStyle.ts | 478 +++++++++ .../FlowNode/FlowNodeValidators.js | 165 ++++ .../FlowNode/FlowNodeValidators.ts | 135 +++ .../FlowNode/flowNodeProperties.js | 222 +++++ .../FlowNode/flowNodeProperties.ts | 223 +++++ .../home-automation/FlowNode/icons.js | 101 ++ .../home-automation/FlowNode/icons.ts | 107 ++ .../home-automation/HomeAutomationDemo.js | 79 ++ .../home-automation/HomeAutomationDemo.ts | 76 ++ .../ImportExportManager/EdgeData.js | 144 +++ .../ImportExportManager/EdgeData.ts | 125 +++ .../ImportExportManager/GraphData.js | 131 +++ .../ImportExportManager/GraphData.ts | 121 +++ .../ImportExportManager.js | 145 +++ .../ImportExportManager.ts | 138 +++ .../ImportExportManager/NodeData.js | 137 +++ .../ImportExportManager/NodeData.ts | 127 +++ demos/showcase/home-automation/README.md | 29 + .../UI/initializeContextMenu.js | 77 ++ .../UI/initializeContextMenu.ts | 74 ++ .../UI/initializeDragAndDropPanel.js | 41 + .../UI/initializeDragAndDropPanel.ts | 41 + .../UI/initializeTagExplorer.js | 158 +++ .../UI/initializeTagExplorer.ts | 164 ++++ .../home-automation/UI/initializeToolbar.js | 44 + .../home-automation/UI/initializeToolbar.ts | 40 + .../home-automation/UI/initializeTooltips.js | 103 ++ .../home-automation/UI/initializeTooltips.ts | 98 ++ .../home-automation/UI/showErrorDialog.js | 82 ++ .../home-automation/UI/showErrorDialog.ts | 73 ++ .../flow-context-menu/FlowContextMenu.js | 155 +++ .../flow-context-menu/FlowContextMenu.ts | 153 +++ .../flow-context-menu/contextMenuUtils.js | 142 +++ .../flow-context-menu/contextMenuUtils.ts | 141 +++ .../home-automation}/index.html | 130 +-- .../inputMode/FlowCreateEdgeInputMode.js | 57 ++ .../inputMode/FlowCreateEdgeInputMode.ts | 54 ++ .../inputMode/FlowMoveInputMode.js | 52 + .../inputMode/FlowMoveInputMode.ts | 49 + .../inputMode/configureInputMode.js | 101 ++ .../inputMode/configureInputMode.ts | 98 ++ .../layout/HierarchicLayout.js | 81 ++ .../layout/HierarchicLayout.ts | 81 ++ .../layout/initializeSnapping.js | 48 + .../layout/initializeSnapping.ts | 45 + .../home-automation/layout/initilaizeGrid.js | 63 ++ .../home-automation/layout/initilaizeGrid.ts | 59 ++ .../home-automation/layout/runLayout.js | 41 + .../home-automation/layout/runLayout.ts | 37 + .../home-automation/resources/style.css | 251 +++++ .../home-automation/resources/weather-data.js | 168 ++++ .../home-automation/resources/weather-data.ts | 170 ++++ .../utils/configureDragAndDrop.js | 38 + .../utils/configureDragAndDrop.ts | 35 + .../utils/configureGraphEvents.js | 39 + .../utils/configureGraphEvents.ts | 36 + .../home-automation/utils/customTriggers.js | 60 ++ .../home-automation/utils/customTriggers.ts | 53 + .../home-automation/utils/elementUtils.js} | 81 +- .../home-automation/utils/elementUtils.ts | 70 ++ .../isometricdrawing/IsometricDrawingDemo.js | 12 +- .../isometricdrawing/IsometricDrawingDemo.ts | 8 +- demos/showcase/isometricdrawing/index.html | 2 +- .../large-graphs/DemoConfiguration.js | 2 +- .../large-graphs/DemoConfiguration.ts | 2 +- .../LargeGraphDemoConfiguration.js | 4 +- .../LargeGraphDemoConfiguration.ts | 2 +- .../showcase/large-graphs/LargeGraphsDemo.js | 14 +- .../showcase/large-graphs/LargeGraphsDemo.ts | 14 +- .../large-graphs/OrgChartDemoConfiguration.js | 2 +- .../large-graphs/OrgChartDemoConfiguration.ts | 2 +- .../showcase/large-graphs/SVGDataURLFetch.js | 4 +- .../showcase/large-graphs/SVGDataURLFetch.ts | 4 +- demos/showcase/large-graphs/index.html | 2 +- .../AggregationHelper.js | 8 +- .../AggregationHelper.ts | 8 +- .../LargeGraphAggregationDemo.js | 57 +- .../LargeGraphAggregationDemo.ts | 43 +- .../showcase/largegraphaggregation/index.html | 2 +- .../layoutstyles/BalloonLayoutConfig.js | 2 +- .../layoutstyles/BalloonLayoutConfig.ts | 2 +- .../layoutstyles/BusEdgeRouterConfig.js | 22 +- .../layoutstyles/BusEdgeRouterConfig.ts | 22 +- .../layoutstyles/ChannelEdgeRouterConfig.js | 6 +- .../layoutstyles/ChannelEdgeRouterConfig.ts | 6 +- .../layoutstyles/CircularLayoutConfig.js | 4 +- .../layoutstyles/CircularLayoutConfig.ts | 4 +- .../layoutstyles/CompactDiskLayoutConfig.js | 4 +- .../layoutstyles/CompactDiskLayoutConfig.ts | 4 +- .../layoutstyles/HierarchicLayoutConfig.js | 19 +- .../layoutstyles/HierarchicLayoutConfig.ts | 14 +- demos/showcase/layoutstyles/LabelingConfig.js | 2 +- demos/showcase/layoutstyles/LabelingConfig.ts | 2 +- .../layoutstyles/LayoutConfiguration.js | 4 +- .../layoutstyles/LayoutConfiguration.ts | 2 +- .../showcase/layoutstyles/LayoutStylesDemo.js | 26 +- .../showcase/layoutstyles/LayoutStylesDemo.ts | 24 +- .../layoutstyles/OrganicLayoutConfig.js | 8 +- .../layoutstyles/OrganicLayoutConfig.ts | 8 +- .../layoutstyles/OrthogonalLayoutConfig.js | 2 +- .../layoutstyles/OrthogonalLayoutConfig.ts | 2 +- .../layoutstyles/ParallelEdgeRouterConfig.js | 4 +- .../layoutstyles/ParallelEdgeRouterConfig.ts | 4 +- .../layoutstyles/PolylineEdgeRouterConfig.js | 20 +- .../layoutstyles/PolylineEdgeRouterConfig.ts | 20 +- .../showcase/layoutstyles/PresetsUiBuilder.js | 6 +- .../showcase/layoutstyles/PresetsUiBuilder.ts | 4 +- .../layoutstyles/RadialLayoutConfig.js | 4 +- .../layoutstyles/RadialLayoutConfig.ts | 4 +- .../layoutstyles/TabularLayoutConfig.js | 4 +- .../layoutstyles/TabularLayoutConfig.ts | 4 +- .../showcase/layoutstyles/TreeLayoutConfig.js | 18 +- .../showcase/layoutstyles/TreeLayoutConfig.ts | 18 +- demos/showcase/layoutstyles/index.html | 2 +- .../resources/edge-labels.graphml | 2 +- .../layoutstyles/resources/styles.css | 7 +- demos/showcase/logicgates/LogicGatesDemo.js | 4 +- demos/showcase/logicgates/LogicGatesDemo.ts | 4 +- demos/showcase/logicgates/index.html | 2 +- .../showcase/logicgates/logicgates-layout.js | 8 +- .../showcase/logicgates/logicgates-layout.ts | 8 +- .../logicgates/node-styles/GateNodeStyle.js | 4 +- .../logicgates/node-styles/GateNodeStyle.ts | 4 +- demos/showcase/map/MapDemo.js | 6 +- demos/showcase/map/MapDemo.ts | 6 +- demos/showcase/map/index.html | 2 +- demos/showcase/map/leaflet-graph-layer.js | 26 +- demos/showcase/map/leaflet-graph-layer.ts | 20 +- demos/showcase/map/shortest-paths.js | 12 +- demos/showcase/map/shortest-paths.ts | 12 +- .../metaballgroups/MetaballGroupsDemo.js | 8 +- .../metaballgroups/MetaballGroupsDemo.ts | 2 +- .../metaballgroups/WebglBlobVisual.js | 4 +- .../metaballgroups/WebglBlobVisual.ts | 4 +- demos/showcase/metaballgroups/index.html | 2 +- demos/showcase/mindmap/MindMapDemo.js | 12 +- demos/showcase/mindmap/MindMapDemo.ts | 10 +- demos/showcase/mindmap/cross-references.js | 4 +- demos/showcase/mindmap/cross-references.ts | 2 +- demos/showcase/mindmap/index.html | 2 +- .../interaction/MindMapPositionHandlers.js | 12 +- .../interaction/MindMapPositionHandlers.ts | 12 +- .../showcase/mindmap/interaction/commands.js | 22 +- .../showcase/mindmap/interaction/commands.ts | 22 +- demos/showcase/mindmap/mind-map-layout.js | 8 +- demos/showcase/mindmap/mind-map-layout.ts | 4 +- demos/showcase/mindmap/node-popup-toolbar.js | 14 +- demos/showcase/mindmap/node-popup-toolbar.ts | 14 +- .../mindmap/resources/mindmapstyle.css | 12 +- .../mindmap/styles/CollapseDecorator.js | 4 +- .../mindmap/styles/CollapseDecorator.ts | 4 +- .../mindmap/styles/MindMapEdgeStyle.ts | 11 +- .../showcase/mindmap/styles/styles-support.js | 4 +- .../showcase/mindmap/styles/styles-support.ts | 4 +- demos/showcase/mindmap/subtrees.js | 28 +- demos/showcase/mindmap/subtrees.ts | 24 +- .../NeighborhoodCirclesDemo.js | 16 +- .../NeighborhoodCirclesDemo.ts | 10 +- .../showcase/neighborhood-circles/index.html | 2 +- .../showcase/neighborhood/NeighborhoodDemo.js | 4 +- .../showcase/neighborhood/NeighborhoodDemo.ts | 4 +- .../showcase/neighborhood/NeighborhoodView.js | 4 +- .../showcase/neighborhood/NeighborhoodView.ts | 4 +- .../neighborhood/apply-layout-callback.js | 2 +- .../neighborhood/apply-layout-callback.ts | 2 +- .../neighborhood/build-graph-callback.js | 10 +- .../neighborhood/build-graph-callback.ts | 10 +- demos/showcase/neighborhood/index.html | 2 +- .../networkmonitoring/ConnectionEdgeStyle.js | 4 +- .../NetworkMonitoringDemo.js | 22 +- .../NetworkMonitoringDemo.ts | 16 +- .../networkmonitoring/device-popup.js | 2 +- .../networkmonitoring/device-popup.ts | 2 +- demos/showcase/networkmonitoring/index.html | 2 +- .../networkmonitoring/model/Connection.ts | 5 +- .../networkmonitoring/model/Network.js | 6 +- .../networkmonitoring/model/Network.ts | 11 +- .../networkmonitoring/model/Simulator.js | 48 +- .../networkmonitoring/model/Simulator.ts | 46 +- .../networkmonitoring/ui/D3BarChart.js | 24 +- .../networkmonitoring/ui/D3BarChart.ts | 24 +- demos/showcase/orgchart/CollapsibleTree.js | 14 +- demos/showcase/orgchart/CollapsibleTree.ts | 8 +- demos/showcase/orgchart/OrgChartDemo.js | 4 +- .../showcase/orgchart/OrgChartGraphSearch.js | 4 +- .../showcase/orgchart/OrgChartGraphSearch.ts | 4 +- .../graph-style/orgchart-port-style.js | 2 +- demos/showcase/orgchart/index.html | 2 +- demos/showcase/orgchart/model/data-loading.js | 2 +- demos/showcase/orgchart/model/data-loading.ts | 2 +- demos/showcase/orgchart/printdocument.html | 2 +- .../orgchart/ui/orgchart-context-menu.js | 2 +- .../orgchart/ui/orgchart-context-menu.ts | 2 +- .../orgchart/ui/orgchart-properties-view.js | 6 +- .../orgchart/ui/orgchart-properties-view.ts | 6 +- .../processmining/ProcessMiningDemo.js | 4 +- .../processmining/ProcessMiningDemo.ts | 2 +- .../ProcessingStepNodeStyleDecorator.js | 2 +- .../ProcessingStepNodeStyleDecorator.ts | 7 +- demos/showcase/processmining/index.html | 2 +- .../processmining/process-graph-extraction.js | 6 +- .../processmining/process-graph-extraction.ts | 6 +- .../process-visualization.js | 16 +- .../process-visualization.ts | 16 +- .../simulation/simulation-graph.js | 2 +- .../processmining/simulation/simulator.js | 6 +- .../processmining/simulation/simulator.ts | 6 +- demos/showcase/tag-cloud/TagCloudDemo.js | 2 +- demos/showcase/tag-cloud/TagCloudDemo.ts | 7 +- demos/showcase/tag-cloud/TagCloudHelper.js | 4 +- demos/showcase/tag-cloud/TagCloudHelper.ts | 4 +- demos/showcase/tag-cloud/index.html | 2 +- demos/showcase/tree-of-life/SectorVisual.js | 14 +- demos/showcase/tree-of-life/SectorVisual.ts | 14 +- demos/showcase/tree-of-life/SubtreeSupport.js | 10 +- demos/showcase/tree-of-life/SubtreeSupport.ts | 14 +- demos/showcase/tree-of-life/TreeOfLifeDemo.js | 26 +- demos/showcase/tree-of-life/TreeOfLifeDemo.ts | 26 +- .../showcase/tree-of-life/TreeOfLifeSearch.js | 6 +- .../showcase/tree-of-life/TreeOfLifeSearch.ts | 6 +- demos/showcase/tree-of-life/index.html | 2 +- demos/showcase/uml/ButtonVisualCreator.js | 2 +- demos/showcase/uml/ButtonVisualCreator.ts | 7 +- demos/showcase/uml/UMLContextButtonFactory.js | 6 +- demos/showcase/uml/UMLContextButtonFactory.ts | 6 +- demos/showcase/uml/UMLEditorDemo.js | 8 +- demos/showcase/uml/UMLEditorDemo.ts | 2 +- demos/showcase/uml/UMLNodeStyle.js | 8 +- demos/showcase/uml/UMLNodeStyle.ts | 2 +- demos/showcase/uml/index.html | 2 +- .../angular-component-node-style/README.html | 2 +- .../angular-component-node-style/package.json | 28 +- .../src/index.html | 2 +- .../angular-component-node-style/src/main.ts | 2 +- .../tsconfig.app.json | 1 - .../tsconfig.spec.json | 1 - .../webpack.config.js | 2 +- .../arrow-edge-style/ArrowEdgeStyleDemo.js | 16 +- .../arrow-edge-style/ArrowEdgeStyleDemo.ts | 16 +- demos/style/arrow-edge-style/index.html | 2 +- .../arrow-node-style/ArrowNodeStyleDemo.js | 10 +- .../arrow-node-style/ArrowNodeStyleDemo.ts | 10 +- demos/style/arrow-node-style/index.html | 2 +- .../BezierEdgeHandleProvider.js | 2 +- .../BezierEdgeHandleProvider.ts | 2 +- .../bezieredgestyle/BezierEdgeStyleDemo.js | 24 +- .../bezieredgestyle/BezierEdgeStyleDemo.ts | 14 +- .../BezierGraphEditorInputMode.js | 10 +- .../BezierGraphEditorInputMode.ts | 8 +- demos/style/bezieredgestyle/index.html | 2 +- .../ClickableStyleDecoratorDemo.js | 8 +- .../ClickableStyleDecoratorDemo.ts | 8 +- .../clickable-style-decorator/index.html | 2 +- .../CompositeNodeStyleDemo.js | 2 +- .../CompositeNodeStyleDemo.ts | 2 +- demos/style/composite-node-style/index.html | 2 +- .../configure-selection-highlight.js | 2 +- .../configure-selection-highlight.ts | 2 +- .../css-item-style/create-sample-graph.js | 6 +- .../css-item-style/create-sample-graph.ts | 2 +- demos/style/css-item-style/index.html | 2 +- .../style/cssstyling/CSS3NodeStyleWrapper.js | 2 +- .../style/cssstyling/CSS3NodeStyleWrapper.ts | 2 +- demos/style/cssstyling/CSSStylingDemo.js | 10 +- demos/style/cssstyling/CSSStylingDemo.ts | 10 +- demos/style/cssstyling/index.html | 2 +- .../customstyles/AnimatedLinearGradient.js | 4 +- demos/style/customstyles/CustomStyleDemo.js | 6 +- demos/style/customstyles/CustomStyleDemo.ts | 6 +- demos/style/customstyles/Sample1Arrow.js | 7 - demos/style/customstyles/Sample1Arrow.ts | 7 - ...e1CollapsibleNodeStyleDecoratorRenderer.js | 2 +- ...e1CollapsibleNodeStyleDecoratorRenderer.ts | 2 +- demos/style/customstyles/Sample1EdgeStyle.js | 9 +- demos/style/customstyles/Sample1EdgeStyle.ts | 9 +- .../customstyles/Sample1GroupNodeStyle.js | 4 +- .../customstyles/Sample1GroupNodeStyle.ts | 4 +- demos/style/customstyles/Sample1LabelStyle.js | 7 - demos/style/customstyles/Sample1LabelStyle.ts | 7 - demos/style/customstyles/Sample1NodeStyle.js | 4 +- demos/style/customstyles/Sample1NodeStyle.ts | 4 +- demos/style/customstyles/Sample2Arrow.js | 4 - demos/style/customstyles/Sample2Arrow.ts | 4 - demos/style/customstyles/Sample2EdgeStyle.js | 4 - demos/style/customstyles/Sample2EdgeStyle.ts | 4 - .../customstyles/Sample2GroupNodeStyle.js | 6 +- .../customstyles/Sample2GroupNodeStyle.ts | 6 +- demos/style/customstyles/Sample2NodeStyle.js | 4 - demos/style/customstyles/Sample2NodeStyle.ts | 4 - demos/style/customstyles/index.html | 2 +- demos/style/d3chartnodes/D3ChartNodeStyle.js | 22 +- demos/style/d3chartnodes/D3ChartNodeStyle.ts | 28 +- demos/style/d3chartnodes/D3ChartNodesDemo.js | 4 +- demos/style/d3chartnodes/D3ChartNodesDemo.ts | 4 +- demos/style/d3chartnodes/index.html | 2 +- demos/style/datatable/DataTableDemo.js | 2 +- demos/style/datatable/DataTableDemo.ts | 2 +- demos/style/datatable/DataTableNodeStyle.js | 2 +- .../style/datatable/DataTableRenderSupport.js | 4 +- .../style/datatable/DataTableRenderSupport.ts | 4 +- demos/style/datatable/index.html | 2 +- demos/style/default-label-style/index.html | 2 +- demos/style/directed-edge-label/index.html | 2 +- .../EditablePathNodeStyle.js | 4 +- .../EditablePathNodeStyle.ts | 2 +- .../style/editablepathstyle/PathStyleDemo.js | 20 +- .../style/editablepathstyle/PathStyleDemo.ts | 20 +- demos/style/editablepathstyle/index.html | 2 +- demos/style/group-node-style/index.html | 2 +- .../group-node-style/svg-webgl-switch.js | 4 +- demos/style/html-controls/HtmlControlsDemo.js | 4 +- demos/style/html-controls/HtmlControlsDemo.ts | 4 +- .../html-controls/HtmlEditableNodeStyle.js | 6 +- .../html-controls/HtmlEditableNodeStyle.ts | 6 +- demos/style/html-controls/index.html | 2 +- .../style/html-label-style/HtmlLabelStyle.js | 10 +- .../style/html-label-style/HtmlLabelStyle.ts | 6 +- .../html-label-style/HtmlLabelStyleDemo.js | 21 +- .../html-label-style/HtmlLabelStyleDemo.ts | 22 +- demos/style/html-label-style/index.html | 2 +- .../ZoomInvariantLabelStyle.js | 2 +- .../ZoomInvariantLabelStyle.ts | 8 +- .../ZoomInvariantLabelStyleDemo.js | 14 +- .../ZoomInvariantLabelStyleDemo.ts | 14 +- demos/style/invariant-label/index.html | 2 +- .../IsometricBarChartStyleDemo.js | 4 +- .../isometric-bar-chart-style/index.html | 2 +- demos/style/level-of-detail-style/index.html | 2 +- .../LitTemplateNodeStyleDemo.js | 8 +- .../LitTemplateNodeStyleDemo.ts | 6 +- .../style/lit-template-node-style/index.html | 2 +- .../style/markdownlabel/MarkdownLabelDemo.js | 10 +- .../style/markdownlabel/MarkdownLabelDemo.ts | 10 +- demos/style/markdownlabel/index.html | 2 +- demos/style/markup-labels/MarkupLabelsDemo.js | 20 +- demos/style/markup-labels/MarkupLabelsDemo.ts | 20 +- demos/style/markup-labels/index.html | 2 +- .../react-component-node-style/README.html | 2 +- .../react-component-node-style/index.html | 2 +- .../react-component-node-style/package.json | 6 +- .../src/HtmlNodeComponent.tsx | 2 +- .../ReactComponentStylesDemo.js | 28 +- .../ReactComponentStylesDemo.ts | 26 +- ...actComponentSvgNodeStyleMarkupExtension.ts | 1 + .../react-template-node-style/SvgText.js | 4 +- .../react-template-node-style/SvgText.ts | 4 +- .../react-template-node-style/index.html | 2 +- .../react-template-node-style/jsx-compiler.js | 2 +- .../react-template-node-style/jsx-compiler.ts | 4 +- .../RectangleNodeStyleDemo.js | 10 +- .../RectangleNodeStyleDemo.ts | 10 +- demos/style/rectangle-node-style/index.html | 2 +- .../richtextlabel/RichTextEditorInputMode.js | 8 +- .../richtextlabel/RichTextEditorInputMode.ts | 6 +- .../style/richtextlabel/RichTextLabelDemo.js | 10 +- .../style/richtextlabel/RichTextLabelDemo.ts | 10 +- demos/style/richtextlabel/index.html | 2 +- .../selectionstyling/SelectionStylingDemo.js | 14 +- .../selectionstyling/SelectionStylingDemo.ts | 14 +- demos/style/selectionstyling/index.html | 2 +- demos/style/shape-node-style/index.html | 2 +- demos/style/simple-arrow-style/index.html | 2 +- .../StringTemplateNodeStyleDemo.js | 8 +- .../StringTemplateNodeStyleDemo.ts | 4 +- .../string-template-node-style/index.html | 2 +- .../styledecorators/NodeStyleDecorator.ts | 5 +- .../styledecorators/StyleDecoratorsDemo.js | 14 +- .../styledecorators/StyleDecoratorsDemo.ts | 14 +- demos/style/styledecorators/index.html | 2 +- demos/style/templatestyles/PropertiesView.js | 2 +- demos/style/templatestyles/PropertiesView.ts | 2 +- .../templatestyles/TemplateStylesDemo.js | 22 +- .../templatestyles/TemplateStylesDemo.ts | 8 +- demos/style/templatestyles/index.html | 2 +- .../style/templatestyles/resources/styles.css | 5 +- .../style/theme-variants/ThemeVariantsDemo.js | 6 +- .../style/theme-variants/ThemeVariantsDemo.ts | 4 +- demos/style/theme-variants/index.html | 2 +- .../vue-component-node-style/README.html | 2 +- .../style/vue-component-node-style/index.html | 2 +- .../vue-component-node-style/package.json | 6 +- .../VueTemplateNodeStyleDemo.js | 11 +- .../VueTemplateNodeStyleDemo.ts | 9 +- .../style/vue-template-node-style/index.html | 2 +- .../webgl-animations/WebGLAnimationsDemo.js | 58 +- .../webgl-animations/WebGLAnimationsDemo.ts | 54 +- demos/style/webgl-animations/index.html | 2 +- .../webgl-icon-node/WebGLIconNodeDemo.js | 2 +- .../webgl-icon-node/WebGLIconNodeDemo.ts | 2 +- demos/style/webgl-icon-node/index.html | 2 +- demos/style/webgl-selection-styles/index.html | 2 +- .../webgl-selection-styles/ui-interaction.js | 28 +- .../webgl-selection-styles/ui-interaction.ts | 28 +- demos/style/webgl-styles/WebGLStylesDemo.js | 12 +- demos/style/webgl-styles/WebGLStylesDemo.ts | 12 +- demos/style/webgl-styles/index.html | 2 +- .../testing/application-under-test/index.html | 2 +- demos/testing/cypress/README.html | 2 +- .../testing/cypress/cypress/e2e/yfiles.cy.ts | 2 +- demos/testing/cypress/package.json | 6 +- demos/testing/jest-puppeteer/README.html | 2 +- .../jest-puppeteer/integration/app.test.js | 10 +- demos/testing/jest-puppeteer/package.json | 2 +- .../testing/jest-puppeteer/public/index.html | 2 +- .../src/components/ReactGraphComponent.css | 4 +- .../src/components/ReactGraphComponent.jsx | 2 +- demos/testing/jest/README.html | 2 +- demos/testing/jest/package.json | 4 +- demos/testing/jest/src/index.html | 2 +- demos/testing/playwright/README.html | 2 +- demos/testing/playwright/package.json | 2 +- demos/testing/playwright/playwright.config.ts | 2 +- demos/testing/selenium-webdriver/README.html | 2 +- demos/testing/vitest/README.html | 2 +- demos/testing/vitest/index.html | 2 +- demos/testing/vitest/package.json | 12 +- demos/testing/vitest/test/yfiles.test.ts | 2 +- demos/testing/wdio/README.html | 2 +- demos/testing/wdio/index.html | 2 +- demos/testing/wdio/package.json | 2 +- .../wdio/test/pageobjects/wdio-demo.page.ts | 8 +- demos/testing/wdio/test/specs/yfiles.e2e.ts | 2 +- demos/toolkit/angular/README.html | 2 +- demos/toolkit/angular/angular.json | 7 +- demos/toolkit/angular/extra-webpack.config.js | 2 +- demos/toolkit/angular/package.json | 39 +- .../toolkit/angular/src/app/app.component.ts | 7 +- .../context-menu/context-menu.component.css | 4 +- .../context-menu/context-menu.component.ts | 7 +- .../graph-overview-component.component.ts | 5 +- .../toolkit/angular/src/app/layout.worker.ts | 2 +- .../src/app/tooltip/tooltip.component.css | 4 +- demos/toolkit/angular/src/index.html | 2 +- demos/toolkit/angular/src/main.ts | 2 +- demos/toolkit/angular/src/polyfills.ts | 2 +- demos/toolkit/angular/src/typings.d.ts | 2 +- .../angular/src/utils/BrowserDetection.ts | 266 +++++ .../toolkit/angular/src/utils/GraphSearch.ts | 237 +++++ demos/toolkit/angular/tsconfig.worker.json | 1 - demos/toolkit/graphql/GraphQLDemo.js | 2 +- demos/toolkit/graphql/GraphQLDemo.ts | 2 +- .../graphql/SocialNetworkGraphBuilder.js | 8 +- .../graphql/SocialNetworkGraphBuilder.ts | 8 +- demos/toolkit/graphql/index.html | 2 +- demos/toolkit/graphql/server/package.json | 4 +- demos/toolkit/graphql/server/resolver.js | 2 +- demos/toolkit/neo4j/Neo4jDemo.js | 18 +- demos/toolkit/neo4j/Neo4jDemo.ts | 17 +- demos/toolkit/neo4j/Neo4jGraphBuilder.js | 10 +- demos/toolkit/neo4j/Neo4jGraphBuilder.ts | 2 +- demos/toolkit/neo4j/index.html | 2 +- demos/toolkit/next/README.html | 2 +- .../app/components/ContextMenuComponent.css | 4 +- .../app/components/ContextMenuComponent.tsx | 2 +- .../next/app/components/DemoDataPanel.tsx | 4 +- .../app/components/GraphOverviewComponent.css | 4 +- .../app/components/ReactGraphComponent.css | 4 +- .../app/components/ReactGraphComponent.tsx | 2 +- demos/toolkit/next/app/page.tsx | 8 +- .../next/app/utils/BrowserDetection.ts | 266 +++++ demos/toolkit/next/app/utils/GraphSearch.ts | 237 +++++ demos/toolkit/next/app/utils/LayoutSupport.ts | 2 +- demos/toolkit/next/app/utils/LayoutWorker.ts | 4 +- .../next/app/utils/use-graph-builder.ts | 6 +- .../next/app/utils/use-graph-search.ts | 2 +- demos/toolkit/next/next.config.js | 4 +- demos/toolkit/next/package.json | 16 +- demos/toolkit/preact/PreactDemo.js | 4 +- .../graphComponent/PreactGraphComponent.js | 6 +- .../graphComponent/PreactGraphComponent.ts | 2 +- demos/toolkit/preact/components/items/Item.js | 2 +- demos/toolkit/preact/components/items/Item.ts | 35 +- .../preact/components/items/ItemList.js | 21 +- .../preact/components/items/ItemList.ts | 19 +- demos/toolkit/preact/index.html | 2 +- .../react-class-components/README.html | 2 +- .../toolkit/react-class-components/index.html | 2 +- .../react-class-components/package.json | 23 +- demos/toolkit/react/README.html | 2 +- demos/toolkit/react/index.html | 2 +- demos/toolkit/react/package.json | 23 +- demos/toolkit/react/src/App.tsx | 6 +- .../src/components/ContextMenuComponent.css | 4 +- .../src/components/ContextMenuComponent.tsx | 2 +- .../react/src/components/DemoDataPanel.tsx | 4 +- .../src/components/GraphOverviewComponent.css | 4 +- .../src/components/ReactGraphComponent.css | 4 +- .../src/components/ReactGraphComponent.tsx | 2 +- .../toolkit/react/src/utils/LayoutSupport.ts | 2 +- demos/toolkit/react/src/utils/LayoutWorker.ts | 4 +- .../react/src/utils/use-graph-builder.ts | 6 +- .../react/src/utils/use-graph-search.ts | 2 +- demos/toolkit/solid/README.html | 2 +- demos/toolkit/solid/index.html | 2 +- demos/toolkit/solid/package.json | 14 +- .../toolkit/solid/src/components/tooltip.css | 4 +- .../toolkit/solid/src/utils/layout.worker.ts | 4 +- demos/toolkit/solid/src/utils/useLayout.ts | 2 +- demos/toolkit/svelte/README.html | 2 +- demos/toolkit/svelte/index.html | 2 +- demos/toolkit/svelte/package.json | 16 +- .../src/web-worker-client-message-handler.ts | 8 +- .../src/web-worker-server-message-handler.ts | 6 +- demos/toolkit/vue/README.html | 2 +- demos/toolkit/vue/env.d.ts | 2 +- demos/toolkit/vue/index.html | 2 +- demos/toolkit/vue/package.json | 20 +- .../vue/src/components/ContextMenu.vue | 4 +- .../components/DiagramOverviewComponent.vue | 4 +- demos/toolkit/vue/src/components/Tooltip.vue | 4 +- .../vue/src/components/layout.worker.ts | 4 +- .../vue/src/composables/useContextMenu.ts | 6 +- .../vue/src/composables/useGraphSearch.ts | 2 +- .../toolkit/vue/src/composables/useLayout.ts | 2 +- demos/toolkit/webcomponents/index.html | 2 +- demos/tsconfig.json | 1 - .../01-create-graph/README.md | 4 +- .../01-create-graph/index.html | 16 +- .../02-create-nodes-sources/README.md | 12 +- .../create-nodes-sources.js | 6 +- .../create-nodes-sources.ts | 12 +- .../02-create-nodes-sources/index.html | 18 +- .../03-create-edges-sources/README.md | 6 +- .../create-edges-sources.js | 6 +- .../create-edges-sources.ts | 6 +- .../03-create-edges-sources/index.html | 12 +- .../04-group-nodes/README.md | 12 +- .../04-group-nodes/create-group-nodes.js | 12 +- .../04-group-nodes/create-group-nodes.ts | 12 +- .../04-group-nodes/index.html | 18 +- .../05-implicit-grouping/README.md | 2 +- .../implicit-group-nodes.js | 18 +- .../implicit-group-nodes.ts | 10 +- .../05-implicit-grouping/index.html | 8 +- .../06-configure-styles/configure-styles.js | 6 +- .../06-configure-styles/index.html | 6 +- .../07-create-labels-sources/README.md | 8 +- .../create-labels-sources.js | 10 +- .../create-labels-sources.ts | 8 +- .../07-create-labels-sources/index.html | 14 +- .../08-configure-labels/README.md | 4 +- .../08-configure-labels/configure-labels.js | 14 +- .../08-configure-labels/configure-labels.ts | 8 +- .../08-configure-labels/index.html | 10 +- .../09-configure-tags/app.js | 2 +- .../09-configure-tags/configure-tags.js | 2 +- .../09-configure-tags/index.html | 6 +- .../10-configure-layout/README.md | 9 +- .../10-configure-layout/configure-layout.js | 6 +- .../10-configure-layout/configure-layout.ts | 9 +- .../10-configure-layout/index.html | 15 +- .../11-update-graph/index.html | 6 +- .../12-adjacency-graph-builder/README.md | 6 +- .../adjacency-graph-building.js | 6 +- .../adjacency-graph-building.ts | 6 +- .../12-adjacency-graph-builder/index.html | 12 +- .../13-tree-builder/README.md | 7 +- .../13-tree-builder/index.html | 13 +- .../13-tree-builder/tree-graph-building.js | 4 +- .../13-tree-builder/tree-graph-building.ts | 7 +- demos/tutorial-graph-builder/common.js | 2 +- demos/tutorial-graph-builder/common.ts | 2 +- .../01-create-a-polyline/CustomEdgeStyle.js | 2 +- .../01-create-a-polyline/CustomEdgeStyle.ts | 2 +- .../01-create-a-polyline/README.md | 8 +- .../01-create-a-polyline/index.html | 24 +- .../02-crop-the-polyline/index.html | 8 +- .../03-create-parallel-polylines/index.html | 8 +- .../04-render-performance/index.html | 8 +- .../index.html | 8 +- .../06-data-from-tag/index.html | 8 +- .../07-hit-testing/index.html | 8 +- .../08-visibility/index.html | 8 +- .../09-bounds/index.html | 8 +- .../10-bridge-support/index.html | 8 +- .../11-adding-arrows/index.html | 8 +- .../12-custom-arrow/index.html | 8 +- .../common.js | 6 +- .../common.ts | 6 +- .../01-render-label-text/README.md | 4 +- .../01-render-label-text/index.html | 20 +- .../02-using-text-utilities/index.html | 8 +- .../03-add-background-shape/index.html | 8 +- .../04-preferred-size/index.html | 8 +- .../05-render-performance/index.html | 8 +- .../06-text-alignment/index.html | 8 +- .../07-line-wrapping/index.html | 8 +- .../08-data-from-tag/index.html | 8 +- .../09-hit-testing/index.html | 8 +- .../10-visibility/index.html | 8 +- .../11-bounds/index.html | 8 +- .../common.js | 4 +- .../common.ts | 4 +- .../01-create-a-rectangle/README.md | 6 +- .../01-create-a-rectangle/index.html | 22 +- .../02-create-a-custom-shape/index.html | 8 +- .../03-render-performance/index.html | 8 +- .../index.html | 8 +- .../05-data-from-tag/index.html | 8 +- .../06-render-text/index.html | 8 +- .../07-hit-testing/index.html | 8 +- .../08-edge-cropping/index.html | 8 +- .../09-visibility/index.html | 8 +- .../10-bounds/index.html | 8 +- .../CustomGroupNodeStyle.js | 2 +- .../CustomGroupNodeStyle.ts | 2 +- .../11-group-node-style/README.md | 2 +- .../11-group-node-style/index.html | 10 +- .../CustomGroupNodeStyle.js | 12 +- .../12-group-node-style-behavior/index.html | 8 +- .../common.js | 4 +- .../common.ts | 4 +- .../01-render-port-shape/README.md | 6 +- .../01-render-port-shape/index.html | 22 +- .../02-port-size/index.html | 8 +- .../03-render-performance/index.html | 8 +- .../04-conditional-coloring/index.html | 8 +- .../05-hit-testing/index.html | 8 +- .../06-edge-cropping/index.html | 8 +- .../common.js | 2 +- .../common.ts | 2 +- .../01-graphcomponent/README.md | 4 +- .../01-graphcomponent/index.html | 18 +- .../02-graph-element-creation/index.html | 8 +- .../03-managing-viewport/index.html | 8 +- .../04-setting-styles/index.html | 8 +- .../05-label-placement/index.html | 8 +- .../05-label-placement/label-placement.js | 4 +- .../05-label-placement/label-placement.ts | 4 +- .../06-basic-interaction/index.html | 8 +- .../07-undo-clipboard-support/index.html | 8 +- .../08-grouping/README.md | 2 +- .../08-grouping/app.js | 4 +- .../08-grouping/app.ts | 4 +- .../08-grouping/grouping.js | 2 +- .../08-grouping/grouping.ts | 2 +- .../08-grouping/index.html | 10 +- .../09-data-binding/data-binding.js | 2 +- .../09-data-binding/index.html | 8 +- .../10-layout/index.html | 8 +- .../11-layout-data/index.html | 8 +- .../11-layout-data/layout-data.js | 2 +- .../12-graph-analysis/app.js | 2 +- .../12-graph-analysis/app.ts | 2 +- .../12-graph-analysis/graph-analysis.js | 16 +- .../12-graph-analysis/graph-analysis.ts | 8 +- .../12-graph-analysis/index.html | 8 +- demos/utils/AggregationGraphWrapper.js | 90 +- demos/utils/AggregationGraphWrapper.ts | 80 +- demos/utils/ContextMenu.js | 50 +- demos/utils/ContextMenu.ts | 46 +- demos/utils/DragAndDropPanel.js | 12 +- demos/utils/DragAndDropPanel.ts | 6 +- demos/utils/GraphSearch.js | 16 +- demos/utils/GraphSearch.ts | 16 +- demos/utils/LoadLayoutFeaturesSampleGraph.js | 36 +- demos/utils/NodeTypePanel.js | 4 +- demos/utils/NodeTypePanel.ts | 4 +- demos/utils/PrintingSupport.js | 10 +- demos/utils/PrintingSupport.ts | 8 +- demos/utils/README.html | 2 +- demos/utils/RandomGraphGenerator.js | 6 +- demos/utils/RandomGraphGenerator.ts | 4 +- demos/utils/SimpleGraphBuilder.js | 46 +- demos/utils/SimpleGraphBuilder.ts | 26 +- demos/utils/VuejsNodeStyle.js | 6 +- demos/utils/VuejsNodeStyle.ts | 6 +- demos/utils/configure-two-pointer-panning.js | 11 +- demos/utils/configure-two-pointer-panning.ts | 11 +- demos/utils/file-support.js | 8 +- demos/utils/file-support.ts | 8 +- demos/utils/json-writer.js | 40 +- demos/utils/json-writer.ts | 40 +- demos/utils/package.json | 5 +- demos/utils/sample-graph.js | 2 +- demos/utils/sample-graph.ts | 2 +- demos/view/arrange-objects/index.html | 2 +- demos/view/bridges/BridgesDemo.js | 8 +- demos/view/bridges/BridgesDemo.ts | 8 +- demos/view/bridges/index.html | 2 +- .../DeferredCutClipboardDemo.js | 14 +- .../DeferredCutClipboardDemo.ts | 14 +- demos/view/clipboard-deferred-cut/index.html | 2 +- demos/view/clipboard/BusinessDataHandling.js | 2 +- demos/view/clipboard/ClipboardDemo.js | 6 +- demos/view/clipboard/ClipboardDemo.ts | 2 +- demos/view/clipboard/index.html | 2 +- demos/view/collapse/CollapseAndExpandNodes.js | 16 +- demos/view/collapse/CollapseAndExpandNodes.ts | 14 +- demos/view/collapse/CollapseDemo.js | 6 +- demos/view/collapse/CollapseDemo.ts | 2 +- demos/view/collapse/index.html | 2 +- .../contextualtoolbar/ContextualToolbar.js | 40 +- .../contextualtoolbar/ContextualToolbar.ts | 40 +- .../ContextualToolbarDemo.js | 18 +- .../ContextualToolbarDemo.ts | 18 +- demos/view/contextualtoolbar/index.html | 2 +- .../resources/contextual-toolbar.css | 12 +- demos/view/deep-zoom/deep-zoom-layout.js | 2 +- demos/view/deep-zoom/deep-zoom-layout.ts | 2 +- demos/view/deep-zoom/deep-zoom-update.js | 6 +- demos/view/deep-zoom/deep-zoom-update.ts | 6 +- demos/view/deep-zoom/index.html | 2 +- .../view/deep-zoom/model/load-sample-graph.js | 10 +- .../view/deep-zoom/model/load-sample-graph.ts | 10 +- demos/view/edgetoedge/EdgeToEdgeDemo.js | 6 +- demos/view/edgetoedge/EdgeToEdgeDemo.ts | 6 +- demos/view/edgetoedge/index.html | 2 +- demos/view/events/EventsDemo.js | 38 +- demos/view/events/EventsDemo.ts | 36 +- demos/view/events/index.html | 2 +- demos/view/ganttchart/GanttChartDemo.js | 11 +- demos/view/ganttchart/GanttChartDemo.ts | 5 +- demos/view/ganttchart/GridVisual.js | 2 +- .../ActivityNodeHandleProvider.ts | 5 +- .../ganttchart/components/TaskComponent.js | 6 +- .../ganttchart/components/TaskComponent.ts | 6 +- .../components/TimelineComponent.js | 2 +- .../components/TimelineComponent.ts | 2 +- demos/view/ganttchart/gantt-utils.js | 4 +- demos/view/ganttchart/gantt-utils.ts | 4 +- demos/view/ganttchart/index.html | 2 +- demos/view/ganttchart/info-panel.js | 2 +- demos/view/ganttchart/input.js | 2 +- demos/view/ganttchart/input.ts | 2 +- demos/view/ganttchart/sweepline-layout.js | 10 +- demos/view/ganttchart/sweepline-layout.ts | 10 +- demos/view/grapheditor/GraphEditorDemo.js | 26 +- demos/view/grapheditor/GraphEditorDemo.ts | 26 +- demos/view/grapheditor/index.html | 2 +- demos/view/graphml/EditorSync.js | 6 +- demos/view/graphml/EditorSync.ts | 6 +- demos/view/graphml/GraphMLDemo.js | 10 +- demos/view/graphml/GraphMLDemo.ts | 10 +- demos/view/graphml/GraphMLProperty.js | 2 - demos/view/graphml/GraphMLProperty.ts | 2 - demos/view/graphml/PropertiesPanel.js | 4 +- demos/view/graphml/PropertiesPanel.ts | 4 +- demos/view/graphml/PropertiesPanelUI.js | 4 +- demos/view/graphml/SimpleOutputHandler.ts | 5 +- demos/view/graphml/index.html | 2 +- demos/view/graphviewer/FastCanvasStyles.js | 6 +- demos/view/graphviewer/FastCanvasStyles.ts | 6 +- demos/view/graphviewer/GraphViewerDemo.js | 10 +- demos/view/graphviewer/GraphViewerDemo.ts | 10 +- demos/view/graphviewer/index.html | 2 +- demos/view/htmlpopup/index.html | 2 +- demos/view/imageexport/ImageExportDemo.js | 4 +- demos/view/imageexport/ImageExportDemo.ts | 4 +- demos/view/imageexport/aspect-ratio.js | 4 +- demos/view/imageexport/aspect-ratio.ts | 4 +- .../export-dialog/export-dialog.js | 2 +- .../export-dialog/export-dialog.ts | 2 +- .../imageexport/image-export-client-side.js | 2 +- .../imageexport/image-export-client-side.ts | 2 +- demos/view/imageexport/index.html | 2 +- .../view/imageexport/node-server/README.html | 2 +- demos/view/imageexport/node-server/index.html | 4 +- .../imageexport/option-panel/option-panel.js | 4 +- .../imageexport/option-panel/option-panel.ts | 4 +- demos/view/imageexport/server-side-export.js | 2 +- demos/view/imageexport/server-side-export.ts | 2 +- demos/view/imageexport/webgl-support.js | 4 +- demos/view/imageexport/webgl-support.ts | 4 +- demos/view/large-tree/LargeTreeDemo.js | 22 +- demos/view/large-tree/LargeTreeDemo.ts | 22 +- demos/view/large-tree/index.html | 2 +- demos/view/list-node/ListNodeDemo.js | 6 +- demos/view/list-node/ListNodeDemo.ts | 6 +- demos/view/list-node/ListNodeStyle.js | 4 +- demos/view/list-node/ListNodeStyle.ts | 4 +- demos/view/list-node/RowPositionHandler.js | 10 +- demos/view/list-node/RowPositionHandler.ts | 15 +- demos/view/list-node/index.html | 2 +- .../OverviewCanvasVisualCreator.js | 2 +- .../OverviewCanvasVisualCreator.ts | 2 +- .../view/overviewstyles/OverviewStylesDemo.js | 16 +- .../view/overviewstyles/OverviewStylesDemo.ts | 18 +- demos/view/overviewstyles/index.html | 2 +- demos/view/pdfexport/PdfExportDemo.js | 6 +- demos/view/pdfexport/PdfExportDemo.ts | 6 +- demos/view/pdfexport/aspect-ratio.js | 4 +- demos/view/pdfexport/aspect-ratio.ts | 4 +- .../pdfexport/export-dialog/export-dialog.js | 2 +- .../pdfexport/export-dialog/export-dialog.ts | 2 +- demos/view/pdfexport/index.html | 21 +- demos/view/pdfexport/load-external-fonts.js | 75 ++ demos/view/pdfexport/load-external-fonts.ts | 74 ++ demos/view/pdfexport/node-server/README.html | 2 +- demos/view/pdfexport/node-server/index.html | 2 +- .../pdfexport/option-panel/option-panel.js | 4 +- .../pdfexport/option-panel/option-panel.ts | 4 +- .../view/pdfexport/pdf-export-client-side.js | 48 +- .../view/pdfexport/pdf-export-client-side.ts | 49 +- .../view/pdfexport/resources/fonts/kosugi.js | 36 - .../resources/fonts/prata-regular-normal.js | 36 - demos/view/pdfexport/server-side-export.js | 2 +- demos/view/pdfexport/server-side-export.ts | 2 +- demos/view/pdfexport/webgl-support.js | 4 +- demos/view/pdfexport/webgl-support.ts | 4 +- demos/view/printing/PrintingDemo.js | 2 +- demos/view/printing/PrintingDemo.ts | 2 +- demos/view/printing/aspect-ratio.js | 4 +- demos/view/printing/aspect-ratio.ts | 4 +- demos/view/printing/index.html | 2 +- .../printing/option-panel/option-panel.js | 8 +- .../printing/option-panel/option-panel.ts | 8 +- demos/view/printing/printdocument.html | 3 +- demos/view/printing/webgl-support.js | 4 +- demos/view/printing/webgl-support.ts | 4 +- .../rendering-optimizations/Animations.js | 4 +- .../rendering-optimizations/Animations.ts | 4 +- .../CanvasEdgeStyle.js | 2 +- .../CanvasEdgeStyle.ts | 7 +- .../CanvasLabelStyle.js | 7 - .../CanvasLabelStyle.ts | 7 - .../ComplexCanvasNodeStyle.ts | 5 +- .../FastGraphModelManager.js | 54 +- .../FastGraphModelManager.ts | 54 +- .../RenderingOptimizationsDemo.js | 54 +- .../RenderingOptimizationsDemo.ts | 54 +- .../SimpleCanvasNodeStyle.ts | 5 +- .../rendering-optimizations/SvgEdgeStyle.ts | 11 +- .../rendering-optimizations/SvgLabelStyle.js | 7 - .../rendering-optimizations/SvgLabelStyle.ts | 7 - demos/view/rendering-optimizations/index.html | 2 +- .../resources/PreConfigurator.js | 2 +- .../resources/PreConfigurator.ts | 2 +- .../view/renderingorder/RenderingOrderDemo.js | 4 +- .../view/renderingorder/RenderingOrderDemo.ts | 4 +- demos/view/renderingorder/index.html | 2 +- demos/view/structureview/StructureView.js | 6 +- demos/view/structureview/StructureView.ts | 11 +- demos/view/structureview/StructureViewDemo.js | 20 +- demos/view/structureview/StructureViewDemo.ts | 18 +- demos/view/structureview/index.html | 2 +- demos/view/svgexport/SvgExportDemo.js | 4 +- demos/view/svgexport/SvgExportDemo.ts | 2 +- demos/view/svgexport/aspect-ratio.js | 4 +- demos/view/svgexport/aspect-ratio.ts | 4 +- .../svgexport/export-dialog/export-dialog.js | 2 +- .../svgexport/export-dialog/export-dialog.ts | 2 +- demos/view/svgexport/index.html | 2 +- .../svgexport/option-panel/option-panel.js | 2 +- .../svgexport/option-panel/option-panel.ts | 2 +- demos/view/svgexport/webgl-support.js | 4 +- demos/view/svgexport/webgl-support.ts | 4 +- .../WebGL2LabelFadingDemo.js | 6 +- .../WebGL2LabelFadingDemo.ts | 4 +- demos/view/webgl-label-fading/index.html | 2 +- demos/view/webgl-label-fading/label-fading.js | 6 +- demos/view/webgl-label-fading/label-fading.ts | 4 +- .../WebGLPrecompilationDemo.js | 8 +- .../WebGLPrecompilationDemo.ts | 8 +- demos/view/webgl-precompilation/index.html | 2 +- .../preload-webgl2-styles.js | 2 +- .../preload-webgl2-styles.ts | 2 +- .../webgl2-styles-util.js | 8 +- .../webgl2-styles-util.ts | 8 +- demos/view/zorder/ZOrderDemo.js | 6 +- demos/view/zorder/ZOrderDemo.ts | 6 +- demos/view/zorder/ZOrderSupport.js | 36 +- demos/view/zorder/ZOrderSupport.ts | 22 +- demos/view/zorder/index.html | 2 +- 1656 files changed, 17750 insertions(+), 9166 deletions(-) delete mode 100644 demos/loading/amdloading/AmdLoadingDemo.js delete mode 100644 demos/loading/amdloading/README.md delete mode 100644 demos/loading/nodejs/server/server.js create mode 100644 demos/loading/nodejs/server/server.mjs delete mode 100644 demos/loading/scriptloading/README.md delete mode 100644 demos/loading/scriptloading/ScriptLoadingDemo.js delete mode 100644 demos/loading/scriptloading/index.html delete mode 100644 demos/loading/webworker-umd/README.md delete mode 100644 demos/loading/webworker-umd/WebWorkerDemo.js delete mode 100644 demos/loading/webworker-umd/index.html delete mode 100644 demos/resources/image/amdloading.png delete mode 100644 demos/resources/image/export-pdf.png delete mode 100644 demos/resources/image/export-png.png delete mode 100644 demos/resources/image/export-print.png delete mode 100644 demos/resources/image/export-svg.png delete mode 100644 demos/resources/image/export.png create mode 100644 demos/resources/image/home-automation.png create mode 100644 demos/resources/image/imageexport.png create mode 100644 demos/resources/image/pdfexport.png delete mode 100644 demos/resources/image/scriptloading.png create mode 100644 demos/resources/image/svgexport.png delete mode 100644 demos/resources/image/webworkerumd.png create mode 100644 demos/showcase/home-automation/FlowEdge/FlowEdge.js create mode 100644 demos/showcase/home-automation/FlowEdge/FlowEdge.ts create mode 100644 demos/showcase/home-automation/FlowEdge/FlowEdgeStyle.js create mode 100644 demos/showcase/home-automation/FlowEdge/FlowEdgeStyle.ts create mode 100644 demos/showcase/home-automation/FlowEdge/FlowPortRelocationHandleProvider.js create mode 100644 demos/showcase/home-automation/FlowEdge/FlowPortRelocationHandleProvider.ts create mode 100644 demos/showcase/home-automation/FlowEdge/flowEdge.css create mode 100644 demos/showcase/home-automation/FlowNode/FlowEdgeReconnectionPortCandidateProvider.js create mode 100644 demos/showcase/home-automation/FlowNode/FlowEdgeReconnectionPortCandidateProvider.ts create mode 100644 demos/showcase/home-automation/FlowNode/FlowNode.js create mode 100644 demos/showcase/home-automation/FlowNode/FlowNode.ts create mode 100644 demos/showcase/home-automation/FlowNode/FlowNodePort.js create mode 100644 demos/showcase/home-automation/FlowNode/FlowNodePort.ts create mode 100644 demos/showcase/home-automation/FlowNode/FlowNodePortCandidateProvider.js create mode 100644 demos/showcase/home-automation/FlowNode/FlowNodePortCandidateProvider.ts create mode 100644 demos/showcase/home-automation/FlowNode/FlowNodePortStyle.js create mode 100644 demos/showcase/home-automation/FlowNode/FlowNodePortStyle.ts create mode 100644 demos/showcase/home-automation/FlowNode/FlowNodeStyle.js create mode 100644 demos/showcase/home-automation/FlowNode/FlowNodeStyle.ts create mode 100644 demos/showcase/home-automation/FlowNode/FlowNodeValidators.js create mode 100644 demos/showcase/home-automation/FlowNode/FlowNodeValidators.ts create mode 100644 demos/showcase/home-automation/FlowNode/flowNodeProperties.js create mode 100644 demos/showcase/home-automation/FlowNode/flowNodeProperties.ts create mode 100644 demos/showcase/home-automation/FlowNode/icons.js create mode 100644 demos/showcase/home-automation/FlowNode/icons.ts create mode 100644 demos/showcase/home-automation/HomeAutomationDemo.js create mode 100644 demos/showcase/home-automation/HomeAutomationDemo.ts create mode 100644 demos/showcase/home-automation/ImportExportManager/EdgeData.js create mode 100644 demos/showcase/home-automation/ImportExportManager/EdgeData.ts create mode 100644 demos/showcase/home-automation/ImportExportManager/GraphData.js create mode 100644 demos/showcase/home-automation/ImportExportManager/GraphData.ts create mode 100644 demos/showcase/home-automation/ImportExportManager/ImportExportManager.js create mode 100644 demos/showcase/home-automation/ImportExportManager/ImportExportManager.ts create mode 100644 demos/showcase/home-automation/ImportExportManager/NodeData.js create mode 100644 demos/showcase/home-automation/ImportExportManager/NodeData.ts create mode 100644 demos/showcase/home-automation/README.md create mode 100644 demos/showcase/home-automation/UI/initializeContextMenu.js create mode 100644 demos/showcase/home-automation/UI/initializeContextMenu.ts create mode 100644 demos/showcase/home-automation/UI/initializeDragAndDropPanel.js create mode 100644 demos/showcase/home-automation/UI/initializeDragAndDropPanel.ts create mode 100644 demos/showcase/home-automation/UI/initializeTagExplorer.js create mode 100644 demos/showcase/home-automation/UI/initializeTagExplorer.ts create mode 100644 demos/showcase/home-automation/UI/initializeToolbar.js create mode 100644 demos/showcase/home-automation/UI/initializeToolbar.ts create mode 100644 demos/showcase/home-automation/UI/initializeTooltips.js create mode 100644 demos/showcase/home-automation/UI/initializeTooltips.ts create mode 100644 demos/showcase/home-automation/UI/showErrorDialog.js create mode 100644 demos/showcase/home-automation/UI/showErrorDialog.ts create mode 100644 demos/showcase/home-automation/flow-context-menu/FlowContextMenu.js create mode 100644 demos/showcase/home-automation/flow-context-menu/FlowContextMenu.ts create mode 100644 demos/showcase/home-automation/flow-context-menu/contextMenuUtils.js create mode 100644 demos/showcase/home-automation/flow-context-menu/contextMenuUtils.ts rename demos/{loading/amdloading => showcase/home-automation}/index.html (54%) create mode 100644 demos/showcase/home-automation/inputMode/FlowCreateEdgeInputMode.js create mode 100644 demos/showcase/home-automation/inputMode/FlowCreateEdgeInputMode.ts create mode 100644 demos/showcase/home-automation/inputMode/FlowMoveInputMode.js create mode 100644 demos/showcase/home-automation/inputMode/FlowMoveInputMode.ts create mode 100644 demos/showcase/home-automation/inputMode/configureInputMode.js create mode 100644 demos/showcase/home-automation/inputMode/configureInputMode.ts create mode 100644 demos/showcase/home-automation/layout/HierarchicLayout.js create mode 100644 demos/showcase/home-automation/layout/HierarchicLayout.ts create mode 100644 demos/showcase/home-automation/layout/initializeSnapping.js create mode 100644 demos/showcase/home-automation/layout/initializeSnapping.ts create mode 100644 demos/showcase/home-automation/layout/initilaizeGrid.js create mode 100644 demos/showcase/home-automation/layout/initilaizeGrid.ts create mode 100644 demos/showcase/home-automation/layout/runLayout.js create mode 100644 demos/showcase/home-automation/layout/runLayout.ts create mode 100644 demos/showcase/home-automation/resources/style.css create mode 100644 demos/showcase/home-automation/resources/weather-data.js create mode 100644 demos/showcase/home-automation/resources/weather-data.ts create mode 100644 demos/showcase/home-automation/utils/configureDragAndDrop.js create mode 100644 demos/showcase/home-automation/utils/configureDragAndDrop.ts create mode 100644 demos/showcase/home-automation/utils/configureGraphEvents.js create mode 100644 demos/showcase/home-automation/utils/configureGraphEvents.ts create mode 100644 demos/showcase/home-automation/utils/customTriggers.js create mode 100644 demos/showcase/home-automation/utils/customTriggers.ts rename demos/{loading/webworker-umd/WorkerLayout.js => showcase/home-automation/utils/elementUtils.js} (51%) create mode 100644 demos/showcase/home-automation/utils/elementUtils.ts create mode 100644 demos/toolkit/angular/src/utils/BrowserDetection.ts create mode 100644 demos/toolkit/angular/src/utils/GraphSearch.ts create mode 100644 demos/toolkit/next/app/utils/BrowserDetection.ts create mode 100644 demos/toolkit/next/app/utils/GraphSearch.ts create mode 100644 demos/view/pdfexport/load-external-fonts.js create mode 100644 demos/view/pdfexport/load-external-fonts.ts delete mode 100644 demos/view/pdfexport/resources/fonts/kosugi.js delete mode 100644 demos/view/pdfexport/resources/fonts/prata-regular-normal.js diff --git a/demos/.prettierignore b/demos/.prettierignore index c35b48992..02d849318 100644 --- a/demos/.prettierignore +++ b/demos/.prettierignore @@ -2,8 +2,10 @@ **/dist/ **/node_modules/ /application-features/webgl-rendering/resources/*.json -/showcase/tree-of-life/resources/TreeOfLifeData.json +/showcase/large-graphs/resources/*.json /internal/webgl-performance-tests/resources/*.json +/showcase/tree-of-life/resources/TreeOfLifeData.json /starter-kits/** /view/large-graphs/resources/*.json /view/rendering-optimizations/resources/*.json +/view/webgl-label-fading/resources/hierarchic_2000_2100.json diff --git a/demos/.prettierrc.json b/demos/.prettierrc.json index 15af642b3..84d043693 100644 --- a/demos/.prettierrc.json +++ b/demos/.prettierrc.json @@ -1,13 +1,9 @@ { "printWidth": 100, - "tabWidth": 2, - "endOfLine": "auto", - "useTabs": false, "semi": false, "singleQuote": true, - "bracketSpacing": true, "trailingComma": "none", - "arrowParens": "avoid", + "endOfLine": "auto", "overrides": [ { "files": ["tutorial-*/**/*.ts"], diff --git a/demos/README.html b/demos/README.html index d64016deb..0af6fe0be 100644 --- a/demos/README.html +++ b/demos/README.html @@ -1,4 +1,4 @@ - + @@ -107,7 +107,7 @@

Your browser does not support modern CSS

- The yFiles for HTML 2.6.0.2 demo applications are available in + The yFiles for HTML 2.6.0.3 demo applications are available in both JavaScript and TypeScript. Show TypeScript Demos. { + dendrogramComponent.addDragFinishedListener((cutOffValue) => { removeClusterVisuals() runHierarchicalClustering(cutOffValue) }) @@ -474,7 +474,7 @@ function visualizeClusteringResult() { // creates a map the holds for each cluster id, the list of nodes that belong to the particular cluster const clustering = new Map() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { let clusterId = result.nodeClusterIds.get(node) // biconnected components returns -1 as cluster id when only one node is present. // We change the clusterId manually here, as otherwise we'll get an exception in @@ -592,7 +592,7 @@ function loadGraph(sampleData) { { data: sampleData.nodes, id: 'id', - layout: data => new Rect(data.x, data.y, data.w, data.h), + layout: (data) => new Rect(data.x, data.y, data.w, data.h), labels: ['label'] } ], @@ -623,10 +623,10 @@ function initializeUI() { // edge-betweenness menu const minInput = document.querySelector(`#ebMinClusterNumber`) - minInput.addEventListener('change', _ => { + minInput.addEventListener('change', (_) => { const value = parseFloat(minInput.value) const maximumClusterNumber = parseFloat(document.querySelector(`#ebMaxClusterNumber`).value) - if (isNaN(value) || value < 1) { + if (Number.isNaN(value) || value < 1) { alert('Number of clusters should be non-negative.') minInput.value = '1' return @@ -647,10 +647,10 @@ function initializeUI() { }) const maxInput = document.querySelector(`#ebMaxClusterNumber`) - maxInput.addEventListener('change', _ => { + maxInput.addEventListener('change', (_) => { const value = parseFloat(maxInput.value) const minimumClusterNumber = parseFloat(document.querySelector(`#ebMinClusterNumber`).value) - if (isNaN(value) || value < minimumClusterNumber || minimumClusterNumber < 1) { + if (Number.isNaN(value) || value < minimumClusterNumber || minimumClusterNumber < 1) { const message = value < minimumClusterNumber ? 'Desired maximum number of clusters cannot be smaller than the desired minimum number of clusters.' @@ -665,7 +665,7 @@ function initializeUI() { const considerEdgeDirection = document.querySelector(`#directed`) considerEdgeDirection.addEventListener('click', () => { const isChecked = considerEdgeDirection.checked - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { graph.setStyle(edge, isChecked ? directedEdgeStyle : graph.edgeDefaults.style) }) @@ -674,7 +674,7 @@ function initializeUI() { const considerEdgeCosts = document.querySelector(`#edgeCosts`) considerEdgeCosts.addEventListener('click', () => { - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { if (considerEdgeCosts.checked) { const edgeCost = Math.floor(Math.random() * 200 + 1) if (edge.labels.size > 0) { @@ -683,7 +683,7 @@ function initializeUI() { graph.addLabel(edge, `${edgeCost}`) } } else { - edge.labels.toArray().forEach(label => { + edge.labels.toArray().forEach((label) => { graph.remove(label) }) } @@ -695,9 +695,9 @@ function initializeUI() { const distanceCombobox = document.querySelector(`#distance-metrics`) distanceCombobox.addEventListener('change', runAlgorithm) const kMeansInput = document.querySelector(`#kMeansMaxClusterNumber`) - kMeansInput.addEventListener('change', _ => { + kMeansInput.addEventListener('change', (_) => { const value = parseFloat(kMeansInput.value) - if (isNaN(value) || value < 1) { + if (Number.isNaN(value) || value < 1) { alert('Desired maximum number of clusters should be greater than zero.') kMeansInput.value = '1' return @@ -705,9 +705,9 @@ function initializeUI() { runAlgorithm() }) const iterationInput = document.querySelector(`#iterations`) - iterationInput.addEventListener('change', _ => { + iterationInput.addEventListener('change', (_) => { const value = parseFloat(iterationInput.value) - if (isNaN(value) || value < 0) { + if (Number.isNaN(value) || value < 0) { alert('Desired maximum number of iterations should be non-negative.') iterationInput.value = '0' return @@ -748,7 +748,7 @@ function getEdgeWeight(edge) { if (edge.labels.size > 0) { // ...try to return its value const edgeWeight = parseFloat(edge.labels.first().text) - if (!isNaN(edgeWeight)) { + if (!Number.isNaN(edgeWeight)) { return edgeWeight > 0 ? edgeWeight : 1 } } diff --git a/demos/analysis/clustering/ClusteringDemo.ts b/demos/analysis/clustering/ClusteringDemo.ts index 6c309e1ec..984f4778e 100644 --- a/demos/analysis/clustering/ClusteringDemo.ts +++ b/demos/analysis/clustering/ClusteringDemo.ts @@ -270,7 +270,7 @@ function configureUserInteraction(graphComponent: GraphComponent): void { */ function configureDendrogramComponent(dendrogramComponent: DendrogramComponent): void { // add a dragging listener to run the hierarchical algorithm when the dragging of the cutoff line has finished - dendrogramComponent.addDragFinishedListener(cutOffValue => { + dendrogramComponent.addDragFinishedListener((cutOffValue) => { removeClusterVisuals() runHierarchicalClustering(cutOffValue) }) @@ -473,7 +473,7 @@ function visualizeClusteringResult(): void { // creates a map the holds for each cluster id, the list of nodes that belong to the particular cluster const clustering = new Map() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { let clusterId: number = result.nodeClusterIds.get(node)! // biconnected components returns -1 as cluster id when only one node is present. // We change the clusterId manually here, as otherwise we'll get an exception in @@ -622,12 +622,12 @@ function initializeUI(): void { // edge-betweenness menu const minInput = document.querySelector(`#ebMinClusterNumber`)! - minInput.addEventListener('change', _ => { + minInput.addEventListener('change', (_) => { const value = parseFloat(minInput.value) const maximumClusterNumber = parseFloat( document.querySelector(`#ebMaxClusterNumber`)!.value ) - if (isNaN(value) || value < 1) { + if (Number.isNaN(value) || value < 1) { alert('Number of clusters should be non-negative.') minInput.value = '1' return @@ -648,12 +648,12 @@ function initializeUI(): void { }) const maxInput = document.querySelector(`#ebMaxClusterNumber`)! - maxInput.addEventListener('change', _ => { + maxInput.addEventListener('change', (_) => { const value = parseFloat(maxInput.value) const minimumClusterNumber = parseFloat( document.querySelector(`#ebMinClusterNumber`)!.value ) - if (isNaN(value) || value < minimumClusterNumber || minimumClusterNumber < 1) { + if (Number.isNaN(value) || value < minimumClusterNumber || minimumClusterNumber < 1) { const message = value < minimumClusterNumber ? 'Desired maximum number of clusters cannot be smaller than the desired minimum number of clusters.' @@ -668,7 +668,7 @@ function initializeUI(): void { const considerEdgeDirection = document.querySelector(`#directed`)! considerEdgeDirection.addEventListener('click', () => { const isChecked = considerEdgeDirection.checked - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { graph.setStyle(edge, isChecked ? directedEdgeStyle : graph.edgeDefaults.style) }) @@ -677,7 +677,7 @@ function initializeUI(): void { const considerEdgeCosts = document.querySelector(`#edgeCosts`)! considerEdgeCosts.addEventListener('click', () => { - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { if (considerEdgeCosts.checked) { const edgeCost = Math.floor(Math.random() * 200 + 1) if (edge.labels.size > 0) { @@ -686,7 +686,7 @@ function initializeUI(): void { graph.addLabel(edge, `${edgeCost}`) } } else { - edge.labels.toArray().forEach(label => { + edge.labels.toArray().forEach((label) => { graph.remove(label) }) } @@ -698,9 +698,9 @@ function initializeUI(): void { const distanceCombobox = document.querySelector(`#distance-metrics`)! distanceCombobox.addEventListener('change', runAlgorithm) const kMeansInput = document.querySelector(`#kMeansMaxClusterNumber`)! - kMeansInput.addEventListener('change', _ => { + kMeansInput.addEventListener('change', (_) => { const value = parseFloat(kMeansInput.value) - if (isNaN(value) || value < 1) { + if (Number.isNaN(value) || value < 1) { alert('Desired maximum number of clusters should be greater than zero.') kMeansInput.value = '1' return @@ -708,9 +708,9 @@ function initializeUI(): void { runAlgorithm() }) const iterationInput = document.querySelector(`#iterations`)! - iterationInput.addEventListener('change', _ => { + iterationInput.addEventListener('change', (_) => { const value = parseFloat(iterationInput.value) - if (isNaN(value) || value < 0) { + if (Number.isNaN(value) || value < 0) { alert('Desired maximum number of iterations should be non-negative.') iterationInput.value = '0' return @@ -751,7 +751,7 @@ function getEdgeWeight(edge: IEdge): number { if (edge.labels.size > 0) { // ...try to return its value const edgeWeight = parseFloat(edge.labels.first().text) - if (!isNaN(edgeWeight)) { + if (!Number.isNaN(edgeWeight)) { return edgeWeight > 0 ? edgeWeight : 1 } } diff --git a/demos/analysis/clustering/DemoVisuals.js b/demos/analysis/clustering/DemoVisuals.js index 643c77b64..979d74897 100644 --- a/demos/analysis/clustering/DemoVisuals.js +++ b/demos/analysis/clustering/DemoVisuals.js @@ -99,7 +99,7 @@ export class VoronoiVisual extends BaseClass(IVisualCreator) { container.appendChild(svgPath) }) - this.clusters.centroids.forEach(point => { + this.clusters.centroids.forEach((point) => { VoronoiVisual.drawClusterCenter(point, container) }) @@ -172,7 +172,7 @@ export class PolygonVisual extends BaseClass(IVisualCreator) { let generalPath = new GeneralPath() if (clusterNodeBounds.length > 1) { const points = new YList() - clusterNodeBounds.forEach(layout => { + clusterNodeBounds.forEach((layout) => { const offset = 0 const x = layout.x const y = layout.y diff --git a/demos/analysis/clustering/DemoVisuals.ts b/demos/analysis/clustering/DemoVisuals.ts index f1c018c05..40ff835a7 100644 --- a/demos/analysis/clustering/DemoVisuals.ts +++ b/demos/analysis/clustering/DemoVisuals.ts @@ -104,7 +104,7 @@ export class VoronoiVisual container.appendChild(svgPath) }) - this.clusters.centroids.forEach(point => { + this.clusters.centroids.forEach((point) => { VoronoiVisual.drawClusterCenter(point, container) }) @@ -185,7 +185,7 @@ export class PolygonVisual let generalPath: GeneralPath = new GeneralPath() if (clusterNodeBounds.length > 1) { const points = new YList() - clusterNodeBounds.forEach(layout => { + clusterNodeBounds.forEach((layout) => { const offset = 0 const x = layout.x const y = layout.y @@ -254,7 +254,10 @@ export class AxisVisual extends BaseClass(IVisualCreator) implements IVisualCreator { - constructor(private maxY: number, private rect: Rect) { + constructor( + private maxY: number, + private rect: Rect + ) { super() } @@ -338,7 +341,10 @@ export class CutoffVisual { public cutOffValue: number - constructor(public rectangle: IRectangle, private readonly maxY: number) { + constructor( + public rectangle: IRectangle, + private readonly maxY: number + ) { super() this.rectangle = rectangle this.maxY = maxY diff --git a/demos/analysis/clustering/DendrogramSupport.js b/demos/analysis/clustering/DendrogramSupport.js index 13e106940..a2ae01a2d 100644 --- a/demos/analysis/clustering/DendrogramSupport.js +++ b/demos/analysis/clustering/DendrogramSupport.js @@ -227,14 +227,14 @@ export class DendrogramComponent { layers.set(hierarchicClusteredNode, layer) maxLayer = Math.max(layer, maxLayer) - node.children.forEach(child => { + node.children.forEach((child) => { stack.push(child) }) } // calculate the distance values and move all leaf-nodes to the bottommost layer const distanceValues = new Mapper() - hierarchicalGraph.nodes.forEach(node => { + hierarchicalGraph.nodes.forEach((node) => { // move all leaves at the bottom-most layers if (hierarchicalGraph.outDegree(node) === 0) { layers.set(node, maxLayer - 1) @@ -365,8 +365,8 @@ export class DendrogramComponent { ) this.visited = new Set() - result.clusters.forEach(cluster => { - cluster.nodes.forEach(node => { + result.clusters.forEach((cluster) => { + cluster.nodes.forEach((node) => { // get the color of the cluster in the original graph const color = colors[result.nodeClusterIds.get(node)] if (result.getDendrogramNode(node)) { @@ -380,7 +380,7 @@ export class DendrogramComponent { this.updateNodeStyle(hierarchicalParent, color) // update the style of the out-edges - this.dendrogramComponent.graph.outEdgesAt(hierarchicalParent).forEach(edge => { + this.dendrogramComponent.graph.outEdgesAt(hierarchicalParent).forEach((edge) => { this.updateEdgeStyle(edge, color) }) this.visited.add(parent) @@ -441,7 +441,7 @@ export class DendrogramComponent { const stack = [dendrogramNode] while (stack.length > 0) { const descendant = stack.pop() - descendant.children.forEach(childNode => { + descendant.children.forEach((childNode) => { highlightIndicatorManager.addHighlight(this.dendro2hierarchical.get(childNode)) stack.push(childNode) }) @@ -590,7 +590,7 @@ class DendrogramLayout extends BaseClass(ILayoutAlgorithm) { const distanceValues = graph.getDataProvider(DendrogramLayout.DISTANCE_VALUES_DP_KEY) - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const edges = node.outEdges.toArray() if (edges.length > 0) { // apply port constraints so that one of the edges adjacent to the source node uses the right side while @@ -604,7 +604,7 @@ class DendrogramLayout extends BaseClass(ILayoutAlgorithm) { }) // use the difference of the distances between the source and the target node as minimum length - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const edgeLength = distanceValues.get(edge.source) - distanceValues.get(edge.target) edgeLayoutDescriptors.set( edge, @@ -630,7 +630,7 @@ class DendrogramLayout extends BaseClass(ILayoutAlgorithm) { let maxYValue = -Number.MIN_VALUE let maxDistanceValue = -Number.MIN_VALUE - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const layer = layers.get(node) if (!layersMap.get(layer)) { layersMap.set(layer, []) @@ -641,18 +641,18 @@ class DendrogramLayout extends BaseClass(ILayoutAlgorithm) { }) this.maxY = -Number.MIN_VALUE - layersMap.forEach(layerNodes => { - layerNodes.forEach(node => { + layersMap.forEach((layerNodes) => { + layerNodes.forEach((node) => { const distanceValue = distanceValues.get(node) const newY = maxYValue - distanceValue // adjust the node center graph.setCenter(node, graph.getCenterX(node), newY) - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { // move also the bends const points = graph.getPointList(edge) const newBendPositions = new YList() - points.forEach(point => { + points.forEach((point) => { newBendPositions.add(new YPoint(point.x, newY)) }) graph.setPoints(edge, newBendPositions) @@ -666,7 +666,7 @@ class DendrogramLayout extends BaseClass(ILayoutAlgorithm) { // bends of the this.adjustXCoordinates(graph, graph.nodes.at(0)) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const updatedPath = new YList() updatedPath.add(new YPoint(graph.getCenterX(edge.target), graph.getCenterY(edge.source))) graph.setPoints(edge, updatedPath) @@ -686,7 +686,7 @@ class DendrogramLayout extends BaseClass(ILayoutAlgorithm) { if (root == null) { return } - root.outEdges.forEach(edge => { + root.outEdges.forEach((edge) => { this.adjustXCoordinates(graph, edge.target) }) diff --git a/demos/analysis/clustering/DendrogramSupport.ts b/demos/analysis/clustering/DendrogramSupport.ts index 065561f11..e7af28b59 100644 --- a/demos/analysis/clustering/DendrogramSupport.ts +++ b/demos/analysis/clustering/DendrogramSupport.ts @@ -225,14 +225,14 @@ export class DendrogramComponent { layers.set(hierarchicClusteredNode, layer) maxLayer = Math.max(layer, maxLayer) - node.children.forEach(child => { + node.children.forEach((child) => { stack.push(child) }) } // calculate the distance values and move all leaf-nodes to the bottommost layer const distanceValues = new Mapper() - hierarchicalGraph.nodes.forEach(node => { + hierarchicalGraph.nodes.forEach((node) => { // move all leaves at the bottom-most layers if (hierarchicalGraph.outDegree(node) === 0) { layers.set(node, maxLayer - 1) @@ -363,8 +363,8 @@ export class DendrogramComponent { ) this.visited = new Set() - result.clusters.forEach(cluster => { - cluster.nodes.forEach(node => { + result.clusters.forEach((cluster) => { + cluster.nodes.forEach((node) => { // get the color of the cluster in the original graph const color = colors[result.nodeClusterIds.get(node)!] if (result.getDendrogramNode(node)) { @@ -378,7 +378,7 @@ export class DendrogramComponent { this.updateNodeStyle(hierarchicalParent, color) // update the style of the out-edges - this.dendrogramComponent.graph.outEdgesAt(hierarchicalParent).forEach(edge => { + this.dendrogramComponent.graph.outEdgesAt(hierarchicalParent).forEach((edge) => { this.updateEdgeStyle(edge, color) }) this.visited.add(parent) @@ -439,7 +439,7 @@ export class DendrogramComponent { const stack: DendrogramNode[] = [dendrogramNode] while (stack.length > 0) { const descendant = stack.pop()! - descendant.children.forEach(childNode => { + descendant.children.forEach((childNode) => { highlightIndicatorManager.addHighlight(this.dendro2hierarchical.get(childNode)!) stack.push(childNode) }) @@ -588,7 +588,7 @@ class DendrogramLayout const distanceValues = graph.getDataProvider(DendrogramLayout.DISTANCE_VALUES_DP_KEY)! - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const edges = node.outEdges.toArray() if (edges.length > 0) { // apply port constraints so that one of the edges adjacent to the source node uses the right side while @@ -602,7 +602,7 @@ class DendrogramLayout }) // use the difference of the distances between the source and the target node as minimum length - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const edgeLength = distanceValues.get(edge.source) - distanceValues.get(edge.target) edgeLayoutDescriptors.set( edge, @@ -628,7 +628,7 @@ class DendrogramLayout let maxYValue: number = -Number.MIN_VALUE let maxDistanceValue: number = -Number.MIN_VALUE - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const layer = layers.get(node) if (!layersMap.get(layer)) { layersMap.set(layer, []) @@ -639,18 +639,18 @@ class DendrogramLayout }) this.maxY = -Number.MIN_VALUE - layersMap.forEach(layerNodes => { - layerNodes.forEach(node => { + layersMap.forEach((layerNodes) => { + layerNodes.forEach((node) => { const distanceValue = distanceValues.get(node) const newY = maxYValue - distanceValue // adjust the node center graph.setCenter(node, graph.getCenterX(node), newY) - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { // move also the bends const points = graph.getPointList(edge) const newBendPositions = new YList() - points.forEach(point => { + points.forEach((point) => { newBendPositions.add(new YPoint(point.x, newY)) }) graph.setPoints(edge, newBendPositions) @@ -664,7 +664,7 @@ class DendrogramLayout // bends of the this.adjustXCoordinates(graph, graph.nodes.at(0)) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const updatedPath = new YList() updatedPath.add(new YPoint(graph.getCenterX(edge.target), graph.getCenterY(edge.source))) graph.setPoints(edge, updatedPath) @@ -683,7 +683,7 @@ class DendrogramLayout if (root == null) { return } - root.outEdges.forEach(edge => { + root.outEdges.forEach((edge) => { this.adjustXCoordinates(graph, edge.target) }) diff --git a/demos/analysis/clustering/VoronoiDiagram.js b/demos/analysis/clustering/VoronoiDiagram.js index cc99d72ca..57976f5b1 100644 --- a/demos/analysis/clustering/VoronoiDiagram.js +++ b/demos/analysis/clustering/VoronoiDiagram.js @@ -91,7 +91,7 @@ export class VoronoiDiagram { const existOnlyTwoFaces = faces.size === 2 let externalFaceFound = false - faces.forEach(face => { + faces.forEach((face) => { // for each face, except the outerFace add a Voronoi node for this face that lies on the faces circumcenter if (!face.outer || (existOnlyTwoFaces && externalFaceFound)) { const circumcenter = face.calculateCircumcenter() @@ -135,10 +135,10 @@ export class VoronoiDiagram { const edges = outerFace != null ? outerFace.edges : [] const outerFaceEdges = new Set(edges) const visitedEdges = new Set() - faces.forEach(face => { + faces.forEach((face) => { if (!face.outer || (existOnlyTwoFaces && face.circumcenter)) { const circumcenter = face.circumcenter - face.edges.forEach(edge => { + face.edges.forEach((edge) => { const oppositeEdge = edge.target.getEdge(edge.source) if (!visitedEdges.has(edge) && !visitedEdges.has(oppositeEdge)) { visitedEdges.add(edge) @@ -334,7 +334,7 @@ export class VoronoiDiagram { // determine which nodes of the graph belong to the boundary so that we connect the consecutive ones and create // the Voronoi areas const boundaryNodes = [] - voronoiGraph.nodes.forEach(node => { + voronoiGraph.nodes.forEach((node) => { if (this.belongsToBoundary(voronoiNodeCoordinates.get(node))) { boundaryNodes.push(node) } @@ -381,7 +381,7 @@ export class VoronoiDiagram { // remove nodes that might lie on the exterior of the graph's bounding box, these can occur only if a // circumcenter lies on the exterior of the bounding box after the triangulation - voronoiGraph.nodes.toArray().forEach(node => { + voronoiGraph.nodes.toArray().forEach((node) => { if (node.degree === 0) { voronoiGraph.removeNode(node) } @@ -404,7 +404,7 @@ export class VoronoiDiagram { const revMap = delauneyGraph.createEdgeMap() // fill the pointData with the coordinates of the nodes of the delauney graph - this.centroids.forEach(centroid => { + this.centroids.forEach((centroid) => { const center = delauneyGraph.createNode() pointData.set(center, new YPoint(centroid.x, centroid.y)) }) @@ -443,7 +443,7 @@ export class VoronoiDiagram { calculateDelauneyFaces(graph, reversedEdgesMap, coordinatesMap, edge2face) { const mark = [] const faceList = new List() - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { if (!mark[edge.index]) { const face = this.createFace(edge, mark, reversedEdgesMap) faceList.add(face) @@ -451,8 +451,8 @@ export class VoronoiDiagram { } }) - faceList.forEach(face => { - face.edges.forEach(edge => { + faceList.forEach((face) => { + face.edges.forEach((edge) => { this.addEdgeToFace(edge, face, edge2face) this.addEdgeToFace(reversedEdgesMap.get(edge), face, edge2face) }) @@ -616,7 +616,7 @@ export class VoronoiDiagram { // for each edge segment, we create two darts, one for each direction s -> t and t -> s let index = -1 - voronoiGraph.edges.forEach(edge => { + voronoiGraph.edges.forEach((edge) => { const source = edge.source const target = edge.target const dart1 = new VoronoiDart(source, target, edge, index++) @@ -640,7 +640,7 @@ export class VoronoiDiagram { }) // for each dart, we calculate the angle that creates with the x-axis in counter-clockwise order - darts.forEach(dart => { + darts.forEach((dart) => { const sourceCenter = voronoiNodeCoordinates.get(dart.source) const targetCenter = voronoiNodeCoordinates.get(dart.target) const angle = Math.atan2(sourceCenter.y - targetCenter.y, sourceCenter.x - targetCenter.x) @@ -648,7 +648,7 @@ export class VoronoiDiagram { }) // we sort the darts around their origin based on the angle the form with the x-axis - voronoiGraph.nodes.forEach(node => { + voronoiGraph.nodes.forEach((node) => { const nodeDarts = node2Darts.get(node) if (nodeDarts !== null) { nodeDarts.sort((dart1, dart2) => { @@ -668,7 +668,7 @@ export class VoronoiDiagram { // we iterate over the darts to create the faces const faces = [] - darts.forEach(dart => { + darts.forEach((dart) => { const face = [] if (!dart.marked) { let d = dart @@ -697,7 +697,7 @@ export class VoronoiDiagram { // we create the general paths that form the geometric face const voronoiFaces = [] - faces.forEach(face => { + faces.forEach((face) => { if (face.length > 2) { const facePath = new GeneralPath() for (let i = 0; i < face.length - 1; i++) { @@ -856,8 +856,6 @@ class VoronoiFace { */ outer = false - constructor() {} - /** * Adds the given edges to the list of edges of the given face. * @param {!Edge} edge The edge to add diff --git a/demos/analysis/clustering/VoronoiDiagram.ts b/demos/analysis/clustering/VoronoiDiagram.ts index 7476aadc3..b1a66b29d 100644 --- a/demos/analysis/clustering/VoronoiDiagram.ts +++ b/demos/analysis/clustering/VoronoiDiagram.ts @@ -89,7 +89,7 @@ export class VoronoiDiagram { const existOnlyTwoFaces = faces.size === 2 let externalFaceFound = false - faces.forEach(face => { + faces.forEach((face) => { // for each face, except the outerFace add a Voronoi node for this face that lies on the faces circumcenter if (!face.outer || (existOnlyTwoFaces && externalFaceFound)) { const circumcenter = face.calculateCircumcenter() @@ -133,10 +133,10 @@ export class VoronoiDiagram { const edges: Edge[] = outerFace != null ? (outerFace as VoronoiFace).edges : [] const outerFaceEdges = new Set(edges) const visitedEdges = new Set() - faces.forEach(face => { + faces.forEach((face) => { if (!face.outer || (existOnlyTwoFaces && face.circumcenter)) { const circumcenter = face.circumcenter! - face.edges.forEach(edge => { + face.edges.forEach((edge) => { const oppositeEdge = edge.target.getEdge(edge.source)! if (!visitedEdges.has(edge) && !visitedEdges.has(oppositeEdge)) { visitedEdges.add(edge) @@ -332,7 +332,7 @@ export class VoronoiDiagram { // determine which nodes of the graph belong to the boundary so that we connect the consecutive ones and create // the Voronoi areas const boundaryNodes: YNode[] = [] - voronoiGraph.nodes.forEach(node => { + voronoiGraph.nodes.forEach((node) => { if (this.belongsToBoundary(voronoiNodeCoordinates.get(node))) { boundaryNodes.push(node) } @@ -379,7 +379,7 @@ export class VoronoiDiagram { // remove nodes that might lie on the exterior of the graph's bounding box, these can occur only if a // circumcenter lies on the exterior of the bounding box after the triangulation - voronoiGraph.nodes.toArray().forEach(node => { + voronoiGraph.nodes.toArray().forEach((node) => { if (node.degree === 0) { voronoiGraph.removeNode(node) } @@ -405,7 +405,7 @@ export class VoronoiDiagram { const revMap = delauneyGraph.createEdgeMap() // fill the pointData with the coordinates of the nodes of the delauney graph - this.centroids.forEach(centroid => { + this.centroids.forEach((centroid) => { const center = delauneyGraph.createNode() pointData.set(center, new YPoint(centroid.x, centroid.y)) }) @@ -449,7 +449,7 @@ export class VoronoiDiagram { ): IEnumerable { const mark: boolean[] = [] const faceList = new List() - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { if (!mark[edge.index]) { const face = this.createFace(edge, mark, reversedEdgesMap) faceList.add(face) @@ -457,8 +457,8 @@ export class VoronoiDiagram { } }) - faceList.forEach(face => { - face.edges.forEach(edge => { + faceList.forEach((face) => { + face.edges.forEach((edge) => { this.addEdgeToFace(edge, face, edge2face) this.addEdgeToFace(reversedEdgesMap.get(edge), face, edge2face) }) @@ -627,7 +627,7 @@ export class VoronoiDiagram { // for each edge segment, we create two darts, one for each direction s -> t and t -> s let index = -1 - voronoiGraph.edges.forEach(edge => { + voronoiGraph.edges.forEach((edge) => { const source = edge.source const target = edge.target const dart1 = new VoronoiDart(source, target, edge, index++) @@ -651,7 +651,7 @@ export class VoronoiDiagram { }) // for each dart, we calculate the angle that creates with the x-axis in counter-clockwise order - darts.forEach(dart => { + darts.forEach((dart) => { const sourceCenter = voronoiNodeCoordinates.get(dart.source) const targetCenter = voronoiNodeCoordinates.get(dart.target) const angle = Math.atan2(sourceCenter.y - targetCenter.y, sourceCenter.x - targetCenter.x) @@ -659,7 +659,7 @@ export class VoronoiDiagram { }) // we sort the darts around their origin based on the angle the form with the x-axis - voronoiGraph.nodes.forEach(node => { + voronoiGraph.nodes.forEach((node) => { const nodeDarts = node2Darts.get(node) if (nodeDarts !== null) { nodeDarts.sort((dart1, dart2) => { @@ -679,7 +679,7 @@ export class VoronoiDiagram { // we iterate over the darts to create the faces const faces: VoronoiDart[][] = [] - darts.forEach(dart => { + darts.forEach((dart) => { const face: VoronoiDart[] = [] if (!dart.marked) { let d: VoronoiDart = dart @@ -708,7 +708,7 @@ export class VoronoiDiagram { // we create the general paths that form the geometric face const voronoiFaces: GeneralPath[] = [] - faces.forEach(face => { + faces.forEach((face) => { if (face.length > 2) { const facePath = new GeneralPath() for (let i = 0; i < face.length - 1; i++) { @@ -866,8 +866,6 @@ class VoronoiFace { */ outer = false - constructor() {} - /** * Adds the given edges to the list of edges of the given face. * @param edge The edge to add diff --git a/demos/analysis/clustering/index.html b/demos/analysis/clustering/index.html index d7a37a7b3..6be434b0d 100644 --- a/demos/analysis/clustering/index.html +++ b/demos/analysis/clustering/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/analysis/criticalpathanalysis/CriticalPathAnalysisDemo.js b/demos/analysis/criticalpathanalysis/CriticalPathAnalysisDemo.js index 793634b18..3fde2234f 100644 --- a/demos/analysis/criticalpathanalysis/CriticalPathAnalysisDemo.js +++ b/demos/analysis/criticalpathanalysis/CriticalPathAnalysisDemo.js @@ -102,7 +102,7 @@ function showResult(graphComponent) { const startNodeStyle = createDemoNodeStyle('demo-palette-402') const finishNodeStyle = createDemoNodeStyle('demo-palette-403') - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const sourceNode = edge.sourceNode const targetNode = edge.targetNode @@ -181,7 +181,7 @@ function loadSampleGraph(graphComponent) { builder.buildGraph() // we add a label that shows the duration of each task - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (node.labels.get(0).text !== 'START' && node.labels.get(0).text !== 'FINISH') { graph.addLabel(node, `${node.tag.duration} d`, InteriorLabelModel.CENTER) } diff --git a/demos/analysis/criticalpathanalysis/CriticalPathAnalysisDemo.ts b/demos/analysis/criticalpathanalysis/CriticalPathAnalysisDemo.ts index efa0ec2bd..94fc84b22 100644 --- a/demos/analysis/criticalpathanalysis/CriticalPathAnalysisDemo.ts +++ b/demos/analysis/criticalpathanalysis/CriticalPathAnalysisDemo.ts @@ -101,7 +101,7 @@ function showResult(graphComponent: GraphComponent) { const startNodeStyle = createDemoNodeStyle('demo-palette-402') const finishNodeStyle = createDemoNodeStyle('demo-palette-403') - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const sourceNode = edge.sourceNode! const targetNode = edge.targetNode! @@ -180,7 +180,7 @@ function loadSampleGraph(graphComponent: GraphComponent) { builder.buildGraph() // we add a label that shows the duration of each task - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (node.labels.get(0).text !== 'START' && node.labels.get(0).text !== 'FINISH') { graph.addLabel(node, `${node.tag.duration} d`, InteriorLabelModel.CENTER) } diff --git a/demos/analysis/criticalpathanalysis/CriticalPathHelper.js b/demos/analysis/criticalpathanalysis/CriticalPathHelper.js index be361bcc2..a3f077ff6 100644 --- a/demos/analysis/criticalpathanalysis/CriticalPathHelper.js +++ b/demos/analysis/criticalpathanalysis/CriticalPathHelper.js @@ -51,9 +51,9 @@ import { export function calculateCriticalPathEdges(graphComponent) { const graph = graphComponent.graph // the duration of each task is stored in the node's tag - const taskDuration = node => node.tag.duration | 0 + const taskDuration = (node) => node.tag.duration | 0 // the duration when moving from the source's task to the target's task - const transitionDuration = edge => edge.tag.transitionDuration | 0 + const transitionDuration = (edge) => edge.tag.transitionDuration | 0 // runs the rank assignment algorithm to calculate the ranks and the slacks const results = calculateRanksAndSlacks(graph, taskDuration, transitionDuration) @@ -74,7 +74,7 @@ export function calculateCriticalPathEdges(graphComponent) { // adds the result information to the edges' tags const criticalEdgeSet = new Set(criticalPathEdges) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { edge.tag.slack = results.slack(edge) if (criticalEdgeSet.has(edge)) { edge.tag.critical = true @@ -88,7 +88,7 @@ export function calculateCriticalPathEdges(graphComponent) { */ export function findHighestLowestNodes(graph) { const order = graph.nodes.orderBy( - node => node.tag.layerId || 0, + (node) => node.tag.layerId || 0, (a, b) => Math.sign(Number(a) - Number(b)) ) const lowestNode = order.first() @@ -108,23 +108,23 @@ export function findHighestLowestNodes(graph) { function calculateRanksAndSlacks(graph, taskDuration, transitionDuration) { // for each edge the min distance is the time needed for the task of each source node to be completed // plus the time needed to move from the source task to the target task - const minDistance = edge => { + const minDistance = (edge) => { return transitionDuration(edge) + taskDuration(edge.sourceNode) } // run the rank assignment algorithm const rankAssignmentResult = new RankAssignment({ - minimumEdgeLengths: edge => transitionDuration(edge) + taskDuration(edge.sourceNode) + minimumEdgeLengths: (edge) => transitionDuration(edge) + taskDuration(edge.sourceNode) }).run(graph) // store the ranking of each node at its tag const rankIds = rankAssignmentResult.nodeRankIds - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { node.tag.layerId = rankIds.get(node) || 0 }) return { - slack: edge => + slack: (edge) => (rankIds.get(edge.targetNode) || 0) - (rankIds.get(edge.sourceNode) || 0) - minDistance(edge) } } @@ -148,9 +148,9 @@ export async function runLayout(graphComponent) { const layoutData = new HierarchicLayoutData({ // the information about the layering is stored in the node tags - givenLayersLayererIds: node => node.tag.layerId, + givenLayersLayererIds: (node) => node.tag.layerId, // edges that belong to the critical path have priority - criticalEdgePriorities: edge => (edge.tag.critical ? 1 : 0), + criticalEdgePriorities: (edge) => (edge.tag.critical ? 1 : 0), // configure the edge placement edgeLabelPreferredPlacement: () => { const preferredPlacementDescriptor = new PreferredPlacementDescriptor() diff --git a/demos/analysis/criticalpathanalysis/CriticalPathHelper.ts b/demos/analysis/criticalpathanalysis/CriticalPathHelper.ts index d056f57e9..3b25be6da 100644 --- a/demos/analysis/criticalpathanalysis/CriticalPathHelper.ts +++ b/demos/analysis/criticalpathanalysis/CriticalPathHelper.ts @@ -74,7 +74,7 @@ export function calculateCriticalPathEdges(graphComponent: GraphComponent) { // adds the result information to the edges' tags const criticalEdgeSet = new Set(criticalPathEdges) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { edge.tag.slack = results.slack(edge) if (criticalEdgeSet.has(edge)) { edge.tag.critical = true @@ -88,7 +88,7 @@ export function calculateCriticalPathEdges(graphComponent: GraphComponent) { */ export function findHighestLowestNodes(graph: IGraph) { const order = graph.nodes.orderBy( - node => node.tag.layerId || 0, + (node) => node.tag.layerId || 0, (a, b) => Math.sign(Number(a) - Number(b)) ) const lowestNode = order.first() @@ -117,17 +117,17 @@ function calculateRanksAndSlacks( // run the rank assignment algorithm const rankAssignmentResult = new RankAssignment({ - minimumEdgeLengths: edge => transitionDuration(edge) + taskDuration(edge.sourceNode!) + minimumEdgeLengths: (edge) => transitionDuration(edge) + taskDuration(edge.sourceNode!) }).run(graph) // store the ranking of each node at its tag const rankIds = rankAssignmentResult.nodeRankIds - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { node.tag.layerId = rankIds.get(node) || 0 }) return { - slack: edge => + slack: (edge) => (rankIds.get(edge.targetNode) || 0) - (rankIds.get(edge.sourceNode) || 0) - minDistance(edge) } } @@ -150,9 +150,9 @@ export async function runLayout(graphComponent: GraphComponent): Promise { const layoutData = new HierarchicLayoutData({ // the information about the layering is stored in the node tags - givenLayersLayererIds: node => node.tag.layerId, + givenLayersLayererIds: (node) => node.tag.layerId, // edges that belong to the critical path have priority - criticalEdgePriorities: edge => (edge.tag.critical ? 1 : 0), + criticalEdgePriorities: (edge) => (edge.tag.critical ? 1 : 0), // configure the edge placement edgeLabelPreferredPlacement: () => { const preferredPlacementDescriptor = new PreferredPlacementDescriptor() diff --git a/demos/analysis/criticalpathanalysis/index.html b/demos/analysis/criticalpathanalysis/index.html index 4643e4bb2..536203ae0 100644 --- a/demos/analysis/criticalpathanalysis/index.html +++ b/demos/analysis/criticalpathanalysis/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/analysis/intersection-detection/DemoVisuals.js b/demos/analysis/intersection-detection/DemoVisuals.js index 944ff156a..0c3772399 100644 --- a/demos/analysis/intersection-detection/DemoVisuals.js +++ b/demos/analysis/intersection-detection/DemoVisuals.js @@ -55,13 +55,6 @@ const LABEL_INTERSECTION_COLOR_STROKE = Color.from(colorSets['demo-green'].strok export class IntersectionVisualCreator extends BaseClass(IVisualCreator) { intersections = [] - /** - * Creates a new instance. - */ - constructor() { - super() - } - /** * Creates the visual showing the intersections found by the intersection algorithm. * @param {!IRenderContext} context The context that describes where the visual will be used diff --git a/demos/analysis/intersection-detection/DemoVisuals.ts b/demos/analysis/intersection-detection/DemoVisuals.ts index df7273cbf..38cb1e768 100644 --- a/demos/analysis/intersection-detection/DemoVisuals.ts +++ b/demos/analysis/intersection-detection/DemoVisuals.ts @@ -58,13 +58,6 @@ export class IntersectionVisualCreator { public intersections: Intersection[] = [] - /** - * Creates a new instance. - */ - constructor() { - super() - } - /** * Creates the visual showing the intersections found by the intersection algorithm. * @param context The context that describes where the visual will be used diff --git a/demos/analysis/intersection-detection/IntersectionDetectionDemo.js b/demos/analysis/intersection-detection/IntersectionDetectionDemo.js index 6e03f312b..0169b0260 100644 --- a/demos/analysis/intersection-detection/IntersectionDetectionDemo.js +++ b/demos/analysis/intersection-detection/IntersectionDetectionDemo.js @@ -178,7 +178,7 @@ function runIntersectionAlgorithm() { // whether to consider only the selected elements if (considerSelectionBox.checked) { - intersections.affectedItems.delegate = item => graphComponent.selection.isSelected(item) + intersections.affectedItems.delegate = (item) => graphComponent.selection.isSelected(item) } // run the algorithm and obtain the result @@ -250,10 +250,10 @@ function initializeIntersectionVisual() { function loadSampleGraph(graph) { const builder = new GraphBuilder(graph) const ns = builder.createNodesSource({ - data: GraphData.nodeList.filter(data => !data.isGroup), + data: GraphData.nodeList.filter((data) => !data.isGroup), id: 'id', layout: 'layout', - parentId: dataItem => dataItem.parent + parentId: (dataItem) => dataItem.parent }) ns.nodeCreator.addNodeCreatedListener((_, evt) => { if (evt.dataItem.isEllipse) { @@ -268,8 +268,10 @@ function loadSampleGraph(graph) { ) } }) - const nodeLabelCreator = ns.nodeCreator.createLabelsSource(data => data.labels || []).labelCreator - nodeLabelCreator.textProvider = data => data.text || '' + const nodeLabelCreator = ns.nodeCreator.createLabelsSource( + (data) => data.labels || [] + ).labelCreator + nodeLabelCreator.textProvider = (data) => data.text || '' nodeLabelCreator.addLabelAddedListener((_, evt) => { const label = evt.item const data = evt.dataItem @@ -284,14 +286,14 @@ function loadSampleGraph(graph) { }) const groupSource = builder.createGroupNodesSource({ - data: GraphData.nodeList.filter(data => data.isGroup), + data: GraphData.nodeList.filter((data) => data.isGroup), id: 'id', layout: 'layout' }) const groupLabelCreator = groupSource.nodeCreator.createLabelsSource( - data => data.labels + (data) => data.labels ).labelCreator - groupLabelCreator.textProvider = data => data.text || '' + groupLabelCreator.textProvider = (data) => data.text || '' const es = builder.createEdgesSource({ data: GraphData.edgeList, @@ -300,8 +302,10 @@ function loadSampleGraph(graph) { targetId: 'target', bends: 'bends' }) - const edgeLabelCreator = es.edgeCreator.createLabelsSource(data => data.labels || []).labelCreator - edgeLabelCreator.textProvider = data => data.text || '' + const edgeLabelCreator = es.edgeCreator.createLabelsSource( + (data) => data.labels || [] + ).labelCreator + edgeLabelCreator.textProvider = (data) => data.text || '' edgeLabelCreator.addLabelAddedListener((_, evt) => { const label = evt.item const data = evt.dataItem @@ -317,7 +321,7 @@ function loadSampleGraph(graph) { builder.buildGraph() - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { if (edge.tag.sourcePort) { graph.setPortLocation(edge.sourcePort, Point.from(edge.tag.sourcePort)) } @@ -363,7 +367,7 @@ function initializeGraph(graphComponent) { graph.decorator.portDecorator.edgePathCropperDecorator.setImplementation( new DefaultEdgePathCropper({ cropAtPort: false, extraCropLength: 0 }) ) - graph.decorator.labelDecorator.positionHandlerDecorator.setFactory(label => { + graph.decorator.labelDecorator.positionHandlerDecorator.setFactory((label) => { const positionHandler = new LabelPositionHandler(label) positionHandler.visualization = Visualization.LIVE return positionHandler diff --git a/demos/analysis/intersection-detection/IntersectionDetectionDemo.ts b/demos/analysis/intersection-detection/IntersectionDetectionDemo.ts index d60693897..1f47003eb 100644 --- a/demos/analysis/intersection-detection/IntersectionDetectionDemo.ts +++ b/demos/analysis/intersection-detection/IntersectionDetectionDemo.ts @@ -174,7 +174,7 @@ function runIntersectionAlgorithm(): void { // whether to consider only the selected elements if (considerSelectionBox.checked) { - intersections.affectedItems.delegate = item => graphComponent.selection.isSelected(item) + intersections.affectedItems.delegate = (item) => graphComponent.selection.isSelected(item) } // run the algorithm and obtain the result @@ -243,10 +243,10 @@ function initializeIntersectionVisual(): void { function loadSampleGraph(graph: IGraph): void { const builder = new GraphBuilder(graph) const ns = builder.createNodesSource({ - data: GraphData.nodeList.filter(data => !data.isGroup), + data: GraphData.nodeList.filter((data) => !data.isGroup), id: 'id', layout: 'layout', - parentId: dataItem => dataItem.parent + parentId: (dataItem) => dataItem.parent }) ns.nodeCreator.addNodeCreatedListener((_, evt) => { if (evt.dataItem.isEllipse) { @@ -261,8 +261,10 @@ function loadSampleGraph(graph: IGraph): void { ) } }) - const nodeLabelCreator = ns.nodeCreator.createLabelsSource(data => data.labels || []).labelCreator - nodeLabelCreator.textProvider = data => data.text || '' + const nodeLabelCreator = ns.nodeCreator.createLabelsSource( + (data) => data.labels || [] + ).labelCreator + nodeLabelCreator.textProvider = (data) => data.text || '' nodeLabelCreator.addLabelAddedListener((_, evt) => { const label = evt.item const data = evt.dataItem @@ -277,14 +279,14 @@ function loadSampleGraph(graph: IGraph): void { }) const groupSource = builder.createGroupNodesSource({ - data: GraphData.nodeList.filter(data => data.isGroup), + data: GraphData.nodeList.filter((data) => data.isGroup), id: 'id', layout: 'layout' }) const groupLabelCreator = groupSource.nodeCreator.createLabelsSource( - data => data.labels + (data) => data.labels ).labelCreator - groupLabelCreator.textProvider = data => data.text || '' + groupLabelCreator.textProvider = (data) => data.text || '' const es = builder.createEdgesSource({ data: GraphData.edgeList, @@ -293,8 +295,10 @@ function loadSampleGraph(graph: IGraph): void { targetId: 'target', bends: 'bends' }) - const edgeLabelCreator = es.edgeCreator.createLabelsSource(data => data.labels || []).labelCreator - edgeLabelCreator.textProvider = data => data.text || '' + const edgeLabelCreator = es.edgeCreator.createLabelsSource( + (data) => data.labels || [] + ).labelCreator + edgeLabelCreator.textProvider = (data) => data.text || '' edgeLabelCreator.addLabelAddedListener((_, evt) => { const label = evt.item const data = evt.dataItem @@ -310,7 +314,7 @@ function loadSampleGraph(graph: IGraph): void { builder.buildGraph() - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { if (edge.tag.sourcePort) { graph.setPortLocation(edge.sourcePort!, Point.from(edge.tag.sourcePort)) } @@ -355,7 +359,7 @@ function initializeGraph(graphComponent: GraphComponent): void { graph.decorator.portDecorator.edgePathCropperDecorator.setImplementation( new DefaultEdgePathCropper({ cropAtPort: false, extraCropLength: 0 }) ) - graph.decorator.labelDecorator.positionHandlerDecorator.setFactory(label => { + graph.decorator.labelDecorator.positionHandlerDecorator.setFactory((label) => { const positionHandler = new LabelPositionHandler(label) positionHandler.visualization = Visualization.LIVE return positionHandler diff --git a/demos/analysis/intersection-detection/TooltipHelper.js b/demos/analysis/intersection-detection/TooltipHelper.js index f04cb3f7a..caee8cbb5 100644 --- a/demos/analysis/intersection-detection/TooltipHelper.js +++ b/demos/analysis/intersection-detection/TooltipHelper.js @@ -38,7 +38,7 @@ import { IEdge, IModelItem, INode, Intersection, Point } from 'yfiles' */ export function createToolTipContent(item, intersectionInfoArray) { const filteredIntersections = intersectionInfoArray.filter( - intersection => item === intersection.item1 || item === intersection.item2 + (intersection) => item === intersection.item1 || item === intersection.item2 ) if (filteredIntersections.length === 0) { return null diff --git a/demos/analysis/intersection-detection/TooltipHelper.ts b/demos/analysis/intersection-detection/TooltipHelper.ts index 263c7522b..ff00b5d8e 100644 --- a/demos/analysis/intersection-detection/TooltipHelper.ts +++ b/demos/analysis/intersection-detection/TooltipHelper.ts @@ -38,7 +38,7 @@ export function createToolTipContent( intersectionInfoArray: Intersection[] ): HTMLElement | null { const filteredIntersections = intersectionInfoArray.filter( - intersection => item === intersection.item1 || item === intersection.item2 + (intersection) => item === intersection.item1 || item === intersection.item2 ) if (filteredIntersections.length === 0) { return null diff --git a/demos/analysis/intersection-detection/index.html b/demos/analysis/intersection-detection/index.html index b742a94f6..69da9c4e4 100644 --- a/demos/analysis/intersection-detection/index.html +++ b/demos/analysis/intersection-detection/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/analysis/networkflows/DemoStyles.js b/demos/analysis/networkflows/DemoStyles.js index d4b2f9d06..0e1fc8f0c 100644 --- a/demos/analysis/networkflows/DemoStyles.js +++ b/demos/analysis/networkflows/DemoStyles.js @@ -552,7 +552,7 @@ export class NetworkFlowEdgeStyle extends EdgeStyleBase { gradientsToRemove.push(child) } } - gradientsToRemove.forEach(gradient => defs.removeChild(gradient)) + gradientsToRemove.forEach((gradient) => defs.removeChild(gradient)) } /** @@ -609,7 +609,7 @@ export class NetworkFlowEdgeStyle extends EdgeStyleBase { const path = new GeneralPath() path.moveTo(edge.sourcePort.location) path.lineTo(edge.sourcePort.location.add(new Point(5, 0))) - edge.bends.forEach(bend => path.lineTo(bend.location)) + edge.bends.forEach((bend) => path.lineTo(bend.location)) path.lineTo(edge.targetPort.location.subtract(new Point(5, 0))) path.lineTo(edge.targetPort.location) return path @@ -692,7 +692,7 @@ function createAnimatedGradient(linearGradient) { let previousTime = null - const frameRequestCallback = timestamp => { + const frameRequestCallback = (timestamp) => { // calculate the time since the last animation frame if (previousTime == null) { previousTime = timestamp diff --git a/demos/analysis/networkflows/DemoStyles.ts b/demos/analysis/networkflows/DemoStyles.ts index fa59dedf3..d180356dd 100644 --- a/demos/analysis/networkflows/DemoStyles.ts +++ b/demos/analysis/networkflows/DemoStyles.ts @@ -560,7 +560,7 @@ export class NetworkFlowEdgeStyle extends EdgeStyleBase { gradientsToRemove.push(child) } } - gradientsToRemove.forEach(gradient => defs.removeChild(gradient)) + gradientsToRemove.forEach((gradient) => defs.removeChild(gradient)) } /** @@ -617,7 +617,7 @@ export class NetworkFlowEdgeStyle extends EdgeStyleBase { const path = new GeneralPath() path.moveTo(edge.sourcePort!.location) path.lineTo(edge.sourcePort!.location.add(new Point(5, 0))) - edge.bends.forEach(bend => path.lineTo(bend.location)) + edge.bends.forEach((bend) => path.lineTo(bend.location)) path.lineTo(edge.targetPort!.location.subtract(new Point(5, 0))) path.lineTo(edge.targetPort!.location) return path @@ -882,7 +882,10 @@ export class MinCutLine extends BaseClass(IVisualCreator) implements IVisualCrea * The equals method detects if the cache has changed. */ class MclRenderDataCache { - constructor(private bounds: Rect, private visible: boolean) {} + constructor( + private bounds: Rect, + private visible: boolean + ) {} /** * Checks if the data stored in the given cache is equal to data in this cache. diff --git a/demos/analysis/networkflows/NetworkFlowsDemo.js b/demos/analysis/networkflows/NetworkFlowsDemo.js index daa7fcb0c..030849b88 100644 --- a/demos/analysis/networkflows/NetworkFlowsDemo.js +++ b/demos/analysis/networkflows/NetworkFlowsDemo.js @@ -285,7 +285,7 @@ function createEditorInputMode() { const deletedCompoundEdit = graphComponent.graph.beginEdit('Element deleted', 'Element deleted') // if an edge was removed, calculate the new node size of its endpoints if (nodesToChange.length > 0) { - nodesToChange.forEach(node => { + nodesToChange.forEach((node) => { if (graphComponent.graph.contains(node)) { calculateNodeSize(node) } @@ -300,7 +300,7 @@ function createEditorInputMode() { inputMode.addDeletingSelectionListener((_, evt) => { edgePopup.currentItem = null // collect all nodes that are endpoints of removed edges - evt.selection.forEach(item => { + evt.selection.forEach((item) => { if (item instanceof IEdge) { nodesToChange.push(item.sourceNode) nodesToChange.push(item.targetNode) @@ -365,8 +365,8 @@ function createEditorInputMode() { function calculateNodeSize(node) { const graph = graphComponent.graph - const incomingCapacity = graph.inEdgesAt(node).sum(inEdge => inEdge.tag.capacity) - const outgoingCapacity = graph.outEdgesAt(node).sum(outEdge => outEdge.tag.capacity) + const incomingCapacity = graph.inEdgesAt(node).sum((inEdge) => inEdge.tag.capacity) + const outgoingCapacity = graph.outEdgesAt(node).sum((outEdge) => outEdge.tag.capacity) const height = Math.max(incomingCapacity, outgoingCapacity) const newBounds = new Rect(node.layout.x, node.layout.y, node.layout.width, Math.max(height, 30)) @@ -411,7 +411,7 @@ function runFlowAlgorithm() { } // update the node tags - graphComponent.graph.nodes.forEach(node => { + graphComponent.graph.nodes.forEach((node) => { node.tag = { ...node.tag, cut: false, source: false, sink: false } }) @@ -458,7 +458,7 @@ function calculateMaxFlowMinCut(minCut) { return 0 } - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const labels = edge.labels if (labels.size > 1) { graph.remove(labels.get(1)) @@ -473,13 +473,13 @@ function calculateMaxFlowMinCut(minCut) { sources: sourceNodes, sinks: sinkNodes, // the capacity of an edge is stored in its tag - capacities: edge => edge.tag.capacity + capacities: (edge) => edge.tag.capacity }) const maxFlowMinCutResult = maxFlowMinCut.run(graph) - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const flow = (graph.inDegree(node) > 0 ? graph.inEdgesAt(node) : graph.outEdgesAt(node)).sum( - edge => maxFlowMinCutResult.flow.get(edge) || 0 + (edge) => maxFlowMinCutResult.flow.get(edge) || 0 ) node.tag = { flow, @@ -488,17 +488,17 @@ function calculateMaxFlowMinCut(minCut) { } }) - sourceNodes.forEach(sourceNode => (sourceNode.tag.source = true)) - sinkNodes.forEach(sinkNode => (sinkNode.tag.sink = true)) + sourceNodes.forEach((sourceNode) => (sourceNode.tag.source = true)) + sinkNodes.forEach((sinkNode) => (sinkNode.tag.sink = true)) // add the flow values as tags to edges maxFlowMinCutResult.flow.forEach(({ key, value }) => (key.tag.flow = value)) if (minCut) { // add tags for the nodes that belong to the cut - maxFlowMinCutResult.sourcePartition.forEach(node => (node.tag.cut = true)) + maxFlowMinCutResult.sourcePartition.forEach((node) => (node.tag.cut = true)) - maxFlowMinCutResult.sinkPartition.forEach(node => (node.tag.cut = false)) + maxFlowMinCutResult.sinkPartition.forEach((node) => (node.tag.cut = false)) } // show the result @@ -515,10 +515,10 @@ function calculateMinCostFlow() { let minCostFlowResult = null try { const minCostFlow = new MinimumCostFlow({ - maximumCapacities: edge => edge.tag.capacity, - costs: edge => (edge.tag && edge.tag.cost ? edge.tag.cost : 0), + maximumCapacities: (edge) => edge.tag.capacity, + costs: (edge) => (edge.tag && edge.tag.cost ? edge.tag.cost : 0), // the supply or demand of a node was calculated in calculateMaxFlow and set as node tag - supply: node => (node.tag.supply ? node.tag.supply * node.layout.height : 0) + supply: (node) => (node.tag.supply ? node.tag.supply * node.layout.height : 0) }) minCostFlowResult = minCostFlow.run(graph) @@ -526,7 +526,7 @@ function calculateMinCostFlow() { alert(err) } finally { // store the flow for each edge in its tag - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { edge.tag.flow = minCostFlowResult ? minCostFlowResult.flow.get(edge) || 0 : 0 if (edge.labels.size > 1) { @@ -547,9 +547,9 @@ function calculateMinCostFlow() { visualizeResult() let flow = 0 - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (graph.inDegree(node) > 0) { - flow = graph.inEdgesAt(node).sum(edge => edge.tag.flow) + flow = graph.inEdgesAt(node).sum((edge) => edge.tag.flow) } node.tag = { flow, @@ -558,8 +558,8 @@ function calculateMinCostFlow() { } }) - getSupplyNodes(graph).forEach(supplyNode => (supplyNode.tag.source = true)) - getDemandNodes(graph).forEach(demandNode => (demandNode.tag.sink = true)) + getSupplyNodes(graph).forEach((supplyNode) => (supplyNode.tag.source = true)) + getDemandNodes(graph).forEach((demandNode) => (demandNode.tag.sink = true)) } return minCostFlowResult ? minCostFlowResult.totalCost : 0 } @@ -571,7 +571,7 @@ function calculateMinCostFlow() { */ function getSupplyNodes(graph) { const supplyNodes = [] - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (node.tag.supply > 0) { supplyNodes.push(node) } @@ -586,7 +586,7 @@ function getSupplyNodes(graph) { */ function getDemandNodes(graph) { const demandNodes = [] - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (node.tag.supply < 0) { demandNodes.push(node) } @@ -617,7 +617,7 @@ async function runLayout(incremental, additionalIncrementalNodes) { layoutAlgorithm.backLoopRouting = true const layoutData = new HierarchicLayoutData({ - edgeThickness: edge => edge.tag.capacity + edgeThickness: (edge) => edge.tag.capacity }) if (incremental && algorithmComboBox.selectedIndex !== MAX_FLOW_MIN_CUT) { @@ -626,14 +626,14 @@ async function runLayout(incremental, additionalIncrementalNodes) { // mark all sources and sinks as well as passed additional nodes as incremental const hintsFactory = layoutAlgorithm.createIncrementalHintsFactory() const incrementalNodesMapper = new Mapper() - getSourceNodes().forEach(node => + getSourceNodes().forEach((node) => incrementalNodesMapper.set(node, hintsFactory.createLayerIncrementallyHint(node)) ) - getSinkNodes().forEach(node => + getSinkNodes().forEach((node) => incrementalNodesMapper.set(node, hintsFactory.createLayerIncrementallyHint(node)) ) if (additionalIncrementalNodes) { - additionalIncrementalNodes.forEach(node => + additionalIncrementalNodes.forEach((node) => incrementalNodesMapper.set(node, hintsFactory.createLayerIncrementallyHint(node)) ) } @@ -644,9 +644,9 @@ async function runLayout(incremental, additionalIncrementalNodes) { // sources will be in the first layer, sinks in the last layer const layerConstraints = layoutData.layerConstraints - getSourceNodes().forEach(node => layerConstraints.placeAtTop(node)) + getSourceNodes().forEach((node) => layerConstraints.placeAtTop(node)) - getSinkNodes().forEach(node => layerConstraints.placeAtBottom(node)) + getSinkNodes().forEach((node) => layerConstraints.placeAtBottom(node)) if (algorithmComboBox.selectedIndex === MAX_FLOW_MIN_CUT) { layoutData.partitionGridData = new PartitionGridData({ @@ -655,7 +655,7 @@ async function runLayout(incremental, additionalIncrementalNodes) { }) } - layoutData.edgeLabelPreferredPlacement.delegate = key => { + layoutData.edgeLabelPreferredPlacement.delegate = (key) => { const preferredPlacementDescriptor = new PreferredPlacementDescriptor() if (key.tag === 'cost') { preferredPlacementDescriptor.sideOfEdge = LabelPlacements.LEFT_OF_EDGE @@ -669,7 +669,7 @@ async function runLayout(incremental, additionalIncrementalNodes) { } await graphComponent.morphLayout(layoutAlgorithm, '1s', layoutData) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { if (lastFlowMap.get(edge) !== edge.tag.flow) { graphComponent.highlightIndicatorManager.addHighlight(edge) lastFlowMap.set(edge, edge.tag.flow) @@ -695,7 +695,7 @@ async function runLayout(incremental, additionalIncrementalNodes) { function getSourceNodes() { const sourceNodes = [] const graph = graphComponent.graph - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (graph.inDegree(node) === 0 && graph.outDegree(node) !== 0) { sourceNodes.push(node) } @@ -704,7 +704,7 @@ function getSourceNodes() { if (sourceNodes.length === 0) { sourceNodes.push(graph.nodes.first()) } - sourceNodes.forEach(node => (node.tag.source = true)) + sourceNodes.forEach((node) => (node.tag.source = true)) return sourceNodes } @@ -715,19 +715,19 @@ function getSourceNodes() { function getSinkNodes() { const sinkNodes = [] const graph = graphComponent.graph - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (graph.outDegree(node) === 0 && graph.inDegree(node) !== 0) { sinkNodes.push(node) } }) // Special case: No node with out-degree 0 was found, take the first node of the graph that is not already marked as source if (sinkNodes.length === 0) { - const randomSink = graph.nodes.find(node => !node.tag.source) + const randomSink = graph.nodes.find((node) => !node.tag.source) if (randomSink) { sinkNodes.push(randomSink) } } - sinkNodes.forEach(node => (node.tag.sink = true)) + sinkNodes.forEach((node) => (node.tag.sink = true)) return sinkNodes } @@ -746,7 +746,7 @@ function updateMinCutLine() { minCutLine.visible = true let minX = Number.NEGATIVE_INFINITY let maxX = Number.POSITIVE_INFINITY - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (node.tag.cut) { minX = Math.max(minX, node.layout.maxX) } else { @@ -754,7 +754,7 @@ function updateMinCutLine() { } }) - if (isFinite(minX) && isFinite(maxX)) { + if (Number.isFinite(minX) && Number.isFinite(maxX)) { minCutLine.bounds = new Rect( (minX + maxX) * 0.5 - 5, graphBounds.y - 30, @@ -791,7 +791,7 @@ function visualizeResult() { const extrema = calculateExtrema(graph, false) const diff = extrema.max - extrema.min - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { let colorIndex = 0 if (edge.tag.capacity !== 0) { if (diff === 0) { @@ -826,7 +826,7 @@ function calculateExtrema(graph, useCapacity) { let min = Number.MAX_VALUE let max = -Number.MAX_VALUE - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { let value = 0 if (edge.tag && edge.tag.capacity !== 0) { value = useCapacity ? edge.tag.capacity : (edge.tag.flow * 100) / edge.tag.capacity @@ -904,7 +904,7 @@ async function onAlgorithmChanged() { minCutLine.visible = algorithmComboBox.selectedIndex === MAX_FLOW_MIN_CUT // make sure that there is flow in case the algorithm changed to "Minimum Cost Problem" - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (graph.inDegree(node) === 0) { node.tag.supply = 0.5 } else if (graph.outDegree(node) === 0) { @@ -1118,9 +1118,9 @@ function createSampleGraph() { } }) - graph.edges.forEach(edge => updateEdgeThickness(edge)) + graph.edges.forEach((edge) => updateEdgeThickness(edge)) - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { let supply = 0 if (graph.inDegree(node) === 0) { supply = 0.5 diff --git a/demos/analysis/networkflows/NetworkFlowsDemo.ts b/demos/analysis/networkflows/NetworkFlowsDemo.ts index 6dc010873..7c3e27e6b 100644 --- a/demos/analysis/networkflows/NetworkFlowsDemo.ts +++ b/demos/analysis/networkflows/NetworkFlowsDemo.ts @@ -278,7 +278,7 @@ function createEditorInputMode(): void { const deletedCompoundEdit = graphComponent.graph.beginEdit('Element deleted', 'Element deleted') // if an edge was removed, calculate the new node size of its endpoints if (nodesToChange.length > 0) { - nodesToChange.forEach(node => { + nodesToChange.forEach((node) => { if (graphComponent.graph.contains(node)) { calculateNodeSize(node) } @@ -293,7 +293,7 @@ function createEditorInputMode(): void { inputMode.addDeletingSelectionListener((_, evt) => { edgePopup.currentItem = null // collect all nodes that are endpoints of removed edges - evt.selection.forEach(item => { + evt.selection.forEach((item) => { if (item instanceof IEdge) { nodesToChange.push(item.sourceNode!) nodesToChange.push(item.targetNode!) @@ -358,8 +358,8 @@ function createEditorInputMode(): void { function calculateNodeSize(node: INode): void { const graph = graphComponent.graph - const incomingCapacity = graph.inEdgesAt(node).sum(inEdge => inEdge.tag.capacity) - const outgoingCapacity = graph.outEdgesAt(node).sum(outEdge => outEdge.tag.capacity) + const incomingCapacity = graph.inEdgesAt(node).sum((inEdge) => inEdge.tag.capacity) + const outgoingCapacity = graph.outEdgesAt(node).sum((outEdge) => outEdge.tag.capacity) const height = Math.max(incomingCapacity, outgoingCapacity) const newBounds = new Rect(node.layout.x, node.layout.y, node.layout.width, Math.max(height, 30)) @@ -404,7 +404,7 @@ function runFlowAlgorithm(): void { } // update the node tags - graphComponent.graph.nodes.forEach(node => { + graphComponent.graph.nodes.forEach((node) => { node.tag = { ...node.tag, cut: false, source: false, sink: false } }) @@ -451,7 +451,7 @@ function calculateMaxFlowMinCut(minCut: boolean): number { return 0 } - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const labels = edge.labels if (labels.size > 1) { graph.remove(labels.get(1)) @@ -466,11 +466,11 @@ function calculateMaxFlowMinCut(minCut: boolean): number { sources: sourceNodes, sinks: sinkNodes, // the capacity of an edge is stored in its tag - capacities: edge => edge.tag.capacity + capacities: (edge) => edge.tag.capacity }) const maxFlowMinCutResult = maxFlowMinCut.run(graph) - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const flow = (graph.inDegree(node) > 0 ? graph.inEdgesAt(node) : graph.outEdgesAt(node)).sum( (edge: IEdge): number => maxFlowMinCutResult.flow.get(edge) || 0 ) @@ -481,17 +481,17 @@ function calculateMaxFlowMinCut(minCut: boolean): number { } }) - sourceNodes.forEach(sourceNode => (sourceNode.tag.source = true)) - sinkNodes.forEach(sinkNode => (sinkNode.tag.sink = true)) + sourceNodes.forEach((sourceNode) => (sourceNode.tag.source = true)) + sinkNodes.forEach((sinkNode) => (sinkNode.tag.sink = true)) // add the flow values as tags to edges maxFlowMinCutResult.flow.forEach(({ key, value }) => (key.tag.flow = value)) if (minCut) { // add tags for the nodes that belong to the cut - maxFlowMinCutResult.sourcePartition.forEach(node => (node.tag.cut = true)) + maxFlowMinCutResult.sourcePartition.forEach((node) => (node.tag.cut = true)) - maxFlowMinCutResult.sinkPartition.forEach(node => (node.tag.cut = false)) + maxFlowMinCutResult.sinkPartition.forEach((node) => (node.tag.cut = false)) } // show the result @@ -508,10 +508,10 @@ function calculateMinCostFlow(): number { let minCostFlowResult: MinimumCostFlowResult | null = null try { const minCostFlow = new MinimumCostFlow({ - maximumCapacities: edge => edge.tag.capacity, - costs: edge => (edge.tag && edge.tag.cost ? edge.tag.cost : 0), + maximumCapacities: (edge) => edge.tag.capacity, + costs: (edge) => (edge.tag && edge.tag.cost ? edge.tag.cost : 0), // the supply or demand of a node was calculated in calculateMaxFlow and set as node tag - supply: node => (node.tag.supply ? node.tag.supply * node.layout.height : 0) + supply: (node) => (node.tag.supply ? node.tag.supply * node.layout.height : 0) }) minCostFlowResult = minCostFlow.run(graph) @@ -519,7 +519,7 @@ function calculateMinCostFlow(): number { alert(err) } finally { // store the flow for each edge in its tag - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { edge.tag.flow = minCostFlowResult ? minCostFlowResult.flow.get(edge) || 0 : 0 if (edge.labels.size > 1) { @@ -540,9 +540,9 @@ function calculateMinCostFlow(): number { visualizeResult() let flow = 0 - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (graph.inDegree(node) > 0) { - flow = graph.inEdgesAt(node).sum(edge => edge.tag.flow) + flow = graph.inEdgesAt(node).sum((edge) => edge.tag.flow) } node.tag = { flow, @@ -551,8 +551,8 @@ function calculateMinCostFlow(): number { } }) - getSupplyNodes(graph).forEach(supplyNode => (supplyNode.tag.source = true)) - getDemandNodes(graph).forEach(demandNode => (demandNode.tag.sink = true)) + getSupplyNodes(graph).forEach((supplyNode) => (supplyNode.tag.source = true)) + getDemandNodes(graph).forEach((demandNode) => (demandNode.tag.sink = true)) } return minCostFlowResult ? minCostFlowResult.totalCost : 0 } @@ -564,7 +564,7 @@ function calculateMinCostFlow(): number { */ function getSupplyNodes(graph: IGraph): INode[] { const supplyNodes: INode[] = [] - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (node.tag.supply > 0) { supplyNodes.push(node) } @@ -579,7 +579,7 @@ function getSupplyNodes(graph: IGraph): INode[] { */ function getDemandNodes(graph: IGraph): INode[] { const demandNodes: INode[] = [] - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (node.tag.supply < 0) { demandNodes.push(node) } @@ -611,7 +611,7 @@ async function runLayout( layoutAlgorithm.backLoopRouting = true const layoutData = new HierarchicLayoutData({ - edgeThickness: edge => edge.tag.capacity + edgeThickness: (edge) => edge.tag.capacity }) if (incremental && algorithmComboBox.selectedIndex !== MAX_FLOW_MIN_CUT) { @@ -620,14 +620,14 @@ async function runLayout( // mark all sources and sinks as well as passed additional nodes as incremental const hintsFactory = layoutAlgorithm.createIncrementalHintsFactory()! const incrementalNodesMapper = new Mapper() - getSourceNodes().forEach(node => + getSourceNodes().forEach((node) => incrementalNodesMapper.set(node, hintsFactory.createLayerIncrementallyHint(node)) ) - getSinkNodes().forEach(node => + getSinkNodes().forEach((node) => incrementalNodesMapper.set(node, hintsFactory.createLayerIncrementallyHint(node)) ) if (additionalIncrementalNodes) { - additionalIncrementalNodes.forEach(node => + additionalIncrementalNodes.forEach((node) => incrementalNodesMapper.set(node, hintsFactory.createLayerIncrementallyHint(node)) ) } @@ -638,9 +638,9 @@ async function runLayout( // sources will be in the first layer, sinks in the last layer const layerConstraints = layoutData.layerConstraints - getSourceNodes().forEach(node => layerConstraints.placeAtTop(node)) + getSourceNodes().forEach((node) => layerConstraints.placeAtTop(node)) - getSinkNodes().forEach(node => layerConstraints.placeAtBottom(node)) + getSinkNodes().forEach((node) => layerConstraints.placeAtBottom(node)) if (algorithmComboBox.selectedIndex === MAX_FLOW_MIN_CUT) { layoutData.partitionGridData = new PartitionGridData({ @@ -650,7 +650,7 @@ async function runLayout( }) } - layoutData.edgeLabelPreferredPlacement.delegate = key => { + layoutData.edgeLabelPreferredPlacement.delegate = (key) => { const preferredPlacementDescriptor = new PreferredPlacementDescriptor() if (key.tag === 'cost') { preferredPlacementDescriptor.sideOfEdge = LabelPlacements.LEFT_OF_EDGE @@ -690,7 +690,7 @@ async function runLayout( function getSourceNodes(): INode[] { const sourceNodes: INode[] = [] const graph = graphComponent.graph - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (graph.inDegree(node) === 0 && graph.outDegree(node) !== 0) { sourceNodes.push(node) } @@ -699,7 +699,7 @@ function getSourceNodes(): INode[] { if (sourceNodes.length === 0) { sourceNodes.push(graph.nodes.first()) } - sourceNodes.forEach(node => (node.tag.source = true)) + sourceNodes.forEach((node) => (node.tag.source = true)) return sourceNodes } @@ -710,19 +710,19 @@ function getSourceNodes(): INode[] { function getSinkNodes(): INode[] { const sinkNodes: INode[] = [] const graph = graphComponent.graph - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (graph.outDegree(node) === 0 && graph.inDegree(node) !== 0) { sinkNodes.push(node) } }) // Special case: No node with out-degree 0 was found, take the first node of the graph that is not already marked as source if (sinkNodes.length === 0) { - const randomSink = graph.nodes.find(node => !node.tag.source) + const randomSink = graph.nodes.find((node) => !node.tag.source) if (randomSink) { sinkNodes.push(randomSink) } } - sinkNodes.forEach(node => (node.tag.sink = true)) + sinkNodes.forEach((node) => (node.tag.sink = true)) return sinkNodes } @@ -741,7 +741,7 @@ function updateMinCutLine(): void { minCutLine.visible = true let minX: number = Number.NEGATIVE_INFINITY let maxX: number = Number.POSITIVE_INFINITY - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (node.tag.cut) { minX = Math.max(minX, node.layout.maxX) } else { @@ -749,7 +749,7 @@ function updateMinCutLine(): void { } }) - if (isFinite(minX) && isFinite(maxX)) { + if (Number.isFinite(minX) && Number.isFinite(maxX)) { minCutLine.bounds = new Rect( (minX + maxX) * 0.5 - 5, graphBounds.y - 30, @@ -786,7 +786,7 @@ function visualizeResult(): void { const extrema = calculateExtrema(graph, false) const diff = extrema.max - extrema.min - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { let colorIndex = 0 if (edge.tag.capacity !== 0) { if (diff === 0) { @@ -820,7 +820,7 @@ function calculateExtrema(graph: IGraph, useCapacity: boolean): { min: number; m let min: number = Number.MAX_VALUE let max: number = -Number.MAX_VALUE - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { let value = 0 if (edge.tag && edge.tag.capacity !== 0) { value = useCapacity ? edge.tag.capacity : (edge.tag.flow * 100) / edge.tag.capacity @@ -902,7 +902,7 @@ async function onAlgorithmChanged() { minCutLine.visible = algorithmComboBox.selectedIndex === MAX_FLOW_MIN_CUT // make sure that there is flow in case the algorithm changed to "Minimum Cost Problem" - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (graph.inDegree(node) === 0) { node.tag.supply = 0.5 } else if (graph.outDegree(node) === 0) { @@ -1116,9 +1116,9 @@ function createSampleGraph(): void { } }) - graph.edges.forEach(edge => updateEdgeThickness(edge)) + graph.edges.forEach((edge) => updateEdgeThickness(edge)) - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { let supply = 0 if (graph.inDegree(node) === 0) { supply = 0.5 diff --git a/demos/analysis/networkflows/index.html b/demos/analysis/networkflows/index.html index 29093eff7..eb46c8281 100644 --- a/demos/analysis/networkflows/index.html +++ b/demos/analysis/networkflows/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/analysis/transitivity/TransitivityDemo.js b/demos/analysis/transitivity/TransitivityDemo.js index 8d4f8f8e4..560786ba7 100644 --- a/demos/analysis/transitivity/TransitivityDemo.js +++ b/demos/analysis/transitivity/TransitivityDemo.js @@ -493,7 +493,7 @@ async function loadGraph() { const graph = builder.buildGraph() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const label = node.labels.first() const nodeLayout = new Rect( node.layout.x, @@ -525,7 +525,7 @@ async function loadGraph() { */ function getInitialPackage(packageName) { let initialPackageNode = null - filteredGraph.wrappedGraph.nodes.forEach(node => { + filteredGraph.wrappedGraph.nodes.forEach((node) => { if (packageName === node.labels.get(0).text) { initialPackageNode = node } @@ -573,7 +573,7 @@ function applyAlgorithm() { const transitivityClosureResult = transitivityClosure.run(graph) const newEdges = transitivityClosureResult.edgesToAdd - newEdges.forEach(edge => { + newEdges.forEach((edge) => { const newEdge = graph.createEdge(edge.source, edge.target) graph.setStyle(newEdge, addedEdgeStyle) @@ -595,7 +595,7 @@ function applyAlgorithm() { } const transitiveEdges = transitivityReductionResult.edgesToRemove - transitiveEdges.forEach(edge => { + transitiveEdges.forEach((edge) => { if (showTransitiveEdges) { graph.setStyle(edge, removedEdgeStyle) incrementalEdges.push(edge) @@ -667,7 +667,7 @@ async function filterGraph(clickedNode) { startNode = clickedNode // take all in-edges and mark the other endpoint as a neighbor of clickedNode - fullGraph.inEdgesAt(clickedNode).forEach(edge => { + fullGraph.inEdgesAt(clickedNode).forEach((edge) => { const oppositeNode = edge.opposite(clickedNode) // we have to check if the node is already taken into consideration in the calculation of dependents if (!filteredNodes.has(oppositeNode)) { @@ -685,7 +685,7 @@ async function filterGraph(clickedNode) { // check if new nodes are inserted in the graph if (existingNodes) { - fullGraph.nodes.forEach(node => { + fullGraph.nodes.forEach((node) => { if (!existingNodes.has(node)) { incrementalNodes.push(node) } @@ -712,7 +712,7 @@ function collectConnectedNodes(initialNode, graph, out) { while (stack.length > 0) { const node = stack.pop() const edges = out ? graph.outEdgesAt(node) : graph.inEdgesAt(node) - edges.forEach(edge => { + edges.forEach((edge) => { filteredEdges.add(edge) const oppositeNode = edge.opposite(node) stack.push(oppositeNode) @@ -735,7 +735,7 @@ function collectConnectedNodes(initialNode, graph, out) { */ function resetGraph() { if (addedEdges.length !== 0) { - addedEdges.forEach(edge => filteredGraph.remove(edge)) + addedEdges.forEach((edge) => filteredGraph.remove(edge)) addedEdges = [] } @@ -743,9 +743,9 @@ function resetGraph() { removedEdgesSet = null filteredGraph.edgePredicateChanged() - filteredGraph.edges.forEach(edge => filteredGraph.setStyle(edge, normalEdgeStyle)) + filteredGraph.edges.forEach((edge) => filteredGraph.setStyle(edge, normalEdgeStyle)) - filteredGraph.nodes.forEach(node => (node.tag.highlight = false)) + filteredGraph.nodes.forEach((node) => (node.tag.highlight = false)) } /** @@ -781,10 +781,10 @@ async function applyLayout(incremental) { if (incremental) { layout.layoutMode = LayoutMode.INCREMENTAL - layoutData.incrementalHints.incrementalLayeringNodes = incrementalNodes.filter(node => + layoutData.incrementalHints.incrementalLayeringNodes = incrementalNodes.filter((node) => filteredGraph.contains(node) ) - layoutData.incrementalHints.incrementalSequencingItems = incrementalEdges.filter(edge => + layoutData.incrementalHints.incrementalSequencingItems = incrementalEdges.filter((edge) => filteredGraph.contains(edge) ) @@ -828,7 +828,7 @@ function prepareSmoothLayoutAnimation() { layout.removeBends = true const layoutData = new PlaceNodesAtBarycenterStageData({ - affectedNodes: node => incrementalNodes.includes(node) && filteredGraph.contains(node) + affectedNodes: (node) => incrementalNodes.includes(node) && filteredGraph.contains(node) }) graph.applyLayout(layout, layoutData) diff --git a/demos/analysis/transitivity/TransitivityDemo.ts b/demos/analysis/transitivity/TransitivityDemo.ts index 3926641fa..35f35934d 100644 --- a/demos/analysis/transitivity/TransitivityDemo.ts +++ b/demos/analysis/transitivity/TransitivityDemo.ts @@ -472,7 +472,7 @@ async function loadGraph(): Promise { const graph = builder.buildGraph() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const label = node.labels.first() const nodeLayout = new Rect( node.layout.x, @@ -503,7 +503,7 @@ async function loadGraph(): Promise { */ function getInitialPackage(packageName: string): INode | null { let initialPackageNode: INode | null = null - filteredGraph.wrappedGraph!.nodes.forEach(node => { + filteredGraph.wrappedGraph!.nodes.forEach((node) => { if (packageName === node.labels.get(0).text) { initialPackageNode = node } @@ -552,7 +552,7 @@ function applyAlgorithm(): void { const transitivityClosureResult = transitivityClosure.run(graph) const newEdges = transitivityClosureResult.edgesToAdd - newEdges.forEach(edge => { + newEdges.forEach((edge) => { const newEdge = graph.createEdge(edge.source, edge.target) graph.setStyle(newEdge, addedEdgeStyle) @@ -574,7 +574,7 @@ function applyAlgorithm(): void { } const transitiveEdges = transitivityReductionResult.edgesToRemove - transitiveEdges.forEach(edge => { + transitiveEdges.forEach((edge) => { if (showTransitiveEdges) { graph.setStyle(edge, removedEdgeStyle) incrementalEdges.push(edge) @@ -641,7 +641,7 @@ async function filterGraph(clickedNode: INode): Promise { startNode = clickedNode // take all in-edges and mark the other endpoint as a neighbor of clickedNode - fullGraph.inEdgesAt(clickedNode).forEach(edge => { + fullGraph.inEdgesAt(clickedNode).forEach((edge) => { const oppositeNode = edge.opposite(clickedNode) as INode // we have to check if the node is already taken into consideration in the calculation of dependents if (!filteredNodes!.has(oppositeNode)) { @@ -659,7 +659,7 @@ async function filterGraph(clickedNode: INode): Promise { // check if new nodes are inserted in the graph if (existingNodes) { - fullGraph.nodes.forEach(node => { + fullGraph.nodes.forEach((node) => { if (!existingNodes.has(node)) { incrementalNodes.push(node) } @@ -686,7 +686,7 @@ function collectConnectedNodes(initialNode: INode, graph: IGraph, out: boolean): while (stack.length > 0) { const node = stack.pop()! const edges = out ? graph.outEdgesAt(node) : graph.inEdgesAt(node) - edges.forEach(edge => { + edges.forEach((edge) => { filteredEdges!.add(edge) const oppositeNode = edge.opposite(node)! as INode stack.push(oppositeNode) @@ -709,7 +709,7 @@ function collectConnectedNodes(initialNode: INode, graph: IGraph, out: boolean): */ function resetGraph(): void { if (addedEdges.length !== 0) { - addedEdges.forEach(edge => filteredGraph.remove(edge)) + addedEdges.forEach((edge) => filteredGraph.remove(edge)) addedEdges = [] } @@ -717,9 +717,9 @@ function resetGraph(): void { removedEdgesSet = null filteredGraph.edgePredicateChanged() - filteredGraph.edges.forEach(edge => filteredGraph.setStyle(edge, normalEdgeStyle)) + filteredGraph.edges.forEach((edge) => filteredGraph.setStyle(edge, normalEdgeStyle)) - filteredGraph.nodes.forEach(node => (node.tag.highlight = false)) + filteredGraph.nodes.forEach((node) => (node.tag.highlight = false)) } /** @@ -754,10 +754,10 @@ async function applyLayout(incremental: boolean): Promise { if (incremental) { layout.layoutMode = LayoutMode.INCREMENTAL - layoutData.incrementalHints.incrementalLayeringNodes = incrementalNodes.filter(node => + layoutData.incrementalHints.incrementalLayeringNodes = incrementalNodes.filter((node) => filteredGraph.contains(node) ) - layoutData.incrementalHints.incrementalSequencingItems = incrementalEdges.filter(edge => + layoutData.incrementalHints.incrementalSequencingItems = incrementalEdges.filter((edge) => filteredGraph.contains(edge) ) @@ -801,7 +801,7 @@ function prepareSmoothLayoutAnimation(): void { layout.removeBends = true const layoutData = new PlaceNodesAtBarycenterStageData({ - affectedNodes: node => incrementalNodes.includes(node) && filteredGraph.contains(node) + affectedNodes: (node) => incrementalNodes.includes(node) && filteredGraph.contains(node) }) graph.applyLayout(layout, layoutData) diff --git a/demos/analysis/transitivity/index.html b/demos/analysis/transitivity/index.html index eaf177254..61bd42105 100644 --- a/demos/analysis/transitivity/index.html +++ b/demos/analysis/transitivity/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/accessibility/AccessibilityDemo.js b/demos/application-features/accessibility/AccessibilityDemo.js index 8d20981e1..35a9cd077 100644 --- a/demos/application-features/accessibility/AccessibilityDemo.js +++ b/demos/application-features/accessibility/AccessibilityDemo.js @@ -111,26 +111,26 @@ function buildGraph(graph, graphData) { graphBuilder .createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) - .edgeCreator.createLabelBinding(item => item.label) + .edgeCreator.createLabelBinding((item) => item.label) graphBuilder.buildGraph() } diff --git a/demos/application-features/accessibility/AccessibilityDemo.ts b/demos/application-features/accessibility/AccessibilityDemo.ts index b62b7ee75..10181d3b1 100644 --- a/demos/application-features/accessibility/AccessibilityDemo.ts +++ b/demos/application-features/accessibility/AccessibilityDemo.ts @@ -108,26 +108,26 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { graphBuilder .createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) - .edgeCreator.createLabelBinding(item => item.label) + .edgeCreator.createLabelBinding((item) => item.label) graphBuilder.buildGraph() } diff --git a/demos/application-features/accessibility/index.html b/demos/application-features/accessibility/index.html index f5f14108d..c28d9bff5 100644 --- a/demos/application-features/accessibility/index.html +++ b/demos/application-features/accessibility/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/background-image/BackgroundImageDemo.js b/demos/application-features/background-image/BackgroundImageDemo.js index 2eaac0c60..7dc78c626 100644 --- a/demos/application-features/background-image/BackgroundImageDemo.js +++ b/demos/application-features/background-image/BackgroundImageDemo.js @@ -110,22 +110,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/background-image/BackgroundImageDemo.ts b/demos/application-features/background-image/BackgroundImageDemo.ts index d2cb8f8d9..dc6ebd998 100644 --- a/demos/application-features/background-image/BackgroundImageDemo.ts +++ b/demos/application-features/background-image/BackgroundImageDemo.ts @@ -106,22 +106,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/background-image/index.html b/demos/application-features/background-image/index.html index 5105abe56..7e17de66e 100644 --- a/demos/application-features/background-image/index.html +++ b/demos/application-features/background-image/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/building-graph-from-data/BuildingGraphFromDataDemo.js b/demos/application-features/building-graph-from-data/BuildingGraphFromDataDemo.js index 72e1bf047..4d3cfcbee 100644 --- a/demos/application-features/building-graph-from-data/BuildingGraphFromDataDemo.js +++ b/demos/application-features/building-graph-from-data/BuildingGraphFromDataDemo.js @@ -103,7 +103,7 @@ function buildGraph(graph, graphData) { const nodes = {} // Iterate the group data and create the according group nodes. - graphData.groupsSource.forEach(groupData => { + graphData.groupsSource.forEach((groupData) => { groups[groupData.id] = graph.createGroupNode({ labels: groupData.label != null ? [groupData.label] : [], layout: groupData.layout, @@ -112,7 +112,7 @@ function buildGraph(graph, graphData) { }) // Iterate the node data and create the according nodes. - graphData.nodesSource.forEach(nodeData => { + graphData.nodesSource.forEach((nodeData) => { const node = graph.createNode({ labels: nodeData.label != null ? [nodeData.label] : [], layout: nodeData.layout, @@ -128,14 +128,14 @@ function buildGraph(graph, graphData) { }) // Set the parent groups after all nodes/groups are created. - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (node.tag.group) { graph.setParent(node, groups[node.tag.group]) } }) // Iterate the edge data and create the according edges. - graphData.edgesSource.forEach(edgeData => { + graphData.edgesSource.forEach((edgeData) => { // Note that nodes and groups need to have disjoint sets of ids, otherwise it is impossible to determine // which node is the correct source/target. graph.createEdge({ @@ -147,7 +147,7 @@ function buildGraph(graph, graphData) { }) // If given, apply the edge layout information - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const edgeData = edge.tag if (edgeData.sourcePort) { graph.setPortLocation(edge.sourcePort, Point.from(edgeData.sourcePort)) @@ -156,7 +156,7 @@ function buildGraph(graph, graphData) { graph.setPortLocation(edge.targetPort, Point.from(edgeData.targetPort)) } if (edgeData.bends) { - edgeData.bends.forEach(bendLocation => { + edgeData.bends.forEach((bendLocation) => { graph.addBend(edge, bendLocation) }) } diff --git a/demos/application-features/building-graph-from-data/index.html b/demos/application-features/building-graph-from-data/index.html index e54055b30..6b968ab9f 100644 --- a/demos/application-features/building-graph-from-data/index.html +++ b/demos/application-features/building-graph-from-data/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/building-swimlanes-from-data/BuildingSwimlanesFromDataDemo.js b/demos/application-features/building-swimlanes-from-data/BuildingSwimlanesFromDataDemo.js index 1e7e6cf35..dfbdd62a1 100644 --- a/demos/application-features/building-swimlanes-from-data/BuildingSwimlanesFromDataDemo.js +++ b/demos/application-features/building-swimlanes-from-data/BuildingSwimlanesFromDataDemo.js @@ -147,7 +147,7 @@ function configureTableEditing(graphComponent) { graphComponent.clipboard = clipboard // prevent selection of the table node - graphEditorInputMode.selectablePredicate = item => { + graphEditorInputMode.selectablePredicate = (item) => { return !(INode.isInstance(item) && item.lookup(ITable.$class)) } } @@ -233,7 +233,7 @@ function buildGraph(graph, graphData) { const tableGroupNode = graph.createGroupNode(null, table.layout.toRect(), tableStyle) // Iterate the node data and create the according nodes. - graphData.nodesSource.forEach(nodeData => { + graphData.nodesSource.forEach((nodeData) => { const size = nodeData.size || [50, 50] const node = graph.createNode({ labels: nodeData.label != null ? [nodeData.label] : [], @@ -264,7 +264,7 @@ function buildGraph(graph, graphData) { }) // Iterate the edge data and create the according edges. - graphData.edgesSource.forEach(edgeData => { + graphData.edgesSource.forEach((edgeData) => { // Note that nodes and groups need to have disjoint sets of ids, otherwise it is impossible to determine // which node is the correct source/target. graph.createEdge({ @@ -289,7 +289,7 @@ function writeToJSON(graph) { } // find the table, we assume there is only one - const tableNode = graph.nodes.find(node => !!node.lookup(ITable.$class)) + const tableNode = graph.nodes.find((node) => !!node.lookup(ITable.$class)) const table = tableNode ? tableNode.lookup(ITable.$class) : null // serialize the nodes with their swimlane information @@ -321,7 +321,7 @@ function writeToJSON(graph) { const columnId = `lane${table.findColumn(tableNode, node.layout.center).index}` jsonNode.lane = columnId // store new lanes in the json - if (!jsonOutput.lanesSource.find(lane => lane.id === columnId)) { + if (!jsonOutput.lanesSource.find((lane) => lane.id === columnId)) { const jsonLane = { id: columnId } if (column.labels.size > 0) { jsonNode.label = column.labels.first().text @@ -333,7 +333,7 @@ function writeToJSON(graph) { }) // serialize the edges - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const sourceId = node2id.get(edge.sourceNode) const targetId = node2id.get(edge.targetNode) jsonOutput.edgesSource.push({ diff --git a/demos/application-features/building-swimlanes-from-data/index.html b/demos/application-features/building-swimlanes-from-data/index.html index fa3977f64..58ab3811c 100644 --- a/demos/application-features/building-swimlanes-from-data/index.html +++ b/demos/application-features/building-swimlanes-from-data/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/complex-highlight-decorator/ComplexHighlightDecoratorDemo.js b/demos/application-features/complex-highlight-decorator/ComplexHighlightDecoratorDemo.js index 0e88a9d99..4571acf08 100644 --- a/demos/application-features/complex-highlight-decorator/ComplexHighlightDecoratorDemo.js +++ b/demos/application-features/complex-highlight-decorator/ComplexHighlightDecoratorDemo.js @@ -96,23 +96,23 @@ function buildGraph(graph, graphData) { graphBuilder .createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) - .nodeCreator.styleBindings.addBinding('shape', item => item.tag) + .nodeCreator.styleBindings.addBinding('shape', (item) => item.tag) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/complex-highlight-decorator/ComplexHighlightDecoratorDemo.ts b/demos/application-features/complex-highlight-decorator/ComplexHighlightDecoratorDemo.ts index b1e37c77f..f197ed965 100644 --- a/demos/application-features/complex-highlight-decorator/ComplexHighlightDecoratorDemo.ts +++ b/demos/application-features/complex-highlight-decorator/ComplexHighlightDecoratorDemo.ts @@ -94,23 +94,23 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { graphBuilder .createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) - .nodeCreator.styleBindings.addBinding('shape', item => item.tag) + .nodeCreator.styleBindings.addBinding('shape', (item) => item.tag) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/complex-highlight-decorator/index.html b/demos/application-features/complex-highlight-decorator/index.html index 502844c29..b1dc997d2 100644 --- a/demos/application-features/complex-highlight-decorator/index.html +++ b/demos/application-features/complex-highlight-decorator/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/custom-graphml/CustomGraphMLDemo.js b/demos/application-features/custom-graphml/CustomGraphMLDemo.js index a57e773ff..3f35442b9 100644 --- a/demos/application-features/custom-graphml/CustomGraphMLDemo.js +++ b/demos/application-features/custom-graphml/CustomGraphMLDemo.js @@ -117,22 +117,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -212,7 +212,7 @@ function createGraphMLIOHandler() { graphMLIOHandler.addInputMapper( INode.$class, YObject.$class, - element => GraphMLIOHandler.matchesName(element, DATE_TIME_MAPPER_KEY), + (element) => GraphMLIOHandler.matchesName(element, DATE_TIME_MAPPER_KEY), dateMapper, (sender, e) => { // The actual value is a text node that can be retrieved from the event diff --git a/demos/application-features/custom-graphml/CustomGraphMLDemo.ts b/demos/application-features/custom-graphml/CustomGraphMLDemo.ts index 803d77d5b..ca76db028 100644 --- a/demos/application-features/custom-graphml/CustomGraphMLDemo.ts +++ b/demos/application-features/custom-graphml/CustomGraphMLDemo.ts @@ -115,22 +115,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/custom-graphml/index.html b/demos/application-features/custom-graphml/index.html index effad492e..1322680fc 100644 --- a/demos/application-features/custom-graphml/index.html +++ b/demos/application-features/custom-graphml/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/drag-and-drop/SimpleDragAndDropDemo.js b/demos/application-features/drag-and-drop/SimpleDragAndDropDemo.js index b384117f5..924f7fa46 100644 --- a/demos/application-features/drag-and-drop/SimpleDragAndDropDemo.js +++ b/demos/application-features/drag-and-drop/SimpleDragAndDropDemo.js @@ -109,22 +109,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -139,7 +139,7 @@ function configureDragAndDrop() { // By default the mode available in GraphEditorInputMode is disabled, so first enable it. nodeDropInputMode.enabled = true // Certain nodes should be created as group nodes. In this case, we distinguish them by their style. - nodeDropInputMode.isGroupNodePredicate = draggedNode => + nodeDropInputMode.isGroupNodePredicate = (draggedNode) => draggedNode.style instanceof GroupNodeStyle // When dragging the node within the GraphComponent, we want to show a preview of that node. nodeDropInputMode.showPreview = true @@ -162,7 +162,7 @@ function initializeDragAndDropPanel() { const nodeStyles = [defaultNodeStyle, otherNodeStyle, defaultGroupNodeStyle] // add a visual for each node style to the palette - nodeStyles.forEach(style => { + nodeStyles.forEach((style) => { addNodeVisual(style, panel) }) } @@ -216,7 +216,7 @@ function addNodeVisual(style, panel) { img.addEventListener( 'mousedown', - event => { + (event) => { startDrag() event.preventDefault() }, @@ -224,7 +224,7 @@ function addNodeVisual(style, panel) { ) img.addEventListener( 'touchstart', - event => { + (event) => { startDrag() event.preventDefault() }, diff --git a/demos/application-features/drag-and-drop/SimpleDragAndDropDemo.ts b/demos/application-features/drag-and-drop/SimpleDragAndDropDemo.ts index 3905c3958..a10c3f983 100644 --- a/demos/application-features/drag-and-drop/SimpleDragAndDropDemo.ts +++ b/demos/application-features/drag-and-drop/SimpleDragAndDropDemo.ts @@ -106,22 +106,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/drag-and-drop/index.html b/demos/application-features/drag-and-drop/index.html index aade230c0..d5464461d 100644 --- a/demos/application-features/drag-and-drop/index.html +++ b/demos/application-features/drag-and-drop/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/external-links/ExternalLinksDemo.js b/demos/application-features/external-links/ExternalLinksDemo.js index 8e07e531e..e76c67377 100644 --- a/demos/application-features/external-links/ExternalLinksDemo.js +++ b/demos/application-features/external-links/ExternalLinksDemo.js @@ -106,23 +106,23 @@ function buildGraph(graph, graphData) { graphBuilder .createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -142,7 +142,7 @@ function initializeLinkListener() { url = label.text.startsWith('www.') || label.text.startsWith('http') ? label.text : '' } else if (INode.isInstance(clickedItem) || IEdge.isInstance(clickedItem)) { // if a node or edge was clicked, we see whether it has any label that resembles a link - clickedItem.labels.forEach(label => { + clickedItem.labels.forEach((label) => { const text = label.text if (text.startsWith('www.') || text.startsWith('http')) { url = text diff --git a/demos/application-features/external-links/ExternalLinksDemo.ts b/demos/application-features/external-links/ExternalLinksDemo.ts index 5aae8bdf7..be00cdde3 100644 --- a/demos/application-features/external-links/ExternalLinksDemo.ts +++ b/demos/application-features/external-links/ExternalLinksDemo.ts @@ -103,23 +103,23 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { graphBuilder .createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/external-links/LinkItemHoverInputMode.js b/demos/application-features/external-links/LinkItemHoverInputMode.js index dd46aa194..b7aefb921 100644 --- a/demos/application-features/external-links/LinkItemHoverInputMode.js +++ b/demos/application-features/external-links/LinkItemHoverInputMode.js @@ -104,7 +104,7 @@ export default class LinkItemHoverInputMode extends ItemHoverInputMode { if (ILabel.isInstance(item) && (item.text.startsWith('www.') || item.text.startsWith('http'))) { labelLink = item } else if (INode.isInstance(item) || IEdge.isInstance(item)) { - item.labels.forEach(label => { + item.labels.forEach((label) => { const text = label.text if (text.startsWith('www.') || text.startsWith('http')) { labelLink = label diff --git a/demos/application-features/external-links/index.html b/demos/application-features/external-links/index.html index 4156fa507..f0ec28424 100644 --- a/demos/application-features/external-links/index.html +++ b/demos/application-features/external-links/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/file-operations/index.html b/demos/application-features/file-operations/index.html index 12690dfa4..72d6461bc 100644 --- a/demos/application-features/file-operations/index.html +++ b/demos/application-features/file-operations/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/file-operations/json-support.js b/demos/application-features/file-operations/json-support.js index 3199ebf5b..6168e23af 100644 --- a/demos/application-features/file-operations/json-support.js +++ b/demos/application-features/file-operations/json-support.js @@ -51,30 +51,30 @@ export function readJSON(graphComponent, text) { graphComponent.graph.clear() const graphBuilder = new GraphBuilder(graphComponent.graph) const nodesSource = graphBuilder.createNodesSource({ - data: data.nodeList.filter(item => item.isGroup !== true), - id: item => item.id, - parentId: item => item.parentId, - layout: item => item.layout + data: data.nodeList.filter((item) => item.isGroup !== true), + id: (item) => item.id, + parentId: (item) => item.parentId, + layout: (item) => item.layout }) nodesSource.nodeCreator.createLabelsSource({ - data: data => data.labels || [], - text: data => data.text, - layoutParameter: data => + data: (data) => data.labels || [], + text: (data) => data.text, + layoutParameter: (data) => data.layoutParameter ? ILabelModelParameter.deserializeParameter(data.layoutParameter) : null }) const groupNodesSource = graphBuilder.createGroupNodesSource({ - data: data.nodeList.filter(item => item.isGroup === true), - id: item => item.id, - parentId: item => item.parentId, - layout: item => item.layout + data: data.nodeList.filter((item) => item.isGroup === true), + id: (item) => item.id, + parentId: (item) => item.parentId, + layout: (item) => item.layout }) groupNodesSource.nodeCreator.createLabelsSource({ - data: data => data.labels || [], - text: data => data.text, - layoutParameter: data => + data: (data) => data.labels || [], + text: (data) => data.text, + layoutParameter: (data) => data.layoutParameter ? ILabelModelParameter.deserializeParameter(data.layoutParameter) : null @@ -82,18 +82,18 @@ export function readJSON(graphComponent, text) { const { edgeCreator } = graphBuilder.createEdgesSource( data.edgeList, - item => item.source, - item => item.target + (item) => item.source, + (item) => item.target ) edgeCreator.createLabelsSource({ - data: data => data.labels || [], - text: data => data.text, - layoutParameter: data => + data: (data) => data.labels || [], + text: (data) => data.text, + layoutParameter: (data) => data.layoutParameter ? ILabelModelParameter.deserializeParameter(data.layoutParameter) : null }) - edgeCreator.bendsProvider = item => item.bends + edgeCreator.bendsProvider = (item) => item.bends // Ports are not handled by GraphBuilder by default, so we use the EdgeCreated event for this. const tryDeserializePortParameter = (portParameter, port, graph) => { diff --git a/demos/application-features/file-operations/json-support.ts b/demos/application-features/file-operations/json-support.ts index 6d22f006c..213256702 100644 --- a/demos/application-features/file-operations/json-support.ts +++ b/demos/application-features/file-operations/json-support.ts @@ -52,30 +52,30 @@ export function readJSON(graphComponent: GraphComponent, text: string): void { graphComponent.graph.clear() const graphBuilder = new GraphBuilder(graphComponent.graph) const nodesSource = graphBuilder.createNodesSource({ - data: data.nodeList.filter(item => item.isGroup !== true), - id: item => item.id, - parentId: item => item.parentId, - layout: item => item.layout + data: data.nodeList.filter((item) => item.isGroup !== true), + id: (item) => item.id, + parentId: (item) => item.parentId, + layout: (item) => item.layout }) nodesSource.nodeCreator.createLabelsSource({ - data: data => data.labels || [], - text: data => data.text, - layoutParameter: data => + data: (data) => data.labels || [], + text: (data) => data.text, + layoutParameter: (data) => data.layoutParameter ? ILabelModelParameter.deserializeParameter(data.layoutParameter) : null }) const groupNodesSource = graphBuilder.createGroupNodesSource({ - data: data.nodeList.filter(item => item.isGroup === true), - id: item => item.id, - parentId: item => item.parentId, - layout: item => item.layout + data: data.nodeList.filter((item) => item.isGroup === true), + id: (item) => item.id, + parentId: (item) => item.parentId, + layout: (item) => item.layout }) groupNodesSource.nodeCreator.createLabelsSource({ - data: data => data.labels || [], - text: data => data.text, - layoutParameter: data => + data: (data) => data.labels || [], + text: (data) => data.text, + layoutParameter: (data) => data.layoutParameter ? ILabelModelParameter.deserializeParameter(data.layoutParameter) : null @@ -83,13 +83,13 @@ export function readJSON(graphComponent: GraphComponent, text: string): void { const { edgeCreator } = graphBuilder.createEdgesSource( data.edgeList, - item => item.source, - item => item.target + (item) => item.source, + (item) => item.target ) edgeCreator.createLabelsSource({ - data: data => data.labels || [], - text: data => data.text, - layoutParameter: data => + data: (data) => data.labels || [], + text: (data) => data.text, + layoutParameter: (data) => data.layoutParameter ? ILabelModelParameter.deserializeParameter(data.layoutParameter) : null diff --git a/demos/application-features/filtering-with-folding/FilteringWithFoldingDemo.js b/demos/application-features/filtering-with-folding/FilteringWithFoldingDemo.js index 6f2a2cee3..c8af2930a 100644 --- a/demos/application-features/filtering-with-folding/FilteringWithFoldingDemo.js +++ b/demos/application-features/filtering-with-folding/FilteringWithFoldingDemo.js @@ -119,22 +119,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -156,8 +156,8 @@ function enableFilteringAndFolding() { initializeGraph(fullGraph) // we want to hide items whose tag contains the string 'filtered' - const nodePredicate = node => !node.tag || !node.tag.filtered - const edgePredicate = edge => !edge.tag || !edge.tag.filtered + const nodePredicate = (node) => !node.tag || !node.tag.filtered + const edgePredicate = (edge) => !edge.tag || !edge.tag.filtered // create a filtered graph const filteredGraph = new FilteredGraphWrapper(fullGraph, nodePredicate, edgePredicate) @@ -231,10 +231,10 @@ function initializeUI() { filterItemsButton.addEventListener('click', () => { // mark the selected items such that the nodePredicate or edgePredicate will filter them - graphComponent.selection.selectedNodes.forEach(node => { + graphComponent.selection.selectedNodes.forEach((node) => { filterItemWithUndoUnit(node, true) }) - graphComponent.selection.selectedEdges.forEach(edge => { + graphComponent.selection.selectedEdges.forEach((edge) => { filterItemWithUndoUnit(edge, true) }) @@ -251,10 +251,10 @@ function initializeUI() { // access the unfiltered, unfolded graph to remove the filter mark from all items const filteredGraph = graphComponent.graph.foldingView.manager.masterGraph const fullGraph = filteredGraph.wrappedGraph - fullGraph.nodes.forEach(node => { + fullGraph.nodes.forEach((node) => { filterItemWithUndoUnit(node, false) }) - fullGraph.edges.forEach(edge => { + fullGraph.edges.forEach((edge) => { filterItemWithUndoUnit(edge, false) }) @@ -279,8 +279,8 @@ function updateResetButtonState() { const filteredGraph = graphComponent.graph.foldingView.manager.masterGraph const fullGraph = filteredGraph.wrappedGraph const hasFilteredItems = - fullGraph.nodes.some(node => node.tag && node.tag.filtered) || - fullGraph.edges.some(edge => edge.tag && edge.tag.filtered) + fullGraph.nodes.some((node) => node.tag && node.tag.filtered) || + fullGraph.edges.some((edge) => edge.tag && edge.tag.filtered) // set the reset button document.querySelector('#reset-filter').disabled = !hasFilteredItems } diff --git a/demos/application-features/filtering-with-folding/FilteringWithFoldingDemo.ts b/demos/application-features/filtering-with-folding/FilteringWithFoldingDemo.ts index bb43f4fc2..74463491e 100644 --- a/demos/application-features/filtering-with-folding/FilteringWithFoldingDemo.ts +++ b/demos/application-features/filtering-with-folding/FilteringWithFoldingDemo.ts @@ -116,22 +116,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -227,10 +227,10 @@ function initializeUI(): void { filterItemsButton.addEventListener('click', (): void => { // mark the selected items such that the nodePredicate or edgePredicate will filter them - graphComponent.selection.selectedNodes.forEach(node => { + graphComponent.selection.selectedNodes.forEach((node) => { filterItemWithUndoUnit(node, true) }) - graphComponent.selection.selectedEdges.forEach(edge => { + graphComponent.selection.selectedEdges.forEach((edge) => { filterItemWithUndoUnit(edge, true) }) @@ -249,10 +249,10 @@ function initializeUI(): void { const filteredGraph = graphComponent.graph.foldingView!.manager .masterGraph as FilteredGraphWrapper const fullGraph = filteredGraph.wrappedGraph! - fullGraph.nodes.forEach(node => { + fullGraph.nodes.forEach((node) => { filterItemWithUndoUnit(node, false) }) - fullGraph.edges.forEach(edge => { + fullGraph.edges.forEach((edge) => { filterItemWithUndoUnit(edge, false) }) @@ -278,8 +278,8 @@ function updateResetButtonState(): void { .masterGraph as FilteredGraphWrapper const fullGraph = filteredGraph.wrappedGraph! const hasFilteredItems = - fullGraph.nodes.some(node => node.tag && node.tag.filtered) || - fullGraph.edges.some(edge => edge.tag && edge.tag.filtered) + fullGraph.nodes.some((node) => node.tag && node.tag.filtered) || + fullGraph.edges.some((edge) => edge.tag && edge.tag.filtered) // set the reset button document.querySelector('#reset-filter')!.disabled = !hasFilteredItems } diff --git a/demos/application-features/filtering-with-folding/index.html b/demos/application-features/filtering-with-folding/index.html index d328974b4..a5a01f7b0 100644 --- a/demos/application-features/filtering-with-folding/index.html +++ b/demos/application-features/filtering-with-folding/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/filtering/FilteringDemo.js b/demos/application-features/filtering/FilteringDemo.js index 2aa8a8efb..48ae8242a 100644 --- a/demos/application-features/filtering/FilteringDemo.js +++ b/demos/application-features/filtering/FilteringDemo.js @@ -128,22 +128,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -162,8 +162,8 @@ function createFilterGraph() { initializeGraph(fullGraph) // we want to hide items whose tag contains the string 'filtered' - const nodePredicate = node => !node.tag || !node.tag.filtered - const edgePredicate = edge => !edge.tag || !edge.tag.filtered + const nodePredicate = (node) => !node.tag || !node.tag.filtered + const edgePredicate = (edge) => !edge.tag || !edge.tag.filtered // create a filtered graph return new FilteredGraphWrapper(fullGraph, nodePredicate, edgePredicate) @@ -174,10 +174,10 @@ function createFilterGraph() { */ function filterItems() { // mark the selected items such that the nodePredicate or edgePredicate will filter them - graphComponent.selection.selectedNodes.forEach(node => { + graphComponent.selection.selectedNodes.forEach((node) => { filterItemWithUndoUnit(node, true) }) - graphComponent.selection.selectedEdges.forEach(edge => { + graphComponent.selection.selectedEdges.forEach((edge) => { filterItemWithUndoUnit(edge, true) }) @@ -194,10 +194,10 @@ function restoreItems() { // access the unfiltered, unfolded graph to remove the filter mark from all items const filteredGraph = graphComponent.graph const fullGraph = filteredGraph.wrappedGraph - fullGraph.nodes.forEach(node => { + fullGraph.nodes.forEach((node) => { filterItemWithUndoUnit(node, false) }) - fullGraph.edges.forEach(edge => { + fullGraph.edges.forEach((edge) => { filterItemWithUndoUnit(edge, false) }) @@ -287,8 +287,8 @@ function initializeUI() { function updateResetButtonState() { const fullGraph = graphComponent.graph.wrappedGraph const hasFilteredItems = - fullGraph.nodes.some(node => node.tag && node.tag.filtered) || - fullGraph.edges.some(edge => edge.tag && edge.tag.filtered) + fullGraph.nodes.some((node) => node.tag && node.tag.filtered) || + fullGraph.edges.some((edge) => edge.tag && edge.tag.filtered) // set the reset button document.querySelector('#reset-filter').disabled = !hasFilteredItems } diff --git a/demos/application-features/filtering/FilteringDemo.ts b/demos/application-features/filtering/FilteringDemo.ts index 280807c70..c4a67b77b 100644 --- a/demos/application-features/filtering/FilteringDemo.ts +++ b/demos/application-features/filtering/FilteringDemo.ts @@ -125,22 +125,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -171,10 +171,10 @@ function createFilterGraph(): FilteredGraphWrapper { */ function filterItems(): void { // mark the selected items such that the nodePredicate or edgePredicate will filter them - graphComponent.selection.selectedNodes.forEach(node => { + graphComponent.selection.selectedNodes.forEach((node) => { filterItemWithUndoUnit(node, true) }) - graphComponent.selection.selectedEdges.forEach(edge => { + graphComponent.selection.selectedEdges.forEach((edge) => { filterItemWithUndoUnit(edge, true) }) @@ -191,10 +191,10 @@ function restoreItems(): void { // access the unfiltered, unfolded graph to remove the filter mark from all items const filteredGraph = graphComponent.graph as FilteredGraphWrapper const fullGraph = filteredGraph.wrappedGraph! - fullGraph.nodes.forEach(node => { + fullGraph.nodes.forEach((node) => { filterItemWithUndoUnit(node, false) }) - fullGraph.edges.forEach(edge => { + fullGraph.edges.forEach((edge) => { filterItemWithUndoUnit(edge, false) }) @@ -282,8 +282,8 @@ function initializeUI(): void { function updateResetButtonState(): void { const fullGraph = (graphComponent.graph as FilteredGraphWrapper).wrappedGraph! const hasFilteredItems = - fullGraph.nodes.some(node => node.tag && node.tag.filtered) || - fullGraph.edges.some(edge => edge.tag && edge.tag.filtered) + fullGraph.nodes.some((node) => node.tag && node.tag.filtered) || + fullGraph.edges.some((edge) => edge.tag && edge.tag.filtered) // set the reset button document.querySelector('#reset-filter')!.disabled = !hasFilteredItems } diff --git a/demos/application-features/filtering/index.html b/demos/application-features/filtering/index.html index 2642ed946..29a0b2ab4 100644 --- a/demos/application-features/filtering/index.html +++ b/demos/application-features/filtering/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/folding/FoldingDemo.js b/demos/application-features/folding/FoldingDemo.js index 5d0c33f56..7acc6a314 100644 --- a/demos/application-features/folding/FoldingDemo.js +++ b/demos/application-features/folding/FoldingDemo.js @@ -101,22 +101,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/folding/FoldingDemo.ts b/demos/application-features/folding/FoldingDemo.ts index 0bcca11b1..77455c8c6 100644 --- a/demos/application-features/folding/FoldingDemo.ts +++ b/demos/application-features/folding/FoldingDemo.ts @@ -98,22 +98,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/folding/index.html b/demos/application-features/folding/index.html index c0682afcb..b14351164 100644 --- a/demos/application-features/folding/index.html +++ b/demos/application-features/folding/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/graph-copy/GraphCopyDemo.js b/demos/application-features/graph-copy/GraphCopyDemo.js index f9d9e1a33..4fccc60f6 100644 --- a/demos/application-features/graph-copy/GraphCopyDemo.js +++ b/demos/application-features/graph-copy/GraphCopyDemo.js @@ -108,22 +108,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -146,7 +146,7 @@ function copyGraph() { copyGraphComponent.graph.clear() graphCopier.copy( originalGraphComponent.graph, - item => { + (item) => { const selection = originalGraphComponent.selection if (INode.isInstance(item)) { // copy selected node diff --git a/demos/application-features/graph-copy/GraphCopyDemo.ts b/demos/application-features/graph-copy/GraphCopyDemo.ts index 3797d3712..1c1306b5f 100644 --- a/demos/application-features/graph-copy/GraphCopyDemo.ts +++ b/demos/application-features/graph-copy/GraphCopyDemo.ts @@ -106,22 +106,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/graph-copy/index.html b/demos/application-features/graph-copy/index.html index a5bdfea7a..474459146 100644 --- a/demos/application-features/graph-copy/index.html +++ b/demos/application-features/graph-copy/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/graph-decorator/GraphDecoratorDemo.js b/demos/application-features/graph-decorator/GraphDecoratorDemo.js index 05058c40f..ecb484f3b 100644 --- a/demos/application-features/graph-decorator/GraphDecoratorDemo.js +++ b/demos/application-features/graph-decorator/GraphDecoratorDemo.js @@ -96,22 +96,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -146,7 +146,7 @@ function configurePortCandidateProvider(graph) { // IPortCandidateProvider.fromExistingPorts provides port candidates at the locations of already existing ports. // IPortCandidateProvider.fromNodeCenter provides a single port candidate at the center of the node. // IPortCandidateProvider.fromShapeGeometry provides several port candidates based on the shape of the node's style. - portCandidateProviderDecorator.setFactory(node => + portCandidateProviderDecorator.setFactory((node) => IPortCandidateProvider.combine([ IPortCandidateProvider.fromExistingPorts(node), IPortCandidateProvider.fromNodeCenter(node), diff --git a/demos/application-features/graph-decorator/GraphDecoratorDemo.ts b/demos/application-features/graph-decorator/GraphDecoratorDemo.ts index 66ee1e769..7ef1c64a5 100644 --- a/demos/application-features/graph-decorator/GraphDecoratorDemo.ts +++ b/demos/application-features/graph-decorator/GraphDecoratorDemo.ts @@ -93,22 +93,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/graph-decorator/index.html b/demos/application-features/graph-decorator/index.html index dcf612c9d..d1108e202 100644 --- a/demos/application-features/graph-decorator/index.html +++ b/demos/application-features/graph-decorator/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/graph-search/GraphSearchDemo.js b/demos/application-features/graph-search/GraphSearchDemo.js index 2efd63031..4e97e1b62 100644 --- a/demos/application-features/graph-search/GraphSearchDemo.js +++ b/demos/application-features/graph-search/GraphSearchDemo.js @@ -93,15 +93,15 @@ async function run() { function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) - const nodesSource = graphBuilder.createNodesSource(graphData.nodeList, item => item.id) - nodesSource.nodeCreator.layoutProvider = item => + const nodesSource = graphBuilder.createNodesSource(graphData.nodeList, (item) => item.id) + nodesSource.nodeCreator.layoutProvider = (item) => item.label === 'Hobbies' ? new Rect(0, 0, 130, 70) : new Rect(0, 0, 80, 40) - nodesSource.nodeCreator.createLabelBinding(data => data.label) + nodesSource.nodeCreator.createLabelBinding((data) => data.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -138,7 +138,7 @@ function updateSearch(searchText) { // first remove previous highlights manager.clearHighlights() if (searchText.trim() !== '') { - graphComponent.graph.nodes.forEach(node => { + graphComponent.graph.nodes.forEach((node) => { if (matches(node, searchText)) { // if the node is a match, highlight it manager.addHighlight(node) @@ -154,7 +154,7 @@ function updateSearch(searchText) { * @returns {boolean} True if the node matches the text, false otherwise */ function matches(node, text) { - return node.labels.some(label => label.text.toLowerCase().includes(text.toLowerCase())) + return node.labels.some((label) => label.text.toLowerCase().includes(text.toLowerCase())) } /** @@ -177,7 +177,7 @@ function initializeGraph(graph) { */ function initializeUI() { // adds the listener to the search box - document.querySelector('#searchBox').addEventListener('input', e => { + document.querySelector('#searchBox').addEventListener('input', (e) => { updateSearch(e.target.value) }) } diff --git a/demos/application-features/graph-search/GraphSearchDemo.ts b/demos/application-features/graph-search/GraphSearchDemo.ts index 07197a633..674efc561 100644 --- a/demos/application-features/graph-search/GraphSearchDemo.ts +++ b/demos/application-features/graph-search/GraphSearchDemo.ts @@ -91,15 +91,15 @@ async function run(): Promise { function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) - const nodesSource = graphBuilder.createNodesSource(graphData.nodeList, item => item.id) - nodesSource.nodeCreator.layoutProvider = item => + const nodesSource = graphBuilder.createNodesSource(graphData.nodeList, (item) => item.id) + nodesSource.nodeCreator.layoutProvider = (item) => item.label === 'Hobbies' ? new Rect(0, 0, 130, 70) : new Rect(0, 0, 80, 40) - nodesSource.nodeCreator.createLabelBinding(data => data.label) + nodesSource.nodeCreator.createLabelBinding((data) => data.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -136,7 +136,7 @@ function updateSearch(searchText: string): void { // first remove previous highlights manager.clearHighlights() if (searchText.trim() !== '') { - graphComponent.graph.nodes.forEach(node => { + graphComponent.graph.nodes.forEach((node) => { if (matches(node, searchText)) { // if the node is a match, highlight it manager.addHighlight(node) @@ -152,7 +152,7 @@ function updateSearch(searchText: string): void { * @returns True if the node matches the text, false otherwise */ function matches(node: INode, text: string): boolean { - return node.labels.some(label => label.text.toLowerCase().includes(text.toLowerCase())) + return node.labels.some((label) => label.text.toLowerCase().includes(text.toLowerCase())) } /** diff --git a/demos/application-features/graph-search/index.html b/demos/application-features/graph-search/index.html index eac0511b7..747a781e7 100644 --- a/demos/application-features/graph-search/index.html +++ b/demos/application-features/graph-search/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/grid-snapping/GridSnappingDemo.js b/demos/application-features/grid-snapping/GridSnappingDemo.js index 9f7de9c5b..22f6ac8c0 100644 --- a/demos/application-features/grid-snapping/GridSnappingDemo.js +++ b/demos/application-features/grid-snapping/GridSnappingDemo.js @@ -118,22 +118,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -327,7 +327,7 @@ function createColorPicker(sortedGridColors) { // Remove styling from previous selection gridColorPicker .querySelectorAll('.selected-color') - .forEach(rect => rect.classList.remove('selected-color')) + .forEach((rect) => rect.classList.remove('selected-color')) rect.classList.add('selected-color') updateGridColor(fill) }.bind(null, Fill.from(colorName.replace(' ', '-')), rect) diff --git a/demos/application-features/grid-snapping/GridSnappingDemo.ts b/demos/application-features/grid-snapping/GridSnappingDemo.ts index 1dc23601a..202f7a391 100644 --- a/demos/application-features/grid-snapping/GridSnappingDemo.ts +++ b/demos/application-features/grid-snapping/GridSnappingDemo.ts @@ -115,22 +115,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/grid-snapping/index.html b/demos/application-features/grid-snapping/index.html index 27edda3f9..48945e61a 100644 --- a/demos/application-features/grid-snapping/index.html +++ b/demos/application-features/grid-snapping/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/input-output/InputOutputDemo.js b/demos/application-features/input-output/InputOutputDemo.js index c50753979..06df1fde7 100644 --- a/demos/application-features/input-output/InputOutputDemo.js +++ b/demos/application-features/input-output/InputOutputDemo.js @@ -106,22 +106,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/input-output/InputOutputDemo.ts b/demos/application-features/input-output/InputOutputDemo.ts index cdb3538d5..f969af1b3 100644 --- a/demos/application-features/input-output/InputOutputDemo.ts +++ b/demos/application-features/input-output/InputOutputDemo.ts @@ -103,22 +103,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/input-output/index.html b/demos/application-features/input-output/index.html index 77e4ebb42..065b305bf 100644 --- a/demos/application-features/input-output/index.html +++ b/demos/application-features/input-output/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/interactiveaggregation/InteractiveAggregationDemo.js b/demos/application-features/interactiveaggregation/InteractiveAggregationDemo.js index 4e93d6558..d4b8683e2 100644 --- a/demos/application-features/interactiveaggregation/InteractiveAggregationDemo.js +++ b/demos/application-features/interactiveaggregation/InteractiveAggregationDemo.js @@ -76,32 +76,32 @@ let graphComponent = null let aggregateGraph = null // selectors for shape and/or color -const shapeSelector = n => n.style.shape +const shapeSelector = (n) => n.style.shape -const fillColorSelector = n => { +const fillColorSelector = (n) => { const fill = n.style.fill return fill.color } -const shapeAndFillSelector = n => new ShapeAndFill(shapeSelector(n), fillColorSelector(n)) +const shapeAndFillSelector = (n) => new ShapeAndFill(shapeSelector(n), fillColorSelector(n)) const grayBorder = new Stroke('#77776E', 2.0) // style factories for aggregation nodes -const shapeStyle = shape => +const shapeStyle = (shape) => new ShapeNodeStyle({ fill: '#C7C7A6', shape: shape, stroke: grayBorder }) -const fillStyle = fillColor => +const fillStyle = (fillColor) => new ShapeNodeStyle({ fill: new SolidColorFill(fillColor), shape: ShapeNodeShape.ELLIPSE, stroke: grayBorder }) -const shapeAndFillStyle = shapeAndFill => +const shapeAndFillStyle = (shapeAndFill) => new ShapeNodeStyle({ fill: new SolidColorFill(shapeAndFill.fillColor), shape: shapeAndFill.shape, @@ -169,7 +169,7 @@ function configureContextMenu(graphComponent) { // Add event listeners to the various events that open the context menu. These listeners then // call the provided callback function which in turn asks the current ContextMenuInputMode if a // context menu should be shown at the current location. - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (inputMode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } @@ -208,7 +208,7 @@ function populateContextMenu(contextMenu, _sender, e) { const selectedNodes = graphComponent.selection.selectedNodes if (selectedNodes.size > 0) { // only allow aggregation operations on nodes that are not aggregation nodes already - const aggregateAllowed = selectedNodes.some(n => !aggregateGraph.isAggregationItem(n)) + const aggregateAllowed = selectedNodes.some((n) => !aggregateGraph.isAggregationItem(n)) if (aggregateAllowed) { // add aggregation menu items @@ -226,7 +226,7 @@ function populateContextMenu(contextMenu, _sender, e) { ) } - const separateAllowed = selectedNodes.some(n => aggregateGraph.isAggregationItem(n)) + const separateAllowed = selectedNodes.some((n) => aggregateGraph.isAggregationItem(n)) if (separateAllowed) { contextMenu.addMenuItem('Separate', () => separate(selectedNodes.toList())) @@ -246,7 +246,7 @@ function populateContextMenu(contextMenu, _sender, e) { aggregateAll(shapeAndFillSelector, shapeAndFillStyle) ) - const separateAllowed = graphComponent.graph.nodes.some(node => + const separateAllowed = graphComponent.graph.nodes.some((node) => aggregateGraph.isAggregationItem(node) ) @@ -332,9 +332,9 @@ function buildGraph(graph, graphData) { graphBuilder.createNodesSource({ data: graphData.nodeList, - id: item => item.id, - parentId: item => item.parentId - }).nodeCreator.styleProvider = item => { + id: (item) => item.id, + parentId: (item) => item.parentId + }).nodeCreator.styleProvider = (item) => { switch (item.tag) { case 'b1': return new ShapeNodeStyle({ @@ -383,8 +383,8 @@ function buildGraph(graph, graphData) { graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -401,15 +401,15 @@ function buildGraph(graph, graphData) { function aggregateSame(nodes, selector, styleFactory) { // get one representative of each kind of node (determined by the selector) ignoring aggregation nodes const distinctNodes = nodes - .filter(n => !aggregateGraph.isAggregationItem(n)) + .filter((n) => !aggregateGraph.isAggregationItem(n)) .groupBy({ keySelector: selector, resultCreator: (key, enumerable) => ({ key: key, enumerable: enumerable }) }) - .map(grouping => grouping.enumerable.first()) + .map((grouping) => grouping.enumerable.first()) .toList() - distinctNodes.forEach(node => { + distinctNodes.forEach((node) => { // aggregate all nodes of the same kind as the representing node const nodesOfSameKind = collectNodesOfSameKind(node, selector) aggregate(nodesOfSameKind, selector(node), styleFactory) @@ -428,8 +428,8 @@ function aggregateSame(nodes, selector, styleFactory) { function collectNodesOfSameKind(node, selector) { const nodeKind = selector(node) return graphComponent.graph.nodes - .filter(n => !aggregateGraph.isAggregationItem(n)) - .filter(n => YObject.equals(selector(n), nodeKind)) + .filter((n) => !aggregateGraph.isAggregationItem(n)) + .filter((n) => YObject.equals(selector(n), nodeKind)) .toList() } @@ -450,7 +450,7 @@ function aggregateAll(selector, styleFactory) { resultCreator: (key, enumerable) => ({ key: key, enumerable: enumerable }) }) .toList() - .forEach(arg => { + .forEach((arg) => { aggregate(arg.enumerable.toList(), arg.key, styleFactory) }) @@ -478,7 +478,7 @@ function aggregate(nodes, key, styleFactory) { * @param {!IEnumerable.} nodes the nodes to separate */ function separate(nodes) { - nodes.forEach(node => { + nodes.forEach((node) => { if (aggregateGraph.isAggregationItem(node)) { aggregateGraph.separate(node) } diff --git a/demos/application-features/interactiveaggregation/InteractiveAggregationDemo.ts b/demos/application-features/interactiveaggregation/InteractiveAggregationDemo.ts index df29cee6a..17fb4294a 100644 --- a/demos/application-features/interactiveaggregation/InteractiveAggregationDemo.ts +++ b/demos/application-features/interactiveaggregation/InteractiveAggregationDemo.ts @@ -166,7 +166,7 @@ function configureContextMenu(graphComponent: GraphComponent): void { // Add event listeners to the various events that open the context menu. These listeners then // call the provided callback function which in turn asks the current ContextMenuInputMode if a // context menu should be shown at the current location. - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (inputMode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } @@ -244,7 +244,7 @@ function populateContextMenu( aggregateAll(shapeAndFillSelector, shapeAndFillStyle) ) - const separateAllowed = graphComponent.graph.nodes.some(node => + const separateAllowed = graphComponent.graph.nodes.some((node) => aggregateGraph.isAggregationItem(node) ) @@ -328,9 +328,9 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { graphBuilder.createNodesSource({ data: graphData.nodeList, - id: item => item.id, - parentId: item => item.parentId - }).nodeCreator.styleProvider = item => { + id: (item) => item.id, + parentId: (item) => item.parentId + }).nodeCreator.styleProvider = (item) => { switch (item.tag) { case 'b1': return new ShapeNodeStyle({ @@ -379,8 +379,8 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -400,15 +400,15 @@ function aggregateSame( ): void { // get one representative of each kind of node (determined by the selector) ignoring aggregation nodes const distinctNodes: IList = nodes - .filter(n => !aggregateGraph.isAggregationItem(n)) + .filter((n) => !aggregateGraph.isAggregationItem(n)) .groupBy({ keySelector: selector, resultCreator: (key, enumerable) => ({ key: key, enumerable: enumerable }) }) - .map(grouping => grouping.enumerable.first()) + .map((grouping) => grouping.enumerable.first()) .toList() - distinctNodes.forEach(node => { + distinctNodes.forEach((node) => { // aggregate all nodes of the same kind as the representing node const nodesOfSameKind = collectNodesOfSameKind(node, selector) aggregate(nodesOfSameKind, selector(node), styleFactory) @@ -425,8 +425,8 @@ function aggregateSame( function collectNodesOfSameKind(node: INode, selector: (arg: INode) => TKey): IList { const nodeKind = selector(node) return graphComponent.graph.nodes - .filter(n => !aggregateGraph.isAggregationItem(n)) - .filter(n => YObject.equals(selector(n), nodeKind)) + .filter((n) => !aggregateGraph.isAggregationItem(n)) + .filter((n) => YObject.equals(selector(n), nodeKind)) .toList() } @@ -447,7 +447,7 @@ function aggregateAll( resultCreator: (key, enumerable) => ({ key: key, enumerable: enumerable }) }) .toList() - .forEach(arg => { + .forEach((arg) => { aggregate(arg.enumerable.toList(), arg.key, styleFactory) }) @@ -478,7 +478,7 @@ function aggregate( * @param nodes the nodes to separate */ function separate(nodes: IEnumerable): void { - nodes.forEach(node => { + nodes.forEach((node) => { if (aggregateGraph.isAggregationItem(node)) { aggregateGraph.separate(node) } diff --git a/demos/application-features/interactiveaggregation/index.html b/demos/application-features/interactiveaggregation/index.html index 3296d0c3c..cbf3b33e6 100644 --- a/demos/application-features/interactiveaggregation/index.html +++ b/demos/application-features/interactiveaggregation/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/label-text-wrapping/index.html b/demos/application-features/label-text-wrapping/index.html index 4f973b342..fcc37cc54 100644 --- a/demos/application-features/label-text-wrapping/index.html +++ b/demos/application-features/label-text-wrapping/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/native-listeners/NativeListenersDemo.js b/demos/application-features/native-listeners/NativeListenersDemo.js index deaf04dc6..6f3fad014 100644 --- a/demos/application-features/native-listeners/NativeListenersDemo.js +++ b/demos/application-features/native-listeners/NativeListenersDemo.js @@ -90,22 +90,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/native-listeners/NativeListenersDemo.ts b/demos/application-features/native-listeners/NativeListenersDemo.ts index 6bdb82357..808874a61 100644 --- a/demos/application-features/native-listeners/NativeListenersDemo.ts +++ b/demos/application-features/native-listeners/NativeListenersDemo.ts @@ -87,22 +87,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/native-listeners/NodeStyleDecorator.js b/demos/application-features/native-listeners/NodeStyleDecorator.js index 98ee1366b..05f6e68ce 100644 --- a/demos/application-features/native-listeners/NodeStyleDecorator.js +++ b/demos/application-features/native-listeners/NodeStyleDecorator.js @@ -87,7 +87,7 @@ export default class NodeStyleDecorator extends NodeStyleBase { // register a native click listener on the SVG element button.addEventListener('click', showToast) // the input mode should not handle any event on the the button where we registered a native click listener - button.addEventListener('mousedown', e => e.preventDefault()) + button.addEventListener('mousedown', (e) => e.preventDefault()) const decorationVisual = new SvgVisual(button) diff --git a/demos/application-features/native-listeners/index.html b/demos/application-features/native-listeners/index.html index 78fe37f35..8a9617a24 100644 --- a/demos/application-features/native-listeners/index.html +++ b/demos/application-features/native-listeners/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/orthogonal-edges/OrthogonalEdgesDemo.js b/demos/application-features/orthogonal-edges/OrthogonalEdgesDemo.js index 03a55ba71..5867feef6 100644 --- a/demos/application-features/orthogonal-edges/OrthogonalEdgesDemo.js +++ b/demos/application-features/orthogonal-edges/OrthogonalEdgesDemo.js @@ -96,22 +96,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/orthogonal-edges/OrthogonalEdgesDemo.ts b/demos/application-features/orthogonal-edges/OrthogonalEdgesDemo.ts index 04b204d03..867a4b4ff 100644 --- a/demos/application-features/orthogonal-edges/OrthogonalEdgesDemo.ts +++ b/demos/application-features/orthogonal-edges/OrthogonalEdgesDemo.ts @@ -93,22 +93,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/orthogonal-edges/index.html b/demos/application-features/orthogonal-edges/index.html index b527945f1..c8f3ce64b 100644 --- a/demos/application-features/orthogonal-edges/index.html +++ b/demos/application-features/orthogonal-edges/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/overview/OverviewComponentDemo.js b/demos/application-features/overview/OverviewComponentDemo.js index 89e4d3819..0cc4bd3b8 100644 --- a/demos/application-features/overview/OverviewComponentDemo.js +++ b/demos/application-features/overview/OverviewComponentDemo.js @@ -118,22 +118,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/overview/OverviewComponentDemo.ts b/demos/application-features/overview/OverviewComponentDemo.ts index 3bb248abb..66d20bfe2 100644 --- a/demos/application-features/overview/OverviewComponentDemo.ts +++ b/demos/application-features/overview/OverviewComponentDemo.ts @@ -115,22 +115,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/overview/index.html b/demos/application-features/overview/index.html index ec9b1f83e..fb62d7d7b 100644 --- a/demos/application-features/overview/index.html +++ b/demos/application-features/overview/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/rectangular-indicator/RectangularIndicatorDemo.js b/demos/application-features/rectangular-indicator/RectangularIndicatorDemo.js index 9866ef55e..7a42df74e 100644 --- a/demos/application-features/rectangular-indicator/RectangularIndicatorDemo.js +++ b/demos/application-features/rectangular-indicator/RectangularIndicatorDemo.js @@ -116,22 +116,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/rectangular-indicator/RectangularIndicatorDemo.ts b/demos/application-features/rectangular-indicator/RectangularIndicatorDemo.ts index e751f7b94..24f08b376 100644 --- a/demos/application-features/rectangular-indicator/RectangularIndicatorDemo.ts +++ b/demos/application-features/rectangular-indicator/RectangularIndicatorDemo.ts @@ -112,22 +112,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/rectangular-indicator/index.html b/demos/application-features/rectangular-indicator/index.html index 1175eb5c1..0d11cfd7d 100644 --- a/demos/application-features/rectangular-indicator/index.html +++ b/demos/application-features/rectangular-indicator/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/rotatablenodes/RotatableNodeLabels.js b/demos/application-features/rotatablenodes/RotatableNodeLabels.js index 645c3a719..ea2d614d8 100644 --- a/demos/application-features/rotatablenodes/RotatableNodeLabels.js +++ b/demos/application-features/rotatablenodes/RotatableNodeLabels.js @@ -85,7 +85,7 @@ export class RotatableNodeLabelModelDecorator extends BaseClass( * Provides custom implementations of {@link ILabelModelParameterProvider} and * {@link ILabelModelParameterFinder} that consider the nodes rotation. * Wraps the default implementations in a special wrapper which supports rotation. - * @template {*} T + * @template T * @param {!Class.} type * @returns {?T} */ @@ -326,7 +326,7 @@ class RotatedNodeLabelModelParameterProvider extends BaseClass(ILabelModelParame const wrapperModel = model const parameters = this.wrappedProvider.getParameters(label, wrapperModel.wrapped) const result = new List() - parameters.forEach(parameter => { + parameters.forEach((parameter) => { result.add(wrapperModel.createWrappingParameter(parameter)) }) return result @@ -390,10 +390,6 @@ export class RotatableNodeLabelModelDecoratorExtension extends MarkupExtension { _useNodeRotation = true _wrapped = null - constructor() { - super() - } - /** * @type {boolean} */ diff --git a/demos/application-features/rotatablenodes/RotatableNodeLabels.ts b/demos/application-features/rotatablenodes/RotatableNodeLabels.ts index 7e0bfeed3..37d75c950 100644 --- a/demos/application-features/rotatablenodes/RotatableNodeLabels.ts +++ b/demos/application-features/rotatablenodes/RotatableNodeLabels.ts @@ -86,7 +86,7 @@ export class RotatableNodeLabelModelDecorator * Wraps the default implementations in a special wrapper which supports rotation. */ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - lookup(type: Class): T | null { + lookup(type: Class): T | null { if (type === ILabelModelParameterProvider.$class) { const provider = this.wrapped.lookup(ILabelModelParameterProvider.$class) if (provider) { @@ -295,7 +295,7 @@ class RotatedNodeLabelModelParameterProvider const wrapperModel = model as RotatableNodeLabelModelDecorator const parameters = this.wrappedProvider.getParameters(label, wrapperModel.wrapped) const result = new List() - parameters.forEach(parameter => { + parameters.forEach((parameter) => { result.add(wrapperModel.createWrappingParameter(parameter)) }) return result @@ -361,10 +361,6 @@ export class RotatableNodeLabelModelDecoratorExtension extends MarkupExtension { private _useNodeRotation = true private _wrapped: ILabelModel = null! - constructor() { - super() - } - get useNodeRotation(): boolean { return this._useNodeRotation } diff --git a/demos/application-features/rotatablenodes/RotatableNodes.js b/demos/application-features/rotatablenodes/RotatableNodes.js index ee86df364..b2cb34cf5 100644 --- a/demos/application-features/rotatablenodes/RotatableNodes.js +++ b/demos/application-features/rotatablenodes/RotatableNodes.js @@ -710,7 +710,7 @@ class RotatedNodeResizeHandle extends BaseClass(IHandle, IPoint) { this.portHandles.clear() const portContext = new DelegatingContext(inputModeContext) - this.node.ports.forEach(port => { + this.node.ports.forEach((port) => { const portHandle = new DummyPortLocationModelParameterHandle(port) portHandle.initializeDrag(portContext) this.portHandles.add(portHandle) @@ -779,7 +779,7 @@ class RotatedNodeResizeHandle extends BaseClass(IHandle, IPoint) { ) const portContext = new DelegatingContext(inputModeContext) - this.portHandles.forEach(portHandle => { + this.portHandles.forEach((portHandle) => { portHandle.handleMove(portContext, this.dummyLocation, newLocation) }) if (this.reshapeHandler) { @@ -859,7 +859,7 @@ class RotatedNodeResizeHandle extends BaseClass(IHandle, IPoint) { this.initialLayout.size.toSize() ) const portContext = new DelegatingContext(inputModeContext) - this.portHandles.forEach(portHandle => { + this.portHandles.forEach((portHandle) => { portHandle.cancelDrag(portContext, originalLocation) }) this.portHandles.clear() @@ -883,7 +883,7 @@ class RotatedNodeResizeHandle extends BaseClass(IHandle, IPoint) { this.dummySize ) const portContext = new DelegatingContext(inputModeContext) - this.portHandles.forEach(portHandle => { + this.portHandles.forEach((portHandle) => { portHandle.dragFinished(portContext, originalLocation, newLocation) }) this.portHandles.clear() @@ -1178,7 +1178,7 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { this.portHandles.clear() const portContext = new DelegatingContext(inputModeContext) - this.node.ports.forEach(port => { + this.node.ports.forEach((port) => { const portHandle = new DummyPortLocationModelParameterHandle(port) portHandle.initializeDrag(portContext) this.portHandles.add(portHandle) @@ -1193,7 +1193,7 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { // only collect nodes that are in the viewport const rotatedNodes = canvas .getCanvasObjects() - .filter(co => { + .filter((co) => { const userObject = co.userObject return ( userObject !== this.node && @@ -1202,11 +1202,11 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { canvas.viewport.intersects(userObject.layout.toRect()) ) }) - .map(co => co.userObject) + .map((co) => co.userObject) // Group nodes by identical angles this.nodeAngles = rotatedNodes.reduce((groups, node) => { const angle = node.style.angle - const group = groups.find(g => g.angle === angle) + const group = groups.find((g) => g.angle === angle) if (group) { group.nodes.push(node) } else { @@ -1236,7 +1236,7 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { this.setAngle(inputModeContext, angle) const portContext = new DelegatingContext(inputModeContext) - this.portHandles.forEach(portHandle => { + this.portHandles.forEach((portHandle) => { portHandle.handleMove(portContext, originalLocation, newLocation) }) if (this.reshapeHandler) { @@ -1278,7 +1278,8 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { const candidate = this.nodeAngles .sort((nodeAngle1, nodeAngle2) => nodeAngle2.angle - nodeAngle1.angle) .find( - nodeAngle => normalizeAngle(Math.abs(nodeAngle.angle - angle)) < this.snapToSameAngleDelta + (nodeAngle) => + normalizeAngle(Math.abs(nodeAngle.angle - angle)) < this.snapToSameAngleDelta ) if (candidate) { // Add highlight to every matching node @@ -1286,7 +1287,7 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { if (this.sameAngleHighlightedNodes !== candidate.nodes) { this.clearSameAngleHighlights(inputModeContext) } - candidate.nodes.forEach(matchingNode => { + candidate.nodes.forEach((matchingNode) => { canvas.highlightIndicatorManager.addHighlight(matchingNode) }) this.sameAngleHighlightedNodes = candidate.nodes @@ -1312,7 +1313,7 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { this.setAngle(context, this.initialAngle) const portContext = new DelegatingContext(context) - this.portHandles.forEach(portHandle => { + this.portHandles.forEach((portHandle) => { portHandle.cancelDrag(portContext, originalLocation) }) this.portHandles.clear() @@ -1349,7 +1350,7 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { } const portContext = new DelegatingContext(context) - this.portHandles.forEach(portHandle => { + this.portHandles.forEach((portHandle) => { portHandle.dragFinished(portContext, originalLocation, newLocation) }) this.portHandles.clear() @@ -1384,7 +1385,7 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { */ clearSameAngleHighlights(context) { if (this.sameAngleHighlightedNodes) { - this.sameAngleHighlightedNodes.forEach(highlightedNode => { + this.sameAngleHighlightedNodes.forEach((highlightedNode) => { context.canvasComponent.highlightIndicatorManager.removeHighlight(highlightedNode) }) this.sameAngleHighlightedNodes = null @@ -1764,7 +1765,7 @@ class DelegatingContext extends BaseClass(IInputModeContext) { /** * Delegates to the wrapped context's lookup but cancels the snap context. - * @template {*} T + * @template T * @param {!Class.} type * @returns {?T} */ @@ -1817,10 +1818,6 @@ export class RotatableNodeStyleDecoratorExtension extends MarkupExtension { _angle = 0 _wrapped - constructor() { - super() - } - /** * @type {number} */ diff --git a/demos/application-features/rotatablenodes/RotatableNodes.ts b/demos/application-features/rotatablenodes/RotatableNodes.ts index c0ee862fc..ee4e374d6 100644 --- a/demos/application-features/rotatablenodes/RotatableNodes.ts +++ b/demos/application-features/rotatablenodes/RotatableNodes.ts @@ -652,7 +652,7 @@ class RotatedNodeResizeHandle extends BaseClass(IHandle, IPoint) { this.portHandles.clear() const portContext = new DelegatingContext(inputModeContext) - this.node.ports.forEach(port => { + this.node.ports.forEach((port) => { const portHandle = new DummyPortLocationModelParameterHandle(port) portHandle.initializeDrag(portContext) this.portHandles.add(portHandle) @@ -722,7 +722,7 @@ class RotatedNodeResizeHandle extends BaseClass(IHandle, IPoint) { ) const portContext = new DelegatingContext(inputModeContext) - this.portHandles.forEach(portHandle => { + this.portHandles.forEach((portHandle) => { portHandle.handleMove(portContext, this.dummyLocation, newLocation) }) if (this.reshapeHandler) { @@ -792,7 +792,7 @@ class RotatedNodeResizeHandle extends BaseClass(IHandle, IPoint) { this.initialLayout.size.toSize() ) const portContext = new DelegatingContext(inputModeContext) - this.portHandles.forEach(portHandle => { + this.portHandles.forEach((portHandle) => { portHandle.cancelDrag(portContext, originalLocation) }) this.portHandles.clear() @@ -817,7 +817,7 @@ class RotatedNodeResizeHandle extends BaseClass(IHandle, IPoint) { this.dummySize ) const portContext = new DelegatingContext(inputModeContext) - this.portHandles.forEach(portHandle => { + this.portHandles.forEach((portHandle) => { portHandle.dragFinished(portContext, originalLocation, newLocation) }) this.portHandles.clear() @@ -1005,7 +1005,10 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { /** * Creates a new instance. */ - constructor(private node: INode, private reshapeHandler: IReshapeHandler) { + constructor( + private node: INode, + private reshapeHandler: IReshapeHandler + ) { super() this.snapDelta = 0 this.snapStep = 0 @@ -1082,7 +1085,7 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { this.portHandles.clear() const portContext = new DelegatingContext(inputModeContext) - this.node.ports.forEach(port => { + this.node.ports.forEach((port) => { const portHandle = new DummyPortLocationModelParameterHandle(port) portHandle.initializeDrag(portContext) this.portHandles.add(portHandle) @@ -1097,7 +1100,7 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { // only collect nodes that are in the viewport const rotatedNodes = canvas .getCanvasObjects() - .filter(co => { + .filter((co) => { const userObject = co.userObject return ( userObject !== this.node && @@ -1106,11 +1109,11 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { canvas.viewport.intersects(userObject.layout.toRect()) ) }) - .map(co => co.userObject) + .map((co) => co.userObject) // Group nodes by identical angles this.nodeAngles = rotatedNodes.reduce((groups: SameAngleGroup[], node: INode) => { const angle = (node.style as RotatableNodeStyleDecorator).angle - const group = groups.find(g => g.angle === angle) + const group = groups.find((g) => g.angle === angle) if (group) { group.nodes.push(node) } else { @@ -1141,7 +1144,7 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { this.setAngle(inputModeContext, angle) const portContext = new DelegatingContext(inputModeContext) - this.portHandles.forEach(portHandle => { + this.portHandles.forEach((portHandle) => { portHandle.handleMove(portContext, originalLocation, newLocation) }) if (this.reshapeHandler) { @@ -1181,7 +1184,8 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { nodeAngle2.angle - nodeAngle1.angle ) .find( - nodeAngle => normalizeAngle(Math.abs(nodeAngle.angle - angle)) < this.snapToSameAngleDelta + (nodeAngle) => + normalizeAngle(Math.abs(nodeAngle.angle - angle)) < this.snapToSameAngleDelta ) if (candidate) { // Add highlight to every matching node @@ -1189,7 +1193,7 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { if (this.sameAngleHighlightedNodes !== candidate.nodes) { this.clearSameAngleHighlights(inputModeContext) } - candidate.nodes.forEach(matchingNode => { + candidate.nodes.forEach((matchingNode) => { canvas.highlightIndicatorManager.addHighlight(matchingNode) }) this.sameAngleHighlightedNodes = candidate.nodes @@ -1213,7 +1217,7 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { this.setAngle(context, this.initialAngle) const portContext = new DelegatingContext(context) - this.portHandles.forEach(portHandle => { + this.portHandles.forEach((portHandle) => { portHandle.cancelDrag(portContext, originalLocation) }) this.portHandles.clear() @@ -1247,7 +1251,7 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { } const portContext = new DelegatingContext(context) - this.portHandles.forEach(portHandle => { + this.portHandles.forEach((portHandle) => { portHandle.dragFinished(portContext, originalLocation, newLocation) }) this.portHandles.clear() @@ -1281,7 +1285,7 @@ export class NodeRotateHandle extends BaseClass(IHandle, IPoint) { */ clearSameAngleHighlights(context: IInputModeContext): void { if (this.sameAngleHighlightedNodes) { - this.sameAngleHighlightedNodes.forEach(highlightedNode => { + this.sameAngleHighlightedNodes.forEach((highlightedNode) => { ;(context.canvasComponent as GraphComponent).highlightIndicatorManager.removeHighlight( highlightedNode ) @@ -1618,7 +1622,7 @@ class DelegatingContext extends BaseClass(IInputModeContext) { * Delegates to the wrapped context's lookup but cancels the snap context. */ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - lookup(type: Class): T | null { + lookup(type: Class): T | null { return type === SnapContext.$class ? null : this.context.lookup(type) } @@ -1660,10 +1664,6 @@ export class RotatableNodeStyleDecoratorExtension extends MarkupExtension { private _angle = 0 private _wrapped!: INodeStyle - constructor() { - super() - } - get angle(): number { return this._angle } diff --git a/demos/application-features/rotatablenodes/RotatableNodesDemo.js b/demos/application-features/rotatablenodes/RotatableNodesDemo.js index 6bf612adb..e70b2c840 100644 --- a/demos/application-features/rotatablenodes/RotatableNodesDemo.js +++ b/demos/application-features/rotatablenodes/RotatableNodesDemo.js @@ -145,7 +145,7 @@ function initializeInputMode() { const handleInputMode = graphComponent.inputMode.handleInputMode handleInputMode.addDraggedListener((src, evt) => { if (src.currentHandle instanceof RotatableNodes.NodeRotateHandle) { - const rotatedNode = src.affectedItems.find(item => item instanceof INode) + const rotatedNode = src.affectedItems.find((item) => item instanceof INode) if ( rotatedNode && rotatedNode.style instanceof RotatableNodes.RotatableNodeStyleDecorator && @@ -193,7 +193,7 @@ function initializeGraph() { // For rotated nodes, need to provide port candidates that are backed by a rotatable port location model // If you want to support non-rotated port candidates, you can just provide undecorated instances here decorator.nodeDecorator.portCandidateProviderDecorator.setFactory( - node => node.style instanceof RotatableNodes.RotatableNodeStyleDecorator, + (node) => node.style instanceof RotatableNodes.RotatableNodeStyleDecorator, createPortCandidateProvider ) @@ -367,7 +367,7 @@ function createPortCandidateProvider(node) { const shapeProvider = IPortCandidateProvider.fromShapeGeometry(dummyNode, 0) const shapeCandidates = shapeProvider.getAllTargetPortCandidates(null) const rotatingCandidates = shapeCandidates.map( - candidate => + (candidate) => new DefaultPortCandidate( node, rotatedPortModel.createWrappingParameter(candidate.locationParameter) @@ -423,14 +423,14 @@ function loadGraph(sample) { const nodesSource = builder.createNodesSource({ data: data.nodes, id: 'id', - layout: data => new Rect(data.cx, data.cy, defaultNodeSize.width, defaultNodeSize.height), - style: data => { + layout: (data) => new Rect(data.cx, data.cy, defaultNodeSize.width, defaultNodeSize.height), + style: (data) => { const nodeStyle = graph.nodeDefaults.getStyleInstance() nodeStyle.angle = data.angle return nodeStyle } }) - nodesSource.nodeCreator.createLabelBinding(data => `${data.angle}°`) + nodesSource.nodeCreator.createLabelBinding((data) => `${data.angle}°`) builder.createEdgesSource(data.edges, 'source', 'target') builder.buildGraph() @@ -440,7 +440,7 @@ function loadGraph(sample) { INode.$class, YObject.$class, RotatedNodeLayoutStage.ROTATED_NODE_LAYOUT_DP_KEY, - node => { + (node) => { const style = node.style return { outline: getOutline(style, node), @@ -494,7 +494,7 @@ async function applyLayout() { INode.$class, YObject.$class, RotatedNodeLayoutStage.ROTATED_NODE_LAYOUT_DP_KEY, - node => { + (node) => { const style = node.style return { outline: getOutline(style, node), @@ -607,7 +607,7 @@ function initializeUI() { inputMode.orthogonalEdgeEditingContext.enabled = orthogonalEditing.checked }) - addNavigationButtons(selectSample).addEventListener('change', e => { + addNavigationButtons(selectSample).addEventListener('change', (e) => { loadGraph(e.target.value) }) @@ -622,12 +622,12 @@ function initializeUI() { */ function addRotatedStyles() { const graph = graphComponent.graph - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (!graph.isGroupNode(node)) { if (!(node.style instanceof RotatableNodes.RotatableNodeStyleDecorator)) { graph.setStyle(node, new RotatableNodes.RotatableNodeStyleDecorator(node.style)) } - node.labels.forEach(label => { + node.labels.forEach((label) => { if ( !( label.layoutParameter instanceof @@ -642,7 +642,7 @@ function addRotatedStyles() { ) } }) - node.ports.forEach(port => { + node.ports.forEach((port) => { if ( !( port.locationParameter instanceof diff --git a/demos/application-features/rotatablenodes/RotatableNodesDemo.ts b/demos/application-features/rotatablenodes/RotatableNodesDemo.ts index 17afb2b90..7d6b5dcf1 100644 --- a/demos/application-features/rotatablenodes/RotatableNodesDemo.ts +++ b/demos/application-features/rotatablenodes/RotatableNodesDemo.ts @@ -137,7 +137,7 @@ function initializeInputMode(): void { const handleInputMode = (graphComponent.inputMode as GraphEditorInputMode).handleInputMode handleInputMode.addDraggedListener((src, evt) => { if (src.currentHandle instanceof RotatableNodes.NodeRotateHandle) { - const rotatedNode = src.affectedItems.find(item => item instanceof INode) as INode + const rotatedNode = src.affectedItems.find((item) => item instanceof INode) as INode if ( rotatedNode && rotatedNode.style instanceof RotatableNodes.RotatableNodeStyleDecorator && @@ -185,7 +185,7 @@ function initializeGraph(): void { // For rotated nodes, need to provide port candidates that are backed by a rotatable port location model // If you want to support non-rotated port candidates, you can just provide undecorated instances here decorator.nodeDecorator.portCandidateProviderDecorator.setFactory( - node => node.style instanceof RotatableNodes.RotatableNodeStyleDecorator, + (node) => node.style instanceof RotatableNodes.RotatableNodeStyleDecorator, createPortCandidateProvider ) @@ -358,7 +358,7 @@ function createPortCandidateProvider(node: INode) { const shapeProvider = IPortCandidateProvider.fromShapeGeometry(dummyNode, 0) const shapeCandidates = shapeProvider.getAllTargetPortCandidates(null!) const rotatingCandidates = shapeCandidates.map( - candidate => + (candidate) => new DefaultPortCandidate( node, rotatedPortModel.createWrappingParameter(candidate.locationParameter) @@ -411,14 +411,14 @@ function loadGraph(sample: 'sine' | 'circle'): void { const nodesSource = builder.createNodesSource({ data: data.nodes, id: 'id', - layout: data => new Rect(data.cx, data.cy, defaultNodeSize.width, defaultNodeSize.height), - style: data => { + layout: (data) => new Rect(data.cx, data.cy, defaultNodeSize.width, defaultNodeSize.height), + style: (data) => { const nodeStyle = graph.nodeDefaults.getStyleInstance() as RotatableNodeStyleDecorator nodeStyle.angle = data.angle return nodeStyle } }) - nodesSource.nodeCreator.createLabelBinding(data => `${data.angle}°`) + nodesSource.nodeCreator.createLabelBinding((data) => `${data.angle}°`) builder.createEdgesSource(data.edges, 'source', 'target') builder.buildGraph() @@ -428,7 +428,7 @@ function loadGraph(sample: 'sine' | 'circle'): void { INode.$class, YObject.$class, RotatedNodeLayoutStage.ROTATED_NODE_LAYOUT_DP_KEY, - node => { + (node) => { const style = node.style return { outline: getOutline(style, node), @@ -472,7 +472,7 @@ async function applyLayout() { INode.$class, YObject.$class, RotatedNodeLayoutStage.ROTATED_NODE_LAYOUT_DP_KEY, - node => { + (node) => { const style = node.style return { outline: getOutline(style, node), @@ -600,12 +600,12 @@ function initializeUI(): void { */ function addRotatedStyles(): void { const graph = graphComponent.graph - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (!graph.isGroupNode(node)) { if (!(node.style instanceof RotatableNodes.RotatableNodeStyleDecorator)) { graph.setStyle(node, new RotatableNodes.RotatableNodeStyleDecorator(node.style)) } - node.labels.forEach(label => { + node.labels.forEach((label) => { if ( !( label.layoutParameter instanceof @@ -620,7 +620,7 @@ function addRotatedStyles(): void { ) } }) - node.ports.forEach(port => { + node.ports.forEach((port) => { if ( !( port.locationParameter instanceof diff --git a/demos/application-features/rotatablenodes/RotatablePorts.js b/demos/application-features/rotatablenodes/RotatablePorts.js index e548989f8..bbf5f0789 100644 --- a/demos/application-features/rotatablenodes/RotatablePorts.js +++ b/demos/application-features/rotatablenodes/RotatablePorts.js @@ -72,13 +72,6 @@ export class RotatablePortLocationModelDecorator extends BaseClass( RotatablePortLocationModelDecorator.$INSTANCE = INSTANCE } - /** - * Creates a new instance wrapping a {@link FreeNodePortLocationModel}. - */ - constructor() { - super() - } - /** * Delegates to the wrapped location model's lookup. * @template T diff --git a/demos/application-features/rotatablenodes/RotatablePorts.ts b/demos/application-features/rotatablenodes/RotatablePorts.ts index 6f551e535..9d8d53a32 100644 --- a/demos/application-features/rotatablenodes/RotatablePorts.ts +++ b/demos/application-features/rotatablenodes/RotatablePorts.ts @@ -60,13 +60,6 @@ export class RotatablePortLocationModelDecorator extends BaseClass( static INSTANCE: RotatablePortLocationModelDecorator = new RotatablePortLocationModelDecorator() - /** - * Creates a new instance wrapping a {@link FreeNodePortLocationModel}. - */ - constructor() { - super() - } - /** * Delegates to the wrapped location model's lookup. */ diff --git a/demos/application-features/rotatablenodes/RotatedNodeLayoutStage.js b/demos/application-features/rotatablenodes/RotatedNodeLayoutStage.js index dd615554a..e9a9e2486 100644 --- a/demos/application-features/rotatablenodes/RotatedNodeLayoutStage.js +++ b/demos/application-features/rotatablenodes/RotatedNodeLayoutStage.js @@ -65,14 +65,6 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { /** How to connect edges from the bounding box to the actual shape. */ edgeRoutingMode = 'shortest-straight-path-to-border' - /** - * Creates a new instance with an optional core layout algorithm. - * @param {?ILayoutAlgorithm} [coreLayout=null] - */ - constructor(coreLayout = null) { - super(coreLayout) - } - /** * The {@link IDataProvider} key to register a data provider that provides the outline and * oriented layout to this stage. @@ -135,7 +127,7 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { } try { const originalDimensions = Maps.createHashedNodeMap() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const { outline, orientedLayout } = boundsProvider.get(node) if (orientedLayout) { // if the current node is rotated: apply fixes @@ -173,7 +165,7 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { ) // for each out edge - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { // create a strong port constraint for the side which is closest to the port location (without rotation) const constraint = sourcePortConstraints.get(edge) if (!constraint) { @@ -182,7 +174,7 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { sourcePortConstraints.set(edge, PortConstraint.create(side, true)) } }) - node.inEdges.forEach(edge => { + node.inEdges.forEach((edge) => { // create a strong port constraint for the side which is closest to the port location (without rotation) const constraint = targetPortConstraints.get(edge) if (!constraint) { @@ -196,12 +188,12 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { // For source and target port constraints: fix the PortSide according to the rotation const angle = Math.atan2(orientedLayout.upY, orientedLayout.upX) if (sourcePortConstraints) { - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { this.fixPortConstraintSide(sourcePortConstraints, edge, angle) }) } if (targetPortConstraints) { - node.inEdges.forEach(edge => { + node.inEdges.forEach((edge) => { this.fixPortConstraintSide(targetPortConstraints, edge, angle) }) } @@ -218,7 +210,7 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { layout.applyLayout(graph) const groups = graph.getDataProvider(GroupingKeys.GROUP_DP_KEY) - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (groups && groups.getBoolean(node)) { // groups don't need to be adjusted to their former size and location because their bounds are entirely // calculated by the layout algorithm and they are not rotated @@ -245,7 +237,7 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { if (this.edgeRoutingMode === 'no-routing') { // NoRouting still needs fix for self-loops - node.edges.forEach(edge => { + node.edges.forEach((edge) => { if (edge.selfLoop) { this.fixPorts(graph, edge, path, false) this.fixPorts(graph, edge, path, true) @@ -260,10 +252,10 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { // enlarge the adjacent segment to the oriented rectangle (represented by the path) // handling in and out edges separately will automatically cause self-loops to be handled correctly - node.inEdges.forEach(edge => { + node.inEdges.forEach((edge) => { this.fixPorts(graph, edge, path, false) }) - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { this.fixPorts(graph, edge, path, true) }) }) diff --git a/demos/application-features/rotatablenodes/RotatedNodeLayoutStage.ts b/demos/application-features/rotatablenodes/RotatedNodeLayoutStage.ts index 2e52c88b8..dbaa068f5 100644 --- a/demos/application-features/rotatablenodes/RotatedNodeLayoutStage.ts +++ b/demos/application-features/rotatablenodes/RotatedNodeLayoutStage.ts @@ -65,13 +65,6 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { public edgeRoutingMode: 'shortest-straight-path-to-border' | 'no-routing' | 'fixed-port' = 'shortest-straight-path-to-border' - /** - * Creates a new instance with an optional core layout algorithm. - */ - constructor(coreLayout: ILayoutAlgorithm | null = null) { - super(coreLayout) - } - /** * The {@link IDataProvider} key to register a data provider that provides the outline and * oriented layout to this stage. @@ -132,7 +125,7 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { } try { const originalDimensions = Maps.createHashedNodeMap() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const { outline, orientedLayout } = boundsProvider.get(node) as { outline: GeneralPath orientedLayout: IOrientedRectangle @@ -173,7 +166,7 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { ) // for each out edge - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { // create a strong port constraint for the side which is closest to the port location (without rotation) const constraint = sourcePortConstraints!.get(edge) if (!constraint) { @@ -182,7 +175,7 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { sourcePortConstraints!.set(edge, PortConstraint.create(side, true)) } }) - node.inEdges.forEach(edge => { + node.inEdges.forEach((edge) => { // create a strong port constraint for the side which is closest to the port location (without rotation) const constraint = targetPortConstraints!.get(edge) if (!constraint) { @@ -196,12 +189,12 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { // For source and target port constraints: fix the PortSide according to the rotation const angle = Math.atan2(orientedLayout.upY, orientedLayout.upX) if (sourcePortConstraints) { - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { this.fixPortConstraintSide(sourcePortConstraints!, edge, angle) }) } if (targetPortConstraints) { - node.inEdges.forEach(edge => { + node.inEdges.forEach((edge) => { this.fixPortConstraintSide(targetPortConstraints!, edge, angle) }) } @@ -218,7 +211,7 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { layout.applyLayout(graph) const groups = graph.getDataProvider(GroupingKeys.GROUP_DP_KEY) - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (groups && groups.getBoolean(node)) { // groups don't need to be adjusted to their former size and location because their bounds are entirely // calculated by the layout algorithm and they are not rotated @@ -245,7 +238,7 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { if (this.edgeRoutingMode === 'no-routing') { // NoRouting still needs fix for self-loops - node.edges.forEach(edge => { + node.edges.forEach((edge) => { if (edge.selfLoop) { this.fixPorts(graph, edge, path, false) this.fixPorts(graph, edge, path, true) @@ -260,10 +253,10 @@ export default class RotatedNodeLayoutStage extends LayoutStageBase { // enlarge the adjacent segment to the oriented rectangle (represented by the path) // handling in and out edges separately will automatically cause self-loops to be handled correctly - node.inEdges.forEach(edge => { + node.inEdges.forEach((edge) => { this.fixPorts(graph, edge, path, false) }) - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { this.fixPorts(graph, edge, path, true) }) }) diff --git a/demos/application-features/rotatablenodes/RotationAwareGroupBoundsCalculator.js b/demos/application-features/rotatablenodes/RotationAwareGroupBoundsCalculator.js index a00277ee6..f38d92868 100644 --- a/demos/application-features/rotatablenodes/RotationAwareGroupBoundsCalculator.js +++ b/demos/application-features/rotatablenodes/RotationAwareGroupBoundsCalculator.js @@ -50,7 +50,7 @@ export default class RotationAwareGroupBoundsCalculator extends BaseClass(IGroup */ calculateBounds(graph, groupNode) { let bounds = Rect.EMPTY - graph.getChildren(groupNode).forEach(node => { + graph.getChildren(groupNode).forEach((node) => { const style = node.style if (style instanceof RotatableNodeStyleDecorator) { // if the node supports rotation: add the outer bounds of the rotated layout diff --git a/demos/application-features/rotatablenodes/RotationAwareGroupBoundsCalculator.ts b/demos/application-features/rotatablenodes/RotationAwareGroupBoundsCalculator.ts index 67a5dc8e8..dd79aeaf8 100644 --- a/demos/application-features/rotatablenodes/RotationAwareGroupBoundsCalculator.ts +++ b/demos/application-features/rotatablenodes/RotationAwareGroupBoundsCalculator.ts @@ -47,7 +47,7 @@ export default class RotationAwareGroupBoundsCalculator extends BaseClass(IGroup */ calculateBounds(graph: IGraph, groupNode: INode): Rect { let bounds = Rect.EMPTY - graph.getChildren(groupNode).forEach(node => { + graph.getChildren(groupNode).forEach((node) => { const style = node.style if (style instanceof RotatableNodeStyleDecorator) { // if the node supports rotation: add the outer bounds of the rotated layout diff --git a/demos/application-features/rotatablenodes/index.html b/demos/application-features/rotatablenodes/index.html index 32e717956..fa5ccd0fe 100644 --- a/demos/application-features/rotatablenodes/index.html +++ b/demos/application-features/rotatablenodes/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/simple-highlight-decorator/SimpleHighlightDecoratorDemo.js b/demos/application-features/simple-highlight-decorator/SimpleHighlightDecoratorDemo.js index 4bbed7a89..2616bef7f 100644 --- a/demos/application-features/simple-highlight-decorator/SimpleHighlightDecoratorDemo.js +++ b/demos/application-features/simple-highlight-decorator/SimpleHighlightDecoratorDemo.js @@ -100,22 +100,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/simple-highlight-decorator/SimpleHighlightDecoratorDemo.ts b/demos/application-features/simple-highlight-decorator/SimpleHighlightDecoratorDemo.ts index 66fa0c259..a0530f3e5 100644 --- a/demos/application-features/simple-highlight-decorator/SimpleHighlightDecoratorDemo.ts +++ b/demos/application-features/simple-highlight-decorator/SimpleHighlightDecoratorDemo.ts @@ -98,22 +98,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/simple-highlight-decorator/index.html b/demos/application-features/simple-highlight-decorator/index.html index 4d2764357..70fea463c 100644 --- a/demos/application-features/simple-highlight-decorator/index.html +++ b/demos/application-features/simple-highlight-decorator/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/smart-click-navigation/index.html b/demos/application-features/smart-click-navigation/index.html index 208440fe2..458c7e122 100644 --- a/demos/application-features/smart-click-navigation/index.html +++ b/demos/application-features/smart-click-navigation/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/snapping/SnappingDemo.js b/demos/application-features/snapping/SnappingDemo.js index c52733ca9..6e43a038b 100644 --- a/demos/application-features/snapping/SnappingDemo.js +++ b/demos/application-features/snapping/SnappingDemo.js @@ -95,22 +95,22 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/snapping/SnappingDemo.ts b/demos/application-features/snapping/SnappingDemo.ts index f16b7602d..18bdff7fb 100644 --- a/demos/application-features/snapping/SnappingDemo.ts +++ b/demos/application-features/snapping/SnappingDemo.ts @@ -92,22 +92,22 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/application-features/snapping/index.html b/demos/application-features/snapping/index.html index caf379506..8af6ed459 100644 --- a/demos/application-features/snapping/index.html +++ b/demos/application-features/snapping/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/subdivide-edges/SubdivideEdgeDropInputMode.js b/demos/application-features/subdivide-edges/SubdivideEdgeDropInputMode.js index 5491ba067..b84fd14d5 100644 --- a/demos/application-features/subdivide-edges/SubdivideEdgeDropInputMode.js +++ b/demos/application-features/subdivide-edges/SubdivideEdgeDropInputMode.js @@ -76,7 +76,7 @@ export class SubdivideEdgeDropInputMode extends NodeDropInputMode { const hitItems = parentMode.findItems( dragLocation, [GraphItemTypes.EDGE], - e => !foldingView?.isInFoldingState(e) + (e) => !foldingView?.isInFoldingState(e) ) hitItem = hitItems.at(0) } @@ -99,7 +99,7 @@ export class SubdivideEdgeDropInputMode extends NodeDropInputMode { if (dropTarget instanceof IEdge) { const groupAtDropLocation = context .lookup(GraphInputMode.$class) - ?.findItems(context, this.dropLocation, [GraphItemTypes.NODE], item => + ?.findItems(context, this.dropLocation, [GraphItemTypes.NODE], (item) => graph.isGroupNode(item) ) ?.at(0) @@ -150,7 +150,7 @@ export class SubdivideEdgeDropInputMode extends NodeDropInputMode { }) // copy the labels of the original edge to the newly created edges - edge.labels.forEach(label => { + edge.labels.forEach((label) => { targetGraph.addLabel( newEdge1, label.text, diff --git a/demos/application-features/subdivide-edges/SubdivideEdgeDropInputMode.ts b/demos/application-features/subdivide-edges/SubdivideEdgeDropInputMode.ts index 0b5bd83f9..1e8062be0 100644 --- a/demos/application-features/subdivide-edges/SubdivideEdgeDropInputMode.ts +++ b/demos/application-features/subdivide-edges/SubdivideEdgeDropInputMode.ts @@ -74,7 +74,7 @@ export class SubdivideEdgeDropInputMode extends NodeDropInputMode { const hitItems = parentMode.findItems( dragLocation, [GraphItemTypes.EDGE], - e => !foldingView?.isInFoldingState(e) + (e) => !foldingView?.isInFoldingState(e) ) hitItem = hitItems.at(0) } @@ -102,7 +102,7 @@ export class SubdivideEdgeDropInputMode extends NodeDropInputMode { if (dropTarget instanceof IEdge) { const groupAtDropLocation = context .lookup(GraphInputMode.$class) - ?.findItems(context, this.dropLocation, [GraphItemTypes.NODE], item => + ?.findItems(context, this.dropLocation, [GraphItemTypes.NODE], (item) => graph.isGroupNode(item as INode) ) ?.at(0) @@ -152,7 +152,7 @@ export class SubdivideEdgeDropInputMode extends NodeDropInputMode { }) // copy the labels of the original edge to the newly created edges - edge.labels.forEach(label => { + edge.labels.forEach((label) => { targetGraph.addLabel( newEdge1, label.text, diff --git a/demos/application-features/subdivide-edges/SubdivideEdgesDemo.js b/demos/application-features/subdivide-edges/SubdivideEdgesDemo.js index 4f205df39..7cc19a476 100644 --- a/demos/application-features/subdivide-edges/SubdivideEdgesDemo.js +++ b/demos/application-features/subdivide-edges/SubdivideEdgesDemo.js @@ -96,7 +96,7 @@ function configureDragAndDrop() { // create and configure the node drop input mode const nodeDropInputMode = new SubdivideEdgeDropInputMode() // nodes with GroupNodeStyle should be created as group nodes - nodeDropInputMode.isGroupNodePredicate = draggedNode => + nodeDropInputMode.isGroupNodePredicate = (draggedNode) => draggedNode.style instanceof GroupNodeStyle // assign the new node input mode to the graphComponent inputMode.nodeDropInputMode = nodeDropInputMode @@ -121,7 +121,7 @@ function initializeDragAndDropPanel() { const nodeStyles = [defaultNodeStyle, otherNodeStyle, defaultGroupNodeStyle] // add a visual for each node style to the palette - nodeStyles.forEach(style => { + nodeStyles.forEach((style) => { addNodeVisual(style, panel) }) } @@ -175,7 +175,7 @@ function addNodeVisual(style, panel) { img.addEventListener( 'mousedown', - event => { + (event) => { startDrag() event.preventDefault() }, @@ -183,7 +183,7 @@ function addNodeVisual(style, panel) { ) img.addEventListener( 'touchstart', - event => { + (event) => { startDrag() event.preventDefault() }, diff --git a/demos/application-features/subdivide-edges/SubdivideEdgesDemo.ts b/demos/application-features/subdivide-edges/SubdivideEdgesDemo.ts index cbf483378..1673ac46b 100644 --- a/demos/application-features/subdivide-edges/SubdivideEdgesDemo.ts +++ b/demos/application-features/subdivide-edges/SubdivideEdgesDemo.ts @@ -94,7 +94,7 @@ function configureDragAndDrop(): void { // create and configure the node drop input mode const nodeDropInputMode = new SubdivideEdgeDropInputMode() // nodes with GroupNodeStyle should be created as group nodes - nodeDropInputMode.isGroupNodePredicate = draggedNode => + nodeDropInputMode.isGroupNodePredicate = (draggedNode) => draggedNode.style instanceof GroupNodeStyle // assign the new node input mode to the graphComponent inputMode.nodeDropInputMode = nodeDropInputMode @@ -119,7 +119,7 @@ function initializeDragAndDropPanel(): void { const nodeStyles = [defaultNodeStyle, otherNodeStyle, defaultGroupNodeStyle] // add a visual for each node style to the palette - nodeStyles.forEach(style => { + nodeStyles.forEach((style) => { addNodeVisual(style, panel) }) } @@ -171,7 +171,7 @@ function addNodeVisual(style: INodeStyle, panel: Element): void { img.addEventListener( 'mousedown', - event => { + (event) => { startDrag() event.preventDefault() }, @@ -179,7 +179,7 @@ function addNodeVisual(style: INodeStyle, panel: Element): void { ) img.addEventListener( 'touchstart', - event => { + (event) => { startDrag() event.preventDefault() }, diff --git a/demos/application-features/subdivide-edges/index.html b/demos/application-features/subdivide-edges/index.html index 4cc578aff..fd77327d7 100644 --- a/demos/application-features/subdivide-edges/index.html +++ b/demos/application-features/subdivide-edges/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/tableeditor/DragAndDropSupport.js b/demos/application-features/tableeditor/DragAndDropSupport.js index 64981656f..c890e65f7 100644 --- a/demos/application-features/tableeditor/DragAndDropSupport.js +++ b/demos/application-features/tableeditor/DragAndDropSupport.js @@ -62,12 +62,12 @@ export function configureDndInputMode(graph) { const nodeDropInputMode = new NodeDropInputMode({ showPreview: true, enabled: true, - isGroupNodePredicate: draggedNode => + isGroupNodePredicate: (draggedNode) => // tables and tagged nodes should be created as group nodes draggedNode.lookup(ITable.$class) !== null || draggedNode.tag === 'GroupNode' }) - nodeDropInputMode.isValidParentPredicate = node => { + nodeDropInputMode.isValidParentPredicate = (node) => { const draggedNode = nodeDropInputMode.lastDragEventArgs.item.getData('yfiles.graph.INode') if (draggedNode.lookup(ITable.$class) !== null && node !== null && graph.isGroupNode(node)) { // this node has a table associated - disallow dragging into a group node. diff --git a/demos/application-features/tableeditor/DragAndDropSupport.ts b/demos/application-features/tableeditor/DragAndDropSupport.ts index 1da64872f..d3457ce9f 100644 --- a/demos/application-features/tableeditor/DragAndDropSupport.ts +++ b/demos/application-features/tableeditor/DragAndDropSupport.ts @@ -60,12 +60,12 @@ export function configureDndInputMode(graph: IGraph): NodeDropInputMode { const nodeDropInputMode = new NodeDropInputMode({ showPreview: true, enabled: true, - isGroupNodePredicate: draggedNode => + isGroupNodePredicate: (draggedNode) => // tables and tagged nodes should be created as group nodes draggedNode.lookup(ITable.$class) !== null || draggedNode.tag === 'GroupNode' }) - nodeDropInputMode.isValidParentPredicate = node => { + nodeDropInputMode.isValidParentPredicate = (node) => { const draggedNode = nodeDropInputMode.lastDragEventArgs!.item.getData('yfiles.graph.INode') if (draggedNode.lookup(ITable.$class) !== null && node !== null && graph.isGroupNode(node)) { // this node has a table associated - disallow dragging into a group node. diff --git a/demos/application-features/tableeditor/TableEditorDemo.js b/demos/application-features/tableeditor/TableEditorDemo.js index c0c991184..643dc1dc9 100644 --- a/demos/application-features/tableeditor/TableEditorDemo.js +++ b/demos/application-features/tableeditor/TableEditorDemo.js @@ -178,16 +178,16 @@ function configureTableEditing() { // provide no candidates for edge creation at pool nodes - this effectively disables // edge creations for those nodes graph.decorator.nodeDecorator.portCandidateProviderDecorator.setImplementation( - node => node.lookup(ITable.$class) !== null, + (node) => node.lookup(ITable.$class) !== null, IPortCandidateProvider.NO_CANDIDATES ) // customize marquee selection handling for pool nodes graph.decorator.nodeDecorator.marqueeTestableDecorator.setFactory( - node => node.lookup(ITable.$class) !== null, + (node) => node.lookup(ITable.$class) !== null, // the marquee testable for pool nodes. The pool node should only be selected by marquee, if the entire bounds are // within the marquee. - node => + (node) => IMarqueeTestable.create((context, box) => { const rectangle = node.layout return box.contains(rectangle.topLeft) && box.contains(rectangle.bottomRight) @@ -248,7 +248,7 @@ function configureContextMenu(tableEditorInputMode) { // add event listeners to the various events that open the context menu. These listeners then // call the provided callback function which in turn asks the current ContextMenuInputMode if a // context menu should be shown at the current location. - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if ( graphInputMode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location)) ) { @@ -313,7 +313,7 @@ function populateContextMenu(contextMenu, args, tableEditorInputMode) { const tableNode = graphInputMode.findItems( args.queryLocation, [GraphItemTypes.NODE], - item => item.lookup(ITable.$class) !== null + (item) => item.lookup(ITable.$class) !== null ) if (tableNode !== null && tableNode.size > 0) { contextMenu.addMenuItem(`ContextMenu for ${tableNode.at(0)}`, null) diff --git a/demos/application-features/tableeditor/TableEditorDemo.ts b/demos/application-features/tableeditor/TableEditorDemo.ts index 2dc10429c..75fce7047 100644 --- a/demos/application-features/tableeditor/TableEditorDemo.ts +++ b/demos/application-features/tableeditor/TableEditorDemo.ts @@ -173,16 +173,16 @@ function configureTableEditing(): TableEditorInputMode { // provide no candidates for edge creation at pool nodes - this effectively disables // edge creations for those nodes graph.decorator.nodeDecorator.portCandidateProviderDecorator.setImplementation( - node => node.lookup(ITable.$class) !== null, + (node) => node.lookup(ITable.$class) !== null, IPortCandidateProvider.NO_CANDIDATES ) // customize marquee selection handling for pool nodes graph.decorator.nodeDecorator.marqueeTestableDecorator.setFactory( - node => node.lookup(ITable.$class) !== null, + (node) => node.lookup(ITable.$class) !== null, // the marquee testable for pool nodes. The pool node should only be selected by marquee, if the entire bounds are // within the marquee. - node => + (node) => IMarqueeTestable.create((context: IInputModeContext, box: Rect): boolean => { const rectangle = node.layout return box.contains(rectangle.topLeft) && box.contains(rectangle.bottomRight) @@ -243,7 +243,7 @@ function configureContextMenu(tableEditorInputMode: TableEditorInputMode): void // add event listeners to the various events that open the context menu. These listeners then // call the provided callback function which in turn asks the current ContextMenuInputMode if a // context menu should be shown at the current location. - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if ( graphInputMode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location)) ) { @@ -312,7 +312,7 @@ function populateContextMenu( const tableNode = graphInputMode.findItems( args.queryLocation, [GraphItemTypes.NODE], - item => item.lookup(ITable.$class) !== null + (item) => item.lookup(ITable.$class) !== null ) if (tableNode !== null && tableNode.size > 0) { contextMenu.addMenuItem(`ContextMenu for ${tableNode.at(0)}`, null) diff --git a/demos/application-features/tableeditor/index.html b/demos/application-features/tableeditor/index.html index 5d66e2a54..1726ac8d1 100644 --- a/demos/application-features/tableeditor/index.html +++ b/demos/application-features/tableeditor/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/theming/ThemingDemo.js b/demos/application-features/theming/ThemingDemo.js index a1474eb45..6fe79cef8 100644 --- a/demos/application-features/theming/ThemingDemo.js +++ b/demos/application-features/theming/ThemingDemo.js @@ -118,26 +118,26 @@ function buildGraph(graph, graphData) { graphBuilder .createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) - .edgeCreator.createLabelBinding(item => item.label) + .edgeCreator.createLabelBinding((item) => item.label) graphBuilder.buildGraph() } diff --git a/demos/application-features/theming/ThemingDemo.ts b/demos/application-features/theming/ThemingDemo.ts index f08efcaa9..868a5fdfc 100644 --- a/demos/application-features/theming/ThemingDemo.ts +++ b/demos/application-features/theming/ThemingDemo.ts @@ -113,26 +113,26 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { graphBuilder .createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) - .edgeCreator.createLabelBinding(item => item.label) + .edgeCreator.createLabelBinding((item) => item.label) graphBuilder.buildGraph() } diff --git a/demos/application-features/theming/index.html b/demos/application-features/theming/index.html index 096d0b736..567998d7a 100644 --- a/demos/application-features/theming/index.html +++ b/demos/application-features/theming/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/tooltips/TooltipsDemo.js b/demos/application-features/tooltips/TooltipsDemo.js index 46fa8244e..29f3cdc64 100644 --- a/demos/application-features/tooltips/TooltipsDemo.js +++ b/demos/application-features/tooltips/TooltipsDemo.js @@ -106,26 +106,26 @@ function buildGraph(graph, graphData) { graphBuilder .createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) - .edgeCreator.createLabelBinding(item => item.label) + .edgeCreator.createLabelBinding((item) => item.label) graphBuilder.buildGraph() } diff --git a/demos/application-features/tooltips/TooltipsDemo.ts b/demos/application-features/tooltips/TooltipsDemo.ts index 2a03968f7..72a99836a 100644 --- a/demos/application-features/tooltips/TooltipsDemo.ts +++ b/demos/application-features/tooltips/TooltipsDemo.ts @@ -103,26 +103,26 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { graphBuilder .createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) - .edgeCreator.createLabelBinding(item => item.label) + .edgeCreator.createLabelBinding((item) => item.label) graphBuilder.buildGraph() } diff --git a/demos/application-features/tooltips/index.html b/demos/application-features/tooltips/index.html index de11670e1..e3f24e6fd 100644 --- a/demos/application-features/tooltips/index.html +++ b/demos/application-features/tooltips/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/application-features/webgl-rendering/WebGLRenderingDemo.js b/demos/application-features/webgl-rendering/WebGLRenderingDemo.js index 6089dad3f..2c68cb45a 100644 --- a/demos/application-features/webgl-rendering/WebGLRenderingDemo.js +++ b/demos/application-features/webgl-rendering/WebGLRenderingDemo.js @@ -147,7 +147,7 @@ async function createGraph(graph) { const response = await fetch('./resources/hierarchic_2000_2100.json') const graphData = await response.json() - const getRandomInt = upper => Math.floor(Math.random() * upper) + const getRandomInt = (upper) => Math.floor(Math.random() * upper) graph.clear() // create a map to store the nodes for edge creation @@ -182,7 +182,7 @@ async function createGraph(graph) { // add the bends if (edgeData.b != null) { const bendData = edgeData.b - bendData.forEach(bend => { + bendData.forEach((bend) => { graph.addBend(edge, Point.from(bend)) }) } diff --git a/demos/application-features/webgl-rendering/WebGLRenderingDemo.ts b/demos/application-features/webgl-rendering/WebGLRenderingDemo.ts index e1c25112a..917042d7e 100644 --- a/demos/application-features/webgl-rendering/WebGLRenderingDemo.ts +++ b/demos/application-features/webgl-rendering/WebGLRenderingDemo.ts @@ -178,7 +178,7 @@ async function createGraph(graph: IGraph) { // add the bends if (edgeData.b != null) { const bendData = edgeData.b as { x: number; y: number }[] - bendData.forEach(bend => { + bendData.forEach((bend) => { graph.addBend(edge, Point.from(bend)) }) } diff --git a/demos/application-features/webgl-rendering/index.html b/demos/application-features/webgl-rendering/index.html index 6fd318e62..036a164ae 100644 --- a/demos/application-features/webgl-rendering/index.html +++ b/demos/application-features/webgl-rendering/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/databinding/adjacencygraphbuilder/EditAdjacencyNodeSourceDialog.js b/demos/databinding/adjacencygraphbuilder/EditAdjacencyNodeSourceDialog.js index a3de42d83..2ba31ad0b 100644 --- a/demos/databinding/adjacencygraphbuilder/EditAdjacencyNodeSourceDialog.js +++ b/demos/databinding/adjacencygraphbuilder/EditAdjacencyNodeSourceDialog.js @@ -122,7 +122,7 @@ export class EditAdjacencyNodesSourceDialog { // CodeMirror requires the textArea to be in the DOM and visible already when instantiating this.dialogContainerModal.style.removeProperty('display') - return new Promise(resolve => { + return new Promise((resolve) => { setTimeout(() => { this.initialize() diff --git a/demos/databinding/adjacencygraphbuilder/EditAdjacencyNodeSourceDialog.ts b/demos/databinding/adjacencygraphbuilder/EditAdjacencyNodeSourceDialog.ts index cc18f2280..f0d05c64e 100644 --- a/demos/databinding/adjacencygraphbuilder/EditAdjacencyNodeSourceDialog.ts +++ b/demos/databinding/adjacencygraphbuilder/EditAdjacencyNodeSourceDialog.ts @@ -126,7 +126,7 @@ export class EditAdjacencyNodesSourceDialog { // CodeMirror requires the textArea to be in the DOM and visible already when instantiating this.dialogContainerModal.style.removeProperty('display') - return new Promise(resolve => { + return new Promise((resolve) => { setTimeout(() => { this.initialize() diff --git a/demos/databinding/adjacencygraphbuilder/ModelClasses.js b/demos/databinding/adjacencygraphbuilder/ModelClasses.js index a4e8f360b..8646415ef 100644 --- a/demos/databinding/adjacencygraphbuilder/ModelClasses.js +++ b/demos/databinding/adjacencygraphbuilder/ModelClasses.js @@ -115,7 +115,7 @@ export function createBinding(bindingString) { const func = new Function(`return (${bindingString})`)() // wrap the binding function with a function that catches and reports errors // that occur in the binding functions - return dataItem => { + return (dataItem) => { try { // eslint-disable-next-line no-useless-call const result = func.apply(null, [dataItem]) @@ -129,10 +129,10 @@ export function createBinding(bindingString) { } } } catch (ignored) { - return dataItem => (bindingString.length > 0 ? dataItem[bindingString] : undefined) + return (dataItem) => (bindingString.length > 0 ? dataItem[bindingString] : undefined) } } - return dataItem => (bindingString.length > 0 ? dataItem[bindingString] : undefined) + return (dataItem) => (bindingString.length > 0 ? dataItem[bindingString] : undefined) } /** diff --git a/demos/databinding/adjacencygraphbuilder/SchemaComponent.js b/demos/databinding/adjacencygraphbuilder/SchemaComponent.js index f18891bbf..b89477415 100644 --- a/demos/databinding/adjacencygraphbuilder/SchemaComponent.js +++ b/demos/databinding/adjacencygraphbuilder/SchemaComponent.js @@ -322,7 +322,7 @@ export class SchemaComponent { const contextMenu = new ContextMenu(graphComponent) - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { const worldLocation = graphComponent.toWorldFromPage(location) const showMenu = inputMode.contextMenuInputMode.shouldOpenMenu(worldLocation) if (showMenu) { @@ -384,20 +384,23 @@ export class SchemaComponent { this.adjacencyGraphBuilder = new AdjacencyGraphBuilder(this.resultGraph) const schemaGraphBuilder = new GraphBuilder(this.schemaGraphComponent.graph) - const schemaNodesSource = schemaGraphBuilder.createNodesSource(sample.nodesSources, n => n.name) + const schemaNodesSource = schemaGraphBuilder.createNodesSource( + sample.nodesSources, + (n) => n.name + ) - schemaNodesSource.nodeCreator.createLabelBinding(n => n.name) + schemaNodesSource.nodeCreator.createLabelBinding((n) => n.name) - schemaNodesSource.nodeCreator.tagProvider = sourceDefinition => + schemaNodesSource.nodeCreator.tagProvider = (sourceDefinition) => this.createAdjacencyNodesSourceConnector(sourceDefinition) - schemaNodesSource.nodeCreator.styleProvider = data => + schemaNodesSource.nodeCreator.styleProvider = (data) => SchemaComponent.createSchemaNodeStyle(data) const schemaEdgesSource = schemaGraphBuilder.createEdgesSource( sample.edgesSource, - e => e.thisSource, - e => e.neighborSource + (e) => e.thisSource, + (e) => e.neighborSource ) schemaEdgesSource.edgeCreator.addEdgeCreatedListener((_, evt) => { this.createNeighborRelationship( @@ -422,14 +425,14 @@ export class SchemaComponent { // gather remaining source definitions const adjacencyNodesSourcesDefinitions = [] - schemaGraph.nodes.forEach(node => { + schemaGraph.nodes.forEach((node) => { const sourceConnector = node.tag adjacencyNodesSourcesDefinitions.push(sourceConnector.sourceDefinition) }) // gather remaining edge definitions const edgesSourceDefinitions = [] - schemaGraph.edges.forEach(edge => { + schemaGraph.edges.forEach((edge) => { const sourceConnector = edge.sourceNode.tag const targetConnector = edge.targetNode.tag @@ -470,7 +473,7 @@ export class SchemaComponent { neighborType } - const neighborProvider = dataItem => edge.tag.binding(dataItem) + const neighborProvider = (dataItem) => edge.tag.binding(dataItem) const neighborSource = targetConnector.nodesSource if (neighborType === 'successor') { @@ -591,7 +594,7 @@ export class SchemaComponent { this.schemaGraphComponent.graph.nodeDefaults.size // each edge creation should use another random target node color - createEdgeInputMode.addGestureStartingListener(src => { + createEdgeInputMode.addGestureStartingListener((src) => { const nodeStyle = new ShapeNodeStyle({ shape: 'ellipse', fill: '#6495ED', diff --git a/demos/databinding/adjacencygraphbuilder/SchemaComponent.ts b/demos/databinding/adjacencygraphbuilder/SchemaComponent.ts index 765471eaf..1a54b18b9 100644 --- a/demos/databinding/adjacencygraphbuilder/SchemaComponent.ts +++ b/demos/databinding/adjacencygraphbuilder/SchemaComponent.ts @@ -372,9 +372,12 @@ export class SchemaComponent { this.adjacencyGraphBuilder = new AdjacencyGraphBuilder(this.resultGraph) const schemaGraphBuilder = new GraphBuilder(this.schemaGraphComponent.graph) - const schemaNodesSource = schemaGraphBuilder.createNodesSource(sample.nodesSources, n => n.name) + const schemaNodesSource = schemaGraphBuilder.createNodesSource( + sample.nodesSources, + (n) => n.name + ) - schemaNodesSource.nodeCreator.createLabelBinding(n => n.name) + schemaNodesSource.nodeCreator.createLabelBinding((n) => n.name) schemaNodesSource.nodeCreator.tagProvider = ( sourceDefinition @@ -386,8 +389,8 @@ export class SchemaComponent { const schemaEdgesSource = schemaGraphBuilder.createEdgesSource( sample.edgesSource, - e => e.thisSource, - e => e.neighborSource + (e) => e.thisSource, + (e) => e.neighborSource ) schemaEdgesSource.edgeCreator.addEdgeCreatedListener((_, evt) => { this.createNeighborRelationship( @@ -584,7 +587,7 @@ export class SchemaComponent { this.schemaGraphComponent.graph.nodeDefaults.size // each edge creation should use another random target node color - createEdgeInputMode.addGestureStartingListener(src => { + createEdgeInputMode.addGestureStartingListener((src) => { const nodeStyle = new ShapeNodeStyle({ shape: 'ellipse', fill: '#6495ED', diff --git a/demos/databinding/adjacencygraphbuilder/index.html b/demos/databinding/adjacencygraphbuilder/index.html index b7dcf1f85..ca0e6d63a 100644 --- a/demos/databinding/adjacencygraphbuilder/index.html +++ b/demos/databinding/adjacencygraphbuilder/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/databinding/graphbuilder/EditSourceDialog.js b/demos/databinding/graphbuilder/EditSourceDialog.js index be1342ce7..d7a6f1a7d 100644 --- a/demos/databinding/graphbuilder/EditSourceDialog.js +++ b/demos/databinding/graphbuilder/EditSourceDialog.js @@ -59,7 +59,7 @@ export class SourceDialog { // CodeMirror requires the textArea to be in the DOM and visible already when instantiating this.dialogContainerModal.style.removeProperty('display') - return new Promise(resolve => { + return new Promise((resolve) => { setTimeout(() => { this.initialize() diff --git a/demos/databinding/graphbuilder/EditSourceDialog.ts b/demos/databinding/graphbuilder/EditSourceDialog.ts index 708965196..95cba1d05 100644 --- a/demos/databinding/graphbuilder/EditSourceDialog.ts +++ b/demos/databinding/graphbuilder/EditSourceDialog.ts @@ -60,7 +60,7 @@ export abstract class SourceDialog { // CodeMirror requires the textArea to be in the DOM and visible already when instantiating this.dialogContainerModal.style.removeProperty('display') - return new Promise(resolve => { + return new Promise((resolve) => { setTimeout(() => { this.initialize() diff --git a/demos/databinding/graphbuilder/GraphBuilderDemo.js b/demos/databinding/graphbuilder/GraphBuilderDemo.js index 7ba14deff..cee27799c 100644 --- a/demos/databinding/graphbuilder/GraphBuilderDemo.js +++ b/demos/databinding/graphbuilder/GraphBuilderDemo.js @@ -227,7 +227,7 @@ function loadSample(sample) { const { nodesSourcesListBox, edgesSourcesListBox } = createSourcesLists(sourcesFactory) - sampleClone.nodesSources.forEach(nodesSourceDefinition => { + sampleClone.nodesSources.forEach((nodesSourceDefinition) => { const connector = sourcesFactory.createNodesSourceConnector( nodesSourceDefinition.name, nodesSourceDefinition @@ -236,7 +236,7 @@ function loadSample(sample) { nodesSourcesListBox.addDefinition(connector) }) - sampleClone.edgesSources.forEach(edgesSourceDefinition => { + sampleClone.edgesSources.forEach((edgesSourceDefinition) => { const connector = sourcesFactory.createEdgesSourceConnector( edgesSourceDefinition.name, edgesSourceDefinition @@ -279,7 +279,7 @@ function createSourcesLists(sourcesFactory) { removeAllChildren(edgesSourcesListRootElement) const nodesSourcesListBox = new SourcesListBox( - sourceName => sourcesFactory.createNodesSourceConnector(sourceName), + (sourceName) => sourcesFactory.createNodesSourceConnector(sourceName), NodesSourceDialog, nodeSourcesListRootElement, () => { @@ -288,7 +288,7 @@ function createSourcesLists(sourcesFactory) { ) const edgesSourcesListBox = new SourcesListBox( - sourceName => sourcesFactory.createEdgesSourceConnector(sourceName), + (sourceName) => sourcesFactory.createEdgesSourceConnector(sourceName), EdgesSourceDialog, edgesSourcesListRootElement, () => { diff --git a/demos/databinding/graphbuilder/GraphBuilderDemo.ts b/demos/databinding/graphbuilder/GraphBuilderDemo.ts index 8a622358e..567dc26bc 100644 --- a/demos/databinding/graphbuilder/GraphBuilderDemo.ts +++ b/demos/databinding/graphbuilder/GraphBuilderDemo.ts @@ -222,7 +222,7 @@ function loadSample(sample: GraphBuilderSample): void { const { nodesSourcesListBox, edgesSourcesListBox } = createSourcesLists(sourcesFactory) - sampleClone.nodesSources.forEach(nodesSourceDefinition => { + sampleClone.nodesSources.forEach((nodesSourceDefinition) => { const connector = sourcesFactory.createNodesSourceConnector( nodesSourceDefinition.name, nodesSourceDefinition @@ -231,7 +231,7 @@ function loadSample(sample: GraphBuilderSample): void { nodesSourcesListBox.addDefinition(connector) }) - sampleClone.edgesSources.forEach(edgesSourceDefinition => { + sampleClone.edgesSources.forEach((edgesSourceDefinition) => { const connector = sourcesFactory.createEdgesSourceConnector( edgesSourceDefinition.name, edgesSourceDefinition @@ -273,7 +273,7 @@ function createSourcesLists(sourcesFactory: SourcesFactory): { removeAllChildren(edgesSourcesListRootElement) const nodesSourcesListBox = new SourcesListBox( - sourceName => sourcesFactory.createNodesSourceConnector(sourceName), + (sourceName) => sourcesFactory.createNodesSourceConnector(sourceName), NodesSourceDialog, nodeSourcesListRootElement, () => { @@ -282,7 +282,7 @@ function createSourcesLists(sourcesFactory: SourcesFactory): { ) const edgesSourcesListBox = new SourcesListBox( - sourceName => sourcesFactory.createEdgesSourceConnector(sourceName), + (sourceName) => sourcesFactory.createEdgesSourceConnector(sourceName), EdgesSourceDialog, edgesSourcesListRootElement, () => { diff --git a/demos/databinding/graphbuilder/ModelClasses.js b/demos/databinding/graphbuilder/ModelClasses.js index 48d75d5fd..aba0174dd 100644 --- a/demos/databinding/graphbuilder/ModelClasses.js +++ b/demos/databinding/graphbuilder/ModelClasses.js @@ -219,7 +219,7 @@ function createBinding(bindingString) { // wrap the binding function with a function that catches and reports errors // that occur in the binding functions - return dataItem => { + return (dataItem) => { try { // eslint-disable-next-line no-useless-call const result = func.apply(null, [dataItem]) @@ -233,10 +233,10 @@ function createBinding(bindingString) { } } } catch (ignored) { - return dataItem => (bindingString.length > 0 ? dataItem[bindingString] : undefined) + return (dataItem) => (bindingString.length > 0 ? dataItem[bindingString] : undefined) } } - return dataItem => (bindingString.length > 0 ? dataItem[bindingString] : undefined) + return (dataItem) => (bindingString.length > 0 ? dataItem[bindingString] : undefined) } /** @@ -315,9 +315,9 @@ export class SourcesFactory { const edgesSource = this.graphBuilder.createEdgesSource( [], - edgeDataItem => + (edgeDataItem) => definition.sourceProvider ? definition.sourceProvider(edgeDataItem) : undefined, - edgeDataItem => + (edgeDataItem) => definition.targetProvider ? definition.targetProvider(edgeDataItem) : undefined ) @@ -327,7 +327,7 @@ export class SourcesFactory { targetArrow: new Arrow({ color: '#662b00', type: 'triangle' }) }) edgeCreator.defaults.shareStyleInstance = false - edgeCreator.styleBindings.addBinding('stroke', edgeDataItem => + edgeCreator.styleBindings.addBinding('stroke', (edgeDataItem) => definition.strokeProvider ? definition.strokeProvider(edgeDataItem) : '#662b00' ) @@ -336,7 +336,7 @@ export class SourcesFactory { edgeCreator.updateLabels(evt.graph, evt.item, evt.dataItem) }) - edgeCreator.createLabelBinding(edgeDataItem => + edgeCreator.createLabelBinding((edgeDataItem) => definition.labelTextProvider ? definition.labelTextProvider(edgeDataItem) : undefined ) diff --git a/demos/databinding/graphbuilder/ModelClasses.ts b/demos/databinding/graphbuilder/ModelClasses.ts index b5a74bdd3..fb5b05bd9 100644 --- a/demos/databinding/graphbuilder/ModelClasses.ts +++ b/demos/databinding/graphbuilder/ModelClasses.ts @@ -320,9 +320,9 @@ export class SourcesFactory { const edgesSource = this.graphBuilder.createEdgesSource( [], - edgeDataItem => + (edgeDataItem) => definition.sourceProvider ? definition.sourceProvider(edgeDataItem) : undefined, - edgeDataItem => + (edgeDataItem) => definition.targetProvider ? definition.targetProvider(edgeDataItem) : undefined ) @@ -332,7 +332,7 @@ export class SourcesFactory { targetArrow: new Arrow({ color: '#662b00', type: 'triangle' }) }) edgeCreator.defaults.shareStyleInstance = false - edgeCreator.styleBindings.addBinding('stroke', edgeDataItem => + edgeCreator.styleBindings.addBinding('stroke', (edgeDataItem) => definition.strokeProvider ? definition.strokeProvider(edgeDataItem) : '#662b00' ) @@ -341,7 +341,7 @@ export class SourcesFactory { edgeCreator.updateLabels(evt.graph, evt.item, evt.dataItem) }) - edgeCreator.createLabelBinding(edgeDataItem => + edgeCreator.createLabelBinding((edgeDataItem) => definition.labelTextProvider ? definition.labelTextProvider(edgeDataItem) : undefined ) diff --git a/demos/databinding/graphbuilder/SourcesListBox.js b/demos/databinding/graphbuilder/SourcesListBox.js index bc175cf2a..b7f0131e2 100644 --- a/demos/databinding/graphbuilder/SourcesListBox.js +++ b/demos/databinding/graphbuilder/SourcesListBox.js @@ -46,7 +46,7 @@ export class SourcesListBox { * connector via the factory class * @param {!object} dialogFactory the Node- or Edge- {@link SourceDialog} to use * @param {!HTMLElement} rootElement the HTMLElement used to display the list box - * @param {!function} dataUpdatedCallback the callback arrow function used to update the graph after the + * @param {!function} dataUpdatedCallback the callback arrow function used to update the graph after * the SourceDialog was closed as accepted */ constructor(factory, dialogFactory, rootElement, dataUpdatedCallback) { @@ -87,11 +87,12 @@ export class SourcesListBox { const label = document.createElement('span') label.textContent = newDefinition.sourceDefinition.name + label.classList.add('sourceLabel') const editButton = document.createElement('button') editButton.classList.add('editButton') editButton.addEventListener('click', () => { - new this.DialogFactory(newDefinition, () => { + void new this.DialogFactory(newDefinition, () => { label.textContent = newDefinition.sourceDefinition.name this.dataUpdatedCallback() }).show() diff --git a/demos/databinding/graphbuilder/SourcesListBox.ts b/demos/databinding/graphbuilder/SourcesListBox.ts index 3cfe5f72b..f0cd17f7e 100644 --- a/demos/databinding/graphbuilder/SourcesListBox.ts +++ b/demos/databinding/graphbuilder/SourcesListBox.ts @@ -53,7 +53,7 @@ export class SourcesListBox< * connector via the factory class * @param dialogFactory the Node- or Edge- {@link SourceDialog} to use * @param rootElement the HTMLElement used to display the list box - * @param dataUpdatedCallback the callback arrow function used to update the graph after the + * @param dataUpdatedCallback the callback arrow function used to update the graph after * the SourceDialog was closed as accepted */ constructor( @@ -100,11 +100,12 @@ export class SourcesListBox< const label = document.createElement('span') label.textContent = newDefinition.sourceDefinition.name + label.classList.add('sourceLabel') const editButton = document.createElement('button') editButton.classList.add('editButton') editButton.addEventListener('click', () => { - new this.DialogFactory(newDefinition, () => { + void new this.DialogFactory(newDefinition, () => { label.textContent = newDefinition.sourceDefinition.name this.dataUpdatedCallback() }).show() diff --git a/demos/databinding/graphbuilder/index.html b/demos/databinding/graphbuilder/index.html index 733e559a1..70b79139c 100644 --- a/demos/databinding/graphbuilder/index.html +++ b/demos/databinding/graphbuilder/index.html @@ -1,4 +1,4 @@ - + @@ -103,19 +103,19 @@ padding: 10px; margin: 0 0 5px; position: relative; + display: flex; + align-items: baseline; } - .sourceCard button { - position: absolute; - width: 70px; - top: 10px; + .sourceCard .sourceLabel { + flex: 1 0 auto; } - - .sourceCard .editButton { - right: 90px; + .sourceCard button.editButton { + width: 80px; } - .sourceCard .removeButton { - right: 10px; + .sourceCard button.removeButton { + width: 80px; + margin-right: 0; } .buttonsContainer { diff --git a/demos/databinding/port-aware-adjacency-graph-builder/AdjacencyGraphBuilder.js b/demos/databinding/port-aware-adjacency-graph-builder/AdjacencyGraphBuilder.js index 1c7d98dc4..e2fa4c19e 100644 --- a/demos/databinding/port-aware-adjacency-graph-builder/AdjacencyGraphBuilder.js +++ b/demos/databinding/port-aware-adjacency-graph-builder/AdjacencyGraphBuilder.js @@ -418,12 +418,12 @@ class PortAwareEdgeCreator extends EdgeCreator { // if no ID is specified: get the first port const sourcePortId = this.getSourcePortId(data) const sourcePort = sourcePortId - ? source.ports.find(p => p.tag === sourcePortId) + ? source.ports.find((p) => p.tag === sourcePortId) : source.ports.at(0) // same for the target port const targetPortId = this.getTargetPortId(data) const targetPort = targetPortId - ? target.ports.find(p => p.tag === targetPortId) + ? target.ports.find((p) => p.tag === targetPortId) : target.ports.at(0) // create the edges between source and target port. if no port is provided, add a default port. @@ -455,13 +455,13 @@ class PortAwareEdgeCreator extends EdgeCreator { const sourcePortId = this.getSourcePortId(data) const sourcePort = sourcePortId && sourcePortId !== edge.sourcePort.tag - ? edge.sourcePort.owner.ports.find(p => p.tag === sourcePortId) + ? edge.sourcePort.owner.ports.find((p) => p.tag === sourcePortId) : edge.sourcePort // same for the target port const targetPortId = this.getTargetPortId(data) const targetPort = targetPortId && targetPortId !== edge.targetPort.tag - ? edge.targetPort.owner.ports.find(p => p.tag === targetPortId) + ? edge.targetPort.owner.ports.find((p) => p.tag === targetPortId) : edge.targetPort // remember the current source and target ports const oldSource = edge.sourcePort diff --git a/demos/databinding/port-aware-adjacency-graph-builder/AdjacencyGraphBuilder.ts b/demos/databinding/port-aware-adjacency-graph-builder/AdjacencyGraphBuilder.ts index e69daa8e7..eed13e16f 100644 --- a/demos/databinding/port-aware-adjacency-graph-builder/AdjacencyGraphBuilder.ts +++ b/demos/databinding/port-aware-adjacency-graph-builder/AdjacencyGraphBuilder.ts @@ -383,7 +383,10 @@ class PortAwareNodeCreator extends NodeCreator { * Determines the port with that ID and connects the edge to that port. */ class PortAwareEdgeCreator extends EdgeCreator { - constructor(private successor: boolean, defaults: IEdgeDefaults) { + constructor( + private successor: boolean, + defaults: IEdgeDefaults + ) { super() this.defaults = defaults } @@ -409,12 +412,12 @@ class PortAwareEdgeCreator extends EdgeCreator { // if no ID is specified: get the first port const sourcePortId = this.getSourcePortId(data) const sourcePort = sourcePortId - ? source.ports.find(p => p.tag === sourcePortId) + ? source.ports.find((p) => p.tag === sourcePortId) : source.ports.at(0) // same for the target port const targetPortId = this.getTargetPortId(data) const targetPort = targetPortId - ? target.ports.find(p => p.tag === targetPortId) + ? target.ports.find((p) => p.tag === targetPortId) : target.ports.at(0) // create the edges between source and target port. if no port is provided, add a default port. @@ -446,13 +449,13 @@ class PortAwareEdgeCreator extends EdgeCreator { const sourcePortId = this.getSourcePortId(data) const sourcePort = sourcePortId && sourcePortId !== edge.sourcePort!.tag - ? edge.sourcePort!.owner!.ports.find(p => p.tag === sourcePortId) + ? edge.sourcePort!.owner!.ports.find((p) => p.tag === sourcePortId) : edge.sourcePort // same for the target port const targetPortId = this.getTargetPortId(data) const targetPort = targetPortId && targetPortId !== edge.targetPort!.tag - ? edge.targetPort!.owner!.ports.find(p => p.tag === targetPortId) + ? edge.targetPort!.owner!.ports.find((p) => p.tag === targetPortId) : edge.targetPort // remember the current source and target ports const oldSource = edge.sourcePort! diff --git a/demos/databinding/port-aware-adjacency-graph-builder/index.html b/demos/databinding/port-aware-adjacency-graph-builder/index.html index 6925fa32d..86d8093a5 100644 --- a/demos/databinding/port-aware-adjacency-graph-builder/index.html +++ b/demos/databinding/port-aware-adjacency-graph-builder/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/databinding/port-aware-graph-builder/GraphBuilder.js b/demos/databinding/port-aware-graph-builder/GraphBuilder.js index 5020f2b49..295310963 100644 --- a/demos/databinding/port-aware-graph-builder/GraphBuilder.js +++ b/demos/databinding/port-aware-graph-builder/GraphBuilder.js @@ -105,7 +105,7 @@ export function createPortAwareGraphBuilder(graph, sampleNodes, sampleEdges) { }) // change the node creator but keep the original's defaults nodesSource.nodeCreator = new TypeAwareNodeCreator({ defaults: nodesSource.nodeCreator.defaults }) - nodesSource.nodeCreator.styleProvider = nodeData => { + nodesSource.nodeCreator.styleProvider = (nodeData) => { switch (nodeData.type) { case 'or': return new OrNodeStyle(false, '#A49966', '#625F50', '#FFFFFF') @@ -132,8 +132,8 @@ export function createPortAwareGraphBuilder(graph, sampleNodes, sampleEdges) { data: sampleEdges, // we extract the node Id from source Id and target Id // that way EdgeCreator.createEdgeCore will already get the correct source and target nodes - sourceId: data => getNodeId(data.from), - targetId: data => getNodeId(data.to), + sourceId: (data) => getNodeId(data.from), + targetId: (data) => getNodeId(data.to), id: 'id' }) edgesSource.edgeCreator = new PortAwareEdgeCreator({ defaults: edgesSource.edgeCreator.defaults }) @@ -188,7 +188,7 @@ class TypeAwareNodeCreator extends NodeCreator { const node = super.createNodeCore(graph, groupNode, parent, layout, style, tag) // add ports according to their type - this.getPorts(nodeData).forEach(pin => { + this.getPorts(nodeData).forEach((pin) => { this.addPort(graph, node, pin) }) return node @@ -392,11 +392,11 @@ class PortAwareEdgeCreator extends EdgeCreator { // if no ID is specified: get the first port const sourcePortId = this.getSourcePortId(edgeData) const sourcePort = sourcePortId - ? source.ports.find(p => p.tag === sourcePortId) + ? source.ports.find((p) => p.tag === sourcePortId) : source.ports.at(0) const targetPortId = this.getTargetPortId(edgeData) const targetPort = targetPortId - ? target.ports.find(p => p.tag === targetPortId) + ? target.ports.find((p) => p.tag === targetPortId) : target.ports.at(0) // create the edges between source and target port. If no port is provided add a default port. @@ -424,13 +424,13 @@ class PortAwareEdgeCreator extends EdgeCreator { const sourcePortId = this.getSourcePortId(dataItem) const sourcePort = sourcePortId && sourcePortId !== edge.sourcePort.tag - ? edge.sourcePort.owner.ports.find(p => p.tag === sourcePortId) + ? edge.sourcePort.owner.ports.find((p) => p.tag === sourcePortId) : edge.sourcePort // same for the target port const targetPortId = this.getTargetPortId(dataItem) const targetPort = targetPortId && targetPortId !== edge.targetPort.tag - ? edge.targetPort.owner.ports.find(p => p.tag === targetPortId) + ? edge.targetPort.owner.ports.find((p) => p.tag === targetPortId) : edge.targetPort // remember the current source and target ports const oldSource = edge.sourcePort diff --git a/demos/databinding/port-aware-graph-builder/GraphBuilder.ts b/demos/databinding/port-aware-graph-builder/GraphBuilder.ts index db1084939..58034aacf 100644 --- a/demos/databinding/port-aware-graph-builder/GraphBuilder.ts +++ b/demos/databinding/port-aware-graph-builder/GraphBuilder.ts @@ -111,7 +111,7 @@ export function createPortAwareGraphBuilder( }) // change the node creator but keep the original's defaults nodesSource.nodeCreator = new TypeAwareNodeCreator({ defaults: nodesSource.nodeCreator.defaults }) - nodesSource.nodeCreator.styleProvider = nodeData => { + nodesSource.nodeCreator.styleProvider = (nodeData) => { switch (nodeData.type) { case 'or': return new OrNodeStyle(false, '#A49966', '#625F50', '#FFFFFF') @@ -138,8 +138,8 @@ export function createPortAwareGraphBuilder( data: sampleEdges, // we extract the node Id from source Id and target Id // that way EdgeCreator.createEdgeCore will already get the correct source and target nodes - sourceId: data => getNodeId(data.from), - targetId: data => getNodeId(data.to), + sourceId: (data) => getNodeId(data.from), + targetId: (data) => getNodeId(data.to), id: 'id' }) edgesSource.edgeCreator = new PortAwareEdgeCreator({ defaults: edgesSource.edgeCreator.defaults }) @@ -193,7 +193,7 @@ class TypeAwareNodeCreator extends NodeCreator { const node = super.createNodeCore(graph, groupNode, parent, layout, style, tag) // add ports according to their type - this.getPorts(nodeData).forEach(pin => { + this.getPorts(nodeData).forEach((pin) => { this.addPort(graph, node, pin) }) return node @@ -391,11 +391,11 @@ class PortAwareEdgeCreator extends EdgeCreator { // if no ID is specified: get the first port const sourcePortId = this.getSourcePortId(edgeData) const sourcePort = sourcePortId - ? source.ports.find(p => p.tag === sourcePortId) + ? source.ports.find((p) => p.tag === sourcePortId) : source.ports.at(0) const targetPortId = this.getTargetPortId(edgeData) const targetPort = targetPortId - ? target.ports.find(p => p.tag === targetPortId) + ? target.ports.find((p) => p.tag === targetPortId) : target.ports.at(0) // create the edges between source and target port. If no port is provided add a default port. @@ -423,13 +423,13 @@ class PortAwareEdgeCreator extends EdgeCreator { const sourcePortId = this.getSourcePortId(dataItem) const sourcePort = sourcePortId && sourcePortId !== edge.sourcePort!.tag - ? edge.sourcePort!.owner!.ports.find(p => p.tag === sourcePortId) + ? edge.sourcePort!.owner!.ports.find((p) => p.tag === sourcePortId) : edge.sourcePort // same for the target port const targetPortId = this.getTargetPortId(dataItem) const targetPort = targetPortId && targetPortId !== edge.targetPort!.tag - ? edge.targetPort!.owner!.ports.find(p => p.tag === targetPortId) + ? edge.targetPort!.owner!.ports.find((p) => p.tag === targetPortId) : edge.targetPort // remember the current source and target ports const oldSource = edge.sourcePort! diff --git a/demos/databinding/port-aware-graph-builder/index.html b/demos/databinding/port-aware-graph-builder/index.html index abd7d7b9d..a44204b1a 100644 --- a/demos/databinding/port-aware-graph-builder/index.html +++ b/demos/databinding/port-aware-graph-builder/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/databinding/port-aware-tree-builder/TreeBuilder.js b/demos/databinding/port-aware-tree-builder/TreeBuilder.js index 0f00060e2..372299278 100644 --- a/demos/databinding/port-aware-tree-builder/TreeBuilder.js +++ b/demos/databinding/port-aware-tree-builder/TreeBuilder.js @@ -383,12 +383,12 @@ class PortAwareEdgeCreator extends EdgeCreator { // if no ID is specified: get the first port const sourcePortId = this.getSourcePortId(data) const sourcePort = sourcePortId - ? source.ports.find(p => p.tag === sourcePortId) + ? source.ports.find((p) => p.tag === sourcePortId) : source.ports.at(0) // same for the target port const targetPortId = this.getTargetPortId(data) const targetPort = targetPortId - ? target.ports.find(p => p.tag === targetPortId) + ? target.ports.find((p) => p.tag === targetPortId) : target.ports.at(0) // create the edges between source and target port. if no port is provided, add a default port. @@ -416,13 +416,13 @@ class PortAwareEdgeCreator extends EdgeCreator { const sourcePortId = this.getSourcePortId(dataItem) const sourcePort = sourcePortId && sourcePortId !== edge.sourcePort.tag - ? edge.sourcePort.owner.ports.find(p => p.tag === sourcePortId) + ? edge.sourcePort.owner.ports.find((p) => p.tag === sourcePortId) : edge.sourcePort // same for the target port const targetPortId = this.getTargetPortId(dataItem) const targetPort = targetPortId && targetPortId !== edge.targetPort.tag - ? edge.targetPort.owner.ports.find(p => p.tag === targetPortId) + ? edge.targetPort.owner.ports.find((p) => p.tag === targetPortId) : edge.targetPort // remember the current source and target ports const oldSource = edge.sourcePort diff --git a/demos/databinding/port-aware-tree-builder/TreeBuilder.ts b/demos/databinding/port-aware-tree-builder/TreeBuilder.ts index 6fecb3efb..be15e51ff 100644 --- a/demos/databinding/port-aware-tree-builder/TreeBuilder.ts +++ b/demos/databinding/port-aware-tree-builder/TreeBuilder.ts @@ -378,12 +378,12 @@ class PortAwareEdgeCreator extends EdgeCreator { // if no ID is specified: get the first port const sourcePortId = this.getSourcePortId(data) const sourcePort = sourcePortId - ? source.ports.find(p => p.tag === sourcePortId) + ? source.ports.find((p) => p.tag === sourcePortId) : source.ports.at(0) // same for the target port const targetPortId = this.getTargetPortId(data) const targetPort = targetPortId - ? target.ports.find(p => p.tag === targetPortId) + ? target.ports.find((p) => p.tag === targetPortId) : target.ports.at(0) // create the edges between source and target port. if no port is provided, add a default port. @@ -411,13 +411,13 @@ class PortAwareEdgeCreator extends EdgeCreator { const sourcePortId = this.getSourcePortId(dataItem) const sourcePort = sourcePortId && sourcePortId !== edge.sourcePort!.tag - ? edge.sourcePort!.owner!.ports.find(p => p.tag === sourcePortId) + ? edge.sourcePort!.owner!.ports.find((p) => p.tag === sourcePortId) : edge.sourcePort // same for the target port const targetPortId = this.getTargetPortId(dataItem) const targetPort = targetPortId && targetPortId !== edge.targetPort!.tag - ? edge.targetPort!.owner!.ports.find(p => p.tag === targetPortId) + ? edge.targetPort!.owner!.ports.find((p) => p.tag === targetPortId) : edge.targetPort // remember the current source and target ports const oldSource = edge.sourcePort! diff --git a/demos/databinding/port-aware-tree-builder/index.html b/demos/databinding/port-aware-tree-builder/index.html index 5e37623e9..7d58f0eca 100644 --- a/demos/databinding/port-aware-tree-builder/index.html +++ b/demos/databinding/port-aware-tree-builder/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/databinding/simplegraphbuilder/SimpleGraphBuilderDemo.js b/demos/databinding/simplegraphbuilder/SimpleGraphBuilderDemo.js index 17d99705f..781c6ec47 100644 --- a/demos/databinding/simplegraphbuilder/SimpleGraphBuilderDemo.js +++ b/demos/databinding/simplegraphbuilder/SimpleGraphBuilderDemo.js @@ -164,7 +164,7 @@ function createGraphBuilder(graph) { parentId: 'parentGroup' }) // Add some labels to the group nodes - groupsSource.nodeCreator.createLabelBinding(group => group.id) + groupsSource.nodeCreator.createLabelBinding((group) => group.id) graphBuilder.createEdgesSource({ // stores the edges of the graph @@ -206,12 +206,12 @@ function createGraphBuilderWithImplicitGrouping(graph) { // The children of each group are defined directly in the data const childSource = nodesSource.createChildNodesSource( // specifies how to retrieve the children for each group - group => group.members, + (group) => group.members, // specifies how the child nodes are identified globally - item => item.id + (item) => item.id ) // And the groups are additionally grouped again by location - const parentSource = nodesSource.createParentNodesSource(group => group.location) + const parentSource = nodesSource.createParentNodesSource((group) => group.location) // We want to set up reasonable defaults for the styles. // Since the entities in the nodesSource and the parentsSource are both group nodes, they @@ -219,8 +219,8 @@ function createGraphBuilderWithImplicitGrouping(graph) { nodesSource.nodeCreator.defaults.style = parentSource.nodeCreator.defaults.style = graph.groupNodeDefaults.style // We also show labels for the groups - nodesSource.nodeCreator.createLabelBinding(group => group.id) - parentSource.nodeCreator.createLabelBinding(location => location) + nodesSource.nodeCreator.createLabelBinding((group) => group.id) + parentSource.nodeCreator.createLabelBinding((location) => location) nodesSource.nodeCreator.defaults.labels = parentSource.nodeCreator.defaults.labels = graph.groupNodeDefaults.labels // The nodes in the childSource are just plain leaf nodes and are styles with a normal node style @@ -260,7 +260,7 @@ function createTreeBuilder(graph, builderType) { // identifies the property of a node object that contains its child nodes const rootNodesSource = treeBuilder.createRootNodesSource(nodesSource) // configure the recursive tree structure - rootNodesSource.addChildNodesSource(data => data.children, rootNodesSource) + rootNodesSource.addChildNodesSource((data) => data.children, rootNodesSource) return treeBuilder } @@ -285,7 +285,7 @@ function createAdjacencyGraphBuilder(graph, builderType) { // configure the successor nodes adjacencyNodesSource.addSuccessorsSource( - data => data.children, + (data) => data.children, adjacencyNodesSource, new EdgeCreator({ defaults: graph.edgeDefaults }) ) @@ -300,7 +300,7 @@ function createAdjacencyGraphBuilder(graph, builderType) { ) // Configure the successor nodes adjacencyNodesSource.addSuccessorIds( - data => data.children, + (data) => data.children, new EdgeCreator({ defaults: graph.edgeDefaults }) ) } @@ -361,7 +361,7 @@ async function arrangeGraph(graphComponent) { * @param {!GraphComponent} graphComponent */ function initializeUI(graphComponent) { - selectBox.addEventListener('change', async e => { + selectBox.addEventListener('change', async (e) => { // build graph from new data selectBox.disabled = true buildGraph(graphComponent.graph, e.target.value) diff --git a/demos/databinding/simplegraphbuilder/SimpleGraphBuilderDemo.ts b/demos/databinding/simplegraphbuilder/SimpleGraphBuilderDemo.ts index 994bd2103..f3d112bd8 100644 --- a/demos/databinding/simplegraphbuilder/SimpleGraphBuilderDemo.ts +++ b/demos/databinding/simplegraphbuilder/SimpleGraphBuilderDemo.ts @@ -160,7 +160,7 @@ function createGraphBuilder(graph: IGraph): GraphBuilder { parentId: 'parentGroup' }) // Add some labels to the group nodes - groupsSource.nodeCreator.createLabelBinding(group => group.id) + groupsSource.nodeCreator.createLabelBinding((group) => group.id) graphBuilder.createEdgesSource({ // stores the edges of the graph @@ -200,12 +200,12 @@ function createGraphBuilderWithImplicitGrouping(graph: IGraph): GraphBuilder { // The children of each group are defined directly in the data const childSource = nodesSource.createChildNodesSource( // specifies how to retrieve the children for each group - group => group.members, + (group) => group.members, // specifies how the child nodes are identified globally - item => item.id + (item) => item.id ) // And the groups are additionally grouped again by location - const parentSource = nodesSource.createParentNodesSource(group => group.location) + const parentSource = nodesSource.createParentNodesSource((group) => group.location) // We want to set up reasonable defaults for the styles. // Since the entities in the nodesSource and the parentsSource are both group nodes, they @@ -213,8 +213,8 @@ function createGraphBuilderWithImplicitGrouping(graph: IGraph): GraphBuilder { nodesSource.nodeCreator.defaults.style = parentSource.nodeCreator.defaults.style = graph.groupNodeDefaults.style // We also show labels for the groups - nodesSource.nodeCreator.createLabelBinding(group => group.id) - parentSource.nodeCreator.createLabelBinding(location => location) + nodesSource.nodeCreator.createLabelBinding((group) => group.id) + parentSource.nodeCreator.createLabelBinding((location) => location) nodesSource.nodeCreator.defaults.labels = parentSource.nodeCreator.defaults.labels = graph.groupNodeDefaults.labels // The nodes in the childSource are just plain leaf nodes and are styles with a normal node style @@ -344,7 +344,7 @@ async function arrangeGraph(graphComponent: GraphComponent): Promise { * Registers the actions for the toolbar buttons during the creation of this application. */ function initializeUI(graphComponent: GraphComponent): void { - selectBox.addEventListener('change', async e => { + selectBox.addEventListener('change', async (e) => { // build graph from new data selectBox.disabled = true buildGraph(graphComponent.graph, (e.target as HTMLSelectElement).value) diff --git a/demos/databinding/simplegraphbuilder/data-view.css b/demos/databinding/simplegraphbuilder/data-view.css index 89921244d..c05afcbb6 100644 --- a/demos/databinding/simplegraphbuilder/data-view.css +++ b/demos/databinding/simplegraphbuilder/data-view.css @@ -29,7 +29,9 @@ #data-view { width: 400px; height: 340px; - transition: width 1s, height 1s; + transition: + width 1s, + height 1s; font-size: 13px; } #data-view.collapsed { diff --git a/demos/databinding/simplegraphbuilder/index.html b/demos/databinding/simplegraphbuilder/index.html index c337b3e59..1fd7e13e9 100644 --- a/demos/databinding/simplegraphbuilder/index.html +++ b/demos/databinding/simplegraphbuilder/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/databinding/treebuilder/EditTreeNodeSourceDialog.js b/demos/databinding/treebuilder/EditTreeNodeSourceDialog.js index 3088a2456..73c5785ec 100644 --- a/demos/databinding/treebuilder/EditTreeNodeSourceDialog.js +++ b/demos/databinding/treebuilder/EditTreeNodeSourceDialog.js @@ -123,7 +123,7 @@ export class EditTreeNodesSourceDialog { // CodeMirror requires the textArea to be in the DOM and visible already when instantiating this.dialogContainerModal.style.removeProperty('display') - return new Promise(resolve => { + return new Promise((resolve) => { setTimeout(() => { this.initialize() diff --git a/demos/databinding/treebuilder/EditTreeNodeSourceDialog.ts b/demos/databinding/treebuilder/EditTreeNodeSourceDialog.ts index bb10d7537..e3bb57cef 100644 --- a/demos/databinding/treebuilder/EditTreeNodeSourceDialog.ts +++ b/demos/databinding/treebuilder/EditTreeNodeSourceDialog.ts @@ -127,7 +127,7 @@ export class EditTreeNodesSourceDialog { // CodeMirror requires the textArea to be in the DOM and visible already when instantiating this.dialogContainerModal.style.removeProperty('display') - return new Promise(resolve => { + return new Promise((resolve) => { setTimeout(() => { this.initialize() diff --git a/demos/databinding/treebuilder/ModelClasses.js b/demos/databinding/treebuilder/ModelClasses.js index e31630c8e..4a370abfe 100644 --- a/demos/databinding/treebuilder/ModelClasses.js +++ b/demos/databinding/treebuilder/ModelClasses.js @@ -115,7 +115,7 @@ export function createBinding(bindingString) { const func = new Function(`return (${bindingString})`)() // wrap the binding function with a function that catches and reports errors // that occur in the binding functions - return dataItem => { + return (dataItem) => { try { // eslint-disable-next-line no-useless-call const result = func.apply(null, [dataItem]) @@ -129,10 +129,10 @@ export function createBinding(bindingString) { } } } catch (ignored) { - return dataItem => (bindingString.length > 0 ? dataItem[bindingString] : undefined) + return (dataItem) => (bindingString.length > 0 ? dataItem[bindingString] : undefined) } } - return dataItem => (bindingString.length > 0 ? dataItem[bindingString] : undefined) + return (dataItem) => (bindingString.length > 0 ? dataItem[bindingString] : undefined) } /** diff --git a/demos/databinding/treebuilder/SchemaComponent.js b/demos/databinding/treebuilder/SchemaComponent.js index 69a6037dc..4c1baa7e2 100644 --- a/demos/databinding/treebuilder/SchemaComponent.js +++ b/demos/databinding/treebuilder/SchemaComponent.js @@ -300,20 +300,23 @@ export class SchemaComponent { this.treeBuilder = new TreeBuilder(this.resultGraph) const schemaGraphBuilder = new GraphBuilder(this.schemaGraphComponent.graph) - const schemaNodesSource = schemaGraphBuilder.createNodesSource(sample.nodesSources, n => n.name) + const schemaNodesSource = schemaGraphBuilder.createNodesSource( + sample.nodesSources, + (n) => n.name + ) - schemaNodesSource.nodeCreator.createLabelBinding(n => n.name) + schemaNodesSource.nodeCreator.createLabelBinding((n) => n.name) - schemaNodesSource.nodeCreator.tagProvider = sourceDefinition => + schemaNodesSource.nodeCreator.tagProvider = (sourceDefinition) => this.createTreeNodesSourceConnector(sourceDefinition) - schemaNodesSource.nodeCreator.styleProvider = data => + schemaNodesSource.nodeCreator.styleProvider = (data) => SchemaComponent.createSchemaNodeStyle(data) const schemaEdgesSource = schemaGraphBuilder.createEdgesSource( sample.edgesSource, - e => e.parentSource, - e => e.childSource + (e) => e.parentSource, + (e) => e.childSource ) schemaEdgesSource.edgeCreator.addEdgeCreatedListener((_, evt) => { this.createChildRelationship(evt.dataItem.childBinding, evt.item) @@ -334,14 +337,14 @@ export class SchemaComponent { // gather remaining source definitions const treeNodesSourcesDefinitions = [] - schemaGraph.nodes.forEach(node => { + schemaGraph.nodes.forEach((node) => { const sourceConnector = node.tag treeNodesSourcesDefinitions.push(sourceConnector.sourceDefinition) }) // gather remaining edge definitions const edgesSourceDefinitions = [] - schemaGraph.edges.forEach(edge => { + schemaGraph.edges.forEach((edge) => { const sourceConnector = edge.sourceNode.tag const targetConnector = edge.targetNode.tag @@ -377,7 +380,7 @@ export class SchemaComponent { edge.tag = { provider: childDataProvider, binding: createBinding(childDataProvider) } sourceConnector.nodesSource.addChildNodesSource( - dataItem => edge.tag.binding(dataItem), + (dataItem) => edge.tag.binding(dataItem), targetConnector.nodesSource ) @@ -485,7 +488,7 @@ export class SchemaComponent { this.schemaGraphComponent.graph.nodeDefaults.size // each edge creation should use another random target node color - createEdgeInputMode.addGestureStartingListener(src => { + createEdgeInputMode.addGestureStartingListener((src) => { const nodeStyle = new ShapeNodeStyle({ shape: 'ellipse', fill: '#6495ED', diff --git a/demos/databinding/treebuilder/SchemaComponent.ts b/demos/databinding/treebuilder/SchemaComponent.ts index a1e5626f9..9b2997f86 100644 --- a/demos/databinding/treebuilder/SchemaComponent.ts +++ b/demos/databinding/treebuilder/SchemaComponent.ts @@ -290,9 +290,12 @@ export class SchemaComponent { this.treeBuilder = new TreeBuilder(this.resultGraph) const schemaGraphBuilder = new GraphBuilder(this.schemaGraphComponent.graph) - const schemaNodesSource = schemaGraphBuilder.createNodesSource(sample.nodesSources, n => n.name) + const schemaNodesSource = schemaGraphBuilder.createNodesSource( + sample.nodesSources, + (n) => n.name + ) - schemaNodesSource.nodeCreator.createLabelBinding(n => n.name) + schemaNodesSource.nodeCreator.createLabelBinding((n) => n.name) schemaNodesSource.nodeCreator.tagProvider = ( sourceDefinition @@ -304,8 +307,8 @@ export class SchemaComponent { const schemaEdgesSource = schemaGraphBuilder.createEdgesSource( sample.edgesSource, - e => e.parentSource, - e => e.childSource + (e) => e.parentSource, + (e) => e.childSource ) schemaEdgesSource.edgeCreator.addEdgeCreatedListener((_, evt) => { this.createChildRelationship(evt.dataItem.childBinding, evt.item) @@ -369,7 +372,7 @@ export class SchemaComponent { edge.tag = { provider: childDataProvider, binding: createBinding(childDataProvider) } sourceConnector.nodesSource.addChildNodesSource( - dataItem => edge.tag.binding(dataItem), + (dataItem) => edge.tag.binding(dataItem), targetConnector.nodesSource ) @@ -476,7 +479,7 @@ export class SchemaComponent { this.schemaGraphComponent.graph.nodeDefaults.size // each edge creation should use another random target node color - createEdgeInputMode.addGestureStartingListener(src => { + createEdgeInputMode.addGestureStartingListener((src) => { const nodeStyle = new ShapeNodeStyle({ shape: 'ellipse', fill: '#6495ED', diff --git a/demos/databinding/treebuilder/index.html b/demos/databinding/treebuilder/index.html index c263c7804..3b6679352 100644 --- a/demos/databinding/treebuilder/index.html +++ b/demos/databinding/treebuilder/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/input/button-input-mode/ButtonInputMode.js b/demos/input/button-input-mode/ButtonInputMode.js index 719d41aa6..f972ddd5b 100644 --- a/demos/input/button-input-mode/ButtonInputMode.js +++ b/demos/input/button-input-mode/ButtonInputMode.js @@ -377,7 +377,7 @@ export class ButtonInputMode extends InputModeBase { if (item) { this.buttonOwner = item this.buttons = this.getButtons(item) - this.buttons.forEach(button => { + this.buttons.forEach((button) => { this.buttonLabels.add(button) }) @@ -470,7 +470,7 @@ export class ButtonInputMode extends InputModeBase { getHitButtons(context, location) { return context.canvasComponent .hitElementsAt(context, location, this.buttonLabelManager.canvasObjectGroup) - .map(canvasObject => canvasObject.userObject) + .map((canvasObject) => canvasObject.userObject) } /** @@ -840,7 +840,7 @@ export class ButtonInputMode extends InputModeBase { } // As bends don't have their own visualization, bend hit testing has to be explicitly checked const hitBend = hitItem.bends.find( - bend => bend.location.distanceTo(location) < context.hitTestRadius + (bend) => bend.location.distanceTo(location) < context.hitTestRadius ) return hitBend ?? hitItem } diff --git a/demos/input/button-input-mode/ButtonInputMode.ts b/demos/input/button-input-mode/ButtonInputMode.ts index 4e2f5e10d..200e5ec36 100644 --- a/demos/input/button-input-mode/ButtonInputMode.ts +++ b/demos/input/button-input-mode/ButtonInputMode.ts @@ -351,7 +351,7 @@ export class ButtonInputMode extends InputModeBase { if (item) { this.buttonOwner = item this.buttons = this.getButtons(item) - this.buttons.forEach(button => { + this.buttons.forEach((button) => { this.buttonLabels.add(button) }) @@ -440,7 +440,7 @@ export class ButtonInputMode extends InputModeBase { private getHitButtons(context: IInputModeContext, location: Point) { return context .canvasComponent!.hitElementsAt(context, location, this.buttonLabelManager.canvasObjectGroup) - .map(canvasObject => canvasObject.userObject as Button) + .map((canvasObject) => canvasObject.userObject as Button) } private getFirstHitButton(location: Point): Button | null { @@ -743,7 +743,7 @@ export class ButtonInputMode extends InputModeBase { } // As bends don't have their own visualization, bend hit testing has to be explicitly checked const hitBend = hitItem.bends.find( - bend => bend.location.distanceTo(location) < context.hitTestRadius + (bend) => bend.location.distanceTo(location) < context.hitTestRadius ) return hitBend ?? hitItem } diff --git a/demos/input/button-input-mode/ButtonInputModeDemo.js b/demos/input/button-input-mode/ButtonInputModeDemo.js index 3be9f35b3..6fd165ed8 100644 --- a/demos/input/button-input-mode/ButtonInputModeDemo.js +++ b/demos/input/button-input-mode/ButtonInputModeDemo.js @@ -367,14 +367,14 @@ function buildGraph(graph, graphData) { graphBuilder .createNodesSource({ data: graphData.nodeList, - id: item => item.id + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/input/button-input-mode/ButtonInputModeDemo.ts b/demos/input/button-input-mode/ButtonInputModeDemo.ts index 47492a090..4bcaabf14 100644 --- a/demos/input/button-input-mode/ButtonInputModeDemo.ts +++ b/demos/input/button-input-mode/ButtonInputModeDemo.ts @@ -362,14 +362,14 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { graphBuilder .createNodesSource({ data: graphData.nodeList, - id: item => item.id + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/input/button-input-mode/OffsetLabelModelWrapper.js b/demos/input/button-input-mode/OffsetLabelModelWrapper.js index 22c4e0c13..66dbf9df6 100644 --- a/demos/input/button-input-mode/OffsetLabelModelWrapper.js +++ b/demos/input/button-input-mode/OffsetLabelModelWrapper.js @@ -170,7 +170,7 @@ export class OffsetLabelModelWrapper extends BaseClass(ILabelModel) { } /** - * @template {*} T + * @template T * @param {!Class.} type * @returns {?T} */ diff --git a/demos/input/button-input-mode/OffsetLabelModelWrapper.ts b/demos/input/button-input-mode/OffsetLabelModelWrapper.ts index b5c430488..273d96a49 100644 --- a/demos/input/button-input-mode/OffsetLabelModelWrapper.ts +++ b/demos/input/button-input-mode/OffsetLabelModelWrapper.ts @@ -145,7 +145,7 @@ export class OffsetLabelModelWrapper extends BaseClass(ILabelModel) { } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - lookup(type: Class): T | null { + lookup(type: Class): T | null { return null } } diff --git a/demos/input/button-input-mode/index.html b/demos/input/button-input-mode/index.html index 89ce765f4..7e9711d4e 100644 --- a/demos/input/button-input-mode/index.html +++ b/demos/input/button-input-mode/index.html @@ -1,4 +1,4 @@ - + @@ -41,7 +41,9 @@ - - - -

- -
- yFiles for HTML - Demos - - Web Worker UMD - yFiles Demos -
-
- - -
-
- - - -
- - - - - - - - - - - - - -
- -
-
-
-
-
- Running Web Worker -
Cancel Layout
-
-
-
- - - - - - - diff --git a/demos/loading/webworker-webpack/README.html b/demos/loading/webworker-webpack/README.html index c05d66790..2f043eb08 100644 --- a/demos/loading/webworker-webpack/README.html +++ b/demos/loading/webworker-webpack/README.html @@ -1,4 +1,4 @@ - + diff --git a/demos/loading/webworker-webpack/index.template.html b/demos/loading/webworker-webpack/index.template.html index 8d5fe680d..38f968e3e 100644 --- a/demos/loading/webworker-webpack/index.template.html +++ b/demos/loading/webworker-webpack/index.template.html @@ -1,4 +1,4 @@ - + diff --git a/demos/loading/webworker-webpack/package.json b/demos/loading/webworker-webpack/package.json index 07ff6c19c..ac90fc838 100644 --- a/demos/loading/webworker-webpack/package.json +++ b/demos/loading/webworker-webpack/package.json @@ -14,18 +14,18 @@ "dependencies": { "demo-resources": "../../resources", "utils": "../../utils", - "yfiles": "../../../lib-dev/yfiles-26.0.2+dev.tgz" + "yfiles": "../../../lib-dev/yfiles-26.0.3+dev.tgz" }, "devDependencies": { - "@yworks/optimizer": "^1.8.1", + "@yworks/optimizer": "^1.8.2", "copy-webpack-plugin": "^11.0.0", "css-loader": "6.8.1", "html-webpack-harddisk-plugin": "2.0.0", "html-webpack-plugin": "5.5.3", "mini-css-extract-plugin": "2.7.6", - "ts-loader": "^9.4.3", - "typescript": "~5.1.6", - "webpack": "5.88.0", + "ts-loader": "^9.5.1", + "typescript": "~5.3.2", + "webpack": "5.89.0", "webpack-cli": "5.1.4", "webpack-dev-server": "4.15.1" }, diff --git a/demos/loading/webworker-webpack/src/WebWorkerWebpackDemo.ts b/demos/loading/webworker-webpack/src/WebWorkerWebpackDemo.ts index 4dae9eb11..e13ff7f25 100644 --- a/demos/loading/webworker-webpack/src/WebWorkerWebpackDemo.ts +++ b/demos/loading/webworker-webpack/src/WebWorkerWebpackDemo.ts @@ -94,7 +94,7 @@ async function runWebWorkerLayout(clearUndo: boolean): Promise { // helper function that performs the actual message passing to the web worker function webWorkerMessageHandler(data: unknown): Promise { - return new Promise(resolve => { + return new Promise((resolve) => { worker.onmessage = (e: any) => resolve(e.data) worker.postMessage(data) }) diff --git a/demos/loading/webworker-webpack/src/WorkerLayout.ts b/demos/loading/webworker-webpack/src/WorkerLayout.ts index cb4aaee12..08dcd56b2 100644 --- a/demos/loading/webworker-webpack/src/WorkerLayout.ts +++ b/demos/loading/webworker-webpack/src/WorkerLayout.ts @@ -49,7 +49,7 @@ function applyLayout(graph: LayoutGraph, layoutDescriptor: LayoutDescriptor): vo addEventListener( 'message', - e => { + (e) => { // create a new remote layout executor const executor = new LayoutExecutorAsyncWorker(applyLayout) executor.process(e.data).then(postMessage).catch(postMessage) diff --git a/demos/package.json b/demos/package.json index a5b57518a..755bc9392 100644 --- a/demos/package.json +++ b/demos/package.json @@ -1,6 +1,6 @@ { "name": "typescript-demos-for-yfiles-for-html", - "version": "26.0.2", + "version": "26.0.3", "author": "yWorks GmbH ", "license": "https://www.yworks.com/products/yfiles-for-html/sla", "private": true, @@ -14,29 +14,28 @@ }, "dependencies": { "@babel/standalone": "7.19.3", - "@fortawesome/fontawesome-free": "6.4.0", - "codemirror": "5.65.13", + "@fortawesome/fontawesome-free": "6.4.2", + "codemirror": "5.65.16", "d3": "~7.8.5", "demo-utils": "./utils", "demo-resources": "./resources", - "jspdf": "2.3.1", + "jspdf": "2.5.1", "leaflet": "~1.9.4", - "markdown-it": "13.0.1", - "neo4j-driver": "5.9.1", + "markdown-it": "13.0.2", + "neo4j-driver": "5.14.0", "quill": "1.3.7", "react": "18.2.0", "react-dom": "18.2.0", - "svg2pdf.js": "2.1.0", - "yfiles": "^26.0.0", - "yfiles-umd": "^26.0.0" + "svg2pdf.js": "2.2.2", + "yfiles": "^26.0.0" }, "devDependencies": { - "@types/babel__standalone": "7.1.4", - "@types/codemirror": "~5.60.8", - "@types/d3": "~7.4.0", - "@types/leaflet": "~1.9.3", - "@types/markdown-it": "~12.2.3", - "@types/quill": "~1.3.10", - "@types/react-dom": "18.2.0" + "@types/babel__standalone": "7.1.7", + "@types/codemirror": "~5.60.15", + "@types/d3": "~7.4.3", + "@types/leaflet": "~1.9.8", + "@types/markdown-it": "~13.0.7", + "@types/quill": "~2.0.14", + "@types/react-dom": "18.2.17" } } diff --git a/demos/resources/README.html b/demos/resources/README.html index 0ec161290..49aac819f 100644 --- a/demos/resources/README.html +++ b/demos/resources/README.html @@ -1,4 +1,4 @@ - + diff --git a/demos/resources/apply-local-storage-variables.js b/demos/resources/apply-local-storage-variables.js index c2892ef72..5008a60ba 100644 --- a/demos/resources/apply-local-storage-variables.js +++ b/demos/resources/apply-local-storage-variables.js @@ -63,7 +63,7 @@ function getNumberItemFromLocalStorage(key) { } const size = parseFloat(storageItem) - return isFinite(size) ? size : null + return Number.isFinite(size) ? size : null } applyLocalStorageVariables() diff --git a/demos/resources/demo-error.js b/demos/resources/demo-error.js index f190a86aa..7fa405681 100644 --- a/demos/resources/demo-error.js +++ b/demos/resources/demo-error.js @@ -74,7 +74,7 @@ export function registerErrorDialog() { } // Register a handler for unhandled errors - window.addEventListener('error', e => { + window.addEventListener('error', (e) => { e.preventDefault() if (inErrorState()) { return @@ -86,7 +86,7 @@ export function registerErrorDialog() { }) // Register a handler for unhandled promise rejections - window.addEventListener('unhandledrejection', e => { + window.addEventListener('unhandledrejection', (e) => { e.preventDefault() if (inErrorState()) { return @@ -102,11 +102,11 @@ export function registerErrorDialog() { // Forward errors occurring in internal event handlers of yFiles to the standard reportError function // https://docs.yworks.com/yfileshtml/#/api/Exception - Exception.handler = error => openErrorOverlay(error) + Exception.handler = (error) => openErrorOverlay(error) // Forward errors occurring during require.js module loading to the standard reportError function if (anyWindow.require != null) { - anyWindow.onError = error => openErrorOverlay(error) + anyWindow.onError = (error) => openErrorOverlay(error) } } @@ -140,8 +140,8 @@ function unwindStack(error) { return !stack || stack.length === 0 ? '' : error.cause - ? `${stack}\nCaused by:\n${unwindStack(error.cause)}` - : stack + ? `${stack}\nCaused by:\n${unwindStack(error.cause)}` + : stack } /** diff --git a/demos/resources/demo-error.ts b/demos/resources/demo-error.ts index 824afaf66..a05c50b0f 100644 --- a/demos/resources/demo-error.ts +++ b/demos/resources/demo-error.ts @@ -133,8 +133,8 @@ function unwindStack(error: any): string | undefined { return !stack || stack.length === 0 ? '' : error.cause - ? `${stack}\nCaused by:\n${unwindStack(error.cause)}` - : stack + ? `${stack}\nCaused by:\n${unwindStack(error.cause)}` + : stack } /** diff --git a/demos/resources/demo-ui/element-utils.js b/demos/resources/demo-ui/element-utils.js index b99b88672..9ed5a52d1 100644 --- a/demos/resources/demo-ui/element-utils.js +++ b/demos/resources/demo-ui/element-utils.js @@ -79,7 +79,7 @@ export function addNavigationButtons( const prevButton = document.createElement('button') prevButton.classList.add('demo-icon-yIconPrevious', 'navigation-button', ...classes) prevButton.setAttribute('title', 'Previous') - prevButton.addEventListener('click', _ => { + prevButton.addEventListener('click', (_) => { const oldIndex = selectElement.selectedIndex const newIndex = lastIndexOfEnabled(selectElement, oldIndex - 1, wrapAround) if (oldIndex != newIndex && newIndex > -1) { @@ -91,7 +91,7 @@ export function addNavigationButtons( const nextButton = document.createElement('button') nextButton.classList.add('demo-icon-yIconNext', 'navigation-button', ...classes) nextButton.setAttribute('title', 'Next') - nextButton.addEventListener('click', _ => { + nextButton.addEventListener('click', (_) => { const oldIndex = selectElement.selectedIndex const newIndex = indexOfEnabled(selectElement, oldIndex + 1, wrapAround) if (oldIndex != newIndex && newIndex > -1) { @@ -123,11 +123,11 @@ export function addNavigationButtons( selectElement.disabled || (!wrapAround && selectElement.selectedIndex === lastIndex) } - selectElement.addEventListener('change', _ => { + selectElement.addEventListener('change', (_) => { updateDisabled() }) - const disabledObserver = new MutationObserver(mutations => { + const disabledObserver = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.attributeName === 'disabled') { updateDisabled() @@ -284,7 +284,7 @@ export function bindYFilesCommand(selector, command, target, parameter, tooltip) */ export function disableUIElements(...elementSelectors) { for (const selector of elementSelectors) { - document.querySelectorAll(selector).forEach(element => { + document.querySelectorAll(selector).forEach((element) => { if (!element || element.hasAttribute('disabled')) return element.setAttribute('disabled', '') @@ -297,7 +297,7 @@ export function disableUIElements(...elementSelectors) { * Re-enables all elements which were previously disabled by {@see disableUIElements}. */ export function enableUIElements() { - document.querySelectorAll('[data-disabled]').forEach(e => { + document.querySelectorAll('[data-disabled]').forEach((e) => { e.removeAttribute('disabled') }) } @@ -315,5 +315,5 @@ export async function showLoadingIndicator(visible, message) { if (message) { loadingIndicator.innerText = message } - return new Promise(resolve => setTimeout(resolve, 0)) + return new Promise((resolve) => setTimeout(resolve, 0)) } diff --git a/demos/resources/demo-ui/element-utils.ts b/demos/resources/demo-ui/element-utils.ts index a4e155313..374f3df6b 100644 --- a/demos/resources/demo-ui/element-utils.ts +++ b/demos/resources/demo-ui/element-utils.ts @@ -72,7 +72,7 @@ export function addNavigationButtons( const prevButton = document.createElement('button') prevButton.classList.add('demo-icon-yIconPrevious', 'navigation-button', ...classes) prevButton.setAttribute('title', 'Previous') - prevButton.addEventListener('click', _ => { + prevButton.addEventListener('click', (_) => { const oldIndex = selectElement.selectedIndex const newIndex = lastIndexOfEnabled(selectElement, oldIndex - 1, wrapAround) if (oldIndex != newIndex && newIndex > -1) { @@ -84,7 +84,7 @@ export function addNavigationButtons( const nextButton = document.createElement('button') nextButton.classList.add('demo-icon-yIconNext', 'navigation-button', ...classes) nextButton.setAttribute('title', 'Next') - nextButton.addEventListener('click', _ => { + nextButton.addEventListener('click', (_) => { const oldIndex = selectElement.selectedIndex const newIndex = indexOfEnabled(selectElement, oldIndex + 1, wrapAround) if (oldIndex != newIndex && newIndex > -1) { @@ -116,11 +116,11 @@ export function addNavigationButtons( selectElement.disabled || (!wrapAround && selectElement.selectedIndex === lastIndex) } - selectElement.addEventListener('change', _ => { + selectElement.addEventListener('change', (_) => { updateDisabled() }) - const disabledObserver = new MutationObserver(mutations => { + const disabledObserver = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.attributeName === 'disabled') { updateDisabled() @@ -281,7 +281,7 @@ export function bindYFilesCommand( */ export function disableUIElements(...elementSelectors: string[]): void { for (const selector of elementSelectors) { - document.querySelectorAll(selector).forEach(element => { + document.querySelectorAll(selector).forEach((element) => { if (!element || element.hasAttribute('disabled')) return element.setAttribute('disabled', '') @@ -294,7 +294,7 @@ export function disableUIElements(...elementSelectors: string[]): void { * Re-enables all elements which were previously disabled by {@see disableUIElements}. */ export function enableUIElements() { - document.querySelectorAll('[data-disabled]').forEach(e => { + document.querySelectorAll('[data-disabled]').forEach((e) => { e.removeAttribute('disabled') }) } @@ -310,5 +310,5 @@ export async function showLoadingIndicator(visible: boolean, message?: string): if (message) { loadingIndicator.innerText = message } - return new Promise(resolve => setTimeout(resolve, 0)) + return new Promise((resolve) => setTimeout(resolve, 0)) } diff --git a/demos/resources/demo-ui/sidebars.js b/demos/resources/demo-ui/sidebars.js index 7feb1d8be..01b1386fd 100644 --- a/demos/resources/demo-ui/sidebars.js +++ b/demos/resources/demo-ui/sidebars.js @@ -91,7 +91,7 @@ function enableDraggableDescription(description) { description.append(verticalDragArea, horizontalDragArea) let resizingElement - const resize = event => { + const resize = (event) => { if (!resizingElement) return const vertical = resizingElement.classList.contains('demo-description__drag-area--vertical') @@ -102,10 +102,10 @@ function enableDraggableDescription(description) { ? event.pageX : window.innerHeight - event.pageY : event instanceof TouchEvent - ? vertical - ? event.touches.item(0).pageX - : window.innerHeight - event.touches.item(0).pageY - : null + ? vertical + ? event.touches.item(0).pageX + : window.innerHeight - event.touches.item(0).pageY + : null if (eventPos == null) { return diff --git a/demos/resources/demo-ui/sidebars.ts b/demos/resources/demo-ui/sidebars.ts index 7a1349e63..ae280e4a3 100644 --- a/demos/resources/demo-ui/sidebars.ts +++ b/demos/resources/demo-ui/sidebars.ts @@ -99,10 +99,10 @@ function enableDraggableDescription(description: Element) { ? event.pageX : window.innerHeight - event.pageY : event instanceof TouchEvent - ? vertical - ? event.touches.item(0)!.pageX - : window.innerHeight - event.touches.item(0)!.pageY - : null + ? vertical + ? event.touches.item(0)!.pageX + : window.innerHeight - event.touches.item(0)!.pageY + : null if (eventPos == null) { return diff --git a/demos/resources/demo-ui/toolbar.js b/demos/resources/demo-ui/toolbar.js index c20bceba3..c9725d431 100644 --- a/demos/resources/demo-ui/toolbar.js +++ b/demos/resources/demo-ui/toolbar.js @@ -60,7 +60,7 @@ function getToolbarWidthAndPadding(entry) { // borderBoxSize and contentBoxSize are not supported by Safari < 15, // fallback to contentRect if (!entry.borderBoxSize || !entry.contentBoxSize) { - toolbarWidth = entry.contentRect.width + 32 ?? 0 + toolbarWidth = entry.contentRect.width ? entry.contentRect.width + 32 : 0 toolbarContentWidth = entry.contentRect.width ?? 0 } // old Firefox implemented borderBoxSize and contentBoxSize as non array @@ -81,7 +81,7 @@ function getToolbarWidthAndPadding(entry) { */ function toolbarSizeChanged(entries, observer) { window.requestAnimationFrame(() => { - entries.forEach(entry => { + entries.forEach((entry) => { const { toolbarWidth, toolbarPadding } = getToolbarWidthAndPadding(entry) if (toolbarWidth > 0) { wrapToolbar(entry.target, toolbarWidth, toolbarPadding) @@ -109,7 +109,7 @@ function initToolbarResponsiveness(toolbar) { overflowButton.classList.add('overflow-button') overflowButton.setAttribute('title', 'More...') - const closeContainerHandler = e => { + const closeContainerHandler = (e) => { let current = e.target while (current !== overflowContainer && current.parentNode) { current = current.parentNode @@ -120,7 +120,7 @@ function initToolbarResponsiveness(toolbar) { e.preventDefault() } } - overflowButton.addEventListener('click', e => { + overflowButton.addEventListener('click', (e) => { if (e.target !== overflowButton) return overflowContainer.classList.toggle('overflow-container--open') @@ -243,12 +243,12 @@ function pushBackOverflow(toolbar, overflowContainerContent, toolbarWidth) { function initTutorialToolbar(toolbar) { const dropdown = toolbar.querySelector('.demo-toolbar__tutorial-dropdown') - const closeDropdown = e => { + const closeDropdown = (e) => { dropdown.classList.remove('demo-toolbar__tutorial-dropdown--expanded') document.body.removeEventListener('click', closeDropdown) } - dropdown?.addEventListener('click', e => { + dropdown?.addEventListener('click', (e) => { if (dropdown.classList.contains('demo-toolbar__tutorial-dropdown--expanded')) { closeDropdown(e) } else { diff --git a/demos/resources/demo-ui/toolbar.ts b/demos/resources/demo-ui/toolbar.ts index 2937db55e..fe3df56b1 100644 --- a/demos/resources/demo-ui/toolbar.ts +++ b/demos/resources/demo-ui/toolbar.ts @@ -57,7 +57,7 @@ function getToolbarWidthAndPadding(entry: ResizeObserverEntry) { // borderBoxSize and contentBoxSize are not supported by Safari < 15, // fallback to contentRect if (!entry.borderBoxSize || !entry.contentBoxSize) { - toolbarWidth = entry.contentRect.width + 32 ?? 0 + toolbarWidth = entry.contentRect.width ? entry.contentRect.width + 32 : 0 toolbarContentWidth = entry.contentRect.width ?? 0 } // old Firefox implemented borderBoxSize and contentBoxSize as non array @@ -76,7 +76,7 @@ function getToolbarWidthAndPadding(entry: ResizeObserverEntry) { function toolbarSizeChanged(entries: ResizeObserverEntry[], observer: ResizeObserver) { window.requestAnimationFrame(() => { - entries.forEach(entry => { + entries.forEach((entry) => { const { toolbarWidth, toolbarPadding } = getToolbarWidthAndPadding(entry) if (toolbarWidth > 0) { wrapToolbar(entry.target, toolbarWidth, toolbarPadding) @@ -114,7 +114,7 @@ function initToolbarResponsiveness(toolbar: Element) { e.preventDefault() } } - overflowButton.addEventListener('click', e => { + overflowButton.addEventListener('click', (e) => { if (e.target !== overflowButton) return overflowContainer.classList.toggle('overflow-container--open') @@ -230,7 +230,7 @@ function initTutorialToolbar(toolbar: Element) { document.body.removeEventListener('click', closeDropdown) } - dropdown?.addEventListener('click', e => { + dropdown?.addEventListener('click', (e) => { if (dropdown.classList.contains('demo-toolbar__tutorial-dropdown--expanded')) { closeDropdown(e) } else { diff --git a/demos/resources/fetch-license.js b/demos/resources/fetch-license.js index f7759e311..da6f27328 100644 --- a/demos/resources/fetch-license.js +++ b/demos/resources/fetch-license.js @@ -74,7 +74,7 @@ function checkLicense(licenseData) { const g = new DefaultGraph() g.createNode() if (g.nodes.size === 1) { - return new Promise(resolve => resolve(licenseData)) + return new Promise((resolve) => resolve(licenseData)) } window.setTimeout(() => { @@ -107,7 +107,7 @@ function parseLicense(licenseString) { } /** - * @returns {!Promise.<(Record.|void)>} + * @returns {!(Promise.>|Promise)} */ function loadLicenseFromLocalStorage() { if (typeof window === 'undefined') { @@ -125,10 +125,10 @@ function loadLicenseFromLocalStorage() { } /** - * @returns {!Promise.<(Record.|void)>} + * @returns {!Promise.>} */ async function showLicenseDialog() { - return new Promise(resolve => { + return new Promise((resolve) => { window.setTimeout(function () { const div = document.createElement('div') div.setAttribute( diff --git a/demos/resources/fetch-license.ts b/demos/resources/fetch-license.ts index 5ccb24aeb..ba47d4778 100644 --- a/demos/resources/fetch-license.ts +++ b/demos/resources/fetch-license.ts @@ -71,7 +71,7 @@ function checkLicense(licenseData: Record): Promise { const g = new DefaultGraph() g.createNode() if (g.nodes.size === 1) { - return new Promise(resolve => resolve(licenseData)) + return new Promise((resolve) => resolve(licenseData)) } window.setTimeout(() => { @@ -99,7 +99,9 @@ function parseLicense(licenseString: string | undefined): Record | void> { +function loadLicenseFromLocalStorage(): + | Promise | undefined> + | Promise { if (typeof window === 'undefined') { console.warn('yFiles demo app: No yFiles for HTML license included!') return Promise.resolve() @@ -114,8 +116,8 @@ function loadLicenseFromLocalStorage(): Promise | void> return showLicenseDialog() } -async function showLicenseDialog(): Promise | void> { - return new Promise(resolve => { +async function showLicenseDialog(): Promise | undefined> { + return new Promise((resolve) => { window.setTimeout(function () { const div = document.createElement('div') div.setAttribute( diff --git a/demos/resources/filesystem-warning.js b/demos/resources/filesystem-warning.js index e6cf554a4..9abc6ef5d 100644 --- a/demos/resources/filesystem-warning.js +++ b/demos/resources/filesystem-warning.js @@ -26,8 +26,7 @@ ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ** ***************************************************************************/ -/* eslint-disable no-eval */ -/* eslint-disable no-var */ +/* eslint-disable no-eval, no-var */ ;(function () { var demoRoot = window.location.toString().indexOf('/demos-ts/') > -1 ? 'demos-ts' : 'demos-js' diff --git a/demos/resources/image/amdloading.png b/demos/resources/image/amdloading.png deleted file mode 100644 index c65bc2862bce3762f4ebcc3e6bdfd567c93bde75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27049 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 pO}-?N*ChPEuXXtUG) diff --git a/demos/resources/image/export-png.png b/demos/resources/image/export-png.png deleted file mode 100644 index 0459aa32079171df179451693bdfc3ef2556571c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9942 zcmc(F1yfvG5M~l$K!OGc1b62#xCAHI;4Xn61Hs+x1$VbWgIkcnhk+qD1B1K!5MXeJ z0L$C0t=-x`uw8YkZr{^ipFZdItvdZxN2;mFy~iTMdiLzudj)wJjc3oEBcDdXTa2eS z9~6EHK6{4oObw_d`}m*SJ9q>>Tpm9@Jg%;9-akIFuzq{&$#{5pcnH2N`H_X*-D`Y z8WQp_R8slt*YWX*V#?S*|CpmH12i-PQ~S|(cYjY$&s9`Zegp;G+}!4mpVimb-#7%aREg+lH8a+XmKuWN2;32NEcDD|op&X&(!WoBmP z=H}KuNmJ{}%8G=9gn8MDudlCoG-7LO>v3*(^XU+3iHEGsR$yT1>MO@??VCP$ zY^-c-@(Wlak#nJ~C_iwuxVS^h`rotj^YFZyuC6W@7nj(|-v|VvYVj7R?D>cqPe{+P z=Xo6)TjuQzmXnjy*Vo6w!itP8xkvmo49qPbSPE}iaEUKAGz=M^Tyb)8YKIS7S;b^! z)o0J#CubJ~mJhnQxyi`5I668!^yT#Q^tfk3iHV8l=GT7&ewQ!JwZA7Ts`kTo?QRRjEfu8zLjS#$u0)>~6 zv@#8;b9#l}r+a#6Bcnp1G`5O2h+aO=SHx%cX(Ud(ZLt~ZHL$HRNVl1YH$;GylL(P$A>eeKeBElzT@-F+aDX$zEc$}UQuy$r= zr$II6$CYw7$hH*c-HS+ybA;hdJnT|*5T&!ReUByWybJ&fBy|b-o4Tq6ZuFb@MVJ`0 zuWau*sW?%!yo0Yx*ZP0|L)x^>G2Y41xpeCmpyWn|^jg%f?c}~GZ0|4?Bc)tn;^4_% zuHny2CTUw(z_noffvi40!o3KV5<~0~1_j^g+nl}~INzps_B-haSOwgrBCm2GsLR+h za+7`kLY{NFqxpjj0GE)XquoY#e6GLw#omQLp5LPfyA{p=ngSfZelHwtU4e`5dU;>N zZa6Q~`e1Hn$Jic|UXN*(ScOPGgTCxE;|?awZ*~Zm7FZMk%)eZOLwc4ozc=wDdUrKj zp+OsRzd-OS|7_eLVX`~afzTMhSFWVHQ*1s*Z(|27|H{^;}C z;xY)hvw7TioA=$otskD%nC6X2tN*^58}87A3#x-R?^`r&gJ%HA4n3rKDS>?abF=9xx}(`|d$~lCLrBi$ht6sIj(MkF z*kBrmrQ^tewQRcs*LT1(GNM*olRL9cOn`1jvc>J;vrf07wQnj;1Xde98#?Uaq`0RI z$i!|dx4!*P^BBsOwcb-qyCSGHAKPC!z@WRt9N=wgG*G14>Q{I|61Ox#Q5UAG@zVP9*ubu08<~9Zg@i>N-lGM44pTF@e%Q`!Z za>}XF7S}c5(*%1x8;mIy3fxqIBj$ABGaQowm%mORNi~8q-Y9*`SGBfarVI{Fy2(wB zWD^BiN)z>DjZ}u&p&{>K*&_M$pyqt~70ZeqjGC-egp{;2s7{b`QFJ08+?<04k?y+E zlBcv3)0lQc&M~>Ojsv=s5Kyw&!_#1Ody$yC`ki@8o0uk1qhP(T3$>uw^C<#Aacuv~ zv37d}?F#QCUyaN!c{{VrK8aCpd{_kXX~?1oKLbL(6zP7)XVp#Q&~+<=zAZl&Hgz_D z^eh4NF$@`P@4d5vCr5`dQP(3-8{x7fM}{*~)QZpIQEm3mi+21ajAeG7@6?G{PVjP> z7c5eW6a-su6ATn-a|mcKM}(|u=P?XWt|eQm8jRtD0sWWY=wG?L@bP@$s7E~=j6Zv) zIsW5sfWMB(`<;)f-py!B$gm*}sLz1PW;e%S#wsswxmI{|Wm}M++x;iP;?5s4ChCVf6T^6FG_2(b=UC(d1z32$ornaQpjbMi zEpRLhVwjB%svMejb{_#kaiGlB(Y2!z1N1)6n^K{jGiB^N82-pQQeorzHcZu`kJQJ) zk=UY@E4kZy>IZOyr{oui=W0|uNyiL-*& zs(+MZJq}17scA|_tS>nW+x7X-31S+Qcn)sSgZnR#0RL{PG$HSfnInmkW{-Mul@@Dk z=)mBKvfR<-;MLLkJcDa%Z!JH#ZKYd){A*;mcYEAUCfSvE$7!y;b3OpojJ{X`1 zEb(r^03$Ww&7Xz#6?~wLz>(}@+4ySygISlejt-wbTE|!5&&)j8Ld?tK#c~;?JqyMK z|9acEEEi*=dIBzx-Aso|LMO`yHD*HBw#S|D_TzgXr!FJ5y}f;osE`&vHR$qo`-=~1 za{#B2iE@!N!ALDJ5%MfqU0p5j>kGhLK5}v%R*-)9OO+o1{QZ*AqW}F{@ak0}a*pzk zB}kfpkOBdGYm{yTz_UR0U&bOAbJO*cz<=~Czpb^x_7|X69l)6bG3VX8qW&u%eg;Ml zYS%wI7u;9vz>Qcj>&$55ljCA95D9@uqPL@Cw?9{M3(?{oNs}Mf>SUgork&$_SJ2S-}PC`kHz4pqjqnVVW1h04VI9J$?p+_Kso+-mo(c3?TkElV5&JI#X zn6SV2eE2dU;PC;m&}jSk@W$^BLG=*lX!85)j^wey<6vQyxcaW+F-Q2B#qHk*$%2kx zkQfye)gyvREZ^ag^6BX>)yZF2bYN6`e4y|8&mZG}$U9ZW;*%Q|y-nLk1HvbZd8Ob( zqT1L00%z`NTnAOf`*1TaU%~Hpqq&1O0{`wdM@XKCKkQ!rl{|q48sBWTzu+H0lb}_A zeh<;50k<2`TyU>FZ$FWPN$!sO%SFdPtlQa+II71p6DHC7oA&3m0=|K6^>vr%9;$%r z^;wgvqt%W_WhCqfes^eF(|$Sf1i73Se-!$2xE%62VcsMHJC?;-(4V=n%3sIQ!_3X? z9AZBHBrpOVt_BJ*H(~LVp;zE%124hU^@a@&dmDsjp25H)#)bgjFC=+Brgvim3A^o1 zg}8oRWkWE2Zp=>v29hP|709L2PQ3I>4W%##d7M{ZWBJ85hL+H5r)*>2Z`R}k=K%g3 z;nqq+sJYGGaXZd|b6PT&<~MF<3kc8hG zl|2!!fA|8FO0Ag&&b`d^xm=BR89oUw2y7qDPj?m`_+B;yBaMOVv-V|g56bR;RsE@T zw_jWt&&3gNR_gx`t!~4OdcCid{__cm>W?WyffBqsfcY1V+DZ1L>yc z&ODtfAYpgB$K@z2iZl1unW-#^d5Oc(LangC_~mz|0XH59de3uPM420wZD&xLQz~i6 z#gJBAl)*`#m1e!;5&g8jhrp+Nf+5a4`LWOslc^SyKwwwcD{ksZ4nVWt7^+L4X`&hC z_9O2JF=Ji&&cY+BiSx-tNrt74C&N6itIxp?bG@C8U|_n?RBz7&a>R>76{`_44;_U}j>tW{0QfRPdP3BjZP|V z`AHH_uKug{Fx~ts3i78!Xo@V3&T$#g2zrY=n7LcGsNc#|(oOQ-qS=GxBuiQ{v0Axr zM-+ecAx+x1iLwsp$Hk67C%hvu?hN>w9~b8l1Pr60vO}QzX1@TWuy@SL|I2lg?3&7q$}r5$kz=rz>*33cGWb$X z8W)g6p4L5wDC}-(8Bad4I$jsX&*>^lH?3X)J zcsh|0_WVH@Fen1Ep{+;_xc58h1`0Wad9d0sp~2gh|4vhJx+*bBruaEHE95WD{GjJ? zjt7|`+^?cuKG6e|x~LLhWdjep=Q9NR4I{F>r_RxP32_3PWC+CxMU(vlfS%BVFDbF# z>7h)rzpmZCI9ndUSmk~90gRw|(;|ahc~4X&)A7=?p1r>V{##kphc$E_JD=|8>riEO@QKRbO6y&YwCt3(mpE=TFG^`7@vUVnS0B=zzr1 zG=ohgjPgAu;=tGp>T77@<8xc099}jT9{$eS*0w|9l?m!H!n1o_i}gBR+6@|>F}O^* z7X{4ra2RbTb{RIKYJWMa&|0p7lVxraabgJ$=j^yVggL^YfsYS6+;>+U3il43i;f-l z9-RIeF3glpyE>feNoeLv5y8v&gF_r6`VMo_yPJDQIO6Y0*J7{-lrfo_>@p-Igq+V& z#)HiKs5#;uC+^<0PGD&1d<6Y7LOmH32j<@{CwYhnR6QtXne1cF<+bq{vsyeRtSg6% zC2(Nlz?L$|;60FZHOTH75FPPT+^SV1%Gujv%&lZ;22%QHU9JRBo$WU%6e|!ChyJx5 zJAZQ9m%;j~pOVK@q32w+yUhS)TOW)QX8^E9X0T}H76gzw4t_9lC`1zYeEU>SBe2+5 z2z!q^f+PG9V!`>9#qHQyu*u4zI#VTek-v!z0F@?a$`CV=FK|9d&#T6Y(gRp?x0n0_ zAyG9Tz~4Fmj8=8o(r{j`m9;nbJ`(4;4MaChc#dK>gt5=#VT2*>| z9ww;t9i8#7ScF*>N6q{cWpJmH4C~1fAQ%8zXy7(yTn`hIp45#R$fMdnIuCdbe z6zPeo(dQP47#H$9EK*Q%qWa|K5Xxk+F{ia?1h76Vf_m|FUidqN%2`zB+sBddMVcYX zA|iGrD@UrFogHTqk)db0*@m*ynAIr?$&9fDv&nqe?FN>&B4^H3NF1!Fh>x=P-2Ogl zV`!qYcrOW5*NyG5Fy#lyH}Owo;qQG!6kQ&a!}T1W{F5guu#@iTTT~e|S`H_Q!#K`P zyKc^AvkP^c&FvPp@g38lc(a0sIKen7;$C=J?+l!`uA@az{^;jmITVj@DV1nM_ar1Q z;94R?JKTz>`JfVj^Q@d`TZVNVX3lfRgphGZ+o;Jm!dB{BEvUy7v<5(Eua~W@>A2*;XVM zvH1mpGO?T7X#5Zuvb(!3PS3E(x1mV)?mFFO(}kZWEdo>@f&IoR*u}wIDh1_Wbn(9B|`SWSl!N4F7IX}O>l0n=>7TRqgUqoyj9-4Dl!o2D{S2z-#176 z4`liCJizTwzXv3*Sm~jDaS_>0Wf=0%Fa97I-q$I9X#>vhfAL~(ALE~k3#csy25^eX z^$N{#$Vr}oL+RvvAk66C?=Nw$(cE#9X&v*G3a{4lVCSMw6<@8xwGjqb+#8-wsO6t^ ztAUHHB?t^Ph$e>lUTL|-r`B(10h&ukUK{wZ))tO&4mJGBMQcLauPDCa=8jEI0+i2w z;PQeoll9Ah=vvh8-TZ8>!~4P*5~c)6pCs zuC(uqjaFSbp^-uF$bBi|+JAR1+ZjrJ>3hL?Ikx1x6hN&>OvvMXx|M+_4L2HCBBG$6 z$Pu(nE>&TQ$pTK+R^`OC+v?cFgtLY&3WHg_6>NMhX^BM6oFbFwMn=DT&d`^e^>3AO zWu;R1vkMCg!>Z^5>Htg_?rBA#GUzux}y`E9Po|!~!j{Q1UJ4%I$ ztR1THV6y@MSYbF6tpZs}XqD_~_+nL1m2v9`?d3{POw3lkbofea8ve~V_+ZyzJ|=R- z_hduX91TN|k|M5GCO*>g3fC`R7OBm?es56!lun;{h3EP$PTb|B$QL0^wQJ`_H z00hO@Cka_sgzAk+40u>_W~Cl|f#NV2Rx?Ro%~)xzF_5tF<1hU|gGiQc7;pEU4P99L zS-y>mxo!8dx1)pryH@$UXt3>x(P3`k`#x6rFE9ZUAZgjLg|GYOeVei6Zn0^1m}W9v zn4#*W(x2T4`L1zAvD1lK)9ya*Nx{vkGwYTMQFqVlVvRSDc`+ug_x(qo9p;WrH_O1z zQ||+kom(=_>87N+!*M@3JouGZ;ojvvQo_g8GZA7?+>U{-{0@g8a901#jqL5o-}}GW zsu7ugW0SW{w;9@9e@vvoPnCrwH$jn`?+>;QJAB9iK;W zga00XHWCz##&*CwW4?H25y0!hmgzhJ*Wfpfml5rej_Cgv`B;uP|JNJy^9lP&7I&Q| z2xsR+jsEmmK)Ri@>T-C3mDycUZq7ku; zA}Al``3snU_h^V|uNU)YmkdB#7sG8GBi5!c*&RY`4N2 z_!1L^T9EF1D6dfF3FaWj7`apR{1MJ3tr5?;;@ZBu{DBYLQi}OxG|;(oEJ+UQD2{)0 zh|0ex^WpFgmJ3kLW&y5f0s}SZpJ+j8T5AZDy>U44i_IWdktSLpAk81rJ5BJ{vGRZn zj?blBDP7iP5P6LS-+jemjk;yw*J+WIobF4-3)9!hY!r`G9aPL z)PnXP@uQhpUtLD2G6bD6ve_Xs{Af1H!qiX24|9?o zA?ma*jaSl}1Duw|l$4@Pa`N6fHYz)1&(gh7f^|?c6}#c~ls7i>uq)x8p>km37q!A9 zvV^8YyD5RFEzv|)E;$*??Zn{<_Sly4)~6E7<7*tVazQ$4bmhdkO}V^wal%At11e7M*;6dSThS@^{{5uULADhgfF@iDLIGrFEP^v2anQDuth{qlDAPd=;#+ zSVRU4+UJSDoVVpRMXxowN%(2$?NYR`)$EO(u{2oei;Qv#r6z>wnQI7{<#WQ=iRmOE zdU~M=5xX`r6mIoJ@;TF8vk9!$$yoV}MEV?(EZY?TdNp~4C&HVCAyibd5HB*tqVZ=N zPU5z^0S2x16VFo$Qws+qN*Wl-L```s3(*p6N65#1RFKh+r6+jW3R#i*S!mHue%xY2 zqH!ie4@JEejfB3e)}L&b#EcyYMv|kJ{ECtk(V{{8nF=DCBAqgAgFv`c2BUhR$2@I7kfrV%JhoH&%ie7=|WDbdo3}Y$-Yu zNwLt%lBFqNUHJGDany^x%5P;Ye@qqHrG zrqsT2#_>*3S4q|sSd=vKlB~ypYiQJ!C|5)yGBYa-U;5M4L%HL_D=MPYTp6Smcoi*m z;=T-%U>8-?krNFL68v;AU@R|}6xGJ6#%oUn_Aj$n4DnEFX=#UIRpJrsvw*i1Wn(2& z6e}`s$m-Z*S|4ie>Ncx1A#4Mjkpuiin@j*j{ zHESsGOHP6A*t#Jj|{Q|%Ie@RF#->b!)0}fr7RP~ z(Zx|Ew9=aO1qoUyySx@)eV2s9I2{z{5R%cmWIWSdRfp-Ii| z*G(V@rLSzO=6+vZXY(P{Ao#6zn$o0!8?9+Rb1yw%_))Nf2VFZG^%h-GA8A%ylTMH; zU$zxih>b#(8kvF(HbOXGP%dsu%AHioEMEtYg_y1gl&MUU;~wKCO090#EnTD*OM4UA zMJ_XgRVG>wEMOyy!dJvkV(CiBB&H|Yi+b5cZkkV~powSG$rjO8_at$LUH<4d^mr2+ zML&T-2@=u4sVDfH-*Pf)h)NRV*|(xYIR>SQJ}6?l%WD``$_R5vnX$TWX>vKI%#Hq> z>0)dXp5CUG;a~sePLOR?kn*8UL61p-PwH)#|5MKlhSPt&0L5B*Wt9xmvKu92MQm&p zl0kqZRuP7J;vJ(l<6)j%&R&Xe!^Sl?y=RpU;`@!RrquHNbF#^g%IDJ2 zI$gs&qC~!eFkMg`|H$!K=^K{&s($vIcOYSw%NUI}{Yi>eGPjysC!UommV&}6e5V&R zRfP_GI5W3~B*_m(h7?4rRQV@&XxthIe>`lY&;TsntAi4Mpq>)FmI`!)2YRP`awY-?cEU8;Oi?n|!Q^;aBz+cWtqq1|IOmQLZ_pB6Y#bDcYbx$a4_ zKzF>wa8}Rqsg64&0+ z`{51dA(dXZi9cVvi5Bivo!wq=hson_3|vu(<$v0?oqlwK9NQaL+T7Z2AQw1!9V-oJ zjw}H{zt_-*->1{4pZrQxUPF5aySg19gW8pKIj8k*xU)Cwor_UN!VN>`pr3osAAGvk zxRl&p=YPiOzQmVUzKO5RDYbgdqeQM+7(2O!A1tRf`Q68|;evNgZ0@@p5C3GuII?OJ zVSQuQJ;qa*BUguYi7m$pNU2**Jc<7^;(V-nsP%p z7_(qagr*c>wV0diHajQ33Nl~R!^H4scm(@`aqqX&ADkn`^N3VAb diff --git a/demos/resources/image/export-print.png b/demos/resources/image/export-print.png deleted file mode 100644 index 335636724a8feafb28c725eb4c7010ed029ade68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9484 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 Z+|Abq2Yl2CX8KPAP|{MYR`?k5{{Sq+=EeX3 diff --git a/demos/resources/image/export-svg.png b/demos/resources/image/export-svg.png deleted file mode 100644 index 4bba8a6987080136b7569d67e6f34effa52e7336..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11844 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 diff --git a/demos/resources/image/export.png b/demos/resources/image/export.png deleted file mode 100644 index cb4e3d5f161e272db3f7c30667281b5b01c86045..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7480 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 diff --git a/demos/resources/image/home-automation.png b/demos/resources/image/home-automation.png new file mode 100644 index 0000000000000000000000000000000000000000..cb5d143d074f082e2bf92b34b5ccccd6522e90e2 GIT binary patch literal 7649 zcmchccQhMr^#40m^@9$rRlD|Vv{sGQrc})sMXeBo*50~MVh0hsR;*f4u|jPjR;-Fm z5F^wk*0=bm%#J?Gwg&inN|k#FB<(9*EbT)A?ER!dXW;L4S&xPKN; zef{4JgI12rl`Gd`-|85tU0zw~e1Cs`Y;5f8?5wr5_4M@A#Kh$M{2YtL9v>f{oSaZ7l*!4-&d$!?zkeSd z9?s0ntgo-<<>eI@7iVT>4h;=;baa@Snl85_69@zx4tH>HU}k1!VPUbbu&}eU0|Ww( zj*i^i+>l7*-rnBo>gvkM%7+gh`uqDUD=Uda;`sRZ=H_NuS=s35=*Y-OUtgbudc36N=jN>T$f&EUb9Z+S z3=DK|aBy~ZzBt*-$;k-|3kwYmMWImX>FF1ggY&(W+}vCU1k%{pn2?ZgO6>FS@TjP$ z`2PKSQBjeXm)F_)*u}v{P*BkEP+m<<4HOD3DJk*u^ZWeyb4p5zudgp@b!B;;a5Rtu z27|r5y-ydr&Nd09rKO&pp7X6)3(cwX9rqNV!E$d5qIjI=NUrd`rcGs*)w-x{NHJ}EO4U%AQ9xXI~4 ztsm)Sj|^;(wEIBefAU{Vl$>0YHk6>hS%>>m_XM(`gKJLpLc-nz1Y`%FyN3+QqEjs1 z8rLzXB!?&s!ro3{jz&+W_p**M0^P7o{|fp4bv{w^KsZ|@spJ_%mm(92s(sHtK9rZ& zx^z`-sy4K*Zl_YQkha6QcVK3X+No7Yewq$fOU*cisi98zntSQ(V-@C9>)niCb zFi0}`^+=H&YWqvkAj%d9onNY|WXq0D$}I!Jb!|;``Cy&r*%wMj*M)I6ITal3@_TjM zFT1BgnNve4MaVxl{PlSZ*D&W1zDhw-)Yg{=Eg>(M4|7!u4UQIHXE#-f35W-6_gOQl zSbOzs2Jf`r6}p+-ayoVG7)pg$LtC4-E;o2*w-`ps3$WPNULNspm=K|JKc=Fo5Xsxk zmDRXGrSh-8spE^Em@xdJL#Kb~c@})xo@`luI@zcc_WLg2K-3!SFydq+mv7D{egSbd<>kWW$^v-nc5Fiv~f9kGy2Zs9` zi;cjByL)+MD9$W6cs(pgP3lfYjCa^k;pLO3+g<>X9}8o4>YmLRy0A;fg(scZb%k7_ z6)!fV7iduRqh}H`SixN$pM%}V<3@0tpybamy`&pu#7f(lHrBH#m3xEzG`xH6Os6*U zH%ONzg_NfEu{1}$w-ieGP$r&=g*qFL*te~*x0^g4azjMsxDEd-=2@n9pE}53m(ufI z6%No5nhB+aQX@;fB8w;0i4TI4F*?}b3^D3DdBJi4298wvaB*fEezA;u$|rT9)BV|GuyNVW-B(9! za|invjjo$3sX|x!HNx|DJ8KVMtc>f|`b5C>_$tzt96dqO6fo!KplpLn4DbU0*9B_P z0=!RQ5lD;&-ay(rV`hF5#IZrB6L^987KV+b7!CI$i6`8Q{QsZ`}`^7!!B)H^U{X| z&HNr2RyqVI#;Q2<052L-E~s3%BnsJxX*X82RR{y9#A0q8awxv?YUh#MU|QP%MV%IX z?$3Jgc zTA#zLu$NV8ug#|XF!nWSoxxI+T42O}(1_`^q)D|5%EiBSms-N}t^G?jsmI~Sp7}tD ze4M+L?_PE&ji_XBtm4Bet$JkwI~Od&E>~?f*X@oS>%0AG*PIE`x5YI{-yv2&T0j0x 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 literal 0 HcmV?d00001 diff --git a/demos/resources/image/pdfexport.png b/demos/resources/image/pdfexport.png new file mode 100644 index 0000000000000000000000000000000000000000..20fa5a586e9779acbe88f271d12e7767837696a6 GIT binary patch literal 15913 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 UyI;ND{&$6wlTniXEeQ(#KTpvH)Bpeg literal 0 HcmV?d00001 diff --git a/demos/resources/image/printing.png b/demos/resources/image/printing.png index e5f1d16ea9dc0d937e7f682c57fa238e2fe0ec76..8b3e2a8fc6f6f3654e0d7ba017b14f4f1df63685 100644 GIT binary patch literal 22289 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 JO7mIl{{gx6Z}I>D diff --git a/demos/resources/image/scriptloading.png b/demos/resources/image/scriptloading.png deleted file mode 100644 index d80ad59e430e67e3cbe20c42a60f34ec3bcf57f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75218 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 Eg3O9`T>t<8 diff --git a/demos/resources/image/svgexport.png b/demos/resources/image/svgexport.png new file mode 100644 index 0000000000000000000000000000000000000000..ef696fca887fdc642ca1c119d8f66ea421d3566f GIT binary patch literal 18804 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 eiV|TyIQl>OR0#e<+H!XQ0000p8WD=RH6 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∨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∋UyCLi$GyHU1kBu-?|t-n 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", "license": "https://www.yworks.com/products/yfiles-for-html/sla", "private": true, @@ -12,7 +12,6 @@ "./tutorial.css": "./style/tutorial.css" }, "dependencies": { - "yfiles": "^26.0.0 || ^26.0.0-A", - "yfiles-umd": "^26.0.0 || ^26.0.0-A" + "yfiles": "^26.0.0 || ^26.0.0-A" } } diff --git a/demos/resources/readme-demo-data.js b/demos/resources/readme-demo-data.js index 673feff53..8d2c37ac3 100644 --- a/demos/resources/readme-demo-data.js +++ b/demos/resources/readme-demo-data.js @@ -917,6 +917,18 @@ function getDemoData() { 'webgl2' ] }, + { + id: 'home-automation', + name: 'Home Automation', + demoPath: 'showcase/home-automation/', + summary: 'Demonstrates visual programming of a home automation network.', + description: `This demo simulates a tool for visually programming a home automation network. The nodes represent + various stages of data flow within the system.`, + category: 'showcase', + thumbnailPath: 'resources/image/home-automation.png', + tags: ['interaction', 'layout', 'drag and drop'], + keywords: ['v2.6.0.3', 'validation', 'grid'] + }, { id: 'tree-map', name: 'Tree Map', @@ -2650,7 +2662,7 @@ function getDemoData() { demoPath: 'view/imageexport/', summary: 'Shows how to export the whole diagram or a part of it to a PNG image.', category: 'view', - thumbnailPath: 'resources/image/export.png', + thumbnailPath: 'resources/image/imageexport.png', tags: ['export', 'png', 'jpg'], keywords: ['jpeg', 'bitmap', 'save', 'handles'] }, @@ -2660,7 +2672,7 @@ function getDemoData() { demoPath: 'view/svgexport/', summary: 'Shows how to export the whole diagram or a part of it to an SVG image.', category: 'view', - thumbnailPath: 'resources/image/export.png', + thumbnailPath: 'resources/image/svgexport.png', tags: ['export', 'svg', 'vector graphics'], keywords: ['scalable vector graphics', 'save', 'handles', 'curves', 'bezier'] }, @@ -2670,7 +2682,7 @@ function getDemoData() { demoPath: 'view/pdfexport/', summary: 'Shows how to export the whole diagram or a part of it to a PDF.', category: 'view', - thumbnailPath: 'resources/image/export.png', + thumbnailPath: 'resources/image/pdfexport.png', tags: ['export', 'pdf'], keywords: ['vector graphics', 'handles'] }, @@ -2680,7 +2692,7 @@ function getDemoData() { demoPath: 'view/printing/', summary: 'Shows how to print the whole diagram or a part of it.', category: 'view', - thumbnailPath: 'resources/image/export.png', + thumbnailPath: 'resources/image/printing.png', tags: ['printing'], keywords: ['posters', 'vector graphics', 'handles'] }, @@ -2900,17 +2912,6 @@ function getDemoData() { thumbnailPath: 'resources/image/web_components.png', tags: ['web components', 'shadow dom', 'html imports'] }, - { - id: 'amd-loading', - name: 'AMD Loading', - demoPath: 'loading/amdloading/', - summary: 'Loads the yFiles library modules with the AMD loading standard (require.js).', - category: 'loading', - languageType: 'js-only', - thumbnailPath: 'resources/image/amdloading.png', - tags: ['loader', 'modules'], - keywords: ['requirejs', 'require.js', 'non-symbolic'] - }, { id: 'basic-module-loading', name: 'Basic Module Loading', @@ -2933,17 +2934,6 @@ function getDemoData() { tags: ['deployment', 'optimizer'], keywords: ['v2.2.0.0', 'web worker', 'modules'] }, - { - id: 'script-loading', - name: 'Script Loading', - demoPath: 'loading/scriptloading/', - summary: 'Loads the yFiles modules using plain old <script> tags.', - category: 'loading', - languageType: 'js-only', - thumbnailPath: 'resources/image/scriptloading.png', - tags: ['loader', 'modules'], - keywords: ['script loading', 'non-symbolic'] - }, { id: 'web-worker-webpack', name: 'Web Worker Webpack', @@ -2976,27 +2966,6 @@ function getDemoData() { tags: ['modules', 'web worker', 'layout'], keywords: ['v2.4.0.0', 'threads', 'threading', 'background', 'async', 'modules', 'hierarchic'] }, - { - id: 'web-worker-umd', - name: 'Web Worker UMD', - demoPath: 'loading/webworker-umd/', - summary: 'Shows how to run a yFiles layout algorithm in a Web Worker task using AMD modules.', - category: 'loading', - languageType: 'js-only', - thumbnailPath: 'resources/image/webworkerumd.png', - tags: ['umd', 'web worker', 'layout'], - keywords: [ - 'v2.4.0.0', - 'threads', - 'threading', - 'background', - 'json', - 'folding', - 'hierarchic', - 'non-symbolic', - 'umd' - ] - }, { id: 'webpack', name: 'webpack', diff --git a/demos/resources/readme-demo-support.js b/demos/resources/readme-demo-support.js index 3cbfb39ff..086430a8d 100644 --- a/demos/resources/readme-demo-support.js +++ b/demos/resources/readme-demo-support.js @@ -26,39 +26,37 @@ ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ** ***************************************************************************/ -/* eslint-disable no-var,no-eval */ -;(function () { - var isTsReadme = location.pathname.includes('demos-ts') - var isJsReadme = location.pathname.includes('demos-js') +/* eslint-disable no-eval */ +;(() => { + const isTsReadme = location.pathname.includes('demos-ts') + const isJsReadme = location.pathname.includes('demos-js') document.body.className += isTsReadme ? ' ts' : isJsReadme ? ' js' : '' const demoData = window.getDemoData() - var categoryNames = window.getCategoryNames() - var layoutCategories = window.getLayoutCategories() + const categoryNames = window.getCategoryNames() + const layoutCategories = window.getLayoutCategories() - var tutorialIds = demoData.filter(item => item.category === 'tutorial').map(item => item.id) + const tutorialIds = demoData.filter((item) => item.category === 'tutorial').map((item) => item.id) - var isViewerPackage = document.title.indexOf('Viewer') > -1 - var isLayoutPackage = document.title.indexOf('Layout') > -1 - var isCompletePackage = !isViewerPackage && !isLayoutPackage + const isViewerPackage = document.title.indexOf('Viewer') > -1 + const isLayoutPackage = document.title.indexOf('Layout') > -1 + const isCompletePackage = !isViewerPackage && !isLayoutPackage - var demos = demoData.filter(function (demo) { - return !demo.hidden - }) + const demos = demoData.filter((demo) => !demo.hidden) const accordionItems = [] if (isTsReadme) { // Thumbnails are only in /demos-js/ directory - demos.forEach(function (demo) { + demos.forEach((demo) => { if (demo.thumbnailPath != null) { demo.thumbnailPath = '../demos-js/' + demo.thumbnailPath } }) } - demos.forEach(function (item) { + demos.forEach((item) => { item.availableInPackage = isCompletePackage || (isViewerPackage && @@ -67,16 +65,16 @@ (isLayoutPackage && item.distributionType === 'no-viewer') }) - var gridItemTemplate = document.querySelector('#grid-item-template') - var accordionItemTemplate = document.querySelector('#accordion-template') - var demoHeaderTemplate = document.querySelector('#demo-header-template') + const gridItemTemplate = document.querySelector('#grid-item-template') + const accordionItemTemplate = document.querySelector('#accordion-template') + const demoHeaderTemplate = document.querySelector('#demo-header-template') function createGridItem(demo, index) { - var gridItem = document.createElement('div') + const gridItem = document.createElement('div') gridItem.className = 'grid-item' gridItem.innerHTML = gridItemTemplate.innerHTML.replace( /{{([^}]+)}}/gi, - function (match, propertyName) { + (match, propertyName) => { if (propertyName === 'demoPath' && isTsReadme && demo.languageType === 'js-only') { return '../demos-js/' + demo.demoPath } else if (propertyName === 'demoPath' && isJsReadme && demo.languageType === 'ts-only') { @@ -91,10 +89,10 @@ } ) if (demo.tags) { - var tagContainer = gridItem.querySelector('.tags') - demo.tags.forEach(function (tag) { - var tagItem = document.createElement('span') - var anchor = document.createElement('a') + const tagContainer = gridItem.querySelector('.tags') + demo.tags.forEach((tag) => { + const tagItem = document.createElement('span') + const anchor = document.createElement('a') anchor.setAttribute('href', '#' + encodeURIComponent(tag)) tagItem.className += ' tag' anchor.textContent = tag @@ -103,14 +101,14 @@ }) } - var languageTypeBadge = createLanguageTypeBatch(demo) + const languageTypeBadge = createLanguageTypeBatch(demo) if (languageTypeBadge != null) { gridItem.querySelector('.thumbnail').appendChild(languageTypeBadge) } if (!demo.availableInPackage) { gridItem.className += ' not-available' - var notAvailableNotice = document.createElement('div') + const notAvailableNotice = document.createElement('div') notAvailableNotice.className = 'not-available-notice' notAvailableNotice.innerHTML = `
@@ -60,49 +62,25 @@
-
- - - -
- - yFiles for HTML - - - Demos - - AMD Loading Demo -
-
+ + + + - - - - - - - - - + + -
@@ -119,54 +97,52 @@
Start here
-

Amd Loading Demo

+

Home Automation Demo

- This demo loads the yFiles module resources using the require function that is - defined in the AMD loading standard. -

-

- In order to run this demo, please run npm install in the demo folder. This - will download the require.js AMD loader. -

-

- The default AMD Loader in use by this demo is the - RequireJS loader. -

-

- Resources that have a .js ending are loaded as JavaScript files. Loading a - resource without the .js postfix loads the resource as a module. Only the - required top-level modules in the reference-tree have to be required explicitly. Dependent - modules are resolved and loaded automatically without having to specify them. -

-

- Please take a look at the HTML sources of this document to see how to load modules using - require. -

-

- Modules can be loaded dynamically at runtime when certain resources are needed. This - improves the app's loading speed at startup. In order to observe dynamic loading at - runtime, hit the layout button. The modules layout-hierarchic and - graph-layout-bridge needed for layout calculation are loaded when the button is - hit for the first time. Please take a look at the demo's source code to see how dynamic - loading works. -

-

- In order to improve code completion, install the UMD variant of yFiles locally as npm - module. + This demo simulates a tool for visually programming a home automation network. The nodes + represent various stages of data flow within the system. The graph features basic + validation: output ports can only be connected to input ports, no edges can be duplicated, + and nodes with required properties display an error indicator if some of those properties + are missing.

+ +

Things to Try

+
    +
  • Add nodes by dragging and dropping them from the node palette on the right.
  • +
  • + Connect nodes. Note that the output port (right) of one node can only connect to the + input port (left) of another node. +
  • +
  • Reconnect existing edges to other nodes.
  • +
  • Select a node to highlight all of its connected edges.
  • +
  • Move connected nodes around to see how edges dynamically change their shape.
  • +
  • Apply automatic Layout to arrange the graph hierarchically.
  • +
  • Select a node to view and edit its properties in the right panel.
  • +
  • + Enter a long label in the node properties to observe how the node size changes. +
  • +
  • + Note that some node properties are required. If not set, the corresponding node will + display an error indicator. Hover over a node to see a popup with more details about the + missing properties. +
  • +
  • Save/load the current graph using JSON.
  • +
+
- - - - - - + diff --git a/demos/showcase/home-automation/inputMode/FlowCreateEdgeInputMode.js b/demos/showcase/home-automation/inputMode/FlowCreateEdgeInputMode.js new file mode 100644 index 000000000..026ded373 --- /dev/null +++ b/demos/showcase/home-automation/inputMode/FlowCreateEdgeInputMode.js @@ -0,0 +1,57 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { BaseClass, CreateEdgeInputMode, InputModeEventArgs, Point } from 'yfiles' +import { getSmoothEdgeControlPoints } from '../FlowEdge/FlowEdge.js' +import { validatePortTag } from '../FlowNode/FlowNodePort.js' + +export class FlowCreateEdgeInputMode extends BaseClass(CreateEdgeInputMode) { + /** + * @param {!InputModeEventArgs} evt + */ + onMoved(evt) { + super.onMoved(evt) + + const dummyEdge = this.dummyEdge + const dummyEdgeGraph = this.dummyEdgeGraph + + const portTag = dummyEdge.sourcePort?.tag + if (!validatePortTag(portTag)) { + return + } + + const bends = getSmoothEdgeControlPoints({ + start: this.startPoint, + end: Point.from(this.dragPoint), + fromSide: portTag.side + }) + + dummyEdgeGraph.clearBends(dummyEdge) + dummyEdgeGraph.addBends(dummyEdge, bends) + } +} diff --git a/demos/showcase/home-automation/inputMode/FlowCreateEdgeInputMode.ts b/demos/showcase/home-automation/inputMode/FlowCreateEdgeInputMode.ts new file mode 100644 index 000000000..ff6c3ef38 --- /dev/null +++ b/demos/showcase/home-automation/inputMode/FlowCreateEdgeInputMode.ts @@ -0,0 +1,54 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { BaseClass, CreateEdgeInputMode, InputModeEventArgs, Point } from 'yfiles' +import { getSmoothEdgeControlPoints } from '../FlowEdge/FlowEdge' +import { validatePortTag } from '../FlowNode/FlowNodePort' + +export class FlowCreateEdgeInputMode extends BaseClass(CreateEdgeInputMode) { + protected onMoved(evt: InputModeEventArgs) { + super.onMoved(evt) + + const dummyEdge = this.dummyEdge + const dummyEdgeGraph = this.dummyEdgeGraph + + const portTag = dummyEdge.sourcePort?.tag + if (!validatePortTag(portTag)) { + return + } + + const bends = getSmoothEdgeControlPoints({ + start: this.startPoint, + end: Point.from(this.dragPoint), + fromSide: portTag.side + }) + + dummyEdgeGraph.clearBends(dummyEdge) + dummyEdgeGraph.addBends(dummyEdge, bends) + } +} diff --git a/demos/showcase/home-automation/inputMode/FlowMoveInputMode.js b/demos/showcase/home-automation/inputMode/FlowMoveInputMode.js new file mode 100644 index 000000000..ef0fa76ef --- /dev/null +++ b/demos/showcase/home-automation/inputMode/FlowMoveInputMode.js @@ -0,0 +1,52 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { BaseClass, IEnumerable, INode, InputModeEventArgs, MoveInputMode } from 'yfiles' +import { recalculateEdges } from '../FlowEdge/FlowEdge.js' + +export class FlowMoveInputMode extends BaseClass(MoveInputMode) { + /** + * @param {!InputModeEventArgs} evt + */ + onDragging(evt) { + super.onDragging(evt) + + // Get graph component and affected nodes + const graph = this.inputModeContext?.graph + const nodes = this.affectedItems.filter((item) => INode.isInstance(item)) + + if (!graph) { + return + } + + // Iterate over affected nodes to access connected edges + nodes.forEach((node) => { + recalculateEdges(graph, node) + }) + } +} diff --git a/demos/showcase/home-automation/inputMode/FlowMoveInputMode.ts b/demos/showcase/home-automation/inputMode/FlowMoveInputMode.ts new file mode 100644 index 000000000..1a6e827af --- /dev/null +++ b/demos/showcase/home-automation/inputMode/FlowMoveInputMode.ts @@ -0,0 +1,49 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { BaseClass, IEnumerable, INode, InputModeEventArgs, MoveInputMode } from 'yfiles' +import { recalculateEdges } from '../FlowEdge/FlowEdge' + +export class FlowMoveInputMode extends BaseClass(MoveInputMode) { + protected onDragging(evt: InputModeEventArgs) { + super.onDragging(evt) + + // Get graph component and affected nodes + const graph = this.inputModeContext?.graph + const nodes = this.affectedItems.filter(item => INode.isInstance(item)) as IEnumerable + + if (!graph) { + return + } + + // Iterate over affected nodes to access connected edges + nodes.forEach(node => { + recalculateEdges(graph, node) + }) + } +} diff --git a/demos/showcase/home-automation/inputMode/configureInputMode.js b/demos/showcase/home-automation/inputMode/configureInputMode.js new file mode 100644 index 000000000..13a43667e --- /dev/null +++ b/demos/showcase/home-automation/inputMode/configureInputMode.js @@ -0,0 +1,101 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + GraphComponent, + GraphEditorInputMode, + GraphItemTypes, + INode, + ItemHoverInputMode +} from 'yfiles' +import { FlowMoveInputMode } from './FlowMoveInputMode.js' +import { FlowCreateEdgeInputMode } from './FlowCreateEdgeInputMode.js' +import { configureCreateEdgeInputMode } from '../FlowEdge/FlowEdge.js' + +/** + * @param {!GraphComponent} gc + */ +export function configureInputMode(gc) { + // Highlight any nodes/edges being hovered, and bring them + // to the top of their respective group (particularly important for edges). + const itemHoverInputMode = new ItemHoverInputMode({ + enabled: true, + hoverItems: GraphItemTypes.NODE | GraphItemTypes.EDGE + }) + itemHoverInputMode.addHoveredItemChangedListener(({ inputModeContext }, { item }) => { + const gc = inputModeContext?.canvasComponent + if (!(gc instanceof GraphComponent)) { + return + } + gc.highlightIndicatorManager.clearHighlights() + if (item) { + gc.highlightIndicatorManager.addHighlight(item) + gc.graphModelManager.toFront(item) + } + }) + + gc.selection.addItemSelectionChangedListener((_sender, { item, itemSelected }) => { + if (itemSelected && item instanceof INode) { + const connectedEdges = gc.graph.edgesAt(item) + gc.graphModelManager.toFront(connectedEdges) + } + }) + + // Custom CreateEdgeInputMode for overwriting onMoved method + const createEdgeInputMode = new FlowCreateEdgeInputMode() + configureCreateEdgeInputMode(createEdgeInputMode) + + // Custom MoveInputMode for overwriting onDragging method + const moveInputMode = new FlowMoveInputMode() + const moveUnselectedInputMode = new FlowMoveInputMode() + + const inputMode = new GraphEditorInputMode({ + allowCreateNode: false, + allowEditLabel: false, + allowReverseEdge: false, + showHandleItems: GraphItemTypes.EDGE, + movableItems: GraphItemTypes.NODE, + selectableItems: GraphItemTypes.NODE | GraphItemTypes.EDGE, + marqueeSelectableItems: GraphItemTypes.NODE, + clickHitTestOrder: [GraphItemTypes.NODE, GraphItemTypes.EDGE, GraphItemTypes.PORT], + moveInputMode, + moveUnselectedInputMode, + createEdgeInputMode, + itemHoverInputMode + }) + + inputMode.moveUnselectedInputMode.enabled = true + // Increase priority over handleInputMode to not block edge creation by dragging from ports + inputMode.moveUnselectedInputMode.priority = inputMode.handleInputMode.priority + 1 + inputMode.moveInputMode.priority = inputMode.handleInputMode.priority + 1 + + inputMode.marqueeSelectionInputMode.enabled = true + inputMode.marqueeSelectionInputMode.priority = inputMode.moveUnselectedInputMode.priority + 1 + + gc.inputMode = inputMode +} diff --git a/demos/showcase/home-automation/inputMode/configureInputMode.ts b/demos/showcase/home-automation/inputMode/configureInputMode.ts new file mode 100644 index 000000000..dd92c7bf9 --- /dev/null +++ b/demos/showcase/home-automation/inputMode/configureInputMode.ts @@ -0,0 +1,98 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + GraphComponent, + GraphEditorInputMode, + GraphItemTypes, + INode, + ItemHoverInputMode +} from 'yfiles' +import { FlowMoveInputMode } from './FlowMoveInputMode' +import { FlowCreateEdgeInputMode } from './FlowCreateEdgeInputMode' +import { configureCreateEdgeInputMode } from '../FlowEdge/FlowEdge' + +export function configureInputMode(gc: GraphComponent): void { + // Highlight any nodes/edges being hovered, and bring them + // to the top of their respective group (particularly important for edges). + const itemHoverInputMode = new ItemHoverInputMode({ + enabled: true, + hoverItems: GraphItemTypes.NODE | GraphItemTypes.EDGE + }) + itemHoverInputMode.addHoveredItemChangedListener(({ inputModeContext }, { item }) => { + const gc = inputModeContext?.canvasComponent + if (!(gc instanceof GraphComponent)) { + return + } + gc.highlightIndicatorManager.clearHighlights() + if (item) { + gc.highlightIndicatorManager.addHighlight(item) + gc.graphModelManager.toFront(item) + } + }) + + gc.selection.addItemSelectionChangedListener((_sender, { item, itemSelected }) => { + if (itemSelected && item instanceof INode) { + const connectedEdges = gc.graph.edgesAt(item) + gc.graphModelManager.toFront(connectedEdges) + } + }) + + // Custom CreateEdgeInputMode for overwriting onMoved method + const createEdgeInputMode = new FlowCreateEdgeInputMode() + configureCreateEdgeInputMode(createEdgeInputMode) + + // Custom MoveInputMode for overwriting onDragging method + const moveInputMode = new FlowMoveInputMode() + const moveUnselectedInputMode = new FlowMoveInputMode() + + const inputMode = new GraphEditorInputMode({ + allowCreateNode: false, + allowEditLabel: false, + allowReverseEdge: false, + showHandleItems: GraphItemTypes.EDGE, + movableItems: GraphItemTypes.NODE, + selectableItems: GraphItemTypes.NODE | GraphItemTypes.EDGE, + marqueeSelectableItems: GraphItemTypes.NODE, + clickHitTestOrder: [GraphItemTypes.NODE, GraphItemTypes.EDGE, GraphItemTypes.PORT], + moveInputMode, + moveUnselectedInputMode, + createEdgeInputMode, + itemHoverInputMode + }) + + inputMode.moveUnselectedInputMode.enabled = true + // Increase priority over handleInputMode to not block edge creation by dragging from ports + inputMode.moveUnselectedInputMode.priority = inputMode.handleInputMode.priority + 1 + inputMode.moveInputMode.priority = inputMode.handleInputMode.priority + 1 + + inputMode.marqueeSelectionInputMode.enabled = true + inputMode.marqueeSelectionInputMode.priority = inputMode.moveUnselectedInputMode.priority + 1 + + gc.inputMode = inputMode +} diff --git a/demos/showcase/home-automation/layout/HierarchicLayout.js b/demos/showcase/home-automation/layout/HierarchicLayout.js new file mode 100644 index 000000000..bc61fc88d --- /dev/null +++ b/demos/showcase/home-automation/layout/HierarchicLayout.js @@ -0,0 +1,81 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + HierarchicLayout, + HierarchicLayoutData, + HierarchicLayoutEdgeRoutingStyle, + ILayoutAlgorithm, + LayoutData, + LayoutOrientation, + PortConstraint, + PortSide, + SimplexNodePlacer +} from 'yfiles' + +/** + * @returns {!object} + */ +export function createHierarchicLayoutConfiguration() { + const layout = new HierarchicLayout() + const defaultEdgeDescriptor = layout.edgeLayoutDescriptor + defaultEdgeDescriptor.minimumFirstSegmentLength = 5 + defaultEdgeDescriptor.minimumLastSegmentLength = 5 + defaultEdgeDescriptor.routingStyle.defaultEdgeRoutingStyle = + HierarchicLayoutEdgeRoutingStyle.CURVED + + const defaultNodeDescriptor = layout.nodeLayoutDescriptor + defaultNodeDescriptor.layerAlignment = 0.5 + + layout.layoutOrientation = LayoutOrientation.LEFT_TO_RIGHT + // specify a preferred maximum duration to prevent very long runtimes for LARGE graphs + layout.maximumDuration = 5000 + // the minimum vertical distance between nodes in subsequent layers + layout.minimumLayerDistance = 30 + // the minimum horizontal distance between nodes and long edges that span multiple layers + layout.nodeToEdgeDistance = 10 + // the minimum horizontal distance between two nodes in the same layer + layout.nodeToNodeDistance = 30 + // try to reduce the number of bends in edges that connect nodes in subsequent layers + // this produces more readable results in this demo scenario + const placer = layout.nodePlacer + placer.straightenEdges = true + // disable the barycenter node placer mode for straightenEdges option to take effect + placer.barycenterMode = false + // reflects our standard spacing so that nodes end up aligned with the snap grid + layout.gridSpacing = 15 + + // create and configure layout data for the hierarchic layout algorithm + // this makes sure that edges start and end in the correct ports + const layoutData = new HierarchicLayoutData({ + sourcePortConstraints: PortConstraint.create(PortSide.EAST, true), + targetPortConstraints: PortConstraint.create(PortSide.WEST, true) + }) + + return { layout, layoutData } +} diff --git a/demos/showcase/home-automation/layout/HierarchicLayout.ts b/demos/showcase/home-automation/layout/HierarchicLayout.ts new file mode 100644 index 000000000..301392096 --- /dev/null +++ b/demos/showcase/home-automation/layout/HierarchicLayout.ts @@ -0,0 +1,81 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + HierarchicLayout, + HierarchicLayoutData, + HierarchicLayoutEdgeRoutingStyle, + ILayoutAlgorithm, + LayoutData, + LayoutOrientation, + PortConstraint, + PortSide, + SimplexNodePlacer +} from 'yfiles' + +export function createHierarchicLayoutConfiguration(): { + layout: ILayoutAlgorithm + layoutData: LayoutData +} { + const layout = new HierarchicLayout() + const defaultEdgeDescriptor = layout.edgeLayoutDescriptor + defaultEdgeDescriptor.minimumFirstSegmentLength = 5 + defaultEdgeDescriptor.minimumLastSegmentLength = 5 + defaultEdgeDescriptor.routingStyle.defaultEdgeRoutingStyle = + HierarchicLayoutEdgeRoutingStyle.CURVED + + const defaultNodeDescriptor = layout.nodeLayoutDescriptor + defaultNodeDescriptor.layerAlignment = 0.5 + + layout.layoutOrientation = LayoutOrientation.LEFT_TO_RIGHT + // specify a preferred maximum duration to prevent very long runtimes for LARGE graphs + layout.maximumDuration = 5000 + // the minimum vertical distance between nodes in subsequent layers + layout.minimumLayerDistance = 30 + // the minimum horizontal distance between nodes and long edges that span multiple layers + layout.nodeToEdgeDistance = 10 + // the minimum horizontal distance between two nodes in the same layer + layout.nodeToNodeDistance = 30 + // try to reduce the number of bends in edges that connect nodes in subsequent layers + // this produces more readable results in this demo scenario + const placer = layout.nodePlacer as SimplexNodePlacer + placer.straightenEdges = true + // disable the barycenter node placer mode for straightenEdges option to take effect + placer.barycenterMode = false + // reflects our standard spacing so that nodes end up aligned with the snap grid + layout.gridSpacing = 15 + + // create and configure layout data for the hierarchic layout algorithm + // this makes sure that edges start and end in the correct ports + const layoutData = new HierarchicLayoutData({ + sourcePortConstraints: PortConstraint.create(PortSide.EAST, true), + targetPortConstraints: PortConstraint.create(PortSide.WEST, true) + }) + + return { layout, layoutData } +} diff --git a/demos/showcase/home-automation/layout/initializeSnapping.js b/demos/showcase/home-automation/layout/initializeSnapping.js new file mode 100644 index 000000000..5527c3277 --- /dev/null +++ b/demos/showcase/home-automation/layout/initializeSnapping.js @@ -0,0 +1,48 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, GraphEditorInputMode, GraphSnapContext, LabelSnapContext } from 'yfiles' + +/** + * @param {!GraphComponent} graphComponent + */ +export function initializeSnapping(graphComponent) { + const gcInputMode = graphComponent.inputMode + const graphSnapContext = new GraphSnapContext({ + enabled: true, + snapNodesToSnapLines: true, + snapBendAdjacentSegments: false, + snapBendsToSnapLines: false, + snapOrthogonalMovement: false, + snapPortAdjacentSegments: false, + snapSegmentsToSnapLines: false + }) + const labelSnapContext = new LabelSnapContext() + gcInputMode.snapContext = graphSnapContext + gcInputMode.labelSnapContext = labelSnapContext +} diff --git a/demos/showcase/home-automation/layout/initializeSnapping.ts b/demos/showcase/home-automation/layout/initializeSnapping.ts new file mode 100644 index 000000000..f22e4f1ef --- /dev/null +++ b/demos/showcase/home-automation/layout/initializeSnapping.ts @@ -0,0 +1,45 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, GraphEditorInputMode, GraphSnapContext, LabelSnapContext } from 'yfiles' + +export function initializeSnapping(graphComponent: GraphComponent): void { + const gcInputMode = graphComponent.inputMode as GraphEditorInputMode + const graphSnapContext = new GraphSnapContext({ + enabled: true, + snapNodesToSnapLines: true, + snapBendAdjacentSegments: false, + snapBendsToSnapLines: false, + snapOrthogonalMovement: false, + snapPortAdjacentSegments: false, + snapSegmentsToSnapLines: false + }) + const labelSnapContext = new LabelSnapContext() + gcInputMode.snapContext = graphSnapContext + gcInputMode.labelSnapContext = labelSnapContext +} diff --git a/demos/showcase/home-automation/layout/initilaizeGrid.js b/demos/showcase/home-automation/layout/initilaizeGrid.js new file mode 100644 index 000000000..a35f0d625 --- /dev/null +++ b/demos/showcase/home-automation/layout/initilaizeGrid.js @@ -0,0 +1,63 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + GraphComponent, + GraphEditorInputMode, + GraphSnapContext, + GridConstraintProvider, + GridInfo, + GridSnapTypes, + GridStyle, + GridVisualCreator, + INode, + Stroke +} from 'yfiles' + +/** + * @param {!GraphComponent} graphComponent + * @returns {!GridVisualCreator} + */ +export function initializeGrid(graphComponent) { + const gridInfo = new GridInfo() + gridInfo.horizontalSpacing = 15 + gridInfo.verticalSpacing = 15 + + const grid = new GridVisualCreator(gridInfo) + grid.gridStyle = GridStyle.DOTS + grid.stroke = new Stroke('#4F4F4F', 0.5) + grid.visibilityThreshold = 10 + graphComponent.backgroundGroup.addChild(grid) + + const gInputMode = graphComponent.inputMode + const graphSnapContext = gInputMode.snapContext + graphSnapContext.nodeGridConstraintProvider = new GridConstraintProvider(gridInfo) + graphSnapContext.gridSnapType = GridSnapTypes.LINES + + return grid +} diff --git a/demos/showcase/home-automation/layout/initilaizeGrid.ts b/demos/showcase/home-automation/layout/initilaizeGrid.ts new file mode 100644 index 000000000..ee5628324 --- /dev/null +++ b/demos/showcase/home-automation/layout/initilaizeGrid.ts @@ -0,0 +1,59 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + GraphComponent, + GraphEditorInputMode, + GraphSnapContext, + GridConstraintProvider, + GridInfo, + GridSnapTypes, + GridStyle, + GridVisualCreator, + INode, + Stroke +} from 'yfiles' + +export function initializeGrid(graphComponent: GraphComponent): GridVisualCreator { + const gridInfo = new GridInfo() + gridInfo.horizontalSpacing = 15 + gridInfo.verticalSpacing = 15 + + const grid = new GridVisualCreator(gridInfo) + grid.gridStyle = GridStyle.DOTS + grid.stroke = new Stroke('#4F4F4F', 0.5) + grid.visibilityThreshold = 10 + graphComponent.backgroundGroup.addChild(grid) + + const gInputMode = graphComponent.inputMode as GraphEditorInputMode + const graphSnapContext = gInputMode.snapContext as GraphSnapContext + graphSnapContext.nodeGridConstraintProvider = new GridConstraintProvider(gridInfo) + graphSnapContext.gridSnapType = GridSnapTypes.LINES + + return grid +} diff --git a/demos/showcase/home-automation/layout/runLayout.js b/demos/showcase/home-automation/layout/runLayout.js new file mode 100644 index 000000000..70ba82174 --- /dev/null +++ b/demos/showcase/home-automation/layout/runLayout.js @@ -0,0 +1,41 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { createHierarchicLayoutConfiguration } from './HierarchicLayout.js' +import { GraphComponent } from 'yfiles' + +/** + * @param {!GraphComponent} graphComponent + * @returns {!Promise} + */ +export async function runLayout(graphComponent) { + const { layout: hierarchicLayout, layoutData: hierarchicLayoutData } = + createHierarchicLayoutConfiguration() + + await graphComponent.morphLayout(hierarchicLayout, '1s', hierarchicLayoutData) +} diff --git a/demos/showcase/home-automation/layout/runLayout.ts b/demos/showcase/home-automation/layout/runLayout.ts new file mode 100644 index 000000000..c65d5b28e --- /dev/null +++ b/demos/showcase/home-automation/layout/runLayout.ts @@ -0,0 +1,37 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { createHierarchicLayoutConfiguration } from './HierarchicLayout' +import { GraphComponent } from 'yfiles' + +export async function runLayout(graphComponent: GraphComponent): Promise { + const { layout: hierarchicLayout, layoutData: hierarchicLayoutData } = + createHierarchicLayoutConfiguration() + + await graphComponent.morphLayout(hierarchicLayout, '1s', hierarchicLayoutData) +} diff --git a/demos/showcase/home-automation/resources/style.css b/demos/showcase/home-automation/resources/style.css new file mode 100644 index 000000000..1d865b30c --- /dev/null +++ b/demos/showcase/home-automation/resources/style.css @@ -0,0 +1,251 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ + +.sidebar-slim { + position: relative; + display: flex; + flex-direction: column; + background: var(--toolbar-color); + line-height: 140%; + border-left: 1px solid #d5d7d8; + width: 274px; +} + +#drag-and-drop-panel { + padding: 8px 0 0 8px; + width: 100%; + height: 50%; + overflow-y: auto; + display: flex; + flex-direction: column; + align-items: center; +} + +.demo-dnd-panel__item { + height: 64px; + padding: 8px 0; +} + +.demo-dnd-panel__item, +.demo-dnd-panel__item * { + cursor: move !important; +} +#tags-explorer { + width: 100%; + height: 50%; + padding: 16px 0; + border-top: 5px solid #d5d7d8; +} + +.tags-explorer__title { + padding: 12px; + height: 41px; + margin: 0; + color: #676767; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: 0.28px; +} + +#tags-explorer-list { + width: 100%; + list-style-type: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + overflow: auto; + max-height: calc(100% - 41px); +} +.tags-explorer-list-item { + width: 100%; + display: flex; + align-items: center; + padding: 8px 12px; +} +.tags-explorer-list-label { + flex: 1 0 0; + display: flex; + color: #282828; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: normal; +} +.tags-explorer-list-input { + max-width: 50%; + display: flex; + padding: 8px; + align-items: center; + gap: 4px; + border-radius: 8px; + border: 1px solid #D5D7D8; + background: #FFF; +} +.tags-explorer-list-input:focus { + outline: #2F80ED solid 2px; +} + +.tags-explorer-list-input:disabled { + background: #D5D7D8; +} + +.tags-explorer-list-item__invalid .tags-explorer-list-input { + border: #CB1414 1px solid; +} + +.sidebar { + position: relative; + display: flex; + flex-direction: column; + background: var(--toolbar-color); + line-height: 140%; + width: var(--sidebar-width); + border-left: 1px solid #d5d7d8; +} + +.tooltip { + position: relative; + padding-left: 12px; +} + +.tooltip:before { + position: absolute; + top: 0; + bottom: 0; + left: 0; + content: ""; + width: 4px; + border-radius: 100px; + background: #CB1414; +} + +.tooltip h4 { + margin: 4px; + color: #2E2E2E; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 148%; /* 20.72px */ +} + +.tooltip ul { + margin: 4px; + color: #2E2E2E; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 148%; /* 20.72px */ +} + +.yfiles-tooltip { + border-radius: 12px; + border: 1px solid #D5D7D8; + background: #FFF; + /* light shadow */ + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); + display: inline-flex; + padding: 12px; + justify-content: center; + align-items: center; + gap: 8px; +} + +.flow-context-menu { + display: inline-flex; + padding: 12px; + flex-direction: column; + align-items: flex-start; + gap: 8px; + border-radius: 12px; + border: 1px solid #D5D7D8; + background: #FFF; + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); + top: 0; + left: 0; + list-style: none; + margin: 0; + min-width: 186px; + width: auto; + user-select: none; + opacity: 0; + /* the context menu is faded in */ + transition: opacity 0.2s ease-in; +} +.flow-context-menu--visible { + opacity: 1; +} +.flow-context-menu--clone { + pointer-events: none; + transition: opacity 0.2s ease-out; +} +.flow-context-menu__item { + position: relative; + display: flex; + gap: 8px; + align-items: center; + min-width: fit-content; + width: 100%; + padding: 8px 12px; + background-color: #FFFFFF; + cursor: pointer; + border: none; + border-radius: 8px; + color: #2E2E2E; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 148%; /* 20.72px */ +} + +.flow-context-menu__item-icon { + width: 16px; + height: 16px; +} + +.flow-context-menu__item-disabled { + background: #FFFFFF; + opacity: 32%; +} +.flow-context-menu__item:hover { + background-color: #EBEBEB !important; +} +.flow-context-menu__item:focus { + outline: solid #EBEBEB 1px; +} +.flow-context-menu__separator { + width: 100%; + height: 1px; + background-color: #D5D7D8; +} +.flow-context-menu:last-child { + border-bottom: none; +} diff --git a/demos/showcase/home-automation/resources/weather-data.js b/demos/showcase/home-automation/resources/weather-data.js new file mode 100644 index 000000000..cffc2b416 --- /dev/null +++ b/demos/showcase/home-automation/resources/weather-data.js @@ -0,0 +1,168 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +export const SampleData = { + nodes: [ + { + position: [0, -46], + properties: { + variant: 'commonStatus', + label: 'Humidity', + status: 'humidity' + } + }, + { + position: [0, -121], + properties: { + variant: 'commonStatus', + label: 'Temperature', + status: 'temperature' + } + }, + { + position: [203.64501953125, -46], + properties: { + variant: 'functionFunction', + label: 'analyzeHumidity', + function: + 'if (msg.payload > 70) { msg.suggestion = "Open the window"; } else if (msg.payload < 30) { msg.suggestion = "Use a humidifier"; } else { msg.suggestion = "Nothing to do."; }' + } + }, + { + position: [420, -46], + properties: { + variant: 'functionDelay', + label: 'delay', + pauseType: 'delay', + timeout: '5', + timeoutUnits: 'minutes' + } + }, + { + position: [883.3037109375, -46], + properties: { + variant: 'commonLinkOut', + label: 'Send Message' + } + }, + { + position: [191.576171875, -121], + properties: { + variant: 'functionFunction', + label: 'analyzeTemperature', + function: + 'if (msg.payload > 25) { msg.suggestion = "Open the window"; } else if (msg.payload < 18) { msg.suggestion = "Turn on the heating"; } else { msg.suggestion = "Nothing to do"; }' + } + }, + { + position: [690, -46], + properties: { + variant: 'sequenceJoin', + label: 'join', + mode: 'auto', + build: 'object', + property: 'payload', + propertyType: 'msg', + key: 'topic', + joiner: '\\n', + joinerType: 'str', + accumulate: false, + timeout: '', + count: '', + reduceRight: false + } + }, + { + position: [420, -121], + properties: { + variant: 'functionDelay', + label: 'delay', + pauseType: 'delay', + timeout: '5', + timeoutUnits: 'minutes' + } + } + ], + edges: [ + { + bends: [], + sourceNodeIndex: 0, + targetNodeIndex: 2 + }, + { + bends: [], + sourceNodeIndex: 2, + targetNodeIndex: 3 + }, + { + bends: [], + sourceNodeIndex: 3, + targetNodeIndex: 6 + }, + { + bends: [], + sourceNodeIndex: 1, + targetNodeIndex: 5 + }, + { + bends: [], + sourceNodeIndex: 6, + targetNodeIndex: 4 + }, + { + bends: [ + [575, -105], + [577.3992919921875, -104.853515625], + [582.4761962890625, -103.681640625], + [587.8021240234375, -101.337890625], + [593.2305908203125, -97.822265625], + [598.6151123046875, -93.134765625], + [603.8092041015625, -87.275390625], + [608.6663818359375, -80.244140625], + [613.0401611328125, -72.041015625], + [615, -67.5], + [616.988525390625, -62.958984375], + [621.580810546875, -54.755859375], + [626.846923828125, -47.724609375], + [632.611083984375, -41.865234375], + [638.697509765625, -37.177734375], + [644.930419921875, -33.662109375], + [651.134033203125, -31.318359375], + [657.132568359375, -30.146484375], + [660, -30] + ], + sourceNodeIndex: 7, + targetNodeIndex: 6 + }, + { + bends: [], + sourceNodeIndex: 5, + targetNodeIndex: 7 + } + ] +} diff --git a/demos/showcase/home-automation/resources/weather-data.ts b/demos/showcase/home-automation/resources/weather-data.ts new file mode 100644 index 000000000..d83c4c360 --- /dev/null +++ b/demos/showcase/home-automation/resources/weather-data.ts @@ -0,0 +1,170 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { type SerializableGraphData } from '../ImportExportManager/GraphData' + +export const SampleData: SerializableGraphData = { + nodes: [ + { + position: [0, -46], + properties: { + variant: 'commonStatus', + label: 'Humidity', + status: 'humidity' + } + }, + { + position: [0, -121], + properties: { + variant: 'commonStatus', + label: 'Temperature', + status: 'temperature' + } + }, + { + position: [203.64501953125, -46], + properties: { + variant: 'functionFunction', + label: 'analyzeHumidity', + function: + 'if (msg.payload > 70) { msg.suggestion = "Open the window"; } else if (msg.payload < 30) { msg.suggestion = "Use a humidifier"; } else { msg.suggestion = "Nothing to do."; }' + } + }, + { + position: [420, -46], + properties: { + variant: 'functionDelay', + label: 'delay', + pauseType: 'delay', + timeout: '5', + timeoutUnits: 'minutes' + } + }, + { + position: [883.3037109375, -46], + properties: { + variant: 'commonLinkOut', + label: 'Send Message' + } + }, + { + position: [191.576171875, -121], + properties: { + variant: 'functionFunction', + label: 'analyzeTemperature', + function: + 'if (msg.payload > 25) { msg.suggestion = "Open the window"; } else if (msg.payload < 18) { msg.suggestion = "Turn on the heating"; } else { msg.suggestion = "Nothing to do"; }' + } + }, + { + position: [690, -46], + properties: { + variant: 'sequenceJoin', + label: 'join', + mode: 'auto', + build: 'object', + property: 'payload', + propertyType: 'msg', + key: 'topic', + joiner: '\\n', + joinerType: 'str', + accumulate: false, + timeout: '', + count: '', + reduceRight: false + } + }, + { + position: [420, -121], + properties: { + variant: 'functionDelay', + label: 'delay', + pauseType: 'delay', + timeout: '5', + timeoutUnits: 'minutes' + } + } + ], + edges: [ + { + bends: [], + sourceNodeIndex: 0, + targetNodeIndex: 2 + }, + { + bends: [], + sourceNodeIndex: 2, + targetNodeIndex: 3 + }, + { + bends: [], + sourceNodeIndex: 3, + targetNodeIndex: 6 + }, + { + bends: [], + sourceNodeIndex: 1, + targetNodeIndex: 5 + }, + { + bends: [], + sourceNodeIndex: 6, + targetNodeIndex: 4 + }, + { + bends: [ + [575, -105], + [577.3992919921875, -104.853515625], + [582.4761962890625, -103.681640625], + [587.8021240234375, -101.337890625], + [593.2305908203125, -97.822265625], + [598.6151123046875, -93.134765625], + [603.8092041015625, -87.275390625], + [608.6663818359375, -80.244140625], + [613.0401611328125, -72.041015625], + [615, -67.5], + [616.988525390625, -62.958984375], + [621.580810546875, -54.755859375], + [626.846923828125, -47.724609375], + [632.611083984375, -41.865234375], + [638.697509765625, -37.177734375], + [644.930419921875, -33.662109375], + [651.134033203125, -31.318359375], + [657.132568359375, -30.146484375], + [660, -30] + ], + sourceNodeIndex: 7, + targetNodeIndex: 6 + }, + { + bends: [], + sourceNodeIndex: 5, + targetNodeIndex: 7 + } + ] +} diff --git a/demos/showcase/home-automation/utils/configureDragAndDrop.js b/demos/showcase/home-automation/utils/configureDragAndDrop.js new file mode 100644 index 000000000..e67831299 --- /dev/null +++ b/demos/showcase/home-automation/utils/configureDragAndDrop.js @@ -0,0 +1,38 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, GraphEditorInputMode } from 'yfiles' + +/** + * @param {!GraphComponent} graphComponent + */ +export function configureDragAndDrop(graphComponent) { + const nodeDropInputMode = graphComponent.inputMode.nodeDropInputMode + nodeDropInputMode.enabled = true + nodeDropInputMode.showPreview = true +} diff --git a/demos/showcase/home-automation/utils/configureDragAndDrop.ts b/demos/showcase/home-automation/utils/configureDragAndDrop.ts new file mode 100644 index 000000000..fb0d7a174 --- /dev/null +++ b/demos/showcase/home-automation/utils/configureDragAndDrop.ts @@ -0,0 +1,35 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, GraphEditorInputMode } from 'yfiles' + +export function configureDragAndDrop(graphComponent: GraphComponent): void { + const nodeDropInputMode = (graphComponent.inputMode as GraphEditorInputMode).nodeDropInputMode + nodeDropInputMode.enabled = true + nodeDropInputMode.showPreview = true +} diff --git a/demos/showcase/home-automation/utils/configureGraphEvents.js b/demos/showcase/home-automation/utils/configureGraphEvents.js new file mode 100644 index 000000000..3b3852502 --- /dev/null +++ b/demos/showcase/home-automation/utils/configureGraphEvents.js @@ -0,0 +1,39 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent } from 'yfiles' + +/** + * @param {!GraphComponent} graphComponent + */ +export function configureGraphEvents(graphComponent) { + // trigger a re-rendering of the graph when a node tag changed to have the labels reflect the data + graphComponent.graph.addNodeTagChangedListener(() => { + graphComponent.invalidate() + }) +} diff --git a/demos/showcase/home-automation/utils/configureGraphEvents.ts b/demos/showcase/home-automation/utils/configureGraphEvents.ts new file mode 100644 index 000000000..2afb07be4 --- /dev/null +++ b/demos/showcase/home-automation/utils/configureGraphEvents.ts @@ -0,0 +1,36 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent } from 'yfiles' + +export function configureGraphEvents(graphComponent: GraphComponent): void { + // trigger a re-rendering of the graph when a node tag changed to have the labels reflect the data + graphComponent.graph.addNodeTagChangedListener(() => { + graphComponent.invalidate() + }) +} diff --git a/demos/showcase/home-automation/utils/customTriggers.js b/demos/showcase/home-automation/utils/customTriggers.js new file mode 100644 index 000000000..8adc2491a --- /dev/null +++ b/demos/showcase/home-automation/utils/customTriggers.js @@ -0,0 +1,60 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { runLayout } from '../layout/runLayout.js' +import { GraphComponent, GraphEditorInputMode, GridVisualCreator } from 'yfiles' + +/** + * @param {!GraphComponent} graphComponent + */ +export async function runAutoLayout(graphComponent) { + const layoutButton = document.querySelector('#layoutButton') + // disable and re-enable the button before and after morphing the layout + layoutButton.setAttribute('disabled', 'disabled') + try { + await runLayout(graphComponent) + } catch (e) { + alert(`An error occurred during layout ${e}`) + } finally { + layoutButton.removeAttribute('disabled') + } +} + +/** + * @param {!GraphComponent} graphComponent + * @param {!GridVisualCreator} grid + */ +export function triggerGridDisplay(graphComponent, grid) { + const gridButton = document.querySelector('#grid-button') + const gcInputModeSnapContext = graphComponent.inputMode.snapContext + grid.visible = gridButton.checked + if (gcInputModeSnapContext) { + gcInputModeSnapContext.enabled = gridButton.checked + } + graphComponent.invalidate() // triggers repaint +} diff --git a/demos/showcase/home-automation/utils/customTriggers.ts b/demos/showcase/home-automation/utils/customTriggers.ts new file mode 100644 index 000000000..d867fb94b --- /dev/null +++ b/demos/showcase/home-automation/utils/customTriggers.ts @@ -0,0 +1,53 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { runLayout } from '../layout/runLayout' +import { GraphComponent, GraphEditorInputMode, GridVisualCreator } from 'yfiles' + +export async function runAutoLayout(graphComponent: GraphComponent) { + const layoutButton = document.querySelector('#layoutButton')! + // disable and re-enable the button before and after morphing the layout + layoutButton.setAttribute('disabled', 'disabled') + try { + await runLayout(graphComponent) + } catch (e) { + alert(`An error occurred during layout ${e}`) + } finally { + layoutButton.removeAttribute('disabled') + } +} + +export function triggerGridDisplay(graphComponent: GraphComponent, grid: GridVisualCreator) { + const gridButton = document.querySelector('#grid-button')! + const gcInputModeSnapContext = (graphComponent.inputMode as GraphEditorInputMode).snapContext + grid.visible = gridButton.checked + if (gcInputModeSnapContext) { + gcInputModeSnapContext.enabled = gridButton.checked + } + graphComponent.invalidate() // triggers repaint +} diff --git a/demos/loading/webworker-umd/WorkerLayout.js b/demos/showcase/home-automation/utils/elementUtils.js similarity index 51% rename from demos/loading/webworker-umd/WorkerLayout.js rename to demos/showcase/home-automation/utils/elementUtils.js index be8401e3d..70be0046a 100644 --- a/demos/loading/webworker-umd/WorkerLayout.js +++ b/demos/showcase/home-automation/utils/elementUtils.js @@ -26,55 +26,44 @@ ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ** ***************************************************************************/ -;(() => { - // eslint-disable-next-line no-undef - importScripts('https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js') +import { EventArgs, ICommand } from 'yfiles' - // @yjs:keep = onError - require.onError = error => { - throw error - } - require.config({ - packages: [ - { - name: 'yfiles-umd', - location: '../../../node_modules/yfiles-umd', - main: 'complete' - } - ] - }) +/** + * Binds the given command to the element specified by the given selector. + * + * In more detail, register a click listener that executes the command and an event listener that + * syncs the disabled state of the command to the button. + * @param {!string} selector + * @param {!ICommand} command + * @param {*} target + * @param {*} parameter + * @param {!string} tooltip + */ - require(['yfiles-umd/view-layout-bridge', 'yfiles-umd/layout-hierarchic'], ( - /** @param {yfiles} yfiles */ yfiles - ) => { - const { LayoutExecutorAsyncWorker, HierarchicLayout, MinimumNodeSizeStage } = yfiles +export function bindYFilesCommand(selector, command, target, parameter, tooltip) { + const element = document.querySelector(selector) + // Check whether an event listener is already registered + if (!element || element.getAttribute('data-command-registered')) { + return + } - function applyLayout(graph, layoutDescriptor) { - if (layoutDescriptor.name === 'HierarchicLayout') { - // create and apply a new hierarchic layout using the given layout properties - // and wrap it in a MinimumNodeSizeStage - const layout = new MinimumNodeSizeStage(new HierarchicLayout(layoutDescriptor.properties)) - layout.applyLayout(graph) - } + // Add a click listener that executes the command + element.addEventListener('click', () => { + if (command.canExecute(parameter, target)) { + command.execute(parameter, target) } + }) - fetch('../../../lib/license.json') - .then(response => response.json()) - .then(licenseData => { - yfiles.License.value = licenseData - - // signal that the webworker thread is ready to execute - postMessage('ready') - }) - - self.addEventListener( - 'message', - e => { - // create a new remote layout executor - const executor = new LayoutExecutorAsyncWorker(applyLayout) - executor.process(e.data).then(postMessage).catch(postMessage) - }, - false - ) + // Add an event listener that syncs the disabled state of the command to the element + command.addCanExecuteChangedListener((command, _evt) => { + if (command.canExecute(parameter, target)) { + element.removeAttribute('disabled') + } else { + element.setAttribute('disabled', 'disabled') + } }) -})() + + // Mark the element as having an event listener, and add a tooltip + element.setAttribute('data-command-registered', '') + element.setAttribute('title', tooltip) +} diff --git a/demos/showcase/home-automation/utils/elementUtils.ts b/demos/showcase/home-automation/utils/elementUtils.ts new file mode 100644 index 000000000..726b7a8aa --- /dev/null +++ b/demos/showcase/home-automation/utils/elementUtils.ts @@ -0,0 +1,70 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { EventArgs, ICommand } from 'yfiles' + +/** + * Binds the given command to the element specified by the given selector. + * + * In more detail, register a click listener that executes the command and an event listener that + * syncs the disabled state of the command to the button. + */ + +export function bindYFilesCommand( + selector: string, + command: ICommand, + target: any, + parameter: any, + tooltip: string +): void { + const element = document.querySelector(selector) + // Check whether an event listener is already registered + if (!element || element.getAttribute('data-command-registered')) { + return + } + + // Add a click listener that executes the command + element.addEventListener('click', () => { + if (command.canExecute(parameter, target)) { + command.execute(parameter, target) + } + }) + + // Add an event listener that syncs the disabled state of the command to the element + command.addCanExecuteChangedListener((command: ICommand, _evt: EventArgs) => { + if (command.canExecute(parameter, target)) { + element.removeAttribute('disabled') + } else { + element.setAttribute('disabled', 'disabled') + } + }) + + // Mark the element as having an event listener, and add a tooltip + element.setAttribute('data-command-registered', '') + element.setAttribute('title', tooltip) +} diff --git a/demos/showcase/isometricdrawing/IsometricDrawingDemo.js b/demos/showcase/isometricdrawing/IsometricDrawingDemo.js index 725f9e595..d4e918a40 100644 --- a/demos/showcase/isometricdrawing/IsometricDrawingDemo.js +++ b/demos/showcase/isometricdrawing/IsometricDrawingDemo.js @@ -248,13 +248,13 @@ function initializeGraph() { // add handle that enables the user to change the height of a node graph.decorator.nodeDecorator.handleProviderDecorator.setImplementationWrapper( - n => !graph.isGroupNode(n), + (n) => !graph.isGroupNode(n), (node, delegateProvider) => new HeightHandleProvider(node, delegateProvider, MINIMUM_NODE_HEIGHT) ) graph.decorator.nodeDecorator.insetsProviderDecorator.setImplementation( - node => graph.isGroupNode(node), + (node) => graph.isGroupNode(node), INodeInsetsProvider.create(() => new Insets(10, 10, 10, 50)) ) @@ -285,7 +285,7 @@ async function loadGraph() { id: 'id', parentId: 'group', labels: ['label'], - layout: data => new Rect(0, 0, data.width, data.depth) + layout: (data) => new Rect(0, 0, data.width, data.depth) }) graphBuilder.createGroupNodesSource({ data: IsometricData.groupsSource, @@ -297,7 +297,7 @@ async function loadGraph() { sourceId: 'from', targetId: 'to' }) - edgesSource.edgeCreator.createLabelsSource(edgeData => [edgeData.label]) + edgesSource.edgeCreator.createLabelsSource((edgeData) => [edgeData.label]) graphBuilder.buildGraph() @@ -311,7 +311,7 @@ async function loadGraph() { function adaptGroupNodes() { const graph = graphComponent.graph - for (const groupNode of graph.nodes.filter(n => graph.isGroupNode(n))) { + for (const groupNode of graph.nodes.filter((n) => graph.isGroupNode(n))) { const nestingLevel = graph.groupingSupport.getPathToRoot(groupNode).size groupNode.tag.height = nestingLevel * 0.01 // make sure edges are still drawn on top of group nodes @@ -364,7 +364,7 @@ function initializeUI() { ) const rotationSlider = document.querySelector('#rotation') - rotationSlider.addEventListener('input', evt => { + rotationSlider.addEventListener('input', (evt) => { const isometricProjection = Matrix.ISOMETRIC.clone() isometricProjection.rotate(parseFloat(rotationSlider.value)) graphComponent.projection = isometricProjection diff --git a/demos/showcase/isometricdrawing/IsometricDrawingDemo.ts b/demos/showcase/isometricdrawing/IsometricDrawingDemo.ts index bd93df527..bc6f5cca8 100644 --- a/demos/showcase/isometricdrawing/IsometricDrawingDemo.ts +++ b/demos/showcase/isometricdrawing/IsometricDrawingDemo.ts @@ -234,13 +234,13 @@ function initializeGraph(): void { // add handle that enables the user to change the height of a node graph.decorator.nodeDecorator.handleProviderDecorator.setImplementationWrapper( - n => !graph.isGroupNode(n), + (n) => !graph.isGroupNode(n), (node, delegateProvider) => new HeightHandleProvider(node!, delegateProvider!, MINIMUM_NODE_HEIGHT) ) graph.decorator.nodeDecorator.insetsProviderDecorator.setImplementation( - node => graph.isGroupNode(node), + (node) => graph.isGroupNode(node), INodeInsetsProvider.create(() => new Insets(10, 10, 10, 50)) ) @@ -296,7 +296,7 @@ async function loadGraph(): Promise { function adaptGroupNodes(): void { const graph = graphComponent.graph - for (const groupNode of graph.nodes.filter(n => graph.isGroupNode(n))) { + for (const groupNode of graph.nodes.filter((n) => graph.isGroupNode(n))) { const nestingLevel = graph.groupingSupport.getPathToRoot(groupNode).size groupNode.tag.height = nestingLevel * 0.01 // make sure edges are still drawn on top of group nodes @@ -348,7 +348,7 @@ function initializeUI(): void { ) const rotationSlider = document.querySelector('#rotation')! - rotationSlider.addEventListener('input', evt => { + rotationSlider.addEventListener('input', (evt) => { const isometricProjection = Matrix.ISOMETRIC.clone() isometricProjection.rotate(parseFloat(rotationSlider.value)) graphComponent.projection = isometricProjection diff --git a/demos/showcase/isometricdrawing/index.html b/demos/showcase/isometricdrawing/index.html index eb67d7279..d60fcb6a4 100644 --- a/demos/showcase/isometricdrawing/index.html +++ b/demos/showcase/isometricdrawing/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/large-graphs/DemoConfiguration.js b/demos/showcase/large-graphs/DemoConfiguration.js index 4a33d2ccf..6cc48c570 100644 --- a/demos/showcase/large-graphs/DemoConfiguration.js +++ b/demos/showcase/large-graphs/DemoConfiguration.js @@ -155,7 +155,7 @@ export class DemoConfiguration { // add the bends if (edgeData.b != null) { const bendData = edgeData.b - bendData.forEach(bend => { + bendData.forEach((bend) => { graph.addBend(edge, bend) }) } diff --git a/demos/showcase/large-graphs/DemoConfiguration.ts b/demos/showcase/large-graphs/DemoConfiguration.ts index 8dbbf2b02..8c6519fdd 100644 --- a/demos/showcase/large-graphs/DemoConfiguration.ts +++ b/demos/showcase/large-graphs/DemoConfiguration.ts @@ -152,7 +152,7 @@ export abstract class DemoConfiguration { // add the bends if (edgeData.b != null) { const bendData = edgeData.b as { x: number; y: number }[] - bendData.forEach(bend => { + bendData.forEach((bend) => { graph.addBend(edge, bend) }) } diff --git a/demos/showcase/large-graphs/LargeGraphDemoConfiguration.js b/demos/showcase/large-graphs/LargeGraphDemoConfiguration.js index cd7190087..099c9d115 100644 --- a/demos/showcase/large-graphs/LargeGraphDemoConfiguration.js +++ b/demos/showcase/large-graphs/LargeGraphDemoConfiguration.js @@ -66,7 +66,7 @@ class LargeGraphDemoConfiguration extends DemoConfiguration { /** * Creates a random integer in the range [0, upper[. */ - getRandomInt = upper => Math.floor(Math.random() * upper) + getRandomInt = (upper) => Math.floor(Math.random() * upper) webGL2EdgeStyleProvider = (edge, graph) => { return this.webGL2EdgeStyle @@ -147,7 +147,7 @@ class LargeGraphDemoConfiguration extends DemoConfiguration { * @param {!HTMLImageElement} image */ createImageDataPromise(ctx, image) { - return new Promise(resolve => { + return new Promise((resolve) => { image.onload = () => { ctx.clearRect(0, 0, 128, 128) ctx.drawImage(image, 0, 0, 75, 75, 0, 0, 128, 128) diff --git a/demos/showcase/large-graphs/LargeGraphDemoConfiguration.ts b/demos/showcase/large-graphs/LargeGraphDemoConfiguration.ts index 4be89028e..27c9c6d95 100644 --- a/demos/showcase/large-graphs/LargeGraphDemoConfiguration.ts +++ b/demos/showcase/large-graphs/LargeGraphDemoConfiguration.ts @@ -143,7 +143,7 @@ abstract class LargeGraphDemoConfiguration extends DemoConfiguration { * Returns a promise, which draws an image into the canvas after loading it. */ private createImageDataPromise(ctx: CanvasRenderingContext2D, image: HTMLImageElement) { - return new Promise(resolve => { + return new Promise((resolve) => { image.onload = () => { ctx.clearRect(0, 0, 128, 128) ctx.drawImage(image, 0, 0, 75, 75, 0, 0, 128, 128) diff --git a/demos/showcase/large-graphs/LargeGraphsDemo.js b/demos/showcase/large-graphs/LargeGraphsDemo.js index c0d7158ca..5565448b8 100644 --- a/demos/showcase/large-graphs/LargeGraphsDemo.js +++ b/demos/showcase/large-graphs/LargeGraphsDemo.js @@ -99,7 +99,7 @@ async function loadGraph(graphComponent, config) { await config.initializeStyleDefaults(graph) const svgThresholdSelect = document.querySelector('#svgThreshold') const newIndex = Array.from(svgThresholdSelect.options).findIndex( - item => item.value === String(config.svgThreshold) + (item) => item.value === String(config.svgThreshold) ) svgThresholdSelect.selectedIndex = newIndex !== -1 ? newIndex : 1 renderingTypesManager = new RenderingTypesManager( @@ -142,7 +142,7 @@ function configureInteraction(graphComponent) { // Disable moving of individual edge segments graphComponent.graph.decorator.edgeDecorator.positionHandlerDecorator.hideImplementation() - graphComponent.graph.decorator.nodeDecorator.reshapeHandleProviderDecorator.setFactory(node => { + graphComponent.graph.decorator.nodeDecorator.reshapeHandleProviderDecorator.setFactory((node) => { const keepAspectRatio = new NodeReshapeHandleProvider( node, node.lookup(IReshapeHandler.$class), @@ -187,13 +187,13 @@ function updateGraphInformation(graph) { * @param {!GraphComponent} graphComponent */ function initRenderingInformationUI(graphComponent) { - graphComponent.addZoomChangedListener(graphComponent => { + graphComponent.addZoomChangedListener((graphComponent) => { updateRenderingInformationUI(graphComponent) }) updateRenderingInformationUI(graphComponent) // Show a popup when the rendering type changes - renderingTypesManager.addRenderingTypeChangedListener(newMode => { + renderingTypesManager.addRenderingTypeChangedListener((newMode) => { const thresholdPercent = Math.floor(renderingTypesManager.svgThreshold * 100) const renderingInfoPopup = document.querySelector('#renderingInfoPopup') renderingInfoPopup.textContent = @@ -226,7 +226,7 @@ function setUIDisabled(disabled) { document.querySelector('#sampleSelection').disabled = disabled document.querySelector('#svgThreshold').disabled = disabled - return new Promise(resolve => setTimeout(resolve, 0)) + return new Promise((resolve) => setTimeout(resolve, 0)) } /** @@ -236,7 +236,7 @@ function setUIDisabled(disabled) { function initToolbar(graphComponent) { const sampleSelect = document.querySelector('#sampleSelection') addOptions(sampleSelect, 'Hierarchic', 'Organic', 'OrgChart') - sampleSelect.addEventListener('change', async e => { + sampleSelect.addEventListener('change', async (e) => { await setUIDisabled(true) sampleSelect.disabled = true const hierarchicOrganicDescription = document.querySelector('#hierarchicOrganic') @@ -281,7 +281,7 @@ function initToolbar(graphComponent) { { text: '\u2265 100%', value: '1.0' }, { text: 'WebGL only', value: '-1' } ) - svgThresholdSelect.addEventListener('change', e => { + svgThresholdSelect.addEventListener('change', (e) => { const selectElement = e.target const newThreshold = Number(selectElement.value) renderingTypesManager.svgThreshold = newThreshold < 0 ? Number.POSITIVE_INFINITY : newThreshold diff --git a/demos/showcase/large-graphs/LargeGraphsDemo.ts b/demos/showcase/large-graphs/LargeGraphsDemo.ts index 35bc7126c..6e93fc8d0 100644 --- a/demos/showcase/large-graphs/LargeGraphsDemo.ts +++ b/demos/showcase/large-graphs/LargeGraphsDemo.ts @@ -94,7 +94,7 @@ async function loadGraph(graphComponent: GraphComponent, config: DemoConfigurati await config.initializeStyleDefaults(graph) const svgThresholdSelect = document.querySelector('#svgThreshold')! const newIndex = Array.from(svgThresholdSelect.options).findIndex( - item => item.value === String(config.svgThreshold) + (item) => item.value === String(config.svgThreshold) ) svgThresholdSelect.selectedIndex = newIndex !== -1 ? newIndex : 1 renderingTypesManager = new RenderingTypesManager( @@ -136,7 +136,7 @@ function configureInteraction(graphComponent: GraphComponent) { // Disable moving of individual edge segments graphComponent.graph.decorator.edgeDecorator.positionHandlerDecorator.hideImplementation() - graphComponent.graph.decorator.nodeDecorator.reshapeHandleProviderDecorator.setFactory(node => { + graphComponent.graph.decorator.nodeDecorator.reshapeHandleProviderDecorator.setFactory((node) => { const keepAspectRatio = new NodeReshapeHandleProvider( node, node.lookup(IReshapeHandler.$class) as IReshapeHandler, @@ -176,13 +176,13 @@ function updateGraphInformation(graph: IGraph) { * and zoom level. */ function initRenderingInformationUI(graphComponent: GraphComponent) { - graphComponent.addZoomChangedListener(graphComponent => { + graphComponent.addZoomChangedListener((graphComponent) => { updateRenderingInformationUI(graphComponent) }) updateRenderingInformationUI(graphComponent) // Show a popup when the rendering type changes - renderingTypesManager.addRenderingTypeChangedListener(newMode => { + renderingTypesManager.addRenderingTypeChangedListener((newMode) => { const thresholdPercent = Math.floor(renderingTypesManager.svgThreshold * 100) const renderingInfoPopup = document.querySelector('#renderingInfoPopup')! renderingInfoPopup.textContent = @@ -211,7 +211,7 @@ function setUIDisabled(disabled: boolean) { document.querySelector('#sampleSelection')!.disabled = disabled document.querySelector('#svgThreshold')!.disabled = disabled - return new Promise(resolve => setTimeout(resolve, 0)) + return new Promise((resolve) => setTimeout(resolve, 0)) } /** @@ -220,7 +220,7 @@ function setUIDisabled(disabled: boolean) { function initToolbar(graphComponent: GraphComponent): void { const sampleSelect = document.querySelector('#sampleSelection')! addOptions(sampleSelect, 'Hierarchic', 'Organic', 'OrgChart') - sampleSelect.addEventListener('change', async e => { + sampleSelect.addEventListener('change', async (e) => { await setUIDisabled(true) sampleSelect.disabled = true const hierarchicOrganicDescription = @@ -266,7 +266,7 @@ function initToolbar(graphComponent: GraphComponent): void { { text: '\u2265 100%', value: '1.0' }, { text: 'WebGL only', value: '-1' } ) - svgThresholdSelect.addEventListener('change', e => { + svgThresholdSelect.addEventListener('change', (e) => { const selectElement = e.target as HTMLSelectElement const newThreshold = Number(selectElement.value) renderingTypesManager.svgThreshold = newThreshold < 0 ? Number.POSITIVE_INFINITY : newThreshold diff --git a/demos/showcase/large-graphs/OrgChartDemoConfiguration.js b/demos/showcase/large-graphs/OrgChartDemoConfiguration.js index 704040c28..b3b836af4 100644 --- a/demos/showcase/large-graphs/OrgChartDemoConfiguration.js +++ b/demos/showcase/large-graphs/OrgChartDemoConfiguration.js @@ -97,7 +97,7 @@ export default class OrgChartDemoConfiguration extends DemoConfiguration { * @returns {!Promise} */ async initializeStyleDefaults(graph) { - return new Promise(resolve => { + return new Promise((resolve) => { // use the VuejsNodeStyle to display the nodes through a svg template // in this svg template you can see three styles in three zoom levels graph.nodeDefaults.style = new VuejsNodeStyle(this.nodeStyleTemplate) diff --git a/demos/showcase/large-graphs/OrgChartDemoConfiguration.ts b/demos/showcase/large-graphs/OrgChartDemoConfiguration.ts index 6b7402067..c1440f089 100644 --- a/demos/showcase/large-graphs/OrgChartDemoConfiguration.ts +++ b/demos/showcase/large-graphs/OrgChartDemoConfiguration.ts @@ -97,7 +97,7 @@ export default class OrgChartDemoConfiguration extends DemoConfiguration { } async initializeStyleDefaults(graph: IGraph): Promise { - return new Promise(resolve => { + return new Promise((resolve) => { // use the VuejsNodeStyle to display the nodes through a svg template // in this svg template you can see three styles in three zoom levels graph.nodeDefaults.style = new VuejsNodeStyle(this.nodeStyleTemplate) diff --git a/demos/showcase/large-graphs/SVGDataURLFetch.js b/demos/showcase/large-graphs/SVGDataURLFetch.js index 90471fa94..71ab02265 100644 --- a/demos/showcase/large-graphs/SVGDataURLFetch.js +++ b/demos/showcase/large-graphs/SVGDataURLFetch.js @@ -54,7 +54,7 @@ function encodeSvgDataUrl(data) { * @returns {!string} */ function escapeUnicodeSurrogatePair(text) { - return text.replace(new RegExp('[\uD800-\uDBFF][\uDC00-\uDFFF]', 'g'), txt => { + return text.replace(new RegExp('[\uD800-\uDBFF][\uDC00-\uDFFF]', 'g'), (txt) => { const high = txt.charCodeAt(0) const low = txt.charCodeAt(1) const codepoint = (((high & 0x03ff) << 10) | (low & 0x03ff)) + 0x10000 @@ -69,5 +69,5 @@ function escapeUnicodeSurrogatePair(text) { */ function escapeNonLatin1(text) { // eslint-disable-next-line no-control-regex - return text.replace(new RegExp('[^\x00-\x7F]', 'g'), txt => `&#${txt.charCodeAt(0)};`) + return text.replace(new RegExp('[^\x00-\x7F]', 'g'), (txt) => `&#${txt.charCodeAt(0)};`) } diff --git a/demos/showcase/large-graphs/SVGDataURLFetch.ts b/demos/showcase/large-graphs/SVGDataURLFetch.ts index 802c6bd9a..fa28c3631 100644 --- a/demos/showcase/large-graphs/SVGDataURLFetch.ts +++ b/demos/showcase/large-graphs/SVGDataURLFetch.ts @@ -52,7 +52,7 @@ function encodeSvgDataUrl(data: string): string { * @param text the text to escape */ function escapeUnicodeSurrogatePair(text: string): string { - return text.replace(new RegExp('[\uD800-\uDBFF][\uDC00-\uDFFF]', 'g'), txt => { + return text.replace(new RegExp('[\uD800-\uDBFF][\uDC00-\uDFFF]', 'g'), (txt) => { const high = txt.charCodeAt(0) const low = txt.charCodeAt(1) const codepoint = (((high & 0x03ff) << 10) | (low & 0x03ff)) + 0x10000 @@ -66,5 +66,5 @@ function escapeUnicodeSurrogatePair(text: string): string { */ function escapeNonLatin1(text: string): string { // eslint-disable-next-line no-control-regex - return text.replace(new RegExp('[^\x00-\x7F]', 'g'), txt => `&#${txt.charCodeAt(0)};`) + return text.replace(new RegExp('[^\x00-\x7F]', 'g'), (txt) => `&#${txt.charCodeAt(0)};`) } diff --git a/demos/showcase/large-graphs/index.html b/demos/showcase/large-graphs/index.html index f7f1227e1..7a0fe917e 100644 --- a/demos/showcase/large-graphs/index.html +++ b/demos/showcase/large-graphs/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/largegraphaggregation/AggregationHelper.js b/demos/showcase/largegraphaggregation/AggregationHelper.js index 30faa3db5..36686d037 100644 --- a/demos/showcase/largegraphaggregation/AggregationHelper.js +++ b/demos/showcase/largegraphaggregation/AggregationHelper.js @@ -103,7 +103,7 @@ export class AggregationHelper { */ get visibleNodes() { return this.aggregateGraph - ? this.aggregateGraph.nodes.filter(node => this.isOriginalNodeOrPlaceHolder(node)).size + ? this.aggregateGraph.nodes.filter((node) => this.isOriginalNodeOrPlaceHolder(node)).size : 0 } @@ -127,7 +127,7 @@ export class AggregationHelper { * @type {number} */ get visibleEdges() { - return this.aggregateGraph ? this.aggregateGraph.edges.filter(e => !e.tag).size : 0 + return this.aggregateGraph ? this.aggregateGraph.edges.filter((e) => !e.tag).size : 0 } /** @@ -321,7 +321,7 @@ export class AggregationHelper { const aggregate = aggregationInfo.aggregate const aggregatedItems = this.aggregateGraph .getAggregatedItems(node) - .filter(n => n !== aggregate.node) + .filter((n) => n !== aggregate.node) .toList() this.aggregateGraph.separate(node) @@ -402,7 +402,7 @@ export class AggregationHelper { this.aggregateGraph.wrappedGraph .edgesAt(originalNode, AdjacencyTypes.ALL) .toList() - .forEach(edge => { + .forEach((edge) => { if (edge.targetPort.owner === originalNode) { this.$createReplacementEdge(edge.sourceNode, node, edge, true) } else { diff --git a/demos/showcase/largegraphaggregation/AggregationHelper.ts b/demos/showcase/largegraphaggregation/AggregationHelper.ts index 8f96fbe83..f4144658d 100644 --- a/demos/showcase/largegraphaggregation/AggregationHelper.ts +++ b/demos/showcase/largegraphaggregation/AggregationHelper.ts @@ -101,7 +101,7 @@ export class AggregationHelper { public get visibleNodes(): number { return this.aggregateGraph - ? this.aggregateGraph.nodes.filter(node => this.isOriginalNodeOrPlaceHolder(node)).size + ? this.aggregateGraph.nodes.filter((node) => this.isOriginalNodeOrPlaceHolder(node)).size : 0 } @@ -117,7 +117,7 @@ export class AggregationHelper { } public get visibleEdges(): number { - return this.aggregateGraph ? this.aggregateGraph.edges.filter(e => !e.tag).size : 0 + return this.aggregateGraph ? this.aggregateGraph.edges.filter((e) => !e.tag).size : 0 } /** @@ -301,7 +301,7 @@ export class AggregationHelper { const aggregate = aggregationInfo.aggregate const aggregatedItems = this.aggregateGraph .getAggregatedItems(node) - .filter(n => n !== aggregate.node) + .filter((n) => n !== aggregate.node) .toList() this.aggregateGraph.separate(node) @@ -376,7 +376,7 @@ export class AggregationHelper { this.aggregateGraph .wrappedGraph!.edgesAt(originalNode, AdjacencyTypes.ALL) .toList() - .forEach(edge => { + .forEach((edge) => { if (edge.targetPort!.owner === originalNode) { this.$createReplacementEdge(edge.sourceNode!, node, edge, true) } else { diff --git a/demos/showcase/largegraphaggregation/LargeGraphAggregationDemo.js b/demos/showcase/largegraphaggregation/LargeGraphAggregationDemo.js index a2a44dbcd..b54a74ee6 100644 --- a/demos/showcase/largegraphaggregation/LargeGraphAggregationDemo.js +++ b/demos/showcase/largegraphaggregation/LargeGraphAggregationDemo.js @@ -309,7 +309,7 @@ function onHoveredItemChanged(sender, evt) { if (node && aggregationHelper.isOriginalNodeOrPlaceHolder(node)) { addHighlight(node) // and if it's a node, we highlight all adjacent edges, too - graphComponent.graph.edgesAt(node, AdjacencyTypes.ALL).forEach(adjacentEdge => { + graphComponent.graph.edgesAt(node, AdjacencyTypes.ALL).forEach((adjacentEdge) => { if (aggregationHelper.isHierarchyEdge(adjacentEdge)) { return } @@ -434,7 +434,7 @@ function createFilteredView() { // create a new FilteredGraphWrapper that filters the original graph and shows only the currently visible nodes const filteredGraph = new FilteredGraphWrapper( originalGraph, - node => { + (node) => { node = aggregationHelper.getPlaceholder(node) const aggregateGraph = aggregationHelper.aggregateGraph return aggregateGraph.contains(node) @@ -483,7 +483,7 @@ async function runAggregationAndReplaceGraph(originalGraph) { * @returns {!Promise} */ async function applyAggregation(originalGraph, aggregateGraph) { - return new Promise(resolve => { + return new Promise((resolve) => { const nodeAggregation = createConfiguredAggregation() const aggregationResult = nodeAggregation.run(originalGraph) @@ -549,12 +549,12 @@ async function runBalloonLayout(affectedNodes) { treeReductionStage.edgeBundling.bundlingStrength = 1 layout.prependStage(treeReductionStage) const nonTreeEdges = graphComponent.graph.edges - .filter(e => !aggregationHelper.isHierarchyEdge(e)) + .filter((e) => !aggregationHelper.isHierarchyEdge(e)) .toList() const treeReductionStageData = new TreeReductionStageData({ nonTreeEdges: nonTreeEdges, - edgeBundleDescriptors: edge => { + edgeBundleDescriptors: (edge) => { return new EdgeBundleDescriptor({ bundled: nonTreeEdges.includes(edge), bezierFitting: true @@ -604,7 +604,7 @@ async function runCactusLayout(affectedNodes) { const innerTreeNodes = [] const innerTreeNode2Descriptor = new Map() for (const node of graph.nodes) { - if (graph.outEdgesAt(node).some(e => aggregationHelper.isHierarchyEdge(e))) { + if (graph.outEdgesAt(node).some((e) => aggregationHelper.isHierarchyEdge(e))) { innerTreeNodes.push(node) innerTreeNode2Descriptor.set(node, new TemporaryGroupDescriptor()) } @@ -620,13 +620,13 @@ async function runCactusLayout(affectedNodes) { // ... members of the group are all the successor nodes temporaryGroup.source = graph .outEdgesAt(treeNode) - .filter(e => aggregationHelper.isHierarchyEdge(e)) - .map(e => e.targetNode) + .filter((e) => aggregationHelper.isHierarchyEdge(e)) + .map((e) => e.targetNode) // if the tree node has a parent too, then the parent descriptor must be setup as well const inEdge = graph .inEdgesAt(treeNode) - .filter(e => aggregationHelper.isHierarchyEdge(e)) + .filter((e) => aggregationHelper.isHierarchyEdge(e)) .at(0) if (inEdge) { const parentGroupDescriptor = innerTreeNode2Descriptor.get(inEdge.sourceNode) @@ -642,7 +642,7 @@ async function runCactusLayout(affectedNodes) { INode.$class, TemporaryGroupDescriptor.$class, 'INNER_TREE_NODES', - n => { + (n) => { if (innerTreeNode2Descriptor.has(n)) { return innerTreeNode2Descriptor.get(n) } @@ -712,13 +712,6 @@ class CustomCactusLayoutStage extends LayoutStageBase { * {@link TemporaryGroupDescriptor} for the {@link CactusGroupLayout} algorithm. */ class TemporaryGroupCustomizationStage extends LayoutStageBase { - /** - * @param {!CactusGroupLayout} cactus - */ - constructor(cactus) { - super(cactus) - } - /** * @param {!LayoutGraph} graph */ @@ -735,11 +728,11 @@ class TemporaryGroupCustomizationStage extends LayoutStageBase { // collect the temporary group nodes inserted by TemporaryGroupNodeInsertionStage earlier // and the respective original tree node const temporaryGroups = graph.nodes - .filter(n => isInsertedTmpGroupDp.getBoolean(n)) - .map(tmpGroup => { + .filter((n) => isInsertedTmpGroupDp.getBoolean(n)) + .map((tmpGroup) => { const child = grouping.getChildren(tmpGroup).firstNode() const originalTreeNode = graph.nodes.find( - n => innerNodesDp.get(n) === childNode2DescriptorDp.get(child) + (n) => innerNodesDp.get(n) === childNode2DescriptorDp.get(child) ) return { group: tmpGroup, treeNode: originalTreeNode } }) @@ -798,10 +791,10 @@ class TemporaryGroupCustomizationStage extends LayoutStageBase { * @yjs:keep = c */ function initializeStyles() { - StringTemplateNodeStyle.CONVERTERS.radius = diameter => diameter * 0.5 - StringTemplateNodeStyle.CONVERTERS.sign = aggregated => + StringTemplateNodeStyle.CONVERTERS.radius = (diameter) => diameter * 0.5 + StringTemplateNodeStyle.CONVERTERS.sign = (aggregated) => aggregated ? 'M 0 -7 L 0 7 M -7 0 L 7 0' : 'M -5 0 L 5 0' - StringTemplateNodeStyle.CONVERTERS.centered = bounds => + StringTemplateNodeStyle.CONVERTERS.centered = (bounds) => `translate(${bounds.width * 0.5},${bounds.height * 0.5})` StringTemplateNodeStyle.CONVERTERS.fillColor = (info, parameter) => { if (!info || !parameter) { @@ -827,7 +820,7 @@ function initializeStyles() { return parameter } - StringTemplateNodeStyle.CONVERTERS.strokeStyle = info => { + StringTemplateNodeStyle.CONVERTERS.strokeStyle = (info) => { const aggregationInfo = info if (aggregationInfo && aggregationInfo.aggregate.node) { return '' @@ -885,7 +878,7 @@ function switchHierarchyEdgeVisibility(graph, visible) { * @returns {!Promise} */ function setUiDisabled(disabled) { - return new Promise(resolve => { + return new Promise((resolve) => { document.querySelector('#calculating-indicator').style.display = !disabled ? 'none' : 'block' document.querySelector('#aggregation-mode-select').disabled = disabled document.querySelector('#maximum-duration-range').disabled = disabled @@ -898,7 +891,7 @@ function setUiDisabled(disabled) { // otherwise the graph in filtered view will be empty document.querySelector('#switch-view').disabled = disabled || - graphComponent.graph.nodes.every(node => + graphComponent.graph.nodes.every((node) => aggregationHelper.aggregateGraph.isAggregationItem(node) ) @@ -976,7 +969,7 @@ function onInfoPanelPropertiesChanged() { // enable switching to filtered view only if there are some real edges in the graph // otherwise the graph in filtered view will be empty - document.querySelector('#switch-view').disabled = graphComponent.graph.nodes.every(node => + document.querySelector('#switch-view').disabled = graphComponent.graph.nodes.every((node) => aggregationHelper.aggregateGraph.isAggregationItem(node) ) } @@ -1006,13 +999,13 @@ class ZoomToNodesLayoutExecutor extends LayoutExecutor { return super.createViewportAnimation(targetBounds) } - if (!this.nodes.every(node => this.graph.contains(node))) { + if (!this.nodes.every((node) => this.graph.contains(node))) { throw new Error('Cannot zoom to nodes that are not in the graph') } const layoutGraph = this.layoutGraph const bounds = this.nodes - .map(node => layoutGraph.getBoundingBox(layoutGraph.getCopiedNode(node))) + .map((node) => layoutGraph.getBoundingBox(layoutGraph.getCopiedNode(node))) .reduce((acc, current) => Rect.add(acc, current.toRect()), Rect.EMPTY) const viewportAnimation = new ViewportAnimation(this.graphComponent, bounds, this.duration) @@ -1026,15 +1019,15 @@ class ZoomToNodesLayoutExecutor extends LayoutExecutor { * Binds the buttons in the toolbar to their functionality. */ function initializeUI() { - document.querySelector('#maximum-duration-range').addEventListener('change', evt => { + document.querySelector('#maximum-duration-range').addEventListener('change', (evt) => { const maximumDurationLabel = document.querySelector('#maximum-duration-label') maximumDurationLabel.innerText = evt.target.value }) - document.querySelector('#minimum-cluster-size-range').addEventListener('change', evt => { + document.querySelector('#minimum-cluster-size-range').addEventListener('change', (evt) => { const minimumClusterSizeLabel = document.querySelector('#minimum-cluster-size-label') minimumClusterSizeLabel.innerText = evt.target.value }) - document.querySelector('#maximum-cluster-size-range').addEventListener('change', evt => { + document.querySelector('#maximum-cluster-size-range').addEventListener('change', (evt) => { const maximumClusterSizeLabel = document.querySelector('#maximum-cluster-size-label') maximumClusterSizeLabel.innerText = evt.target.value }) diff --git a/demos/showcase/largegraphaggregation/LargeGraphAggregationDemo.ts b/demos/showcase/largegraphaggregation/LargeGraphAggregationDemo.ts index ee484ef4f..9d63158bf 100644 --- a/demos/showcase/largegraphaggregation/LargeGraphAggregationDemo.ts +++ b/demos/showcase/largegraphaggregation/LargeGraphAggregationDemo.ts @@ -296,7 +296,7 @@ function onHoveredItemChanged(sender: object, evt: HoveredItemChangedEventArgs): if (node && aggregationHelper.isOriginalNodeOrPlaceHolder(node)) { addHighlight(node) // and if it's a node, we highlight all adjacent edges, too - graphComponent.graph.edgesAt(node, AdjacencyTypes.ALL).forEach(adjacentEdge => { + graphComponent.graph.edgesAt(node, AdjacencyTypes.ALL).forEach((adjacentEdge) => { if (aggregationHelper.isHierarchyEdge(adjacentEdge)) { return } @@ -415,7 +415,7 @@ function createFilteredView(): FilteredGraphWrapper { // create a new FilteredGraphWrapper that filters the original graph and shows only the currently visible nodes const filteredGraph = new FilteredGraphWrapper( originalGraph, - node => { + (node) => { node = aggregationHelper.getPlaceholder(node) const aggregateGraph = aggregationHelper.aggregateGraph return aggregateGraph.contains(node) @@ -462,7 +462,7 @@ async function applyAggregation( originalGraph: IGraph, aggregateGraph: AggregationGraphWrapper ): Promise { - return new Promise(resolve => { + return new Promise((resolve) => { const nodeAggregation = createConfiguredAggregation() const aggregationResult = nodeAggregation.run(originalGraph) @@ -527,7 +527,7 @@ async function runBalloonLayout(affectedNodes?: IListEnumerable): Promise treeReductionStage.edgeBundling.bundlingStrength = 1 layout.prependStage(treeReductionStage) const nonTreeEdges = graphComponent.graph.edges - .filter(e => !aggregationHelper.isHierarchyEdge(e)) + .filter((e) => !aggregationHelper.isHierarchyEdge(e)) .toList() const treeReductionStageData = new TreeReductionStageData({ @@ -577,7 +577,7 @@ async function runCactusLayout(affectedNodes?: IListEnumerable): Promise< const innerTreeNodes: INode[] = [] const innerTreeNode2Descriptor = new Map() for (const node of graph.nodes) { - if (graph.outEdgesAt(node).some(e => aggregationHelper.isHierarchyEdge(e))) { + if (graph.outEdgesAt(node).some((e) => aggregationHelper.isHierarchyEdge(e))) { innerTreeNodes.push(node) innerTreeNode2Descriptor.set(node, new TemporaryGroupDescriptor()) } @@ -593,13 +593,13 @@ async function runCactusLayout(affectedNodes?: IListEnumerable): Promise< // ... members of the group are all the successor nodes temporaryGroup.source = graph .outEdgesAt(treeNode) - .filter(e => aggregationHelper.isHierarchyEdge(e)) - .map(e => e.targetNode!) + .filter((e) => aggregationHelper.isHierarchyEdge(e)) + .map((e) => e.targetNode!) // if the tree node has a parent too, then the parent descriptor must be setup as well const inEdge = graph .inEdgesAt(treeNode) - .filter(e => aggregationHelper.isHierarchyEdge(e)) + .filter((e) => aggregationHelper.isHierarchyEdge(e)) .at(0) if (inEdge) { const parentGroupDescriptor = innerTreeNode2Descriptor.get(inEdge.sourceNode!) @@ -615,7 +615,7 @@ async function runCactusLayout(affectedNodes?: IListEnumerable): Promise< INode.$class, TemporaryGroupDescriptor.$class, 'INNER_TREE_NODES', - n => { + (n) => { if (innerTreeNode2Descriptor.has(n)) { return innerTreeNode2Descriptor.get(n) } @@ -682,9 +682,6 @@ class CustomCactusLayoutStage extends LayoutStageBase { * {@link TemporaryGroupDescriptor} for the {@link CactusGroupLayout} algorithm. */ class TemporaryGroupCustomizationStage extends LayoutStageBase { - constructor(cactus: CactusGroupLayout) { - super(cactus) - } applyLayout(graph: LayoutGraph): void { const innerNodesDp = graph.getDataProvider('INNER_TREE_NODES')! @@ -699,11 +696,11 @@ class TemporaryGroupCustomizationStage extends LayoutStageBase { // collect the temporary group nodes inserted by TemporaryGroupNodeInsertionStage earlier // and the respective original tree node const temporaryGroups = graph.nodes - .filter(n => isInsertedTmpGroupDp.getBoolean(n)) - .map(tmpGroup => { + .filter((n) => isInsertedTmpGroupDp.getBoolean(n)) + .map((tmpGroup) => { const child = grouping.getChildren(tmpGroup)!.firstNode() const originalTreeNode = graph.nodes.find( - n => innerNodesDp.get(n) === childNode2DescriptorDp.get(child) + (n) => innerNodesDp.get(n) === childNode2DescriptorDp.get(child) )! return { group: tmpGroup, treeNode: originalTreeNode } }) @@ -851,7 +848,7 @@ function switchHierarchyEdgeVisibility(graph: IGraph, visible: boolean) { * Enables/disables the UI. */ function setUiDisabled(disabled: boolean): Promise { - return new Promise(resolve => { + return new Promise((resolve) => { document.querySelector('#calculating-indicator')!.style.display = !disabled ? 'none' : 'block' @@ -866,7 +863,7 @@ function setUiDisabled(disabled: boolean): Promise { // otherwise the graph in filtered view will be empty document.querySelector('#switch-view')!.disabled = disabled || - graphComponent.graph.nodes.every(node => + graphComponent.graph.nodes.every((node) => aggregationHelper.aggregateGraph.isAggregationItem(node) ) @@ -943,7 +940,7 @@ function onInfoPanelPropertiesChanged(): void { // enable switching to filtered view only if there are some real edges in the graph // otherwise the graph in filtered view will be empty document.querySelector('#switch-view')!.disabled = - graphComponent.graph.nodes.every(node => + graphComponent.graph.nodes.every((node) => aggregationHelper.aggregateGraph.isAggregationItem(node) ) } @@ -968,13 +965,13 @@ class ZoomToNodesLayoutExecutor extends LayoutExecutor { return super.createViewportAnimation(targetBounds) } - if (!this.nodes.every(node => this.graph.contains(node))) { + if (!this.nodes.every((node) => this.graph.contains(node))) { throw new Error('Cannot zoom to nodes that are not in the graph') } const layoutGraph = this.layoutGraph const bounds = this.nodes - .map(node => layoutGraph.getBoundingBox(layoutGraph.getCopiedNode(node)!)) + .map((node) => layoutGraph.getBoundingBox(layoutGraph.getCopiedNode(node)!)) .reduce((acc: Rect, current: YRectangle) => Rect.add(acc, current.toRect()), Rect.EMPTY) const viewportAnimation = new ViewportAnimation(this.graphComponent, bounds, this.duration) @@ -990,14 +987,14 @@ class ZoomToNodesLayoutExecutor extends LayoutExecutor { function initializeUI(): void { document .querySelector('#maximum-duration-range')! - .addEventListener('change', evt => { + .addEventListener('change', (evt) => { const maximumDurationLabel = document.querySelector('#maximum-duration-label')! maximumDurationLabel.innerText = (evt.target as HTMLInputElement).value as string }) document .querySelector('#minimum-cluster-size-range')! - .addEventListener('change', evt => { + .addEventListener('change', (evt) => { const minimumClusterSizeLabel = document.querySelector( '#minimum-cluster-size-label' )! @@ -1005,7 +1002,7 @@ function initializeUI(): void { }) document .querySelector('#maximum-cluster-size-range')! - .addEventListener('change', evt => { + .addEventListener('change', (evt) => { const maximumClusterSizeLabel = document.querySelector( '#maximum-cluster-size-label' )! diff --git a/demos/showcase/largegraphaggregation/index.html b/demos/showcase/largegraphaggregation/index.html index f6efe5182..d81014c63 100644 --- a/demos/showcase/largegraphaggregation/index.html +++ b/demos/showcase/largegraphaggregation/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/layoutstyles/BalloonLayoutConfig.js b/demos/showcase/layoutstyles/BalloonLayoutConfig.js index 214d7b11c..df9acf303 100644 --- a/demos/showcase/layoutstyles/BalloonLayoutConfig.js +++ b/demos/showcase/layoutstyles/BalloonLayoutConfig.js @@ -191,7 +191,7 @@ const BalloonLayoutConfig = Class('BalloonLayoutConfig', { this.nodeLabelingStyleItem === NodeLabelingPolicies.RAYLIKE_LEAVES || this.nodeLabelingStyleItem === NodeLabelingPolicies.HORIZONTAL ) { - graphComponent.graph.nodeLabels.forEach(label => { + graphComponent.graph.nodeLabels.forEach((label) => { graphComponent.graph.setLabelLayoutParameter( label, FreeNodeLabelModel.INSTANCE.findBestParameter( diff --git a/demos/showcase/layoutstyles/BalloonLayoutConfig.ts b/demos/showcase/layoutstyles/BalloonLayoutConfig.ts index 25898c2d8..686a02739 100644 --- a/demos/showcase/layoutstyles/BalloonLayoutConfig.ts +++ b/demos/showcase/layoutstyles/BalloonLayoutConfig.ts @@ -192,7 +192,7 @@ const BalloonLayoutConfig = (Class as any)('BalloonLayoutConfig', { this.nodeLabelingStyleItem === NodeLabelingPolicies.RAYLIKE_LEAVES || this.nodeLabelingStyleItem === NodeLabelingPolicies.HORIZONTAL ) { - graphComponent.graph.nodeLabels.forEach(label => { + graphComponent.graph.nodeLabels.forEach((label) => { graphComponent.graph.setLabelLayoutParameter( label, FreeNodeLabelModel.INSTANCE.findBestParameter( diff --git a/demos/showcase/layoutstyles/BusEdgeRouterConfig.js b/demos/showcase/layoutstyles/BusEdgeRouterConfig.js index dd0cefe7c..6d8260566 100644 --- a/demos/showcase/layoutstyles/BusEdgeRouterConfig.js +++ b/demos/showcase/layoutstyles/BusEdgeRouterConfig.js @@ -142,7 +142,7 @@ const BusEdgeRouterConfig = Class('BusEdgeRouterConfig', { edgeDescriptors: busIds }) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const isFixed = scopePartial && !graphSelection.isSelected(edge.sourceNode) && @@ -156,25 +156,25 @@ const BusEdgeRouterConfig = Class('BusEdgeRouterConfig', { const selectedIds = new Set() switch (this.scopeItem) { case BusRouterScope.SUBSET: - layoutData.affectedEdges.delegate = edge => graphSelection.isSelected(edge) + layoutData.affectedEdges.delegate = (edge) => graphSelection.isSelected(edge) break case BusRouterScope.SUBSET_BUS: graph.edges - .filter(item => graphSelection.isSelected(item)) - .forEach(edge => { + .filter((item) => graphSelection.isSelected(item)) + .forEach((edge) => { const busId = busIds.get(edge).busId if (!selectedIds.has(busId)) { selectedIds.add(busId) } }) - layoutData.affectedEdges.delegate = edge => selectedIds.has(busIds.get(edge).busId) + layoutData.affectedEdges.delegate = (edge) => selectedIds.has(busIds.get(edge).busId) break case BusRouterScope.PARTIAL: { graph.nodes - .filter(item => graphSelection.isSelected(item)) - .flatMap(node => graph.edgesAt(node, AdjacencyTypes.ALL)) - .forEach(edge => { + .filter((item) => graphSelection.isSelected(item)) + .flatMap((node) => graph.edgesAt(node, AdjacencyTypes.ALL)) + .forEach((edge) => { const busId = busIds.get(edge).busId if (!selectedIds.has(busId)) { selectedIds.add(busId) @@ -186,7 +186,7 @@ const BusEdgeRouterConfig = Class('BusEdgeRouterConfig', { HideNonOrthogonalEdgesStage.SELECTED_NODES_DP_KEY, graphSelection.selectedNodes ) - layoutData.affectedEdges.delegate = edge => selectedIds.has(busIds.get(edge).busId) + layoutData.affectedEdges.delegate = (edge) => selectedIds.has(busIds.get(edge).busId) return layoutData.combineWith(hideNonOrthogonalEdgesLayoutData) } @@ -527,7 +527,7 @@ class HideNonOrthogonalEdgesStage extends LayoutStageBase { const selectedNodes = graph.getDataProvider(HideNonOrthogonalEdgesStage.SELECTED_NODES_DP_KEY) const hider = new LayoutGraphHider(graph) const hiddenEdges = new Set() - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { if ( affectedEdges.getBoolean(edge) && selectedNodes !== null && @@ -544,7 +544,7 @@ class HideNonOrthogonalEdgesStage extends LayoutStageBase { } } }) - hiddenEdges.forEach(edge => { + hiddenEdges.forEach((edge) => { hider.hide(edge) }) diff --git a/demos/showcase/layoutstyles/BusEdgeRouterConfig.ts b/demos/showcase/layoutstyles/BusEdgeRouterConfig.ts index 74aeb0ebf..a839a456b 100644 --- a/demos/showcase/layoutstyles/BusEdgeRouterConfig.ts +++ b/demos/showcase/layoutstyles/BusEdgeRouterConfig.ts @@ -146,7 +146,7 @@ const BusEdgeRouterConfig = (Class as any)('BusEdgeRouterConfig', { edgeDescriptors: busIds }) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const isFixed = scopePartial && !graphSelection.isSelected(edge.sourceNode!) && @@ -160,25 +160,25 @@ const BusEdgeRouterConfig = (Class as any)('BusEdgeRouterConfig', { const selectedIds = new Set() switch (this.scopeItem) { case BusRouterScope.SUBSET: - layoutData.affectedEdges.delegate = edge => graphSelection.isSelected(edge) + layoutData.affectedEdges.delegate = (edge) => graphSelection.isSelected(edge) break case BusRouterScope.SUBSET_BUS: graph.edges - .filter(item => graphSelection.isSelected(item)) - .forEach(edge => { + .filter((item) => graphSelection.isSelected(item)) + .forEach((edge) => { const busId = busIds.get(edge)!.busId if (!selectedIds.has(busId)) { selectedIds.add(busId) } }) - layoutData.affectedEdges.delegate = edge => selectedIds.has(busIds.get(edge)!.busId) + layoutData.affectedEdges.delegate = (edge) => selectedIds.has(busIds.get(edge)!.busId) break case BusRouterScope.PARTIAL: { graph.nodes - .filter(item => graphSelection.isSelected(item)) - .flatMap(node => graph.edgesAt(node, AdjacencyTypes.ALL)) - .forEach(edge => { + .filter((item) => graphSelection.isSelected(item)) + .flatMap((node) => graph.edgesAt(node, AdjacencyTypes.ALL)) + .forEach((edge) => { const busId = busIds.get(edge)!.busId if (!selectedIds.has(busId)) { selectedIds.add(busId) @@ -190,7 +190,7 @@ const BusEdgeRouterConfig = (Class as any)('BusEdgeRouterConfig', { HideNonOrthogonalEdgesStage.SELECTED_NODES_DP_KEY, graphSelection.selectedNodes ) - layoutData.affectedEdges.delegate = edge => selectedIds.has(busIds.get(edge)!.busId) + layoutData.affectedEdges.delegate = (edge) => selectedIds.has(busIds.get(edge)!.busId) return layoutData.combineWith(hideNonOrthogonalEdgesLayoutData) } @@ -520,7 +520,7 @@ class HideNonOrthogonalEdgesStage extends LayoutStageBase { const selectedNodes = graph.getDataProvider(HideNonOrthogonalEdgesStage.SELECTED_NODES_DP_KEY) const hider = new LayoutGraphHider(graph) const hiddenEdges = new Set() - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { if ( affectedEdges!.getBoolean(edge) && selectedNodes !== null && @@ -537,7 +537,7 @@ class HideNonOrthogonalEdgesStage extends LayoutStageBase { } } }) - hiddenEdges.forEach(edge => { + hiddenEdges.forEach((edge) => { hider.hide(edge) }) diff --git a/demos/showcase/layoutstyles/ChannelEdgeRouterConfig.js b/demos/showcase/layoutstyles/ChannelEdgeRouterConfig.js index d6d01ff38..f4dccc40d 100644 --- a/demos/showcase/layoutstyles/ChannelEdgeRouterConfig.js +++ b/demos/showcase/layoutstyles/ChannelEdgeRouterConfig.js @@ -123,12 +123,12 @@ const ChannelEdgeRouterConfig = Class('ChannelEdgeRouterConfig', { const layoutData = new ChannelEdgeRouterData() const selection = graphComponent.selection if (this.scopeItem === EdgeRouterScope.ROUTE_EDGES_AT_AFFECTED_NODES) { - layoutData.affectedEdges.delegate = edge => + layoutData.affectedEdges.delegate = (edge) => selection.isSelected(edge.sourceNode) || selection.isSelected(edge.targetNode) } else if (this.scopeItem === EdgeRouterScope.ROUTE_AFFECTED_EDGES) { - layoutData.affectedEdges.delegate = edge => selection.isSelected(edge) + layoutData.affectedEdges.delegate = (edge) => selection.isSelected(edge) } else { - layoutData.affectedEdges.delegate = _ => true + layoutData.affectedEdges.delegate = (_) => true } return layoutData }, diff --git a/demos/showcase/layoutstyles/ChannelEdgeRouterConfig.ts b/demos/showcase/layoutstyles/ChannelEdgeRouterConfig.ts index d6d97b1a8..8bf1c7723 100644 --- a/demos/showcase/layoutstyles/ChannelEdgeRouterConfig.ts +++ b/demos/showcase/layoutstyles/ChannelEdgeRouterConfig.ts @@ -127,12 +127,12 @@ const ChannelEdgeRouterConfig = (Class as any)('ChannelEdgeRouterConfig', { const layoutData = new ChannelEdgeRouterData() const selection = graphComponent.selection if (this.scopeItem === EdgeRouterScope.ROUTE_EDGES_AT_AFFECTED_NODES) { - layoutData.affectedEdges.delegate = edge => + layoutData.affectedEdges.delegate = (edge) => selection.isSelected(edge.sourceNode!) || selection.isSelected(edge.targetNode!) } else if (this.scopeItem === EdgeRouterScope.ROUTE_AFFECTED_EDGES) { - layoutData.affectedEdges.delegate = edge => selection.isSelected(edge) + layoutData.affectedEdges.delegate = (edge) => selection.isSelected(edge) } else { - layoutData.affectedEdges.delegate = _ => true + layoutData.affectedEdges.delegate = (_) => true } return layoutData }, diff --git a/demos/showcase/layoutstyles/CircularLayoutConfig.js b/demos/showcase/layoutstyles/CircularLayoutConfig.js index d04aed62e..e2acfe7f1 100644 --- a/demos/showcase/layoutstyles/CircularLayoutConfig.js +++ b/demos/showcase/layoutstyles/CircularLayoutConfig.js @@ -207,7 +207,7 @@ const CircularLayoutConfig = Class('CircularLayoutConfig', { this.nodeLabelingStyleItem === NodeLabelingPolicies.RAYLIKE_LEAVES || this.nodeLabelingStyleItem === NodeLabelingPolicies.HORIZONTAL ) { - graphComponent.graph.nodeLabels.forEach(label => { + graphComponent.graph.nodeLabels.forEach((label) => { graphComponent.graph.setLabelLayoutParameter( label, FreeNodeLabelModel.INSTANCE.findBestParameter( @@ -233,7 +233,7 @@ const CircularLayoutConfig = Class('CircularLayoutConfig', { const layoutData = new CircularLayoutData() if (this.layoutStyleItem === CircularLayoutStyle.CUSTOM_GROUPS) { - layoutData.customGroups.delegate = node => graphComponent.graph.getParent(node) + layoutData.customGroups.delegate = (node) => graphComponent.graph.getParent(node) } if (this.edgeRoutingItem === CircularLayoutEdgeRoutingPolicy.MARKED_EXTERIOR) { diff --git a/demos/showcase/layoutstyles/CircularLayoutConfig.ts b/demos/showcase/layoutstyles/CircularLayoutConfig.ts index 07faa3cda..643560b73 100644 --- a/demos/showcase/layoutstyles/CircularLayoutConfig.ts +++ b/demos/showcase/layoutstyles/CircularLayoutConfig.ts @@ -208,7 +208,7 @@ const CircularLayoutConfig = (Class as any)('CircularLayoutConfig', { this.nodeLabelingStyleItem === NodeLabelingPolicies.RAYLIKE_LEAVES || this.nodeLabelingStyleItem === NodeLabelingPolicies.HORIZONTAL ) { - graphComponent.graph.nodeLabels.forEach(label => { + graphComponent.graph.nodeLabels.forEach((label) => { graphComponent.graph.setLabelLayoutParameter( label, FreeNodeLabelModel.INSTANCE.findBestParameter( @@ -237,7 +237,7 @@ const CircularLayoutConfig = (Class as any)('CircularLayoutConfig', { const layoutData = new CircularLayoutData() if (this.layoutStyleItem === CircularLayoutStyle.CUSTOM_GROUPS) { - layoutData.customGroups.delegate = node => graphComponent.graph.getParent(node) + layoutData.customGroups.delegate = (node) => graphComponent.graph.getParent(node) } if (this.edgeRoutingItem === CircularLayoutEdgeRoutingPolicy.MARKED_EXTERIOR) { diff --git a/demos/showcase/layoutstyles/CompactDiskLayoutConfig.js b/demos/showcase/layoutstyles/CompactDiskLayoutConfig.js index 59d0179fd..6f98815e3 100644 --- a/demos/showcase/layoutstyles/CompactDiskLayoutConfig.js +++ b/demos/showcase/layoutstyles/CompactDiskLayoutConfig.js @@ -83,7 +83,7 @@ const CompactDiskLayoutConfig = Class('CompactDiskLayoutConfig', { createConfiguredLayout: function (graphComponent) { if ( this.layoutGroupsItem === GroupLayout.RECURSIVE && - graphComponent.graph.nodes.some(n => graphComponent.graph.isGroupNode(n)) + graphComponent.graph.nodes.some((n) => graphComponent.graph.isGroupNode(n)) ) { // if the recursive group layout option is enabled, use RecursiveGroupLayout with organic for // the top-level hierarchy - the actual compact disk layout will be specified as layout for @@ -151,7 +151,7 @@ const CompactDiskLayoutConfig = Class('CompactDiskLayoutConfig', { this.nodeLabelingStyleItem === NodeLabelingPolicies.RAYLIKE_LEAVES || this.nodeLabelingStyleItem === NodeLabelingPolicies.HORIZONTAL ) { - graphComponent.graph.nodeLabels.forEach(label => { + graphComponent.graph.nodeLabels.forEach((label) => { graphComponent.graph.setLabelLayoutParameter( label, FreeNodeLabelModel.INSTANCE.findBestParameter( diff --git a/demos/showcase/layoutstyles/CompactDiskLayoutConfig.ts b/demos/showcase/layoutstyles/CompactDiskLayoutConfig.ts index 55a631075..711345e35 100644 --- a/demos/showcase/layoutstyles/CompactDiskLayoutConfig.ts +++ b/demos/showcase/layoutstyles/CompactDiskLayoutConfig.ts @@ -84,7 +84,7 @@ const CompactDiskLayoutConfig = (Class as any)('CompactDiskLayoutConfig', { createConfiguredLayout: function (graphComponent: GraphComponent): ILayoutAlgorithm { if ( this.layoutGroupsItem === GroupLayout.RECURSIVE && - graphComponent.graph.nodes.some(n => graphComponent.graph.isGroupNode(n)) + graphComponent.graph.nodes.some((n) => graphComponent.graph.isGroupNode(n)) ) { // if the recursive group layout option is enabled, use RecursiveGroupLayout with organic for // the top-level hierarchy - the actual compact disk layout will be specified as layout for @@ -155,7 +155,7 @@ const CompactDiskLayoutConfig = (Class as any)('CompactDiskLayoutConfig', { this.nodeLabelingStyleItem === NodeLabelingPolicies.RAYLIKE_LEAVES || this.nodeLabelingStyleItem === NodeLabelingPolicies.HORIZONTAL ) { - graphComponent.graph.nodeLabels.forEach(label => { + graphComponent.graph.nodeLabels.forEach((label) => { graphComponent.graph.setLabelLayoutParameter( label, FreeNodeLabelModel.INSTANCE.findBestParameter( diff --git a/demos/showcase/layoutstyles/HierarchicLayoutConfig.js b/demos/showcase/layoutstyles/HierarchicLayoutConfig.js index 8a5e2e4cd..76f321493 100644 --- a/demos/showcase/layoutstyles/HierarchicLayoutConfig.js +++ b/demos/showcase/layoutstyles/HierarchicLayoutConfig.js @@ -297,7 +297,7 @@ const HierarchicLayoutConfig = Class('HierarchicLayoutConfig', { if (incrementalLayout && selectedElements) { // configure the mode const ihf = layout.createIncrementalHintsFactory() - layoutData.incrementalHints.delegate = item => { + layoutData.incrementalHints.delegate = (item) => { // Return the correct hint type for each model item that appears in one of these sets if (INode.isInstance(item) && graphComponent.selection.isSelected(item)) { return ihf.createLayerIncrementallyHint(item) @@ -310,12 +310,12 @@ const HierarchicLayoutConfig = Class('HierarchicLayoutConfig', { } if (this.rankingPolicyItem === HierarchicLayoutLayeringStrategy.BFS) { - layoutData.bfsLayererCoreNodes.delegate = node => graphComponent.selection.isSelected(node) + layoutData.bfsLayererCoreNodes.delegate = (node) => graphComponent.selection.isSelected(node) } if (this.gridEnabledItem) { const nld = layout.nodeLayoutDescriptor - layoutData.nodeLayoutDescriptors.delegate = node => + layoutData.nodeLayoutDescriptors.delegate = (node) => new HierarchicLayoutNodeLayoutDescriptor({ layerAlignment: nld.layerAlignment, minimumDistance: nld.minimumDistance, @@ -328,7 +328,7 @@ const HierarchicLayoutConfig = Class('HierarchicLayoutConfig', { } if (this.edgeDirectednessItem) { - layoutData.edgeDirectedness.delegate = edge => { + layoutData.edgeDirectedness.delegate = (edge) => { const style = edge.style if ( style instanceof PolylineEdgeStyle && @@ -343,7 +343,7 @@ const HierarchicLayoutConfig = Class('HierarchicLayoutConfig', { } if (this.edgeThicknessItem) { - layoutData.edgeThickness.delegate = edge => { + layoutData.edgeThickness.delegate = (edge) => { const style = edge.style if (style instanceof PolylineEdgeStyle) { return style.stroke.thickness @@ -386,11 +386,12 @@ const HierarchicLayoutConfig = Class('HierarchicLayoutConfig', { const feedbackEdgeSetResult = new FeedbackEdgeSet().run(graph) const longestPath = new LongestPath({ subgraphEdges: { - excludes: edge => feedbackEdgeSetResult.feedbackEdgeSet.contains(edge) || edge.isSelfloop + excludes: (edge) => + feedbackEdgeSetResult.feedbackEdgeSet.contains(edge) || edge.isSelfloop } }).run(graph) if (longestPath.edges.size > 0) { - layoutData.criticalEdgePriorities.delegate = edge => { + layoutData.criticalEdgePriorities.delegate = (edge) => { if (longestPath.edges.contains(edge)) { return 10 } @@ -401,7 +402,7 @@ const HierarchicLayoutConfig = Class('HierarchicLayoutConfig', { if (this.automaticBusRoutingEnabledItem) { const allBusNodes = new Set() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (!graph.isGroupNode(node) && !allBusNodes.has(node)) { // search for good opportunities for bus structures rooted at this node if (graph.inDegree(node) >= 4) { @@ -511,7 +512,7 @@ const HierarchicLayoutConfig = Class('HierarchicLayoutConfig', { * @param graph The input graph */ containsTable(graph) { - return graph.nodes.find(node => !!node.lookup(ITable.$class)) !== null + return graph.nodes.find((node) => !!node.lookup(ITable.$class)) !== null }, /** @type {boolean} */ diff --git a/demos/showcase/layoutstyles/HierarchicLayoutConfig.ts b/demos/showcase/layoutstyles/HierarchicLayoutConfig.ts index 6bb3d0868..0cb5e383c 100644 --- a/demos/showcase/layoutstyles/HierarchicLayoutConfig.ts +++ b/demos/showcase/layoutstyles/HierarchicLayoutConfig.ts @@ -301,7 +301,7 @@ const HierarchicLayoutConfig = (Class as any)('HierarchicLayoutConfig', { if (incrementalLayout && selectedElements) { // configure the mode const ihf = layout.createIncrementalHintsFactory() - layoutData.incrementalHints.delegate = item => { + layoutData.incrementalHints.delegate = (item) => { // Return the correct hint type for each model item that appears in one of these sets if (INode.isInstance(item) && graphComponent.selection.isSelected(item)) { return ihf.createLayerIncrementallyHint(item) @@ -314,12 +314,12 @@ const HierarchicLayoutConfig = (Class as any)('HierarchicLayoutConfig', { } if (this.rankingPolicyItem === HierarchicLayoutLayeringStrategy.BFS) { - layoutData.bfsLayererCoreNodes.delegate = node => graphComponent.selection.isSelected(node) + layoutData.bfsLayererCoreNodes.delegate = (node) => graphComponent.selection.isSelected(node) } if (this.gridEnabledItem) { const nld = layout.nodeLayoutDescriptor - layoutData.nodeLayoutDescriptors.delegate = node => + layoutData.nodeLayoutDescriptors.delegate = (node) => new HierarchicLayoutNodeLayoutDescriptor({ layerAlignment: nld.layerAlignment, minimumDistance: nld.minimumDistance, @@ -332,7 +332,7 @@ const HierarchicLayoutConfig = (Class as any)('HierarchicLayoutConfig', { } if (this.edgeDirectednessItem) { - layoutData.edgeDirectedness.delegate = edge => { + layoutData.edgeDirectedness.delegate = (edge) => { const style = edge.style if ( style instanceof PolylineEdgeStyle && @@ -347,7 +347,7 @@ const HierarchicLayoutConfig = (Class as any)('HierarchicLayoutConfig', { } if (this.edgeThicknessItem) { - layoutData.edgeThickness.delegate = edge => { + layoutData.edgeThickness.delegate = (edge) => { const style = edge.style if (style instanceof PolylineEdgeStyle) { return style.stroke!.thickness @@ -395,7 +395,7 @@ const HierarchicLayoutConfig = (Class as any)('HierarchicLayoutConfig', { } }).run(graph) if (longestPath.edges.size > 0) { - layoutData.criticalEdgePriorities.delegate = edge => { + layoutData.criticalEdgePriorities.delegate = (edge) => { if (longestPath.edges.contains(edge)) { return 10 } @@ -406,7 +406,7 @@ const HierarchicLayoutConfig = (Class as any)('HierarchicLayoutConfig', { if (this.automaticBusRoutingEnabledItem) { const allBusNodes = new Set() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (!graph.isGroupNode(node) && !allBusNodes.has(node)) { // search for good opportunities for bus structures rooted at this node if (graph.inDegree(node) >= 4) { diff --git a/demos/showcase/layoutstyles/LabelingConfig.js b/demos/showcase/layoutstyles/LabelingConfig.js index f57f8cc5c..e12e9d856 100644 --- a/demos/showcase/layoutstyles/LabelingConfig.js +++ b/demos/showcase/layoutstyles/LabelingConfig.js @@ -132,7 +132,7 @@ const LabelingConfig = Class('LabelingConfig', { const selection = graphComponent.selection if (selection !== null) { layoutData.affectedLabels.dpKey = SELECTED_LABELS_KEY - layoutData.affectedLabels.delegate = label => + layoutData.affectedLabels.delegate = (label) => selection.isSelected(label) || selection.isSelected(label.owner) } diff --git a/demos/showcase/layoutstyles/LabelingConfig.ts b/demos/showcase/layoutstyles/LabelingConfig.ts index e63c9fd08..9621c7c3a 100644 --- a/demos/showcase/layoutstyles/LabelingConfig.ts +++ b/demos/showcase/layoutstyles/LabelingConfig.ts @@ -136,7 +136,7 @@ const LabelingConfig = (Class as any)('LabelingConfig', { const selection = graphComponent.selection if (selection !== null) { layoutData.affectedLabels.dpKey = SELECTED_LABELS_KEY - layoutData.affectedLabels.delegate = label => + layoutData.affectedLabels.delegate = (label) => selection.isSelected(label) || selection.isSelected(label.owner!) } diff --git a/demos/showcase/layoutstyles/LayoutConfiguration.js b/demos/showcase/layoutstyles/LayoutConfiguration.js index 8e9cc081b..213aff7e2 100644 --- a/demos/showcase/layoutstyles/LayoutConfiguration.js +++ b/demos/showcase/layoutstyles/LayoutConfiguration.js @@ -188,7 +188,7 @@ const LayoutConfiguration = Class('LayoutConfiguration', { // change to a free edge label model to support integrated edge labeling const model = new FreeEdgeLabelModel() - graph.edgeLabels.forEach(label => { + graph.edgeLabels.forEach((label) => { if (!(label.layoutParameter.model instanceof FreeEdgeLabelModel)) { graph.setLabelLayoutParameter(label, model.findBestParameter(label, model, label.layout)) } @@ -198,7 +198,7 @@ const LayoutConfiguration = Class('LayoutConfiguration', { labelItemMappings: [ [ LayoutGraphAdapter.EDGE_LABEL_LAYOUT_PREFERRED_PLACEMENT_DESCRIPTOR_DP_KEY, - label => descriptor + (label) => descriptor ] ] }) diff --git a/demos/showcase/layoutstyles/LayoutConfiguration.ts b/demos/showcase/layoutstyles/LayoutConfiguration.ts index e8d0fd1c3..7f1665a60 100644 --- a/demos/showcase/layoutstyles/LayoutConfiguration.ts +++ b/demos/showcase/layoutstyles/LayoutConfiguration.ts @@ -197,7 +197,7 @@ const LayoutConfiguration = (Class as any)('LayoutConfiguration', { // change to a free edge label model to support integrated edge labeling const model = new FreeEdgeLabelModel() - graph.edgeLabels.forEach(label => { + graph.edgeLabels.forEach((label) => { if (!(label.layoutParameter.model instanceof FreeEdgeLabelModel)) { graph.setLabelLayoutParameter(label, model.findBestParameter(label, model, label.layout)) } diff --git a/demos/showcase/layoutstyles/LayoutStylesDemo.js b/demos/showcase/layoutstyles/LayoutStylesDemo.js index 727ff2bd4..b0a1cf243 100644 --- a/demos/showcase/layoutstyles/LayoutStylesDemo.js +++ b/demos/showcase/layoutstyles/LayoutStylesDemo.js @@ -223,7 +223,7 @@ function enableGraphML() { */ function initializeSamples(layoutName) { const selectedLayout = LayoutStyles.find( - entry => !isSeparator(entry) && getNormalizedName(entry.layout) === layoutName + (entry) => !isSeparator(entry) && getNormalizedName(entry.layout) === layoutName ) if (selectedLayout) { const files = [...selectedLayout.samples] @@ -241,7 +241,7 @@ function initializeSamples(layoutName) { * Loads all layout modules and populates the layout combo box. */ function initializeLayoutAlgorithms() { - const layoutNames = LayoutStyles.map(entry => + const layoutNames = LayoutStyles.map((entry) => isSeparator(entry) ? comboBoxSeparatorItem : entry.layout ) initializeComboBox(layoutComboBox, layoutNames) @@ -436,7 +436,7 @@ async function onLayoutChanged(initSamples = true, appliedPresetId = '') { config.collapsedInitialization = true optionEditor.config = config - optionEditor.validateConfigCallback = b => { + optionEditor.validateConfigCallback = (b) => { configOptionsValid = b layoutButton.disabled = !(configOptionsValid && !inLayout) } @@ -716,9 +716,9 @@ function updateModifiedGraphSample() { * @param {!IGraph} graph */ function updateStyleDefaults(graph) { - const firstNode = graph.nodes.find(n => !graph.isGroupNode(n)) + const firstNode = graph.nodes.find((n) => !graph.isGroupNode(n)) const firstGroupNode = graph.nodes.find( - n => graph.isGroupNode(n) && !(n.style instanceof TableNodeStyle) + (n) => graph.isGroupNode(n) && !(n.style instanceof TableNodeStyle) ) const firstEdge = graph.edges.at(0) if (firstNode) { @@ -1004,7 +1004,7 @@ function addCustomGraphEntry() { customGraphSelected = true customGraph = customGraph || new DefaultGraph() let customGraphIdx = [...sampleComboBox.options].findIndex( - entry => entry.value === 'modified-graph' + (entry) => entry.value === 'modified-graph' ) if (customGraphIdx === -1) { const option = document.createElement('option') @@ -1076,7 +1076,7 @@ function initializeContextMenu(inputMode) { // Add event listeners to the various events that open the context menu. These listeners then // call the provided callback function which in turn asks the current ContextMenuInputMode if a // context menu should be shown at the current location. - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (inputMode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } @@ -1112,7 +1112,7 @@ function populateContextMenu(contextMenu, args) { ) // check whether a node was it. If it was, we prefer it over edges - const hit = hits.find(item => INode.isInstance(item)) || hits.at(0) + const hit = hits.find((item) => INode.isInstance(item)) || hits.at(0) if (!hit) { // empty canvas hit: provide 'select all' @@ -1128,7 +1128,7 @@ function populateContextMenu(contextMenu, args) { if (INode.isInstance(hit)) { contextMenu.addMenuItem('Select All Nodes', () => { graphComponent.selection.clear() - graphComponent.graph.nodes.forEach(node => { + graphComponent.graph.nodes.forEach((node) => { graphComponent.selection.setSelected(node, true) }) }) @@ -1139,7 +1139,7 @@ function populateContextMenu(contextMenu, args) { } else if (IEdge.isInstance(hit)) { contextMenu.addMenuItem('Select All Edges', () => { graphComponent.selection.clear() - graphComponent.graph.edges.forEach(edge => { + graphComponent.graph.edges.forEach((edge) => { graphComponent.selection.setSelected(edge, true) }) }) @@ -1246,15 +1246,15 @@ function initializeUI() { }) // apply layout shortcut with CTRL+Enter - window.addEventListener('keydown', e => { + window.addEventListener('keydown', (e) => { const geim = graphComponent.inputMode - if (!geim.textEditorInputMode.editing && e.which === 13 && (e.ctrlKey || e.metaKey)) { + if (!geim.textEditorInputMode.editing && e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { applyLayout(false) e.preventDefault() } }) // also allow 'enter' within the option-editor - document.querySelector(`#data-editor`).addEventListener('keydown', e => { + document.querySelector(`#data-editor`).addEventListener('keydown', (e) => { if (e.key === 'Enter') { applyLayout(false) e.preventDefault() diff --git a/demos/showcase/layoutstyles/LayoutStylesDemo.ts b/demos/showcase/layoutstyles/LayoutStylesDemo.ts index ed7ed0e77..012bb1746 100644 --- a/demos/showcase/layoutstyles/LayoutStylesDemo.ts +++ b/demos/showcase/layoutstyles/LayoutStylesDemo.ts @@ -212,7 +212,7 @@ function enableGraphML(): void { */ function initializeSamples(layoutName: string): void { const selectedLayout = LayoutStyles.find( - entry => !isSeparator(entry) && getNormalizedName(entry.layout) === layoutName + (entry) => !isSeparator(entry) && getNormalizedName(entry.layout) === layoutName ) as LayoutSample if (selectedLayout) { const files = [...selectedLayout.samples] @@ -230,7 +230,7 @@ function initializeSamples(layoutName: string): void { * Loads all layout modules and populates the layout combo box. */ function initializeLayoutAlgorithms(): void { - const layoutNames = LayoutStyles.map(entry => + const layoutNames = LayoutStyles.map((entry) => isSeparator(entry) ? comboBoxSeparatorItem : entry.layout ) initializeComboBox(layoutComboBox, layoutNames) @@ -688,9 +688,9 @@ function updateModifiedGraphSample(): void { * Adjusts the style defaults to match the overall graph theme. */ function updateStyleDefaults(graph: IGraph): void { - const firstNode = graph.nodes.find(n => !graph.isGroupNode(n)) + const firstNode = graph.nodes.find((n) => !graph.isGroupNode(n)) const firstGroupNode = graph.nodes.find( - n => graph.isGroupNode(n) && !(n.style instanceof TableNodeStyle) + (n) => graph.isGroupNode(n) && !(n.style instanceof TableNodeStyle) ) const firstEdge = graph.edges.at(0) if (firstNode) { @@ -945,7 +945,7 @@ function addCustomGraphEntry(): void { customGraphSelected = true customGraph = customGraph || new DefaultGraph() let customGraphIdx: number = [...sampleComboBox.options].findIndex( - entry => entry.value === 'modified-graph' + (entry) => entry.value === 'modified-graph' ) if (customGraphIdx === -1) { const option = document.createElement('option') @@ -1014,7 +1014,7 @@ function initializeContextMenu(inputMode: GraphInputMode): void { // Add event listeners to the various events that open the context menu. These listeners then // call the provided callback function which in turn asks the current ContextMenuInputMode if a // context menu should be shown at the current location. - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (inputMode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } @@ -1051,7 +1051,7 @@ function populateContextMenu( ) // check whether a node was it. If it was, we prefer it over edges - const hit = hits.find(item => INode.isInstance(item)) || hits.at(0) + const hit = hits.find((item) => INode.isInstance(item)) || hits.at(0) if (!hit) { // empty canvas hit: provide 'select all' @@ -1067,7 +1067,7 @@ function populateContextMenu( if (INode.isInstance(hit)) { contextMenu.addMenuItem('Select All Nodes', () => { graphComponent.selection.clear() - graphComponent.graph.nodes.forEach(node => { + graphComponent.graph.nodes.forEach((node) => { graphComponent.selection.setSelected(node, true) }) }) @@ -1078,7 +1078,7 @@ function populateContextMenu( } else if (IEdge.isInstance(hit)) { contextMenu.addMenuItem('Select All Edges', () => { graphComponent.selection.clear() - graphComponent.graph.edges.forEach(edge => { + graphComponent.graph.edges.forEach((edge) => { graphComponent.selection.setSelected(edge, true) }) }) @@ -1187,15 +1187,15 @@ function initializeUI(): void { }) // apply layout shortcut with CTRL+Enter - window.addEventListener('keydown', e => { + window.addEventListener('keydown', (e) => { const geim = graphComponent.inputMode as GraphEditorInputMode - if (!geim.textEditorInputMode.editing && e.which === 13 && (e.ctrlKey || e.metaKey)) { + if (!geim.textEditorInputMode.editing && e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { applyLayout(false) e.preventDefault() } }) // also allow 'enter' within the option-editor - document.querySelector(`#data-editor`)!.addEventListener('keydown', e => { + document.querySelector(`#data-editor`)!.addEventListener('keydown', (e) => { if (e.key === 'Enter') { applyLayout(false) e.preventDefault() diff --git a/demos/showcase/layoutstyles/OrganicLayoutConfig.js b/demos/showcase/layoutstyles/OrganicLayoutConfig.js index 53d6f78ea..0c41a809e 100644 --- a/demos/showcase/layoutstyles/OrganicLayoutConfig.js +++ b/demos/showcase/layoutstyles/OrganicLayoutConfig.js @@ -222,14 +222,14 @@ const OrganicLayoutConfig = Class('OrganicLayoutConfig', { // do nothing... break case GroupLayoutPolicy.FIX_GROUP_BOUNDS: - layoutData.groupNodeModes.delegate = node => { + layoutData.groupNodeModes.delegate = (node) => { return graphComponent.graph.isGroupNode(node) ? GroupNodeMode.FIX_BOUNDS : GroupNodeMode.NORMAL } break case GroupLayoutPolicy.FIX_GROUP_CONTENTS: - layoutData.groupNodeModes.delegate = node => { + layoutData.groupNodeModes.delegate = (node) => { return graphComponent.graph.isGroupNode(node) ? GroupNodeMode.FIX_CONTENTS : GroupNodeMode.NORMAL @@ -242,7 +242,7 @@ const OrganicLayoutConfig = Class('OrganicLayoutConfig', { } if (this.edgeDirectednessItem) { - layoutData.edgeDirectedness.delegate = edge => { + layoutData.edgeDirectedness.delegate = (edge) => { if ( edge.style instanceof PolylineEdgeStyle && edge.style.targetArrow && @@ -259,7 +259,7 @@ const OrganicLayoutConfig = Class('OrganicLayoutConfig', { } if (this.orientationItem !== null) { - layoutData.edgeOrientations.delegate = edge => { + layoutData.edgeOrientations.delegate = (edge) => { if ( edge.style instanceof PolylineEdgeStyle && edge.style.targetArrow && diff --git a/demos/showcase/layoutstyles/OrganicLayoutConfig.ts b/demos/showcase/layoutstyles/OrganicLayoutConfig.ts index ba33eed4c..6c2e7614a 100644 --- a/demos/showcase/layoutstyles/OrganicLayoutConfig.ts +++ b/demos/showcase/layoutstyles/OrganicLayoutConfig.ts @@ -226,14 +226,14 @@ const OrganicLayoutConfig = (Class as any)('OrganicLayoutConfig', { // do nothing... break case GroupLayoutPolicy.FIX_GROUP_BOUNDS: - layoutData.groupNodeModes.delegate = node => { + layoutData.groupNodeModes.delegate = (node) => { return graphComponent.graph.isGroupNode(node) ? GroupNodeMode.FIX_BOUNDS : GroupNodeMode.NORMAL } break case GroupLayoutPolicy.FIX_GROUP_CONTENTS: - layoutData.groupNodeModes.delegate = node => { + layoutData.groupNodeModes.delegate = (node) => { return graphComponent.graph.isGroupNode(node) ? GroupNodeMode.FIX_CONTENTS : GroupNodeMode.NORMAL @@ -246,7 +246,7 @@ const OrganicLayoutConfig = (Class as any)('OrganicLayoutConfig', { } if (this.edgeDirectednessItem) { - layoutData.edgeDirectedness.delegate = edge => { + layoutData.edgeDirectedness.delegate = (edge) => { if ( edge.style instanceof PolylineEdgeStyle && edge.style.targetArrow && @@ -263,7 +263,7 @@ const OrganicLayoutConfig = (Class as any)('OrganicLayoutConfig', { } if (this.orientationItem !== null) { - layoutData.edgeOrientations.delegate = edge => { + layoutData.edgeOrientations.delegate = (edge) => { if ( edge.style instanceof PolylineEdgeStyle && edge.style.targetArrow && diff --git a/demos/showcase/layoutstyles/OrthogonalLayoutConfig.js b/demos/showcase/layoutstyles/OrthogonalLayoutConfig.js index d0435b713..cea6efbfc 100644 --- a/demos/showcase/layoutstyles/OrthogonalLayoutConfig.js +++ b/demos/showcase/layoutstyles/OrthogonalLayoutConfig.js @@ -180,7 +180,7 @@ const OrthogonalLayoutConfig = Class('OrthogonalLayoutConfig', { }) } else { layoutData = new OrthogonalLayoutData({ - directedEdges: edge => false + directedEdges: (edge) => false }) } diff --git a/demos/showcase/layoutstyles/OrthogonalLayoutConfig.ts b/demos/showcase/layoutstyles/OrthogonalLayoutConfig.ts index 64c494a3d..cd793145e 100644 --- a/demos/showcase/layoutstyles/OrthogonalLayoutConfig.ts +++ b/demos/showcase/layoutstyles/OrthogonalLayoutConfig.ts @@ -184,7 +184,7 @@ const OrthogonalLayoutConfig = (Class as any)('OrthogonalLayoutConfig', { }) } else { layoutData = new OrthogonalLayoutData({ - directedEdges: edge => false + directedEdges: (edge) => false }) } diff --git a/demos/showcase/layoutstyles/ParallelEdgeRouterConfig.js b/demos/showcase/layoutstyles/ParallelEdgeRouterConfig.js index 67d915fc0..cf5af77e1 100644 --- a/demos/showcase/layoutstyles/ParallelEdgeRouterConfig.js +++ b/demos/showcase/layoutstyles/ParallelEdgeRouterConfig.js @@ -102,12 +102,12 @@ const ParallelEdgeRouterConfig = Class('ParallelEdgeRouterConfig', { const selection = graphComponent.selection if (this.scopeItem === Scope.SCOPE_AT_SELECTED_NODES) { - layoutData.affectedEdges.delegate = edge => + layoutData.affectedEdges.delegate = (edge) => selection.isSelected(edge.sourceNode) || selection.isSelected(edge.targetNode) } else if (this.scopeItem === Scope.SCOPE_SELECTED_EDGES) { layoutData.affectedEdges.items = selection.selectedEdges.toList() } else { - layoutData.affectedEdges.delegate = edge => true + layoutData.affectedEdges.delegate = (edge) => true } if (this.useSelectedEdgesAsMasterItem) { diff --git a/demos/showcase/layoutstyles/ParallelEdgeRouterConfig.ts b/demos/showcase/layoutstyles/ParallelEdgeRouterConfig.ts index f9c47cb2f..a9068bf50 100644 --- a/demos/showcase/layoutstyles/ParallelEdgeRouterConfig.ts +++ b/demos/showcase/layoutstyles/ParallelEdgeRouterConfig.ts @@ -106,12 +106,12 @@ const ParallelEdgeRouterConfig = (Class as any)('ParallelEdgeRouterConfig', { const selection = graphComponent.selection if (this.scopeItem === Scope.SCOPE_AT_SELECTED_NODES) { - layoutData.affectedEdges.delegate = edge => + layoutData.affectedEdges.delegate = (edge) => selection.isSelected(edge.sourceNode!) || selection.isSelected(edge.targetNode!) } else if (this.scopeItem === Scope.SCOPE_SELECTED_EDGES) { layoutData.affectedEdges.items = selection.selectedEdges.toList() } else { - layoutData.affectedEdges.delegate = edge => true + layoutData.affectedEdges.delegate = (edge) => true } if (this.useSelectedEdgesAsMasterItem) { diff --git a/demos/showcase/layoutstyles/PolylineEdgeRouterConfig.js b/demos/showcase/layoutstyles/PolylineEdgeRouterConfig.js index 61099334b..0e81617a7 100644 --- a/demos/showcase/layoutstyles/PolylineEdgeRouterConfig.js +++ b/demos/showcase/layoutstyles/PolylineEdgeRouterConfig.js @@ -174,7 +174,7 @@ const PolylineEdgeRouterConfig = Class('PolylineEdgeRouterConfig', { */ createConfiguredLayoutData: function (graphComponent, layout) { const layoutData = new EdgeRouterData({ - edgeLayoutDescriptors: edge => { + edgeLayoutDescriptors: (edge) => { const descriptor = new EdgeRouterEdgeLayoutDescriptor({ minimumEdgeToEdgeDistance: this.minimumEdgeToEdgeDistanceItem, minimumNodeCornerDistance: this.minimumNodeCornerDistanceItem, @@ -212,7 +212,7 @@ const PolylineEdgeRouterConfig = Class('PolylineEdgeRouterConfig', { if (this.useIntermediatePointsItem) { const intermediateRoutingPoints = new List() - edge.bends.forEach(bend => + edge.bends.forEach((bend) => intermediateRoutingPoints.add(new YPoint(bend.location.x, bend.location.y)) ) descriptor.intermediateRoutingPoints = intermediateRoutingPoints @@ -224,12 +224,12 @@ const PolylineEdgeRouterConfig = Class('PolylineEdgeRouterConfig', { const selection = graphComponent.selection if (this.scopeItem === EdgeRouterScope.ROUTE_EDGES_AT_AFFECTED_NODES) { - layoutData.affectedNodes.delegate = node => selection.isSelected(node) + layoutData.affectedNodes.delegate = (node) => selection.isSelected(node) } else if (this.scopeItem === EdgeRouterScope.ROUTE_AFFECTED_EDGES) { - layoutData.affectedEdges.delegate = edge => selection.isSelected(edge) + layoutData.affectedEdges.delegate = (edge) => selection.isSelected(edge) } else { - layoutData.affectedEdges.delegate = edge => true - layoutData.affectedNodes.delegate = edge => true + layoutData.affectedEdges.delegate = (edge) => true + layoutData.affectedNodes.delegate = (edge) => true } if (this.portSidesItem !== PortSides.ANY) { @@ -257,11 +257,11 @@ const PolylineEdgeRouterConfig = Class('PolylineEdgeRouterConfig', { } case BusMembership.LABEL: { const visitedLabels = new Set() - graphComponent.graph.edgeLabels.forEach(label => { + graphComponent.graph.edgeLabels.forEach((label) => { if (!visitedLabels.has(label.text)) { visitedLabels.add(label.text) const busDescriptor = this.createBusDescriptor() - layoutData.buses.add(busDescriptor).delegate = edge => + layoutData.buses.add(busDescriptor).delegate = (edge) => edge.labels.size > 0 && edge.labels.first().text === label.text } }) @@ -269,7 +269,7 @@ const PolylineEdgeRouterConfig = Class('PolylineEdgeRouterConfig', { } case BusMembership.TAG: { const visitedTags = new Set() - graphComponent.graph.edges.forEach(edge => { + graphComponent.graph.edges.forEach((edge) => { const tag = edge.tag if (!tag) { const busDescriptor = this.createBusDescriptor() @@ -278,7 +278,7 @@ const PolylineEdgeRouterConfig = Class('PolylineEdgeRouterConfig', { visitedTags.add(tag) const busDescriptor = this.createBusDescriptor() - layoutData.buses.add(busDescriptor).delegate = edge => edge.tag === tag + layoutData.buses.add(busDescriptor).delegate = (edge) => edge.tag === tag } }) break diff --git a/demos/showcase/layoutstyles/PolylineEdgeRouterConfig.ts b/demos/showcase/layoutstyles/PolylineEdgeRouterConfig.ts index 2b599d157..aea966821 100644 --- a/demos/showcase/layoutstyles/PolylineEdgeRouterConfig.ts +++ b/demos/showcase/layoutstyles/PolylineEdgeRouterConfig.ts @@ -178,7 +178,7 @@ const PolylineEdgeRouterConfig = (Class as any)('PolylineEdgeRouterConfig', { layout: EdgeRouter ): LayoutData { const layoutData = new EdgeRouterData({ - edgeLayoutDescriptors: edge => { + edgeLayoutDescriptors: (edge) => { const descriptor = new EdgeRouterEdgeLayoutDescriptor({ minimumEdgeToEdgeDistance: this.minimumEdgeToEdgeDistanceItem, minimumNodeCornerDistance: this.minimumNodeCornerDistanceItem, @@ -216,7 +216,7 @@ const PolylineEdgeRouterConfig = (Class as any)('PolylineEdgeRouterConfig', { if (this.useIntermediatePointsItem) { const intermediateRoutingPoints = new List() - edge.bends.forEach(bend => + edge.bends.forEach((bend) => intermediateRoutingPoints.add(new YPoint(bend.location.x, bend.location.y)) ) descriptor.intermediateRoutingPoints = intermediateRoutingPoints @@ -228,12 +228,12 @@ const PolylineEdgeRouterConfig = (Class as any)('PolylineEdgeRouterConfig', { const selection = graphComponent.selection if (this.scopeItem === EdgeRouterScope.ROUTE_EDGES_AT_AFFECTED_NODES) { - layoutData.affectedNodes.delegate = node => selection.isSelected(node) + layoutData.affectedNodes.delegate = (node) => selection.isSelected(node) } else if (this.scopeItem === EdgeRouterScope.ROUTE_AFFECTED_EDGES) { - layoutData.affectedEdges.delegate = edge => selection.isSelected(edge) + layoutData.affectedEdges.delegate = (edge) => selection.isSelected(edge) } else { - layoutData.affectedEdges.delegate = edge => true - layoutData.affectedNodes.delegate = edge => true + layoutData.affectedEdges.delegate = (edge) => true + layoutData.affectedNodes.delegate = (edge) => true } if (this.portSidesItem !== PortSides.ANY) { @@ -261,11 +261,11 @@ const PolylineEdgeRouterConfig = (Class as any)('PolylineEdgeRouterConfig', { } case BusMembership.LABEL: { const visitedLabels = new Set() - graphComponent.graph.edgeLabels.forEach(label => { + graphComponent.graph.edgeLabels.forEach((label) => { if (!visitedLabels.has(label.text)) { visitedLabels.add(label.text) const busDescriptor = this.createBusDescriptor() - layoutData.buses.add(busDescriptor).delegate = edge => + layoutData.buses.add(busDescriptor).delegate = (edge) => edge.labels.size > 0 && edge.labels.first().text === label.text } }) @@ -273,7 +273,7 @@ const PolylineEdgeRouterConfig = (Class as any)('PolylineEdgeRouterConfig', { } case BusMembership.TAG: { const visitedTags = new Set() - graphComponent.graph.edges.forEach(edge => { + graphComponent.graph.edges.forEach((edge) => { const tag = edge.tag if (!tag) { const busDescriptor = this.createBusDescriptor() @@ -282,7 +282,7 @@ const PolylineEdgeRouterConfig = (Class as any)('PolylineEdgeRouterConfig', { visitedTags.add(tag) const busDescriptor = this.createBusDescriptor() - layoutData.buses.add(busDescriptor).delegate = edge => edge.tag === tag + layoutData.buses.add(busDescriptor).delegate = (edge) => edge.tag === tag } }) break diff --git a/demos/showcase/layoutstyles/PresetsUiBuilder.js b/demos/showcase/layoutstyles/PresetsUiBuilder.js index d776c49f2..088c82315 100644 --- a/demos/showcase/layoutstyles/PresetsUiBuilder.js +++ b/demos/showcase/layoutstyles/PresetsUiBuilder.js @@ -122,7 +122,7 @@ export class PresetsUiBuilder { createPresetButton(preset, presetId, handler, invalidPresets) { const btn = document.createElement('button') btn.innerText = preset.label - btn.addEventListener('click', e => { + btn.addEventListener('click', (e) => { if (btn.classList.contains(CSS_CLASS_INVALID_PRESET)) { //ignore click because preset is invalid for current sample return @@ -141,7 +141,7 @@ export class PresetsUiBuilder { } if (preset.description) { - btn.onmouseenter = e => { + btn.onmouseenter = (e) => { const invalid = btn.classList.contains(CSS_CLASS_INVALID_PRESET) // open tooltip with delay this.tooltipTimer = setTimeout(() => { @@ -205,7 +205,7 @@ function newButtonHandler(optionEditor, preset) { } } - return htmlElement => { + return (htmlElement) => { updateCss(htmlElement, CSS_CLASS_PRESET_APPLIED) applyValues(optionEditor, setters) } diff --git a/demos/showcase/layoutstyles/PresetsUiBuilder.ts b/demos/showcase/layoutstyles/PresetsUiBuilder.ts index 8bf495c55..17919bd39 100644 --- a/demos/showcase/layoutstyles/PresetsUiBuilder.ts +++ b/demos/showcase/layoutstyles/PresetsUiBuilder.ts @@ -125,7 +125,7 @@ export class PresetsUiBuilder { ): HTMLButtonElement { const btn = document.createElement('button') btn.innerText = preset.label - btn.addEventListener('click', e => { + btn.addEventListener('click', (e) => { if (btn.classList.contains(CSS_CLASS_INVALID_PRESET)) { //ignore click because preset is invalid for current sample return @@ -144,7 +144,7 @@ export class PresetsUiBuilder { } if (preset.description) { - btn.onmouseenter = e => { + btn.onmouseenter = (e) => { const invalid = btn.classList.contains(CSS_CLASS_INVALID_PRESET) // open tooltip with delay this.tooltipTimer = setTimeout(() => { diff --git a/demos/showcase/layoutstyles/RadialLayoutConfig.js b/demos/showcase/layoutstyles/RadialLayoutConfig.js index 2054c0f5e..9330496a0 100644 --- a/demos/showcase/layoutstyles/RadialLayoutConfig.js +++ b/demos/showcase/layoutstyles/RadialLayoutConfig.js @@ -177,7 +177,7 @@ const RadialLayoutConfig = Class('RadialLayoutConfig', { this.nodeLabelingStyleItem === NodeLabelingPolicies.RAYLIKE || this.nodeLabelingStyleItem === NodeLabelingPolicies.HORIZONTAL ) { - graphComponent.graph.nodeLabels.forEach(label => { + graphComponent.graph.nodeLabels.forEach((label) => { graphComponent.graph.setLabelLayoutParameter( label, FreeNodeLabelModel.INSTANCE.findBestParameter( @@ -200,7 +200,7 @@ const RadialLayoutConfig = Class('RadialLayoutConfig', { let layoutData if (this.centerStrategyItem === CenterNodesPolicy.CUSTOM) { layoutData = new RadialLayoutData({ - centerNodes: node => graphComponent.selection.isSelected(node) + centerNodes: (node) => graphComponent.selection.isSelected(node) }) } else { layoutData = new RadialLayoutData() diff --git a/demos/showcase/layoutstyles/RadialLayoutConfig.ts b/demos/showcase/layoutstyles/RadialLayoutConfig.ts index 7ac34b107..88c6c3e5b 100644 --- a/demos/showcase/layoutstyles/RadialLayoutConfig.ts +++ b/demos/showcase/layoutstyles/RadialLayoutConfig.ts @@ -178,7 +178,7 @@ const RadialLayoutConfig = (Class as any)('RadialLayoutConfig', { this.nodeLabelingStyleItem === NodeLabelingPolicies.RAYLIKE || this.nodeLabelingStyleItem === NodeLabelingPolicies.HORIZONTAL ) { - graphComponent.graph.nodeLabels.forEach(label => { + graphComponent.graph.nodeLabels.forEach((label) => { graphComponent.graph.setLabelLayoutParameter( label, FreeNodeLabelModel.INSTANCE.findBestParameter( @@ -204,7 +204,7 @@ const RadialLayoutConfig = (Class as any)('RadialLayoutConfig', { let layoutData if (this.centerStrategyItem === CenterNodesPolicy.CUSTOM) { layoutData = new RadialLayoutData({ - centerNodes: node => graphComponent.selection.isSelected(node) + centerNodes: (node) => graphComponent.selection.isSelected(node) }) } else { layoutData = new RadialLayoutData() diff --git a/demos/showcase/layoutstyles/TabularLayoutConfig.js b/demos/showcase/layoutstyles/TabularLayoutConfig.js index a2c112e70..06bc7f817 100644 --- a/demos/showcase/layoutstyles/TabularLayoutConfig.js +++ b/demos/showcase/layoutstyles/TabularLayoutConfig.js @@ -175,12 +175,12 @@ const TabularLayoutConfig = Class('TabularLayoutConfig', { const minimumRowHeight = this.minimumRowHeightItem const minimumColumnWidth = this.minimumColumnWidthItem const cellInsets = this.cellInsetsItem - partitionGrid.rows.forEach(row => { + partitionGrid.rows.forEach((row) => { row.minimumHeight = minimumRowHeight row.topInset = cellInsets row.bottomInset = cellInsets }) - partitionGrid.columns.forEach(column => { + partitionGrid.columns.forEach((column) => { column.minimumWidth = minimumColumnWidth column.leftInset = cellInsets column.rightInset = cellInsets diff --git a/demos/showcase/layoutstyles/TabularLayoutConfig.ts b/demos/showcase/layoutstyles/TabularLayoutConfig.ts index 6cdcfccdb..3aa478abe 100644 --- a/demos/showcase/layoutstyles/TabularLayoutConfig.ts +++ b/demos/showcase/layoutstyles/TabularLayoutConfig.ts @@ -179,12 +179,12 @@ const TabularLayoutConfig = (Class as any)('TabularLayoutConfig', { const minimumRowHeight = this.minimumRowHeightItem const minimumColumnWidth = this.minimumColumnWidthItem const cellInsets = this.cellInsetsItem - partitionGrid.rows.forEach(row => { + partitionGrid.rows.forEach((row) => { row.minimumHeight = minimumRowHeight row.topInset = cellInsets row.bottomInset = cellInsets }) - partitionGrid.columns.forEach(column => { + partitionGrid.columns.forEach((column) => { column.minimumWidth = minimumColumnWidth column.leftInset = cellInsets column.rightInset = cellInsets diff --git a/demos/showcase/layoutstyles/TreeLayoutConfig.js b/demos/showcase/layoutstyles/TreeLayoutConfig.js index dc51d2788..b61ae4f59 100644 --- a/demos/showcase/layoutstyles/TreeLayoutConfig.js +++ b/demos/showcase/layoutstyles/TreeLayoutConfig.js @@ -179,8 +179,8 @@ const TreeLayoutConfig = Class('TreeLayoutConfig', { this.nodePlacerItem === TreeNodePlacer.HV ? this.createLayoutDataHorizontalVertical(graphComponent) : this.nodePlacerItem === TreeNodePlacer.DELEGATING_LAYERED - ? this.createLayoutDataDelegatingPlacer(graphComponent) - : this.createLayoutDataTree(graphComponent, layout) + ? this.createLayoutDataDelegatingPlacer(graphComponent) + : this.createLayoutDataTree(graphComponent, layout) return layoutData.combineWith( this.createLabelingLayoutData( @@ -196,7 +196,7 @@ const TreeLayoutConfig = Class('TreeLayoutConfig', { createLayoutDataTree: function (graphComponent, layout) { const graph = graphComponent.graph return new TreeLayoutData({ - gridNodePlacerRowIndices: node => { + gridNodePlacerRowIndices: (node) => { const predecessors = graph.predecessors(node) const parent = predecessors.at(0) if (parent) { @@ -205,7 +205,7 @@ const TreeLayoutConfig = Class('TreeLayoutConfig', { } return 0 }, - leftRightNodePlacerLeftNodes: node => { + leftRightNodePlacerLeftNodes: (node) => { const predecessors = graph.predecessors(node) const parent = predecessors.at(0) if (parent) { @@ -215,7 +215,7 @@ const TreeLayoutConfig = Class('TreeLayoutConfig', { return false }, compactNodePlacerStrategyMementos: new Mapper(), - assistantNodes: node => { + assistantNodes: (node) => { return node.tag ? node.tag.assistant : null } }) @@ -223,7 +223,7 @@ const TreeLayoutConfig = Class('TreeLayoutConfig', { createLayoutDataHorizontalVertical: function (graphComponent) { return new TreeLayoutData({ - nodePlacers: node => { + nodePlacers: (node) => { // children of selected nodes should be placed vertical and to the right of their child nodes, while // the children of non-selected horizontal downwards const childPlacement = graphComponent.selection.isSelected(node) @@ -245,7 +245,7 @@ const TreeLayoutConfig = Class('TreeLayoutConfig', { const graph = graphComponent.graph //half the subtrees are delegated to the left placer and half to the right placer const leftNodes = new Set() - const root = graph.nodes.find(node => graph.inDegree(node) === 0) + const root = graph.nodes.find((node) => graph.inDegree(node) === 0) let left = true for (const successor of graph.successors(root)) { const stack = [successor] @@ -260,9 +260,9 @@ const TreeLayoutConfig = Class('TreeLayoutConfig', { left = !left } const layoutData = new TreeLayoutData({ - delegatingNodePlacerPrimaryNodes: node => leftNodes.has(node), + delegatingNodePlacerPrimaryNodes: (node) => leftNodes.has(node), // tells the layout which node placer to use for a node - nodePlacers: node => { + nodePlacers: (node) => { if (node === root) { return this.delegatingRootPlacer } diff --git a/demos/showcase/layoutstyles/TreeLayoutConfig.ts b/demos/showcase/layoutstyles/TreeLayoutConfig.ts index 31cfc3ded..9cf70fc63 100644 --- a/demos/showcase/layoutstyles/TreeLayoutConfig.ts +++ b/demos/showcase/layoutstyles/TreeLayoutConfig.ts @@ -183,8 +183,8 @@ const TreeLayoutConfig = (Class as any)('TreeLayoutConfig', { this.nodePlacerItem === TreeNodePlacer.HV ? this.createLayoutDataHorizontalVertical(graphComponent) : this.nodePlacerItem === TreeNodePlacer.DELEGATING_LAYERED - ? this.createLayoutDataDelegatingPlacer(graphComponent) - : this.createLayoutDataTree(graphComponent, layout) + ? this.createLayoutDataDelegatingPlacer(graphComponent) + : this.createLayoutDataTree(graphComponent, layout) return layoutData.combineWith( this.createLabelingLayoutData( @@ -200,7 +200,7 @@ const TreeLayoutConfig = (Class as any)('TreeLayoutConfig', { createLayoutDataTree: function (graphComponent: GraphComponent, layout: TreeLayout): LayoutData { const graph = graphComponent.graph return new TreeLayoutData({ - gridNodePlacerRowIndices: node => { + gridNodePlacerRowIndices: (node) => { const predecessors = graph.predecessors(node) const parent = predecessors.at(0) if (parent) { @@ -209,7 +209,7 @@ const TreeLayoutConfig = (Class as any)('TreeLayoutConfig', { } return 0 }, - leftRightNodePlacerLeftNodes: node => { + leftRightNodePlacerLeftNodes: (node) => { const predecessors = graph.predecessors(node) const parent = predecessors.at(0) if (parent) { @@ -219,7 +219,7 @@ const TreeLayoutConfig = (Class as any)('TreeLayoutConfig', { return false }, compactNodePlacerStrategyMementos: new Mapper(), - assistantNodes: node => { + assistantNodes: (node) => { return node.tag ? node.tag.assistant : null } }) @@ -227,7 +227,7 @@ const TreeLayoutConfig = (Class as any)('TreeLayoutConfig', { createLayoutDataHorizontalVertical: function (graphComponent: GraphComponent): TreeLayoutData { return new TreeLayoutData({ - nodePlacers: node => { + nodePlacers: (node) => { // children of selected nodes should be placed vertical and to the right of their child nodes, while // the children of non-selected horizontal downwards const childPlacement = graphComponent.selection.isSelected(node) @@ -249,7 +249,7 @@ const TreeLayoutConfig = (Class as any)('TreeLayoutConfig', { const graph = graphComponent.graph //half the subtrees are delegated to the left placer and half to the right placer const leftNodes = new Set() - const root = graph.nodes.find(node => graph.inDegree(node) === 0)! + const root = graph.nodes.find((node) => graph.inDegree(node) === 0)! let left = true for (const successor of graph.successors(root)) { const stack = [successor] @@ -264,9 +264,9 @@ const TreeLayoutConfig = (Class as any)('TreeLayoutConfig', { left = !left } const layoutData = new TreeLayoutData({ - delegatingNodePlacerPrimaryNodes: node => leftNodes.has(node), + delegatingNodePlacerPrimaryNodes: (node) => leftNodes.has(node), // tells the layout which node placer to use for a node - nodePlacers: node => { + nodePlacers: (node) => { if (node === root) { return this.delegatingRootPlacer } diff --git a/demos/showcase/layoutstyles/index.html b/demos/showcase/layoutstyles/index.html index b80a0b7ec..b2768c3bd 100644 --- a/demos/showcase/layoutstyles/index.html +++ b/demos/showcase/layoutstyles/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/layoutstyles/resources/edge-labels.graphml b/demos/showcase/layoutstyles/resources/edge-labels.graphml index b7b375b74..aef3f6b5a 100644 --- a/demos/showcase/layoutstyles/resources/edge-labels.graphml +++ b/demos/showcase/layoutstyles/resources/edge-labels.graphml @@ -28,7 +28,7 @@ // ////////////////////////////////////////////////////////////////////////--> - + diff --git a/demos/showcase/layoutstyles/resources/styles.css b/demos/showcase/layoutstyles/resources/styles.css index fa40899ad..b300abd0d 100644 --- a/demos/showcase/layoutstyles/resources/styles.css +++ b/demos/showcase/layoutstyles/resources/styles.css @@ -161,7 +161,8 @@ border-width: 0; overflow: visible; cursor: pointer; - transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1), + transition: + background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1); } @@ -273,7 +274,9 @@ padding: 0 18px; box-sizing: border-box; background: #dadada; - box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), + box-shadow: + 0 2px 4px -1px rgba(0, 0, 0, 0.2), + 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12) !important; } diff --git a/demos/showcase/logicgates/LogicGatesDemo.js b/demos/showcase/logicgates/LogicGatesDemo.js index 0f0d35269..e8ce3d539 100644 --- a/demos/showcase/logicgates/LogicGatesDemo.js +++ b/demos/showcase/logicgates/LogicGatesDemo.js @@ -108,7 +108,7 @@ function createDragAndDropPanelNodes() { ] const nodeContainer = nodeStyles.map( - style => + (style) => new SimpleNode({ layout: new Rect(0, 0, 100, 50), style: style @@ -135,7 +135,7 @@ function initializeGraph() { graph.nodeDefaults.ports.labels.style = new VoidLabelStyle() // set the port candidate provider graph.decorator.nodeDecorator.portCandidateProviderDecorator.setFactory( - node => new DescriptorDependentPortCandidateProvider(node) + (node) => new DescriptorDependentPortCandidateProvider(node) ) graph.edgeDefaults.style = new PolylineEdgeStyle({ stroke: '2px black' }) graph.decorator.edgeDecorator.edgeReconnectionPortCandidateProviderDecorator.setImplementation( diff --git a/demos/showcase/logicgates/LogicGatesDemo.ts b/demos/showcase/logicgates/LogicGatesDemo.ts index 2a87cffac..8b828ceb0 100644 --- a/demos/showcase/logicgates/LogicGatesDemo.ts +++ b/demos/showcase/logicgates/LogicGatesDemo.ts @@ -103,7 +103,7 @@ function createDragAndDropPanelNodes(): SimpleNode[] { ] const nodeContainer = nodeStyles.map( - style => + (style) => new SimpleNode({ layout: new Rect(0, 0, 100, 50), style: style @@ -130,7 +130,7 @@ function initializeGraph(): void { graph.nodeDefaults.ports.labels.style = new VoidLabelStyle() // set the port candidate provider graph.decorator.nodeDecorator.portCandidateProviderDecorator.setFactory( - node => new DescriptorDependentPortCandidateProvider(node) + (node) => new DescriptorDependentPortCandidateProvider(node) ) graph.edgeDefaults.style = new PolylineEdgeStyle({ stroke: '2px black' }) graph.decorator.edgeDecorator.edgeReconnectionPortCandidateProviderDecorator.setImplementation( diff --git a/demos/showcase/logicgates/index.html b/demos/showcase/logicgates/index.html index f76931e33..896e00221 100644 --- a/demos/showcase/logicgates/index.html +++ b/demos/showcase/logicgates/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/logicgates/logicgates-layout.js b/demos/showcase/logicgates/logicgates-layout.js index 7990748c5..26ef0b841 100644 --- a/demos/showcase/logicgates/logicgates-layout.js +++ b/demos/showcase/logicgates/logicgates-layout.js @@ -58,14 +58,14 @@ export async function runLayout(graphComponent, clearUndo, fitBounds = true) { orthogonalRouting: true }) layoutData = new HierarchicLayoutData({ - sourcePortConstraints: _ => PortConstraint.create(PortSide.EAST, true), - targetPortConstraints: _ => PortConstraint.create(PortSide.WEST, true) + sourcePortConstraints: (_) => PortConstraint.create(PortSide.EAST, true), + targetPortConstraints: (_) => PortConstraint.create(PortSide.WEST, true) }) } else { layout = new EdgeRouter() layoutData = new EdgeRouterData({ - sourcePortConstraints: _ => PortConstraint.create(PortSide.EAST, true), - targetPortConstraints: _ => PortConstraint.create(PortSide.WEST, true) + sourcePortConstraints: (_) => PortConstraint.create(PortSide.EAST, true), + targetPortConstraints: (_) => PortConstraint.create(PortSide.WEST, true) }) } diff --git a/demos/showcase/logicgates/logicgates-layout.ts b/demos/showcase/logicgates/logicgates-layout.ts index 10b30b6bc..ac3acdd57 100644 --- a/demos/showcase/logicgates/logicgates-layout.ts +++ b/demos/showcase/logicgates/logicgates-layout.ts @@ -63,14 +63,14 @@ export async function runLayout( orthogonalRouting: true }) layoutData = new HierarchicLayoutData({ - sourcePortConstraints: _ => PortConstraint.create(PortSide.EAST, true), - targetPortConstraints: _ => PortConstraint.create(PortSide.WEST, true) + sourcePortConstraints: (_) => PortConstraint.create(PortSide.EAST, true), + targetPortConstraints: (_) => PortConstraint.create(PortSide.WEST, true) }) } else { layout = new EdgeRouter() layoutData = new EdgeRouterData({ - sourcePortConstraints: _ => PortConstraint.create(PortSide.EAST, true), - targetPortConstraints: _ => PortConstraint.create(PortSide.WEST, true) + sourcePortConstraints: (_) => PortConstraint.create(PortSide.EAST, true), + targetPortConstraints: (_) => PortConstraint.create(PortSide.WEST, true) }) } diff --git a/demos/showcase/logicgates/node-styles/GateNodeStyle.js b/demos/showcase/logicgates/node-styles/GateNodeStyle.js index 8b27bb592..40fc08a86 100644 --- a/demos/showcase/logicgates/node-styles/GateNodeStyle.js +++ b/demos/showcase/logicgates/node-styles/GateNodeStyle.js @@ -184,8 +184,8 @@ export function createRenderDataCache(node) { */ export function getPointOnCurve(t, firstPoint, endPoint, c1, c2) { return ( - Math.pow(1 - t, 3) * firstPoint.x + - 3 * Math.pow(1 - t, 2) * t * c1.x + + (1 - t) ** 3 * firstPoint.x + + 3 * (1 - t) ** 2 * t * c1.x + 3 * (1 - t) * t * t * c2.x + t * t * t * endPoint.x ) diff --git a/demos/showcase/logicgates/node-styles/GateNodeStyle.ts b/demos/showcase/logicgates/node-styles/GateNodeStyle.ts index bfe7048d6..d35cee3e3 100644 --- a/demos/showcase/logicgates/node-styles/GateNodeStyle.ts +++ b/demos/showcase/logicgates/node-styles/GateNodeStyle.ts @@ -147,8 +147,8 @@ export function getPointOnCurve( c2: Point ): number { return ( - Math.pow(1 - t, 3) * firstPoint.x + - 3 * Math.pow(1 - t, 2) * t * c1.x + + (1 - t) ** 3 * firstPoint.x + + 3 * (1 - t) ** 2 * t * c1.x + 3 * (1 - t) * t * t * c2.x + t * t * t * endPoint.x ) diff --git a/demos/showcase/map/MapDemo.js b/demos/showcase/map/MapDemo.js index 4d578c863..6d2f14c8c 100644 --- a/demos/showcase/map/MapDemo.js +++ b/demos/showcase/map/MapDemo.js @@ -72,7 +72,7 @@ async function run() { /** * Builds the initial graph from FlightData. * @param {!GraphComponent} graphComponent - * @param {!Map} map + * @param {!LeafletMap} map */ function createGraph(graphComponent, map) { const graph = graphComponent.graph @@ -92,7 +92,7 @@ function createGraph(graphComponent, map) { builder.buildGraph() // add a filter to determine which nodes are visible depending on the zoom level of the map - graphComponent.graph = new FilteredGraphWrapper(graph, node => { + graphComponent.graph = new FilteredGraphWrapper(graph, (node) => { const zoom = map.getZoom() const airportData = getAirportData(node) const passengers = airportData.passengers @@ -158,7 +158,7 @@ function zoomChanged(graphComponent, zoom) { updateHighlights(graphComponent) // update the label for the airports since they depend on the zoom level - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const airportData = getAirportData(node) // show the airport's IATA-code when the zoom value is low graph.setLabelText(node.labels.at(0), zoom >= 4 ? airportData.name : airportData.iata) diff --git a/demos/showcase/map/MapDemo.ts b/demos/showcase/map/MapDemo.ts index 09c4cf4c4..53e8dd9d8 100644 --- a/demos/showcase/map/MapDemo.ts +++ b/demos/showcase/map/MapDemo.ts @@ -40,7 +40,7 @@ import { finishLoading } from 'demo-resources/demo-page' import { flightData } from './resources/flight-data' import { initializeDefaultMapStyles } from './map-styles' import { createMap } from './leaflet-graph-layer' -import type { Map } from 'leaflet' +import type { Map as LeafletMap } from 'leaflet' import { initializeShortestPaths, updateHighlights } from './shortest-paths' import { getAirportData } from './data-types' @@ -78,7 +78,7 @@ async function run(): Promise { /** * Builds the initial graph from FlightData. */ -function createGraph(graphComponent: GraphComponent, map: Map): void { +function createGraph(graphComponent: GraphComponent, map: LeafletMap): void { const graph = graphComponent.graph // prepare the styles for the graph @@ -155,7 +155,7 @@ function zoomChanged(graphComponent: GraphComponent, zoom: number): void { updateHighlights(graphComponent) // update the label for the airports since they depend on the zoom level - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const airportData = getAirportData(node) // show the airport's IATA-code when the zoom value is low graph.setLabelText(node.labels.at(0)!, zoom >= 4 ? airportData.name : airportData.iata) diff --git a/demos/showcase/map/index.html b/demos/showcase/map/index.html index 25b72404e..85a52c4a7 100644 --- a/demos/showcase/map/index.html +++ b/demos/showcase/map/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/map/leaflet-graph-layer.js b/demos/showcase/map/leaflet-graph-layer.js index 9bf6c1d9e..841422509 100644 --- a/demos/showcase/map/leaflet-graph-layer.js +++ b/demos/showcase/map/leaflet-graph-layer.js @@ -26,7 +26,15 @@ ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ** ***************************************************************************/ -import { control, DomUtil, LatLng, latLngBounds, Layer, Map, TileLayer } from 'leaflet' +import { + control, + DomUtil, + LatLng, + latLngBounds, + Layer, + Map as LeafletMap, + TileLayer +} from 'leaflet' import { GraphComponent, GraphItemTypes, GraphViewerInputMode, Point } from 'yfiles' import { getArcHeight } from './map-styles.js' import 'leaflet/dist/leaflet.css' @@ -45,7 +53,7 @@ import 'leaflet/dist/leaflet.css' /** * @typedef {Object} MapData * @property {GraphLayer} graphLayer - * @property {Map} map + * @property {LeafletMap} map */ /** @@ -64,7 +72,7 @@ export function createMap(containerId, coordinateMapping, zoomChanged, leafletOp const osmAttrib = 'Map data © OpenStreetMap contributors' // create the map - const worldMap = new Map(containerId, leafletOptions) + const worldMap = new LeafletMap(containerId, leafletOptions) worldMap.setView(new LatLng(15.538, 16.523), 3) worldMap.addLayer( new TileLayer(osmUrl, { @@ -136,7 +144,7 @@ export class GraphLayer extends Layer { /** * @yjs:keep = animate - * @param {!Map} map + * @param {!LeafletMap} map * @returns {*} */ onAdd(map) { @@ -168,7 +176,7 @@ export class GraphLayer extends Layer { } /** - * @param {!Map} map + * @param {!LeafletMap} map * @returns {*} */ onRemove(map) { @@ -197,7 +205,7 @@ export class GraphLayer extends Layer { /** * Synchronizes the viewport of the map and the {@link GraphComponent}. * @yjs:keep = getSize,setPosition,getPosition - * @param {!Map} map + * @param {!LeafletMap} map */ updateGraphDiv(map) { const graphComponent = this.graphComponent @@ -234,12 +242,12 @@ export class GraphLayer extends Layer { * Calculates the coordinates of the nodes from their geolocations * and updates the arc heights of the edges. * @param {!GraphComponent} graphComponent - * @param {!Map} map + * @param {!LeafletMap} map */ mapLayout(graphComponent, map) { const graph = graphComponent.graph // transform geolocations and update the node locations - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const coords = this.getGeoCoordinates(node) const layerPoint = map.latLngToLayerPoint(new LatLng(coords.lat, coords.lng)) // apply the new node locations @@ -247,7 +255,7 @@ export class GraphLayer extends Layer { }) // update the arc heights for the edges - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const style = edge.style style.height = getArcHeight(edge) }) diff --git a/demos/showcase/map/leaflet-graph-layer.ts b/demos/showcase/map/leaflet-graph-layer.ts index da2bef4cb..ccecb83c3 100644 --- a/demos/showcase/map/leaflet-graph-layer.ts +++ b/demos/showcase/map/leaflet-graph-layer.ts @@ -34,7 +34,7 @@ import { Layer, type LayerOptions, type LeafletEvent, - Map, + Map as LeafletMap, type MapOptions, TileLayer } from 'leaflet' @@ -57,7 +57,7 @@ export type GraphLayerOptions = { zoomChanged?: ZoomChanged } & LayerOptions -export type MapData = { graphLayer: GraphLayer; map: Map } +export type MapData = { graphLayer: GraphLayer; map: LeafletMap } /** * Creates a Leaflet map and adds a graph layer which contains a {@link GraphComponent}. @@ -75,7 +75,7 @@ export function createMap( const osmAttrib = 'Map data © OpenStreetMap contributors' // create the map - const worldMap = new Map(containerId, leafletOptions) + const worldMap = new LeafletMap(containerId, leafletOptions) worldMap.setView(new LatLng(15.538, 16.523), 3) worldMap.addLayer( new TileLayer(osmUrl, { @@ -147,7 +147,7 @@ export class GraphLayer extends Layer { /** * @yjs:keep = animate */ - onAdd(map: Map): this { + onAdd(map: LeafletMap): this { this.pane = map.getPane('overlayPane')! this.pane.appendChild(this.graphComponent.div) this.mapPane = map.getPane('mapPane') @@ -175,7 +175,7 @@ export class GraphLayer extends Layer { return this } - onRemove(map: Map): this { + onRemove(map: LeafletMap): this { map.off( 'zoom viewreset resize move moveend zoomend', this.updateGraphDivHandler.bind(this), @@ -194,14 +194,14 @@ export class GraphLayer extends Layer { * Listener for various {@link Map} events, see {@link onAdd} and {@link onRemove}. */ updateGraphDivHandler(evt: LeafletEvent): void { - this.updateGraphDiv(evt.target as Map) + this.updateGraphDiv(evt.target as LeafletMap) } /** * Synchronizes the viewport of the map and the {@link GraphComponent}. * @yjs:keep = getSize,setPosition,getPosition */ - updateGraphDiv(map: Map): void { + updateGraphDiv(map: LeafletMap): void { const graphComponent = this.graphComponent // get the size of the map in DOM coordinates const mapSize = map.getSize() @@ -236,10 +236,10 @@ export class GraphLayer extends Layer { * Calculates the coordinates of the nodes from their geolocations * and updates the arc heights of the edges. */ - private mapLayout(graphComponent: GraphComponent, map: Map): void { + private mapLayout(graphComponent: GraphComponent, map: LeafletMap): void { const graph = graphComponent.graph // transform geolocations and update the node locations - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const coords = this.getGeoCoordinates(node) const layerPoint = map.latLngToLayerPoint(new LatLng(coords.lat, coords.lng)) // apply the new node locations @@ -247,7 +247,7 @@ export class GraphLayer extends Layer { }) // update the arc heights for the edges - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const style = edge.style as ArcEdgeStyle style.height = getArcHeight(edge) }) diff --git a/demos/showcase/map/shortest-paths.js b/demos/showcase/map/shortest-paths.js index 984bd12b4..f3afd2918 100644 --- a/demos/showcase/map/shortest-paths.js +++ b/demos/showcase/map/shortest-paths.js @@ -45,7 +45,7 @@ let lastClickedNode /** * Registers listeners to graph changes and clicks to update the highlighted of the shortest paths. * @param {!GraphComponent} graphComponent - * @param {!Map} map + * @param {!LeafletMap} map */ export function initializeShortestPaths(graphComponent, map) { const inputMode = graphComponent.inputMode @@ -85,7 +85,7 @@ function initializeHighlights(graphComponent) { // use highlightDecorator for edges as we want to use different arc heights for individual edges graphComponent.graph.decorator.edgeDecorator.highlightDecorator.setFactory( - edge => + (edge) => new EdgeStyleDecorationInstaller({ edgeStyle: new ArcEdgeStyle({ stroke: '5px dashed #db3a34', @@ -99,7 +99,7 @@ function initializeHighlights(graphComponent) { * Highlights the shortest path between the current clickNode and the last clicked node. * @param {!INode} clickedNode * @param {!GraphComponent} graphComponent - * @param {!Map} map + * @param {!LeafletMap} map */ function updateShortestPathHighlight(clickedNode, graphComponent, map) { const highlightManager = graphComponent.highlightIndicatorManager @@ -111,7 +111,7 @@ function updateShortestPathHighlight(clickedNode, graphComponent, map) { const algorithm = new ShortestPath({ source: start, sink: clickedNode, - costs: edge => { + costs: (edge) => { // use the actual distance between the two airports as costs const { lat: sourceLat, lng: sourceLng } = getAirportData(edge.sourceNode) const { lat: targetLat, lng: targetLng } = getAirportData(edge.targetNode) @@ -124,7 +124,7 @@ function updateShortestPathHighlight(clickedNode, graphComponent, map) { highlightManager.clearHighlights() const result = algorithm.run(graph) - result.edges.forEach(edge => { + result.edges.forEach((edge) => { // highlight the edge, its source/target nodes and their associated labels highlightManager.addHighlight(edge) const sourceNode = edge.sourceNode @@ -154,7 +154,7 @@ export function updateHighlights(graphComponent) { const highlightManager = graphComponent.highlightIndicatorManager const highlightedItems = highlightManager.selectionModel.toArray() highlightManager.clearHighlights() - highlightedItems.forEach(item => { + highlightedItems.forEach((item) => { graphComponent.highlightIndicatorManager.addHighlight(item) }) } diff --git a/demos/showcase/map/shortest-paths.ts b/demos/showcase/map/shortest-paths.ts index e8c9e7d5b..5337b3cf7 100644 --- a/demos/showcase/map/shortest-paths.ts +++ b/demos/showcase/map/shortest-paths.ts @@ -40,7 +40,7 @@ import { ShortestPath } from 'yfiles' import { getArcHeight } from './map-styles' -import type { Map } from 'leaflet' +import type { Map as LeafletMap } from 'leaflet' import { LatLng } from 'leaflet' import { getAirportData } from './data-types' @@ -49,7 +49,7 @@ let lastClickedNode: INode | undefined /** * Registers listeners to graph changes and clicks to update the highlighted of the shortest paths. */ -export function initializeShortestPaths(graphComponent: GraphComponent, map: Map): void { +export function initializeShortestPaths(graphComponent: GraphComponent, map: LeafletMap): void { const inputMode = graphComponent.inputMode as GraphViewerInputMode inputMode.addItemClickedListener((_, evt) => { updateShortestPathHighlight(evt.item as INode, graphComponent, map) @@ -86,7 +86,7 @@ function initializeHighlights(graphComponent: GraphComponent): void { // use highlightDecorator for edges as we want to use different arc heights for individual edges graphComponent.graph.decorator.edgeDecorator.highlightDecorator.setFactory( - edge => + (edge) => new EdgeStyleDecorationInstaller({ edgeStyle: new ArcEdgeStyle({ stroke: '5px dashed #db3a34', @@ -102,7 +102,7 @@ function initializeHighlights(graphComponent: GraphComponent): void { function updateShortestPathHighlight( clickedNode: INode, graphComponent: GraphComponent, - map: Map + map: LeafletMap ): void { const highlightManager = graphComponent.highlightIndicatorManager const graph = graphComponent.graph @@ -126,7 +126,7 @@ function updateShortestPathHighlight( highlightManager.clearHighlights() const result = algorithm.run(graph) - result.edges.forEach(edge => { + result.edges.forEach((edge) => { // highlight the edge, its source/target nodes and their associated labels highlightManager.addHighlight(edge) const sourceNode = edge.sourceNode! @@ -155,7 +155,7 @@ export function updateHighlights(graphComponent: GraphComponent): void { const highlightManager = graphComponent.highlightIndicatorManager const highlightedItems = highlightManager.selectionModel!.toArray() highlightManager.clearHighlights() - highlightedItems.forEach(item => { + highlightedItems.forEach((item) => { graphComponent.highlightIndicatorManager.addHighlight(item) }) } diff --git a/demos/showcase/metaballgroups/MetaballGroupsDemo.js b/demos/showcase/metaballgroups/MetaballGroupsDemo.js index 144e65fe1..5d4160e09 100644 --- a/demos/showcase/metaballgroups/MetaballGroupsDemo.js +++ b/demos/showcase/metaballgroups/MetaballGroupsDemo.js @@ -81,7 +81,7 @@ async function run() { // add a blob visualization for the reddish group graphComponent.backgroundGroup.addChild( new BlobBackground( - n => { + (n) => { const color = n.style.fill.color return color == redColor || color == purpleColor }, @@ -94,7 +94,7 @@ async function run() { // add a blob visualization for the bluish group graphComponent.backgroundGroup.addChild( new BlobBackground( - n => { + (n) => { const color = n.style.fill.color return color == blueColor || color == purpleColor }, @@ -156,7 +156,7 @@ function createSampleGraph() { '47:54,49:51,50:51,51:53,21:12,10:2,0:37' ) .split(',') - .map(e => e.split(':').map(Number)) + .map((e) => e.split(':').map(Number)) const nodes = graph.nodes.toArray() for (const e of edges) { @@ -222,7 +222,7 @@ class BlobBackground extends BaseClass(IVisualCreator) { return new WebglBlobVisual( renderContext.canvasComponent.graph.nodes .filter(this.selector) - .map(n => n.layout.center.toPoint()), + .map((n) => n.layout.center.toPoint()), this.color, this.size ) diff --git a/demos/showcase/metaballgroups/MetaballGroupsDemo.ts b/demos/showcase/metaballgroups/MetaballGroupsDemo.ts index 6b0e2b5cc..bb74701d7 100644 --- a/demos/showcase/metaballgroups/MetaballGroupsDemo.ts +++ b/demos/showcase/metaballgroups/MetaballGroupsDemo.ts @@ -152,7 +152,7 @@ function createSampleGraph(): void { '47:54,49:51,50:51,51:53,21:12,10:2,0:37' ) .split(',') - .map(e => e.split(':').map(Number)) + .map((e) => e.split(':').map(Number)) const nodes = graph.nodes.toArray() for (const e of edges) { diff --git a/demos/showcase/metaballgroups/WebglBlobVisual.js b/demos/showcase/metaballgroups/WebglBlobVisual.js index 7f9a755dc..e0e5f6953 100644 --- a/demos/showcase/metaballgroups/WebglBlobVisual.js +++ b/demos/showcase/metaballgroups/WebglBlobVisual.js @@ -118,7 +118,7 @@ export default class WebglBlobVisual extends WebGLVisual { ) const ballSize = this.size - const centers = this.locations.map(p => renderContext.toViewCoordinates(p)) + const centers = this.locations.map((p) => renderContext.toViewCoordinates(p)) const dataToSend = this.dataToSend const maxDist = ballSize * 2 * renderContext.zoom @@ -129,7 +129,7 @@ export default class WebglBlobVisual extends WebGLVisual { const pixelRatio = renderContext.canvasComponent.devicePixelRatio let count = 0 - centers.forEach(center => { + centers.forEach((center) => { if ( center.x > -maxDist && center.y > -maxDist && diff --git a/demos/showcase/metaballgroups/WebglBlobVisual.ts b/demos/showcase/metaballgroups/WebglBlobVisual.ts index a99858f95..217a5f736 100644 --- a/demos/showcase/metaballgroups/WebglBlobVisual.ts +++ b/demos/showcase/metaballgroups/WebglBlobVisual.ts @@ -110,7 +110,7 @@ export default class WebglBlobVisual extends WebGLVisual { ) const ballSize = this.size - const centers = this.locations.map(p => renderContext.toViewCoordinates(p)) + const centers = this.locations.map((p) => renderContext.toViewCoordinates(p)) const dataToSend = this.dataToSend! const maxDist = ballSize * 2 * renderContext.zoom @@ -121,7 +121,7 @@ export default class WebglBlobVisual extends WebGLVisual { const pixelRatio = renderContext.canvasComponent!.devicePixelRatio let count = 0 - centers.forEach(center => { + centers.forEach((center) => { if ( center.x > -maxDist && center.y > -maxDist && diff --git a/demos/showcase/metaballgroups/index.html b/demos/showcase/metaballgroups/index.html index ca760d060..1a9c5c7e2 100644 --- a/demos/showcase/metaballgroups/index.html +++ b/demos/showcase/metaballgroups/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/mindmap/MindMapDemo.js b/demos/showcase/mindmap/MindMapDemo.js index 16490a27f..47461fea9 100644 --- a/demos/showcase/mindmap/MindMapDemo.js +++ b/demos/showcase/mindmap/MindMapDemo.js @@ -180,7 +180,7 @@ function initializeInputModes() { graphEditorInputMode.moveViewportInputMode.priority - 1 // make only the nodes and the cross-reference edges selectable - graphEditorInputMode.selectablePredicate = item => { + graphEditorInputMode.selectablePredicate = (item) => { if (item instanceof IEdge) { return isCrossReference(item) } @@ -238,11 +238,11 @@ async function buildGraph(graph) { // create the styles for the nodes and edges based on the elements' data updateStyles( - graph.nodes.find(node => graph.inDegree(node) === 0), + graph.nodes.find((node) => graph.inDegree(node) === 0), graph ) // calculate the bounds for each node based on its label's size - graph.nodes.forEach(node => adjustNodeBounds(node, graph)) + graph.nodes.forEach((node) => adjustNodeBounds(node, graph)) graphComponent.fitGraphBounds() // arrange the graph using a tree layout @@ -265,7 +265,7 @@ function initializeNodeData(graph) { '#' + color .substring(1) - .replace(/../g, colorValue => + .replace(/../g, (colorValue) => Math.min(255, Math.max(0, parseInt(colorValue, 16) + amount)).toString(16) ) ) @@ -276,7 +276,7 @@ function initializeNodeData(graph) { // i.e., the distance of a node from the root node. // Ignore the cross-reference edges, because they do not belong to the tree structure const treeAnalysis = new TreeAnalysis({ - subgraphEdges: e => !isCrossReference(e) + subgraphEdges: (e) => !isCrossReference(e) }) const analysisResult = treeAnalysis.run(graph) @@ -300,7 +300,7 @@ function initializeNodeData(graph) { nodeData.stateIcon = 0 // get the subtree of the node const subtreeNodes = analysisResult.getSubtree(node) - subtreeNodes.nodes.forEach(subtreeNode => { + subtreeNodes.nodes.forEach((subtreeNode) => { if (subtreeNode !== node) { const subtreeNodeData = getNodeData(subtreeNode) const depth = analysisResult.getDepth(subtreeNode) diff --git a/demos/showcase/mindmap/MindMapDemo.ts b/demos/showcase/mindmap/MindMapDemo.ts index 7c117388b..3a227b116 100644 --- a/demos/showcase/mindmap/MindMapDemo.ts +++ b/demos/showcase/mindmap/MindMapDemo.ts @@ -232,9 +232,9 @@ async function buildGraph(graph: IGraph): Promise { initializeNodeData(graph) // create the styles for the nodes and edges based on the elements' data - updateStyles(graph.nodes.find(node => graph.inDegree(node) === 0)!, graph) + updateStyles(graph.nodes.find((node) => graph.inDegree(node) === 0)!, graph) // calculate the bounds for each node based on its label's size - graph.nodes.forEach(node => adjustNodeBounds(node, graph)) + graph.nodes.forEach((node) => adjustNodeBounds(node, graph)) graphComponent.fitGraphBounds() // arrange the graph using a tree layout @@ -256,7 +256,7 @@ function initializeNodeData(graph: IGraph): void { '#' + color .substring(1) - .replace(/../g, colorValue => + .replace(/../g, (colorValue) => Math.min(255, Math.max(0, parseInt(colorValue, 16) + amount)).toString(16) ) ) @@ -267,7 +267,7 @@ function initializeNodeData(graph: IGraph): void { // i.e., the distance of a node from the root node. // Ignore the cross-reference edges, because they do not belong to the tree structure const treeAnalysis = new TreeAnalysis({ - subgraphEdges: e => !isCrossReference(e) + subgraphEdges: (e) => !isCrossReference(e) }) const analysisResult = treeAnalysis.run(graph) @@ -291,7 +291,7 @@ function initializeNodeData(graph: IGraph): void { nodeData.stateIcon = 0 // get the subtree of the node const subtreeNodes = analysisResult.getSubtree(node) - subtreeNodes.nodes.forEach(subtreeNode => { + subtreeNodes.nodes.forEach((subtreeNode) => { if (subtreeNode !== node) { const subtreeNodeData = getNodeData(subtreeNode) const depth = analysisResult.getDepth(subtreeNode) diff --git a/demos/showcase/mindmap/cross-references.js b/demos/showcase/mindmap/cross-references.js index 94759884a..45bbf0ebd 100644 --- a/demos/showcase/mindmap/cross-references.js +++ b/demos/showcase/mindmap/cross-references.js @@ -71,7 +71,7 @@ export function initializeCrossReferences(graphComponent) { // disable all edge handles except for height handle decorator.edgeDecorator.handleProviderDecorator.setFactory( - edge => new CrossReferenceEdgeHandleProvider(edge) + (edge) => new CrossReferenceEdgeHandleProvider(edge) ) const edgeDefaults = graph.edgeDefaults @@ -122,7 +122,7 @@ export function initializeCrossReferences(graphComponent) { // customize the port candidate provider // to ensure that cross-reference edges connect to the node center decorator.nodeDecorator.portCandidateProviderDecorator.setFactory( - node => + (node) => new (class extends PortCandidateProviderBase { /** * @param {!IInputModeContext} context diff --git a/demos/showcase/mindmap/cross-references.ts b/demos/showcase/mindmap/cross-references.ts index 0d519e235..0f3b8f4c6 100644 --- a/demos/showcase/mindmap/cross-references.ts +++ b/demos/showcase/mindmap/cross-references.ts @@ -131,7 +131,7 @@ export function initializeCrossReferences(graphComponent: GraphComponent): void // customize the port candidate provider // to ensure that cross-reference edges connect to the node center decorator.nodeDecorator.portCandidateProviderDecorator.setFactory( - node => + (node) => new (class extends PortCandidateProviderBase { getPortCandidates(context: IInputModeContext): IEnumerable { return List.fromArray([ diff --git a/demos/showcase/mindmap/index.html b/demos/showcase/mindmap/index.html index 5fce0d067..642aeefe3 100644 --- a/demos/showcase/mindmap/index.html +++ b/demos/showcase/mindmap/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/mindmap/interaction/MindMapPositionHandlers.js b/demos/showcase/mindmap/interaction/MindMapPositionHandlers.js index ca613920a..e61c861ed 100644 --- a/demos/showcase/mindmap/interaction/MindMapPositionHandlers.js +++ b/demos/showcase/mindmap/interaction/MindMapPositionHandlers.js @@ -195,7 +195,7 @@ export class SubtreePositionHandler extends BaseClass(IPositionHandler) { mirrorSubtree(left, graph) { if (isLeft(this.movedNode) !== left) { // set isLeft state - this.subtreeNodes.forEach(n => (getNodeData(n).left = !isLeft(n))) + this.subtreeNodes.forEach((n) => (getNodeData(n).left = !isLeft(n))) // calculate an automatic layout layoutSubtree(graph, this.movedNode, this.subtreeNodes, this.subtreeEdges) } @@ -208,15 +208,15 @@ export class SubtreePositionHandler extends BaseClass(IPositionHandler) { */ moveSubtree(delta, graph) { // move all subtree nodes - this.subtreeNodes.forEach(n => + this.subtreeNodes.forEach((n) => graph.setNodeLayout( n, new Rect(n.layout.x + delta.x, n.layout.y + delta.y, n.layout.width, n.layout.height) ) ) // move all bends of subtree edges - this.subtreeEdges.forEach(e => - e.bends.forEach(bend => + this.subtreeEdges.forEach((e) => + e.bends.forEach((bend) => graph.setBendLocation(bend, new Point(bend.location.x + delta.x, bend.location.y + delta.y)) ) ) @@ -260,7 +260,7 @@ export class SubtreePositionHandler extends BaseClass(IPositionHandler) { let dMin = Number.POSITIVE_INFINITY let newParent = null - graph.nodes.forEach(n => { + graph.nodes.forEach((n) => { if (!this.subtreeNodes.includes(n)) { let /** @type {Point} */ q @@ -305,7 +305,7 @@ export class SubtreePositionHandler extends BaseClass(IPositionHandler) { */ getBendLocations(edge) { const points = new List() - edge.bends.forEach(bend => points.add(bend.location.toPoint())) + edge.bends.forEach((bend) => points.add(bend.location.toPoint())) return points } } diff --git a/demos/showcase/mindmap/interaction/MindMapPositionHandlers.ts b/demos/showcase/mindmap/interaction/MindMapPositionHandlers.ts index ac685fa68..5012c18d5 100644 --- a/demos/showcase/mindmap/interaction/MindMapPositionHandlers.ts +++ b/demos/showcase/mindmap/interaction/MindMapPositionHandlers.ts @@ -203,7 +203,7 @@ export class SubtreePositionHandler mirrorSubtree(left: boolean, graph: IGraph): void { if (isLeft(this.movedNode) !== left) { // set isLeft state - this.subtreeNodes.forEach(n => (getNodeData(n).left = !isLeft(n))) + this.subtreeNodes.forEach((n) => (getNodeData(n).left = !isLeft(n))) // calculate an automatic layout layoutSubtree(graph, this.movedNode, this.subtreeNodes, this.subtreeEdges) } @@ -214,15 +214,15 @@ export class SubtreePositionHandler */ moveSubtree(delta: Point, graph: IGraph): void { // move all subtree nodes - this.subtreeNodes.forEach(n => + this.subtreeNodes.forEach((n) => graph.setNodeLayout( n, new Rect(n.layout.x + delta.x, n.layout.y + delta.y, n.layout.width, n.layout.height) ) ) // move all bends of subtree edges - this.subtreeEdges.forEach(e => - e.bends.forEach(bend => + this.subtreeEdges.forEach((e) => + e.bends.forEach((bend) => graph.setBendLocation(bend, new Point(bend.location.x + delta.x, bend.location.y + delta.y)) ) ) @@ -262,7 +262,7 @@ export class SubtreePositionHandler let dMin: number = Number.POSITIVE_INFINITY let newParent: INode | null = null - graph.nodes.forEach(n => { + graph.nodes.forEach((n) => { if (!this.subtreeNodes.includes(n)) { let /** @type {Point} */ q @@ -306,7 +306,7 @@ export class SubtreePositionHandler */ getBendLocations(edge: IEdge): List { const points = new List() - edge.bends.forEach(bend => points.add(bend.location.toPoint())) + edge.bends.forEach((bend) => points.add(bend.location.toPoint())) return points } } diff --git a/demos/showcase/mindmap/interaction/commands.js b/demos/showcase/mindmap/interaction/commands.js index 404156264..c89870cb7 100644 --- a/demos/showcase/mindmap/interaction/commands.js +++ b/demos/showcase/mindmap/interaction/commands.js @@ -60,7 +60,7 @@ export function initializeCommands(graphComponent) { // create a child node when INSERT is pressed keyboardInputMode.addKeyBinding({ key: Key.INSERT, - execute: _ => { + execute: (_) => { const currentItem = graphComponent.currentItem if (currentItem) { const depth = getDepth(currentItem) @@ -73,38 +73,38 @@ export function initializeCommands(graphComponent) { } return true }, - canExecute: _ => canExecuteCreateChild(graphComponent) + canExecute: (_) => canExecuteCreateChild(graphComponent) }) // remove a child node when DELETE is pressed keyboardInputMode.addKeyBinding({ key: Key.DELETE, - execute: _ => { + execute: (_) => { hidePopup(graphComponent) void executeDeleteItem(graphComponent) return true }, - canExecute: _ => canExecuteDeleteItem(graphComponent) + canExecute: (_) => canExecuteDeleteItem(graphComponent) }) // expand the subtree when ADD is pressed keyboardInputMode.addKeyBinding({ key: Key.ADD, - execute: _ => executeExpandNode(graphComponent), - canExecute: _ => canExecuteExpandNode(graphComponent) + execute: (_) => executeExpandNode(graphComponent), + canExecute: (_) => canExecuteExpandNode(graphComponent) }) // collapse the subtree when SUBTRACT is pressed keyboardInputMode.addKeyBinding({ key: Key.SUBTRACT, - execute: _ => executeCollapseNode(graphComponent), - canExecute: _ => canExecuteCollapseNode(graphComponent) + execute: (_) => executeCollapseNode(graphComponent), + canExecute: (_) => canExecuteCollapseNode(graphComponent) }) // create a sibling node when ENTER is pressed keyboardInputMode.addKeyBinding({ key: Key.ENTER, - execute: _ => { + execute: (_) => { const currentItem = graphComponent.currentItem if (currentItem) { const depth = getDepth(currentItem) @@ -117,7 +117,7 @@ export function initializeCommands(graphComponent) { } return true }, - canExecute: _ => canExecuteCreateSibling(graphComponent) + canExecute: (_) => canExecuteCreateSibling(graphComponent) }) } @@ -184,7 +184,7 @@ async function collapseNode(node, collapsed, graphComponent) { // collect subtree nodes to expand/collapse them with a nice animation let { nodes: subtreeNodes } = getSubtree(fullGraph, node) - subtreeNodes = subtreeNodes.filter(subtreeNode => subtreeNode !== node) + subtreeNodes = subtreeNodes.filter((subtreeNode) => subtreeNode !== node) // update the layout await layoutTree(graphComponent, subtreeNodes, collapsed) diff --git a/demos/showcase/mindmap/interaction/commands.ts b/demos/showcase/mindmap/interaction/commands.ts index 6ce48bbcf..d3a0fa5cb 100644 --- a/demos/showcase/mindmap/interaction/commands.ts +++ b/demos/showcase/mindmap/interaction/commands.ts @@ -68,7 +68,7 @@ export function initializeCommands(graphComponent: GraphComponent): void { // create a child node when INSERT is pressed keyboardInputMode.addKeyBinding({ key: Key.INSERT, - execute: _ => { + execute: (_) => { const currentItem = graphComponent.currentItem if (currentItem) { const depth = getDepth(currentItem as INode) @@ -81,38 +81,38 @@ export function initializeCommands(graphComponent: GraphComponent): void { } return true }, - canExecute: _ => canExecuteCreateChild(graphComponent) + canExecute: (_) => canExecuteCreateChild(graphComponent) }) // remove a child node when DELETE is pressed keyboardInputMode.addKeyBinding({ key: Key.DELETE, - execute: _ => { + execute: (_) => { hidePopup(graphComponent) void executeDeleteItem(graphComponent) return true }, - canExecute: _ => canExecuteDeleteItem(graphComponent) + canExecute: (_) => canExecuteDeleteItem(graphComponent) }) // expand the subtree when ADD is pressed keyboardInputMode.addKeyBinding({ key: Key.ADD, - execute: _ => executeExpandNode(graphComponent), - canExecute: _ => canExecuteExpandNode(graphComponent) + execute: (_) => executeExpandNode(graphComponent), + canExecute: (_) => canExecuteExpandNode(graphComponent) }) // collapse the subtree when SUBTRACT is pressed keyboardInputMode.addKeyBinding({ key: Key.SUBTRACT, - execute: _ => executeCollapseNode(graphComponent), - canExecute: _ => canExecuteCollapseNode(graphComponent) + execute: (_) => executeCollapseNode(graphComponent), + canExecute: (_) => canExecuteCollapseNode(graphComponent) }) // create a sibling node when ENTER is pressed keyboardInputMode.addKeyBinding({ key: Key.ENTER, - execute: _ => { + execute: (_) => { const currentItem = graphComponent.currentItem if (currentItem) { const depth = getDepth(currentItem as INode) @@ -125,7 +125,7 @@ export function initializeCommands(graphComponent: GraphComponent): void { } return true }, - canExecute: _ => canExecuteCreateSibling(graphComponent) + canExecute: (_) => canExecuteCreateSibling(graphComponent) }) } @@ -190,7 +190,7 @@ async function collapseNode( // collect subtree nodes to expand/collapse them with a nice animation let { nodes: subtreeNodes } = getSubtree(fullGraph, node) - subtreeNodes = subtreeNodes.filter(subtreeNode => subtreeNode !== node) + subtreeNodes = subtreeNodes.filter((subtreeNode) => subtreeNode !== node) // update the layout await layoutTree(graphComponent, subtreeNodes, collapsed) diff --git a/demos/showcase/mindmap/mind-map-layout.js b/demos/showcase/mindmap/mind-map-layout.js index 496835f96..76b083279 100644 --- a/demos/showcase/mindmap/mind-map-layout.js +++ b/demos/showcase/mindmap/mind-map-layout.js @@ -112,7 +112,7 @@ function createLayoutData() { // tells the DelegatingNodePlacer which side a node is on delegatingNodePlacerPrimaryNodes: isLeft, // tells the layout which node placer to use for a node - nodePlacers: node => { + nodePlacers: (node) => { if (isRoot(node)) { return placerRoot } @@ -122,7 +122,7 @@ function createLayoutData() { return placerRight }, // tells the layout how to sort the children of specific nodes - outEdgeComparers: _ => { + outEdgeComparers: (_) => { return (edge1, edge2) => { if (edge1 === edge2) { return 0 @@ -137,10 +137,10 @@ function createLayoutData() { } }, // tells the layout which side to place a source port on - sourcePortConstraints: edge => + sourcePortConstraints: (edge) => PortConstraint.create(isLeft(edge.targetNode) ? PortSide.WEST : PortSide.EAST, true), // tells the layout which side to place a target port on - targetPortConstraints: edge => + targetPortConstraints: (edge) => PortConstraint.create(isLeft(edge.targetNode) ? PortSide.EAST : PortSide.WEST, true) // a layout stage that hides cross-reference edges from the layout }).combineWith( diff --git a/demos/showcase/mindmap/mind-map-layout.ts b/demos/showcase/mindmap/mind-map-layout.ts index 8cbdf4941..c7888e86d 100644 --- a/demos/showcase/mindmap/mind-map-layout.ts +++ b/demos/showcase/mindmap/mind-map-layout.ts @@ -138,10 +138,10 @@ function createLayoutData(): LayoutData { } }, // tells the layout which side to place a source port on - sourcePortConstraints: edge => + sourcePortConstraints: (edge) => PortConstraint.create(isLeft(edge.targetNode!) ? PortSide.WEST : PortSide.EAST, true), // tells the layout which side to place a target port on - targetPortConstraints: edge => + targetPortConstraints: (edge) => PortConstraint.create(isLeft(edge.targetNode!) ? PortSide.EAST : PortSide.WEST, true) // a layout stage that hides cross-reference edges from the layout }).combineWith( diff --git a/demos/showcase/mindmap/node-popup-toolbar.js b/demos/showcase/mindmap/node-popup-toolbar.js index c49f96e1a..65ba30ebe 100644 --- a/demos/showcase/mindmap/node-popup-toolbar.js +++ b/demos/showcase/mindmap/node-popup-toolbar.js @@ -81,7 +81,7 @@ export function initializeNodePopups(graphComponent) { showToolbar(evt.item) }) - inputMode.moveInputMode.addDragStartedListener(_ => { + inputMode.moveInputMode.addDragStartedListener((_) => { hidePopup(graphComponent) }) @@ -113,14 +113,14 @@ function createNodePopup(graphComponent) { document .getElementById('state-icon-picker') - .addEventListener('click', evt => showPickerContainer(graphComponent, evt.target)) + .addEventListener('click', (evt) => showPickerContainer(graphComponent, evt.target)) document .getElementById('color-picker') - .addEventListener('click', evt => showPickerContainer(graphComponent, evt.target)) + .addEventListener('click', (evt) => showPickerContainer(graphComponent, evt.target)) document.getElementById('cross-edge-creation').addEventListener( 'click', - async evt => { + async (evt) => { const currentItem = nodePopup.currentItem hidePopup(graphComponent) if (!isInLayout() && currentItem) { @@ -133,7 +133,7 @@ function createNodePopup(graphComponent) { document.getElementById('child-creation').addEventListener( 'click', - async evt => { + async (evt) => { const currentItem = nodePopup.currentItem hidePopup(graphComponent) if (currentItem) { @@ -151,7 +151,7 @@ function createNodePopup(graphComponent) { ) document.getElementById('node-removal').addEventListener( 'click', - async evt => { + async (evt) => { hidePopup(graphComponent) await executeDeleteItem(graphComponent) evt.target.checked = false @@ -273,7 +273,7 @@ function createColorPicker(graphComponent) { ] const colorContainer = document.querySelector('#color-picker-colors') - colorPickerColors.forEach(color => { + colorPickerColors.forEach((color) => { const colorButton = document.createElement('button') colorButton.setAttribute('data-color', color) colorButton.setAttribute('style', `background-color:${color}`) diff --git a/demos/showcase/mindmap/node-popup-toolbar.ts b/demos/showcase/mindmap/node-popup-toolbar.ts index 983798dd7..b42f49441 100644 --- a/demos/showcase/mindmap/node-popup-toolbar.ts +++ b/demos/showcase/mindmap/node-popup-toolbar.ts @@ -81,7 +81,7 @@ export function initializeNodePopups(graphComponent: GraphComponent): void { showToolbar(evt.item) }) - inputMode.moveInputMode.addDragStartedListener(_ => { + inputMode.moveInputMode.addDragStartedListener((_) => { hidePopup(graphComponent) }) @@ -111,18 +111,18 @@ function createNodePopup(graphComponent: GraphComponent): void { document .getElementById('state-icon-picker')! - .addEventListener('click', evt => + .addEventListener('click', (evt) => showPickerContainer(graphComponent, evt.target as HTMLInputElement) ) document .getElementById('color-picker')! - .addEventListener('click', evt => + .addEventListener('click', (evt) => showPickerContainer(graphComponent, evt.target as HTMLInputElement) ) document.getElementById('cross-edge-creation')!.addEventListener( 'click', - async evt => { + async (evt) => { const currentItem = nodePopup.currentItem hidePopup(graphComponent) if (!isInLayout() && currentItem) { @@ -135,7 +135,7 @@ function createNodePopup(graphComponent: GraphComponent): void { document.getElementById('child-creation')!.addEventListener( 'click', - async evt => { + async (evt) => { const currentItem = nodePopup.currentItem hidePopup(graphComponent) if (currentItem) { @@ -153,7 +153,7 @@ function createNodePopup(graphComponent: GraphComponent): void { ) document.getElementById('node-removal')!.addEventListener( 'click', - async evt => { + async (evt) => { hidePopup(graphComponent) await executeDeleteItem(graphComponent) ;(evt.target as HTMLInputElement).checked = false @@ -278,7 +278,7 @@ function createColorPicker(graphComponent: GraphComponent): void { ] const colorContainer = document.querySelector('#color-picker-colors')! - colorPickerColors.forEach(color => { + colorPickerColors.forEach((color) => { const colorButton = document.createElement('button') colorButton.setAttribute('data-color', color) colorButton.setAttribute('style', `background-color:${color}`) diff --git a/demos/showcase/mindmap/resources/mindmapstyle.css b/demos/showcase/mindmap/resources/mindmapstyle.css index 49dd65d86..edb45703b 100644 --- a/demos/showcase/mindmap/resources/mindmapstyle.css +++ b/demos/showcase/mindmap/resources/mindmapstyle.css @@ -79,8 +79,12 @@ box-sizing: border-box; user-select: none; background-color: #f7f7f7; - box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.16), 0 2px 5px 0 rgba(0, 0, 0, 0.26); - transition: opacity 0.2s ease-out, width 0.2s ease-out; + box-shadow: + 0 2px 10px 0 rgba(0, 0, 0, 0.16), + 0 2px 5px 0 rgba(0, 0, 0, 0.26); + transition: + opacity 0.2s ease-out, + width 0.2s ease-out; } .contextual-toolbar button, @@ -141,7 +145,9 @@ .picker-container { position: absolute; background-color: #f7f7f7; - box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.16), 0 2px 5px 0 rgba(0, 0, 0, 0.26); + box-shadow: + 0 2px 10px 0 rgba(0, 0, 0, 0.16), + 0 2px 5px 0 rgba(0, 0, 0, 0.26); opacity: 0; display: none; transition: opacity 0.2s ease-out; diff --git a/demos/showcase/mindmap/styles/CollapseDecorator.js b/demos/showcase/mindmap/styles/CollapseDecorator.js index 08333bea6..eb06907ca 100644 --- a/demos/showcase/mindmap/styles/CollapseDecorator.js +++ b/demos/showcase/mindmap/styles/CollapseDecorator.js @@ -196,7 +196,7 @@ export class CollapseDecorator extends NodeStyleBase { // register the click and touch listeners for the collapse/expand operation g.addEventListener( 'click', - _ => { + (_) => { if (canExecuteToggleCollapseState(graphComponent, node)) { executeToggleCollapseState(graphComponent, node) } @@ -205,7 +205,7 @@ export class CollapseDecorator extends NodeStyleBase { ) g.addEventListener( 'touchstart', - evt => { + (evt) => { // prevent subsequent firing of a click event evt.preventDefault() if (canExecuteToggleCollapseState(graphComponent, node)) { diff --git a/demos/showcase/mindmap/styles/CollapseDecorator.ts b/demos/showcase/mindmap/styles/CollapseDecorator.ts index f4599de0c..99a22f647 100644 --- a/demos/showcase/mindmap/styles/CollapseDecorator.ts +++ b/demos/showcase/mindmap/styles/CollapseDecorator.ts @@ -197,7 +197,7 @@ export class CollapseDecorator extends NodeStyleBase { // register the click and touch listeners for the collapse/expand operation g.addEventListener( 'click', - _ => { + (_) => { if (canExecuteToggleCollapseState(graphComponent, node)) { executeToggleCollapseState(graphComponent, node) } @@ -206,7 +206,7 @@ export class CollapseDecorator extends NodeStyleBase { ) g.addEventListener( 'touchstart', - evt => { + (evt) => { // prevent subsequent firing of a click event evt.preventDefault() if (canExecuteToggleCollapseState(graphComponent, node)) { diff --git a/demos/showcase/mindmap/styles/MindMapEdgeStyle.ts b/demos/showcase/mindmap/styles/MindMapEdgeStyle.ts index 96a144b2b..8a6006b11 100644 --- a/demos/showcase/mindmap/styles/MindMapEdgeStyle.ts +++ b/demos/showcase/mindmap/styles/MindMapEdgeStyle.ts @@ -48,7 +48,10 @@ export class MindMapEdgeStyle extends EdgeStyleBase { * @param thicknessStart The thickness of the edge at its start. * @param thicknessEnd The thickness of the edge at its end. */ - constructor(public thicknessStart: number, public thicknessEnd: number) { + constructor( + public thicknessStart: number, + public thicknessEnd: number + ) { super() } @@ -108,7 +111,11 @@ class MindMapCanvasVisual extends HtmlCanvasVisual { * @param thicknessStart The thickness of the edge at its start. * @param thicknessEnd The thickness of the edge at its end. */ - constructor(public edge: IEdge, public thicknessStart: number, public thicknessEnd: number) { + constructor( + public edge: IEdge, + public thicknessStart: number, + public thicknessEnd: number + ) { super() this.edge = edge this.thicknessStart = thicknessStart diff --git a/demos/showcase/mindmap/styles/styles-support.js b/demos/showcase/mindmap/styles/styles-support.js index 86d2902da..f997c216d 100644 --- a/demos/showcase/mindmap/styles/styles-support.js +++ b/demos/showcase/mindmap/styles/styles-support.js @@ -93,7 +93,7 @@ export function initializeStyles() { export function updateStyles(subtreeRoot, fullGraph) { const { nodes: subtreeNodes, edges: subtreeEdges } = getSubtree(fullGraph, subtreeRoot) - subtreeNodes.forEach(node => { + subtreeNodes.forEach((node) => { const depth = getDepth(node) const label = node.labels.first() const nodeStyle = getNodeStyle(depth) @@ -102,7 +102,7 @@ export function updateStyles(subtreeRoot, fullGraph) { fullGraph.setStyle(label, labelStyle) }) - subtreeEdges.forEach(edge => { + subtreeEdges.forEach((edge) => { const depth = getDepth(edge.sourceNode) const edgeStyle = getEdgeStyle(depth) fullGraph.setStyle(edge, edgeStyle) diff --git a/demos/showcase/mindmap/styles/styles-support.ts b/demos/showcase/mindmap/styles/styles-support.ts index fd811bd89..9f43b29af 100644 --- a/demos/showcase/mindmap/styles/styles-support.ts +++ b/demos/showcase/mindmap/styles/styles-support.ts @@ -98,7 +98,7 @@ export function initializeStyles(): void { export function updateStyles(subtreeRoot: INode, fullGraph: IGraph): void { const { nodes: subtreeNodes, edges: subtreeEdges } = getSubtree(fullGraph, subtreeRoot) - subtreeNodes.forEach(node => { + subtreeNodes.forEach((node) => { const depth = getDepth(node) const label = node.labels.first() const nodeStyle = getNodeStyle(depth) @@ -107,7 +107,7 @@ export function updateStyles(subtreeRoot: INode, fullGraph: IGraph): void { fullGraph.setStyle(label, labelStyle) }) - subtreeEdges.forEach(edge => { + subtreeEdges.forEach((edge) => { const depth = getDepth(edge.sourceNode!) const edgeStyle = getEdgeStyle(depth) fullGraph.setStyle(edge, edgeStyle) diff --git a/demos/showcase/mindmap/subtrees.js b/demos/showcase/mindmap/subtrees.js index 265141d15..f69a5cd0c 100644 --- a/demos/showcase/mindmap/subtrees.js +++ b/demos/showcase/mindmap/subtrees.js @@ -48,10 +48,10 @@ import { SubtreePositionHandler } from './interaction/MindMapPositionHandlers.js export function initializeSubtrees(graphComponent) { const inputMode = graphComponent.inputMode // register handlers for dragging and relocating subtrees - inputMode.moveInputMode.addDragStartedListener(_ => prepareRelocateSubtree(graphComponent)) - inputMode.moveInputMode.addDraggedListener(_ => updateSubtreeStylesAndLayout(graphComponent)) - inputMode.moveInputMode.addDragCanceledListener(_ => resetSubtree(graphComponent)) - inputMode.moveInputMode.addDragFinishedListener(_ => relocateSubtree(graphComponent)) + inputMode.moveInputMode.addDragStartedListener((_) => prepareRelocateSubtree(graphComponent)) + inputMode.moveInputMode.addDraggedListener((_) => updateSubtreeStylesAndLayout(graphComponent)) + inputMode.moveInputMode.addDragCanceledListener((_) => resetSubtree(graphComponent)) + inputMode.moveInputMode.addDragFinishedListener((_) => relocateSubtree(graphComponent)) // customize the position handler to move a whole subtree and update the styles and layout const filteredGraph = graphComponent.graph @@ -150,8 +150,8 @@ export async function relocateSubtree(graphComponent) { oldNodeData, newNodeData, movedNode, - node => - getSubtree(fullGraph, node).nodes.forEach(n => { + (node) => + getSubtree(fullGraph, node).nodes.forEach((n) => { const nData = getNodeData(n) nData.left = isLeft(node) }) @@ -169,7 +169,7 @@ export async function relocateSubtree(graphComponent) { oldNodeData, newTagData, movedNode, - node => filteredGraph.nodePredicateChanged(node) + (node) => filteredGraph.nodePredicateChanged(node) ) ) @@ -237,7 +237,7 @@ export function collapseSubtree(node, collapsed, filteredGraph) { */ export function getRoot(graph) { // find the first node with no incoming mind map edges - return graph.nodes.find(node => !getInEdge(node, graph)) + return graph.nodes.find((node) => !getInEdge(node, graph)) } /** @@ -248,7 +248,7 @@ export function getRoot(graph) { */ export function getSubtree(graph, subtreeRoot) { const treeAnalysis = new TreeAnalysis({ - subgraphEdges: e => !isCrossReference(e) + subgraphEdges: (e) => !isCrossReference(e) }) const analysisResult = treeAnalysis.run(graph) const subtree = analysisResult.getSubtree(subtreeRoot) @@ -262,7 +262,7 @@ export function getSubtree(graph, subtreeRoot) { * @returns {?IEdge} */ export function getInEdge(node, graph) { - return graph.inEdgesAt(node).find(edge => !isCrossReference(edge)) + return graph.inEdgesAt(node).find((edge) => !isCrossReference(edge)) } /** @@ -310,7 +310,7 @@ export function createChild(graph, parent, nodeStyle, edgeStyle, labelStyle) { if (isRoot(parent)) { // get all edges starting at root and count left or right let balance = 0 - graph.outEdgesAt(parent).forEach(edge => { + graph.outEdgesAt(parent).forEach((edge) => { if (!isCrossReference(edge)) { balance += isLeft(edge.targetNode) ? -1 : 1 } @@ -342,7 +342,7 @@ export function removeSubtree(graph, subtreeRoot) { while (nodesToCheck.length > 0) { const node = nodesToCheck.pop() - for (const outEdge of graph.outEdgesAt(node).filter(edge => !isCrossReference(edge))) { + for (const outEdge of graph.outEdgesAt(node).filter((edge) => !isCrossReference(edge))) { nodesToCheck.push(outEdge.targetNode) } graph.remove(node) @@ -356,7 +356,7 @@ export function removeSubtree(graph, subtreeRoot) { * @param {number} depth The given depth. */ export function setSubtreeDepths(graph, node, depth) { - graph.outEdgesAt(node).forEach(edge => { + graph.outEdgesAt(node).forEach((edge) => { if (!isCrossReference(edge)) { setSubtreeDepths(graph, edge.targetNode, depth + 1) } @@ -372,7 +372,7 @@ export function setSubtreeDepths(graph, node, depth) { * @returns {boolean} True if a node ahs children, false otherwise */ export function hasChildNodes(node, graph) { - return graph.outEdgesAt(node).filter(edge => !isCrossReference(edge)).size > 0 + return graph.outEdgesAt(node).filter((edge) => !isCrossReference(edge)).size > 0 } /** diff --git a/demos/showcase/mindmap/subtrees.ts b/demos/showcase/mindmap/subtrees.ts index f06f32ab8..50f3d813e 100644 --- a/demos/showcase/mindmap/subtrees.ts +++ b/demos/showcase/mindmap/subtrees.ts @@ -61,10 +61,10 @@ import { SubtreePositionHandler } from './interaction/MindMapPositionHandlers' export function initializeSubtrees(graphComponent: GraphComponent): void { const inputMode = graphComponent.inputMode as GraphEditorInputMode // register handlers for dragging and relocating subtrees - inputMode.moveInputMode.addDragStartedListener(_ => prepareRelocateSubtree(graphComponent)) - inputMode.moveInputMode.addDraggedListener(_ => updateSubtreeStylesAndLayout(graphComponent)) - inputMode.moveInputMode.addDragCanceledListener(_ => resetSubtree(graphComponent)) - inputMode.moveInputMode.addDragFinishedListener(_ => relocateSubtree(graphComponent)) + inputMode.moveInputMode.addDragStartedListener((_) => prepareRelocateSubtree(graphComponent)) + inputMode.moveInputMode.addDraggedListener((_) => updateSubtreeStylesAndLayout(graphComponent)) + inputMode.moveInputMode.addDragCanceledListener((_) => resetSubtree(graphComponent)) + inputMode.moveInputMode.addDragFinishedListener((_) => relocateSubtree(graphComponent)) // customize the position handler to move a whole subtree and update the styles and layout const filteredGraph = graphComponent.graph as FilteredGraphWrapper @@ -157,7 +157,7 @@ export async function relocateSubtree(graphComponent: GraphComponent): Promise - getSubtree(fullGraph, node).nodes.forEach(n => { + getSubtree(fullGraph, node).nodes.forEach((n) => { const nData = getNodeData(n) nData.left = isLeft(node) }) @@ -241,7 +241,7 @@ export function collapseSubtree( */ export function getRoot(graph: IGraph): INode { // find the first node with no incoming mind map edges - return graph.nodes.find(node => !getInEdge(node, graph))! + return graph.nodes.find((node) => !getInEdge(node, graph))! } /** @@ -249,7 +249,7 @@ export function getRoot(graph: IGraph): INode { */ export function getSubtree(graph: IGraph, subtreeRoot: INode): { nodes: INode[]; edges: IEdge[] } { const treeAnalysis = new TreeAnalysis({ - subgraphEdges: e => !isCrossReference(e) + subgraphEdges: (e) => !isCrossReference(e) }) const analysisResult = treeAnalysis.run(graph) const subtree = analysisResult.getSubtree(subtreeRoot) @@ -260,7 +260,7 @@ export function getSubtree(graph: IGraph, subtreeRoot: INode): { nodes: INode[]; * Gets the first incoming edge that's not a cross-reference or null. */ export function getInEdge(node: INode, graph: IGraph): IEdge | null { - return graph.inEdgesAt(node).find(edge => !isCrossReference(edge)) + return graph.inEdgesAt(node).find((edge) => !isCrossReference(edge)) } /** @@ -320,7 +320,7 @@ export function createChild( if (isRoot(parent)) { // get all edges starting at root and count left or right let balance = 0 - graph.outEdgesAt(parent).forEach(edge => { + graph.outEdgesAt(parent).forEach((edge) => { if (!isCrossReference(edge)) { balance += isLeft(edge.targetNode!) ? -1 : 1 } @@ -352,7 +352,7 @@ export function removeSubtree(graph: IGraph, subtreeRoot: INode): void { while (nodesToCheck.length > 0) { const node = nodesToCheck.pop()! - for (const outEdge of graph.outEdgesAt(node).filter(edge => !isCrossReference(edge))) { + for (const outEdge of graph.outEdgesAt(node).filter((edge) => !isCrossReference(edge))) { nodesToCheck.push(outEdge.targetNode!) } graph.remove(node) @@ -366,7 +366,7 @@ export function removeSubtree(graph: IGraph, subtreeRoot: INode): void { * @param depth The given depth. */ export function setSubtreeDepths(graph: IGraph, node: INode, depth: number): void { - graph.outEdgesAt(node).forEach(edge => { + graph.outEdgesAt(node).forEach((edge) => { if (!isCrossReference(edge)) { setSubtreeDepths(graph, edge.targetNode!, depth + 1) } @@ -382,7 +382,7 @@ export function setSubtreeDepths(graph: IGraph, node: INode, depth: number): voi * @returns True if a node ahs children, false otherwise */ export function hasChildNodes(node: INode, graph: IGraph): boolean { - return graph.outEdgesAt(node).filter(edge => !isCrossReference(edge)).size > 0 + return graph.outEdgesAt(node).filter((edge) => !isCrossReference(edge)).size > 0 } /** diff --git a/demos/showcase/neighborhood-circles/NeighborhoodCirclesDemo.js b/demos/showcase/neighborhood-circles/NeighborhoodCirclesDemo.js index 76e5fa729..1e80528ff 100644 --- a/demos/showcase/neighborhood-circles/NeighborhoodCirclesDemo.js +++ b/demos/showcase/neighborhood-circles/NeighborhoodCirclesDemo.js @@ -119,14 +119,14 @@ function buildGraph(graph, graphData) { const nodesSource = graphBuilder.createNodesSource({ data: graphData.nodeList, - id: item => item.id, - parentId: item => item.parentId + id: (item) => item.id, + parentId: (item) => item.parentId }) - nodesSource.nodeCreator.styleProvider = item => + nodesSource.nodeCreator.styleProvider = (item) => new ImageNodeStyle({ image: `./resources/${item.tag}.svg` }) - nodesSource.nodeCreator.createLabelBinding(item => item.label) + nodesSource.nodeCreator.createLabelBinding((item) => item.label) nodesSource.nodeCreator.defaults.size = new Size(48, 48) nodesSource.nodeCreator.defaults.labels.style = new DefaultLabelStyle({ @@ -145,8 +145,8 @@ function buildGraph(graph, graphData) { graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -167,11 +167,11 @@ function createNeighborhoodView(graphComponent) { neighborhoodView.buildNeighborhoodGraph = getBuildGraphCallback(NeighborhoodType.NEIGHBORHOOD, 1) neighborhoodView.graphComponent = graphComponent // mirror navigation in the neighborhood view to the demo's main GraphComponent - neighborhoodView.clickCallback = node => { + neighborhoodView.clickCallback = (node) => { graphComponent.selection.clear() graphComponent.selection.setSelected(node, true) } - neighborhoodView.onNeighborhoodUpdated = view => { + neighborhoodView.onNeighborhoodUpdated = (view) => { // show the circles on which the neighborhood nodes have been arranged (unless the neighborhood // graph is empty) const isEmpty = view.neighborhoodGraph.nodes.size < 1 diff --git a/demos/showcase/neighborhood-circles/NeighborhoodCirclesDemo.ts b/demos/showcase/neighborhood-circles/NeighborhoodCirclesDemo.ts index 65deccb8b..6d5ebe5d2 100644 --- a/demos/showcase/neighborhood-circles/NeighborhoodCirclesDemo.ts +++ b/demos/showcase/neighborhood-circles/NeighborhoodCirclesDemo.ts @@ -120,14 +120,14 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const nodesSource = graphBuilder.createNodesSource({ data: graphData.nodeList, - id: item => item.id, - parentId: item => item.parentId + id: (item) => item.id, + parentId: (item) => item.parentId }) nodesSource.nodeCreator.styleProvider = (item): INodeStyle => new ImageNodeStyle({ image: `./resources/${item.tag}.svg` }) - nodesSource.nodeCreator.createLabelBinding(item => item.label) + nodesSource.nodeCreator.createLabelBinding((item) => item.label) nodesSource.nodeCreator.defaults.size = new Size(48, 48) nodesSource.nodeCreator.defaults.labels.style = new DefaultLabelStyle({ @@ -146,8 +146,8 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() diff --git a/demos/showcase/neighborhood-circles/index.html b/demos/showcase/neighborhood-circles/index.html index 5abdf10d5..1dc7f2af9 100644 --- a/demos/showcase/neighborhood-circles/index.html +++ b/demos/showcase/neighborhood-circles/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/neighborhood/NeighborhoodDemo.js b/demos/showcase/neighborhood/NeighborhoodDemo.js index a6fbda044..9eabf8dec 100644 --- a/demos/showcase/neighborhood/NeighborhoodDemo.js +++ b/demos/showcase/neighborhood/NeighborhoodDemo.js @@ -239,7 +239,7 @@ function configureNeighborhoodView(neighborhoodView, type, distance) { // mirror navigation in the NeighborhoodView to the demo's main GraphComponent neighborhoodView.clickCallback = NeighborhoodType.FOLDER_CONTENTS === type - ? node => { + ? (node) => { const foldingView = graphComponent.graph.foldingView if (foldingView.manager.masterGraph.contains(node)) { const viewNode = foldingView.getViewItem(node) @@ -249,7 +249,7 @@ function configureNeighborhoodView(neighborhoodView, type, distance) { } } } - : node => { + : (node) => { graphComponent.selection.clear() graphComponent.selection.setSelected(node, true) } diff --git a/demos/showcase/neighborhood/NeighborhoodDemo.ts b/demos/showcase/neighborhood/NeighborhoodDemo.ts index 7569f2345..5fe85c93d 100644 --- a/demos/showcase/neighborhood/NeighborhoodDemo.ts +++ b/demos/showcase/neighborhood/NeighborhoodDemo.ts @@ -226,7 +226,7 @@ function configureNeighborhoodView( // mirror navigation in the NeighborhoodView to the demo's main GraphComponent neighborhoodView.clickCallback = NeighborhoodType.FOLDER_CONTENTS === type - ? node => { + ? (node) => { const foldingView = graphComponent.graph.foldingView! if (foldingView.manager.masterGraph.contains(node)) { const viewNode = foldingView.getViewItem(node) @@ -236,7 +236,7 @@ function configureNeighborhoodView( } } } - : node => { + : (node) => { graphComponent.selection.clear() graphComponent.selection.setSelected(node, true) } diff --git a/demos/showcase/neighborhood/NeighborhoodView.js b/demos/showcase/neighborhood/NeighborhoodView.js index a548a23f0..e00067e2c 100644 --- a/demos/showcase/neighborhood/NeighborhoodView.js +++ b/demos/showcase/neighborhood/NeighborhoodView.js @@ -96,7 +96,7 @@ export class NeighborhoodView { /** * Invoked after the neighborhood graph has been updated. */ - onNeighborhoodUpdated = view => { + onNeighborhoodUpdated = (view) => { // Ensure the neighborhood graph fits inside the neighborhood graph component. view.neighborhoodComponent.fitGraphBounds(new Insets(5)) } @@ -523,7 +523,7 @@ export class NeighborhoodView { if (this.showHighlight && copiedStartNodes.length > 0) { const manager = this.neighborhoodComponent.highlightIndicatorManager manager.clearHighlights() - copiedStartNodes.forEach(startNode => { + copiedStartNodes.forEach((startNode) => { manager.addHighlight(startNode) }) } diff --git a/demos/showcase/neighborhood/NeighborhoodView.ts b/demos/showcase/neighborhood/NeighborhoodView.ts index cba38067b..0ff427f8e 100644 --- a/demos/showcase/neighborhood/NeighborhoodView.ts +++ b/demos/showcase/neighborhood/NeighborhoodView.ts @@ -100,7 +100,7 @@ export class NeighborhoodView { /** * Invoked after the neighborhood graph has been updated. */ - onNeighborhoodUpdated: (view: NeighborhoodView) => void = view => { + onNeighborhoodUpdated: (view: NeighborhoodView) => void = (view) => { // Ensure the neighborhood graph fits inside the neighborhood graph component. view.neighborhoodComponent.fitGraphBounds(new Insets(5)) } @@ -517,7 +517,7 @@ export class NeighborhoodView { if (this.showHighlight && copiedStartNodes.length > 0) { const manager = this.neighborhoodComponent.highlightIndicatorManager manager.clearHighlights() - copiedStartNodes.forEach(startNode => { + copiedStartNodes.forEach((startNode) => { manager.addHighlight(startNode) }) } diff --git a/demos/showcase/neighborhood/apply-layout-callback.js b/demos/showcase/neighborhood/apply-layout-callback.js index 7c3a6b2d6..f5fecb095 100644 --- a/demos/showcase/neighborhood/apply-layout-callback.js +++ b/demos/showcase/neighborhood/apply-layout-callback.js @@ -37,7 +37,7 @@ import { NeighborhoodType } from './NeighborhoodType.js' export function getApplyLayoutCallback(neighborhoodType) { return neighborhoodType === NeighborhoodType.FOLDER_CONTENTS ? (view, nodes) => applyComponentLayout(view, nodes) - : view => applyHierarchicLayout(view) + : (view) => applyHierarchicLayout(view) } /** diff --git a/demos/showcase/neighborhood/apply-layout-callback.ts b/demos/showcase/neighborhood/apply-layout-callback.ts index 8bfe6ffa4..16cae7dc8 100644 --- a/demos/showcase/neighborhood/apply-layout-callback.ts +++ b/demos/showcase/neighborhood/apply-layout-callback.ts @@ -37,7 +37,7 @@ import type { ApplyLayoutCallback, NeighborhoodView } from './NeighborhoodView' export function getApplyLayoutCallback(neighborhoodType: NeighborhoodType): ApplyLayoutCallback { return neighborhoodType === NeighborhoodType.FOLDER_CONTENTS ? (view, nodes) => applyComponentLayout(view, nodes) - : view => applyHierarchicLayout(view) + : (view) => applyHierarchicLayout(view) } function applyComponentLayout(view: NeighborhoodView, selectedViewNodes: INode[]): void { diff --git a/demos/showcase/neighborhood/build-graph-callback.js b/demos/showcase/neighborhood/build-graph-callback.js index 32d83116d..6a85bdbcb 100644 --- a/demos/showcase/neighborhood/build-graph-callback.js +++ b/demos/showcase/neighborhood/build-graph-callback.js @@ -80,8 +80,8 @@ function buildFolderContents(view, selectedSourceNodes, elementCopiedCallback) { // Get descendants of root nodes. const masterGraph = foldingView.manager.masterGraph const groupingSupport = masterGraph.groupingSupport - selectedSourceNodes.forEach(node => { - groupingSupport.getDescendants(foldingView.getMasterItem(node)).forEach(descendant => { + selectedSourceNodes.forEach((node) => { + groupingSupport.getDescendants(foldingView.getMasterItem(node)).forEach((descendant) => { nodesToCopy.add(descendant) }) }) @@ -92,11 +92,11 @@ function buildFolderContents(view, selectedSourceNodes, elementCopiedCallback) { graphCopier.copy({ sourceGraph: masterGraph, targetGraph: view.neighborhoodGraph, - filter: item => { + filter: (item) => { if (item instanceof IEdge) { // filter intra-component edges return !!selectedSourceNodes.find( - node => + (node) => groupingSupport.isDescendant(item.sourceNode, foldingView.getMasterItem(node)) && groupingSupport.isDescendant(item.targetNode, foldingView.getMasterItem(node)) ) @@ -144,7 +144,7 @@ function buildNeighborhood( graphCopier.copy({ sourceGraph: sourceGraph, targetGraph: view.neighborhoodGraph, - filter: item => !(item instanceof INode) || nodesToCopy.has(item), + filter: (item) => !(item instanceof INode) || nodesToCopy.has(item), elementCopiedCallback }) } diff --git a/demos/showcase/neighborhood/build-graph-callback.ts b/demos/showcase/neighborhood/build-graph-callback.ts index 034b52840..dde62342e 100644 --- a/demos/showcase/neighborhood/build-graph-callback.ts +++ b/demos/showcase/neighborhood/build-graph-callback.ts @@ -85,8 +85,8 @@ function buildFolderContents( // Get descendants of root nodes. const masterGraph = foldingView.manager.masterGraph const groupingSupport = masterGraph.groupingSupport - selectedSourceNodes.forEach(node => { - groupingSupport.getDescendants(foldingView.getMasterItem(node)).forEach(descendant => { + selectedSourceNodes.forEach((node) => { + groupingSupport.getDescendants(foldingView.getMasterItem(node)).forEach((descendant) => { nodesToCopy.add(descendant) }) }) @@ -97,11 +97,11 @@ function buildFolderContents( graphCopier.copy({ sourceGraph: masterGraph, targetGraph: view.neighborhoodGraph, - filter: item => { + filter: (item) => { if (item instanceof IEdge) { // filter intra-component edges return !!selectedSourceNodes.find( - node => + (node) => groupingSupport.isDescendant(item.sourceNode, foldingView.getMasterItem(node)) && groupingSupport.isDescendant(item.targetNode, foldingView.getMasterItem(node)) ) @@ -144,7 +144,7 @@ function buildNeighborhood( graphCopier.copy({ sourceGraph: sourceGraph, targetGraph: view.neighborhoodGraph, - filter: item => !(item instanceof INode) || nodesToCopy.has(item), + filter: (item) => !(item instanceof INode) || nodesToCopy.has(item), elementCopiedCallback }) } diff --git a/demos/showcase/neighborhood/index.html b/demos/showcase/neighborhood/index.html index 2a8fd77f4..c9013c3a8 100644 --- a/demos/showcase/neighborhood/index.html +++ b/demos/showcase/neighborhood/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/networkmonitoring/ConnectionEdgeStyle.js b/demos/showcase/networkmonitoring/ConnectionEdgeStyle.js index df645cbec..7b2176f6d 100644 --- a/demos/showcase/networkmonitoring/ConnectionEdgeStyle.js +++ b/demos/showcase/networkmonitoring/ConnectionEdgeStyle.js @@ -208,7 +208,7 @@ export class ConnectionEdgeStyle extends EdgeStyleBase { getPath(edge) { const path = new GeneralPath() path.moveTo(edge.sourcePort.location) - edge.bends.forEach(bend => { + edge.bends.forEach((bend) => { path.lineTo(bend.location) }) path.lineTo(edge.targetPort.location) @@ -264,7 +264,7 @@ export class ConnectionEdgeStyle extends EdgeStyleBase { imageExclamation.setAttribute('cursor', 'pointer') // TODO - add exclamation mark to hit test of edge and get rid of click and touch handling - const repairEdge = evt => { + const repairEdge = (evt) => { const connection = edge.tag connection.repair() evt.stopImmediatePropagation() diff --git a/demos/showcase/networkmonitoring/NetworkMonitoringDemo.js b/demos/showcase/networkmonitoring/NetworkMonitoringDemo.js index d0943ad57..abccc0f56 100644 --- a/demos/showcase/networkmonitoring/NetworkMonitoringDemo.js +++ b/demos/showcase/networkmonitoring/NetworkMonitoringDemo.js @@ -92,7 +92,7 @@ async function run() { const graphInputMode = createInputMode() - initializeToolTips(graphInputMode, item => + initializeToolTips(graphInputMode, (item) => item instanceof INode ? getDeviceTooltip(getDevice(item)) : getConnectionTooltip(getConnection(item)) @@ -122,13 +122,13 @@ async function run() { }) ) - network.onDeviceFailure = async device => { + network.onDeviceFailure = async (device) => { const node = graphBuilder.getNodeById(device.id) addFailureHighlight(node) await zoomToBounds(graphComponent, node.layout.toRect()) } - network.onConnectionFailure = async connection => { + network.onConnectionFailure = async (connection) => { const sourceNode = graphBuilder.getNodeById(connection.sender.id) const targetNode = graphBuilder.getNodeById(connection.receiver.id) // We don't need to consider bends in this demo as there are none. @@ -214,12 +214,14 @@ function createGraphBuilder(data, graphComponent) { const nodeCreator = graphBuilder.createNodesSource({ data: data.devices, - id: item => item.id + id: (item) => item.id }).nodeCreator nodeCreator.defaults.style = new DeviceNodeStyle(getDevice, getImage) nodeCreator.defaults.size = new Size(64, 64) - const nodeLabelCreator = nodeCreator.createLabelBinding(device => `${device.name}\n${device.ip}`) + const nodeLabelCreator = nodeCreator.createLabelBinding( + (device) => `${device.name}\n${device.ip}` + ) nodeLabelCreator.defaults.style = new DefaultLabelStyle({ backgroundStroke: null, backgroundFill: 'rgba(255,255,255,0.5)' @@ -232,8 +234,8 @@ function createGraphBuilder(data, graphComponent) { const edgeCreator = graphBuilder.createEdgesSource({ data: data.connections, - sourceId: item => item.sender.id, - targetId: item => item.receiver.id + sourceId: (item) => item.sender.id, + targetId: (item) => item.receiver.id }).edgeCreator // create an animator instance that can be used by the edge style @@ -266,17 +268,17 @@ async function zoomToBounds(graphComponent, bounds) { * @param {!Simulator} simulator */ function initializeUI(graphComponent, simulator) { - document.querySelector('#toggleLabels').addEventListener('click', event => { + document.querySelector('#toggleLabels').addEventListener('click', (event) => { const button = event.target graphComponent.graphModelManager.nodeLabelGroup.visible = button.checked }) - document.querySelector('#toggleFailures').addEventListener('click', evt => { + document.querySelector('#toggleFailures').addEventListener('click', (evt) => { const button = evt.target simulator.failuresEnabled = button.checked }) - document.querySelector('#pauseSimulation').addEventListener('click', evt => { + document.querySelector('#pauseSimulation').addEventListener('click', (evt) => { const button = evt.target edgeAnimator.paused = button.checked simulator.paused = button.checked diff --git a/demos/showcase/networkmonitoring/NetworkMonitoringDemo.ts b/demos/showcase/networkmonitoring/NetworkMonitoringDemo.ts index e22960f5c..644f2783e 100644 --- a/demos/showcase/networkmonitoring/NetworkMonitoringDemo.ts +++ b/demos/showcase/networkmonitoring/NetworkMonitoringDemo.ts @@ -92,7 +92,7 @@ async function run(): Promise { const graphInputMode = createInputMode() - initializeToolTips(graphInputMode, item => + initializeToolTips(graphInputMode, (item) => item instanceof INode ? getDeviceTooltip(getDevice(item)) : getConnectionTooltip(getConnection(item)) @@ -202,12 +202,14 @@ function createGraphBuilder(data: Network, graphComponent: GraphComponent): Grap const nodeCreator = graphBuilder.createNodesSource({ data: data.devices, - id: item => item.id + id: (item) => item.id }).nodeCreator nodeCreator.defaults.style = new DeviceNodeStyle(getDevice, getImage) nodeCreator.defaults.size = new Size(64, 64) - const nodeLabelCreator = nodeCreator.createLabelBinding(device => `${device.name}\n${device.ip}`) + const nodeLabelCreator = nodeCreator.createLabelBinding( + (device) => `${device.name}\n${device.ip}` + ) nodeLabelCreator.defaults.style = new DefaultLabelStyle({ backgroundStroke: null, backgroundFill: 'rgba(255,255,255,0.5)' @@ -220,8 +222,8 @@ function createGraphBuilder(data: Network, graphComponent: GraphComponent): Grap const edgeCreator = graphBuilder.createEdgesSource({ data: data.connections, - sourceId: item => item.sender.id, - targetId: item => item.receiver.id + sourceId: (item) => item.sender.id, + targetId: (item) => item.receiver.id }).edgeCreator // create an animator instance that can be used by the edge style @@ -250,12 +252,12 @@ function initializeUI(graphComponent: GraphComponent, simulator: Simulator): voi graphComponent.graphModelManager.nodeLabelGroup.visible = button.checked }) - document.querySelector('#toggleFailures')!.addEventListener('click', evt => { + document.querySelector('#toggleFailures')!.addEventListener('click', (evt) => { const button = evt.target as HTMLInputElement simulator.failuresEnabled = button.checked }) - document.querySelector('#pauseSimulation')!.addEventListener('click', evt => { + document.querySelector('#pauseSimulation')!.addEventListener('click', (evt) => { const button = evt.target as HTMLInputElement edgeAnimator.paused = button.checked simulator.paused = button.checked diff --git a/demos/showcase/networkmonitoring/device-popup.js b/demos/showcase/networkmonitoring/device-popup.js index c534ad7fe..bd3e65221 100644 --- a/demos/showcase/networkmonitoring/device-popup.js +++ b/demos/showcase/networkmonitoring/device-popup.js @@ -108,7 +108,7 @@ function updatePowerButtonState(enabled) { */ function updateDeviceInfoElement(device) { // Find and update elements according to their data-id attribute - devicePopup.div.querySelectorAll('div[data-id]').forEach(element => { + devicePopup.div.querySelectorAll('div[data-id]').forEach((element) => { const key = element.getAttribute('data-id') element.textContent = String(device[key] ?? '') }) diff --git a/demos/showcase/networkmonitoring/device-popup.ts b/demos/showcase/networkmonitoring/device-popup.ts index e262a0816..9338e64c1 100644 --- a/demos/showcase/networkmonitoring/device-popup.ts +++ b/demos/showcase/networkmonitoring/device-popup.ts @@ -106,7 +106,7 @@ function updatePowerButtonState(enabled: boolean): void { function updateDeviceInfoElement(device: Device): void { // Find and update elements according to their data-id attribute - devicePopup.div.querySelectorAll('div[data-id]').forEach(element => { + devicePopup.div.querySelectorAll('div[data-id]').forEach((element) => { const key = element.getAttribute('data-id') as keyof Device element.textContent = String(device[key] ?? '') }) diff --git a/demos/showcase/networkmonitoring/index.html b/demos/showcase/networkmonitoring/index.html index 6e6e814c4..6e769df7c 100644 --- a/demos/showcase/networkmonitoring/index.html +++ b/demos/showcase/networkmonitoring/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/networkmonitoring/model/Connection.ts b/demos/showcase/networkmonitoring/model/Connection.ts index 003e86f5e..1e16c2855 100644 --- a/demos/showcase/networkmonitoring/model/Connection.ts +++ b/demos/showcase/networkmonitoring/model/Connection.ts @@ -38,7 +38,10 @@ export class Connection { * @param sender The sending device. * @param receiver The receiving device. */ - constructor(readonly sender: Device, readonly receiver: Device) { + constructor( + readonly sender: Device, + readonly receiver: Device + ) { this.sender = sender this.receiver = receiver } diff --git a/demos/showcase/networkmonitoring/model/Network.js b/demos/showcase/networkmonitoring/model/Network.js index 1343f71c6..3653520e3 100644 --- a/demos/showcase/networkmonitoring/model/Network.js +++ b/demos/showcase/networkmonitoring/model/Network.js @@ -44,7 +44,7 @@ export class Network { */ static loadFromJSON(data) { const idMap = new Map() - const devices = data.nodeList.map(tag => { + const devices = data.nodeList.map((tag) => { const device = new Device() device.name = tag.name device.ip = tag.ip @@ -58,7 +58,7 @@ export class Network { }) const connections = data.edgeList.map( - edge => new Connection(idMap.get(edge.source), idMap.get(edge.target)) + (edge) => new Connection(idMap.get(edge.source), idMap.get(edge.target)) ) return new Network(devices, connections) @@ -80,7 +80,7 @@ export class Network { * @returns {!Array.} The connections that are connected to the device. */ getAdjacentConnections(device) { - return this.connections.filter(connection => Network.isAdjacentConnection(connection, device)) + return this.connections.filter((connection) => Network.isAdjacentConnection(connection, device)) } /** diff --git a/demos/showcase/networkmonitoring/model/Network.ts b/demos/showcase/networkmonitoring/model/Network.ts index 15e96ee8e..5aabba5d8 100644 --- a/demos/showcase/networkmonitoring/model/Network.ts +++ b/demos/showcase/networkmonitoring/model/Network.ts @@ -43,7 +43,7 @@ export class Network { */ static loadFromJSON(data: NetworkData): Network { const idMap = new Map() - const devices = data.nodeList.map(tag => { + const devices = data.nodeList.map((tag) => { const device = new Device() device.name = tag.name device.ip = tag.ip @@ -57,7 +57,7 @@ export class Network { }) const connections = data.edgeList.map( - edge => new Connection(idMap.get(edge.source)!, idMap.get(edge.target)!) + (edge) => new Connection(idMap.get(edge.source)!, idMap.get(edge.target)!) ) return new Network(devices, connections) @@ -68,7 +68,10 @@ export class Network { * @param devices The devices in the network. * @param connections The connections in the network. */ - constructor(readonly devices: Device[], readonly connections: Connection[]) {} + constructor( + readonly devices: Device[], + readonly connections: Connection[] + ) {} /** * Returns the connections having the given device as either sender or receiver. @@ -76,7 +79,7 @@ export class Network { * @returns The connections that are connected to the device. */ getAdjacentConnections(device: Device): Connection[] { - return this.connections.filter(connection => Network.isAdjacentConnection(connection, device)) + return this.connections.filter((connection) => Network.isAdjacentConnection(connection, device)) } /** diff --git a/demos/showcase/networkmonitoring/model/Simulator.js b/demos/showcase/networkmonitoring/model/Simulator.js index d0b68017a..92f22dfb1 100644 --- a/demos/showcase/networkmonitoring/model/Simulator.js +++ b/demos/showcase/networkmonitoring/model/Simulator.js @@ -135,7 +135,7 @@ export class Simulator { } // reset packet-related properties on the connections - this.activePackets.forEach(packet => { + this.activePackets.forEach((packet) => { packet.connection.hasForwardPacket = false packet.connection.hasBackwardPacket = false }) @@ -146,7 +146,7 @@ export class Simulator { this.createPackets() - this.activePackets.forEach(packet => { + this.activePackets.forEach((packet) => { const connection = packet.connection connection.hasForwardPacket = connection.hasForwardPacket || packet.sender === connection.sender @@ -162,20 +162,20 @@ export class Simulator { * Determines for every connection and device whether it should fail and does so, if necessary. */ breakThings() { - const isFailing = item => Math.random() < Simulator.FAILURE_PROBABILITY * (item.load + 0.1) + const isFailing = (item) => Math.random() < Simulator.FAILURE_PROBABILITY * (item.load + 0.1) this.network.devices - .filter(item => !item.failed && isFailing(item)) + .filter((item) => !item.failed && isFailing(item)) .slice(0, 3) - .forEach(item => { + .forEach((item) => { item.fail() this.network.onDeviceFailure?.(item) }) this.network.connections - .filter(item => !item.failed && isFailing(item)) + .filter((item) => !item.failed && isFailing(item)) .slice(0, 3) - .forEach(item => { + .forEach((item) => { item.fail() this.network.onConnectionFailure?.(item) }) @@ -190,12 +190,12 @@ export class Simulator { // Find all connections that are still enabled and unbroken. Connections are automatically // disabled if either endpoint is disabled or broken. const enabledConnections = this.network.connections.filter( - connection => connection.enabled && !connection.failed + (connection) => connection.enabled && !connection.failed ) // Restrict them to those edges that are adjacent to a node that can send packets. const eligibleConnections = enabledConnections.filter( - connection => connection.sender.canSendPackets() || connection.receiver.canSendPackets() + (connection) => connection.sender.canSendPackets() || connection.receiver.canSendPackets() ) // Pick a number of those edges at random @@ -204,7 +204,7 @@ export class Simulator { Simulator.NEW_PACKETS_PER_TICK + 1 ) - const packets = selectedConnections.map(connection => { + const packets = selectedConnections.map((connection) => { const sender = connection.sender.canSendPackets() ? connection.sender : connection.receiver const receiver = connection.sender.canSendPackets() ? connection.receiver : connection.sender return this.createPacket(sender, receiver, connection) @@ -225,14 +225,14 @@ export class Simulator { // Find packets that need to be considered for moving. // This excludes packets that end in a disabled or broken device or that travel along a now-broken connection. // We don't care whether the source is alive or not by now. - const packetsToMove = this.activePackets.filter(packet => { + const packetsToMove = this.activePackets.filter((packet) => { const isConnectionWorking = packet.connection.enabled && !packet.connection.failed const isReceiverWorking = packet.receiver.enabled && !packet.receiver.failed return isConnectionWorking && isReceiverWorking }) // Packets that arrive at servers or databases. They result in a reply packet. - const replyPackets = packetsToMove.filter(packet => { + const replyPackets = packetsToMove.filter((packet) => { const isSenderWorking = packet.sender.enabled && !packet.sender.failed const doReceiverReply = packet.receiver.kind === DeviceKind.SERVER || packet.receiver.kind === DeviceKind.DATABASE @@ -240,13 +240,13 @@ export class Simulator { }) // All other packets that just move on to their next destination. - const movingPackets = packetsToMove.filter(packet => !packet.receiver.canReceivePackets()) + const movingPackets = packetsToMove.filter((packet) => !packet.receiver.canReceivePackets()) // All packets have to be moved to the history list. We create new ones appropriately. this.historicalPackets.push(...this.activePackets) this.activePackets = [] - movingPackets.forEach(packet => { + movingPackets.forEach((packet) => { const origin = packet.sender const currentConnection = packet.connection @@ -256,13 +256,13 @@ export class Simulator { // Try finding a random connection to follow ... const possiblePathConnections = this.network .getAdjacentConnections(startDevice) - .filter(connection => connection !== currentConnection) - .filter(connection => { + .filter((connection) => connection !== currentConnection) + .filter((connection) => { const oppositeDevice = connection.sender === startDevice ? connection.receiver : connection.sender return origin.canConnectTo(oppositeDevice) }) - .filter(connection => connection.enabled && !connection.failed) + .filter((connection) => connection.enabled && !connection.failed) if (possiblePathConnections.length > 0) { const connection = shuffle(possiblePathConnections)[0] @@ -275,7 +275,7 @@ export class Simulator { } }) - replyPackets.forEach(packet => { + replyPackets.forEach((packet) => { // We just bounce a new packet on the same edge, but in reverse direction. this.activePackets.push(this.createPacket(packet.receiver, packet.sender, packet.connection)) }) @@ -287,7 +287,7 @@ export class Simulator { */ pruneOldPackets() { this.historicalPackets = this.historicalPackets.filter( - packet => packet.time >= this.time - Simulator.HISTORY_SIZE + (packet) => packet.time >= this.time - Simulator.HISTORY_SIZE ) } @@ -301,18 +301,18 @@ export class Simulator { const history = [...this.activePackets, ...this.historicalPackets] // update connection loads - this.network.connections.forEach(connection => { + this.network.connections.forEach((connection) => { const timestamps = history - .filter(packet => packet.connection === connection) - .map(packet => packet.time) + .filter((packet) => packet.connection === connection) + .map((packet) => packet.time) const numberOfHistoryPackets = new Set(timestamps).size connection.load = Math.min(1, numberOfHistoryPackets / Simulator.HISTORY_SIZE) }) // update device loads - this.network.devices.forEach(device => { + this.network.devices.forEach((device) => { const timestamps = history.filter( - packet => packet.sender === device || packet.receiver === device + (packet) => packet.sender === device || packet.receiver === device ) const numberOfHistoryPackets = new Set(timestamps).size device.load = Math.min( diff --git a/demos/showcase/networkmonitoring/model/Simulator.ts b/demos/showcase/networkmonitoring/model/Simulator.ts index fd105a6a4..83b91f114 100644 --- a/demos/showcase/networkmonitoring/model/Simulator.ts +++ b/demos/showcase/networkmonitoring/model/Simulator.ts @@ -104,7 +104,7 @@ export class Simulator { } // reset packet-related properties on the connections - this.activePackets.forEach(packet => { + this.activePackets.forEach((packet) => { packet.connection.hasForwardPacket = false packet.connection.hasBackwardPacket = false }) @@ -115,7 +115,7 @@ export class Simulator { this.createPackets() - this.activePackets.forEach(packet => { + this.activePackets.forEach((packet) => { const connection = packet.connection connection.hasForwardPacket = connection.hasForwardPacket || packet.sender === connection.sender @@ -135,17 +135,17 @@ export class Simulator { Math.random() < Simulator.FAILURE_PROBABILITY * (item.load + 0.1) this.network.devices - .filter(item => !item.failed && isFailing(item)) + .filter((item) => !item.failed && isFailing(item)) .slice(0, 3) - .forEach(item => { + .forEach((item) => { item.fail() this.network.onDeviceFailure?.(item) }) this.network.connections - .filter(item => !item.failed && isFailing(item)) + .filter((item) => !item.failed && isFailing(item)) .slice(0, 3) - .forEach(item => { + .forEach((item) => { item.fail() this.network.onConnectionFailure?.(item) }) @@ -160,12 +160,12 @@ export class Simulator { // Find all connections that are still enabled and unbroken. Connections are automatically // disabled if either endpoint is disabled or broken. const enabledConnections = this.network.connections.filter( - connection => connection.enabled && !connection.failed + (connection) => connection.enabled && !connection.failed ) // Restrict them to those edges that are adjacent to a node that can send packets. const eligibleConnections = enabledConnections.filter( - connection => connection.sender.canSendPackets() || connection.receiver.canSendPackets() + (connection) => connection.sender.canSendPackets() || connection.receiver.canSendPackets() ) // Pick a number of those edges at random @@ -174,7 +174,7 @@ export class Simulator { Simulator.NEW_PACKETS_PER_TICK + 1 ) - const packets = selectedConnections.map(connection => { + const packets = selectedConnections.map((connection) => { const sender = connection.sender.canSendPackets() ? connection.sender : connection.receiver const receiver = connection.sender.canSendPackets() ? connection.receiver : connection.sender return this.createPacket(sender, receiver, connection) @@ -195,14 +195,14 @@ export class Simulator { // Find packets that need to be considered for moving. // This excludes packets that end in a disabled or broken device or that travel along a now-broken connection. // We don't care whether the source is alive or not by now. - const packetsToMove = this.activePackets.filter(packet => { + const packetsToMove = this.activePackets.filter((packet) => { const isConnectionWorking = packet.connection.enabled && !packet.connection.failed const isReceiverWorking = packet.receiver.enabled && !packet.receiver.failed return isConnectionWorking && isReceiverWorking }) // Packets that arrive at servers or databases. They result in a reply packet. - const replyPackets = packetsToMove.filter(packet => { + const replyPackets = packetsToMove.filter((packet) => { const isSenderWorking = packet.sender.enabled && !packet.sender.failed const doReceiverReply = packet.receiver.kind === DeviceKind.SERVER || packet.receiver.kind === DeviceKind.DATABASE @@ -210,13 +210,13 @@ export class Simulator { }) // All other packets that just move on to their next destination. - const movingPackets = packetsToMove.filter(packet => !packet.receiver.canReceivePackets()) + const movingPackets = packetsToMove.filter((packet) => !packet.receiver.canReceivePackets()) // All packets have to be moved to the history list. We create new ones appropriately. this.historicalPackets.push(...this.activePackets) this.activePackets = [] - movingPackets.forEach(packet => { + movingPackets.forEach((packet) => { const origin = packet.sender const currentConnection = packet.connection @@ -226,13 +226,13 @@ export class Simulator { // Try finding a random connection to follow ... const possiblePathConnections = this.network .getAdjacentConnections(startDevice) - .filter(connection => connection !== currentConnection) - .filter(connection => { + .filter((connection) => connection !== currentConnection) + .filter((connection) => { const oppositeDevice = connection.sender === startDevice ? connection.receiver : connection.sender return origin.canConnectTo(oppositeDevice) }) - .filter(connection => connection.enabled && !connection.failed) + .filter((connection) => connection.enabled && !connection.failed) if (possiblePathConnections.length > 0) { const connection = shuffle(possiblePathConnections)[0] @@ -245,7 +245,7 @@ export class Simulator { } }) - replyPackets.forEach(packet => { + replyPackets.forEach((packet) => { // We just bounce a new packet on the same edge, but in reverse direction. this.activePackets.push(this.createPacket(packet.receiver, packet.sender, packet.connection)) }) @@ -257,7 +257,7 @@ export class Simulator { */ pruneOldPackets(): void { this.historicalPackets = this.historicalPackets.filter( - packet => packet.time >= this.time - Simulator.HISTORY_SIZE + (packet) => packet.time >= this.time - Simulator.HISTORY_SIZE ) } @@ -271,18 +271,18 @@ export class Simulator { const history = [...this.activePackets, ...this.historicalPackets] // update connection loads - this.network.connections.forEach(connection => { + this.network.connections.forEach((connection) => { const timestamps = history - .filter(packet => packet.connection === connection) - .map(packet => packet.time) + .filter((packet) => packet.connection === connection) + .map((packet) => packet.time) const numberOfHistoryPackets = new Set(timestamps).size connection.load = Math.min(1, numberOfHistoryPackets / Simulator.HISTORY_SIZE) }) // update device loads - this.network.devices.forEach(device => { + this.network.devices.forEach((device) => { const timestamps = history.filter( - packet => packet.sender === device || packet.receiver === device + (packet) => packet.sender === device || packet.receiver === device ) const numberOfHistoryPackets = new Set(timestamps).size device.load = Math.min( diff --git a/demos/showcase/networkmonitoring/ui/D3BarChart.js b/demos/showcase/networkmonitoring/ui/D3BarChart.js index 5c25e7700..a23d76500 100644 --- a/demos/showcase/networkmonitoring/ui/D3BarChart.js +++ b/demos/showcase/networkmonitoring/ui/D3BarChart.js @@ -98,17 +98,17 @@ export class D3BarChart { // ... the actual bar element newGroups .append('rect') - .style('fill', d => convertLoadToColor(d, 0.75)) - .attr('y', d => y(d)) - .attr('height', d => this.chartHeight - y(d)) + .style('fill', (d) => convertLoadToColor(d, 0.75)) + .attr('y', (d) => y(d)) + .attr('height', (d) => this.chartHeight - y(d)) .attr('width', barWidth - 1) // Update the already constructed bars and labels if no new data is added groups .select('rect') - .style('fill', d => convertLoadToColor(d, 0.75)) - .attr('y', d => y(d)) - .attr('height', d => this.chartHeight - y(d)) + .style('fill', (d) => convertLoadToColor(d, 0.75)) + .attr('y', (d) => y(d)) + .attr('height', (d) => this.chartHeight - y(d)) // Remove bars which are no longer bound to data in the current data set groups.exit().remove() @@ -129,9 +129,9 @@ export class D3BarChart { newCurrentGroup .append('rect') - .style('fill', d => convertLoadToColor(d, 1)) - .attr('y', d => y(d)) - .attr('height', d => this.chartHeight - y(d)) + .style('fill', (d) => convertLoadToColor(d, 1)) + .attr('y', (d) => y(d)) + .attr('height', (d) => this.chartHeight - y(d)) .attr('width', currentBarWidth - 1) newCurrentGroup @@ -145,9 +145,9 @@ export class D3BarChart { // Update data currentGroup .select('rect') - .style('fill', d => convertLoadToColor(d, 1)) - .attr('y', d => y(d)) - .attr('height', d => this.chartHeight - y(d)) + .style('fill', (d) => convertLoadToColor(d, 1)) + .attr('y', (d) => y(d)) + .attr('height', (d) => this.chartHeight - y(d)) // Remove old data currentGroup.exit().remove() diff --git a/demos/showcase/networkmonitoring/ui/D3BarChart.ts b/demos/showcase/networkmonitoring/ui/D3BarChart.ts index 26d79d5a1..ea8517f09 100644 --- a/demos/showcase/networkmonitoring/ui/D3BarChart.ts +++ b/demos/showcase/networkmonitoring/ui/D3BarChart.ts @@ -97,17 +97,17 @@ export class D3BarChart { // ... the actual bar element newGroups .append('rect') - .style('fill', d => convertLoadToColor(d, 0.75)) - .attr('y', d => y(d)) - .attr('height', d => this.chartHeight - y(d)) + .style('fill', (d) => convertLoadToColor(d, 0.75)) + .attr('y', (d) => y(d)) + .attr('height', (d) => this.chartHeight - y(d)) .attr('width', barWidth - 1) // Update the already constructed bars and labels if no new data is added groups .select('rect') - .style('fill', d => convertLoadToColor(d, 0.75)) - .attr('y', d => y(d)) - .attr('height', d => this.chartHeight - y(d)) + .style('fill', (d) => convertLoadToColor(d, 0.75)) + .attr('y', (d) => y(d)) + .attr('height', (d) => this.chartHeight - y(d)) // Remove bars which are no longer bound to data in the current data set groups.exit().remove() @@ -128,9 +128,9 @@ export class D3BarChart { newCurrentGroup .append('rect') - .style('fill', d => convertLoadToColor(d, 1)) - .attr('y', d => y(d)) - .attr('height', d => this.chartHeight - y(d)) + .style('fill', (d) => convertLoadToColor(d, 1)) + .attr('y', (d) => y(d)) + .attr('height', (d) => this.chartHeight - y(d)) .attr('width', currentBarWidth - 1) newCurrentGroup @@ -144,9 +144,9 @@ export class D3BarChart { // Update data currentGroup .select('rect') - .style('fill', d => convertLoadToColor(d, 1)) - .attr('y', d => y(d)) - .attr('height', d => this.chartHeight - y(d)) + .style('fill', (d) => convertLoadToColor(d, 1)) + .attr('y', (d) => y(d)) + .attr('height', (d) => this.chartHeight - y(d)) // Remove old data currentGroup.exit().remove() diff --git a/demos/showcase/orgchart/CollapsibleTree.js b/demos/showcase/orgchart/CollapsibleTree.js index aa702cf79..5c98e45ca 100644 --- a/demos/showcase/orgchart/CollapsibleTree.js +++ b/demos/showcase/orgchart/CollapsibleTree.js @@ -100,7 +100,7 @@ export class CollapsibleTree { constructor(graphComponent, completeGraph = new DefaultGraph()) { this.completeGraph = completeGraph this.graphComponent = graphComponent - const nodeFilter = node => !this.hiddenNodesSet.has(node) + const nodeFilter = (node) => !this.hiddenNodesSet.has(node) this.filteredGraph = new FilteredGraphWrapper(completeGraph, nodeFilter) } @@ -477,11 +477,11 @@ export class CollapsibleTree { * @returns {!TreeLayoutData} */ createConfiguredLayoutData(graph = null, incrementalNodes = new Set()) { - const hasIncrementalParent = node => + const hasIncrementalParent = (node) => graph.inDegree(node) > 0 && incrementalNodes.has(graph.predecessors(node).first()) return new TreeLayoutData({ - assistantNodes: node => + assistantNodes: (node) => this.isAssistantNode(node) && graph.inDegree(node) > 0 && !hasIncrementalParent(node), outEdgeComparers: this.outEdgeComparers, nodeTypes: this.nodeTypesMapping, @@ -599,10 +599,10 @@ export class CollapsibleTree { */ static findEmptyGroups(graph, nodesToHide) { return graph.nodes.filter( - node => + (node) => graph.isGroupNode(node) && graph.degree(node) === 0 && - graph.getChildren(node).every(child => nodesToHide.has(child)) + graph.getChildren(node).every((child) => nodesToHide.has(child)) ) } @@ -631,9 +631,9 @@ export class CollapsibleTree { * @returns {!Set.} */ static collectAllNodesExceptSubtree(graph, excludedRoot) { - const subtree = this.collectDescendants(graph, excludedRoot) + const subtree = CollapsibleTree.collectDescendants(graph, excludedRoot) subtree.add(excludedRoot) - return new Set(graph.nodes.filter(node => !subtree.has(node))) + return new Set(graph.nodes.filter((node) => !subtree.has(node))) } /** diff --git a/demos/showcase/orgchart/CollapsibleTree.ts b/demos/showcase/orgchart/CollapsibleTree.ts index da055069f..2a5c93a40 100644 --- a/demos/showcase/orgchart/CollapsibleTree.ts +++ b/demos/showcase/orgchart/CollapsibleTree.ts @@ -566,10 +566,10 @@ export class CollapsibleTree { private static findEmptyGroups(graph: IGraph, nodesToHide: Set): IEnumerable { return graph.nodes.filter( - node => + (node) => graph.isGroupNode(node) && graph.degree(node) === 0 && - graph.getChildren(node).every(child => nodesToHide.has(child)) + graph.getChildren(node).every((child) => nodesToHide.has(child)) ) } @@ -593,9 +593,9 @@ export class CollapsibleTree { * Creates an array of all nodes excluding the nodes in the subtree rooted in the excluded sub-root. */ private static collectAllNodesExceptSubtree(graph: IGraph, excludedRoot: INode): Set { - const subtree = this.collectDescendants(graph, excludedRoot) + const subtree = CollapsibleTree.collectDescendants(graph, excludedRoot) subtree.add(excludedRoot) - return new Set(graph.nodes.filter(node => !subtree.has(node))) + return new Set(graph.nodes.filter((node) => !subtree.has(node))) } /** diff --git a/demos/showcase/orgchart/OrgChartDemo.js b/demos/showcase/orgchart/OrgChartDemo.js index 50594cc02..ddd4d3795 100644 --- a/demos/showcase/orgchart/OrgChartDemo.js +++ b/demos/showcase/orgchart/OrgChartDemo.js @@ -90,8 +90,8 @@ async function run() { * @param {!CollapsibleTree} orgChartGraph */ function configureOrgChartLayout(orgChartGraph) { - orgChartGraph.isAssistantNode = node => getEmployee(node)?.assistant ?? false - orgChartGraph.nodeTypesMapping = node => getEmployee(node)?.status ?? null + orgChartGraph.isAssistantNode = (node) => getEmployee(node)?.assistant ?? false + orgChartGraph.nodeTypesMapping = (node) => getEmployee(node)?.status ?? null // sort subtrees by a fixed order of the business units const businessUnitOrder = ['Engineering', 'Production', 'Accounting', 'Sales', 'Marketing'] diff --git a/demos/showcase/orgchart/OrgChartGraphSearch.js b/demos/showcase/orgchart/OrgChartGraphSearch.js index c5b187506..28b0934f5 100644 --- a/demos/showcase/orgchart/OrgChartGraphSearch.js +++ b/demos/showcase/orgchart/OrgChartGraphSearch.js @@ -26,7 +26,7 @@ ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ** ***************************************************************************/ -import GraphSearch from 'demo-utils/GraphSearch' +import { GraphSearch } from 'demo-utils/GraphSearch' import { getEmployee } from './model/data-loading.js' /** @@ -66,6 +66,6 @@ export class OrgChartGraphSearch extends GraphSearch { ) { return true } - return node.labels.some(label => label.text.toLowerCase().indexOf(lowercaseText) !== -1) + return node.labels.some((label) => label.text.toLowerCase().indexOf(lowercaseText) !== -1) } } diff --git a/demos/showcase/orgchart/OrgChartGraphSearch.ts b/demos/showcase/orgchart/OrgChartGraphSearch.ts index f5a987b4e..0f9792541 100644 --- a/demos/showcase/orgchart/OrgChartGraphSearch.ts +++ b/demos/showcase/orgchart/OrgChartGraphSearch.ts @@ -27,7 +27,7 @@ ** ***************************************************************************/ import type { INode, GraphComponent } from 'yfiles' -import GraphSearch from 'demo-utils/GraphSearch' +import { GraphSearch } from 'demo-utils/GraphSearch' import { getEmployee } from './model/data-loading' import type { CollapsibleTree } from './CollapsibleTree' @@ -69,6 +69,6 @@ export class OrgChartGraphSearch extends GraphSearch { ) { return true } - return node.labels.some(label => label.text.toLowerCase().indexOf(lowercaseText) !== -1) + return node.labels.some((label) => label.text.toLowerCase().indexOf(lowercaseText) !== -1) } } diff --git a/demos/showcase/orgchart/graph-style/orgchart-port-style.js b/demos/showcase/orgchart/graph-style/orgchart-port-style.js index fefb9903c..8498facc3 100644 --- a/demos/showcase/orgchart/graph-style/orgchart-port-style.js +++ b/demos/showcase/orgchart/graph-style/orgchart-port-style.js @@ -83,7 +83,7 @@ function setPortStylesToFirstOutgoingPorts(orgChartGraph) { */ function initializeStringTemplatePortStyle() { StringTemplatePortStyle.CONVERTERS.orgChartConverters = { - portIconStateConverter: val => + portIconStateConverter: (val) => 'port-icon ' + (val.collapsed ?? false ? 'port-icon-expand' : 'port-icon-collapse') } } diff --git a/demos/showcase/orgchart/index.html b/demos/showcase/orgchart/index.html index e18d4ab47..8e28e4861 100644 --- a/demos/showcase/orgchart/index.html +++ b/demos/showcase/orgchart/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/orgchart/model/data-loading.js b/demos/showcase/orgchart/model/data-loading.js index e855c0a38..a0b27343c 100644 --- a/demos/showcase/orgchart/model/data-loading.js +++ b/demos/showcase/orgchart/model/data-loading.js @@ -45,7 +45,7 @@ export function buildGraph(graph) { // configure the root nodes const rootSource = treeBuilder.createRootNodesSource(data) // configure the recursive structure of the children - rootSource.addChildNodesSource(dataItem => dataItem.subordinates, rootSource) + rootSource.addChildNodesSource((dataItem) => dataItem.subordinates, rootSource) treeBuilder.buildGraph() } diff --git a/demos/showcase/orgchart/model/data-loading.ts b/demos/showcase/orgchart/model/data-loading.ts index 641aa0bed..b53da586a 100644 --- a/demos/showcase/orgchart/model/data-loading.ts +++ b/demos/showcase/orgchart/model/data-loading.ts @@ -43,7 +43,7 @@ export function buildGraph(graph: IGraph): void { // configure the root nodes const rootSource = treeBuilder.createRootNodesSource(data) // configure the recursive structure of the children - rootSource.addChildNodesSource(dataItem => dataItem.subordinates, rootSource) + rootSource.addChildNodesSource((dataItem) => dataItem.subordinates, rootSource) treeBuilder.buildGraph() } diff --git a/demos/showcase/orgchart/printdocument.html b/demos/showcase/orgchart/printdocument.html index cdfe932ab..25c32fad2 100644 --- a/demos/showcase/orgchart/printdocument.html +++ b/demos/showcase/orgchart/printdocument.html @@ -1,4 +1,4 @@ - + -# Tutorial: Graph Builder - Tutorial: Graph Builder +# 01 Create Graph - Tutorial: Graph Builder # Creating a graph from business data @@ -73,4 +73,4 @@ There are two other graph builder classes that are better suited for specific sc See the steps about [AdjacencyGraphBuilder](../12-adjacency-graph-builder/) and [TreeBuilder](../13-tree-builder/) of this tutorial for an explanation on how these classes work. -[01 Create Graph](../../tutorial-graph-builder/01-create-graph/) +[02 Create Nodes Sources](../../tutorial-graph-builder/02-create-nodes-sources/) diff --git a/demos/tutorial-graph-builder/01-create-graph/index.html b/demos/tutorial-graph-builder/01-create-graph/index.html index c91c28177..6bbddda14 100644 --- a/demos/tutorial-graph-builder/01-create-graph/index.html +++ b/demos/tutorial-graph-builder/01-create-graph/index.html @@ -1,4 +1,4 @@ - + @@ -33,7 +33,7 @@ // ////////////////////////////////////////////////////////////////////////--> - Tutorial: Graph Builder - Tutorial: Graph Builder [yFiles for HTML] + 01 Create Graph - Tutorial: Graph Builder [yFiles for HTML] @@ -54,7 +54,7 @@ Tutorial: Graph Builder - Tutorial: Graph Builder + 01 Create Graph yFiles Demos
Requires "${ isViewerPackage ? 'layout' : 'viewer' @@ -123,10 +121,10 @@ } function createDemoHeader(category) { - var tmpDiv = document.createElement('div') + const tmpDiv = document.createElement('div') tmpDiv.innerHTML = demoHeaderTemplate.innerHTML.replace( /{{([^}]+)}}/gi, - function (match, propertyName) { + (match, propertyName) => { if (Object.prototype.hasOwnProperty.call(category, propertyName)) { return category[propertyName] } else { @@ -139,10 +137,10 @@ } function createAccordionItem(category) { - var tmpDiv = document.createElement('div') + const tmpDiv = document.createElement('div') tmpDiv.innerHTML = accordionItemTemplate.innerHTML.replace( /{{([^}]+)}}/gi, - function (match, propertyName) { + (match, propertyName) => { if (Object.prototype.hasOwnProperty.call(category, propertyName)) { return category[propertyName] } else { @@ -151,14 +149,14 @@ } } ) - var item = tmpDiv.firstElementChild - item.querySelector('.accordion-title').addEventListener('click', function () { + const item = tmpDiv.firstElementChild + item.querySelector('.accordion-title').addEventListener('click', () => { if (item.classList.contains('expanded')) { item.classList.remove('expanded') filterByCategory('') changeTextContent('') } else { - accordionItems.forEach(accordion => accordion.classList.remove('expanded')) + accordionItems.forEach((accordion) => accordion.classList.remove('expanded')) item.classList.add('expanded') clearSearchBox() filterByCategory(category.identifier) @@ -169,17 +167,17 @@ } function createSidebarItem(demo) { - var sidebarItem = document.createElement('div') + const sidebarItem = document.createElement('div') sidebarItem.className = 'demo-sidebar-item' if (!demo.availableInPackage) { sidebarItem.className += ' not-available' sidebarItem.className += isViewerPackage ? ' viewer-package' : ' layout-package' } - var link = document.createElement('a') + const link = document.createElement('a') link.textContent = demo.name link.setAttribute('href', demo.demoPath) - var languageTypeBadge = createLanguageTypeBatch(demo) + const languageTypeBadge = createLanguageTypeBatch(demo) if (languageTypeBadge != null) { link.appendChild(languageTypeBadge) if (isTsReadme && demo.languageType === 'js-only') @@ -203,7 +201,7 @@ ) { return null } - var badge = document.createElement('span') + const badge = document.createElement('span') badge.className = 'js-badge' if (demo.languageType === 'js-only') { badge.textContent = 'JS' @@ -216,10 +214,10 @@ } function insertSortedChild(parent, newChild) { - var children = parent.querySelectorAll('div') + const children = parent.querySelectorAll('div') parent.appendChild(newChild) - for (var i = 0; i < children.length; i++) { - var child = children[i] + for (let i = 0; i < children.length; i++) { + const child = children[i] if (child.textContent > newChild.textContent) { parent.insertBefore(newChild, child) break @@ -238,12 +236,10 @@ if (categoryFilter && demo.category !== categoryFilter) { return 0 } - var words = needle.split(/[^.\w/]/) + const words = needle.split(/[^.\w/]/) return words - .map(function (word) { - return matchWord(demo, word) - }) - .reduce(function (prev, curr) { + .map((word) => matchWord(demo, word)) + .reduce((prev, curr) => { if (categoryFilter) { // when filtering a specific demo category, avoid any priorities, but show demos in the given order return prev > 0 || curr > 0 ? 1 : 0 @@ -262,23 +258,14 @@ * the value is 0 if the demo doesn't match at all. */ function matchWord(demo, word) { - var regex = new RegExp(word, 'gi') + const regex = new RegExp(word, 'gi') if (regex.test(demo.name)) { return 100 } - if ( - demo.tags.some(function (tag) { - return regex.test(normalize(tag)) - }) - ) { + if (demo.tags.some((tag) => regex.test(normalize(tag)))) { return 50 } - if ( - demo.keywords && - demo.keywords.some(function (keyword) { - return regex.test(normalize(keyword)) - }) - ) { + if (demo.keywords && demo.keywords.some((keyword) => regex.test(normalize(keyword)))) { return 20 } if (regex.test(demo.category)) { @@ -291,26 +278,28 @@ return word.replaceAll(/\s|-/g, '') } - var demoGrid = document.getElementById('non-tutorial-grid') - var tutBasicFeaturesGrid = document.getElementById('tutorial-basic-features-grid') - var tutCustomNodeStyleGrid = document.getElementById('tutorial-node-style-implementation-grid') - var tutCustomLabelStyleGrid = document.getElementById('tutorial-label-style-implementation-grid') - var tutCustomEdgeStyleGrid = document.getElementById('tutorial-edge-style-implementation-grid') - var tutCustomPortStyleGrid = document.getElementById('tutorial-port-style-implementation-grid') - var tutGraphBuilderGrid = document.getElementById('tutorial-graph-builder-grid') - var searchBox = document.querySelector('#search') - var noSearchResultsElement = document.querySelector('#no-search-results') - var resetSearchButton = document.querySelector('.reset-search') - var unAvailableGrid = document.getElementById('unavailable-grid') - var unAvailableGridHeader = document.getElementById('unavailable-header') + const demoGrid = document.getElementById('non-tutorial-grid') + const tutBasicFeaturesGrid = document.getElementById('tutorial-basic-features-grid') + const tutCustomNodeStyleGrid = document.getElementById('tutorial-node-style-implementation-grid') + const tutCustomLabelStyleGrid = document.getElementById( + 'tutorial-label-style-implementation-grid' + ) + const tutCustomEdgeStyleGrid = document.getElementById('tutorial-edge-style-implementation-grid') + const tutCustomPortStyleGrid = document.getElementById('tutorial-port-style-implementation-grid') + const tutGraphBuilderGrid = document.getElementById('tutorial-graph-builder-grid') + const searchBox = document.querySelector('#search') + const noSearchResultsElement = document.querySelector('#no-search-results') + const resetSearchButton = document.querySelector('.reset-search') + const unAvailableGrid = document.getElementById('unavailable-grid') + const unAvailableGridHeader = document.getElementById('unavailable-header') if (isViewerPackage || isLayoutPackage) { unAvailableGrid.style.display = 'block' unAvailableGridHeader.style.display = 'block' } - demos.forEach(function (demo, index) { - var gridItem = createGridItem(demo, index) + demos.forEach((demo, index) => { + const gridItem = createGridItem(demo, index) if (demo.category === 'tutorial-basic-features') { tutBasicFeaturesGrid.appendChild(gridItem) } else if (demo.category === 'tutorial-node-style-implementation') { @@ -329,8 +318,8 @@ demoGrid.appendChild(gridItem) } demo.element = gridItem - var sidebarItem = createSidebarItem(demo) - var element = document.querySelector('.demo-items-' + demo.category) + const sidebarItem = createSidebarItem(demo) + let element = document.querySelector('.demo-items-' + demo.category) if (!element) { let categoryName = categoryNames[demo.category] || demo.category if (categoryName.match(/^Tutorial:/)) { @@ -338,7 +327,7 @@ .replace('Tutorial: ', 'Tutorial: ') .replace(' Implementation', '') } - let accordionItem = createAccordionItem({ + const accordionItem = createAccordionItem({ title: categoryName, identifier: demo.category }) @@ -347,7 +336,7 @@ element = document.querySelector('.demo-items-' + demo.category) // insert demo-header if (!demo.category.match(/tutorial-.*/)) { - let demoHeader = createDemoHeader({ + const demoHeader = createDemoHeader({ title: categoryName, identifier: demo.category }) @@ -362,7 +351,7 @@ searchBox.addEventListener( 'input', debounce( - function () { + () => { searchBoxChanged() updateHash() }, @@ -371,10 +360,10 @@ ) ) searchBox.addEventListener('click', searchBoxClicked) - searchBox.addEventListener('blur', function () { + searchBox.addEventListener('blur', () => { searchBox.addEventListener('click', searchBoxClicked) }) - resetSearchButton.addEventListener('click', function () { + resetSearchButton.addEventListener('click', () => { searchBox.value = '' }) @@ -394,7 +383,7 @@ // Don't care about IE 9 return } - var searchTerm = searchBox.value.trim() + const searchTerm = searchBox.value.trim() history.replaceState({}, '', `#${searchTerm}`) } @@ -413,7 +402,7 @@ function showDemoCategoryHeader(categoryName) { document .querySelectorAll('.tutorial-header') - .forEach(element => element.classList.add('hidden')) + .forEach((element) => element.classList.add('hidden')) document.getElementById(categoryName + '-header')?.classList.remove('hidden') } @@ -428,20 +417,20 @@ const searchBoxEmpty = searchTerm === '' // when the search term is a category, use category matching/sorting - const matchedCategory = Object.keys(categoryNames).find(categoryId => categoryId === searchTerm) + const matchedCategory = Object.keys(categoryNames).find( + (categoryId) => categoryId === searchTerm + ) if (matchedCategory) { categoryFilter = matchedCategory searchTerm = '' } - const sortedDemos = demos.map(function (demo) { - return { - demo: demo, - prio: matchDemo(demo, searchTerm, categoryFilter) - } - }) + const sortedDemos = demos.map((demo) => ({ + demo: demo, + prio: matchDemo(demo, searchTerm, categoryFilter) + })) - sortedDemos.sort(function (i1, i2) { + sortedDemos.sort((i1, i2) => { if (i1.prio === i2.prio) { return 0 } @@ -456,7 +445,7 @@ // The first indexes are reserved for other elements. let baseTabIndex = 2 - sortedDemos.forEach(function (item, index) { + sortedDemos.forEach((item, index) => { const demo = item.demo // Reorder the nodes in each grid section demo.element.parentElement.appendChild(demo.element) @@ -492,14 +481,14 @@ }) baseTabIndex += sortedDemos.length - tutorialIds.forEach(function (id) { + tutorialIds.forEach((id) => { const gridElement = document.getElementById(id + '-grid') if (!gridElement) { return } const children = gridElement.children let allHidden = true - for (var i = 0; i < children.length; i++) { + for (let i = 0; i < children.length; i++) { const demoCard = children[i] if (demoCard.getAttribute('class').indexOf('filtered') === -1) { allHidden = false @@ -519,10 +508,10 @@ } function searchBoxChanged() { - var searchTerm = searchBox.value.trim() + const searchTerm = searchBox.value.trim() showDemoCategoryHeader('') - accordionItems.forEach(accordion => accordion.classList.remove('expanded')) + accordionItems.forEach((accordion) => accordion.classList.remove('expanded')) filterDemos(searchTerm, '') @@ -531,16 +520,16 @@ function getDemosWithDescriptionElement() { return demoData - .map(item => item.category) - .map(category => document.getElementById(category)) - .filter(element => element != null) + .map((item) => item.category) + .map((category) => document.getElementById(category)) + .filter((element) => element != null) } function changeTextContent(categoryName) { - getDemosWithDescriptionElement().forEach(element => { + getDemosWithDescriptionElement().forEach((element) => { element.style.display = 'none' }) - var content = document.getElementById(categoryName) + const content = document.getElementById(categoryName) if (content != null) { content.style.display = 'block' } @@ -554,21 +543,20 @@ * @returns {(function(): void)|*} */ function debounce(func, delay, immediate) { - var timeout + let timeout return function () { - var context = this, - args = arguments - var later = function () { + const args = arguments + const later = () => { timeout = null if (!immediate) { - func.apply(context, args) + func.apply(this, args) } } - var callNow = immediate && !timeout + const callNow = immediate && !timeout clearTimeout(timeout) timeout = setTimeout(later, delay) if (callNow) { - func.apply(context, args) + func.apply(this, args) } } } diff --git a/demos/resources/style/demo-option-editor.css b/demos/resources/style/demo-option-editor.css index 891163926..301a51772 100644 --- a/demos/resources/style/demo-option-editor.css +++ b/demos/resources/style/demo-option-editor.css @@ -94,11 +94,15 @@ .option-ui-tab .tab-content { text-align: center; - -webkit-transition: opacity 0.1s cubic-bezier(0.4, 0, 1, 1) 0s, + -webkit-transition: + opacity 0.1s cubic-bezier(0.4, 0, 1, 1) 0s, color 0.1s cubic-bezier(0.4, 0, 1, 1) 0s; - -moz-transition: opacity 0.1s cubic-bezier(0.4, 0, 1, 1) 0s, + -moz-transition: + opacity 0.1s cubic-bezier(0.4, 0, 1, 1) 0s, + color 0.1s cubic-bezier(0.4, 0, 1, 1) 0s; + transition: + opacity 0.1s cubic-bezier(0.4, 0, 1, 1) 0s, color 0.1s cubic-bezier(0.4, 0, 1, 1) 0s; - transition: opacity 0.1s cubic-bezier(0.4, 0, 1, 1) 0s, color 0.1s cubic-bezier(0.4, 0, 1, 1) 0s; cursor: default; pointer-events: none; -ms-user-select: initial; @@ -114,9 +118,15 @@ left: 0; width: 0; background-color: #efefef; - -webkit-transition: width 0.15s ease-in-out 0.18s, left 0.15s ease-in-out 0.18s; - -moz-transition: width 0.15s ease-in-out 0.18s, left 0.15s ease-in-out 0.18s; - transition: width 0.15s ease-in-out 0.18s, left 0.15s ease-in-out 0.18s; + -webkit-transition: + width 0.15s ease-in-out 0.18s, + left 0.15s ease-in-out 0.18s; + -moz-transition: + width 0.15s ease-in-out 0.18s, + left 0.15s ease-in-out 0.18s; + transition: + width 0.15s ease-in-out 0.18s, + left 0.15s ease-in-out 0.18s; } .option-ui-tab:not(.active) .tab-content { @@ -511,7 +521,8 @@ form.option-ui-spinner div.spinner-container label { } .option-ui-form-group .option-ui-button { - transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1), + transition: + background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1); font-family: Tahoma, Verdana, sans-serif; font-size: 14px; diff --git a/demos/resources/style/demo.css b/demos/resources/style/demo.css index e6c278a88..428098d85 100644 --- a/demos/resources/style/demo.css +++ b/demos/resources/style/demo.css @@ -1537,33 +1537,57 @@ body.loaded .hide-before-loaded { visibility: visible; } .elevation-1 { - box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 2px 1px -1px rgba(0, 0, 0, 0.12); + box-shadow: + 0 1px 3px 0 rgba(0, 0, 0, 0.2), + 0 1px 1px 0 rgba(0, 0, 0, 0.14), + 0 2px 1px -1px rgba(0, 0, 0, 0.12); } /* Floating Buttons */ .elevation-2 { - box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12); + box-shadow: + 0 1px 5px 0 rgba(0, 0, 0, 0.2), + 0 2px 2px 0 rgba(0, 0, 0, 0.14), + 0 3px 1px -2px rgba(0, 0, 0, 0.12); } .elevation-3 { - box-shadow: 0 1px 8px 0 rgba(0, 0, 0, 0.2), 0 3px 4px 0 rgba(0, 0, 0, 0.14), 0 3px 3px -2px rgba(0, 0, 0, 0.12); + box-shadow: + 0 1px 8px 0 rgba(0, 0, 0, 0.2), + 0 3px 4px 0 rgba(0, 0, 0, 0.14), + 0 3px 3px -2px rgba(0, 0, 0, 0.12); } /* header */ .elevation-4 { - box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12); + box-shadow: + 0 2px 4px -1px rgba(0, 0, 0, 0.2), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14), + 0px 1px 10px 0px rgba(0, 0, 0, 0.12); } /* Menu, Floating Buttons:hover */ .elevation-8 { - box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12); + box-shadow: + 0 5px 5px -3px rgba(0, 0, 0, 0.2), + 0px 8px 10px 1px rgba(0, 0, 0, 0.14), + 0px 3px 14px 2px rgba(0, 0, 0, 0.12); } .elevation-12 { - box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), 0px 12px 17px 2px rgba(0, 0, 0, 0.14), 0px 5px 22px 4px rgba(0, 0, 0, 0.12); + box-shadow: + 0 7px 8px -4px rgba(0, 0, 0, 0.2), + 0px 12px 17px 2px rgba(0, 0, 0, 0.14), + 0px 5px 22px 4px rgba(0, 0, 0, 0.12); } /* Nav Drawer */ .elevation-16 { - box-shadow: 0 8px 10px -5px rgba(0, 0, 0, 0.2), 0px 16px 24px 2px rgba(0, 0, 0, 0.14), 0px 6px 30px 5px rgba(0, 0, 0, 0.12); + box-shadow: + 0 8px 10px -5px rgba(0, 0, 0, 0.2), + 0px 16px 24px 2px rgba(0, 0, 0, 0.14), + 0px 6px 30px 5px rgba(0, 0, 0, 0.12); } /* Dialog */ .elevation-24 { - box-shadow: 0 11px 15px -7px rgba(0, 0, 0, 0.2), 0px 24px 38px 3px rgba(0, 0, 0, 0.14), 0px 9px 46px 8px rgba(0, 0, 0, 0.12); + box-shadow: + 0 11px 15px -7px rgba(0, 0, 0, 0.2), + 0px 24px 38px 3px rgba(0, 0, 0, 0.14), + 0px 9px 46px 8px rgba(0, 0, 0, 0.12); } .layer-0 { z-index: 0; @@ -1614,7 +1638,9 @@ body { - otherwise, event coordinates will be wrong if scrolling is re-enabled later */ display: grid; - grid-template-columns: clamp(var(--description-min-width), var(--description-width), var(--description-max-width)) 1fr; + grid-template-columns: + clamp(var(--description-min-width), var(--description-width), var(--description-max-width)) + 1fr; grid-template-rows: 60px auto 1fr; grid-template-areas: 'sidebar header' 'sidebar toolbar' 'sidebar content'; transition: grid-template 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; @@ -1627,7 +1653,7 @@ body.demo-left-hidden { grid-template-rows: 30px auto 1fr; } } -@media screen and (max-width: 640px ) { +@media screen and (max-width: 640px) { body { grid-template-columns: 100% 100%; } @@ -1665,10 +1691,12 @@ body:not(.loaded) .demo-page__main:after { top: 0; left: 0; z-index: 999; - background: #fff url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMy42NCAyNS4wMiIgaWQ9InN2ZzUxMDUiPg0KICA8ZyBpZD0ibGF5ZXIxIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtOTMuODEgLTExMS4xMykiPg0KICAgIDxnIGlkPSJnNTczNiIgdHJhbnNmb3JtPSJtYXRyaXgoLjI2NDU4IDAgMCAuMjY0NTggOTAuNjMgMTA4LjY1KSI+DQogICAgICA8cGF0aCBpZD0icGF0aDU2NTIiIGQ9Ik0xMi4zOSA0Ny4yOWwuMSAxOS4zYTI3Ljc0IDI3Ljc0IDAgMCAwIDEzLjkgMjMuOGwxNi44IDkuNmEyNy4zMiAyNy4zMiAwIDAgMCAyNy41LS4yTDg3LjM5IDkwQTI3LjQ0IDI3LjQ0IDAgMCAwIDEwMSA2Ni4wOWwtLjEtMTkuNEEyNy44NiAyNy44NiAwIDAgMCA4NyAyMi44OWwtMTYuNy05LjVhMjcuMzIgMjcuMzIgMCAwIDAtMjcuNS4ybC0xNi43IDkuOGEyNy41IDI3LjUgMCAwIDAtMTMuNyAyMy45IiBmaWxsPSIjMjQyMjY1Ii8+DQogICAgICA8ZyBpZD0iZzU2NzYiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE1IDkpIj4NCiAgICAgICAgPGcgaWQ9Imc1Njc0IiB0cmFuc2Zvcm09InJvdGF0ZSg1NCkiPg0KICAgICAgICAgIDxwYXRoIGlkPSJwYXRoNTY1NCIgZD0iTTY2Ljg5LTIwLjA0bC4xIDQuOGMuMiAyLjQuOCA0LjUgMi4zIDYuNSAxLjMgMS44IDMgMyA1IDRsMi41IDEuNWMyIDEuNSAxLjMgMy4yLTIuMyAxLjJsLTMtMS42YTExLjYgMTEuNiAwIDAgMC0xMC40LS4zbC0zLjkgMi4xYy00IDEtMi41LTEgMS0zLjEgMy0xLjUgNS4xLTQuMSA2LTcuMy40LTEuNS42LTMgLjUtNC42di0zLjdjLS41LTQgMi44LTQgMi4yLjV6IiBmaWxsPSIjMDBkOGZmIi8+DQogICAgICAgICAgPGcgaWQ9Imc1NjYwIj4NCiAgICAgICAgICAgIDxnIGlkPSJnNTY1OCIgY2xhc3M9IndvYmJsZSIgc3R5bGU9InRyYW5zZm9ybS1vcmlnaW46NjdweCAwcHg7YW5pbWF0aW9uLWRlbGF5Oi4xMjVzIj4NCiAgICAgICAgICAgICAgPHBhdGggaWQ9InBhdGg1NjU2IiBkPSJNNjUuNjEtMzguMzRjLTEuMyAwLTIuNS42LTMuNSAxLjVhNiA2IDAgMCAwLTEuNiA2LjNjLjQgMS4yIDEgMi4zIDEuOCAzLjRsMS4yIDIuMmMuOCAxLjYgMS4yIDMuMyAxLjQgNXYzLjFjMCAxLS4zIDQuOSAxIDQuOSAxLjQgMCAxLjMtMy44IDEuMi00Ljl2LTIuNmMwLTEuNi40LTMuMiAxLTQuNy41LTEuMiAxLjItMi4zIDEuOC0zLjQuNy0xLjIgMS40LTIuNSAxLjYtMy45YTYgNiAwIDAgMC0xLjctNS40IDUuNiA1LjYgMCAwIDAtNC4yLTEuNSIgZmlsbD0iIzAwZDhmZiIvPg0KICAgICAgICAgICAgPC9nPg0KICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8ZyBpZD0iZzU2NjYiIHRyYW5zZm9ybT0icm90YXRlKDEyMCA0NS45IDQ4LjcpIj4NCiAgICAgICAgICAgIDxnIGlkPSJnNTY2NCIgY2xhc3M9IndvYmJsZSIgc3R5bGU9InRyYW5zZm9ybS1vcmlnaW46LTE0LjVweCA0OHB4O2FuaW1hdGlvbi1kZWxheTouNDVzIj4NCiAgICAgICAgICAgICAgPHBhdGggaWQ9InBhdGg1NjYyIiBkPSJNLTEzLjk4IDMwLjMyYy0xLjMgMC0yLjUuNi0zLjUgMS41YTYgNiAwIDAgMC0xLjYgNi4zYy40IDEuMiAxIDIuMyAxLjggMy40bDEuMiAyLjJjLjggMS42IDEuMiAzLjMgMS40IDV2My4xYzAgMS0uMyA0LjkgMSA0LjkgMS40IDAgMS4zLTMuOCAxLjItNC45di0yLjZjMC0xLjYuNC0zLjIgMS00LjcuNS0xLjIgMS4yLTIuMyAxLjgtMy40LjctMS4yIDEuNC0yLjUgMS42LTMuOWE2IDYgMCAwIDAtMS43LTUuNCA1LjYgNS42IDAgMCAwLTQuMi0xLjUiIGZpbGw9IiMwMGQ4ZmYiLz4NCiAgICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8L2c+DQogICAgICAgICAgPGcgaWQ9Imc1NjcyIiB0cmFuc2Zvcm09InJvdGF0ZSgtMTIwIDQ1LjkgNDguNykiPg0KICAgICAgICAgICAgPGcgaWQ9Imc1NjcwIiBjbGFzcz0id29iYmxlIiBzdHlsZT0idHJhbnNmb3JtLW9yaWdpbjo4NnB4IDg5cHg7YW5pbWF0aW9uLWRlbGF5Oi43OXMiPg0KICAgICAgICAgICAgICA8cGF0aCBpZD0icGF0aDU2NjgiIGQ9Ik04NC44NSA2NC43NWMtMS4zIDAtMi41LjYtMy41IDEuNWE2IDYgMCAwIDAtMS42IDYuM2MuNCAxLjIgMSAyLjMgMS44IDMuNGwxLjIgMi4yYy44IDEuNiAxLjIgMy4zIDEuNCA1djMuMWMwIDEtLjMgNC45IDEgNC45IDEuNCAwIDEuMy0zLjggMS4yLTQuOXYtMi42YzAtMS42LjQtMy4yIDEtNC43LjUtMS4yIDEuMi0yLjMgMS44LTMuNC43LTEuMiAxLjQtMi41IDEuNi0zLjlhNiA2IDAgMCAwLTEuNy01LjQgNS42IDUuNiAwIDAgMC00LjItMS41IiBmaWxsPSIjMDBkOGZmIi8+DQogICAgICAgICAgICA8L2c+DQogICAgICAgICAgPC9nPg0KICAgICAgICA8L2c+DQogICAgICA8L2c+DQogICAgICA8ZyBpZD0iZzU3MDAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDkgNikiPg0KICAgICAgICA8ZyBpZD0iZzU2OTgiIHRyYW5zZm9ybT0icm90YXRlKDEwKSI+DQogICAgICAgICAgPHBhdGggaWQ9InBhdGg1Njc4IiBkPSJNNTQuMjMgMjguMzVsLjEgNC44Yy4yIDIuNC44IDQuNSAyLjMgNi41IDEuMyAxLjggMyAzIDUgNGwyLjUgMS41YzIgMS41IDEuMyAzLjItMi4zIDEuMmwtMy0xLjZhMTEuNiAxMS42IDAgMCAwLTEwLjQtLjNsLTMuOSAyLjFjLTQgMS0yLjUtMSAxLTMuMSAzLTEuNSA1LjEtNC4xIDYtNy4zLjQtMS41LjYtMyAuNS00LjZ2LTMuN2MtLjUtNCAyLjgtNCAyLjIuNXoiIGZpbGw9IiMwMGQ4ZmYiLz4NCiAgICAgICAgICA8ZyBpZD0iZzU2ODQiPg0KICAgICAgICAgICAgPGcgaWQ9Imc1NjgyIiBjbGFzcz0id29iYmxlIiBzdHlsZT0idHJhbnNmb3JtLW9yaWdpbjogNTNweCAzM3B4OyI+DQogICAgICAgICAgICAgIDxwYXRoIGlkPSJwYXRoNTY4MCIgZD0iTTUyLjcxIDEwLjA5Yy0xLjMgMC0yLjUuNi0zLjUgMS41YTYgNiAwIDAgMC0xLjYgNi4zYy40IDEuMiAxIDIuMyAxLjggMy40bDEuMiAyLjJjLjggMS42IDEuMiAzLjMgMS40IDV2My4xYzAgMS0uMyA0LjkgMSA0LjkgMS40IDAgMS4zLTMuOCAxLjItNC45di0yLjZjMC0xLjYuNC0zLjIgMS00LjcuNS0xLjIgMS4yLTIuMyAxLjgtMy40LjctMS4yIDEuNC0yLjUgMS42LTMuOWE2IDYgMCAwIDAtMS43LTUuNCA1LjYgNS42IDAgMCAwLTQuMi0xLjUiIGZpbGw9IiMwMGQ4ZmYiLz4NCiAgICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8L2c+DQogICAgICAgICAgPGcgaWQ9Imc1NjkwIiB0cmFuc2Zvcm09InJvdGF0ZSgxMjAgNDUuOSA0OC43KSI+DQogICAgICAgICAgICA8ZyBpZD0iZzU2ODgiIGNsYXNzPSJ3b2JibGUiIHN0eWxlPSJ0cmFuc2Zvcm0tb3JpZ2luOjM3cHggNTBweDthbmltYXRpb24tZGVsYXk6LjMzMzNzIj4NCiAgICAgICAgICAgICAgPHBhdGggaWQ9InBhdGg1Njg2IiBkPSJNMzQuMzQgMTYuMzljLTEuMyAwLTIuNS42LTMuNSAxLjVhNiA2IDAgMCAwLTEuNiA2LjNjLjQgMS4yIDEgMi4zIDEuOCAzLjRsMS4yIDIuMmMuOCAxLjYgMS4yIDMuMyAxLjQgNXYzLjFjMCAxLS4zIDQuOSAxIDQuOSAxLjQgMCAxLjMtMy44IDEuMi00Ljl2LTIuNmMwLTEuNi40LTMuMiAxLTQuNy41LTEuMiAxLjItMi4zIDEuOC0zLjQuNy0xLjIgMS40LTIuNSAxLjYtMy45YTYgNiAwIDAgMC0xLjctNS40IDUuNiA1LjYgMCAwIDAtNC4yLTEuNSIgZmlsbD0iIzAwZDhmZiIvPg0KICAgICAgICAgICAgPC9nPg0KICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8ZyBpZD0iZzU2OTYiIHRyYW5zZm9ybT0icm90YXRlKC0xMjAgNDUuOSA0OC43KSI+DQogICAgICAgICAgICA8ZyBpZD0iZzU2OTQiIGNsYXNzPSJ3b2JibGUiIHN0eWxlPSJ0cmFuc2Zvcm0tb3JpZ2luOjQ5cHggNTNweDthbmltYXRpb24tZGVsYXk6LjY2NnMiPg0KICAgICAgICAgICAgICA8cGF0aCBpZD0icGF0aDU2OTIiIGQ9Ik00OS4wMSAyOC45MmMtMS4zIDAtMi41LjYtMy41IDEuNWE2IDYgMCAwIDAtMS42IDYuM2MuNCAxLjIgMSAyLjMgMS44IDMuNGwxLjIgMi4yYy44IDEuNiAxLjIgMy4zIDEuNCA1djMuMWMwIDEtLjMgNC45IDEgNC45IDEuNCAwIDEuMy0zLjggMS4yLTQuOXYtMi42YzAtMS42LjQtMy4yIDEtNC43LjUtMS4yIDEuMi0yLjMgMS44LTMuNC43LTEuMiAxLjQtMi41IDEuNi0zLjlhNiA2IDAgMCAwLTEuNy01LjQgNS42IDUuNiAwIDAgMC00LjItMS41IiBmaWxsPSIjMDBkOGZmIi8+DQogICAgICAgICAgICA8L2c+DQogICAgICAgICAgPC9nPg0KICAgICAgICA8L2c+DQogICAgICA8L2c+DQogICAgPC9nPg0KICA8L2c+DQogIDxzdHlsZSBpZD0ic3R5bGU1NjUwIj4NCiAgICBAa2V5ZnJhbWVzIHdvYmJsZSB7DQogICAgMCUsDQogICAgMzMlLA0KICAgIHRvIHsNCiAgICB0cmFuc2Zvcm06IHNjYWxlKDEpOw0KICAgIH0NCiAgICA1MCUgew0KICAgIHRyYW5zZm9ybTogc2NhbGUoMC45NSk7DQogICAgfQ0KICAgIDY2JSB7DQogICAgdHJhbnNmb3JtOiBzY2FsZSgxLjE1KTsNCiAgICB9DQogICAgfQ0KICAgIC53b2JibGUgew0KICAgIGFuaW1hdGlvbjogd29iYmxlIDFzIGVhc2UtaW4tb3V0IGluZmluaXRlOw0KICAgIHRyYW5zZm9ybS1vcmlnaW46IDUyLjNweCAzNC40NXB4Ow0KICAgIH0NCiAgPC9zdHlsZT4NCjwvc3ZnPg==) no-repeat center; + background: #fff + url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMy42NCAyNS4wMiIgaWQ9InN2ZzUxMDUiPg0KICA8ZyBpZD0ibGF5ZXIxIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtOTMuODEgLTExMS4xMykiPg0KICAgIDxnIGlkPSJnNTczNiIgdHJhbnNmb3JtPSJtYXRyaXgoLjI2NDU4IDAgMCAuMjY0NTggOTAuNjMgMTA4LjY1KSI+DQogICAgICA8cGF0aCBpZD0icGF0aDU2NTIiIGQ9Ik0xMi4zOSA0Ny4yOWwuMSAxOS4zYTI3Ljc0IDI3Ljc0IDAgMCAwIDEzLjkgMjMuOGwxNi44IDkuNmEyNy4zMiAyNy4zMiAwIDAgMCAyNy41LS4yTDg3LjM5IDkwQTI3LjQ0IDI3LjQ0IDAgMCAwIDEwMSA2Ni4wOWwtLjEtMTkuNEEyNy44NiAyNy44NiAwIDAgMCA4NyAyMi44OWwtMTYuNy05LjVhMjcuMzIgMjcuMzIgMCAwIDAtMjcuNS4ybC0xNi43IDkuOGEyNy41IDI3LjUgMCAwIDAtMTMuNyAyMy45IiBmaWxsPSIjMjQyMjY1Ii8+DQogICAgICA8ZyBpZD0iZzU2NzYiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE1IDkpIj4NCiAgICAgICAgPGcgaWQ9Imc1Njc0IiB0cmFuc2Zvcm09InJvdGF0ZSg1NCkiPg0KICAgICAgICAgIDxwYXRoIGlkPSJwYXRoNTY1NCIgZD0iTTY2Ljg5LTIwLjA0bC4xIDQuOGMuMiAyLjQuOCA0LjUgMi4zIDYuNSAxLjMgMS44IDMgMyA1IDRsMi41IDEuNWMyIDEuNSAxLjMgMy4yLTIuMyAxLjJsLTMtMS42YTExLjYgMTEuNiAwIDAgMC0xMC40LS4zbC0zLjkgMi4xYy00IDEtMi41LTEgMS0zLjEgMy0xLjUgNS4xLTQuMSA2LTcuMy40LTEuNS42LTMgLjUtNC42di0zLjdjLS41LTQgMi44LTQgMi4yLjV6IiBmaWxsPSIjMDBkOGZmIi8+DQogICAgICAgICAgPGcgaWQ9Imc1NjYwIj4NCiAgICAgICAgICAgIDxnIGlkPSJnNTY1OCIgY2xhc3M9IndvYmJsZSIgc3R5bGU9InRyYW5zZm9ybS1vcmlnaW46NjdweCAwcHg7YW5pbWF0aW9uLWRlbGF5Oi4xMjVzIj4NCiAgICAgICAgICAgICAgPHBhdGggaWQ9InBhdGg1NjU2IiBkPSJNNjUuNjEtMzguMzRjLTEuMyAwLTIuNS42LTMuNSAxLjVhNiA2IDAgMCAwLTEuNiA2LjNjLjQgMS4yIDEgMi4zIDEuOCAzLjRsMS4yIDIuMmMuOCAxLjYgMS4yIDMuMyAxLjQgNXYzLjFjMCAxLS4zIDQuOSAxIDQuOSAxLjQgMCAxLjMtMy44IDEuMi00Ljl2LTIuNmMwLTEuNi40LTMuMiAxLTQuNy41LTEuMiAxLjItMi4zIDEuOC0zLjQuNy0xLjIgMS40LTIuNSAxLjYtMy45YTYgNiAwIDAgMC0xLjctNS40IDUuNiA1LjYgMCAwIDAtNC4yLTEuNSIgZmlsbD0iIzAwZDhmZiIvPg0KICAgICAgICAgICAgPC9nPg0KICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8ZyBpZD0iZzU2NjYiIHRyYW5zZm9ybT0icm90YXRlKDEyMCA0NS45IDQ4LjcpIj4NCiAgICAgICAgICAgIDxnIGlkPSJnNTY2NCIgY2xhc3M9IndvYmJsZSIgc3R5bGU9InRyYW5zZm9ybS1vcmlnaW46LTE0LjVweCA0OHB4O2FuaW1hdGlvbi1kZWxheTouNDVzIj4NCiAgICAgICAgICAgICAgPHBhdGggaWQ9InBhdGg1NjYyIiBkPSJNLTEzLjk4IDMwLjMyYy0xLjMgMC0yLjUuNi0zLjUgMS41YTYgNiAwIDAgMC0xLjYgNi4zYy40IDEuMiAxIDIuMyAxLjggMy40bDEuMiAyLjJjLjggMS42IDEuMiAzLjMgMS40IDV2My4xYzAgMS0uMyA0LjkgMSA0LjkgMS40IDAgMS4zLTMuOCAxLjItNC45di0yLjZjMC0xLjYuNC0zLjIgMS00LjcuNS0xLjIgMS4yLTIuMyAxLjgtMy40LjctMS4yIDEuNC0yLjUgMS42LTMuOWE2IDYgMCAwIDAtMS43LTUuNCA1LjYgNS42IDAgMCAwLTQuMi0xLjUiIGZpbGw9IiMwMGQ4ZmYiLz4NCiAgICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8L2c+DQogICAgICAgICAgPGcgaWQ9Imc1NjcyIiB0cmFuc2Zvcm09InJvdGF0ZSgtMTIwIDQ1LjkgNDguNykiPg0KICAgICAgICAgICAgPGcgaWQ9Imc1NjcwIiBjbGFzcz0id29iYmxlIiBzdHlsZT0idHJhbnNmb3JtLW9yaWdpbjo4NnB4IDg5cHg7YW5pbWF0aW9uLWRlbGF5Oi43OXMiPg0KICAgICAgICAgICAgICA8cGF0aCBpZD0icGF0aDU2NjgiIGQ9Ik04NC44NSA2NC43NWMtMS4zIDAtMi41LjYtMy41IDEuNWE2IDYgMCAwIDAtMS42IDYuM2MuNCAxLjIgMSAyLjMgMS44IDMuNGwxLjIgMi4yYy44IDEuNiAxLjIgMy4zIDEuNCA1djMuMWMwIDEtLjMgNC45IDEgNC45IDEuNCAwIDEuMy0zLjggMS4yLTQuOXYtMi42YzAtMS42LjQtMy4yIDEtNC43LjUtMS4yIDEuMi0yLjMgMS44LTMuNC43LTEuMiAxLjQtMi41IDEuNi0zLjlhNiA2IDAgMCAwLTEuNy01LjQgNS42IDUuNiAwIDAgMC00LjItMS41IiBmaWxsPSIjMDBkOGZmIi8+DQogICAgICAgICAgICA8L2c+DQogICAgICAgICAgPC9nPg0KICAgICAgICA8L2c+DQogICAgICA8L2c+DQogICAgICA8ZyBpZD0iZzU3MDAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDkgNikiPg0KICAgICAgICA8ZyBpZD0iZzU2OTgiIHRyYW5zZm9ybT0icm90YXRlKDEwKSI+DQogICAgICAgICAgPHBhdGggaWQ9InBhdGg1Njc4IiBkPSJNNTQuMjMgMjguMzVsLjEgNC44Yy4yIDIuNC44IDQuNSAyLjMgNi41IDEuMyAxLjggMyAzIDUgNGwyLjUgMS41YzIgMS41IDEuMyAzLjItMi4zIDEuMmwtMy0xLjZhMTEuNiAxMS42IDAgMCAwLTEwLjQtLjNsLTMuOSAyLjFjLTQgMS0yLjUtMSAxLTMuMSAzLTEuNSA1LjEtNC4xIDYtNy4zLjQtMS41LjYtMyAuNS00LjZ2LTMuN2MtLjUtNCAyLjgtNCAyLjIuNXoiIGZpbGw9IiMwMGQ4ZmYiLz4NCiAgICAgICAgICA8ZyBpZD0iZzU2ODQiPg0KICAgICAgICAgICAgPGcgaWQ9Imc1NjgyIiBjbGFzcz0id29iYmxlIiBzdHlsZT0idHJhbnNmb3JtLW9yaWdpbjogNTNweCAzM3B4OyI+DQogICAgICAgICAgICAgIDxwYXRoIGlkPSJwYXRoNTY4MCIgZD0iTTUyLjcxIDEwLjA5Yy0xLjMgMC0yLjUuNi0zLjUgMS41YTYgNiAwIDAgMC0xLjYgNi4zYy40IDEuMiAxIDIuMyAxLjggMy40bDEuMiAyLjJjLjggMS42IDEuMiAzLjMgMS40IDV2My4xYzAgMS0uMyA0LjkgMSA0LjkgMS40IDAgMS4zLTMuOCAxLjItNC45di0yLjZjMC0xLjYuNC0zLjIgMS00LjcuNS0xLjIgMS4yLTIuMyAxLjgtMy40LjctMS4yIDEuNC0yLjUgMS42LTMuOWE2IDYgMCAwIDAtMS43LTUuNCA1LjYgNS42IDAgMCAwLTQuMi0xLjUiIGZpbGw9IiMwMGQ4ZmYiLz4NCiAgICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8L2c+DQogICAgICAgICAgPGcgaWQ9Imc1NjkwIiB0cmFuc2Zvcm09InJvdGF0ZSgxMjAgNDUuOSA0OC43KSI+DQogICAgICAgICAgICA8ZyBpZD0iZzU2ODgiIGNsYXNzPSJ3b2JibGUiIHN0eWxlPSJ0cmFuc2Zvcm0tb3JpZ2luOjM3cHggNTBweDthbmltYXRpb24tZGVsYXk6LjMzMzNzIj4NCiAgICAgICAgICAgICAgPHBhdGggaWQ9InBhdGg1Njg2IiBkPSJNMzQuMzQgMTYuMzljLTEuMyAwLTIuNS42LTMuNSAxLjVhNiA2IDAgMCAwLTEuNiA2LjNjLjQgMS4yIDEgMi4zIDEuOCAzLjRsMS4yIDIuMmMuOCAxLjYgMS4yIDMuMyAxLjQgNXYzLjFjMCAxLS4zIDQuOSAxIDQuOSAxLjQgMCAxLjMtMy44IDEuMi00Ljl2LTIuNmMwLTEuNi40LTMuMiAxLTQuNy41LTEuMiAxLjItMi4zIDEuOC0zLjQuNy0xLjIgMS40LTIuNSAxLjYtMy45YTYgNiAwIDAgMC0xLjctNS40IDUuNiA1LjYgMCAwIDAtNC4yLTEuNSIgZmlsbD0iIzAwZDhmZiIvPg0KICAgICAgICAgICAgPC9nPg0KICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8ZyBpZD0iZzU2OTYiIHRyYW5zZm9ybT0icm90YXRlKC0xMjAgNDUuOSA0OC43KSI+DQogICAgICAgICAgICA8ZyBpZD0iZzU2OTQiIGNsYXNzPSJ3b2JibGUiIHN0eWxlPSJ0cmFuc2Zvcm0tb3JpZ2luOjQ5cHggNTNweDthbmltYXRpb24tZGVsYXk6LjY2NnMiPg0KICAgICAgICAgICAgICA8cGF0aCBpZD0icGF0aDU2OTIiIGQ9Ik00OS4wMSAyOC45MmMtMS4zIDAtMi41LjYtMy41IDEuNWE2IDYgMCAwIDAtMS42IDYuM2MuNCAxLjIgMSAyLjMgMS44IDMuNGwxLjIgMi4yYy44IDEuNiAxLjIgMy4zIDEuNCA1djMuMWMwIDEtLjMgNC45IDEgNC45IDEuNCAwIDEuMy0zLjggMS4yLTQuOXYtMi42YzAtMS42LjQtMy4yIDEtNC43LjUtMS4yIDEuMi0yLjMgMS44LTMuNC43LTEuMiAxLjQtMi41IDEuNi0zLjlhNiA2IDAgMCAwLTEuNy01LjQgNS42IDUuNiAwIDAgMC00LjItMS41IiBmaWxsPSIjMDBkOGZmIi8+DQogICAgICAgICAgICA8L2c+DQogICAgICAgICAgPC9nPg0KICAgICAgICA8L2c+DQogICAgICA8L2c+DQogICAgPC9nPg0KICA8L2c+DQogIDxzdHlsZSBpZD0ic3R5bGU1NjUwIj4NCiAgICBAa2V5ZnJhbWVzIHdvYmJsZSB7DQogICAgMCUsDQogICAgMzMlLA0KICAgIHRvIHsNCiAgICB0cmFuc2Zvcm06IHNjYWxlKDEpOw0KICAgIH0NCiAgICA1MCUgew0KICAgIHRyYW5zZm9ybTogc2NhbGUoMC45NSk7DQogICAgfQ0KICAgIDY2JSB7DQogICAgdHJhbnNmb3JtOiBzY2FsZSgxLjE1KTsNCiAgICB9DQogICAgfQ0KICAgIC53b2JibGUgew0KICAgIGFuaW1hdGlvbjogd29iYmxlIDFzIGVhc2UtaW4tb3V0IGluZmluaXRlOw0KICAgIHRyYW5zZm9ybS1vcmlnaW46IDUyLjNweCAzNC40NXB4Ow0KICAgIH0NCiAgPC9zdHlsZT4NCjwvc3ZnPg==) + no-repeat center; background-size: 200px; } -@media screen and (max-width: 640px ) { +@media screen and (max-width: 640px) { .demo-page__main { min-width: 100vw; } @@ -1688,7 +1716,10 @@ body:not(.loaded) .demo-page__main:after { font-family: Tahoma, Verdana, sans-serif; font-size: 14px; letter-spacing: 1px; - transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1); + transition: + background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1), + box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .flat-button.secondary { border: thin solid #4caf50; @@ -1710,11 +1741,14 @@ body:not(.loaded) .demo-page__main:after { height: 230px; background: #f7f7f7; overflow: hidden; - box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12); + box-shadow: + 0 2px 4px -1px rgba(0, 0, 0, 0.2), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14), + 0px 1px 10px 0px rgba(0, 0, 0, 0.12); z-index: 4; transition: height 1s cubic-bezier(0.23, 1, 0.32, 1) 0s; } -@media screen and (max-width: 640px ) { +@media screen and (max-width: 640px) { .demo-overlay { visibility: hidden; } @@ -1866,7 +1900,9 @@ body:not(.loaded) .demo-page__main:after { bottom: 0; right: 0; width: 320px; - font: 20px Tahoma, sans-serif; + font: + 20px Tahoma, + sans-serif; padding: 1em; box-sizing: border-box; } @@ -2070,7 +2106,7 @@ rect:not(.yfiles-node-highlight-inner-template):not(.yfiles-node-highlight-middl width: 60px; height: 60px; } -@media screen and (max-width: 960px ) { +@media screen and (max-width: 960px) { .demo-header__right > * { margin: 0 5px; width: 30px; @@ -2273,7 +2309,7 @@ body:not(.loaded) .demo-page__toolbar:after { .demo-page__toolbar .demo-toggle-button:checked:active + label { background-color: #b2b2b2; } -@media screen and (max-width: 640px ) { +@media screen and (max-width: 640px) { .demo-page__toolbar button.demo-unimportant { display: none; } @@ -2308,7 +2344,9 @@ body:not(.loaded) .demo-page__toolbar:after { .demo-page__toolbar .overflow-button { position: absolute; right: 4px; - background: #ebeef0 url("data:image/svg+xml,%3Csvg fill='%23666' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z'/%3E%3C/svg%3E") no-repeat center 4px; + background: #ebeef0 + url("data:image/svg+xml,%3Csvg fill='%23666' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z'/%3E%3C/svg%3E") + no-repeat center 4px; } .demo-page__toolbar .overflow-button.hidden { display: none; @@ -2327,7 +2365,10 @@ body:not(.loaded) .demo-page__toolbar:after { right: 0; width: 200px; background-color: #f7f7f7; - box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12); + box-shadow: + 0 2px 4px -1px rgba(0, 0, 0, 0.2), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14), + 0px 1px 10px 0px rgba(0, 0, 0, 0.12); visibility: collapse; z-index: 1000; } @@ -2372,26 +2413,54 @@ body:not(.loaded) .demo-page__toolbar:after { line-height: 24px; } } -.demo-page__toolbar .overflow-container__content > *:not(.labeled)[class^='demo-icon-'][title]::after, -.demo-page__toolbar .overflow-container__content > button:not(.labeled)[class^='demo-icon-'][title]::after, -.demo-page__toolbar .overflow-container__content > label:not(.labeled)[class^='demo-icon-'][title]::after, -.demo-page__toolbar .overflow-container__content > .demo-toggle-button + label:not(.labeled)[class^='demo-icon-'][title]::after, -.demo-page__toolbar .overflow-container__content > .demo-toggle-button.labeled + label:not(.labeled)[class^='demo-icon-'][title]::after, -.demo-page__toolbar .overflow-container__content > button.labeled:not(.labeled)[class^='demo-icon-'][title]::after, +.demo-page__toolbar + .overflow-container__content + > *:not(.labeled)[class^='demo-icon-'][title]::after, +.demo-page__toolbar + .overflow-container__content + > button:not(.labeled)[class^='demo-icon-'][title]::after, +.demo-page__toolbar + .overflow-container__content + > label:not(.labeled)[class^='demo-icon-'][title]::after, +.demo-page__toolbar + .overflow-container__content + > .demo-toggle-button + + label:not(.labeled)[class^='demo-icon-'][title]::after, +.demo-page__toolbar + .overflow-container__content + > .demo-toggle-button.labeled + + label:not(.labeled)[class^='demo-icon-'][title]::after, +.demo-page__toolbar + .overflow-container__content + > button.labeled:not(.labeled)[class^='demo-icon-'][title]::after, .demo-page__toolbar .overflow-container__content > *:not(.labeled)[data-command][title]::after, .demo-page__toolbar .overflow-container__content > button:not(.labeled)[data-command][title]::after, .demo-page__toolbar .overflow-container__content > label:not(.labeled)[data-command][title]::after, -.demo-page__toolbar .overflow-container__content > .demo-toggle-button + label:not(.labeled)[data-command][title]::after, -.demo-page__toolbar .overflow-container__content > .demo-toggle-button.labeled + label:not(.labeled)[data-command][title]::after, -.demo-page__toolbar .overflow-container__content > button.labeled:not(.labeled)[data-command][title]::after { +.demo-page__toolbar + .overflow-container__content + > .demo-toggle-button + + label:not(.labeled)[data-command][title]::after, +.demo-page__toolbar + .overflow-container__content + > .demo-toggle-button.labeled + + label:not(.labeled)[data-command][title]::after, +.demo-page__toolbar + .overflow-container__content + > button.labeled:not(.labeled)[data-command][title]::after { content: attr(title); display: block; } .demo-page__toolbar .overflow-container__content > *:not(.labeled)[title]::after, .demo-page__toolbar .overflow-container__content > button:not(.labeled)[title]::after, .demo-page__toolbar .overflow-container__content > label:not(.labeled)[title]::after, -.demo-page__toolbar .overflow-container__content > .demo-toggle-button + label:not(.labeled)[title]::after, -.demo-page__toolbar .overflow-container__content > .demo-toggle-button.labeled + label:not(.labeled)[title]::after, +.demo-page__toolbar + .overflow-container__content + > .demo-toggle-button + + label:not(.labeled)[title]::after, +.demo-page__toolbar + .overflow-container__content + > .demo-toggle-button.labeled + + label:not(.labeled)[title]::after, .demo-page__toolbar .overflow-container__content > button.labeled:not(.labeled)[title]::after, .demo-page__toolbar .overflow-container__content > *.labeled, .demo-page__toolbar .overflow-container__content > button.labeled, @@ -2422,7 +2491,10 @@ body:not(.loaded) .demo-page__toolbar:after { .demo-page__toolbar .overflow-container__content input[type='checkbox']:focus { outline: none; } -.demo-page__toolbar .overflow-container__content input[type='checkbox'].demo-toggle-button.labeled + label { +.demo-page__toolbar + .overflow-container__content + input[type='checkbox'].demo-toggle-button.labeled + + label { padding-left: 45px !important; line-height: 32px; white-space: nowrap; @@ -2431,7 +2503,10 @@ body:not(.loaded) .demo-page__toolbar:after { text-align: left; } @media screen and (max-height: 500px) { - .demo-page__toolbar .overflow-container__content input[type='checkbox'].demo-toggle-button.labeled + label { + .demo-page__toolbar + .overflow-container__content + input[type='checkbox'].demo-toggle-button.labeled + + label { line-height: 24px; } } @@ -2461,11 +2536,15 @@ body:not(.loaded) .demo-page__toolbar:after { height: 100%; width: 100%; line-height: 140%; - min-width: clamp(var(--description-min-width), var(--description-width), var(--description-max-width)); + min-width: clamp( + var(--description-min-width), + var(--description-width), + var(--description-max-width) + ); background-color: var(--description-color); z-index: 16; } -@media screen and (max-width: 640px ) { +@media screen and (max-width: 640px) { .demo-page__description { --description-width: 100vw; } @@ -2556,14 +2635,15 @@ body:not(.loaded) .demo-page__toolbar:after { right: 0; top: 0; cursor: pointer; - background: url("data:image/svg+xml,%3Csvg fill='%23343f4a' height='24' viewBox='2 2 20 20' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M7 7h10v10H7z' fill='%2300d8ff'/%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5v-9l6 4.5-6 4.5z'/%3E%3C/svg%3E") no-repeat calc(100% - 15px); + background: url("data:image/svg+xml,%3Csvg fill='%23343f4a' height='24' viewBox='2 2 20 20' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M7 7h10v10H7z' fill='%2300d8ff'/%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5v-9l6 4.5-6 4.5z'/%3E%3C/svg%3E") + no-repeat calc(100% - 15px); background-size: 35px; font-size: 1.1rem; line-height: 60px; text-align: right; padding-right: 60px; } -@media screen and (max-width: 640px ) { +@media screen and (max-width: 640px) { .demo-description__play-button { display: block; } @@ -2597,7 +2677,7 @@ body:not(.loaded) .demo-page__toolbar:after { top: 50%; line-height: 7px; } -@media screen and (max-width: 960px ) { +@media screen and (max-width: 960px) { .demo-description__drag-area--vertical { display: none; } @@ -2613,7 +2693,7 @@ body:not(.loaded) .demo-page__toolbar:after { top: 0; line-height: 9px; } -@media screen and (max-width: 960px ) { +@media screen and (max-width: 960px) { .demo-description__drag-area--horizontal { display: block; } @@ -2625,7 +2705,7 @@ body:not(.loaded) .demo-page__toolbar:after { .demo-description--draggable .demo-description__play-button { display: none; } -@media screen and (max-width: 960px ) { +@media screen and (max-width: 960px) { .demo-description--draggable { border-right: none; border-top: 9px solid #d5d7d8; @@ -2656,7 +2736,9 @@ body:not(.loaded) .demo-page__toolbar:after { line-height: 140%; width: var(--sidebar-width); border-left: 1px solid #d5d7d8; - transition: width 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, padding-left 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; + transition: + width 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, + padding-left 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; } .demo-main__sidebar--hidden { padding-left: 24px; @@ -2696,7 +2778,10 @@ body:not(.loaded) .demo-page__toolbar:after { background-image: url('../icons/single-arrow-right.svg'); background-size: 8px; background-repeat: no-repeat; - transition: top 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, background-color 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, transform 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; + transition: + top 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, + background-color 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, + transform 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; } .demo-sidebar__toggle__button:hover { background-color: #dedede; @@ -2738,7 +2823,9 @@ body:not(.loaded) .demo-page__toolbar:after { .demo-main__sidebar--hidden .demo-sidebar__toggle__title { opacity: 1; transform: translateX(0); - transition: transform 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0.1s, opacity 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; + transition: + transform 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0.1s, + opacity 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; } .demo-properties__settings fieldset { border: 1px solid #888; diff --git a/demos/resources/style/tutorial.css b/demos/resources/style/tutorial.css index 50ab37fd0..67098694b 100644 --- a/demos/resources/style/tutorial.css +++ b/demos/resources/style/tutorial.css @@ -1537,33 +1537,57 @@ body.loaded .hide-before-loaded { visibility: visible; } .elevation-1 { - box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 2px 1px -1px rgba(0, 0, 0, 0.12); + box-shadow: + 0 1px 3px 0 rgba(0, 0, 0, 0.2), + 0 1px 1px 0 rgba(0, 0, 0, 0.14), + 0 2px 1px -1px rgba(0, 0, 0, 0.12); } /* Floating Buttons */ .elevation-2 { - box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12); + box-shadow: + 0 1px 5px 0 rgba(0, 0, 0, 0.2), + 0 2px 2px 0 rgba(0, 0, 0, 0.14), + 0 3px 1px -2px rgba(0, 0, 0, 0.12); } .elevation-3 { - box-shadow: 0 1px 8px 0 rgba(0, 0, 0, 0.2), 0 3px 4px 0 rgba(0, 0, 0, 0.14), 0 3px 3px -2px rgba(0, 0, 0, 0.12); + box-shadow: + 0 1px 8px 0 rgba(0, 0, 0, 0.2), + 0 3px 4px 0 rgba(0, 0, 0, 0.14), + 0 3px 3px -2px rgba(0, 0, 0, 0.12); } /* header */ .elevation-4 { - box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12); + box-shadow: + 0 2px 4px -1px rgba(0, 0, 0, 0.2), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14), + 0px 1px 10px 0px rgba(0, 0, 0, 0.12); } /* Menu, Floating Buttons:hover */ .elevation-8 { - box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12); + box-shadow: + 0 5px 5px -3px rgba(0, 0, 0, 0.2), + 0px 8px 10px 1px rgba(0, 0, 0, 0.14), + 0px 3px 14px 2px rgba(0, 0, 0, 0.12); } .elevation-12 { - box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), 0px 12px 17px 2px rgba(0, 0, 0, 0.14), 0px 5px 22px 4px rgba(0, 0, 0, 0.12); + box-shadow: + 0 7px 8px -4px rgba(0, 0, 0, 0.2), + 0px 12px 17px 2px rgba(0, 0, 0, 0.14), + 0px 5px 22px 4px rgba(0, 0, 0, 0.12); } /* Nav Drawer */ .elevation-16 { - box-shadow: 0 8px 10px -5px rgba(0, 0, 0, 0.2), 0px 16px 24px 2px rgba(0, 0, 0, 0.14), 0px 6px 30px 5px rgba(0, 0, 0, 0.12); + box-shadow: + 0 8px 10px -5px rgba(0, 0, 0, 0.2), + 0px 16px 24px 2px rgba(0, 0, 0, 0.14), + 0px 6px 30px 5px rgba(0, 0, 0, 0.12); } /* Dialog */ .elevation-24 { - box-shadow: 0 11px 15px -7px rgba(0, 0, 0, 0.2), 0px 24px 38px 3px rgba(0, 0, 0, 0.14), 0px 9px 46px 8px rgba(0, 0, 0, 0.12); + box-shadow: + 0 11px 15px -7px rgba(0, 0, 0, 0.2), + 0px 24px 38px 3px rgba(0, 0, 0, 0.14), + 0px 9px 46px 8px rgba(0, 0, 0, 0.12); } .layer-0 { z-index: 0; @@ -1614,7 +1638,9 @@ body { - otherwise, event coordinates will be wrong if scrolling is re-enabled later */ display: grid; - grid-template-columns: clamp(var(--description-min-width), var(--description-width), var(--description-max-width)) 1fr; + grid-template-columns: + clamp(var(--description-min-width), var(--description-width), var(--description-max-width)) + 1fr; grid-template-rows: 60px auto 1fr; grid-template-areas: 'sidebar header' 'sidebar toolbar' 'sidebar content'; transition: grid-template 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; @@ -1627,7 +1653,7 @@ body.demo-left-hidden { grid-template-rows: 30px auto 1fr; } } -@media screen and (max-width: 640px ) { +@media screen and (max-width: 640px) { body { grid-template-columns: 100% 100%; } @@ -1665,10 +1691,12 @@ body:not(.loaded) .demo-page__main:after { top: 0; left: 0; z-index: 999; - background: #fff url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMy42NCAyNS4wMiIgaWQ9InN2ZzUxMDUiPg0KICA8ZyBpZD0ibGF5ZXIxIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtOTMuODEgLTExMS4xMykiPg0KICAgIDxnIGlkPSJnNTczNiIgdHJhbnNmb3JtPSJtYXRyaXgoLjI2NDU4IDAgMCAuMjY0NTggOTAuNjMgMTA4LjY1KSI+DQogICAgICA8cGF0aCBpZD0icGF0aDU2NTIiIGQ9Ik0xMi4zOSA0Ny4yOWwuMSAxOS4zYTI3Ljc0IDI3Ljc0IDAgMCAwIDEzLjkgMjMuOGwxNi44IDkuNmEyNy4zMiAyNy4zMiAwIDAgMCAyNy41LS4yTDg3LjM5IDkwQTI3LjQ0IDI3LjQ0IDAgMCAwIDEwMSA2Ni4wOWwtLjEtMTkuNEEyNy44NiAyNy44NiAwIDAgMCA4NyAyMi44OWwtMTYuNy05LjVhMjcuMzIgMjcuMzIgMCAwIDAtMjcuNS4ybC0xNi43IDkuOGEyNy41IDI3LjUgMCAwIDAtMTMuNyAyMy45IiBmaWxsPSIjMjQyMjY1Ii8+DQogICAgICA8ZyBpZD0iZzU2NzYiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE1IDkpIj4NCiAgICAgICAgPGcgaWQ9Imc1Njc0IiB0cmFuc2Zvcm09InJvdGF0ZSg1NCkiPg0KICAgICAgICAgIDxwYXRoIGlkPSJwYXRoNTY1NCIgZD0iTTY2Ljg5LTIwLjA0bC4xIDQuOGMuMiAyLjQuOCA0LjUgMi4zIDYuNSAxLjMgMS44IDMgMyA1IDRsMi41IDEuNWMyIDEuNSAxLjMgMy4yLTIuMyAxLjJsLTMtMS42YTExLjYgMTEuNiAwIDAgMC0xMC40LS4zbC0zLjkgMi4xYy00IDEtMi41LTEgMS0zLjEgMy0xLjUgNS4xLTQuMSA2LTcuMy40LTEuNS42LTMgLjUtNC42di0zLjdjLS41LTQgMi44LTQgMi4yLjV6IiBmaWxsPSIjMDBkOGZmIi8+DQogICAgICAgICAgPGcgaWQ9Imc1NjYwIj4NCiAgICAgICAgICAgIDxnIGlkPSJnNTY1OCIgY2xhc3M9IndvYmJsZSIgc3R5bGU9InRyYW5zZm9ybS1vcmlnaW46NjdweCAwcHg7YW5pbWF0aW9uLWRlbGF5Oi4xMjVzIj4NCiAgICAgICAgICAgICAgPHBhdGggaWQ9InBhdGg1NjU2IiBkPSJNNjUuNjEtMzguMzRjLTEuMyAwLTIuNS42LTMuNSAxLjVhNiA2IDAgMCAwLTEuNiA2LjNjLjQgMS4yIDEgMi4zIDEuOCAzLjRsMS4yIDIuMmMuOCAxLjYgMS4yIDMuMyAxLjQgNXYzLjFjMCAxLS4zIDQuOSAxIDQuOSAxLjQgMCAxLjMtMy44IDEuMi00Ljl2LTIuNmMwLTEuNi40LTMuMiAxLTQuNy41LTEuMiAxLjItMi4zIDEuOC0zLjQuNy0xLjIgMS40LTIuNSAxLjYtMy45YTYgNiAwIDAgMC0xLjctNS40IDUuNiA1LjYgMCAwIDAtNC4yLTEuNSIgZmlsbD0iIzAwZDhmZiIvPg0KICAgICAgICAgICAgPC9nPg0KICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8ZyBpZD0iZzU2NjYiIHRyYW5zZm9ybT0icm90YXRlKDEyMCA0NS45IDQ4LjcpIj4NCiAgICAgICAgICAgIDxnIGlkPSJnNTY2NCIgY2xhc3M9IndvYmJsZSIgc3R5bGU9InRyYW5zZm9ybS1vcmlnaW46LTE0LjVweCA0OHB4O2FuaW1hdGlvbi1kZWxheTouNDVzIj4NCiAgICAgICAgICAgICAgPHBhdGggaWQ9InBhdGg1NjYyIiBkPSJNLTEzLjk4IDMwLjMyYy0xLjMgMC0yLjUuNi0zLjUgMS41YTYgNiAwIDAgMC0xLjYgNi4zYy40IDEuMiAxIDIuMyAxLjggMy40bDEuMiAyLjJjLjggMS42IDEuMiAzLjMgMS40IDV2My4xYzAgMS0uMyA0LjkgMSA0LjkgMS40IDAgMS4zLTMuOCAxLjItNC45di0yLjZjMC0xLjYuNC0zLjIgMS00LjcuNS0xLjIgMS4yLTIuMyAxLjgtMy40LjctMS4yIDEuNC0yLjUgMS42LTMuOWE2IDYgMCAwIDAtMS43LTUuNCA1LjYgNS42IDAgMCAwLTQuMi0xLjUiIGZpbGw9IiMwMGQ4ZmYiLz4NCiAgICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8L2c+DQogICAgICAgICAgPGcgaWQ9Imc1NjcyIiB0cmFuc2Zvcm09InJvdGF0ZSgtMTIwIDQ1LjkgNDguNykiPg0KICAgICAgICAgICAgPGcgaWQ9Imc1NjcwIiBjbGFzcz0id29iYmxlIiBzdHlsZT0idHJhbnNmb3JtLW9yaWdpbjo4NnB4IDg5cHg7YW5pbWF0aW9uLWRlbGF5Oi43OXMiPg0KICAgICAgICAgICAgICA8cGF0aCBpZD0icGF0aDU2NjgiIGQ9Ik04NC44NSA2NC43NWMtMS4zIDAtMi41LjYtMy41IDEuNWE2IDYgMCAwIDAtMS42IDYuM2MuNCAxLjIgMSAyLjMgMS44IDMuNGwxLjIgMi4yYy44IDEuNiAxLjIgMy4zIDEuNCA1djMuMWMwIDEtLjMgNC45IDEgNC45IDEuNCAwIDEuMy0zLjggMS4yLTQuOXYtMi42YzAtMS42LjQtMy4yIDEtNC43LjUtMS4yIDEuMi0yLjMgMS44LTMuNC43LTEuMiAxLjQtMi41IDEuNi0zLjlhNiA2IDAgMCAwLTEuNy01LjQgNS42IDUuNiAwIDAgMC00LjItMS41IiBmaWxsPSIjMDBkOGZmIi8+DQogICAgICAgICAgICA8L2c+DQogICAgICAgICAgPC9nPg0KICAgICAgICA8L2c+DQogICAgICA8L2c+DQogICAgICA8ZyBpZD0iZzU3MDAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDkgNikiPg0KICAgICAgICA8ZyBpZD0iZzU2OTgiIHRyYW5zZm9ybT0icm90YXRlKDEwKSI+DQogICAgICAgICAgPHBhdGggaWQ9InBhdGg1Njc4IiBkPSJNNTQuMjMgMjguMzVsLjEgNC44Yy4yIDIuNC44IDQuNSAyLjMgNi41IDEuMyAxLjggMyAzIDUgNGwyLjUgMS41YzIgMS41IDEuMyAzLjItMi4zIDEuMmwtMy0xLjZhMTEuNiAxMS42IDAgMCAwLTEwLjQtLjNsLTMuOSAyLjFjLTQgMS0yLjUtMSAxLTMuMSAzLTEuNSA1LjEtNC4xIDYtNy4zLjQtMS41LjYtMyAuNS00LjZ2LTMuN2MtLjUtNCAyLjgtNCAyLjIuNXoiIGZpbGw9IiMwMGQ4ZmYiLz4NCiAgICAgICAgICA8ZyBpZD0iZzU2ODQiPg0KICAgICAgICAgICAgPGcgaWQ9Imc1NjgyIiBjbGFzcz0id29iYmxlIiBzdHlsZT0idHJhbnNmb3JtLW9yaWdpbjogNTNweCAzM3B4OyI+DQogICAgICAgICAgICAgIDxwYXRoIGlkPSJwYXRoNTY4MCIgZD0iTTUyLjcxIDEwLjA5Yy0xLjMgMC0yLjUuNi0zLjUgMS41YTYgNiAwIDAgMC0xLjYgNi4zYy40IDEuMiAxIDIuMyAxLjggMy40bDEuMiAyLjJjLjggMS42IDEuMiAzLjMgMS40IDV2My4xYzAgMS0uMyA0LjkgMSA0LjkgMS40IDAgMS4zLTMuOCAxLjItNC45di0yLjZjMC0xLjYuNC0zLjIgMS00LjcuNS0xLjIgMS4yLTIuMyAxLjgtMy40LjctMS4yIDEuNC0yLjUgMS42LTMuOWE2IDYgMCAwIDAtMS43LTUuNCA1LjYgNS42IDAgMCAwLTQuMi0xLjUiIGZpbGw9IiMwMGQ4ZmYiLz4NCiAgICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8L2c+DQogICAgICAgICAgPGcgaWQ9Imc1NjkwIiB0cmFuc2Zvcm09InJvdGF0ZSgxMjAgNDUuOSA0OC43KSI+DQogICAgICAgICAgICA8ZyBpZD0iZzU2ODgiIGNsYXNzPSJ3b2JibGUiIHN0eWxlPSJ0cmFuc2Zvcm0tb3JpZ2luOjM3cHggNTBweDthbmltYXRpb24tZGVsYXk6LjMzMzNzIj4NCiAgICAgICAgICAgICAgPHBhdGggaWQ9InBhdGg1Njg2IiBkPSJNMzQuMzQgMTYuMzljLTEuMyAwLTIuNS42LTMuNSAxLjVhNiA2IDAgMCAwLTEuNiA2LjNjLjQgMS4yIDEgMi4zIDEuOCAzLjRsMS4yIDIuMmMuOCAxLjYgMS4yIDMuMyAxLjQgNXYzLjFjMCAxLS4zIDQuOSAxIDQuOSAxLjQgMCAxLjMtMy44IDEuMi00Ljl2LTIuNmMwLTEuNi40LTMuMiAxLTQuNy41LTEuMiAxLjItMi4zIDEuOC0zLjQuNy0xLjIgMS40LTIuNSAxLjYtMy45YTYgNiAwIDAgMC0xLjctNS40IDUuNiA1LjYgMCAwIDAtNC4yLTEuNSIgZmlsbD0iIzAwZDhmZiIvPg0KICAgICAgICAgICAgPC9nPg0KICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8ZyBpZD0iZzU2OTYiIHRyYW5zZm9ybT0icm90YXRlKC0xMjAgNDUuOSA0OC43KSI+DQogICAgICAgICAgICA8ZyBpZD0iZzU2OTQiIGNsYXNzPSJ3b2JibGUiIHN0eWxlPSJ0cmFuc2Zvcm0tb3JpZ2luOjQ5cHggNTNweDthbmltYXRpb24tZGVsYXk6LjY2NnMiPg0KICAgICAgICAgICAgICA8cGF0aCBpZD0icGF0aDU2OTIiIGQ9Ik00OS4wMSAyOC45MmMtMS4zIDAtMi41LjYtMy41IDEuNWE2IDYgMCAwIDAtMS42IDYuM2MuNCAxLjIgMSAyLjMgMS44IDMuNGwxLjIgMi4yYy44IDEuNiAxLjIgMy4zIDEuNCA1djMuMWMwIDEtLjMgNC45IDEgNC45IDEuNCAwIDEuMy0zLjggMS4yLTQuOXYtMi42YzAtMS42LjQtMy4yIDEtNC43LjUtMS4yIDEuMi0yLjMgMS44LTMuNC43LTEuMiAxLjQtMi41IDEuNi0zLjlhNiA2IDAgMCAwLTEuNy01LjQgNS42IDUuNiAwIDAgMC00LjItMS41IiBmaWxsPSIjMDBkOGZmIi8+DQogICAgICAgICAgICA8L2c+DQogICAgICAgICAgPC9nPg0KICAgICAgICA8L2c+DQogICAgICA8L2c+DQogICAgPC9nPg0KICA8L2c+DQogIDxzdHlsZSBpZD0ic3R5bGU1NjUwIj4NCiAgICBAa2V5ZnJhbWVzIHdvYmJsZSB7DQogICAgMCUsDQogICAgMzMlLA0KICAgIHRvIHsNCiAgICB0cmFuc2Zvcm06IHNjYWxlKDEpOw0KICAgIH0NCiAgICA1MCUgew0KICAgIHRyYW5zZm9ybTogc2NhbGUoMC45NSk7DQogICAgfQ0KICAgIDY2JSB7DQogICAgdHJhbnNmb3JtOiBzY2FsZSgxLjE1KTsNCiAgICB9DQogICAgfQ0KICAgIC53b2JibGUgew0KICAgIGFuaW1hdGlvbjogd29iYmxlIDFzIGVhc2UtaW4tb3V0IGluZmluaXRlOw0KICAgIHRyYW5zZm9ybS1vcmlnaW46IDUyLjNweCAzNC40NXB4Ow0KICAgIH0NCiAgPC9zdHlsZT4NCjwvc3ZnPg==) no-repeat center; + background: #fff + url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMy42NCAyNS4wMiIgaWQ9InN2ZzUxMDUiPg0KICA8ZyBpZD0ibGF5ZXIxIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtOTMuODEgLTExMS4xMykiPg0KICAgIDxnIGlkPSJnNTczNiIgdHJhbnNmb3JtPSJtYXRyaXgoLjI2NDU4IDAgMCAuMjY0NTggOTAuNjMgMTA4LjY1KSI+DQogICAgICA8cGF0aCBpZD0icGF0aDU2NTIiIGQ9Ik0xMi4zOSA0Ny4yOWwuMSAxOS4zYTI3Ljc0IDI3Ljc0IDAgMCAwIDEzLjkgMjMuOGwxNi44IDkuNmEyNy4zMiAyNy4zMiAwIDAgMCAyNy41LS4yTDg3LjM5IDkwQTI3LjQ0IDI3LjQ0IDAgMCAwIDEwMSA2Ni4wOWwtLjEtMTkuNEEyNy44NiAyNy44NiAwIDAgMCA4NyAyMi44OWwtMTYuNy05LjVhMjcuMzIgMjcuMzIgMCAwIDAtMjcuNS4ybC0xNi43IDkuOGEyNy41IDI3LjUgMCAwIDAtMTMuNyAyMy45IiBmaWxsPSIjMjQyMjY1Ii8+DQogICAgICA8ZyBpZD0iZzU2NzYiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE1IDkpIj4NCiAgICAgICAgPGcgaWQ9Imc1Njc0IiB0cmFuc2Zvcm09InJvdGF0ZSg1NCkiPg0KICAgICAgICAgIDxwYXRoIGlkPSJwYXRoNTY1NCIgZD0iTTY2Ljg5LTIwLjA0bC4xIDQuOGMuMiAyLjQuOCA0LjUgMi4zIDYuNSAxLjMgMS44IDMgMyA1IDRsMi41IDEuNWMyIDEuNSAxLjMgMy4yLTIuMyAxLjJsLTMtMS42YTExLjYgMTEuNiAwIDAgMC0xMC40LS4zbC0zLjkgMi4xYy00IDEtMi41LTEgMS0zLjEgMy0xLjUgNS4xLTQuMSA2LTcuMy40LTEuNS42LTMgLjUtNC42di0zLjdjLS41LTQgMi44LTQgMi4yLjV6IiBmaWxsPSIjMDBkOGZmIi8+DQogICAgICAgICAgPGcgaWQ9Imc1NjYwIj4NCiAgICAgICAgICAgIDxnIGlkPSJnNTY1OCIgY2xhc3M9IndvYmJsZSIgc3R5bGU9InRyYW5zZm9ybS1vcmlnaW46NjdweCAwcHg7YW5pbWF0aW9uLWRlbGF5Oi4xMjVzIj4NCiAgICAgICAgICAgICAgPHBhdGggaWQ9InBhdGg1NjU2IiBkPSJNNjUuNjEtMzguMzRjLTEuMyAwLTIuNS42LTMuNSAxLjVhNiA2IDAgMCAwLTEuNiA2LjNjLjQgMS4yIDEgMi4zIDEuOCAzLjRsMS4yIDIuMmMuOCAxLjYgMS4yIDMuMyAxLjQgNXYzLjFjMCAxLS4zIDQuOSAxIDQuOSAxLjQgMCAxLjMtMy44IDEuMi00Ljl2LTIuNmMwLTEuNi40LTMuMiAxLTQuNy41LTEuMiAxLjItMi4zIDEuOC0zLjQuNy0xLjIgMS40LTIuNSAxLjYtMy45YTYgNiAwIDAgMC0xLjctNS40IDUuNiA1LjYgMCAwIDAtNC4yLTEuNSIgZmlsbD0iIzAwZDhmZiIvPg0KICAgICAgICAgICAgPC9nPg0KICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8ZyBpZD0iZzU2NjYiIHRyYW5zZm9ybT0icm90YXRlKDEyMCA0NS45IDQ4LjcpIj4NCiAgICAgICAgICAgIDxnIGlkPSJnNTY2NCIgY2xhc3M9IndvYmJsZSIgc3R5bGU9InRyYW5zZm9ybS1vcmlnaW46LTE0LjVweCA0OHB4O2FuaW1hdGlvbi1kZWxheTouNDVzIj4NCiAgICAgICAgICAgICAgPHBhdGggaWQ9InBhdGg1NjYyIiBkPSJNLTEzLjk4IDMwLjMyYy0xLjMgMC0yLjUuNi0zLjUgMS41YTYgNiAwIDAgMC0xLjYgNi4zYy40IDEuMiAxIDIuMyAxLjggMy40bDEuMiAyLjJjLjggMS42IDEuMiAzLjMgMS40IDV2My4xYzAgMS0uMyA0LjkgMSA0LjkgMS40IDAgMS4zLTMuOCAxLjItNC45di0yLjZjMC0xLjYuNC0zLjIgMS00LjcuNS0xLjIgMS4yLTIuMyAxLjgtMy40LjctMS4yIDEuNC0yLjUgMS42LTMuOWE2IDYgMCAwIDAtMS43LTUuNCA1LjYgNS42IDAgMCAwLTQuMi0xLjUiIGZpbGw9IiMwMGQ4ZmYiLz4NCiAgICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8L2c+DQogICAgICAgICAgPGcgaWQ9Imc1NjcyIiB0cmFuc2Zvcm09InJvdGF0ZSgtMTIwIDQ1LjkgNDguNykiPg0KICAgICAgICAgICAgPGcgaWQ9Imc1NjcwIiBjbGFzcz0id29iYmxlIiBzdHlsZT0idHJhbnNmb3JtLW9yaWdpbjo4NnB4IDg5cHg7YW5pbWF0aW9uLWRlbGF5Oi43OXMiPg0KICAgICAgICAgICAgICA8cGF0aCBpZD0icGF0aDU2NjgiIGQ9Ik04NC44NSA2NC43NWMtMS4zIDAtMi41LjYtMy41IDEuNWE2IDYgMCAwIDAtMS42IDYuM2MuNCAxLjIgMSAyLjMgMS44IDMuNGwxLjIgMi4yYy44IDEuNiAxLjIgMy4zIDEuNCA1djMuMWMwIDEtLjMgNC45IDEgNC45IDEuNCAwIDEuMy0zLjggMS4yLTQuOXYtMi42YzAtMS42LjQtMy4yIDEtNC43LjUtMS4yIDEuMi0yLjMgMS44LTMuNC43LTEuMiAxLjQtMi41IDEuNi0zLjlhNiA2IDAgMCAwLTEuNy01LjQgNS42IDUuNiAwIDAgMC00LjItMS41IiBmaWxsPSIjMDBkOGZmIi8+DQogICAgICAgICAgICA8L2c+DQogICAgICAgICAgPC9nPg0KICAgICAgICA8L2c+DQogICAgICA8L2c+DQogICAgICA8ZyBpZD0iZzU3MDAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDkgNikiPg0KICAgICAgICA8ZyBpZD0iZzU2OTgiIHRyYW5zZm9ybT0icm90YXRlKDEwKSI+DQogICAgICAgICAgPHBhdGggaWQ9InBhdGg1Njc4IiBkPSJNNTQuMjMgMjguMzVsLjEgNC44Yy4yIDIuNC44IDQuNSAyLjMgNi41IDEuMyAxLjggMyAzIDUgNGwyLjUgMS41YzIgMS41IDEuMyAzLjItMi4zIDEuMmwtMy0xLjZhMTEuNiAxMS42IDAgMCAwLTEwLjQtLjNsLTMuOSAyLjFjLTQgMS0yLjUtMSAxLTMuMSAzLTEuNSA1LjEtNC4xIDYtNy4zLjQtMS41LjYtMyAuNS00LjZ2LTMuN2MtLjUtNCAyLjgtNCAyLjIuNXoiIGZpbGw9IiMwMGQ4ZmYiLz4NCiAgICAgICAgICA8ZyBpZD0iZzU2ODQiPg0KICAgICAgICAgICAgPGcgaWQ9Imc1NjgyIiBjbGFzcz0id29iYmxlIiBzdHlsZT0idHJhbnNmb3JtLW9yaWdpbjogNTNweCAzM3B4OyI+DQogICAgICAgICAgICAgIDxwYXRoIGlkPSJwYXRoNTY4MCIgZD0iTTUyLjcxIDEwLjA5Yy0xLjMgMC0yLjUuNi0zLjUgMS41YTYgNiAwIDAgMC0xLjYgNi4zYy40IDEuMiAxIDIuMyAxLjggMy40bDEuMiAyLjJjLjggMS42IDEuMiAzLjMgMS40IDV2My4xYzAgMS0uMyA0LjkgMSA0LjkgMS40IDAgMS4zLTMuOCAxLjItNC45di0yLjZjMC0xLjYuNC0zLjIgMS00LjcuNS0xLjIgMS4yLTIuMyAxLjgtMy40LjctMS4yIDEuNC0yLjUgMS42LTMuOWE2IDYgMCAwIDAtMS43LTUuNCA1LjYgNS42IDAgMCAwLTQuMi0xLjUiIGZpbGw9IiMwMGQ4ZmYiLz4NCiAgICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8L2c+DQogICAgICAgICAgPGcgaWQ9Imc1NjkwIiB0cmFuc2Zvcm09InJvdGF0ZSgxMjAgNDUuOSA0OC43KSI+DQogICAgICAgICAgICA8ZyBpZD0iZzU2ODgiIGNsYXNzPSJ3b2JibGUiIHN0eWxlPSJ0cmFuc2Zvcm0tb3JpZ2luOjM3cHggNTBweDthbmltYXRpb24tZGVsYXk6LjMzMzNzIj4NCiAgICAgICAgICAgICAgPHBhdGggaWQ9InBhdGg1Njg2IiBkPSJNMzQuMzQgMTYuMzljLTEuMyAwLTIuNS42LTMuNSAxLjVhNiA2IDAgMCAwLTEuNiA2LjNjLjQgMS4yIDEgMi4zIDEuOCAzLjRsMS4yIDIuMmMuOCAxLjYgMS4yIDMuMyAxLjQgNXYzLjFjMCAxLS4zIDQuOSAxIDQuOSAxLjQgMCAxLjMtMy44IDEuMi00Ljl2LTIuNmMwLTEuNi40LTMuMiAxLTQuNy41LTEuMiAxLjItMi4zIDEuOC0zLjQuNy0xLjIgMS40LTIuNSAxLjYtMy45YTYgNiAwIDAgMC0xLjctNS40IDUuNiA1LjYgMCAwIDAtNC4yLTEuNSIgZmlsbD0iIzAwZDhmZiIvPg0KICAgICAgICAgICAgPC9nPg0KICAgICAgICAgIDwvZz4NCiAgICAgICAgICA8ZyBpZD0iZzU2OTYiIHRyYW5zZm9ybT0icm90YXRlKC0xMjAgNDUuOSA0OC43KSI+DQogICAgICAgICAgICA8ZyBpZD0iZzU2OTQiIGNsYXNzPSJ3b2JibGUiIHN0eWxlPSJ0cmFuc2Zvcm0tb3JpZ2luOjQ5cHggNTNweDthbmltYXRpb24tZGVsYXk6LjY2NnMiPg0KICAgICAgICAgICAgICA8cGF0aCBpZD0icGF0aDU2OTIiIGQ9Ik00OS4wMSAyOC45MmMtMS4zIDAtMi41LjYtMy41IDEuNWE2IDYgMCAwIDAtMS42IDYuM2MuNCAxLjIgMSAyLjMgMS44IDMuNGwxLjIgMi4yYy44IDEuNiAxLjIgMy4zIDEuNCA1djMuMWMwIDEtLjMgNC45IDEgNC45IDEuNCAwIDEuMy0zLjggMS4yLTQuOXYtMi42YzAtMS42LjQtMy4yIDEtNC43LjUtMS4yIDEuMi0yLjMgMS44LTMuNC43LTEuMiAxLjQtMi41IDEuNi0zLjlhNiA2IDAgMCAwLTEuNy01LjQgNS42IDUuNiAwIDAgMC00LjItMS41IiBmaWxsPSIjMDBkOGZmIi8+DQogICAgICAgICAgICA8L2c+DQogICAgICAgICAgPC9nPg0KICAgICAgICA8L2c+DQogICAgICA8L2c+DQogICAgPC9nPg0KICA8L2c+DQogIDxzdHlsZSBpZD0ic3R5bGU1NjUwIj4NCiAgICBAa2V5ZnJhbWVzIHdvYmJsZSB7DQogICAgMCUsDQogICAgMzMlLA0KICAgIHRvIHsNCiAgICB0cmFuc2Zvcm06IHNjYWxlKDEpOw0KICAgIH0NCiAgICA1MCUgew0KICAgIHRyYW5zZm9ybTogc2NhbGUoMC45NSk7DQogICAgfQ0KICAgIDY2JSB7DQogICAgdHJhbnNmb3JtOiBzY2FsZSgxLjE1KTsNCiAgICB9DQogICAgfQ0KICAgIC53b2JibGUgew0KICAgIGFuaW1hdGlvbjogd29iYmxlIDFzIGVhc2UtaW4tb3V0IGluZmluaXRlOw0KICAgIHRyYW5zZm9ybS1vcmlnaW46IDUyLjNweCAzNC40NXB4Ow0KICAgIH0NCiAgPC9zdHlsZT4NCjwvc3ZnPg==) + no-repeat center; background-size: 200px; } -@media screen and (max-width: 640px ) { +@media screen and (max-width: 640px) { .demo-page__main { min-width: 100vw; } @@ -1688,7 +1716,10 @@ body:not(.loaded) .demo-page__main:after { font-family: Tahoma, Verdana, sans-serif; font-size: 14px; letter-spacing: 1px; - transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1); + transition: + background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1), + box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .flat-button.secondary { border: thin solid #4caf50; @@ -1710,11 +1741,14 @@ body:not(.loaded) .demo-page__main:after { height: 230px; background: #f7f7f7; overflow: hidden; - box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12); + box-shadow: + 0 2px 4px -1px rgba(0, 0, 0, 0.2), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14), + 0px 1px 10px 0px rgba(0, 0, 0, 0.12); z-index: 4; transition: height 1s cubic-bezier(0.23, 1, 0.32, 1) 0s; } -@media screen and (max-width: 640px ) { +@media screen and (max-width: 640px) { .demo-overlay { visibility: hidden; } @@ -1866,7 +1900,9 @@ body:not(.loaded) .demo-page__main:after { bottom: 0; right: 0; width: 320px; - font: 20px Tahoma, sans-serif; + font: + 20px Tahoma, + sans-serif; padding: 1em; box-sizing: border-box; } @@ -2070,7 +2106,7 @@ rect:not(.yfiles-node-highlight-inner-template):not(.yfiles-node-highlight-middl width: 60px; height: 60px; } -@media screen and (max-width: 960px ) { +@media screen and (max-width: 960px) { .demo-header__right > * { margin: 0 5px; width: 30px; @@ -2273,7 +2309,7 @@ body:not(.loaded) .demo-page__toolbar:after { .demo-page__toolbar .demo-toggle-button:checked:active + label { background-color: #b2b2b2; } -@media screen and (max-width: 640px ) { +@media screen and (max-width: 640px) { .demo-page__toolbar button.demo-unimportant { display: none; } @@ -2308,7 +2344,9 @@ body:not(.loaded) .demo-page__toolbar:after { .demo-page__toolbar .overflow-button { position: absolute; right: 4px; - background: #ebeef0 url("data:image/svg+xml,%3Csvg fill='%23666' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z'/%3E%3C/svg%3E") no-repeat center 4px; + background: #ebeef0 + url("data:image/svg+xml,%3Csvg fill='%23666' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z'/%3E%3C/svg%3E") + no-repeat center 4px; } .demo-page__toolbar .overflow-button.hidden { display: none; @@ -2327,7 +2365,10 @@ body:not(.loaded) .demo-page__toolbar:after { right: 0; width: 200px; background-color: #f7f7f7; - box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12); + box-shadow: + 0 2px 4px -1px rgba(0, 0, 0, 0.2), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14), + 0px 1px 10px 0px rgba(0, 0, 0, 0.12); visibility: collapse; z-index: 1000; } @@ -2372,26 +2413,54 @@ body:not(.loaded) .demo-page__toolbar:after { line-height: 24px; } } -.demo-page__toolbar .overflow-container__content > *:not(.labeled)[class^='demo-icon-'][title]::after, -.demo-page__toolbar .overflow-container__content > button:not(.labeled)[class^='demo-icon-'][title]::after, -.demo-page__toolbar .overflow-container__content > label:not(.labeled)[class^='demo-icon-'][title]::after, -.demo-page__toolbar .overflow-container__content > .demo-toggle-button + label:not(.labeled)[class^='demo-icon-'][title]::after, -.demo-page__toolbar .overflow-container__content > .demo-toggle-button.labeled + label:not(.labeled)[class^='demo-icon-'][title]::after, -.demo-page__toolbar .overflow-container__content > button.labeled:not(.labeled)[class^='demo-icon-'][title]::after, +.demo-page__toolbar + .overflow-container__content + > *:not(.labeled)[class^='demo-icon-'][title]::after, +.demo-page__toolbar + .overflow-container__content + > button:not(.labeled)[class^='demo-icon-'][title]::after, +.demo-page__toolbar + .overflow-container__content + > label:not(.labeled)[class^='demo-icon-'][title]::after, +.demo-page__toolbar + .overflow-container__content + > .demo-toggle-button + + label:not(.labeled)[class^='demo-icon-'][title]::after, +.demo-page__toolbar + .overflow-container__content + > .demo-toggle-button.labeled + + label:not(.labeled)[class^='demo-icon-'][title]::after, +.demo-page__toolbar + .overflow-container__content + > button.labeled:not(.labeled)[class^='demo-icon-'][title]::after, .demo-page__toolbar .overflow-container__content > *:not(.labeled)[data-command][title]::after, .demo-page__toolbar .overflow-container__content > button:not(.labeled)[data-command][title]::after, .demo-page__toolbar .overflow-container__content > label:not(.labeled)[data-command][title]::after, -.demo-page__toolbar .overflow-container__content > .demo-toggle-button + label:not(.labeled)[data-command][title]::after, -.demo-page__toolbar .overflow-container__content > .demo-toggle-button.labeled + label:not(.labeled)[data-command][title]::after, -.demo-page__toolbar .overflow-container__content > button.labeled:not(.labeled)[data-command][title]::after { +.demo-page__toolbar + .overflow-container__content + > .demo-toggle-button + + label:not(.labeled)[data-command][title]::after, +.demo-page__toolbar + .overflow-container__content + > .demo-toggle-button.labeled + + label:not(.labeled)[data-command][title]::after, +.demo-page__toolbar + .overflow-container__content + > button.labeled:not(.labeled)[data-command][title]::after { content: attr(title); display: block; } .demo-page__toolbar .overflow-container__content > *:not(.labeled)[title]::after, .demo-page__toolbar .overflow-container__content > button:not(.labeled)[title]::after, .demo-page__toolbar .overflow-container__content > label:not(.labeled)[title]::after, -.demo-page__toolbar .overflow-container__content > .demo-toggle-button + label:not(.labeled)[title]::after, -.demo-page__toolbar .overflow-container__content > .demo-toggle-button.labeled + label:not(.labeled)[title]::after, +.demo-page__toolbar + .overflow-container__content + > .demo-toggle-button + + label:not(.labeled)[title]::after, +.demo-page__toolbar + .overflow-container__content + > .demo-toggle-button.labeled + + label:not(.labeled)[title]::after, .demo-page__toolbar .overflow-container__content > button.labeled:not(.labeled)[title]::after, .demo-page__toolbar .overflow-container__content > *.labeled, .demo-page__toolbar .overflow-container__content > button.labeled, @@ -2422,7 +2491,10 @@ body:not(.loaded) .demo-page__toolbar:after { .demo-page__toolbar .overflow-container__content input[type='checkbox']:focus { outline: none; } -.demo-page__toolbar .overflow-container__content input[type='checkbox'].demo-toggle-button.labeled + label { +.demo-page__toolbar + .overflow-container__content + input[type='checkbox'].demo-toggle-button.labeled + + label { padding-left: 45px !important; line-height: 32px; white-space: nowrap; @@ -2431,7 +2503,10 @@ body:not(.loaded) .demo-page__toolbar:after { text-align: left; } @media screen and (max-height: 500px) { - .demo-page__toolbar .overflow-container__content input[type='checkbox'].demo-toggle-button.labeled + label { + .demo-page__toolbar + .overflow-container__content + input[type='checkbox'].demo-toggle-button.labeled + + label { line-height: 24px; } } @@ -2461,11 +2536,15 @@ body:not(.loaded) .demo-page__toolbar:after { height: 100%; width: 100%; line-height: 140%; - min-width: clamp(var(--description-min-width), var(--description-width), var(--description-max-width)); + min-width: clamp( + var(--description-min-width), + var(--description-width), + var(--description-max-width) + ); background-color: var(--description-color); z-index: 16; } -@media screen and (max-width: 640px ) { +@media screen and (max-width: 640px) { .demo-page__description { --description-width: 100vw; } @@ -2556,14 +2635,15 @@ body:not(.loaded) .demo-page__toolbar:after { right: 0; top: 0; cursor: pointer; - background: url("data:image/svg+xml,%3Csvg fill='%23343f4a' height='24' viewBox='2 2 20 20' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M7 7h10v10H7z' fill='%2300d8ff'/%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5v-9l6 4.5-6 4.5z'/%3E%3C/svg%3E") no-repeat calc(100% - 15px); + background: url("data:image/svg+xml,%3Csvg fill='%23343f4a' height='24' viewBox='2 2 20 20' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M7 7h10v10H7z' fill='%2300d8ff'/%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5v-9l6 4.5-6 4.5z'/%3E%3C/svg%3E") + no-repeat calc(100% - 15px); background-size: 35px; font-size: 1.1rem; line-height: 60px; text-align: right; padding-right: 60px; } -@media screen and (max-width: 640px ) { +@media screen and (max-width: 640px) { .demo-description__play-button { display: block; } @@ -2597,7 +2677,7 @@ body:not(.loaded) .demo-page__toolbar:after { top: 50%; line-height: 7px; } -@media screen and (max-width: 960px ) { +@media screen and (max-width: 960px) { .demo-description__drag-area--vertical { display: none; } @@ -2613,7 +2693,7 @@ body:not(.loaded) .demo-page__toolbar:after { top: 0; line-height: 9px; } -@media screen and (max-width: 960px ) { +@media screen and (max-width: 960px) { .demo-description__drag-area--horizontal { display: block; } @@ -2625,7 +2705,7 @@ body:not(.loaded) .demo-page__toolbar:after { .demo-description--draggable .demo-description__play-button { display: none; } -@media screen and (max-width: 960px ) { +@media screen and (max-width: 960px) { .demo-description--draggable { border-right: none; border-top: 9px solid #d5d7d8; @@ -2656,7 +2736,9 @@ body:not(.loaded) .demo-page__toolbar:after { line-height: 140%; width: var(--sidebar-width); border-left: 1px solid #d5d7d8; - transition: width 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, padding-left 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; + transition: + width 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, + padding-left 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; } .demo-main__sidebar--hidden { padding-left: 24px; @@ -2696,7 +2778,10 @@ body:not(.loaded) .demo-page__toolbar:after { background-image: url('../icons/single-arrow-right.svg'); background-size: 8px; background-repeat: no-repeat; - transition: top 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, background-color 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, transform 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; + transition: + top 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, + background-color 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, + transform 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; } .demo-sidebar__toggle__button:hover { background-color: #dedede; @@ -2738,7 +2823,9 @@ body:not(.loaded) .demo-page__toolbar:after { .demo-main__sidebar--hidden .demo-sidebar__toggle__title { opacity: 1; transform: translateX(0); - transition: transform 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0.1s, opacity 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; + transition: + transform 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0.1s, + opacity 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; } .demo-properties__settings fieldset { border: 1px solid #888; @@ -3069,14 +3156,14 @@ body.demo-tutorial { --description-color: none; grid-template-areas: 'header header' 'toolbar toolbar' 'sidebar content'; } -@media screen and (max-width: 960px ) { +@media screen and (max-width: 960px) { body.demo-tutorial { grid-template-columns: 1fr; grid-template-rows: 60px auto 1fr clamp(25%, var(--description-drag-height), 65%); grid-template-areas: 'header' 'toolbar' 'content' 'sidebar'; } } -@media screen and (max-width: 960px ) and (max-height: 500px) { +@media screen and (max-width: 960px) and (max-height: 500px) { body.demo-tutorial { grid-template-rows: 30px auto 1fr clamp(25%, var(--description-drag-height), 65%); } @@ -3158,7 +3245,7 @@ body.demo-tutorial { .demo-toolbar__button--forward:after { border-width: 2px 2px 0 0; } -@media screen and (max-width: 640px ) { +@media screen and (max-width: 640px) { .demo-toolbar__button--back, .demo-toolbar__button--forward { padding: 0; @@ -3174,7 +3261,7 @@ body.demo-tutorial { padding-right: 4px; } } -@media screen and (max-width: 960px ) { +@media screen and (max-width: 960px) { .demo-toolbar__tutorial-step-title { display: none; } @@ -3215,8 +3302,13 @@ body.demo-tutorial { border-radius: 6px; background-color: var(--toolbar-color); overflow-y: hidden; - box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12); - transition: opacity 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, max-height 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; + box-shadow: + 0 5px 5px -3px rgba(0, 0, 0, 0.2), + 0px 8px 10px 1px rgba(0, 0, 0, 0.14), + 0px 3px 14px 2px rgba(0, 0, 0, 0.12); + transition: + opacity 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, + max-height 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; } .demo-toolbar__tutorial-dropdown .tutorial-dropdown__item { display: block; @@ -3242,7 +3334,9 @@ body.demo-tutorial { margin-bottom: 1px; transform: translateX(-10px) rotate(45deg); opacity: 0; - transition: transform 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, opacity 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; + transition: + transform 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s, + opacity 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0s; } .demo-toolbar__tutorial-dropdown .tutorial-dropdown__item:hover { background-color: #29323c; @@ -3275,7 +3369,9 @@ body.demo-tutorial.theme-dark .demo-description__content .paragraph code { background: var(--description-kbd-background); border: 1px solid var(--description-kbd-border-color); border-radius: 0.25em; - box-shadow: 0 1px 0 var(--description-kbd-border-color), 0 0 0 0.1em var(--description-kbd-background) inset; + box-shadow: + 0 1px 0 var(--description-kbd-border-color), + 0 0 0 0.1em var(--description-kbd-background) inset; display: inline-block; font-size: 80%; padding: 0.1em 0.5em; diff --git a/demos/showcase/bpmn/BpmnEditorDemo.js b/demos/showcase/bpmn/BpmnEditorDemo.js index 6a19f9cb6..e3970a17d 100644 --- a/demos/showcase/bpmn/BpmnEditorDemo.js +++ b/demos/showcase/bpmn/BpmnEditorDemo.js @@ -214,7 +214,7 @@ function initializeGraphComponent() { graphComponent.clipboard = graphClipboard const decorator = graphComponent.graph.decorator - decorator.nodeDecorator.editLabelHelperDecorator.setFactory(node => { + decorator.nodeDecorator.editLabelHelperDecorator.setFactory((node) => { const style = node.style if (style.lookup && style.lookup(node, IEditLabelHelper.$class)) { return style.lookup(node, IEditLabelHelper.$class) @@ -266,7 +266,7 @@ function initializeInputMode() { const nodeDropInputMode = new NoNestedTablesDropInputMode() nodeDropInputMode.showPreview = true nodeDropInputMode.enabled = true - nodeDropInputMode.isGroupNodePredicate = draggedNode => + nodeDropInputMode.isGroupNodePredicate = (draggedNode) => !!draggedNode.lookup(ITable.$class) || draggedNode.tag === 'GroupNode' graphEditorInputMode.nodeDropInputMode = nodeDropInputMode @@ -347,7 +347,7 @@ function enableFolding() { graphComponent.graph.nodeDefaults.labels.layoutParameter = compositeLabelModel.createDefaultParameter() - manager.masterGraph.decorator.nodeDecorator.portCandidateProviderDecorator.setFactory(node => { + manager.masterGraph.decorator.nodeDecorator.portCandidateProviderDecorator.setFactory((node) => { if (node.lookup(ITable.$class)) { // Pool only have a dynamic PortCandidate return IPortCandidateProvider.fromCandidates([ @@ -528,7 +528,7 @@ function onFileSelected(e) { const isGraphML = file.name.toLowerCase().endsWith('.graphml') if (isBPMN || isGraphML) { const reader = new FileReader() - reader.onload = async ev => { + reader.onload = async (ev) => { // get the file content that shall be parsed by a BpmnDiParser or a GraphMLIOHandler const content = ev.target.result if (isBPMN) { @@ -563,7 +563,7 @@ function initializeContextMenu() { // Add event listeners to the various events that open the context menu. These listeners then // call the provided callback function which in turn asks the current ContextMenuInputMode if a // context menu should be shown at the current location. - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (inputMode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } @@ -920,8 +920,8 @@ class AdditionalEditLabelHelper extends EditLabelHelper { return InteriorLabelModel.NORTH } // eslint-disable-next-line arrow-body-style - const validParameters = parameters.filter(parameter => - owner.labels.every(label => { + const validParameters = parameters.filter((parameter) => + owner.labels.every((label) => { const bounds = label.layoutParameter.model.getGeometry(label, label.layoutParameter) return !parameter.model.getGeometry(label, parameter).bounds.intersects(bounds) }) diff --git a/demos/showcase/bpmn/BpmnEditorDemo.ts b/demos/showcase/bpmn/BpmnEditorDemo.ts index ae335d390..2ca77a05e 100644 --- a/demos/showcase/bpmn/BpmnEditorDemo.ts +++ b/demos/showcase/bpmn/BpmnEditorDemo.ts @@ -208,7 +208,7 @@ function initializeGraphComponent(): void { graphComponent.clipboard = graphClipboard const decorator = graphComponent.graph.decorator - decorator.nodeDecorator.editLabelHelperDecorator.setFactory(node => { + decorator.nodeDecorator.editLabelHelperDecorator.setFactory((node) => { const style = node.style as BpmnNodeStyle if (style.lookup && style.lookup(node, IEditLabelHelper.$class)) { return style.lookup(node, IEditLabelHelper.$class) as IEditLabelHelper @@ -341,7 +341,7 @@ function enableFolding(): void { graphComponent.graph.nodeDefaults.labels.layoutParameter = compositeLabelModel.createDefaultParameter() - manager.masterGraph.decorator.nodeDecorator.portCandidateProviderDecorator.setFactory(node => { + manager.masterGraph.decorator.nodeDecorator.portCandidateProviderDecorator.setFactory((node) => { if (node.lookup(ITable.$class)) { // Pool only have a dynamic PortCandidate return IPortCandidateProvider.fromCandidates([ @@ -516,7 +516,7 @@ function onFileSelected(e: Event): void { const isGraphML = file.name.toLowerCase().endsWith('.graphml') if (isBPMN || isGraphML) { const reader = new FileReader() - reader.onload = async ev => { + reader.onload = async (ev) => { // get the file content that shall be parsed by a BpmnDiParser or a GraphMLIOHandler const content = ev.target!.result as string if (isBPMN) { @@ -894,8 +894,8 @@ class AdditionalEditLabelHelper extends EditLabelHelper { return InteriorLabelModel.NORTH } // eslint-disable-next-line arrow-body-style - const validParameters = parameters.filter(parameter => - owner.labels.every(label => { + const validParameters = parameters.filter((parameter) => + owner.labels.every((label) => { const bounds = label.layoutParameter.model.getGeometry(label, label.layoutParameter) return !parameter.model.getGeometry(label, parameter).bounds.intersects(bounds) }) diff --git a/demos/showcase/bpmn/BpmnLayout.js b/demos/showcase/bpmn/BpmnLayout.js index 34a5a3afe..65318e742 100644 --- a/demos/showcase/bpmn/BpmnLayout.js +++ b/demos/showcase/bpmn/BpmnLayout.js @@ -200,12 +200,12 @@ export default class BpmnLayout extends BaseClass(ILayoutAlgorithm) { configurePartitionGrid(graph) { const grid = PartitionGrid.getPartitionGrid(graph) if (grid != null) { - grid.columns.forEach(columnObject => { + grid.columns.forEach((columnObject) => { const column = columnObject column.leftInset += this.laneInsets column.rightInset += this.laneInsets }) - grid.rows.forEach(rowObject => { + grid.rows.forEach((rowObject) => { const row = rowObject row.topInset += this.laneInsets row.bottomInset += this.laneInsets @@ -586,7 +586,7 @@ class BalancingPortOptimizer extends PortConstraintOptimizerBase { const criticalEdges = Maps.createHashedEdgeMap() // determine whether an edge crosses a swim-lane border and if so in which direction - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const originalEdge = this.getOriginalEdge(edge, ldp) // now we have a 'real' edge with valid source and target nodes @@ -605,12 +605,12 @@ class BalancingPortOptimizer extends PortConstraintOptimizerBase { }) // determine basic node alignment - graph.nodes.forEach(n => { + graph.nodes.forEach((n) => { const alignment = this.calculateLaneAlignment(n) this.node2LaneAlignment.set(n, alignment) }) - graph.nodes.forEach(n => { + graph.nodes.forEach((n) => { // sort the edges with the provided comparer n.sortInEdges(inEdgeOrder) n.sortOutEdges(outEdgeOrder) @@ -907,7 +907,7 @@ class BalancingPortOptimizer extends PortConstraintOptimizerBase { calculateLaneAlignment(n) { let toRightCount = 0 let toLeftCount = 0 - n.edges.forEach(edge => { + n.edges.forEach((edge) => { const crossing = this.edge2LaneCrossing.get(edge) if (n === edge.source) { if (crossing === LaneCrossing.TO_EAST) { @@ -1061,7 +1061,7 @@ class BpmnLabelProfitModel extends BaseClass(IProfitModel) { // diagonal candidates get a bit less profit profit = 0.9 } - node.edges.forEach(edge => { + node.edges.forEach((edge) => { const portLocation = edge.source === node ? this.graph.getSourcePointRel(edge) diff --git a/demos/showcase/bpmn/BpmnLayout.ts b/demos/showcase/bpmn/BpmnLayout.ts index 96ab7ba50..06de5310f 100644 --- a/demos/showcase/bpmn/BpmnLayout.ts +++ b/demos/showcase/bpmn/BpmnLayout.ts @@ -196,12 +196,12 @@ export default class BpmnLayout extends BaseClass(ILayoutAlgorithm) { configurePartitionGrid(graph: LayoutGraph): void { const grid = PartitionGrid.getPartitionGrid(graph) if (grid != null) { - grid.columns.forEach(columnObject => { + grid.columns.forEach((columnObject) => { const column = columnObject column.leftInset += this.laneInsets column.rightInset += this.laneInsets }) - grid.rows.forEach(rowObject => { + grid.rows.forEach((rowObject) => { const row = rowObject row.topInset += this.laneInsets row.bottomInset += this.laneInsets @@ -543,7 +543,7 @@ class BalancingPortOptimizer extends PortConstraintOptimizerBase { const criticalEdges = Maps.createHashedEdgeMap() // determine whether an edge crosses a swim-lane border and if so in which direction - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const originalEdge = this.getOriginalEdge(edge, ldp) // now we have a 'real' edge with valid source and target nodes @@ -562,12 +562,12 @@ class BalancingPortOptimizer extends PortConstraintOptimizerBase { }) // determine basic node alignment - graph.nodes.forEach(n => { + graph.nodes.forEach((n) => { const alignment = this.calculateLaneAlignment(n) this.node2LaneAlignment!.set(n, alignment) }) - graph.nodes.forEach(n => { + graph.nodes.forEach((n) => { // sort the edges with the provided comparer n.sortInEdges(inEdgeOrder) n.sortOutEdges(outEdgeOrder) @@ -846,7 +846,7 @@ class BalancingPortOptimizer extends PortConstraintOptimizerBase { calculateLaneAlignment(n: YNode): LaneAlignment { let toRightCount = 0 let toLeftCount = 0 - n.edges.forEach(edge => { + n.edges.forEach((edge) => { const crossing = this.edge2LaneCrossing!.get(edge) if (n === edge.source) { if (crossing === LaneCrossing.TO_EAST) { @@ -999,7 +999,7 @@ class BpmnLabelProfitModel extends BaseClass(IProfitModel) { // diagonal candidates get a bit less profit profit = 0.9 } - node.edges.forEach(edge => { + node.edges.forEach((edge) => { const portLocation = edge.source === node ? this.graph.getSourcePointRel(edge) diff --git a/demos/showcase/bpmn/BpmnLayoutData.js b/demos/showcase/bpmn/BpmnLayoutData.js index 55322b96c..4bd176a06 100644 --- a/demos/showcase/bpmn/BpmnLayoutData.js +++ b/demos/showcase/bpmn/BpmnLayoutData.js @@ -147,11 +147,11 @@ export default class BpmnLayoutData { data.addEdgeItemCollection(BpmnLayout.SEQUENCE_FLOW_EDGES_DP_KEY).delegate = isSequenceFlow // mark boundary interrupting edges for the BalancingPortOptimizer - data.addEdgeItemCollection(BpmnLayout.BOUNDARY_INTERRUPTING_EDGES_DP_KEY).delegate = edge => + data.addEdgeItemCollection(BpmnLayout.BOUNDARY_INTERRUPTING_EDGES_DP_KEY).delegate = (edge) => edge.sourcePort.style instanceof EventPortStyle // mark conversations, events and gateways so their port locations are adjusted - data.addNodeItemCollection(BpmnLayout.ADJUST_PORT_LOCATION_NODES_DP_KEY).delegate = node => + data.addNodeItemCollection(BpmnLayout.ADJUST_PORT_LOCATION_NODES_DP_KEY).delegate = (node) => node.style instanceof ConversationNodeStyle || node.style instanceof EventNodeStyle || node.style instanceof GatewayNodeStyle @@ -169,7 +169,7 @@ export default class BpmnLayoutData { markFixedAndAffectedItems(data, hierarchicLayoutData, selection, layoutOnlySelection) // mark associations and message flows as undirected so they have less impact on layering - hierarchicLayoutData.edgeDirectedness.delegate = edge => + hierarchicLayoutData.edgeDirectedness.delegate = (edge) => isMessageFlow(edge) || isAssociation(edge) ? 0 : 1 // add layer constraints for start events, sub processes and message flows @@ -197,7 +197,7 @@ const addLayerConstraints = ( // use layer constraints via HierarchicLayoutData const layerConstraints = hierarchicLayoutData.layerConstraints - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { if (isMessageFlow(edge) && !compactMessageFlowLayering) { // message flow layering compaction is disabled, we add a 'weak' same layer constraint, i.e. source node shall // be placed at least 0 layers above target node @@ -217,7 +217,7 @@ const addLayerConstraints = ( // if start events should be pulled to the first layer, add PlaceNodeAtTop constraint. if (startNodesFirst) { - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if ( node.style instanceof EventNodeStyle && node.style.characteristic === EventCharacteristic.START && @@ -244,8 +244,8 @@ function addAboveLayerConstraint(layerConstraints, edge, graph) { const targetNodes = [] collectLeafNodes(graph, sourceNode, sourceNodes) collectLeafNodes(graph, targetNode, targetNodes) - sourceNodes.forEach(source => { - targetNodes.forEach(target => { + sourceNodes.forEach((source) => { + targetNodes.forEach((target) => { layerConstraints.placeAbove(target, source) }) }) @@ -260,7 +260,7 @@ function addAboveLayerConstraint(layerConstraints, edge, graph) { function collectLeafNodes(graph, node, leafNodes) { const children = graph.getChildren(node) if (children.size > 0) { - children.forEach(child => { + children.forEach((child) => { collectLeafNodes(graph, child, leafNodes) }) } else { @@ -277,9 +277,9 @@ function addMinimumEdgeLength(hierarchicLayoutData, minimumEdgeLength) { // each edge should have a minimum length so that all its labels can be placed on it one // after another with a minimum label-to-label distance const minLabelToLabelDistance = 5 - hierarchicLayoutData.edgeLayoutDescriptors.delegate = edge => { + hierarchicLayoutData.edgeLayoutDescriptors.delegate = (edge) => { let minLength = 0 - edge.labels.forEach(label => { + edge.labels.forEach((label) => { const labelSize = label.layout.bounds minLength += Math.max(labelSize.width, labelSize.height) }) @@ -361,14 +361,14 @@ function isAssociation(edge) { */ function addNodeHalos(data, graph, selection, layoutOnlySelection) { const nodeHalos = new Mapper() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { let top = 0.0 let left = 0.0 let bottom = 0.0 let right = 0.0 // for each port with an EventPortStyle extend the node halo to cover the ports render size - node.ports.forEach(port => { + node.ports.forEach((port) => { if (port.style instanceof EventPortStyle) { const eventPortStyle = port.style const renderSize = eventPortStyle.renderSize @@ -383,7 +383,7 @@ function addNodeHalos(data, graph, selection, layoutOnlySelection) { // for each node without incoming or outgoing edges reserve space for laid out exterior labels if (graph.inDegree(node) === 0 || graph.outDegree(node) === 0) { const margin = 15 - node.labels.forEach(label => { + node.labels.forEach((label) => { if (isNodeLabelAffected(graph, selection, label, layoutOnlySelection)) { const labelBounds = label.layout.bounds if (graph.inDegree(node) === 0) { @@ -444,7 +444,7 @@ function addEdgeLabelPlacementDescriptors(data) { }) data.addLabelItemMapping( LayoutGraphAdapter.EDGE_LABEL_LAYOUT_PREFERRED_PLACEMENT_DESCRIPTOR_DP_KEY - ).delegate = label => { + ).delegate = (label) => { const labelOwner = label.owner const edgeType = labelOwner.style.type if ( @@ -469,7 +469,7 @@ function addEdgeLabelPlacementDescriptors(data) { function markFixedAndAffectedItems(data, hierarchicLayoutData, selection, layoutOnlySelection) { if (layoutOnlySelection) { const affectedEdges = IMapper.fromDelegate( - edge => + (edge) => selection.isSelected(edge) || selection.isSelected(edge.sourceNode) || selection.isSelected(edge.targetNode) @@ -478,13 +478,13 @@ function markFixedAndAffectedItems(data, hierarchicLayoutData, selection, layout data.addEdgeItemCollection(LayoutKeys.AFFECTED_EDGES_DP_KEY).mapper = affectedEdges // fix ports of unselected edges and edges at event ports - data.addEdgeItemMapping(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_DP_KEY).delegate = edge => { + data.addEdgeItemMapping(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_DP_KEY).delegate = (edge) => { if (!affectedEdges.get(edge) || edge.sourcePort.style instanceof EventPortStyle) { return PortConstraint.create(getSide(edge, true)) } return null } - data.addEdgeItemMapping(PortConstraintKeys.TARGET_PORT_CONSTRAINT_DP_KEY).delegate = edge => { + data.addEdgeItemMapping(PortConstraintKeys.TARGET_PORT_CONSTRAINT_DP_KEY).delegate = (edge) => { if (!affectedEdges.get(edge)) { return PortConstraint.create(getSide(edge, false)) } @@ -500,7 +500,7 @@ function markFixedAndAffectedItems(data, hierarchicLayoutData, selection, layout } return null } - data.addLabelItemCollection(BpmnLayout.AFFECTED_LABELS_DP_KEY).delegate = label => { + data.addLabelItemCollection(BpmnLayout.AFFECTED_LABELS_DP_KEY).delegate = (label) => { if (label.owner instanceof IEdge) { return affectedEdges.get(label.owner) } @@ -515,14 +515,14 @@ function markFixedAndAffectedItems(data, hierarchicLayoutData, selection, layout } } else { // fix source port of edges at event ports - data.addEdgeItemMapping(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_DP_KEY).delegate = edge => { + data.addEdgeItemMapping(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_DP_KEY).delegate = (edge) => { if (edge.sourcePort.style instanceof EventPortStyle) { return PortConstraint.create(getSide(edge, true)) } return null } - data.addLabelItemCollection(BpmnLayout.AFFECTED_LABELS_DP_KEY).delegate = label => { + data.addLabelItemCollection(BpmnLayout.AFFECTED_LABELS_DP_KEY).delegate = (label) => { if (label.owner instanceof IEdge) { return true } diff --git a/demos/showcase/bpmn/BpmnLayoutData.ts b/demos/showcase/bpmn/BpmnLayoutData.ts index 7d27547f3..bf07370b1 100644 --- a/demos/showcase/bpmn/BpmnLayoutData.ts +++ b/demos/showcase/bpmn/BpmnLayoutData.ts @@ -188,7 +188,7 @@ const addLayerConstraints = ( // use layer constraints via HierarchicLayoutData const layerConstraints = hierarchicLayoutData.layerConstraints - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { if (isMessageFlow(edge) && !compactMessageFlowLayering) { // message flow layering compaction is disabled, we add a 'weak' same layer constraint, i.e. source node shall // be placed at least 0 layers above target node @@ -208,7 +208,7 @@ const addLayerConstraints = ( // if start events should be pulled to the first layer, add PlaceNodeAtTop constraint. if (startNodesFirst) { - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if ( node.style instanceof EventNodeStyle && node.style.characteristic === EventCharacteristic.START && @@ -236,8 +236,8 @@ function addAboveLayerConstraint( const targetNodes: INode[] = [] collectLeafNodes(graph, sourceNode, sourceNodes) collectLeafNodes(graph, targetNode, targetNodes) - sourceNodes.forEach(source => { - targetNodes.forEach(target => { + sourceNodes.forEach((source) => { + targetNodes.forEach((target) => { layerConstraints.placeAbove(target, source) }) }) @@ -249,7 +249,7 @@ function addAboveLayerConstraint( function collectLeafNodes(graph: IGraph, node: INode, leafNodes: INode[]): void { const children = graph.getChildren(node) if (children.size > 0) { - children.forEach(child => { + children.forEach((child) => { collectLeafNodes(graph, child, leafNodes) }) } else { @@ -269,7 +269,7 @@ function addMinimumEdgeLength( const minLabelToLabelDistance = 5 hierarchicLayoutData.edgeLayoutDescriptors.delegate = (edge: IEdge) => { let minLength = 0 - edge.labels.forEach(label => { + edge.labels.forEach((label) => { const labelSize = label.layout.bounds minLength += Math.max(labelSize.width, labelSize.height) }) @@ -344,14 +344,14 @@ function addNodeHalos( layoutOnlySelection: boolean ): void { const nodeHalos: Mapper = new Mapper() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { let top = 0.0 let left = 0.0 let bottom = 0.0 let right = 0.0 // for each port with an EventPortStyle extend the node halo to cover the ports render size - node.ports.forEach(port => { + node.ports.forEach((port) => { if (port.style instanceof EventPortStyle) { const eventPortStyle = port.style const renderSize = eventPortStyle.renderSize @@ -366,7 +366,7 @@ function addNodeHalos( // for each node without incoming or outgoing edges reserve space for laid out exterior labels if (graph.inDegree(node) === 0 || graph.outDegree(node) === 0) { const margin = 15 - node.labels.forEach(label => { + node.labels.forEach((label) => { if (isNodeLabelAffected(graph, selection, label, layoutOnlySelection)) { const labelBounds = label.layout.bounds if (graph.inDegree(node) === 0) { diff --git a/demos/showcase/bpmn/BpmnPopupSupport.js b/demos/showcase/bpmn/BpmnPopupSupport.js index 51bd31894..c1125f9ce 100644 --- a/demos/showcase/bpmn/BpmnPopupSupport.js +++ b/demos/showcase/bpmn/BpmnPopupSupport.js @@ -777,7 +777,7 @@ export default class BpmnPopupSupport { ) }) - document.querySelectorAll('.close-popup-button').forEach(btn => + document.querySelectorAll('.close-popup-button').forEach((btn) => btn.addEventListener('click', () => { this.hidePropertyPopup() }) @@ -854,7 +854,7 @@ export default class BpmnPopupSupport { * @param {!Class} enumType */ function populateComboBox(comboBox, enumType) { - Enum.getValueNames(enumType).forEach(name => { + Enum.getValueNames(enumType).forEach((name) => { const option = document.createElement('option') option.innerText = getFriendlyName(name) option.value = name diff --git a/demos/showcase/bpmn/BpmnPopupSupport.ts b/demos/showcase/bpmn/BpmnPopupSupport.ts index 59de319dd..4f4071421 100644 --- a/demos/showcase/bpmn/BpmnPopupSupport.ts +++ b/demos/showcase/bpmn/BpmnPopupSupport.ts @@ -785,7 +785,7 @@ export default class BpmnPopupSupport { ) }) - document.querySelectorAll('.close-popup-button').forEach(btn => + document.querySelectorAll('.close-popup-button').forEach((btn) => btn.addEventListener('click', () => { this.hidePropertyPopup() }) @@ -863,7 +863,7 @@ export default class BpmnPopupSupport { * Adds options to the given combo box for the content of the enum type. */ function populateComboBox(comboBox: HTMLSelectElement, enumType: Class): void { - Enum.getValueNames(enumType).forEach(name => { + Enum.getValueNames(enumType).forEach((name) => { const option = document.createElement('option') option.innerText = getFriendlyName(name) option.value = name diff --git a/demos/showcase/bpmn/bpmn-di.js b/demos/showcase/bpmn/bpmn-di.js index ab7256280..6dba977a0 100644 --- a/demos/showcase/bpmn/bpmn-di.js +++ b/demos/showcase/bpmn/bpmn-di.js @@ -288,23 +288,23 @@ export class BpmnDiParser { // Get the Diagram to load let diaToLoad - return new Promise(resolve => { + return new Promise((resolve) => { if (selectDiagramCallback) { selectDiagramCallback( - topLevelDiagrams.map(d => ({ + topLevelDiagrams.map((d) => ({ name: d.name, nodeCount: d.plane.listOfShapes.size, edgeCount: d.plane.listOfEdges.size })) - ).then(chosenName => { - diaToLoad = topLevelDiagrams.find(d => d.name === chosenName) + ).then((chosenName) => { + diaToLoad = topLevelDiagrams.find((d) => d.name === chosenName) resolve(diaToLoad) }) } else { diaToLoad = topLevelDiagrams.at(0) ?? null resolve(diaToLoad) } - }).then(diaToLoad => { + }).then((diaToLoad) => { // Loads the selected Diagram into the supplied Graph if (diaToLoad) { this.loadDiagram(diaToLoad, null) @@ -595,14 +595,14 @@ export class BpmnDiParser { } const groupNodes = this.masterGraph.nodes - .filter(node => node.style instanceof GroupNodeStyle) + .filter((node) => node.style instanceof GroupNodeStyle) .toList() for (const groupNode of groupNodes) { if (this.masterGraph.getChildren(groupNode).size === 0) { const newChildren = this.masterGraph .getChildren(this.masterGraph.getParent(groupNode)) .filter( - child => + (child) => child !== groupNode && groupNode.layout.contains(child.layout.topLeft) && groupNode.layout.contains(child.layout.bottomRight) @@ -769,7 +769,7 @@ export class BpmnDiParser { return } const labelingData = new LabelingData() - labelingData.edgeLabelPreferredPlacement.delegate = label => { + labelingData.edgeLabelPreferredPlacement.delegate = (label) => { if (/yes|no/i.test(label.text)) { const preferredPlacementDescriptor = new PreferredPlacementDescriptor() preferredPlacementDescriptor.placeAlongEdge = LabelPlacements.AT_SOURCE @@ -1433,7 +1433,7 @@ export class BpmnDiParser { this.masterGraph.setStyle(node, partStyle) const table = partStyle.tableNodeStyle.table - if (shape.isHorizontal) { + if (shape.isHorizontal ?? false) { const row = table.rootRow.childRows.first() BpmnDiParser.addTableLabel(table, row, shape) } else { @@ -2148,7 +2148,7 @@ export class BpmnDiParser { } let layout = Rect.EMPTY - let isHorizontal = false + let isHorizontal = null let multipleInstance = false let tableShape = this.getShape(element, plane) @@ -2171,20 +2171,30 @@ export class BpmnDiParser { if (tableShape) { // table has a shape itself so we use its layout to initialize the table layout = new Rect(tableShape.x, tableShape.y, tableShape.width, tableShape.height) - isHorizontal = tableShape.isHorizontal - } else { + if (tableShape.isHorizontal != null) { + isHorizontal = tableShape.isHorizontal + } + } + const calculateRect = layout.isEmpty + if (calculateRect || isHorizontal == null) { // check the child lanes for their shapes for (const lane of element.getChildren('lane')) { const laneShape = this.getShape(lane, plane) if (laneShape) { - layout = Rect.add( - layout, - new Rect(laneShape.x, laneShape.y, laneShape.width, laneShape.height) - ) - isHorizontal = laneShape.isHorizontal + if (calculateRect) { + layout = Rect.add( + layout, + new Rect(laneShape.x, laneShape.y, laneShape.width, laneShape.height) + ) + } + if (isHorizontal == null && laneShape.isHorizontal != null) { + isHorizontal = laneShape.isHorizontal + } } } } + // fallback + isHorizontal = isHorizontal ?? false let node if (!layout.isEmpty) { let table @@ -2227,7 +2237,7 @@ export class BpmnDiParser { ? table.rootRow.childRows.at(0) ?? table.rootRow : table.rootColumn.childColumns.at(0) ?? table.rootColumn if (tableShape) { - parentStripe = this.addToTable(tableShape, table, node, parentStripe) + parentStripe = this.addToTable(tableShape, table, node, parentStripe, isHorizontal) } element.node = node @@ -2235,17 +2245,17 @@ export class BpmnDiParser { parent.node = node } - this.addChildLanes(element, table, parentStripe, plane, node) + this.addChildLanes(element, table, parentStripe, plane, node, isHorizontal) // Resize the root row/column after adding a column/row with insets if (isHorizontal) { const max = table.rootRow.leaves - .map(s => s.layout.x - table.layout.x + s.insets.left) + .map((s) => s.layout.x - table.layout.x + s.insets.left) .reduce((acc, val) => Math.max(acc, val), Number.MIN_VALUE) table.setSize(table.rootColumn.childColumns.first(), node.layout.width - max) } else { const max = table.rootColumn.leaves - .map(s => s.layout.y - table.layout.y + s.insets.top) + .map((s) => s.layout.y - table.layout.y + s.insets.top) .reduce((acc, val) => Math.max(acc, val), Number.MIN_VALUE) table.setSize(table.rootRow.childRows.first(), node.layout.height - max) } @@ -2276,12 +2286,13 @@ export class BpmnDiParser { * @param {!IStripe} parentStripe * @param {!BpmnPlane} plane * @param {!INode} node + * @param {boolean} isHorizontal */ - addChildLanes(element, table, parentStripe, plane, node) { + addChildLanes(element, table, parentStripe, plane, node, isHorizontal) { for (const lane of element.getChildren('lane')) { const laneShape = this.getShape(lane, plane) if (laneShape) { - const addedStripe = this.addToTable(laneShape, table, node, parentStripe) + const addedStripe = this.addToTable(laneShape, table, node, parentStripe, isHorizontal) for (const refElement of lane.getChildren('flowNodeRef')) { const bpmnElement = { value: null } if (refElement.value && this.tryGetElementForId(refElement.value, bpmnElement)) { @@ -2290,7 +2301,7 @@ export class BpmnDiParser { } const childLaneSet = lane.getChild('childLaneSet') if (childLaneSet) { - this.addChildLanes(childLaneSet, table, addedStripe, plane, node) + this.addChildLanes(childLaneSet, table, addedStripe, plane, node, isHorizontal) } } } @@ -2302,20 +2313,21 @@ export class BpmnDiParser { * @param {!ITable} table * @param {!INode} node * @param {!IStripe} parentStripe + * @param {boolean} isHorizontal * @returns {!IStripe} */ - addToTable(shape, table, node, parentStripe) { + addToTable(shape, table, node, parentStripe, isHorizontal) { // lane element const element = shape.element // Link the node to the BpmnElement of the lane element.node = node - if (shape.isHorizontal) { + if (isHorizontal) { const parentRow = parentStripe instanceof IRow ? parentStripe : null // getIndex const index = parentRow - ? parentRow.childRows.filter(siblingRow => siblingRow.tag.y < shape.y).size + ? parentRow.childRows.filter((siblingRow) => siblingRow.tag.y < shape.y).size : -1 const row = table.createChildRow(parentRow, shape.height, null, null, null, null, index) @@ -2327,7 +2339,7 @@ export class BpmnDiParser { const parentCol = IColumn.isInstance(parentStripe) ? parentStripe : null // getIndex const index = parentCol - ? parentCol.childColumns.filter(siblingCol => siblingCol.tag.x < shape.x).size + ? parentCol.childColumns.filter((siblingCol) => siblingCol.tag.x < shape.x).size : -1 const col = table.createChildColumn(parentCol, shape.width, null, null, null, null, index) @@ -2343,7 +2355,7 @@ export class BpmnDiParser { * @returns {!PoolNodeStyle} */ static createTable(shape) { - const poolNodeStyle = BpmnDiParser.createPoolNodeStyle(shape.isHorizontal) + const poolNodeStyle = BpmnDiParser.createPoolNodeStyle(shape.isHorizontal ?? false) const table = poolNodeStyle.tableNodeStyle.table // Create first row & column @@ -3500,7 +3512,7 @@ export class BpmnNamespaceManager { */ static attributesInNamespace(list, nameSpace) { // Some Attributes do not have a namespace declared explicitly. Since we test the parent for the correct namespace this is ok. - return list.filter(el => !el.namespaceURI || el.namespaceURI === nameSpace) + return list.filter((el) => !el.namespaceURI || el.namespaceURI === nameSpace) } /** @@ -3635,7 +3647,7 @@ export class BpmnShape { id = null // Get all additional Attributes // Attribute which indicates the orientation if this is a pool or lane - isHorizontal = false + isHorizontal = null // String id of the expansion state of this shape isExpanded = null // Determines, if a marker should be depicted on the shape for exclusive Gateways. @@ -3666,9 +3678,12 @@ export class BpmnShape { this.id = BpmnNamespaceManager.getAttributeValue(xShape, BpmnNamespaceManager.BPMN_DI, 'id') - this.isHorizontal = convertToBoolean( - BpmnNamespaceManager.getAttributeValue(xShape, BpmnNamespaceManager.BPMN_DI, 'isHorizontal') + const isHorizontalString = BpmnNamespaceManager.getAttributeValue( + xShape, + BpmnNamespaceManager.BPMN_DI, + 'isHorizontal' ) + this.isHorizontal = isHorizontalString != null ? convertToBoolean(isHorizontalString) : null this.isExpanded = BpmnNamespaceManager.getAttributeValue( xShape, BpmnNamespaceManager.BPMN_DI, @@ -3843,10 +3858,6 @@ export class MultiLabelFolderNodeConverter extends DefaultFolderNodeConverter { */ copyLabels = false - constructor() { - super() - } - /** * @param {!FolderNodeState} state * @param {!IFoldingView} foldingView diff --git a/demos/showcase/bpmn/bpmn-di.ts b/demos/showcase/bpmn/bpmn-di.ts index aca8715ad..b2467a57d 100644 --- a/demos/showcase/bpmn/bpmn-di.ts +++ b/demos/showcase/bpmn/bpmn-di.ts @@ -282,20 +282,20 @@ export class BpmnDiParser { return new Promise((resolve: (value?: BpmnDiagram | null) => void): void => { if (selectDiagramCallback) { selectDiagramCallback( - topLevelDiagrams.map(d => ({ + topLevelDiagrams.map((d) => ({ name: d.name, nodeCount: d.plane!.listOfShapes.size, edgeCount: d.plane!.listOfEdges.size })) ).then((chosenName: string) => { - diaToLoad = topLevelDiagrams.find(d => d.name === chosenName) + diaToLoad = topLevelDiagrams.find((d) => d.name === chosenName) resolve(diaToLoad) }) } else { diaToLoad = topLevelDiagrams.at(0) ?? null resolve(diaToLoad) } - }).then(diaToLoad => { + }).then((diaToLoad) => { // Loads the selected Diagram into the supplied Graph if (diaToLoad) { this.loadDiagram(diaToLoad, null) @@ -586,14 +586,14 @@ export class BpmnDiParser { } const groupNodes = this.masterGraph.nodes - .filter(node => node.style instanceof GroupNodeStyle) + .filter((node) => node.style instanceof GroupNodeStyle) .toList() for (const groupNode of groupNodes) { if (this.masterGraph.getChildren(groupNode).size === 0) { const newChildren = this.masterGraph .getChildren(this.masterGraph.getParent(groupNode)) .filter( - child => + (child) => child !== groupNode && groupNode.layout.contains(child.layout.topLeft) && groupNode.layout.contains(child.layout.bottomRight) @@ -1417,7 +1417,7 @@ export class BpmnDiParser { this.masterGraph.setStyle(node, partStyle) const table = partStyle.tableNodeStyle.table - if (shape.isHorizontal) { + if (shape.isHorizontal ?? false) { const row = table.rootRow.childRows.first() BpmnDiParser.addTableLabel(table, row, shape) } else { @@ -2091,7 +2091,7 @@ export class BpmnDiParser { } let layout: Rect = Rect.EMPTY - let isHorizontal = false + let isHorizontal: boolean | null = null let multipleInstance = false let tableShape: BpmnShape | null = this.getShape(element, plane) @@ -2115,20 +2115,30 @@ export class BpmnDiParser { if (tableShape) { // table has a shape itself so we use its layout to initialize the table layout = new Rect(tableShape.x, tableShape.y, tableShape.width, tableShape.height) - isHorizontal = tableShape.isHorizontal - } else { + if (tableShape.isHorizontal != null) { + isHorizontal = tableShape.isHorizontal + } + } + const calculateRect = layout.isEmpty + if (calculateRect || isHorizontal == null) { // check the child lanes for their shapes for (const lane of element.getChildren('lane')) { const laneShape = this.getShape(lane, plane) if (laneShape) { - layout = Rect.add( - layout, - new Rect(laneShape.x, laneShape.y, laneShape.width, laneShape.height) - ) - isHorizontal = laneShape.isHorizontal + if (calculateRect) { + layout = Rect.add( + layout, + new Rect(laneShape.x, laneShape.y, laneShape.width, laneShape.height) + ) + } + if (isHorizontal == null && laneShape.isHorizontal != null) { + isHorizontal = laneShape.isHorizontal + } } } } + // fallback + isHorizontal = isHorizontal ?? false let node: INode if (!layout.isEmpty) { let table: ITable @@ -2171,7 +2181,7 @@ export class BpmnDiParser { ? table.rootRow.childRows.at(0) ?? table.rootRow : table.rootColumn.childColumns.at(0) ?? table.rootColumn if (tableShape) { - parentStripe = this.addToTable(tableShape, table, node, parentStripe) + parentStripe = this.addToTable(tableShape, table, node, parentStripe, isHorizontal) } element.node = node @@ -2179,7 +2189,7 @@ export class BpmnDiParser { parent.node = node } - this.addChildLanes(element, table, parentStripe, plane, node) + this.addChildLanes(element, table, parentStripe, plane, node, isHorizontal) // Resize the root row/column after adding a column/row with insets if (isHorizontal) { @@ -2219,12 +2229,13 @@ export class BpmnDiParser { table: ITable, parentStripe: IStripe, plane: BpmnPlane, - node: INode + node: INode, + isHorizontal: boolean ): void { for (const lane of element.getChildren('lane')) { const laneShape = this.getShape(lane, plane) if (laneShape) { - const addedStripe = this.addToTable(laneShape, table, node, parentStripe) + const addedStripe = this.addToTable(laneShape, table, node, parentStripe, isHorizontal) for (const refElement of lane.getChildren('flowNodeRef')) { const bpmnElement = { value: null } if (refElement.value && this.tryGetElementForId(refElement.value, bpmnElement)) { @@ -2233,7 +2244,7 @@ export class BpmnDiParser { } const childLaneSet = lane.getChild('childLaneSet') if (childLaneSet) { - this.addChildLanes(childLaneSet, table, addedStripe, plane, node) + this.addChildLanes(childLaneSet, table, addedStripe, plane, node, isHorizontal) } } } @@ -2242,18 +2253,24 @@ export class BpmnDiParser { /** * Adds the given lane to the appropriate table (pool), or creates a new one */ - private addToTable(shape: BpmnShape, table: ITable, node: INode, parentStripe: IStripe): IStripe { + private addToTable( + shape: BpmnShape, + table: ITable, + node: INode, + parentStripe: IStripe, + isHorizontal: boolean + ): IStripe { // lane element const element = shape.element! // Link the node to the BpmnElement of the lane element.node = node - if (shape.isHorizontal) { + if (isHorizontal) { const parentRow = parentStripe instanceof IRow ? parentStripe : null // getIndex const index = parentRow - ? parentRow.childRows.filter(siblingRow => siblingRow.tag.y < shape.y).size + ? parentRow.childRows.filter((siblingRow) => siblingRow.tag.y < shape.y).size : -1 const row = table.createChildRow(parentRow, shape.height, null, null, null, null, index) @@ -2265,7 +2282,7 @@ export class BpmnDiParser { const parentCol = IColumn.isInstance(parentStripe) ? parentStripe : null // getIndex const index = parentCol - ? parentCol.childColumns.filter(siblingCol => siblingCol.tag.x < shape.x).size + ? parentCol.childColumns.filter((siblingCol) => siblingCol.tag.x < shape.x).size : -1 const col = table.createChildColumn(parentCol, shape.width, null, null, null, null, index) @@ -2279,7 +2296,7 @@ export class BpmnDiParser { * Creates table (participant/pool) */ private static createTable(shape: BpmnShape): PoolNodeStyle { - const poolNodeStyle = BpmnDiParser.createPoolNodeStyle(shape.isHorizontal) + const poolNodeStyle = BpmnDiParser.createPoolNodeStyle(shape.isHorizontal ?? false) const table = poolNodeStyle.tableNodeStyle.table // Create first row & column @@ -3419,7 +3436,7 @@ export class BpmnNamespaceManager { */ static attributesInNamespace(list: IEnumerable, nameSpace: string): IEnumerable { // Some Attributes do not have a namespace declared explicitly. Since we test the parent for the correct namespace this is ok. - return list.filter(el => !el.namespaceURI || el.namespaceURI === nameSpace) + return list.filter((el) => !el.namespaceURI || el.namespaceURI === nameSpace) } /** @@ -3556,7 +3573,7 @@ export class BpmnShape { id: string = null! // Get all additional Attributes // Attribute which indicates the orientation if this is a pool or lane - isHorizontal = false + isHorizontal: boolean | null = null // String id of the expansion state of this shape isExpanded: string = null! // Determines, if a marker should be depicted on the shape for exclusive Gateways. @@ -3587,9 +3604,12 @@ export class BpmnShape { this.id = BpmnNamespaceManager.getAttributeValue(xShape, BpmnNamespaceManager.BPMN_DI, 'id') - this.isHorizontal = convertToBoolean( - BpmnNamespaceManager.getAttributeValue(xShape, BpmnNamespaceManager.BPMN_DI, 'isHorizontal') + const isHorizontalString = BpmnNamespaceManager.getAttributeValue( + xShape, + BpmnNamespaceManager.BPMN_DI, + 'isHorizontal' ) + this.isHorizontal = isHorizontalString != null ? convertToBoolean(isHorizontalString) : null this.isExpanded = BpmnNamespaceManager.getAttributeValue( xShape, BpmnNamespaceManager.BPMN_DI, @@ -3762,10 +3782,6 @@ export class MultiLabelFolderNodeConverter extends DefaultFolderNodeConverter { */ copyLabels = false - constructor() { - super() - } - updateFolderNodeState( state: FolderNodeState, foldingView: IFoldingView, diff --git a/demos/showcase/bpmn/bpmn-view.js b/demos/showcase/bpmn/bpmn-view.js index cfc7f87cd..15a83a6d5 100644 --- a/demos/showcase/bpmn/bpmn-view.js +++ b/demos/showcase/bpmn/bpmn-view.js @@ -505,7 +505,7 @@ class ScalingLabelModel extends BaseClass(ILabelModel) { * @param {!Class.} type the type for which an instance shall be returned * @returns {?T} an instance that is assignable to type or `null` * @see Specified by {@link ILookup.lookup}. - * @template {*} T + * @template T */ lookup(type) { return ScalingLabelModel.STRETCH_MODEL.lookup(type) @@ -743,7 +743,7 @@ export class BpmnPortCandidateProvider extends PortCandidateProviderBase { const portCandidates = new List() // provide existing ports as candidates only if they use EventPortStyle and have no edges attached to them. - node.ports.forEach(port => { + node.ports.forEach((port) => { if (port.style instanceof EventPortStyle && context.graph.edgesAt(port).size === 0) { portCandidates.add(new DefaultPortCandidate(port)) } @@ -920,7 +920,7 @@ class AspectRatioHandle extends BaseClass(IHandle) { let deltaDragY = newLocation.y - originalLocation.y if (this.ratio === 0) { deltaDragX = 0 - } else if (!isFinite(this.ratio)) { + } else if (!Number.isFinite(this.ratio)) { deltaDragY = 0 } else if (Math.abs(this.ratio) > 1) { const sign = @@ -1361,7 +1361,7 @@ export class ChoreographyLabelModel extends BaseClass(ILabelModel, ILabelModelPa * @param {!Class.} type the type for which an instance shall be returned * @returns {?T} an instance that is assignable to type or `null` * @see Specified by {@link ILookup.lookup}. - * @template {*} T + * @template T */ lookup(type) { if (type === ILabelModelParameterProvider.$class) { @@ -1429,7 +1429,7 @@ export class ChoreographyLabelModel extends BaseClass(ILabelModel, ILabelModelPa ) // check which label positions are already taken - node.labels.forEach(label => { + node.labels.forEach((label) => { if (label.layoutParameter instanceof ChoreographyParameter) { let index = 0 const parameter = label.layoutParameter @@ -1932,7 +1932,7 @@ class LineUpIcon extends Icon { const container = new SvgVisualGroup() let offset = 0 - this.icons.forEach(pathIcon => { + this.icons.forEach((pathIcon) => { pathIcon.setBounds(new Rect(offset, 0, this.innerIconSize.width, this.innerIconSize.height)) const pathIconVisual = pathIcon.createVisual(context) container.add(pathIconVisual) @@ -2126,7 +2126,7 @@ class CombinedIcon extends Icon { const container = new SvgVisualGroup() const iconBounds = new Rect(Point.ORIGIN, this.bounds.toSize()) - this.icons.forEach(icon => { + this.icons.forEach((icon) => { icon.setBounds(iconBounds) const iconVisual = icon.createVisual(context) container.add(iconVisual) @@ -4665,7 +4665,7 @@ class ParticipantList extends BaseClass(IList) { */ getHeight() { let height = 0 - this.innerList.forEach(participant => { + this.innerList.forEach((participant) => { height += participant.getSize() }) return height @@ -4676,7 +4676,7 @@ class ParticipantList extends BaseClass(IList) { */ getParticipantModCount() { let participantCount = 0 - this.innerList.forEach(participant => { + this.innerList.forEach((participant) => { participantCount += participant.modCount }) return participantCount @@ -5303,7 +5303,7 @@ export class ChoreographyNodeStyle extends BpmnNodeStyle { // top participants let topOffset = 0 let first = true - this._topParticipants.forEach(participant => { + this._topParticipants.forEach((participant) => { const participantIcon = this.createParticipantIcon(participant, true, first) tpi.add(participantIcon) const height = participant.getSize() @@ -5318,7 +5318,7 @@ export class ChoreographyNodeStyle extends BpmnNodeStyle { // bottom participants let bottomOffset = bounds.height first = true - this._bottomParticipants.forEach(participant => { + this._bottomParticipants.forEach((participant) => { const participantIcon = this.createParticipantIcon(participant, false, first) bpi.add(participantIcon) const height = participant.getSize() @@ -5813,10 +5813,10 @@ export class ChoreographyNodeStyle extends BpmnNodeStyle { clone.background = this.background clone.messageOutline = this.messageOutline - this.topParticipants.forEach(participant => { + this.topParticipants.forEach((participant) => { clone.topParticipants.add(participant.clone()) }) - this.bottomParticipants.forEach(participant => { + this.bottomParticipants.forEach((participant) => { clone.bottomParticipants.add(participant.clone()) }) return clone @@ -7527,14 +7527,14 @@ export class AlternatingLeafStripeStyle extends StripeStyleBase { // Get all leaf columns const leaves = col.table.rootColumn.leaves.toList() // Determine the index - index = leaves.findIndex(curr => col === curr) + index = leaves.findIndex((curr) => col === curr) // Use the correct descriptor descriptor = index % 2 === 0 ? this.evenLeafDescriptor : this.oddLeafDescriptor actualBorderThickness = descriptor.borderThickness } else { const row = stripe const leaves = row.table.rootRow.leaves.toList() - index = leaves.findIndex(curr => row === curr) + index = leaves.findIndex((curr) => row === curr) descriptor = index % 2 === 0 ? this.evenLeafDescriptor : this.oddLeafDescriptor actualBorderThickness = descriptor.borderThickness } @@ -7595,13 +7595,13 @@ export class AlternatingLeafStripeStyle extends StripeStyleBase { if (stripe instanceof IColumn) { const col = stripe const leaves = col.table.rootColumn.leaves.toList() - index = leaves.findIndex(curr => col === curr) + index = leaves.findIndex((curr) => col === curr) descriptor = index % 2 === 0 ? this.evenLeafDescriptor : this.oddLeafDescriptor actualBorderThickness = descriptor.borderThickness } else { const row = stripe const leaves = row.table.rootRow.leaves.toList() - index = leaves.findIndex(curr => row === curr) + index = leaves.findIndex((curr) => row === curr) descriptor = index % 2 === 0 ? this.evenLeafDescriptor : this.oddLeafDescriptor actualBorderThickness = descriptor.borderThickness } @@ -9013,7 +9013,7 @@ export class PoolHeaderLabelModel extends BaseClass(ILabelModel, ILabelModelPara * @param {!Class.} type the type for which an instance shall be returned * @returns {?T} an instance that is assignable to type or `null` * @see Specified by {@link ILookup.lookup}. - * @template {*} T + * @template T */ lookup(type) { if (type === ILabelModelParameterProvider.$class) { @@ -10312,10 +10312,6 @@ export class GroupNodeStyle extends BaseClass(INodeStyle) { _insets = new Insets(15) _renderer = new GroupNodeStyleRenderer() - constructor() { - super() - } - /** * Gets the insets for the node. * These insets are returned via an {@link INodeInsetsProvider} if such an instance is queried @@ -10581,7 +10577,7 @@ class GroupNodeStyleRenderer extends BaseClass(INodeStyleRenderer, ILookup) { * @param {!Class.} type the type for which an instance shall be returned * @returns {?T} an instance that is assignable to type or `null` * @see Specified by {@link ILookup.lookup}. - * @template {*} T + * @template T */ lookup(type) { if (type === INodeInsetsProvider.$class && this.lastStyle != null) { @@ -11100,7 +11096,7 @@ class EventPortStyleRenderer extends BaseClass(IPortStyleRenderer, ILookup) { * @param {!Class.} type the type for which an instance shall be returned * @returns {?T} an instance that is assignable to type or `null` * @see Specified by {@link ILookup.lookup}. - * @template {*} T + * @template T */ lookup(type) { if (type === IEdgePathCropper.$class) { @@ -11222,10 +11218,6 @@ class DataObjectIcon extends Icon { fill = null stroke = null - constructor() { - super() - } - /** * @param {!IRenderContext} context * @returns {!SvgVisual} @@ -11435,7 +11427,7 @@ class CollapseButtonIcon extends Icon { false ) - this.onTouchEndDelegate = event => { + this.onTouchEndDelegate = (event) => { // prevent click event event.preventDefault() this.onTouchEnd(button, currentItem, context) @@ -12701,10 +12693,10 @@ export class ChoreographyNodeStyleExtension extends MarkupExtension { const style = new ChoreographyNodeStyle() style.loopCharacteristic = this.loopCharacteristic style.subState = this.subState - this.topParticipants.forEach(participant => { + this.topParticipants.forEach((participant) => { style.topParticipants.add(participant) }) - this.bottomParticipants.forEach(participant => { + this.bottomParticipants.forEach((participant) => { style.bottomParticipants.add(participant) }) style.initiatingMessage = this.initiatingMessage @@ -13051,10 +13043,10 @@ export class LegacyChoreographyNodeStyleExtension extends MarkupExtension { const style = new ChoreographyNodeStyle() style.loopCharacteristic = this.loopCharacteristic style.subState = this.subState - this.topParticipants.forEach(participant => { + this.topParticipants.forEach((participant) => { style.topParticipants.add(participant) }) - this.bottomParticipants.forEach(participant => { + this.bottomParticipants.forEach((participant) => { style.bottomParticipants.add(participant) }) style.initiatingMessage = this.initiatingMessage @@ -14351,10 +14343,10 @@ export const BpmnHandleSerializationListener = (source, args) => { markupExtension = new ChoreographyNodeStyleExtension() markupExtension.loopCharacteristic = item.loopCharacteristic markupExtension.subState = item.subState - item.topParticipants.forEach(participant => { + item.topParticipants.forEach((participant) => { markupExtension.topParticipants.add(participant) }) - item.bottomParticipants.forEach(participant => { + item.bottomParticipants.forEach((participant) => { markupExtension.bottomParticipants.add(participant) }) markupExtension.initiatingMessage = item.initiatingMessage diff --git a/demos/showcase/bpmn/bpmn-view.ts b/demos/showcase/bpmn/bpmn-view.ts index fd6b31c52..6776a44ab 100644 --- a/demos/showcase/bpmn/bpmn-view.ts +++ b/demos/showcase/bpmn/bpmn-view.ts @@ -499,7 +499,7 @@ class ScalingLabelModel extends BaseClass(ILabelModel) { * @see Specified by {@link ILookup.lookup}. */ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - lookup(type: Class): T | null { + lookup(type: Class): T | null { return ScalingLabelModel.STRETCH_MODEL.lookup(type) } @@ -694,7 +694,7 @@ export class BpmnPortCandidateProvider extends PortCandidateProviderBase { const portCandidates = new List() // provide existing ports as candidates only if they use EventPortStyle and have no edges attached to them. - node.ports.forEach(port => { + node.ports.forEach((port) => { if (port.style instanceof EventPortStyle && context.graph!.edgesAt(port).size === 0) { portCandidates.add(new DefaultPortCandidate(port)) } @@ -854,7 +854,7 @@ class AspectRatioHandle extends BaseClass(IHandle) { let deltaDragY = newLocation.y - originalLocation.y if (this.ratio === 0) { deltaDragX = 0 - } else if (!isFinite(this.ratio)) { + } else if (!Number.isFinite(this.ratio)) { deltaDragY = 0 } else if (Math.abs(this.ratio) > 1) { const sign = @@ -1256,7 +1256,7 @@ export class ChoreographyLabelModel extends BaseClass(ILabelModel, ILabelModelPa * @see Specified by {@link ILookup.lookup}. */ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - lookup(type: Class): T | null { + lookup(type: Class): T | null { if (type === ILabelModelParameterProvider.$class) { return this as unknown as T } @@ -1320,7 +1320,7 @@ export class ChoreographyLabelModel extends BaseClass(ILabelModel, ILabelModelPa ) // check which label positions are already taken - node.labels.forEach(label => { + node.labels.forEach((label) => { if (label.layoutParameter instanceof ChoreographyParameter) { let index = 0 const parameter = label.layoutParameter @@ -1753,7 +1753,7 @@ class LineUpIcon extends Icon { const container = new SvgVisualGroup() let offset = 0 - this.icons.forEach(pathIcon => { + this.icons.forEach((pathIcon) => { pathIcon.setBounds(new Rect(offset, 0, this.innerIconSize.width, this.innerIconSize.height)) const pathIconVisual = pathIcon.createVisual(context)! container.add(pathIconVisual) @@ -1903,7 +1903,7 @@ class CombinedIcon extends Icon { const container = new SvgVisualGroup() const iconBounds = new Rect(Point.ORIGIN, this.bounds.toSize()) - this.icons.forEach(icon => { + this.icons.forEach((icon) => { icon.setBounds(iconBounds) const iconVisual = icon.createVisual(context)! container.add(iconVisual) @@ -4113,7 +4113,7 @@ class ParticipantList extends BaseClass(IList) { getHeight(): number { let height = 0 - this.innerList.forEach(participant => { + this.innerList.forEach((participant) => { height += participant.getSize() }) return height @@ -4121,7 +4121,7 @@ class ParticipantList extends BaseClass(IList) { getParticipantModCount(): number { let participantCount = 0 - this.innerList.forEach(participant => { + this.innerList.forEach((participant) => { participantCount += participant.modCount }) return participantCount @@ -4668,7 +4668,7 @@ export class ChoreographyNodeStyle extends BpmnNodeStyle { // top participants let topOffset = 0 let first = true - this._topParticipants.forEach(participant => { + this._topParticipants.forEach((participant) => { const participantIcon = this.createParticipantIcon(participant, true, first) tpi.add(participantIcon) const height = participant.getSize() @@ -4683,7 +4683,7 @@ export class ChoreographyNodeStyle extends BpmnNodeStyle { // bottom participants let bottomOffset = bounds.height first = true - this._bottomParticipants.forEach(participant => { + this._bottomParticipants.forEach((participant) => { const participantIcon = this.createParticipantIcon(participant, false, first) bpi.add(participantIcon) const height = participant.getSize() @@ -5169,10 +5169,10 @@ export class ChoreographyNodeStyle extends BpmnNodeStyle { clone.background = this.background clone.messageOutline = this.messageOutline - this.topParticipants.forEach(participant => { + this.topParticipants.forEach((participant) => { clone.topParticipants.add(participant.clone()) }) - this.bottomParticipants.forEach(participant => { + this.bottomParticipants.forEach((participant) => { clone.bottomParticipants.add(participant.clone()) }) return clone as this @@ -6748,14 +6748,14 @@ export class AlternatingLeafStripeStyle extends StripeStyleBase { // Get all leaf columns const leaves = col.table!.rootColumn.leaves.toList() // Determine the index - index = leaves.findIndex(curr => col === curr) + index = leaves.findIndex((curr) => col === curr) // Use the correct descriptor descriptor = index % 2 === 0 ? this.evenLeafDescriptor : this.oddLeafDescriptor actualBorderThickness = descriptor.borderThickness } else { const row = stripe as IRow const leaves = row.table!.rootRow.leaves.toList() - index = leaves.findIndex(curr => row === curr) + index = leaves.findIndex((curr) => row === curr) descriptor = index % 2 === 0 ? this.evenLeafDescriptor : this.oddLeafDescriptor actualBorderThickness = descriptor.borderThickness } @@ -6824,13 +6824,13 @@ export class AlternatingLeafStripeStyle extends StripeStyleBase { if (stripe instanceof IColumn) { const col = stripe const leaves = col.table!.rootColumn.leaves.toList() - index = leaves.findIndex(curr => col === curr) + index = leaves.findIndex((curr) => col === curr) descriptor = index % 2 === 0 ? this.evenLeafDescriptor : this.oddLeafDescriptor actualBorderThickness = descriptor.borderThickness } else { const row = stripe as IRow const leaves = row.table!.rootRow.leaves.toList() - index = leaves.findIndex(curr => row === curr) + index = leaves.findIndex((curr) => row === curr) descriptor = index % 2 === 0 ? this.evenLeafDescriptor : this.oddLeafDescriptor actualBorderThickness = descriptor.borderThickness } @@ -8155,7 +8155,7 @@ export class PoolHeaderLabelModel extends BaseClass(ILabelModel, ILabelModelPara * @see Specified by {@link ILookup.lookup}. */ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - lookup(type: Class): T | null { + lookup(type: Class): T | null { if (type === ILabelModelParameterProvider.$class) { return this as unknown as T } @@ -9324,10 +9324,6 @@ export class GroupNodeStyle extends BaseClass(INodeStyle) { private _insets: Insets = new Insets(15) private _renderer: GroupNodeStyleRenderer = new GroupNodeStyleRenderer() - constructor() { - super() - } - /** * Gets the insets for the node. * These insets are returned via an {@link INodeInsetsProvider} if such an instance is queried @@ -9584,7 +9580,7 @@ class GroupNodeStyleRenderer extends BaseClass(INodeStyleRenderer, ILookup) { * @see Specified by {@link ILookup.lookup}. */ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - lookup(type: Class): T | null { + lookup(type: Class): T | null { if (type === INodeInsetsProvider.$class && this.lastStyle != null) { return new GroupInsetsProvider(this.lastStyle) as T } @@ -10064,7 +10060,7 @@ class EventPortStyleRenderer extends BaseClass(IPortStyleRenderer, ILookup) { * @see Specified by {@link ILookup.lookup}. */ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - lookup(type: Class): T | null { + lookup(type: Class): T | null { if (type === IEdgePathCropper.$class) { return EventPortEdgeIntersectionCalculator.CalculatorInstance as T } @@ -10164,10 +10160,6 @@ class DataObjectIcon extends Icon { fill: Fill | null = null stroke: Stroke | null = null - constructor() { - super() - } - createVisual(context: IRenderContext): SvgVisual { const container = document.createElementNS('http://www.w3.org/2000/svg', 'g') @@ -11317,10 +11309,10 @@ export class ChoreographyNodeStyleExtension extends MarkupExtension { const style = new ChoreographyNodeStyle() style.loopCharacteristic = this.loopCharacteristic style.subState = this.subState - this.topParticipants.forEach(participant => { + this.topParticipants.forEach((participant) => { style.topParticipants.add(participant) }) - this.bottomParticipants.forEach(participant => { + this.bottomParticipants.forEach((participant) => { style.bottomParticipants.add(participant) }) style.initiatingMessage = this.initiatingMessage @@ -11581,10 +11573,10 @@ export class LegacyChoreographyNodeStyleExtension extends MarkupExtension { const style = new ChoreographyNodeStyle() style.loopCharacteristic = this.loopCharacteristic style.subState = this.subState - this.topParticipants.forEach(participant => { + this.topParticipants.forEach((participant) => { style.topParticipants.add(participant) }) - this.bottomParticipants.forEach(participant => { + this.bottomParticipants.forEach((participant) => { style.bottomParticipants.add(participant) }) style.initiatingMessage = this.initiatingMessage @@ -12566,10 +12558,10 @@ export const BpmnHandleSerializationListener = ( markupExtension = new ChoreographyNodeStyleExtension() markupExtension.loopCharacteristic = item.loopCharacteristic markupExtension.subState = item.subState - item.topParticipants.forEach(participant => { + item.topParticipants.forEach((participant) => { markupExtension.topParticipants.add(participant) }) - item.bottomParticipants.forEach(participant => { + item.bottomParticipants.forEach((participant) => { markupExtension.bottomParticipants.add(participant) }) markupExtension.initiatingMessage = item.initiatingMessage diff --git a/demos/showcase/bpmn/index.html b/demos/showcase/bpmn/index.html index 3ae6e3960..a18d99885 100644 --- a/demos/showcase/bpmn/index.html +++ b/demos/showcase/bpmn/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/chord-diagram-non-ribbon/HighlightSupport.js b/demos/showcase/chord-diagram-non-ribbon/HighlightSupport.js index 82d086529..16c7d3a48 100644 --- a/demos/showcase/chord-diagram-non-ribbon/HighlightSupport.js +++ b/demos/showcase/chord-diagram-non-ribbon/HighlightSupport.js @@ -167,7 +167,7 @@ export function configureHighlight(graphComponent) { // highlight the node, its adjacent edges and the node labels const node = item highlightNode(node, highlightIndicatorManager) - graphComponent.graph.edgesAt(node).forEach(edge => { + graphComponent.graph.edgesAt(node).forEach((edge) => { highlightIndicatorManager.addHighlight(edge) }) } else if (item instanceof IEdge) { @@ -187,7 +187,7 @@ export function configureHighlight(graphComponent) { */ function highlightNode(node, highlightIndicatorManager) { highlightIndicatorManager.addHighlight(node) - node.labels.forEach(label => { + node.labels.forEach((label) => { highlightIndicatorManager.addHighlight(label) }) } diff --git a/demos/showcase/chord-diagram-non-ribbon/HighlightSupport.ts b/demos/showcase/chord-diagram-non-ribbon/HighlightSupport.ts index ff6576cb4..9c37d9c77 100644 --- a/demos/showcase/chord-diagram-non-ribbon/HighlightSupport.ts +++ b/demos/showcase/chord-diagram-non-ribbon/HighlightSupport.ts @@ -165,7 +165,7 @@ export function configureHighlight(graphComponent: GraphComponent) { // highlight the node, its adjacent edges and the node labels const node = item highlightNode(node, highlightIndicatorManager) - graphComponent.graph.edgesAt(node).forEach(edge => { + graphComponent.graph.edgesAt(node).forEach((edge) => { highlightIndicatorManager.addHighlight(edge) }) } else if (item instanceof IEdge) { @@ -188,7 +188,7 @@ function highlightNode( highlightIndicatorManager: HighlightIndicatorManager ) { highlightIndicatorManager.addHighlight(node) - node.labels.forEach(label => { + node.labels.forEach((label) => { highlightIndicatorManager.addHighlight(label) }) } diff --git a/demos/showcase/chord-diagram-non-ribbon/NonRibbonChordDiagramDemo.js b/demos/showcase/chord-diagram-non-ribbon/NonRibbonChordDiagramDemo.js index 0ed44c9cf..f21ad6928 100644 --- a/demos/showcase/chord-diagram-non-ribbon/NonRibbonChordDiagramDemo.js +++ b/demos/showcase/chord-diagram-non-ribbon/NonRibbonChordDiagramDemo.js @@ -165,7 +165,7 @@ async function initializeGraph(graphComponent) { nodeStyle: VoidNodeStyle.INSTANCE, edgeStyle: VoidEdgeStyle.INSTANCE }) - const graphData = await fetch('resources/GraphData.json').then(response => response.json()) + const graphData = await fetch('resources/GraphData.json').then((response) => response.json()) const builder = new GraphBuilder(graph) const nodesSource = builder.createNodesSource({ @@ -176,7 +176,7 @@ async function initializeGraph(graphComponent) { tag: 'tag' }) - nodesSource.nodeCreator.styleProvider = data => { + nodesSource.nodeCreator.styleProvider = (data) => { const colorSet = colorSets[predefinedColorSets.get(data.tag?.department) || 'demo-palette-41'] return new ShapeNodeStyle({ shape: 'ellipse', @@ -196,7 +196,7 @@ async function initializeGraph(graphComponent) { builder.buildGraph() // sets the style for the labels - graph.labels.forEach(label => { + graph.labels.forEach((label) => { const colorSet = colorSets[predefinedColorSets.get(label.owner.tag?.department) || 'demo-palette-41'] graph.setStyle( @@ -213,7 +213,7 @@ async function initializeGraph(graphComponent) { // normalizes the nodes based on the number of connections stored in the tag let minConnections = Number.MAX_VALUE let maxConnections = -Number.MAX_VALUE - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const connections = node.tag?.connections || 1 minConnections = Math.min(minConnections, connections) maxConnections = Math.max(maxConnections, connections) @@ -222,7 +222,7 @@ async function initializeGraph(graphComponent) { const largest = 100 const smallest = 40 - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const connections = node.tag?.connections || 1 const sizeScale = (largest - smallest) / connectionsDelta const size = Math.floor(smallest + (connections - minConnections) * sizeScale) @@ -235,7 +235,7 @@ async function initializeGraph(graphComponent) { * @param {!GraphComponent} graphComponent The given graphComponent */ function initializeUI(graphComponent) { - document.querySelector('#labelStyleSelect').addEventListener('change', async evt => { + document.querySelector('#labelStyleSelect').addEventListener('change', async (evt) => { await configureAndRunChordLayout(graphComponent, evt.target.value) }) document.querySelector('#labelStyleRayLike').addEventListener('click', async () => { @@ -279,7 +279,7 @@ async function configureAndRunChordLayout(graphComponent, labelingPolicy) { // creates the layout data needed in order to sort the edges based on their type const chordLayoutData = new CircularLayoutData({ - nodeTypes: node => node.tag?.department + nodeTypes: (node) => node.tag?.department }) // apply the layout diff --git a/demos/showcase/chord-diagram-non-ribbon/NonRibbonChordDiagramDemo.ts b/demos/showcase/chord-diagram-non-ribbon/NonRibbonChordDiagramDemo.ts index 933c24e27..d6498847c 100644 --- a/demos/showcase/chord-diagram-non-ribbon/NonRibbonChordDiagramDemo.ts +++ b/demos/showcase/chord-diagram-non-ribbon/NonRibbonChordDiagramDemo.ts @@ -164,7 +164,7 @@ async function initializeGraph(graphComponent: GraphComponent): Promise { nodeStyle: VoidNodeStyle.INSTANCE, edgeStyle: VoidEdgeStyle.INSTANCE }) - const graphData = await fetch('resources/GraphData.json').then(response => response.json()) + const graphData = await fetch('resources/GraphData.json').then((response) => response.json()) const builder = new GraphBuilder(graph) const nodesSource = builder.createNodesSource({ @@ -195,7 +195,7 @@ async function initializeGraph(graphComponent: GraphComponent): Promise { builder.buildGraph() // sets the style for the labels - graph.labels.forEach(label => { + graph.labels.forEach((label) => { const colorSet = colorSets[predefinedColorSets.get(label.owner!.tag?.department) || 'demo-palette-41'] graph.setStyle( @@ -212,7 +212,7 @@ async function initializeGraph(graphComponent: GraphComponent): Promise { // normalizes the nodes based on the number of connections stored in the tag let minConnections = Number.MAX_VALUE let maxConnections = -Number.MAX_VALUE - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const connections = node.tag?.connections || 1 minConnections = Math.min(minConnections, connections) maxConnections = Math.max(maxConnections, connections) @@ -221,7 +221,7 @@ async function initializeGraph(graphComponent: GraphComponent): Promise { const largest = 100 const smallest = 40 - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const connections = node.tag?.connections || 1 const sizeScale = (largest - smallest) / connectionsDelta const size = Math.floor(smallest + (connections - minConnections) * sizeScale) @@ -236,7 +236,7 @@ async function initializeGraph(graphComponent: GraphComponent): Promise { function initializeUI(graphComponent: GraphComponent): void { document .querySelector('#labelStyleSelect')! - .addEventListener('change', async evt => { + .addEventListener('change', async (evt) => { await configureAndRunChordLayout( graphComponent, (evt.target as HTMLSelectElement).value as NodeLabelingPolicyStringValues diff --git a/demos/showcase/chord-diagram-non-ribbon/index.html b/demos/showcase/chord-diagram-non-ribbon/index.html index d8eacc029..001288bdb 100644 --- a/demos/showcase/chord-diagram-non-ribbon/index.html +++ b/demos/showcase/chord-diagram-non-ribbon/index.html @@ -1,4 +1,4 @@ - + @@ -42,7 +42,9 @@ .yfiles-tooltip { background: #ebeef0; color: #29323c; - box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), + box-shadow: + 0 2px 4px -1px rgba(0, 0, 0, 0.2), + 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12); padding: 6px; border-radius: 4px; diff --git a/demos/showcase/chord-diagram/ChordDiagramDemo.js b/demos/showcase/chord-diagram/ChordDiagramDemo.js index 197da22e8..452a1a6a4 100644 --- a/demos/showcase/chord-diagram/ChordDiagramDemo.js +++ b/demos/showcase/chord-diagram/ChordDiagramDemo.js @@ -108,14 +108,14 @@ function configureUserInteraction(graphComponent) { gvim.itemHoverInputMode.addHoveredItemChangedListener((_, evt) => { // reset opacities of all edges - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { edge.tag.opacity = ChordEdgeStyle.defaultOpacity }) // if hovered on a node, highlight all edges of this node if (evt.item instanceof INode) { const node = evt.item - graph.edgesAt(node).forEach(edge => { + graph.edgesAt(node).forEach((edge) => { edge.tag.opacity = 1.0 manager.toFront(edge) }) @@ -145,9 +145,9 @@ function configureUserInteraction(graphComponent) { // deselect all other edges graphComponent.selection.selectedEdges - .filter(e => e != edge) + .filter((e) => e != edge) .toList() - .forEach(e => graphComponent.selection.setSelected(e, false)) + .forEach((e) => graphComponent.selection.setSelected(e, false)) } else { edge.tag.highlighted = false slider.disabled = graphComponent.selection.selectedEdges.size == 0 @@ -189,7 +189,7 @@ function createSampleGraph(graph) { builder.createNodesSource({ data: SampleData.nodes, id: 'id', - layout: data => new Rect(data.x, data.y, defaultNodeSize.width, defaultNodeSize.height) + layout: (data) => new Rect(data.x, data.y, defaultNodeSize.width, defaultNodeSize.height) }) builder.createEdgesSource({ data: SampleData.edges, @@ -204,7 +204,7 @@ function createSampleGraph(graph) { ChordDiagramLayout.EDGE_WEIGHT_KEY ).mapper - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { // create initial weights for the edges, these are relative and will be normalized by the layout weightMapping.set(edge, parseFloat(edge.tag.thickness)) }) @@ -268,15 +268,17 @@ function updateGapRatio(graphComponent, gapRatio) { function initializeUI(graphComponent) { document .querySelector('#toggle-actual-graph') - .addEventListener('change', evt => showGraph(graphComponent, evt.target.checked)) + .addEventListener('change', (evt) => showGraph(graphComponent, evt.target.checked)) // when the slider is moved, increase/decrease the weight of the edge and update the chord layout document .querySelector('#thickness') - .addEventListener('input', evt => updateDiagram(graphComponent, parseFloat(evt.target.value))) + .addEventListener('input', (evt) => updateDiagram(graphComponent, parseFloat(evt.target.value))) // when the gap slider is moved increase/decrease the gaps between the nodes document .querySelector('#gap-ratio') - .addEventListener('input', evt => updateGapRatio(graphComponent, parseFloat(evt.target.value))) + .addEventListener('input', (evt) => + updateGapRatio(graphComponent, parseFloat(evt.target.value)) + ) } run().then(finishLoading) diff --git a/demos/showcase/chord-diagram/ChordDiagramDemo.ts b/demos/showcase/chord-diagram/ChordDiagramDemo.ts index 205796747..7d82060a3 100644 --- a/demos/showcase/chord-diagram/ChordDiagramDemo.ts +++ b/demos/showcase/chord-diagram/ChordDiagramDemo.ts @@ -105,14 +105,14 @@ function configureUserInteraction(graphComponent: GraphComponent): void { gvim.itemHoverInputMode.addHoveredItemChangedListener((_, evt) => { // reset opacities of all edges - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { edge.tag.opacity = ChordEdgeStyle.defaultOpacity }) // if hovered on a node, highlight all edges of this node if (evt.item instanceof INode) { const node = evt.item - graph.edgesAt(node).forEach(edge => { + graph.edgesAt(node).forEach((edge) => { edge.tag.opacity = 1.0 manager.toFront(edge) }) @@ -142,9 +142,9 @@ function configureUserInteraction(graphComponent: GraphComponent): void { // deselect all other edges graphComponent.selection.selectedEdges - .filter(e => e != edge) + .filter((e) => e != edge) .toList() - .forEach(e => graphComponent.selection.setSelected(e, false)) + .forEach((e) => graphComponent.selection.setSelected(e, false)) } else { edge.tag.highlighted = false slider.disabled = graphComponent.selection.selectedEdges.size == 0 @@ -185,7 +185,7 @@ function createSampleGraph(graph: IGraph): void { builder.createNodesSource({ data: SampleData.nodes, id: 'id', - layout: data => new Rect(data.x, data.y, defaultNodeSize.width, defaultNodeSize.height) + layout: (data) => new Rect(data.x, data.y, defaultNodeSize.width, defaultNodeSize.height) }) builder.createEdgesSource({ data: SampleData.edges, @@ -200,7 +200,7 @@ function createSampleGraph(graph: IGraph): void { ChordDiagramLayout.EDGE_WEIGHT_KEY ).mapper - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { // create initial weights for the edges, these are relative and will be normalized by the layout weightMapping.set(edge, parseFloat(edge.tag.thickness)) }) @@ -261,19 +261,19 @@ function updateGapRatio(graphComponent: GraphComponent, gapRatio: number) { function initializeUI(graphComponent: GraphComponent): void { document .querySelector('#toggle-actual-graph')! - .addEventListener('change', evt => + .addEventListener('change', (evt) => showGraph(graphComponent, (evt.target as HTMLInputElement).checked) ) // when the slider is moved, increase/decrease the weight of the edge and update the chord layout document .querySelector('#thickness')! - .addEventListener('input', evt => + .addEventListener('input', (evt) => updateDiagram(graphComponent, parseFloat((evt.target as HTMLInputElement).value)) ) // when the gap slider is moved increase/decrease the gaps between the nodes document .querySelector('#gap-ratio')! - .addEventListener('input', evt => + .addEventListener('input', (evt) => updateGapRatio(graphComponent, parseFloat((evt.target as HTMLInputElement).value)) ) } diff --git a/demos/showcase/chord-diagram/ChordDiagramLayout.js b/demos/showcase/chord-diagram/ChordDiagramLayout.js index 69bb2e035..74976401e 100644 --- a/demos/showcase/chord-diagram/ChordDiagramLayout.js +++ b/demos/showcase/chord-diagram/ChordDiagramLayout.js @@ -128,7 +128,7 @@ export class ChordDiagramLayout extends LayoutStageBase { ) // normalize the edge weights - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const votesOnEdge = edgeThicknessProvider.getNumber(edge) const normalizedThickness = (votesOnEdge / totalEdgeWeights) * (Math.PI * (1 - this.gapRatio)) // reserve half the circle for gaps between nodes edgeThicknessProvider.setNumber(edge, normalizedThickness) @@ -143,21 +143,23 @@ export class ChordDiagramLayout extends LayoutStageBase { // compute a map of node sizes. The node size is equal to the compound size of all edges at the node const nodeSizes = graph.nodes - .map(node => node.edges.reduce((acc, curr) => acc + edgeThicknessProvider.getNumber(curr), 0)) + .map((node) => + node.edges.reduce((acc, curr) => acc + edgeThicknessProvider.getNumber(curr), 0) + ) .toArray() // sort edges to prevent intra-node crossings graph.nodes.forEach((n, idx) => { const outComparer = IComparer.create((edge0, edge1) => { - const targetIndex0 = graph.nodes.findIndex(node => node == edge0.target) - const targetIndex1 = graph.nodes.findIndex(node => node == edge1.target) + const targetIndex0 = graph.nodes.findIndex((node) => node == edge0.target) + const targetIndex1 = graph.nodes.findIndex((node) => node == edge1.target) return compareEdges(idx, targetIndex0, targetIndex1, nodeSizes, gap) }) n.sortOutEdges(outComparer) const inComparer = IComparer.create((edge0, edge1) => { - const targetIndex0 = graph.nodes.findIndex(node => node == edge0.source) - const targetIndex1 = graph.nodes.findIndex(node => node == edge1.source) + const targetIndex0 = graph.nodes.findIndex((node) => node == edge0.source) + const targetIndex1 = graph.nodes.findIndex((node) => node == edge1.source) return compareEdges(idx, targetIndex0, targetIndex1, nodeSizes, gap) }) n.sortInEdges(inComparer) diff --git a/demos/showcase/chord-diagram/ChordDiagramLayout.ts b/demos/showcase/chord-diagram/ChordDiagramLayout.ts index 9f00b3a64..73013fb3f 100644 --- a/demos/showcase/chord-diagram/ChordDiagramLayout.ts +++ b/demos/showcase/chord-diagram/ChordDiagramLayout.ts @@ -102,7 +102,7 @@ export class ChordDiagramLayout extends LayoutStageBase { ) // normalize the edge weights - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const votesOnEdge = edgeThicknessProvider.getNumber(edge) const normalizedThickness = (votesOnEdge / totalEdgeWeights) * (Math.PI * (1 - this.gapRatio)) // reserve half the circle for gaps between nodes edgeThicknessProvider.setNumber(edge, normalizedThickness) @@ -117,21 +117,23 @@ export class ChordDiagramLayout extends LayoutStageBase { // compute a map of node sizes. The node size is equal to the compound size of all edges at the node const nodeSizes = graph.nodes - .map(node => node.edges.reduce((acc, curr) => acc + edgeThicknessProvider.getNumber(curr), 0)) + .map((node) => + node.edges.reduce((acc, curr) => acc + edgeThicknessProvider.getNumber(curr), 0) + ) .toArray() // sort edges to prevent intra-node crossings graph.nodes.forEach((n, idx) => { const outComparer = IComparer.create((edge0: Edge, edge1: Edge) => { - const targetIndex0 = graph.nodes.findIndex(node => node == edge0.target) - const targetIndex1 = graph.nodes.findIndex(node => node == edge1.target) + const targetIndex0 = graph.nodes.findIndex((node) => node == edge0.target) + const targetIndex1 = graph.nodes.findIndex((node) => node == edge1.target) return compareEdges(idx, targetIndex0, targetIndex1, nodeSizes, gap) }) n.sortOutEdges(outComparer) const inComparer = IComparer.create((edge0: Edge, edge1: Edge) => { - const targetIndex0 = graph.nodes.findIndex(node => node == edge0.source) - const targetIndex1 = graph.nodes.findIndex(node => node == edge1.source) + const targetIndex0 = graph.nodes.findIndex((node) => node == edge0.source) + const targetIndex1 = graph.nodes.findIndex((node) => node == edge1.source) return compareEdges(idx, targetIndex0, targetIndex1, nodeSizes, gap) }) n.sortInEdges(inComparer) diff --git a/demos/showcase/chord-diagram/CircleSegmentNodeStyle.ts b/demos/showcase/chord-diagram/CircleSegmentNodeStyle.ts index 044fef29b..98641023d 100644 --- a/demos/showcase/chord-diagram/CircleSegmentNodeStyle.ts +++ b/demos/showcase/chord-diagram/CircleSegmentNodeStyle.ts @@ -356,7 +356,10 @@ type NodeData = { } class NodeRenderDataCache { - constructor(public readonly nodeData: NodeData, public readonly showStyleHints: boolean) {} + constructor( + public readonly nodeData: NodeData, + public readonly showStyleHints: boolean + ) {} equals(other?: NodeRenderDataCache): boolean { if (!other) { diff --git a/demos/showcase/chord-diagram/index.html b/demos/showcase/chord-diagram/index.html index 1092f50bc..5fd6d3346 100644 --- a/demos/showcase/chord-diagram/index.html +++ b/demos/showcase/chord-diagram/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/company-ownership/CompanyOwnershipDemo.js b/demos/showcase/company-ownership/CompanyOwnershipDemo.js index 598a2e0d6..42e2e14f7 100644 --- a/demos/showcase/company-ownership/CompanyOwnershipDemo.js +++ b/demos/showcase/company-ownership/CompanyOwnershipDemo.js @@ -64,8 +64,8 @@ async function run() { applyDemoTheme(graphComponent) // add the listeners for the companyStructureView - companyStructureView.addNodeClickedListener(node => propertiesView.showNodeProperties(node)) - companyStructureView.addEdgeClickedListener(edge => propertiesView.showEdgeProperties(edge)) + companyStructureView.addNodeClickedListener((node) => propertiesView.showNodeProperties(node)) + companyStructureView.addEdgeClickedListener((edge) => propertiesView.showEdgeProperties(edge)) // initialize graph search graphSearch = new CompanyOwnershipSearch(graphComponent) @@ -100,20 +100,20 @@ function createPropertiesView() { * @param {!GraphComponent} graphComponent */ function initializeUI(graphComponent) { - document.querySelector('#styles').addEventListener('change', async event => { + document.querySelector('#styles').addEventListener('change', async (event) => { const select = event.target companyStructureView.useShapeNodeStyle = select.value === 'shapes' await loadGraph(graphComponent) }) - document.querySelectorAll('[data-command="Shapes"]').forEach(element => { + document.querySelectorAll('[data-command="Shapes"]').forEach((element) => { element.addEventListener('click', async () => { document.querySelector('#styles').value = 'shapes' companyStructureView.useShapeNodeStyle = true await loadGraph(graphComponent) }) }) - document.querySelectorAll('[data-command="Tables"]').forEach(element => { + document.querySelectorAll('[data-command="Tables"]').forEach((element) => { element.addEventListener('click', async () => { document.querySelector('#styles').value = 'tables' companyStructureView.useShapeNodeStyle = false @@ -147,10 +147,10 @@ async function loadGraph(graphComponent, animate = true) { */ function setUIDisabled(disabled) { document.querySelector('#styles').disabled = disabled - document.querySelectorAll('[data-command="Shapes"]').forEach(element => { + document.querySelectorAll('[data-command="Shapes"]').forEach((element) => { element.disabled = disabled }) - document.querySelectorAll('[data-command="Tables"]').forEach(element => { + document.querySelectorAll('[data-command="Tables"]').forEach((element) => { element.disabled = disabled }) } diff --git a/demos/showcase/company-ownership/CompanyOwnershipDemo.ts b/demos/showcase/company-ownership/CompanyOwnershipDemo.ts index 356d32a54..c380641ee 100644 --- a/demos/showcase/company-ownership/CompanyOwnershipDemo.ts +++ b/demos/showcase/company-ownership/CompanyOwnershipDemo.ts @@ -60,8 +60,8 @@ async function run(): Promise { applyDemoTheme(graphComponent) // add the listeners for the companyStructureView - companyStructureView.addNodeClickedListener(node => propertiesView.showNodeProperties(node)) - companyStructureView.addEdgeClickedListener(edge => propertiesView.showEdgeProperties(edge)) + companyStructureView.addNodeClickedListener((node) => propertiesView.showNodeProperties(node)) + companyStructureView.addEdgeClickedListener((edge) => propertiesView.showEdgeProperties(edge)) // initialize graph search graphSearch = new CompanyOwnershipSearch(graphComponent) @@ -98,20 +98,22 @@ function createPropertiesView(): void { * Binds actions to the buttons in the toolbar. */ function initializeUI(graphComponent: GraphComponent): void { - document.querySelector('#styles')!.addEventListener('change', async event => { - const select = event.target as HTMLSelectElement - companyStructureView.useShapeNodeStyle = select.value === 'shapes' - await loadGraph(graphComponent) - }) + document + .querySelector('#styles')! + .addEventListener('change', async (event) => { + const select = event.target as HTMLSelectElement + companyStructureView.useShapeNodeStyle = select.value === 'shapes' + await loadGraph(graphComponent) + }) - document.querySelectorAll('[data-command="Shapes"]').forEach(element => { + document.querySelectorAll('[data-command="Shapes"]').forEach((element) => { element.addEventListener('click', async () => { document.querySelector('#styles')!.value = 'shapes' companyStructureView.useShapeNodeStyle = true await loadGraph(graphComponent) }) }) - document.querySelectorAll('[data-command="Tables"]').forEach(element => { + document.querySelectorAll('[data-command="Tables"]').forEach((element) => { element.addEventListener('click', async () => { document.querySelector('#styles')!.value = 'tables' companyStructureView.useShapeNodeStyle = false @@ -143,10 +145,10 @@ async function loadGraph(graphComponent: GraphComponent, animate = true): Promis */ function setUIDisabled(disabled: boolean): void { document.querySelector('#styles')!.disabled = disabled - document.querySelectorAll('[data-command="Shapes"]').forEach(element => { + document.querySelectorAll('[data-command="Shapes"]').forEach((element) => { element.disabled = disabled }) - document.querySelectorAll('[data-command="Tables"]').forEach(element => { + document.querySelectorAll('[data-command="Tables"]').forEach((element) => { element.disabled = disabled }) } diff --git a/demos/showcase/company-ownership/CompanyOwnershipSearch.js b/demos/showcase/company-ownership/CompanyOwnershipSearch.js index e1adc7712..72b53ccdd 100644 --- a/demos/showcase/company-ownership/CompanyOwnershipSearch.js +++ b/demos/showcase/company-ownership/CompanyOwnershipSearch.js @@ -26,7 +26,7 @@ ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ** ***************************************************************************/ -import GraphSearch from 'demo-utils/GraphSearch' +import { GraphSearch } from 'demo-utils/GraphSearch' import { getEntityData } from '../frauddetection/entity-data.js' /** @@ -51,6 +51,6 @@ export class CompanyOwnershipSearch extends GraphSearch { ) { return true } - return node.labels.some(label => label.text.toLowerCase().indexOf(lowercaseText) !== -1) + return node.labels.some((label) => label.text.toLowerCase().indexOf(lowercaseText) !== -1) } } diff --git a/demos/showcase/company-ownership/CompanyOwnershipSearch.ts b/demos/showcase/company-ownership/CompanyOwnershipSearch.ts index 64baf9fa4..d10a81222 100644 --- a/demos/showcase/company-ownership/CompanyOwnershipSearch.ts +++ b/demos/showcase/company-ownership/CompanyOwnershipSearch.ts @@ -26,7 +26,7 @@ ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ** ***************************************************************************/ -import GraphSearch from 'demo-utils/GraphSearch' +import { GraphSearch } from 'demo-utils/GraphSearch' import type { INode } from 'yfiles' import { getEntityData } from '../frauddetection/entity-data' @@ -52,6 +52,6 @@ export class CompanyOwnershipSearch extends GraphSearch { ) { return true } - return node.labels.some(label => label.text.toLowerCase().indexOf(lowercaseText) !== -1) + return node.labels.some((label) => label.text.toLowerCase().indexOf(lowercaseText) !== -1) } } diff --git a/demos/showcase/company-ownership/CompanyStructureView.js b/demos/showcase/company-ownership/CompanyStructureView.js index c7f4a270a..79f51cefb 100644 --- a/demos/showcase/company-ownership/CompanyStructureView.js +++ b/demos/showcase/company-ownership/CompanyStructureView.js @@ -63,13 +63,13 @@ import { modifyGraph } from './prepare-smooth-animation.js' * Returns whether the edge is a hierarchy edge. * @param edge The edge to be checked */ -const isHierarchyEdge = edge => getRelationship(edge).type === EdgeTypeEnum.Hierarchy +const isHierarchyEdge = (edge) => getRelationship(edge).type === EdgeTypeEnum.Hierarchy /** * Returns the ownership percentage for the given edge. * @param edge The edge to be checked */ -const ownershipPercentage = edge => { +const ownershipPercentage = (edge) => { const relationship = getRelationship(edge) if (relationship.type === EdgeTypeEnum.Hierarchy) { return relationship.ownership @@ -82,7 +82,7 @@ const ownershipPercentage = edge => { * Checks whether the given node should be visible. * @param node The node to be checked */ -const isVisible = node => !getCompany(node).invisible +const isVisible = (node) => !getCompany(node).invisible /** * Sets the visibility of the given node. * @param node The given node @@ -103,7 +103,7 @@ const collapseInput = (node, collapse) => { /** * Checks whether the node has collapsed neighbors (from the outgoing edges) */ -const isOutputCollapsed = node => getCompany(node).outputCollapsed +const isOutputCollapsed = (node) => getCompany(node).outputCollapsed /** * Collapses/expands the neighbors of the given node (from the outgoing edges) * @param node The given node @@ -123,12 +123,12 @@ const setDominantHierarchyEdge = (edge, dominant) => { /** * Returns the node id needed for the `NormalizeGraphElementOrderStage`. */ -const getNodeId = node => 'node-' + getCompany(node).id.toString() +const getNodeId = (node) => 'node-' + getCompany(node).id.toString() /** * Returns the edge id needed for the `NormalizeGraphElementOrderStage`. */ -const getEdgeId = edge => getRelationship(edge).id +const getEdgeId = (edge) => getRelationship(edge).id /** * Central class of the application that manages the graph component and the handling of the data. @@ -160,9 +160,9 @@ export class CompanyStructureView { enableBridgeRendering(graphComponent) graphComponent.graph = new FilteredGraphWrapper( (this.completeGraph = graphComponent.graph), - node => + (node) => this.completeGraph.isGroupNode(node) - ? !this.completeGraph.getChildren(node).every(child => !isVisible(child)) + ? !this.completeGraph.getChildren(node).every((child) => !isVisible(child)) : isVisible(node) ) configureLayoutNormalizationIds(graphComponent.graph, getNodeId, getEdgeId) @@ -268,13 +268,13 @@ export class CompanyStructureView { * @param {!function} collapse The collapse function */ addToggleButtonPorts(graph, nodes, expand, collapse) { - nodes.forEach(n => { + nodes.forEach((n) => { if (graph.outDegree(n) > 0) { this.toggleButtonSupport.addPort( graph, n, FreeNodePortLocationModel.NODE_BOTTOM_ANCHORED, - collapsed => (collapsed ? collapse(n, false) : expand(n, false)) + (collapsed) => (collapsed ? collapse(n, false) : expand(n, false)) ) } }) @@ -308,7 +308,7 @@ export class CompanyStructureView { */ async adjustVisibility() { this.updateVisibility(this.completeGraph) - modifyGraph(graph => graph.nodePredicateChanged(), this.graphComponent.graph) + modifyGraph((graph) => graph.nodePredicateChanged(), this.graphComponent.graph) await this.layout() } @@ -319,9 +319,9 @@ export class CompanyStructureView { calculateHierarchy(graph) { const treeResult = new SpanningTree({ subgraphEdges: isHierarchyEdge, - costs: item => 1 - ownershipPercentage(item) + costs: (item) => 1 - ownershipPercentage(item) }).run(graph) - graph.edges.forEach(e => { + graph.edges.forEach((e) => { setDominantHierarchyEdge(e, treeResult.edges.contains(e)) }) } @@ -332,11 +332,11 @@ export class CompanyStructureView { */ updateVisibility(graph) { if (graph.nodes.some(isOutputCollapsed)) { - graph.nodes.forEach(n => { + graph.nodes.forEach((n) => { setVisible(n, this.shouldBeShown(graph, n)) }) } else { - graph.nodes.forEach(n => { + graph.nodes.forEach((n) => { setVisible(n, true) }) } @@ -354,8 +354,8 @@ export class CompanyStructureView { graph.inDegree(node) === 0 || graph .inEdgesAt(node) - .map(e => e.sourceNode) - .some(parent => !isOutputCollapsed(parent) && this.shouldBeShown(graph, parent)) + .map((e) => e.sourceNode) + .some((parent) => !isOutputCollapsed(parent) && this.shouldBeShown(graph, parent)) ) } @@ -402,8 +402,8 @@ export class CompanyStructureView { const nodeSource = builder.createNodesSource({ data: filteredNodes, - id: dataItem => dataItem.id, - style: dataItem => getNodeStyle(dataItem, this.useShapeNodeStyle), + id: (dataItem) => dataItem.id, + style: (dataItem) => getNodeStyle(dataItem, this.useShapeNodeStyle), layout: () => getNodeLayout(this.useShapeNodeStyle) }) @@ -416,7 +416,7 @@ export class CompanyStructureView { // adds the node labels if the shape node style is selected if (this.useShapeNodeStyle) { const nameLabel = nodeSource.nodeCreator.createLabelBinding({ - text: dataItem => dataItem.name, + text: (dataItem) => dataItem.name, defaults: nameLabelDefaults, preferredSize: () => labelSizeDefaults }) @@ -429,14 +429,15 @@ export class CompanyStructureView { const filteredEdges = graphData.edges.filter(edgePredicate) const edgeSource = builder.createEdgesSource({ data: filteredEdges, - id: data => data.id, - sourceId: data => data.sourceId, - targetId: data => data.targetId, + id: (data) => data.id, + sourceId: (data) => data.sourceId, + targetId: (data) => data.targetId, style: getEdgeStyle }) const edgeLabel = edgeSource.edgeCreator.createLabelBinding({ - text: dataItem => (dataItem.type === EdgeTypeEnum.Hierarchy ? `${dataItem.ownership}` : null), + text: (dataItem) => + dataItem.type === EdgeTypeEnum.Hierarchy ? `${dataItem.ownership}` : null, defaults: edgeLabelDefaults }) edgeSource.edgeCreator.addEdgeUpdatedListener((_, evt) => { diff --git a/demos/showcase/company-ownership/CompanyStructureView.ts b/demos/showcase/company-ownership/CompanyStructureView.ts index 850ef78eb..54c061942 100644 --- a/demos/showcase/company-ownership/CompanyStructureView.ts +++ b/demos/showcase/company-ownership/CompanyStructureView.ts @@ -170,9 +170,9 @@ export class CompanyStructureView { enableBridgeRendering(graphComponent) graphComponent.graph = new FilteredGraphWrapper( (this.completeGraph = graphComponent.graph), - node => + (node) => this.completeGraph.isGroupNode(node) - ? !this.completeGraph.getChildren(node).every(child => !isVisible(child)) + ? !this.completeGraph.getChildren(node).every((child) => !isVisible(child)) : isVisible(node) ) configureLayoutNormalizationIds(graphComponent.graph, getNodeId, getEdgeId) @@ -285,13 +285,13 @@ export class CompanyStructureView { expand: (node: INode, incoming: boolean) => void, collapse: (node: INode, incoming: boolean) => void ): void { - nodes.forEach(n => { + nodes.forEach((n) => { if (graph.outDegree(n) > 0) { this.toggleButtonSupport.addPort( graph, n, FreeNodePortLocationModel.NODE_BOTTOM_ANCHORED, - collapsed => (collapsed ? collapse(n, false) : expand(n, false)) + (collapsed) => (collapsed ? collapse(n, false) : expand(n, false)) ) } }) @@ -323,7 +323,7 @@ export class CompanyStructureView { private async adjustVisibility(): Promise { this.updateVisibility(this.completeGraph) modifyGraph( - graph => (graph as FilteredGraphWrapper).nodePredicateChanged(), + (graph) => (graph as FilteredGraphWrapper).nodePredicateChanged(), this.graphComponent.graph ) await this.layout() @@ -336,9 +336,9 @@ export class CompanyStructureView { private calculateHierarchy(graph: IGraph): void { const treeResult = new SpanningTree({ subgraphEdges: isHierarchyEdge, - costs: item => 1 - ownershipPercentage(item) + costs: (item) => 1 - ownershipPercentage(item) }).run(graph) - graph.edges.forEach(e => { + graph.edges.forEach((e) => { setDominantHierarchyEdge(e, treeResult.edges.contains(e)) }) } @@ -349,11 +349,11 @@ export class CompanyStructureView { */ private updateVisibility(graph: IGraph): void { if (graph.nodes.some(isOutputCollapsed)) { - graph.nodes.forEach(n => { + graph.nodes.forEach((n) => { setVisible(n, this.shouldBeShown(graph, n)) }) } else { - graph.nodes.forEach(n => { + graph.nodes.forEach((n) => { setVisible(n, true) }) } @@ -368,7 +368,7 @@ export class CompanyStructureView { graph.inDegree(node) === 0 || graph .inEdgesAt(node) - .map(e => e.sourceNode!) + .map((e) => e.sourceNode!) .some((parent: INode) => !isOutputCollapsed(parent) && this.shouldBeShown(graph, parent)) ) } @@ -414,8 +414,8 @@ export class CompanyStructureView { const nodeSource = builder.createNodesSource({ data: filteredNodes, - id: dataItem => dataItem.id, - style: dataItem => getNodeStyle(dataItem, this.useShapeNodeStyle), + id: (dataItem) => dataItem.id, + style: (dataItem) => getNodeStyle(dataItem, this.useShapeNodeStyle), layout: () => getNodeLayout(this.useShapeNodeStyle) }) @@ -428,7 +428,7 @@ export class CompanyStructureView { // adds the node labels if the shape node style is selected if (this.useShapeNodeStyle) { const nameLabel = nodeSource.nodeCreator.createLabelBinding({ - text: dataItem => dataItem.name, + text: (dataItem) => dataItem.name, defaults: nameLabelDefaults, preferredSize: () => labelSizeDefaults }) @@ -441,14 +441,15 @@ export class CompanyStructureView { const filteredEdges = graphData.edges.filter(edgePredicate) const edgeSource = builder.createEdgesSource({ data: filteredEdges, - id: data => data.id, - sourceId: data => data.sourceId, - targetId: data => data.targetId, + id: (data) => data.id, + sourceId: (data) => data.sourceId, + targetId: (data) => data.targetId, style: getEdgeStyle }) const edgeLabel = edgeSource.edgeCreator.createLabelBinding({ - text: dataItem => (dataItem.type === EdgeTypeEnum.Hierarchy ? `${dataItem.ownership}` : null), + text: (dataItem) => + dataItem.type === EdgeTypeEnum.Hierarchy ? `${dataItem.ownership}` : null, defaults: edgeLabelDefaults }) edgeSource.edgeCreator.addEdgeUpdatedListener((_, evt) => { diff --git a/demos/showcase/company-ownership/TogglePortButtonSupport.js b/demos/showcase/company-ownership/TogglePortButtonSupport.js index 5b9951917..0c5d2679a 100644 --- a/demos/showcase/company-ownership/TogglePortButtonSupport.js +++ b/demos/showcase/company-ownership/TogglePortButtonSupport.js @@ -65,7 +65,7 @@ export default class TogglePortButtonSupport { constructor() { // initialize converter StringTemplatePortStyle.CONVERTERS.companyOwnershipConverters = { - portIconStateConverter: val => + portIconStateConverter: (val) => 'port-icon ' + (val.collapsed ?? false ? 'port-icon-expand' : 'port-icon-collapse') } } diff --git a/demos/showcase/company-ownership/configure-highlight.js b/demos/showcase/company-ownership/configure-highlight.js index 551f11f73..7b1aceb68 100644 --- a/demos/showcase/company-ownership/configure-highlight.js +++ b/demos/showcase/company-ownership/configure-highlight.js @@ -56,7 +56,7 @@ export function enableHoverHighlights(viewerInputMode, graphComponent) { // configures the highlighting style for the edges const decorator = graphComponent.graph.decorator - decorator.nodeDecorator.highlightDecorator.setFactory(node => { + decorator.nodeDecorator.highlightDecorator.setFactory((node) => { const shape = node.style instanceof CustomShapeNodeStyle ? highlightShapes.get(getCompany(node).nodeType) @@ -72,7 +72,7 @@ export function enableHoverHighlights(viewerInputMode, graphComponent) { }) }) decorator.edgeDecorator.highlightDecorator.setFactory( - edge => + (edge) => new EdgeStyleDecorationInstaller({ edgeStyle: new PolylineEdgeStyle({ stroke: '3px #ab2346', @@ -112,14 +112,14 @@ export function enableHoverHighlights(viewerInputMode, graphComponent) { if (evt.item) { highlightIndicatorManager.addHighlight(evt.item) if (evt.item instanceof INode) { - graphComponent.graph.edgesAt(evt.item).forEach(edge => { + graphComponent.graph.edgesAt(evt.item).forEach((edge) => { highlightIndicatorManager.addHighlight(edge) - edge.labels.forEach(label => { + edge.labels.forEach((label) => { highlightIndicatorManager.addHighlight(label) }) }) } else if (evt.item instanceof IEdge) { - evt.item.labels.forEach(label => { + evt.item.labels.forEach((label) => { highlightIndicatorManager.addHighlight(label) }) } else if (evt.item instanceof ILabel) { diff --git a/demos/showcase/company-ownership/configure-highlight.ts b/demos/showcase/company-ownership/configure-highlight.ts index 9e2148991..0aa394d64 100644 --- a/demos/showcase/company-ownership/configure-highlight.ts +++ b/demos/showcase/company-ownership/configure-highlight.ts @@ -61,7 +61,7 @@ export function enableHoverHighlights( // configures the highlighting style for the edges const decorator = graphComponent.graph.decorator - decorator.nodeDecorator.highlightDecorator.setFactory(node => { + decorator.nodeDecorator.highlightDecorator.setFactory((node) => { const shape = node.style instanceof CustomShapeNodeStyle ? highlightShapes.get(getCompany(node).nodeType) @@ -77,7 +77,7 @@ export function enableHoverHighlights( }) }) decorator.edgeDecorator.highlightDecorator.setFactory( - edge => + (edge) => new EdgeStyleDecorationInstaller({ edgeStyle: new PolylineEdgeStyle({ stroke: '3px #ab2346', @@ -117,14 +117,14 @@ export function enableHoverHighlights( if (evt.item) { highlightIndicatorManager.addHighlight(evt.item) if (evt.item instanceof INode) { - graphComponent.graph.edgesAt(evt.item).forEach(edge => { + graphComponent.graph.edgesAt(evt.item).forEach((edge) => { highlightIndicatorManager.addHighlight(edge) - edge.labels.forEach(label => { + edge.labels.forEach((label) => { highlightIndicatorManager.addHighlight(label) }) }) } else if (evt.item instanceof IEdge) { - evt.item.labels.forEach(label => { + evt.item.labels.forEach((label) => { highlightIndicatorManager.addHighlight(label) }) } else if (evt.item instanceof ILabel) { diff --git a/demos/showcase/company-ownership/configure-layout.js b/demos/showcase/company-ownership/configure-layout.js index 088cb2c64..4aebfbefb 100644 --- a/demos/showcase/company-ownership/configure-layout.js +++ b/demos/showcase/company-ownership/configure-layout.js @@ -85,10 +85,10 @@ export function createLayoutData(graph, isHierarchyEdge, nodeId, moveToTop = fal configureEdgeHierarchyCandidates(isHierarchyEdge) const layoutData = new HierarchicLayoutData({ - edgeDirectedness: item => (isHierarchyEdge(item) ? 1 : 0), - sourceGroupIds: item => + edgeDirectedness: (item) => (isHierarchyEdge(item) ? 1 : 0), + sourceGroupIds: (item) => isHierarchyEdge(item) ? 's-' + nodeId(item.sourceNode).toString() : null, - targetGroupIds: item => + targetGroupIds: (item) => isHierarchyEdge(item) ? 't-' + nodeId(item.targetNode).toString() : null, nodePortCandidateSets: createPortCandidateSet, sourcePortCandidates: createSourcePortCandidates, @@ -96,7 +96,7 @@ export function createLayoutData(graph, isHierarchyEdge, nodeId, moveToTop = fal }) if (moveToTop) { - layoutData.partitionGridData.rowIndices = new ItemMapping(node => + layoutData.partitionGridData.rowIndices = new ItemMapping((node) => graph.inEdgesAt(node).filter(isHierarchyEdge).size === 0 ? 0 : 1 ) } @@ -129,9 +129,9 @@ function configureEdgeHierarchyCandidates(isHierarchyEdge) { return portCandidateSet } - const createSourcePortCandidates = edge => + const createSourcePortCandidates = (edge) => isHierarchyEdge(edge) ? hierarchyEdgeSourceSideCandidates : regularEdgeSourceSideCandidates - const createTargetPortCandidates = edge => + const createTargetPortCandidates = (edge) => isHierarchyEdge(edge) ? hierarchyEdgeTargetSideCandidates : regularEdgeTargetSideCandidates return { createPortCandidateSet, createSourcePortCandidates, createTargetPortCandidates } diff --git a/demos/showcase/company-ownership/configure-layout.ts b/demos/showcase/company-ownership/configure-layout.ts index c2115108a..8321dae9d 100644 --- a/demos/showcase/company-ownership/configure-layout.ts +++ b/demos/showcase/company-ownership/configure-layout.ts @@ -94,7 +94,7 @@ export function createLayoutData( configureEdgeHierarchyCandidates(isHierarchyEdge) const layoutData = new HierarchicLayoutData({ - edgeDirectedness: item => (isHierarchyEdge(item) ? 1 : 0), + edgeDirectedness: (item) => (isHierarchyEdge(item) ? 1 : 0), sourceGroupIds: (item: IEdge) => isHierarchyEdge(item) ? 's-' + nodeId(item.sourceNode!).toString() : null, targetGroupIds: (item: IEdge) => @@ -105,7 +105,7 @@ export function createLayoutData( }) if (moveToTop) { - layoutData.partitionGridData.rowIndices = new ItemMapping(node => + layoutData.partitionGridData.rowIndices = new ItemMapping((node) => graph.inEdgesAt(node).filter(isHierarchyEdge).size === 0 ? 0 : 1 ) } diff --git a/demos/showcase/company-ownership/index.html b/demos/showcase/company-ownership/index.html index 914dc247d..3524a9374 100644 --- a/demos/showcase/company-ownership/index.html +++ b/demos/showcase/company-ownership/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/company-ownership/prepare-smooth-animation.js b/demos/showcase/company-ownership/prepare-smooth-animation.js index 16421bba9..217674654 100644 --- a/demos/showcase/company-ownership/prepare-smooth-animation.js +++ b/demos/showcase/company-ownership/prepare-smooth-animation.js @@ -89,7 +89,7 @@ export function modifyGraph(modification, graph) { // then we reset the new edges so that they grow out of their source nodes by placing the target port at // the source port's location - newEdges.forEach(e => { + newEdges.forEach((e) => { graph.clearBends(e) graph.setPortLocationParameter( e.targetPort, diff --git a/demos/showcase/company-ownership/prepare-smooth-animation.ts b/demos/showcase/company-ownership/prepare-smooth-animation.ts index a28d4d683..b47274403 100644 --- a/demos/showcase/company-ownership/prepare-smooth-animation.ts +++ b/demos/showcase/company-ownership/prepare-smooth-animation.ts @@ -100,7 +100,7 @@ export function modifyGraph(modification: (graph: IGraph) => void, graph: IGraph // then we reset the new edges so that they grow out of their source nodes by placing the target port at // the source port's location - newEdges.forEach(e => { + newEdges.forEach((e) => { graph.clearBends(e) graph.setPortLocationParameter( e.targetPort!, diff --git a/demos/showcase/company-ownership/styles/CustomShapeNodeStyle.ts b/demos/showcase/company-ownership/styles/CustomShapeNodeStyle.ts index 5f89c045f..99e21c814 100644 --- a/demos/showcase/company-ownership/styles/CustomShapeNodeStyle.ts +++ b/demos/showcase/company-ownership/styles/CustomShapeNodeStyle.ts @@ -50,7 +50,11 @@ export class CustomShapeNodeStyle extends NodeStyleBase { /** * Creates the custom style for the given type of node. */ - constructor(public type?: NodeTypeEnum, public stroke?: Stroke, public fill?: Fill) { + constructor( + public type?: NodeTypeEnum, + public stroke?: Stroke, + public fill?: Fill + ) { super() this.type = type ?? NodeTypeEnum.CORPORATION this.stroke = Stroke.from(stroke ?? 'black') diff --git a/demos/showcase/company-ownership/styles/TableNodeStyle.js b/demos/showcase/company-ownership/styles/TableNodeStyle.js index c0c195263..7368a9c75 100644 --- a/demos/showcase/company-ownership/styles/TableNodeStyle.js +++ b/demos/showcase/company-ownership/styles/TableNodeStyle.js @@ -66,12 +66,12 @@ export const tableNodeStyle = new StringTemplateNodeStyle(` export function initializeConverters() { TemplateNodeStyle.CONVERTERS.templateNodeConverters = { // converter function for the background color of nodes - typeColorConverter: value => { + typeColorConverter: (value) => { return colorSets[predefinedColorSets.get(value) || 'demo-palette-51'].fill || 'white' }, // converter function for reading the values of the node attributes - valueConverter: value => { + valueConverter: (value) => { return value || '---' } } diff --git a/demos/showcase/decisiontree/decision-tree-component/DecisionTree.js b/demos/showcase/decisiontree/decision-tree-component/DecisionTree.js index 98b2bbe74..c4d8bb64b 100644 --- a/demos/showcase/decisiontree/decision-tree-component/DecisionTree.js +++ b/demos/showcase/decisiontree/decision-tree-component/DecisionTree.js @@ -264,7 +264,7 @@ export default class DecisionTree { const copiedNodes = [] const copiedParentNodes = new Map() // copy the successors from the original graph - this.originalGraph.outEdgesAt(originalNode).forEach(originalEdge => { + this.originalGraph.outEdgesAt(originalNode).forEach((originalEdge) => { const originalTargetNode = originalEdge.targetNode if (!this.originalGraph.isGroupNode(originalTargetNode)) { // target is not a group, thus copy it @@ -295,14 +295,14 @@ export default class DecisionTree { const copiedGroupNode = this.copyGroupNode(originalTargetNode) // copy children - this.originalGraph.getChildren(originalTargetNode).forEach(originalNode => { + this.originalGraph.getChildren(originalTargetNode).forEach((originalNode) => { const copiedChildNode = this.copyNode(originalNode, copiedGroupNode) copiedNodes.push(copiedChildNode) }) this.copyEdge(originalEdge, copiedNode, copiedGroupNode) - this.graph.getChildren(copiedGroupNode).forEach(copiedChildNode => { + this.graph.getChildren(copiedGroupNode).forEach((copiedChildNode) => { if (this.getOriginalOutDegree(copiedChildNode) > 0) { // child nodes that have outgoing edges are clickable nodes this.activeNodes.add(copiedChildNode) @@ -327,11 +327,11 @@ export default class DecisionTree { // get all nodes in this layer let layerNodes = this.layerToNodesMap.get(layer) // remove all nodes in this layer from the path - layerNodes.forEach(node => this.pathNodes.delete(node)) + layerNodes.forEach((node) => this.pathNodes.delete(node)) // remove all nodes in lower layers for (let l = layer + 1; l <= this.currentLayer; l++) { layerNodes = this.layerToNodesMap.get(l) - layerNodes.forEach(node => { + layerNodes.forEach((node) => { this.graph.remove(node) this.pathNodes.delete(node) this.activeNodes.delete(node) @@ -349,7 +349,7 @@ export default class DecisionTree { * Updates the styles of all nodes. */ updateNodeStyles() { - this.graph.nodes.forEach(node => { + this.graph.nodes.forEach((node) => { if (this.graph.isGroupNode(node)) { this.graph.setStyle(node, groupNodeStyle) } else if (this.getOriginalOutDegree(node) === 0) { @@ -379,7 +379,7 @@ export default class DecisionTree { originalNode.style, originalNode.tag ) - originalNode.labels.forEach(label => + originalNode.labels.forEach((label) => this.graph.addLabel( copiedNode, label.text, @@ -410,7 +410,7 @@ export default class DecisionTree { originalNode.style, originalNode.tag ) - originalNode.labels.forEach(label => + originalNode.labels.forEach((label) => this.graph.addLabel( copiedGroupNode, label.text, @@ -454,7 +454,7 @@ export default class DecisionTree { edgeStyle, originalEdge.tag ) - originalEdge.labels.forEach(label => + originalEdge.labels.forEach((label) => this.graph.addLabel( copiedEdge, label.text, @@ -516,7 +516,7 @@ export default class DecisionTree { layoutData.incrementalHints.incrementalLayeringNodes = incrementalNodes // configure critical edges so the path edges are aligned - layoutData.criticalEdgePriorities = edge => + layoutData.criticalEdgePriorities = (edge) => this.pathNodes.has(edge.sourceNode) && this.pathNodes.has(edge.targetNode) ? 1 : 0 this.runningLayout = true @@ -543,7 +543,7 @@ export default class DecisionTree { // mark the new nodes and place them between their neighbors const layoutData = new PlaceNodesAtBarycenterStageData({ - affectedNodes: node => incrementalNodes.indexOf(node) > -1 + affectedNodes: (node) => incrementalNodes.indexOf(node) > -1 }) const layout = new PlaceNodesAtBarycenterStage() graph.applyLayout(layout, layoutData) diff --git a/demos/showcase/decisiontree/decision-tree-component/DecisionTree.ts b/demos/showcase/decisiontree/decision-tree-component/DecisionTree.ts index 70db8c927..ceba21c42 100644 --- a/demos/showcase/decisiontree/decision-tree-component/DecisionTree.ts +++ b/demos/showcase/decisiontree/decision-tree-component/DecisionTree.ts @@ -257,7 +257,7 @@ export default class DecisionTree { const copiedNodes: INode[] = [] const copiedParentNodes = new Map() // copy the successors from the original graph - this.originalGraph.outEdgesAt(originalNode).forEach(originalEdge => { + this.originalGraph.outEdgesAt(originalNode).forEach((originalEdge) => { const originalTargetNode = originalEdge.targetNode! if (!this.originalGraph.isGroupNode(originalTargetNode)) { // target is not a group, thus copy it @@ -288,14 +288,14 @@ export default class DecisionTree { const copiedGroupNode = this.copyGroupNode(originalTargetNode) // copy children - this.originalGraph.getChildren(originalTargetNode).forEach(originalNode => { + this.originalGraph.getChildren(originalTargetNode).forEach((originalNode) => { const copiedChildNode = this.copyNode(originalNode, copiedGroupNode) copiedNodes.push(copiedChildNode) }) this.copyEdge(originalEdge, copiedNode, copiedGroupNode) - this.graph.getChildren(copiedGroupNode).forEach(copiedChildNode => { + this.graph.getChildren(copiedGroupNode).forEach((copiedChildNode) => { if (this.getOriginalOutDegree(copiedChildNode) > 0) { // child nodes that have outgoing edges are clickable nodes this.activeNodes.add(copiedChildNode) @@ -320,11 +320,11 @@ export default class DecisionTree { // get all nodes in this layer let layerNodes = this.layerToNodesMap.get(layer)! // remove all nodes in this layer from the path - layerNodes.forEach(node => this.pathNodes.delete(node)) + layerNodes.forEach((node) => this.pathNodes.delete(node)) // remove all nodes in lower layers for (let l: number = layer + 1; l <= this.currentLayer; l++) { layerNodes = this.layerToNodesMap.get(l)! - layerNodes.forEach(node => { + layerNodes.forEach((node) => { this.graph.remove(node) this.pathNodes.delete(node) this.activeNodes.delete(node) @@ -342,7 +342,7 @@ export default class DecisionTree { * Updates the styles of all nodes. */ private updateNodeStyles(): void { - this.graph.nodes.forEach(node => { + this.graph.nodes.forEach((node) => { if (this.graph.isGroupNode(node)) { this.graph.setStyle(node, groupNodeStyle) } else if (this.getOriginalOutDegree(node) === 0) { @@ -371,7 +371,7 @@ export default class DecisionTree { originalNode.style, originalNode.tag ) - originalNode.labels.forEach(label => + originalNode.labels.forEach((label) => this.graph.addLabel( copiedNode, label.text, @@ -401,7 +401,7 @@ export default class DecisionTree { originalNode.style, originalNode.tag ) - originalNode.labels.forEach(label => + originalNode.labels.forEach((label) => this.graph.addLabel( copiedGroupNode, label.text, @@ -445,7 +445,7 @@ export default class DecisionTree { edgeStyle, originalEdge.tag ) - originalEdge.labels.forEach(label => + originalEdge.labels.forEach((label) => this.graph.addLabel( copiedEdge, label.text, @@ -532,7 +532,7 @@ export default class DecisionTree { // mark the new nodes and place them between their neighbors const layoutData = new PlaceNodesAtBarycenterStageData({ - affectedNodes: node => incrementalNodes.indexOf(node) > -1 + affectedNodes: (node) => incrementalNodes.indexOf(node) > -1 }) const layout = new PlaceNodesAtBarycenterStage() graph.applyLayout(layout, layoutData) diff --git a/demos/showcase/decisiontree/decision-tree-component/decision-tree-component.js b/demos/showcase/decisiontree/decision-tree-component/decision-tree-component.js index 824181569..a01fb9f80 100644 --- a/demos/showcase/decisiontree/decision-tree-component/decision-tree-component.js +++ b/demos/showcase/decisiontree/decision-tree-component/decision-tree-component.js @@ -55,7 +55,7 @@ export function initializeDecisionTreeComponent(graphComponent) { // add the sample graphs const samples = document.querySelector('#samples') - ;['cars', 'what-to-do', 'quiz'].forEach(graph => { + ;['cars', 'what-to-do', 'quiz'].forEach((graph) => { const option = document.createElement('option') option.text = graph option.value = graph @@ -109,7 +109,7 @@ export function hideDecisionTree() { */ function setLayoutRunning(running, graphComponent) { graphComponent.inputMode.waitInputMode.waiting = running - document.querySelectorAll('#decision-tree-toolbar Button,Select').forEach(element => { + document.querySelectorAll('#decision-tree-toolbar Button,Select').forEach((element) => { element.disabled = running }) } diff --git a/demos/showcase/decisiontree/decision-tree-component/decision-tree-component.ts b/demos/showcase/decisiontree/decision-tree-component/decision-tree-component.ts index 499326b42..b2af61b9d 100644 --- a/demos/showcase/decisiontree/decision-tree-component/decision-tree-component.ts +++ b/demos/showcase/decisiontree/decision-tree-component/decision-tree-component.ts @@ -50,7 +50,7 @@ export function initializeDecisionTreeComponent(graphComponent: GraphComponent): // add the sample graphs const samples = document.querySelector('#samples')! - ;['cars', 'what-to-do', 'quiz'].forEach(graph => { + ;['cars', 'what-to-do', 'quiz'].forEach((graph) => { const option = document.createElement('option') option.text = graph option.value = graph @@ -105,7 +105,7 @@ function setLayoutRunning(running: boolean, graphComponent: GraphComponent): voi ;(graphComponent.inputMode as GraphEditorInputMode).waitInputMode.waiting = running document .querySelectorAll('#decision-tree-toolbar Button,Select') - .forEach(element => { + .forEach((element) => { element.disabled = running }) } diff --git a/demos/showcase/decisiontree/editor-component/context-menu.js b/demos/showcase/decisiontree/editor-component/context-menu.js index 251850842..2090d0771 100644 --- a/demos/showcase/decisiontree/editor-component/context-menu.js +++ b/demos/showcase/decisiontree/editor-component/context-menu.js @@ -44,7 +44,7 @@ export function configureContextMenu(graphComponent, setAsRootNode) { // Add event listeners to the various events that open the context menu. These listeners then // call the provided callback-function which in turn asks the current ContextMenuInputMode if a // context menu should be shown at the current location. - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (inputMode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } diff --git a/demos/showcase/decisiontree/editor-component/context-menu.ts b/demos/showcase/decisiontree/editor-component/context-menu.ts index c61d61820..2c5dcd32c 100644 --- a/demos/showcase/decisiontree/editor-component/context-menu.ts +++ b/demos/showcase/decisiontree/editor-component/context-menu.ts @@ -45,7 +45,7 @@ export function configureContextMenu( // Add event listeners to the various events that open the context menu. These listeners then // call the provided callback-function which in turn asks the current ContextMenuInputMode if a // context menu should be shown at the current location. - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (inputMode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } diff --git a/demos/showcase/decisiontree/editor-component/editor-component.js b/demos/showcase/decisiontree/editor-component/editor-component.js index df140947f..0611b9003 100644 --- a/demos/showcase/decisiontree/editor-component/editor-component.js +++ b/demos/showcase/decisiontree/editor-component/editor-component.js @@ -69,7 +69,7 @@ export function initializeEditorComponent(graphComponent) { // configures a green outline as custom highlight for the root node of the decision tree // see also setAsRootNode action graphComponent.graph.decorator.nodeDecorator.highlightDecorator.setImplementation( - node => node === rootNode, + (node) => node === rootNode, new NodeStyleDecorationInstaller({ nodeStyle: new ShapeNodeStyle({ fill: null, @@ -115,8 +115,8 @@ function initializeGraph(graph) { // provide a single port at the top of the node for group nodes graph.decorator.nodeDecorator.portCandidateProviderDecorator.setFactory( - node => graph.isGroupNode(node), - node => new GroupNodePortCandidateProvider(node) + (node) => graph.isGroupNode(node), + (node) => new GroupNodePortCandidateProvider(node) ) } @@ -242,7 +242,7 @@ export async function readSampleGraph(graphComponent) { function setLayoutRunning(running, graphComponent) { runningLayout = running graphComponent.inputMode.waitInputMode.waiting = running - document.querySelectorAll('#editor-toolbar Button').forEach(button => { + document.querySelectorAll('#editor-toolbar Button').forEach((button) => { button.disabled = running }) } diff --git a/demos/showcase/decisiontree/editor-component/editor-component.ts b/demos/showcase/decisiontree/editor-component/editor-component.ts index bce9330da..3b49db6b9 100644 --- a/demos/showcase/decisiontree/editor-component/editor-component.ts +++ b/demos/showcase/decisiontree/editor-component/editor-component.ts @@ -69,7 +69,7 @@ export function initializeEditorComponent(graphComponent: GraphComponent): void // configures a green outline as custom highlight for the root node of the decision tree // see also setAsRootNode action graphComponent.graph.decorator.nodeDecorator.highlightDecorator.setImplementation( - node => node === rootNode, + (node) => node === rootNode, new NodeStyleDecorationInstaller({ nodeStyle: new ShapeNodeStyle({ fill: null, @@ -116,8 +116,8 @@ function initializeGraph(graph: IGraph): void { // provide a single port at the top of the node for group nodes graph.decorator.nodeDecorator.portCandidateProviderDecorator.setFactory( - node => graph.isGroupNode(node), - node => new GroupNodePortCandidateProvider(node) + (node) => graph.isGroupNode(node), + (node) => new GroupNodePortCandidateProvider(node) ) } @@ -237,7 +237,7 @@ export async function readSampleGraph(graphComponent: GraphComponent): Promise('#editor-toolbar Button').forEach(button => { + document.querySelectorAll('#editor-toolbar Button').forEach((button) => { button.disabled = running }) } diff --git a/demos/showcase/decisiontree/index.html b/demos/showcase/decisiontree/index.html index 42d0a1611..2015745d1 100644 --- a/demos/showcase/decisiontree/index.html +++ b/demos/showcase/decisiontree/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/decisiontree/switch-components-button/switch-components-button.js b/demos/showcase/decisiontree/switch-components-button/switch-components-button.js index 4804a09f1..2a72f1e62 100644 --- a/demos/showcase/decisiontree/switch-components-button/switch-components-button.js +++ b/demos/showcase/decisiontree/switch-components-button/switch-components-button.js @@ -101,7 +101,7 @@ export function updateButtonState(graphComponent) { if (activeDecisionTree) { switchComponentsButton.classList.remove('disabled') switchComponentsButton.title = 'Edit Decision Tree Graph' - } else if (graph.nodes.size > 0 && graph.nodes.some(node => !graph.isGroupNode(node))) { + } else if (graph.nodes.size > 0 && graph.nodes.some((node) => !graph.isGroupNode(node))) { switchComponentsButton.classList.remove('disabled') switchComponentsButton.title = 'Show Decision Tree' } else { diff --git a/demos/showcase/decisiontree/switch-components-button/switch-components-button.ts b/demos/showcase/decisiontree/switch-components-button/switch-components-button.ts index 44b48bf90..f90b53148 100644 --- a/demos/showcase/decisiontree/switch-components-button/switch-components-button.ts +++ b/demos/showcase/decisiontree/switch-components-button/switch-components-button.ts @@ -100,7 +100,7 @@ export function updateButtonState(graphComponent: GraphComponent): void { if (activeDecisionTree) { switchComponentsButton.classList.remove('disabled') switchComponentsButton.title = 'Edit Decision Tree Graph' - } else if (graph.nodes.size > 0 && graph.nodes.some(node => !graph.isGroupNode(node))) { + } else if (graph.nodes.size > 0 && graph.nodes.some((node) => !graph.isGroupNode(node))) { switchComponentsButton.classList.remove('disabled') switchComponentsButton.title = 'Show Decision Tree' } else { diff --git a/demos/showcase/flowchart/index.html b/demos/showcase/flowchart/index.html index 17eda1fc6..1432d3354 100644 --- a/demos/showcase/flowchart/index.html +++ b/demos/showcase/flowchart/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/flowchart/interaction/drag-and-drop.js b/demos/showcase/flowchart/interaction/drag-and-drop.js index 27e7fabfd..392d52a0f 100644 --- a/demos/showcase/flowchart/interaction/drag-and-drop.js +++ b/demos/showcase/flowchart/interaction/drag-and-drop.js @@ -40,7 +40,7 @@ export function initializeDnd(graphEditorInputMode) { }) const dndNodes = Object.keys(FlowchartNodeType).map( - type => + (type) => new SimpleNode({ layout: new Rect(0, 0, 80, 40), style: new FlowchartNodeStyle(FlowchartNodeType[type]) diff --git a/demos/showcase/flowchart/interaction/drag-and-drop.ts b/demos/showcase/flowchart/interaction/drag-and-drop.ts index 4738dcdd8..a5a39c964 100644 --- a/demos/showcase/flowchart/interaction/drag-and-drop.ts +++ b/demos/showcase/flowchart/interaction/drag-and-drop.ts @@ -38,7 +38,7 @@ export function initializeDnd(graphEditorInputMode: GraphEditorInputMode): void }) const dndNodes = Object.keys(FlowchartNodeType).map( - type => + (type) => new SimpleNode({ layout: new Rect(0, 0, 80, 40), style: new FlowchartNodeStyle(FlowchartNodeType[type as keyof typeof FlowchartNodeType]) diff --git a/demos/showcase/flowchart/layout/FlowchartLayout.js b/demos/showcase/flowchart/layout/FlowchartLayout.js index abbd4ef47..5813ca77a 100644 --- a/demos/showcase/flowchart/layout/FlowchartLayout.js +++ b/demos/showcase/flowchart/layout/FlowchartLayout.js @@ -201,12 +201,12 @@ export class FlowchartLayout extends BaseClass(ILayoutAlgorithm) { const grid = PartitionGrid.getPartitionGrid(graph) if (grid) { // adjust insets - grid.columns.forEach(column => { + grid.columns.forEach((column) => { column.leftInset = this.laneInsets column.rightInset = this.laneInsets }) - grid.rows.forEach(row => { + grid.rows.forEach((row) => { row.topInset = this.laneInsets row.bottomInset = this.laneInsets }) @@ -224,7 +224,7 @@ export class FlowchartLayout extends BaseClass(ILayoutAlgorithm) { graph.removeDataProvider(HierarchicLayout.LAYER_INDEX_DP_KEY) } const edge2LayoutDescriptor = Maps.createHashedEdgeMap() - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { edge2LayoutDescriptor.set( edge, this.createEdgeLayoutDescriptor( @@ -306,7 +306,7 @@ export class FlowchartLayout extends BaseClass(ILayoutAlgorithm) { createEdgeLayoutDescriptor(edge, graph, defaultDescriptor, horizontal) { const ell = graph.getLabelLayout(edge) let minLength = 0.0 - ell.forEach(label => { + ell.forEach((label) => { const labelSize = label.boundingBox if (isRegularEdge(graph, edge)) { minLength += horizontal ? labelSize.width : labelSize.height @@ -485,10 +485,6 @@ class FlowchartTransformerStage extends LayoutStageBase { dummyLayerIds = null groupNodeIdWrapper = null - constructor() { - super() - } - /** * @param {!LayoutGraph} graph */ @@ -631,7 +627,7 @@ class FlowchartTransformerStage extends LayoutStageBase { const succeedingGroupingConfigurator = new SucceedingLayersInEdgeGroupingConfigurator(this) const edgesToReverse = new EdgeList() const groupingLists = getGroupingLists(graph) - groupingLists.forEach(groupingList => { + groupingLists.forEach((groupingList) => { if (groupingList === null || groupingList.isEmpty()) { return } @@ -645,13 +641,13 @@ class FlowchartTransformerStage extends LayoutStageBase { ) } else { const target = groupingList.firstEdge().target - groupingList.forEach(edge => { + groupingList.forEach((edge) => { this.targetGroupIds.set(edge, target) }) } }) - edgesToReverse.forEach(edge => { + edgesToReverse.forEach((edge) => { graph.reverseEdge(edge) // Reverse the port candidate data if an original edge was reversed if ( @@ -676,11 +672,11 @@ class FlowchartTransformerStage extends LayoutStageBase { if (!directions) { return } - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { let leftCount = 0 let rightCount = 0 - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { const dir = directions.getInt(edge) if (dir === BranchDirection.LeftInFlow) { leftCount++ @@ -695,7 +691,7 @@ class FlowchartTransformerStage extends LayoutStageBase { // If there is more than one edge to the left or right side, // set less restrictive candidates to allow nicer images. - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { const dir = directions.getInt(edge) if (dir === BranchDirection.LeftInFlow || dir === BranchDirection.RightInFlow) { this.sourceCandidates.set(edge, this.getPortCandidateCollection(BranchDirection.Flatwise)) @@ -718,7 +714,7 @@ class FlowchartTransformerStage extends LayoutStageBase { const precedingLayers = [] const succeedingLayers = [] let previousLayer = -1 - groupedInEdges.forEach(edge => { + groupedInEdges.forEach((edge) => { const layer = this.getLayerId(edge.source) const layers = layer <= referenceLayer ? precedingLayers : succeedingLayers if (layer !== previousLayer) { @@ -1054,7 +1050,7 @@ class InEdgeGroupingConfigurator { const target = layers[0].firstEdge().target const nonSingletonLayerEdges = new EdgeList() const unfinishedEdges = new EdgeList() - layers.forEach(layer => { + layers.forEach((layer) => { // maybe we should also check if a singleton node is connected to too many such buses if (nonSingletonLayerEdges.isEmpty() && layer.size === 1) { const edge = layer.firstEdge() @@ -1067,7 +1063,7 @@ class InEdgeGroupingConfigurator { this.enclosing.getLayerId(edge.source) ) // Change unfinished edges to the dummy node - unfinishedEdges.forEach(e => { + unfinishedEdges.forEach((e) => { this.changeEdge(graph, e, e.source, layerDummy) if (unfinishedEdges.size > 1) { this.setGroupId(e, layerDummy) @@ -1120,7 +1116,7 @@ class InEdgeGroupingConfigurator { createGrouping(nonBusEdges, neighborLayerNode, graph) { const nodeIds = graph.getDataProvider(LayoutKeys.NODE_ID_DP_KEY) const groupId = nodeIds.get(nonBusEdges.firstEdge().target) - nonBusEdges.forEach(edge => { + nonBusEdges.forEach((edge) => { this.setGroupId(edge, groupId) this.enclosing.targetCandidates.set( edge, @@ -1189,13 +1185,6 @@ class InEdgeGroupingConfigurator { class SucceedingLayersInEdgeGroupingConfigurator extends InEdgeGroupingConfigurator { edgesToReverse = null - /** - * @param {!FlowchartTransformerStage} enclosing - */ - constructor(enclosing) { - super(enclosing) - } - /** * Creates the complete grouping dummy structure. * This class stores all edges that must be reversed after the @@ -1307,7 +1296,7 @@ class SucceedingLayersInEdgeGroupingConfigurator extends InEdgeGroupingConfigura const target = nonBusEdges.firstEdge().target const groupId = graph.getDataProvider(LayoutKeys.NODE_ID_DP_KEY).get(target) const neighborLayerIndex = this.enclosing.getLayerId(neighborLayerNode) - nonBusEdges.forEach(edge => { + nonBusEdges.forEach((edge) => { let groupingEdge if (neighborLayerIndex === this.enclosing.getLayerId(edge.source)) { groupingEdge = edge @@ -1342,7 +1331,7 @@ class SucceedingLayersInEdgeGroupingConfigurator extends InEdgeGroupingConfigura sameLayerEdge, this.createStrongPortCandidate(sameLayerEdge, true, PortDirections.AGAINST_THE_FLOW, graph) ) - nonBusEdges.forEach(edge => { + nonBusEdges.forEach((edge) => { graph.changeEdge(edge, edge.source, target) }) } @@ -1424,7 +1413,7 @@ function getGroupingLists(graph) { const groupIdDP = graph.getDataProvider(PortConstraintKeys.TARGET_GROUP_ID_DP_KEY) // Partition edges according to group id const idToListsMap = Maps.createHashMap() - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const id = groupIdDP.get(edge) if (id) { if (idToListsMap.has(id)) { @@ -1437,12 +1426,12 @@ function getGroupingLists(graph) { }) // Divide the group id partitions according to edge target nodes const targetGroupLists = [] - idToListsMap.values.forEach(groupList => { + idToListsMap.values.forEach((groupList) => { // Sort the edges according to target nodes such that edges with the same target have consecutive indices groupList.sort(new EdgeIndexComparer()) // Add edges to lists and start a new list whenever a new target is found let targetGroupList - groupList.forEach(edge => { + groupList.forEach((edge) => { if (!targetGroupList || !edge.target.equals(targetGroupList.firstEdge().target)) { targetGroupList = new EdgeList() targetGroupLists.push(targetGroupList) @@ -1463,14 +1452,14 @@ function restoreOriginalGraph(graph) { return } graph.removeDataProvider(FlowchartTransformerStage.GROUPING_NODES_DP_KEY) - new YNodeList(graph.getNodeCursor()).forEach(node => { + new YNodeList(graph.getNodeCursor()).forEach((node) => { let outPath const groupingDummyId = groupingDummiesDP.getInt(node) if (groupingDummyId === NodeLayerType.Preceding) { const outEdge = node.firstOutEdge outPath = graph.getPathList(outEdge) outPath.set(0, graph.getCenter(node)) - new EdgeList(node.getInEdgeCursor()).forEach(edge => { + new EdgeList(node.getInEdgeCursor()).forEach((edge) => { const inPath = graph.getPathList(edge) inPath.pop() graph.changeEdge(edge, edge.source, outEdge.target) @@ -1482,7 +1471,7 @@ function restoreOriginalGraph(graph) { const inEdgeFromOriginal = groupingDummiesDP.getInt(inEdge.source) === 0 const inPath = graph.getPathList(inEdge) inPath.set(inPath.size - 1, graph.getCenter(node)) - new EdgeList(node.getOutEdgeCursor()).forEach(edge => { + new EdgeList(node.getOutEdgeCursor()).forEach((edge) => { const outEdgeFromOriginal = groupingDummiesDP.getInt(edge.target) === 0 outPath = graph.getPathList(edge) outPath.shift() @@ -1666,7 +1655,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { */ optimizeAfterSequencingForSingleNode(node, inEdgeOrder, outEdgeOrder, graph, ldp, itemFactory) { // set EAST or WEST temporary constraints for the same layer edges - node.edges.forEach(edge => { + node.edges.forEach((edge) => { if (FlowchartPortOptimizer.isTemporarySameLayerEdge(edge, ldp)) { const preferredSide = FlowchartPortOptimizer.getPreferredSideForTemporarySameLayerEdge( edge, @@ -1696,7 +1685,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { const flatwiseEdges = Maps.createHashSet() const centralEdges = new EdgeList() const edges = source ? node.outEdges : node.inEdges - edges.forEach(edge => { + edges.forEach((edge) => { const edgeData = ldp.getEdgeData(edge) const constraint = source ? edgeData.sourcePortConstraint : edgeData.targetPortConstraint const candidates = source ? edgeData.sourcePortCandidates : edgeData.targetPortCandidates @@ -1717,7 +1706,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { centralEdges.addAll(flatwiseEdges) centralEdges.sort(edgeOrder) centralEdges.forEach((edge, i) => { - if (flatwiseEdges.some(flatwiseEdge => flatwiseEdge === edge)) { + if (flatwiseEdges.some((flatwiseEdge) => flatwiseEdge === edge)) { const side = i < ((centralEdges.size / 2) | 0) ? PortSide.WEST : PortSide.EAST itemFactory.setTemporaryPortConstraint(edge, source, PortConstraint.create(side)) } @@ -1733,7 +1722,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { * @param {!IDataProvider} edge2Length */ optimizeForAlignment(graph, ldp, itemFactory, node2AlignWith, edge2Length) { - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (!this.alignmentCalculator.isSpecialNode(graph, node, ldp) || node.degree < 2) { return } @@ -1750,7 +1739,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { // port constraint for the same side at the opposite end, too. Otherwise, such an edge gets many bends and // may even destroy the alignment. if (criticalInEdge !== null) { - node.inEdges.forEach(edge => { + node.inEdges.forEach((edge) => { if (criticalInEdge !== edge && criticalInEdge.source === edge.source) { const pc = ldp.getEdgeData(edge).targetPortConstraint if (FlowchartPortOptimizer.isFlatwisePortConstraint(pc)) { @@ -1760,7 +1749,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { }) } if (criticalOutEdge !== null) { - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { if (criticalOutEdge !== edge && criticalOutEdge.target === edge.target) { const pc = ldp.getEdgeData(edge).sourcePortConstraint if (FlowchartPortOptimizer.isFlatwisePortConstraint(pc)) { @@ -1826,7 +1815,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { static getAllSameLayerEdges(graph, ldp) { const sameLayerEdges = new EdgeList() const edge2Seen = Maps.createHashedEdgeMap() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const nData = ldp.getNodeData(node) for (let cell = nData.firstSameLayerEdgeCell; cell !== null; cell = cell.succ()) { const sameLayerEdge = cell.info @@ -1894,7 +1883,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { } let containsEast = false let containsWest = false - portCandidates.forEach(pc => { + portCandidates.forEach((pc) => { const direction = pc.getDirectionForLayoutOrientation(layoutOrientation) if (!containsEast && (PortDirections.EAST & direction) !== 0) { containsEast = true @@ -2262,7 +2251,7 @@ function setOptimizedPortConstraint(edge, source, direction, ldp, factory) { function optimizeMessageNodes(graph, ldp, factory) { const edges = new EdgeList(graph.getEdgeCursor()) edges.splice(FlowchartPortOptimizer.getAllSameLayerEdges(graph, ldp)) - edges.forEach(e => { + edges.forEach((e) => { const original = FlowchartPortOptimizer.getOriginalEdge(e, ldp) const sourceLaneId = FlowchartPortOptimizer.getSwimlaneId(original.source, ldp) const targetLaneId = FlowchartPortOptimizer.getSwimlaneId(original.target, ldp) @@ -2296,7 +2285,7 @@ function optimizeMessageNodes(graph, ldp, factory) { */ function getCriticalInEdge(node, node2AlignWith, edge2Length) { let bestEdge = null - node.inEdges.forEach(edge => { + node.inEdges.forEach((edge) => { if ( node2AlignWith.get(node) === edge.source && (bestEdge === null || edge2Length.getNumber(bestEdge) < edge2Length.getInt(edge)) @@ -2318,7 +2307,7 @@ function getCriticalInEdge(node, node2AlignWith, edge2Length) { */ function getCriticalOutEdge(node, node2AlignWith, edge2Length) { let bestEdge = null - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { if ( node2AlignWith.get(edge.target) === node && (bestEdge === null || edge2Length.getNumber(bestEdge) < edge2Length.getInt(edge)) @@ -2347,13 +2336,6 @@ class FlowchartLayerer extends BaseClass(ILayerer) { $assignStartNodesToLeftOrTop = false $allowFlatwiseDefaultFlow = false - // Be careful: due to the handling of edges attaching to group nodes the degree of "degree-one" nodes may be > 1. - // We are interested in nodes with degree one in the initial graph. - - constructor() { - super() - } - /** * Returns whether start nodes are assigned at the top or to the left of the layout. * @type {boolean} @@ -2405,16 +2387,16 @@ class FlowchartLayerer extends BaseClass(ILayerer) { // assign layers RankAssignmentAlgorithm.simplex(graph, node2Layer, weight, minLength) // undo graph transformation - dummies.forEach(dummy => { + dummies.forEach((dummy) => { graph.removeNode(dummy) }) hider.unhideAll() - reversedEdges.forEach(edge => { + reversedEdges.forEach((edge) => { graph.reverseEdge(edge) }) // special handling for some single degree nodes (draw the incident edge as the same layer edge) - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (isDegreeOneNode(node, ldp)) { handleDegreeOneNode(node, graph, node2Layer, ldp) } @@ -2424,7 +2406,7 @@ class FlowchartLayerer extends BaseClass(ILayerer) { for (let i = 0; i < layerCount; i++) { layers.insert(LayerType.NORMAL, i) } - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const layer = node2Layer.getInt(node) layers.getLayer(layer).add(node) }) @@ -2451,16 +2433,16 @@ class FlowchartLayerer extends BaseClass(ILayerer) { const groupingNodesDP = graph.getDataProvider(FlowchartTransformerStage.GROUPING_NODES_DP_KEY) const targetGroupIdDP = graph.getDataProvider(PortConstraintKeys.TARGET_GROUP_ID_DP_KEY) const outEdgeBranchTypes = graph.createNodeMap() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { let type = 0 - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { type |= preferredDirectionDP.getInt(edge) }) outEdgeBranchTypes.setInt(node, type) }) const dummies = new YNodeList() const edges = graph.getEdgeArray() - edges.forEach(edge => { + edges.forEach((edge) => { let dummyEdge2 let dummyEdge1 let dummyNode @@ -2529,7 +2511,7 @@ class FlowchartLayerer extends BaseClass(ILayerer) { */ insertSuperRoot(graph, weight, minLength) { const superRoot = graph.createNode() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (!node.equals(superRoot) && node.inDegree === 0) { const dummyEdge = graph.createEdge(superRoot, node) weight.setInt( @@ -2551,7 +2533,7 @@ class FlowchartLayerer extends BaseClass(ILayerer) { function reverseCycles(graph) { // we only consider edges of type sequence flow const hider = new LayoutGraphHider(graph) - graph.edges.forEach(e => { + graph.edges.forEach((e) => { if (getType(graph, e) !== EdgeType.SequenceFlow) { hider.hide(e) } @@ -2562,7 +2544,7 @@ function reverseCycles(graph) { try { // try to identify backedges and assign lower weights to them const coreNodes = new YNodeList() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (node.inDegree === 0) { coreNodes.addLast(node) } @@ -2570,7 +2552,7 @@ function reverseCycles(graph) { const node2Depth = graph.createNodeMap() try { BfsAlgorithm.getLayers(graph, coreNodes, true, node2Depth) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { if (node2Depth.getInt(edge.source) > node2Depth.getInt(edge.target)) { // likely to be a back-edge edge2Weight.setNumber(edge, CYCLE_WEIGHT_BACKEDGE) @@ -2585,7 +2567,7 @@ function reverseCycles(graph) { reversedEdges = new EdgeList() CycleAlgorithm.findCycleEdges(graph, cyclingEdges, edge2Weight) - graph.edges.forEach(e => { + graph.edges.forEach((e) => { if (cyclingEdges.getBoolean(e)) { graph.reverseEdge(e) reversedEdges.addLast(e) @@ -2684,7 +2666,7 @@ function handleDegreeOneNode(node, graph, node2Layer, ldp) { let sameLayerEdgeCount = 0 let oppositeOutDegree = 0 let oppositeInDegree = 0 - opposite.outEdges.forEach(edge => { + opposite.outEdges.forEach((edge) => { if (!edge.equals(realEdge) && isNormalEdge(ldp.getEdgeData(edge))) { const layerDiff = node2Layer.getInt(edge.source) - node2Layer.getInt(edge.target) if (layerDiff > 0) { @@ -2697,7 +2679,7 @@ function handleDegreeOneNode(node, graph, node2Layer, ldp) { } }) - opposite.inEdges.forEach(edge => { + opposite.inEdges.forEach((edge) => { if (!edge.equals(realEdge) && isNormalEdge(ldp.getEdgeData(edge))) { const layerDiff = node2Layer.getInt(edge.source) - node2Layer.getInt(edge.target) if (layerDiff > 0) { @@ -2848,7 +2830,7 @@ class FlowchartLabelProfitModel extends BaseClass(IProfitModel) { super() this.graph = graph this.label2OriginalBox = Maps.createHashMap() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const nll = graph.getLabelLayout(node) for (let i = 0; i < nll.length; i++) { const nlm = nll[i].labelModel @@ -3096,7 +3078,7 @@ class FlowchartAlignmentCalculator { ldp ) { const edgeIsAlignable = Maps.createHashedEdgeMap() - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { edgeIsAlignable.setBoolean( edge, this.isAlignable(graph, ldp, edge) && this.isRelevant(graph, edge, ldp) @@ -3119,7 +3101,7 @@ class FlowchartAlignmentCalculator { const BasicEdgeLength = 5 const PenaltyLength = BasicEdgeLength + graph.nodeCount const HighPenaltyLength = PenaltyLength * 8 - graph.edges.forEach(e => { + graph.edges.forEach((e) => { if (hasFlatwisePortConstraint(layoutData.getEdgeData(e))) { edgeLength.setInt(e, ZeroLength) } else if (isRealEdge(e, layoutData)) { @@ -3128,7 +3110,7 @@ class FlowchartAlignmentCalculator { edgeLength.setInt(e, BasicDummyEdgeLength) } }) - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { let i let edges const nodeData = layoutData.getNodeData(node) @@ -3178,7 +3160,7 @@ class FlowchartAlignmentCalculator { } } let hasStraightBranch = false - node.edges.forEach(edge => { + node.edges.forEach((edge) => { if (isStraightBranch(graph, edge, layoutData)) { hasStraightBranch = true edgeLength.setInt(edge, edgeLength.getInt(edge) + PenaltyLength) @@ -3263,7 +3245,7 @@ class NodeAlignmentCalculator { const groupNode2EndRep = Maps.createHashMap() const network = new Graph() // create network nodes - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const data = ldp.getNodeData(node) if (data !== null && data.type === NodeDataType.GROUP_BEGIN) { // all groups begin dummies of the same group node are mapped to the same network node @@ -3286,7 +3268,7 @@ class NodeAlignmentCalculator { }) // consider edges const nonAlignableEdges = new EdgeList() - graph.edges.forEach(e => { + graph.edges.forEach((e) => { if (e.selfLoop || (isGroupNodeBorder(e.source, ldp) && isGroupNodeBorder(e.target, ldp))) { return } @@ -3362,7 +3344,7 @@ class NodeAlignmentCalculator { // For each non-alignable edge, we create a connector with min length 1, // but only it has no other alignable in-edge. const nonAlignableConnectorMap = Maps.createHashedEdgeMap() - nonAlignableEdges.forEach(e => { + nonAlignableEdges.forEach((e) => { const hasAlignableInEdge = checkPredicate(e.target.getInEdgeCursor(), edgeAlignable) if (hasAlignableInEdge) { return @@ -3488,7 +3470,7 @@ class NodeAlignmentCalculator { ldp ) { const node2LaneAlignment = Maps.createHashedNodeMap() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { node2LaneAlignment.setInt(node, this.getLaneAlignment(node, ldp)) }) return node2LaneAlignment @@ -3506,7 +3488,7 @@ class NodeAlignmentCalculator { const nEdges = new EdgeList(node.getEdgeCursor()) nEdges.splice(FlowchartPortOptimizer.getSameLayerEdges(node, true, ldp)) nEdges.splice(FlowchartPortOptimizer.getSameLayerEdges(node, false, ldp)) - nEdges.forEach(edge => { + nEdges.forEach((edge) => { if (FlowchartPortOptimizer.isToLeftPartition(node, edge.opposite(node), ldp)) { toLeftCount++ } else if (FlowchartPortOptimizer.isToRightPartition(node, edge.opposite(node), ldp)) { @@ -3752,7 +3734,7 @@ function determineEdgePriorities(graph, edgeIsAlignable, edgeLength) { const hider = new LayoutGraphHider(graph) try { // hide irrelevant edges - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { edgePriority.setInt(edge, Priority.Basic) if (!edgeIsAlignable.getBoolean(edge)) { hider.hide(edge) @@ -3764,6 +3746,7 @@ function determineEdgePriorities(graph, edgeIsAlignable, edgeLength) { const gpm = new GraphPartitionManager(graph, node2CompId) try { gpm.hideAll() + // biome-ignore lint/correctness/noUnreachable: Seems to be an incorrect warning for (let i = 0; i < compCount; i++) { gpm.displayPartition(i) const localHider = new LayoutGraphHider(graph) diff --git a/demos/showcase/flowchart/layout/FlowchartLayout.ts b/demos/showcase/flowchart/layout/FlowchartLayout.ts index 9ccf4ff8f..6dda20122 100644 --- a/demos/showcase/flowchart/layout/FlowchartLayout.ts +++ b/demos/showcase/flowchart/layout/FlowchartLayout.ts @@ -244,12 +244,12 @@ export class FlowchartLayout const grid = PartitionGrid.getPartitionGrid(graph) if (grid) { // adjust insets - grid.columns.forEach(column => { + grid.columns.forEach((column) => { column.leftInset = this.laneInsets column.rightInset = this.laneInsets }) - grid.rows.forEach(row => { + grid.rows.forEach((row) => { row.topInset = this.laneInsets row.bottomInset = this.laneInsets }) @@ -267,7 +267,7 @@ export class FlowchartLayout graph.removeDataProvider(HierarchicLayout.LAYER_INDEX_DP_KEY) } const edge2LayoutDescriptor = Maps.createHashedEdgeMap() - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { edge2LayoutDescriptor.set( edge, this.createEdgeLayoutDescriptor( @@ -348,7 +348,7 @@ export class FlowchartLayout ): HierarchicLayoutEdgeLayoutDescriptor { const ell = graph.getLabelLayout(edge) let minLength = 0.0 - ell.forEach(label => { + ell.forEach((label) => { const labelSize = label.boundingBox if (isRegularEdge(graph, edge)) { minLength += horizontal ? labelSize.width : labelSize.height @@ -504,10 +504,6 @@ class FlowchartTransformerStage extends LayoutStageBase { dummyLayerIds: INodeMap = null! groupNodeIdWrapper: HashedDataProviderWrapper | null = null - constructor() { - super() - } - applyLayout(graph: LayoutGraph): void { const hierarchicLayout = getHierarchicCoreLayout(this) if (!hierarchicLayout) { @@ -646,7 +642,7 @@ class FlowchartTransformerStage extends LayoutStageBase { const succeedingGroupingConfigurator = new SucceedingLayersInEdgeGroupingConfigurator(this) const edgesToReverse = new EdgeList() const groupingLists = getGroupingLists(graph) - groupingLists.forEach(groupingList => { + groupingLists.forEach((groupingList) => { if (groupingList === null || groupingList.isEmpty()) { return } @@ -660,7 +656,7 @@ class FlowchartTransformerStage extends LayoutStageBase { ) } else { const target = groupingList.firstEdge()!.target - groupingList.forEach(edge => { + groupingList.forEach((edge) => { this.targetGroupIds.set(edge, target) }) } @@ -690,11 +686,11 @@ class FlowchartTransformerStage extends LayoutStageBase { if (!directions) { return } - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { let leftCount = 0 let rightCount = 0 - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { const dir = directions.getInt(edge) if (dir === BranchDirection.LeftInFlow) { leftCount++ @@ -709,7 +705,7 @@ class FlowchartTransformerStage extends LayoutStageBase { // If there is more than one edge to the left or right side, // set less restrictive candidates to allow nicer images. - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { const dir = directions.getInt(edge) if (dir === BranchDirection.LeftInFlow || dir === BranchDirection.RightInFlow) { this.sourceCandidates.set(edge, this.getPortCandidateCollection(BranchDirection.Flatwise)) @@ -882,7 +878,10 @@ class IdProvider extends DataProviderBase { * or else the value in the provider. */ class NodeIdDataProvider extends DataProviderBase { - constructor(private backupNodeIdDP: IDataProvider, private provider: IdProvider) { + constructor( + private backupNodeIdDP: IDataProvider, + private provider: IdProvider + ) { super() } @@ -912,7 +911,10 @@ class EdgeIndexComparer extends BaseClass>(IComparer) implements * Comparer, which uses the layer index of the edges' source nodes as an order. */ class LayerIndexComparer extends BaseClass>(IComparer) implements IComparer { - constructor(private enclosing: FlowchartTransformerStage, private hasLayerIds: boolean) { + constructor( + private enclosing: FlowchartTransformerStage, + private hasLayerIds: boolean + ) { super() } @@ -1001,7 +1003,7 @@ class InEdgeGroupingConfigurator { const target = layers[0].firstEdge()!.target const nonSingletonLayerEdges = new EdgeList() const unfinishedEdges = new EdgeList() - layers.forEach(layer => { + layers.forEach((layer) => { // maybe we should also check if a singleton node is connected to too many such buses if (nonSingletonLayerEdges.isEmpty() && layer.size === 1) { const edge = layer.firstEdge()! @@ -1127,10 +1129,6 @@ class InEdgeGroupingConfigurator { class SucceedingLayersInEdgeGroupingConfigurator extends InEdgeGroupingConfigurator { private edgesToReverse: EdgeList | null = null - constructor(enclosing: FlowchartTransformerStage) { - super(enclosing) - } - /** * Creates the complete grouping dummy structure. * This class stores all edges that must be reversed after the @@ -1274,7 +1272,10 @@ class HashedDataProviderWrapper extends BaseClass(IDataProvider) implements IDataProvider { - constructor(readonly map: IMap, readonly fallback: IDataProvider) { + constructor( + readonly map: IMap, + readonly fallback: IDataProvider + ) { super() } @@ -1342,7 +1343,7 @@ function getGroupingLists(graph: LayoutGraph): EdgeList[] { }) // Divide the group id partitions according to edge target nodes const targetGroupLists: EdgeList[] = [] - idToListsMap.values.forEach(groupList => { + idToListsMap.values.forEach((groupList) => { // Sort the edges according to target nodes such that edges with the same target have consecutive indices groupList.sort(new EdgeIndexComparer()) // Add edges to lists and start a new list whenever a new target is found @@ -1569,7 +1570,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { itemFactory: IItemFactory ): void { // set EAST or WEST temporary constraints for the same layer edges - node.edges.forEach(edge => { + node.edges.forEach((edge) => { if (FlowchartPortOptimizer.isTemporarySameLayerEdge(edge, ldp)) { const preferredSide = FlowchartPortOptimizer.getPreferredSideForTemporarySameLayerEdge( edge, @@ -1600,7 +1601,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { const flatwiseEdges = Maps.createHashSet() const centralEdges = new EdgeList() const edges = source ? node.outEdges : node.inEdges - edges.forEach(edge => { + edges.forEach((edge) => { const edgeData = ldp.getEdgeData(edge)! const constraint = source ? edgeData.sourcePortConstraint : edgeData.targetPortConstraint const candidates = ( @@ -1623,7 +1624,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { centralEdges.addAll(flatwiseEdges) centralEdges.sort(edgeOrder) centralEdges.forEach((edge: Edge, i) => { - if (flatwiseEdges.some(flatwiseEdge => flatwiseEdge === edge)) { + if (flatwiseEdges.some((flatwiseEdge) => flatwiseEdge === edge)) { const side = i < ((centralEdges.size / 2) | 0) ? PortSide.WEST : PortSide.EAST itemFactory.setTemporaryPortConstraint(edge, source, PortConstraint.create(side)) } @@ -1640,7 +1641,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { node2AlignWith: IDataProvider, edge2Length: IDataProvider ): void { - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (!this.alignmentCalculator.isSpecialNode(graph, node, ldp) || node.degree < 2) { return } @@ -1657,7 +1658,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { // port constraint for the same side at the opposite end, too. Otherwise, such an edge gets many bends and // may even destroy the alignment. if (criticalInEdge !== null) { - node.inEdges.forEach(edge => { + node.inEdges.forEach((edge) => { if (criticalInEdge !== edge && criticalInEdge.source === edge.source) { const pc = ldp.getEdgeData(edge)!.targetPortConstraint! if (FlowchartPortOptimizer.isFlatwisePortConstraint(pc)) { @@ -1667,7 +1668,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { }) } if (criticalOutEdge !== null) { - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { if (criticalOutEdge !== edge && criticalOutEdge.target === edge.target) { const pc = ldp.getEdgeData(edge)!.sourcePortConstraint! if (FlowchartPortOptimizer.isFlatwisePortConstraint(pc)) { @@ -1720,7 +1721,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { static getAllSameLayerEdges(graph: LayoutGraph, ldp: ILayoutDataProvider): EdgeList { const sameLayerEdges = new EdgeList() const edge2Seen = Maps.createHashedEdgeMap() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const nData = ldp.getNodeData(node)! for (let cell = nData.firstSameLayerEdgeCell; cell !== null; cell = cell.succ()) { const sameLayerEdge = cell.info as Edge @@ -1778,7 +1779,7 @@ class FlowchartPortOptimizer extends PortConstraintOptimizerBase { } let containsEast = false let containsWest = false - portCandidates.forEach(pc => { + portCandidates.forEach((pc) => { const direction = pc.getDirectionForLayoutOrientation(layoutOrientation) if (!containsEast && (PortDirections.EAST & direction) !== 0) { containsEast = true @@ -1867,7 +1868,10 @@ class PositionEdgeComparer private sameLayerNodePositionComparer: SameLayerNodePositionComparer private portConstraintComparer: SingleSidePortConstraintComparer - constructor(private source: boolean, private ldp: ILayoutDataProvider) { + constructor( + private source: boolean, + private ldp: ILayoutDataProvider + ) { super() this.sameLayerNodePositionComparer = new SameLayerNodePositionComparer(ldp) this.portConstraintComparer = new SingleSidePortConstraintComparer() @@ -2151,7 +2155,7 @@ function getCriticalInEdge( edge2Length: IDataProvider ): Edge | null { let bestEdge: Edge | null = null - node.inEdges.forEach(edge => { + node.inEdges.forEach((edge) => { if ( node2AlignWith.get(node) === edge.source && (bestEdge === null || edge2Length.getNumber(bestEdge) < edge2Length.getInt(edge)) @@ -2173,7 +2177,7 @@ function getCriticalOutEdge( edge2Length: IDataProvider ): Edge | null { let bestEdge: Edge | null = null - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { if ( node2AlignWith.get(edge.target) === node && (bestEdge === null || edge2Length.getNumber(bestEdge) < edge2Length.getInt(edge)) @@ -2202,13 +2206,6 @@ class FlowchartLayerer extends BaseClass(ILayerer) implements ILayerer private $assignStartNodesToLeftOrTop = false private $allowFlatwiseDefaultFlow = false - // Be careful: due to the handling of edges attaching to group nodes the degree of "degree-one" nodes may be > 1. - // We are interested in nodes with degree one in the initial graph. - - constructor() { - super() - } - /** * Returns whether start nodes are assigned at the top or to the left of the layout. */ @@ -2260,7 +2257,7 @@ class FlowchartLayerer extends BaseClass(ILayerer) implements ILayerer }) // special handling for some single degree nodes (draw the incident edge as the same layer edge) - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (isDegreeOneNode(node, ldp)) { handleDegreeOneNode(node, graph, node2Layer, ldp) } @@ -2270,7 +2267,7 @@ class FlowchartLayerer extends BaseClass(ILayerer) implements ILayerer for (let i = 0; i < layerCount; i++) { layers.insert(LayerType.NORMAL, i) } - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const layer = node2Layer.getInt(node) layers.getLayer(layer)!.add(node) }) @@ -2297,16 +2294,16 @@ class FlowchartLayerer extends BaseClass(ILayerer) implements ILayerer const groupingNodesDP = graph.getDataProvider(FlowchartTransformerStage.GROUPING_NODES_DP_KEY)! const targetGroupIdDP = graph.getDataProvider(PortConstraintKeys.TARGET_GROUP_ID_DP_KEY)! const outEdgeBranchTypes = graph.createNodeMap() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { let type = 0 - node.outEdges.forEach(edge => { + node.outEdges.forEach((edge) => { type |= preferredDirectionDP.getInt(edge) }) outEdgeBranchTypes.setInt(node, type) }) const dummies = new YNodeList() const edges = graph.getEdgeArray() - edges.forEach(edge => { + edges.forEach((edge) => { let dummyEdge2: Edge let dummyEdge1: Edge let dummyNode: YNode @@ -2371,7 +2368,7 @@ class FlowchartLayerer extends BaseClass(ILayerer) implements ILayerer */ insertSuperRoot(graph: LayoutGraph, weight: IDataAcceptor, minLength: IDataAcceptor): YNode { const superRoot = graph.createNode() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (!node.equals(superRoot) && node.inDegree === 0) { const dummyEdge = graph.createEdge(superRoot, node) weight.setInt( @@ -2391,7 +2388,7 @@ class FlowchartLayerer extends BaseClass(ILayerer) implements ILayerer function reverseCycles(graph: LayoutGraph): EdgeList { // we only consider edges of type sequence flow const hider = new LayoutGraphHider(graph) - graph.edges.forEach(e => { + graph.edges.forEach((e) => { if (getType(graph, e) !== EdgeType.SequenceFlow) { hider.hide(e) } @@ -2402,7 +2399,7 @@ function reverseCycles(graph: LayoutGraph): EdgeList { try { // try to identify backedges and assign lower weights to them const coreNodes = new YNodeList() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (node.inDegree === 0) { coreNodes.addLast(node) } @@ -2410,7 +2407,7 @@ function reverseCycles(graph: LayoutGraph): EdgeList { const node2Depth = graph.createNodeMap() try { BfsAlgorithm.getLayers(graph, coreNodes, true, node2Depth) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { if (node2Depth.getInt(edge.source) > node2Depth.getInt(edge.target)) { // likely to be a back-edge edge2Weight.setNumber(edge, CYCLE_WEIGHT_BACKEDGE) @@ -2425,7 +2422,7 @@ function reverseCycles(graph: LayoutGraph): EdgeList { reversedEdges = new EdgeList() CycleAlgorithm.findCycleEdges(graph, cyclingEdges, edge2Weight) - graph.edges.forEach(e => { + graph.edges.forEach((e) => { if (cyclingEdges.getBoolean(e)) { graph.reverseEdge(e) reversedEdges.addLast(e) @@ -2519,7 +2516,7 @@ function handleDegreeOneNode( let sameLayerEdgeCount = 0 let oppositeOutDegree = 0 let oppositeInDegree = 0 - opposite.outEdges.forEach(edge => { + opposite.outEdges.forEach((edge) => { if (!edge.equals(realEdge) && isNormalEdge(ldp.getEdgeData(edge))) { const layerDiff = node2Layer.getInt(edge.source) - node2Layer.getInt(edge.target) if (layerDiff > 0) { @@ -2532,7 +2529,7 @@ function handleDegreeOneNode( } }) - opposite.inEdges.forEach(edge => { + opposite.inEdges.forEach((edge) => { if (!edge.equals(realEdge) && isNormalEdge(ldp.getEdgeData(edge))) { const layerDiff = node2Layer.getInt(edge.source) - node2Layer.getInt(edge.target) if (layerDiff > 0) { @@ -2666,7 +2663,7 @@ class FlowchartLabelProfitModel constructor(private graph: LayoutGraph) { super() this.label2OriginalBox = Maps.createHashMap() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const nll = graph.getLabelLayout(node) for (let i = 0; i < nll.length; i++) { const nlm = nll[i].labelModel @@ -2881,7 +2878,7 @@ class FlowchartAlignmentCalculator { ldp: ILayoutDataProvider ): IDataProvider { const edgeIsAlignable = Maps.createHashedEdgeMap() - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { edgeIsAlignable.setBoolean( edge, this.isAlignable(graph, ldp, edge) && this.isRelevant(graph, edge, ldp) @@ -2905,7 +2902,7 @@ class FlowchartAlignmentCalculator { const BasicEdgeLength = 5 const PenaltyLength = BasicEdgeLength + graph.nodeCount const HighPenaltyLength = PenaltyLength * 8 - graph.edges.forEach(e => { + graph.edges.forEach((e) => { if (hasFlatwisePortConstraint(layoutData.getEdgeData(e)!)) { edgeLength.setInt(e, ZeroLength) } else if (isRealEdge(e, layoutData)) { @@ -2914,7 +2911,7 @@ class FlowchartAlignmentCalculator { edgeLength.setInt(e, BasicDummyEdgeLength) } }) - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { let i: number let edges: Edge[] const nodeData = layoutData.getNodeData(node)! @@ -2964,7 +2961,7 @@ class FlowchartAlignmentCalculator { } } let hasStraightBranch = false - node.edges.forEach(edge => { + node.edges.forEach((edge) => { if (isStraightBranch(graph, edge, layoutData)) { hasStraightBranch = true edgeLength.setInt(edge, edgeLength.getInt(edge) + PenaltyLength) @@ -3041,7 +3038,7 @@ class NodeAlignmentCalculator { const groupNode2EndRep = Maps.createHashMap() const network = new Graph() // create network nodes - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const data = ldp.getNodeData(node) if (data !== null && data.type === NodeDataType.GROUP_BEGIN) { // all groups begin dummies of the same group node are mapped to the same network node @@ -3064,7 +3061,7 @@ class NodeAlignmentCalculator { }) // consider edges const nonAlignableEdges = new EdgeList() - graph.edges.forEach(e => { + graph.edges.forEach((e) => { if (e.selfLoop || (isGroupNodeBorder(e.source, ldp) && isGroupNodeBorder(e.target, ldp))) { return } @@ -3263,7 +3260,7 @@ class NodeAlignmentCalculator { ldp: ILayoutDataProvider ): INodeMap { const node2LaneAlignment = Maps.createHashedNodeMap() - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { node2LaneAlignment.setInt(node, this.getLaneAlignment(node, ldp)) }) return node2LaneAlignment @@ -3488,7 +3485,7 @@ function determineEdgePriorities( const hider = new LayoutGraphHider(graph) try { // hide irrelevant edges - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { edgePriority.setInt(edge, Priority.Basic) if (!edgeIsAlignable.getBoolean(edge)) { hider.hide(edge) @@ -3500,6 +3497,7 @@ function determineEdgePriorities( const gpm = new GraphPartitionManager(graph, node2CompId) try { gpm.hideAll() + // biome-ignore lint/correctness/noUnreachable: Seems to be an incorrect warning for (let i = 0; i < compCount; i++) { gpm.displayPartition(i) const localHider = new LayoutGraphHider(graph) diff --git a/demos/showcase/flowchart/layout/FlowchartLayoutData.js b/demos/showcase/flowchart/layout/FlowchartLayoutData.js index 5a5f46bbc..821088422 100644 --- a/demos/showcase/flowchart/layout/FlowchartLayoutData.js +++ b/demos/showcase/flowchart/layout/FlowchartLayoutData.js @@ -240,14 +240,14 @@ export class FlowchartLayoutData { create(graph) { const data = new GenericLayoutData() - data.addEdgeItemMapping(FlowchartLayout.PREFERRED_DIRECTION_DP_KEY).delegate = edge => + data.addEdgeItemMapping(FlowchartLayout.PREFERRED_DIRECTION_DP_KEY).delegate = (edge) => this.getBranchType(edge) - data.addNodeItemMapping(NODE_TYPE_DP_KEY).delegate = node => this.getType(node) - data.addEdgeItemMapping(EDGE_TYPE_DP_KEY).delegate = edge => this.getType(edge) + data.addNodeItemMapping(NODE_TYPE_DP_KEY).delegate = (node) => this.getType(node) + data.addEdgeItemMapping(EDGE_TYPE_DP_KEY).delegate = (edge) => this.getType(edge) if (graph.groupingSupport.hasGroupNodes()) { - data.addLabelItemMapping(FlowchartLayout.LABEL_LAYOUT_DP_KEY).delegate = label => { + data.addLabelItemMapping(FlowchartLayout.LABEL_LAYOUT_DP_KEY).delegate = (label) => { const node = label.owner return node instanceof INode && label === node.labels.at(0) && !graph.isGroupNode(node) } @@ -264,7 +264,7 @@ export class FlowchartLayoutData { degreeThreshold = 4 } - data.addEdgeItemMapping(PortConstraintKeys.TARGET_GROUP_ID_DP_KEY).delegate = edge => { + data.addEdgeItemMapping(PortConstraintKeys.TARGET_GROUP_ID_DP_KEY).delegate = (edge) => { const node = edge.targetNode return graph.inDegree(node) >= 2 && graph.inDegree(node) >= inDegreeThreshold && diff --git a/demos/showcase/flowchart/model/load-flowchart.js b/demos/showcase/flowchart/model/load-flowchart.js index d6bfe2807..50f477513 100644 --- a/demos/showcase/flowchart/model/load-flowchart.js +++ b/demos/showcase/flowchart/model/load-flowchart.js @@ -54,11 +54,11 @@ export function loadFlowchart(graphComponent, sample) { data: data.nodes, id: 'id', labels: ['label'], - layout: dataItem => + layout: (dataItem) => dataItem.type === 'decision' ? Rect.fromCenter(Point.ORIGIN, new Size(145, 100)) : Rect.fromCenter(Point.ORIGIN, new Size(145, 60)), - style: dataItem => new FlowchartNodeStyle(dataItem.type) + style: (dataItem) => new FlowchartNodeStyle(dataItem.type) } ], edges: [ diff --git a/demos/showcase/flowchart/option-panel/option-panel.css b/demos/showcase/flowchart/option-panel/option-panel.css index 42ae889bb..0c806dbf7 100644 --- a/demos/showcase/flowchart/option-panel/option-panel.css +++ b/demos/showcase/flowchart/option-panel/option-panel.css @@ -128,7 +128,8 @@ font-family: Tahoma, Verdana, sans-serif; font-size: 14px; letter-spacing: 1px; - transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1), + transition: + background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .settings-editor-row5.flat-button:disabled { diff --git a/demos/showcase/frauddetection/FraudDetectionDemo.js b/demos/showcase/frauddetection/FraudDetectionDemo.js index fe72c4cf2..164ef5383 100644 --- a/demos/showcase/frauddetection/FraudDetectionDemo.js +++ b/demos/showcase/frauddetection/FraudDetectionDemo.js @@ -117,7 +117,7 @@ function initializeUI() { const bankFraudDescription = document.querySelector('#bank-fraud-detection') const insuranceFraudDescription = document.querySelector('#insurance-fraud-detection') const samples = document.querySelector('#samples') - samples.addEventListener('change', async event => { + samples.addEventListener('change', async (event) => { clearPropertiesView() // if an inspection view is open, close it closeFraudDetectionView() @@ -196,12 +196,12 @@ async function buildGraph(graph, data) { graph.clear() function convertDates(dates) { - return Array.isArray(dates) ? dates.map(e => new Date(e)) : [new Date(dates)] + return Array.isArray(dates) ? dates.map((e) => new Date(e)) : [new Date(dates)] } const builder = new GraphBuilder(graph) const entityNodesSource = builder.createNodesSource(data.nodesSource, 'id') - entityNodesSource.nodeCreator.tagProvider = entity => ({ + entityNodesSource.nodeCreator.tagProvider = (entity) => ({ ...entity, enter: convertDates(entity.enter), exit: convertDates(entity.exit) @@ -280,7 +280,7 @@ function initializeTimelineComponent(selector, graphComponent) { timeline = new Timeline(selector, getTimeEntry) // filter the elements that are not part of the current timeframe - const filteredGraph = new FilteredGraphWrapper(graphComponent.graph, node => + const filteredGraph = new FilteredGraphWrapper(graphComponent.graph, (node) => timeline.filter(getEntityData(node)) ) graphComponent.graph = filteredGraph @@ -294,25 +294,25 @@ function initializeTimelineComponent(selector, graphComponent) { updateFraudWarnings(fraudsters) }) - timeline.addBarSelectListener(items => { + timeline.addBarSelectListener((items) => { const selection = graphComponent.selection selection.clear() - const selectedItems = new Set(items.map(item => item.id)) - graphComponent.graph.nodes.forEach(node => { + const selectedItems = new Set(items.map((item) => item.id)) + graphComponent.graph.nodes.forEach((node) => { const entity = getEntityData(node) if (selectedItems.has(entity.id)) { selection.setSelected(node, true) } }) }) - timeline.addBarHoverListener(items => { + timeline.addBarHoverListener((items) => { const highlightManager = graphComponent.highlightIndicatorManager highlightManager.clearHighlights() - const selected = new Set(items.map(item => item.id)) + const selected = new Set(items.map((item) => item.id)) - graphComponent.graph.nodes.forEach(node => { + graphComponent.graph.nodes.forEach((node) => { const entity = getEntityData(node) if (selected.has(entity.id)) { highlightManager.addHighlight(node) diff --git a/demos/showcase/frauddetection/FraudDetectionDemo.ts b/demos/showcase/frauddetection/FraudDetectionDemo.ts index 4d3502226..b46a11e75 100644 --- a/demos/showcase/frauddetection/FraudDetectionDemo.ts +++ b/demos/showcase/frauddetection/FraudDetectionDemo.ts @@ -120,7 +120,7 @@ function initializeUI(): void { '#insurance-fraud-detection' )! const samples = document.querySelector('#samples')! - samples.addEventListener('change', async event => { + samples.addEventListener('change', async (event) => { clearPropertiesView() // if an inspection view is open, close it closeFraudDetectionView() @@ -293,25 +293,25 @@ function initializeTimelineComponent( updateFraudWarnings(fraudsters) }) - timeline.addBarSelectListener(items => { + timeline.addBarSelectListener((items) => { const selection = graphComponent.selection selection.clear() - const selectedItems = new Set(items.map(item => item.id)) - graphComponent.graph.nodes.forEach(node => { + const selectedItems = new Set(items.map((item) => item.id)) + graphComponent.graph.nodes.forEach((node) => { const entity = getEntityData(node) if (selectedItems.has(entity.id)) { selection.setSelected(node, true) } }) }) - timeline.addBarHoverListener(items => { + timeline.addBarHoverListener((items) => { const highlightManager = graphComponent.highlightIndicatorManager highlightManager.clearHighlights() - const selected = new Set(items.map(item => item.id)) + const selected = new Set(items.map((item) => item.id)) - graphComponent.graph.nodes.forEach(node => { + graphComponent.graph.nodes.forEach((node) => { const entity = getEntityData(node) if (selected.has(entity.id)) { highlightManager.addHighlight(node) diff --git a/demos/showcase/frauddetection/entity-data.js b/demos/showcase/frauddetection/entity-data.js index 089c2978f..9ebfb61df 100644 --- a/demos/showcase/frauddetection/entity-data.js +++ b/demos/showcase/frauddetection/entity-data.js @@ -105,7 +105,7 @@ export function getInfoMap(node) { return { info: info } } else { const records = {} - Object.keys(info).forEach(key => { + Object.keys(info).forEach((key) => { let value = info[key] if (Array.isArray(value)) { value = value[0] @@ -184,7 +184,7 @@ export function getTimeEntry(item) { * @returns {?INode} */ export function getNode(graph, entityData) { - return graph.nodes.find(node => { + return graph.nodes.find((node) => { const data = getEntityData(node) return data.id === entityData.id }) diff --git a/demos/showcase/frauddetection/entity-data.ts b/demos/showcase/frauddetection/entity-data.ts index 2e2f3ac45..2a1653d13 100644 --- a/demos/showcase/frauddetection/entity-data.ts +++ b/demos/showcase/frauddetection/entity-data.ts @@ -164,7 +164,7 @@ export function getTimeEntry(item: Entity): TimeEntry { } export function getNode(graph: IGraph, entityData: Entity): INode | null { - return graph.nodes.find(node => { + return graph.nodes.find((node) => { const data = getEntityData(node) return data.id === entityData.id }) diff --git a/demos/showcase/frauddetection/fraud-detection/fraud-components.js b/demos/showcase/frauddetection/fraud-detection/fraud-components.js index a45ce59c5..d2ce21a54 100644 --- a/demos/showcase/frauddetection/fraud-detection/fraud-components.js +++ b/demos/showcase/frauddetection/fraud-detection/fraud-components.js @@ -91,7 +91,7 @@ export function updateFraudWarnings(fraudsters) { const currentFraudComponents = new Set() // add fraud warning for new fraud components - fraudsters.forEach(node => { + fraudsters.forEach((node) => { const componentIdx = getComponentIdx(node) if (visibleFraudComponents.indexOf(componentIdx) < 0) { visibleFraudComponents.push(componentIdx) @@ -108,7 +108,7 @@ export function updateFraudWarnings(fraudsters) { visibleFraudComponents.splice(i, 1) const componentNodes = getComponentNodes(componentIdx) // remove highlight from the component related to the removed warning sign - componentNodes.forEach(node => { + componentNodes.forEach((node) => { fraudHighlightManager.removeHighlight(node) }) removeFraudWarning(componentIdx) @@ -137,7 +137,7 @@ function createFraudWarning(componentIdx) { warningButton.addEventListener('click', () => openFraudDetectionView(componentIdx, graphComponent) ) - warningButton.addEventListener('mouseover', event => + warningButton.addEventListener('mouseover', (event) => addFraudComponentHighlight(parseInt(event.currentTarget.id)) ) warningButton.addEventListener('mouseleave', () => removeFraudComponentHighlight()) @@ -200,7 +200,7 @@ async function animateViewPort(componentIdx) { let maxX = Number.NEGATIVE_INFINITY let minY = Number.POSITIVE_INFINITY let maxY = Number.NEGATIVE_INFINITY - componentNodes.forEach(node => { + componentNodes.forEach((node) => { if (graphComponent.graph.contains(node) && isFraud(node)) { const { x, y, width, height } = node.layout minX = Math.min(minX, x) @@ -209,7 +209,12 @@ async function animateViewPort(componentIdx) { maxY = Math.max(maxY, y + height) } }) - if (isFinite(minX) && isFinite(maxX) && isFinite(minY) && isFinite(maxY)) { + if ( + Number.isFinite(minX) && + Number.isFinite(maxX) && + Number.isFinite(minY) && + Number.isFinite(maxY) + ) { let rect = new Rect(minX, minY, maxX - minX, maxY - minY) if (graphComponent.viewport.contains(rect) && graphComponent.zoom > 0.8) { return @@ -269,12 +274,12 @@ function updateFraudHighlights(item, oldItem) { function highlightFraudComponent(componentIndex) { const componentNodes = getComponentNodes(componentIndex) const componentNodesSet = new Set(componentNodes) - graphComponent.graph.edges.forEach(edge => { + graphComponent.graph.edges.forEach((edge) => { if (componentNodesSet.has(edge.sourceNode) && isFraud(edge)) { fraudHighlightManager.addHighlight(edge) } }) - componentNodes.forEach(node => { + componentNodes.forEach((node) => { if (isFraud(node)) { fraudHighlightManager.addHighlight(node) } @@ -304,11 +309,11 @@ export function calculateComponents() { // for bank fraud, we remove the bank branch nodes to avoid having // large components that contain nodes that have no actual relationship with each other const result = new ConnectedComponents({ - subgraphNodes: node => !bankFraud || getEntityData(node).type !== 'Bank Branch' + subgraphNodes: (node) => !bankFraud || getEntityData(node).type !== 'Bank Branch' }).run(fullGraph) const nodeComponentIds = result.nodeComponentIds - fullGraph.nodes.forEach(node => { + fullGraph.nodes.forEach((node) => { const componentIdx = nodeComponentIds.get(node) node2Component.set(node, componentIdx) if (!component2Nodes.get(componentIdx)) { @@ -320,9 +325,9 @@ export function calculateComponents() { if (bankFraud) { // we un-hide the bank branch nodes // and add them to the components to which their neighbor nodes belong - fullGraph.nodes.forEach(node => { + fullGraph.nodes.forEach((node) => { if (getEntityData(node).type === 'Bank Branch') { - fullGraph.edgesAt(node).forEach(edge => { + fullGraph.edgesAt(node).forEach((edge) => { const sourceNode = edge.sourceNode const targetNode = edge.targetNode const componentIdx = diff --git a/demos/showcase/frauddetection/fraud-detection/fraud-components.ts b/demos/showcase/frauddetection/fraud-detection/fraud-components.ts index 5d2db64ee..743923ea2 100644 --- a/demos/showcase/frauddetection/fraud-detection/fraud-components.ts +++ b/demos/showcase/frauddetection/fraud-detection/fraud-components.ts @@ -88,7 +88,7 @@ export function updateFraudWarnings(fraudsters: INode[]): void { const currentFraudComponents = new Set() // add fraud warning for new fraud components - fraudsters.forEach(node => { + fraudsters.forEach((node) => { const componentIdx = getComponentIdx(node) if (visibleFraudComponents.indexOf(componentIdx) < 0) { visibleFraudComponents.push(componentIdx) @@ -105,7 +105,7 @@ export function updateFraudWarnings(fraudsters: INode[]): void { visibleFraudComponents.splice(i, 1) const componentNodes = getComponentNodes(componentIdx) // remove highlight from the component related to the removed warning sign - componentNodes.forEach(node => { + componentNodes.forEach((node) => { fraudHighlightManager.removeHighlight(node) }) removeFraudWarning(componentIdx) @@ -134,7 +134,7 @@ function createFraudWarning(componentIdx: number): void { warningButton.addEventListener('click', () => openFraudDetectionView(componentIdx, graphComponent) ) - warningButton.addEventListener('mouseover', event => + warningButton.addEventListener('mouseover', (event) => addFraudComponentHighlight(parseInt((event.currentTarget as HTMLElement).id)) ) warningButton.addEventListener('mouseleave', () => removeFraudComponentHighlight()) @@ -188,7 +188,7 @@ async function animateViewPort(componentIdx: number): Promise { let maxX: number = Number.NEGATIVE_INFINITY let minY: number = Number.POSITIVE_INFINITY let maxY: number = Number.NEGATIVE_INFINITY - componentNodes.forEach(node => { + componentNodes.forEach((node) => { if (graphComponent.graph.contains(node) && isFraud(node)) { const { x, y, width, height } = node.layout minX = Math.min(minX, x) @@ -197,7 +197,7 @@ async function animateViewPort(componentIdx: number): Promise { maxY = Math.max(maxY, y + height) } }) - if (isFinite(minX) && isFinite(maxX) && isFinite(minY) && isFinite(maxY)) { + if (Number.isFinite(minX) && Number.isFinite(maxX) && Number.isFinite(minY) && Number.isFinite(maxY)) { let rect: Rect = new Rect(minX, minY, maxX - minX, maxY - minY) if (graphComponent.viewport.contains(rect) && graphComponent.zoom > 0.8) { return @@ -252,12 +252,12 @@ function updateFraudHighlights(item: IModelItem | null, oldItem: IModelItem | nu function highlightFraudComponent(componentIndex: number): void { const componentNodes = getComponentNodes(componentIndex) const componentNodesSet = new Set(componentNodes) - graphComponent.graph.edges.forEach(edge => { + graphComponent.graph.edges.forEach((edge) => { if (componentNodesSet.has(edge.sourceNode!) && isFraud(edge)) { fraudHighlightManager.addHighlight(edge) } }) - componentNodes.forEach(node => { + componentNodes.forEach((node) => { if (isFraud(node)) { fraudHighlightManager.addHighlight(node) } @@ -287,11 +287,11 @@ export function calculateComponents(): void { // for bank fraud, we remove the bank branch nodes to avoid having // large components that contain nodes that have no actual relationship with each other const result = new ConnectedComponents({ - subgraphNodes: node => !bankFraud || getEntityData(node).type !== 'Bank Branch' + subgraphNodes: (node) => !bankFraud || getEntityData(node).type !== 'Bank Branch' }).run(fullGraph) const nodeComponentIds = result.nodeComponentIds - fullGraph.nodes.forEach(node => { + fullGraph.nodes.forEach((node) => { const componentIdx = nodeComponentIds.get(node) node2Component.set(node, componentIdx) if (!component2Nodes.get(componentIdx)) { @@ -303,9 +303,9 @@ export function calculateComponents(): void { if (bankFraud) { // we un-hide the bank branch nodes // and add them to the components to which their neighbor nodes belong - fullGraph.nodes.forEach(node => { + fullGraph.nodes.forEach((node) => { if (getEntityData(node).type === 'Bank Branch') { - fullGraph.edgesAt(node).forEach(edge => { + fullGraph.edgesAt(node).forEach((edge) => { const sourceNode = edge.sourceNode! const targetNode = edge.targetNode! const componentIdx = diff --git a/demos/showcase/frauddetection/fraud-detection/fraud-detection.js b/demos/showcase/frauddetection/fraud-detection/fraud-detection.js index 918ebcbdf..86270cb4f 100644 --- a/demos/showcase/frauddetection/fraud-detection/fraud-detection.js +++ b/demos/showcase/frauddetection/fraud-detection/fraud-detection.js @@ -50,7 +50,7 @@ export function detectBankFraud(graphComponent) { const result = new CycleEdges({ directed: false, // only consider "non-bank branch" nodes to avoid finding cycles other than fraud cycles - subgraphNodes: node => getEntityData(node).type !== 'Bank Branch' + subgraphNodes: (node) => getEntityData(node).type !== 'Bank Branch' }).run(graph) for (const edge of result.edges) { @@ -90,11 +90,11 @@ export function detectInsuranceFraud(graphComponent) { const fraudsterNodes = [] const result = new ConnectedComponents().run(graph) - result.components.forEach(component => { + result.components.forEach((component) => { const node2Accidents = new Mapper() let involvedAccidents = 0 - component.nodes.forEach(node => { + component.nodes.forEach((node) => { const entityData = getEntityData(node) if (entityData.type === 'Accident') { involvedAccidents++ @@ -160,7 +160,7 @@ export function detectInsuranceFraud(graphComponent) { updateNodeFraudTag(person, true) fraudsterNodes.push(person) - graph.edgesAt(person).forEach(edge => { + graph.edgesAt(person).forEach((edge) => { updateEdgeFraudTag(edge, true) }) } @@ -214,10 +214,10 @@ function updateEdgeFraudTag(edge, isFraud) { */ function resetTags(graphComponent) { const graph = graphComponent.graph - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { updateNodeFraudTag(node, false) }) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { updateEdgeFraudTag(edge, false) }) } diff --git a/demos/showcase/frauddetection/fraud-detection/fraud-detection.ts b/demos/showcase/frauddetection/fraud-detection/fraud-detection.ts index d5fd06c0c..d2ab3c7e6 100644 --- a/demos/showcase/frauddetection/fraud-detection/fraud-detection.ts +++ b/demos/showcase/frauddetection/fraud-detection/fraud-detection.ts @@ -50,7 +50,7 @@ export function detectBankFraud(graphComponent: GraphComponent): INode[] { const result = new CycleEdges({ directed: false, // only consider "non-bank branch" nodes to avoid finding cycles other than fraud cycles - subgraphNodes: node => getEntityData(node).type !== 'Bank Branch' + subgraphNodes: (node) => getEntityData(node).type !== 'Bank Branch' }).run(graph) for (const edge of result.edges) { @@ -88,11 +88,11 @@ export function detectInsuranceFraud(graphComponent: GraphComponent): INode[] { const fraudsterNodes: INode[] = [] const result = new ConnectedComponents().run(graph) - result.components.forEach(component => { + result.components.forEach((component) => { const node2Accidents = new Mapper() let involvedAccidents = 0 - component.nodes.forEach(node => { + component.nodes.forEach((node) => { const entityData = getEntityData(node) if (entityData.type === 'Accident') { involvedAccidents++ @@ -158,7 +158,7 @@ export function detectInsuranceFraud(graphComponent: GraphComponent): INode[] { updateNodeFraudTag(person, true) fraudsterNodes.push(person) - graph.edgesAt(person).forEach(edge => { + graph.edgesAt(person).forEach((edge) => { updateEdgeFraudTag(edge, true) }) } @@ -207,10 +207,10 @@ function updateEdgeFraudTag(edge: IEdge, isFraud: boolean): void { */ function resetTags(graphComponent: GraphComponent): void { const graph = graphComponent.graph - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { updateNodeFraudTag(node, false) }) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { updateEdgeFraudTag(edge, false) }) } diff --git a/demos/showcase/frauddetection/fraud-detection/inspection-view.js b/demos/showcase/frauddetection/fraud-detection/inspection-view.js index df6543d58..c1b49dd00 100644 --- a/demos/showcase/frauddetection/fraud-detection/inspection-view.js +++ b/demos/showcase/frauddetection/fraud-detection/inspection-view.js @@ -160,7 +160,7 @@ function initializeInputMode() { const selection = evt.selection for (const item of selection) { if (item instanceof INode) { - filteredGraph.edgesAt(item, AdjacencyTypes.ALL).forEach(edge => { + filteredGraph.edgesAt(item, AdjacencyTypes.ALL).forEach((edge) => { if (!selection.isSelected(edge.opposite(item))) { incrementalNodes.push(edge.opposite(item)) } @@ -191,7 +191,7 @@ function copyGraph(graph, componentNodes) { const graphCopier = new GraphCopier() graphCopier.copy( graph, - item => + (item) => !INode.isInstance(item) || (componentNodes.has(item) && getEntityData(item).type !== 'Bank Branch'), fraudDetectionComponent.graph, @@ -205,13 +205,13 @@ function copyGraph(graph, componentNodes) { function initializeTimelineComponent() { fraudDetectionTimeline = new Timeline('fraud-detection-timeline-component', getTimeEntry) fraudDetectionTimeline.items = fraudDetectionComponent.graph.nodes.map(getEntityData).toArray() - fraudDetectionTimeline.addBarHoverListener(nodes => { + fraudDetectionTimeline.addBarHoverListener((nodes) => { const highlightManager = fraudDetectionComponent.highlightIndicatorManager highlightManager.clearHighlights() - const selected = new Set(nodes.map(node => node.id)) + const selected = new Set(nodes.map((node) => node.id)) - fraudDetectionComponent.graph.nodes.forEach(node => { + fraudDetectionComponent.graph.nodes.forEach((node) => { const entity = getEntityData(node) if (selected.has(entity.id)) { highlightManager.addHighlight(node) @@ -219,7 +219,7 @@ function initializeTimelineComponent() { }) }) - fraudDetectionTimeline.addBarSelectListener(nodes => { + fraudDetectionTimeline.addBarSelectListener((nodes) => { fraudDetectionComponent.selection.clear() if (nodes.length > 0) { let minX = Number.POSITIVE_INFINITY @@ -227,9 +227,9 @@ function initializeTimelineComponent() { let minY = Number.POSITIVE_INFINITY let maxY = Number.NEGATIVE_INFINITY nodes - .map(node => getNode(fraudDetectionComponent.graph, node)) - .filter(node => filteredGraph.contains(node)) - .forEach(node => { + .map((node) => getNode(fraudDetectionComponent.graph, node)) + .filter((node) => filteredGraph.contains(node)) + .forEach((node) => { fraudDetectionComponent.selection.setSelected(node, true) const { x, y, width, height } = node.layout minX = Math.min(minX, x) @@ -262,7 +262,7 @@ function initializeTimelineComponent() { */ function initializeGraph(graph) { // get the graph from the timeline component - filteredGraph = new FilteredGraphWrapper(graph, node => { + filteredGraph = new FilteredGraphWrapper(graph, (node) => { const visible = fraudDetectionTimeline.filter(getEntityData(node)) if (!visible) { @@ -278,7 +278,7 @@ function initializeGraph(graph) { getEntityData(node).type === 'Bank Branch' && !filteredGraph.wrappedGraph .neighbors(node) - .every(neighbor => fraudDetectionTimeline.filter(getEntityData(neighbor))) + .every((neighbor) => fraudDetectionTimeline.filter(getEntityData(neighbor))) ) { return false } @@ -448,14 +448,6 @@ export function closeFraudDetectionView() { * neighbor that is already placed. */ class InitialPositionsStage extends LayoutStageBase { - /** - * Creates a new instance of InitialPositionsStage. - * @param {!ILayoutAlgorithm} layout - */ - constructor(layout) { - super(layout) - } - /** * Applies the layout * @param {!LayoutGraph} graph The graph to be laid out. @@ -463,7 +455,7 @@ class InitialPositionsStage extends LayoutStageBase { applyLayout(graph) { const nodesAdded = graph.getDataProvider('NODES_ADDED') - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (nodesAdded.getBoolean(node)) { const visited = new Set() const stack = [node] diff --git a/demos/showcase/frauddetection/fraud-detection/inspection-view.ts b/demos/showcase/frauddetection/fraud-detection/inspection-view.ts index 14032b990..c7c4b76bf 100644 --- a/demos/showcase/frauddetection/fraud-detection/inspection-view.ts +++ b/demos/showcase/frauddetection/fraud-detection/inspection-view.ts @@ -152,7 +152,7 @@ function initializeInputMode(): void { const selection = evt.selection for (const item of selection) { if (item instanceof INode) { - filteredGraph.edgesAt(item, AdjacencyTypes.ALL).forEach(edge => { + filteredGraph.edgesAt(item, AdjacencyTypes.ALL).forEach((edge) => { if (!selection.isSelected(edge.opposite(item))) { incrementalNodes.push(edge.opposite(item) as INode) } @@ -181,7 +181,7 @@ function copyGraph(graph: IGraph, componentNodes: Set): void { const graphCopier = new GraphCopier() graphCopier.copy( graph, - item => + (item) => !INode.isInstance(item) || (componentNodes.has(item) && getEntityData(item).type !== 'Bank Branch'), fraudDetectionComponent.graph, @@ -195,13 +195,13 @@ function copyGraph(graph: IGraph, componentNodes: Set): void { function initializeTimelineComponent(): void { fraudDetectionTimeline = new Timeline('fraud-detection-timeline-component', getTimeEntry) fraudDetectionTimeline.items = fraudDetectionComponent.graph.nodes.map(getEntityData).toArray() - fraudDetectionTimeline.addBarHoverListener(nodes => { + fraudDetectionTimeline.addBarHoverListener((nodes) => { const highlightManager = fraudDetectionComponent.highlightIndicatorManager highlightManager.clearHighlights() - const selected = new Set(nodes.map(node => node.id)) + const selected = new Set(nodes.map((node) => node.id)) - fraudDetectionComponent.graph.nodes.forEach(node => { + fraudDetectionComponent.graph.nodes.forEach((node) => { const entity = getEntityData(node) if (selected.has(entity.id)) { highlightManager.addHighlight(node) @@ -209,7 +209,7 @@ function initializeTimelineComponent(): void { }) }) - fraudDetectionTimeline.addBarSelectListener(nodes => { + fraudDetectionTimeline.addBarSelectListener((nodes) => { fraudDetectionComponent.selection.clear() if (nodes.length > 0) { let minX: number = Number.POSITIVE_INFINITY @@ -217,9 +217,9 @@ function initializeTimelineComponent(): void { let minY: number = Number.POSITIVE_INFINITY let maxY: number = Number.NEGATIVE_INFINITY nodes - .map(node => getNode(fraudDetectionComponent.graph, node)) - .filter(node => filteredGraph.contains(node)) - .forEach(node => { + .map((node) => getNode(fraudDetectionComponent.graph, node)) + .filter((node) => filteredGraph.contains(node)) + .forEach((node) => { fraudDetectionComponent.selection.setSelected(node!, true) const { x, y, width, height } = node!.layout minX = Math.min(minX, x) @@ -251,7 +251,7 @@ function initializeTimelineComponent(): void { */ function initializeGraph(graph: IGraph): void { // get the graph from the timeline component - filteredGraph = new FilteredGraphWrapper(graph, node => { + filteredGraph = new FilteredGraphWrapper(graph, (node) => { const visible = fraudDetectionTimeline.filter(getEntityData(node)) if (!visible) { @@ -267,7 +267,7 @@ function initializeGraph(graph: IGraph): void { getEntityData(node).type === 'Bank Branch' && !filteredGraph .wrappedGraph!.neighbors(node) - .every(neighbor => fraudDetectionTimeline.filter(getEntityData(neighbor))) + .every((neighbor) => fraudDetectionTimeline.filter(getEntityData(neighbor))) ) { return false } @@ -436,12 +436,6 @@ export function closeFraudDetectionView(): void { * neighbor that is already placed. */ class InitialPositionsStage extends LayoutStageBase { - /** - * Creates a new instance of InitialPositionsStage. - */ - constructor(layout: ILayoutAlgorithm) { - super(layout) - } /** * Applies the layout @@ -450,7 +444,7 @@ class InitialPositionsStage extends LayoutStageBase { applyLayout(graph: LayoutGraph): void { const nodesAdded = graph.getDataProvider('NODES_ADDED')! - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (nodesAdded.getBoolean(node)) { const visited = new Set() const stack = [node] diff --git a/demos/showcase/frauddetection/index.html b/demos/showcase/frauddetection/index.html index 504a16ac1..28ebbd9a1 100644 --- a/demos/showcase/frauddetection/index.html +++ b/demos/showcase/frauddetection/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/frauddetection/interactive-layout.js b/demos/showcase/frauddetection/interactive-layout.js index df907cb84..731466774 100644 --- a/demos/showcase/frauddetection/interactive-layout.js +++ b/demos/showcase/frauddetection/interactive-layout.js @@ -79,16 +79,16 @@ export function initializeLayout(gc) { function prepareInteraction() { const inputMode = graphComponent.inputMode const moveUnselectedInputMode = inputMode.moveUnselectedInputMode - moveUnselectedInputMode.addDragStartedListener(moveInputMode => { + moveUnselectedInputMode.addDragStartedListener((moveInputMode) => { restartLayout(moveInputMode.affectedItems.at(0)) }) - moveUnselectedInputMode.addDraggedListener(moveInputMode => { + moveUnselectedInputMode.addDraggedListener((moveInputMode) => { updateDraggedComponent(moveInputMode.affectedItems.at(0)) }) - moveUnselectedInputMode.addDragCanceledListener(moveInputMode => { + moveUnselectedInputMode.addDragCanceledListener((moveInputMode) => { setFinalNodeLocation(moveInputMode.affectedItems.at(0)) }) - moveUnselectedInputMode.addDragFinishedListener(moveInputMode => { + moveUnselectedInputMode.addDragFinishedListener((moveInputMode) => { setFinalNodeLocation(moveInputMode.affectedItems.at(0)) }) } @@ -137,7 +137,7 @@ export function startLayout() { // make the nodes unmovable at the beginning, // so that the layout of the graph is maintained as it is in the initial layout - copiedLayoutGraph.nodes.forEach(node => { + copiedLayoutGraph.nodes.forEach((node) => { organicLayout.setInertia(node, 1) }) @@ -149,7 +149,7 @@ export function startLayout() { // configure how the new edges with their source/target node can move when an edge is added in the graph if (edgesAdded.length > 0) { - edgesAdded.forEach(edge => { + edgesAdded.forEach((edge) => { const copiedSource = copiedLayoutGraph.getCopiedNode(edge.sourceNode) const copiedTarget = copiedLayoutGraph.getCopiedNode(edge.targetNode) @@ -184,7 +184,7 @@ export function startLayout() { // configure how the new node can move when they are added in the graph if (nodesAdded.length > 0) { // configure how the new nodes can be moved - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (nodesAdded.includes(node)) { const copiedNode = copiedLayoutGraph.getCopiedNode(node) if (copiedNode) { @@ -200,7 +200,7 @@ export function startLayout() { // configure how the source/target nodes of an edge can move when an edge is removed from the graph if (edgesRemoved.length > 0) { - edgesRemoved.forEach(edge => { + edgesRemoved.forEach((edge) => { const sourceNode = edge.sourceNode if (graph.contains(sourceNode)) { const copiedSource = copiedLayoutGraph.getCopiedNode(sourceNode) @@ -347,7 +347,7 @@ function updateStressAndInertiaForOtherNodes(draggedNode) { graphComponent.graph ) - movedComponent.reachableNodes.forEach(node => { + movedComponent.reachableNodes.forEach((node) => { const copiedNode = copiedLayoutGraph.getCopiedNode(node) if (copiedNode && copiedNode !== copiedMovedNode) { // allow the nodes of the moved component to move close to the dragged node diff --git a/demos/showcase/frauddetection/interactive-layout.ts b/demos/showcase/frauddetection/interactive-layout.ts index 83f3f5cc0..15d19fa03 100644 --- a/demos/showcase/frauddetection/interactive-layout.ts +++ b/demos/showcase/frauddetection/interactive-layout.ts @@ -83,16 +83,16 @@ export function initializeLayout(gc: GraphComponent): void { function prepareInteraction(): void { const inputMode = graphComponent.inputMode as GraphEditorInputMode const moveUnselectedInputMode = inputMode.moveUnselectedInputMode - moveUnselectedInputMode.addDragStartedListener(moveInputMode => { + moveUnselectedInputMode.addDragStartedListener((moveInputMode) => { restartLayout(moveInputMode.affectedItems.at(0) as INode) }) - moveUnselectedInputMode.addDraggedListener(moveInputMode => { + moveUnselectedInputMode.addDraggedListener((moveInputMode) => { updateDraggedComponent(moveInputMode.affectedItems.at(0) as INode) }) - moveUnselectedInputMode.addDragCanceledListener(moveInputMode => { + moveUnselectedInputMode.addDragCanceledListener((moveInputMode) => { setFinalNodeLocation(moveInputMode.affectedItems.at(0) as INode) }) - moveUnselectedInputMode.addDragFinishedListener(moveInputMode => { + moveUnselectedInputMode.addDragFinishedListener((moveInputMode) => { setFinalNodeLocation(moveInputMode.affectedItems.at(0) as INode) }) } @@ -141,7 +141,7 @@ export function startLayout(): void { // make the nodes unmovable at the beginning, // so that the layout of the graph is maintained as it is in the initial layout - copiedLayoutGraph.nodes.forEach(node => { + copiedLayoutGraph.nodes.forEach((node) => { organicLayout.setInertia(node, 1) }) @@ -153,7 +153,7 @@ export function startLayout(): void { // configure how the new edges with their source/target node can move when an edge is added in the graph if (edgesAdded.length > 0) { - edgesAdded.forEach(edge => { + edgesAdded.forEach((edge) => { const copiedSource = copiedLayoutGraph.getCopiedNode(edge.sourceNode) const copiedTarget = copiedLayoutGraph.getCopiedNode(edge.targetNode) @@ -188,7 +188,7 @@ export function startLayout(): void { // configure how the new node can move when they are added in the graph if (nodesAdded.length > 0) { // configure how the new nodes can be moved - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (nodesAdded.includes(node)) { const copiedNode = copiedLayoutGraph.getCopiedNode(node) if (copiedNode) { @@ -204,7 +204,7 @@ export function startLayout(): void { // configure how the source/target nodes of an edge can move when an edge is removed from the graph if (edgesRemoved.length > 0) { - edgesRemoved.forEach(edge => { + edgesRemoved.forEach((edge) => { const sourceNode = edge.sourceNode if (graph.contains(sourceNode)) { const copiedSource = copiedLayoutGraph.getCopiedNode(sourceNode) @@ -343,7 +343,7 @@ function updateStressAndInertiaForOtherNodes(draggedNode: INode): void { graphComponent.graph ) - movedComponent.reachableNodes.forEach(node => { + movedComponent.reachableNodes.forEach((node) => { const copiedNode = copiedLayoutGraph.getCopiedNode(node) if (copiedNode && copiedNode !== copiedMovedNode) { // allow the nodes of the moved component to move close to the dragged node diff --git a/demos/showcase/frauddetection/properties-view.js b/demos/showcase/frauddetection/properties-view.js index 9a075d947..ecc1c6ea7 100644 --- a/demos/showcase/frauddetection/properties-view.js +++ b/demos/showcase/frauddetection/properties-view.js @@ -125,7 +125,7 @@ function showEntityProperties(node, parentContainer) { tableContainer.appendChild(table) const infoMap = getInfoMap(node) - Object.keys(infoMap).forEach(key => { + Object.keys(infoMap).forEach((key) => { const tr = document.createElement('tr') tr.appendChild(createElement('td', key)) tr.appendChild(createElement('td', infoMap[key])) diff --git a/demos/showcase/frauddetection/styles/ConnectionEdgeStyle.js b/demos/showcase/frauddetection/styles/ConnectionEdgeStyle.js index 7d9ed4160..8265902ee 100644 --- a/demos/showcase/frauddetection/styles/ConnectionEdgeStyle.js +++ b/demos/showcase/frauddetection/styles/ConnectionEdgeStyle.js @@ -102,7 +102,7 @@ class EdgeRenderVisual extends HtmlCanvasVisual { ctx.beginPath() let location = this.edge.sourcePort.location ctx.moveTo(location.x, location.y) - this.edge.bends.forEach(bend => { + this.edge.bends.forEach((bend) => { location = bend.location ctx.lineTo(location.x, location.y) }) diff --git a/demos/showcase/frauddetection/styles/ConnectionEdgeStyle.ts b/demos/showcase/frauddetection/styles/ConnectionEdgeStyle.ts index d0565686c..8d76fe5d2 100644 --- a/demos/showcase/frauddetection/styles/ConnectionEdgeStyle.ts +++ b/demos/showcase/frauddetection/styles/ConnectionEdgeStyle.ts @@ -96,7 +96,7 @@ class EdgeRenderVisual extends HtmlCanvasVisual { ctx.beginPath() let location: IPoint = this.edge.sourcePort!.location ctx.moveTo(location.x, location.y) - this.edge.bends.forEach(bend => { + this.edge.bends.forEach((bend) => { location = bend.location ctx.lineTo(location.x, location.y) }) diff --git a/demos/showcase/frauddetection/styles/initialize-webgl-styles.js b/demos/showcase/frauddetection/styles/initialize-webgl-styles.js index 94417855f..823e8716e 100644 --- a/demos/showcase/frauddetection/styles/initialize-webgl-styles.js +++ b/demos/showcase/frauddetection/styles/initialize-webgl-styles.js @@ -142,11 +142,11 @@ export function setWebGL2Styles(graphComponent) { return } const graph = graphComponent.graph - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { updateNodeStyle(graphComponent, node) }) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { updateEdgeStyle(graphComponent, edge) }) } diff --git a/demos/showcase/frauddetection/styles/initialize-webgl-styles.ts b/demos/showcase/frauddetection/styles/initialize-webgl-styles.ts index 9391aeec9..50659e31d 100644 --- a/demos/showcase/frauddetection/styles/initialize-webgl-styles.ts +++ b/demos/showcase/frauddetection/styles/initialize-webgl-styles.ts @@ -135,11 +135,11 @@ export function setWebGL2Styles(graphComponent: GraphComponent): void { return } const graph = graphComponent.graph - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { updateNodeStyle(graphComponent, node) }) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { updateEdgeStyle(graphComponent, edge) }) } diff --git a/demos/showcase/frauddetection/timeline/Styling.js b/demos/showcase/frauddetection/timeline/Styling.js index 478ae480c..e8bc31cda 100644 --- a/demos/showcase/frauddetection/timeline/Styling.js +++ b/demos/showcase/frauddetection/timeline/Styling.js @@ -90,7 +90,7 @@ export class Styling { nodeDecorator.focusIndicatorDecorator.hideImplementation() nodeDecorator.highlightDecorator.hideImplementation() nodeDecorator.selectionDecorator.setFactory( - node => + (node) => new NodeStyleDecorationInstaller({ nodeStyle: graphComponent.graph.isGroupNode(node) ? new ShapeNodeStyle(style.sectionSelect ?? defaultStyling.sectionSelect) diff --git a/demos/showcase/frauddetection/timeline/Styling.ts b/demos/showcase/frauddetection/timeline/Styling.ts index fcc141e6f..1a85cd2bc 100644 --- a/demos/showcase/frauddetection/timeline/Styling.ts +++ b/demos/showcase/frauddetection/timeline/Styling.ts @@ -103,7 +103,7 @@ export class Styling { nodeDecorator.focusIndicatorDecorator.hideImplementation() nodeDecorator.highlightDecorator.hideImplementation() nodeDecorator.selectionDecorator.setFactory( - node => + (node) => new NodeStyleDecorationInstaller({ nodeStyle: graphComponent.graph.isGroupNode(node) ? new ShapeNodeStyle(style.sectionSelect ?? defaultStyling.sectionSelect) diff --git a/demos/showcase/frauddetection/timeline/TimeframeRectangle.ts b/demos/showcase/frauddetection/timeline/TimeframeRectangle.ts index 1e99ebb1b..406ce155b 100644 --- a/demos/showcase/frauddetection/timeline/TimeframeRectangle.ts +++ b/demos/showcase/frauddetection/timeline/TimeframeRectangle.ts @@ -180,7 +180,10 @@ class RectangleVisual extends BaseClass(IVisualCreator) { * @param rectangle The rectangle that determines the bounds of this visual object. * @param style The styling for the rectangle */ - constructor(public rectangle: MutableRectangle, private readonly style?: TimeFrameStyle) { + constructor( + public rectangle: MutableRectangle, + private readonly style?: TimeFrameStyle + ) { super() } @@ -244,7 +247,10 @@ class RectangleVisual extends BaseClass(IVisualCreator) { class RectanglePositionHandler extends BaseClass(IPositionHandler) { private initialPosition = new Point(0, 0) - constructor(private readonly rectangle: IMutableRectangle, public limits: Rect = Rect.INFINITE) { + constructor( + private readonly rectangle: IMutableRectangle, + public limits: Rect = Rect.INFINITE + ) { super() } diff --git a/demos/showcase/frauddetection/timeline/Timeline.js b/demos/showcase/frauddetection/timeline/Timeline.js index b63830c48..e0317b335 100644 --- a/demos/showcase/frauddetection/timeline/Timeline.js +++ b/demos/showcase/frauddetection/timeline/Timeline.js @@ -201,7 +201,7 @@ export default class Timeline { get selectedItems() { return this.graphComponent.selection.selectedNodes .toArray() - .flatMap(selectedNode => getItemsFromBucket(selectedNode)) + .flatMap((selectedNode) => getItemsFromBucket(selectedNode)) } /** @@ -318,7 +318,7 @@ export default class Timeline { }) // install a tooltip on the timeline items that reports the content of the possibly aggregated entry - initializeToolTips(inputMode, item => { + initializeToolTips(inputMode, (item) => { if (item instanceof INode) { const bucket = getBucket(item) if (bucket.label !== undefined) { @@ -354,7 +354,7 @@ export default class Timeline { } } }) - inputMode.addCanvasClickedListener(_ => { + inputMode.addCanvasClickedListener((_) => { this.barSelectListener?.([]) }) @@ -596,16 +596,16 @@ export default class Timeline { */ initializeGraphBuilder(masterGraph) { const graphBuilder = new GraphBuilder(masterGraph) - const getBucketId = b => `${b.layer}-${b.start.getTime()}-${b.end.getTime()}` + const getBucketId = (b) => `${b.layer}-${b.start.getTime()}-${b.end.getTime()}` const nodesSource = graphBuilder.createGroupNodesSource({ data: this.buckets, id: getBucketId, - parentId: b => (b.parent ? getBucketId(b.parent) : null) + parentId: (b) => (b.parent ? getBucketId(b.parent) : null) }) const nodeCreator = nodesSource.nodeCreator nodeCreator.createLabelsSource({ - data: b => (b.label != null ? [b] : []), + data: (b) => (b.label != null ? [b] : []), text: 'label' }) @@ -627,7 +627,7 @@ export default class Timeline { rectangleIndicator.setBounds(graphComponent.contentRect) rectangleIndicator.limits = graphComponent.contentRect - rectangleIndicator.addBoundsChangedListener(bounds => { + rectangleIndicator.addBoundsChangedListener((bounds) => { this.updateTimeframe(bounds) }) @@ -657,8 +657,8 @@ export default class Timeline { getTimeframeFromBounds(bounds) { const graph = this.graphComponent.graph const nodesInFrame = graph.nodes - .filter(node => !graph.isGroupNode(node)) - .filter(node => bounds.contains(node.layout.center)) + .filter((node) => !graph.isGroupNode(node)) + .filter((node) => bounds.contains(node.layout.center)) if (nodesInFrame.size === 0) { // no nodes in timeframe, this returns an "empty" timeframe to trigger the update @@ -710,8 +710,8 @@ export default class Timeline { const graphComponent = this.graphComponent const graph = graphComponent.graph const nodesInTimeframe = graph.nodes - .filter(node => !graph.isGroupNode(node)) - .filter(node => { + .filter((node) => !graph.isGroupNode(node)) + .filter((node) => { const bucket = getBucket(node) return intervalsIntersect(bucket.start, bucket.end, timeframe[0], timeframe[1]) }) @@ -725,7 +725,7 @@ export default class Timeline { let minX = Number.POSITIVE_INFINITY let maxX = Number.NEGATIVE_INFINITY - nodesInTimeframe.forEach(current => { + nodesInTimeframe.forEach((current) => { minX = Math.min(minX, current.layout.x) maxX = Math.max(maxX, current.layout.maxX) }) @@ -778,7 +778,7 @@ export default class Timeline { const timeEntry = this.getTimeEntry(item) if (Array.isArray(timeEntry)) { - return timeEntry.some(entry => { + return timeEntry.some((entry) => { if (typeof entry === 'number') { const time = entry return start <= time && time < end @@ -926,7 +926,7 @@ export default class Timeline { ) { // create a new animation object if there is none or if the timeframe has changed this.timeframeAnimation = new TimeframeAnimation(this.timeframeRect.rect, this.graphComponent) - this.timeframeAnimation.addTimeframeListener(rect => this.updateTimeframe(rect)) + this.timeframeAnimation.addTimeframeListener((rect) => this.updateTimeframe(rect)) this.timeframeAnimation.addAnimationEndedListener(() => { // stop the animation and revert the state of the play button if (this.showPlayButton && !(this.timeframeAnimation?.animating ?? false)) { @@ -982,7 +982,7 @@ export default class Timeline { }, true ) - playButton.addEventListener('mousedown', evt => { + playButton.addEventListener('mousedown', (evt) => { // prevent events to trigger a selection in the timeline evt.stopPropagation() }) diff --git a/demos/showcase/frauddetection/timeline/Timeline.ts b/demos/showcase/frauddetection/timeline/Timeline.ts index afe102b9d..f166edf64 100644 --- a/demos/showcase/frauddetection/timeline/Timeline.ts +++ b/demos/showcase/frauddetection/timeline/Timeline.ts @@ -195,7 +195,7 @@ export default class Timeline { get selectedItems(): TDataItem[] { return this.graphComponent.selection.selectedNodes .toArray() - .flatMap(selectedNode => getItemsFromBucket(selectedNode)) + .flatMap((selectedNode) => getItemsFromBucket(selectedNode)) } /** @@ -297,7 +297,7 @@ export default class Timeline { }) // install a tooltip on the timeline items that reports the content of the possibly aggregated entry - initializeToolTips(inputMode, item => { + initializeToolTips(inputMode, (item) => { if (item instanceof INode) { const bucket = getBucket(item) if (bucket.label !== undefined) { @@ -332,7 +332,7 @@ export default class Timeline { } } }) - inputMode.addCanvasClickedListener(_ => { + inputMode.addCanvasClickedListener((_) => { this.barSelectListener?.([]) }) @@ -564,12 +564,12 @@ export default class Timeline { const nodesSource = graphBuilder.createGroupNodesSource({ data: this.buckets, id: getBucketId, - parentId: b => (b.parent ? getBucketId(b.parent) : null) + parentId: (b) => (b.parent ? getBucketId(b.parent) : null) }) const nodeCreator = nodesSource.nodeCreator nodeCreator.createLabelsSource>({ - data: b => (b.label != null ? [b] : []), + data: (b) => (b.label != null ? [b] : []), text: 'label' }) @@ -590,7 +590,7 @@ export default class Timeline { rectangleIndicator.setBounds(graphComponent.contentRect) rectangleIndicator.limits = graphComponent.contentRect - rectangleIndicator.addBoundsChangedListener(bounds => { + rectangleIndicator.addBoundsChangedListener((bounds) => { this.updateTimeframe(bounds) }) @@ -618,8 +618,8 @@ export default class Timeline { private getTimeframeFromBounds(bounds: Rect): TimeInterval { const graph = this.graphComponent.graph const nodesInFrame = graph.nodes - .filter(node => !graph.isGroupNode(node)) - .filter(node => bounds.contains(node.layout.center)) + .filter((node) => !graph.isGroupNode(node)) + .filter((node) => bounds.contains(node.layout.center)) if (nodesInFrame.size === 0) { // no nodes in timeframe, this returns an "empty" timeframe to trigger the update @@ -668,8 +668,8 @@ export default class Timeline { const graphComponent = this.graphComponent const graph = graphComponent.graph const nodesInTimeframe = graph.nodes - .filter(node => !graph.isGroupNode(node)) - .filter(node => { + .filter((node) => !graph.isGroupNode(node)) + .filter((node) => { const bucket = getBucket(node) return intervalsIntersect(bucket.start, bucket.end, timeframe[0], timeframe[1]) }) @@ -683,7 +683,7 @@ export default class Timeline { let minX = Number.POSITIVE_INFINITY let maxX = Number.NEGATIVE_INFINITY - nodesInTimeframe.forEach(current => { + nodesInTimeframe.forEach((current) => { minX = Math.min(minX, current.layout.x) maxX = Math.max(maxX, current.layout.maxX) }) @@ -733,7 +733,7 @@ export default class Timeline { const timeEntry = this.getTimeEntry(item) if (Array.isArray(timeEntry)) { - return timeEntry.some(entry => { + return timeEntry.some((entry) => { if (typeof entry === 'number') { const time = entry return start <= time && time < end @@ -876,7 +876,7 @@ export default class Timeline { ) { // create a new animation object if there is none or if the timeframe has changed this.timeframeAnimation = new TimeframeAnimation(this.timeframeRect.rect, this.graphComponent) - this.timeframeAnimation.addTimeframeListener(rect => this.updateTimeframe(rect)) + this.timeframeAnimation.addTimeframeListener((rect) => this.updateTimeframe(rect)) this.timeframeAnimation.addAnimationEndedListener(() => { // stop the animation and revert the state of the play button if (this.showPlayButton && !(this.timeframeAnimation?.animating ?? false)) { @@ -932,7 +932,7 @@ export default class Timeline { }, true ) - playButton.addEventListener('mousedown', evt => { + playButton.addEventListener('mousedown', (evt) => { // prevent events to trigger a selection in the timeline evt.stopPropagation() }) diff --git a/demos/showcase/frauddetection/timeline/bucket-aggregation.js b/demos/showcase/frauddetection/timeline/bucket-aggregation.js index 546eacd83..0e034c150 100644 --- a/demos/showcase/frauddetection/timeline/bucket-aggregation.js +++ b/demos/showcase/frauddetection/timeline/bucket-aggregation.js @@ -213,11 +213,11 @@ function createLeafBucket(item, label, start, end) { * @returns {!Array.>} */ function collectLeafBuckets(items, getTimeEntry) { - const entries = items.flatMap(item => { + const entries = items.flatMap((item) => { const timeEntry = getTimeEntry(item) if (timeEntry) { if (Array.isArray(timeEntry)) { - return timeEntry.flatMap(entry => { + return timeEntry.flatMap((entry) => { if (typeof entry === 'number') { const date = new Date(entry) return createLeafBucket(item, `${date.getHours()}:${date.getMinutes()}`, date, date) @@ -250,6 +250,6 @@ function getLeaves(bucket) { if (bucket.type === 'leaf') { return [bucket.item] } else { - return bucket.children.flatMap(child => getLeaves(child)) + return bucket.children.flatMap((child) => getLeaves(child)) } } diff --git a/demos/showcase/frauddetection/timeline/bucket-aggregation.ts b/demos/showcase/frauddetection/timeline/bucket-aggregation.ts index 108c043b8..bb0f828f3 100644 --- a/demos/showcase/frauddetection/timeline/bucket-aggregation.ts +++ b/demos/showcase/frauddetection/timeline/bucket-aggregation.ts @@ -200,11 +200,11 @@ function collectLeafBuckets( items: TDataItem[], getTimeEntry: (t: TDataItem) => TimeEntry | undefined ): Bucket[] { - const entries: Bucket[] = items.flatMap(item => { + const entries: Bucket[] = items.flatMap((item) => { const timeEntry = getTimeEntry(item) if (timeEntry) { if (Array.isArray(timeEntry)) { - return timeEntry.flatMap(entry => { + return timeEntry.flatMap((entry) => { if (typeof entry === 'number') { const date = new Date(entry) return createLeafBucket(item, `${date.getHours()}:${date.getMinutes()}`, date, date) @@ -234,6 +234,6 @@ function getLeaves(bucket: Bucket): TDataItem[] { if (bucket.type === 'leaf') { return [bucket.item] } else { - return bucket.children.flatMap(child => getLeaves(child)) + return bucket.children.flatMap((child) => getLeaves(child)) } } diff --git a/demos/showcase/frauddetection/timeline/timeline-layout.js b/demos/showcase/frauddetection/timeline/timeline-layout.js index e3111d419..8dc994043 100644 --- a/demos/showcase/frauddetection/timeline/timeline-layout.js +++ b/demos/showcase/frauddetection/timeline/timeline-layout.js @@ -75,7 +75,7 @@ export function applyTimelineLayout(graphComponent, styling, zoom, minZoom, maxZ partitionGridData: new PartitionGridData({ grid: new PartitionGrid({ rowCount: 1, columnCount: 31 }) }), - nodeHalos: item => { + nodeHalos: (item) => { const bucket = getBucket(item) const index = bucket.index ?? -1 return NodeHalo.create( @@ -152,14 +152,14 @@ class BarScalingStage extends LayoutStageBase { const tagProvider = graph.getDataProvider(LayoutGraphAdapter.ORIGINAL_TAG_DP_KEY) let maxValue = 0 - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const tag = tagProvider.get(node) if (tag?.type === 'group' && tag.layer === this.zoom) { maxValue = Math.max(maxValue, tag.aggregatedValue) } }) const scale = maxValue > 0 ? this.maxHeight / maxValue : 1 - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const tag = tagProvider.get(node) if (tag?.type === 'group') { graph.setSize(node, graph.getWidth(node), tag.aggregatedValue * scale) diff --git a/demos/showcase/frauddetection/timeline/timeline-layout.ts b/demos/showcase/frauddetection/timeline/timeline-layout.ts index 7adffa3b4..03d9f33f4 100644 --- a/demos/showcase/frauddetection/timeline/timeline-layout.ts +++ b/demos/showcase/frauddetection/timeline/timeline-layout.ts @@ -140,7 +140,10 @@ export function applyTimelineLayout( * components bounds. */ class BarScalingStage extends LayoutStageBase { - constructor(private maxHeight: number, private zoom: number) { + constructor( + private maxHeight: number, + private zoom: number + ) { super() } @@ -148,14 +151,14 @@ class BarScalingStage extends LayoutStageBase { const tagProvider = graph.getDataProvider(LayoutGraphAdapter.ORIGINAL_TAG_DP_KEY)! let maxValue = 0 - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const tag = tagProvider.get(node) as Bucket | null if (tag?.type === 'group' && tag.layer === this.zoom) { maxValue = Math.max(maxValue, tag.aggregatedValue) } }) const scale = maxValue > 0 ? this.maxHeight / maxValue : 1 - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const tag = tagProvider.get(node) as Bucket | null if (tag?.type === 'group') { graph.setSize(node, graph.getWidth(node), tag.aggregatedValue * scale) diff --git a/demos/showcase/graph-wizard-for-flowchart/Actions.js b/demos/showcase/graph-wizard-for-flowchart/Actions.js index 8f629346d..e3e867c9b 100644 --- a/demos/showcase/graph-wizard-for-flowchart/Actions.js +++ b/demos/showcase/graph-wizard-for-flowchart/Actions.js @@ -74,7 +74,7 @@ import { GraphWizardInputMode, WizardEventArgs } from './GraphWizardInputMode.js export function createSmartNavigate() { return new WizardAction( 'navigate', - mode => true, + (mode) => true, (mode, item, type, args) => { const graph = mode.graph const key = args.key @@ -136,9 +136,9 @@ export function createSmartNavigate() { // check for connected hits in 90° let children = graph .outEdgesAt(item) - .map(edge => edge.targetNode) - .concat(graph.inEdgesAt(item).map(edge => edge.sourceNode)) - .filter(child => { + .map((edge) => edge.targetNode) + .concat(graph.inEdgesAt(item).map((edge) => edge.sourceNode)) + .filter((child) => { const childCenter = child.layout.center const dx = center.x - childCenter.x const dy = center.y - childCenter.y @@ -149,7 +149,7 @@ export function createSmartNavigate() { // check for unconnected hits in 90° if (children.length === 0) { children = graph.nodes - .filter(child => { + .filter((child) => { const childCenter = child.layout.center const dx = center.x - childCenter.x const dy = center.y - childCenter.y @@ -162,9 +162,9 @@ export function createSmartNavigate() { if (children.length === 0) { children = graph .outEdgesAt(item) - .map(edge => edge.targetNode) - .concat(graph.inEdgesAt(item).map(edge => edge.sourceNode)) - .filter(child => { + .map((edge) => edge.targetNode) + .concat(graph.inEdgesAt(item).map((edge) => edge.sourceNode)) + .filter((child) => { const childCenter = child.layout.center const dx = center.x - childCenter.x const dy = center.y - childCenter.y @@ -176,7 +176,7 @@ export function createSmartNavigate() { // check for unconnected hits in general direction if (children.length === 0) { children = graph.nodes - .filter(child => { + .filter((child) => { const childCenter = child.layout.center const dx = center.x - childCenter.x const dy = center.y - childCenter.y @@ -185,7 +185,7 @@ export function createSmartNavigate() { .toArray() } - const childScores = children.map(child => { + const childScores = children.map((child) => { const childCenter = child.layout.center const dx = Math.abs(center.x - childCenter.x) const dy = Math.abs(center.y - childCenter.y) @@ -213,7 +213,7 @@ export function createSmartNavigate() { } else { // no current item => select the one closest to the center of the viewport const center = mode.graphComponent.viewport.center - target = mode.graph.nodes.orderBy(node => center.distanceTo(node.layout.center)).at(0) + target = mode.graph.nodes.orderBy((node) => center.distanceTo(node.layout.center)).at(0) } if (target) { mode.graphComponent.currentItem = target @@ -248,7 +248,7 @@ export function createSmartNavigateEdge(direction) { checkForEdge, checkAnd([ checkForNode, - mode => { + (mode) => { const item = mode.currentItem return ( (direction === 'NextOutgoing' && mode.graph.outDegree(item) > 0) || @@ -358,11 +358,11 @@ export function createChangeNodeColorSet(preCondition, colorTheme, getItemFill, style: { type: 'rect', fill: colorTheme[i].fill, outline: colorTheme[i].outline } }) } - const itemToColorSetIndex = node => { + const itemToColorSetIndex = (node) => { const fill = getItemFill(node) if (fill instanceof SolidColorFill) { const fillString = colorToHexString(fill.color) - return colorTheme.findIndex(colorSet => colorSet.fill === fillString) + return colorTheme.findIndex((colorSet) => colorSet.fill === fillString) } return 0 } @@ -373,7 +373,7 @@ export function createChangeNodeColorSet(preCondition, colorTheme, getItemFill, (mode, item, type, args) => { const currentItem = item let colorSetIndex = parseInt(type) - if (isNaN(colorSetIndex)) { + if (Number.isNaN(colorSetIndex)) { colorSetIndex = itemToColorSetIndex(currentItem) } let colorSet = colorTheme[colorSetIndex] @@ -385,7 +385,7 @@ export function createChangeNodeColorSet(preCondition, colorTheme, getItemFill, const nodeStyle = currentItem.style.clone() setStyleColors(nodeStyle, colorSet.fill, colorSet.outline) mode.graph.setStyle(currentItem, nodeStyle) - currentItem.labels.forEach(label => { + currentItem.labels.forEach((label) => { const style = label.style.clone() style.textFill = colorSet.labelText style.backgroundFill = colorSet.labelFill @@ -396,8 +396,8 @@ export function createChangeNodeColorSet(preCondition, colorTheme, getItemFill, [{ key: Key.C }], 'Change the node color', { - typeFactory: item => String(itemToColorSetIndex(item)), - styleFactory: item => { + typeFactory: (item) => String(itemToColorSetIndex(item)), + styleFactory: (item) => { const colorSet = colorTheme[itemToColorSetIndex(item)] return { type: 'rect', fill: colorSet.fill, outline: colorSet.outline } }, @@ -418,7 +418,7 @@ export function createChangeNodeColorSet(preCondition, colorTheme, getItemFill, export function createStartEdgeCreation(helpText, buttonOptions) { return new WizardAction( 'startEdgeCreation', - checkAnd([checkNotCreatingEdge, checkForNode, mode => mode.graph.nodes.size > 1]), + checkAnd([checkNotCreatingEdge, checkForNode, (mode) => mode.graph.nodes.size > 1]), (mode, item) => { mode.createEdgeMode.doStartEdgeCreation( new DefaultPortCandidate(item, FreeNodePortLocationModel.NODE_CENTER_ANCHORED), @@ -484,7 +484,7 @@ export function runLayout(mode, layout, layoutData, fixNodes, newNodes, deletedN const oldNodeSizes = new Mapper() if (newNodes) { const graph = mode.graph - newNodes.forEach(node => { + newNodes.forEach((node) => { oldNodeSizes.set(node, node.layout.toSize()) graph.setNodeLayout(node, new Rect(node.layout.center, Size.ZERO)) }) diff --git a/demos/showcase/graph-wizard-for-flowchart/Actions.ts b/demos/showcase/graph-wizard-for-flowchart/Actions.ts index 13aa5563d..abe120512 100644 --- a/demos/showcase/graph-wizard-for-flowchart/Actions.ts +++ b/demos/showcase/graph-wizard-for-flowchart/Actions.ts @@ -76,7 +76,7 @@ import { GraphWizardInputMode, WizardEventArgs } from './GraphWizardInputMode' export function createSmartNavigate(): WizardAction { return new WizardAction( 'navigate', - mode => true, + (mode) => true, (mode, item, type, args) => { const graph = mode.graph const key = (args as KeyEventArgs).key @@ -138,9 +138,9 @@ export function createSmartNavigate(): WizardAction { // check for connected hits in 90° let children = graph .outEdgesAt(item) - .map(edge => edge.targetNode!) - .concat(graph.inEdgesAt(item).map(edge => edge.sourceNode)) - .filter(child => { + .map((edge) => edge.targetNode!) + .concat(graph.inEdgesAt(item).map((edge) => edge.sourceNode)) + .filter((child) => { const childCenter = child.layout.center const dx = center.x - childCenter.x const dy = center.y - childCenter.y @@ -151,7 +151,7 @@ export function createSmartNavigate(): WizardAction { // check for unconnected hits in 90° if (children.length === 0) { children = graph.nodes - .filter(child => { + .filter((child) => { const childCenter = child.layout.center const dx = center.x - childCenter.x const dy = center.y - childCenter.y @@ -164,9 +164,9 @@ export function createSmartNavigate(): WizardAction { if (children.length === 0) { children = graph .outEdgesAt(item) - .map(edge => edge.targetNode!) - .concat(graph.inEdgesAt(item).map(edge => edge.sourceNode)) - .filter(child => { + .map((edge) => edge.targetNode!) + .concat(graph.inEdgesAt(item).map((edge) => edge.sourceNode)) + .filter((child) => { const childCenter = child.layout.center const dx = center.x - childCenter.x const dy = center.y - childCenter.y @@ -178,7 +178,7 @@ export function createSmartNavigate(): WizardAction { // check for unconnected hits in general direction if (children.length === 0) { children = graph.nodes - .filter(child => { + .filter((child) => { const childCenter = child.layout.center const dx = center.x - childCenter.x const dy = center.y - childCenter.y @@ -187,7 +187,7 @@ export function createSmartNavigate(): WizardAction { .toArray() } - const childScores = children.map(child => { + const childScores = children.map((child) => { const childCenter = child.layout.center const dx = Math.abs(center.x - childCenter.x) const dy = Math.abs(center.y - childCenter.y) @@ -215,7 +215,7 @@ export function createSmartNavigate(): WizardAction { } else { // no current item => select the one closest to the center of the viewport const center = mode.graphComponent.viewport.center - target = mode.graph.nodes.orderBy(node => center.distanceTo(node.layout.center)).at(0) + target = mode.graph.nodes.orderBy((node) => center.distanceTo(node.layout.center)).at(0) } if (target) { mode.graphComponent.currentItem = target @@ -249,7 +249,7 @@ export function createSmartNavigateEdge(direction: 'NextOutgoing' | 'NextIncomin checkForEdge, checkAnd([ checkForNode, - mode => { + (mode) => { const item = mode.currentItem as INode return ( (direction === 'NextOutgoing' && mode.graph.outDegree(item) > 0) || @@ -368,7 +368,7 @@ export function createChangeNodeColorSet( const fill = getItemFill(node) if (fill instanceof SolidColorFill) { const fillString = colorToHexString(fill.color) - return colorTheme.findIndex(colorSet => colorSet.fill === fillString) + return colorTheme.findIndex((colorSet) => colorSet.fill === fillString) } return 0 } @@ -379,7 +379,7 @@ export function createChangeNodeColorSet( (mode, item, type, args) => { const currentItem = item as INode let colorSetIndex = parseInt(type) - if (isNaN(colorSetIndex)) { + if (Number.isNaN(colorSetIndex)) { colorSetIndex = itemToColorSetIndex(currentItem) } let colorSet = colorTheme[colorSetIndex] @@ -391,7 +391,7 @@ export function createChangeNodeColorSet( const nodeStyle = currentItem.style.clone() setStyleColors(nodeStyle, colorSet.fill, colorSet.outline) mode.graph.setStyle(currentItem, nodeStyle) - currentItem.labels.forEach(label => { + currentItem.labels.forEach((label) => { const style = label.style.clone() as DefaultLabelStyle style.textFill = colorSet.labelText style.backgroundFill = colorSet.labelFill @@ -402,8 +402,8 @@ export function createChangeNodeColorSet( [{ key: Key.C }], 'Change the node color', { - typeFactory: item => String(itemToColorSetIndex(item as INode)), - styleFactory: item => { + typeFactory: (item) => String(itemToColorSetIndex(item as INode)), + styleFactory: (item) => { const colorSet = colorTheme[itemToColorSetIndex(item as INode)] return { type: 'rect', fill: colorSet.fill, outline: colorSet.outline } }, @@ -424,7 +424,7 @@ export function createStartEdgeCreation( ): WizardAction { return new WizardAction( 'startEdgeCreation', - checkAnd([checkNotCreatingEdge, checkForNode, mode => mode.graph.nodes.size > 1]), + checkAnd([checkNotCreatingEdge, checkForNode, (mode) => mode.graph.nodes.size > 1]), (mode, item) => { mode.createEdgeMode.doStartEdgeCreation( new DefaultPortCandidate(item as INode, FreeNodePortLocationModel.NODE_CENTER_ANCHORED), @@ -493,7 +493,7 @@ export function runLayout( const oldNodeSizes = new Mapper() if (newNodes) { const graph = mode.graph - newNodes.forEach(node => { + newNodes.forEach((node) => { oldNodeSizes.set(node, node.layout.toSize()) graph.setNodeLayout(node, new Rect(node.layout.center, Size.ZERO)) }) diff --git a/demos/showcase/graph-wizard-for-flowchart/FlowchartConfiguration.js b/demos/showcase/graph-wizard-for-flowchart/FlowchartConfiguration.js index 7ac157df4..767fc77d8 100644 --- a/demos/showcase/graph-wizard-for-flowchart/FlowchartConfiguration.js +++ b/demos/showcase/graph-wizard-for-flowchart/FlowchartConfiguration.js @@ -310,7 +310,7 @@ export default class FlowchartConfiguration { const wizardMode = new GraphWizardInputMode(legendDiv) mode.moveUnselectedInputMode.enabled = true mode.moveUnselectedInputMode.priority = mode.moveViewportInputMode.priority - 1 - mode.moveUnselectedInputMode.addDragFinishedListener(_ => + mode.moveUnselectedInputMode.addDragFinishedListener((_) => runLayout(wizardMode, this.createLayout(true), this.layoutData) ) @@ -375,7 +375,7 @@ export default class FlowchartConfiguration { createChangeNodeColorSet( checkAnd([checkNotCreatingEdge, checkForNodeStyle(FlowchartNodeStyle)]), this.colorTheme, - node => node.style.fill, + (node) => node.style.fill, (style, fill, outline) => { style.fill = Fill.from(fill) style.stroke = new Stroke({ fill: outline, thickness: 1.5 }) @@ -421,8 +421,8 @@ export default class FlowchartConfiguration { [{ key: Key.T }], 'Change the node type', { - typeFactory: item => this.getFlowchartType(item), - styleFactory: item => { + typeFactory: (item) => this.getFlowchartType(item), + styleFactory: (item) => { return { type: 'icon', iconPath: 'resources/icons/flowchart-' + this.getFlowchartType(item) + '.svg' @@ -464,10 +464,10 @@ export default class FlowchartConfiguration { * @returns {!Array.} */ createChangeFlowchartTypeButtons() { - const typeToTooltip = type => { + const typeToTooltip = (type) => { const name = type - .replace(/\d+/, value => ' ' + value) - .replace(/([a-z][A-Z])/g, value => value.substring(0, 1) + ' ' + value.substring(1, 2)) + .replace(/\d+/, (value) => ' ' + value) + .replace(/([a-z][A-Z])/g, (value) => value.substring(0, 1) + ' ' + value.substring(1, 2)) return name.substring(0, 1).toUpperCase() + name.substring(1, name.length) } const pickerButtons = [] @@ -644,7 +644,7 @@ export default class FlowchartConfiguration { checkAnd([ checkNotCreatingEdge, checkOr([checkForNode, checkForEdge]), - mode => mode.graph.nodes.size > 1 + (mode) => mode.graph.nodes.size > 1 ]), (mode, item, type, args) => { const graph = mode.graph @@ -739,7 +739,7 @@ export default class FlowchartConfiguration { outData: { child, edge } } }, - undo: undoData => { + undo: (undoData) => { const { currentItem, child, edge } = undoData const graph = mode.graph graph.remove(edge) @@ -757,7 +757,7 @@ export default class FlowchartConfiguration { ) { // only add an edge label when parent was a decision node const step2 = { - action: async inData => { + action: async (inData) => { const { edge } = inData const labelPicked = await mode.showPickerSelection( this.createSelectEdgeLabelTextAction(edge, parentInputMode), @@ -770,7 +770,7 @@ export default class FlowchartConfiguration { outData: inData } }, - undo: inData => { + undo: (inData) => { if (inData) { mode.graph.remove(inData) } @@ -798,7 +798,7 @@ export default class FlowchartConfiguration { } return { success, undoData: null, outData: inData } }, - undo: undoData => { + undo: (undoData) => { // don't undo setting the flowchart type so the new picker selection starts with the previous // choice } @@ -808,7 +808,7 @@ export default class FlowchartConfiguration { if (parentInputMode instanceof GraphEditorInputMode) { // edit node label const step4 = { - action: async inData => { + action: async (inData) => { const { child, edge } = inData // when label editing was canceled, it returns null const label = await parentInputMode.addLabel(child) @@ -819,7 +819,7 @@ export default class FlowchartConfiguration { } return { success: label != null, undoData: label, outData: edge } }, - undo: inData => { + undo: (inData) => { if (inData) { mode.graph.remove(inData) } @@ -842,7 +842,7 @@ export default class FlowchartConfiguration { createSelectEdgeLabelTextAction(edge, geim) { return new WizardAction( 'text', - mode => true, + (mode) => true, async (mode, item, type) => { if (type === 'custom') { const newLabel = await geim.addLabel(edge) @@ -931,7 +931,7 @@ export default class FlowchartConfiguration { * @returns {!PreCondition} */ isNodeType(type) { - return mode => + return (mode) => mode.currentItem instanceof INode && mode.currentItem.style instanceof FlowchartNodeStyle && mode.currentItem.style.type === type @@ -942,10 +942,6 @@ export default class FlowchartConfiguration { * A {@link FlowchartLayout} using an incremental {@link HierarchicLayout}. */ class IncrementalFlowchartLayout extends FlowchartLayout { - constructor() { - super() - } - /** * @returns {!HierarchicLayout} */ diff --git a/demos/showcase/graph-wizard-for-flowchart/FlowchartConfiguration.ts b/demos/showcase/graph-wizard-for-flowchart/FlowchartConfiguration.ts index 0f72e4869..7d9de83be 100644 --- a/demos/showcase/graph-wizard-for-flowchart/FlowchartConfiguration.ts +++ b/demos/showcase/graph-wizard-for-flowchart/FlowchartConfiguration.ts @@ -300,7 +300,7 @@ export default class FlowchartConfiguration { const wizardMode = new GraphWizardInputMode(legendDiv) mode.moveUnselectedInputMode.enabled = true mode.moveUnselectedInputMode.priority = mode.moveViewportInputMode.priority - 1 - mode.moveUnselectedInputMode.addDragFinishedListener(_ => + mode.moveUnselectedInputMode.addDragFinishedListener((_) => runLayout(wizardMode, this.createLayout(true), this.layoutData!) ) @@ -365,7 +365,7 @@ export default class FlowchartConfiguration { createChangeNodeColorSet( checkAnd([checkNotCreatingEdge, checkForNodeStyle(FlowchartNodeStyle)]), this.colorTheme, - node => (node.style as FlowchartNodeStyle).fill, + (node) => (node.style as FlowchartNodeStyle).fill, (style, fill, outline) => { ;(style as FlowchartNodeStyle).fill = Fill.from(fill) ;(style as FlowchartNodeStyle).stroke = new Stroke({ fill: outline, thickness: 1.5 }) @@ -408,8 +408,8 @@ export default class FlowchartConfiguration { [{ key: Key.T }], 'Change the node type', { - typeFactory: item => this.getFlowchartType(item as INode), - styleFactory: item => { + typeFactory: (item) => this.getFlowchartType(item as INode), + styleFactory: (item) => { return { type: 'icon', iconPath: 'resources/icons/flowchart-' + this.getFlowchartType(item as INode) + '.svg' @@ -446,8 +446,8 @@ export default class FlowchartConfiguration { private createChangeFlowchartTypeButtons(): ButtonOptions[] { const typeToTooltip = (type: FlowchartNodeType) => { const name = type - .replace(/\d+/, value => ' ' + value) - .replace(/([a-z][A-Z])/g, value => value.substring(0, 1) + ' ' + value.substring(1, 2)) + .replace(/\d+/, (value) => ' ' + value) + .replace(/([a-z][A-Z])/g, (value) => value.substring(0, 1) + ' ' + value.substring(1, 2)) return name.substring(0, 1).toUpperCase() + name.substring(1, name.length) } const pickerButtons: ButtonOptions[] = [] @@ -704,7 +704,7 @@ export default class FlowchartConfiguration { outData: { child, edge } } }, - undo: undoData => { + undo: (undoData) => { const { currentItem, child, edge } = undoData as { currentItem: IModelItem child: INode @@ -726,7 +726,7 @@ export default class FlowchartConfiguration { ) { // only add an edge label when parent was a decision node const step2: ActionStep = { - action: async inData => { + action: async (inData) => { const { edge } = inData as { edge: IEdge } const labelPicked = await mode.showPickerSelection( this.createSelectEdgeLabelTextAction(edge, parentInputMode), @@ -739,7 +739,7 @@ export default class FlowchartConfiguration { outData: inData } }, - undo: inData => { + undo: (inData) => { if (inData) { mode.graph.remove(inData as ILabel) } @@ -767,7 +767,7 @@ export default class FlowchartConfiguration { } return { success, undoData: null, outData: inData } }, - undo: undoData => { + undo: (undoData) => { // don't undo setting the flowchart type so the new picker selection starts with the previous // choice } @@ -777,7 +777,7 @@ export default class FlowchartConfiguration { if (parentInputMode instanceof GraphEditorInputMode) { // edit node label const step4: ActionStep = { - action: async inData => { + action: async (inData) => { const { child, edge } = inData as { child: INode; edge: IEdge } // when label editing was canceled, it returns null const label = await parentInputMode.addLabel(child) @@ -788,7 +788,7 @@ export default class FlowchartConfiguration { } return { success: label != null, undoData: label, outData: edge } }, - undo: inData => { + undo: (inData) => { if (inData) { mode.graph.remove(inData as ILabel) } @@ -810,7 +810,7 @@ export default class FlowchartConfiguration { createSelectEdgeLabelTextAction(edge: IEdge, geim: GraphEditorInputMode): WizardAction { return new WizardAction( 'text', - mode => true, + (mode) => true, async (mode, item, type) => { if (type === 'custom') { const newLabel = await geim.addLabel(edge) @@ -897,7 +897,7 @@ export default class FlowchartConfiguration { * @param type The type of flowchart node to check for. */ isNodeType(type: FlowchartNodeType): PreCondition { - return mode => + return (mode) => mode.currentItem instanceof INode && mode.currentItem.style instanceof FlowchartNodeStyle && mode.currentItem.style.type === type @@ -908,9 +908,6 @@ export default class FlowchartConfiguration { * A {@link FlowchartLayout} using an incremental {@link HierarchicLayout}. */ class IncrementalFlowchartLayout extends FlowchartLayout { - constructor() { - super() - } createHierarchicLayout(): HierarchicLayout { const hierarchicLayout = super.createHierarchicLayout() diff --git a/demos/showcase/graph-wizard-for-flowchart/GraphWizardInputMode.js b/demos/showcase/graph-wizard-for-flowchart/GraphWizardInputMode.js index 96736016c..3b33a5e3b 100644 --- a/demos/showcase/graph-wizard-for-flowchart/GraphWizardInputMode.js +++ b/demos/showcase/graph-wizard-for-flowchart/GraphWizardInputMode.js @@ -368,7 +368,7 @@ export class GraphWizardInputMode extends MultiplexingInputMode { // clear all active actions and find new ones whose pre-condition is met this.clear() - this._activeActions = this._actions.filter(action => action.preCondition(this)).toList() + this._activeActions = this._actions.filter((action) => action.preCondition(this)).toList() // update the buttons and the legend for the active actions this.updateButtons() @@ -386,8 +386,8 @@ export class GraphWizardInputMode extends MultiplexingInputMode { const gridDiv = document.createElement('div') this._legendDiv.appendChild(gridDiv) this._activeActions - .filter(action => !!action.description) - .forEach(action => { + .filter((action) => !!action.description) + .forEach((action) => { const div = document.createElement('div') div.innerHTML = WizardAction.getTextWithShortcuts( action.description, @@ -474,7 +474,7 @@ export class GraphWizardInputMode extends MultiplexingInputMode { } // find an active action that is triggered by the source/event combination - const triggeredAction = this._activeActions.find(action => action.trigger(source, e)) + const triggeredAction = this._activeActions.find((action) => action.trigger(source, e)) if (triggeredAction !== null) { if (e instanceof KeyEventArgs || e instanceof MouseEventArgs) { e.preventDefault() @@ -632,14 +632,14 @@ export class GraphWizardInputMode extends MultiplexingInputMode { // place all auto-placed buttons in a row this.addAutoPlacedButtons( - autoPlacedActions.map(action => action.buttonOptions), + autoPlacedActions.map((action) => action.buttonOptions), PickerLayout.Row, GraphWizardInputMode.getBaseLayout(this.currentItem), - index => this.createWizardActionHandler(autoPlacedActions[index]), + (index) => this.createWizardActionHandler(autoPlacedActions[index]), (button, index) => { autoPlacedActions[index].button = button }, - index => autoPlacedActions[index].shortcuts, + (index) => autoPlacedActions[index].shortcuts, evt ) @@ -663,9 +663,9 @@ export class GraphWizardInputMode extends MultiplexingInputMode { options.pickerButtons, options.pickerLayout ?? PickerLayout.Grid, backgroundLayout, - options => defaultActionHandler, + (options) => defaultActionHandler, this.setPickerButton.bind(this), - index => undefined, + (index) => undefined, evt ) } @@ -679,7 +679,7 @@ export class GraphWizardInputMode extends MultiplexingInputMode { addPickerSelectionButtons(action, evt) { const parentOptions = action.buttonOptions // handle action and close picker selection afterwards - const handler = async button => { + const handler = async (button) => { this.buttonMode.hideButtons() const actionSuccessful = await action.handler( this, @@ -695,9 +695,9 @@ export class GraphWizardInputMode extends MultiplexingInputMode { parentOptions.pickerButtons, parentOptions.pickerLayout ?? PickerLayout.Grid, WizardAction.getButtonLayout(parentOptions, evt.owner), - index => handler.bind(this), + (index) => handler.bind(this), this.setPickerButton.bind(this), - index => undefined, + (index) => undefined, evt ) } @@ -750,7 +750,7 @@ export class GraphWizardInputMode extends MultiplexingInputMode { // triggering the main button when picker buttons are available should toggle whether those // picker buttons are visible return action.buttonOptions.pickerButtons - ? button => this.togglePickerButtons(action) + ? (button) => this.togglePickerButtons(action) : this.createDefaultButtonHandler(action) } @@ -760,7 +760,7 @@ export class GraphWizardInputMode extends MultiplexingInputMode { * @returns {!ButtonActionListener} */ createDefaultButtonHandler(action) { - return button => { + return (button) => { this.handleAction( action, button.owner, @@ -842,10 +842,10 @@ export class GraphWizardInputMode extends MultiplexingInputMode { } // if no picker button has its own layout parameter set, add a common background 'button' - if (options.every(opt => !opt.layout && !opt.layoutFactory)) { + if (options.every((opt) => !opt.layout && !opt.layoutFactory)) { // add background button evt.addButton({ - onAction: button => {}, + onAction: (button) => {}, layoutParameter: baseLayout, style: new DefaultLabelStyle({ renderer: new DropShadowLabelRenderer(), @@ -977,7 +977,9 @@ export class GraphWizardInputMode extends MultiplexingInputMode { if (hidePickers) { const pickerButtonWasFocused = this.pickerButtons && - this.pickerButtons.some(row => row.some(button => button === this.buttonMode.focusedButton)) + this.pickerButtons.some((row) => + row.some((button) => button === this.buttonMode.focusedButton) + ) const pickerActionWasFocused = this.activePickersAction && this.activePickersAction.button === this.buttonMode.focusedButton @@ -994,7 +996,7 @@ export class GraphWizardInputMode extends MultiplexingInputMode { let newActivePickersAction = action ? action : null if (!newActivePickersAction && this.buttonMode.focusedButton) { newActivePickersAction = this._activeActions.find( - action => + (action) => action.button === this.buttonMode.focusedButton && action.buttonOptions !== null && action.buttonOptions.pickerButtons !== null @@ -1019,8 +1021,8 @@ export class GraphWizardInputMode extends MultiplexingInputMode { * @param {*} mainTag */ findPickerButtonToFocus(mainTag) { - this.pickerButtons.forEach(row => { - row.forEach(button => { + this.pickerButtons.forEach((row) => { + row.forEach((button) => { if (button.tag == mainTag) { return button } @@ -1087,7 +1089,7 @@ export class GraphWizardInputMode extends MultiplexingInputMode { * Calls {@link WizardAction.clear clear} for all active actions. */ cleanupActiveAction() { - this._activeActions.forEach(action => action.clear()) + this._activeActions.forEach((action) => action.clear()) this.activePickersAction = null this.pickerButtons = null } @@ -1234,10 +1236,6 @@ export class WizardEventArgs extends EventArgs { * Adds the __container-drop-shadow__ style class to the {@link SVGElement} of the created visual. */ class DropShadowLabelRenderer extends DefaultLabelStyleRenderer { - constructor() { - super() - } - /** * @param {!IRenderContext} context * @returns {?Visual} diff --git a/demos/showcase/graph-wizard-for-flowchart/GraphWizardInputMode.ts b/demos/showcase/graph-wizard-for-flowchart/GraphWizardInputMode.ts index 251543540..e78540fd6 100644 --- a/demos/showcase/graph-wizard-for-flowchart/GraphWizardInputMode.ts +++ b/demos/showcase/graph-wizard-for-flowchart/GraphWizardInputMode.ts @@ -349,7 +349,7 @@ export class GraphWizardInputMode extends MultiplexingInputMode { // clear all active actions and find new ones whose pre-condition is met this.clear() - this._activeActions = this._actions.filter(action => action.preCondition(this)).toList() + this._activeActions = this._actions.filter((action) => action.preCondition(this)).toList() // update the buttons and the legend for the active actions this.updateButtons() @@ -367,8 +367,8 @@ export class GraphWizardInputMode extends MultiplexingInputMode { const gridDiv = document.createElement('div') this._legendDiv.appendChild(gridDiv) this._activeActions - .filter(action => !!action.description) - .forEach(action => { + .filter((action) => !!action.description) + .forEach((action) => { const div = document.createElement('div') div.innerHTML = WizardAction.getTextWithShortcuts( action.description!, @@ -447,7 +447,7 @@ export class GraphWizardInputMode extends MultiplexingInputMode { } // find an active action that is triggered by the source/event combination - const triggeredAction = this._activeActions.find(action => action.trigger(source, e)) + const triggeredAction = this._activeActions.find((action) => action.trigger(source, e)) if (triggeredAction !== null) { if (e instanceof KeyEventArgs || e instanceof MouseEventArgs) { e.preventDefault() @@ -599,11 +599,11 @@ export class GraphWizardInputMode extends MultiplexingInputMode { autoPlacedActions.map((action: WizardAction) => action.buttonOptions!), PickerLayout.Row, GraphWizardInputMode.getBaseLayout(this.currentItem!), - index => this.createWizardActionHandler(autoPlacedActions[index]), + (index) => this.createWizardActionHandler(autoPlacedActions[index]), (button, index) => { autoPlacedActions[index].button = button }, - index => autoPlacedActions[index].shortcuts, + (index) => autoPlacedActions[index].shortcuts, evt ) @@ -627,9 +627,9 @@ export class GraphWizardInputMode extends MultiplexingInputMode { options.pickerButtons!, options.pickerLayout ?? PickerLayout.Grid, backgroundLayout, - options => defaultActionHandler, + (options) => defaultActionHandler, this.setPickerButton.bind(this), - index => undefined, + (index) => undefined, evt ) } @@ -643,7 +643,7 @@ export class GraphWizardInputMode extends MultiplexingInputMode { private addPickerSelectionButtons(action: WizardAction, evt: QueryButtonsEvent): void { const parentOptions = action.buttonOptions! // handle action and close picker selection afterwards - const handler: ButtonActionListener = async button => { + const handler: ButtonActionListener = async (button) => { this.buttonMode.hideButtons() const actionSuccessful = await action.handler( this, @@ -659,9 +659,9 @@ export class GraphWizardInputMode extends MultiplexingInputMode { parentOptions.pickerButtons!, parentOptions.pickerLayout ?? PickerLayout.Grid, WizardAction.getButtonLayout(parentOptions, evt.owner), - index => handler.bind(this), + (index) => handler.bind(this), this.setPickerButton.bind(this), - index => undefined, + (index) => undefined, evt ) } @@ -804,10 +804,10 @@ export class GraphWizardInputMode extends MultiplexingInputMode { } // if no picker button has its own layout parameter set, add a common background 'button' - if (options.every(opt => !opt.layout && !opt.layoutFactory)) { + if (options.every((opt) => !opt.layout && !opt.layoutFactory)) { // add background button evt.addButton({ - onAction: button => {}, + onAction: (button) => {}, layoutParameter: baseLayout, style: new DefaultLabelStyle({ renderer: new DropShadowLabelRenderer(), @@ -944,7 +944,9 @@ export class GraphWizardInputMode extends MultiplexingInputMode { if (hidePickers) { const pickerButtonWasFocused = this.pickerButtons && - this.pickerButtons.some(row => row.some(button => button === this.buttonMode.focusedButton)) + this.pickerButtons.some((row) => + row.some((button) => button === this.buttonMode.focusedButton) + ) const pickerActionWasFocused = this.activePickersAction && this.activePickersAction.button === this.buttonMode.focusedButton @@ -961,7 +963,7 @@ export class GraphWizardInputMode extends MultiplexingInputMode { let newActivePickersAction: WizardAction | null = action ? action : null if (!newActivePickersAction && this.buttonMode.focusedButton) { newActivePickersAction = this._activeActions.find( - action => + (action) => action.button === this.buttonMode.focusedButton && action.buttonOptions !== null && action.buttonOptions.pickerButtons !== null @@ -983,8 +985,8 @@ export class GraphWizardInputMode extends MultiplexingInputMode { } private findPickerButtonToFocus(mainTag: any) { - this.pickerButtons!.forEach(row => { - row.forEach(button => { + this.pickerButtons!.forEach((row) => { + row.forEach((button) => { if (button.tag == mainTag) { return button } @@ -1051,7 +1053,7 @@ export class GraphWizardInputMode extends MultiplexingInputMode { * Calls {@link WizardAction.clear clear} for all active actions. */ public cleanupActiveAction(): void { - this._activeActions.forEach(action => action.clear()) + this._activeActions.forEach((action) => action.clear()) this.activePickersAction = null this.pickerButtons = null } @@ -1198,9 +1200,6 @@ export class WizardEventArgs extends EventArgs { * Adds the __container-drop-shadow__ style class to the {@link SVGElement} of the created visual. */ class DropShadowLabelRenderer extends DefaultLabelStyleRenderer { - constructor() { - super() - } createVisual(context: IRenderContext): Visual | null { const visual = super.createVisual(context) as SvgVisual diff --git a/demos/showcase/graph-wizard-for-flowchart/Preconditions.js b/demos/showcase/graph-wizard-for-flowchart/Preconditions.js index 3082566b3..11cc1916c 100644 --- a/demos/showcase/graph-wizard-for-flowchart/Preconditions.js +++ b/demos/showcase/graph-wizard-for-flowchart/Preconditions.js @@ -38,7 +38,7 @@ import { CreateEdgeInputMode, IEdge, INode } from 'yfiles' * @returns {!PreCondition} */ export function checkAnd(conditions) { - return mode => conditions.every(condition => condition(mode)) + return (mode) => conditions.every((condition) => condition(mode)) } /** @@ -47,7 +47,7 @@ export function checkAnd(conditions) { * @returns {!PreCondition} */ export function checkOr(conditions) { - return mode => conditions.some(condition => condition(mode)) + return (mode) => conditions.some((condition) => condition(mode)) } /** @@ -56,7 +56,7 @@ export function checkOr(conditions) { * @returns {!PreCondition} */ export function checkNot(condition) { - return mode => !condition(mode) + return (mode) => !condition(mode) } /** @@ -84,7 +84,7 @@ export function checkForEdge(mode) { * @returns {!PreCondition} */ export function checkForNodeStyle(styleClass) { - return mode => mode.currentItem instanceof INode && mode.currentItem.style instanceof styleClass + return (mode) => mode.currentItem instanceof INode && mode.currentItem.style instanceof styleClass } /** @@ -94,7 +94,7 @@ export function checkForNodeStyle(styleClass) { * @returns {!PreCondition} */ export function checkForEdgeStyle(styleClass) { - return mode => mode.currentItem instanceof IEdge && mode.currentItem.style instanceof styleClass + return (mode) => mode.currentItem instanceof IEdge && mode.currentItem.style instanceof styleClass } /** diff --git a/demos/showcase/graph-wizard-for-flowchart/Preconditions.ts b/demos/showcase/graph-wizard-for-flowchart/Preconditions.ts index 96251653d..60d8af02f 100644 --- a/demos/showcase/graph-wizard-for-flowchart/Preconditions.ts +++ b/demos/showcase/graph-wizard-for-flowchart/Preconditions.ts @@ -36,7 +36,7 @@ export type PreCondition = (mode: GraphWizardInputMode) => boolean * @param conditions The conditions to combine. */ export function checkAnd(conditions: PreCondition[]): PreCondition { - return mode => conditions.every(condition => condition(mode)) + return (mode) => conditions.every((condition) => condition(mode)) } /** @@ -44,7 +44,7 @@ export function checkAnd(conditions: PreCondition[]): PreCondition { * @param conditions The conditions to combine. */ export function checkOr(conditions: PreCondition[]): PreCondition { - return mode => conditions.some(condition => condition(mode)) + return (mode) => conditions.some((condition) => condition(mode)) } /** @@ -52,7 +52,7 @@ export function checkOr(conditions: PreCondition[]): PreCondition { * @param condition The condition to negate. */ export function checkNot(condition: PreCondition): PreCondition { - return mode => !condition(mode) + return (mode) => !condition(mode) } /** @@ -77,7 +77,7 @@ export function checkForEdge(mode: GraphWizardInputMode): boolean { * @param styleClass The style class the current node is checked for. */ export function checkForNodeStyle(styleClass: any): PreCondition { - return mode => mode.currentItem instanceof INode && mode.currentItem.style instanceof styleClass + return (mode) => mode.currentItem instanceof INode && mode.currentItem.style instanceof styleClass } /** @@ -86,7 +86,7 @@ export function checkForNodeStyle(styleClass: any): PreCondition { * @param styleClass The style class the current edge is checked for. */ export function checkForEdgeStyle(styleClass: any): PreCondition { - return mode => mode.currentItem instanceof IEdge && mode.currentItem.style instanceof styleClass + return (mode) => mode.currentItem instanceof IEdge && mode.currentItem.style instanceof styleClass } /** diff --git a/demos/showcase/graph-wizard-for-flowchart/WizardAction.js b/demos/showcase/graph-wizard-for-flowchart/WizardAction.js index 98ef51ed2..833785f40 100644 --- a/demos/showcase/graph-wizard-for-flowchart/WizardAction.js +++ b/demos/showcase/graph-wizard-for-flowchart/WizardAction.js @@ -177,7 +177,7 @@ export default class WizardAction { evt.eventType === KeyEventType.DOWN ) { return this.shortcuts.some( - shortCut => + (shortCut) => shortCut.key == evt.key && (shortCut.modifier === undefined || shortCut.modifier == evt.modifiers) ) @@ -253,7 +253,7 @@ export default class WizardAction { * @returns {!ButtonActionListener} */ getDefaultAction(mode) { - return button => { + return (button) => { mode.handleAction( this, button.owner, @@ -388,7 +388,7 @@ export default class WizardAction { } else if (options.tooltipFactory) { tooltip = options.tooltipFactory(owner) } - return this.getTextWithShortcuts(tooltip, 'tooltip', shortcuts) + return WizardAction.getTextWithShortcuts(tooltip, 'tooltip', shortcuts) } /** @@ -441,7 +441,7 @@ export default class WizardAction { tooltip += startKey + 'Alt' + endKey + '+' } } - tooltip += startKey + this.getKeyName(shortcut.key) + endKey + tooltip += startKey + WizardAction.getKeyName(shortcut.key) + endKey } return tooltip } diff --git a/demos/showcase/graph-wizard-for-flowchart/WizardAction.ts b/demos/showcase/graph-wizard-for-flowchart/WizardAction.ts index af9986bf8..20dca5de2 100644 --- a/demos/showcase/graph-wizard-for-flowchart/WizardAction.ts +++ b/demos/showcase/graph-wizard-for-flowchart/WizardAction.ts @@ -188,7 +188,7 @@ export default class WizardAction { evt.eventType === KeyEventType.DOWN ) { return this.shortcuts.some( - shortCut => + (shortCut) => shortCut.key == evt.key && (shortCut.modifier === undefined || shortCut.modifier == evt.modifiers) ) @@ -389,7 +389,7 @@ export default class WizardAction { } else if (options.tooltipFactory) { tooltip = options.tooltipFactory(owner) } - return this.getTextWithShortcuts(tooltip, 'tooltip', shortcuts) + return WizardAction.getTextWithShortcuts(tooltip, 'tooltip', shortcuts) } /** @@ -439,7 +439,7 @@ export default class WizardAction { tooltip += startKey + 'Alt' + endKey + '+' } } - tooltip += startKey + this.getKeyName(shortcut.key) + endKey + tooltip += startKey + WizardAction.getKeyName(shortcut.key) + endKey } return tooltip } diff --git a/demos/showcase/graph-wizard-for-flowchart/index.html b/demos/showcase/graph-wizard-for-flowchart/index.html index 347d9f0a6..7dd7d19f4 100644 --- a/demos/showcase/graph-wizard-for-flowchart/index.html +++ b/demos/showcase/graph-wizard-for-flowchart/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/graph-wizard-for-flowchart/resources/graph-creation-wizard.css b/demos/showcase/graph-wizard-for-flowchart/resources/graph-creation-wizard.css index cf6c2b691..62807b982 100644 --- a/demos/showcase/graph-wizard-for-flowchart/resources/graph-creation-wizard.css +++ b/demos/showcase/graph-wizard-for-flowchart/resources/graph-creation-wizard.css @@ -49,7 +49,9 @@ legend { color: grey; padding: 3px 6px; - font: 1rem 'Fira Sans', sans-serif; + font: + 1rem 'Fira Sans', + sans-serif; } .demo-overlay { @@ -66,7 +68,9 @@ legend { background-color: #e1e3e5; border: 1px solid #ccc; border-radius: 3px; - box-shadow: 0 1px 0 rgb(0 0 0), 0 0 0 0.1em #fff inset; + box-shadow: + 0 1px 0 rgb(0 0 0), + 0 0 0 0.1em #fff inset; white-space: nowrap; font-style: normal; color: #000; diff --git a/demos/showcase/graphanalysis/GraphAnalysisDemo.js b/demos/showcase/graphanalysis/GraphAnalysisDemo.js index 3625ffdfb..b74bc86a2 100644 --- a/demos/showcase/graphanalysis/GraphAnalysisDemo.js +++ b/demos/showcase/graphanalysis/GraphAnalysisDemo.js @@ -144,12 +144,12 @@ function createEditorMode(graphComponent) { // deletion const graph = graphComponent.graph - inputMode.addDeletingSelectionListener(async _ => { + inputMode.addDeletingSelectionListener(async (_) => { applyAlgorithm(graph) await runLayout(graphComponent, true, []) }) - inputMode.addDeletedSelectionListener(async _ => { + inputMode.addDeletedSelectionListener(async (_) => { applyAlgorithm(graph) await runLayout(graphComponent, true, []) }) @@ -173,9 +173,9 @@ function createEditorMode(graphComponent) { await runLayout(graphComponent, true, incrementalNodes) }) - inputMode.moveInputMode.addDragFinishedListener(async inputModeMove => { + inputMode.moveInputMode.addDragFinishedListener(async (inputModeMove) => { const affectedNodes = inputModeMove.affectedItems - .filter(item => item instanceof INode) + .filter((item) => item instanceof INode) .toArray() if (affectedNodes.length < graph.nodes.size) { applyAlgorithm(graph) @@ -184,11 +184,11 @@ function createEditorMode(graphComponent) { }) // run the algorithm on node creation, edge port changes or label text changes - inputMode.addEdgePortsChangedListener(async _ => { + inputMode.addEdgePortsChangedListener(async (_) => { applyAlgorithm(graph) }) - inputMode.addNodeCreatedListener(_ => { + inputMode.addNodeCreatedListener((_) => { applyAlgorithm(graph) }) diff --git a/demos/showcase/graphanalysis/GraphAnalysisDemo.ts b/demos/showcase/graphanalysis/GraphAnalysisDemo.ts index 41213318f..02e37f9d0 100644 --- a/demos/showcase/graphanalysis/GraphAnalysisDemo.ts +++ b/demos/showcase/graphanalysis/GraphAnalysisDemo.ts @@ -140,12 +140,12 @@ function createEditorMode(graphComponent: GraphComponent): GraphEditorInputMode // deletion const graph = graphComponent.graph - inputMode.addDeletingSelectionListener(async _ => { + inputMode.addDeletingSelectionListener(async (_) => { applyAlgorithm(graph) await runLayout(graphComponent, true, []) }) - inputMode.addDeletedSelectionListener(async _ => { + inputMode.addDeletedSelectionListener(async (_) => { applyAlgorithm(graph) await runLayout(graphComponent, true, []) }) @@ -169,9 +169,9 @@ function createEditorMode(graphComponent: GraphComponent): GraphEditorInputMode await runLayout(graphComponent, true, incrementalNodes) }) - inputMode.moveInputMode.addDragFinishedListener(async inputModeMove => { + inputMode.moveInputMode.addDragFinishedListener(async (inputModeMove) => { const affectedNodes = inputModeMove.affectedItems - .filter(item => item instanceof INode) + .filter((item) => item instanceof INode) .toArray() as INode[] if (affectedNodes.length < graph.nodes.size) { applyAlgorithm(graph) @@ -180,11 +180,11 @@ function createEditorMode(graphComponent: GraphComponent): GraphEditorInputMode }) // run the algorithm on node creation, edge port changes or label text changes - inputMode.addEdgePortsChangedListener(async _ => { + inputMode.addEdgePortsChangedListener(async (_) => { applyAlgorithm(graph) }) - inputMode.addNodeCreatedListener(_ => { + inputMode.addNodeCreatedListener((_) => { applyAlgorithm(graph) }) diff --git a/demos/showcase/graphanalysis/algorithms/algorithms.js b/demos/showcase/graphanalysis/algorithms/algorithms.js index cbb07d051..52e64718a 100644 --- a/demos/showcase/graphanalysis/algorithms/algorithms.js +++ b/demos/showcase/graphanalysis/algorithms/algorithms.js @@ -129,7 +129,7 @@ import { * @param {!IGraph} graph */ export function resetTypes(graph) { - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { resetType(node) }) } @@ -159,17 +159,17 @@ export function applyAlgorithm(graph) { * @param {!IGraph} graph */ export function resetGraph(graph) { - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { resetResult(node) }) - graph.edges.forEach(edge => resetResult(edge)) - graph.nodeLabels.toArray().forEach(label => { + graph.edges.forEach((edge) => resetResult(edge)) + graph.nodeLabels.toArray().forEach((label) => { graph.remove(label) }) graph.edgeLabels .toArray() - .filter(label => label.tag !== 'weight') - .forEach(label => { + .filter((label) => label.tag !== 'weight') + .forEach((label) => { graph.remove(label) }) } @@ -378,7 +378,7 @@ export const algorithms = { */ function getEdgeWeights(graph) { const weights = new Map() - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { if (useUniformEdgeWeights()) { weights.set(edge, 1) } else { @@ -407,7 +407,7 @@ function getStartNodes(graph) { return [] } - const startNodes = graph.nodes.filter(node => getTag(node).type === 'start').toArray() + const startNodes = graph.nodes.filter((node) => getTag(node).type === 'start').toArray() if (startNodes.length === 0) { const startNode = graph.nodes.first() const tag = copyAndReplaceTag(startNode) @@ -429,7 +429,7 @@ function getEndNodes(graph) { return [] } - const endNodes = graph.nodes.filter(node => getTag(node).type === 'end').toArray() + const endNodes = graph.nodes.filter((node) => getTag(node).type === 'end').toArray() if (endNodes.length === 0) { const endNode = graph.nodes.last() const tag = copyAndReplaceTag(endNode) diff --git a/demos/showcase/graphanalysis/algorithms/algorithms.ts b/demos/showcase/graphanalysis/algorithms/algorithms.ts index 8b1bfcaef..4956749d3 100644 --- a/demos/showcase/graphanalysis/algorithms/algorithms.ts +++ b/demos/showcase/graphanalysis/algorithms/algorithms.ts @@ -131,7 +131,7 @@ export type AlgorithmConfig = { * Resets the node types. */ export function resetTypes(graph: IGraph): void { - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { resetType(node) }) } @@ -159,17 +159,17 @@ export function applyAlgorithm(graph: IGraph): void { * Removes all result-labels and resets the tag. */ export function resetGraph(graph: IGraph): void { - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { resetResult(node) }) - graph.edges.forEach(edge => resetResult(edge)) - graph.nodeLabels.toArray().forEach(label => { + graph.edges.forEach((edge) => resetResult(edge)) + graph.nodeLabels.toArray().forEach((label) => { graph.remove(label) }) graph.edgeLabels .toArray() - .filter(label => label.tag !== 'weight') - .forEach(label => { + .filter((label) => label.tag !== 'weight') + .forEach((label) => { graph.remove(label) }) } @@ -376,7 +376,7 @@ export const algorithms: Record = { */ function getEdgeWeights(graph: IGraph): Map { const weights = new Map() - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { if (useUniformEdgeWeights()) { weights.set(edge, 1) } else { @@ -403,7 +403,7 @@ function getStartNodes(graph: IGraph): INode[] { return [] } - const startNodes = graph.nodes.filter(node => getTag(node).type === 'start').toArray() + const startNodes = graph.nodes.filter((node) => getTag(node).type === 'start').toArray() if (startNodes.length === 0) { const startNode = graph.nodes.first() const tag = copyAndReplaceTag(startNode) @@ -423,7 +423,7 @@ function getEndNodes(graph: IGraph): INode[] { return [] } - const endNodes = graph.nodes.filter(node => getTag(node).type === 'end').toArray() + const endNodes = graph.nodes.filter((node) => getTag(node).type === 'end').toArray() if (endNodes.length === 0) { const endNode = graph.nodes.last() const tag = copyAndReplaceTag(endNode) diff --git a/demos/showcase/graphanalysis/algorithms/centrality.js b/demos/showcase/graphanalysis/algorithms/centrality.js index aad7992f2..0295de35c 100644 --- a/demos/showcase/graphanalysis/algorithms/centrality.js +++ b/demos/showcase/graphanalysis/algorithms/centrality.js @@ -68,7 +68,7 @@ export function calculateDegreeCentrality(graph) { const nodeCentrality = result.nodeCentrality const normalizedNodeCentrality = result.normalizedNodeCentrality - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const centrality = normalizedNodeCentrality.get(node) setCentrality(node, centrality) graph.addLabel({ @@ -100,7 +100,7 @@ export const weightCentralityDescription = ` const nodeCentrality = result.nodeCentrality const normalizedNodeCentrality = result.normalizedNodeCentrality - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const centrality = normalizedNodeCentrality.get(node) setCentrality(node, centrality) graph.addLabel({ owner: node, text: nodeCentrality.get(node).toFixed(2) }) @@ -131,7 +131,7 @@ export function calculateGraphCentrality(graph, config) { const nodeCentrality = result.nodeCentrality const normalizedNodeCentrality = result.normalizedNodeCentrality - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const centrality = normalizedNodeCentrality.get(node) setCentrality(node, centrality) graph.addLabel({ owner: node, text: nodeCentrality.get(node).toFixed(2) }) @@ -160,7 +160,7 @@ export function calculateNodeEdgeBetweennessCentrality(graph, config) { const nodeCentrality = result.nodeCentrality const normalizedNodeCentrality = result.normalizedNodeCentrality - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const centrality = normalizedNodeCentrality.get(node) setCentrality(node, centrality) graph.addLabel({ owner: node, text: nodeCentrality.get(node).toFixed(2) }) @@ -168,7 +168,7 @@ export function calculateNodeEdgeBetweennessCentrality(graph, config) { const edgeCentrality = result.edgeCentrality const normalizedEdgeCentrality = result.normalizedEdgeCentrality - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const centrality = normalizedEdgeCentrality.get(edge) setCentrality(edge, centrality) graph.addLabel({ @@ -204,7 +204,7 @@ export function calculateClosenessCentrality(graph, config) { }).run(graph) const normalizedNodeCentrality = result.normalizedNodeCentrality - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const centrality = normalizedNodeCentrality.get(node) setCentrality(node, centrality) graph.addLabel({ @@ -217,7 +217,7 @@ export function calculateClosenessCentrality(graph, config) { // if the graph is not connected, we run the algorithm separately to each connected component const connectedComponentsResult = new ConnectedComponents().run(graph) - connectedComponentsResult.components.forEach(component => { + connectedComponentsResult.components.forEach((component) => { const result = new ClosenessCentrality({ weights: config.edgeWeights, directed: config.directed, @@ -225,7 +225,7 @@ export function calculateClosenessCentrality(graph, config) { }).run(graph) const normalizedNodeCentrality = result.normalizedNodeCentrality - component.nodes.forEach(node => { + component.nodes.forEach((node) => { const centrality = normalizedNodeCentrality.get(node) setCentrality(node, centrality) // we use normalized node centrality for the labels to avoid very small values @@ -252,12 +252,12 @@ export function calculateEigenvectorCentrality(graph) { const result = new EigenvectorCentrality().run(graph) const nodeCentrality = result.nodeCentrality - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const centrality = nodeCentrality.get(node) setCentrality(node, centrality) graph.addLabel({ owner: node, - text: isNaN(centrality) ? 'Inf' : centrality.toFixed(2) + text: Number.isNaN(centrality) ? 'Inf' : centrality.toFixed(2) }) }) } @@ -296,7 +296,7 @@ export function calculatePageRankCentrality(graph, config) { return Math.max(maxRank, pageRank.get(node)) }, 0) - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const rank = pageRank.get(node) setCentrality(node, rank / maximumRank) graph.addLabel({ diff --git a/demos/showcase/graphanalysis/algorithms/centrality.ts b/demos/showcase/graphanalysis/algorithms/centrality.ts index 669e2d64a..3a7ea6c29 100644 --- a/demos/showcase/graphanalysis/algorithms/centrality.ts +++ b/demos/showcase/graphanalysis/algorithms/centrality.ts @@ -69,7 +69,7 @@ export function calculateDegreeCentrality(graph: IGraph): void { const nodeCentrality = result.nodeCentrality const normalizedNodeCentrality = result.normalizedNodeCentrality - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const centrality = normalizedNodeCentrality.get(node)! setCentrality(node, centrality) graph.addLabel({ @@ -99,7 +99,7 @@ export const weightCentralityDescription = ` const nodeCentrality = result.nodeCentrality const normalizedNodeCentrality = result.normalizedNodeCentrality - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const centrality = normalizedNodeCentrality.get(node)! setCentrality(node, centrality) graph.addLabel({ owner: node, text: nodeCentrality.get(node)!.toFixed(2) }) @@ -128,7 +128,7 @@ export function calculateGraphCentrality(graph: IGraph, config: AlgorithmConfig) const nodeCentrality = result.nodeCentrality const normalizedNodeCentrality = result.normalizedNodeCentrality - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const centrality = normalizedNodeCentrality.get(node)! setCentrality(node, centrality) graph.addLabel({ owner: node, text: nodeCentrality.get(node)!.toFixed(2) }) @@ -158,7 +158,7 @@ export function calculateNodeEdgeBetweennessCentrality( const nodeCentrality = result.nodeCentrality const normalizedNodeCentrality = result.normalizedNodeCentrality - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const centrality = normalizedNodeCentrality.get(node)! setCentrality(node, centrality) graph.addLabel({ owner: node, text: nodeCentrality.get(node)!.toFixed(2) }) @@ -166,7 +166,7 @@ export function calculateNodeEdgeBetweennessCentrality( const edgeCentrality = result.edgeCentrality const normalizedEdgeCentrality = result.normalizedEdgeCentrality - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const centrality = normalizedEdgeCentrality.get(edge)! setCentrality(edge, centrality) graph.addLabel({ @@ -200,7 +200,7 @@ export function calculateClosenessCentrality(graph: IGraph, config: AlgorithmCon }).run(graph) const normalizedNodeCentrality = result.normalizedNodeCentrality - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const centrality = normalizedNodeCentrality.get(node)! setCentrality(node, centrality) graph.addLabel({ @@ -213,7 +213,7 @@ export function calculateClosenessCentrality(graph: IGraph, config: AlgorithmCon // if the graph is not connected, we run the algorithm separately to each connected component const connectedComponentsResult = new ConnectedComponents().run(graph) - connectedComponentsResult.components.forEach(component => { + connectedComponentsResult.components.forEach((component) => { const result = new ClosenessCentrality({ weights: config.edgeWeights, directed: config.directed, @@ -221,7 +221,7 @@ export function calculateClosenessCentrality(graph: IGraph, config: AlgorithmCon }).run(graph) const normalizedNodeCentrality = result.normalizedNodeCentrality - component.nodes.forEach(node => { + component.nodes.forEach((node) => { const centrality = normalizedNodeCentrality.get(node)! setCentrality(node, centrality) // we use normalized node centrality for the labels to avoid very small values @@ -247,12 +247,12 @@ export function calculateEigenvectorCentrality(graph: IGraph): void { const result = new EigenvectorCentrality().run(graph) const nodeCentrality = result.nodeCentrality - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const centrality = nodeCentrality.get(node)! setCentrality(node, centrality) graph.addLabel({ owner: node, - text: isNaN(centrality) ? 'Inf' : centrality.toFixed(2) + text: Number.isNaN(centrality) ? 'Inf' : centrality.toFixed(2) }) }) } @@ -289,7 +289,7 @@ export function calculatePageRankCentrality(graph: IGraph, config: AlgorithmConf return Math.max(maxRank, pageRank.get(node)!) }, 0) - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const rank = pageRank.get(node)! setCentrality(node, rank / maximumRank) graph.addLabel({ diff --git a/demos/showcase/graphanalysis/algorithms/connectivity.js b/demos/showcase/graphanalysis/algorithms/connectivity.js index 50522ae7c..b2d00549c 100644 --- a/demos/showcase/graphanalysis/algorithms/connectivity.js +++ b/demos/showcase/graphanalysis/algorithms/connectivity.js @@ -50,8 +50,8 @@ export function calculateConnectedComponents(graph) { const result = new ConnectedComponents().run(graph) result.components.forEach((component, componentIndex) => { - component.nodes.forEach(node => markItem(node, componentIndex)) - component.inducedEdges.forEach(edge => markItem(edge, componentIndex)) + component.nodes.forEach((node) => markItem(node, componentIndex)) + component.inducedEdges.forEach((edge) => markItem(edge, componentIndex)) }) } @@ -71,10 +71,10 @@ export function calculateBiconnectedComponents(graph) { const result = new BiconnectedComponents().run(graph) result.components.forEach((component, componentIndex) => { - component.nodes.forEach(node => { + component.nodes.forEach((node) => { markItem(node, componentIndex) }) - component.edges.forEach(edge => { + component.edges.forEach((edge) => { markItem(edge, componentIndex) }) }) @@ -95,8 +95,8 @@ export function calculateStronglyConnectedComponents(graph) { const result = new StronglyConnectedComponents().run(graph) result.components.forEach((component, componentIndex) => { - component.nodes.forEach(node => markItem(node, componentIndex)) - component.inducedEdges.forEach(edge => markItem(edge, componentIndex)) + component.nodes.forEach((node) => markItem(node, componentIndex)) + component.inducedEdges.forEach((edge) => markItem(edge, componentIndex)) }) } @@ -126,13 +126,13 @@ export function calculateReachableNodes(graph, config) { startNodes: markedSource }).run(graph) - result.reachableNodes.forEach(node => { + result.reachableNodes.forEach((node) => { markItem(node, 0) }) graph.edges - .filter(edge => result.isReachable(edge.sourceNode) && result.isReachable(edge.targetNode)) - .forEach(edge => { + .filter((edge) => result.isReachable(edge.sourceNode) && result.isReachable(edge.targetNode)) + .forEach((edge) => { markItem(edge, 0) }) } @@ -166,13 +166,13 @@ export function calculateKCoreComponents(graph) { for (let k = maximumK; k > 0; k--) { const kCore = result.getKCore(k) - kCore.forEach(node => markItem(node, k)) + kCore.forEach((node) => markItem(node, k)) graph.edges - .filter(edge => kCore.contains(edge.sourceNode) && kCore.contains(edge.targetNode)) - .forEach(edge => markItem(edge, k)) + .filter((edge) => kCore.contains(edge.sourceNode) && kCore.contains(edge.targetNode)) + .forEach((edge) => markItem(edge, k)) } const kCores = result.kCores - graph.nodes.forEach(node => graph.addLabel({ owner: node, text: String(kCores.get(node)) })) + graph.nodes.forEach((node) => graph.addLabel({ owner: node, text: String(kCores.get(node)) })) } diff --git a/demos/showcase/graphanalysis/algorithms/connectivity.ts b/demos/showcase/graphanalysis/algorithms/connectivity.ts index 45eb91578..94f2844f8 100644 --- a/demos/showcase/graphanalysis/algorithms/connectivity.ts +++ b/demos/showcase/graphanalysis/algorithms/connectivity.ts @@ -50,8 +50,8 @@ export function calculateConnectedComponents(graph: IGraph): void { const result = new ConnectedComponents().run(graph) result.components.forEach((component, componentIndex) => { - component.nodes.forEach(node => markItem(node, componentIndex)) - component.inducedEdges.forEach(edge => markItem(edge, componentIndex)) + component.nodes.forEach((node) => markItem(node, componentIndex)) + component.inducedEdges.forEach((edge) => markItem(edge, componentIndex)) }) } @@ -70,10 +70,10 @@ export function calculateBiconnectedComponents(graph: IGraph): void { const result = new BiconnectedComponents().run(graph) result.components.forEach((component, componentIndex) => { - component.nodes.forEach(node => { + component.nodes.forEach((node) => { markItem(node, componentIndex) }) - component.edges.forEach(edge => { + component.edges.forEach((edge) => { markItem(edge, componentIndex) }) }) @@ -93,8 +93,8 @@ export function calculateStronglyConnectedComponents(graph: IGraph): void { const result = new StronglyConnectedComponents().run(graph) result.components.forEach((component, componentIndex) => { - component.nodes.forEach(node => markItem(node, componentIndex)) - component.inducedEdges.forEach(edge => markItem(edge, componentIndex)) + component.nodes.forEach((node) => markItem(node, componentIndex)) + component.inducedEdges.forEach((edge) => markItem(edge, componentIndex)) }) } @@ -122,13 +122,13 @@ export function calculateReachableNodes(graph: IGraph, config: AlgorithmConfig): startNodes: markedSource }).run(graph) - result.reachableNodes.forEach(node => { + result.reachableNodes.forEach((node) => { markItem(node, 0) }) graph.edges - .filter(edge => result.isReachable(edge.sourceNode!) && result.isReachable(edge.targetNode!)) - .forEach(edge => { + .filter((edge) => result.isReachable(edge.sourceNode!) && result.isReachable(edge.targetNode!)) + .forEach((edge) => { markItem(edge, 0) }) } @@ -161,13 +161,13 @@ export function calculateKCoreComponents(graph: IGraph): void { for (let k = maximumK; k > 0; k--) { const kCore = result.getKCore(k) - kCore.forEach(node => markItem(node, k)) + kCore.forEach((node) => markItem(node, k)) graph.edges - .filter(edge => kCore.contains(edge.sourceNode!) && kCore.contains(edge.targetNode!)) - .forEach(edge => markItem(edge, k)) + .filter((edge) => kCore.contains(edge.sourceNode!) && kCore.contains(edge.targetNode!)) + .forEach((edge) => markItem(edge, k)) } const kCores = result.kCores - graph.nodes.forEach(node => graph.addLabel({ owner: node, text: String(kCores.get(node)) })) + graph.nodes.forEach((node) => graph.addLabel({ owner: node, text: String(kCores.get(node)) })) } diff --git a/demos/showcase/graphanalysis/algorithms/cycles.js b/demos/showcase/graphanalysis/algorithms/cycles.js index 945475b43..05f89c2bf 100644 --- a/demos/showcase/graphanalysis/algorithms/cycles.js +++ b/demos/showcase/graphanalysis/algorithms/cycles.js @@ -56,12 +56,12 @@ export function calculateCycles(graph, config) { // consisting only of elements that belong a cycle const connectedComponentsResult = new ConnectedComponents({ subgraphEdges: cycleEdges, - subgraphNodes: node => graph.edgesAt(node).some(edge => cycleEdges.includes(edge)) + subgraphNodes: (node) => graph.edgesAt(node).some((edge) => cycleEdges.includes(edge)) }).run(graph) // color the cycle edges depending on which component they belong to connectedComponentsResult.components.forEach((cycle, cycleId) => { - cycle.nodes.forEach(node => markItem(node, cycleId)) - cycle.inducedEdges.forEach(edge => markItem(edge, cycleId)) + cycle.nodes.forEach((node) => markItem(node, cycleId)) + cycle.inducedEdges.forEach((edge) => markItem(edge, cycleId)) }) } diff --git a/demos/showcase/graphanalysis/algorithms/cycles.ts b/demos/showcase/graphanalysis/algorithms/cycles.ts index d88c84d26..f5f859350 100644 --- a/demos/showcase/graphanalysis/algorithms/cycles.ts +++ b/demos/showcase/graphanalysis/algorithms/cycles.ts @@ -54,12 +54,12 @@ export function calculateCycles(graph: IGraph, config: AlgorithmConfig): void { // consisting only of elements that belong a cycle const connectedComponentsResult = new ConnectedComponents({ subgraphEdges: cycleEdges, - subgraphNodes: node => graph.edgesAt(node).some(edge => cycleEdges.includes(edge)) + subgraphNodes: (node) => graph.edgesAt(node).some((edge) => cycleEdges.includes(edge)) }).run(graph) // color the cycle edges depending on which component they belong to connectedComponentsResult.components.forEach((cycle, cycleId) => { - cycle.nodes.forEach(node => markItem(node, cycleId)) - cycle.inducedEdges.forEach(edge => markItem(edge, cycleId)) + cycle.nodes.forEach((node) => markItem(node, cycleId)) + cycle.inducedEdges.forEach((edge) => markItem(edge, cycleId)) }) } diff --git a/demos/showcase/graphanalysis/algorithms/minimum-spanning-tree.js b/demos/showcase/graphanalysis/algorithms/minimum-spanning-tree.js index 8b42098db..55b33c6f6 100644 --- a/demos/showcase/graphanalysis/algorithms/minimum-spanning-tree.js +++ b/demos/showcase/graphanalysis/algorithms/minimum-spanning-tree.js @@ -51,11 +51,11 @@ export function calculateMinimumSpanningTree(graph, config) { const result = new SpanningTree({ costs: config.edgeWeights }).run(graph) // mark those edges - result.edges.forEach(edge => { + result.edges.forEach((edge) => { markItem(edge) }) - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { markItem(node) }) } diff --git a/demos/showcase/graphanalysis/algorithms/minimum-spanning-tree.ts b/demos/showcase/graphanalysis/algorithms/minimum-spanning-tree.ts index a2bdf7c28..0d8bb6c67 100644 --- a/demos/showcase/graphanalysis/algorithms/minimum-spanning-tree.ts +++ b/demos/showcase/graphanalysis/algorithms/minimum-spanning-tree.ts @@ -50,11 +50,11 @@ export function calculateMinimumSpanningTree(graph: IGraph, config: AlgorithmCon const result = new SpanningTree({ costs: config.edgeWeights }).run(graph) // mark those edges - result.edges.forEach(edge => { + result.edges.forEach((edge) => { markItem(edge) }) - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { markItem(node) }) } diff --git a/demos/showcase/graphanalysis/algorithms/paths.js b/demos/showcase/graphanalysis/algorithms/paths.js index c2a4998ac..102143809 100644 --- a/demos/showcase/graphanalysis/algorithms/paths.js +++ b/demos/showcase/graphanalysis/algorithms/paths.js @@ -68,10 +68,10 @@ export function calculatedShortestPaths(graph, config) { sink: endNodes[0] }).run(graph) - result.nodes.forEach(node => { + result.nodes.forEach((node) => { markItem(node) }) - result.edges.forEach(edge => { + result.edges.forEach((edge) => { markItem(edge) }) } else { @@ -83,10 +83,10 @@ export function calculatedShortestPaths(graph, config) { }).run(graph) result.paths.forEach((path, pathIndex) => { - path.nodes.forEach(node => { + path.nodes.forEach((node) => { markItem(node, pathIndex) }) - path.edges.forEach(edge => { + path.edges.forEach((edge) => { markItem(edge, pathIndex) }) }) @@ -123,10 +123,10 @@ export function calculateAllPaths(graph, config) { }).run(graph) result.paths.forEach((path, pathIndex) => { - path.nodes.forEach(node => { + path.nodes.forEach((node) => { markItem(node, pathIndex) }) - path.edges.forEach(edge => { + path.edges.forEach((edge) => { markItem(edge, pathIndex) }) }) @@ -150,10 +150,10 @@ export function calculateAllChains(graph, config) { const result = new Chains({ directed: config.directed }).run(graph) result.chains.forEach((chain, pathIndex) => { - chain.nodes.forEach(node => { + chain.nodes.forEach((node) => { markItem(node, pathIndex) }) - chain.edges.forEach(edge => { + chain.edges.forEach((edge) => { markItem(edge, pathIndex) }) }) @@ -202,11 +202,11 @@ export function calculateSingleSourceShortestPaths(graph, config) { const predecessors = result.predecessors result.paths .orderBy( - path => path.nodes.size, + (path) => path.nodes.size, (p1, p2) => p1 - p2 ) .forEach((path, pathIndex) => { - path.nodes.forEach(node => { + path.nodes.forEach((node) => { const distance = distances.get(node) / longestPathLength setComponent(node, pathIndex) diff --git a/demos/showcase/graphanalysis/algorithms/paths.ts b/demos/showcase/graphanalysis/algorithms/paths.ts index 946d8bf6b..e7212547d 100644 --- a/demos/showcase/graphanalysis/algorithms/paths.ts +++ b/demos/showcase/graphanalysis/algorithms/paths.ts @@ -68,10 +68,10 @@ export function calculatedShortestPaths(graph: IGraph, config: AlgorithmConfig): sink: endNodes[0] }).run(graph) - result.nodes.forEach(node => { + result.nodes.forEach((node) => { markItem(node) }) - result.edges.forEach(edge => { + result.edges.forEach((edge) => { markItem(edge) }) } else { @@ -83,10 +83,10 @@ export function calculatedShortestPaths(graph: IGraph, config: AlgorithmConfig): }).run(graph) result.paths.forEach((path, pathIndex) => { - path.nodes.forEach(node => { + path.nodes.forEach((node) => { markItem(node, pathIndex) }) - path.edges.forEach(edge => { + path.edges.forEach((edge) => { markItem(edge, pathIndex) }) }) @@ -121,10 +121,10 @@ export function calculateAllPaths(graph: IGraph, config: AlgorithmConfig): void }).run(graph) result.paths.forEach((path, pathIndex) => { - path.nodes.forEach(node => { + path.nodes.forEach((node) => { markItem(node, pathIndex) }) - path.edges.forEach(edge => { + path.edges.forEach((edge) => { markItem(edge, pathIndex) }) }) @@ -146,10 +146,10 @@ export function calculateAllChains(graph: IGraph, config: AlgorithmConfig): void const result = new Chains({ directed: config.directed }).run(graph) result.chains.forEach((chain, pathIndex) => { - chain.nodes.forEach(node => { + chain.nodes.forEach((node) => { markItem(node, pathIndex) }) - chain.edges.forEach(edge => { + chain.edges.forEach((edge) => { markItem(edge, pathIndex) }) }) @@ -196,11 +196,11 @@ export function calculateSingleSourceShortestPaths(graph: IGraph, config: Algori const predecessors = result.predecessors result.paths .orderBy( - path => path.nodes.size, + (path) => path.nodes.size, (p1, p2) => p1 - p2 ) .forEach((path, pathIndex) => { - path.nodes.forEach(node => { + path.nodes.forEach((node) => { const distance = distances.get(node)! / longestPathLength setComponent(node, pathIndex) diff --git a/demos/showcase/graphanalysis/algorithms/substructures.js b/demos/showcase/graphanalysis/algorithms/substructures.js index a3d59969b..5b65c6d22 100644 --- a/demos/showcase/graphanalysis/algorithms/substructures.js +++ b/demos/showcase/graphanalysis/algorithms/substructures.js @@ -58,10 +58,10 @@ export function calculateChainSubstructures(graph, config) { const substructures = chainSubstructures.run(graph) substructures.chains.forEach((chain, chainIndex) => { - chain.nodes.forEach(node => { + chain.nodes.forEach((node) => { markItem(node, chainIndex) }) - chain.edges.forEach(edge => { + chain.edges.forEach((edge) => { markItem(edge, chainIndex) }) }) @@ -92,10 +92,10 @@ export function calculateCycleSubstructures(graph, config) { const substructures = cycleSubstructures.run(graph) substructures.cycles.forEach((cycle, cycleIndex) => { - cycle.nodes.forEach(node => { + cycle.nodes.forEach((node) => { markItem(node, cycleIndex) }) - cycle.edges.forEach(edge => { + cycle.edges.forEach((edge) => { markItem(edge, cycleIndex) }) }) @@ -124,10 +124,10 @@ export function calculateStarSubstructures(graph, config) { const substructures = starSubstructures.run(graph) substructures.stars.forEach((star, starIndex) => { - star.nodes.forEach(node => { + star.nodes.forEach((node) => { markItem(node, starIndex) }) - star.edges.forEach(edge => { + star.edges.forEach((edge) => { markItem(edge, starIndex) }) }) @@ -155,10 +155,10 @@ export function calculateTreeSubstructures(graph, config) { const substructures = treeSubstructures.run(graph) substructures.trees.forEach((tree, treeIndex) => { - tree.nodes.forEach(node => { + tree.nodes.forEach((node) => { markItem(node, treeIndex) }) - tree.edges.forEach(edge => { + tree.edges.forEach((edge) => { markItem(edge, treeIndex) }) }) @@ -185,10 +185,10 @@ export function calculateCliqueSubstructures(graph) { const substructures = cliqueSubstructures.run(graph) substructures.cliques.forEach((clique, cliqueIndex) => { - clique.nodes.forEach(node => { + clique.nodes.forEach((node) => { markItem(node, cliqueIndex) }) - clique.edges.forEach(edge => { + clique.edges.forEach((edge) => { markItem(edge, cliqueIndex) }) }) diff --git a/demos/showcase/graphanalysis/algorithms/substructures.ts b/demos/showcase/graphanalysis/algorithms/substructures.ts index 452023670..4ad0b5db2 100644 --- a/demos/showcase/graphanalysis/algorithms/substructures.ts +++ b/demos/showcase/graphanalysis/algorithms/substructures.ts @@ -58,10 +58,10 @@ export function calculateChainSubstructures(graph: IGraph, config: AlgorithmConf const substructures = chainSubstructures.run(graph) substructures.chains.forEach((chain, chainIndex) => { - chain.nodes.forEach(node => { + chain.nodes.forEach((node) => { markItem(node, chainIndex) }) - chain.edges.forEach(edge => { + chain.edges.forEach((edge) => { markItem(edge, chainIndex) }) }) @@ -90,10 +90,10 @@ export function calculateCycleSubstructures(graph: IGraph, config: AlgorithmConf const substructures = cycleSubstructures.run(graph) substructures.cycles.forEach((cycle, cycleIndex) => { - cycle.nodes.forEach(node => { + cycle.nodes.forEach((node) => { markItem(node, cycleIndex) }) - cycle.edges.forEach(edge => { + cycle.edges.forEach((edge) => { markItem(edge, cycleIndex) }) }) @@ -120,10 +120,10 @@ export function calculateStarSubstructures(graph: IGraph, config: AlgorithmConfi const substructures = starSubstructures.run(graph) substructures.stars.forEach((star, starIndex) => { - star.nodes.forEach(node => { + star.nodes.forEach((node) => { markItem(node, starIndex) }) - star.edges.forEach(edge => { + star.edges.forEach((edge) => { markItem(edge, starIndex) }) }) @@ -149,10 +149,10 @@ export function calculateTreeSubstructures(graph: IGraph, config: AlgorithmConfi const substructures = treeSubstructures.run(graph) substructures.trees.forEach((tree, treeIndex) => { - tree.nodes.forEach(node => { + tree.nodes.forEach((node) => { markItem(node, treeIndex) }) - tree.edges.forEach(edge => { + tree.edges.forEach((edge) => { markItem(edge, treeIndex) }) }) @@ -178,10 +178,10 @@ export function calculateCliqueSubstructures(graph: IGraph): void { const substructures = cliqueSubstructures.run(graph) substructures.cliques.forEach((clique, cliqueIndex) => { - clique.nodes.forEach(node => { + clique.nodes.forEach((node) => { markItem(node, cliqueIndex) }) - clique.edges.forEach(edge => { + clique.edges.forEach((edge) => { markItem(edge, cliqueIndex) }) }) diff --git a/demos/showcase/graphanalysis/index.html b/demos/showcase/graphanalysis/index.html index 4541ffcd6..db42a2eaf 100644 --- a/demos/showcase/graphanalysis/index.html +++ b/demos/showcase/graphanalysis/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/showcase/graphanalysis/layout/CentralityStage.js b/demos/showcase/graphanalysis/layout/CentralityStage.js index 8e5e20abf..707d190fd 100644 --- a/demos/showcase/graphanalysis/layout/CentralityStage.js +++ b/demos/showcase/graphanalysis/layout/CentralityStage.js @@ -37,14 +37,6 @@ export class CentralityStage extends LayoutStageBase { */ directed = false - /** - * Creates a new instance of CentralityStage - * @param {!ILayoutAlgorithm} layout - */ - constructor(layout) { - super(layout) - } - /** * Applies the layout to the given graph. * @param {!LayoutGraph} graph the given graph @@ -54,16 +46,16 @@ export class CentralityStage extends LayoutStageBase { // change the node sizes if a centrality algorithm is applied const isCentralityAlgorithm = graph.nodes.find( - node => this.getTag(tags, node).centrality !== undefined + (node) => this.getTag(tags, node).centrality !== undefined ) if (isCentralityAlgorithm) { const mostCentralSize = 100 const leastCentralSize = 30 - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const nodeLayout = graph.getLayout(node) let centralityId = this.getTag(tags, node).centrality // centrality values are already normalized in [0,1] - if (isNaN(centralityId)) { + if (Number.isNaN(centralityId)) { centralityId = 0 } const size = Math.floor( @@ -72,7 +64,7 @@ export class CentralityStage extends LayoutStageBase { nodeLayout.setSize(size, size) }) } else { - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const nodeLayout = graph.getLayout(node) nodeLayout.setSize(30, 30) }) diff --git a/demos/showcase/graphanalysis/layout/CentralityStage.ts b/demos/showcase/graphanalysis/layout/CentralityStage.ts index 636c6e127..33aa21429 100644 --- a/demos/showcase/graphanalysis/layout/CentralityStage.ts +++ b/demos/showcase/graphanalysis/layout/CentralityStage.ts @@ -45,13 +45,6 @@ export class CentralityStage extends LayoutStageBase { */ directed = false - /** - * Creates a new instance of CentralityStage - */ - constructor(layout: ILayoutAlgorithm) { - super(layout) - } - /** * Applies the layout to the given graph. * @param graph the given graph @@ -61,16 +54,16 @@ export class CentralityStage extends LayoutStageBase { // change the node sizes if a centrality algorithm is applied const isCentralityAlgorithm = graph.nodes.find( - node => this.getTag(tags, node).centrality !== undefined + (node) => this.getTag(tags, node).centrality !== undefined ) if (isCentralityAlgorithm) { const mostCentralSize = 100 const leastCentralSize = 30 - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const nodeLayout = graph.getLayout(node) let centralityId = this.getTag(tags, node).centrality! // centrality values are already normalized in [0,1] - if (isNaN(centralityId)) { + if (Number.isNaN(centralityId)) { centralityId = 0 } const size = Math.floor( @@ -79,7 +72,7 @@ export class CentralityStage extends LayoutStageBase { nodeLayout.setSize(size, size) }) } else { - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { const nodeLayout = graph.getLayout(node) nodeLayout.setSize(30, 30) }) diff --git a/demos/showcase/graphanalysis/layout/layout.js b/demos/showcase/graphanalysis/layout/layout.js index ceccf9135..19d50a2f5 100644 --- a/demos/showcase/graphanalysis/layout/layout.js +++ b/demos/showcase/graphanalysis/layout/layout.js @@ -79,7 +79,7 @@ function getOrganicLayoutConfiguration(affectedNodes) { organicLayout.prependStage(new CentralityStage(organicLayout)) const organicLayoutData = new OrganicLayoutData({ - preferredEdgeLengths: edge => + preferredEdgeLengths: (edge) => edge.labels.reduce((width, label) => { return Math.max(label.layout.width + 50, width) }, 100), @@ -88,7 +88,7 @@ function getOrganicLayoutConfiguration(affectedNodes) { }) const labelingData = new LabelingData({ - edgeLabelPreferredPlacement: label => { + edgeLabelPreferredPlacement: (label) => { return new PreferredPlacementDescriptor({ sideOfEdge: label.tag === 'centrality' ? 'on-edge' : 'left-of-edge', distanceToEdge: 5 diff --git a/demos/showcase/graphanalysis/layout/layout.ts b/demos/showcase/graphanalysis/layout/layout.ts index 5f578a156..64754dfac 100644 --- a/demos/showcase/graphanalysis/layout/layout.ts +++ b/demos/showcase/graphanalysis/layout/layout.ts @@ -86,7 +86,7 @@ function getOrganicLayoutConfiguration(affectedNodes?: INode[] | undefined): { organicLayout.prependStage(new CentralityStage(organicLayout)) const organicLayoutData = new OrganicLayoutData({ - preferredEdgeLengths: edge => + preferredEdgeLengths: (edge) => edge.labels.reduce((width, label) => { return Math.max(label.layout.width + 50, width) }, 100), diff --git a/demos/showcase/graphanalysis/ui/ComponentSwitchingInputMode.js b/demos/showcase/graphanalysis/ui/ComponentSwitchingInputMode.js index 421731a59..f82f70503 100644 --- a/demos/showcase/graphanalysis/ui/ComponentSwitchingInputMode.js +++ b/demos/showcase/graphanalysis/ui/ComponentSwitchingInputMode.js @@ -362,7 +362,7 @@ class ComponentOverlayDescriptor extends BaseClass( if ( visual.size === width && components.length === visual.components.size && - components.every(c => visual.components.has(c)) + components.every((c) => visual.components.has(c)) ) { const g = visual.svgElement diff --git a/demos/showcase/graphanalysis/ui/ComponentSwitchingInputMode.ts b/demos/showcase/graphanalysis/ui/ComponentSwitchingInputMode.ts index 8d65f3c97..d4fd096a7 100644 --- a/demos/showcase/graphanalysis/ui/ComponentSwitchingInputMode.ts +++ b/demos/showcase/graphanalysis/ui/ComponentSwitchingInputMode.ts @@ -343,7 +343,7 @@ class ComponentOverlayDescriptor extends BaseClass( if ( visual.size === width && components.length === visual.components.size && - components.every(c => visual.components!.has(c)) + components.every((c) => visual.components!.has(c)) ) { const g = visual.svgElement diff --git a/demos/showcase/graphanalysis/ui/context-menu.js b/demos/showcase/graphanalysis/ui/context-menu.js index 0fa1838bb..099f9ba8a 100644 --- a/demos/showcase/graphanalysis/ui/context-menu.js +++ b/demos/showcase/graphanalysis/ui/context-menu.js @@ -45,7 +45,7 @@ export function initializeContextMenu(inputMode, graphComponent) { // Add event listeners to the various events that open the context menu. These listeners then // call the provided callback-function which in turn asks the current ContextMenuInputMode if a // context menu should be shown at the current location. - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (inputMode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } @@ -88,7 +88,7 @@ function populateContextMenu(graphComponent, contextMenu, args) { // get the item which is located at the mouse position const hits = graphComponent.graphModelManager.hitElementsAt(args.queryLocation).toArray() // use the first hit node - const hitNode = hits.find(hit => hit instanceof INode) + const hitNode = hits.find((hit) => hit instanceof INode) const selection = graphComponent.selection if (hitNode) { selection.setSelected(hitNode, true) @@ -104,7 +104,7 @@ function populateContextMenu(graphComponent, contextMenu, args) { if (needsStartNodes) { contextMenu.addMenuItem('Mark as Start Node', () => { // clear previous start nodes - graphComponent.graph.nodes.forEach(node => { + graphComponent.graph.nodes.forEach((node) => { if (getTag(node).type === 'start') { const tag = copyAndReplaceTag(node) delete tag.type @@ -116,7 +116,7 @@ function populateContextMenu(graphComponent, contextMenu, args) { const tag = copyAndReplaceTag(hitNode || selectedNodes.first()) tag.type = 'start' } else { - selectedNodes.forEach(node => { + selectedNodes.forEach((node) => { const tag = copyAndReplaceTag(node) tag.type = 'start' }) @@ -128,7 +128,7 @@ function populateContextMenu(graphComponent, contextMenu, args) { } if (needsEndNodes) { contextMenu.addMenuItem('Mark as End Node', () => { - graphComponent.graph.nodes.forEach(node => { + graphComponent.graph.nodes.forEach((node) => { // clear previous end nodes if (getTag(node).type === 'end') { const tag = copyAndReplaceTag(node) @@ -136,7 +136,7 @@ function populateContextMenu(graphComponent, contextMenu, args) { } }) - selectedNodes.forEach(node => { + selectedNodes.forEach((node) => { const tag = copyAndReplaceTag(node) tag.type = 'end' }) diff --git a/demos/showcase/graphanalysis/ui/context-menu.ts b/demos/showcase/graphanalysis/ui/context-menu.ts index 0c2fcdae9..33b030acc 100644 --- a/demos/showcase/graphanalysis/ui/context-menu.ts +++ b/demos/showcase/graphanalysis/ui/context-menu.ts @@ -54,7 +54,7 @@ export function initializeContextMenu( // Add event listeners to the various events that open the context menu. These listeners then // call the provided callback-function which in turn asks the current ContextMenuInputMode if a // context menu should be shown at the current location. - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (inputMode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } @@ -98,7 +98,7 @@ function populateContextMenu( // get the item which is located at the mouse position const hits = graphComponent.graphModelManager.hitElementsAt(args.queryLocation).toArray() // use the first hit node - const hitNode: IModelItem | undefined = hits.find(hit => hit instanceof INode) + const hitNode: IModelItem | undefined = hits.find((hit) => hit instanceof INode) const selection = graphComponent.selection if (hitNode) { selection.setSelected(hitNode, true) @@ -114,7 +114,7 @@ function populateContextMenu( if (needsStartNodes) { contextMenu.addMenuItem('Mark as Start Node', () => { // clear previous start nodes - graphComponent.graph.nodes.forEach(node => { + graphComponent.graph.nodes.forEach((node) => { if (getTag(node).type === 'start') { const tag = copyAndReplaceTag(node) delete tag.type @@ -126,7 +126,7 @@ function populateContextMenu( const tag = copyAndReplaceTag(hitNode || selectedNodes.first()) tag.type = 'start' } else { - selectedNodes.forEach(node => { + selectedNodes.forEach((node) => { const tag = copyAndReplaceTag(node) tag.type = 'start' }) @@ -138,7 +138,7 @@ function populateContextMenu( } if (needsEndNodes) { contextMenu.addMenuItem('Mark as End Node', () => { - graphComponent.graph.nodes.forEach(node => { + graphComponent.graph.nodes.forEach((node) => { // clear previous end nodes if (getTag(node).type === 'end') { const tag = copyAndReplaceTag(node) @@ -146,7 +146,7 @@ function populateContextMenu( } }) - selectedNodes.forEach(node => { + selectedNodes.forEach((node) => { const tag = copyAndReplaceTag(node) tag.type = 'end' }) diff --git a/demos/showcase/graphanalysis/ui/graph-structure-information.js b/demos/showcase/graphanalysis/ui/graph-structure-information.js index 28c76ce99..a917d72d5 100644 --- a/demos/showcase/graphanalysis/ui/graph-structure-information.js +++ b/demos/showcase/graphanalysis/ui/graph-structure-information.js @@ -36,24 +36,24 @@ import './graph-structure-information.css' export function initializeGraphInformation(graphComponent) { const inputMode = graphComponent.inputMode - inputMode.addDeletingSelectionListener(async _ => { + inputMode.addDeletingSelectionListener(async (_) => { updateGraphInformation(graphComponent) }) - inputMode.addDeletedSelectionListener(async _ => { + inputMode.addDeletedSelectionListener(async (_) => { updateGraphInformation(graphComponent) }) // edge creation - inputMode.createEdgeInputMode.addEdgeCreatedListener(async _ => { + inputMode.createEdgeInputMode.addEdgeCreatedListener(async (_) => { updateGraphInformation(graphComponent) }) - inputMode.addEdgePortsChangedListener(async _ => { + inputMode.addEdgePortsChangedListener(async (_) => { updateGraphInformation(graphComponent) }) - inputMode.addNodeCreatedListener(_ => { + inputMode.addNodeCreatedListener((_) => { updateGraphInformation(graphComponent) }) @@ -197,7 +197,7 @@ function updateNumberOfElements(graph, container) { container.lastElementChild?.remove() } - ;['Nodes', 'Edges'].forEach(element => { + ;['Nodes', 'Edges'].forEach((element) => { const row = document.createElement('div') row.classList.add('row', 'bold') container.appendChild(row) @@ -226,7 +226,7 @@ function updateStructureAnalysis(graph, container) { container.lastElementChild?.remove() } - structureAnalysis.forEach(algorithm => { + structureAnalysis.forEach((algorithm) => { const row = document.createElement('div') row.className = 'row' container.appendChild(row) diff --git a/demos/showcase/graphanalysis/ui/graph-structure-information.ts b/demos/showcase/graphanalysis/ui/graph-structure-information.ts index aa025de9c..d0dd189b1 100644 --- a/demos/showcase/graphanalysis/ui/graph-structure-information.ts +++ b/demos/showcase/graphanalysis/ui/graph-structure-information.ts @@ -40,24 +40,24 @@ import './graph-structure-information.css' export function initializeGraphInformation(graphComponent: GraphComponent): void { const inputMode = graphComponent.inputMode as GraphEditorInputMode - inputMode.addDeletingSelectionListener(async _ => { + inputMode.addDeletingSelectionListener(async (_) => { updateGraphInformation(graphComponent) }) - inputMode.addDeletedSelectionListener(async _ => { + inputMode.addDeletedSelectionListener(async (_) => { updateGraphInformation(graphComponent) }) // edge creation - inputMode.createEdgeInputMode.addEdgeCreatedListener(async _ => { + inputMode.createEdgeInputMode.addEdgeCreatedListener(async (_) => { updateGraphInformation(graphComponent) }) - inputMode.addEdgePortsChangedListener(async _ => { + inputMode.addEdgePortsChangedListener(async (_) => { updateGraphInformation(graphComponent) }) - inputMode.addNodeCreatedListener(_ => { + inputMode.addNodeCreatedListener((_) => { updateGraphInformation(graphComponent) }) @@ -176,7 +176,7 @@ function updateNumberOfElements(graph: IGraph, container: HTMLDivElement): void container.lastElementChild?.remove() } - ;['Nodes', 'Edges'].forEach(element => { + ;['Nodes', 'Edges'].forEach((element) => { const row = document.createElement('div') row.classList.add('row', 'bold') container.appendChild(row) @@ -203,7 +203,7 @@ function updateStructureAnalysis(graph: IGraph, container: Element): void { container.lastElementChild?.remove() } - structureAnalysis.forEach(algorithm => { + structureAnalysis.forEach((algorithm) => { const row = document.createElement('div') row.className = 'row' container.appendChild(row) diff --git a/demos/showcase/graphanalysis/ui/ui-utils.js b/demos/showcase/graphanalysis/ui/ui-utils.js index 2d12bff10..066d355ba 100644 --- a/demos/showcase/graphanalysis/ui/ui-utils.js +++ b/demos/showcase/graphanalysis/ui/ui-utils.js @@ -198,7 +198,7 @@ function loadGraph(graph, sample) { edgeCreator.tagProvider = () => ({ components: [] }) - nodesSource.addSuccessorIds(item => item, edgeCreator) + nodesSource.addSuccessorIds((item) => item, edgeCreator) graphBuilder.buildGraph() } @@ -231,7 +231,7 @@ async function switchAlgorithm(graphComponent) { export function setUIDisabled(disabled, graphComponent) { document .querySelectorAll('.demo-page__toolbar select, .demo-page__toolbar button') - .forEach(element => { + .forEach((element) => { if (element instanceof HTMLSelectElement || element instanceof HTMLButtonElement) { element.disabled = disabled } @@ -255,8 +255,8 @@ export function setUIDisabled(disabled, graphComponent) { function deleteWeightLabels(graph) { graph.edgeLabels .toArray() - .filter(label => label.tag === 'weight') - .forEach(label => { + .filter((label) => label.tag === 'weight') + .forEach((label) => { graph.remove(label) }) } @@ -266,7 +266,7 @@ function deleteWeightLabels(graph) { * @param {!IGraph} graph */ function generateWeightLabels(graph) { - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { graph.addLabel({ owner: edge, // select a weight from 1 to 20 diff --git a/demos/showcase/graphanalysis/ui/ui-utils.ts b/demos/showcase/graphanalysis/ui/ui-utils.ts index 319d5a523..8483626c3 100644 --- a/demos/showcase/graphanalysis/ui/ui-utils.ts +++ b/demos/showcase/graphanalysis/ui/ui-utils.ts @@ -200,7 +200,7 @@ function loadGraph(graph: IGraph, sample: SampleData): void { edgeCreator.defaults.style = graph.edgeDefaults.style // add an array to each edge's tag to store the component to which it belongs edgeCreator.tagProvider = (): Tag => ({ components: [] }) - nodesSource.addSuccessorIds(item => item, edgeCreator) + nodesSource.addSuccessorIds((item) => item, edgeCreator) graphBuilder.buildGraph() } @@ -229,7 +229,7 @@ async function switchAlgorithm(graphComponent: GraphComponent): Promise { export function setUIDisabled(disabled: boolean, graphComponent: GraphComponent): void { document .querySelectorAll('.demo-page__toolbar select, .demo-page__toolbar button') - .forEach(element => { + .forEach((element) => { if (element instanceof HTMLSelectElement || element instanceof HTMLButtonElement) { element.disabled = disabled } @@ -252,8 +252,8 @@ export function setUIDisabled(disabled: boolean, graphComponent: GraphComponent) function deleteWeightLabels(graph: IGraph): void { graph.edgeLabels .toArray() - .filter(label => label.tag === 'weight') - .forEach(label => { + .filter((label) => label.tag === 'weight') + .forEach((label) => { graph.remove(label) }) } @@ -262,7 +262,7 @@ function deleteWeightLabels(graph: IGraph): void { * Generates labels for each edge in the graph with a random weight. */ function generateWeightLabels(graph: IGraph): void { - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { graph.addLabel({ owner: edge, // select a weight from 1 to 20 diff --git a/demos/showcase/home-automation/FlowEdge/FlowEdge.js b/demos/showcase/home-automation/FlowEdge/FlowEdge.js new file mode 100644 index 000000000..7b6a8a645 --- /dev/null +++ b/demos/showcase/home-automation/FlowEdge/FlowEdge.js @@ -0,0 +1,231 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { CreateEdgeInputMode, GraphComponent, IGraph, INode, Point, YPoint } from 'yfiles' +import './flowEdge.css' +import { FlowPortRelocationHandleProvider } from './FlowPortRelocationHandleProvider.js' +import { validatePortTag } from '../FlowNode/FlowNodePort.js' +import { FlowEdgeStyle } from './FlowEdgeStyle.js' + +/** + * Modifies general edge-related settings on the graph component. + * @param {!GraphComponent} gc + */ +export function configureFlowEdges(gc) { + const { graph } = gc + + graph.edgeDefaults.style = new FlowEdgeStyle() + graph.decorator.edgeDecorator.selectionDecorator.hideImplementation() + graph.decorator.edgeDecorator.highlightDecorator.hideImplementation() + graph.decorator.edgeDecorator.focusIndicatorDecorator.hideImplementation() + + graph.decorator.edgeDecorator.edgePortHandleProviderDecorator.setFactory((edge) => { + return new FlowPortRelocationHandleProvider(graph, edge) + }) +} + +/** + * Calculates and applies custom edge bends. + * @param {!IGraph} graph + * @param {!INode} node + */ +export function recalculateEdges(graph, node) { + const affectedEdges = graph.edgesAt(node) + affectedEdges.forEach((edge) => { + const portTag = edge.sourcePort?.tag + if (!validatePortTag(portTag)) { + return + } + const fromSide = portTag.side + const bends = getSmoothEdgeControlPoints({ + start: edge.sourcePort.location, + end: edge.targetPort.location, + fromSide + }) + + graph.clearBends(edge) + graph.addBends(edge, bends) + }) +} + +/** + * Modifies edge-related input mode settings. + * @param {!CreateEdgeInputMode} inputMode + */ +export function configureCreateEdgeInputMode(inputMode) { + inputMode.startOverCandidateOnly = true + inputMode.useHitItemsCandidatesOnly = false + inputMode.allowCreateBend = false + + inputMode.addEdgeCreationStartedListener(({ inputModeContext, dummyEdgeGraph, dummyEdge }) => { + const gc = inputModeContext?.canvasComponent + if (!(gc instanceof GraphComponent)) { + return + } + // Clear any existing selection when a new edge is being created. + // This is for visual purposes: a node being selected affects the appearance + // of its connected edges, which makes the graph look very "busy". + gc.selection.clear() + + dummyEdgeGraph.setStyle(dummyEdge, new FlowEdgeStyle('newEdge')) + }) + + inputMode.addEdgeCreatedListener(({ inputModeContext }, { item: edge }) => { + const gc = inputModeContext?.canvasComponent + if (!(gc instanceof GraphComponent)) { + return + } + gc.selection.setSelected(edge, true) + gc.graph.setStyle(edge, new FlowEdgeStyle()) + }) + + inputMode.edgeCreator = (_, graph, sourcePortCandidate, targetPortCandidate, dummyEdge) => { + // Assign ports to constants to simplify references + let targetPort = targetPortCandidate.port + let sourcePort = sourcePortCandidate.port + const sourcePortSide = sourcePort?.tag.side + const targetPortSide = targetPort?.tag.side + if (sourcePortSide === 'left' && targetPortSide === 'right') { + const tempPort = targetPort + targetPort = sourcePort + sourcePort = tempPort + } + const portTag = sourcePort.tag + if (!validatePortTag(portTag)) { + return null + } + const fromSide = portTag.side + + const bends = getSmoothEdgeControlPoints({ + start: sourcePort.location.toPoint(), + end: targetPort.location.toPoint(), + fromSide + }) + const edge = graph.createEdge(sourcePort, targetPort, dummyEdge.style) + graph.addBends(edge, bends) + + // create the edge from the source port candidate to the new node + return edge + } +} + +/** + * Calculates 2 cubic Bezier control points to create a smooth curve + * instead of a straight line segment when creating a new edge + * or reconnecting an existing one. This algorithm is also used to modify + * any edges connected to a node whose layout has changed. + * + * The shape of the resulting curve depends on both the relative position + * of the two endpoints and from which side (left/right) + * the edge is being plotted. The idea here is that the end portions + * of the resulting edge should curve away from the intended port for a moment. + * @param {!object} edge + * @returns {!Array.} + */ +export function getSmoothEdgeControlPoints(edge) { + // Minimum length between the endpoint and its corresponding control point. + // If too close to 0, edges that are close to vertical get almost no curve. + let minCPDisplacement = 36 + + // Maximum length between the endpoint and its corresponding control point. + // It must be capped, or otherwise plotting edges between very distant ports + // results in bends that go very far away from the ports (in extreme cases, + // beyond the graph viewport). + const maxCPDisplacement = 200 + + const start = new YPoint(edge.start.x, edge.start.y) + const end = new YPoint(edge.end.x, edge.end.y) + const displacement = YPoint.subtract(start, end) + const direction = { left: -1, right: 1 }[edge.fromSide] + + // For extremely short edges, cap the max control point displacement + // depending on the distance between `start` and `end`: + minCPDisplacement = Math.min(minCPDisplacement, Math.abs(start.distanceTo(end)) / 2) + + const cPDisplacementMagnitude = Math.max( + minCPDisplacement, + Math.min(maxCPDisplacement, Math.abs(displacement.x) / 2) + ) + + const controlPoints = [ + new Point(start.x + direction * cPDisplacementMagnitude, start.y), + new Point(end.x - direction * cPDisplacementMagnitude, end.y) + ] + + // Depending on the angle of the line segment (start, end), we may want to rotate (start, C1) + // and (C2, end) slightly. We ignore this angle for some time, meaning that for the most part, + // the derived angle will be 0 and have no effect. + + const deadAngleZone = Math.PI * 0.75 + const angleModifier = 0.5 + + let angle = direction === 1 ? getAngle(start, end) : getAngle(end, start) + const angleSign = Math.sign(angle) + angle = Math.abs(angle) + angle = Math.max(0, angle - deadAngleZone) * angleModifier + angle = angle * angleSign + + // Apply the rotation, if any: + controlPoints[0] = rotateByAngle(start.toPoint(), controlPoints[0], angle)[1] + controlPoints[1] = rotateByAngle(end.toPoint(), controlPoints[1], angle)[1] + + return controlPoints +} + +/** + * Calculates the angle (in radians) between the line crossing points `a` and `b` and the horizontal line + * passing through `a`. + * @param {!YPoint} a + * @param {!YPoint} b + * @returns {number} + */ +function getAngle(a, b) { + const deltaY = b.y - a.y + const deltaX = b.x - a.x + return Math.atan2(deltaY, deltaX) +} + +/** + * For a line segment (`a`, `b`), calculates a new line segment by applying + * the specified angle (in radians). + * @param {!Point} a + * @param {!Point} b + * @param {number} angle + * @returns {!Array.} + */ +function rotateByAngle(a, b, angle) { + const distanceAB = Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2) + + const currentAngle = Math.atan2(b.y - a.y, b.x - a.x) + const newAngle = currentAngle + angle + + const newX = a.x + distanceAB * Math.cos(newAngle) + const newY = a.y + distanceAB * Math.sin(newAngle) + + return [a, new Point(newX, newY)] +} diff --git a/demos/showcase/home-automation/FlowEdge/FlowEdge.ts b/demos/showcase/home-automation/FlowEdge/FlowEdge.ts new file mode 100644 index 000000000..116313595 --- /dev/null +++ b/demos/showcase/home-automation/FlowEdge/FlowEdge.ts @@ -0,0 +1,222 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { CreateEdgeInputMode, GraphComponent, IGraph, INode, Point, YPoint } from 'yfiles' +import './flowEdge.css' +import { FlowPortRelocationHandleProvider } from './FlowPortRelocationHandleProvider' +import { validatePortTag } from '../FlowNode/FlowNodePort' +import { FlowEdgeStyle } from './FlowEdgeStyle' + +/** + * Modifies general edge-related settings on the graph component. + */ +export function configureFlowEdges(gc: GraphComponent): void { + const { graph } = gc + + graph.edgeDefaults.style = new FlowEdgeStyle() + graph.decorator.edgeDecorator.selectionDecorator.hideImplementation() + graph.decorator.edgeDecorator.highlightDecorator.hideImplementation() + graph.decorator.edgeDecorator.focusIndicatorDecorator.hideImplementation() + + graph.decorator.edgeDecorator.edgePortHandleProviderDecorator.setFactory(edge => { + return new FlowPortRelocationHandleProvider(graph, edge) + }) +} + +/** + * Calculates and applies custom edge bends. + */ +export function recalculateEdges(graph: IGraph, node: INode): void { + const affectedEdges = graph.edgesAt(node) + affectedEdges.forEach(edge => { + const portTag = edge.sourcePort?.tag + if (!validatePortTag(portTag)) { + return + } + const fromSide = portTag.side + const bends = getSmoothEdgeControlPoints({ + start: edge.sourcePort!.location, + end: edge.targetPort!.location, + fromSide + }) + + graph.clearBends(edge) + graph.addBends(edge, bends) + }) +} + +/** + * Modifies edge-related input mode settings. + */ +export function configureCreateEdgeInputMode(inputMode: CreateEdgeInputMode): void { + inputMode.startOverCandidateOnly = true + inputMode.useHitItemsCandidatesOnly = false + inputMode.allowCreateBend = false + + inputMode.addEdgeCreationStartedListener(({ inputModeContext, dummyEdgeGraph, dummyEdge }) => { + const gc = inputModeContext?.canvasComponent + if (!(gc instanceof GraphComponent)) { + return + } + // Clear any existing selection when a new edge is being created. + // This is for visual purposes: a node being selected affects the appearance + // of its connected edges, which makes the graph look very "busy". + gc.selection.clear() + + dummyEdgeGraph.setStyle(dummyEdge, new FlowEdgeStyle('newEdge')) + }) + + inputMode.addEdgeCreatedListener(({ inputModeContext }, { item: edge }) => { + const gc = inputModeContext?.canvasComponent + if (!(gc instanceof GraphComponent)) { + return + } + gc.selection.setSelected(edge, true) + gc.graph.setStyle(edge, new FlowEdgeStyle()) + }) + + inputMode.edgeCreator = (_, graph, sourcePortCandidate, targetPortCandidate, dummyEdge) => { + // Assign ports to constants to simplify references + let targetPort = targetPortCandidate!.port! + let sourcePort = sourcePortCandidate.port! + const sourcePortSide = sourcePort?.tag.side + const targetPortSide = targetPort?.tag.side + if (sourcePortSide === 'left' && targetPortSide === 'right') { + const tempPort = targetPort + targetPort = sourcePort + sourcePort = tempPort + } + const portTag = sourcePort.tag + if (!validatePortTag(portTag)) { + return null + } + const fromSide = portTag.side + + const bends = getSmoothEdgeControlPoints({ + start: sourcePort.location.toPoint(), + end: targetPort.location.toPoint(), + fromSide + }) + const edge = graph.createEdge(sourcePort, targetPort, dummyEdge.style) + graph.addBends(edge, bends) + + // create the edge from the source port candidate to the new node + return edge + } +} + +/** + * Calculates 2 cubic Bezier control points to create a smooth curve + * instead of a straight line segment when creating a new edge + * or reconnecting an existing one. This algorithm is also used to modify + * any edges connected to a node whose layout has changed. + * + * The shape of the resulting curve depends on both the relative position + * of the two endpoints and from which side (left/right) + * the edge is being plotted. The idea here is that the end portions + * of the resulting edge should curve away from the intended port for a moment. + */ +export function getSmoothEdgeControlPoints(edge: { + start: Readonly + end: Readonly + fromSide: 'left' | 'right' +}): Array { + // Minimum length between the endpoint and its corresponding control point. + // If too close to 0, edges that are close to vertical get almost no curve. + let minCPDisplacement = 36 + + // Maximum length between the endpoint and its corresponding control point. + // It must be capped, or otherwise plotting edges between very distant ports + // results in bends that go very far away from the ports (in extreme cases, + // beyond the graph viewport). + const maxCPDisplacement = 200 + + const start = new YPoint(edge.start.x, edge.start.y) + const end = new YPoint(edge.end.x, edge.end.y) + const displacement = YPoint.subtract(start, end) + const direction = { left: -1, right: 1 }[edge.fromSide] + + // For extremely short edges, cap the max control point displacement + // depending on the distance between `start` and `end`: + minCPDisplacement = Math.min(minCPDisplacement, Math.abs(start.distanceTo(end)) / 2) + + const cPDisplacementMagnitude = Math.max( + minCPDisplacement, + Math.min(maxCPDisplacement, Math.abs(displacement.x) / 2) + ) + + const controlPoints = [ + new Point(start.x + direction * cPDisplacementMagnitude, start.y), + new Point(end.x - direction * cPDisplacementMagnitude, end.y) + ] + + // Depending on the angle of the line segment (start, end), we may want to rotate (start, C1) + // and (C2, end) slightly. We ignore this angle for some time, meaning that for the most part, + // the derived angle will be 0 and have no effect. + + const deadAngleZone = Math.PI * 0.75 + const angleModifier = 0.5 + + let angle = direction === 1 ? getAngle(start, end) : getAngle(end, start) + const angleSign = Math.sign(angle) + angle = Math.abs(angle) + angle = Math.max(0, angle - deadAngleZone) * angleModifier + angle = angle * angleSign + + // Apply the rotation, if any: + controlPoints[0] = rotateByAngle(start.toPoint(), controlPoints[0], angle)[1] + controlPoints[1] = rotateByAngle(end.toPoint(), controlPoints[1], angle)[1] + + return controlPoints +} + +/** + * Calculates the angle (in radians) between the line crossing points `a` and `b` and the horizontal line + * passing through `a`. + */ +function getAngle(a: YPoint, b: YPoint): number { + const deltaY = b.y - a.y + const deltaX = b.x - a.x + return Math.atan2(deltaY, deltaX) +} + +/** + * For a line segment (`a`, `b`), calculates a new line segment by applying + * the specified angle (in radians). + */ +function rotateByAngle(a: Point, b: Point, angle: number): [Point, Point] { + const distanceAB = Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2) + + const currentAngle = Math.atan2(b.y - a.y, b.x - a.x) + const newAngle = currentAngle + angle + + const newX = a.x + distanceAB * Math.cos(newAngle) + const newY = a.y + distanceAB * Math.sin(newAngle) + + return [a, new Point(newX, newY)] +} diff --git a/demos/showcase/home-automation/FlowEdge/FlowEdgeStyle.js b/demos/showcase/home-automation/FlowEdge/FlowEdgeStyle.js new file mode 100644 index 000000000..219025465 --- /dev/null +++ b/demos/showcase/home-automation/FlowEdge/FlowEdgeStyle.js @@ -0,0 +1,236 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + BezierEdgeStyle, + BezierEdgeStyleRenderer, + GeneralPath, + GraphComponent, + IRenderContext, + SvgVisualGroup, + Visual +} from 'yfiles' + +/** + * This class decorates the original `BezierEdgeStyleRenderer` in such a way that the resulting + * SVG visuals stay almost the same, but gain an additional outline edge that follows + * the same curve as the original path. This way we don't need to re-implement any of the complex logic + * behind plotting cubic Beziér curves based on point data. + */ +class FlowEdgeRenderer extends BezierEdgeStyleRenderer { + /** @type {{container: 'flow-edge', basePath: 'flow-edge__main', outline: 'flow-edge__outline', animation: 'flow-edge__animation'}} */ + static get cssClass() { + if (typeof FlowEdgeRenderer.$cssClass === 'undefined') { + FlowEdgeRenderer.$cssClass = { + container: 'flow-edge', + basePath: 'flow-edge__main', + outline: 'flow-edge__outline', + animation: 'flow-edge__animation' + } + } + + return FlowEdgeRenderer.$cssClass + } + + /** @type {{container: 'flow-edge', basePath: 'flow-edge__main', outline: 'flow-edge__outline', animation: 'flow-edge__animation'}} */ + static set cssClass(cssClass) { + FlowEdgeRenderer.$cssClass = cssClass + } + + /** + * @param {?Visual} visual + * @returns {?SVGGElement} + */ + static getGroupElement(visual) { + if (!(visual instanceof SvgVisualGroup) || !(visual.svgElement instanceof SVGGElement)) { + return null + } + return visual.svgElement + } + + /** + * Retrieves the created by the original `createVisual` implementation. + * @param {?Visual} visual + * @returns {?SVGPathElement} + */ + static getOriginalPathElement(visual) { + if (!(visual instanceof SvgVisualGroup)) { + return null + } + const path = visual.svgElement.querySelector( + `path:not(.${FlowEdgeRenderer.cssClass.outline}):not(.${FlowEdgeRenderer.cssClass.animation})` + ) + return path instanceof SVGPathElement ? path : null + } + + /** + * Retrieves the outline created by our ` createVisual` override. + * @param {?Visual} visual + * @returns {?SVGPathElement} + */ + static getPathOutlineElement(visual) { + if (!(visual instanceof SvgVisualGroup)) { + return null + } + const path = visual.svgElement.querySelector(`path.${FlowEdgeRenderer.cssClass.outline}`) + return path instanceof SVGPathElement ? path : null + } + + /** + * Retrieves the animation created by our ` createVisual` override. + * @param {?Visual} visual + * @returns {?SVGPathElement} + */ + static getPathAnimationElement(visual) { + if (!(visual instanceof SvgVisualGroup)) { + return null + } + const path = visual.svgElement.querySelector(`path.${FlowEdgeRenderer.cssClass.animation}`) + return path instanceof SVGPathElement ? path : null + } + + isDummyNewEdge + isReconnecting + + /** + * @param {!('newEdge'|'edgeReconnection')} [mode] + */ + constructor(mode) { + super() + this.isDummyNewEdge = mode === 'newEdge' + this.isReconnecting = mode === 'edgeReconnection' + } + + /** + * Applies BEM-style class modifiers to the provided element depending on the state + * of the edge. + * @param {!IRenderContext} context + * @param {!SVGElement} element + */ + setClassModifiers(context, element) { + const gc = context.canvasComponent instanceof GraphComponent ? context.canvasComponent : null + const { sourceNode, targetNode } = this.edge + + const isEdgeSelected = gc?.selection.isSelected(this.edge) + const isEdgeHovered = gc?.highlightIndicatorManager.selectionModel?.includes(this.edge) + + const isConnectedNodeSelected = + (sourceNode && gc?.selection.isSelected(sourceNode)) || + (targetNode && gc?.selection.isSelected(targetNode)) + + const modifiers = { + selected: isEdgeSelected, + 'connected-node-selected': isConnectedNodeSelected, + reversed: this.edge.sourcePort?.tag.side === 'left', + hovered: isEdgeHovered, + 'dummy-new-edge': this.isDummyNewEdge, + reconnecting: this.isReconnecting + } + + for (const [modifier, enabled] of Object.entries(modifiers)) { + element.classList.toggle( + `${FlowEdgeRenderer.cssClass.container}--${modifier}`, + enabled ?? false + ) + } + } + + /** + * Takes whatever visual is created by `BezierEdgeStyleRenderer`, + * clones the `` element from its group, sets a slightly thicker stroke + * with a different color, and appends it to the top of the group so that + * it's displayed in the background. This creates a border effect along the path. + * @param {!IRenderContext} context + * @returns {?Visual} + */ + createVisual(context) { + const visual = super.createVisual(context) + const groupElement = FlowEdgeRenderer.getGroupElement(visual) + const path = FlowEdgeRenderer.getOriginalPathElement(visual) + if (!groupElement || !path) { + return visual + } + + this.setClassModifiers(context, groupElement) + + const outline = path.cloneNode(true) + const animation = path.cloneNode(true) + path.insertAdjacentElement('beforebegin', outline) + path.insertAdjacentElement('afterend', animation) + + groupElement.classList.add(FlowEdgeRenderer.cssClass.container) + path.classList.add(FlowEdgeRenderer.cssClass.basePath) + outline.classList.add(FlowEdgeRenderer.cssClass.outline) + animation.classList.add(FlowEdgeRenderer.cssClass.animation) + + return visual + } + + /** + * While updating the visual, we retrieve the extra outline added earlier + * and synchronize its path data with the original 's data. + * @param {!IRenderContext} context + * @param {?Visual} oldVisual + * @returns {?Visual} + */ + updateVisual(context, oldVisual) { + const visual = super.updateVisual(context, oldVisual) + const groupElement = FlowEdgeRenderer.getGroupElement(visual) + const path = FlowEdgeRenderer.getOriginalPathElement(visual) + const outline = FlowEdgeRenderer.getPathOutlineElement(visual) + const animation = FlowEdgeRenderer.getPathAnimationElement(visual) + + if (path && outline && animation) { + const pathData = path.getAttribute('d') + pathData && outline.setAttribute('d', pathData) + pathData && animation.setAttribute('d', pathData) + } + + groupElement && this.setClassModifiers(context, groupElement) + + return visual + } + + /** + * @param {!GeneralPath} path + * @returns {!GeneralPath} + */ + cropPath(path) { + return path + } +} + +export class FlowEdgeStyle extends BezierEdgeStyle { + /** + * @param {!('newEdge'|'edgeReconnection')} [mode] + */ + constructor(mode) { + const renderer = new FlowEdgeRenderer(mode) + super(renderer) + } +} diff --git a/demos/showcase/home-automation/FlowEdge/FlowEdgeStyle.ts b/demos/showcase/home-automation/FlowEdge/FlowEdgeStyle.ts new file mode 100644 index 000000000..cd7febfc1 --- /dev/null +++ b/demos/showcase/home-automation/FlowEdge/FlowEdgeStyle.ts @@ -0,0 +1,197 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + BezierEdgeStyle, + BezierEdgeStyleRenderer, + GeneralPath, + GraphComponent, + IRenderContext, + SvgVisualGroup, + Visual +} from 'yfiles' + +/** + * This class decorates the original `BezierEdgeStyleRenderer` in such a way that the resulting + * SVG visuals stay almost the same, but gain an additional outline edge that follows + * the same curve as the original path. This way we don't need to re-implement any of the complex logic + * behind plotting cubic Beziér curves based on point data. + */ +class FlowEdgeRenderer extends BezierEdgeStyleRenderer { + private static cssClass = { + container: 'flow-edge', + basePath: 'flow-edge__main', + outline: 'flow-edge__outline', + animation: 'flow-edge__animation' + } + + private static getGroupElement(visual: Visual | null): SVGGElement | null { + if (!(visual instanceof SvgVisualGroup) || !(visual.svgElement instanceof SVGGElement)) { + return null + } + return visual.svgElement + } + + /** + * Retrieves the created by the original `createVisual` implementation. + */ + private static getOriginalPathElement(visual: Visual | null): SVGPathElement | null { + if (!(visual instanceof SvgVisualGroup)) { + return null + } + const path = visual.svgElement.querySelector( + `path:not(.${FlowEdgeRenderer.cssClass.outline}):not(.${FlowEdgeRenderer.cssClass.animation})` + ) + return path instanceof SVGPathElement ? path : null + } + + /** + * Retrieves the outline created by our ` createVisual` override. + */ + private static getPathOutlineElement(visual: Visual | null): SVGPathElement | null { + if (!(visual instanceof SvgVisualGroup)) { + return null + } + const path = visual.svgElement.querySelector(`path.${FlowEdgeRenderer.cssClass.outline}`) + return path instanceof SVGPathElement ? path : null + } + + /** + * Retrieves the animation created by our ` createVisual` override. + */ + private static getPathAnimationElement(visual: Visual | null): SVGPathElement | null { + if (!(visual instanceof SvgVisualGroup)) { + return null + } + const path = visual.svgElement.querySelector(`path.${FlowEdgeRenderer.cssClass.animation}`) + return path instanceof SVGPathElement ? path : null + } + + private readonly isDummyNewEdge: boolean + private readonly isReconnecting: boolean + + constructor(mode?: 'newEdge' | 'edgeReconnection') { + super() + this.isDummyNewEdge = mode === 'newEdge' + this.isReconnecting = mode === 'edgeReconnection' + } + + /** + * Applies BEM-style class modifiers to the provided element depending on the state + * of the edge. + */ + private setClassModifiers(context: IRenderContext, element: SVGElement): void { + const gc = context.canvasComponent instanceof GraphComponent ? context.canvasComponent : null + const { sourceNode, targetNode } = this.edge + + const isEdgeSelected = gc?.selection.isSelected(this.edge) + const isEdgeHovered = gc?.highlightIndicatorManager.selectionModel?.includes(this.edge) + + const isConnectedNodeSelected = + (sourceNode && gc?.selection.isSelected(sourceNode)) || + (targetNode && gc?.selection.isSelected(targetNode)) + + const modifiers = { + selected: isEdgeSelected, + 'connected-node-selected': isConnectedNodeSelected, + reversed: this.edge.sourcePort?.tag.side === 'left', + hovered: isEdgeHovered, + 'dummy-new-edge': this.isDummyNewEdge, + reconnecting: this.isReconnecting + } + + for (const [modifier, enabled] of Object.entries(modifiers)) { + element.classList.toggle( + `${FlowEdgeRenderer.cssClass.container}--${modifier}`, + enabled ?? false + ) + } + } + + /** + * Takes whatever visual is created by `BezierEdgeStyleRenderer`, + * clones the `` element from its group, sets a slightly thicker stroke + * with a different color, and appends it to the top of the group so that + * it's displayed in the background. This creates a border effect along the path. + */ + createVisual(context: IRenderContext): Visual | null { + const visual = super.createVisual(context) + const groupElement = FlowEdgeRenderer.getGroupElement(visual) + const path = FlowEdgeRenderer.getOriginalPathElement(visual) + if (!groupElement || !path) { + return visual + } + + this.setClassModifiers(context, groupElement) + + const outline = path.cloneNode(true) as SVGPathElement + const animation = path.cloneNode(true) as SVGPathElement + path.insertAdjacentElement('beforebegin', outline) + path.insertAdjacentElement('afterend', animation) + + groupElement.classList.add(FlowEdgeRenderer.cssClass.container) + path.classList.add(FlowEdgeRenderer.cssClass.basePath) + outline.classList.add(FlowEdgeRenderer.cssClass.outline) + animation.classList.add(FlowEdgeRenderer.cssClass.animation) + + return visual + } + + /** + * While updating the visual, we retrieve the extra outline added earlier + * and synchronize its path data with the original 's data. + */ + updateVisual(context: IRenderContext, oldVisual: Visual | null): Visual | null { + const visual = super.updateVisual(context, oldVisual) + const groupElement = FlowEdgeRenderer.getGroupElement(visual) + const path = FlowEdgeRenderer.getOriginalPathElement(visual) + const outline = FlowEdgeRenderer.getPathOutlineElement(visual) + const animation = FlowEdgeRenderer.getPathAnimationElement(visual) + + if (path && outline && animation) { + const pathData = path.getAttribute('d') + pathData && outline.setAttribute('d', pathData) + pathData && animation.setAttribute('d', pathData) + } + + groupElement && this.setClassModifiers(context, groupElement) + + return visual + } + + protected cropPath(path: GeneralPath): GeneralPath { + return path + } +} + +export class FlowEdgeStyle extends BezierEdgeStyle { + constructor(mode?: 'newEdge' | 'edgeReconnection') { + const renderer = new FlowEdgeRenderer(mode) + super(renderer) + } +} diff --git a/demos/showcase/home-automation/FlowEdge/FlowPortRelocationHandleProvider.js b/demos/showcase/home-automation/FlowEdge/FlowPortRelocationHandleProvider.js new file mode 100644 index 000000000..d6b2ea627 --- /dev/null +++ b/demos/showcase/home-automation/FlowEdge/FlowPortRelocationHandleProvider.js @@ -0,0 +1,153 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + IEdge, + IGraph, + IHandle, + IInputModeContext, + IPort, + IPortCandidate, + Point, + PortRelocationHandle, + PortRelocationHandleProvider, + Visualization +} from 'yfiles' +import { FlowEdgeStyle } from './FlowEdgeStyle.js' +import { validatePortTag } from '../FlowNode/FlowNodePort.js' +import { getSmoothEdgeControlPoints } from './FlowEdge.js' + +/** + * A handle provider that provides port relocation handles that look the same as the handles + * as during new edge creation. + */ +export class FlowPortRelocationHandleProvider extends PortRelocationHandleProvider { + /** + * @param {?IGraph} graph + * @param {!IEdge} edge + * @param {boolean} sourcePort + * @returns {?IHandle} + */ + createPortRelocationHandle(graph, edge, sourcePort) { + if (!graph) { + return null + } + const portRelocationHandle = new FlowPortRelocationHandle(graph, edge, sourcePort) + portRelocationHandle.showHitPortOwnerCandidatesOnly = false + portRelocationHandle.addExistingPort = false + portRelocationHandle.visualization = Visualization.DUMMY + return portRelocationHandle + } +} + +class FlowPortRelocationHandle extends PortRelocationHandle { + originalBendLocations = null + fixedPort = null + lastClosestPortCandidate = null + + /** + * Store the port candidate so that the edge can visually snap to it. + * @param {?IPortCandidate} portCandidate + */ + setClosestCandidate(portCandidate) { + super.setClosestCandidate(portCandidate) + this.lastClosestPortCandidate = portCandidate + } + + /** + * To perform edge curve calculations later on, we need to identify which port + * is the one that's not going to change as a result of the reconnection process. + * + * We also store the original bends of the edge, so they can be restored + * if reconnection is canceled. + * @param {!IInputModeContext} context + */ + initializeDrag(context) { + super.initializeDrag(context) + this.fixedPort = this.sourceEnd ? this.edge.targetPort : this.edge.sourcePort + this.originalBendLocations = this.edge.bends.map((bend) => bend.location.toPoint()).toArray() + this.getGraph(context)?.setStyle(this.edge, new FlowEdgeStyle('edgeReconnection')) + if (this.dummyEdge) { + this.dummyEdge.style = new FlowEdgeStyle('edgeReconnection') + } + } + + /** + * On each position change, apply new edge bends. The visual result should be exactly the same + * as during creating a new edge. + * @param {!IInputModeContext} context + * @param {!Point} originalLocation + * @param {!Point} newLocation + */ + handleMove(context, originalLocation, newLocation) { + super.handleMove(context, originalLocation, newLocation) + + const { fixedPort, sourceEnd } = this + const fromSide = validatePortTag(fixedPort?.tag) ? fixedPort?.tag.side : null + + const newVisualLocation = this.lastClosestPortCandidate?.port?.location ?? newLocation + const fixedPortLocation = fixedPort?.location + + if (!fromSide || !fixedPortLocation) { + return + } + + const oppositeSide = { left: 'right', right: 'left' } + + const bends = getSmoothEdgeControlPoints({ + start: sourceEnd ? newVisualLocation : fixedPortLocation, + end: sourceEnd ? fixedPortLocation : newVisualLocation, + fromSide: sourceEnd ? oppositeSide[fromSide] : fromSide + }) + + this.getGraph(context)?.clearBends(this.edge) + this.getGraph(context)?.addBends(this.edge, bends) + } + + /** + * Restore the original edge bends that were saved earlier. + * @param {!IInputModeContext} context + * @param {!Point} originalLocation + */ + cancelDrag(context, originalLocation) { + super.cancelDrag(context, originalLocation) + this.getGraph(context)?.clearBends(this.edge) + this.getGraph(context)?.addBends(this.edge, this.originalBendLocations) + } + + /** + * Restore the standard, unmodified edge style. + * @param {!IInputModeContext} context + * @param {!Point} originalLocation + * @param {!Point} newLocation + */ + dragFinished(context, originalLocation, newLocation) { + super.dragFinished(context, originalLocation, newLocation) + this.getGraph(context)?.setStyle(this.edge, new FlowEdgeStyle()) + } +} diff --git a/demos/showcase/home-automation/FlowEdge/FlowPortRelocationHandleProvider.ts b/demos/showcase/home-automation/FlowEdge/FlowPortRelocationHandleProvider.ts new file mode 100644 index 000000000..efab5260e --- /dev/null +++ b/demos/showcase/home-automation/FlowEdge/FlowPortRelocationHandleProvider.ts @@ -0,0 +1,141 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + IEdge, + IGraph, + IHandle, + IInputModeContext, + IPort, + IPortCandidate, + Point, + PortRelocationHandle, + PortRelocationHandleProvider, + Visualization +} from 'yfiles' +import { FlowEdgeStyle } from './FlowEdgeStyle' +import { validatePortTag } from '../FlowNode/FlowNodePort' +import { getSmoothEdgeControlPoints } from './FlowEdge' + +/** + * A handle provider that provides port relocation handles that look the same as the handles + * as during new edge creation. + */ +export class FlowPortRelocationHandleProvider extends PortRelocationHandleProvider { + protected createPortRelocationHandle( + graph: IGraph | null, + edge: IEdge, + sourcePort: boolean + ): IHandle | null { + if (!graph) { + return null + } + const portRelocationHandle = new FlowPortRelocationHandle(graph, edge, sourcePort) + portRelocationHandle.showHitPortOwnerCandidatesOnly = false + portRelocationHandle.addExistingPort = false + portRelocationHandle.visualization = Visualization.DUMMY + return portRelocationHandle + } +} + +class FlowPortRelocationHandle extends PortRelocationHandle { + private originalBendLocations: Array | null = null + private fixedPort: IPort | null = null + private lastClosestPortCandidate: IPortCandidate | null = null + + /** + * Store the port candidate so that the edge can visually snap to it. + */ + protected setClosestCandidate(portCandidate: IPortCandidate | null): void { + super.setClosestCandidate(portCandidate) + this.lastClosestPortCandidate = portCandidate + } + + /** + * To perform edge curve calculations later on, we need to identify which port + * is the one that's not going to change as a result of the reconnection process. + * + * We also store the original bends of the edge, so they can be restored + * if reconnection is canceled. + */ + initializeDrag(context: IInputModeContext): void { + super.initializeDrag(context) + this.fixedPort = this.sourceEnd ? this.edge.targetPort : this.edge.sourcePort + this.originalBendLocations = this.edge.bends.map(bend => bend.location.toPoint()).toArray() + this.getGraph(context)?.setStyle(this.edge, new FlowEdgeStyle('edgeReconnection')) + if (this.dummyEdge) { + this.dummyEdge.style = new FlowEdgeStyle('edgeReconnection') + } + } + + /** + * On each position change, apply new edge bends. The visual result should be exactly the same + * as during creating a new edge. + */ + handleMove(context: IInputModeContext, originalLocation: Point, newLocation: Point): void { + super.handleMove(context, originalLocation, newLocation) + + const { fixedPort, sourceEnd } = this + const fromSide = validatePortTag(fixedPort?.tag) ? fixedPort?.tag.side : null + + const newVisualLocation = this.lastClosestPortCandidate?.port?.location ?? newLocation + const fixedPortLocation = fixedPort?.location + + if (!fromSide || !fixedPortLocation) { + return + } + + const oppositeSide = { left: 'right', right: 'left' } as const + + const bends = getSmoothEdgeControlPoints({ + start: sourceEnd ? newVisualLocation : fixedPortLocation, + end: sourceEnd ? fixedPortLocation : newVisualLocation, + fromSide: sourceEnd ? oppositeSide[fromSide] : fromSide + }) + + this.getGraph(context)?.clearBends(this.edge) + this.getGraph(context)?.addBends(this.edge, bends) + } + + /** + * Restore the original edge bends that were saved earlier. + */ + cancelDrag(context: IInputModeContext, originalLocation: Point): void { + super.cancelDrag(context, originalLocation) + this.getGraph(context)?.clearBends(this.edge) + this.getGraph(context)?.addBends(this.edge, this.originalBendLocations!) + } + + /** + * Restore the standard, unmodified edge style. + */ + dragFinished(context: IInputModeContext, originalLocation: Point, newLocation: Point): void { + super.dragFinished(context, originalLocation, newLocation) + this.getGraph(context)?.setStyle(this.edge, new FlowEdgeStyle()) + } +} diff --git a/demos/showcase/home-automation/FlowEdge/flowEdge.css b/demos/showcase/home-automation/FlowEdge/flowEdge.css new file mode 100644 index 000000000..fe2cea168 --- /dev/null +++ b/demos/showcase/home-automation/FlowEdge/flowEdge.css @@ -0,0 +1,104 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +.flow-edge { +} + +.flow-edge__main { + stroke-width: 2; + transition: + opacity 0.4s, + stroke 0.2s; + stroke: rgb(153, 153, 153); +} + +.flow-edge__outline { + stroke-width: 4; + stroke: rgb(153, 153, 153); +} + +.flow-edge__animation { + opacity: 0; + stroke-width: 2; + transition: opacity 0.4s; + stroke: rgb(255, 255, 255); + stroke-dasharray: 5; + stroke-dashoffset: 0; + animation: marquee 0.5s linear infinite; +} + +.flow-edge--reversed .flow-edge__animation { + animation: marquee-reversed 0.5s linear infinite; +} + +@keyframes marquee { + to { + stroke-dashoffset: -10; + } +} + +@keyframes marquee-reversed { + to { + stroke-dashoffset: 10; + } +} + +/* Existing edge is hovered */ + +.flow-edge--hovered .flow-edge__main { + stroke: rgb(93, 93, 93); +} + +/* Existing edge is selected OR edge is being reconnected */ + +.flow-edge--selected .flow-edge__main, +.flow-edge--reconnecting .flow-edge__main { + stroke: rgb(255, 108, 0); +} + +/* New edge is being created */ + +.flow-edge--dummy-new-edge .flow-edge__main { + stroke: rgb(255, 108, 0); +} + +.flow-edge--dummy-new-edge .flow-edge__animation { + opacity: 1; +} + +/* Connected node is selected OR edge is selected & hovered */ + +.flow-edge--selected.flow-edge--hovered .flow-edge__main, +.flow-edge--connected-node-selected .flow-edge__main { + stroke: rgb(255, 108, 0); +} + +.flow-edge--selected.flow-edge--hovered .flow-edge__animation, +.flow-edge--connected-node-selected .flow-edge__animation { + opacity: 1; +} diff --git a/demos/showcase/home-automation/FlowNode/FlowEdgeReconnectionPortCandidateProvider.js b/demos/showcase/home-automation/FlowNode/FlowEdgeReconnectionPortCandidateProvider.js new file mode 100644 index 000000000..844d0c2a2 --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/FlowEdgeReconnectionPortCandidateProvider.js @@ -0,0 +1,123 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + List, + BaseClass, + IEnumerable, + IPortCandidate, + IInputModeContext, + IEdgeReconnectionPortCandidateProvider, + IEdge, + DefaultPortCandidate, + PortCandidateValidity +} from 'yfiles' + +export class FlowEdgeReconnectionPortCandidateProvider extends BaseClass( + IEdgeReconnectionPortCandidateProvider +) { + edge + + /** + * @param {!IEdge} edge + */ + constructor(edge) { + super() + this.edge = edge + } + + /** + * @param {!IInputModeContext} undefined + * @returns {!IEnumerable.} + */ + getSourcePortCandidates({ graph }) { + const { edge } = this + const candidates = new List() + if (!graph) { + return candidates + } + + graph.ports + // Exclude right-side port on the edge's source node: + .filter((port) => port.owner !== edge.targetNode) + // Exclude any right-side ports + .filter((port) => port.tag.side !== 'left') + // Exclude source ports that the edge's target node already connects to: + .filter( + (port) => + !graph.edges.some( + (otherEdge) => + otherEdge !== edge && + otherEdge.targetPort === edge.targetPort && + otherEdge.sourcePort === port + ) + ) + .forEach((port) => { + const portCandidate = new DefaultPortCandidate(port) + portCandidate.validity = PortCandidateValidity.VALID + candidates.add(portCandidate) + }) + + return candidates + } + + /** + * @param {!IInputModeContext} undefined + * @returns {!IEnumerable.} + */ + getTargetPortCandidates({ graph }) { + const { edge } = this + const candidates = new List() + if (!graph) { + return candidates + } + + graph.ports + // Exclude left-side port on the edge's source node: + .filter((port) => port.owner !== edge.sourceNode) + // Exclude any right-side ports + .filter((port) => port.tag.side !== 'right') + // Exclude target ports that the edge's source node already connects to: + .filter( + (port) => + !graph.edges.some( + (otherEdge) => + otherEdge !== edge && + otherEdge.sourcePort === edge.sourcePort && + otherEdge.targetPort === port + ) + ) + .forEach((port) => { + const portCandidate = new DefaultPortCandidate(port) + portCandidate.validity = PortCandidateValidity.VALID + candidates.add(portCandidate) + }) + + return candidates + } +} diff --git a/demos/showcase/home-automation/FlowNode/FlowEdgeReconnectionPortCandidateProvider.ts b/demos/showcase/home-automation/FlowNode/FlowEdgeReconnectionPortCandidateProvider.ts new file mode 100644 index 000000000..27e44af6e --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/FlowEdgeReconnectionPortCandidateProvider.ts @@ -0,0 +1,113 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + List, + BaseClass, + IEnumerable, + IPortCandidate, + IInputModeContext, + IEdgeReconnectionPortCandidateProvider, + IEdge, + DefaultPortCandidate, + PortCandidateValidity +} from 'yfiles' + +export class FlowEdgeReconnectionPortCandidateProvider + extends BaseClass(IEdgeReconnectionPortCandidateProvider) + implements IEdgeReconnectionPortCandidateProvider +{ + private edge: IEdge + + constructor(edge: IEdge) { + super() + this.edge = edge + } + + getSourcePortCandidates({ graph }: IInputModeContext): IEnumerable { + const { edge } = this + const candidates = new List() + if (!graph) { + return candidates + } + + graph.ports + // Exclude right-side port on the edge's source node: + .filter(port => port.owner !== edge.targetNode) + // Exclude any right-side ports + .filter(port => port.tag.side !== 'left') + // Exclude source ports that the edge's target node already connects to: + .filter( + port => + !graph.edges.some( + otherEdge => + otherEdge !== edge && + otherEdge.targetPort === edge.targetPort && + otherEdge.sourcePort === port + ) + ) + .forEach(port => { + const portCandidate = new DefaultPortCandidate(port) + portCandidate.validity = PortCandidateValidity.VALID + candidates.add(portCandidate) + }) + + return candidates + } + + getTargetPortCandidates({ graph }: IInputModeContext): IEnumerable { + const { edge } = this + const candidates = new List() + if (!graph) { + return candidates + } + + graph.ports + // Exclude left-side port on the edge's source node: + .filter(port => port.owner !== edge.sourceNode) + // Exclude any right-side ports + .filter(port => port.tag.side !== 'right') + // Exclude target ports that the edge's source node already connects to: + .filter( + port => + !graph.edges.some( + otherEdge => + otherEdge !== edge && + otherEdge.sourcePort === edge.sourcePort && + otherEdge.targetPort === port + ) + ) + .forEach(port => { + const portCandidate = new DefaultPortCandidate(port) + portCandidate.validity = PortCandidateValidity.VALID + candidates.add(portCandidate) + }) + + return candidates + } +} diff --git a/demos/showcase/home-automation/FlowNode/FlowNode.js b/demos/showcase/home-automation/FlowNode/FlowNode.js new file mode 100644 index 000000000..af7aa7644 --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/FlowNode.js @@ -0,0 +1,216 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { FreeNodePortLocationModel, GraphComponent, IGraph, INode, Point, SimpleNode } from 'yfiles' +import { FlowNodePortStyle } from './FlowNodePortStyle.js' +import { FlowNodeStyle } from './FlowNodeStyle.js' +import { flowNodeProperties } from './flowNodeProperties.js' + +export const flowNodeVariants = [ + 'storageWriteFile', + 'storageReadFile', + 'parserCsv', + 'parserJson', + 'parserXml', + 'sequenceSort', + 'sequenceJoin', + 'networkTcpIn', + 'networkTcpOut', + 'networkTcpRequest', + 'functionFunction', + 'functionDelay', + 'functionFilter', + 'commonComment', + 'commonLinkIn', + 'commonLinkOut', + 'commonLinkCall', + 'commonStatus' +] +/** + * @typedef {*} FlowNodeVariant + */ +/** + * @typedef {Object} FlowNodeValidation + * @property {Array.} invalidProperties + * @property {Array.} validationMessages + */ +/** + * @typedef {function} FlowNodeValidationFn + */ + +/** + * @typedef {Object} FlowNodeProperties + * @property {FlowNodeVariant} variant + * @property {string} label + * @property {boolean} hasLeftPort + * @property {boolean} hasRightPort + * @property {FlowNodeValidationFn} [validate] + */ + +/** + * @typedef {*} FlowNode + */ + +/** + * @typedef {Object} FlowNodeInGraphOptions + * @property {FlowNodeVariant} variant + * @property {Point} position + * @property {IGraph} graph + */ + +/** + * Properties that should never appear in the tag editor + */ +export let hiddenProperties = ['hasLeftPort', 'hasRightPort', 'validate'] +export let lockedProperties = ['variant'] + +const portStyle = new FlowNodePortStyle() + +/** + * Modifies node-related graph configuration. + * @param {!GraphComponent} undefined + */ +export function configureFlowNodes({ graph, selection }) { + graph.decorator.nodeDecorator.focusIndicatorDecorator.hideImplementation() + graph.decorator.nodeDecorator.highlightDecorator.hideImplementation() + graph.decorator.nodeDecorator.selectionDecorator.hideImplementation() + + // When a new node appears in the graph, its ports are added automatically. This is done + // in reaction to `NodeCreated` event so that nodes added from the DnD palette, which doesn't + // handle nodes with ports correctly, end up having their ports properly configured as soon as + // they're dropped onto the main graph. + graph.addNodeCreatedListener((_sender, event) => { + const node = event.item + if (!isFlowNode(node)) { + return + } + const { hasLeftPort, hasRightPort } = node.tag + if (node.ports.size === 0) { + hasLeftPort && + graph.addPort({ + owner: node, + style: portStyle, + locationParameter: FreeNodePortLocationModel.NODE_LEFT_ANCHORED, + tag: { + side: 'left' + } + }) + hasRightPort && + graph.addPort({ + owner: node, + style: portStyle, + locationParameter: FreeNodePortLocationModel.NODE_RIGHT_ANCHORED, + tag: { + side: 'right' + } + }) + } + const label = node.tag.label + const duplicateLabelNodes = graph.nodes.filter((node) => node.tag.label.startsWith(label)) + if (!!label && duplicateLabelNodes.size > 1) { + const lastLabelNumber = duplicateLabelNodes + .toArray() + .map((node) => node.tag.label.split('#')[1] || '0') + .map((value) => Number.parseInt(value)) + .sort((a, b) => b - a)[0] + node.tag = { ...node.tag, label: label + ` #${lastLabelNumber + 1}` } + } + + selection.clear() + selection.setSelected(node, true) + }) +} + +/** + * Creates a FlowNode and adds it to the graph at the specified position. + * Ports will be added automatically on node creation. + * @param {!FlowNodeInGraphOptions} undefined + * @returns {!FlowNode} + */ +export function createInGraph({ variant, position, graph }) { + const properties = { ...flowNodeProperties[variant] } + return graph.createNode({ + style: new FlowNodeStyle(), + layout: { + width: FlowNodeStyle.defaultWidth, + height: FlowNodeStyle.defaultHeight, + x: position.x, + y: position.y + }, + tag: properties + }) +} + +/** + * Creates a graph-less FlowNode without ports (but dummy port visuals will still be rendered + * as part of the node visual). + * @param {!FlowNodeVariant} variant + * @returns {!FlowNode} + */ +export function createFlowNode(variant) { + const properties = { ...flowNodeProperties[variant] } + return new SimpleNode({ + style: new FlowNodeStyle(), + layout: { + width: FlowNodeStyle.defaultWidth, + height: FlowNodeStyle.defaultHeight, + x: 0, + y: 0 + }, + tag: properties + }) +} + +/** + * @param {!unknown} node + * @returns {!FlowNode} + */ +export function assertIsFlowNode(node) { + if (!isFlowNode(node)) { + throw new Error('Node not satisfy type FlowNode') + } +} + +/** + * @param {!unknown} node + * @returns {!FlowNode} + */ +export function isFlowNode(node) { + if (!(node instanceof INode)) { + return false + } + return validateNodeTag(node.tag) +} + +/** + * @param {!unknown} tag + * @returns {!FlowNodeProperties} + */ +export function validateNodeTag(tag) { + return typeof tag === 'object' && tag !== null && flowNodeVariants.includes(tag.variant) +} diff --git a/demos/showcase/home-automation/FlowNode/FlowNode.ts b/demos/showcase/home-automation/FlowNode/FlowNode.ts new file mode 100644 index 000000000..3e1065483 --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/FlowNode.ts @@ -0,0 +1,200 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { FreeNodePortLocationModel, GraphComponent, IGraph, INode, Point, SimpleNode } from 'yfiles' +import { type FlowNodePortProperties } from './FlowNodePort' +import { FlowNodePortStyle } from './FlowNodePortStyle' +import { FlowNodeStyle } from './FlowNodeStyle' +import { flowNodeProperties } from './flowNodeProperties' + +export const flowNodeVariants = [ + 'storageWriteFile', + 'storageReadFile', + 'parserCsv', + 'parserJson', + 'parserXml', + 'sequenceSort', + 'sequenceJoin', + 'networkTcpIn', + 'networkTcpOut', + 'networkTcpRequest', + 'functionFunction', + 'functionDelay', + 'functionFilter', + 'commonComment', + 'commonLinkIn', + 'commonLinkOut', + 'commonLinkCall', + 'commonStatus' +] +export type FlowNodeVariant = (typeof flowNodeVariants)[number] +export type FlowNodeValidation = { + invalidProperties: Array + validationMessages: Array +} +export type FlowNodeValidationFn = (args: FlowNodeProperties) => FlowNodeValidation + +export type FlowNodeProperties = { + readonly variant: FlowNodeVariant + label: string + hasLeftPort: boolean + hasRightPort: boolean + validate?: FlowNodeValidationFn + [key: string]: string | number | boolean | undefined | Array | FlowNodeValidationFn +} + +export type FlowNode = Omit & { tag: FlowNodeProperties } + +type FlowNodeInGraphOptions = { + variant: FlowNodeVariant + position: Point + graph: IGraph +} + +/** + * Properties that should never appear in the tag editor + */ +export let hiddenProperties: Array = [ + 'hasLeftPort', + 'hasRightPort', + 'validate' +] +export let lockedProperties: Array = ['variant'] + +const portStyle = new FlowNodePortStyle() + +/** + * Modifies node-related graph configuration. + */ +export function configureFlowNodes({ graph, selection }: GraphComponent): void { + graph.decorator.nodeDecorator.focusIndicatorDecorator.hideImplementation() + graph.decorator.nodeDecorator.highlightDecorator.hideImplementation() + graph.decorator.nodeDecorator.selectionDecorator.hideImplementation() + + // When a new node appears in the graph, its ports are added automatically. This is done + // in reaction to `NodeCreated` event so that nodes added from the DnD palette, which doesn't + // handle nodes with ports correctly, end up having their ports properly configured as soon as + // they're dropped onto the main graph. + graph.addNodeCreatedListener((_sender, event) => { + const node = event.item + if (!isFlowNode(node)) { + return + } + const { hasLeftPort, hasRightPort } = node.tag + if (node.ports.size === 0) { + hasLeftPort && + graph.addPort({ + owner: node, + style: portStyle, + locationParameter: FreeNodePortLocationModel.NODE_LEFT_ANCHORED, + tag: { + side: 'left' + } as FlowNodePortProperties + }) + hasRightPort && + graph.addPort({ + owner: node, + style: portStyle, + locationParameter: FreeNodePortLocationModel.NODE_RIGHT_ANCHORED, + tag: { + side: 'right' + } as FlowNodePortProperties + }) + } + const label = node.tag.label + const duplicateLabelNodes = graph.nodes.filter(node => node.tag.label.startsWith(label)) + if (!!label && duplicateLabelNodes.size > 1) { + const lastLabelNumber = duplicateLabelNodes + .toArray() + .map(node => node.tag.label.split('#')[1] || '0') + .map(value => Number.parseInt(value)) + .sort((a, b) => b - a)[0] + node.tag = { ...node.tag, label: label + ` #${lastLabelNumber + 1}` } + } + + selection.clear() + selection.setSelected(node, true) + }) +} + +/** + * Creates a FlowNode and adds it to the graph at the specified position. + * Ports will be added automatically on node creation. + */ +export function createInGraph({ variant, position, graph }: FlowNodeInGraphOptions): FlowNode { + const properties = { ...flowNodeProperties[variant] } + return graph.createNode({ + style: new FlowNodeStyle(), + layout: { + width: FlowNodeStyle.defaultWidth, + height: FlowNodeStyle.defaultHeight, + x: position.x, + y: position.y + }, + tag: properties + }) +} + +/** + * Creates a graph-less FlowNode without ports (but dummy port visuals will still be rendered + * as part of the node visual). + */ +export function createFlowNode(variant: FlowNodeVariant): FlowNode { + const properties = { ...flowNodeProperties[variant] } + return new SimpleNode({ + style: new FlowNodeStyle(), + layout: { + width: FlowNodeStyle.defaultWidth, + height: FlowNodeStyle.defaultHeight, + x: 0, + y: 0 + }, + tag: properties + }) +} + +export function assertIsFlowNode(node: unknown): asserts node is FlowNode { + if (!isFlowNode(node)) { + throw new Error('Node not satisfy type FlowNode') + } +} + +export function isFlowNode(node: unknown): node is FlowNode { + if (!(node instanceof INode)) { + return false + } + return validateNodeTag(node.tag) +} + +export function validateNodeTag(tag: unknown): tag is FlowNodeProperties { + return ( + typeof tag === 'object' && + tag !== null && + flowNodeVariants.includes((tag).variant) + ) +} diff --git a/demos/showcase/home-automation/FlowNode/FlowNodePort.js b/demos/showcase/home-automation/FlowNode/FlowNodePort.js new file mode 100644 index 000000000..a0444741c --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/FlowNodePort.js @@ -0,0 +1,74 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, PortLayerPolicy } from 'yfiles' +import { FlowNodePortCandidateProvider } from './FlowNodePortCandidateProvider.js' +import { FlowEdgeReconnectionPortCandidateProvider } from './FlowEdgeReconnectionPortCandidateProvider.js' + +/** + * @typedef {Object} FlowNodePortProperties + * @property {('left'|'right')} side + */ + +/** + * Modifies port-related graph configuration. + * @param {!GraphComponent} gc + */ +export function configureFlowNodePorts(gc) { + gc.graphModelManager.portLayerPolicy = PortLayerPolicy.AT_OWNER + gc.graph.nodeDefaults.ports.autoCleanUp = false + gc.graph.decorator.nodeDecorator.portCandidateProviderDecorator.setFactory( + (node) => new FlowNodePortCandidateProvider(node) + ) + gc.graph.decorator.edgeDecorator.edgeReconnectionPortCandidateProviderDecorator.setFactory( + (edge) => new FlowEdgeReconnectionPortCandidateProvider(edge) + ) +} + +/** + * @param {!unknown} tag + * @returns {!FlowNodePortProperties} + */ +export function assertPortTag(tag) { + if (validatePortTag(tag)) { + return + } else { + throw new Error('Tag value does not satisfy type FlowNodePortProperties') + } +} + +/** + * @param {!unknown} tag + * @returns {!FlowNodePortProperties} + */ +export function validatePortTag(tag) { + return ( + (typeof tag === 'object' && tag !== null && 'side' in tag && tag.side === 'left') || + tag.side === 'right' + ) +} diff --git a/demos/showcase/home-automation/FlowNode/FlowNodePort.ts b/demos/showcase/home-automation/FlowNode/FlowNodePort.ts new file mode 100644 index 000000000..789e9d644 --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/FlowNodePort.ts @@ -0,0 +1,67 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, PortLayerPolicy } from 'yfiles' +import { FlowNodePortCandidateProvider } from './FlowNodePortCandidateProvider' +import { FlowEdgeReconnectionPortCandidateProvider } from './FlowEdgeReconnectionPortCandidateProvider' + +export type FlowNodePortProperties = { + side: 'left' | 'right' +} + +/** + * Modifies port-related graph configuration. + */ +export function configureFlowNodePorts(gc: GraphComponent): void { + gc.graphModelManager.portLayerPolicy = PortLayerPolicy.AT_OWNER + gc.graph.nodeDefaults.ports.autoCleanUp = false + gc.graph.decorator.nodeDecorator.portCandidateProviderDecorator.setFactory( + node => new FlowNodePortCandidateProvider(node) + ) + gc.graph.decorator.edgeDecorator.edgeReconnectionPortCandidateProviderDecorator.setFactory( + edge => new FlowEdgeReconnectionPortCandidateProvider(edge) + ) +} + +export function assertPortTag(tag: unknown): asserts tag is FlowNodePortProperties { + if (validatePortTag(tag)) { + return + } else { + throw new Error('Tag value does not satisfy type FlowNodePortProperties') + } +} + +export function validatePortTag(tag: unknown): tag is FlowNodePortProperties { + return ( + (typeof tag === 'object' && + tag !== null && + 'side' in tag && + (tag as FlowNodePortProperties).side === 'left') || + (tag as FlowNodePortProperties).side === 'right' + ) +} diff --git a/demos/showcase/home-automation/FlowNode/FlowNodePortCandidateProvider.js b/demos/showcase/home-automation/FlowNode/FlowNodePortCandidateProvider.js new file mode 100644 index 000000000..9879274e4 --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/FlowNodePortCandidateProvider.js @@ -0,0 +1,105 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + DefaultPortCandidate, + IEnumerable, + IInputModeContext, + INode, + IPortCandidate, + List, + PortCandidateProviderBase, + PortCandidateValidity +} from 'yfiles' +import { assertPortTag } from './FlowNodePort.js' + +export class FlowNodePortCandidateProvider extends PortCandidateProviderBase { + /** + * @param {!INode} owner + */ + constructor(owner) { + super() + this.owner = owner + } + + /** + * @param {!IInputModeContext} _context + * @returns {!IEnumerable.} + */ + getPortCandidates(_context) { + const candidates = new List() + this.addExistingPorts(this.owner, candidates) + return candidates + } + + /** + * A valid target port candidate must: + * - have a different side than the source port; + * - be on a different node than the source port; + * - not already be connected to the source port (regardless of the direction). + * @param {!IInputModeContext} context + * @param {!IPortCandidate} source + * @returns {!IEnumerable.} + */ + getTargetPortCandidates(context, source) { + const graph = context.graph + const candidates = new List() + if (!graph || !source.port) { + return candidates + } + assertPortTag(source.port.tag) + + const sourceSide = source.port.tag.side + + graph.ports + // Exclude same-sided ports: + .filter((port) => port.tag.side !== sourceSide) + // Exclude ports on the source node: + .filter((port) => port.owner !== source.owner) + // Exclude ports that the source port already connects to: + .filter( + (port) => + !graph.edges.some((edge) => { + // Compare points by string representations instead of comparing just simple port instances. + // This helps to avoid scenario where node is recreated with undo and port instances don't match on the recreated edge + const edgePortPoints = [edge.sourcePort?.toString(), edge.targetPort?.toString()] + return ( + edgePortPoints.includes(port.toString()) && + edgePortPoints.includes(source.port?.toString()) + ) + }) + ) + .forEach((port) => { + const portCandidate = new DefaultPortCandidate(port) + portCandidate.validity = PortCandidateValidity.VALID + candidates.add(portCandidate) + }) + + return candidates + } +} diff --git a/demos/showcase/home-automation/FlowNode/FlowNodePortCandidateProvider.ts b/demos/showcase/home-automation/FlowNode/FlowNodePortCandidateProvider.ts new file mode 100644 index 000000000..9acab2dfe --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/FlowNodePortCandidateProvider.ts @@ -0,0 +1,97 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + DefaultPortCandidate, + IEnumerable, + IInputModeContext, + INode, + IPortCandidate, + List, + PortCandidateProviderBase, + PortCandidateValidity +} from 'yfiles' +import { assertPortTag } from './FlowNodePort' + +export class FlowNodePortCandidateProvider extends PortCandidateProviderBase { + constructor(private owner: INode) { + super() + } + + protected getPortCandidates(_context: IInputModeContext): IEnumerable { + const candidates = new List() + this.addExistingPorts(this.owner, candidates) + return candidates + } + + /** + * A valid target port candidate must: + * - have a different side than the source port; + * - be on a different node than the source port; + * - not already be connected to the source port (regardless of the direction). + */ + getTargetPortCandidates( + context: IInputModeContext, + source: IPortCandidate + ): IEnumerable { + const graph = context.graph + const candidates = new List() + if (!graph || !source.port) { + return candidates + } + assertPortTag(source.port.tag) + + const sourceSide = source.port.tag.side + + graph.ports + // Exclude same-sided ports: + .filter(port => port.tag.side !== sourceSide) + // Exclude ports on the source node: + .filter(port => port.owner !== source.owner) + // Exclude ports that the source port already connects to: + .filter( + port => + !graph.edges.some(edge => { + // Compare points by string representations instead of comparing just simple port instances. + // This helps to avoid scenario where node is recreated with undo and port instances don't match on the recreated edge + const edgePortPoints = [edge.sourcePort?.toString(), edge.targetPort?.toString()] + return ( + edgePortPoints.includes(port.toString()) && + edgePortPoints.includes(source.port?.toString()) + ) + }) + ) + .forEach(port => { + const portCandidate = new DefaultPortCandidate(port) + portCandidate.validity = PortCandidateValidity.VALID + candidates.add(portCandidate) + }) + + return candidates + } +} diff --git a/demos/showcase/home-automation/FlowNode/FlowNodePortStyle.js b/demos/showcase/home-automation/FlowNode/FlowNodePortStyle.js new file mode 100644 index 000000000..789292236 --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/FlowNodePortStyle.js @@ -0,0 +1,144 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { ICanvasContext, IPort, Point, PortStyleBase, Rect } from 'yfiles' + +/** + * @typedef {Object} DummyPortOptions + * @property {Rect} nodeBounds + * @property {*} side + * @property {boolean} [isConnected] + */ + +export class FlowNodePortStyle extends PortStyleBase { + /** @type {number} */ + static get size() { + if (typeof FlowNodePortStyle.$size === 'undefined') { + FlowNodePortStyle.$size = 12 + } + + return FlowNodePortStyle.$size + } + + /** @type {number} */ + static get nodeMargin() { + if (typeof FlowNodePortStyle.$nodeMargin === 'undefined') { + FlowNodePortStyle.$nodeMargin = 3 + } + + return FlowNodePortStyle.$nodeMargin + } + + /** @type {undefined} */ + static get nodeReservedWidthForPort() { + if (typeof FlowNodePortStyle.$nodeReservedWidthForPort === 'undefined') { + FlowNodePortStyle.$nodeReservedWidthForPort = + FlowNodePortStyle.size / 2 + FlowNodePortStyle.nodeMargin + } + + return FlowNodePortStyle.$nodeReservedWidthForPort + } + + /** + * Creates a visual for the dummy "port" that will be rendered as part of node visual + * to bypass DnD limitations. (We can't render actual ports when preparing nodes + * for the DnD palette, as dragging such a node to the main graph results in an + * uncatchable exception). + * @param {!DummyPortOptions} undefined + * @returns {!SVGGElement} + */ + static createDummyPortElement({ nodeBounds, side, isConnected = false }) { + const { size } = FlowNodePortStyle + const outerRadius = size / 2 + const innerRadius = size / 2 / 2 + 0.5 + const color = isConnected ? 'rgb(0, 0, 0)' : 'rgb(153, 153, 153)' + + const location = { + left: new Point(0, nodeBounds.height / 2), + right: new Point(nodeBounds.width, nodeBounds.height / 2) + }[side] + const { x, y } = location + + const group = document.createElementNS('http://www.w3.org/2000/svg', 'g') + const outer = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse') + const inner = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse') + + outer.setAttribute('cx', String(x)) + outer.setAttribute('cy', String(y)) + outer.setAttribute('rx', String(outerRadius)) + outer.setAttribute('ry', String(outerRadius)) + outer.setAttribute('fill', 'rgb(255, 255, 255)') + outer.setAttribute('stroke', color) + outer.setAttribute('stroke-width', '1') + + inner.setAttribute('cx', String(x)) + inner.setAttribute('cy', String(y)) + inner.setAttribute('rx', String(innerRadius)) + inner.setAttribute('ry', String(innerRadius)) + inner.setAttribute('fill', color) + + group.setAttribute('style', 'cursor: crosshair') + group.appendChild(outer) + group.appendChild(inner) + + return group + } + + /** + * @param {*} undefined + */ + static updateDummyPortElement({ element, nodeBounds, side }) { + const location = { + left: new Point(0, nodeBounds.height / 2), + right: new Point(nodeBounds.width, nodeBounds.height / 2) + }[side] + const { x, y } = location + element.querySelectorAll('ellipse').forEach((e) => { + e.setAttribute('cx', String(x)) + e.setAttribute('cy', String(y)) + }) + } + + /** + * The actual visual is rendered as part of the accompanying node. + * @returns {!null} + */ + createVisual() { + return null + } + + /** + * @param {!ICanvasContext} _context + * @param {!IPort} port + * @returns {!Rect} + */ + getBounds(_context, port) { + const { size } = FlowNodePortStyle + return Rect.fromCenter(port.location, [size, size]) + } +} diff --git a/demos/showcase/home-automation/FlowNode/FlowNodePortStyle.ts b/demos/showcase/home-automation/FlowNode/FlowNodePortStyle.ts new file mode 100644 index 000000000..67fb5d815 --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/FlowNodePortStyle.ts @@ -0,0 +1,118 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { ICanvasContext, IPort, Point, PortStyleBase, Rect } from 'yfiles' +import type { FlowNodePortProperties } from './FlowNodePort' + +type DummyPortOptions = { + nodeBounds: Rect + side: FlowNodePortProperties['side'] + isConnected?: boolean +} + +export class FlowNodePortStyle extends PortStyleBase { + static readonly size = 12 + static readonly nodeMargin = 3 + static readonly nodeReservedWidthForPort = + FlowNodePortStyle.size / 2 + FlowNodePortStyle.nodeMargin + + /** + * Creates a visual for the dummy "port" that will be rendered as part of node visual + * to bypass DnD limitations. (We can't render actual ports when preparing nodes + * for the DnD palette, as dragging such a node to the main graph results in an + * uncatchable exception). + */ + static createDummyPortElement({ + nodeBounds, + side, + isConnected = false + }: DummyPortOptions): SVGGElement { + const { size } = FlowNodePortStyle + const outerRadius = size / 2 + const innerRadius = size / 2 / 2 + 0.5 + const color = isConnected ? 'rgb(0, 0, 0)' : 'rgb(153, 153, 153)' + + const location = { + left: new Point(0, nodeBounds.height / 2), + right: new Point(nodeBounds.width, nodeBounds.height / 2) + }[side] + const { x, y } = location + + const group = document.createElementNS('http://www.w3.org/2000/svg', 'g') + const outer = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse') + const inner = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse') + + outer.setAttribute('cx', String(x)) + outer.setAttribute('cy', String(y)) + outer.setAttribute('rx', String(outerRadius)) + outer.setAttribute('ry', String(outerRadius)) + outer.setAttribute('fill', 'rgb(255, 255, 255)') + outer.setAttribute('stroke', color) + outer.setAttribute('stroke-width', '1') + + inner.setAttribute('cx', String(x)) + inner.setAttribute('cy', String(y)) + inner.setAttribute('rx', String(innerRadius)) + inner.setAttribute('ry', String(innerRadius)) + inner.setAttribute('fill', color) + + group.setAttribute('style', 'cursor: crosshair') + group.appendChild(outer) + group.appendChild(inner) + + return group + } + + static updateDummyPortElement({ + element, + nodeBounds, + side + }: { element: SVGGElement } & DummyPortOptions): void { + const location = { + left: new Point(0, nodeBounds.height / 2), + right: new Point(nodeBounds.width, nodeBounds.height / 2) + }[side] + const { x, y } = location + element.querySelectorAll('ellipse').forEach((e) => { + e.setAttribute('cx', String(x)) + e.setAttribute('cy', String(y)) + }) + } + + /** + * The actual visual is rendered as part of the accompanying node. + */ + protected createVisual(): null { + return null + } + + protected getBounds(_context: ICanvasContext, port: IPort): Rect { + const { size } = FlowNodePortStyle + return Rect.fromCenter(port.location, [size, size]) + } +} diff --git a/demos/showcase/home-automation/FlowNode/FlowNodeStyle.js b/demos/showcase/home-automation/FlowNode/FlowNodeStyle.js new file mode 100644 index 000000000..bf07d77e9 --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/FlowNodeStyle.js @@ -0,0 +1,617 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + Font, + GraphComponent, + ICanvasContext, + INode, + IRenderContext, + NodeStyleBase, + Point, + Rect, + Size, + SvgVisual, + TextRenderSupport, + TextWrapping +} from 'yfiles' +import { FlowNodePortStyle } from './FlowNodePortStyle.js' +import { getNodeIconSvg } from './icons.js' +import { assertIsFlowNode } from './FlowNode.js' + +/** + * @typedef {TaggedSvgVisual.} SvgVisualWithCache + */ + +/** + * @typedef {Object} SvgComponents + * @property {SVGGElement} wrapper + * @property {SVGRectElement} mainShape + * @property {SVGRectElement} iconBox + * @property {SVGRectElement} border + * @property {SVGTextElement} label + * @property {SVGGElement} invalidMark + * @property {SVGGElement} leftDummyPort + * @property {SVGGElement} rightDummyPort + */ + +export class FlowNodeStyle extends NodeStyleBase { + /** @type {number} */ + static get defaultWidth() { + if (typeof FlowNodeStyle.$defaultWidth === 'undefined') { + FlowNodeStyle.$defaultWidth = 150 + } + + return FlowNodeStyle.$defaultWidth + } + + /** @type {undefined} */ + static get defaultWidthWithPorts() { + if (typeof FlowNodeStyle.$defaultWidthWithPorts === 'undefined') { + FlowNodeStyle.$defaultWidthWithPorts = 150 + FlowNodePortStyle.size + } + + return FlowNodeStyle.$defaultWidthWithPorts + } + + /** @type {number} */ + static get defaultHeight() { + if (typeof FlowNodeStyle.$defaultHeight === 'undefined') { + FlowNodeStyle.$defaultHeight = 32 + } + + return FlowNodeStyle.$defaultHeight + } + + /** @type {number} */ + static get minWidth() { + if (typeof FlowNodeStyle.$minWidth === 'undefined') { + FlowNodeStyle.$minWidth = 150 + } + + return FlowNodeStyle.$minWidth + } + + /** @type {number} */ + static get maxWidth() { + if (typeof FlowNodeStyle.$maxWidth === 'undefined') { + FlowNodeStyle.$maxWidth = 300 + } + + return FlowNodeStyle.$maxWidth + } + + /** @type {number} */ + static get labelFontSize() { + if (typeof FlowNodeStyle.$labelFontSize === 'undefined') { + FlowNodeStyle.$labelFontSize = 14 + } + + return FlowNodeStyle.$labelFontSize + } + + /** @type {number} */ + static get labelHorizontalMargin() { + if (typeof FlowNodeStyle.$labelHorizontalMargin === 'undefined') { + FlowNodeStyle.$labelHorizontalMargin = 8 + } + + return FlowNodeStyle.$labelHorizontalMargin + } + + /** @type {number} */ + static get radius() { + if (typeof FlowNodeStyle.$radius === 'undefined') { + FlowNodeStyle.$radius = 8 + } + + return FlowNodeStyle.$radius + } + + /** @type {number} */ + static get iconSize() { + if (typeof FlowNodeStyle.$iconSize === 'undefined') { + FlowNodeStyle.$iconSize = 14 + } + + return FlowNodeStyle.$iconSize + } + + /** @type {number} */ + static get iconContainerWidth() { + if (typeof FlowNodeStyle.$iconContainerWidth === 'undefined') { + FlowNodeStyle.$iconContainerWidth = 20 + } + + return FlowNodeStyle.$iconContainerWidth + } + + /** @type {number} */ + static get iconContainerHeight() { + if (typeof FlowNodeStyle.$iconContainerHeight === 'undefined') { + FlowNodeStyle.$iconContainerHeight = 20 + } + + return FlowNodeStyle.$iconContainerHeight + } + + /** @type {number} */ + static get iconContainerRadius() { + if (typeof FlowNodeStyle.$iconContainerRadius === 'undefined') { + FlowNodeStyle.$iconContainerRadius = 4 + } + + return FlowNodeStyle.$iconContainerRadius + } + + /** @type {'M11.8649 9.16693C12.2495 9.83348 11.7668 10.6667 10.9988 10.6667H1.00113C0.23161 10.6667 -0.248848 9.83218 0.134943 9.16693L5.13382 0.499687C5.51855 -0.167167 6.48215 -0.165958 6.86619 0.499687L11.8649 9.16693ZM6 7.375C5.47073 7.375 5.04167 7.80406 5.04167 8.33333C5.04167 8.8626 5.47073 9.29166 6 9.29166C6.52927 9.29166 6.95834 8.8626 6.95834 8.33333C6.95834 7.80406 6.52927 7.375 6 7.375ZM5.09015 3.93029L5.24469 6.76362C5.25192 6.89621 5.36155 7 5.49432 7H6.50569C6.63846 7 6.74809 6.89621 6.75532 6.76362L6.90986 3.93029C6.91767 3.78708 6.80365 3.66667 6.66023 3.66667H5.33975C5.19634 3.66667 5.08234 3.78708 5.09015 3.93029Z'} */ + static get errorIndicatorPath() { + if (typeof FlowNodeStyle.$errorIndicatorPath === 'undefined') { + FlowNodeStyle.$errorIndicatorPath = + 'M11.8649 9.16693C12.2495 9.83348 11.7668 10.6667 10.9988 10.6667H1.00113C0.23161 10.6667 -0.248848 9.83218 0.134943 9.16693L5.13382 0.499687C5.51855 -0.167167 6.48215 -0.165958 6.86619 0.499687L11.8649 9.16693ZM6 7.375C5.47073 7.375 5.04167 7.80406 5.04167 8.33333C5.04167 8.8626 5.47073 9.29166 6 9.29166C6.52927 9.29166 6.95834 8.8626 6.95834 8.33333C6.95834 7.80406 6.52927 7.375 6 7.375ZM5.09015 3.93029L5.24469 6.76362C5.25192 6.89621 5.36155 7 5.49432 7H6.50569C6.63846 7 6.74809 6.89621 6.75532 6.76362L6.90986 3.93029C6.91767 3.78708 6.80365 3.66667 6.66023 3.66667H5.33975C5.19634 3.66667 5.08234 3.78708 5.09015 3.93029Z' + } + + return FlowNodeStyle.$errorIndicatorPath + } + + /** @type {number} */ + static get errorIndicatorWidth() { + if (typeof FlowNodeStyle.$errorIndicatorWidth === 'undefined') { + FlowNodeStyle.$errorIndicatorWidth = 12 + } + + return FlowNodeStyle.$errorIndicatorWidth + } + + /** @type {number} */ + static get errorIndicatorHeight() { + if (typeof FlowNodeStyle.$errorIndicatorHeight === 'undefined') { + FlowNodeStyle.$errorIndicatorHeight = 12 + } + + return FlowNodeStyle.$errorIndicatorHeight + } + + /** @type {Record.<,string>} */ + static get color() { + if (typeof FlowNodeStyle.$color === 'undefined') { + FlowNodeStyle.$color = { + storageWriteFile: '#DEB887', + storageReadFile: '#DEB887', + parserCsv: '#DEBD5C', + parserJson: '#DEBD5C', + parserXml: '#DEBD5C', + sequenceSort: '#E2D96E', + sequenceJoin: '#E2D96E', + networkTcpIn: '#C0C0C0', + networkTcpOut: '#C0C0C0', + networkTcpRequest: '#C0C0C0', + functionFunction: '#F8CFA1', + functionDelay: '#E6E0F8', + functionFilter: '#E2D96E', + commonComment: '#FFFFFF', + commonLinkIn: '#DDDDDD', + commonLinkOut: '#DDDDDD', + commonLinkCall: '#DDDDDD', + commonStatus: '#94C1D0' + } + } + + return FlowNodeStyle.$color + } + + /** @type {Record.<,string>} */ + static set color(color) { + FlowNodeStyle.$color = color + } + + /** + * Resolves detailed dimensions of every specific item of the node visual. Importantly, + * the total width calculated depends on the actual width of the label text. + * @param {!object} undefined + */ + static getDimensions({ label, hasErrorIndicator = false }) { + const { + defaultHeight: height, + minWidth, + maxWidth, + labelFontSize, + labelHorizontalMargin, + radius, + iconSize, + iconContainerWidth, + iconContainerHeight, + iconContainerRadius, + errorIndicatorWidth, + errorIndicatorHeight + } = FlowNodeStyle + const { nodeReservedWidthForPort: portReservedWidth } = FlowNodePortStyle + + const iconContainerMargin = (height - iconContainerHeight) / 2 + const errorIndicatorMargin = (height - errorIndicatorHeight) / 2 + + let maxLabelWidth = maxWidth - 2 * portReservedWidth - 2 * labelHorizontalMargin + // Reserve space for left icon box + maxLabelWidth = maxLabelWidth - iconContainerWidth - iconContainerMargin + // Reserve space for right icon box (error indicator) + if (hasErrorIndicator) { + maxLabelWidth = maxLabelWidth - errorIndicatorWidth - errorIndicatorMargin + } + + const { width: labelWidth, height: labelHeight } = TextRenderSupport.measureText({ + text: label, + font: new Font('sans-serif', labelFontSize), + wrapping: TextWrapping.CHARACTER_ELLIPSIS, + maximumSize: new Size(maxLabelWidth, height) + }) + + // Layout width & height includes some extra left & right spacing to accommodate + // ports, which are outside the node shape (but in reality, we want them + // to be right on the edge of the "true" node layout). + const layoutHeight = height + let layoutWidth = Math.max( + minWidth, + 2 * portReservedWidth + + iconContainerMargin + + iconContainerWidth + + labelHorizontalMargin + + labelWidth + + labelHorizontalMargin + ) + let actualWidth = + 2 * portReservedWidth + + iconContainerMargin + + labelWidth + + iconContainerWidth + + 2 * labelHorizontalMargin + if (hasErrorIndicator) { + actualWidth = actualWidth + errorIndicatorMargin + errorIndicatorWidth + } + if (hasErrorIndicator && actualWidth > minWidth) { + layoutWidth = layoutWidth + errorIndicatorWidth + errorIndicatorMargin + } + + const visibleWidth = layoutWidth - 2 * portReservedWidth + const visibleHeight = layoutHeight + + return { + layoutWidth, + layoutHeight, + visibleWidth, + visibleHeight, + portReservedWidth, + radius, + labelFontSize, + maxLabelWidth, + labelWidth, + labelHeight, + labelHorizontalMargin, + iconSize, + iconContainerWidth, + iconContainerHeight, + iconContainerMargin, + iconContainerRadius, + errorIndicatorWidth, + errorIndicatorHeight, + errorIndicatorMargin + } + } + + /** + * @param {!IRenderContext} context + * @param {!INode} node + * @returns {boolean} + */ + static isSelected(context, node) { + const gc = context.canvasComponent instanceof GraphComponent ? context.canvasComponent : null + return gc?.selection.isSelected(node) ?? false + } + + /** + * @param {!IRenderContext} context + * @param {!INode} node + * @returns {boolean} + */ + static isHovered(context, node) { + const gc = context.canvasComponent instanceof GraphComponent ? context.canvasComponent : null + return gc?.highlightIndicatorManager.selectionModel?.includes(node) ?? false + } + + /** + * @param {!IRenderContext} context + * @param {!INode} node + * @returns {!SvgVisualWithCache} + */ + createVisual(context, node) { + assertIsFlowNode(node) + + const graph = + context.canvasComponent instanceof GraphComponent ? context.canvasComponent.graph : null + + const { variant, label, hasLeftPort, hasRightPort } = node.tag + const { x, y } = node.layout + const isHovered = FlowNodeStyle.isHovered(context, node) + const isSelected = FlowNodeStyle.isSelected(context, node) + const { + labelFontSize, + radius, + maxLabelWidth, + labelHeight, + iconSize, + iconContainerWidth, + iconContainerHeight, + iconContainerMargin, + iconContainerRadius, + layoutWidth, + layoutHeight, + visibleWidth, + visibleHeight, + portReservedWidth, + labelHorizontalMargin, + errorIndicatorWidth, + errorIndicatorMargin + } = FlowNodeStyle.getDimensions({ label }) + + const svg = { + wrapper: document.createElementNS('http://www.w3.org/2000/svg', 'g'), + mainShape: document.createElementNS('http://www.w3.org/2000/svg', 'rect'), + iconBox: document.createElementNS('http://www.w3.org/2000/svg', 'rect'), + border: document.createElementNS('http://www.w3.org/2000/svg', 'rect'), + label: document.createElementNS('http://www.w3.org/2000/svg', 'text'), + invalidMark: document.createElementNS('http://www.w3.org/2000/svg', 'g'), + leftDummyPort: null, + rightDummyPort: null + } + + svg.wrapper.classList.add('flow-node', variant) + svg.mainShape.classList.add('flow-node__main') + svg.iconBox.classList.add('flow-node__icon-box') + svg.border.classList.add('flow-node__border') + svg.label.classList.add('flow-node__label') + svg.invalidMark.classList.add('flow-node__invalid-mark') + + svg.wrapper.setAttribute('transform', `translate(${x} ${y})`) + + TextRenderSupport.addText({ + targetElement: svg.label, + text: label, + font: new Font('sans-serif', labelFontSize), + wrapping: TextWrapping.CHARACTER_ELLIPSIS, + maximumSize: new Size(maxLabelWidth, visibleHeight) + }) + svg.label.setAttribute( + 'transform', + `translate(${ + portReservedWidth + iconContainerMargin + iconContainerWidth + labelHorizontalMargin + } ${(visibleHeight - labelHeight) / 2})` + ) + svg.label.setAttribute('fill', 'rgb(85, 85, 85)') + + svg.mainShape.setAttribute('width', String(visibleWidth)) + svg.mainShape.setAttribute('x', String(portReservedWidth)) + svg.mainShape.setAttribute('height', String(visibleHeight)) + svg.mainShape.setAttribute('rx', String(radius)) + svg.mainShape.setAttribute('fill', 'rgb(255, 255, 255)') + + svg.iconBox.setAttribute('x', String(portReservedWidth + iconContainerMargin)) + svg.iconBox.setAttribute('y', String(iconContainerMargin)) + svg.iconBox.setAttribute('width', String(iconContainerWidth)) + svg.iconBox.setAttribute('height', String(iconContainerHeight)) + svg.iconBox.setAttribute('rx', String(iconContainerRadius)) + + const icon = getNodeIconSvg({ + nodeVariant: variant, + size: iconSize, + color: + FlowNodeStyle.color[variant] === '#FFFFFF' ? 'rgb(191, 191, 191)' : 'rgb(255, 255, 255)' + }) + + const iconPosition = new Point( + (layoutHeight - iconSize) / 2 + portReservedWidth, + (layoutHeight - iconSize) / 2 + ) + const iconContainer = document.createElementNS('http://www.w3.org/2000/svg', 'g') + iconContainer.setAttribute('transform', `translate(${iconPosition.x}, ${iconPosition.y})`) + iconContainer.appendChild(icon) + + svg.iconBox.setAttribute('fill', FlowNodeStyle.color[variant]) + svg.iconBox.setAttribute('stroke', 'rgba(0, 0, 0, 0.1)') + + const invalidMarkPath = document.createElementNS('http://www.w3.org/2000/svg', 'path') + invalidMarkPath.setAttribute('d', FlowNodeStyle.errorIndicatorPath) + svg.invalidMark.setAttribute( + 'transform', + `translate(${ + portReservedWidth + visibleWidth - errorIndicatorWidth - errorIndicatorMargin + } ${errorIndicatorMargin})` + ) + svg.invalidMark.setAttribute('fill', 'rgb(203,20,20)') + svg.invalidMark.style.opacity = '0' + svg.invalidMark.style.transition = 'opacity 0.2s' + svg.invalidMark.append(invalidMarkPath) + + svg.border.setAttribute('width', String(visibleWidth)) + svg.border.setAttribute('x', String(portReservedWidth)) + svg.border.setAttribute('height', String(visibleHeight)) + svg.border.setAttribute('rx', '5') + svg.border.setAttribute('fill', 'transparent') + svg.border.style.transition = 'stroke .4s, stroke-width .2s' + svg.border.setAttribute('stroke', isHovered || isSelected ? 'rgb(255, 108, 0)' : '#999999') + svg.border.setAttribute('stroke-width', isSelected ? '2' : '1') + + svg.wrapper.append( + svg.mainShape, + svg.iconBox, + iconContainer, + svg.border, + svg.label, + svg.invalidMark + ) + + // During drag & drop operation, a node is created before becoming part of the target graph. + // In that case, setting the layout won't work anyway. + if (graph?.nodes.includes(node)) { + graph.setNodeLayout(node, new Rect(x, y, layoutWidth, layoutHeight)) + } + + // Add dummy port visuals + const nodeBounds = new Rect(x, y, layoutWidth, layoutHeight) + if (hasLeftPort) { + const dummyPort = FlowNodePortStyle.createDummyPortElement({ nodeBounds, side: 'left' }) + dummyPort.classList.add('flow-node__dummy-port--left') + svg.wrapper.appendChild(dummyPort) + } + if (hasRightPort) { + const dummyPort = FlowNodePortStyle.createDummyPortElement({ nodeBounds, side: 'right' }) + dummyPort.classList.add('flow-node__dummy-port--right') + svg.wrapper.appendChild(dummyPort) + } + + return SvgVisual.from(svg.wrapper, { + label, + layoutWidth, + hasErrorIndicator: false + }) + } + + /** + * @param {!IRenderContext} context + * @param {!SvgVisualWithCache} oldVisual + * @param {!unknown} node + * @returns {!SvgVisualWithCache} + */ + updateVisual(context, oldVisual, node) { + assertIsFlowNode(node) + + const graph = + context.canvasComponent instanceof GraphComponent ? context.canvasComponent.graph : null + + const { x, y } = node.layout + const { label, validate } = node.tag + const isHovered = FlowNodeStyle.isHovered(context, node) + const isSelected = FlowNodeStyle.isSelected(context, node) + const cache = oldVisual.tag + const hasErrorIndicator = validate(node.tag).invalidProperties.length > 0 + + const { + labelFontSize, + maxLabelWidth, + labelHeight, + iconContainerWidth, + iconContainerMargin, + layoutWidth, + layoutHeight, + visibleWidth, + visibleHeight, + portReservedWidth, + labelHorizontalMargin, + errorIndicatorWidth, + errorIndicatorMargin + } = FlowNodeStyle.getDimensions({ label, hasErrorIndicator }) + + const svg = { + wrapper: oldVisual.svgElement, + mainShape: oldVisual.svgElement.querySelector('.flow-node__main'), + iconBox: oldVisual.svgElement.querySelector('.flow-node__icon-box'), + border: oldVisual.svgElement.querySelector('.flow-node__border'), + label: oldVisual.svgElement.querySelector('.flow-node__label'), + invalidMark: oldVisual.svgElement.querySelector('.flow-node__invalid-mark'), + leftDummyPort: oldVisual.svgElement.querySelector('.flow-node__dummy-port--left'), + rightDummyPort: oldVisual.svgElement.querySelector('.flow-node__dummy-port--right') + } + + if (cache.label !== label || cache.hasErrorIndicator !== hasErrorIndicator) { + cache.label = label + cache.hasErrorIndicator = hasErrorIndicator + + TextRenderSupport.addText({ + targetElement: svg.label, + text: label, + font: new Font('sans-serif', labelFontSize), + wrapping: TextWrapping.CHARACTER_ELLIPSIS, + maximumSize: new Size(maxLabelWidth, visibleHeight) + }) + + svg.label.setAttribute( + 'transform', + `translate(${ + portReservedWidth + iconContainerMargin + iconContainerWidth + labelHorizontalMargin + } ${(visibleHeight - labelHeight) / 2})` + ) + + svg.mainShape.setAttribute('width', String(visibleWidth)) + svg.border.setAttribute('width', String(visibleWidth)) + + // This may fail while drag-and-dropping, which is completely fine. + try { + graph?.setNodeLayout(node, new Rect(x, y, layoutWidth, layoutHeight)) + } catch (e) {} + } + + svg.border.setAttribute('stroke', isHovered || isSelected ? 'rgb(255, 108, 0)' : '#999999') + svg.border.setAttribute('stroke-width', isSelected ? '2' : '1') + + svg.wrapper.setAttribute('transform', `translate(${x} ${y})`) + + svg.invalidMark.style.opacity = hasErrorIndicator ? '1' : '0' + svg.invalidMark.setAttribute( + 'transform', + `translate(${ + portReservedWidth + visibleWidth - errorIndicatorWidth - errorIndicatorMargin + } ${errorIndicatorMargin})` + ) + + // Update right port position if necessary: + if (cache.layoutWidth !== layoutWidth) { + cache.layoutWidth = layoutWidth + if (svg.rightDummyPort) { + const nodeBounds = new Rect(x, y, layoutWidth, layoutHeight) + FlowNodePortStyle.updateDummyPortElement({ + element: svg.rightDummyPort, + nodeBounds, + side: 'right' + }) + } + } + + return oldVisual + } + + /** + * @param {!ICanvasContext} _context + * @param {!unknown} node + * @returns {!Rect} + */ + getBounds(_context, node) { + assertIsFlowNode(node) + const { label } = node.tag + const { x, y } = node.layout + const { layoutWidth, layoutHeight } = FlowNodeStyle.getDimensions({ label }) + return new Rect(x, y, layoutWidth, layoutHeight) + } +} diff --git a/demos/showcase/home-automation/FlowNode/FlowNodeStyle.ts b/demos/showcase/home-automation/FlowNode/FlowNodeStyle.ts new file mode 100644 index 000000000..3e5cddeb1 --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/FlowNodeStyle.ts @@ -0,0 +1,478 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + Font, + GraphComponent, + ICanvasContext, + INode, + IRenderContext, + NodeStyleBase, + Point, + Rect, + Size, + SvgVisual, + type TaggedSvgVisual, + TextRenderSupport, + TextWrapping +} from 'yfiles' +import { FlowNodePortStyle } from './FlowNodePortStyle' +import { getNodeIconSvg } from './icons' +import { assertIsFlowNode, type FlowNodeProperties } from './FlowNode' + +type SvgVisualWithCache = TaggedSvgVisual< + SVGGElement, + { + label: string + hasErrorIndicator: boolean + layoutWidth: number + } +> + +type SvgComponents = { + wrapper: SVGGElement + mainShape: SVGRectElement + iconBox: SVGRectElement + border: SVGRectElement + label: SVGTextElement + invalidMark: SVGGElement + leftDummyPort: SVGGElement | null + rightDummyPort: SVGGElement | null +} + +export class FlowNodeStyle extends NodeStyleBase { + static readonly defaultWidth = 150 + static readonly defaultWidthWithPorts = 150 + FlowNodePortStyle.size + static readonly defaultHeight = 32 + static readonly minWidth = 150 + static readonly maxWidth = 300 + + static readonly labelFontSize = 14 + static readonly labelHorizontalMargin = 8 + + static readonly radius = 8 + + static readonly iconSize = 14 + static readonly iconContainerWidth = 20 + static readonly iconContainerHeight = 20 + static readonly iconContainerRadius = 4 + + static readonly errorIndicatorPath = + 'M11.8649 9.16693C12.2495 9.83348 11.7668 10.6667 10.9988 10.6667H1.00113C0.23161 10.6667 -0.248848 9.83218 0.134943 9.16693L5.13382 0.499687C5.51855 -0.167167 6.48215 -0.165958 6.86619 0.499687L11.8649 9.16693ZM6 7.375C5.47073 7.375 5.04167 7.80406 5.04167 8.33333C5.04167 8.8626 5.47073 9.29166 6 9.29166C6.52927 9.29166 6.95834 8.8626 6.95834 8.33333C6.95834 7.80406 6.52927 7.375 6 7.375ZM5.09015 3.93029L5.24469 6.76362C5.25192 6.89621 5.36155 7 5.49432 7H6.50569C6.63846 7 6.74809 6.89621 6.75532 6.76362L6.90986 3.93029C6.91767 3.78708 6.80365 3.66667 6.66023 3.66667H5.33975C5.19634 3.66667 5.08234 3.78708 5.09015 3.93029Z' + static readonly errorIndicatorWidth = 12 + static readonly errorIndicatorHeight = 12 + + private static color: Record = { + storageWriteFile: '#DEB887', + storageReadFile: '#DEB887', + parserCsv: '#DEBD5C', + parserJson: '#DEBD5C', + parserXml: '#DEBD5C', + sequenceSort: '#E2D96E', + sequenceJoin: '#E2D96E', + networkTcpIn: '#C0C0C0', + networkTcpOut: '#C0C0C0', + networkTcpRequest: '#C0C0C0', + functionFunction: '#F8CFA1', + functionDelay: '#E6E0F8', + functionFilter: '#E2D96E', + commonComment: '#FFFFFF', + commonLinkIn: '#DDDDDD', + commonLinkOut: '#DDDDDD', + commonLinkCall: '#DDDDDD', + commonStatus: '#94C1D0' + } + + /** + * Resolves detailed dimensions of every specific item of the node visual. Importantly, + * the total width calculated depends on the actual width of the label text. + */ + private static getDimensions({ + label, + hasErrorIndicator = false + }: { + label: string + hasErrorIndicator?: boolean + }) { + const { + defaultHeight: height, + minWidth, + maxWidth, + labelFontSize, + labelHorizontalMargin, + radius, + iconSize, + iconContainerWidth, + iconContainerHeight, + iconContainerRadius, + errorIndicatorWidth, + errorIndicatorHeight + } = FlowNodeStyle + const { nodeReservedWidthForPort: portReservedWidth } = FlowNodePortStyle + + const iconContainerMargin = (height - iconContainerHeight) / 2 + const errorIndicatorMargin = (height - errorIndicatorHeight) / 2 + + let maxLabelWidth = maxWidth - 2 * portReservedWidth - 2 * labelHorizontalMargin + // Reserve space for left icon box + maxLabelWidth = maxLabelWidth - iconContainerWidth - iconContainerMargin + // Reserve space for right icon box (error indicator) + if (hasErrorIndicator) { + maxLabelWidth = maxLabelWidth - errorIndicatorWidth - errorIndicatorMargin + } + + const { width: labelWidth, height: labelHeight } = TextRenderSupport.measureText({ + text: label, + font: new Font('sans-serif', labelFontSize), + wrapping: TextWrapping.CHARACTER_ELLIPSIS, + maximumSize: new Size(maxLabelWidth, height) + }) + + // Layout width & height includes some extra left & right spacing to accommodate + // ports, which are outside the node shape (but in reality, we want them + // to be right on the edge of the "true" node layout). + const layoutHeight = height + let layoutWidth = Math.max( + minWidth, + 2 * portReservedWidth + + iconContainerMargin + + iconContainerWidth + + labelHorizontalMargin + + labelWidth + + labelHorizontalMargin + ) + let actualWidth = + 2 * portReservedWidth + + iconContainerMargin + + labelWidth + + iconContainerWidth + + 2 * labelHorizontalMargin + if (hasErrorIndicator) { + actualWidth = actualWidth + errorIndicatorMargin + errorIndicatorWidth + } + if (hasErrorIndicator && actualWidth > minWidth) { + layoutWidth = layoutWidth + errorIndicatorWidth + errorIndicatorMargin + } + + const visibleWidth = layoutWidth - 2 * portReservedWidth + const visibleHeight = layoutHeight + + return { + layoutWidth, + layoutHeight, + visibleWidth, + visibleHeight, + portReservedWidth, + radius, + labelFontSize, + maxLabelWidth, + labelWidth, + labelHeight, + labelHorizontalMargin, + iconSize, + iconContainerWidth, + iconContainerHeight, + iconContainerMargin, + iconContainerRadius, + errorIndicatorWidth, + errorIndicatorHeight, + errorIndicatorMargin + } + } + + private static isSelected(context: IRenderContext, node: INode): boolean { + const gc = context.canvasComponent instanceof GraphComponent ? context.canvasComponent : null + return gc?.selection.isSelected(node) ?? false + } + + private static isHovered(context: IRenderContext, node: INode): boolean { + const gc = context.canvasComponent instanceof GraphComponent ? context.canvasComponent : null + return gc?.highlightIndicatorManager.selectionModel?.includes(node) ?? false + } + + protected createVisual(context: IRenderContext, node: INode): SvgVisualWithCache { + assertIsFlowNode(node) + + const graph = + context.canvasComponent instanceof GraphComponent ? context.canvasComponent.graph : null + + const { variant, label, hasLeftPort, hasRightPort } = node.tag + const { x, y } = node.layout + const isHovered = FlowNodeStyle.isHovered(context, node as INode) + const isSelected = FlowNodeStyle.isSelected(context, node as INode) + const { + labelFontSize, + radius, + maxLabelWidth, + labelHeight, + iconSize, + iconContainerWidth, + iconContainerHeight, + iconContainerMargin, + iconContainerRadius, + layoutWidth, + layoutHeight, + visibleWidth, + visibleHeight, + portReservedWidth, + labelHorizontalMargin, + errorIndicatorWidth, + errorIndicatorMargin + } = FlowNodeStyle.getDimensions({ label }) + + const svg: SvgComponents = { + wrapper: document.createElementNS('http://www.w3.org/2000/svg', 'g'), + mainShape: document.createElementNS('http://www.w3.org/2000/svg', 'rect'), + iconBox: document.createElementNS('http://www.w3.org/2000/svg', 'rect'), + border: document.createElementNS('http://www.w3.org/2000/svg', 'rect'), + label: document.createElementNS('http://www.w3.org/2000/svg', 'text'), + invalidMark: document.createElementNS('http://www.w3.org/2000/svg', 'g'), + leftDummyPort: null, + rightDummyPort: null + } + + svg.wrapper.classList.add('flow-node', variant) + svg.mainShape.classList.add('flow-node__main') + svg.iconBox.classList.add('flow-node__icon-box') + svg.border.classList.add('flow-node__border') + svg.label.classList.add('flow-node__label') + svg.invalidMark.classList.add('flow-node__invalid-mark') + + svg.wrapper.setAttribute('transform', `translate(${x} ${y})`) + + TextRenderSupport.addText({ + targetElement: svg.label, + text: label, + font: new Font('sans-serif', labelFontSize), + wrapping: TextWrapping.CHARACTER_ELLIPSIS, + maximumSize: new Size(maxLabelWidth, visibleHeight) + }) + svg.label.setAttribute( + 'transform', + `translate(${ + portReservedWidth + iconContainerMargin + iconContainerWidth + labelHorizontalMargin + } ${(visibleHeight - labelHeight) / 2})` + ) + svg.label.setAttribute('fill', 'rgb(85, 85, 85)') + + svg.mainShape.setAttribute('width', String(visibleWidth)) + svg.mainShape.setAttribute('x', String(portReservedWidth)) + svg.mainShape.setAttribute('height', String(visibleHeight)) + svg.mainShape.setAttribute('rx', String(radius)) + svg.mainShape.setAttribute('fill', 'rgb(255, 255, 255)') + + svg.iconBox.setAttribute('x', String(portReservedWidth + iconContainerMargin)) + svg.iconBox.setAttribute('y', String(iconContainerMargin)) + svg.iconBox.setAttribute('width', String(iconContainerWidth)) + svg.iconBox.setAttribute('height', String(iconContainerHeight)) + svg.iconBox.setAttribute('rx', String(iconContainerRadius)) + + const icon = getNodeIconSvg({ + nodeVariant: variant, + size: iconSize, + color: + FlowNodeStyle.color[variant] === '#FFFFFF' ? 'rgb(191, 191, 191)' : 'rgb(255, 255, 255)' + }) + + const iconPosition = new Point( + (layoutHeight - iconSize) / 2 + portReservedWidth, + (layoutHeight - iconSize) / 2 + ) + const iconContainer = document.createElementNS('http://www.w3.org/2000/svg', 'g') + iconContainer.setAttribute('transform', `translate(${iconPosition.x}, ${iconPosition.y})`) + iconContainer.appendChild(icon) + + svg.iconBox.setAttribute('fill', FlowNodeStyle.color[variant]) + svg.iconBox.setAttribute('stroke', 'rgba(0, 0, 0, 0.1)') + + const invalidMarkPath = document.createElementNS('http://www.w3.org/2000/svg', 'path') + invalidMarkPath.setAttribute('d', FlowNodeStyle.errorIndicatorPath) + svg.invalidMark.setAttribute( + 'transform', + `translate(${ + portReservedWidth + visibleWidth - errorIndicatorWidth - errorIndicatorMargin + } ${errorIndicatorMargin})` + ) + svg.invalidMark.setAttribute('fill', 'rgb(203,20,20)') + svg.invalidMark.style.opacity = '0' + svg.invalidMark.style.transition = 'opacity 0.2s' + svg.invalidMark.append(invalidMarkPath) + + svg.border.setAttribute('width', String(visibleWidth)) + svg.border.setAttribute('x', String(portReservedWidth)) + svg.border.setAttribute('height', String(visibleHeight)) + svg.border.setAttribute('rx', '5') + svg.border.setAttribute('fill', 'transparent') + svg.border.style.transition = 'stroke .4s, stroke-width .2s' + svg.border.setAttribute('stroke', isHovered || isSelected ? 'rgb(255, 108, 0)' : '#999999') + svg.border.setAttribute('stroke-width', isSelected ? '2' : '1') + + svg.wrapper.append( + svg.mainShape, + svg.iconBox, + iconContainer, + svg.border, + svg.label, + svg.invalidMark + ) + + // During drag & drop operation, a node is created before becoming part of the target graph. + // In that case, setting the layout won't work anyway. + if (graph?.nodes.includes(node as INode)) { + graph.setNodeLayout(node as INode, new Rect(x, y, layoutWidth, layoutHeight)) + } + + // Add dummy port visuals + const nodeBounds = new Rect(x, y, layoutWidth, layoutHeight) + if (hasLeftPort) { + const dummyPort = FlowNodePortStyle.createDummyPortElement({ nodeBounds, side: 'left' }) + dummyPort.classList.add('flow-node__dummy-port--left') + svg.wrapper.appendChild(dummyPort) + } + if (hasRightPort) { + const dummyPort = FlowNodePortStyle.createDummyPortElement({ nodeBounds, side: 'right' }) + dummyPort.classList.add('flow-node__dummy-port--right') + svg.wrapper.appendChild(dummyPort) + } + + return SvgVisual.from(svg.wrapper, { + label, + layoutWidth, + hasErrorIndicator: false + }) + } + + protected updateVisual( + context: IRenderContext, + oldVisual: SvgVisualWithCache, + node: unknown + ): SvgVisualWithCache { + assertIsFlowNode(node) + + const graph = + context.canvasComponent instanceof GraphComponent ? context.canvasComponent.graph : null + + const { x, y } = node.layout + const { label, validate } = node.tag + const isHovered = FlowNodeStyle.isHovered(context, node as INode) + const isSelected = FlowNodeStyle.isSelected(context, node as INode) + const cache = oldVisual.tag + const hasErrorIndicator = validate!(node.tag)!.invalidProperties.length > 0 + + const { + labelFontSize, + maxLabelWidth, + labelHeight, + iconContainerWidth, + iconContainerMargin, + layoutWidth, + layoutHeight, + visibleWidth, + visibleHeight, + portReservedWidth, + labelHorizontalMargin, + errorIndicatorWidth, + errorIndicatorMargin + } = FlowNodeStyle.getDimensions({ label, hasErrorIndicator }) + + const svg: SvgComponents = { + wrapper: oldVisual.svgElement, + mainShape: oldVisual.svgElement.querySelector('.flow-node__main')!, + iconBox: oldVisual.svgElement.querySelector('.flow-node__icon-box')!, + border: oldVisual.svgElement.querySelector('.flow-node__border')!, + label: oldVisual.svgElement.querySelector('.flow-node__label')!, + invalidMark: oldVisual.svgElement.querySelector('.flow-node__invalid-mark')!, + leftDummyPort: oldVisual.svgElement.querySelector('.flow-node__dummy-port--left'), + rightDummyPort: oldVisual.svgElement.querySelector('.flow-node__dummy-port--right') + } + + if (cache.label !== label || cache.hasErrorIndicator !== hasErrorIndicator) { + cache.label = label + cache.hasErrorIndicator = hasErrorIndicator + + TextRenderSupport.addText({ + targetElement: svg.label, + text: label, + font: new Font('sans-serif', labelFontSize), + wrapping: TextWrapping.CHARACTER_ELLIPSIS, + maximumSize: new Size(maxLabelWidth, visibleHeight) + }) + + svg.label.setAttribute( + 'transform', + `translate(${ + portReservedWidth + iconContainerMargin + iconContainerWidth + labelHorizontalMargin + } ${(visibleHeight - labelHeight) / 2})` + ) + + svg.mainShape.setAttribute('width', String(visibleWidth)) + svg.border.setAttribute('width', String(visibleWidth)) + + // This may fail while drag-and-dropping, which is completely fine. + try { + graph?.setNodeLayout(node as INode, new Rect(x, y, layoutWidth, layoutHeight)) + } catch (e) {} + } + + svg.border.setAttribute('stroke', isHovered || isSelected ? 'rgb(255, 108, 0)' : '#999999') + svg.border.setAttribute('stroke-width', isSelected ? '2' : '1') + + svg.wrapper.setAttribute('transform', `translate(${x} ${y})`) + + svg.invalidMark.style.opacity = hasErrorIndicator ? '1' : '0' + svg.invalidMark.setAttribute( + 'transform', + `translate(${ + portReservedWidth + visibleWidth - errorIndicatorWidth - errorIndicatorMargin + } ${errorIndicatorMargin})` + ) + + // Update right port position if necessary: + if (cache.layoutWidth !== layoutWidth) { + cache.layoutWidth = layoutWidth + if (svg.rightDummyPort) { + const nodeBounds = new Rect(x, y, layoutWidth, layoutHeight) + FlowNodePortStyle.updateDummyPortElement({ + element: svg.rightDummyPort, + nodeBounds, + side: 'right' + }) + } + } + + return oldVisual + } + + protected getBounds(_context: ICanvasContext, node: unknown): Rect { + assertIsFlowNode(node) + const { label } = node.tag + const { x, y } = node.layout + const { layoutWidth, layoutHeight } = FlowNodeStyle.getDimensions({ label }) + return new Rect(x, y, layoutWidth, layoutHeight) + } +} diff --git a/demos/showcase/home-automation/FlowNode/FlowNodeValidators.js b/demos/showcase/home-automation/FlowNode/FlowNodeValidators.js new file mode 100644 index 000000000..73ed52708 --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/FlowNodeValidators.js @@ -0,0 +1,165 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +/** + * @param {!FlowNodeProperties} _ + * @returns {!FlowNodeValidation} + */ +export function defaultFlowNodeValidationFn(_) { + return { + invalidProperties: [], + validationMessages: [] + } +} + +/** + * @param {!FlowNodeProperties} args + * @returns {!FlowNodeValidation} + */ +export function validateStorageReadWriteNode(args) { + const invalidProperties = [] + const validationMessages = [] + if (!args.fileName) { + invalidProperties.push('fileName') + validationMessages.push('File name is not defined') + } + return { + invalidProperties, + validationMessages + } +} + +/** + * @param {!FlowNodeProperties} args + * @returns {!FlowNodeValidation} + */ +export function validateParserCsvNode(args) { + const invalidProperties = [] + const validationMessages = [] + if (!args.separator) { + invalidProperties.push('separator') + validationMessages.push('Separator is not defined') + } + if (!args.newLine) { + invalidProperties.push('newLine') + validationMessages.push('New line symbol is not defined') + } + return { + invalidProperties, + validationMessages + } +} + +/** + * @param {!FlowNodeProperties} args + * @returns {!FlowNodeValidation} + */ +export function validateParserJsonNode(args) { + const invalidProperties = [] + const validationMessages = [] + if (!args.property) { + invalidProperties.push('property') + validationMessages.push('Property value is not defined') + } + return { + invalidProperties, + validationMessages + } +} + +/** + * @param {!FlowNodeProperties} args + * @returns {!FlowNodeValidation} + */ +export function validateParserXmlNode(args) { + const invalidProperties = [] + const validationMessages = [] + if (!args.property) { + invalidProperties.push('property') + validationMessages.push('Property value is not defined') + } + return { + invalidProperties, + validationMessages + } +} + +/** + * @param {!FlowNodeProperties} args + * @returns {!FlowNodeValidation} + */ +export function validateNetworkTcpNode(args) { + const invalidProperties = [] + const validationMessages = [] + if (!args.server) { + invalidProperties.push('server') + validationMessages.push('Server is not defined') + } + return { + invalidProperties, + validationMessages + } +} + +/** + * @param {!FlowNodeProperties} args + * @returns {!FlowNodeValidation} + */ +export function validateFunctionFunctionNode(args) { + const invalidProperties = [] + const validationMessages = [] + if (!args.function) { + invalidProperties.push('function') + validationMessages.push('No function to execute') + } + return { + invalidProperties, + validationMessages + } +} + +/** + * @param {!FlowNodeProperties} args + * @returns {!FlowNodeValidation} + */ +export function validateFunctionDelayNode(args) { + const invalidProperties = [] + const validationMessages = [] + if (!args.timeout) { + invalidProperties.push('timeout') + validationMessages.push('Timeout is not defined') + } + if (!args.timeoutUnits) { + invalidProperties.push('timeoutUnits') + validationMessages.push('Timeout units are not defined') + } + return { + invalidProperties, + validationMessages + } +} diff --git a/demos/showcase/home-automation/FlowNode/FlowNodeValidators.ts b/demos/showcase/home-automation/FlowNode/FlowNodeValidators.ts new file mode 100644 index 000000000..3fb3ff089 --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/FlowNodeValidators.ts @@ -0,0 +1,135 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { type FlowNodeProperties, type FlowNodeValidation } from './FlowNode' + +export function defaultFlowNodeValidationFn(_: FlowNodeProperties): FlowNodeValidation { + return { + invalidProperties: [], + validationMessages: [] + } +} + +export function validateStorageReadWriteNode(args: FlowNodeProperties): FlowNodeValidation { + const invalidProperties = [] + const validationMessages = [] + if (!args.fileName) { + invalidProperties.push('fileName') + validationMessages.push('File name is not defined') + } + return { + invalidProperties, + validationMessages + } +} + +export function validateParserCsvNode(args: FlowNodeProperties): FlowNodeValidation { + const invalidProperties = [] + const validationMessages = [] + if (!args.separator) { + invalidProperties.push('separator') + validationMessages.push('Separator is not defined') + } + if (!args.newLine) { + invalidProperties.push('newLine') + validationMessages.push('New line symbol is not defined') + } + return { + invalidProperties, + validationMessages + } +} + +export function validateParserJsonNode(args: FlowNodeProperties): FlowNodeValidation { + const invalidProperties = [] + const validationMessages = [] + if (!args.property) { + invalidProperties.push('property') + validationMessages.push('Property value is not defined') + } + return { + invalidProperties, + validationMessages + } +} + +export function validateParserXmlNode(args: FlowNodeProperties): FlowNodeValidation { + const invalidProperties = [] + const validationMessages = [] + if (!args.property) { + invalidProperties.push('property') + validationMessages.push('Property value is not defined') + } + return { + invalidProperties, + validationMessages + } +} + +export function validateNetworkTcpNode(args: FlowNodeProperties): FlowNodeValidation { + const invalidProperties = [] + const validationMessages = [] + if (!args.server) { + invalidProperties.push('server') + validationMessages.push('Server is not defined') + } + return { + invalidProperties, + validationMessages + } +} + +export function validateFunctionFunctionNode(args: FlowNodeProperties): FlowNodeValidation { + const invalidProperties = [] + const validationMessages = [] + if (!args.function) { + invalidProperties.push('function') + validationMessages.push('No function to execute') + } + return { + invalidProperties, + validationMessages + } +} + +export function validateFunctionDelayNode(args: FlowNodeProperties): FlowNodeValidation { + const invalidProperties = [] + const validationMessages = [] + if (!args.timeout) { + invalidProperties.push('timeout') + validationMessages.push('Timeout is not defined') + } + if (!args.timeoutUnits) { + invalidProperties.push('timeoutUnits') + validationMessages.push('Timeout units are not defined') + } + return { + invalidProperties, + validationMessages + } +} diff --git a/demos/showcase/home-automation/FlowNode/flowNodeProperties.js b/demos/showcase/home-automation/FlowNode/flowNodeProperties.js new file mode 100644 index 000000000..771e77859 --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/flowNodeProperties.js @@ -0,0 +1,222 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + defaultFlowNodeValidationFn, + validateFunctionDelayNode, + validateFunctionFunctionNode, + validateNetworkTcpNode, + validateParserCsvNode, + validateParserJsonNode, + validateParserXmlNode, + validateStorageReadWriteNode +} from './FlowNodeValidators.js' + +export const flowNodeProperties = { + storageWriteFile: { + variant: 'storageWriteFile', + label: 'Write File', + hasLeftPort: true, + hasRightPort: true, + fileName: '', + validate: validateStorageReadWriteNode + }, + storageReadFile: { + variant: 'storageReadFile', + label: 'Read File', + hasLeftPort: true, + hasRightPort: true, + fileName: '', + validate: validateStorageReadWriteNode + }, + parserCsv: { + variant: 'parserCsv', + label: 'csv', + hasLeftPort: true, + hasRightPort: true, + separator: ',', + newLine: '\\n', + validate: validateParserCsvNode + }, + parserJson: { + variant: 'parserJson', + label: 'json', + hasLeftPort: true, + hasRightPort: true, + property: 'payload', + pretty: false, + validate: validateParserJsonNode + }, + parserXml: { + variant: 'parserXml', + label: 'xml', + hasLeftPort: true, + hasRightPort: true, + property: 'payload', + attribute: '', + validate: validateParserXmlNode + }, + sequenceSort: { + variant: 'sequenceSort', + label: 'sort', + hasLeftPort: true, + hasRightPort: true, + order: 'ascending', + asNumber: false, + target: 'payload', + targetType: 'msg', + msgKey: 'payload', + msgKeyType: 'elem', + seqKey: 'payload', + seqKeyType: 'msg', + validate: defaultFlowNodeValidationFn + }, + sequenceJoin: { + variant: 'sequenceJoin', + label: 'join', + hasLeftPort: true, + hasRightPort: true, + mode: 'auto', + build: 'object', + property: 'payload', + propertyType: 'msg', + key: 'topic', + joiner: '\\n', + joinerType: 'str', + accumulate: false, + timeout: '', + count: '', + reduceRight: false, + validate: defaultFlowNodeValidationFn + }, + networkTcpIn: { + variant: 'networkTcpIn', + label: 'tcp in', + hasLeftPort: false, + hasRightPort: true, + server: '', + host: '', + port: '', + dataMode: 'stream', + dataType: 'buffer', + base64: false, + validate: validateNetworkTcpNode + }, + networkTcpOut: { + variant: 'networkTcpOut', + label: 'tcp out', + hasLeftPort: true, + hasRightPort: false, + host: '', + port: '', + server: '', + base64: false, + validate: validateNetworkTcpNode + }, + networkTcpRequest: { + variant: 'networkTcpRequest', + label: 'tcp request', + hasLeftPort: true, + hasRightPort: true, + server: 'server', + port: '', + out: 'time', + ret: 'buffer', + validate: validateNetworkTcpNode + }, + functionFunction: { + variant: 'functionFunction', + label: 'function', + hasLeftPort: true, + hasRightPort: true, + function: 'return msg;', + validate: validateFunctionFunctionNode + }, + functionDelay: { + variant: 'functionDelay', + label: 'delay', + hasLeftPort: true, + hasRightPort: true, + pauseType: 'delay', + timeout: '5', + timeoutUnits: 'seconds', + validate: validateFunctionDelayNode + }, + functionFilter: { + variant: 'functionFilter', + label: 'filter', + hasLeftPort: true, + hasRightPort: true, + function: 'rbe', + gap: '', + start: '', + inout: 'out', + property: 'payload', + topic: 'topic', + validate: defaultFlowNodeValidationFn + }, + commonComment: { + variant: 'commonComment', + label: 'comment', + hasLeftPort: false, + hasRightPort: false, + comment: '', + validate: defaultFlowNodeValidationFn + }, + commonLinkIn: { + variant: 'commonLinkIn', + label: 'link in', + hasLeftPort: false, + hasRightPort: true, + validate: defaultFlowNodeValidationFn + }, + commonLinkOut: { + variant: 'commonLinkOut', + label: 'link out', + hasLeftPort: true, + hasRightPort: false, + validate: defaultFlowNodeValidationFn + }, + commonLinkCall: { + variant: 'commonLinkCall', + label: 'link call', + hasLeftPort: true, + hasRightPort: true, + linkType: 'static', + timeout: 30, + validate: defaultFlowNodeValidationFn + }, + commonStatus: { + variant: 'commonStatus', + label: 'status', + hasLeftPort: false, + hasRightPort: true, + status: undefined, + validate: defaultFlowNodeValidationFn + } +} diff --git a/demos/showcase/home-automation/FlowNode/flowNodeProperties.ts b/demos/showcase/home-automation/FlowNode/flowNodeProperties.ts new file mode 100644 index 000000000..480788101 --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/flowNodeProperties.ts @@ -0,0 +1,223 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + defaultFlowNodeValidationFn, + validateFunctionDelayNode, + validateFunctionFunctionNode, + validateNetworkTcpNode, + validateParserCsvNode, + validateParserJsonNode, + validateParserXmlNode, + validateStorageReadWriteNode +} from './FlowNodeValidators' +import type { FlowNodeProperties, FlowNodeVariant } from './FlowNode' + +export const flowNodeProperties: Record = { + storageWriteFile: { + variant: 'storageWriteFile', + label: 'Write File', + hasLeftPort: true, + hasRightPort: true, + fileName: '', + validate: validateStorageReadWriteNode + }, + storageReadFile: { + variant: 'storageReadFile', + label: 'Read File', + hasLeftPort: true, + hasRightPort: true, + fileName: '', + validate: validateStorageReadWriteNode + }, + parserCsv: { + variant: 'parserCsv', + label: 'csv', + hasLeftPort: true, + hasRightPort: true, + separator: ',', + newLine: '\\n', + validate: validateParserCsvNode + }, + parserJson: { + variant: 'parserJson', + label: 'json', + hasLeftPort: true, + hasRightPort: true, + property: 'payload', + pretty: false, + validate: validateParserJsonNode + }, + parserXml: { + variant: 'parserXml', + label: 'xml', + hasLeftPort: true, + hasRightPort: true, + property: 'payload', + attribute: '', + validate: validateParserXmlNode + }, + sequenceSort: { + variant: 'sequenceSort', + label: 'sort', + hasLeftPort: true, + hasRightPort: true, + order: 'ascending', + asNumber: false, + target: 'payload', + targetType: 'msg', + msgKey: 'payload', + msgKeyType: 'elem', + seqKey: 'payload', + seqKeyType: 'msg', + validate: defaultFlowNodeValidationFn + }, + sequenceJoin: { + variant: 'sequenceJoin', + label: 'join', + hasLeftPort: true, + hasRightPort: true, + mode: 'auto', + build: 'object', + property: 'payload', + propertyType: 'msg', + key: 'topic', + joiner: '\\n', + joinerType: 'str', + accumulate: false, + timeout: '', + count: '', + reduceRight: false, + validate: defaultFlowNodeValidationFn + }, + networkTcpIn: { + variant: 'networkTcpIn', + label: 'tcp in', + hasLeftPort: false, + hasRightPort: true, + server: '', + host: '', + port: '', + dataMode: 'stream', + dataType: 'buffer', + base64: false, + validate: validateNetworkTcpNode + }, + networkTcpOut: { + variant: 'networkTcpOut', + label: 'tcp out', + hasLeftPort: true, + hasRightPort: false, + host: '', + port: '', + server: '', + base64: false, + validate: validateNetworkTcpNode + }, + networkTcpRequest: { + variant: 'networkTcpRequest', + label: 'tcp request', + hasLeftPort: true, + hasRightPort: true, + server: 'server', + port: '', + out: 'time', + ret: 'buffer', + validate: validateNetworkTcpNode + }, + functionFunction: { + variant: 'functionFunction', + label: 'function', + hasLeftPort: true, + hasRightPort: true, + function: 'return msg;', + validate: validateFunctionFunctionNode + }, + functionDelay: { + variant: 'functionDelay', + label: 'delay', + hasLeftPort: true, + hasRightPort: true, + pauseType: 'delay', + timeout: '5', + timeoutUnits: 'seconds', + validate: validateFunctionDelayNode + }, + functionFilter: { + variant: 'functionFilter', + label: 'filter', + hasLeftPort: true, + hasRightPort: true, + function: 'rbe', + gap: '', + start: '', + inout: 'out', + property: 'payload', + topic: 'topic', + validate: defaultFlowNodeValidationFn + }, + commonComment: { + variant: 'commonComment', + label: 'comment', + hasLeftPort: false, + hasRightPort: false, + comment: '', + validate: defaultFlowNodeValidationFn + }, + commonLinkIn: { + variant: 'commonLinkIn', + label: 'link in', + hasLeftPort: false, + hasRightPort: true, + validate: defaultFlowNodeValidationFn + }, + commonLinkOut: { + variant: 'commonLinkOut', + label: 'link out', + hasLeftPort: true, + hasRightPort: false, + validate: defaultFlowNodeValidationFn + }, + commonLinkCall: { + variant: 'commonLinkCall', + label: 'link call', + hasLeftPort: true, + hasRightPort: true, + linkType: 'static', + timeout: 30, + validate: defaultFlowNodeValidationFn + }, + commonStatus: { + variant: 'commonStatus', + label: 'status', + hasLeftPort: false, + hasRightPort: true, + status: undefined, + validate: defaultFlowNodeValidationFn + } +} diff --git a/demos/showcase/home-automation/FlowNode/icons.js b/demos/showcase/home-automation/FlowNode/icons.js new file mode 100644 index 000000000..3b25f731f --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/icons.js @@ -0,0 +1,101 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +/** + * @param {!object} undefined + * @returns {!SVGElement} + */ +export function getNodeIconSvg({ nodeVariant, size, color }) { + const svgContent = nodeIcons[nodeVariant] + + const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg') + svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg') + svgWrapper.setAttribute('viewBox', `0 0 ${size} ${size}`) + svgWrapper.setAttribute('width', String(size)) + svgWrapper.setAttribute('height', String(size)) + + // Create a element to hold the translated content + const innerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') + innerSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg') + innerSvg.setAttribute('viewBox', `0 0 ${size} ${size}`) + innerSvg.setAttribute('width', String(size)) + innerSvg.setAttribute('height', String(size)) + + // Actual icon element + const parser = new DOMParser() + const svgDocument = parser.parseFromString(svgContent, 'image/svg+xml') + const svgIcon = svgDocument.documentElement + svgIcon.setAttribute('fill', color) + + svgWrapper.appendChild(svgIcon) + + return svgWrapper +} + +/** + * These are taken right from the fontawesome package (`@fortawesome/fontawesome-free/svgs/solid/`). + */ + +export const nodeIcons = { + storageWriteFile: + '', + storageReadFile: + '', + parserCsv: + '', + parserJson: + '', + parserXml: + '', + sequenceSort: + '', + sequenceJoin: + '', + networkTcpIn: + '', + networkTcpOut: + '', + networkTcpRequest: + '', + functionFunction: + '', + functionDelay: + '', + functionFilter: + '', + commonComment: + '', + commonLinkIn: + '', + commonLinkOut: + '', + commonLinkCall: + '', + commonStatus: + '' +} diff --git a/demos/showcase/home-automation/FlowNode/icons.ts b/demos/showcase/home-automation/FlowNode/icons.ts new file mode 100644 index 000000000..2ea4f4c60 --- /dev/null +++ b/demos/showcase/home-automation/FlowNode/icons.ts @@ -0,0 +1,107 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import type { FlowNodeVariant } from './FlowNode' + +export function getNodeIconSvg({ + nodeVariant, + size, + color +}: { + nodeVariant: string + size: number + color: string +}): SVGElement { + const svgContent = nodeIcons[nodeVariant] + + const svgWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg') + svgWrapper.setAttribute('xmlns', 'http://www.w3.org/2000/svg') + svgWrapper.setAttribute('viewBox', `0 0 ${size} ${size}`) + svgWrapper.setAttribute('width', String(size)) + svgWrapper.setAttribute('height', String(size)) + + // Create a element to hold the translated content + const innerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') + innerSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg') + innerSvg.setAttribute('viewBox', `0 0 ${size} ${size}`) + innerSvg.setAttribute('width', String(size)) + innerSvg.setAttribute('height', String(size)) + + // Actual icon element + const parser = new DOMParser() + const svgDocument = parser.parseFromString(svgContent, 'image/svg+xml') + const svgIcon = svgDocument.documentElement + svgIcon.setAttribute('fill', color) + + svgWrapper.appendChild(svgIcon) + + return svgWrapper +} + +/** + * These are taken right from the fontawesome package (`@fortawesome/fontawesome-free/svgs/solid/`). + */ + +export const nodeIcons: Record = { + storageWriteFile: + '', + storageReadFile: + '', + parserCsv: + '', + parserJson: + '', + parserXml: + '', + sequenceSort: + '', + sequenceJoin: + '', + networkTcpIn: + '', + networkTcpOut: + '', + networkTcpRequest: + '', + functionFunction: + '', + functionDelay: + '', + functionFilter: + '', + commonComment: + '', + commonLinkIn: + '', + commonLinkOut: + '', + commonLinkCall: + '', + commonStatus: + '' +} diff --git a/demos/showcase/home-automation/HomeAutomationDemo.js b/demos/showcase/home-automation/HomeAutomationDemo.js new file mode 100644 index 000000000..50285dc32 --- /dev/null +++ b/demos/showcase/home-automation/HomeAutomationDemo.js @@ -0,0 +1,79 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, License } from 'yfiles' +import { finishLoading } from 'demo-resources/demo-page' +import { initializeSnapping } from './layout/initializeSnapping.js' +import { initializeGrid } from './layout/initilaizeGrid.js' +import { initializeToolbar } from './UI/initializeToolbar.js' +import { initializeDragAndDropPanel } from './UI/initializeDragAndDropPanel.js' +import { configureDragAndDrop } from './utils/configureDragAndDrop.js' +import { configureInputMode } from './inputMode/configureInputMode.js' +import { initializeTagExplorer } from './UI/initializeTagExplorer.js' +import { configureGraphEvents } from './utils/configureGraphEvents.js' +import { initializeTooltips } from './UI/initializeTooltips.js' +import { initializeContextMenu } from './UI/initializeContextMenu.js' +import { fetchLicense } from 'demo-resources/fetch-license' +import { configureFlowNodes } from './FlowNode/FlowNode.js' +import { configureFlowNodePorts } from './FlowNode/FlowNodePort.js' +import { configureFlowEdges } from './FlowEdge/FlowEdge.js' +import { importGraphData, initializeJsonIo } from './ImportExportManager/ImportExportManager.js' +import { SampleData } from './resources/weather-data.js' + +/** + * @returns {!Promise} + */ +async function run() { + License.value = await fetchLicense() + + const graphComponent = new GraphComponent('graphComponent') + configureInputMode(graphComponent) + configureFlowNodePorts(graphComponent) + initializeSnapping(graphComponent) + + configureDragAndDrop(graphComponent) + initializeDragAndDropPanel() + configureGraphEvents(graphComponent) + initializeTagExplorer(graphComponent) + + initializeJsonIo(graphComponent) + initializeToolbar(graphComponent, initializeGrid(graphComponent)) + initializeTooltips(graphComponent) + initializeContextMenu(graphComponent) + + configureFlowNodes(graphComponent) + configureFlowEdges(graphComponent) + + await importGraphData(graphComponent, SampleData) + + graphComponent.fitGraphBounds() + + graphComponent.graph.undoEngineEnabled = true +} + +void run().then(finishLoading) diff --git a/demos/showcase/home-automation/HomeAutomationDemo.ts b/demos/showcase/home-automation/HomeAutomationDemo.ts new file mode 100644 index 000000000..2e93f5a89 --- /dev/null +++ b/demos/showcase/home-automation/HomeAutomationDemo.ts @@ -0,0 +1,76 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, License } from 'yfiles' +import { finishLoading } from 'demo-resources/demo-page' +import { initializeSnapping } from './layout/initializeSnapping' +import { initializeGrid } from './layout/initilaizeGrid' +import { initializeToolbar } from './UI/initializeToolbar' +import { initializeDragAndDropPanel } from './UI/initializeDragAndDropPanel' +import { configureDragAndDrop } from './utils/configureDragAndDrop' +import { configureInputMode } from './inputMode/configureInputMode' +import { initializeTagExplorer } from './UI/initializeTagExplorer' +import { configureGraphEvents } from './utils/configureGraphEvents' +import { initializeTooltips } from './UI/initializeTooltips' +import { initializeContextMenu } from './UI/initializeContextMenu' +import { fetchLicense } from 'demo-resources/fetch-license' +import { configureFlowNodes } from './FlowNode/FlowNode' +import { configureFlowNodePorts } from './FlowNode/FlowNodePort' +import { configureFlowEdges } from './FlowEdge/FlowEdge' +import { importGraphData, initializeJsonIo } from './ImportExportManager/ImportExportManager' +import { SampleData } from './resources/weather-data' + +async function run(): Promise { + License.value = await fetchLicense() + + const graphComponent = new GraphComponent('graphComponent') + configureInputMode(graphComponent) + configureFlowNodePorts(graphComponent) + initializeSnapping(graphComponent) + + configureDragAndDrop(graphComponent) + initializeDragAndDropPanel() + configureGraphEvents(graphComponent) + initializeTagExplorer(graphComponent) + + initializeJsonIo(graphComponent) + initializeToolbar(graphComponent, initializeGrid(graphComponent)) + initializeTooltips(graphComponent) + initializeContextMenu(graphComponent) + + configureFlowNodes(graphComponent) + configureFlowEdges(graphComponent) + + await importGraphData(graphComponent, SampleData) + + graphComponent.fitGraphBounds() + + graphComponent.graph.undoEngineEnabled = true +} + +void run().then(finishLoading) diff --git a/demos/showcase/home-automation/ImportExportManager/EdgeData.js b/demos/showcase/home-automation/ImportExportManager/EdgeData.js new file mode 100644 index 000000000..af6e8a526 --- /dev/null +++ b/demos/showcase/home-automation/ImportExportManager/EdgeData.js @@ -0,0 +1,144 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { IEdge, IGraph, INode, IPort, Point } from 'yfiles' + +/** + * @typedef {Object} SerializableEdgeData + * @property {Array.>} bends + * @property {number} sourceNodeIndex + * @property {number} targetNodeIndex + */ + +/** + * @typedef {SerializableEdgeData} EdgeDataOptions + */ + +/** + * A simple, minimal data structure that can be used for storing a FlowEdge in JSON + * and then re-create it in the graph. + */ +export class EdgeData { + bends + sourceNodeIndex + targetNodeIndex + + /** + * Creates EdgeData from an actual edge + * @param {!IEdge} edge + * @param {number} sourceNodeIndex + * @param {number} targetNodeIndex + * @returns {!EdgeData} + */ + static fromGraphItem(edge, sourceNodeIndex, targetNodeIndex) { + const bends = edge.bends.toArray().map((bend) => [bend.location.x, bend.location.y]) + return new EdgeData({ bends, sourceNodeIndex, targetNodeIndex }) + } + + /** + * Converts an arbitrary piece of data to EdgeData after validation. + * @param {!unknown} data + * @returns {!EdgeData} + */ + static fromJSONData(data) { + EdgeData.validate(data) + return new EdgeData(data) + } + + /** + * Checks if an arbitrary piece of data (as it comes from a JSON source) + * conforms to the format required by EdgeData. + * @param {!unknown} data + * @returns {!EdgeDataOptions} + */ + static validate(data) { + const bendsAreValid = data.bends.every( + (bend) => Array.isArray(bend) && Number.isFinite(bend[0]) && Number.isFinite(bend[1]) + ) + if ( + data !== null && + typeof data === 'object' && + bendsAreValid && + Number.isFinite(data.sourceNodeIndex) && + Number.isFinite(data.targetNodeIndex) + ) { + return + } + throw new Error('Malformed edge data') + } + + /** + * @param {!EdgeDataOptions} undefined + */ + constructor({ bends, sourceNodeIndex, targetNodeIndex }) { + this.bends = bends + this.sourceNodeIndex = sourceNodeIndex + this.targetNodeIndex = targetNodeIndex + } + + /** + * Matches the intended source and target ports based on the source and target node indices. + * @param {!Array.} nodes + * @returns {!Array.} + */ + matchPorts(nodes) { + const sourceNode = nodes[this.sourceNodeIndex] + const targetNode = nodes[this.targetNodeIndex] + + const sourcePort = sourceNode.ports.find((p) => p.tag.side === 'right') + const targetPort = targetNode.ports.find((p) => p.tag.side === 'left') + if (!sourcePort || !targetPort) { + throw new Error('Malformed edge data') + } + return [sourcePort, targetPort] + } + + /** + * Converts node data to an actual graph node. + * @param {!IGraph} graph + * @param {!IPort} sourcePort + * @param {!IPort} targetPort + * @returns {!IEdge} + */ + createGraphItem(graph, sourcePort, targetPort) { + const bends = this.bends.map((b) => new Point(...b)) + return graph.createEdge({ sourcePort, targetPort, bends }) + } + + /** + * Converts node data to a serializable format. + * @returns {!SerializableEdgeData} + */ + toJSONData() { + return { + bends: this.bends, + sourceNodeIndex: this.sourceNodeIndex, + targetNodeIndex: this.targetNodeIndex + } + } +} diff --git a/demos/showcase/home-automation/ImportExportManager/EdgeData.ts b/demos/showcase/home-automation/ImportExportManager/EdgeData.ts new file mode 100644 index 000000000..11112e772 --- /dev/null +++ b/demos/showcase/home-automation/ImportExportManager/EdgeData.ts @@ -0,0 +1,125 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { IEdge, IGraph, INode, IPort, Point } from 'yfiles' + +export type SerializableEdgeData = { + bends: Array<[number, number]> + sourceNodeIndex: number + targetNodeIndex: number +} + +type EdgeDataOptions = SerializableEdgeData + +/** + * A simple, minimal data structure that can be used for storing a FlowEdge in JSON + * and then re-create it in the graph. + */ +export class EdgeData { + bends: Array<[number, number]> + sourceNodeIndex: number + targetNodeIndex: number + + /** + * Creates EdgeData from an actual edge + */ + static fromGraphItem(edge: IEdge, sourceNodeIndex: number, targetNodeIndex: number): EdgeData { + const bends = edge.bends + .toArray() + .map(bend => [bend.location.x, bend.location.y] as [number, number]) + return new EdgeData({ bends, sourceNodeIndex, targetNodeIndex }) + } + + /** + * Converts an arbitrary piece of data to EdgeData after validation. + */ + static fromJSONData(data: unknown): EdgeData { + EdgeData.validate(data) + return new EdgeData(data) + } + + /** + * Checks if an arbitrary piece of data (as it comes from a JSON source) + * conforms to the format required by EdgeData. + */ + static validate(data: unknown): asserts data is EdgeDataOptions { + const bendsAreValid = (data as EdgeDataOptions).bends.every( + bend => Array.isArray(bend) && Number.isFinite(bend[0]) && Number.isFinite(bend[1]) + ) + if ( + data !== null && + typeof data === 'object' && + bendsAreValid && + Number.isFinite((data as EdgeDataOptions).sourceNodeIndex) && + Number.isFinite((data as EdgeDataOptions).targetNodeIndex) + ) { + return + } + throw new Error('Malformed edge data') + } + + constructor({ bends, sourceNodeIndex, targetNodeIndex }: EdgeDataOptions) { + this.bends = bends + this.sourceNodeIndex = sourceNodeIndex + this.targetNodeIndex = targetNodeIndex + } + + /** + * Matches the intended source and target ports based on the source and target node indices. + */ + matchPorts(nodes: Array): [IPort, IPort] { + const sourceNode = nodes[this.sourceNodeIndex] + const targetNode = nodes[this.targetNodeIndex] + + const sourcePort = sourceNode.ports.find(p => p.tag.side === 'right') + const targetPort = targetNode.ports.find(p => p.tag.side === 'left') + if (!sourcePort || !targetPort) { + throw new Error('Malformed edge data') + } + return [sourcePort, targetPort] + } + + /** + * Converts node data to an actual graph node. + */ + createGraphItem(graph: IGraph, sourcePort: IPort, targetPort: IPort): IEdge { + const bends = this.bends.map(b => new Point(...b)) + return graph.createEdge({ sourcePort, targetPort, bends }) + } + + /** + * Converts node data to a serializable format. + */ + toJSONData(): SerializableEdgeData { + return { + bends: this.bends, + sourceNodeIndex: this.sourceNodeIndex, + targetNodeIndex: this.targetNodeIndex + } + } +} diff --git a/demos/showcase/home-automation/ImportExportManager/GraphData.js b/demos/showcase/home-automation/ImportExportManager/GraphData.js new file mode 100644 index 000000000..7686fa496 --- /dev/null +++ b/demos/showcase/home-automation/ImportExportManager/GraphData.js @@ -0,0 +1,131 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { IGraph } from 'yfiles' +import { NodeData } from './NodeData.js' +import { EdgeData } from './EdgeData.js' + +/** + * @typedef {Object} SerializableGraphData + * @property {Array.} nodes + * @property {Array.} edges + */ + +/** + * @typedef {Object} GraphDataOptions + * @property {Array.} nodeDataItems + * @property {Array.} edgeDataItems + */ + +/** + * A simple, minimal data structure that can be used for exporting Graph data + * and re-creating the Graph. + */ +export class GraphData { + nodeDataItems + edgeDataItems + + /** + * Creates GraphData from an actual node. We exclude the validator function, + * if present as it cannot be serialized, and it will be automatically set + * on node re-creation anyway. + * @param {!IGraph} graph + * @returns {!GraphData} + */ + static fromGraph(graph) { + const nodes = graph.nodes.toArray() + + const edges = graph.edges + .toArray() + .map((edge) => [ + edge, + nodes.findIndex((node) => node === edge.sourceNode), + nodes.findIndex((node) => node === edge.targetNode) + ]) + + const nodeDataItems = nodes.map(NodeData.fromGraphItem) + const edgeDataItems = edges.map((edge) => EdgeData.fromGraphItem(...edge)) + return new GraphData({ nodeDataItems, edgeDataItems }) + } + + /** + * Converts an arbitrary piece of data to GraphData after validation. + * @param {!unknown} data + * @returns {!GraphData} + */ + static fromJSONData(data) { + this.validate(data) + + const nodeDataItems = data.nodes.map(NodeData.fromJSONData) + const edgeDataItems = data.edges.map(EdgeData.fromJSONData) + return new GraphData({ nodeDataItems, edgeDataItems }) + } + + /** + * Checks if an arbitrary piece of data (as it comes from a JSON source) + * conforms to the format required by GraphData. + * @param {!unknown} data + * @returns {!SerializableGraphData} + */ + static validate(data) { + if (data) { + return + } + throw new Error('Malformed graph data') + } + + /** + * @param {!GraphDataOptions} undefined + */ + constructor({ nodeDataItems, edgeDataItems }) { + this.nodeDataItems = nodeDataItems + this.edgeDataItems = edgeDataItems + } + + /** + * Applies data to the actual Graph after clearing it. + * @param {!IGraph} graph + */ + applyToGraph(graph) { + graph.clear() + const nodes = this.nodeDataItems.map((n) => n.createGraphItem(graph)) + this.edgeDataItems.forEach((e) => e.createGraphItem(graph, ...e.matchPorts(nodes))) + } + + /** + * Converts GraphData to a JSON string. + * @returns {!string} + */ + toJSON() { + const data = { + nodes: this.nodeDataItems.map((n) => n.toJSONData()), + edges: this.edgeDataItems.map((e) => e.toJSONData()) + } + return JSON.stringify(data, null, 2) + } +} diff --git a/demos/showcase/home-automation/ImportExportManager/GraphData.ts b/demos/showcase/home-automation/ImportExportManager/GraphData.ts new file mode 100644 index 000000000..4128ccdb2 --- /dev/null +++ b/demos/showcase/home-automation/ImportExportManager/GraphData.ts @@ -0,0 +1,121 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { IGraph } from 'yfiles' +import { NodeData, type SerializableNodeData } from './NodeData' +import { EdgeData, type SerializableEdgeData } from './EdgeData' + +export type SerializableGraphData = { + nodes: Array + edges: Array +} + +type GraphDataOptions = { + nodeDataItems: Array + edgeDataItems: Array +} + +/** + * A simple, minimal data structure that can be used for exporting Graph data + * and re-creating the Graph. + */ +export class GraphData { + private nodeDataItems: GraphDataOptions['nodeDataItems'] + private edgeDataItems: GraphDataOptions['edgeDataItems'] + + /** + * Creates GraphData from an actual node. We exclude the validator function, + * if present as it cannot be serialized, and it will be automatically set + * on node re-creation anyway. + */ + static fromGraph(graph: IGraph): GraphData { + const nodes = graph.nodes.toArray() + + const edges = graph.edges + .toArray() + .map( + edge => + [ + edge, + nodes.findIndex(node => node === edge.sourceNode), + nodes.findIndex(node => node === edge.targetNode) + ] as const + ) + + const nodeDataItems = nodes.map(NodeData.fromGraphItem) + const edgeDataItems = edges.map(edge => EdgeData.fromGraphItem(...edge)) + return new GraphData({ nodeDataItems, edgeDataItems }) + } + + /** + * Converts an arbitrary piece of data to GraphData after validation. + */ + static fromJSONData(data: unknown): GraphData { + this.validate(data) + + const nodeDataItems = data.nodes.map(NodeData.fromJSONData) + const edgeDataItems = data.edges.map(EdgeData.fromJSONData) + return new GraphData({ nodeDataItems, edgeDataItems }) + } + + /** + * Checks if an arbitrary piece of data (as it comes from a JSON source) + * conforms to the format required by GraphData. + */ + static validate(data: unknown): asserts data is SerializableGraphData { + if (data) { + return + } + throw new Error('Malformed graph data') + } + + constructor({ nodeDataItems, edgeDataItems }: GraphDataOptions) { + this.nodeDataItems = nodeDataItems + this.edgeDataItems = edgeDataItems + } + + /** + * Applies data to the actual Graph after clearing it. + */ + applyToGraph(graph: IGraph): void { + graph.clear() + const nodes = this.nodeDataItems.map(n => n.createGraphItem(graph)) + this.edgeDataItems.forEach(e => e.createGraphItem(graph, ...e.matchPorts(nodes))) + } + + /** + * Converts GraphData to a JSON string. + */ + toJSON(): string { + const data: SerializableGraphData = { + nodes: this.nodeDataItems.map(n => n.toJSONData()), + edges: this.edgeDataItems.map(e => e.toJSONData()) + } + return JSON.stringify(data, null, 2) + } +} diff --git a/demos/showcase/home-automation/ImportExportManager/ImportExportManager.js b/demos/showcase/home-automation/ImportExportManager/ImportExportManager.js new file mode 100644 index 000000000..80511a5ca --- /dev/null +++ b/demos/showcase/home-automation/ImportExportManager/ImportExportManager.js @@ -0,0 +1,145 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { showErrorDialog } from '../UI/showErrorDialog.js' +import { GraphData } from './GraphData.js' +import { GraphComponent } from 'yfiles' + +/** + * @param {!GraphComponent} graphComponent + */ +export function initializeJsonIo(graphComponent) { + setupButtons(graphComponent) +} + +/** + * Retrieves the "Open" & "Save" buttons from the DOM and sets up event listeners. + * @param {!GraphComponent} graphComponent + */ +function setupButtons(graphComponent) { + const buttons = { + open: document.querySelector('button[data-command="OPEN_JSON"]'), + save: document.querySelector('button[data-command="SAVE_JSON"]') + } + if (!buttons.open || !buttons.save) { + throw new Error('Could not retrieve command buttons from the DOM') + } + + buttons.open.addEventListener('click', () => importGraphData(graphComponent)) + buttons.save.addEventListener('click', () => exportGraphData(graphComponent)) +} + +/** + * Creates a new graph from graph data. If the dataset is not specified, the user + * will be prompted to select a JSON file from their local filesystem. + * @param {!GraphComponent} graphComponent + * @param {!SerializableGraphData} [data] + */ +export async function importGraphData(graphComponent, data) { + try { + if (!data) { + data = await importJsonData() + } + GraphData.fromJSONData(data).applyToGraph(graphComponent.graph) + + graphComponent.selection.clear() + await graphComponent.fitGraphBounds({ animated: true }) + } catch (error) { + showErrorDialog({ + title: 'Graph import error', + message: error instanceof Error ? error.message : 'Could not load graph data.' + }) + return + } +} + +/** + * Handles the details of prompting file selection, reading the specified file, + * and parsing its JSON contents. + * @returns {!Promise.} + */ +function importJsonData() { + return new Promise((resolve, reject) => { + const fileInput = document.createElement('input') + fileInput.type = 'file' + fileInput.accept = '.json' + fileInput.style.display = 'none' + document.body.appendChild(fileInput) + fileInput.click() + + function handleFileSelect(event) { + const target = event.target + target.removeEventListener('change', handleFileSelect) + document.body.removeChild(fileInput) + + const file = target.files?.[0] + if (!file) { + return + } + + const reader = new FileReader() + reader.onload = (event) => { + const fileContent = event.target?.result + if (typeof fileContent !== 'string') { + throw new Error('Error reading file') + } + + try { + resolve(JSON.parse(fileContent)) + } catch (error) { + if (error instanceof Error) { + reject(error) + } else { + reject(new Error('Error parsing JSON file')) + } + } + } + reader.onerror = () => { + reject(new Error('Error reading file')) + } + + reader.readAsText(file) + } + + fileInput.addEventListener('change', handleFileSelect) + }) +} + +/** + * @param {!GraphComponent} graphComponent + */ +function exportGraphData(graphComponent) { + const dataString = GraphData.fromGraph(graphComponent.graph).toJSON() + const blob = new Blob([dataString], { type: 'application/json' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = 'data.json' + a.click() + URL.revokeObjectURL(url) +} diff --git a/demos/showcase/home-automation/ImportExportManager/ImportExportManager.ts b/demos/showcase/home-automation/ImportExportManager/ImportExportManager.ts new file mode 100644 index 000000000..1fdbde121 --- /dev/null +++ b/demos/showcase/home-automation/ImportExportManager/ImportExportManager.ts @@ -0,0 +1,138 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { showErrorDialog } from '../UI/showErrorDialog' +import { GraphData, type SerializableGraphData } from './GraphData' +import { GraphComponent } from 'yfiles' + +export function initializeJsonIo(graphComponent: GraphComponent): void { + setupButtons(graphComponent) +} + +/** + * Retrieves the "Open" & "Save" buttons from the DOM and sets up event listeners. + */ +function setupButtons(graphComponent: GraphComponent): void { + const buttons = { + open: document.querySelector('button[data-command="OPEN_JSON"]')!, + save: document.querySelector('button[data-command="SAVE_JSON"]')! + } + if (!buttons.open || !buttons.save) { + throw new Error('Could not retrieve command buttons from the DOM') + } + + buttons.open.addEventListener('click', () => importGraphData(graphComponent)) + buttons.save.addEventListener('click', () => exportGraphData(graphComponent)) +} + +/** + * Creates a new graph from graph data. If the dataset is not specified, the user + * will be prompted to select a JSON file from their local filesystem. + */ +export async function importGraphData( + graphComponent: GraphComponent, + data?: SerializableGraphData +) { + try { + if (!data) { + data = await importJsonData() + } + GraphData.fromJSONData(data).applyToGraph(graphComponent.graph) + + graphComponent.selection.clear() + await graphComponent.fitGraphBounds({ animated: true }) + } catch (error) { + showErrorDialog({ + title: 'Graph import error', + message: error instanceof Error ? error.message : 'Could not load graph data.' + }) + return + } +} + +/** + * Handles the details of prompting file selection, reading the specified file, + * and parsing its JSON contents. + */ +function importJsonData(): Promise { + return new Promise((resolve, reject) => { + const fileInput = document.createElement('input') + fileInput.type = 'file' + fileInput.accept = '.json' + fileInput.style.display = 'none' + document.body.appendChild(fileInput) + fileInput.click() + + function handleFileSelect(event: Event) { + const target = event.target as HTMLInputElement + target.removeEventListener('change', handleFileSelect) + document.body.removeChild(fileInput) + + const file = target.files?.[0] + if (!file) { + return + } + + const reader = new FileReader() + reader.onload = event => { + const fileContent = event.target?.result + if (typeof fileContent !== 'string') { + throw new Error('Error reading file') + } + + try { + resolve(JSON.parse(fileContent)) + } catch (error) { + if (error instanceof Error) { + reject(error) + } else { + reject(new Error('Error parsing JSON file')) + } + } + } + reader.onerror = () => { + reject(new Error('Error reading file')) + } + + reader.readAsText(file) + } + + fileInput.addEventListener('change', handleFileSelect) + }) +} + +function exportGraphData(graphComponent: GraphComponent) { + const dataString = GraphData.fromGraph(graphComponent.graph).toJSON() + const blob = new Blob([dataString], { type: 'application/json' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = 'data.json' + a.click() + URL.revokeObjectURL(url) +} diff --git a/demos/showcase/home-automation/ImportExportManager/NodeData.js b/demos/showcase/home-automation/ImportExportManager/NodeData.js new file mode 100644 index 000000000..8bb772db0 --- /dev/null +++ b/demos/showcase/home-automation/ImportExportManager/NodeData.js @@ -0,0 +1,137 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { IGraph, INode, Point } from 'yfiles' +import { createInGraph, validateNodeTag } from '../FlowNode/FlowNode.js' + +/** + * @typedef {Object} SerializableNodeData + * @property {Array.} position + * @property {Omit.} properties + */ + +/** + * @typedef {SerializableNodeData} NodeDataOptions + */ + +/** + * A simple, minimal data structure that can be used for storing a FlowNode in JSON + * and then re-create it in the graph. + */ +export class NodeData { + position + properties + + /** + * Creates NodeData from an actual node. We exclude the validator function, + * if present as it cannot be serialized, and it will be automatically set + * on node re-creation anyway. + * @param {!INode} undefined + * @returns {!NodeData} + */ + static fromGraphItem({ tag, layout }) { + const keysToFilter = ['validate', 'hasLeftPort', 'hasRightPort'] + if (!validateNodeTag(tag)) { + throw new Error('Invalid node tag') + } + const properties = Object.fromEntries( + Object.entries(tag).filter(([key]) => !keysToFilter.includes(key)) + ) + return new NodeData({ + properties, + position: [layout.x, layout.y] + }) + } + + /** + * Converts an arbitrary piece of data to NodeData after validation. + * @param {!unknown} data + * @returns {!NodeData} + */ + static fromJSONData(data) { + NodeData.validate(data) + return new NodeData(data) + } + + /** + * Checks if an arbitrary piece of data (as it comes from a JSON source) + * conforms to the format required by NodeData. + * @param {!unknown} data + * @returns {!SerializableNodeData} + */ + static validate(data) { + if ( + data !== null && + typeof data === 'object' && + Array.isArray(data.position) && + Number.isFinite(data.position[0]) && + Number.isFinite(data.position[1]) && + validateNodeTag(data.properties) + ) { + return + } + throw new Error('Malformed node data') + } + + /** + * @param {!NodeDataOptions} undefined + */ + constructor({ properties, position }) { + this.position = position + this.properties = properties + } + + /** + * Converts node data to an actual graph node. + * @param {!IGraph} graph + * @returns {!INode} + */ + createGraphItem(graph) { + const node = createInGraph({ + graph, + variant: this.properties.variant, + position: new Point(...this.position) + }) + node.tag = { + ...node.tag, + ...this.properties + } + return node + } + + /** + * Converts node data to a serializable format. + * @returns {!SerializableNodeData} + */ + toJSONData() { + return { + position: this.position, + properties: this.properties + } + } +} diff --git a/demos/showcase/home-automation/ImportExportManager/NodeData.ts b/demos/showcase/home-automation/ImportExportManager/NodeData.ts new file mode 100644 index 000000000..58fe75a8f --- /dev/null +++ b/demos/showcase/home-automation/ImportExportManager/NodeData.ts @@ -0,0 +1,127 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { IGraph, INode, Point } from 'yfiles' +import { + createInGraph, + type FlowNodeProperties, + type FlowNodeVariant, + validateNodeTag +} from '../FlowNode/FlowNode' + +export type SerializableNodeData = { + position: [number, number] + properties: Omit +} + +type NodeDataOptions = SerializableNodeData + +/** + * A simple, minimal data structure that can be used for storing a FlowNode in JSON + * and then re-create it in the graph. + */ +export class NodeData { + private readonly position: NodeDataOptions['position'] + private readonly properties: NodeDataOptions['properties'] + + /** + * Creates NodeData from an actual node. We exclude the validator function, + * if present as it cannot be serialized, and it will be automatically set + * on node re-creation anyway. + */ + static fromGraphItem({ tag, layout }: INode): NodeData { + const keysToFilter = ['validate', 'hasLeftPort', 'hasRightPort'] + if (!validateNodeTag(tag)) { + throw new Error('Invalid node tag') + } + const properties = Object.fromEntries( + Object.entries(tag).filter(([key]) => !keysToFilter.includes(key)) + ) as FlowNodeProperties + return new NodeData({ + properties, + position: [layout.x, layout.y] + }) + } + + /** + * Converts an arbitrary piece of data to NodeData after validation. + */ + static fromJSONData(data: unknown): NodeData { + NodeData.validate(data) + return new NodeData(data) + } + + /** + * Checks if an arbitrary piece of data (as it comes from a JSON source) + * conforms to the format required by NodeData. + */ + static validate(data: unknown): asserts data is SerializableNodeData { + if ( + data !== null && + typeof data === 'object' && + Array.isArray((data as NodeDataOptions).position) && + Number.isFinite((data as NodeDataOptions).position[0]) && + Number.isFinite((data as NodeDataOptions).position[1]) && + validateNodeTag((data as NodeDataOptions).properties) + ) { + return + } + throw new Error('Malformed node data') + } + + constructor({ properties, position }: NodeDataOptions) { + this.position = position + this.properties = properties + } + + /** + * Converts node data to an actual graph node. + */ + createGraphItem(graph: IGraph): INode { + const node = createInGraph({ + graph, + variant: this.properties.variant as FlowNodeVariant, + position: new Point(...this.position) + }) as INode + node.tag = { + ...node.tag, + ...this.properties + } + return node + } + + /** + * Converts node data to a serializable format. + */ + toJSONData(): SerializableNodeData { + return { + position: this.position, + properties: this.properties + } + } +} diff --git a/demos/showcase/home-automation/README.md b/demos/showcase/home-automation/README.md new file mode 100644 index 000000000..a51a62a92 --- /dev/null +++ b/demos/showcase/home-automation/README.md @@ -0,0 +1,29 @@ + +# Home Automation Demo + +# Home Automation Demo + +This demo simulates a tool for visually programming a home automation network. The nodes represent various stages of data flow within the system. The graph features basic validation: output ports can only be connected to input ports, no edges can be duplicated, and nodes with required properties display an error indicator if some of those properties are missing. + +### Things to Try + +- Add nodes by dragging and dropping them from the node palette on the right. +- Connect nodes. Note that the output port (right) of one node can only connect to the input port (left) of another node. +- Reconnect existing edges to other nodes. +- Select a node to highlight all of its connected edges. +- Move connected nodes around to see how edges dynamically change their shape. +- Apply automatic _Layout_ to arrange the graph hierarchically. +- Select a node to view and edit its properties in the right panel. +- Enter a long _label_ in the node properties to observe how the node size changes. +- Note that some node properties are required. If not set, the corresponding node will display an error indicator. Hover over a node to see a popup with more details about the missing properties. +- Save/load the current graph using JSON. diff --git a/demos/showcase/home-automation/UI/initializeContextMenu.js b/demos/showcase/home-automation/UI/initializeContextMenu.js new file mode 100644 index 000000000..b4bffd6bc --- /dev/null +++ b/demos/showcase/home-automation/UI/initializeContextMenu.js @@ -0,0 +1,77 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, GraphEditorInputMode, GraphItemTypes } from 'yfiles' +import { populateContextMenu } from '../flow-context-menu/contextMenuUtils.js' +import { FlowContextMenu } from '../flow-context-menu/FlowContextMenu.js' + +/** + * @param {!GraphComponent} graphComponent + */ +export function initializeContextMenu(graphComponent) { + const graphEditorInputMode = graphComponent.inputMode + + // This code example uses mostly the sample implementation from the Context Menu demo, + // copied as FlowContextMenu mostly to customize items population, + // but you can use any other context menu widget as well. + // See the Context Menu demo for more details about working with context menus. + const contextMenu = new FlowContextMenu(graphComponent) + + // handle context menus only for nodes + graphEditorInputMode.contextMenuItems = GraphItemTypes.NODE + + // Add an event listener that populates the context menu according to the hit elements, or cancels showing a menu. + // This PopulateItemContextMenu is fired when calling the ContextMenuInputMode.shouldOpenMenu method above. + graphEditorInputMode.addPopulateItemContextMenuListener((_, args) => + populateContextMenu(contextMenu, graphComponent, args) + ) + + // Register event listeners for the various "contextmenu" events of the application. + contextMenu.addOpeningEventListeners(graphComponent, (location) => { + // On "contextmenu" event, inform the input mode and check if the context menu may open now. + if ( + graphEditorInputMode.contextMenuInputMode.shouldOpenMenu( + graphComponent.toWorldFromPage(location) + ) + ) { + // Display the UI elements of the context menu at the given location. + contextMenu.show(location) + } + }) + + // Add a listener that closes the menu when the input mode requests it. + graphEditorInputMode.contextMenuInputMode.addCloseMenuListener(() => { + contextMenu.close() + }) + + // If the context menu closes itself, for example because a menu item was clicked, + // we must inform the input mode to keep everything in sync. + contextMenu.onClosedCallback = () => { + graphEditorInputMode.contextMenuInputMode.menuClosed() + } +} diff --git a/demos/showcase/home-automation/UI/initializeContextMenu.ts b/demos/showcase/home-automation/UI/initializeContextMenu.ts new file mode 100644 index 000000000..d34db1e1e --- /dev/null +++ b/demos/showcase/home-automation/UI/initializeContextMenu.ts @@ -0,0 +1,74 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, GraphEditorInputMode, GraphItemTypes } from 'yfiles' +import { populateContextMenu } from '../flow-context-menu/contextMenuUtils' +import { FlowContextMenu } from '../flow-context-menu/FlowContextMenu' + +export function initializeContextMenu(graphComponent: GraphComponent): void { + const graphEditorInputMode = graphComponent.inputMode as GraphEditorInputMode + + // This code example uses mostly the sample implementation from the Context Menu demo, + // copied as FlowContextMenu mostly to customize items population, + // but you can use any other context menu widget as well. + // See the Context Menu demo for more details about working with context menus. + const contextMenu = new FlowContextMenu(graphComponent) + + // handle context menus only for nodes + graphEditorInputMode.contextMenuItems = GraphItemTypes.NODE + + // Add an event listener that populates the context menu according to the hit elements, or cancels showing a menu. + // This PopulateItemContextMenu is fired when calling the ContextMenuInputMode.shouldOpenMenu method above. + graphEditorInputMode.addPopulateItemContextMenuListener((_, args) => + populateContextMenu(contextMenu, graphComponent, args) + ) + + // Register event listeners for the various "contextmenu" events of the application. + contextMenu.addOpeningEventListeners(graphComponent, location => { + // On "contextmenu" event, inform the input mode and check if the context menu may open now. + if ( + graphEditorInputMode.contextMenuInputMode.shouldOpenMenu( + graphComponent.toWorldFromPage(location) + ) + ) { + // Display the UI elements of the context menu at the given location. + contextMenu.show(location) + } + }) + + // Add a listener that closes the menu when the input mode requests it. + graphEditorInputMode.contextMenuInputMode.addCloseMenuListener(() => { + contextMenu.close() + }) + + // If the context menu closes itself, for example because a menu item was clicked, + // we must inform the input mode to keep everything in sync. + contextMenu.onClosedCallback = () => { + graphEditorInputMode.contextMenuInputMode.menuClosed() + } +} diff --git a/demos/showcase/home-automation/UI/initializeDragAndDropPanel.js b/demos/showcase/home-automation/UI/initializeDragAndDropPanel.js new file mode 100644 index 000000000..4d24f038c --- /dev/null +++ b/demos/showcase/home-automation/UI/initializeDragAndDropPanel.js @@ -0,0 +1,41 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { SimpleNode } from 'yfiles' +import { DragAndDropPanel } from 'demo-utils/DragAndDropPanel' +import { FlowNodeStyle } from '../FlowNode/FlowNodeStyle.js' +import { createFlowNode, flowNodeVariants } from '../FlowNode/FlowNode.js' + +export function initializeDragAndDropPanel() { + const panel = new DragAndDropPanel(document.getElementById('drag-and-drop-panel')) + + const palette = flowNodeVariants.map((nodeVariant) => createFlowNode(nodeVariant)) + + panel.maxItemWidth = FlowNodeStyle.defaultWidthWithPorts + panel.populatePanel(palette) +} diff --git a/demos/showcase/home-automation/UI/initializeDragAndDropPanel.ts b/demos/showcase/home-automation/UI/initializeDragAndDropPanel.ts new file mode 100644 index 000000000..45c6cfd36 --- /dev/null +++ b/demos/showcase/home-automation/UI/initializeDragAndDropPanel.ts @@ -0,0 +1,41 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { SimpleNode } from 'yfiles' +import { DragAndDropPanel } from 'demo-utils/DragAndDropPanel' +import { FlowNodeStyle } from '../FlowNode/FlowNodeStyle' +import { createFlowNode, flowNodeVariants } from '../FlowNode/FlowNode' + +export function initializeDragAndDropPanel(): void { + const panel = new DragAndDropPanel(document.getElementById('drag-and-drop-panel')!) + + const palette = flowNodeVariants.map(nodeVariant => createFlowNode(nodeVariant)) as SimpleNode[] + + panel.maxItemWidth = FlowNodeStyle.defaultWidthWithPorts + panel.populatePanel(palette) +} diff --git a/demos/showcase/home-automation/UI/initializeTagExplorer.js b/demos/showcase/home-automation/UI/initializeTagExplorer.js new file mode 100644 index 000000000..8ac7c6ae1 --- /dev/null +++ b/demos/showcase/home-automation/UI/initializeTagExplorer.js @@ -0,0 +1,158 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, INode } from 'yfiles' +import { hiddenProperties, isFlowNode, lockedProperties } from '../FlowNode/FlowNode.js' +import { showErrorDialog } from './showErrorDialog.js' + +/** + * @param {!GraphComponent} graphComponent + */ +export function initializeTagExplorer(graphComponent) { + // Use selection event instead of currentItemChanged event to be able to clear tag explorer when no item is selected + graphComponent.selection.addItemSelectionChangedListener((selection) => { + try { + const list = document.getElementById('tags-explorer-list') + list.innerHTML = '' + // Leave tag explorer empty if no item is selected + if (selection.size === 0) { + return + } + // If current item isn't a node then don't populate tag explorer + const node = selection.first() + if (!isFlowNode(node)) { + return + } + + populateTagExplorer(node) + } catch (e) { + const message = e instanceof Error ? e.message : 'Error initializing node properties panel' + showErrorDialog({ title: message, message }) + } + }) + + // Repopulate current tag manager view with updated tags whenever tag is changed by outside source, e.g. undo-redo + graphComponent.graph.addNodeTagChangedListener((_, evt) => { + const graphSelection = graphComponent.selection + if (graphSelection.selectedNodes.size === 0) { + return + } + + const changedNode = evt.item + const selectedNode = graphSelection.selectedNodes.first() + + if ( + !selectedNode || + !changedNode || + !isFlowNode(selectedNode) || + changedNode !== selectedNode + ) { + return + } + + populateTagExplorer(selectedNode) + }) +} + +/** + * @param {!INode} selectedNode + */ +function populateTagExplorer(selectedNode) { + const list = document.getElementById('tags-explorer-list') + list.innerHTML = '' + + const tagKeys = Object.keys(selectedNode.tag) + tagKeys + .filter((key) => !hiddenProperties.includes(key)) + .forEach((key) => { + list.appendChild( + createTagInputLine({ + name: key, + initValue: selectedNode.tag[key], + node: selectedNode, + validate: selectedNode.tag.validate + }) + ) + }) +} + +/** + * @param {!object} undefined + * @returns {!Node} + */ +function createTagInputLine({ name, initValue, node, validate }) { + const inputName = name + const label = document.createElement('label') + label.setAttribute('for', inputName) + label.className = 'tags-explorer-list-label' + label.innerHTML = inputName + ':' + + const item = document.createElement('li') + const input = document.createElement('input') + input.setAttribute('id', inputName) + input.setAttribute('type', 'text') + input.setAttribute('value', initValue) + if (lockedProperties.includes(name)) { + input.setAttribute('disabled', 'true') + } + + input.addEventListener('change', (event) => { + if (!event.target || !(event instanceof Event) || !(event.target instanceof HTMLInputElement)) { + return + } + const newTags = { ...node.tag, [name]: event.target.value } + // validate tags on each change of input + const validation = validate(newTags) + configureValidationClassOnListItem(item, validation, name) + + node.tag = newTags + }) + + input.className = 'tags-explorer-list-input' + item.appendChild(label) + item.appendChild(input) + item.classList.add('tags-explorer-list-item') + // validate tags on initial render of input + const validation = validate(node.tag) + configureValidationClassOnListItem(item, validation, name) + + return item +} + +/** + * @param {!HTMLLIElement} item + * @param {!FlowNodeValidation} validation + * @param {*} name + */ +function configureValidationClassOnListItem(item, validation, name) { + if (validation.invalidProperties.includes(String(name))) { + item.classList.add('tags-explorer-list-item__invalid') + } else { + item.classList.remove('tags-explorer-list-item__invalid') + } +} diff --git a/demos/showcase/home-automation/UI/initializeTagExplorer.ts b/demos/showcase/home-automation/UI/initializeTagExplorer.ts new file mode 100644 index 000000000..f2e4f735d --- /dev/null +++ b/demos/showcase/home-automation/UI/initializeTagExplorer.ts @@ -0,0 +1,164 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, INode } from 'yfiles' +import { + type FlowNodeValidation, + type FlowNodeValidationFn, + hiddenProperties, + type FlowNode, + isFlowNode, + lockedProperties +} from '../FlowNode/FlowNode' +import { showErrorDialog } from './showErrorDialog' + +export function initializeTagExplorer(graphComponent: GraphComponent): void { + // Use selection event instead of currentItemChanged event to be able to clear tag explorer when no item is selected + graphComponent.selection.addItemSelectionChangedListener(selection => { + try { + const list = document.getElementById('tags-explorer-list') as Element + list.innerHTML = '' + // Leave tag explorer empty if no item is selected + if (selection.size === 0) { + return + } + // If current item isn't a node then don't populate tag explorer + const node = selection.first() + if (!isFlowNode(node)) { + return + } + + populateTagExplorer(node as INode) + } catch (e) { + const message = e instanceof Error ? e.message : 'Error initializing node properties panel' + showErrorDialog({ title: message, message }) + } + }) + + // Repopulate current tag manager view with updated tags whenever tag is changed by outside source, e.g. undo-redo + graphComponent.graph.addNodeTagChangedListener((_, evt) => { + const graphSelection = graphComponent.selection + if (graphSelection.selectedNodes.size === 0) { + return + } + + const changedNode = evt.item + const selectedNode = graphSelection.selectedNodes.first() + + if ( + !selectedNode || + !changedNode || + !isFlowNode(selectedNode) || + changedNode !== selectedNode + ) { + return + } + + populateTagExplorer(selectedNode as INode) + }) +} + +function populateTagExplorer(selectedNode: INode) { + const list = document.getElementById('tags-explorer-list') as Element + list.innerHTML = '' + + const tagKeys = Object.keys(selectedNode.tag) as Array + tagKeys + .filter(key => !hiddenProperties.includes(key)) + .forEach(key => { + list.appendChild( + createTagInputLine({ + name: key, + initValue: selectedNode.tag[key], + node: selectedNode, + validate: selectedNode.tag.validate + }) + ) + }) +} + +function createTagInputLine({ + name, + initValue, + node, + validate +}: { + name: keyof FlowNode['tag'] + initValue: string + node: FlowNode + validate: FlowNodeValidationFn +}): Node { + const inputName = name as string + const label = document.createElement('label') + label.setAttribute('for', inputName) + label.className = 'tags-explorer-list-label' + label.innerHTML = inputName + ':' + + const item = document.createElement('li') + const input = document.createElement('input') + input.setAttribute('id', inputName) + input.setAttribute('type', 'text') + input.setAttribute('value', initValue) + if (lockedProperties.includes(name)) { + input.setAttribute('disabled', 'true') + } + + input.addEventListener('change', event => { + if (!event.target || !(event instanceof Event) || !(event.target instanceof HTMLInputElement)) { + return + } + const newTags = { ...node.tag, [name]: event.target.value } + // validate tags on each change of input + const validation = validate(newTags) + configureValidationClassOnListItem(item, validation, name) + + node.tag = newTags + }) + + input.className = 'tags-explorer-list-input' + item.appendChild(label) + item.appendChild(input) + item.classList.add('tags-explorer-list-item') + // validate tags on initial render of input + const validation = validate(node.tag) + configureValidationClassOnListItem(item, validation, name) + + return item +} + +function configureValidationClassOnListItem( + item: HTMLLIElement, + validation: FlowNodeValidation, + name: keyof FlowNode['tag'] +) { + if (validation.invalidProperties.includes(String(name))) { + item.classList.add('tags-explorer-list-item__invalid') + } else { + item.classList.remove('tags-explorer-list-item__invalid') + } +} diff --git a/demos/showcase/home-automation/UI/initializeToolbar.js b/demos/showcase/home-automation/UI/initializeToolbar.js new file mode 100644 index 000000000..37f963a0f --- /dev/null +++ b/demos/showcase/home-automation/UI/initializeToolbar.js @@ -0,0 +1,44 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, GridVisualCreator, ICommand } from 'yfiles' +import { runAutoLayout, triggerGridDisplay } from '../utils/customTriggers.js' +import { bindYFilesCommand } from '../utils/elementUtils.js' + +/** + * @param {!GraphComponent} graphComponent + * @param {!GridVisualCreator} grid + */ +export function initializeToolbar(graphComponent, grid) { + const gridButton = document.querySelector('#grid-button') + gridButton.addEventListener('click', () => triggerGridDisplay(graphComponent, grid)) + bindYFilesCommand("button[data-command='Undo2']", ICommand.UNDO, graphComponent, null, 'Undo') + bindYFilesCommand("button[data-command='Redo2']", ICommand.REDO, graphComponent, null, 'Redo') + const layoutButton = document.querySelector('#layoutButton') + layoutButton.addEventListener('click', async () => runAutoLayout(graphComponent)) +} diff --git a/demos/showcase/home-automation/UI/initializeToolbar.ts b/demos/showcase/home-automation/UI/initializeToolbar.ts new file mode 100644 index 000000000..a977ba215 --- /dev/null +++ b/demos/showcase/home-automation/UI/initializeToolbar.ts @@ -0,0 +1,40 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, GridVisualCreator, ICommand } from 'yfiles' +import { runAutoLayout, triggerGridDisplay } from '../utils/customTriggers' +import { bindYFilesCommand } from '../utils/elementUtils' + +export function initializeToolbar(graphComponent: GraphComponent, grid: GridVisualCreator): void { + const gridButton = document.querySelector('#grid-button')! + gridButton.addEventListener('click', () => triggerGridDisplay(graphComponent, grid)) + bindYFilesCommand("button[data-command='Undo2']", ICommand.UNDO, graphComponent, null, 'Undo') + bindYFilesCommand("button[data-command='Redo2']", ICommand.REDO, graphComponent, null, 'Redo') + const layoutButton = document.querySelector('#layoutButton')! + layoutButton.addEventListener('click', async () => runAutoLayout(graphComponent)) +} diff --git a/demos/showcase/home-automation/UI/initializeTooltips.js b/demos/showcase/home-automation/UI/initializeTooltips.js new file mode 100644 index 000000000..7a3dfa55b --- /dev/null +++ b/demos/showcase/home-automation/UI/initializeTooltips.js @@ -0,0 +1,103 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, GraphEditorInputMode, GraphItemTypes, Point, TimeSpan } from 'yfiles' +import { isFlowNode } from '../FlowNode/FlowNode.js' + +/** + * @param {!GraphComponent} graphComponent + */ +export function initializeTooltips(graphComponent) { + // Assume input mode has already been initialized because of order of operations in the main run function + const inputMode = graphComponent.inputMode + + const mouseHoverInputMode = inputMode.mouseHoverInputMode + mouseHoverInputMode.toolTipLocationOffset = new Point(10, 10) + // Increase time it takes for tooltip to appear and the time before it disappears + mouseHoverInputMode.delay = TimeSpan.fromMilliseconds(300) + mouseHoverInputMode.duration = TimeSpan.fromSeconds(20) + + inputMode.toolTipItems = GraphItemTypes.NODE + inputMode.addQueryItemToolTipListener((_, eventArgs) => { + if (eventArgs.handled) { + // Tooltip content has already been assigned -> nothing to do. + return + } + const item = eventArgs.item + // Validate if the node matches our custom FlowNode type + if (!isFlowNode(item)) { + return + } + + // If validation messages is empty we don't have anything to show in the tooltip + const validation = item.tag.validate(item.tag) + if (!validation.validationMessages.length) { + return + } + + // Use a rich HTML element as tooltip content. Alternatively, a plain string would do as well. + eventArgs.toolTip = createValidationTooltipContent(validation.validationMessages) + + // Indicate that the tooltip content has been set. + eventArgs.handled = true + }) +} + +/** + * The tooltip may either be a plain string or it can also be a rich HTML element. In this case, we + * show the latter. We use validationMessages returned by node's validation method and show them as a list in the + * tooltip. + * Basic tooltip styling can be done using yfiles-tooltip CSS class (see /resources/style.css). + * @param {!Array.} validationMessages + * @returns {!HTMLElement} + */ +function createValidationTooltipContent(validationMessages) { + // build the tooltip container + const tooltip = document.createElement('div') + tooltip.classList.add('tooltip') + + // const lineMark = document.createElement('div') + // lineMark.classList.add('tooltip__line-mark') + // tooltip.appendChild(lineMark) + + // Append the static title and append it to tooltip container + const title = document.createElement('h4') + title.innerHTML = 'There seems to be a problem with one or more properties:' + tooltip.appendChild(title) + + // Create list of messages and append it to tooltip container + const ul = document.createElement('ul') + validationMessages.forEach((message) => { + const li = document.createElement('li') + li.innerHTML = message + ul.appendChild(li) + }) + tooltip.appendChild(ul) + + return tooltip +} diff --git a/demos/showcase/home-automation/UI/initializeTooltips.ts b/demos/showcase/home-automation/UI/initializeTooltips.ts new file mode 100644 index 000000000..ef107086a --- /dev/null +++ b/demos/showcase/home-automation/UI/initializeTooltips.ts @@ -0,0 +1,98 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { GraphComponent, GraphEditorInputMode, GraphItemTypes, Point, TimeSpan } from 'yfiles' +import { type FlowNodeValidation, isFlowNode } from '../FlowNode/FlowNode' + +export function initializeTooltips(graphComponent: GraphComponent): void { + // Assume input mode has already been initialized because of order of operations in the main run function + const inputMode = graphComponent.inputMode as GraphEditorInputMode + + const mouseHoverInputMode = inputMode.mouseHoverInputMode + mouseHoverInputMode.toolTipLocationOffset = new Point(10, 10) + // Increase time it takes for tooltip to appear and the time before it disappears + mouseHoverInputMode.delay = TimeSpan.fromMilliseconds(300) + mouseHoverInputMode.duration = TimeSpan.fromSeconds(20) + + inputMode.toolTipItems = GraphItemTypes.NODE + inputMode.addQueryItemToolTipListener((_, eventArgs): void => { + if (eventArgs.handled) { + // Tooltip content has already been assigned -> nothing to do. + return + } + const item = eventArgs.item! + // Validate if the node matches our custom FlowNode type + if (!isFlowNode(item)) { + return + } + + // If validation messages is empty we don't have anything to show in the tooltip + const validation: FlowNodeValidation = item.tag.validate(item.tag) + if (!validation.validationMessages.length) { + return + } + + // Use a rich HTML element as tooltip content. Alternatively, a plain string would do as well. + eventArgs.toolTip = createValidationTooltipContent(validation.validationMessages) + + // Indicate that the tooltip content has been set. + eventArgs.handled = true + }) +} + +/** + * The tooltip may either be a plain string or it can also be a rich HTML element. In this case, we + * show the latter. We use validationMessages returned by node's validation method and show them as a list in the + * tooltip. + * Basic tooltip styling can be done using yfiles-tooltip CSS class (see /resources/style.css). + */ +function createValidationTooltipContent(validationMessages: Array): HTMLElement { + // build the tooltip container + const tooltip = document.createElement('div') + tooltip.classList.add('tooltip') + + // const lineMark = document.createElement('div') + // lineMark.classList.add('tooltip__line-mark') + // tooltip.appendChild(lineMark) + + // Append the static title and append it to tooltip container + const title = document.createElement('h4') + title.innerHTML = 'There seems to be a problem with one or more properties:' + tooltip.appendChild(title) + + // Create list of messages and append it to tooltip container + const ul = document.createElement('ul') + validationMessages.forEach(message => { + const li = document.createElement('li') + li.innerHTML = message + ul.appendChild(li) + }) + tooltip.appendChild(ul) + + return tooltip +} diff --git a/demos/showcase/home-automation/UI/showErrorDialog.js b/demos/showcase/home-automation/UI/showErrorDialog.js new file mode 100644 index 000000000..2fd42108c --- /dev/null +++ b/demos/showcase/home-automation/UI/showErrorDialog.js @@ -0,0 +1,82 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +/** + * @typedef {Object} ErrorDialogOptions + * @property {string} title + * @property {string} message + */ + +/** + * @typedef {Object} DialogElements + * @property {HTMLElement} dialog + * @property {HTMLButtonElement} dismissButton + */ + +/** + * @param {!ErrorDialogOptions} undefined + * @returns {!DialogElements} + */ +function createErrorDialog({ title, message }) { + const htmlString = ` +
+
+

${title}

+
+

${message}

+ +
+
+
+ ` + const parser = new DOMParser() + const dialog = parser.parseFromString(htmlString, 'text/html').body.firstChild + + if (dialog instanceof HTMLElement) { + const dismissButton = dialog.querySelector('button') + if (!(dismissButton instanceof HTMLButtonElement)) { + throw new Error() + } + return { dialog, dismissButton } + } else { + throw new Error('Could not create error dialog') + } +} + +/** + * @param {!ErrorDialogOptions} options + */ +export function showErrorDialog(options) { + const { dialog, dismissButton } = createErrorDialog(options) + document.body.appendChild(dialog) + const handleDismiss = () => { + document.body.removeChild(dialog) + dismissButton.removeEventListener('click', handleDismiss) + } + dismissButton.addEventListener('click', handleDismiss) +} diff --git a/demos/showcase/home-automation/UI/showErrorDialog.ts b/demos/showcase/home-automation/UI/showErrorDialog.ts new file mode 100644 index 000000000..46941441b --- /dev/null +++ b/demos/showcase/home-automation/UI/showErrorDialog.ts @@ -0,0 +1,73 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +type ErrorDialogOptions = { + title: string + message: string +} + +type DialogElements = { + dialog: HTMLElement + dismissButton: HTMLButtonElement +} + +function createErrorDialog({ title, message }: ErrorDialogOptions): DialogElements { + const htmlString = ` +
+
+

${title}

+
+

${message}

+ +
+
+
+ ` + const parser = new DOMParser() + const dialog = parser.parseFromString(htmlString, 'text/html').body.firstChild + + if (dialog instanceof HTMLElement) { + const dismissButton = dialog.querySelector('button') + if (!(dismissButton instanceof HTMLButtonElement)) { + throw new Error() + } + return { dialog, dismissButton } + } else { + throw new Error('Could not create error dialog') + } +} + +export function showErrorDialog(options: ErrorDialogOptions) { + const { dialog, dismissButton } = createErrorDialog(options) + document.body.appendChild(dialog) + const handleDismiss = () => { + document.body.removeChild(dialog) + dismissButton.removeEventListener('click', handleDismiss) + } + dismissButton.addEventListener('click', handleDismiss) +} diff --git a/demos/showcase/home-automation/flow-context-menu/FlowContextMenu.js b/demos/showcase/home-automation/flow-context-menu/FlowContextMenu.js new file mode 100644 index 000000000..907f3d5ba --- /dev/null +++ b/demos/showcase/home-automation/flow-context-menu/FlowContextMenu.js @@ -0,0 +1,155 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { ContextMenu } from 'demo-utils/ContextMenu' +import { GraphComponent, Point } from 'yfiles' + +/** + * An extension of demo implementation of a context menu that is used in various yFiles demos. + */ +export class FlowContextMenu extends ContextMenu { + /** + * Creates a new empty menu. + * + * @param {!GraphComponent} graphComponent The graph component of this context menu. + */ + constructor(graphComponent) { + super(graphComponent) + this.element.setAttribute('class', 'flow-context-menu') + } + + /** + * Adds a new separator to this menu. + */ + addSeparator() { + const separator = document.createElement('div') + separator.setAttribute('class', 'flow-context-menu__separator') + this.element.appendChild(separator) + } + + /** + * Shows this menu at the given location. + * + * This menu only shows if it has at least one menu item. + * + * @param {!Point} location The location of the menu relative to the left edge of the entire + * document. These are typically the pageX and pageY coordinates of the contextmenu event. + */ + show(location) { + if (this.element.childElementCount <= 0) { + return + } + this.element.addEventListener('focusout', this.focusOutListener) + this.element.addEventListener('focusin', this.focusInListener) + this.element.addEventListener('click', this.closeListener, false) + document.addEventListener('keydown', this.closeOnEscListener, false) + + // Set the location of this menu and append it to the body + const style = this.element.style + style.setProperty('position', 'absolute', '') + style.setProperty('left', `${location.x}px`, '') + style.setProperty('top', `${location.y}px`, '') + if (document.fullscreenElement && !document.fullscreenElement.contains(document.body)) { + document.fullscreenElement.appendChild(this.element) + } else { + document.body.appendChild(this.element) + } + + // trigger enter animation + setTimeout(() => { + this.element.classList.add('flow-context-menu--visible') + }, 0) + this.element.firstElementChild.focus() + this.isOpen = true + } + + /** + * Closes this menu. + */ + close() { + this.element.removeEventListener('focusout', this.focusOutListener) + this.element.removeEventListener('focusin', this.focusInListener) + this.element.removeEventListener('click', this.closeListener, false) + document.removeEventListener('keydown', this.closeOnEscListener, false) + + const parentNode = this.element.parentNode + if (parentNode) { + // trigger fade-out animation on a clone + const contextMenuClone = this.element.cloneNode(true) + contextMenuClone.classList.add('flow-context-menu--clone') + parentNode.appendChild(contextMenuClone) + // fade the clone out, then remove it from the DOM. Both actions need to be timed. + setTimeout(() => { + contextMenuClone.classList.remove('flow-context-menu--visible') + + setTimeout(() => { + parentNode.removeChild(contextMenuClone) + }, 300) + }, 0) + + this.element.classList.remove('flow-context-menu--visible') + parentNode.removeChild(this.element) + } + + this.isOpen = false + } + + /** + * @param {!string} label + * @param {?function} clickListener + * @param {boolean} [disabled] + * @param {!string} [icon] + * @returns {!HTMLElement} + */ + addMenuItem(label, clickListener, disabled, icon) { + const menuButton = document.createElement('button') + menuButton.classList.add('flow-context-menu__item') + if (disabled) { + menuButton.classList.add('flow-context-menu__item-disabled') + } + + if (clickListener !== null) { + menuButton.addEventListener('click', clickListener, false) + } + + const iconItem = document.createElement('div') + iconItem.classList.add('flow-context-menu__item-icon') + if (icon) { + iconItem.style.backgroundImage = `url(${icon})` + } + menuButton.appendChild(iconItem) + + const buttonText = document.createElement('span') + buttonText.innerHTML = label + menuButton.appendChild(buttonText) + + // flow-context-menu__item-icon + this.element.appendChild(menuButton) + return menuButton + } +} diff --git a/demos/showcase/home-automation/flow-context-menu/FlowContextMenu.ts b/demos/showcase/home-automation/flow-context-menu/FlowContextMenu.ts new file mode 100644 index 000000000..5a84dac55 --- /dev/null +++ b/demos/showcase/home-automation/flow-context-menu/FlowContextMenu.ts @@ -0,0 +1,153 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { ContextMenu } from 'demo-utils/ContextMenu' +import { GraphComponent, Point } from 'yfiles' + +/** + * An extension of demo implementation of a context menu that is used in various yFiles demos. + */ +export class FlowContextMenu extends ContextMenu { + /** + * Creates a new empty menu. + * + * @param graphComponent The graph component of this context menu. + */ + constructor(graphComponent: GraphComponent) { + super(graphComponent) + this.element.setAttribute('class', 'flow-context-menu') + } + + /** + * Adds a new separator to this menu. + */ + addSeparator(): void { + const separator = document.createElement('div') + separator.setAttribute('class', 'flow-context-menu__separator') + this.element.appendChild(separator) + } + + /** + * Shows this menu at the given location. + * + * This menu only shows if it has at least one menu item. + * + * @param location The location of the menu relative to the left edge of the entire + * document. These are typically the pageX and pageY coordinates of the contextmenu event. + */ + show(location: Point): void { + if (this.element.childElementCount <= 0) { + return + } + this.element.addEventListener('focusout', this.focusOutListener) + this.element.addEventListener('focusin', this.focusInListener) + this.element.addEventListener('click', this.closeListener, false) + document.addEventListener('keydown', this.closeOnEscListener, false) + + // Set the location of this menu and append it to the body + const style = this.element.style + style.setProperty('position', 'absolute', '') + style.setProperty('left', `${location.x}px`, '') + style.setProperty('top', `${location.y}px`, '') + if (document.fullscreenElement && !document.fullscreenElement.contains(document.body)) { + document.fullscreenElement.appendChild(this.element) + } else { + document.body.appendChild(this.element) + } + + // trigger enter animation + setTimeout(() => { + this.element.classList.add('flow-context-menu--visible') + }, 0) + ;(this.element.firstElementChild! as HTMLElement).focus() + this.isOpen = true + } + + /** + * Closes this menu. + */ + close(): void { + this.element.removeEventListener('focusout', this.focusOutListener) + this.element.removeEventListener('focusin', this.focusInListener) + this.element.removeEventListener('click', this.closeListener, false) + document.removeEventListener('keydown', this.closeOnEscListener, false) + + const parentNode = this.element.parentNode + if (parentNode) { + // trigger fade-out animation on a clone + const contextMenuClone = this.element.cloneNode(true) as HTMLElement + contextMenuClone.classList.add('flow-context-menu--clone') + parentNode.appendChild(contextMenuClone) + // fade the clone out, then remove it from the DOM. Both actions need to be timed. + setTimeout(() => { + contextMenuClone.classList.remove('flow-context-menu--visible') + + setTimeout(() => { + parentNode.removeChild(contextMenuClone) + }, 300) + }, 0) + + this.element.classList.remove('flow-context-menu--visible') + parentNode.removeChild(this.element) + } + + this.isOpen = false + } + + addMenuItem( + label: string, + clickListener: ((e: MouseEvent) => void) | null, + disabled?: boolean, + icon?: string + ): HTMLElement { + const menuButton = document.createElement('button') + menuButton.classList.add('flow-context-menu__item') + if (disabled) { + menuButton.classList.add('flow-context-menu__item-disabled') + } + + if (clickListener !== null) { + menuButton.addEventListener('click', clickListener, false) + } + + const iconItem = document.createElement('div') + iconItem.classList.add('flow-context-menu__item-icon') + if (icon) { + iconItem.style.backgroundImage = `url(${icon})` + } + menuButton.appendChild(iconItem) + + const buttonText = document.createElement('span') + buttonText.innerHTML = label + menuButton.appendChild(buttonText) + + // flow-context-menu__item-icon + this.element.appendChild(menuButton) + return menuButton + } +} diff --git a/demos/showcase/home-automation/flow-context-menu/contextMenuUtils.js b/demos/showcase/home-automation/flow-context-menu/contextMenuUtils.js new file mode 100644 index 000000000..a41681dbd --- /dev/null +++ b/demos/showcase/home-automation/flow-context-menu/contextMenuUtils.js @@ -0,0 +1,142 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + GraphComponent, + ICommand, + IModelItem, + INode, + PopulateItemContextMenuEventArgs +} from 'yfiles' +import { runAutoLayout } from '../utils/customTriggers.js' +import { FlowContextMenu } from './FlowContextMenu.js' +import CutIcon from 'demo-resources/icons/cut2-16.svg' +import PasteIcon from 'demo-resources/icons/paste-16.svg' +import CopyIcon from 'demo-resources/icons/copy-16.svg' +import DeleteIcon from 'demo-resources/icons/delete3-16.svg' +import RedoIcon from 'demo-resources/icons/redo-16.svg' +import UndoIcon from 'demo-resources/icons/undo-16.svg' +import FitContentIcon from 'demo-resources/icons/fit-16.svg' +import LayoutIcon from 'demo-resources/icons/play2-16.svg' + +/** + * Populates the context menu based on the item that was clicked. + * @param {!FlowContextMenu} contextMenu + * @param {!GraphComponent} graphComponent + * @param {!PopulateItemContextMenuEventArgs.} args + */ +export function populateContextMenu(contextMenu, graphComponent, args) { + // The 'showMenu' property is set to true to inform the input mode that we actually want to show a context menu + // for this item (or more generally, the location provided by the event args). + // If you don't want to show a context menu for some locations, set 'false' in these cases. + args.showMenu = true + + contextMenu.clearItems() + + const node = args.item + // If the cursor is over a node select it + updateSelection(graphComponent, node) + + // Create the context menu items + if (graphComponent.selection.selectedNodes.size > 0) { + contextMenu.addMenuItem( + 'Delete Node', + () => ICommand.DELETE.execute(null, graphComponent), + !ICommand.DELETE.canExecute(null, graphComponent), + DeleteIcon + ) + contextMenu.addSeparator() + contextMenu.addMenuItem( + 'Copy', + () => ICommand.COPY.execute(null, graphComponent), + !ICommand.COPY.canExecute(null, graphComponent), + CopyIcon + ) + contextMenu.addMenuItem( + 'Paste', + () => ICommand.PASTE.execute(args.queryLocation, graphComponent), + !ICommand.PASTE.canExecute(args.queryLocation, graphComponent), + PasteIcon + ) + contextMenu.addMenuItem( + 'Cut', + () => ICommand.CUT.execute(null, graphComponent), + !ICommand.CUT.canExecute(null, graphComponent), + CutIcon + ) + } else { + // no node has been hit + contextMenu.addMenuItem( + 'Paste', + () => ICommand.PASTE.execute(args.queryLocation, graphComponent), + !ICommand.PASTE.canExecute(args.queryLocation, graphComponent), + PasteIcon + ) + } + + contextMenu.addSeparator() + contextMenu.addMenuItem( + 'Undo', + () => ICommand.UNDO.execute(null, graphComponent), + !ICommand.UNDO.canExecute(null, graphComponent), + UndoIcon + ) + contextMenu.addMenuItem( + 'Redo', + () => ICommand.REDO.execute(null, graphComponent), + !ICommand.REDO.canExecute(null, graphComponent), + RedoIcon + ) + contextMenu.addSeparator() + contextMenu.addMenuItem( + 'Fit Content', + () => ICommand.FIT_CONTENT.execute(null, graphComponent), + !ICommand.FIT_CONTENT.canExecute(null, graphComponent), + FitContentIcon + ) + contextMenu.addMenuItem('Auto Layout', () => runAutoLayout(graphComponent), undefined, LayoutIcon) +} + +/** + * Updates the selection of the given node. + * @param {!GraphComponent} graphComponent + * @param {?INode} node + */ +function updateSelection(graphComponent, node) { + if (node === null) { + // clear the whole selection + graphComponent.selection.clear() + } else if (!graphComponent.selection.selectedNodes.isSelected(node)) { + // no - clear the remaining selection + graphComponent.selection.clear() + // and select the node + graphComponent.selection.selectedNodes.setSelected(node, true) + // also update the current item + graphComponent.currentItem = node + } +} diff --git a/demos/showcase/home-automation/flow-context-menu/contextMenuUtils.ts b/demos/showcase/home-automation/flow-context-menu/contextMenuUtils.ts new file mode 100644 index 000000000..c3d4532f3 --- /dev/null +++ b/demos/showcase/home-automation/flow-context-menu/contextMenuUtils.ts @@ -0,0 +1,141 @@ +/**************************************************************************** + ** @license + ** This demo file is part of yFiles for HTML 2.6. + ** Copyright (c) 2000-2023 by yWorks GmbH, Vor dem Kreuzberg 28, + ** 72070 Tuebingen, Germany. All rights reserved. + ** + ** yFiles demo files exhibit yFiles for HTML functionalities. Any redistribution + ** of demo files in source code or binary form, with or without + ** modification, is not permitted. + ** + ** Owners of a valid software license for a yFiles for HTML version that this + ** demo is shipped with are allowed to use the demo source code as basis + ** for their own yFiles for HTML powered applications. Use of such programs is + ** governed by the rights and conditions as set out in the yFiles for HTML + ** license agreement. + ** + ** THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED + ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + ** NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + ** TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ***************************************************************************/ +import { + GraphComponent, + ICommand, + IModelItem, + INode, + PopulateItemContextMenuEventArgs +} from 'yfiles' +import { runAutoLayout } from '../utils/customTriggers' +import { FlowContextMenu } from './FlowContextMenu' +import CutIcon from 'demo-resources/icons/cut2-16.svg' +import PasteIcon from 'demo-resources/icons/paste-16.svg' +import CopyIcon from 'demo-resources/icons/copy-16.svg' +import DeleteIcon from 'demo-resources/icons/delete3-16.svg' +import RedoIcon from 'demo-resources/icons/redo-16.svg' +import UndoIcon from 'demo-resources/icons/undo-16.svg' +import FitContentIcon from 'demo-resources/icons/fit-16.svg' +import LayoutIcon from 'demo-resources/icons/play2-16.svg' + +/** + * Populates the context menu based on the item that was clicked. + */ +export function populateContextMenu( + contextMenu: FlowContextMenu, + graphComponent: GraphComponent, + args: PopulateItemContextMenuEventArgs +): void { + // The 'showMenu' property is set to true to inform the input mode that we actually want to show a context menu + // for this item (or more generally, the location provided by the event args). + // If you don't want to show a context menu for some locations, set 'false' in these cases. + args.showMenu = true + + contextMenu.clearItems() + + const node = args.item + // If the cursor is over a node select it + updateSelection(graphComponent, node as INode) + + // Create the context menu items + if (graphComponent.selection.selectedNodes.size > 0) { + contextMenu.addMenuItem( + 'Delete Node', + () => ICommand.DELETE.execute(null, graphComponent), + !ICommand.DELETE.canExecute(null, graphComponent), + DeleteIcon + ) + contextMenu.addSeparator() + contextMenu.addMenuItem( + 'Copy', + () => ICommand.COPY.execute(null, graphComponent), + !ICommand.COPY.canExecute(null, graphComponent), + CopyIcon + ) + contextMenu.addMenuItem( + 'Paste', + () => ICommand.PASTE.execute(args.queryLocation, graphComponent), + !ICommand.PASTE.canExecute(args.queryLocation, graphComponent), + PasteIcon + ) + contextMenu.addMenuItem( + 'Cut', + () => ICommand.CUT.execute(null, graphComponent), + !ICommand.CUT.canExecute(null, graphComponent), + CutIcon + ) + } else { + // no node has been hit + contextMenu.addMenuItem( + 'Paste', + () => ICommand.PASTE.execute(args.queryLocation, graphComponent), + !ICommand.PASTE.canExecute(args.queryLocation, graphComponent), + PasteIcon + ) + } + + contextMenu.addSeparator() + contextMenu.addMenuItem( + 'Undo', + () => ICommand.UNDO.execute(null, graphComponent), + !ICommand.UNDO.canExecute(null, graphComponent), + UndoIcon + ) + contextMenu.addMenuItem( + 'Redo', + () => ICommand.REDO.execute(null, graphComponent), + !ICommand.REDO.canExecute(null, graphComponent), + RedoIcon + ) + contextMenu.addSeparator() + contextMenu.addMenuItem( + 'Fit Content', + () => ICommand.FIT_CONTENT.execute(null, graphComponent), + !ICommand.FIT_CONTENT.canExecute(null, graphComponent), + FitContentIcon + ) + contextMenu.addMenuItem('Auto Layout', () => runAutoLayout(graphComponent), undefined, LayoutIcon) +} + +/** + * Updates the selection of the given node. + */ +function updateSelection(graphComponent: GraphComponent, node: INode | null): void { + if (node === null) { + // clear the whole selection + graphComponent.selection.clear() + } else if (!graphComponent.selection.selectedNodes.isSelected(node)) { + // no - clear the remaining selection + graphComponent.selection.clear() + // and select the node + graphComponent.selection.selectedNodes.setSelected(node, true) + // also update the current item + graphComponent.currentItem = node + } +} diff --git a/demos/loading/amdloading/index.html b/demos/showcase/home-automation/index.html similarity index 54% rename from demos/loading/amdloading/index.html rename to demos/showcase/home-automation/index.html index 47cfb68f7..bf22257ef 100644 --- a/demos/loading/amdloading/index.html +++ b/demos/showcase/home-automation/index.html @@ -1,11 +1,9 @@ - + - - - Amd Loading Demo [yFiles for HTML] + Home Automation Demo [yFiles for HTML] + + + + - +
yFiles for HTML Demos - AMD Loading + Home Automation yFiles Demos
@@ -72,14 +72,12 @@
- Step 1/14 — Tutorial: Graph Builder + Step 1/13 — 01 Create Graph
diff --git a/demos/tutorial-graph-builder/02-create-nodes-sources/README.md b/demos/tutorial-graph-builder/02-create-nodes-sources/README.md index 188499306..b2782d3ee 100644 --- a/demos/tutorial-graph-builder/02-create-nodes-sources/README.md +++ b/demos/tutorial-graph-builder/02-create-nodes-sources/README.md @@ -31,7 +31,10 @@ In our examples, we add a property named `id` to the business data. const nodeData = [{ id: '00' }, { id: '01' }, { id: '02' }] // nodes source for the turquoise nodes -const nodesSource = graphBuilder.createNodesSource(nodeData, item => item.id) +const nodesSource = graphBuilder.createNodesSource( + nodeData, + (item) => item.id +) ``` Another one is the **[IEnumerable](https://docs.yworks.com/yfileshtml/#/api/IEnumerable)**. The [id provider](https://docs.yworks.com/yfileshtml/#/api/NodesSource#NodesSource-property-idProvider) function retrieves the identifiers for the nodes. @@ -42,7 +45,10 @@ Another one is the **[IEnumerable](https://docs.yworks.com/yfileshtml/#/api/I const nodeData = IEnumerable.from([{ id: '10' }, { id: '11' }, { id: '12' }]) // nodes source for the blue nodes -const nodesSource = graphBuilder.createNodesSource(nodeData, item => item.id) +const nodesSource = graphBuilder.createNodesSource( + nodeData, + (item) => item.id +) ``` If you have organized the data in a **[Map](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)** for easy and fast access by a `key`, you can pass it to [GraphBuilder](https://docs.yworks.com/yfileshtml/#/api/GraphBuilder) as is. The map key of an item is passed to the [id provider](https://docs.yworks.com/yfileshtml/#/api/NodesSource#NodesSource-property-idProvider) function as a second parameter and can be used as the identifier or a part of it. @@ -88,7 +94,7 @@ function* nodes(): Generator<{ id: string }> { } // nodes source for the brown nodes -const nodesSource = graphBuilder.createNodesSource(nodes, item => item.id) +const nodesSource = graphBuilder.createNodesSource(nodes, (item) => item.id) ``` Note diff --git a/demos/tutorial-graph-builder/02-create-nodes-sources/create-nodes-sources.js b/demos/tutorial-graph-builder/02-create-nodes-sources/create-nodes-sources.js index 60ed729cc..12270d4d0 100644 --- a/demos/tutorial-graph-builder/02-create-nodes-sources/create-nodes-sources.js +++ b/demos/tutorial-graph-builder/02-create-nodes-sources/create-nodes-sources.js @@ -36,7 +36,7 @@ export function createNodesSourceFromArray(graphBuilder) { const nodeData = [{ id: '00' }, { id: '01' }, { id: '02' }] // nodes source for the turquoise nodes - const nodesSource = graphBuilder.createNodesSource(nodeData, item => item.id) + const nodesSource = graphBuilder.createNodesSource(nodeData, (item) => item.id) return nodesSource } @@ -49,7 +49,7 @@ export function createNodesSourceFromIEnumerable(graphBuilder) { const nodeData = IEnumerable.from([{ id: '10' }, { id: '11' }, { id: '12' }]) // nodes source for the blue nodes - const nodesSource = graphBuilder.createNodesSource(nodeData, item => item.id) + const nodesSource = graphBuilder.createNodesSource(nodeData, (item) => item.id) return nodesSource } @@ -100,7 +100,7 @@ export function createNodesSourceFromGenerator(graphBuilder) { } // nodes source for the brown nodes - const nodesSource = graphBuilder.createNodesSource(nodes, item => item.id) + const nodesSource = graphBuilder.createNodesSource(nodes, (item) => item.id) return nodesSource } diff --git a/demos/tutorial-graph-builder/02-create-nodes-sources/create-nodes-sources.ts b/demos/tutorial-graph-builder/02-create-nodes-sources/create-nodes-sources.ts index 505818a95..6be30c1b5 100644 --- a/demos/tutorial-graph-builder/02-create-nodes-sources/create-nodes-sources.ts +++ b/demos/tutorial-graph-builder/02-create-nodes-sources/create-nodes-sources.ts @@ -34,7 +34,10 @@ export function createNodesSourceFromArray( const nodeData = [{ id: '00' }, { id: '01' }, { id: '02' }] // nodes source for the turquoise nodes - const nodesSource = graphBuilder.createNodesSource(nodeData, item => item.id) + const nodesSource = graphBuilder.createNodesSource( + nodeData, + (item) => item.id + ) return nodesSource } @@ -45,7 +48,10 @@ export function createNodesSourceFromIEnumerable( const nodeData = IEnumerable.from([{ id: '10' }, { id: '11' }, { id: '12' }]) // nodes source for the blue nodes - const nodesSource = graphBuilder.createNodesSource(nodeData, item => item.id) + const nodesSource = graphBuilder.createNodesSource( + nodeData, + (item) => item.id + ) return nodesSource } @@ -96,7 +102,7 @@ export function createNodesSourceFromGenerator( } // nodes source for the brown nodes - const nodesSource = graphBuilder.createNodesSource(nodes, item => item.id) + const nodesSource = graphBuilder.createNodesSource(nodes, (item) => item.id) return nodesSource } diff --git a/demos/tutorial-graph-builder/02-create-nodes-sources/index.html b/demos/tutorial-graph-builder/02-create-nodes-sources/index.html index 2f6e51e27..96a57cc2b 100644 --- a/demos/tutorial-graph-builder/02-create-nodes-sources/index.html +++ b/demos/tutorial-graph-builder/02-create-nodes-sources/index.html @@ -1,4 +1,4 @@ - + @@ -73,12 +73,10 @@
@@ -217,7 +218,10 @@

Import nodes from an iterable

>const nodeData = IEnumerable.from([{ id: '10' }, { id: '11' }, { id: '12' }]) // nodes source for the blue nodes -const nodesSource = graphBuilder.createNodesSource(nodeData, item => item.id) +const nodesSource = graphBuilder.createNodesSource( + nodeData, + (item) => item.id +)
@@ -302,7 +306,7 @@

} // nodes source for the brown nodes -const nodesSource = graphBuilder.createNodesSource(nodes, item => item.id) +const nodesSource = graphBuilder.createNodesSource(nodes, (item) => item.id)

diff --git a/demos/tutorial-graph-builder/03-create-edges-sources/README.md b/demos/tutorial-graph-builder/03-create-edges-sources/README.md index 7fdf0cdc1..29d3587ac 100644 --- a/demos/tutorial-graph-builder/03-create-edges-sources/README.md +++ b/demos/tutorial-graph-builder/03-create-edges-sources/README.md @@ -39,9 +39,9 @@ const edgeData = [ // create an edges source with id providers for sources and targets const edgesSource = graphBuilder.createEdgesSource({ data: edgeData, - id: item => item.id, - sourceId: item => item.sourceId, - targetId: item => item.targetId + id: (item) => item.id, + sourceId: (item) => item.sourceId, + targetId: (item) => item.targetId }) ``` diff --git a/demos/tutorial-graph-builder/03-create-edges-sources/create-edges-sources.js b/demos/tutorial-graph-builder/03-create-edges-sources/create-edges-sources.js index 848a61886..c31c0c30f 100644 --- a/demos/tutorial-graph-builder/03-create-edges-sources/create-edges-sources.js +++ b/demos/tutorial-graph-builder/03-create-edges-sources/create-edges-sources.js @@ -44,9 +44,9 @@ export function createEdgesSourceFromArray(graphBuilder) { // create an edges source with id providers for sources and targets const edgesSource = graphBuilder.createEdgesSource({ data: edgeData, - id: item => item.id, - sourceId: item => item.sourceId, - targetId: item => item.targetId + id: (item) => item.id, + sourceId: (item) => item.sourceId, + targetId: (item) => item.targetId }) return edgesSource diff --git a/demos/tutorial-graph-builder/03-create-edges-sources/create-edges-sources.ts b/demos/tutorial-graph-builder/03-create-edges-sources/create-edges-sources.ts index 882738666..e9a8e1235 100644 --- a/demos/tutorial-graph-builder/03-create-edges-sources/create-edges-sources.ts +++ b/demos/tutorial-graph-builder/03-create-edges-sources/create-edges-sources.ts @@ -48,9 +48,9 @@ export function createEdgesSourceFromArray( // create an edges source with id providers for sources and targets const edgesSource = graphBuilder.createEdgesSource({ data: edgeData, - id: item => item.id, - sourceId: item => item.sourceId, - targetId: item => item.targetId + id: (item) => item.id, + sourceId: (item) => item.sourceId, + targetId: (item) => item.targetId }) return edgesSource diff --git a/demos/tutorial-graph-builder/03-create-edges-sources/index.html b/demos/tutorial-graph-builder/03-create-edges-sources/index.html index 18bdf7791..64bda9c87 100644 --- a/demos/tutorial-graph-builder/03-create-edges-sources/index.html +++ b/demos/tutorial-graph-builder/03-create-edges-sources/index.html @@ -1,4 +1,4 @@ - + @@ -73,12 +73,10 @@ diff --git a/demos/tutorial-graph-builder/04-group-nodes/README.md b/demos/tutorial-graph-builder/04-group-nodes/README.md index 3c2a0a43d..473d970ec 100644 --- a/demos/tutorial-graph-builder/04-group-nodes/README.md +++ b/demos/tutorial-graph-builder/04-group-nodes/README.md @@ -39,15 +39,15 @@ You can use `GraphBuilder.createGroupNodes` to import the group nodes data. Then ``` const nodesSource = graphBuilder.createNodesSource({ - data: nodeData.filter(item => item.id.startsWith('item')), - id: item => item.id, - parentId: item => item.parentId + data: nodeData.filter((item) => item.id.startsWith('item')), + id: (item) => item.id, + parentId: (item) => item.parentId }) const groupNodesSource = graphBuilder.createGroupNodesSource({ - data: nodeData.filter(item => item.id.startsWith('group')), - id: item => item.id, - parentId: item => item.parentId + data: nodeData.filter((item) => item.id.startsWith('group')), + id: (item) => item.id, + parentId: (item) => item.parentId }) ``` diff --git a/demos/tutorial-graph-builder/04-group-nodes/create-group-nodes.js b/demos/tutorial-graph-builder/04-group-nodes/create-group-nodes.js index 88d440515..c543a55b0 100644 --- a/demos/tutorial-graph-builder/04-group-nodes/create-group-nodes.js +++ b/demos/tutorial-graph-builder/04-group-nodes/create-group-nodes.js @@ -39,14 +39,14 @@ export function createGroupNodes(graphBuilder) { ] const nodesSource = graphBuilder.createNodesSource({ - data: nodeData.filter(item => item.id.startsWith('item')), - id: item => item.id, - parentId: item => item.parentId + data: nodeData.filter((item) => item.id.startsWith('item')), + id: (item) => item.id, + parentId: (item) => item.parentId }) const groupNodesSource = graphBuilder.createGroupNodesSource({ - data: nodeData.filter(item => item.id.startsWith('group')), - id: item => item.id, - parentId: item => item.parentId + data: nodeData.filter((item) => item.id.startsWith('group')), + id: (item) => item.id, + parentId: (item) => item.parentId }) } diff --git a/demos/tutorial-graph-builder/04-group-nodes/create-group-nodes.ts b/demos/tutorial-graph-builder/04-group-nodes/create-group-nodes.ts index f5492f918..f40e7b69c 100644 --- a/demos/tutorial-graph-builder/04-group-nodes/create-group-nodes.ts +++ b/demos/tutorial-graph-builder/04-group-nodes/create-group-nodes.ts @@ -38,14 +38,14 @@ export function createGroupNodes(graphBuilder: GraphBuilder): void { ] const nodesSource = graphBuilder.createNodesSource({ - data: nodeData.filter(item => item.id.startsWith('item')), - id: item => item.id, - parentId: item => item.parentId + data: nodeData.filter((item) => item.id.startsWith('item')), + id: (item) => item.id, + parentId: (item) => item.parentId }) const groupNodesSource = graphBuilder.createGroupNodesSource({ - data: nodeData.filter(item => item.id.startsWith('group')), - id: item => item.id, - parentId: item => item.parentId + data: nodeData.filter((item) => item.id.startsWith('group')), + id: (item) => item.id, + parentId: (item) => item.parentId }) } diff --git a/demos/tutorial-graph-builder/04-group-nodes/index.html b/demos/tutorial-graph-builder/04-group-nodes/index.html index dc249acc1..221ae39da 100644 --- a/demos/tutorial-graph-builder/04-group-nodes/index.html +++ b/demos/tutorial-graph-builder/04-group-nodes/index.html @@ -1,4 +1,4 @@ - + @@ -73,12 +73,10 @@ diff --git a/demos/tutorial-graph-builder/05-implicit-grouping/README.md b/demos/tutorial-graph-builder/05-implicit-grouping/README.md index 9ebb38b11..dee0e39b8 100644 --- a/demos/tutorial-graph-builder/05-implicit-grouping/README.md +++ b/demos/tutorial-graph-builder/05-implicit-grouping/README.md @@ -93,7 +93,7 @@ const nodesSource = graphBuilder.createNodesSource({ In the next step, we describe how to navigate from an entity to its path container ``` -const parentsSource = nodesSource.createParentNodesSource(item => item.path) +const parentsSource = nodesSource.createParentNodesSource((item) => item.path) ``` To ascend further in the hierarchy, we add another parent source on top and enable recursion by augmenting that source. For the higher levels, we strip the last path entry from the existing path and terminate when we don’t have any further levels to ascend to. diff --git a/demos/tutorial-graph-builder/05-implicit-grouping/implicit-group-nodes.js b/demos/tutorial-graph-builder/05-implicit-grouping/implicit-group-nodes.js index 8eec7440c..7f3e116ab 100644 --- a/demos/tutorial-graph-builder/05-implicit-grouping/implicit-group-nodes.js +++ b/demos/tutorial-graph-builder/05-implicit-grouping/implicit-group-nodes.js @@ -34,7 +34,7 @@ import { edgeData, nodeData } from './group-data.js' */ export function createGroupNodes(graphBuilder) { // Create the initial set of nodes that correspond to the top level entries in the NodeData array - const idProvider = item => item.id + const idProvider = (item) => item.id const nodesSource = graphBuilder.createNodesSource({ data: nodeData, id: idProvider @@ -43,21 +43,21 @@ export function createGroupNodes(graphBuilder) { nodesSource.nodeCreator.defaults.size = new Size(60, 40) // Describe how to create the first level of groups from the items in the NodeData - const parentsSource = nodesSource.createParentNodesSource(item => item.path) - parentsSource.nodeCreator.createLabelBinding(data => data) + const parentsSource = nodesSource.createParentNodesSource((item) => item.path) + parentsSource.nodeCreator.createLabelBinding((data) => data) // Describe how to navigate higher up in the hierarchy - const parentDataProvider = path => { + const parentDataProvider = (path) => { const separator = path.lastIndexOf('/') return separator === 0 ? null : path.substring(0, separator) } const ancestorSource = parentsSource.createParentNodesSource(parentDataProvider) // Enable recursive processing higher up in the container hierarchy ancestorSource.addParentNodesSource(parentDataProvider, ancestorSource) - ancestorSource.nodeCreator.createLabelBinding(data => data) + ancestorSource.nodeCreator.createLabelBinding((data) => data) // Enable processing of the contents of the nodes in the NodeData - const childDataProvider = item => item.children ?? [] + const childDataProvider = (item) => item.children ?? [] const childNodesSource = nodesSource.createChildNodesSource(childDataProvider, idProvider) // Enable processing of the contents of the child nodes const descendantsSource = childNodesSource.createChildNodesSource(childDataProvider, idProvider) @@ -67,8 +67,8 @@ export function createGroupNodes(graphBuilder) { // Declare edges between all different kinds of entities const edgesSource = graphBuilder.createEdgesSource({ data: edgeData, - sourceId: item => item.from, - targetId: item => item.to + sourceId: (item) => item.from, + targetId: (item) => item.to }) // Styling for the group nodes @@ -88,7 +88,7 @@ export function createGroupNodes(graphBuilder) { nodesSource.nodeCreator.styleProvider = // Since the NodeData and all the ChildNodes can possibly be either group nodes // or normal nodes, styling should be done via a style provider - item => + (item) => item.children && item.children.length > 0 ? graph.groupNodeDefaults.style : graph.nodeDefaults.style diff --git a/demos/tutorial-graph-builder/05-implicit-grouping/implicit-group-nodes.ts b/demos/tutorial-graph-builder/05-implicit-grouping/implicit-group-nodes.ts index 061c83c13..79d26684c 100644 --- a/demos/tutorial-graph-builder/05-implicit-grouping/implicit-group-nodes.ts +++ b/demos/tutorial-graph-builder/05-implicit-grouping/implicit-group-nodes.ts @@ -42,8 +42,8 @@ export function createGroupNodes(graphBuilder: GraphBuilder): void { nodesSource.nodeCreator.defaults.size = new Size(60, 40) // Describe how to create the first level of groups from the items in the NodeData - const parentsSource = nodesSource.createParentNodesSource(item => item.path) - parentsSource.nodeCreator.createLabelBinding(data => data) + const parentsSource = nodesSource.createParentNodesSource((item) => item.path) + parentsSource.nodeCreator.createLabelBinding((data) => data) // Describe how to navigate higher up in the hierarchy const parentDataProvider = (path: string | null): string | null => { @@ -54,7 +54,7 @@ export function createGroupNodes(graphBuilder: GraphBuilder): void { parentsSource.createParentNodesSource(parentDataProvider) // Enable recursive processing higher up in the container hierarchy ancestorSource.addParentNodesSource(parentDataProvider, ancestorSource) - ancestorSource.nodeCreator.createLabelBinding(data => data) + ancestorSource.nodeCreator.createLabelBinding((data) => data) // Enable processing of the contents of the nodes in the NodeData const childDataProvider = (item: ChildData | ItemData): ChildData[] => @@ -74,8 +74,8 @@ export function createGroupNodes(graphBuilder: GraphBuilder): void { // Declare edges between all different kinds of entities const edgesSource = graphBuilder.createEdgesSource({ data: edgeData, - sourceId: item => item.from, - targetId: item => item.to + sourceId: (item) => item.from, + targetId: (item) => item.to }) // Styling for the group nodes diff --git a/demos/tutorial-graph-builder/05-implicit-grouping/index.html b/demos/tutorial-graph-builder/05-implicit-grouping/index.html index 6babe6fcc..473dd2a13 100644 --- a/demos/tutorial-graph-builder/05-implicit-grouping/index.html +++ b/demos/tutorial-graph-builder/05-implicit-grouping/index.html @@ -1,4 +1,4 @@ - + @@ -73,12 +73,10 @@
- Step 6/14Step 5/13 — 05 Implicit Grouping
diff --git a/demos/tutorial-graph-builder/06-configure-styles/configure-styles.js b/demos/tutorial-graph-builder/06-configure-styles/configure-styles.js index fb1bca7d6..4c840154e 100644 --- a/demos/tutorial-graph-builder/06-configure-styles/configure-styles.js +++ b/demos/tutorial-graph-builder/06-configure-styles/configure-styles.js @@ -69,7 +69,7 @@ export function configureStylesWithBinding(nodesSource) { // disable sharing of styles nodesSource.nodeCreator.defaults.shareStyleInstance = false - nodesSource.nodeCreator.styleBindings.addBinding('stroke', entityData => { + nodesSource.nodeCreator.styleBindings.addBinding('stroke', (entityData) => { return new Stroke({ fill: entityData.currency === 'EUR' ? darkBlue : red, thickness: 3 @@ -83,7 +83,7 @@ export function configureStylesWithBinding(nodesSource) { export function configureStylesWithProvider(nodesSource) { const gold = '#F0C808' const green = '#56926E' - nodesSource.nodeCreator.styleProvider = entityData => { + nodesSource.nodeCreator.styleProvider = (entityData) => { if (entityData.type === 'Branch') { return new ShapeNodeStyle({ shape: 'round-rectangle', @@ -104,7 +104,7 @@ export function configureStylesWithProvider(nodesSource) { export function configureEdgeStylesWithProvider(edgesSource) { const red = '#DB3A34' const gray = '#C1C1C1' - edgesSource.edgeCreator.styleProvider = connectionData => { + edgesSource.edgeCreator.styleProvider = (connectionData) => { if (connectionData.ownership) { return new PolylineEdgeStyle({ stroke: new Stroke({ diff --git a/demos/tutorial-graph-builder/06-configure-styles/index.html b/demos/tutorial-graph-builder/06-configure-styles/index.html index 110448891..7cb3d7d43 100644 --- a/demos/tutorial-graph-builder/06-configure-styles/index.html +++ b/demos/tutorial-graph-builder/06-configure-styles/index.html @@ -1,4 +1,4 @@ - + @@ -73,12 +73,10 @@
- Step 7/14Step 6/13 — 06 Configure Styles
Tutorial: Graph Builder01 Create Graph data.name) +nodesSource.nodeCreator.createLabelBinding((data) => data.name) ``` The label binding can also be set explicitly using the [text provider](https://docs.yworks.com/yfileshtml/#/api/LabelCreator#LabelCreator-property-textProvider) property. In this example, the label text is converted to upper-case to show that text can be augmented. @@ -67,7 +67,7 @@ In this case, the provider which is given as parameter is expected to return an ``` // create the label sources based on the `owners` property const labelsSource = nodesSource.nodeCreator.createLabelsSource( - data => data.owners + (data) => data.owners ) ``` @@ -100,7 +100,9 @@ const edgesSource = graphBuilder.createEdgesSource( ) // bind the label text data and add some more text information -edgesSource.edgeCreator.createLabelBinding(data => `Owns ${data.ownership}%`) +edgesSource.edgeCreator.createLabelBinding( + (data) => `Owns ${data.ownership}%` +) ``` Note diff --git a/demos/tutorial-graph-builder/07-create-labels-sources/create-labels-sources.js b/demos/tutorial-graph-builder/07-create-labels-sources/create-labels-sources.js index 6c1caba94..56ee76839 100644 --- a/demos/tutorial-graph-builder/07-create-labels-sources/create-labels-sources.js +++ b/demos/tutorial-graph-builder/07-create-labels-sources/create-labels-sources.js @@ -39,7 +39,7 @@ export function createNodeLabelsWithBinding(graphBuilder) { const nodesSource = graphBuilder.createNodesSource(nodeData, 'id') // create the label binding to the name property - nodesSource.nodeCreator.createLabelBinding(data => data.name) + nodesSource.nodeCreator.createLabelBinding((data) => data.name) } /** * @param {!GraphBuilder} graphBuilder @@ -50,7 +50,7 @@ export function createNodeLabelsWithProvider(graphBuilder) { // create the text provider that will return the name of each node const labelCreator = nodesSource.nodeCreator.createLabelBinding() - labelCreator.textProvider = data => data.name.toUpperCase() + labelCreator.textProvider = (data) => data.name.toUpperCase() } /** * @param {!GraphBuilder} graphBuilder @@ -63,8 +63,8 @@ export function createNodeLabelsWithSources(graphBuilder) { const nodesSource = graphBuilder.createNodesSource(nodeData, 'id') // create the label sources based on the `owners` property - const labelsSource = nodesSource.nodeCreator.createLabelsSource(data => data.owners) - labelsSource.labelCreator.layoutParameterProvider = data => + const labelsSource = nodesSource.nodeCreator.createLabelsSource((data) => data.owners) + labelsSource.labelCreator.layoutParameterProvider = (data) => data.endsWith('Group') ? InteriorLabelModel.CENTER : InteriorLabelModel.SOUTH } /** @@ -90,5 +90,5 @@ export function createEdgeLabelsWithProvider(graphBuilder) { const edgesSource = graphBuilder.createEdgesSource(edgeData, 'sourceId', 'targetId', 'id') // bind the label text data and add some more text information - edgesSource.edgeCreator.createLabelBinding(data => `Owns ${data.ownership}%`) + edgesSource.edgeCreator.createLabelBinding((data) => `Owns ${data.ownership}%`) } diff --git a/demos/tutorial-graph-builder/07-create-labels-sources/create-labels-sources.ts b/demos/tutorial-graph-builder/07-create-labels-sources/create-labels-sources.ts index 836aa1240..4f01fa9f1 100644 --- a/demos/tutorial-graph-builder/07-create-labels-sources/create-labels-sources.ts +++ b/demos/tutorial-graph-builder/07-create-labels-sources/create-labels-sources.ts @@ -37,7 +37,7 @@ export function createNodeLabelsWithBinding(graphBuilder: GraphBuilder): void { const nodesSource = graphBuilder.createNodesSource(nodeData, 'id') // create the label binding to the name property - nodesSource.nodeCreator.createLabelBinding(data => data.name) + nodesSource.nodeCreator.createLabelBinding((data) => data.name) } export function createNodeLabelsWithProvider(graphBuilder: GraphBuilder): void { const nodeData = [{ id: '2', name: 'Monster Inc' }] @@ -56,7 +56,7 @@ export function createNodeLabelsWithSources(graphBuilder: GraphBuilder): void { // create the label sources based on the `owners` property const labelsSource = nodesSource.nodeCreator.createLabelsSource( - data => data.owners + (data) => data.owners ) labelsSource.labelCreator.layoutParameterProvider = ( data @@ -90,5 +90,7 @@ export function createEdgeLabelsWithProvider(graphBuilder: GraphBuilder): void { ) // bind the label text data and add some more text information - edgesSource.edgeCreator.createLabelBinding(data => `Owns ${data.ownership}%`) + edgesSource.edgeCreator.createLabelBinding( + (data) => `Owns ${data.ownership}%` + ) } diff --git a/demos/tutorial-graph-builder/07-create-labels-sources/index.html b/demos/tutorial-graph-builder/07-create-labels-sources/index.html index ae49f5a49..d8ed8f132 100644 --- a/demos/tutorial-graph-builder/07-create-labels-sources/index.html +++ b/demos/tutorial-graph-builder/07-create-labels-sources/index.html @@ -1,4 +1,4 @@ - + @@ -73,12 +73,10 @@
@@ -252,7 +250,7 @@

class="highlight" >// create the label sources based on the `owners` property const labelsSource = nodesSource.nodeCreator.createLabelsSource( - data => data.owners + (data) => data.owners )

@@ -294,7 +292,9 @@

Loading edge label information

) // bind the label text data and add some more text information -edgesSource.edgeCreator.createLabelBinding(data => `Owns ${data.ownership}%`) +edgesSource.edgeCreator.createLabelBinding( + (data) => `Owns ${data.ownership}%` +)
diff --git a/demos/tutorial-graph-builder/08-configure-labels/README.md b/demos/tutorial-graph-builder/08-configure-labels/README.md index 914cef66c..415d36876 100644 --- a/demos/tutorial-graph-builder/08-configure-labels/README.md +++ b/demos/tutorial-graph-builder/08-configure-labels/README.md @@ -139,7 +139,7 @@ typeLabelCreator.preferredSizeProvider = (data): Size => data.type === 'Trust' ? new Size(70, 15) : new Size(100, 15) // set different widths for nodes with type 'Trust' -typeLabelCreator.preferredSizeBindings.addBinding('width', data => { +typeLabelCreator.preferredSizeBindings.addBinding('width', (data) => { return data.type === 'Trust' ? 200 : 100 }) ``` @@ -157,7 +157,7 @@ edgeLabelCreator.layoutParameterProvider = (): ILabelModelParameter => // configure its style edgeLabelCreator.defaults.shareStyleInstance = false -edgeLabelCreator.styleBindings.addBinding('textFill', data => { +edgeLabelCreator.styleBindings.addBinding('textFill', (data) => { return (data.ownership ?? 0) > 50 ? red : grey }) ``` diff --git a/demos/tutorial-graph-builder/08-configure-labels/configure-labels.js b/demos/tutorial-graph-builder/08-configure-labels/configure-labels.js index 41e333a7d..201d549f3 100644 --- a/demos/tutorial-graph-builder/08-configure-labels/configure-labels.js +++ b/demos/tutorial-graph-builder/08-configure-labels/configure-labels.js @@ -79,7 +79,7 @@ export function configureLabelStylingWithProvider(nameLabelCreator) { // disable the sharing of the label style nameLabelCreator.defaults.shareStyleInstance = false // create a provider that will assign a new style, based on the type property - nameLabelCreator.styleProvider = data => { + nameLabelCreator.styleProvider = (data) => { if (data.type === 'Corporation') { return new DefaultLabelStyle({ backgroundFill: orange, @@ -104,7 +104,7 @@ export function configureLabelStylingWithProvider(nameLabelCreator) { */ export function configureLabelSizeWithProvider(typeLabelCreator) { // set a new size for the labels with type 'Trust' - typeLabelCreator.preferredSizeProvider = data => + typeLabelCreator.preferredSizeProvider = (data) => data.type === 'Trust' ? new Size(70, 15) : new Size(100, 15) } @@ -114,7 +114,7 @@ export function configureLabelSizeWithProvider(typeLabelCreator) { export function configureLabelSizeWithBinding(typeLabelCreator) { // set different widths for nodes with type 'Trust' - typeLabelCreator.preferredSizeBindings.addBinding('width', data => { + typeLabelCreator.preferredSizeBindings.addBinding('width', (data) => { return data.type === 'Trust' ? 200 : 100 }) } @@ -124,7 +124,7 @@ export function configureLabelSizeWithBinding(typeLabelCreator) { */ export function configureEdgeLabels(edgesSource) { // bind the label text data and add some more text information - const edgeLabelCreator = edgesSource.edgeCreator.createLabelBinding(data => + const edgeLabelCreator = edgesSource.edgeCreator.createLabelBinding((data) => data.ownership ?? 0 ? `Owns ${data.ownership}%` : '' ) const red = '#ab2346' @@ -137,7 +137,7 @@ export function configureEdgeLabels(edgesSource) { // configure its style edgeLabelCreator.defaults.shareStyleInstance = false - edgeLabelCreator.styleBindings.addBinding('textFill', data => { + edgeLabelCreator.styleBindings.addBinding('textFill', (data) => { return (data.ownership ?? 0) > 50 ? red : grey }) } @@ -220,7 +220,7 @@ export function createEdgesSource(graphBuilder) { * @returns {!LabelCreator.} */ export function createLabelsForName(nodesSource) { - const labelCreator = nodesSource.nodeCreator.createLabelBinding(data => data.name) + const labelCreator = nodesSource.nodeCreator.createLabelBinding((data) => data.name) return labelCreator } @@ -230,6 +230,6 @@ export function createLabelsForName(nodesSource) { */ export function createLabelsForType(nodesSource) { const labelCreator = nodesSource.nodeCreator.createLabelBinding() - labelCreator.textProvider = data => data.type + labelCreator.textProvider = (data) => data.type return labelCreator } diff --git a/demos/tutorial-graph-builder/08-configure-labels/configure-labels.ts b/demos/tutorial-graph-builder/08-configure-labels/configure-labels.ts index 2dca6530d..53abfebd3 100644 --- a/demos/tutorial-graph-builder/08-configure-labels/configure-labels.ts +++ b/demos/tutorial-graph-builder/08-configure-labels/configure-labels.ts @@ -115,7 +115,7 @@ export function configureLabelSizeWithBinding( ): void { // set different widths for nodes with type 'Trust' - typeLabelCreator.preferredSizeBindings.addBinding('width', data => { + typeLabelCreator.preferredSizeBindings.addBinding('width', (data) => { return data.type === 'Trust' ? 200 : 100 }) } @@ -124,7 +124,7 @@ export function configureEdgeLabels( edgesSource: EdgesSource ): void { // bind the label text data and add some more text information - const edgeLabelCreator = edgesSource.edgeCreator.createLabelBinding(data => + const edgeLabelCreator = edgesSource.edgeCreator.createLabelBinding((data) => data.ownership ?? 0 ? `Owns ${data.ownership}%` : '' ) const red = '#ab2346' @@ -137,7 +137,7 @@ export function configureEdgeLabels( // configure its style edgeLabelCreator.defaults.shareStyleInstance = false - edgeLabelCreator.styleBindings.addBinding('textFill', data => { + edgeLabelCreator.styleBindings.addBinding('textFill', (data) => { return (data.ownership ?? 0) > 50 ? red : grey }) } @@ -223,7 +223,7 @@ export function createLabelsForName( nodesSource: NodesSource ): LabelCreator { const labelCreator = nodesSource.nodeCreator.createLabelBinding( - data => data.name + (data) => data.name ) return labelCreator } diff --git a/demos/tutorial-graph-builder/08-configure-labels/index.html b/demos/tutorial-graph-builder/08-configure-labels/index.html index 5920201d1..f9d6f0680 100644 --- a/demos/tutorial-graph-builder/08-configure-labels/index.html +++ b/demos/tutorial-graph-builder/08-configure-labels/index.html @@ -1,4 +1,4 @@ - + @@ -73,12 +73,10 @@ diff --git a/demos/tutorial-graph-builder/09-configure-tags/app.js b/demos/tutorial-graph-builder/09-configure-tags/app.js index c581b4b05..9596c91a5 100644 --- a/demos/tutorial-graph-builder/09-configure-tags/app.js +++ b/demos/tutorial-graph-builder/09-configure-tags/app.js @@ -52,7 +52,7 @@ createNodeTags(nodesSource) graphBuilder.createEdgesSource(data.edgesSource, 'sourceId', 'targetId', 'id') -nodesSource.nodeCreator.createLabelsSource(data => [data.name]) +nodesSource.nodeCreator.createLabelsSource((data) => [data.name]) graphBuilder.buildGraph() diff --git a/demos/tutorial-graph-builder/09-configure-tags/configure-tags.js b/demos/tutorial-graph-builder/09-configure-tags/configure-tags.js index e435b7864..b43f77deb 100644 --- a/demos/tutorial-graph-builder/09-configure-tags/configure-tags.js +++ b/demos/tutorial-graph-builder/09-configure-tags/configure-tags.js @@ -52,7 +52,7 @@ */ export function createNodeTags(nodesSource) { // configure the provider that returns an object with the name and the type property of the nodes - nodesSource.nodeCreator.tagProvider = data => { + nodesSource.nodeCreator.tagProvider = (data) => { return { name: data.name, type: data.type } } } diff --git a/demos/tutorial-graph-builder/09-configure-tags/index.html b/demos/tutorial-graph-builder/09-configure-tags/index.html index 762fe433f..865c2061e 100644 --- a/demos/tutorial-graph-builder/09-configure-tags/index.html +++ b/demos/tutorial-graph-builder/09-configure-tags/index.html @@ -1,4 +1,4 @@ - + @@ -73,12 +73,10 @@
- Step 10/14Step 9/13 — 09 Configure Tags
Tutorial: Graph Builder01 Create Graph data.layout +nodeSource.nodeCreator.layoutProvider = (data) => data.layout ``` Consider, for example, the dataset below. It only contains a property for the y-coordinate of the location. @@ -63,7 +63,10 @@ To use this y-coordinate and assign the same x-coordinate to all nodes, use a la ``` // create some binding for the x, y, width and height properties of the layout nodeSources.nodeCreator.layoutBindings.addBinding('x', () => 250) -nodeSources.nodeCreator.layoutBindings.addBinding('y', data => data.locationY) +nodeSources.nodeCreator.layoutBindings.addBinding( + 'y', + (data) => data.locationY +) nodeSources.nodeCreator.layoutBindings.addBinding('width', () => 50) nodeSources.nodeCreator.layoutBindings.addBinding('height', () => 30) ``` @@ -99,7 +102,7 @@ Transfer the bend information to the [edge layout](https://docs.yworks.com/yfile ``` // configure the bend provider to return the location of each bend point -edgeSources.edgeCreator.bendsProvider = data => data.bends +edgeSources.edgeCreator.bendsProvider = (data) => data.bends ``` Note diff --git a/demos/tutorial-graph-builder/10-configure-layout/configure-layout.js b/demos/tutorial-graph-builder/10-configure-layout/configure-layout.js index 5f13bf750..c4aa9010a 100644 --- a/demos/tutorial-graph-builder/10-configure-layout/configure-layout.js +++ b/demos/tutorial-graph-builder/10-configure-layout/configure-layout.js @@ -61,7 +61,7 @@ export function configureNodeLayoutWithProvider(graphBuilder) { // create the node using the id property const nodeSource = graphBuilder.createNodesSource(nodeData, 'id') // configure the layout provider that returns the layout information - nodeSource.nodeCreator.layoutProvider = data => data.layout + nodeSource.nodeCreator.layoutProvider = (data) => data.layout return nodeSource } @@ -93,7 +93,7 @@ export function configureBends(graphBuilder) { // create the edges using the sourceId/targetId const edgeSources = graphBuilder.createEdgesSource(edgeData, 'sourceId', 'targetId', 'id') // configure the bend provider to return the location of each bend point - edgeSources.edgeCreator.bendsProvider = data => data.bends + edgeSources.edgeCreator.bendsProvider = (data) => data.bends return edgeSources } @@ -111,7 +111,7 @@ export function configureNodeLayoutWithBinding(graphBuilder) { // create some binding for the x, y, width and height properties of the layout nodeSources.nodeCreator.layoutBindings.addBinding('x', () => 250) - nodeSources.nodeCreator.layoutBindings.addBinding('y', data => data.locationY) + nodeSources.nodeCreator.layoutBindings.addBinding('y', (data) => data.locationY) nodeSources.nodeCreator.layoutBindings.addBinding('width', () => 50) nodeSources.nodeCreator.layoutBindings.addBinding('height', () => 30) diff --git a/demos/tutorial-graph-builder/10-configure-layout/configure-layout.ts b/demos/tutorial-graph-builder/10-configure-layout/configure-layout.ts index ae25e638c..1f1a25b31 100644 --- a/demos/tutorial-graph-builder/10-configure-layout/configure-layout.ts +++ b/demos/tutorial-graph-builder/10-configure-layout/configure-layout.ts @@ -65,7 +65,7 @@ export function configureNodeLayoutWithProvider( // create the node using the id property const nodeSource = graphBuilder.createNodesSource(nodeData, 'id') // configure the layout provider that returns the layout information - nodeSource.nodeCreator.layoutProvider = data => data.layout + nodeSource.nodeCreator.layoutProvider = (data) => data.layout return nodeSource } @@ -100,7 +100,7 @@ export function configureBends( 'id' ) // configure the bend provider to return the location of each bend point - edgeSources.edgeCreator.bendsProvider = data => data.bends + edgeSources.edgeCreator.bendsProvider = (data) => data.bends return edgeSources } @@ -116,7 +116,10 @@ export function configureNodeLayoutWithBinding( // create some binding for the x, y, width and height properties of the layout nodeSources.nodeCreator.layoutBindings.addBinding('x', () => 250) - nodeSources.nodeCreator.layoutBindings.addBinding('y', data => data.locationY) + nodeSources.nodeCreator.layoutBindings.addBinding( + 'y', + (data) => data.locationY + ) nodeSources.nodeCreator.layoutBindings.addBinding('width', () => 50) nodeSources.nodeCreator.layoutBindings.addBinding('height', () => 30) diff --git a/demos/tutorial-graph-builder/10-configure-layout/index.html b/demos/tutorial-graph-builder/10-configure-layout/index.html index 61fe50ac7..c269fb350 100644 --- a/demos/tutorial-graph-builder/10-configure-layout/index.html +++ b/demos/tutorial-graph-builder/10-configure-layout/index.html @@ -1,4 +1,4 @@ - + @@ -73,12 +73,10 @@
@@ -243,7 +241,10 @@

Loading node layout information

class="highlight" >// create some binding for the x, y, width and height properties of the layout nodeSources.nodeCreator.layoutBindings.addBinding('x', () => 250) -nodeSources.nodeCreator.layoutBindings.addBinding('y', data => data.locationY) +nodeSources.nodeCreator.layoutBindings.addBinding( + 'y', + (data) => data.locationY +) nodeSources.nodeCreator.layoutBindings.addBinding('width', () => 50) nodeSources.nodeCreator.layoutBindings.addBinding('height', () => 30)
@@ -302,7 +303,7 @@

Loading the bend information

// configure the bend provider to return the location of each bend point
-edgeSources.edgeCreator.bendsProvider = data => data.bends
+edgeSources.edgeCreator.bendsProvider = (data) => data.bends
diff --git a/demos/tutorial-graph-builder/11-update-graph/index.html b/demos/tutorial-graph-builder/11-update-graph/index.html index c7072162b..b6dbbb1e9 100644 --- a/demos/tutorial-graph-builder/11-update-graph/index.html +++ b/demos/tutorial-graph-builder/11-update-graph/index.html @@ -1,4 +1,4 @@ - + @@ -73,12 +73,10 @@
- Step 12/14Step 11/13 — 11 Update Graph
Tutorial: Graph Builder01 Create Graph item.id + (item) => item.id ) ``` @@ -72,7 +72,7 @@ In the second step, we configure the _colleagues_ or _successors_ on the [Adjace ``` adjacencyNodesSource.addSuccessorIds( - data => data.colleagues, + (data) => data.colleagues, new EdgeCreator({ defaults: graph.edgeDefaults }) ) ``` @@ -81,7 +81,7 @@ Finally, we add labels to the graph building process by providing a label bindin ``` adjacencyNodesSource.nodeCreator.createLabelBinding({ - text: dataItem => dataItem.name + text: (dataItem) => dataItem.name }) ``` diff --git a/demos/tutorial-graph-builder/12-adjacency-graph-builder/adjacency-graph-building.js b/demos/tutorial-graph-builder/12-adjacency-graph-builder/adjacency-graph-building.js index b19e89be3..884281f62 100644 --- a/demos/tutorial-graph-builder/12-adjacency-graph-builder/adjacency-graph-building.js +++ b/demos/tutorial-graph-builder/12-adjacency-graph-builder/adjacency-graph-building.js @@ -36,15 +36,15 @@ import { AdjacencyGraphBuilder, EdgeCreator } from 'yfiles' export function configureGraphBuilder(graph, nodesData) { const adjacencyGraphBuilder = new AdjacencyGraphBuilder(graph) - const adjacencyNodesSource = adjacencyGraphBuilder.createNodesSource(nodesData, item => item.id) + const adjacencyNodesSource = adjacencyGraphBuilder.createNodesSource(nodesData, (item) => item.id) adjacencyNodesSource.addSuccessorIds( - data => data.colleagues, + (data) => data.colleagues, new EdgeCreator({ defaults: graph.edgeDefaults }) ) adjacencyNodesSource.nodeCreator.createLabelBinding({ - text: dataItem => dataItem.name + text: (dataItem) => dataItem.name }) return adjacencyGraphBuilder diff --git a/demos/tutorial-graph-builder/12-adjacency-graph-builder/adjacency-graph-building.ts b/demos/tutorial-graph-builder/12-adjacency-graph-builder/adjacency-graph-building.ts index cbd9fefa6..ea77cdf9a 100644 --- a/demos/tutorial-graph-builder/12-adjacency-graph-builder/adjacency-graph-building.ts +++ b/demos/tutorial-graph-builder/12-adjacency-graph-builder/adjacency-graph-building.ts @@ -38,16 +38,16 @@ export function configureGraphBuilder( const adjacencyNodesSource = adjacencyGraphBuilder.createNodesSource( nodesData, - item => item.id + (item) => item.id ) adjacencyNodesSource.addSuccessorIds( - data => data.colleagues, + (data) => data.colleagues, new EdgeCreator({ defaults: graph.edgeDefaults }) ) adjacencyNodesSource.nodeCreator.createLabelBinding({ - text: dataItem => dataItem.name + text: (dataItem) => dataItem.name }) return adjacencyGraphBuilder diff --git a/demos/tutorial-graph-builder/12-adjacency-graph-builder/index.html b/demos/tutorial-graph-builder/12-adjacency-graph-builder/index.html index d6a39f872..3d0a22c4e 100644 --- a/demos/tutorial-graph-builder/12-adjacency-graph-builder/index.html +++ b/demos/tutorial-graph-builder/12-adjacency-graph-builder/index.html @@ -1,4 +1,4 @@ - + @@ -73,12 +73,10 @@ @@ -228,7 +226,7 @@

Building the graph

adjacencyNodesSource.addSuccessorIds(
-  data => data.colleagues,
+  (data) => data.colleagues,
   new EdgeCreator({ defaults: graph.edgeDefaults })
 )
@@ -244,7 +242,7 @@

Building the graph

adjacencyNodesSource.nodeCreator.createLabelBinding({
-  text: dataItem => dataItem.name
+  text: (dataItem) => dataItem.name
 })
diff --git a/demos/tutorial-graph-builder/13-tree-builder/README.md b/demos/tutorial-graph-builder/13-tree-builder/README.md index ef920e37d..fe1e614e2 100644 --- a/demos/tutorial-graph-builder/13-tree-builder/README.md +++ b/demos/tutorial-graph-builder/13-tree-builder/README.md @@ -76,7 +76,10 @@ In the second step, we configure a child [NodesSource](https://docs.yworks.com/y ``` // the childDataProvider identifies the property of a node object that contains its child nodes -rootNodesSource.addChildNodesSource(data => data.colleagues, rootNodesSource) +rootNodesSource.addChildNodesSource( + (data) => data.colleagues, + rootNodesSource +) ``` Note that we have used the `rootNodesSource` as the source for the colleagues _recursively._ The [TreeBuilder](https://docs.yworks.com/yfileshtml/#/api/TreeBuilder) makes sure no nodes or edges with the same `id` are created twice. @@ -85,7 +88,7 @@ Finally, we add labels to the graph building process by providing a label bindin ``` rootNodesSource.nodeCreator.createLabelBinding({ - text: dataItem => dataItem.name + text: (dataItem) => dataItem.name }) ``` diff --git a/demos/tutorial-graph-builder/13-tree-builder/index.html b/demos/tutorial-graph-builder/13-tree-builder/index.html index 376958750..ebd696b13 100644 --- a/demos/tutorial-graph-builder/13-tree-builder/index.html +++ b/demos/tutorial-graph-builder/13-tree-builder/index.html @@ -1,4 +1,4 @@ - + @@ -73,12 +73,10 @@
@@ -257,7 +258,7 @@

Building the graph

rootNodesSource.nodeCreator.createLabelBinding({
-  text: dataItem => dataItem.name
+  text: (dataItem) => dataItem.name
 })
diff --git a/demos/tutorial-graph-builder/13-tree-builder/tree-graph-building.js b/demos/tutorial-graph-builder/13-tree-builder/tree-graph-building.js index 65cb439c4..af4b63f49 100644 --- a/demos/tutorial-graph-builder/13-tree-builder/tree-graph-building.js +++ b/demos/tutorial-graph-builder/13-tree-builder/tree-graph-building.js @@ -40,10 +40,10 @@ export function configureGraphBuilder(graph, nodesData) { // the childDataProvider identifies the property of a node object that contains its child nodes - rootNodesSource.addChildNodesSource(data => data.colleagues, rootNodesSource) + rootNodesSource.addChildNodesSource((data) => data.colleagues, rootNodesSource) rootNodesSource.nodeCreator.createLabelBinding({ - text: dataItem => dataItem.name + text: (dataItem) => dataItem.name }) return treeBuilder diff --git a/demos/tutorial-graph-builder/13-tree-builder/tree-graph-building.ts b/demos/tutorial-graph-builder/13-tree-builder/tree-graph-building.ts index 52c067020..9b3bdb1a9 100644 --- a/demos/tutorial-graph-builder/13-tree-builder/tree-graph-building.ts +++ b/demos/tutorial-graph-builder/13-tree-builder/tree-graph-building.ts @@ -40,10 +40,13 @@ export function configureGraphBuilder( // the childDataProvider identifies the property of a node object that contains its child nodes - rootNodesSource.addChildNodesSource(data => data.colleagues, rootNodesSource) + rootNodesSource.addChildNodesSource( + (data) => data.colleagues, + rootNodesSource + ) rootNodesSource.nodeCreator.createLabelBinding({ - text: dataItem => dataItem.name + text: (dataItem) => dataItem.name }) return treeBuilder diff --git a/demos/tutorial-graph-builder/common.js b/demos/tutorial-graph-builder/common.js index bad9b09c5..fc77fcac4 100644 --- a/demos/tutorial-graph-builder/common.js +++ b/demos/tutorial-graph-builder/common.js @@ -44,7 +44,7 @@ import { applyDemoTheme } from 'demo-resources/demo-styles' * @returns {!Promise.} */ export async function getData() { - return fetch('../ownership-data.json').then(response => response.json()) + return fetch('../ownership-data.json').then((response) => response.json()) } /** diff --git a/demos/tutorial-graph-builder/common.ts b/demos/tutorial-graph-builder/common.ts index 210a7a092..5e1108b98 100644 --- a/demos/tutorial-graph-builder/common.ts +++ b/demos/tutorial-graph-builder/common.ts @@ -46,7 +46,7 @@ import { applyDemoTheme } from 'demo-resources/demo-styles' */ export async function getData(): Promise { return fetch('../ownership-data.json').then( - response => response.json() as Promise + (response) => response.json() as Promise ) } diff --git a/demos/tutorial-style-implementation-edge/01-create-a-polyline/CustomEdgeStyle.js b/demos/tutorial-style-implementation-edge/01-create-a-polyline/CustomEdgeStyle.js index aa4d52241..d342c7de3 100644 --- a/demos/tutorial-style-implementation-edge/01-create-a-polyline/CustomEdgeStyle.js +++ b/demos/tutorial-style-implementation-edge/01-create-a-polyline/CustomEdgeStyle.js @@ -52,7 +52,7 @@ export class CustomEdgeStyle extends EdgeStyleBase { */ createPathData(edge) { const points = IEdge.getPathPoints(edge).toArray() - return 'M ' + points.map(point => `${point.x} ${point.y}`).join(' L ') + return 'M ' + points.map((point) => `${point.x} ${point.y}`).join(' L ') } } diff --git a/demos/tutorial-style-implementation-edge/01-create-a-polyline/CustomEdgeStyle.ts b/demos/tutorial-style-implementation-edge/01-create-a-polyline/CustomEdgeStyle.ts index ab7262a48..3e64ab811 100644 --- a/demos/tutorial-style-implementation-edge/01-create-a-polyline/CustomEdgeStyle.ts +++ b/demos/tutorial-style-implementation-edge/01-create-a-polyline/CustomEdgeStyle.ts @@ -49,7 +49,7 @@ export class CustomEdgeStyle extends EdgeStyleBase { private createPathData(edge: IEdge): string { const points = IEdge.getPathPoints(edge).toArray() - return 'M ' + points.map(point => `${point.x} ${point.y}`).join(' L ') + return 'M ' + points.map((point) => `${point.x} ${point.y}`).join(' L ') } } diff --git a/demos/tutorial-style-implementation-edge/01-create-a-polyline/README.md b/demos/tutorial-style-implementation-edge/01-create-a-polyline/README.md index ac8d48972..47facf871 100644 --- a/demos/tutorial-style-implementation-edge/01-create-a-polyline/README.md +++ b/demos/tutorial-style-implementation-edge/01-create-a-polyline/README.md @@ -9,9 +9,7 @@ // ////////////////////////////////////////////////////////////////////////////// --> -# - - Tutorial: Edge Style Implementation - Tutorial: Edge Style Implementation +# 01 Create a Polyline - Tutorial: Edge Style Implementation # Custom edge visualizations @@ -57,7 +55,7 @@ The `createPathData` method generates the path data for the SVG path element usi ``` private createPathData(edge: IEdge): string { const points = IEdge.getPathPoints(edge).toArray() - return 'M ' + points.map(point => `${point.x} ${point.y}`).join(' L ') + return 'M ' + points.map((point) => `${point.x} ${point.y}`).join(' L ') } ``` @@ -65,4 +63,4 @@ Note It’s important that SvgVisual contains an SVG element in the 'http://www.w3.org/2000/svg' namespace. HTML elements are not supported. -[01 Create a Polyline](../../tutorial-style-implementation-edge/01-create-a-polyline/) +[02 Crop the Polyline](../../tutorial-style-implementation-edge/02-crop-the-polyline/) diff --git a/demos/tutorial-style-implementation-edge/01-create-a-polyline/index.html b/demos/tutorial-style-implementation-edge/01-create-a-polyline/index.html index 2d5a24896..a4bc28bf5 100644 --- a/demos/tutorial-style-implementation-edge/01-create-a-polyline/index.html +++ b/demos/tutorial-style-implementation-edge/01-create-a-polyline/index.html @@ -1,4 +1,4 @@ - + @@ -33,9 +33,7 @@ // ////////////////////////////////////////////////////////////////////////--> - - Tutorial: Edge Style Implementation - Tutorial: Edge Style Implementation [yFiles for HTML] - + 01 Create a Polyline - Tutorial: Edge Style Implementation [yFiles for HTML] @@ -58,7 +56,7 @@ Tutorial: Edge Style Implementation - Tutorial: Edge Style Implementation + 01 Create a Polyline yFiles Demos
@@ -76,18 +74,12 @@
- Step 1/13 - — Tutorial: Edge Style Implementation + Step 1/12 — 01 Create a Polyline
@@ -272,7 +264,7 @@

Subclassing EdgeStyleBase

diff --git a/demos/tutorial-style-implementation-edge/02-crop-the-polyline/index.html b/demos/tutorial-style-implementation-edge/02-crop-the-polyline/index.html index 5700d20e9..a380944ce 100644 --- a/demos/tutorial-style-implementation-edge/02-crop-the-polyline/index.html +++ b/demos/tutorial-style-implementation-edge/02-crop-the-polyline/index.html @@ -1,4 +1,4 @@ - + @@ -75,14 +75,10 @@
- Step 3/13Step 2/12 — 02 Crop the Polyline
Tutorial: Edge Style Implementation01 Create a Polyline + @@ -77,16 +77,12 @@
- Step 4/13Step 3/12 — 03 Create Parallel Polylines
Tutorial: Edge Style Implementation01 Create a Polyline + @@ -75,14 +75,10 @@
- Step 5/13Step 4/12 — 04 Render Performance
Tutorial: Edge Style Implementation01 Create a Polyline + @@ -77,16 +77,12 @@
- Step 6/13Step 5/12 — 05 Making the Style Configurable
Tutorial: Edge Style Implementation01 Create a Polyline + @@ -75,14 +75,10 @@
- Step 7/13Step 6/12 — 06 Data from Tag
Tutorial: Edge Style Implementation01 Create a Polyline + @@ -75,14 +75,10 @@
- Step 8/13Step 7/12 — 07 Hit-Testing
Tutorial: Edge Style Implementation01 Create a Polyline + @@ -75,14 +75,10 @@
- Step 9/13Step 8/12 — 08 Item Visibility
Tutorial: Edge Style Implementation01 Create a Polyline + @@ -75,14 +75,10 @@
- Step 10/13Step 9/12 — 09 Render Boundaries
Tutorial: Edge Style Implementation01 Create a Polyline + @@ -75,14 +75,10 @@
- Step 11/13Step 10/12 — 10 Bridge Support
Tutorial: Edge Style Implementation01 Create a Polyline + @@ -75,14 +75,10 @@
- Step 12/13Step 11/12 — 11 Adding Arrows
Tutorial: Edge Style Implementation01 Create a Polyline + @@ -75,14 +75,10 @@
- Step 13/13Step 12/12 — 12 Custom Arrow
Tutorial: Edge Style Implementation01 Create a Polyline `${point.x} ${point.y}`).join(' L ') + return 'M ' + points.map((point) => `${point.x} ${point.y}`).join(' L ') } /** @@ -440,7 +440,7 @@ export class IsVisibleEdgeStyleDescriptor extends BaseClass(ICanvasObjectDescrip */ export function startAnimation(graphComponent) { graphComponent.sizeChangedDetection = SizeChangedDetectionMode.TIMER - graphComponent.addSizeChangedListener(_ => { + graphComponent.addSizeChangedListener((_) => { setTimeout(() => { setAnimationStartPoint(graphComponent) void animate() @@ -498,7 +498,7 @@ export class BoundsVisual extends BaseClass(IVisualCreator) { const g = document.createElementNS('http://www.w3.org/2000/svg', 'g') g.append( - ...graph.edges.map(e => { + ...graph.edges.map((e) => { const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect') const { x, y, width, height } = e.style.renderer .getBoundsProvider(e, e.style) diff --git a/demos/tutorial-style-implementation-edge/common.ts b/demos/tutorial-style-implementation-edge/common.ts index b62afb9f4..b239d4aa9 100644 --- a/demos/tutorial-style-implementation-edge/common.ts +++ b/demos/tutorial-style-implementation-edge/common.ts @@ -309,7 +309,7 @@ export function addHoverEffect( function createPathData(edge: IEdge): string { const points = IEdge.getPathPoints(edge).toArray() - return 'M ' + points.map(point => `${point.x} ${point.y}`).join(' L ') + return 'M ' + points.map((point) => `${point.x} ${point.y}`).join(' L ') } export function zoomToContent(graphComponent: GraphComponent): void { @@ -390,7 +390,7 @@ export class IsVisibleEdgeStyleDescriptor extends BaseClass( export function startAnimation(graphComponent: GraphComponent): void { graphComponent.sizeChangedDetection = SizeChangedDetectionMode.TIMER - graphComponent.addSizeChangedListener(_ => { + graphComponent.addSizeChangedListener((_) => { setTimeout(() => { setAnimationStartPoint(graphComponent) void animate() @@ -456,7 +456,7 @@ export class BoundsVisual extends BaseClass(IVisualCreator) { const g = document.createElementNS('http://www.w3.org/2000/svg', 'g') g.append( - ...graph.edges.map(e => { + ...graph.edges.map((e) => { const rect = document.createElementNS( 'http://www.w3.org/2000/svg', 'rect' diff --git a/demos/tutorial-style-implementation-label/01-render-label-text/README.md b/demos/tutorial-style-implementation-label/01-render-label-text/README.md index 4163445e4..c50658c53 100644 --- a/demos/tutorial-style-implementation-label/01-render-label-text/README.md +++ b/demos/tutorial-style-implementation-label/01-render-label-text/README.md @@ -11,7 +11,7 @@ --> # - Tutorial: Label Style Implementation - Tutorial: Label Style Implementation + 01 Rendering the Label Text - Tutorial: Label Style Implementation # Custom label visualizations @@ -85,4 +85,4 @@ Since the SVG text anchor point is bottom-left and the label layout anchor point textElement.setAttribute('dy', String(label.layout.height)) ``` -[01 Rendering the Label Text](../../tutorial-style-implementation-label/01-render-label-text/) +[02 Using Text Utilities](../../tutorial-style-implementation-label/02-using-text-utilities/) diff --git a/demos/tutorial-style-implementation-label/01-render-label-text/index.html b/demos/tutorial-style-implementation-label/01-render-label-text/index.html index 2c7590ff6..fcb9e303b 100644 --- a/demos/tutorial-style-implementation-label/01-render-label-text/index.html +++ b/demos/tutorial-style-implementation-label/01-render-label-text/index.html @@ -1,4 +1,4 @@ - + @@ -34,7 +34,7 @@ ////////////////////////////////////////////////////////////////////////--> - Tutorial: Label Style Implementation - Tutorial: Label Style Implementation [yFiles for HTML] + 01 Rendering the Label Text - Tutorial: Label Style Implementation [yFiles for HTML] @@ -58,7 +58,7 @@ Tutorial: Label Style Implementation - Tutorial: Label Style Implementation + 01 Rendering the Label Text yFiles Demos
@@ -76,18 +76,12 @@
- Step 1/12 - — Tutorial: Label Style Implementation + Step 1/11 — 01 Rendering the Label Text
diff --git a/demos/tutorial-style-implementation-label/02-using-text-utilities/index.html b/demos/tutorial-style-implementation-label/02-using-text-utilities/index.html index 696eed127..bcb26d3d2 100644 --- a/demos/tutorial-style-implementation-label/02-using-text-utilities/index.html +++ b/demos/tutorial-style-implementation-label/02-using-text-utilities/index.html @@ -1,4 +1,4 @@ - + @@ -75,14 +75,10 @@
- Step 3/12Step 2/11 — 02 Using Text Utilities
Tutorial: Label Style Implementation01 Rendering the Label Text + @@ -77,16 +77,12 @@
- Step 4/12Step 3/11 — 03 Adding a Background Shape
Tutorial: Label Style Implementation01 Rendering the Label Text + @@ -75,14 +75,10 @@
- Step 5/12Step 4/11 — 04 Preferred Label Size
Tutorial: Label Style Implementation01 Rendering the Label Text + @@ -75,14 +75,10 @@
- Step 6/12Step 5/11 — 05 Render Performance
Tutorial: Label Style Implementation01 Rendering the Label Text + @@ -75,14 +75,10 @@
- Step 7/12Step 6/11 — 06 Text Alignment
Tutorial: Label Style Implementation01 Rendering the Label Text + @@ -75,14 +75,10 @@
- Step 8/12Step 7/11 — 07 Line Wrapping
Tutorial: Label Style Implementation01 Rendering the Label Text + @@ -75,14 +75,10 @@
- Step 9/12Step 8/11 — 08 Data From Tag
Tutorial: Label Style Implementation01 Rendering the Label Text + @@ -75,14 +75,10 @@
- Step 10/12Step 9/11 — 09 Hit-Testing
Tutorial: Label Style Implementation01 Rendering the Label Text + @@ -75,14 +75,10 @@
- Step 11/12Step 10/11 — 10 Visibility
Tutorial: Label Style Implementation01 Rendering the Label Text + @@ -75,13 +75,9 @@
- Step 12/12 — 11 Bounds + Step 11/11 — 11 Bounds
Tutorial: Label Style Implementation01 Rendering the Label Text { + graphComponent.addSizeChangedListener((_) => { setTimeout(() => { setAnimationStartPoint(graphComponent) void animate() @@ -494,7 +494,7 @@ export class BoundsVisual extends BaseClass(IVisualCreator) { const g = document.createElementNS('http://www.w3.org/2000/svg', 'g') g.append( - ...graph.labels.map(label => { + ...graph.labels.map((label) => { const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect') const { x, y, width, height } = label.style.renderer .getBoundsProvider(label, label.style) diff --git a/demos/tutorial-style-implementation-label/common.ts b/demos/tutorial-style-implementation-label/common.ts index f32706d0c..23e152128 100644 --- a/demos/tutorial-style-implementation-label/common.ts +++ b/demos/tutorial-style-implementation-label/common.ts @@ -366,7 +366,7 @@ export class IsVisibleLabelStyleDescriptor extends BaseClass( export function startNodeAnimation(graphComponent: GraphComponent): void { graphComponent.sizeChangedDetection = SizeChangedDetectionMode.TIMER - graphComponent.addSizeChangedListener(_ => { + graphComponent.addSizeChangedListener((_) => { setTimeout(() => { setAnimationStartPoint(graphComponent) void animate() @@ -439,7 +439,7 @@ export class BoundsVisual extends BaseClass(IVisualCreator) { const g = document.createElementNS('http://www.w3.org/2000/svg', 'g') g.append( - ...graph.labels.map(label => { + ...graph.labels.map((label) => { const rect = document.createElementNS( 'http://www.w3.org/2000/svg', 'rect' diff --git a/demos/tutorial-style-implementation-node/01-create-a-rectangle/README.md b/demos/tutorial-style-implementation-node/01-create-a-rectangle/README.md index aece0327b..70c44aa01 100644 --- a/demos/tutorial-style-implementation-node/01-create-a-rectangle/README.md +++ b/demos/tutorial-style-implementation-node/01-create-a-rectangle/README.md @@ -9,9 +9,7 @@ // ////////////////////////////////////////////////////////////////////////////// --> -# - - Tutorial: Node Style Implementation - Tutorial: Node Style Implementation +# 01 Create A Rectangle - Tutorial: Node Style Implementation # Custom node visualizations @@ -68,4 +66,4 @@ Note It’s important that SvgVisual contains an SVG element in the 'http://www.w3.org/2000/svg' namespace. HTML elements are not supported. -[01 Create A Rectangle](../../tutorial-style-implementation-node/01-create-a-rectangle/) +[02 Create A Custom Shape](../../tutorial-style-implementation-node/02-create-a-custom-shape/) diff --git a/demos/tutorial-style-implementation-node/01-create-a-rectangle/index.html b/demos/tutorial-style-implementation-node/01-create-a-rectangle/index.html index 9f04c25f8..3824f0ce8 100644 --- a/demos/tutorial-style-implementation-node/01-create-a-rectangle/index.html +++ b/demos/tutorial-style-implementation-node/01-create-a-rectangle/index.html @@ -1,4 +1,4 @@ - + @@ -33,9 +33,7 @@ // ////////////////////////////////////////////////////////////////////////--> - - Tutorial: Node Style Implementation - Tutorial: Node Style Implementation [yFiles for HTML] - + 01 Create A Rectangle - Tutorial: Node Style Implementation [yFiles for HTML] @@ -58,7 +56,7 @@ Tutorial: Node Style Implementation - Tutorial: Node Style Implementation + 01 Create A Rectangle yFiles Demos
@@ -76,18 +74,12 @@
- Step 1/13 - — Tutorial: Node Style Implementation + Step 1/12 — 01 Create A Rectangle
diff --git a/demos/tutorial-style-implementation-node/02-create-a-custom-shape/index.html b/demos/tutorial-style-implementation-node/02-create-a-custom-shape/index.html index 5b8a06e31..3b3e122db 100644 --- a/demos/tutorial-style-implementation-node/02-create-a-custom-shape/index.html +++ b/demos/tutorial-style-implementation-node/02-create-a-custom-shape/index.html @@ -1,4 +1,4 @@ - + @@ -75,14 +75,10 @@
- Step 3/13Step 2/12 — 02 Create A Custom Shape
Tutorial: Node Style Implementation01 Create A Rectangle + @@ -75,14 +75,10 @@
- Step 4/13Step 3/12 — 03 Render Performance
Tutorial: Node Style Implementation01 Create A Rectangle + @@ -77,16 +77,12 @@
- Step 5/13Step 4/12 — 04 Making the Style Configurable
Tutorial: Node Style Implementation01 Create A Rectangle + @@ -75,14 +75,10 @@
- Step 6/13Step 5/12 — 05 Data from Tag
Tutorial: Node Style Implementation01 Create A Rectangle + @@ -75,14 +75,10 @@
- Step 7/13Step 6/12 — 06 Rendering Text
Tutorial: Node Style Implementation01 Create A Rectangle + @@ -75,14 +75,10 @@
- Step 8/13Step 7/12 — 07 Hit-Testing
Tutorial: Node Style Implementation01 Create A Rectangle + @@ -75,14 +75,10 @@
- Step 9/13Step 8/12 — 08 Edge Cropping
Tutorial: Node Style Implementation01 Create A Rectangle + @@ -75,14 +75,10 @@
- Step 10/13Step 9/12 — 09 Item Visibility
Tutorial: Node Style Implementation01 Create A Rectangle + @@ -75,14 +75,10 @@
- Step 11/13Step 10/12 — 10 Render Boundaries
Tutorial: Node Style Implementation01 Create A Rectangle new Insets(4, tabHeight + 4, 4, 4)) + return INodeInsetsProvider.create((node) => new Insets(4, tabHeight + 4, 4, 4)) } return super.lookup(node, type) } diff --git a/demos/tutorial-style-implementation-node/11-group-node-style/CustomGroupNodeStyle.ts b/demos/tutorial-style-implementation-node/11-group-node-style/CustomGroupNodeStyle.ts index 514f389af..195e21bc7 100644 --- a/demos/tutorial-style-implementation-node/11-group-node-style/CustomGroupNodeStyle.ts +++ b/demos/tutorial-style-implementation-node/11-group-node-style/CustomGroupNodeStyle.ts @@ -173,7 +173,7 @@ export class CustomGroupNodeStyle extends NodeStyleBase new Insets(4, tabHeight + 4, 4, 4) + (node) => new Insets(4, tabHeight + 4, 4, 4) ) } return super.lookup(node, type) diff --git a/demos/tutorial-style-implementation-node/11-group-node-style/README.md b/demos/tutorial-style-implementation-node/11-group-node-style/README.md index eaef4d8ae..b3af7ea33 100644 --- a/demos/tutorial-style-implementation-node/11-group-node-style/README.md +++ b/demos/tutorial-style-implementation-node/11-group-node-style/README.md @@ -22,7 +22,7 @@ protected lookup(node: INode, type: Class): any { if (type === INodeInsetsProvider.$class) { // use a custom insets provider that reserves space for the tab return INodeInsetsProvider.create( - node => new Insets(4, tabHeight + 4, 4, 4) + (node) => new Insets(4, tabHeight + 4, 4, 4) ) } return super.lookup(node, type) diff --git a/demos/tutorial-style-implementation-node/11-group-node-style/index.html b/demos/tutorial-style-implementation-node/11-group-node-style/index.html index 593256e86..14c3fb8b9 100644 --- a/demos/tutorial-style-implementation-node/11-group-node-style/index.html +++ b/demos/tutorial-style-implementation-node/11-group-node-style/index.html @@ -1,4 +1,4 @@ - + @@ -75,14 +75,10 @@
- Step 12/13Step 11/12 — 11 Group Node Style
Tutorial: Node Style Implementation01 Create A RectangleWriting a custom group node style if (type === INodeInsetsProvider.$class) { // use a custom insets provider that reserves space for the tab return INodeInsetsProvider.create( - node => new Insets(4, tabHeight + 4, 4, 4) + (node) => new Insets(4, tabHeight + 4, 4, 4) ) } return super.lookup(node, type) diff --git a/demos/tutorial-style-implementation-node/12-group-node-style-behavior/CustomGroupNodeStyle.js b/demos/tutorial-style-implementation-node/12-group-node-style-behavior/CustomGroupNodeStyle.js index 799dcc57a..042a16e50 100644 --- a/demos/tutorial-style-implementation-node/12-group-node-style-behavior/CustomGroupNodeStyle.js +++ b/demos/tutorial-style-implementation-node/12-group-node-style-behavior/CustomGroupNodeStyle.js @@ -66,7 +66,7 @@ export class CustomGroupNodeStyle extends NodeStyleBase { lookup(node, type) { if (type === INodeInsetsProvider.$class) { // use a custom insets provider that reserves space for the tab - return INodeInsetsProvider.create(group => new Insets(4, tabHeight + 4, 4, 4)) + return INodeInsetsProvider.create((group) => new Insets(4, tabHeight + 4, 4, 4)) } // Determines the minimum and maximum node size. @@ -74,11 +74,11 @@ export class CustomGroupNodeStyle extends NodeStyleBase { // use a custom size constraint provider to make sure that the node doesn't get smaller than the tab return INodeSizeConstraintProvider.create({ // returns the tab size plus a small margin - getMinimumSize: item => new Size(tabWidth + 20, tabHeight + 20), + getMinimumSize: (item) => new Size(tabWidth + 20, tabHeight + 20), // don't limit the maximum size - getMaximumSize: item => Size.INFINITE, + getMaximumSize: (item) => Size.INFINITE, // don't constrain the area - getMinimumEnclosedArea: item => Rect.EMPTY + getMinimumEnclosedArea: (item) => Rect.EMPTY }) } @@ -89,9 +89,9 @@ export class CustomGroupNodeStyle extends NodeStyleBase { return IGroupBoundsCalculator.create((graph, groupNode) => { let bounds = Rect.EMPTY const children = graph.getChildren(groupNode) - children.forEach(child => { + children.forEach((child) => { bounds = Rect.add(bounds, child.layout.toRect()) - child.labels.forEach(label => { + child.labels.forEach((label) => { bounds = Rect.add(bounds, label.layout.bounds) }) }) diff --git a/demos/tutorial-style-implementation-node/12-group-node-style-behavior/index.html b/demos/tutorial-style-implementation-node/12-group-node-style-behavior/index.html index 8dc1d28c9..47e0366ed 100644 --- a/demos/tutorial-style-implementation-node/12-group-node-style-behavior/index.html +++ b/demos/tutorial-style-implementation-node/12-group-node-style-behavior/index.html @@ -1,4 +1,4 @@ - + @@ -77,16 +77,12 @@
- Step 13/13Step 12/12 — 12 Group Node Style Behavior
Tutorial: Node Style Implementation01 Create A Rectangle { + ...graph.nodes.map((n) => { const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect') const { x, y, width, height } = n.style.renderer .getBoundsProvider(n, n.style) @@ -404,7 +404,7 @@ export class BoundsVisual extends BaseClass(IVisualCreator) { */ export function startNodeAnimation(graphComponent) { graphComponent.sizeChangedDetection = SizeChangedDetectionMode.TIMER - graphComponent.addSizeChangedListener(_ => { + graphComponent.addSizeChangedListener((_) => { setTimeout(() => { setAnimationStartPoint(graphComponent) void animate() diff --git a/demos/tutorial-style-implementation-node/common.ts b/demos/tutorial-style-implementation-node/common.ts index 6b109e901..7015c7143 100644 --- a/demos/tutorial-style-implementation-node/common.ts +++ b/demos/tutorial-style-implementation-node/common.ts @@ -362,7 +362,7 @@ export class BoundsVisual extends BaseClass(IVisualCreator) { const g = document.createElementNS('http://www.w3.org/2000/svg', 'g') g.append( - ...graph.nodes.map(n => { + ...graph.nodes.map((n) => { const rect = document.createElementNS( 'http://www.w3.org/2000/svg', 'rect' @@ -396,7 +396,7 @@ export class BoundsVisual extends BaseClass(IVisualCreator) { export function startNodeAnimation(graphComponent: GraphComponent): void { graphComponent.sizeChangedDetection = SizeChangedDetectionMode.TIMER - graphComponent.addSizeChangedListener(_ => { + graphComponent.addSizeChangedListener((_) => { setTimeout(() => { setAnimationStartPoint(graphComponent) void animate() diff --git a/demos/tutorial-style-implementation-port/01-render-port-shape/README.md b/demos/tutorial-style-implementation-port/01-render-port-shape/README.md index 1579502fd..9f7e9e312 100644 --- a/demos/tutorial-style-implementation-port/01-render-port-shape/README.md +++ b/demos/tutorial-style-implementation-port/01-render-port-shape/README.md @@ -9,9 +9,7 @@ // ////////////////////////////////////////////////////////////////////////////// --> -# - - Tutorial: Port Style Implementation - Tutorial: Port Style Implementation +# 01 Rendering the Port - Tutorial: Port Style Implementation # Custom port visualizations @@ -76,4 +74,4 @@ protected getBounds(context: ICanvasContext, port: IPort): Rect { } ``` -[01 Rendering the Port](../../tutorial-style-implementation-port/01-render-port-shape/) +[02 Port Size](../../tutorial-style-implementation-port/02-port-size/) diff --git a/demos/tutorial-style-implementation-port/01-render-port-shape/index.html b/demos/tutorial-style-implementation-port/01-render-port-shape/index.html index 6f202ef07..0c915fe36 100644 --- a/demos/tutorial-style-implementation-port/01-render-port-shape/index.html +++ b/demos/tutorial-style-implementation-port/01-render-port-shape/index.html @@ -1,4 +1,4 @@ - + @@ -33,9 +33,7 @@ // ////////////////////////////////////////////////////////////////////////--> - - Tutorial: Port Style Implementation - Tutorial: Port Style Implementation [yFiles for HTML] - + 01 Rendering the Port - Tutorial: Port Style Implementation [yFiles for HTML] @@ -58,7 +56,7 @@ Tutorial: Port Style Implementation - Tutorial: Port Style Implementation + 01 Rendering the Port yFiles Demos
@@ -76,18 +74,12 @@
- Step 1/7 - — Tutorial: Port Style Implementation + Step 1/6 — 01 Rendering the Port
diff --git a/demos/tutorial-style-implementation-port/02-port-size/index.html b/demos/tutorial-style-implementation-port/02-port-size/index.html index 658dea12e..934d6f1d0 100644 --- a/demos/tutorial-style-implementation-port/02-port-size/index.html +++ b/demos/tutorial-style-implementation-port/02-port-size/index.html @@ -1,4 +1,4 @@ - + @@ -75,13 +75,9 @@
- Step 3/7 — 02 Port Size + Step 2/6 — 02 Port Size
Tutorial: Port Style Implementation01 Rendering the Port + @@ -75,14 +75,10 @@
- Step 4/7Step 3/6 — 03 Render Performance
Tutorial: Port Style Implementation01 Rendering the Port + @@ -77,16 +77,12 @@
- Step 5/7Step 4/6 — 04 Conditional Port Coloring
Tutorial: Port Style Implementation01 Rendering the Port + @@ -75,14 +75,10 @@
- Step 6/7Step 5/6 — 05 Hit-Testing
Tutorial: Port Style Implementation01 Rendering the Port + @@ -75,14 +75,10 @@
- Step 7/7Step 6/6 — 06 Edge Cropping
Tutorial: Port Style Implementation01 Rendering the Port + graphComponent.graph.decorator.nodeDecorator.portCandidateProviderDecorator.setFactory((node) => IPortCandidateProvider.fromExistingPorts(node) ) return graphEditorInputMode diff --git a/demos/tutorial-style-implementation-port/common.ts b/demos/tutorial-style-implementation-port/common.ts index 1001d808a..6e1b5e5b7 100644 --- a/demos/tutorial-style-implementation-port/common.ts +++ b/demos/tutorial-style-implementation-port/common.ts @@ -191,7 +191,7 @@ export function enableGraphEditing( graphComponent.inputMode = graphEditorInputMode graphComponent.graph.nodeDefaults.ports.autoCleanUp = false graphComponent.graph.decorator.nodeDecorator.portCandidateProviderDecorator.setFactory( - node => IPortCandidateProvider.fromExistingPorts(node) + (node) => IPortCandidateProvider.fromExistingPorts(node) ) return graphEditorInputMode } diff --git a/demos/tutorial-yfiles-basic-features/01-graphcomponent/README.md b/demos/tutorial-yfiles-basic-features/01-graphcomponent/README.md index d30b5d33f..92be8e68b 100644 --- a/demos/tutorial-yfiles-basic-features/01-graphcomponent/README.md +++ b/demos/tutorial-yfiles-basic-features/01-graphcomponent/README.md @@ -9,7 +9,7 @@ // ////////////////////////////////////////////////////////////////////////////// --> -# Tutorial: Basic Features - Tutorial: Basic Features +# 01 Creating the View - Tutorial: Basic Features # Creating the View @@ -63,4 +63,4 @@ For a minimal example of using yFiles for HTML to display a graph on a web page, Alternatively, you may use the [App Generator](https://www.yworks.com/products/app-generator) to create visualization prototypes – quickly and easily. It will allow you to scaffold a working code example using your preferred UI framework (React, Vue, Angular, Plain, etc.) -[01 Creating the View](../../tutorial-yfiles-basic-features/01-graphcomponent/) +[02 Creating Graph Elements](../../tutorial-yfiles-basic-features/02-graph-element-creation/) diff --git a/demos/tutorial-yfiles-basic-features/01-graphcomponent/index.html b/demos/tutorial-yfiles-basic-features/01-graphcomponent/index.html index d9aebe729..26db7327c 100644 --- a/demos/tutorial-yfiles-basic-features/01-graphcomponent/index.html +++ b/demos/tutorial-yfiles-basic-features/01-graphcomponent/index.html @@ -1,4 +1,4 @@ - + @@ -33,7 +33,7 @@ // ////////////////////////////////////////////////////////////////////////--> - Tutorial: Basic Features - Tutorial: Basic Features [yFiles for HTML] + 01 Creating the View - Tutorial: Basic Features [yFiles for HTML] @@ -54,7 +54,7 @@ Tutorial: Basic Features - Tutorial: Basic Features + 01 Creating the View yFiles Demos
@@ -72,16 +72,12 @@
- Step 1/13 — Tutorial: Basic Features + Step 1/12 — 01 Creating the View
diff --git a/demos/tutorial-yfiles-basic-features/02-graph-element-creation/index.html b/demos/tutorial-yfiles-basic-features/02-graph-element-creation/index.html index 149da2145..89836ba0f 100644 --- a/demos/tutorial-yfiles-basic-features/02-graph-element-creation/index.html +++ b/demos/tutorial-yfiles-basic-features/02-graph-element-creation/index.html @@ -1,4 +1,4 @@ - + @@ -73,14 +73,10 @@
- Step 3/13Step 2/12 — 02 Creating Graph Elements
Tutorial: Basic Features01 Creating the View + @@ -73,14 +73,10 @@
- Step 4/13Step 3/12 — 03 Managing Viewport
Tutorial: Basic Features01 Creating the View + @@ -73,14 +73,10 @@
- Step 5/13Step 4/12 — 04 Setting Styles
Tutorial: Basic Features01 Creating the View + @@ -73,14 +73,10 @@
- Step 6/13Step 5/12 — 05 Label Placement
Tutorial: Basic Features01 Creating the View + graph.nodeLabels.forEach((label) => graph.setLabelLayoutParameter(label, graph.nodeDefaults.labels.layoutParameter) ) - graph.edgeLabels.forEach(label => + graph.edgeLabels.forEach((label) => graph.setLabelLayoutParameter(label, graph.edgeDefaults.labels.layoutParameter) ) } diff --git a/demos/tutorial-yfiles-basic-features/05-label-placement/label-placement.ts b/demos/tutorial-yfiles-basic-features/05-label-placement/label-placement.ts index a08d91ec0..eab33f7e8 100644 --- a/demos/tutorial-yfiles-basic-features/05-label-placement/label-placement.ts +++ b/demos/tutorial-yfiles-basic-features/05-label-placement/label-placement.ts @@ -88,13 +88,13 @@ export function changeLabelLayoutParameters(graph: IGraph) { * Resets each label layout to the graph default. */ export function resetLabelLayoutParameters(graph: IGraph) { - graph.nodeLabels.forEach(label => + graph.nodeLabels.forEach((label) => graph.setLabelLayoutParameter( label, graph.nodeDefaults.labels.layoutParameter ) ) - graph.edgeLabels.forEach(label => + graph.edgeLabels.forEach((label) => graph.setLabelLayoutParameter( label, graph.edgeDefaults.labels.layoutParameter diff --git a/demos/tutorial-yfiles-basic-features/06-basic-interaction/index.html b/demos/tutorial-yfiles-basic-features/06-basic-interaction/index.html index 473c739dc..552e86f64 100644 --- a/demos/tutorial-yfiles-basic-features/06-basic-interaction/index.html +++ b/demos/tutorial-yfiles-basic-features/06-basic-interaction/index.html @@ -1,4 +1,4 @@ - + @@ -73,14 +73,10 @@
- Step 7/13Step 6/12 — 06 Basic Interaction
Tutorial: Basic Features01 Creating the View + @@ -73,14 +73,10 @@
- Step 8/13Step 7/12 — 07 Undo Clipboard Support
Tutorial: Basic Features01 Creating the View graph.isGroupNode(node)) +const groupNode = graph.nodes.first((node) => graph.isGroupNode(node)) // Create a child node that's outside the group bounds graph.createNode({ parent: groupNode, layout: [100, -60, 30, 30] }) // Adjust the group node layout to contain the new child diff --git a/demos/tutorial-yfiles-basic-features/08-grouping/app.js b/demos/tutorial-yfiles-basic-features/08-grouping/app.js index fe1289b65..2b073cfb3 100644 --- a/demos/tutorial-yfiles-basic-features/08-grouping/app.js +++ b/demos/tutorial-yfiles-basic-features/08-grouping/app.js @@ -52,8 +52,8 @@ createSampleGraph(graph) createGroupNodes(graph) configureInteraction(graphComponent) -const nodes = graph.nodes.filter(node => !graph.isGroupNode(node) && node.labels.size > 0) -nodes.forEach(node => graphComponent.selection.setSelected(node, true)) +const nodes = graph.nodes.filter((node) => !graph.isGroupNode(node) && node.labels.size > 0) +nodes.forEach((node) => graphComponent.selection.setSelected(node, true)) fitGraphBounds(graphComponent) finishLoading() diff --git a/demos/tutorial-yfiles-basic-features/08-grouping/app.ts b/demos/tutorial-yfiles-basic-features/08-grouping/app.ts index ecdb3c364..97e9dce7c 100644 --- a/demos/tutorial-yfiles-basic-features/08-grouping/app.ts +++ b/demos/tutorial-yfiles-basic-features/08-grouping/app.ts @@ -57,9 +57,9 @@ createGroupNodes(graph) configureInteraction(graphComponent) const nodes = graph.nodes.filter( - node => !graph.isGroupNode(node) && node.labels.size > 0 + (node) => !graph.isGroupNode(node) && node.labels.size > 0 ) -nodes.forEach(node => graphComponent.selection.setSelected(node, true)) +nodes.forEach((node) => graphComponent.selection.setSelected(node, true)) fitGraphBounds(graphComponent) finishLoading() diff --git a/demos/tutorial-yfiles-basic-features/08-grouping/grouping.js b/demos/tutorial-yfiles-basic-features/08-grouping/grouping.js index 1f268473c..876026761 100644 --- a/demos/tutorial-yfiles-basic-features/08-grouping/grouping.js +++ b/demos/tutorial-yfiles-basic-features/08-grouping/grouping.js @@ -90,7 +90,7 @@ export function createGroupNodes(graph) { */ export function adjustGroupNodeSize(graph) { // Get a group node - const groupNode = graph.nodes.first(node => graph.isGroupNode(node)) + const groupNode = graph.nodes.first((node) => graph.isGroupNode(node)) // Create a child node that's outside the group bounds graph.createNode({ parent: groupNode, layout: [100, -60, 30, 30] }) // Adjust the group node layout to contain the new child diff --git a/demos/tutorial-yfiles-basic-features/08-grouping/grouping.ts b/demos/tutorial-yfiles-basic-features/08-grouping/grouping.ts index cb1ba1bec..19e9cfac5 100644 --- a/demos/tutorial-yfiles-basic-features/08-grouping/grouping.ts +++ b/demos/tutorial-yfiles-basic-features/08-grouping/grouping.ts @@ -85,7 +85,7 @@ export function createGroupNodes(graph: IGraph): void { export function adjustGroupNodeSize(graph: IGraph) { // Get a group node - const groupNode = graph.nodes.first(node => graph.isGroupNode(node)) + const groupNode = graph.nodes.first((node) => graph.isGroupNode(node)) // Create a child node that's outside the group bounds graph.createNode({ parent: groupNode, layout: [100, -60, 30, 30] }) // Adjust the group node layout to contain the new child diff --git a/demos/tutorial-yfiles-basic-features/08-grouping/index.html b/demos/tutorial-yfiles-basic-features/08-grouping/index.html index d0686cae3..05749c234 100644 --- a/demos/tutorial-yfiles-basic-features/08-grouping/index.html +++ b/demos/tutorial-yfiles-basic-features/08-grouping/index.html @@ -1,4 +1,4 @@ - + @@ -73,13 +73,9 @@
- Step 9/13 — 08 Grouping + Step 8/12 — 08 Grouping
Tutorial: Basic Features01 Creating the ViewGrouped Graphs
// Get a group node
-const groupNode = graph.nodes.first(node => graph.isGroupNode(node))
+const groupNode = graph.nodes.first((node) => graph.isGroupNode(node))
 // Create a child node that's outside the group bounds
 graph.createNode({ parent: groupNode, layout: [100, -60, 30, 30] })
 // Adjust the group node layout to contain the new child
diff --git a/demos/tutorial-yfiles-basic-features/09-data-binding/data-binding.js b/demos/tutorial-yfiles-basic-features/09-data-binding/data-binding.js
index 44b4a00ff..42069f156 100644
--- a/demos/tutorial-yfiles-basic-features/09-data-binding/data-binding.js
+++ b/demos/tutorial-yfiles-basic-features/09-data-binding/data-binding.js
@@ -119,7 +119,7 @@ export function setupContextMenu(graphComponent, graphEditorInputMode) {
   // Add event listeners to the various events that open the context menu. These listeners then
   // call the provided callback function which in turn asks the current ContextMenuInputMode if a
   // context menu should be shown at the current location.
-  contextMenu.addOpeningEventListeners(graphComponent, location => {
+  contextMenu.addOpeningEventListeners(graphComponent, (location) => {
     const worldLocation = graphComponent.toWorldFromPage(location)
 
     // Inform the input mode that a context menu should be opened.
diff --git a/demos/tutorial-yfiles-basic-features/09-data-binding/index.html b/demos/tutorial-yfiles-basic-features/09-data-binding/index.html
index b9bb39393..c4177f7bf 100644
--- a/demos/tutorial-yfiles-basic-features/09-data-binding/index.html
+++ b/demos/tutorial-yfiles-basic-features/09-data-binding/index.html
@@ -1,4 +1,4 @@
-
+
 
   
     
@@ -73,14 +73,10 @@
       
- Step 10/13Step 9/12 — 09 Data Binding
Tutorial: Basic Features01 Creating the View + @@ -73,13 +73,9 @@
- Step 11/13 — 10 Layout + Step 10/12 — 10 Layout
Tutorial: Basic Features01 Creating the View + @@ -73,14 +73,10 @@
- Step 12/13Step 11/12 — 11 Layout Data
Tutorial: Basic Features01 Creating the View + nodeLayoutDescriptors: (node) => new HierarchicLayoutNodeLayoutDescriptor({ // Set the alignment of the node based on the label layerAlignment: getAlignment(node) diff --git a/demos/tutorial-yfiles-basic-features/12-graph-analysis/app.js b/demos/tutorial-yfiles-basic-features/12-graph-analysis/app.js index 4b3a8b8c4..5761baf7c 100644 --- a/demos/tutorial-yfiles-basic-features/12-graph-analysis/app.js +++ b/demos/tutorial-yfiles-basic-features/12-graph-analysis/app.js @@ -52,7 +52,7 @@ configureHighlights(graphComponent) createSampleGraphAnalysis(graphComponent.graph) graphComponent.selection.setSelected( - graphComponent.graph.nodes.first(node => node.labels.first().text === '15'), + graphComponent.graph.nodes.first((node) => node.labels.first().text === '15'), true ) diff --git a/demos/tutorial-yfiles-basic-features/12-graph-analysis/app.ts b/demos/tutorial-yfiles-basic-features/12-graph-analysis/app.ts index 77c28f8d9..28b494f48 100644 --- a/demos/tutorial-yfiles-basic-features/12-graph-analysis/app.ts +++ b/demos/tutorial-yfiles-basic-features/12-graph-analysis/app.ts @@ -55,7 +55,7 @@ configureHighlights(graphComponent) createSampleGraphAnalysis(graphComponent.graph) graphComponent.selection.setSelected( - graphComponent.graph.nodes.first(node => node.labels.first().text === '15'), + graphComponent.graph.nodes.first((node) => node.labels.first().text === '15'), true ) diff --git a/demos/tutorial-yfiles-basic-features/12-graph-analysis/graph-analysis.js b/demos/tutorial-yfiles-basic-features/12-graph-analysis/graph-analysis.js index e97b00522..bdeaa35ed 100644 --- a/demos/tutorial-yfiles-basic-features/12-graph-analysis/graph-analysis.js +++ b/demos/tutorial-yfiles-basic-features/12-graph-analysis/graph-analysis.js @@ -33,7 +33,7 @@ import { GraphEditorInputMode, IEdge, INode, Reachability, ShortestPath } from ' * @param {!GraphComponent} graphComponent */ export function runReachabilityAlgorithm(graphComponent) { - const nodes = graphComponent.graph.nodes.filter(node => !graphComponent.graph.isGroupNode(node)) + const nodes = graphComponent.graph.nodes.filter((node) => !graphComponent.graph.isGroupNode(node)) if (nodes.size === 0) { return } @@ -56,7 +56,7 @@ export function runReachabilityAlgorithm(graphComponent) { const reachabilityResult = reachability.run(graphComponent.graph) // highlight the reachable nodes - reachabilityResult.reachableNodes.forEach(n => { + reachabilityResult.reachableNodes.forEach((n) => { graphComponent.highlightIndicatorManager.addHighlight(n) }) } @@ -71,7 +71,7 @@ export function runShortestPathAlgorithm(graphComponent) { const graph = graphComponent.graph - const nodes = graph.nodes.filter(node => !graph.isGroupNode(node)) + const nodes = graph.nodes.filter((node) => !graph.isGroupNode(node)) if (nodes.size < 2) { return } @@ -96,7 +96,7 @@ export function runShortestPathAlgorithm(graphComponent) { sink: sinkNode, directed: false, // don't consider edge direction // calculate the cost per edge as the distance between source and target node - costs: edge => + costs: (edge) => edge.sourceNode.layout.center.subtract(edge.targetNode.layout.center).vectorLength }) const shortestPathResult = shortestPath.run(graph) @@ -106,16 +106,16 @@ export function runShortestPathAlgorithm(graphComponent) { const endNode = shortestPathResult.path?.end const pathEdges = shortestPathResult.edges - if (!isFinite(pathDistance)) { + if (!Number.isFinite(pathDistance)) { return } - pathNodes.forEach(node => { + pathNodes.forEach((node) => { graphComponent.highlightIndicatorManager.addHighlight(node) }) graph.edges - .filter(edge => pathEdges.contains(edge)) + .filter((edge) => pathEdges.contains(edge)) // and we select all matching edges - .forEach(edge => graphComponent.highlightIndicatorManager.addHighlight(edge)) + .forEach((edge) => graphComponent.highlightIndicatorManager.addHighlight(edge)) // finally, we use the explicit "path.end" field to show the distance as a tooltip above // the sink node diff --git a/demos/tutorial-yfiles-basic-features/12-graph-analysis/graph-analysis.ts b/demos/tutorial-yfiles-basic-features/12-graph-analysis/graph-analysis.ts index 1d4c79058..d98636452 100644 --- a/demos/tutorial-yfiles-basic-features/12-graph-analysis/graph-analysis.ts +++ b/demos/tutorial-yfiles-basic-features/12-graph-analysis/graph-analysis.ts @@ -40,7 +40,7 @@ import { */ export function runReachabilityAlgorithm(graphComponent: GraphComponent): void { const nodes = graphComponent.graph.nodes.filter( - node => !graphComponent.graph.isGroupNode(node) + (node) => !graphComponent.graph.isGroupNode(node) ) if (nodes.size === 0) { return @@ -78,7 +78,7 @@ export function runShortestPathAlgorithm(graphComponent: GraphComponent): void { const graph = graphComponent.graph - const nodes = graph.nodes.filter(node => !graph.isGroupNode(node)) + const nodes = graph.nodes.filter((node) => !graph.isGroupNode(node)) if (nodes.size < 2) { return } @@ -114,7 +114,7 @@ export function runShortestPathAlgorithm(graphComponent: GraphComponent): void { const endNode = shortestPathResult.path?.end const pathEdges = shortestPathResult.edges - if (!isFinite(pathDistance)) { + if (!Number.isFinite(pathDistance)) { return } pathNodes.forEach((node: INode): void => { @@ -123,7 +123,7 @@ export function runShortestPathAlgorithm(graphComponent: GraphComponent): void { graph.edges .filter((edge: IEdge): boolean => pathEdges.contains(edge)) // and we select all matching edges - .forEach(edge => + .forEach((edge) => graphComponent.highlightIndicatorManager.addHighlight(edge) ) diff --git a/demos/tutorial-yfiles-basic-features/12-graph-analysis/index.html b/demos/tutorial-yfiles-basic-features/12-graph-analysis/index.html index 399b0aec1..09bcb9e26 100644 --- a/demos/tutorial-yfiles-basic-features/12-graph-analysis/index.html +++ b/demos/tutorial-yfiles-basic-features/12-graph-analysis/index.html @@ -1,4 +1,4 @@ - + @@ -73,14 +73,10 @@
- Step 13/13Step 12/12 — 12 Analysis Algorithms
Tutorial: Basic Features01 Creating the View node.ports).concat(this.$edges.flatMap(edge => edge.ports)) + this.$nodes.flatMap((node) => node.ports).concat(this.$edges.flatMap((edge) => edge.ports)) ) this.$labels = new ListEnumerable( this.$nodes - .flatMap(node => node.labels) + .flatMap((node) => node.labels) .concat( - this.$edges.flatMap(edge => edge.labels).concat(this.$ports.flatMap(port => port.labels)) + this.$edges + .flatMap((edge) => edge.labels) + .concat(this.$ports.flatMap((port) => port.labels)) ) ) } @@ -336,8 +338,8 @@ export class AggregationGraphWrapper extends GraphWrapperBase { // hide adjacent aggregation edges (which are not hidden by filtered graph) this.edgesAt(portOwner, AdjacencyTypes.ALL) - .filter(edge => edge instanceof AggregationEdge) - .forEach(edge => { + .filter((edge) => edge instanceof AggregationEdge) + .forEach((edge) => { this.$hide(edge) }) this.$filteredOriginalNodes.add(portOwner) @@ -348,7 +350,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { * @param {!(AggregationNode|AggregationEdge|AggregationPort)} portOwner */ $hideAdjacentEdges(portOwner) { - this.edgesAt(portOwner, AdjacencyTypes.ALL).forEach(edge => this.$hide(edge)) + this.edgesAt(portOwner, AdjacencyTypes.ALL).forEach((edge) => this.$hide(edge)) } /** @@ -393,7 +395,8 @@ export class AggregationGraphWrapper extends GraphWrapperBase { $showAdjacentEdges(portOwner) { // - cannot use EdgesAt() here, since hidden edges are not considered there const adjacentEdges = this.$edges.filter( - edge => portOwner.ports.includes(edge.sourcePort) || portOwner.ports.includes(edge.targetPort) + (edge) => + portOwner.ports.includes(edge.sourcePort) || portOwner.ports.includes(edge.targetPort) ) for (const edge of adjacentEdges) { if (this.ports.includes(edge.sourcePort) && this.ports.includes(edge.targetPort)) { @@ -470,7 +473,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { * @param {*} [tag] */ aggregate(nodes, layout, style, tag) { - const badNode = nodes.find(node => !this.contains(node)) + const badNode = nodes.find((node) => !this.contains(node)) if (badNode != null) { throw new Error( `ArgumentError: Affected parameter nodes: Cannot aggregate node ${badNode} that is not in this graph.` @@ -696,7 +699,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { $replaceMissingEdgesCore(adjacencyType, node, seenNodes) { const isIncoming = adjacencyType === AdjacencyTypes.INCOMING - let edgesAt = this.$aggregationEdges.filter(edge => + let edgesAt = this.$aggregationEdges.filter((edge) => node.ports.includes(isIncoming ? edge.targetPort : edge.sourcePort) ) @@ -738,7 +741,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { * @returns {?AggregationNode} */ $findAggregationNode(node) { - return this.aggregationNodes.find(n => n.aggregatedNodes.includes(node)) + return this.aggregationNodes.find((n) => n.aggregatedNodes.includes(node)) } /** @@ -888,14 +891,14 @@ export class AggregationGraphWrapper extends GraphWrapperBase { isAggregationItem ? this.aggregationNodeDefaults : this.isGroupNode((tmp = port.owner) instanceof INode ? tmp : null) - ? this.wrappedGraph.groupNodeDefaults - : this.wrappedGraph.nodeDefaults + ? this.wrappedGraph.groupNodeDefaults + : this.wrappedGraph.nodeDefaults ).ports.autoCleanUp if (!autoCleanUp) { return } let edgesAtPort = this.$aggregationEdges.filter( - edge => edge.sourcePort === port || edge.targetPort === port + (edge) => edge.sourcePort === port || edge.targetPort === port ).size if (!isAggregationItem) { edgesAtPort += super.edgesAt(port, AdjacencyTypes.ALL).size @@ -993,17 +996,18 @@ export class AggregationGraphWrapper extends GraphWrapperBase { return IListEnumerable.EMPTY case AdjacencyTypes.INCOMING: return new ListEnumerable( - this.edges.filter(edge => owner.ports.includes(edge.targetPort)) + this.edges.filter((edge) => owner.ports.includes(edge.targetPort)) ) case AdjacencyTypes.OUTGOING: return new ListEnumerable( - this.edges.filter(edge => owner.ports.includes(edge.sourcePort)) + this.edges.filter((edge) => owner.ports.includes(edge.sourcePort)) ) default: case AdjacencyTypes.ALL: return new ListEnumerable( this.edges.filter( - edge => owner.ports.includes(edge.sourcePort) || owner.ports.includes(edge.targetPort) + (edge) => + owner.ports.includes(edge.sourcePort) || owner.ports.includes(edge.targetPort) ) ) } @@ -1012,13 +1016,13 @@ export class AggregationGraphWrapper extends GraphWrapperBase { case AdjacencyTypes.NONE: return IListEnumerable.EMPTY case AdjacencyTypes.INCOMING: - return new ListEnumerable(this.edges.filter(edge => owner === edge.targetPort)) + return new ListEnumerable(this.edges.filter((edge) => owner === edge.targetPort)) case AdjacencyTypes.OUTGOING: - return new ListEnumerable(this.edges.filter(edge => owner === edge.sourcePort)) + return new ListEnumerable(this.edges.filter((edge) => owner === edge.sourcePort)) default: case AdjacencyTypes.ALL: return new ListEnumerable( - this.edges.filter(edge => owner === edge.sourcePort || owner === edge.targetPort) + this.edges.filter((edge) => owner === edge.sourcePort || owner === edge.targetPort) ) } } @@ -1088,7 +1092,12 @@ export class AggregationGraphWrapper extends GraphWrapperBase { if (!this.contains(node)) { throw new Error('ArgumentError: Affected parameter node: Node is not in this graph.') } - if (isNaN(layout.x) || isNaN(layout.y) || isNaN(layout.width) || isNaN(layout.height)) { + if ( + Number.isNaN(layout.x) || + Number.isNaN(layout.y) || + Number.isNaN(layout.width) || + Number.isNaN(layout.height) + ) { throw new Error( `ArgumentError: Affected parameter layout: The layout must not contain a NaN value. ${layout}` ) @@ -1195,7 +1204,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { if (!this.contains(edge)) { throw new Error('ArgumentError: Affected parameter edge: Edge is not in this graph.') } - if (isNaN(location.x) || isNaN(location.y)) { + if (Number.isNaN(location.x) || Number.isNaN(location.y)) { throw new Error( 'ArgumentError: Affected parameter location: The location must not contain a NaN value.' ) @@ -1227,7 +1236,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { if (!this.contains(bend)) { throw new Error('ArgumentError: Affected parameter bend: Edge is not in this graph.') } - if (isNaN(location.x) || isNaN(location.y)) { + if (Number.isNaN(location.x) || Number.isNaN(location.y)) { throw new Error( 'ArgumentError: Affected parameter location: The location must not contain a NaN value.' ) @@ -1277,7 +1286,10 @@ export class AggregationGraphWrapper extends GraphWrapperBase { if (!this.contains(owner)) { throw new Error('ArgumentError: Affected parameter owner: Owner is not in this graph.') } - if (!!preferredSize && (isNaN(preferredSize.width) || isNaN(preferredSize.height))) { + if ( + !!preferredSize && + (Number.isNaN(preferredSize.width) || Number.isNaN(preferredSize.height)) + ) { throw new Error( 'ArgumentError: Affected parameter preferredSize: The size must not contain a NaN value.' ) @@ -1377,7 +1389,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { if (!this.contains(label)) { throw new Error('ArgumentError: Affected parameter label: Label is not in this graph.') } - if (isNaN(preferredSize.width) || isNaN(preferredSize.height)) { + if (Number.isNaN(preferredSize.width) || Number.isNaN(preferredSize.height)) { throw new Error( 'ArgumentError: Affected parameter preferredSize: The size must not contain a NaN value.' ) @@ -1488,7 +1500,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { getChildren(node) { if (!node) { // top-level nodes - return new ListEnumerable(this.nodes.filter(n => this.getParent(n) === null)) + return new ListEnumerable(this.nodes.filter((n) => this.getParent(n) === null)) } if (!this.contains(node)) { @@ -1501,7 +1513,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { } return new ListEnumerable( - super.getChildren(node).concat(this.aggregationNodes.filter(an => an.parent === node)) + super.getChildren(node).concat(this.aggregationNodes.filter((an) => an.parent === node)) ) } @@ -1520,7 +1532,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { } const aggregationNodeParent = this.aggregationNodes.find( - parent => !!parent.children && parent.children.includes(node) + (parent) => !!parent.children && parent.children.includes(node) ) if (aggregationNodeParent) { return aggregationNodeParent @@ -1696,7 +1708,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { } /** - * @template {*} T + * @template T * @param {!Class.} type * @returns {?T} */ @@ -1875,7 +1887,7 @@ class AggregationLookupDecorator extends BaseClass(ILookup, ILookupDecorator) { } /** - * @template {*} T + * @template T * @param {!Class.} type * @returns {?T} */ @@ -2141,7 +2153,7 @@ class AggregationNode extends BaseClass(INode) { } /** - * @template {*} T + * @template T * @param {!Class.} type * @returns {?T} */ @@ -2159,7 +2171,7 @@ class AggregationNode extends BaseClass(INode) { } /** - * @template {*} T + * @template T * @param {!Class.} type * @returns {?T} */ @@ -2345,7 +2357,7 @@ class AggregationEdge extends BaseClass(IEdge) { } /** - * @template {*} T + * @template T * @param {!Class.} type * @returns {?T} */ @@ -2354,7 +2366,7 @@ class AggregationEdge extends BaseClass(IEdge) { } /** - * @template {*} T + * @template T * @param {!Class.} type * @returns {?T} */ @@ -2458,7 +2470,7 @@ class AggregationBend extends BaseClass(IBend) { } /** - * @template {*} T + * @template T * @param {!Class.} type * @returns {?T} */ @@ -2467,7 +2479,7 @@ class AggregationBend extends BaseClass(IBend) { } /** - * @template {*} T + * @template T * @param {!Class.} type * @returns {?T} */ @@ -2594,7 +2606,7 @@ class AggregationPort extends BaseClass(IPort) { } /** - * @template {*} T + * @template T * @param {!Class.} type * @returns {?T} */ @@ -2603,7 +2615,7 @@ class AggregationPort extends BaseClass(IPort) { } /** - * @template {*} T + * @template T * @param {!Class.} type * @returns {?T} */ @@ -2780,7 +2792,7 @@ class AggregationLabel extends BaseClass(ILabel) { } /** - * @template {*} T + * @template T * @param {!Class.} type * @returns {?T} */ @@ -2789,7 +2801,7 @@ class AggregationLabel extends BaseClass(ILabel) { } /** - * @template {*} T + * @template T * @param {!Class.} type * @returns {?T} */ diff --git a/demos/utils/AggregationGraphWrapper.ts b/demos/utils/AggregationGraphWrapper.ts index 3127a0187..747a4dfc2 100644 --- a/demos/utils/AggregationGraphWrapper.ts +++ b/demos/utils/AggregationGraphWrapper.ts @@ -185,13 +185,15 @@ export class AggregationGraphWrapper extends GraphWrapperBase { ) ) this.$ports = new ListEnumerable( - this.$nodes.flatMap(node => node.ports).concat(this.$edges.flatMap(edge => edge.ports)) + this.$nodes.flatMap((node) => node.ports).concat(this.$edges.flatMap((edge) => edge.ports)) ) this.$labels = new ListEnumerable( this.$nodes - .flatMap(node => node.labels) + .flatMap((node) => node.labels) .concat( - this.$edges.flatMap(edge => edge.labels).concat(this.$ports.flatMap(port => port.labels)) + this.$edges + .flatMap((edge) => edge.labels) + .concat(this.$ports.flatMap((port) => port.labels)) ) ) } @@ -305,8 +307,8 @@ export class AggregationGraphWrapper extends GraphWrapperBase { // hide adjacent aggregation edges (which are not hidden by filtered graph) this.edgesAt(portOwner, AdjacencyTypes.ALL) - .filter(edge => edge instanceof AggregationEdge) - .forEach(edge => { + .filter((edge) => edge instanceof AggregationEdge) + .forEach((edge) => { this.$hide(edge) }) this.$filteredOriginalNodes.add(portOwner) @@ -314,7 +316,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { } private $hideAdjacentEdges(portOwner: AggregationNode | AggregationEdge | AggregationPort): void { - this.edgesAt(portOwner, AdjacencyTypes.ALL).forEach(edge => this.$hide(edge)) + this.edgesAt(portOwner, AdjacencyTypes.ALL).forEach((edge) => this.$hide(edge)) } /** @@ -355,7 +357,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { private $showAdjacentEdges(portOwner: IPortOwner): void { // - cannot use EdgesAt() here, since hidden edges are not considered there const adjacentEdges = this.$edges.filter( - edge => + (edge) => portOwner.ports.includes(edge.sourcePort!) || portOwner.ports.includes(edge.targetPort!) ) for (const edge of adjacentEdges) { @@ -422,7 +424,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { style?: INodeStyle, tag?: any ): INode { - const badNode = nodes.find(node => !this.contains(node)) + const badNode = nodes.find((node) => !this.contains(node)) if (badNode != null) { throw new Error( `ArgumentError: Affected parameter nodes: Cannot aggregate node ${badNode} that is not in this graph.` @@ -645,7 +647,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { ): void { const isIncoming = adjacencyType === AdjacencyTypes.INCOMING - let edgesAt: IEnumerable = this.$aggregationEdges.filter(edge => + let edgesAt: IEnumerable = this.$aggregationEdges.filter((edge) => node.ports.includes(isIncoming ? edge.targetPort : edge.sourcePort) ) @@ -683,7 +685,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { } private $findAggregationNode(node: INode): AggregationNode | null { - return this.aggregationNodes.find(n => n.aggregatedNodes.includes(node)) + return this.aggregationNodes.find((n) => n.aggregatedNodes.includes(node)) } /** @@ -826,14 +828,14 @@ export class AggregationGraphWrapper extends GraphWrapperBase { isAggregationItem ? this.aggregationNodeDefaults : this.isGroupNode((tmp = port.owner) instanceof INode ? tmp : null) - ? this.wrappedGraph!.groupNodeDefaults - : this.wrappedGraph!.nodeDefaults + ? this.wrappedGraph!.groupNodeDefaults + : this.wrappedGraph!.nodeDefaults ).ports.autoCleanUp if (!autoCleanUp) { return } let edgesAtPort = this.$aggregationEdges.filter( - edge => edge.sourcePort === port || edge.targetPort === port + (edge) => edge.sourcePort === port || edge.targetPort === port ).size if (!isAggregationItem) { edgesAtPort += super.edgesAt(port, AdjacencyTypes.ALL).size @@ -916,17 +918,17 @@ export class AggregationGraphWrapper extends GraphWrapperBase { return IListEnumerable.EMPTY case AdjacencyTypes.INCOMING: return new ListEnumerable( - this.edges.filter(edge => owner.ports.includes(edge.targetPort!)) + this.edges.filter((edge) => owner.ports.includes(edge.targetPort!)) ) case AdjacencyTypes.OUTGOING: return new ListEnumerable( - this.edges.filter(edge => owner.ports.includes(edge.sourcePort!)) + this.edges.filter((edge) => owner.ports.includes(edge.sourcePort!)) ) default: case AdjacencyTypes.ALL: return new ListEnumerable( this.edges.filter( - edge => + (edge) => owner.ports.includes(edge.sourcePort!) || owner.ports.includes(edge.targetPort!) ) ) @@ -936,13 +938,13 @@ export class AggregationGraphWrapper extends GraphWrapperBase { case AdjacencyTypes.NONE: return IListEnumerable.EMPTY case AdjacencyTypes.INCOMING: - return new ListEnumerable(this.edges.filter(edge => owner === edge.targetPort)) + return new ListEnumerable(this.edges.filter((edge) => owner === edge.targetPort)) case AdjacencyTypes.OUTGOING: - return new ListEnumerable(this.edges.filter(edge => owner === edge.sourcePort)) + return new ListEnumerable(this.edges.filter((edge) => owner === edge.sourcePort)) default: case AdjacencyTypes.ALL: return new ListEnumerable( - this.edges.filter(edge => owner === edge.sourcePort || owner === edge.targetPort) + this.edges.filter((edge) => owner === edge.sourcePort || owner === edge.targetPort) ) } } @@ -999,7 +1001,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { if (!this.contains(node)) { throw new Error('ArgumentError: Affected parameter node: Node is not in this graph.') } - if (isNaN(layout.x) || isNaN(layout.y) || isNaN(layout.width) || isNaN(layout.height)) { + if (Number.isNaN(layout.x) || Number.isNaN(layout.y) || Number.isNaN(layout.width) || Number.isNaN(layout.height)) { throw new Error( `ArgumentError: Affected parameter layout: The layout must not contain a NaN value. ${layout}` ) @@ -1106,7 +1108,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { if (!this.contains(edge)) { throw new Error('ArgumentError: Affected parameter edge: Edge is not in this graph.') } - if (isNaN(location.x) || isNaN(location.y)) { + if (Number.isNaN(location.x) || Number.isNaN(location.y)) { throw new Error( 'ArgumentError: Affected parameter location: The location must not contain a NaN value.' ) @@ -1134,7 +1136,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { if (!this.contains(bend)) { throw new Error('ArgumentError: Affected parameter bend: Edge is not in this graph.') } - if (isNaN(location.x) || isNaN(location.y)) { + if (Number.isNaN(location.x) || Number.isNaN(location.y)) { throw new Error( 'ArgumentError: Affected parameter location: The location must not contain a NaN value.' ) @@ -1191,7 +1193,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { if (!this.contains(owner)) { throw new Error('ArgumentError: Affected parameter owner: Owner is not in this graph.') } - if (!!preferredSize && (isNaN(preferredSize.width) || isNaN(preferredSize.height))) { + if (!!preferredSize && (Number.isNaN(preferredSize.width) || Number.isNaN(preferredSize.height))) { throw new Error( 'ArgumentError: Affected parameter preferredSize: The size must not contain a NaN value.' ) @@ -1277,7 +1279,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { if (!this.contains(label)) { throw new Error('ArgumentError: Affected parameter label: Label is not in this graph.') } - if (isNaN(preferredSize.width) || isNaN(preferredSize.height)) { + if (Number.isNaN(preferredSize.width) || Number.isNaN(preferredSize.height)) { throw new Error( 'ArgumentError: Affected parameter preferredSize: The size must not contain a NaN value.' ) @@ -1383,7 +1385,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { public getChildren(node: INode): IListEnumerable { if (!node) { // top-level nodes - return new ListEnumerable(this.nodes.filter(n => this.getParent(n) === null)) + return new ListEnumerable(this.nodes.filter((n) => this.getParent(n) === null)) } if (!this.contains(node)) { @@ -1396,7 +1398,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { } return new ListEnumerable( - super.getChildren(node).concat(this.aggregationNodes.filter(an => an.parent === node)) + super.getChildren(node).concat(this.aggregationNodes.filter((an) => an.parent === node)) ) } @@ -1411,7 +1413,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { } const aggregationNodeParent = this.aggregationNodes.find( - parent => !!parent.children && parent.children.includes(node) + (parent) => !!parent.children && parent.children.includes(node) ) if (aggregationNodeParent) { return aggregationNodeParent @@ -1568,7 +1570,7 @@ export class AggregationGraphWrapper extends GraphWrapperBase { } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - public lookup(type: Class): T | null { + public lookup(type: Class): T | null { return this.$lookupDecorator.lookup(type) } @@ -1716,7 +1718,7 @@ class AggregationLookupDecorator extends BaseClass(ILookup, ILookupDecorator) { } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - public lookup(type: Class): T | null { + public lookup(type: Class): T | null { if (type === ILookupDecorator.$class) { this.$wrappedDecorator = this.$graph.baseLookup(type) as ILookupDecorator return this as unknown as T @@ -1903,7 +1905,7 @@ class AggregationNode extends BaseClass(INode) { } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - public innerLookup(type: Class): T | null { + public innerLookup(type: Class): T | null { if (type === INodeStyle.$class) { return this.style as T } @@ -1917,7 +1919,7 @@ class AggregationNode extends BaseClass(INode) { } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - lookup(type: Class): T | null { + lookup(type: Class): T | null { return this.graph ? (this.graph.delegateLookup(this, type) as T) : null } @@ -2047,12 +2049,12 @@ class AggregationEdge extends BaseClass(IEdge) { } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - lookup(type: Class): T | null { + lookup(type: Class): T | null { return this.graph ? (this.graph.delegateLookup(this, type) as T) : null } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - public innerLookup(type: Class): T | null { + public innerLookup(type: Class): T | null { if (type === IEdgeStyle.$class) { return this.style as T } @@ -2126,12 +2128,12 @@ class AggregationBend extends BaseClass(IBend) { } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - lookup(type: Class): T | null { + lookup(type: Class): T | null { return this.graph ? (this.graph.delegateLookup(this, type) as T) : null } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - public innerLookup(type: Class): T | null { + public innerLookup(type: Class): T | null { if (type.isInstance(this.location)) { return this.location } @@ -2220,12 +2222,12 @@ class AggregationPort extends BaseClass(IPort) { } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - lookup(type: Class): T | null { + lookup(type: Class): T | null { return this.graph ? (this.graph.delegateLookup(this, type) as T) : null } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - public innerLookup(type: Class): T | null { + public innerLookup(type: Class): T | null { if (type === IPortStyle.$class) { return this.style as T } @@ -2349,12 +2351,12 @@ class AggregationLabel extends BaseClass(ILabel) { } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - lookup(type: Class): T | null { + lookup(type: Class): T | null { return this.graph ? (this.graph.delegateLookup(this, type) as T) : null } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - public innerLookup(type: Class): T | null { + public innerLookup(type: Class): T | null { if (type === ILabelStyle.$class) { return this.style as T } diff --git a/demos/utils/ContextMenu.js b/demos/utils/ContextMenu.js index a1893eee5..fd57cfd01 100644 --- a/demos/utils/ContextMenu.js +++ b/demos/utils/ContextMenu.js @@ -33,7 +33,7 @@ import { BrowserDetection } from './BrowserDetection.js' * A demo implementation of a context menu that is used in various yFiles demos. * * The yFiles for HTML library can easily be used with any context menu implementation. For your project, we recommend to - * use the context menu that comes with your GUI framework, if any. However, feel tree to re-use parts of this + * use the context menu that comes with your GUI framework, if any. However, feel free to re-use parts of this * implementation in your project if it fits your needs. * * This context menu uses buttons as menu items and thus works with mouse, touch, and keyboard. Once a menu item is @@ -66,7 +66,7 @@ export class ContextMenu { this.onClosedCallbackField = null // Listeners for focus events since this menu closes itself if it loses the focus. - this.focusOutListener = evt => { + this.focusOutListener = (evt) => { this.onFocusOut(evt.relatedTarget) } @@ -79,7 +79,7 @@ export class ContextMenu { // A click listener that closes the menu and calls the onCloseCallback. // This way, the individual menu items do not need to handle this by themselves. - this.closeListener = evt => { + this.closeListener = (evt) => { evt.stopPropagation() evt.preventDefault() this.close() @@ -88,16 +88,12 @@ export class ContextMenu { this.onClosedCallback() } - // A ESC key press listener that closes the menu and calls the callback. - this.closeOnEscListener = evt => { + // An ESC key press listener that closes the menu and calls the callback. + this.closeOnEscListener = (evt) => { if (evt.key === 'Escape' && this.element.parentNode) { this.closeListener(evt) } } - - // We lower the default long-press duration to 100ms to avoid conflicts between the context menu and - // other gestures on long-press, e.g. edge creation. - graphComponent.longPressTime = TimeSpan.fromMilliseconds(100) } /** @@ -142,7 +138,7 @@ export class ContextMenu { * This menu only shows if it has at least one menu item. * * @param {!Point} location The location of the menu relative to the left edge of the entire - * document. This are typically the pageX and pageY coordinates of the contextmenu event. + * document. These are typically the pageX and pageY coordinates of the contextmenu event. */ show(location) { if (this.element.childElementCount <= 0) { @@ -242,21 +238,22 @@ export class ContextMenu { * open the context menu occurred. It gets the location of the event. */ addOpeningEventListeners(graphComponent, openingCallback) { - const contextMenuListener = evt => { - evt.preventDefault() - if (this.isOpen) { - // might be open already because of the longpress listener - return - } - openingCallback(new Point(evt.pageX, evt.pageY)) - } - // Listen for the contextmenu event // Note: On Linux based systems (e.g. Ubuntu), the contextmenu event is fired on mouse down - // which triggers the ContextMenuInputMode before the ClickInputMode. Therefore handling the - // event, will prevent the ItemRightClicked event from firing. + // which triggers the ContextMenuInputMode before the ClickInputMode. Therefore, handling the + // event will prevent the ItemRightClicked event from firing. // For more information, see https://docs.yworks.com/yfileshtml/#/kb/article/780/ - graphComponent.div.addEventListener('contextmenu', contextMenuListener, false) + graphComponent.div.addEventListener( + 'contextmenu', + (evt) => { + evt.preventDefault() + if (!this.isOpen) { + // might be open already because of the long press event listener + openingCallback(new Point(evt.pageX, evt.pageY)) + } + }, + false + ) if (BrowserDetection.safariVersion > 0 || BrowserDetection.iOSVersion > 0) { // Additionally add a long press listener especially for iOS, since it does not fire the contextmenu event. @@ -267,10 +264,9 @@ export class ContextMenu { clearTimeout(contextMenuTimer) return } - contextMenuTimer = setTimeout(() => { - openingCallback( - graphComponent.toPageFromView(graphComponent.toViewCoordinates(evt.location)) - ) + contextMenuTimer = window.setTimeout(() => { + const viewLocation = graphComponent.toViewCoordinates(evt.location) + openingCallback(graphComponent.toPageFromView(viewLocation)) }, 500) }) graphComponent.addTouchUpListener(() => { @@ -279,7 +275,7 @@ export class ContextMenu { } // Listen to the context menu key to make it work in Chrome - graphComponent.div.addEventListener('keyup', evt => { + graphComponent.div.addEventListener('keyup', (evt) => { if (evt.key === 'ContextMenu') { evt.preventDefault() openingCallback(ContextMenu.getCenterInPage(graphComponent.div)) diff --git a/demos/utils/ContextMenu.ts b/demos/utils/ContextMenu.ts index 4eccaee5a..7032ef818 100644 --- a/demos/utils/ContextMenu.ts +++ b/demos/utils/ContextMenu.ts @@ -34,7 +34,7 @@ import { BrowserDetection } from './BrowserDetection' * A demo implementation of a context menu that is used in various yFiles demos. * * The yFiles for HTML library can easily be used with any context menu implementation. For your project, we recommend to - * use the context menu that comes with your GUI framework, if any. However, feel tree to re-use parts of this + * use the context menu that comes with your GUI framework, if any. However, feel free to re-use parts of this * implementation in your project if it fits your needs. * * This context menu uses buttons as menu items and thus works with mouse, touch, and keyboard. Once a menu item is @@ -89,16 +89,12 @@ export class ContextMenu { this.onClosedCallback() } - // A ESC key press listener that closes the menu and calls the callback. + // An ESC key press listener that closes the menu and calls the callback. this.closeOnEscListener = (evt): void => { if (evt.key === 'Escape' && this.element.parentNode) { this.closeListener(evt) } } - - // We lower the default long-press duration to 100ms to avoid conflicts between the context menu and - // other gestures on long-press, e.g. edge creation. - graphComponent.longPressTime = TimeSpan.fromMilliseconds(100) } /** @@ -140,7 +136,7 @@ export class ContextMenu { * This menu only shows if it has at least one menu item. * * @param location The location of the menu relative to the left edge of the entire - * document. This are typically the pageX and pageY coordinates of the contextmenu event. + * document. These are typically the pageX and pageY coordinates of the contextmenu event. */ show(location: Point): void { if (this.element.childElementCount <= 0) { @@ -240,21 +236,22 @@ export class ContextMenu { graphComponent: GraphComponent, openingCallback: (p: Point) => void ): void { - const contextMenuListener = (evt: MouseEvent): void => { - evt.preventDefault() - if (this.isOpen) { - // might be open already because of the longpress listener - return - } - openingCallback(new Point(evt.pageX, evt.pageY)) - } - // Listen for the contextmenu event // Note: On Linux based systems (e.g. Ubuntu), the contextmenu event is fired on mouse down - // which triggers the ContextMenuInputMode before the ClickInputMode. Therefore handling the - // event, will prevent the ItemRightClicked event from firing. + // which triggers the ContextMenuInputMode before the ClickInputMode. Therefore, handling the + // event will prevent the ItemRightClicked event from firing. // For more information, see https://docs.yworks.com/yfileshtml/#/kb/article/780/ - graphComponent.div.addEventListener('contextmenu', contextMenuListener, false) + graphComponent.div.addEventListener( + 'contextmenu', + (evt: MouseEvent): void => { + evt.preventDefault() + if (!this.isOpen) { + // might be open already because of the long press event listener + openingCallback(new Point(evt.pageX, evt.pageY)) + } + }, + false + ) if (BrowserDetection.safariVersion > 0 || BrowserDetection.iOSVersion > 0) { // Additionally add a long press listener especially for iOS, since it does not fire the contextmenu event. @@ -265,11 +262,10 @@ export class ContextMenu { clearTimeout(contextMenuTimer) return } - contextMenuTimer = setTimeout(() => { - openingCallback( - graphComponent.toPageFromView(graphComponent.toViewCoordinates(evt.location)) - ) - }, 500) as any as number + contextMenuTimer = window.setTimeout(() => { + const viewLocation = graphComponent.toViewCoordinates(evt.location) + openingCallback(graphComponent.toPageFromView(viewLocation)) + }, 500) }) graphComponent.addTouchUpListener(() => { clearTimeout(contextMenuTimer) @@ -277,7 +273,7 @@ export class ContextMenu { } // Listen to the context menu key to make it work in Chrome - graphComponent.div.addEventListener('keyup', evt => { + graphComponent.div.addEventListener('keyup', (evt) => { if (evt.key === 'ContextMenu') { evt.preventDefault() openingCallback(ContextMenu.getCenterInPage(graphComponent.div)) diff --git a/demos/utils/DragAndDropPanel.js b/demos/utils/DragAndDropPanel.js index 8da61037d..0f3874dab 100644 --- a/demos/utils/DragAndDropPanel.js +++ b/demos/utils/DragAndDropPanel.js @@ -177,7 +177,7 @@ export class DragAndDropPanel { const originalNode = original instanceof INode ? original : original.modelItem const node = graph.createNode(originalNode.layout, originalNode.style, originalNode.tag) - originalNode.labels.forEach(label => { + originalNode.labels.forEach((label) => { graph.addLabel( node, label.text, @@ -187,7 +187,7 @@ export class DragAndDropPanel { label.tag ) }) - originalNode.ports.forEach(port => { + originalNode.ports.forEach((port) => { graph.addPort(node, port.locationParameter, port.style, port.tag) }) @@ -284,7 +284,7 @@ export class DragAndDropPanel { element.addEventListener( 'mousedown', - evt => { + (evt) => { if (evt.button !== 0) { return } @@ -294,7 +294,7 @@ export class DragAndDropPanel { false ) - const touchStartListener = evt => { + const touchStartListener = (evt) => { doDragOperation() evt.preventDefault() } @@ -302,7 +302,7 @@ export class DragAndDropPanel { if (window.PointerEvent !== undefined) { element.addEventListener( 'pointerdown', - evt => { + (evt) => { if (evt.pointerType === 'touch' || evt.pointerType === 'pen') { touchStartListener(evt) } @@ -312,7 +312,7 @@ export class DragAndDropPanel { } else if (window.MSPointerEvent !== undefined) { element.addEventListener( 'MSPointerDown', - evt => { + (evt) => { if ( evt.pointerType === evt.MSPOINTER_TYPE_TOUCH || evt.pointerType === evt.MSPOINTER_TYPE_PEN diff --git a/demos/utils/DragAndDropPanel.ts b/demos/utils/DragAndDropPanel.ts index 25d888ef6..b2c4a4eca 100644 --- a/demos/utils/DragAndDropPanel.ts +++ b/demos/utils/DragAndDropPanel.ts @@ -178,7 +178,7 @@ export class DragAndDropPanel { label.tag ) }) - originalNode.ports.forEach(port => { + originalNode.ports.forEach((port) => { graph.addPort(node, port.locationParameter, port.style, port.tag) }) @@ -270,7 +270,7 @@ export class DragAndDropPanel { element.addEventListener( 'mousedown', - evt => { + (evt) => { if (evt.button !== 0) { return } @@ -288,7 +288,7 @@ export class DragAndDropPanel { if (window.PointerEvent !== undefined) { element.addEventListener( 'pointerdown', - evt => { + (evt) => { if (evt.pointerType === 'touch' || evt.pointerType === 'pen') { touchStartListener(evt) } diff --git a/demos/utils/GraphSearch.js b/demos/utils/GraphSearch.js index 7a6ef11d5..de7e8d32d 100644 --- a/demos/utils/GraphSearch.js +++ b/demos/utils/GraphSearch.js @@ -39,7 +39,7 @@ import { StyleDecorationZoomPolicy } from 'yfiles' -export default class GraphSearch { +export class GraphSearch { graphComponent searchHighlightIndicatorManager matchingNodes = [] @@ -66,7 +66,7 @@ export default class GraphSearch { graphSearch.updateAutoCompleteSuggestions(searchBox, autoCompleteSuggestions) } - searchBox.addEventListener('input', e => { + searchBox.addEventListener('input', (e) => { const input = e.target const searchText = input.value graphSearch.updateSearch(searchText) @@ -85,7 +85,7 @@ export default class GraphSearch { }) // adds the listener that will focus to the result of the search - searchBox.addEventListener('keypress', e => { + searchBox.addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault() graphSearch.zoomToSearchResult() @@ -93,7 +93,7 @@ export default class GraphSearch { }) // adds the listener to enable auto-completion - searchBox.addEventListener('keyup', e => { + searchBox.addEventListener('keyup', (e) => { if (e.key === 'Enter') { return } @@ -152,8 +152,8 @@ export default class GraphSearch { this.matchingNodes = [] if (searchText.trim() !== '') { this.graphComponent.graph.nodes - .filter(node => this.matches(node, searchText)) - .forEach(node => { + .filter((node) => this.matches(node, searchText)) + .forEach((node) => { manager.addHighlight(node) this.matchingNodes.push(node) }) @@ -195,7 +195,7 @@ export default class GraphSearch { } const maxRect = this.matchingNodes - .map(node => node.layout.toRect()) + .map((node) => node.layout.toRect()) .reduce((prev, current) => Rect.add(prev, current)) if (!maxRect.isFinite) { return Promise.resolve() @@ -220,7 +220,7 @@ export default class GraphSearch { * @returns {boolean} True if the node matches the text, false otherwise */ matches(node, text) { - return node.labels.some(label => label.text.toLowerCase().indexOf(text.toLowerCase()) !== -1) + return node.labels.some((label) => label.text.toLowerCase().indexOf(text.toLowerCase()) !== -1) } } diff --git a/demos/utils/GraphSearch.ts b/demos/utils/GraphSearch.ts index f0787589d..44f53514e 100644 --- a/demos/utils/GraphSearch.ts +++ b/demos/utils/GraphSearch.ts @@ -40,7 +40,7 @@ import { StyleDecorationZoomPolicy } from 'yfiles' -export default class GraphSearch { +export class GraphSearch { graphComponent: GraphComponent searchHighlightIndicatorManager: GraphHighlightIndicatorManager matchingNodes: INode[] = [] @@ -70,7 +70,7 @@ export default class GraphSearch { graphSearch.updateAutoCompleteSuggestions(searchBox, autoCompleteSuggestions) } - searchBox.addEventListener('input', e => { + searchBox.addEventListener('input', (e) => { const input = e.target as HTMLInputElement const searchText = input.value graphSearch.updateSearch(searchText) @@ -89,7 +89,7 @@ export default class GraphSearch { }) // adds the listener that will focus to the result of the search - searchBox.addEventListener('keypress', e => { + searchBox.addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault() graphSearch.zoomToSearchResult() @@ -97,7 +97,7 @@ export default class GraphSearch { }) // adds the listener to enable auto-completion - searchBox.addEventListener('keyup', e => { + searchBox.addEventListener('keyup', (e) => { if (e.key === 'Enter') { return } @@ -154,8 +154,8 @@ export default class GraphSearch { this.matchingNodes = [] if (searchText.trim() !== '') { this.graphComponent.graph.nodes - .filter(node => this.matches(node, searchText)) - .forEach(node => { + .filter((node) => this.matches(node, searchText)) + .forEach((node) => { manager.addHighlight(node) this.matchingNodes.push(node) }) @@ -196,7 +196,7 @@ export default class GraphSearch { } const maxRect = this.matchingNodes - .map(node => node.layout.toRect()) + .map((node) => node.layout.toRect()) .reduce((prev, current) => Rect.add(prev, current)) if (!maxRect.isFinite) { return Promise.resolve() @@ -221,7 +221,7 @@ export default class GraphSearch { * @returns True if the node matches the text, false otherwise */ matches(node: INode, text: string): boolean { - return node.labels.some(label => label.text.toLowerCase().indexOf(text.toLowerCase()) !== -1) + return node.labels.some((label) => label.text.toLowerCase().indexOf(text.toLowerCase()) !== -1) } } diff --git a/demos/utils/LoadLayoutFeaturesSampleGraph.js b/demos/utils/LoadLayoutFeaturesSampleGraph.js index 46cd12f2c..c7076b1c9 100644 --- a/demos/utils/LoadLayoutFeaturesSampleGraph.js +++ b/demos/utils/LoadLayoutFeaturesSampleGraph.js @@ -65,35 +65,37 @@ export async function loadLayoutSampleGraph(graph, fileName) { const builder = new GraphBuilder(graph) const nodesSource = builder.createNodesSource({ - data: data.nodeList.filter(node => !node.isGroup), + data: data.nodeList.filter((node) => !node.isGroup), id: 'id', tag: 'tag', layout: 'layout', parentId: 'parent' }) const groupSource = builder.createGroupNodesSource({ - data: data.nodeList.filter(node => node.isGroup), + data: data.nodeList.filter((node) => node.isGroup), id: 'id', tag: 'tag', layout: 'layout' }) const nodeCreator = nodesSource.nodeCreator - nodeCreator.styleProvider = data => getNodeStyle(data) + nodeCreator.styleProvider = (data) => getNodeStyle(data) - const nodeLabelCreator = nodeCreator.createLabelsSource(data => data.labels || []).labelCreator - nodeLabelCreator.textProvider = data => data.text - nodeLabelCreator.layoutParameterProvider = data => InteriorLabelModel.CENTER - nodeLabelCreator.styleProvider = data => getLabelStyle(2.5, data) + const nodeLabelCreator = nodeCreator.createLabelsSource((data) => data.labels || []).labelCreator + nodeLabelCreator.textProvider = (data) => data.text + nodeLabelCreator.layoutParameterProvider = (data) => InteriorLabelModel.CENTER + nodeLabelCreator.styleProvider = (data) => getLabelStyle(2.5, data) const groupCreator = groupSource.nodeCreator - groupCreator.styleProvider = data => getGroupNodeStyle(data) + groupCreator.styleProvider = (data) => getGroupNodeStyle(data) - const groupLabelCreator = groupCreator.createLabelsSource(data => data.labels || []).labelCreator - groupLabelCreator.textProvider = data => data.text - groupLabelCreator.layoutParameterProvider = data => + const groupLabelCreator = groupCreator.createLabelsSource( + (data) => data.labels || [] + ).labelCreator + groupLabelCreator.textProvider = (data) => data.text + groupLabelCreator.layoutParameterProvider = (data) => new GroupNodeLabelModel().createTabBackgroundParameter() - groupLabelCreator.styleProvider = data => getGroupLabelStyle() + groupLabelCreator.styleProvider = (data) => getGroupLabelStyle() const edgesSource = builder.createEdgesSource({ data: data.edgeList, @@ -104,13 +106,13 @@ export async function loadLayoutSampleGraph(graph, fileName) { tag: 'tag' }) const edgeCreator = edgesSource.edgeCreator - edgeCreator.styleProvider = data => getEdgeStyle(data) + edgeCreator.styleProvider = (data) => getEdgeStyle(data) - const edgeLabelCreator = edgeCreator.createLabelsSource(data => data.labels || []).labelCreator - edgeLabelCreator.textProvider = data => data.text || '' - edgeLabelCreator.layoutParameterProvider = data => + const edgeLabelCreator = edgeCreator.createLabelsSource((data) => data.labels || []).labelCreator + edgeLabelCreator.textProvider = (data) => data.text || '' + edgeLabelCreator.layoutParameterProvider = (data) => new FreeEdgeLabelModel().createDefaultParameter() - edgeLabelCreator.styleProvider = data => getLabelStyle(2.0, data) + edgeLabelCreator.styleProvider = (data) => getLabelStyle(2.0, data) builder.buildGraph() } diff --git a/demos/utils/NodeTypePanel.js b/demos/utils/NodeTypePanel.js index 05b92b8ad..a90911be7 100644 --- a/demos/utils/NodeTypePanel.js +++ b/demos/utils/NodeTypePanel.js @@ -155,7 +155,7 @@ export default class NodeTypePanel { } element.addEventListener('click', () => { if (this.currentItems) { - this.currentItems.forEach(item => { + this.currentItems.forEach((item) => { const oldType = item.tag && item.tag.type if (oldType !== type) { this.nodeTypeChanged(item, type, oldType) @@ -177,7 +177,7 @@ export default class NodeTypePanel { btn.classList.remove('current-type') } if (this.currentItems) { - this.currentItems.forEach(item => { + this.currentItems.forEach((item) => { this.div .querySelector(`.type-${(item.tag && item.tag.type) || 0}`) .classList.add('current-type') diff --git a/demos/utils/NodeTypePanel.ts b/demos/utils/NodeTypePanel.ts index ef49f9e46..6ed4f3786 100644 --- a/demos/utils/NodeTypePanel.ts +++ b/demos/utils/NodeTypePanel.ts @@ -145,7 +145,7 @@ export default class NodeTypePanel { } element.addEventListener('click', () => { if (this.currentItems) { - this.currentItems.forEach(item => { + this.currentItems.forEach((item) => { const oldType = item.tag && item.tag.type if (oldType !== type) { this.nodeTypeChanged(item, type, oldType) @@ -167,7 +167,7 @@ export default class NodeTypePanel { btn.classList.remove('current-type') } if (this.currentItems) { - this.currentItems.forEach(item => { + this.currentItems.forEach((item) => { this.div .querySelector(`.type-${(item.tag && item.tag.type) || 0}`)! .classList.add('current-type') diff --git a/demos/utils/PrintingSupport.js b/demos/utils/PrintingSupport.js index 32ab35f7c..eace38b36 100644 --- a/demos/utils/PrintingSupport.js +++ b/demos/utils/PrintingSupport.js @@ -62,8 +62,6 @@ export default class PrintingSupport { // this should be set to the same value. projection = Matrix.IDENTITY - constructor() {} - /** * Prints the detail of the given graph that is specified by either a * rectangle in world coordinates or a collection of world coordinate points which @@ -100,11 +98,11 @@ export default class PrintingSupport { targetRect = this.getBoundsFromPoints( graphComponent .getCanvasObjects(graphComponent.rootGroup) - .map(co => + .map((co) => co.descriptor.getBoundsProvider(co.userObject).getBounds(graphComponent.canvasContext) ) - .filter(bounds => bounds.isFinite) - .flatMap(bounds => + .filter((bounds) => bounds.isFinite) + .flatMap((bounds) => IEnumerable.from([ bounds.topLeft, bounds.topRight, @@ -221,7 +219,7 @@ export default class PrintingSupport { } // This function has to be global, because it is called from the print preview window. - window.addPrintDom = win => { + window.addPrintDom = (win) => { win.document.body.innerHTML = resultingHTML win.document.close() // Wait for everything to be rendered before printing diff --git a/demos/utils/PrintingSupport.ts b/demos/utils/PrintingSupport.ts index 88efa0e0f..237ae05a2 100644 --- a/demos/utils/PrintingSupport.ts +++ b/demos/utils/PrintingSupport.ts @@ -62,8 +62,6 @@ export default class PrintingSupport { // this should be set to the same value. projection = Matrix.IDENTITY - constructor() {} - /** * Prints the detail of the given graph that is specified by either a * rectangle in world coordinates or a collection of world coordinate points which @@ -95,11 +93,11 @@ export default class PrintingSupport { targetRect = this.getBoundsFromPoints( graphComponent .getCanvasObjects(graphComponent.rootGroup) - .map(co => + .map((co) => co.descriptor.getBoundsProvider(co.userObject).getBounds(graphComponent.canvasContext) ) - .filter(bounds => bounds.isFinite) - .flatMap(bounds => + .filter((bounds) => bounds.isFinite) + .flatMap((bounds) => IEnumerable.from([ bounds.topLeft, bounds.topRight, diff --git a/demos/utils/README.html b/demos/utils/README.html index eb3a0c85c..f1c6e0acf 100644 --- a/demos/utils/README.html +++ b/demos/utils/README.html @@ -1,4 +1,4 @@ - + diff --git a/demos/utils/RandomGraphGenerator.js b/demos/utils/RandomGraphGenerator.js index 18e5fba94..6bbc2a13f 100644 --- a/demos/utils/RandomGraphGenerator.js +++ b/demos/utils/RandomGraphGenerator.js @@ -66,7 +66,7 @@ export default class RandomGraphGenerator { * @param {!object} config */ constructor(config) { - this.nodeCreator = config.nodeCreator || (graph => graph.createNode()) + this.nodeCreator = config.nodeCreator || ((graph) => graph.createNode()) this.nodeCount = config.$nodeCount || 30 this.edgeCount = config.$edgeCount || 40 this.allowSelfLoops = config.$allowSelfLoops || false @@ -126,7 +126,7 @@ export default class RandomGraphGenerator { } if (!this.allowCycles) { - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const sourcePort = edge.sourcePort const targetPort = edge.targetPort if (index.get(sourcePort.owner) > index.get(targetPort.owner)) { @@ -204,7 +204,7 @@ export default class RandomGraphGenerator { } if (!this.allowCycles) { - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const sourcePort = edge.sourcePort const targetPort = edge.targetPort if (index.get(sourcePort.owner) > index.get(targetPort.owner)) { diff --git a/demos/utils/RandomGraphGenerator.ts b/demos/utils/RandomGraphGenerator.ts index 951c41be0..140d601d6 100644 --- a/demos/utils/RandomGraphGenerator.ts +++ b/demos/utils/RandomGraphGenerator.ts @@ -130,7 +130,7 @@ export default class RandomGraphGenerator { } if (!this.allowCycles) { - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const sourcePort = edge.sourcePort! const targetPort = edge.targetPort! if (index.get(sourcePort.owner as INode)! > index.get(targetPort.owner as INode)!) { @@ -206,7 +206,7 @@ export default class RandomGraphGenerator { } if (!this.allowCycles) { - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { const sourcePort = edge.sourcePort! const targetPort = edge.targetPort! if (index.get(sourcePort.owner as INode)! > index.get(targetPort.owner as INode)!) { diff --git a/demos/utils/SimpleGraphBuilder.js b/demos/utils/SimpleGraphBuilder.js index 3f50218bd..99a5ebe4e 100644 --- a/demos/utils/SimpleGraphBuilder.js +++ b/demos/utils/SimpleGraphBuilder.js @@ -210,8 +210,8 @@ export class SimpleGraphBuilder { this.$builderEdgesSource = this.$graphBuilder.createEdgesSource( [], - dataItem => this.$sourceIdProvider && this.$sourceIdProvider(dataItem), - dataItem => this.$targetIdProvider && this.$targetIdProvider(dataItem) + (dataItem) => this.$sourceIdProvider && this.$sourceIdProvider(dataItem), + (dataItem) => this.$targetIdProvider && this.$targetIdProvider(dataItem) ) this.$builderEdgesSource.edgeCreator = this.$graphBuilderHelper.createEdgeCreator() @@ -294,7 +294,7 @@ export class SimpleGraphBuilder { this.$builderGroupsSource.idProvider = GraphBuilderHelper.createIdProvider(this.groupIdBinding) this.$builderEdgesSource.idProvider = GraphBuilderHelper.createIdProvider(this.edgeIdBinding) - this.$builderEdgesSource.edgeCreator.tagProvider = e => e + this.$builderEdgesSource.edgeCreator.tagProvider = (e) => e this.$builderNodesSource.parentIdProvider = GraphBuilderHelper.createBinding(this.groupBinding) this.$builderGroupsSource.parentIdProvider = GraphBuilderHelper.createBinding( @@ -343,16 +343,16 @@ export class SimpleGraphBuilder { iteratorResult = iterator.next(newEdgeDataItem) } - this.$sourceIdProvider = e => e.source - this.$targetIdProvider = e => e.target + this.$sourceIdProvider = (e) => e.source + this.$targetIdProvider = (e) => e.target - const tagProvider = e => e.dataItem + const tagProvider = (e) => e.dataItem const edgesSource = this.$builderEdgesSource const helper = this.$graphBuilderHelper if (edgesSource.idProvider) { - edgesSource.idProvider = compose(e => edgesSource.idProvider(e, null), tagProvider) + edgesSource.idProvider = compose((e) => edgesSource.idProvider(e, null), tagProvider) } helper.edgeLabelProvider = compose(helper.edgeLabelProvider, tagProvider) @@ -1180,12 +1180,12 @@ class ObjectNodeCollectionCloner { this.$currentIndex = 0 this.$object = {} this.$valueSet = new Set() - Object.keys(originalCollection).forEach(key => { + Object.keys(originalCollection).forEach((key) => { this.$object[key] = originalCollection[key] this.$valueSet.add(key) const numberIndex = parseInt(key) - if (!isNaN(numberIndex)) { + if (!Number.isNaN(numberIndex)) { this.$currentIndex = Math.max(this.$currentIndex, numberIndex) } }) @@ -2340,22 +2340,22 @@ export class SimpleAdjacentNodesGraphBuilder { this.$builderEdgeCreator = this.$graphBuilderHelper.createEdgeCreator(true) this.$builderNodesSource.addSuccessorsSource( - dataItem => this.$successorsProvider && this.$successorsProvider(dataItem), + (dataItem) => this.$successorsProvider && this.$successorsProvider(dataItem), this.$builderNodesSource, this.$builderEdgeCreator ) this.$builderNodesSource.addPredecessorsSource( - dataItem => this.$predecessorsProvider && this.$predecessorsProvider(dataItem), + (dataItem) => this.$predecessorsProvider && this.$predecessorsProvider(dataItem), this.$builderNodesSource, this.$builderEdgeCreator ) this.$builderNodesSource.addSuccessorIds( - dataItem => this.$successorsIdProvider && this.$successorsIdProvider(dataItem), + (dataItem) => this.$successorsIdProvider && this.$successorsIdProvider(dataItem), this.$builderEdgeCreator ) this.$builderNodesSource.addPredecessorIds( - dataItem => this.$predecessorsIdProvider && this.$predecessorsIdProvider(dataItem), + (dataItem) => this.$predecessorsIdProvider && this.$predecessorsIdProvider(dataItem), this.$builderEdgeCreator ) @@ -2436,12 +2436,12 @@ export class SimpleAdjacentNodesGraphBuilder { const predecessorsProvider = GraphBuilderHelper.createBinding(this.predecessorsBinding) const distinctPredecessorsProvider = predecessorsProvider - ? dataItem => this.$eliminateDuplicateEdges(dataItem, predecessorsProvider(dataItem), false) + ? (dataItem) => this.$eliminateDuplicateEdges(dataItem, predecessorsProvider(dataItem), false) : null const successorsProvider = GraphBuilderHelper.createBinding(this.successorsBinding) const distinctSuccessorsProvider = successorsProvider - ? dataItem => this.$eliminateDuplicateEdges(dataItem, successorsProvider(dataItem), true) + ? (dataItem) => this.$eliminateDuplicateEdges(dataItem, successorsProvider(dataItem), true) : null if (this.nodeIdBinding) { @@ -2459,7 +2459,7 @@ export class SimpleAdjacentNodesGraphBuilder { this.$builderNodesSource.idProvider = GraphBuilderHelper.createIdProvider(this.nodeIdBinding) this.$builderGroupsSource.idProvider = GraphBuilderHelper.createIdProvider(this.groupIdBinding) - this.$builderNodesSource.nodeCreator.tagProvider = n => n + this.$builderNodesSource.nodeCreator.tagProvider = (n) => n this.$builderEdgeCreator.tagProvider = () => null this.$builderNodesSource.parentIdProvider = GraphBuilderHelper.createBinding(this.groupBinding) @@ -3581,7 +3581,7 @@ class GraphBuilderHelper { * @returns {?IEdge} */ getEdge(businessObject) { - return this.$graph.edges.find(e => e.tag === businessObject) + return this.$graph.edges.find((e) => e.tag === businessObject) } /** @@ -3589,7 +3589,7 @@ class GraphBuilderHelper { * @returns {?INode} */ getGroup(groupObject) { - return this.$graph.nodes.find(n => n.tag === groupObject) + return this.$graph.nodes.find((n) => n.tag === groupObject) } /** @@ -3597,7 +3597,7 @@ class GraphBuilderHelper { * @returns {?INode} */ getNode(nodeObject) { - return this.$graph.nodes.find(n => n.tag === nodeObject) + return this.$graph.nodes.find((n) => n.tag === nodeObject) } /** @@ -3608,7 +3608,7 @@ class GraphBuilderHelper { if (binding === null || binding === undefined) { return null } else if (typeof binding === 'string') { - return dataItem => dataItem[binding] + return (dataItem) => dataItem[binding] } else { return binding } @@ -3622,7 +3622,7 @@ class GraphBuilderHelper { if (binding === undefined || binding === null) { return null } else if (typeof binding === 'string') { - return dataItem => dataItem[binding] + return (dataItem) => dataItem[binding] } else { return binding } @@ -3892,7 +3892,7 @@ class GraphBuilderHelper { * @param {!TEvent} evt */ $fireEvent(listeners, evt) { - listeners.forEach(l => l(this.$eventSender, evt)) + listeners.forEach((l) => l(this.$eventSender, evt)) } /** @@ -4074,7 +4074,7 @@ class GraphBuilderHelper { */ function compose(f1, f2) { if (f2 && f1) { - return a => f1(f2(a)) + return (a) => f1(f2(a)) } return null } diff --git a/demos/utils/SimpleGraphBuilder.ts b/demos/utils/SimpleGraphBuilder.ts index deca55dd1..c31169a11 100644 --- a/demos/utils/SimpleGraphBuilder.ts +++ b/demos/utils/SimpleGraphBuilder.ts @@ -205,8 +205,8 @@ export class SimpleGraphBuilder { this.$builderEdgesSource = this.$graphBuilder.createEdgesSource( [], - dataItem => this.$sourceIdProvider && this.$sourceIdProvider(dataItem), - dataItem => this.$targetIdProvider && this.$targetIdProvider(dataItem) + (dataItem) => this.$sourceIdProvider && this.$sourceIdProvider(dataItem), + (dataItem) => this.$targetIdProvider && this.$targetIdProvider(dataItem) ) this.$builderEdgesSource.edgeCreator = this.$graphBuilderHelper.createEdgeCreator() @@ -341,7 +341,7 @@ export class SimpleGraphBuilder { const helper = this.$graphBuilderHelper if (edgesSource.idProvider) { - edgesSource.idProvider = compose(e => edgesSource.idProvider!(e, null), tagProvider) + edgesSource.idProvider = compose((e) => edgesSource.idProvider!(e, null), tagProvider) } helper.edgeLabelProvider = compose(helper.edgeLabelProvider, tagProvider) @@ -1104,12 +1104,12 @@ class ObjectNodeCollectionCloner implements NodeCollectionCloner { this.$currentIndex = 0 this.$object = {} this.$valueSet = new Set() - Object.keys(originalCollection).forEach(key => { + Object.keys(originalCollection).forEach((key) => { this.$object[key] = originalCollection[key] this.$valueSet.add(key) const numberIndex = parseInt(key) - if (!isNaN(numberIndex)) { + if (!Number.isNaN(numberIndex)) { this.$currentIndex = Math.max(this.$currentIndex, numberIndex) } }) @@ -2162,22 +2162,22 @@ export class SimpleAdjacentNodesGraphBuilder { this.$builderEdgeCreator = this.$graphBuilderHelper.createEdgeCreator(true) this.$builderNodesSource.addSuccessorsSource( - dataItem => this.$successorsProvider && this.$successorsProvider(dataItem), + (dataItem) => this.$successorsProvider && this.$successorsProvider(dataItem), this.$builderNodesSource, this.$builderEdgeCreator ) this.$builderNodesSource.addPredecessorsSource( - dataItem => this.$predecessorsProvider && this.$predecessorsProvider(dataItem), + (dataItem) => this.$predecessorsProvider && this.$predecessorsProvider(dataItem), this.$builderNodesSource, this.$builderEdgeCreator ) this.$builderNodesSource.addSuccessorIds( - dataItem => this.$successorsIdProvider && this.$successorsIdProvider(dataItem), + (dataItem) => this.$successorsIdProvider && this.$successorsIdProvider(dataItem), this.$builderEdgeCreator ) this.$builderNodesSource.addPredecessorIds( - dataItem => this.$predecessorsIdProvider && this.$predecessorsIdProvider(dataItem), + (dataItem) => this.$predecessorsIdProvider && this.$predecessorsIdProvider(dataItem), this.$builderEdgeCreator ) @@ -3351,15 +3351,15 @@ class GraphBuilderHelper { } getEdge(businessObject: any): IEdge | null { - return this.$graph.edges.find(e => e.tag === businessObject) + return this.$graph.edges.find((e) => e.tag === businessObject) } getGroup(groupObject: any): INode | null { - return this.$graph.nodes.find(n => n.tag === groupObject) + return this.$graph.nodes.find((n) => n.tag === groupObject) } getNode(nodeObject: any): INode | null { - return this.$graph.nodes.find(n => n.tag === nodeObject) + return this.$graph.nodes.find((n) => n.tag === nodeObject) } public static createIdProvider(binding: any): ((dataItem: any, canonicalId: any) => any) | null { @@ -3544,7 +3544,7 @@ class GraphBuilderHelper { } private $fireEvent(listeners: ((sender: any, evt: TEvent) => void)[], evt: TEvent): void { - listeners.forEach(l => l(this.$eventSender, evt)) + listeners.forEach((l) => l(this.$eventSender, evt)) } private $onNodeCreated(node: INode, dataItem: any): INode { diff --git a/demos/utils/VuejsNodeStyle.js b/demos/utils/VuejsNodeStyle.js index a58b64bb8..a74b787e3 100644 --- a/demos/utils/VuejsNodeStyle.js +++ b/demos/utils/VuejsNodeStyle.js @@ -110,7 +110,7 @@ class ObservedContext { this.observed = {} if ( oldState && - ['tag', 'layout', 'zoom', 'selected', 'highlighted', 'focused'].some(name => + ['tag', 'layout', 'zoom', 'selected', 'highlighted', 'focused'].some((name) => oldState.hasOwnProperty(name) ) ) { @@ -916,8 +916,8 @@ function initializeDesignerVueComponents() { return this.align === 'end' ? Number(this.width) : this.align === 'middle' - ? Number(this.width) * 0.5 - : 0 + ? Number(this.width) * 0.5 + : 0 }, $textAnchor() { return this.align === 'end' || this.align === 'middle' ? this.align : false diff --git a/demos/utils/VuejsNodeStyle.ts b/demos/utils/VuejsNodeStyle.ts index dfaa0d78b..1cc550db3 100644 --- a/demos/utils/VuejsNodeStyle.ts +++ b/demos/utils/VuejsNodeStyle.ts @@ -104,7 +104,7 @@ class ObservedContext { this.observed = {} if ( oldState && - ['tag', 'layout', 'zoom', 'selected', 'highlighted', 'focused'].some(name => + ['tag', 'layout', 'zoom', 'selected', 'highlighted', 'focused'].some((name) => oldState.hasOwnProperty(name) ) ) { @@ -888,8 +888,8 @@ function initializeDesignerVueComponents(): void { return this.align === 'end' ? Number(this.width) : this.align === 'middle' - ? Number(this.width) * 0.5 - : 0 + ? Number(this.width) * 0.5 + : 0 }, $textAnchor(this: TextPropsType): string | boolean { return this.align === 'end' || this.align === 'middle' ? this.align : false diff --git a/demos/utils/configure-two-pointer-panning.js b/demos/utils/configure-two-pointer-panning.js index f37cfe9cf..8ae5e7e1d 100644 --- a/demos/utils/configure-two-pointer-panning.js +++ b/demos/utils/configure-two-pointer-panning.js @@ -45,18 +45,21 @@ import { BrowserDetection } from './BrowserDetection.js' export function configureTwoPointerPanning(graphComponent) { const inputMode = graphComponent.inputMode if (inputMode instanceof GraphEditorInputMode) { - // disable single pointer movement to allow other gestures to start on a simple single press - inputMode.moveViewportInputMode.allowSinglePointerMovement = false + // start marquee selection on long press to allow other gestures to start on a simple single press + inputMode.marqueeSelectionInputMode.pressedRecognizerTouch = + TouchEventRecognizers.TOUCH_LONG_PRESS_PRIMARY + // set gestures to an immediate touch-down recognizer instead of the long-press recognizer inputMode.moveInputMode.pressedRecognizerTouch = TouchEventRecognizers.TOUCH_DOWN_PRIMARY inputMode.createEdgeInputMode.prepareRecognizerTouch = TouchEventRecognizers.TOUCH_DOWN_PRIMARY inputMode.createBendInputMode.prepareRecognizerTouch = TouchEventRecognizers.TOUCH_DOWN_PRIMARY inputMode.handleInputMode.pressedRecognizerTouch = TouchEventRecognizers.TOUCH_DOWN_PRIMARY - inputMode.marqueeSelectionInputMode.pressedRecognizerTouch = - TouchEventRecognizers.TOUCH_DOWN_PRIMARY inputMode.moveUnselectedInputMode.pressedRecognizerTouch = TouchEventRecognizers.TOUCH_DOWN_PRIMARY inputMode.moveLabelInputMode.pressedRecognizerTouch = TouchEventRecognizers.TOUCH_DOWN_PRIMARY + + // make sure that starting the input modes above has higher priority than moving the viewport + inputMode.moveViewportInputMode.priority = inputMode.marqueeSelectionInputMode.priority - 1 } // prevent accidental start of edit gesture for now immediate touchdown gestures diff --git a/demos/utils/configure-two-pointer-panning.ts b/demos/utils/configure-two-pointer-panning.ts index cd72810d3..5688bc1da 100644 --- a/demos/utils/configure-two-pointer-panning.ts +++ b/demos/utils/configure-two-pointer-panning.ts @@ -44,18 +44,21 @@ import { BrowserDetection } from './BrowserDetection' export function configureTwoPointerPanning(graphComponent: GraphComponent): void { const inputMode = graphComponent.inputMode if (inputMode instanceof GraphEditorInputMode) { - // disable single pointer movement to allow other gestures to start on a simple single press - inputMode.moveViewportInputMode.allowSinglePointerMovement = false + // start marquee selection on long press to allow other gestures to start on a simple single press + inputMode.marqueeSelectionInputMode.pressedRecognizerTouch = + TouchEventRecognizers.TOUCH_LONG_PRESS_PRIMARY + // set gestures to an immediate touch-down recognizer instead of the long-press recognizer inputMode.moveInputMode.pressedRecognizerTouch = TouchEventRecognizers.TOUCH_DOWN_PRIMARY inputMode.createEdgeInputMode.prepareRecognizerTouch = TouchEventRecognizers.TOUCH_DOWN_PRIMARY inputMode.createBendInputMode.prepareRecognizerTouch = TouchEventRecognizers.TOUCH_DOWN_PRIMARY inputMode.handleInputMode.pressedRecognizerTouch = TouchEventRecognizers.TOUCH_DOWN_PRIMARY - inputMode.marqueeSelectionInputMode.pressedRecognizerTouch = - TouchEventRecognizers.TOUCH_DOWN_PRIMARY inputMode.moveUnselectedInputMode.pressedRecognizerTouch = TouchEventRecognizers.TOUCH_DOWN_PRIMARY inputMode.moveLabelInputMode.pressedRecognizerTouch = TouchEventRecognizers.TOUCH_DOWN_PRIMARY + + // make sure that starting the input modes above has higher priority than moving the viewport + inputMode.moveViewportInputMode.priority = inputMode.marqueeSelectionInputMode.priority - 1 } // prevent accidental start of edit gesture for now immediate touchdown gestures diff --git a/demos/utils/file-support.js b/demos/utils/file-support.js index cb1ef07a6..94973e887 100644 --- a/demos/utils/file-support.js +++ b/demos/utils/file-support.js @@ -61,7 +61,7 @@ export function openFile(encoding = 'utf-8') { } const reader = new FileReader() - reader.addEventListener('loadend', evt => { + reader.addEventListener('loadend', (evt) => { const fileReader = evt.target if (fileReader.error == null) { resolve({ @@ -107,13 +107,14 @@ export function downloadFile(content, filename, contentType) { */ function createBlob(content, type) { switch (type) { - case 'application/pdf': + case 'application/pdf': { const uint8Array = new Uint8Array(content.length) for (let i = 0; i < content.length; i++) { uint8Array[i] = content.charCodeAt(i) } return new Blob([uint8Array], { type }) - case 'application/png': + } + case 'application/png': { const dataUrlParts = content.split(',') const bString = window.atob(dataUrlParts[1]) const byteArray = [] @@ -121,6 +122,7 @@ function createBlob(content, type) { byteArray.push(bString.charCodeAt(i)) } return new Blob([new Uint8Array(byteArray)], { type }) + } default: return new Blob([content], { type }) } diff --git a/demos/utils/file-support.ts b/demos/utils/file-support.ts index d5fdd2d53..102775fd5 100644 --- a/demos/utils/file-support.ts +++ b/demos/utils/file-support.ts @@ -61,7 +61,7 @@ export function openFile(encoding = 'utf-8'): Promise { } const reader = new FileReader() - reader.addEventListener('loadend', evt => { + reader.addEventListener('loadend', (evt) => { const fileReader = evt.target! if (fileReader.error == null) { resolve({ @@ -102,13 +102,14 @@ export function downloadFile(content: string, filename: string, contentType?: st function createBlob(content: string, type: string) { switch (type) { - case 'application/pdf': + case 'application/pdf': { const uint8Array = new Uint8Array(content.length) for (let i = 0; i < content.length; i++) { uint8Array[i] = content.charCodeAt(i) } return new Blob([uint8Array], { type }) - case 'application/png': + } + case 'application/png': { const dataUrlParts = content.split(',') const bString = window.atob(dataUrlParts[1]) const byteArray = [] @@ -116,6 +117,7 @@ function createBlob(content: string, type: string) { byteArray.push(bString.charCodeAt(i)) } return new Blob([new Uint8Array(byteArray)], { type }) + } default: return new Blob([content], { type }) } diff --git a/demos/utils/json-writer.js b/demos/utils/json-writer.js index 2fab5ddad..022b5e264 100644 --- a/demos/utils/json-writer.js +++ b/demos/utils/json-writer.js @@ -63,18 +63,18 @@ export function getDefaultWriterOptions() { data.layout = { x, y, width, height } }, nodeLabels: (data, node) => { - data.labels = node.labels.toArray().map(label => createLabelData(label, 'parameter')) + data.labels = node.labels.toArray().map((label) => createLabelData(label, 'parameter')) }, edgeBends: (data, edge) => { - data.bends = edge.bends.toArray().map(bend => toPoint(bend.location)) + data.bends = edge.bends.toArray().map((bend) => toPoint(bend.location)) }, edgePortLocations: (data, edge) => { data.sourcePort = toPoint(edge.sourcePort.location) data.targetPort = toPoint(edge.targetPort.location) }, edgeLabels: (data, edge) => { - data.labels = edge.labels.toArray().map(label => createLabelData(label, 'parameter')) + data.labels = edge.labels.toArray().map((label) => createLabelData(label, 'parameter')) } } } @@ -93,10 +93,10 @@ export function toJSON(graph, options) { return { nodeList: graph.nodes .toArray() - .map(node => createNodeData(node, graph, nodeIdProvider, actualOptions)), + .map((node) => createNodeData(node, graph, nodeIdProvider, actualOptions)), edgeList: graph.edges .toArray() - .map(edge => createEdgeData(edge, graph, nodeIdProvider, actualOptions)) + .map((edge) => createEdgeData(edge, graph, nodeIdProvider, actualOptions)) } } @@ -176,21 +176,21 @@ export function createLabelData(label, details = 'none') { return details === 'none' ? { text } : details === 'parameter' - ? { - text, - layoutParameter: ILabelModelParameter.serializeParameter(layoutParameter) - } - : { - text, - layout: { - anchorX: layout.anchorX, - anchorY: layout.anchorY, - upX: layout.upX, - upY: layout.upY, - width: layout.width, - height: layout.height + ? { + text, + layoutParameter: ILabelModelParameter.serializeParameter(layoutParameter) + } + : { + text, + layout: { + anchorX: layout.anchorX, + anchorY: layout.anchorY, + upX: layout.upX, + upY: layout.upY, + width: layout.width, + height: layout.height + } } - } } /** @@ -208,7 +208,7 @@ function toPoint(pointLike) { */ function createNodeIdProvider() { const nodeToIdMap = new Map() - return node => { + return (node) => { if (nodeToIdMap.has(node)) { return nodeToIdMap.get(node) } diff --git a/demos/utils/json-writer.ts b/demos/utils/json-writer.ts index 03dade41b..6021d30c8 100644 --- a/demos/utils/json-writer.ts +++ b/demos/utils/json-writer.ts @@ -88,18 +88,18 @@ export function getDefaultWriterOptions(): WriterOptions { data.layout = { x, y, width, height } }, nodeLabels: (data, node) => { - data.labels = node.labels.toArray().map(label => createLabelData(label, 'parameter')) + data.labels = node.labels.toArray().map((label) => createLabelData(label, 'parameter')) }, edgeBends: (data, edge) => { - data.bends = edge.bends.toArray().map(bend => toPoint(bend.location)) + data.bends = edge.bends.toArray().map((bend) => toPoint(bend.location)) }, edgePortLocations: (data, edge) => { data.sourcePort = toPoint(edge.sourcePort!.location) data.targetPort = toPoint(edge.targetPort!.location) }, edgeLabels: (data, edge) => { - data.labels = edge.labels.toArray().map(label => createLabelData(label, 'parameter')) + data.labels = edge.labels.toArray().map((label) => createLabelData(label, 'parameter')) } } } @@ -115,10 +115,10 @@ export function toJSON(graph: IGraph, options?: WriterOptions): JSONGraph { return { nodeList: graph.nodes .toArray() - .map(node => createNodeData(node, graph, nodeIdProvider, actualOptions)), + .map((node) => createNodeData(node, graph, nodeIdProvider, actualOptions)), edgeList: graph.edges .toArray() - .map(edge => createEdgeData(edge, graph, nodeIdProvider, actualOptions)) + .map((edge) => createEdgeData(edge, graph, nodeIdProvider, actualOptions)) } } @@ -196,21 +196,21 @@ export function createLabelData( return details === 'none' ? { text } : details === 'parameter' - ? { - text, - layoutParameter: ILabelModelParameter.serializeParameter(layoutParameter) - } - : { - text, - layout: { - anchorX: layout.anchorX, - anchorY: layout.anchorY, - upX: layout.upX, - upY: layout.upY, - width: layout.width, - height: layout.height + ? { + text, + layoutParameter: ILabelModelParameter.serializeParameter(layoutParameter) + } + : { + text, + layout: { + anchorX: layout.anchorX, + anchorY: layout.anchorY, + upX: layout.upX, + upY: layout.upY, + width: layout.width, + height: layout.height + } } - } } /** @@ -225,7 +225,7 @@ function toPoint(pointLike: { x: number; y: number }): JSONPoint { */ function createNodeIdProvider(): NodeIdProvider { const nodeToIdMap = new Map() - return node => { + return (node) => { if (nodeToIdMap.has(node)) { return nodeToIdMap.get(node) } diff --git a/demos/utils/package.json b/demos/utils/package.json index f960101fa..680457782 100644 --- a/demos/utils/package.json +++ b/demos/utils/package.json @@ -1,6 +1,6 @@ { "name": "demo-utils-ts", - "version": "26.0.2", + "version": "26.0.3", "author": "yWorks GmbH ", "license": "https://www.yworks.com/products/yfiles-for-html/sla", "private": true, @@ -8,7 +8,6 @@ "./*": "./*" }, "dependencies": { - "yfiles": "^26.0.0 || ^26.0.0-A", - "yfiles-umd": "^26.0.0 || ^26.0.0-A" + "yfiles": "^26.0.0 || ^26.0.0-A" } } diff --git a/demos/utils/sample-graph.js b/demos/utils/sample-graph.js index d413ed1d7..8e2d203ea 100644 --- a/demos/utils/sample-graph.js +++ b/demos/utils/sample-graph.js @@ -92,7 +92,7 @@ export function createGroupedSampleGraph(graph) { } generateItemLabels(graph, graph.edges.toList()) - generateItemLabels(graph, graph.nodes.filter(node => !graph.isGroupNode(node)).toList()) + generateItemLabels(graph, graph.nodes.filter((node) => !graph.isGroupNode(node)).toList()) } /** diff --git a/demos/utils/sample-graph.ts b/demos/utils/sample-graph.ts index f18913586..4ce8e5940 100644 --- a/demos/utils/sample-graph.ts +++ b/demos/utils/sample-graph.ts @@ -92,7 +92,7 @@ export function createGroupedSampleGraph(graph: IGraph) { } generateItemLabels(graph, graph.edges.toList()) - generateItemLabels(graph, graph.nodes.filter(node => !graph.isGroupNode(node)).toList()) + generateItemLabels(graph, graph.nodes.filter((node) => !graph.isGroupNode(node)).toList()) } /** diff --git a/demos/view/arrange-objects/index.html b/demos/view/arrange-objects/index.html index cbe8600bf..400e198d2 100644 --- a/demos/view/arrange-objects/index.html +++ b/demos/view/arrange-objects/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/bridges/BridgesDemo.js b/demos/view/bridges/BridgesDemo.js index 1a2b7d010..ad4cde7fd 100644 --- a/demos/view/bridges/BridgesDemo.js +++ b/demos/view/bridges/BridgesDemo.js @@ -110,8 +110,8 @@ function configureBridges() { // We register a custom obstacle provider in the node's lookup of group nodes // that can be used by bridgeManager (through provider...) graphComponent.graph.decorator.nodeDecorator.obstacleProviderDecorator.setFactory( - node => graphComponent.graph.isGroupNode(node), - node => new GroupNodeObstacleProvider(node) + (node) => graphComponent.graph.isGroupNode(node), + (node) => new GroupNodeObstacleProvider(node) ) } @@ -226,13 +226,13 @@ function initializeToolBarElements() { ] fillComboBox(bridgeOrientationComboBox, bridgeOrientationElements) - document.querySelector('#bridge-width-slider').addEventListener('change', evt => { + document.querySelector('#bridge-width-slider').addEventListener('change', (evt) => { const value = evt.target.value bridgeManager.defaultBridgeWidth = parseInt(value) graphComponent.invalidate() document.getElementById('bridge-width-label').textContent = value }) - document.querySelector('#bridge-height-slider').addEventListener('change', evt => { + document.querySelector('#bridge-height-slider').addEventListener('change', (evt) => { const value = evt.target.value bridgeManager.defaultBridgeHeight = parseInt(value) graphComponent.invalidate() diff --git a/demos/view/bridges/BridgesDemo.ts b/demos/view/bridges/BridgesDemo.ts index 0077f5f19..8e64c52f7 100644 --- a/demos/view/bridges/BridgesDemo.ts +++ b/demos/view/bridges/BridgesDemo.ts @@ -107,8 +107,8 @@ function configureBridges(): void { // We register a custom obstacle provider in the node's lookup of group nodes // that can be used by bridgeManager (through provider...) graphComponent.graph.decorator.nodeDecorator.obstacleProviderDecorator.setFactory( - node => graphComponent.graph.isGroupNode(node), - node => new GroupNodeObstacleProvider(node) + (node) => graphComponent.graph.isGroupNode(node), + (node) => new GroupNodeObstacleProvider(node) ) } @@ -226,7 +226,7 @@ function initializeToolBarElements(): void { document .querySelector('#bridge-width-slider')! - .addEventListener('change', evt => { + .addEventListener('change', (evt) => { const value = (evt.target as HTMLInputElement).value bridgeManager.defaultBridgeWidth = parseInt(value) graphComponent.invalidate() @@ -234,7 +234,7 @@ function initializeToolBarElements(): void { }) document .querySelector('#bridge-height-slider')! - .addEventListener('change', evt => { + .addEventListener('change', (evt) => { const value = (evt.target as HTMLInputElement).value bridgeManager.defaultBridgeHeight = parseInt(value) graphComponent.invalidate() diff --git a/demos/view/bridges/index.html b/demos/view/bridges/index.html index 076058e16..3b834110e 100644 --- a/demos/view/bridges/index.html +++ b/demos/view/bridges/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/clipboard-deferred-cut/DeferredCutClipboardDemo.js b/demos/view/clipboard-deferred-cut/DeferredCutClipboardDemo.js index b1723af9a..a3cc9506d 100644 --- a/demos/view/clipboard-deferred-cut/DeferredCutClipboardDemo.js +++ b/demos/view/clipboard-deferred-cut/DeferredCutClipboardDemo.js @@ -87,17 +87,17 @@ async function run() { graphBuilder .createNodesSource({ data: graphData.nodeList, - id: item => item.id + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) - .edgeCreator.createLabelBinding(item => item.label) + .edgeCreator.createLabelBinding((item) => item.label) graphBuilder.buildGraph() } @@ -119,7 +119,7 @@ async function run() { // configure the clipboard itself const clipboard = new DeferredCutClipboard() // trigger a repaint after copy since copy removed the "marked for cut" mark from the elements - clipboard.addElementsCopiedListener(_ => graphComponent.invalidate()) + clipboard.addElementsCopiedListener((_) => graphComponent.invalidate()) graphComponent.clipboard = clipboard // set up the input mode @@ -144,7 +144,7 @@ function configureContextMenu(graphComponent) { const inputMode = graphComponent.inputMode const contextMenu = new ContextMenu(graphComponent) - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (inputMode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } diff --git a/demos/view/clipboard-deferred-cut/DeferredCutClipboardDemo.ts b/demos/view/clipboard-deferred-cut/DeferredCutClipboardDemo.ts index 5cc63ad6b..b986dfd68 100644 --- a/demos/view/clipboard-deferred-cut/DeferredCutClipboardDemo.ts +++ b/demos/view/clipboard-deferred-cut/DeferredCutClipboardDemo.ts @@ -85,17 +85,17 @@ async function run(): Promise { graphBuilder .createNodesSource({ data: graphData.nodeList, - id: item => item.id + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) - .edgeCreator.createLabelBinding(item => item.label) + .edgeCreator.createLabelBinding((item) => item.label) graphBuilder.buildGraph() } @@ -117,7 +117,7 @@ async function run(): Promise { // configure the clipboard itself const clipboard = new DeferredCutClipboard() // trigger a repaint after copy since copy removed the "marked for cut" mark from the elements - clipboard.addElementsCopiedListener(_ => graphComponent.invalidate()) + clipboard.addElementsCopiedListener((_) => graphComponent.invalidate()) graphComponent.clipboard = clipboard // set up the input mode @@ -141,7 +141,7 @@ function configureContextMenu(graphComponent: GraphComponent): void { const inputMode = graphComponent.inputMode as GraphEditorInputMode const contextMenu = new ContextMenu(graphComponent) - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (inputMode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } diff --git a/demos/view/clipboard-deferred-cut/index.html b/demos/view/clipboard-deferred-cut/index.html index 260b4e238..58eb77492 100644 --- a/demos/view/clipboard-deferred-cut/index.html +++ b/demos/view/clipboard-deferred-cut/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/clipboard/BusinessDataHandling.js b/demos/view/clipboard/BusinessDataHandling.js index 1c6cc7945..1d7b877f9 100644 --- a/demos/view/clipboard/BusinessDataHandling.js +++ b/demos/view/clipboard/BusinessDataHandling.js @@ -55,5 +55,5 @@ export function getCommonName(selectedNodes) { return '' } const name = selectedNodes.first().tag.name - return selectedNodes.some(node => name !== node.tag.name) ? '' : name + return selectedNodes.some((node) => name !== node.tag.name) ? '' : name } diff --git a/demos/view/clipboard/ClipboardDemo.js b/demos/view/clipboard/ClipboardDemo.js index df001ae34..e11d0fe4e 100644 --- a/demos/view/clipboard/ClipboardDemo.js +++ b/demos/view/clipboard/ClipboardDemo.js @@ -266,7 +266,7 @@ function onPasteSpecialCommand(component) { component.selection.clear() // This is the filter for the Paste call. - const filter = item => item instanceof INode || item instanceof ILabel + const filter = (item) => item instanceof INode || item instanceof ILabel // This callback is executed for every pasted element. We use it to select the pasted nodes. const pasted = (originalItem, pastedItem) => { if (pastedItem instanceof INode) { @@ -289,11 +289,11 @@ function onEditNameCommand(component, elementID) { const nodeNameInput = nameDialog.querySelector('#node-name-input') nodeNameInput.value = getCommonName(component.selection.selectedNodes) - const applyListener = evt => { + const applyListener = (evt) => { evt.preventDefault() nameDialog.style.display = 'none' const name = nodeNameInput.value - component.selection.selectedNodes.forEach(node => { + component.selection.selectedNodes.forEach((node) => { node.tag.name = name // The firePropertyChanged method is available on the tag because it was added by the // {@link StringTemplateNodeStyle.makeObservable} method. diff --git a/demos/view/clipboard/ClipboardDemo.ts b/demos/view/clipboard/ClipboardDemo.ts index 02c48875e..6a003b4cd 100644 --- a/demos/view/clipboard/ClipboardDemo.ts +++ b/demos/view/clipboard/ClipboardDemo.ts @@ -290,7 +290,7 @@ function onEditNameCommand(component: GraphComponent, elementID: string): void { evt.preventDefault() nameDialog.style.display = 'none' const name = nodeNameInput.value - component.selection.selectedNodes.forEach(node => { + component.selection.selectedNodes.forEach((node) => { node.tag.name = name // The firePropertyChanged method is available on the tag because it was added by the // {@link StringTemplateNodeStyle.makeObservable} method. diff --git a/demos/view/clipboard/index.html b/demos/view/clipboard/index.html index b70010af7..6b087692c 100644 --- a/demos/view/clipboard/index.html +++ b/demos/view/clipboard/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/collapse/CollapseAndExpandNodes.js b/demos/view/collapse/CollapseAndExpandNodes.js index 2fd8c743a..8f1a19dc0 100644 --- a/demos/view/collapse/CollapseAndExpandNodes.js +++ b/demos/view/collapse/CollapseAndExpandNodes.js @@ -127,9 +127,9 @@ export default class CollapseAndExpandNodes { this.setCollapsed(node, collapse) const filteredGraph = this.graphComponent.graph - CollapseAndExpandNodes.getDescendants(filteredGraph.wrappedGraph, node, succ => + CollapseAndExpandNodes.getDescendants(filteredGraph.wrappedGraph, node, (succ) => this.isCollapsed(succ) - ).forEach(succ => { + ).forEach((succ) => { this.setNodeVisibility(succ, !collapse) }) } @@ -169,7 +169,7 @@ export default class CollapseAndExpandNodes { // mark the new nodes and place them between their neighbors const layoutData = new PlaceNodesAtBarycenterStageData({ - affectedNodes: node => incrementalNodes.has(node) + affectedNodes: (node) => incrementalNodes.has(node) }) const layout = new PlaceNodesAtBarycenterStage() @@ -199,10 +199,10 @@ export default class CollapseAndExpandNodes { const incrementalNodes = CollapseAndExpandNodes.getDescendants( graph, toggledNode, - node => false + (node) => false ) const incrementalMap = new HashMap() - incrementalNodes.forEach(node => { + incrementalNodes.forEach((node) => { incrementalMap.set(node, true) const co = this.graphComponent.graphModelManager.getMainCanvasObject(node) const toggledNodeCo = this.graphComponent.graphModelManager.getMainCanvasObject(toggledNode) @@ -218,7 +218,7 @@ export default class CollapseAndExpandNodes { // configure PlaceNodesAtBarycenterStage for a smooth animation currentLayoutData.items.add( new PlaceNodesAtBarycenterStageData({ - affectedNodes: node => incrementalMap.has(node) + affectedNodes: (node) => incrementalMap.has(node) }) ) } @@ -240,8 +240,8 @@ export default class CollapseAndExpandNodes { currentLayoutData.items.add( new OrganicLayoutData({ - nodeInertia: obj => 1 - 1 / (layerIds.get(obj) + 1), - nodeStress: obj => 1 / (layerIds.get(obj) + 1) + nodeInertia: (obj) => 1 - 1 / (layerIds.get(obj) + 1), + nodeStress: (obj) => 1 / (layerIds.get(obj) + 1) }) ) } else if (currentLayout instanceof HierarchicLayout) { diff --git a/demos/view/collapse/CollapseAndExpandNodes.ts b/demos/view/collapse/CollapseAndExpandNodes.ts index 4a193de0f..6802539f2 100644 --- a/demos/view/collapse/CollapseAndExpandNodes.ts +++ b/demos/view/collapse/CollapseAndExpandNodes.ts @@ -120,9 +120,9 @@ export default class CollapseAndExpandNodes { this.setCollapsed(node, collapse) const filteredGraph = this.graphComponent.graph as FilteredGraphWrapper - CollapseAndExpandNodes.getDescendants(filteredGraph.wrappedGraph!, node, succ => + CollapseAndExpandNodes.getDescendants(filteredGraph.wrappedGraph!, node, (succ) => this.isCollapsed(succ) - ).forEach(succ => { + ).forEach((succ) => { this.setNodeVisibility(succ, !collapse) }) } @@ -166,7 +166,7 @@ export default class CollapseAndExpandNodes { // mark the new nodes and place them between their neighbors const layoutData = new PlaceNodesAtBarycenterStageData({ - affectedNodes: node => incrementalNodes.has(node) + affectedNodes: (node) => incrementalNodes.has(node) }) const layout = new PlaceNodesAtBarycenterStage() @@ -201,10 +201,10 @@ export default class CollapseAndExpandNodes { const incrementalNodes = CollapseAndExpandNodes.getDescendants( graph, toggledNode, - node => false + (node) => false ) const incrementalMap = new HashMap() - incrementalNodes.forEach(node => { + incrementalNodes.forEach((node) => { incrementalMap.set(node, true) const co = this.graphComponent.graphModelManager.getMainCanvasObject(node) const toggledNodeCo = this.graphComponent.graphModelManager.getMainCanvasObject(toggledNode) @@ -242,8 +242,8 @@ export default class CollapseAndExpandNodes { currentLayoutData.items.add( new OrganicLayoutData({ - nodeInertia: obj => 1 - 1 / (layerIds.get(obj)! + 1), - nodeStress: obj => 1 / (layerIds.get(obj)! + 1) + nodeInertia: (obj) => 1 - 1 / (layerIds.get(obj)! + 1), + nodeStress: (obj) => 1 / (layerIds.get(obj)! + 1) }) ) } else if (currentLayout instanceof HierarchicLayout) { diff --git a/demos/view/collapse/CollapseDemo.js b/demos/view/collapse/CollapseDemo.js index 37a07791d..5e1421aad 100644 --- a/demos/view/collapse/CollapseDemo.js +++ b/demos/view/collapse/CollapseDemo.js @@ -119,9 +119,9 @@ function createGraph() { // set the converters for the collapsible node styles TemplateNodeStyle.CONVERTERS.collapseDemo = { // converter function for node background - backgroundConverter: data => (data && data.collapsed ? '#f26419' : '#01baff'), + backgroundConverter: (data) => (data && data.collapsed ? '#f26419' : '#01baff'), // converter function for node icon - iconConverter: data => (data && data.collapsed ? '#expand_icon' : '#collapse_icon') + iconConverter: (data) => (data && data.collapsed ? '#expand_icon' : '#collapse_icon') } // set a default edge style @@ -145,7 +145,7 @@ function createGraph() { // The predicate methods specify which should be part of the filtered graph. return new FilteredGraphWrapper( completeGraph, - node => collapseAndExpandNodes.getNodeVisibility(node), + (node) => collapseAndExpandNodes.getNodeVisibility(node), () => true ) } diff --git a/demos/view/collapse/CollapseDemo.ts b/demos/view/collapse/CollapseDemo.ts index 861ab6377..e68520ece 100644 --- a/demos/view/collapse/CollapseDemo.ts +++ b/demos/view/collapse/CollapseDemo.ts @@ -141,7 +141,7 @@ function createGraph(): FilteredGraphWrapper { // The predicate methods specify which should be part of the filtered graph. return new FilteredGraphWrapper( completeGraph, - node => collapseAndExpandNodes.getNodeVisibility(node), + (node) => collapseAndExpandNodes.getNodeVisibility(node), () => true ) } diff --git a/demos/view/collapse/index.html b/demos/view/collapse/index.html index 54783de73..9743fb858 100644 --- a/demos/view/collapse/index.html +++ b/demos/view/collapse/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/contextualtoolbar/ContextualToolbar.js b/demos/view/contextualtoolbar/ContextualToolbar.js index eb546a4ee..e18f5fb36 100644 --- a/demos/view/contextualtoolbar/ContextualToolbar.js +++ b/demos/view/contextualtoolbar/ContextualToolbar.js @@ -206,7 +206,7 @@ export default class ContextualToolbar { newSelection.push(newNode) } this.graphComponent.selection.clear() - newSelection.forEach(item => this.graphComponent.selection.setSelected(item, true)) + newSelection.forEach((item) => this.graphComponent.selection.setSelected(item, true)) } /** @@ -227,7 +227,7 @@ export default class ContextualToolbar { originalLayout.width, originalLayout.height ) - const noOverlaps = nodes.every(node => { + const noOverlaps = nodes.every((node) => { const layout = node.layout return ( layout.x + layout.width < newLayout.x || @@ -356,7 +356,7 @@ export default class ContextualToolbar { * @returns {!Array.} */ getSelectedEdges() { - return this.selectedItems.filter(item => item instanceof IEdge) + return this.selectedItems.filter((item) => item instanceof IEdge) } /** @@ -364,7 +364,7 @@ export default class ContextualToolbar { * @returns {!Array.} */ getSelectedNodes() { - return this.selectedItems.filter(item => item instanceof INode) + return this.selectedItems.filter((item) => item instanceof INode) } /** @@ -484,7 +484,7 @@ export default class ContextualToolbar { if (this.containsEdges && !this.containsNodes) { // if only edges are selected, we want to use the first edge as position reference - dummyOwner = this.selectedItems.find(item => item instanceof IEdge) + dummyOwner = this.selectedItems.find((item) => item instanceof IEdge) labelModelParameter = this.edgeLabelModelParameter } else { // if nodes and edges are selected, we use the union of the node's bounding boxes as position reference @@ -568,22 +568,22 @@ export default class ContextualToolbar { registerClickListeners() { document .querySelector('#clipboard') - ?.addEventListener('click', e => this.showPickerContainer(e)) + ?.addEventListener('click', (e) => this.showPickerContainer(e)) document .querySelector('#color-picker') - ?.addEventListener('click', e => this.showPickerContainer(e)) + ?.addEventListener('click', (e) => this.showPickerContainer(e)) document .querySelector('#shape-picker') - ?.addEventListener('click', e => this.showPickerContainer(e)) + ?.addEventListener('click', (e) => this.showPickerContainer(e)) document .querySelector('#font-color-picker') - ?.addEventListener('click', e => this.showPickerContainer(e)) + ?.addEventListener('click', (e) => this.showPickerContainer(e)) document .querySelector('#edge-color-picker') - ?.addEventListener('click', e => this.showPickerContainer(e)) + ?.addEventListener('click', (e) => this.showPickerContainer(e)) const sourceArrowPicker = document.querySelector('#source-arrow-picker') const targetArrowPicker = document.querySelector('#target-arrow-picker') - sourceArrowPicker?.addEventListener('click', e => { + sourceArrowPicker?.addEventListener('click', (e) => { targetArrowPicker.checked = false const pickerContainer = document.getElementById( sourceArrowPicker.getAttribute('data-container-id') @@ -591,7 +591,7 @@ export default class ContextualToolbar { pickerContainer.classList.remove('target') this.showPickerContainer(e) }) - targetArrowPicker?.addEventListener('click', e => { + targetArrowPicker?.addEventListener('click', (e) => { sourceArrowPicker.checked = false const pickerContainer = document.getElementById( targetArrowPicker.getAttribute('data-container-id') @@ -601,21 +601,21 @@ export default class ContextualToolbar { }) for (const button of document.querySelectorAll('#color-picker-colors > button')) { - button.addEventListener('click', e => { + button.addEventListener('click', (e) => { const color = button.getAttribute('data-color') this.applyNodeStyle(color) }) } for (const button of document.querySelectorAll('#font-color-picker-colors > button')) { - button.addEventListener('click', e => { + button.addEventListener('click', (e) => { const color = button.getAttribute('data-color') this.applyFontStyle({}, color) }) } for (const button of document.querySelectorAll('#shape-picker-shapes > button')) { - button.addEventListener('click', e => { + button.addEventListener('click', (e) => { const shape = button.getAttribute('data-shape') this.applyNodeStyle(null, shape) }) @@ -626,7 +626,7 @@ export default class ContextualToolbar { ?.addEventListener('click', () => this.createConnectedNode()) for (const button of document.querySelectorAll('#arrow-picker-types > button')) { - button.addEventListener('click', e => { + button.addEventListener('click', (e) => { const arrowType = button.getAttribute('data-type') const isTarget = button.parentElement.classList.contains('target') if (isTarget) { @@ -638,27 +638,27 @@ export default class ContextualToolbar { } for (const button of document.querySelectorAll('#edge-colors > button')) { - button.addEventListener('click', e => { + button.addEventListener('click', (e) => { const color = button.getAttribute('data-color') this.applyEdgeStyle(color, null, null) }) } - document.querySelector('#font-bold')?.addEventListener('click', e => { + document.querySelector('#font-bold')?.addEventListener('click', (e) => { this.hideAllPickerContainer() const target = e.target this.applyFontStyle({ fontWeight: target.checked ? target.getAttribute('data-fontWeight') : 'normal' }) }) - document.querySelector('#font-italic')?.addEventListener('click', e => { + document.querySelector('#font-italic')?.addEventListener('click', (e) => { this.hideAllPickerContainer() const target = e.target this.applyFontStyle({ fontStyle: target.checked ? target.getAttribute('data-fontStyle') : 'normal' }) }) - document.querySelector('#font-underline')?.addEventListener('click', e => { + document.querySelector('#font-underline')?.addEventListener('click', (e) => { this.hideAllPickerContainer() const target = e.target this.applyFontStyle({ diff --git a/demos/view/contextualtoolbar/ContextualToolbar.ts b/demos/view/contextualtoolbar/ContextualToolbar.ts index 1211999ff..cd7c6140e 100644 --- a/demos/view/contextualtoolbar/ContextualToolbar.ts +++ b/demos/view/contextualtoolbar/ContextualToolbar.ts @@ -198,7 +198,7 @@ export default class ContextualToolbar { newSelection.push(newNode) } this.graphComponent.selection.clear() - newSelection.forEach(item => this.graphComponent.selection.setSelected(item, true)) + newSelection.forEach((item) => this.graphComponent.selection.setSelected(item, true)) } /** @@ -217,7 +217,7 @@ export default class ContextualToolbar { originalLayout.width, originalLayout.height ) - const noOverlaps = nodes.every(node => { + const noOverlaps = nodes.every((node) => { const layout = node.layout return ( layout.x + layout.width < newLayout.x || @@ -349,14 +349,14 @@ export default class ContextualToolbar { * Returns an array of the currently selected edges. */ getSelectedEdges(): IEdge[] { - return this.selectedItems.filter(item => item instanceof IEdge) as IEdge[] + return this.selectedItems.filter((item) => item instanceof IEdge) as IEdge[] } /** * Returns an array of the currently selected nodes. */ getSelectedNodes(): INode[] { - return this.selectedItems.filter(item => item instanceof INode) as INode[] + return this.selectedItems.filter((item) => item instanceof INode) as INode[] } /** @@ -475,7 +475,7 @@ export default class ContextualToolbar { if (this.containsEdges && !this.containsNodes) { // if only edges are selected, we want to use the first edge as position reference - dummyOwner = this.selectedItems.find(item => item instanceof IEdge) as IEdge + dummyOwner = this.selectedItems.find((item) => item instanceof IEdge) as IEdge labelModelParameter = this.edgeLabelModelParameter } else { // if nodes and edges are selected, we use the union of the node's bounding boxes as position reference @@ -562,22 +562,22 @@ export default class ContextualToolbar { registerClickListeners(): void { document .querySelector('#clipboard') - ?.addEventListener('click', e => this.showPickerContainer(e)) + ?.addEventListener('click', (e) => this.showPickerContainer(e)) document .querySelector('#color-picker') - ?.addEventListener('click', e => this.showPickerContainer(e)) + ?.addEventListener('click', (e) => this.showPickerContainer(e)) document .querySelector('#shape-picker') - ?.addEventListener('click', e => this.showPickerContainer(e)) + ?.addEventListener('click', (e) => this.showPickerContainer(e)) document .querySelector('#font-color-picker') - ?.addEventListener('click', e => this.showPickerContainer(e)) + ?.addEventListener('click', (e) => this.showPickerContainer(e)) document .querySelector('#edge-color-picker') - ?.addEventListener('click', e => this.showPickerContainer(e)) + ?.addEventListener('click', (e) => this.showPickerContainer(e)) const sourceArrowPicker = document.querySelector('#source-arrow-picker') const targetArrowPicker = document.querySelector('#target-arrow-picker') - sourceArrowPicker?.addEventListener('click', e => { + sourceArrowPicker?.addEventListener('click', (e) => { targetArrowPicker!.checked = false const pickerContainer = document.getElementById( sourceArrowPicker.getAttribute('data-container-id')! @@ -585,7 +585,7 @@ export default class ContextualToolbar { pickerContainer.classList.remove('target') this.showPickerContainer(e) }) - targetArrowPicker?.addEventListener('click', e => { + targetArrowPicker?.addEventListener('click', (e) => { sourceArrowPicker!.checked = false const pickerContainer = document.getElementById( targetArrowPicker.getAttribute('data-container-id')! @@ -597,7 +597,7 @@ export default class ContextualToolbar { for (const button of document.querySelectorAll( '#color-picker-colors > button' )) { - button.addEventListener('click', e => { + button.addEventListener('click', (e) => { const color = button.getAttribute('data-color') this.applyNodeStyle(color) }) @@ -606,7 +606,7 @@ export default class ContextualToolbar { for (const button of document.querySelectorAll( '#font-color-picker-colors > button' )) { - button.addEventListener('click', e => { + button.addEventListener('click', (e) => { const color = button.getAttribute('data-color') this.applyFontStyle({}, color) }) @@ -615,7 +615,7 @@ export default class ContextualToolbar { for (const button of document.querySelectorAll( '#shape-picker-shapes > button' )) { - button.addEventListener('click', e => { + button.addEventListener('click', (e) => { const shape = button.getAttribute('data-shape') as ShapeNodeShapeStringValues | null this.applyNodeStyle(null, shape) }) @@ -628,7 +628,7 @@ export default class ContextualToolbar { for (const button of document.querySelectorAll( '#arrow-picker-types > button' )) { - button.addEventListener('click', e => { + button.addEventListener('click', (e) => { const arrowType = button.getAttribute('data-type') as ArrowTypeStringValues | null const isTarget = button.parentElement!.classList.contains('target') if (isTarget) { @@ -640,27 +640,27 @@ export default class ContextualToolbar { } for (const button of document.querySelectorAll('#edge-colors > button')) { - button.addEventListener('click', e => { + button.addEventListener('click', (e) => { const color = button.getAttribute('data-color') this.applyEdgeStyle(color, null, null) }) } - document.querySelector('#font-bold')?.addEventListener('click', e => { + document.querySelector('#font-bold')?.addEventListener('click', (e) => { this.hideAllPickerContainer() const target = e.target as HTMLInputElement this.applyFontStyle({ fontWeight: target.checked ? target.getAttribute('data-fontWeight')! : 'normal' }) }) - document.querySelector('#font-italic')?.addEventListener('click', e => { + document.querySelector('#font-italic')?.addEventListener('click', (e) => { this.hideAllPickerContainer() const target = e.target as HTMLInputElement this.applyFontStyle({ fontStyle: target.checked ? target.getAttribute('data-fontStyle')! : 'normal' }) }) - document.querySelector('#font-underline')?.addEventListener('click', e => { + document.querySelector('#font-underline')?.addEventListener('click', (e) => { this.hideAllPickerContainer() const target = e.target as HTMLInputElement this.applyFontStyle({ diff --git a/demos/view/contextualtoolbar/ContextualToolbarDemo.js b/demos/view/contextualtoolbar/ContextualToolbarDemo.js index 653a9148c..8ea410205 100644 --- a/demos/view/contextualtoolbar/ContextualToolbarDemo.js +++ b/demos/view/contextualtoolbar/ContextualToolbarDemo.js @@ -105,11 +105,11 @@ function buildGraph(graph, graphData) { const nodesSource = graphBuilder.createNodesSource({ data: graphData.nodeList, - id: item => item.id + id: (item) => item.id }) - nodesSource.nodeCreator.createLabelBinding(item => item.label) - nodesSource.nodeCreator.styleBindings.addBinding('shape', item => item.tag) - nodesSource.nodeCreator.styleBindings.addBinding('fill', item => { + nodesSource.nodeCreator.createLabelBinding((item) => item.label) + nodesSource.nodeCreator.styleBindings.addBinding('shape', (item) => item.tag) + nodesSource.nodeCreator.styleBindings.addBinding('fill', (item) => { if (item.id === 0 || item.id === 4) { return '#e01a4f' } @@ -120,10 +120,10 @@ function buildGraph(graph, graphData) { const edgesSource = graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) - edgesSource.edgeCreator.createLabelBinding(item => item.label) + edgesSource.edgeCreator.createLabelBinding((item) => item.label) graphBuilder.buildGraph() } @@ -162,7 +162,7 @@ function initializeInputMode() { mode.addMultiSelectionFinishedListener((_, evt) => { // this implementation of the contextual toolbar only supports nodes, edges and labels contextualToolbar.selectedItems = evt.selection - .filter(item => item instanceof INode || item instanceof ILabel || item instanceof IEdge) + .filter((item) => item instanceof INode || item instanceof ILabel || item instanceof IEdge) .toArray() }) // ... or when an item is right clicked @@ -179,7 +179,7 @@ function initializeInputMode() { graphComponent.selection.addItemSelectionChangedListener((_, evt) => { if (!evt.itemSelected) { // remove the element from the selectedItems of the contextual toolbar - const idx = contextualToolbar.selectedItems.findIndex(item => item === evt.item) + const idx = contextualToolbar.selectedItems.findIndex((item) => item === evt.item) const newSelection = contextualToolbar.selectedItems.slice() newSelection.splice(idx, 1) contextualToolbar.selectedItems = newSelection diff --git a/demos/view/contextualtoolbar/ContextualToolbarDemo.ts b/demos/view/contextualtoolbar/ContextualToolbarDemo.ts index 8de24211d..75b2dec46 100644 --- a/demos/view/contextualtoolbar/ContextualToolbarDemo.ts +++ b/demos/view/contextualtoolbar/ContextualToolbarDemo.ts @@ -99,11 +99,11 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const nodesSource = graphBuilder.createNodesSource({ data: graphData.nodeList, - id: item => item.id + id: (item) => item.id }) - nodesSource.nodeCreator.createLabelBinding(item => item.label) - nodesSource.nodeCreator.styleBindings.addBinding('shape', item => item.tag) - nodesSource.nodeCreator.styleBindings.addBinding('fill', item => { + nodesSource.nodeCreator.createLabelBinding((item) => item.label) + nodesSource.nodeCreator.styleBindings.addBinding('shape', (item) => item.tag) + nodesSource.nodeCreator.styleBindings.addBinding('fill', (item) => { if (item.id === 0 || item.id === 4) { return '#e01a4f' } @@ -114,10 +114,10 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const edgesSource = graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) - edgesSource.edgeCreator.createLabelBinding(item => item.label) + edgesSource.edgeCreator.createLabelBinding((item) => item.label) graphBuilder.buildGraph() } @@ -155,7 +155,7 @@ function initializeInputMode(): void { mode.addMultiSelectionFinishedListener((_, evt) => { // this implementation of the contextual toolbar only supports nodes, edges and labels contextualToolbar.selectedItems = evt.selection - .filter(item => item instanceof INode || item instanceof ILabel || item instanceof IEdge) + .filter((item) => item instanceof INode || item instanceof ILabel || item instanceof IEdge) .toArray() }) // ... or when an item is right clicked @@ -172,7 +172,7 @@ function initializeInputMode(): void { graphComponent.selection.addItemSelectionChangedListener((_, evt) => { if (!evt.itemSelected) { // remove the element from the selectedItems of the contextual toolbar - const idx = contextualToolbar.selectedItems.findIndex(item => item === evt.item) + const idx = contextualToolbar.selectedItems.findIndex((item) => item === evt.item) const newSelection = contextualToolbar.selectedItems.slice() newSelection.splice(idx, 1) contextualToolbar.selectedItems = newSelection diff --git a/demos/view/contextualtoolbar/index.html b/demos/view/contextualtoolbar/index.html index b44fbdf8c..3aeabde67 100644 --- a/demos/view/contextualtoolbar/index.html +++ b/demos/view/contextualtoolbar/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/contextualtoolbar/resources/contextual-toolbar.css b/demos/view/contextualtoolbar/resources/contextual-toolbar.css index 8278f756d..7edbb1ce3 100644 --- a/demos/view/contextualtoolbar/resources/contextual-toolbar.css +++ b/demos/view/contextualtoolbar/resources/contextual-toolbar.css @@ -43,8 +43,12 @@ box-sizing: border-box; user-select: none; background-color: #f7f7f7; - box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.16), 0 2px 5px 0 rgba(0, 0, 0, 0.26); - transition: opacity 0.2s ease-out, width 0.2s ease-out; + box-shadow: + 0 2px 10px 0 rgba(0, 0, 0, 0.16), + 0 2px 5px 0 rgba(0, 0, 0, 0.26); + transition: + opacity 0.2s ease-out, + width 0.2s ease-out; } .contextual-toolbar button, @@ -112,7 +116,9 @@ .picker-container { position: absolute; background-color: #f7f7f7; - box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.16), 0 2px 5px 0 rgba(0, 0, 0, 0.26); + box-shadow: + 0 2px 10px 0 rgba(0, 0, 0, 0.16), + 0 2px 5px 0 rgba(0, 0, 0, 0.26); opacity: 0; display: none; transition: opacity 0.2s ease-out; diff --git a/demos/view/deep-zoom/deep-zoom-layout.js b/demos/view/deep-zoom/deep-zoom-layout.js index 5aba7360a..afd4d8c85 100644 --- a/demos/view/deep-zoom/deep-zoom-layout.js +++ b/demos/view/deep-zoom/deep-zoom-layout.js @@ -48,7 +48,7 @@ export function applyDeepZoomLayout(foldingView) { const childGroups = masterGraph .getChildren(root) - .filter(child => masterGraph.isGroupNode(child)) + .filter((child) => masterGraph.isGroupNode(child)) for (const childGroup of childGroups) { queue.push([childGroup, layer + 1]) } diff --git a/demos/view/deep-zoom/deep-zoom-layout.ts b/demos/view/deep-zoom/deep-zoom-layout.ts index 8364cf5d8..33a3cdce6 100644 --- a/demos/view/deep-zoom/deep-zoom-layout.ts +++ b/demos/view/deep-zoom/deep-zoom-layout.ts @@ -56,7 +56,7 @@ export function applyDeepZoomLayout(foldingView: IFoldingView): void { const childGroups = masterGraph .getChildren(root) - .filter(child => masterGraph.isGroupNode(child)) + .filter((child) => masterGraph.isGroupNode(child)) for (const childGroup of childGroups) { queue.push([childGroup, layer + 1]) } diff --git a/demos/view/deep-zoom/deep-zoom-update.js b/demos/view/deep-zoom/deep-zoom-update.js index 269f76521..2375c9972 100644 --- a/demos/view/deep-zoom/deep-zoom-update.js +++ b/demos/view/deep-zoom/deep-zoom-update.js @@ -120,7 +120,7 @@ function updateGraphComponentOnViewportChange(graphComponent) { * @returns {?INode} */ function findGroupNodeToEnter(graphComponent, foldingView, masterGraph, viewport) { - return graphComponent.graph.nodes.find(node => { + return graphComponent.graph.nodes.find((node) => { const nodeLayout = node.layout.toRect() const masterNode = foldingView.getMasterItem(node) return ( @@ -268,7 +268,7 @@ function hasGrandChildren(masterGraph, groupNode) { return ( masterGraph .getChildren(groupNode) - .filter(child => masterGraph.isGroupNode(child)) - .flatMap(child => masterGraph.getChildren(child)).size > 0 + .filter((child) => masterGraph.isGroupNode(child)) + .flatMap((child) => masterGraph.getChildren(child)).size > 0 ) } diff --git a/demos/view/deep-zoom/deep-zoom-update.ts b/demos/view/deep-zoom/deep-zoom-update.ts index 4e656bd34..0c6248e77 100644 --- a/demos/view/deep-zoom/deep-zoom-update.ts +++ b/demos/view/deep-zoom/deep-zoom-update.ts @@ -112,7 +112,7 @@ function findGroupNodeToEnter( masterGraph: IGraph, viewport: Rect ): INode | null { - return graphComponent.graph.nodes.find(node => { + return graphComponent.graph.nodes.find((node) => { const nodeLayout = node.layout.toRect() const masterNode = foldingView.getMasterItem(node) return ( @@ -248,7 +248,7 @@ function hasGrandChildren(masterGraph: IGraph, groupNode: INode | null): boolean return ( masterGraph .getChildren(groupNode) - .filter(child => masterGraph.isGroupNode(child)) - .flatMap(child => masterGraph.getChildren(child)).size > 0 + .filter((child) => masterGraph.isGroupNode(child)) + .flatMap((child) => masterGraph.getChildren(child)).size > 0 ) } diff --git a/demos/view/deep-zoom/index.html b/demos/view/deep-zoom/index.html index 8f45125e7..c6c7ee435 100644 --- a/demos/view/deep-zoom/index.html +++ b/demos/view/deep-zoom/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/deep-zoom/model/load-sample-graph.js b/demos/view/deep-zoom/model/load-sample-graph.js index c59196a6d..0e8626b1b 100644 --- a/demos/view/deep-zoom/model/load-sample-graph.js +++ b/demos/view/deep-zoom/model/load-sample-graph.js @@ -46,8 +46,8 @@ export async function loadSampleGraph(graphComponent) { id: 'id', parentId: 'parentId', styleBindings: { - fill: data => data.fill, - stroke: dataItem => `1.5px ${dataItem.stroke}` + fill: (data) => data.fill, + stroke: (dataItem) => `1.5px ${dataItem.stroke}` } }) @@ -58,7 +58,7 @@ export async function loadSampleGraph(graphComponent) { id: 'id', parentId: 'parentId', styleBindings: { - backgroundStyle: dataItem => + backgroundStyle: (dataItem) => new ShapeNodeStyle({ fill: dataItem.fill, stroke: `2.5px ${dataItem.stroke}`, @@ -74,8 +74,8 @@ export async function loadSampleGraph(graphComponent) { sourceId: 'from', targetId: 'to', styleBindings: { - stroke: dataItem => `1.5px ${dataItem.color}`, - targetArrow: data => `${data.color} medium triangle` + stroke: (dataItem) => `1.5px ${dataItem.color}`, + targetArrow: (data) => `${data.color} medium triangle` } }) diff --git a/demos/view/deep-zoom/model/load-sample-graph.ts b/demos/view/deep-zoom/model/load-sample-graph.ts index 396b0718d..5a98d6ba3 100644 --- a/demos/view/deep-zoom/model/load-sample-graph.ts +++ b/demos/view/deep-zoom/model/load-sample-graph.ts @@ -44,8 +44,8 @@ export async function loadSampleGraph(graphComponent: GraphComponent): Promise data.fill, - stroke: dataItem => `1.5px ${dataItem.stroke}` + fill: (data) => data.fill, + stroke: (dataItem) => `1.5px ${dataItem.stroke}` } }) @@ -56,7 +56,7 @@ export async function loadSampleGraph(graphComponent: GraphComponent): Promise + backgroundStyle: (dataItem) => new ShapeNodeStyle({ fill: dataItem.fill, stroke: `2.5px ${dataItem.stroke}`, @@ -72,8 +72,8 @@ export async function loadSampleGraph(graphComponent: GraphComponent): Promise `1.5px ${dataItem.color}`, - targetArrow: data => `${data.color} medium triangle` + stroke: (dataItem) => `1.5px ${dataItem.color}`, + targetArrow: (data) => `${data.color} medium triangle` } }) diff --git a/demos/view/edgetoedge/EdgeToEdgeDemo.js b/demos/view/edgetoedge/EdgeToEdgeDemo.js index 75bb3d7f5..06df28536 100644 --- a/demos/view/edgetoedge/EdgeToEdgeDemo.js +++ b/demos/view/edgetoedge/EdgeToEdgeDemo.js @@ -117,14 +117,14 @@ function initializeGraph() { // enable edge port candidates graph.decorator.edgeDecorator.portCandidateProviderDecorator.setFactory( - edge => new EdgePathPortCandidateProvider(edge) + (edge) => new EdgePathPortCandidateProvider(edge) ) // set IEdgeReconnectionPortCandidateProvider to allow re-connecting edges to other edges graph.decorator.edgeDecorator.edgeReconnectionPortCandidateProviderDecorator.setImplementation( IEdgeReconnectionPortCandidateProvider.ALL_NODE_AND_EDGE_CANDIDATES ) - graph.decorator.edgeDecorator.handleProviderDecorator.setFactory(edge => { + graph.decorator.edgeDecorator.handleProviderDecorator.setFactory((edge) => { const portRelocationHandleProvider = new PortRelocationHandleProvider(null, edge) portRelocationHandleProvider.visualization = Visualization.LIVE return portRelocationHandleProvider @@ -213,7 +213,7 @@ function createSampleGraph() { */ function initializeUI() { const snappingButton = document.querySelector('#demo-snapping-button') - snappingButton.addEventListener('click', evt => { + snappingButton.addEventListener('click', (evt) => { const inputMode = graphComponent.inputMode inputMode.snapContext.enabled = snappingButton.checked }) diff --git a/demos/view/edgetoedge/EdgeToEdgeDemo.ts b/demos/view/edgetoedge/EdgeToEdgeDemo.ts index 1d688dc69..ee5c3f00f 100644 --- a/demos/view/edgetoedge/EdgeToEdgeDemo.ts +++ b/demos/view/edgetoedge/EdgeToEdgeDemo.ts @@ -115,14 +115,14 @@ function initializeGraph(): void { // enable edge port candidates graph.decorator.edgeDecorator.portCandidateProviderDecorator.setFactory( - edge => new EdgePathPortCandidateProvider(edge) + (edge) => new EdgePathPortCandidateProvider(edge) ) // set IEdgeReconnectionPortCandidateProvider to allow re-connecting edges to other edges graph.decorator.edgeDecorator.edgeReconnectionPortCandidateProviderDecorator.setImplementation( IEdgeReconnectionPortCandidateProvider.ALL_NODE_AND_EDGE_CANDIDATES ) - graph.decorator.edgeDecorator.handleProviderDecorator.setFactory(edge => { + graph.decorator.edgeDecorator.handleProviderDecorator.setFactory((edge) => { const portRelocationHandleProvider = new PortRelocationHandleProvider(null, edge) portRelocationHandleProvider.visualization = Visualization.LIVE return portRelocationHandleProvider @@ -210,7 +210,7 @@ function createSampleGraph(): void { */ function initializeUI(): void { const snappingButton = document.querySelector('#demo-snapping-button')! - snappingButton.addEventListener('click', evt => { + snappingButton.addEventListener('click', (evt) => { const inputMode = graphComponent.inputMode as GraphEditorInputMode inputMode.snapContext!.enabled = snappingButton.checked }) diff --git a/demos/view/edgetoedge/index.html b/demos/view/edgetoedge/index.html index af7bf59db..9524a6265 100644 --- a/demos/view/edgetoedge/index.html +++ b/demos/view/edgetoedge/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/events/EventsDemo.js b/demos/view/events/EventsDemo.js index f707a9ace..8a7dd5d5c 100644 --- a/demos/view/events/EventsDemo.js +++ b/demos/view/events/EventsDemo.js @@ -190,26 +190,26 @@ function buildGraph(graph, graphData) { graphBuilder .createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) - .edgeCreator.createLabelBinding(item => item.label) + .edgeCreator.createLabelBinding((item) => item.label) graphBuilder.buildGraph() } @@ -3077,7 +3077,7 @@ function createDraggableNode() { img.addEventListener( 'mousedown', - event => { + (event) => { startDrag() event.preventDefault() }, @@ -3086,7 +3086,7 @@ function createDraggableNode() { img.addEventListener( 'touchstart', - event => { + (event) => { startDrag() event.preventDefault() }, @@ -3149,7 +3149,7 @@ function createDraggableLabel() { img.addEventListener( 'mousedown', - event => { + (event) => { startDrag() event.preventDefault() }, @@ -3158,7 +3158,7 @@ function createDraggableLabel() { img.addEventListener( 'touchstart', - event => { + (event) => { startDrag() event.preventDefault() }, @@ -3223,7 +3223,7 @@ function createDraggablePort() { img.addEventListener( 'mousedown', - event => { + (event) => { startDrag() event.preventDefault() }, @@ -3232,7 +3232,7 @@ function createDraggablePort() { img.addEventListener( 'touchstart', - event => { + (event) => { startDrag() event.preventDefault() }, @@ -3297,7 +3297,7 @@ function bindEventCheckBoxes() { for (let i = 0; i < elements.length; i++) { const element = elements[i] - element.addEventListener('click', e => { + element.addEventListener('click', (e) => { const eventKind = element.getAttribute('data-event-kind') if (eventKind) { const enable = element.checked @@ -3383,7 +3383,7 @@ function initOptionHeadings() { const optionsHeadings = document.getElementsByClassName('event-options-heading') for (let i = 0; i < optionsHeadings.length; i++) { const heading = optionsHeadings[i] - optionsHeadings[i].addEventListener('click', e => { + optionsHeadings[i].addEventListener('click', (e) => { e.preventDefault() const parentNode = heading.parentNode const optionsElements = parentNode.getElementsByClassName('event-options-content') @@ -3404,7 +3404,7 @@ function initOptionHeadings() { const headings = document.getElementsByClassName('event-options-heading') for (let i = 0; i < headings.length; i++) { const heading = headings[i] - heading.addEventListener('click', evt => { + heading.addEventListener('click', (evt) => { if (evt.target instanceof HTMLDivElement) { evt.target.scrollIntoView() } diff --git a/demos/view/events/EventsDemo.ts b/demos/view/events/EventsDemo.ts index 81bc7874d..a47eabe31 100644 --- a/demos/view/events/EventsDemo.ts +++ b/demos/view/events/EventsDemo.ts @@ -183,26 +183,26 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { graphBuilder .createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder .createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) - .edgeCreator.createLabelBinding(item => item.label) + .edgeCreator.createLabelBinding((item) => item.label) graphBuilder.buildGraph() } @@ -3118,7 +3118,7 @@ function createDraggableNode(): HTMLElement { img.addEventListener( 'mousedown', - event => { + (event) => { startDrag() event.preventDefault() }, @@ -3127,7 +3127,7 @@ function createDraggableNode(): HTMLElement { img.addEventListener( 'touchstart', - event => { + (event) => { startDrag() event.preventDefault() }, @@ -3187,7 +3187,7 @@ function createDraggableLabel(): HTMLDivElement { img.addEventListener( 'mousedown', - event => { + (event) => { startDrag() event.preventDefault() }, @@ -3196,7 +3196,7 @@ function createDraggableLabel(): HTMLDivElement { img.addEventListener( 'touchstart', - event => { + (event) => { startDrag() event.preventDefault() }, @@ -3258,7 +3258,7 @@ function createDraggablePort(): HTMLDivElement { img.addEventListener( 'mousedown', - event => { + (event) => { startDrag() event.preventDefault() }, @@ -3267,7 +3267,7 @@ function createDraggablePort(): HTMLDivElement { img.addEventListener( 'touchstart', - event => { + (event) => { startDrag() event.preventDefault() }, @@ -3418,7 +3418,7 @@ function initOptionHeadings(): void { const optionsHeadings = document.getElementsByClassName('event-options-heading') for (let i = 0; i < optionsHeadings.length; i++) { const heading = optionsHeadings[i] - optionsHeadings[i].addEventListener('click', e => { + optionsHeadings[i].addEventListener('click', (e) => { e.preventDefault() const parentNode = heading.parentNode as Element const optionsElements = parentNode.getElementsByClassName( @@ -3441,7 +3441,7 @@ function initOptionHeadings(): void { const headings = document.getElementsByClassName('event-options-heading') for (let i = 0; i < headings.length; i++) { const heading = headings[i] - heading.addEventListener('click', evt => { + heading.addEventListener('click', (evt) => { if (evt.target instanceof HTMLDivElement) { evt.target.scrollIntoView() } diff --git a/demos/view/events/index.html b/demos/view/events/index.html index bee5ec2a5..f9d7223ba 100644 --- a/demos/view/events/index.html +++ b/demos/view/events/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/ganttchart/GanttChartDemo.js b/demos/view/ganttchart/GanttChartDemo.js index 087eec9fd..fb402eb9d 100644 --- a/demos/view/ganttchart/GanttChartDemo.js +++ b/demos/view/ganttchart/GanttChartDemo.js @@ -121,8 +121,11 @@ async function populateGraph() { const graph = graphComponent.graph const graphBuilder = new AdjacencyGraphBuilder(graph) - const nodesSource = graphBuilder.createNodesSource(dataModel.activities, activity => activity.id) - nodesSource.nodeCreator.layoutProvider = activity => { + const nodesSource = graphBuilder.createNodesSource( + dataModel.activities, + (activity) => activity.id + ) + nodesSource.nodeCreator.layoutProvider = (activity) => { return new Rect( getX(new GanttTimestamp(activity.startDate)) - getLeadWidth(activity), getActivityY(activity), @@ -130,11 +133,11 @@ async function populateGraph() { ganttActivityHeight ) } - nodesSource.nodeCreator.styleProvider = activity => { + nodesSource.nodeCreator.styleProvider = (activity) => { const task = getTaskForId(activity.taskId) return new ActivityNodeStyle(getTaskColor(task)) } - nodesSource.nodeCreator.tagProvider = activity => activity + nodesSource.nodeCreator.tagProvider = (activity) => activity nodesSource.nodeCreator.createLabelBinding('name') diff --git a/demos/view/ganttchart/GanttChartDemo.ts b/demos/view/ganttchart/GanttChartDemo.ts index 26547a71a..ad5ff632a 100644 --- a/demos/view/ganttchart/GanttChartDemo.ts +++ b/demos/view/ganttchart/GanttChartDemo.ts @@ -116,7 +116,10 @@ async function populateGraph(): Promise { const graph = graphComponent.graph const graphBuilder = new AdjacencyGraphBuilder(graph) - const nodesSource = graphBuilder.createNodesSource(dataModel.activities, activity => activity.id) + const nodesSource = graphBuilder.createNodesSource( + dataModel.activities, + (activity) => activity.id + ) nodesSource.nodeCreator.layoutProvider = (activity): Rect => { return new Rect( getX(new GanttTimestamp(activity.startDate)) - getLeadWidth(activity), diff --git a/demos/view/ganttchart/GridVisual.js b/demos/view/ganttchart/GridVisual.js index c8a9d190e..c0ab57117 100644 --- a/demos/view/ganttchart/GridVisual.js +++ b/demos/view/ganttchart/GridVisual.js @@ -126,7 +126,7 @@ export class GridVisual extends BaseClass(HtmlCanvasVisual, IVisualCreator) { renderingContext2D.setLineDash([5, 5]) renderingContext2D.beginPath() - this.dataModel.tasks.forEach(task => { + this.dataModel.tasks.forEach((task) => { const y = getTaskY(task) + getCompleteTaskHeight(task) + ganttTaskSpacing * 0.5 renderingContext2D.moveTo(beginX, y) renderingContext2D.lineTo(endX, y) diff --git a/demos/view/ganttchart/activity-node/ActivityNodeHandleProvider.ts b/demos/view/ganttchart/activity-node/ActivityNodeHandleProvider.ts index 74ea72b2f..c9b46b22e 100644 --- a/demos/view/ganttchart/activity-node/ActivityNodeHandleProvider.ts +++ b/demos/view/ganttchart/activity-node/ActivityNodeHandleProvider.ts @@ -213,7 +213,10 @@ export class TimeHandle extends BaseClass(IHandle, IPoint) { private readonly activity: Activity private originalExtent = 0 - constructor(readonly node: INode, readonly isFollowUpTime = false) { + constructor( + readonly node: INode, + readonly isFollowUpTime = false + ) { super() this.activity = getActivity(this.node) } diff --git a/demos/view/ganttchart/components/TaskComponent.js b/demos/view/ganttchart/components/TaskComponent.js index 428faa1d9..5218d4880 100644 --- a/demos/view/ganttchart/components/TaskComponent.js +++ b/demos/view/ganttchart/components/TaskComponent.js @@ -51,7 +51,7 @@ export class TaskComponent { this.parent.append(this.taskWrapper) // synchronize with y-axis with the graphComponent - graphComponent.addViewportChangedListener(graphComponent => { + graphComponent.addViewportChangedListener((graphComponent) => { this.taskWrapper.style.top = `${-graphComponent.viewPoint.y}px` }) } @@ -60,7 +60,7 @@ export class TaskComponent { * Creates a div element for each task stored in the data and assigns the corresponding task color. */ createTasks() { - dataModel.tasks.forEach(task => { + dataModel.tasks.forEach((task) => { const height = getCompleteTaskHeight(task) + ganttTaskSpacing const taskDiv = document.createElement('div') taskDiv.className = 'task-list__task' @@ -78,7 +78,7 @@ export class TaskComponent { * Called when node positions have been modified. */ updateTasks() { - dataModel.tasks.forEach(task => { + dataModel.tasks.forEach((task) => { const elem = this.getTaskElementById(task.id) if (elem) { const height = getCompleteTaskHeight(task) + ganttTaskSpacing diff --git a/demos/view/ganttchart/components/TaskComponent.ts b/demos/view/ganttchart/components/TaskComponent.ts index 9630c3bab..b91f6f31f 100644 --- a/demos/view/ganttchart/components/TaskComponent.ts +++ b/demos/view/ganttchart/components/TaskComponent.ts @@ -48,7 +48,7 @@ export class TaskComponent { this.parent.append(this.taskWrapper) // synchronize with y-axis with the graphComponent - graphComponent.addViewportChangedListener(graphComponent => { + graphComponent.addViewportChangedListener((graphComponent) => { this.taskWrapper.style.top = `${-graphComponent.viewPoint.y}px` }) } @@ -57,7 +57,7 @@ export class TaskComponent { * Creates a div element for each task stored in the data and assigns the corresponding task color. */ createTasks(): void { - dataModel.tasks.forEach(task => { + dataModel.tasks.forEach((task) => { const height = getCompleteTaskHeight(task) + ganttTaskSpacing const taskDiv = document.createElement('div') taskDiv.className = 'task-list__task' @@ -75,7 +75,7 @@ export class TaskComponent { * Called when node positions have been modified. */ updateTasks(): void { - dataModel.tasks.forEach(task => { + dataModel.tasks.forEach((task) => { const elem = this.getTaskElementById(task.id) if (elem) { const height = getCompleteTaskHeight(task) + ganttTaskSpacing diff --git a/demos/view/ganttchart/components/TimelineComponent.js b/demos/view/ganttchart/components/TimelineComponent.js index 0fe5b71b4..f7f979446 100644 --- a/demos/view/ganttchart/components/TimelineComponent.js +++ b/demos/view/ganttchart/components/TimelineComponent.js @@ -51,7 +51,7 @@ export class TimelineComponent { this.update(graphComponent) // synchronize with x-axis with the graph component - graphComponent.addViewportChangedListener(src => this.update(src)) + graphComponent.addViewportChangedListener((src) => this.update(src)) } /** diff --git a/demos/view/ganttchart/components/TimelineComponent.ts b/demos/view/ganttchart/components/TimelineComponent.ts index 8f6b513ce..4469cecc4 100644 --- a/demos/view/ganttchart/components/TimelineComponent.ts +++ b/demos/view/ganttchart/components/TimelineComponent.ts @@ -48,7 +48,7 @@ export class TimelineComponent { this.update(graphComponent) // synchronize with x-axis with the graph component - graphComponent.addViewportChangedListener(src => this.update(src)) + graphComponent.addViewportChangedListener((src) => this.update(src)) } /** diff --git a/demos/view/ganttchart/gantt-utils.js b/demos/view/ganttchart/gantt-utils.js index a868e512c..a11173d70 100644 --- a/demos/view/ganttchart/gantt-utils.js +++ b/demos/view/ganttchart/gantt-utils.js @@ -255,7 +255,7 @@ export function getActivityWidth(activity) { * @returns {!Task} */ export function getTaskForId(taskId) { - return tasks.find(task => task.id === taskId) + return tasks.find((task) => task.id === taskId) } /** @@ -264,7 +264,7 @@ export function getTaskForId(taskId) { * @returns {!string} */ export function getTaskColor(task) { - const taskIndex = tasks.findIndex(t => t.id === task.id) + const taskIndex = tasks.findIndex((t) => t.id === task.id) return colorPalette[taskIndex % colorPalette.length] } diff --git a/demos/view/ganttchart/gantt-utils.ts b/demos/view/ganttchart/gantt-utils.ts index 86d6381c9..7ebdb5505 100644 --- a/demos/view/ganttchart/gantt-utils.ts +++ b/demos/view/ganttchart/gantt-utils.ts @@ -236,14 +236,14 @@ export function getActivityWidth(activity: Activity): number { * Returns the task with the given id. */ export function getTaskForId(taskId: number): Task { - return tasks.find(task => task.id === taskId)! + return tasks.find((task) => task.id === taskId)! } /** * Returns the color for the given task. */ export function getTaskColor(task: Task): string { - const taskIndex = tasks.findIndex(t => t.id === task.id) + const taskIndex = tasks.findIndex((t) => t.id === task.id) return colorPalette[taskIndex % colorPalette.length] } diff --git a/demos/view/ganttchart/index.html b/demos/view/ganttchart/index.html index 8fdd86b8f..0d5c72838 100644 --- a/demos/view/ganttchart/index.html +++ b/demos/view/ganttchart/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/ganttchart/info-panel.js b/demos/view/ganttchart/info-panel.js index 0659cb1b1..5579395a2 100644 --- a/demos/view/ganttchart/info-panel.js +++ b/demos/view/ganttchart/info-panel.js @@ -70,7 +70,7 @@ export function showActivityInfo(activity, location, graphComponent) { const viewLocation = graphComponent.toViewCoordinates(location) const pageLocation = graphComponent.toPageFromView(viewLocation) - const formatted = isoString => { + const formatted = (isoString) => { return new GanttTimestamp(isoString).format() } diff --git a/demos/view/ganttchart/input.js b/demos/view/ganttchart/input.js index e93dc15cd..79648ec23 100644 --- a/demos/view/ganttchart/input.js +++ b/demos/view/ganttchart/input.js @@ -275,7 +275,7 @@ function updateHighlights(sender, hoveredItemChangedEventArgs) { manager.addHighlight(item) if (item instanceof INode) { // highlight dependencies and their activities - sender.inputModeContext.graph.inEdgesAt(item).forEach(edge => { + sender.inputModeContext.graph.inEdgesAt(item).forEach((edge) => { manager.addHighlight(edge) manager.addHighlight(edge.sourceNode) }) diff --git a/demos/view/ganttchart/input.ts b/demos/view/ganttchart/input.ts index a681f21d1..f5946c77e 100644 --- a/demos/view/ganttchart/input.ts +++ b/demos/view/ganttchart/input.ts @@ -286,7 +286,7 @@ function updateHighlights( manager.addHighlight(item) if (item instanceof INode) { // highlight dependencies and their activities - sender.inputModeContext!.graph!.inEdgesAt(item).forEach(edge => { + sender.inputModeContext!.graph!.inEdgesAt(item).forEach((edge) => { manager.addHighlight(edge) manager.addHighlight(edge.sourceNode!) }) diff --git a/demos/view/ganttchart/sweepline-layout.js b/demos/view/ganttchart/sweepline-layout.js index 0d88d8e3b..bc97039e4 100644 --- a/demos/view/ganttchart/sweepline-layout.js +++ b/demos/view/ganttchart/sweepline-layout.js @@ -52,7 +52,7 @@ const subRowCountMap = new Map() */ export function getTaskY(task) { const tasks = dataModel.tasks - const index = tasks.findIndex(t => t.id === task.id) + const index = tasks.findIndex((t) => t.id === task.id) let height = ganttTaskSpacing for (let i = 0; i < index; i++) { height += getCompleteTaskHeight(tasks[i]) + ganttTaskSpacing @@ -85,7 +85,7 @@ export function getTask(y) { */ export function getActivityY(activity) { const taskId = activity.taskId - const task = dataModel.tasks.find(t => t.id === taskId) + const task = dataModel.tasks.find((t) => t.id === taskId) let y = getTaskY(task) + ganttActivitySpacing const subRow = getSubRowIndex(activity) y += subRow * (ganttActivityHeight + ganttActivitySpacing) @@ -160,7 +160,7 @@ export function updateSubRowMappings(graphComponent) { } // calculate the sub-row mapping for each task - dataModel.tasks.forEach(task => { + dataModel.tasks.forEach((task) => { const maxRowIndex = calculateMappingForTask(task, taskId2Activities, subRowMap, graphComponent) subRowCountMap.set(task.id, maxRowIndex + 1) }) @@ -247,7 +247,7 @@ export function calculateMappingForTask( // create an array for the sweep-line algorithm const sweeplineData = [] // push the information about start and end dates for each activity to the array - activityNodes.forEach(node => { + activityNodes.forEach((node) => { const bounds = node.style.renderer .getBoundsProvider(node, node.style) .getBounds(graphComponent.canvasContext) @@ -272,7 +272,7 @@ export function calculateMappingForTask( sweeplineData.sort((t1, t2) => t1.x - t2.x) const subRows = [] // holds information about available and unavailable sub-rows // sweep (scan) the data - sweeplineData.forEach(d => { + sweeplineData.forEach((d) => { // a new task begins if (d.open) { // search for the first available sub-row diff --git a/demos/view/ganttchart/sweepline-layout.ts b/demos/view/ganttchart/sweepline-layout.ts index 68574bb41..96bfb8001 100644 --- a/demos/view/ganttchart/sweepline-layout.ts +++ b/demos/view/ganttchart/sweepline-layout.ts @@ -49,7 +49,7 @@ const subRowCountMap: Map = new Map() */ export function getTaskY(task: Task): number { const tasks = dataModel.tasks - const index = tasks.findIndex(t => t.id === task.id) + const index = tasks.findIndex((t) => t.id === task.id) let height: number = ganttTaskSpacing for (let i = 0; i < index; i++) { height += getCompleteTaskHeight(tasks[i]) + ganttTaskSpacing @@ -78,7 +78,7 @@ export function getTask(y: number): Task { */ export function getActivityY(activity: Activity): number { const taskId = activity.taskId - const task = dataModel.tasks.find(t => t.id === taskId)! + const task = dataModel.tasks.find((t) => t.id === taskId)! let y: number = getTaskY(task) + ganttActivitySpacing const subRow = getSubRowIndex(activity) y += subRow * (ganttActivityHeight + ganttActivitySpacing) @@ -145,7 +145,7 @@ export function updateSubRowMappings(graphComponent: GraphComponent): void { } // calculate the sub-row mapping for each task - dataModel.tasks.forEach(task => { + dataModel.tasks.forEach((task) => { const maxRowIndex = calculateMappingForTask(task, taskId2Activities, subRowMap, graphComponent) subRowCountMap.set(task.id, maxRowIndex + 1) }) @@ -230,7 +230,7 @@ export function calculateMappingForTask( // create an array for the sweep-line algorithm const sweeplineData: SweepLineData[] = [] // push the information about start and end dates for each activity to the array - activityNodes.forEach(node => { + activityNodes.forEach((node) => { const bounds = node.style.renderer .getBoundsProvider(node, node.style) .getBounds(graphComponent.canvasContext) @@ -255,7 +255,7 @@ export function calculateMappingForTask( sweeplineData.sort((t1, t2) => t1.x - t2.x) const subRows: Activity[] = [] // holds information about available and unavailable sub-rows // sweep (scan) the data - sweeplineData.forEach(d => { + sweeplineData.forEach((d) => { // a new task begins if (d.open) { // search for the first available sub-row diff --git a/demos/view/grapheditor/GraphEditorDemo.js b/demos/view/grapheditor/GraphEditorDemo.js index 129ff87c2..8b6d6a9e1 100644 --- a/demos/view/grapheditor/GraphEditorDemo.js +++ b/demos/view/grapheditor/GraphEditorDemo.js @@ -132,23 +132,23 @@ function buildGraph(graph, graphData) { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -190,7 +190,7 @@ function createEditorMode() { // Add event listeners to the various events that open the context menu. These listeners then // call the provided callback function, which in turn asks the current ContextMenuInputMode if a // context menu should be shown at the current location. - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (mode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } @@ -293,12 +293,12 @@ function initializeUI(graphComponent) { function selectAllEdges() { graphComponent.selection.clear() - graphComponent.graph.edges.forEach(edge => graphComponent.selection.setSelected(edge, true)) + graphComponent.graph.edges.forEach((edge) => graphComponent.selection.setSelected(edge, true)) } function selectAllNodes() { graphComponent.selection.clear() - graphComponent.graph.nodes.forEach(node => graphComponent.selection.setSelected(node, true)) + graphComponent.graph.nodes.forEach((node) => graphComponent.selection.setSelected(node, true)) } /** @@ -318,7 +318,7 @@ function populateContextMenu(contextMenu, args) { const hits = graphComponent.graphModelManager.hitElementsAt(args.queryLocation) // Check whether a node was it. If it was, we prefer it over edges - const hit = hits.find(item => INode.isInstance(item)) || hits.at(0) + const hit = hits.find((item) => INode.isInstance(item)) || hits.at(0) const graphSelection = graphComponent.selection if (INode.isInstance(hit)) { diff --git a/demos/view/grapheditor/GraphEditorDemo.ts b/demos/view/grapheditor/GraphEditorDemo.ts index d97c8c08b..57ae47496 100644 --- a/demos/view/grapheditor/GraphEditorDemo.ts +++ b/demos/view/grapheditor/GraphEditorDemo.ts @@ -128,23 +128,23 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { const graphBuilder = new GraphBuilder(graph) graphBuilder.createNodesSource({ - data: graphData.nodeList.filter(item => !item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => !item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) graphBuilder .createGroupNodesSource({ - data: graphData.nodeList.filter(item => item.isGroup), - id: item => item.id, - parentId: item => item.parentId + data: graphData.nodeList.filter((item) => item.isGroup), + id: (item) => item.id, + parentId: (item) => item.parentId }) - .nodeCreator.createLabelBinding(item => item.label) + .nodeCreator.createLabelBinding((item) => item.label) graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -185,7 +185,7 @@ function createEditorMode(): GraphEditorInputMode { // Add event listeners to the various events that open the context menu. These listeners then // call the provided callback function, which in turn asks the current ContextMenuInputMode if a // context menu should be shown at the current location. - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (mode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } @@ -286,12 +286,12 @@ function initializeUI(graphComponent: GraphComponent): void { function selectAllEdges(): void { graphComponent.selection.clear() - graphComponent.graph.edges.forEach(edge => graphComponent.selection.setSelected(edge, true)) + graphComponent.graph.edges.forEach((edge) => graphComponent.selection.setSelected(edge, true)) } function selectAllNodes(): void { graphComponent.selection.clear() - graphComponent.graph.nodes.forEach(node => graphComponent.selection.setSelected(node, true)) + graphComponent.graph.nodes.forEach((node) => graphComponent.selection.setSelected(node, true)) } /** @@ -314,7 +314,7 @@ function populateContextMenu( const hits = graphComponent.graphModelManager.hitElementsAt(args.queryLocation) // Check whether a node was it. If it was, we prefer it over edges - const hit = hits.find(item => INode.isInstance(item)) || hits.at(0) + const hit = hits.find((item) => INode.isInstance(item)) || hits.at(0) const graphSelection = graphComponent.selection if (INode.isInstance(hit)) { diff --git a/demos/view/grapheditor/index.html b/demos/view/grapheditor/index.html index 2d2faf6e8..704355d0c 100644 --- a/demos/view/grapheditor/index.html +++ b/demos/view/grapheditor/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/graphml/EditorSync.js b/demos/view/graphml/EditorSync.js index 9b688aa71..8cba75034 100644 --- a/demos/view/graphml/EditorSync.js +++ b/demos/view/graphml/EditorSync.js @@ -227,7 +227,7 @@ export class EditorSync { * Map editor markers to graph items. */ setMarkers() { - this.itemToMarkerMap.values.forEach(marker => { + this.itemToMarkerMap.values.forEach((marker) => { marker.clear() }) this.itemToMarkerMap.clear() @@ -236,10 +236,10 @@ export class EditorSync { const editorText = this.editor.getValue() const graph = this._graph - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { this.setMarkersForItem(node, 'node', editorText) }) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { this.setMarkersForItem(edge, 'edge', editorText) }) } diff --git a/demos/view/graphml/EditorSync.ts b/demos/view/graphml/EditorSync.ts index df0b6694b..160184af7 100644 --- a/demos/view/graphml/EditorSync.ts +++ b/demos/view/graphml/EditorSync.ts @@ -222,7 +222,7 @@ export class EditorSync { * Map editor markers to graph items. */ private setMarkers(): void { - this.itemToMarkerMap.values.forEach(marker => { + this.itemToMarkerMap.values.forEach((marker) => { marker.clear() }) this.itemToMarkerMap.clear() @@ -231,10 +231,10 @@ export class EditorSync { const editorText = this.editor.getValue() const graph = this._graph! - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { this.setMarkersForItem(node, 'node', editorText) }) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { this.setMarkersForItem(edge, 'edge', editorText) }) } diff --git a/demos/view/graphml/GraphMLDemo.js b/demos/view/graphml/GraphMLDemo.js index 5b5e0ae45..08efd0d05 100644 --- a/demos/view/graphml/GraphMLDemo.js +++ b/demos/view/graphml/GraphMLDemo.js @@ -307,10 +307,10 @@ function createGraphMLIOHandler() { function initializeEditorSynchronization() { editorSync.initialize(foldingView.manager.masterGraph) // Update the view when the editor's selection or content changes - editorSync.addItemSelectedListener(evt => { + editorSync.addItemSelectedListener((evt) => { onEditorItemSelected(evt.item) }) - editorSync.addEditorContentChangedListener(evt => { + editorSync.addEditorContentChangedListener((evt) => { onEditorContentChanged(evt.value) }) @@ -495,7 +495,7 @@ function parse(type, value) { * @param {!QueryOutputHandlersEventArgs} args */ function queryOutputHandlers(args) { - propertiesPanel.properties.forEach(property => { + propertiesPanel.properties.forEach((property) => { if (property.type !== KeyType.COMPLEX && property.keyScope === args.scope) { args.addOutputHandler(new SimpleOutputHandler(property, propertiesPanel)) } @@ -564,9 +564,9 @@ function getMasterItem(item) { function initializeUI() { document .querySelector('button[data-command="NEW"]') - .addEventListener('click', evt => clearGraph()) + .addEventListener('click', (evt) => clearGraph()) const openButton = document.querySelector('button[data-command="OPEN"]') - openButton.addEventListener('click', evt => onOpenCommandExecuted()) + openButton.addEventListener('click', (evt) => onOpenCommandExecuted()) // prevent auto-registering the OPEN command by finishLoading openButton.setAttribute('data-command-registered', 'true') } diff --git a/demos/view/graphml/GraphMLDemo.ts b/demos/view/graphml/GraphMLDemo.ts index 0c70d0c2a..aaef1e968 100644 --- a/demos/view/graphml/GraphMLDemo.ts +++ b/demos/view/graphml/GraphMLDemo.ts @@ -298,10 +298,10 @@ function createGraphMLIOHandler(): GraphMLIOHandler { function initializeEditorSynchronization(): void { editorSync.initialize(foldingView.manager.masterGraph) // Update the view when the editor's selection or content changes - editorSync.addItemSelectedListener(evt => { + editorSync.addItemSelectedListener((evt) => { onEditorItemSelected(evt.item) }) - editorSync.addEditorContentChangedListener(evt => { + editorSync.addEditorContentChangedListener((evt) => { onEditorContentChanged(evt.value) }) @@ -478,7 +478,7 @@ function parse(type: Class, value: string): T { * written back to GraphML. For this purpose, it adds an output handler for each property. */ function queryOutputHandlers(args: QueryOutputHandlersEventArgs): void { - propertiesPanel.properties.forEach(property => { + propertiesPanel.properties.forEach((property) => { if (property.type !== KeyType.COMPLEX && property.keyScope === args.scope) { args.addOutputHandler(new SimpleOutputHandler(property, propertiesPanel)) } @@ -545,9 +545,9 @@ function getMasterItem(item: IModelItem | null): IModelItem | null { function initializeUI(): void { document .querySelector('button[data-command="NEW"]')! - .addEventListener('click', evt => clearGraph()) + .addEventListener('click', (evt) => clearGraph()) const openButton = document.querySelector('button[data-command="OPEN"]')! - openButton.addEventListener('click', evt => onOpenCommandExecuted()) + openButton.addEventListener('click', (evt) => onOpenCommandExecuted()) // prevent auto-registering the OPEN command by finishLoading openButton.setAttribute('data-command-registered', 'true') } diff --git a/demos/view/graphml/GraphMLProperty.js b/demos/view/graphml/GraphMLProperty.js index 7c4bf8cc6..19da9574b 100644 --- a/demos/view/graphml/GraphMLProperty.js +++ b/demos/view/graphml/GraphMLProperty.js @@ -37,6 +37,4 @@ export class GraphMLProperty { name = null type = KeyType.INT keyScope = KeyScope.ALL - - constructor() {} } diff --git a/demos/view/graphml/GraphMLProperty.ts b/demos/view/graphml/GraphMLProperty.ts index 4e65e54e9..aeb1488e8 100644 --- a/demos/view/graphml/GraphMLProperty.ts +++ b/demos/view/graphml/GraphMLProperty.ts @@ -37,6 +37,4 @@ export class GraphMLProperty { name: string | null = null type: KeyType = KeyType.INT keyScope: KeyScope = KeyScope.ALL - - constructor() {} } diff --git a/demos/view/graphml/PropertiesPanel.js b/demos/view/graphml/PropertiesPanel.js index 25bf4335a..a252d5310 100644 --- a/demos/view/graphml/PropertiesPanel.js +++ b/demos/view/graphml/PropertiesPanel.js @@ -195,7 +195,7 @@ export class PropertiesPanel { this._currentItem = currentItem if (currentItem) { - this.itemMap.keys.forEach(property => { + this.itemMap.keys.forEach((property) => { if (PropertiesPanel.suitsScope(currentItem, property.keyScope)) { this.ui.addItemProperty(property, this.getItemValue(currentItem, property)) } @@ -207,7 +207,7 @@ export class PropertiesPanel { * Displays the graph properties in the UI after all properties have been added. */ showGraphProperties() { - this.graphMap.keys.forEach(property => { + this.graphMap.keys.forEach((property) => { this.ui.addGraphProperty(property, this.graphMap.get(property)) }) } diff --git a/demos/view/graphml/PropertiesPanel.ts b/demos/view/graphml/PropertiesPanel.ts index 3e2f0b95f..5c0349fa3 100644 --- a/demos/view/graphml/PropertiesPanel.ts +++ b/demos/view/graphml/PropertiesPanel.ts @@ -170,7 +170,7 @@ export class PropertiesPanel { this._currentItem = currentItem if (currentItem) { - this.itemMap.keys.forEach(property => { + this.itemMap.keys.forEach((property) => { if (PropertiesPanel.suitsScope(currentItem, property.keyScope)) { this.ui.addItemProperty(property, this.getItemValue(currentItem, property)) } @@ -182,7 +182,7 @@ export class PropertiesPanel { * Displays the graph properties in the UI after all properties have been added. */ showGraphProperties(): void { - this.graphMap.keys.forEach(property => { + this.graphMap.keys.forEach((property) => { this.ui.addGraphProperty(property, this.graphMap.get(property)) }) } diff --git a/demos/view/graphml/PropertiesPanelUI.js b/demos/view/graphml/PropertiesPanelUI.js index 9dc938179..661f870d7 100644 --- a/demos/view/graphml/PropertiesPanelUI.js +++ b/demos/view/graphml/PropertiesPanelUI.js @@ -147,7 +147,7 @@ export default class PropertiesPanelUI { const nameInputGraph = inputsGraph[0] const valueInputGraph = inputsGraph[1] - const graphDataListener = event => { + const graphDataListener = (event) => { if (event.key === 'Enter') { if (this.graphPropertyAddedCallback && nameInputGraph.value) { this.graphPropertyAddedCallback(nameInputGraph.value, valueInputGraph.value) @@ -166,7 +166,7 @@ export default class PropertiesPanelUI { const nameInputItem = inputsItem[0] const valueInputItem = inputsItem[1] - const itemDataListener = event => { + const itemDataListener = (event) => { if (event.key === 'Enter') { if (this.itemPropertyAddedCallback) { this.itemPropertyAddedCallback(nameInputItem.value, valueInputItem.value) diff --git a/demos/view/graphml/SimpleOutputHandler.ts b/demos/view/graphml/SimpleOutputHandler.ts index 755080cc0..ddc206b4f 100644 --- a/demos/view/graphml/SimpleOutputHandler.ts +++ b/demos/view/graphml/SimpleOutputHandler.ts @@ -34,7 +34,10 @@ import type { PropertiesPanel } from './PropertiesPanel' * An output handler that writes primitive data types and ignores complex types. */ export class SimpleOutputHandler extends OutputHandlerBase { - constructor(private property: GraphMLProperty, private propertiesPanel: PropertiesPanel) { + constructor( + private property: GraphMLProperty, + private propertiesPanel: PropertiesPanel + ) { super(YObject.$class, YObject.$class, property.keyScope, property.name, property.type) this.defaultExists = property.defaultExists if (property.defaultExists) { diff --git a/demos/view/graphml/index.html b/demos/view/graphml/index.html index b13d5c35a..7e10eaa9b 100644 --- a/demos/view/graphml/index.html +++ b/demos/view/graphml/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/graphviewer/FastCanvasStyles.js b/demos/view/graphviewer/FastCanvasStyles.js index d23f59f2e..c145a7cd1 100644 --- a/demos/view/graphviewer/FastCanvasStyles.js +++ b/demos/view/graphviewer/FastCanvasStyles.js @@ -173,7 +173,7 @@ class EdgeCanvasVisual extends HtmlCanvasVisual { let location = this.sourcePortLocation ctx.moveTo(location.x, location.y) if (this.bends.size > 0) { - this.bends.forEach(bend => { + this.bends.forEach((bend) => { location = bend.location ctx.lineTo(location.x, location.y) }) @@ -192,10 +192,6 @@ export class FastLabelStyle extends LabelStyleBase { _zoomThreshold = 0.7 _font = new Font() - constructor() { - super() - } - /** * @type {number} */ diff --git a/demos/view/graphviewer/FastCanvasStyles.ts b/demos/view/graphviewer/FastCanvasStyles.ts index 663b02c85..d8aba5462 100644 --- a/demos/view/graphviewer/FastCanvasStyles.ts +++ b/demos/view/graphviewer/FastCanvasStyles.ts @@ -147,7 +147,7 @@ class EdgeCanvasVisual extends HtmlCanvasVisual { let location: IPoint = this.sourcePortLocation ctx.moveTo(location.x, location.y) if (this.bends.size > 0) { - this.bends.forEach(bend => { + this.bends.forEach((bend) => { location = bend.location ctx.lineTo(location.x, location.y) }) @@ -166,10 +166,6 @@ export class FastLabelStyle extends LabelStyleBase { private _zoomThreshold = 0.7 private _font: Font = new Font() - constructor() { - super() - } - get zoomThreshold(): number { return this._zoomThreshold } diff --git a/demos/view/graphviewer/GraphViewerDemo.js b/demos/view/graphviewer/GraphViewerDemo.js index 83092fbe5..f79b5d08f 100644 --- a/demos/view/graphviewer/GraphViewerDemo.js +++ b/demos/view/graphviewer/GraphViewerDemo.js @@ -64,7 +64,7 @@ import { YString } from 'yfiles' -import GraphSearch from 'demo-utils/GraphSearch' +import { GraphSearch } from 'demo-utils/GraphSearch' import FastCanvasStyles from './FastCanvasStyles.js' import { ContextMenu } from 'demo-utils/ContextMenu' import { applyDemoTheme, DemoStyleOverviewPaintable } from 'demo-resources/demo-styles' @@ -226,7 +226,7 @@ function initializeHighlightStyles() { zoomPolicy: StyleDecorationZoomPolicy.VIEW_COORDINATES }) - graphComponent.graph.decorator.edgeDecorator.highlightDecorator.setFactory(edge => + graphComponent.graph.decorator.edgeDecorator.highlightDecorator.setFactory((edge) => edge.style instanceof BezierEdgeStyle ? bezierEdgeStyleHighlight : edgeStyleHighlight ) } @@ -320,7 +320,7 @@ function initializeContextMenu(inputMode) { // Add event listeners to the various events that open the context menu. These listeners then // call the provided callback function which in turn asks the current ContextMenuInputMode if a // context menu should be shown at the current location. - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (inputMode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } @@ -669,7 +669,7 @@ class CustomGraphSearch extends GraphSearch { if ( node.tag && Object.getOwnPropertyNames(node.tag).some( - prop => + (prop) => prop !== 'icon' && node.tag[prop] && node.tag[prop].toString().toLowerCase().indexOf(lowercaseText) !== -1 @@ -677,7 +677,7 @@ class CustomGraphSearch extends GraphSearch { ) { return true } - return node.labels.some(label => label.text.toLowerCase().indexOf(lowercaseText) !== -1) + return node.labels.some((label) => label.text.toLowerCase().indexOf(lowercaseText) !== -1) } } diff --git a/demos/view/graphviewer/GraphViewerDemo.ts b/demos/view/graphviewer/GraphViewerDemo.ts index d23a4ee3a..57370b3c5 100644 --- a/demos/view/graphviewer/GraphViewerDemo.ts +++ b/demos/view/graphviewer/GraphViewerDemo.ts @@ -64,7 +64,7 @@ import { YString } from 'yfiles' -import GraphSearch from 'demo-utils/GraphSearch' +import { GraphSearch } from 'demo-utils/GraphSearch' import FastCanvasStyles from './FastCanvasStyles' import { ContextMenu } from 'demo-utils/ContextMenu' import { applyDemoTheme, DemoStyleOverviewPaintable } from 'demo-resources/demo-styles' @@ -219,7 +219,7 @@ function initializeHighlightStyles(): void { zoomPolicy: StyleDecorationZoomPolicy.VIEW_COORDINATES }) - graphComponent.graph.decorator.edgeDecorator.highlightDecorator.setFactory(edge => + graphComponent.graph.decorator.edgeDecorator.highlightDecorator.setFactory((edge) => edge.style instanceof BezierEdgeStyle ? bezierEdgeStyleHighlight : edgeStyleHighlight ) } @@ -312,7 +312,7 @@ function initializeContextMenu(inputMode: GraphInputMode): void { // Add event listeners to the various events that open the context menu. These listeners then // call the provided callback function which in turn asks the current ContextMenuInputMode if a // context menu should be shown at the current location. - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (inputMode.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } @@ -649,7 +649,7 @@ class CustomGraphSearch extends GraphSearch { if ( node.tag && Object.getOwnPropertyNames(node.tag).some( - prop => + (prop) => prop !== 'icon' && node.tag[prop] && node.tag[prop].toString().toLowerCase().indexOf(lowercaseText) !== -1 @@ -657,7 +657,7 @@ class CustomGraphSearch extends GraphSearch { ) { return true } - return node.labels.some(label => label.text.toLowerCase().indexOf(lowercaseText) !== -1) + return node.labels.some((label) => label.text.toLowerCase().indexOf(lowercaseText) !== -1) } } diff --git a/demos/view/graphviewer/index.html b/demos/view/graphviewer/index.html index b2a70e8c6..23b2686c4 100644 --- a/demos/view/graphviewer/index.html +++ b/demos/view/graphviewer/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/htmlpopup/index.html b/demos/view/htmlpopup/index.html index 74c87c6b0..97ac714e8 100644 --- a/demos/view/htmlpopup/index.html +++ b/demos/view/htmlpopup/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/imageexport/ImageExportDemo.js b/demos/view/imageexport/ImageExportDemo.js index 7ccc42b3c..9987e9761 100644 --- a/demos/view/imageexport/ImageExportDemo.js +++ b/demos/view/imageexport/ImageExportDemo.js @@ -66,7 +66,7 @@ async function run() { const exportRect = initializeExportRectangle(graphComponent) - initializeOptionPanel(async options => { + initializeOptionPanel(async (options) => { const rect = options.useExportRectangle ? exportRect.toRect() : undefined if (options.serverExport) { @@ -77,7 +77,7 @@ async function run() { } }) - initializeExportDialog('Client-side Image Export', imageElement => { + initializeExportDialog('Client-side Image Export', (imageElement) => { const image = imageElement try { downloadFile(image.src, 'graph.png') diff --git a/demos/view/imageexport/ImageExportDemo.ts b/demos/view/imageexport/ImageExportDemo.ts index 8e318dac2..acde0f653 100644 --- a/demos/view/imageexport/ImageExportDemo.ts +++ b/demos/view/imageexport/ImageExportDemo.ts @@ -63,7 +63,7 @@ async function run(): Promise { const exportRect = initializeExportRectangle(graphComponent) - initializeOptionPanel(async options => { + initializeOptionPanel(async (options) => { const rect = options.useExportRectangle ? exportRect.toRect() : undefined if (options.serverExport) { @@ -74,7 +74,7 @@ async function run(): Promise { } }) - initializeExportDialog('Client-side Image Export', imageElement => { + initializeExportDialog('Client-side Image Export', (imageElement) => { const image = imageElement as HTMLImageElement try { downloadFile(image.src, 'graph.png') diff --git a/demos/view/imageexport/aspect-ratio.js b/demos/view/imageexport/aspect-ratio.js index 44d685c07..935aa2293 100644 --- a/demos/view/imageexport/aspect-ratio.js +++ b/demos/view/imageexport/aspect-ratio.js @@ -41,8 +41,8 @@ import { */ export function retainAspectRatio(graph) { graph.decorator.nodeDecorator.reshapeHandleProviderDecorator.setFactory( - node => node.style instanceof ImageNodeStyle, - node => { + (node) => node.style instanceof ImageNodeStyle, + (node) => { const keepAspectRatio = new NodeReshapeHandleProvider( node, node.lookup(IReshapeHandler.$class), diff --git a/demos/view/imageexport/aspect-ratio.ts b/demos/view/imageexport/aspect-ratio.ts index 38152a215..f0f1d6190 100644 --- a/demos/view/imageexport/aspect-ratio.ts +++ b/demos/view/imageexport/aspect-ratio.ts @@ -41,8 +41,8 @@ import { */ export function retainAspectRatio(graph: IGraph): void { graph.decorator.nodeDecorator.reshapeHandleProviderDecorator.setFactory( - node => node.style instanceof ImageNodeStyle, - node => { + (node) => node.style instanceof ImageNodeStyle, + (node) => { const keepAspectRatio = new NodeReshapeHandleProvider( node, node.lookup(IReshapeHandler.$class)!, diff --git a/demos/view/imageexport/export-dialog/export-dialog.js b/demos/view/imageexport/export-dialog/export-dialog.js index f59a4c49d..0b2a746a7 100644 --- a/demos/view/imageexport/export-dialog/export-dialog.js +++ b/demos/view/imageexport/export-dialog/export-dialog.js @@ -57,7 +57,7 @@ export function initializeExportDialog(heading, saveCallback) { saveCallback(previewElement) }) - closeButton.addEventListener('click', _ => { + closeButton.addEventListener('click', (_) => { // Hide the popup dialog.style.display = 'none' // Remove the exported SVG element from the popup since it is no longer needed diff --git a/demos/view/imageexport/export-dialog/export-dialog.ts b/demos/view/imageexport/export-dialog/export-dialog.ts index 106ceffe4..46d0a9220 100644 --- a/demos/view/imageexport/export-dialog/export-dialog.ts +++ b/demos/view/imageexport/export-dialog/export-dialog.ts @@ -60,7 +60,7 @@ export function initializeExportDialog( saveCallback(previewElement) }) - closeButton.addEventListener('click', _ => { + closeButton.addEventListener('click', (_) => { // Hide the popup dialog.style.display = 'none' // Remove the exported SVG element from the popup since it is no longer needed diff --git a/demos/view/imageexport/image-export-client-side.js b/demos/view/imageexport/image-export-client-side.js index 1fa358e35..5a3c60e05 100644 --- a/demos/view/imageexport/image-export-client-side.js +++ b/demos/view/imageexport/image-export-client-side.js @@ -98,7 +98,7 @@ function renderSvgToPng(svgElement, size, margins) { const svgString = SvgExport.exportSvgString(svgElement) const svgUrl = SvgExport.encodeSvgDataUrl(svgString) - return new Promise(resolve => { + return new Promise((resolve) => { // The SVG image is now used as the source of an HTML image element, // which is then rendered onto a Canvas element. diff --git a/demos/view/imageexport/image-export-client-side.ts b/demos/view/imageexport/image-export-client-side.ts index 0a5413cde..665fd3320 100644 --- a/demos/view/imageexport/image-export-client-side.ts +++ b/demos/view/imageexport/image-export-client-side.ts @@ -110,7 +110,7 @@ function renderSvgToPng( const svgString = SvgExport.exportSvgString(svgElement) const svgUrl = SvgExport.encodeSvgDataUrl(svgString) - return new Promise(resolve => { + return new Promise((resolve) => { // The SVG image is now used as the source of an HTML image element, // which is then rendered onto a Canvas element. diff --git a/demos/view/imageexport/index.html b/demos/view/imageexport/index.html index f73c830b5..b0a3255ba 100644 --- a/demos/view/imageexport/index.html +++ b/demos/view/imageexport/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/imageexport/node-server/README.html b/demos/view/imageexport/node-server/README.html index ddc38ca7d..e8cd4f9f4 100644 --- a/demos/view/imageexport/node-server/README.html +++ b/demos/view/imageexport/node-server/README.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/imageexport/node-server/index.html b/demos/view/imageexport/node-server/index.html index d86ded61c..bdbf2f300 100644 --- a/demos/view/imageexport/node-server/index.html +++ b/demos/view/imageexport/node-server/index.html @@ -1,4 +1,4 @@ - + @@ -39,7 +39,7 @@ w = parseInt(w) h = parseInt(h) margin = parseInt(margin) - return new Promise(resolve => { + return new Promise((resolve) => { const targetCanvas = document.createElement('canvas') const targetContext = targetCanvas.getContext('2d') const image = new Image() diff --git a/demos/view/imageexport/option-panel/option-panel.js b/demos/view/imageexport/option-panel/option-panel.js index 86428e673..79cb095da 100644 --- a/demos/view/imageexport/option-panel/option-panel.js +++ b/demos/view/imageexport/option-panel/option-panel.js @@ -52,11 +52,11 @@ export function initializeOptionPanel(exportCallback) { serverExport: serverExportInput.checked } - if (isNaN(options.scale) || options.scale <= 0) { + if (Number.isNaN(options.scale) || options.scale <= 0) { alert('Scale must be a positive number.') return false } - if (isNaN(options.margin) || options.margin < 0) { + if (Number.isNaN(options.margin) || options.margin < 0) { alert('Margin must be a non-negative number.') return false } diff --git a/demos/view/imageexport/option-panel/option-panel.ts b/demos/view/imageexport/option-panel/option-panel.ts index 4c7447bc8..3e3484b02 100644 --- a/demos/view/imageexport/option-panel/option-panel.ts +++ b/demos/view/imageexport/option-panel/option-panel.ts @@ -50,11 +50,11 @@ export function initializeOptionPanel(exportCallback: (options: ImageExportOptio serverExport: serverExportInput.checked } - if (isNaN(options.scale) || options.scale <= 0) { + if (Number.isNaN(options.scale) || options.scale <= 0) { alert('Scale must be a positive number.') return false } - if (isNaN(options.margin) || options.margin < 0) { + if (Number.isNaN(options.margin) || options.margin < 0) { alert('Margin must be a non-negative number.') return false } diff --git a/demos/view/imageexport/server-side-export.js b/demos/view/imageexport/server-side-export.js index 05b46234d..182cc0ab1 100644 --- a/demos/view/imageexport/server-side-export.js +++ b/demos/view/imageexport/server-side-export.js @@ -40,7 +40,7 @@ export function initializeServerSideExport(url) { // if a server is available, enable the server export button isServerAlive(url) - .then(response => { + .then((response) => { document.querySelector('#server-export').disabled = false }) .catch(() => { diff --git a/demos/view/imageexport/server-side-export.ts b/demos/view/imageexport/server-side-export.ts index 9d5b5f4a1..575d41aae 100644 --- a/demos/view/imageexport/server-side-export.ts +++ b/demos/view/imageexport/server-side-export.ts @@ -39,7 +39,7 @@ export function initializeServerSideExport(url: string): void { // if a server is available, enable the server export button isServerAlive(url) - .then(response => { + .then((response) => { document.querySelector('#server-export')!.disabled = false }) .catch(() => { diff --git a/demos/view/imageexport/webgl-support.js b/demos/view/imageexport/webgl-support.js index 92cf01cec..f177889b6 100644 --- a/demos/view/imageexport/webgl-support.js +++ b/demos/view/imageexport/webgl-support.js @@ -63,7 +63,7 @@ export function initializeToggleWebGl2RenderingButton(graphComponent, addSeparat toggleButton.type = 'checkbox' toggleButton.classList.add('demo-toggle-button', 'labeled') toggleButton.disabled = true - toggleButton.addEventListener('change', evt => { + toggleButton.addEventListener('change', (evt) => { if (evt.target.checked) { useWebGL2Rendering(graphComponent) } else { @@ -109,7 +109,7 @@ export async function createIconImageData() { const svgSize = new Size(70, 70) const ctx = createCanvasContext(128, 128) const imageDataArray = await Promise.all( - deviceNames.map(device => createUrlIcon(ctx, `./resources/${device}.svg`, svgSize)) + deviceNames.map((device) => createUrlIcon(ctx, `./resources/${device}.svg`, svgSize)) ) for (let i = 0; i < deviceNames.length; i++) { diff --git a/demos/view/imageexport/webgl-support.ts b/demos/view/imageexport/webgl-support.ts index 7cc061373..f3c96a3a4 100644 --- a/demos/view/imageexport/webgl-support.ts +++ b/demos/view/imageexport/webgl-support.ts @@ -67,7 +67,7 @@ export function initializeToggleWebGl2RenderingButton( toggleButton.type = 'checkbox' toggleButton.classList.add('demo-toggle-button', 'labeled') toggleButton.disabled = true - toggleButton.addEventListener('change', evt => { + toggleButton.addEventListener('change', (evt) => { if ((evt.target as HTMLInputElement).checked) { useWebGL2Rendering(graphComponent) } else { @@ -112,7 +112,7 @@ export async function createIconImageData(): Promise { const svgSize = new Size(70, 70) const ctx = createCanvasContext(128, 128) const imageDataArray = await Promise.all( - deviceNames.map(device => createUrlIcon(ctx, `./resources/${device}.svg`, svgSize)) + deviceNames.map((device) => createUrlIcon(ctx, `./resources/${device}.svg`, svgSize)) ) for (let i = 0; i < deviceNames.length; i++) { diff --git a/demos/view/large-tree/LargeTreeDemo.js b/demos/view/large-tree/LargeTreeDemo.js index 52439643e..4d190e39a 100644 --- a/demos/view/large-tree/LargeTreeDemo.js +++ b/demos/view/large-tree/LargeTreeDemo.js @@ -98,7 +98,7 @@ async function run() { * Initializes the WebGL2 node and edge styles */ function initializeStyleDefaults() { - ;['#242265', '#01baff', '#f26419', '#fdca40'].forEach(color => { + ;['#242265', '#01baff', '#f26419', '#fdca40'].forEach((color) => { nodeStyles.push(new WebGL2ShapeNodeStyle('round-rectangle', color, '#0000')) }) @@ -140,7 +140,7 @@ function updateLayersUI(graphComponent) { // disable/enable add layer button const leaves = graphComponent.graph.nodes.filter( - node => graphComponent.graph.outDegree(node) == 0 + (node) => graphComponent.graph.outDegree(node) == 0 ).size const childCount = Number(document.querySelector('#childCountInput').value) document.querySelector('#add-layer').disabled = @@ -228,8 +228,8 @@ async function addLayer(graphComponent) { const queue = [] graph.nodes - .filter(node => node.tag.layer == graphInfo.maxLayer) - .forEach(node => { + .filter((node) => node.tag.layer == graphInfo.maxLayer) + .forEach((node) => { queue.push(node) }) @@ -313,7 +313,7 @@ async function runExtendLayout(graphComponent, fadeInAnimation) { const graph = graphComponent.graph const fixedNodeData = new FixNodeLayoutData({ - fixedNodes: graph.nodes.find(n => graph.inDegree(n) == 0) + fixedNodes: graph.nodes.find((n) => graph.inDegree(n) == 0) }) const layout = new FixNodeLayoutStage(coreLayout) @@ -333,7 +333,7 @@ async function removeLayer(graphComponent) { setUIDisabled(true) const removeNodes = [] - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (node.tag == null || node.tag.layer > currentLayers) { removeNodes.push(node) } @@ -365,7 +365,7 @@ async function reduceTree(graphComponent, removeNodes) { const barycenterData = new PlaceNodesAtBarycenterStageData({ affectedNodes: removeNodes }) const subgraphData = new SubgraphLayoutData({ - subgraphNodes: node => !removeNodes.includes(node) + subgraphNodes: (node) => !removeNodes.includes(node) }) const barycenterStage = new PlaceNodesAtBarycenterStage() @@ -392,7 +392,7 @@ async function reduceTree(graphComponent, removeNodes) { timing: '500ms ease' }) - removeNodes.forEach(node => { + removeNodes.forEach((node) => { gmm.setAnimations(node, [nodeFadeOutAnimation]) gmm.setAnimations(graph.edgesAt(node).get(0), [edgeFadeOutAnimation]) }) @@ -405,7 +405,7 @@ async function reduceTree(graphComponent, removeNodes) { edgeFadeOutAnimation ) - removeNodes.forEach(node => { + removeNodes.forEach((node) => { graph.remove(node) }) } @@ -427,10 +427,10 @@ function shouldReduceEdgeLength(graph) { function cleanupAnimations(graphComponent) { const graph = graphComponent.graph const gmm = graphComponent.graphModelManager - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { gmm.setAnimations(node, []) }) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { gmm.setAnimations(edge, []) }) } diff --git a/demos/view/large-tree/LargeTreeDemo.ts b/demos/view/large-tree/LargeTreeDemo.ts index a93ab5fcb..67f2bbcf2 100644 --- a/demos/view/large-tree/LargeTreeDemo.ts +++ b/demos/view/large-tree/LargeTreeDemo.ts @@ -93,7 +93,7 @@ async function run(): Promise { * Initializes the WebGL2 node and edge styles */ function initializeStyleDefaults(): void { - ;['#242265', '#01baff', '#f26419', '#fdca40'].forEach(color => { + ;['#242265', '#01baff', '#f26419', '#fdca40'].forEach((color) => { nodeStyles.push(new WebGL2ShapeNodeStyle('round-rectangle', color, '#0000')) }) @@ -132,7 +132,7 @@ function updateLayersUI(graphComponent: GraphComponent): void { // disable/enable add layer button const leaves = graphComponent.graph.nodes.filter( - node => graphComponent.graph.outDegree(node) == 0 + (node) => graphComponent.graph.outDegree(node) == 0 ).size const childCount = Number(document.querySelector('#childCountInput')!.value) document.querySelector('#add-layer')!.disabled = @@ -214,8 +214,8 @@ async function addLayer(graphComponent: GraphComponent): Promise { const queue: INode[] = [] graph.nodes - .filter(node => node.tag.layer == graphInfo.maxLayer) - .forEach(node => { + .filter((node) => node.tag.layer == graphInfo.maxLayer) + .forEach((node) => { queue.push(node) }) @@ -300,7 +300,7 @@ async function runExtendLayout( const graph = graphComponent.graph const fixedNodeData = new FixNodeLayoutData({ - fixedNodes: graph.nodes.find(n => graph.inDegree(n) == 0)! + fixedNodes: graph.nodes.find((n) => graph.inDegree(n) == 0)! }) const layout = new FixNodeLayoutStage(coreLayout) @@ -318,7 +318,7 @@ async function removeLayer(graphComponent: GraphComponent): Promise { setUIDisabled(true) const removeNodes: INode[] = [] - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { if (node.tag == null || node.tag.layer > currentLayers) { removeNodes.push(node) } @@ -349,7 +349,7 @@ async function reduceTree(graphComponent: GraphComponent, removeNodes: INode[]): const barycenterData = new PlaceNodesAtBarycenterStageData({ affectedNodes: removeNodes }) const subgraphData = new SubgraphLayoutData({ - subgraphNodes: node => !removeNodes.includes(node) + subgraphNodes: (node) => !removeNodes.includes(node) }) const barycenterStage = new PlaceNodesAtBarycenterStage() @@ -376,7 +376,7 @@ async function reduceTree(graphComponent: GraphComponent, removeNodes: INode[]): timing: '500ms ease' }) - removeNodes.forEach(node => { + removeNodes.forEach((node) => { gmm.setAnimations(node, [nodeFadeOutAnimation]) gmm.setAnimations(graph.edgesAt(node).get(0), [edgeFadeOutAnimation]) }) @@ -389,7 +389,7 @@ async function reduceTree(graphComponent: GraphComponent, removeNodes: INode[]): edgeFadeOutAnimation ) - removeNodes.forEach(node => { + removeNodes.forEach((node) => { graph.remove(node) }) } @@ -409,10 +409,10 @@ function shouldReduceEdgeLength(graph: IGraph): boolean { function cleanupAnimations(graphComponent: GraphComponent): void { const graph = graphComponent.graph const gmm = graphComponent.graphModelManager as WebGL2GraphModelManager - graph.nodes.forEach(node => { + graph.nodes.forEach((node) => { gmm.setAnimations(node, []) }) - graph.edges.forEach(edge => { + graph.edges.forEach((edge) => { gmm.setAnimations(edge, []) }) } diff --git a/demos/view/large-tree/index.html b/demos/view/large-tree/index.html index 30f9a6b24..08deefb99 100644 --- a/demos/view/large-tree/index.html +++ b/demos/view/large-tree/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/list-node/ListNodeDemo.js b/demos/view/list-node/ListNodeDemo.js index 6d3fb866f..28382c469 100644 --- a/demos/view/list-node/ListNodeDemo.js +++ b/demos/view/list-node/ListNodeDemo.js @@ -289,7 +289,7 @@ function removeRow(graph, node, rowIndex) { const nodeInfo = node.tag const portForData = getPortForData(node, nodeInfo.rows[rowIndex]) - portForData.toArray().forEach(port => { + portForData.toArray().forEach((port) => { graph.remove(port) }) nodeInfo.rows.splice(rowIndex, 1) @@ -298,7 +298,7 @@ function removeRow(graph, node, rowIndex) { for (let i = rowIndex; i < nodeInfo.rows.length; i++) { const ri = nodeInfo.rows[i] const portForData = getPortForData(node, ri) - portForData.forEach(port => { + portForData.forEach((port) => { const incoming = port.tag.incoming graph.setPortLocationParameter(port, createPortLocationParameter(i, incoming, node.style)) // keep adjacent edges orthogonal @@ -392,7 +392,7 @@ function createSampleGraph(graph) { */ function registerContextMenu(graphComponent, geim) { const contextMenu = new ContextMenu(graphComponent) - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (geim.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } diff --git a/demos/view/list-node/ListNodeDemo.ts b/demos/view/list-node/ListNodeDemo.ts index 0d923d760..76d706546 100644 --- a/demos/view/list-node/ListNodeDemo.ts +++ b/demos/view/list-node/ListNodeDemo.ts @@ -293,7 +293,7 @@ function removeRow(graph: IGraph, node: INode, rowIndex: number): void { const nodeInfo = node.tag as NodeInfo const portForData = getPortForData(node, nodeInfo.rows[rowIndex]) - portForData.toArray().forEach(port => { + portForData.toArray().forEach((port) => { graph.remove(port) }) nodeInfo.rows.splice(rowIndex, 1) @@ -302,7 +302,7 @@ function removeRow(graph: IGraph, node: INode, rowIndex: number): void { for (let i = rowIndex; i < nodeInfo.rows.length; i++) { const ri = nodeInfo.rows[i] const portForData = getPortForData(node, ri) - portForData.forEach(port => { + portForData.forEach((port) => { const incoming = port.tag.incoming graph.setPortLocationParameter( port, @@ -395,7 +395,7 @@ function createSampleGraph(graph: IGraph): void { */ function registerContextMenu(graphComponent: GraphComponent, geim: GraphEditorInputMode): void { const contextMenu = new ContextMenu(graphComponent) - contextMenu.addOpeningEventListeners(graphComponent, location => { + contextMenu.addOpeningEventListeners(graphComponent, (location) => { if (geim.contextMenuInputMode.shouldOpenMenu(graphComponent.toWorldFromPage(location))) { contextMenu.show(location) } diff --git a/demos/view/list-node/ListNodeStyle.js b/demos/view/list-node/ListNodeStyle.js index 024a51737..4a2f71601 100644 --- a/demos/view/list-node/ListNodeStyle.js +++ b/demos/view/list-node/ListNodeStyle.js @@ -361,8 +361,8 @@ class ExistingAndFreePortCandidateProvider extends PortCandidateProviderBase { // Create the candidate for each port if (graph) { this.node.ports - .filter(port => port.tag.incoming === incoming) - .forEach(port => { + .filter((port) => port.tag.incoming === incoming) + .forEach((port) => { const portCandidate = new DefaultPortCandidate(port) portCandidate.validity = graph.degree(port) === 0 ? PortCandidateValidity.VALID : PortCandidateValidity.INVALID diff --git a/demos/view/list-node/ListNodeStyle.ts b/demos/view/list-node/ListNodeStyle.ts index 755b41a89..e1aef4fb8 100644 --- a/demos/view/list-node/ListNodeStyle.ts +++ b/demos/view/list-node/ListNodeStyle.ts @@ -335,8 +335,8 @@ class ExistingAndFreePortCandidateProvider extends PortCandidateProviderBase { // Create the candidate for each port if (graph) { this.node.ports - .filter(port => port.tag.incoming === incoming) - .forEach(port => { + .filter((port) => port.tag.incoming === incoming) + .forEach((port) => { const portCandidate = new DefaultPortCandidate(port) portCandidate.validity = graph.degree(port) === 0 ? PortCandidateValidity.VALID : PortCandidateValidity.INVALID diff --git a/demos/view/list-node/RowPositionHandler.js b/demos/view/list-node/RowPositionHandler.js index 8d6aa4502..d006e8794 100644 --- a/demos/view/list-node/RowPositionHandler.js +++ b/demos/view/list-node/RowPositionHandler.js @@ -89,7 +89,7 @@ export class RowPositionHandler extends BaseClass(IPositionHandler) { for (let i = 0; i < this.originalState.length; i++) { const portMoveInfo = this.originalState[i] const ports = getPortForData(this.node, portMoveInfo.info) - ports.forEach(port => { + ports.forEach((port) => { const portLocation = port.location portMoveInfo.handle.dragFinished(context, portMoveInfo.originalHandleLocation, portLocation) // Moving ports through their handle might result in port location parameters whose anchor @@ -144,7 +144,7 @@ export class RowPositionHandler extends BaseClass(IPositionHandler) { nodeInfo.rows[this.currentIndex] = otherInfo nodeInfo.rows[newIndex] = rowInfo - ports.forEach(port => { + ports.forEach((port) => { const newMoveInfo = this.portHandle.get(port) newMoveInfo.handle.handleMove( context, @@ -155,7 +155,7 @@ export class RowPositionHandler extends BaseClass(IPositionHandler) { ) ) }) - otherPorts.forEach(port => { + otherPorts.forEach((port) => { const otherMoveInfo = this.portHandle.get(port) otherMoveInfo.handle.handleMove( context, @@ -188,7 +188,7 @@ export class RowPositionHandler extends BaseClass(IPositionHandler) { // besides the one at the current row for (const p of nodeInfo.rows) { const ports = getPortForData(this.node, p) - ports.forEach(port => { + ports.forEach((port) => { const handle = port.lookup(IHandle.$class) const info = { info: p, @@ -230,7 +230,7 @@ export class RowPositionHandler extends BaseClass(IPositionHandler) { * @returns {!IEnumerable.} */ export function getPortForData(node, rowInfo) { - return node.ports.filter(p => p.tag.rowInfo === rowInfo) + return node.ports.filter((p) => p.tag.rowInfo === rowInfo) } /** diff --git a/demos/view/list-node/RowPositionHandler.ts b/demos/view/list-node/RowPositionHandler.ts index 2dc618d71..f7199e610 100644 --- a/demos/view/list-node/RowPositionHandler.ts +++ b/demos/view/list-node/RowPositionHandler.ts @@ -52,7 +52,10 @@ export class RowPositionHandler extends BaseClass(IPositionHandler) { private originalState: RowMoveInfo[] = [] private portHandle: Map = new Map() - constructor(private node: INode, private index: number) { + constructor( + private node: INode, + private index: number + ) { super() } @@ -81,7 +84,7 @@ export class RowPositionHandler extends BaseClass(IPositionHandler) { for (let i = 0; i < this.originalState.length; i++) { const portMoveInfo = this.originalState[i] const ports = getPortForData(this.node, portMoveInfo.info) - ports.forEach(port => { + ports.forEach((port) => { const portLocation = port.location portMoveInfo.handle.dragFinished(context, portMoveInfo.originalHandleLocation, portLocation) // Moving ports through their handle might result in port location parameters whose anchor @@ -133,7 +136,7 @@ export class RowPositionHandler extends BaseClass(IPositionHandler) { nodeInfo.rows[this.currentIndex] = otherInfo nodeInfo.rows[newIndex] = rowInfo - ports.forEach(port => { + ports.forEach((port) => { const newMoveInfo = this.portHandle.get(port)! newMoveInfo.handle.handleMove( context, @@ -144,7 +147,7 @@ export class RowPositionHandler extends BaseClass(IPositionHandler) { ) ) }) - otherPorts.forEach(port => { + otherPorts.forEach((port) => { const otherMoveInfo = this.portHandle.get(port)! otherMoveInfo.handle.handleMove( context, @@ -176,7 +179,7 @@ export class RowPositionHandler extends BaseClass(IPositionHandler) { // besides the one at the current row for (const p of nodeInfo.rows) { const ports = getPortForData(this.node, p) - ports.forEach(port => { + ports.forEach((port) => { const handle = port.lookup(IHandle.$class)! const info = { info: p, @@ -214,7 +217,7 @@ export class RowPositionHandler extends BaseClass(IPositionHandler) { * @param rowInfo the row information that identifies the row whose port is returned. */ export function getPortForData(node: INode, rowInfo: RowInfo): IEnumerable { - return node.ports.filter(p => p.tag.rowInfo === rowInfo)! + return node.ports.filter((p) => p.tag.rowInfo === rowInfo)! } /** diff --git a/demos/view/list-node/index.html b/demos/view/list-node/index.html index e5f358a31..38fad6fda 100644 --- a/demos/view/list-node/index.html +++ b/demos/view/list-node/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/overviewstyles/OverviewCanvasVisualCreator.js b/demos/view/overviewstyles/OverviewCanvasVisualCreator.js index 38caa3529..61504b2b0 100644 --- a/demos/view/overviewstyles/OverviewCanvasVisualCreator.js +++ b/demos/view/overviewstyles/OverviewCanvasVisualCreator.js @@ -78,7 +78,7 @@ export default class OrgchartOverviewCanvasVisualCreator extends GraphOverviewCa paintEdge(renderContext, ctx, edge) { ctx.beginPath() ctx.moveTo(edge.sourcePort.location.x, edge.sourcePort.location.y) - edge.bends.forEach(bend => { + edge.bends.forEach((bend) => { ctx.lineTo(bend.location.x, bend.location.y) }) ctx.lineTo(edge.targetPort.location.x, edge.targetPort.location.y) diff --git a/demos/view/overviewstyles/OverviewCanvasVisualCreator.ts b/demos/view/overviewstyles/OverviewCanvasVisualCreator.ts index 8699ef4a0..3198ffe5c 100644 --- a/demos/view/overviewstyles/OverviewCanvasVisualCreator.ts +++ b/demos/view/overviewstyles/OverviewCanvasVisualCreator.ts @@ -78,7 +78,7 @@ export default class OrgchartOverviewCanvasVisualCreator extends GraphOverviewCa paintEdge(renderContext: IRenderContext, ctx: CanvasRenderingContext2D, edge: IEdge): void { ctx.beginPath() ctx.moveTo(edge.sourcePort!.location.x, edge.sourcePort!.location.y) - edge.bends.forEach(bend => { + edge.bends.forEach((bend) => { ctx.lineTo(bend.location.x, bend.location.y) }) ctx.lineTo(edge.targetPort!.location.x, edge.targetPort!.location.y) diff --git a/demos/view/overviewstyles/OverviewStylesDemo.js b/demos/view/overviewstyles/OverviewStylesDemo.js index 6f6bece88..38632db5d 100644 --- a/demos/view/overviewstyles/OverviewStylesDemo.js +++ b/demos/view/overviewstyles/OverviewStylesDemo.js @@ -166,13 +166,13 @@ function buildGraph(graph, graphData) { graphBuilder.createNodesSource({ data: graphData.nodeList, - id: item => item.id - }).nodeCreator.tagProvider = item => item.tag + id: (item) => item.id + }).nodeCreator.tagProvider = (item) => item.tag graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -266,7 +266,7 @@ function getWebGL2GraphOverviewVisualCreator() { * toolbar buttons, during the creation of this application. */ function initializeUI() { - addNavigationButtons(overViewStyleBox).addEventListener('change', evt => { + addNavigationButtons(overViewStyleBox).addEventListener('change', (evt) => { const selectedValue = evt.target.value overviewStyling(selectedValue) }) @@ -305,7 +305,7 @@ function probeWebGL2Support() { */ function initializeConverters() { StringTemplateNodeStyle.CONVERTERS.orgChartConverters = { - overviewConverter: value => { + overviewConverter: (value) => { if (typeof value === 'string' && value.length > 0) { return value.replace(/^(.)(\S*)(.*)/, '$1.$3') } @@ -336,13 +336,13 @@ function initializeConverters() { return value }, // converter function that returns a color according to the employee's status - colorConverter: value => + colorConverter: (value) => ({ busy: '#AB2346', present: '#76B041', travel: '#A367DC', unavailable: '#C1C1C1' - }[value] || '#C1C1C1') + })[value] || '#C1C1C1' } } diff --git a/demos/view/overviewstyles/OverviewStylesDemo.ts b/demos/view/overviewstyles/OverviewStylesDemo.ts index 0b5fbf2d8..588cb1a29 100644 --- a/demos/view/overviewstyles/OverviewStylesDemo.ts +++ b/demos/view/overviewstyles/OverviewStylesDemo.ts @@ -161,13 +161,13 @@ function buildGraph(graph: IGraph, graphData: JSONGraph): void { graphBuilder.createNodesSource({ data: graphData.nodeList, - id: item => item.id - }).nodeCreator.tagProvider = item => item.tag + id: (item) => item.id + }).nodeCreator.tagProvider = (item) => item.tag graphBuilder.createEdgesSource({ data: graphData.edgeList, - sourceId: item => item.source, - targetId: item => item.target + sourceId: (item) => item.source, + targetId: (item) => item.target }) graphBuilder.buildGraph() @@ -261,7 +261,7 @@ function getWebGL2GraphOverviewVisualCreator(): WebGL2GraphOverviewVisualCreator * toolbar buttons, during the creation of this application. */ function initializeUI(): void { - addNavigationButtons(overViewStyleBox).addEventListener('change', evt => { + addNavigationButtons(overViewStyleBox).addEventListener('change', (evt) => { const selectedValue = (evt.target as HTMLSelectElement).value overviewStyling(selectedValue as string) }) @@ -332,14 +332,14 @@ function initializeConverters(): void { }, // converter function that returns a color according to the employee's status colorConverter: (value: string): string => - (( - { + ( + ({ busy: '#AB2346', present: '#76B041', travel: '#A367DC', unavailable: '#C1C1C1' - } as Record - )[value] || '#C1C1C1') + }) as Record + )[value] || '#C1C1C1' } } diff --git a/demos/view/overviewstyles/index.html b/demos/view/overviewstyles/index.html index 05bbf27a2..0a9c3f298 100644 --- a/demos/view/overviewstyles/index.html +++ b/demos/view/overviewstyles/index.html @@ -1,4 +1,4 @@ - + diff --git a/demos/view/pdfexport/PdfExportDemo.js b/demos/view/pdfexport/PdfExportDemo.js index 649f641a1..abb3637d2 100644 --- a/demos/view/pdfexport/PdfExportDemo.js +++ b/demos/view/pdfexport/PdfExportDemo.js @@ -41,6 +41,7 @@ import { exportPdfServerSide, NODE_SERVER_URL } from './pdf-export-server-side.j import { exportPdfClientSide } from './pdf-export-client-side.js' import { retainAspectRatio } from './aspect-ratio.js' import { downloadFile } from 'demo-utils/file-support' +import { loadExternalFonts } from './load-external-fonts.js' /** * @returns {!Promise} @@ -67,7 +68,7 @@ async function run() { let pdf = '' - initializeOptionPanel(async options => { + initializeOptionPanel(async (options) => { const rect = options.useExportRectangle ? exportRect.toRect() : undefined if (options.serverExport) { @@ -78,7 +79,8 @@ async function run() { options.scale, options.margin, options.paperSize, - rect + rect, + await loadExternalFonts() ) pdf = pdfData showExportDialog(previewElement) diff --git a/demos/view/pdfexport/PdfExportDemo.ts b/demos/view/pdfexport/PdfExportDemo.ts index 8c68c7817..738c0c366 100644 --- a/demos/view/pdfexport/PdfExportDemo.ts +++ b/demos/view/pdfexport/PdfExportDemo.ts @@ -41,6 +41,7 @@ import { exportPdfServerSide, NODE_SERVER_URL } from './pdf-export-server-side' import { exportPdfClientSide } from './pdf-export-client-side' import { retainAspectRatio } from './aspect-ratio' import { downloadFile } from 'demo-utils/file-support' +import { loadExternalFonts } from './load-external-fonts' async function run(): Promise { License.value = await fetchLicense() @@ -64,7 +65,7 @@ async function run(): Promise { let pdf = '' - initializeOptionPanel(async options => { + initializeOptionPanel(async (options) => { const rect = options.useExportRectangle ? exportRect.toRect() : undefined if (options.serverExport) { @@ -75,7 +76,8 @@ async function run(): Promise { options.scale, options.margin, options.paperSize, - rect + rect, + await loadExternalFonts() ) pdf = pdfData showExportDialog(previewElement) diff --git a/demos/view/pdfexport/aspect-ratio.js b/demos/view/pdfexport/aspect-ratio.js index 44d685c07..935aa2293 100644 --- a/demos/view/pdfexport/aspect-ratio.js +++ b/demos/view/pdfexport/aspect-ratio.js @@ -41,8 +41,8 @@ import { */ export function retainAspectRatio(graph) { graph.decorator.nodeDecorator.reshapeHandleProviderDecorator.setFactory( - node => node.style instanceof ImageNodeStyle, - node => { + (node) => node.style instanceof ImageNodeStyle, + (node) => { const keepAspectRatio = new NodeReshapeHandleProvider( node, node.lookup(IReshapeHandler.$class), diff --git a/demos/view/pdfexport/aspect-ratio.ts b/demos/view/pdfexport/aspect-ratio.ts index 38152a215..f0f1d6190 100644 --- a/demos/view/pdfexport/aspect-ratio.ts +++ b/demos/view/pdfexport/aspect-ratio.ts @@ -41,8 +41,8 @@ import { */ export function retainAspectRatio(graph: IGraph): void { graph.decorator.nodeDecorator.reshapeHandleProviderDecorator.setFactory( - node => node.style instanceof ImageNodeStyle, - node => { + (node) => node.style instanceof ImageNodeStyle, + (node) => { const keepAspectRatio = new NodeReshapeHandleProvider( node, node.lookup(IReshapeHandler.$class)!, diff --git a/demos/view/pdfexport/export-dialog/export-dialog.js b/demos/view/pdfexport/export-dialog/export-dialog.js index f59a4c49d..0b2a746a7 100644 --- a/demos/view/pdfexport/export-dialog/export-dialog.js +++ b/demos/view/pdfexport/export-dialog/export-dialog.js @@ -57,7 +57,7 @@ export function initializeExportDialog(heading, saveCallback) { saveCallback(previewElement) }) - closeButton.addEventListener('click', _ => { + closeButton.addEventListener('click', (_) => { // Hide the popup dialog.style.display = 'none' // Remove the exported SVG element from the popup since it is no longer needed diff --git a/demos/view/pdfexport/export-dialog/export-dialog.ts b/demos/view/pdfexport/export-dialog/export-dialog.ts index 106ceffe4..46d0a9220 100644 --- a/demos/view/pdfexport/export-dialog/export-dialog.ts +++ b/demos/view/pdfexport/export-dialog/export-dialog.ts @@ -60,7 +60,7 @@ export function initializeExportDialog( saveCallback(previewElement) }) - closeButton.addEventListener('click', _ => { + closeButton.addEventListener('click', (_) => { // Hide the popup dialog.style.display = 'none' // Remove the exported SVG element from the popup since it is no longer needed diff --git a/demos/view/pdfexport/index.html b/demos/view/pdfexport/index.html index 6ba656068..43cfd5589 100644 --- a/demos/view/pdfexport/index.html +++ b/demos/view/pdfexport/index.html @@ -1,4 +1,4 @@ - + @@ -40,8 +40,23 @@ -