Sum values per month from array of objects in JavaScript - javascript

I have an array of objects like this:
var json = [ { day: '01-01-2018', hour: '00:00', value: '121' }, { day: '01-02-2018', hour: '05:24', value: '131' }, { day: '26-01-2018', hour: '00:07', value: '101' }, { day: '16-02-2018', hour: '08:02', value: '56' }, { day: '20-02-2018', hour: '12:02', value: '123' }, { day: '24-03-2018', hour: '10:11', value: '45' }];
I would like to change format of date from DD-MM-YYYY to YYYY-MM-DD and then sum values per month to create a chart.
My attempt:
var mapDateVolume = [];
for (var i = 0; i < json.length; i++)
{
var allDate = json[i].day;
var oneVolume = json[i].value;
var splitedDate = allDate.split("-");
var newOrderY = splitedDate[2] + "-"
var newOrderM = splitedDate[1] + "-";
var newOrderD = splitedDate[0];
var newOrderAllDate = newOrderY + newOrderM + newOrderD;
var newOrder = {};
newOrder[newOrderAllDate] = oneVolume;
mapDateVolume.push(newOrder);
}
var result = [];
for (var i = 0; i < mapDateVolume.length; i++){
var key = Object.keys(mapDateVolume)[i].split("-")[0] + "/";
key += Object.keys(mapDateVolume)[i].split("-")[1];
var value = Object.values(mapDateVolume)[i];
var oldValue = Object.keys(result)[i] != null ? Object.keys(result)[i] : 0;
var newResult = {};
newResult[key] = value;
result.push(newResult);
}
for (var i = 0; i < result.length; i++) {
xAxis.push(Object.keys(result)[i]);
yAxis.push(Object.values(result)[i]);
}
I use Chart.js and it works fine with days:
for ( var i = 0; i < jsonAll.length; i++ )
{
xAxis.push(json[i].day+'-'+json[i].hour);
yAxis.push(json[i].value);
}
I know that something is wrong with this sum because I see only an empty chart. Unfortunately, I have little experience with JavaScript.

First of all lets format whole collection (data array).
We need to:
Date format: DD-MM-YYYY to YYYY-MM-DD.
Value property is a string type we need it as number.
Simple with Array#Map:
const collection = data.map(x => ({ ...x, day: x.day.split("-").reverse().join("-"), value: Number(x.value)}));
Next thing is to use again Array#Map to replace day property to Month.
const mapDayToMonth = collection.map(x => ({...x, day: new Date(x.day).getMonth()}));
The getMonth() method returns the month (from 0 to 11) for the specified date, according to local time. So, 0 is January, 1 is February ...etc
Last step Array#Reduce to sum value per Month:
const sumPerMonth = mapDayToMonth.reduce((acc, cur) => {
acc[cur.day] = acc[cur.day] + cur.value || cur.value;
return acc;
}, {})
Full example JSFiddle:
const data = [ { day: '01-01-2018', hour: '00:00', value: '121' }, { day: '01-02-2018', hour: '05:24', value: '131' }, { day: '26-01-2018', hour: '00:07', value: '101' }, { day: '16-02-2018', hour: '08:02', value: '56' }, { day: '20-02-2018', hour: '12:02', value: '123' }, { day: '24-03-2018', hour: '10:11', value: '45' }];
const collection = data.map(x => ({ ...x, day: x.day.split("-").reverse().join("-"), value: Number(x.value)}));
console.log(collection);
const mapDayToMonth = collection.map(x => ({...x, day: new Date(x.day).getMonth()}));
console.log(mapDayToMonth);
const sumPerMonth = mapDayToMonth.reduce((acc, cur) => {
acc[cur.day] = acc[cur.day] + cur.value || cur.value; // increment or initialize to cur.value
return acc;
}, {})
console.log(sumPerMonth)
Useful links:
Array Map
Array Reduce
Number Object
Spread Syntax

Related

How to add the values ​in an array of objects depending on the date value of each object

I have this array:
[{start_date: "2022-12-05T04:00:00Z" ,distance: 1000, time: 3600}
,{start_date: "2022-02-07T04:00:00Z" ,distance: 1500, time: 6400},
{start_date: "2022-12-08T04:00:00Z" ,distance: 1000, time: 1300}]
I want to add the distance and time values ​​grouping them by the month indicated by the start_date value. For example, if two start_dates have the same month 2022-12-01 and 2022-12-08, how can I add the distance and time values ​​of those two months?
so i get a new array like this:
[{month: 12 ,total distance: 2000, total time: 4900},
{month: 02 , total distance: 1500, total time: 6400} ]
you can use reduce to group them by month which will give an object like
{
12: {
distance: 2000,
month: 12,
time: 4900
},
2: {
distance: 1500,
month: 2,
time: 6400
}
}
and using Object.values get the values array of it
let x = [{start_date: "2022-12-05T04:00:00Z" ,distance: 1000, time: 3600},{start_date: "2022-02-07T04:00:00Z" ,distance: 1500, time: 6400},{start_date: "2022-12-08T04:00:00Z" ,distance: 1000, time: 1300}]
let res = Object.values(x.reduce((acc,{start_date,distance,time})=> {
let month = new Date(start_date).getMonth()+1
if(!acc[month])acc[month] = {totalDistance:0,totalTime:0,month:month}
acc[month].totalDistance+=distance
acc[month].totalTime+=time
return acc;
},{}))
console.log(res)
You can use a object as a dictionary and save a accumulated value of time and distance per month key. Then, reduce all keys to an array with the requested format.
const groupPerMonth = (list) => {
const extractMonth = (stringDate) => {
const month = new Date(stringDate).getMonth() + 1;
return month < 10 ? `0${month}` : `${month}`;
}
const months = {};
for (const item of list) {
const month = extractMonth(item.start_date);
if (!(month in months)) {
months[month] = {
distance: 0,
total_time: 0,
};
}
months[month].distance += item.distance;
months[month].total_time += item.time;
}
const result = [];
for (const month in months) {
result.push({
month,
...months[month]
});
}
return result;
};
And test it:
console.log(
groupPerMonth([
{ start_date: "2022-12-05T04:00:00Z", distance: 1000, time: 3600 },
{ start_date: "2022-02-07T04:00:00Z", distance: 1500, time: 6400 },
{ start_date: "2022-12-08T04:00:00Z", distance: 1000, time: 1300 },
])
);
Output:
[
{ month: '12', distance: 2000, total_time: 4900 },
{ month: '02', distance: 1500, total_time: 6400 }
]
There might be different solutions to this, but one way to solve this is by using the lodash library to solve this. We can first group by month, followed by mapping each grouped item and adding the distance and time values in each group using reduce:
const list = [
{start_date: "2022-12-05T04:00:00Z" ,distance: 1000, time: 3600},
{start_date: "2022-02-07T04:00:00Z" ,distance: 1500, time: 6400},
{start_date: "2022-12-08T04:00:00Z" ,distance: 1000, time: 1300}
]
const grouped = _.groupBy(list, item => {
const date = new Date(item.start_date)
return date.getMonth() + 1
})
const groupedAndMapped = _.map(grouped, function(groupedList, date){
return {
month: date,
total_distance: _.reduce(groupedList, (total, current) => {
return total + current.distance
}, 0),
total_time:_.reduce(groupedList, (total, current) => {
return total + current.time
}, 0)
}
})
One improvement you could do is to format the month to an "MM-YYYY" format or something similar as its possible your data set can include items with different years.

Get sum of array values based on date values of other array

So i have problem like this:
I have 2 arrays:
one from mysql query that contains date when order have been created and total sum of it
const ordersData = [
0: {id: 1, data: '2021-11-23T00:00:00.000Z', price: 394}
1: {id: 2, data: '2021-11-23T00:00:00.000Z', price: 315.3}
2: {id: 3, data: '2021-11-23T00:00:00.000Z', price: 16445}
...
6: {id: 7, data: '2021-11-23T00:00:00.000Z', price: 200}
7: {id: 8, data: '2021-12-22T00:00:00.000Z', price: 618}
]
second is array is where i have monthly sum, month, first and last day of month
const pastMonthsData = [
0: {
month: "december",
firstDay: Wed Dec 01 2021,
lastDay: Fri Dec 31 2021,
totalMonthSum: x
},
1: {
month: "november",
firstDay: Mon Nov 01 2021,
lastDay: Tue Nov 30 2021,
totalMonthSum: x
}
]
I need to check if date from order array is in between date of pastMonthsData and add price to totalMonthSum.
So far i created func like this but it only works for december, for november there is no result.
pastMonthsData.forEach((el, i) => {
el.totalMonthSum = ordersData.reduce((total, item) => {
let itemDate = new Date(item.data);
if(el.firstDay.getTime() < itemDate.getTime() && itemDate.getTime() < el.lastDay.getTime()) {
return total + item.price
} else {
return 0
}
})
});
Two fixes:
initialize the accumulated total to zero
return the accumulated total, rather than zero, when a date is not in range
For example:
pastMonthsData.forEach((el, i) => {
el.totalMonthSum = ordersData.reduce((total, item) => {
let itemDate = new Date(item.data);
if (el.firstDay.getTime() < itemDate.getTime() && itemDate.getTime() < el.lastDay.getTime()) {
return total + item.price;
} else {
return total;
}
}, 0)
});

Populating an array of object with 0 if object does not exist

I am building a chart for monthly data which would have the x axis as wk1 - wk4 and y axis being the amount of goods etc. I was able to build out a solution but the problem lies when there is no data for a particular week. This is my code below.
const byAmount = obj => {
const res = [];
const keys = Object.keys(obj);
keys.forEach(key => {
res.push({
week: `wk${key}`,
amount: obj[key]
});
});
return res.sort((a, b) => a.amount - b.amount).slice(0, 5);;
};
const getWeeklyFromMonth = (arr, month) => {
const week = arr.map(a => ({ ...a, week: Math.floor((moment(a.dateScanned.$date).date() - 1) / 7) + 1 }));
let dataForMonth = [];
let total;
week.map(data => {
if (moment(data.dateScanned.$date).format("MMM") === month) {
dataForMonth.push(data);
const totalPerWeek = dataForMonth.reduce((acc, cur) => {
acc[cur.week] = acc[cur.week] + cur.amount || cur.amount;
return acc;
}, {});
total = totalPerWeek;
}
});
return byAmount(total);
}
When I run this I get the below:
[
{ week: 'wk1', amount: 14 },
{ week: 'wk2', amount: 27 },
{ week: 'wk4', amount: 43 }
]
This is fine but I want to populate the array with 0 if there is no data say for week 3. I would want it to be this
[
{ week: 'wk1', amount: 14 },
{ week: 'wk2', amount: 27 },
{ week: 'wk3', amount: 0 },
{ week: 'wk4', amount: 43 }
]
I was thinking of having an array of like [1, 2, 3, 4] and if the array includes the week number, pop it out of the array and then the remaining item should be used to populate it but I find myself scratching my head. Does anyone know a decent way to do this?
Thank you in advance.
You can try this:
const byAmount = obj => {
const res = [];
const keys = Object.keys(obj);
const [min, max] = [Math.min(...keys), Math.max(...keys)];
for(let key = min; key <= max; key++) {
res.push({
week: `wk${key}`,
amount: obj[key] || 0
});
}
return res.sort((a, b) => a.amount - b.amount).slice(0, 5);;
};

Get value from array key and build a new array

I cannot get the value of 'Date' key to build my array.
const input = [{
"Date": "12/08/2020",
"Day": "Wednesday"
}, {
"Date": "13/08/2020",
"Day": "Thursday"
}, {
"Date": "14/08/2020",
"Day": "Friday"
}];
function get(o, days) {
const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const [dd, mm, yyyy] = Object.keys(o)[0].split('/');
const date = new Date(`${yyyy}-${mm}-${dd}`);
date.setUTCDate(date.getUTCDate() + days);
const key = `${
`${date.getUTCDate()}`.padStart(2, '0')
}/${
`${(date.getUTCMonth() + 1)}`.padStart(2, '0')
}/${
date.getUTCFullYear()
}`;
const value = weekdays[date.getUTCDay()];
return {
[key]: value
};
}
function prepend(array, count) {
while (count-- > 0) {
array.unshift(get(input[0], -1));
}
}
function append(array, count) {
while (count-- > 0) {
array.push(get(input[input.length - 1], 1));
}
}
prepend(input, 1);
append(input, 1);
console.log(input);
The console shows this output:
{NaN/NaN/NaN: undefined},{Date: "12/08/2020", Day: "Wednesday"},{Date: "13/08/2020", Day: "Thursday"},{Date: "14/08/2020", Day: "Friday"},{NaN/NaN/NaN: undefined}
Seems like the problem is with Object.keys(o)[0]. How can I fix this?
You actually want the first value, not the first key.
const [dd, mm, yyyy] = Object.values(o)[0].split('/');
However, since you already know the name of the key, you can simply use o.Date.
const [dd, mm, yyyy] = o.Date.split('/');

how to split the array list in separate array by day wise using javascript

how to split the array list by days
const days = [
{'name':'Mon','value':1},
{'name':'Tue','value':5},
{'name':'Wed','value':10},
{'name':'Wed','value':30},
{'name':'Fri','value':18},
{'name':'Sat','value':80},
{'name':'Sat','value':90},
{'name':'Sun','value':20},
]
I having the above array list by days i wed and Sat i am having two values for thu I am not having values. i need to split the repeated key values into separate array if day are not in the list i need to add zero value for that for example my out put will be
const result = [
[1,5,10,0,18,80,20],
[0,0,30,0,0,90,0]
]
I need like this result is it possible to do in javascript.
You can do this using native javascript.
The algorithm is very simple:
For each day in daysArray you should search it in your given array and just remove first occurence from days.
Do step 1 until days is empty. With the other words, execute step 1 until days.length == 0 condition is satisfied.
let days = [ {'name':'Mon','value':1}, {'name':'Tue','value':5}, {'name':'Wed','value':10}, {'name':'Wed','value':30}, {'name':'Fri','value':18}, {'name':'Sat','value':80}, {'name':'Sat','value':90}, {'name':'Sun','value':20} ], daysArray = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
let result = [];
while(days.length){
sub_array = [];
daysArray.forEach(function(item){
let index = days.findIndex(a => a.name == item);
if(index == -1)
sub_array.push(0);
else{
sub_array.push(days[index].value);
days.splice(index, 1);
}
});
result.push(sub_array);
}
console.log(result);
Add an array of days in the order that you want - daysList.
Group the day objects into a Map using the name as key - daysMap.
Reduce the daysList, and get the days objects from the map by the current day. Iterate the array of days with Array.forEach(), and for each add a week array filled with 0s if missing, and assign the day's value to the day index di.
const daysList = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
const days = [{"name":"Mon","value":1},{"name":"Tue","value":5},{"name":"Wed","value":10},{"name":"Wed","value":30},{"name":"Fri","value":18},{"name":"Sat","value":80},{"name":"Sat","value":90},{"name":"Sun","value":20}]
// group the days by name into a map
const daysMap = days.reduce((m, o) => {
m.has(o.name) || m.set(o.name, [])
m.get(o.name).push(o)
return m
}, new Map)
// iterate the daysList
const result = daysList.reduce((r, d, di) => {
//get the array of the days with the same name from the group
const daysObjs = daysMap.get(d) || []
//iterate the daysObjs array
daysObjs.forEach(({ name, value }, wi) => {
// add a new week array filled with 0 if the row is missing
r[wi] || r.push(new Array(daysList.length).fill(0))
// assign the day value to the week array
r[wi][di] = value
})
return r
}, [])
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
You could take an object for the day indices and for the ouiter indixec which is incremented by every inset of data.
var days = [{ name: 'Mon', value: 1 }, { name: 'Tue', value: 5 }, { name: 'Wed', value: 10 }, { name: 'Wed', value: 30 }, { name: 'Fri', value: 18 }, { name: 'Sat', value: 80 }, { name: 'Sat', value: 90 }, { name: 'Sun', value: 20 }],
indices = { Mon: { day: 0, index: 0 }, Tue: { day: 1, index: 0 }, Wed: { day: 2, index: 0 }, Thu: { day: 3, index: 0 }, Fri: { day: 4, index: 0 }, Sat: { day: 5, index: 0 }, Sun: { day: 6, index: 0 } },
result = days.reduce((r, { name, value }) => {
r[indices[name].index] = r[indices[name].index] || Array.from({ length: 7 }).fill(0);
r[indices[name].index++][indices[name].day] = value;
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use array#reduce and a lookup object of day and create an array of value based on a given day.
const days = [{'name':'Mon','value':1}, {'name':'Tue','value':5}, {'name':'Wed','value':10}, {'name':'Wed','value':30}, {'name':'Fri','value':18}, {'name':'Sat','value':80}, {'name':'Sat','value':90}, {'name':'Sun','value':20}, ],
day = {'Mon':0, 'Tue':1, 'Wed':2, 'Thu': 3, 'Fri': 4, 'Sat': 5, 'Sun': 6},
result = days.reduce((r,o) => {
var index = 0;
if(r[index][day[o.name]]) {
while(r[index] && r[index][day[o.name]]) {
index++;
}
if(!r[index]) {
r[index] = Array.from({length: 7}, _=> 0);
}
}
r[index][day[o.name]] = o.value;
return r;
},[Array.from({length: 7}, _=> 0)]);
console.log(result);
another solution:
const res = _.chain(days)
.map('name') // get days name
.uniq() // uniq days name
.map(day => _.filter(days, { name: day })) // iterate days name and get all day items
.map(sameDays => _.map(sameDays, 'value')) // get value for each day items
.map(vs => [vs[0], vs[1] || 0]) // set 0 value if only one item for day
.thru(_.spread(_.zip)) // transpose the array
.value()

Categories

Resources