@juspay/svelte-ui-components 2.104.0 → 2.106.0
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.
|
@@ -37,7 +37,9 @@
|
|
|
37
37
|
minLinkWidth = 1,
|
|
38
38
|
dataLabelOffsetX = 0,
|
|
39
39
|
disableDimOnHover = false,
|
|
40
|
-
firstColumnLabelSide = 'left'
|
|
40
|
+
firstColumnLabelSide = 'left',
|
|
41
|
+
lastColumnLabelSide = 'right',
|
|
42
|
+
marginX = 40
|
|
41
43
|
}: SankeyChartProperties = $props();
|
|
42
44
|
|
|
43
45
|
// ── State ──────────────────────────────────────────────────────
|
|
@@ -99,8 +101,11 @@
|
|
|
99
101
|
// used to run past the svg edge and clip. Reserve a capped gutter sized from the
|
|
100
102
|
// sink-node labels (sinks are what land in the final column) and lay the diagram
|
|
101
103
|
// out in the remaining width instead.
|
|
104
|
+
// With `lastColumnLabelSide: 'left'` sink labels render inside the plot over
|
|
105
|
+
// the incoming ribbons (mirroring the first column's 'right' mode), so no
|
|
106
|
+
// right gutter is reserved and the diagram runs to the right edge.
|
|
102
107
|
let lastColumnLabelGutter = $derived.by(() => {
|
|
103
|
-
if (!showLabels || nodes.length === 0 || chartWidth <= 0) {
|
|
108
|
+
if (lastColumnLabelSide === 'left' || !showLabels || nodes.length === 0 || chartWidth <= 0) {
|
|
104
109
|
return 0;
|
|
105
110
|
}
|
|
106
111
|
const sourceIds = new Set(links.map((link) => link.source));
|
|
@@ -150,14 +155,15 @@
|
|
|
150
155
|
});
|
|
151
156
|
|
|
152
157
|
let plotWidth = $derived(
|
|
153
|
-
Math.max(0, chartWidth -
|
|
158
|
+
Math.max(0, chartWidth - marginX * 2 - firstColumnLabelGutter - lastColumnLabelGutter)
|
|
154
159
|
);
|
|
160
|
+
let plotHeight = $derived(Math.max(0, chartHeight - MARGIN * 2));
|
|
155
161
|
let layout = $derived(
|
|
156
162
|
computeSankeyLayout(
|
|
157
163
|
nodes,
|
|
158
164
|
links,
|
|
159
165
|
plotWidth,
|
|
160
|
-
|
|
166
|
+
plotHeight,
|
|
161
167
|
nodeWidth,
|
|
162
168
|
nodePadding,
|
|
163
169
|
iterations,
|
|
@@ -202,6 +208,29 @@
|
|
|
202
208
|
return truncateToWidth(text, Math.max(0, colWidth - 6), colLabelFont);
|
|
203
209
|
};
|
|
204
210
|
|
|
211
|
+
// Column headers centre on their column's bar; at tight horizontal margins an
|
|
212
|
+
// edge header (e.g. a long last-column title) would otherwise paint past the
|
|
213
|
+
// svg boundary. Clamp each header's centre so its rendered text stays on the
|
|
214
|
+
// canvas — a no-op at the default margins.
|
|
215
|
+
const columnLabelX = (columnIndex: number, label: string): number => {
|
|
216
|
+
const center = columnIndex * colWidth + nodeWidth / 2;
|
|
217
|
+
const textWidth = measureText(truncateColumnLabel(label), colLabelFont).width;
|
|
218
|
+
const canvasLeft = -(marginX + firstColumnLabelGutter) + 2;
|
|
219
|
+
const canvasRight = plotWidth + lastColumnLabelGutter + marginX - 2;
|
|
220
|
+
const min = canvasLeft + textWidth / 2;
|
|
221
|
+
const max = canvasRight - textWidth / 2;
|
|
222
|
+
// Text wider than the whole canvas keeps its centre; the colWidth-based
|
|
223
|
+
// truncation budget makes that unreachable in practice.
|
|
224
|
+
return max < min ? center : Math.min(Math.max(center, min), max);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// Which side of its bar a node's label renders on. First and last columns are
|
|
228
|
+
// configurable (`firstColumnLabelSide` / `lastColumnLabelSide`); in a
|
|
229
|
+
// single-column chart the first-column setting wins.
|
|
230
|
+
const labelOnLeft = (column: number): boolean =>
|
|
231
|
+
(column === 0 && firstColumnLabelSide === 'left') ||
|
|
232
|
+
(column === columnCount - 1 && column !== 0 && lastColumnLabelSide === 'left');
|
|
233
|
+
|
|
205
234
|
const truncateLabel = (text: string, column: number): string => {
|
|
206
235
|
// Middle columns must budget for dataLabelOffsetX too: the label starts at
|
|
207
236
|
// node.x + nodeWidth + 6 + dataLabelOffsetX, so the room before the next
|
|
@@ -213,9 +242,9 @@
|
|
|
213
242
|
// symmetric with the last column's sink-gutter budget below.
|
|
214
243
|
const available =
|
|
215
244
|
column === 0 && firstColumnLabelSide === 'left'
|
|
216
|
-
? Math.max(0, firstColumnLabelGutter +
|
|
217
|
-
: column === columnCount - 1
|
|
218
|
-
? Math.max(0, lastColumnLabelGutter +
|
|
245
|
+
? Math.max(0, firstColumnLabelGutter + marginX - 6 - dataLabelOffsetX)
|
|
246
|
+
: column === columnCount - 1 && lastColumnLabelSide === 'right'
|
|
247
|
+
? Math.max(0, lastColumnLabelGutter + marginX - 6 - dataLabelOffsetX)
|
|
219
248
|
: Math.max(0, colWidth - nodeWidth - 12 - dataLabelOffsetX);
|
|
220
249
|
// No usable room — hide the label rather than force text that would overflow;
|
|
221
250
|
// the full text is still reachable via the node's <title> on hover.
|
|
@@ -453,12 +482,16 @@
|
|
|
453
482
|
<div class="chart-empty">{@render empty()}</div>
|
|
454
483
|
{:else}
|
|
455
484
|
<ChartContainer bind:width={chartWidth} bind:height={chartHeight} {aspectRatio} {maxHeight}>
|
|
456
|
-
<g transform="translate({
|
|
485
|
+
<g transform="translate({marginX + firstColumnLabelGutter}, {MARGIN})">
|
|
486
|
+
<!-- Optional backdrop panel behind the diagram (--sankey-plot-background,
|
|
487
|
+
transparent by default). Spans exactly the plot: first column's bars
|
|
488
|
+
to last column's bars, header row excluded. -->
|
|
489
|
+
<rect class="sankey-plot-bg" x={0} y={0} width={plotWidth} height={plotHeight} />
|
|
457
490
|
{#if columnLabels != null && columnLabels.length > 0}
|
|
458
491
|
{#each columnLabels.slice(0, columnCount) as label, ci (ci)}
|
|
459
492
|
<text
|
|
460
493
|
class="sankey-col-label"
|
|
461
|
-
x={ci
|
|
494
|
+
x={columnLabelX(ci, label)}
|
|
462
495
|
y={-8}
|
|
463
496
|
text-anchor="middle"
|
|
464
497
|
dominant-baseline="auto">{truncateColumnLabel(label)}<title>{label}</title></text
|
|
@@ -518,11 +551,11 @@
|
|
|
518
551
|
<text
|
|
519
552
|
class="sankey-label"
|
|
520
553
|
class:node-dimmed={dimmed}
|
|
521
|
-
x={node.column
|
|
554
|
+
x={labelOnLeft(node.column)
|
|
522
555
|
? node.x - 6 - dataLabelOffsetX
|
|
523
556
|
: node.x + node.width + 6 + dataLabelOffsetX}
|
|
524
557
|
y={node.y + node.height / 2}
|
|
525
|
-
text-anchor={node.column
|
|
558
|
+
text-anchor={labelOnLeft(node.column) ? 'end' : 'start'}
|
|
526
559
|
dominant-baseline="middle"
|
|
527
560
|
>{truncateLabel(
|
|
528
561
|
showValues ? `${node.label} (${format(node.value)})` : node.label,
|
|
@@ -551,6 +584,10 @@
|
|
|
551
584
|
width: 100%;
|
|
552
585
|
position: relative;
|
|
553
586
|
}
|
|
587
|
+
.sankey-plot-bg {
|
|
588
|
+
fill: var(--sankey-plot-background, transparent);
|
|
589
|
+
pointer-events: none;
|
|
590
|
+
}
|
|
554
591
|
.sankey-link {
|
|
555
592
|
transition: stroke-opacity var(--chart-transition-duration, 0.2s) ease;
|
|
556
593
|
cursor: pointer;
|
|
@@ -92,6 +92,21 @@ export type OptionalSankeyChartProperties = {
|
|
|
92
92
|
* diagram gains that width.
|
|
93
93
|
*/
|
|
94
94
|
firstColumnLabelSide?: 'left' | 'right';
|
|
95
|
+
/**
|
|
96
|
+
* Which side of a last-column (sink) node its label renders on. `'right'` (default)
|
|
97
|
+
* anchors the label into a reserved right gutter outside the diagram. `'left'` renders it
|
|
98
|
+
* inside the plot — left of the bar, over the incoming ribbons (pair with the
|
|
99
|
+
* `--sankey-label-halo-*` tokens); no right gutter is reserved, so the diagram runs to the
|
|
100
|
+
* right edge. In a single-column chart `firstColumnLabelSide` wins.
|
|
101
|
+
*/
|
|
102
|
+
lastColumnLabelSide?: 'left' | 'right';
|
|
103
|
+
/**
|
|
104
|
+
* Horizontal inset in pixels between the svg edges and the diagram (plus any label
|
|
105
|
+
* gutters). Defaults to `40`, matching the fixed vertical margin. Lower it for
|
|
106
|
+
* edge-to-edge funnels when both first/last column labels render inside the plot;
|
|
107
|
+
* column headers clamp to the canvas so they never paint past the svg.
|
|
108
|
+
*/
|
|
109
|
+
marginX?: number;
|
|
95
110
|
};
|
|
96
111
|
export type SankeyChartEventProperties = {
|
|
97
112
|
onnodeclick?: (event: {
|