start
This commit is contained in:
commit
c79fb7de78
11 changed files with 229 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
node_modules
|
17
biome.json
Normal file
17
biome.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentWidth": 2,
|
||||
"indentStyle": "space"
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
}
|
||||
}
|
BIN
bun.lockb
Executable file
BIN
bun.lockb
Executable file
Binary file not shown.
2
index.html
Normal file
2
index.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
<script type="module" src="./src/index.tsx"></script>
|
||||
<div id="root"></div>
|
16
package.json
Normal file
16
package.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"d3": "^7.9.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.8.3",
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react-swc": "^3.7.0",
|
||||
"sass": "^1.77.6",
|
||||
"vite": "^5.3.2"
|
||||
}
|
||||
}
|
21
src/App.module.scss
Normal file
21
src/App.module.scss
Normal file
|
@ -0,0 +1,21 @@
|
|||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
circle {
|
||||
fill: cadetblue;
|
||||
}
|
||||
|
||||
line {
|
||||
stroke: #ccc;
|
||||
}
|
||||
|
||||
text {
|
||||
text-anchor: middle;
|
||||
font-family: "Helvetica Neue", Helvetica, sans-serif;
|
||||
fill: #666;
|
||||
font-size: 16px;
|
||||
}
|
148
src/App.tsx
Normal file
148
src/App.tsx
Normal file
|
@ -0,0 +1,148 @@
|
|||
import * as d3 from "d3";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import styles from "./App.module.scss";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
interface NodeInfo extends d3.SimulationNodeDatum {
|
||||
name: string;
|
||||
}
|
||||
|
||||
const nodes: NodeInfo[] = [
|
||||
{ name: "A" },
|
||||
{ name: "B" },
|
||||
{ name: "C" },
|
||||
{ name: "D" },
|
||||
{ name: "E" },
|
||||
{ name: "F" },
|
||||
{ name: "G" },
|
||||
{ name: "H" },
|
||||
];
|
||||
|
||||
const links = [
|
||||
{ source: 0, target: 1 },
|
||||
{ source: 0, target: 2 },
|
||||
{ source: 0, target: 3 },
|
||||
{ source: 1, target: 6 },
|
||||
{ source: 3, target: 4 },
|
||||
{ source: 3, target: 7 },
|
||||
{ source: 4, target: 5 },
|
||||
{ source: 4, target: 7 },
|
||||
];
|
||||
|
||||
export default function App() {
|
||||
const svgRef = useRef();
|
||||
const simulationRef = useRef();
|
||||
|
||||
// Resize listener
|
||||
const [rect, setRect] = useState<DOMRectReadOnly | null>(null);
|
||||
useEffect(() => {
|
||||
new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
setRect(entry.contentRect);
|
||||
}
|
||||
}).observe(document.body);
|
||||
}, []);
|
||||
|
||||
const [simulationNodes, setSimulationNodes] = useState(() => nodes);
|
||||
const ticked = useCallback((simulation: d3.Simulation<NodeInfo, any>) => {
|
||||
const nodes = simulation.nodes();
|
||||
// setSimulationNodes(nodes);
|
||||
// d3.select("svg .links")
|
||||
// .selectAll("line")
|
||||
// .data(links)
|
||||
// .join("line")
|
||||
// .attr("x1", (d) => d.source.x)
|
||||
// .attr("x2", (d) => d.target.x)
|
||||
// .attr("y1", (d) => d.source.y)
|
||||
// .attr("y2", (d) => d.target.y);
|
||||
|
||||
d3.select("svg .nodes")
|
||||
.selectAll("rect")
|
||||
.data(nodes)
|
||||
.join("rect")
|
||||
// .append("foreignObject")
|
||||
.text((d) => d.name)
|
||||
.attr("x", (d) => d.x)
|
||||
.attr("y", (d) => d.y)
|
||||
.attr("dy", (d) => 5);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!rect) return;
|
||||
|
||||
const simulation = d3
|
||||
.forceSimulation<NodeInfo>(nodes)
|
||||
.force("charge", d3.forceManyBody())
|
||||
.force("link", d3.forceLink(links))
|
||||
.force("center", d3.forceCenter(rect.width / 2, rect.height / 2));
|
||||
|
||||
simulation.on("tick", () => ticked(simulation));
|
||||
|
||||
simulationRef.current = simulation;
|
||||
}, [ticked, rect]);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{rect && (
|
||||
<svg ref={svgRef} width={rect.width} height={rect.height}>
|
||||
<title>Hello</title>
|
||||
<g className="links" />
|
||||
<g className="nodes">
|
||||
{simulationNodes.map((node, idx) => {
|
||||
console.log("node", node);
|
||||
return (
|
||||
<text
|
||||
key={idx}
|
||||
x={(node.x ?? 0).toString()}
|
||||
y={(node.y ?? 0).toString()}
|
||||
>
|
||||
{node.name}
|
||||
</text>
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ForeignObjectWrapper({ id }) {
|
||||
const el = useMemo(() => {
|
||||
return document.getElementById(id);
|
||||
}, [id]);
|
||||
return createPortal(<Counter />, el);
|
||||
}
|
||||
|
||||
function Counter() {
|
||||
const [counter, setCounter] = useState(0);
|
||||
|
||||
//increase counter
|
||||
const increase = () => {
|
||||
setCounter((count) => count + 1);
|
||||
};
|
||||
|
||||
//decrease counter
|
||||
const decrease = () => {
|
||||
setCounter((count) => count - 1);
|
||||
};
|
||||
|
||||
//reset counter
|
||||
const reset = () => {
|
||||
setCounter(0);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="btn__container">
|
||||
<button className="control__btn" onClick={increase}>
|
||||
+
|
||||
</button>
|
||||
<button className="control__btn" onClick={decrease}>
|
||||
-
|
||||
</button>
|
||||
<button className="reset" onClick={reset}>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
8
src/global.scss
Normal file
8
src/global.scss
Normal file
|
@ -0,0 +1,8 @@
|
|||
html,
|
||||
body,
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
5
src/index.tsx
Normal file
5
src/index.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import App from "./App";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "./global.scss";
|
||||
|
||||
createRoot(document.getElementById("root")).render(<App />);
|
5
tsconfig.json
Normal file
5
tsconfig.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx"
|
||||
}
|
||||
}
|
6
vite.config.ts
Normal file
6
vite.config.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react-swc";
|
||||
|
||||
defineConfig({
|
||||
plugins: [react()],
|
||||
});
|
Loading…
Reference in a new issue