javascript - 创建 d3 条形图
问题描述
我有一个 csv 文件,其中包含 2010-2019 年 Spotify 上的热门歌曲。每首歌的属性都有流派、艺术家、年份、流行度、bpm 等。我想创建一个条形图,其中 x 轴为年份,y 轴为流行度,然后每个流派代表一个图表上的不同颜色条。我已附上我正在使用的 csv 文件,任何帮助将不胜感激。
解决方案
我假设您正在寻找堆叠条,但如果没有,未堆叠将是更简单的方法。使用此示例作为基础,我们需要首先转换数据以表示您在帖子中描述的配置。本质上,我们需要按 分组year
,genre
并以适合堆叠的方式对其进行转换。例如像这样的东西......
const data = [
{ month: new Date(2015, 0, 1), apples: 3840, bananas: 1920, cherries: 960, dates: 400 },
];
或者在你的数据的情况下是这样的......
const data = [
{ year: 2015, genre1: popularity1, genre2: popularity1, ...rest },
];
幸运的是d3
提供了很多transformations
供我们使用。
注意:我在以下代码中使用的是 d3 v6 而不是 v4(如示例),API 略有不同,但概念基本相同。
我创建了以下工作片段供您查看。我曾经d3.rollups
以上述方式对数据进行分组。我建议您记录每个转换以查看新结构的外观。d3.mean
如果每年每种类型的歌曲超过 1 首,我就会选择流行度。一旦你有了上面定义的结构,我们将它传递给每个系列(又名组或流派)d3.stack
的正确y0
和y1
值的数据格式。
最后,通过将堆叠的数据集添加到基本示例中的渲染逻辑并进行一些调整,您将得到如下所示的内容。抱歉,它有点粗糙,但这应该为您提供一个很好的迭代起点。
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>
推荐阅读
- regression - 如何为 lme4 中的特定对比度指定随机斜率?
- google-apps-script - 当 2 列应用程序脚本中的值匹配时删除行
- sql-server - 如何获得一年或更短的剩余到期日
- java - 随机开关绑定取决于填充的 TextViews
- javascript - 你如何用反应预加载背景img?
- r - 在 Y 轴上创建一个最左列的箱线图
- spring - Spring MVC 在不应该提交表单时构造子对象
- android - 如何使用 JBitmap/JBitmapFactory 解码 TMemoryStream 中的 PNG 图像
- android - 如何在kotlin中设置多个警报?
- docker - 如何在 ECR 上下载或标记未标记的图像?