Skip to content

Commit 6adef67

Browse files
committed
#10: Call getRootNode less and pass the root element to functions that measure labels or paddings
1 parent 449b528 commit 6adef67

File tree

3 files changed

+60
-49
lines changed

3 files changed

+60
-49
lines changed

yfiles-layout-reactflow/src/layout/LayoutSupport.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { GraphComponent, IGraph, LayoutExecutor, LayoutExecutorAsync } from '@yf
22
import { getLayoutAlgorithm, getLayoutData } from './layout-algorithms.ts'
33
import { LayoutAlgorithmConfiguration, LayoutDataProvider } from './layout-types.ts'
44
import { registerWebWorker } from './WebWorkerSupport.ts'
5+
import { RefObject } from 'react'
56

67
export class LayoutSupport {
78
private readonly workerPromise: Promise<Worker> | null = null
@@ -17,21 +18,21 @@ export class LayoutSupport {
1718
graph: IGraph,
1819
layoutConfiguration: LayoutAlgorithmConfiguration,
1920
layoutDataProvider?: LayoutDataProvider<TNodeData, TEdgeData>,
20-
reactFlowElement?: HTMLDivElement
21+
reactFlowRef?: RefObject<HTMLDivElement>
2122
): Promise<void> {
2223
const executor =
2324
this.workerPromise !== null
2425
? await this.createLayoutExecutorAsync(
2526
graph,
2627
layoutConfiguration,
2728
layoutDataProvider,
28-
reactFlowElement
29+
reactFlowRef
2930
)
3031
: await this.createLayoutExecutor(
3132
graph,
3233
layoutConfiguration,
3334
layoutDataProvider,
34-
reactFlowElement
35+
reactFlowRef
3536
)
3637

3738
try {
@@ -50,14 +51,14 @@ export class LayoutSupport {
5051
graph: IGraph,
5152
layoutConfiguration: LayoutAlgorithmConfiguration,
5253
layoutDataProvider?: LayoutDataProvider<TNodeData, TEdgeData>,
53-
reactFlowElement?: HTMLDivElement
54+
reactFlowRef?: RefObject<HTMLDivElement>
5455
): Promise<LayoutExecutor> {
5556
const layoutAlgorithm = await getLayoutAlgorithm(layoutConfiguration)
5657
return Promise.resolve(
5758
new LayoutExecutor({
5859
graphComponent: new GraphComponent({ graph }),
5960
layout: layoutAlgorithm,
60-
layoutData: getLayoutData(layoutConfiguration.name, layoutDataProvider, reactFlowElement)
61+
layoutData: getLayoutData(layoutConfiguration.name, layoutDataProvider, reactFlowRef)
6162
})
6263
)
6364
}
@@ -66,7 +67,7 @@ export class LayoutSupport {
6667
graph: IGraph,
6768
layoutConfiguration: LayoutAlgorithmConfiguration,
6869
layoutDataProvider?: LayoutDataProvider<TNodeData, TEdgeData>,
69-
reactFlowElement?: HTMLDivElement
70+
reactFlowRef?: RefObject<HTMLDivElement>
7071
): Promise<LayoutExecutorAsync> {
7172
if (this.executor) {
7273
await this.executor.cancel()
@@ -97,7 +98,7 @@ export class LayoutSupport {
9798
messageHandler: webWorkerMessageHandler,
9899
graph,
99100
layoutDescriptor: layoutConfiguration,
100-
layoutData: getLayoutData(layoutConfiguration.name, layoutDataProvider, reactFlowElement)
101+
layoutData: getLayoutData(layoutConfiguration.name, layoutDataProvider, reactFlowRef)
101102
})
102103
return this.executor
103104
}

yfiles-layout-reactflow/src/layout/layout-algorithms.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
PortDataProvider,
4242
PortSides
4343
} from './layout-types.ts'
44+
import { RefObject } from 'react'
4445

4546
export async function getLayoutAlgorithm(
4647
layoutDescriptor: LayoutDescriptor
@@ -88,12 +89,13 @@ export async function getLayoutAlgorithm(
8889
export function getLayoutData<TNodeData, TEdgeData>(
8990
layoutName: LayoutName,
9091
layoutDataDescriptor?: LayoutDataProvider<TNodeData, TEdgeData>,
91-
reactFlowElement?: HTMLDivElement
92+
reactFlowRef?: RefObject<HTMLElement>
9293
): LayoutData | null {
9394
const layoutData = new GenericLayoutData()
95+
const rootElement = getRootNode(reactFlowRef)
9496
layoutData.addItemMapping(LayoutKeys.GROUP_NODE_PADDING_DATA_KEY, (node: INode) =>
9597
// @ts-expect-error property-groupNodePadding-does-not-exist-on-type-LayoutDataProvider
96-
translatePadding(node, layoutDataDescriptor?.groupNodePadding?.(node.tag), reactFlowElement)
98+
translatePadding(rootElement, node, layoutDataDescriptor?.groupNodePadding?.(node.tag))
9799
)
98100

99101
switch (layoutName) {
@@ -263,17 +265,17 @@ function translateNodeMargins(insets: Insets | number): YFilesInsets {
263265
}
264266

265267
function translatePadding(
268+
rootElement: HTMLElement,
266269
node: INode,
267-
insets?: Insets,
268-
reactFlowElement?: HTMLDivElement
270+
insets?: Insets
269271
): YFilesInsets | null {
270272
if (insets) {
271273
if (typeof insets === 'number') {
272274
return new YFilesInsets(insets, insets, insets, insets)
273275
}
274276
return new YFilesInsets(insets.top, insets.right, insets.bottom, insets.left)
275277
}
276-
return getGroupNodePadding(node, reactFlowElement)
278+
return getGroupNodePadding(node, rootElement)
277279
}
278280

279281
function translateEdgeLabelPreferredPlacement(
@@ -282,9 +284,8 @@ function translateEdgeLabelPreferredPlacement(
282284
return new YFilesEdgeLabelPreferredPlacement(descriptor)
283285
}
284286

285-
function getGroupNodePadding(node: INode, reactFlowElement?: HTMLDivElement): YFilesInsets {
286-
const root = getRootNode(reactFlowElement)
287-
const nodeElement = root.querySelector(`[data-id="${node.tag.id}"]`)
287+
function getGroupNodePadding(node: INode, rootElement: HTMLElement): YFilesInsets {
288+
const nodeElement = rootElement.querySelector(`[data-id="${node.tag.id}"]`)
288289
if (nodeElement) {
289290
const computedStyle = window.getComputedStyle(nodeElement)
290291
const paddingTop = parseInt(computedStyle.getPropertyValue('padding-top')) ?? 0
@@ -297,16 +298,21 @@ function getGroupNodePadding(node: INode, reactFlowElement?: HTMLDivElement): YF
297298
}
298299

299300
export function getRootNode(
300-
reactFlowElement?: HTMLDivElement
301+
reactFlowRef?: RefObject<HTMLElement>
301302
): Element | Document | DocumentFragment {
302-
if (reactFlowElement) {
303-
return reactFlowElement.getRootNode() as HTMLElement
303+
if (reactFlowRef?.current) {
304+
return reactFlowRef.current.getRootNode() as HTMLElement
304305
}
305306

306307
const shadowRoots = Array.from(document.querySelectorAll('*'))
307308
.filter(e => !!e.shadowRoot)
308309
.map(e => e.shadowRoot)
309310
if (shadowRoots.length > 0) {
311+
if (shadowRoots.length > 1) {
312+
console.warn(
313+
'There are more than one shadow roots. For correct results, please call useLayout or useLayoutSupport with a reference to the corresponding reactflow component.'
314+
)
315+
}
310316
return shadowRoots.at(0) as DocumentFragment
311317
}
312318

yfiles-layout-reactflow/src/layout/layout-hooks.ts

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,10 @@ export function useLayout<
214214
graph,
215215
{ name, properties: layoutOptions } as LayoutAlgorithmConfiguration,
216216
layoutData,
217-
context?.reactFlowRef?.current ?? undefined
217+
context?.reactFlowRef
218218
)
219219
.then(() => {
220-
const { arrangedNodes, arrangedEdges } = transferLayout(graph)
220+
const { arrangedNodes, arrangedEdges } = transferLayout(graph, context?.reactFlowRef)
221221
setNodes(arrangedNodes)
222222
setEdges(arrangedEdges)
223223
setTimeout(() => fitView())
@@ -248,11 +248,6 @@ export interface LayoutSupport {
248248
* Transfers the calculated layout from the graph to lists of nodes and edges.
249249
*/
250250
transferLayout: (graph: IGraph) => { arrangedNodes: Node[]; arrangedEdges: Edge[] }
251-
/**
252-
* An optional reference to the React Flow element. This reference is particularly useful in scenarios where there
253-
* are multiple React Flow elements on the same page or when the flow is encapsulated within a closed shadow DOM.
254-
*/
255-
reactFlowRef?: RefObject<HTMLElement>
256251
}
257252

258253
/**
@@ -334,11 +329,19 @@ export interface LayoutSupport {
334329
* )
335330
* }
336331
* ```
337-
*
332+
* @param {RefObject<HTMLElement>}[reactFlowRef] An optional reference to the React Flow element. This reference is
333+
* particularly useful in scenarios where there are multiple React Flow elements on the same page or when the flow is
334+
* encapsulated within a closed shadow DOM.
338335
* @returns functions to transfer the data into a graph and get the layout information from the graph.
339336
*/
340-
export function useLayoutSupport(): LayoutSupport {
341-
return { buildGraph, transferLayout }
337+
export function useLayoutSupport(reactFlowRef?: RefObject<HTMLElement>): LayoutSupport {
338+
return {
339+
buildGraph: useCallback(
340+
(nodes, edges, zoom) => buildGraph(nodes, edges, zoom, reactFlowRef),
341+
[reactFlowRef]
342+
),
343+
transferLayout: useCallback(graph => transferLayout(graph, reactFlowRef), [reactFlowRef])
344+
}
342345
}
343346

344347
/**
@@ -411,7 +414,7 @@ function buildGraph(
411414
nodes: Node[],
412415
edges: Edge[],
413416
zoom: number,
414-
reactFlowRef?: RefObject<HTMLDivElement>
417+
reactFlowRef?: RefObject<HTMLElement>
415418
): IGraph {
416419
if (!checkLicense()) {
417420
return new Graph()
@@ -455,13 +458,11 @@ function buildGraph(
455458

456459
const graph = builder.buildGraph()
457460

461+
const rootElement = getRootNode(reactFlowRef)
462+
458463
graph.nodes.forEach(node => {
459464
node.labels.forEach((label, index) => {
460-
const preferredSize = measureLabel(
461-
`node-label-${node.tag.id}-${index}`,
462-
zoom,
463-
reactFlowRef?.current ?? undefined
464-
)
465+
const preferredSize = measureLabel(`node-label-${node.tag.id}-${index}`, zoom, rootElement)
465466
if (preferredSize.width > 0 && preferredSize.height > 0) {
466467
graph.setLabelPreferredSize(label, preferredSize)
467468
}
@@ -494,11 +495,7 @@ function buildGraph(
494495

495496
graph.edges.forEach(edge => {
496497
edge.labels.forEach((label, index) => {
497-
const preferredSize = measureLabel(
498-
`edge-label-${edge.tag.id}-${index}`,
499-
zoom,
500-
reactFlowRef?.current ?? undefined
501-
)
498+
const preferredSize = measureLabel(`edge-label-${edge.tag.id}-${index}`, zoom, rootElement)
502499
if (preferredSize.width > 0 && preferredSize.height > 0) {
503500
graph.setLabelPreferredSize(label, preferredSize)
504501
}
@@ -550,7 +547,10 @@ function buildGraph(
550547
return graph
551548
}
552549

553-
function transferLayout(graph: IGraph): { arrangedNodes: Node[]; arrangedEdges: Edge[] } {
550+
function transferLayout(
551+
graph: IGraph,
552+
reactFlowRef?: RefObject<HTMLElement>
553+
): { arrangedNodes: Node[]; arrangedEdges: Edge[] } {
554554
if (!checkLicense()) {
555555
return { arrangedNodes: [], arrangedEdges: [] }
556556
}
@@ -569,6 +569,8 @@ function transferLayout(graph: IGraph): { arrangedNodes: Node[]; arrangedEdges:
569569
}
570570
})
571571

572+
const rootElement = getRootNode(reactFlowRef)
573+
572574
const arrangedNodes = graph.nodes
573575
.map(node => {
574576
const offset = nodeOffsets.get(node) ?? { dx: 0, dy: 0 }
@@ -605,7 +607,7 @@ function transferLayout(graph: IGraph): { arrangedNodes: Node[]; arrangedEdges:
605607
.map((label, index) => {
606608
const rect = label.layout as OrientedRectangle
607609
const labelId = `node-label-${node.tag.id}-${index}`
608-
const padding = calculatePadding(labelId)
610+
const padding = calculatePadding(labelId, rootElement)
609611
const angle = rect.angle
610612
return {
611613
id: labelId,
@@ -646,7 +648,7 @@ function transferLayout(graph: IGraph): { arrangedNodes: Node[]; arrangedEdges:
646648
.map((label, index) => {
647649
const rect = label.layout as OrientedRectangle
648650
const labelId = `edge-label-${edge.tag.id}-${index}`
649-
const padding = calculatePadding(labelId)
651+
const padding = calculatePadding(labelId, rootElement)
650652
return {
651653
id: labelId,
652654
x: rect.anchorX,
@@ -745,17 +747,20 @@ function translateSide(side: 'left' | 'right' | 'top' | 'bottom' | 'center' | 'o
745747
}
746748
}
747749

748-
function measureLabel(labelId: string, zoom: number, reactFlowElement?: HTMLDivElement): Size {
749-
const root = getRootNode(reactFlowElement)
750-
const labelElement = root.querySelector(`[data-id="${labelId}"]`) as HTMLDivElement
750+
function measureLabel(
751+
labelId: string,
752+
zoom: number,
753+
rootElement: Element | Document | DocumentFragment
754+
): Size {
755+
const labelElement = rootElement.querySelector(`[data-id="${labelId}"]`) as HTMLDivElement
751756
if (labelElement) {
752757
const oldTransform = labelElement.style.transform
753758
labelElement.style.transform = ''
754759
const oldBoxSizing = labelElement.style.boxSizing
755760
labelElement.style.boxSizing = 'content-box'
756761
const computedStyle = window.getComputedStyle(labelElement)
757762
// label padding should also be included in the width/size during the layout
758-
const labelPadding = calculatePadding(labelId)
763+
const labelPadding = calculatePadding(labelId, rootElement)
759764
let width = parseFloat(computedStyle.width)
760765
let height = parseFloat(computedStyle.height)
761766
if (width <= 0 || height <= 0) {
@@ -773,9 +778,8 @@ function measureLabel(labelId: string, zoom: number, reactFlowElement?: HTMLDivE
773778
return Size.ZERO
774779
}
775780

776-
function calculatePadding(labelId: string, reactFlowElement?: HTMLDivElement) {
777-
const root = getRootNode(reactFlowElement)
778-
const element = root.querySelector(`[data-id="${labelId}"]`) as HTMLDivElement
781+
function calculatePadding(labelId: string, rootElement: Element | Document | DocumentFragment) {
782+
const element = rootElement.querySelector(`[data-id="${labelId}"]`) as HTMLDivElement
779783
if (element) {
780784
const elementStyle = window.getComputedStyle(element)
781785
return {

0 commit comments

Comments
 (0)