gantt-canvas-chart 1.5.3 → 1.6.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 +58 -21
- package/dist/index.css +5 -3
- package/dist/index.d.ts +11 -0
- package/dist/index.es.js +58 -21
- package/dist/index.umd.js +58 -21
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* gantt-canvas-chart v1.
|
|
2
|
+
* gantt-canvas-chart v1.6.0
|
|
3
3
|
* (c) 2025-present chandq
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
@@ -146,6 +146,7 @@ class GanttChart {
|
|
|
146
146
|
resizeObserver;
|
|
147
147
|
taskPositions;
|
|
148
148
|
taskMap;
|
|
149
|
+
holidaysMap;
|
|
149
150
|
isLoadingData = false;
|
|
150
151
|
hasMoreDataLeft = true;
|
|
151
152
|
hasMoreDataRight = true;
|
|
@@ -194,9 +195,13 @@ class GanttChart {
|
|
|
194
195
|
tooltipFormat: null,
|
|
195
196
|
tooltipColor: "black",
|
|
196
197
|
todayColor: "#ff4d4f",
|
|
198
|
+
weekendBgColor: "#f7f7f7",
|
|
199
|
+
holidays: [],
|
|
200
|
+
dateSeparator: "/",
|
|
197
201
|
offsetTop: 0,
|
|
198
202
|
offsetLeft: 0,
|
|
199
203
|
scrollEdgeThresholds: 10,
|
|
204
|
+
xGap: 0,
|
|
200
205
|
enabledLoadMore: [],
|
|
201
206
|
viewFactors: { Day: 80, Week: 20, Month: 15, Year: 6 },
|
|
202
207
|
planBorderColor: "#C1EFCF",
|
|
@@ -231,6 +236,7 @@ class GanttChart {
|
|
|
231
236
|
this.totalHeight = 0;
|
|
232
237
|
this.taskPositions = /* @__PURE__ */ new Map();
|
|
233
238
|
this.taskMap = /* @__PURE__ */ new Map();
|
|
239
|
+
this.holidaysMap = /* @__PURE__ */ new Map();
|
|
234
240
|
this.handleMouseMove = this.handleMouseMove.bind(this);
|
|
235
241
|
this.handleMouseLeave = this.handleMouseLeave.bind(this);
|
|
236
242
|
this.handleScroll = this.handleScroll.bind(this);
|
|
@@ -240,6 +246,9 @@ class GanttChart {
|
|
|
240
246
|
this.init();
|
|
241
247
|
}
|
|
242
248
|
init() {
|
|
249
|
+
if (this.config.holidays.length > 0) {
|
|
250
|
+
this.buildHolidaysMap();
|
|
251
|
+
}
|
|
243
252
|
this.buildTaskMap();
|
|
244
253
|
this.updatePixelsPerDay();
|
|
245
254
|
this.calculateFullTimeline();
|
|
@@ -288,10 +297,26 @@ class GanttChart {
|
|
|
288
297
|
this.updatePixelsPerDay();
|
|
289
298
|
this.calculateFullTimeline();
|
|
290
299
|
}
|
|
300
|
+
if (this.config.holidays.length !== this.holidaysMap.size) {
|
|
301
|
+
this.buildHolidaysMap();
|
|
302
|
+
}
|
|
291
303
|
this.updateLoadMoreConf();
|
|
292
304
|
this.updateDimensions();
|
|
293
305
|
this.render();
|
|
294
306
|
}
|
|
307
|
+
buildHolidaysMap() {
|
|
308
|
+
this.holidaysMap.clear();
|
|
309
|
+
const separator = this.config.dateSeparator;
|
|
310
|
+
if (this.config.holidays && this.config.holidays.length > 0 && this.config.holidays[0].includes(separator)) {
|
|
311
|
+
this.config.holidays.forEach((holiday) => {
|
|
312
|
+
this.holidaysMap.set(holiday, true);
|
|
313
|
+
});
|
|
314
|
+
} else {
|
|
315
|
+
this.config.holidays.forEach((holiday) => {
|
|
316
|
+
this.holidaysMap.set(DateUtils.format(new Date(holiday), `yyyy${separator}MM${separator}dd`), true);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
295
320
|
setData(newData, newConfig) {
|
|
296
321
|
this.data = newData;
|
|
297
322
|
this.buildTaskMap();
|
|
@@ -553,8 +578,17 @@ class GanttChart {
|
|
|
553
578
|
canvas.style.height = `${height}px`;
|
|
554
579
|
const ctx = canvas.getContext("2d");
|
|
555
580
|
ctx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
|
581
|
+
ctx.textBaseline = "middle";
|
|
582
|
+
ctx.imageSmoothingEnabled = false;
|
|
556
583
|
return ctx;
|
|
557
584
|
}
|
|
585
|
+
/**
|
|
586
|
+
* 辅助函数:像素对齐(确保MAC/Windows等不同设备上1px线条清晰)
|
|
587
|
+
* 用于 1px 线条,使其落在 x.5 位置,填满一个物理像素,避免模糊
|
|
588
|
+
*/
|
|
589
|
+
snap(val) {
|
|
590
|
+
return Math.floor(val) + 0.5;
|
|
591
|
+
}
|
|
558
592
|
calculateAllTaskPositions() {
|
|
559
593
|
this.taskPositions.clear();
|
|
560
594
|
let visibleRowIndex = 0;
|
|
@@ -640,9 +674,9 @@ class GanttChart {
|
|
|
640
674
|
const h = this.config.headerHeight;
|
|
641
675
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
642
676
|
ctx.save();
|
|
643
|
-
ctx.translate(-this.scrollLeft, 0);
|
|
677
|
+
ctx.translate(-Math.round(this.scrollLeft), 0);
|
|
644
678
|
ctx.fillStyle = this.config.headerBgColor;
|
|
645
|
-
ctx.fillRect(this.scrollLeft, 0, this.viewportWidth, h);
|
|
679
|
+
ctx.fillRect(Math.round(this.scrollLeft), 0, this.viewportWidth, h);
|
|
646
680
|
ctx.textBaseline = "middle";
|
|
647
681
|
ctx.textRendering = "optimizeLegibility";
|
|
648
682
|
let currentDate = new Date(this.visibleDateRange.start);
|
|
@@ -720,20 +754,20 @@ class GanttChart {
|
|
|
720
754
|
}
|
|
721
755
|
const groupedBlocks = this.groupConsecutiveBlocks(visibleBlocks);
|
|
722
756
|
ctx.fillStyle = "#333";
|
|
723
|
-
ctx.font =
|
|
757
|
+
ctx.font = "bold 14px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
724
758
|
ctx.textAlign = "left";
|
|
725
759
|
groupedBlocks.forEach((group) => {
|
|
726
760
|
const visibleStart = Math.max(group.startX, this.scrollLeft);
|
|
727
761
|
const visibleEnd = Math.min(group.endX, this.scrollLeft + this.viewportWidth);
|
|
728
762
|
if (visibleEnd > visibleStart) {
|
|
729
763
|
ctx.fillStyle = this.config.headerBgColor;
|
|
730
|
-
ctx.fillRect(visibleStart, 0, visibleEnd - visibleStart, h * 0.5);
|
|
764
|
+
ctx.fillRect(Math.round(visibleStart), 0, Math.round(visibleEnd - visibleStart), Math.round(h * 0.5));
|
|
731
765
|
ctx.fillStyle = "#333";
|
|
732
|
-
ctx.fillText(group.text, visibleStart + 5, group.yPos);
|
|
766
|
+
ctx.fillText(group.text, Math.round(visibleStart + 5), Math.round(group.yPos));
|
|
733
767
|
}
|
|
734
768
|
});
|
|
735
769
|
while (currentDateForLower <= this.visibleDateRange.end) {
|
|
736
|
-
const x = this.dateToX(currentDateForLower);
|
|
770
|
+
const x = this.snap(this.dateToX(currentDateForLower));
|
|
737
771
|
let lowerText = "";
|
|
738
772
|
let nextDate;
|
|
739
773
|
switch (this.config.viewMode) {
|
|
@@ -768,7 +802,7 @@ class GanttChart {
|
|
|
768
802
|
}
|
|
769
803
|
const unitWidth = this.dateToX(nextDate) - x;
|
|
770
804
|
ctx.fillStyle = "#000412";
|
|
771
|
-
ctx.font =
|
|
805
|
+
ctx.font = "14px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
772
806
|
ctx.textAlign = "center";
|
|
773
807
|
ctx.fillText(lowerText, Math.round(x + unitWidth / 2), Math.round(h * 0.7));
|
|
774
808
|
ctx.beginPath();
|
|
@@ -782,11 +816,6 @@ class GanttChart {
|
|
|
782
816
|
currentDateForLower = nextDate;
|
|
783
817
|
}
|
|
784
818
|
}
|
|
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
819
|
ctx.restore();
|
|
791
820
|
}
|
|
792
821
|
// Helper method to group consecutive blocks with same text
|
|
@@ -809,7 +838,7 @@ class GanttChart {
|
|
|
809
838
|
const ctx = this.mainCtx;
|
|
810
839
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
811
840
|
ctx.save();
|
|
812
|
-
ctx.translate(-this.scrollLeft, -this.scrollTop);
|
|
841
|
+
ctx.translate(-Math.round(this.scrollLeft), -Math.round(this.scrollTop));
|
|
813
842
|
const { start: startDate, end: endDate } = this.visibleDateRange;
|
|
814
843
|
this.drawGrid(ctx, startDate, endDate);
|
|
815
844
|
this.drawToday(ctx);
|
|
@@ -898,7 +927,7 @@ class GanttChart {
|
|
|
898
927
|
}
|
|
899
928
|
drawAllTasks(ctx) {
|
|
900
929
|
ctx.textBaseline = "middle";
|
|
901
|
-
ctx.font =
|
|
930
|
+
ctx.font = "12px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
902
931
|
ctx.textRendering = "optimizeSpeed";
|
|
903
932
|
ctx.imageSmoothingEnabled = false;
|
|
904
933
|
let visibleRowIndex = 0;
|
|
@@ -925,6 +954,7 @@ class GanttChart {
|
|
|
925
954
|
}
|
|
926
955
|
// In the drawGrid method
|
|
927
956
|
drawGrid(ctx, startDate, endDate) {
|
|
957
|
+
const separator = this.config.dateSeparator;
|
|
928
958
|
ctx.strokeStyle = "#e6e6e6";
|
|
929
959
|
ctx.lineWidth = 1;
|
|
930
960
|
ctx.beginPath();
|
|
@@ -938,8 +968,9 @@ class GanttChart {
|
|
|
938
968
|
for (let i = 0; i <= visibleRowCount; i++) {
|
|
939
969
|
const y = i * this.config.rowHeight;
|
|
940
970
|
if (y < this.scrollTop || y > this.scrollTop + this.viewportHeight) continue;
|
|
941
|
-
|
|
942
|
-
ctx.
|
|
971
|
+
const sharpY = this.snap(y);
|
|
972
|
+
ctx.moveTo(this.scrollLeft, sharpY);
|
|
973
|
+
ctx.lineTo(this.scrollLeft + this.viewportWidth, sharpY);
|
|
943
974
|
}
|
|
944
975
|
}
|
|
945
976
|
if (this.config.showColLines) {
|
|
@@ -980,9 +1011,15 @@ class GanttChart {
|
|
|
980
1011
|
currentDate = nextDate;
|
|
981
1012
|
}
|
|
982
1013
|
while (currentDate <= endDate) {
|
|
983
|
-
const x = this.dateToX(currentDate);
|
|
1014
|
+
const x = this.snap(this.dateToX(currentDate));
|
|
984
1015
|
ctx.moveTo(x, this.scrollTop);
|
|
985
1016
|
ctx.lineTo(x, this.scrollTop + this.viewportHeight);
|
|
1017
|
+
if (this.config.viewMode === "Day") {
|
|
1018
|
+
if ([0, 6].includes(currentDate.getDay()) || this.holidaysMap.has(DateUtils.format(currentDate, `yyyy${separator}MM${separator}dd`))) {
|
|
1019
|
+
ctx.fillStyle = this.config.weekendBgColor;
|
|
1020
|
+
ctx.fillRect(x + 1, this.scrollTop, Math.round(this.pixelsPerDay - 1), Math.round(this.scrollTop + this.viewportHeight));
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
986
1023
|
switch (this.config.viewMode) {
|
|
987
1024
|
case "Day":
|
|
988
1025
|
nextDate = DateUtils.addDays(currentDate, 1);
|
|
@@ -1029,14 +1066,14 @@ class GanttChart {
|
|
|
1029
1066
|
const [offsetX_actual, percent_actual] = this.config.viewMode === "Day" && task.actualOffsetPercent ? task.actualOffsetPercent : [0, 1];
|
|
1030
1067
|
if (this.config.showActual && pos.x_actual_start) {
|
|
1031
1068
|
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));
|
|
1069
|
+
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
1070
|
}
|
|
1034
1071
|
if (this.config.showPlan && pos.x_plan_start && pos.x_plan_end) {
|
|
1035
1072
|
ctx.strokeStyle = task.planBorderColor ? task.planBorderColor : this.config.planBorderColor;
|
|
1036
1073
|
ctx.lineWidth = 4;
|
|
1037
1074
|
ctx.beginPath();
|
|
1038
|
-
ctx.moveTo(pos.offset_x_plan_start + offset / 2, taskY);
|
|
1039
|
-
ctx.lineTo(pos.offset_x_plan_end - offset / 2, taskY);
|
|
1075
|
+
ctx.moveTo(pos.offset_x_plan_start + offset / 2 + this.config.xGap, taskY);
|
|
1076
|
+
ctx.lineTo(pos.offset_x_plan_end - offset / 2 - this.config.xGap, taskY);
|
|
1040
1077
|
ctx.stroke();
|
|
1041
1078
|
}
|
|
1042
1079
|
ctx.fillStyle = "#000";
|
package/dist/index.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* gantt-canvas-chart v1.
|
|
2
|
+
* gantt-canvas-chart v1.6.0
|
|
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
|
@@ -85,6 +85,7 @@ export declare class GanttChart {
|
|
|
85
85
|
row: number;
|
|
86
86
|
task: Task;
|
|
87
87
|
}>;
|
|
88
|
+
holidaysMap: Map<string, boolean>;
|
|
88
89
|
private isLoadingData;
|
|
89
90
|
private hasMoreDataLeft;
|
|
90
91
|
private hasMoreDataRight;
|
|
@@ -99,6 +100,7 @@ export declare class GanttChart {
|
|
|
99
100
|
private buildTaskMap;
|
|
100
101
|
private setupEvents;
|
|
101
102
|
updateConfig(newConfig: GanttConfig): void;
|
|
103
|
+
private buildHolidaysMap;
|
|
102
104
|
setData(newData: GanttData, newConfig?: GanttConfig): void;
|
|
103
105
|
destroy(): void;
|
|
104
106
|
private calculateFullTimeline;
|
|
@@ -118,6 +120,11 @@ export declare class GanttChart {
|
|
|
118
120
|
private updateVirtualRanges;
|
|
119
121
|
private updateDimensions;
|
|
120
122
|
private setupCanvas;
|
|
123
|
+
/**
|
|
124
|
+
* 辅助函数:像素对齐(确保MAC/Windows等不同设备上1px线条清晰)
|
|
125
|
+
* 用于 1px 线条,使其落在 x.5 位置,填满一个物理像素,避免模糊
|
|
126
|
+
*/
|
|
127
|
+
snap(val: number): number;
|
|
121
128
|
private calculateAllTaskPositions;
|
|
122
129
|
private getIterationStartDate;
|
|
123
130
|
render(scrolling?: boolean): void;
|
|
@@ -175,9 +182,13 @@ export declare interface GanttConfig {
|
|
|
175
182
|
showTooltip?: boolean;
|
|
176
183
|
tooltipColor?: 'black' | 'white';
|
|
177
184
|
todayColor?: string;
|
|
185
|
+
weekendBgColor?: string;
|
|
186
|
+
holidays?: string[];
|
|
187
|
+
dateSeparator?: string;
|
|
178
188
|
offsetTop?: number;
|
|
179
189
|
offsetLeft?: number;
|
|
180
190
|
scrollEdgeThresholds?: number;
|
|
191
|
+
xGap?: number;
|
|
181
192
|
enabledLoadMore?: [LoadMoreDirection?, LoadMoreDirection?, LoadMoreDirection?];
|
|
182
193
|
viewFactors?: {
|
|
183
194
|
Day: number;
|
package/dist/index.es.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* gantt-canvas-chart v1.
|
|
2
|
+
* gantt-canvas-chart v1.6.0
|
|
3
3
|
* (c) 2025-present chandq
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
@@ -144,6 +144,7 @@ class GanttChart {
|
|
|
144
144
|
resizeObserver;
|
|
145
145
|
taskPositions;
|
|
146
146
|
taskMap;
|
|
147
|
+
holidaysMap;
|
|
147
148
|
isLoadingData = false;
|
|
148
149
|
hasMoreDataLeft = true;
|
|
149
150
|
hasMoreDataRight = true;
|
|
@@ -192,9 +193,13 @@ class GanttChart {
|
|
|
192
193
|
tooltipFormat: null,
|
|
193
194
|
tooltipColor: "black",
|
|
194
195
|
todayColor: "#ff4d4f",
|
|
196
|
+
weekendBgColor: "#f7f7f7",
|
|
197
|
+
holidays: [],
|
|
198
|
+
dateSeparator: "/",
|
|
195
199
|
offsetTop: 0,
|
|
196
200
|
offsetLeft: 0,
|
|
197
201
|
scrollEdgeThresholds: 10,
|
|
202
|
+
xGap: 0,
|
|
198
203
|
enabledLoadMore: [],
|
|
199
204
|
viewFactors: { Day: 80, Week: 20, Month: 15, Year: 6 },
|
|
200
205
|
planBorderColor: "#C1EFCF",
|
|
@@ -229,6 +234,7 @@ class GanttChart {
|
|
|
229
234
|
this.totalHeight = 0;
|
|
230
235
|
this.taskPositions = /* @__PURE__ */ new Map();
|
|
231
236
|
this.taskMap = /* @__PURE__ */ new Map();
|
|
237
|
+
this.holidaysMap = /* @__PURE__ */ new Map();
|
|
232
238
|
this.handleMouseMove = this.handleMouseMove.bind(this);
|
|
233
239
|
this.handleMouseLeave = this.handleMouseLeave.bind(this);
|
|
234
240
|
this.handleScroll = this.handleScroll.bind(this);
|
|
@@ -238,6 +244,9 @@ class GanttChart {
|
|
|
238
244
|
this.init();
|
|
239
245
|
}
|
|
240
246
|
init() {
|
|
247
|
+
if (this.config.holidays.length > 0) {
|
|
248
|
+
this.buildHolidaysMap();
|
|
249
|
+
}
|
|
241
250
|
this.buildTaskMap();
|
|
242
251
|
this.updatePixelsPerDay();
|
|
243
252
|
this.calculateFullTimeline();
|
|
@@ -286,10 +295,26 @@ class GanttChart {
|
|
|
286
295
|
this.updatePixelsPerDay();
|
|
287
296
|
this.calculateFullTimeline();
|
|
288
297
|
}
|
|
298
|
+
if (this.config.holidays.length !== this.holidaysMap.size) {
|
|
299
|
+
this.buildHolidaysMap();
|
|
300
|
+
}
|
|
289
301
|
this.updateLoadMoreConf();
|
|
290
302
|
this.updateDimensions();
|
|
291
303
|
this.render();
|
|
292
304
|
}
|
|
305
|
+
buildHolidaysMap() {
|
|
306
|
+
this.holidaysMap.clear();
|
|
307
|
+
const separator = this.config.dateSeparator;
|
|
308
|
+
if (this.config.holidays && this.config.holidays.length > 0 && this.config.holidays[0].includes(separator)) {
|
|
309
|
+
this.config.holidays.forEach((holiday) => {
|
|
310
|
+
this.holidaysMap.set(holiday, true);
|
|
311
|
+
});
|
|
312
|
+
} else {
|
|
313
|
+
this.config.holidays.forEach((holiday) => {
|
|
314
|
+
this.holidaysMap.set(DateUtils.format(new Date(holiday), `yyyy${separator}MM${separator}dd`), true);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
293
318
|
setData(newData, newConfig) {
|
|
294
319
|
this.data = newData;
|
|
295
320
|
this.buildTaskMap();
|
|
@@ -551,8 +576,17 @@ class GanttChart {
|
|
|
551
576
|
canvas.style.height = `${height}px`;
|
|
552
577
|
const ctx = canvas.getContext("2d");
|
|
553
578
|
ctx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
|
579
|
+
ctx.textBaseline = "middle";
|
|
580
|
+
ctx.imageSmoothingEnabled = false;
|
|
554
581
|
return ctx;
|
|
555
582
|
}
|
|
583
|
+
/**
|
|
584
|
+
* 辅助函数:像素对齐(确保MAC/Windows等不同设备上1px线条清晰)
|
|
585
|
+
* 用于 1px 线条,使其落在 x.5 位置,填满一个物理像素,避免模糊
|
|
586
|
+
*/
|
|
587
|
+
snap(val) {
|
|
588
|
+
return Math.floor(val) + 0.5;
|
|
589
|
+
}
|
|
556
590
|
calculateAllTaskPositions() {
|
|
557
591
|
this.taskPositions.clear();
|
|
558
592
|
let visibleRowIndex = 0;
|
|
@@ -638,9 +672,9 @@ class GanttChart {
|
|
|
638
672
|
const h = this.config.headerHeight;
|
|
639
673
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
640
674
|
ctx.save();
|
|
641
|
-
ctx.translate(-this.scrollLeft, 0);
|
|
675
|
+
ctx.translate(-Math.round(this.scrollLeft), 0);
|
|
642
676
|
ctx.fillStyle = this.config.headerBgColor;
|
|
643
|
-
ctx.fillRect(this.scrollLeft, 0, this.viewportWidth, h);
|
|
677
|
+
ctx.fillRect(Math.round(this.scrollLeft), 0, this.viewportWidth, h);
|
|
644
678
|
ctx.textBaseline = "middle";
|
|
645
679
|
ctx.textRendering = "optimizeLegibility";
|
|
646
680
|
let currentDate = new Date(this.visibleDateRange.start);
|
|
@@ -718,20 +752,20 @@ class GanttChart {
|
|
|
718
752
|
}
|
|
719
753
|
const groupedBlocks = this.groupConsecutiveBlocks(visibleBlocks);
|
|
720
754
|
ctx.fillStyle = "#333";
|
|
721
|
-
ctx.font =
|
|
755
|
+
ctx.font = "bold 14px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
722
756
|
ctx.textAlign = "left";
|
|
723
757
|
groupedBlocks.forEach((group) => {
|
|
724
758
|
const visibleStart = Math.max(group.startX, this.scrollLeft);
|
|
725
759
|
const visibleEnd = Math.min(group.endX, this.scrollLeft + this.viewportWidth);
|
|
726
760
|
if (visibleEnd > visibleStart) {
|
|
727
761
|
ctx.fillStyle = this.config.headerBgColor;
|
|
728
|
-
ctx.fillRect(visibleStart, 0, visibleEnd - visibleStart, h * 0.5);
|
|
762
|
+
ctx.fillRect(Math.round(visibleStart), 0, Math.round(visibleEnd - visibleStart), Math.round(h * 0.5));
|
|
729
763
|
ctx.fillStyle = "#333";
|
|
730
|
-
ctx.fillText(group.text, visibleStart + 5, group.yPos);
|
|
764
|
+
ctx.fillText(group.text, Math.round(visibleStart + 5), Math.round(group.yPos));
|
|
731
765
|
}
|
|
732
766
|
});
|
|
733
767
|
while (currentDateForLower <= this.visibleDateRange.end) {
|
|
734
|
-
const x = this.dateToX(currentDateForLower);
|
|
768
|
+
const x = this.snap(this.dateToX(currentDateForLower));
|
|
735
769
|
let lowerText = "";
|
|
736
770
|
let nextDate;
|
|
737
771
|
switch (this.config.viewMode) {
|
|
@@ -766,7 +800,7 @@ class GanttChart {
|
|
|
766
800
|
}
|
|
767
801
|
const unitWidth = this.dateToX(nextDate) - x;
|
|
768
802
|
ctx.fillStyle = "#000412";
|
|
769
|
-
ctx.font =
|
|
803
|
+
ctx.font = "14px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
770
804
|
ctx.textAlign = "center";
|
|
771
805
|
ctx.fillText(lowerText, Math.round(x + unitWidth / 2), Math.round(h * 0.7));
|
|
772
806
|
ctx.beginPath();
|
|
@@ -780,11 +814,6 @@ class GanttChart {
|
|
|
780
814
|
currentDateForLower = nextDate;
|
|
781
815
|
}
|
|
782
816
|
}
|
|
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
817
|
ctx.restore();
|
|
789
818
|
}
|
|
790
819
|
// Helper method to group consecutive blocks with same text
|
|
@@ -807,7 +836,7 @@ class GanttChart {
|
|
|
807
836
|
const ctx = this.mainCtx;
|
|
808
837
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
809
838
|
ctx.save();
|
|
810
|
-
ctx.translate(-this.scrollLeft, -this.scrollTop);
|
|
839
|
+
ctx.translate(-Math.round(this.scrollLeft), -Math.round(this.scrollTop));
|
|
811
840
|
const { start: startDate, end: endDate } = this.visibleDateRange;
|
|
812
841
|
this.drawGrid(ctx, startDate, endDate);
|
|
813
842
|
this.drawToday(ctx);
|
|
@@ -896,7 +925,7 @@ class GanttChart {
|
|
|
896
925
|
}
|
|
897
926
|
drawAllTasks(ctx) {
|
|
898
927
|
ctx.textBaseline = "middle";
|
|
899
|
-
ctx.font =
|
|
928
|
+
ctx.font = "12px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
900
929
|
ctx.textRendering = "optimizeSpeed";
|
|
901
930
|
ctx.imageSmoothingEnabled = false;
|
|
902
931
|
let visibleRowIndex = 0;
|
|
@@ -923,6 +952,7 @@ class GanttChart {
|
|
|
923
952
|
}
|
|
924
953
|
// In the drawGrid method
|
|
925
954
|
drawGrid(ctx, startDate, endDate) {
|
|
955
|
+
const separator = this.config.dateSeparator;
|
|
926
956
|
ctx.strokeStyle = "#e6e6e6";
|
|
927
957
|
ctx.lineWidth = 1;
|
|
928
958
|
ctx.beginPath();
|
|
@@ -936,8 +966,9 @@ class GanttChart {
|
|
|
936
966
|
for (let i = 0; i <= visibleRowCount; i++) {
|
|
937
967
|
const y = i * this.config.rowHeight;
|
|
938
968
|
if (y < this.scrollTop || y > this.scrollTop + this.viewportHeight) continue;
|
|
939
|
-
|
|
940
|
-
ctx.
|
|
969
|
+
const sharpY = this.snap(y);
|
|
970
|
+
ctx.moveTo(this.scrollLeft, sharpY);
|
|
971
|
+
ctx.lineTo(this.scrollLeft + this.viewportWidth, sharpY);
|
|
941
972
|
}
|
|
942
973
|
}
|
|
943
974
|
if (this.config.showColLines) {
|
|
@@ -978,9 +1009,15 @@ class GanttChart {
|
|
|
978
1009
|
currentDate = nextDate;
|
|
979
1010
|
}
|
|
980
1011
|
while (currentDate <= endDate) {
|
|
981
|
-
const x = this.dateToX(currentDate);
|
|
1012
|
+
const x = this.snap(this.dateToX(currentDate));
|
|
982
1013
|
ctx.moveTo(x, this.scrollTop);
|
|
983
1014
|
ctx.lineTo(x, this.scrollTop + this.viewportHeight);
|
|
1015
|
+
if (this.config.viewMode === "Day") {
|
|
1016
|
+
if ([0, 6].includes(currentDate.getDay()) || this.holidaysMap.has(DateUtils.format(currentDate, `yyyy${separator}MM${separator}dd`))) {
|
|
1017
|
+
ctx.fillStyle = this.config.weekendBgColor;
|
|
1018
|
+
ctx.fillRect(x + 1, this.scrollTop, Math.round(this.pixelsPerDay - 1), Math.round(this.scrollTop + this.viewportHeight));
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
984
1021
|
switch (this.config.viewMode) {
|
|
985
1022
|
case "Day":
|
|
986
1023
|
nextDate = DateUtils.addDays(currentDate, 1);
|
|
@@ -1027,14 +1064,14 @@ class GanttChart {
|
|
|
1027
1064
|
const [offsetX_actual, percent_actual] = this.config.viewMode === "Day" && task.actualOffsetPercent ? task.actualOffsetPercent : [0, 1];
|
|
1028
1065
|
if (this.config.showActual && pos.x_actual_start) {
|
|
1029
1066
|
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));
|
|
1067
|
+
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
1068
|
}
|
|
1032
1069
|
if (this.config.showPlan && pos.x_plan_start && pos.x_plan_end) {
|
|
1033
1070
|
ctx.strokeStyle = task.planBorderColor ? task.planBorderColor : this.config.planBorderColor;
|
|
1034
1071
|
ctx.lineWidth = 4;
|
|
1035
1072
|
ctx.beginPath();
|
|
1036
|
-
ctx.moveTo(pos.offset_x_plan_start + offset / 2, taskY);
|
|
1037
|
-
ctx.lineTo(pos.offset_x_plan_end - offset / 2, taskY);
|
|
1073
|
+
ctx.moveTo(pos.offset_x_plan_start + offset / 2 + this.config.xGap, taskY);
|
|
1074
|
+
ctx.lineTo(pos.offset_x_plan_end - offset / 2 - this.config.xGap, taskY);
|
|
1038
1075
|
ctx.stroke();
|
|
1039
1076
|
}
|
|
1040
1077
|
ctx.fillStyle = "#000";
|
package/dist/index.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* gantt-canvas-chart v1.
|
|
2
|
+
* gantt-canvas-chart v1.6.0
|
|
3
3
|
* (c) 2025-present chandq
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
@@ -148,6 +148,7 @@
|
|
|
148
148
|
resizeObserver;
|
|
149
149
|
taskPositions;
|
|
150
150
|
taskMap;
|
|
151
|
+
holidaysMap;
|
|
151
152
|
isLoadingData = false;
|
|
152
153
|
hasMoreDataLeft = true;
|
|
153
154
|
hasMoreDataRight = true;
|
|
@@ -196,9 +197,13 @@
|
|
|
196
197
|
tooltipFormat: null,
|
|
197
198
|
tooltipColor: "black",
|
|
198
199
|
todayColor: "#ff4d4f",
|
|
200
|
+
weekendBgColor: "#f7f7f7",
|
|
201
|
+
holidays: [],
|
|
202
|
+
dateSeparator: "/",
|
|
199
203
|
offsetTop: 0,
|
|
200
204
|
offsetLeft: 0,
|
|
201
205
|
scrollEdgeThresholds: 10,
|
|
206
|
+
xGap: 0,
|
|
202
207
|
enabledLoadMore: [],
|
|
203
208
|
viewFactors: { Day: 80, Week: 20, Month: 15, Year: 6 },
|
|
204
209
|
planBorderColor: "#C1EFCF",
|
|
@@ -233,6 +238,7 @@
|
|
|
233
238
|
this.totalHeight = 0;
|
|
234
239
|
this.taskPositions = /* @__PURE__ */ new Map();
|
|
235
240
|
this.taskMap = /* @__PURE__ */ new Map();
|
|
241
|
+
this.holidaysMap = /* @__PURE__ */ new Map();
|
|
236
242
|
this.handleMouseMove = this.handleMouseMove.bind(this);
|
|
237
243
|
this.handleMouseLeave = this.handleMouseLeave.bind(this);
|
|
238
244
|
this.handleScroll = this.handleScroll.bind(this);
|
|
@@ -242,6 +248,9 @@
|
|
|
242
248
|
this.init();
|
|
243
249
|
}
|
|
244
250
|
init() {
|
|
251
|
+
if (this.config.holidays.length > 0) {
|
|
252
|
+
this.buildHolidaysMap();
|
|
253
|
+
}
|
|
245
254
|
this.buildTaskMap();
|
|
246
255
|
this.updatePixelsPerDay();
|
|
247
256
|
this.calculateFullTimeline();
|
|
@@ -290,10 +299,26 @@
|
|
|
290
299
|
this.updatePixelsPerDay();
|
|
291
300
|
this.calculateFullTimeline();
|
|
292
301
|
}
|
|
302
|
+
if (this.config.holidays.length !== this.holidaysMap.size) {
|
|
303
|
+
this.buildHolidaysMap();
|
|
304
|
+
}
|
|
293
305
|
this.updateLoadMoreConf();
|
|
294
306
|
this.updateDimensions();
|
|
295
307
|
this.render();
|
|
296
308
|
}
|
|
309
|
+
buildHolidaysMap() {
|
|
310
|
+
this.holidaysMap.clear();
|
|
311
|
+
const separator = this.config.dateSeparator;
|
|
312
|
+
if (this.config.holidays && this.config.holidays.length > 0 && this.config.holidays[0].includes(separator)) {
|
|
313
|
+
this.config.holidays.forEach((holiday) => {
|
|
314
|
+
this.holidaysMap.set(holiday, true);
|
|
315
|
+
});
|
|
316
|
+
} else {
|
|
317
|
+
this.config.holidays.forEach((holiday) => {
|
|
318
|
+
this.holidaysMap.set(DateUtils.format(new Date(holiday), `yyyy${separator}MM${separator}dd`), true);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
297
322
|
setData(newData, newConfig) {
|
|
298
323
|
this.data = newData;
|
|
299
324
|
this.buildTaskMap();
|
|
@@ -555,8 +580,17 @@
|
|
|
555
580
|
canvas.style.height = `${height}px`;
|
|
556
581
|
const ctx = canvas.getContext("2d");
|
|
557
582
|
ctx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
|
583
|
+
ctx.textBaseline = "middle";
|
|
584
|
+
ctx.imageSmoothingEnabled = false;
|
|
558
585
|
return ctx;
|
|
559
586
|
}
|
|
587
|
+
/**
|
|
588
|
+
* 辅助函数:像素对齐(确保MAC/Windows等不同设备上1px线条清晰)
|
|
589
|
+
* 用于 1px 线条,使其落在 x.5 位置,填满一个物理像素,避免模糊
|
|
590
|
+
*/
|
|
591
|
+
snap(val) {
|
|
592
|
+
return Math.floor(val) + 0.5;
|
|
593
|
+
}
|
|
560
594
|
calculateAllTaskPositions() {
|
|
561
595
|
this.taskPositions.clear();
|
|
562
596
|
let visibleRowIndex = 0;
|
|
@@ -642,9 +676,9 @@
|
|
|
642
676
|
const h = this.config.headerHeight;
|
|
643
677
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
644
678
|
ctx.save();
|
|
645
|
-
ctx.translate(-this.scrollLeft, 0);
|
|
679
|
+
ctx.translate(-Math.round(this.scrollLeft), 0);
|
|
646
680
|
ctx.fillStyle = this.config.headerBgColor;
|
|
647
|
-
ctx.fillRect(this.scrollLeft, 0, this.viewportWidth, h);
|
|
681
|
+
ctx.fillRect(Math.round(this.scrollLeft), 0, this.viewportWidth, h);
|
|
648
682
|
ctx.textBaseline = "middle";
|
|
649
683
|
ctx.textRendering = "optimizeLegibility";
|
|
650
684
|
let currentDate = new Date(this.visibleDateRange.start);
|
|
@@ -722,20 +756,20 @@
|
|
|
722
756
|
}
|
|
723
757
|
const groupedBlocks = this.groupConsecutiveBlocks(visibleBlocks);
|
|
724
758
|
ctx.fillStyle = "#333";
|
|
725
|
-
ctx.font =
|
|
759
|
+
ctx.font = "bold 14px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
726
760
|
ctx.textAlign = "left";
|
|
727
761
|
groupedBlocks.forEach((group) => {
|
|
728
762
|
const visibleStart = Math.max(group.startX, this.scrollLeft);
|
|
729
763
|
const visibleEnd = Math.min(group.endX, this.scrollLeft + this.viewportWidth);
|
|
730
764
|
if (visibleEnd > visibleStart) {
|
|
731
765
|
ctx.fillStyle = this.config.headerBgColor;
|
|
732
|
-
ctx.fillRect(visibleStart, 0, visibleEnd - visibleStart, h * 0.5);
|
|
766
|
+
ctx.fillRect(Math.round(visibleStart), 0, Math.round(visibleEnd - visibleStart), Math.round(h * 0.5));
|
|
733
767
|
ctx.fillStyle = "#333";
|
|
734
|
-
ctx.fillText(group.text, visibleStart + 5, group.yPos);
|
|
768
|
+
ctx.fillText(group.text, Math.round(visibleStart + 5), Math.round(group.yPos));
|
|
735
769
|
}
|
|
736
770
|
});
|
|
737
771
|
while (currentDateForLower <= this.visibleDateRange.end) {
|
|
738
|
-
const x = this.dateToX(currentDateForLower);
|
|
772
|
+
const x = this.snap(this.dateToX(currentDateForLower));
|
|
739
773
|
let lowerText = "";
|
|
740
774
|
let nextDate;
|
|
741
775
|
switch (this.config.viewMode) {
|
|
@@ -770,7 +804,7 @@
|
|
|
770
804
|
}
|
|
771
805
|
const unitWidth = this.dateToX(nextDate) - x;
|
|
772
806
|
ctx.fillStyle = "#000412";
|
|
773
|
-
ctx.font =
|
|
807
|
+
ctx.font = "14px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
774
808
|
ctx.textAlign = "center";
|
|
775
809
|
ctx.fillText(lowerText, Math.round(x + unitWidth / 2), Math.round(h * 0.7));
|
|
776
810
|
ctx.beginPath();
|
|
@@ -784,11 +818,6 @@
|
|
|
784
818
|
currentDateForLower = nextDate;
|
|
785
819
|
}
|
|
786
820
|
}
|
|
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
821
|
ctx.restore();
|
|
793
822
|
}
|
|
794
823
|
// Helper method to group consecutive blocks with same text
|
|
@@ -811,7 +840,7 @@
|
|
|
811
840
|
const ctx = this.mainCtx;
|
|
812
841
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
813
842
|
ctx.save();
|
|
814
|
-
ctx.translate(-this.scrollLeft, -this.scrollTop);
|
|
843
|
+
ctx.translate(-Math.round(this.scrollLeft), -Math.round(this.scrollTop));
|
|
815
844
|
const { start: startDate, end: endDate } = this.visibleDateRange;
|
|
816
845
|
this.drawGrid(ctx, startDate, endDate);
|
|
817
846
|
this.drawToday(ctx);
|
|
@@ -900,7 +929,7 @@
|
|
|
900
929
|
}
|
|
901
930
|
drawAllTasks(ctx) {
|
|
902
931
|
ctx.textBaseline = "middle";
|
|
903
|
-
ctx.font =
|
|
932
|
+
ctx.font = "12px Roboto,PingFang SC,Noto Sans SC,Microsoft YaHei UI,Microsoft YaHei,Segoe UI,Helvetica Neue,Helvetica,Arial,sans-serif";
|
|
904
933
|
ctx.textRendering = "optimizeSpeed";
|
|
905
934
|
ctx.imageSmoothingEnabled = false;
|
|
906
935
|
let visibleRowIndex = 0;
|
|
@@ -927,6 +956,7 @@
|
|
|
927
956
|
}
|
|
928
957
|
// In the drawGrid method
|
|
929
958
|
drawGrid(ctx, startDate, endDate) {
|
|
959
|
+
const separator = this.config.dateSeparator;
|
|
930
960
|
ctx.strokeStyle = "#e6e6e6";
|
|
931
961
|
ctx.lineWidth = 1;
|
|
932
962
|
ctx.beginPath();
|
|
@@ -940,8 +970,9 @@
|
|
|
940
970
|
for (let i = 0; i <= visibleRowCount; i++) {
|
|
941
971
|
const y = i * this.config.rowHeight;
|
|
942
972
|
if (y < this.scrollTop || y > this.scrollTop + this.viewportHeight) continue;
|
|
943
|
-
|
|
944
|
-
ctx.
|
|
973
|
+
const sharpY = this.snap(y);
|
|
974
|
+
ctx.moveTo(this.scrollLeft, sharpY);
|
|
975
|
+
ctx.lineTo(this.scrollLeft + this.viewportWidth, sharpY);
|
|
945
976
|
}
|
|
946
977
|
}
|
|
947
978
|
if (this.config.showColLines) {
|
|
@@ -982,9 +1013,15 @@
|
|
|
982
1013
|
currentDate = nextDate;
|
|
983
1014
|
}
|
|
984
1015
|
while (currentDate <= endDate) {
|
|
985
|
-
const x = this.dateToX(currentDate);
|
|
1016
|
+
const x = this.snap(this.dateToX(currentDate));
|
|
986
1017
|
ctx.moveTo(x, this.scrollTop);
|
|
987
1018
|
ctx.lineTo(x, this.scrollTop + this.viewportHeight);
|
|
1019
|
+
if (this.config.viewMode === "Day") {
|
|
1020
|
+
if ([0, 6].includes(currentDate.getDay()) || this.holidaysMap.has(DateUtils.format(currentDate, `yyyy${separator}MM${separator}dd`))) {
|
|
1021
|
+
ctx.fillStyle = this.config.weekendBgColor;
|
|
1022
|
+
ctx.fillRect(x + 1, this.scrollTop, Math.round(this.pixelsPerDay - 1), Math.round(this.scrollTop + this.viewportHeight));
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
988
1025
|
switch (this.config.viewMode) {
|
|
989
1026
|
case "Day":
|
|
990
1027
|
nextDate = DateUtils.addDays(currentDate, 1);
|
|
@@ -1031,14 +1068,14 @@
|
|
|
1031
1068
|
const [offsetX_actual, percent_actual] = this.config.viewMode === "Day" && task.actualOffsetPercent ? task.actualOffsetPercent : [0, 1];
|
|
1032
1069
|
if (this.config.showActual && pos.x_actual_start) {
|
|
1033
1070
|
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));
|
|
1071
|
+
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
1072
|
}
|
|
1036
1073
|
if (this.config.showPlan && pos.x_plan_start && pos.x_plan_end) {
|
|
1037
1074
|
ctx.strokeStyle = task.planBorderColor ? task.planBorderColor : this.config.planBorderColor;
|
|
1038
1075
|
ctx.lineWidth = 4;
|
|
1039
1076
|
ctx.beginPath();
|
|
1040
|
-
ctx.moveTo(pos.offset_x_plan_start + offset / 2, taskY);
|
|
1041
|
-
ctx.lineTo(pos.offset_x_plan_end - offset / 2, taskY);
|
|
1077
|
+
ctx.moveTo(pos.offset_x_plan_start + offset / 2 + this.config.xGap, taskY);
|
|
1078
|
+
ctx.lineTo(pos.offset_x_plan_end - offset / 2 - this.config.xGap, taskY);
|
|
1042
1079
|
ctx.stroke();
|
|
1043
1080
|
}
|
|
1044
1081
|
ctx.fillStyle = "#000";
|