@operato/gantt 7.0.20 → 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 +20 -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 +6 -1
- package/dist/src/ox-gantt-chart.js +39 -8
- package/dist/src/ox-gantt-chart.js.map +1 -1
- package/dist/src/ox-gantt-resize-splitter.js +8 -7
- package/dist/src/ox-gantt-resize-splitter.js.map +1 -1
- package/dist/src/ox-gantt.d.ts +24 -15
- package/dist/src/ox-gantt.js +224 -82
- package/dist/src/ox-gantt.js.map +1 -1
- package/dist/src/ox-resource-chart.d.ts +42 -0
- package/dist/src/ox-resource-chart.js +317 -0
- package/dist/src/ox-resource-chart.js.map +1 -0
- package/dist/src/types.d.ts +5 -0
- package/dist/src/types.js.map +1 -1
- package/dist/stories/ox-gantt.stories.js +75 -4
- 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 +46 -9
- package/src/ox-gantt-resize-splitter.ts +8 -7
- package/src/ox-gantt.ts +231 -86
- package/src/ox-resource-chart.ts +328 -0
- package/src/types.ts +6 -0
- package/stories/ox-gantt.stories.ts +75 -4
- package/translations/en.json +4 -1
- package/translations/ja.json +3 -1
- package/translations/ko.json +4 -1
- package/translations/ms.json +3 -1
- package/translations/zh.json +3 -1
|
@@ -2,6 +2,7 @@ import { LitElement, ReactiveController, ReactiveControllerHost } from 'lit'
|
|
|
2
2
|
import * as d3 from 'd3'
|
|
3
3
|
import { TimeScale, Task, TaskTag } from './types'
|
|
4
4
|
import { OxTaskTooltip } from './ox-task-tooltip'
|
|
5
|
+
import { drawTimeScaleHeader, getScale, getChartWidth } from './draw-time-scale-header'
|
|
5
6
|
|
|
6
7
|
export class GanttChartController implements ReactiveController {
|
|
7
8
|
private host: ReactiveControllerHost & LitElement
|
|
@@ -60,200 +61,16 @@ export class GanttChartController implements ReactiveController {
|
|
|
60
61
|
this.timeScale.range([0, this.getChartWidth()])
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
return (this.
|
|
64
|
+
getScale() {
|
|
65
|
+
return getScale(this.level)
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return 7 / 2
|
|
70
|
-
} else if (this.level == 'year-month') {
|
|
71
|
-
return 30 / 2
|
|
72
|
-
} else if (this.level == 'year-quarter') {
|
|
73
|
-
return 90 / 2
|
|
74
|
-
}
|
|
75
|
-
return 1
|
|
68
|
+
getChartWidth() {
|
|
69
|
+
return getChartWidth(this.fromTime, this.toTime, this.baseWidth, this.level)
|
|
76
70
|
}
|
|
77
71
|
|
|
78
72
|
drawAxes(svgHeader: SVGElement) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
header.selectAll('*').remove()
|
|
82
|
-
header.attr('width', this.getChartWidth())
|
|
83
|
-
|
|
84
|
-
const addGridLines = (ticks: Date[], yStart: number, yEnd: number) => {
|
|
85
|
-
ticks.forEach(tick => {
|
|
86
|
-
const startX = this.timeScale(tick)
|
|
87
|
-
header
|
|
88
|
-
.append('line')
|
|
89
|
-
.attr('x1', startX)
|
|
90
|
-
.attr('x2', startX)
|
|
91
|
-
.attr('y1', yStart)
|
|
92
|
-
.attr('y2', yEnd)
|
|
93
|
-
.attr('stroke', 'var(--ox-gantt-color-gridlines, var(--md-sys-color-on-secondary-container))')
|
|
94
|
-
.attr('stroke-width', 1)
|
|
95
|
-
})
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const centerLabel = (
|
|
99
|
-
selection: d3.Selection<SVGGElement, unknown, null, any>,
|
|
100
|
-
tickInterval: d3.CountableTimeInterval
|
|
101
|
-
) => {
|
|
102
|
-
selection.selectAll('text').attr('transform', (d: any) => {
|
|
103
|
-
const tickIntervalWidth = this.timeScale(tickInterval.offset(d, 1)) - this.timeScale(d)
|
|
104
|
-
return `translate(${tickIntervalWidth / 2}, -2)`
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (this.level === 'week-day') {
|
|
109
|
-
const weekAxis = d3
|
|
110
|
-
.axisTop(this.timeScale)
|
|
111
|
-
.ticks(d3.timeWeek.every(1))
|
|
112
|
-
.tickFormat(d3.timeFormat('%B %d') as any)
|
|
113
|
-
.tickSize(0)
|
|
114
|
-
const weekTicks = d3.timeWeek
|
|
115
|
-
.every(1)!
|
|
116
|
-
.range(d3.timeWeek.floor(this.fromTime), d3.timeWeek.offset(this.toTime, 1))
|
|
117
|
-
addGridLines(weekTicks, 0, 20)
|
|
118
|
-
|
|
119
|
-
const weekAxisG = header
|
|
120
|
-
.append('g')
|
|
121
|
-
.classed('week-axis no-select md-typescale-label-small', true)
|
|
122
|
-
.attr('transform', `translate(0, 20)`)
|
|
123
|
-
.call(weekAxis)
|
|
124
|
-
|
|
125
|
-
centerLabel(weekAxisG, d3.timeWeek)
|
|
126
|
-
|
|
127
|
-
const dayAxis = d3
|
|
128
|
-
.axisTop(this.timeScale)
|
|
129
|
-
.ticks(d3.timeDay.every(1))
|
|
130
|
-
.tickFormat(d3.timeFormat('%d') as any)
|
|
131
|
-
.tickSize(0)
|
|
132
|
-
|
|
133
|
-
const dayTicks = d3.timeDay.every(1)!.range(d3.timeDay.floor(this.fromTime), d3.timeDay.offset(this.toTime, 1))
|
|
134
|
-
addGridLines(dayTicks, 20, 40)
|
|
135
|
-
|
|
136
|
-
const dayAxisG = header
|
|
137
|
-
.append('g')
|
|
138
|
-
.classed('day-axis no-select md-typescale-label-small', true)
|
|
139
|
-
.attr('transform', `translate(0, 40)`)
|
|
140
|
-
.call(dayAxis)
|
|
141
|
-
|
|
142
|
-
centerLabel(dayAxisG, d3.timeDay)
|
|
143
|
-
} else if (this.level === 'quarter-week') {
|
|
144
|
-
const quarterAxis = d3
|
|
145
|
-
.axisTop(this.timeScale)
|
|
146
|
-
.ticks(d3.timeMonth.every(3))
|
|
147
|
-
.tickFormat(d3.timeFormat('%Y %b') as any)
|
|
148
|
-
.tickSize(0)
|
|
149
|
-
|
|
150
|
-
const quarterTicks = d3.timeMonth
|
|
151
|
-
.every(3)!
|
|
152
|
-
.range(d3.timeMonth.floor(this.fromTime), d3.timeMonth.offset(this.toTime, 3))
|
|
153
|
-
addGridLines(quarterTicks, 0, 20)
|
|
154
|
-
|
|
155
|
-
const quarterAxisG = header
|
|
156
|
-
.append('g')
|
|
157
|
-
.classed('quarter-axis no-select md-typescale-label-small', true)
|
|
158
|
-
.attr('transform', `translate(0, 20)`)
|
|
159
|
-
.call(quarterAxis)
|
|
160
|
-
|
|
161
|
-
centerLabel(quarterAxisG, d3.timeMonth.every(3) as any)
|
|
162
|
-
|
|
163
|
-
const weekAxis = d3
|
|
164
|
-
.axisTop(this.timeScale)
|
|
165
|
-
.ticks(d3.timeWeek.every(1))
|
|
166
|
-
.tickFormat(d3.timeFormat('%b %d') as any)
|
|
167
|
-
.tickSize(0)
|
|
168
|
-
const weekTicks = d3.timeWeek
|
|
169
|
-
.every(1)!
|
|
170
|
-
.range(d3.timeWeek.floor(this.fromTime), d3.timeWeek.offset(this.toTime, 1))
|
|
171
|
-
addGridLines(weekTicks, 20, 40)
|
|
172
|
-
|
|
173
|
-
const weekAxisG = header
|
|
174
|
-
.append('g')
|
|
175
|
-
.classed('week-axis no-select md-typescale-label-small', true)
|
|
176
|
-
.attr('transform', `translate(0, 40)`)
|
|
177
|
-
.call(weekAxis)
|
|
178
|
-
|
|
179
|
-
centerLabel(weekAxisG, d3.timeWeek)
|
|
180
|
-
} else if (this.level === 'year-month') {
|
|
181
|
-
const yearAxis = d3
|
|
182
|
-
.axisTop(this.timeScale)
|
|
183
|
-
.ticks(d3.timeYear.every(1))
|
|
184
|
-
.tickFormat(d3.timeFormat('%Y') as any)
|
|
185
|
-
.tickSize(0)
|
|
186
|
-
|
|
187
|
-
const yearTicks = d3.timeYear
|
|
188
|
-
.every(1)!
|
|
189
|
-
.range(d3.timeYear.floor(this.fromTime), d3.timeYear.offset(this.toTime, 1))
|
|
190
|
-
addGridLines(yearTicks, 0, 20)
|
|
191
|
-
|
|
192
|
-
const yearAxisG = header
|
|
193
|
-
.append('g')
|
|
194
|
-
.classed('year-axis no-select md-typescale-label-small', true)
|
|
195
|
-
.attr('transform', `translate(0, 20)`)
|
|
196
|
-
.call(yearAxis)
|
|
197
|
-
|
|
198
|
-
centerLabel(yearAxisG, d3.timeYear.every(1)! as any)
|
|
199
|
-
|
|
200
|
-
const monthAxis = d3
|
|
201
|
-
.axisTop(this.timeScale)
|
|
202
|
-
.ticks(d3.timeMonth.every(1))
|
|
203
|
-
.tickFormat(d3.timeFormat('%b') as any)
|
|
204
|
-
.tickSize(0)
|
|
205
|
-
|
|
206
|
-
const monthTicks = d3.timeMonth
|
|
207
|
-
.every(1)!
|
|
208
|
-
.range(d3.timeMonth.floor(this.fromTime), d3.timeMonth.offset(this.toTime, 1))
|
|
209
|
-
addGridLines(monthTicks, 20, 40)
|
|
210
|
-
|
|
211
|
-
const monthAxisG = header
|
|
212
|
-
.append('g')
|
|
213
|
-
.classed('month-axis no-select md-typescale-label-small', true)
|
|
214
|
-
.attr('transform', `translate(0, 40)`)
|
|
215
|
-
.call(monthAxis)
|
|
216
|
-
|
|
217
|
-
centerLabel(monthAxisG, d3.timeMonth.every(1) as any)
|
|
218
|
-
} else if (this.level === 'year-quarter') {
|
|
219
|
-
const yearAxis = d3
|
|
220
|
-
.axisTop(this.timeScale)
|
|
221
|
-
.ticks(d3.timeYear.every(1))
|
|
222
|
-
.tickFormat(d3.timeFormat('%Y') as any)
|
|
223
|
-
.tickSize(0)
|
|
224
|
-
|
|
225
|
-
const yearTicks = d3.timeYear
|
|
226
|
-
.every(1)!
|
|
227
|
-
.range(d3.timeYear.floor(this.fromTime), d3.timeYear.offset(this.toTime, 1))
|
|
228
|
-
addGridLines(yearTicks, 0, 20)
|
|
229
|
-
|
|
230
|
-
const yearAxisG = header
|
|
231
|
-
.append('g')
|
|
232
|
-
.classed('year-axis no-select md-typescale-label-small', true)
|
|
233
|
-
.attr('transform', `translate(0, 20)`)
|
|
234
|
-
.call(yearAxis)
|
|
235
|
-
|
|
236
|
-
centerLabel(yearAxisG, d3.timeYear.every(1)! as any)
|
|
237
|
-
|
|
238
|
-
const quarterAxis = d3
|
|
239
|
-
.axisTop(this.timeScale)
|
|
240
|
-
.ticks(d3.timeMonth.every(3))
|
|
241
|
-
.tickFormat(d3.timeFormat('%Y %b') as any)
|
|
242
|
-
.tickSize(0)
|
|
243
|
-
|
|
244
|
-
const quarterTicks = d3.timeMonth
|
|
245
|
-
.every(3)!
|
|
246
|
-
.range(d3.timeMonth.floor(this.fromTime), d3.timeMonth.offset(this.toTime, 3))
|
|
247
|
-
addGridLines(quarterTicks, 20, 40)
|
|
248
|
-
|
|
249
|
-
const quarterAxisG = header
|
|
250
|
-
.append('g')
|
|
251
|
-
.classed('quarter-axis no-select md-typescale-label-small', true)
|
|
252
|
-
.attr('transform', `translate(0, 40)`)
|
|
253
|
-
.call(quarterAxis)
|
|
254
|
-
|
|
255
|
-
centerLabel(quarterAxisG, d3.timeMonth.every(3) as any)
|
|
256
|
-
}
|
|
73
|
+
drawTimeScaleHeader(svgHeader, this.timeScale, this.fromTime, this.toTime, this.baseWidth, this.level)
|
|
257
74
|
}
|
|
258
75
|
|
|
259
76
|
drawBody(svgBody: SVGElement, tooltip: OxTaskTooltip) {
|
package/src/ox-gantt-chart.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import './ox-task-tooltip'
|
|
1
|
+
import './ox-task-tooltip.js'
|
|
2
|
+
import './ox-resource-chart.js'
|
|
2
3
|
|
|
3
4
|
import { LitElement, html, css, PropertyValues, PropertyValueMap } from 'lit'
|
|
4
5
|
import { customElement, property, query } from 'lit/decorators.js'
|
|
@@ -39,7 +40,6 @@ export class OxGanttChart extends LitElement {
|
|
|
39
40
|
height: 40px;
|
|
40
41
|
background-color: var(--ox-gantt-header-background-color, var(--md-sys-color-secondary-container));
|
|
41
42
|
color: var(--ox-gantt-header-color, var(--md-sys-color-on-secondary-container));
|
|
42
|
-
|
|
43
43
|
position: sticky;
|
|
44
44
|
top: 0;
|
|
45
45
|
z-index: 1;
|
|
@@ -79,7 +79,7 @@ export class OxGanttChart extends LitElement {
|
|
|
79
79
|
@query('svg[header]') svgHeader!: SVGElement
|
|
80
80
|
@query('svg[body]') svgBody!: SVGElement
|
|
81
81
|
@query('[chart-container]') container!: HTMLDivElement
|
|
82
|
-
@query('ox-task-tooltip') tooltip!: OxTaskTooltip
|
|
82
|
+
@query('ox-task-tooltip') tooltip!: OxTaskTooltip
|
|
83
83
|
|
|
84
84
|
private ganttChartController: GanttChartController
|
|
85
85
|
|
|
@@ -98,12 +98,10 @@ export class OxGanttChart extends LitElement {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
if (changedProperties.has('tasks')) {
|
|
101
|
-
this.ganttChartController.setTasks(this.tasks)
|
|
102
|
-
this.svgBody.style.height =
|
|
103
|
-
Math.max(this.tasks.length * 30, 2000 /* 태스크가 많이 없어도, 빈 공간이 없도록, 충분히 높게 */) + 'px'
|
|
101
|
+
this.ganttChartController.setTasks(this.flattenTasks(this.tasks))
|
|
102
|
+
this.svgBody.style.height = Math.max(this.tasks.length * 30, 2000) + 'px'
|
|
104
103
|
}
|
|
105
104
|
|
|
106
|
-
// Update locale
|
|
107
105
|
if (changedProperties.has('locale')) {
|
|
108
106
|
this.updateLocale(this.locale)
|
|
109
107
|
}
|
|
@@ -135,15 +133,19 @@ export class OxGanttChart extends LitElement {
|
|
|
135
133
|
>
|
|
136
134
|
<svg
|
|
137
135
|
header
|
|
138
|
-
@date-range-selected=${(e: CustomEvent) =>
|
|
136
|
+
@date-range-selected=${(e: CustomEvent) => this.handleDateRangeSelected(e)}
|
|
139
137
|
@wheel=${this.handleWheelForHeader.bind(this)}
|
|
140
138
|
></svg>
|
|
141
139
|
<svg body></svg>
|
|
142
|
-
<ox-task-tooltip></ox-task-tooltip>
|
|
140
|
+
<ox-task-tooltip></ox-task-tooltip>
|
|
143
141
|
</div>
|
|
144
142
|
`
|
|
145
143
|
}
|
|
146
144
|
|
|
145
|
+
flattenTasks(tasks: Task[], level: number = 0): Task[] {
|
|
146
|
+
return tasks.flatMap(task => [{ ...task, level }, ...this.flattenTasks(task.children || [], level + 1)])
|
|
147
|
+
}
|
|
148
|
+
|
|
147
149
|
setVerticalOffset(offset: number) {
|
|
148
150
|
this.container.scrollTop = offset
|
|
149
151
|
}
|
|
@@ -152,6 +154,14 @@ export class OxGanttChart extends LitElement {
|
|
|
152
154
|
return this.container.scrollTop
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
setHorizontalOffset(offset: number) {
|
|
158
|
+
this.container.scrollLeft = offset
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getHorizontalOffset(): number {
|
|
162
|
+
return this.container.scrollLeft
|
|
163
|
+
}
|
|
164
|
+
|
|
155
165
|
handleWheelForHeader(event: WheelEvent) {
|
|
156
166
|
event.preventDefault()
|
|
157
167
|
event.stopPropagation()
|
|
@@ -183,9 +193,25 @@ export class OxGanttChart extends LitElement {
|
|
|
183
193
|
|
|
184
194
|
this.ganttChartController.updateTimeScale()
|
|
185
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)
|
|
186
207
|
}
|
|
187
208
|
}
|
|
188
209
|
|
|
210
|
+
handleDateRangeSelected(event: CustomEvent) {
|
|
211
|
+
const detail = event.detail
|
|
212
|
+
this.dispatchEvent(new CustomEvent('date-range-selected', { detail }))
|
|
213
|
+
}
|
|
214
|
+
|
|
189
215
|
updateLocale(locale: string) {
|
|
190
216
|
const localeDefinition = (locales as any)[locale || 'en-US']
|
|
191
217
|
if (localeDefinition) {
|
|
@@ -241,6 +267,17 @@ export class OxGanttChart extends LitElement {
|
|
|
241
267
|
lastTouchDistance = currentDistance
|
|
242
268
|
this.ganttChartController.updateTimeScale()
|
|
243
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)
|
|
244
281
|
} else if (isDragging && startX !== null) {
|
|
245
282
|
const touch = event.touches[0]
|
|
246
283
|
const containerOffsetX = header.node()!.getBoundingClientRect().left
|
|
@@ -8,6 +8,8 @@ export class OxGanttResizeSplitter extends LitElement {
|
|
|
8
8
|
position: relative;
|
|
9
9
|
opacity: 0.7;
|
|
10
10
|
background-color: var(--splitter-background-color);
|
|
11
|
+
width: var(--width);
|
|
12
|
+
height: var(--height);
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
:host(:hover) {
|
|
@@ -15,9 +17,8 @@ export class OxGanttResizeSplitter extends LitElement {
|
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
div {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
height: 100%;
|
|
20
|
+
width: var(--width);
|
|
21
|
+
height: var(--height);
|
|
21
22
|
}
|
|
22
23
|
`
|
|
23
24
|
|
|
@@ -27,12 +28,12 @@ export class OxGanttResizeSplitter extends LitElement {
|
|
|
27
28
|
super.connectedCallback()
|
|
28
29
|
|
|
29
30
|
if (this.hasAttribute('vertical')) {
|
|
30
|
-
this.style.width
|
|
31
|
-
this.style.height
|
|
31
|
+
this.style.setProperty('--width', '3px')
|
|
32
|
+
this.style.setProperty('--height', '100%')
|
|
32
33
|
this.style.cursor = 'col-resize'
|
|
33
34
|
} else {
|
|
34
|
-
this.style.
|
|
35
|
-
this.style.
|
|
35
|
+
this.style.setProperty('--height', '3px')
|
|
36
|
+
this.style.setProperty('--width', '100%')
|
|
36
37
|
this.style.cursor = 'row-resize'
|
|
37
38
|
}
|
|
38
39
|
}
|