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