gantt-canvas-chart 1.5.3 → 1.5.4
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 +27 -21
- package/dist/index.css +5 -3
- package/dist/index.d.ts +6 -0
- package/dist/index.es.js +27 -21
- package/dist/index.umd.js +27 -21
- 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.4
|
|
3
3
|
* (c) 2025-present chandq
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
@@ -197,6 +197,7 @@ class GanttChart {
|
|
|
197
197
|
offsetTop: 0,
|
|
198
198
|
offsetLeft: 0,
|
|
199
199
|
scrollEdgeThresholds: 10,
|
|
200
|
+
xGap: 0,
|
|
200
201
|
enabledLoadMore: [],
|
|
201
202
|
viewFactors: { Day: 80, Week: 20, Month: 15, Year: 6 },
|
|
202
203
|
planBorderColor: "#C1EFCF",
|
|
@@ -553,8 +554,17 @@ class GanttChart {
|
|
|
553
554
|
canvas.style.height = `${height}px`;
|
|
554
555
|
const ctx = canvas.getContext("2d");
|
|
555
556
|
ctx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
|
557
|
+
ctx.textBaseline = "middle";
|
|
558
|
+
ctx.imageSmoothingEnabled = false;
|
|
556
559
|
return ctx;
|
|
557
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* 辅助函数:像素对齐(确保MAC/Windows等不同设备上1px线条清晰)
|
|
563
|
+
* 用于 1px 线条,使其落在 x.5 位置,填满一个物理像素,避免模糊
|
|
564
|
+
*/
|
|
565
|
+
snap(val) {
|
|
566
|
+
return Math.floor(val) + 0.5;
|
|
567
|
+
}
|
|
558
568
|
calculateAllTaskPositions() {
|
|
559
569
|
this.taskPositions.clear();
|
|
560
570
|
let visibleRowIndex = 0;
|
|
@@ -640,9 +650,9 @@ class GanttChart {
|
|
|
640
650
|
const h = this.config.headerHeight;
|
|
641
651
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
642
652
|
ctx.save();
|
|
643
|
-
ctx.translate(-this.scrollLeft, 0);
|
|
653
|
+
ctx.translate(-Math.round(this.scrollLeft), 0);
|
|
644
654
|
ctx.fillStyle = this.config.headerBgColor;
|
|
645
|
-
ctx.fillRect(this.scrollLeft, 0, this.viewportWidth, h);
|
|
655
|
+
ctx.fillRect(Math.round(this.scrollLeft), 0, this.viewportWidth, h);
|
|
646
656
|
ctx.textBaseline = "middle";
|
|
647
657
|
ctx.textRendering = "optimizeLegibility";
|
|
648
658
|
let currentDate = new Date(this.visibleDateRange.start);
|
|
@@ -720,20 +730,20 @@ class GanttChart {
|
|
|
720
730
|
}
|
|
721
731
|
const groupedBlocks = this.groupConsecutiveBlocks(visibleBlocks);
|
|
722
732
|
ctx.fillStyle = "#333";
|
|
723
|
-
ctx.font =
|
|
733
|
+
ctx.font = "bold 14px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
724
734
|
ctx.textAlign = "left";
|
|
725
735
|
groupedBlocks.forEach((group) => {
|
|
726
736
|
const visibleStart = Math.max(group.startX, this.scrollLeft);
|
|
727
737
|
const visibleEnd = Math.min(group.endX, this.scrollLeft + this.viewportWidth);
|
|
728
738
|
if (visibleEnd > visibleStart) {
|
|
729
739
|
ctx.fillStyle = this.config.headerBgColor;
|
|
730
|
-
ctx.fillRect(visibleStart, 0, visibleEnd - visibleStart, h * 0.5);
|
|
740
|
+
ctx.fillRect(Math.round(visibleStart), 0, Math.round(visibleEnd - visibleStart), Math.round(h * 0.5));
|
|
731
741
|
ctx.fillStyle = "#333";
|
|
732
|
-
ctx.fillText(group.text, visibleStart + 5, group.yPos);
|
|
742
|
+
ctx.fillText(group.text, Math.round(visibleStart + 5), Math.round(group.yPos));
|
|
733
743
|
}
|
|
734
744
|
});
|
|
735
745
|
while (currentDateForLower <= this.visibleDateRange.end) {
|
|
736
|
-
const x = this.dateToX(currentDateForLower);
|
|
746
|
+
const x = this.snap(this.dateToX(currentDateForLower));
|
|
737
747
|
let lowerText = "";
|
|
738
748
|
let nextDate;
|
|
739
749
|
switch (this.config.viewMode) {
|
|
@@ -768,7 +778,7 @@ class GanttChart {
|
|
|
768
778
|
}
|
|
769
779
|
const unitWidth = this.dateToX(nextDate) - x;
|
|
770
780
|
ctx.fillStyle = "#000412";
|
|
771
|
-
ctx.font =
|
|
781
|
+
ctx.font = "14px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
772
782
|
ctx.textAlign = "center";
|
|
773
783
|
ctx.fillText(lowerText, Math.round(x + unitWidth / 2), Math.round(h * 0.7));
|
|
774
784
|
ctx.beginPath();
|
|
@@ -782,11 +792,6 @@ class GanttChart {
|
|
|
782
792
|
currentDateForLower = nextDate;
|
|
783
793
|
}
|
|
784
794
|
}
|
|
785
|
-
ctx.beginPath();
|
|
786
|
-
ctx.moveTo(this.scrollLeft, h - 0.5);
|
|
787
|
-
ctx.lineTo(this.scrollLeft + this.viewportWidth, h - 0.5);
|
|
788
|
-
ctx.strokeStyle = "#e0e0e0";
|
|
789
|
-
ctx.stroke();
|
|
790
795
|
ctx.restore();
|
|
791
796
|
}
|
|
792
797
|
// Helper method to group consecutive blocks with same text
|
|
@@ -809,7 +814,7 @@ class GanttChart {
|
|
|
809
814
|
const ctx = this.mainCtx;
|
|
810
815
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
811
816
|
ctx.save();
|
|
812
|
-
ctx.translate(-this.scrollLeft, -this.scrollTop);
|
|
817
|
+
ctx.translate(-Math.round(this.scrollLeft), -Math.round(this.scrollTop));
|
|
813
818
|
const { start: startDate, end: endDate } = this.visibleDateRange;
|
|
814
819
|
this.drawGrid(ctx, startDate, endDate);
|
|
815
820
|
this.drawToday(ctx);
|
|
@@ -898,7 +903,7 @@ class GanttChart {
|
|
|
898
903
|
}
|
|
899
904
|
drawAllTasks(ctx) {
|
|
900
905
|
ctx.textBaseline = "middle";
|
|
901
|
-
ctx.font =
|
|
906
|
+
ctx.font = "12px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
902
907
|
ctx.textRendering = "optimizeSpeed";
|
|
903
908
|
ctx.imageSmoothingEnabled = false;
|
|
904
909
|
let visibleRowIndex = 0;
|
|
@@ -938,8 +943,9 @@ class GanttChart {
|
|
|
938
943
|
for (let i = 0; i <= visibleRowCount; i++) {
|
|
939
944
|
const y = i * this.config.rowHeight;
|
|
940
945
|
if (y < this.scrollTop || y > this.scrollTop + this.viewportHeight) continue;
|
|
941
|
-
|
|
942
|
-
ctx.
|
|
946
|
+
const sharpY = this.snap(y);
|
|
947
|
+
ctx.moveTo(this.scrollLeft, sharpY);
|
|
948
|
+
ctx.lineTo(this.scrollLeft + this.viewportWidth, sharpY);
|
|
943
949
|
}
|
|
944
950
|
}
|
|
945
951
|
if (this.config.showColLines) {
|
|
@@ -980,7 +986,7 @@ class GanttChart {
|
|
|
980
986
|
currentDate = nextDate;
|
|
981
987
|
}
|
|
982
988
|
while (currentDate <= endDate) {
|
|
983
|
-
const x = this.dateToX(currentDate);
|
|
989
|
+
const x = this.snap(this.dateToX(currentDate));
|
|
984
990
|
ctx.moveTo(x, this.scrollTop);
|
|
985
991
|
ctx.lineTo(x, this.scrollTop + this.viewportHeight);
|
|
986
992
|
switch (this.config.viewMode) {
|
|
@@ -1029,14 +1035,14 @@ class GanttChart {
|
|
|
1029
1035
|
const [offsetX_actual, percent_actual] = this.config.viewMode === "Day" && task.actualOffsetPercent ? task.actualOffsetPercent : [0, 1];
|
|
1030
1036
|
if (this.config.showActual && pos.x_actual_start) {
|
|
1031
1037
|
ctx.fillStyle = task.actualBgColor ? task.actualBgColor : this.config.actualBgColor;
|
|
1032
|
-
ctx.fillRect(pos.offset_x_actual_start, Math.round(taskY + 2), Math.round(pos.x_actual_width * percent_actual), Math.round(taskHeight - 2));
|
|
1038
|
+
ctx.fillRect(pos.offset_x_actual_start + this.config.xGap, Math.round(taskY + 2), Math.round(pos.x_actual_width * percent_actual - this.config.xGap), Math.round(taskHeight - 2));
|
|
1033
1039
|
}
|
|
1034
1040
|
if (this.config.showPlan && pos.x_plan_start && pos.x_plan_end) {
|
|
1035
1041
|
ctx.strokeStyle = task.planBorderColor ? task.planBorderColor : this.config.planBorderColor;
|
|
1036
1042
|
ctx.lineWidth = 4;
|
|
1037
1043
|
ctx.beginPath();
|
|
1038
|
-
ctx.moveTo(pos.offset_x_plan_start + offset / 2, taskY);
|
|
1039
|
-
ctx.lineTo(pos.offset_x_plan_end - offset / 2, taskY);
|
|
1044
|
+
ctx.moveTo(pos.offset_x_plan_start + offset / 2 + this.config.xGap, taskY);
|
|
1045
|
+
ctx.lineTo(pos.offset_x_plan_end - offset / 2 - this.config.xGap, taskY);
|
|
1040
1046
|
ctx.stroke();
|
|
1041
1047
|
}
|
|
1042
1048
|
ctx.fillStyle = "#000";
|
package/dist/index.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* gantt-canvas-chart v1.5.
|
|
2
|
+
* gantt-canvas-chart v1.5.4
|
|
3
3
|
* (c) 2025-present chandq
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
overflow: hidden;
|
|
11
11
|
position: relative;
|
|
12
12
|
background: white;
|
|
13
|
-
font-family:
|
|
13
|
+
font-family: Roboto, PingFang SC, Noto Sans SC, Microsoft YaHei UI, Microsoft YaHei, Segoe UI, Helvetica Neue,
|
|
14
|
+
Helvetica, Arial, sans-serif;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
.__gantt-chart-container {
|
|
@@ -19,7 +20,8 @@
|
|
|
19
20
|
position: relative;
|
|
20
21
|
background: #ffffff;
|
|
21
22
|
height: 100%;
|
|
22
|
-
font-family:
|
|
23
|
+
font-family: Roboto, PingFang SC, Noto Sans SC, Microsoft YaHei UI, Microsoft YaHei, Segoe UI, Helvetica Neue,
|
|
24
|
+
Helvetica, Arial, sans-serif;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
.__gantt-scroll-dummy {
|
package/dist/index.d.ts
CHANGED
|
@@ -118,6 +118,11 @@ export declare class GanttChart {
|
|
|
118
118
|
private updateVirtualRanges;
|
|
119
119
|
private updateDimensions;
|
|
120
120
|
private setupCanvas;
|
|
121
|
+
/**
|
|
122
|
+
* 辅助函数:像素对齐(确保MAC/Windows等不同设备上1px线条清晰)
|
|
123
|
+
* 用于 1px 线条,使其落在 x.5 位置,填满一个物理像素,避免模糊
|
|
124
|
+
*/
|
|
125
|
+
snap(val: number): number;
|
|
121
126
|
private calculateAllTaskPositions;
|
|
122
127
|
private getIterationStartDate;
|
|
123
128
|
render(scrolling?: boolean): void;
|
|
@@ -178,6 +183,7 @@ export declare interface GanttConfig {
|
|
|
178
183
|
offsetTop?: number;
|
|
179
184
|
offsetLeft?: number;
|
|
180
185
|
scrollEdgeThresholds?: number;
|
|
186
|
+
xGap?: number;
|
|
181
187
|
enabledLoadMore?: [LoadMoreDirection?, LoadMoreDirection?, LoadMoreDirection?];
|
|
182
188
|
viewFactors?: {
|
|
183
189
|
Day: 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.4
|
|
3
3
|
* (c) 2025-present chandq
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
@@ -195,6 +195,7 @@ class GanttChart {
|
|
|
195
195
|
offsetTop: 0,
|
|
196
196
|
offsetLeft: 0,
|
|
197
197
|
scrollEdgeThresholds: 10,
|
|
198
|
+
xGap: 0,
|
|
198
199
|
enabledLoadMore: [],
|
|
199
200
|
viewFactors: { Day: 80, Week: 20, Month: 15, Year: 6 },
|
|
200
201
|
planBorderColor: "#C1EFCF",
|
|
@@ -551,8 +552,17 @@ class GanttChart {
|
|
|
551
552
|
canvas.style.height = `${height}px`;
|
|
552
553
|
const ctx = canvas.getContext("2d");
|
|
553
554
|
ctx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
|
555
|
+
ctx.textBaseline = "middle";
|
|
556
|
+
ctx.imageSmoothingEnabled = false;
|
|
554
557
|
return ctx;
|
|
555
558
|
}
|
|
559
|
+
/**
|
|
560
|
+
* 辅助函数:像素对齐(确保MAC/Windows等不同设备上1px线条清晰)
|
|
561
|
+
* 用于 1px 线条,使其落在 x.5 位置,填满一个物理像素,避免模糊
|
|
562
|
+
*/
|
|
563
|
+
snap(val) {
|
|
564
|
+
return Math.floor(val) + 0.5;
|
|
565
|
+
}
|
|
556
566
|
calculateAllTaskPositions() {
|
|
557
567
|
this.taskPositions.clear();
|
|
558
568
|
let visibleRowIndex = 0;
|
|
@@ -638,9 +648,9 @@ class GanttChart {
|
|
|
638
648
|
const h = this.config.headerHeight;
|
|
639
649
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
640
650
|
ctx.save();
|
|
641
|
-
ctx.translate(-this.scrollLeft, 0);
|
|
651
|
+
ctx.translate(-Math.round(this.scrollLeft), 0);
|
|
642
652
|
ctx.fillStyle = this.config.headerBgColor;
|
|
643
|
-
ctx.fillRect(this.scrollLeft, 0, this.viewportWidth, h);
|
|
653
|
+
ctx.fillRect(Math.round(this.scrollLeft), 0, this.viewportWidth, h);
|
|
644
654
|
ctx.textBaseline = "middle";
|
|
645
655
|
ctx.textRendering = "optimizeLegibility";
|
|
646
656
|
let currentDate = new Date(this.visibleDateRange.start);
|
|
@@ -718,20 +728,20 @@ class GanttChart {
|
|
|
718
728
|
}
|
|
719
729
|
const groupedBlocks = this.groupConsecutiveBlocks(visibleBlocks);
|
|
720
730
|
ctx.fillStyle = "#333";
|
|
721
|
-
ctx.font =
|
|
731
|
+
ctx.font = "bold 14px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
722
732
|
ctx.textAlign = "left";
|
|
723
733
|
groupedBlocks.forEach((group) => {
|
|
724
734
|
const visibleStart = Math.max(group.startX, this.scrollLeft);
|
|
725
735
|
const visibleEnd = Math.min(group.endX, this.scrollLeft + this.viewportWidth);
|
|
726
736
|
if (visibleEnd > visibleStart) {
|
|
727
737
|
ctx.fillStyle = this.config.headerBgColor;
|
|
728
|
-
ctx.fillRect(visibleStart, 0, visibleEnd - visibleStart, h * 0.5);
|
|
738
|
+
ctx.fillRect(Math.round(visibleStart), 0, Math.round(visibleEnd - visibleStart), Math.round(h * 0.5));
|
|
729
739
|
ctx.fillStyle = "#333";
|
|
730
|
-
ctx.fillText(group.text, visibleStart + 5, group.yPos);
|
|
740
|
+
ctx.fillText(group.text, Math.round(visibleStart + 5), Math.round(group.yPos));
|
|
731
741
|
}
|
|
732
742
|
});
|
|
733
743
|
while (currentDateForLower <= this.visibleDateRange.end) {
|
|
734
|
-
const x = this.dateToX(currentDateForLower);
|
|
744
|
+
const x = this.snap(this.dateToX(currentDateForLower));
|
|
735
745
|
let lowerText = "";
|
|
736
746
|
let nextDate;
|
|
737
747
|
switch (this.config.viewMode) {
|
|
@@ -766,7 +776,7 @@ class GanttChart {
|
|
|
766
776
|
}
|
|
767
777
|
const unitWidth = this.dateToX(nextDate) - x;
|
|
768
778
|
ctx.fillStyle = "#000412";
|
|
769
|
-
ctx.font =
|
|
779
|
+
ctx.font = "14px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
770
780
|
ctx.textAlign = "center";
|
|
771
781
|
ctx.fillText(lowerText, Math.round(x + unitWidth / 2), Math.round(h * 0.7));
|
|
772
782
|
ctx.beginPath();
|
|
@@ -780,11 +790,6 @@ class GanttChart {
|
|
|
780
790
|
currentDateForLower = nextDate;
|
|
781
791
|
}
|
|
782
792
|
}
|
|
783
|
-
ctx.beginPath();
|
|
784
|
-
ctx.moveTo(this.scrollLeft, h - 0.5);
|
|
785
|
-
ctx.lineTo(this.scrollLeft + this.viewportWidth, h - 0.5);
|
|
786
|
-
ctx.strokeStyle = "#e0e0e0";
|
|
787
|
-
ctx.stroke();
|
|
788
793
|
ctx.restore();
|
|
789
794
|
}
|
|
790
795
|
// Helper method to group consecutive blocks with same text
|
|
@@ -807,7 +812,7 @@ class GanttChart {
|
|
|
807
812
|
const ctx = this.mainCtx;
|
|
808
813
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
809
814
|
ctx.save();
|
|
810
|
-
ctx.translate(-this.scrollLeft, -this.scrollTop);
|
|
815
|
+
ctx.translate(-Math.round(this.scrollLeft), -Math.round(this.scrollTop));
|
|
811
816
|
const { start: startDate, end: endDate } = this.visibleDateRange;
|
|
812
817
|
this.drawGrid(ctx, startDate, endDate);
|
|
813
818
|
this.drawToday(ctx);
|
|
@@ -896,7 +901,7 @@ class GanttChart {
|
|
|
896
901
|
}
|
|
897
902
|
drawAllTasks(ctx) {
|
|
898
903
|
ctx.textBaseline = "middle";
|
|
899
|
-
ctx.font =
|
|
904
|
+
ctx.font = "12px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
900
905
|
ctx.textRendering = "optimizeSpeed";
|
|
901
906
|
ctx.imageSmoothingEnabled = false;
|
|
902
907
|
let visibleRowIndex = 0;
|
|
@@ -936,8 +941,9 @@ class GanttChart {
|
|
|
936
941
|
for (let i = 0; i <= visibleRowCount; i++) {
|
|
937
942
|
const y = i * this.config.rowHeight;
|
|
938
943
|
if (y < this.scrollTop || y > this.scrollTop + this.viewportHeight) continue;
|
|
939
|
-
|
|
940
|
-
ctx.
|
|
944
|
+
const sharpY = this.snap(y);
|
|
945
|
+
ctx.moveTo(this.scrollLeft, sharpY);
|
|
946
|
+
ctx.lineTo(this.scrollLeft + this.viewportWidth, sharpY);
|
|
941
947
|
}
|
|
942
948
|
}
|
|
943
949
|
if (this.config.showColLines) {
|
|
@@ -978,7 +984,7 @@ class GanttChart {
|
|
|
978
984
|
currentDate = nextDate;
|
|
979
985
|
}
|
|
980
986
|
while (currentDate <= endDate) {
|
|
981
|
-
const x = this.dateToX(currentDate);
|
|
987
|
+
const x = this.snap(this.dateToX(currentDate));
|
|
982
988
|
ctx.moveTo(x, this.scrollTop);
|
|
983
989
|
ctx.lineTo(x, this.scrollTop + this.viewportHeight);
|
|
984
990
|
switch (this.config.viewMode) {
|
|
@@ -1027,14 +1033,14 @@ class GanttChart {
|
|
|
1027
1033
|
const [offsetX_actual, percent_actual] = this.config.viewMode === "Day" && task.actualOffsetPercent ? task.actualOffsetPercent : [0, 1];
|
|
1028
1034
|
if (this.config.showActual && pos.x_actual_start) {
|
|
1029
1035
|
ctx.fillStyle = task.actualBgColor ? task.actualBgColor : this.config.actualBgColor;
|
|
1030
|
-
ctx.fillRect(pos.offset_x_actual_start, Math.round(taskY + 2), Math.round(pos.x_actual_width * percent_actual), Math.round(taskHeight - 2));
|
|
1036
|
+
ctx.fillRect(pos.offset_x_actual_start + this.config.xGap, Math.round(taskY + 2), Math.round(pos.x_actual_width * percent_actual - this.config.xGap), Math.round(taskHeight - 2));
|
|
1031
1037
|
}
|
|
1032
1038
|
if (this.config.showPlan && pos.x_plan_start && pos.x_plan_end) {
|
|
1033
1039
|
ctx.strokeStyle = task.planBorderColor ? task.planBorderColor : this.config.planBorderColor;
|
|
1034
1040
|
ctx.lineWidth = 4;
|
|
1035
1041
|
ctx.beginPath();
|
|
1036
|
-
ctx.moveTo(pos.offset_x_plan_start + offset / 2, taskY);
|
|
1037
|
-
ctx.lineTo(pos.offset_x_plan_end - offset / 2, taskY);
|
|
1042
|
+
ctx.moveTo(pos.offset_x_plan_start + offset / 2 + this.config.xGap, taskY);
|
|
1043
|
+
ctx.lineTo(pos.offset_x_plan_end - offset / 2 - this.config.xGap, taskY);
|
|
1038
1044
|
ctx.stroke();
|
|
1039
1045
|
}
|
|
1040
1046
|
ctx.fillStyle = "#000";
|
package/dist/index.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* gantt-canvas-chart v1.5.
|
|
2
|
+
* gantt-canvas-chart v1.5.4
|
|
3
3
|
* (c) 2025-present chandq
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
@@ -199,6 +199,7 @@
|
|
|
199
199
|
offsetTop: 0,
|
|
200
200
|
offsetLeft: 0,
|
|
201
201
|
scrollEdgeThresholds: 10,
|
|
202
|
+
xGap: 0,
|
|
202
203
|
enabledLoadMore: [],
|
|
203
204
|
viewFactors: { Day: 80, Week: 20, Month: 15, Year: 6 },
|
|
204
205
|
planBorderColor: "#C1EFCF",
|
|
@@ -555,8 +556,17 @@
|
|
|
555
556
|
canvas.style.height = `${height}px`;
|
|
556
557
|
const ctx = canvas.getContext("2d");
|
|
557
558
|
ctx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
|
559
|
+
ctx.textBaseline = "middle";
|
|
560
|
+
ctx.imageSmoothingEnabled = false;
|
|
558
561
|
return ctx;
|
|
559
562
|
}
|
|
563
|
+
/**
|
|
564
|
+
* 辅助函数:像素对齐(确保MAC/Windows等不同设备上1px线条清晰)
|
|
565
|
+
* 用于 1px 线条,使其落在 x.5 位置,填满一个物理像素,避免模糊
|
|
566
|
+
*/
|
|
567
|
+
snap(val) {
|
|
568
|
+
return Math.floor(val) + 0.5;
|
|
569
|
+
}
|
|
560
570
|
calculateAllTaskPositions() {
|
|
561
571
|
this.taskPositions.clear();
|
|
562
572
|
let visibleRowIndex = 0;
|
|
@@ -642,9 +652,9 @@
|
|
|
642
652
|
const h = this.config.headerHeight;
|
|
643
653
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
644
654
|
ctx.save();
|
|
645
|
-
ctx.translate(-this.scrollLeft, 0);
|
|
655
|
+
ctx.translate(-Math.round(this.scrollLeft), 0);
|
|
646
656
|
ctx.fillStyle = this.config.headerBgColor;
|
|
647
|
-
ctx.fillRect(this.scrollLeft, 0, this.viewportWidth, h);
|
|
657
|
+
ctx.fillRect(Math.round(this.scrollLeft), 0, this.viewportWidth, h);
|
|
648
658
|
ctx.textBaseline = "middle";
|
|
649
659
|
ctx.textRendering = "optimizeLegibility";
|
|
650
660
|
let currentDate = new Date(this.visibleDateRange.start);
|
|
@@ -722,20 +732,20 @@
|
|
|
722
732
|
}
|
|
723
733
|
const groupedBlocks = this.groupConsecutiveBlocks(visibleBlocks);
|
|
724
734
|
ctx.fillStyle = "#333";
|
|
725
|
-
ctx.font =
|
|
735
|
+
ctx.font = "bold 14px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
726
736
|
ctx.textAlign = "left";
|
|
727
737
|
groupedBlocks.forEach((group) => {
|
|
728
738
|
const visibleStart = Math.max(group.startX, this.scrollLeft);
|
|
729
739
|
const visibleEnd = Math.min(group.endX, this.scrollLeft + this.viewportWidth);
|
|
730
740
|
if (visibleEnd > visibleStart) {
|
|
731
741
|
ctx.fillStyle = this.config.headerBgColor;
|
|
732
|
-
ctx.fillRect(visibleStart, 0, visibleEnd - visibleStart, h * 0.5);
|
|
742
|
+
ctx.fillRect(Math.round(visibleStart), 0, Math.round(visibleEnd - visibleStart), Math.round(h * 0.5));
|
|
733
743
|
ctx.fillStyle = "#333";
|
|
734
|
-
ctx.fillText(group.text, visibleStart + 5, group.yPos);
|
|
744
|
+
ctx.fillText(group.text, Math.round(visibleStart + 5), Math.round(group.yPos));
|
|
735
745
|
}
|
|
736
746
|
});
|
|
737
747
|
while (currentDateForLower <= this.visibleDateRange.end) {
|
|
738
|
-
const x = this.dateToX(currentDateForLower);
|
|
748
|
+
const x = this.snap(this.dateToX(currentDateForLower));
|
|
739
749
|
let lowerText = "";
|
|
740
750
|
let nextDate;
|
|
741
751
|
switch (this.config.viewMode) {
|
|
@@ -770,7 +780,7 @@
|
|
|
770
780
|
}
|
|
771
781
|
const unitWidth = this.dateToX(nextDate) - x;
|
|
772
782
|
ctx.fillStyle = "#000412";
|
|
773
|
-
ctx.font =
|
|
783
|
+
ctx.font = "14px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
774
784
|
ctx.textAlign = "center";
|
|
775
785
|
ctx.fillText(lowerText, Math.round(x + unitWidth / 2), Math.round(h * 0.7));
|
|
776
786
|
ctx.beginPath();
|
|
@@ -784,11 +794,6 @@
|
|
|
784
794
|
currentDateForLower = nextDate;
|
|
785
795
|
}
|
|
786
796
|
}
|
|
787
|
-
ctx.beginPath();
|
|
788
|
-
ctx.moveTo(this.scrollLeft, h - 0.5);
|
|
789
|
-
ctx.lineTo(this.scrollLeft + this.viewportWidth, h - 0.5);
|
|
790
|
-
ctx.strokeStyle = "#e0e0e0";
|
|
791
|
-
ctx.stroke();
|
|
792
797
|
ctx.restore();
|
|
793
798
|
}
|
|
794
799
|
// Helper method to group consecutive blocks with same text
|
|
@@ -811,7 +816,7 @@
|
|
|
811
816
|
const ctx = this.mainCtx;
|
|
812
817
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
813
818
|
ctx.save();
|
|
814
|
-
ctx.translate(-this.scrollLeft, -this.scrollTop);
|
|
819
|
+
ctx.translate(-Math.round(this.scrollLeft), -Math.round(this.scrollTop));
|
|
815
820
|
const { start: startDate, end: endDate } = this.visibleDateRange;
|
|
816
821
|
this.drawGrid(ctx, startDate, endDate);
|
|
817
822
|
this.drawToday(ctx);
|
|
@@ -900,7 +905,7 @@
|
|
|
900
905
|
}
|
|
901
906
|
drawAllTasks(ctx) {
|
|
902
907
|
ctx.textBaseline = "middle";
|
|
903
|
-
ctx.font =
|
|
908
|
+
ctx.font = "12px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
904
909
|
ctx.textRendering = "optimizeSpeed";
|
|
905
910
|
ctx.imageSmoothingEnabled = false;
|
|
906
911
|
let visibleRowIndex = 0;
|
|
@@ -940,8 +945,9 @@
|
|
|
940
945
|
for (let i = 0; i <= visibleRowCount; i++) {
|
|
941
946
|
const y = i * this.config.rowHeight;
|
|
942
947
|
if (y < this.scrollTop || y > this.scrollTop + this.viewportHeight) continue;
|
|
943
|
-
|
|
944
|
-
ctx.
|
|
948
|
+
const sharpY = this.snap(y);
|
|
949
|
+
ctx.moveTo(this.scrollLeft, sharpY);
|
|
950
|
+
ctx.lineTo(this.scrollLeft + this.viewportWidth, sharpY);
|
|
945
951
|
}
|
|
946
952
|
}
|
|
947
953
|
if (this.config.showColLines) {
|
|
@@ -982,7 +988,7 @@
|
|
|
982
988
|
currentDate = nextDate;
|
|
983
989
|
}
|
|
984
990
|
while (currentDate <= endDate) {
|
|
985
|
-
const x = this.dateToX(currentDate);
|
|
991
|
+
const x = this.snap(this.dateToX(currentDate));
|
|
986
992
|
ctx.moveTo(x, this.scrollTop);
|
|
987
993
|
ctx.lineTo(x, this.scrollTop + this.viewportHeight);
|
|
988
994
|
switch (this.config.viewMode) {
|
|
@@ -1031,14 +1037,14 @@
|
|
|
1031
1037
|
const [offsetX_actual, percent_actual] = this.config.viewMode === "Day" && task.actualOffsetPercent ? task.actualOffsetPercent : [0, 1];
|
|
1032
1038
|
if (this.config.showActual && pos.x_actual_start) {
|
|
1033
1039
|
ctx.fillStyle = task.actualBgColor ? task.actualBgColor : this.config.actualBgColor;
|
|
1034
|
-
ctx.fillRect(pos.offset_x_actual_start, Math.round(taskY + 2), Math.round(pos.x_actual_width * percent_actual), Math.round(taskHeight - 2));
|
|
1040
|
+
ctx.fillRect(pos.offset_x_actual_start + this.config.xGap, Math.round(taskY + 2), Math.round(pos.x_actual_width * percent_actual - this.config.xGap), Math.round(taskHeight - 2));
|
|
1035
1041
|
}
|
|
1036
1042
|
if (this.config.showPlan && pos.x_plan_start && pos.x_plan_end) {
|
|
1037
1043
|
ctx.strokeStyle = task.planBorderColor ? task.planBorderColor : this.config.planBorderColor;
|
|
1038
1044
|
ctx.lineWidth = 4;
|
|
1039
1045
|
ctx.beginPath();
|
|
1040
|
-
ctx.moveTo(pos.offset_x_plan_start + offset / 2, taskY);
|
|
1041
|
-
ctx.lineTo(pos.offset_x_plan_end - offset / 2, taskY);
|
|
1046
|
+
ctx.moveTo(pos.offset_x_plan_start + offset / 2 + this.config.xGap, taskY);
|
|
1047
|
+
ctx.lineTo(pos.offset_x_plan_end - offset / 2 - this.config.xGap, taskY);
|
|
1042
1048
|
ctx.stroke();
|
|
1043
1049
|
}
|
|
1044
1050
|
ctx.fillStyle = "#000";
|