Skip to content

Commit 05e2548

Browse files
authored
feat: improve drawing (#11)
## New Features - Draw edges - Make it possible to set height & width units ## Internal - Refactor to reuse more code
2 parents 2687851 + ef16697 commit 05e2548

File tree

6 files changed

+360
-200
lines changed

6 files changed

+360
-200
lines changed

src/BeautifulTree.tsx

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,64 @@
1+
import { edgesIterator, postOrderIterator } from './traversal'
12
import type { Tree } from './types'
23
import { computeLeftShiftLayout } from './layouts'
3-
import { postOrderIterator } from './traversal'
44

55
export interface BeautifulTreeProps {
66
readonly id: string
77
readonly svgProps: {
88
readonly width: number
99
readonly height: number
10+
readonly sizeUnit?: '%' | 'em' | 'px' | 'rem'
1011
}
1112
readonly tree: Tree
1213
}
1314

14-
export function BeautifulTree(
15-
props: Readonly<BeautifulTreeProps>,
16-
): JSX.Element {
17-
const treeWithLayout = computeLeftShiftLayout(props.tree)
18-
const orderedNodes = [...postOrderIterator(treeWithLayout)]
15+
export function BeautifulTree({
16+
id,
17+
svgProps,
18+
tree,
19+
}: Readonly<BeautifulTreeProps>): JSX.Element {
20+
const { tree: treeWithLayout, maxX, maxY } = computeLeftShiftLayout(tree)
21+
const { width, height, sizeUnit = 'px' } = svgProps
1922

20-
let maxX = 0
21-
let maxY = 0
22-
for (const node of orderedNodes) {
23-
if (node.meta.abstractPosition.x > maxX) {
24-
maxX = node.meta.abstractPosition.x
25-
}
26-
if (node.meta.abstractPosition.y > maxY) {
27-
maxY = node.meta.abstractPosition.y
28-
}
29-
}
23+
const xDivisor = maxX + 2
24+
const yDivisor = maxY + 2
3025

3126
return (
3227
<svg
3328
xmlns="http://www.w3.org/2000/svg"
34-
id={props.id}
35-
viewBox={`0 0 ${props.svgProps.width} ${props.svgProps.height}`}
29+
id={id}
30+
viewBox={`0 0 ${width} ${height}`}
3631
style={{
37-
width: `${props.svgProps.width}px`,
38-
height: `${props.svgProps.height}px`,
32+
width: `${width}${sizeUnit}`,
33+
height: `${height}${sizeUnit}`,
3934
}}
4035
className={'beautiful-tree-react'}
4136
>
42-
{orderedNodes.map((node, idx) => {
37+
{/* TODO: introduce edge "styles" (straight, cornered, curved..., plus CSS styles) */}
38+
{[...edgesIterator(treeWithLayout)].map((edge, idx) => {
39+
return (
40+
<line
41+
key={`${id}-edge-${idx}`}
42+
x1={((edge.start.x + 1) * width) / xDivisor}
43+
y1={((edge.start.y + 1) * height) / yDivisor}
44+
x2={((edge.end.x + 1) * width) / xDivisor}
45+
y2={((edge.end.y + 1) * height) / yDivisor}
46+
stroke="black"
47+
/>
48+
)
49+
})}
50+
51+
{[...postOrderIterator(treeWithLayout)].map((node, idx) => {
4352
const aX = node.meta.abstractPosition.x
4453
const aY = node.meta.abstractPosition.y
4554
return (
4655
<circle
47-
key={`${props.id}-node-${idx}`}
48-
cx={((aX + 1) * props.svgProps.width) / (maxX + 2)}
49-
cy={((aY + 1) * props.svgProps.height) / (maxY + 2)}
50-
stroke="blue"
51-
fill="purple"
56+
key={`${id}-node-${idx}`}
57+
className={'beautiful-tree-node'}
58+
cx={((aX + 1) * width) / xDivisor}
59+
cy={((aY + 1) * height) / yDivisor}
60+
stroke="black"
61+
fill="white"
5262
r="5"
5363
/>
5464
)

src/layouts.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
import type { Tree, TreeChild, TreeWithLayout } from './types'
22

3+
export interface WrappedTreeWithLayout {
4+
readonly tree: Readonly<TreeWithLayout>
5+
readonly maxX: number
6+
readonly maxY: number
7+
}
8+
39
const _computeLeftShiftLayout = (
410
tree: Tree,
511
depth = 0,
612
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
7-
counters?: number[][],
8-
): TreeWithLayout => {
9-
counters ??= []
13+
counters: { layers: number[][]; maxX: number },
14+
): Readonly<TreeWithLayout> => {
15+
const layers = counters.layers
1016

11-
if (counters[depth] === undefined) {
12-
counters[depth] = []
17+
if (layers[depth] === undefined) {
18+
layers[depth] = []
1319
}
14-
const x = (counters[depth]?.at(-1) ?? -1) + 1
20+
const x = (layers[depth]?.at(-1) ?? -1) + 1
1521
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
16-
counters[depth]!.push(x)
22+
layers[depth]!.push(x)
23+
counters.maxX = Math.max(counters.maxX, x)
1724

1825
return {
1926
data: tree.data,
@@ -26,8 +33,16 @@ const _computeLeftShiftLayout = (
2633
isLeaf: tree.children === undefined || tree.children.length === 0,
2734
abstractPosition: { x, y: depth },
2835
},
29-
} satisfies TreeWithLayout
36+
} satisfies Readonly<TreeWithLayout>
3037
}
3138

32-
export const computeLeftShiftLayout: (tree: Tree) => TreeWithLayout =
33-
_computeLeftShiftLayout
39+
export const computeLeftShiftLayout = (
40+
tree: Readonly<Tree>,
41+
): Readonly<WrappedTreeWithLayout> => {
42+
const counters = { layers: [], maxX: 0 }
43+
return {
44+
tree: _computeLeftShiftLayout(tree, 0, counters),
45+
maxX: counters.maxX,
46+
maxY: counters.layers.length - 1,
47+
}
48+
}

0 commit comments

Comments
 (0)