angular-gantt
Version:
Gantt chart component for AngularJS
355 lines (296 loc) • 15.2 kB
JavaScript
(function() {
'use strict';
angular.module('gantt').factory('GanttColumn', ['moment', function(moment) {
// Used to display the Gantt grid and header.
// The columns are generated by the column generator.
var Column = function(date, endDate, left, width, calendar, timeFramesWorkingMode, timeFramesNonWorkingMode) {
this.date = date;
this.endDate = endDate;
this.left = left;
this.width = width;
this.calendar = calendar;
this.duration = this.endDate.diff(this.date, 'milliseconds');
this.timeFramesWorkingMode = timeFramesWorkingMode;
this.timeFramesNonWorkingMode = timeFramesNonWorkingMode;
this.timeFrames = [];
this.currentDate = false;
this.visibleTimeFrames = [];
this.daysTimeFrames = {};
this.cropped = false;
this.originalSize = {left: this.left, width: this.width};
this.updateTimeFrames();
};
var getDateKey = function(date) {
return date.year() + '-' + date.month() + '-' + date.date();
};
Column.prototype.updateView = function() {
if (this.$element) {
if (this.currentDate) {
this.$element.addClass('gantt-foreground-col-current-date');
} else {
this.$element.removeClass('gantt-foreground-col-current-date');
}
this.$element.css({'left': this.left + 'px', 'width': this.width + 'px'});
for (var i = 0, l = this.timeFrames.length; i < l; i++) {
this.timeFrames[i].updateView();
}
}
};
Column.prototype.updateTimeFrames = function() {
var self = this;
if (self.calendar !== undefined && (self.timeFramesNonWorkingMode !== 'hidden' || self.timeFramesWorkingMode !== 'hidden')) {
var buildPushTimeFrames = function(timeFrames, startDate, endDate) {
return function(timeFrame) {
var start = timeFrame.start;
if (start === undefined) {
start = startDate;
}
var end = timeFrame.end;
if (end === undefined) {
end = endDate;
}
if (start < self.date) {
start = self.date;
}
if (end > self.endDate) {
end = self.endDate;
}
timeFrame = timeFrame.clone();
timeFrame.start = moment(start);
timeFrame.end = moment(end);
timeFrames.push(timeFrame);
};
};
var cDate = self.date;
var cDateStartOfDay = moment(cDate).startOf('day');
var cDateNextDay = cDateStartOfDay.add(1, 'day');
while (cDate < self.endDate) {
var timeFrames = self.calendar.getTimeFrames(cDate);
var nextCDate = moment.min(cDateNextDay, self.endDate);
timeFrames = self.calendar.solve(timeFrames, cDate, nextCDate);
var cTimeFrames = [];
angular.forEach(timeFrames, buildPushTimeFrames(cTimeFrames, cDate, nextCDate));
self.timeFrames = self.timeFrames.concat(cTimeFrames);
var cDateKey = getDateKey(cDate);
self.daysTimeFrames[cDateKey] = cTimeFrames;
cDate = nextCDate;
cDateStartOfDay = moment(cDate).startOf('day');
cDateNextDay = cDateStartOfDay.add(1, 'day');
}
angular.forEach(self.timeFrames, function(timeFrame) {
var positionDuration = timeFrame.start.diff(self.date, 'milliseconds');
var position = positionDuration / self.duration * self.width;
var timeFrameDuration = timeFrame.end.diff(timeFrame.start, 'milliseconds');
var timeFramePosition = timeFrameDuration / self.duration * self.width;
var hidden = false;
if (timeFrame.working && self.timeFramesWorkingMode !== 'visible') {
hidden = true;
} else if (!timeFrame.working && self.timeFramesNonWorkingMode !== 'visible') {
hidden = true;
}
if (!hidden) {
self.visibleTimeFrames.push(timeFrame);
}
timeFrame.hidden = hidden;
timeFrame.left = position;
timeFrame.width = timeFramePosition;
timeFrame.originalSize = {left: timeFrame.left, width: timeFrame.width};
});
if (self.timeFramesNonWorkingMode === 'cropped' || self.timeFramesWorkingMode === 'cropped') {
var timeFramesWidth = 0;
angular.forEach(self.timeFrames, function(timeFrame) {
if (!timeFrame.working && self.timeFramesNonWorkingMode !== 'cropped' ||
timeFrame.working && self.timeFramesWorkingMode !== 'cropped') {
timeFramesWidth += timeFrame.width;
}
});
if (timeFramesWidth !== self.width) {
var croppedRatio = self.width / timeFramesWidth;
var croppedWidth = 0;
var originalCroppedWidth = 0;
var allCropped = true;
angular.forEach(self.timeFrames, function(timeFrame) {
if (!timeFrame.working && self.timeFramesNonWorkingMode !== 'cropped' ||
timeFrame.working && self.timeFramesWorkingMode !== 'cropped') {
timeFrame.left = (timeFrame.left - croppedWidth) * croppedRatio;
timeFrame.width = timeFrame.width * croppedRatio;
timeFrame.originalSize.left = (timeFrame.originalSize.left - originalCroppedWidth) * croppedRatio;
timeFrame.originalSize.width = timeFrame.originalSize.width * croppedRatio;
timeFrame.cropped = false;
allCropped = false;
} else {
croppedWidth += timeFrame.width;
originalCroppedWidth += timeFrame.originalSize.width;
timeFrame.left = undefined;
timeFrame.width = 0;
timeFrame.originalSize = {left: undefined, width: 0};
timeFrame.cropped = true;
}
});
self.cropped = allCropped;
} else {
self.cropped = false;
}
}
}
};
Column.prototype.clone = function() {
return new Column(moment(this.date), moment(this.endDate), this.left, this.width, this.calendar);
};
Column.prototype.containsDate = function(date) {
return date > this.date && date <= this.endDate;
};
Column.prototype.equals = function(other) {
return this.date === other.date;
};
Column.prototype.roundTo = function(date, unit, offset, midpoint) {
// Waiting merge of https://github.com/moment/moment/pull/1794
if (unit === 'day') {
// Inconsistency in units in momentJS.
unit = 'date';
}
offset = offset || 1;
var value = date.get(unit);
switch (midpoint) {
case 'up':
value = Math.ceil(value / offset);
break;
case 'down':
value = Math.floor(value / offset);
break;
default:
value = Math.round(value / offset);
break;
}
var units = ['millisecond', 'second', 'minute', 'hour', 'date', 'month', 'year'];
date.set(unit, value * offset);
var indexOf = units.indexOf(unit);
for (var i = 0; i < indexOf; i++) {
date.set(units[i], 0);
}
return date;
};
Column.prototype.getMagnetDate = function(date, magnetValue, magnetUnit, timeFramesMagnet) {
if (magnetValue > 0 && magnetUnit !== undefined) {
var initialDate = date;
date = moment(date);
if (magnetUnit === 'column') {
// Snap to column borders only.
var position = this.getPositionByDate(date);
if (position < this.width / 2) {
date = moment(this.date);
} else {
date = moment(this.endDate);
}
} else {
// Round the value
date = this.roundTo(date, magnetUnit, magnetValue);
// Snap to column borders if date overflows.
if (date < this.date) {
date = moment(this.date);
} else if (date > this.endDate) {
date = moment(this.endDate);
}
}
if (timeFramesMagnet) {
var maxTimeFrameDiff = Math.abs(initialDate.diff(date, 'milliseconds'));
var currentTimeFrameDiff;
for (var i=0; i<this.timeFrames.length; i++) {
var timeFrame = this.timeFrames[i];
if (timeFrame.magnet) {
var previousTimeFrame = this.timeFrames[i-1];
var nextTimeFrame = this.timeFrames[i+1];
var timeFrameDiff;
if (previousTimeFrame === undefined || previousTimeFrame.working !== timeFrame.working) {
timeFrameDiff = Math.abs(initialDate.diff(timeFrame.start, 'milliseconds'));
if (timeFrameDiff < maxTimeFrameDiff && (currentTimeFrameDiff === undefined || timeFrameDiff < currentTimeFrameDiff)) {
currentTimeFrameDiff = timeFrameDiff;
date = timeFrame.start;
}
}
if (nextTimeFrame === undefined || nextTimeFrame.working !== timeFrame.working) {
timeFrameDiff = Math.abs(initialDate.diff(timeFrame.end, 'milliseconds'));
if (timeFrameDiff < maxTimeFrameDiff && (currentTimeFrameDiff === undefined || timeFrameDiff < currentTimeFrameDiff)) {
currentTimeFrameDiff = timeFrameDiff;
date = timeFrame.end;
}
}
}
}
}
}
return date;
};
Column.prototype.getDateByPositionUsingTimeFrames = function(position) {
for (var i = 0, l = this.timeFrames.length; i < l; i++) {
// TODO: performance optimization could be done.
var timeFrame = this.timeFrames[i];
if (!timeFrame.cropped && position >= timeFrame.left && position <= timeFrame.left + timeFrame.width) {
var positionDuration = timeFrame.getDuration() / timeFrame.width * (position - timeFrame.left);
var date = moment(timeFrame.start).add(positionDuration, 'milliseconds');
return date;
}
}
};
Column.prototype.getDateByPosition = function(position, magnetValue, magnetUnit, timeFramesMagnet) {
var positionDuration;
var date;
if (position < 0) {
position = 0;
}
if (position > this.width) {
position = this.width;
}
if (this.timeFramesNonWorkingMode === 'cropped' || this.timeFramesWorkingMode === 'cropped') {
date = this.getDateByPositionUsingTimeFrames(position);
}
if (date === undefined) {
positionDuration = this.duration / this.width * position;
date = moment(this.date).add(positionDuration, 'milliseconds');
}
date = this.getMagnetDate(date, magnetValue, magnetUnit, timeFramesMagnet);
return date;
};
Column.prototype.getDayTimeFrame = function(date) {
var dtf = this.daysTimeFrames[getDateKey(date)];
if (dtf === undefined) {
return [];
}
return dtf;
};
Column.prototype.getPositionByDate = function(date) {
var positionDuration;
var position;
if (this.timeFramesNonWorkingMode === 'cropped' || this.timeFramesWorkingMode === 'cropped') {
var croppedDate = date;
var timeFrames = this.getDayTimeFrame(croppedDate);
for (var i = 0; i < timeFrames.length; i++) {
var timeFrame = timeFrames[i];
if (croppedDate >= timeFrame.start && croppedDate <= timeFrame.end) {
if (timeFrame.cropped) {
if (timeFrames.length > i + 1) {
croppedDate = timeFrames[i + 1].start;
} else {
croppedDate = timeFrame.end;
}
} else {
positionDuration = croppedDate.diff(timeFrame.start, 'milliseconds');
position = positionDuration / timeFrame.getDuration() * timeFrame.width;
return this.left + timeFrame.left + position;
}
}
}
}
positionDuration = date.diff(this.date, 'milliseconds');
position = positionDuration / this.duration * this.width;
if (position < 0) {
position = 0;
}
if (position > this.width) {
position = this.width;
}
return this.left + position;
};
return Column;
}]);
}());