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": [ "nodes": [
{ {
"id": "1", "id": "Dios",
"module": 0, "image": "images/dios.jpg",
"i": 0 "group": "Pop/Alternative Rock",
"link": "https://www.youtube.com/@Dios_band"
}, },
{ {
"id": "3", "id": "The Home Team",
"module": 0, "image": "images/the home team.jpg",
"i": 1, "group": "Pop/Alternative Rock",
"image": "https://legonzaur.fr/Luka/condo/2026_01/20251109_020401.jpg" "link": "https://www.youtube.com/@TheHomeTeamOfficial"
}, },
{ "id": "8", "module": 2, "i": 2 },
{ {
"id": "9", "id": "Itchika Nito",
"module": 4, "image": "images/itchika nito.jpg",
"i": 3, "group": "Prog Rock",
"image": "https://legonzaur.fr/Luka/condo/2026_01/20251109_020401.jpg" "link": "https://www.youtube.com/@ichika_nito"
}, },
{ "id": "12", "module": 2, "i": 4 }, {
{ "id": "15", "module": 2, "i": 5 }, "id": "Bernth",
{ "id": "23", "module": 3, "i": 6 }, "image": "images/bernth.jpg",
{ "id": "26", "module": 3, "i": 7 }, "link": "https://www.youtube.com/@Bernthguitar"
{ "id": "37", "module": 10, "i": 8 }, },
{ "id": "46", "module": 2, "i": 9 }, {
{ "id": "2", "module": 1, "i": 10 }, "id": "Syncatto",
{ "id": "4", "module": 2, "i": 11 }, "image": "images/syncatto.png",
{ "id": "5", "module": 3, "i": 12 }, "link": "https://www.youtube.com/channel/UCs6hz1aQNlSBwAkxEGhx2-g"
{ "id": "6", "module": 3, "i": 13 }, },
{ "id": "7", "module": 1, "i": 14 }, {
{ "id": "10", "module": 4, "i": 15 }, "id": "Plini",
{ "id": "11", "module": 1, "i": 16 }, "image": "images/plini.png",
{ "id": "13", "module": 1, "i": 17 }, "link": "https://www.youtube.com/@plinimusic"
{ "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": "Tim Henson",
{ "id": "20", "module": 1, "i": 22 }, "image": "images/tim henson.png",
{ "id": "21", "module": 6, "i": 23 }, "link": "https://www.youtube.com/channel/UCdKj0VzVR0Af0wzd7-68Avw"
{ "id": "22", "module": 2, "i": 24 }, },
{ "id": "25", "module": 3, "i": 25 }, {
{ "id": "27", "module": 1, "i": 26 }, "id": "Polyphia",
{ "id": "28", "module": 3, "i": 27 }, "image": "images/polyphia.png",
{ "id": "29", "module": 1, "i": 28 }, "link": "https://www.youtube.com/c/Polyphia/videos"
{ "id": "31", "module": 2, "i": 29 }, },
{ "id": "33", "module": 2, "i": 30 }, {
{ "id": "34", "module": 9, "i": 31 }, "id": "Anomalie",
{ "id": "35", "module": 1, "i": 32 }, "image": "images/anomalie.png",
{ "id": "38", "module": 2, "i": 33 }, "link": "https://www.youtube.com/@anomaliebeats"
{ "id": "41", "module": 4, "i": 34 }, },
{ "id": "32", "module": 2, "i": 35 }, {
{ "id": "40", "module": 2, "i": 36 }, "id": "Cory Wong",
{ "id": "17", "module": 4, "i": 37 }, "image": "images/cory wong.png",
{ "id": "36", "module": 3, "i": 38 }, "link": "https://www.youtube.com/@CoryWongMusic/videos"
{ "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 }
], ],
"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="" /> <meta name="description" content="" />
<link rel="icon" href="favicon.png" /> <link rel="icon" href="favicon.png" />
<style>
text {
pointer-events: none;
user-select: none;
}
image {
clip-path: inset(0% round 50%);
}
</style>
</head> </head>
<body> <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> <script src="dist/graph.js" type="module"></script>
</body> </body>
</html> </html>
+106 -14
View File
@@ -1,12 +1,22 @@
import { import {
BaseType,
drag,
DragBehavior,
forceCenter, forceCenter,
forceCollide,
forceLink, forceLink,
forceManyBody, forceManyBody,
forceSimulation, forceSimulation,
forceX,
forceY,
json, json,
select, select,
selectAll,
Selection,
} from "d3"; } from "d3";
const NODESIZE = 75;
const CONTAINERSIZE = 250;
interface Node { interface Node {
id: string; id: string;
module: number; module: number;
@@ -14,11 +24,14 @@ interface Node {
x: number; x: number;
y: number; y: number;
image?: string; image?: string;
radius?: number;
} }
interface Edge { interface Edge {
source: Node; source: Node;
target: Node; target: Node;
relation?: string;
directional?: boolean;
} }
interface Graph { interface Graph {
@@ -26,26 +39,39 @@ interface Graph {
edges: Edge[]; 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"); 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 { nodes, edges } = graph;
const svg = select("svg"); const svg = select("svg");
const width = Number(svg.attr("width"));
const height = Number(svg.attr("height"));
// Define the force simulation // Define the force simulation
const simulation = forceSimulation(nodes) const simulation = forceSimulation(nodes)
.force( .force(
"link", "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(
.force("center", forceCenter(width / 2, height / 2)); "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 // Adding links
const link = svg const link = svg
@@ -56,23 +82,77 @@ const link = svg
.data(edges) .data(edges)
.join("line"); .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 // Adding nodes
const node = svg const node = svg
.append("g") .append("g")
.attr("stroke", "#fff") .attr("stroke", "#fff")
.attr("stroke-width", 1.5) .attr("stroke-width", 1.5)
.selectAll("image") .selectAll("g")
.data(nodes) .data(nodes)
.join("image") .join("svg")
.attr("r", 5) .attr("width", CONTAINERSIZE)
.attr("height", 50) .attr("height", CONTAINERSIZE)
.attr("width", 50) .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("fill", "#69b3a2")
.attr( .attr(
"xlink:href", "xlink:href",
(d) => d.image || "https://legonzaur.fr/Luka/condo/2026_01/images-6.jpeg" (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 // Updating node positions
simulation.on("tick", () => { simulation.on("tick", () => {
@@ -82,5 +162,17 @@ simulation.on("tick", () => {
.attr("x2", (d) => d.target.x) .attr("x2", (d) => d.target.x)
.attr("y2", (d) => d.target.y); .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);
}); });