微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

我是 JavaScript 和 D3.js 的新手,我正在尝试创建一个 Angular 应用程序,它带有堆叠条形图,将每天的不同测试结果“OK”、“NOK”和“Aborted”的数量可视化为一个堆叠y 轴为 bar,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
  };
})

如果我理解正确的话,这会给我一个带有 用于每个测试结果。

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

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

现在我有一个嵌套的 Map,以日期(每天只有一个)为键,将值作为测试结果,每个值都带有当天的总和。

我有几个关于如何继续的例子,不需要使用汇总方法,例如here 并尝试使其适应我的地图:

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

但是我不知道如何去做。 欢迎任何有关如何准备我的数据的帮助。也许我不应该使用 rollup 方法,但我觉得它非常适合我的需求。

解决方法

如果我们的想法是您最终希望利用一些现有的代码示例,因此需要这样的数据:

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. 您正在将数据从密集转换为稀疏,因为您需要为例如创建零数据点NOK 上的 20-05-2021,因为原始数据中不存在该数据点。

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

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

要使用 d3.rollup 实现这种重新整形的数据:

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

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

  • 然后迭代 columnsResult 的不同之处)以评估您是否需要获取内部 Map 中的值(当天的总和 {{ 1}}) 或插入 Result,因为那天没有发生 0(每点 (1))。

    在示例中,这一行:

    Result

    意思是:对于一个列标题,如果内部resultColumns.map(col => row[col] = innerMap.has(col) ? innerMap.get(col) : 0); Map 那个列标题作为键,那么has这个值,否则为零。

工作示例:

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(...)forEach 方法(这是不同的从 Array.prototype.forEach) 迭代外部 Map,然后执行类似的操作来评估是否需要创建零数据点:

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,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,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; }

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。