@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/Column.ts CHANGED
@@ -1,659 +1,659 @@
1
- import { INDChart, ITooltip } from "@hpcc-js/api";
2
- import { InputField, Text } from "@hpcc-js/common";
3
- import { format as d3Format } from "d3-format";
4
- import { scaleBand as d3ScaleBand } from "d3-scale";
5
- import { local as d3Local, select as d3Select } from "d3-selection";
6
- import { XYAxis } from "./XYAxis";
7
-
8
- import "../src/Column.css";
9
-
10
- export class Column extends XYAxis {
11
- static __inputs: InputField[] = [{
12
- id: "label",
13
- type: "string"
14
- }, {
15
- id: "values",
16
- type: "number",
17
- multi: true
18
- }];
19
-
20
- protected _linearGap: number;
21
- private textLocal = d3Local<Text>();
22
- private stackedTextLocal = d3Local<Text>();
23
- private isHorizontal: boolean;
24
-
25
- constructor() {
26
- super();
27
- INDChart.call(this);
28
- ITooltip.call(this);
29
-
30
- this._selection.skipBringToTop(true);
31
-
32
- this._linearGap = 25.0;
33
- }
34
-
35
- layerEnter(host: XYAxis, element, duration: number = 250) {
36
- super.layerEnter(host, element, duration);
37
- const context = this;
38
- this
39
- .tooltipHTML(function (d) {
40
- switch (context.tooltipStyle()) {
41
- case "series-table":
42
- return context.tooltipFormat({
43
- label: d.row[0],
44
- arr: context.columns().slice(1).map(function (column, i) {
45
- return {
46
- label: column,
47
- color: context._palette(column),
48
- value: d.row[i + 1]
49
- };
50
- })
51
- });
52
- default:
53
- let value = d.row[d.idx];
54
- if (value instanceof Array) {
55
- value = value[1] - value[0];
56
- }
57
- return context.tooltipFormat({ label: d.row[0], series: context.layerColumns(host)[d.idx], value });
58
- }
59
- })
60
- ;
61
- }
62
-
63
- adjustedData(host: XYAxis) {
64
- const retVal = this.layerData(host).map(row => {
65
- let prevValue = 0;
66
- return row.map((cell, idx) => {
67
- if (idx === 0) {
68
- return cell;
69
- }
70
- if (idx >= this.layerColumns(host).length) {
71
- return cell;
72
- }
73
- const retVal2 = host.yAxisStacked() ? [prevValue, prevValue + cell] : cell;
74
- prevValue += cell;
75
- return retVal2;
76
- }, this);
77
- }, this);
78
- return retVal;
79
- }
80
-
81
- layerUpdate(host: XYAxis, element, duration: number = 250) {
82
- super.layerUpdate(host, element, duration);
83
- const isHorizontal = host.orientation() === "horizontal";
84
- this.isHorizontal = isHorizontal;
85
- const context = this;
86
-
87
- this._palette = this._palette.switch(this.paletteID());
88
- if (this.useClonedPalette()) {
89
- this._palette = this._palette.cloneNotExists(this.paletteID() + "_" + this.id());
90
- }
91
- const formatPct = d3Format(context.showValueAsPercentFormat());
92
-
93
- let dataLen = 10;
94
- let offset = 0;
95
- switch (host.xAxisType()) {
96
- case "ordinal":
97
- dataLen = host.bandwidth();
98
- offset = -dataLen / 2;
99
- break;
100
- case "linear":
101
- case "time":
102
- dataLen = Math.max(Math.abs(host.dataPos(2) - host.dataPos(1)) * (100 - this._linearGap) / 100, dataLen);
103
- offset = -dataLen / 2;
104
- break;
105
- default:
106
- }
107
-
108
- this.tooltip.direction(isHorizontal ? "n" : "e");
109
-
110
- const columnScale = d3ScaleBand()
111
- .domain(context.layerColumns(host).filter(function (_d, idx) { return idx > 0; }))
112
- .rangeRound(isHorizontal ? [0, dataLen] : [dataLen, 0])
113
- .paddingInner(this.xAxisSeriesPaddingInner())
114
- .paddingOuter(0)
115
- ;
116
- let domainSums = [];
117
- const seriesSums = [];
118
- const columnLength = this.columns().length;
119
- const rowData = this.data();
120
- if (this.showValue() && this.showValueAsPercent() === "series") {
121
- rowData.forEach((row) => {
122
- row.filter((_, idx) => idx > 0 && idx < columnLength).forEach((col, idx) => {
123
- if (seriesSums[idx + 1] === undefined) {
124
- seriesSums[idx + 1] = 0;
125
- }
126
- seriesSums[idx + 1] += col;
127
- });
128
- });
129
- }
130
-
131
- if (this.showDomainTotal() || (this.showValue() && this.showValueAsPercent() === "domain")) {
132
- domainSums = rowData.map(row => {
133
- return row.filter((cell, idx) => idx > 0 && idx < columnLength).reduce((sum, cell) => {
134
- return sum + cell;
135
- }, 0);
136
- });
137
- }
138
-
139
- const column = element.selectAll(".dataRow")
140
- .data(this.adjustedData(host))
141
- ;
142
- const hostData = host.data();
143
- const axisSize = this.getAxisSize(host);
144
- column.enter().append("g")
145
- .attr("class", "dataRow")
146
- .merge(column)
147
- .each(function (dataRow, dataRowIdx) {
148
- const element = d3Select(this);
149
-
150
- const columnGRect = element.selectAll(".dataCell").data(dataRow.filter(function (_d, i) { return i < context.layerColumns(host).length; }).map(function (d, i) {
151
- return {
152
- column: context.layerColumns(host)[i],
153
- row: dataRow,
154
- origRow: hostData[dataRowIdx],
155
- value: d,
156
- idx: i
157
- };
158
- }).filter(function (d) { return d.value !== null && d.idx > 0; }), (d: any) => d.column);
159
-
160
- const columnGEnter = columnGRect
161
- .enter().append("g")
162
- .attr("class", "dataCell")
163
- .on("mouseout.tooltip", function (d: any) {
164
- if (!context.tooltipInnerTextEllipsedOnly() || (d.innerTextObj && d.innerTextObj.isTruncated)) {
165
- context.tooltip.hide.apply(context, arguments);
166
- }
167
- })
168
- .on("mousemove.tooltip", function (d: any) {
169
- if (!context.tooltipInnerTextEllipsedOnly() || (d.innerTextObj && d.innerTextObj.isTruncated)) {
170
- context.tooltip.show.apply(context, arguments);
171
- }
172
- })
173
- .call(host._selection.enter.bind(host._selection))
174
- .on("click", function (d: any) {
175
- context.click(host.rowToObj(d.origRow), d.column, host._selection.selected(this));
176
- })
177
- .on("dblclick", function (d: any) {
178
- context.dblclick(host.rowToObj(d.origRow), d.column, host._selection.selected(this));
179
- })
180
- .style("opacity", 0)
181
- .each(function (this: SVGElement, d: any) {
182
- const element = d3Select(this);
183
- element.append("rect")
184
- .attr("class", "columnRect series series-" + context.cssTag(d.column))
185
- ;
186
- element.append("text")
187
- .attr("class", "columnRectText")
188
- .style("stroke", "transparent")
189
- ;
190
- })
191
- ;
192
- columnGEnter.transition().duration(duration)
193
- .style("opacity", 1)
194
- ;
195
- const domainLength = host.yAxisStacked() ? dataLen : columnScale.bandwidth();
196
- columnGEnter.merge(columnGRect as any).each(function (this: SVGElement, d: any) {
197
- const element = d3Select(this);
198
- const domainPos = host.dataPos(dataRow[0]) + (host.yAxisStacked() ? 0 : columnScale(d.column)) + offset;
199
- const upperValue = d.value instanceof Array ? d.value[1] : d.value;
200
- let valueText = d.origRow[d.idx];
201
- if (context.showValue()) {
202
- const dm = context.dataMeta();
203
- switch (context.showValueAsPercent()) {
204
- case "series":
205
- const seriesSum = typeof dm.sum !== "undefined" ? dm.sum : seriesSums[d.idx];
206
- valueText = formatPct(valueText / seriesSum);
207
- break;
208
- case "domain":
209
- const domainSum = typeof dm.sum !== "undefined" ? dm.sum : domainSums[dataRowIdx];
210
- valueText = formatPct(valueText / domainSum);
211
- break;
212
- case null:
213
- default:
214
- valueText = d3Format(context.showValueFormat())(valueText);
215
- break;
216
- }
217
- }
218
- const upperValuePos = host.valuePos(upperValue);
219
- const lowerValuePos = host.valuePos(d.value instanceof Array ? d.value[0] : 0);
220
- const valuePos = Math.min(lowerValuePos, upperValuePos);
221
- const valueLength = Math.abs(upperValuePos - lowerValuePos);
222
-
223
- const innerTextHeight = context.innerTextFontSize();
224
- const innerTextPadding = context.innerTextPadding_exists() ? context.innerTextPadding() : innerTextHeight / 2.5;
225
-
226
- const dataRect = context.intersectRectRect(
227
- {
228
- x: isHorizontal ? domainPos : valuePos,
229
- y: isHorizontal ? valuePos : domainPos,
230
- width: isHorizontal ? domainLength : valueLength,
231
- height: isHorizontal ? valueLength : domainLength
232
- },
233
- {
234
- x: 0,
235
- y: 0,
236
- width: axisSize.width,
237
- height: axisSize.height
238
- }
239
- );
240
-
241
- const _rects = element.select("rect").transition().duration(duration)
242
- .style("fill", (d: any) => context.fillColor(d.row, d.column, d.value, d.origRow))
243
- ;
244
-
245
- if (isHorizontal) {
246
- _rects
247
- .attr("x", domainPos)
248
- .attr("y", valuePos)
249
- .attr("width", domainLength)
250
- .attr("height", valueLength)
251
- ;
252
- } else {
253
- _rects
254
- .attr("y", domainPos)
255
- .attr("x", valuePos)
256
- .attr("height", domainLength)
257
- .attr("width", valueLength)
258
- ;
259
- }
260
- const _texts = element.select("text").transition().duration(duration)
261
- .style("font-size", innerTextHeight + "px")
262
- .style("fill", (d: any) => context.textColor(d.row, d.column, d.value, d.origRow))
263
- ;
264
-
265
- _texts.style("font-family", context.innerTextFontFamily_exists() ? context.innerTextFontFamily() : null);
266
-
267
- const padding = context.innerTextPadding_exists() ? context.innerTextPadding() : 8;
268
-
269
- const textHeightOffset = innerTextHeight / 2.7;
270
-
271
- if (isHorizontal) { // Column
272
- const y = dataRect.y + dataRect.height - innerTextPadding;
273
- _texts
274
- .attr("x", domainPos + (domainLength / 2))
275
- .attr("y", y + textHeightOffset)
276
- .attr("transform", `rotate(-90, ${domainPos + (domainLength / 2)}, ${y})`)
277
- ;
278
- } else { // Bar
279
- _texts
280
- .attr("x", dataRect.x + padding)
281
- .attr("y", domainPos + (domainLength / 2) + textHeightOffset)
282
- ;
283
- }
284
- _texts
285
- .attr("height", domainLength)
286
- .attr("width", valueLength)
287
- ;
288
- if (context.showInnerText()) {
289
- _texts
290
- .text((d: any) => {
291
- const innerText = context.innerText(d.origRow, d.origRow[columnLength], d.idx);
292
- if (innerText) {
293
- const clippedValueLength = isHorizontal ? dataRect.height : dataRect.width;
294
- const innerTextObj = context.calcInnerText(clippedValueLength, innerText, valueText);
295
- d.innerTextObj = innerTextObj;
296
-
297
- return innerTextObj.text;
298
- }
299
- return "";
300
- })
301
- ;
302
- }
303
- const dataText = element.selectAll(".dataText").data(context.showValue() ? [`${upperValue}`] : []);
304
- const dataTextEnter = dataText.enter().append("g")
305
- .attr("class", "dataText")
306
- .each(function (this: SVGElement, d) {
307
- context.textLocal.set(this, new Text().target(this).colorStroke_default("transparent"));
308
- });
309
- dataTextEnter.merge(dataText as any)
310
- .each(function (this: SVGElement) {
311
- const pos = { x: 0, y: 0 };
312
- const valueFontFamily = context.valueFontFamily();
313
- const valueFontSize = context.valueFontSize();
314
- const textSize = context.textSize(valueText, valueFontFamily, valueFontSize);
315
-
316
- const isPositive = parseFloat(valueText) >= 0;
317
-
318
- let valueAnchor = context.valueAnchor() ? context.valueAnchor() : isHorizontal ? "middle" : "start";
319
-
320
- const leftSpace = dataRect.x;
321
- const rightSpace = axisSize.width - (dataRect.x + dataRect.width);
322
- const topSpace = dataRect.y;
323
- const bottomSpace = axisSize.height - (dataRect.y + dataRect.height);
324
-
325
- let noRoomInside;
326
- let isOutside;
327
- let noRoomOnExpectedSide;
328
-
329
- if (d.innerTextObj) {
330
- const { padding, valueTextWidth } = d.innerTextObj;
331
- isOutside = false;
332
- if (isHorizontal) { // Column
333
- valueAnchor = "middle";
334
- pos.x = domainPos + (domainLength / 2);
335
-
336
- if (d.innerTextObj.category === 4) {
337
- isOutside = true;
338
- pos.y = valuePos - padding - (valueFontSize / 2);
339
- } else {
340
- pos.y = valuePos + padding + (valueFontSize / 2);
341
- }
342
- } else { // Bar
343
- valueAnchor = "start";
344
- if (d.innerTextObj.category === 4) {
345
- isOutside = true;
346
- pos.x = (valueLength + valuePos) + padding;
347
- } else {
348
- pos.x = (valueLength + valuePos) - valueTextWidth - padding;
349
- }
350
- pos.y = domainPos + (domainLength / 2);
351
- }
352
- } else {
353
- /*
354
- IF this.valueCentered() and NO ROOM INSIDE
355
- ...then ASSUME THERES ROOM OUTSIDE
356
- IF NO ROOM OUTSIDE ON EXPECTED SIDE
357
- ...then ASSUME THERES ROOM ON THE OPPOSITE SIDE
358
- */
359
- if (isHorizontal) { // Column
360
- noRoomInside = dataRect.height < textSize.height;
361
- isOutside = !context.valueCentered() || noRoomInside;
362
-
363
- pos.x = dataRect.x + (dataRect.width / 2);
364
-
365
- if (isOutside) {
366
- if (isPositive) {
367
- noRoomOnExpectedSide = topSpace < textSize.height + padding;
368
- if (noRoomOnExpectedSide) {
369
- if (!noRoomInside) {
370
- isOutside = false;
371
- pos.y = dataRect.y + (dataRect.height / 2);
372
- } else {
373
- pos.y = dataRect.y + dataRect.height + textSize.height;
374
- }
375
- } else {
376
- pos.y = dataRect.y - (textSize.height / 2) - padding;
377
- }
378
- } else {
379
- noRoomOnExpectedSide = bottomSpace < textSize.height;
380
- if (noRoomOnExpectedSide) {
381
- if (!noRoomInside) {
382
- isOutside = false;
383
- pos.y = dataRect.y + (dataRect.height / 2);
384
- } else {
385
- pos.y = dataRect.y - (textSize.height / 2) - padding;
386
- }
387
- } else {
388
- pos.y = dataRect.y + textSize.height + padding;
389
- }
390
- }
391
- } else {
392
- pos.y = dataRect.y + (dataRect.height / 2);
393
- }
394
- } else { // Bar
395
- noRoomInside = dataRect.width < textSize.width;
396
- isOutside = !context.valueCentered() || noRoomInside;
397
-
398
- pos.y = dataRect.y + (dataRect.height / 2);
399
-
400
- if (isOutside) {
401
- if (isPositive) {
402
- noRoomOnExpectedSide = rightSpace < textSize.width + padding;
403
- if (noRoomOnExpectedSide) {
404
- if (context.showInnerText() || !noRoomInside) {
405
- isOutside = false;
406
- pos.x = dataRect.x + (dataRect.width / 2);
407
- } else {
408
- pos.x = dataRect.x - (textSize.width - padding);
409
- }
410
- } else {
411
- pos.x = dataRect.x + dataRect.width + (textSize.width / 2) + padding;
412
- }
413
- } else {
414
- noRoomOnExpectedSide = leftSpace < textSize.width;
415
- if (noRoomOnExpectedSide) {
416
- if (context.showInnerText() || !noRoomInside) {
417
- isOutside = false;
418
- pos.x = dataRect.x + (dataRect.width / 2);
419
- } else {
420
- pos.x = dataRect.x + dataRect.width + (textSize.width - padding);
421
- }
422
- } else {
423
- pos.x = dataRect.x - (textSize.width - padding);
424
- }
425
- }
426
- } else {
427
- pos.x = dataRect.x + (dataRect.width / 2);
428
- }
429
- }
430
- }
431
- const textColor = isOutside ? null : context.textColor(d.row, d.column, d.value, d.origRow);
432
-
433
- // Prevent overlapping labels on stacked columns
434
- const columns = context.columns();
435
- const hideValue = (context.yAxisStacked() && noRoomInside) ||
436
- (isOutside && context.yAxisStacked() && columns.indexOf(d.column) !== columns.length - 1);
437
- context.textLocal.get(this)
438
- .pos(pos)
439
- .anchor(valueAnchor)
440
- .fontFamily(valueFontFamily)
441
- .fontSize(valueFontSize)
442
- .text(`${valueText}`)
443
- .colorFill(textColor)
444
- .visible(context.showValue() && !hideValue)
445
- .render()
446
- ;
447
-
448
- });
449
- dataText.exit()
450
- .each(function (this: SVGElement, d) {
451
- context.textLocal.get(this).target(null);
452
- })
453
- .remove()
454
- ;
455
- });
456
- columnGRect.exit().transition().duration(duration)
457
- .style("opacity", 0)
458
- .remove()
459
- ;
460
-
461
- const value4pos = host.yAxisStacked() ? domainSums[dataRowIdx] : Math.max(...dataRow.filter((_, idx) => idx > 0 && idx < columnLength));
462
- const stackedTotalText = element.selectAll(".stackedTotalText").data(context.showDomainTotal() ? [domainSums[dataRowIdx]] : []);
463
- const stackedTotalTextEnter = stackedTotalText.enter().append("g")
464
- .attr("class", "stackedTotalText")
465
- .each(function (this: SVGElement, d) {
466
- context.stackedTextLocal.set(this, new Text().target(this).colorStroke_default("transparent"));
467
- });
468
- stackedTotalTextEnter.merge(stackedTotalText as any)
469
- .each(function (this: SVGElement, d: any) {
470
- const pos = { x: 0, y: 0 };
471
- const domainPos = host.dataPos(dataRow[0]);
472
- const valuePos = host.valuePos(value4pos);
473
-
474
- const valueFontFamily = context.valueFontFamily();
475
- const valueFontSize = context.valueFontSize();
476
- const textSize = context.textSize(d, valueFontFamily, valueFontSize);
477
-
478
- const isPositive = parseFloat(d) >= 0;
479
- let valueAnchor: "start" | "middle" | "end" = "middle";
480
- if (isHorizontal) {
481
- pos.x = domainPos;
482
- if (isPositive) {
483
- pos.y = valuePos - textSize.height / 2;
484
- } else {
485
- pos.y = valuePos + textSize.height / 2;
486
- }
487
- } else {
488
- valueAnchor = "start";
489
- pos.y = domainPos;
490
- if (isPositive) {
491
- pos.x = valuePos + textSize.width / 2;
492
- } else {
493
- pos.x = valuePos - textSize.width / 2;
494
- }
495
- }
496
-
497
- context.stackedTextLocal.get(this)
498
- .pos(pos)
499
- .anchor(valueAnchor)
500
- .fontFamily(valueFontFamily)
501
- .fontSize(valueFontSize)
502
- .text(d)
503
- .render()
504
- ;
505
-
506
- });
507
- stackedTotalText.exit()
508
- .each(function (this: SVGElement, d) {
509
- context.textLocal.get(this).target(null);
510
- })
511
- .remove()
512
- ;
513
- });
514
- column.exit().transition().duration(duration)
515
- .remove()
516
- ;
517
- }
518
-
519
- calcInnerText(offset, innerText, valueText) {
520
-
521
- const fontFamily = this.innerTextFontFamily_exists() ? this.innerTextFontFamily() : "Verdana";
522
- const fontSize = this.innerTextFontSize();
523
- const valueFontFamily = this.valueFontFamily_exists() ? this.valueFontFamily() : "Verdana";
524
- const valueFontSize = this.valueFontSize();
525
- const padding = this.innerTextPadding_exists() ? this.innerTextPadding() : fontSize / 2.5;
526
- const valueTextWidth = this.isHorizontal ? valueFontSize : this.textSize(valueText, valueFontFamily, valueFontSize).width;
527
- const ellipsisWidth = this.textSize("...", fontFamily, fontSize).width;
528
- const innerTextWidth = this.textSize(innerText, fontFamily, fontSize).width;
529
- const origInnerText = innerText;
530
-
531
- const fullWidth = (padding * 3) + innerTextWidth + valueTextWidth;
532
- const fullWidth2 = (padding * 3) + ellipsisWidth + valueTextWidth;
533
- const fullWidth3 = (padding * 1) + valueTextWidth;
534
- /*
535
- Categories:
536
- 1) room to display inner text (with padding) AND value text (with padding)
537
- 2) room to display ellipsis (with padding) AND value text (with padding)
538
- 3) room to display value text only (with padding)
539
- 4) no room to display any text except value on the outside
540
- */
541
- let category = 4;
542
- if (fullWidth < offset) {
543
- category = 1;
544
- } else if (fullWidth2 < offset) {
545
- const excessWidth = offset - fullWidth2;
546
- let _text = "";
547
- for (const letter of innerText) {
548
- if (this.textSize(_text + letter, fontFamily, fontSize).width > excessWidth) {
549
- innerText = _text + "...";
550
- break;
551
- } else {
552
- _text += letter;
553
- }
554
- }
555
- category = 2;
556
- } else if (fullWidth3 < offset) {
557
- innerText = "";
558
- category = 3;
559
- } else {
560
- innerText = "";
561
- }
562
-
563
- return {
564
- text: innerText,
565
- isTruncated: origInnerText !== innerText,
566
- padding,
567
- category,
568
- valueTextWidth
569
- };
570
- }
571
-
572
- innerText(origRow, lparam, idx): string {
573
- return origRow[0];
574
- }
575
-
576
- // INDChart ---
577
- _palette;
578
- fillColor: (row, column, value, origRow) => string;
579
- textColor: (row, column, value, origRow) => string;
580
- dblclick: (row, column, selected) => void;
581
-
582
- // ITooltip ---
583
- tooltip;
584
- tooltipHTML: (_) => string;
585
- tooltipFormat: (_) => string;
586
- tooltipStyle: () => "default" | "none" | "series-table";
587
- }
588
- Column.prototype._class += " chart_Column";
589
- Column.prototype.implements(INDChart.prototype);
590
- Column.prototype.implements(ITooltip.prototype);
591
-
592
- export interface Column {
593
- paletteID(): string;
594
- paletteID(_: string): this;
595
- useClonedPalette(): boolean;
596
- useClonedPalette(_: boolean): this;
597
- showValue(): boolean;
598
- showValue(_: boolean): this;
599
- showInnerText(): boolean;
600
- showInnerText(_: boolean): this;
601
- showValueFormat(): string;
602
- showValueFormat(_: string): this;
603
- showValueAsPercent(): null | "series" | "domain";
604
- showValueAsPercent(_: null | "series" | "domain"): this;
605
- showValueAsPercentFormat(): string;
606
- showValueAsPercentFormat(_: string): this;
607
- showDomainTotal(): boolean;
608
- showDomainTotal(_: boolean): this;
609
- valueCentered(): boolean;
610
- valueCentered(_: boolean): this;
611
- valueAnchor(): "start" | "middle" | "end";
612
- valueAnchor(_: "start" | "middle" | "end"): this;
613
- valueFontFamily(): string;
614
- valueFontFamily(_: string): this;
615
- valueFontFamily_exists(): boolean;
616
- valueFontSize(): number;
617
- valueFontSize(_: number): this;
618
- xAxisSeriesPaddingInner(): number;
619
- xAxisSeriesPaddingInner(_: number): this;
620
- innerTextFontFamily(): string;
621
- innerTextFontFamily(_: string): this;
622
- innerTextFontFamily_exists(): boolean;
623
- innerTextFontSize(): number;
624
- innerTextFontSize(_: number): this;
625
- innerTextPadding(): number;
626
- innerTextPadding(_: number): this;
627
- innerTextPadding_exists(): boolean;
628
- tooltipInnerTextEllipsedOnly(): boolean;
629
- tooltipInnerTextEllipsedOnly(_: boolean): this;
630
- }
631
-
632
- Column.prototype.publish("valueFontFamily", null, "string", "Font family of value text", null, { optional: true });
633
- Column.prototype.publish("valueFontSize", 12, "number", "Height of value text (pixels)");
634
- Column.prototype.publish("innerTextFontFamily", null, "string", "Font family of inner text", null, { optional: true });
635
- Column.prototype.publish("innerTextPadding", 8, "number", "Offset of inner text (pixels)", null, { optional: true });
636
- Column.prototype.publish("innerTextFontSize", 12, "number", "Height of inner text (pixels)");
637
- Column.prototype.publish("paletteID", "default", "set", "Color palette for this widget", () => Column.prototype._palette.switch(), { tags: ["Basic", "Shared"] });
638
- Column.prototype.publish("useClonedPalette", false, "boolean", "Enable or disable using a cloned palette", null, { tags: ["Intermediate", "Shared"] });
639
- Column.prototype.publish("showValue", false, "boolean", "Show Value in column");
640
- Column.prototype.publish("showInnerText", false, "boolean", "Show Label in column");
641
- Column.prototype.publish("showValueFormat", ",", "string", "D3 Format for Value", null, { disable: (w: Column) => !w.showValue() || !!w.showValueAsPercent() });
642
- Column.prototype.publish("showValueAsPercent", null, "set", "If showValue is true, optionally show value as a percentage by Series or Domain", [null, "series", "domain"], { disable: w => !w.showValue(), optional: true });
643
- Column.prototype.publish("showValueAsPercentFormat", ".0%", "string", "D3 Format for %", null, { disable: (w: Column) => !w.showValue() || !w.showValueAsPercent() });
644
- Column.prototype.publish("showDomainTotal", false, "boolean", "Show Total Value for Stacked Columns", null);
645
- Column.prototype.publish("valueCentered", false, "boolean", "Show Value in center of column");
646
- Column.prototype.publish("valueAnchor", "middle", "set", "text-anchor for shown value text", ["start", "middle", "end"]);
647
- Column.prototype.publish("xAxisSeriesPaddingInner", 0, "number", "Determines the ratio of the range that is reserved for blank space between band (0->1)");
648
- Column.prototype.publish("tooltipInnerTextEllipsedOnly", false, "boolean", "Show tooltip only when inner text is truncated with an ellipsis");
649
-
650
- /*
651
- const origUseClonedPalette = Column.prototype.useClonedPalette;
652
- Column.prototype.useClonedPalette = function (this: Column, _?) {
653
- const retVal = origUseClonedPalette.apply(this, arguments);
654
- if (arguments.length) {
655
- this._useClonedPalette = _;
656
- }
657
- return retVal;;
658
- }
659
- */
1
+ import { INDChart, ITooltip } from "@hpcc-js/api";
2
+ import { InputField, Text } from "@hpcc-js/common";
3
+ import { format as d3Format } from "d3-format";
4
+ import { scaleBand as d3ScaleBand } from "d3-scale";
5
+ import { local as d3Local, select as d3Select } from "d3-selection";
6
+ import { XYAxis } from "./XYAxis";
7
+
8
+ import "../src/Column.css";
9
+
10
+ export class Column extends XYAxis {
11
+ static __inputs: InputField[] = [{
12
+ id: "label",
13
+ type: "string"
14
+ }, {
15
+ id: "values",
16
+ type: "number",
17
+ multi: true
18
+ }];
19
+
20
+ protected _linearGap: number;
21
+ private textLocal = d3Local<Text>();
22
+ private stackedTextLocal = d3Local<Text>();
23
+ private isHorizontal: boolean;
24
+
25
+ constructor() {
26
+ super();
27
+ INDChart.call(this);
28
+ ITooltip.call(this);
29
+
30
+ this._selection.skipBringToTop(true);
31
+
32
+ this._linearGap = 25.0;
33
+ }
34
+
35
+ layerEnter(host: XYAxis, element, duration: number = 250) {
36
+ super.layerEnter(host, element, duration);
37
+ const context = this;
38
+ this
39
+ .tooltipHTML(function (d) {
40
+ switch (context.tooltipStyle()) {
41
+ case "series-table":
42
+ return context.tooltipFormat({
43
+ label: d.row[0],
44
+ arr: context.columns().slice(1).map(function (column, i) {
45
+ return {
46
+ label: column,
47
+ color: context._palette(column),
48
+ value: d.row[i + 1]
49
+ };
50
+ })
51
+ });
52
+ default:
53
+ let value = d.row[d.idx];
54
+ if (value instanceof Array) {
55
+ value = value[1] - value[0];
56
+ }
57
+ return context.tooltipFormat({ label: d.row[0], series: context.layerColumns(host)[d.idx], value });
58
+ }
59
+ })
60
+ ;
61
+ }
62
+
63
+ adjustedData(host: XYAxis) {
64
+ const retVal = this.layerData(host).map(row => {
65
+ let prevValue = 0;
66
+ return row.map((cell, idx) => {
67
+ if (idx === 0) {
68
+ return cell;
69
+ }
70
+ if (idx >= this.layerColumns(host).length) {
71
+ return cell;
72
+ }
73
+ const retVal2 = host.yAxisStacked() ? [prevValue, prevValue + cell] : cell;
74
+ prevValue += cell;
75
+ return retVal2;
76
+ }, this);
77
+ }, this);
78
+ return retVal;
79
+ }
80
+
81
+ layerUpdate(host: XYAxis, element, duration: number = 250) {
82
+ super.layerUpdate(host, element, duration);
83
+ const isHorizontal = host.orientation() === "horizontal";
84
+ this.isHorizontal = isHorizontal;
85
+ const context = this;
86
+
87
+ this._palette = this._palette.switch(this.paletteID());
88
+ if (this.useClonedPalette()) {
89
+ this._palette = this._palette.cloneNotExists(this.paletteID() + "_" + this.id());
90
+ }
91
+ const formatPct = d3Format(context.showValueAsPercentFormat());
92
+
93
+ let dataLen = 10;
94
+ let offset = 0;
95
+ switch (host.xAxisType()) {
96
+ case "ordinal":
97
+ dataLen = host.bandwidth();
98
+ offset = -dataLen / 2;
99
+ break;
100
+ case "linear":
101
+ case "time":
102
+ dataLen = Math.max(Math.abs(host.dataPos(2) - host.dataPos(1)) * (100 - this._linearGap) / 100, dataLen);
103
+ offset = -dataLen / 2;
104
+ break;
105
+ default:
106
+ }
107
+
108
+ this.tooltip.direction(isHorizontal ? "n" : "e");
109
+
110
+ const columnScale = d3ScaleBand()
111
+ .domain(context.layerColumns(host).filter(function (_d, idx) { return idx > 0; }))
112
+ .rangeRound(isHorizontal ? [0, dataLen] : [dataLen, 0])
113
+ .paddingInner(this.xAxisSeriesPaddingInner())
114
+ .paddingOuter(0)
115
+ ;
116
+ let domainSums = [];
117
+ const seriesSums = [];
118
+ const columnLength = this.columns().length;
119
+ const rowData = this.data();
120
+ if (this.showValue() && this.showValueAsPercent() === "series") {
121
+ rowData.forEach((row) => {
122
+ row.filter((_, idx) => idx > 0 && idx < columnLength).forEach((col, idx) => {
123
+ if (seriesSums[idx + 1] === undefined) {
124
+ seriesSums[idx + 1] = 0;
125
+ }
126
+ seriesSums[idx + 1] += col;
127
+ });
128
+ });
129
+ }
130
+
131
+ if (this.showDomainTotal() || (this.showValue() && this.showValueAsPercent() === "domain")) {
132
+ domainSums = rowData.map(row => {
133
+ return row.filter((cell, idx) => idx > 0 && idx < columnLength).reduce((sum, cell) => {
134
+ return sum + cell;
135
+ }, 0);
136
+ });
137
+ }
138
+
139
+ const column = element.selectAll(".dataRow")
140
+ .data(this.adjustedData(host))
141
+ ;
142
+ const hostData = host.data();
143
+ const axisSize = this.getAxisSize(host);
144
+ column.enter().append("g")
145
+ .attr("class", "dataRow")
146
+ .merge(column)
147
+ .each(function (dataRow, dataRowIdx) {
148
+ const element = d3Select(this);
149
+
150
+ const columnGRect = element.selectAll(".dataCell").data(dataRow.filter(function (_d, i) { return i < context.layerColumns(host).length; }).map(function (d, i) {
151
+ return {
152
+ column: context.layerColumns(host)[i],
153
+ row: dataRow,
154
+ origRow: hostData[dataRowIdx],
155
+ value: d,
156
+ idx: i
157
+ };
158
+ }).filter(function (d) { return d.value !== null && d.idx > 0; }), (d: any) => d.column);
159
+
160
+ const columnGEnter = columnGRect
161
+ .enter().append("g")
162
+ .attr("class", "dataCell")
163
+ .on("mouseout.tooltip", function (d: any) {
164
+ if (!context.tooltipInnerTextEllipsedOnly() || (d.innerTextObj && d.innerTextObj.isTruncated)) {
165
+ context.tooltip.hide.apply(context, arguments);
166
+ }
167
+ })
168
+ .on("mousemove.tooltip", function (d: any) {
169
+ if (!context.tooltipInnerTextEllipsedOnly() || (d.innerTextObj && d.innerTextObj.isTruncated)) {
170
+ context.tooltip.show.apply(context, arguments);
171
+ }
172
+ })
173
+ .call(host._selection.enter.bind(host._selection))
174
+ .on("click", function (d: any) {
175
+ context.click(host.rowToObj(d.origRow), d.column, host._selection.selected(this));
176
+ })
177
+ .on("dblclick", function (d: any) {
178
+ context.dblclick(host.rowToObj(d.origRow), d.column, host._selection.selected(this));
179
+ })
180
+ .style("opacity", 0)
181
+ .each(function (this: SVGElement, d: any) {
182
+ const element = d3Select(this);
183
+ element.append("rect")
184
+ .attr("class", "columnRect series series-" + context.cssTag(d.column))
185
+ ;
186
+ element.append("text")
187
+ .attr("class", "columnRectText")
188
+ .style("stroke", "transparent")
189
+ ;
190
+ })
191
+ ;
192
+ columnGEnter.transition().duration(duration)
193
+ .style("opacity", 1)
194
+ ;
195
+ const domainLength = host.yAxisStacked() ? dataLen : columnScale.bandwidth();
196
+ columnGEnter.merge(columnGRect as any).each(function (this: SVGElement, d: any) {
197
+ const element = d3Select(this);
198
+ const domainPos = host.dataPos(dataRow[0]) + (host.yAxisStacked() ? 0 : columnScale(d.column)) + offset;
199
+ const upperValue = d.value instanceof Array ? d.value[1] : d.value;
200
+ let valueText = d.origRow[d.idx];
201
+ if (context.showValue()) {
202
+ const dm = context.dataMeta();
203
+ switch (context.showValueAsPercent()) {
204
+ case "series":
205
+ const seriesSum = typeof dm.sum !== "undefined" ? dm.sum : seriesSums[d.idx];
206
+ valueText = formatPct(valueText / seriesSum);
207
+ break;
208
+ case "domain":
209
+ const domainSum = typeof dm.sum !== "undefined" ? dm.sum : domainSums[dataRowIdx];
210
+ valueText = formatPct(valueText / domainSum);
211
+ break;
212
+ case null:
213
+ default:
214
+ valueText = d3Format(context.showValueFormat())(valueText);
215
+ break;
216
+ }
217
+ }
218
+ const upperValuePos = host.valuePos(upperValue);
219
+ const lowerValuePos = host.valuePos(d.value instanceof Array ? d.value[0] : 0);
220
+ const valuePos = Math.min(lowerValuePos, upperValuePos);
221
+ const valueLength = Math.abs(upperValuePos - lowerValuePos);
222
+
223
+ const innerTextHeight = context.innerTextFontSize();
224
+ const innerTextPadding = context.innerTextPadding_exists() ? context.innerTextPadding() : innerTextHeight / 2.5;
225
+
226
+ const dataRect = context.intersectRectRect(
227
+ {
228
+ x: isHorizontal ? domainPos : valuePos,
229
+ y: isHorizontal ? valuePos : domainPos,
230
+ width: isHorizontal ? domainLength : valueLength,
231
+ height: isHorizontal ? valueLength : domainLength
232
+ },
233
+ {
234
+ x: 0,
235
+ y: 0,
236
+ width: axisSize.width,
237
+ height: axisSize.height
238
+ }
239
+ );
240
+
241
+ const _rects = element.select("rect").transition().duration(duration)
242
+ .style("fill", (d: any) => context.fillColor(d.row, d.column, d.value, d.origRow))
243
+ ;
244
+
245
+ if (isHorizontal) {
246
+ _rects
247
+ .attr("x", domainPos)
248
+ .attr("y", valuePos)
249
+ .attr("width", domainLength)
250
+ .attr("height", valueLength)
251
+ ;
252
+ } else {
253
+ _rects
254
+ .attr("y", domainPos)
255
+ .attr("x", valuePos)
256
+ .attr("height", domainLength)
257
+ .attr("width", valueLength)
258
+ ;
259
+ }
260
+ const _texts = element.select("text").transition().duration(duration)
261
+ .style("font-size", innerTextHeight + "px")
262
+ .style("fill", (d: any) => context.textColor(d.row, d.column, d.value, d.origRow))
263
+ ;
264
+
265
+ _texts.style("font-family", context.innerTextFontFamily_exists() ? context.innerTextFontFamily() : null);
266
+
267
+ const padding = context.innerTextPadding_exists() ? context.innerTextPadding() : 8;
268
+
269
+ const textHeightOffset = innerTextHeight / 2.7;
270
+
271
+ if (isHorizontal) { // Column
272
+ const y = dataRect.y + dataRect.height - innerTextPadding;
273
+ _texts
274
+ .attr("x", domainPos + (domainLength / 2))
275
+ .attr("y", y + textHeightOffset)
276
+ .attr("transform", `rotate(-90, ${domainPos + (domainLength / 2)}, ${y})`)
277
+ ;
278
+ } else { // Bar
279
+ _texts
280
+ .attr("x", dataRect.x + padding)
281
+ .attr("y", domainPos + (domainLength / 2) + textHeightOffset)
282
+ ;
283
+ }
284
+ _texts
285
+ .attr("height", domainLength)
286
+ .attr("width", valueLength)
287
+ ;
288
+ if (context.showInnerText()) {
289
+ _texts
290
+ .text((d: any) => {
291
+ const innerText = context.innerText(d.origRow, d.origRow[columnLength], d.idx);
292
+ if (innerText) {
293
+ const clippedValueLength = isHorizontal ? dataRect.height : dataRect.width;
294
+ const innerTextObj = context.calcInnerText(clippedValueLength, innerText, valueText);
295
+ d.innerTextObj = innerTextObj;
296
+
297
+ return innerTextObj.text;
298
+ }
299
+ return "";
300
+ })
301
+ ;
302
+ }
303
+ const dataText = element.selectAll(".dataText").data(context.showValue() ? [`${upperValue}`] : []);
304
+ const dataTextEnter = dataText.enter().append("g")
305
+ .attr("class", "dataText")
306
+ .each(function (this: SVGElement, d) {
307
+ context.textLocal.set(this, new Text().target(this).colorStroke_default("transparent"));
308
+ });
309
+ dataTextEnter.merge(dataText as any)
310
+ .each(function (this: SVGElement) {
311
+ const pos = { x: 0, y: 0 };
312
+ const valueFontFamily = context.valueFontFamily();
313
+ const valueFontSize = context.valueFontSize();
314
+ const textSize = context.textSize(valueText, valueFontFamily, valueFontSize);
315
+
316
+ const isPositive = parseFloat(valueText) >= 0;
317
+
318
+ let valueAnchor = context.valueAnchor() ? context.valueAnchor() : isHorizontal ? "middle" : "start";
319
+
320
+ const leftSpace = dataRect.x;
321
+ const rightSpace = axisSize.width - (dataRect.x + dataRect.width);
322
+ const topSpace = dataRect.y;
323
+ const bottomSpace = axisSize.height - (dataRect.y + dataRect.height);
324
+
325
+ let noRoomInside;
326
+ let isOutside;
327
+ let noRoomOnExpectedSide;
328
+
329
+ if (d.innerTextObj) {
330
+ const { padding, valueTextWidth } = d.innerTextObj;
331
+ isOutside = false;
332
+ if (isHorizontal) { // Column
333
+ valueAnchor = "middle";
334
+ pos.x = domainPos + (domainLength / 2);
335
+
336
+ if (d.innerTextObj.category === 4) {
337
+ isOutside = true;
338
+ pos.y = valuePos - padding - (valueFontSize / 2);
339
+ } else {
340
+ pos.y = valuePos + padding + (valueFontSize / 2);
341
+ }
342
+ } else { // Bar
343
+ valueAnchor = "start";
344
+ if (d.innerTextObj.category === 4) {
345
+ isOutside = true;
346
+ pos.x = (valueLength + valuePos) + padding;
347
+ } else {
348
+ pos.x = (valueLength + valuePos) - valueTextWidth - padding;
349
+ }
350
+ pos.y = domainPos + (domainLength / 2);
351
+ }
352
+ } else {
353
+ /*
354
+ IF this.valueCentered() and NO ROOM INSIDE
355
+ ...then ASSUME THERES ROOM OUTSIDE
356
+ IF NO ROOM OUTSIDE ON EXPECTED SIDE
357
+ ...then ASSUME THERES ROOM ON THE OPPOSITE SIDE
358
+ */
359
+ if (isHorizontal) { // Column
360
+ noRoomInside = dataRect.height < textSize.height;
361
+ isOutside = !context.valueCentered() || noRoomInside;
362
+
363
+ pos.x = dataRect.x + (dataRect.width / 2);
364
+
365
+ if (isOutside) {
366
+ if (isPositive) {
367
+ noRoomOnExpectedSide = topSpace < textSize.height + padding;
368
+ if (noRoomOnExpectedSide) {
369
+ if (!noRoomInside) {
370
+ isOutside = false;
371
+ pos.y = dataRect.y + (dataRect.height / 2);
372
+ } else {
373
+ pos.y = dataRect.y + dataRect.height + textSize.height;
374
+ }
375
+ } else {
376
+ pos.y = dataRect.y - (textSize.height / 2) - padding;
377
+ }
378
+ } else {
379
+ noRoomOnExpectedSide = bottomSpace < textSize.height;
380
+ if (noRoomOnExpectedSide) {
381
+ if (!noRoomInside) {
382
+ isOutside = false;
383
+ pos.y = dataRect.y + (dataRect.height / 2);
384
+ } else {
385
+ pos.y = dataRect.y - (textSize.height / 2) - padding;
386
+ }
387
+ } else {
388
+ pos.y = dataRect.y + textSize.height + padding;
389
+ }
390
+ }
391
+ } else {
392
+ pos.y = dataRect.y + (dataRect.height / 2);
393
+ }
394
+ } else { // Bar
395
+ noRoomInside = dataRect.width < textSize.width;
396
+ isOutside = !context.valueCentered() || noRoomInside;
397
+
398
+ pos.y = dataRect.y + (dataRect.height / 2);
399
+
400
+ if (isOutside) {
401
+ if (isPositive) {
402
+ noRoomOnExpectedSide = rightSpace < textSize.width + padding;
403
+ if (noRoomOnExpectedSide) {
404
+ if (context.showInnerText() || !noRoomInside) {
405
+ isOutside = false;
406
+ pos.x = dataRect.x + (dataRect.width / 2);
407
+ } else {
408
+ pos.x = dataRect.x - (textSize.width - padding);
409
+ }
410
+ } else {
411
+ pos.x = dataRect.x + dataRect.width + (textSize.width / 2) + padding;
412
+ }
413
+ } else {
414
+ noRoomOnExpectedSide = leftSpace < textSize.width;
415
+ if (noRoomOnExpectedSide) {
416
+ if (context.showInnerText() || !noRoomInside) {
417
+ isOutside = false;
418
+ pos.x = dataRect.x + (dataRect.width / 2);
419
+ } else {
420
+ pos.x = dataRect.x + dataRect.width + (textSize.width - padding);
421
+ }
422
+ } else {
423
+ pos.x = dataRect.x - (textSize.width - padding);
424
+ }
425
+ }
426
+ } else {
427
+ pos.x = dataRect.x + (dataRect.width / 2);
428
+ }
429
+ }
430
+ }
431
+ const textColor = isOutside ? null : context.textColor(d.row, d.column, d.value, d.origRow);
432
+
433
+ // Prevent overlapping labels on stacked columns
434
+ const columns = context.columns();
435
+ const hideValue = (context.yAxisStacked() && noRoomInside) ||
436
+ (isOutside && context.yAxisStacked() && columns.indexOf(d.column) !== columns.length - 1);
437
+ context.textLocal.get(this)
438
+ .pos(pos)
439
+ .anchor(valueAnchor)
440
+ .fontFamily(valueFontFamily)
441
+ .fontSize(valueFontSize)
442
+ .text(`${valueText}`)
443
+ .colorFill(textColor)
444
+ .visible(context.showValue() && !hideValue)
445
+ .render()
446
+ ;
447
+
448
+ });
449
+ dataText.exit()
450
+ .each(function (this: SVGElement, d) {
451
+ context.textLocal.get(this).target(null);
452
+ })
453
+ .remove()
454
+ ;
455
+ });
456
+ columnGRect.exit().transition().duration(duration)
457
+ .style("opacity", 0)
458
+ .remove()
459
+ ;
460
+
461
+ const value4pos = host.yAxisStacked() ? domainSums[dataRowIdx] : Math.max(...dataRow.filter((_, idx) => idx > 0 && idx < columnLength));
462
+ const stackedTotalText = element.selectAll(".stackedTotalText").data(context.showDomainTotal() ? [domainSums[dataRowIdx]] : []);
463
+ const stackedTotalTextEnter = stackedTotalText.enter().append("g")
464
+ .attr("class", "stackedTotalText")
465
+ .each(function (this: SVGElement, d) {
466
+ context.stackedTextLocal.set(this, new Text().target(this).colorStroke_default("transparent"));
467
+ });
468
+ stackedTotalTextEnter.merge(stackedTotalText as any)
469
+ .each(function (this: SVGElement, d: any) {
470
+ const pos = { x: 0, y: 0 };
471
+ const domainPos = host.dataPos(dataRow[0]);
472
+ const valuePos = host.valuePos(value4pos);
473
+
474
+ const valueFontFamily = context.valueFontFamily();
475
+ const valueFontSize = context.valueFontSize();
476
+ const textSize = context.textSize(d, valueFontFamily, valueFontSize);
477
+
478
+ const isPositive = parseFloat(d) >= 0;
479
+ let valueAnchor: "start" | "middle" | "end" = "middle";
480
+ if (isHorizontal) {
481
+ pos.x = domainPos;
482
+ if (isPositive) {
483
+ pos.y = valuePos - textSize.height / 2;
484
+ } else {
485
+ pos.y = valuePos + textSize.height / 2;
486
+ }
487
+ } else {
488
+ valueAnchor = "start";
489
+ pos.y = domainPos;
490
+ if (isPositive) {
491
+ pos.x = valuePos + textSize.width / 2;
492
+ } else {
493
+ pos.x = valuePos - textSize.width / 2;
494
+ }
495
+ }
496
+
497
+ context.stackedTextLocal.get(this)
498
+ .pos(pos)
499
+ .anchor(valueAnchor)
500
+ .fontFamily(valueFontFamily)
501
+ .fontSize(valueFontSize)
502
+ .text(d)
503
+ .render()
504
+ ;
505
+
506
+ });
507
+ stackedTotalText.exit()
508
+ .each(function (this: SVGElement, d) {
509
+ context.textLocal.get(this).target(null);
510
+ })
511
+ .remove()
512
+ ;
513
+ });
514
+ column.exit().transition().duration(duration)
515
+ .remove()
516
+ ;
517
+ }
518
+
519
+ calcInnerText(offset, innerText, valueText) {
520
+
521
+ const fontFamily = this.innerTextFontFamily_exists() ? this.innerTextFontFamily() : "Verdana";
522
+ const fontSize = this.innerTextFontSize();
523
+ const valueFontFamily = this.valueFontFamily_exists() ? this.valueFontFamily() : "Verdana";
524
+ const valueFontSize = this.valueFontSize();
525
+ const padding = this.innerTextPadding_exists() ? this.innerTextPadding() : fontSize / 2.5;
526
+ const valueTextWidth = this.isHorizontal ? valueFontSize : this.textSize(valueText, valueFontFamily, valueFontSize).width;
527
+ const ellipsisWidth = this.textSize("...", fontFamily, fontSize).width;
528
+ const innerTextWidth = this.textSize(innerText, fontFamily, fontSize).width;
529
+ const origInnerText = innerText;
530
+
531
+ const fullWidth = (padding * 3) + innerTextWidth + valueTextWidth;
532
+ const fullWidth2 = (padding * 3) + ellipsisWidth + valueTextWidth;
533
+ const fullWidth3 = (padding * 1) + valueTextWidth;
534
+ /*
535
+ Categories:
536
+ 1) room to display inner text (with padding) AND value text (with padding)
537
+ 2) room to display ellipsis (with padding) AND value text (with padding)
538
+ 3) room to display value text only (with padding)
539
+ 4) no room to display any text except value on the outside
540
+ */
541
+ let category = 4;
542
+ if (fullWidth < offset) {
543
+ category = 1;
544
+ } else if (fullWidth2 < offset) {
545
+ const excessWidth = offset - fullWidth2;
546
+ let _text = "";
547
+ for (const letter of innerText) {
548
+ if (this.textSize(_text + letter, fontFamily, fontSize).width > excessWidth) {
549
+ innerText = _text + "...";
550
+ break;
551
+ } else {
552
+ _text += letter;
553
+ }
554
+ }
555
+ category = 2;
556
+ } else if (fullWidth3 < offset) {
557
+ innerText = "";
558
+ category = 3;
559
+ } else {
560
+ innerText = "";
561
+ }
562
+
563
+ return {
564
+ text: innerText,
565
+ isTruncated: origInnerText !== innerText,
566
+ padding,
567
+ category,
568
+ valueTextWidth
569
+ };
570
+ }
571
+
572
+ innerText(origRow, lparam, idx): string {
573
+ return origRow[0];
574
+ }
575
+
576
+ // INDChart ---
577
+ _palette;
578
+ fillColor: (row, column, value, origRow) => string;
579
+ textColor: (row, column, value, origRow) => string;
580
+ dblclick: (row, column, selected) => void;
581
+
582
+ // ITooltip ---
583
+ tooltip;
584
+ tooltipHTML: (_) => string;
585
+ tooltipFormat: (_) => string;
586
+ tooltipStyle: () => "default" | "none" | "series-table";
587
+ }
588
+ Column.prototype._class += " chart_Column";
589
+ Column.prototype.implements(INDChart.prototype);
590
+ Column.prototype.implements(ITooltip.prototype);
591
+
592
+ export interface Column {
593
+ paletteID(): string;
594
+ paletteID(_: string): this;
595
+ useClonedPalette(): boolean;
596
+ useClonedPalette(_: boolean): this;
597
+ showValue(): boolean;
598
+ showValue(_: boolean): this;
599
+ showInnerText(): boolean;
600
+ showInnerText(_: boolean): this;
601
+ showValueFormat(): string;
602
+ showValueFormat(_: string): this;
603
+ showValueAsPercent(): null | "series" | "domain";
604
+ showValueAsPercent(_: null | "series" | "domain"): this;
605
+ showValueAsPercentFormat(): string;
606
+ showValueAsPercentFormat(_: string): this;
607
+ showDomainTotal(): boolean;
608
+ showDomainTotal(_: boolean): this;
609
+ valueCentered(): boolean;
610
+ valueCentered(_: boolean): this;
611
+ valueAnchor(): "start" | "middle" | "end";
612
+ valueAnchor(_: "start" | "middle" | "end"): this;
613
+ valueFontFamily(): string;
614
+ valueFontFamily(_: string): this;
615
+ valueFontFamily_exists(): boolean;
616
+ valueFontSize(): number;
617
+ valueFontSize(_: number): this;
618
+ xAxisSeriesPaddingInner(): number;
619
+ xAxisSeriesPaddingInner(_: number): this;
620
+ innerTextFontFamily(): string;
621
+ innerTextFontFamily(_: string): this;
622
+ innerTextFontFamily_exists(): boolean;
623
+ innerTextFontSize(): number;
624
+ innerTextFontSize(_: number): this;
625
+ innerTextPadding(): number;
626
+ innerTextPadding(_: number): this;
627
+ innerTextPadding_exists(): boolean;
628
+ tooltipInnerTextEllipsedOnly(): boolean;
629
+ tooltipInnerTextEllipsedOnly(_: boolean): this;
630
+ }
631
+
632
+ Column.prototype.publish("valueFontFamily", null, "string", "Font family of value text", null, { optional: true });
633
+ Column.prototype.publish("valueFontSize", 12, "number", "Height of value text (pixels)");
634
+ Column.prototype.publish("innerTextFontFamily", null, "string", "Font family of inner text", null, { optional: true });
635
+ Column.prototype.publish("innerTextPadding", 8, "number", "Offset of inner text (pixels)", null, { optional: true });
636
+ Column.prototype.publish("innerTextFontSize", 12, "number", "Height of inner text (pixels)");
637
+ Column.prototype.publish("paletteID", "default", "set", "Color palette for this widget", () => Column.prototype._palette.switch(), { tags: ["Basic", "Shared"] });
638
+ Column.prototype.publish("useClonedPalette", false, "boolean", "Enable or disable using a cloned palette", null, { tags: ["Intermediate", "Shared"] });
639
+ Column.prototype.publish("showValue", false, "boolean", "Show Value in column");
640
+ Column.prototype.publish("showInnerText", false, "boolean", "Show Label in column");
641
+ Column.prototype.publish("showValueFormat", ",", "string", "D3 Format for Value", null, { disable: (w: Column) => !w.showValue() || !!w.showValueAsPercent() });
642
+ Column.prototype.publish("showValueAsPercent", null, "set", "If showValue is true, optionally show value as a percentage by Series or Domain", [null, "series", "domain"], { disable: w => !w.showValue(), optional: true });
643
+ Column.prototype.publish("showValueAsPercentFormat", ".0%", "string", "D3 Format for %", null, { disable: (w: Column) => !w.showValue() || !w.showValueAsPercent() });
644
+ Column.prototype.publish("showDomainTotal", false, "boolean", "Show Total Value for Stacked Columns", null);
645
+ Column.prototype.publish("valueCentered", false, "boolean", "Show Value in center of column");
646
+ Column.prototype.publish("valueAnchor", "middle", "set", "text-anchor for shown value text", ["start", "middle", "end"]);
647
+ Column.prototype.publish("xAxisSeriesPaddingInner", 0, "number", "Determines the ratio of the range that is reserved for blank space between band (0->1)");
648
+ Column.prototype.publish("tooltipInnerTextEllipsedOnly", false, "boolean", "Show tooltip only when inner text is truncated with an ellipsis");
649
+
650
+ /*
651
+ const origUseClonedPalette = Column.prototype.useClonedPalette;
652
+ Column.prototype.useClonedPalette = function (this: Column, _?) {
653
+ const retVal = origUseClonedPalette.apply(this, arguments);
654
+ if (arguments.length) {
655
+ this._useClonedPalette = _;
656
+ }
657
+ return retVal;;
658
+ }
659
+ */