【问题标题】:Creating d3 bar chart创建 d3 条形图
【发布时间】:2021-04-24 16:17:14
【问题描述】:

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

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

【问题讨论】:

标签: javascript html d3.js webstorm


【解决方案1】:

我假设您正在寻找堆叠条,但如果不是,未堆叠将是更简单的方法。使用这个example 作为基础,我们需要首先转换数据以表示您在帖子中描述的配置。本质上,我们需要按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 略有不同但概念基本相同。

我创建了以下工作 sn-p 供您查看。我使用d3.rollups 以上述方式对数据进行分组。我建议您记录每个转换以查看新结构的外观。如果每年每种类型的歌曲超过 1 首,我就选择了受欢迎程度的d3.mean。一旦您获得了上述定义的结构,我们将其传递给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>

【讨论】:

  • 非常感谢!您解释每个步骤的方式对理解该过程非常有帮助。我刚刚在学习 d3,这非常有用,我很感激。
  • 只是为了澄清一下,在您给出的示例中,我需要如何按年份和类型对数据进行分组。我会写出每首歌曲的年份、流派和流行分数?
  • 我不确定您是否在我发表评论时收到通知,或者我是否必须标记您,所以我两者都做。我的道歉。 @Nickofthyme
  • 不用担心,是的,您会收到关于帖子的 cmets 通知。不确定您要问什么,但这实际上取决于您希望如何可视化数据。您可以按流派然后按年份对所有歌曲进行分组,并像我在示例中所做的那样取平均值,然后在工具提示中列出所有歌曲。或者,您可以将所有歌曲渲染为按流派着色的单独条形图。如果我是你,我会查看observablehq.com/@d3 上的其他示例,看看其他人如何显示与你的数据相似的数据。一旦你知道你想要它的样子,你就可以理解你需要的数据操作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-10
  • 1970-01-01
相关资源
最近更新 更多