Instead of using lodash and moment.js we can group and sum in plain javascript using the Date object, indexOf method and array.reduce. Why use moment.js when we only need a few lines of code to accomplish the same in plain js.
UPDATE..since the original writing of this article, now in 2024, the ECMAScript standard has added the Object.groupBy method which eliminates the need to use array.reduce() in grouping. Go to the end of this article to see the new and much simplified code.
Use case - we often need time series data in Data Analytics and Visualizations. For example, a bar chart showing sales per month of each product line.
In order to achieve group like behavior on timestamps we use logic that says while our month name is the current month sum all values for current mo. When month name changes start all over again. For summing vals we use the array.reduce method. For grouping we keep track of IndexOf date at each pass of our original array.
Solution - we grab the month name from an array of timestamps by using the getMonth method of the javascript Date object. We need to keep track of when the month changes so we establish an empty control array and push in month name upon its change. The indexOf date will be -1 when this happens because new month is not in our array yet so there is no match found. The indexOf method is used to return the index found at each pass.
I am now getting into the weeds so let me show u an example.
// Because JS doesn't have a nice way to name months
var monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
var result = []; //transformed array after below operations are performed
var dateArr = []; //to keep track of month name and when it changes
let initialValue = 0; //need to pass in an initial value or reduce omits first element of array
var dataByMonth= original_array.reduce(function(prev, currValue){
date = new Date(currValue['order_date']);
date = monthNames[date.getMonth()]; //gets month names
//next index of is acting like a find function
var index = dateArr.indexOf(date); //equaling -1 on first pass
if (index == -1) { //is equal to -1 only for first pass, above forces -1 so we can initialize first pass
dateArr.push(date); //no match for month name until it is pushed..dateArr starts as empty until mo val is pushed
var obj = {month: date, product1: currValue.product1, product2: currValue.product2, product3: currValue.product3};
result.push(obj); //push in default vals for new mo
}
else {
//index = 0 because dateArr has val now...then for next mo index = 1
//on subsequent passes index will not be -1 so just increment cur vals until new mo is encountered
result[index].product1 += currValue.product1; //increment sum each pass starting index =0
result[index].product2 += currValue.product2; //no need to push, result val updates each time
result[index].product3 += currValue.product3;
}
return prev +currValue; //need to return both prev and curr, for first element to register ??
} //end of reduce
, initialValue); //initial value here, this is the 2nd arg of array.reduce, first arg is callback function above
console.log(JSON.stringify(result, null, 1));
Click below for the full code that I am operating on. Be sure timestamps are in valid date format yyyy-mm-dd otherwise I do have a quickie conversion method shown in the full code. PS press ctrl, shift,j to show each iteration logged onto console.
Click here for an example of my stacked bar chart
https://dev.to/rickdelpo1/how-to-populate-a-stacked-bar-chart-in-plain-javascript-12p9
Happy Coding !!
Important 2024 update
The 15th edition of the ECMAScript standard, aka ES2024 or ES15, includes the new Object.groupBy method which was released in June of 2024. This method returns an object of arrays where each inner array is a grouping with its corresponding name. Once this method returns the new object we then need to iterate over it using first object.entries then a double forEach loop iterating outer and inner arrays. At first it seems confusing and intimidating but the code is actually rather simple and we do not need to use Reduce, indexOf or Lodash to get there. So here is the code below.
summary
1 object.groupby produces an object of arrays
2 object.entries returns array of arrays or an array of key value pairs (elem 1 is key ie month and elem 2 is val ie nested array of objects to be iterated over) entries transforms object to array of arrays
in js we cannot iterate over an object thus object needs transformation into array via the entries methos
no need to use array.reduce anymore
3 we iterate over the vals in position 1 of each entry in order to sum
Use case
Click here for original code of my stacked bar chart which groups and sums sales of 3 product lines over 5 months
https://dev.to/rickdelpo1/stacked-bar-chart-using-a-json-data-source-plain-vanilla-javascript-plain-css-and-no-chart-libraries-2j29
now the new, simplified and improved code
<!DOCTYPE html>
<html>
<body>
<script>
var original_array =
[{"order_date":"1-03-22","total_dollar_amt":200,"product1":200,"product2":0,"product3":0},
{"order_date":"1-02-22","total_dollar_amt":50,"product1":0,"product2":50,"product3":0},
{"order_date":"1-16-22","total_dollar_amt":250,"product1":0,"product2":200,"product3":50},
{"order_date":"1-17-22","total_dollar_amt":100,"product1":100,"product2":0,"product3":0},
{"order_date":"1-15-22","total_dollar_amt":50,"product1":0,"product2":50,"product3":0},
{"order_date":"2-5-22","total_dollar_amt":100,"product1":100,"product2":0,"product3":0},
{"order_date":"2-6-22","total_dollar_amt":50,"product1":0,"product2":30,"product3":20},
{"order_date":"2-7-22","total_dollar_amt":100,"product1":100,"product2":0,"product3":0},
{"order_date":"3-23-22","total_dollar_amt":200,"product1":0,"product2":200,"product3":0},
{"order_date":"3-5-22","total_dollar_amt":120,"product1":0,"product2":100,"product3":20},
{"order_date":"3-29-22","total_dollar_amt":100,"product1":100,"product2":0,"product3":0},
{"order_date":"3-25-22","total_dollar_amt":100,"product1":100,"product2":0,"product3":0},
{"order_date":"4-23-22","total_dollar_amt":600,"product1":500,"product2":50,"product3":50},
{"order_date":"4-24-22","total_dollar_amt":150,"product1":0,"product2":100,"product3":50},
{"order_date":"5-10-22","total_dollar_amt":50,"product1":0,"product2":0,"product3":50},
{"order_date":"5-15-22","total_dollar_amt":100,"product1":100,"product2":0,"product3":0},
{"order_date":"5-25-22","total_dollar_amt":200,"product1":0,"product2":50,"product3":150}]
; //end of array
//below is my resultant array after I convert order dates above to months, see code for this, link to use case referenced above
var orig =
[
{
"month": "Jan",
"product1": 200,
"product2": 0,
"product3": 0
},
{
"month": "Jan",
"product1": 0,
"product2": 50,
"product3": 0
},
{
"month": "Jan",
"product1": 0,
"product2": 200,
"product3": 50
},
{
"month": "Jan",
"product1": 100,
"product2": 0,
"product3": 0
},
{
"month": "Jan",
"product1": 0,
"product2": 50,
"product3": 0
},
{
"month": "Feb",
"product1": 100,
"product2": 0,
"product3": 0
},
{
"month": "Feb",
"product1": 0,
"product2": 30,
"product3": 20
},
{
"month": "Feb",
"product1": 100,
"product2": 0,
"product3": 0
},
{
"month": "Mar",
"product1": 0,
"product2": 200,
"product3": 0
},
{
"month": "Mar",
"product1": 0,
"product2": 100,
"product3": 20
},
{
"month": "Mar",
"product1": 100,
"product2": 0,
"product3": 0
},
{
"month": "Mar",
"product1": 100,
"product2": 0,
"product3": 0
},
{
"month": "Apr",
"product1": 500,
"product2": 50,
"product3": 50
},
{
"month": "Apr",
"product1": 0,
"product2": 100,
"product3": 50
},
{
"month": "May",
"product1": 0,
"product2": 0,
"product3": 50
},
{
"month": "May",
"product1": 100,
"product2": 0,
"product3": 0
},
{
"month": "May",
"product1": 0,
"product2": 50,
"product3": 150
}
]
; //end of array
//using a one liner we can group the above array by month
// this is the new method introduced in June of 2024
const result = Object.groupBy(orig, ({ month }) => month); //lets get daring and use arrow notation
//or without an arrow function, using args, orig array and a callback function
/*
const result = Object.groupBy(orig, myCallback);
function myCallback({ month }) {
return month;
}
*/
console.log("first an object of arrays is returned after groupby method is applied");
console.log(JSON.stringify(result, null, 1));
//this results in object of arrays, then iterate thru object entries (the grouped arrays)
//first we have outer loop which is groupby result then we have inner loop which is each array entry in the object
//get entries first then inner loop iterates thru each entry at position 1 which is the val of each obj inside each entry
let resultArray = [] //our final result after first grouping then transforming then summing
console.log("next, expand this entries method to see the array of 5 arrays returned after transforming our object to an array");
let entries = Object.entries(result); // each entry is a key/value pair where the second value is a nested array (the inner array)
console.log(entries,null,1); //this is just to see what entries looks like with nested array
//then object.entries transforms the object into an array which we will then use array method forEach to sum vals
//below we are grabbing sales vals for 3 product lines and summing inside the inner forEach loop
//this is a double ForEach loop
Object.entries(result).forEach(entry => {
let product_1_total = 0
let product_2_total = 0
let product_3_total = 0
entry[1].forEach(inner => product_1_total += parseFloat(inner.product1)) //this is just for prod1
//for each array in entry position 1 sum vals for product 1
entry[1].forEach(inner => product_2_total += parseFloat(inner.product2)) //getting val2 of each object inside each array entry of the outer object
// for each entry at index 1 evaluate vals inside the inner array (inner array is position 1)
entry[1].forEach(inner => product_3_total += parseFloat(inner.product3))
//this part below is optional just to be sure it works..this is the final result
resultArray.push({
"id": entry[0], //the 0 position of each entry is the month and the 1 position is the nested array grouping for each month
"prod1 sum": product_1_total.toString(), // product_1_total
"prod2 sum": product_2_total.toString(), // product 2 total
"prod3 sum": product_3_total.toString()
}) //end of optional push
let grand_total = product_1_total+product_2_total+product_3_total; //grand total sales for each month..set sumVal as global var if we want to use outside the loop
// if needed we can push each grand total to a new array to be used for further calcs outside this loop
//console.log(grand_total);
//then do percent of total z1 outside this loop, the use case for this code is a stacked bar chart which can be found here
}) //end outer foreach
// final result is that all entries now becomes an array of objects, an array of entries
console.log("below is the final array returned after grouping and summing");
console.log(JSON.stringify(resultArray, null, 1));
</script>
<p> Press ctrl + shift + J to view console items </p>
<p> using Object.entries on a group transforms our group object into an array of 5 separate arrays where position 0 (key) is the month and position 1 (value) is a nested array where we will sum vals for each month</p>
<p>with console open be sure that all the log entries are expanded, especially the result from using object.entries which produces an array of arrays
</p>
</body>
</html>
A quick note from Rick Delpo to beginner programmers. My guess is that the reason you came to this page is due to some frustration with coding. Below, I wrote this piece about learning Javascript solely from a Data Perspective and this Data methodology actually helped propel me into the world of Data Science. Without studying Data Structures I would have never made it as a programmer. Click here for a Javascript Refresher Course to free yourself from a learning deficit that can certainly be cured.
Happy Coding !!
Top comments (0)