tg-ganttchart 0.0.1 → 0.0.3

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.
Files changed (35) hide show
  1. package/README.md +2 -0
  2. package/dist/demo.html +1 -0
  3. package/dist/tgganttchart.common.js +9529 -0
  4. package/dist/tgganttchart.common.js.map +1 -0
  5. package/dist/tgganttchart.css +1 -0
  6. package/dist/tgganttchart.umd.js +9540 -0
  7. package/dist/tgganttchart.umd.js.map +1 -0
  8. package/dist/tgganttchart.umd.min.js +7 -0
  9. package/dist/tgganttchart.umd.min.js.map +1 -0
  10. package/package.json +8 -4
  11. package/babel.config.js +0 -5
  12. package/src/components/GanttElastic.standalone.vue +0 -349
  13. package/src/components/GanttElastic.vue +0 -1643
  14. package/src/components/components/Calendar/Calendar.vue +0 -332
  15. package/src/components/components/Calendar/CalendarRow.vue +0 -85
  16. package/src/components/components/Chart/Chart.vue +0 -110
  17. package/src/components/components/Chart/DaysHighlight.vue +0 -70
  18. package/src/components/components/Chart/DependencyLines.vue +0 -112
  19. package/src/components/components/Chart/Grid.vue +0 -164
  20. package/src/components/components/Chart/ProgressBar.vue +0 -109
  21. package/src/components/components/Chart/Row/Milestone.vue +0 -117
  22. package/src/components/components/Chart/Row/Project.vue +0 -131
  23. package/src/components/components/Chart/Row/Task.mixin.js +0 -47
  24. package/src/components/components/Chart/Row/Task.vue +0 -107
  25. package/src/components/components/Chart/Text.vue +0 -105
  26. package/src/components/components/Expander.vue +0 -126
  27. package/src/components/components/Header/Header.vue +0 -266
  28. package/src/components/components/MainView.vue +0 -282
  29. package/src/components/components/TaskList/ItemColumn.vue +0 -98
  30. package/src/components/components/TaskList/TaskList.vue +0 -42
  31. package/src/components/components/TaskList/TaskListHeader.vue +0 -143
  32. package/src/components/components/TaskList/TaskListItem.vue +0 -39
  33. package/src/components/components/bundle.js +0 -28
  34. package/src/index.js +0 -13
  35. package/vue.config.js +0 -42
@@ -1,1643 +0,0 @@
1
-
2
- <template>
3
- <div class="gantt-elastic" style="width:100%">
4
- <slot name="header"></slot>
5
- <main-view ref="mainView"></main-view>
6
- <slot name="footer"></slot>
7
- </div>
8
- </template>
9
-
10
- <script>
11
- // import VueInstance from 'vue';
12
- import dayjs from 'dayjs';
13
- import MainView from './components/MainView.vue';
14
- import getStyle from './style.js';
15
- import ResizeObserver from 'resize-observer-polyfill';
16
-
17
- const ctx = document.createElement('canvas').getContext('2d');
18
- // let VueInst = VueInstance;
19
- // function initVue() {
20
- // if (typeof Vue !== 'undefined' && typeof VueInst === 'undefined') {
21
- // VueInst = Vue;
22
- // }
23
- // }
24
- // initVue();
25
-
26
- let hourWidthCache = null;
27
-
28
- /**
29
- * Helper function to fill out empty options in user settings
30
- *
31
- * @param {object} userOptions - initial user options that will merge with those below
32
- * @returns {object} merged options
33
- */
34
- function getOptions(userOptions) {
35
- let localeName = 'en';
36
- if (typeof userOptions.locale !== 'undefined' && typeof userOptions.locale.name !== 'undefined') {
37
- localeName = userOptions.locale.name;
38
- }
39
- return {
40
- slots: {
41
- header: {}
42
- },
43
- taskMapping: {
44
- //*
45
- id: 'id',
46
- start: 'start',
47
- label: 'label',
48
- duration: 'duration',
49
- progress: 'progress',
50
- type: 'type',
51
- style: 'style',
52
- collapsed: 'collapsed'
53
- },
54
- width: 0,
55
- height: 0,
56
- clientWidth: 0,
57
- outerHeight: 0,
58
- rowsHeight: 0,
59
- allVisibleTasksHeight: 0,
60
- scroll: {
61
- scrolling: false,
62
- dragXMoveMultiplier: 3, //*
63
- dragYMoveMultiplier: 2, //*
64
- top: 0,
65
- taskList: {
66
- left: 0,
67
- right: 0,
68
- top: 0,
69
- bottom: 0
70
- },
71
- chart: {
72
- left: 0,
73
- right: 0,
74
- percent: 0,
75
- timePercent: 0,
76
- top: 0,
77
- bottom: 0,
78
- time: 0,
79
- timeCenter: 0,
80
- dateTime: {
81
- left: '',
82
- right: ''
83
- }
84
- }
85
- },
86
- scope: {
87
- //*
88
- before: 1,
89
- after: 1
90
- },
91
- times: {
92
- timeScale: 60 * 1000,
93
- timeZoom: 17, //*
94
- timePerPixel: 0,
95
- firstTime: null,
96
- lastTime: null,
97
- firstTaskTime: 0,
98
- lastTaskTime: 0,
99
- totalViewDurationMs: 0,
100
- totalViewDurationPx: 0,
101
- stepDuration: 'day',
102
- steps: []
103
- },
104
- row: {
105
- height: 24 //*
106
- },
107
- maxRows: 20, //*
108
- maxHeight: 0, //*
109
- chart: {
110
- grid: {
111
- horizontal: {
112
- gap: 6 //*
113
- }
114
- },
115
- progress: {
116
- width: 20, //*
117
- height: 6, //*
118
- pattern: true,
119
- bar: false
120
- },
121
- text: {
122
- offset: 4, //*
123
- xPadding: 10, //*
124
- display: true //*
125
- },
126
- expander: {
127
- type: 'chart',
128
- display: false, //*
129
- displayIfTaskListHidden: true, //*
130
- offset: 4, //*
131
- size: 18
132
- }
133
- },
134
- taskList: {
135
- display: true, //*
136
- resizeAfterThreshold: true, //*
137
- widthThreshold: 75, //*
138
- columns: [
139
- //*
140
- {
141
- id: 0,
142
- label: 'ID',
143
- value: 'id',
144
- width: 40
145
- }
146
- ],
147
- percent: 100, //*
148
- width: 0,
149
- finalWidth: 0,
150
- widthFromPercentage: 0,
151
- minWidth: 18,
152
- expander: {
153
- type: 'task-list',
154
- size: 16,
155
- columnWidth: 24,
156
- padding: 16,
157
- margin: 10,
158
- straight: false
159
- }
160
- },
161
- calendar: {
162
- workingDays: [1, 2, 3, 4, 5], //*
163
- gap: 6, //*
164
- height: 0,
165
- strokeWidth: 1,
166
- hour: {
167
- height: 20, //*
168
- display: true, //*
169
- widths: [],
170
- maxWidths: { short: 0, medium: 0, long: 0 },
171
- formatted: {
172
- long: [],
173
- medium: [],
174
- short: []
175
- },
176
- format: {
177
- //*
178
- long(date) {
179
- return date.format('HH:mm');
180
- },
181
- medium(date) {
182
- return date.format('HH:mm');
183
- },
184
- short(date) {
185
- return date.format('HH');
186
- }
187
- }
188
- },
189
- day: {
190
- height: 20, //*
191
- display: true, //*
192
- widths: [],
193
- maxWidths: { short: 0, medium: 0, long: 0 },
194
- format: {
195
- long(date) {
196
- return date.format('DD dddd');
197
- },
198
- medium(date) {
199
- return date.format('DD ddd');
200
- },
201
- short(date) {
202
- return date.format('DD');
203
- }
204
- }
205
- },
206
- month: {
207
- height: 20, //*
208
- display: true, //*
209
- widths: [],
210
- maxWidths: { short: 0, medium: 0, long: 0 },
211
- format: {
212
- //*
213
- short(date) {
214
- return date.format('MM');
215
- },
216
- medium(date) {
217
- return date.format("MMM 'YY");
218
- },
219
- long(date) {
220
- return date.format('MMMM YYYY');
221
- }
222
- }
223
- }
224
- },
225
- locale: {
226
- //*
227
- name: 'en',
228
- weekdays: 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
229
- weekdaysShort: 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
230
- weekdaysMin: 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
231
- months: 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
232
- monthsShort: 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
233
- weekStart: 1,
234
- relativeTime: {
235
- future: 'in %s',
236
- past: '%s ago',
237
- s: 'a few seconds',
238
- m: 'a minute',
239
- mm: '%d minutes',
240
- h: 'an hour',
241
- hh: '%d hours',
242
- d: 'a day',
243
- dd: '%d days',
244
- M: 'a month',
245
- MM: '%d months',
246
- y: 'a year',
247
- yy: '%d years'
248
- },
249
- formats: {
250
- LT: 'HH:mm',
251
- LTS: 'HH:mm:ss',
252
- L: 'DD/MM/YYYY',
253
- LL: 'D MMMM YYYY',
254
- LLL: 'D MMMM YYYY HH:mm',
255
- LLLL: 'dddd, D MMMM YYYY HH:mm'
256
- },
257
- ordinal: n => {
258
- const s = ['th', 'st', 'nd', 'rd'];
259
- const v = n % 100;
260
- return `[${n}${s[(v - 20) % 10] || s[v] || s[0]}]`;
261
- }
262
- }
263
- };
264
- }
265
-
266
- /**
267
- * Prepare style
268
- *
269
- * @returns {object}
270
- */
271
- function prepareStyle(userStyle) {
272
- let fontSize = '12px';
273
- let fontFamily = window
274
- .getComputedStyle(document.body)
275
- .getPropertyValue('font-family')
276
- .toString();
277
- if (typeof userStyle !== 'undefined') {
278
- if (typeof userStyle.fontSize !== 'undefined') {
279
- fontSize = userStyle.fontSize;
280
- }
281
- if (typeof userStyle.fontFamily !== 'undefined') {
282
- fontFamily = userStyle.fontFamily;
283
- }
284
- }
285
- return getStyle(fontSize, fontFamily);
286
- }
287
-
288
- /**
289
- * Helper function to determine if specified variable is an object
290
- *
291
- * @param {any} item
292
- *
293
- * @returns {boolean}
294
- */
295
- function isObject(item) {
296
- return (
297
- item &&
298
- typeof item === 'object' &&
299
- !Array.isArray(item) &&
300
- !(item instanceof HTMLElement) &&
301
- !(item instanceof CanvasRenderingContext2D) &&
302
- typeof item !== 'function'
303
- );
304
- }
305
-
306
- /**
307
- * Helper function which will merge objects recursively - creating brand new one - like clone
308
- *
309
- * @param {object} target
310
- * @params {object} sources
311
- *
312
- * @returns {object}
313
- */
314
- export function mergeDeep(target, ...sources) {
315
- if (!sources.length) {
316
- return target;
317
- }
318
- const source = sources.shift();
319
- if (isObject(target) && isObject(source)) {
320
- for (const key in source) {
321
- if (isObject(source[key])) {
322
- if (typeof target[key] === 'undefined') {
323
- target[key] = {};
324
- }
325
- target[key] = mergeDeep(target[key], source[key]);
326
- } else if (Array.isArray(source[key])) {
327
- target[key] = [];
328
- for (let item of source[key]) {
329
- if (isObject(item)) {
330
- target[key].push(mergeDeep({}, item));
331
- continue;
332
- }
333
- target[key].push(item);
334
- }
335
- } else {
336
- target[key] = source[key];
337
- }
338
- }
339
- }
340
- return mergeDeep(target, ...sources);
341
- }
342
-
343
- /**
344
- * Detect if object or array is observable
345
- *
346
- * @param {object|array} obj
347
- *
348
- * @returns {boolean}
349
- */
350
- function isObservable(obj) {
351
- return typeof obj === 'object' && Object.prototype.hasOwnProperty.call(obj, '__ob__');
352
- }
353
-
354
-
355
- /**
356
- * Same as above but with reactivity in mind
357
- *
358
- * @param {object} target
359
- * @params {object} sources
360
- *
361
- * @returns {object}
362
- */
363
- export function mergeDeepReactive(component, target, ...sources) {
364
- if (!sources.length) {
365
- return target;
366
- }
367
- const source = sources.shift();
368
- if (isObject(target) && isObject(source)) {
369
- for (const key in source) {
370
- if (isObject(source[key])) {
371
- if (typeof target[key] === 'undefined') {
372
- component.$set(target, key, {});
373
- }
374
- mergeDeepReactive(component, target[key], source[key]);
375
- } else if (Array.isArray(source[key])) {
376
- component.$set(target, key, source[key]);
377
- } else if (typeof source[key] === 'function') {
378
- if (source[key].toString().indexOf('[native code]') === -1) {
379
- target[key] = source[key];
380
- }
381
- } else {
382
- component.$set(target, key, source[key]);
383
- }
384
- }
385
- }
386
- return mergeDeepReactive(component, target, ...sources);
387
- }
388
- /**
389
- * Check if objects or arrays are equal by comparing nested values
390
- *
391
- * @param {object|array} left
392
- * @param {object|array} right
393
- *
394
- * @returns {boolean}
395
- */
396
- export function notEqualDeep(left, right, cache = [], path = '') {
397
- if (typeof right !== typeof left) {
398
- return { left, right, what: path + '.typeof' };
399
- } else if (Array.isArray(left) && !Array.isArray(right)) {
400
- return { left, right, what: path + '.isArray' };
401
- } else if (Array.isArray(right) && !Array.isArray(left)) {
402
- return { left, right, what: path + '.isArray' };
403
- } else if (Array.isArray(left) && Array.isArray(right)) {
404
- if (left.length !== right.length) {
405
- return { left, right, what: path + '.length' };
406
- }
407
- let what;
408
- for (let index = 0, len = left.length; index < len; index++) {
409
- if ((what = notEqualDeep(left[index], right[index], cache, path + '.' + index))) {
410
- return what;
411
- }
412
- }
413
- } else if (isObject(left) && !isObject(right)) {
414
- return { left, right, what: path + '.isObject' };
415
- } else if (isObject(right) && !isObject(left)) {
416
- return { left, right, what: path + '.isObject' };
417
- } else if (isObject(left) && isObject(right)) {
418
- for (let key in left) {
419
- if (
420
- !Object.prototype.hasOwnProperty.call(left, key) ||
421
- !Object.prototype.propertyIsEnumerable.call(left, key)
422
- ) {
423
- continue;
424
- }
425
-
426
- if (!Object.prototype.hasOwnProperty.call(right, key)) {
427
- return { left, right, what: path + '.' + key };
428
- }
429
- let what;
430
- if ((what = notEqualDeep(left[key], right[key], cache, path + '.' + key))) {
431
- return what;
432
- }
433
- }
434
- } else if (left !== right) {
435
- return { left, right, what: path + '. !==' };
436
- }
437
- return false;
438
- }
439
-
440
- /**
441
- * GanttElastic
442
- * Main vue component
443
- */
444
- const GanttElastic = {
445
- name: 'GanttElastic',
446
- components: {
447
- MainView
448
- },
449
- props: ['tasks', 'options', 'dynamicStyle'],
450
- provide() {
451
- const provider = {};
452
- const self = this;
453
- Object.defineProperty(provider, 'root', {
454
- enumerable: true,
455
- get: () => self
456
- });
457
- return provider;
458
- },
459
- data() {
460
- return {
461
- state: {
462
- tasks: [],
463
- options: {
464
- scrollBarHeight: 0,
465
- allVisibleTasksHeight: 0,
466
- outerHeight: 0,
467
- scroll: {
468
- left: 0,
469
- top: 0
470
- }
471
- },
472
- dynamicStyle: {},
473
- refs: {},
474
- tasksById: {},
475
- taskTree: {},
476
- ctx,
477
- emitTasksChanges: true, // some operations may pause emitting changes to parent component
478
- emitOptionsChanges: true, // some operations may pause emitting changes to parent component
479
- resizeObserver: null,
480
- unwatchTasks: null,
481
- unwatchOptions: null,
482
- unwatchStyle: null,
483
- unwatchOutputTasks: null,
484
- unwatchOutputOptions: null,
485
- unwatchOutputStyle: null
486
- }
487
- };
488
- },
489
- methods: {
490
- mergeDeep,
491
- mergeDeepReactive,
492
-
493
- /**
494
- * Calculate height of scrollbar in current browser
495
- *
496
- * @returns {number}
497
- */
498
- getScrollBarHeight() {
499
- const outer = document.createElement('div');
500
- outer.style.visibility = 'hidden';
501
- outer.style.height = '100px';
502
- outer.style.msOverflowStyle = 'scrollbar';
503
- document.body.appendChild(outer);
504
- var noScroll = outer.offsetHeight;
505
- outer.style.overflow = 'scroll';
506
- var inner = document.createElement('div');
507
- inner.style.height = '100%';
508
- outer.appendChild(inner);
509
- var withScroll = inner.offsetHeight;
510
- outer.parentNode.removeChild(outer);
511
- const height = noScroll - withScroll;
512
- this.style['chart-scroll-container--vertical']['margin-left'] = `-${height}px`;
513
- return (this.state.options.scrollBarHeight = height);
514
- },
515
-
516
- /**
517
- * Fill out empty task properties and make it reactive
518
- *
519
- * @param {array} tasks
520
- */
521
- fillTasks(tasks) {
522
- for (let task of tasks) {
523
- if (typeof task.x === 'undefined') {
524
- task.x = 0;
525
- }
526
- if (typeof task.y === 'undefined') {
527
- task.y = 0;
528
- }
529
- if (typeof task.width === 'undefined') {
530
- task.width = 0;
531
- }
532
- if (typeof task.height === 'undefined') {
533
- task.height = 0;
534
- }
535
- if (typeof task.mouseOver === 'undefined') {
536
- task.mouseOver = false;
537
- }
538
- if (typeof task.collapsed === 'undefined') {
539
- task.collapsed = false;
540
- }
541
- if (typeof task.dependentOn === 'undefined') {
542
- task.dependentOn = [];
543
- }
544
- if (typeof task.parentId === 'undefined') {
545
- task.parentId = null;
546
- }
547
- if (typeof task.style === 'undefined') {
548
- task.style = {};
549
- }
550
- if (typeof task.children === 'undefined') {
551
- task.children = [];
552
- }
553
- if (typeof task.allChildren === 'undefined') {
554
- task.allChildren = [];
555
- }
556
- if (typeof task.parents === 'undefined') {
557
- task.parents = [];
558
- }
559
- if (typeof task.parent === 'undefined') {
560
- task.parent = null;
561
- }
562
- if (typeof task.startTime === 'undefined') {
563
- task.startTime = dayjs(task.start).valueOf();
564
- }
565
- if (typeof task.endTime === 'undefined' && Object.prototype.hasOwnProperty.call(task, 'end')) {
566
- task.endTime = dayjs(task.end).valueOf();
567
- } else if (typeof task.endTime === 'undefined' && Object.prototype.hasOwnProperty.call(task, 'duration')) {
568
- task.endTime = task.startTime + task.duration;
569
- }
570
- if (typeof task.duration === 'undefined' && Object.prototype.hasOwnProperty.call(task, 'endTime')) {
571
- task.duration = task.endTime - task.startTime;
572
- }
573
- }
574
- return tasks;
575
- },
576
-
577
- /**
578
- * Map tasks
579
- *
580
- * @param {Array} tasks
581
- * @param {Object} options
582
- */
583
- mapTasks(tasks, options) {
584
- for (let [index, task] of tasks.entries()) {
585
- tasks[index] = {
586
- ...task,
587
- id: task[options.taskMapping.id],
588
- start: task[options.taskMapping.start],
589
- label: task[options.taskMapping.label],
590
- duration: task[options.taskMapping.duration],
591
- progress: task[options.taskMapping.progress],
592
- type: task[options.taskMapping.type],
593
- style: task[options.taskMapping.style],
594
- collapsed: task[options.taskMapping.collapsed]
595
- };
596
- }
597
- return tasks;
598
- },
599
-
600
- /**
601
- * Initialize component
602
- */
603
- initialize(itsUpdate = '') {
604
- let options = mergeDeep({}, this.state.options, getOptions(this.options), this.options);
605
- let tasks = this.mapTasks(this.tasks, options);
606
- if (Object.keys(this.state.dynamicStyle).length === 0) {
607
- this.initializeStyle();
608
- }
609
- dayjs.locale(options.locale, null, true);
610
- dayjs.locale(options.locale.name);
611
- if (typeof options.taskList === 'undefined') {
612
- options.taskList = {};
613
- }
614
- options.taskList.columns = options.taskList.columns.map((column, index) => {
615
- column.thresholdPercent = 100;
616
- column.widthFromPercentage = 0;
617
- column.finalWidth = 0;
618
- if (typeof column.height === 'undefined') {
619
- column.height = 0;
620
- }
621
- if (typeof column.style === 'undefined') {
622
- column.style = {};
623
- }
624
- column._id = `${index}-${column.label}`;
625
- return column;
626
- });
627
- this.state.options = options;
628
- tasks = this.fillTasks(tasks);
629
- this.state.tasksById = this.resetTaskTree(tasks);
630
- this.state.taskTree = this.makeTaskTree(this.state.rootTask, tasks);
631
- this.state.tasks = this.state.taskTree.allChildren.map(childId => this.getTask(childId));
632
- this.calculateTaskListColumnsDimensions();
633
- this.state.options.scrollBarHeight = this.getScrollBarHeight();
634
- this.state.options.outerHeight = this.state.options.height + this.state.options.scrollBarHeight;
635
- this.globalOnResize();
636
- },
637
-
638
- /**
639
- * Initialize style
640
- */
641
- initializeStyle() {
642
- this.state.dynamicStyle = mergeDeep({}, prepareStyle(this.dynamicStyle), this.dynamicStyle);
643
- },
644
-
645
- /**
646
- * Get calendar rows outer height
647
- *
648
- * @returns {int}
649
- */
650
- getCalendarHeight() {
651
- return this.state.options.calendar.height + this.state.options.calendar.strokeWidth;
652
- },
653
-
654
- /**
655
- * Get maximal level of nested task children
656
- *
657
- * @returns {int}
658
- */
659
- getMaximalLevel() {
660
- let maximalLevel = 0;
661
- this.state.tasks.forEach(task => {
662
- if (task.parents.length > maximalLevel) {
663
- maximalLevel = task.parents.length;
664
- }
665
- });
666
- return maximalLevel - 1;
667
- },
668
-
669
- /**
670
- * Get maximal expander width - to calculate straight task list text
671
- *
672
- * @returns {int}
673
- */
674
- getMaximalExpanderWidth() {
675
- return (
676
- this.getMaximalLevel() * this.state.options.taskList.expander.padding +
677
- this.state.options.taskList.expander.margin
678
- );
679
- },
680
-
681
- /**
682
- * Synchronize scrollTop property when row height is changed
683
- */
684
- syncScrollTop() {
685
- if (
686
- this.state.refs.taskListItems &&
687
- this.state.refs.chartGraph.scrollTop !== this.state.refs.taskListItems.scrollTop
688
- ) {
689
- this.state.options.scroll.top = this.state.refs.taskListItems.scrollTop = this.state.refs.chartScrollContainerVertical.scrollTop = this.state.refs.chartGraph.scrollTop;
690
- }
691
- },
692
-
693
- /**
694
- * Calculate task list columns dimensions
695
- */
696
- calculateTaskListColumnsDimensions() {
697
- let final = 0;
698
- let percentage = 0;
699
- for (let column of this.state.options.taskList.columns) {
700
- if (column.expander) {
701
- column.widthFromPercentage =
702
- ((this.getMaximalExpanderWidth() + column.width) / 100) * this.state.options.taskList.percent;
703
- } else {
704
- column.widthFromPercentage = (column.width / 100) * this.state.options.taskList.percent;
705
- }
706
- percentage += column.widthFromPercentage;
707
- column.finalWidth = (column.thresholdPercent * column.widthFromPercentage) / 100;
708
- final += column.finalWidth;
709
- column.height = this.getTaskHeight() - this.style['grid-line-horizontal']['stroke-width'];
710
- }
711
- this.state.options.taskList.widthFromPercentage = percentage;
712
- this.state.options.taskList.finalWidth = final;
713
- },
714
-
715
- /**
716
- * Reset task tree - which is used to create tree like structure inside task list
717
- */
718
- resetTaskTree(tasks) {
719
- this.$set(this.state, 'rootTask', {
720
- id: null,
721
- label: 'root',
722
- children: [],
723
- allChildren: [],
724
- parents: [],
725
- parent: null,
726
- __root: true
727
- });
728
- const tasksById = {};
729
- for (let i = 0, len = tasks.length; i < len; i++) {
730
- let current = tasks[i];
731
- current.children = [];
732
- current.allChildren = [];
733
- current.parent = null;
734
- current.parents = [];
735
- tasksById[current.id] = current;
736
- }
737
- return tasksById;
738
- },
739
-
740
- /**
741
- * Make task tree, after reset - look above
742
- *
743
- * @param {object} task
744
- * @returns {object} tasks with children and parents
745
- */
746
- makeTaskTree(task, tasks) {
747
- for (let i = 0, len = tasks.length; i < len; i++) {
748
- let current = tasks[i];
749
- if (current.parentId === task.id) {
750
- if (task.parents.length) {
751
- task.parents.forEach(parent => current.parents.push(parent));
752
- }
753
- if (!Object.prototype.propertyIsEnumerable.call(task, '__root')) {
754
- current.parents.push(task.id);
755
- current.parent = task.id;
756
- } else {
757
- current.parents = [];
758
- current.parent = null;
759
- }
760
- current = this.makeTaskTree(current, tasks);
761
- task.allChildren.push(current.id);
762
- task.children.push(current.id);
763
- current.allChildren.forEach(childId => task.allChildren.push(childId));
764
- }
765
- }
766
- return task;
767
- },
768
-
769
- /**
770
- * Get task by id
771
- *
772
- * @param {any} taskId
773
- * @returns {object|null} task
774
- */
775
- getTask(taskId) {
776
- if (typeof this.state.tasksById[taskId] !== 'undefined') {
777
- return this.state.tasksById[taskId];
778
- }
779
- return null;
780
- },
781
-
782
- /**
783
- * Get children tasks for specified taskId
784
- *
785
- * @param {any} taskId
786
- * @returns {array} children
787
- */
788
- getChildren(taskId) {
789
- return this.state.tasks.filter(task => task.parent === taskId);
790
- },
791
-
792
- /**
793
- * Is task visible
794
- *
795
- * @param {Number|String|Task} task
796
- */
797
- isTaskVisible(task) {
798
- if (typeof task === 'number' || typeof task === 'string') {
799
- task = this.getTask(task);
800
- }
801
- for (let i = 0, len = task.parents.length; i < len; i++) {
802
- if (this.getTask(task.parents[i]).collapsed) {
803
- return false;
804
- }
805
- }
806
- return true;
807
- },
808
-
809
- /**
810
- * Get svg
811
- *
812
- * @returns {string} html svg image of gantt
813
- */
814
- getSVG() {
815
- return this.state.options.mainView.outerHTML;
816
- },
817
-
818
- /**
819
- * Get image
820
- *
821
- * @param {string} type image format
822
- * @returns {Promise} when resolved returns base64 image string of gantt
823
- */
824
- getImage(type = 'image/png') {
825
- return new Promise(resolve => {
826
- const img = new Image();
827
- img.onload = () => {
828
- const canvas = document.createElement('canvas');
829
- canvas.width = this.state.options.mainView.clientWidth;
830
- canvas.height = this.state.options.rowsHeight;
831
- canvas.getContext('2d').drawImage(img, 0, 0);
832
- resolve(canvas.toDataURL(type));
833
- };
834
- img.src = 'data:image/svg+xml,' + encodeURIComponent(this.getSVG());
835
- });
836
- },
837
-
838
- /**
839
- * Get gantt total height
840
- *
841
- * @returns {number}
842
- */
843
- getHeight(visibleTasks, outer = false) {
844
- let height =
845
- visibleTasks.length * (this.state.options.row.height + this.state.options.chart.grid.horizontal.gap * 2) +
846
- this.state.options.calendar.height +
847
- this.state.options.calendar.strokeWidth +
848
- this.state.options.calendar.gap;
849
- if (outer) {
850
- height += this.state.options.scrollBarHeight;
851
- }
852
- return height;
853
- },
854
-
855
- /**
856
- * Get one task height
857
- *
858
- * @returns {number}
859
- */
860
- getTaskHeight(withStroke = false) {
861
- if (withStroke) {
862
- return (
863
- this.state.options.row.height +
864
- this.state.options.chart.grid.horizontal.gap * 2 +
865
- this.style['grid-line-horizontal']['stroke-width']
866
- );
867
- }
868
- return this.state.options.row.height + this.state.options.chart.grid.horizontal.gap * 2;
869
- },
870
-
871
- /**
872
- * Get specified tasks height
873
- *
874
- * @returns {number}
875
- */
876
- getTasksHeight(visibleTasks) {
877
- return visibleTasks.length * this.getTaskHeight();
878
- },
879
-
880
- /**
881
- * Convert time (in milliseconds) to pixel offset inside chart
882
- *
883
- * @param {int} ms
884
- * @returns {number}
885
- */
886
- timeToPixelOffsetX(ms) {
887
- let x = ms - this.state.options.times.firstTime;
888
- if (x) {
889
- x = x / this.state.options.times.timePerPixel;
890
- }
891
- return x;
892
- },
893
-
894
- /**
895
- * Convert pixel offset inside chart to corresponding time offset in milliseconds
896
- *
897
- * @param {number} pixelOffsetX
898
- * @returns {int} milliseconds
899
- */
900
- pixelOffsetXToTime(pixelOffsetX) {
901
- let offset = pixelOffsetX + this.style['grid-line-vertical']['stroke-width'] / 2;
902
- return offset * this.state.options.times.timePerPixel + this.state.options.times.firstTime;
903
- },
904
-
905
- /**
906
- * Determine if element is inside current view port
907
- *
908
- * @param {number} x - element placement
909
- * @param {number} width - element width
910
- * @param {int} buffer - or threshold, if element is outside viewport but offset from view port is below this value return true
911
- * @returns {boolean}
912
- */
913
- isInsideViewPort(x, width, buffer = 5000) {
914
- return (
915
- (x + width + buffer >= this.state.options.scroll.chart.left &&
916
- x - buffer <= this.state.options.scroll.chart.right) ||
917
- (x - buffer <= this.state.options.scroll.chart.left &&
918
- x + width + buffer >= this.state.options.scroll.chart.right)
919
- );
920
- },
921
-
922
- /**
923
- * Chart scroll event handler
924
- *
925
- * @param {event} ev
926
- */
927
- onScrollChart(ev) {
928
- this._onScrollChart(
929
- this.state.refs.chartScrollContainerHorizontal.scrollLeft,
930
- this.state.refs.chartScrollContainerVertical.scrollTop
931
- );
932
- },
933
-
934
- /**
935
- * After same as above but with different arguments - normalized
936
- *
937
- * @param {number} left
938
- * @param {number} top
939
- */
940
- _onScrollChart(left, top) {
941
- if (this.state.options.scroll.chart.left === left && this.state.options.scroll.chart.top === top) {
942
- return;
943
- }
944
- const chartContainerWidth = this.state.refs.chartContainer.clientWidth;
945
- this.state.options.scroll.chart.left = left;
946
- this.state.options.scroll.chart.right = left + chartContainerWidth;
947
- this.state.options.scroll.chart.percent = (left / this.state.options.times.totalViewDurationPx) * 100;
948
- this.state.options.scroll.chart.top = top;
949
- this.state.options.scroll.chart.time = this.pixelOffsetXToTime(left);
950
- this.state.options.scroll.chart.timeCenter = this.pixelOffsetXToTime(left + chartContainerWidth / 2);
951
- this.state.options.scroll.chart.dateTime.left = dayjs(this.state.options.scroll.chart.time).valueOf();
952
- this.state.options.scroll.chart.dateTime.right = dayjs(
953
- this.pixelOffsetXToTime(left + this.state.refs.chart.clientWidth)
954
- ).valueOf();
955
- this.scrollTo(left, top);
956
- },
957
-
958
- /**
959
- * Scroll current chart to specified time (in milliseconds)
960
- *
961
- * @param {int} time
962
- */
963
- scrollToTime(time) {
964
- let pos = this.timeToPixelOffsetX(time);
965
- const chartContainerWidth = this.state.refs.chartContainer.clientWidth;
966
- pos = pos - chartContainerWidth / 2;
967
- if (pos > this.state.options.width) {
968
- pos = this.state.options.width - chartContainerWidth;
969
- }
970
- this.scrollTo(pos);
971
- },
972
-
973
- /**
974
- * Scroll chart or task list to specified pixel values
975
- *
976
- * @param {number|null} left
977
- * @param {number|null} top
978
- */
979
- scrollTo(left = null, top = null) {
980
- if (left !== null) {
981
- this.state.refs.chartCalendarContainer.scrollLeft = left;
982
- this.state.refs.chartGraphContainer.scrollLeft = left;
983
- this.state.refs.chartScrollContainerHorizontal.scrollLeft = left;
984
- this.state.options.scroll.left = left;
985
- }
986
- if (top !== null) {
987
- this.state.refs.chartScrollContainerVertical.scrollTop = top;
988
- this.state.refs.chartGraph.scrollTop = top;
989
- this.state.refs.taskListItems.scrollTop = top;
990
- this.state.options.scroll.top = top;
991
- this.syncScrollTop();
992
- }
993
- },
994
-
995
- /**
996
- * After some actions like time zoom change we need to recompensate scroll position
997
- * so as a result everything will be in same place
998
- */
999
- fixScrollPos() {
1000
- this.scrollToTime(this.state.options.scroll.chart.timeCenter);
1001
- },
1002
-
1003
- /**
1004
- * Mouse wheel event handler
1005
- */
1006
- onWheelChart(ev) {
1007
- if (!ev.shiftKey && ev.deltaX === 0) {
1008
- let top = this.state.options.scroll.top + ev.deltaY;
1009
- const chartClientHeight = this.state.options.rowsHeight;
1010
- const scrollHeight = this.state.refs.chartGraph.scrollHeight - chartClientHeight;
1011
- if (top < 0) {
1012
- top = 0;
1013
- } else if (top > scrollHeight) {
1014
- top = scrollHeight;
1015
- }
1016
- this.scrollTo(null, top);
1017
- } else if (ev.shiftKey && ev.deltaX === 0) {
1018
- let left = this.state.options.scroll.left + ev.deltaY;
1019
- const chartClientWidth = this.state.refs.chartScrollContainerHorizontal.clientWidth;
1020
- const scrollWidth = this.state.refs.chartScrollContainerHorizontal.scrollWidth - chartClientWidth;
1021
- if (left < 0) {
1022
- left = 0;
1023
- } else if (left > scrollWidth) {
1024
- left = scrollWidth;
1025
- }
1026
- this.scrollTo(left);
1027
- } else {
1028
- let left = this.state.options.scroll.left + ev.deltaX;
1029
- const chartClientWidth = this.state.refs.chartScrollContainerHorizontal.clientWidth;
1030
- const scrollWidth = this.state.refs.chartScrollContainerHorizontal.scrollWidth - chartClientWidth;
1031
- if (left < 0) {
1032
- left = 0;
1033
- } else if (left > scrollWidth) {
1034
- left = scrollWidth;
1035
- }
1036
- this.scrollTo(left);
1037
- }
1038
- },
1039
-
1040
- /**
1041
- * Time zoom change event handler
1042
- */
1043
- onTimeZoomChange(timeZoom) {
1044
- this.state.options.times.timeZoom = timeZoom;
1045
- this.recalculateTimes();
1046
- this.calculateSteps();
1047
- this.fixScrollPos();
1048
- },
1049
-
1050
- /**
1051
- * Row height change event handler
1052
- */
1053
- onRowHeightChange(height) {
1054
- this.state.options.row.height = height;
1055
- this.calculateTaskListColumnsDimensions();
1056
- this.syncScrollTop();
1057
- },
1058
-
1059
- /**
1060
- * Scope change event handler
1061
- */
1062
- onScopeChange(value) {
1063
- this.state.options.scope.before = value;
1064
- this.state.options.scope.after = value;
1065
- this.initTimes();
1066
- this.calculateSteps();
1067
- this.computeCalendarWidths();
1068
- this.fixScrollPos();
1069
- },
1070
-
1071
- /**
1072
- * Task list width change event handler
1073
- */
1074
- onTaskListWidthChange(value) {
1075
- this.state.options.taskList.percent = value;
1076
- this.calculateTaskListColumnsDimensions();
1077
- this.fixScrollPos();
1078
- },
1079
-
1080
- /**
1081
- * Task list column width change event handler
1082
- */
1083
- onTaskListColumnWidthChange() {
1084
- this.calculateTaskListColumnsDimensions();
1085
- this.fixScrollPos();
1086
- },
1087
-
1088
- /**
1089
- * Listen to specified event names
1090
- */
1091
- initializeEvents() {
1092
- this.$on('chart-scroll-horizontal', this.onScrollChart);
1093
- this.$on('chart-scroll-vertical', this.onScrollChart);
1094
- this.$on('chart-wheel', this.onWheelChart);
1095
- this.$on('times-timeZoom-change', this.onTimeZoomChange);
1096
- this.$on('row-height-change', this.onRowHeightChange);
1097
- this.$on('scope-change', this.onScopeChange);
1098
- this.$on('taskList-width-change', this.onTaskListWidthChange);
1099
- this.$on('taskList-column-width-change', this.onTaskListColumnWidthChange);
1100
- },
1101
-
1102
- /**
1103
- * When some action was performed (scale change for example) - recalculate time variables
1104
- */
1105
- recalculateTimes() {
1106
- let max = this.state.options.times.timeScale * 60;
1107
- let min = this.state.options.times.timeScale;
1108
- let steps = max / min;
1109
- let percent = this.state.options.times.timeZoom / 100;
1110
- this.state.options.times.timePerPixel =
1111
- this.state.options.times.timeScale * steps * percent + Math.pow(2, this.state.options.times.timeZoom);
1112
- this.state.options.times.totalViewDurationMs = dayjs(this.state.options.times.lastTime).diff(
1113
- this.state.options.times.firstTime,
1114
- 'milliseconds'
1115
- );
1116
- this.state.options.times.totalViewDurationPx =
1117
- this.state.options.times.totalViewDurationMs / this.state.options.times.timePerPixel;
1118
- this.state.options.width =
1119
- this.state.options.times.totalViewDurationPx + this.style['grid-line-vertical']['stroke-width'];
1120
- },
1121
-
1122
- /**
1123
- * Initialize time variables
1124
- */
1125
- initTimes() {
1126
- this.state.options.times.firstTime = dayjs(this.state.options.times.firstTaskTime)
1127
- .locale(this.state.options.locale.name)
1128
- .startOf('day')
1129
- .subtract(this.state.options.scope.before, 'days')
1130
- .startOf('day')
1131
- .valueOf();
1132
- this.state.options.times.lastTime = dayjs(this.state.options.times.lastTaskTime)
1133
- .locale(this.state.options.locale.name)
1134
- .endOf('day')
1135
- .add(this.state.options.scope.after, 'days')
1136
- .endOf('day')
1137
- .valueOf();
1138
- this.recalculateTimes();
1139
- },
1140
-
1141
- /**
1142
- * Calculate steps
1143
- * Steps are days by default
1144
- * Each step contain information about time offset and pixel offset of this time inside gantt chart
1145
- */
1146
- calculateSteps() {
1147
- const steps = [];
1148
- const lastMs = dayjs(this.state.options.times.lastTime).valueOf();
1149
- const currentDate = dayjs(this.state.options.times.firstTime);
1150
- steps.push({
1151
- time: currentDate.valueOf(),
1152
- offset: {
1153
- ms: 0,
1154
- px: 0
1155
- }
1156
- });
1157
- for (
1158
- let currentDate = dayjs(this.state.options.times.firstTime)
1159
- .add(1, this.state.options.times.stepDuration)
1160
- .startOf('day');
1161
- currentDate.valueOf() <= lastMs;
1162
- currentDate = currentDate.add(1, this.state.options.times.stepDuration).startOf('day')
1163
- ) {
1164
- const offsetMs = currentDate.diff(this.state.options.times.firstTime, 'milliseconds');
1165
- const offsetPx = offsetMs / this.state.options.times.timePerPixel;
1166
- const step = {
1167
- time: currentDate.valueOf(),
1168
- offset: {
1169
- ms: offsetMs,
1170
- px: offsetPx
1171
- }
1172
- };
1173
- const previousStep = steps[steps.length - 1];
1174
- previousStep.width = {
1175
- ms: offsetMs - previousStep.offset.ms,
1176
- px: offsetPx - previousStep.offset.px
1177
- };
1178
- steps.push(step);
1179
- }
1180
- const lastStep = steps[steps.length - 1];
1181
- lastStep.width = {
1182
- ms: this.state.options.times.totalViewDurationMs - lastStep.offset.ms,
1183
- px: this.state.options.times.totalViewDurationPx - lastStep.offset.px
1184
- };
1185
- this.state.options.times.steps = steps;
1186
- },
1187
-
1188
- /**
1189
- * Calculate calendar widths - when scale was changed for example
1190
- */
1191
- computeCalendarWidths() {
1192
- this.computeDayWidths();
1193
- this.computeHourWidths();
1194
- this.computeMonthWidths();
1195
- },
1196
-
1197
- /**
1198
- * Compute width of calendar hours column widths basing on text widths
1199
- */
1200
- computeHourWidths() {
1201
- const style = { ...this.style['calendar-row-text'], ...this.style['calendar-row-text--hour'] };
1202
- this.state.ctx.font = style['font-size'] + ' ' + style['font-family'];
1203
- const localeName = this.state.options.locale.name;
1204
- let currentDate = dayjs('2018-01-01T00:00:00').locale(localeName); // any date will be good for hours
1205
- let maxWidths = this.state.options.calendar.hour.maxWidths;
1206
- if (maxWidths.length) {
1207
- return;
1208
- }
1209
- for (let formatName in this.state.options.calendar.hour.format) {
1210
- maxWidths[formatName] = 0;
1211
- }
1212
- for (let hour = 0; hour < 24; hour++) {
1213
- let widths = { hour };
1214
- for (let formatName in this.state.options.calendar.hour.format) {
1215
- const hourFormatted = this.state.options.calendar.hour.format[formatName](currentDate);
1216
- this.state.options.calendar.hour.formatted[formatName].push(hourFormatted);
1217
- widths[formatName] = this.state.ctx.measureText(hourFormatted).width;
1218
- }
1219
- this.state.options.calendar.hour.widths.push(widths);
1220
- for (let formatName in this.state.options.calendar.hour.format) {
1221
- if (widths[formatName] > maxWidths[formatName]) {
1222
- maxWidths[formatName] = widths[formatName];
1223
- }
1224
- }
1225
- currentDate = currentDate.add(1, 'hour');
1226
- }
1227
- },
1228
-
1229
- /**
1230
- * Compute calendar days column widths basing on text widths
1231
- */
1232
- computeDayWidths() {
1233
- const style = { ...this.style['calendar-row-text'], ...this.style['calendar-row-text--day'] };
1234
- this.state.ctx.font = style['font-size'] + ' ' + style['font-family'];
1235
- const localeName = this.state.options.locale.name;
1236
- let currentDate = dayjs(this.state.options.times.steps[0].time).locale(localeName);
1237
- let maxWidths = this.state.options.calendar.day.maxWidths;
1238
- this.state.options.calendar.day.widths = [];
1239
- Object.keys(this.state.options.calendar.day.format).forEach(formatName => {
1240
- maxWidths[formatName] = 0;
1241
- });
1242
- for (let day = 0, daysLen = this.state.options.times.steps.length; day < daysLen; day++) {
1243
- const widths = {
1244
- day
1245
- };
1246
- Object.keys(this.state.options.calendar.day.format).forEach(formatName => {
1247
- widths[formatName] = this.state.ctx.measureText(
1248
- this.state.options.calendar.day.format[formatName](currentDate)
1249
- ).width;
1250
- });
1251
- this.state.options.calendar.day.widths.push(widths);
1252
- Object.keys(this.state.options.calendar.day.format).forEach(formatName => {
1253
- if (widths[formatName] > maxWidths[formatName]) {
1254
- maxWidths[formatName] = widths[formatName];
1255
- }
1256
- });
1257
- currentDate = currentDate.add(1, 'day');
1258
- }
1259
- },
1260
-
1261
- /**
1262
- * Months count
1263
- *
1264
- * @description Returns number of different months in specified time range
1265
- *
1266
- * @param {number} fromTime - date in ms
1267
- * @param {number} toTime - date in ms
1268
- *
1269
- * @returns {number} different months count
1270
- */
1271
- monthsCount(fromTime, toTime) {
1272
- if (fromTime > toTime) {
1273
- return 0;
1274
- }
1275
- let currentMonth = dayjs(fromTime);
1276
- let previousMonth = currentMonth.clone();
1277
- let monthsCount = 1;
1278
- while (currentMonth.valueOf() <= toTime) {
1279
- currentMonth = currentMonth.add(1, 'day');
1280
- if (previousMonth.month() !== currentMonth.month()) {
1281
- monthsCount++;
1282
- }
1283
- previousMonth = currentMonth.clone();
1284
- }
1285
- return monthsCount;
1286
- },
1287
-
1288
- /**
1289
- * Compute month calendar columns widths basing on text widths
1290
- */
1291
- computeMonthWidths() {
1292
- const style = { ...this.style['calendar-row-text'], ...this.style['calendar-row-text--month'] };
1293
- this.state.ctx.font = style['font-size'] + ' ' + style['font-family'];
1294
- let maxWidths = this.state.options.calendar.month.maxWidths;
1295
- this.state.options.calendar.month.widths = [];
1296
- Object.keys(this.state.options.calendar.month.format).forEach(formatName => {
1297
- maxWidths[formatName] = 0;
1298
- });
1299
- const localeName = this.state.options.locale.name;
1300
- let currentDate = dayjs(this.state.options.times.firstTime).locale(localeName);
1301
- const monthsCount = this.monthsCount(this.state.options.times.firstTime, this.state.options.times.lastTime);
1302
- for (let month = 0; month < monthsCount; month++) {
1303
- const widths = {
1304
- month
1305
- };
1306
- Object.keys(this.state.options.calendar.month.format).forEach(formatName => {
1307
- widths[formatName] = this.state.ctx.measureText(
1308
- this.state.options.calendar.month.format[formatName](currentDate)
1309
- ).width;
1310
- });
1311
- this.state.options.calendar.month.widths.push(widths);
1312
- Object.keys(this.state.options.calendar.month.format).forEach(formatName => {
1313
- if (widths[formatName] > maxWidths[formatName]) {
1314
- maxWidths[formatName] = widths[formatName];
1315
- }
1316
- });
1317
- currentDate = currentDate.add(1, 'month');
1318
- }
1319
- },
1320
-
1321
- /**
1322
- * Prepare time and date variables for gantt
1323
- */
1324
- prepareDates() {
1325
- let firstTaskTime = Number.MAX_SAFE_INTEGER;
1326
- let lastTaskTime = 0;
1327
- for (let index = 0, len = this.state.tasks.length; index < len; index++) {
1328
- let task = this.state.tasks[index];
1329
- if (task.startTime < firstTaskTime) {
1330
- firstTaskTime = task.startTime;
1331
- }
1332
- if (task.startTime + task.duration > lastTaskTime) {
1333
- lastTaskTime = task.startTime + task.duration;
1334
- }
1335
- }
1336
- this.state.options.times.firstTaskTime = firstTaskTime;
1337
- this.state.options.times.lastTaskTime = lastTaskTime;
1338
- this.state.options.times.firstTime = dayjs(firstTaskTime)
1339
- .locale(this.state.options.locale.name)
1340
- .startOf('day')
1341
- .subtract(this.state.options.scope.before, 'days')
1342
- .startOf('day')
1343
- .valueOf();
1344
- this.state.options.times.lastTime = dayjs(lastTaskTime)
1345
- .locale(this.state.options.locale.name)
1346
- .endOf('day')
1347
- .add(this.state.options.scope.after, 'days')
1348
- .endOf('day')
1349
- .valueOf();
1350
- },
1351
-
1352
- /**
1353
- * Setup and calculate everything
1354
- */
1355
- setup(itsUpdate = '') {
1356
- this.initialize(itsUpdate);
1357
- this.prepareDates();
1358
- this.initTimes();
1359
- this.calculateSteps();
1360
- this.computeCalendarWidths();
1361
- this.state.options.taskList.width = this.state.options.taskList.columns.reduce(
1362
- (prev, current) => {
1363
- return { width: prev.width + current.width };
1364
- },
1365
- { width: 0 }
1366
- ).width;
1367
- },
1368
-
1369
- /**
1370
- * Global resize event (from window.addEventListener)
1371
- */
1372
- globalOnResize() {
1373
- if (typeof this.$el === 'undefined' || !this.$el) {
1374
- return;
1375
- }
1376
- this.state.options.clientWidth = this.$el.clientWidth;
1377
- if (
1378
- this.state.options.taskList.widthFromPercentage >
1379
- (this.state.options.clientWidth / 100) * this.state.options.taskList.widthThreshold
1380
- ) {
1381
- const diff =
1382
- this.state.options.taskList.widthFromPercentage -
1383
- (this.state.options.clientWidth / 100) * this.state.options.taskList.widthThreshold;
1384
- let diffPercent = 100 - (diff / this.state.options.taskList.widthFromPercentage) * 100;
1385
- if (diffPercent < 0) {
1386
- diffPercent = 0;
1387
- }
1388
- this.state.options.taskList.columns.forEach(column => {
1389
- column.thresholdPercent = diffPercent;
1390
- });
1391
- } else {
1392
- this.state.options.taskList.columns.forEach(column => {
1393
- column.thresholdPercent = 100;
1394
- });
1395
- }
1396
- this.calculateTaskListColumnsDimensions();
1397
- this.$emit('calendar-recalculate');
1398
- this.syncScrollTop();
1399
- }
1400
- },
1401
-
1402
- computed: {
1403
- /**
1404
- * Get visible tasks
1405
- * Very important method which will bring us only those tasks that are visible inside gantt chart
1406
- * For example when task is collapsed - children of this task are not visible - we should not render them
1407
- */
1408
- visibleTasks() {
1409
- const visibleTasks = this.state.tasks.filter(task => this.isTaskVisible(task));
1410
- const maxRows = visibleTasks.slice(0, this.state.options.maxRows);
1411
- this.state.options.rowsHeight = this.getTasksHeight(maxRows);
1412
- let heightCompensation = 0;
1413
- if (this.state.options.maxHeight && this.state.options.rowsHeight > this.state.options.maxHeight) {
1414
- heightCompensation = this.state.options.rowsHeight - this.state.options.maxHeight;
1415
- this.state.options.rowsHeight = this.state.options.maxHeight;
1416
- }
1417
- this.state.options.height = this.getHeight(maxRows) - heightCompensation;
1418
- this.state.options.allVisibleTasksHeight = this.getTasksHeight(visibleTasks);
1419
- this.state.options.outerHeight = this.getHeight(maxRows, true) - heightCompensation;
1420
- let len = visibleTasks.length;
1421
- for (let index = 0; index < len; index++) {
1422
- let task = visibleTasks[index];
1423
- task.width =
1424
- task.duration / this.state.options.times.timePerPixel - this.style['grid-line-vertical']['stroke-width'];
1425
- if (task.width < 0) {
1426
- task.width = 0;
1427
- }
1428
- task.height = this.state.options.row.height;
1429
- task.x = this.timeToPixelOffsetX(task.startTime);
1430
- task.y =
1431
- (this.state.options.row.height + this.state.options.chart.grid.horizontal.gap * 2) * index +
1432
- this.state.options.chart.grid.horizontal.gap;
1433
- }
1434
- return visibleTasks;
1435
- },
1436
-
1437
- /**
1438
- * Style shortcut
1439
- */
1440
- style() {
1441
- return this.state.dynamicStyle;
1442
- },
1443
-
1444
- /**
1445
- * Get columns and compute dimensions on the fly
1446
- */
1447
- getTaskListColumns() {
1448
- this.calculateTaskListColumnsDimensions();
1449
- return this.state.options.taskList.columns;
1450
- },
1451
-
1452
- /**
1453
- * Tasks used for communicate with parent component
1454
- */
1455
- outputTasks() {
1456
- return this.state.tasks;
1457
- },
1458
-
1459
- /**
1460
- * Options used to communicate with parent component
1461
- */
1462
- outputOptions() {
1463
- return this.state.options;
1464
- }
1465
- },
1466
-
1467
- /**
1468
- * Watch tasks after gantt instance is created and react when we have new kids on the block
1469
- */
1470
- created() {
1471
- this.initializeEvents();
1472
- this.setup();
1473
- this.state.unwatchTasks = this.$watch(
1474
- 'tasks',
1475
- tasks => {
1476
- const notEqual = notEqualDeep(tasks, this.outputTasks);
1477
- if (notEqual) {
1478
- this.setup('tasks');
1479
- }
1480
- },
1481
- { deep: true }
1482
- );
1483
- this.state.unwatchOptions = this.$watch(
1484
- 'options',
1485
- opts => {
1486
- const notEqual = notEqualDeep(opts, this.outputOptions);
1487
- if (notEqual) {
1488
- this.setup('options');
1489
- }
1490
- },
1491
- { deep: true }
1492
- );
1493
- this.state.unwatchStyle = this.$watch(
1494
- 'dynamicStyle',
1495
- style => {
1496
- const notEqual = notEqualDeep(style, this.dynamicStyle);
1497
- if (notEqual) {
1498
- this.initializeStyle();
1499
- }
1500
- },
1501
- { deep: true, immediate: true }
1502
- );
1503
-
1504
- this.state.unwatchOutputTasks = this.$watch(
1505
- 'outputTasks',
1506
- tasks => {
1507
- this.$emit('tasks-changed', tasks.map(task => task));
1508
- },
1509
- { deep: true }
1510
- );
1511
- this.state.unwatchOutputOptions = this.$watch(
1512
- 'outputOptions',
1513
- options => {
1514
- this.$emit('options-changed', mergeDeep({}, options));
1515
- },
1516
- { deep: true }
1517
- );
1518
- this.state.unwatchOutputStyle = this.$watch(
1519
- 'style',
1520
- style => {
1521
- this.$emit('dynamic-style-changed', mergeDeep({}, style));
1522
- },
1523
- { deep: true }
1524
- );
1525
-
1526
- this.$root.$emit('gantt-elastic-created', this);
1527
- this.$emit('created', this);
1528
- },
1529
-
1530
- /**
1531
- * Emit before-mount event
1532
- */
1533
- beforeMount() {
1534
- this.$emit('before-mount', this);
1535
- },
1536
-
1537
- /**
1538
- * Emit ready/mounted events and deliver this gantt instance to outside world when needed
1539
- */
1540
- mounted() {
1541
- this.state.options.clientWidth = this.$el.clientWidth;
1542
- this.state.resizeObserver = new ResizeObserver((entries, observer) => {
1543
- this.globalOnResize();
1544
- });
1545
- this.state.resizeObserver.observe(this.$el.parentNode);
1546
- this.globalOnResize();
1547
- this.$emit('ready', this);
1548
- this.$root.$emit('gantt-elastic-mounted', this);
1549
- this.$emit('mounted', this);
1550
- this.$root.$emit('gantt-elastic-ready', this);
1551
- },
1552
-
1553
- /**
1554
- * Emit event when data was changed and before update (you can cleanup dom events here for example)
1555
- */
1556
- beforeUpdate() {
1557
- this.$emit('before-update');
1558
- },
1559
-
1560
- /**
1561
- * Emit event when gantt-elastic view was updated
1562
- */
1563
- updated() {
1564
- this.$nextTick(() => {
1565
- this.$emit('updated');
1566
- });
1567
- },
1568
-
1569
- /**
1570
- * Before destroy event - clean up
1571
- */
1572
- beforeDestroy() {
1573
- this.state.resizeObserver.unobserve(this.$el.parentNode);
1574
- this.state.unwatchTasks();
1575
- this.state.unwatchOptions();
1576
- this.state.unwatchStyle();
1577
- this.state.unwatchOutputTasks();
1578
- this.state.unwatchOutputOptions();
1579
- this.state.unwatchOutputStyle();
1580
- this.$emit('before-destroy');
1581
- },
1582
-
1583
- /**
1584
- * Emit event after gantt-elastic was destroyed
1585
- */
1586
- destroyed() {
1587
- this.$emit('destroyed');
1588
- }
1589
- };
1590
- export default GanttElastic;
1591
- </script>
1592
-
1593
- <style>
1594
- [class^='gantt-elastic'],
1595
- [class*=' gantt-elastic'] {
1596
- box-sizing: border-box;
1597
- }
1598
-
1599
- .gantt-elastic__main-view svg {
1600
- display: block;
1601
- }
1602
-
1603
- .gantt-elastic__grid-horizontal-line,
1604
- .gantt-elastic__grid-vertical-line {
1605
- stroke: #a0a0a0;
1606
- stroke-width: 1;
1607
- }
1608
-
1609
- foreignObject>* {
1610
- margin: 0px;
1611
- }
1612
-
1613
- .gantt-elastic .p-2 {
1614
- padding: 10rem;
1615
- }
1616
-
1617
- .gantt-elastic__main-view-main-container,
1618
- .gantt-elastic__main-view-container {
1619
- overflow: hidden;
1620
- max-width: 100%;
1621
- }
1622
-
1623
- .gantt-elastic__task-list-header-column:last-of-type {
1624
- border-right: 1px solid #00000050;
1625
- }
1626
-
1627
- .gantt-elastic__task-list-item:last-of-type {
1628
- border-bottom: 1px solid #00000050;
1629
- }
1630
-
1631
- .gantt-elastic__task-list-item-value-wrapper:hover {
1632
- overflow: visible !important;
1633
- }
1634
-
1635
- .gantt-elastic__task-list-item-value-wrapper:hover>.gantt-elastic__task-list-item-value-container {
1636
- position: relative;
1637
- overflow: visible !important;
1638
- }
1639
-
1640
- .gantt-elastic__task-list-item-value-wrapper:hover>.gantt-elastic__task-list-item-value {
1641
- position: absolute;
1642
- }
1643
- </style>