File size: 3,768 Bytes
c5ae2fb
 
 
afe9b98
 
 
 
 
c5ae2fb
 
afe9b98
 
16f8458
0546309
fb8bfbc
c5ae2fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e1866e1
c5ae2fb
 
 
 
 
5baf49c
3142413
c5ae2fb
 
 
 
e1866e1
c5ae2fb
 
 
 
 
e1866e1
c5ae2fb
 
 
 
 
 
 
 
e1866e1
c5ae2fb
 
 
 
 
 
 
 
 
 
 
 
 
e1866e1
c5ae2fb
e1866e1
c5ae2fb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Graph Data - Reconstructed Structure
const graphData = {
    nodes: [
        { id: "About", group: 1 },
        { id: "Notes", group: 2 },
        { id: "Projects", group: 2 },
        { id: "Audio", group: 2 },
        { id: "Visual", group: 3 }
    ],
    links: [
        { source: "About", target: "Notes" },
        { source: "About", target: "Projects" },
        { source: "About", target: "Audio" },
        { source: "About", target: "Visual" }  
    ],
};

// Setup Canvas
const container = document.getElementById('graph-container');
let width = container.clientWidth;
let height = container.clientHeight;

// Clear any existing SVG to prevent duplicates on reload
d3.select("#graph-container").selectAll("*").remove();

const svg = d3.select("#graph-container")
    .append("svg")
    .attr("width", "100%")
    .attr("height", "100%")
    .attr("viewBox", [0, 0, width, height])
    .call(d3.zoom().on("zoom", (event) => {
        g.attr("transform", event.transform);
    }));

const g = svg.append("g");

// Simulation Setup
const simulation = d3.forceSimulation(graphData.nodes)
    .force("link", d3.forceLink(graphData.links).id(d => d.id).distance(150))
    .force("charge", d3.forceManyBody().strength(-400))
    .force("center", d3.forceCenter(width / 2, height / 2))
    .force("collide", d3.forceCollide().radius(30));

// Render Links
const link = g.append("g")
    .attr("class", "links")
    .selectAll("line")
    .data(graphData.links)
    .join("line")
    .attr("stroke-width", 2)
    // CSS variables are handled by style.css targeting 'line'
    // but we can add specific classes if needed
    .attr("class", "graph-link");

// Render Nodes
const node = g.append("g")
    .attr("class", "nodes")
    .selectAll("g")
    .data(graphData.nodes)
    .join("g")
    .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

// Node Circles
node.append("circle")
    .attr("r", 8)
    .attr("class", "graph-node");

// Node Labels
node.append("text")
    .attr("x", 12)
    .attr("y", "0.31em")
    .text(d => d.id)
    .attr("class", "graph-text")
    .clone(true).lower()
    .attr("fill", "none")
    .attr("stroke", "var(--bg-primary)")
    .attr("stroke-width", 3);

// Simulation Tick
simulation.on("tick", () => {
    link
        .attr("x1", d => d.source.x)
        .attr("y1", d => d.source.y)
        .attr("x2", d => d.target.x)
        .attr("y2", d => d.target.y);

    node
        .attr("transform", d => `translate(${d.x},${d.y})`);
});

// Drag Interaction Functions
function dragstarted(event, d) {
    if (!event.active) simulation.alphaTarget(0.3).restart();
    d.fx = d.x;
    d.fy = d.y;
}

function dragged(event, d) {
    d.fx = event.x;
    d.fy = event.y;
}

function dragended(event, d) {
    if (!event.active) simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
}

// Handle Window Resize
window.addEventListener('resize', () => {
    width = container.clientWidth;
    height = container.clientHeight;
    
    simulation.force("center", d3.forceCenter(width / 2, height / 2));
    simulation.alpha(0.3).restart();
});

// Populate Sidebar (Optional integration)
const nodeList = document.getElementById('node-list');
if (nodeList) {
    nodeList.innerHTML = ''; // Clear existing
    graphData.nodes.forEach(node => {
        const item = document.createElement('div');
        item.style.padding = '0.5rem';
        item.style.cursor = 'pointer';
        item.style.borderBottom = '1px solid var(--border-color)';
        item.textContent = node.id;
        item.addEventListener('click', () => {
            // Optional: Zoom to node
            console.log('Focus on:', node.id);
        });
        nodeList.appendChild(item);
    });
}