@operato/gantt 2.0.0-beta.4 → 7.0.0-rc.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/CHANGELOG.md +116 -0
- package/dist/src/gantt-chart-controller.d.ts +32 -0
- package/dist/src/gantt-chart-controller.js +395 -0
- package/dist/src/gantt-chart-controller.js.map +1 -0
- package/dist/src/locales.d.ts +52 -0
- package/dist/src/locales.js +79 -0
- package/dist/src/locales.js.map +1 -0
- package/dist/src/ox-gantt-chart.d.ts +11 -20
- package/dist/src/ox-gantt-chart.js +141 -435
- package/dist/src/ox-gantt-chart.js.map +1 -1
- package/dist/src/ox-gantt-resize-splitter.d.ts +2 -3
- package/dist/src/ox-gantt-resize-splitter.js +15 -22
- package/dist/src/ox-gantt-resize-splitter.js.map +1 -1
- package/dist/src/ox-gantt.d.ts +17 -3
- package/dist/src/ox-gantt.js +169 -29
- package/dist/src/ox-gantt.js.map +1 -1
- package/dist/src/ox-task-tooltip.d.ts +12 -0
- package/dist/src/ox-task-tooltip.js +132 -0
- package/dist/src/ox-task-tooltip.js.map +1 -0
- package/dist/src/types.d.ts +11 -4
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils.d.ts +3 -3
- package/dist/src/utils.js.map +1 -1
- package/dist/stories/ox-gantt.stories.d.ts +19 -2
- package/dist/stories/ox-gantt.stories.js +104 -90
- package/dist/stories/ox-gantt.stories.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -4
- package/src/gantt-chart-controller.ts +473 -0
- package/src/locales.ts +78 -0
- package/src/ox-gantt-chart.ts +151 -484
- package/src/ox-gantt-resize-splitter.ts +18 -25
- package/src/ox-gantt.ts +174 -31
- package/src/ox-task-tooltip.ts +120 -0
- package/src/types.ts +13 -5
- package/src/utils.ts +4 -4
- package/stories/ox-gantt.stories.ts +118 -91
- package/themes/grist-theme.css +5 -3
- package/translations/en.json +11 -1
- package/translations/ja.json +11 -1
- package/translations/ko.json +11 -1
- package/translations/ms.json +11 -1
- package/translations/zh.json +11 -1
- package/dist/src/locale.d.ts +0 -1
- package/dist/src/locale.js +0 -49
- package/dist/src/locale.js.map +0 -1
- package/src/locale.ts +0 -51
|
@@ -1,435 +1,168 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
|
+
import './ox-task-tooltip';
|
|
2
3
|
import { LitElement, html, css } from 'lit';
|
|
3
|
-
import { customElement, property, query
|
|
4
|
-
import { styles as MDTypeScaleStyles } from '@material/web/typography/md-typescale-styles';
|
|
4
|
+
import { customElement, property, query } from 'lit/decorators.js';
|
|
5
5
|
import * as d3 from 'd3';
|
|
6
|
-
import {
|
|
6
|
+
import { locales } from './locales';
|
|
7
|
+
import { styles as MDTypeScaleStyles } from '@material/web/typography/md-typescale-styles';
|
|
7
8
|
import { ScrollbarStyles } from '@operato/styles';
|
|
9
|
+
import { GanttChartController } from './gantt-chart-controller';
|
|
10
|
+
import { levelDown, levelUp } from './utils';
|
|
8
11
|
let OxGanttChart = class OxGanttChart extends LitElement {
|
|
9
|
-
constructor() {
|
|
10
|
-
super(...arguments);
|
|
11
|
-
this.fromDate = new Date(new Date().getFullYear(), 0, 1).toISOString().split('T')[0]; /* beginning of this year */
|
|
12
|
-
this.toDate = new Date(new Date().getFullYear(), 11, 31).toISOString().split('T')[0]; /* end of this year */
|
|
13
|
-
this.axisScale = 'week-day';
|
|
14
|
-
this.extendGridLines = false;
|
|
15
|
-
this.tasks = [];
|
|
16
|
-
this.baseWidth = 30;
|
|
17
|
-
this.level = this.axisScale;
|
|
18
|
-
this.fromTime = new Date(this.fromDate);
|
|
19
|
-
this.toTime = new Date(this.toDate);
|
|
20
|
-
this.totalDays = d3.timeDay.count(this.fromTime, d3.timeDay.offset(this.toTime, 1));
|
|
21
|
-
}
|
|
22
12
|
static { this.styles = [
|
|
23
13
|
MDTypeScaleStyles,
|
|
24
14
|
ScrollbarStyles,
|
|
25
15
|
css `
|
|
26
16
|
:host {
|
|
27
17
|
display: flex;
|
|
28
|
-
|
|
29
18
|
overflow: hidden;
|
|
19
|
+
position: relative;
|
|
30
20
|
}
|
|
31
|
-
|
|
32
21
|
div[chart-container] {
|
|
33
22
|
display: block;
|
|
34
|
-
|
|
35
23
|
overflow-x: auto;
|
|
36
|
-
overflow-y:
|
|
37
|
-
|
|
38
|
-
position: relative;
|
|
24
|
+
overflow-y: hidden;
|
|
39
25
|
}
|
|
40
|
-
|
|
41
26
|
svg {
|
|
42
27
|
min-width: 100%;
|
|
43
28
|
}
|
|
44
|
-
|
|
45
29
|
svg[header] {
|
|
46
30
|
display: block;
|
|
47
31
|
min-width: 100%;
|
|
48
32
|
box-sizing: border-box;
|
|
49
|
-
border-bottom: 1px solid var(--ox-gantt-border-color, var(--md-sys-color-on-
|
|
33
|
+
border-bottom: 1px solid var(--ox-gantt-header-border-color, var(--md-sys-color-on-secondary-container));
|
|
50
34
|
height: 40px;
|
|
51
35
|
background-color: var(--ox-gantt-header-background-color, var(--md-sys-color-secondary-container));
|
|
36
|
+
color: var(--ox-gantt-header-color, var(--md-sys-color-on-secondary-container));
|
|
52
37
|
|
|
53
38
|
position: sticky;
|
|
54
39
|
top: 0;
|
|
55
40
|
z-index: 1;
|
|
56
41
|
}
|
|
57
|
-
|
|
58
42
|
.top-axis text,
|
|
59
43
|
.bottom-axis text {
|
|
60
44
|
font-size: 12px;
|
|
61
45
|
}
|
|
62
|
-
|
|
63
46
|
.grid line {
|
|
64
47
|
stroke: #e0e0e0;
|
|
65
48
|
}
|
|
66
|
-
|
|
67
49
|
.grid path {
|
|
68
50
|
stroke-width: 0;
|
|
69
51
|
}
|
|
70
|
-
|
|
71
52
|
.no-select {
|
|
72
53
|
user-select: none;
|
|
73
54
|
}
|
|
55
|
+
.selected {
|
|
56
|
+
stroke-width: 2px;
|
|
57
|
+
stroke: var(--ox-gantt-selected-color, blue);
|
|
58
|
+
}
|
|
74
59
|
`
|
|
75
60
|
]; }
|
|
61
|
+
constructor() {
|
|
62
|
+
super();
|
|
63
|
+
this.fromDate = new Date(new Date().getFullYear(), 0, 1)
|
|
64
|
+
.toISOString()
|
|
65
|
+
.split('T')[0];
|
|
66
|
+
this.toDate = new Date(new Date().getFullYear(), 11, 31)
|
|
67
|
+
.toISOString()
|
|
68
|
+
.split('T')[0];
|
|
69
|
+
this.axisScale = 'week-day';
|
|
70
|
+
this.extendGridLines = false;
|
|
71
|
+
this.tasks = [];
|
|
72
|
+
this.locale = 'en-US';
|
|
73
|
+
this.ganttChartController = new GanttChartController(this, this.fromDate, this.toDate, this.axisScale);
|
|
74
|
+
}
|
|
76
75
|
firstUpdated(_changedProperties) {
|
|
77
76
|
this.initHeaderGestureHandlers();
|
|
78
77
|
}
|
|
79
78
|
updated(changedProperties) {
|
|
80
|
-
var redrawRequired = false;
|
|
81
79
|
if (changedProperties.has('fromDate') || changedProperties.has('toDate')) {
|
|
82
|
-
this.
|
|
83
|
-
this.toTime = new Date(this.toDate);
|
|
84
|
-
this.totalDays = d3.timeDay.count(this.fromTime, d3.timeDay.offset(this.toTime, 1));
|
|
85
|
-
redrawRequired = true;
|
|
86
|
-
}
|
|
87
|
-
if (changedProperties.has('axisScale')) {
|
|
88
|
-
redrawRequired = true;
|
|
80
|
+
this.ganttChartController.updateRange(this.fromDate, this.toDate);
|
|
89
81
|
}
|
|
90
82
|
if (changedProperties.has('tasks')) {
|
|
91
|
-
this.
|
|
92
|
-
|
|
83
|
+
this.ganttChartController.setTasks(this.tasks);
|
|
84
|
+
this.svgBody.style.height =
|
|
85
|
+
Math.max(this.tasks.length * 30, 2000 /* 태스크가 많이 없어도, 빈 공간이 없도록, 충분히 높게 */) + 'px';
|
|
86
|
+
}
|
|
87
|
+
// Update locale
|
|
88
|
+
if (changedProperties.has('locale')) {
|
|
89
|
+
this.updateLocale(this.locale);
|
|
93
90
|
}
|
|
94
|
-
|
|
91
|
+
if (changedProperties.has('selectedTaskId')) {
|
|
92
|
+
this.ganttChartController.selectedTaskId = this.selectedTaskId;
|
|
93
|
+
}
|
|
94
|
+
if (changedProperties.has('extendGridLines')) {
|
|
95
|
+
this.ganttChartController.extendGridLines = this.extendGridLines;
|
|
96
|
+
}
|
|
97
|
+
if (changedProperties.has('axisScale')) {
|
|
98
|
+
this.ganttChartController.level = this.axisScale;
|
|
99
|
+
this.ganttChartController.updateTimeScale();
|
|
100
|
+
}
|
|
101
|
+
this.ganttChartController.drawAxes(this.svgHeader);
|
|
102
|
+
this.ganttChartController.drawBody(this.svgBody, this.tooltip);
|
|
95
103
|
}
|
|
96
104
|
render() {
|
|
97
105
|
return html `
|
|
98
|
-
<div
|
|
106
|
+
<div
|
|
107
|
+
chart-container
|
|
108
|
+
@wheel=${(e) => this.ganttChartController.handleWheelForBody(e, this.container)}
|
|
109
|
+
@touchstart=${(e) => this.ganttChartController.handleTouchStartForBody(e, this.container)}
|
|
110
|
+
@touchmove=${(e) => this.ganttChartController.handleTouchMoveForBody(e, this.container)}
|
|
111
|
+
>
|
|
99
112
|
<svg
|
|
100
113
|
header
|
|
101
|
-
@date-range-selected=${(e) =>
|
|
102
|
-
console.log(e.detail);
|
|
103
|
-
}}
|
|
114
|
+
@date-range-selected=${(e) => console.log(e.detail)}
|
|
104
115
|
@wheel=${this.handleWheelForHeader.bind(this)}
|
|
105
116
|
></svg>
|
|
106
117
|
<svg body></svg>
|
|
118
|
+
<ox-task-tooltip></ox-task-tooltip> // 툴팁 요소 추가
|
|
107
119
|
</div>
|
|
108
120
|
`;
|
|
109
121
|
}
|
|
110
122
|
setVerticalOffset(offset) {
|
|
111
|
-
console.log(offset);
|
|
112
123
|
this.container.scrollTop = offset;
|
|
113
124
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return 7 / 2;
|
|
117
|
-
}
|
|
118
|
-
else if (this.level == 'year-month') {
|
|
119
|
-
return 30 / 2;
|
|
120
|
-
}
|
|
121
|
-
else if (this.level == 'year-quarter') {
|
|
122
|
-
return 90 / 2;
|
|
123
|
-
}
|
|
124
|
-
return 1;
|
|
125
|
-
}
|
|
126
|
-
getChartWidth() {
|
|
127
|
-
return (this.totalDays * this.baseWidth) / this.getScale(); // 총 차트 폭 계산
|
|
125
|
+
getVerticalOffset() {
|
|
126
|
+
return this.container.scrollTop;
|
|
128
127
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
.
|
|
135
|
-
|
|
128
|
+
handleWheelForHeader(event) {
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
event.stopPropagation();
|
|
131
|
+
if (event.altKey || event.ctrlKey || event.shiftKey || event.metaKey) {
|
|
132
|
+
this.container.scrollBy({
|
|
133
|
+
left: event.shiftKey ? event.deltaX : event.deltaY
|
|
134
|
+
});
|
|
136
135
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
body.selectAll('*').remove();
|
|
149
|
-
body.attr('width', this.getChartWidth());
|
|
150
|
-
if (this.extendGridLines) {
|
|
151
|
-
const addGridLines = (ticks) => {
|
|
152
|
-
ticks.forEach(tick => {
|
|
153
|
-
const startX = this.timeScale(tick);
|
|
154
|
-
body
|
|
155
|
-
.append('line')
|
|
156
|
-
.attr('x1', startX)
|
|
157
|
-
.attr('x2', startX)
|
|
158
|
-
.attr('y1', 0)
|
|
159
|
-
.attr('y2', Number(this.svgBody.getBoundingClientRect().height) || 0)
|
|
160
|
-
.attr('stroke', 'var(--ox-gantt-color-gridlines, var(--md-sys-color-outline))')
|
|
161
|
-
.attr('stroke-width', 1);
|
|
162
|
-
});
|
|
163
|
-
};
|
|
164
|
-
if (this.level === 'week-day') {
|
|
165
|
-
const dayTicks = d3.timeDay.every(1).range(d3.timeDay.floor(this.fromTime), d3.timeDay.offset(this.toTime, 1));
|
|
166
|
-
addGridLines(dayTicks);
|
|
167
|
-
}
|
|
168
|
-
else if (this.level === 'quarter-week') {
|
|
169
|
-
const weekTicks = d3.timeWeek
|
|
170
|
-
.every(1)
|
|
171
|
-
.range(d3.timeWeek.floor(this.fromTime), d3.timeWeek.offset(this.toTime, 1));
|
|
172
|
-
addGridLines(weekTicks);
|
|
136
|
+
else {
|
|
137
|
+
const zoomFactor = event.deltaY < 0 ? 1.1 : 0.9;
|
|
138
|
+
this.ganttChartController.baseWidth *= zoomFactor;
|
|
139
|
+
if (this.ganttChartController.baseWidth > 40) {
|
|
140
|
+
if (this.ganttChartController.level == 'week-day') {
|
|
141
|
+
this.ganttChartController.baseWidth = 40;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
this.ganttChartController.level = levelDown(this.ganttChartController.level);
|
|
145
|
+
this.ganttChartController.baseWidth = 30;
|
|
146
|
+
}
|
|
173
147
|
}
|
|
174
|
-
else if (this.
|
|
175
|
-
|
|
176
|
-
.
|
|
177
|
-
|
|
178
|
-
addGridLines(monthTicks);
|
|
148
|
+
else if (this.ganttChartController.baseWidth < 30) {
|
|
149
|
+
if (this.ganttChartController.level == 'year-quarter') {
|
|
150
|
+
this.ganttChartController.baseWidth = 30;
|
|
151
|
+
}
|
|
179
152
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
.range(d3.timeMonth.floor(this.fromTime), d3.timeMonth.offset(this.toTime, 3));
|
|
184
|
-
addGridLines(quarterTicks);
|
|
153
|
+
if (this.ganttChartController.baseWidth < 20) {
|
|
154
|
+
this.ganttChartController.level = levelUp(this.ganttChartController.level);
|
|
155
|
+
this.ganttChartController.baseWidth = 30;
|
|
185
156
|
}
|
|
157
|
+
this.ganttChartController.updateTimeScale();
|
|
158
|
+
this.requestUpdate();
|
|
186
159
|
}
|
|
187
|
-
const taskPositions = {};
|
|
188
|
-
// Add task bars
|
|
189
|
-
this.tasks.forEach((task, index) => {
|
|
190
|
-
const startX = this.timeScale(d3.timeDay.floor(d3.min([task.startTime, task.endTime])));
|
|
191
|
-
const endX = this.timeScale(d3.timeDay.offset(d3.timeDay.ceil(d3.max([task.startTime, task.endTime])), -1));
|
|
192
|
-
const taskWidth = endX - startX + this.baseWidth / this.getScale();
|
|
193
|
-
const taskY = index * 30 + 7.5;
|
|
194
|
-
// Main task bar
|
|
195
|
-
const taskBar = body
|
|
196
|
-
.append('rect')
|
|
197
|
-
.attr('x', startX)
|
|
198
|
-
.attr('y', taskY)
|
|
199
|
-
.attr('width', taskWidth)
|
|
200
|
-
.attr('height', 15)
|
|
201
|
-
.attr('fill', this.getTaskColor(task.tags || []))
|
|
202
|
-
.attr('stroke', 'black')
|
|
203
|
-
.attr('stroke-width', 1);
|
|
204
|
-
// Add click event listener to task bar
|
|
205
|
-
taskBar.on('click', () => {
|
|
206
|
-
const customEvent = new CustomEvent('task-clicked', {
|
|
207
|
-
detail: { task },
|
|
208
|
-
bubbles: true,
|
|
209
|
-
composed: true
|
|
210
|
-
});
|
|
211
|
-
this.dispatchEvent(customEvent);
|
|
212
|
-
});
|
|
213
|
-
// Progress bar
|
|
214
|
-
if (task.progress !== undefined) {
|
|
215
|
-
const progressWidth = taskWidth * (task.progress / 100);
|
|
216
|
-
body
|
|
217
|
-
.append('rect')
|
|
218
|
-
.attr('x', startX)
|
|
219
|
-
.attr('y', taskY + 7.5 - 3.75) // Adjust to center within the task bar
|
|
220
|
-
.attr('width', progressWidth)
|
|
221
|
-
.attr('height', 7.5) // Set height to 50% of the task bar height
|
|
222
|
-
.attr('fill', 'lightgreen');
|
|
223
|
-
}
|
|
224
|
-
// Save task position for linking lines
|
|
225
|
-
taskPositions[task.id] = { x: endX + this.baseWidth / this.getScale(), y: taskY + 7.5, width: taskWidth };
|
|
226
|
-
});
|
|
227
|
-
// Add linking lines
|
|
228
|
-
this.tasks.forEach(task => {
|
|
229
|
-
if (task.after && taskPositions[task.after]) {
|
|
230
|
-
const prevTask = taskPositions[task.after];
|
|
231
|
-
const currTask = taskPositions[task.id];
|
|
232
|
-
// Draw the linking line
|
|
233
|
-
body
|
|
234
|
-
.append('path')
|
|
235
|
-
.attr('d', `
|
|
236
|
-
M${prevTask.x},${prevTask.y}
|
|
237
|
-
L${prevTask.x + 10},${prevTask.y}
|
|
238
|
-
${prevTask.x + 10 < currTask.x - 10 - currTask.width
|
|
239
|
-
? `
|
|
240
|
-
L${(prevTask.x + currTask.x - currTask.width) / 2},${prevTask.y}
|
|
241
|
-
L${(prevTask.x + currTask.x - currTask.width) / 2},${currTask.y}
|
|
242
|
-
`
|
|
243
|
-
: `
|
|
244
|
-
L${prevTask.x + 10},${(prevTask.y + currTask.y) / 2}
|
|
245
|
-
L${currTask.x - 10 - currTask.width},${(prevTask.y + currTask.y) / 2}
|
|
246
|
-
`}
|
|
247
|
-
L${currTask.x - 10 - currTask.width},${currTask.y}
|
|
248
|
-
L${currTask.x - currTask.width},${currTask.y}
|
|
249
|
-
`)
|
|
250
|
-
.attr('stroke', 'black')
|
|
251
|
-
.attr('fill', 'none')
|
|
252
|
-
.attr('stroke-width', 1);
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
160
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
else if (tags.includes('milestone')) {
|
|
262
|
-
return 'blue';
|
|
263
|
-
}
|
|
264
|
-
else if (tags.includes('done')) {
|
|
265
|
-
return 'green';
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
return 'grey';
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
drawAxes() {
|
|
272
|
-
const header = d3.select(this.svgHeader);
|
|
273
|
-
header.selectAll('*').remove();
|
|
274
|
-
header.attr('width', this.getChartWidth());
|
|
275
|
-
// 차트 컨테이너의 폭 설정 및 스크롤 가능하도록 설정
|
|
276
|
-
header.attr('width', this.getChartWidth());
|
|
277
|
-
const addGridLines = (ticks, yStart, yEnd, extendable) => {
|
|
278
|
-
ticks.forEach(tick => {
|
|
279
|
-
const startX = this.timeScale(tick);
|
|
280
|
-
header
|
|
281
|
-
.append('line')
|
|
282
|
-
.attr('x1', startX)
|
|
283
|
-
.attr('x2', startX)
|
|
284
|
-
.attr('y1', yStart)
|
|
285
|
-
.attr('y2', yEnd)
|
|
286
|
-
.attr('stroke', 'var(--ox-gantt-color-gridlines, var(--md-sys-color-outline))')
|
|
287
|
-
.attr('stroke-width', 1);
|
|
288
|
-
});
|
|
289
|
-
};
|
|
290
|
-
const centerLabel = (selection, tickInterval) => {
|
|
291
|
-
selection.selectAll('text').attr('transform', (d) => {
|
|
292
|
-
const tickIntervalWidth = this.timeScale(tickInterval.offset(d, 1)) - this.timeScale(d);
|
|
293
|
-
return `translate(${tickIntervalWidth / 2}, -2)`;
|
|
294
|
-
});
|
|
295
|
-
};
|
|
296
|
-
if (this.level === 'week-day') {
|
|
297
|
-
// Level 1: week-day
|
|
298
|
-
// Weekly axis
|
|
299
|
-
const weekAxis = d3
|
|
300
|
-
.axisTop(this.timeScale)
|
|
301
|
-
.ticks(d3.timeWeek.every(1))
|
|
302
|
-
.tickFormat(d3.timeFormat('%B %d'))
|
|
303
|
-
.tickSize(0);
|
|
304
|
-
const weekTicks = d3.timeWeek
|
|
305
|
-
.every(1)
|
|
306
|
-
.range(d3.timeWeek.floor(this.fromTime), d3.timeWeek.offset(this.toTime, 1));
|
|
307
|
-
addGridLines(weekTicks, 0, 20);
|
|
308
|
-
const weekAxisG = header
|
|
309
|
-
.append('g')
|
|
310
|
-
.classed('week-axis no-select md-typescale-label-small', true)
|
|
311
|
-
.attr('transform', `translate(0, 20)`)
|
|
312
|
-
.call(weekAxis);
|
|
313
|
-
centerLabel(weekAxisG, d3.timeWeek);
|
|
314
|
-
// Daily axis
|
|
315
|
-
const dayAxis = d3
|
|
316
|
-
.axisTop(this.timeScale)
|
|
317
|
-
.ticks(d3.timeDay.every(1))
|
|
318
|
-
.tickFormat(d3.timeFormat('%d'))
|
|
319
|
-
.tickSize(0);
|
|
320
|
-
const dayTicks = d3.timeDay.every(1).range(d3.timeDay.floor(this.fromTime), d3.timeDay.offset(this.toTime, 1));
|
|
321
|
-
addGridLines(dayTicks, 20, 40);
|
|
322
|
-
const dayAxisG = header
|
|
323
|
-
.append('g')
|
|
324
|
-
.classed('day-axis no-select md-typescale-label-small', true)
|
|
325
|
-
.attr('transform', `translate(0, 40)`)
|
|
326
|
-
.call(dayAxis);
|
|
327
|
-
centerLabel(dayAxisG, d3.timeDay);
|
|
328
|
-
}
|
|
329
|
-
else if (this.level === 'quarter-week') {
|
|
330
|
-
// Level 2: quarter-week
|
|
331
|
-
// Quarterly axis
|
|
332
|
-
const quarterAxis = d3
|
|
333
|
-
.axisTop(this.timeScale)
|
|
334
|
-
.ticks(d3.timeMonth.every(3))
|
|
335
|
-
.tickFormat(d3.timeFormat('%Y %b'))
|
|
336
|
-
.tickSize(0);
|
|
337
|
-
const quarterTicks = d3.timeMonth
|
|
338
|
-
.every(3)
|
|
339
|
-
.range(d3.timeMonth.floor(this.fromTime), d3.timeMonth.offset(this.toTime, 3));
|
|
340
|
-
addGridLines(quarterTicks, 0, 20);
|
|
341
|
-
const quarterAxisG = header
|
|
342
|
-
.append('g')
|
|
343
|
-
.classed('quarter-axis no-select md-typescale-label-small', true)
|
|
344
|
-
.attr('transform', `translate(0, 20)`)
|
|
345
|
-
.call(quarterAxis);
|
|
346
|
-
centerLabel(quarterAxisG, d3.timeMonth.every(3));
|
|
347
|
-
// Weekly axis
|
|
348
|
-
const weekAxis = d3
|
|
349
|
-
.axisTop(this.timeScale)
|
|
350
|
-
.ticks(d3.timeWeek.every(1))
|
|
351
|
-
.tickFormat(d3.timeFormat('%b %d'))
|
|
352
|
-
.tickSize(0);
|
|
353
|
-
const weekTicks = d3.timeWeek
|
|
354
|
-
.every(1)
|
|
355
|
-
.range(d3.timeWeek.floor(this.fromTime), d3.timeWeek.offset(this.toTime, 1));
|
|
356
|
-
addGridLines(weekTicks, 20, 40);
|
|
357
|
-
const weekAxisG = header
|
|
358
|
-
.append('g')
|
|
359
|
-
.classed('week-axis no-select md-typescale-label-small', true)
|
|
360
|
-
.attr('transform', `translate(0, 40)`)
|
|
361
|
-
.call(weekAxis);
|
|
362
|
-
centerLabel(weekAxisG, d3.timeWeek);
|
|
363
|
-
}
|
|
364
|
-
else if (this.level === 'year-month') {
|
|
365
|
-
// Level 3: year-month
|
|
366
|
-
// Yearly axis
|
|
367
|
-
const yearAxis = d3
|
|
368
|
-
.axisTop(this.timeScale)
|
|
369
|
-
.ticks(d3.timeYear.every(1))
|
|
370
|
-
.tickFormat(d3.timeFormat('%Y'))
|
|
371
|
-
.tickSize(0);
|
|
372
|
-
const yearTicks = d3.timeYear
|
|
373
|
-
.every(1)
|
|
374
|
-
.range(d3.timeYear.floor(this.fromTime), d3.timeYear.offset(this.toTime, 1));
|
|
375
|
-
addGridLines(yearTicks, 0, 20);
|
|
376
|
-
const yearAxisG = header
|
|
377
|
-
.append('g')
|
|
378
|
-
.classed('year-axis no-select md-typescale-label-small', true)
|
|
379
|
-
.attr('transform', `translate(0, 20)`)
|
|
380
|
-
.call(yearAxis);
|
|
381
|
-
centerLabel(yearAxisG, d3.timeYear.every(1));
|
|
382
|
-
// Monthly axis
|
|
383
|
-
const monthAxis = d3
|
|
384
|
-
.axisTop(this.timeScale)
|
|
385
|
-
.ticks(d3.timeMonth.every(1))
|
|
386
|
-
.tickFormat(d3.timeFormat('%b'))
|
|
387
|
-
.tickSize(0);
|
|
388
|
-
const monthTicks = d3.timeMonth
|
|
389
|
-
.every(1)
|
|
390
|
-
.range(d3.timeMonth.floor(this.fromTime), d3.timeMonth.offset(this.toTime, 1));
|
|
391
|
-
addGridLines(monthTicks, 20, 40);
|
|
392
|
-
const monthAxisG = header
|
|
393
|
-
.append('g')
|
|
394
|
-
.classed('month-axis no-select md-typescale-label-small', true)
|
|
395
|
-
.attr('transform', `translate(0, 40)`)
|
|
396
|
-
.call(monthAxis);
|
|
397
|
-
centerLabel(monthAxisG, d3.timeMonth.every(1));
|
|
398
|
-
}
|
|
399
|
-
else if (this.level === 'year-quarter') {
|
|
400
|
-
// Level 4: year-quarter
|
|
401
|
-
// Yearly axis
|
|
402
|
-
const yearAxis = d3
|
|
403
|
-
.axisTop(this.timeScale)
|
|
404
|
-
.ticks(d3.timeYear.every(1))
|
|
405
|
-
.tickFormat(d3.timeFormat('%Y'))
|
|
406
|
-
.tickSize(0);
|
|
407
|
-
const yearTicks = d3.timeYear
|
|
408
|
-
.every(1)
|
|
409
|
-
.range(d3.timeYear.floor(this.fromTime), d3.timeYear.offset(this.toTime, 1));
|
|
410
|
-
addGridLines(yearTicks, 0, 20);
|
|
411
|
-
const yearAxisG = header
|
|
412
|
-
.append('g')
|
|
413
|
-
.classed('year-axis no-select md-typescale-label-small', true)
|
|
414
|
-
.attr('transform', `translate(0, 20)`)
|
|
415
|
-
.call(yearAxis);
|
|
416
|
-
centerLabel(yearAxisG, d3.timeYear.every(1));
|
|
417
|
-
// Quarterly axis
|
|
418
|
-
const quarterAxis = d3
|
|
419
|
-
.axisTop(this.timeScale)
|
|
420
|
-
.ticks(d3.timeMonth.every(3))
|
|
421
|
-
.tickFormat(d3.timeFormat('%Y %b'))
|
|
422
|
-
.tickSize(0);
|
|
423
|
-
const quarterTicks = d3.timeMonth
|
|
424
|
-
.every(3)
|
|
425
|
-
.range(d3.timeMonth.floor(this.fromTime), d3.timeMonth.offset(this.toTime, 3));
|
|
426
|
-
addGridLines(quarterTicks, 20, 40);
|
|
427
|
-
const quarterAxisG = header
|
|
428
|
-
.append('g')
|
|
429
|
-
.classed('quarter-axis no-select md-typescale-label-small', true)
|
|
430
|
-
.attr('transform', `translate(0, 40)`)
|
|
431
|
-
.call(quarterAxis);
|
|
432
|
-
centerLabel(quarterAxisG, d3.timeMonth.every(3));
|
|
161
|
+
updateLocale(locale) {
|
|
162
|
+
const localeDefinition = locales[locale || 'en-US'];
|
|
163
|
+
if (localeDefinition) {
|
|
164
|
+
d3.timeFormatDefaultLocale(localeDefinition);
|
|
165
|
+
this.ganttChartController.updateTimeScale();
|
|
433
166
|
}
|
|
434
167
|
}
|
|
435
168
|
initHeaderGestureHandlers() {
|
|
@@ -438,7 +171,7 @@ let OxGanttChart = class OxGanttChart extends LitElement {
|
|
|
438
171
|
let startX = null;
|
|
439
172
|
let lastTouchDistance = null;
|
|
440
173
|
const handleTouchStart = (event) => {
|
|
441
|
-
event.preventDefault();
|
|
174
|
+
event.preventDefault();
|
|
442
175
|
if (event.touches.length === 2) {
|
|
443
176
|
lastTouchDistance = getTouchDistance(event.touches);
|
|
444
177
|
}
|
|
@@ -450,32 +183,32 @@ let OxGanttChart = class OxGanttChart extends LitElement {
|
|
|
450
183
|
}
|
|
451
184
|
};
|
|
452
185
|
const handleTouchMove = (event) => {
|
|
453
|
-
event.preventDefault();
|
|
186
|
+
event.preventDefault();
|
|
454
187
|
if (event.touches.length === 2 && lastTouchDistance !== null) {
|
|
455
188
|
const currentDistance = getTouchDistance(event.touches);
|
|
456
189
|
const zoomFactor = currentDistance / lastTouchDistance;
|
|
457
|
-
this.baseWidth *= zoomFactor;
|
|
458
|
-
if (this.baseWidth > 60) {
|
|
459
|
-
if (this.level == 'year-quarter') {
|
|
460
|
-
this.baseWidth = 60;
|
|
190
|
+
this.ganttChartController.baseWidth *= zoomFactor;
|
|
191
|
+
if (this.ganttChartController.baseWidth > 60) {
|
|
192
|
+
if (this.ganttChartController.level == 'year-quarter') {
|
|
193
|
+
this.ganttChartController.baseWidth = 60;
|
|
461
194
|
}
|
|
462
195
|
else {
|
|
463
|
-
this.level = levelUp(this.level);
|
|
464
|
-
this.baseWidth = 30;
|
|
196
|
+
this.ganttChartController.level = levelUp(this.ganttChartController.level);
|
|
197
|
+
this.ganttChartController.baseWidth = 30;
|
|
465
198
|
}
|
|
466
199
|
}
|
|
467
|
-
else if (this.baseWidth < 30) {
|
|
468
|
-
if (this.level == 'week-day') {
|
|
469
|
-
this.baseWidth = 30;
|
|
200
|
+
else if (this.ganttChartController.baseWidth < 30) {
|
|
201
|
+
if (this.ganttChartController.level == 'week-day') {
|
|
202
|
+
this.ganttChartController.baseWidth = 30;
|
|
470
203
|
}
|
|
471
204
|
else {
|
|
472
|
-
this.level = levelDown(this.level);
|
|
473
|
-
this.baseWidth = 60;
|
|
205
|
+
this.ganttChartController.level = levelDown(this.ganttChartController.level);
|
|
206
|
+
this.ganttChartController.baseWidth = 60;
|
|
474
207
|
}
|
|
475
208
|
}
|
|
476
209
|
lastTouchDistance = currentDistance;
|
|
477
|
-
this.updateTimeScale();
|
|
478
|
-
this.
|
|
210
|
+
this.ganttChartController.updateTimeScale();
|
|
211
|
+
this.requestUpdate();
|
|
479
212
|
}
|
|
480
213
|
else if (isDragging && startX !== null) {
|
|
481
214
|
const touch = event.touches[0];
|
|
@@ -485,12 +218,11 @@ let OxGanttChart = class OxGanttChart extends LitElement {
|
|
|
485
218
|
}
|
|
486
219
|
};
|
|
487
220
|
const handleTouchEnd = (event) => {
|
|
488
|
-
event.preventDefault();
|
|
221
|
+
event.preventDefault();
|
|
489
222
|
if (event.touches.length < 2) {
|
|
490
223
|
lastTouchDistance = null;
|
|
491
224
|
}
|
|
492
225
|
if (!isDragging || startX === null) {
|
|
493
|
-
// 클릭 이벤트 처리
|
|
494
226
|
const touch = event.changedTouches[0];
|
|
495
227
|
const containerOffsetX = header.node().getBoundingClientRect().left;
|
|
496
228
|
const clickX = touch.clientX - containerOffsetX;
|
|
@@ -523,7 +255,6 @@ let OxGanttChart = class OxGanttChart extends LitElement {
|
|
|
523
255
|
};
|
|
524
256
|
const handleMouseUp = (event) => {
|
|
525
257
|
if (!isDragging || startX === null) {
|
|
526
|
-
// 클릭 이벤트 처리
|
|
527
258
|
const containerOffsetX = header.node().getBoundingClientRect().left;
|
|
528
259
|
const clickX = event.clientX - containerOffsetX;
|
|
529
260
|
this.finalizeSelection(clickX, clickX);
|
|
@@ -545,22 +276,22 @@ let OxGanttChart = class OxGanttChart extends LitElement {
|
|
|
545
276
|
highlightSelection(startX, endX) {
|
|
546
277
|
const header = d3.select(this.svgHeader);
|
|
547
278
|
const body = d3.select(this.svgBody);
|
|
548
|
-
let startDate = this.timeScale.invert(startX);
|
|
549
|
-
let endDate = this.timeScale.invert(endX);
|
|
279
|
+
let startDate = this.ganttChartController.timeScale.invert(startX);
|
|
280
|
+
let endDate = this.ganttChartController.timeScale.invert(endX);
|
|
550
281
|
let start, end;
|
|
551
|
-
if (this.level === 'week-day') {
|
|
282
|
+
if (this.ganttChartController.level === 'week-day') {
|
|
552
283
|
start = d3.timeDay.floor(d3.min([startDate, endDate]));
|
|
553
284
|
end = d3.timeDay.offset(d3.timeDay.ceil(d3.max([startDate, endDate])), -1);
|
|
554
285
|
}
|
|
555
|
-
else if (this.level === 'quarter-week') {
|
|
286
|
+
else if (this.ganttChartController.level === 'quarter-week') {
|
|
556
287
|
start = d3.timeWeek.floor(d3.min([startDate, endDate]));
|
|
557
288
|
end = d3.timeDay.offset(d3.timeWeek.ceil(d3.max([startDate, endDate])), -1);
|
|
558
289
|
}
|
|
559
|
-
else if (this.level === 'year-month') {
|
|
290
|
+
else if (this.ganttChartController.level === 'year-month') {
|
|
560
291
|
start = d3.timeMonth.floor(d3.min([startDate, endDate]));
|
|
561
292
|
end = d3.timeDay.offset(d3.timeMonth.ceil(d3.max([startDate, endDate])), -1);
|
|
562
293
|
}
|
|
563
|
-
else if (this.level === 'year-quarter') {
|
|
294
|
+
else if (this.ganttChartController.level === 'year-quarter') {
|
|
564
295
|
start = d3.timeMonth.every(3).floor(d3.min([startDate, endDate]));
|
|
565
296
|
end = d3.timeDay.offset(d3.timeMonth.every(3).ceil(d3.max([startDate, endDate])), -1);
|
|
566
297
|
}
|
|
@@ -569,18 +300,22 @@ let OxGanttChart = class OxGanttChart extends LitElement {
|
|
|
569
300
|
header
|
|
570
301
|
.append('rect')
|
|
571
302
|
.attr('class', 'selection-rect')
|
|
572
|
-
.attr('x', this.timeScale(start))
|
|
303
|
+
.attr('x', this.ganttChartController.timeScale(start))
|
|
573
304
|
.attr('y', 20)
|
|
574
|
-
.attr('width', this.timeScale(end) -
|
|
305
|
+
.attr('width', this.ganttChartController.timeScale(end) -
|
|
306
|
+
this.ganttChartController.timeScale(start) +
|
|
307
|
+
this.ganttChartController.baseWidth / this.ganttChartController.getScale())
|
|
575
308
|
.attr('height', 20)
|
|
576
309
|
.attr('fill', 'var(--ox-gantt-selection-color, var(--md-sys-color-secondary))')
|
|
577
310
|
.attr('opacity', '.2');
|
|
578
311
|
body
|
|
579
312
|
.append('rect')
|
|
580
313
|
.attr('class', 'selection-rect')
|
|
581
|
-
.attr('x', this.timeScale(start))
|
|
314
|
+
.attr('x', this.ganttChartController.timeScale(start))
|
|
582
315
|
.attr('y', 0)
|
|
583
|
-
.attr('width', this.timeScale(end) -
|
|
316
|
+
.attr('width', this.ganttChartController.timeScale(end) -
|
|
317
|
+
this.ganttChartController.timeScale(start) +
|
|
318
|
+
this.ganttChartController.baseWidth / this.ganttChartController.getScale())
|
|
584
319
|
.attr('height', '100%')
|
|
585
320
|
.attr('fill', 'var(--ox-gantt-selection-color, var(--md-sys-color-secondary))')
|
|
586
321
|
.attr('opacity', '.2');
|
|
@@ -588,22 +323,22 @@ let OxGanttChart = class OxGanttChart extends LitElement {
|
|
|
588
323
|
finalizeSelection(startX, endX) {
|
|
589
324
|
const header = d3.select(this.svgHeader);
|
|
590
325
|
const body = d3.select(this.svgBody);
|
|
591
|
-
let startDate = this.timeScale.invert(startX);
|
|
592
|
-
let endDate = this.timeScale.invert(endX);
|
|
326
|
+
let startDate = this.ganttChartController.timeScale.invert(startX);
|
|
327
|
+
let endDate = this.ganttChartController.timeScale.invert(endX);
|
|
593
328
|
let start, end;
|
|
594
|
-
if (this.level === 'week-day') {
|
|
329
|
+
if (this.ganttChartController.level === 'week-day') {
|
|
595
330
|
start = d3.timeDay.floor(d3.min([startDate, endDate]));
|
|
596
331
|
end = d3.timeDay.offset(d3.timeDay.ceil(d3.max([startDate, endDate])), -1);
|
|
597
332
|
}
|
|
598
|
-
else if (this.level === 'quarter-week') {
|
|
333
|
+
else if (this.ganttChartController.level === 'quarter-week') {
|
|
599
334
|
start = d3.timeWeek.floor(d3.min([startDate, endDate]));
|
|
600
335
|
end = d3.timeDay.offset(d3.timeWeek.ceil(d3.max([startDate, endDate])), -1);
|
|
601
336
|
}
|
|
602
|
-
else if (this.level === 'year-month') {
|
|
337
|
+
else if (this.ganttChartController.level === 'year-month') {
|
|
603
338
|
start = d3.timeMonth.floor(d3.min([startDate, endDate]));
|
|
604
339
|
end = d3.timeDay.offset(d3.timeMonth.ceil(d3.max([startDate, endDate])), -1);
|
|
605
340
|
}
|
|
606
|
-
else if (this.level === 'year-quarter') {
|
|
341
|
+
else if (this.ganttChartController.level === 'year-quarter') {
|
|
607
342
|
start = d3.timeMonth.every(3).floor(d3.min([startDate, endDate]));
|
|
608
343
|
end = d3.timeDay.offset(d3.timeMonth.every(3).ceil(d3.max([startDate, endDate])), -1);
|
|
609
344
|
}
|
|
@@ -612,18 +347,22 @@ let OxGanttChart = class OxGanttChart extends LitElement {
|
|
|
612
347
|
header
|
|
613
348
|
.append('rect')
|
|
614
349
|
.attr('class', 'selection-rect')
|
|
615
|
-
.attr('x', this.timeScale(start))
|
|
350
|
+
.attr('x', this.ganttChartController.timeScale(start))
|
|
616
351
|
.attr('y', 20)
|
|
617
|
-
.attr('width', this.timeScale(end) -
|
|
352
|
+
.attr('width', this.ganttChartController.timeScale(end) -
|
|
353
|
+
this.ganttChartController.timeScale(start) +
|
|
354
|
+
this.ganttChartController.baseWidth / this.ganttChartController.getScale())
|
|
618
355
|
.attr('height', 20)
|
|
619
356
|
.attr('fill', 'var(--ox-gantt-selection-color, var(--md-sys-color-secondary))')
|
|
620
357
|
.attr('opacity', '.2');
|
|
621
358
|
body
|
|
622
359
|
.append('rect')
|
|
623
360
|
.attr('class', 'selection-rect')
|
|
624
|
-
.attr('x', this.timeScale(start))
|
|
361
|
+
.attr('x', this.ganttChartController.timeScale(start))
|
|
625
362
|
.attr('y', 0)
|
|
626
|
-
.attr('width', this.timeScale(end) -
|
|
363
|
+
.attr('width', this.ganttChartController.timeScale(end) -
|
|
364
|
+
this.ganttChartController.timeScale(start) +
|
|
365
|
+
this.ganttChartController.baseWidth / this.ganttChartController.getScale())
|
|
627
366
|
.attr('height', '100%')
|
|
628
367
|
.attr('fill', 'var(--ox-gantt-selection-color, var(--md-sys-color-secondary))')
|
|
629
368
|
.attr('opacity', '.2');
|
|
@@ -633,42 +372,6 @@ let OxGanttChart = class OxGanttChart extends LitElement {
|
|
|
633
372
|
});
|
|
634
373
|
header.node()?.dispatchEvent(customEvent);
|
|
635
374
|
}
|
|
636
|
-
handleWheelForHeader(event) {
|
|
637
|
-
event.preventDefault();
|
|
638
|
-
event.stopPropagation();
|
|
639
|
-
if (event.altKey || event.ctrlKey || event.shiftKey || event.metaKey) {
|
|
640
|
-
// 좌우 스크롤
|
|
641
|
-
this.container.scrollBy({
|
|
642
|
-
left: event.shiftKey ? event.deltaX : event.deltaY,
|
|
643
|
-
behavior: 'smooth'
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
else {
|
|
647
|
-
// Zoom in/out
|
|
648
|
-
const zoomFactor = event.deltaY < 0 ? 1.1 : 0.9;
|
|
649
|
-
this.baseWidth *= zoomFactor;
|
|
650
|
-
if (this.baseWidth > 40) {
|
|
651
|
-
if (this.level == 'week-day') {
|
|
652
|
-
this.baseWidth = 40;
|
|
653
|
-
}
|
|
654
|
-
else {
|
|
655
|
-
this.level = levelDown(this.level);
|
|
656
|
-
this.baseWidth = 30;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
else if (this.baseWidth < 30) {
|
|
660
|
-
if (this.level == 'year-quarter') {
|
|
661
|
-
this.baseWidth = 30;
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
if (this.baseWidth < 20) {
|
|
665
|
-
this.level = levelUp(this.level);
|
|
666
|
-
this.baseWidth = 30;
|
|
667
|
-
}
|
|
668
|
-
this.updateTimeScale();
|
|
669
|
-
this.redraw();
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
375
|
};
|
|
673
376
|
__decorate([
|
|
674
377
|
property({ type: String, attribute: 'from-date' })
|
|
@@ -686,11 +389,11 @@ __decorate([
|
|
|
686
389
|
property({ type: Array })
|
|
687
390
|
], OxGanttChart.prototype, "tasks", void 0);
|
|
688
391
|
__decorate([
|
|
689
|
-
|
|
690
|
-
], OxGanttChart.prototype, "
|
|
392
|
+
property({ type: String, attribute: 'selected-task-id' })
|
|
393
|
+
], OxGanttChart.prototype, "selectedTaskId", void 0);
|
|
691
394
|
__decorate([
|
|
692
|
-
|
|
693
|
-
], OxGanttChart.prototype, "
|
|
395
|
+
property({ type: String })
|
|
396
|
+
], OxGanttChart.prototype, "locale", void 0);
|
|
694
397
|
__decorate([
|
|
695
398
|
query('svg[header]')
|
|
696
399
|
], OxGanttChart.prototype, "svgHeader", void 0);
|
|
@@ -700,6 +403,9 @@ __decorate([
|
|
|
700
403
|
__decorate([
|
|
701
404
|
query('[chart-container]')
|
|
702
405
|
], OxGanttChart.prototype, "container", void 0);
|
|
406
|
+
__decorate([
|
|
407
|
+
query('ox-task-tooltip')
|
|
408
|
+
], OxGanttChart.prototype, "tooltip", void 0);
|
|
703
409
|
OxGanttChart = __decorate([
|
|
704
410
|
customElement('ox-gantt-chart')
|
|
705
411
|
], OxGanttChart);
|