gantt-canvas-chart 1.5.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.5.0
2
+ * gantt-canvas-chart v1.5.1
3
3
  * (c) 2025-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -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
  }
@@ -535,21 +535,17 @@ class GanttChart {
535
535
  x_plan_end && (offset_x_plan_end = offset_x_plan_start + x_plan_width * percent_plan);
536
536
  isValidPlanTask = true;
537
537
  }
538
- if (task.actualStart) {
539
- 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;
540
544
  isValidActualTask = true;
541
545
  }
542
- if (task.actualEnd) {
543
- x_actual_end = this.dateToX(DateUtils.addDays(task.actualEnd, 1));
544
- }
545
546
  if (!isValidPlanTask && !isValidActualTask) {
546
547
  return;
547
548
  }
548
- if (x_actual_start) {
549
- x_actual_width = (x_actual_end ? x_actual_end : this.dateToX(this.today)) - x_actual_start;
550
- offset_x_actual_start = Math.round(x_actual_start + x_actual_width * offsetX_actual);
551
- x_actual_end && (offset_x_actual_end = offset_x_actual_start + x_actual_width * percent_actual);
552
- }
553
549
  this.taskPositions.set(task.id, {
554
550
  x_plan_start,
555
551
  x_plan_end,
@@ -861,10 +857,9 @@ class GanttChart {
861
857
  row.tasks.forEach((task) => {
862
858
  const pos = this.taskPositions.get(task.id);
863
859
  if (!pos) return;
864
- if (pos.x_plan_end < this.scrollLeft || pos.x_plan_start > this.scrollLeft + this.viewportWidth) {
865
- if (!pos.x_actual_start || pos.x_actual_end < this.scrollLeft || pos.x_actual_start > this.scrollLeft + this.viewportWidth)
866
- return;
867
- }
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;
868
863
  this.drawTask(ctx, task, y, pos);
869
864
  });
870
865
  }
@@ -950,7 +945,7 @@ class GanttChart {
950
945
  ctx.stroke();
951
946
  }
952
947
  drawToday(ctx) {
953
- const x = this.dateToX(this.today);
948
+ const x = this.dateToX(DateUtils.addDays(this.today, 1));
954
949
  if (x >= this.scrollLeft && x <= this.scrollLeft + this.viewportWidth) {
955
950
  ctx.strokeStyle = this.config.todayColor;
956
951
  ctx.lineWidth = 1;
@@ -981,11 +976,11 @@ class GanttChart {
981
976
  ctx.fillStyle = "#000";
982
977
  if (this.config.showLeftRemark && task.leftRemark) {
983
978
  ctx.textAlign = "right";
984
- 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));
985
980
  }
986
981
  if (this.config.showRightRemark && task.rightRemark) {
987
982
  ctx.textAlign = "left";
988
- 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));
989
984
  }
990
985
  if (this.config.showCenterRemark && task.centerRemark) {
991
986
  const centerX = pos.offset_x_actual_start + (pos.offset_x_actual_end - pos.offset_x_actual_start) / 2;
@@ -1099,7 +1094,7 @@ class GanttChart {
1099
1094
  const startDate = date ? date : this.minDate;
1100
1095
  if (startDate) {
1101
1096
  const xPosition = this.dateToX(startDate);
1102
- this.container.scrollTo({ left: xPosition - 80 });
1097
+ this.container.scrollTo({ left: Math.max(this.config.scrollEdgeThresholds + 2, xPosition - 80) });
1103
1098
  }
1104
1099
  }
1105
1100
  /**
@@ -1111,7 +1106,7 @@ class GanttChart {
1111
1106
  if (params && (params.rowId || params.rowIndex)) {
1112
1107
  const rowIndex = params.rowIndex ? params.rowIndex : this.data.findIndex((row) => row.id === params.rowId);
1113
1108
  const yPosition = this.config.rowHeight * rowIndex;
1114
- this.container.scrollTo({ top: yPosition - 80 });
1109
+ this.container.scrollTo({ top: Math.max(this.config.scrollEdgeThresholds + 2, yPosition - 80) });
1115
1110
  }
1116
1111
  }
1117
1112
  }
package/dist/index.css CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * gantt-canvas-chart v1.5.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,6 +20,24 @@ 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;
@@ -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;
package/dist/index.es.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * gantt-canvas-chart v1.5.0
2
+ * gantt-canvas-chart v1.5.1
3
3
  * (c) 2025-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -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
  }
@@ -533,21 +533,17 @@ class GanttChart {
533
533
  x_plan_end && (offset_x_plan_end = offset_x_plan_start + x_plan_width * percent_plan);
534
534
  isValidPlanTask = true;
535
535
  }
536
- if (task.actualStart) {
537
- 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;
538
542
  isValidActualTask = true;
539
543
  }
540
- if (task.actualEnd) {
541
- x_actual_end = this.dateToX(DateUtils.addDays(task.actualEnd, 1));
542
- }
543
544
  if (!isValidPlanTask && !isValidActualTask) {
544
545
  return;
545
546
  }
546
- if (x_actual_start) {
547
- x_actual_width = (x_actual_end ? x_actual_end : this.dateToX(this.today)) - x_actual_start;
548
- offset_x_actual_start = Math.round(x_actual_start + x_actual_width * offsetX_actual);
549
- x_actual_end && (offset_x_actual_end = offset_x_actual_start + x_actual_width * percent_actual);
550
- }
551
547
  this.taskPositions.set(task.id, {
552
548
  x_plan_start,
553
549
  x_plan_end,
@@ -859,10 +855,9 @@ class GanttChart {
859
855
  row.tasks.forEach((task) => {
860
856
  const pos = this.taskPositions.get(task.id);
861
857
  if (!pos) return;
862
- if (pos.x_plan_end < this.scrollLeft || pos.x_plan_start > this.scrollLeft + this.viewportWidth) {
863
- if (!pos.x_actual_start || pos.x_actual_end < this.scrollLeft || pos.x_actual_start > this.scrollLeft + this.viewportWidth)
864
- return;
865
- }
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;
866
861
  this.drawTask(ctx, task, y, pos);
867
862
  });
868
863
  }
@@ -948,7 +943,7 @@ class GanttChart {
948
943
  ctx.stroke();
949
944
  }
950
945
  drawToday(ctx) {
951
- const x = this.dateToX(this.today);
946
+ const x = this.dateToX(DateUtils.addDays(this.today, 1));
952
947
  if (x >= this.scrollLeft && x <= this.scrollLeft + this.viewportWidth) {
953
948
  ctx.strokeStyle = this.config.todayColor;
954
949
  ctx.lineWidth = 1;
@@ -979,11 +974,11 @@ class GanttChart {
979
974
  ctx.fillStyle = "#000";
980
975
  if (this.config.showLeftRemark && task.leftRemark) {
981
976
  ctx.textAlign = "right";
982
- 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));
983
978
  }
984
979
  if (this.config.showRightRemark && task.rightRemark) {
985
980
  ctx.textAlign = "left";
986
- 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));
987
982
  }
988
983
  if (this.config.showCenterRemark && task.centerRemark) {
989
984
  const centerX = pos.offset_x_actual_start + (pos.offset_x_actual_end - pos.offset_x_actual_start) / 2;
@@ -1097,7 +1092,7 @@ class GanttChart {
1097
1092
  const startDate = date ? date : this.minDate;
1098
1093
  if (startDate) {
1099
1094
  const xPosition = this.dateToX(startDate);
1100
- this.container.scrollTo({ left: xPosition - 80 });
1095
+ this.container.scrollTo({ left: Math.max(this.config.scrollEdgeThresholds + 2, xPosition - 80) });
1101
1096
  }
1102
1097
  }
1103
1098
  /**
@@ -1109,7 +1104,7 @@ class GanttChart {
1109
1104
  if (params && (params.rowId || params.rowIndex)) {
1110
1105
  const rowIndex = params.rowIndex ? params.rowIndex : this.data.findIndex((row) => row.id === params.rowId);
1111
1106
  const yPosition = this.config.rowHeight * rowIndex;
1112
- this.container.scrollTo({ top: yPosition - 80 });
1107
+ this.container.scrollTo({ top: Math.max(this.config.scrollEdgeThresholds + 2, yPosition - 80) });
1113
1108
  }
1114
1109
  }
1115
1110
  }
package/dist/index.umd.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * gantt-canvas-chart v1.5.0
2
+ * gantt-canvas-chart v1.5.1
3
3
  * (c) 2025-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -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
  }
@@ -537,21 +537,17 @@
537
537
  x_plan_end && (offset_x_plan_end = offset_x_plan_start + x_plan_width * percent_plan);
538
538
  isValidPlanTask = true;
539
539
  }
540
- if (task.actualStart) {
541
- 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;
542
546
  isValidActualTask = true;
543
547
  }
544
- if (task.actualEnd) {
545
- x_actual_end = this.dateToX(DateUtils.addDays(task.actualEnd, 1));
546
- }
547
548
  if (!isValidPlanTask && !isValidActualTask) {
548
549
  return;
549
550
  }
550
- if (x_actual_start) {
551
- x_actual_width = (x_actual_end ? x_actual_end : this.dateToX(this.today)) - x_actual_start;
552
- offset_x_actual_start = Math.round(x_actual_start + x_actual_width * offsetX_actual);
553
- x_actual_end && (offset_x_actual_end = offset_x_actual_start + x_actual_width * percent_actual);
554
- }
555
551
  this.taskPositions.set(task.id, {
556
552
  x_plan_start,
557
553
  x_plan_end,
@@ -863,10 +859,9 @@
863
859
  row.tasks.forEach((task) => {
864
860
  const pos = this.taskPositions.get(task.id);
865
861
  if (!pos) return;
866
- if (pos.x_plan_end < this.scrollLeft || pos.x_plan_start > this.scrollLeft + this.viewportWidth) {
867
- if (!pos.x_actual_start || pos.x_actual_end < this.scrollLeft || pos.x_actual_start > this.scrollLeft + this.viewportWidth)
868
- return;
869
- }
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;
870
865
  this.drawTask(ctx, task, y, pos);
871
866
  });
872
867
  }
@@ -952,7 +947,7 @@
952
947
  ctx.stroke();
953
948
  }
954
949
  drawToday(ctx) {
955
- const x = this.dateToX(this.today);
950
+ const x = this.dateToX(DateUtils.addDays(this.today, 1));
956
951
  if (x >= this.scrollLeft && x <= this.scrollLeft + this.viewportWidth) {
957
952
  ctx.strokeStyle = this.config.todayColor;
958
953
  ctx.lineWidth = 1;
@@ -983,11 +978,11 @@
983
978
  ctx.fillStyle = "#000";
984
979
  if (this.config.showLeftRemark && task.leftRemark) {
985
980
  ctx.textAlign = "right";
986
- 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));
987
982
  }
988
983
  if (this.config.showRightRemark && task.rightRemark) {
989
984
  ctx.textAlign = "left";
990
- 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));
991
986
  }
992
987
  if (this.config.showCenterRemark && task.centerRemark) {
993
988
  const centerX = pos.offset_x_actual_start + (pos.offset_x_actual_end - pos.offset_x_actual_start) / 2;
@@ -1101,7 +1096,7 @@
1101
1096
  const startDate = date ? date : this.minDate;
1102
1097
  if (startDate) {
1103
1098
  const xPosition = this.dateToX(startDate);
1104
- this.container.scrollTo({ left: xPosition - 80 });
1099
+ this.container.scrollTo({ left: Math.max(this.config.scrollEdgeThresholds + 2, xPosition - 80) });
1105
1100
  }
1106
1101
  }
1107
1102
  /**
@@ -1113,7 +1108,7 @@
1113
1108
  if (params && (params.rowId || params.rowIndex)) {
1114
1109
  const rowIndex = params.rowIndex ? params.rowIndex : this.data.findIndex((row) => row.id === params.rowId);
1115
1110
  const yPosition = this.config.rowHeight * rowIndex;
1116
- this.container.scrollTo({ top: yPosition - 80 });
1111
+ this.container.scrollTo({ top: Math.max(this.config.scrollEdgeThresholds + 2, yPosition - 80) });
1117
1112
  }
1118
1113
  }
1119
1114
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gantt-canvas-chart",
3
- "version": "1.5.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",