@operato/gantt 7.0.21 → 7.0.22
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 +10 -0
- package/dist/src/draw-time-scale-header.d.ts +5 -0
- package/dist/src/draw-time-scale-header.js +154 -0
- package/dist/src/draw-time-scale-header.js.map +1 -0
- package/dist/src/gantt-chart-controller.d.ts +1 -1
- package/dist/src/gantt-chart-controller.js +6 -161
- package/dist/src/gantt-chart-controller.js.map +1 -1
- package/dist/src/ox-gantt-chart.d.ts +2 -0
- package/dist/src/ox-gantt-chart.js +26 -0
- package/dist/src/ox-gantt-chart.js.map +1 -1
- package/dist/src/ox-gantt.d.ts +1 -4
- package/dist/src/ox-gantt.js +37 -27
- package/dist/src/ox-gantt.js.map +1 -1
- package/dist/src/ox-resource-chart.d.ts +27 -4
- package/dist/src/ox-resource-chart.js +162 -39
- package/dist/src/ox-resource-chart.js.map +1 -1
- package/dist/stories/ox-gantt.stories.js +17 -10
- package/dist/stories/ox-gantt.stories.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/draw-time-scale-header.ts +192 -0
- package/src/gantt-chart-controller.ts +6 -189
- package/src/ox-gantt-chart.ts +30 -0
- package/src/ox-gantt.ts +41 -25
- package/src/ox-resource-chart.ts +179 -43
- package/stories/ox-gantt.stories.ts +17 -10
- package/translations/en.json +3 -1
- package/translations/ja.json +3 -1
- package/translations/ko.json +3 -1
- package/translations/ms.json +3 -1
- package/translations/zh.json +3 -1
package/src/ox-gantt-chart.ts
CHANGED
|
@@ -154,6 +154,14 @@ export class OxGanttChart extends LitElement {
|
|
|
154
154
|
return this.container.scrollTop
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
setHorizontalOffset(offset: number) {
|
|
158
|
+
this.container.scrollLeft = offset
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getHorizontalOffset(): number {
|
|
162
|
+
return this.container.scrollLeft
|
|
163
|
+
}
|
|
164
|
+
|
|
157
165
|
handleWheelForHeader(event: WheelEvent) {
|
|
158
166
|
event.preventDefault()
|
|
159
167
|
event.stopPropagation()
|
|
@@ -185,6 +193,17 @@ export class OxGanttChart extends LitElement {
|
|
|
185
193
|
|
|
186
194
|
this.ganttChartController.updateTimeScale()
|
|
187
195
|
this.requestUpdate()
|
|
196
|
+
|
|
197
|
+
// Dispatch custom event to sync with ox-resource-chart
|
|
198
|
+
const customEvent = new CustomEvent('timescale-changed', {
|
|
199
|
+
detail: {
|
|
200
|
+
baseWidth: this.ganttChartController.baseWidth,
|
|
201
|
+
level: this.ganttChartController.level
|
|
202
|
+
},
|
|
203
|
+
bubbles: true,
|
|
204
|
+
composed: true
|
|
205
|
+
})
|
|
206
|
+
this.dispatchEvent(customEvent)
|
|
188
207
|
}
|
|
189
208
|
}
|
|
190
209
|
|
|
@@ -248,6 +267,17 @@ export class OxGanttChart extends LitElement {
|
|
|
248
267
|
lastTouchDistance = currentDistance
|
|
249
268
|
this.ganttChartController.updateTimeScale()
|
|
250
269
|
this.requestUpdate()
|
|
270
|
+
|
|
271
|
+
// Dispatch custom event to sync with ox-resource-chart
|
|
272
|
+
const customEvent = new CustomEvent('timescale-changed', {
|
|
273
|
+
detail: {
|
|
274
|
+
baseWidth: this.ganttChartController.baseWidth,
|
|
275
|
+
level: this.ganttChartController.level
|
|
276
|
+
},
|
|
277
|
+
bubbles: true,
|
|
278
|
+
composed: true
|
|
279
|
+
})
|
|
280
|
+
this.dispatchEvent(customEvent)
|
|
251
281
|
} else if (isDragging && startX !== null) {
|
|
252
282
|
const touch = event.touches[0]
|
|
253
283
|
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
package/src/ox-gantt.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LitElement, html, css, PropertyValues, PropertyValueMap, nothing, TemplateResult } from 'lit'
|
|
2
|
+
import * as d3 from 'd3'
|
|
2
3
|
import i18next from 'i18next'
|
|
3
4
|
import { customElement, property, query, state } from 'lit/decorators.js'
|
|
4
5
|
import { styles as MDTypeScaleStyles } from '@material/web/typography/md-typescale-styles'
|
|
@@ -108,6 +109,7 @@ export class OxGantt extends LitElement {
|
|
|
108
109
|
th {
|
|
109
110
|
height: 40px;
|
|
110
111
|
box-sizing: border-box;
|
|
112
|
+
text-align: center;
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
td {
|
|
@@ -182,11 +184,9 @@ export class OxGantt extends LitElement {
|
|
|
182
184
|
|
|
183
185
|
@query('.task-container') taskContainer!: HTMLDivElement
|
|
184
186
|
@query('.task-table-container') taskTableContainer!: HTMLDivElement
|
|
185
|
-
@query('.task-chart-container') taskChartContainer!: HTMLDivElement
|
|
186
187
|
@query('.resource-container') resourceContainer!: HTMLDivElement
|
|
187
188
|
@query('.resource-table-container') resourceTableContainer!: HTMLDivElement
|
|
188
|
-
@query('.
|
|
189
|
-
@query('table') taskTable!: HTMLTableElement
|
|
189
|
+
@query('.task-table') taskTable!: HTMLTableElement
|
|
190
190
|
@query('ox-gantt-chart') ganttChart!: OxGanttChart
|
|
191
191
|
@query('ox-resource-chart') resourceChart!: OxResourceChart
|
|
192
192
|
|
|
@@ -207,10 +207,7 @@ export class OxGantt extends LitElement {
|
|
|
207
207
|
></ox-gantt-resize-splitter>
|
|
208
208
|
<ox-gantt-chart
|
|
209
209
|
class="task-chart-container"
|
|
210
|
-
@scroll=${
|
|
211
|
-
this.syncScroll(e)
|
|
212
|
-
this.syncResourceChartScroll(e)
|
|
213
|
-
}}
|
|
210
|
+
@scroll=${this.syncScroll}
|
|
214
211
|
.fromDate=${this.fromDate}
|
|
215
212
|
.toDate=${this.toDate}
|
|
216
213
|
.tasks=${this.tasks}
|
|
@@ -218,6 +215,7 @@ export class OxGantt extends LitElement {
|
|
|
218
215
|
.axisScale=${this.timeScale}
|
|
219
216
|
?extend-grid-lines=${this.extendGridLines}
|
|
220
217
|
@task-clicked=${this.handleTaskClicked}
|
|
218
|
+
@timescale-changed=${(e: CustomEvent) => this.syncTimeScale(e)}
|
|
221
219
|
></ox-gantt-chart>
|
|
222
220
|
</div>
|
|
223
221
|
<ox-gantt-resize-splitter
|
|
@@ -227,12 +225,12 @@ export class OxGantt extends LitElement {
|
|
|
227
225
|
@splitter-dragend=${(e: CustomEvent) => this.resizeVerticalEnd(e)}
|
|
228
226
|
></ox-gantt-resize-splitter>
|
|
229
227
|
<div class="resource-container">
|
|
230
|
-
<div class="resource-table-container">
|
|
228
|
+
<div class="resource-table-container" @scroll=${this.syncScroll}>
|
|
231
229
|
<table>
|
|
232
230
|
<thead>
|
|
233
231
|
<tr>
|
|
234
|
-
<th style="width: 100px"
|
|
235
|
-
<th
|
|
232
|
+
<th style="width: 100px">${i18next.t('label.gantt-resource-type')}</th>
|
|
233
|
+
<th>${i18next.t('label.gantt-resource-allocated')}</th>
|
|
236
234
|
</tr>
|
|
237
235
|
</thead>
|
|
238
236
|
<tbody>
|
|
@@ -250,9 +248,10 @@ export class OxGantt extends LitElement {
|
|
|
250
248
|
class="resource-chart-container"
|
|
251
249
|
.fromDate=${this.fromDate}
|
|
252
250
|
.toDate=${this.toDate}
|
|
251
|
+
.tasks=${this.tasks}
|
|
253
252
|
.resources=${this.resources}
|
|
254
253
|
.axisScale=${this.timeScale}
|
|
255
|
-
@scroll=${this.
|
|
254
|
+
@scroll=${this.syncScroll}
|
|
256
255
|
></ox-resource-chart>
|
|
257
256
|
</div>
|
|
258
257
|
</div>
|
|
@@ -268,7 +267,7 @@ export class OxGantt extends LitElement {
|
|
|
268
267
|
const sortedColumns = [...this.columnConfig].sort((a, b) => a.order - b.order)
|
|
269
268
|
|
|
270
269
|
return html`
|
|
271
|
-
<table>
|
|
270
|
+
<table class="task-table">
|
|
272
271
|
<thead>
|
|
273
272
|
<tr>
|
|
274
273
|
${sortedColumns.map(col =>
|
|
@@ -295,7 +294,7 @@ export class OxGantt extends LitElement {
|
|
|
295
294
|
<tr class=${this.selectedTaskId === task.id ? 'selected' : ''} @click=${() => this.selectTask(task.id)}>
|
|
296
295
|
${this.columnConfig.map(col =>
|
|
297
296
|
col.visible
|
|
298
|
-
? html`<td class="task-row" style="padding-left: ${col.name == '
|
|
297
|
+
? html`<td class="task-row" style="padding-left: ${col.name == 'title' ? level * 8 + 4 : 4}px">
|
|
299
298
|
${this.getTaskFieldValue(task, col.name)}
|
|
300
299
|
</td>`
|
|
301
300
|
: null
|
|
@@ -365,13 +364,20 @@ export class OxGantt extends LitElement {
|
|
|
365
364
|
const traverseTasks = (tasks: Task[]) => {
|
|
366
365
|
tasks.forEach(task => {
|
|
367
366
|
if (task.resources) {
|
|
367
|
+
const startDate = new Date(task.startDate)
|
|
368
|
+
const endDate = new Date(task.endDate)
|
|
369
|
+
const durationDays = d3.timeDay.count(startDate, d3.timeDay.offset(endDate, 1))
|
|
370
|
+
|
|
368
371
|
task.resources.forEach(resource => {
|
|
372
|
+
const totalAllocation = resource.allocated * durationDays
|
|
369
373
|
if (!resourceMap[resource.type]) {
|
|
370
374
|
resourceMap[resource.type] = 0
|
|
371
375
|
}
|
|
372
|
-
resourceMap[resource.type] +=
|
|
376
|
+
resourceMap[resource.type] += totalAllocation
|
|
377
|
+
console.log(resource.type, resource.allocated, durationDays, totalAllocation)
|
|
373
378
|
})
|
|
374
379
|
}
|
|
380
|
+
|
|
375
381
|
if (task.children) {
|
|
376
382
|
traverseTasks(task.children)
|
|
377
383
|
}
|
|
@@ -388,31 +394,41 @@ export class OxGantt extends LitElement {
|
|
|
388
394
|
|
|
389
395
|
syncScroll(e: Event) {
|
|
390
396
|
const target = e.target as HTMLElement
|
|
397
|
+
|
|
391
398
|
if (target === this.taskTableContainer) {
|
|
392
|
-
// Sync chart scroll with table scroll
|
|
393
399
|
const newScrollTop = this.taskTableContainer.scrollTop
|
|
394
400
|
this.ganttChart.setVerticalOffset(newScrollTop)
|
|
395
401
|
|
|
396
|
-
// Ensure chart scroll matches table scroll
|
|
397
402
|
if (this.ganttChart.getVerticalOffset() !== newScrollTop) {
|
|
398
403
|
this.ganttChart.setVerticalOffset(newScrollTop)
|
|
399
404
|
}
|
|
400
405
|
} else if (target === this.ganttChart) {
|
|
401
|
-
// Sync table scroll with chart scroll
|
|
402
406
|
const newScrollTop = this.ganttChart.getVerticalOffset()
|
|
403
407
|
this.taskTableContainer.scrollTop = newScrollTop
|
|
404
|
-
|
|
405
|
-
// Ensure table scroll matches chart scroll
|
|
406
408
|
this.ganttChart.setVerticalOffset(this.taskTableContainer.scrollTop)
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
409
|
|
|
410
|
-
|
|
411
|
-
|
|
410
|
+
const newScrollLeft = this.ganttChart.getHorizontalOffset()
|
|
411
|
+
this.resourceChart.setHorizontalOffset(newScrollLeft)
|
|
412
|
+
} else if (target === this.resourceChart) {
|
|
413
|
+
const newScrollTop = this.ganttChart.getVerticalOffset()
|
|
414
|
+
this.resourceTableContainer.scrollTop = newScrollTop
|
|
415
|
+
this.resourceChart.setVerticalOffset(this.taskTableContainer.scrollTop)
|
|
416
|
+
|
|
417
|
+
const newScrollLeft = this.resourceChart.getHorizontalOffset()
|
|
418
|
+
this.ganttChart.setHorizontalOffset(newScrollLeft)
|
|
419
|
+
} else if (target === this.resourceTableContainer) {
|
|
420
|
+
const newScrollTop = this.taskTableContainer.scrollTop
|
|
421
|
+
this.resourceChart.setVerticalOffset(newScrollTop)
|
|
422
|
+
|
|
423
|
+
if (this.resourceChart.getVerticalOffset() !== newScrollTop) {
|
|
424
|
+
this.resourceChart.setVerticalOffset(newScrollTop)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
412
427
|
}
|
|
413
428
|
|
|
414
|
-
|
|
415
|
-
|
|
429
|
+
syncTimeScale(e: CustomEvent) {
|
|
430
|
+
const detail = e.detail
|
|
431
|
+
this.resourceChart.syncTimeScale(detail)
|
|
416
432
|
}
|
|
417
433
|
|
|
418
434
|
resizeHorizontalStart(e: CustomEvent) {
|
package/src/ox-resource-chart.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { LitElement, html, css, PropertyValues } from 'lit'
|
|
2
|
-
import { customElement, property } from 'lit/decorators.js'
|
|
2
|
+
import { customElement, property, query } from 'lit/decorators.js'
|
|
3
3
|
import * as d3 from 'd3'
|
|
4
4
|
|
|
5
5
|
import { ScrollbarStyles } from '@operato/styles'
|
|
6
6
|
|
|
7
|
+
import { drawTimeScaleHeader } from './draw-time-scale-header'
|
|
8
|
+
import { Task, TimeScale } from './types'
|
|
9
|
+
|
|
7
10
|
@customElement('ox-resource-chart')
|
|
8
11
|
export class OxResourceChart extends LitElement {
|
|
9
12
|
static styles = [
|
|
@@ -14,14 +17,15 @@ export class OxResourceChart extends LitElement {
|
|
|
14
17
|
overflow: hidden;
|
|
15
18
|
position: relative;
|
|
16
19
|
}
|
|
20
|
+
|
|
17
21
|
div[chart-container] {
|
|
18
|
-
display:
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
|
|
19
25
|
overflow-x: auto;
|
|
20
|
-
overflow-y:
|
|
21
|
-
}
|
|
22
|
-
svg {
|
|
23
|
-
min-width: 100%;
|
|
26
|
+
overflow-y: auto;
|
|
24
27
|
}
|
|
28
|
+
|
|
25
29
|
svg[header] {
|
|
26
30
|
display: block;
|
|
27
31
|
min-width: 100%;
|
|
@@ -34,23 +38,30 @@ export class OxResourceChart extends LitElement {
|
|
|
34
38
|
top: 0;
|
|
35
39
|
z-index: 1;
|
|
36
40
|
}
|
|
41
|
+
|
|
37
42
|
svg[body] {
|
|
43
|
+
flex: 1;
|
|
38
44
|
display: block;
|
|
39
45
|
min-width: 100%;
|
|
40
46
|
}
|
|
47
|
+
|
|
41
48
|
.top-axis text,
|
|
42
49
|
.bottom-axis text {
|
|
43
50
|
font-size: 12px;
|
|
44
51
|
}
|
|
52
|
+
|
|
45
53
|
.grid line {
|
|
46
54
|
stroke: #e0e0e0;
|
|
47
55
|
}
|
|
56
|
+
|
|
48
57
|
.grid path {
|
|
49
58
|
stroke-width: 0;
|
|
50
59
|
}
|
|
60
|
+
|
|
51
61
|
.no-select {
|
|
52
62
|
user-select: none;
|
|
53
63
|
}
|
|
64
|
+
|
|
54
65
|
.selected {
|
|
55
66
|
stroke-width: 2px;
|
|
56
67
|
stroke: var(--ox-gantt-selected-color, blue);
|
|
@@ -62,10 +73,17 @@ export class OxResourceChart extends LitElement {
|
|
|
62
73
|
@property({ type: String }) toDate: string = new Date(new Date().getFullYear(), 11, 31).toISOString().split('T')[0]
|
|
63
74
|
@property({ type: String }) axisScale: string = 'week-day'
|
|
64
75
|
@property({ type: Array }) resources: any[] = []
|
|
76
|
+
@property({ type: Array }) tasks: Task[] = []
|
|
65
77
|
|
|
66
|
-
private
|
|
67
|
-
private
|
|
68
|
-
private
|
|
78
|
+
private timeScale: any
|
|
79
|
+
private baseWidth: number = 30
|
|
80
|
+
private level: TimeScale = 'week-day'
|
|
81
|
+
private fromTime?: Date
|
|
82
|
+
private toTime?: Date
|
|
83
|
+
|
|
84
|
+
@query('[chart-container') container!: HTMLDivElement
|
|
85
|
+
@query('svg[header]') svgHeader!: SVGSVGElement
|
|
86
|
+
@query('svg[body]') svgBody!: SVGSVGElement
|
|
69
87
|
|
|
70
88
|
render() {
|
|
71
89
|
return html`
|
|
@@ -81,7 +99,7 @@ export class OxResourceChart extends LitElement {
|
|
|
81
99
|
this.updateTimeScale()
|
|
82
100
|
}
|
|
83
101
|
|
|
84
|
-
if (changedProperties.has('resources')) {
|
|
102
|
+
if (changedProperties.has('resources') || changedProperties.has('tasks')) {
|
|
85
103
|
this.drawBody()
|
|
86
104
|
}
|
|
87
105
|
|
|
@@ -89,7 +107,10 @@ export class OxResourceChart extends LitElement {
|
|
|
89
107
|
}
|
|
90
108
|
|
|
91
109
|
updateTimeScale() {
|
|
92
|
-
this.
|
|
110
|
+
this.fromTime = new Date(this.fromDate)
|
|
111
|
+
this.toTime = new Date(this.toDate)
|
|
112
|
+
|
|
113
|
+
this.timeScale = d3
|
|
93
114
|
.scaleTime()
|
|
94
115
|
.domain([d3.timeDay.floor(new Date(this.fromDate)), d3.timeDay.offset(new Date(this.toDate), 1)])
|
|
95
116
|
.range([0, this.getChartWidth()])
|
|
@@ -97,50 +118,42 @@ export class OxResourceChart extends LitElement {
|
|
|
97
118
|
|
|
98
119
|
getChartWidth() {
|
|
99
120
|
const totalDays = d3.timeDay.count(new Date(this.fromDate), d3.timeDay.offset(new Date(this.toDate), 1))
|
|
100
|
-
return (totalDays * this.
|
|
121
|
+
return (totalDays * this.baseWidth) / this.getScale()
|
|
101
122
|
}
|
|
102
123
|
|
|
103
124
|
getScale() {
|
|
104
|
-
if (this.
|
|
125
|
+
if (this.level == 'quarter-week') {
|
|
105
126
|
return 7 / 2
|
|
106
|
-
} else if (this.
|
|
127
|
+
} else if (this.level == 'year-month') {
|
|
107
128
|
return 30 / 2
|
|
108
|
-
} else if (this.
|
|
129
|
+
} else if (this.level === 'year-quarter') {
|
|
109
130
|
return 90 / 2
|
|
110
131
|
}
|
|
111
132
|
return 1
|
|
112
133
|
}
|
|
113
134
|
|
|
114
135
|
drawAxes() {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
header.selectAll('*').remove()
|
|
118
|
-
header.attr('width', this.getChartWidth())
|
|
119
|
-
|
|
120
|
-
const axisTop = d3.axisTop(this._timeScale).tickSize(-20)
|
|
121
|
-
const axisBottom = d3.axisBottom(this._timeScale).tickSize(-20)
|
|
122
|
-
|
|
123
|
-
header.append('g').attr('class', 'top-axis').attr('transform', 'translate(0, 20)').call(axisTop)
|
|
124
|
-
header.append('g').attr('class', 'bottom-axis').attr('transform', 'translate(0, 40)').call(axisBottom)
|
|
136
|
+
drawTimeScaleHeader(this.svgHeader as any, this.timeScale, this.fromTime!, this.toTime!, this.baseWidth, this.level)
|
|
125
137
|
}
|
|
126
138
|
|
|
127
139
|
drawBody() {
|
|
128
|
-
const body = d3.select(this.
|
|
140
|
+
const body = d3.select(this.svgBody)
|
|
129
141
|
|
|
130
142
|
body.selectAll('*').remove()
|
|
131
143
|
body.attr('width', this.getChartWidth())
|
|
132
144
|
|
|
133
|
-
// Add grid lines for resource chart
|
|
134
145
|
if (this.resources.length > 0) {
|
|
146
|
+
const chartHeight = this.resources.length * 30
|
|
147
|
+
|
|
135
148
|
const addVerticalGridLines = (ticks: Date[]) => {
|
|
136
149
|
ticks.forEach(tick => {
|
|
137
|
-
const startX = this.
|
|
150
|
+
const startX = this.timeScale(tick)
|
|
138
151
|
body
|
|
139
152
|
.append('line')
|
|
140
153
|
.attr('x1', startX)
|
|
141
154
|
.attr('x2', startX)
|
|
142
155
|
.attr('y1', 0)
|
|
143
|
-
.attr('y2',
|
|
156
|
+
.attr('y2', chartHeight) // Ensure vertical lines extend to the bottom
|
|
144
157
|
.attr('stroke', 'var(--ox-gantt-color-gridlines, var(--md-sys-color-outline))')
|
|
145
158
|
.attr('stroke-width', 1)
|
|
146
159
|
})
|
|
@@ -158,35 +171,158 @@ export class OxResourceChart extends LitElement {
|
|
|
158
171
|
.attr('stroke', 'var(--ox-gantt-color-gridlines, var(--md-sys-color-outline))')
|
|
159
172
|
.attr('stroke-width', 1)
|
|
160
173
|
})
|
|
174
|
+
|
|
175
|
+
// Ensure the last horizontal line extends to the bottom of the chart
|
|
176
|
+
body
|
|
177
|
+
.append('line')
|
|
178
|
+
.attr('x1', 0)
|
|
179
|
+
.attr('x2', this.getChartWidth())
|
|
180
|
+
.attr('y1', chartHeight)
|
|
181
|
+
.attr('y2', chartHeight)
|
|
182
|
+
.attr('stroke', 'var(--ox-gantt-color-gridlines, var(--md-sys-color-outline))')
|
|
183
|
+
.attr('stroke-width', 1)
|
|
161
184
|
}
|
|
162
185
|
|
|
163
|
-
|
|
164
|
-
|
|
186
|
+
let ticks
|
|
187
|
+
if (this.level === 'week-day') {
|
|
188
|
+
ticks = d3.timeDay
|
|
165
189
|
.every(1)!
|
|
166
190
|
.range(d3.timeDay.floor(new Date(this.fromDate)), d3.timeDay.offset(new Date(this.toDate), 1))
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const weekTicks = d3.timeWeek
|
|
191
|
+
} else if (this.level === 'quarter-week') {
|
|
192
|
+
ticks = d3.timeWeek
|
|
170
193
|
.every(1)!
|
|
171
194
|
.range(d3.timeWeek.floor(new Date(this.fromDate)), d3.timeWeek.offset(new Date(this.toDate), 1))
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const monthTicks = d3.timeMonth
|
|
195
|
+
} else if (this.level === 'year-month') {
|
|
196
|
+
ticks = d3.timeMonth
|
|
175
197
|
.every(1)!
|
|
176
198
|
.range(d3.timeMonth.floor(new Date(this.fromDate)), d3.timeMonth.offset(new Date(this.toDate), 1))
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const quarterTicks = d3.timeMonth
|
|
199
|
+
} else if (this.level === 'year-quarter') {
|
|
200
|
+
ticks = d3.timeMonth
|
|
180
201
|
.every(3)!
|
|
181
202
|
.range(d3.timeMonth.floor(new Date(this.fromDate)), d3.timeMonth.offset(new Date(this.toDate), 3))
|
|
182
|
-
addVerticalGridLines(quarterTicks)
|
|
183
203
|
}
|
|
184
204
|
|
|
205
|
+
addVerticalGridLines(ticks!)
|
|
185
206
|
addHorizontalGridLines()
|
|
207
|
+
|
|
208
|
+
// Render the resource allocations
|
|
209
|
+
this.renderResourceAllocations(body, ticks!)
|
|
186
210
|
}
|
|
187
211
|
}
|
|
188
212
|
|
|
189
|
-
|
|
190
|
-
this.
|
|
213
|
+
renderResourceAllocations(body: d3.Selection<SVGSVGElement, unknown, null, undefined>, ticks: Date[]) {
|
|
214
|
+
const resourceAllocations = this.calculateResourceAllocations(ticks)
|
|
215
|
+
|
|
216
|
+
this.resources.forEach((resource, index) => {
|
|
217
|
+
const y = index * 30 + 15 // Center the text vertically in the row
|
|
218
|
+
ticks.forEach(tick => {
|
|
219
|
+
const tickEnd = this.getTickEnd(tick)
|
|
220
|
+
const dateKey = this.formatDateKey(tick, tickEnd)
|
|
221
|
+
const allocation = resourceAllocations[resource.type][dateKey]
|
|
222
|
+
if (allocation > 0) {
|
|
223
|
+
const startX = this.timeScale(tick)
|
|
224
|
+
const tickInterval = this.timeScale(tickEnd) - startX
|
|
225
|
+
|
|
226
|
+
body
|
|
227
|
+
.append('text')
|
|
228
|
+
.attr('x', startX + tickInterval / 2)
|
|
229
|
+
.attr('y', y)
|
|
230
|
+
.attr('text-anchor', 'middle')
|
|
231
|
+
.attr('alignment-baseline', 'middle')
|
|
232
|
+
.attr('dominant-baseline', 'middle')
|
|
233
|
+
.text(allocation)
|
|
234
|
+
.style('font-size', '12px')
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
calculateResourceAllocations(ticks: Date[]) {
|
|
241
|
+
const resourceAllocations: { [key: string]: { [key: string]: number } } = {}
|
|
242
|
+
|
|
243
|
+
// Initialize the resourceAllocations object
|
|
244
|
+
this.resources.forEach(resource => {
|
|
245
|
+
resourceAllocations[resource.type] = {}
|
|
246
|
+
ticks.forEach(tick => {
|
|
247
|
+
const tickEnd = this.getTickEnd(tick)
|
|
248
|
+
const dateKey = this.formatDateKey(tick, tickEnd)
|
|
249
|
+
resourceAllocations[resource.type][dateKey] = 0
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
// Function to process tasks recursively
|
|
254
|
+
const processTask = (task: Task) => {
|
|
255
|
+
const startDate = new Date(task.startDate)
|
|
256
|
+
const endDate = new Date(task.endDate)
|
|
257
|
+
let currentDate = startDate
|
|
258
|
+
|
|
259
|
+
while (currentDate <= endDate) {
|
|
260
|
+
const tick = ticks.find(t => currentDate >= t && currentDate < this.getTickEnd(t))
|
|
261
|
+
if (tick) {
|
|
262
|
+
const tickEnd = this.getTickEnd(tick)
|
|
263
|
+
const dateKey = this.formatDateKey(tick, tickEnd)
|
|
264
|
+
|
|
265
|
+
;(task.resources || []).forEach(resource => {
|
|
266
|
+
if (resourceAllocations[resource.type][dateKey] !== undefined) {
|
|
267
|
+
resourceAllocations[resource.type][dateKey] += resource.allocated
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
currentDate.setDate(currentDate.getDate() + 1)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Recursively process child tasks
|
|
276
|
+
if (task.children) {
|
|
277
|
+
task.children.forEach(childTask => processTask(childTask))
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Process all tasks
|
|
282
|
+
this.tasks.forEach(task => processTask(task))
|
|
283
|
+
|
|
284
|
+
return resourceAllocations
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
getTickEnd(tick: Date) {
|
|
288
|
+
if (this.level === 'week-day') {
|
|
289
|
+
return d3.timeDay.offset(tick, 1)
|
|
290
|
+
} else if (this.level === 'quarter-week') {
|
|
291
|
+
return d3.timeWeek.offset(tick, 1)
|
|
292
|
+
} else if (this.level === 'year-month') {
|
|
293
|
+
return d3.timeMonth.offset(tick, 1)
|
|
294
|
+
} else if (this.level === 'year-quarter') {
|
|
295
|
+
return d3.timeMonth.offset(tick, 3)
|
|
296
|
+
}
|
|
297
|
+
return d3.timeDay.offset(tick, 1)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
formatDateKey(start: Date, end: Date) {
|
|
301
|
+
return `${start.toISOString().split('T')[0]}_${end.toISOString().split('T')[0]}`
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
syncTimeScale(detail: { baseWidth: number; level: TimeScale }) {
|
|
305
|
+
this.baseWidth = detail.baseWidth
|
|
306
|
+
this.level = detail.level
|
|
307
|
+
|
|
308
|
+
this.updateTimeScale()
|
|
309
|
+
this.drawAxes()
|
|
310
|
+
this.drawBody()
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
setVerticalOffset(offset: number) {
|
|
314
|
+
this.container.scrollTop = offset
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
getVerticalOffset(): number {
|
|
318
|
+
return this.container.scrollTop
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
setHorizontalOffset(offset: number) {
|
|
322
|
+
this.container.scrollLeft = offset
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
getHorizontalOffset(): number {
|
|
326
|
+
return this.container.scrollLeft
|
|
191
327
|
}
|
|
192
328
|
}
|
|
@@ -78,8 +78,8 @@ const Template: Story<ArgTypes> = ({
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
ox-gantt {
|
|
81
|
-
width:
|
|
82
|
-
height:
|
|
81
|
+
width: 100%;
|
|
82
|
+
height: 800px;
|
|
83
83
|
box-sizing: border-box;
|
|
84
84
|
|
|
85
85
|
background-color: var(--md-sys-color-primary-container);
|
|
@@ -182,8 +182,8 @@ Regular.args = {
|
|
|
182
182
|
progress: 60,
|
|
183
183
|
dependsOn: '',
|
|
184
184
|
length: '4d',
|
|
185
|
-
tags: ['
|
|
186
|
-
resources: [{ type: '창호공', allocated:
|
|
185
|
+
tags: ['active'],
|
|
186
|
+
resources: [{ type: '창호공', allocated: 5 }],
|
|
187
187
|
children: []
|
|
188
188
|
},
|
|
189
189
|
{
|
|
@@ -196,7 +196,7 @@ Regular.args = {
|
|
|
196
196
|
dependsOn: '3-1-1',
|
|
197
197
|
length: '5d',
|
|
198
198
|
tags: ['active'],
|
|
199
|
-
resources: [{ type: '창호공', allocated:
|
|
199
|
+
resources: [{ type: '창호공', allocated: 3 }],
|
|
200
200
|
children: []
|
|
201
201
|
}
|
|
202
202
|
]
|
|
@@ -227,7 +227,7 @@ Regular.args = {
|
|
|
227
227
|
dependsOn: '1',
|
|
228
228
|
length: '1d',
|
|
229
229
|
tags: ['done'],
|
|
230
|
-
resources: [{ type: '방수공', allocated:
|
|
230
|
+
resources: [{ type: '방수공', allocated: 5 }],
|
|
231
231
|
children: []
|
|
232
232
|
},
|
|
233
233
|
{
|
|
@@ -240,7 +240,7 @@ Regular.args = {
|
|
|
240
240
|
dependsOn: '4',
|
|
241
241
|
length: '1d',
|
|
242
242
|
tags: ['milestone'],
|
|
243
|
-
resources: [{ type: '목공', allocated:
|
|
243
|
+
resources: [{ type: '목공', allocated: 3 }],
|
|
244
244
|
children: []
|
|
245
245
|
},
|
|
246
246
|
{
|
|
@@ -253,7 +253,7 @@ Regular.args = {
|
|
|
253
253
|
dependsOn: '5',
|
|
254
254
|
length: '1d',
|
|
255
255
|
tags: ['milestone'],
|
|
256
|
-
resources: [{ type: '금속공', allocated:
|
|
256
|
+
resources: [{ type: '금속공', allocated: 1 }],
|
|
257
257
|
children: []
|
|
258
258
|
},
|
|
259
259
|
{
|
|
@@ -279,7 +279,7 @@ Regular.args = {
|
|
|
279
279
|
dependsOn: '7',
|
|
280
280
|
length: '1d',
|
|
281
281
|
tags: ['critical'],
|
|
282
|
-
resources: [{ type: '창호공', allocated:
|
|
282
|
+
resources: [{ type: '창호공', allocated: 7 }],
|
|
283
283
|
children: []
|
|
284
284
|
},
|
|
285
285
|
{
|
|
@@ -292,7 +292,7 @@ Regular.args = {
|
|
|
292
292
|
dependsOn: '8',
|
|
293
293
|
length: '1d',
|
|
294
294
|
tags: ['active'],
|
|
295
|
-
resources: [{ type: '도배공', allocated:
|
|
295
|
+
resources: [{ type: '도배공', allocated: 8 }],
|
|
296
296
|
children: []
|
|
297
297
|
},
|
|
298
298
|
{
|
|
@@ -318,6 +318,7 @@ Regular.args = {
|
|
|
318
318
|
dependsOn: '9',
|
|
319
319
|
length: '1d',
|
|
320
320
|
tags: ['milestone'],
|
|
321
|
+
resources: [{ type: '철근/형틀공', allocated: 6 }],
|
|
321
322
|
children: []
|
|
322
323
|
},
|
|
323
324
|
{
|
|
@@ -329,6 +330,7 @@ Regular.args = {
|
|
|
329
330
|
endDate: '2023-01-23',
|
|
330
331
|
dependsOn: '9',
|
|
331
332
|
length: '1d',
|
|
333
|
+
resources: [{ type: '방수공', allocated: 6 }],
|
|
332
334
|
tags: ['milestone'],
|
|
333
335
|
children: [
|
|
334
336
|
{
|
|
@@ -342,6 +344,7 @@ Regular.args = {
|
|
|
342
344
|
dependsOn: '',
|
|
343
345
|
length: '4d',
|
|
344
346
|
tags: ['done'],
|
|
347
|
+
resources: [{ type: '방수공', allocated: 6 }],
|
|
345
348
|
children: []
|
|
346
349
|
},
|
|
347
350
|
{
|
|
@@ -354,6 +357,7 @@ Regular.args = {
|
|
|
354
357
|
dependsOn: '12-1',
|
|
355
358
|
length: '5d',
|
|
356
359
|
tags: ['active'],
|
|
360
|
+
resources: [{ type: '조적공', allocated: 6 }],
|
|
357
361
|
children: []
|
|
358
362
|
}
|
|
359
363
|
]
|
|
@@ -380,6 +384,7 @@ Regular.args = {
|
|
|
380
384
|
dependsOn: '13',
|
|
381
385
|
length: '1d',
|
|
382
386
|
tags: ['milestone'],
|
|
387
|
+
resources: [{ type: '금속공', allocated: 1 }],
|
|
383
388
|
children: []
|
|
384
389
|
},
|
|
385
390
|
{
|
|
@@ -403,6 +408,7 @@ Regular.args = {
|
|
|
403
408
|
endDate: '2023-02-05',
|
|
404
409
|
dependsOn: '15',
|
|
405
410
|
length: '1d',
|
|
411
|
+
resources: [{ type: '창호공', allocated: 1 }],
|
|
406
412
|
tags: ['active'],
|
|
407
413
|
children: []
|
|
408
414
|
},
|
|
@@ -440,6 +446,7 @@ Regular.args = {
|
|
|
440
446
|
dependsOn: '18',
|
|
441
447
|
length: '1d',
|
|
442
448
|
tags: ['milestone'],
|
|
449
|
+
resources: [{ type: '창호공', allocated: 7 }],
|
|
443
450
|
children: []
|
|
444
451
|
}
|
|
445
452
|
]
|
package/translations/en.json
CHANGED
|
@@ -8,5 +8,7 @@
|
|
|
8
8
|
"label.gantt-task-tags": "tags",
|
|
9
9
|
"label.gantt-task-progress": "progress",
|
|
10
10
|
"label.gantt-task-previous": "previous",
|
|
11
|
-
"label.gantt-task-resources": "resources"
|
|
11
|
+
"label.gantt-task-resources": "resources",
|
|
12
|
+
"label.gantt-resource-type": "type",
|
|
13
|
+
"label.gantt-resource-allocated": "allocated"
|
|
12
14
|
}
|