@operato/gantt 2.0.0-beta.0 → 2.0.0-beta.1
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/locale.d.ts +1 -0
- package/dist/src/locale.js +49 -0
- package/dist/src/locale.js.map +1 -0
- package/dist/src/ox-gantt-chart.d.ts +39 -0
- package/dist/src/ox-gantt-chart.js +707 -0
- package/dist/src/ox-gantt-chart.js.map +1 -0
- package/dist/src/ox-gantt-resize-splitter.d.ts +13 -0
- package/dist/src/ox-gantt-resize-splitter.js +117 -0
- package/dist/src/ox-gantt-resize-splitter.js.map +1 -0
- package/dist/src/ox-gantt.d.ts +24 -0
- package/dist/src/ox-gantt.js +186 -0
- package/dist/src/ox-gantt.js.map +1 -0
- package/dist/src/types.d.ts +10 -5
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils.d.ts +3 -0
- package/dist/src/utils.js +10 -0
- package/dist/src/utils.js.map +1 -0
- package/dist/stories/ox-gantt-chart.stories.d.ts +41 -0
- package/dist/stories/{ox-lite-gantt.stories.js → ox-gantt-chart.stories.js} +53 -21
- package/dist/stories/ox-gantt-chart.stories.js.map +1 -0
- package/dist/stories/ox-gantt.stories.d.ts +6 -1
- package/dist/stories/ox-gantt.stories.js +286 -15
- package/dist/stories/ox-gantt.stories.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -4
- package/src/locale.ts +51 -0
- package/src/ox-gantt-chart.ts +773 -0
- package/src/ox-gantt-resize-splitter.ts +137 -0
- package/src/ox-gantt.ts +177 -0
- package/src/types.ts +12 -6
- package/src/utils.ts +13 -0
- package/stories/{ox-lite-gantt.stories.ts → ox-gantt-chart.stories.ts} +65 -22
- package/stories/ox-gantt.stories.ts +289 -16
- package/themes/dark-hc.css +151 -0
- package/themes/dark-mc.css +151 -0
- package/themes/dark.css +151 -0
- package/themes/grist-theme.css +169 -0
- package/themes/light-hc.css +151 -0
- package/themes/light-mc.css +151 -0
- package/themes/light.css +151 -0
- package/themes/md-typescale-styles.css +100 -0
- package/themes/spacing.css +43 -0
- package/themes/state-color.css +6 -0
- package/dist/src/gantt.d.ts +0 -28
- package/dist/src/gantt.js +0 -178
- package/dist/src/gantt.js.map +0 -1
- package/dist/src/lite-gantt.d.ts +0 -27
- package/dist/src/lite-gantt.js +0 -146
- package/dist/src/lite-gantt.js.map +0 -1
- package/dist/stories/ox-lite-gantt.stories.d.ts +0 -22
- package/dist/stories/ox-lite-gantt.stories.js.map +0 -1
- package/src/gantt.ts +0 -191
- package/src/lite-gantt.ts +0 -152
- package/src/renderer.txt +0 -775
package/src/renderer.txt
DELETED
|
@@ -1,775 +0,0 @@
|
|
|
1
|
-
import dayjs from 'dayjs'
|
|
2
|
-
import {
|
|
3
|
-
select,
|
|
4
|
-
scaleTime,
|
|
5
|
-
min,
|
|
6
|
-
max,
|
|
7
|
-
scaleLinear,
|
|
8
|
-
interpolateHcl,
|
|
9
|
-
axisBottom,
|
|
10
|
-
axisTop,
|
|
11
|
-
timeFormat,
|
|
12
|
-
timeMillisecond,
|
|
13
|
-
timeSecond,
|
|
14
|
-
timeMinute,
|
|
15
|
-
timeHour,
|
|
16
|
-
timeDay,
|
|
17
|
-
timeMonday,
|
|
18
|
-
timeTuesday,
|
|
19
|
-
timeWednesday,
|
|
20
|
-
timeThursday,
|
|
21
|
-
timeFriday,
|
|
22
|
-
timeSaturday,
|
|
23
|
-
timeSunday,
|
|
24
|
-
timeMonth
|
|
25
|
-
} from 'd3'
|
|
26
|
-
import common from '../common/common.js'
|
|
27
|
-
import { getConfig } from '../../diagram-api/diagramAPI.js'
|
|
28
|
-
import { configureSvgSize } from '../../setupGraphViewbox.js'
|
|
29
|
-
|
|
30
|
-
export const setConf = function () {
|
|
31
|
-
console.log('Something is calling, setConf, remove the call')
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* This will map any day of the week that can be set in the `weekday` option to
|
|
36
|
-
* the corresponding d3-time function that is used to calculate the ticks.
|
|
37
|
-
*/
|
|
38
|
-
const mapWeekdayToTimeFunction = {
|
|
39
|
-
monday: timeMonday,
|
|
40
|
-
tuesday: timeTuesday,
|
|
41
|
-
wednesday: timeWednesday,
|
|
42
|
-
thursday: timeThursday,
|
|
43
|
-
friday: timeFriday,
|
|
44
|
-
saturday: timeSaturday,
|
|
45
|
-
sunday: timeSunday
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* For this issue:
|
|
50
|
-
* https://github.com/mermaid-js/mermaid/issues/1618
|
|
51
|
-
*
|
|
52
|
-
* Finds the number of intersections between tasks that happen at any point in time.
|
|
53
|
-
* Used to figure out how many rows are needed to display the tasks when the display
|
|
54
|
-
* mode is set to 'compact'.
|
|
55
|
-
*
|
|
56
|
-
* @param tasks
|
|
57
|
-
* @param orderOffset
|
|
58
|
-
*/
|
|
59
|
-
const getMaxIntersections = (tasks, orderOffset) => {
|
|
60
|
-
let timeline = [...tasks].map(() => -Infinity)
|
|
61
|
-
let sorted = [...tasks].sort((a, b) => a.startTime - b.startTime || a.order - b.order)
|
|
62
|
-
let maxIntersections = 0
|
|
63
|
-
for (const element of sorted) {
|
|
64
|
-
for (let j = 0; j < timeline.length; j++) {
|
|
65
|
-
if (element.startTime >= timeline[j]) {
|
|
66
|
-
timeline[j] = element.endTime
|
|
67
|
-
element.order = j + orderOffset
|
|
68
|
-
if (j > maxIntersections) {
|
|
69
|
-
maxIntersections = j
|
|
70
|
-
}
|
|
71
|
-
break
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return maxIntersections
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let w
|
|
80
|
-
export const draw = function (text, id, version, diagObj) {
|
|
81
|
-
const conf = getConfig().gantt
|
|
82
|
-
|
|
83
|
-
const securityLevel = getConfig().securityLevel
|
|
84
|
-
// Handle root and Document for when rendering in sandbox mode
|
|
85
|
-
let sandboxElement
|
|
86
|
-
if (securityLevel === 'sandbox') {
|
|
87
|
-
sandboxElement = select('#i' + id)
|
|
88
|
-
}
|
|
89
|
-
const root = securityLevel === 'sandbox' ? select(sandboxElement.nodes()[0].contentDocument.body) : select('body')
|
|
90
|
-
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document
|
|
91
|
-
|
|
92
|
-
const elem = doc.getElementById(id)
|
|
93
|
-
w = elem.parentElement.offsetWidth
|
|
94
|
-
|
|
95
|
-
if (w === undefined) {
|
|
96
|
-
w = 1200
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (conf.useWidth !== undefined) {
|
|
100
|
-
w = conf.useWidth
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const taskArray = diagObj.db.getTasks()
|
|
104
|
-
|
|
105
|
-
// Set height based on number of tasks
|
|
106
|
-
|
|
107
|
-
let categories = []
|
|
108
|
-
|
|
109
|
-
for (const element of taskArray) {
|
|
110
|
-
categories.push(element.type)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
categories = checkUnique(categories)
|
|
114
|
-
const categoryHeights = {}
|
|
115
|
-
|
|
116
|
-
let h = 2 * conf.topPadding
|
|
117
|
-
if (diagObj.db.getDisplayMode() === 'compact' || conf.displayMode === 'compact') {
|
|
118
|
-
const categoryElements = {}
|
|
119
|
-
for (const element of taskArray) {
|
|
120
|
-
if (categoryElements[element.section] === undefined) {
|
|
121
|
-
categoryElements[element.section] = [element]
|
|
122
|
-
} else {
|
|
123
|
-
categoryElements[element.section].push(element)
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
let intersections = 0
|
|
128
|
-
for (const category of Object.keys(categoryElements)) {
|
|
129
|
-
const categoryHeight = getMaxIntersections(categoryElements[category], intersections) + 1
|
|
130
|
-
intersections += categoryHeight
|
|
131
|
-
h += categoryHeight * (conf.barHeight + conf.barGap)
|
|
132
|
-
categoryHeights[category] = categoryHeight
|
|
133
|
-
}
|
|
134
|
-
} else {
|
|
135
|
-
h += taskArray.length * (conf.barHeight + conf.barGap)
|
|
136
|
-
for (const category of categories) {
|
|
137
|
-
categoryHeights[category] = taskArray.filter(task => task.type === category).length
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Set viewBox
|
|
142
|
-
elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h)
|
|
143
|
-
const svg = root.select(`[id="${id}"]`)
|
|
144
|
-
|
|
145
|
-
// Set timescale
|
|
146
|
-
const timeScale = scaleTime()
|
|
147
|
-
.domain([
|
|
148
|
-
min(taskArray, function (d) {
|
|
149
|
-
return d.startTime
|
|
150
|
-
}),
|
|
151
|
-
max(taskArray, function (d) {
|
|
152
|
-
return d.endTime
|
|
153
|
-
})
|
|
154
|
-
])
|
|
155
|
-
.rangeRound([0, w - conf.leftPadding - conf.rightPadding])
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* @param a
|
|
159
|
-
* @param b
|
|
160
|
-
*/
|
|
161
|
-
function taskCompare(a, b) {
|
|
162
|
-
const taskA = a.startTime
|
|
163
|
-
const taskB = b.startTime
|
|
164
|
-
let result = 0
|
|
165
|
-
if (taskA > taskB) {
|
|
166
|
-
result = 1
|
|
167
|
-
} else if (taskA < taskB) {
|
|
168
|
-
result = -1
|
|
169
|
-
}
|
|
170
|
-
return result
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Sort the task array using the above taskCompare() so that
|
|
174
|
-
// tasks are created based on their order of startTime
|
|
175
|
-
taskArray.sort(taskCompare)
|
|
176
|
-
|
|
177
|
-
makeGantt(taskArray, w, h)
|
|
178
|
-
|
|
179
|
-
configureSvgSize(svg, h, w, conf.useMaxWidth)
|
|
180
|
-
|
|
181
|
-
svg
|
|
182
|
-
.append('text')
|
|
183
|
-
.text(diagObj.db.getDiagramTitle())
|
|
184
|
-
.attr('x', w / 2)
|
|
185
|
-
.attr('y', conf.titleTopMargin)
|
|
186
|
-
.attr('class', 'titleText')
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* @param tasks
|
|
190
|
-
* @param pageWidth
|
|
191
|
-
* @param pageHeight
|
|
192
|
-
*/
|
|
193
|
-
function makeGantt(tasks, pageWidth, pageHeight) {
|
|
194
|
-
const barHeight = conf.barHeight
|
|
195
|
-
const gap = barHeight + conf.barGap
|
|
196
|
-
const topPadding = conf.topPadding
|
|
197
|
-
const leftPadding = conf.leftPadding
|
|
198
|
-
|
|
199
|
-
const colorScale = scaleLinear()
|
|
200
|
-
.domain([0, categories.length])
|
|
201
|
-
.range(['#00B9FA', '#F95002'])
|
|
202
|
-
.interpolate(interpolateHcl)
|
|
203
|
-
|
|
204
|
-
drawExcludeDays(
|
|
205
|
-
gap,
|
|
206
|
-
topPadding,
|
|
207
|
-
leftPadding,
|
|
208
|
-
pageWidth,
|
|
209
|
-
pageHeight,
|
|
210
|
-
tasks,
|
|
211
|
-
diagObj.db.getExcludes(),
|
|
212
|
-
diagObj.db.getIncludes()
|
|
213
|
-
)
|
|
214
|
-
makeGrid(leftPadding, topPadding, pageWidth, pageHeight)
|
|
215
|
-
drawRects(tasks, gap, topPadding, leftPadding, barHeight, colorScale, pageWidth, pageHeight)
|
|
216
|
-
vertLabels(gap, topPadding, leftPadding, barHeight, colorScale)
|
|
217
|
-
drawToday(leftPadding, topPadding, pageWidth, pageHeight)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* @param theArray
|
|
222
|
-
* @param theGap
|
|
223
|
-
* @param theTopPad
|
|
224
|
-
* @param theSidePad
|
|
225
|
-
* @param theBarHeight
|
|
226
|
-
* @param theColorScale
|
|
227
|
-
* @param w
|
|
228
|
-
*/
|
|
229
|
-
function drawRects(theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w) {
|
|
230
|
-
// Get unique task orders. Required to draw the background rects when display mode is compact.
|
|
231
|
-
const uniqueTaskOrderIds = [...new Set(theArray.map(item => item.order))]
|
|
232
|
-
const uniqueTasks = uniqueTaskOrderIds.map(id => theArray.find(item => item.order === id))
|
|
233
|
-
|
|
234
|
-
// Draw background rects covering the entire width of the graph, these form the section rows.
|
|
235
|
-
svg
|
|
236
|
-
.append('g')
|
|
237
|
-
.selectAll('rect')
|
|
238
|
-
.data(uniqueTasks)
|
|
239
|
-
.enter()
|
|
240
|
-
.append('rect')
|
|
241
|
-
.attr('x', 0)
|
|
242
|
-
.attr('y', function (d, i) {
|
|
243
|
-
// Ignore the incoming i value and use our order instead
|
|
244
|
-
i = d.order
|
|
245
|
-
return i * theGap + theTopPad - 2
|
|
246
|
-
})
|
|
247
|
-
.attr('width', function () {
|
|
248
|
-
return w - conf.rightPadding / 2
|
|
249
|
-
})
|
|
250
|
-
.attr('height', theGap)
|
|
251
|
-
.attr('class', function (d) {
|
|
252
|
-
for (const [i, category] of categories.entries()) {
|
|
253
|
-
if (d.type === category) {
|
|
254
|
-
return 'section section' + (i % conf.numberSectionStyles)
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return 'section section0'
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
// Draw the rects representing the tasks
|
|
261
|
-
const rectangles = svg.append('g').selectAll('rect').data(theArray).enter()
|
|
262
|
-
|
|
263
|
-
const links = diagObj.db.getLinks()
|
|
264
|
-
|
|
265
|
-
// Render the tasks with links
|
|
266
|
-
// Render the other tasks
|
|
267
|
-
rectangles
|
|
268
|
-
.append('rect')
|
|
269
|
-
.attr('id', function (d) {
|
|
270
|
-
return d.id
|
|
271
|
-
})
|
|
272
|
-
.attr('rx', 3)
|
|
273
|
-
.attr('ry', 3)
|
|
274
|
-
.attr('x', function (d) {
|
|
275
|
-
if (d.milestone) {
|
|
276
|
-
return (
|
|
277
|
-
timeScale(d.startTime) +
|
|
278
|
-
theSidePad +
|
|
279
|
-
0.5 * (timeScale(d.endTime) - timeScale(d.startTime)) -
|
|
280
|
-
0.5 * theBarHeight
|
|
281
|
-
)
|
|
282
|
-
}
|
|
283
|
-
return timeScale(d.startTime) + theSidePad
|
|
284
|
-
})
|
|
285
|
-
.attr('y', function (d, i) {
|
|
286
|
-
// Ignore the incoming i value and use our order instead
|
|
287
|
-
i = d.order
|
|
288
|
-
return i * theGap + theTopPad
|
|
289
|
-
})
|
|
290
|
-
.attr('width', function (d) {
|
|
291
|
-
if (d.milestone) {
|
|
292
|
-
return theBarHeight
|
|
293
|
-
}
|
|
294
|
-
return timeScale(d.renderEndTime || d.endTime) - timeScale(d.startTime)
|
|
295
|
-
})
|
|
296
|
-
.attr('height', theBarHeight)
|
|
297
|
-
.attr('transform-origin', function (d, i) {
|
|
298
|
-
// Ignore the incoming i value and use our order instead
|
|
299
|
-
i = d.order
|
|
300
|
-
|
|
301
|
-
return (
|
|
302
|
-
(timeScale(d.startTime) + theSidePad + 0.5 * (timeScale(d.endTime) - timeScale(d.startTime))).toString() +
|
|
303
|
-
'px ' +
|
|
304
|
-
(i * theGap + theTopPad + 0.5 * theBarHeight).toString() +
|
|
305
|
-
'px'
|
|
306
|
-
)
|
|
307
|
-
})
|
|
308
|
-
.attr('class', function (d) {
|
|
309
|
-
const res = 'task'
|
|
310
|
-
|
|
311
|
-
let classStr = ''
|
|
312
|
-
if (d.classes.length > 0) {
|
|
313
|
-
classStr = d.classes.join(' ')
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
let secNum = 0
|
|
317
|
-
for (const [i, category] of categories.entries()) {
|
|
318
|
-
if (d.type === category) {
|
|
319
|
-
secNum = i % conf.numberSectionStyles
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
let taskClass = ''
|
|
324
|
-
if (d.active) {
|
|
325
|
-
if (d.crit) {
|
|
326
|
-
taskClass += ' activeCrit'
|
|
327
|
-
} else {
|
|
328
|
-
taskClass = ' active'
|
|
329
|
-
}
|
|
330
|
-
} else if (d.done) {
|
|
331
|
-
if (d.crit) {
|
|
332
|
-
taskClass = ' doneCrit'
|
|
333
|
-
} else {
|
|
334
|
-
taskClass = ' done'
|
|
335
|
-
}
|
|
336
|
-
} else {
|
|
337
|
-
if (d.crit) {
|
|
338
|
-
taskClass += ' crit'
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (taskClass.length === 0) {
|
|
343
|
-
taskClass = ' task'
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (d.milestone) {
|
|
347
|
-
taskClass = ' milestone ' + taskClass
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
taskClass += secNum
|
|
351
|
-
|
|
352
|
-
taskClass += ' ' + classStr
|
|
353
|
-
|
|
354
|
-
return res + taskClass
|
|
355
|
-
})
|
|
356
|
-
|
|
357
|
-
// Append task labels
|
|
358
|
-
rectangles
|
|
359
|
-
.append('text')
|
|
360
|
-
.attr('id', function (d) {
|
|
361
|
-
return d.id + '-text'
|
|
362
|
-
})
|
|
363
|
-
.text(function (d) {
|
|
364
|
-
return d.task
|
|
365
|
-
})
|
|
366
|
-
.attr('font-size', conf.fontSize)
|
|
367
|
-
.attr('x', function (d) {
|
|
368
|
-
let startX = timeScale(d.startTime)
|
|
369
|
-
let endX = timeScale(d.renderEndTime || d.endTime)
|
|
370
|
-
if (d.milestone) {
|
|
371
|
-
startX += 0.5 * (timeScale(d.endTime) - timeScale(d.startTime)) - 0.5 * theBarHeight
|
|
372
|
-
}
|
|
373
|
-
if (d.milestone) {
|
|
374
|
-
endX = startX + theBarHeight
|
|
375
|
-
}
|
|
376
|
-
const textWidth = this.getBBox().width
|
|
377
|
-
|
|
378
|
-
// Check id text width > width of rectangle
|
|
379
|
-
if (textWidth > endX - startX) {
|
|
380
|
-
if (endX + textWidth + 1.5 * conf.leftPadding > w) {
|
|
381
|
-
return startX + theSidePad - 5
|
|
382
|
-
} else {
|
|
383
|
-
return endX + theSidePad + 5
|
|
384
|
-
}
|
|
385
|
-
} else {
|
|
386
|
-
return (endX - startX) / 2 + startX + theSidePad
|
|
387
|
-
}
|
|
388
|
-
})
|
|
389
|
-
.attr('y', function (d, i) {
|
|
390
|
-
// Ignore the incoming i value and use our order instead
|
|
391
|
-
i = d.order
|
|
392
|
-
return i * theGap + conf.barHeight / 2 + (conf.fontSize / 2 - 2) + theTopPad
|
|
393
|
-
})
|
|
394
|
-
.attr('text-height', theBarHeight)
|
|
395
|
-
.attr('class', function (d) {
|
|
396
|
-
const startX = timeScale(d.startTime)
|
|
397
|
-
let endX = timeScale(d.endTime)
|
|
398
|
-
if (d.milestone) {
|
|
399
|
-
endX = startX + theBarHeight
|
|
400
|
-
}
|
|
401
|
-
const textWidth = this.getBBox().width
|
|
402
|
-
|
|
403
|
-
let classStr = ''
|
|
404
|
-
if (d.classes.length > 0) {
|
|
405
|
-
classStr = d.classes.join(' ')
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
let secNum = 0
|
|
409
|
-
for (const [i, category] of categories.entries()) {
|
|
410
|
-
if (d.type === category) {
|
|
411
|
-
secNum = i % conf.numberSectionStyles
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
let taskType = ''
|
|
416
|
-
if (d.active) {
|
|
417
|
-
if (d.crit) {
|
|
418
|
-
taskType = 'activeCritText' + secNum
|
|
419
|
-
} else {
|
|
420
|
-
taskType = 'activeText' + secNum
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (d.done) {
|
|
425
|
-
if (d.crit) {
|
|
426
|
-
taskType = taskType + ' doneCritText' + secNum
|
|
427
|
-
} else {
|
|
428
|
-
taskType = taskType + ' doneText' + secNum
|
|
429
|
-
}
|
|
430
|
-
} else {
|
|
431
|
-
if (d.crit) {
|
|
432
|
-
taskType = taskType + ' critText' + secNum
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (d.milestone) {
|
|
437
|
-
taskType += ' milestoneText'
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Check id text width > width of rectangle
|
|
441
|
-
if (textWidth > endX - startX) {
|
|
442
|
-
if (endX + textWidth + 1.5 * conf.leftPadding > w) {
|
|
443
|
-
return classStr + ' taskTextOutsideLeft taskTextOutside' + secNum + ' ' + taskType
|
|
444
|
-
} else {
|
|
445
|
-
return classStr + ' taskTextOutsideRight taskTextOutside' + secNum + ' ' + taskType + ' width-' + textWidth
|
|
446
|
-
}
|
|
447
|
-
} else {
|
|
448
|
-
return classStr + ' taskText taskText' + secNum + ' ' + taskType + ' width-' + textWidth
|
|
449
|
-
}
|
|
450
|
-
})
|
|
451
|
-
|
|
452
|
-
const securityLevel = getConfig().securityLevel
|
|
453
|
-
|
|
454
|
-
// Wrap the tasks in an a tag for working links without javascript
|
|
455
|
-
if (securityLevel === 'sandbox') {
|
|
456
|
-
let sandboxElement
|
|
457
|
-
sandboxElement = select('#i' + id)
|
|
458
|
-
const doc = sandboxElement.nodes()[0].contentDocument
|
|
459
|
-
|
|
460
|
-
rectangles
|
|
461
|
-
.filter(function (d) {
|
|
462
|
-
return links[d.id] !== undefined
|
|
463
|
-
})
|
|
464
|
-
.each(function (o) {
|
|
465
|
-
var taskRect = doc.querySelector('#' + o.id)
|
|
466
|
-
var taskText = doc.querySelector('#' + o.id + '-text')
|
|
467
|
-
const oldParent = taskRect.parentNode
|
|
468
|
-
var Link = doc.createElement('a')
|
|
469
|
-
Link.setAttribute('xlink:href', links[o.id])
|
|
470
|
-
Link.setAttribute('target', '_top')
|
|
471
|
-
oldParent.appendChild(Link)
|
|
472
|
-
Link.appendChild(taskRect)
|
|
473
|
-
Link.appendChild(taskText)
|
|
474
|
-
})
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
/**
|
|
478
|
-
* @param theGap
|
|
479
|
-
* @param theTopPad
|
|
480
|
-
* @param theSidePad
|
|
481
|
-
* @param w
|
|
482
|
-
* @param h
|
|
483
|
-
* @param tasks
|
|
484
|
-
* @param {unknown[]} excludes
|
|
485
|
-
* @param {unknown[]} includes
|
|
486
|
-
*/
|
|
487
|
-
function drawExcludeDays(theGap, theTopPad, theSidePad, w, h, tasks, excludes, includes) {
|
|
488
|
-
if (excludes.length === 0 && includes.length === 0) {
|
|
489
|
-
return
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
let minTime
|
|
493
|
-
let maxTime
|
|
494
|
-
for (const { startTime, endTime } of tasks) {
|
|
495
|
-
if (minTime === undefined || startTime < minTime) {
|
|
496
|
-
minTime = startTime
|
|
497
|
-
}
|
|
498
|
-
if (maxTime === undefined || endTime > maxTime) {
|
|
499
|
-
maxTime = endTime
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
if (!minTime || !maxTime) {
|
|
504
|
-
return
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
if (dayjs(maxTime).diff(dayjs(minTime), 'year') > 5) {
|
|
508
|
-
console.warn(
|
|
509
|
-
'The difference between the min and max time is more than 5 years. This will cause performance issues. Skipping drawing exclude days.'
|
|
510
|
-
)
|
|
511
|
-
return
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const dateFormat = diagObj.db.getDateFormat()
|
|
515
|
-
const excludeRanges = []
|
|
516
|
-
let range = null
|
|
517
|
-
let d = dayjs(minTime)
|
|
518
|
-
while (d.valueOf() <= maxTime) {
|
|
519
|
-
if (diagObj.db.isInvalidDate(d, dateFormat, excludes, includes)) {
|
|
520
|
-
if (!range) {
|
|
521
|
-
range = {
|
|
522
|
-
start: d,
|
|
523
|
-
end: d
|
|
524
|
-
}
|
|
525
|
-
} else {
|
|
526
|
-
range.end = d
|
|
527
|
-
}
|
|
528
|
-
} else {
|
|
529
|
-
if (range) {
|
|
530
|
-
excludeRanges.push(range)
|
|
531
|
-
range = null
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
d = d.add(1, 'd')
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
const rectangles = svg.append('g').selectAll('rect').data(excludeRanges).enter()
|
|
538
|
-
|
|
539
|
-
rectangles
|
|
540
|
-
.append('rect')
|
|
541
|
-
.attr('id', function (d) {
|
|
542
|
-
return 'exclude-' + d.start.format('YYYY-MM-DD')
|
|
543
|
-
})
|
|
544
|
-
.attr('x', function (d) {
|
|
545
|
-
return timeScale(d.start) + theSidePad
|
|
546
|
-
})
|
|
547
|
-
.attr('y', conf.gridLineStartPadding)
|
|
548
|
-
.attr('width', function (d) {
|
|
549
|
-
const renderEnd = d.end.add(1, 'day')
|
|
550
|
-
return timeScale(renderEnd) - timeScale(d.start)
|
|
551
|
-
})
|
|
552
|
-
.attr('height', h - theTopPad - conf.gridLineStartPadding)
|
|
553
|
-
.attr('transform-origin', function (d, i) {
|
|
554
|
-
return (
|
|
555
|
-
(timeScale(d.start) + theSidePad + 0.5 * (timeScale(d.end) - timeScale(d.start))).toString() +
|
|
556
|
-
'px ' +
|
|
557
|
-
(i * theGap + 0.5 * h).toString() +
|
|
558
|
-
'px'
|
|
559
|
-
)
|
|
560
|
-
})
|
|
561
|
-
.attr('class', 'exclude-range')
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* @param theSidePad
|
|
566
|
-
* @param theTopPad
|
|
567
|
-
* @param w
|
|
568
|
-
* @param h
|
|
569
|
-
*/
|
|
570
|
-
function makeGrid(theSidePad, theTopPad, w, h) {
|
|
571
|
-
let bottomXAxis = axisBottom(timeScale)
|
|
572
|
-
.tickSize(-h + theTopPad + conf.gridLineStartPadding)
|
|
573
|
-
.tickFormat(timeFormat(diagObj.db.getAxisFormat() || conf.axisFormat || '%Y-%m-%d'))
|
|
574
|
-
|
|
575
|
-
const reTickInterval = /^([1-9]\d*)(millisecond|second|minute|hour|day|week|month)$/
|
|
576
|
-
const resultTickInterval = reTickInterval.exec(diagObj.db.getTickInterval() || conf.tickInterval)
|
|
577
|
-
|
|
578
|
-
if (resultTickInterval !== null) {
|
|
579
|
-
const every = resultTickInterval[1]
|
|
580
|
-
const interval = resultTickInterval[2]
|
|
581
|
-
const weekday = diagObj.db.getWeekday() || conf.weekday
|
|
582
|
-
|
|
583
|
-
switch (interval) {
|
|
584
|
-
case 'millisecond':
|
|
585
|
-
bottomXAxis.ticks(timeMillisecond.every(every))
|
|
586
|
-
break
|
|
587
|
-
case 'second':
|
|
588
|
-
bottomXAxis.ticks(timeSecond.every(every))
|
|
589
|
-
break
|
|
590
|
-
case 'minute':
|
|
591
|
-
bottomXAxis.ticks(timeMinute.every(every))
|
|
592
|
-
break
|
|
593
|
-
case 'hour':
|
|
594
|
-
bottomXAxis.ticks(timeHour.every(every))
|
|
595
|
-
break
|
|
596
|
-
case 'day':
|
|
597
|
-
bottomXAxis.ticks(timeDay.every(every))
|
|
598
|
-
break
|
|
599
|
-
case 'week':
|
|
600
|
-
bottomXAxis.ticks(mapWeekdayToTimeFunction[weekday].every(every))
|
|
601
|
-
break
|
|
602
|
-
case 'month':
|
|
603
|
-
bottomXAxis.ticks(timeMonth.every(every))
|
|
604
|
-
break
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
svg
|
|
609
|
-
.append('g')
|
|
610
|
-
.attr('class', 'grid')
|
|
611
|
-
.attr('transform', 'translate(' + theSidePad + ', ' + (h - 50) + ')')
|
|
612
|
-
.call(bottomXAxis)
|
|
613
|
-
.selectAll('text')
|
|
614
|
-
.style('text-anchor', 'middle')
|
|
615
|
-
.attr('fill', '#000')
|
|
616
|
-
.attr('stroke', 'none')
|
|
617
|
-
.attr('font-size', 10)
|
|
618
|
-
.attr('dy', '1em')
|
|
619
|
-
|
|
620
|
-
if (diagObj.db.topAxisEnabled() || conf.topAxis) {
|
|
621
|
-
let topXAxis = axisTop(timeScale)
|
|
622
|
-
.tickSize(-h + theTopPad + conf.gridLineStartPadding)
|
|
623
|
-
.tickFormat(timeFormat(diagObj.db.getAxisFormat() || conf.axisFormat || '%Y-%m-%d'))
|
|
624
|
-
|
|
625
|
-
if (resultTickInterval !== null) {
|
|
626
|
-
const every = resultTickInterval[1]
|
|
627
|
-
const interval = resultTickInterval[2]
|
|
628
|
-
const weekday = diagObj.db.getWeekday() || conf.weekday
|
|
629
|
-
|
|
630
|
-
switch (interval) {
|
|
631
|
-
case 'millisecond':
|
|
632
|
-
topXAxis.ticks(timeMillisecond.every(every))
|
|
633
|
-
break
|
|
634
|
-
case 'second':
|
|
635
|
-
topXAxis.ticks(timeSecond.every(every))
|
|
636
|
-
break
|
|
637
|
-
case 'minute':
|
|
638
|
-
topXAxis.ticks(timeMinute.every(every))
|
|
639
|
-
break
|
|
640
|
-
case 'hour':
|
|
641
|
-
topXAxis.ticks(timeHour.every(every))
|
|
642
|
-
break
|
|
643
|
-
case 'day':
|
|
644
|
-
topXAxis.ticks(timeDay.every(every))
|
|
645
|
-
break
|
|
646
|
-
case 'week':
|
|
647
|
-
topXAxis.ticks(mapWeekdayToTimeFunction[weekday].every(every))
|
|
648
|
-
break
|
|
649
|
-
case 'month':
|
|
650
|
-
topXAxis.ticks(timeMonth.every(every))
|
|
651
|
-
break
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
svg
|
|
656
|
-
.append('g')
|
|
657
|
-
.attr('class', 'grid')
|
|
658
|
-
.attr('transform', 'translate(' + theSidePad + ', ' + theTopPad + ')')
|
|
659
|
-
.call(topXAxis)
|
|
660
|
-
.selectAll('text')
|
|
661
|
-
.style('text-anchor', 'middle')
|
|
662
|
-
.attr('fill', '#000')
|
|
663
|
-
.attr('stroke', 'none')
|
|
664
|
-
.attr('font-size', 10)
|
|
665
|
-
// .attr('dy', '1em');
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
/**
|
|
670
|
-
* @param theGap
|
|
671
|
-
* @param theTopPad
|
|
672
|
-
*/
|
|
673
|
-
function vertLabels(theGap, theTopPad) {
|
|
674
|
-
let prevGap = 0
|
|
675
|
-
|
|
676
|
-
const numOccurrences = Object.keys(categoryHeights).map(d => [d, categoryHeights[d]])
|
|
677
|
-
|
|
678
|
-
svg
|
|
679
|
-
.append('g') // without doing this, impossible to put grid lines behind text
|
|
680
|
-
.selectAll('text')
|
|
681
|
-
.data(numOccurrences)
|
|
682
|
-
.enter()
|
|
683
|
-
.append(function (d) {
|
|
684
|
-
const rows = d[0].split(common.lineBreakRegex)
|
|
685
|
-
const dy = -(rows.length - 1) / 2
|
|
686
|
-
|
|
687
|
-
const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text')
|
|
688
|
-
svgLabel.setAttribute('dy', dy + 'em')
|
|
689
|
-
|
|
690
|
-
for (const [j, row] of rows.entries()) {
|
|
691
|
-
const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan')
|
|
692
|
-
tspan.setAttribute('alignment-baseline', 'central')
|
|
693
|
-
tspan.setAttribute('x', '10')
|
|
694
|
-
if (j > 0) {
|
|
695
|
-
tspan.setAttribute('dy', '1em')
|
|
696
|
-
}
|
|
697
|
-
tspan.textContent = row
|
|
698
|
-
svgLabel.appendChild(tspan)
|
|
699
|
-
}
|
|
700
|
-
return svgLabel
|
|
701
|
-
})
|
|
702
|
-
.attr('x', 10)
|
|
703
|
-
.attr('y', function (d, i) {
|
|
704
|
-
if (i > 0) {
|
|
705
|
-
for (let j = 0; j < i; j++) {
|
|
706
|
-
prevGap += numOccurrences[i - 1][1]
|
|
707
|
-
return (d[1] * theGap) / 2 + prevGap * theGap + theTopPad
|
|
708
|
-
}
|
|
709
|
-
} else {
|
|
710
|
-
return (d[1] * theGap) / 2 + theTopPad
|
|
711
|
-
}
|
|
712
|
-
})
|
|
713
|
-
.attr('font-size', conf.sectionFontSize)
|
|
714
|
-
.attr('class', function (d) {
|
|
715
|
-
for (const [i, category] of categories.entries()) {
|
|
716
|
-
if (d[0] === category) {
|
|
717
|
-
return 'sectionTitle sectionTitle' + (i % conf.numberSectionStyles)
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
return 'sectionTitle'
|
|
721
|
-
})
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
/**
|
|
725
|
-
* @param theSidePad
|
|
726
|
-
* @param theTopPad
|
|
727
|
-
* @param w
|
|
728
|
-
* @param h
|
|
729
|
-
*/
|
|
730
|
-
function drawToday(theSidePad, theTopPad, w, h) {
|
|
731
|
-
const todayMarker = diagObj.db.getTodayMarker()
|
|
732
|
-
if (todayMarker === 'off') {
|
|
733
|
-
return
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
const todayG = svg.append('g').attr('class', 'today')
|
|
737
|
-
const today = new Date()
|
|
738
|
-
const todayLine = todayG.append('line')
|
|
739
|
-
|
|
740
|
-
todayLine
|
|
741
|
-
.attr('x1', timeScale(today) + theSidePad)
|
|
742
|
-
.attr('x2', timeScale(today) + theSidePad)
|
|
743
|
-
.attr('y1', conf.titleTopMargin)
|
|
744
|
-
.attr('y2', h - conf.titleTopMargin)
|
|
745
|
-
.attr('class', 'today')
|
|
746
|
-
|
|
747
|
-
if (todayMarker !== '') {
|
|
748
|
-
todayLine.attr('style', todayMarker.replace(/,/g, ';'))
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
/**
|
|
753
|
-
* From this stack exchange question:
|
|
754
|
-
* http://stackoverflow.com/questions/1890203/unique-for-arrays-in-javascript
|
|
755
|
-
*
|
|
756
|
-
* @param arr
|
|
757
|
-
*/
|
|
758
|
-
function checkUnique(arr) {
|
|
759
|
-
const hash = {}
|
|
760
|
-
const result = []
|
|
761
|
-
for (let i = 0, l = arr.length; i < l; ++i) {
|
|
762
|
-
if (!Object.prototype.hasOwnProperty.call(hash, arr[i])) {
|
|
763
|
-
// it works with objects! in FF, at least
|
|
764
|
-
hash[arr[i]] = true
|
|
765
|
-
result.push(arr[i])
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
return result
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
export default {
|
|
773
|
-
setConf,
|
|
774
|
-
draw
|
|
775
|
-
}
|