Public
Edited
Mar 27
Fork of Untitled
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// logic for selecting the year we want to view
viewof selectedYear = Inputs.Range([d3.min(years),d3.max(years)],
{
step: 1,
label: "Pick a year: ",
disabled: showAllYears // disable the slider when we want to see all years
}
);
Insert cell
//calculates a suitable X value for tooltip left position
function tooltipX(eventX) {
if(eventX > 790){
return eventX - 270;
}else{
return eventX + 70;
}
}
Insert cell
chart2_diego = () => {
const width = 1100, height = 800, margin = { top: 80, right: 50, bottom: 120, left: 50 };

// if the showAllYears is toggled on, just show all years. Otherwise, filter by the selected year from the slider
const filteredData = showAllYears ? spotify_data :
spotify_data.filter(d => new Date(d.track_album_release_date).getFullYear() === selectedYear);

const sortedData = [...filteredData].sort((a, b) => a.danceability - b.danceability);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);

const g = svg.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

// fixed x axis to max danceability & fixed y axis max popularity
const xScale = d3.scaleLinear()
.domain([0, d3.max(spotify_data, d => d.danceability)])
.range([0, width - margin.left - margin.right - 30]);

const yScale = d3.scaleLinear()
.domain([0, d3.max(spotify_data, d => d.track_popularity)])
.range([height - margin.top - margin.bottom, 0]);

// const xScale = d3.scaleLinear()
// .domain(d3.extent(sortedData, d => d.danceability)).nice()
// .range([0, width - margin.left - margin.right]);

// const yScale = d3.scaleLinear()
// .domain(d3.extent(sortedData, d => d.track_popularity)).nice()
// .range([height - margin.top - margin.bottom, 0]);

const rScale = d3.scaleSqrt()
.domain(d3.extent(sortedData, d => d.valence))
.range([5, 20]);

const colorScale = d3.scaleOrdinal()
.domain([...spotify_data].map(d => d.playlist_genre))
.range([
"#ff6f4b",
"#F06292",
"#D81B60",
"#FFB343",
"#9C1C6C",
"#6A1B9A",
"#4A148C",
"#290E7D",
]);

g.append("g")
.attr("transform", `translate(0, ${height - margin.top - margin.bottom})`)
.call(d3.axisBottom(xScale));

g.append("g")
.call(d3.axisLeft(yScale));

g.selectAll("circle")
.data(sortedData)
.enter().append("circle")
.attr("cx", d => xScale(d.danceability) + 8)
.attr("cy", d => yScale(d.track_popularity) - 18)
.attr("r", d => rScale(d.valence))
.attr("fill", d => colorScale(d.playlist_genre))
.attr("opacity", 0.75)
.on("mouseover", (event, d) => {
d3.select("#tooltip")
.style("left", `${event.pageX + 10}px`)
.style("top", `${event.pageY - 20}px`)
.style("display", "block")
.html(`
<strong>${d.track_name}</strong> by ${d.track_artist}<br>
Genre: ${d.playlist_genre}<br>
Danceability: ${d.danceability}<br>
Energy: ${d.energy}<br>
Popularity: ${d.track_popularity}<br>
Valence: ${d.valence}<br>
Tempo: ${d.tempo}<br>
<img src="${d.image_url}" alt="${d.track_name}" style="max-width: 100px; height: auto; display: block; margin: 10px auto;">
<iframe src="https://open.spotify.com/embed/track/${d.spotify_url.split('/').pop()}" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>
`);
})
.on("mouseout", () => d3.select("#tooltip").style("display", "none"));

if (d3.select("#tooltip").empty()) {
d3.select("body").append("div")
.attr("id", "tooltip")
.style("position", "absolute")
.style("background", "white")
.style("padding", "5px")
.style("border", "1px solid black")
.style("display", "none");
}

svg.append("text")
.attr("x", width / 2)
.attr("y", margin.top / 2.5)
.attr("text-anchor", "middle")
.attr("font-size", "18px")
.attr("font-weight", "bold")
// show the currently selected Year at the top of the graph for clarity, feel free to remove if too much
.text(`Mapping Danceability and Popularity by Genre of Spotify Tracks ${
showAllYears ? " - 1965 to 2020" : " - "+selectedYear}`);

svg.append("text")
.attr("x", width / 2)
.attr("y", height - margin.bottom + 40)
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.text("Danceability");

svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", margin.left - 40)
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.text("Popularity");

// adding the legend to the right hand side
const legend = svg.append("g")
.attr("transform", `translate(${width - margin.right - 15}, ${margin.top})`);

// grab the unique genres
const genres = [...new Set(spotify_data.map(d => d.playlist_genre))];

// create the item to store the colored rectangle and genre text
const legendItem = legend.selectAll(".legend-item")
.data(genres)
.enter().append("g")
.attr("class", "legend-item")
.attr("transform", (d, i) => `translate(0, ${i * 25})`);

legendItem.append("rect")
.attr("width", 20)
.attr("height", 20)
.attr("fill", d => colorScale(d));

legendItem.append("text")
.attr("x", 30)
.attr("y", 15)
.attr("font-size", "14px")
.text(d => d);

return svg.node();
}

Insert cell
//calculates a suitable Y value for tooltip top position
function tooltipY(scaledY, eventY){
if(scaledY > 350){
return eventY - 320;
}else{
return eventY - 20;
}
}
Insert cell
Insert cell
Insert cell
chart2_stephen();
Insert cell
Insert cell
chart2 = () => {
const width = 1100, height = 800, margin = { top: 80, right: 50, bottom: 120, left: 50 };

const sortedData = [...spotify_data].sort((a, b) => a.danceability - b.danceability);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);

const g = svg.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

const xScale = d3.scaleLinear()
.domain(d3.extent(sortedData, d => d.danceability)).nice()
.range([0, width - margin.left - margin.right]);

const yScale = d3.scaleLinear()
.domain(d3.extent(sortedData, d => d.track_popularity)).nice()
.range([height - margin.top - margin.bottom, 0]);

const rScale = d3.scaleSqrt()
.domain(d3.extent(sortedData, d => d.valence))
.range([5, 20]);

const colorScale = d3.scaleOrdinal()
.domain(sortedData.map(d => d.playlist_genre))
.range([
"#ff6f4b",
"#F06292",
"#D81B60",
"#FFB343",
"#9C1C6C",
"#6A1B9A",
"#4A148C",
"#290E7D",
]);

g.append("g")
.attr("transform", `translate(0, ${height - margin.top - margin.bottom})`)
.call(d3.axisBottom(xScale));

g.append("g")
.call(d3.axisLeft(yScale));

g.selectAll("circle")
.data(sortedData)
.enter().append("circle")
.attr("cx", d => xScale(d.danceability) + 8)
.attr("cy", d => yScale(d.track_popularity) - 18)
.attr("r", d => rScale(d.valence))
.attr("fill", d => colorScale(d.playlist_genre))
.attr("opacity", 0.8)
.on("mouseover", (event, d) => {
d3.select("#tooltip")
.style("left", `${event.pageX + 10}px`)
.style("top", `${event.pageY - 20}px`)
.style("display", "block")
.html(`
<strong>${d.track_name}</strong> by ${d.track_artist}<br>
Genre: ${d.playlist_genre}<br>
Danceability: ${d.danceability}<br>
Energy: ${d.energy}<br>
Popularity: ${d.track_popularity}<br>
Valence: ${d.valence}<br>
Tempo: ${d.tempo}<br>
<img src="${d.image_url}" alt="${d.track_name}" style="max-width: 100px; height: auto; display: block; margin: 10px auto;">
<iframe src="https://open.spotify.com/embed/track/${d.spotify_url.split('/').pop()}" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>
`);
})
.on("mouseout", () => d3.select("#tooltip").style("display", "none"));

if (d3.select("#tooltip").empty()) {
d3.select("body").append("div")
.attr("id", "tooltip")
.style("position", "absolute")
.style("background", "white")
.style("padding", "5px")
.style("border", "1px solid black")
.style("display", "none");
}

svg.append("text")
.attr("x", width / 2)
.attr("y", margin.top / 2)
.attr("text-anchor", "middle")
.attr("font-size", "18px")
.attr("font-weight", "bold")
.text(`Mapping Danceability and Popularity by Genre of Spotify Tracks`);

svg.append("text")
.attr("x", width / 2)
.attr("y", height - margin.bottom + 40)
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.text("Danceability");

svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", margin.left - 40)
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.text("Popularity");

return svg.node();
}

Insert cell
Insert cell
chart2_stephen = () => {
const width = 1100, height = 800, margin = { top: 80, right: 50, bottom: 120, left: 50 };

// if the showAllYears is toggled on, just show all years. Otherwise, filter by the selected year from the slider
const filteredData = showAllYears ? spotify_data :
spotify_data.filter(d => new Date(d.track_album_release_date).getFullYear() === selectedYear);

const sortedData = [...filteredData].sort((a, b) => a.danceability - b.danceability);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);

const g = svg.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

// fixed x axis to max danceability & fixed y axis max popularity
const xScale = d3.scaleLinear()
.domain([0, d3.max(spotify_data, d => d.danceability)])
.range([0, width - margin.left - margin.right - 30]);

const yScale = d3.scaleLinear()
.domain([0, d3.max(spotify_data, d => d.track_popularity)])
.range([height - margin.top - margin.bottom, 0]);

// const xScale = d3.scaleLinear()
// .domain(d3.extent(sortedData, d => d.danceability)).nice()
// .range([0, width - margin.left - margin.right]);

// const yScale = d3.scaleLinear()
// .domain(d3.extent(sortedData, d => d.track_popularity)).nice()
// .range([height - margin.top - margin.bottom, 0]);

const rScale = d3.scaleSqrt()
.domain(d3.extent(sortedData, d => d.valence))
.range([5, 20]);

const colorScale = d3.scaleOrdinal()
.domain([...spotify_data].map(d => d.playlist_genre))
.range([
"#ff6f4b",
"#F06292",
"#D81B60",
"#FFB343",
"#9C1C6C",
"#6A1B9A",
"#4A148C",
"#290E7D",
]);

g.append("g")
.attr("transform", `translate(0, ${height - margin.top - margin.bottom})`)
.call(d3.axisBottom(xScale));

g.append("g")
.call(d3.axisLeft(yScale));

g.selectAll("circle")
.data(sortedData)
.enter().append("circle")
.attr("cx", d => xScale(d.danceability) + 8)
.attr("cy", d => yScale(d.track_popularity) - 18)
.attr("r", d => rScale(d.valence))
.attr("fill", d => colorScale(d.playlist_genre))
.attr("opacity", 0.8)
.on("mouseover", (event, d) => {
d3.select("#tooltip")
.style("left", `${tooltipX(xScale(d.danceability) + 8)}px`)
.style("top", `${tooltipY(yScale(d.track_popularity) - 18, event.pageY)}px`)
.style("width", '300px')
.style("height",'350px')
.style("display", "block")
.html(`
<strong>${d.track_name}</strong> by ${d.track_artist}<br>
Genre: ${d.playlist_genre}<br>
Danceability: ${d.danceability.toFixed(2)}<br>
Energy: ${d.energy.toFixed(2)}<br>
Popularity: ${d.track_popularity.toFixed(2)}<br>
Valence: ${d.valence.toFixed(2)}<br>
Tempo: ${d.tempo.toFixed(2)}<br>
<img src="${d.image_url}" alt="${d.track_name}" style="max-width: 100px; height: auto; display: block; margin: 10px auto;">
<iframe src="https://open.spotify.com/embed/track/${d.spotify_url.split('/').pop()}" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>
`);
})
.on("mouseout", () => d3.select("#tooltip").style("display", "none"));

if (d3.select("#tooltip").empty()) {
d3.select("body").append("div")
.attr("id", "tooltip")
.style("position", "absolute")
.style("background", "white")
.style("padding", "5px")
.style("border", "1px solid black")
.style("display", "none");
}

svg.append("text")
.attr("x", width / 2)
.attr("y", margin.top / 2.5)
.attr("text-anchor", "middle")
.attr("font-size", "18px")
.attr("font-weight", "bold")
// show the currently selected Year at the top of the graph for clarity, feel free to remove if too much
.text(`Mapping Danceability and Popularity by Genre of Spotify Tracks ${
showAllYears ? " - 1965 to 2020" : " - "+selectedYear}`);

svg.append("text")
.attr("x", width / 2)
.attr("y", height - margin.bottom + 40)
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.text("Danceability");

svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", margin.left - 40)
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.text("Popularity");

// adding the legend to the right hand side
const legend = svg.append("g")
.attr("transform", `translate(${width - margin.right - 15}, ${margin.top})`);

// grab the unique genres
const genres = [...new Set(spotify_data.map(d => d.playlist_genre))];

// create the item to store the colored rectangle and genre text
const legendItem = legend.selectAll(".legend-item")
.data(genres)
.enter().append("g")
.attr("class", "legend-item")
.attr("transform", (d, i) => `translate(0, ${i * 25})`);

legendItem.append("rect")
.attr("width", 20)
.attr("height", 20)
.attr("fill", d => colorScale(d));

legendItem.append("text")
.attr("x", 30)
.attr("y", 15)
.attr("font-size", "14px")
.text(d => d);

return svg.node();
}

Insert cell
years = spotify_data.map(d => new Date(d.track_album_release_date).getFullYear()); // make an array of the years
Insert cell
Insert cell
Insert cell
Insert cell
chart4 = {

spotify_data.forEach(d => {
d.track_popularity = +d.track_popularity;
});


const genreAvgPopularity = d3.rollup(
spotify_data,
v => d3.mean(v, d => d.track_popularity),
d => d.playlist_genre
);


const genreData = Array.from(genreAvgPopularity, ([genre, avgPopularity]) => ({
genre,
avgPopularity
}));


genreData.sort((a, b) => b.avgPopularity - a.avgPopularity);


const width = 640;
const height = 400;
const marginTop = 25;
const marginRight = 20;
const marginBottom = 35;
const marginLeft = 40;


const x = d3.scaleBand()
.domain(genreData.map(d => d.genre))
.range([marginLeft, width - marginRight])
.padding(0.1);


const y = d3.scaleLinear()
.domain([0, d3.max(genreData, d => d.avgPopularity)])
.nice()
.range([height - marginBottom, marginTop]);


const color = d3.scaleOrdinal()
.domain(genreData.map(d => d.genre))
.range([
"#D84B9B", // Light Pink
"#C54B9B", // Medium Pink
"#6A0DAD", // Violet
"#8A2BE2", // Blue Violet
"#D5A6D2", // Light Lilac
"#9B30FF", // Purple
"#E9A3D1", // Soft Pink
"#C71585", // Medium Violet Red
"#DA70D6" // Orchid
]);


const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");


svg.append("text")
.attr("x", width / 2)
.attr("y", marginTop - 10)
.attr("text-anchor", "middle")
.attr("font-size", "16px")
.attr("font-weight", "bold")
.text("Average Track Popularity by Genre");

svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x))
.selectAll("text")
.style("text-anchor", "middle")
.style("font-size", "10px")
.attr("dx", "-.8em")
.attr("dy", ".15em");


svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y));

// Append bars for each genre with pretty colors
svg.append("g")
.selectAll("rect")
.data(genreData)
.join("rect")
.attr("x", d => x(d.genre)) // Set the x-position based on the genre
.attr("y", d => y(d.avgPopularity)) // Set the y-position based on the average popularity
.attr("width", x.bandwidth()) // Set the bar width
.attr("height", d => height - marginBottom - y(d.avgPopularity)) // Set the bar height
.attr("fill", (d, i) => color(i)); // Use the custom color scale

// Add x-axis label
svg.append("text")
.attr("x", width / 2)
.attr("y", height - 5)
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.text("Playlist Genre");

// Add y-axis label
svg.append("text")
.attr("x", -height / 2)
.attr("y", 8)
.attr("transform", "rotate(-90)")
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.text("Average Track Popularity");

return svg.node();

}
Insert cell
Inputs = require("@observablehq/inputs@0.7.8/dist/inputs.umd.min.js")
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