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 +47 -10
- package/dist/index.css +1 -1
- package/dist/index.d.ts +40 -2
- package/dist/index.es.js +48 -11
- package/dist/index.umd.js +47 -10
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* gantt-canvas-chart v1.5.
|
|
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 =
|
|
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 =
|
|
838
|
+
const fromX = getMinMax([fromPos.offset_x_plan_end, fromPos.offset_x_actual_end])?.max;
|
|
805
839
|
const fromY = fromPos.y;
|
|
806
|
-
const toX =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
1034
|
-
if (date >=
|
|
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
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.
|
|
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 =
|
|
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 =
|
|
836
|
+
const fromX = getMinMax([fromPos.offset_x_plan_end, fromPos.offset_x_actual_end])?.max;
|
|
803
837
|
const fromY = fromPos.y;
|
|
804
|
-
const toX =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
1032
|
-
if (date >=
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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 =
|
|
840
|
+
const fromX = getMinMax([fromPos.offset_x_plan_end, fromPos.offset_x_actual_end])?.max;
|
|
807
841
|
const fromY = fromPos.y;
|
|
808
|
-
const toX =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
1036
|
-
if (date >=
|
|
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
|
}));
|