Skip to content

Commit 898142c

Browse files
authored
feat(react): Allow Different Tree Orientations (#56)
This PR adds the feature of allowing different tree orientations mentioned in #24 . One thing to note is that since the while svg is rotated, so for Left - Right or Right -> Left orientations the width and height are flipped.
2 parents 452c5f9 + c85f6a0 commit 898142c

File tree

5 files changed

+16118
-14
lines changed

5 files changed

+16118
-14
lines changed

@beautiful-tree/react/src/BeautifulTree.tsx

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
import {
2+
computeAxesCoefAndNodeDimension,
3+
coordinateCreators,
4+
} from './treePosition'
15
import {
26
computeSmartLayout,
37
edgesIterator,
48
postOrderIterator,
59
} from '@beautiful-tree/algorithms'
610
import { Fragment } from 'react'
11+
import type { Orientation } from './treePosition'
712
import type { Tree } from '@beautiful-tree/types'
813
import type { WrappedTreeWithLayout } from '@beautiful-tree/algorithms'
914
export {
@@ -33,6 +38,7 @@ export interface BeautifulTreeProps {
3338
readonly nodeShape?: 'circle' | 'rect'
3439
readonly hCoef?: number
3540
readonly tree: Tree
41+
readonly orientation?: Orientation
3642
readonly computeLayout?:
3743
| ((tree: Readonly<Tree>) => Readonly<WrappedTreeWithLayout>)
3844
| undefined
@@ -63,6 +69,7 @@ export function BeautifulTree({
6369
nodeShape,
6470
hCoef = 1,
6571
tree,
72+
orientation = 'T-D',
6673
computeLayout,
6774
getNodeClass,
6875
getNodeShape,
@@ -74,14 +81,32 @@ export function BeautifulTree({
7481
const { tree: treeWithLayout, mX, mY } = computeLayout(tree)
7582
const { width, height, sizeUnit = 'px' } = svgProps
7683

77-
const xCoef = width / (mX + 2)
78-
const yCoef = height / (mY + 2)
79-
const maxNodeWidth = xCoef * 0.5
80-
const maxNodeHeight = Math.min(yCoef * 0.5, maxNodeWidth * hCoef)
84+
const { xCoef, yCoef, maxNodeHeight, maxNodeWidth } =
85+
computeAxesCoefAndNodeDimension(orientation, {
86+
width,
87+
height,
88+
hCoef,
89+
mX,
90+
mY,
91+
})
92+
8193
const widthCenterShift = maxNodeWidth * 0.5
8294
const heightCenterShift = maxNodeHeight * 0.5
8395
const maxNodeRadius = maxNodeHeight * 0.5
8496

97+
const {
98+
circleCoordinateCreator,
99+
lineCoordinateCreator,
100+
rectCoordinateCreator,
101+
} = coordinateCreators(orientation, {
102+
width,
103+
height,
104+
xCoef,
105+
yCoef,
106+
heightCenterShift,
107+
widthCenterShift,
108+
})
109+
85110
return (
86111
<svg
87112
xmlns="http://www.w3.org/2000/svg"
@@ -108,10 +133,7 @@ export function BeautifulTree({
108133
getEdgeClass,
109134
edge.eData,
110135
)}`}
111-
x1={(edge.start.x + 1) * xCoef}
112-
y1={(edge.start.y + 1) * yCoef}
113-
x2={(edge.end.x + 1) * xCoef}
114-
y2={(edge.end.y + 1) * yCoef}
136+
{...lineCoordinateCreator(edge)}
115137
/>
116138
)
117139
})}
@@ -134,25 +156,22 @@ export function BeautifulTree({
134156
getNodeClass,
135157
node.data,
136158
)}`}
137-
x={(nm.pos.x + 1) * xCoef - widthCenterShift}
138-
y={(nm.pos.y + 1) * yCoef - heightCenterShift}
159+
{...rectCoordinateCreator(nm)}
139160
width={maxNodeWidth}
140161
height={maxNodeHeight}
141162
/>
142163
) : (
143164
<circle
144165
key={`${id}-node-c-${idx}`}
145166
className={`beautiful-tree-node${_nodeClass}`}
146-
cx={(nm.pos.x + 1) * xCoef}
147-
cy={(nm.pos.y + 1) * yCoef}
167+
{...circleCoordinateCreator(nm)}
148168
r={maxNodeRadius}
149169
/>
150170
)}
151171
{getNodeContent ? (
152172
<foreignObject
153173
key={`${id}-node-fo-${idx}`}
154-
x={(nm.pos.x + 1) * xCoef - widthCenterShift}
155-
y={(nm.pos.y + 1) * yCoef - heightCenterShift}
174+
{...rectCoordinateCreator(nm)}
156175
width={maxNodeWidth}
157176
height={maxNodeHeight}
158177
>

@beautiful-tree/react/src/stories/BeautifulTree.stories.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,100 @@ export const Centered3_Wide_Tree_Random: Story = {
211211
getEdgeClass: getCssFromEdgeData,
212212
},
213213
}
214+
215+
export const Normal_Orientation_Small_Tree: Story = {
216+
args: {
217+
id: 'normal_orientation_small_tree',
218+
svgProps: {
219+
width: 400,
220+
height: 400,
221+
},
222+
tree: smallTree,
223+
orientation: 'T-D',
224+
getNodeContent: (data) => data?.['v']?.toString() ?? '',
225+
},
226+
}
227+
228+
export const LR_Orientation_Small_Tree: Story = {
229+
args: {
230+
id: 'lr_orientation_small_tree',
231+
svgProps: {
232+
width: 400,
233+
height: 400,
234+
},
235+
tree: smallTree,
236+
orientation: 'L-R',
237+
getNodeContent: (data) => data?.['v']?.toString() ?? '',
238+
},
239+
}
240+
241+
export const RL_Orientation_Small_Tree: Story = {
242+
args: {
243+
id: 'rl_orientation_small_tree',
244+
svgProps: {
245+
width: 400,
246+
height: 400,
247+
},
248+
tree: smallTree,
249+
orientation: 'R-L',
250+
computeLayout: computeSmartLayout,
251+
getNodeContent: (data) => data?.['v']?.toString() ?? '',
252+
},
253+
}
254+
255+
export const DT_Orientation_Small_Tree: Story = {
256+
args: {
257+
id: 'dt_orientation_small_tree',
258+
svgProps: {
259+
width: 400,
260+
height: 400,
261+
},
262+
tree: smallTree,
263+
orientation: 'D-T',
264+
computeLayout: computeSmartLayout,
265+
getNodeContent: (data) => data?.['v']?.toString() ?? '',
266+
},
267+
}
268+
269+
export const LR_Orientation_Wide_Tree_A_On_Rectangle: Story = {
270+
args: {
271+
id: 'lr_orientation_wide_tree_a_on_rectangle',
272+
svgProps: {
273+
width: 250,
274+
height: 450,
275+
},
276+
tree: wideTree_A,
277+
orientation: 'L-R',
278+
computeLayout: computeSmartLayout,
279+
getNodeClass: getCssFromNodeData,
280+
getEdgeClass: getCssFromEdgeData,
281+
},
282+
}
283+
284+
export const RL_Orientation_Wide_Tree_Bm_On_Rectangle: Story = {
285+
args: {
286+
id: 'rl_orientation_wide_tree_bm_on_rectangle',
287+
svgProps: {
288+
width: 250,
289+
height: 450,
290+
},
291+
tree: smallTree,
292+
orientation: 'R-L',
293+
computeLayout: computeSmartLayout,
294+
getNodeContent: (data) => data?.['v']?.toString() ?? '',
295+
},
296+
}
297+
298+
export const DT_Orientation_Wide_Tree_D_On_Rectangle: Story = {
299+
args: {
300+
id: 'dt_orientation_wide_tree_d_on_rectangle',
301+
svgProps: {
302+
width: 250,
303+
height: 450,
304+
},
305+
tree: smallTree,
306+
orientation: 'D-T',
307+
computeLayout: computeSmartLayout,
308+
getNodeContent: (data) => data?.['v']?.toString() ?? '',
309+
},
310+
}

@beautiful-tree/react/src/tests/BeautifulTree.test.tsx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,75 @@ describe('BeautifulTree : Smart Layout', () => {
225225

226226
expect(rendered).toMatchSnapshot()
227227
})
228+
229+
it('renders small tree, with T-D orientation', () => {
230+
const rendered = render(
231+
<BeautifulTree
232+
id="smart-small-tree_T-D-orientation"
233+
svgProps={{
234+
width: 400,
235+
height: 400,
236+
}}
237+
tree={smallTree}
238+
orientation="T-D"
239+
computeLayout={computeSmartLayout}
240+
/>,
241+
)
242+
243+
expect(rendered).toMatchSnapshot()
244+
})
245+
246+
it('renders small tree, with L-R orientation', () => {
247+
const rendered = render(
248+
<BeautifulTree
249+
id="smart-small-tree_L-R-orientation"
250+
svgProps={{
251+
width: 400,
252+
height: 400,
253+
}}
254+
tree={smallTree}
255+
orientation="L-R"
256+
computeLayout={computeSmartLayout}
257+
getNodeContent={(data): string => data?.['v']?.toString() ?? ''}
258+
/>,
259+
)
260+
261+
expect(rendered).toMatchSnapshot()
262+
})
263+
264+
it('renders small tree, with R-L orientation', () => {
265+
const rendered = render(
266+
<BeautifulTree
267+
id="smart-small-tree_R-L-orientation"
268+
svgProps={{
269+
width: 400,
270+
height: 400,
271+
}}
272+
tree={smallTree}
273+
orientation="R-L"
274+
computeLayout={computeSmartLayout}
275+
getNodeContent={(data): string => data?.['v']?.toString() ?? ''}
276+
/>,
277+
)
278+
279+
expect(rendered).toMatchSnapshot()
280+
})
281+
282+
it('renders small tree, with D-T orientation', () => {
283+
const rendered = render(
284+
<BeautifulTree
285+
id="smart-small-tree_D-T-orientation"
286+
svgProps={{
287+
width: 400,
288+
height: 400,
289+
}}
290+
tree={smallTree}
291+
orientation="D-T"
292+
computeLayout={computeSmartLayout}
293+
getNodeContent={(data): string => data?.['v']?.toString() ?? ''}
294+
/>,
295+
)
296+
297+
expect(rendered).toMatchSnapshot()
298+
})
228299
})

0 commit comments

Comments
 (0)