如何使用汇总为d3堆叠条形图准备数据



我是JavaScript、D3.js和atm的新手,我试图创建一个Angular应用程序,该应用程序使用堆叠条形图,将每天的不同测试结果"OK"、"NOK"one_answers"Aborted"的数量可视化为y轴上的堆叠条形图和x轴上的日期。

我的data.csv如下所示:

Date;Result
20-05-2021 17:54:02;Aborted
20-05-2021 17:55:24;OK
21-05-2021 21:48:45;NOK
22-05-2021 17:55:24;OK
22-05-2021 17:54:02;Aborted
22-05-2021 17:55:24;OK

由于我需要每天计算结果,我首先使用timeParse将日期解析为正确的格式,然后使用timeDay.floor方法来消除时间:

let jsonObj = await d3.dsv(";", "/assets/data.csv", function (d) {
let time = timeParse("%d-%m-%Y %-H:%M:%S")(d['Date'])
let date = timeDay.floor(new Date(time));
return {
Date: date,
Result: d.Result
};
})

如果我理解正确,这会给我一个数组,其中<日期|string>对于每个测试结果。

现在总结一下我使用汇总的同一天的测试结果:

let data_count = rollup(jsonObj, v => v.length, d => d.Date, d => d.Result)

现在我有了一个嵌套的Map,它以日期(每天只有一个(为键,以值为测试结果,每个值都有当天的总数。

我举了几个不需要使用汇总方法的例子,比如这里并试图将其适应我的地图:

let processed_data = data_count.map( d => {
let y0 = 0;
let total = 0;
return {
date: d.key,
//call second mapping function on every test-result
values: (d.valules).map( d => {
let return_object = {
result: d.key,
count: d.values,
y0: y0,
y1: y0 + d.values
};
//calculate the updated y0 for each new test-result on a given date
y0 = y0 + d.values;
//add the total for a given test-result to the sum total for that test-result
total = total + d.values;
return return_object;  
}),
total: total
};
});

但是我得到了一个错误:

Property 'map' does not exist on type 'InternMap<Date, InternMap<string, number>>'.ts(2339)

我知道地图功能不能用在地图上。我还试图将这部分重写为单独的函数,以不使用map函数,但它也不起作用。也许我有一些语法错误或其他什么,但我得到了:

TypeError: Cannot read property 'key' of undefined

我可能需要对映射的值使用get((方法,但不确定如何实现它

现在我纠结于如何继续为堆叠条形图准备数据。在bl.ocks.org的这个例子中,CSV看起来不同。我想以某种方式操纵我的数据,以适应这个例子的形状:

Date;OK;NOK;Aborted
20-05-2021 17:54:02;1;0;1
21-05-2021 21:48:45;0;1;0
22-05-2021 17:55:24;2;0;1

但我不知道该怎么做。欢迎任何关于如何准备我的数据的帮助。也许我不应该使用汇总法,但我觉得它非常适合我的需求。

如果我们运行时认为您最终想要利用一些现有的代码示例,因此需要这样的数据:

Date;OK;NOK;Aborted
20-05-2021 17:54:02;1;0;1
21-05-2021 21:48:45;0;1;0
22-05-2021 17:55:24;2;0;1

有几件事需要考虑:

  1. 您正在将数据从密集转换为稀疏,因为您需要为20-05-2021上的NOK创建一个零数据点,因为原始数据中不存在该数据点。

  2. 您需要Result值的不同值作为转换数据中的行标题。您可以使用:const columns = [...new Set(data.map(d => d.Result))];获得此

  3. 您发现不能在Map对象上使用Array.protoype.map,因此您只需要考虑在Map对象上迭代的其他选项(下面给出的两个选项(,例如使用Map.prototype.entries()Map.prototype.forEach

使用d3.rollup:实现此重新整形数据

  • d3.rollup语句包装在Array.from(...)中,这将获得Map.prototype.entries(),您可以将其传递给reduce函数。

  • reduce函数中,您可以访问外部Map[key, value]对,其中value本身就是Map(由d3.rollup嵌套(

  • 然后迭代columns(Result的区别(,以评估是否需要取内部Map(当天Result的总和(中的值,或者插入一个0,因为Result在当天没有出现(每点(1((。

    在示例中,此行:

    resultColumns.map(col => row[col] = innerMap.has(col) ? innerMap.get(col) : 0);

    意味着:对于列标题,如果内部Maphas将该列标题作为键,则get为每个键的值,否则为零。

工作示例:

// your data setup
const csv = `Date;Result
20-05-2021 17:54:02;Aborted
20-05-2021 17:55:24;OK
21-05-2021 21:48:45;NOK
22-05-2021 17:55:24;OK
22-05-2021 17:54:02;Aborted
22-05-2021 17:55:24;OK`;
// your data processing
const data = d3.dsvFormat(";").parse(csv, d => {
const time = d3.timeParse("%d-%m-%Y %-H:%M:%S")(d.Date);
const date = d3.timeDay.floor(new Date(time));
return {
Date: date,
Result: d.Result
}
});
// distinct Results for column headers per point (2)
const resultColumns = [...new Set(data.map(d => d.Result))];
// cast nested Maps to array of objects
const data_wide = Array.from( // get the Map.entries()
d3.rollup(
data,
v => v.length,
d => d.Date,
d => d.Result
)
).reduce((accumlator, [dateKey, innerMap]) => {
// create a 'row' with a Date property
let row = {Date: dateKey}
// add further properties to the 'row' based on existence of keys in the inner Map per point (1)
resultColumns.map(col => row[col] = innerMap.has(col) ? innerMap.get(col) : 0);
// store and return the accumulated result
accumlator.push(row);
return accumlator;
}, []);
console.log(data_wide);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>

如果你喜欢一种更程序化(也许更可读(的方法来获得相同的结果,你可以避免Array.from(...),并使用MapforEach方法(与Array.prototype.forEach不同(来迭代外部Map,然后执行类似的操作来评估是否需要创建零数据点:

// your data setup
const csv = `Date;Result
20-05-2021 17:54:02;Aborted
20-05-2021 17:55:24;OK
21-05-2021 21:48:45;NOK
22-05-2021 17:55:24;OK
22-05-2021 17:54:02;Aborted
22-05-2021 17:55:24;OK`;
// your data processing
const data = d3.dsvFormat(";").parse(csv, d => {
const time = d3.timeParse("%d-%m-%Y %-H:%M:%S")(d.Date);
const date = d3.timeDay.floor(new Date(time));
return {
Date: date,
Result: d.Result
}
});
// distinct Results for column headers per point (2)
const resultColumns = [...new Set(data.map(d => d.Result))];
// rollup the data
const rolled = d3.rollup(
data,
v => v.length,
d => d.Date,
d => d.Result
);
// create an output array
const data_wide = [];
// populate the output array
rolled.forEach((innerMap, dateKey) => {
// create a 'row' with the date property
const row = {Date: dateKey}
// add further properties to the 'row' based on existence of keys in the inner Map per point (1)
resultColumns.map(col => row[col] = innerMap.has(col) ? innerMap.get(col) : 0);
// store the result
data_wide.push(row);
});
console.log(data_wide);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>

最新更新