You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于多对象最新记录计算累计值并绘制D3折线图

需求:用D3实现多对象累计值随时间变化的折线图

(注:底部已更新部分解答)

我希望创建一个D3折线图,展示多个值随时间变化的对象的累计和。

数据结构如下:

const data = [
    {"id": 1, "object": "A", "date": "2019-10-01", "value": 324},
    {"id": 2, "object": "A", "date": "2019-10-06", "value": 123},
    {"id": 3, "object": "A", "date": "2019-10-12", "value": 37},
    {"id": 4, "object": "B", "date": "2019-09-24", "value": 16},
    {"id": 5, "object": "B", "date": "2019-10-16", "value": 17},
    {"id": 6, "object": "B", "date": "2019-10-24", "value": 14},
    {"id": 7, "object": "B", "date": "2019-10-25", "value": 44},
    {"id": 8, "object": "C", "date": "2019-10-02", "value": 62},
    {"id": 9, "object": "C", "date": "2019-10-06", "value": 74},
    {"id": 10, "object": "C", "date": "2019-10-16", "value": 64},
];

以日期2019-10-12为例,我难以将当天的变更(ID 3)与其他对象的最新值进行聚合。例如,2019-10-12的数据点应为ID 3、ID 4和ID 9的数值之和。

以下代码仅展示当天的分组值,未包含其他对象的最新聚合值。例如,对于2019-10-16,它只会累加ID 5和ID 10的值,但未包含应加入的ID 3。

const svg = DOM.svg(500 , 500);
const parse = d3.timeParse("%Y-%m-%d");
data.forEach(d => {d.date = parse(d.date)});

const groupedData = d3.rollups(data, v => d3.sum(v, d => d.value), d => d.date)

const dateMinMax = d3.extent(groupedData, d => d[0]) // d[0] is date
const xScale = d3.scaleTime().domain(dateMinMax).range([0, 500])
const valueMinMax = d3.extent(groupedData, d => d[1]) // d[1] is value
const yScale = d3.scaleLinear().domain(valueMinMax).range([0, 500])
d3.select(svg).selectAll('circle').data(groupedData).enter()
    .append('circle')
      .attr('fill', 'green')
      .attr('cy', d => yScale(d[1]))
      .attr('cx', d => xScale(d[0]))
      .attr("r", 5);

return svg

我的尝试

我生成了所有唯一日期的数组,然后用它创建了包含curr_date和当天所有变更映射的对象数组。我开始处理注释行,思路是创建循环从最近到最远遍历之前的日期,为映射中尚未存在的对象添加ID到curr_date的"changes"映射中,但我觉得这不是正确方向,因为遍历所有之前的日期效率不高。

const parse = d3.timeParse("%Y-%m-%d");
rawData.forEach(d => {
  d.date = parse(d.date)
});

const sortedData = rawData.sort((a, b) => d3.ascending(a.date, b.date));

const allActiveDates = sortedData.map(d => d.date.getTime())
const allActiveDatesNoDuplicates = [...new Set(allActiveDates)].map(d => new Date(d)) // remove duplicates.

const sortedDataLookup = d3.index(sortedData, d => d.date, d => d.id)

const changes =  allActiveDatesNoDuplicates.map((curr_date, index, array) => {
  
  //return array.slice(0,index).reverse().forEach()
  
  return {curr_date, changes: sortedDataLookup.get(curr_date)}
});

return changes

我还有另一个思路:使用LEAD(date) OVER (Partition by Object),为每一行添加startdate和enddate。然后可以执行...(伪代码)cumsum(value) by startdate - cumsum(value) by enddate。但我觉得为此引入SQL的开销也不合适。

有什么思路吗?谢谢!


更新

以下代码可以运行,它返回一个对象数组,每个对象包含一个日期和截至该日期所有对象的最新记录数组。所以我认为将其转换为所需的D3可视化并不困难。

但,我觉得这个方案的扩展性不好,仍希望得到指导。我认为它扩展性差是因为availableRecords会变得非常大,而且每个映射内部还有更多映射,这包含大量循环。我觉得这是一个相对简单的问题,但我把它复杂化了。

{
  
  const rawData = [
    {"id": 1, "object": "A", "date": "2019-10-01", "value": 324},
    {"id": 2, "object": "A", "date": "2019-10-06", "value": 123},
    {"id": 3, "object": "A", "date": "2019-10-12", "value": 37},
    {"id": 4, "object": "B", "date": "2019-09-24", "value": 16},
    {"id": 5, "object": "B", "date": "2019-10-16", "value": 17},
    {"id": 6, "object": "B", "date": "2019-10-24", "value": 14},
    {"id": 7, "object": "B", "date": "2019-10-25", "value": 44},
    {"id": 8, "object": "C", "date": "2019-10-02", "value": 62},
    {"id": 9, "object": "C", "date": "2019-10-06", "value": 74},
    {"id": 10, "object": "C", "date": "2019-10-16", "value": 64},
  ];

  const parse = d3.timeParse("%Y-%m-%d");
  rawData.forEach(d => {
    d.date = parse(d.date)
  });

  const sortedData = rawData.sort((a, b) => d3.ascending(a.date, b.date));

  const days = d3.groups(sortedData, d => d.date) // 2D Array (date array[object array])
  
  const changes = days.map((day, index, all_days) => {
    
    const availableRecords = all_days.slice(0,index+1)//.reverse()
    const availableRecordsFlat = availableRecords.map(d => d[1]).flat(1)

    const availableObjects = [...new Set(availableRecordsFlat.map(d => d.object))]
    const availableObjectsRecentRecord = availableObjects.map(d => {
      const records = availableRecordsFlat.filter(r => r.object == d).sort((a, b) => d3.descending(a.date, b.date));
      const recentRecord = records[0]
      
      return {d, recentRecord}
    });
    
    const date = day[0]
    return {date: date, currentRecords: availableObjectsRecentRecord}
  });

  return changes
}

内容的提问来源于stack exchange,提问作者louis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 10:32:04