多系列图表 - 时间(年份)间隔 x 轴叠加多年的数据

4

背景

我正在使用D3 4.x构建一个多系列折线图(参见示例),以比较多年的年度数据,其中Y轴是整个年份可能值的范围,X轴表示01/01-12/31日期之间的时间间隔。

问题

在构建此图表时,似乎时间间隔是特定于年份的,从而阻止了不同年份的数据在同一张图表上呈现。要重申的是,当前的方法是在同一张图表上叠加每一年的数据;每年一个线条。为实现这一点,对年份特定的时间间隔进行大量(和错误的)数据操作。

在下面的示例图表中,可以看到2014年从左侧接近(颜色:蓝色),2015年继续向右(颜色:橙色)。我希望看到这两个重叠在一起。

enter image description here

致谢 / 警告

承认了将多年的数据重叠在一起进行比较的一些复杂性(例如,每条线代表一年)。例如,如何处理具有闰日的年份与那些没有闰日的年份。这在程序上使事情变得复杂,但在视觉上不应该是一个问题。

问题

1) 当年份妨碍时,使用时间间隔的最佳方法是什么?

2) 是否有更好、更优雅的方法创建多系列折线图,表示多年层叠在一起,而不是将各个部分拼凑在一起?

3) 是否有一种方法(非静态地)从轴中移除年份(即2015),并改用月份(1月)?

4) 是否可以将X轴上的文本向右移动(程序化地),以便月份出现在刻度标记之间(在我看来,这样更加准确)?

1个回答

3
有几种方法可以解决这个问题。实际上,它们非常多,以至于这个问题被认为是“过于宽泛”。当然,每种方法都有其优缺点。例如,一个简单的方法是完全舍弃时间尺度,使用一个点尺度作为x轴。
我建议采用一种不同的方法,稍微复杂一些,但仍然使用时间尺度:创建“若干”不同的时间尺度,每年一个。它们每个都具有“相同的范围”:这样,每年的线条可以在彼此之上叠加。
例如,从2013年到2017年:
var years = [2013, 2014, 2015, 2016, 2017];

var scales = {};

years.forEach(d => {
    scales["scale" + d] = d3.scaleTime()
        .domain([new Date(+d, 0, 1), new Date(+d, 11, 31)])
        .range([30, 470]);
});

那么,您可以继续使用时间轴来表示x轴。问题在于:时间轴处理每年都不同。有些年份有更多的天数,有些年份则较少。甚至一天的小时数也可能有所不同。
因此,我选择了2017年,并仅在x轴上显示了该年的月份。结果可能不是非常精确,但如果您在x轴上拥有整个年份,则足够好。
这是演示。数据是随机生成的,每条线代表一个不同的年份(从2013年到2017年),从1月1日到12月31日:

var svg = d3.select("body").append("svg")
  .attr("width", 500)
  .attr("height", 200);

var color = d3.scaleOrdinal(d3.schemeCategory10);

var thisYear;

var line = d3.line()
  .x(d => scales[thisYear](d.x))
  .y(d => yScale(d.y))
  .curve(d3.curveMonotoneX);

var years = [2013, 2014, 2015, 2016, 2017];

var data = [];

years.forEach((d, i) => {
  data.push({
    year: d,
    points: []
  });
  for (var j = 0; j < 12; j++) {
    data[i].points.push({
      y: Math.random() * 80 + 20,
      x: new Date(d, j)
    })
  }
});

var scales = {};

var yScale = d3.scaleLinear()
  .domain([0, 100])
  .range([170, 30]);

years.forEach(d => {
  scales["scale" + d] = d3.scaleTime()
    .domain([new Date(+d, 0, 1), new Date(+d, 11, 31)])
    .range([30, 470]);
});

var paths = svg.selectAll("foo")
  .data(data)
  .enter()
  .append("path");

paths.attr("stroke", (d, i) => color(i))
  .attr("d", d =>{
  thisYear = "scale" + d.year;
  return line(d.points);
  })
  .attr("fill", "none");
  
var xAxis = d3.axisBottom(scales.scale2017).tickFormat(d=>d3.timeFormat("%b")(d));
var yAxis = d3.axisLeft(yScale);

var gX = svg.append("g").attr("transform", "translate(0,170)").call(xAxis);

var gY = svg.append("g").attr("transform", "translate(30,0)").call(yAxis);
<script src="https://d3js.org/d3.v4.js"></script>


两个问题和一个陈述。首先,如果在第var paths = svg.selectAll("foo")行的引号内,例如“foo”,本质上可以是任何东西且图表仍然有效,那么selectAll的意义是什么?这不是我见过的唯一例子。其次,enter()确切地做什么?最后,我欣赏这个例子。我没有考虑到创建多个比例尺,这是一个非常干净和明显正确的方法。 - DanCat
1
我在这里写了一篇文档,回答了你的两个问题:http://stackoverflow.com/documentation/d3.js/2135/selections/16948/the-role-of-placeholders-in-enter-selections#t=201703182330550339512 - Gerardo Furtado
作为与问题相关的代码记录,基于这个答案的工作流程图被打上标签,并可以在 github 上进行查看。 - DanCat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接