first draft

This commit is contained in:
2026-01-11 12:14:09 +01:00
parent 75c91276d0
commit 12d055b50d
14 changed files with 257 additions and 70 deletions
+2
View File
@@ -0,0 +1,2 @@
*.jpg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
+109 -55
View File
@@ -1,65 +1,119 @@
{
"nodes": [
{
"id": "1",
"module": 0,
"i": 0
"id": "Dios",
"image": "images/dios.jpg",
"group": "Pop/Alternative Rock",
"link": "https://www.youtube.com/@Dios_band"
},
{
"id": "3",
"module": 0,
"i": 1,
"image": "https://legonzaur.fr/Luka/condo/2026_01/20251109_020401.jpg"
"id": "The Home Team",
"image": "images/the home team.jpg",
"group": "Pop/Alternative Rock",
"link": "https://www.youtube.com/@TheHomeTeamOfficial"
},
{ "id": "8", "module": 2, "i": 2 },
{
"id": "9",
"module": 4,
"i": 3,
"image": "https://legonzaur.fr/Luka/condo/2026_01/20251109_020401.jpg"
"id": "Itchika Nito",
"image": "images/itchika nito.jpg",
"group": "Prog Rock",
"link": "https://www.youtube.com/@ichika_nito"
},
{ "id": "12", "module": 2, "i": 4 },
{ "id": "15", "module": 2, "i": 5 },
{ "id": "23", "module": 3, "i": 6 },
{ "id": "26", "module": 3, "i": 7 },
{ "id": "37", "module": 10, "i": 8 },
{ "id": "46", "module": 2, "i": 9 },
{ "id": "2", "module": 1, "i": 10 },
{ "id": "4", "module": 2, "i": 11 },
{ "id": "5", "module": 3, "i": 12 },
{ "id": "6", "module": 3, "i": 13 },
{ "id": "7", "module": 1, "i": 14 },
{ "id": "10", "module": 4, "i": 15 },
{ "id": "11", "module": 1, "i": 16 },
{ "id": "13", "module": 1, "i": 17 },
{ "id": "14", "module": 1, "i": 18 },
{ "id": "16", "module": 5, "i": 19 },
{ "id": "18", "module": 1, "i": 20 },
{ "id": "19", "module": 3, "i": 21 },
{ "id": "20", "module": 1, "i": 22 },
{ "id": "21", "module": 6, "i": 23 },
{ "id": "22", "module": 2, "i": 24 },
{ "id": "25", "module": 3, "i": 25 },
{ "id": "27", "module": 1, "i": 26 },
{ "id": "28", "module": 3, "i": 27 },
{ "id": "29", "module": 1, "i": 28 },
{ "id": "31", "module": 2, "i": 29 },
{ "id": "33", "module": 2, "i": 30 },
{ "id": "34", "module": 9, "i": 31 },
{ "id": "35", "module": 1, "i": 32 },
{ "id": "38", "module": 2, "i": 33 },
{ "id": "41", "module": 4, "i": 34 },
{ "id": "32", "module": 2, "i": 35 },
{ "id": "40", "module": 2, "i": 36 },
{ "id": "17", "module": 4, "i": 37 },
{ "id": "36", "module": 3, "i": 38 },
{ "id": "39", "module": 3, "i": 39 },
{ "id": "44", "module": 3, "i": 40 },
{ "id": "45", "module": 3, "i": 41 },
{ "id": "42", "module": 9, "i": 42 },
{ "id": "43", "module": 11, "i": 43 },
{ "id": "24", "module": 7, "i": 44 },
{ "id": "30", "module": 8, "i": 45 }
{
"id": "Bernth",
"image": "images/bernth.jpg",
"link": "https://www.youtube.com/@Bernthguitar"
},
{
"id": "Syncatto",
"image": "images/syncatto.png",
"link": "https://www.youtube.com/channel/UCs6hz1aQNlSBwAkxEGhx2-g"
},
{
"id": "Plini",
"image": "images/plini.png",
"link": "https://www.youtube.com/@plinimusic"
},
{
"id": "Tim Henson",
"image": "images/tim henson.png",
"link": "https://www.youtube.com/channel/UCdKj0VzVR0Af0wzd7-68Avw"
},
{
"id": "Polyphia",
"image": "images/polyphia.png",
"link": "https://www.youtube.com/c/Polyphia/videos"
},
{
"id": "Anomalie",
"image": "images/anomalie.png",
"link": "https://www.youtube.com/@anomaliebeats"
},
{
"id": "Cory Wong",
"image": "images/cory wong.png",
"link": "https://www.youtube.com/@CoryWongMusic/videos"
}
],
"edges": [{ "source": "1", "target": "3" }]
"edges": [
{
"source": "Itchika Nito",
"target": "Dios",
"relation": "Member",
"directional": true
},
{
"source": "Bernth",
"target": "Itchika Nito",
"relation": "Collab",
"link": "https://www.youtube.com/watch?v=sYnbnRuNLNQ"
},
{
"source": "Bernth",
"target": "Syncatto",
"relation": "Collab",
"link": "https://www.youtube.com/watch?v=9XnHJNKEcW0"
},
{
"source": "Tim Henson",
"target": "Itchika Nito",
"relation": "Has anyone seen them both in the same room ?"
},
{
"source": "Tim Henson",
"target": "Polyphia",
"relation": "Member",
"directional": true
},
{
"source": "Tim Henson",
"target": "Plini",
"relation": "Collab",
"link": "https://www.youtube.com/watch?v=DRPX8kI06v8"
},
{
"source": "Tim Henson",
"target": "Cory Wong",
"relation": "Collab",
"link": "https://www.youtube.com/watch?v=DRPX8kI06v8"
},
{
"source": "Plini",
"target": "Cory Wong",
"relation": "Collab",
"link": "https://www.youtube.com/watch?v=DRPX8kI06v8"
},
{
"source": "Anomalie",
"target": "Plini",
"relation": "Collab",
"link": "https://www.youtube.com/watch?v=M9JMXAYB2q8"
},
{
"source": "Anomalie",
"target": "Polyphia",
"relation": "Collab",
"link": "https://www.youtube.com/watch?v=FHalHkfM5Aw"
}
]
}
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+10 -1
View File
@@ -11,10 +11,19 @@
<meta name="description" content="" />
<link rel="icon" href="favicon.png" />
<style>
text {
pointer-events: none;
user-select: none;
}
image {
clip-path: inset(0% round 50%);
}
</style>
</head>
<body>
<svg width="1000" height="1000"></svg>
<svg width="1000" height="1000" viewBox="-500 -500 1000 1000"></svg>
<script src="dist/graph.js" type="module"></script>
</body>
</html>
+106 -14
View File
@@ -1,12 +1,22 @@
import {
BaseType,
drag,
DragBehavior,
forceCenter,
forceCollide,
forceLink,
forceManyBody,
forceSimulation,
forceX,
forceY,
json,
select,
selectAll,
Selection,
} from "d3";
const NODESIZE = 75;
const CONTAINERSIZE = 250;
interface Node {
id: string;
module: number;
@@ -14,11 +24,14 @@ interface Node {
x: number;
y: number;
image?: string;
radius?: number;
}
interface Edge {
source: Node;
target: Node;
relation?: string;
directional?: boolean;
}
interface Graph {
@@ -26,26 +39,39 @@ interface Graph {
edges: Edge[];
}
const graph = await json<Graph>("graph.json");
const rawGraph = await json<{ [key: string]: any[] }>("graph.json");
if (!graph) {
if (!rawGraph) {
throw Error("graph does not exists");
}
rawGraph.edges.forEach((e) => {
e.source = String(rawGraph.nodes.findIndex((n) => n.id == e.source));
e.target = String(rawGraph.nodes.findIndex((n) => n.id == e.target));
});
const graph = rawGraph as unknown as Graph;
const { nodes, edges } = graph;
const svg = select("svg");
const width = Number(svg.attr("width"));
const height = Number(svg.attr("height"));
// Define the force simulation
const simulation = forceSimulation(nodes)
.force(
"link",
forceLink(edges).id((d) => String(d.index))
forceLink(edges)
.id((d) => String(d.index))
.distance(NODESIZE * 1.7)
)
.force("charge", forceManyBody().strength(-100))
.force("center", forceCenter(width / 2, height / 2));
.force(
"charge",
forceManyBody()
.strength((d) => nodes[d.index!].radius || -2000)
.distanceMax(500)
)
.force("x", forceX())
.force("y", forceY());
// .force("center", forceCenter(0, 0).strength(0.5));
// Adding links
const link = svg
@@ -56,23 +82,77 @@ const link = svg
.data(edges)
.join("line");
const lineLegend = svg
.append("g")
.selectAll("line")
.data(edges)
.join("text")
.text((d) => d.relation || "")
.attr("text-anchor", "middle")
.attr("font-size", ".7em")
.attr("transform", "translate(0, 3)");
// Adding nodes
const node = svg
.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll("image")
.selectAll("g")
.data(nodes)
.join("image")
.attr("r", 5)
.attr("height", 50)
.attr("width", 50)
.join("svg")
.attr("width", CONTAINERSIZE)
.attr("height", CONTAINERSIZE)
.attr(
"viewBox",
`${-CONTAINERSIZE / 2 + NODESIZE / 2}
${-CONTAINERSIZE / 2 + NODESIZE / 2}
${CONTAINERSIZE} ${CONTAINERSIZE}`
);
node
.append("image")
.attr("height", NODESIZE)
.attr("width", NODESIZE)
.attr("fill", "#69b3a2")
.attr(
"xlink:href",
(d) => d.image || "https://legonzaur.fr/Luka/condo/2026_01/images-6.jpeg"
);
// .call(drag(simulation as unknown));
node
.append("text")
.attr("x", NODESIZE / 2)
.attr("y", NODESIZE + 5)
.attr("text-anchor", "middle")
.attr("paint-order", "stroke")
.attr("stroke", "#ffffff")
.attr("fill", "#000000")
.text((d) => d.id);
// Drag
function dragstarted(this: SVGImageElement, event: any, d: Node) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}
// Update the subject (dragged node) position during drag.
function dragged(this: SVGImageElement, event: any, d: Node) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}
// Restore the target alpha so the simulation cools after dragging ends.
// Unfix the subject position now that it’s no longer being dragged.
function dragended(this: SVGImageElement, event: any, d: Node) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}
const d = drag<SVGImageElement, Node>();
d.on("start", dragstarted).on("drag", dragged).on("end", dragended);
selectAll<SVGImageElement, Node>("image").call(d);
// Updating node positions
simulation.on("tick", () => {
@@ -82,5 +162,17 @@ simulation.on("tick", () => {
.attr("x2", (d) => d.target.x)
.attr("y2", (d) => d.target.y);
node.attr("x", (d) => d.x - 25).attr("y", (d) => d.y - 25);
lineLegend.attr("x", (d) => d.source.x + (d.target.x - d.source.x) / 2);
lineLegend.attr("y", (d) => d.source.y + (d.target.y - d.source.y) / 2);
// node
// .attr("cx", function (d) {
// return d.x;
// })
// .attr("cy", function (d) {
// return d.y;
// });
node
.attr("x", (d) => d.x - CONTAINERSIZE / 2)
.attr("y", (d) => d.y - CONTAINERSIZE / 2);
});