@hpcc-js/timeline 3.4.4 → 3.4.6

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/src/MiniGantt.ts CHANGED
@@ -1,612 +1,612 @@
1
- import { ITooltip } from "@hpcc-js/api";
2
- import { Axis } from "@hpcc-js/chart";
3
- import { d3Event, EntityPin, EntityRect, local as d3Local, select as d3Select, SVGWidget, Utility } from "@hpcc-js/common";
4
- import { extent as d3Extent } from "d3-array";
5
- import { scaleBand as d3ScaleBand } from "d3-scale";
6
- import { timeFormat as d3TimeFormat, timeParse as d3TimeParse } from "d3-time-format";
7
- import { zoom as d3Zoom, zoomIdentity as d3ZoomIdentity } from "d3-zoom";
8
-
9
- import "../src/MiniGantt.css";
10
-
11
- export class MiniGantt extends SVGWidget {
12
- protected tlAxis: Axis;
13
- protected brAxis: Axis;
14
- protected verticalBands;
15
- protected _zoom;
16
- protected gUpperContent;
17
- protected gUpperAxis;
18
- protected gMiddleContent;
19
- protected gLowerAxis;
20
- protected gLowerContent;
21
- private localRect = d3Local<EntityRect>();
22
- private localEntityPin = d3Local<EntityPin>();
23
- private tooltipFormatter: (date: Date) => string;
24
- private _dateCache;
25
-
26
- protected rootExtent;
27
- protected _title_idx = 0;
28
- protected _startDate_idx = 1;
29
- protected _endDate_idx = 2;
30
- protected _icon_idx = -1;
31
- protected _color_idx = -1;
32
- protected _yoffset_idx = -1;
33
-
34
- constructor() {
35
- super();
36
- ITooltip.call(this);
37
- Utility.SimpleSelectionMixin.call(this);
38
-
39
- this._drawStartPos = "origin";
40
- this.tooltipHTML((d: any) => `<center>${d[this._title_idx]}</center><br>${this.tooltipFormatter(this.brAxis.parse(d[this._startDate_idx]))} -> ${this.tooltipFormatter(this.brAxis.parse(d[this._endDate_idx]))}`);
41
-
42
- this.tlAxis = new Axis()
43
- .type("time")
44
- ;
45
- this.brAxis = new Axis()
46
- .type("time")
47
- ;
48
- this.verticalBands = d3ScaleBand()
49
- .paddingOuter(0.2)
50
- .paddingInner(0.2)
51
- ;
52
- }
53
-
54
- isHorizontal(): boolean {
55
- return this.orientation() === "horizontal";
56
- }
57
-
58
- fullExtent() {
59
- const data = [...this.data().map(d => d[this._startDate_idx]), ...this.data().filter(d => !!d[this._endDate_idx]).map(d => d[this._endDate_idx])];
60
- return d3Extent(data);
61
- }
62
-
63
- extent() {
64
- const extent = this.rootExtent ? [this.rootExtent[1], this.rootExtent[2]] : this.fullExtent();
65
- if (extent[0] !== undefined && extent[1] !== undefined) {
66
- if (extent[0] === extent[1] || this.centerOnMostRecent()) {
67
- const parser = d3TimeParse(this.timePattern());
68
- const formatter = d3TimeFormat(this.timePattern());
69
- const date1 = parser(extent[0]);
70
- const date2 = parser(extent[1]);
71
- if (extent[0] === extent[1]) {
72
- extent[0] = formatter(new Date(date1.setFullYear(date1.getFullYear() - 1)));
73
- extent[1] = formatter(new Date(date1.setFullYear(date1.getFullYear() + 2)));
74
- } else {
75
- const time1 = date1.getTime();
76
- const timeDiff = date2.getTime() - time1;
77
- extent[0] = formatter(date1);
78
- extent[1] = formatter(new Date(time1 + (timeDiff * 2)));
79
- }
80
- }
81
- }
82
- return extent;
83
- }
84
-
85
- dataStartPos(d) {
86
- if (typeof this._dateCache[d[this._startDate_idx]] !== "undefined") {
87
- return this._dateCache[d[this._startDate_idx]];
88
- }
89
- const pos = this.brAxis.scalePos(d[this._startDate_idx]);
90
- this._dateCache[d[this._startDate_idx]] = pos;
91
- return pos;
92
- }
93
-
94
- dataEndPos(d) {
95
- if (typeof this._dateCache[d[this._endDate_idx]] !== "undefined") {
96
- return this._dateCache[d[this._endDate_idx]];
97
- }
98
- const pos = this.brAxis.scalePos(d[this._endDate_idx]);
99
- this._dateCache[d[this._endDate_idx]] = pos;
100
- return pos;
101
- }
102
-
103
- dataWidth(d) {
104
- return this.dataEndPos(d) - this.dataStartPos(d);
105
- }
106
-
107
- private transform;
108
- resetZoom() {
109
- // Triggers a "zoomed" event ---
110
- this._zoom.transform(this.element(), d3ZoomIdentity.translate(0, this.isHorizontal() ? 0 : this.height()));
111
- }
112
-
113
- zoomed() {
114
- this.transform = d3Event().transform;
115
- this.render();
116
- }
117
-
118
- private background;
119
- enter(domNode, element) {
120
- super.enter(domNode, element);
121
- this._zoom = d3Zoom()
122
- .scaleExtent([0, this.maxZoom()])
123
- .on("zoom", () => {
124
- this.zoomed();
125
- })
126
- ;
127
-
128
- this.background = element.append("rect")
129
- .attr("fill", "white")
130
- .attr("opacity", 0)
131
- .on("dblclick", () => {
132
- d3Event().stopPropagation();
133
- delete this.rootExtent;
134
- this.resetZoom();
135
- })
136
- ;
137
- this.gUpperContent = element.append("g").attr("class", "gUpperContent");
138
- this.gUpperAxis = element.append("g").attr("class", "gUpperAxis");
139
- this.gMiddleContent = element.append("g").attr("class", "gMiddleContent");
140
- this.gLowerAxis = element.append("g").attr("class", "gLowerAxis");
141
- this.gLowerContent = element.append("g").attr("class", "gLowerContent");
142
- this.tlAxis
143
- .target(this.gUpperAxis.node())
144
- .tickFormat(this.tickFormat())
145
- .guideTarget(this.gUpperAxis.append("g").node())
146
- .shrinkToFit("none")
147
- .overlapMode(this.tickFormat_exists() ? "stagger" : "none")
148
- .extend(0.1)
149
- ;
150
- this.brAxis
151
- .target(this.gLowerAxis.node())
152
- .tickFormat(this.tickFormat())
153
- .guideTarget(this.gLowerAxis.append("g").node())
154
- .shrinkToFit("none")
155
- .overlapMode(this.tickFormat_exists() ? "stagger" : "none")
156
- .extend(0.1)
157
- ;
158
-
159
- element.call(this._zoom);
160
- this._selection.widgetElement(this.gMiddleContent);
161
- }
162
-
163
- private _prevIsHorizontal;
164
- update(domNode, element) {
165
- super.update(domNode, element);
166
-
167
- this._dateCache = {};
168
-
169
- this._title_idx = this.titleColumn() !== null ? this.columns().indexOf(this.titleColumn()) : this._title_idx;
170
- this._startDate_idx = this.startDateColumn() !== null ? this.columns().indexOf(this.startDateColumn()) : this._startDate_idx;
171
- this._endDate_idx = this.endDateColumn() !== null ? this.columns().indexOf(this.endDateColumn()) : this._endDate_idx;
172
- this._icon_idx = this.iconColumn() !== null ? this.columns().indexOf(this.iconColumn()) : this._icon_idx;
173
- this._color_idx = this.colorColumn() !== null ? this.columns().indexOf(this.colorColumn()) : this._color_idx;
174
- this._yoffset_idx = this.yOffsetColumn() !== null ? this.columns().indexOf(this.yOffsetColumn()) : this._yoffset_idx;
175
-
176
- if (this._prevIsHorizontal !== this.isHorizontal()) {
177
- this._prevIsHorizontal = this.isHorizontal();
178
- this.resetZoom();
179
- return;
180
- }
181
-
182
- this.tooltipFormatter = d3TimeFormat(this.tooltipTimeFormat());
183
-
184
- const width = this.width();
185
- const height = this.height();
186
-
187
- this.background
188
- .attr("x", 0)
189
- .attr("y", 0)
190
- .attr("width", width)
191
- .attr("height", height)
192
- ;
193
-
194
- const extent = this.extent();
195
- this.tlAxis
196
- .x(width / 2)
197
- .orientation(this.isHorizontal() ? "top" : "left")
198
- .reverse(!this.isHorizontal())
199
- .timePattern(this.timePattern()) // "%Y-%m-%dT%H:%M:%S.%LZ"
200
- .width(width - 1)
201
- .low(extent[0])
202
- .high(extent[1])
203
- .updateScale()
204
- ;
205
-
206
- this.brAxis
207
- .x(width / 2)
208
- .y(height / 2)
209
- .orientation(this.isHorizontal() ? "bottom" : "right")
210
- .reverse(!this.isHorizontal())
211
- .timePattern(this.timePattern()) // "%Y-%m-%dT%H:%M:%S.%LZ"
212
- .width(width - 1)
213
- .height(height)
214
- .low(extent[0])
215
- .high(extent[1])
216
- .updateScale()
217
- ;
218
-
219
- if (this.transform) {
220
- let low;
221
- let hi;
222
- if (this.isHorizontal()) {
223
- low = this.tlAxis.parseInvert(this.tlAxis.invert(this.transform.invertX(0)));
224
- hi = this.tlAxis.parseInvert(this.tlAxis.invert(this.transform.invertX(width - 1)));
225
- } else {
226
- low = this.tlAxis.parseInvert(this.tlAxis.invert(- this.transform.invertY(0)));
227
- hi = this.tlAxis.parseInvert(this.tlAxis.invert(- this.transform.invertY(height - 1)));
228
- }
229
- this.tlAxis
230
- .low(low)
231
- .high(hi)
232
- .updateScale()
233
- ;
234
- this.brAxis
235
- .low(low)
236
- .high(hi)
237
- .updateScale()
238
- ;
239
- }
240
-
241
- const data = this.data().sort(this.isHorizontal() ? (l, r) => {
242
- const retVal = this.brAxis.scalePos(l[1]) - this.brAxis.scalePos(r[1]);
243
- if (retVal === 0) {
244
- return ("" + l[0]).localeCompare("" + r[0]);
245
- }
246
- return retVal;
247
- } : (l, r) => {
248
- return this.brAxis.scalePos(r[1]) - this.brAxis.scalePos(l[1]);
249
- });
250
- const events = data.filter(d => !d[this._endDate_idx]);
251
- const ranges = data.filter(d => !!d[this._endDate_idx]);
252
-
253
- this.tlAxis
254
- .render()
255
- ;
256
- this.brAxis
257
- .render()
258
- ;
259
- const brAxisBBox = this.brAxis.getBBox();
260
-
261
- let upperContentHeight = this.updateEntityPins(events);
262
- const lowerAxisHeight = brAxisBBox.height;
263
- let lowerHeight = height - upperContentHeight;
264
- const minYOffset = this._yoffset_idx !== -1 ? Math.min.apply(undefined, this.data().filter(row => !isNaN(row[this._yoffset_idx])).map(row => row[this._yoffset_idx])) : 0;
265
- if (events.length > 0 && ranges.length === 0) {
266
- // ONLY EVENTS
267
- this.tlAxis.visible(false);
268
- let y_offset = upperContentHeight / 4;
269
- if (y_offset > (height / 2) - lowerAxisHeight) {
270
- y_offset = (height / 2) - lowerAxisHeight;
271
- }
272
- const upperContentYOffset = (height / 2) + y_offset;
273
- const lowerAxisYOffset = ((height / 2) - lowerAxisHeight - y_offset) * -1;
274
- const halfMinYOffset = minYOffset !== 0 ? minYOffset / 2 : 0;
275
- this.gUpperContent.attr("transform", `translate(0, ${upperContentYOffset - halfMinYOffset})`);
276
- this.gLowerAxis.attr("transform", `translate(0, ${lowerAxisYOffset - halfMinYOffset})`);
277
- } else if (events.length === 0 && ranges.length > 0) {
278
- // ONLY RANGES
279
- this.tlAxis.visible(true);
280
- this.gUpperContent.attr("transform", `translate(0, ${upperContentHeight})`);
281
- this.gUpperAxis.attr("transform", `translate(0, ${upperContentHeight})`);
282
- } else {
283
- upperContentHeight -= minYOffset;
284
- lowerHeight += minYOffset;
285
- // BOTH
286
- this.tlAxis.visible(true);
287
- this.gUpperContent.attr("transform", `translate(0, ${upperContentHeight})`);
288
- this.gUpperAxis.attr("transform", `translate(0, ${upperContentHeight})`);
289
- this.gMiddleContent.attr("transform", `translate(0, ${upperContentHeight})`);
290
- }
291
- this.tlAxis
292
- .render()
293
- ;
294
- const tlAxisBBox = this.tlAxis.getBBox();
295
- interface BucketInfo {
296
- endPos: number;
297
- }
298
- const bucketData: BucketInfo[] = [];
299
- const bucketIndex = {};
300
- for (const range of ranges) {
301
- for (let i = 0; i < bucketData.length; ++i) {
302
- const bucket = bucketData[i];
303
- if (bucket.endPos + this.overlapTolerence() <= this.dataStartPos(range)) {
304
- bucketIndex[range] = i;
305
- bucket.endPos = this.dataEndPos(range);
306
- break;
307
- }
308
- }
309
-
310
- if (bucketIndex[range] === undefined) {
311
- bucketIndex[range] = bucketData.length;
312
- bucketData.push({
313
- endPos: this.dataEndPos(range)
314
- });
315
- }
316
- }
317
-
318
- const vbLower = this.isHorizontal() ? 0 + tlAxisBBox.height : 0 + tlAxisBBox.width;
319
- const vbHigher = this.isHorizontal() ? lowerHeight - brAxisBBox.height : width - brAxisBBox.width;
320
- this.verticalBands
321
- .range([vbLower, vbHigher])
322
- .domain(bucketData.map((_d, i) => i))
323
- ;
324
-
325
- if (ranges.length > 0) {
326
- this.updateEventRanges(events, ranges, bucketIndex, lowerHeight, tlAxisBBox, brAxisBBox, width);
327
- }
328
- }
329
-
330
- updateEntityPins(events) {
331
- let event_height = 0;
332
- const context = this;
333
- const entityPins = this.gUpperContent.selectAll(".entity_pin").data(events, d => d[0] + ":" + d[1]);
334
- const eventFontColor_idx = this.eventFontColorColumn() ? this.columns().indexOf(this.eventFontColorColumn()) : -1;
335
- const eventBorderColor_idx = this.eventBorderColorColumn() ? this.columns().indexOf(this.eventBorderColorColumn()) : -1;
336
- const eventBackgroundColor_idx = this.eventBackgroundColorColumn() ? this.columns().indexOf(this.eventBackgroundColorColumn()) : -1;
337
- const title_counts = {};
338
- for (const d of events) {
339
- const type = typeof d[context._title_idx] !== "undefined" ? d[context._title_idx] : d[0];
340
- title_counts[type] = title_counts[type] ? title_counts[type] + 1 : 1;
341
- }
342
- const title_types = Object.keys(title_counts);
343
- const title_group_offset = context.eventGroupOffset();
344
- const entityPinsEnter = entityPins.enter().append("g")
345
- .attr("class", "entity_pin");
346
- entityPinsEnter.append("line")
347
- .attr("class", "entity_line");
348
-
349
- entityPinsEnter
350
- .on("mouseover", function (d) {
351
- d3Select(this).raise();
352
- })
353
- .each(function (d, i) {
354
- const entityPin = new EntityPin()
355
- .target(this)
356
- .icon("")
357
- .iconOnlyShowOnHover(context.hideIconWhenCollapsed())
358
- .titleOnlyShowOnHover(context.hideTitleWhenCollapsed())
359
- .descriptionOnlyShowOnHover(context.hideDescriptionWhenCollapsed())
360
- .annotationOnlyShowOnHover(context.hideAnnotationsWhenCollapsed())
361
- .iconDiameter(18)
362
- .iconPaddingPercent(1)
363
- .titleFontSize(14)
364
- .descriptionColor("#333")
365
- .descriptionFontSize(15)
366
- .iconColor(eventFontColor_idx === -1 ? "#333" : d[eventFontColor_idx])
367
- .titleColor(eventFontColor_idx === -1 ? "#333" : d[eventFontColor_idx])
368
- .descriptionColor(eventFontColor_idx === -1 ? "#333" : d[eventFontColor_idx])
369
- .backgroundShape("pin")
370
- .backgroundColorFill(eventFontColor_idx === -1 ? "#f8f8f8" : d[eventBackgroundColor_idx])
371
- .backgroundColorStroke(eventFontColor_idx === -1 ? "#ccc" : d[eventBorderColor_idx])
372
- .cornerRadius(5)
373
- .arrowHeight(10)
374
- .arrowWidth(16)
375
- ;
376
- context.localEntityPin.set(this, entityPin);
377
- })
378
- .merge(entityPins)
379
- .each(function (d, i) {
380
- const entityPin = context.localEntityPin.get(this);
381
- const _title = typeof d[context._title_idx] !== "undefined" ? d[context._title_idx] : entityPin.title();
382
- const x_offset = context.dataStartPos(d) - 0;
383
- let y_offset = ((title_types.indexOf(_title) % context.eventGroupMod()) * title_group_offset) - 5;
384
- if (typeof d[context._yoffset_idx] !== "undefined") y_offset += d[context._yoffset_idx] ? d[context._yoffset_idx] : 0;
385
- if (d[context._title_idx] !== entityPin.title() && d[context._startDate_idx] !== entityPin.description()) {
386
- const parsed_start_time = context.brAxis.parse(d[context._startDate_idx]);
387
- const formatted_start_time = context.tooltipFormatter(parsed_start_time);
388
- entityPin
389
- .x(x_offset)
390
- .y(y_offset)
391
- .iconOnlyShowOnHover(context.hideIconWhenCollapsed())
392
- .titleOnlyShowOnHover(context.hideTitleWhenCollapsed())
393
- .descriptionOnlyShowOnHover(context.hideDescriptionWhenCollapsed())
394
- .annotationOnlyShowOnHover(context.hideAnnotationsWhenCollapsed())
395
- .icon(typeof d[context._icon_idx] !== "undefined" ? d[context._icon_idx] : entityPin.icon())
396
- .title(_title)
397
- .description(formatted_start_time)
398
- .animationFrameRender()
399
- ;
400
- } else {
401
- entityPin.move({ x: x_offset, y: y_offset });
402
- }
403
- const calc_height = entityPin.calcHeight();
404
- if (event_height < calc_height) event_height = calc_height;
405
-
406
- d3Select(this).selectAll(".entity_line")
407
- .attr("x1", x_offset)
408
- .attr("x2", x_offset)
409
- .attr("y1", 0)
410
- .attr("y2", y_offset)
411
- .style("stroke", eventFontColor_idx === -1 ? "#ccc" : d[eventBorderColor_idx])
412
- .style("stroke-width", 1)
413
- ;
414
- })
415
- ;
416
- entityPins.exit()
417
- .each(function (d, i) {
418
- const entityPin = context.localEntityPin.get(this);
419
- entityPin.target(null);
420
-
421
- })
422
- .remove();
423
- const event_offset = Math.abs(Math.min(events.length, context.eventGroupMod()) * context.eventGroupOffset());
424
- return event_height + event_offset;
425
- }
426
-
427
- updateEventRanges(events, ranges, bucketIndex, eventRangeHeight, tlAxisBBox, brAxisBBox, width) {
428
- const context = this;
429
-
430
- const lines = this.gMiddleContent.selectAll(".line").data(events, d => {
431
- return d[context._title_idx];
432
- });
433
- lines.enter().append("line")
434
- .attr("class", "line")
435
- .merge(lines)
436
- .attr(this.isHorizontal() ? "x1" : "y1", d => this.dataStartPos(d) - 0)
437
- .attr(this.isHorizontal() ? "x2" : "y2", d => this.dataStartPos(d) - 0)
438
- .attr(this.isHorizontal() ? "y1" : "x1", this.isHorizontal() ? tlAxisBBox.height : tlAxisBBox.width)
439
- .attr(this.isHorizontal() ? "y2" : "x2", this.isHorizontal() ? eventRangeHeight - brAxisBBox.height : width - brAxisBBox.width)
440
- ;
441
- lines.exit().remove();
442
- const buckets = this.gMiddleContent.selectAll(".buckets").data(ranges, d => d[context._title_idx]);
443
- buckets.enter().append("g")
444
- .attr("class", "buckets")
445
- .call(this._selection.enter.bind(this._selection))
446
- .each(function (d) {
447
- const entityRect = new EntityRect()
448
- .target(this)
449
- .iconDiameter(28)
450
- .iconPaddingPercent(0)
451
- .titleFontSize(28)
452
- .titleColor(context.rangeFontColor())
453
- .descriptionColor(context.rangeFontColor())
454
- .iconColor(context.rangeFontColor())
455
- .backgroundShape("rect")
456
- .backgroundColorFill(d[context._color_idx])
457
- ;
458
- context.localRect.set(this, entityRect);
459
- context.enterEntityRect(entityRect, d);
460
- })
461
- .on("click", function (d) {
462
- context.click(context.rowToObj(d), "range", context._selection.selected(this));
463
- }, false)
464
- .on("dblclick", function (d) {
465
- context.rootExtent = d;
466
- context.resetZoom();
467
- context.dblclick(context.rowToObj(d), "range", context._selection.selected(this));
468
- }, true)
469
- .on("mouseout.tooltip", this.tooltip.hide)
470
- .on("mousemove.tooltip", this.tooltip.show)
471
- .merge(buckets)
472
- .attr("transform", d => context.isHorizontal() ?
473
- `translate(${this.dataStartPos(d)}, ${this.verticalBands(bucketIndex[d])}) ` :
474
- `translate(${this.verticalBands(bucketIndex[d])}, ${this.dataStartPos(d)}) `)
475
- .each(function (d) {
476
- const textBox = context.localRect.get(this);
477
- const x = context.dataWidth(d) / 2;
478
- const y = context.verticalBands.bandwidth() / 2;
479
- const rectWidth = Math.max(context.dataWidth(d), 2);
480
- const rectHeight = Math.max(context.verticalBands.bandwidth(), 2);
481
- const fontHeightRatio = 0.618;
482
- const paddingRatio = ((1 - fontHeightRatio) / 2);
483
- const paddingSize = paddingRatio * rectHeight;
484
- const fontSize = rectHeight * fontHeightRatio;
485
- const iconSize = fontSize;
486
- textBox
487
- .pos(context.isHorizontal() ? { x, y } : { x: y, y: x })
488
- .fixedHeight(context.isHorizontal() ? rectHeight : rectWidth)
489
- .fixedWidth(context.isHorizontal() ? rectWidth : rectHeight)
490
- .icon(typeof d[context._icon_idx] !== "undefined" ? d[context._icon_idx] : "")
491
- .title(typeof d[context._title_idx] !== "undefined" ? d[context._title_idx] : "")
492
- .padding(paddingSize)
493
- .iconDiameter(iconSize)
494
- .titleFontSize(fontSize)
495
- ;
496
- if (iconSize * 1.5 > rectWidth) {
497
- textBox.icon(null);
498
- }
499
- context.updateEntityRect(textBox, d[context._icon_idx]);
500
- textBox
501
- .render()
502
- ;
503
- });
504
- buckets.exit().remove();
505
- }
506
-
507
- exit(domNode, element) {
508
- this.brAxis.target(null);
509
- this.tlAxis.target(null);
510
- super.exit(domNode, element);
511
- }
512
-
513
- // Events ---
514
- click(row, col, sel) {
515
- }
516
-
517
- dblclick(row, col, sel) {
518
- }
519
-
520
- enterEntityRect(textbox: EntityRect, d) {
521
- }
522
-
523
- updateEntityRect(textbox: EntityRect, d) {
524
- }
525
- }
526
- MiniGantt.prototype._class += " timeline_MiniGantt";
527
- MiniGantt.prototype.implements(ITooltip.prototype);
528
- MiniGantt.prototype.mixin(Utility.SimpleSelectionMixin);
529
-
530
- export interface MiniGantt {
531
- // ITooltip ---
532
- tooltip;
533
- tooltipHTML(_): string;
534
- tooltipFormat(_): string;
535
-
536
- // SimpleSelectionMixin
537
- _selection;
538
-
539
- // Properties ---
540
- timePattern(): string;
541
- timePattern(_: string): this;
542
- tickFormat(): string;
543
- tickFormat(_: string): this;
544
- tickFormat_exists(): boolean;
545
- tooltipTimeFormat(): string;
546
- tooltipTimeFormat(_: string): this;
547
- overlapTolerence(): number;
548
- overlapTolerence(_: number): this;
549
- orientation(): string;
550
- orientation(_: string): this;
551
- rangeFontColor(): string;
552
- rangeFontColor(_: string): this;
553
- titleColumn(): string;
554
- titleColumn(_: string): this;
555
- startDateColumn(): string;
556
- startDateColumn(_: string): this;
557
- endDateColumn(): string;
558
- endDateColumn(_: string): this;
559
- iconColumn(): string;
560
- iconColumn(_: string): this;
561
- colorColumn(): string;
562
- colorColumn(_: string): this;
563
- yOffsetColumn(): string;
564
- yOffsetColumn(_: string): this;
565
- maxZoom(): number;
566
- maxZoom(_: number): this;
567
- eventGroupMod(): number;
568
- eventGroupMod(_: number): this;
569
- eventGroupOffset(): number;
570
- eventGroupOffset(_: number): this;
571
- eventFontColorColumn(): string;
572
- eventFontColorColumn(_: string): this;
573
- eventBorderColorColumn(): string;
574
- eventBorderColorColumn(_: string): this;
575
- eventBackgroundColorColumn(): string;
576
- eventBackgroundColorColumn(_: string): this;
577
- hideIconWhenCollapsed(): boolean;
578
- hideIconWhenCollapsed(_: boolean): this;
579
- hideTitleWhenCollapsed(): boolean;
580
- hideTitleWhenCollapsed(_: boolean): this;
581
- hideDescriptionWhenCollapsed(): boolean;
582
- hideDescriptionWhenCollapsed(_: boolean): this;
583
- hideAnnotationsWhenCollapsed(): boolean;
584
- hideAnnotationsWhenCollapsed(_: boolean): this;
585
- centerOnMostRecent(): boolean;
586
- centerOnMostRecent(_: boolean): this;
587
-
588
- }
589
-
590
- MiniGantt.prototype.publish("timePattern", "%Y-%m-%d", "string", "timePattern");
591
- MiniGantt.prototype.publish("tickFormat", null, "string", "tickFormat", undefined, { optional: true });
592
- MiniGantt.prototype.publish("tooltipTimeFormat", "%Y-%m-%d", "string", "tooltipTimeFormat");
593
- MiniGantt.prototype.publish("overlapTolerence", 2, "number", "overlapTolerence");
594
- MiniGantt.prototype.publish("orientation", "horizontal", "set", "orientation", ["horizontal", "vertical"]);
595
- MiniGantt.prototype.publish("rangeFontColor", "#ecf0f1", "html-color", "rangeFontColor");
596
- MiniGantt.prototype.publish("titleColumn", null, "string", "titleColumn");
597
- MiniGantt.prototype.publish("startDateColumn", null, "string", "startDateColumn");
598
- MiniGantt.prototype.publish("endDateColumn", null, "string", "endDateColumn");
599
- MiniGantt.prototype.publish("iconColumn", null, "string", "iconColumn");
600
- MiniGantt.prototype.publish("colorColumn", null, "string", "colorColumn");
601
- MiniGantt.prototype.publish("yOffsetColumn", null, "string", "yOffsetColumn");
602
- MiniGantt.prototype.publish("maxZoom", 16, "number", "maxZoom");
603
- MiniGantt.prototype.publish("eventGroupOffset", -50, "number", "eventGroupOffset");
604
- MiniGantt.prototype.publish("eventGroupMod", 5, "number", "eventGroupMod");
605
- MiniGantt.prototype.publish("eventFontColorColumn", null, "string", "eventFontColorColumn");
606
- MiniGantt.prototype.publish("eventBorderColorColumn", null, "string", "eventBorderColorColumn");
607
- MiniGantt.prototype.publish("eventBackgroundColorColumn", null, "string", "eventBackgroundColorColumn");
608
- MiniGantt.prototype.publish("hideIconWhenCollapsed", false, "boolean", "hideIconWhenCollapsed");
609
- MiniGantt.prototype.publish("hideTitleWhenCollapsed", false, "boolean", "hideTitleWhenCollapsed");
610
- MiniGantt.prototype.publish("hideDescriptionWhenCollapsed", false, "boolean", "hideDescriptionWhenCollapsed");
611
- MiniGantt.prototype.publish("hideAnnotationsWhenCollapsed", true, "boolean", "hideAnnotationsWhenCollapsed");
612
- MiniGantt.prototype.publish("centerOnMostRecent", false, "boolean", "If true, the timeline will be centered on the most recent data point");
1
+ import { ITooltip } from "@hpcc-js/api";
2
+ import { Axis } from "@hpcc-js/chart";
3
+ import { d3Event, EntityPin, EntityRect, local as d3Local, select as d3Select, SVGWidget, Utility } from "@hpcc-js/common";
4
+ import { extent as d3Extent } from "d3-array";
5
+ import { scaleBand as d3ScaleBand } from "d3-scale";
6
+ import { timeFormat as d3TimeFormat, timeParse as d3TimeParse } from "d3-time-format";
7
+ import { zoom as d3Zoom, zoomIdentity as d3ZoomIdentity } from "d3-zoom";
8
+
9
+ import "../src/MiniGantt.css";
10
+
11
+ export class MiniGantt extends SVGWidget {
12
+ protected tlAxis: Axis;
13
+ protected brAxis: Axis;
14
+ protected verticalBands;
15
+ protected _zoom;
16
+ protected gUpperContent;
17
+ protected gUpperAxis;
18
+ protected gMiddleContent;
19
+ protected gLowerAxis;
20
+ protected gLowerContent;
21
+ private localRect = d3Local<EntityRect>();
22
+ private localEntityPin = d3Local<EntityPin>();
23
+ private tooltipFormatter: (date: Date) => string;
24
+ private _dateCache;
25
+
26
+ protected rootExtent;
27
+ protected _title_idx = 0;
28
+ protected _startDate_idx = 1;
29
+ protected _endDate_idx = 2;
30
+ protected _icon_idx = -1;
31
+ protected _color_idx = -1;
32
+ protected _yoffset_idx = -1;
33
+
34
+ constructor() {
35
+ super();
36
+ ITooltip.call(this);
37
+ Utility.SimpleSelectionMixin.call(this);
38
+
39
+ this._drawStartPos = "origin";
40
+ this.tooltipHTML((d: any) => `<center>${d[this._title_idx]}</center><br>${this.tooltipFormatter(this.brAxis.parse(d[this._startDate_idx]))} -> ${this.tooltipFormatter(this.brAxis.parse(d[this._endDate_idx]))}`);
41
+
42
+ this.tlAxis = new Axis()
43
+ .type("time")
44
+ ;
45
+ this.brAxis = new Axis()
46
+ .type("time")
47
+ ;
48
+ this.verticalBands = d3ScaleBand()
49
+ .paddingOuter(0.2)
50
+ .paddingInner(0.2)
51
+ ;
52
+ }
53
+
54
+ isHorizontal(): boolean {
55
+ return this.orientation() === "horizontal";
56
+ }
57
+
58
+ fullExtent() {
59
+ const data = [...this.data().map(d => d[this._startDate_idx]), ...this.data().filter(d => !!d[this._endDate_idx]).map(d => d[this._endDate_idx])];
60
+ return d3Extent(data);
61
+ }
62
+
63
+ extent() {
64
+ const extent = this.rootExtent ? [this.rootExtent[1], this.rootExtent[2]] : this.fullExtent();
65
+ if (extent[0] !== undefined && extent[1] !== undefined) {
66
+ if (extent[0] === extent[1] || this.centerOnMostRecent()) {
67
+ const parser = d3TimeParse(this.timePattern());
68
+ const formatter = d3TimeFormat(this.timePattern());
69
+ const date1 = parser(extent[0]);
70
+ const date2 = parser(extent[1]);
71
+ if (extent[0] === extent[1]) {
72
+ extent[0] = formatter(new Date(date1.setFullYear(date1.getFullYear() - 1)));
73
+ extent[1] = formatter(new Date(date1.setFullYear(date1.getFullYear() + 2)));
74
+ } else {
75
+ const time1 = date1.getTime();
76
+ const timeDiff = date2.getTime() - time1;
77
+ extent[0] = formatter(date1);
78
+ extent[1] = formatter(new Date(time1 + (timeDiff * 2)));
79
+ }
80
+ }
81
+ }
82
+ return extent;
83
+ }
84
+
85
+ dataStartPos(d) {
86
+ if (typeof this._dateCache[d[this._startDate_idx]] !== "undefined") {
87
+ return this._dateCache[d[this._startDate_idx]];
88
+ }
89
+ const pos = this.brAxis.scalePos(d[this._startDate_idx]);
90
+ this._dateCache[d[this._startDate_idx]] = pos;
91
+ return pos;
92
+ }
93
+
94
+ dataEndPos(d) {
95
+ if (typeof this._dateCache[d[this._endDate_idx]] !== "undefined") {
96
+ return this._dateCache[d[this._endDate_idx]];
97
+ }
98
+ const pos = this.brAxis.scalePos(d[this._endDate_idx]);
99
+ this._dateCache[d[this._endDate_idx]] = pos;
100
+ return pos;
101
+ }
102
+
103
+ dataWidth(d) {
104
+ return this.dataEndPos(d) - this.dataStartPos(d);
105
+ }
106
+
107
+ private transform;
108
+ resetZoom() {
109
+ // Triggers a "zoomed" event ---
110
+ this._zoom.transform(this.element(), d3ZoomIdentity.translate(0, this.isHorizontal() ? 0 : this.height()));
111
+ }
112
+
113
+ zoomed() {
114
+ this.transform = d3Event().transform;
115
+ this.render();
116
+ }
117
+
118
+ private background;
119
+ enter(domNode, element) {
120
+ super.enter(domNode, element);
121
+ this._zoom = d3Zoom()
122
+ .scaleExtent([0, this.maxZoom()])
123
+ .on("zoom", () => {
124
+ this.zoomed();
125
+ })
126
+ ;
127
+
128
+ this.background = element.append("rect")
129
+ .attr("fill", "white")
130
+ .attr("opacity", 0)
131
+ .on("dblclick", () => {
132
+ d3Event().stopPropagation();
133
+ delete this.rootExtent;
134
+ this.resetZoom();
135
+ })
136
+ ;
137
+ this.gUpperContent = element.append("g").attr("class", "gUpperContent");
138
+ this.gUpperAxis = element.append("g").attr("class", "gUpperAxis");
139
+ this.gMiddleContent = element.append("g").attr("class", "gMiddleContent");
140
+ this.gLowerAxis = element.append("g").attr("class", "gLowerAxis");
141
+ this.gLowerContent = element.append("g").attr("class", "gLowerContent");
142
+ this.tlAxis
143
+ .target(this.gUpperAxis.node())
144
+ .tickFormat(this.tickFormat())
145
+ .guideTarget(this.gUpperAxis.append("g").node())
146
+ .shrinkToFit("none")
147
+ .overlapMode(this.tickFormat_exists() ? "stagger" : "none")
148
+ .extend(0.1)
149
+ ;
150
+ this.brAxis
151
+ .target(this.gLowerAxis.node())
152
+ .tickFormat(this.tickFormat())
153
+ .guideTarget(this.gLowerAxis.append("g").node())
154
+ .shrinkToFit("none")
155
+ .overlapMode(this.tickFormat_exists() ? "stagger" : "none")
156
+ .extend(0.1)
157
+ ;
158
+
159
+ element.call(this._zoom);
160
+ this._selection.widgetElement(this.gMiddleContent);
161
+ }
162
+
163
+ private _prevIsHorizontal;
164
+ update(domNode, element) {
165
+ super.update(domNode, element);
166
+
167
+ this._dateCache = {};
168
+
169
+ this._title_idx = this.titleColumn() !== null ? this.columns().indexOf(this.titleColumn()) : this._title_idx;
170
+ this._startDate_idx = this.startDateColumn() !== null ? this.columns().indexOf(this.startDateColumn()) : this._startDate_idx;
171
+ this._endDate_idx = this.endDateColumn() !== null ? this.columns().indexOf(this.endDateColumn()) : this._endDate_idx;
172
+ this._icon_idx = this.iconColumn() !== null ? this.columns().indexOf(this.iconColumn()) : this._icon_idx;
173
+ this._color_idx = this.colorColumn() !== null ? this.columns().indexOf(this.colorColumn()) : this._color_idx;
174
+ this._yoffset_idx = this.yOffsetColumn() !== null ? this.columns().indexOf(this.yOffsetColumn()) : this._yoffset_idx;
175
+
176
+ if (this._prevIsHorizontal !== this.isHorizontal()) {
177
+ this._prevIsHorizontal = this.isHorizontal();
178
+ this.resetZoom();
179
+ return;
180
+ }
181
+
182
+ this.tooltipFormatter = d3TimeFormat(this.tooltipTimeFormat());
183
+
184
+ const width = this.width();
185
+ const height = this.height();
186
+
187
+ this.background
188
+ .attr("x", 0)
189
+ .attr("y", 0)
190
+ .attr("width", width)
191
+ .attr("height", height)
192
+ ;
193
+
194
+ const extent = this.extent();
195
+ this.tlAxis
196
+ .x(width / 2)
197
+ .orientation(this.isHorizontal() ? "top" : "left")
198
+ .reverse(!this.isHorizontal())
199
+ .timePattern(this.timePattern()) // "%Y-%m-%dT%H:%M:%S.%LZ"
200
+ .width(width - 1)
201
+ .low(extent[0])
202
+ .high(extent[1])
203
+ .updateScale()
204
+ ;
205
+
206
+ this.brAxis
207
+ .x(width / 2)
208
+ .y(height / 2)
209
+ .orientation(this.isHorizontal() ? "bottom" : "right")
210
+ .reverse(!this.isHorizontal())
211
+ .timePattern(this.timePattern()) // "%Y-%m-%dT%H:%M:%S.%LZ"
212
+ .width(width - 1)
213
+ .height(height)
214
+ .low(extent[0])
215
+ .high(extent[1])
216
+ .updateScale()
217
+ ;
218
+
219
+ if (this.transform) {
220
+ let low;
221
+ let hi;
222
+ if (this.isHorizontal()) {
223
+ low = this.tlAxis.parseInvert(this.tlAxis.invert(this.transform.invertX(0)));
224
+ hi = this.tlAxis.parseInvert(this.tlAxis.invert(this.transform.invertX(width - 1)));
225
+ } else {
226
+ low = this.tlAxis.parseInvert(this.tlAxis.invert(- this.transform.invertY(0)));
227
+ hi = this.tlAxis.parseInvert(this.tlAxis.invert(- this.transform.invertY(height - 1)));
228
+ }
229
+ this.tlAxis
230
+ .low(low)
231
+ .high(hi)
232
+ .updateScale()
233
+ ;
234
+ this.brAxis
235
+ .low(low)
236
+ .high(hi)
237
+ .updateScale()
238
+ ;
239
+ }
240
+
241
+ const data = this.data().sort(this.isHorizontal() ? (l, r) => {
242
+ const retVal = this.brAxis.scalePos(l[1]) - this.brAxis.scalePos(r[1]);
243
+ if (retVal === 0) {
244
+ return ("" + l[0]).localeCompare("" + r[0]);
245
+ }
246
+ return retVal;
247
+ } : (l, r) => {
248
+ return this.brAxis.scalePos(r[1]) - this.brAxis.scalePos(l[1]);
249
+ });
250
+ const events = data.filter(d => !d[this._endDate_idx]);
251
+ const ranges = data.filter(d => !!d[this._endDate_idx]);
252
+
253
+ this.tlAxis
254
+ .render()
255
+ ;
256
+ this.brAxis
257
+ .render()
258
+ ;
259
+ const brAxisBBox = this.brAxis.getBBox();
260
+
261
+ let upperContentHeight = this.updateEntityPins(events);
262
+ const lowerAxisHeight = brAxisBBox.height;
263
+ let lowerHeight = height - upperContentHeight;
264
+ const minYOffset = this._yoffset_idx !== -1 ? Math.min.apply(undefined, this.data().filter(row => !isNaN(row[this._yoffset_idx])).map(row => row[this._yoffset_idx])) : 0;
265
+ if (events.length > 0 && ranges.length === 0) {
266
+ // ONLY EVENTS
267
+ this.tlAxis.visible(false);
268
+ let y_offset = upperContentHeight / 4;
269
+ if (y_offset > (height / 2) - lowerAxisHeight) {
270
+ y_offset = (height / 2) - lowerAxisHeight;
271
+ }
272
+ const upperContentYOffset = (height / 2) + y_offset;
273
+ const lowerAxisYOffset = ((height / 2) - lowerAxisHeight - y_offset) * -1;
274
+ const halfMinYOffset = minYOffset !== 0 ? minYOffset / 2 : 0;
275
+ this.gUpperContent.attr("transform", `translate(0, ${upperContentYOffset - halfMinYOffset})`);
276
+ this.gLowerAxis.attr("transform", `translate(0, ${lowerAxisYOffset - halfMinYOffset})`);
277
+ } else if (events.length === 0 && ranges.length > 0) {
278
+ // ONLY RANGES
279
+ this.tlAxis.visible(true);
280
+ this.gUpperContent.attr("transform", `translate(0, ${upperContentHeight})`);
281
+ this.gUpperAxis.attr("transform", `translate(0, ${upperContentHeight})`);
282
+ } else {
283
+ upperContentHeight -= minYOffset;
284
+ lowerHeight += minYOffset;
285
+ // BOTH
286
+ this.tlAxis.visible(true);
287
+ this.gUpperContent.attr("transform", `translate(0, ${upperContentHeight})`);
288
+ this.gUpperAxis.attr("transform", `translate(0, ${upperContentHeight})`);
289
+ this.gMiddleContent.attr("transform", `translate(0, ${upperContentHeight})`);
290
+ }
291
+ this.tlAxis
292
+ .render()
293
+ ;
294
+ const tlAxisBBox = this.tlAxis.getBBox();
295
+ interface BucketInfo {
296
+ endPos: number;
297
+ }
298
+ const bucketData: BucketInfo[] = [];
299
+ const bucketIndex = {};
300
+ for (const range of ranges) {
301
+ for (let i = 0; i < bucketData.length; ++i) {
302
+ const bucket = bucketData[i];
303
+ if (bucket.endPos + this.overlapTolerence() <= this.dataStartPos(range)) {
304
+ bucketIndex[range] = i;
305
+ bucket.endPos = this.dataEndPos(range);
306
+ break;
307
+ }
308
+ }
309
+
310
+ if (bucketIndex[range] === undefined) {
311
+ bucketIndex[range] = bucketData.length;
312
+ bucketData.push({
313
+ endPos: this.dataEndPos(range)
314
+ });
315
+ }
316
+ }
317
+
318
+ const vbLower = this.isHorizontal() ? 0 + tlAxisBBox.height : 0 + tlAxisBBox.width;
319
+ const vbHigher = this.isHorizontal() ? lowerHeight - brAxisBBox.height : width - brAxisBBox.width;
320
+ this.verticalBands
321
+ .range([vbLower, vbHigher])
322
+ .domain(bucketData.map((_d, i) => i))
323
+ ;
324
+
325
+ if (ranges.length > 0) {
326
+ this.updateEventRanges(events, ranges, bucketIndex, lowerHeight, tlAxisBBox, brAxisBBox, width);
327
+ }
328
+ }
329
+
330
+ updateEntityPins(events) {
331
+ let event_height = 0;
332
+ const context = this;
333
+ const entityPins = this.gUpperContent.selectAll(".entity_pin").data(events, d => d[0] + ":" + d[1]);
334
+ const eventFontColor_idx = this.eventFontColorColumn() ? this.columns().indexOf(this.eventFontColorColumn()) : -1;
335
+ const eventBorderColor_idx = this.eventBorderColorColumn() ? this.columns().indexOf(this.eventBorderColorColumn()) : -1;
336
+ const eventBackgroundColor_idx = this.eventBackgroundColorColumn() ? this.columns().indexOf(this.eventBackgroundColorColumn()) : -1;
337
+ const title_counts = {};
338
+ for (const d of events) {
339
+ const type = typeof d[context._title_idx] !== "undefined" ? d[context._title_idx] : d[0];
340
+ title_counts[type] = title_counts[type] ? title_counts[type] + 1 : 1;
341
+ }
342
+ const title_types = Object.keys(title_counts);
343
+ const title_group_offset = context.eventGroupOffset();
344
+ const entityPinsEnter = entityPins.enter().append("g")
345
+ .attr("class", "entity_pin");
346
+ entityPinsEnter.append("line")
347
+ .attr("class", "entity_line");
348
+
349
+ entityPinsEnter
350
+ .on("mouseover", function (d) {
351
+ d3Select(this).raise();
352
+ })
353
+ .each(function (d, i) {
354
+ const entityPin = new EntityPin()
355
+ .target(this)
356
+ .icon("")
357
+ .iconOnlyShowOnHover(context.hideIconWhenCollapsed())
358
+ .titleOnlyShowOnHover(context.hideTitleWhenCollapsed())
359
+ .descriptionOnlyShowOnHover(context.hideDescriptionWhenCollapsed())
360
+ .annotationOnlyShowOnHover(context.hideAnnotationsWhenCollapsed())
361
+ .iconDiameter(18)
362
+ .iconPaddingPercent(1)
363
+ .titleFontSize(14)
364
+ .descriptionColor("#333")
365
+ .descriptionFontSize(15)
366
+ .iconColor(eventFontColor_idx === -1 ? "#333" : d[eventFontColor_idx])
367
+ .titleColor(eventFontColor_idx === -1 ? "#333" : d[eventFontColor_idx])
368
+ .descriptionColor(eventFontColor_idx === -1 ? "#333" : d[eventFontColor_idx])
369
+ .backgroundShape("pin")
370
+ .backgroundColorFill(eventFontColor_idx === -1 ? "#f8f8f8" : d[eventBackgroundColor_idx])
371
+ .backgroundColorStroke(eventFontColor_idx === -1 ? "#ccc" : d[eventBorderColor_idx])
372
+ .cornerRadius(5)
373
+ .arrowHeight(10)
374
+ .arrowWidth(16)
375
+ ;
376
+ context.localEntityPin.set(this, entityPin);
377
+ })
378
+ .merge(entityPins)
379
+ .each(function (d, i) {
380
+ const entityPin = context.localEntityPin.get(this);
381
+ const _title = typeof d[context._title_idx] !== "undefined" ? d[context._title_idx] : entityPin.title();
382
+ const x_offset = context.dataStartPos(d) - 0;
383
+ let y_offset = ((title_types.indexOf(_title) % context.eventGroupMod()) * title_group_offset) - 5;
384
+ if (typeof d[context._yoffset_idx] !== "undefined") y_offset += d[context._yoffset_idx] ? d[context._yoffset_idx] : 0;
385
+ if (d[context._title_idx] !== entityPin.title() && d[context._startDate_idx] !== entityPin.description()) {
386
+ const parsed_start_time = context.brAxis.parse(d[context._startDate_idx]);
387
+ const formatted_start_time = context.tooltipFormatter(parsed_start_time);
388
+ entityPin
389
+ .x(x_offset)
390
+ .y(y_offset)
391
+ .iconOnlyShowOnHover(context.hideIconWhenCollapsed())
392
+ .titleOnlyShowOnHover(context.hideTitleWhenCollapsed())
393
+ .descriptionOnlyShowOnHover(context.hideDescriptionWhenCollapsed())
394
+ .annotationOnlyShowOnHover(context.hideAnnotationsWhenCollapsed())
395
+ .icon(typeof d[context._icon_idx] !== "undefined" ? d[context._icon_idx] : entityPin.icon())
396
+ .title(_title)
397
+ .description(formatted_start_time)
398
+ .animationFrameRender()
399
+ ;
400
+ } else {
401
+ entityPin.move({ x: x_offset, y: y_offset });
402
+ }
403
+ const calc_height = entityPin.calcHeight();
404
+ if (event_height < calc_height) event_height = calc_height;
405
+
406
+ d3Select(this).selectAll(".entity_line")
407
+ .attr("x1", x_offset)
408
+ .attr("x2", x_offset)
409
+ .attr("y1", 0)
410
+ .attr("y2", y_offset)
411
+ .style("stroke", eventFontColor_idx === -1 ? "#ccc" : d[eventBorderColor_idx])
412
+ .style("stroke-width", 1)
413
+ ;
414
+ })
415
+ ;
416
+ entityPins.exit()
417
+ .each(function (d, i) {
418
+ const entityPin = context.localEntityPin.get(this);
419
+ entityPin.target(null);
420
+
421
+ })
422
+ .remove();
423
+ const event_offset = Math.abs(Math.min(events.length, context.eventGroupMod()) * context.eventGroupOffset());
424
+ return event_height + event_offset;
425
+ }
426
+
427
+ updateEventRanges(events, ranges, bucketIndex, eventRangeHeight, tlAxisBBox, brAxisBBox, width) {
428
+ const context = this;
429
+
430
+ const lines = this.gMiddleContent.selectAll(".line").data(events, d => {
431
+ return d[context._title_idx];
432
+ });
433
+ lines.enter().append("line")
434
+ .attr("class", "line")
435
+ .merge(lines)
436
+ .attr(this.isHorizontal() ? "x1" : "y1", d => this.dataStartPos(d) - 0)
437
+ .attr(this.isHorizontal() ? "x2" : "y2", d => this.dataStartPos(d) - 0)
438
+ .attr(this.isHorizontal() ? "y1" : "x1", this.isHorizontal() ? tlAxisBBox.height : tlAxisBBox.width)
439
+ .attr(this.isHorizontal() ? "y2" : "x2", this.isHorizontal() ? eventRangeHeight - brAxisBBox.height : width - brAxisBBox.width)
440
+ ;
441
+ lines.exit().remove();
442
+ const buckets = this.gMiddleContent.selectAll(".buckets").data(ranges, d => d[context._title_idx]);
443
+ buckets.enter().append("g")
444
+ .attr("class", "buckets")
445
+ .call(this._selection.enter.bind(this._selection))
446
+ .each(function (d) {
447
+ const entityRect = new EntityRect()
448
+ .target(this)
449
+ .iconDiameter(28)
450
+ .iconPaddingPercent(0)
451
+ .titleFontSize(28)
452
+ .titleColor(context.rangeFontColor())
453
+ .descriptionColor(context.rangeFontColor())
454
+ .iconColor(context.rangeFontColor())
455
+ .backgroundShape("rect")
456
+ .backgroundColorFill(d[context._color_idx])
457
+ ;
458
+ context.localRect.set(this, entityRect);
459
+ context.enterEntityRect(entityRect, d);
460
+ })
461
+ .on("click", function (d) {
462
+ context.click(context.rowToObj(d), "range", context._selection.selected(this));
463
+ }, false)
464
+ .on("dblclick", function (d) {
465
+ context.rootExtent = d;
466
+ context.resetZoom();
467
+ context.dblclick(context.rowToObj(d), "range", context._selection.selected(this));
468
+ }, true)
469
+ .on("mouseout.tooltip", this.tooltip.hide)
470
+ .on("mousemove.tooltip", this.tooltip.show)
471
+ .merge(buckets)
472
+ .attr("transform", d => context.isHorizontal() ?
473
+ `translate(${this.dataStartPos(d)}, ${this.verticalBands(bucketIndex[d])}) ` :
474
+ `translate(${this.verticalBands(bucketIndex[d])}, ${this.dataStartPos(d)}) `)
475
+ .each(function (d) {
476
+ const textBox = context.localRect.get(this);
477
+ const x = context.dataWidth(d) / 2;
478
+ const y = context.verticalBands.bandwidth() / 2;
479
+ const rectWidth = Math.max(context.dataWidth(d), 2);
480
+ const rectHeight = Math.max(context.verticalBands.bandwidth(), 2);
481
+ const fontHeightRatio = 0.618;
482
+ const paddingRatio = ((1 - fontHeightRatio) / 2);
483
+ const paddingSize = paddingRatio * rectHeight;
484
+ const fontSize = rectHeight * fontHeightRatio;
485
+ const iconSize = fontSize;
486
+ textBox
487
+ .pos(context.isHorizontal() ? { x, y } : { x: y, y: x })
488
+ .fixedHeight(context.isHorizontal() ? rectHeight : rectWidth)
489
+ .fixedWidth(context.isHorizontal() ? rectWidth : rectHeight)
490
+ .icon(typeof d[context._icon_idx] !== "undefined" ? d[context._icon_idx] : "")
491
+ .title(typeof d[context._title_idx] !== "undefined" ? d[context._title_idx] : "")
492
+ .padding(paddingSize)
493
+ .iconDiameter(iconSize)
494
+ .titleFontSize(fontSize)
495
+ ;
496
+ if (iconSize * 1.5 > rectWidth) {
497
+ textBox.icon(null);
498
+ }
499
+ context.updateEntityRect(textBox, d[context._icon_idx]);
500
+ textBox
501
+ .render()
502
+ ;
503
+ });
504
+ buckets.exit().remove();
505
+ }
506
+
507
+ exit(domNode, element) {
508
+ this.brAxis.target(null);
509
+ this.tlAxis.target(null);
510
+ super.exit(domNode, element);
511
+ }
512
+
513
+ // Events ---
514
+ click(row, col, sel) {
515
+ }
516
+
517
+ dblclick(row, col, sel) {
518
+ }
519
+
520
+ enterEntityRect(textbox: EntityRect, d) {
521
+ }
522
+
523
+ updateEntityRect(textbox: EntityRect, d) {
524
+ }
525
+ }
526
+ MiniGantt.prototype._class += " timeline_MiniGantt";
527
+ MiniGantt.prototype.implements(ITooltip.prototype);
528
+ MiniGantt.prototype.mixin(Utility.SimpleSelectionMixin);
529
+
530
+ export interface MiniGantt {
531
+ // ITooltip ---
532
+ tooltip;
533
+ tooltipHTML(_): string;
534
+ tooltipFormat(_): string;
535
+
536
+ // SimpleSelectionMixin
537
+ _selection;
538
+
539
+ // Properties ---
540
+ timePattern(): string;
541
+ timePattern(_: string): this;
542
+ tickFormat(): string;
543
+ tickFormat(_: string): this;
544
+ tickFormat_exists(): boolean;
545
+ tooltipTimeFormat(): string;
546
+ tooltipTimeFormat(_: string): this;
547
+ overlapTolerence(): number;
548
+ overlapTolerence(_: number): this;
549
+ orientation(): string;
550
+ orientation(_: string): this;
551
+ rangeFontColor(): string;
552
+ rangeFontColor(_: string): this;
553
+ titleColumn(): string;
554
+ titleColumn(_: string): this;
555
+ startDateColumn(): string;
556
+ startDateColumn(_: string): this;
557
+ endDateColumn(): string;
558
+ endDateColumn(_: string): this;
559
+ iconColumn(): string;
560
+ iconColumn(_: string): this;
561
+ colorColumn(): string;
562
+ colorColumn(_: string): this;
563
+ yOffsetColumn(): string;
564
+ yOffsetColumn(_: string): this;
565
+ maxZoom(): number;
566
+ maxZoom(_: number): this;
567
+ eventGroupMod(): number;
568
+ eventGroupMod(_: number): this;
569
+ eventGroupOffset(): number;
570
+ eventGroupOffset(_: number): this;
571
+ eventFontColorColumn(): string;
572
+ eventFontColorColumn(_: string): this;
573
+ eventBorderColorColumn(): string;
574
+ eventBorderColorColumn(_: string): this;
575
+ eventBackgroundColorColumn(): string;
576
+ eventBackgroundColorColumn(_: string): this;
577
+ hideIconWhenCollapsed(): boolean;
578
+ hideIconWhenCollapsed(_: boolean): this;
579
+ hideTitleWhenCollapsed(): boolean;
580
+ hideTitleWhenCollapsed(_: boolean): this;
581
+ hideDescriptionWhenCollapsed(): boolean;
582
+ hideDescriptionWhenCollapsed(_: boolean): this;
583
+ hideAnnotationsWhenCollapsed(): boolean;
584
+ hideAnnotationsWhenCollapsed(_: boolean): this;
585
+ centerOnMostRecent(): boolean;
586
+ centerOnMostRecent(_: boolean): this;
587
+
588
+ }
589
+
590
+ MiniGantt.prototype.publish("timePattern", "%Y-%m-%d", "string", "timePattern");
591
+ MiniGantt.prototype.publish("tickFormat", null, "string", "tickFormat", undefined, { optional: true });
592
+ MiniGantt.prototype.publish("tooltipTimeFormat", "%Y-%m-%d", "string", "tooltipTimeFormat");
593
+ MiniGantt.prototype.publish("overlapTolerence", 2, "number", "overlapTolerence");
594
+ MiniGantt.prototype.publish("orientation", "horizontal", "set", "orientation", ["horizontal", "vertical"]);
595
+ MiniGantt.prototype.publish("rangeFontColor", "#ecf0f1", "html-color", "rangeFontColor");
596
+ MiniGantt.prototype.publish("titleColumn", null, "string", "titleColumn");
597
+ MiniGantt.prototype.publish("startDateColumn", null, "string", "startDateColumn");
598
+ MiniGantt.prototype.publish("endDateColumn", null, "string", "endDateColumn");
599
+ MiniGantt.prototype.publish("iconColumn", null, "string", "iconColumn");
600
+ MiniGantt.prototype.publish("colorColumn", null, "string", "colorColumn");
601
+ MiniGantt.prototype.publish("yOffsetColumn", null, "string", "yOffsetColumn");
602
+ MiniGantt.prototype.publish("maxZoom", 16, "number", "maxZoom");
603
+ MiniGantt.prototype.publish("eventGroupOffset", -50, "number", "eventGroupOffset");
604
+ MiniGantt.prototype.publish("eventGroupMod", 5, "number", "eventGroupMod");
605
+ MiniGantt.prototype.publish("eventFontColorColumn", null, "string", "eventFontColorColumn");
606
+ MiniGantt.prototype.publish("eventBorderColorColumn", null, "string", "eventBorderColorColumn");
607
+ MiniGantt.prototype.publish("eventBackgroundColorColumn", null, "string", "eventBackgroundColorColumn");
608
+ MiniGantt.prototype.publish("hideIconWhenCollapsed", false, "boolean", "hideIconWhenCollapsed");
609
+ MiniGantt.prototype.publish("hideTitleWhenCollapsed", false, "boolean", "hideTitleWhenCollapsed");
610
+ MiniGantt.prototype.publish("hideDescriptionWhenCollapsed", false, "boolean", "hideDescriptionWhenCollapsed");
611
+ MiniGantt.prototype.publish("hideAnnotationsWhenCollapsed", true, "boolean", "hideAnnotationsWhenCollapsed");
612
+ MiniGantt.prototype.publish("centerOnMostRecent", false, "boolean", "If true, the timeline will be centered on the most recent data point");