Skip to content

Commit 4c102eb

Browse files
committed
feat: center3 layout algorithm
now all "aesthetic axioms" are respected Signed-off-by: Andres Correa Casablanca <castarco@coderspirit.xyz>
1 parent 6cfd18f commit 4c102eb

File tree

3 files changed

+402
-18
lines changed

3 files changed

+402
-18
lines changed

src/BeautifulTree.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { edgesIterator, postOrderIterator } from './traversal'
22
import type { Tree } from './types'
33
import type { WrappedTreeWithLayout } from './layouts'
4-
export { computeLeftShiftLayout, computeCenter2Layout } from './layouts'
4+
export { computeLeftShiftLayout, computeCenter3Layout } from './layouts'
55

66
export interface BeautifulTreeProps {
77
readonly id: string

src/layouts.ts

Lines changed: 136 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,12 @@ const _computeCenter2Layout = (
6666
depth = 0,
6767
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
6868
offsets: number[],
69+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
70+
tracer: { maxX: number },
6971
): DeepWriteable<TreeWithLayout> => {
7072
const children = tree.children?.map((child) => ({
7173
edgeData: child.edgeData,
72-
node: _computeCenter2Layout(child.node, depth + 1, offsets),
74+
node: _computeCenter2Layout(child.node, depth + 1, offsets, tracer),
7375
}))
7476

7577
let x: number
@@ -89,6 +91,7 @@ const _computeCenter2Layout = (
8991
x = Math.max(offsets[depth] ?? 0, c)
9092
m = x - c
9193
}
94+
tracer.maxX = Math.max(tracer.maxX, x)
9295
offsets[depth] = 1 + x
9396

9497
return {
@@ -103,15 +106,144 @@ const _computeCenter2Layout = (
103106
} satisfies Readonly<TreeWithLayout> as DeepWriteable<TreeWithLayout>
104107
}
105108

106-
export const computeCenter2Layout = (
109+
const _inPlaceEvenSpacingUpdate = (
110+
numChildren: number,
111+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
112+
tree: DeepWriteable<TreeWithLayout>,
113+
shift: number,
114+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
115+
offsets: number[],
116+
depth: number,
117+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
118+
tracer: { maxX: number },
119+
): void => {
120+
if (numChildren === 0) {
121+
tree.meta.pos.x += shift
122+
tree.meta.m = 0
123+
} else if (numChildren === 1) {
124+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
125+
tree.meta.pos.x = tree.children![0]!.node.meta.pos.x
126+
tree.meta.m = 0
127+
} else {
128+
const c = // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
129+
(tree.children![0]!.node.meta.pos.x +
130+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
131+
tree.children![numChildren - 1]!.node.meta.pos.x) *
132+
0.5
133+
tree.meta.pos.x = Math.max(offsets[depth] ?? 0, c)
134+
}
135+
tracer.maxX = Math.max(tracer.maxX, tree.meta.pos.x)
136+
offsets[depth] = 1 + tree.meta.pos.x
137+
}
138+
139+
const _siblingsEvenSpacing = (
140+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
141+
tree: DeepWriteable<TreeWithLayout>,
142+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
143+
offsets: number[],
144+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
145+
tracer: { maxX: number },
146+
depth = 0,
147+
shift = 0,
148+
// eslint-disable-next-line sonarjs/cognitive-complexity
149+
): void => {
150+
const numChildren = tree.children?.length ?? 0
151+
let lastFixedIdx: number | undefined
152+
let maxSpacing = 1
153+
for (const [idx, child] of (tree.children ?? []).entries()) {
154+
const isFixed = (child.node.children?.length ?? 0) > 0
155+
if (isFixed) {
156+
if (lastFixedIdx !== undefined) {
157+
const spacing =
158+
(child.node.meta.pos.x -
159+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
160+
tree.children![lastFixedIdx]!.node.meta.pos.x) /
161+
(idx - lastFixedIdx)
162+
maxSpacing = Math.max(maxSpacing, spacing)
163+
}
164+
lastFixedIdx = idx
165+
}
166+
}
167+
168+
let accShift = shift
169+
for (const [idx, child] of (tree.children ?? []).entries()) {
170+
if (idx === 0) {
171+
if (numChildren > 1) {
172+
accShift = Math.max(
173+
0,
174+
shift +
175+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
176+
tree.children![1]!.node.meta.pos.x -
177+
maxSpacing -
178+
child.node.meta.pos.x,
179+
)
180+
}
181+
} else {
182+
accShift =
183+
shift +
184+
Math.max(
185+
0,
186+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
187+
tree.children![idx - 1]!.node.meta.pos.x +
188+
maxSpacing -
189+
child.node.meta.pos.x,
190+
)
191+
}
192+
_siblingsEvenSpacing(child.node, offsets, tracer, depth + 1, accShift)
193+
}
194+
195+
_inPlaceEvenSpacingUpdate(numChildren, tree, shift, offsets, depth, tracer)
196+
}
197+
198+
const _cousinsEvenSpacing = (
199+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
200+
tree: DeepWriteable<TreeWithLayout>,
201+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
202+
offsets: number[],
203+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
204+
tracer: { maxX: number },
205+
depth = 0,
206+
shift = 0,
207+
): void => {
208+
const numChildren = tree.children?.length ?? 0
209+
210+
const nextOffset = offsets[depth + 1]
211+
let accShift = shift
212+
if (
213+
numChildren >= 2 &&
214+
nextOffset !== undefined &&
215+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
216+
(tree.children![0]!.node.children?.length ?? 0) === 0
217+
) {
218+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
219+
const mid = tree.children![1]!.node.meta.pos.x - 1
220+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
221+
accShift = shift + Math.max(0, mid - tree.children![0]!.node.meta.pos.x)
222+
}
223+
224+
for (const [idx, child] of (tree.children ?? []).entries()) {
225+
if (idx === 0) {
226+
_cousinsEvenSpacing(child.node, offsets, tracer, depth + 1, accShift)
227+
} else {
228+
_cousinsEvenSpacing(child.node, offsets, tracer, depth + 1, shift)
229+
}
230+
}
231+
232+
_inPlaceEvenSpacingUpdate(numChildren, tree, shift, offsets, depth, tracer)
233+
}
234+
235+
export const computeCenter3Layout = (
107236
tree: Readonly<Tree>,
108237
): Readonly<WrappedTreeWithLayout> => {
109238
const offsets: number[] = []
110-
const t = _computeCenter2Layout(tree, 0, offsets)
111-
112239
const tracer = { maxX: 0 }
240+
241+
const t = _computeCenter2Layout(tree, 0, offsets, tracer)
113242
_addMods(t, 0, tracer)
114243

244+
_cousinsEvenSpacing(t, [], tracer)
245+
_siblingsEvenSpacing(t, [], tracer)
246+
115247
return {
116248
tree: t,
117249
maxY: offsets.length - 1,

0 commit comments

Comments
 (0)