@coinbase/cds-mobile-visualization 3.4.0-beta.24 → 3.4.0-beta.26

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 (49) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dts/chart/Path.d.ts +2 -1
  3. package/dts/chart/Path.d.ts.map +1 -1
  4. package/dts/chart/PeriodSelector.d.ts +11 -1
  5. package/dts/chart/PeriodSelector.d.ts.map +1 -1
  6. package/dts/chart/bar/Bar.d.ts +18 -54
  7. package/dts/chart/bar/Bar.d.ts.map +1 -1
  8. package/dts/chart/bar/BarChart.d.ts +2 -2
  9. package/dts/chart/bar/BarPlot.d.ts.map +1 -1
  10. package/dts/chart/bar/BarStack.d.ts +4 -4
  11. package/dts/chart/bar/BarStack.d.ts.map +1 -1
  12. package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
  13. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
  14. package/dts/chart/point/Point.d.ts +2 -1
  15. package/dts/chart/point/Point.d.ts.map +1 -1
  16. package/dts/chart/scrubber/Scrubber.d.ts +4 -2
  17. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
  18. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -1
  19. package/dts/chart/utils/bar.d.ts +155 -0
  20. package/dts/chart/utils/bar.d.ts.map +1 -1
  21. package/dts/chart/utils/chart.d.ts +2 -1
  22. package/dts/chart/utils/chart.d.ts.map +1 -1
  23. package/dts/chart/utils/path.d.ts.map +1 -1
  24. package/dts/sparkline/Sparkline.d.ts +2 -1
  25. package/dts/sparkline/Sparkline.d.ts.map +1 -1
  26. package/dts/sparkline/SparklineArea.d.ts +2 -1
  27. package/dts/sparkline/SparklineArea.d.ts.map +1 -1
  28. package/dts/sparkline/SparklineGradient.d.ts +2 -1
  29. package/dts/sparkline/SparklineGradient.d.ts.map +1 -1
  30. package/dts/sparkline/sparkline-interactive/SparklineInteractive.d.ts +2 -1
  31. package/dts/sparkline/sparkline-interactive/SparklineInteractive.d.ts.map +1 -1
  32. package/esm/chart/bar/Bar.js +8 -14
  33. package/esm/chart/bar/BarChart.js +7 -7
  34. package/esm/chart/bar/BarPlot.js +37 -46
  35. package/esm/chart/bar/BarStack.js +71 -604
  36. package/esm/chart/bar/DefaultBar.js +11 -18
  37. package/esm/chart/bar/DefaultBarStack.js +12 -21
  38. package/esm/chart/bar/__stories__/BarChart.stories.js +104 -6
  39. package/esm/chart/line/__stories__/LineChart.stories.js +1 -0
  40. package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +9 -5
  41. package/esm/chart/utils/bar.js +775 -0
  42. package/esm/chart/utils/chart.js +2 -1
  43. package/esm/chart/utils/path.js +5 -12
  44. package/esm/sparkline/Sparkline.js +2 -1
  45. package/esm/sparkline/SparklineArea.js +2 -1
  46. package/esm/sparkline/SparklineGradient.js +2 -1
  47. package/esm/sparkline/sparkline-interactive/SparklineInteractive.js +2 -1
  48. package/esm/sparkline/sparkline-interactive-header/__stories__/SparklineInteractiveHeader.stories.js +2 -0
  49. package/package.json +5 -5
@@ -1,18 +1,16 @@
1
- function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
2
1
  import React, { memo, useMemo } from 'react';
3
2
  import { useTheme } from '@coinbase/cds-mobile/hooks/useTheme';
4
3
  import { useCartesianChartContext } from '../ChartProvider';
5
- import { evaluateGradientAtValue, getGradientStops } from '../utils/gradient';
4
+ import { EPSILON, getBars, getStackBaseline, getStackOrigin } from '../utils/bar';
5
+ import { getGradientStops } from '../utils/gradient';
6
6
  import { convertToSerializableScale } from '../utils/scale';
7
7
  import { Bar } from './Bar';
8
8
  import { DefaultBarStack } from './DefaultBarStack';
9
- import { jsx as _jsx } from "react/jsx-runtime";
10
- const EPSILON = 1e-4;
11
9
 
12
10
  /**
13
11
  * Extended series type that includes bar-specific properties.
14
12
  */
15
-
13
+ import { jsx as _jsx } from "react/jsx-runtime";
16
14
  /**
17
15
  * BarStack component that renders a single stack of bars at a specific category index.
18
16
  * Handles the stacking logic for bars within a single category.
@@ -50,23 +48,11 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
50
48
  } = useCartesianChartContext();
51
49
  const xAxis = getXAxis(xAxisId);
52
50
  const yAxis = getYAxis(yAxisId);
53
- const barsGrowVertically = layout !== 'horizontal';
54
- const baseline = useMemo(() => {
55
- var _valueScale;
56
- const domain = valueScale.domain();
57
- const [domainMin, domainMax] = domain;
58
- const baselineValue = domainMin >= 0 ? domainMin : domainMax <= 0 ? domainMax : 0;
59
- const fallback = barsGrowVertically ? rect.y + rect.height : rect.x;
60
- const baselinePos = (_valueScale = valueScale(baselineValue)) != null ? _valueScale : fallback;
61
- if (barsGrowVertically) {
62
- return Math.max(rect.y, Math.min(baselinePos, rect.y + rect.height));
63
- }
64
- return Math.max(rect.x, Math.min(baselinePos, rect.x + rect.width));
65
- }, [rect, valueScale, barsGrowVertically]);
51
+ const baseline = useMemo(() => getStackBaseline(valueScale, rect, layout), [rect, valueScale, layout]);
66
52
  const seriesGradients = useMemo(() => {
67
53
  return series.map(s => {
68
54
  if (!s.gradient) return;
69
- const gradientScale = s.gradient.axis === 'x' ? barsGrowVertically ? indexScale : valueScale : barsGrowVertically ? valueScale : indexScale;
55
+ const gradientScale = s.gradient.axis === 'x' ? layout === 'vertical' ? indexScale : valueScale : layout === 'vertical' ? valueScale : indexScale;
70
56
  const serializableScale = convertToSerializableScale(gradientScale);
71
57
  if (!serializableScale) return;
72
58
  const domain = {
@@ -81,613 +67,95 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
81
67
  stops
82
68
  };
83
69
  });
84
- }, [series, indexScale, valueScale, barsGrowVertically]);
85
-
86
- // Calculate bars for this specific category
87
- const {
88
- bars,
89
- stackRect
90
- } = useMemo(() => {
91
- const x = indexPos;
92
- const width = thickness;
93
- const yScale = valueScale;
94
- let allBars = [];
95
- if (!barsGrowVertically) {
96
- let minX = Infinity;
97
- let maxX = -Infinity;
98
- series.forEach(s => {
99
- var _yScale, _yScale2;
100
- const data = getSeriesData(s.id);
101
- if (!data) return;
102
- const value = data[categoryIndex];
103
- if (value === null || value === undefined) return;
104
- const originalData = s.data;
105
- const originalValue = originalData == null ? void 0 : originalData[categoryIndex];
106
- const shouldApplyGap = !Array.isArray(originalValue);
107
- const [bottom, top] = value.sort((a, b) => a - b);
108
- const edgeBottom = (_yScale = yScale(bottom)) != null ? _yScale : baseline;
109
- const edgeTop = (_yScale2 = yScale(top)) != null ? _yScale2 : baseline;
110
- const length = Math.abs(edgeBottom - edgeTop);
111
- const barX = Math.min(edgeBottom, edgeTop);
112
- if (length <= 0) return;
113
- minX = Math.min(minX, barX);
114
- maxX = Math.max(maxX, barX + length);
115
- let barFill = s.color || theme.color.fgPrimary;
116
- const seriesGradientConfig = seriesGradients.find(g => (g == null ? void 0 : g.seriesId) === s.id);
117
- if (seriesGradientConfig && originalValue !== null && originalValue !== undefined) {
118
- var _seriesGradientConfig;
119
- const axis = (_seriesGradientConfig = seriesGradientConfig.gradient.axis) != null ? _seriesGradientConfig : 'y';
120
- const evalValue = axis === 'x' ? Array.isArray(originalValue) ? originalValue[1] : originalValue : categoryIndex;
121
- const evaluatedColor = evaluateGradientAtValue(seriesGradientConfig.stops, evalValue, seriesGradientConfig.scale);
122
- if (evaluatedColor) {
123
- barFill = evaluatedColor;
124
- }
125
- }
126
- const roundTop = roundBaseline || Math.abs(edgeTop - baseline) >= EPSILON;
127
- const roundBottom = roundBaseline || Math.abs(edgeBottom - baseline) >= EPSILON;
128
- allBars.push({
129
- seriesId: s.id,
130
- x: barX,
131
- y: x,
132
- width: length,
133
- height: width,
134
- dataY: value,
135
- fill: barFill,
136
- roundTop,
137
- roundBottom,
138
- BarComponent: s.BarComponent,
139
- shouldApplyGap
140
- });
141
- });
142
- if (stackGap && allBars.length > 1) {
143
- const barsAboveBaseline = allBars.filter(bar => {
144
- const [bottom, top] = bar.dataY.sort((a, b) => a - b);
145
- return bottom >= 0 && top !== bottom && bar.shouldApplyGap;
146
- });
147
- const barsBelowBaseline = allBars.filter(bar => {
148
- const [bottom, top] = bar.dataY.sort((a, b) => a - b);
149
- return bottom <= 0 && bottom !== top && bar.shouldApplyGap;
150
- });
151
- if (barsAboveBaseline.length > 1) {
152
- const totalGapSpace = stackGap * (barsAboveBaseline.length - 1);
153
- const totalDataLength = barsAboveBaseline.reduce((sum, bar) => sum + bar.width, 0);
154
- const lengthReduction = totalGapSpace / totalDataLength;
155
- const sortedBars = barsAboveBaseline.sort((a, b) => a.x - b.x);
156
- let currentEdge = baseline;
157
- sortedBars.forEach((bar, index) => {
158
- const newLength = bar.width * (1 - lengthReduction);
159
- const newX = currentEdge;
160
- currentEdge = newX + newLength + (index < sortedBars.length - 1 ? stackGap : 0);
161
- const barIndex = allBars.findIndex(b => b.seriesId === bar.seriesId);
162
- if (barIndex !== -1) {
163
- allBars[barIndex] = _extends({}, allBars[barIndex], {
164
- width: newLength,
165
- x: newX
166
- });
167
- }
168
- });
169
- }
170
- if (barsBelowBaseline.length > 1) {
171
- const totalGapSpace = stackGap * (barsBelowBaseline.length - 1);
172
- const totalDataLength = barsBelowBaseline.reduce((sum, bar) => sum + bar.width, 0);
173
- const lengthReduction = totalGapSpace / totalDataLength;
174
- const sortedBars = barsBelowBaseline.sort((a, b) => b.x - a.x);
175
- let currentEdge = baseline;
176
- sortedBars.forEach((bar, index) => {
177
- const newLength = bar.width * (1 - lengthReduction);
178
- const newX = currentEdge - newLength;
179
- currentEdge = newX - (index < sortedBars.length - 1 ? stackGap : 0);
180
- const barIndex = allBars.findIndex(b => b.seriesId === bar.seriesId);
181
- if (barIndex !== -1) {
182
- allBars[barIndex] = _extends({}, allBars[barIndex], {
183
- width: newLength,
184
- x: newX
185
- });
186
- }
187
- });
188
- }
189
- if (allBars.length > 0) {
190
- minX = Math.min(...allBars.map(bar => bar.x));
191
- maxX = Math.max(...allBars.map(bar => bar.x + bar.width));
192
- }
193
- }
194
-
195
- // Horizontal border radius logic: left-to-right sorting.
196
- const sortedBars = [...allBars].sort((a, b) => a.x - b.x);
197
- const roundedBars = sortedBars.map((bar, index) => {
198
- const barBefore = index > 0 ? sortedBars[index - 1] : null;
199
- const barAfter = index < sortedBars.length - 1 ? sortedBars[index + 1] : null;
200
- const shouldRoundLower = index === 0 || bar.shouldApplyGap && stackGap || !bar.shouldApplyGap && barAfter && barAfter.x + barAfter.width !== bar.x;
201
- const shouldRoundHigher = index === sortedBars.length - 1 || bar.shouldApplyGap && stackGap || !bar.shouldApplyGap && barBefore && barBefore.x !== bar.x + bar.width;
202
- return _extends({}, bar, {
203
- roundTop: Boolean(bar.roundTop && shouldRoundHigher),
204
- roundBottom: Boolean(bar.roundBottom && shouldRoundLower)
205
- });
206
- });
207
- const stackBounds = {
208
- x: minX === Infinity ? baseline : minX,
209
- y: x,
210
- width: maxX === -Infinity ? 0 : maxX - minX,
211
- height: width
212
- };
70
+ }, [series, indexScale, valueScale, layout]);
71
+ const categoryAxis = layout === 'vertical' ? xAxis : yAxis;
72
+ const categoryData = categoryAxis != null && categoryAxis.data && Array.isArray(categoryAxis.data) && typeof categoryAxis.data[0] === 'number' ? categoryAxis.data : undefined;
73
+ const categoryValue = categoryData ? categoryData[categoryIndex] : categoryIndex;
74
+ const seriesData = useMemo(() => Object.fromEntries(series.map(s => {
75
+ var _getSeriesData;
76
+ return [s.id, (_getSeriesData = getSeriesData(s.id)) != null ? _getSeriesData : []];
77
+ })), [series, getSeriesData]);
78
+ const bars = useMemo(() => getBars({
79
+ series,
80
+ seriesData,
81
+ categoryIndex,
82
+ categoryValue,
83
+ indexPos,
84
+ thickness,
85
+ valueScale,
86
+ seriesGradients,
87
+ roundBaseline,
88
+ layout,
89
+ baseline,
90
+ stackGap,
91
+ barMinSize,
92
+ stackMinSize,
93
+ defaultFill: theme.color.fgPrimary,
94
+ borderRadius,
95
+ defaultFillOpacity,
96
+ defaultStroke,
97
+ defaultStrokeWidth,
98
+ defaultBarComponent
99
+ }), [series, seriesData, indexPos, thickness, categoryIndex, categoryValue, roundBaseline, baseline, stackGap, barMinSize, stackMinSize, valueScale, seriesGradients, theme.color.fgPrimary, layout, borderRadius, defaultFillOpacity, defaultStroke, defaultStrokeWidth, defaultBarComponent]);
100
+ const stackRect = useMemo(() => {
101
+ if (bars.length === 0) {
213
102
  return {
214
- bars: roundedBars,
215
- stackRect: stackBounds
103
+ x: layout === 'vertical' ? indexPos : baseline,
104
+ y: layout === 'vertical' ? baseline : indexPos,
105
+ width: layout === 'vertical' ? thickness : 0,
106
+ height: layout === 'vertical' ? 0 : thickness
216
107
  };
217
108
  }
218
-
219
- // Track how many bars we've stacked in each direction for gap calculation
220
- let positiveBarCount = 0;
221
- let negativeBarCount = 0;
222
-
223
- // Track stack bounds for clipping
224
- let minY = Infinity;
225
- let maxY = -Infinity;
226
-
227
- // Process each series in the stack
228
- series.forEach(s => {
229
- var _yScale3, _yScale4;
230
- const data = getSeriesData(s.id);
231
- if (!data) return;
232
- const value = data[categoryIndex];
233
- if (value === null || value === undefined) return;
234
- const originalData = s.data;
235
- const originalValue = originalData == null ? void 0 : originalData[categoryIndex];
236
- // Only apply gap logic if the original data wasn't tuple format
237
- const shouldApplyGap = !Array.isArray(originalValue);
238
-
239
- // Sort to be in ascending order
240
- const [bottom, top] = value.sort((a, b) => a - b);
241
- const isAboveBaseline = bottom >= 0 && top !== bottom;
242
- const isBelowBaseline = bottom <= 0 && bottom !== top;
243
- const barBottom = (_yScale3 = yScale(bottom)) != null ? _yScale3 : baseline;
244
- const barTop = (_yScale4 = yScale(top)) != null ? _yScale4 : baseline;
245
-
246
- // Track bar counts for later gap calculations
247
- if (shouldApplyGap) {
248
- if (isAboveBaseline) {
249
- positiveBarCount++;
250
- } else if (isBelowBaseline) {
251
- negativeBarCount++;
252
- }
253
- }
254
-
255
- // Calculate height (remember SVG y coordinates are inverted)
256
- const height = Math.abs(barBottom - barTop);
257
- const y = Math.min(barBottom, barTop);
258
-
259
- // Skip bars that would have zero or negative height
260
- if (height <= 0) {
261
- return;
262
- }
263
-
264
- // Update stack bounds
265
- minY = Math.min(minY, y);
266
- maxY = Math.max(maxY, y + height);
267
-
268
- // Determine fill color, respecting gradient if present
269
- let barFill = s.color || theme.color.fgPrimary;
270
-
271
- // Evaluate gradient if provided (using precomputed stops)
272
- const seriesGradientConfig = seriesGradients.find(g => (g == null ? void 0 : g.seriesId) === s.id);
273
- if (seriesGradientConfig) {
274
- var _seriesGradientConfig2;
275
- const axis = (_seriesGradientConfig2 = seriesGradientConfig.gradient.axis) != null ? _seriesGradientConfig2 : 'y';
276
- // For x-axis gradient, use the categoryIndex
277
- // For y-axis gradient, use the actual data value
278
- const dataValue = axis === 'x' ? categoryIndex : top;
279
- const evaluatedColor = evaluateGradientAtValue(seriesGradientConfig.stops, dataValue, seriesGradientConfig.scale);
280
- if (evaluatedColor) {
281
- // Only apply gradient color if fill is not explicitly set
282
- barFill = evaluatedColor;
283
- }
284
- }
285
- allBars.push({
286
- seriesId: s.id,
287
- x,
288
- y,
289
- width,
290
- height,
291
- dataY: value,
292
- // Store the actual data value
293
- fill: barFill,
294
- // Check if the bar should be rounded based on the baseline, with an epsilon to handle floating-point rounding
295
- roundTop: roundBaseline || Math.abs(barTop - baseline) >= EPSILON,
296
- roundBottom: roundBaseline || Math.abs(barBottom - baseline) >= EPSILON,
297
- BarComponent: s.BarComponent,
298
- shouldApplyGap
299
- });
300
- });
301
-
302
- // Apply proportional gap distribution to maintain total stack height
303
- if (stackGap && allBars.length > 1) {
304
- // Separate bars by baseline side
305
- const barsAboveBaseline = allBars.filter(bar => {
306
- const [bottom, top] = bar.dataY.sort((a, b) => a - b);
307
- return bottom >= 0 && top !== bottom && bar.shouldApplyGap;
308
- });
309
- const barsBelowBaseline = allBars.filter(bar => {
310
- const [bottom, top] = bar.dataY.sort((a, b) => a - b);
311
- return bottom <= 0 && bottom !== top && bar.shouldApplyGap;
312
- });
313
-
314
- // Apply proportional gaps to bars above baseline
315
- if (barsAboveBaseline.length > 1) {
316
- const totalGapSpace = stackGap * (barsAboveBaseline.length - 1);
317
- const totalDataHeight = barsAboveBaseline.reduce((sum, bar) => sum + bar.height, 0);
318
- const heightReduction = totalGapSpace / totalDataHeight;
319
-
320
- // Sort bars by position (from baseline upward)
321
- const sortedBars = barsAboveBaseline.sort((a, b) => b.y - a.y);
322
- let currentY = baseline;
323
- sortedBars.forEach((bar, index) => {
324
- // Reduce bar height proportionally
325
- const newHeight = bar.height * (1 - heightReduction);
326
- const newY = currentY - newHeight;
327
-
328
- // Update the bar in allBars array
329
- const barIndex = allBars.findIndex(b => b.seriesId === bar.seriesId);
330
- if (barIndex !== -1) {
331
- allBars[barIndex] = _extends({}, allBars[barIndex], {
332
- height: newHeight,
333
- y: newY
334
- });
335
- }
336
-
337
- // Move to next position (include gap for next bar)
338
- currentY = newY - (index < sortedBars.length - 1 ? stackGap : 0);
339
- });
340
- }
341
-
342
- // Apply proportional gaps to bars below baseline
343
- if (barsBelowBaseline.length > 1) {
344
- const totalGapSpace = stackGap * (barsBelowBaseline.length - 1);
345
- const totalDataHeight = barsBelowBaseline.reduce((sum, bar) => sum + bar.height, 0);
346
- const heightReduction = totalGapSpace / totalDataHeight;
347
-
348
- // Sort bars by position (from baseline downward)
349
- const sortedBars = barsBelowBaseline.sort((a, b) => a.y - b.y);
350
- let currentY = baseline;
351
- sortedBars.forEach((bar, index) => {
352
- // Reduce bar height proportionally
353
- const newHeight = bar.height * (1 - heightReduction);
354
-
355
- // Update the bar in allBars array
356
- const barIndex = allBars.findIndex(b => b.seriesId === bar.seriesId);
357
- if (barIndex !== -1) {
358
- allBars[barIndex] = _extends({}, allBars[barIndex], {
359
- height: newHeight,
360
- y: currentY
361
- });
362
- }
363
-
364
- // Move to next position (include gap for next bar)
365
- currentY = currentY + newHeight + (index < sortedBars.length - 1 ? stackGap : 0);
366
- });
367
- }
368
-
369
- // Recalculate stack bounds after gap adjustments
370
- if (allBars.length > 0) {
371
- minY = Math.min(...allBars.map(bar => bar.y));
372
- maxY = Math.max(...allBars.map(bar => bar.y + bar.height));
373
- }
374
- }
375
-
376
- // Apply barMinSize constraints
377
- if (barMinSize) {
378
- // First, expand bars that need it and track the expansion
379
- const expandedBars = allBars.map((bar, index) => {
380
- if (bar.height < barMinSize) {
381
- var _yScale5, _yScale6, _yScale7, _yScale8;
382
- const heightIncrease = barMinSize - bar.height;
383
- const [bottom, top] = bar.dataY.sort((a, b) => a - b);
384
-
385
- // Determine how to expand the bar
386
- let newBottom = bottom;
387
- let newTop = top;
388
- const scaleUnit = Math.abs(((_yScale5 = yScale(1)) != null ? _yScale5 : 0) - ((_yScale6 = yScale(0)) != null ? _yScale6 : 0));
389
- if (bottom === 0) {
390
- // Expand away from baseline (upward for positive)
391
- newTop = top + heightIncrease / scaleUnit;
392
- } else if (top === 0) {
393
- // Expand away from baseline (downward for negative)
394
- newBottom = bottom - heightIncrease / scaleUnit;
395
- } else {
396
- // Expand in both directions
397
- const halfIncrease = heightIncrease / scaleUnit / 2;
398
- newBottom = bottom - halfIncrease;
399
- newTop = top + halfIncrease;
400
- }
401
-
402
- // Recalculate bar position with new data values
403
- const newBarBottom = (_yScale7 = yScale(newBottom)) != null ? _yScale7 : baseline;
404
- const newBarTop = (_yScale8 = yScale(newTop)) != null ? _yScale8 : baseline;
405
- const newHeight = Math.abs(newBarBottom - newBarTop);
406
- const newY = Math.min(newBarBottom, newBarTop);
407
- return _extends({}, bar, {
408
- height: newHeight,
409
- y: newY,
410
- wasExpanded: true
411
- });
412
- }
413
- return _extends({}, bar, {
414
- wasExpanded: false
415
- });
416
- });
417
-
418
- // Now reposition all bars to avoid overlaps, similar to stackMinSize logic
419
-
420
- // Sort bars by position to maintain order
421
- const sortedExpandedBars = [...expandedBars].sort((a, b) => a.y - b.y);
422
-
423
- // Determine if we have bars above and below baseline
424
- const barsAboveBaseline = sortedExpandedBars.filter(bar => bar.y + bar.height <= baseline);
425
- const barsBelowBaseline = sortedExpandedBars.filter(bar => bar.y >= baseline);
426
-
427
- // Create a map of new positions
428
- const newPositions = new Map();
429
-
430
- // Start positioning from the baseline and work outward
431
- let currentYAbove = baseline; // Start at baseline, work upward (decreasing Y)
432
- let currentYBelow = baseline; // Start at baseline, work downward (increasing Y)
433
-
434
- // Position bars above baseline (positive values, decreasing Y)
435
- for (let i = barsAboveBaseline.length - 1; i >= 0; i--) {
436
- const bar = barsAboveBaseline[i];
437
- const newY = currentYAbove - bar.height;
438
- newPositions.set(bar.seriesId, {
439
- y: newY,
440
- height: bar.height
441
- });
442
-
443
- // Update currentYAbove for next bar (preserve gaps)
444
- if (i > 0) {
445
- const currentBar = barsAboveBaseline[i];
446
- const nextBar = barsAboveBaseline[i - 1];
447
- // Find original bars to get original gap
448
- const originalCurrent = allBars.find(b => b.seriesId === currentBar.seriesId);
449
- const originalNext = allBars.find(b => b.seriesId === nextBar.seriesId);
450
- const originalGap = originalCurrent.y - (originalNext.y + originalNext.height);
451
- currentYAbove = newY - originalGap;
452
- }
453
- }
454
-
455
- // Position bars below baseline (negative values, increasing Y)
456
- for (let i = 0; i < barsBelowBaseline.length; i++) {
457
- const bar = barsBelowBaseline[i];
458
- const newY = currentYBelow;
459
- newPositions.set(bar.seriesId, {
460
- y: newY,
461
- height: bar.height
462
- });
463
-
464
- // Update currentYBelow for next bar (preserve gaps)
465
- if (i < barsBelowBaseline.length - 1) {
466
- const currentBar = barsBelowBaseline[i];
467
- const nextBar = barsBelowBaseline[i + 1];
468
- // Find original bars to get original gap
469
- const originalCurrent = allBars.find(b => b.seriesId === currentBar.seriesId);
470
- const originalNext = allBars.find(b => b.seriesId === nextBar.seriesId);
471
- const originalGap = originalNext.y - (originalCurrent.y + originalCurrent.height);
472
- currentYBelow = newY + bar.height + originalGap;
473
- }
474
- }
475
-
476
- // Apply new positions to all bars
477
- allBars = expandedBars.map(bar => {
478
- const newPos = newPositions.get(bar.seriesId);
479
- if (newPos) {
480
- return _extends({}, bar, {
481
- y: newPos.y,
482
- height: newPos.height
483
- });
484
- }
485
- return bar;
486
- });
487
-
488
- // Recalculate stack bounds after barMinSize expansion and repositioning
489
- if (allBars.length > 0) {
490
- minY = Math.min(...allBars.map(bar => bar.y));
491
- maxY = Math.max(...allBars.map(bar => bar.y + bar.height));
492
- }
493
- }
494
-
495
- // Apply border radius logic (will be reapplied after stackMinSize if needed)
496
- const applyBorderRadiusLogic = bars => {
497
- return bars.sort((a, b) => b.y - a.y).map((a, index) => {
498
- const barBefore = index > 0 ? bars[index - 1] : null;
499
- const barAfter = index < bars.length - 1 ? bars[index + 1] : null;
500
- const shouldRoundTop = index === bars.length - 1 || a.shouldApplyGap && stackGap || !a.shouldApplyGap && barAfter && barAfter.y + barAfter.height !== a.y;
501
- const shouldRoundBottom = index === 0 || a.shouldApplyGap && stackGap || !a.shouldApplyGap && barBefore && barBefore.y !== a.y + a.height;
502
- return _extends({}, a, {
503
- roundTop: Boolean(a.roundTop && shouldRoundTop),
504
- roundBottom: Boolean(a.roundBottom && shouldRoundBottom)
505
- });
506
- });
507
- };
508
- allBars = applyBorderRadiusLogic(allBars);
509
-
510
- // Calculate the bounding rect for the entire stack
511
- let stackBounds = {
512
- x,
513
- y: minY === Infinity ? baseline : minY,
514
- width,
515
- height: maxY === -Infinity ? 0 : maxY - minY
516
- };
517
-
518
- // Apply stackMinSize constraints
519
- if (stackMinSize) {
520
- if (allBars.length === 1 && stackBounds.height < stackMinSize) {
521
- var _yScale9, _yScale0, _yScale1, _yScale10;
522
- // For single bars (non-stacked), treat stackMinSize like barMinSize
523
-
524
- const bar = allBars[0];
525
- const heightIncrease = stackMinSize - bar.height;
526
- const [bottom, top] = bar.dataY.sort((a, b) => a - b);
527
-
528
- // Determine how to expand the bar (same logic as barMinSize)
529
- let newBottom = bottom;
530
- let newTop = top;
531
- const scaleUnit = Math.abs(((_yScale9 = yScale(1)) != null ? _yScale9 : 0) - ((_yScale0 = yScale(0)) != null ? _yScale0 : 0));
532
- if (bottom === 0) {
533
- // Expand away from baseline (upward for positive)
534
- newTop = top + heightIncrease / scaleUnit;
535
- } else if (top === 0) {
536
- // Expand away from baseline (downward for negative)
537
- newBottom = bottom - heightIncrease / scaleUnit;
538
- } else {
539
- // Expand in both directions
540
- const halfIncrease = heightIncrease / scaleUnit / 2;
541
- newBottom = bottom - halfIncrease;
542
- newTop = top + halfIncrease;
543
- }
544
-
545
- // Recalculate bar position with new data values
546
- const newBarBottom = (_yScale1 = yScale(newBottom)) != null ? _yScale1 : baseline;
547
- const newBarTop = (_yScale10 = yScale(newTop)) != null ? _yScale10 : baseline;
548
- const newHeight = Math.abs(newBarBottom - newBarTop);
549
- const newY = Math.min(newBarBottom, newBarTop);
550
- allBars[0] = _extends({}, bar, {
551
- height: newHeight,
552
- y: newY
553
- });
554
-
555
- // Recalculate stack bounds
556
- stackBounds = {
557
- x,
558
- y: newY,
559
- width,
560
- height: newHeight
561
- };
562
- } else if (allBars.length > 1 && stackBounds.height < stackMinSize) {
563
- // For multiple bars (stacked), scale heights while preserving gaps
564
-
565
- // Calculate total bar height (excluding gaps)
566
- const totalBarHeight = allBars.reduce((sum, bar) => sum + bar.height, 0);
567
- const totalGapHeight = stackBounds.height - totalBarHeight;
568
-
569
- // Calculate how much we need to increase bar heights
570
- const requiredBarHeight = stackMinSize - totalGapHeight;
571
- const barScaleFactor = requiredBarHeight / totalBarHeight;
572
-
573
- // Sort bars by position to maintain order
574
- const sortedBars = [...allBars].sort((a, b) => a.y - b.y);
575
-
576
- // Determine if we have bars above and below baseline
577
- const barsAboveBaseline = sortedBars.filter(bar => bar.y + bar.height <= baseline);
578
- const barsBelowBaseline = sortedBars.filter(bar => bar.y >= baseline);
579
-
580
- // Create a map of new positions
581
- const newPositions = new Map();
582
-
583
- // Start positioning from the baseline and work outward
584
- let currentYAbove = baseline; // Start at baseline, work upward (decreasing Y)
585
- let currentYBelow = baseline; // Start at baseline, work downward (increasing Y)
586
-
587
- // Position bars above baseline (positive values, decreasing Y)
588
- for (let i = barsAboveBaseline.length - 1; i >= 0; i--) {
589
- const bar = barsAboveBaseline[i];
590
- const newHeight = bar.height * barScaleFactor;
591
- const newY = currentYAbove - newHeight;
592
- newPositions.set(bar.seriesId, {
593
- y: newY,
594
- height: newHeight
595
- });
596
-
597
- // Update currentYAbove for next bar (preserve gaps)
598
- if (i > 0) {
599
- const currentBar = barsAboveBaseline[i];
600
- const nextBar = barsAboveBaseline[i - 1];
601
- const originalGap = currentBar.y - (nextBar.y + nextBar.height);
602
- currentYAbove = newY - originalGap;
603
- }
604
- }
605
-
606
- // Position bars below baseline (negative values, increasing Y)
607
- for (let i = 0; i < barsBelowBaseline.length; i++) {
608
- const bar = barsBelowBaseline[i];
609
- const newHeight = bar.height * barScaleFactor;
610
- const newY = currentYBelow;
611
- newPositions.set(bar.seriesId, {
612
- y: newY,
613
- height: newHeight
614
- });
615
-
616
- // Update currentYBelow for next bar (preserve gaps)
617
- if (i < barsBelowBaseline.length - 1) {
618
- const currentBar = barsBelowBaseline[i];
619
- const nextBar = barsBelowBaseline[i + 1];
620
- const originalGap = nextBar.y - (currentBar.y + currentBar.height);
621
- currentYBelow = newY + newHeight + originalGap;
622
- }
623
- }
624
-
625
- // Apply new positions to all bars
626
- allBars = allBars.map(bar => {
627
- const newPos = newPositions.get(bar.seriesId);
628
- if (!newPos) return bar;
629
- return _extends({}, bar, {
630
- height: newPos.height,
631
- y: newPos.y
632
- });
633
- });
634
-
635
- // Recalculate stack bounds
636
- const newMinY = Math.min(...allBars.map(bar => bar.y));
637
- const newMaxY = Math.max(...allBars.map(bar => bar.y + bar.height));
638
- stackBounds = {
639
- x,
640
- y: newMinY,
641
- width,
642
- height: newMaxY - newMinY
643
- };
644
- }
645
-
646
- // Reapply border radius logic only if we actually scaled
647
- if (stackBounds.height < stackMinSize) {
648
- allBars = applyBorderRadiusLogic(allBars);
649
- }
650
- }
109
+ const minX = Math.min(...bars.map(b => b.x));
110
+ const minY = Math.min(...bars.map(b => b.y));
111
+ const maxX = Math.max(...bars.map(b => b.x + b.width));
112
+ const maxY = Math.max(...bars.map(b => b.y + b.height));
651
113
  return {
652
- bars: allBars,
653
- stackRect: stackBounds
114
+ x: minX,
115
+ y: minY,
116
+ width: maxX - minX,
117
+ height: maxY - minY
654
118
  };
655
- }, [series, indexPos, thickness, getSeriesData, categoryIndex, roundBaseline, baseline, stackGap, barMinSize, stackMinSize, valueScale, seriesGradients, theme.color.fgPrimary, barsGrowVertically]);
656
- const categoryAxis = barsGrowVertically ? xAxis : yAxis;
657
- const categoryData = categoryAxis != null && categoryAxis.data && Array.isArray(categoryAxis.data) && typeof categoryAxis.data[0] === 'number' ? categoryAxis.data : undefined;
658
- const categoryValue = categoryData ? categoryData[categoryIndex] : categoryIndex;
119
+ }, [bars, baseline, indexPos, layout, thickness]);
120
+ const stackOrigin = useMemo(() => {
121
+ var _getStackOrigin;
122
+ return (_getStackOrigin = getStackOrigin(bars.map(b => b.origin), bars.map(b => {
123
+ var _b$minSize;
124
+ return (_b$minSize = b.minSize) != null ? _b$minSize : 0;
125
+ }))) != null ? _getStackOrigin : baseline;
126
+ }, [bars, baseline]);
659
127
  const barElements = bars.map((bar, index) => /*#__PURE__*/_jsx(Bar, {
660
- BarComponent: bar.BarComponent || defaultBarComponent,
661
- borderRadius: borderRadius,
662
- dataX: barsGrowVertically ? categoryValue : bar.dataY,
663
- dataY: barsGrowVertically ? bar.dataY : categoryValue,
128
+ BarComponent: bar.BarComponent,
129
+ borderRadius: bar.borderRadius,
130
+ dataX: bar.dataX,
131
+ dataY: bar.dataY,
664
132
  fill: bar.fill,
665
- fillOpacity: defaultFillOpacity,
133
+ fillOpacity: bar.fillOpacity,
666
134
  height: bar.height,
667
- origin: baseline,
135
+ minSize: bar.minSize,
136
+ origin: bar.origin,
668
137
  roundBottom: bar.roundBottom,
669
138
  roundTop: bar.roundTop,
670
139
  seriesId: bar.seriesId,
671
- stroke: defaultStroke,
672
- strokeWidth: defaultStrokeWidth,
140
+ stroke: bar.stroke,
141
+ strokeWidth: bar.strokeWidth,
673
142
  transition: transition,
674
143
  transitions: transitions,
675
144
  width: bar.width,
676
145
  x: bar.x,
677
146
  y: bar.y
678
147
  }, bar.seriesId + "-" + categoryIndex + "-" + index));
679
-
680
- // Check if the stack should be rounded based on baseline, across both orientations.
681
- const edge = barsGrowVertically ? stackRect.y : stackRect.x;
682
- const size = barsGrowVertically ? stackRect.height : stackRect.width;
148
+ const edge = layout === 'vertical' ? stackRect.y : stackRect.x;
149
+ const size = layout === 'vertical' ? stackRect.height : stackRect.width;
683
150
  const stackRoundLower = roundBaseline || Math.abs(edge - baseline) >= EPSILON;
684
151
  const stackRoundHigher = roundBaseline || Math.abs(edge + size - baseline) >= EPSILON;
685
- const stackRoundTop = barsGrowVertically ? stackRoundLower : stackRoundHigher;
686
- const stackRoundBottom = barsGrowVertically ? stackRoundHigher : stackRoundLower;
152
+ const stackRoundTop = layout === 'vertical' ? stackRoundLower : stackRoundHigher;
153
+ const stackRoundBottom = layout === 'vertical' ? stackRoundHigher : stackRoundLower;
687
154
  return /*#__PURE__*/_jsx(BarStackComponent, {
688
155
  borderRadius: borderRadius,
689
156
  categoryIndex: categoryIndex,
690
157
  height: stackRect.height,
158
+ origin: stackOrigin,
691
159
  roundBottom: stackRoundBottom,
692
160
  roundTop: stackRoundTop,
693
161
  transition: transition,
@@ -695,7 +163,6 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
695
163
  width: stackRect.width,
696
164
  x: stackRect.x,
697
165
  y: stackRect.y,
698
- yOrigin: baseline,
699
166
  children: barElements
700
167
  });
701
168
  });