-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathd3-network.html
More file actions
130 lines (118 loc) · 3.58 KB
/
d3-network.html
File metadata and controls
130 lines (118 loc) · 3.58 KB
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>A–Z Weighted Network</title>
<style>
body { font-family: sans-serif; }
.tooltip {
position: absolute;
background: #fff;
border: 1px solid #ccc;
padding: 6px;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s;
}
</style>
</head>
<body>
<svg width="800" height="800"></svg>
<div class="tooltip" id="tooltip"></div>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
const svg = d3.select("svg");
const width = +svg.attr("width");
const height = +svg.attr("height");
const centerX = width / 2;
const centerY = height / 2;
const radius = 300;
const tooltip = d3.select("#tooltip");
// 建立 A–Z 節點
const alphabet = Array.from({ length: 26 }, (_, i) => String.fromCharCode(65 + i));
const nodes = alphabet.map((letter, i) => ({
id: letter,
angle: (i / 26) * 2 * Math.PI,
}));
// 計算圓形位置
nodes.forEach(d => {
d.x = centerX + radius * Math.cos(d.angle);
d.y = centerY + radius * Math.sin(d.angle);
});
// 建立隨機連線(每個節點連 1–10 個其他節點)
const links = [];
nodes.forEach(source => {
const targets = nodes.filter(n => n.id !== source.id);
const count = Math.floor(Math.random() * 10) + 1;
const sampled = d3.shuffle(targets).slice(0, count);
sampled.forEach(target => {
links.push({
source: source.id,
target: target.id,
weight: (Math.random() * 0.9 + 0.1).toFixed(2)
});
});
});
// 計算每個節點的連線數量
const linkCount = {};
links.forEach(link => {
linkCount[link.source] = (linkCount[link.source] || 0) + 1;
linkCount[link.target] = (linkCount[link.target] || 0) + 1;
});
// 顏色比例尺:連線越多顏色越深
const colorScale = d3.scaleSequential()
.domain([0, d3.max(Object.values(linkCount))])
.interpolator(d3.interpolateBlues);
// 畫連線
const link = svg.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke", "#ccc")
.attr("stroke-width", d => d.weight * 2)
.attr("x1", d => nodes.find(n => n.id === d.source).x)
.attr("y1", d => nodes.find(n => n.id === d.source).y)
.attr("x2", d => nodes.find(n => n.id === d.target).x)
.attr("y2", d => nodes.find(n => n.id === d.target).y)
.on("mouseover", function(event, d) {
tooltip.style("opacity", 1)
.html(`Weight: ${d.weight}<br>${d.source} → ${d.target}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 20) + "px");
d3.select(this).attr("stroke", "#f00");
})
.on("mouseout", function() {
tooltip.style("opacity", 0);
d3.select(this).attr("stroke", "#ccc");
});
const node = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 16)
.attr("fill", d => colorScale(linkCount[d.id] || 0))
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.on("mouseover", function(event, d) {
const count = linkCount[d.id] || 0;
tooltip.style("opacity", 1)
.html(`Letter: ${d.id}<br>Connections: ${count}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 20) + "px");
d3.select(this).attr("stroke", "#333").attr("stroke-width", 3);
})
.on("mouseout", function() {
tooltip.style("opacity", 0);
d3.select(this).attr("stroke", null);
});
// 加上文字標籤(居中在圓內)
svg.selectAll("text")
.data(nodes)
.enter().append("text")
.text(d => d.id)
.attr("x", d => d.x)
.attr("y", d => d.y + 4)
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.attr("fill", "#fff");
</script>
</body>
</html>