@operato/gantt 2.0.0-beta.14 → 2.0.0-beta.15

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/src/gantt-chart-controller.d.ts +32 -0
  3. package/dist/src/gantt-chart-controller.js +393 -0
  4. package/dist/src/gantt-chart-controller.js.map +1 -0
  5. package/dist/src/locales.d.ts +52 -0
  6. package/dist/src/locales.js +79 -0
  7. package/dist/src/locales.js.map +1 -0
  8. package/dist/src/ox-gantt-chart.d.ts +10 -17
  9. package/dist/src/ox-gantt-chart.js +134 -440
  10. package/dist/src/ox-gantt-chart.js.map +1 -1
  11. package/dist/src/ox-gantt-resize-splitter.d.ts +2 -3
  12. package/dist/src/ox-gantt-resize-splitter.js +15 -22
  13. package/dist/src/ox-gantt-resize-splitter.js.map +1 -1
  14. package/dist/src/ox-gantt.d.ts +13 -1
  15. package/dist/src/ox-gantt.js +144 -27
  16. package/dist/src/ox-gantt.js.map +1 -1
  17. package/dist/src/ox-task-tooltip.d.ts +12 -0
  18. package/dist/src/ox-task-tooltip.js +132 -0
  19. package/dist/src/ox-task-tooltip.js.map +1 -0
  20. package/dist/src/types.d.ts +10 -3
  21. package/dist/src/types.js.map +1 -1
  22. package/dist/stories/ox-gantt.stories.js +91 -85
  23. package/dist/stories/ox-gantt.stories.js.map +1 -1
  24. package/dist/tsconfig.tsbuildinfo +1 -1
  25. package/package.json +3 -2
  26. package/src/gantt-chart-controller.ts +470 -0
  27. package/src/locales.ts +78 -0
  28. package/src/ox-gantt-chart.ts +141 -487
  29. package/src/ox-gantt-resize-splitter.ts +18 -25
  30. package/src/ox-gantt.ts +151 -29
  31. package/src/ox-task-tooltip.ts +120 -0
  32. package/src/types.ts +11 -3
  33. package/stories/ox-gantt.stories.ts +93 -85
  34. package/translations/en.json +11 -1
  35. package/translations/ja.json +11 -1
  36. package/translations/ko.json +11 -1
  37. package/translations/ms.json +11 -1
  38. package/translations/zh.json +11 -1
  39. package/dist/src/locale.d.ts +0 -1
  40. package/dist/src/locale.js +0 -49
  41. package/dist/src/locale.js.map +0 -1
  42. 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, state } from 'lit/decorators.js';
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 { levelDown, levelUp } from './utils';
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: clip;
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.fromTime = new Date(this.fromDate);
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('axisScale')) {
89
- redrawRequired = true;
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
- if (changedProperties.has('extendGridLines')) {
92
- redrawRequired = true;
85
+ // Update locale
86
+ if (changedProperties.has('locale')) {
87
+ this.updateLocale(this.locale);
93
88
  }
94
- if (changedProperties.has('tasks')) {
95
- const chartContainerHeight = this.container.getBoundingClientRect().height;
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
- redrawRequired && this.redraw();
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 chart-container>
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
- getScale() {
121
- if (this.level == 'quarter-week') {
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
- get timeScale() {
136
- if (!this._timeScale) {
137
- // 시간축 스케일 생성
138
- this._timeScale = d3
139
- .scaleTime()
140
- .domain([d3.timeDay.floor(this.fromTime), d3.timeDay.offset(this.toTime, 1)])
141
- .range([0, this.getChartWidth()]);
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
- return this._timeScale;
144
- }
145
- updateTimeScale() {
146
- this.timeScale.range([0, this.getChartWidth()]);
147
- }
148
- redraw() {
149
- this.drawAxes();
150
- this.drawBody();
151
- }
152
- drawBody() {
153
- const body = d3.select(this.svgBody);
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
- const taskPositions = {};
194
- // Add task bars
195
- this.tasks.forEach((task, index) => {
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
- // Save task position for linking lines
231
- taskPositions[task.id] = { x: endX + this.baseWidth / this.getScale(), y: taskY + 7.5, width: taskWidth };
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
- drawAxes() {
278
- const header = d3.select(this.svgHeader);
279
- header.selectAll('*').remove();
280
- header.attr('width', this.getChartWidth());
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.redraw();
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) - this.timeScale(start) + this.baseWidth / this.getScale())
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) - this.timeScale(start) + this.baseWidth / this.getScale())
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) - this.timeScale(start) + this.baseWidth / this.getScale())
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) - this.timeScale(start) + this.baseWidth / this.getScale())
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
- state()
696
- ], OxGanttChart.prototype, "baseWidth", void 0);
386
+ property({ type: String, attribute: 'selected-task-id' })
387
+ ], OxGanttChart.prototype, "selectedTaskId", void 0);
697
388
  __decorate([
698
- state()
699
- ], OxGanttChart.prototype, "level", void 0);
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);