@operato/gantt 7.1.27 → 7.1.32

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,120 +0,0 @@
1
- import { LitElement, html, css } from 'lit'
2
- import { customElement, property } from 'lit/decorators.js'
3
-
4
- @customElement('ox-task-tooltip')
5
- export class OxTaskTooltip extends LitElement {
6
- static styles = css`
7
- :host {
8
- position: fixed; /* position: absolute로 변경 */
9
- background-color: var(--ox-gantt-tooltip-background-color, var(--md-sys-color-surface-variant, #f9f9f9));
10
- border: 1px solid var(--ox-gantt-tooltip-border-color, var(--md-sys-color-outline, #ccc));
11
- padding: var(--spacing-medium, 10px);
12
- border-radius: var(--spacing-medium, 8px);
13
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
14
- visibility: hidden;
15
- z-index: 10;
16
- max-width: 300px;
17
- font-family: Roboto;
18
- font-size: 14px;
19
- line-height: 1.6;
20
- color: var(--ox-gantt-tooltip-color, var(--md-sys-color-on-surface, #333));
21
- transition:
22
- visibility 0.1s,
23
- opacity 0.1s;
24
- opacity: 0; /* 초기에는 보이지 않음 */
25
- }
26
- :host([visible]) {
27
- visibility: visible;
28
- opacity: 1; /* 보일 때는 불투명 */
29
- }
30
- .title {
31
- font-weight: bold;
32
- font-size: 16px;
33
- margin-bottom: 8px;
34
- color: var(--ox-gantt-tooltip-title-color, var(--md-sys-color-primary, #0078d4)); /* 파란색 텍스트 */
35
- }
36
- .detail {
37
- display: grid;
38
- grid-template-columns: auto auto 1fr;
39
- gap: 4px;
40
- align-items: center;
41
- }
42
- .label {
43
- font-weight: bold;
44
- color: var(--ox-gantt-tooltip-color, var(--md-sys-color-secondary, #333));
45
- text-align: right; /* 라벨을 우측 정렬 */
46
- }
47
- .colon {
48
- text-align: center;
49
- color: var(--ox-gantt-tooltip-color, var(--md-sys-color-secondary, #333));
50
- }
51
- .value {
52
- text-align: left; /* 값을 좌측 정렬 */
53
- color: var(--ox-gantt-tooltip-color, var(--md-sys-color-on-surface, #333));
54
- }
55
- `
56
-
57
- @property({ type: String }) title = ''
58
- @property({ type: String }) startDate = ''
59
- @property({ type: String }) endDate = ''
60
- @property({ type: Number }) progress = 0
61
- @property({ type: String }) type = ''
62
-
63
- show(x: number, y: number) {
64
- const tooltipWidth = this.offsetWidth
65
- const tooltipHeight = this.offsetHeight
66
- const offsetX = 10
67
- const offsetY = 10
68
-
69
- // 기본 위치 계산
70
- let left = x + offsetX
71
- let top = y + offsetY
72
-
73
- // 화면 경계 확인 및 조정
74
- const screenWidth = window.innerWidth
75
- const screenHeight = window.innerHeight
76
-
77
- if (left + tooltipWidth > screenWidth) {
78
- left = x - tooltipWidth - offsetX
79
- }
80
- if (top + tooltipHeight > screenHeight) {
81
- top = y - tooltipHeight - offsetY
82
- }
83
-
84
- // offsetParent 요소를 기준으로 위치 설정
85
- if (this.offsetParent) {
86
- const parentRect = this.offsetParent!.getBoundingClientRect()
87
- this.style.left = `${left - parentRect.left}px`
88
- this.style.top = `${top - parentRect.top}px`
89
- } else {
90
- this.style.left = `${left}px`
91
- this.style.top = `${top}px`
92
-
93
- const myRect = this.getBoundingClientRect()
94
-
95
- /* 실제 바운드와 설정한 left/top 이 일치하지 않는다면, 부모 어딘가에서 transform 되었다고 판단하고, 보정해줌 */
96
- if (myRect.left !== left || myRect.top !== top) {
97
- this.style.left = `${left + (left - myRect.left)}px`
98
- this.style.top = `${top + (top - myRect.top)}px`
99
- }
100
- }
101
-
102
- this.setAttribute('visible', '')
103
- }
104
-
105
- hide() {
106
- this.removeAttribute('visible')
107
- }
108
-
109
- render() {
110
- return html`
111
- <div class="title">${this.title}</div>
112
- <div class="detail">
113
- <span class="label">Type</span><span class="colon">:</span><span class="value">${this.type}</span>
114
- <span class="label">Start Date</span><span class="colon">:</span><span class="value">${this.startDate}</span>
115
- <span class="label">End Date</span><span class="colon">:</span><span class="value">${this.endDate}</span>
116
- <span class="label">Progress</span><span class="colon">:</span><span class="value">${this.progress}%</span>
117
- </div>
118
- `
119
- }
120
- }
@@ -1,284 +0,0 @@
1
- import { LitElement, ReactiveController, ReactiveControllerHost } from 'lit'
2
- import * as d3 from 'd3'
3
- import { TimeScale, Task, Resource } from './types'
4
- import { drawTimeScaleHeader, getScale, getChartWidth } from './draw-time-scale-header'
5
-
6
- export class ResourceChartController implements ReactiveController {
7
- private host: ReactiveControllerHost & LitElement
8
- private fromTime: Date
9
- private toTime: Date
10
- private totalDays: number
11
- private _timeScale?: d3.ScaleTime<number, number, never>
12
-
13
- private touchStartY?: number
14
- private touchStartX?: number
15
- private scrollStartX?: number
16
- private scrollStartY?: number
17
-
18
- public baseWidth: number = 30
19
- public level: TimeScale
20
- public resources: Resource[] = []
21
- public tasks: Task[] = []
22
-
23
- constructor(host: ReactiveControllerHost & LitElement, fromDate: string, toDate: string, axisScale: TimeScale) {
24
- this.host = host
25
- this.fromTime = new Date(fromDate)
26
- this.toTime = new Date(toDate)
27
- this.level = axisScale
28
- this.totalDays = d3.timeDay.count(this.fromTime, d3.timeDay.offset(this.toTime, 1))
29
- this.host.addController(this)
30
- }
31
-
32
- hostConnected() {
33
- this.updateTimeScale()
34
- }
35
-
36
- get timeScale() {
37
- if (!this._timeScale) {
38
- this._timeScale = d3
39
- .scaleTime()
40
- .domain([d3.timeDay.floor(this.fromTime), d3.timeDay.offset(this.toTime, 1)])
41
- .range([0, this.getChartWidth()])
42
- }
43
- return this._timeScale
44
- }
45
-
46
- public syncTimeScale(detail: { baseWidth: number; level: TimeScale }) {
47
- this.baseWidth = detail.baseWidth
48
- this.level = detail.level
49
-
50
- this.updateTimeScale()
51
- }
52
-
53
- updateRange(fromDate: string, toDate: string) {
54
- this.fromTime = new Date(fromDate)
55
- this.toTime = new Date(toDate)
56
- this.totalDays = d3.timeDay.count(this.fromTime, d3.timeDay.offset(this.toTime, 1))
57
- this._timeScale = undefined
58
- this.host.requestUpdate()
59
- }
60
-
61
- setResources(resources: Resource[]) {
62
- this.resources = resources
63
- this.host.requestUpdate()
64
- }
65
-
66
- setTasks(tasks: Task[]) {
67
- this.tasks = tasks
68
- this.host.requestUpdate()
69
- }
70
-
71
- updateTimeScale() {
72
- this.timeScale.range([0, this.getChartWidth()])
73
- }
74
-
75
- getScale() {
76
- return getScale(this.level)
77
- }
78
-
79
- getChartWidth() {
80
- return getChartWidth(this.fromTime, this.toTime, this.baseWidth, this.level)
81
- }
82
-
83
- drawAxes(svgHeader: SVGElement) {
84
- drawTimeScaleHeader(svgHeader, this.timeScale, this.fromTime, this.toTime, this.baseWidth, this.level, false)
85
- }
86
-
87
- drawBody(svgBody: SVGElement) {
88
- const body = d3.select(svgBody)
89
-
90
- body.selectAll('*').remove()
91
- body.attr('width', this.getChartWidth())
92
-
93
- if (this.resources.length > 0) {
94
- const chartHeight = this.resources.length * 30
95
-
96
- const addGridLines = (ticks: Date[]) => {
97
- ticks.forEach(tick => {
98
- const startX = this.timeScale(tick)
99
- body
100
- .append('line')
101
- .attr('x1', startX)
102
- .attr('x2', startX)
103
- .attr('y1', 0)
104
- .attr('y2', chartHeight) // Ensure vertical lines extend to the bottom
105
- .attr('stroke', 'var(--ox-gantt-color-gridlines, var(--md-sys-color-outline))')
106
- .attr('stroke-width', 1)
107
- })
108
- }
109
-
110
- let ticks
111
- if (this.level === 'week-day') {
112
- ticks = d3.timeDay.every(1)!.range(d3.timeDay.floor(this.fromTime), d3.timeDay.offset(this.toTime, 1))
113
- } else if (this.level === 'quarter-week') {
114
- ticks = d3.timeWeek.every(1)!.range(d3.timeWeek.floor(this.fromTime), d3.timeWeek.offset(this.toTime, 1))
115
- } else if (this.level === 'year-month') {
116
- ticks = d3.timeMonth.every(1)!.range(d3.timeMonth.floor(this.fromTime), d3.timeMonth.offset(this.toTime, 1))
117
- } else if (this.level === 'year-quarter') {
118
- ticks = d3.timeMonth.every(3)!.range(d3.timeMonth.floor(this.fromTime), d3.timeMonth.offset(this.toTime, 3))
119
- }
120
-
121
- addGridLines(ticks!)
122
-
123
- this.renderResourceAllocations(body, ticks!, chartHeight)
124
- }
125
- }
126
-
127
- renderResourceAllocations(
128
- body: d3.Selection<SVGElement, unknown, null, undefined>,
129
- ticks: Date[],
130
- chartHeight: number
131
- ) {
132
- const resourceAllocations = this.calculateResourceAllocations(ticks)
133
-
134
- this.resources.forEach((resource, index) => {
135
- const y = index * 30 + 15 // Center the text vertically in the row
136
- ticks.forEach(tick => {
137
- const tickEnd = this.getTickEnd(tick)
138
- const dateKey = this.formatDateKey(tick, tickEnd)
139
- const allocation = resourceAllocations[resource.type][dateKey]
140
- if (allocation > 0) {
141
- const startX = this.timeScale(tick)
142
- const tickInterval = this.timeScale(tickEnd) - startX
143
-
144
- body
145
- .append('text')
146
- .attr('x', startX + tickInterval / 2)
147
- .attr('y', y)
148
- .attr('text-anchor', 'middle')
149
- .attr('alignment-baseline', 'middle')
150
- .attr('dominant-baseline', 'middle')
151
- .attr('stroke', 'var(--ox-gantt-color-text, var(--md-sys-color-on-primary-container))')
152
- .text(allocation)
153
- .style('font-size', '12px')
154
- }
155
- })
156
- })
157
-
158
- // Draw horizontal grid lines for each resource row
159
- for (let i = 0; i <= this.resources.length; i++) {
160
- body
161
- .append('line')
162
- .attr('x1', 0)
163
- .attr('x2', this.getChartWidth())
164
- .attr('y1', i * 30)
165
- .attr('y2', i * 30)
166
- .attr('stroke', 'var(--ox-gantt-color-gridlines, var(--md-sys-color-outline))')
167
- .attr('stroke-width', 1)
168
- }
169
- }
170
-
171
- calculateResourceAllocations(ticks: Date[]): { [key: string]: { [key: string]: number } } {
172
- const resourceAllocations: { [key: string]: { [key: string]: number } } = {}
173
-
174
- this.resources.forEach(resource => {
175
- resourceAllocations[resource.type] = {}
176
- ticks.forEach(tick => {
177
- const tickEnd = this.getTickEnd(tick)
178
- const dateKey = this.formatDateKey(tick, tickEnd)
179
- resourceAllocations[resource.type][dateKey] = 0
180
- })
181
- })
182
-
183
- this.tasks.forEach(task => {
184
- const startDate = new Date(task.startDate)
185
- const endDate = new Date(task.endDate)
186
- let currentDate = startDate
187
-
188
- while (currentDate <= endDate) {
189
- const tick = ticks.find(t => currentDate >= t && currentDate < this.getTickEnd(t))
190
- if (tick) {
191
- const tickEnd = this.getTickEnd(tick)
192
- const dateKey = this.formatDateKey(tick, tickEnd)
193
-
194
- ;(task.resources || []).forEach(resource => {
195
- if (resourceAllocations[resource.type][dateKey] !== undefined) {
196
- resourceAllocations[resource.type][dateKey] += resource.allocated
197
- }
198
- })
199
- }
200
-
201
- currentDate.setDate(currentDate.getDate() + 1)
202
- }
203
- })
204
-
205
- return resourceAllocations
206
- }
207
-
208
- getTickEnd(tick: Date): Date {
209
- if (this.level === 'week-day') {
210
- return d3.timeDay.offset(tick, 1)
211
- } else if (this.level === 'quarter-week') {
212
- return d3.timeWeek.offset(tick, 1)
213
- } else if (this.level === 'year-month') {
214
- return d3.timeMonth.offset(tick, 1)
215
- } else if (this.level === 'year-quarter') {
216
- return d3.timeMonth.offset(tick, 3)
217
- }
218
- return d3.timeDay.offset(tick, 1)
219
- }
220
-
221
- formatDateKey(start: Date, end: Date): string {
222
- return `${start.toISOString().split('T')[0]}_${end.toISOString().split('T')[0]}`
223
- }
224
-
225
- handleWheelForBody(event: WheelEvent, container: HTMLDivElement) {
226
- event.preventDefault()
227
- event.stopPropagation()
228
-
229
- const deltaModeFactor =
230
- event.deltaMode === WheelEvent.DOM_DELTA_LINE
231
- ? 16
232
- : event.deltaMode === WheelEvent.DOM_DELTA_PAGE
233
- ? container.clientHeight
234
- : 1
235
- const deltaX = event.deltaX * deltaModeFactor
236
- const deltaY = event.deltaY * deltaModeFactor
237
-
238
- container.scrollLeft += deltaX
239
- container.scrollTop += deltaY
240
-
241
- const customEvent = new CustomEvent('scroll', {
242
- detail: {
243
- scrollLeft: container.scrollLeft,
244
- scrollTop: container.scrollTop
245
- },
246
- bubbles: true,
247
- composed: true
248
- })
249
-
250
- this.host.dispatchEvent(customEvent)
251
- }
252
-
253
- handleTouchStartForBody(event: TouchEvent, container: HTMLDivElement) {
254
- event.stopPropagation()
255
-
256
- this.touchStartX = event.touches[0].clientX
257
- this.touchStartY = event.touches[0].clientY
258
-
259
- this.scrollStartX = container.scrollLeft
260
- this.scrollStartY = container.scrollTop
261
- }
262
-
263
- handleTouchMoveForBody(event: TouchEvent, container: HTMLDivElement) {
264
- const touchCurrentX = event.touches[0].clientX
265
- const touchCurrentY = event.touches[0].clientY
266
-
267
- const deltaX = this.touchStartX! - touchCurrentX
268
- const deltaY = this.touchStartY! - touchCurrentY
269
-
270
- container.scrollLeft = this.scrollStartX! + deltaX
271
- container.scrollTop = this.scrollStartY! + deltaY
272
-
273
- const customEvent = new CustomEvent('scroll', {
274
- detail: {
275
- scrollLeft: container.scrollLeft,
276
- scrollTop: container.scrollTop
277
- },
278
- bubbles: true,
279
- composed: true
280
- })
281
-
282
- this.host.dispatchEvent(customEvent)
283
- }
284
- }
package/src/types.ts DELETED
@@ -1,32 +0,0 @@
1
- export type TaskTag = 'done' | 'active' | 'critical' | 'milestone'
2
- export type TaskType = 'group' | 'task' | 'milestone' | 'phase'
3
-
4
- export interface Task {
5
- id: string
6
- title: string
7
- type: TaskType
8
- startDate: Date | string
9
- endDate: Date | string
10
- progress?: number
11
- dependsOn?: string
12
- duration: number
13
- tags?: TaskTag[]
14
- children?: Task[]
15
- resources?: Resource[]
16
- collapsed?: boolean
17
- }
18
-
19
- export interface ColumnConfig {
20
- name: string
21
- label: string
22
- visible: boolean
23
- width: number
24
- order: number
25
- }
26
-
27
- export type TimeScale = 'year-quarter' | 'year-month' | 'quarter-week' | 'week-day'
28
-
29
- export interface Resource {
30
- type: string
31
- allocated: number
32
- }
package/src/utils.ts DELETED
@@ -1,13 +0,0 @@
1
- import { TimeScale } from './types'
2
-
3
- const levels: TimeScale[] = ['week-day', 'quarter-week', 'year-month', 'year-quarter']
4
-
5
- export function levelUp(level: 'year-quarter' | 'year-month' | 'quarter-week' | 'week-day'): TimeScale {
6
- const current = Math.min(levels.length, levels.indexOf(level) + 1)
7
- return levels[current]
8
- }
9
-
10
- export function levelDown(level: 'year-quarter' | 'year-month' | 'quarter-week' | 'week-day'): TimeScale {
11
- const current = Math.max(0, levels.indexOf(level) - 1)
12
- return levels[current]
13
- }
@@ -1,125 +0,0 @@
1
- import { html, TemplateResult } from 'lit'
2
- import { CommonHeaderStyles } from '@operato/styles'
3
- import '../src/ox-gantt-chart.js'
4
-
5
- import { styles as MDTypeScaleStyles } from '@material/web/typography/md-typescale-styles'
6
-
7
- export default {
8
- title: 'ox-gantt-chart',
9
- component: 'ox-gantt-chart',
10
- argTypes: {
11
- axisScale: { control: 'select', options: ['year-quarter', 'year-month', 'quarter-week', 'week-day'] },
12
- fromDate: { control: 'string' },
13
- toDate: { control: 'string' },
14
- extendGridLines: { control: 'boolean' }
15
- }
16
- }
17
-
18
- interface Story<T> {
19
- (args: T): TemplateResult
20
- args?: Partial<T>
21
- argTypes?: Record<string, unknown>
22
- }
23
-
24
- interface ArgTypes {
25
- axisScale: 'year-quarter' | 'year-month' | 'quarter-week' | 'week-day'
26
- fromDate: string
27
- toDate: string
28
- extendGridLines: boolean
29
- }
30
-
31
- const Template: Story<ArgTypes> = ({
32
- axisScale = 'week-day',
33
- fromDate = '',
34
- toDate = '',
35
- extendGridLines
36
- }: ArgTypes) => html`
37
- <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet" />
38
-
39
- <link href="/themes/light.css" rel="stylesheet" />
40
- <link href="/themes/dark.css" rel="stylesheet" />
41
- <link href="/themes/spacing.css" rel="stylesheet" />
42
- <link href="/themes/md-typescale-styles.css" rel="stylesheet" />
43
-
44
- <link
45
- href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL@20..48,100..700,0..1"
46
- rel="stylesheet"
47
- />
48
- <link
49
- href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL@20..48,100..700,0..1"
50
- rel="stylesheet"
51
- />
52
- <link
53
- href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp:opsz,wght,FILL@20..48,100..700,0..1"
54
- rel="stylesheet"
55
- />
56
-
57
- <style>
58
- ${MDTypeScaleStyles.cssText}
59
- </style>
60
-
61
- <style>
62
- ${CommonHeaderStyles.cssText}
63
- </style>
64
-
65
- <style>
66
- body {
67
- width: 100%;
68
- height: 800px;
69
- text-align: center;
70
-
71
- background-color: var(--md-sys-color-background);
72
- color: var(--md-sys-color-on-background);
73
- }
74
-
75
- ox-gantt {
76
- width: 90%;
77
- height: 500px;
78
- box-sizing: border-box;
79
-
80
- background-color: var(--md-sys-color-primary-container);
81
- color: var(--md-sys-color-on-primary-container);
82
- }
83
- </style>
84
-
85
- <style>
86
- ox-gantt-chart {
87
- height: 500px;
88
- border: 1px solid black;
89
- }
90
- </style>
91
-
92
- <ox-gantt-chart
93
- .axisScale=${axisScale}
94
- .fromDate=${fromDate}
95
- .toDate=${toDate}
96
- ?extend-grid-lines=${extendGridLines}
97
- ></ox-gantt-chart>
98
- `
99
-
100
- export const YearMonth = Template.bind({})
101
- YearMonth.args = {
102
- axisScale: 'year-quarter',
103
- fromDate: '2020-01-01',
104
- toDate: '2025-12-31'
105
- }
106
-
107
- YearMonth.args = {
108
- axisScale: 'year-month',
109
- fromDate: '2022-01-01',
110
- toDate: '2024-12-31'
111
- }
112
-
113
- export const MonthWeek = Template.bind({})
114
- MonthWeek.args = {
115
- axisScale: 'quarter-week',
116
- fromDate: '2022-01-01',
117
- toDate: '2023-03-31'
118
- }
119
-
120
- export const WeekDay = Template.bind({})
121
- WeekDay.args = {
122
- axisScale: 'week-day',
123
- fromDate: '2022-01-01',
124
- toDate: '2022-11-15'
125
- }