[英]How to prepare data for d3 stacked barchart using rollup
I am new to JavaScript and D3.js and atm trying to create an Angular App with a stacked bar chart visualisation of the number of different test results 'OK', 'NOK' and 'Aborted' of each day as a stacked bar in the y-axis and the dates as the x-axis. I am new to JavaScript and D3.js and atm trying to create an Angular App with a stacked bar chart visualisation of the number of different test results 'OK', 'NOK' and 'Aborted' of each day as a stacked bar in the y-轴和日期作为 x 轴。
My data.csv looks like this:我的 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
Since I need to count the results per day I first parse the date into the right format using timeParse and then I use the timeDay.floor method to get rid of the time:因为我需要每天计算结果,所以我首先使用 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
};
})
If I understand correctly, this gives me an array with < date |如果我理解正确,这会给我一个带有 < date | 的数组。 string > for every test result.每个测试结果的字符串 >。
To now summarize the counts of the test results of identical days I use rollup:现在总结我使用汇总的同一天的测试结果的计数:
let data_count = rollup(jsonObj, v => v.length, d => d.Date, d => d.Result)
Now I have a nested Map with the date (only one for each day) as key and the values as the test result each with the summed number for that day.现在我有一个嵌套的 Map ,其中日期(每天只有一个)作为键,值作为测试结果,每个都有当天的总和。
I foud several examples on how to continue that don't need to use the rollup method eg here and tried to adapt it to my map:我找到了几个关于如何继续不需要使用汇总方法的示例,例如这里并尝试将其调整到我的 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
};
});
but I am getting an error:但我收到一个错误:
Property 'map' does not exist on type 'InternMap<Date, InternMap<string, number>>'.ts(2339)
I understand that the map function can't be used on maps, I guess.我知道 map function 不能在地图上使用,我猜。 I also tried to rewrite this part into separate functions to not use the map function but it doesn't work either.我还尝试将此部分重写为单独的函数,以不使用 map function 但它也不起作用。 Maybe I have some syntax error or something but I get:也许我有一些语法错误或其他东西,但我得到:
TypeError: Cannot read property 'key' of undefined
I probably need to use the get() method for the values of a map, but not sure how to implement it.我可能需要对 map 的值使用 get() 方法,但不确定如何实现它。
Now I am stuck as to how I should continue to prepare the data for the stacked bar chart.现在,我不知道应该如何继续为堆积条形图准备数据。 In this example from bl.ocks.org the CSV looks different.在这个来自bl.ocks.org的示例中,CSV 看起来不同。 I was thinking about somehow manipulating my data to fit that example's shape:我正在考虑以某种方式操纵我的数据以适应该示例的形状:
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
But I have no idea how to go about it.但我不知道如何 go 关于它。 Any help as to how to prepare my data would be welcome.欢迎任何有关如何准备我的数据的帮助。 Maybe I should not use the rollup method, but I feel like it's a perfect fit for my needs.也许我不应该使用汇总方法,但我觉得它非常适合我的需求。
If we run with the idea that you ultimately want to leverage some existing code examples and therefore need data shaped like this:如果我们的想法是您最终想要利用一些现有的代码示例,因此需要如下形状的数据:
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
There are several things to consider:有几件事情需要考虑:
You are converting your data from dense to sparse in that you need to create a zero data point for eg NOK
on 20-05-2021
because that data point did not exist in the original data.您正在将数据从密集转换为稀疏,因为您需要在20-05-2021
年 5 月 20 日为例如NOK
创建一个零数据点,因为原始数据中不存在该数据点。
You need the distinct of the Result
values as row headers in the transformed data.您需要将不同的Result
值作为转换数据中的行标题。 You can get this with: const columns = [...new Set(data.map(d => d.Result))];
您可以通过以下方式获得: const columns = [...new Set(data.map(d => d.Result))];
You found that you can't use Array.protoype.map
on a Map
object, so you just need to consider the other options (two presented below) for iterating over Map
objects eg use Map.prototype.entries()
or Map.prototype.forEach
. You found that you can't use Array.protoype.map
on a Map
object, so you just need to consider the other options (two presented below) for iterating over Map
objects eg use Map.prototype.entries()
or Map.prototype.forEach
。
To achieve this re-shaped data with d3.rollup
:要使用d3.rollup
实现这种重新整形的数据:
Wrap the d3.rollup
statement in Array.from(...)
which gets you Map.prototype.entries()
which you can pass to a reduce
function.将d3.rollup
语句包装在Array.from(...)
中,得到Map.prototype.entries()
,您可以将其传递给reduce
function。
In the reduce
function you can then access the [key, value]
pairs of the outer Map
where value
is itself a Map
(nested by d3.rollup
)然后在reduce
function 中,您可以访问外部Map
的[key, value]
对,其中value
本身就是Map
(由d3.rollup
嵌套)
Then iterate columns
(the distinct of Result
) in order to assess if you need to either take the value in the inner Map
(aggregate of that days Result
) or insert a 0
because that Result
did not occur on that day (per point (1)).然后迭代columns
(不同的Result
),以评估您是否需要取内部Map
中的值(当天Result
的聚合)或插入0
因为Result
当天没有发生(每点(1 ))。
In the example, this line:在示例中,这一行:
resultColumns.map(col => row[col] = innerMap.has(col)? innerMap.get(col): 0);
Means: for a column header, if the inner Map
has
that column header as a key , then get
the value per that key , otherwise it is zero.意思是:对于 header 列,如果内部Map
has
该列 header作为键,则get
每个键的值,否则为零。
Working example:工作示例:
// 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>
If you prefer a more procedural (and maybe more readable) way to get to the same outcome you can avoid Array.from(...)
and use the Map
's forEach
method (which is different from Array.prototype.forEach
) to iterate the outer Map
and then perform a similar operation to assess if data points of zero need to be created:如果您更喜欢程序化(并且可能更具可读性)的方式来获得相同的结果,您可以避免使用Array.from(...)
并使用Map
的forEach
方法(与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>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.