@hpcc-js/chart 3.7.1 → 3.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +43 -43
  2. package/README.md +93 -93
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.umd.cjs +1 -1
  6. package/dist/index.umd.cjs.map +1 -1
  7. package/package.json +6 -6
  8. package/src/Area.md +176 -176
  9. package/src/Area.ts +12 -12
  10. package/src/Axis.css +35 -35
  11. package/src/Axis.ts +781 -781
  12. package/src/Bar.md +90 -90
  13. package/src/Bar.ts +9 -9
  14. package/src/Bubble.css +16 -16
  15. package/src/Bubble.md +69 -69
  16. package/src/Bubble.ts +196 -196
  17. package/src/BubbleXY.ts +14 -14
  18. package/src/Bullet.css +59 -59
  19. package/src/Bullet.md +104 -104
  20. package/src/Bullet.ts +176 -176
  21. package/src/Column.css +44 -44
  22. package/src/Column.md +90 -90
  23. package/src/Column.ts +684 -684
  24. package/src/Contour.md +88 -88
  25. package/src/Contour.ts +97 -97
  26. package/src/D3Cloud.ts +403 -403
  27. package/src/Gantt.md +119 -119
  28. package/src/Gantt.ts +14 -14
  29. package/src/Gauge.md +148 -148
  30. package/src/Gauge.ts +368 -368
  31. package/src/HalfPie.md +62 -62
  32. package/src/HalfPie.ts +26 -26
  33. package/src/Heat.md +42 -42
  34. package/src/Heat.ts +283 -283
  35. package/src/HexBin.css +8 -8
  36. package/src/HexBin.md +88 -88
  37. package/src/HexBin.ts +144 -144
  38. package/src/Line.css +4 -4
  39. package/src/Line.md +170 -170
  40. package/src/Line.ts +14 -14
  41. package/src/Pie.css +50 -50
  42. package/src/Pie.md +88 -88
  43. package/src/Pie.ts +546 -546
  44. package/src/QuarterPie.md +61 -61
  45. package/src/QuarterPie.ts +35 -35
  46. package/src/QuartileCandlestick.md +129 -129
  47. package/src/QuartileCandlestick.ts +349 -349
  48. package/src/Radar.css +14 -14
  49. package/src/Radar.md +104 -104
  50. package/src/Radar.ts +336 -336
  51. package/src/RadialBar.css +25 -25
  52. package/src/RadialBar.md +91 -91
  53. package/src/RadialBar.ts +217 -217
  54. package/src/Scatter.css +42 -42
  55. package/src/Scatter.md +163 -163
  56. package/src/Scatter.ts +412 -412
  57. package/src/StatChart.md +117 -117
  58. package/src/StatChart.ts +261 -261
  59. package/src/Step.md +163 -163
  60. package/src/Step.ts +12 -12
  61. package/src/Summary.css +55 -55
  62. package/src/Summary.md +219 -219
  63. package/src/Summary.ts +322 -322
  64. package/src/SummaryC.md +154 -154
  65. package/src/SummaryC.ts +240 -240
  66. package/src/WordCloud.css +2 -2
  67. package/src/WordCloud.md +144 -144
  68. package/src/WordCloud.ts +268 -268
  69. package/src/XYAxis.css +40 -40
  70. package/src/XYAxis.md +149 -149
  71. package/src/XYAxis.ts +809 -809
  72. package/src/__package__.ts +3 -3
  73. package/src/__tests__/heat.ts +71 -71
  74. package/src/__tests__/index.ts +3 -3
  75. package/src/__tests__/pie.ts +20 -20
  76. package/src/__tests__/stat.ts +16 -16
  77. package/src/__tests__/test3.ts +68 -68
  78. package/src/index.ts +28 -28
  79. package/src/test.ts +70 -70
  80. package/src/timeFormats.ts +26 -26
package/src/Axis.ts CHANGED
@@ -1,781 +1,781 @@
1
- import { SVGWidget } from "@hpcc-js/common";
2
- import { axisBottom as d3AxisBottom, axisLeft as d3AxisLeft, axisRight as d3AxisRight, axisTop as d3AxisTop } from "d3-axis";
3
- import { format as d3Format } from "d3-format";
4
- import { scaleBand as d3ScaleBand, scaleLinear as d3ScaleLinear, scaleLog as d3ScaleLog, scalePow as d3ScalePow, scaleTime as d3ScaleTime } from "d3-scale";
5
- import { select as d3Select } from "d3-selection";
6
- import { timeFormat as d3TimeFormat, timeParse as d3TimeParse } from "d3-time-format";
7
-
8
- import "../src/Axis.css";
9
-
10
- export interface IOverflow {
11
- left: number;
12
- top: number;
13
- right: number;
14
- bottom: number;
15
- depth: number;
16
- tickOverlapModulus: number;
17
- }
18
-
19
- export class Axis extends SVGWidget {
20
- _origType;
21
- _origTimePattern;
22
-
23
- protected parser;
24
- protected parserInvert;
25
- protected formatter: ((d: Date | any) => string) | null;
26
- protected d3Scale;
27
- protected d3Axis;
28
- protected d3Guides;
29
- protected _guideElement;
30
- protected svg;
31
- protected svgAxis;
32
- protected svgGuides;
33
- protected _tickFormatFunc?: (d: any) => string;
34
-
35
- constructor(drawStartPosition: "origin" | "center" = "origin") {
36
- super();
37
- this._drawStartPos = drawStartPosition;
38
-
39
- this.updateScale();
40
- }
41
-
42
- lowValue() {
43
- return this.parse(this.low());
44
- }
45
-
46
- highValue() {
47
- return this.parse(this.high());
48
- }
49
-
50
- parse(d, forceNumeric?) {
51
- if (d instanceof Array) {
52
- return d.map(function (d2) {
53
- return this.parse(d2);
54
- }, this);
55
- }
56
- if (d !== undefined && d !== null) {
57
- if (this.parser) {
58
- return this.parser(typeof d === "number" ? d.toString() : d);
59
- }
60
- if (forceNumeric && typeof d === "string") {
61
- return +d;
62
- }
63
- }
64
- return d;
65
- }
66
-
67
- parseInvert(d) {
68
- if (d instanceof Array) {
69
- return d.map(function (d2) {
70
- return this.parseInvert(d2);
71
- }, this);
72
- }
73
- if (this.parserInvert && d) {
74
- return this.parserInvert(d);
75
- }
76
- return d;
77
- }
78
-
79
- format(d) {
80
- if (d instanceof Array) {
81
- return d.map(function (d2) {
82
- return this.format(d2);
83
- }, this);
84
- }
85
- if (d !== undefined && d !== null && this.formatter) {
86
- return this.formatter(d);
87
- }
88
- return d;
89
- }
90
-
91
- parseFormat(d) {
92
- return this.format(this.parse(d));
93
- }
94
-
95
- tickFormatFunc(fn: (d: any) => string): this;
96
- tickFormatFunc(): ((d: any) => string) | undefined;
97
- tickFormatFunc(fn?: (d: any) => string): this | ((d: any) => string) | undefined {
98
- if (!arguments.length) return this._tickFormatFunc;
99
- this._tickFormatFunc = fn;
100
- this.updateScale();
101
- return this;
102
- }
103
-
104
- scalePos(d) {
105
- let retVal = this.d3Scale(this.parse(d));
106
- if (this.type() === "ordinal") {
107
- retVal += this.bandwidth() / 2;
108
- }
109
- return retVal;
110
- }
111
-
112
- bandwidth() {
113
- return this.d3Scale.bandwidth ? this.d3Scale.bandwidth() : 0;
114
- }
115
-
116
- isHorizontal() {
117
- switch (this.orientation()) {
118
- case "left":
119
- case "right":
120
- return false;
121
- default:
122
- }
123
- return true;
124
- }
125
-
126
- domain(_?) {
127
- if (!arguments.length) return this.d3Scale.domain();
128
- this.d3Scale.domain(_);
129
- return this;
130
- }
131
-
132
- range(_?) {
133
- if (!arguments.length) {
134
- if (this.d3Scale.rangeRoundBands) {
135
- return this.d3Scale.rangeExtent();
136
- } else if (this.d3Scale.rangeRound) {
137
- return this.d3Scale.range();
138
- }
139
- }
140
- if (this.d3Scale.rangeRoundBands) {
141
- this.d3Scale.rangeRoundBands(_, 0.1);
142
- } else if (this.d3Scale.rangeRound) {
143
- this.d3Scale.range(_);
144
- }
145
- return this;
146
- }
147
-
148
- invert(pos) {
149
- return this.d3Scale.invert(pos);
150
- }
151
-
152
- guideTarget(_) {
153
- this._guideElement = d3Select(_)
154
- .attr("class", this._class)
155
- ;
156
- return this;
157
- }
158
-
159
- enter(domNode, element) {
160
- super.enter(domNode, element);
161
- this.svg = element.append("g");
162
- this.svgAxis = this.svg.append("g")
163
- .attr("class", "axis")
164
- ;
165
- this.svgGuides = (this._guideElement || element).append("g")
166
- .attr("class", "guide")
167
- ;
168
- }
169
-
170
- protected _prevOrientation;
171
- updateScale(): this {
172
- switch (this.type()) {
173
- case "ordinal":
174
- this.d3Scale = d3ScaleBand()
175
- .paddingInner(this.ordinalPaddingInner())
176
- .paddingOuter(this.ordinalPaddingOuter())
177
- ;
178
- if (this.ordinals_exists()) {
179
- this.d3Scale.domain(this.ordinals());
180
- }
181
- this.parser = null;
182
- if (this.ordinalMappings_exists()) {
183
- const mappings = this.ordinalMappings();
184
- this.formatter = (_: any) => mappings[_] || _;
185
- } else {
186
- this.formatter = null;
187
- }
188
- break;
189
- case "linear":
190
- this.d3Scale = d3ScaleLinear();
191
- if (this.low_exists() && this.high_exists()) {
192
- this.d3Scale.domain([this.lowValue(), this.highValue()]);
193
- }
194
- this.parser = null;
195
- this.formatter = this.tickFormat_exists() ? d3Format(this.tickFormat()) : null;
196
- break;
197
- case "pow":
198
- this.d3Scale = d3ScalePow()
199
- .exponent(this.powExponent())
200
- ;
201
- if (this.low_exists() && this.high_exists()) {
202
- this.d3Scale.domain([this.lowValue(), this.highValue()]);
203
- }
204
- this.parser = null;
205
- this.formatter = this.tickFormat_exists() ? d3Format(this.tickFormat()) : null;
206
- break;
207
- case "log":
208
- this.d3Scale = d3ScaleLog()
209
- .base(this.logBase())
210
- ;
211
- if (this.low_exists() && this.high_exists()) {
212
- this.d3Scale.domain([this.lowValue(), this.highValue()]);
213
- }
214
- this.parser = null;
215
- this.formatter = this.tickFormat_exists() ? d3Format(this.tickFormat()) : null;
216
- break;
217
- case "time":
218
- this.d3Scale = d3ScaleTime();
219
- if (this.low_exists() && this.high_exists()) {
220
- this.d3Scale.domain([this.lowValue(), this.highValue()]);
221
- }
222
- this.parser = this.timePattern_exists() ? d3TimeParse(this.timePattern()) : null;
223
- this.parserInvert = this.timePattern_exists() ? d3TimeFormat(this.timePattern()) : null;
224
- if (this._tickFormatFunc) {
225
- this.formatter = this._tickFormatFunc;
226
- } else {
227
- this.formatter = this.tickFormat_exists() ? d3TimeFormat(this.tickFormat()) : null;
228
- }
229
- break;
230
- default:
231
- }
232
- if (this._prevOrientation !== this.orientation()) {
233
- switch (this.orientation()) {
234
- case "left":
235
- this.d3Axis = d3AxisLeft(this.d3Scale);
236
- this.d3Guides = d3AxisLeft(this.d3Scale);
237
- break;
238
- case "top":
239
- this.d3Axis = d3AxisTop(this.d3Scale);
240
- this.d3Guides = d3AxisTop(this.d3Scale);
241
- break;
242
- case "right":
243
- this.d3Axis = d3AxisRight(this.d3Scale);
244
- this.d3Guides = d3AxisRight(this.d3Scale);
245
- break;
246
- case "bottom":
247
- default:
248
- this.d3Axis = d3AxisBottom(this.d3Scale);
249
- this.d3Guides = d3AxisBottom(this.d3Scale);
250
- break;
251
- }
252
- this._prevOrientation = this.orientation();
253
- if (this.svgAxis) {
254
- this.svgAxis.html("");
255
- }
256
- if (this.svgGuides) {
257
- this.svgGuides.html("");
258
- }
259
- }
260
-
261
- if (this.extend()) {
262
- switch (this.type()) {
263
- case "ordinal":
264
- break;
265
- default:
266
- let length;
267
- let delta;
268
- let low;
269
- let high;
270
- let newLow;
271
- let newHigh;
272
- if (this.isHorizontal()) {
273
- length = this.width();
274
- this.d3Scale.range([0, length]);
275
- delta = length * this.extend() / 100;
276
- low = this.d3Scale.invert(0);
277
- newLow = this.d3Scale.invert(-delta);
278
- high = this.d3Scale.invert(length);
279
- newHigh = this.d3Scale.invert(length + delta);
280
- } else {
281
- length = this.height();
282
- this.d3Scale.range([length, 0]);
283
- delta = length * this.extend() / 100;
284
- low = this.d3Scale.invert(length);
285
- newLow = this.d3Scale.invert(length + delta);
286
- high = this.d3Scale.invert(0);
287
- newHigh = this.d3Scale.invert(-delta);
288
- }
289
- if (newLow === low) { // Edge case when there is only one item in the domain ---
290
- newLow = low - low * this.extend() / 100;
291
- }
292
- if (newHigh === high) { // Edge case when there is only one item in the domain ---
293
- newHigh = high + high * this.extend() / 100;
294
- }
295
- if ((Math as any).sign(low) !== (Math as any).sign(newLow)) {
296
- newLow = 0;
297
- }
298
- if ((Math as any).sign(high) !== (Math as any).sign(newHigh)) {
299
- newHigh = 0;
300
- }
301
- this.d3Scale.domain([newLow, newHigh]);
302
- break;
303
- }
304
- }
305
-
306
- this.d3Axis
307
- .scale(this.d3Scale)
308
- .tickFormat(this.formatter)
309
- .ticks(this.tickCount())
310
- ;
311
- this.d3Guides
312
- .scale(this.d3Scale)
313
- .tickSize(this.tickLength_exists() ? -this.tickLength() : 0)
314
- .tickFormat("")
315
- .ticks(this.tickCount())
316
- ;
317
- const customTicks = this.ticks();
318
- if (customTicks.length) {
319
- this.d3Axis
320
- .tickValues(customTicks.map(d => this.parse(d.value)))
321
- .tickFormat((_d, i) => {
322
- return customTicks[i].label;
323
- });
324
- this.d3Guides
325
- .tickValues(customTicks.map(d => this.parse(d.value)));
326
- }
327
- return this;
328
- }
329
-
330
- adjustText(svg, tickOverlapModulus) {
331
- const isHoriztontal = this.isHorizontal();
332
- const isLeft = this.orientation() === "left";
333
- const isBottom = this.orientation() === "bottom";
334
- const context = this;
335
- const textSelection = svg.selectAll(".tick > text")
336
- .style("font-family", this.fontFamily())
337
- .style("font-size", this.fontSize_exists() ? this.fontSize() + "px" : null)
338
- ;
339
- if (this.overlapMode() === "linebreak") {
340
- if (this.type() === "ordinal") {
341
- textSelection
342
- .call(function () {
343
- return context.linebreak.apply(context, arguments);
344
- }, this.bandwidth())
345
- ;
346
- }
347
- } else if (this.overlapMode() === "wrap") {
348
- if (this.type() === "ordinal") {
349
- textSelection
350
- .call(function () {
351
- return context.wrap.apply(context, arguments);
352
- }, this.bandwidth())
353
- ;
354
- }
355
- } else {
356
- switch (isHoriztontal ? this.overlapMode() : "none") {
357
- case "stagger":
358
- textSelection
359
- .style("text-anchor", "middle")
360
- .attr("dy", function (_d, i) { return (isBottom ? 1 : -1) * ((isBottom ? 0.71 : 0) + i % tickOverlapModulus) + "em"; })
361
- .attr("dx", 0)
362
- .attr("visibility", null)
363
- .attr("transform", "rotate(0)")
364
- ;
365
- break;
366
- case "hide":
367
- textSelection
368
- .style("text-anchor", "middle")
369
- .attr("dy", (isBottom ? 0.71 : 0) + "em")
370
- .attr("dx", 0)
371
- .attr("visibility", function (_d, i) { return i % tickOverlapModulus ? "hidden" : null; })
372
- .attr("transform", "rotate(0)")
373
- ;
374
- break;
375
- case "rotate":
376
- const deg = -(this.labelRotation()) || 0;
377
- if (deg !== 0 && tickOverlapModulus > 1) {
378
- textSelection
379
- .each(function () {
380
- const elm = d3Select(this);
381
- const bbox = elm.node().getBBox();
382
- const dyOff = (isBottom ? 1 : -1) * Math.sin(Math.PI * (-Math.abs(deg) / 180));
383
- elm
384
- .style("text-anchor", deg > 0 ? (isBottom ? "start" : "end") : (isBottom ? "end" : "start"))
385
- .attr("dy", (bbox.height / 2 * dyOff) + "px")
386
- .attr("dx", deg > 0 ? (isBottom ? "0.71em" : "-0.71em") : (isBottom ? "-0.71em" : "0.71em"))
387
- .attr("transform", "rotate(" + deg + ")")
388
- .attr("visibility", null)
389
- ;
390
- })
391
- ;
392
- break;
393
- }
394
- /* falls through */
395
- default:
396
- textSelection
397
- .style("text-anchor", isHoriztontal ? "middle" : isLeft ? "end" : "start")
398
- .attr("dy", isHoriztontal ? ((isBottom ? 0.71 : 0) + "em") : "0.32em")
399
- .attr("dx", 0)
400
- .attr("visibility", null)
401
- .attr("transform", "rotate(0)")
402
- ;
403
- }
404
- }
405
- }
406
-
407
- calcTickOverlapModulus(element) {
408
- let retVal = 1;
409
- switch (this.overlapMode()) {
410
- case "rotate":
411
- case "stagger":
412
- case "hide":
413
- const bboxArr = [];
414
- element.selectAll(".tick > text").each(function () {
415
- const bbox = this.getBoundingClientRect();
416
- for (let i = bboxArr.length - 1; i >= 0; --i) {
417
- if (bboxArr[i].right < bbox.left) {
418
- break;
419
- }
420
- if (bboxArr.length + 1 - i > retVal) {
421
- retVal = bboxArr.length + 1 - i;
422
- }
423
- }
424
- bboxArr.push(bbox);
425
- });
426
- break;
427
- default:
428
- }
429
- return retVal;
430
- }
431
-
432
- calcOverflow(element, ignoreText?): IOverflow {
433
- this.updateScale();
434
- if (this.hidden()) {
435
- return {
436
- left: 0,
437
- top: 0,
438
- right: 0,
439
- bottom: 0,
440
- depth: 0,
441
- tickOverlapModulus: 1
442
- };
443
- }
444
- const isHorizontal = this.isHorizontal();
445
- this.range(isHorizontal ? [0, this.width()] : [this.height(), 0]);
446
- const tmpSvg = element.append("g").attr("class", this.classID());
447
- const tmpSvgG = tmpSvg.append("g");
448
- tmpSvgG
449
- .attr("class", isHorizontal ? "x" : "y")
450
- .call(this.d3Axis)
451
- ;
452
- if (ignoreText) {
453
- element.selectAll(".tick > text").remove();
454
- }
455
-
456
- const retVal: IOverflow = {
457
- left: 0,
458
- top: 0,
459
- right: 0,
460
- bottom: 0,
461
- depth: 0,
462
- tickOverlapModulus: this.calcTickOverlapModulus(tmpSvgG)
463
- };
464
- this.adjustText(tmpSvgG, retVal.tickOverlapModulus);
465
-
466
- const bbox = tmpSvgG.node().getBBox();
467
- retVal.depth = isHorizontal ? bbox.height : bbox.width;
468
- switch (this.shrinkToFit()) {
469
- case "low":
470
- case "both":
471
- retVal.left = isHorizontal ? -bbox.x : 0;
472
- retVal.bottom = isHorizontal ? 0 : -(this.height() - (bbox.height + bbox.y));
473
- break;
474
- default:
475
- }
476
- switch (this.shrinkToFit()) {
477
- case "high":
478
- case "both":
479
- retVal.top = isHorizontal ? 0 : -bbox.y;
480
- retVal.right = isHorizontal ? -(this.width() - bbox.x - bbox.width) : 0;
481
- break;
482
- default:
483
- }
484
- tmpSvg.remove();
485
-
486
- return retVal;
487
- }
488
-
489
- wrap(_text, bandSize, re) {
490
- re = re || /\s+/;
491
- const context = this;
492
- _text.each(function () {
493
- const text = d3Select(this);
494
- const words = text.text().split(re).reverse();
495
- let line = [];
496
- let lineNumber = 0;
497
- const lineHeight = 1.1;
498
- const x = text.attr("x");
499
- const y = text.attr("y");
500
- const fs = parseFloat(text.style("font-size")) || 10;
501
- const maxLinesPerBand = Math.floor(bandSize / (fs * lineHeight)) - 1;
502
- const minWordsPerLine = context.isHorizontal() ? 1 : Math.ceil(words.length / maxLinesPerBand);
503
- const dy = parseFloat(text.attr("dy"))
504
- ;
505
- let tspan = text.text(null).append("tspan")
506
- .attr("x", x)
507
- .attr("y", y)
508
- .attr("dy", dy + "em")
509
- ;
510
- let wordsOnLine = 0;
511
- let word = words.pop();
512
- while (word) {
513
- line.push(word);
514
- tspan.text(line.join(" "));
515
- wordsOnLine++;
516
- if ((tspan.node() as any).getComputedTextLength() > bandSize && wordsOnLine >= minWordsPerLine) {
517
- line.pop();
518
- tspan.text(line.join(" "));
519
- line = [word];
520
- tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
521
- wordsOnLine = 0;
522
- }
523
- word = words.pop();
524
- }
525
- if (!context.isHorizontal()) {
526
- text.selectAll("tspan")
527
- .attr("y", (-lineNumber / 2) + "em")
528
- ;
529
- }
530
- });
531
- }
532
-
533
- linebreak(text, bandSize) {
534
- this.wrap(text, bandSize, "\n");
535
- }
536
-
537
- update(domNode, element) {
538
- super.update(domNode, element);
539
-
540
- this.svg.style("display", this.hidden() ? "none" : null);
541
-
542
- const overlap = this.calcOverflow(element);
543
-
544
- const lowerPos: number = this.isHorizontal() ? overlap.left : this.height() - overlap.top - overlap.bottom;
545
- const upperPos: number = this.isHorizontal() ? this.width() - overlap.right - this.padding() : 0 + this.padding();
546
-
547
- this.range(this.reverse() ? [upperPos, lowerPos] : [lowerPos, upperPos]);
548
-
549
- const context = this;
550
- function doPosition(element) {
551
- element.attr("transform", function () {
552
- switch (context.orientation()) {
553
- case "left":
554
- return "translate(" + overlap.depth + ", " + overlap.top + ")";
555
- case "top":
556
- return "translate(0," + overlap.depth + ")";
557
- case "right":
558
- return "translate(" + (context.width() - overlap.depth) + ", " + overlap.top + ")";
559
- case "bottom":
560
- return "translate(0," + (context.height() - overlap.depth) + ")";
561
- default:
562
- }
563
- return "translate(0,0)";
564
- });
565
- }
566
- this.svg
567
- // .attr("class", this.isHorizontal() ? "x" : "y")
568
- .style("visibility", this.type() === "none" ? "hidden" : null)
569
- .transition()
570
- .call(doPosition)
571
- ;
572
- if (this._guideElement) {
573
- this.svgGuides
574
- .transition()
575
- .call(doPosition)
576
- ;
577
- }
578
- this.svgAxis
579
- .call(this.d3Axis)
580
- ;
581
- this.adjustText(this.svgAxis, overlap.tickOverlapModulus);
582
-
583
- const svgLineBBox = {
584
- x: this.pos().x,
585
- width: this.width()
586
- };
587
- const svgText = this.svgAxis.selectAll(".axisTitle").data(this.title() ? [this.title()] : []);
588
- const svgTextUpdate = svgText.enter().append("text")
589
- .attr("class", "axisTitle")
590
- .merge(svgText)
591
- ;
592
- const svgTitleTransition = svgTextUpdate.transition()
593
- .attr("dx", null)
594
- .style("text-anchor", "end")
595
- ;
596
- switch (this.orientation()) {
597
- case "left":
598
- svgTitleTransition
599
- .attr("transform", "rotate(-90)")
600
- .attr("x", -2)
601
- .attr("y", 2)
602
- .attr("dy", ".71em")
603
- ;
604
- break;
605
- case "right":
606
- svgTitleTransition
607
- .attr("transform", "rotate(-90)")
608
- .attr("x", -2)
609
- .attr("y", 4)
610
- .attr("dx", null)
611
- .attr("dy", "-.71em")
612
- ;
613
- break;
614
- case "top":
615
- svgTitleTransition
616
- .attr("transform", "rotate(0)")
617
- .attr("x", svgLineBBox.width - 2)
618
- .attr("y", 2)
619
- .attr("dx", null)
620
- .attr("dy", ".71em")
621
- ;
622
- break;
623
- case "bottom":
624
- svgTitleTransition
625
- .attr("transform", "rotate(0)")
626
- .attr("x", svgLineBBox.width - 2)
627
- .attr("y", -2)
628
- .attr("dy", null)
629
- ;
630
- break;
631
- default:
632
- }
633
- svgTitleTransition
634
- .text(this.title_exists() ? this.title() : "")
635
- ;
636
- svgText.exit().remove();
637
- this.svgGuides
638
- .call(this.d3Guides)
639
- .selectAll(".tick").classed("guide-0", d => d === 0 && this.low() < 0)
640
- ;
641
- }
642
-
643
- rerender() {
644
- this.svgAxis.call(this.d3Axis);
645
- this.svgGuides.call(this.d3Guides);
646
- }
647
-
648
- postUpdate(domNode, element) {
649
- super.postUpdate(domNode, element);
650
- if (this._guideElement) {
651
- this._guideElement
652
- .attr("transform", this._element.attr("transform"))
653
- ;
654
- }
655
- }
656
- }
657
- Axis.prototype._class += " chart_Axis";
658
-
659
- export interface Axis {
660
- type(): string;
661
- type(_: string): this;
662
- type_exists(): boolean;
663
- timePattern(): string;
664
- timePattern(_: string): this;
665
- timePattern_exists(): boolean;
666
- reverse(): boolean;
667
- reverse(_: boolean): this;
668
-
669
- title(): string;
670
- title(_: string): this;
671
- title_exists(): boolean;
672
- orientation(): string;
673
- orientation(_: string): this;
674
- orientation_default(): string;
675
- orientation_default(_: string): this;
676
- powExponent(): number;
677
- powExponent(_: number): this;
678
- logBase(): number;
679
- logBase(_: number): this;
680
- ordinals(): string[];
681
- ordinals(_: string[]): this;
682
- ordinals_exists(): boolean;
683
- fontSize(): number;
684
- fontSize(_: number): this;
685
- fontSize_exists(): boolean;
686
- fontFamily(): string;
687
- fontFamily(_: string): this;
688
- tickCount(): number;
689
- tickCount(_: number): this;
690
- tickFormat(): string;
691
- tickFormat(_: string): this;
692
- tickFormat_exists(): boolean;
693
- tickFormat_reset(): void;
694
- tickFormatFunc(fn: (d: any) => string): this;
695
- tickFormatFunc(): ((d: any) => string) | undefined;
696
- tickLength(): number;
697
- tickLength(_: number): this;
698
- tickLength_exists(): boolean;
699
- ticks(): Array<{ value: string, label: string }>;
700
- ticks(_: Array<{ value: string, label: string }>): this;
701
- low(): any;
702
- low(_: any): this;
703
- low_exists(): boolean;
704
- high(): any;
705
- high(_: any): this;
706
- high_exists(): boolean;
707
- overlapMode(): string;
708
- overlapMode(_: string): this;
709
- overlapMode_default(): string;
710
- overlapMode_default(_: string): this;
711
- labelRotation(): number;
712
- labelRotation(_: number): this;
713
- shrinkToFit(): string;
714
- shrinkToFit(_: string): this;
715
- shrinkToFit_default(): string;
716
- shrinkToFit_default(_: string): this;
717
- extend(): number;
718
- extend(_: number): this;
719
- extend_default(): number;
720
- extend_default(_: number): this;
721
- hidden(): boolean;
722
- hidden(_: boolean): this;
723
-
724
- ordinalPaddingInner(): number;
725
- ordinalPaddingInner(_: number): this;
726
- ordinalPaddingOuter(): number;
727
- ordinalPaddingOuter(_: number): this;
728
- ordinalMappings(_: { [key: string]: string }): this;
729
- ordinalMappings(): { [key: string]: string };
730
- ordinalMappings_exists(): boolean;
731
- padding(): number;
732
- padding(_: number): this;
733
- }
734
-
735
- Axis.prototype.publish("type", "linear", "set", "Type", ["none", "ordinal", "linear", "pow", "log", "time"]);
736
- Axis.prototype.publish("timePattern", "%Y-%m-%d", "string", "Time Series Pattern", null, { disable: (w: any) => w.type() !== "time" });
737
- Axis.prototype.publish("reverse", false, "boolean", "Reverse");
738
-
739
- Axis.prototype.publish("title", null, "string", "Title");
740
- Axis.prototype.publish("orientation", "bottom", "set", "Placement/orientation of the axis", ["left", "top", "right", "bottom"]);
741
- Axis.prototype.publish("powExponent", 2, "number", "Power exponent (disabled when type is not 'pow')", null, { disable: (w: any) => w.type() !== "pow" });
742
- Axis.prototype.publish("logBase", 10, "number", "Logarithmic base (disabled when type is not 'log')", null, { disable: (w: any) => w.type() !== "log" });
743
- Axis.prototype.publish("ordinals", [], "array", "Array of ordinal values to display (disabled when type is not 'ordinal')", null, { disable: (w: any) => w.type() !== "ordinal" });
744
- Axis.prototype.publish("fontSize", null, "number", "Size of tick label font (pixels)", null, { optional: true });
745
- Axis.prototype.publish("fontFamily", null, "string", "Font family of tick labels", null, { optional: true });
746
- Axis.prototype.publish("tickCount", null, "number", "Number of ticks to display (disabled when type is 'ordinal')", null, { optional: true, disable: (w: any) => w.type() === "ordinal" });
747
- Axis.prototype.publish("tickFormat", null, "string", "Format rules for tick text (disabled when type is 'ordinal')", null, { optional: true, disable: (w: any) => w.type() === "ordinal" });
748
- Axis.prototype.publish("tickLength", null, "number", "Height (or width for left/right orientations) of the axis ticks (in pixels)", null, { optional: true });
749
- Axis.prototype.publish("ticks", [], "array", "Custom tick labels", null, { optional: true }); // TODO: What does this control?
750
- Axis.prototype.publish("low", null, "any", "Minimum tick value (disabled when type is ordinal)", null, { optional: true, disable: (w: any) => w.type() === "ordinal" });
751
- Axis.prototype.publish("high", null, "any", "Maximum tick value (disabled when type is ordinal)", null, { optional: true, disable: (w: any) => w.type() === "ordinal" });
752
- Axis.prototype.publish("overlapMode", "none", "set", "Specifies the behavior when tick labels overlap", ["none", "stagger", "hide", "rotate", "linebreak", "wrap"]);
753
- Axis.prototype.publish("labelRotation", 33, "number", "Angle of rotation for tick labels (disabled when overlapMode is not 'rotate')", null, { optional: true, disable: (w: any) => w.overlapMode() !== "rotate" });
754
- Axis.prototype.publish("shrinkToFit", "both", "set", "shrinkToFit", ["none", "low", "high", "both"]); // TODO: What does this control?
755
- Axis.prototype.publish("extend", 5, "number", "Extend the axis range by this % beyond what is needed to display the data (disabled when type is 'ordinal')", null, { optional: true, disable: (w: any) => w.type() === "ordinal" });
756
- Axis.prototype.publish("hidden", false, "boolean", "Hides axis when 'true'");
757
-
758
- Axis.prototype.publish("ordinalPaddingInner", 0.1, "number", "Determines the ratio of the range that is reserved for blank space between band (0->1)", null, { disable: (w: Axis) => w.type() !== "ordinal" });
759
- Axis.prototype.publish("ordinalPaddingOuter", 0.1, "number", "Determines the ratio of the range that is reserved for blank space before the first band and after the last band (0->1)", null, { disable: (w: Axis) => w.type() !== "ordinal" });
760
- Axis.prototype.publish("ordinalMappings", null, "object", "Alternative label mappings (icons)", null, { optional: true });
761
- Axis.prototype.publish("padding", 0, "number", "Padding space at top of axis (pixels)", null, { optional: true });
762
-
763
- Axis.prototype._origType = Axis.prototype.type;
764
- Axis.prototype.type = function (_?: string) {
765
- const retVal = Axis.prototype._origType.apply(this, arguments);
766
- if (_ !== undefined) {
767
- this._type = _;
768
- this.updateScale();
769
- }
770
- return retVal;
771
- };
772
-
773
- Axis.prototype._origTimePattern = Axis.prototype.timePattern;
774
- Axis.prototype.timePattern = function (_?: string) {
775
- const retVal = Axis.prototype._origTimePattern.apply(this, arguments);
776
- if (_ !== undefined) {
777
- this._timePattern = _;
778
- this.updateScale();
779
- }
780
- return retVal;
781
- };
1
+ import { SVGWidget } from "@hpcc-js/common";
2
+ import { axisBottom as d3AxisBottom, axisLeft as d3AxisLeft, axisRight as d3AxisRight, axisTop as d3AxisTop } from "d3-axis";
3
+ import { format as d3Format } from "d3-format";
4
+ import { scaleBand as d3ScaleBand, scaleLinear as d3ScaleLinear, scaleLog as d3ScaleLog, scalePow as d3ScalePow, scaleTime as d3ScaleTime } from "d3-scale";
5
+ import { select as d3Select } from "d3-selection";
6
+ import { timeFormat as d3TimeFormat, timeParse as d3TimeParse } from "d3-time-format";
7
+
8
+ import "../src/Axis.css";
9
+
10
+ export interface IOverflow {
11
+ left: number;
12
+ top: number;
13
+ right: number;
14
+ bottom: number;
15
+ depth: number;
16
+ tickOverlapModulus: number;
17
+ }
18
+
19
+ export class Axis extends SVGWidget {
20
+ _origType;
21
+ _origTimePattern;
22
+
23
+ protected parser;
24
+ protected parserInvert;
25
+ protected formatter: ((d: Date | any) => string) | null;
26
+ protected d3Scale;
27
+ protected d3Axis;
28
+ protected d3Guides;
29
+ protected _guideElement;
30
+ protected svg;
31
+ protected svgAxis;
32
+ protected svgGuides;
33
+ protected _tickFormatFunc?: (d: any) => string;
34
+
35
+ constructor(drawStartPosition: "origin" | "center" = "origin") {
36
+ super();
37
+ this._drawStartPos = drawStartPosition;
38
+
39
+ this.updateScale();
40
+ }
41
+
42
+ lowValue() {
43
+ return this.parse(this.low());
44
+ }
45
+
46
+ highValue() {
47
+ return this.parse(this.high());
48
+ }
49
+
50
+ parse(d, forceNumeric?) {
51
+ if (d instanceof Array) {
52
+ return d.map(function (d2) {
53
+ return this.parse(d2);
54
+ }, this);
55
+ }
56
+ if (d !== undefined && d !== null) {
57
+ if (this.parser) {
58
+ return this.parser(typeof d === "number" ? d.toString() : d);
59
+ }
60
+ if (forceNumeric && typeof d === "string") {
61
+ return +d;
62
+ }
63
+ }
64
+ return d;
65
+ }
66
+
67
+ parseInvert(d) {
68
+ if (d instanceof Array) {
69
+ return d.map(function (d2) {
70
+ return this.parseInvert(d2);
71
+ }, this);
72
+ }
73
+ if (this.parserInvert && d) {
74
+ return this.parserInvert(d);
75
+ }
76
+ return d;
77
+ }
78
+
79
+ format(d) {
80
+ if (d instanceof Array) {
81
+ return d.map(function (d2) {
82
+ return this.format(d2);
83
+ }, this);
84
+ }
85
+ if (d !== undefined && d !== null && this.formatter) {
86
+ return this.formatter(d);
87
+ }
88
+ return d;
89
+ }
90
+
91
+ parseFormat(d) {
92
+ return this.format(this.parse(d));
93
+ }
94
+
95
+ tickFormatFunc(fn: (d: any) => string): this;
96
+ tickFormatFunc(): ((d: any) => string) | undefined;
97
+ tickFormatFunc(fn?: (d: any) => string): this | ((d: any) => string) | undefined {
98
+ if (!arguments.length) return this._tickFormatFunc;
99
+ this._tickFormatFunc = fn;
100
+ this.updateScale();
101
+ return this;
102
+ }
103
+
104
+ scalePos(d) {
105
+ let retVal = this.d3Scale(this.parse(d));
106
+ if (this.type() === "ordinal") {
107
+ retVal += this.bandwidth() / 2;
108
+ }
109
+ return retVal;
110
+ }
111
+
112
+ bandwidth() {
113
+ return this.d3Scale.bandwidth ? this.d3Scale.bandwidth() : 0;
114
+ }
115
+
116
+ isHorizontal() {
117
+ switch (this.orientation()) {
118
+ case "left":
119
+ case "right":
120
+ return false;
121
+ default:
122
+ }
123
+ return true;
124
+ }
125
+
126
+ domain(_?) {
127
+ if (!arguments.length) return this.d3Scale.domain();
128
+ this.d3Scale.domain(_);
129
+ return this;
130
+ }
131
+
132
+ range(_?) {
133
+ if (!arguments.length) {
134
+ if (this.d3Scale.rangeRoundBands) {
135
+ return this.d3Scale.rangeExtent();
136
+ } else if (this.d3Scale.rangeRound) {
137
+ return this.d3Scale.range();
138
+ }
139
+ }
140
+ if (this.d3Scale.rangeRoundBands) {
141
+ this.d3Scale.rangeRoundBands(_, 0.1);
142
+ } else if (this.d3Scale.rangeRound) {
143
+ this.d3Scale.range(_);
144
+ }
145
+ return this;
146
+ }
147
+
148
+ invert(pos) {
149
+ return this.d3Scale.invert(pos);
150
+ }
151
+
152
+ guideTarget(_) {
153
+ this._guideElement = d3Select(_)
154
+ .attr("class", this._class)
155
+ ;
156
+ return this;
157
+ }
158
+
159
+ enter(domNode, element) {
160
+ super.enter(domNode, element);
161
+ this.svg = element.append("g");
162
+ this.svgAxis = this.svg.append("g")
163
+ .attr("class", "axis")
164
+ ;
165
+ this.svgGuides = (this._guideElement || element).append("g")
166
+ .attr("class", "guide")
167
+ ;
168
+ }
169
+
170
+ protected _prevOrientation;
171
+ updateScale(): this {
172
+ switch (this.type()) {
173
+ case "ordinal":
174
+ this.d3Scale = d3ScaleBand()
175
+ .paddingInner(this.ordinalPaddingInner())
176
+ .paddingOuter(this.ordinalPaddingOuter())
177
+ ;
178
+ if (this.ordinals_exists()) {
179
+ this.d3Scale.domain(this.ordinals());
180
+ }
181
+ this.parser = null;
182
+ if (this.ordinalMappings_exists()) {
183
+ const mappings = this.ordinalMappings();
184
+ this.formatter = (_: any) => mappings[_] || _;
185
+ } else {
186
+ this.formatter = null;
187
+ }
188
+ break;
189
+ case "linear":
190
+ this.d3Scale = d3ScaleLinear();
191
+ if (this.low_exists() && this.high_exists()) {
192
+ this.d3Scale.domain([this.lowValue(), this.highValue()]);
193
+ }
194
+ this.parser = null;
195
+ this.formatter = this.tickFormat_exists() ? d3Format(this.tickFormat()) : null;
196
+ break;
197
+ case "pow":
198
+ this.d3Scale = d3ScalePow()
199
+ .exponent(this.powExponent())
200
+ ;
201
+ if (this.low_exists() && this.high_exists()) {
202
+ this.d3Scale.domain([this.lowValue(), this.highValue()]);
203
+ }
204
+ this.parser = null;
205
+ this.formatter = this.tickFormat_exists() ? d3Format(this.tickFormat()) : null;
206
+ break;
207
+ case "log":
208
+ this.d3Scale = d3ScaleLog()
209
+ .base(this.logBase())
210
+ ;
211
+ if (this.low_exists() && this.high_exists()) {
212
+ this.d3Scale.domain([this.lowValue(), this.highValue()]);
213
+ }
214
+ this.parser = null;
215
+ this.formatter = this.tickFormat_exists() ? d3Format(this.tickFormat()) : null;
216
+ break;
217
+ case "time":
218
+ this.d3Scale = d3ScaleTime();
219
+ if (this.low_exists() && this.high_exists()) {
220
+ this.d3Scale.domain([this.lowValue(), this.highValue()]);
221
+ }
222
+ this.parser = this.timePattern_exists() ? d3TimeParse(this.timePattern()) : null;
223
+ this.parserInvert = this.timePattern_exists() ? d3TimeFormat(this.timePattern()) : null;
224
+ if (this._tickFormatFunc) {
225
+ this.formatter = this._tickFormatFunc;
226
+ } else {
227
+ this.formatter = this.tickFormat_exists() ? d3TimeFormat(this.tickFormat()) : null;
228
+ }
229
+ break;
230
+ default:
231
+ }
232
+ if (this._prevOrientation !== this.orientation()) {
233
+ switch (this.orientation()) {
234
+ case "left":
235
+ this.d3Axis = d3AxisLeft(this.d3Scale);
236
+ this.d3Guides = d3AxisLeft(this.d3Scale);
237
+ break;
238
+ case "top":
239
+ this.d3Axis = d3AxisTop(this.d3Scale);
240
+ this.d3Guides = d3AxisTop(this.d3Scale);
241
+ break;
242
+ case "right":
243
+ this.d3Axis = d3AxisRight(this.d3Scale);
244
+ this.d3Guides = d3AxisRight(this.d3Scale);
245
+ break;
246
+ case "bottom":
247
+ default:
248
+ this.d3Axis = d3AxisBottom(this.d3Scale);
249
+ this.d3Guides = d3AxisBottom(this.d3Scale);
250
+ break;
251
+ }
252
+ this._prevOrientation = this.orientation();
253
+ if (this.svgAxis) {
254
+ this.svgAxis.html("");
255
+ }
256
+ if (this.svgGuides) {
257
+ this.svgGuides.html("");
258
+ }
259
+ }
260
+
261
+ if (this.extend()) {
262
+ switch (this.type()) {
263
+ case "ordinal":
264
+ break;
265
+ default:
266
+ let length;
267
+ let delta;
268
+ let low;
269
+ let high;
270
+ let newLow;
271
+ let newHigh;
272
+ if (this.isHorizontal()) {
273
+ length = this.width();
274
+ this.d3Scale.range([0, length]);
275
+ delta = length * this.extend() / 100;
276
+ low = this.d3Scale.invert(0);
277
+ newLow = this.d3Scale.invert(-delta);
278
+ high = this.d3Scale.invert(length);
279
+ newHigh = this.d3Scale.invert(length + delta);
280
+ } else {
281
+ length = this.height();
282
+ this.d3Scale.range([length, 0]);
283
+ delta = length * this.extend() / 100;
284
+ low = this.d3Scale.invert(length);
285
+ newLow = this.d3Scale.invert(length + delta);
286
+ high = this.d3Scale.invert(0);
287
+ newHigh = this.d3Scale.invert(-delta);
288
+ }
289
+ if (newLow === low) { // Edge case when there is only one item in the domain ---
290
+ newLow = low - low * this.extend() / 100;
291
+ }
292
+ if (newHigh === high) { // Edge case when there is only one item in the domain ---
293
+ newHigh = high + high * this.extend() / 100;
294
+ }
295
+ if ((Math as any).sign(low) !== (Math as any).sign(newLow)) {
296
+ newLow = 0;
297
+ }
298
+ if ((Math as any).sign(high) !== (Math as any).sign(newHigh)) {
299
+ newHigh = 0;
300
+ }
301
+ this.d3Scale.domain([newLow, newHigh]);
302
+ break;
303
+ }
304
+ }
305
+
306
+ this.d3Axis
307
+ .scale(this.d3Scale)
308
+ .tickFormat(this.formatter)
309
+ .ticks(this.tickCount())
310
+ ;
311
+ this.d3Guides
312
+ .scale(this.d3Scale)
313
+ .tickSize(this.tickLength_exists() ? -this.tickLength() : 0)
314
+ .tickFormat("")
315
+ .ticks(this.tickCount())
316
+ ;
317
+ const customTicks = this.ticks();
318
+ if (customTicks.length) {
319
+ this.d3Axis
320
+ .tickValues(customTicks.map(d => this.parse(d.value)))
321
+ .tickFormat((_d, i) => {
322
+ return customTicks[i].label;
323
+ });
324
+ this.d3Guides
325
+ .tickValues(customTicks.map(d => this.parse(d.value)));
326
+ }
327
+ return this;
328
+ }
329
+
330
+ adjustText(svg, tickOverlapModulus) {
331
+ const isHoriztontal = this.isHorizontal();
332
+ const isLeft = this.orientation() === "left";
333
+ const isBottom = this.orientation() === "bottom";
334
+ const context = this;
335
+ const textSelection = svg.selectAll(".tick > text")
336
+ .style("font-family", this.fontFamily())
337
+ .style("font-size", this.fontSize_exists() ? this.fontSize() + "px" : null)
338
+ ;
339
+ if (this.overlapMode() === "linebreak") {
340
+ if (this.type() === "ordinal") {
341
+ textSelection
342
+ .call(function () {
343
+ return context.linebreak.apply(context, arguments);
344
+ }, this.bandwidth())
345
+ ;
346
+ }
347
+ } else if (this.overlapMode() === "wrap") {
348
+ if (this.type() === "ordinal") {
349
+ textSelection
350
+ .call(function () {
351
+ return context.wrap.apply(context, arguments);
352
+ }, this.bandwidth())
353
+ ;
354
+ }
355
+ } else {
356
+ switch (isHoriztontal ? this.overlapMode() : "none") {
357
+ case "stagger":
358
+ textSelection
359
+ .style("text-anchor", "middle")
360
+ .attr("dy", function (_d, i) { return (isBottom ? 1 : -1) * ((isBottom ? 0.71 : 0) + i % tickOverlapModulus) + "em"; })
361
+ .attr("dx", 0)
362
+ .attr("visibility", null)
363
+ .attr("transform", "rotate(0)")
364
+ ;
365
+ break;
366
+ case "hide":
367
+ textSelection
368
+ .style("text-anchor", "middle")
369
+ .attr("dy", (isBottom ? 0.71 : 0) + "em")
370
+ .attr("dx", 0)
371
+ .attr("visibility", function (_d, i) { return i % tickOverlapModulus ? "hidden" : null; })
372
+ .attr("transform", "rotate(0)")
373
+ ;
374
+ break;
375
+ case "rotate":
376
+ const deg = -(this.labelRotation()) || 0;
377
+ if (deg !== 0 && tickOverlapModulus > 1) {
378
+ textSelection
379
+ .each(function () {
380
+ const elm = d3Select(this);
381
+ const bbox = elm.node().getBBox();
382
+ const dyOff = (isBottom ? 1 : -1) * Math.sin(Math.PI * (-Math.abs(deg) / 180));
383
+ elm
384
+ .style("text-anchor", deg > 0 ? (isBottom ? "start" : "end") : (isBottom ? "end" : "start"))
385
+ .attr("dy", (bbox.height / 2 * dyOff) + "px")
386
+ .attr("dx", deg > 0 ? (isBottom ? "0.71em" : "-0.71em") : (isBottom ? "-0.71em" : "0.71em"))
387
+ .attr("transform", "rotate(" + deg + ")")
388
+ .attr("visibility", null)
389
+ ;
390
+ })
391
+ ;
392
+ break;
393
+ }
394
+ /* falls through */
395
+ default:
396
+ textSelection
397
+ .style("text-anchor", isHoriztontal ? "middle" : isLeft ? "end" : "start")
398
+ .attr("dy", isHoriztontal ? ((isBottom ? 0.71 : 0) + "em") : "0.32em")
399
+ .attr("dx", 0)
400
+ .attr("visibility", null)
401
+ .attr("transform", "rotate(0)")
402
+ ;
403
+ }
404
+ }
405
+ }
406
+
407
+ calcTickOverlapModulus(element) {
408
+ let retVal = 1;
409
+ switch (this.overlapMode()) {
410
+ case "rotate":
411
+ case "stagger":
412
+ case "hide":
413
+ const bboxArr = [];
414
+ element.selectAll(".tick > text").each(function () {
415
+ const bbox = this.getBoundingClientRect();
416
+ for (let i = bboxArr.length - 1; i >= 0; --i) {
417
+ if (bboxArr[i].right < bbox.left) {
418
+ break;
419
+ }
420
+ if (bboxArr.length + 1 - i > retVal) {
421
+ retVal = bboxArr.length + 1 - i;
422
+ }
423
+ }
424
+ bboxArr.push(bbox);
425
+ });
426
+ break;
427
+ default:
428
+ }
429
+ return retVal;
430
+ }
431
+
432
+ calcOverflow(element, ignoreText?): IOverflow {
433
+ this.updateScale();
434
+ if (this.hidden()) {
435
+ return {
436
+ left: 0,
437
+ top: 0,
438
+ right: 0,
439
+ bottom: 0,
440
+ depth: 0,
441
+ tickOverlapModulus: 1
442
+ };
443
+ }
444
+ const isHorizontal = this.isHorizontal();
445
+ this.range(isHorizontal ? [0, this.width()] : [this.height(), 0]);
446
+ const tmpSvg = element.append("g").attr("class", this.classID());
447
+ const tmpSvgG = tmpSvg.append("g");
448
+ tmpSvgG
449
+ .attr("class", isHorizontal ? "x" : "y")
450
+ .call(this.d3Axis)
451
+ ;
452
+ if (ignoreText) {
453
+ element.selectAll(".tick > text").remove();
454
+ }
455
+
456
+ const retVal: IOverflow = {
457
+ left: 0,
458
+ top: 0,
459
+ right: 0,
460
+ bottom: 0,
461
+ depth: 0,
462
+ tickOverlapModulus: this.calcTickOverlapModulus(tmpSvgG)
463
+ };
464
+ this.adjustText(tmpSvgG, retVal.tickOverlapModulus);
465
+
466
+ const bbox = tmpSvgG.node().getBBox();
467
+ retVal.depth = isHorizontal ? bbox.height : bbox.width;
468
+ switch (this.shrinkToFit()) {
469
+ case "low":
470
+ case "both":
471
+ retVal.left = isHorizontal ? -bbox.x : 0;
472
+ retVal.bottom = isHorizontal ? 0 : -(this.height() - (bbox.height + bbox.y));
473
+ break;
474
+ default:
475
+ }
476
+ switch (this.shrinkToFit()) {
477
+ case "high":
478
+ case "both":
479
+ retVal.top = isHorizontal ? 0 : -bbox.y;
480
+ retVal.right = isHorizontal ? -(this.width() - bbox.x - bbox.width) : 0;
481
+ break;
482
+ default:
483
+ }
484
+ tmpSvg.remove();
485
+
486
+ return retVal;
487
+ }
488
+
489
+ wrap(_text, bandSize, re) {
490
+ re = re || /\s+/;
491
+ const context = this;
492
+ _text.each(function () {
493
+ const text = d3Select(this);
494
+ const words = text.text().split(re).reverse();
495
+ let line = [];
496
+ let lineNumber = 0;
497
+ const lineHeight = 1.1;
498
+ const x = text.attr("x");
499
+ const y = text.attr("y");
500
+ const fs = parseFloat(text.style("font-size")) || 10;
501
+ const maxLinesPerBand = Math.floor(bandSize / (fs * lineHeight)) - 1;
502
+ const minWordsPerLine = context.isHorizontal() ? 1 : Math.ceil(words.length / maxLinesPerBand);
503
+ const dy = parseFloat(text.attr("dy"))
504
+ ;
505
+ let tspan = text.text(null).append("tspan")
506
+ .attr("x", x)
507
+ .attr("y", y)
508
+ .attr("dy", dy + "em")
509
+ ;
510
+ let wordsOnLine = 0;
511
+ let word = words.pop();
512
+ while (word) {
513
+ line.push(word);
514
+ tspan.text(line.join(" "));
515
+ wordsOnLine++;
516
+ if ((tspan.node() as any).getComputedTextLength() > bandSize && wordsOnLine >= minWordsPerLine) {
517
+ line.pop();
518
+ tspan.text(line.join(" "));
519
+ line = [word];
520
+ tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
521
+ wordsOnLine = 0;
522
+ }
523
+ word = words.pop();
524
+ }
525
+ if (!context.isHorizontal()) {
526
+ text.selectAll("tspan")
527
+ .attr("y", (-lineNumber / 2) + "em")
528
+ ;
529
+ }
530
+ });
531
+ }
532
+
533
+ linebreak(text, bandSize) {
534
+ this.wrap(text, bandSize, "\n");
535
+ }
536
+
537
+ update(domNode, element) {
538
+ super.update(domNode, element);
539
+
540
+ this.svg.style("display", this.hidden() ? "none" : null);
541
+
542
+ const overlap = this.calcOverflow(element);
543
+
544
+ const lowerPos: number = this.isHorizontal() ? overlap.left : this.height() - overlap.top - overlap.bottom;
545
+ const upperPos: number = this.isHorizontal() ? this.width() - overlap.right - this.padding() : 0 + this.padding();
546
+
547
+ this.range(this.reverse() ? [upperPos, lowerPos] : [lowerPos, upperPos]);
548
+
549
+ const context = this;
550
+ function doPosition(element) {
551
+ element.attr("transform", function () {
552
+ switch (context.orientation()) {
553
+ case "left":
554
+ return "translate(" + overlap.depth + ", " + overlap.top + ")";
555
+ case "top":
556
+ return "translate(0," + overlap.depth + ")";
557
+ case "right":
558
+ return "translate(" + (context.width() - overlap.depth) + ", " + overlap.top + ")";
559
+ case "bottom":
560
+ return "translate(0," + (context.height() - overlap.depth) + ")";
561
+ default:
562
+ }
563
+ return "translate(0,0)";
564
+ });
565
+ }
566
+ this.svg
567
+ // .attr("class", this.isHorizontal() ? "x" : "y")
568
+ .style("visibility", this.type() === "none" ? "hidden" : null)
569
+ .transition()
570
+ .call(doPosition)
571
+ ;
572
+ if (this._guideElement) {
573
+ this.svgGuides
574
+ .transition()
575
+ .call(doPosition)
576
+ ;
577
+ }
578
+ this.svgAxis
579
+ .call(this.d3Axis)
580
+ ;
581
+ this.adjustText(this.svgAxis, overlap.tickOverlapModulus);
582
+
583
+ const svgLineBBox = {
584
+ x: this.pos().x,
585
+ width: this.width()
586
+ };
587
+ const svgText = this.svgAxis.selectAll(".axisTitle").data(this.title() ? [this.title()] : []);
588
+ const svgTextUpdate = svgText.enter().append("text")
589
+ .attr("class", "axisTitle")
590
+ .merge(svgText)
591
+ ;
592
+ const svgTitleTransition = svgTextUpdate.transition()
593
+ .attr("dx", null)
594
+ .style("text-anchor", "end")
595
+ ;
596
+ switch (this.orientation()) {
597
+ case "left":
598
+ svgTitleTransition
599
+ .attr("transform", "rotate(-90)")
600
+ .attr("x", -2)
601
+ .attr("y", 2)
602
+ .attr("dy", ".71em")
603
+ ;
604
+ break;
605
+ case "right":
606
+ svgTitleTransition
607
+ .attr("transform", "rotate(-90)")
608
+ .attr("x", -2)
609
+ .attr("y", 4)
610
+ .attr("dx", null)
611
+ .attr("dy", "-.71em")
612
+ ;
613
+ break;
614
+ case "top":
615
+ svgTitleTransition
616
+ .attr("transform", "rotate(0)")
617
+ .attr("x", svgLineBBox.width - 2)
618
+ .attr("y", 2)
619
+ .attr("dx", null)
620
+ .attr("dy", ".71em")
621
+ ;
622
+ break;
623
+ case "bottom":
624
+ svgTitleTransition
625
+ .attr("transform", "rotate(0)")
626
+ .attr("x", svgLineBBox.width - 2)
627
+ .attr("y", -2)
628
+ .attr("dy", null)
629
+ ;
630
+ break;
631
+ default:
632
+ }
633
+ svgTitleTransition
634
+ .text(this.title_exists() ? this.title() : "")
635
+ ;
636
+ svgText.exit().remove();
637
+ this.svgGuides
638
+ .call(this.d3Guides)
639
+ .selectAll(".tick").classed("guide-0", d => d === 0 && this.low() < 0)
640
+ ;
641
+ }
642
+
643
+ rerender() {
644
+ this.svgAxis.call(this.d3Axis);
645
+ this.svgGuides.call(this.d3Guides);
646
+ }
647
+
648
+ postUpdate(domNode, element) {
649
+ super.postUpdate(domNode, element);
650
+ if (this._guideElement) {
651
+ this._guideElement
652
+ .attr("transform", this._element.attr("transform"))
653
+ ;
654
+ }
655
+ }
656
+ }
657
+ Axis.prototype._class += " chart_Axis";
658
+
659
+ export interface Axis {
660
+ type(): string;
661
+ type(_: string): this;
662
+ type_exists(): boolean;
663
+ timePattern(): string;
664
+ timePattern(_: string): this;
665
+ timePattern_exists(): boolean;
666
+ reverse(): boolean;
667
+ reverse(_: boolean): this;
668
+
669
+ title(): string;
670
+ title(_: string): this;
671
+ title_exists(): boolean;
672
+ orientation(): string;
673
+ orientation(_: string): this;
674
+ orientation_default(): string;
675
+ orientation_default(_: string): this;
676
+ powExponent(): number;
677
+ powExponent(_: number): this;
678
+ logBase(): number;
679
+ logBase(_: number): this;
680
+ ordinals(): string[];
681
+ ordinals(_: string[]): this;
682
+ ordinals_exists(): boolean;
683
+ fontSize(): number;
684
+ fontSize(_: number): this;
685
+ fontSize_exists(): boolean;
686
+ fontFamily(): string;
687
+ fontFamily(_: string): this;
688
+ tickCount(): number;
689
+ tickCount(_: number): this;
690
+ tickFormat(): string;
691
+ tickFormat(_: string): this;
692
+ tickFormat_exists(): boolean;
693
+ tickFormat_reset(): void;
694
+ tickFormatFunc(fn: (d: any) => string): this;
695
+ tickFormatFunc(): ((d: any) => string) | undefined;
696
+ tickLength(): number;
697
+ tickLength(_: number): this;
698
+ tickLength_exists(): boolean;
699
+ ticks(): Array<{ value: string, label: string }>;
700
+ ticks(_: Array<{ value: string, label: string }>): this;
701
+ low(): any;
702
+ low(_: any): this;
703
+ low_exists(): boolean;
704
+ high(): any;
705
+ high(_: any): this;
706
+ high_exists(): boolean;
707
+ overlapMode(): string;
708
+ overlapMode(_: string): this;
709
+ overlapMode_default(): string;
710
+ overlapMode_default(_: string): this;
711
+ labelRotation(): number;
712
+ labelRotation(_: number): this;
713
+ shrinkToFit(): string;
714
+ shrinkToFit(_: string): this;
715
+ shrinkToFit_default(): string;
716
+ shrinkToFit_default(_: string): this;
717
+ extend(): number;
718
+ extend(_: number): this;
719
+ extend_default(): number;
720
+ extend_default(_: number): this;
721
+ hidden(): boolean;
722
+ hidden(_: boolean): this;
723
+
724
+ ordinalPaddingInner(): number;
725
+ ordinalPaddingInner(_: number): this;
726
+ ordinalPaddingOuter(): number;
727
+ ordinalPaddingOuter(_: number): this;
728
+ ordinalMappings(_: { [key: string]: string }): this;
729
+ ordinalMappings(): { [key: string]: string };
730
+ ordinalMappings_exists(): boolean;
731
+ padding(): number;
732
+ padding(_: number): this;
733
+ }
734
+
735
+ Axis.prototype.publish("type", "linear", "set", "Type", ["none", "ordinal", "linear", "pow", "log", "time"]);
736
+ Axis.prototype.publish("timePattern", "%Y-%m-%d", "string", "Time Series Pattern", null, { disable: (w: any) => w.type() !== "time" });
737
+ Axis.prototype.publish("reverse", false, "boolean", "Reverse");
738
+
739
+ Axis.prototype.publish("title", null, "string", "Title");
740
+ Axis.prototype.publish("orientation", "bottom", "set", "Placement/orientation of the axis", ["left", "top", "right", "bottom"]);
741
+ Axis.prototype.publish("powExponent", 2, "number", "Power exponent (disabled when type is not 'pow')", null, { disable: (w: any) => w.type() !== "pow" });
742
+ Axis.prototype.publish("logBase", 10, "number", "Logarithmic base (disabled when type is not 'log')", null, { disable: (w: any) => w.type() !== "log" });
743
+ Axis.prototype.publish("ordinals", [], "array", "Array of ordinal values to display (disabled when type is not 'ordinal')", null, { disable: (w: any) => w.type() !== "ordinal" });
744
+ Axis.prototype.publish("fontSize", null, "number", "Size of tick label font (pixels)", null, { optional: true });
745
+ Axis.prototype.publish("fontFamily", null, "string", "Font family of tick labels", null, { optional: true });
746
+ Axis.prototype.publish("tickCount", null, "number", "Number of ticks to display (disabled when type is 'ordinal')", null, { optional: true, disable: (w: any) => w.type() === "ordinal" });
747
+ Axis.prototype.publish("tickFormat", null, "string", "Format rules for tick text (disabled when type is 'ordinal')", null, { optional: true, disable: (w: any) => w.type() === "ordinal" });
748
+ Axis.prototype.publish("tickLength", null, "number", "Height (or width for left/right orientations) of the axis ticks (in pixels)", null, { optional: true });
749
+ Axis.prototype.publish("ticks", [], "array", "Custom tick labels", null, { optional: true }); // TODO: What does this control?
750
+ Axis.prototype.publish("low", null, "any", "Minimum tick value (disabled when type is ordinal)", null, { optional: true, disable: (w: any) => w.type() === "ordinal" });
751
+ Axis.prototype.publish("high", null, "any", "Maximum tick value (disabled when type is ordinal)", null, { optional: true, disable: (w: any) => w.type() === "ordinal" });
752
+ Axis.prototype.publish("overlapMode", "none", "set", "Specifies the behavior when tick labels overlap", ["none", "stagger", "hide", "rotate", "linebreak", "wrap"]);
753
+ Axis.prototype.publish("labelRotation", 33, "number", "Angle of rotation for tick labels (disabled when overlapMode is not 'rotate')", null, { optional: true, disable: (w: any) => w.overlapMode() !== "rotate" });
754
+ Axis.prototype.publish("shrinkToFit", "both", "set", "shrinkToFit", ["none", "low", "high", "both"]); // TODO: What does this control?
755
+ Axis.prototype.publish("extend", 5, "number", "Extend the axis range by this % beyond what is needed to display the data (disabled when type is 'ordinal')", null, { optional: true, disable: (w: any) => w.type() === "ordinal" });
756
+ Axis.prototype.publish("hidden", false, "boolean", "Hides axis when 'true'");
757
+
758
+ Axis.prototype.publish("ordinalPaddingInner", 0.1, "number", "Determines the ratio of the range that is reserved for blank space between band (0->1)", null, { disable: (w: Axis) => w.type() !== "ordinal" });
759
+ Axis.prototype.publish("ordinalPaddingOuter", 0.1, "number", "Determines the ratio of the range that is reserved for blank space before the first band and after the last band (0->1)", null, { disable: (w: Axis) => w.type() !== "ordinal" });
760
+ Axis.prototype.publish("ordinalMappings", null, "object", "Alternative label mappings (icons)", null, { optional: true });
761
+ Axis.prototype.publish("padding", 0, "number", "Padding space at top of axis (pixels)", null, { optional: true });
762
+
763
+ Axis.prototype._origType = Axis.prototype.type;
764
+ Axis.prototype.type = function (_?: string) {
765
+ const retVal = Axis.prototype._origType.apply(this, arguments);
766
+ if (_ !== undefined) {
767
+ this._type = _;
768
+ this.updateScale();
769
+ }
770
+ return retVal;
771
+ };
772
+
773
+ Axis.prototype._origTimePattern = Axis.prototype.timePattern;
774
+ Axis.prototype.timePattern = function (_?: string) {
775
+ const retVal = Axis.prototype._origTimePattern.apply(this, arguments);
776
+ if (_ !== undefined) {
777
+ this._timePattern = _;
778
+ this.updateScale();
779
+ }
780
+ return retVal;
781
+ };