@operato/gantt 2.0.0-beta.0 → 2.0.0-beta.1

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 (55) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/src/locale.d.ts +1 -0
  3. package/dist/src/locale.js +49 -0
  4. package/dist/src/locale.js.map +1 -0
  5. package/dist/src/ox-gantt-chart.d.ts +39 -0
  6. package/dist/src/ox-gantt-chart.js +707 -0
  7. package/dist/src/ox-gantt-chart.js.map +1 -0
  8. package/dist/src/ox-gantt-resize-splitter.d.ts +13 -0
  9. package/dist/src/ox-gantt-resize-splitter.js +117 -0
  10. package/dist/src/ox-gantt-resize-splitter.js.map +1 -0
  11. package/dist/src/ox-gantt.d.ts +24 -0
  12. package/dist/src/ox-gantt.js +186 -0
  13. package/dist/src/ox-gantt.js.map +1 -0
  14. package/dist/src/types.d.ts +10 -5
  15. package/dist/src/types.js.map +1 -1
  16. package/dist/src/utils.d.ts +3 -0
  17. package/dist/src/utils.js +10 -0
  18. package/dist/src/utils.js.map +1 -0
  19. package/dist/stories/ox-gantt-chart.stories.d.ts +41 -0
  20. package/dist/stories/{ox-lite-gantt.stories.js → ox-gantt-chart.stories.js} +53 -21
  21. package/dist/stories/ox-gantt-chart.stories.js.map +1 -0
  22. package/dist/stories/ox-gantt.stories.d.ts +6 -1
  23. package/dist/stories/ox-gantt.stories.js +286 -15
  24. package/dist/stories/ox-gantt.stories.js.map +1 -1
  25. package/dist/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +12 -4
  27. package/src/locale.ts +51 -0
  28. package/src/ox-gantt-chart.ts +773 -0
  29. package/src/ox-gantt-resize-splitter.ts +137 -0
  30. package/src/ox-gantt.ts +177 -0
  31. package/src/types.ts +12 -6
  32. package/src/utils.ts +13 -0
  33. package/stories/{ox-lite-gantt.stories.ts → ox-gantt-chart.stories.ts} +65 -22
  34. package/stories/ox-gantt.stories.ts +289 -16
  35. package/themes/dark-hc.css +151 -0
  36. package/themes/dark-mc.css +151 -0
  37. package/themes/dark.css +151 -0
  38. package/themes/grist-theme.css +169 -0
  39. package/themes/light-hc.css +151 -0
  40. package/themes/light-mc.css +151 -0
  41. package/themes/light.css +151 -0
  42. package/themes/md-typescale-styles.css +100 -0
  43. package/themes/spacing.css +43 -0
  44. package/themes/state-color.css +6 -0
  45. package/dist/src/gantt.d.ts +0 -28
  46. package/dist/src/gantt.js +0 -178
  47. package/dist/src/gantt.js.map +0 -1
  48. package/dist/src/lite-gantt.d.ts +0 -27
  49. package/dist/src/lite-gantt.js +0 -146
  50. package/dist/src/lite-gantt.js.map +0 -1
  51. package/dist/stories/ox-lite-gantt.stories.d.ts +0 -22
  52. package/dist/stories/ox-lite-gantt.stories.js.map +0 -1
  53. package/src/gantt.ts +0 -191
  54. package/src/lite-gantt.ts +0 -152
  55. package/src/renderer.txt +0 -775
@@ -0,0 +1,707 @@
1
+ import { __decorate } from "tslib";
2
+ 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';
5
+ import * as d3 from 'd3';
6
+ import { levelDown, levelUp } from './utils';
7
+ import { ScrollbarStyles } from '@operato/styles';
8
+ 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
+ static { this.styles = [
23
+ MDTypeScaleStyles,
24
+ ScrollbarStyles,
25
+ css `
26
+ :host {
27
+ display: flex;
28
+
29
+ overflow: hidden;
30
+ }
31
+
32
+ div[chart-container] {
33
+ display: block;
34
+
35
+ overflow-x: auto;
36
+ overflow-y: clip;
37
+
38
+ position: relative;
39
+ }
40
+
41
+ svg {
42
+ min-width: 100%;
43
+ }
44
+
45
+ svg[header] {
46
+ display: block;
47
+ min-width: 100%;
48
+ box-sizing: border-box;
49
+ border-bottom: 1px solid var(--ox-gantt-border-color, var(--md-sys-color-on-surface));
50
+ height: 40px;
51
+ background-color: var(--ox-gantt-header-background-color, var(--md-sys-color-secondary-container));
52
+
53
+ position: sticky;
54
+ top: 0;
55
+ z-index: 1;
56
+ }
57
+
58
+ .top-axis text,
59
+ .bottom-axis text {
60
+ font-size: 12px;
61
+ }
62
+
63
+ .grid line {
64
+ stroke: #e0e0e0;
65
+ }
66
+
67
+ .grid path {
68
+ stroke-width: 0;
69
+ }
70
+
71
+ .no-select {
72
+ user-select: none;
73
+ }
74
+ `
75
+ ]; }
76
+ firstUpdated(_changedProperties) {
77
+ this.initHeaderGestureHandlers();
78
+ }
79
+ updated(changedProperties) {
80
+ var redrawRequired = false;
81
+ 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;
89
+ }
90
+ if (changedProperties.has('tasks')) {
91
+ this.svgBody.style.height = this.tasks.length * 30 + 'px';
92
+ redrawRequired = true;
93
+ }
94
+ redrawRequired && this.redraw();
95
+ }
96
+ render() {
97
+ return html `
98
+ <div chart-container>
99
+ <svg
100
+ header
101
+ @date-range-selected=${(e) => {
102
+ console.log(e.detail);
103
+ }}
104
+ @wheel=${this.handleWheelForHeader.bind(this)}
105
+ ></svg>
106
+ <svg body></svg>
107
+ </div>
108
+ `;
109
+ }
110
+ setVerticalOffset(offset) {
111
+ console.log(offset);
112
+ this.container.scrollTop = offset;
113
+ }
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(); // 총 차트 폭 계산
128
+ }
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()]);
136
+ }
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);
173
+ }
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);
179
+ }
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);
185
+ }
186
+ }
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
+ }
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));
433
+ }
434
+ }
435
+ initHeaderGestureHandlers() {
436
+ const header = d3.select(this.svgHeader);
437
+ let isDragging = false;
438
+ let startX = null;
439
+ let lastTouchDistance = null;
440
+ const handleTouchStart = (event) => {
441
+ event.preventDefault(); // 기본 터치 이벤트 막기
442
+ if (event.touches.length === 2) {
443
+ lastTouchDistance = getTouchDistance(event.touches);
444
+ }
445
+ else {
446
+ isDragging = true;
447
+ const touch = event.touches[0];
448
+ const containerOffsetX = header.node().getBoundingClientRect().left;
449
+ startX = touch.clientX - containerOffsetX;
450
+ }
451
+ };
452
+ const handleTouchMove = (event) => {
453
+ event.preventDefault(); // 기본 터치 이벤트 막기
454
+ if (event.touches.length === 2 && lastTouchDistance !== null) {
455
+ const currentDistance = getTouchDistance(event.touches);
456
+ const zoomFactor = currentDistance / lastTouchDistance;
457
+ this.baseWidth *= zoomFactor;
458
+ if (this.baseWidth > 60) {
459
+ if (this.level == 'year-quarter') {
460
+ this.baseWidth = 60;
461
+ }
462
+ else {
463
+ this.level = levelUp(this.level);
464
+ this.baseWidth = 30;
465
+ }
466
+ }
467
+ else if (this.baseWidth < 30) {
468
+ if (this.level == 'week-day') {
469
+ this.baseWidth = 30;
470
+ }
471
+ else {
472
+ this.level = levelDown(this.level);
473
+ this.baseWidth = 60;
474
+ }
475
+ }
476
+ lastTouchDistance = currentDistance;
477
+ this.updateTimeScale();
478
+ this.redraw();
479
+ }
480
+ else if (isDragging && startX !== null) {
481
+ const touch = event.touches[0];
482
+ const containerOffsetX = header.node().getBoundingClientRect().left;
483
+ const currentX = touch.clientX - containerOffsetX;
484
+ this.highlightSelection(startX, currentX);
485
+ }
486
+ };
487
+ const handleTouchEnd = (event) => {
488
+ event.preventDefault(); // 기본 터치 이벤트 막기
489
+ if (event.touches.length < 2) {
490
+ lastTouchDistance = null;
491
+ }
492
+ if (!isDragging || startX === null) {
493
+ // 클릭 이벤트 처리
494
+ const touch = event.changedTouches[0];
495
+ const containerOffsetX = header.node().getBoundingClientRect().left;
496
+ const clickX = touch.clientX - containerOffsetX;
497
+ this.finalizeSelection(clickX, clickX);
498
+ return;
499
+ }
500
+ const touch = event.changedTouches[0];
501
+ const containerOffsetX = header.node().getBoundingClientRect().left;
502
+ const endX = touch.clientX - containerOffsetX;
503
+ this.finalizeSelection(startX, endX);
504
+ isDragging = false;
505
+ startX = null;
506
+ };
507
+ const getTouchDistance = (touches) => {
508
+ const dx = touches[0].clientX - touches[1].clientX;
509
+ const dy = touches[0].clientY - touches[1].clientY;
510
+ return Math.sqrt(dx * dx + dy * dy);
511
+ };
512
+ const handleMouseDown = (event) => {
513
+ isDragging = true;
514
+ const containerOffsetX = header.node().getBoundingClientRect().left;
515
+ startX = event.clientX - containerOffsetX;
516
+ };
517
+ const handleMouseMove = (event) => {
518
+ if (!isDragging || startX === null)
519
+ return;
520
+ const containerOffsetX = header.node().getBoundingClientRect().left;
521
+ const currentX = event.clientX - containerOffsetX;
522
+ this.highlightSelection(startX, currentX);
523
+ };
524
+ const handleMouseUp = (event) => {
525
+ if (!isDragging || startX === null) {
526
+ // 클릭 이벤트 처리
527
+ const containerOffsetX = header.node().getBoundingClientRect().left;
528
+ const clickX = event.clientX - containerOffsetX;
529
+ this.finalizeSelection(clickX, clickX);
530
+ return;
531
+ }
532
+ const containerOffsetX = header.node().getBoundingClientRect().left;
533
+ const endX = event.clientX - containerOffsetX;
534
+ this.finalizeSelection(startX, endX);
535
+ isDragging = false;
536
+ startX = null;
537
+ };
538
+ header.on('mousedown', handleMouseDown);
539
+ header.on('mousemove', handleMouseMove);
540
+ header.on('mouseup', handleMouseUp);
541
+ header.on('touchstart', handleTouchStart);
542
+ header.on('touchmove', handleTouchMove);
543
+ header.on('touchend', handleTouchEnd);
544
+ }
545
+ highlightSelection(startX, endX) {
546
+ const header = d3.select(this.svgHeader);
547
+ const body = d3.select(this.svgBody);
548
+ let startDate = this.timeScale.invert(startX);
549
+ let endDate = this.timeScale.invert(endX);
550
+ let start, end;
551
+ if (this.level === 'week-day') {
552
+ start = d3.timeDay.floor(d3.min([startDate, endDate]));
553
+ end = d3.timeDay.offset(d3.timeDay.ceil(d3.max([startDate, endDate])), -1);
554
+ }
555
+ else if (this.level === 'quarter-week') {
556
+ start = d3.timeWeek.floor(d3.min([startDate, endDate]));
557
+ end = d3.timeDay.offset(d3.timeWeek.ceil(d3.max([startDate, endDate])), -1);
558
+ }
559
+ else if (this.level === 'year-month') {
560
+ start = d3.timeMonth.floor(d3.min([startDate, endDate]));
561
+ end = d3.timeDay.offset(d3.timeMonth.ceil(d3.max([startDate, endDate])), -1);
562
+ }
563
+ else if (this.level === 'year-quarter') {
564
+ start = d3.timeMonth.every(3).floor(d3.min([startDate, endDate]));
565
+ end = d3.timeDay.offset(d3.timeMonth.every(3).ceil(d3.max([startDate, endDate])), -1);
566
+ }
567
+ header.selectAll('.selection-rect').remove();
568
+ body.selectAll('.selection-rect').remove();
569
+ header
570
+ .append('rect')
571
+ .attr('class', 'selection-rect')
572
+ .attr('x', this.timeScale(start))
573
+ .attr('y', 20)
574
+ .attr('width', this.timeScale(end) - this.timeScale(start) + this.baseWidth / this.getScale())
575
+ .attr('height', 20)
576
+ .attr('fill', 'var(--ox-gantt-selection-color, var(--md-sys-color-secondary))')
577
+ .attr('opacity', '.2');
578
+ body
579
+ .append('rect')
580
+ .attr('class', 'selection-rect')
581
+ .attr('x', this.timeScale(start))
582
+ .attr('y', 0)
583
+ .attr('width', this.timeScale(end) - this.timeScale(start) + this.baseWidth / this.getScale())
584
+ .attr('height', '100%')
585
+ .attr('fill', 'var(--ox-gantt-selection-color, var(--md-sys-color-secondary))')
586
+ .attr('opacity', '.2');
587
+ }
588
+ finalizeSelection(startX, endX) {
589
+ const header = d3.select(this.svgHeader);
590
+ const body = d3.select(this.svgBody);
591
+ let startDate = this.timeScale.invert(startX);
592
+ let endDate = this.timeScale.invert(endX);
593
+ let start, end;
594
+ if (this.level === 'week-day') {
595
+ start = d3.timeDay.floor(d3.min([startDate, endDate]));
596
+ end = d3.timeDay.offset(d3.timeDay.ceil(d3.max([startDate, endDate])), -1);
597
+ }
598
+ else if (this.level === 'quarter-week') {
599
+ start = d3.timeWeek.floor(d3.min([startDate, endDate]));
600
+ end = d3.timeDay.offset(d3.timeWeek.ceil(d3.max([startDate, endDate])), -1);
601
+ }
602
+ else if (this.level === 'year-month') {
603
+ start = d3.timeMonth.floor(d3.min([startDate, endDate]));
604
+ end = d3.timeDay.offset(d3.timeMonth.ceil(d3.max([startDate, endDate])), -1);
605
+ }
606
+ else if (this.level === 'year-quarter') {
607
+ start = d3.timeMonth.every(3).floor(d3.min([startDate, endDate]));
608
+ end = d3.timeDay.offset(d3.timeMonth.every(3).ceil(d3.max([startDate, endDate])), -1);
609
+ }
610
+ header.selectAll('.selection-rect').remove();
611
+ body.selectAll('.selection-rect').remove();
612
+ header
613
+ .append('rect')
614
+ .attr('class', 'selection-rect')
615
+ .attr('x', this.timeScale(start))
616
+ .attr('y', 20)
617
+ .attr('width', this.timeScale(end) - this.timeScale(start) + this.baseWidth / this.getScale())
618
+ .attr('height', 20)
619
+ .attr('fill', 'var(--ox-gantt-selection-color, var(--md-sys-color-secondary))')
620
+ .attr('opacity', '.2');
621
+ body
622
+ .append('rect')
623
+ .attr('class', 'selection-rect')
624
+ .attr('x', this.timeScale(start))
625
+ .attr('y', 0)
626
+ .attr('width', this.timeScale(end) - this.timeScale(start) + this.baseWidth / this.getScale())
627
+ .attr('height', '100%')
628
+ .attr('fill', 'var(--ox-gantt-selection-color, var(--md-sys-color-secondary))')
629
+ .attr('opacity', '.2');
630
+ const formatDate = d3.timeFormat('%Y-%m-%d');
631
+ const customEvent = new CustomEvent('date-range-selected', {
632
+ detail: { start: formatDate(start), end: formatDate(end) }
633
+ });
634
+ header.node()?.dispatchEvent(customEvent);
635
+ }
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
+ };
673
+ __decorate([
674
+ property({ type: String, attribute: 'from-date' })
675
+ ], OxGanttChart.prototype, "fromDate", void 0);
676
+ __decorate([
677
+ property({ type: String, attribute: 'to-date' })
678
+ ], OxGanttChart.prototype, "toDate", void 0);
679
+ __decorate([
680
+ property({ type: String, attribute: 'axis-scale' })
681
+ ], OxGanttChart.prototype, "axisScale", void 0);
682
+ __decorate([
683
+ property({ type: Boolean, attribute: 'extend-grid-lines' })
684
+ ], OxGanttChart.prototype, "extendGridLines", void 0);
685
+ __decorate([
686
+ property({ type: Array })
687
+ ], OxGanttChart.prototype, "tasks", void 0);
688
+ __decorate([
689
+ state()
690
+ ], OxGanttChart.prototype, "baseWidth", void 0);
691
+ __decorate([
692
+ state()
693
+ ], OxGanttChart.prototype, "level", void 0);
694
+ __decorate([
695
+ query('svg[header]')
696
+ ], OxGanttChart.prototype, "svgHeader", void 0);
697
+ __decorate([
698
+ query('svg[body]')
699
+ ], OxGanttChart.prototype, "svgBody", void 0);
700
+ __decorate([
701
+ query('[chart-container]')
702
+ ], OxGanttChart.prototype, "container", void 0);
703
+ OxGanttChart = __decorate([
704
+ customElement('ox-gantt-chart')
705
+ ], OxGanttChart);
706
+ export { OxGanttChart };
707
+ //# sourceMappingURL=ox-gantt-chart.js.map