@operato/gantt 8.0.0-alpha.4 → 8.0.0-alpha.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/dist/src/gantt-chart-controller.d.ts +5 -6
- package/dist/src/gantt-chart-controller.js +92 -37
- package/dist/src/gantt-chart-controller.js.map +1 -1
- package/dist/src/ox-gantt-chart.d.ts +374 -3
- package/dist/src/ox-gantt-chart.js +86 -180
- package/dist/src/ox-gantt-chart.js.map +1 -1
- package/dist/src/ox-gantt-resize-splitter.d.ts +6 -2
- package/dist/src/ox-gantt-resize-splitter.js +70 -39
- package/dist/src/ox-gantt-resize-splitter.js.map +1 -1
- package/dist/src/ox-gantt.js +10 -1
- package/dist/src/ox-gantt.js.map +1 -1
- package/dist/src/ox-resource-chart.js +2 -0
- package/dist/src/ox-resource-chart.js.map +1 -1
- package/dist/src/ox-task-tooltip.d.ts +3 -1
- package/dist/src/ox-task-tooltip.js +36 -11
- package/dist/src/ox-task-tooltip.js.map +1 -1
- package/dist/stories/ox-gantt.stories.js +17 -1
- package/dist/stories/ox-gantt.stories.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -13
- package/src/gantt-chart-controller.ts +100 -34
- package/src/ox-gantt-chart.ts +92 -192
- package/src/ox-gantt-resize-splitter.ts +72 -44
- package/src/ox-gantt.ts +10 -1
- package/src/ox-resource-chart.ts +2 -0
- package/src/ox-task-tooltip.ts +31 -9
- package/stories/ox-gantt.stories.ts +17 -1
- package/themes/calendar-theme.css +3 -1
- package/assets/images/icon-editor-gradient-direction.png +0 -0
- package/assets/images/icon-properties-label.png +0 -0
- package/assets/images/icon-properties-line-type.png +0 -0
- package/assets/images/icon-properties-table.png +0 -0
|
@@ -3,19 +3,16 @@ import * as d3 from 'd3'
|
|
|
3
3
|
import { TimeScale, Task, TaskTag } from './types'
|
|
4
4
|
import { OxTaskTooltip } from './ox-task-tooltip'
|
|
5
5
|
import { drawTimeScaleHeader, getScale, getChartWidth } from './draw-time-scale-header'
|
|
6
|
+
import { levelDown, levelUp } from './utils'
|
|
7
|
+
import { GestureEventPinch, GestureEventDoubleTap, GestureEventDrag } from '@operato/utils/gesture-mixin.js'
|
|
6
8
|
|
|
7
9
|
export class GanttChartController implements ReactiveController {
|
|
8
|
-
private host: ReactiveControllerHost & LitElement
|
|
10
|
+
private host: (ReactiveControllerHost & LitElement) | null
|
|
9
11
|
private fromTime: Date
|
|
10
12
|
private toTime: Date
|
|
11
13
|
private totalDays: number
|
|
12
14
|
private _timeScale?: d3.ScaleTime<number, number, never>
|
|
13
15
|
|
|
14
|
-
private touchStartX?: number
|
|
15
|
-
private touchStartY?: number
|
|
16
|
-
private scrollStartX?: number
|
|
17
|
-
private scrollStartY?: number
|
|
18
|
-
|
|
19
16
|
private colorProvider?: (task: Task) => string
|
|
20
17
|
|
|
21
18
|
public baseWidth: number = 30
|
|
@@ -34,6 +31,10 @@ export class GanttChartController implements ReactiveController {
|
|
|
34
31
|
this.host.addController(this)
|
|
35
32
|
}
|
|
36
33
|
|
|
34
|
+
dispose() {
|
|
35
|
+
this.host = null
|
|
36
|
+
}
|
|
37
|
+
|
|
37
38
|
hostConnected() {
|
|
38
39
|
this.updateTimeScale()
|
|
39
40
|
}
|
|
@@ -57,12 +58,12 @@ export class GanttChartController implements ReactiveController {
|
|
|
57
58
|
this.toTime = new Date(toDate)
|
|
58
59
|
this.totalDays = d3.timeDay.count(this.fromTime, d3.timeDay.offset(this.toTime, 1))
|
|
59
60
|
this._timeScale = undefined
|
|
60
|
-
this.host
|
|
61
|
+
this.host?.requestUpdate()
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
setTasks(tasks: Task[]) {
|
|
64
65
|
this.tasks = tasks
|
|
65
|
-
this.host
|
|
66
|
+
this.host?.requestUpdate()
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
updateTimeScale() {
|
|
@@ -93,7 +94,7 @@ export class GanttChartController implements ReactiveController {
|
|
|
93
94
|
.append('marker')
|
|
94
95
|
.attr('id', 'arrow')
|
|
95
96
|
.attr('viewBox', '0 0 10 10')
|
|
96
|
-
.attr('refX', 10)
|
|
97
|
+
.attr('refX', 10)
|
|
97
98
|
.attr('refY', 5)
|
|
98
99
|
.attr('markerWidth', 6)
|
|
99
100
|
.attr('markerHeight', 6)
|
|
@@ -150,14 +151,20 @@ export class GanttChartController implements ReactiveController {
|
|
|
150
151
|
const taskWidth = endX - startX + this.baseWidth / this.getScale()
|
|
151
152
|
const taskY = index * 30 + 7.5
|
|
152
153
|
|
|
153
|
-
const showTooltip = (event: MouseEvent) => {
|
|
154
|
+
const showTooltip = (event: MouseEvent | TouchEvent) => {
|
|
155
|
+
tooltip.hide()
|
|
156
|
+
|
|
157
|
+
const touch = 'touches' in event
|
|
158
|
+
const x = touch ? event.touches[0].clientX : event.clientX
|
|
159
|
+
const y = touch ? event.touches[0].clientY : event.clientY
|
|
160
|
+
|
|
154
161
|
tooltip.title = task.title
|
|
155
162
|
tooltip.startDate = startDate.toISOString().split('T')[0]
|
|
156
163
|
tooltip.endDate = endDate.toISOString().split('T')[0]
|
|
157
164
|
tooltip.progress = task.progress ?? 0
|
|
158
165
|
tooltip.type = task.type
|
|
159
166
|
|
|
160
|
-
tooltip.show(
|
|
167
|
+
tooltip.show(x, y)
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
const hideTooltip = () => {
|
|
@@ -185,15 +192,18 @@ export class GanttChartController implements ReactiveController {
|
|
|
185
192
|
.attr('stroke-width', 1)
|
|
186
193
|
|
|
187
194
|
taskBar
|
|
188
|
-
.on('click',
|
|
195
|
+
.on('click', e => {
|
|
189
196
|
this.selectedTaskId = task.id
|
|
190
197
|
const customEvent = new CustomEvent('task-clicked', {
|
|
191
198
|
detail: { taskId: task.id },
|
|
192
199
|
bubbles: true,
|
|
193
200
|
composed: true
|
|
194
201
|
})
|
|
195
|
-
|
|
196
|
-
|
|
202
|
+
|
|
203
|
+
showTooltip(e)
|
|
204
|
+
|
|
205
|
+
this.host?.dispatchEvent(customEvent)
|
|
206
|
+
this.host?.requestUpdate()
|
|
197
207
|
})
|
|
198
208
|
.on('mouseover', showTooltip)
|
|
199
209
|
.on('mouseout', hideTooltip)
|
|
@@ -209,15 +219,18 @@ export class GanttChartController implements ReactiveController {
|
|
|
209
219
|
.attr('fill', 'lightgreen')
|
|
210
220
|
|
|
211
221
|
progressBar
|
|
212
|
-
.on('click',
|
|
222
|
+
.on('click', e => {
|
|
213
223
|
this.selectedTaskId = task.id
|
|
214
224
|
const customEvent = new CustomEvent('task-clicked', {
|
|
215
225
|
detail: { taskId: task.id },
|
|
216
226
|
bubbles: true,
|
|
217
227
|
composed: true
|
|
218
228
|
})
|
|
219
|
-
|
|
220
|
-
|
|
229
|
+
|
|
230
|
+
showTooltip(e)
|
|
231
|
+
|
|
232
|
+
this.host?.dispatchEvent(customEvent)
|
|
233
|
+
this.host?.requestUpdate()
|
|
221
234
|
})
|
|
222
235
|
.on('mouseover', showTooltip)
|
|
223
236
|
.on('mouseout', hideTooltip)
|
|
@@ -256,7 +269,7 @@ export class GanttChartController implements ReactiveController {
|
|
|
256
269
|
.attr('stroke', 'var(--md-sys-color-on-primary-container, black)')
|
|
257
270
|
.attr('fill', 'none')
|
|
258
271
|
.attr('stroke-width', 1)
|
|
259
|
-
.attr('marker-end', 'url(#arrow)')
|
|
272
|
+
.attr('marker-end', 'url(#arrow)')
|
|
260
273
|
}
|
|
261
274
|
})
|
|
262
275
|
}
|
|
@@ -277,6 +290,8 @@ export class GanttChartController implements ReactiveController {
|
|
|
277
290
|
event.preventDefault()
|
|
278
291
|
event.stopPropagation()
|
|
279
292
|
|
|
293
|
+
const { offsetHeight } = container
|
|
294
|
+
|
|
280
295
|
const deltaModeFactor =
|
|
281
296
|
event.deltaMode === WheelEvent.DOM_DELTA_LINE
|
|
282
297
|
? 16
|
|
@@ -288,7 +303,8 @@ export class GanttChartController implements ReactiveController {
|
|
|
288
303
|
|
|
289
304
|
// 가로 스크롤 및 세로 스크롤 모두 처리
|
|
290
305
|
container.scrollLeft += deltaX
|
|
291
|
-
container.scrollTop += deltaY
|
|
306
|
+
// container.scrollTop += deltaY
|
|
307
|
+
container.scrollTop = Math.min(this.tasks.length * 30 - (offsetHeight - 40), container.scrollTop + deltaY)
|
|
292
308
|
|
|
293
309
|
const customEvent = new CustomEvent('scroll', {
|
|
294
310
|
detail: {
|
|
@@ -299,28 +315,78 @@ export class GanttChartController implements ReactiveController {
|
|
|
299
315
|
composed: true
|
|
300
316
|
})
|
|
301
317
|
|
|
302
|
-
this.host
|
|
318
|
+
this.host?.dispatchEvent(customEvent)
|
|
303
319
|
}
|
|
304
320
|
|
|
305
|
-
|
|
306
|
-
event.
|
|
321
|
+
handlePinchForBody = (event: GestureEventPinch) => {
|
|
322
|
+
const { scale } = event.detail
|
|
323
|
+
|
|
324
|
+
this.baseWidth *= scale
|
|
325
|
+
|
|
326
|
+
if (this.baseWidth > 40) {
|
|
327
|
+
if (this.level === 'week-day') {
|
|
328
|
+
this.baseWidth = 40
|
|
329
|
+
} else {
|
|
330
|
+
this.level = levelDown(this.level)
|
|
331
|
+
this.baseWidth = 30
|
|
332
|
+
}
|
|
333
|
+
} else if (this.baseWidth < 30) {
|
|
334
|
+
if (this.level === 'year-quarter') {
|
|
335
|
+
this.baseWidth = 30
|
|
336
|
+
}
|
|
337
|
+
}
|
|
307
338
|
|
|
308
|
-
this.
|
|
309
|
-
|
|
339
|
+
if (this.baseWidth < 20) {
|
|
340
|
+
this.level = levelUp(this.level)
|
|
341
|
+
this.baseWidth = 30
|
|
342
|
+
}
|
|
310
343
|
|
|
311
|
-
this.
|
|
312
|
-
this.
|
|
344
|
+
this.updateTimeScale()
|
|
345
|
+
this.host?.requestUpdate()
|
|
346
|
+
|
|
347
|
+
const customEvent = new CustomEvent('timescale-changed', {
|
|
348
|
+
detail: {
|
|
349
|
+
baseWidth: this.baseWidth,
|
|
350
|
+
level: this.level
|
|
351
|
+
},
|
|
352
|
+
bubbles: true,
|
|
353
|
+
composed: true
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
this.host?.dispatchEvent(customEvent)
|
|
313
357
|
}
|
|
314
358
|
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
|
|
359
|
+
handleDoubleTapForBody = (event: GestureEventDoubleTap) => {
|
|
360
|
+
const container = (this.host as any).container
|
|
361
|
+
|
|
362
|
+
container.scrollLeft = 0
|
|
363
|
+
container.scrollTop = 0
|
|
364
|
+
|
|
365
|
+
this.baseWidth = 30
|
|
366
|
+
this.level = 'week-day'
|
|
367
|
+
|
|
368
|
+
this.updateTimeScale()
|
|
369
|
+
this.host?.requestUpdate()
|
|
370
|
+
|
|
371
|
+
const customEvent = new CustomEvent('timescale-changed', {
|
|
372
|
+
detail: {
|
|
373
|
+
baseWidth: this.baseWidth,
|
|
374
|
+
level: this.level
|
|
375
|
+
},
|
|
376
|
+
bubbles: true,
|
|
377
|
+
composed: true
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
this.host?.dispatchEvent(customEvent)
|
|
381
|
+
}
|
|
318
382
|
|
|
319
|
-
|
|
320
|
-
const deltaY =
|
|
383
|
+
handleDragForBody = (event: GestureEventDrag) => {
|
|
384
|
+
const { deltaX: dx, deltaY: dy } = event.detail
|
|
385
|
+
const container = (this.host as any).container
|
|
386
|
+
const { offsetHeight } = container
|
|
321
387
|
|
|
322
|
-
container.scrollLeft
|
|
323
|
-
container.scrollTop = this.
|
|
388
|
+
container.scrollLeft -= dx
|
|
389
|
+
container.scrollTop = Math.min(this.tasks.length * 30 - (offsetHeight - 40), container.scrollTop - dy)
|
|
324
390
|
|
|
325
391
|
const customEvent = new CustomEvent('scroll', {
|
|
326
392
|
detail: {
|
|
@@ -331,6 +397,6 @@ export class GanttChartController implements ReactiveController {
|
|
|
331
397
|
composed: true
|
|
332
398
|
})
|
|
333
399
|
|
|
334
|
-
this.host
|
|
400
|
+
this.host?.dispatchEvent(customEvent)
|
|
335
401
|
}
|
|
336
402
|
}
|
package/src/ox-gantt-chart.ts
CHANGED
|
@@ -2,7 +2,7 @@ import './ox-task-tooltip.js'
|
|
|
2
2
|
import './ox-resource-chart.js'
|
|
3
3
|
|
|
4
4
|
import { LitElement, html, css, PropertyValues, PropertyValueMap } from 'lit'
|
|
5
|
-
import { customElement, property, query } from 'lit/decorators.js'
|
|
5
|
+
import { customElement, property, query, state } from 'lit/decorators.js'
|
|
6
6
|
import * as d3 from 'd3'
|
|
7
7
|
import { locales } from './locales'
|
|
8
8
|
|
|
@@ -13,8 +13,10 @@ import { Task, TimeScale } from './types.js'
|
|
|
13
13
|
import { levelDown, levelUp } from './utils'
|
|
14
14
|
import { OxTaskTooltip } from './ox-task-tooltip.js'
|
|
15
15
|
|
|
16
|
+
import { gesture } from '@operato/utils/gesture-mixin.js'
|
|
17
|
+
|
|
16
18
|
@customElement('ox-gantt-chart')
|
|
17
|
-
export class OxGanttChart extends LitElement {
|
|
19
|
+
export class OxGanttChart extends gesture(LitElement) {
|
|
18
20
|
static styles = [
|
|
19
21
|
MDTypeScaleStyles,
|
|
20
22
|
ScrollbarStyles,
|
|
@@ -23,15 +25,21 @@ export class OxGanttChart extends LitElement {
|
|
|
23
25
|
display: flex;
|
|
24
26
|
overflow: hidden;
|
|
25
27
|
position: relative;
|
|
28
|
+
user-select: none;
|
|
26
29
|
}
|
|
30
|
+
|
|
27
31
|
div[chart-container] {
|
|
28
32
|
display: block;
|
|
29
33
|
overflow-x: auto;
|
|
30
34
|
overflow-y: hidden;
|
|
35
|
+
overscroll-behavior: none;
|
|
36
|
+
touch-action: none;
|
|
31
37
|
}
|
|
38
|
+
|
|
32
39
|
svg {
|
|
33
40
|
min-width: 100%;
|
|
34
41
|
}
|
|
42
|
+
|
|
35
43
|
svg[header] {
|
|
36
44
|
display: block;
|
|
37
45
|
min-width: 100%;
|
|
@@ -44,27 +52,27 @@ export class OxGanttChart extends LitElement {
|
|
|
44
52
|
top: 0;
|
|
45
53
|
z-index: 1;
|
|
46
54
|
}
|
|
47
|
-
|
|
48
|
-
position: absolute;
|
|
49
|
-
top: 44px; /* 헤더 바로 아래 위치 */
|
|
50
|
-
right: 4px;
|
|
51
|
-
display: flex;
|
|
52
|
-
align-items: center;
|
|
53
|
-
gap: 2px;
|
|
54
|
-
}
|
|
55
|
+
|
|
55
56
|
.scroll-button {
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
position: absolute;
|
|
58
|
+
top: 46px; /* 헤더 바로 아래 위치 */
|
|
59
|
+
width: 14px;
|
|
60
|
+
height: 14px;
|
|
58
61
|
background-color: var(--ox-gantt-header-color, var(--md-sys-color-on-secondary-container));
|
|
62
|
+
opacity: 0.5;
|
|
59
63
|
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
|
|
60
64
|
cursor: pointer;
|
|
61
65
|
transform: rotate(90deg);
|
|
62
66
|
}
|
|
67
|
+
|
|
63
68
|
.scroll-button.left {
|
|
69
|
+
left: 6px;
|
|
64
70
|
transform: rotate(-90deg);
|
|
65
71
|
}
|
|
72
|
+
|
|
66
73
|
.scroll-button.right {
|
|
67
74
|
transform: rotate(90deg);
|
|
75
|
+
right: 6px;
|
|
68
76
|
}
|
|
69
77
|
`
|
|
70
78
|
]
|
|
@@ -81,20 +89,38 @@ export class OxGanttChart extends LitElement {
|
|
|
81
89
|
@property({ type: String }) locale: string = 'en-US'
|
|
82
90
|
@property({ type: Object }) colorProvider!: (task: Task) => string
|
|
83
91
|
|
|
92
|
+
@state() dateRangeSelected?: { start: Date; end: Date }
|
|
93
|
+
|
|
84
94
|
@query('svg[header]') svgHeader!: SVGElement
|
|
85
95
|
@query('svg[body]') svgBody!: SVGElement
|
|
86
96
|
@query('[chart-container]') container!: HTMLDivElement
|
|
87
97
|
@query('ox-task-tooltip') tooltip!: OxTaskTooltip
|
|
88
98
|
|
|
89
|
-
private ganttChartController
|
|
99
|
+
private ganttChartController!: GanttChartController
|
|
100
|
+
|
|
101
|
+
protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
|
|
102
|
+
this.initHeaderGestureHandlers()
|
|
103
|
+
|
|
104
|
+
// this.addEventListener('date-range-cleared', e => this.clearSelection())
|
|
105
|
+
}
|
|
90
106
|
|
|
91
|
-
|
|
92
|
-
super()
|
|
107
|
+
connectedCallback(): void {
|
|
108
|
+
super.connectedCallback()
|
|
93
109
|
this.ganttChartController = new GanttChartController(this, this.fromDate, this.toDate, this.axisScale)
|
|
110
|
+
|
|
111
|
+
this.addEventListener('pinch', this.ganttChartController.handlePinchForBody as EventListener)
|
|
112
|
+
this.addEventListener('drag', this.ganttChartController.handleDragForBody as EventListener)
|
|
113
|
+
this.addEventListener('doubletap', this.ganttChartController.handleDoubleTapForBody as EventListener)
|
|
94
114
|
}
|
|
95
115
|
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
disconnectedCallback(): void {
|
|
117
|
+
super.disconnectedCallback()
|
|
118
|
+
|
|
119
|
+
this.removeEventListener('pinch', this.ganttChartController.handlePinchForBody as EventListener)
|
|
120
|
+
this.removeEventListener('drag', this.ganttChartController.handleDragForBody as EventListener)
|
|
121
|
+
this.removeEventListener('doubletap', this.ganttChartController.handleDoubleTapForBody as EventListener)
|
|
122
|
+
|
|
123
|
+
this.ganttChartController.dispose()
|
|
98
124
|
}
|
|
99
125
|
|
|
100
126
|
updated(changedProperties: PropertyValues<this>) {
|
|
@@ -103,8 +129,9 @@ export class OxGanttChart extends LitElement {
|
|
|
103
129
|
}
|
|
104
130
|
|
|
105
131
|
if (changedProperties.has('tasks')) {
|
|
106
|
-
this.
|
|
107
|
-
this.
|
|
132
|
+
const flattenTasks = this.flattenTasks(this.tasks)
|
|
133
|
+
this.ganttChartController.setTasks(flattenTasks)
|
|
134
|
+
this.svgBody.style.height = Math.max(flattenTasks.length * 30, 2000) + 'px'
|
|
108
135
|
}
|
|
109
136
|
|
|
110
137
|
if (changedProperties.has('locale')) {
|
|
@@ -126,26 +153,16 @@ export class OxGanttChart extends LitElement {
|
|
|
126
153
|
|
|
127
154
|
this.ganttChartController.drawAxes(this.svgHeader)
|
|
128
155
|
this.ganttChartController.drawBody(this.svgBody, this.tooltip)
|
|
156
|
+
this.drawSelectedRange()
|
|
129
157
|
}
|
|
130
158
|
|
|
131
159
|
render() {
|
|
132
160
|
return html`
|
|
133
|
-
<div
|
|
134
|
-
|
|
135
|
-
@wheel=${(e: WheelEvent) => this.ganttChartController.handleWheelForBody(e, this.container)}
|
|
136
|
-
@touchstart=${(e: TouchEvent) => this.ganttChartController.handleTouchStartForBody(e, this.container)}
|
|
137
|
-
@touchmove=${(e: TouchEvent) => this.ganttChartController.handleTouchMoveForBody(e, this.container)}
|
|
138
|
-
>
|
|
139
|
-
<svg
|
|
140
|
-
header
|
|
141
|
-
@date-range-selected=${(e: CustomEvent) => this.handleDateRangeSelected(e)}
|
|
142
|
-
@wheel=${this.handleWheelForHeader.bind(this)}
|
|
143
|
-
></svg>
|
|
161
|
+
<div chart-container @wheel=${(e: WheelEvent) => this.ganttChartController.handleWheelForBody(e, this.container)}>
|
|
162
|
+
<svg header @wheel=${this.handleWheelForHeader.bind(this)}></svg>
|
|
144
163
|
<svg body></svg>
|
|
145
|
-
<div class="scroll-
|
|
146
|
-
|
|
147
|
-
<div class="scroll-button right" @click=${this.scrollToEnd}></div>
|
|
148
|
-
</div>
|
|
164
|
+
<div class="scroll-button left" @click=${this.scrollToStart} @touchstart=${this.scrollToStart}></div>
|
|
165
|
+
<div class="scroll-button right" @click=${this.scrollToEnd} @touchstart=${this.scrollToEnd}></div>
|
|
149
166
|
<ox-task-tooltip></ox-task-tooltip>
|
|
150
167
|
</div>
|
|
151
168
|
`
|
|
@@ -248,11 +265,6 @@ export class OxGanttChart extends LitElement {
|
|
|
248
265
|
this.dispatchEvent(customEvent)
|
|
249
266
|
}
|
|
250
267
|
|
|
251
|
-
handleDateRangeSelected(event: CustomEvent) {
|
|
252
|
-
const detail = event.detail
|
|
253
|
-
this.dispatchEvent(new CustomEvent('date-range-selected', { detail }))
|
|
254
|
-
}
|
|
255
|
-
|
|
256
268
|
updateLocale(locale: string) {
|
|
257
269
|
const localeDefinition = (locales as any)[locale || 'en-US']
|
|
258
270
|
if (localeDefinition) {
|
|
@@ -264,141 +276,47 @@ export class OxGanttChart extends LitElement {
|
|
|
264
276
|
initHeaderGestureHandlers() {
|
|
265
277
|
const header = d3.select(this.svgHeader)
|
|
266
278
|
|
|
267
|
-
let isDragging = false
|
|
268
279
|
let startX: number | null = null
|
|
269
|
-
let lastTouchDistance: number | null = null
|
|
270
280
|
|
|
271
|
-
const
|
|
281
|
+
const handlePointerDown = (event: PointerEvent) => {
|
|
272
282
|
event.preventDefault()
|
|
283
|
+
event.stopPropagation()
|
|
273
284
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
} else {
|
|
277
|
-
isDragging = true
|
|
278
|
-
const touch = event.touches[0]
|
|
279
|
-
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
280
|
-
startX = touch.clientX - containerOffsetX
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const handleTouchMove = (event: TouchEvent) => {
|
|
285
|
-
event.preventDefault()
|
|
285
|
+
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
286
|
+
startX = event.clientX - containerOffsetX
|
|
286
287
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const zoomFactor = currentDistance / lastTouchDistance
|
|
290
|
-
this.ganttChartController.baseWidth *= zoomFactor
|
|
291
|
-
|
|
292
|
-
if (this.ganttChartController.baseWidth > 60) {
|
|
293
|
-
if (this.ganttChartController.level == 'year-quarter') {
|
|
294
|
-
this.ganttChartController.baseWidth = 60
|
|
295
|
-
} else {
|
|
296
|
-
this.ganttChartController.level = levelUp(this.ganttChartController.level)
|
|
297
|
-
this.ganttChartController.baseWidth = 30
|
|
298
|
-
}
|
|
299
|
-
} else if (this.ganttChartController.baseWidth < 30) {
|
|
300
|
-
if (this.ganttChartController.level == 'week-day') {
|
|
301
|
-
this.ganttChartController.baseWidth = 30
|
|
302
|
-
} else {
|
|
303
|
-
this.ganttChartController.level = levelDown(this.ganttChartController.level)
|
|
304
|
-
this.ganttChartController.baseWidth = 60
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
lastTouchDistance = currentDistance
|
|
309
|
-
this.ganttChartController.updateTimeScale()
|
|
310
|
-
this.requestUpdate()
|
|
311
|
-
|
|
312
|
-
// Dispatch custom event to sync with ox-resource-chart
|
|
313
|
-
const customEvent = new CustomEvent('timescale-changed', {
|
|
314
|
-
detail: {
|
|
315
|
-
baseWidth: this.ganttChartController.baseWidth,
|
|
316
|
-
level: this.ganttChartController.level
|
|
317
|
-
},
|
|
318
|
-
bubbles: true,
|
|
319
|
-
composed: true
|
|
320
|
-
})
|
|
321
|
-
this.dispatchEvent(customEvent)
|
|
322
|
-
} else if (isDragging && startX !== null) {
|
|
323
|
-
const touch = event.touches[0]
|
|
324
|
-
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
325
|
-
const currentX = touch.clientX - containerOffsetX
|
|
326
|
-
this.highlightSelection(startX, currentX)
|
|
327
|
-
}
|
|
288
|
+
window.addEventListener('pointermove', handlePointerMove)
|
|
289
|
+
window.addEventListener('pointerup', handlePointerUp)
|
|
328
290
|
}
|
|
329
291
|
|
|
330
|
-
const
|
|
292
|
+
const handlePointerMove = (event: PointerEvent) => {
|
|
331
293
|
event.preventDefault()
|
|
294
|
+
event.stopPropagation()
|
|
332
295
|
|
|
333
|
-
if (event.touches.length < 2) {
|
|
334
|
-
lastTouchDistance = null
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (!isDragging || startX === null) {
|
|
338
|
-
const touch = event.changedTouches[0]
|
|
339
|
-
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
340
|
-
const clickX = touch.clientX - containerOffsetX
|
|
341
|
-
this.finalizeSelection(clickX, clickX)
|
|
342
|
-
return
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const touch = event.changedTouches[0]
|
|
346
296
|
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
347
|
-
const
|
|
348
|
-
this.finalizeSelection(startX, endX)
|
|
349
|
-
|
|
350
|
-
isDragging = false
|
|
351
|
-
startX = null
|
|
352
|
-
}
|
|
297
|
+
const currentX = event.clientX - containerOffsetX
|
|
353
298
|
|
|
354
|
-
|
|
355
|
-
const dx = touches[0].clientX - touches[1].clientX
|
|
356
|
-
const dy = touches[0].clientY - touches[1].clientY
|
|
357
|
-
return Math.sqrt(dx * dx + dy * dy)
|
|
299
|
+
this.highlightSelection(startX!, currentX)
|
|
358
300
|
}
|
|
359
301
|
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
startX = event.clientX - containerOffsetX
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const handleMouseMove = (event: MouseEvent) => {
|
|
367
|
-
if (!isDragging || startX === null) return
|
|
368
|
-
|
|
369
|
-
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
370
|
-
const currentX = event.clientX - containerOffsetX
|
|
371
|
-
this.highlightSelection(startX, currentX)
|
|
372
|
-
}
|
|
302
|
+
const handlePointerUp = (event: PointerEvent) => {
|
|
303
|
+
event.preventDefault()
|
|
304
|
+
event.stopPropagation()
|
|
373
305
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
377
|
-
const clickX = event.clientX - containerOffsetX
|
|
378
|
-
this.finalizeSelection(clickX, clickX)
|
|
379
|
-
return
|
|
380
|
-
}
|
|
306
|
+
window.removeEventListener('pointermove', handlePointerMove)
|
|
307
|
+
window.removeEventListener('pointerup', handlePointerUp)
|
|
381
308
|
|
|
382
309
|
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
383
310
|
const endX = event.clientX - containerOffsetX
|
|
384
|
-
this.finalizeSelection(startX
|
|
311
|
+
this.finalizeSelection(startX!, endX)
|
|
385
312
|
|
|
386
|
-
isDragging = false
|
|
387
313
|
startX = null
|
|
388
314
|
}
|
|
389
315
|
|
|
390
|
-
header.on('
|
|
391
|
-
header.on('mousemove', handleMouseMove)
|
|
392
|
-
header.on('mouseup', handleMouseUp)
|
|
393
|
-
header.on('touchstart', handleTouchStart)
|
|
394
|
-
header.on('touchmove', handleTouchMove)
|
|
395
|
-
header.on('touchend', handleTouchEnd)
|
|
316
|
+
header.on('pointerdown', handlePointerDown)
|
|
396
317
|
}
|
|
397
318
|
|
|
398
319
|
highlightSelection(startX: number, endX: number) {
|
|
399
|
-
const header = d3.select(this.svgHeader)
|
|
400
|
-
const body = d3.select(this.svgBody)
|
|
401
|
-
|
|
402
320
|
let startDate = this.ganttChartController.timeScale.invert(startX)
|
|
403
321
|
let endDate = this.ganttChartController.timeScale.invert(endX)
|
|
404
322
|
|
|
@@ -418,44 +336,10 @@ export class OxGanttChart extends LitElement {
|
|
|
418
336
|
end = d3.timeDay.offset(d3.timeMonth.every(3)!.ceil(d3.max([startDate, endDate])!), -1)
|
|
419
337
|
}
|
|
420
338
|
|
|
421
|
-
|
|
422
|
-
body.selectAll('.selection-rect').remove()
|
|
423
|
-
|
|
424
|
-
header
|
|
425
|
-
.append('rect')
|
|
426
|
-
.attr('class', 'selection-rect')
|
|
427
|
-
.attr('x', this.ganttChartController.timeScale(start!))
|
|
428
|
-
.attr('y', 20)
|
|
429
|
-
.attr(
|
|
430
|
-
'width',
|
|
431
|
-
this.ganttChartController.timeScale(end!) -
|
|
432
|
-
this.ganttChartController.timeScale(start!) +
|
|
433
|
-
this.ganttChartController.baseWidth / this.ganttChartController.getScale()
|
|
434
|
-
)
|
|
435
|
-
.attr('height', 20)
|
|
436
|
-
.attr('fill', 'var(--ox-gantt-selection-color, var(--md-sys-color-on-primary-container))')
|
|
437
|
-
.attr('opacity', '.2')
|
|
438
|
-
|
|
439
|
-
body
|
|
440
|
-
.append('rect')
|
|
441
|
-
.attr('class', 'selection-rect')
|
|
442
|
-
.attr('x', this.ganttChartController.timeScale(start!))
|
|
443
|
-
.attr('y', 0)
|
|
444
|
-
.attr(
|
|
445
|
-
'width',
|
|
446
|
-
this.ganttChartController.timeScale(end!) -
|
|
447
|
-
this.ganttChartController.timeScale(start!) +
|
|
448
|
-
this.ganttChartController.baseWidth / this.ganttChartController.getScale()
|
|
449
|
-
)
|
|
450
|
-
.attr('height', '100%')
|
|
451
|
-
.attr('fill', 'var(--ox-gantt-selection-color, var(--md-sys-color-on-primary-container))')
|
|
452
|
-
.attr('opacity', '.2')
|
|
339
|
+
this.dateRangeSelected = { start: start!, end: end! }
|
|
453
340
|
}
|
|
454
341
|
|
|
455
342
|
finalizeSelection(startX: number, endX: number) {
|
|
456
|
-
const header = d3.select(this.svgHeader)
|
|
457
|
-
const body = d3.select(this.svgBody)
|
|
458
|
-
|
|
459
343
|
let startDate = this.ganttChartController.timeScale.invert(startX)
|
|
460
344
|
let endDate = this.ganttChartController.timeScale.invert(endX)
|
|
461
345
|
|
|
@@ -475,6 +359,29 @@ export class OxGanttChart extends LitElement {
|
|
|
475
359
|
end = d3.timeDay.offset(d3.timeMonth.every(3)!.ceil(d3.max([startDate, endDate])!), -1)
|
|
476
360
|
}
|
|
477
361
|
|
|
362
|
+
this.dateRangeSelected = { start: start!, end: end! }
|
|
363
|
+
|
|
364
|
+
const formatDate = d3.timeFormat('%Y-%m-%d')
|
|
365
|
+
|
|
366
|
+
this.dispatchEvent(
|
|
367
|
+
new CustomEvent('date-range-selected', {
|
|
368
|
+
detail: { start: formatDate(start!), end: formatDate(end!) },
|
|
369
|
+
bubbles: true,
|
|
370
|
+
composed: true
|
|
371
|
+
})
|
|
372
|
+
)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
drawSelectedRange() {
|
|
376
|
+
if (!this.dateRangeSelected) {
|
|
377
|
+
return
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
let { start, end } = this.dateRangeSelected
|
|
381
|
+
|
|
382
|
+
const header = d3.select(this.svgHeader)
|
|
383
|
+
const body = d3.select(this.svgBody)
|
|
384
|
+
|
|
478
385
|
header.selectAll('.selection-rect').remove()
|
|
479
386
|
body.selectAll('.selection-rect').remove()
|
|
480
387
|
|
|
@@ -507,12 +414,5 @@ export class OxGanttChart extends LitElement {
|
|
|
507
414
|
.attr('height', '100%')
|
|
508
415
|
.attr('fill', 'var(--ox-gantt-selection-color, var(--md-sys-color-on-primary-container))')
|
|
509
416
|
.attr('opacity', '.2')
|
|
510
|
-
|
|
511
|
-
const formatDate = d3.timeFormat('%Y-%m-%d')
|
|
512
|
-
const customEvent = new CustomEvent('date-range-selected', {
|
|
513
|
-
detail: { start: formatDate(start!), end: formatDate(end!) }
|
|
514
|
-
})
|
|
515
|
-
|
|
516
|
-
header.node()?.dispatchEvent(customEvent)
|
|
517
417
|
}
|
|
518
418
|
}
|