Modify dataGrouping in Highcharts - javascript

Highcharts hour data aggregation flow
follows the next rules: 01 AM = 01:00 + 01:15 + 01:30 + 01:45. (example). So all points between current hour and the next one.
I want it to follow the rule: 01 AM = 00:00 + 00:15 + 00:30 + 00:45
I didn't find a possibility to do that in datagrouping options of library.
Now I'm thinking about hack to modify serie.groupedData object (adding 1 hour to time present there), but unfortunately, even though data is modified it still display old values in chart
chartConfiguration.xAxis[0].events = {
setExtremes(event) {
this.series.forEach(serie => {
if (serie.hasGroupedData && serie.currentDataGrouping.unitName == "hour") {
let groupedData = serie.groupedData.map(dataPiece => {
dataPiece.x = dataPiece.x + 3600000;
return dataPiece;
});
serie.update(groupedData);
}
})
}
};

This code changes x value of all the points to one hour earlier if 1 hour data grouping is applied:
chart: {
events: {
render: function() {
if (redrawEnabled) { // prevent infinte recursive loop - this.redraw() calls render event
redrawEnabled = false;
var series = this.series[0],
isHourUnit = series.currentDataGrouping.unitName === 'hour';
if (isHourUnit && !hourOffsetApplied) {
// change the data
series.setData(data.slice().map((p) => [p[0] - hour, p[1]]), false);
hourOffsetApplied = true;
} else if (!isHourUnit && hourOffsetApplied) {
// resotre the original data
series.setData(data, false);
hourOffsetApplied = false;
}
this.redraw();
redrawEnabled = true;
}
}
}
},
Live demo: http://jsfiddle.net/BlackLabel/z850035n/
If you change dataGrouping.units to ['day', [1]] point are displayed without time offset.

Related

Five Consecutive days excluding weekends

I have written a code that raises a flag when number of leaves taken by a person exceeds 5 business days. However, it doesn't raise the flag when we have weekend in middle. Say, someone takes leave on 23,24 and then on 27,28 and 29. As 25 and 26 are weekends, tool doesn't count it. Can someone help me on how i must check the weekend dates here and push the value as "Yes" that includes weekend dates ?
function PrepareReport(reportData) {
var vacationData = [];
var dayValuesStr = '';
var dayValuesArray = [];
if ($("#ddlfromYear").val() == $("#ddltoYear").val()) {
count = parseInt($("#ddltoMonth").val()) - parseInt($("#ddlfromMonth").val());
}
else {
count = 12 - parseInt($("#ddlfromMonth").val()) + parseInt($("#ddltoMonth").val());
}
//val.ResourceID FullName EnterpriseID WorkOrder Domain Totalhours
vacationData.push(reportData.FullName);
vacationData.push(reportData.EnterpriseID);
vacationData.push(reportData.WorkOrder);
vacationData.push(reportData.Domain);
if (reportData.IsOffshore == 1) {
vacationData.push('Offshore');
}
else {
vacationData.push('Onshore');
}
vacationData.push(reportData.TOTALHOURS);
var counter = 0;
FlagCounterLastVal = 0;
vacationData.push("No");
for (var monthNum = 0; monthNum <= count; monthNum++) {
dayValuesStr = reportData['MONTH' + monthNum];
if (dayValuesStr) {
dayValuesArray = dayValuesStr.split(',');
var countArray = dayValuesArray.length - 1;
$.each(dayValuesArray, function (key, val) {
if (val.endsWith('.00'))//round off values
{
if (parseInt(val) == 0) {
vacationData.push('-');
counter = 0; // resetting counter to 0 for non-consecutive days
}
else {
if (FlagCounterLastVal > 0) {
counter = FlagCounterLastVal;
}
counter++;
vacationData.push(parseInt(val));
****if (counter >= 5 && FlagCounterLastVal == 0) {
var index = vacationData.indexOf("No");
vacationData[index] = "Yes";
}****
if (key == (countArray) && count > 0) { // vacation taken at the month ends
FlagCounterLastVal = counter;
}
}
}
else {
vacationData.push(val);
}
});
}
}
return vacationData;
}
You can use getDay for that.
var day = yourDateObject.getDay();
var isWeekend = (day === 6) || (day === 0);
6 = Saturday, 0 = Sunday
I won't be sharing code here as it's Data Structure and would like you to think a little bit, and try yourself first. If still you won't be able to code it then I will help you out in your code.
What I would do is have 2 arrays, both will be sorted this will help in search.
1. Having all weekends dates. // weekendArr
2. Leave dates taken by employee. //leaveArr
Steps
1. delete all the weekendArr dates from leaveArr.
2. Check if leaveArr have continues dates more then 5 or what ever you want it to be.
3. If it is greater then 5 then raise a flag for that employee.
Now you need to decide what data structure you will use to maintain employee leaves, employeeID, and leave flag.
Hope this will be enough to figure out code now.

How do I get daily step count in a gear fit 2 pro web based watchface?

I am building a html/js powered watchface for the gear fit 2 pro and I'm having trouble accomplishing what seems like a simple task: getting the daily step count.
I have poured over the documentation, but it only describes how to count either steps since the watchface has started, or steps since the device has been booted. Other watchfaces immediately detect the system-wide step count and display it, but I don't see how this is possible!
Does anyone have an example of how to do this? I suspect the stepdifference or readrecorderdata functions might be involved, but the first is impossible to use due to inadequate documentation and the second does not seem to actually be present in the device.
You can setAccumulativePedometerListener() for the time period sensor data required. In you case you can reset the listener at end of the day. I've written a pseudo_code for you to show daily step count.
var sensor_data = document.getElementById("sensor-data");
var step_count=0,
offset=0, // to reduce yesterday's data
currentDailyStep=0;
function updateTime() {
var datetime = tizen.time.getCurrentDateTime(),
hour = datetime.getHours(),
minute = datetime.getMinutes(),
second = datetime.getSeconds();
if(hour === 23 && minute === 59 && second === 59){ // at the end of the day
tizen.humanactivitymonitor.unsetAccumulativePedometerListener();
tizen.humanactivitymonitor.stop("PEDOMETER");
offset = step_count; // store today's count
pedometer_init(); //reset
}
/*
* Other Codes
* ............
* .........
*/
}
function onchangedCB(pedometerInfo) {
step_count = pedometerInfo.accumulativeTotalStepCount;
currentDailyStep = step_count - offset; // totl count - total count till yesterday
sensor_data.innerHTML = currentDailyStep;
}
function pedometer_init(){
tizen.humanactivitymonitor.start("PEDOMETER");
tizen.humanactivitymonitor.setAccumulativePedometerListener(onchangedCB);
}
function init(){
pedometer_init();
}
window.onload = init();
You need to reduce offset manually as stop() function don't reset the count. Store the daily step data If you are interested to show statistics.
In addition, In Tizen Developers API References there's a Code Sample using HumanActivityRecorder to record Step count daily, Please Check If it helps:
function onerror(error){
console.log(error.name + ": " + error.message);
}
function onread(data){
for (var idx = 0; idx < data.length; ++idx)
{
console.log("*** " + idx);
console.log('totalStepCount: ' + data[idx].totalStepCount);
}
}
var type = 'PEDOMETER';
var now = new Date();
var startTime = now.setDate(now.getDate() - 7);
var anchorTime = (new Date(2000, 1, 2, 6)).getTime();
var query ={
startTime: startTime / 1000,
anchorTime: anchorTime / 1000,
interval: 1440 /* 1 day */
};
try{
tizen.humanactivitymonitor.readRecorderData(type, query, onread, onerror);
}
catch (err){
console.log(err.name + ': ' + err.message);
}

Flatpickr onDayCreate add class

I'm struggling with a Flatpickr's (https://chmln.github.io/flatpickr/) onDayCreate event. Is here anyone who knows better how to check if picker's date object matches any date in my array of dates?
I have an array (or array of objects, not really sure how to call this) with dates going like this:
dates: {
"20161029": 3,
"20161030": 0,
"20161031": 0,
"20161101": 4,
"20161102": 4,
"20161103": 4,
"20161104": 5,
"20161105": 1,
"20161106": 0,
"20161107": 4,
"20161108": 3,
"20161109": 3,
"20161110": 4
}
And I would need to check if value is 0, >3 or 5 and add class to that date. Flatpickr has an example but it is using math function to randomize which dates should have new span element (example). But I can't configure my if else to addClass.
I created a dictionary of the classes just for convenience. You can use the keys of your object as a way to retrieve the number associated to a day when the flatpickr is triggering the onCreateDay callback. With the value associated to a day, you can get the class from the dictionary and, if it's not empty, add it to the element.
I've added some explanations to the code in order to highlight some things I consider relevant.
You can check it running the script in this page (full screen if don't see it) or you can check it in this fiddle.
Hope it helps.
var dates = {
20161029: 3,
20161030: 0,
20161031: 0,
20161101: 4,
20161102: 4,
20161103: 4,
20161104: 5,
20161105: 1,
20161106: 0,
20161107: 4,
20161108: 3,
20161109: 3,
20161110: 4
},
classDict = {
0: 'redClass',
1: 'greenClass',
3: 'blueClass',
4: 'greyClass',
5: 'orangeClass'
};
// Better always use a two digit format in your dates obj
function get2DigitFmt(val) {
return ('0' + val).slice(-2);
}
// onDayCreate event, add class to day if date has a class
flatpickr("#dayCreate", {
onDayCreate: function(dObj, dStr, fp, dayElem) {
var date = dayElem.dateObj,
// Note the + 1 in the month.
key = date.getFullYear() + get2DigitFmt(date.getMonth() + 1) + get2DigitFmt(date.getDate()),
value = classDict[dates[key]];
if (value) {
dayElem.className += ' ' + value;
}
}
});
.redClass {
background-color: red !important;
}
.greenClass {
background-color: green !important;
}
.blueClass {
background-color: blue !important;
}
.greyClass {
background-color: grey !important;
}
.orangeClass {
background-color: orange !important;
}
<link rel="stylesheet" href="https://unpkg.com/flatpickr/dist/flatpickr.min.css">
<script src="https://unpkg.com/flatpickr"></script>
<input id="dayCreate" type="text" placeholder="Select Date..">
UPDATE
The idea of the dictionary was to simplify adding/removing classes and avoid ugly switches or long ifs. However, you could easily modify the code in order to filter by value (only values greater than 3 get class) and add any class you want when the condition is met.
For instance (fiddle):
function getClass(value) {
// Here you could put any logic you want. Ifs, add the value to a string, whatever...
return value === 4 ? 'redClass' : 'blueClass';
}
// onDayCreate event, add class to day if date has a class
flatpickr("#dayCreate", {
onDayCreate: function(dObj, dStr, fp, dayElem) {
var date = dayElem.dateObj,
// Note the + 1 in the month.
key = date.getFullYear() + get2DigitFmt(date.getMonth() + 1) + get2DigitFmt(date.getDate()),
value = dates[key];
if (value > 3) {
dayElem.className += ' ' + getClass(value);
}
}
});
As you can see in the solutions I provided, there's no need to loop over the object all the time in order to get the value associated to a date, you can get it in constant time composing the key of the date from the date that flatpickr provides while constructing the day (onCreateDay callback).
UPDATE
According to the documentation (or so it seems), in order to get the date of a ccurrent day inside of the onDayCreate callback, you must use properties of fp (currentYear and currentMonth) and dayElem (textContent).
However, currentMonth returns always the month that the flatpicker is currently showing, not the month of the day (the calendar can be showing november, but the day can be in october or december) so a bit of tinkering is needed to avoid marking incorrect dates.
In this fiddle you can find a solution that doesn't use dateObj and works more like the documentation says.
And here's the code:
// Better always use a two digit format in your dates obj
function get2DigitFmt(val) {
return ('0' + val).slice(-2);
}
function getClass(value) {
// Here you could put any logic you want. Ifs, add the value to a string, whatever...
return value === 4 ? 'redClass' : 'blueClass';
}
// Adjust month depending on the month's day
function getMonth(currentMonth, dayClass) {
return currentMonth + (dayClass.contains('prevMonthDay') ? 0 : (1 + Number(dayClass.contains('nextMonthDay'))));
}
function getDateKey(year, month, day) {
return year + get2DigitFmt(month) + get2DigitFmt(day);
}
// onDayCreate event, add class to day if date has a class
flatpickr("#dayCreate", {
onDayCreate: function(dObj, dStr, fp, dayElem) {
var key = getDateKey(fp.currentYear, getMonth(fp.currentMonth, dayElem.className), dayElem.textContent),
value = dates[key];
if (value > 3) {
dayElem.className += ' ' + getClass(value);
}
}
});
Something like this should do it (intentionally verbose so you can see what's going on):
var dates = {
20161029: 3,
20161030: 0,
20161031: 0,
20161101: 4,
20161102: 4,
20161103: 4,
20161104: 5,
20161105: 1,
20161106: 0,
20161107: 4,
20161108: 3,
20161109: 3,
20161110: 4
};
flatpickr("#dayCreate", {
onDayCreate: function (dObj, dStr, fp, dayElem) {
//because you're working with an object and not an array,
//we'll iterate using its keys
var myDateKeys = Object.keys(dates);
for (var i = 0; i < myDateKeys.length; i++) {
var myDateKey = myDateKeys[i]; // "20161029
var myDateVal = dates[myDateKey]; // 3
var myYear = parseInt(myDateKey.substr(0, 4));
var myMonth = parseInt(myDateKey.substr(4, 2));
var myDay = parseInt(myDateKey.substr(6, 2));
var myDateObj = new Date(myYear + '-' + myMonth + '-' + myDay + ' 00:00:00');
var fDateObj = dayElem.dateObj;
//compare with event date
if (myDateObj.getTime() == fDateObj.getTime()) {
$(dayElem).addClass('myFancyDateClass' + myDateVal);
}
}
}
});
Then add style rules to your page to highlight the dates accordingly:
.myFancyDateClass0{
color: red;
}
.myFancyDateClass1{
color: green;
}
.myFancyDateClass3{
color: blue;
}
.myFancyDateClass4{
color: yellow;
}
.myFancyDateClass5{
color: pink;
}
Thanks nfreeze and acontell for helping me figure this out when I think documentation for that plugin was kind of hard to understand (for me atleast).
Both answers from nfreeze and acontell worked from the start when I figured out my error with the dayElem.dateObj.
The error was due my older version of the plugin. I had 2.0.5 (which is currently the version for bower) and dateObj started working when I manually updated to version 2.0.6.
My way to actually use this was to make new object (for my own purposes for the future use) and then use every items value for class:
var obj = dates,
keys = Object.keys(obj),
notAvailable = {},
fewAvailable = {},
available = {},
value,
date;
var formattedDates = keys.filter(function(key) {
if ( !obj[key]) {
date = moment( key, 'YYYYMMDD' ).format();
value = 'full';
return notAvailable[date] = value;
} else if( obj[key] < 3 ) {
date = moment( key, 'YYYYMMDD' ).format();
value = 'fewAvailable';
return fewAvailable[date] = value;
} else if( obj[key] >= 3 ) {
date = moment( key, 'YYYYMMDD' ).format();
value = 'available';
return available[date] = value;
}
});
var datesForPicker = Object.assign(notAvailable, fewAvailable, available);
And then onCreate event I used that :
flatpickr("#dayCreate", {
onDayCreate: function(dObj, dStr, fp, dayElem) {
var date = moment(dayElem.dateObj).format(),
value = datesForPicker[date];
if( value ){
dayElem.className += ' ' + value;
}
}
});
Use of Moment.js is logical because I already have it in my project, the reason for moment format is to get those dates in the same format. You could do that other ways too.

Find available days within a date range

Let's say there's a system that shows date availability for events. We have a main date range where all events go. Events can be date ranges themselves too.
Example:
[Date X]========================================[Date Y]
[A]=====[A] [B]=====[B][C]=====[C]
[ Event A ][ Open ][ Event B ][ Event C ]
Where Date X and Date Y are the main date range where events go. And A,B, and C are events that have been scheduled.
How can I efficiently retrieve the open date range?
Example 2:
var rangeStart = new Date("04-01-2016");
var rangeEnd = new Date("04-31-2016");
var eventAStart = new Date("04-01-2016");
var eventAEnd = new Date("04-06-2016");
var eventBStart = new Date("04-15-2016");
var eventBEnd = new Date("04-30-2016");
I need to return something like:
var availableRangeStart = "04-07-2015";
var availableRangeEnd = "04-14-2016";
because these are the dates in the main range that are not overlapped by "event" ranges.
To be exact on what I am trying to do:
My app is a trip planner where the user sets the dates for their trip and then adds different destinations to that trip that have their own dates. (User is going on a trip to Europe April 1st to April 30th, they will be in Paris on April 1st to April 6, then they will be in London April 15th to April 30th). But the user has not planned anything from April 7th to April 14th. I am trying to return these dates so that when they add a new destination, the dates are pre-filled.
I just give you an algorithm because the final implementation depends of your code.
var aprilAvailableDays = [true, true, true, etc...] // a boolean for each day
aprilEvents.forEach(function (event) {
for (var i = event.startDay; i <= event.endDay; i++) {
aprilAvailableDays[i] = false;
}
});
Here is a solution that returns from/to periods that are free:
// Helper function
function addDays(date, days) {
return new Date(date.getTime() + days * 24*60*60*1000);
}
// Main function
function gaps(period, events) {
events = events.slice(0).filter(function (a) {
// Exclude events which are outside the main period
return a.to >= period.from && a.from <= period.to;
}).sort(function (a, b) {
// Sort events by their starting date
return a.from - b.from;
});
var result = events.reduce(function (result, curr) {
if (curr.from - result.free > 0) {
// gap found
result.gaps.push({
from: result.free,
to: addDays(curr.from, -1)
});
}
if (curr.to - result.free >= 0) {
// first free day is after this event
result.free = addDays(curr.to, 1)
}
return result;
}, { gaps: [], free: period.from } );
// Potentially add gap between last event end period-end
if (period.to - result.free >= 0) {
result.gaps.push({
from: result.free,
to: period.to
});
}
return result.gaps;
}
// Sample data:
var period = {
from: new Date('2016-01-01'),
to: new Date('2016-12-31')
};
var events = [
{ from: new Date('2016-02-01'), to: new Date('2016-02-29') },
{ from: new Date('2016-03-01'), to: new Date('2016-03-15') },
{ from: new Date('2016-04-16'), to: new Date('2016-04-30') },
];
// Call to function
var res = gaps(period, events);
// Output in snippet
document.write('<pre>' + JSON.stringify(res, null, 4));

Amcharts: Interpolating step line chart values, show balloon of interpolated value

I'm a newbie at amCharts, trying to create a pretty simple step line chart.
I have data where the value stays constant a long time, and only records changes to the value, for example:
{
"timeOfDay": "18:20",
"value": 100
}, {
"timeOfDay": "19:40",
"value": 80
}, {
"timeOfDay": "21:40",
"value": 20
}, {
"timeOfDay": "22:40",
"value": 50
} // and so on
The chart should draw a horizontal line until the next change point, and then again until the next and so on, which I've managed to do. However, at the moment, it's only showing the balloon text at the data points and not where the cursor is, like this:
Here I'm hovering around time 20:15 though the screenshot taker didn't capture my cursor. The balloon text is displayed at the nearest data point. I'd want the balloon text to show up either at my cursor, or over the graph on the spot my cursor is in, and for it to show the time at the place where the cursor is in.
I know I could generate new data points in between the existing ones, but would rather not, since it would be pretty heavy of an operation since I want it to show the time with an accuracy of one second - that'd be thousands of data points an hour, and I'm trusting amCharts has an interpolation option for times hidden somewhere - I tried to search the docs / google for such an option, but didn't find one so far.
EDIT: Current chart code here: http://pastebin.com/BEZxgtCb
UPDATE: Managed to extract the time with the following functions and attaching an event listener to the chartCursor object - still can't get it to show anywhere else but on the predefined points on the graph though.
var parseDate = function(dateObj) {
return dateObj.getHours() + ":" + dateObj.getMinutes() + ":" + dateObj.getSeconds();
}
var handleMove = function(event) {
var time = event.chart.categoryAxis.coordinateToDate(event.x);
event.chart.graphs[0].balloonText = parseDate(time);
}
the balloon only shows up on actual data points which mean you need to inject them manually to show the balloon across the "interpolated" time. Following walkthrough your data, calculates the distance between the previous points and inject the amount of minutes in between.
var prevTime = undefined;
var prevValue = undefined;
var interpolate = [];
for ( var i1 = 0; i1 < chart.dataProvider.length; i1++ ) {
var item = chart.dataProvider[i1];
var curTime = AmCharts.stringToDate(item.timeOfDay,"JJ:NN")
var curVal = item.value;
if ( prevTime ) {
var distance = (Number(curTime) - Number(prevTime)) / 1000 / 60;
for ( var i2 = 0; i2 < distance; i2++ ) {
var newDate = new Date(prevTime);
newDate.setMinutes(prevTime.getMinutes() + i2);
if ( Number(newDate) < Number(curTime) ) {
interpolate.push({
timeOfDay: AmCharts.formatDate(newDate,"JJ:NN"),
value: prevValue
});
}
}
}
// NEW ONE
interpolate.push(item);
// FOR NEXT TIME
prevTime = curTime;
prevValue = curVal;
}
chart.dataProvider = interpolate;
Unfortunately there is no option to achieve that functionality.

Categories

Resources