从许多对象的最新记录(Javascript 和 D3)中获取运行总和

Get Running Sum From Many Object's Most Recent Record (Javascript & D3)

提问人:louis 提问时间:11/14/2023 最后编辑:louis 更新时间:11/14/2023 访问量:27

问:

(注:底部更新了部分答案)

我希望创建一个 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(它应该包括 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“更改”映射中,但我觉得我走错了路, 因为循环遍历所有以前的日期似乎效率不高。


  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

我的另一个想法是做一个 ,所以我现在每行都有一个开始日期和一个结束日期。那我就可以做...(伪代码如下)... .但是,不要觉得添加 SQL 的开销也是正确的方法。LEAD(date) OVER (Partition by Object)cumsum(value) by startdate - cumsum(value) by enddate

有什么想法吗?谢谢!

-------------------更新-------------------

下面的代码有效。它返回一个对象数组。每个对象都有一个日期,以及截至该日期的所有最新对象记录的数组。因此,我认为这很难变成我正在寻找的 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
}
JavaScript D3.js javascript 对象 汇总

评论


答: 暂无答案