简体   繁体   English

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

[英]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:有几件事情需要考虑:

  1. 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创建一个零数据点,因为原始数据中不存在该数据点。

  2. 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))];

  3. 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(...)并使用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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM