developer
developer

Reputation: 1685

Calculate Total Row using Javascript / TypeScript

I have the following array:

[ 
     {category: 'Category 1', data: [ {date: '01/04/2021', value: 10}, {date: '01/03/2021', value: 20}, {date: '01/02/2021', value: 5}] },
     {category: 'Category 2', data: [ {date: '01/04/2021', value: 8}, {date: '01/03/2021', value: 2}, {date: '01/02/2021', value: 15}] },
     {category: 'Category 3', data: [ {date: '01/04/2021', value: 7}, {date: '01/03/2021', value: 1}, {date: '01/02/2021', value: 5}] }
]

I would like to add a total row containing sum of values for each date for categories.

Example:

[ 
     {category: 'Category 1', data: [ {date: '01/04/2021', value: 10}, {date: '01/03/2021', value: 20}, {date: '01/02/2021', value: 5}] },
     {category: 'Category 2', data: [ {date: '01/04/2021', value: 8}, {date: '01/03/2021', value: 2}, {date: '01/02/2021', value: 15}] },
     {category: 'Category 3', data: [ {date: '01/04/2021', value: 7}, {date: '01/03/2021', value: 1}, {date: '01/02/2021', value: 5}] },
     {category: 'Total', data: [ {date: '01/04/2021', value: 25}, {date: '01/03/2021', value: 23}, {date: '01/02/2021', value: 30}] }
]

I am using the following code to do calculate the total:

const totalRow = categories.reduce((acc, curr) => {
  if(!acc['data']) {
    acc['data'] = [];
  }
  curr.data.forEach(d => {

      if(!acc['data'].find(a => a.date == d.date)) {
        acc['data'].push(d);
      } else {
        let monthData = acc['data'].find(a => a.date == d.date);
        monthData.value +=  d.value;
      }
  });
  return acc;
}, {});

categories.push({category: 'Total', ...totalRow});

However, the total row only contains Category 1 values.

How can I fix this? Also, is there a better way to calculate the total row?

Upvotes: 1

Views: 423

Answers (4)

Nguyễn Văn Phong
Nguyễn Văn Phong

Reputation: 14228

You can try this way.

const data = [{category:'Category 1',data:[{date:'01/04/2021',value:10},{date:'01/03/2021',value:20},{date:'01/02/2021',value:5}]},{category:'Category 2',data:[{date:'01/04/2021',value:8},{date:'01/03/2021',value:2},{date:'01/02/2021',value:15}]},{category:'Category 3',data:[{date:'01/04/2021',value:7},{date:'01/03/2021',value:1},{date:'01/02/2021',value:5}]}];

const total = data.reduce((acc, {data}) => {
  for(const {date, value} of data){
    acc[date] ??= {date: date, value: 0};
    acc[date].value += value;
  }
  
  return acc;
}, {});

console.log([...data, {category: 'Total',  data: Object.values(total)}]);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 1

uminder
uminder

Reputation: 26170

This could be done as follows:

const categories = [ 
     {category: 'Category 1', data: [ {date: '01/04/2021', value: 10}, {date: '01/03/2021', value: 20}, {date: '01/02/2021', value: 5}] },
     {category: 'Category 2', data: [ {date: '01/04/2021', value: 8}, {date: '01/03/2021', value: 2}, {date: '01/02/2021', value: 15}] },
     {category: 'Category 3', data: [ {date: '01/04/2021', value: 7}, {date: '01/03/2021', value: 1}, {date: '01/02/2021', value: 5}] }
];

const totalData = categories.map(o => o.data)
  .flat()
  .reduce((acc, v) => {
    const existing = acc.find(e => e.date == v.date);
    if (existing) {
      existing.value += v.value;
    } else {
      acc.push(JSON.parse(JSON.stringify(v)));
    }
    return acc;
  }, []);
  
categories.push({ category: 'Total', data: totalData });

console.log(categories)

Note that I'm using acc.push(JSON.parse(JSON.stringify(v))); in order to avoid the logging issue described here.

Stack Snippets logging nested objects weirdly

Upvotes: 2

Jacek Rojek
Jacek Rojek

Reputation: 1122

const categories = [{
    category: 'Category 1',
    data: [{
      date: '01/04/2021',
      value: 10
    }, {
      date: '01/03/2021',
      value: 20
    }, {
      date: '01/02/2021',
      value: 5
    }]
  },
  {
    category: 'Category 2',
    data: [{
      date: '01/04/2021',
      value: 8
    }, {
      date: '01/03/2021',
      value: 2
    }, {
      date: '01/02/2021',
      value: 15
    }]
  },
  {
    category: 'Category 3',
    data: [{
      date: '01/04/2021',
      value: 7
    }, {
      date: '01/03/2021',
      value: 1
    }, {
      date: '01/02/2021',
      value: 5
    }]
  }
]

const totalRow = categories.reduce((acc, curr) => {
  return curr.data.map(x => {
    const y = acc.find(i => i.date === x.date);
    return y ? {
      ...x,
      value: x.value + y.value
    } : x
  });
}, []);

categories.push({
  category: 'Total',
  data: totalRow
});
console.log(categories);

Upvotes: 1

Som Shekhar Mukherjee
Som Shekhar Mukherjee

Reputation: 8188

This how I achieved the desired results:

const data = [
  {
    category: 'Category 1',
    data: [
      { date: '01/04/2021', value: 10 },
      { date: '01/03/2021', value: 20 },
      { date: '01/02/2021', value: 5 },
    ],
  },
  {
    category: 'Category 2',
    data: [
      { date: '01/04/2021', value: 8 },
      { date: '01/03/2021', value: 2 },
      { date: '01/02/2021', value: 15 },
    ],
  },
  {
    category: 'Category 3',
    data: [
      { date: '01/04/2021', value: 7 },
      { date: '01/03/2021', value: 1 },
      { date: '01/02/2021', value: 5 },
    ],
  },
];

data.push(
  data.reduce(
    (res, { data }) => (
      data.forEach((d) => {
        res.data.find((r) => r.date === d.date)
          ? (res.data.find((r) => r.date === d.date).value += d.value)
          : res.data.push({ ...d });
      }),
      res
    ),
    { category: 'Total', data: [] }
  )
);

console.log(data);

Upvotes: 1

Related Questions