UNPKG

@yworks/react-yfiles-company-ownership

Version:

yFiles React Company Ownership Component - A powerful and versatile React component based on the yFiles library, allows you to seamlessly incorporate dynamic and interactive company ownership diagrams into your applications.

1,584 lines (1,564 loc) 68.3 kB
import { componentBackgroundColor, createLayout, defaultExportMargins, defaultGraphFitInsets, defaultLayoutOptions, defaultOwnershipEdgeStyle, defaultPortAdjustmentPolicy, defaultRelationEdgeStyle, hoverHighlightColor, initializeWebWorker, maximumZoom, minimumZoom, neighborhoodHighlightColor, selectionHighlightColor } from "./chunk-7BEEWV7H.js"; // src/CompanyOwnershipProvider.tsx import { createContext, useContext, useMemo } from "react"; import { useGraphComponent, withGraphComponentProvider } from "@yworks/react-yfiles-core"; import { Graph as Graph2, ViewportLimitingPolicy } from "@yfiles/yfiles"; // src/CompanyOwnershipModel.ts function getNode(item, graph) { return item ? getNodeFromId(item.id, graph) : null; } function getNodeFromId(id, graph) { return graph.nodes.find((node) => node.tag.id === id); } function getFilteredGraphWrapper(graphComponent) { return graphComponent.graph; } // src/CompanyOwnershipModelImpl.ts import { Command, Insets, MutableRectangle } from "@yfiles/yfiles"; import { exportImageAndSave, exportSvgAndSave, printDiagram } from "@yworks/react-yfiles-core"; // src/core/NeighborhoodIndicatorManager.ts import { AdjacencyTypes, Arrow, EdgeStyleIndicatorRenderer, GraphComponent, HighlightIndicatorManager, IEdge, IEdgeStyle, ILookupDecorator, INode, INodeStyle, LookupDecorator, Neighborhood, NodeStyleIndicatorRenderer, PolylineEdgeStyle, ShapeNodeStyle, TraversalDirection } from "@yfiles/yfiles"; var NeighborhoodIndicatorManager = class extends HighlightIndicatorManager { // remember the neighborhood's master items to restore the highlighting after collapse/expand neighborhoodCollection = /* @__PURE__ */ new Set(); nodeStyle = INodeStyle.VOID_NODE_STYLE; edgeStyle = IEdgeStyle.VOID_EDGE_STYLE; constructor(graphComponent, highlightOptions) { super(); this.canvasComponent = graphComponent; this.update(highlightOptions); } update(highlightOptions) { const neighborhoodColor = highlightOptions?.neighborhoodHighlightColor ?? neighborhoodHighlightColor; const neighborhoodClass = highlightOptions?.neighborhoodHighlightCssClass ?? ""; this.nodeStyle = new ShapeNodeStyle({ shape: "round-rectangle", stroke: `3px ${neighborhoodColor}`, fill: "none", cssClass: neighborhoodClass }); this.edgeStyle = new PolylineEdgeStyle({ targetArrow: new Arrow({ type: "triangle", stroke: `2px ${neighborhoodColor}`, fill: neighborhoodColor }), stroke: `3px ${neighborhoodColor}`, cssClass: neighborhoodClass }); } getRenderer(item) { return item instanceof INode ? new NodeStyleIndicatorRenderer({ nodeStyle: this.nodeStyle, // the padding from the actual node to its highlight visualization margins: 2 }) : item instanceof IEdge ? new EdgeStyleIndicatorRenderer({ edgeStyle: this.edgeStyle, zoomPolicy: "world-coordinates" }) : super.getRenderer(item); } highlightNeighborhood(viewStartNode) { super.items?.clear(); const graph = this.canvasComponent.graph; this.addHighlight(viewStartNode); const neighborhood = new Neighborhood({ startNodes: [viewStartNode], traversalDirection: TraversalDirection.BOTH }); const result = neighborhood.run(graph); for (const node of result.neighbors) { const highlightItem2 = node; if (highlightItem2) { this.addHighlight(highlightItem2); } for (const adjacentOutEdge of graph.edgesAt(node, AdjacencyTypes.OUTGOING)) { if (result.neighbors.includes(adjacentOutEdge.targetNode) || adjacentOutEdge.targetNode === viewStartNode) { const viewItem = adjacentOutEdge; if (viewItem) { this.addHighlight(viewItem); } } } for (const adjacentInEdge of graph.edgesAt(node, AdjacencyTypes.INCOMING)) { if (result.neighbors.includes(adjacentInEdge.sourceNode) || adjacentInEdge.sourceNode === viewStartNode) { const viewItem = adjacentInEdge; if (viewItem) { this.addHighlight(viewItem); } } } } } addHighlight(viewItem) { super.items?.add(viewItem); this.neighborhoodCollection.add(viewItem); } clearHighlights() { this.neighborhoodCollection.clear(); super.items?.clear(); } deactivateHighlights() { super.items?.clear(); } activateHighlights() { let visibleItem = null; for (const item of this.neighborhoodCollection) { if (item instanceof INode) { visibleItem = item; } else if (item instanceof IEdge) { visibleItem = item; } if (visibleItem) { super.items?.add(visibleItem); } } } }; function registerNeighborHoodIndicatorManager(graphComponent, highlightOptions) { const neighborhoodIndicatorManager = new NeighborhoodIndicatorManager( graphComponent, highlightOptions ); const lookupDecorator = graphComponent.lookup(ILookupDecorator); new LookupDecorator(GraphComponent, NeighborhoodIndicatorManager, lookupDecorator).addConstant( neighborhoodIndicatorManager ); } function getNeighborhoodIndicatorManager(graphComponent) { const manager = graphComponent.lookup(NeighborhoodIndicatorManager); if (!manager) { throw new Error("No NeighborhoodIndicatorManager registered on the GraphComponent"); } return manager; } // src/CompanyOwnershipModelImpl.ts function createCompanyOwnershipModel(graphComponent, collapsibleTree, layoutSupport) { let onRenderedCallback = null; const setRenderedCallback = (cb) => { onRenderedCallback = cb; }; const onRendered = () => { onRenderedCallback?.(); onRenderedCallback = null; }; function showGenealogy(item) { const node = getNode(item, graphComponent.graph); if (node) { clearConnectedItemsHighlight(); collapsibleTree.executeShowGenealogy(node); } } function isConnection(item) { return "sourceId" in item || "connections" in item; } function applyLayout(incremental, incrementalItems, fixedItem = null, fitViewport = false) { if (!layoutSupport) { return Promise.resolve(); } const incrementalNodes = []; incrementalItems?.forEach((item) => { const graph = graphComponent.graph; const node = getNode(item, graph); if (node) { incrementalNodes.push(node); } }); const fixedNode = getNode(fixedItem, graphComponent.graph); return layoutSupport.runLayout(incremental ?? false, incrementalNodes, fixedNode, fitViewport); } function highlightConnectedItems(item) { const startNode = getNode(item, graphComponent.graph); if (!startNode) { return; } const neighborhoodIndicatorManager = getNeighborhoodIndicatorManager(graphComponent); neighborhoodIndicatorManager.highlightNeighborhood(startNode); } function canClearConnectedItemsHighlight() { const neighborhoodIndicatorManager = getNeighborhoodIndicatorManager(graphComponent); return neighborhoodIndicatorManager.items ? neighborhoodIndicatorManager.items.size > 0 : false; } function clearConnectedItemsHighlight() { const neighborhoodIndicatorManager = getNeighborhoodIndicatorManager(graphComponent); neighborhoodIndicatorManager.clearHighlights(); } async function showAll() { const neighborhoodHighlightManager = getNeighborhoodIndicatorManager(graphComponent); neighborhoodHighlightManager.clearHighlights(); await collapsibleTree.executeShowAll(); } function canShowAll() { return collapsibleTree.canExecuteShowAll(); } function zoomIn() { graphComponent.executeCommand(Command.INCREASE_ZOOM, null); } function zoomOut() { graphComponent.executeCommand(Command.DECREASE_ZOOM, null); } function fitContent(insets = 0) { graphComponent.fitGraphBounds(new Insets(insets), true); } function zoomToOriginal() { graphComponent.executeCommand(Command.ZOOM, 1); } async function exportToSvg(exportSettings) { const settings = Object.assign( { zoom: graphComponent.zoom, scale: graphComponent.zoom, margins: defaultExportMargins, inlineImages: true, background: componentBackgroundColor }, exportSettings ); const callback = _hasReactItems ? setRenderedCallback : (resolve) => { resolve(); }; await exportSvgAndSave(settings, graphComponent, callback); } async function exportToPng(exportSettings) { const settings = Object.assign( { zoom: graphComponent.zoom, scale: graphComponent.zoom, margins: defaultExportMargins, inlineImages: true, background: componentBackgroundColor }, exportSettings ); const callback = _hasReactItems ? setRenderedCallback : (resolve) => { resolve(); }; await exportImageAndSave(settings, graphComponent, callback); } async function print(printSettings) { const settings = Object.assign( { zoom: graphComponent.zoom, scale: 1, margins: defaultExportMargins }, printSettings ); await printDiagram(settings, graphComponent); } function refresh() { graphComponent.invalidate(); } function zoomTo(items) { if (items.length === 0) { return; } const graph = graphComponent.graph; const targetBounds = new MutableRectangle(); items.forEach((item) => { if (isConnection(item)) { const source = getNodeFromId(item.sourceId, graph); const target = getNodeFromId(item.targetId, graph); targetBounds.add(source.layout); targetBounds.add(target.layout); } else { const node = getNode(item, graph); targetBounds.add(node.layout); } }); const enlargedTargetBounds = targetBounds.toRect().getEnlarged(200); const gcSize = graphComponent.size; const newZoom = Math.min( gcSize.width / enlargedTargetBounds.width, gcSize.height / enlargedTargetBounds.height ); graphComponent.zoomToAnimated( Math.max(newZoom, graphComponent.zoom), enlargedTargetBounds.center ); } function addGraphUpdatedListener(listener) { collapsibleTree.addGraphUpdatedListener(listener); } function removeGraphUpdatedListener(listener) { collapsibleTree.removeGraphUpdatedListener(listener); } function getVisibleItems() { return collapsibleTree.graphComponent.graph.nodes.map((item) => item.tag).toArray(); } async function showPredecessors(item) { const node = getNode(item, graphComponent.graph); if (node) { const neighborhoodHighlightManager = getNeighborhoodIndicatorManager(graphComponent); neighborhoodHighlightManager.clearHighlights(); await collapsibleTree.executeShowParent(node); } } function canShowPredecessors(item) { const node = getNode(item, graphComponent.graph); if (node) { return collapsibleTree.canExecuteShowParent(node); } return false; } async function hidePredecessors(item) { const node = getNode(item, graphComponent.graph); if (node) { const neighborhoodHighlightManager = getNeighborhoodIndicatorManager(graphComponent); neighborhoodHighlightManager.clearHighlights(); await collapsibleTree.executeHideParent(node); } } function canHidePredecessors(item) { const node = getNode(item, graphComponent.graph); if (node) { return collapsibleTree.canExecuteHideParent(node); } return false; } async function showSuccessors(item) { const node = getNode(item, graphComponent.graph); if (node) { const neighborhoodHighlightManager = getNeighborhoodIndicatorManager(graphComponent); neighborhoodHighlightManager.clearHighlights(); await collapsibleTree.executeShowChildren(node); } } function canShowSuccessors(item) { const node = getNode(item, graphComponent.graph); if (node) { return collapsibleTree.canExecuteShowChildren(node); } return false; } async function hideSuccessors(item) { const node = getNode(item, graphComponent.graph); if (node) { const neighborhoodHighlightManager = getNeighborhoodIndicatorManager(graphComponent); neighborhoodHighlightManager.clearHighlights(); await collapsibleTree.executeHideChildren(node); } } function canHideSuccessors(item) { const node = getNode(item, graphComponent.graph); if (node) { return collapsibleTree.canExecuteHideChildren(node); } return false; } let _hasReactItems = false; return { graphComponent, layoutSupport, applyLayout, canClearConnectedItemsHighlight, clearConnectedItemsHighlight, highlightConnectedItems, showGenealogy, canShowAll, showAll, exportToPng, exportToSvg, print, isConnection, fitContent, zoomIn, zoomOut, zoomTo, zoomToOriginal, getSearchHits: () => [], // will be replaced during initialization onRendered, refresh, addGraphUpdatedListener, removeGraphUpdatedListener, getVisibleItems, showPredecessors, canShowPredecessors, hidePredecessors, canHidePredecessors, showSuccessors, canShowSuccessors, hideSuccessors, canHideSuccessors, get hasReactItems() { return _hasReactItems; }, set hasReactItems(value) { _hasReactItems = value; } }; } // src/core/LayoutSupport.ts import { HierarchicalLayoutData, INode as INode3, LayoutAnchoringPolicy, LayoutAnchoringStageData, LayoutExecutor, LayoutExecutorAsync } from "@yfiles/yfiles"; import { registerWebWorker } from "@yworks/react-yfiles-core"; var LayoutSupport = class { constructor(graphComponent) { this.graphComponent = graphComponent; } workerPromise = null; set layoutWorker(worker) { if (worker) { this.workerPromise = registerWebWorker(worker); } else { this.workerPromise = null; } } executorAsync = null; executor = null; layoutOptions = defaultLayoutOptions; setLayoutRunning; createLayoutData(incremental, incrementalNodes, fixedNode = null) { let layoutData = null; if (incremental) { layoutData = new HierarchicalLayoutData({ incrementalNodes: (node) => node instanceof INode3 && incrementalNodes.includes(node) }); } if (fixedNode) { const fixNodeLayoutData = new LayoutAnchoringStageData(); fixNodeLayoutData.nodeAnchoringPolicies.mapperFunction = (key) => key === fixedNode ? LayoutAnchoringPolicy.CENTER : LayoutAnchoringPolicy.NONE; if (!layoutData) { layoutData = new HierarchicalLayoutData(); } layoutData = layoutData.combineWith(fixNodeLayoutData); } return layoutData; } async runLayout(incremental = false, incrementalNodes = [], fixedNode = null, fitViewport = false) { this.setLayoutRunning?.(true); const graph = this.graphComponent.graph; if (incrementalNodes.length > 0) { incrementalNodes.map((node) => graph.edgesAt(node).toArray()).flat().forEach((edge) => { edge.bends.toArray().forEach((bend) => graph.remove(bend)); }); } const executor = this.workerPromise !== null ? await this.createLayoutExecutorAsync( incremental, incrementalNodes, fixedNode, fitViewport ) : await this.createLayoutExecutor(incremental, incrementalNodes, fixedNode, fitViewport); try { await executor.start(); } catch (e) { if (e.name === "AlgorithmAbortedError") { console.error("Layout calculation was aborted because maximum duration time was exceeded."); } else { console.error("Something went wrong during the layout calculation"); console.error(e); } } finally { this.setLayoutRunning?.(false); } } /** * When a layout animation is already running, it might have started * with now obsolete node sizes - stop the running animation and restore * the latest measured node sizes. */ async maybeCancel() { const syncRunning = this.executor && this.executor.running; const asyncRunning = this.executorAsync && this.executorAsync.running; if (syncRunning || asyncRunning) { const layouts = /* @__PURE__ */ new Map(); for (const node of this.graphComponent.graph.nodes) { layouts.set(node, node.layout.toRect()); } await this.executor?.stop(); await this.executorAsync?.cancel(); for (const node of this.graphComponent.graph.nodes) { if (layouts.has(node)) { this.graphComponent.graph.setNodeLayout(node, layouts.get(node)); } } for (const edge of this.graphComponent.graph.edges) { this.graphComponent.graph.clearBends(edge); } } } async createLayoutExecutor(incremental, incrementalNodes, fixedNode = null, fitViewport) { await this.maybeCancel(); this.executor = new LayoutExecutor({ graphComponent: this.graphComponent, layout: createLayout(incremental, this.layoutOptions), layoutData: this.createLayoutData(incremental, incrementalNodes, fixedNode), animationDuration: "300ms", animateViewport: fitViewport, updateContentBounds: true, targetBoundsPadding: defaultGraphFitInsets, portAdjustmentPolicies: defaultPortAdjustmentPolicy }); return Promise.resolve(this.executor); } async createLayoutExecutorAsync(incremental, incrementalNodes, fixedNode = null, fitViewport) { await this.maybeCancel(); const worker = await this.workerPromise; const webWorkerMessageHandler = (data) => { const thisRequest = data.token; data.incremental = incremental; data.layoutOptions = this.layoutOptions; return new Promise((resolve, reject) => { worker.onmessage = (e) => { if (e.data && thisRequest === e.data.token) { if (e.data.name === "AlgorithmAbortedError") { reject(e.data); } else { resolve(e.data); } } }; worker.postMessage(data); }); }; this.executorAsync = new LayoutExecutorAsync({ messageHandler: webWorkerMessageHandler, graphComponent: this.graphComponent, layoutData: this.createLayoutData(incremental, incrementalNodes, fixedNode), animationDuration: "300ms", animateViewport: fitViewport, updateContentBounds: true, targetBoundsPadding: defaultGraphFitInsets, portAdjustmentPolicies: defaultPortAdjustmentPolicy }); return this.executorAsync; } }; // src/core/CollapsibleTree.ts import { Graph, delegate, FilteredGraphWrapper, Neighborhood as Neighborhood2, PlaceNodesAtBarycenterStage, PlaceNodesAtBarycenterStageData, TraversalDirection as TraversalDirection2 } from "@yfiles/yfiles"; var CollapsibleTree = class _CollapsibleTree { constructor(_graphComponent, completeGraph = new Graph(), layoutSupport) { this._graphComponent = _graphComponent; this.completeGraph = completeGraph; this.layoutSupport = layoutSupport; const nodeFilter = (node) => !this.itemsHiddenByCollapse.has(node) && !this.itemsHiddenByGenealogy.has(node); this.filteredGraph = new FilteredGraphWrapper(completeGraph, nodeFilter); } itemsHiddenByCollapse = /* @__PURE__ */ new Set(); itemsHiddenByGenealogy = /* @__PURE__ */ new Set(); filteredGraph; doingLayout = false; graphUpdatedListener = null; collapsedStateUpdatedListener = null; get graphComponent() { return this._graphComponent; } /** * Adds an event listener to the graphUpdated event that is fired after the filtered graph * has changed and the layout was updated. */ addGraphUpdatedListener(listener) { this.graphUpdatedListener = delegate.combine(this.graphUpdatedListener, listener); } removeGraphUpdatedListener(listener) { this.graphUpdatedListener = delegate.remove(this.graphUpdatedListener, listener); } /** * Adds an event listener to the collapsedStateUpdated event that is fired when the collapsed * state of a port has changed. */ addCollapsedStateUpdatedListener(listener) { this.collapsedStateUpdatedListener = delegate.combine( this.collapsedStateUpdatedListener, listener ); } removeCollapsedStateUpdatedListener(listener) { this.collapsedStateUpdatedListener = delegate.remove( this.collapsedStateUpdatedListener, listener ); } /** * Hides the children of the given node and updates the layout. */ async executeHideChildren(item) { if (!this.canExecuteHideChildren(item)) { return Promise.resolve(); } const descendants = _CollapsibleTree.collectDescendants(this.completeGraph, item); for (const node of descendants) { this.updateCollapsedState(node, true); } this.updateCollapsedState(item, true); const allSiblingNodes = this.completeGraph.outEdgesAt(item).map((outEdge) => outEdge.targetNode).flatMap((targetNode) => this.completeGraph.inEdgesAt(targetNode)).map((edge) => edge.sourceNode).filter((sourceNode) => sourceNode !== item).distinct(); allSiblingNodes.forEach((node) => { if (this.filteredGraph.outEdgesAt(node).some((edge) => descendants.has(edge.targetNode))) { this.updateCollapsedState(node, true); } }); this.removeEmptyGroups(descendants); this.filteredGraph.nodePredicateChanged(); await this.refreshLayout(item, descendants, true); this.addToHiddenNodes(descendants); this.filteredGraph.nodePredicateChanged(); this.onGraphUpdated(); } /** * @returns Whether the children of the given node can be hidden. */ canExecuteHideChildren(item) { return !this.doingLayout && this.filteredGraph.outDegree(item) > 0; } /** * Shows the children of the given node and updates the layout. */ async executeShowChildren(item) { if (!this.canExecuteShowChildren(item)) { return Promise.resolve(); } const descendants = _CollapsibleTree.collectDescendants(this.completeGraph, item); const incrementalNodes = new Set( Array.from(descendants).filter((node) => !this.filteredGraph.contains(node)) ); this.showChildren(item); this.filteredGraph.nodePredicateChanged(); await this.refreshLayout(item, incrementalNodes, false); this.updateCollapsedState(item, false); const allSiblingNodes = this.completeGraph.outEdgesAt(item).map((outEdge) => outEdge.targetNode).flatMap((targetNode) => this.completeGraph.inEdgesAt(targetNode)).filter((edge) => edge.sourceNode !== item).map((edge) => edge.sourceNode).distinct(); allSiblingNodes.forEach((node) => { if (this.completeGraph.outEdgesAt(node).every((edge) => descendants.has(edge.targetNode))) { this.updateCollapsedState(node, false); } }); this.onGraphUpdated(); } showChildren(node) { for (const childEdge of this.completeGraph.outEdgesAt(node)) { const child = childEdge.targetNode; this.itemsHiddenByCollapse.delete(child); _CollapsibleTree.restoreGroup(this.completeGraph, this.itemsHiddenByCollapse, child); this.onCollapsedStateUpdated(childEdge.sourcePort, false); } } /** * @returns Whether the children of the given node can be shown. */ canExecuteShowChildren(item) { return !this.doingLayout && this.filteredGraph.outDegree(item) !== this.completeGraph.outDegree(item); } /** * Shows the parent of the given node and updates the layout. * * In contrast to {@link executeHideParent}, it only shows the * direct parent and not any of its children. */ async executeShowParent(node) { if (this.doingLayout) { return Promise.resolve(); } const incrementalNodes = /* @__PURE__ */ new Set(); this.showParents(node, incrementalNodes); this.filteredGraph.nodePredicateChanged(); await this.refreshLayout(node, incrementalNodes, false); this.onGraphUpdated(); } showParents(node, incrementalNodes) { for (const parentEdge of this.completeGraph.inEdgesAt(node)) { const parent = parentEdge.sourceNode; this.itemsHiddenByCollapse.delete(parent); _CollapsibleTree.restoreGroup(this.completeGraph, this.itemsHiddenByCollapse, parent); incrementalNodes.add(parent); } } /** * @returns Whether the parent of the given node can be shown. */ canExecuteShowParent(node) { return !this.doingLayout && this.filteredGraph.inDegree(node) === 0 && this.completeGraph.inDegree(node) > 0; } /** * Hides the parent of the given node and updates the layout. * * In contrast to {@link executeShowParent}, this method also hides all ancestors * and their descendants and other isolated trees leaving only the node and its descendants * in the graph. */ async executeHideParent(node) { if (this.doingLayout) { return Promise.resolve(); } const nodes = _CollapsibleTree.collectAllNodesExceptSubtree(this.completeGraph, node); this.removeEmptyGroups(nodes); this.filteredGraph.nodePredicateChanged(); await this.refreshLayout(node, nodes, true); this.addToHiddenNodes(nodes); this.filteredGraph.nodePredicateChanged(); this.onGraphUpdated(); } /** * @returns Whether the parent of the given node can be hidden. */ canExecuteHideParent(node) { return !this.doingLayout && this.filteredGraph.inDegree(node) > 0; } /** * Shows all nodes and updates the layout. */ async executeShowAll() { if (this.doingLayout) { return Promise.resolve(); } const incrementalNodes = /* @__PURE__ */ new Set([ ...this.itemsHiddenByCollapse, ...this.itemsHiddenByGenealogy ]); this.itemsHiddenByCollapse.clear(); this.itemsHiddenByGenealogy.clear(); for (const edge of this.completeGraph.edges) { this.onCollapsedStateUpdated(edge.sourcePort, false); } this.filteredGraph.nodePredicateChanged(); await this.refreshLayout( this._graphComponent.currentItem, incrementalNodes, false ); this.onGraphUpdated(); } /** * @returns Whether {@link executeShowAll} can be executed. */ canExecuteShowAll() { return (this.itemsHiddenByCollapse.size !== 0 || this.itemsHiddenByGenealogy.size !== 0) && !this.doingLayout; } /** * Refreshes the node after modifications on the tree. * @returns a promise which is resolved when the layout has been executed. */ async refreshLayout(centerNode, incrementalNodes, collapse) { if (this.doingLayout) { return Promise.resolve(); } this.doingLayout = true; if (!collapse) { this.prepareSmoothExpandLayoutAnimation(incrementalNodes); } this.layoutSupport.runLayout( incrementalNodes.size > 0, [...incrementalNodes], centerNode, true ).then(() => { this.doingLayout = false; }); } /** * Moves incremental nodes to a location between their neighbors before expanding for a smooth animation. */ prepareSmoothExpandLayoutAnimation(incrementalNodes) { const graph = this._graphComponent.graph; const layoutData = new PlaceNodesAtBarycenterStageData({ affectedNodes: incrementalNodes }); const layout = new PlaceNodesAtBarycenterStage(); graph.applyLayout(layout, layoutData); } addToHiddenNodes(nodes) { for (const node of nodes) { this.itemsHiddenByCollapse.add(node); } } /** * Set the collapsed state to all the node's ports. */ updateCollapsedState(node, collapsed) { for (const outEdge of this.completeGraph.outEdgesAt(node)) { this.onCollapsedStateUpdated(outEdge.sourcePort, collapsed); } } /** * Restores the group containing the given node if needed. */ static restoreGroup(graph, hiddenNodesSet, node) { const parent = graph.getParent(node); if (parent && hiddenNodesSet.has(parent)) { hiddenNodesSet.delete(parent); } } /** * Removes all groups in the given graph that will be empty after removing the given nodes. */ removeEmptyGroups(nodesToHide) { const emptyGroups = _CollapsibleTree.findEmptyGroups(this.filteredGraph, nodesToHide).toArray(); for (const group of emptyGroups) { this.itemsHiddenByCollapse.add(group); } } static findEmptyGroups(graph, nodesToHide) { return graph.nodes.filter( (node) => graph.isGroupNode(node) && graph.degree(node) === 0 && graph.getChildren(node).every((child) => nodesToHide.has(child)) ); } /** * @returns all descendants of the passed node excluding the node itself. */ static collectDescendants(graph, root) { const nodes = /* @__PURE__ */ new Set(); const queue = [root]; while (queue.length > 0) { const node = queue.pop(); for (const outEdge of graph.outEdgesAt(node)) { queue.unshift(outEdge.targetNode); nodes.add(outEdge.targetNode); } } return nodes; } /** * Creates an array of all nodes excluding the nodes in the subtree rooted in the excluded sub-root. */ static collectAllNodesExceptSubtree(graph, excludedRoot) { const subtree = this.collectDescendants(graph, excludedRoot); subtree.add(excludedRoot); return new Set(graph.nodes.filter((node) => !subtree.has(node))); } /** * Informs the listener that the graph was updated. */ onGraphUpdated() { this.graphUpdatedListener?.(); } /** * Informs the listener that the collapsed state was updated. */ onCollapsedStateUpdated(port, collapsed) { this.collapsedStateUpdatedListener?.(port, collapsed); } executeShowGenealogy(node) { const graph = this.graphComponent.graph; const filteredGraphWrapper = getFilteredGraphWrapper(this.graphComponent); this.itemsHiddenByCollapse.clear(); this.itemsHiddenByGenealogy.clear(); filteredGraphWrapper.nodePredicateChanged(); filteredGraphWrapper.edgePredicateChanged(); const neighborhood = new Neighborhood2({ startNodes: [node], traversalDirection: TraversalDirection2.BOTH }); const result = neighborhood.run(graph); const resultNodes = new Set(result.neighbors.toArray()); resultNodes.add(node); const groupingSupport = graph.groupingSupport; const genealogy = /* @__PURE__ */ new Set(); resultNodes.forEach((node2) => { genealogy.add(node2); groupingSupport.getAncestors(node2).forEach((parent) => genealogy.add(parent)); }); graph.nodes.forEach((node2) => { if (!genealogy.has(node2)) { this.itemsHiddenByGenealogy.add(node2); if (graph.isGroupNode(node2)) { groupingSupport.getDescendants(node2).forEach((child) => { this.itemsHiddenByGenealogy.add(child); }); } } }); const neighborhoodHighlightManager = getNeighborhoodIndicatorManager(this.graphComponent); neighborhoodHighlightManager.deactivateHighlights(); filteredGraphWrapper.nodePredicateChanged(); filteredGraphWrapper.edgePredicateChanged(); this.onGraphUpdated(); this.layoutSupport.runLayout(false, [], null, true).then(() => { neighborhoodHighlightManager.activateHighlights(); }); } }; // src/CompanyOwnershipProvider.tsx import { jsx } from "react/jsx-runtime"; var CompanyOwnershipContext = createContext(null); function useCompanyOwnershipContextInternal() { return useContext(CompanyOwnershipContext); } function useCompanyOwnershipContext() { const context = useContext(CompanyOwnershipContext); if (context === null) { throw new Error( "This method can only be used inside a CompanyOwnership component or CompanyOwnershipProvider." ); } return context; } var CompanyOwnershipProvider = withGraphComponentProvider( ({ children }) => { const graphComponent = useGraphComponent(); if (!graphComponent) { return children; } const CompanyOwnership2 = useMemo(() => { const fullGraph = new Graph2(); graphComponent.htmlElement.style.backgroundColor = componentBackgroundColor; graphComponent.viewportLimiter.policy = ViewportLimitingPolicy.WITHIN_MARGINS; graphComponent.viewportLimiter.viewportContentMargins = defaultGraphFitInsets.getEnlarged(20); graphComponent.maximumZoom = maximumZoom; graphComponent.minimumZoom = minimumZoom; const layoutSupport = new LayoutSupport(graphComponent); const collapsibleTree = new CollapsibleTree(graphComponent, fullGraph, layoutSupport); graphComponent.graph = collapsibleTree.filteredGraph; return createCompanyOwnershipModel(graphComponent, collapsibleTree, layoutSupport); }, []); return /* @__PURE__ */ jsx(CompanyOwnershipContext.Provider, { value: CompanyOwnership2, children }); } ); // src/components/template-utils.ts function stringifyData(data) { return typeof data === "object" ? JSON.stringify(data) : String(data); } function getNodeClass(level) { return level === "high" ? "yfiles-react-detail-node" : "yfiles-react-overview-node"; } function getHighlightClasses(selected, hovered, focused) { const highlights = ["yfiles-react-node-highlight"]; if (focused) { highlights.push("yfiles-react-node-highlight--focused"); } if (hovered) { highlights.push("yfiles-react-node-highlight--hovered"); } if (selected) { highlights.push("yfiles-react-node-highlight--selected"); } return highlights.join(" "); } function getUserProperties(data) { const internalProperties = [ "id", "className", "style", "width", "height", "parentId", "connections" ]; return Object.fromEntries( Object.entries(data).filter( ([property, _value]) => !property.startsWith("__") && internalProperties.every((key) => property !== key) ).slice(0, 10) ); } // src/components/RenderEntity.tsx import { Fragment } from "react"; import { Fragment as Fragment2, jsx as jsx2, jsxs } from "react/jsx-runtime"; function RenderEntity({ dataItem, detail, hovered, focused, selected }) { const customCompanyOwnershipItem = dataItem; const properties = getUserProperties(customCompanyOwnershipItem); const companyType = customCompanyOwnershipItem.type ?? "Unknown"; const style = { ...customCompanyOwnershipItem.style, ...{ width: "100%", height: "100%", overflow: "hidden" } }; return /* @__PURE__ */ jsx2(Fragment2, { children: /* @__PURE__ */ jsx2( "div", { style, className: `yfiles-react-node ${getNodeClass(detail)} ${getHighlightClasses( selected, hovered, focused )} company-node-${companyType.toLowerCase()} ${customCompanyOwnershipItem.className ?? ""}`.trim(), children: detail === "high" ? /* @__PURE__ */ jsxs("div", { className: "yfiles-react-detail-node__content", children: [ /* @__PURE__ */ jsx2("div", { className: "yfiles-react-detail-node__name", children: customCompanyOwnershipItem.name ?? `ID: ${customCompanyOwnershipItem.id}` }), /* @__PURE__ */ jsx2("div", { className: "yfiles-react-detail-node__data-grid", children: Object.entries(properties).map(([property, value], i) => /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx2("div", { className: "yfiles-react-detail-node__data-grid--key", children: property }), /* @__PURE__ */ jsx2("div", { className: "yfiles-react-detail-node__data-grid--value", children: stringifyData(value) }) ] }, i)) }) ] }) : /* @__PURE__ */ jsx2("div", { className: "yfiles-react-overview-node__content", children: customCompanyOwnershipItem.name ?? `ID: ${customCompanyOwnershipItem.id}` }) } ) }); } // src/components/RenderTooltip.tsx import { Fragment as Fragment3 } from "react"; import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime"; function RenderTooltip({ data }) { const context = useCompanyOwnershipContext(); if (!data) { return null; } let title = ""; if (context.isConnection(data)) { title = `${data.sourceId} -> ${data.targetId}`; } else { title = data.name ?? String(data.id); } const properties = getUserProperties(data); return /* @__PURE__ */ jsxs2("div", { className: "yfiles-react-tooltip", children: [ title && /* @__PURE__ */ jsx3("div", { className: "yfiles-react-tooltip__name", children: stringifyData(title) }), /* @__PURE__ */ jsx3("div", { className: "yfiles-react-tooltip__data-grid", children: Object.entries(properties).map(([property, value], i) => /* @__PURE__ */ jsxs2(Fragment3, { children: [ /* @__PURE__ */ jsx3("div", { className: "yfiles-react-tooltip__data-grid--key", children: property }), /* @__PURE__ */ jsx3("div", { className: "yfiles-react-tooltip__data-grid--value", children: stringifyData(value) }) ] }, i)) }) ] }); } // src/components/CompanyOwnershipContextMenuItems.tsx function DefaultContextMenuItems(item) { const { isConnection, highlightConnectedItems, showGenealogy, canClearConnectedItemsHighlight, clearConnectedItemsHighlight, canShowAll, showAll, canShowSuccessors, showSuccessors, canHideSuccessors, hideSuccessors, canShowPredecessors, showPredecessors, canHidePredecessors, hidePredecessors } = useCompanyOwnershipContext(); const menuItems = []; if (item) { const isEdge = isConnection(item); if (!isEdge) { menuItems.push({ title: "Highlight Visible Connected Entities", action: () => { highlightConnectedItems(item); } }); menuItems.push({ title: "Filter Company Genealogy", action: () => { showGenealogy(item); } }); if (canShowSuccessors(item)) { menuItems.push({ title: "Show Successors", action: () => { void showSuccessors(item); } }); } if (canHideSuccessors(item)) { menuItems.push({ title: "Hide Successors", action: () => { void hideSuccessors(item); } }); } if (canShowPredecessors(item)) { menuItems.push({ title: "Show Predecessors", action: () => { void showPredecessors(item); } }); } if (canHidePredecessors(item)) { menuItems.push({ title: "Hide Predecessors", action: () => { void hidePredecessors(item); } }); } } } else { if (canClearConnectedItemsHighlight()) { menuItems.push({ title: "Clear Connected Entities Highlight", action: () => { clearConnectedItemsHighlight(); } }); } if (canShowAll()) { menuItems.push({ title: "Show Entire Diagram", action: () => { showAll(); } }); } } return menuItems; } // src/components/CompanyOwnershipControlButtons.tsx import { DefaultControlButtons as CoreDefaultControlButtons } from "@yworks/react-yfiles-core"; function DefaultControlButtons() { return CoreDefaultControlButtons(); } // src/CompanyOwnership.tsx import { useEffect, useMemo as useMemo2, useState } from "react"; import { BridgeCrossingStyle, BridgeManager, GraphObstacleProvider, GroupPaddingProvider, Size as Size2 } from "@yfiles/yfiles"; // src/core/input.ts import { GraphItemTypes, GraphViewerInputMode as GraphViewerInputMode2, IEdge as IEdge4, INode as INode5 } from "@yfiles/yfiles"; // src/core/data-loading.ts import { LabelStyle as LabelStyle2, GraphBuilder } from "@yfiles/yfiles"; import { convertToPolylineEdgeStyle, ReactComponentHtmlNodeStyle } from "@yworks/react-yfiles-core"; // src/styles/CompanyOwnershipNodeStyles.ts import { LabelStyle, InteriorNodeLabelModel, LabelDefaults, Size } from "@yfiles/yfiles"; // src/styles/CustomShapeNodeStyle.ts import { Fill, GeneralPath, GeneralPathNodeStyle, NodeStyleBase, Point, Rect as Rect2, Stroke } from "@yfiles/yfiles"; var CustomShapeNodeStyle = class extends NodeStyleBase { /** * Creates the custom style for the given type of node. */ constructor(type, stroke, fill) { super(); this.type = type; this.stroke = stroke; this.fill = fill; this.type = type; this.stroke = Stroke.from(stroke ?? "black"); this.fill = Fill.from(fill ?? "white"); let gp; this.gpNodeStyle = new GeneralPathNodeStyle(); this.gpNodeStyle.stroke = this.stroke; this.gpNodeStyle.fill = this.fill; switch (type) { case "Corporation": gp = createCorporationPath(); break; case "CTB": gp = createCtbPath(); break; case "Partnership": gp = createPartnershipPath(); break; case "RCTB": gp = createRctbPath(); break; case "Branch": case "Individual": case "Unknown": gp = createBranchPath(); break; case "Disregarded": gp = createDisregardedPath(); break; case "Dual Resident": gp = createDualResidentPath(); break; case "Multiple": gp = createMultiplePath(); break; case "Trust": gp = createTrustPath(); break; case "Third Party": gp = createThirdPartyPath(); break; case "Trapezoid": gp = createTrapezoidPath(); break; case "PE_Risk": this.gpNodeStyle.stroke = new Stroke({ fill: this.stroke.fill, thickness: 2, dashStyle: "dash", lineCap: "square" }); this.gpNodeStyle.stroke.freeze(); gp = createPeRiskPath(); break; case "Asset": gp = createAssetPath(); break; case "Octagon": gp = createOctagonPath(); break; case "Bank": gp = createBankPath(); break; default: throw new Error("Unknown Type"); } this.gpNodeStyle.path = gp; this.gpNodeStyle.cssClass = `company-node company-node-${type.toLowerCase().replace(/[_\s]/g, "-")}`; } gpNodeStyle; /** * Creates the visual for the given node. * @param renderContext The render context * @param node The node to which this style is assigned * @see Overrides {@link NodeStyleBase.createVisual} */ createVisual(renderContext, node) { return this.gpNodeStyle.renderer.getVisualCreator(node, this.gpNodeStyle).createVisual(renderContext); } /** * Updates the visual for the given node. * @param renderContext The render context * @param oldVisual The visual that has been created in the call to createVisual * @param node The node to which this style is assigned */ updateVisual(renderContext, oldVisual, node) { return this.gpNodeStyle.renderer.getVisualCreator(node, this.gpNodeStyle).updateVisual(renderContext, oldVisual); } /** * Gets the outline of the visual style. * @param node The node to which this style is assigned */ getOutline(node) { switch (this.type) { case "Asset": case "Bank": case "Branch": case "Individual": case "Multiple": case "Octagon": case "PE_Risk": case "Partnership": case "Third Party": case "Trust": case "Unknown": return this.gpNodeStyle.renderer.getShapeGeometry(node, this.gpNodeStyle).getOutline(); default: return null; } } }; function createPartnershipPath() { const gp = new GeneralPath(); gp.moveTo(0, 1); gp.lineTo(0.5, 0); gp.lineTo(1, 1); gp.close(); return gp; } function createRctbPath() { const gp = new GeneralPath(); gp.moveTo(0, 0); gp.lineTo(1, 0); gp.lineTo(1, 1); gp.lineTo(0, 1); gp.close(); gp.moveTo(1, 0); gp.lineTo(0.5, 1); gp.lineTo(0, 0); return gp; } function createTrapezoidPath() { const gp = new GeneralPath(); gp.moveTo(0, 0); gp.lineTo(1, 0); gp.lineTo(1, 1); gp.lineTo(0, 1); gp.close(); gp.moveTo(0.2, 0); gp.lineTo(0.8, 0); gp.lineTo(1, 1); gp.lineTo(0, 1); gp.lineTo(0.2, 0); return gp; } function createBranchPath() { const gp = new GeneralPath(); gp.appendEllipse(new Rect2(0, 0, 1, 1), false); return gp; } function createDisregardedPath() { const gp = new GeneralPath(); gp.moveTo(0, 0); gp.lineTo(1, 0); gp.lineTo(1, 1); gp.lineTo(0, 1); gp.close(); gp.appendEllipse(new Rect2(0, 0, 1, 1), false); return gp; } function createDualResidentPath() { const gp = new GeneralPath(); gp.moveTo(0, 0); gp.lineTo(1, 0); gp.lineTo(1, 1); gp.lineTo(0, 1); gp.close(); gp.moveTo(0, 1); gp.lineTo(1, 0); return gp; } function createMultiplePath() { const gp = new GeneralPath(); gp.moveTo(0, 0); gp.lineTo(0.9, 0); gp.lineTo(0.9, 0.9); gp.lineTo(0, 0.9); gp.close(); gp.moveTo(0.9, 0.1); gp.lineTo(1, 0.1); gp.lineTo(1, 1); gp.lineTo(0.1, 1); gp.lineTo(0.1, 0.9); gp.lineTo(0.9, 0.9); gp.close(); return gp; } function createTrustPath() { const gp = new GeneralPath(); gp.moveTo(0, 0.5); gp.lineTo(0.5, 0); gp.lineTo(1, 0.5); gp.lineTo(0.5, 1); gp.close(); return gp; } function createPeRiskPath() { const gp = new GeneralPath(); gp.appendEllipse(new Rect2(0, 0, 1, 1), false); return gp; } function createThirdPartyPath() { const gp = new GeneralPath(); gp.moveTo(0.25273825759228363, 0.2106077406985223); gp.cubicTo( new Point(0.37940464379944383, 0.008533694660719517), new Point(0.5427384738867617, -0.07436838307589484), new Point(0.7327381431952041, 0.20542116176184805) ); gp.cubicTo( new Point(0.9727395859583705, 0.2054237109534111), new Point(1.026070204427681, 0.5059367593821318), new Point(0.9360671322061148, 0.6302855466359552) ); gp.cubicTo( new Point(0.9727385659844104, 1.0499824248785579), new Point(0.7327384631870348, 0.9929823150021839), new Point(0.5727383979886994, 0.9308125068113157) ); gp.cubicTo( new Point(0.37607164889080386, 1.044795193100142), new Point(0.23606605323366095, 0.9878057307616991), new Point(0.17274109991971903, 0.8064517974237797) ); gp.cubicTo( new Point(-0.1039264767570484, 0.68210650753643), new Point(0.012736869827713297, 0.2572344906314848), new Point(0.25273825759228363, 0.2106077406985223) ); gp.close(); return gp; } function createCorporationPath() { const gp = new GeneralPath(); gp.moveTo(0, 0); gp.lineTo(1, 0); gp.lineTo(1, 1); gp.lineTo(0, 1); gp.close(); return gp; } function createCtbPath() { const gp = new GeneralPath(); gp.moveTo(0, 0); gp.lineTo(1, 0); gp.lineTo(1, 1); gp.lineTo(0, 1); gp.close(); gp.moveTo(0, 1); gp.lineTo(0.5, 0); gp.lineTo(1, 1); return gp; } function createOctagonPath() { const gp = new GeneralPath(); gp.moveTo(0.25, 0); gp.lineTo(0.75, 0); gp.lineTo(1, 0.25); gp.lineTo(1, 0.75); gp.lineTo(0.75, 1); gp.lineTo(0.25, 1); gp.lineTo(0, 0.75); gp.lineTo(0, 0.25); gp.close(); return gp; } function createAssetPath() { const gp = new GeneralPath(); gp.moveTo(0, 0.5); gp.lineTo(0.25, 0); gp.lineTo(0.75, 0); gp.lineTo(1, 0.5); gp.lineTo(0.75, 1); gp.lineTo(0.25, 1); gp.lineTo(0, 0.5); gp.close(); return gp; } function createBankPath() { const gp = new GeneralPath(); gp.moveTo(0.5, 0); gp.lineTo(0, 0.47); gp.lineTo(0.191, 1); gp.lineTo(0.809, 1); gp.lineTo(1, 0.47); gp.lineTo(0.5, 0); gp.close(); return gp; } // src/styles/CompanyOwnershipNodeStyles.ts function getNodeStyle(item) { return new CustomShapeNodeStyle(item.type ?? "Unknown"); } var nodeLabelStyle = new LabelStyle({ wrapping: "wrap-word-ellipsis", horizontalTextAlignment: "center", verticalTextAlignment: "center" }); var nodeLabelParameter = InteriorNodeLabelModel.CENTER; var labelSizeDefaults = new Size(80, 60); var nameLabelDefaults = new LabelDefaults({ style: nodeLabelStyle, layoutParameter: nodeLabelParameter, autoAdjustPreferredSize: false }); // src/core/data-loading.ts var GraphManager = class { constructor(graphBuilder, nodesSource, connectionsSource) { this.graphBuilder = graphBuilder; this.nodesSource = nodesSource; this.connectionsSource = connectionsSource; } nodeData = []; connectionsData = []; renderEntities; connectionStyleProvider; connectionLabelProvider; itemLabelProvider; incrementalElements = []; updateGraph(data, renderEntity, connectionStyleProvider, connectionLabelProvider, itemLabelProvider) { const nodeData = data.companies; const connectionsData = data.connections; this.incrementalElements = compareData(this.nodeData, nodeData); this.nodeData = nodeData; this.connectionsData = connectionsData; if (!this.graphBuilder || !this.nodesSource || !this.connectionsSource) { return; } if (renderEntity) { this.renderEntities = renderEntity; } if (connectionStyleProvider) { this.connectionStyleProvider = connectionStyleProvider; } if (connectionLabelProvider) { this.connectionLabelProvider = connectionLabelProvider; } else { this.connectionLabelProvider = (item) => { return item.type === "Ownership" && typeof item.ownership !== "undefined" ? { text: String(item.ownership) } : void 0; }; } if (itemLabelProvider) { this.itemLabelProvider = itemLabelProvider; } else { this.itemLabelProvider = (item) => { return item.name ? { text: item.name } : { text: `ID: ${item.id}` }; }; } this.graphBuilder.setData(this.nodesSource, nodeData); this.graphBuilder.setData(this.connectionsSource, connectionsData); this.graphBuilder.updateGraph(); } }; function initializeGraphManager(graph, setNodeInfos, compa