@juspay/svelte-ui-components 2.106.2 → 2.107.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.
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
stackNormalize = false,
|
|
79
79
|
scrollable = false,
|
|
80
80
|
minBandWidth = 48,
|
|
81
|
+
marginX,
|
|
81
82
|
tooltipSnippet,
|
|
82
83
|
empty,
|
|
83
84
|
renderOverlay,
|
|
@@ -309,7 +310,19 @@
|
|
|
309
310
|
base: { top: 20, left: showYAxis ? 50 : 28, right: 28, bottom: showXAxis ? 40 : 8 }
|
|
310
311
|
});
|
|
311
312
|
});
|
|
312
|
-
let dims = $derived(
|
|
313
|
+
let dims = $derived.by(() => {
|
|
314
|
+
if (marginX == null) {
|
|
315
|
+
return layout;
|
|
316
|
+
}
|
|
317
|
+
// Fixed horizontal inset: overrides the auto layout's tick-label padding
|
|
318
|
+
// so the plot (and its edge bars) runs to the requested inset.
|
|
319
|
+
const inset = Math.max(0, marginX);
|
|
320
|
+
return {
|
|
321
|
+
...layout,
|
|
322
|
+
margin: { ...layout.margin, left: inset, right: inset },
|
|
323
|
+
innerWidth: Math.max(0, chartWidth - inset * 2)
|
|
324
|
+
};
|
|
325
|
+
});
|
|
313
326
|
|
|
314
327
|
let catScale = $derived(
|
|
315
328
|
createBandScale(labels, isVertical ? [0, dims.innerWidth] : [0, dims.innerHeight], barPadding)
|
|
@@ -610,6 +623,38 @@
|
|
|
610
623
|
}
|
|
611
624
|
}
|
|
612
625
|
|
|
626
|
+
// Grouped/single bars round only their value end (design-system bar spec):
|
|
627
|
+
// a vertical bar rounds its top (bottom when the value is negative), a
|
|
628
|
+
// horizontal bar its right (left when negative). The old all-corner rect
|
|
629
|
+
// rounding let the backdrop/track behind a bar peek through the notches at
|
|
630
|
+
// its baseline corners. Floating [low, high] range bars round both ends.
|
|
631
|
+
function valueEndBarPath(bar: BarRect): string {
|
|
632
|
+
if (barRadius <= 0) {
|
|
633
|
+
return roundedRectPath(bar.x, bar.y, bar.width, bar.height, 0, 0, 0, 0);
|
|
634
|
+
}
|
|
635
|
+
if (bar.isFloating) {
|
|
636
|
+
return roundedRectPath(
|
|
637
|
+
bar.x,
|
|
638
|
+
bar.y,
|
|
639
|
+
bar.width,
|
|
640
|
+
bar.height,
|
|
641
|
+
barRadius,
|
|
642
|
+
barRadius,
|
|
643
|
+
barRadius,
|
|
644
|
+
barRadius
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
const isNegative = (bar.dataPoint.value ?? 0) < 0;
|
|
648
|
+
if (isVertical) {
|
|
649
|
+
return isNegative
|
|
650
|
+
? roundedRectPath(bar.x, bar.y, bar.width, bar.height, 0, 0, barRadius, barRadius)
|
|
651
|
+
: roundedRectPath(bar.x, bar.y, bar.width, bar.height, barRadius, barRadius, 0, 0);
|
|
652
|
+
}
|
|
653
|
+
return isNegative
|
|
654
|
+
? roundedRectPath(bar.x, bar.y, bar.width, bar.height, barRadius, 0, 0, barRadius)
|
|
655
|
+
: roundedRectPath(bar.x, bar.y, bar.width, bar.height, 0, barRadius, barRadius, 0);
|
|
656
|
+
}
|
|
657
|
+
|
|
613
658
|
// ── Fill attribute helper ──────────────────────────────────────
|
|
614
659
|
|
|
615
660
|
function barFillAttr(bar: BarRect): string {
|
|
@@ -918,7 +963,7 @@
|
|
|
918
963
|
onclick={() => handleClick(bar)}
|
|
919
964
|
/>
|
|
920
965
|
{:else}
|
|
921
|
-
<
|
|
966
|
+
<path
|
|
922
967
|
class="bar"
|
|
923
968
|
class:hovered={hovered?.si === bar.si && hovered?.pi === bar.pi}
|
|
924
969
|
class:highlighted={effectiveHighlightedIndex !== null &&
|
|
@@ -927,12 +972,7 @@
|
|
|
927
972
|
(hovered.si !== bar.si || hovered.pi !== bar.pi)) ||
|
|
928
973
|
(effectiveHighlightedIndex !== null &&
|
|
929
974
|
effectiveHighlightedIndex !== bar.pi)}
|
|
930
|
-
|
|
931
|
-
y={bar.y}
|
|
932
|
-
width={bar.width}
|
|
933
|
-
height={bar.height}
|
|
934
|
-
rx={barRadius}
|
|
935
|
-
ry={barRadius}
|
|
975
|
+
d={valueEndBarPath(bar)}
|
|
936
976
|
fill={barFillAttr(bar)}
|
|
937
977
|
data-pw={`bar-${i}`}
|
|
938
978
|
tabindex="0"
|
|
@@ -161,6 +161,14 @@ export type OptionalBarChartProperties = {
|
|
|
161
161
|
* is `false`. Default is `48`.
|
|
162
162
|
*/
|
|
163
163
|
minBandWidth?: number;
|
|
164
|
+
/**
|
|
165
|
+
* Fixed horizontal inset (px) between the svg edges and the plot, overriding
|
|
166
|
+
* the auto-computed left/right margins. Use for edge-to-edge funnels where
|
|
167
|
+
* the auto layout's tick-label padding leaves dead space beside the first and
|
|
168
|
+
* last bars. Category tick labels near the edges may clip if they are wider
|
|
169
|
+
* than their band, so pair small values with short labels.
|
|
170
|
+
*/
|
|
171
|
+
marginX?: number;
|
|
164
172
|
tooltipSnippet?: Snippet<[BarChartDataPoint, number]>;
|
|
165
173
|
empty?: Snippet;
|
|
166
174
|
/**
|
package/dist/_chart/geometry.js
CHANGED
|
@@ -145,30 +145,49 @@ export function computeSankeyLayout(nodes, links, width, height, nodeWidth = 16,
|
|
|
145
145
|
}
|
|
146
146
|
const colWidth = maxCol === 0 ? 0 : (width - nodeWidth) / maxCol;
|
|
147
147
|
const columnPadding = new Map();
|
|
148
|
-
//
|
|
148
|
+
// Global px-per-value scale (d3-sankey's `ky`): the tightest column — least
|
|
149
|
+
// height left after node gaps — sets one scale for the whole diagram, so a
|
|
150
|
+
// given value renders the same height in every column. The previous layout
|
|
151
|
+
// stretched every column to fill the full plot height, which gave each
|
|
152
|
+
// column its own scale; link widths (sized at the source column's scale)
|
|
153
|
+
// then overflowed target nodes laid out at a smaller scale, and the ribbon
|
|
154
|
+
// stacks spilled below the bottom node row.
|
|
155
|
+
let pxPerValue = Number.POSITIVE_INFINITY;
|
|
156
|
+
for (const ids of columnGroups.values()) {
|
|
157
|
+
const totalValue = ids.reduce((s, id) => s + (nodeValues.get(id) ?? 0), 0);
|
|
158
|
+
const availableHeight = height - (ids.length - 1) * nodePadding;
|
|
159
|
+
if (totalValue > 0 && availableHeight > 0) {
|
|
160
|
+
pxPerValue = Math.min(pxPerValue, availableHeight / totalValue);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (!Number.isFinite(pxPerValue)) {
|
|
164
|
+
// All-zero data: nothing carries volume, every bar collapses to the minimum.
|
|
165
|
+
pxPerValue = 0;
|
|
166
|
+
}
|
|
167
|
+
const linkRenderWidth = (value) => Math.max(minLinkWidth, value * pxPerValue);
|
|
168
|
+
// Initialize y positions. A node must be at least as tall as its thicker
|
|
169
|
+
// side's rendered link stack: every ribbon is clamped to minLinkWidth, so a
|
|
170
|
+
// node fanning into many near-zero links would otherwise be shorter than the
|
|
171
|
+
// inflated stack attached to it.
|
|
149
172
|
const nodeY = new Map();
|
|
150
173
|
const nodeH = new Map();
|
|
151
174
|
for (const [col, ids] of columnGroups) {
|
|
152
|
-
const totalValue = ids.reduce((s, id) => s + (nodeValues.get(id) ?? 0), 0);
|
|
153
175
|
const gapCount = ids.length - 1;
|
|
154
|
-
const availableHeight = height - gapCount * nodePadding;
|
|
155
176
|
const renderedHeights = ids.map((id) => {
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
return Math.max(minLinkWidth,
|
|
177
|
+
const outStack = (outgoing.get(id) ?? []).reduce((s, link) => s + linkRenderWidth(link.value), 0);
|
|
178
|
+
const inStack = (incoming.get(id) ?? []).reduce((s, link) => s + linkRenderWidth(link.value), 0);
|
|
179
|
+
return Math.max(minLinkWidth, (nodeValues.get(id) ?? 0) * pxPerValue, outStack, inStack);
|
|
159
180
|
});
|
|
160
181
|
const sumRendered = renderedHeights.reduce((s, h) => s + h, 0);
|
|
161
182
|
const paddingBudget = gapCount > 0 ? (height - sumRendered) / gapCount : 0;
|
|
162
183
|
const effectivePadding = Math.max(0, Math.min(nodePadding, paddingBudget));
|
|
163
|
-
const scale = sumRendered > height && sumRendered > 0 ? height / sumRendered : 1;
|
|
164
184
|
columnPadding.set(col, effectivePadding);
|
|
165
185
|
let y = 0;
|
|
166
186
|
for (let index = 0; index < ids.length; index++) {
|
|
167
187
|
const id = ids[index];
|
|
168
|
-
const renderedH = renderedHeights[index] * scale;
|
|
169
188
|
nodeY.set(id, y);
|
|
170
|
-
nodeH.set(id,
|
|
171
|
-
y +=
|
|
189
|
+
nodeH.set(id, renderedHeights[index]);
|
|
190
|
+
y += renderedHeights[index] + effectivePadding;
|
|
172
191
|
}
|
|
173
192
|
}
|
|
174
193
|
// Iterative relaxation (upstream pass)
|
|
@@ -245,11 +264,11 @@ export function computeSankeyLayout(nodes, links, width, height, nodeWidth = 16,
|
|
|
245
264
|
}));
|
|
246
265
|
const nodeById = new Map(computedNodes.map((n) => [n.id, n]));
|
|
247
266
|
const linkKey = (l) => `${l.source}${l.target}`;
|
|
267
|
+
// Link widths use the same global scale as node heights, so each node's link
|
|
268
|
+
// stack fills its bar exactly and never runs past its bottom edge.
|
|
248
269
|
const linkWidths = new Map();
|
|
249
270
|
for (const l of links) {
|
|
250
|
-
|
|
251
|
-
const sourceH = nodeH.get(l.source) ?? 0;
|
|
252
|
-
linkWidths.set(linkKey(l), Math.max(minLinkWidth, (l.value / Math.max(sVal, 1)) * sourceH));
|
|
271
|
+
linkWidths.set(linkKey(l), linkRenderWidth(l.value));
|
|
253
272
|
}
|
|
254
273
|
const linkSy = new Map();
|
|
255
274
|
const linkTy = new Map();
|