@juspay/svelte-ui-components 2.70.0 → 2.72.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.
- package/dist/AreaChart/AreaChart.svelte +80 -6
- package/dist/AreaChart/properties.d.ts +1 -0
- package/dist/DateRangePicker/DateRangePicker.svelte +16 -0
- package/dist/DateRangePicker/properties.d.ts +7 -0
- package/dist/LineChart/LineChart.svelte +82 -3
- package/dist/LineChart/properties.d.ts +2 -0
- package/dist/_chart/paths.js +1 -0
- package/dist/_chart/types.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { AreaChartProperties, AreaChartTooltipContext } from './properties';
|
|
3
|
+
import { onMount } from 'svelte';
|
|
3
4
|
import ChartContainer from '../_chart/ChartContainer.svelte';
|
|
4
5
|
import Axis from '../_chart/Axis.svelte';
|
|
5
6
|
import ChartTooltip from '../_chart/ChartTooltip.svelte';
|
|
@@ -8,17 +9,24 @@
|
|
|
8
9
|
import { computeChartDimensions, computeStackedValues } from '../_chart/geometry';
|
|
9
10
|
import { linePath, areaPath } from '../_chart/paths';
|
|
10
11
|
import { getColor } from '../_chart/colors';
|
|
11
|
-
import { formatNumber } from '../_chart/format';
|
|
12
|
+
import { formatNumber, formatPercent } from '../_chart/format';
|
|
12
13
|
import type { LegendItem, Point } from '../_chart/types';
|
|
13
14
|
|
|
14
15
|
// ── Props ──────────────────────────────────────────────────────
|
|
15
16
|
|
|
17
|
+
// Per-instance ID for SVG gradient <linearGradient id> references. Initialised
|
|
18
|
+
// inside onMount so the value is only ever generated on the client — avoids an
|
|
19
|
+
// SSR hydration mismatch that would occur if Math.random() ran on both server
|
|
20
|
+
// and client and produced different strings.
|
|
21
|
+
let uid = $state('');
|
|
22
|
+
|
|
16
23
|
let {
|
|
17
24
|
series,
|
|
18
25
|
curve = 'monotone',
|
|
19
26
|
stacked = false,
|
|
20
27
|
stackNormalize = false,
|
|
21
28
|
fillOpacity = 0.3,
|
|
29
|
+
gradientFill = false,
|
|
22
30
|
showDots = false,
|
|
23
31
|
showLine = true,
|
|
24
32
|
showValues = false,
|
|
@@ -51,6 +59,10 @@
|
|
|
51
59
|
let mouseX = $state(0);
|
|
52
60
|
let mouseY = $state(0);
|
|
53
61
|
|
|
62
|
+
onMount(() => {
|
|
63
|
+
uid = Math.random().toString(36).slice(2, 9);
|
|
64
|
+
});
|
|
65
|
+
|
|
54
66
|
// ── Layout ─────────────────────────────────────────────────────
|
|
55
67
|
|
|
56
68
|
let dims = $derived(computeChartDimensions(chartWidth, chartHeight));
|
|
@@ -169,8 +181,23 @@
|
|
|
169
181
|
if (tooltipContext === null) {
|
|
170
182
|
return null;
|
|
171
183
|
}
|
|
184
|
+
const title = xTickFormat
|
|
185
|
+
? xTickFormat(tooltipContext.x)
|
|
186
|
+
: `x: ${formatNumber(tooltipContext.x)}`;
|
|
187
|
+
if (stackNormalize) {
|
|
188
|
+
const pi = hovered?.pi ?? 0;
|
|
189
|
+
const columnTotal = series.reduce((sum, s) => sum + Math.max(0, s.data[pi]?.y ?? 0), 0);
|
|
190
|
+
return {
|
|
191
|
+
title,
|
|
192
|
+
items: tooltipContext.points.map((p) => ({
|
|
193
|
+
label: p.name,
|
|
194
|
+
value: formatPercent(Math.max(0, p.y), columnTotal),
|
|
195
|
+
color: p.color
|
|
196
|
+
}))
|
|
197
|
+
};
|
|
198
|
+
}
|
|
172
199
|
return {
|
|
173
|
-
title
|
|
200
|
+
title,
|
|
174
201
|
items: tooltipContext.points.map((p) => ({
|
|
175
202
|
label: p.name,
|
|
176
203
|
value: formatNumber(p.y),
|
|
@@ -273,6 +300,37 @@
|
|
|
273
300
|
{/if}
|
|
274
301
|
|
|
275
302
|
<ChartContainer bind:width={chartWidth} bind:height={chartHeight} {aspectRatio}>
|
|
303
|
+
{#if gradientFill}
|
|
304
|
+
<!-- <defs> must be a direct child of <svg> (SVG root), not inside a transformed <g>.
|
|
305
|
+
gradientUnits="userSpaceOnUse" with y1/y2 in the inner coordinate space (0..innerHeight)
|
|
306
|
+
correctly spans the full chart height regardless of how thin each band is. -->
|
|
307
|
+
<defs>
|
|
308
|
+
{#each areas as area, si (si)}
|
|
309
|
+
<linearGradient
|
|
310
|
+
id="area-grad-{uid}-{si}"
|
|
311
|
+
x1="0"
|
|
312
|
+
y1="0"
|
|
313
|
+
x2="0"
|
|
314
|
+
y2={dims.innerHeight}
|
|
315
|
+
gradientUnits="userSpaceOnUse"
|
|
316
|
+
>
|
|
317
|
+
<!-- The gradient top stop is fillOpacity + 0.3 (clamped to 1), giving a richer
|
|
318
|
+
anchor at the top that fades to transparent at the bottom. This intentionally
|
|
319
|
+
exceeds the base fillOpacity so that gradient-fill areas appear more vivid
|
|
320
|
+
than their solid-fill counterparts (where fill-opacity equals fillOpacity). -->
|
|
321
|
+
<stop
|
|
322
|
+
offset="0%"
|
|
323
|
+
stop-color={area.color}
|
|
324
|
+
stop-opacity={Math.min(
|
|
325
|
+
(hovered?.si === si ? fillOpacity + 0.2 : fillOpacity) + 0.3,
|
|
326
|
+
1
|
|
327
|
+
)}
|
|
328
|
+
/>
|
|
329
|
+
<stop offset="100%" stop-color={area.color} stop-opacity={0} />
|
|
330
|
+
</linearGradient>
|
|
331
|
+
{/each}
|
|
332
|
+
</defs>
|
|
333
|
+
{/if}
|
|
276
334
|
<g transform="translate({dims.margin.left}, {dims.margin.top})">
|
|
277
335
|
{#if showYAxis}
|
|
278
336
|
<Axis
|
|
@@ -295,8 +353,8 @@
|
|
|
295
353
|
class="area-fill"
|
|
296
354
|
class:dimmed={hovered !== null && hovered.si !== si}
|
|
297
355
|
d={area.areaD}
|
|
298
|
-
fill={area.color}
|
|
299
|
-
fill-opacity={hovered?.si === si ? fillOpacity + 0.2 : fillOpacity}
|
|
356
|
+
fill={gradientFill ? `url(#area-grad-${uid}-${si})` : area.color}
|
|
357
|
+
fill-opacity={gradientFill ? 1 : hovered?.si === si ? fillOpacity + 0.2 : fillOpacity}
|
|
300
358
|
/>
|
|
301
359
|
{#if showLine}
|
|
302
360
|
<path
|
|
@@ -308,6 +366,16 @@
|
|
|
308
366
|
fill="none"
|
|
309
367
|
/>
|
|
310
368
|
{/if}
|
|
369
|
+
{#if area.points.length === 1 && !showDots}
|
|
370
|
+
<circle
|
|
371
|
+
class="single-point"
|
|
372
|
+
class:dimmed={hovered !== null && hovered.si !== si}
|
|
373
|
+
cx={area.points[0].x}
|
|
374
|
+
cy={area.points[0].y}
|
|
375
|
+
r={6}
|
|
376
|
+
fill={area.color}
|
|
377
|
+
/>
|
|
378
|
+
{/if}
|
|
311
379
|
{#if showDots}
|
|
312
380
|
{#each area.points as point, pi (pi)}
|
|
313
381
|
<circle
|
|
@@ -385,6 +453,12 @@
|
|
|
385
453
|
.area-line.dimmed {
|
|
386
454
|
opacity: var(--areachart-dimmed-opacity, 0.1);
|
|
387
455
|
}
|
|
456
|
+
.single-point {
|
|
457
|
+
pointer-events: none;
|
|
458
|
+
}
|
|
459
|
+
.single-point.dimmed {
|
|
460
|
+
opacity: var(--areachart-dimmed-opacity, 0.1);
|
|
461
|
+
}
|
|
388
462
|
.dot {
|
|
389
463
|
transition:
|
|
390
464
|
r var(--chart-transition-duration, 0.2s) ease,
|
|
@@ -400,9 +474,9 @@
|
|
|
400
474
|
pointer-events: none;
|
|
401
475
|
}
|
|
402
476
|
.hover-line {
|
|
403
|
-
stroke: var(--linechart-hover-line-color, #ccc);
|
|
477
|
+
stroke: var(--areachart-hover-line-color, var(--linechart-hover-line-color, #ccc));
|
|
404
478
|
stroke-width: 1;
|
|
405
|
-
stroke-dasharray: 4 4;
|
|
479
|
+
stroke-dasharray: var(--areachart-hover-line-dash, var(--linechart-hover-line-dash, 4 4));
|
|
406
480
|
pointer-events: none;
|
|
407
481
|
}
|
|
408
482
|
.hover-overlay {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
presetCheckmark = false,
|
|
31
31
|
showDateInputs = false,
|
|
32
32
|
showTimeSelection = false,
|
|
33
|
+
presetToggle = false,
|
|
33
34
|
placeholder = 'Select date',
|
|
34
35
|
dualMonth,
|
|
35
36
|
timePicker,
|
|
@@ -388,6 +389,21 @@
|
|
|
388
389
|
}
|
|
389
390
|
|
|
390
391
|
function handlePreset(preset: DateRangePreset): void {
|
|
392
|
+
// presetToggle: clicking the already-selected preset deselects it and reverts the
|
|
393
|
+
// draft to the committed selection (mirrors openPicker). Lets a toggle-style preset
|
|
394
|
+
// such as "No Comparison" be switched back off without having to pick a calendar
|
|
395
|
+
// date, instead of being a one-way radio choice.
|
|
396
|
+
if (presetToggle && selectedPresetLabel === preset.label) {
|
|
397
|
+
draftStart = rangeStart;
|
|
398
|
+
draftEnd = rangeEnd;
|
|
399
|
+
draftValue = value;
|
|
400
|
+
selectedPresetLabel = committedPresetLabel;
|
|
401
|
+
const anchor = rangeStart !== null ? rangeStart : value !== null ? value : now;
|
|
402
|
+
leftYear = anchor.getFullYear();
|
|
403
|
+
leftMonth = anchor.getMonth();
|
|
404
|
+
calendarKey++;
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
391
407
|
// User explicitly picked a preset — clear the initial-preset display label
|
|
392
408
|
activePresetLabel = null;
|
|
393
409
|
const { start, end } = preset.getValue();
|
|
@@ -52,6 +52,13 @@ export type OptionalDateRangePickerProperties = {
|
|
|
52
52
|
* Opt-in. Default: false.
|
|
53
53
|
*/
|
|
54
54
|
showTimeSelection?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Make presets toggle instead of being one-way: clicking the already-selected
|
|
57
|
+
* preset deselects it and reverts the draft to the committed selection. Lets a
|
|
58
|
+
* toggle-style preset such as "No Comparison" be switched back off without having
|
|
59
|
+
* to pick a calendar date. Opt-in. Default: false.
|
|
60
|
+
*/
|
|
61
|
+
presetToggle?: boolean;
|
|
55
62
|
/** Placeholder shown when no range is selected. */
|
|
56
63
|
placeholder?: string;
|
|
57
64
|
/** Show two months side by side. Defaults to true for range mode, false for single. */
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { LineChartProperties, LineChartTooltipContext } from './properties';
|
|
3
|
+
import { onMount } from 'svelte';
|
|
3
4
|
import ChartContainer from '../_chart/ChartContainer.svelte';
|
|
4
5
|
import Axis from '../_chart/Axis.svelte';
|
|
5
6
|
import ChartTooltip from '../_chart/ChartTooltip.svelte';
|
|
6
7
|
import Legend from '../_chart/Legend.svelte';
|
|
7
8
|
import { createLinearScale, niceLinearDomain } from '../_chart/scales';
|
|
8
9
|
import { computeChartDimensions } from '../_chart/geometry';
|
|
9
|
-
import { linePath } from '../_chart/paths';
|
|
10
|
+
import { linePath, areaPath } from '../_chart/paths';
|
|
10
11
|
import { getColor } from '../_chart/colors';
|
|
11
12
|
import { formatNumber } from '../_chart/format';
|
|
12
13
|
import type { LegendItem, Point } from '../_chart/types';
|
|
13
14
|
|
|
15
|
+
// Per-instance ID for SVG gradient <linearGradient id> references. Initialised
|
|
16
|
+
// inside onMount so the value is only ever generated on the client — avoids an
|
|
17
|
+
// SSR hydration mismatch that would occur if Math.random() ran on both server
|
|
18
|
+
// and client and produced different strings.
|
|
19
|
+
let uid = $state('');
|
|
20
|
+
|
|
14
21
|
// ── Props ──────────────────────────────────────────────────────
|
|
15
22
|
|
|
16
23
|
let {
|
|
17
24
|
series,
|
|
18
25
|
curve = 'monotone',
|
|
26
|
+
gradientFill = false,
|
|
27
|
+
fillOpacity = 0.3,
|
|
19
28
|
showDots = true,
|
|
20
29
|
showValues = false,
|
|
21
30
|
dotRadius = 4,
|
|
@@ -48,6 +57,10 @@
|
|
|
48
57
|
let mouseX = $state(0);
|
|
49
58
|
let mouseY = $state(0);
|
|
50
59
|
|
|
60
|
+
onMount(() => {
|
|
61
|
+
uid = Math.random().toString(36).slice(2, 9);
|
|
62
|
+
});
|
|
63
|
+
|
|
51
64
|
// ── Layout ─────────────────────────────────────────────────────
|
|
52
65
|
|
|
53
66
|
let dims = $derived(computeChartDimensions(chartWidth, chartHeight));
|
|
@@ -82,7 +95,12 @@
|
|
|
82
95
|
series.map((s, si) => {
|
|
83
96
|
const color = s.color ?? getColor(si);
|
|
84
97
|
const points: Point[] = s.data.map((d) => ({ x: xScale(d.x), y: yScale(d.y) }));
|
|
85
|
-
return {
|
|
98
|
+
return {
|
|
99
|
+
color,
|
|
100
|
+
points,
|
|
101
|
+
path: linePath(points, curve),
|
|
102
|
+
areaD: areaPath(points, dims.innerHeight, curve)
|
|
103
|
+
};
|
|
86
104
|
})
|
|
87
105
|
);
|
|
88
106
|
|
|
@@ -123,7 +141,7 @@
|
|
|
123
141
|
return null;
|
|
124
142
|
}
|
|
125
143
|
return {
|
|
126
|
-
title: `x: ${formatNumber(tooltipContext.x)}`,
|
|
144
|
+
title: xTickFormat ? xTickFormat(tooltipContext.x) : `x: ${formatNumber(tooltipContext.x)}`,
|
|
127
145
|
items: tooltipContext.points.map((p) => ({
|
|
128
146
|
label: p.name,
|
|
129
147
|
value: formatNumber(p.y),
|
|
@@ -227,6 +245,34 @@
|
|
|
227
245
|
{/if}
|
|
228
246
|
|
|
229
247
|
<ChartContainer bind:width={chartWidth} bind:height={chartHeight} {aspectRatio}>
|
|
248
|
+
{#if gradientFill}
|
|
249
|
+
<defs>
|
|
250
|
+
{#each lines as line, si (si)}
|
|
251
|
+
<linearGradient
|
|
252
|
+
id="line-grad-{uid}-{si}"
|
|
253
|
+
x1="0"
|
|
254
|
+
y1="0"
|
|
255
|
+
x2="0"
|
|
256
|
+
y2={dims.innerHeight}
|
|
257
|
+
gradientUnits="userSpaceOnUse"
|
|
258
|
+
>
|
|
259
|
+
<!-- The gradient top stop is fillOpacity + 0.3 (clamped to 1), giving a richer
|
|
260
|
+
anchor at the top that fades to transparent at the bottom. This intentionally
|
|
261
|
+
exceeds the base fillOpacity so that gradient-fill areas appear more vivid
|
|
262
|
+
than a flat solid-fill at fillOpacity alone. -->
|
|
263
|
+
<stop
|
|
264
|
+
offset="0%"
|
|
265
|
+
stop-color={line.color}
|
|
266
|
+
stop-opacity={Math.min(
|
|
267
|
+
(hovered?.si === si ? fillOpacity + 0.2 : fillOpacity) + 0.3,
|
|
268
|
+
1
|
|
269
|
+
)}
|
|
270
|
+
/>
|
|
271
|
+
<stop offset="100%" stop-color={line.color} stop-opacity={0} />
|
|
272
|
+
</linearGradient>
|
|
273
|
+
{/each}
|
|
274
|
+
</defs>
|
|
275
|
+
{/if}
|
|
230
276
|
<g transform="translate({dims.margin.left}, {dims.margin.top})">
|
|
231
277
|
{#if showYAxis}
|
|
232
278
|
<Axis
|
|
@@ -245,6 +291,14 @@
|
|
|
245
291
|
{/if}
|
|
246
292
|
|
|
247
293
|
{#each lines as line, si (si)}
|
|
294
|
+
{#if gradientFill}
|
|
295
|
+
<path
|
|
296
|
+
class="line-area-fill"
|
|
297
|
+
class:dimmed={hovered !== null && hovered.si !== si}
|
|
298
|
+
d={line.areaD}
|
|
299
|
+
fill="url(#line-grad-{uid}-{si})"
|
|
300
|
+
/>
|
|
301
|
+
{/if}
|
|
248
302
|
<path
|
|
249
303
|
class="line-path"
|
|
250
304
|
class:dimmed={hovered !== null && hovered.si !== si}
|
|
@@ -253,6 +307,16 @@
|
|
|
253
307
|
stroke-width={strokeWidth}
|
|
254
308
|
fill="none"
|
|
255
309
|
/>
|
|
310
|
+
{#if line.points.length === 1 && !showDots}
|
|
311
|
+
<circle
|
|
312
|
+
class="single-point"
|
|
313
|
+
class:dimmed={hovered !== null && hovered.si !== si}
|
|
314
|
+
cx={line.points[0].x}
|
|
315
|
+
cy={line.points[0].y}
|
|
316
|
+
r={dotRadius * 1.5}
|
|
317
|
+
fill={line.color}
|
|
318
|
+
/>
|
|
319
|
+
{/if}
|
|
256
320
|
{#if showDots}
|
|
257
321
|
{#each line.points as point, pi (pi)}
|
|
258
322
|
<circle
|
|
@@ -313,6 +377,15 @@
|
|
|
313
377
|
width: 100%;
|
|
314
378
|
position: relative;
|
|
315
379
|
}
|
|
380
|
+
.line-area-fill {
|
|
381
|
+
transition:
|
|
382
|
+
fill-opacity var(--chart-transition-duration, 0.2s) ease,
|
|
383
|
+
opacity var(--chart-transition-duration, 0.2s) ease;
|
|
384
|
+
pointer-events: none;
|
|
385
|
+
}
|
|
386
|
+
.line-area-fill.dimmed {
|
|
387
|
+
opacity: var(--linechart-dimmed-opacity, 0.2);
|
|
388
|
+
}
|
|
316
389
|
.line-path {
|
|
317
390
|
transition: opacity var(--chart-transition-duration, 0.2s) ease;
|
|
318
391
|
stroke-linecap: round;
|
|
@@ -322,6 +395,12 @@
|
|
|
322
395
|
.line-path.dimmed {
|
|
323
396
|
opacity: var(--linechart-dimmed-opacity, 0.2);
|
|
324
397
|
}
|
|
398
|
+
.single-point {
|
|
399
|
+
pointer-events: none;
|
|
400
|
+
}
|
|
401
|
+
.single-point.dimmed {
|
|
402
|
+
opacity: var(--linechart-dimmed-opacity, 0.2);
|
|
403
|
+
}
|
|
325
404
|
.dot {
|
|
326
405
|
transition:
|
|
327
406
|
r var(--chart-transition-duration, 0.2s) ease,
|
package/dist/_chart/paths.js
CHANGED
package/dist/_chart/types.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export type BandScale = {
|
|
|
31
31
|
bandwidth: number;
|
|
32
32
|
step: number;
|
|
33
33
|
};
|
|
34
|
-
export type CurveType = 'linear' | 'monotone' | 'step' | 'natural';
|
|
34
|
+
export type CurveType = 'linear' | 'monotone' | 'spline' | 'step' | 'natural';
|
|
35
35
|
export type AxisOrientation = 'top' | 'right' | 'bottom' | 'left';
|
|
36
36
|
export type PieSliceLayout = {
|
|
37
37
|
index: number;
|