-
Notifications
You must be signed in to change notification settings - Fork 0
/
script.js
192 lines (165 loc) · 6.22 KB
/
script.js
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// Function to fetch data from the JSON file
async function fetchData() {
try {
// Fetch the data from the JSON file
const response = await fetch('pulseroots.genres.json');
const data = await response.json();
console.log('Received data from JSON:', data);
// Call the function to create the tree visualization
createTree(data);
} catch (error) {
console.error('Error obtaining data:', error);
}
}
// Function to create the tree visualization with D3.js
function createTree(data) {
// Set the dimensions of the SVG container
const width = 1300;
const height = 3000;
// Create a color scale for the nodes
const colorScale = d3.scaleOrdinal(d3.schemeTableau10);
// Select the SVG container and set its dimensions
const svg = d3.select("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `0 0 ${width} ${height}`)
.style("display", "block")
.style("margin", "0 auto");
// Transform the data into a hierarchical structure
const hierarchicalData = {
name: "Electronic Music",
children: data.map(style => ({
name: style.style,
description: style.description,
example: style.example,
spotify_track_id: style.spotify_track_id,
children: style.substyles.map(substyle => ({
name: substyle.name,
description: substyle.description,
example: substyle.example,
spotify_track_id: substyle.spotify_track_id,
children: substyle.substyles ? substyle.substyles.map(subsubstyle => ({
name: subsubstyle.name,
description: subsubstyle.description,
example: subsubstyle.example,
spotify_track_id: subsubstyle.spotify_track_id,
children: subsubstyle.substyles ? subsubstyle.substyles.map(subsubsubstyle => ({
name: subsubsubstyle.name,
description: subsubsubstyle.description,
example: subsubsubstyle.example,
spotify_track_id: subsubsubstyle.spotify_track_id
})) : undefined
})) : undefined
}))
}))
};
// Create a hierarchical data structure from the transformed data
const root = d3.hierarchy(hierarchicalData);
// Create a tree layout with the specified dimensions
const treeLayout = d3.tree().size([height, width -200]);
// Filter the nodes to only show the ones that are not the root node
const nodesToShow = root.descendants().slice(1);
// Apply the tree layout to the data
treeLayout(root);
// Create the links between the nodes
svg.selectAll('.link')
.data(root.links())
.enter()
.append('path')
.attr('class', 'link')
.attr('d', d3.linkHorizontal()
.x(d => d.y)
.y(d => d.x));
// Create the nodes
const node = svg.selectAll('.node')
.data(nodesToShow) // Bind the filtered data
.enter()
.append('g')
.attr('class', 'node')
.attr('transform', d => `translate(${d.y},${d.x})`)
.attr('class', d => d.depth === 1 || d.depth === 2 || d.depth === 3 || d.depth === 4 ? 'node clickable-node' : 'node')
.on('click', (event, d) => {
// Handle the click event on the nodes
if (d.depth === 0) {
return;
}
const infoPanel = document.getElementById('info-panel');
const infoContent = document.getElementById('info-content');
const spotifyEmbed = document.getElementById('spotify-embed');
// Update the info panel with the node's data
infoContent.innerHTML = `
<h2><i class="bi bi-tag-fill"></i> ${d.data.name}</h2>
<p>${d.data.description || 'No description available'}</p>
<p><i class="bi bi-soundwave"></i> <b>Example track: ${d.data.example || 'N/A'}</b></p>
`;
// Access the spotify_track_id correctly
const trackId = d.data.spotify_track_id || '1234567890';
console.log('Track ID:', trackId);
// Embed the Spotify player for the selected track
spotifyEmbed.innerHTML = `
<iframe style="border-radius:12px" src="https://open.spotify.com/embed/track/${trackId}?utm_source=generator" width="100%" height="160" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe>
`;
// Show the info panel
infoPanel.classList.add('visible');
});
// Function to show the tooltip
function showTooltip(event, d) {
if (d.depth === 0) return; // Don't show tooltip for the root node
// Create a div element for the tooltip
const tooltip = d3.select('body')
.append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
// Add content to the tooltip
tooltip.html(`
<h3>${d.data.name}</h3>
<p>${d.data.description || 'No description available'}</p>
<p><i class="bi bi-hand-index"></i> Click the node to listen the example track.</p>
`);
// Animate the tooltip's appearance
tooltip.transition()
.duration(200)
.style('opacity', 0.9);
// Position the tooltip near the node
tooltip.style('left', (event.pageX + 10) + 'px')
.style('top', (event.pageY - 28) + 'px');
}
// Function to hide the tooltip
function hideTooltip() {
d3.select('.tooltip').remove();
}
// Add the event listeners to the nodes
node.on('mouseover', (event, d) => {
// Check if the screen is small before showing the tooltip
if (window.innerWidth < 768) {
return;
}
showTooltip(event, d);
});
node.on('mouseout', () => {
hideTooltip();
});
// Add the circles for the nodes
node.append('circle')
.attr('r',6)
.style('fill', d => {
if (d.depth === 0) return '#000'; // Root node
if (d.depth === 1) return colorScale(d.data.name); // Main genres
if (d.parent) return colorScale(d.parent.data.name); // Subgenres inherit parent color
return '#999'; // Fallback color
});
// Add the text labels for the nodes
node.append('text')
.attr('dy', 3)
.attr('x', d => d.children ? -8 : 8)
.style('text-anchor', d => d.children ? 'end' : 'start')
.style('font-family', 'Aleo, serif')
.style('font-size', '14px')
.text(d => d.depth === 0 ? '' : d.data.name);
}
// Add an event listener to close the info panel
document.getElementById('close-panel').addEventListener('click', () => {
document.getElementById('info-panel').classList.remove('visible');
});
// Call the function to fetch the data
fetchData();