@operato/gantt 2.0.0-beta.5 → 7.0.0-rc.10

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 (47) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/dist/src/gantt-chart-controller.d.ts +32 -0
  3. package/dist/src/gantt-chart-controller.js +395 -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 +11 -20
  9. package/dist/src/ox-gantt-chart.js +141 -435
  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 +17 -3
  15. package/dist/src/ox-gantt.js +169 -29
  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 +11 -4
  21. package/dist/src/types.js.map +1 -1
  22. package/dist/src/utils.d.ts +3 -3
  23. package/dist/src/utils.js.map +1 -1
  24. package/dist/stories/ox-gantt.stories.d.ts +19 -2
  25. package/dist/stories/ox-gantt.stories.js +104 -90
  26. package/dist/stories/ox-gantt.stories.js.map +1 -1
  27. package/dist/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +7 -4
  29. package/src/gantt-chart-controller.ts +473 -0
  30. package/src/locales.ts +78 -0
  31. package/src/ox-gantt-chart.ts +151 -484
  32. package/src/ox-gantt-resize-splitter.ts +18 -25
  33. package/src/ox-gantt.ts +174 -31
  34. package/src/ox-task-tooltip.ts +120 -0
  35. package/src/types.ts +13 -5
  36. package/src/utils.ts +4 -4
  37. package/stories/ox-gantt.stories.ts +118 -91
  38. package/themes/grist-theme.css +1 -3
  39. package/translations/en.json +11 -1
  40. package/translations/ja.json +11 -1
  41. package/translations/ko.json +11 -1
  42. package/translations/ms.json +11 -1
  43. package/translations/zh.json +11 -1
  44. package/dist/src/locale.d.ts +0 -1
  45. package/dist/src/locale.js +0 -49
  46. package/dist/src/locale.js.map +0 -1
  47. 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, 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%;
48
32
  box-sizing: border-box;
49
- border-bottom: 1px solid var(--ox-gantt-border-color, var(--md-sys-color-on-surface));
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.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
- 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.svgBody.style.height = this.tasks.length * 30 + 'px';
92
- redrawRequired = true;
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
- redrawRequired && this.redraw();
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 chart-container>
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
- getScale() {
115
- if (this.level == 'quarter-week') {
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
- get timeScale() {
130
- if (!this._timeScale) {
131
- // 시간축 스케일 생성
132
- this._timeScale = d3
133
- .scaleTime()
134
- .domain([d3.timeDay.floor(this.fromTime), d3.timeDay.offset(this.toTime, 1)])
135
- .range([0, this.getChartWidth()]);
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
- return this._timeScale;
138
- }
139
- updateTimeScale() {
140
- this.timeScale.range([0, this.getChartWidth()]);
141
- }
142
- redraw() {
143
- this.drawAxes();
144
- this.drawBody();
145
- }
146
- drawBody() {
147
- const body = d3.select(this.svgBody);
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.level === 'year-month') {
175
- const monthTicks = d3.timeMonth
176
- .every(1)
177
- .range(d3.timeMonth.floor(this.fromTime), d3.timeMonth.offset(this.toTime, 1));
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
- else if (this.level === 'year-quarter') {
181
- const quarterTicks = d3.timeMonth
182
- .every(3)
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
- // Helper function to determine task color based on tags
257
- getTaskColor(tags) {
258
- if (tags.includes('critical')) {
259
- return 'red';
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.redraw();
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) - this.timeScale(start) + this.baseWidth / this.getScale())
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) - this.timeScale(start) + this.baseWidth / this.getScale())
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) - this.timeScale(start) + this.baseWidth / this.getScale())
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) - this.timeScale(start) + this.baseWidth / this.getScale())
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
- state()
690
- ], OxGanttChart.prototype, "baseWidth", void 0);
392
+ property({ type: String, attribute: 'selected-task-id' })
393
+ ], OxGanttChart.prototype, "selectedTaskId", void 0);
691
394
  __decorate([
692
- state()
693
- ], OxGanttChart.prototype, "level", void 0);
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);