gantt-canvas-chart 1.4.0 → 1.5.1

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/dist/index.cjs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * gantt-canvas-chart v1.4.0
2
+ * gantt-canvas-chart v1.5.1
3
3
  * (c) 2025-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -205,8 +205,8 @@ class GanttChart {
205
205
  }
206
206
  init() {
207
207
  this.buildTaskMap();
208
- this.calculateFullTimeline();
209
208
  this.updatePixelsPerDay();
209
+ this.calculateFullTimeline();
210
210
  this.setupEvents();
211
211
  this.handleResize();
212
212
  }
@@ -237,8 +237,8 @@ class GanttChart {
237
237
  updateConfig(newConfig) {
238
238
  Object.assign(this.config, newConfig);
239
239
  if (newConfig.viewMode) {
240
- this.container.scrollLeft = 0;
241
- this.scrollLeft = 0;
240
+ this.container.scrollLeft = this.config.scrollEdgeThresholds + 2;
241
+ this.scrollLeft = this.config.scrollEdgeThresholds + 2;
242
242
  this.updatePixelsPerDay();
243
243
  this.calculateFullTimeline();
244
244
  }
@@ -278,6 +278,9 @@ class GanttChart {
278
278
  maxDate = DateUtils.addDays(/* @__PURE__ */ new Date(), 60);
279
279
  } else {
280
280
  this.taskMap.forEach(({ task }) => {
281
+ if (task.hide) {
282
+ return;
283
+ }
281
284
  const pStart = new Date(task.planStart);
282
285
  const pEnd = new Date(task.planEnd);
283
286
  if (pStart < minDate) minDate = pStart;
@@ -311,8 +314,8 @@ class GanttChart {
311
314
  break;
312
315
  case "Day":
313
316
  default:
314
- this.timelineStart = DateUtils.addDays(minDate, -3);
315
- this.timelineEnd = DateUtils.addDays(maxDate, 3);
317
+ this.timelineStart = DateUtils.addDays(minDate, 3);
318
+ this.timelineEnd = DateUtils.addDays(maxDate, -5);
316
319
  break;
317
320
  }
318
321
  }
@@ -478,7 +481,7 @@ class GanttChart {
478
481
  if (this.scrollTop !== scrollTop) this.container.scrollTop = scrollTop;
479
482
  }
480
483
  updateVirtualRanges() {
481
- const buffer = 200;
484
+ const buffer = 100;
482
485
  this.visibleDateRange = {
483
486
  start: this.xToDate(this.scrollLeft - buffer),
484
487
  end: this.xToDate(this.scrollLeft + this.viewportWidth + buffer)
@@ -508,8 +511,14 @@ class GanttChart {
508
511
  this.taskPositions.clear();
509
512
  for (let i = 0; i < this.data.length; i++) {
510
513
  const row = this.data[i];
514
+ if (row.hide) {
515
+ continue;
516
+ }
511
517
  const y = i * this.config.rowHeight;
512
518
  row.tasks.forEach((task) => {
519
+ if (task.hide) {
520
+ return;
521
+ }
513
522
  const x_plan_start = this.dateToX(new Date(task.planStart));
514
523
  const x_plan_end = this.dateToX(DateUtils.addDays(task.planEnd, 1));
515
524
  let x_actual_start = null, x_actual_end = null;
@@ -526,21 +535,17 @@ class GanttChart {
526
535
  x_plan_end && (offset_x_plan_end = offset_x_plan_start + x_plan_width * percent_plan);
527
536
  isValidPlanTask = true;
528
537
  }
529
- if (task.actualStart) {
530
- x_actual_start = this.dateToX(new Date(task.actualStart));
538
+ x_actual_start = this.dateToX(new Date(task.actualStart));
539
+ x_actual_end = this.dateToX(DateUtils.addDays(task.actualEnd ? task.actualEnd : this.today, 1));
540
+ if (x_actual_start && x_actual_end && x_actual_start < x_actual_end) {
541
+ x_actual_width = x_actual_end - x_actual_start;
542
+ offset_x_actual_start = Math.round(x_actual_start + x_actual_width * offsetX_actual);
543
+ offset_x_actual_end = offset_x_actual_start + x_actual_width * percent_actual;
531
544
  isValidActualTask = true;
532
545
  }
533
546
  if (!isValidPlanTask && !isValidActualTask) {
534
547
  return;
535
548
  }
536
- if (task.actualEnd) {
537
- x_actual_end = this.dateToX(DateUtils.addDays(task.actualEnd, 1));
538
- }
539
- if (x_actual_start) {
540
- x_actual_width = (x_actual_end ? x_actual_end : this.dateToX(this.today)) - x_actual_start;
541
- offset_x_actual_start = Math.round(x_actual_start + x_actual_width * offsetX_actual);
542
- x_actual_end && (offset_x_actual_end = offset_x_actual_start + x_actual_width * percent_actual);
543
- }
544
549
  this.taskPositions.set(task.id, {
545
550
  x_plan_start,
546
551
  x_plan_end,
@@ -852,10 +857,9 @@ class GanttChart {
852
857
  row.tasks.forEach((task) => {
853
858
  const pos = this.taskPositions.get(task.id);
854
859
  if (!pos) return;
855
- if (pos.x_plan_end < this.scrollLeft || pos.x_plan_start > this.scrollLeft + this.viewportWidth) {
856
- if (!pos.x_actual_start || pos.x_actual_end < this.scrollLeft || pos.x_actual_start > this.scrollLeft + this.viewportWidth)
857
- return;
858
- }
860
+ const isPlanVisible = pos.x_plan_end >= this.scrollLeft && pos.x_plan_start <= this.scrollLeft + this.viewportWidth;
861
+ const isActualVisible = pos.x_actual_start && pos.x_actual_end && pos.x_actual_end >= this.scrollLeft && pos.x_actual_start <= this.scrollLeft + this.viewportWidth;
862
+ if (!isPlanVisible && !isActualVisible) return;
859
863
  this.drawTask(ctx, task, y, pos);
860
864
  });
861
865
  }
@@ -941,7 +945,7 @@ class GanttChart {
941
945
  ctx.stroke();
942
946
  }
943
947
  drawToday(ctx) {
944
- const x = this.dateToX(this.today);
948
+ const x = this.dateToX(DateUtils.addDays(this.today, 1));
945
949
  if (x >= this.scrollLeft && x <= this.scrollLeft + this.viewportWidth) {
946
950
  ctx.strokeStyle = this.config.todayColor;
947
951
  ctx.lineWidth = 1;
@@ -972,11 +976,11 @@ class GanttChart {
972
976
  ctx.fillStyle = "#000";
973
977
  if (this.config.showLeftRemark && task.leftRemark) {
974
978
  ctx.textAlign = "right";
975
- ctx.fillText(task.leftRemark, Math.round(Math.min(...[pos.offset_x_plan_start, pos.offset_x_actual_start].filter((val) => val !== null && val !== void 0)) - 8), Math.round(textY));
979
+ ctx.fillText(task.leftRemark, Math.round(Math.min(...[pos.offset_x_plan_start, pos.offset_x_actual_start].filter((val) => val !== null && val !== void 0)) - 8 * 2), Math.round(textY));
976
980
  }
977
981
  if (this.config.showRightRemark && task.rightRemark) {
978
982
  ctx.textAlign = "left";
979
- ctx.fillText(task.rightRemark, Math.round(Math.max(...[pos.offset_x_plan_end, pos.offset_x_actual_end].filter((val) => val !== null && val !== void 0)) + 8), Math.round(textY));
983
+ ctx.fillText(task.rightRemark, Math.round(Math.max(...[pos.offset_x_plan_end, pos.offset_x_actual_end].filter((val) => val !== null && val !== void 0)) + 8 * 2), Math.round(textY));
980
984
  }
981
985
  if (this.config.showCenterRemark && task.centerRemark) {
982
986
  const centerX = pos.offset_x_actual_start + (pos.offset_x_actual_end - pos.offset_x_actual_start) / 2;
@@ -1090,7 +1094,7 @@ class GanttChart {
1090
1094
  const startDate = date ? date : this.minDate;
1091
1095
  if (startDate) {
1092
1096
  const xPosition = this.dateToX(startDate);
1093
- this.container.scrollTo({ left: xPosition - 80 });
1097
+ this.container.scrollTo({ left: Math.max(this.config.scrollEdgeThresholds + 2, xPosition - 80) });
1094
1098
  }
1095
1099
  }
1096
1100
  /**
@@ -1102,7 +1106,7 @@ class GanttChart {
1102
1106
  if (params && (params.rowId || params.rowIndex)) {
1103
1107
  const rowIndex = params.rowIndex ? params.rowIndex : this.data.findIndex((row) => row.id === params.rowId);
1104
1108
  const yPosition = this.config.rowHeight * rowIndex;
1105
- this.container.scrollTo({ top: yPosition - 80 });
1109
+ this.container.scrollTo({ top: Math.max(this.config.scrollEdgeThresholds + 2, yPosition - 80) });
1106
1110
  }
1107
1111
  }
1108
1112
  }
package/dist/index.css CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * gantt-canvas-chart v1.4.0
2
+ * gantt-canvas-chart v1.5.1
3
3
  * (c) 2025-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/dist/index.d.ts CHANGED
@@ -20,10 +20,28 @@ export declare class DateUtils {
20
20
  */
21
21
  export declare function firstValidValue(...args: any[]): any;
22
22
 
23
+ /**
24
+ * GanttChart Class
25
+ *
26
+ * Core Features:
27
+ * 1. Virtual Rendering: Only renders tasks visible in the current viewport for performance optimization
28
+ * 2. Dynamic Scrolling: Supports both horizontal and vertical scrolling with virtualized data loading
29
+ * 3. Dual Timeline Display: Shows both planned and actual timelines for tasks
30
+ * 4. Data Validation Handling:
31
+ * - Automatically handles invalid data where start dates are after end dates by skipping rendering
32
+ * - For tasks with only actual start time (no end time), automatically extends visualization to today
33
+ * - Filters out tasks with completely invalid date ranges
34
+ * 5. Dependency Visualization: Draws arrows between dependent tasks with smart routing
35
+ * 6. Infinite Scroll Loading: Supports dynamic data loading when scrolling to edges
36
+ * 7. Responsive Design: Adapts to container size changes using ResizeObserver
37
+ * 8. Tooltip System: Provides detailed information on hover with customizable formatting
38
+ * 9. Multiple View Modes: Supports Day/Week/Month/Year views with appropriate scaling
39
+ * 10. Custom Styling: Allows per-task styling through configuration options
40
+ */
23
41
  export declare class GanttChart {
24
42
  private rootContainer;
25
43
  container: HTMLElement;
26
- private data;
44
+ data: GanttData;
27
45
  private config;
28
46
  private headerCanvas;
29
47
  private mainCanvas;
@@ -49,7 +67,10 @@ export declare class GanttChart {
49
67
  private totalHeight;
50
68
  private resizeObserver;
51
69
  private taskPositions;
52
- private taskMap;
70
+ taskMap: Map<string, {
71
+ row: number;
72
+ task: Task;
73
+ }>;
53
74
  private isLoadingData;
54
75
  private hasMoreDataLeft;
55
76
  private hasMoreDataRight;
@@ -160,13 +181,14 @@ export declare type LoadMoreDirection = 'left' | 'right' | 'bottom';
160
181
  export declare interface Row {
161
182
  id: string;
162
183
  name: string;
184
+ hide?: boolean;
163
185
  tasks: Task[];
164
186
  }
165
187
 
166
188
  export declare interface Task {
167
189
  id: string;
168
190
  name: string;
169
- type?: 'task' | 'leave';
191
+ type?: 'task' | 'leave' | 'overtime' | string;
170
192
  planStart?: string;
171
193
  planEnd?: string;
172
194
  actualStart?: string;
@@ -178,6 +200,7 @@ export declare interface Task {
178
200
  styleClass?: string;
179
201
  planBorderColor?: string;
180
202
  actualBgColor?: string;
203
+ hide?: boolean;
181
204
  _data?: any;
182
205
  planOffsetPercent?: [number, number];
183
206
  actualOffsetPercent?: [number, number];
package/dist/index.es.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * gantt-canvas-chart v1.4.0
2
+ * gantt-canvas-chart v1.5.1
3
3
  * (c) 2025-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -203,8 +203,8 @@ class GanttChart {
203
203
  }
204
204
  init() {
205
205
  this.buildTaskMap();
206
- this.calculateFullTimeline();
207
206
  this.updatePixelsPerDay();
207
+ this.calculateFullTimeline();
208
208
  this.setupEvents();
209
209
  this.handleResize();
210
210
  }
@@ -235,8 +235,8 @@ class GanttChart {
235
235
  updateConfig(newConfig) {
236
236
  Object.assign(this.config, newConfig);
237
237
  if (newConfig.viewMode) {
238
- this.container.scrollLeft = 0;
239
- this.scrollLeft = 0;
238
+ this.container.scrollLeft = this.config.scrollEdgeThresholds + 2;
239
+ this.scrollLeft = this.config.scrollEdgeThresholds + 2;
240
240
  this.updatePixelsPerDay();
241
241
  this.calculateFullTimeline();
242
242
  }
@@ -276,6 +276,9 @@ class GanttChart {
276
276
  maxDate = DateUtils.addDays(/* @__PURE__ */ new Date(), 60);
277
277
  } else {
278
278
  this.taskMap.forEach(({ task }) => {
279
+ if (task.hide) {
280
+ return;
281
+ }
279
282
  const pStart = new Date(task.planStart);
280
283
  const pEnd = new Date(task.planEnd);
281
284
  if (pStart < minDate) minDate = pStart;
@@ -309,8 +312,8 @@ class GanttChart {
309
312
  break;
310
313
  case "Day":
311
314
  default:
312
- this.timelineStart = DateUtils.addDays(minDate, -3);
313
- this.timelineEnd = DateUtils.addDays(maxDate, 3);
315
+ this.timelineStart = DateUtils.addDays(minDate, 3);
316
+ this.timelineEnd = DateUtils.addDays(maxDate, -5);
314
317
  break;
315
318
  }
316
319
  }
@@ -476,7 +479,7 @@ class GanttChart {
476
479
  if (this.scrollTop !== scrollTop) this.container.scrollTop = scrollTop;
477
480
  }
478
481
  updateVirtualRanges() {
479
- const buffer = 200;
482
+ const buffer = 100;
480
483
  this.visibleDateRange = {
481
484
  start: this.xToDate(this.scrollLeft - buffer),
482
485
  end: this.xToDate(this.scrollLeft + this.viewportWidth + buffer)
@@ -506,8 +509,14 @@ class GanttChart {
506
509
  this.taskPositions.clear();
507
510
  for (let i = 0; i < this.data.length; i++) {
508
511
  const row = this.data[i];
512
+ if (row.hide) {
513
+ continue;
514
+ }
509
515
  const y = i * this.config.rowHeight;
510
516
  row.tasks.forEach((task) => {
517
+ if (task.hide) {
518
+ return;
519
+ }
511
520
  const x_plan_start = this.dateToX(new Date(task.planStart));
512
521
  const x_plan_end = this.dateToX(DateUtils.addDays(task.planEnd, 1));
513
522
  let x_actual_start = null, x_actual_end = null;
@@ -524,21 +533,17 @@ class GanttChart {
524
533
  x_plan_end && (offset_x_plan_end = offset_x_plan_start + x_plan_width * percent_plan);
525
534
  isValidPlanTask = true;
526
535
  }
527
- if (task.actualStart) {
528
- x_actual_start = this.dateToX(new Date(task.actualStart));
536
+ x_actual_start = this.dateToX(new Date(task.actualStart));
537
+ x_actual_end = this.dateToX(DateUtils.addDays(task.actualEnd ? task.actualEnd : this.today, 1));
538
+ if (x_actual_start && x_actual_end && x_actual_start < x_actual_end) {
539
+ x_actual_width = x_actual_end - x_actual_start;
540
+ offset_x_actual_start = Math.round(x_actual_start + x_actual_width * offsetX_actual);
541
+ offset_x_actual_end = offset_x_actual_start + x_actual_width * percent_actual;
529
542
  isValidActualTask = true;
530
543
  }
531
544
  if (!isValidPlanTask && !isValidActualTask) {
532
545
  return;
533
546
  }
534
- if (task.actualEnd) {
535
- x_actual_end = this.dateToX(DateUtils.addDays(task.actualEnd, 1));
536
- }
537
- if (x_actual_start) {
538
- x_actual_width = (x_actual_end ? x_actual_end : this.dateToX(this.today)) - x_actual_start;
539
- offset_x_actual_start = Math.round(x_actual_start + x_actual_width * offsetX_actual);
540
- x_actual_end && (offset_x_actual_end = offset_x_actual_start + x_actual_width * percent_actual);
541
- }
542
547
  this.taskPositions.set(task.id, {
543
548
  x_plan_start,
544
549
  x_plan_end,
@@ -850,10 +855,9 @@ class GanttChart {
850
855
  row.tasks.forEach((task) => {
851
856
  const pos = this.taskPositions.get(task.id);
852
857
  if (!pos) return;
853
- if (pos.x_plan_end < this.scrollLeft || pos.x_plan_start > this.scrollLeft + this.viewportWidth) {
854
- if (!pos.x_actual_start || pos.x_actual_end < this.scrollLeft || pos.x_actual_start > this.scrollLeft + this.viewportWidth)
855
- return;
856
- }
858
+ const isPlanVisible = pos.x_plan_end >= this.scrollLeft && pos.x_plan_start <= this.scrollLeft + this.viewportWidth;
859
+ const isActualVisible = pos.x_actual_start && pos.x_actual_end && pos.x_actual_end >= this.scrollLeft && pos.x_actual_start <= this.scrollLeft + this.viewportWidth;
860
+ if (!isPlanVisible && !isActualVisible) return;
857
861
  this.drawTask(ctx, task, y, pos);
858
862
  });
859
863
  }
@@ -939,7 +943,7 @@ class GanttChart {
939
943
  ctx.stroke();
940
944
  }
941
945
  drawToday(ctx) {
942
- const x = this.dateToX(this.today);
946
+ const x = this.dateToX(DateUtils.addDays(this.today, 1));
943
947
  if (x >= this.scrollLeft && x <= this.scrollLeft + this.viewportWidth) {
944
948
  ctx.strokeStyle = this.config.todayColor;
945
949
  ctx.lineWidth = 1;
@@ -970,11 +974,11 @@ class GanttChart {
970
974
  ctx.fillStyle = "#000";
971
975
  if (this.config.showLeftRemark && task.leftRemark) {
972
976
  ctx.textAlign = "right";
973
- ctx.fillText(task.leftRemark, Math.round(Math.min(...[pos.offset_x_plan_start, pos.offset_x_actual_start].filter((val) => val !== null && val !== void 0)) - 8), Math.round(textY));
977
+ ctx.fillText(task.leftRemark, Math.round(Math.min(...[pos.offset_x_plan_start, pos.offset_x_actual_start].filter((val) => val !== null && val !== void 0)) - 8 * 2), Math.round(textY));
974
978
  }
975
979
  if (this.config.showRightRemark && task.rightRemark) {
976
980
  ctx.textAlign = "left";
977
- ctx.fillText(task.rightRemark, Math.round(Math.max(...[pos.offset_x_plan_end, pos.offset_x_actual_end].filter((val) => val !== null && val !== void 0)) + 8), Math.round(textY));
981
+ ctx.fillText(task.rightRemark, Math.round(Math.max(...[pos.offset_x_plan_end, pos.offset_x_actual_end].filter((val) => val !== null && val !== void 0)) + 8 * 2), Math.round(textY));
978
982
  }
979
983
  if (this.config.showCenterRemark && task.centerRemark) {
980
984
  const centerX = pos.offset_x_actual_start + (pos.offset_x_actual_end - pos.offset_x_actual_start) / 2;
@@ -1088,7 +1092,7 @@ class GanttChart {
1088
1092
  const startDate = date ? date : this.minDate;
1089
1093
  if (startDate) {
1090
1094
  const xPosition = this.dateToX(startDate);
1091
- this.container.scrollTo({ left: xPosition - 80 });
1095
+ this.container.scrollTo({ left: Math.max(this.config.scrollEdgeThresholds + 2, xPosition - 80) });
1092
1096
  }
1093
1097
  }
1094
1098
  /**
@@ -1100,7 +1104,7 @@ class GanttChart {
1100
1104
  if (params && (params.rowId || params.rowIndex)) {
1101
1105
  const rowIndex = params.rowIndex ? params.rowIndex : this.data.findIndex((row) => row.id === params.rowId);
1102
1106
  const yPosition = this.config.rowHeight * rowIndex;
1103
- this.container.scrollTo({ top: yPosition - 80 });
1107
+ this.container.scrollTo({ top: Math.max(this.config.scrollEdgeThresholds + 2, yPosition - 80) });
1104
1108
  }
1105
1109
  }
1106
1110
  }
package/dist/index.umd.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * gantt-canvas-chart v1.4.0
2
+ * gantt-canvas-chart v1.5.1
3
3
  * (c) 2025-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -207,8 +207,8 @@
207
207
  }
208
208
  init() {
209
209
  this.buildTaskMap();
210
- this.calculateFullTimeline();
211
210
  this.updatePixelsPerDay();
211
+ this.calculateFullTimeline();
212
212
  this.setupEvents();
213
213
  this.handleResize();
214
214
  }
@@ -239,8 +239,8 @@
239
239
  updateConfig(newConfig) {
240
240
  Object.assign(this.config, newConfig);
241
241
  if (newConfig.viewMode) {
242
- this.container.scrollLeft = 0;
243
- this.scrollLeft = 0;
242
+ this.container.scrollLeft = this.config.scrollEdgeThresholds + 2;
243
+ this.scrollLeft = this.config.scrollEdgeThresholds + 2;
244
244
  this.updatePixelsPerDay();
245
245
  this.calculateFullTimeline();
246
246
  }
@@ -280,6 +280,9 @@
280
280
  maxDate = DateUtils.addDays(/* @__PURE__ */ new Date(), 60);
281
281
  } else {
282
282
  this.taskMap.forEach(({ task }) => {
283
+ if (task.hide) {
284
+ return;
285
+ }
283
286
  const pStart = new Date(task.planStart);
284
287
  const pEnd = new Date(task.planEnd);
285
288
  if (pStart < minDate) minDate = pStart;
@@ -313,8 +316,8 @@
313
316
  break;
314
317
  case "Day":
315
318
  default:
316
- this.timelineStart = DateUtils.addDays(minDate, -3);
317
- this.timelineEnd = DateUtils.addDays(maxDate, 3);
319
+ this.timelineStart = DateUtils.addDays(minDate, 3);
320
+ this.timelineEnd = DateUtils.addDays(maxDate, -5);
318
321
  break;
319
322
  }
320
323
  }
@@ -480,7 +483,7 @@
480
483
  if (this.scrollTop !== scrollTop) this.container.scrollTop = scrollTop;
481
484
  }
482
485
  updateVirtualRanges() {
483
- const buffer = 200;
486
+ const buffer = 100;
484
487
  this.visibleDateRange = {
485
488
  start: this.xToDate(this.scrollLeft - buffer),
486
489
  end: this.xToDate(this.scrollLeft + this.viewportWidth + buffer)
@@ -510,8 +513,14 @@
510
513
  this.taskPositions.clear();
511
514
  for (let i = 0; i < this.data.length; i++) {
512
515
  const row = this.data[i];
516
+ if (row.hide) {
517
+ continue;
518
+ }
513
519
  const y = i * this.config.rowHeight;
514
520
  row.tasks.forEach((task) => {
521
+ if (task.hide) {
522
+ return;
523
+ }
515
524
  const x_plan_start = this.dateToX(new Date(task.planStart));
516
525
  const x_plan_end = this.dateToX(DateUtils.addDays(task.planEnd, 1));
517
526
  let x_actual_start = null, x_actual_end = null;
@@ -528,21 +537,17 @@
528
537
  x_plan_end && (offset_x_plan_end = offset_x_plan_start + x_plan_width * percent_plan);
529
538
  isValidPlanTask = true;
530
539
  }
531
- if (task.actualStart) {
532
- x_actual_start = this.dateToX(new Date(task.actualStart));
540
+ x_actual_start = this.dateToX(new Date(task.actualStart));
541
+ x_actual_end = this.dateToX(DateUtils.addDays(task.actualEnd ? task.actualEnd : this.today, 1));
542
+ if (x_actual_start && x_actual_end && x_actual_start < x_actual_end) {
543
+ x_actual_width = x_actual_end - x_actual_start;
544
+ offset_x_actual_start = Math.round(x_actual_start + x_actual_width * offsetX_actual);
545
+ offset_x_actual_end = offset_x_actual_start + x_actual_width * percent_actual;
533
546
  isValidActualTask = true;
534
547
  }
535
548
  if (!isValidPlanTask && !isValidActualTask) {
536
549
  return;
537
550
  }
538
- if (task.actualEnd) {
539
- x_actual_end = this.dateToX(DateUtils.addDays(task.actualEnd, 1));
540
- }
541
- if (x_actual_start) {
542
- x_actual_width = (x_actual_end ? x_actual_end : this.dateToX(this.today)) - x_actual_start;
543
- offset_x_actual_start = Math.round(x_actual_start + x_actual_width * offsetX_actual);
544
- x_actual_end && (offset_x_actual_end = offset_x_actual_start + x_actual_width * percent_actual);
545
- }
546
551
  this.taskPositions.set(task.id, {
547
552
  x_plan_start,
548
553
  x_plan_end,
@@ -854,10 +859,9 @@
854
859
  row.tasks.forEach((task) => {
855
860
  const pos = this.taskPositions.get(task.id);
856
861
  if (!pos) return;
857
- if (pos.x_plan_end < this.scrollLeft || pos.x_plan_start > this.scrollLeft + this.viewportWidth) {
858
- if (!pos.x_actual_start || pos.x_actual_end < this.scrollLeft || pos.x_actual_start > this.scrollLeft + this.viewportWidth)
859
- return;
860
- }
862
+ const isPlanVisible = pos.x_plan_end >= this.scrollLeft && pos.x_plan_start <= this.scrollLeft + this.viewportWidth;
863
+ const isActualVisible = pos.x_actual_start && pos.x_actual_end && pos.x_actual_end >= this.scrollLeft && pos.x_actual_start <= this.scrollLeft + this.viewportWidth;
864
+ if (!isPlanVisible && !isActualVisible) return;
861
865
  this.drawTask(ctx, task, y, pos);
862
866
  });
863
867
  }
@@ -943,7 +947,7 @@
943
947
  ctx.stroke();
944
948
  }
945
949
  drawToday(ctx) {
946
- const x = this.dateToX(this.today);
950
+ const x = this.dateToX(DateUtils.addDays(this.today, 1));
947
951
  if (x >= this.scrollLeft && x <= this.scrollLeft + this.viewportWidth) {
948
952
  ctx.strokeStyle = this.config.todayColor;
949
953
  ctx.lineWidth = 1;
@@ -974,11 +978,11 @@
974
978
  ctx.fillStyle = "#000";
975
979
  if (this.config.showLeftRemark && task.leftRemark) {
976
980
  ctx.textAlign = "right";
977
- ctx.fillText(task.leftRemark, Math.round(Math.min(...[pos.offset_x_plan_start, pos.offset_x_actual_start].filter((val) => val !== null && val !== void 0)) - 8), Math.round(textY));
981
+ ctx.fillText(task.leftRemark, Math.round(Math.min(...[pos.offset_x_plan_start, pos.offset_x_actual_start].filter((val) => val !== null && val !== void 0)) - 8 * 2), Math.round(textY));
978
982
  }
979
983
  if (this.config.showRightRemark && task.rightRemark) {
980
984
  ctx.textAlign = "left";
981
- ctx.fillText(task.rightRemark, Math.round(Math.max(...[pos.offset_x_plan_end, pos.offset_x_actual_end].filter((val) => val !== null && val !== void 0)) + 8), Math.round(textY));
985
+ ctx.fillText(task.rightRemark, Math.round(Math.max(...[pos.offset_x_plan_end, pos.offset_x_actual_end].filter((val) => val !== null && val !== void 0)) + 8 * 2), Math.round(textY));
982
986
  }
983
987
  if (this.config.showCenterRemark && task.centerRemark) {
984
988
  const centerX = pos.offset_x_actual_start + (pos.offset_x_actual_end - pos.offset_x_actual_start) / 2;
@@ -1092,7 +1096,7 @@
1092
1096
  const startDate = date ? date : this.minDate;
1093
1097
  if (startDate) {
1094
1098
  const xPosition = this.dateToX(startDate);
1095
- this.container.scrollTo({ left: xPosition - 80 });
1099
+ this.container.scrollTo({ left: Math.max(this.config.scrollEdgeThresholds + 2, xPosition - 80) });
1096
1100
  }
1097
1101
  }
1098
1102
  /**
@@ -1104,7 +1108,7 @@
1104
1108
  if (params && (params.rowId || params.rowIndex)) {
1105
1109
  const rowIndex = params.rowIndex ? params.rowIndex : this.data.findIndex((row) => row.id === params.rowId);
1106
1110
  const yPosition = this.config.rowHeight * rowIndex;
1107
- this.container.scrollTo({ top: yPosition - 80 });
1111
+ this.container.scrollTo({ top: Math.max(this.config.scrollEdgeThresholds + 2, yPosition - 80) });
1108
1112
  }
1109
1113
  }
1110
1114
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gantt-canvas-chart",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "High performance Gantt chart component based on Canvas, can be applied to any framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.es.js",