@hpcc-js/timeline 2.57.0 → 2.57.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/src/ReactGantt.ts CHANGED
@@ -1,696 +1,696 @@
1
- import { d3Event, select as d3Select, SVGZoomWidget, Utility } from "@hpcc-js/common";
2
- import { HTMLTooltip } from "@hpcc-js/html";
3
- import { scaleLinear as d3ScaleLinear } from "d3-scale";
4
- import { React, render, LabelledRect } from "@hpcc-js/react";
5
-
6
- export type IGanttData = [string, number, number, any?];
7
-
8
- export interface IRangeOptions {
9
- rangePadding: number;
10
- fontFamily: string;
11
- fontSize: number;
12
- strokeWidth?: number;
13
- fill: string;
14
- stroke: string;
15
- textFill: string;
16
- cornerRadius: number;
17
- }
18
-
19
- export class ReactGantt extends SVGZoomWidget {
20
-
21
- protected _selection = new Utility.Selection(this);
22
-
23
- protected _buckets;
24
- protected _interpolateX;
25
- protected _interpolateY;
26
-
27
- protected _bucketsBySeries;
28
- protected _dataBySeries;
29
- protected _origIdxMap;
30
- private _seriesBackgrounds;
31
-
32
- protected _maxFontSize;
33
-
34
- public _tooltip;
35
-
36
- public _minStart: number;
37
- public _maxEnd: number;
38
-
39
- protected _title_idx = 0;
40
- protected _startDate_idx = 1;
41
- protected _endDate_idx = 2;
42
- protected _icon_idx = -1;
43
- protected _color_idx = -1;
44
- protected _series_idx = -1;
45
- protected _bucket_idx = -1;
46
- protected _yoffset_idx = -1;
47
-
48
- protected _maxX: number;
49
- protected _maxY: number;
50
-
51
- private _rangeOptions: IRangeOptions = {
52
- rangePadding: 2,
53
- fontFamily: "Verdana",
54
- fontSize: 12,
55
- fill: "white",
56
- stroke: "black",
57
- textFill: "black",
58
- cornerRadius: 3,
59
- strokeWidth: 0
60
- };
61
-
62
- constructor(drawStartPosition: "origin" | "center" = "origin") {
63
- super();
64
- this._drawStartPos = drawStartPosition;
65
-
66
- this.showToolbar_default(false);
67
-
68
- this._tooltip = new HTMLTooltip();
69
- this._tooltip
70
- .tooltipHTML(d => {
71
- return `<div style="text-align:center;">${d[0]}<br/><br/>${d[1]} -&gt; ${d[2]}</div>`;
72
- });
73
- this._tooltip
74
- .followCursor(true)
75
- ;
76
- }
77
-
78
- selection(_: any[]): this;
79
- selection(): any[];
80
- selection(_?: any[]): any[] | this {
81
- if (!arguments.length) return this._selection.get();
82
- this._selection.set(_);
83
- return this;
84
- }
85
-
86
- private _rangeRenderer: React.FunctionComponent = LabelledRect;
87
- rangeRenderer(): React.FunctionComponent;
88
- rangeRenderer(_: React.FunctionComponent): this;
89
- rangeRenderer(_?: React.FunctionComponent): this | React.FunctionComponent {
90
- if (!arguments.length) return this._rangeRenderer;
91
- this._rangeRenderer = _;
92
- return this._rangeRenderer;
93
- }
94
-
95
- enter(domNode, element) {
96
- super.enter(domNode, element);
97
-
98
- const context = this;
99
- element
100
- .on("click", function (this: SVGElement, d) {
101
- context._selection.clear();
102
- });
103
-
104
- this._tooltip.target(domNode);
105
- }
106
- update(domNode, element) {
107
- super.update(domNode, element);
108
-
109
- this.zoomExtent([0.05, this.maxZoom()]);
110
-
111
- this._title_idx = this.titleColumn() !== null ? this.columns().indexOf(this.titleColumn()) : this._title_idx;
112
- this._startDate_idx = this.startDateColumn() !== null ? this.columns().indexOf(this.startDateColumn()) : this._startDate_idx;
113
- this._endDate_idx = this.endDateColumn() !== null ? this.columns().indexOf(this.endDateColumn()) : this._endDate_idx;
114
- this._icon_idx = this.iconColumn() !== null ? this.columns().indexOf(this.iconColumn()) : this._icon_idx;
115
- this._color_idx = this.colorColumn() !== null ? this.columns().indexOf(this.colorColumn()) : this._color_idx;
116
- this._series_idx = this.seriesColumn() !== null ? this.columns().indexOf(this.seriesColumn()) : this._series_idx;
117
- this._bucket_idx = this.bucketColumn() !== null ? this.columns().indexOf(this.bucketColumn()) : -1;
118
-
119
- const context = this;
120
- const w = this.width();
121
-
122
- const x0 = 0;
123
- const x1 = w;
124
-
125
- this._interpolateX = d3ScaleLinear()
126
- .domain([this._minStart, this._maxEnd])
127
- .range([x0, x1])
128
- ;
129
-
130
- this.data().sort((a, b) => a[1] - b[1]);
131
-
132
- if (this._series_idx > -1) {
133
- this._origIdxMap = {};
134
- this._dataBySeries = {};
135
- this._bucketsBySeries = {};
136
- this.data().forEach((dataRow, origIdx) => {
137
- const seriesKey = dataRow[this._series_idx];
138
- if (!this._dataBySeries[seriesKey]) {
139
- this._origIdxMap[seriesKey] = {};
140
- this._dataBySeries[seriesKey] = [];
141
- }
142
- this._dataBySeries[seriesKey].push({
143
- dataRow,
144
- origIdx
145
- });
146
- });
147
- const gutter = this.gutter();
148
- let bucketOffset = 0;
149
- const seriesKeys = Object.keys(this._dataBySeries);
150
- seriesKeys.forEach(seriesKey => {
151
- this._dataBySeries[seriesKey].sort((a, b) => a.dataRow[1] - b.dataRow[1]);
152
- this._bucketsBySeries[seriesKey] = this.calcBuckets(this._dataBySeries[seriesKey].map(n => n.dataRow), 1, 2);
153
- this._bucketsBySeries[seriesKey].bucketHeight = this.bucketHeight();
154
- this._bucketsBySeries[seriesKey].bucketOffset = bucketOffset;
155
- bucketOffset += (this._bucketsBySeries[seriesKey].bucketHeight + this.strokeWidth() + this.gutter()) * (this._bucketsBySeries[seriesKey].maxBucket + 1);
156
- this._dataBySeries[seriesKey].forEach((n, i) => {
157
- this._origIdxMap[seriesKey][n.origIdx] = i;
158
- });
159
- });
160
- this._seriesBackgrounds = this._renderElement.selectAll(".series-background")
161
- .data(seriesKeys.map(key => {
162
- return this._bucketsBySeries[key];
163
- }))
164
- ;
165
- this._seriesBackgrounds
166
- .join(
167
- enter => enter.append("rect")
168
- .attr("class", "series-background"),
169
- update => update,
170
- exit => exit
171
- .each(function (d) {
172
- delete d.element;
173
- })
174
- .remove()
175
- )
176
- .attr("opacity", d => d.props && d.props.hidden ? 0 : 1)
177
- .each(function (this: SVGGElement, d, i) {
178
- d3Select(this)
179
- .attr("x", 0)
180
- .attr("y", d.bucketOffset - (gutter / 2))
181
- .attr("width", w)
182
- .attr("height", ((d.bucketHeight + gutter) * (d.maxBucket + 1)) + gutter)
183
- .attr("fill", i % 2 ? context.oddSeriesBackground() : context.evenSeriesBackground())
184
- ;
185
- });
186
- } else {
187
- if (this._bucket_idx !== -1) {
188
- this._buckets = this.calcBuckets(this.data(), this._startDate_idx, this._endDate_idx, this._bucket_idx);
189
- } else {
190
- this._buckets = this.calcBuckets(this.data(), this._startDate_idx, this._endDate_idx);
191
- }
192
- }
193
- const interpedStart = this._interpolateX(this._minStart);
194
-
195
- this.zoomTo(
196
- [interpedStart, 0],
197
- 1
198
- );
199
-
200
- const bucketHeight = this.bucketHeight();
201
-
202
- this.setRangeOptions();
203
-
204
- this._maxFontScale = (bucketHeight - (this.rangePadding() * 2));
205
- this.measureDataText();
206
-
207
- const itemSelection = this._renderElement.selectAll(".item")
208
- .data(this.data())
209
- ;
210
- const borderOffset1 = this.strokeWidth();
211
- const borderOffset2 = borderOffset1 * 2;
212
- itemSelection
213
- .join(
214
- enter => enter.append("g")
215
- .attr("class", "item")
216
- .on("click.selectionBag", function (d, i) {
217
- const _id = d.id === undefined ? i : d.id;
218
- if (context._selection.isSelected({ _id, element: d.element })) {
219
- context._selection.clear();
220
- } else {
221
- context._selection.click(
222
- {
223
- _id,
224
- element: () => d.element
225
- },
226
- d3Event
227
- );
228
- }
229
- context.selectionChanged();
230
- d3Event().stopPropagation();
231
- })
232
- .on("click", function (this: SVGElement, d) {
233
- const selected = d.element.classed("selected");
234
- if (d[context.columns().length]) {
235
- d.__lparam = d[context.columns().length];
236
- }
237
- context.click(d, "", selected);
238
- })
239
- .on("dblclick", function (this: SVGElement, d) {
240
- const selected = d.element.classed("selected");
241
- if (d[context.columns().length]) {
242
- d.__lparam = d[context.columns().length];
243
- }
244
- context.click(d, "", selected);
245
- })
246
- .on("mousein", function (d) {
247
- context.highlightItem(d3Select(this), d);
248
- const selected = d.element.classed("selected");
249
- context.mousein(d, "", selected);
250
- })
251
- .on("mouseover", function (d) {
252
- const d3evt = d3Event();
253
- context._tooltip._triggerElement = d.element;
254
- context._tooltip._cursorLoc = [
255
- d3evt.clientX,
256
- d3evt.clientY
257
- ];
258
- context._tooltip
259
- .data(d)
260
- .visible(true)
261
- .fitContent(true)
262
- .render()
263
- ;
264
- context.highlightItem(d3Select(this), d);
265
- const selected = d.element.classed("selected");
266
- context.mouseover(d, "", selected);
267
- })
268
- .on("mouseout", function (d) {
269
- context._tooltip
270
- .visible(false)
271
- .render()
272
- ;
273
- context.highlightItem(null, null);
274
- const selected = d.element.classed("selected");
275
- context.mouseout(d, "", selected);
276
- })
277
- .each(function (d, i) {
278
- d.that = this;
279
- d.element = d3Select(this);
280
- d.x = context._interpolateX(d[1]);
281
- const endX = context._interpolateX(d[2]);
282
- if (context._series_idx > -1) {
283
- const seriesKey = d[context._series_idx];
284
- const bucket = context._bucketsBySeries[seriesKey].bucketMap[context._origIdxMap[seriesKey][i]];
285
- d.y = context._bucketsBySeries[seriesKey].interpolateY(bucket) + context._bucketsBySeries[seriesKey].bucketOffset;
286
- } else {
287
- const _i = context._bucket_idx === -1 ? i : d[context._bucket_idx];
288
- d.y = context._buckets.interpolateY(context._buckets.bucketMap[_i]);
289
- }
290
- d.props = {
291
- ...d[3],
292
- text: d[0]
293
- };
294
- d.props.width = endX - d.x;
295
- d.props.height = bucketHeight;
296
- d.x += borderOffset1;
297
- d.y += borderOffset1;
298
- d.props.width -= borderOffset2;
299
- d.props.height -= borderOffset2;
300
- d.element.attr("transform", `translate(${d.x + (d.props.width / 2)} ${d.y + (d.props.height / 2)})`);
301
- }),
302
- update => update,
303
- exit => exit
304
- .each(function (d) {
305
- delete d.element;
306
- })
307
- .remove()
308
- )
309
- .attr("opacity", d => d.props && d.props.hidden ? 0 : 1)
310
- .each(function (this: SVGGElement, d, i) {
311
- d.that = this;
312
- if (context._series_idx > -1) {
313
- const seriesKey = d[context._series_idx];
314
- d.x = context.renderRangeElement(d, i, false, context._rangeOptions, seriesKey);
315
- } else {
316
- d.x = context.renderRangeElement(d, i, false, context._rangeOptions);
317
- }
318
- })
319
- .on("dblclick.zoom", d => {
320
- const x1 = this._interpolateX(d[1]);
321
- const x2 = this._interpolateX(d[2]);
322
- const xRange = x2 - x1;
323
- const xScale = w / xRange;
324
- this.zoomTo(
325
- [
326
- -x1 * xScale,
327
- 0
328
- ],
329
- xScale
330
- );
331
- })
332
- ;
333
- element.on("dblclick.zoom", null);
334
- }
335
- renderRangeElement(d, i, transformEach = false, options: any = {}, seriesKey?: string) {
336
- const borderOffset1 = options.strokeWidth;
337
- const borderOffset2 = borderOffset1 * 2;
338
- const padding = options.rangePadding;
339
- let endX;
340
- const x = isNaN(this._transform.x) ? 0 : this._transform.x;
341
- const k = isNaN(this._transform.k) ? 1 : this._transform.k;
342
- let b;
343
- const bucketHeight = this.bucketHeight();
344
- d.that.setAttribute("data-series", seriesKey);
345
-
346
- if (this._color_idx > -1) {
347
- d.that.setAttribute("data-color", d[this._color_idx]);
348
- }
349
-
350
- if (seriesKey !== undefined) {
351
- b = this._bucketsBySeries[seriesKey].bucketMap[this._origIdxMap[seriesKey][i]];
352
- d.that.setAttribute("data-b", b);
353
- d.that.setAttribute("data-bucketOffset", this._bucketsBySeries[seriesKey].bucketOffset);
354
- d.y = this._bucketsBySeries[seriesKey].interpolateY(b) + this._bucketsBySeries[seriesKey].bucketOffset;
355
- d.that.setAttribute("data-dy", d.y);
356
- } else {
357
- b = this._buckets.bucketMap[i];
358
- d.y = this._buckets.interpolateY(b);
359
- }
360
- if (this._color_idx > -1) {
361
- options.fill = d[this._color_idx];
362
- }
363
- if (!transformEach) {
364
- d.x = this._interpolateX(d[1]);
365
- endX = this._interpolateX(d[2]);
366
- d.props = {
367
- ...d[3],
368
- text: d[0]
369
- };
370
- d.props.width = (endX - d.x) / k;
371
- } else {
372
- d.x = this._interpolateX(d[1]) * k;
373
- endX = this._interpolateX(d[2]) * k;
374
- d.props = {
375
- ...d[3],
376
- text: d[0]
377
- };
378
- d.props.width = (endX - d.x) / k;
379
- d.x += x;
380
- d.props.width *= k;
381
- }
382
- d.props.height = bucketHeight;
383
- if (seriesKey === undefined && this._buckets.bucketScale < 1) {
384
- d.props.height = this._buckets.bucketScale * bucketHeight;
385
- }
386
- if (d.element === undefined && d.that) {
387
- d.element = d3Select(d.that);
388
- }
389
- d.element.attr("transform", `translate(${d.x + (d.props.width / 2)} ${d.y + (d.props.height / 2)})`);
390
-
391
- d.x += borderOffset1;
392
- d.y += borderOffset1;
393
- d.props.width -= borderOffset2;
394
- d.props.height -= borderOffset2;
395
- d.props.width = d.props.width < 1 ? 1 : d.props.width;
396
- d.props.height = d.props.height < 1 ? 1 : d.props.height;
397
-
398
- let text = this.truncateText(d.props.text, d.props.width - padding, this._maxFontScale);
399
-
400
- if (text !== d.props.text) {
401
- text = this.truncateText(d.props.text, d.props.width - padding);
402
- } else {
403
- d.props.fontSize = this._maxFontScale * options.fontSize;
404
- }
405
- if (seriesKey === undefined && this._buckets.bucketScale < 1) {
406
- d.props.fontSize = Math.min(this._maxFontScale, this._buckets.bucketScale) * options.fontSize;
407
- }
408
- if (!this._maxY || this._maxY < d.y + d.props.height) {
409
- this._maxY = d.y + d.props.height;
410
- }
411
- if (!this._maxX || this._maxX < d.x + d.props.width) {
412
- this._maxX = d.x + d.props.width;
413
- }
414
- render(
415
- this._rangeRenderer,
416
- {
417
- ...options,
418
- ...d.props,
419
- text,
420
- },
421
- d.that
422
- );
423
- }
424
-
425
- setRangeOptions() {
426
- this._rangeOptions = {
427
- rangePadding: this.rangePadding(),
428
- fontFamily: this.fontFamily(),
429
- fontSize: this.fontSize(),
430
- strokeWidth: this.strokeWidth(),
431
- fill: this.fill(),
432
- stroke: this.stroke(),
433
- textFill: this.rangeFontColor(),
434
- cornerRadius: this.cornerRadius(),
435
- };
436
- }
437
-
438
- public _transform = { k: 1, x: 0, y: 0 };
439
- zoomed(transform) {
440
- this._transform = transform;
441
- switch (this.renderMode()) {
442
- case "scale-all":
443
- this._zoomScale = transform.k;
444
- this._zoomTranslate = [transform.x, 0];
445
- this._zoomG.attr("transform", `translate(${transform.x} ${0})scale(${transform.k} 1)`);
446
- break;
447
- default:
448
- const options = this._rangeOptions;
449
- this.data().forEach((d, i) => {
450
- if (this._color_idx > -1) {
451
- options.fill = d[this._color_idx];
452
- }
453
- if (this._series_idx > -1) {
454
- const seriesKey = d[this._series_idx];
455
- this.renderRangeElement(d, i, true, options, seriesKey);
456
- } else {
457
- this.renderRangeElement(d, i, true, options);
458
- }
459
- });
460
- }
461
-
462
- this.zoomedHook(transform);
463
- }
464
-
465
- zoomedHook(transform) {
466
-
467
- }
468
-
469
- private calcBuckets(data, startKey: string | number, endKey: string | number, bucketKey?: string | number) {
470
- const bucketMap = {};
471
- const bucketKeyMap = {};
472
- const tol = this.overlapTolerence();
473
- const buckets = [{ end: -Infinity }];
474
- let maxBucket = 0;
475
- if (bucketKey !== undefined) {
476
- data.forEach((d, i) => {
477
- bucketMap[i] = d[bucketKey];
478
- bucketKeyMap[d[bucketKey]] = true;
479
- });
480
- maxBucket = Object.keys(bucketKeyMap).length;
481
- } else {
482
- data.forEach((d, i) => {
483
- for (let i2 = 0; i2 < buckets.length; ++i2) {
484
- if (i === 0 || buckets[i2][endKey] + tol <= d[startKey]) {
485
- bucketMap[i] = i2;
486
- if (maxBucket < i2) maxBucket = i2;
487
- buckets[i2][endKey] = d[endKey];
488
- break;
489
- }
490
- }
491
- if (bucketMap[i] === undefined) {
492
- bucketMap[i] = buckets.length;
493
- const b = {};
494
- b[endKey] = d[endKey];
495
- buckets.push(b as any);
496
- }
497
-
498
- if (maxBucket < bucketMap[i]) maxBucket = bucketMap[i];
499
- });
500
- }
501
- const height = (maxBucket + 1) * (this.bucketHeight() + this.gutter());
502
- return {
503
- bucketMap,
504
- maxBucket,
505
- bucketScale: this.height() / height,
506
- interpolateY: d3ScaleLinear()
507
- .domain([0, maxBucket + 1])
508
- .range([0, Math.min(this.height(), height)])
509
- };
510
- }
511
-
512
- data(): IGanttData[];
513
- data(_: IGanttData[]): this;
514
- data(_?: IGanttData[]): this | IGanttData[] {
515
- const retVal = super.data.apply(this, arguments);
516
- if (arguments.length > 0) {
517
- this._minStart = Math.min(...this.data().map(n => n[1])) ?? 0;
518
- this._maxEnd = Math.max(...this.data().map(n => n[2])) ?? 1;
519
- this.measureDataText(true);
520
- }
521
- return retVal;
522
- }
523
-
524
- protected _textWidths;
525
- protected _maxFontScale;
526
- protected _characterWidths;
527
- protected _prevFontFamily;
528
- protected _prevFontSize;
529
- measureDataText(forceMeasure = false) {
530
- const textWidths = {};
531
- const characterWidths = {};
532
- const fontFamily = this.fontFamily();
533
- const fontSize = this.fontSize();
534
- const bucketHeight = this.bucketHeight();
535
-
536
- if (bucketHeight) {
537
- this._maxFontScale = (bucketHeight - (this.rangePadding() * 2)) / fontSize;
538
- }
539
-
540
- if (forceMeasure || this._prevFontFamily !== fontFamily || this._prevFontSize !== fontSize) {
541
- characterWidths["."] = Utility.textSize(".", fontFamily, fontSize).width;
542
- this.data().forEach(d => {
543
- if (!textWidths[d[0]]) {
544
- textWidths[d[0]] = Utility.textSize(d[0], fontFamily, fontSize).width;
545
- }
546
- d[0].split("").forEach(char => {
547
- if (!characterWidths[char]) {
548
- characterWidths[char] = Utility.textSize(char, fontFamily, fontSize).width;
549
- }
550
- });
551
- });
552
- this._textWidths = textWidths;
553
- this._characterWidths = characterWidths;
554
- }
555
- this._prevFontFamily = fontFamily;
556
- this._prevFontSize = fontSize;
557
- }
558
-
559
- truncateText(text, width, scale = 1) {
560
- const textFits = this._textWidths[text] * scale < width;
561
- if (textFits) {
562
- return text;
563
- }
564
- let ret = "";
565
- let sum = 0;
566
- const _width = width - (this._characterWidths["."] * 3);
567
- for (const char of text) {
568
- sum += this._characterWidths[char];
569
- if (sum < _width) {
570
- ret += char;
571
- } else {
572
- break;
573
- }
574
- }
575
- return _width < 0 ? "" : ret + "...";
576
- }
577
-
578
- resize(_size?: { width: number, height: number }) {
579
- let retVal;
580
- if (this.fitWidthToContent() || this.fitHeightToContent()) {
581
- retVal = super.resize.call(this, {
582
- width: _size.width,
583
- height: this._maxY
584
- });
585
- } else {
586
- retVal = super.resize.apply(this, arguments);
587
- }
588
- return retVal;
589
- }
590
-
591
- selectionChanged() {
592
-
593
- }
594
-
595
- highlightItem(_element, d) {
596
-
597
- }
598
-
599
- click(row, _col, sel) {
600
-
601
- }
602
-
603
- dblclick(row, _col, sel) {
604
-
605
- }
606
-
607
- mousein(row, _col, sel) {
608
- }
609
-
610
- mouseover(row, _col, sel) {
611
- }
612
-
613
- mouseout(row, _col, sel) {
614
- }
615
- }
616
- ReactGantt.prototype._class += " timeline_ReactGantt";
617
-
618
- export interface ReactGantt {
619
- titleColumn(): string;
620
- titleColumn(_: string): this;
621
- startDateColumn(): string;
622
- startDateColumn(_: string): this;
623
- endDateColumn(): string;
624
- endDateColumn(_: string): this;
625
- iconColumn(): string;
626
- iconColumn(_: string): this;
627
- colorColumn(): string;
628
- colorColumn(_: string): this;
629
- seriesColumn(): string;
630
- seriesColumn(_: string): this;
631
- bucketColumn(): string;
632
- bucketColumn(_: string): this;
633
- overlapTolerence(): number;
634
- overlapTolerence(_: number): this;
635
- smallestRangeWidth(): number;
636
- smallestRangeWidth(_: number): this;
637
- bucketHeight(): number;
638
- bucketHeight(_: number): this;
639
- gutter(): number;
640
- gutter(_: number): this;
641
- showToolbar_default(_: boolean): this;
642
- fontSize(): number;
643
- fontSize(_: number): this;
644
- fontFamily(): string;
645
- fontFamily(_: string): this;
646
- strokeWidth(): number;
647
- strokeWidth(_: number): this;
648
- stroke(): string;
649
- stroke(_: string): this;
650
- cornerRadius(): number;
651
- cornerRadius(_: number): this;
652
- fill(): string;
653
- fill(_: string): this;
654
- rangeFontColor(): string;
655
- rangeFontColor(_: string): this;
656
- rangePadding(): number;
657
- rangePadding(_: number): this;
658
- renderMode(): "default" | "scale-all";
659
- renderMode(_: "default" | "scale-all"): this;
660
- maxZoom(): number;
661
- maxZoom(_: number): this;
662
- fitWidthToContent(): boolean;
663
- fitWidthToContent(_: boolean): this;
664
- fitHeightToContent(): boolean;
665
- fitHeightToContent(_: boolean): this;
666
- evenSeriesBackground(): string;
667
- evenSeriesBackground(_: string): this;
668
- oddSeriesBackground(): string;
669
- oddSeriesBackground(_: string): this;
670
- }
671
-
672
- ReactGantt.prototype.publish("fitWidthToContent", false, "boolean", "If true, resize will simply reapply the bounding box width");
673
- ReactGantt.prototype.publish("fitHeightToContent", false, "boolean", "If true, resize will simply reapply the bounding box height");
674
- ReactGantt.prototype.publish("titleColumn", null, "string", "Column name to for the title");
675
- ReactGantt.prototype.publish("startDateColumn", null, "string", "Column name to for the start date");
676
- ReactGantt.prototype.publish("endDateColumn", null, "string", "Column name to for the end date");
677
- ReactGantt.prototype.publish("iconColumn", null, "string", "Column name to for the icon");
678
- ReactGantt.prototype.publish("colorColumn", null, "string", "Column name to for the color");
679
- ReactGantt.prototype.publish("seriesColumn", null, "string", "Column name to for the series identifier");
680
- ReactGantt.prototype.publish("bucketColumn", null, "string", "Column name to for the bucket identifier");
681
- ReactGantt.prototype.publish("renderMode", "default", "set", "Render modes vary in features and performance", ["default", "scale-all"]);
682
- ReactGantt.prototype.publish("rangePadding", 3, "number", "Padding within each range rectangle (pixels)");
683
- ReactGantt.prototype.publish("fill", "#1f77b4", "string", "Background color of range rectangle");
684
- ReactGantt.prototype.publish("stroke", null, "string", "Color of range rectangle border");
685
- ReactGantt.prototype.publish("strokeWidth", null, "number", "Width of range rectangle border (pixels)");
686
- ReactGantt.prototype.publish("cornerRadius", 3, "number", "Space between range buckets (pixels)");
687
- ReactGantt.prototype.publish("fontFamily", null, "string", "Font family within range rectangle", null, { optional: true });
688
- ReactGantt.prototype.publish("fontSize", 10, "number", "Size of font within range rectangle (pixels)");
689
- ReactGantt.prototype.publish("rangeFontColor", "#ecf0f1", "html-color", "rangeFontColor");
690
- ReactGantt.prototype.publish("overlapTolerence", 2, "number", "overlapTolerence");
691
- ReactGantt.prototype.publish("smallestRangeWidth", 10, "number", "Width of the shortest range (pixels)");
692
- ReactGantt.prototype.publish("bucketHeight", 100, "number", "Max height of range element (pixels)");
693
- ReactGantt.prototype.publish("gutter", 2, "number", "Space between range buckets (pixels)");
694
- ReactGantt.prototype.publish("maxZoom", 16, "number", "Maximum zoom");
695
- ReactGantt.prototype.publish("evenSeriesBackground", "#FFFFFF", "html-color", "Background color of even series rows");
696
- ReactGantt.prototype.publish("oddSeriesBackground", "#DDDDDD", "html-color", "Background color of odd series rows");
1
+ import { d3Event, select as d3Select, SVGZoomWidget, Utility } from "@hpcc-js/common";
2
+ import { HTMLTooltip } from "@hpcc-js/html";
3
+ import { scaleLinear as d3ScaleLinear } from "d3-scale";
4
+ import { React, render, LabelledRect } from "@hpcc-js/react";
5
+
6
+ export type IGanttData = [string, number, number, any?];
7
+
8
+ export interface IRangeOptions {
9
+ rangePadding: number;
10
+ fontFamily: string;
11
+ fontSize: number;
12
+ strokeWidth?: number;
13
+ fill: string;
14
+ stroke: string;
15
+ textFill: string;
16
+ cornerRadius: number;
17
+ }
18
+
19
+ export class ReactGantt extends SVGZoomWidget {
20
+
21
+ protected _selection = new Utility.Selection(this);
22
+
23
+ protected _buckets;
24
+ protected _interpolateX;
25
+ protected _interpolateY;
26
+
27
+ protected _bucketsBySeries;
28
+ protected _dataBySeries;
29
+ protected _origIdxMap;
30
+ private _seriesBackgrounds;
31
+
32
+ protected _maxFontSize;
33
+
34
+ public _tooltip;
35
+
36
+ public _minStart: number;
37
+ public _maxEnd: number;
38
+
39
+ protected _title_idx = 0;
40
+ protected _startDate_idx = 1;
41
+ protected _endDate_idx = 2;
42
+ protected _icon_idx = -1;
43
+ protected _color_idx = -1;
44
+ protected _series_idx = -1;
45
+ protected _bucket_idx = -1;
46
+ protected _yoffset_idx = -1;
47
+
48
+ protected _maxX: number;
49
+ protected _maxY: number;
50
+
51
+ private _rangeOptions: IRangeOptions = {
52
+ rangePadding: 2,
53
+ fontFamily: "Verdana",
54
+ fontSize: 12,
55
+ fill: "white",
56
+ stroke: "black",
57
+ textFill: "black",
58
+ cornerRadius: 3,
59
+ strokeWidth: 0
60
+ };
61
+
62
+ constructor(drawStartPosition: "origin" | "center" = "origin") {
63
+ super();
64
+ this._drawStartPos = drawStartPosition;
65
+
66
+ this.showToolbar_default(false);
67
+
68
+ this._tooltip = new HTMLTooltip();
69
+ this._tooltip
70
+ .tooltipHTML(d => {
71
+ return `<div style="text-align:center;">${d[0]}<br/><br/>${d[1]} -&gt; ${d[2]}</div>`;
72
+ });
73
+ this._tooltip
74
+ .followCursor(true)
75
+ ;
76
+ }
77
+
78
+ selection(_: any[]): this;
79
+ selection(): any[];
80
+ selection(_?: any[]): any[] | this {
81
+ if (!arguments.length) return this._selection.get();
82
+ this._selection.set(_);
83
+ return this;
84
+ }
85
+
86
+ private _rangeRenderer: React.FunctionComponent = LabelledRect;
87
+ rangeRenderer(): React.FunctionComponent;
88
+ rangeRenderer(_: React.FunctionComponent): this;
89
+ rangeRenderer(_?: React.FunctionComponent): this | React.FunctionComponent {
90
+ if (!arguments.length) return this._rangeRenderer;
91
+ this._rangeRenderer = _;
92
+ return this._rangeRenderer;
93
+ }
94
+
95
+ enter(domNode, element) {
96
+ super.enter(domNode, element);
97
+
98
+ const context = this;
99
+ element
100
+ .on("click", function (this: SVGElement, d) {
101
+ context._selection.clear();
102
+ });
103
+
104
+ this._tooltip.target(domNode);
105
+ }
106
+ update(domNode, element) {
107
+ super.update(domNode, element);
108
+
109
+ this.zoomExtent([0.05, this.maxZoom()]);
110
+
111
+ this._title_idx = this.titleColumn() !== null ? this.columns().indexOf(this.titleColumn()) : this._title_idx;
112
+ this._startDate_idx = this.startDateColumn() !== null ? this.columns().indexOf(this.startDateColumn()) : this._startDate_idx;
113
+ this._endDate_idx = this.endDateColumn() !== null ? this.columns().indexOf(this.endDateColumn()) : this._endDate_idx;
114
+ this._icon_idx = this.iconColumn() !== null ? this.columns().indexOf(this.iconColumn()) : this._icon_idx;
115
+ this._color_idx = this.colorColumn() !== null ? this.columns().indexOf(this.colorColumn()) : this._color_idx;
116
+ this._series_idx = this.seriesColumn() !== null ? this.columns().indexOf(this.seriesColumn()) : this._series_idx;
117
+ this._bucket_idx = this.bucketColumn() !== null ? this.columns().indexOf(this.bucketColumn()) : -1;
118
+
119
+ const context = this;
120
+ const w = this.width();
121
+
122
+ const x0 = 0;
123
+ const x1 = w;
124
+
125
+ this._interpolateX = d3ScaleLinear()
126
+ .domain([this._minStart, this._maxEnd])
127
+ .range([x0, x1])
128
+ ;
129
+
130
+ this.data().sort((a, b) => a[1] - b[1]);
131
+
132
+ if (this._series_idx > -1) {
133
+ this._origIdxMap = {};
134
+ this._dataBySeries = {};
135
+ this._bucketsBySeries = {};
136
+ this.data().forEach((dataRow, origIdx) => {
137
+ const seriesKey = dataRow[this._series_idx];
138
+ if (!this._dataBySeries[seriesKey]) {
139
+ this._origIdxMap[seriesKey] = {};
140
+ this._dataBySeries[seriesKey] = [];
141
+ }
142
+ this._dataBySeries[seriesKey].push({
143
+ dataRow,
144
+ origIdx
145
+ });
146
+ });
147
+ const gutter = this.gutter();
148
+ let bucketOffset = 0;
149
+ const seriesKeys = Object.keys(this._dataBySeries);
150
+ seriesKeys.forEach(seriesKey => {
151
+ this._dataBySeries[seriesKey].sort((a, b) => a.dataRow[1] - b.dataRow[1]);
152
+ this._bucketsBySeries[seriesKey] = this.calcBuckets(this._dataBySeries[seriesKey].map(n => n.dataRow), 1, 2);
153
+ this._bucketsBySeries[seriesKey].bucketHeight = this.bucketHeight();
154
+ this._bucketsBySeries[seriesKey].bucketOffset = bucketOffset;
155
+ bucketOffset += (this._bucketsBySeries[seriesKey].bucketHeight + this.strokeWidth() + this.gutter()) * (this._bucketsBySeries[seriesKey].maxBucket + 1);
156
+ this._dataBySeries[seriesKey].forEach((n, i) => {
157
+ this._origIdxMap[seriesKey][n.origIdx] = i;
158
+ });
159
+ });
160
+ this._seriesBackgrounds = this._renderElement.selectAll(".series-background")
161
+ .data(seriesKeys.map(key => {
162
+ return this._bucketsBySeries[key];
163
+ }))
164
+ ;
165
+ this._seriesBackgrounds
166
+ .join(
167
+ enter => enter.append("rect")
168
+ .attr("class", "series-background"),
169
+ update => update,
170
+ exit => exit
171
+ .each(function (d) {
172
+ delete d.element;
173
+ })
174
+ .remove()
175
+ )
176
+ .attr("opacity", d => d.props && d.props.hidden ? 0 : 1)
177
+ .each(function (this: SVGGElement, d, i) {
178
+ d3Select(this)
179
+ .attr("x", 0)
180
+ .attr("y", d.bucketOffset - (gutter / 2))
181
+ .attr("width", w)
182
+ .attr("height", ((d.bucketHeight + gutter) * (d.maxBucket + 1)) + gutter)
183
+ .attr("fill", i % 2 ? context.oddSeriesBackground() : context.evenSeriesBackground())
184
+ ;
185
+ });
186
+ } else {
187
+ if (this._bucket_idx !== -1) {
188
+ this._buckets = this.calcBuckets(this.data(), this._startDate_idx, this._endDate_idx, this._bucket_idx);
189
+ } else {
190
+ this._buckets = this.calcBuckets(this.data(), this._startDate_idx, this._endDate_idx);
191
+ }
192
+ }
193
+ const interpedStart = this._interpolateX(this._minStart);
194
+
195
+ this.zoomTo(
196
+ [interpedStart, 0],
197
+ 1
198
+ );
199
+
200
+ const bucketHeight = this.bucketHeight();
201
+
202
+ this.setRangeOptions();
203
+
204
+ this._maxFontScale = (bucketHeight - (this.rangePadding() * 2));
205
+ this.measureDataText();
206
+
207
+ const itemSelection = this._renderElement.selectAll(".item")
208
+ .data(this.data())
209
+ ;
210
+ const borderOffset1 = this.strokeWidth();
211
+ const borderOffset2 = borderOffset1 * 2;
212
+ itemSelection
213
+ .join(
214
+ enter => enter.append("g")
215
+ .attr("class", "item")
216
+ .on("click.selectionBag", function (d, i) {
217
+ const _id = d.id === undefined ? i : d.id;
218
+ if (context._selection.isSelected({ _id, element: d.element })) {
219
+ context._selection.clear();
220
+ } else {
221
+ context._selection.click(
222
+ {
223
+ _id,
224
+ element: () => d.element
225
+ },
226
+ d3Event
227
+ );
228
+ }
229
+ context.selectionChanged();
230
+ d3Event().stopPropagation();
231
+ })
232
+ .on("click", function (this: SVGElement, d) {
233
+ const selected = d.element.classed("selected");
234
+ if (d[context.columns().length]) {
235
+ d.__lparam = d[context.columns().length];
236
+ }
237
+ context.click(d, "", selected);
238
+ })
239
+ .on("dblclick", function (this: SVGElement, d) {
240
+ const selected = d.element.classed("selected");
241
+ if (d[context.columns().length]) {
242
+ d.__lparam = d[context.columns().length];
243
+ }
244
+ context.click(d, "", selected);
245
+ })
246
+ .on("mousein", function (d) {
247
+ context.highlightItem(d3Select(this), d);
248
+ const selected = d.element.classed("selected");
249
+ context.mousein(d, "", selected);
250
+ })
251
+ .on("mouseover", function (d) {
252
+ const d3evt = d3Event();
253
+ context._tooltip._triggerElement = d.element;
254
+ context._tooltip._cursorLoc = [
255
+ d3evt.clientX,
256
+ d3evt.clientY
257
+ ];
258
+ context._tooltip
259
+ .data(d)
260
+ .visible(true)
261
+ .fitContent(true)
262
+ .render()
263
+ ;
264
+ context.highlightItem(d3Select(this), d);
265
+ const selected = d.element.classed("selected");
266
+ context.mouseover(d, "", selected);
267
+ })
268
+ .on("mouseout", function (d) {
269
+ context._tooltip
270
+ .visible(false)
271
+ .render()
272
+ ;
273
+ context.highlightItem(null, null);
274
+ const selected = d.element.classed("selected");
275
+ context.mouseout(d, "", selected);
276
+ })
277
+ .each(function (d, i) {
278
+ d.that = this;
279
+ d.element = d3Select(this);
280
+ d.x = context._interpolateX(d[1]);
281
+ const endX = context._interpolateX(d[2]);
282
+ if (context._series_idx > -1) {
283
+ const seriesKey = d[context._series_idx];
284
+ const bucket = context._bucketsBySeries[seriesKey].bucketMap[context._origIdxMap[seriesKey][i]];
285
+ d.y = context._bucketsBySeries[seriesKey].interpolateY(bucket) + context._bucketsBySeries[seriesKey].bucketOffset;
286
+ } else {
287
+ const _i = context._bucket_idx === -1 ? i : d[context._bucket_idx];
288
+ d.y = context._buckets.interpolateY(context._buckets.bucketMap[_i]);
289
+ }
290
+ d.props = {
291
+ ...d[3],
292
+ text: d[0]
293
+ };
294
+ d.props.width = endX - d.x;
295
+ d.props.height = bucketHeight;
296
+ d.x += borderOffset1;
297
+ d.y += borderOffset1;
298
+ d.props.width -= borderOffset2;
299
+ d.props.height -= borderOffset2;
300
+ d.element.attr("transform", `translate(${d.x + (d.props.width / 2)} ${d.y + (d.props.height / 2)})`);
301
+ }),
302
+ update => update,
303
+ exit => exit
304
+ .each(function (d) {
305
+ delete d.element;
306
+ })
307
+ .remove()
308
+ )
309
+ .attr("opacity", d => d.props && d.props.hidden ? 0 : 1)
310
+ .each(function (this: SVGGElement, d, i) {
311
+ d.that = this;
312
+ if (context._series_idx > -1) {
313
+ const seriesKey = d[context._series_idx];
314
+ d.x = context.renderRangeElement(d, i, false, context._rangeOptions, seriesKey);
315
+ } else {
316
+ d.x = context.renderRangeElement(d, i, false, context._rangeOptions);
317
+ }
318
+ })
319
+ .on("dblclick.zoom", d => {
320
+ const x1 = this._interpolateX(d[1]);
321
+ const x2 = this._interpolateX(d[2]);
322
+ const xRange = x2 - x1;
323
+ const xScale = w / xRange;
324
+ this.zoomTo(
325
+ [
326
+ -x1 * xScale,
327
+ 0
328
+ ],
329
+ xScale
330
+ );
331
+ })
332
+ ;
333
+ element.on("dblclick.zoom", null);
334
+ }
335
+ renderRangeElement(d, i, transformEach = false, options: any = {}, seriesKey?: string) {
336
+ const borderOffset1 = options.strokeWidth;
337
+ const borderOffset2 = borderOffset1 * 2;
338
+ const padding = options.rangePadding;
339
+ let endX;
340
+ const x = isNaN(this._transform.x) ? 0 : this._transform.x;
341
+ const k = isNaN(this._transform.k) ? 1 : this._transform.k;
342
+ let b;
343
+ const bucketHeight = this.bucketHeight();
344
+ d.that.setAttribute("data-series", seriesKey);
345
+
346
+ if (this._color_idx > -1) {
347
+ d.that.setAttribute("data-color", d[this._color_idx]);
348
+ }
349
+
350
+ if (seriesKey !== undefined) {
351
+ b = this._bucketsBySeries[seriesKey].bucketMap[this._origIdxMap[seriesKey][i]];
352
+ d.that.setAttribute("data-b", b);
353
+ d.that.setAttribute("data-bucketOffset", this._bucketsBySeries[seriesKey].bucketOffset);
354
+ d.y = this._bucketsBySeries[seriesKey].interpolateY(b) + this._bucketsBySeries[seriesKey].bucketOffset;
355
+ d.that.setAttribute("data-dy", d.y);
356
+ } else {
357
+ b = this._buckets.bucketMap[i];
358
+ d.y = this._buckets.interpolateY(b);
359
+ }
360
+ if (this._color_idx > -1) {
361
+ options.fill = d[this._color_idx];
362
+ }
363
+ if (!transformEach) {
364
+ d.x = this._interpolateX(d[1]);
365
+ endX = this._interpolateX(d[2]);
366
+ d.props = {
367
+ ...d[3],
368
+ text: d[0]
369
+ };
370
+ d.props.width = (endX - d.x) / k;
371
+ } else {
372
+ d.x = this._interpolateX(d[1]) * k;
373
+ endX = this._interpolateX(d[2]) * k;
374
+ d.props = {
375
+ ...d[3],
376
+ text: d[0]
377
+ };
378
+ d.props.width = (endX - d.x) / k;
379
+ d.x += x;
380
+ d.props.width *= k;
381
+ }
382
+ d.props.height = bucketHeight;
383
+ if (seriesKey === undefined && this._buckets.bucketScale < 1) {
384
+ d.props.height = this._buckets.bucketScale * bucketHeight;
385
+ }
386
+ if (d.element === undefined && d.that) {
387
+ d.element = d3Select(d.that);
388
+ }
389
+ d.element.attr("transform", `translate(${d.x + (d.props.width / 2)} ${d.y + (d.props.height / 2)})`);
390
+
391
+ d.x += borderOffset1;
392
+ d.y += borderOffset1;
393
+ d.props.width -= borderOffset2;
394
+ d.props.height -= borderOffset2;
395
+ d.props.width = d.props.width < 1 ? 1 : d.props.width;
396
+ d.props.height = d.props.height < 1 ? 1 : d.props.height;
397
+
398
+ let text = this.truncateText(d.props.text, d.props.width - padding, this._maxFontScale);
399
+
400
+ if (text !== d.props.text) {
401
+ text = this.truncateText(d.props.text, d.props.width - padding);
402
+ } else {
403
+ d.props.fontSize = this._maxFontScale * options.fontSize;
404
+ }
405
+ if (seriesKey === undefined && this._buckets.bucketScale < 1) {
406
+ d.props.fontSize = Math.min(this._maxFontScale, this._buckets.bucketScale) * options.fontSize;
407
+ }
408
+ if (!this._maxY || this._maxY < d.y + d.props.height) {
409
+ this._maxY = d.y + d.props.height;
410
+ }
411
+ if (!this._maxX || this._maxX < d.x + d.props.width) {
412
+ this._maxX = d.x + d.props.width;
413
+ }
414
+ render(
415
+ this._rangeRenderer,
416
+ {
417
+ ...options,
418
+ ...d.props,
419
+ text,
420
+ },
421
+ d.that
422
+ );
423
+ }
424
+
425
+ setRangeOptions() {
426
+ this._rangeOptions = {
427
+ rangePadding: this.rangePadding(),
428
+ fontFamily: this.fontFamily(),
429
+ fontSize: this.fontSize(),
430
+ strokeWidth: this.strokeWidth(),
431
+ fill: this.fill(),
432
+ stroke: this.stroke(),
433
+ textFill: this.rangeFontColor(),
434
+ cornerRadius: this.cornerRadius(),
435
+ };
436
+ }
437
+
438
+ public _transform = { k: 1, x: 0, y: 0 };
439
+ zoomed(transform) {
440
+ this._transform = transform;
441
+ switch (this.renderMode()) {
442
+ case "scale-all":
443
+ this._zoomScale = transform.k;
444
+ this._zoomTranslate = [transform.x, 0];
445
+ this._zoomG.attr("transform", `translate(${transform.x} ${0})scale(${transform.k} 1)`);
446
+ break;
447
+ default:
448
+ const options = this._rangeOptions;
449
+ this.data().forEach((d, i) => {
450
+ if (this._color_idx > -1) {
451
+ options.fill = d[this._color_idx];
452
+ }
453
+ if (this._series_idx > -1) {
454
+ const seriesKey = d[this._series_idx];
455
+ this.renderRangeElement(d, i, true, options, seriesKey);
456
+ } else {
457
+ this.renderRangeElement(d, i, true, options);
458
+ }
459
+ });
460
+ }
461
+
462
+ this.zoomedHook(transform);
463
+ }
464
+
465
+ zoomedHook(transform) {
466
+
467
+ }
468
+
469
+ private calcBuckets(data, startKey: string | number, endKey: string | number, bucketKey?: string | number) {
470
+ const bucketMap = {};
471
+ const bucketKeyMap = {};
472
+ const tol = this.overlapTolerence();
473
+ const buckets = [{ end: -Infinity }];
474
+ let maxBucket = 0;
475
+ if (bucketKey !== undefined) {
476
+ data.forEach((d, i) => {
477
+ bucketMap[i] = d[bucketKey];
478
+ bucketKeyMap[d[bucketKey]] = true;
479
+ });
480
+ maxBucket = Object.keys(bucketKeyMap).length;
481
+ } else {
482
+ data.forEach((d, i) => {
483
+ for (let i2 = 0; i2 < buckets.length; ++i2) {
484
+ if (i === 0 || buckets[i2][endKey] + tol <= d[startKey]) {
485
+ bucketMap[i] = i2;
486
+ if (maxBucket < i2) maxBucket = i2;
487
+ buckets[i2][endKey] = d[endKey];
488
+ break;
489
+ }
490
+ }
491
+ if (bucketMap[i] === undefined) {
492
+ bucketMap[i] = buckets.length;
493
+ const b = {};
494
+ b[endKey] = d[endKey];
495
+ buckets.push(b as any);
496
+ }
497
+
498
+ if (maxBucket < bucketMap[i]) maxBucket = bucketMap[i];
499
+ });
500
+ }
501
+ const height = (maxBucket + 1) * (this.bucketHeight() + this.gutter());
502
+ return {
503
+ bucketMap,
504
+ maxBucket,
505
+ bucketScale: this.height() / height,
506
+ interpolateY: d3ScaleLinear()
507
+ .domain([0, maxBucket + 1])
508
+ .range([0, Math.min(this.height(), height)])
509
+ };
510
+ }
511
+
512
+ data(): IGanttData[];
513
+ data(_: IGanttData[]): this;
514
+ data(_?: IGanttData[]): this | IGanttData[] {
515
+ const retVal = super.data.apply(this, arguments);
516
+ if (arguments.length > 0) {
517
+ this._minStart = Math.min(...this.data().map(n => n[1])) ?? 0;
518
+ this._maxEnd = Math.max(...this.data().map(n => n[2])) ?? 1;
519
+ this.measureDataText(true);
520
+ }
521
+ return retVal;
522
+ }
523
+
524
+ protected _textWidths;
525
+ protected _maxFontScale;
526
+ protected _characterWidths;
527
+ protected _prevFontFamily;
528
+ protected _prevFontSize;
529
+ measureDataText(forceMeasure = false) {
530
+ const textWidths = {};
531
+ const characterWidths = {};
532
+ const fontFamily = this.fontFamily();
533
+ const fontSize = this.fontSize();
534
+ const bucketHeight = this.bucketHeight();
535
+
536
+ if (bucketHeight) {
537
+ this._maxFontScale = (bucketHeight - (this.rangePadding() * 2)) / fontSize;
538
+ }
539
+
540
+ if (forceMeasure || this._prevFontFamily !== fontFamily || this._prevFontSize !== fontSize) {
541
+ characterWidths["."] = Utility.textSize(".", fontFamily, fontSize).width;
542
+ this.data().forEach(d => {
543
+ if (!textWidths[d[0]]) {
544
+ textWidths[d[0]] = Utility.textSize(d[0], fontFamily, fontSize).width;
545
+ }
546
+ d[0].split("").forEach(char => {
547
+ if (!characterWidths[char]) {
548
+ characterWidths[char] = Utility.textSize(char, fontFamily, fontSize).width;
549
+ }
550
+ });
551
+ });
552
+ this._textWidths = textWidths;
553
+ this._characterWidths = characterWidths;
554
+ }
555
+ this._prevFontFamily = fontFamily;
556
+ this._prevFontSize = fontSize;
557
+ }
558
+
559
+ truncateText(text, width, scale = 1) {
560
+ const textFits = this._textWidths[text] * scale < width;
561
+ if (textFits) {
562
+ return text;
563
+ }
564
+ let ret = "";
565
+ let sum = 0;
566
+ const _width = width - (this._characterWidths["."] * 3);
567
+ for (const char of text) {
568
+ sum += this._characterWidths[char];
569
+ if (sum < _width) {
570
+ ret += char;
571
+ } else {
572
+ break;
573
+ }
574
+ }
575
+ return _width < 0 ? "" : ret + "...";
576
+ }
577
+
578
+ resize(_size?: { width: number, height: number }) {
579
+ let retVal;
580
+ if (this.fitWidthToContent() || this.fitHeightToContent()) {
581
+ retVal = super.resize.call(this, {
582
+ width: _size.width,
583
+ height: this._maxY
584
+ });
585
+ } else {
586
+ retVal = super.resize.apply(this, arguments);
587
+ }
588
+ return retVal;
589
+ }
590
+
591
+ selectionChanged() {
592
+
593
+ }
594
+
595
+ highlightItem(_element, d) {
596
+
597
+ }
598
+
599
+ click(row, _col, sel) {
600
+
601
+ }
602
+
603
+ dblclick(row, _col, sel) {
604
+
605
+ }
606
+
607
+ mousein(row, _col, sel) {
608
+ }
609
+
610
+ mouseover(row, _col, sel) {
611
+ }
612
+
613
+ mouseout(row, _col, sel) {
614
+ }
615
+ }
616
+ ReactGantt.prototype._class += " timeline_ReactGantt";
617
+
618
+ export interface ReactGantt {
619
+ titleColumn(): string;
620
+ titleColumn(_: string): this;
621
+ startDateColumn(): string;
622
+ startDateColumn(_: string): this;
623
+ endDateColumn(): string;
624
+ endDateColumn(_: string): this;
625
+ iconColumn(): string;
626
+ iconColumn(_: string): this;
627
+ colorColumn(): string;
628
+ colorColumn(_: string): this;
629
+ seriesColumn(): string;
630
+ seriesColumn(_: string): this;
631
+ bucketColumn(): string;
632
+ bucketColumn(_: string): this;
633
+ overlapTolerence(): number;
634
+ overlapTolerence(_: number): this;
635
+ smallestRangeWidth(): number;
636
+ smallestRangeWidth(_: number): this;
637
+ bucketHeight(): number;
638
+ bucketHeight(_: number): this;
639
+ gutter(): number;
640
+ gutter(_: number): this;
641
+ showToolbar_default(_: boolean): this;
642
+ fontSize(): number;
643
+ fontSize(_: number): this;
644
+ fontFamily(): string;
645
+ fontFamily(_: string): this;
646
+ strokeWidth(): number;
647
+ strokeWidth(_: number): this;
648
+ stroke(): string;
649
+ stroke(_: string): this;
650
+ cornerRadius(): number;
651
+ cornerRadius(_: number): this;
652
+ fill(): string;
653
+ fill(_: string): this;
654
+ rangeFontColor(): string;
655
+ rangeFontColor(_: string): this;
656
+ rangePadding(): number;
657
+ rangePadding(_: number): this;
658
+ renderMode(): "default" | "scale-all";
659
+ renderMode(_: "default" | "scale-all"): this;
660
+ maxZoom(): number;
661
+ maxZoom(_: number): this;
662
+ fitWidthToContent(): boolean;
663
+ fitWidthToContent(_: boolean): this;
664
+ fitHeightToContent(): boolean;
665
+ fitHeightToContent(_: boolean): this;
666
+ evenSeriesBackground(): string;
667
+ evenSeriesBackground(_: string): this;
668
+ oddSeriesBackground(): string;
669
+ oddSeriesBackground(_: string): this;
670
+ }
671
+
672
+ ReactGantt.prototype.publish("fitWidthToContent", false, "boolean", "If true, resize will simply reapply the bounding box width");
673
+ ReactGantt.prototype.publish("fitHeightToContent", false, "boolean", "If true, resize will simply reapply the bounding box height");
674
+ ReactGantt.prototype.publish("titleColumn", null, "string", "Column name to for the title");
675
+ ReactGantt.prototype.publish("startDateColumn", null, "string", "Column name to for the start date");
676
+ ReactGantt.prototype.publish("endDateColumn", null, "string", "Column name to for the end date");
677
+ ReactGantt.prototype.publish("iconColumn", null, "string", "Column name to for the icon");
678
+ ReactGantt.prototype.publish("colorColumn", null, "string", "Column name to for the color");
679
+ ReactGantt.prototype.publish("seriesColumn", null, "string", "Column name to for the series identifier");
680
+ ReactGantt.prototype.publish("bucketColumn", null, "string", "Column name to for the bucket identifier");
681
+ ReactGantt.prototype.publish("renderMode", "default", "set", "Render modes vary in features and performance", ["default", "scale-all"]);
682
+ ReactGantt.prototype.publish("rangePadding", 3, "number", "Padding within each range rectangle (pixels)");
683
+ ReactGantt.prototype.publish("fill", "#1f77b4", "string", "Background color of range rectangle");
684
+ ReactGantt.prototype.publish("stroke", null, "string", "Color of range rectangle border");
685
+ ReactGantt.prototype.publish("strokeWidth", null, "number", "Width of range rectangle border (pixels)");
686
+ ReactGantt.prototype.publish("cornerRadius", 3, "number", "Space between range buckets (pixels)");
687
+ ReactGantt.prototype.publish("fontFamily", null, "string", "Font family within range rectangle", null, { optional: true });
688
+ ReactGantt.prototype.publish("fontSize", 10, "number", "Size of font within range rectangle (pixels)");
689
+ ReactGantt.prototype.publish("rangeFontColor", "#ecf0f1", "html-color", "rangeFontColor");
690
+ ReactGantt.prototype.publish("overlapTolerence", 2, "number", "overlapTolerence");
691
+ ReactGantt.prototype.publish("smallestRangeWidth", 10, "number", "Width of the shortest range (pixels)");
692
+ ReactGantt.prototype.publish("bucketHeight", 100, "number", "Max height of range element (pixels)");
693
+ ReactGantt.prototype.publish("gutter", 2, "number", "Space between range buckets (pixels)");
694
+ ReactGantt.prototype.publish("maxZoom", 16, "number", "Maximum zoom");
695
+ ReactGantt.prototype.publish("evenSeriesBackground", "#FFFFFF", "html-color", "Background color of even series rows");
696
+ ReactGantt.prototype.publish("oddSeriesBackground", "#DDDDDD", "html-color", "Background color of odd series rows");