如何使用汇总为 d3 堆积条形图准备数据
How to prepare data for d3 stacked barchart using rollup
我是 JavaScript 和 D3.js 的新手,我正在尝试创建一个 Angular 应用程序,其中包含不同测试结果数量的堆叠条形图可视化 'OK' ,每天的 'NOK' 和 '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
};
})
如果我没理解错的话,这给了我一个包含 < date | 的数组。 string > 每个测试结果。
现在总结一下我使用汇总的相同天数的测试结果计数:
let data_count = rollup(jsonObj, v => v.length, d => d.Date, d => d.Result)
现在我有一个嵌套的地图,其中日期(每天只有一个)作为键,值作为测试结果,每个值都带有当天的总和。
我发现了几个不需要使用汇总方法就可以继续的示例,例如
并尝试使其适应我的地图:
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
有几件事需要考虑:
您正在将数据从密集型数据转换为稀疏型数据,因为您需要为例如NOK
在 20-05-2021
上,因为原始数据中不存在该数据点。
您需要 Result
值的不同作为转换数据中的行 header。您可以通过以下方式获得:const columns = [...new Set(data.map(d => d.Result))];
您发现您不能在 Map
object 上使用 Array.protoype.map
,因此您只需要考虑其他选项(下面提供两个)迭代 Map
objects 例如使用 Map.prototype.entries()
or Map.prototype.forEach
.
用 d3.rollup
实现此 re-shaped 数据:
将 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);
的意思是:对于一个列header,如果内Map
has
那个列header作为key,然后 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(...)
并使用 Map
的 forEach
method (which is different from 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>
我是 JavaScript 和 D3.js 的新手,我正在尝试创建一个 Angular 应用程序,其中包含不同测试结果数量的堆叠条形图可视化 'OK' ,每天的 'NOK' 和 '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
};
})
如果我没理解错的话,这给了我一个包含 < date | 的数组。 string > 每个测试结果。
现在总结一下我使用汇总的相同天数的测试结果计数:
let data_count = rollup(jsonObj, v => v.length, d => d.Date, d => d.Result)
现在我有一个嵌套的地图,其中日期(每天只有一个)作为键,值作为测试结果,每个值都带有当天的总和。
我发现了几个不需要使用汇总方法就可以继续的示例,例如
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
有几件事需要考虑:
您正在将数据从密集型数据转换为稀疏型数据,因为您需要为例如
NOK
在20-05-2021
上,因为原始数据中不存在该数据点。您需要
Result
值的不同作为转换数据中的行 header。您可以通过以下方式获得:const columns = [...new Set(data.map(d => d.Result))];
您发现您不能在
Map
object 上使用Array.protoype.map
,因此您只需要考虑其他选项(下面提供两个)迭代Map
objects 例如使用Map.prototype.entries()
orMap.prototype.forEach
.
用 d3.rollup
实现此 re-shaped 数据:
将
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);
的意思是:对于一个列header,如果内
Map
has
那个列header作为key,然后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(...)
并使用 Map
的 forEach
method (which is different from 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>