gantt-canvas-chart 1.5.1 → 1.5.2

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.1
2
+ * gantt-canvas-chart v1.5.2
3
3
  * (c) 2025-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -82,6 +82,40 @@ function firstValidValue(...args) {
82
82
  }
83
83
  return null;
84
84
  }
85
+ function getMinMax(values) {
86
+ const validNumbers = values.filter((value) => {
87
+ return typeof value === "number" && !isNaN(value) && value !== Infinity && value !== -Infinity;
88
+ });
89
+ if (validNumbers.length === 0) {
90
+ return null;
91
+ }
92
+ return {
93
+ min: Math.min(...validNumbers),
94
+ max: Math.max(...validNumbers)
95
+ };
96
+ }
97
+ function getMinMaxOptimized(values) {
98
+ let min = Infinity;
99
+ let max = -Infinity;
100
+ let hasValidValue = false;
101
+ for (let i = 0; i < values.length; i++) {
102
+ const value = values[i];
103
+ if (typeof value === "number" && !isNaN(value) && value !== Infinity && value !== -Infinity) {
104
+ hasValidValue = true;
105
+ if (value < min) min = value;
106
+ if (value > max) max = value;
107
+ }
108
+ }
109
+ return hasValidValue ? { min, max } : null;
110
+ }
111
+ function dateToStart(d) {
112
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
113
+ }
114
+ function dateToEnd(value) {
115
+ const d = dateToStart(value);
116
+ d.setDate(d.getDate() + 1);
117
+ return new Date(d.getTime() - 1);
118
+ }
85
119
  class GanttChart {
86
120
  rootContainer;
87
121
  container;
@@ -522,7 +556,7 @@ class GanttChart {
522
556
  const x_plan_start = this.dateToX(new Date(task.planStart));
523
557
  const x_plan_end = this.dateToX(DateUtils.addDays(task.planEnd, 1));
524
558
  let x_actual_start = null, x_actual_end = null;
525
- let offset_x_plan_start = NaN, offset_x_plan_end = NaN;
559
+ let offset_x_plan_start = null, offset_x_plan_end = null;
526
560
  let offset_x_actual_start = null, offset_x_actual_end = null;
527
561
  let x_plan_width = 0;
528
562
  let x_actual_width = 0;
@@ -801,9 +835,9 @@ class GanttChart {
801
835
  if (!fromPos) return;
802
836
  const fromRowIndex = this.taskMap.get(depId).row;
803
837
  const isAdjacent = Math.abs(toRowIndex - fromRowIndex) === 1;
804
- const fromX = Math.max(fromPos.offset_x_plan_end, fromPos.offset_x_actual_end || fromPos.offset_x_plan_end);
838
+ const fromX = getMinMax([fromPos.offset_x_plan_end, fromPos.offset_x_actual_end])?.max;
805
839
  const fromY = fromPos.y;
806
- const toX = Math.min(toPos.offset_x_plan_start, toPos.offset_x_actual_start || toPos.offset_x_plan_start);
840
+ const toX = getMinMax([toPos.offset_x_actual_start, toPos.offset_x_plan_start])?.min;
807
841
  const toY = toPos.y;
808
842
  ctx.beginPath();
809
843
  if (isAdjacent) {
@@ -976,11 +1010,11 @@ class GanttChart {
976
1010
  ctx.fillStyle = "#000";
977
1011
  if (this.config.showLeftRemark && task.leftRemark) {
978
1012
  ctx.textAlign = "right";
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));
1013
+ ctx.fillText(task.leftRemark, Math.round(getMinMax([pos.offset_x_plan_start, pos.offset_x_actual_start])?.min - 8 * 2), Math.round(textY));
980
1014
  }
981
1015
  if (this.config.showRightRemark && task.rightRemark) {
982
1016
  ctx.textAlign = "left";
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));
1017
+ ctx.fillText(task.rightRemark, Math.round(getMinMax([pos.offset_x_plan_end, pos.offset_x_actual_end])?.max + 8 * 2), Math.round(textY));
984
1018
  }
985
1019
  if (this.config.showCenterRemark && task.centerRemark) {
986
1020
  const centerX = pos.offset_x_actual_start + (pos.offset_x_actual_end - pos.offset_x_actual_start) / 2;
@@ -1027,11 +1061,10 @@ class GanttChart {
1027
1061
  this.tooltip.innerHTML = htmlStr;
1028
1062
  } else {
1029
1063
  const overlappingTasks = row.tasks.filter((task) => {
1030
- const pStart = new Date(task.planStart).setHours(0, 0, 0, 0), pEnd = DateUtils.addDays(task.planEnd, 1);
1031
- if (date >= pStart && date < pEnd) return true;
1064
+ if (task.planStart && task.planEnd && date > dateToStart(new Date(task.planStart)) && date <= dateToEnd(new Date(task.planEnd))) return true;
1032
1065
  if (task.actualStart) {
1033
- const aStart = new Date(task.actualStart).setHours(0, 0, 0, 0), aEnd = DateUtils.addDays(task.actualEnd, 1);
1034
- if (date >= aStart && date < aEnd) return true;
1066
+ const aEnd = task.actualEnd ? new Date(task.actualEnd) : this.today;
1067
+ if (date >= dateToStart(new Date(task.actualStart)) && date <= dateToEnd(aEnd)) return true;
1035
1068
  }
1036
1069
  return false;
1037
1070
  });
@@ -1112,4 +1145,8 @@ class GanttChart {
1112
1145
  }
1113
1146
  exports.DateUtils = DateUtils;
1114
1147
  exports.GanttChart = GanttChart;
1148
+ exports.dateToEnd = dateToEnd;
1149
+ exports.dateToStart = dateToStart;
1115
1150
  exports.firstValidValue = firstValidValue;
1151
+ exports.getMinMax = getMinMax;
1152
+ exports.getMinMaxOptimized = getMinMaxOptimized;
package/dist/index.css CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * gantt-canvas-chart v1.5.1
2
+ * gantt-canvas-chart v1.5.2
3
3
  * (c) 2025-present chandq
4
4
  * Released under the MIT License.
5
5
  */
package/dist/index.d.ts CHANGED
@@ -1,3 +1,17 @@
1
+ /**
2
+ * 将日期转换为一天的结束时间,即23点59分59秒999毫秒
3
+ * @param {Date} value
4
+ * @returns {Date}
5
+ */
6
+ export declare function dateToEnd(value: Date): Date;
7
+
8
+ /**
9
+ * 将日期转换为一天的开始时间,即0点0分0秒0毫秒
10
+ * @param {Date} value
11
+ * @returns {Date}
12
+ */
13
+ export declare function dateToStart(d: Date): Date;
14
+
1
15
  export declare class DateUtils {
2
16
  static readonly ONE_DAY_MS: number;
3
17
  static format(date: Date, format?: string): string;
@@ -176,6 +190,30 @@ export declare interface GanttConfig {
176
190
 
177
191
  export declare type GanttData = Row[];
178
192
 
193
+ /**
194
+ * Finds the minimum and maximum values from a series of numbers,
195
+ * automatically filtering out invalid values such as undefined, null, and NaN.
196
+ *
197
+ * @param values - An array of values to process
198
+ * @returns An object containing min and max values, or null if no valid values exist
199
+ */
200
+ export declare function getMinMax(values: any[]): {
201
+ min: number;
202
+ max: number;
203
+ } | null;
204
+
205
+ /**
206
+ * Finds the minimum and maximum values from a series of numbers,for very large datasets using a single-pass algorithm
207
+ * automatically filtering out invalid values such as undefined, null, and NaN.
208
+ *
209
+ * @param values - An array of values to process
210
+ * @returns An object containing min and max values, or null if no valid values exist
211
+ */
212
+ export declare function getMinMaxOptimized(values: any[]): {
213
+ min: number;
214
+ max: number;
215
+ } | null;
216
+
179
217
  export declare type LoadMoreDirection = 'left' | 'right' | 'bottom';
180
218
 
181
219
  export declare interface Row {
@@ -213,8 +251,8 @@ export declare interface TaskPosition {
213
251
  x_actual_end: number | null;
214
252
  x_plan_width: number;
215
253
  x_actual_width: number;
216
- offset_x_plan_start: number;
217
- offset_x_plan_end: number;
254
+ offset_x_plan_start: number | null;
255
+ offset_x_plan_end: number | null;
218
256
  offset_x_actual_start: number | null;
219
257
  offset_x_actual_end: number | null;
220
258
  y: number;
package/dist/index.es.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * gantt-canvas-chart v1.5.1
2
+ * gantt-canvas-chart v1.5.2
3
3
  * (c) 2025-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -80,6 +80,40 @@ function firstValidValue(...args) {
80
80
  }
81
81
  return null;
82
82
  }
83
+ function getMinMax(values) {
84
+ const validNumbers = values.filter((value) => {
85
+ return typeof value === "number" && !isNaN(value) && value !== Infinity && value !== -Infinity;
86
+ });
87
+ if (validNumbers.length === 0) {
88
+ return null;
89
+ }
90
+ return {
91
+ min: Math.min(...validNumbers),
92
+ max: Math.max(...validNumbers)
93
+ };
94
+ }
95
+ function getMinMaxOptimized(values) {
96
+ let min = Infinity;
97
+ let max = -Infinity;
98
+ let hasValidValue = false;
99
+ for (let i = 0; i < values.length; i++) {
100
+ const value = values[i];
101
+ if (typeof value === "number" && !isNaN(value) && value !== Infinity && value !== -Infinity) {
102
+ hasValidValue = true;
103
+ if (value < min) min = value;
104
+ if (value > max) max = value;
105
+ }
106
+ }
107
+ return hasValidValue ? { min, max } : null;
108
+ }
109
+ function dateToStart(d) {
110
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
111
+ }
112
+ function dateToEnd(value) {
113
+ const d = dateToStart(value);
114
+ d.setDate(d.getDate() + 1);
115
+ return new Date(d.getTime() - 1);
116
+ }
83
117
  class GanttChart {
84
118
  rootContainer;
85
119
  container;
@@ -520,7 +554,7 @@ class GanttChart {
520
554
  const x_plan_start = this.dateToX(new Date(task.planStart));
521
555
  const x_plan_end = this.dateToX(DateUtils.addDays(task.planEnd, 1));
522
556
  let x_actual_start = null, x_actual_end = null;
523
- let offset_x_plan_start = NaN, offset_x_plan_end = NaN;
557
+ let offset_x_plan_start = null, offset_x_plan_end = null;
524
558
  let offset_x_actual_start = null, offset_x_actual_end = null;
525
559
  let x_plan_width = 0;
526
560
  let x_actual_width = 0;
@@ -799,9 +833,9 @@ class GanttChart {
799
833
  if (!fromPos) return;
800
834
  const fromRowIndex = this.taskMap.get(depId).row;
801
835
  const isAdjacent = Math.abs(toRowIndex - fromRowIndex) === 1;
802
- const fromX = Math.max(fromPos.offset_x_plan_end, fromPos.offset_x_actual_end || fromPos.offset_x_plan_end);
836
+ const fromX = getMinMax([fromPos.offset_x_plan_end, fromPos.offset_x_actual_end])?.max;
803
837
  const fromY = fromPos.y;
804
- const toX = Math.min(toPos.offset_x_plan_start, toPos.offset_x_actual_start || toPos.offset_x_plan_start);
838
+ const toX = getMinMax([toPos.offset_x_actual_start, toPos.offset_x_plan_start])?.min;
805
839
  const toY = toPos.y;
806
840
  ctx.beginPath();
807
841
  if (isAdjacent) {
@@ -974,11 +1008,11 @@ class GanttChart {
974
1008
  ctx.fillStyle = "#000";
975
1009
  if (this.config.showLeftRemark && task.leftRemark) {
976
1010
  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 * 2), Math.round(textY));
1011
+ ctx.fillText(task.leftRemark, Math.round(getMinMax([pos.offset_x_plan_start, pos.offset_x_actual_start])?.min - 8 * 2), Math.round(textY));
978
1012
  }
979
1013
  if (this.config.showRightRemark && task.rightRemark) {
980
1014
  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 * 2), Math.round(textY));
1015
+ ctx.fillText(task.rightRemark, Math.round(getMinMax([pos.offset_x_plan_end, pos.offset_x_actual_end])?.max + 8 * 2), Math.round(textY));
982
1016
  }
983
1017
  if (this.config.showCenterRemark && task.centerRemark) {
984
1018
  const centerX = pos.offset_x_actual_start + (pos.offset_x_actual_end - pos.offset_x_actual_start) / 2;
@@ -1025,11 +1059,10 @@ class GanttChart {
1025
1059
  this.tooltip.innerHTML = htmlStr;
1026
1060
  } else {
1027
1061
  const overlappingTasks = row.tasks.filter((task) => {
1028
- const pStart = new Date(task.planStart).setHours(0, 0, 0, 0), pEnd = DateUtils.addDays(task.planEnd, 1);
1029
- if (date >= pStart && date < pEnd) return true;
1062
+ if (task.planStart && task.planEnd && date > dateToStart(new Date(task.planStart)) && date <= dateToEnd(new Date(task.planEnd))) return true;
1030
1063
  if (task.actualStart) {
1031
- const aStart = new Date(task.actualStart).setHours(0, 0, 0, 0), aEnd = DateUtils.addDays(task.actualEnd, 1);
1032
- if (date >= aStart && date < aEnd) return true;
1064
+ const aEnd = task.actualEnd ? new Date(task.actualEnd) : this.today;
1065
+ if (date >= dateToStart(new Date(task.actualStart)) && date <= dateToEnd(aEnd)) return true;
1033
1066
  }
1034
1067
  return false;
1035
1068
  });
@@ -1111,5 +1144,9 @@ class GanttChart {
1111
1144
  export {
1112
1145
  DateUtils,
1113
1146
  GanttChart,
1114
- firstValidValue
1147
+ dateToEnd,
1148
+ dateToStart,
1149
+ firstValidValue,
1150
+ getMinMax,
1151
+ getMinMaxOptimized
1115
1152
  };
package/dist/index.umd.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * gantt-canvas-chart v1.5.1
2
+ * gantt-canvas-chart v1.5.2
3
3
  * (c) 2025-present chandq
4
4
  * Released under the MIT License.
5
5
  */
@@ -84,6 +84,40 @@
84
84
  }
85
85
  return null;
86
86
  }
87
+ function getMinMax(values) {
88
+ const validNumbers = values.filter((value) => {
89
+ return typeof value === "number" && !isNaN(value) && value !== Infinity && value !== -Infinity;
90
+ });
91
+ if (validNumbers.length === 0) {
92
+ return null;
93
+ }
94
+ return {
95
+ min: Math.min(...validNumbers),
96
+ max: Math.max(...validNumbers)
97
+ };
98
+ }
99
+ function getMinMaxOptimized(values) {
100
+ let min = Infinity;
101
+ let max = -Infinity;
102
+ let hasValidValue = false;
103
+ for (let i = 0; i < values.length; i++) {
104
+ const value = values[i];
105
+ if (typeof value === "number" && !isNaN(value) && value !== Infinity && value !== -Infinity) {
106
+ hasValidValue = true;
107
+ if (value < min) min = value;
108
+ if (value > max) max = value;
109
+ }
110
+ }
111
+ return hasValidValue ? { min, max } : null;
112
+ }
113
+ function dateToStart(d) {
114
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
115
+ }
116
+ function dateToEnd(value) {
117
+ const d = dateToStart(value);
118
+ d.setDate(d.getDate() + 1);
119
+ return new Date(d.getTime() - 1);
120
+ }
87
121
  class GanttChart {
88
122
  rootContainer;
89
123
  container;
@@ -524,7 +558,7 @@
524
558
  const x_plan_start = this.dateToX(new Date(task.planStart));
525
559
  const x_plan_end = this.dateToX(DateUtils.addDays(task.planEnd, 1));
526
560
  let x_actual_start = null, x_actual_end = null;
527
- let offset_x_plan_start = NaN, offset_x_plan_end = NaN;
561
+ let offset_x_plan_start = null, offset_x_plan_end = null;
528
562
  let offset_x_actual_start = null, offset_x_actual_end = null;
529
563
  let x_plan_width = 0;
530
564
  let x_actual_width = 0;
@@ -803,9 +837,9 @@
803
837
  if (!fromPos) return;
804
838
  const fromRowIndex = this.taskMap.get(depId).row;
805
839
  const isAdjacent = Math.abs(toRowIndex - fromRowIndex) === 1;
806
- const fromX = Math.max(fromPos.offset_x_plan_end, fromPos.offset_x_actual_end || fromPos.offset_x_plan_end);
840
+ const fromX = getMinMax([fromPos.offset_x_plan_end, fromPos.offset_x_actual_end])?.max;
807
841
  const fromY = fromPos.y;
808
- const toX = Math.min(toPos.offset_x_plan_start, toPos.offset_x_actual_start || toPos.offset_x_plan_start);
842
+ const toX = getMinMax([toPos.offset_x_actual_start, toPos.offset_x_plan_start])?.min;
809
843
  const toY = toPos.y;
810
844
  ctx.beginPath();
811
845
  if (isAdjacent) {
@@ -978,11 +1012,11 @@
978
1012
  ctx.fillStyle = "#000";
979
1013
  if (this.config.showLeftRemark && task.leftRemark) {
980
1014
  ctx.textAlign = "right";
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));
1015
+ ctx.fillText(task.leftRemark, Math.round(getMinMax([pos.offset_x_plan_start, pos.offset_x_actual_start])?.min - 8 * 2), Math.round(textY));
982
1016
  }
983
1017
  if (this.config.showRightRemark && task.rightRemark) {
984
1018
  ctx.textAlign = "left";
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));
1019
+ ctx.fillText(task.rightRemark, Math.round(getMinMax([pos.offset_x_plan_end, pos.offset_x_actual_end])?.max + 8 * 2), Math.round(textY));
986
1020
  }
987
1021
  if (this.config.showCenterRemark && task.centerRemark) {
988
1022
  const centerX = pos.offset_x_actual_start + (pos.offset_x_actual_end - pos.offset_x_actual_start) / 2;
@@ -1029,11 +1063,10 @@
1029
1063
  this.tooltip.innerHTML = htmlStr;
1030
1064
  } else {
1031
1065
  const overlappingTasks = row.tasks.filter((task) => {
1032
- const pStart = new Date(task.planStart).setHours(0, 0, 0, 0), pEnd = DateUtils.addDays(task.planEnd, 1);
1033
- if (date >= pStart && date < pEnd) return true;
1066
+ if (task.planStart && task.planEnd && date > dateToStart(new Date(task.planStart)) && date <= dateToEnd(new Date(task.planEnd))) return true;
1034
1067
  if (task.actualStart) {
1035
- const aStart = new Date(task.actualStart).setHours(0, 0, 0, 0), aEnd = DateUtils.addDays(task.actualEnd, 1);
1036
- if (date >= aStart && date < aEnd) return true;
1068
+ const aEnd = task.actualEnd ? new Date(task.actualEnd) : this.today;
1069
+ if (date >= dateToStart(new Date(task.actualStart)) && date <= dateToEnd(aEnd)) return true;
1037
1070
  }
1038
1071
  return false;
1039
1072
  });
@@ -1114,6 +1147,10 @@
1114
1147
  }
1115
1148
  exports2.DateUtils = DateUtils;
1116
1149
  exports2.GanttChart = GanttChart;
1150
+ exports2.dateToEnd = dateToEnd;
1151
+ exports2.dateToStart = dateToStart;
1117
1152
  exports2.firstValidValue = firstValidValue;
1153
+ exports2.getMinMax = getMinMax;
1154
+ exports2.getMinMaxOptimized = getMinMaxOptimized;
1118
1155
  Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
1119
1156
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gantt-canvas-chart",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
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",