|
1 | | -import React from 'react'; |
2 | | -// will create a build func and then call the helper funcs to return an object |
3 | | -// make a new instance of this class in flow, call the build method, and pass this as state |
| 1 | +import { ConnectionLineType, Edge, Node } from 'reactflow'; |
| 2 | +import { Tree } from '../types/tree'; |
| 3 | +import { getNonce } from '../getNonce'; |
| 4 | +import * as d3 from 'd3' |
4 | 5 |
|
5 | | -interface Node { |
6 | | - id: string; |
7 | | - data: { |
8 | | - label: React.ReactNode; |
9 | | - }; |
10 | | - type: string; |
11 | | - position: { x: number, y: number }; |
12 | | - style: { |
13 | | - borderRadius: string; |
14 | | - borderWidth: string; |
15 | | - borderColor: string; |
16 | | - display: string; |
17 | | - justifyContent: string; |
18 | | - placeItems: string; |
19 | | - backgroundColor: string; |
20 | | - }; |
21 | | -} |
| 6 | +// Contructs our family tree for React application root file that was selected |
22 | 7 |
|
23 | | -interface Edge { |
24 | | - id: string; |
25 | | - source: string; |
26 | | - target: string; |
27 | | - type: string; |
28 | | - animated: boolean; |
29 | | -} |
| 8 | +class FlowBuilder { |
30 | 9 |
|
31 | | -interface ParsedDataItem { |
32 | | - fileName: string; |
33 | | - isClientComponent: boolean; |
34 | | - children?: ParsedDataItem[]; |
35 | | - thirdParty?: boolean; |
36 | | - reactRouter?: boolean; |
37 | | -} |
| 10 | + public mappedData (data: Tree, nodes: Node[], edges: Edge[]) : void { |
38 | 11 |
|
39 | | -interface Settings { |
40 | | - thirdParty: boolean; |
41 | | - reactRouter: boolean; |
42 | | -} |
| 12 | + // Create a holder for the heirarchical data (msg.value), data comes in an object of all the Trees |
| 13 | + const root : d3.HierarchyNode<Tree> = d3.hierarchy(data) |
43 | 14 |
|
44 | | -class FlowBuilder { |
45 | | - private parsedData: ParsedDataItem[]; |
46 | | - private viewData: ParsedDataItem[]; |
47 | | - private id: number; |
48 | | - private x: number; |
49 | | - private y: number; |
50 | | - private edgeId: number; |
51 | | - public initialEdges: Edge[]; |
52 | | - public initialNodes: Node[]; |
| 15 | + // Dynamically adjust height and width of display depending on the amount of nodes |
| 16 | + const totalNodes : number = root.descendants().length; |
| 17 | + const width : number = Math.max(totalNodes * 100, 800); |
| 18 | + const height = Math.max(totalNodes * 20, 500) |
| 19 | + |
53 | 20 |
|
54 | | - constructor(data: ParsedDataItem) { |
55 | | - this.parsedData = [data]; |
56 | | - this.id = 0; |
57 | | - this.x = 0; |
58 | | - this.y = 0; |
59 | | - this.initialNodes = []; |
60 | | - this.initialEdges = []; |
61 | | - this.viewData = []; |
62 | | - this.edgeId = 0; |
63 | | - } |
| 21 | + //create tree layout and give nodes their positions and |
| 22 | + const treeLayout : d3.TreeLayout<unknown> = d3.tree() |
| 23 | + .size([ width, height ]) |
| 24 | + .separation((a: d3.HierarchyPointNode<Node>, b: d3.HierarchyPointNode<Node>) => (a.parent == b.parent ? 2 : 2.5)) |
64 | 25 |
|
65 | | - private buildNodesArray(parsedData: ParsedDataItem[] | undefined, x: number = this.x, y: number = this.y): void { |
66 | | - if (!parsedData) return; |
67 | 26 |
|
68 | | - parsedData.forEach((item) => { |
69 | | - const node: Node = { |
70 | | - id: (++this.id).toString(), |
71 | | - data: { |
72 | | - label: ( |
73 | | - <div className="text-sm font-medium text-ellipsis overflow-hidden ..." key={this.id}>{item.fileName}</div> |
74 | | - ) |
75 | | - }, |
76 | | - // type: item.depth === 0 ? 'input' : '', |
77 | | - type: 'default', |
78 | | - position: { x: (x += 40), y: (y += 30) }, |
| 27 | + treeLayout(root); |
| 28 | + // Iterate through each Tree and create a node |
| 29 | + root.each((node: any) : void => { |
| 30 | + |
| 31 | + // Create a Node from the current Root and add it to our nodes array |
| 32 | + nodes.push({ |
| 33 | + id: node.data.id, |
| 34 | + position: { x: node.x ? node.x : 0, y: node.y ? node.y : 0 }, |
| 35 | + type: node.depth === 0 ? 'input' : !node.children ? 'output' : 'default', |
| 36 | + data: { label: node.data.name }, |
79 | 37 | style: { |
80 | 38 | borderRadius: '6px', |
81 | 39 | borderWidth: '2px', |
82 | 40 | borderColor: '#6b7280', |
83 | 41 | display: 'flex', |
84 | 42 | justifyContent: 'center', |
85 | 43 | placeItems: 'center', |
86 | | - backgroundColor: `${(item.isClientComponent) ? '#fdba74' : '#93C5FD'}`, |
87 | | - }, |
88 | | - }; |
89 | | - this.initialNodes.push(node); |
90 | | - if (item.children) { |
91 | | - this.buildNodesArray(item.children, (this.x += 40), (this.y += 30)); |
92 | | - } |
93 | | - }); |
94 | | - }; |
95 | | - |
96 | | - private buildEdgesArray(parsedData: ParsedDataItem[] | undefined, parentID?: number): void { |
97 | | - if (!parsedData) return; |
98 | | - |
99 | | - parsedData.forEach((item) => { |
100 | | - const nodeID = ++this.edgeId; |
101 | | - if (parentID) { |
102 | | - const edge: Edge = { |
103 | | - id: `e${parentID}-${nodeID}`, |
104 | | - source: parentID.toString(), |
105 | | - target: nodeID.toString(), |
106 | | - type: 'bezier', |
107 | | - animated: false, |
| 44 | + backgroundColor: `${(node.data.isClientComponent) ? '#fdba74' : '#93C5FD'}`, |
| 45 | + } |
| 46 | + }); |
| 47 | + |
| 48 | + // If the current node has a parent, create an edge to show relationship |
| 49 | + if (node.data.parent) { |
| 50 | + const newEdge : Edge = { |
| 51 | + id: `${getNonce()}`, |
| 52 | + source: node.data.parent, |
| 53 | + target: node.data.id, |
| 54 | + type: ConnectionLineType.Bezier, |
| 55 | + animated: true, |
108 | 56 | }; |
109 | | - this.initialEdges.push(edge); |
110 | | - } |
111 | | - if (item.children) { |
112 | | - this.buildEdgesArray(item.children, nodeID); |
113 | | - } |
114 | | - }); |
115 | | - } |
116 | | - |
117 | | - public build(settings: Settings): void { |
118 | | - const treeParsed = JSON.parse(JSON.stringify(this.parsedData[0])); |
119 | | - // console.log('settings: ', settings); |
120 | | - const traverse = (node: ParsedDataItem): void => { |
121 | | - let validChildren: ParsedDataItem[] = []; |
122 | 57 |
|
123 | | - for (let i = 0; i < node.children?.length; i++) { |
124 | | - if ( |
125 | | - node.children[i].thirdParty && |
126 | | - settings.thirdParty && |
127 | | - !node.children[i].reactRouter |
128 | | - ) { |
129 | | - validChildren.push(node.children[i]); |
130 | | - } else if (node.children[i].reactRouter && settings.reactRouter) { |
131 | | - validChildren.push(node.children[i]); |
132 | | - } else if ( |
133 | | - !node.children[i].thirdParty && |
134 | | - !node.children[i].reactRouter |
135 | | - ) { |
136 | | - validChildren.push(node.children[i]); |
| 58 | + |
| 59 | + // Check if the edge already exists before adding |
| 60 | + const edgeExists : boolean = edges.some( |
| 61 | + edge => edge.source === newEdge.source && edge.target === newEdge.target |
| 62 | + ); |
| 63 | + |
| 64 | + // If edge does not exist, add to our edges array |
| 65 | + if (!edgeExists) { |
| 66 | + edges.push(newEdge) |
137 | 67 | } |
138 | 68 | } |
139 | | - |
140 | | - // Update children with only valid nodes, and recurse through each node |
141 | | - node.children = validChildren; |
142 | | - node.children.forEach((child) => { |
143 | | - traverse(child); |
144 | | - }); |
145 | 69 | } |
146 | | - traverse(treeParsed); |
147 | | - // Update the viewData state |
148 | | - this.viewData = ([treeParsed]); |
149 | | - console.log('viewData:', this.viewData); |
150 | | - this.buildNodesArray(this.viewData); |
151 | | - this.buildEdgesArray(this.viewData); |
| 70 | + ) |
| 71 | + |
152 | 72 | } |
153 | 73 | } |
154 | 74 |
|
|
0 commit comments