Skip to content

Commit f30f452

Browse files
add node visx
1 parent 842d9e3 commit f30f452

File tree

7 files changed

+564
-189
lines changed

7 files changed

+564
-189
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import React, { useState } from "react";
2+
import styled from "@emotion/styled";
3+
import { GearIcon, Cross1Icon } from "@radix-ui/react-icons";
4+
import { IconToggleButton } from "@code-editor/ui";
5+
6+
type Props = {
7+
layout: string;
8+
orientation: string;
9+
linkType: string;
10+
stepPercent: number;
11+
setLayout: (layout: string) => void;
12+
setOrientation: (orientation: string) => void;
13+
setLinkType: (linkType: string) => void;
14+
setStepPercent: (percent: number) => void;
15+
};
16+
17+
export default function LinkControls({
18+
layout,
19+
orientation,
20+
linkType,
21+
stepPercent,
22+
setLayout,
23+
setOrientation,
24+
setLinkType,
25+
setStepPercent,
26+
}: Props) {
27+
const [isExpanded, setIsExpanded] = useState(false);
28+
29+
return (
30+
<ControlContainer data-expanded={isExpanded}>
31+
<div
32+
className="controls"
33+
style={{
34+
display: isExpanded ? undefined : "none",
35+
}}
36+
>
37+
<div className="control">
38+
<label>layout</label>
39+
<select
40+
onClick={(e) => e.stopPropagation()}
41+
onChange={(e) => setLayout(e.target.value)}
42+
value={layout}
43+
>
44+
<option value="cartesian">cartesian</option>
45+
<option value="polar">polar</option>
46+
</select>
47+
</div>
48+
<div className="control">
49+
<label>orientation</label>
50+
<select
51+
onClick={(e) => e.stopPropagation()}
52+
onChange={(e) => setOrientation(e.target.value)}
53+
value={orientation}
54+
disabled={layout === "polar"}
55+
>
56+
<option value="vertical">vertical</option>
57+
<option value="horizontal">horizontal</option>
58+
</select>
59+
</div>
60+
<div className="control">
61+
<label>link</label>
62+
<select
63+
onClick={(e) => e.stopPropagation()}
64+
onChange={(e) => setLinkType(e.target.value)}
65+
value={linkType}
66+
>
67+
<option value="diagonal">diagonal</option>
68+
<option value="step">step</option>
69+
<option value="curve">curve</option>
70+
<option value="line">line</option>
71+
</select>
72+
</div>
73+
{linkType === "step" && layout !== "polar" && (
74+
<div className="control">
75+
<label>step</label>
76+
<input
77+
onClick={(e) => e.stopPropagation()}
78+
type="range"
79+
min={0}
80+
max={1}
81+
step={0.1}
82+
onChange={(e) => setStepPercent(Number(e.target.value))}
83+
value={stepPercent}
84+
disabled={linkType !== "step" || layout === "polar"}
85+
/>
86+
</div>
87+
)}
88+
</div>
89+
<div style={{ marginLeft: 16 }}>
90+
<IconToggleButton
91+
on={<Cross1Icon />}
92+
off={<GearIcon />}
93+
onChange={setIsExpanded}
94+
/>
95+
</div>
96+
</ControlContainer>
97+
);
98+
}
99+
100+
const ControlContainer = styled.div`
101+
position: absolute;
102+
top: 16px;
103+
right: 16px;
104+
105+
display: flex;
106+
padding: 16px;
107+
108+
border-radius: 4px;
109+
border: 1px solid rgba(255, 255, 255, 0.1);
110+
background-color: rgba(0, 0, 0, 0.5);
111+
backdrop-filter: blur(16px);
112+
font-size: 10px;
113+
color: white;
114+
115+
&[data-expanded="false"] {
116+
border: none;
117+
background-color: transparent;
118+
}
119+
120+
.controls {
121+
display: flex;
122+
align-items: center;
123+
justify-content: center;
124+
gap: 8px;
125+
}
126+
127+
.control {
128+
display: flex;
129+
flex-direction: column;
130+
align-items: flex-start;
131+
gap: 4px;
132+
}
133+
`;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { ComponentType } from "react";
2+
import {
3+
LinkHorizontal,
4+
LinkVertical,
5+
LinkRadial,
6+
LinkHorizontalStep,
7+
LinkVerticalStep,
8+
LinkRadialStep,
9+
LinkHorizontalCurve,
10+
LinkVerticalCurve,
11+
LinkRadialCurve,
12+
LinkHorizontalLine,
13+
LinkVerticalLine,
14+
LinkRadialLine,
15+
} from "@visx/shape";
16+
17+
export default function getLinkComponent({
18+
layout,
19+
linkType,
20+
orientation,
21+
}: {
22+
layout: string;
23+
linkType: string;
24+
orientation: string;
25+
}): ComponentType<any> {
26+
let LinkComponent: ComponentType<any>;
27+
28+
if (layout === "polar") {
29+
if (linkType === "step") {
30+
LinkComponent = LinkRadialStep;
31+
} else if (linkType === "curve") {
32+
LinkComponent = LinkRadialCurve;
33+
} else if (linkType === "line") {
34+
LinkComponent = LinkRadialLine;
35+
} else {
36+
LinkComponent = LinkRadial;
37+
}
38+
} else if (orientation === "vertical") {
39+
if (linkType === "step") {
40+
LinkComponent = LinkVerticalStep;
41+
} else if (linkType === "curve") {
42+
LinkComponent = LinkVerticalCurve;
43+
} else if (linkType === "line") {
44+
LinkComponent = LinkVerticalLine;
45+
} else {
46+
LinkComponent = LinkVertical;
47+
}
48+
} else if (linkType === "step") {
49+
LinkComponent = LinkHorizontalStep;
50+
} else if (linkType === "curve") {
51+
LinkComponent = LinkHorizontalCurve;
52+
} else if (linkType === "line") {
53+
LinkComponent = LinkHorizontalLine;
54+
} else {
55+
LinkComponent = LinkHorizontal;
56+
}
57+
return LinkComponent;
58+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import React, { useState } from "react";
2+
import { Group } from "@visx/group";
3+
import { hierarchy, Tree } from "@visx/hierarchy";
4+
import { LinearGradient } from "@visx/gradient";
5+
import { pointRadial } from "d3-shape";
6+
import LinkControls from "./control";
7+
import getLinkComponent from "./get-link-component";
8+
9+
interface TreeNode {
10+
name: string;
11+
isExpanded?: boolean;
12+
children?: TreeNode[];
13+
}
14+
15+
const defaultMargin = { top: 30, left: 30, right: 30, bottom: 70 };
16+
17+
export type LinkTypesProps = {
18+
data: TreeNode;
19+
width: number;
20+
height: number;
21+
margin?: { top: number; right: number; bottom: number; left: number };
22+
};
23+
24+
export function NodeGraph({
25+
data,
26+
width: totalWidth,
27+
height: totalHeight,
28+
margin = defaultMargin,
29+
}: LinkTypesProps) {
30+
const [layout, setLayout] = useState<string>("cartesian");
31+
const [orientation, setOrientation] = useState<string>("horizontal");
32+
const [linkType, setLinkType] = useState<string>("diagonal");
33+
const [stepPercent, setStepPercent] = useState<number>(0.5);
34+
35+
const innerWidth = totalWidth - margin.left - margin.right;
36+
const innerHeight = totalHeight - margin.top - margin.bottom;
37+
38+
let origin: { x: number; y: number };
39+
let sizeWidth: number;
40+
let sizeHeight: number;
41+
42+
if (layout === "polar") {
43+
origin = {
44+
x: innerWidth / 2,
45+
y: innerHeight / 2,
46+
};
47+
sizeWidth = 2 * Math.PI;
48+
sizeHeight = Math.min(innerWidth, innerHeight) / 2;
49+
} else {
50+
origin = { x: 0, y: 0 };
51+
if (orientation === "vertical") {
52+
sizeWidth = innerWidth;
53+
sizeHeight = innerHeight;
54+
} else {
55+
sizeWidth = innerHeight;
56+
sizeHeight = innerWidth;
57+
}
58+
}
59+
60+
const LinkComponent = getLinkComponent({ layout, linkType, orientation });
61+
62+
return totalWidth < 10 ? null : (
63+
<div style={{ position: "relative" }}>
64+
<LinkControls
65+
layout={layout}
66+
orientation={orientation}
67+
linkType={linkType}
68+
stepPercent={stepPercent}
69+
setLayout={setLayout}
70+
setOrientation={setOrientation}
71+
setLinkType={setLinkType}
72+
setStepPercent={setStepPercent}
73+
/>
74+
<svg width={totalWidth} height={totalHeight}>
75+
<LinearGradient id="links-gradient" from="#fd9b93" to="#fe6e9e" />
76+
<rect width={totalWidth} height={totalHeight} fill="transparent" />
77+
<Group top={margin.top} left={margin.left}>
78+
<Tree
79+
root={hierarchy(data, (d) => (d.isExpanded ? null : d.children))}
80+
size={[sizeWidth, sizeHeight]}
81+
separation={(a, b) => (a.parent === b.parent ? 1 : 0.5) / a.depth}
82+
>
83+
{(tree) => (
84+
<Group top={origin.y} left={origin.x}>
85+
{tree.links().map((link, i) => (
86+
<LinkComponent
87+
key={i}
88+
data={link}
89+
percent={stepPercent}
90+
stroke="rgb(254,110,158,0.6)"
91+
strokeWidth="1"
92+
fill="none"
93+
/>
94+
))}
95+
96+
{tree.descendants().map((node, key) => {
97+
const width = 40;
98+
const height = 20;
99+
100+
let top: number;
101+
let left: number;
102+
if (layout === "polar") {
103+
const [radialX, radialY] = pointRadial(node.x, node.y);
104+
top = radialY;
105+
left = radialX;
106+
} else if (orientation === "vertical") {
107+
top = node.y;
108+
left = node.x;
109+
} else {
110+
top = node.x;
111+
left = node.y;
112+
}
113+
114+
return (
115+
<Group top={top} left={left} key={key}>
116+
{node.depth === 0 && (
117+
<circle
118+
r={12}
119+
fill="url('#links-gradient')"
120+
// onClick={() => {
121+
// node.data.isExpanded = !node.data.isExpanded;
122+
// console.log(node);
123+
// forceUpdate();
124+
// }}
125+
/>
126+
)}
127+
{node.depth !== 0 && (
128+
<rect
129+
height={height}
130+
width={width}
131+
y={-height / 2}
132+
x={-width / 2}
133+
fill="#272b4d"
134+
stroke={node.data.children ? "#03c0dc" : "#26deb0"}
135+
strokeWidth={1}
136+
strokeDasharray={node.data.children ? "0" : "2,2"}
137+
strokeOpacity={node.data.children ? 1 : 0.6}
138+
rx={node.data.children ? 0 : 10}
139+
// onClick={() => {
140+
// node.data.isExpanded = !node.data.isExpanded;
141+
// console.log(node);
142+
// forceUpdate();
143+
// }}
144+
/>
145+
)}
146+
<text
147+
dy=".33em"
148+
fontSize={9}
149+
fontFamily="Arial"
150+
textAnchor="middle"
151+
style={{ pointerEvents: "none" }}
152+
fill={
153+
node.depth === 0
154+
? "#71248e"
155+
: node.children
156+
? "white"
157+
: "#26deb0"
158+
}
159+
>
160+
{node.data.name}
161+
</text>
162+
</Group>
163+
);
164+
})}
165+
</Group>
166+
)}
167+
</Tree>
168+
</Group>
169+
</svg>
170+
</div>
171+
);
172+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from "./tree-view";
2+
export * from "./graph";

0 commit comments

Comments
 (0)