gantt-canvas-chart 1.0.1 → 1.1.0
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 +46 -20
- package/dist/index.css +24 -1
- package/dist/{main.d.ts → index.d.ts} +8 -1
- package/dist/index.es.js +46 -20
- package/dist/index.umd.js +46 -20
- package/package.json +11 -4
package/dist/index.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* gantt-canvas-chart v1.0
|
|
2
|
+
* gantt-canvas-chart v1.1.0
|
|
3
3
|
* (c) 2025-present chandq
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
@@ -95,6 +95,8 @@ class GanttChart {
|
|
|
95
95
|
mainCtx;
|
|
96
96
|
timelineStart;
|
|
97
97
|
timelineEnd;
|
|
98
|
+
minDate;
|
|
99
|
+
// private maxDate: Date | null;
|
|
98
100
|
pixelsPerDay;
|
|
99
101
|
scrollLeft;
|
|
100
102
|
scrollTop;
|
|
@@ -147,8 +149,8 @@ class GanttChart {
|
|
|
147
149
|
offsetTop: 0,
|
|
148
150
|
offsetLeft: 0,
|
|
149
151
|
viewFactors: { Day: 80, Week: 20, Month: 15, Year: 6 },
|
|
150
|
-
planBorderColor: "#
|
|
151
|
-
actualBgColor: "#
|
|
152
|
+
planBorderColor: "#C1EFCF",
|
|
153
|
+
actualBgColor: "#5AC989",
|
|
152
154
|
...config
|
|
153
155
|
};
|
|
154
156
|
this.headerCanvas = headerCanvas;
|
|
@@ -163,6 +165,7 @@ class GanttChart {
|
|
|
163
165
|
this.mainCtx = this.mainCanvas.getContext("2d");
|
|
164
166
|
this.timelineStart = /* @__PURE__ */ new Date();
|
|
165
167
|
this.timelineEnd = /* @__PURE__ */ new Date();
|
|
168
|
+
this.minDate = null;
|
|
166
169
|
this.pixelsPerDay = 40;
|
|
167
170
|
this.scrollLeft = 0;
|
|
168
171
|
this.scrollTop = 0;
|
|
@@ -178,6 +181,7 @@ class GanttChart {
|
|
|
178
181
|
this.boundHandleMouseMove = this.handleMouseMove.bind(this);
|
|
179
182
|
this.boundHandleMouseLeave = this.handleMouseLeave.bind(this);
|
|
180
183
|
this.boundHandleScroll = this.handleScroll.bind(this);
|
|
184
|
+
this.scrollToStartDate = this.scrollToStartDate.bind(this);
|
|
181
185
|
this.init();
|
|
182
186
|
}
|
|
183
187
|
init() {
|
|
@@ -218,12 +222,16 @@ class GanttChart {
|
|
|
218
222
|
this.updateDimensions();
|
|
219
223
|
this.render();
|
|
220
224
|
}
|
|
221
|
-
setData(newData) {
|
|
225
|
+
setData(newData, newConfig) {
|
|
222
226
|
this.data = newData;
|
|
223
227
|
this.buildTaskMap();
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
228
|
+
if (newConfig) {
|
|
229
|
+
this.updateConfig(newConfig);
|
|
230
|
+
} else {
|
|
231
|
+
this.calculateFullTimeline();
|
|
232
|
+
this.updateDimensions();
|
|
233
|
+
this.render();
|
|
234
|
+
}
|
|
227
235
|
}
|
|
228
236
|
destroy() {
|
|
229
237
|
if (this.resizeObserver) {
|
|
@@ -254,6 +262,7 @@ class GanttChart {
|
|
|
254
262
|
}
|
|
255
263
|
});
|
|
256
264
|
}
|
|
265
|
+
this.minDate = minDate;
|
|
257
266
|
minDate = DateUtils.addDays(minDate, -7);
|
|
258
267
|
maxDate = DateUtils.addDays(maxDate, 14);
|
|
259
268
|
switch (this.config.viewMode) {
|
|
@@ -289,8 +298,8 @@ class GanttChart {
|
|
|
289
298
|
this.viewportWidth = this.container.clientWidth;
|
|
290
299
|
this.viewportHeight = this.container.clientHeight;
|
|
291
300
|
this.mainCanvas.style.top = `${this.config.headerHeight}px`;
|
|
292
|
-
this.setupCanvas(this.headerCanvas, this.viewportWidth, this.config.headerHeight);
|
|
293
|
-
this.setupCanvas(this.mainCanvas, this.viewportWidth, this.viewportHeight - this.config.headerHeight);
|
|
301
|
+
this.headerCtx = this.setupCanvas(this.headerCanvas, this.viewportWidth, this.config.headerHeight);
|
|
302
|
+
this.mainCtx = this.setupCanvas(this.mainCanvas, this.viewportWidth, this.viewportHeight - this.config.headerHeight);
|
|
294
303
|
this.updateDimensions();
|
|
295
304
|
this.render();
|
|
296
305
|
}
|
|
@@ -328,6 +337,7 @@ class GanttChart {
|
|
|
328
337
|
canvas.style.height = `${height}px`;
|
|
329
338
|
const ctx = canvas.getContext("2d");
|
|
330
339
|
ctx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
|
340
|
+
return ctx;
|
|
331
341
|
}
|
|
332
342
|
calculateAllTaskPositions() {
|
|
333
343
|
this.taskPositions.clear();
|
|
@@ -383,6 +393,7 @@ class GanttChart {
|
|
|
383
393
|
ctx.fillStyle = "#f9f9f9";
|
|
384
394
|
ctx.fillRect(this.scrollLeft, 0, this.viewportWidth, h);
|
|
385
395
|
ctx.textBaseline = "middle";
|
|
396
|
+
ctx.textRendering = "optimizeLegibility";
|
|
386
397
|
let currentDate = new Date(this.visibleDateRange.start);
|
|
387
398
|
currentDate = this.getIterationStartDate(currentDate);
|
|
388
399
|
let lastUpperText = "";
|
|
@@ -449,15 +460,16 @@ class GanttChart {
|
|
|
449
460
|
const unitWidth = this.dateToX(nextDate) - x;
|
|
450
461
|
if (upperText !== lastUpperText) {
|
|
451
462
|
ctx.fillStyle = "#333";
|
|
452
|
-
ctx.font =
|
|
463
|
+
ctx.font = 'bold 14px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
|
464
|
+
ctx.textRendering = "optimizeLegibility";
|
|
453
465
|
ctx.textAlign = "left";
|
|
454
466
|
ctx.fillText(upperText, x + 5, h * 0.35);
|
|
455
467
|
lastUpperText = upperText;
|
|
456
468
|
}
|
|
457
|
-
ctx.fillStyle = "#
|
|
458
|
-
ctx.font = "
|
|
469
|
+
ctx.fillStyle = "#000412";
|
|
470
|
+
ctx.font = '14px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
|
459
471
|
ctx.textAlign = "center";
|
|
460
|
-
ctx.fillText(lowerText, x + unitWidth / 2, h * 0.7);
|
|
472
|
+
ctx.fillText(lowerText, Math.round(x + unitWidth / 2), Math.round(h * 0.7));
|
|
461
473
|
ctx.beginPath();
|
|
462
474
|
ctx.moveTo(x, h * 0.5);
|
|
463
475
|
ctx.lineTo(x, h);
|
|
@@ -569,7 +581,9 @@ class GanttChart {
|
|
|
569
581
|
}
|
|
570
582
|
drawAllTasks(ctx) {
|
|
571
583
|
ctx.textBaseline = "middle";
|
|
572
|
-
ctx.font =
|
|
584
|
+
ctx.font = '12px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
|
585
|
+
ctx.textRendering = "optimizeSpeed";
|
|
586
|
+
ctx.imageSmoothingEnabled = false;
|
|
573
587
|
for (let i = 0; i < this.data.length; i++) {
|
|
574
588
|
const row = this.data[i];
|
|
575
589
|
const y = i * this.config.rowHeight;
|
|
@@ -586,7 +600,7 @@ class GanttChart {
|
|
|
586
600
|
}
|
|
587
601
|
}
|
|
588
602
|
drawGrid(ctx, startDate, endDate) {
|
|
589
|
-
ctx.strokeStyle = "#
|
|
603
|
+
ctx.strokeStyle = "#e6e6e6";
|
|
590
604
|
ctx.lineWidth = 1;
|
|
591
605
|
ctx.beginPath();
|
|
592
606
|
if (this.config.showRowLines) {
|
|
@@ -689,7 +703,7 @@ class GanttChart {
|
|
|
689
703
|
const aWidth = (pos.x_actual_end ? pos.x_actual_end : this.dateToX(this.today)) - pos.x_actual_start;
|
|
690
704
|
pos.x_actual_start += aWidth * offsetX_actual;
|
|
691
705
|
pos.x_actual_end && (pos.x_actual_end = pos.x_actual_start + aWidth * percent_actual);
|
|
692
|
-
ctx.fillRect(pos.x_actual_start, taskY, aWidth * percent_actual, taskHeight);
|
|
706
|
+
ctx.fillRect(Math.round(pos.x_actual_start), Math.round(taskY + 2), Math.round(aWidth * percent_actual), Math.round(taskHeight - 2));
|
|
693
707
|
}
|
|
694
708
|
if (this.config.showPlan && pos.x_plan_start && pos.x_plan_end) {
|
|
695
709
|
ctx.strokeStyle = task.planBorderColor ? task.planBorderColor : this.config.planBorderColor;
|
|
@@ -701,19 +715,19 @@ class GanttChart {
|
|
|
701
715
|
ctx.lineTo(pos.x_plan_start + width * percent_plan, taskY);
|
|
702
716
|
ctx.stroke();
|
|
703
717
|
}
|
|
704
|
-
ctx.fillStyle = "#
|
|
718
|
+
ctx.fillStyle = "#000";
|
|
705
719
|
if (this.config.showLeftRemark && task.leftRemark) {
|
|
706
720
|
ctx.textAlign = "right";
|
|
707
|
-
ctx.fillText(task.leftRemark, Math.min(...[pos.x_plan_start, pos.x_actual_start].filter((val) => val !== null && val !== void 0)) - 8, textY);
|
|
721
|
+
ctx.fillText(task.leftRemark, Math.round(Math.min(...[pos.x_plan_start, pos.x_actual_start].filter((val) => val !== null && val !== void 0)) - 8), Math.round(textY));
|
|
708
722
|
}
|
|
709
723
|
if (this.config.showRightRemark && task.rightRemark) {
|
|
710
724
|
ctx.textAlign = "left";
|
|
711
|
-
ctx.fillText(task.rightRemark, Math.max(...[pos.x_plan_end, pos.x_actual_end].filter((val) => val !== null && val !== void 0)) + 8, textY);
|
|
725
|
+
ctx.fillText(task.rightRemark, Math.round(Math.max(...[pos.x_plan_end, pos.x_actual_end].filter((val) => val !== null && val !== void 0)) + 8), Math.round(textY));
|
|
712
726
|
}
|
|
713
727
|
if (this.config.showCenterRemark && task.centerRemark) {
|
|
714
728
|
const centerX = pos.x_actual_start + (pos.x_actual_end - pos.x_actual_start) / 2;
|
|
715
729
|
ctx.textAlign = "center";
|
|
716
|
-
ctx.fillText(task.centerRemark, centerX, textY, pos.x_actual_end - pos.x_actual_start);
|
|
730
|
+
ctx.fillText(task.centerRemark, Math.round(centerX), Math.round(textY), Math.round(pos.x_actual_end - pos.x_actual_start));
|
|
717
731
|
}
|
|
718
732
|
}
|
|
719
733
|
getTaskStyles(task) {
|
|
@@ -807,6 +821,18 @@ class GanttChart {
|
|
|
807
821
|
static getTaskWidthPercent(diffMilliseconds, pixelsPerDay) {
|
|
808
822
|
return diffMilliseconds * pixelsPerDay / DateUtils.ONE_DAY_MS;
|
|
809
823
|
}
|
|
824
|
+
/**
|
|
825
|
+
* scroll to specified date position, default to minDate
|
|
826
|
+
*
|
|
827
|
+
* @param date
|
|
828
|
+
*/
|
|
829
|
+
scrollToStartDate(date) {
|
|
830
|
+
const startDate = date ? date : this.minDate;
|
|
831
|
+
if (startDate) {
|
|
832
|
+
const xPosition = this.dateToX(startDate);
|
|
833
|
+
this.container.scrollTo({ left: xPosition - 80 });
|
|
834
|
+
}
|
|
835
|
+
}
|
|
810
836
|
}
|
|
811
837
|
exports.DateUtils = DateUtils;
|
|
812
838
|
exports.GanttChart = GanttChart;
|
package/dist/index.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* gantt-canvas-chart v1.0
|
|
2
|
+
* gantt-canvas-chart v1.1.0
|
|
3
3
|
* (c) 2025-present chandq
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
overflow: hidden;
|
|
11
11
|
position: relative;
|
|
12
12
|
background: white;
|
|
13
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
.__gantt-chart-container {
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
position: relative;
|
|
19
20
|
background: #ffffff;
|
|
20
21
|
height: 100%;
|
|
22
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
.__gantt-scroll-dummy {
|
|
@@ -37,6 +39,7 @@
|
|
|
37
39
|
z-index: 10;
|
|
38
40
|
background: #f8fafc;
|
|
39
41
|
border-bottom: 1px solid #e2e8f0;
|
|
42
|
+
text-rendering: optimizeSpeed;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
.__gantt-main-canvas {
|
|
@@ -44,6 +47,26 @@
|
|
|
44
47
|
top: 56px; /* Corresponds to header height */
|
|
45
48
|
left: 0;
|
|
46
49
|
z-index: 1;
|
|
50
|
+
text-rendering: optimizeSpeed;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Add to your CSS file or create a stylesheet */
|
|
54
|
+
.__gantt-chart-container {
|
|
55
|
+
transform: translateZ(0); /* Force hardware acceleration */
|
|
56
|
+
-webkit-font-smoothing: antialiased;
|
|
57
|
+
-moz-osx-font-smoothing: grayscale;
|
|
58
|
+
text-rendering: optimizeLegibility;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.__gantt-header-canvas,
|
|
62
|
+
.__gantt-main-canvas {
|
|
63
|
+
image-rendering: -webkit-optimize-contrast;
|
|
64
|
+
image-rendering: crisp-edges;
|
|
65
|
+
shape-rendering: crispEdges;
|
|
66
|
+
/* Add font rendering optimizations */
|
|
67
|
+
-webkit-font-smoothing: antialiased;
|
|
68
|
+
-moz-osx-font-smoothing: grayscale;
|
|
69
|
+
text-rendering: optimizeSpeed;
|
|
47
70
|
}
|
|
48
71
|
|
|
49
72
|
/* Tooltip */
|
|
@@ -33,6 +33,7 @@ export declare class GanttChart {
|
|
|
33
33
|
private mainCtx;
|
|
34
34
|
private timelineStart;
|
|
35
35
|
private timelineEnd;
|
|
36
|
+
private minDate;
|
|
36
37
|
private pixelsPerDay;
|
|
37
38
|
private scrollLeft;
|
|
38
39
|
private scrollTop;
|
|
@@ -54,7 +55,7 @@ export declare class GanttChart {
|
|
|
54
55
|
private buildTaskMap;
|
|
55
56
|
private setupEvents;
|
|
56
57
|
updateConfig(newConfig: GanttConfig): void;
|
|
57
|
-
setData(newData: GanttData): void;
|
|
58
|
+
setData(newData: GanttData, newConfig?: GanttConfig): void;
|
|
58
59
|
destroy(): void;
|
|
59
60
|
private calculateFullTimeline;
|
|
60
61
|
private updatePixelsPerDay;
|
|
@@ -88,6 +89,12 @@ export declare class GanttChart {
|
|
|
88
89
|
* @returns
|
|
89
90
|
*/
|
|
90
91
|
static getTaskWidthPercent(diffMilliseconds: number, pixelsPerDay: number): number;
|
|
92
|
+
/**
|
|
93
|
+
* scroll to specified date position, default to minDate
|
|
94
|
+
*
|
|
95
|
+
* @param date
|
|
96
|
+
*/
|
|
97
|
+
scrollToStartDate(date?: Date): void;
|
|
91
98
|
}
|
|
92
99
|
|
|
93
100
|
declare interface GanttConfig {
|
package/dist/index.es.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* gantt-canvas-chart v1.0
|
|
2
|
+
* gantt-canvas-chart v1.1.0
|
|
3
3
|
* (c) 2025-present chandq
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
@@ -93,6 +93,8 @@ class GanttChart {
|
|
|
93
93
|
mainCtx;
|
|
94
94
|
timelineStart;
|
|
95
95
|
timelineEnd;
|
|
96
|
+
minDate;
|
|
97
|
+
// private maxDate: Date | null;
|
|
96
98
|
pixelsPerDay;
|
|
97
99
|
scrollLeft;
|
|
98
100
|
scrollTop;
|
|
@@ -145,8 +147,8 @@ class GanttChart {
|
|
|
145
147
|
offsetTop: 0,
|
|
146
148
|
offsetLeft: 0,
|
|
147
149
|
viewFactors: { Day: 80, Week: 20, Month: 15, Year: 6 },
|
|
148
|
-
planBorderColor: "#
|
|
149
|
-
actualBgColor: "#
|
|
150
|
+
planBorderColor: "#C1EFCF",
|
|
151
|
+
actualBgColor: "#5AC989",
|
|
150
152
|
...config
|
|
151
153
|
};
|
|
152
154
|
this.headerCanvas = headerCanvas;
|
|
@@ -161,6 +163,7 @@ class GanttChart {
|
|
|
161
163
|
this.mainCtx = this.mainCanvas.getContext("2d");
|
|
162
164
|
this.timelineStart = /* @__PURE__ */ new Date();
|
|
163
165
|
this.timelineEnd = /* @__PURE__ */ new Date();
|
|
166
|
+
this.minDate = null;
|
|
164
167
|
this.pixelsPerDay = 40;
|
|
165
168
|
this.scrollLeft = 0;
|
|
166
169
|
this.scrollTop = 0;
|
|
@@ -176,6 +179,7 @@ class GanttChart {
|
|
|
176
179
|
this.boundHandleMouseMove = this.handleMouseMove.bind(this);
|
|
177
180
|
this.boundHandleMouseLeave = this.handleMouseLeave.bind(this);
|
|
178
181
|
this.boundHandleScroll = this.handleScroll.bind(this);
|
|
182
|
+
this.scrollToStartDate = this.scrollToStartDate.bind(this);
|
|
179
183
|
this.init();
|
|
180
184
|
}
|
|
181
185
|
init() {
|
|
@@ -216,12 +220,16 @@ class GanttChart {
|
|
|
216
220
|
this.updateDimensions();
|
|
217
221
|
this.render();
|
|
218
222
|
}
|
|
219
|
-
setData(newData) {
|
|
223
|
+
setData(newData, newConfig) {
|
|
220
224
|
this.data = newData;
|
|
221
225
|
this.buildTaskMap();
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
226
|
+
if (newConfig) {
|
|
227
|
+
this.updateConfig(newConfig);
|
|
228
|
+
} else {
|
|
229
|
+
this.calculateFullTimeline();
|
|
230
|
+
this.updateDimensions();
|
|
231
|
+
this.render();
|
|
232
|
+
}
|
|
225
233
|
}
|
|
226
234
|
destroy() {
|
|
227
235
|
if (this.resizeObserver) {
|
|
@@ -252,6 +260,7 @@ class GanttChart {
|
|
|
252
260
|
}
|
|
253
261
|
});
|
|
254
262
|
}
|
|
263
|
+
this.minDate = minDate;
|
|
255
264
|
minDate = DateUtils.addDays(minDate, -7);
|
|
256
265
|
maxDate = DateUtils.addDays(maxDate, 14);
|
|
257
266
|
switch (this.config.viewMode) {
|
|
@@ -287,8 +296,8 @@ class GanttChart {
|
|
|
287
296
|
this.viewportWidth = this.container.clientWidth;
|
|
288
297
|
this.viewportHeight = this.container.clientHeight;
|
|
289
298
|
this.mainCanvas.style.top = `${this.config.headerHeight}px`;
|
|
290
|
-
this.setupCanvas(this.headerCanvas, this.viewportWidth, this.config.headerHeight);
|
|
291
|
-
this.setupCanvas(this.mainCanvas, this.viewportWidth, this.viewportHeight - this.config.headerHeight);
|
|
299
|
+
this.headerCtx = this.setupCanvas(this.headerCanvas, this.viewportWidth, this.config.headerHeight);
|
|
300
|
+
this.mainCtx = this.setupCanvas(this.mainCanvas, this.viewportWidth, this.viewportHeight - this.config.headerHeight);
|
|
292
301
|
this.updateDimensions();
|
|
293
302
|
this.render();
|
|
294
303
|
}
|
|
@@ -326,6 +335,7 @@ class GanttChart {
|
|
|
326
335
|
canvas.style.height = `${height}px`;
|
|
327
336
|
const ctx = canvas.getContext("2d");
|
|
328
337
|
ctx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
|
338
|
+
return ctx;
|
|
329
339
|
}
|
|
330
340
|
calculateAllTaskPositions() {
|
|
331
341
|
this.taskPositions.clear();
|
|
@@ -381,6 +391,7 @@ class GanttChart {
|
|
|
381
391
|
ctx.fillStyle = "#f9f9f9";
|
|
382
392
|
ctx.fillRect(this.scrollLeft, 0, this.viewportWidth, h);
|
|
383
393
|
ctx.textBaseline = "middle";
|
|
394
|
+
ctx.textRendering = "optimizeLegibility";
|
|
384
395
|
let currentDate = new Date(this.visibleDateRange.start);
|
|
385
396
|
currentDate = this.getIterationStartDate(currentDate);
|
|
386
397
|
let lastUpperText = "";
|
|
@@ -447,15 +458,16 @@ class GanttChart {
|
|
|
447
458
|
const unitWidth = this.dateToX(nextDate) - x;
|
|
448
459
|
if (upperText !== lastUpperText) {
|
|
449
460
|
ctx.fillStyle = "#333";
|
|
450
|
-
ctx.font =
|
|
461
|
+
ctx.font = 'bold 14px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
|
462
|
+
ctx.textRendering = "optimizeLegibility";
|
|
451
463
|
ctx.textAlign = "left";
|
|
452
464
|
ctx.fillText(upperText, x + 5, h * 0.35);
|
|
453
465
|
lastUpperText = upperText;
|
|
454
466
|
}
|
|
455
|
-
ctx.fillStyle = "#
|
|
456
|
-
ctx.font = "
|
|
467
|
+
ctx.fillStyle = "#000412";
|
|
468
|
+
ctx.font = '14px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
|
457
469
|
ctx.textAlign = "center";
|
|
458
|
-
ctx.fillText(lowerText, x + unitWidth / 2, h * 0.7);
|
|
470
|
+
ctx.fillText(lowerText, Math.round(x + unitWidth / 2), Math.round(h * 0.7));
|
|
459
471
|
ctx.beginPath();
|
|
460
472
|
ctx.moveTo(x, h * 0.5);
|
|
461
473
|
ctx.lineTo(x, h);
|
|
@@ -567,7 +579,9 @@ class GanttChart {
|
|
|
567
579
|
}
|
|
568
580
|
drawAllTasks(ctx) {
|
|
569
581
|
ctx.textBaseline = "middle";
|
|
570
|
-
ctx.font =
|
|
582
|
+
ctx.font = '12px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
|
583
|
+
ctx.textRendering = "optimizeSpeed";
|
|
584
|
+
ctx.imageSmoothingEnabled = false;
|
|
571
585
|
for (let i = 0; i < this.data.length; i++) {
|
|
572
586
|
const row = this.data[i];
|
|
573
587
|
const y = i * this.config.rowHeight;
|
|
@@ -584,7 +598,7 @@ class GanttChart {
|
|
|
584
598
|
}
|
|
585
599
|
}
|
|
586
600
|
drawGrid(ctx, startDate, endDate) {
|
|
587
|
-
ctx.strokeStyle = "#
|
|
601
|
+
ctx.strokeStyle = "#e6e6e6";
|
|
588
602
|
ctx.lineWidth = 1;
|
|
589
603
|
ctx.beginPath();
|
|
590
604
|
if (this.config.showRowLines) {
|
|
@@ -687,7 +701,7 @@ class GanttChart {
|
|
|
687
701
|
const aWidth = (pos.x_actual_end ? pos.x_actual_end : this.dateToX(this.today)) - pos.x_actual_start;
|
|
688
702
|
pos.x_actual_start += aWidth * offsetX_actual;
|
|
689
703
|
pos.x_actual_end && (pos.x_actual_end = pos.x_actual_start + aWidth * percent_actual);
|
|
690
|
-
ctx.fillRect(pos.x_actual_start, taskY, aWidth * percent_actual, taskHeight);
|
|
704
|
+
ctx.fillRect(Math.round(pos.x_actual_start), Math.round(taskY + 2), Math.round(aWidth * percent_actual), Math.round(taskHeight - 2));
|
|
691
705
|
}
|
|
692
706
|
if (this.config.showPlan && pos.x_plan_start && pos.x_plan_end) {
|
|
693
707
|
ctx.strokeStyle = task.planBorderColor ? task.planBorderColor : this.config.planBorderColor;
|
|
@@ -699,19 +713,19 @@ class GanttChart {
|
|
|
699
713
|
ctx.lineTo(pos.x_plan_start + width * percent_plan, taskY);
|
|
700
714
|
ctx.stroke();
|
|
701
715
|
}
|
|
702
|
-
ctx.fillStyle = "#
|
|
716
|
+
ctx.fillStyle = "#000";
|
|
703
717
|
if (this.config.showLeftRemark && task.leftRemark) {
|
|
704
718
|
ctx.textAlign = "right";
|
|
705
|
-
ctx.fillText(task.leftRemark, Math.min(...[pos.x_plan_start, pos.x_actual_start].filter((val) => val !== null && val !== void 0)) - 8, textY);
|
|
719
|
+
ctx.fillText(task.leftRemark, Math.round(Math.min(...[pos.x_plan_start, pos.x_actual_start].filter((val) => val !== null && val !== void 0)) - 8), Math.round(textY));
|
|
706
720
|
}
|
|
707
721
|
if (this.config.showRightRemark && task.rightRemark) {
|
|
708
722
|
ctx.textAlign = "left";
|
|
709
|
-
ctx.fillText(task.rightRemark, Math.max(...[pos.x_plan_end, pos.x_actual_end].filter((val) => val !== null && val !== void 0)) + 8, textY);
|
|
723
|
+
ctx.fillText(task.rightRemark, Math.round(Math.max(...[pos.x_plan_end, pos.x_actual_end].filter((val) => val !== null && val !== void 0)) + 8), Math.round(textY));
|
|
710
724
|
}
|
|
711
725
|
if (this.config.showCenterRemark && task.centerRemark) {
|
|
712
726
|
const centerX = pos.x_actual_start + (pos.x_actual_end - pos.x_actual_start) / 2;
|
|
713
727
|
ctx.textAlign = "center";
|
|
714
|
-
ctx.fillText(task.centerRemark, centerX, textY, pos.x_actual_end - pos.x_actual_start);
|
|
728
|
+
ctx.fillText(task.centerRemark, Math.round(centerX), Math.round(textY), Math.round(pos.x_actual_end - pos.x_actual_start));
|
|
715
729
|
}
|
|
716
730
|
}
|
|
717
731
|
getTaskStyles(task) {
|
|
@@ -805,6 +819,18 @@ class GanttChart {
|
|
|
805
819
|
static getTaskWidthPercent(diffMilliseconds, pixelsPerDay) {
|
|
806
820
|
return diffMilliseconds * pixelsPerDay / DateUtils.ONE_DAY_MS;
|
|
807
821
|
}
|
|
822
|
+
/**
|
|
823
|
+
* scroll to specified date position, default to minDate
|
|
824
|
+
*
|
|
825
|
+
* @param date
|
|
826
|
+
*/
|
|
827
|
+
scrollToStartDate(date) {
|
|
828
|
+
const startDate = date ? date : this.minDate;
|
|
829
|
+
if (startDate) {
|
|
830
|
+
const xPosition = this.dateToX(startDate);
|
|
831
|
+
this.container.scrollTo({ left: xPosition - 80 });
|
|
832
|
+
}
|
|
833
|
+
}
|
|
808
834
|
}
|
|
809
835
|
export {
|
|
810
836
|
DateUtils,
|
package/dist/index.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* gantt-canvas-chart v1.0
|
|
2
|
+
* gantt-canvas-chart v1.1.0
|
|
3
3
|
* (c) 2025-present chandq
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
@@ -97,6 +97,8 @@
|
|
|
97
97
|
mainCtx;
|
|
98
98
|
timelineStart;
|
|
99
99
|
timelineEnd;
|
|
100
|
+
minDate;
|
|
101
|
+
// private maxDate: Date | null;
|
|
100
102
|
pixelsPerDay;
|
|
101
103
|
scrollLeft;
|
|
102
104
|
scrollTop;
|
|
@@ -149,8 +151,8 @@
|
|
|
149
151
|
offsetTop: 0,
|
|
150
152
|
offsetLeft: 0,
|
|
151
153
|
viewFactors: { Day: 80, Week: 20, Month: 15, Year: 6 },
|
|
152
|
-
planBorderColor: "#
|
|
153
|
-
actualBgColor: "#
|
|
154
|
+
planBorderColor: "#C1EFCF",
|
|
155
|
+
actualBgColor: "#5AC989",
|
|
154
156
|
...config
|
|
155
157
|
};
|
|
156
158
|
this.headerCanvas = headerCanvas;
|
|
@@ -165,6 +167,7 @@
|
|
|
165
167
|
this.mainCtx = this.mainCanvas.getContext("2d");
|
|
166
168
|
this.timelineStart = /* @__PURE__ */ new Date();
|
|
167
169
|
this.timelineEnd = /* @__PURE__ */ new Date();
|
|
170
|
+
this.minDate = null;
|
|
168
171
|
this.pixelsPerDay = 40;
|
|
169
172
|
this.scrollLeft = 0;
|
|
170
173
|
this.scrollTop = 0;
|
|
@@ -180,6 +183,7 @@
|
|
|
180
183
|
this.boundHandleMouseMove = this.handleMouseMove.bind(this);
|
|
181
184
|
this.boundHandleMouseLeave = this.handleMouseLeave.bind(this);
|
|
182
185
|
this.boundHandleScroll = this.handleScroll.bind(this);
|
|
186
|
+
this.scrollToStartDate = this.scrollToStartDate.bind(this);
|
|
183
187
|
this.init();
|
|
184
188
|
}
|
|
185
189
|
init() {
|
|
@@ -220,12 +224,16 @@
|
|
|
220
224
|
this.updateDimensions();
|
|
221
225
|
this.render();
|
|
222
226
|
}
|
|
223
|
-
setData(newData) {
|
|
227
|
+
setData(newData, newConfig) {
|
|
224
228
|
this.data = newData;
|
|
225
229
|
this.buildTaskMap();
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
230
|
+
if (newConfig) {
|
|
231
|
+
this.updateConfig(newConfig);
|
|
232
|
+
} else {
|
|
233
|
+
this.calculateFullTimeline();
|
|
234
|
+
this.updateDimensions();
|
|
235
|
+
this.render();
|
|
236
|
+
}
|
|
229
237
|
}
|
|
230
238
|
destroy() {
|
|
231
239
|
if (this.resizeObserver) {
|
|
@@ -256,6 +264,7 @@
|
|
|
256
264
|
}
|
|
257
265
|
});
|
|
258
266
|
}
|
|
267
|
+
this.minDate = minDate;
|
|
259
268
|
minDate = DateUtils.addDays(minDate, -7);
|
|
260
269
|
maxDate = DateUtils.addDays(maxDate, 14);
|
|
261
270
|
switch (this.config.viewMode) {
|
|
@@ -291,8 +300,8 @@
|
|
|
291
300
|
this.viewportWidth = this.container.clientWidth;
|
|
292
301
|
this.viewportHeight = this.container.clientHeight;
|
|
293
302
|
this.mainCanvas.style.top = `${this.config.headerHeight}px`;
|
|
294
|
-
this.setupCanvas(this.headerCanvas, this.viewportWidth, this.config.headerHeight);
|
|
295
|
-
this.setupCanvas(this.mainCanvas, this.viewportWidth, this.viewportHeight - this.config.headerHeight);
|
|
303
|
+
this.headerCtx = this.setupCanvas(this.headerCanvas, this.viewportWidth, this.config.headerHeight);
|
|
304
|
+
this.mainCtx = this.setupCanvas(this.mainCanvas, this.viewportWidth, this.viewportHeight - this.config.headerHeight);
|
|
296
305
|
this.updateDimensions();
|
|
297
306
|
this.render();
|
|
298
307
|
}
|
|
@@ -330,6 +339,7 @@
|
|
|
330
339
|
canvas.style.height = `${height}px`;
|
|
331
340
|
const ctx = canvas.getContext("2d");
|
|
332
341
|
ctx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
|
342
|
+
return ctx;
|
|
333
343
|
}
|
|
334
344
|
calculateAllTaskPositions() {
|
|
335
345
|
this.taskPositions.clear();
|
|
@@ -385,6 +395,7 @@
|
|
|
385
395
|
ctx.fillStyle = "#f9f9f9";
|
|
386
396
|
ctx.fillRect(this.scrollLeft, 0, this.viewportWidth, h);
|
|
387
397
|
ctx.textBaseline = "middle";
|
|
398
|
+
ctx.textRendering = "optimizeLegibility";
|
|
388
399
|
let currentDate = new Date(this.visibleDateRange.start);
|
|
389
400
|
currentDate = this.getIterationStartDate(currentDate);
|
|
390
401
|
let lastUpperText = "";
|
|
@@ -451,15 +462,16 @@
|
|
|
451
462
|
const unitWidth = this.dateToX(nextDate) - x;
|
|
452
463
|
if (upperText !== lastUpperText) {
|
|
453
464
|
ctx.fillStyle = "#333";
|
|
454
|
-
ctx.font =
|
|
465
|
+
ctx.font = 'bold 14px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
|
466
|
+
ctx.textRendering = "optimizeLegibility";
|
|
455
467
|
ctx.textAlign = "left";
|
|
456
468
|
ctx.fillText(upperText, x + 5, h * 0.35);
|
|
457
469
|
lastUpperText = upperText;
|
|
458
470
|
}
|
|
459
|
-
ctx.fillStyle = "#
|
|
460
|
-
ctx.font = "
|
|
471
|
+
ctx.fillStyle = "#000412";
|
|
472
|
+
ctx.font = '14px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
|
461
473
|
ctx.textAlign = "center";
|
|
462
|
-
ctx.fillText(lowerText, x + unitWidth / 2, h * 0.7);
|
|
474
|
+
ctx.fillText(lowerText, Math.round(x + unitWidth / 2), Math.round(h * 0.7));
|
|
463
475
|
ctx.beginPath();
|
|
464
476
|
ctx.moveTo(x, h * 0.5);
|
|
465
477
|
ctx.lineTo(x, h);
|
|
@@ -571,7 +583,9 @@
|
|
|
571
583
|
}
|
|
572
584
|
drawAllTasks(ctx) {
|
|
573
585
|
ctx.textBaseline = "middle";
|
|
574
|
-
ctx.font =
|
|
586
|
+
ctx.font = '12px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
|
587
|
+
ctx.textRendering = "optimizeSpeed";
|
|
588
|
+
ctx.imageSmoothingEnabled = false;
|
|
575
589
|
for (let i = 0; i < this.data.length; i++) {
|
|
576
590
|
const row = this.data[i];
|
|
577
591
|
const y = i * this.config.rowHeight;
|
|
@@ -588,7 +602,7 @@
|
|
|
588
602
|
}
|
|
589
603
|
}
|
|
590
604
|
drawGrid(ctx, startDate, endDate) {
|
|
591
|
-
ctx.strokeStyle = "#
|
|
605
|
+
ctx.strokeStyle = "#e6e6e6";
|
|
592
606
|
ctx.lineWidth = 1;
|
|
593
607
|
ctx.beginPath();
|
|
594
608
|
if (this.config.showRowLines) {
|
|
@@ -691,7 +705,7 @@
|
|
|
691
705
|
const aWidth = (pos.x_actual_end ? pos.x_actual_end : this.dateToX(this.today)) - pos.x_actual_start;
|
|
692
706
|
pos.x_actual_start += aWidth * offsetX_actual;
|
|
693
707
|
pos.x_actual_end && (pos.x_actual_end = pos.x_actual_start + aWidth * percent_actual);
|
|
694
|
-
ctx.fillRect(pos.x_actual_start, taskY, aWidth * percent_actual, taskHeight);
|
|
708
|
+
ctx.fillRect(Math.round(pos.x_actual_start), Math.round(taskY + 2), Math.round(aWidth * percent_actual), Math.round(taskHeight - 2));
|
|
695
709
|
}
|
|
696
710
|
if (this.config.showPlan && pos.x_plan_start && pos.x_plan_end) {
|
|
697
711
|
ctx.strokeStyle = task.planBorderColor ? task.planBorderColor : this.config.planBorderColor;
|
|
@@ -703,19 +717,19 @@
|
|
|
703
717
|
ctx.lineTo(pos.x_plan_start + width * percent_plan, taskY);
|
|
704
718
|
ctx.stroke();
|
|
705
719
|
}
|
|
706
|
-
ctx.fillStyle = "#
|
|
720
|
+
ctx.fillStyle = "#000";
|
|
707
721
|
if (this.config.showLeftRemark && task.leftRemark) {
|
|
708
722
|
ctx.textAlign = "right";
|
|
709
|
-
ctx.fillText(task.leftRemark, Math.min(...[pos.x_plan_start, pos.x_actual_start].filter((val) => val !== null && val !== void 0)) - 8, textY);
|
|
723
|
+
ctx.fillText(task.leftRemark, Math.round(Math.min(...[pos.x_plan_start, pos.x_actual_start].filter((val) => val !== null && val !== void 0)) - 8), Math.round(textY));
|
|
710
724
|
}
|
|
711
725
|
if (this.config.showRightRemark && task.rightRemark) {
|
|
712
726
|
ctx.textAlign = "left";
|
|
713
|
-
ctx.fillText(task.rightRemark, Math.max(...[pos.x_plan_end, pos.x_actual_end].filter((val) => val !== null && val !== void 0)) + 8, textY);
|
|
727
|
+
ctx.fillText(task.rightRemark, Math.round(Math.max(...[pos.x_plan_end, pos.x_actual_end].filter((val) => val !== null && val !== void 0)) + 8), Math.round(textY));
|
|
714
728
|
}
|
|
715
729
|
if (this.config.showCenterRemark && task.centerRemark) {
|
|
716
730
|
const centerX = pos.x_actual_start + (pos.x_actual_end - pos.x_actual_start) / 2;
|
|
717
731
|
ctx.textAlign = "center";
|
|
718
|
-
ctx.fillText(task.centerRemark, centerX, textY, pos.x_actual_end - pos.x_actual_start);
|
|
732
|
+
ctx.fillText(task.centerRemark, Math.round(centerX), Math.round(textY), Math.round(pos.x_actual_end - pos.x_actual_start));
|
|
719
733
|
}
|
|
720
734
|
}
|
|
721
735
|
getTaskStyles(task) {
|
|
@@ -809,6 +823,18 @@
|
|
|
809
823
|
static getTaskWidthPercent(diffMilliseconds, pixelsPerDay) {
|
|
810
824
|
return diffMilliseconds * pixelsPerDay / DateUtils.ONE_DAY_MS;
|
|
811
825
|
}
|
|
826
|
+
/**
|
|
827
|
+
* scroll to specified date position, default to minDate
|
|
828
|
+
*
|
|
829
|
+
* @param date
|
|
830
|
+
*/
|
|
831
|
+
scrollToStartDate(date) {
|
|
832
|
+
const startDate = date ? date : this.minDate;
|
|
833
|
+
if (startDate) {
|
|
834
|
+
const xPosition = this.dateToX(startDate);
|
|
835
|
+
this.container.scrollTo({ left: xPosition - 80 });
|
|
836
|
+
}
|
|
837
|
+
}
|
|
812
838
|
}
|
|
813
839
|
exports2.DateUtils = DateUtils;
|
|
814
840
|
exports2.GanttChart = GanttChart;
|
package/package.json
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gantt-canvas-chart",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
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",
|
|
7
7
|
"module": "dist/index.es.js",
|
|
8
|
-
"types": "dist/
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
9
|
"unpkg": "dist/index.umd.js",
|
|
10
10
|
"jsdelivr": "dist/index.umd.js",
|
|
11
|
+
"typesVersions": {
|
|
12
|
+
"*": {
|
|
13
|
+
".": [
|
|
14
|
+
"./dist/index.d.ts"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
},
|
|
11
18
|
"exports": {
|
|
12
19
|
".": {
|
|
13
|
-
"types": "./dist/
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
14
21
|
"import": "./dist/index.es.js",
|
|
15
22
|
"require": "./dist/index.umd.cjs"
|
|
16
23
|
},
|
|
17
|
-
"./
|
|
24
|
+
"./style.css": "./dist/index.css"
|
|
18
25
|
},
|
|
19
26
|
"files": [
|
|
20
27
|
"dist"
|