I am trying to create a stacked bar graph, but I can't figure out how to reshape my data.
The data coming from the backend looks something like below, where it has a date
and some category
key
[
{date: 'January', category: 'A'},
{date: 'January', category: 'B'},
{date: 'February', category: 'A'},
{date: 'February', category: 'B'},
{date: 'February', category: 'C'},
{date: 'February', category: 'B'},
...
]
I need to reshape this data into the form like below, where it will group them into their respective months, and then count the number of times each category was seen
[
{name: 'January', countA: 1, countB: 1, countC: 0 },
{name: 'February', countA: 1, countB: 2, countC: 1 }
...
]
So far, I've only been able to count the total number of categories per month using the reduce
function such as
const filterData = data.reduce((groups, curr) => {
const {January = 0, February = 0, ...} = groups;
switch(curr.date){
case 'January': return {...groups, January: January 1}
case 'February': return {...groups, February: February 1}
...
}
}, {})
But this won't work for the stacked bar graph.
CodePudding user response:
You can use Array#reduce
with an object to store the counts for each month.
let arr = [
{date: 'January', category: 'A'},
{date: 'January', category: 'B'},
{date: 'February', category: 'A'},
{date: 'February', category: 'B'},
{date: 'February', category: 'C'},
{date: 'February', category: 'B'},
];
let res = Object.values(arr.reduce((acc, {date, category})=>{
(acc[date] ??= {date, countA: 0, countB: 0, countC: 0})['count' category];
return acc;
}, {}));
console.log(res);
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
Based on your question we can't really be sure there will be only three categories so here's a solution that will deal with n number of them. It's longer than you may want but hopefully not too verbose. (I've used a for...of
loop instead of reduce
at the end but you can swap them if you want.)
const data = [
{date: 'March', category: 'Z'},
{date: 'January', category: 'A'},
{date: 'January', category: 'B'},
{date: 'February', category: 'A'},
{date: 'November', category: 'D'},
{date: 'February', category: 'M'},
{date: 'February', category: 'B'},
{date: 'February', category: 'C'},
{date: 'February', category: 'B'},
{date: 'January', category: 'D'}
];
// Create a deduped array of elements with
// a `count[letter]` format by creating
// a Set of mapped categories
const catArr = new Set(data.map(obj => {
return `count${obj.category}`;
}));
// Create a new counts object from the set
// setting each count property to 0
const counts = {};
for (const count of catArr) {
counts[count] = 0;
}
// Iterate over the data. If the date key isn't
// on the output object, add it and set it where
// one of the properties is the date, and the others
// are a copy of the categories
const out = {};
for (const obj of data) {
const { date, category } = obj;
const count = `count${category}`;
out[date] = out[date] || { name: date, ...counts };
out[date][count];
}
// Get the values of the output object
console.log(Object.values(out));
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
Since the other answers have covered approaches using reduce
and for...of
, I'll add another approach that is pretty straightforward, using forEach
.
More on forEach here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
let months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
let cleanData =[];
let dirtyData = [];
// get original data
dirtyData = getData();
// iterate through each month
months.forEach(month => {
let monthEntries = dirtyData.filter( e => e.date == month);
let aCount = 0;
let bCount = 0;
let cCount = 0;
// for each month, count how many enteries there were in each category
monthEntries.forEach(e => {
switch (e.category.toLowerCase()){
case "a":
aCount ;
break;
case "b":
bCount ;
break;
case "c":
cCount ;
break;
}
});
// push the data to a new cleanData array in our preferred format
cleanData.push({name: month, countA: aCount, countB: bCount, countC: cCount})
});
// log results
console.log(cleanData);
// function to retreive original data
function getData() { return [
{date: 'January', category: 'A'},
{date: 'January', category: 'B'},
{date: 'February', category: 'A'},
{date: 'February', category: 'B'},
{date: 'February', category: 'C'},
{date: 'March', category: 'B'},
{date: 'March', category: 'A'},
{date: 'March', category: 'B'},
{date: 'March', category: 'C'},
{date: 'March', category: 'B'},
{date: 'April', category: 'A'},
{date: 'April', category: 'B'},
{date: 'April', category: 'C'},
{date: 'April', category: 'B'},
{date: 'May', category: 'A'},
{date: 'May', category: 'B'},
{date: 'May', category: 'C'},
{date: 'May', category: 'B'},
{date: 'May', category: 'A'},
{date: 'May', category: 'B'},
{date: 'May', category: 'C'},
{date: 'May', category: 'B'},
{date: 'May', category: 'A'},
{date: 'May', category: 'B'},
{date: 'June', category: 'C'},
{date: 'June', category: 'B'},
{date: 'June', category: 'A'},
{date: 'July', category: 'B'},
{date: 'July', category: 'C'},
{date: 'July', category: 'B'},
{date: 'July', category: 'A'},
{date: 'July', category: 'B'},
{date: 'August', category: 'C'},
{date: 'September', category: 'B'},
{date: 'September', category: 'A'},
{date: 'September', category: 'B'},
{date: 'October', category: 'C'},
{date: 'October', category: 'B'},
{date: 'October', category: 'A'},
{date: 'October', category: 'B'},
{date: 'November', category: 'C'},
{date: 'November', category: 'A'},
{date: 'December', category: 'B'},
{date: 'December', category: 'B'},
];
}
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>