Related
In my dhtmlx gantt chart, I've configured my scales to show the year and Quarter (3 PIs per quarter)
gantt.config.scales = [
{ name: 'year', format: '%Y' },
{ name: 'quarter', template: date => `PI-${(Math.floor((new Date(date).getMonth() / 3)) + 1 )}` },
]
This gives me the result
| 2022 |
| PI-1 | PI-2 | PI-3 |
I now want to add 2-week increments to my scale to represent sprints per Quarter (6 sprints per quarter, 24 sprints per year)
| 2022 |
| PI-1 | PI-2 | PI-3 |
| S1 | S2 | S3 | S4 | S5 | S6 |
I can't figure out how to structure my template to achieve this. Any thoughts?
It should be noted that if the increment is 2 weeks, then there will be more than 24 sprints in a year. The same can be said about months, one quarter is equal to 3 months, which contain a non-integer number of weeks, so the boundaries of the quarter and sprint scales cannot coincide.
To set sprints in the scale, you need to use Custom time units.
And so I can offer the following solution:
If the increment is to be strictly 2 weeks, first determine the start date of each sprint. It is also necessary to set the start for the first sprint, for example, I set it to start on Monday of the first week of the year:
// specify the start date of the first sprint
gantt.config.project_start = new Date(2022, 0, 3);
// find a sprint for a given date
function findSprint(date) {
const firstSprint = gantt.date.week_start(new Date(gantt.config.project_start));
let currentDate = firstSprint;
let direction = 1;
if (date < firstSprint) {
direction = -1;
}
const increment = 2 * direction;
let nextDate = gantt.date.add(currentDate, increment, "week");
let num = 0;
while (!(currentDate.valueOf() <= date.valueOf() && nextDate.valueOf() > date.valueOf())) {
if (increment > 0) {
currentDate = nextDate;
nextDate = gantt.date.add(currentDate, increment, "week");
} else {
nextDate = currentDate;
currentDate = gantt.date.add(currentDate, increment, "week");
}
num += 1 * direction;
}
return {
sprintStart: currentDate,
sprintEnd: nextDate,
sprintNumber: num
}
}
// custom scale unit definition
gantt.date.sprint_start = function (date) {
return findSprint(date).sprintStart;
};
The next step is to specify that the increment will be two weeks:
gantt.date.add_sprint = function (date, inc) {
return gantt.date.add(gantt.date.sprint_start(date), inc * 2, "week");
};
And finally, add a new unit to the scale:
gantt.config.scales = [
{ unit: "year", step: 1, format: "%Y" },
{
unit: 'quarter',
format: date => {
return `PI-${(Math.floor((new Date(date).getMonth() / 3)) + 1)}`
}
},
{ unit: 'sprint', step: 1, template: function (date) {
const sprintInfo = findSprint(date);
return `Sprint ${sprintInfo.sprintNumber + 1}, (${gantt.templates.date_grid(sprintInfo.sprintStart)} - ${gantt.templates.date_grid(new Date(sprintInfo.sprintEnd - 1))})`
}
}
];
Please see an example: https://snippet.dhtmlx.com/15u2bd85.
Sometimes for some reason the borders of the quarter and year scales do not match, and this is most likely a bug, if this happens to you, please contact dhtmlx technical support.
If you want to have 24 sprints in a year, you can set specific dates for each sprint, it might look something like this:
const sprints = [
{ name: 'S1', start_date: new Date(2022,00,01), end_date: new Date(2022,00,15) },
{ name: 'S2', start_date: new Date(2022,00,15), end_date: new Date(2022,01,01) },
{ name: 'S3', start_date: new Date(2022,01,01), end_date: new Date(2022,01,15) },
{ name: 'S4', start_date: new Date(2022,01,15), end_date: new Date(2022,02,01) },
{ name: 'S5', start_date: new Date(2022,02,01), end_date: new Date(2022,02,15) },
{ name: 'S6', start_date: new Date(2022,02,15), end_date: new Date(2022,03,01) },
...
];
You need to add the given sprints to the unit:
gantt.date.sprints_start = function(date) {
return date;
};
function getSprint(date,type) {
const tempDate = new Date(date);
for (let i = 0; i < sprints.length; i++) {
if (+tempDate >= +sprints[i].start_date && +tempDate < +sprints[i].end_date) {
if (type == 'scaleUnit') {
return sprints[i].end_date;
}
if (type == 'template') {
return "<div class='sprint'>"+sprints[i].name+"</div>";
}
}
}
if (type == 'scaleUnit') {
const newDate = gantt.date.add(date, 1,'day');
return newDate;
}
if (type == 'template') {
return gantt.date.date_to_str("%m-%d")(date);
}
}
gantt.date.add_sprints = function(date, inc) {
return getSprint(date, 'scaleUnit');
};
const sprintsTemplate = function(date) {
return getSprint(date,'template');
}
and also add a new unit to the scale:
gantt.config.scales = [
{ unit: "year", step: 1, format: "%Y" },
{
unit: 'quarter',
format: date => {
return `PI-${(Math.floor((new Date(date).getMonth() / 3)) + 1 )}`
}
},
{ unit: 'month', step: 1, format: "%M" },
{ unit: 'sprints', step: 1, template: sprintsTemplate },
];
Here is an example: https://snippet.dhtmlx.com/0xznw5m9
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.
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('/');
{
"id":1,
"name":"Jack",
"date":"01-06-2017"
},
{
"id":2,
"name":"Allen",
"date":"07-08-2017"
},
{
"id":3,
"name":"Annie",
"date":"22-11-2017"
},
This JSON stored in 'members' Array I want filter the member based on start and end date like:
let startDate;
let endDate;
let selectedMembers = this.members.filter(m => m.date > startDate && m.date < endDate);
something like that..
This is the solution of my problem:
members:any[] = [{
"id":1,
"name":"Jack",
"date":"01-06-2017"
},
{
"id":2,
"name":"Allen",
"date":"07-08-2017"
},
{
"id":3,
"name":"Annie",
"date":"22-11-2017"
}];
let start = "01-02-2017";
let end = "06-07-2017";
let.selectedMembers = this.members.filter(
m => new Date(m.date) >= new Date(startDate) && new Date(m.date) <= new Date(endDate)
);
console.log(selectedMembers);
u need first to transform the dates from string to Date type in order to compare them properly then do something like:
let members: any[] = [{
"id": 1,
"name": "Jack",
"date": "01-06-2017" // this should be of object Date()
},
{
"id": 2,
"name": "Allen",
"date": "07-08-2017" // this should be of object Date()
},
{
"id": 3,
"name": "Annie",
"date": "22-11-2017" // this should be of object Date()
}];
let start = "01-02-2017; // this should be of object Date()
let end = "06-07-2017"; // this should be of object Date()
let selectedMembers = members.filter(m => {
if ( m.date > start && m.date < end) // or you can cast here to Date()
return m;
});
console.log(selectedMembers);
To sort dates stored as strings, you should use an ISO based format:
https://es.wikipedia.org/wiki/ISO_8601
The line you wrote will work with dates like that:
{ "id":1, "name":"Jack", "date":"2017-06-01" },
{ "id":2, "name":"Allen", "date":"2017-08-07" },
{ "id":3, "name":"Annie", "date":"2017-11-22" }
If not, you have to parse the strings to date object and use them in the comparison. Something like this:
let startDate = new Date('2017-05-01');
let endDate = new Date('2017-09-01);
let selectedMembers = this.members.filter(m => new Date(m.date) > startDate && new Date(m.date) < endDate);
You need to reverse your date format, then convert it to TIMESTAMP for comparison.
members = [{
"id":1,
"name":"Jack",
"date":"01-06-2017"
},
{
"id":2,
"name":"Allen",
"date":"07-08-2017"
},
{
"id":3,
"name":"Annie",
"date":"22-11-2017"
}];
const startDate ="25-06-2017";
const endDate = "10-11-2017";
// Here reverse the date format & then convert to TIMESTAMP
function reverseAndTimeStamp(dateString) {
const reverse = new Date(dateString.split("-").reverse().join("-"));
return reverse.getTime();
}
// A simple array filter like what you did
const selectedMembers = members.filter(m => {
return reverseAndTimeStamp(m.date) > reverseAndTimeStamp(startDate) && reverseAndTimeStamp(m.date) < reverseAndTimeStamp(endDate)
}
);
console.log(selectedMembers); // the result objects
Because you use french date format, you need own made transformer between string and Date object.
Then you have to deal with Date javascript object and timestamp representation.
Something like this will work.
function parseDate(input) {
var parts = input.match(/(\d+)/g);
// note parts[1]-1
return new Date(parts[2], parts[1]-1, parts[0]).getTime();
}
let startDate = parseDate('01-06-2017');
let endDate = parseDate('01-06-2018');
let selectedMembers = this.members.filter(m => {
const current = parseDate(m.date);
return current > startDate && current < endDate;
});
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()