@hpcc-js/chart 2.86.3 → 2.86.5

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 (84) hide show
  1. package/LICENSE +43 -43
  2. package/README.md +93 -93
  3. package/dist/index.es6.js +2 -2
  4. package/dist/index.es6.js.map +1 -1
  5. package/dist/index.js +2 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.min.js +1 -1
  8. package/dist/index.min.js.map +1 -1
  9. package/package.json +5 -5
  10. package/src/Area.md +176 -176
  11. package/src/Area.ts +12 -12
  12. package/src/Axis.css +34 -34
  13. package/src/Axis.ts +733 -733
  14. package/src/Bar.md +90 -90
  15. package/src/Bar.ts +9 -9
  16. package/src/Bubble.css +16 -16
  17. package/src/Bubble.md +69 -69
  18. package/src/Bubble.ts +191 -191
  19. package/src/BubbleXY.ts +14 -14
  20. package/src/Bullet.css +60 -60
  21. package/src/Bullet.md +104 -104
  22. package/src/Bullet.ts +167 -167
  23. package/src/Column.css +17 -17
  24. package/src/Column.md +90 -90
  25. package/src/Column.ts +659 -659
  26. package/src/Contour.md +88 -88
  27. package/src/Contour.ts +97 -97
  28. package/src/D3Cloud.ts +400 -400
  29. package/src/Gantt.md +119 -119
  30. package/src/Gantt.ts +14 -14
  31. package/src/Gauge.md +148 -148
  32. package/src/Gauge.ts +358 -358
  33. package/src/HalfPie.md +62 -62
  34. package/src/HalfPie.ts +26 -26
  35. package/src/Heat.md +42 -42
  36. package/src/Heat.ts +283 -283
  37. package/src/HexBin.css +9 -9
  38. package/src/HexBin.md +88 -88
  39. package/src/HexBin.ts +139 -139
  40. package/src/Line.css +6 -6
  41. package/src/Line.md +170 -170
  42. package/src/Line.ts +14 -14
  43. package/src/Pie.css +23 -23
  44. package/src/Pie.md +88 -88
  45. package/src/Pie.ts +503 -503
  46. package/src/QuarterPie.md +61 -61
  47. package/src/QuarterPie.ts +35 -35
  48. package/src/QuartileCandlestick.md +129 -129
  49. package/src/QuartileCandlestick.ts +349 -349
  50. package/src/Radar.css +15 -15
  51. package/src/Radar.md +104 -104
  52. package/src/Radar.ts +336 -336
  53. package/src/RadialBar.css +25 -25
  54. package/src/RadialBar.md +91 -91
  55. package/src/RadialBar.ts +212 -212
  56. package/src/Scatter.css +16 -16
  57. package/src/Scatter.md +163 -163
  58. package/src/Scatter.ts +376 -376
  59. package/src/StatChart.md +117 -117
  60. package/src/StatChart.ts +253 -253
  61. package/src/Step.md +163 -163
  62. package/src/Step.ts +12 -12
  63. package/src/Summary.css +56 -56
  64. package/src/Summary.md +219 -219
  65. package/src/Summary.ts +322 -322
  66. package/src/SummaryC.md +154 -154
  67. package/src/SummaryC.ts +240 -240
  68. package/src/WordCloud.css +3 -3
  69. package/src/WordCloud.md +144 -144
  70. package/src/WordCloud.ts +263 -263
  71. package/src/XYAxis.css +41 -41
  72. package/src/XYAxis.md +149 -149
  73. package/src/XYAxis.ts +803 -803
  74. package/src/__package__.ts +3 -3
  75. package/src/__tests__/heat.ts +71 -71
  76. package/src/__tests__/index.ts +3 -3
  77. package/src/__tests__/pie.ts +20 -20
  78. package/src/__tests__/stat.ts +16 -16
  79. package/src/__tests__/test3.ts +69 -69
  80. package/src/index.ts +27 -27
  81. package/src/test.ts +71 -71
  82. package/types/__package__.d.ts +2 -2
  83. package/types/__package__.d.ts.map +1 -1
  84. package/types-3.4/__package__.d.ts +2 -2
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 });