@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.
Files changed (37) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/src/draw-time-scale-header.d.ts +5 -0
  3. package/dist/src/draw-time-scale-header.js +154 -0
  4. package/dist/src/draw-time-scale-header.js.map +1 -0
  5. package/dist/src/gantt-chart-controller.d.ts +1 -1
  6. package/dist/src/gantt-chart-controller.js +6 -161
  7. package/dist/src/gantt-chart-controller.js.map +1 -1
  8. package/dist/src/ox-gantt-chart.d.ts +6 -1
  9. package/dist/src/ox-gantt-chart.js +39 -8
  10. package/dist/src/ox-gantt-chart.js.map +1 -1
  11. package/dist/src/ox-gantt-resize-splitter.js +8 -7
  12. package/dist/src/ox-gantt-resize-splitter.js.map +1 -1
  13. package/dist/src/ox-gantt.d.ts +24 -15
  14. package/dist/src/ox-gantt.js +224 -82
  15. package/dist/src/ox-gantt.js.map +1 -1
  16. package/dist/src/ox-resource-chart.d.ts +42 -0
  17. package/dist/src/ox-resource-chart.js +317 -0
  18. package/dist/src/ox-resource-chart.js.map +1 -0
  19. package/dist/src/types.d.ts +5 -0
  20. package/dist/src/types.js.map +1 -1
  21. package/dist/stories/ox-gantt.stories.js +75 -4
  22. package/dist/stories/ox-gantt.stories.js.map +1 -1
  23. package/dist/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +2 -2
  25. package/src/draw-time-scale-header.ts +192 -0
  26. package/src/gantt-chart-controller.ts +6 -189
  27. package/src/ox-gantt-chart.ts +46 -9
  28. package/src/ox-gantt-resize-splitter.ts +8 -7
  29. package/src/ox-gantt.ts +231 -86
  30. package/src/ox-resource-chart.ts +328 -0
  31. package/src/types.ts +6 -0
  32. package/stories/ox-gantt.stories.ts +75 -4
  33. package/translations/en.json +4 -1
  34. package/translations/ja.json +3 -1
  35. package/translations/ko.json +4 -1
  36. package/translations/ms.json +3 -1
  37. 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
- getChartWidth() {
64
- return (this.totalDays * this.baseWidth) / this.getScale()
64
+ getScale() {
65
+ return getScale(this.level)
65
66
  }
66
67
 
67
- getScale() {
68
- if (this.level == 'quarter-week') {
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
- const header = d3.select(svgHeader)
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) {
@@ -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) => console.log(e.detail)}
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
- position: absolute;
19
- width: 100%;
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 = '3px'
31
- this.style.height = '100%'
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.width = '100%'
35
- this.style.height = '3px'
35
+ this.style.setProperty('--height', '3px')
36
+ this.style.setProperty('--width', '100%')
36
37
  this.style.cursor = 'row-resize'
37
38
  }
38
39
  }