0

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.

1
  • 1
    Will the categories always be A, B, or C? Or will they have different names? Commented Nov 12, 2021 at 2:51

3 Answers 3

1

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));

Sign up to request clarification or add additional context in comments.

3 Comments

for (const count of catArr.values()) is the same as for (const count of catArr).
Huh. So it is. Thanks @Unmitigated. I don't work with sets a lot.
Thank you! I have adjusted your solution to fit my exact needs.
1

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);

2 Comments

Unfortunately, the ??= operator throws a Module parse failed error on my end.
@Echo You can change it to (acc[date] = acc[date] || {date, countA: 0, countB: 0, countC: 0}).
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.