const svg = d3.select("svg"),
margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Set x, y and colors
const x = d3.scaleBand()
.rangeRound([10, width - 10])
.padding(0.2)
const y = d3.scaleLinear()
.range([height, 0]);
const z = d3.scaleOrdinal(d3.schemePaired);
d3.csv('https://raw.githubusercontent.com/moonpieluvincutie/Spotify/main/top10s%20(version%201).xlsb.csv', (d) => ({
year: +d.year,
pop: d.pop,
genre: d['top genre'],
})).then((data) => {
// Transform the data into groups by genre and year
const groupedDataset = d3.rollups(data, v => d3.mean(v, d => d.pop), d => d.year, d => d.genre)
const flattenedDataset = groupedDataset.map(([year, values]) => {
return {
year,
...values.reduce((acc, [genre, pop]) => {
acc[genre] = pop;
return acc;
}, {}),
}
});
const genres = d3.rollups(data, () => null, d => d.genre).map(([genre]) => genre);
// Stack grouped data
const dataset = d3.stack()
.keys(genres)
.value((d, key) => d[key] ?? 0)
.order(d3.stackOrderNone)
.offset(d3.stackOffsetNone)
(flattenedDataset)
// Assign x, y and z (aka genre group) domains
x.domain(groupedDataset.map(([year]) => year));
y.domain([0, d3.max(
dataset, (d) => {
return d3.max(d, ([y]) => y)
}
)]).nice();
z.domain(genres);
const yAxis = d3.axisLeft()
.scale(y)
.ticks(5)
.tickSize(-width, 0, 0)
.tickFormat( (d) => { return d } );
const xAxis = d3.axisBottom()
.scale(x)
g.append("g")
.selectAll("g")
.data(dataset)
.enter().append("g")
.attr("fill", (d) => z(d.key))
.selectAll("rect")
.data((d) => d)
.enter().append("rect")
.attr("x", (d) => x(+d.data.year))
.attr("y", (d) => y(d[1]))
.attr("height", (d) => y(d[0]) - y(d[1]))
.attr("width", x.bandwidth());
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
g.append("g")
.attr("class", "axis")
.call(yAxis)
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks().pop()) + 0.5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.text("Popularity");
const legend = g.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "end")
.selectAll("g")
.data(genres.slice().reverse())
.enter().append("g")
.attr("transform", (d, i) => "translate(0," + i * 20 + ")");
legend.append("rect")
.attr("x", width - 19)
.attr("width", 19)
.attr("height", 19)
.attr("fill", z);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9.5)
.attr("dy", "0.32em")
.text((d) => d);
})
.axis .domain {
display: none;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script src="https://d3js.org/d3.v6.min.js" charset="utf-8"></script>
</head>
<body>
<!-- chart appended to svg in javaScript -->
<svg width="960" height="500"></svg>
</body>
</html>