@operato/gantt 7.0.21 → 7.0.23
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 +20 -0
- package/dist/src/draw-time-scale-header.d.ts +5 -0
- package/dist/src/draw-time-scale-header.js +88 -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 +40 -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 +163 -40
- 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 +142 -0
- package/src/gantt-chart-controller.ts +6 -189
- package/src/ox-gantt-chart.ts +30 -0
- package/src/ox-gantt.ts +44 -25
- package/src/ox-resource-chart.ts +188 -44
- 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.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 {
|
|
@@ -118,6 +120,10 @@ export class OxGantt extends LitElement {
|
|
|
118
120
|
tr.selected {
|
|
119
121
|
background-color: var(--ox-gantt-selected-background-color, var(--md-sys-color-surface-variant));
|
|
120
122
|
}
|
|
123
|
+
|
|
124
|
+
.resource-table-container th {
|
|
125
|
+
height: 30px;
|
|
126
|
+
}
|
|
121
127
|
`
|
|
122
128
|
]
|
|
123
129
|
|
|
@@ -182,11 +188,9 @@ export class OxGantt extends LitElement {
|
|
|
182
188
|
|
|
183
189
|
@query('.task-container') taskContainer!: HTMLDivElement
|
|
184
190
|
@query('.task-table-container') taskTableContainer!: HTMLDivElement
|
|
185
|
-
@query('.task-chart-container') taskChartContainer!: HTMLDivElement
|
|
186
191
|
@query('.resource-container') resourceContainer!: HTMLDivElement
|
|
187
192
|
@query('.resource-table-container') resourceTableContainer!: HTMLDivElement
|
|
188
|
-
@query('.
|
|
189
|
-
@query('table') taskTable!: HTMLTableElement
|
|
193
|
+
@query('.task-table') taskTable!: HTMLTableElement
|
|
190
194
|
@query('ox-gantt-chart') ganttChart!: OxGanttChart
|
|
191
195
|
@query('ox-resource-chart') resourceChart!: OxResourceChart
|
|
192
196
|
|
|
@@ -207,10 +211,7 @@ export class OxGantt extends LitElement {
|
|
|
207
211
|
></ox-gantt-resize-splitter>
|
|
208
212
|
<ox-gantt-chart
|
|
209
213
|
class="task-chart-container"
|
|
210
|
-
@scroll=${
|
|
211
|
-
this.syncScroll(e)
|
|
212
|
-
this.syncResourceChartScroll(e)
|
|
213
|
-
}}
|
|
214
|
+
@scroll=${this.syncScroll}
|
|
214
215
|
.fromDate=${this.fromDate}
|
|
215
216
|
.toDate=${this.toDate}
|
|
216
217
|
.tasks=${this.tasks}
|
|
@@ -218,6 +219,7 @@ export class OxGantt extends LitElement {
|
|
|
218
219
|
.axisScale=${this.timeScale}
|
|
219
220
|
?extend-grid-lines=${this.extendGridLines}
|
|
220
221
|
@task-clicked=${this.handleTaskClicked}
|
|
222
|
+
@timescale-changed=${(e: CustomEvent) => this.syncTimeScale(e)}
|
|
221
223
|
></ox-gantt-chart>
|
|
222
224
|
</div>
|
|
223
225
|
<ox-gantt-resize-splitter
|
|
@@ -227,12 +229,12 @@ export class OxGantt extends LitElement {
|
|
|
227
229
|
@splitter-dragend=${(e: CustomEvent) => this.resizeVerticalEnd(e)}
|
|
228
230
|
></ox-gantt-resize-splitter>
|
|
229
231
|
<div class="resource-container">
|
|
230
|
-
<div class="resource-table-container">
|
|
232
|
+
<div class="resource-table-container" @scroll=${this.syncScroll}>
|
|
231
233
|
<table>
|
|
232
234
|
<thead>
|
|
233
235
|
<tr>
|
|
234
|
-
<th style="width: 100px"
|
|
235
|
-
<th
|
|
236
|
+
<th style="width: 100px">${i18next.t('label.gantt-resource-type')}</th>
|
|
237
|
+
<th>${i18next.t('label.gantt-resource-allocated')}</th>
|
|
236
238
|
</tr>
|
|
237
239
|
</thead>
|
|
238
240
|
<tbody>
|
|
@@ -250,9 +252,10 @@ export class OxGantt extends LitElement {
|
|
|
250
252
|
class="resource-chart-container"
|
|
251
253
|
.fromDate=${this.fromDate}
|
|
252
254
|
.toDate=${this.toDate}
|
|
255
|
+
.tasks=${this.tasks}
|
|
253
256
|
.resources=${this.resources}
|
|
254
257
|
.axisScale=${this.timeScale}
|
|
255
|
-
@scroll=${this.
|
|
258
|
+
@scroll=${this.syncScroll}
|
|
256
259
|
></ox-resource-chart>
|
|
257
260
|
</div>
|
|
258
261
|
</div>
|
|
@@ -268,7 +271,7 @@ export class OxGantt extends LitElement {
|
|
|
268
271
|
const sortedColumns = [...this.columnConfig].sort((a, b) => a.order - b.order)
|
|
269
272
|
|
|
270
273
|
return html`
|
|
271
|
-
<table>
|
|
274
|
+
<table class="task-table">
|
|
272
275
|
<thead>
|
|
273
276
|
<tr>
|
|
274
277
|
${sortedColumns.map(col =>
|
|
@@ -295,7 +298,7 @@ export class OxGantt extends LitElement {
|
|
|
295
298
|
<tr class=${this.selectedTaskId === task.id ? 'selected' : ''} @click=${() => this.selectTask(task.id)}>
|
|
296
299
|
${this.columnConfig.map(col =>
|
|
297
300
|
col.visible
|
|
298
|
-
? html`<td class="task-row" style="padding-left: ${col.name == '
|
|
301
|
+
? html`<td class="task-row" style="padding-left: ${col.name == 'title' ? level * 8 + 4 : 4}px">
|
|
299
302
|
${this.getTaskFieldValue(task, col.name)}
|
|
300
303
|
</td>`
|
|
301
304
|
: null
|
|
@@ -365,13 +368,19 @@ export class OxGantt extends LitElement {
|
|
|
365
368
|
const traverseTasks = (tasks: Task[]) => {
|
|
366
369
|
tasks.forEach(task => {
|
|
367
370
|
if (task.resources) {
|
|
371
|
+
const startDate = new Date(task.startDate)
|
|
372
|
+
const endDate = new Date(task.endDate)
|
|
373
|
+
const durationDays = d3.timeDay.count(startDate, d3.timeDay.offset(endDate, 1))
|
|
374
|
+
|
|
368
375
|
task.resources.forEach(resource => {
|
|
376
|
+
const totalAllocation = resource.allocated * durationDays
|
|
369
377
|
if (!resourceMap[resource.type]) {
|
|
370
378
|
resourceMap[resource.type] = 0
|
|
371
379
|
}
|
|
372
|
-
resourceMap[resource.type] +=
|
|
380
|
+
resourceMap[resource.type] += totalAllocation
|
|
373
381
|
})
|
|
374
382
|
}
|
|
383
|
+
|
|
375
384
|
if (task.children) {
|
|
376
385
|
traverseTasks(task.children)
|
|
377
386
|
}
|
|
@@ -388,31 +397,41 @@ export class OxGantt extends LitElement {
|
|
|
388
397
|
|
|
389
398
|
syncScroll(e: Event) {
|
|
390
399
|
const target = e.target as HTMLElement
|
|
400
|
+
|
|
391
401
|
if (target === this.taskTableContainer) {
|
|
392
|
-
// Sync chart scroll with table scroll
|
|
393
402
|
const newScrollTop = this.taskTableContainer.scrollTop
|
|
394
403
|
this.ganttChart.setVerticalOffset(newScrollTop)
|
|
395
404
|
|
|
396
|
-
// Ensure chart scroll matches table scroll
|
|
397
405
|
if (this.ganttChart.getVerticalOffset() !== newScrollTop) {
|
|
398
406
|
this.ganttChart.setVerticalOffset(newScrollTop)
|
|
399
407
|
}
|
|
400
408
|
} else if (target === this.ganttChart) {
|
|
401
|
-
// Sync table scroll with chart scroll
|
|
402
409
|
const newScrollTop = this.ganttChart.getVerticalOffset()
|
|
403
410
|
this.taskTableContainer.scrollTop = newScrollTop
|
|
404
|
-
|
|
405
|
-
// Ensure table scroll matches chart scroll
|
|
406
411
|
this.ganttChart.setVerticalOffset(this.taskTableContainer.scrollTop)
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
412
|
|
|
410
|
-
|
|
411
|
-
|
|
413
|
+
const newScrollLeft = this.ganttChart.getHorizontalOffset()
|
|
414
|
+
this.resourceChart.setHorizontalOffset(newScrollLeft)
|
|
415
|
+
} else if (target === this.resourceChart) {
|
|
416
|
+
const newScrollTop = this.ganttChart.getVerticalOffset()
|
|
417
|
+
this.resourceTableContainer.scrollTop = newScrollTop
|
|
418
|
+
this.resourceChart.setVerticalOffset(this.taskTableContainer.scrollTop)
|
|
419
|
+
|
|
420
|
+
const newScrollLeft = this.resourceChart.getHorizontalOffset()
|
|
421
|
+
this.ganttChart.setHorizontalOffset(newScrollLeft)
|
|
422
|
+
} else if (target === this.resourceTableContainer) {
|
|
423
|
+
const newScrollTop = this.taskTableContainer.scrollTop
|
|
424
|
+
this.resourceChart.setVerticalOffset(newScrollTop)
|
|
425
|
+
|
|
426
|
+
if (this.resourceChart.getVerticalOffset() !== newScrollTop) {
|
|
427
|
+
this.resourceChart.setVerticalOffset(newScrollTop)
|
|
428
|
+
}
|
|
429
|
+
}
|
|
412
430
|
}
|
|
413
431
|
|
|
414
|
-
|
|
415
|
-
|
|
432
|
+
syncTimeScale(e: CustomEvent) {
|
|
433
|
+
const detail = e.detail
|
|
434
|
+
this.resourceChart.syncTimeScale(detail)
|
|
416
435
|
}
|
|
417
436
|
|
|
418
437
|
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,43 +17,51 @@ 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%;
|
|
28
32
|
box-sizing: border-box;
|
|
29
33
|
border-bottom: 1px solid var(--ox-gantt-header-border-color, var(--md-sys-color-on-secondary-container));
|
|
30
|
-
height:
|
|
34
|
+
height: 30px;
|
|
31
35
|
background-color: var(--ox-gantt-header-background-color, var(--md-sys-color-secondary-container));
|
|
32
36
|
color: var(--ox-gantt-header-color, var(--md-sys-color-on-secondary-container));
|
|
33
37
|
position: sticky;
|
|
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,50 @@ 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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
header.append('g').attr('class', 'bottom-axis').attr('transform', 'translate(0, 40)').call(axisBottom)
|
|
136
|
+
drawTimeScaleHeader(
|
|
137
|
+
this.svgHeader as any,
|
|
138
|
+
this.timeScale,
|
|
139
|
+
this.fromTime!,
|
|
140
|
+
this.toTime!,
|
|
141
|
+
this.baseWidth,
|
|
142
|
+
this.level,
|
|
143
|
+
false /* 1 level only header */
|
|
144
|
+
)
|
|
125
145
|
}
|
|
126
146
|
|
|
127
147
|
drawBody() {
|
|
128
|
-
const body = d3.select(this.
|
|
148
|
+
const body = d3.select(this.svgBody)
|
|
129
149
|
|
|
130
150
|
body.selectAll('*').remove()
|
|
131
151
|
body.attr('width', this.getChartWidth())
|
|
132
152
|
|
|
133
|
-
// Add grid lines for resource chart
|
|
134
153
|
if (this.resources.length > 0) {
|
|
154
|
+
const chartHeight = this.resources.length * 30
|
|
155
|
+
|
|
135
156
|
const addVerticalGridLines = (ticks: Date[]) => {
|
|
136
157
|
ticks.forEach(tick => {
|
|
137
|
-
const startX = this.
|
|
158
|
+
const startX = this.timeScale(tick)
|
|
138
159
|
body
|
|
139
160
|
.append('line')
|
|
140
161
|
.attr('x1', startX)
|
|
141
162
|
.attr('x2', startX)
|
|
142
163
|
.attr('y1', 0)
|
|
143
|
-
.attr('y2',
|
|
164
|
+
.attr('y2', chartHeight) // Ensure vertical lines extend to the bottom
|
|
144
165
|
.attr('stroke', 'var(--ox-gantt-color-gridlines, var(--md-sys-color-outline))')
|
|
145
166
|
.attr('stroke-width', 1)
|
|
146
167
|
})
|
|
@@ -158,35 +179,158 @@ export class OxResourceChart extends LitElement {
|
|
|
158
179
|
.attr('stroke', 'var(--ox-gantt-color-gridlines, var(--md-sys-color-outline))')
|
|
159
180
|
.attr('stroke-width', 1)
|
|
160
181
|
})
|
|
182
|
+
|
|
183
|
+
// Ensure the last horizontal line extends to the bottom of the chart
|
|
184
|
+
body
|
|
185
|
+
.append('line')
|
|
186
|
+
.attr('x1', 0)
|
|
187
|
+
.attr('x2', this.getChartWidth())
|
|
188
|
+
.attr('y1', chartHeight)
|
|
189
|
+
.attr('y2', chartHeight)
|
|
190
|
+
.attr('stroke', 'var(--ox-gantt-color-gridlines, var(--md-sys-color-outline))')
|
|
191
|
+
.attr('stroke-width', 1)
|
|
161
192
|
}
|
|
162
193
|
|
|
163
|
-
|
|
164
|
-
|
|
194
|
+
let ticks
|
|
195
|
+
if (this.level === 'week-day') {
|
|
196
|
+
ticks = d3.timeDay
|
|
165
197
|
.every(1)!
|
|
166
198
|
.range(d3.timeDay.floor(new Date(this.fromDate)), d3.timeDay.offset(new Date(this.toDate), 1))
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const weekTicks = d3.timeWeek
|
|
199
|
+
} else if (this.level === 'quarter-week') {
|
|
200
|
+
ticks = d3.timeWeek
|
|
170
201
|
.every(1)!
|
|
171
202
|
.range(d3.timeWeek.floor(new Date(this.fromDate)), d3.timeWeek.offset(new Date(this.toDate), 1))
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const monthTicks = d3.timeMonth
|
|
203
|
+
} else if (this.level === 'year-month') {
|
|
204
|
+
ticks = d3.timeMonth
|
|
175
205
|
.every(1)!
|
|
176
206
|
.range(d3.timeMonth.floor(new Date(this.fromDate)), d3.timeMonth.offset(new Date(this.toDate), 1))
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const quarterTicks = d3.timeMonth
|
|
207
|
+
} else if (this.level === 'year-quarter') {
|
|
208
|
+
ticks = d3.timeMonth
|
|
180
209
|
.every(3)!
|
|
181
210
|
.range(d3.timeMonth.floor(new Date(this.fromDate)), d3.timeMonth.offset(new Date(this.toDate), 3))
|
|
182
|
-
addVerticalGridLines(quarterTicks)
|
|
183
211
|
}
|
|
184
212
|
|
|
213
|
+
addVerticalGridLines(ticks!)
|
|
185
214
|
addHorizontalGridLines()
|
|
215
|
+
|
|
216
|
+
// Render the resource allocations
|
|
217
|
+
this.renderResourceAllocations(body, ticks!)
|
|
186
218
|
}
|
|
187
219
|
}
|
|
188
220
|
|
|
189
|
-
|
|
190
|
-
this.
|
|
221
|
+
renderResourceAllocations(body: d3.Selection<SVGSVGElement, unknown, null, undefined>, ticks: Date[]) {
|
|
222
|
+
const resourceAllocations = this.calculateResourceAllocations(ticks)
|
|
223
|
+
|
|
224
|
+
this.resources.forEach((resource, index) => {
|
|
225
|
+
const y = index * 30 + 15 // Center the text vertically in the row
|
|
226
|
+
ticks.forEach(tick => {
|
|
227
|
+
const tickEnd = this.getTickEnd(tick)
|
|
228
|
+
const dateKey = this.formatDateKey(tick, tickEnd)
|
|
229
|
+
const allocation = resourceAllocations[resource.type][dateKey]
|
|
230
|
+
if (allocation > 0) {
|
|
231
|
+
const startX = this.timeScale(tick)
|
|
232
|
+
const tickInterval = this.timeScale(tickEnd) - startX
|
|
233
|
+
|
|
234
|
+
body
|
|
235
|
+
.append('text')
|
|
236
|
+
.attr('x', startX + tickInterval / 2)
|
|
237
|
+
.attr('y', y)
|
|
238
|
+
.attr('text-anchor', 'middle')
|
|
239
|
+
.attr('alignment-baseline', 'middle')
|
|
240
|
+
.attr('dominant-baseline', 'middle')
|
|
241
|
+
.text(allocation)
|
|
242
|
+
.style('font-size', '12px')
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
calculateResourceAllocations(ticks: Date[]) {
|
|
249
|
+
const resourceAllocations: { [key: string]: { [key: string]: number } } = {}
|
|
250
|
+
|
|
251
|
+
// Initialize the resourceAllocations object
|
|
252
|
+
this.resources.forEach(resource => {
|
|
253
|
+
resourceAllocations[resource.type] = {}
|
|
254
|
+
ticks.forEach(tick => {
|
|
255
|
+
const tickEnd = this.getTickEnd(tick)
|
|
256
|
+
const dateKey = this.formatDateKey(tick, tickEnd)
|
|
257
|
+
resourceAllocations[resource.type][dateKey] = 0
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
// Function to process tasks recursively
|
|
262
|
+
const processTask = (task: Task) => {
|
|
263
|
+
const startDate = new Date(task.startDate)
|
|
264
|
+
const endDate = new Date(task.endDate)
|
|
265
|
+
let currentDate = startDate
|
|
266
|
+
|
|
267
|
+
while (currentDate <= endDate) {
|
|
268
|
+
const tick = ticks.find(t => currentDate >= t && currentDate < this.getTickEnd(t))
|
|
269
|
+
if (tick) {
|
|
270
|
+
const tickEnd = this.getTickEnd(tick)
|
|
271
|
+
const dateKey = this.formatDateKey(tick, tickEnd)
|
|
272
|
+
|
|
273
|
+
;(task.resources || []).forEach(resource => {
|
|
274
|
+
if (resourceAllocations[resource.type][dateKey] !== undefined) {
|
|
275
|
+
resourceAllocations[resource.type][dateKey] += resource.allocated
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
currentDate.setDate(currentDate.getDate() + 1)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Recursively process child tasks
|
|
284
|
+
if (task.children) {
|
|
285
|
+
task.children.forEach(childTask => processTask(childTask))
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Process all tasks
|
|
290
|
+
this.tasks.forEach(task => processTask(task))
|
|
291
|
+
|
|
292
|
+
return resourceAllocations
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
getTickEnd(tick: Date) {
|
|
296
|
+
if (this.level === 'week-day') {
|
|
297
|
+
return d3.timeDay.offset(tick, 1)
|
|
298
|
+
} else if (this.level === 'quarter-week') {
|
|
299
|
+
return d3.timeWeek.offset(tick, 1)
|
|
300
|
+
} else if (this.level === 'year-month') {
|
|
301
|
+
return d3.timeMonth.offset(tick, 1)
|
|
302
|
+
} else if (this.level === 'year-quarter') {
|
|
303
|
+
return d3.timeMonth.offset(tick, 3)
|
|
304
|
+
}
|
|
305
|
+
return d3.timeDay.offset(tick, 1)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
formatDateKey(start: Date, end: Date) {
|
|
309
|
+
return `${start.toISOString().split('T')[0]}_${end.toISOString().split('T')[0]}`
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
syncTimeScale(detail: { baseWidth: number; level: TimeScale }) {
|
|
313
|
+
this.baseWidth = detail.baseWidth
|
|
314
|
+
this.level = detail.level
|
|
315
|
+
|
|
316
|
+
this.updateTimeScale()
|
|
317
|
+
this.drawAxes()
|
|
318
|
+
this.drawBody()
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
setVerticalOffset(offset: number) {
|
|
322
|
+
this.container.scrollTop = offset
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
getVerticalOffset(): number {
|
|
326
|
+
return this.container.scrollTop
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
setHorizontalOffset(offset: number) {
|
|
330
|
+
this.container.scrollLeft = offset
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
getHorizontalOffset(): number {
|
|
334
|
+
return this.container.scrollLeft
|
|
191
335
|
}
|
|
192
336
|
}
|
|
@@ -78,8 +78,8 @@ const Template: Story<ArgTypes> = ({
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
ox-gantt {
|
|
81
|
-
width:
|
|
82
|
-
height:
|
|
81
|
+
width: 95%;
|
|
82
|
+
height: 650px;
|
|
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
|
}
|
package/translations/ja.json
CHANGED
|
@@ -7,5 +7,7 @@
|
|
|
7
7
|
"label.gantt-task-end-date": "終了日",
|
|
8
8
|
"label.gantt-task-tags": "タグ",
|
|
9
9
|
"label.gantt-task-progress": "進捗",
|
|
10
|
-
"label.gantt-task-previous": "前"
|
|
10
|
+
"label.gantt-task-previous": "前",
|
|
11
|
+
"label.gantt-resource-type": "タイプ",
|
|
12
|
+
"label.gantt-resource-allocated": "割り当て"
|
|
11
13
|
}
|
package/translations/ko.json
CHANGED
|
@@ -8,5 +8,7 @@
|
|
|
8
8
|
"label.gantt-task-tags": "태그",
|
|
9
9
|
"label.gantt-task-progress": "진행 상황",
|
|
10
10
|
"label.gantt-task-previous": "이전",
|
|
11
|
-
"label.gantt-task-resources": "자원"
|
|
11
|
+
"label.gantt-task-resources": "자원",
|
|
12
|
+
"label.gantt-resource-type": "유형",
|
|
13
|
+
"label.gantt-resource-allocated": "할당"
|
|
12
14
|
}
|