首页 > 解决方案 > 创建 d3 条形图

问题描述

我有一个 csv 文件,其中包含 2010-2019 年 Spotify 上的热门歌曲。每首歌的属性都有流派、艺术家、年份、流行度、bpm 等。我想创建一个条形图,其中 x 轴为年份,y 轴为流行度,然后每个流派代表一个图表上的不同颜色条。我已附上我正在使用的 csv 文件,任何帮助将不胜感激。

链接到 csv: https ://github.com/moonpieluvincutie/Spotify

标签: javascripthtmld3.jswebstorm

解决方案


我假设您正在寻找堆叠条,但如果没有,未堆叠将是更简单的方法。使用此示例作为基础,我们需要首先转换数据以表示您在帖子中描述的配置。本质上,我们需要按 分组yeargenre并以适合堆叠的方式对其进行转换。例如像这样的东西......

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的正确y0y1值的数据格式。

最后,通过将堆叠的数据集添加到基本示例中的渲染逻辑并进行一些调整,您将得到如下所示的内容。抱歉,它有点粗糙,但这应该为您提供一个很好的迭代起点。

示例预览

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>


推荐阅读