Group JSON data into weeks / months without additional external library? - javascript
I am looking for a solution to group data into weeks / months without external library. I have seen the D3.js nest() option here: how can i group JSON data into the weeks according to calender?
Here another way using npm: https://www.npmjs.com/package/group-by-time
Maybe I am crazy having grown up in the age of dialup, but I still like to keep how many things I load on a page down to what is actually needed instead of loading everything possible, just in case I want to use it, even though the user "might" already have a cached copy from a CDN.
I am currently using Chart.js to display data, also available are Bootstrap and jQuery. I would love to be able to switch the chart between day, week month, using only javascript or jQuery, which seems like it should be a fairly common thing, but I don't see any examples without going for a bigger library like I mentioned above.
var chart={
dates: ['2015-09-01', '2015-09-02', '2015-09-03', '2015-09-04', '2015-09-05', '2015-09-06', '2015-09-07', '2015-09-08', '2015-09-09', '2015-09-10', '2015-09-11', '2015-09-12', '2015-09-13', '2015-09-14', '2015-09-15', '2015-09-16', '2015-09-17', '2015-09-18', '2015-09-19', '2015-09-20', '2015-09-21', '2015-09-22', '2015-09-23', '2015-09-24', '2015-09-25', '2015-09-26', '2015-09-27', '2015-09-28', '2015-09-29', '2015-09-30', '2015-10-01', '2015-10-02', '2015-10-03', '2015-10-04', '2015-10-05', '2015-10-06', '2015-10-07', '2015-10-08', '2015-10-09', '2015-10-10', '2015-10-11', '2015-10-12', '2015-10-13', '2015-10-14', '2015-10-15', '2015-10-16', '2015-10-17', '2015-10-18', '2015-10-19', '2015-10-20', '2015-10-21', '2015-10-22', '2015-10-23', '2015-10-24', '2015-10-25', '2015-10-26', '2015-10-27', '2015-10-28', '2015-10-29', '2015-10-30', '2015-10-31', '2015-11-01', '2015-11-02', '2015-11-03', '2015-11-04', '2015-11-05', '2015-11-06', '2015-11-07', '2015-11-08', '2015-11-09', '2015-11-10', '2015-11-11', '2015-11-12', '2015-11-13', '2015-11-14', '2015-11-15', '2015-11-16', '2015-11-17', '2015-11-18', '2015-11-19', '2015-11-20', '2015-11-21', '2015-11-22', '2015-11-23', '2015-11-24', '2015-11-25', '2015-11-26', '2015-11-27', '2015-11-28', '2015-11-29', '2015-11-30', '2015-12-01', '2015-12-02', '2015-12-03', '2015-12-04', '2015-12-05', '2015-12-06', '2015-12-07', '2015-12-08', '2015-12-09', '2015-12-10', '2015-12-11', '2015-12-12', '2015-12-13', '2015-12-14', '2015-12-15', '2015-12-16', '2015-12-17', '2015-12-18', '2015-12-19', '2015-12-20', '2015-12-21', '2015-12-22', '2015-12-23', '2015-12-24', '2015-12-25', '2015-12-26', '2015-12-27', '2015-12-28', '2015-12-29', '2015-12-30', '2015-12-31', '2016-01-01', '2016-01-02', '2016-01-03', '2016-01-04', '2016-01-05', '2016-01-06', '2016-01-07', '2016-01-08', '2016-01-09', '2016-01-10', '2016-01-11', '2016-01-12', '2016-01-13', '2016-01-14', '2016-01-15', '2016-01-16', '2016-01-17', '2016-01-18', '2016-01-19', '2016-01-20', '2016-01-21', '2016-01-22', '2016-01-23', '2016-01-24', '2016-01-25', '2016-01-26', '2016-01-27', '2016-01-28', '2016-01-29', '2016-01-30', '2016-01-31', '2016-02-01', '2016-02-02', '2016-02-03', '2016-02-04', '2016-02-05', '2016-02-06', '2016-02-07', '2016-02-08', '2016-02-09', '2016-02-10', '2016-02-11', '2016-02-12', '2016-02-13', '2016-02-14', '2016-02-15', '2016-02-16', '2016-02-17', '2016-02-18', '2016-02-19', '2016-02-20', '2016-02-21', '2016-02-22', '2016-02-23', '2016-02-24', '2016-02-25', '2016-02-26', '2016-02-27', '2016-02-28', '2016-02-29'],
data: [77.02, 63.80, 21.64, 86.60, 65.40, 46.25, 27.38, 66.65, 67.25, 65.59, 64.80, 01.00, 32.75, 04.30, 51.92, 02.75, 40.20, 72.30, 62.90, 83.60, 66.66, 37.30, 93.90, 01.50, 55.77, 50.00, 73.20, 30.03, 07.95, 21.65, 07.93, 66.94, 11.72, 33.75, 22.80, 14.55, 68.78, 66.78, 52.35, 06.24, 64.78, 22.21, 19.08, 23.69, 54.40, 39.55, 28.76, 22.25, 09.85, 07.50, 22.47, 75.94, 93.34, 16.29, 28.98, 64.40, 78.68, 30.65, 96.65, 99.35, 77.50, 75.30, 89.85, 97.50, 53.90, 97.55, 28.98, 75.08, 25.66, 41.00, 73.72, 68.50, 95.40, 49.50, 32.50, 86.00, 05.43, 88.19, 50.39, 03.90, 82.90, 53.78, 94.20, 82.40, 12.63, 78.80, 07.50, 66.50, 41.75, 91.25, 34.50, 22.50, 85.50, 80.00, 33.75, 59.50, 52.50, 73.25, 76.50, 38.75, 11.00, 55.00, 37.25, 92.50, 74.75, 55.75, 37.25, 61.01, 90.95, 87.65, 99.08, 84.49, 47.00, 72.20, 45.95, 44.35, 10.90, 27.05, 63.10, 63.50, 66.48, 97.25, 39.85, 81.50, 57.90, 02.78, 72.95, 41.55, 39.44, 15.85, 06.60, 13.60, 87.10, 86.40, 90.75, 48.30, 29.75, 16.25, 20.51, 45.40, 51.10, 82.00, 24.24, 92.88, 81.75, 18.50, 89.15, 55.75, 53.50, 74.90, 41.35, 61.40, 07.80, 56.80, 33.75, 75.02, 78.50, 46.10, 39.25, 95.00, 22.85, 36.00, 43.53, 63.80, 32.85, 88.30, 89.69, 05.75, 16.15, 15.25, 20.55, 44.50, 46.28, 71.75, 19.45, 41.75, 25.75, 09.05, 58.77, 76.20, 30.45, 71.75]
};
I'm assuming that date format is YYYY-MM-DD as in sample and for convenience I'm saving both dates and data in different arrays.
var dates = chart.dates;
var data = chart.data;
Keeping above assumptions in mind, you can easily group data by months.
var groupByMonth = {};
dates.forEach(function (d, i) {
var v = data[i];
var monthYear = d.slice(0, 7);
if (groupByMonth.hasOwnProperty(monthYear)) {
groupByMonth[monthYear].push(v);
} else {
groupByMonth[monthYear] = [v];
}
});
console.log(groupByMonth)
However to group by week, you need to find week number. Here is a extension method to do that so:
Date.prototype.getWeekNumber = function(){
var d = new Date(+this);
d.setHours(0,0,0);
d.setDate(d.getDate()+4-(d.getDay()||7));
return Math.ceil((((d-new Date(d.getFullYear(),0,1))/8.64e7)+1)/7);
};
Using above extension method you can easily group data by week number too. Week number repeats in each year so, I'm using combination of week and year to uniqly identify any week.
var groupByWeek = {};
dates.forEach(function (d, i) {
var v = data[i];
var weekYear = d.slice(0, 4) + '-' + new Date(d).getWeekNumber();
if (groupByWeek.hasOwnProperty(weekYear)) {
groupByWeek[weekYear].push(v);
} else {
groupByWeek[weekYear] = [v];
}
});
console.log(groupByWeek)
#Adnan Umer´s solution is probably better for your case since you already have the dates as strings, but here is a more general method in case you had the date instead of string:
function roundDate(date,type){
var d = new Date(date) // so as to not override
var types = ["day","week","month"]
var idx = types.indexOf(type)
if(idx > -1){ // remove all less than day
d.setMilliseconds(0)
d.setSeconds(0)
d.setMinutes(0)
d.setHours(0)
if(idx > 0){ //remove day of the week
d.setDate(d.getDate() - d.getDay()) // make it a sunday
if(idx > 1){ //remove day of month
d.setDate(1)
}
}
}
return d;
}
function parseDate(str){ // in your case, we need to keep the date disregarding the locale offset
return new Date(new Date(str).getTime() + new Date().getTimezoneOffset()*60*1000)
}
var lastDate = null
var grouping = "month"
var datesGrouped = []
var dataGrouped = []
function addToLast(arr,val){return arr[arr.length-1]+=val}
//I am assumming your dates are already sorted
chart.dates.map(function(date,index){
data = chart.data[index] // get corresponding data point
var group = roundDate(parseDate(date),grouping).getTime()
if(group == lastDate){
addToLast(dataGrouped, data)
}else{
datesGrouped.push(group)
dataGrouped.push(data)
lastDate = group
}
})
console.log(datesGrouped, dataGrouped)
Related
Adding custom number to timepicker
I need to make time picker, filled with numbers that I get from the backend. For example, the backend sent to me the time ranges when the product can light up, meaning initial value and how long can light up. Example, backend sends to the start time 17 hours, and the maximum range 3 hours. In first picker we select 17 and in second time picker choose the ultimate value of which shall not be less than the selected start value (17) and do not exceed the maximum time range that I received from the backend. here is html <p>From</p> <div uib-timepicker ng-model="mytimeStart" ng-change="changed()" hour-step="hstep" minute-step="mstep" show-meridian="ismeridian" readonly-input=true></div> <p>To</p> <div uib-timepicker ng-model="mytimeEnd" ng-change="changed()" hour-step="hstep" minute-step="mstep" show-meridian="ismeridian" readonly-input=true></div> <pre class="alert alert-info">Time is: {{mytime | date:'shortTime' }}</pre> my ctrl is here (example of service) $http.get(serviceBase + 'article') .success(function (response) { $scope.start= response.start; $scope.end= response.end; $scope.step = response.step; $scope.mytime = new Date(); $scope.options = { hstep: [1, 2, 3], mstep: [1, 5, 10, 15, 25, 30] }; $scope.hstep = 1; $scope.mstep = 30; $scope.ismeridian = false; $scope.toggleMode = function() { $scope.ismeridian = ! $scope.ismeridian; }; $scope.update = function() { var d = new Date(); d.setHours( 14 ); d.setMinutes( 0 ); $scope.mytime = d; }; $scope.changed = function(){ $scope.maxTime = $scope.max; $scope.minTime = $scope.min; $scope.stepTime = $scope.step; $scope.maxTimeStep = $scope.maxTime + $scope.stepTime; console.log($scope.maxTimeStep); }; $scope.clear = function() { $scope.mytime = null; }; }); With provided that if the chosen start time of 22:00, and the range is 4 hours, timepicker after 24:00 must switch to 01:00. And after the selection, send the selected value on the backend. I tried to ui.bootstrap.timepicker, but I do not know how to set up custom values, and that after a 24 hour shift counter to the 01:00, I hope you understand, thank you Here is [plunker][1] [1]: https://plnkr.co/edit/dJmNNQlJEglxi4mDp0ex?p=preview
CLNDR - How to get Today, Tomorrow and Current Month event list and count and print that information to separate div
Hi I'm using CLNDR and trying to get today, tomorrow and this month event list and event count information into separate div, I did try various things but couldn't find a better solution, can anyone, please help me with this. below code is for reference. doneRendering: function() { // make a moment object representing today var target = moment(); var tommorow = target.add(1,'days'); var eventsTommorow =[]; if(this.options.multiDayEvents) { eventsTommorow = $.makeArray( $(this.options.events).filter( function() { // filter the dates down to the ones that match. return ( ( tommorow.isSame(this._clndrStartDateObject, 'day') || tommorow.isAfter(this._clndrStartDateObject, 'day') ) && ( tommorow.isSame(this._clndrEndDateObject, 'day') || tommorow.isBefore(this._clndrEndDateObject, 'day') ) ); }) ); } else { eventsTommorow = $.makeArray( $(this.options.events).filter( function() { // filter the dates down to the ones that match. return this._clndrDateObject.format('YYYY-MM-DD') == dateString; }) ); } if(eventsTommorow.length) { console.log(eventsTommorow.length); var newNum = eventsTommorow.length < 10 ? "0" + eventsTommorow.length : eventsTommorow.length; return $('.td-emp-count.tdTommorow').text(newNum); } } }, <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Actually, my problem is I can not filter events and print them into separate div, I tried moment.js validation to get today event. but it didn;t work as expected. but finally I tried a simple method to get today, tomorrow and this month event count and it worked. but the problem is if a recurring event happens, let's say maternity leave start on 24th April to 30th may then I can not get between event counts. because I'm capturing CLNDR date, startDate, endDate there is no way to get between events count. please find the code doneRendering: function() { // make a moment object representing today var _self = this; function todayCount() { var totalDate = _.filter(_self.options.events, { date: moment().format('YYYY-MM-DD') }); var totalStartDate = _.filter(_self.options.events, { startDate: moment().format('YYYY-MM-DD') }); var totalEndDate = _.filter(_self.options.events, { endDate: moment().format('YYYY-MM-DD') }); var newNum = (totalDate.length + totalStartDate.length + totalEndDate.length) < 10 ? "0" + (totalDate.length + totalStartDate.length + totalEndDate.length) : (totalDate.length + totalStartDate.length + totalEndDate.length); return newNum; } $('.td-emp-count.tdToday').text(todayCount()); }, This is a simple hack and I'm using underscore.js. but this way I cannot get actual today count, the reason is if a recurring leave happens it cannot get middle leave day as a count eg: Sick leave startDate = 24th April 2016 endDate = 28th April 2016 when I apply my simple hack to this, I can only get 24th April (Monday) as one count and 28th April (Friday) as another count but when I try to print Wednesday leave counts, it cannot get this day as a leave count, I'm new to underscore.js but I really need to know how to print these variables into underscore.js template, Within template event count. I can get the today (actual leave) count by using _.each() method and print them on into day itself like this one <% _.each(days, function(day) { %> <div class="<%= day.classes %>" id="<%= day.id %>"> <div class="num"><%= day.day %></div> <% if (day.events.length) { %> <div class="eventsTitles">Leave : <strong><%= day.events.length %></strong></div> <% } %> <ul class="dayLeaveTypes"> <% _.each(day.events, function(event) { %> <li title="<%= event.type %>" id="<%= event.type %>" style="color:<%= event.color %>;">●</li> <% }); %> </ul> </div> <% }); %> but I need to get this actual count (only specific day today or tomorrow) and print them into separate div, not within the calendar. this is the whole idea i'm trying to solve. :)
Javascript not working when Alert is removed
I use D3.js and upon loading data from csv file the page isn't rendering when I remove the alert() statements. I;'m not a js specialist and I know it's something to do with asynchronous modes of operation but I don't know what to change. when alert("AAAAAAAA"); is removed it works I appreciate this is a bespoke issue so the other posts on the subject were not easily understood. Many Thanks Andy </style> <div id='dashboard2' align="center"></div> <div id='dashboard' align="center"></div> <script src="/xxxxxxxx/templates/uber/js/d3.min.js"></script> <script type="text/javascript"> var climate_csv = function() { d3.csv("/xxxxxxxx/templates/uber/js/theclimate.csv", function(climate_statistics) { //prices is an array of json objects containing the data in from the csv console.log("climate_statistics:", climate_statistics) climate_data = climate_statistics.map(function(d) { //each d is one line of the csv file represented as a json object console.log("d", d) month = d.month; console.log("month:", d.month, month) low = +d.low; console.log("low:", d.low, low) high = +d.high; console.log("high:", d.high, high) rainfall = +d.rainfall; console.log("rainfall:", d.rainfall, rainfall) sunshine = +d.sunshine; console.log("sunshine:", d.sunshine, sunshine) nighttime = +d.nighttime; console.log("nighttime:", d.nighttime, nighttime); return {"Month": month, "cStats": {"low": low , "high": high} , "rainfall": rainfall , "sun":{"Sunshine": sunshine , "Nighttime": nighttime}} }) }) } </script> <script type="text/javascript"> var climateStats2=[ {TempRange:'Low',cStats:{low:0}} ]; var climateStats3=[ {TempRange3:'High',cStats:{high:0}} ]; var climateStats4=[ {TempRange4:'Rainfall',cStats:{rainfall:0}} ]; var climateStats5=[ {TempRange4:'Rainfall',cStats5:{lower5:0, upper5:0}} ]; climate_csv(); alert("AAAAAAAA"); dashboard('#dashboard',climate_data,climateStats2,climateStats3,climateStats4,climateStats5);
Seems like your data is not fully loaded, when you call dashboard. It works with the alert because while the alert is open, your script is paused. Another thing is, that you are using global variables here. Let's avoid this using callbacks. D3's csv function uses a callback and calls it, when the data is ready. d3.csv("/xxxxxxxx/templates/uber/js/theclimate.csv", function(climate_statistics) { /* ... */ }); You can now either call the dashboard function inside the callback, or add a callback to your function. Let's do the latter here: var climate_csv = function( callback ) { d3.csv( "/xxxxxxxx/templates/uber/js/theclimate.csv", function( climate_statistics ) { var climate_data = climate_statistics.map(function( d ) { month = d.month; low = +d.low; high = +d.high; rainfall = +d.rainfall; sunshine = +d.sunshine; nighttime = +d.nighttime; return { "Month": month, "cStats": { "low": low, "high": high }, "rainfall": rainfall, "sun": { "Sunshine": sunshine, "Nighttime": nighttime } }; }); // we are done with mapping our data // let's call the callback with the data callback(climate_data); }); }; /* ... */ climate_csv(function( climate_data ) { dashboard('#dashboard', climate_data, climateStats2, climateStats3, climateStats4, climateStats5); });
Put the function that is not working when you remove the alert inside the success callback, that will make the trick. .csv works async, so when you put the alert, the request is completed on the background, when you remove it no "wait time" is added (as it should be) and that's why it's not working. Not using d3.js, but this doc could help: https://github.com/mbostock/d3/wiki/CSV d3.csv("path/to/file.csv") .row(function(d) { return {key: d.key, value: +d.value}; }) .get(function(error, rows) { console.log(rows); }); Hope it helps.
Google Charts axis labels
I'm trying to draw a chart in an application where I show data month on month, and would like to add labels to the x-axis to signify the years, but when I try to add a label it automatically adds the year and month cluttering up the look of the application. var e=new google.visualization.arrayToDataTable([ ["Year",county_1,county_2,"National"], ["Jan-10",med_jan_10_1,med_jan_10_2,med_jan_10_3], ["Feb-10",med_feb_10_1,med_feb_10_2,med_feb_10_3], ["Mar-10",med_mar_10_1,med_mar_10_2,med_mar_10_3], ["Apr-10",med_apr_10_1,med_apr_10_2,med_apr_10_3], ["May-10",med_may_10_1,med_may_10_2,med_may_10_3], ["Jun-10",med_jun_10_1,med_jun_10_2,med_jun_10_3], ["Jul-10",med_jul_10_1,med_jul_10_2,med_jul_10_3], ["Aug-10",med_aug_10_1,med_aug_10_2,med_aug_10_3], ["Sept-10",med_sep_10_1,med_sep_10_2,med_sep_10_3], ["Oct-10",med_oct_10_1,med_oct_10_2,med_oct_10_3], ["Nov-10",med_nov_10_1,med_nov_10_2,med_nov_10_3], ["Dec-10",med_dec_10_1,med_dec_10_2,med_dec_10_3], ["Jan-11",med_jan_11_1,med_jan_11_2,med_jan_11_3], ["Feb-11",med_feb_11_1,med_feb_11_2,med_feb_11_3], ["Mar-11",med_mar_11_1,med_mar_11_2,med_mar_11_3], ["Apr-11",med_apr_11_1,med_apr_11_2,med_apr_11_3], ["May-11",med_may_11_1,med_may_11_2,med_may_11_3], ["Jun-11",med_jun_11_1,med_jun_11_2,med_jun_11_3], ["Jul-11",med_jul_11_1,med_jul_11_2,med_jul_11_3], ["Aug-11",med_aug_11_1,med_aug_11_2,med_aug_11_3], ["Sept-11",med_sep_11_1,med_sep_11_2,med_sep_11_3], ["Oct-11",med_oct_11_1,med_oct_11_2,med_oct_11_3], ["Nov-11",med_nov_11_1,med_nov_11_2,med_nov_11_3], ["Dec-11",med_dec_11_1,med_dec_11_2,med_dec_11_3], ["Jan-12",med_jan_12_1,med_jan_12_2,med_jan_12_3], ["Feb-12",med_feb_12_1,med_feb_12_2,med_feb_12_3], ["Mar-12",med_mar_12_1,med_mar_12_2,med_mar_12_3], ["Apr-12",med_apr_12_1,med_apr_12_2,med_apr_12_3], ["May-12",med_may_12_1,med_may_12_2,med_may_12_3], ["Jun-12",med_jun_12_1,med_jun_12_2,med_jun_12_3], ["Jul-12",med_jul_12_1,med_jul_12_2,med_jul_12_3], ["Aug-12",med_aug_12_1,med_aug_12_2,med_aug_12_3], ["Sept-12",med_sep_12_1,med_sep_12_2,med_sep_12_3], ["Oct-12",med_oct_12_1,med_oct_12_2,med_oct_12_3], ["Nov-12",med_nov_12_1,med_nov_12_2,med_nov_12_3], ["Dec-12",med_dec_12_1,med_dec_12_2,med_dec_12_3], ["Jan-13",med_jan_13_1,med_jan_13_2,med_jan_13_3], ["Feb-13",med_feb_13_1,med_feb_13_2,med_feb_13_3], ["Mar-13",med_mar_13_1,med_mar_13_2,med_mar_13_3], ["Apr-13",med_apr_13_1,med_apr_13_2,med_apr_13_3], ["May-13",med_may_13_1,med_may_13_2,med_may_13_3], ["Jun-13",med_jun_13_1,med_jun_13_2,med_jun_13_3], ["Jul-13",med_jul_13_1,med_jul_13_2,med_jul_13_3], ["Aug-13",med_aug_13_1,med_aug_13_2,med_aug_13_3], ["Sept-13",med_sep_13_1,med_sep_13_2,med_sep_13_3], ["Oct-13",med_oct_13_1,med_oct_13_2,med_oct_13_3], ["Nov-13",med_nov_13_1,med_nov_13_2,med_nov_13_3], ["Dec-13",med_dec_13_1,med_dec_13_2,med_dec_13_3], ["Jan-14",med_jan_14_1,med_jan_14_2,med_jan_14_3], ["Feb-14",med_feb_14_1,med_feb_14_2,med_feb_14_3], ["Mar-14",med_mar_14_1,med_mar_14_2,med_mar_14_3], ["Apr-14",med_apr_14_1,med_apr_14_2,med_apr_14_3], ["May-14",med_may_14_1,med_may_14_2,med_may_14_3], ["Jun-14",med_jun_14_1,med_jun_14_2,med_jun_14_3], ["Jul-14",med_jul_14_1,med_jul_14_2,med_jul_14_3], ["Aug-14",med_aug_14_1,med_aug_14_2,med_aug_14_3], ["Sept-14",med_sep_14_1,med_sep_14_2,med_sep_14_3], ]); var b={ title:"House Price Index by County:", curveType:"function", is3D:true, legend:"top", width:600, height:250, hAxis: {textPosition: 'none' }}; var d=new google.visualization.LineChart(document.getElementById("chart_div")); d.draw(e,b) } Can anyone suggest a way to just have the labels of the years (2010:2014) on the graphs?
You will likely need to loop through the data to change it as you see fit: var exampleData = ["Jan-10",med_jan_10_1,med_jan_10_2,med_jan_10_3]; for (var i = 0; i < exampleData.length; i++) { var date = exampleData[i][0]; var year = '20' + date.split('-')[1]; exampleData[i][0] = year; }
jQuery Javascript Group Years by Decades
I have the below which loops through each div and returns the year that each div represents, but what I'd like to do is group the returned years into arrays of the decades. I'm not quite sure how to do that and was hoping someone could help with this. <div class="timeline-events timeline-year-1994">This is 1994<div> <div class="timeline-events timeline-year-1997">This is 1997<div> <div class="timeline-events timeline-year-2001">This is 2001<div> <div class="timeline-events timeline-year-2003">This is 2003<div> <div class="timeline-events timeline-year-2012">This is 2012<div> $('.timeline-events').each(function(){ console.log(this.className.match(/timeline-year-(\d+)?/)[1]); }); jsFiddle
You can work out the decade of a year by dividing by ten, flooring it and multiplying the result. From there on out it's grouping by your decade and merging it into your object: var groupedByDecade = {}; $('.timeline-events').each(function(){ var year = this.className.match(/timeline-year-(\d+)?/)[1], decade = Math.floor(year/10)*10; groupedByDecade[decade] = $.merge(groupedByDecade[decade] || [], [year]); }); JSFiddle
If i understood your question correctly; var decades = {}; $('.timeline-events').each(function(){ var year = this.className.match(/timeline-year-(\d+)?/)[1]; var decade = year.substring(0,3) + "0"; if (decades.hasOwnProperty( decade )) { decades[ decade ].push(year); } else { decades[ decade ] = [ year ]; } }); console.log(JSON.stringify(decades)); this creates an object with "decades" as property name which are of the type array, where the array contains the years.
UPDATED for grouping using underscore. http://jsfiddle.net/9o39jxLo/1/ var data = []; var decadedata = new Array(); $(function() { $('.timeline-events').each(function(){ var year = (this.className.match(/timeline-year-(\d+)?/)[1]); var decade = year - (year % 10); data.push({ "decade": decade, "year": year}); }); }); function testdecade(){ var groupedData = _.groupBy(data, function(d){return d.decade}); console.log(groupedData); }
One approach: // iterate over each of the '.timeline-events' elements: $('.timeline-events').each(function() { // finding first the specific (four-digit) year, // then getting a substring, for the decade and adding a 0: var decade = (/timeline\-year\-(\d{4})/).exec(this.className)[1].substring(2, 3) + '0', // if the previous element to the current element is a decade-grouping element and // it's the appropriate decade, we use that element; otherwise we create a new one: decadeDiv = $(this).prev('.decade-' + decade).length ? $(this).prev('.decade-' + decade) : $('<div />', { 'class': 'decade-' + decade // and insert that element before the current element: }).insertBefore(this); // we then append this element to the decade grouping element: $(this).appendTo(decadeDiv); }); src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div class="timeline-events timeline-year-1994">This is 1994 </div> <div class="timeline-events timeline-year-1997">This is 1997 </div> <div class="timeline-events timeline-year-2001">This is 2001 </div> <div class="timeline-events timeline-year-2003">This is 2003 </div> <div class="timeline-events timeline-year-2012">This is 2012 </div> In response to the clarification, in the comments to the question, that you'd like a JSON string, I'd suggest (assuming you want the HTML of the elements) the following: var decadeGroups = {}; $('.timeline-events').map(function () { var decade = [].filter.call(this.classList, function (cName) { return cName.indexOf('timeline-year-') === 0; }).join('').match(/\d{2}$/)[0].replace(/\d$/, '0'); if (decadeGroups[decade]) { decadeGroups[decade].push(this.outerHTML); } else { decadeGroups[decade] = [this.outerHTML]; } }); console.log(JSON.stringify(decadeGroups)); // initialising an object: var decadeGroups = {}; // iterating over the '.timeline-events' elements: $('.timeline-events').each(function() { // reducing the array-like classList, var decade = [].filter.call(this.classList, function(cName) { // keeping only the class-name that starts with 'timeline-year-': return cName.indexOf('timeline-year-') === 0; // turning that into a string with join('') // matching the last two digits of the year, // replacing the last digit with a zero: }).join('').match(/\d{2}$/)[0].replace(/\d$/, '0'); // if the object has a key of the current decade: if (decadeGroups[decade]) { // we add the outerHTML of the current element to that array: decadeGroups[decade].push(this.outerHTML); } else { // otherwise we create a new object key, and assign an array // containing the outerHTML of the current element: decadeGroups[decade] = [this.outerHTML]; } }); // logging the JSON-stringified object: console.log(JSON.stringify(decadeGroups)); src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div class="timeline-events timeline-year-1994">This is 1994 </div> <div class="timeline-events timeline-year-1997">This is 1997 </div> <div class="timeline-events timeline-year-2001">This is 2001 </div> <div class="timeline-events timeline-year-2003">This is 2003 </div> <div class="timeline-events timeline-year-2012">This is 2012 </div> References: JavaScript: Array.prototype.filter(). Array.prototype.join(). Element.classList. Function.prototype.call(). JSON.stringify(). String.indexOf(). String.prototype.match(). jQuery: appendTo(). each(). insertBefore(). prev().