diff --git a/src/bar.js b/src/bar.js
index fc60db4..dd79357 100644
--- a/src/bar.js
+++ b/src/bar.js
@@ -29,10 +29,18 @@ export default class Bar {
this.compute_duration();
this.corner_radius = this.gantt.options.bar_corner_radius;
this.width = this.gantt.config.column_width * this.duration;
+
this.progress_width =
this.gantt.config.column_width *
- this.duration *
+ this.actual_duration *
(this.task.progress / 100) || 0;
+ // Adjust for ignored areas
+ const progress_area = this.x + this.progress_width;
+ this.progress_width +=
+ this.gantt.config.ignored_positions.reduce((acc, val) => {
+ return acc + (val >= this.x && val <= progress_area);
+ }, 0) * this.gantt.config.column_width;
+
this.group = createSVG('g', {
class:
'bar-wrapper' +
@@ -149,7 +157,11 @@ export default class Bar {
append_to: this.bar_group,
});
const x =
- (date_utils.diff(this.task._start, this.gantt.gantt_start, 'hour') /
+ (date_utils.diff(
+ this.task._start,
+ this.gantt.gantt_start,
+ this.gantt.config.unit,
+ ) /
this.gantt.config.step) *
this.gantt.config.column_width;
@@ -348,7 +360,8 @@ export default class Bar {
'MMM D',
this.gantt.options.language,
);
- const subtitle = `${start_date} - ${end_date}
Progress: ${this.task.progress}`;
+
+ const subtitle = `${start_date} - ${end_date} (${this.actual_duration_in_days} days)
Progress: ${this.task.progress}`;
this.gantt.show_popup({
x,
target_element: this.$bar,
@@ -358,14 +371,13 @@ export default class Bar {
});
}
- update_bar_position({ x = null, width = null }) {
+ async update_bar_position({ x = null, width = null }) {
const bar = this.$bar;
+
if (x) {
- // get all x values of parent task
const xs = this.task.dependencies.map((dep) => {
return this.gantt.get_bar(dep).$bar.getX();
});
- // child task must not go before parent
const valid_x = xs.reduce((_, curr) => {
return x >= curr;
}, x);
@@ -374,17 +386,20 @@ export default class Bar {
return;
}
this.update_attr(bar, 'x', x);
+ this.x = x;
this.$date_highlight.style.left = x + 'px';
}
if (width) {
this.update_attr(bar, 'width', width);
this.$date_highlight.style.width = width + 'px';
}
+
this.update_label_position();
this.update_handle_position();
+ this.date_changed();
+ this.compute_duration();
+
if (this.gantt.options.show_expected_progress) {
- this.date_changed();
- this.compute_duration();
this.update_expected_progressbar_position();
}
this.update_progressbar_position();
@@ -448,9 +463,11 @@ export default class Bar {
}
progress_changed() {
- const new_progress = this.compute_progress();
- this.task.progress = new_progress;
- this.gantt.trigger_event('progress_change', [this.task, new_progress]);
+ this.task.progress = this.compute_progress();
+ this.gantt.trigger_event('progress_change', [
+ this.task,
+ this.task.progress,
+ ]);
}
set_action_completed() {
@@ -489,9 +506,20 @@ export default class Bar {
}
compute_progress() {
+ this.progress_width = this.$bar_progress.getWidth();
+ this.x = this.$bar_progress.getBBox().x;
+ const progress_area = this.x + this.progress_width;
const progress =
- (this.$bar_progress.getWidth() / this.$bar.getWidth()) * 100;
- return parseInt(progress, 10);
+ this.progress_width -
+ this.gantt.config.ignored_positions.reduce((acc, val) => {
+ return acc + (val >= this.x && val <= progress_area);
+ }, 0) *
+ this.gantt.config.column_width;
+ if (progress < 0) return 0;
+ const total =
+ this.$bar.getWidth() -
+ this.ignored_duration * this.gantt.config.column_width;
+ return parseInt((progress / total) * 100, 10);
}
compute_expected_progress() {
@@ -548,44 +576,48 @@ export default class Bar {
}
compute_duration() {
+ let actual_duration_in_days = 0,
+ duration_in_days = 0;
+ for (
+ let d = new Date(this.task._start);
+ d < this.task._end;
+ d.setDate(d.getDate() + 1)
+ ) {
+ duration_in_days++;
+ if (
+ !this.gantt.config.ignored_dates.find(
+ (k) => k.getTime() === d.getTime(),
+ ) &&
+ this.gantt.config.ignored_function &&
+ !this.gantt.config.ignored_function(d)
+ ) {
+ actual_duration_in_days++;
+ }
+ }
+ this.actual_duration_in_days = actual_duration_in_days;
+
this.duration =
- date_utils.diff(
- this.task._end,
- this.task._start,
+ date_utils.convert_scales(
+ duration_in_days + 'd',
this.gantt.config.unit,
) / this.gantt.config.step;
+
+ this.actual_duration =
+ date_utils.convert_scales(
+ actual_duration_in_days + 'd',
+ this.gantt.config.unit,
+ ) / this.gantt.config.step;
+ this.ignored_duration = this.duration - this.actual_duration;
}
get_snap_position(dx) {
- let odx = dx,
- rem,
- position;
-
- // if (this.gantt.view_is('Week')) {
- // rem = dx % (this.gantt.config.column_width / 7);
- // position =
- // odx -
- // rem +
- // (rem < this.gantt.config.column_width / 14
- // ? 0
- // : this.gantt.config.column_width / 7);
- // } else if (this.gantt.view_is('Month')) {
- // rem = dx % (this.gantt.config.column_width / 30);
- // position =
- // odx -
- // rem +
- // (rem < this.gantt.config.column_width / 60
- // ? 0
- // : this.gantt.config.column_width / 30);
- // } else {
- rem = dx % this.gantt.config.column_width;
- position =
+ let rem = odx % this.gantt.config.column_width;
+ let position =
odx -
rem +
(rem < this.gantt.config.column_width / 2
? 0
: this.gantt.config.column_width);
- // }
return position;
}
@@ -604,7 +636,7 @@ export default class Bar {
this.$expected_bar_progress.setAttribute(
'width',
this.gantt.config.column_width *
- this.duration *
+ this.actual_duration *
(this.expected_progress / 100) || 0,
);
}
@@ -612,10 +644,18 @@ export default class Bar {
update_progressbar_position() {
if (this.invalid || this.gantt.options.readonly) return;
this.$bar_progress.setAttribute('x', this.$bar.getX());
- this.$bar_progress.setAttribute(
- 'width',
- this.$bar.getWidth() * (this.task.progress / 100),
- );
+ let new_width =
+ this.actual_duration *
+ this.gantt.config.column_width *
+ (this.task.progress / 100);
+ const progress_area = this.x + this.progress_width;
+ new_width +=
+ this.gantt.config.ignored_positions.reduce((acc, val) => {
+ return acc + (val >= this.x && val <= progress_area);
+ }, 0) * this.gantt.config.column_width;
+
+ this.progress_width = new_width;
+ this.$bar_progress.setAttribute('width', new_width);
}
update_label_position() {
@@ -681,11 +721,3 @@ export default class Bar {
}
}
}
-
-function isFunction(functionToCheck) {
- let getType = {};
- return (
- functionToCheck &&
- getType.toString.call(functionToCheck) === '[object Function]'
- );
-}
diff --git a/src/defaults.js b/src/defaults.js
index 4025e29..4262358 100644
--- a/src/defaults.js
+++ b/src/defaults.js
@@ -112,7 +112,6 @@ const DEFAULT_OPTIONS = {
readonly: false,
progress_readonly: false,
dates_readonly: false,
- highlight_weekend: true,
scroll_to: 'start',
lines: 'both',
auto_move_label: true,
@@ -120,6 +119,7 @@ const DEFAULT_OPTIONS = {
view_mode_select: false,
default_snap: '1d',
holiday_highlight: { green: 'weekend' },
+ ignore: ['weekend'],
};
export { DEFAULT_OPTIONS, DEFAULT_VIEW_MODES };
diff --git a/src/index.js b/src/index.js
index 408c9ea..b956d15 100644
--- a/src/index.js
+++ b/src/index.js
@@ -75,10 +75,31 @@ export default class Gantt {
if (custom_mode) this.options = { ...this.options, custom_mode };
this.config = {};
+
+ this.config.ignored_dates = [];
+ this.config.ignored_positions = [];
+
+ if (typeof this.options.ignore !== 'function') {
+ if (typeof this.options.ignore === 'string')
+ this.options.ignore = [this.options.ignord];
+ for (let option of this.options.ignore) {
+ if (typeof option === 'function') {
+ this.config.ignored_function = option;
+ continue;
+ }
+ if (typeof option === 'string') {
+ if (option === 'weekend')
+ this.config.ignored_function = (d) =>
+ d.getDay() == 6 || d.getDay() == 0;
+ else this.config.ignored_dates.push(new Date(option + ' '));
+ }
+ }
+ } else {
+ this.config.ignored_function = this.options.ignore;
+ }
}
setup_tasks(tasks) {
- // prepare tasks
this.tasks = tasks
.map((task, i) => {
if (!task.start) {
@@ -278,8 +299,8 @@ export default class Gantt {
this.setup_layers();
this.make_grid();
this.make_dates();
- this.make_bars();
this.make_grid_extras();
+ this.make_bars();
this.make_arrows();
this.map_arrows_on_bars();
this.set_width();
@@ -533,13 +554,12 @@ export default class Gantt {
}
}
- highlightWeekends() {
+ highlightHolidays() {
for (let color in this.options.holiday_highlight) {
let check_highlight = this.options.holiday_highlight[color];
if (check_highlight === 'weekend')
check_highlight = (d) => d.getDay() === 0 || d.getDay() === 6;
- console.log(check_highlight);
let extra_func;
if (typeof check_highlight === 'object') {
@@ -560,6 +580,14 @@ export default class Gantt {
d <= this.gantt_end;
d.setDate(d.getDate() + 1)
) {
+ if (
+ this.config.ignored_dates.find(
+ (k) => k.getDate() == d.getDate(),
+ ) ||
+ (this.config.ignored_function &&
+ this.config.ignored_function(d))
+ )
+ continue;
if (check_highlight(d) || (extra_func && extra_func(d))) {
const x =
(date_utils.diff(
@@ -572,6 +600,7 @@ export default class Gantt {
const height =
(this.options.bar_height + this.options.padding) *
this.tasks.length;
+
createSVG('rect', {
x,
y:
@@ -592,7 +621,7 @@ export default class Gantt {
*
* @returns Object containing the x-axis distance and date of the current date, or null if the current date is out of the gantt range.
*/
- computeGridHighlightDimensions(view_mode) {
+ highlightToday(view_mode) {
const today = new Date();
if (today < this.gantt_start || today > this.gantt_end) return null;
let diff_in_units = date_utils.diff(
@@ -600,29 +629,14 @@ export default class Gantt {
this.gantt_start,
this.config.unit,
);
- return {
- x: (diff_in_units / this.config.step) * this.config.column_width,
- date: date_utils.format(
- today,
- this.config.view_mode.format_string,
- this.options.language,
- ),
- };
- }
-
- make_grid_highlights() {
- if (this.options.highlight_weekend) this.highlightWeekends();
-
- const highlightDimensions = this.computeGridHighlightDimensions(
- this.config.view_mode,
+ let left =
+ (diff_in_units / this.config.step) * this.config.column_width;
+ let date = date_utils.format(
+ today,
+ this.config.view_mode.format_string,
+ this.options.language,
);
- if (!highlightDimensions) return;
- const { x: left, date } = highlightDimensions;
- const top = this.options.header_height + this.options.padding / 2;
- const height =
- (this.options.bar_height + this.options.padding) *
- this.tasks.length;
this.$current_highlight = this.create_el({
top,
left,
@@ -637,6 +651,55 @@ export default class Gantt {
}
}
+ make_grid_highlights() {
+ this.highlightHolidays();
+
+ const top = this.options.header_height + this.options.padding / 2;
+ const height =
+ (this.options.bar_height + this.options.padding) *
+ this.tasks.length;
+ this.layers.grid.innerHTML += `
+
+ `;
+ for (
+ let d = new Date(this.gantt_start);
+ d <= this.gantt_end;
+ d.setDate(d.getDate() + 1)
+ ) {
+ if (
+ !this.config.ignored_dates.find(
+ (k) => k.getDate() == d.getDate(),
+ ) &&
+ this.config.ignored_function &&
+ !this.config.ignored_function(d)
+ )
+ continue;
+ let diff =
+ date_utils.convert_scales(
+ date_utils.diff(d, this.gantt_start) + 'd',
+ this.config.unit,
+ ) / this.config.step;
+
+ this.config.ignored_positions.push(diff * this.config.column_width);
+ createSVG('rect', {
+ x: diff * this.config.column_width,
+ y: top,
+ width: this.config.column_width,
+ height: height,
+ class: 'ignored-bar',
+ style: 'fill: url(#diagonalHatch);',
+ append_to: this.$svg,
+ });
+ }
+
+ const highlightDimensions = this.highlightToday(this.config.view_mode);
+
+ if (!highlightDimensions) return;
+ }
+
create_el({ left, top, width, height, id, classes, append_to }) {
let $el = document.createElement('div');
$el.classList.add(classes);
@@ -684,7 +747,6 @@ export default class Gantt {
get_dates_to_draw() {
let last_date_info = null;
const dates = this.dates.map((date, i) => {
- console.log('starting', date, last_date_info);
const d = this.get_date_info(date, last_date_info, i);
last_date_info = d;
return d;
@@ -994,6 +1056,7 @@ export default class Gantt {
const $bar = bar.$bar;
if (!$bar.finaldx) return;
bar.date_changed();
+ bar.compute_progress();
bar.set_action_completed();
});
});
@@ -1027,9 +1090,39 @@ export default class Gantt {
$bar_progress.max_dx = $bar.getWidth() - $bar_progress.getWidth();
});
+ const range_positions = this.config.ignored_positions.map((d) => [
+ d,
+ d + this.config.column_width,
+ ]);
+
$.on(this.$svg, 'mousemove', (e) => {
if (!is_resizing) return;
- let dx = (e.offsetX || e.layerX) - x_on_start;
+ let now_x = e.offsetX || e.layerX;
+
+ let moving_right = now_x > x_on_start;
+ if (moving_right) {
+ let k = range_positions.find(
+ ([begin, end]) => now_x >= begin && now_x < end,
+ );
+ while (k) {
+ now_x = k[1];
+ k = range_positions.find(
+ ([begin, end]) => now_x >= begin && now_x < end,
+ );
+ }
+ } else {
+ let k = range_positions.find(
+ ([begin, end]) => now_x > begin && now_x <= end,
+ );
+ while (k) {
+ now_x = k[0];
+ k = range_positions.find(
+ ([begin, end]) => now_x > begin && now_x <= end,
+ );
+ }
+ }
+
+ let dx = now_x - x_on_start;
if (dx > $bar_progress.max_dx) {
dx = $bar_progress.max_dx;
}