如何基于多对象最新记录计算累计值并绘制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

