diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3a24117 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.jpg filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text diff --git a/graph.json b/graph.json index 8dd43a8..871a9a1 100644 --- a/graph.json +++ b/graph.json @@ -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" + } + ] } diff --git a/images/anomalie.png b/images/anomalie.png new file mode 100644 index 0000000..4510705 --- /dev/null +++ b/images/anomalie.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8038be0a714c6625ca3f6be434fe01c9e6bb65c0a8ac4d24b60528a3bbd6bcda +size 37401 diff --git a/images/bernth.jpg b/images/bernth.jpg new file mode 100644 index 0000000..0322282 --- /dev/null +++ b/images/bernth.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1613ea8dce06f8abddc0a88cf2e1f122ff8265c55913fd623aa1a78cf6c9b177 +size 6559 diff --git a/images/cory wong.png b/images/cory wong.png new file mode 100644 index 0000000..1598096 --- /dev/null +++ b/images/cory wong.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e8c4df9121f44c85a59756fbd1fce9949a7bed8e8faac1bc5ca47656aae4b38 +size 29066 diff --git a/images/dios.jpg b/images/dios.jpg new file mode 100644 index 0000000..58603f0 --- /dev/null +++ b/images/dios.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fba3e709ac00f79fdbfacc62cf4c56c788658b7379022e08413c0a467f9f3107 +size 1970 diff --git a/images/itchika nito.jpg b/images/itchika nito.jpg new file mode 100644 index 0000000..1f9e3c1 --- /dev/null +++ b/images/itchika nito.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7cdfde2eab4ec914d6b3445d2b49bbb531a79ef685a94212de4e3d8a31c8ba4 +size 7270 diff --git a/images/plini.png b/images/plini.png new file mode 100644 index 0000000..a28813e --- /dev/null +++ b/images/plini.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69ab45b02d3ff78ab58df2aeef96c57f30f746839ff490b4d6f5efe18ad3b56d +size 17693 diff --git a/images/polyphia.png b/images/polyphia.png new file mode 100644 index 0000000..d10f261 --- /dev/null +++ b/images/polyphia.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:723bf8ea6a4a607015e699f3d69404962b23bb81ac27042eb618eeb553687efe +size 69961 diff --git a/images/syncatto.png b/images/syncatto.png new file mode 100644 index 0000000..e2d14f6 --- /dev/null +++ b/images/syncatto.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:108f61bfcc496bb83be9edda11a32b759dd4c9cf45e0469c4fe62d151a330a1d +size 49175 diff --git a/images/the home team.jpg b/images/the home team.jpg new file mode 100644 index 0000000..ec8adfd --- /dev/null +++ b/images/the home team.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f603a551931fa4dc3dfcb9aab5a1f4376972d8fe11ff965061dae3a3946ad5c +size 9517 diff --git a/images/tim henson.png b/images/tim henson.png new file mode 100644 index 0000000..bd5b48f --- /dev/null +++ b/images/tim henson.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db69e5dc3729156db355ad7ffe3816c3cae52574b56c466d7f8b680636081db3 +size 69805 diff --git a/index.html b/index.html index 037b800..2c9794a 100644 --- a/index.html +++ b/index.html @@ -11,10 +11,19 @@ + - + diff --git a/src/graph.ts b/src/graph.ts index b5fff40..7201db5 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -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.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(); +d.on("start", dragstarted).on("drag", dragged).on("end", dragended); +selectAll("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); });