@hpcc-js/chart 2.86.2 → 2.86.3

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