@operato/gantt 8.0.0-beta.0 → 8.0.0-beta.2

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.
@@ -1,402 +0,0 @@
1
- import { LitElement, ReactiveController, ReactiveControllerHost } from 'lit'
2
- import * as d3 from 'd3'
3
- import { TimeScale, Task, TaskTag } from './types'
4
- import { OxTaskTooltip } from './ox-task-tooltip'
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'
8
-
9
- export class GanttChartController implements ReactiveController {
10
- private host: (ReactiveControllerHost & LitElement) | null
11
- private fromTime: Date
12
- private toTime: Date
13
- private totalDays: number
14
- private _timeScale?: d3.ScaleTime<number, number, never>
15
-
16
- private colorProvider?: (task: Task) => string
17
-
18
- public baseWidth: number = 30
19
- public level: TimeScale
20
- public selectedTaskId?: string
21
-
22
- public tasks: Task[] = []
23
- public extendGridLines: boolean = false
24
-
25
- constructor(host: ReactiveControllerHost & LitElement, fromDate: string, toDate: string, axisScale: TimeScale) {
26
- this.host = host
27
- this.fromTime = new Date(fromDate)
28
- this.toTime = new Date(toDate)
29
- this.level = axisScale
30
- this.totalDays = d3.timeDay.count(this.fromTime, d3.timeDay.offset(this.toTime, 1))
31
- this.host.addController(this)
32
- }
33
-
34
- dispose() {
35
- this.host = null
36
- }
37
-
38
- hostConnected() {
39
- this.updateTimeScale()
40
- }
41
-
42
- setColorProvider(colorProvider: (task: Task) => string) {
43
- this.colorProvider = colorProvider
44
- }
45
-
46
- get timeScale() {
47
- if (!this._timeScale) {
48
- this._timeScale = d3
49
- .scaleTime()
50
- .domain([d3.timeDay.floor(this.fromTime), d3.timeDay.offset(this.toTime, 1)])
51
- .range([0, this.getChartWidth()])
52
- }
53
- return this._timeScale
54
- }
55
-
56
- updateRange(fromDate: string, toDate: string) {
57
- this.fromTime = new Date(fromDate)
58
- this.toTime = new Date(toDate)
59
- this.totalDays = d3.timeDay.count(this.fromTime, d3.timeDay.offset(this.toTime, 1))
60
- this._timeScale = undefined
61
- this.host?.requestUpdate()
62
- }
63
-
64
- setTasks(tasks: Task[]) {
65
- this.tasks = tasks
66
- this.host?.requestUpdate()
67
- }
68
-
69
- updateTimeScale() {
70
- this.timeScale.range([0, this.getChartWidth()])
71
- }
72
-
73
- getScale() {
74
- return getScale(this.level)
75
- }
76
-
77
- getChartWidth() {
78
- return getChartWidth(this.fromTime, this.toTime, this.baseWidth, this.level)
79
- }
80
-
81
- drawAxes(svgHeader: SVGElement) {
82
- drawTimeScaleHeader(svgHeader, this.timeScale, this.fromTime, this.toTime, this.baseWidth, this.level)
83
- }
84
-
85
- drawBody(svgBody: SVGElement, tooltip: OxTaskTooltip) {
86
- const body = d3.select(svgBody)
87
-
88
- body.selectAll('*').remove()
89
- body.attr('width', this.getChartWidth())
90
-
91
- // Define the arrow marker
92
- const defs = body.append('defs')
93
- defs
94
- .append('marker')
95
- .attr('id', 'arrow')
96
- .attr('viewBox', '0 0 10 10')
97
- .attr('refX', 10)
98
- .attr('refY', 5)
99
- .attr('markerWidth', 6)
100
- .attr('markerHeight', 6)
101
- .attr('orient', 'auto-start-reverse')
102
- .append('path')
103
- .attr('d', 'M 0 0 L 10 5 L 0 10 z')
104
- .attr('fill', 'var(--md-sys-color-on-primary-container, black)')
105
-
106
- if (this.extendGridLines) {
107
- const addGridLines = (ticks: Date[]) => {
108
- ticks.forEach(tick => {
109
- const startX = this.timeScale(tick)
110
- body
111
- .append('line')
112
- .attr('x1', startX)
113
- .attr('x2', startX)
114
- .attr('y1', 0)
115
- .attr('y2', Number(svgBody.getBoundingClientRect().height) || 0)
116
- .attr('stroke', 'var(--ox-gantt-color-gridlines, var(--md-sys-color-outline))')
117
- .attr('stroke-width', 1)
118
- })
119
- }
120
-
121
- if (this.level === 'week-day') {
122
- const dayTicks = d3.timeDay.every(1)!.range(d3.timeDay.floor(this.fromTime), d3.timeDay.offset(this.toTime, 1))
123
- addGridLines(dayTicks)
124
- } else if (this.level === 'quarter-week') {
125
- const weekTicks = d3.timeWeek
126
- .every(1)!
127
- .range(d3.timeWeek.floor(this.fromTime), d3.timeWeek.offset(this.toTime, 1))
128
- addGridLines(weekTicks)
129
- } else if (this.level === 'year-month') {
130
- const monthTicks = d3.timeMonth
131
- .every(1)!
132
- .range(d3.timeMonth.floor(this.fromTime), d3.timeMonth.offset(this.toTime, 1))
133
- addGridLines(monthTicks)
134
- } else if (this.level === 'year-quarter') {
135
- const quarterTicks = d3.timeMonth
136
- .every(3)!
137
- .range(d3.timeMonth.floor(this.fromTime), d3.timeMonth.offset(this.toTime, 3))
138
- addGridLines(quarterTicks)
139
- }
140
- }
141
-
142
- const taskPositions: { [key: string]: { x: number; y: number; width: number } } = {}
143
-
144
- this.tasks.forEach((task, index) => {
145
- const startDate = new Date(task.startDate)
146
- const endDate = new Date(task.endDate)
147
-
148
- const startX = this.timeScale(d3.timeDay.floor(d3.min([startDate, endDate])!))
149
- const endX = this.timeScale(d3.timeDay.offset(d3.timeDay.ceil(d3.max([startDate, endDate])!), -1))
150
-
151
- const taskWidth = endX - startX + this.baseWidth / this.getScale()
152
- const taskY = index * 30 + 7.5
153
-
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
-
161
- tooltip.title = task.title
162
- tooltip.startDate = startDate.toISOString().split('T')[0]
163
- tooltip.endDate = endDate.toISOString().split('T')[0]
164
- tooltip.progress = task.progress ?? 0
165
- tooltip.type = task.type
166
-
167
- tooltip.show(x, y)
168
- }
169
-
170
- const hideTooltip = () => {
171
- tooltip.hide()
172
- }
173
-
174
- if (this.selectedTaskId === task.id) {
175
- body
176
- .append('rect')
177
- .attr('x', 0)
178
- .attr('y', taskY - 7.5)
179
- .attr('width', this.getChartWidth())
180
- .attr('height', 30)
181
- .attr('fill', 'var(--ox-gantt-selected-row-background-color, var(--md-sys-color-surface-variant))')
182
- }
183
-
184
- const taskBar = body
185
- .append('rect')
186
- .attr('x', startX)
187
- .attr('y', taskY)
188
- .attr('width', taskWidth)
189
- .attr('height', 15)
190
- .attr('fill', this.colorProvider ? this.colorProvider(task) : 'gray')
191
- .attr('stroke', 'black')
192
- .attr('stroke-width', 1)
193
-
194
- taskBar
195
- .on('click', e => {
196
- this.selectedTaskId = task.id
197
- const customEvent = new CustomEvent('task-clicked', {
198
- detail: { taskId: task.id },
199
- bubbles: true,
200
- composed: true
201
- })
202
-
203
- showTooltip(e)
204
-
205
- this.host?.dispatchEvent(customEvent)
206
- this.host?.requestUpdate()
207
- })
208
- .on('mouseover', showTooltip)
209
- .on('mouseout', hideTooltip)
210
-
211
- if (task.progress !== undefined) {
212
- const progressWidth = taskWidth * (task.progress / 100)
213
- const progressBar = body
214
- .append('rect')
215
- .attr('x', startX)
216
- .attr('y', taskY + 7.5 - 3.75)
217
- .attr('width', progressWidth)
218
- .attr('height', 7.5)
219
- .attr('fill', 'lightgreen')
220
-
221
- progressBar
222
- .on('click', e => {
223
- this.selectedTaskId = task.id
224
- const customEvent = new CustomEvent('task-clicked', {
225
- detail: { taskId: task.id },
226
- bubbles: true,
227
- composed: true
228
- })
229
-
230
- showTooltip(e)
231
-
232
- this.host?.dispatchEvent(customEvent)
233
- this.host?.requestUpdate()
234
- })
235
- .on('mouseover', showTooltip)
236
- .on('mouseout', hideTooltip)
237
- }
238
-
239
- taskPositions[task.id] = { x: endX + this.baseWidth / this.getScale(), y: taskY + 7.5, width: taskWidth }
240
- })
241
-
242
- this.tasks.forEach(task => {
243
- if (task.dependsOn && taskPositions[task.dependsOn]) {
244
- const prevTask = taskPositions[task.dependsOn]
245
- const currTask = taskPositions[task.id]
246
-
247
- body
248
- .append('path')
249
- .attr(
250
- 'd',
251
- `
252
- M${prevTask.x},${prevTask.y}
253
- L${prevTask.x + 10},${prevTask.y}
254
- ${
255
- prevTask.x + 10 < currTask.x - 10 - currTask.width
256
- ? `
257
- L${(prevTask.x + currTask.x - currTask.width) / 2},${prevTask.y}
258
- L${(prevTask.x + currTask.x - currTask.width) / 2},${currTask.y}
259
- `
260
- : `
261
- L${prevTask.x + 10},${(prevTask.y + currTask.y) / 2}
262
- L${currTask.x - 10 - currTask.width},${(prevTask.y + currTask.y) / 2}
263
- `
264
- }
265
- L${currTask.x - 10 - currTask.width},${currTask.y}
266
- L${currTask.x - currTask.width},${currTask.y}
267
- `
268
- )
269
- .attr('stroke', 'var(--md-sys-color-on-primary-container, black)')
270
- .attr('fill', 'none')
271
- .attr('stroke-width', 1)
272
- .attr('marker-end', 'url(#arrow)')
273
- }
274
- })
275
- }
276
-
277
- getTaskColor(tags: TaskTag[]): string {
278
- if (tags.includes('critical')) {
279
- return 'red'
280
- } else if (tags.includes('milestone')) {
281
- return 'blue'
282
- } else if (tags.includes('done')) {
283
- return 'green'
284
- } else {
285
- return 'grey'
286
- }
287
- }
288
-
289
- handleWheelForBody(event: WheelEvent, container: HTMLDivElement) {
290
- event.preventDefault()
291
- event.stopPropagation()
292
-
293
- const { offsetHeight } = container
294
-
295
- const deltaModeFactor =
296
- event.deltaMode === WheelEvent.DOM_DELTA_LINE
297
- ? 16
298
- : event.deltaMode === WheelEvent.DOM_DELTA_PAGE
299
- ? container.clientHeight
300
- : 1
301
- const deltaX = event.deltaX * deltaModeFactor
302
- const deltaY = event.deltaY * deltaModeFactor
303
-
304
- // 가로 스크롤 및 세로 스크롤 모두 처리
305
- container.scrollLeft += deltaX
306
- // container.scrollTop += deltaY
307
- container.scrollTop = Math.min(this.tasks.length * 30 - (offsetHeight - 40), container.scrollTop + deltaY)
308
-
309
- const customEvent = new CustomEvent('scroll', {
310
- detail: {
311
- scrollLeft: container.scrollLeft,
312
- scrollTop: container.scrollTop
313
- },
314
- bubbles: true,
315
- composed: true
316
- })
317
-
318
- this.host?.dispatchEvent(customEvent)
319
- }
320
-
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
- }
338
-
339
- if (this.baseWidth < 20) {
340
- this.level = levelUp(this.level)
341
- this.baseWidth = 30
342
- }
343
-
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)
357
- }
358
-
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
- }
382
-
383
- handleDragForBody = (event: GestureEventDrag) => {
384
- const { deltaX: dx, deltaY: dy } = event.detail
385
- const container = (this.host as any).container
386
- const { offsetHeight } = container
387
-
388
- container.scrollLeft -= dx
389
- container.scrollTop = Math.min(this.tasks.length * 30 - (offsetHeight - 40), container.scrollTop - dy)
390
-
391
- const customEvent = new CustomEvent('scroll', {
392
- detail: {
393
- scrollLeft: container.scrollLeft,
394
- scrollTop: container.scrollTop
395
- },
396
- bubbles: true,
397
- composed: true
398
- })
399
-
400
- this.host?.dispatchEvent(customEvent)
401
- }
402
- }
package/src/index.ts DELETED
File without changes
package/src/locales.ts DELETED
@@ -1,78 +0,0 @@
1
- export const locales = {
2
- 'en-US': {
3
- dateTime: '%x, %X',
4
- date: '%-m/%-d/%Y',
5
- time: '%-I:%M:%S %p',
6
- periods: ['AM', 'PM'],
7
- days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
8
- shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
9
- months: [
10
- 'January',
11
- 'February',
12
- 'March',
13
- 'April',
14
- 'May',
15
- 'June',
16
- 'July',
17
- 'August',
18
- 'September',
19
- 'October',
20
- 'November',
21
- 'December'
22
- ],
23
- shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
24
- },
25
- 'ko-KR': {
26
- dateTime: '%x, %X',
27
- date: '%Y-%m-%d',
28
- time: '%H:%M:%S',
29
- periods: ['오전', '오후'],
30
- days: ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'],
31
- shortDays: ['일', '월', '화', '수', '목', '금', '토'],
32
- months: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
33
- shortMonths: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월']
34
- },
35
- 'ja-JP': {
36
- dateTime: '%x, %X',
37
- date: '%Y/%m/%d',
38
- time: '%H:%M:%S',
39
- periods: ['午前', '午後'],
40
- days: ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'],
41
- shortDays: ['日', '月', '火', '水', '木', '金', '土'],
42
- months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
43
- shortMonths: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
44
- },
45
- 'zh-CN': {
46
- dateTime: '%x, %X',
47
- date: '%Y-%m-%d',
48
- time: '%H:%M:%S',
49
- periods: ['上午', '下午'],
50
- days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
51
- shortDays: ['日', '一', '二', '三', '四', '五', '六'],
52
- months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
53
- shortMonths: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
54
- },
55
- 'ms-MY': {
56
- dateTime: '%x, %X',
57
- date: '%d/%m/%Y',
58
- time: '%H:%M:%S',
59
- periods: ['AM', 'PM'],
60
- days: ['Ahad', 'Isnin', 'Selasa', 'Rabu', 'Khamis', 'Jumaat', 'Sabtu'],
61
- shortDays: ['Ahd', 'Isn', 'Sel', 'Rab', 'Kha', 'Jum', 'Sab'],
62
- months: [
63
- 'Januari',
64
- 'Februari',
65
- 'Mac',
66
- 'April',
67
- 'Mei',
68
- 'Jun',
69
- 'Julai',
70
- 'Ogos',
71
- 'September',
72
- 'Oktober',
73
- 'November',
74
- 'Disember'
75
- ],
76
- shortMonths: ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ogo', 'Sep', 'Okt', 'Nov', 'Dis']
77
- }
78
- }