@internetstiftelsen/charts 0.9.2 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +137 -3
  2. package/dist/area.d.ts +2 -0
  3. package/dist/area.js +39 -31
  4. package/dist/bar.d.ts +20 -1
  5. package/dist/bar.js +395 -519
  6. package/dist/base-chart.d.ts +21 -1
  7. package/dist/base-chart.js +166 -93
  8. package/dist/chart-group.d.ts +137 -0
  9. package/dist/chart-group.js +1155 -0
  10. package/dist/chart-interface.d.ts +1 -1
  11. package/dist/donut-center-content.d.ts +1 -0
  12. package/dist/donut-center-content.js +21 -38
  13. package/dist/donut-chart.js +30 -15
  14. package/dist/gauge-chart.d.ts +20 -0
  15. package/dist/gauge-chart.js +229 -133
  16. package/dist/legend-state.d.ts +19 -0
  17. package/dist/legend-state.js +81 -0
  18. package/dist/legend.d.ts +5 -2
  19. package/dist/legend.js +45 -38
  20. package/dist/line.js +3 -1
  21. package/dist/pie-chart.d.ts +3 -0
  22. package/dist/pie-chart.js +45 -19
  23. package/dist/scatter.d.ts +16 -0
  24. package/dist/scatter.js +165 -0
  25. package/dist/tooltip.d.ts +2 -1
  26. package/dist/tooltip.js +21 -25
  27. package/dist/types.d.ts +19 -1
  28. package/dist/utils.js +11 -19
  29. package/dist/validation.d.ts +4 -0
  30. package/dist/validation.js +19 -0
  31. package/dist/x-axis.d.ts +10 -0
  32. package/dist/x-axis.js +190 -149
  33. package/dist/xy-chart.d.ts +40 -1
  34. package/dist/xy-chart.js +488 -165
  35. package/dist/y-axis.d.ts +7 -2
  36. package/dist/y-axis.js +99 -10
  37. package/docs/chart-group.md +213 -0
  38. package/docs/components.md +321 -0
  39. package/docs/donut-chart.md +193 -0
  40. package/docs/gauge-chart.md +175 -0
  41. package/docs/getting-started.md +311 -0
  42. package/docs/pie-chart.md +123 -0
  43. package/docs/theming.md +162 -0
  44. package/docs/word-cloud-chart.md +98 -0
  45. package/docs/xy-chart.md +517 -0
  46. package/package.json +6 -4
package/dist/bar.js CHANGED
@@ -4,7 +4,7 @@ const LABEL_INSET_DEFAULT = 4;
4
4
  const LABEL_INSET_STACKED = 6;
5
5
  const LABEL_MIN_PADDING_DEFAULT = 8;
6
6
  const LABEL_MIN_PADDING_STACKED = 16;
7
- const LAYER_LABEL_GAP = 6;
7
+ const LABEL_OUTSIDE_OFFSET = 4;
8
8
  function getLabelSpacing(mode) {
9
9
  const stacked = mode !== 'none';
10
10
  return {
@@ -14,6 +14,109 @@ function getLabelSpacing(mode) {
14
14
  : LABEL_MIN_PADDING_DEFAULT,
15
15
  };
16
16
  }
17
+ function getBarSlotLayout(bandwidth, mode, maxBarSize, totalSeries, seriesIndex, gap) {
18
+ if (mode === 'none') {
19
+ const groupSize = maxBarSize
20
+ ? Math.min(bandwidth, maxBarSize * totalSeries)
21
+ : bandwidth;
22
+ const totalGapSpace = groupSize * gap * (totalSeries - 1);
23
+ const availableSize = groupSize - totalGapSpace;
24
+ const thickness = availableSize / totalSeries;
25
+ const gapSize = totalSeries > 1 ? groupSize * gap : 0;
26
+ return {
27
+ thickness,
28
+ offset: (bandwidth - groupSize) / 2 +
29
+ seriesIndex * (thickness + gapSize),
30
+ };
31
+ }
32
+ if (mode === 'layer') {
33
+ const maxSize = maxBarSize
34
+ ? Math.min(bandwidth, maxBarSize)
35
+ : bandwidth;
36
+ const scaleFactor = 1 - (seriesIndex / totalSeries) * 0.7;
37
+ const thickness = maxSize * scaleFactor;
38
+ return {
39
+ thickness,
40
+ offset: (bandwidth - thickness) / 2,
41
+ };
42
+ }
43
+ const thickness = maxBarSize ? Math.min(bandwidth, maxBarSize) : bandwidth;
44
+ return {
45
+ thickness,
46
+ offset: (bandwidth - thickness) / 2,
47
+ };
48
+ }
49
+ function getBarValueRange(categoryKey, value, stackingContext) {
50
+ const mode = stackingContext?.mode ?? 'normal';
51
+ if (mode === 'none' || mode === 'layer') {
52
+ return getUnstackedBarValueRange(value);
53
+ }
54
+ if (mode === 'percent') {
55
+ return getPercentBarValueRange(categoryKey, value, stackingContext);
56
+ }
57
+ return getStackedBarValueRange(categoryKey, value, stackingContext);
58
+ }
59
+ function getUnstackedBarValueRange(value) {
60
+ return {
61
+ start: 0,
62
+ end: value,
63
+ };
64
+ }
65
+ function getPercentBarValueRange(categoryKey, value, stackingContext) {
66
+ const isPositive = value >= 0;
67
+ const { cumulative, total } = getPercentBarRangeMetrics(categoryKey, isPositive, stackingContext);
68
+ if (total === 0) {
69
+ return {
70
+ start: 0,
71
+ end: 0,
72
+ };
73
+ }
74
+ const start = (cumulative / total) * 100;
75
+ const end = ((cumulative + Math.abs(value)) / total) * 100;
76
+ return isPositive ? { start, end } : { start: -start, end: -end };
77
+ }
78
+ function getPercentBarRangeMetrics(categoryKey, isPositive, stackingContext) {
79
+ return isPositive
80
+ ? getPositivePercentBarRangeMetrics(categoryKey, stackingContext)
81
+ : getNegativePercentBarRangeMetrics(categoryKey, stackingContext);
82
+ }
83
+ function getPositivePercentBarRangeMetrics(categoryKey, stackingContext) {
84
+ return {
85
+ cumulative: stackingContext?.positiveCumulativeData.get(categoryKey) ?? 0,
86
+ total: stackingContext?.positiveTotalData.get(categoryKey) ?? 0,
87
+ };
88
+ }
89
+ function getNegativePercentBarRangeMetrics(categoryKey, stackingContext) {
90
+ return {
91
+ cumulative: stackingContext?.negativeCumulativeData.get(categoryKey) ?? 0,
92
+ total: stackingContext?.negativeTotalData.get(categoryKey) ?? 0,
93
+ };
94
+ }
95
+ function getStackedBarValueRange(categoryKey, value, stackingContext) {
96
+ if (value >= 0) {
97
+ const cumulative = stackingContext?.positiveCumulativeData.get(categoryKey) ?? 0;
98
+ return {
99
+ start: cumulative,
100
+ end: cumulative + value,
101
+ };
102
+ }
103
+ const cumulativeMagnitude = stackingContext?.negativeCumulativeData.get(categoryKey) ?? 0;
104
+ const start = -cumulativeMagnitude;
105
+ return {
106
+ start,
107
+ end: start + value,
108
+ };
109
+ }
110
+ function getScaledValueRange(scale, startValue, endValue) {
111
+ const start = scale(startValue) ?? 0;
112
+ const end = scale(endValue) ?? 0;
113
+ return {
114
+ start,
115
+ end,
116
+ min: Math.min(start, end),
117
+ max: Math.max(start, end),
118
+ };
119
+ }
17
120
  export class Bar {
18
121
  constructor(config) {
19
122
  Object.defineProperty(this, "type", {
@@ -46,6 +149,12 @@ export class Bar {
46
149
  writable: true,
47
150
  value: void 0
48
151
  });
152
+ Object.defineProperty(this, "side", {
153
+ enumerable: true,
154
+ configurable: true,
155
+ writable: true,
156
+ value: void 0
157
+ });
49
158
  Object.defineProperty(this, "valueLabel", {
50
159
  enumerable: true,
51
160
  configurable: true,
@@ -62,6 +171,7 @@ export class Bar {
62
171
  this.fill = config.fill || '#8884d8';
63
172
  this.colorAdapter = config.colorAdapter;
64
173
  this.maxBarSize = config.maxBarSize;
174
+ this.side = config.side ?? 'right';
65
175
  this.valueLabel = config.valueLabel;
66
176
  this.exportHooks = config.exportHooks;
67
177
  }
@@ -71,6 +181,7 @@ export class Bar {
71
181
  fill: this.fill,
72
182
  colorAdapter: this.colorAdapter,
73
183
  maxBarSize: this.maxBarSize,
184
+ side: this.side,
74
185
  valueLabel: this.valueLabel,
75
186
  };
76
187
  }
@@ -81,6 +192,12 @@ export class Bar {
81
192
  exportHooks: this.exportHooks,
82
193
  });
83
194
  }
195
+ getRenderedValue(value, orientation = 'vertical') {
196
+ if (orientation === 'horizontal' && this.side === 'left') {
197
+ return -Math.abs(value);
198
+ }
199
+ return value;
200
+ }
84
201
  render(plotGroup, data, xKey, x, y, parseValue, xScaleType = 'band', theme, stackingContext, orientation = 'vertical') {
85
202
  if (orientation === 'vertical') {
86
203
  this.renderVertical(plotGroup, data, xKey, x, y, parseValue, xScaleType, stackingContext);
@@ -101,49 +218,13 @@ export class Bar {
101
218
  renderVertical(plotGroup, data, xKey, x, y, parseValue, xScaleType, stackingContext) {
102
219
  const bandwidth = x.bandwidth ? x.bandwidth() : 20;
103
220
  const mode = stackingContext?.mode ?? 'normal';
104
- // Calculate bar width based on stacking mode
105
- let barWidth;
106
- let barOffset;
107
- if (mode === 'none') {
108
- // Grouped bars: divide bandwidth among series with gap
109
- const totalSeries = stackingContext?.totalSeries ?? 1;
110
- const seriesIndex = stackingContext?.seriesIndex ?? 0;
111
- const gap = stackingContext?.gap ?? 0.1;
112
- const groupWidth = this.maxBarSize
113
- ? Math.min(bandwidth, this.maxBarSize * totalSeries)
114
- : bandwidth;
115
- // Calculate total gap space and individual bar width
116
- const totalGapSpace = groupWidth * gap * (totalSeries - 1);
117
- const availableWidth = groupWidth - totalGapSpace;
118
- barWidth = availableWidth / totalSeries;
119
- const gapSize = totalSeries > 1 ? groupWidth * gap : 0;
120
- barOffset =
121
- (bandwidth - groupWidth) / 2 +
122
- seriesIndex * (barWidth + gapSize);
123
- }
124
- else if (mode === 'layer') {
125
- // Layer mode: each subsequent series has smaller bars
126
- const totalSeries = stackingContext?.totalSeries ?? 1;
127
- const seriesIndex = stackingContext?.seriesIndex ?? 0;
128
- const maxWidth = this.maxBarSize
129
- ? Math.min(bandwidth, this.maxBarSize)
130
- : bandwidth;
131
- // Scale from 100% to a minimum (e.g., 30%) based on series position
132
- const scaleFactor = 1 - (seriesIndex / totalSeries) * 0.7;
133
- barWidth = maxWidth * scaleFactor;
134
- barOffset = (bandwidth - barWidth) / 2;
135
- }
136
- else {
137
- // Normal and Percent modes: full width stacked bars
138
- barWidth = this.maxBarSize
139
- ? Math.min(bandwidth, this.maxBarSize)
140
- : bandwidth;
141
- barOffset = (bandwidth - barWidth) / 2;
142
- }
143
- // Get the baseline value from the Y scale's domain
144
- const yDomain = y.domain();
145
- const baselineValue = yDomain[0] >= 0 ? Math.max(0, yDomain[0]) : yDomain[0];
146
- const yBaseline = y(baselineValue) || 0;
221
+ const { thickness: barWidth, offset: barOffset } = getBarSlotLayout(bandwidth, mode, this.maxBarSize, stackingContext?.totalSeries ?? 1, stackingContext?.seriesIndex ?? 0, stackingContext?.gap ?? 0.1);
222
+ const getVerticalBounds = (d) => {
223
+ const categoryKey = String(d[xKey]);
224
+ const value = this.getRenderedValue(parseValue(d[this.dataKey]), 'vertical');
225
+ const { start, end } = getBarValueRange(categoryKey, value, stackingContext);
226
+ return getScaledValueRange(y, start, end);
227
+ };
147
228
  // Add bar rectangles
148
229
  const sanitizedKey = sanitizeForCSS(this.dataKey);
149
230
  plotGroup
@@ -158,96 +239,24 @@ export class Bar {
158
239
  ? xPos + barOffset
159
240
  : xPos - barWidth / 2;
160
241
  })
161
- .attr('y', (d) => {
162
- const categoryKey = String(d[xKey]);
163
- const value = parseValue(d[this.dataKey]);
164
- if (mode === 'none' || mode === 'layer') {
165
- // No stacking - each bar starts from baseline
166
- const yPos = y(value) || 0;
167
- return Math.min(yBaseline, yPos);
168
- }
169
- else if (mode === 'percent') {
170
- // Percent mode: calculate position based on cumulative percentage
171
- const cumulative = stackingContext?.cumulativeData.get(categoryKey) ?? 0;
172
- const total = stackingContext?.totalData.get(categoryKey) ?? 1;
173
- const percentCumulative = (cumulative / total) * 100;
174
- const percentValue = (value / total) * 100;
175
- return y(percentCumulative + percentValue) || 0;
176
- }
177
- else {
178
- // Normal stacking mode
179
- const cumulative = stackingContext?.cumulativeData.get(categoryKey) ?? 0;
180
- return y(cumulative + value) || 0;
181
- }
182
- })
242
+ .attr('y', (d) => getVerticalBounds(d).min)
183
243
  .attr('width', barWidth)
184
244
  .attr('height', (d) => {
185
- const categoryKey = String(d[xKey]);
186
- const value = parseValue(d[this.dataKey]);
187
- if (mode === 'none' || mode === 'layer') {
188
- const yPos = y(value) || 0;
189
- return Math.abs(yBaseline - yPos);
190
- }
191
- else if (mode === 'percent') {
192
- const total = stackingContext?.totalData.get(categoryKey) ?? 1;
193
- const percentValue = (value / total) * 100;
194
- const yTop = y(percentValue) || 0;
195
- const yBottom = y(0) || 0;
196
- return Math.abs(yBottom - yTop);
197
- }
198
- else {
199
- // Normal stacking mode
200
- const yTop = y(value) || 0;
201
- return Math.abs(yBaseline - yTop);
202
- }
245
+ const bounds = getVerticalBounds(d);
246
+ return Math.abs(bounds.max - bounds.min);
203
247
  })
204
248
  .attr('fill', (d, i) => this.colorAdapter ? this.colorAdapter(d, i) : this.fill);
205
249
  }
206
250
  renderHorizontal(plotGroup, data, xKey, x, y, parseValue, yScaleType, stackingContext) {
207
251
  const bandwidth = y.bandwidth ? y.bandwidth() : 20;
208
252
  const mode = stackingContext?.mode ?? 'normal';
209
- // Calculate bar height based on stacking mode
210
- let barHeight;
211
- let barOffset;
212
- if (mode === 'none') {
213
- // Grouped bars: divide bandwidth among series with gap
214
- const totalSeries = stackingContext?.totalSeries ?? 1;
215
- const seriesIndex = stackingContext?.seriesIndex ?? 0;
216
- const gap = stackingContext?.gap ?? 0.1;
217
- const groupHeight = this.maxBarSize
218
- ? Math.min(bandwidth, this.maxBarSize * totalSeries)
219
- : bandwidth;
220
- // Calculate total gap space and individual bar height
221
- const totalGapSpace = groupHeight * gap * (totalSeries - 1);
222
- const availableHeight = groupHeight - totalGapSpace;
223
- barHeight = availableHeight / totalSeries;
224
- const gapSize = totalSeries > 1 ? groupHeight * gap : 0;
225
- barOffset =
226
- (bandwidth - groupHeight) / 2 +
227
- seriesIndex * (barHeight + gapSize);
228
- }
229
- else if (mode === 'layer') {
230
- // Layer mode: each subsequent series has smaller bars
231
- const totalSeries = stackingContext?.totalSeries ?? 1;
232
- const seriesIndex = stackingContext?.seriesIndex ?? 0;
233
- const maxHeight = this.maxBarSize
234
- ? Math.min(bandwidth, this.maxBarSize)
235
- : bandwidth;
236
- const scaleFactor = 1 - (seriesIndex / totalSeries) * 0.7;
237
- barHeight = maxHeight * scaleFactor;
238
- barOffset = (bandwidth - barHeight) / 2;
239
- }
240
- else {
241
- // Normal and Percent modes: full height stacked bars
242
- barHeight = this.maxBarSize
243
- ? Math.min(bandwidth, this.maxBarSize)
244
- : bandwidth;
245
- barOffset = (bandwidth - barHeight) / 2;
246
- }
247
- // Get the baseline value from the scale's domain
248
- const domain = x.domain();
249
- const baselineValue = domain[0] >= 0 ? Math.max(0, domain[0]) : domain[0];
250
- const xBaseline = x(baselineValue) || 0;
253
+ const { thickness: barHeight, offset: barOffset } = getBarSlotLayout(bandwidth, mode, this.maxBarSize, stackingContext?.totalSeries ?? 1, stackingContext?.seriesIndex ?? 0, stackingContext?.gap ?? 0.1);
254
+ const getHorizontalBounds = (d) => {
255
+ const categoryKey = String(d[xKey]);
256
+ const value = this.getRenderedValue(parseValue(d[this.dataKey]), 'horizontal');
257
+ const { start, end } = getBarValueRange(categoryKey, value, stackingContext);
258
+ return getScaledValueRange(x, start, end);
259
+ };
251
260
  // Add bar rectangles (horizontal)
252
261
  const sanitizedKey = sanitizeForCSS(this.dataKey);
253
262
  plotGroup
@@ -256,25 +265,7 @@ export class Bar {
256
265
  .join('rect')
257
266
  .attr('class', `bar-${sanitizedKey}`)
258
267
  .attr('data-index', (_, i) => i)
259
- .attr('x', (d) => {
260
- const categoryKey = String(d[xKey]);
261
- const value = parseValue(d[this.dataKey]);
262
- if (mode === 'none' || mode === 'layer') {
263
- const xPos = x(value) || 0;
264
- return Math.min(xBaseline, xPos);
265
- }
266
- else if (mode === 'percent') {
267
- const cumulative = stackingContext?.cumulativeData.get(categoryKey) ?? 0;
268
- const total = stackingContext?.totalData.get(categoryKey) ?? 1;
269
- const percentCumulative = (cumulative / total) * 100;
270
- return x(percentCumulative) || 0;
271
- }
272
- else {
273
- // Normal stacking mode
274
- const cumulative = stackingContext?.cumulativeData.get(categoryKey) ?? 0;
275
- return x(cumulative) || 0;
276
- }
277
- })
268
+ .attr('x', (d) => getHorizontalBounds(d).min)
278
269
  .attr('y', (d) => {
279
270
  const yPos = getScalePosition(y, d[xKey], yScaleType);
280
271
  return yScaleType === 'band'
@@ -282,396 +273,281 @@ export class Bar {
282
273
  : yPos - barHeight / 2;
283
274
  })
284
275
  .attr('width', (d) => {
285
- const categoryKey = String(d[xKey]);
286
- const value = parseValue(d[this.dataKey]);
287
- if (mode === 'none' || mode === 'layer') {
288
- const xPos = x(value) || 0;
289
- return Math.abs(xPos - xBaseline);
290
- }
291
- else if (mode === 'percent') {
292
- const total = stackingContext?.totalData.get(categoryKey) ?? 1;
293
- const percentValue = (value / total) * 100;
294
- const xLeft = x(0) || 0;
295
- const xRight = x(percentValue) || 0;
296
- return Math.abs(xRight - xLeft);
297
- }
298
- else {
299
- // Normal stacking mode
300
- const xLeft = x(0) || 0;
301
- const xRight = x(value) || 0;
302
- return Math.abs(xRight - xLeft);
303
- }
276
+ const bounds = getHorizontalBounds(d);
277
+ return Math.abs(bounds.max - bounds.min);
304
278
  })
305
279
  .attr('height', barHeight)
306
280
  .attr('fill', (d, i) => this.colorAdapter ? this.colorAdapter(d, i) : this.fill);
307
281
  }
308
- renderVerticalValueLabels(plotGroup, data, xKey, x, y, parseValue, xScaleType, theme, stackingContext) {
309
- const bandwidth = x.bandwidth ? x.bandwidth() : 20;
310
- const mode = stackingContext?.mode ?? 'normal';
311
- // Calculate bar width based on stacking mode (same logic as renderVertical)
312
- let barWidth;
313
- let barOffset;
314
- if (mode === 'none') {
315
- const totalSeries = stackingContext?.totalSeries ?? 1;
316
- const seriesIndex = stackingContext?.seriesIndex ?? 0;
317
- const gap = stackingContext?.gap ?? 0.1;
318
- const groupWidth = this.maxBarSize
319
- ? Math.min(bandwidth, this.maxBarSize * totalSeries)
320
- : bandwidth;
321
- const totalGapSpace = groupWidth * gap * (totalSeries - 1);
322
- const availableWidth = groupWidth - totalGapSpace;
323
- barWidth = availableWidth / totalSeries;
324
- const gapSize = totalSeries > 1 ? groupWidth * gap : 0;
325
- barOffset =
326
- (bandwidth - groupWidth) / 2 +
327
- seriesIndex * (barWidth + gapSize);
282
+ resolveValueLabelConfig(theme) {
283
+ const config = this.valueLabel;
284
+ return {
285
+ ...this.resolveValueLabelPlacement(config),
286
+ ...this.resolveValueLabelStyle(config, theme),
287
+ formatter: config.formatter,
288
+ autoContrastInside: config.color === undefined,
289
+ };
290
+ }
291
+ resolveValueLabelPlacement(config) {
292
+ return {
293
+ position: config.position ?? 'outside',
294
+ insidePosition: config.insidePosition ?? 'top',
295
+ };
296
+ }
297
+ resolveValueLabelStyle(config, theme) {
298
+ return {
299
+ fontSize: config.fontSize ?? theme.valueLabel.fontSize,
300
+ fontFamily: config.fontFamily ?? theme.valueLabel.fontFamily,
301
+ fontWeight: config.fontWeight ?? theme.valueLabel.fontWeight,
302
+ color: config.color ?? theme.valueLabel.color,
303
+ background: config.background ?? theme.valueLabel.background,
304
+ border: config.border ?? theme.valueLabel.border,
305
+ borderRadius: config.borderRadius ?? theme.valueLabel.borderRadius,
306
+ padding: config.padding ?? theme.valueLabel.padding,
307
+ };
308
+ }
309
+ getValueLabelText(rawValue, data, config) {
310
+ return config.formatter
311
+ ? config.formatter(this.dataKey, rawValue, data)
312
+ : String(rawValue);
313
+ }
314
+ getBarColor(data, index) {
315
+ return this.colorAdapter ? this.colorAdapter(data, index) : this.fill;
316
+ }
317
+ measureLabelBox(labelGroup, valueText, config) {
318
+ const tempText = labelGroup
319
+ .append('text')
320
+ .style('font-size', `${config.fontSize}px`)
321
+ .style('font-family', config.fontFamily)
322
+ .style('font-weight', config.fontWeight)
323
+ .text(valueText);
324
+ const textBox = tempText.node().getBBox();
325
+ tempText.remove();
326
+ return {
327
+ width: textBox.width + config.padding * 2,
328
+ height: textBox.height + config.padding * 2,
329
+ };
330
+ }
331
+ getLabelColor(config, barColor) {
332
+ if (config.position === 'inside' && config.autoContrastInside) {
333
+ return getContrastTextColor(barColor);
328
334
  }
329
- else if (mode === 'layer') {
330
- const totalSeries = stackingContext?.totalSeries ?? 1;
331
- const seriesIndex = stackingContext?.seriesIndex ?? 0;
332
- const maxWidth = this.maxBarSize
333
- ? Math.min(bandwidth, this.maxBarSize)
334
- : bandwidth;
335
- const scaleFactor = 1 - (seriesIndex / totalSeries) * 0.7;
336
- barWidth = maxWidth * scaleFactor;
337
- barOffset = (bandwidth - barWidth) / 2;
335
+ return config.color;
336
+ }
337
+ appendValueLabel(labelGroup, valueText, placement, labelBox, config, labelColor) {
338
+ const group = labelGroup.append('g');
339
+ if (config.position === 'outside') {
340
+ group
341
+ .append('rect')
342
+ .attr('x', placement.x - labelBox.width / 2)
343
+ .attr('y', placement.y - labelBox.height / 2)
344
+ .attr('width', labelBox.width)
345
+ .attr('height', labelBox.height)
346
+ .attr('rx', config.borderRadius)
347
+ .attr('ry', config.borderRadius)
348
+ .attr('fill', config.background)
349
+ .attr('stroke', config.border)
350
+ .attr('stroke-width', 1);
338
351
  }
339
- else {
340
- barWidth = this.maxBarSize
341
- ? Math.min(bandwidth, this.maxBarSize)
342
- : bandwidth;
343
- barOffset = (bandwidth - barWidth) / 2;
352
+ group
353
+ .append('text')
354
+ .attr('x', placement.x)
355
+ .attr('y', placement.y)
356
+ .attr('text-anchor', 'middle')
357
+ .attr('dominant-baseline', 'central')
358
+ .style('font-size', `${config.fontSize}px`)
359
+ .style('font-family', config.fontFamily)
360
+ .style('font-weight', config.fontWeight)
361
+ .style('fill', labelColor)
362
+ .style('pointer-events', 'none')
363
+ .text(valueText);
364
+ }
365
+ getVerticalLabelPlacement(input) {
366
+ if (input.position === 'outside') {
367
+ return this.getVerticalOutsideLabelPlacement(input);
344
368
  }
345
- const yDomain = y.domain();
346
- const baselineValue = yDomain[0] >= 0 ? Math.max(0, yDomain[0]) : yDomain[0];
347
- const yBaseline = y(baselineValue) || 0;
348
- const config = this.valueLabel;
349
- const position = config.position || 'outside';
350
- const insidePosition = config.insidePosition || 'top';
351
- const fontSize = config.fontSize ?? theme.valueLabel.fontSize;
352
- const fontFamily = config.fontFamily ?? theme.valueLabel.fontFamily;
353
- const fontWeight = config.fontWeight ?? theme.valueLabel.fontWeight;
354
- const defaultLabelColor = config.color ?? theme.valueLabel.color;
355
- const background = config.background ?? theme.valueLabel.background;
356
- const border = config.border ?? theme.valueLabel.border;
357
- const borderRadius = config.borderRadius ?? theme.valueLabel.borderRadius;
358
- const padding = config.padding ?? theme.valueLabel.padding;
369
+ if (input.mode === 'layer' && input.insidePosition === 'bottom') {
370
+ return {
371
+ x: input.x,
372
+ y: (input.barTop + input.barBottom) / 2,
373
+ shouldRender: false,
374
+ };
375
+ }
376
+ const { inset, minPadding } = getLabelSpacing(input.mode);
377
+ const y = this.getVerticalInsideLabelY(input.barTop, input.barBottom, input.labelBox.height, input.insidePosition, inset);
378
+ return {
379
+ x: input.x,
380
+ y,
381
+ shouldRender: input.labelBox.height + minPadding <= input.barHeight,
382
+ };
383
+ }
384
+ getVerticalOutsideLabelPlacement(input) {
385
+ const y = input.isNegative
386
+ ? input.barBottom + input.labelBox.height / 2 + LABEL_OUTSIDE_OFFSET
387
+ : input.barTop - input.labelBox.height / 2 - LABEL_OUTSIDE_OFFSET;
388
+ const shouldRender = input.isNegative
389
+ ? y + input.labelBox.height / 2 <= input.plotBottom
390
+ : y - input.labelBox.height / 2 >= input.plotTop;
391
+ return {
392
+ x: input.x,
393
+ y,
394
+ shouldRender,
395
+ };
396
+ }
397
+ getVerticalInsideLabelY(barTop, barBottom, labelHeight, insidePosition, inset) {
398
+ switch (insidePosition) {
399
+ case 'top':
400
+ return barTop + labelHeight / 2 + inset;
401
+ case 'middle':
402
+ return (barTop + barBottom) / 2;
403
+ case 'bottom':
404
+ return barBottom - labelHeight / 2 - inset;
405
+ }
406
+ }
407
+ getHorizontalLabelPlacement(input) {
408
+ if (input.position === 'outside') {
409
+ return this.getHorizontalOutsideLabelPlacement(input);
410
+ }
411
+ if (input.mode === 'layer' && input.insidePosition === 'bottom') {
412
+ return {
413
+ x: (input.barLeft + input.barRight) / 2,
414
+ y: input.y,
415
+ shouldRender: false,
416
+ };
417
+ }
418
+ const { inset, minPadding } = getLabelSpacing(input.mode);
419
+ const x = this.getHorizontalInsideLabelX(input.barLeft, input.barRight, input.labelBox.width, input.isNegative, input.insidePosition, inset);
420
+ const fitsBar = input.labelBox.width + minPadding <= input.barWidth;
421
+ const withinBounds = input.insidePosition === 'middle' ||
422
+ this.isHorizontalLabelWithinBounds(x, input.labelBox.width, input.barLeft, input.barRight);
423
+ return {
424
+ x,
425
+ y: input.y,
426
+ shouldRender: fitsBar && withinBounds,
427
+ };
428
+ }
429
+ getHorizontalOutsideLabelPlacement(input) {
430
+ const x = input.isNegative
431
+ ? input.barLeft - input.labelBox.width / 2 - LABEL_OUTSIDE_OFFSET
432
+ : input.barRight + input.labelBox.width / 2 + LABEL_OUTSIDE_OFFSET;
433
+ const shouldRender = input.isNegative
434
+ ? x - input.labelBox.width / 2 >= input.plotLeft
435
+ : x + input.labelBox.width / 2 <= input.plotRight;
436
+ return {
437
+ x,
438
+ y: input.y,
439
+ shouldRender,
440
+ };
441
+ }
442
+ getHorizontalInsideLabelX(barLeft, barRight, labelWidth, isNegative, insidePosition, inset) {
443
+ switch (insidePosition) {
444
+ case 'top':
445
+ return isNegative
446
+ ? barRight - labelWidth / 2 - inset
447
+ : barLeft + labelWidth / 2 + inset;
448
+ case 'middle':
449
+ return (barLeft + barRight) / 2;
450
+ case 'bottom':
451
+ return isNegative
452
+ ? barLeft + labelWidth / 2 + inset
453
+ : barRight - labelWidth / 2 - inset;
454
+ }
455
+ }
456
+ isHorizontalLabelWithinBounds(labelX, labelWidth, barLeft, barRight) {
457
+ const labelLeft = labelX - labelWidth / 2;
458
+ const labelRight = labelX + labelWidth / 2;
459
+ return labelLeft >= barLeft + 1 && labelRight <= barRight - 1;
460
+ }
461
+ renderVerticalValueLabel(labelGroup, dataItem, index, xKey, x, y, parseValue, xScaleType, stackingContext, config, barWidth, barOffset, mode, plotTop, plotBottom) {
462
+ const categoryKey = String(dataItem[xKey]);
463
+ const rawValue = parseValue(dataItem[this.dataKey]);
464
+ const renderedValue = this.getRenderedValue(rawValue, 'vertical');
465
+ const valueText = this.getValueLabelText(rawValue, dataItem, config);
466
+ const xPos = getScalePosition(x, dataItem[xKey], xScaleType);
467
+ const barColor = this.getBarColor(dataItem, index);
468
+ const { start, end } = getBarValueRange(categoryKey, renderedValue, stackingContext);
469
+ const bounds = getScaledValueRange(y, start, end);
470
+ const barTop = bounds.min;
471
+ const barBottom = bounds.max;
472
+ const barHeight = Math.abs(barBottom - barTop);
473
+ const barCenterX = xPos +
474
+ (xScaleType === 'band' ? barOffset : -barWidth / 2) +
475
+ barWidth / 2;
476
+ const labelBox = this.measureLabelBox(labelGroup, valueText, config);
477
+ const placement = this.getVerticalLabelPlacement({
478
+ x: barCenterX,
479
+ barTop,
480
+ barBottom,
481
+ barHeight,
482
+ labelBox,
483
+ isNegative: renderedValue < 0,
484
+ mode,
485
+ position: config.position,
486
+ insidePosition: config.insidePosition,
487
+ plotTop,
488
+ plotBottom,
489
+ });
490
+ if (!placement.shouldRender) {
491
+ return;
492
+ }
493
+ this.appendValueLabel(labelGroup, valueText, placement, labelBox, config, this.getLabelColor(config, barColor));
494
+ }
495
+ renderHorizontalValueLabel(labelGroup, dataItem, index, xKey, x, y, parseValue, yScaleType, stackingContext, config, barHeight, barOffset, mode, plotLeft, plotRight) {
496
+ const categoryKey = String(dataItem[xKey]);
497
+ const rawValue = parseValue(dataItem[this.dataKey]);
498
+ const renderedValue = this.getRenderedValue(rawValue, 'horizontal');
499
+ const valueText = this.getValueLabelText(rawValue, dataItem, config);
500
+ const yPos = getScalePosition(y, dataItem[xKey], yScaleType);
501
+ const barColor = this.getBarColor(dataItem, index);
502
+ const { start, end } = getBarValueRange(categoryKey, renderedValue, stackingContext);
503
+ const bounds = getScaledValueRange(x, start, end);
504
+ const barLeft = bounds.min;
505
+ const barRight = bounds.max;
506
+ const barWidth = Math.abs(barRight - barLeft);
507
+ const barCenterY = yPos +
508
+ (yScaleType === 'band' ? barOffset : -barHeight / 2) +
509
+ barHeight / 2;
510
+ const labelBox = this.measureLabelBox(labelGroup, valueText, config);
511
+ const placement = this.getHorizontalLabelPlacement({
512
+ y: barCenterY,
513
+ barLeft,
514
+ barRight,
515
+ barWidth,
516
+ labelBox,
517
+ isNegative: renderedValue < 0,
518
+ mode,
519
+ position: config.position,
520
+ insidePosition: config.insidePosition,
521
+ plotLeft,
522
+ plotRight,
523
+ });
524
+ if (!placement.shouldRender) {
525
+ return;
526
+ }
527
+ this.appendValueLabel(labelGroup, valueText, placement, labelBox, config, this.getLabelColor(config, barColor));
528
+ }
529
+ renderVerticalValueLabels(plotGroup, data, xKey, x, y, parseValue, xScaleType, theme, stackingContext) {
530
+ const bandwidth = x.bandwidth ? x.bandwidth() : 20;
531
+ const mode = stackingContext?.mode ?? 'normal';
532
+ const { thickness: barWidth, offset: barOffset } = getBarSlotLayout(bandwidth, mode, this.maxBarSize, stackingContext?.totalSeries ?? 1, stackingContext?.seriesIndex ?? 0, stackingContext?.gap ?? 0.1);
533
+ const config = this.resolveValueLabelConfig(theme);
534
+ const plotTop = Math.min(...y.range());
535
+ const plotBottom = Math.max(...y.range());
359
536
  const labelGroup = plotGroup
360
537
  .append('g')
361
538
  .attr('class', `bar-value-labels-${sanitizeForCSS(this.dataKey)}`);
362
- data.forEach((d, i) => {
363
- const categoryKey = String(d[xKey]);
364
- const value = parseValue(d[this.dataKey]);
365
- const valueText = String(value);
366
- const xPos = getScalePosition(x, d[xKey], xScaleType);
367
- const barColor = this.colorAdapter
368
- ? this.colorAdapter(d, i)
369
- : this.fill;
370
- // Calculate bar position based on stacking mode
371
- let barTop;
372
- let barBottom;
373
- if (mode === 'none' || mode === 'layer') {
374
- const yPos = y(value) || 0;
375
- barTop = Math.min(yBaseline, yPos);
376
- barBottom = Math.max(yBaseline, yPos);
377
- }
378
- else if (mode === 'percent') {
379
- const cumulative = stackingContext?.cumulativeData.get(categoryKey) ?? 0;
380
- const total = stackingContext?.totalData.get(categoryKey) ?? 1;
381
- const percentCumulative = (cumulative / total) * 100;
382
- const percentValue = (value / total) * 100;
383
- barTop = y(percentCumulative + percentValue) || 0;
384
- barBottom = y(percentCumulative) || 0;
385
- }
386
- else {
387
- const cumulative = stackingContext?.cumulativeData.get(categoryKey) ?? 0;
388
- barTop = y(cumulative + value) || 0;
389
- barBottom = y(cumulative) || 0;
390
- }
391
- const barHeight = Math.abs(barBottom - barTop);
392
- const barCenterX = xPos +
393
- (xScaleType === 'band' ? barOffset : -barWidth / 2) +
394
- barWidth / 2;
395
- // Create temporary text to measure dimensions
396
- const tempText = labelGroup
397
- .append('text')
398
- .style('font-size', `${fontSize}px`)
399
- .style('font-family', fontFamily)
400
- .style('font-weight', fontWeight)
401
- .text(valueText);
402
- const textBBox = tempText.node().getBBox();
403
- const boxWidth = textBBox.width + padding * 2;
404
- const boxHeight = textBBox.height + padding * 2;
405
- const labelX = barCenterX;
406
- let labelY = (barTop + barBottom) / 2; // Default to middle
407
- let shouldRender = true;
408
- if (position === 'outside') {
409
- // Place above the bar
410
- labelY = barTop - boxHeight / 2 - 4;
411
- // Check if it fits (not going above plot area)
412
- const plotTop = y.range()[1];
413
- if (labelY - boxHeight / 2 < plotTop) {
414
- shouldRender = false;
415
- }
416
- }
417
- else {
418
- if (mode === 'layer' && insidePosition === 'bottom') {
419
- // Bottom labels in layer mode are visually ambiguous and often hidden by overlap.
420
- shouldRender = false;
421
- }
422
- else {
423
- const { inset, minPadding } = getLabelSpacing(mode);
424
- switch (insidePosition) {
425
- case 'top':
426
- labelY = barTop + boxHeight / 2 + inset;
427
- break;
428
- case 'middle':
429
- labelY = (barTop + barBottom) / 2;
430
- break;
431
- case 'bottom':
432
- labelY = barBottom - boxHeight / 2 - inset;
433
- break;
434
- }
435
- // Check if it fits inside the bar
436
- if (boxHeight + minPadding > barHeight) {
437
- shouldRender = false;
438
- }
439
- // In layer mode, check the label fits in the visible gap
440
- // above the next layer's bar top
441
- if (shouldRender &&
442
- mode === 'layer' &&
443
- insidePosition === 'top' &&
444
- stackingContext?.nextLayerData) {
445
- const nextValue = stackingContext.nextLayerData.get(categoryKey);
446
- if (nextValue !== undefined) {
447
- const nextBarTop = y(nextValue) || 0;
448
- const labelBottom = labelY + boxHeight / 2;
449
- if (labelBottom + LAYER_LABEL_GAP > nextBarTop) {
450
- shouldRender = false;
451
- }
452
- }
453
- }
454
- }
455
- }
456
- tempText.remove();
457
- if (shouldRender) {
458
- const labelColor = position === 'inside' && config.color === undefined
459
- ? getContrastTextColor(barColor)
460
- : defaultLabelColor;
461
- const group = labelGroup.append('g');
462
- if (position === 'outside') {
463
- // Draw rounded rectangle background
464
- group
465
- .append('rect')
466
- .attr('x', labelX - boxWidth / 2)
467
- .attr('y', labelY - boxHeight / 2)
468
- .attr('width', boxWidth)
469
- .attr('height', boxHeight)
470
- .attr('rx', borderRadius)
471
- .attr('ry', borderRadius)
472
- .attr('fill', background)
473
- .attr('stroke', border)
474
- .attr('stroke-width', 1);
475
- }
476
- // Draw text
477
- group
478
- .append('text')
479
- .attr('x', labelX)
480
- .attr('y', labelY)
481
- .attr('text-anchor', 'middle')
482
- .attr('dominant-baseline', 'central')
483
- .style('font-size', `${fontSize}px`)
484
- .style('font-family', fontFamily)
485
- .style('font-weight', fontWeight)
486
- .style('fill', labelColor)
487
- .style('pointer-events', 'none')
488
- .text(valueText);
489
- }
490
- });
539
+ data.forEach((dataItem, index) => this.renderVerticalValueLabel(labelGroup, dataItem, index, xKey, x, y, parseValue, xScaleType, stackingContext, config, barWidth, barOffset, mode, plotTop, plotBottom));
491
540
  }
492
541
  renderHorizontalValueLabels(plotGroup, data, xKey, x, y, parseValue, yScaleType, theme, stackingContext) {
493
542
  const bandwidth = y.bandwidth ? y.bandwidth() : 20;
494
543
  const mode = stackingContext?.mode ?? 'normal';
495
- // Calculate bar height based on stacking mode (same logic as renderHorizontal)
496
- let barHeight;
497
- let barOffset;
498
- if (mode === 'none') {
499
- const totalSeries = stackingContext?.totalSeries ?? 1;
500
- const seriesIndex = stackingContext?.seriesIndex ?? 0;
501
- const gap = stackingContext?.gap ?? 0.1;
502
- const groupHeight = this.maxBarSize
503
- ? Math.min(bandwidth, this.maxBarSize * totalSeries)
504
- : bandwidth;
505
- const totalGapSpace = groupHeight * gap * (totalSeries - 1);
506
- const availableHeight = groupHeight - totalGapSpace;
507
- barHeight = availableHeight / totalSeries;
508
- const gapSize = totalSeries > 1 ? groupHeight * gap : 0;
509
- barOffset =
510
- (bandwidth - groupHeight) / 2 +
511
- seriesIndex * (barHeight + gapSize);
512
- }
513
- else if (mode === 'layer') {
514
- const totalSeries = stackingContext?.totalSeries ?? 1;
515
- const seriesIndex = stackingContext?.seriesIndex ?? 0;
516
- const maxHeight = this.maxBarSize
517
- ? Math.min(bandwidth, this.maxBarSize)
518
- : bandwidth;
519
- const scaleFactor = 1 - (seriesIndex / totalSeries) * 0.7;
520
- barHeight = maxHeight * scaleFactor;
521
- barOffset = (bandwidth - barHeight) / 2;
522
- }
523
- else {
524
- barHeight = this.maxBarSize
525
- ? Math.min(bandwidth, this.maxBarSize)
526
- : bandwidth;
527
- barOffset = (bandwidth - barHeight) / 2;
528
- }
529
- const domain = x.domain();
530
- const baselineValue = domain[0] >= 0 ? Math.max(0, domain[0]) : domain[0];
531
- const xBaseline = x(baselineValue) || 0;
532
- const config = this.valueLabel;
533
- const position = config.position || 'outside';
534
- const insidePosition = config.insidePosition || 'top';
535
- const fontSize = config.fontSize ?? theme.valueLabel.fontSize;
536
- const fontFamily = config.fontFamily ?? theme.valueLabel.fontFamily;
537
- const fontWeight = config.fontWeight ?? theme.valueLabel.fontWeight;
538
- const defaultLabelColor = config.color ?? theme.valueLabel.color;
539
- const background = config.background ?? theme.valueLabel.background;
540
- const border = config.border ?? theme.valueLabel.border;
541
- const borderRadius = config.borderRadius ?? theme.valueLabel.borderRadius;
542
- const padding = config.padding ?? theme.valueLabel.padding;
544
+ const { thickness: barHeight, offset: barOffset } = getBarSlotLayout(bandwidth, mode, this.maxBarSize, stackingContext?.totalSeries ?? 1, stackingContext?.seriesIndex ?? 0, stackingContext?.gap ?? 0.1);
545
+ const config = this.resolveValueLabelConfig(theme);
546
+ const plotLeft = Math.min(...x.range());
547
+ const plotRight = Math.max(...x.range());
543
548
  const labelGroup = plotGroup
544
549
  .append('g')
545
550
  .attr('class', `bar-value-labels-${sanitizeForCSS(this.dataKey)}`);
546
- data.forEach((d, i) => {
547
- const categoryKey = String(d[xKey]);
548
- const value = parseValue(d[this.dataKey]);
549
- const valueText = String(value);
550
- const yPos = getScalePosition(y, d[xKey], yScaleType);
551
- const barColor = this.colorAdapter
552
- ? this.colorAdapter(d, i)
553
- : this.fill;
554
- // Calculate bar position based on stacking mode
555
- let barLeft;
556
- let barRight;
557
- if (mode === 'none' || mode === 'layer') {
558
- const xPos = x(value) || 0;
559
- barLeft = Math.min(xBaseline, xPos);
560
- barRight = Math.max(xBaseline, xPos);
561
- }
562
- else if (mode === 'percent') {
563
- const cumulative = stackingContext?.cumulativeData.get(categoryKey) ?? 0;
564
- const total = stackingContext?.totalData.get(categoryKey) ?? 1;
565
- const percentCumulative = (cumulative / total) * 100;
566
- const percentValue = (value / total) * 100;
567
- barLeft = x(percentCumulative) || 0;
568
- barRight = x(percentCumulative + percentValue) || 0;
569
- }
570
- else {
571
- const cumulative = stackingContext?.cumulativeData.get(categoryKey) ?? 0;
572
- barLeft = x(cumulative) || 0;
573
- barRight = x(cumulative + value) || 0;
574
- }
575
- const barWidth = Math.abs(barRight - barLeft);
576
- const barCenterY = yPos +
577
- (yScaleType === 'band' ? barOffset : -barHeight / 2) +
578
- barHeight / 2;
579
- // Create temporary text to measure dimensions
580
- const tempText = labelGroup
581
- .append('text')
582
- .style('font-size', `${fontSize}px`)
583
- .style('font-family', fontFamily)
584
- .style('font-weight', fontWeight)
585
- .text(valueText);
586
- const textBBox = tempText.node().getBBox();
587
- const boxWidth = textBBox.width + padding * 2;
588
- const boxHeight = textBBox.height + padding * 2;
589
- let labelX = (barLeft + barRight) / 2; // Default to middle
590
- const labelY = barCenterY;
591
- let shouldRender = true;
592
- if (position === 'outside') {
593
- // Place to the right of the bar
594
- labelX = barRight + boxWidth / 2 + 4;
595
- // Check if it fits (not going beyond plot area)
596
- const plotRight = x.range()[1];
597
- if (labelX + boxWidth / 2 > plotRight) {
598
- shouldRender = false;
599
- }
600
- }
601
- else {
602
- // Map top/middle/bottom to start/middle/end for horizontal
603
- if (mode === 'layer' && insidePosition === 'bottom') {
604
- // Bottom labels in layer mode are visually ambiguous and often hidden by overlap.
605
- shouldRender = false;
606
- }
607
- else {
608
- const { inset, minPadding } = getLabelSpacing(mode);
609
- switch (insidePosition) {
610
- case 'top': // start of bar (left side)
611
- labelX = barLeft + boxWidth / 2 + inset;
612
- break;
613
- case 'middle':
614
- labelX = (barLeft + barRight) / 2;
615
- break;
616
- case 'bottom': // end of bar (right side)
617
- labelX = barRight - boxWidth / 2 - inset;
618
- break;
619
- }
620
- // Check if it fits inside the bar
621
- if (boxWidth + minPadding > barWidth) {
622
- shouldRender = false;
623
- }
624
- // In layer mode, check the label fits in the visible gap
625
- // before the next layer's bar end
626
- if (shouldRender &&
627
- mode === 'layer' &&
628
- insidePosition === 'top' &&
629
- stackingContext?.nextLayerData) {
630
- const nextValue = stackingContext.nextLayerData.get(categoryKey);
631
- if (nextValue !== undefined) {
632
- const nextBarRight = x(nextValue) || 0;
633
- const labelRight = labelX + boxWidth / 2;
634
- if (labelRight + LAYER_LABEL_GAP > nextBarRight) {
635
- shouldRender = false;
636
- }
637
- }
638
- }
639
- }
640
- }
641
- tempText.remove();
642
- if (shouldRender) {
643
- const labelColor = position === 'inside' && config.color === undefined
644
- ? getContrastTextColor(barColor)
645
- : defaultLabelColor;
646
- const group = labelGroup.append('g');
647
- if (position === 'outside') {
648
- // Draw rounded rectangle background
649
- group
650
- .append('rect')
651
- .attr('x', labelX - boxWidth / 2)
652
- .attr('y', labelY - boxHeight / 2)
653
- .attr('width', boxWidth)
654
- .attr('height', boxHeight)
655
- .attr('rx', borderRadius)
656
- .attr('ry', borderRadius)
657
- .attr('fill', background)
658
- .attr('stroke', border)
659
- .attr('stroke-width', 1);
660
- }
661
- // Draw text
662
- group
663
- .append('text')
664
- .attr('x', labelX)
665
- .attr('y', labelY)
666
- .attr('text-anchor', 'middle')
667
- .attr('dominant-baseline', 'central')
668
- .style('font-size', `${fontSize}px`)
669
- .style('font-family', fontFamily)
670
- .style('font-weight', fontWeight)
671
- .style('fill', labelColor)
672
- .style('pointer-events', 'none')
673
- .text(valueText);
674
- }
675
- });
551
+ data.forEach((dataItem, index) => this.renderHorizontalValueLabel(labelGroup, dataItem, index, xKey, x, y, parseValue, yScaleType, stackingContext, config, barHeight, barOffset, mode, plotLeft, plotRight));
676
552
  }
677
553
  }