@workiom/frappe-gantt 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/bar.js ADDED
@@ -0,0 +1,804 @@
1
+ import date_utils from './date_utils';
2
+ import { $, createSVG, animateSVG } from './svg_utils';
3
+
4
+ export default class Bar {
5
+ constructor(gantt, task) {
6
+ this.set_defaults(gantt, task);
7
+ this.prepare_wrappers();
8
+ this.prepare_helpers();
9
+ this.refresh();
10
+ }
11
+
12
+ refresh() {
13
+ this.bar_group.innerHTML = '';
14
+ this.handle_group.innerHTML = '';
15
+ if (this.task.custom_class) {
16
+ this.group.classList.add(this.task.custom_class);
17
+ } else {
18
+ this.group.classList = ['bar-wrapper'];
19
+ }
20
+
21
+ this.prepare_values();
22
+ this.draw();
23
+ this.bind();
24
+ }
25
+
26
+ set_defaults(gantt, task) {
27
+ this.action_completed = false;
28
+ this.gantt = gantt;
29
+ this.task = task;
30
+ this.name = this.name || '';
31
+ }
32
+
33
+ prepare_wrappers() {
34
+ this.group = createSVG('g', {
35
+ class:
36
+ 'bar-wrapper' +
37
+ (this.task.custom_class ? ' ' + this.task.custom_class : ''),
38
+ 'data-id': this.task.id,
39
+ });
40
+ this.bar_group = createSVG('g', {
41
+ class: 'bar-group',
42
+ append_to: this.group,
43
+ });
44
+ this.handle_group = createSVG('g', {
45
+ class: 'handle-group',
46
+ append_to: this.group,
47
+ });
48
+ }
49
+
50
+ prepare_values() {
51
+ this.invalid = this.task.invalid;
52
+ this.height = this.gantt.options.bar_height;
53
+ this.image_size = this.height - 5;
54
+ this.task._start = new Date(this.task.start);
55
+ this.task._end = new Date(this.task.end);
56
+ this.compute_x();
57
+ this.compute_y();
58
+ this.compute_duration();
59
+ this.corner_radius = this.gantt.options.bar_corner_radius;
60
+ this.width = this.gantt.config.column_width * this.duration;
61
+ if (!this.task.progress || this.task.progress < 0)
62
+ this.task.progress = 0;
63
+ if (this.task.progress > 100) this.task.progress = 100;
64
+ }
65
+
66
+ prepare_helpers() {
67
+ SVGElement.prototype.getX = function () {
68
+ return +this.getAttribute('x');
69
+ };
70
+ SVGElement.prototype.getY = function () {
71
+ return +this.getAttribute('y');
72
+ };
73
+ SVGElement.prototype.getWidth = function () {
74
+ return +this.getAttribute('width');
75
+ };
76
+ SVGElement.prototype.getHeight = function () {
77
+ return +this.getAttribute('height');
78
+ };
79
+ SVGElement.prototype.getEndX = function () {
80
+ return this.getX() + this.getWidth();
81
+ };
82
+ }
83
+
84
+ prepare_expected_progress_values() {
85
+ this.compute_expected_progress();
86
+ this.expected_progress_width =
87
+ this.gantt.options.column_width *
88
+ this.duration *
89
+ (this.expected_progress / 100) || 0;
90
+ }
91
+
92
+ draw() {
93
+ this.draw_bar();
94
+ this.draw_progress_bar();
95
+ if (this.gantt.options.show_expected_progress) {
96
+ this.prepare_expected_progress_values();
97
+ this.draw_expected_progress_bar();
98
+ }
99
+ this.draw_label();
100
+ this.draw_resize_handles();
101
+
102
+ if (this.task.thumbnail) {
103
+ this.draw_thumbnail();
104
+ }
105
+ }
106
+
107
+ draw_bar() {
108
+ this.$bar = createSVG('rect', {
109
+ x: this.x,
110
+ y: this.y,
111
+ width: this.width,
112
+ height: this.height,
113
+ rx: this.corner_radius,
114
+ ry: this.corner_radius,
115
+ class: 'bar',
116
+ append_to: this.bar_group,
117
+ });
118
+ if (this.task.color) this.$bar.style.fill = this.task.color;
119
+ animateSVG(this.$bar, 'width', 0, this.width);
120
+
121
+ if (this.invalid) {
122
+ this.$bar.classList.add('bar-invalid');
123
+ }
124
+ }
125
+
126
+ draw_expected_progress_bar() {
127
+ if (this.invalid) return;
128
+ this.$expected_bar_progress = createSVG('rect', {
129
+ x: this.x,
130
+ y: this.y,
131
+ width: this.expected_progress_width,
132
+ height: this.height,
133
+ rx: this.corner_radius,
134
+ ry: this.corner_radius,
135
+ class: 'bar-expected-progress',
136
+ append_to: this.bar_group,
137
+ });
138
+
139
+ animateSVG(
140
+ this.$expected_bar_progress,
141
+ 'width',
142
+ 0,
143
+ this.expected_progress_width,
144
+ );
145
+ }
146
+
147
+ draw_progress_bar() {
148
+ if (this.invalid) return;
149
+ this.progress_width = this.calculate_progress_width();
150
+ let r = this.corner_radius;
151
+ if (!/^((?!chrome|android).)*safari/i.test(navigator.userAgent))
152
+ r = this.corner_radius + 2;
153
+ this.$bar_progress = createSVG('rect', {
154
+ x: this.x,
155
+ y: this.y,
156
+ width: this.progress_width,
157
+ height: this.height,
158
+ rx: r,
159
+ ry: r,
160
+ class: 'bar-progress',
161
+ append_to: this.bar_group,
162
+ });
163
+ if (this.task.color_progress)
164
+ this.$bar_progress.style.fill = this.task.color_progress;
165
+ const x =
166
+ (date_utils.diff(
167
+ this.task._start,
168
+ this.gantt.gantt_start,
169
+ this.gantt.config.unit,
170
+ ) /
171
+ this.gantt.config.step) *
172
+ this.gantt.config.column_width;
173
+
174
+ let $date_highlight = this.gantt.create_el({
175
+ classes: `date-range-highlight hide highlight-${this.task.id}`,
176
+ width: this.width,
177
+ left: x,
178
+ });
179
+ this.$date_highlight = $date_highlight;
180
+ this.gantt.$lower_header.prepend(this.$date_highlight);
181
+
182
+ animateSVG(this.$bar_progress, 'width', 0, this.progress_width);
183
+ }
184
+
185
+ calculate_progress_width() {
186
+ const width = this.$bar.getWidth();
187
+ const ignored_end = this.x + width;
188
+ const total_ignored_area =
189
+ this.gantt.config.ignored_positions.reduce((acc, val) => {
190
+ return acc + (val >= this.x && val < ignored_end);
191
+ }, 0) * this.gantt.config.column_width;
192
+ let progress_width =
193
+ ((width - total_ignored_area) * this.task.progress) / 100;
194
+ const progress_end = this.x + progress_width;
195
+ const total_ignored_progress =
196
+ this.gantt.config.ignored_positions.reduce((acc, val) => {
197
+ return acc + (val >= this.x && val < progress_end);
198
+ }, 0) * this.gantt.config.column_width;
199
+
200
+ progress_width += total_ignored_progress;
201
+
202
+ let ignored_regions = this.gantt.get_ignored_region(
203
+ this.x + progress_width,
204
+ );
205
+
206
+ while (ignored_regions.length) {
207
+ progress_width += this.gantt.config.column_width;
208
+ ignored_regions = this.gantt.get_ignored_region(
209
+ this.x + progress_width,
210
+ );
211
+ }
212
+ this.progress_width = progress_width;
213
+ return progress_width;
214
+ }
215
+
216
+ draw_label() {
217
+ let x_coord = this.x + this.$bar.getWidth() / 2;
218
+
219
+ if (this.task.thumbnail) {
220
+ x_coord = this.x + this.image_size + 5;
221
+ }
222
+
223
+ createSVG('text', {
224
+ x: x_coord,
225
+ y: this.y + this.height / 2,
226
+ innerHTML: this.task.name,
227
+ class: 'bar-label',
228
+ append_to: this.bar_group,
229
+ });
230
+ // labels get BBox in the next tick
231
+ requestAnimationFrame(() => this.update_label_position());
232
+ }
233
+
234
+ draw_thumbnail() {
235
+ let x_offset = 10,
236
+ y_offset = 2;
237
+ let defs, clipPath;
238
+
239
+ defs = createSVG('defs', {
240
+ append_to: this.bar_group,
241
+ });
242
+
243
+ createSVG('rect', {
244
+ id: 'rect_' + this.task.id,
245
+ x: this.x + x_offset,
246
+ y: this.y + y_offset,
247
+ width: this.image_size,
248
+ height: this.image_size,
249
+ rx: '15',
250
+ class: 'img_mask',
251
+ append_to: defs,
252
+ });
253
+
254
+ clipPath = createSVG('clipPath', {
255
+ id: 'clip_' + this.task.id,
256
+ append_to: defs,
257
+ });
258
+
259
+ createSVG('use', {
260
+ href: '#rect_' + this.task.id,
261
+ append_to: clipPath,
262
+ });
263
+
264
+ createSVG('image', {
265
+ x: this.x + x_offset,
266
+ y: this.y + y_offset,
267
+ width: this.image_size,
268
+ height: this.image_size,
269
+ class: 'bar-img',
270
+ href: this.task.thumbnail,
271
+ clipPath: 'clip_' + this.task.id,
272
+ append_to: this.bar_group,
273
+ });
274
+ }
275
+
276
+ draw_resize_handles() {
277
+ if (this.invalid || this.gantt.options.readonly) return;
278
+
279
+ const bar = this.$bar;
280
+ const handle_width = 3;
281
+ this.handles = [];
282
+ if (!this.gantt.options.readonly_dates) {
283
+ this.handles.push(
284
+ createSVG('rect', {
285
+ x: bar.getEndX() - handle_width / 2,
286
+ y: bar.getY() + this.height / 4,
287
+ width: handle_width,
288
+ height: this.height / 2,
289
+ rx: 2,
290
+ ry: 2,
291
+ class: 'handle right',
292
+ append_to: this.handle_group,
293
+ }),
294
+ );
295
+
296
+ this.handles.push(
297
+ createSVG('rect', {
298
+ x: bar.getX() - handle_width / 2,
299
+ y: bar.getY() + this.height / 4,
300
+ width: handle_width,
301
+ height: this.height / 2,
302
+ rx: 2,
303
+ ry: 2,
304
+ class: 'handle left',
305
+ append_to: this.handle_group,
306
+ }),
307
+ );
308
+ }
309
+ if (!this.gantt.options.readonly_progress) {
310
+ const bar_progress = this.$bar_progress;
311
+ this.$handle_progress = createSVG('circle', {
312
+ cx: bar_progress.getEndX(),
313
+ cy: bar_progress.getY() + bar_progress.getHeight() / 2,
314
+ r: 4.5,
315
+ class: 'handle progress',
316
+ append_to: this.handle_group,
317
+ });
318
+ this.handles.push(this.$handle_progress);
319
+ }
320
+
321
+ for (let handle of this.handles) {
322
+ $.on(handle, 'mouseenter', () => handle.classList.add('active'));
323
+ $.on(handle, 'mouseleave', () => handle.classList.remove('active'));
324
+ }
325
+ }
326
+
327
+ bind() {
328
+ if (this.invalid) return;
329
+ this.setup_click_event();
330
+ }
331
+
332
+ setup_click_event() {
333
+ let task_id = this.task.id;
334
+ $.on(this.group, 'mouseover', (e) => {
335
+ this.gantt.trigger_event('hover', [
336
+ this.task,
337
+ e.screenX,
338
+ e.screenY,
339
+ e,
340
+ ]);
341
+ });
342
+
343
+ if (this.gantt.options.popup_on === 'click') {
344
+ $.on(this.group, 'mouseup', (e) => {
345
+ const posX = e.offsetX || e.layerX;
346
+ if (this.$handle_progress) {
347
+ const cx = +this.$handle_progress.getAttribute('cx');
348
+ if (cx > posX - 1 && cx < posX + 1) return;
349
+ if (this.gantt.bar_being_dragged) return;
350
+ }
351
+ this.gantt.show_popup({
352
+ x: e.offsetX || e.layerX,
353
+ y: e.offsetY || e.layerY,
354
+ task: this.task,
355
+ target: this.$bar,
356
+ });
357
+ });
358
+ }
359
+ let timeout;
360
+ $.on(this.group, 'mouseenter', (e) => {
361
+ timeout = setTimeout(() => {
362
+ if (this.gantt.options.popup_on === 'hover')
363
+ this.gantt.show_popup({
364
+ x: e.offsetX || e.layerX,
365
+ y: e.offsetY || e.layerY,
366
+ task: this.task,
367
+ target: this.$bar,
368
+ });
369
+ this.gantt.$container
370
+ .querySelector(`.highlight-${task_id}`)
371
+ .classList.remove('hide');
372
+ }, 200);
373
+ });
374
+ $.on(this.group, 'mouseleave', () => {
375
+ clearTimeout(timeout);
376
+ if (this.gantt.options.popup_on === 'hover')
377
+ this.gantt.popup?.hide?.();
378
+ this.gantt.$container
379
+ .querySelector(`.highlight-${task_id}`)
380
+ .classList.add('hide');
381
+ });
382
+
383
+ $.on(this.group, 'click', () => {
384
+ this.gantt.trigger_event('click', [this.task]);
385
+ });
386
+
387
+ $.on(this.group, 'dblclick', (e) => {
388
+ if (this.action_completed) {
389
+ // just finished a move action, wait for a few seconds
390
+ return;
391
+ }
392
+ this.group.classList.remove('active');
393
+ if (this.gantt.popup)
394
+ this.gantt.popup.parent.classList.remove('hide');
395
+
396
+ this.gantt.trigger_event('double_click', [this.task]);
397
+ });
398
+ let tapedTwice = false;
399
+ $.on(this.group, 'touchstart', (e) => {
400
+ if (!tapedTwice) {
401
+ tapedTwice = true;
402
+ setTimeout(function () {
403
+ tapedTwice = false;
404
+ }, 300);
405
+ return false;
406
+ }
407
+ e.preventDefault();
408
+ //action on double tap goes below
409
+
410
+ if (this.action_completed) {
411
+ // just finished a move action, wait for a few seconds
412
+ return;
413
+ }
414
+ this.group.classList.remove('active');
415
+ if (this.gantt.popup)
416
+ this.gantt.popup.parent.classList.remove('hide');
417
+
418
+ this.gantt.trigger_event('double_click', [this.task]);
419
+ });
420
+ }
421
+
422
+ update_bar_position({ x = null, width = null }) {
423
+ const bar = this.$bar;
424
+
425
+ if (x) {
426
+ // Validate against dependency constraints
427
+ if (!this.validate_dependency_constraints(x, width)) {
428
+ return;
429
+ }
430
+ this.update_attr(bar, 'x', x);
431
+ this.x = x;
432
+ this.$date_highlight.style.left = x + 'px';
433
+ }
434
+ if (width > 0) {
435
+ this.update_attr(bar, 'width', width);
436
+ this.$date_highlight.style.width = width + 'px';
437
+ }
438
+
439
+ this.update_label_position();
440
+ this.update_handle_position();
441
+ this.date_changed();
442
+ this.compute_duration();
443
+
444
+ if (this.gantt.options.show_expected_progress) {
445
+ this.update_expected_progressbar_position();
446
+ }
447
+
448
+ this.update_progressbar_position();
449
+ this.update_arrow_position();
450
+ }
451
+
452
+ validate_dependency_constraints(new_x, new_width = null) {
453
+ const dependencies_type = this.task.dependencies_type || this.gantt.options.dependencies_type;
454
+
455
+ // For fixed dependencies, use old validation logic
456
+ if (dependencies_type === 'fixed') {
457
+ const xs = this.task.dependencies.map((dep) => {
458
+ return this.gantt.get_bar(dep).$bar.getX();
459
+ });
460
+ return xs.reduce((prev, curr) => {
461
+ return prev && new_x >= curr;
462
+ }, true);
463
+ }
464
+
465
+ // Calculate what the new dates would be
466
+ const new_start_x = new_x / this.gantt.config.column_width;
467
+ const new_start = date_utils.add(
468
+ this.gantt.gantt_start,
469
+ new_start_x * this.gantt.config.step,
470
+ this.gantt.config.unit
471
+ );
472
+
473
+ const bar_width = new_width || this.$bar.getWidth();
474
+ const width_in_units = bar_width / this.gantt.config.column_width;
475
+ const new_end = date_utils.add(
476
+ new_start,
477
+ width_in_units * this.gantt.config.step,
478
+ this.gantt.config.unit
479
+ );
480
+
481
+ // Check constraints for each parent dependency
482
+ for (const dep_id of this.task.dependencies) {
483
+ const parent_bar = this.gantt.get_bar(dep_id);
484
+ if (!parent_bar) continue;
485
+
486
+ const parent_task = parent_bar.task;
487
+
488
+ switch(dependencies_type) {
489
+ case 'finish-to-start':
490
+ // This task cannot start before parent finishes
491
+ if (new_start < parent_task._end) {
492
+ return false;
493
+ }
494
+ break;
495
+
496
+ case 'start-to-start':
497
+ // This task cannot start before parent starts
498
+ if (new_start < parent_task._start) {
499
+ return false;
500
+ }
501
+ break;
502
+
503
+ case 'finish-to-finish':
504
+ // This task cannot finish before parent finishes
505
+ if (new_end < parent_task._end) {
506
+ return false;
507
+ }
508
+ break;
509
+
510
+ case 'start-to-finish':
511
+ // This task cannot finish before parent starts
512
+ if (new_end < parent_task._start) {
513
+ return false;
514
+ }
515
+ break;
516
+ }
517
+ }
518
+
519
+ return true;
520
+ }
521
+
522
+ update_label_position_on_horizontal_scroll({ x, sx }) {
523
+ const container = this.gantt.$container;
524
+ const label = this.group.querySelector('.bar-label');
525
+ const img = this.group.querySelector('.bar-img') || '';
526
+ const img_mask = this.bar_group.querySelector('.img_mask') || '';
527
+
528
+ let barWidthLimit = this.$bar.getX() + this.$bar.getWidth();
529
+ let newLabelX = label.getX() + x;
530
+ let newImgX = (img && img.getX() + x) || 0;
531
+ let imgWidth = (img && img.getBBox().width + 7) || 7;
532
+ let labelEndX = newLabelX + label.getBBox().width + 7;
533
+ let viewportCentral = sx + container.clientWidth / 2;
534
+
535
+ if (label.classList.contains('big')) return;
536
+
537
+ if (labelEndX < barWidthLimit && x > 0 && labelEndX < viewportCentral) {
538
+ label.setAttribute('x', newLabelX);
539
+ if (img) {
540
+ img.setAttribute('x', newImgX);
541
+ img_mask.setAttribute('x', newImgX);
542
+ }
543
+ } else if (
544
+ newLabelX - imgWidth > this.$bar.getX() &&
545
+ x < 0 &&
546
+ labelEndX > viewportCentral
547
+ ) {
548
+ label.setAttribute('x', newLabelX);
549
+ if (img) {
550
+ img.setAttribute('x', newImgX);
551
+ img_mask.setAttribute('x', newImgX);
552
+ }
553
+ }
554
+ }
555
+
556
+ date_changed() {
557
+ let changed = false;
558
+ const { new_start_date, new_end_date } = this.compute_start_end_date();
559
+ if (Number(this.task._start) !== Number(new_start_date)) {
560
+ changed = true;
561
+ this.task._start = new_start_date;
562
+ }
563
+
564
+ if (Number(this.task._end) !== Number(new_end_date)) {
565
+ changed = true;
566
+ this.task._end = new_end_date;
567
+ }
568
+
569
+ if (!changed) return;
570
+
571
+ this.gantt.trigger_event('date_change', [
572
+ this.task,
573
+ new_start_date,
574
+ date_utils.add(new_end_date, -1, 'second'),
575
+ ]);
576
+ }
577
+
578
+ progress_changed() {
579
+ this.task.progress = this.compute_progress();
580
+ this.gantt.trigger_event('progress_change', [
581
+ this.task,
582
+ this.task.progress,
583
+ ]);
584
+ }
585
+
586
+ set_action_completed() {
587
+ this.action_completed = true;
588
+ setTimeout(() => (this.action_completed = false), 1000);
589
+ }
590
+
591
+ compute_start_end_date() {
592
+ const bar = this.$bar;
593
+ const x_in_units = bar.getX() / this.gantt.config.column_width;
594
+ let new_start_date = date_utils.add(
595
+ this.gantt.gantt_start,
596
+ x_in_units * this.gantt.config.step,
597
+ this.gantt.config.unit,
598
+ );
599
+
600
+ const width_in_units = bar.getWidth() / this.gantt.config.column_width;
601
+ const new_end_date = date_utils.add(
602
+ new_start_date,
603
+ width_in_units * this.gantt.config.step,
604
+ this.gantt.config.unit,
605
+ );
606
+
607
+ return { new_start_date, new_end_date };
608
+ }
609
+
610
+ compute_progress() {
611
+ this.progress_width = this.$bar_progress.getWidth();
612
+ this.x = this.$bar_progress.getBBox().x;
613
+ const progress_area = this.x + this.progress_width;
614
+ const progress =
615
+ this.progress_width -
616
+ this.gantt.config.ignored_positions.reduce((acc, val) => {
617
+ return acc + (val >= this.x && val <= progress_area);
618
+ }, 0) *
619
+ this.gantt.config.column_width;
620
+ if (progress < 0) return 0;
621
+ const total =
622
+ this.$bar.getWidth() -
623
+ this.ignored_duration_raw * this.gantt.config.column_width;
624
+ return parseInt((progress / total) * 100, 10);
625
+ }
626
+
627
+ compute_expected_progress() {
628
+ this.expected_progress =
629
+ date_utils.diff(date_utils.today(), this.task._start, 'hour') /
630
+ this.gantt.config.step;
631
+ this.expected_progress =
632
+ ((this.expected_progress < this.duration
633
+ ? this.expected_progress
634
+ : this.duration) *
635
+ 100) /
636
+ this.duration;
637
+ }
638
+
639
+ compute_x() {
640
+ const { column_width } = this.gantt.config;
641
+ const task_start = this.task._start;
642
+ const gantt_start = this.gantt.gantt_start;
643
+
644
+ const diff =
645
+ date_utils.diff(task_start, gantt_start, this.gantt.config.unit) /
646
+ this.gantt.config.step;
647
+
648
+ let x = diff * column_width;
649
+
650
+ /* Since the column width is based on 30,
651
+ we count the month-difference, multiply it by 30 for a "pseudo-month"
652
+ and then add the days in the month, making sure the number does not exceed 29
653
+ so it is within the column */
654
+
655
+ // if (this.gantt.view_is('Month')) {
656
+ // const diffDaysBasedOn30DayMonths =
657
+ // date_utils.diff(task_start, gantt_start, 'month') * 30;
658
+ // const dayInMonth = Math.min(
659
+ // 29,
660
+ // date_utils.format(
661
+ // task_start,
662
+ // 'DD',
663
+ // this.gantt.options.language,
664
+ // ),
665
+ // );
666
+ // const diff = diffDaysBasedOn30DayMonths + dayInMonth;
667
+
668
+ // x = (diff * column_width) / 30;
669
+ // }
670
+
671
+ this.x = x;
672
+ }
673
+
674
+ compute_y() {
675
+ this.y =
676
+ this.gantt.config.header_height +
677
+ this.gantt.options.padding / 2 +
678
+ this.task._index * (this.height + this.gantt.options.padding);
679
+ }
680
+
681
+ compute_duration() {
682
+ let actual_duration_in_days = 0,
683
+ duration_in_days = 0;
684
+ for (
685
+ let d = new Date(this.task._start);
686
+ d < this.task._end;
687
+ d.setDate(d.getDate() + 1)
688
+ ) {
689
+ duration_in_days++;
690
+ if (
691
+ !this.gantt.config.ignored_dates.find(
692
+ (k) => k.getTime() === d.getTime(),
693
+ ) &&
694
+ (!this.gantt.config.ignored_function ||
695
+ !this.gantt.config.ignored_function(d))
696
+ ) {
697
+ actual_duration_in_days++;
698
+ }
699
+ }
700
+ this.task.actual_duration = actual_duration_in_days;
701
+ this.task.ignored_duration = duration_in_days - actual_duration_in_days;
702
+
703
+ this.duration =
704
+ date_utils.convert_scales(
705
+ duration_in_days + 'd',
706
+ this.gantt.config.unit,
707
+ ) / this.gantt.config.step;
708
+
709
+ this.actual_duration_raw =
710
+ date_utils.convert_scales(
711
+ actual_duration_in_days + 'd',
712
+ this.gantt.config.unit,
713
+ ) / this.gantt.config.step;
714
+
715
+ this.ignored_duration_raw = this.duration - this.actual_duration_raw;
716
+ }
717
+
718
+ update_attr(element, attr, value) {
719
+ value = +value;
720
+ if (!isNaN(value)) {
721
+ element.setAttribute(attr, value);
722
+ }
723
+ return element;
724
+ }
725
+
726
+ update_expected_progressbar_position() {
727
+ if (this.invalid) return;
728
+ this.$expected_bar_progress.setAttribute('x', this.$bar.getX());
729
+ this.compute_expected_progress();
730
+ this.$expected_bar_progress.setAttribute(
731
+ 'width',
732
+ this.gantt.config.column_width *
733
+ this.actual_duration_raw *
734
+ (this.expected_progress / 100) || 0,
735
+ );
736
+ }
737
+
738
+ update_progressbar_position() {
739
+ if (this.invalid || this.gantt.options.readonly) return;
740
+ this.$bar_progress.setAttribute('x', this.$bar.getX());
741
+
742
+ this.$bar_progress.setAttribute(
743
+ 'width',
744
+ this.calculate_progress_width(),
745
+ );
746
+ }
747
+
748
+ update_label_position() {
749
+ const img_mask = this.bar_group.querySelector('.img_mask') || '';
750
+ const bar = this.$bar,
751
+ label = this.group.querySelector('.bar-label'),
752
+ img = this.group.querySelector('.bar-img');
753
+
754
+ let padding = 5;
755
+ let x_offset_label_img = this.image_size + 10;
756
+ const labelWidth = label.getBBox().width;
757
+ const barWidth = bar.getWidth();
758
+ if (labelWidth > barWidth) {
759
+ label.classList.add('big');
760
+ if (img) {
761
+ img.setAttribute('x', bar.getEndX() + padding);
762
+ img_mask.setAttribute('x', bar.getEndX() + padding);
763
+ label.setAttribute('x', bar.getEndX() + x_offset_label_img);
764
+ } else {
765
+ label.setAttribute('x', bar.getEndX() + padding);
766
+ }
767
+ } else {
768
+ label.classList.remove('big');
769
+ if (img) {
770
+ img.setAttribute('x', bar.getX() + padding);
771
+ img_mask.setAttribute('x', bar.getX() + padding);
772
+ label.setAttribute(
773
+ 'x',
774
+ bar.getX() + barWidth / 2 + x_offset_label_img,
775
+ );
776
+ } else {
777
+ label.setAttribute(
778
+ 'x',
779
+ bar.getX() + barWidth / 2 - labelWidth / 2,
780
+ );
781
+ }
782
+ }
783
+ }
784
+
785
+ update_handle_position() {
786
+ if (this.invalid || this.gantt.options.readonly) return;
787
+ const bar = this.$bar;
788
+ this.handle_group
789
+ .querySelector('.handle.left')
790
+ .setAttribute('x', bar.getX());
791
+ this.handle_group
792
+ .querySelector('.handle.right')
793
+ .setAttribute('x', bar.getEndX());
794
+ const handle = this.group.querySelector('.handle.progress');
795
+ handle && handle.setAttribute('cx', this.$bar_progress.getEndX());
796
+ }
797
+
798
+ update_arrow_position() {
799
+ this.arrows = this.arrows || [];
800
+ for (let arrow of this.arrows) {
801
+ arrow.update();
802
+ }
803
+ }
804
+ }