first draft
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
+109
-55
@@ -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
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+10
-1
@@ -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
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user