Skip to content

Commit c1b9f12

Browse files
authored
feat: dynamic css classes (#16)
- Fixes #14 : now we can dynamically compute css classes for nodes and edges based on their metadata - the bundle size has been optimised a bit
2 parents 08c1fa7 + 08ccfb1 commit c1b9f12

File tree

11 files changed

+352
-291
lines changed

11 files changed

+352
-291
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-
[*.{js,cjs,mjs,json,ts,cjs,mts,jsx,tsx}]
7+
[*.{css,js,cjs,mjs,json,ts,cjs,mts,jsx,tsx}]
88
charset = utf-8
99
indent_style = tab
1010
indent_size = 2

.storybook/preview.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { Preview } from "@storybook/react";
22

3+
import './stories.css'
4+
35
const preview: Preview = {
46
parameters: {
57
actions: { argTypesRegex: "^on[A-Z].*" },

.storybook/stories.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.even {
2+
fill: red;
3+
}
4+
5+
.odd {
6+
stroke-dasharray: 1.5 0.5;
7+
}

rollup.config.prod.mjs

Lines changed: 74 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,87 @@
11
import { defineConfig } from 'rollup'
2-
import pluginTs from '@rollup/plugin-typescript'
32
import dts from 'rollup-plugin-dts'
3+
import pluginTs from '@rollup/plugin-typescript'
44
import terser from '@rollup/plugin-terser'
55

66
const input = 'src/BeautifulTree.tsx'
77
const external = ['react', 'react-dom', 'react/jsx-runtime']
88
const globals = {
99
react: 'React',
10-
'react-dom': 'ReactDOM',
10+
'react-dom': 'ReactDOM',
1111
'react/jsx-runtime': 'jsxRuntime',
1212
}
1313

1414
export default defineConfig([
15-
{
16-
input,
17-
output: [
18-
{
19-
file: 'dist/beautiful-tree.cjs',
20-
format: 'cjs',
21-
globals,
22-
sourcemap: true
23-
},
24-
{
25-
file: 'dist/beautiful-tree.mjs',
26-
format: 'es',
27-
globals,
28-
sourcemap: true
29-
},
30-
{
31-
name: 'BeautifulTree',
32-
file: 'dist/beautiful-tree.iife.js',
33-
format: 'iife',
34-
globals,
35-
sourcemap: true
36-
},
37-
{
38-
name: 'BeautifulTree',
39-
file: 'dist/beautiful-tree.umd.js',
40-
format: 'umd',
41-
globals,
42-
sourcemap: true,
43-
},
44-
],
45-
external,
46-
plugins: [pluginTs()]
47-
},
48-
{
49-
input,
50-
output: [
51-
{
52-
file: 'dist/beautiful-tree.min.cjs',
53-
format: 'cjs',
54-
globals,
55-
sourcemap: true,
56-
},
57-
{
58-
file: 'dist/beautiful-tree.min.mjs',
59-
format: 'es',
60-
globals,
61-
sourcemap: true,
62-
},
63-
{
64-
name: 'BeautifulTree',
65-
file: 'dist/beautiful-tree.min.iife.js',
66-
format: 'iife',
67-
globals,
68-
sourcemap: true,
69-
},
70-
{
71-
name: 'BeautifulTree',
72-
file: 'dist/beautiful-tree.min.umd.js',
73-
format: 'umd',
74-
globals,
75-
sourcemap: true,
76-
},
77-
],
78-
external,
79-
plugins: [pluginTs(), terser()]
80-
},
81-
{
82-
input,
83-
output: [
84-
{ file: 'dist/beautiful-tree.d.ts' },
85-
],
86-
external,
87-
plugins: [dts()]
88-
},
15+
{
16+
input,
17+
output: [
18+
{
19+
file: 'dist/beautiful-tree.cjs',
20+
format: 'cjs',
21+
globals,
22+
sourcemap: true,
23+
},
24+
{
25+
file: 'dist/beautiful-tree.mjs',
26+
format: 'es',
27+
globals,
28+
sourcemap: true,
29+
},
30+
{
31+
name: 'BeautifulTree',
32+
file: 'dist/beautiful-tree.iife.js',
33+
format: 'iife',
34+
globals,
35+
sourcemap: true,
36+
},
37+
{
38+
name: 'BeautifulTree',
39+
file: 'dist/beautiful-tree.umd.js',
40+
format: 'umd',
41+
globals,
42+
sourcemap: true,
43+
},
44+
],
45+
external,
46+
plugins: [pluginTs()],
47+
},
48+
{
49+
input,
50+
output: [
51+
{
52+
file: 'dist/beautiful-tree.min.cjs',
53+
format: 'cjs',
54+
globals,
55+
sourcemap: true,
56+
},
57+
{
58+
file: 'dist/beautiful-tree.min.mjs',
59+
format: 'es',
60+
globals,
61+
sourcemap: true,
62+
},
63+
{
64+
name: 'BeautifulTree',
65+
file: 'dist/beautiful-tree.min.iife.js',
66+
format: 'iife',
67+
globals,
68+
sourcemap: true,
69+
},
70+
{
71+
name: 'BeautifulTree',
72+
file: 'dist/beautiful-tree.min.umd.js',
73+
format: 'umd',
74+
globals,
75+
sourcemap: true,
76+
},
77+
],
78+
external,
79+
plugins: [pluginTs(), terser()],
80+
},
81+
{
82+
input,
83+
output: [{ file: 'dist/beautiful-tree.d.ts' }],
84+
external,
85+
plugins: [dts()],
86+
},
8987
])

src/BeautifulTree.tsx

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { edgesIterator, postOrderIterator } from './traversal'
22
import type { Tree } from './types'
33
import type { WrappedTreeWithLayout } from './layouts'
4-
export { computeLeftShiftLayout, computeCenter3Layout } from './layouts'
4+
export { computeNaiveLayout, computeSmartLayout } from './layouts'
5+
6+
export type CssClassesGetter = (
7+
data?: Readonly<Record<string, unknown>> | undefined,
8+
) => string[]
59

610
export interface BeautifulTreeProps {
711
readonly id: string
@@ -14,19 +18,31 @@ export interface BeautifulTreeProps {
1418
readonly computeLayout: (
1519
tree: Readonly<Tree>,
1620
) => Readonly<WrappedTreeWithLayout>
21+
readonly getNodeClass?: CssClassesGetter | undefined
22+
readonly getEdgeClass?: CssClassesGetter | undefined
23+
}
24+
25+
function runClassesGetter(
26+
classesGetter?: CssClassesGetter | undefined,
27+
data?: Readonly<Record<string, unknown>> | undefined,
28+
): string {
29+
const cssClasses = classesGetter?.(data) ?? []
30+
return cssClasses.length === 0 ? '' : ` ${cssClasses.join(' ')}`
1731
}
1832

1933
export function BeautifulTree({
2034
id,
2135
svgProps,
2236
tree,
2337
computeLayout,
38+
getNodeClass: nodeClassesInferrer,
39+
getEdgeClass: edgeClassesInferrer,
2440
}: Readonly<BeautifulTreeProps>): JSX.Element {
25-
const { tree: treeWithLayout, maxX, maxY } = computeLayout(tree)
41+
const { tree: treeWithLayout, mX, mY } = computeLayout(tree)
2642
const { width, height, sizeUnit = 'px' } = svgProps
2743

28-
const xCoef = width / (maxX + 2)
29-
const yCoef = height / (maxY + 2)
44+
const xCoef = width / (mX + 2)
45+
const yCoef = height / (mY + 2)
3046
const maxNodeWidth = xCoef * 0.25
3147
const maxNodeHeight = yCoef * 0.25
3248
const maxNodeRadius = Math.min(maxNodeWidth, maxNodeHeight)
@@ -42,34 +58,35 @@ export function BeautifulTree({
4258
}}
4359
className={'beautiful-tree-react'}
4460
>
45-
<style>{`
46-
line { stroke: black; }
47-
circle { stroke: black; fill: white; }
48-
`}</style>
49-
{/* TODO: introduce edge "styles" (straight, cornered, curved..., plus CSS styles) */}
50-
{[...edgesIterator(treeWithLayout)].map((edge, idx) => {
61+
<style>{'line{stroke:black;}circle{stroke:black;fill:white;}'}</style>
62+
{Array.from(edgesIterator(treeWithLayout), (edge, idx) => {
5163
return (
5264
<line
5365
key={`${id}-edge-${idx}`}
54-
className={'beautiful-tree-edge'}
66+
className={`beautiful-tree-edge${runClassesGetter(
67+
edgeClassesInferrer,
68+
edge.eData,
69+
)}`}
5570
x1={(edge.start.x + 1) * xCoef}
5671
y1={(edge.start.y + 1) * yCoef}
5772
x2={(edge.end.x + 1) * xCoef}
5873
y2={(edge.end.y + 1) * yCoef}
5974
/>
6075
)
6176
})}
62-
{[...postOrderIterator(treeWithLayout)].map((node, idx) => {
63-
const aX = node.meta.pos.x
64-
const aY = node.meta.pos.y
77+
{Array.from(postOrderIterator(treeWithLayout), (node, idx) => {
78+
const nm = node.meta
6579
return (
6680
<circle
6781
key={`${id}-node-${idx}`}
6882
className={`beautiful-tree-node${
69-
node.meta.isRoot ? ' beautiful-tree-root' : ''
70-
}${node.meta.isLeaf ? ' beautiful-tree-leaf' : ''}`}
71-
cx={(aX + 1) * xCoef}
72-
cy={(aY + 1) * yCoef}
83+
nm.isRoot ? ' beautiful-tree-root' : ''
84+
}${nm.isLeaf ? ' beautiful-tree-leaf' : ''}${runClassesGetter(
85+
nodeClassesInferrer,
86+
node.data,
87+
)}`}
88+
cx={(nm.pos.x + 1) * xCoef}
89+
cy={(nm.pos.y + 1) * yCoef}
7390
r={maxNodeRadius}
7491
/>
7592
)

0 commit comments

Comments
 (0)