Skip to content

Commit 68f7dc9

Browse files
committed
feat: add metadata to tree nodes
Signed-off-by: Andres Correa Casablanca <castarco@coderspirit.xyz>
1 parent 96f43df commit 68f7dc9

File tree

6 files changed

+148
-61
lines changed

6 files changed

+148
-61
lines changed

src/BeautifulTree.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Tree } from './layouts'
1+
import type { Tree } from './types'
22

33
export interface BeautifulTreeProps {
44
readonly id: string

src/layouts.ts

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,31 @@
1-
export interface TreeChild<T extends Tree = Tree> {
2-
readonly node: T
3-
readonly edgeData?: Readonly<Record<string, unknown>> | undefined
4-
}
5-
6-
export interface Node {
7-
readonly data?: Readonly<Record<string, unknown>> | undefined
8-
}
9-
10-
export type Tree = Node & {
11-
readonly children?: readonly TreeChild[] | undefined
12-
}
13-
14-
export type TreeWithLayout = Node & {
15-
readonly children?: Required<TreeChild<TreeWithLayout>>[] | undefined
16-
readonly layout: {
17-
readonly plan: {
18-
readonly x: number
19-
readonly y: number
20-
}
21-
}
22-
}
23-
24-
type InternalTreeLayout = number[][]
1+
import type { Tree, TreeChild, TreeWithLayout } from './types'
252

263
const _computeLeftShiftLayout = (
274
tree: Tree,
285
depth = 0,
296
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
30-
layout?: InternalTreeLayout,
7+
counters?: number[][],
318
): TreeWithLayout => {
32-
layout ??= []
9+
counters ??= []
3310

34-
if (layout[depth] === undefined) {
35-
layout[depth] = []
11+
if (counters[depth] === undefined) {
12+
counters[depth] = []
3613
}
37-
const x = (layout[depth]?.at(-1) ?? -1) + 1
14+
const x = (counters[depth]?.at(-1) ?? -1) + 1
3815
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
39-
layout[depth]!.push(x)
16+
counters[depth]!.push(x)
4017

4118
return {
4219
data: tree.data,
4320
children: tree.children?.map((child: Readonly<TreeChild>) => ({
4421
edgeData: child.edgeData,
45-
node: _computeLeftShiftLayout(child.node, depth + 1, layout),
22+
node: _computeLeftShiftLayout(child.node, depth + 1, counters),
4623
})),
47-
layout: { plan: { x, y: depth } },
24+
meta: {
25+
isRoot: depth === 0,
26+
isLeaf: tree.children === undefined || tree.children.length === 0,
27+
abstractPosition: { x, y: depth },
28+
},
4829
} satisfies TreeWithLayout
4930
}
5031

src/tests/layouts.test.ts

Lines changed: 95 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ describe('computeLeftShiftLayout', () => {
88
})
99
expect(resultWithoutChildren).toEqual({
1010
data: { v: 42 },
11-
layout: { plan: { x: 0, y: 0 } },
11+
meta: {
12+
isRoot: true,
13+
isLeaf: true,
14+
abstractPosition: { x: 0, y: 0 },
15+
},
1216
})
1317

1418
const resultWithEmptyChildren = computeLeftShiftLayout({
@@ -18,7 +22,11 @@ describe('computeLeftShiftLayout', () => {
1822
expect(resultWithEmptyChildren).toEqual({
1923
data: { v: 42 },
2024
children: [],
21-
layout: { plan: { x: 0, y: 0 } },
25+
meta: {
26+
isRoot: true,
27+
isLeaf: true,
28+
abstractPosition: { x: 0, y: 0 },
29+
},
2230
})
2331
})
2432

@@ -40,11 +48,19 @@ describe('computeLeftShiftLayout', () => {
4048
edgeData: {},
4149
node: {
4250
data: { v: 43 },
43-
layout: { plan: { x: 0, y: 1 } },
51+
meta: {
52+
isRoot: false,
53+
isLeaf: true,
54+
abstractPosition: { x: 0, y: 1 },
55+
},
4456
},
4557
},
4658
],
47-
layout: { plan: { x: 0, y: 0 } },
59+
meta: {
60+
isRoot: true,
61+
isLeaf: false,
62+
abstractPosition: { x: 0, y: 0 },
63+
},
4864
})
4965
})
5066

@@ -70,18 +86,30 @@ describe('computeLeftShiftLayout', () => {
7086
edgeData: {},
7187
node: {
7288
data: { v: 43 },
73-
layout: { plan: { x: 0, y: 1 } },
89+
meta: {
90+
isRoot: false,
91+
isLeaf: true,
92+
abstractPosition: { x: 0, y: 1 },
93+
},
7494
},
7595
},
7696
{
7797
edgeData: {},
7898
node: {
7999
data: { v: 44 },
80-
layout: { plan: { x: 1, y: 1 } },
100+
meta: {
101+
isRoot: false,
102+
isLeaf: true,
103+
abstractPosition: { x: 1, y: 1 },
104+
},
81105
},
82106
},
83107
],
84-
layout: { plan: { x: 0, y: 0 } },
108+
meta: {
109+
isRoot: true,
110+
isLeaf: false,
111+
abstractPosition: { x: 0, y: 0 },
112+
},
85113
})
86114
})
87115

@@ -132,18 +160,30 @@ describe('computeLeftShiftLayout', () => {
132160
edgeData: {},
133161
node: {
134162
data: { v: 45 },
135-
layout: { plan: { x: 0, y: 2 } },
163+
meta: {
164+
isRoot: false,
165+
isLeaf: true,
166+
abstractPosition: { x: 0, y: 2 },
167+
},
136168
},
137169
},
138170
{
139171
edgeData: {},
140172
node: {
141173
data: { v: 46 },
142-
layout: { plan: { x: 1, y: 2 } },
174+
meta: {
175+
isRoot: false,
176+
isLeaf: true,
177+
abstractPosition: { x: 1, y: 2 },
178+
},
143179
},
144180
},
145181
],
146-
layout: { plan: { x: 0, y: 1 } },
182+
meta: {
183+
isRoot: false,
184+
isLeaf: false,
185+
abstractPosition: { x: 0, y: 1 },
186+
},
147187
},
148188
},
149189
{
@@ -155,15 +195,27 @@ describe('computeLeftShiftLayout', () => {
155195
edgeData: {},
156196
node: {
157197
data: { v: 47 },
158-
layout: { plan: { x: 2, y: 2 } },
198+
meta: {
199+
isRoot: false,
200+
isLeaf: true,
201+
abstractPosition: { x: 2, y: 2 },
202+
},
159203
},
160204
},
161205
],
162-
layout: { plan: { x: 1, y: 1 } },
206+
meta: {
207+
isRoot: false,
208+
isLeaf: false,
209+
abstractPosition: { x: 1, y: 1 },
210+
},
163211
},
164212
},
165213
],
166-
layout: { plan: { x: 0, y: 0 } },
214+
meta: {
215+
isRoot: true,
216+
isLeaf: false,
217+
abstractPosition: { x: 0, y: 0 },
218+
},
167219
})
168220
})
169221

@@ -214,11 +266,19 @@ describe('computeLeftShiftLayout', () => {
214266
edgeData: {},
215267
node: {
216268
data: { v: 45 },
217-
layout: { plan: { x: 0, y: 2 } },
269+
meta: {
270+
isRoot: false,
271+
isLeaf: true,
272+
abstractPosition: { x: 0, y: 2 },
273+
},
218274
},
219275
},
220276
],
221-
layout: { plan: { x: 0, y: 1 } },
277+
meta: {
278+
isRoot: false,
279+
isLeaf: false,
280+
abstractPosition: { x: 0, y: 1 },
281+
},
222282
},
223283
},
224284
{
@@ -230,22 +290,38 @@ describe('computeLeftShiftLayout', () => {
230290
edgeData: {},
231291
node: {
232292
data: { v: 46 },
233-
layout: { plan: { x: 1, y: 2 } },
293+
meta: {
294+
isRoot: false,
295+
isLeaf: true,
296+
abstractPosition: { x: 1, y: 2 },
297+
},
234298
},
235299
},
236300
{
237301
edgeData: {},
238302
node: {
239303
data: { v: 47 },
240-
layout: { plan: { x: 2, y: 2 } },
304+
meta: {
305+
isRoot: false,
306+
isLeaf: true,
307+
abstractPosition: { x: 2, y: 2 },
308+
},
241309
},
242310
},
243311
],
244-
layout: { plan: { x: 1, y: 1 } },
312+
meta: {
313+
isRoot: false,
314+
isLeaf: false,
315+
abstractPosition: { x: 1, y: 1 },
316+
},
245317
},
246318
},
247319
],
248-
layout: { plan: { x: 0, y: 0 } },
320+
meta: {
321+
isRoot: true,
322+
isLeaf: false,
323+
abstractPosition: { x: 0, y: 0 },
324+
},
249325
})
250326
})
251327
})

src/tests/traversal.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ describe('postOrderIterator', () => {
4343
const expandedResult = [...result]
4444

4545
expect(expandedResult).toEqual([
46-
{ v: 45 },
47-
{ v: 43 },
48-
{ v: 46 },
49-
{ v: 47 },
50-
{ v: 44 },
51-
{ v: 42 },
46+
{ data: { v: 45 } },
47+
{ data: { v: 43 } },
48+
{ data: { v: 46 } },
49+
{ data: { v: 47 } },
50+
{ data: { v: 44 } },
51+
{ data: { v: 42 } },
5252
])
5353
})
5454
})

src/traversal.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import type { Tree, TreeWithLayout } from './layouts'
1+
import type { Tree, TreeWithLayout } from './types'
22

33
export function* postOrderIterator<T extends Tree | TreeWithLayout = Tree>(
44
v: Readonly<T>,
5-
): Generator<T['data'], void> {
5+
): Generator<Omit<Readonly<T>, 'children'>, void> {
66
for (const child of v.children ?? []) {
77
yield* postOrderIterator<T>(child.node as T)
88
}
9-
yield v.data
9+
10+
// We'll discard children, but we don't directly access v.data because we
11+
// might also want to access v.layout in case it exists.
12+
13+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
14+
const { children, ...vv } = v
15+
yield vv
1016
}

src/types.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export interface TreeChild<T extends Tree = Tree> {
2+
readonly node: T
3+
readonly edgeData?: Readonly<Record<string, unknown>> | undefined
4+
}
5+
6+
export interface Node {
7+
readonly data?: Readonly<Record<string, unknown>> | undefined
8+
}
9+
10+
export type Tree = Node & {
11+
readonly children?: readonly TreeChild[] | undefined
12+
}
13+
14+
export type TreeWithLayout = Node & {
15+
readonly children?: Required<TreeChild<TreeWithLayout>>[] | undefined
16+
readonly meta: {
17+
readonly isRoot: boolean
18+
readonly isLeaf: boolean
19+
readonly abstractPosition: {
20+
readonly x: number
21+
readonly y: number
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)