I want to display a graph of the total count for groupA and groupB for each month.
The graph library uses chart.js
I want to put the sum of the counts for each month in data:[].
I want to turn the array of values to be retrieved from the data to determine groupA and groupB, and put the count for each month into data
script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"
javascript:
var users = #{raw #user_counts.to_json}
console.log(users)
var ct = document.getElementById('ex_chart');
var ex_chart = new Chart(ct, {
type: 'horizontalBar',
data: {
labels: ["Jan", "Feb", "Mar", "Apr", "May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
datasets: [
{
label: 'groupA',
data: [],
backgroundColor: '#C7CED7'
},
{
label: 'groupB',
data: [],
backgroundColor: '#0068B4'
}
]
},
options: options
})
Contents of users
[
{
activity_type: "groupA"
count: 10
created_at: "2021-01-14T13:46:18.000Z"
id: 1
year: 2020
month: "Jan"
updated_at: "2021-01-14T13:46:18.000Z"
},
{
activity_type: "groupA"
count: 8
created_at: "2021-01-14T13:46:18.000Z"
id: 2
year: 2020
month: "Feb"
updated_at: "2021-01-14T13:46:18.000Z"
},
{
activity_type: "groupB"
count: 8
created_at: "2021-01-14T13:46:18.000Z"
id: 3
year: 2020
month: "Feb"
updated_at: "2021-01-14T13:46:18.000Z"
}
]
In technical terms, you want to group your user counts by two parameters: 'activityType' and 'month'.
Below is a solution using functional programming. You may modify keys, for example to add 'year' parameter, which actually makes sense.
const users = [
{
activity_type: "groupA",
count: 10,
year: 2020,
month: 1
},
{
activity_type: "groupA",
count: 17,
year: 2019,
month: 2,
},
{
activity_type: "groupA",
count: 8,
year: 2020,
month: 2,
},
{
activity_type: "groupB",
count: 8,
year: 2020,
month: 1,
}
];
const keys = ['activity_type', 'month'];
function matches(table, entry, keys) { // finds item with same values
return table.find(e => keys.every(k => e[k] == entry[k]));
}
const usersGroupedByKeys = users.reduce((cur, val) => {
let alreadyIn = matches(cur, val, keys);
if (alreadyIn) {
alreadyIn['count'] = alreadyIn['count'] + val['count'];
} else {
cur.push(val);
}
return cur;
}, []);
console.log(usersGroupedByKeys);
Check the Docs.
Related
I have to remove the comma between the first and last names of "name" in an array called "players" using .map() and .split().
This is the array I'm given:
const players = [
{ name: 'Modrić, Luka', year: 1985 },
{ name: 'Christian, Eriksen', year: 1992 },
{ name: 'Griezmann, Antoine', year: 1991 },
{ name: 'Achraf, Hakimi', year: 1998 },
{ name: 'Martínez, Lautaro', year: 1997 }
];
This is the code I have so far, to get name out of the array using .map():
const mapped = players.map(({ name }) => {
return name;
})
console.log(mapped);
Which logs this in the console:
[
'Modrić, Luka',
'Christian, Eriksen',
'Griezmann, Antoine',
'Achraf, Hakimi',
'Martínez, Lautaro'
]
Now how do I use .split() to remove the the commas between the first and last name? Im lost :(
Thanks for any help! :)
I tried using .map to get each name from the players array. Then I tried using .split() to no avail :(
You can use String#replace.
const players = [
{ name: 'Modrić, Luka', year: 1985 },
{ name: 'Christian, Eriksen', year: 1992 },
{ name: 'Griezmann, Antoine', year: 1991 },
{ name: 'Achraf, Hakimi', year: 1998 },
{ name: 'Martínez, Lautaro', year: 1997 }
];
let res = players.map(({name}) => name.replace(',', ''));
console.log(res);
const players = [
{ name: 'Modrić, Luka', year: 1985 },
{ name: 'Christian, Eriksen', year: 1992 },
{ name: 'Griezmann, Antoine', year: 1991 },
{ name: 'Achraf, Hakimi', year: 1998 },
{ name: 'Martínez, Lautaro', year: 1997 }
];
// use `split` to split
console.log(players.map(player => player.name.split(', ')))
If you really have to use split, which then you would have to use join to change it back to a string:
{ name: 'Modrić, Luka', year: 1985 },
{ name: 'Christian, Eriksen', year: 1992 },
{ name: 'Griezmann, Antoine', year: 1991 },
{ name: 'Achraf, Hakimi', year: 1998 },
{ name: 'Martínez, Lautaro', year: 1997 }
];
const mapped = players.map(({ name }) => {
let splitName = name.split(',');
return splitName.join('')
})
console.log(mapped);
But I think this would be better:
{ name: 'Modrić, Luka', year: 1985 },
{ name: 'Christian, Eriksen', year: 1992 },
{ name: 'Griezmann, Antoine', year: 1991 },
{ name: 'Achraf, Hakimi', year: 1998 },
{ name: 'Martínez, Lautaro', year: 1997 }
];
const mapped = players.map(({ name }) => {
return name.replace(',', '')
})
console.log(mapped);
Given the following Array of Objects:
[{
"id": 1,
"name": "random_name1",
"published_at": "2021-01-16T08:52:24.408Z",
},
{
"id": 2,
"name": "random_name2",
"published_at": "2022-02-16T08:52:24.408Z",
},
{
"id": 3,
"name": "random_name3",
"published_at": "2020-04-16T08:52:24.408Z",
},
{
"id": 4,
"name": "random_name4",
"published_at": "2020-04-16T08:52:24.408Z",
},
{
"id": 5,
"name": "random_name5",
"published_at": "2022-05-16T08:52:24.408Z",
}
]
I need to group the items in one array of nested objects (descending) by Year and Month, result should be:
[
{
year: '2022',
months: [
{
month: '5',
items: [
{
id: '5',
name: 'random_name5'
}
]
},
{
month: '2',
items: [
{
id: '2',
name: 'random_name2'
}
]
}
]
},
{
year: '2021',
months: [
{
month: '1',
items: [
{
id: '1',
name: 'random_name1'
}
]
},
{
month: '2',
items: [
{
id: '2',
name: 'random_name2'
}
]
}
]
},
{
year: '2020',
months: [
{
month: '4',
items: [
{
id: '3',
name: 'random_name3'
},
{
id: '4',
name: 'random_name4'
}
]
}
]
}
];
I have tried the following:
items = [...new Set(items.map((item) => parseInt(item.published_at.split('-')[0])))].map((year) => [
{
year: year,
months: [
...new Set(
items
.filter((item) => parseInt(item.published_at.split('-')[0]) === year)
.map((item) => parseInt(item.published_at.split('-')[1]))
)
].map((month) => [
{
month: month,
items: items.filter(
(item) => parseInt(item.published_at.split('-')[0]) === year && parseInt(item.published_at.split('-')[1]) === month
)
}
])
}
]);
return items
The problem with the above solution, is that it will create a two dimensional array like so (months being two dimensional too):
[
[ { year: 2022, months: [Array] } ],
[ { year: 2021, months: [Array] } ],
[ { year: 2020, months: [Array] } ],
[ { year: 2019, months: [Array] } ],
[ { year: 2018, months: [Array] } ]
]
How to fix this?
If you get a unique list of year-months you can use this to map your object
const items = [{ "id": 1,"name": "random_name1","published_at": "2021-01-16T08:52:24.408Z", },
{ "id": 2, "name": "random_name2", "published_at": "2022-02-16T08:52:24.408Z",},
{ "id": 3, "name": "random_name3","published_at": "2020-04-16T08:52:24.408Z",},
{"id": 4, "name": "random_name4", "published_at": "2020-04-16T08:52:24.408Z",},
{ "id": 5, "name": "random_name5", "published_at": "2022-05-16T08:52:24.408Z",}]
let uniqueYearMonths = [... new Set(items.map(x => x.published_at.substring(0,7)))];
let results = [... new Set(items.map(x => x.published_at.substring(0,4)))]
.map(year => ({
year: year,
months: uniqueYearMonths
.filter(ym => ym.startsWith(year))
.map(ym => ({
month: ym.substring(5,7),
items: items
.filter(item => item.published_at.startsWith(ym))
.map(item => ({
id: item.id,
name: item.name
}))
}))
}));
console.log(results);
Given you array as data, you could do something with array methods like map and reduce.
Like this:
const groupedByYear = data.map((e) => ({ ...e, published_at: new Date(e.published_at) }))
.reduce((acc, e) => {
const year = e.published_at.getFullYear();
const month = e.published_at.getMonth() + 1;
if (!acc[year]) acc[year] = { year };
if (!acc[year][month]) acc[year][month] = [];
acc[year][month] = e;
return acc;
}, {})
const result = Object.values(groupedByYear).reduce((acc, e) => {
const { year, ...months } = e;
acc.push({ year: year, months: months });
return acc;
}, [])
This is an example and is probably not the best way to do this. It is only intended to show you a path of data transformations.
First data.map to be able to do operations on dates. Then a reduce to group data (here using an object). Then creating an array from the object values to match the output you want.
Compared to a solution like you showed, there is the advantage that you limit the number of times that you iterate over the array. It is always a good idea to avoid iterating to much time on an array for better performance.
I would like to group an array with events by year and month. My data looks like this:
const events = [
{
name: "event 1",
year: 2021,
month: 1,
},
{
name: "event 2",
year: 2021,
month: 9,
},
{
name: "event 3",
year: 2021,
month: 1,
},
{
name: "event 4",
year: 2022,
month: 7,
},
]
And my expected outcome should be something like this:
[
{
year: 2021,
month: 1,
events: [
{
name: "event 1"
},
{
name: "event 3"
}
]
},
{
year: 2021,
month: 9,
events: [
{
name: "event 2"
}
]
}
]
What would be the best approach to do this? I found a couple stackoverflow posts to group an array by it's key value but that not what I'm looking for.
const groupBy = (array, key) => {
return array.reduce((result, currentValue) => {
// If an array already present for key, push it to the array. Else create an array and push the object
(result[currentValue[key]] = result[currentValue[key]] || []).push(currentValue);
// Return the current iteration `result` value, this will be taken as next iteration `result` value and accumulate
return result;
}, {}); // empty object is the initial value for result object
};
const groupedByYear = groupBy(events, 'year');
You can do this with reduce and Object.values
const events = [
{
name: "event 1",
year: 2021,
month: 1,
},
{
name: "event 2",
year: 2021,
month: 9,
},
{
name: "event 3",
year: 2021,
month: 1,
},
];
const result = Object.values(events.reduce( (acc,evt) => {
const key = `${evt.year}-${evt.month}`;
if(!acc[key]) {
acc[key] = {year: evt.year, month: evt.month, events:[]}
}
acc[key].events.push( {name:evt.name} );
return acc;
},{}));
console.log(result);
You could take a dynamic approach by using a combined key for wanted properties for grouping.
Then remove all keys of grouing and push a new object without unwanted properties.
const
events = [{ name: "event 1", year: 2021, month: 1 }, { name: "event 2", year: 2021, month: 9 }, { name: "event 3", year: 2021, month: 1 }],
keys = ['year', 'month'],
result = Object.values(events.reduce((r, o) => {
let value,
key = keys.map(k => o[k]).join('|');
if (!r[key]) r[key] = { ...Object.fromEntries(keys.map(k => [k, o[k]])), events: [] };
r[key].events.push(keys.reduce((t, k) => (({ [k]: value, ...t } = t), t), o));
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I need to transform a array of multiple object in one array object,
I explain myself, I wish to group for each table the objects that carry the same "month" and replace the properties that have the same value by adding their ID at the beginning:
ex:
quantity: 1
becomes fpsIssuedQuantity (in camelCase). http://jsfiddle.net/rLjQx/96589/
here are my data :
var data = {
"2018-01": [
{ id:"fpsIssued", month:"2018-01", quantity:"28" },
{ id:"dgfipIncome", month:"2018-01", amount:1350 },
{ id:"antaiPaidFps", month:"2018-01", quantity:2242 }
],
"2018-02": [
{ id: "fpsIssued", month: "2018-02", quantity: "29" },
{ id: "dgfipIncome", month: "2018-02", amount: 8530 },
{ id: "antaiPaidFps", month: "2018-02", quantity: 4857}
]
};
console.log(data);
and the expected data :
var expectedData = {
"2018-01": [
{ month: "2018-01", fpsIssuedquantity: "28",
dgfipIncomeamount: 1350, antaiPaidFpsQuantity: 2242
}
],
"2018-02": [
{ month: "2018-02", fpsIssuedquantity: "29",
dgfipIncomeamount: 8530, antaiPaidFpsQuantity: 4857
}
]
};
console.log(expectedData);
i use lodash and angularjs but i can not get my result .. please could you help me?
You could map new objects with wanted new property names and values in new objects.
var data = { "2018-01": [{ id: "fpsIssued", month: "2018-01", quantity: "28" }, { id: "dgfipIncome", month: "2018-01", amount: 1350 }, { id: "antaiPaidFps", month: "2018-01", quantity: 2242 }], "2018-02": [{ id: "fpsIssued", month: "2018-02", quantity: "29" }, { id: "dgfipIncome", month: "2018-02", amount: 8530 }, { id: "antaiPaidFps", month: "2018-02", quantity: 4857 }] },
result = Object.assign(
...Object
.entries(data)
.map(
([k, v]) => ({ [k]: Object.assign(...v.map(o => ({ month: o.month, [o.id + ('quantity' in o ? 'Quantity' : 'Amount')]: 'quantity' in o ? o.quantity : o.amount }))) })
)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I'm trying to synchronise two functions I run in my app.
First one checks the count of the documents I save to MongoDB every time block (e.g. every 10 seconds) in the real time:
var getVolume = function(timeBlock, cb) {
var triggerTime = Date.now();
var blockPeriod = triggerTime - timeBlock;
Document.find({
time: { $gt: blockPeriod }
}).count(function(err, count) {
log('getting volume since ', new Date(blockPeriod), 'result is', count)
cb(triggerTime, count);
});
};
and then I have the second function which I use whenever I want to get a data for my graph (front end):
var getHistory = function(timeBlock, end, cb) {
Document.aggregate(
{
$match: {
time: {
$gte: new Date(end - 10 * timeBlock),
$lt: new Date(end)
}
}
},
// count number of documents based on time block
// timeBlock is divided by 1000 as we use it as seconds here
// and the timeBlock parameter is in miliseconds
{
$group: {
_id: {
year: { $year: "$time" },
month: { $month: "$time" },
day: { $dayOfMonth: "$time" },
hour: { $hour: "$time" },
minute: { $minute: "$time" },
second: { $subtract: [
{ $second: "$time" },
{ $mod: [
{ $second: "$time" },
timeBlock / 1000
]}
]}
},
count: { $sum: 1 }
}
},
// changing the name _id to timeParts
{
$project: {
timeParts: "$_id",
count: 1,
_id: 0
}
},
// sorting by date, from earliest to latest
{
$sort: {
"time": 1
}
}, function(err, result) {
if (err) {
cb(err)
} else {
log("start", new Date(end - 10 * timeBlock))
log("end", new Date(end))
log("timeBlock", timeBlock)
log(">****", result)
cb(result)
}
})
}
and the problem is that I can't get the same values on my graph and on the back-end code (getVolume function)
I realised that the log from getHistory is not how I would expect it to be (log below):
start Fri Jul 18 2014 11:56:56 GMT+0100 (BST)
end Fri Jul 18 2014 11:58:36 GMT+0100 (BST)
timeBlock 10000
>**** [ { count: 4,
timeParts: { year: 2014, month: 7, day: 18, hour: 10, minute: 58, second: 30 } },
{ count: 6,
timeParts: { year: 2014, month: 7, day: 18, hour: 10, minute: 58, second: 20 } },
{ count: 3,
timeParts: { year: 2014, month: 7, day: 18, hour: 10, minute: 58, second: 10 } },
{ count: 3,
timeParts: { year: 2014, month: 7, day: 18, hour: 10, minute: 58, second: 0 } },
{ count: 2,
timeParts: { year: 2014, month: 7, day: 18, hour: 10, minute: 57, second: 50 } } ]
So I would expect that the getHistory should look up data in mongo every 10 seconds starting from start Fri Jul 18 2014 11:56:56 GMT+0100 (BST) so it will look roughly like:
11:56:56 count: 3
11:57:06 count: 0
11:57:16 count: 14
... etc.
TODO:
1. I know I should cover in my aggregate function the case when the count is 0 at the moment I guess this time is skipped`
Your error is how you're calculating _id for $group operator, specifically its second part:
second: { $subtract: [
{ $second: "$time" },
{ $mod: [
{ $second: "$time" },
timeBlock / 1000
]}
]}
So, instead of splitting all your data into 10 timeBlock milliseconds long chunks starting from new Date(end - 10 * timeBlock), you're splitting it into 11 chunks starting from from the nearest divisor of timeBlock.
To fix it you should first calculate delta = end - $time and then use it instead of the original $time to build your _id.
Here is an example of what I mean:
Document.aggregate({
$match: {
time: {
$gte: new Date(end - 10 * timeBlock),
$lt: new Date(end)
}
}
}, {
$project: {
time: 1,
delta: { $subtract: [
new Date(end),
"$time"
]}
}
}, {
$project: {
time: 1,
delta: { $subtract: [
"$delta",
{ $mod: [
"$delta",
timeBlock
]}
]}
}
}, {
$group: {
_id: { $subtract: [
new Date(end),
"$delta"
]},
count: { $sum: 1 }
}
}, {
$project: {
time: "$_id",
count: 1,
_id: 0
}
}, {
$sort: {
time: 1
}
}, function(err, result) {
// ...
})
I also recommend you to use raw time values (in milliseconds), because it's much easier and because it'll keep you from making a mistake. You could cast time into timeParts after $group using $project operator.