【问题标题】:Multi-series chart - time (year) interval x-axis overlaying multiple years of data多系列图表 - 时间(年)间隔 x 轴覆盖多年数据
【发布时间】:2017-03-18 03:39:54
【问题描述】:

背景

我正在使用 D3 4.x 构建多系列折线图 (see an example) 来比较多年的年度数据,其中 Y 轴是多年来可能值的范围,X-坐标轴表示 01/01-12/31 日期之间的时间间隔。

问题

在构建此图表时,时间间隔似乎是特定于年份的,从而阻止了不同年份的数据在同一个图表上呈现。重申一下,目前的方法是在同一张图表上叠加每年;每年一行。要做到这一点,时间间隔是特定年份的,需要大量(和错误)的数据处理。

在下面的示例图表中,我们可以看到 2014 年从左侧(颜色:蓝色)接近,而 2015 年继续向右(颜色:橙色)。我希望看到的是这两者相互叠加。

确认/警告

承认通过将数据相互叠加(即多行,每行代表一年)来比较多年数据的一些复杂性。例如,与没有闰日的年份相比,如何处理年份。这以编程方式使事情变得复杂,但视觉上不应该成为问题。

问题

1) 当一年刚刚过去时,使用时间间隔的最佳方法是什么?

2) 有没有更好、更优雅的方法来创建代表多个年份的多系列折线图,而不是拼凑在一起?

3) 有没有办法(非静态)从轴中删除年份(即 2015 年)并改用月份(一月)?

4) 是否可以将 X 轴上的文本向右移动(以编程方式),以便月份出现在刻度线之间(IMO,它在视觉上更准确)?

【问题讨论】:

    标签: javascript d3.js charts


    【解决方案1】:

    有几种方法可以解决这个问题。实际上,他们中的很多人都认为这个问题太宽泛了。当然,每种方法都有其优点和缺点。例如,一个简单的方法是完全放弃时间尺度,并为 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);
    &lt;script src="https://d3js.org/d3.v4.js"&gt;&lt;/script&gt;

    【讨论】:

    • 两个问题和一个陈述。首先,selectAllvar paths = svg.selectAll("foo") 的意义何在?如果引号内的文本(在本例中为“foo”)本质上可以是任何内容并且图表仍然有效?这不是我见过这种模式的唯一例子。其次,enter() 到底是做什么的?最后,我很欣赏这个例子。我没有考虑创建多个音阶,这是一种非常干净且明显正确的方法。
    • 我在这里写了一个文档来回答你的两个问题:stackoverflow.com/documentation/d3.js/2135/selections/16948/…
    • 作为与问题相关的代码的记录,工作图表在很大程度上基于此答案,已标记并可供在github 上查看。
    猜你喜欢
    • 1970-01-01
    • 2020-12-31
    • 2014-09-21
    • 2019-08-10
    • 1970-01-01
    • 2016-02-01
    • 2015-05-12
    • 2019-05-07
    • 1970-01-01
    相关资源
    最近更新 更多