Public
Edited
May 5
Fork of Simple D3
Insert cell
Insert cell
// Load data
data = await FileAttachment("songs_normalize.csv").csv()
Insert cell
// Parse & clean
songs = data.map(d => ({
artist: d.artist,
song: d.song,
year: +d.year,
tempo: +d.tempo,
energy: +d.energy,
valence: +d.valence,
danceability: +d.danceability
}))
Insert cell
// Sliders for user mood filtering
viewof energyTarget = Inputs.range([0, 1], {step: 0.01, value: 0.5, label: "Target Energy"})
Insert cell
viewof valenceTarget = Inputs.range([0, 1], {step: 0.01, value: 0.5, label: "Target Valence"})
Insert cell
viewof danceTarget = Inputs.range([0, 1], {step: 0.01, value: 0.5, label: "Target Danceability"})
Insert cell
// Tolerance
tolerance = 0.15
Insert cell
// Filter songs based on mood
filteredSongs = songs.filter(d =>
Math.abs(d.energy - energyTarget) < tolerance &&
Math.abs(d.valence - valenceTarget) < tolerance &&
Math.abs(d.danceability - danceTarget) < tolerance
)
Insert cell
// Create the galaxy plot with star-shaped points and galaxy theme
viewof moodGalaxy = {
const width = 800, height = 800;
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("background", "#1b1c4a")
.style("font-family", "sans-serif")
.style("color", "white");

// Title and subheading
svg.append("text")
.attr("x", width / 2)
.attr("y", 30)
.attr("text-anchor", "middle")
.attr("font-size", 24)
.attr("font-weight", "bold")
.style("fill", "white")
.text("Mood Galaxy: Navigate the Universe of Songs");

svg.append("text")
.attr("x", width / 2)
.attr("y", 55)
.attr("text-anchor", "middle")
.attr("font-size", 14)
.style("fill", "white")
.text("Each star is a song — placement is randomized, size by tempo, color by valence");

const radius = d3.scaleSqrt()
.domain([60, 200])
.range([2, 10]);

// Galaxy color palette: purples, pinks, and light blues
const galaxyColors = d3.scaleSequential()
.domain([0, 1])
.interpolator(d3.interpolateCubehelixDefault); // Suitable for dark backgrounds

const nodes = filteredSongs.map(d => ({
...d,
x: width / 2 + (Math.random() - 0.5) * 300,
y: height / 2 + (Math.random() - 0.5) * 300,
r: radius(d.tempo),
fill: galaxyColors(d.valence)
}));

const simulation = d3.forceSimulation(nodes)
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(1))
.force("collision", d3.forceCollide(d => d.r + 1))
.on("tick", ticked);

const node = svg.selectAll("path")
.data(nodes)
.join("path")
.attr("transform", d => `translate(${d.x},${d.y})`)
.attr("d", d3.symbol().type(d3.symbolStar).size(d => d.r * d.r * 20))
.attr("fill", d => d.fill)
.attr("opacity", 0.85)
.append("title")
.text(d => `${d.song} by ${d.artist}\nEnergy: ${d.energy}\nValence: ${d.valence}`);

function ticked() {
svg.selectAll("path")
.data(nodes)
.attr("transform", d => `translate(${d.x},${d.y})`);
}

// Legend
const legendX = width - 200;
const legendY = height - 280;

svg.append("text")
.attr("x", legendX)
.attr("y", legendY)
.attr("font-weight", "bold")
.attr("fill", "white")
.text("Mood (Valence)");

[0, 0.25, 0.5, 0.75, 1].forEach((v, i) => {
svg.append("path")
.attr("transform", `translate(${legendX}, ${legendY + 20 + i * 20})`)
.attr("d", d3.symbol().type(d3.symbolStar).size(60))
.attr("fill", galaxyColors(v));

svg.append("text")
.attr("x", legendX + 20)
.attr("y", legendY + 25 + i * 20)
.attr("alignment-baseline", "middle")
.attr("fill", "white")
.attr("font-size", 11)
.text(v);
});

svg.append("text")
.attr("x", legendX)
.attr("y", legendY + 140)
.attr("font-weight", "bold")
.attr("fill", "white")
.text("Tempo (Bubble Size)");

[60, 120, 180].forEach((t, i) => {
svg.append("circle")
.attr("cx", legendX + 6)
.attr("cy", legendY + 160 + i * 20)
.attr("r", radius(t))
.attr("fill", "white")
.attr("opacity", 0.6);

svg.append("text")
.attr("x", legendX + 20)
.attr("y", legendY + 160 + i * 20)
.attr("alignment-baseline", "middle")
.attr("fill", "white")
.attr("font-size", 11)
.text(`${t} BPM`);
});

return svg.node();
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more