Skip to content

Commit 6a4446b

Browse files
authored
feat: finish proper smart layout algorithm (#22)
- Fixes #15
2 parents c1b9f12 + 120551c commit 6a4446b

File tree

11 files changed

+20798
-873
lines changed

11 files changed

+20798
-873
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ root = true
44
end_of_line = lf
55
insert_final_newline = true
66

7-
[*.{css,js,cjs,mjs,json,ts,cjs,mts,jsx,tsx}]
7+
[*.{css,js,cjs,mjs,json,ts,cjs,mts,jsx,tsx,md}]
88
charset = utf-8
99
indent_style = tab
1010
indent_size = 2

README.md

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,146 @@
11
# Beautiful-Tree
22

3-
TODO
3+
<p align="center">
4+
<svg xmlns="http://www.w3.org/2000/svg" id="centered3-small-tree" viewBox="0 0 400 400" class="beautiful-tree-react" style="width: 300px; height: 300px;"><style>line{stroke:green;}
5+
circle,rect{stroke:green;fill:white;}
6+
div.beautiful-tree-node-content{margin:0;height:100%;width:100%;text-align:center;line-height:50px;font-size:25px;}</style><line class="beautiful-tree-edge" x1="175" y1="100" x2="100" y2="200"></line><line class="beautiful-tree-edge" x1="100" y1="200" x2="100" y2="300"></line><line class="beautiful-tree-edge" x1="175" y1="100" x2="250" y2="200"></line><line class="beautiful-tree-edge" x1="250" y1="200" x2="200" y2="300"></line><line class="beautiful-tree-edge" x1="250" y1="200" x2="300" y2="300"></line><circle class="beautiful-tree-node beautiful-tree-leaf" cx="100" cy="300" r="25"></circle><foreignObject x="75" y="275" width="50" height="50"><div class="beautiful-tree-node-content beautiful-tree-leaf">C</div></foreignObject><circle class="beautiful-tree-node" cx="100" cy="200" r="25"></circle><foreignObject x="75" y="175" width="50" height="50"><div class="beautiful-tree-node-content">B</div></foreignObject><circle class="beautiful-tree-node beautiful-tree-leaf" cx="200" cy="300" r="25"></circle><foreignObject x="175" y="275" width="50" height="50"><div class="beautiful-tree-node-content beautiful-tree-leaf">E</div></foreignObject><circle class="beautiful-tree-node beautiful-tree-leaf" cx="300" cy="300" r="25"></circle><foreignObject x="275" y="275" width="50" height="50"><div class="beautiful-tree-node-content beautiful-tree-leaf">F</div></foreignObject><circle class="beautiful-tree-node" cx="250" cy="200" r="25"></circle><foreignObject x="225" y="175" width="50" height="50"><div class="beautiful-tree-node-content">D</div></foreignObject><circle class="beautiful-tree-node beautiful-tree-root" cx="175" cy="100" r="25"></circle><foreignObject x="150" y="75" width="50" height="50"><div class="beautiful-tree-node-content beautiful-tree-root">A</div></foreignObject></svg>
7+
</p>
8+
9+
Beautiful-Tree is a lightweight & flexible library to draw trees as SVG images.
10+
11+
Some of its hightlights:
12+
- It is compatible with ESM, CJS, UMD and IIFE
13+
- Very lightweight (3.9Kb in its minimised ESM form, and 4.2Kb in its UMD form)
14+
- The generated trees can be styled with CSS
15+
16+
## Install
17+
18+
```bash
19+
# With NPM
20+
npm install @beautiful-tree/react
21+
22+
# With Yarn
23+
yarn add @beautiful-tree/react
24+
25+
# With PNPM
26+
pnpm add @beautiful-tree/react
27+
```
28+
29+
## Basic Usage
30+
31+
```jsx
32+
import { BeautifulTree } from '@beautiful-tree/react'
33+
34+
const tree = {
35+
data: { v: 'A' },
36+
children: [
37+
{
38+
node: {
39+
/* node data can contain any kews we want */
40+
data: { v: 'B' },
41+
children: [
42+
{
43+
/* we can annotate edges with arbitrary metadata */
44+
eData: { e: 0.5 },
45+
node: { data: { v: 'C' } }
46+
},
47+
],
48+
},
49+
},
50+
{
51+
node: {
52+
data: { v: 'D' },
53+
children: [
54+
{ node: { data: { v: 'E' } } },
55+
{ node: { data: { v: 'F' } } },
56+
],
57+
},
58+
},
59+
],
60+
}
61+
62+
// The 3 main properties that we must always set are:
63+
// - `id`: the id for the tree component
64+
// - `tree:`` the tree data structure that will be rendered
65+
// - `svgProps``: the proportions of the SVG "canvas".
66+
render(
67+
<BeautifulTree
68+
id={'my-tree'}
69+
tree={tree}
70+
svgProps={{
71+
width: 400,
72+
height: 400,
73+
// sizeUnit?: '%' | 'em' | 'px' | 'rem'
74+
}}
75+
/>
76+
)
77+
```
78+
79+
## Exposed CSS classes
80+
81+
- `beautiful-tree-react`: applies to the rendered SVG element.
82+
- `beautiful-tree-edge`: applies to all the rendered edges inside the SVG
83+
element.
84+
- `beautiful-tree-node`: applies to all the rendered nodes inside the SVG
85+
element.
86+
- `beautiful-tree-root`: applies only to the rendered _root_ node.
87+
- `beautiful-tree-leaf`: applies to all the rendered _leaf_ nodes inside the SVG
88+
element.
89+
- `beautiful-tree-node-content`: applies to all the `<div>` elements rendered
90+
inside nodes when using the [`getNodeContent`](#getnodecontent) prop.
91+
92+
## Other component props
93+
94+
We won't go into very deep details because TypeScript's autocomplete is enough
95+
to discover the aspects not mentioned here.
96+
97+
### `nodeShape`
98+
99+
Accepted values are `'circle'` and `'rect'`. It specifies which shape is used
100+
to render the tree nodes.
101+
102+
### `getNodeShape`
103+
104+
In case we want the shape of each node to depend on their associated metadata,
105+
we can pass a function that returns the desired shape for each individual node.
106+
107+
### `getNodeContent`
108+
109+
We can pass a function to read what's inside the `data` property of each node
110+
and return either a `string` value or a `JSX.Element` object that will be
111+
rendered inside the corresponding node.
112+
113+
### `computeLayout`
114+
115+
It specifies the function that is used to compute the tree layout.
116+
- By default it uses `computeSmartLayout`.
117+
- But we can also import a simpler layout `computeNaiveLayout`.
118+
119+
### `getNodeClass`
120+
121+
We can pass a function that takes each node object and returns a list of CSS
122+
classes that will be applied to it. This is useful if we want to make node
123+
styles depend on their associated data.
124+
125+
### `getEdgeClass`
126+
127+
We can pass a function that takes edge metadata as input and returns a list of
128+
CSS classes that will be applied to it. This is useful if we want to make edge
129+
styles depend on their associated data.
130+
131+
### `hCoef`
132+
133+
This parameter, mostly useful for the case when node's shape is `'rect'`, allows
134+
us to control the ratio aspect between height and width of a node. It must be
135+
between `0` and `1`, ideally above `0.5`.
136+
137+
## Future Plans
138+
139+
- Introduce a layout algorithm for dendrograms (with leafs all at the bottom
140+
level, instead of being at the level inmediately below their parents).
141+
- Introduce rotated versions of the tree layout (left-to-right, right-to-left,
142+
bottom-up)
143+
- Allow to use different edge "styles" between nodes (now it's just straight
144+
lines): splines, segmented lines with corners...
145+
- Release versions of this same library for other components systems, such as
146+
Vue, Svelte, Solidjs, and native Web Components.

package.json

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@beautiful-tree/react",
3-
"version": "0.0.1",
3+
"version": "0.1.0",
44
"private": false,
55
"author": "Andres Correa Casablanca <castarco@coderspirit.xyz>",
66
"license": "MIT",
@@ -43,30 +43,32 @@
4343
"@rollup/plugin-node-resolve": "^15.2.1",
4444
"@rollup/plugin-terser": "^0.4.3",
4545
"@rollup/plugin-typescript": "^11.1.3",
46-
"@storybook/addon-essentials": "^7.3.2",
47-
"@storybook/addon-interactions": "^7.3.2",
48-
"@storybook/addon-links": "^7.3.2",
49-
"@storybook/blocks": "^7.3.2",
50-
"@storybook/react": "^7.3.2",
51-
"@storybook/react-vite": "^7.3.2",
46+
"@storybook/addon-essentials": "^7.4.0",
47+
"@storybook/addon-interactions": "^7.4.0",
48+
"@storybook/addon-links": "^7.4.0",
49+
"@storybook/blocks": "^7.4.0",
50+
"@storybook/react": "^7.4.0",
51+
"@storybook/react-vite": "^7.4.0",
5252
"@storybook/testing-library": "^0.2.0",
53-
"@types/node": "^20.5.6",
53+
"@testing-library/react": "^14.0.0",
54+
"@types/node": "^20.5.8",
5455
"@types/react": "^18.2.21",
5556
"@types/react-dom": "^18.2.7",
56-
"@typescript-eslint/parser": "^6.4.1",
57+
"@typescript-eslint/parser": "^6.5.0",
5758
"@vitest/coverage-v8": "^0.34.3",
5859
"eslint": "^8.48.0",
5960
"eslint-plugin-react": "^7.33.2",
6061
"eslint-plugin-react-hooks": "^4.6.0",
6162
"eslint-plugin-react-refresh": "^0.4.3",
6263
"eslint-plugin-storybook": "^0.6.13",
63-
"prettier": "^3.0.2",
64+
"jsdom": "^22.1.0",
65+
"prettier": "^3.0.3",
6466
"publint": "^0.2.2",
6567
"react": "^18.2.0",
6668
"react-dom": "^18.2.0",
6769
"rollup": "^3.28.1",
6870
"rollup-plugin-dts": "^5.3.1",
69-
"storybook": "^7.3.2",
71+
"storybook": "^7.4.0",
7072
"typescript": "^5.2.2",
7173
"vitest": "^0.34.3"
7274
},

0 commit comments

Comments
 (0)