@juspay/svelte-ui-components 2.86.0 → 2.87.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 +229 -131
- package/dist/AreaChart/properties.d.ts +6 -0
- package/dist/BarChart/BarChart.svelte +421 -288
- package/dist/BarChart/properties.d.ts +6 -0
- package/dist/DualAxisBarChart/DualAxisBarChart.svelte +339 -222
- package/dist/DualAxisBarChart/properties.d.ts +4 -0
- package/dist/FunnelChart/FunnelChart.svelte +182 -83
- package/dist/FunnelChart/properties.d.ts +2 -0
- package/dist/LineChart/LineChart.svelte +396 -210
- package/dist/LineChart/properties.d.ts +9 -0
- package/dist/_chart/Axis.svelte +32 -15
- package/dist/_chart/ChartTooltip.svelte +127 -49
- package/dist/_chart/Legend.svelte +36 -6
- package/dist/_chart/geometry.d.ts +28 -18
- package/dist/_chart/geometry.js +47 -42
- package/dist/_chart/interactions.d.ts +20 -0
- package/dist/_chart/interactions.js +34 -0
- package/dist/_chart/labels.d.ts +76 -0
- package/dist/_chart/labels.js +213 -0
- package/dist/_chart/measure.d.ts +12 -0
- package/dist/_chart/measure.js +46 -0
- package/dist/_chart/scales.d.ts +1 -1
- package/dist/_chart/scales.js +7 -2
- package/dist/_chart/tooltipPosition.d.ts +22 -0
- package/dist/_chart/tooltipPosition.js +49 -0
- package/dist/_chart/types.d.ts +29 -1
- package/package.json +1 -1
|
@@ -7,12 +7,16 @@
|
|
|
7
7
|
import Axis from '../_chart/Axis.svelte';
|
|
8
8
|
import ChartTooltip from '../_chart/ChartTooltip.svelte';
|
|
9
9
|
import Legend from '../_chart/Legend.svelte';
|
|
10
|
-
import { createLinearScale, niceLinearDomain } from '../_chart/scales';
|
|
11
|
-
import {
|
|
10
|
+
import { createLinearScale, niceLinearDomain, computeLinearTicks } from '../_chart/scales';
|
|
11
|
+
import { computeAutoLayout } from '../_chart/geometry';
|
|
12
12
|
import { linePath, areaPath } from '../_chart/paths';
|
|
13
13
|
import { getColor } from '../_chart/colors';
|
|
14
|
-
import { formatNumber } from '../_chart/format';
|
|
15
|
-
import
|
|
14
|
+
import { formatNumber, defaultTickFormat } from '../_chart/format';
|
|
15
|
+
import { measureText } from '../_chart/measure';
|
|
16
|
+
import { resolvePointLabels } from '../_chart/labels';
|
|
17
|
+
import type { LegendItem, Point, TooltipAnchor } from '../_chart/types';
|
|
18
|
+
import { pointerPositionIn, dismissOnOutsidePointerDown } from '../_chart/interactions';
|
|
19
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
16
20
|
|
|
17
21
|
// Per-instance ID for SVG gradient <linearGradient id> references. Initialised
|
|
18
22
|
// inside onMount so the value is only ever generated on the client — avoids an
|
|
@@ -49,6 +53,10 @@
|
|
|
49
53
|
maxHeight = DEFAULT_CHART_MAX_HEIGHT,
|
|
50
54
|
tooltipSnippet,
|
|
51
55
|
empty,
|
|
56
|
+
sharedTooltip,
|
|
57
|
+
interactiveLegend = false,
|
|
58
|
+
hideLegendBelow = 360,
|
|
59
|
+
tooltipPortal = false,
|
|
52
60
|
highlightedIndex = null,
|
|
53
61
|
onChartReady,
|
|
54
62
|
onpointclick,
|
|
@@ -60,6 +68,7 @@
|
|
|
60
68
|
// ── State ──────────────────────────────────────────────────────
|
|
61
69
|
|
|
62
70
|
let containerEl: HTMLDivElement | null = $state(null);
|
|
71
|
+
let plotEl: HTMLDivElement | null = $state(null);
|
|
63
72
|
let chartWidth = $state(0);
|
|
64
73
|
let chartHeight = $state(0);
|
|
65
74
|
let hovered = $state<{ si: number; pi: number } | null>(null);
|
|
@@ -68,6 +77,19 @@
|
|
|
68
77
|
let internalHighlight = $state<number | null>(null);
|
|
69
78
|
let mouseX = $state(0);
|
|
70
79
|
let mouseY = $state(0);
|
|
80
|
+
const hiddenSeries = new SvelteSet<number>();
|
|
81
|
+
|
|
82
|
+
function toggleSeries(index: number): void {
|
|
83
|
+
if (hiddenSeries.has(index)) {
|
|
84
|
+
hiddenSeries.delete(index);
|
|
85
|
+
} else {
|
|
86
|
+
hiddenSeries.add(index);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Shared-mode switch: one tooltip listing every visible series at the
|
|
91
|
+
// hovered x. Defaults to true for multi-series charts.
|
|
92
|
+
let shared = $derived(sharedTooltip ?? series.length > 1);
|
|
71
93
|
|
|
72
94
|
// Effective highlighted index: the declarative prop takes precedence when it is a non-null
|
|
73
95
|
// number; otherwise the imperative API value (internalHighlight) is used. This lets callers
|
|
@@ -103,15 +125,15 @@
|
|
|
103
125
|
|
|
104
126
|
// ── Layout ─────────────────────────────────────────────────────
|
|
105
127
|
|
|
106
|
-
let dims = $derived(computeChartDimensions(chartWidth, chartHeight));
|
|
107
|
-
|
|
108
128
|
let isEmpty = $derived(series.length === 0 || series.every((s) => s.data.length === 0));
|
|
109
129
|
|
|
110
130
|
let xExtent = $derived.by<[number, number]>(() => {
|
|
111
131
|
if (xDomain) {
|
|
112
132
|
return xDomain;
|
|
113
133
|
}
|
|
114
|
-
const allX = series
|
|
134
|
+
const allX = series
|
|
135
|
+
.filter((_, si) => !hiddenSeries.has(si))
|
|
136
|
+
.flatMap((s) => s.data.map((d) => d.x));
|
|
115
137
|
if (allX.length === 0) {
|
|
116
138
|
return [0, 1];
|
|
117
139
|
}
|
|
@@ -121,13 +143,54 @@
|
|
|
121
143
|
if (yDomain) {
|
|
122
144
|
return yDomain;
|
|
123
145
|
}
|
|
124
|
-
const allY = series
|
|
146
|
+
const allY = series
|
|
147
|
+
.filter((_, si) => !hiddenSeries.has(si))
|
|
148
|
+
.flatMap((s) => s.data.map((d) => d.y));
|
|
125
149
|
if (allY.length === 0) {
|
|
126
150
|
return [0, 1];
|
|
127
151
|
}
|
|
128
152
|
return niceLinearDomain(Math.min(0, ...allY), Math.max(...allY));
|
|
129
153
|
});
|
|
130
154
|
|
|
155
|
+
// ── Category tick formatter ────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
// When xAxisCategories is supplied we build a formatter that maps the numeric
|
|
158
|
+
// x-value (1-based index used in the data) to the corresponding category label.
|
|
159
|
+
let resolvedXTickFormat = $derived.by(() => {
|
|
160
|
+
if (xAxisCategories && xAxisCategories.length > 0) {
|
|
161
|
+
const categories = xAxisCategories;
|
|
162
|
+
return (value: number | string): string => {
|
|
163
|
+
const numericValue = typeof value === 'string' ? parseFloat(value) : value;
|
|
164
|
+
// x values are 1-based indices; category array is 0-based.
|
|
165
|
+
const categoryIndex = Math.round(numericValue) - 1;
|
|
166
|
+
return categories[categoryIndex] ?? String(value);
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return xTickFormat;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
let yTickCount = $derived(Math.max(2, Math.min(6, Math.floor(chartHeight / 70))));
|
|
173
|
+
let xTickCount = $derived(Math.max(2, Math.min(8, Math.floor(chartWidth / 90))));
|
|
174
|
+
// Category positions are whole numbers — fractional tick steps would repeat labels.
|
|
175
|
+
let xIntegerTicks = $derived(Boolean(xAxisCategories && xAxisCategories.length > 0));
|
|
176
|
+
|
|
177
|
+
let layout = $derived.by(() => {
|
|
178
|
+
const yFmt = yTickFormat ?? defaultTickFormat;
|
|
179
|
+
const xFmt = resolvedXTickFormat ?? defaultTickFormat;
|
|
180
|
+
return computeAutoLayout({
|
|
181
|
+
width: chartWidth,
|
|
182
|
+
height: chartHeight,
|
|
183
|
+
yTickLabels: showYAxis ? computeLinearTicks(yExtent, yTickCount).map((t) => yFmt(t)) : [],
|
|
184
|
+
xTickLabels: showXAxis
|
|
185
|
+
? computeLinearTicks(xExtent, xTickCount, xIntegerTicks).map((t) => xFmt(t))
|
|
186
|
+
: [],
|
|
187
|
+
hasYAxisLabel: Boolean(yAxisLabel) && showYAxis,
|
|
188
|
+
hasXAxisLabel: Boolean(xAxisLabel) && showXAxis,
|
|
189
|
+
base: { top: 20, right: 20, bottom: showXAxis ? 40 : 8, left: showYAxis ? 50 : 20 }
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
let dims = $derived(layout);
|
|
193
|
+
|
|
131
194
|
let xScale = $derived(createLinearScale(xExtent, [0, dims.innerWidth]));
|
|
132
195
|
let yScale = $derived(createLinearScale(yExtent, [dims.innerHeight, 0]));
|
|
133
196
|
|
|
@@ -139,32 +202,20 @@
|
|
|
139
202
|
color,
|
|
140
203
|
points,
|
|
141
204
|
path: linePath(points, curve),
|
|
142
|
-
areaD: areaPath(points, dims.innerHeight, curve)
|
|
205
|
+
areaD: areaPath(points, dims.innerHeight, curve),
|
|
206
|
+
hidden: hiddenSeries.has(si)
|
|
143
207
|
};
|
|
144
208
|
})
|
|
145
209
|
);
|
|
146
210
|
|
|
147
211
|
let legendItems = $derived<LegendItem[]>(
|
|
148
|
-
series.map((s, i) => ({
|
|
212
|
+
series.map((s, i) => ({
|
|
213
|
+
label: s.name,
|
|
214
|
+
color: s.color ?? getColor(i),
|
|
215
|
+
hidden: hiddenSeries.has(i)
|
|
216
|
+
}))
|
|
149
217
|
);
|
|
150
218
|
|
|
151
|
-
// ── Category tick formatter ────────────────────────────────────
|
|
152
|
-
|
|
153
|
-
// When xAxisCategories is supplied we build a formatter that maps the numeric
|
|
154
|
-
// x-value (1-based index used in the data) to the corresponding category label.
|
|
155
|
-
let resolvedXTickFormat = $derived.by(() => {
|
|
156
|
-
if (xAxisCategories && xAxisCategories.length > 0) {
|
|
157
|
-
const categories = xAxisCategories;
|
|
158
|
-
return (value: number | string): string => {
|
|
159
|
-
const numericValue = typeof value === 'string' ? parseFloat(value) : value;
|
|
160
|
-
// x values are 1-based indices; category array is 0-based.
|
|
161
|
-
const categoryIndex = Math.round(numericValue) - 1;
|
|
162
|
-
return categories[categoryIndex] ?? String(value);
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
return xTickFormat;
|
|
166
|
-
});
|
|
167
|
-
|
|
168
219
|
// ── Tooltip ────────────────────────────────────────────────────
|
|
169
220
|
|
|
170
221
|
let hoveredPoint = $derived(
|
|
@@ -181,8 +232,11 @@
|
|
|
181
232
|
if (effectiveHighlight === null) {
|
|
182
233
|
return null;
|
|
183
234
|
}
|
|
184
|
-
// Use the first series that has a point at this index.
|
|
235
|
+
// Use the first VISIBLE series that has a point at this index.
|
|
185
236
|
for (const line of lines) {
|
|
237
|
+
if (line.hidden) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
186
240
|
const point = line.points[effectiveHighlight];
|
|
187
241
|
if (point) {
|
|
188
242
|
return point.x;
|
|
@@ -220,19 +274,76 @@
|
|
|
220
274
|
: `x: ${formatNumber(tooltipContext.x)}`;
|
|
221
275
|
return {
|
|
222
276
|
title: xLabel,
|
|
223
|
-
items: tooltipContext.points
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
277
|
+
items: tooltipContext.points
|
|
278
|
+
.map((p, si) => ({ p, si }))
|
|
279
|
+
.filter(
|
|
280
|
+
({ si }) =>
|
|
281
|
+
!hiddenSeries.has(si) &&
|
|
282
|
+
(shared || si === hovered?.si) &&
|
|
283
|
+
series[si]?.data[hovered!.pi] != null
|
|
284
|
+
)
|
|
285
|
+
.map(({ p }) => ({ label: p.name, value: formatNumber(p.y), color: p.color }))
|
|
228
286
|
};
|
|
229
287
|
});
|
|
230
288
|
|
|
289
|
+
// Highcharts hover halo: a translucent ring behind the active marker(s).
|
|
290
|
+
let haloPoints = $derived.by(() => {
|
|
291
|
+
if (hovered === null) {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
return lines.flatMap((line, si) => {
|
|
295
|
+
if (line.hidden || (!shared && si !== hovered!.si)) {
|
|
296
|
+
return [];
|
|
297
|
+
}
|
|
298
|
+
const p = line.points[hovered!.pi];
|
|
299
|
+
return p ? [{ x: p.x, y: p.y, color: line.color }] : [];
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
let anchor = $derived.by<TooltipAnchor | null>(() => {
|
|
304
|
+
if (hovered === null || haloPoints.length === 0) {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
x: haloPoints[0].x + dims.margin.left,
|
|
309
|
+
y: Math.min(...haloPoints.map((p) => p.y)) + dims.margin.top,
|
|
310
|
+
side: 'top'
|
|
311
|
+
};
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// ── Point labels ───────────────────────────────────────────────
|
|
315
|
+
|
|
316
|
+
let pointLabelPlacements = $derived.by(() => {
|
|
317
|
+
if (!showValues) {
|
|
318
|
+
return [];
|
|
319
|
+
}
|
|
320
|
+
const plot = { width: dims.innerWidth, height: dims.innerHeight };
|
|
321
|
+
const font = { size: 11 };
|
|
322
|
+
return lines.map((line, si) =>
|
|
323
|
+
line.hidden
|
|
324
|
+
? []
|
|
325
|
+
: resolvePointLabels({
|
|
326
|
+
points: line.points,
|
|
327
|
+
labels: series[si].data.map((d) => measureText(formatNumber(d.y), font)),
|
|
328
|
+
plot
|
|
329
|
+
})
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
|
|
231
333
|
// ── Highlight dim logic ────────────────────────────────────────
|
|
232
334
|
|
|
233
335
|
// A point index is "dimmed" when the highlight system is active (hover or
|
|
234
336
|
// imperative highlight) and the point is not the active one.
|
|
235
337
|
const isDotDimmed = (si: number, pi: number): boolean => {
|
|
338
|
+
if (shared) {
|
|
339
|
+
if (hovered !== null) {
|
|
340
|
+
return hovered.pi !== pi;
|
|
341
|
+
}
|
|
342
|
+
if (effectiveHighlight !== null) {
|
|
343
|
+
return pi !== effectiveHighlight;
|
|
344
|
+
}
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
236
347
|
// Hover interaction takes precedence over imperative highlight.
|
|
237
348
|
if (hovered !== null) {
|
|
238
349
|
return hovered.si !== si || hovered.pi !== pi;
|
|
@@ -244,6 +355,9 @@
|
|
|
244
355
|
};
|
|
245
356
|
|
|
246
357
|
const isLineDimmed = (si: number): boolean => {
|
|
358
|
+
if (shared) {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
247
361
|
if (hovered !== null) {
|
|
248
362
|
return hovered.si !== si;
|
|
249
363
|
}
|
|
@@ -263,22 +377,31 @@
|
|
|
263
377
|
|
|
264
378
|
// ── Interactions ───────────────────────────────────────────────
|
|
265
379
|
|
|
266
|
-
const trackMouse = (e:
|
|
267
|
-
|
|
268
|
-
|
|
380
|
+
const trackMouse = (e: PointerEvent): void => {
|
|
381
|
+
const position = pointerPositionIn(plotEl, e);
|
|
382
|
+
if (position !== null) {
|
|
383
|
+
mouseX = position.x;
|
|
384
|
+
mouseY = position.y;
|
|
269
385
|
}
|
|
270
|
-
const rect = containerEl.getBoundingClientRect();
|
|
271
|
-
mouseX = e.clientX - rect.left;
|
|
272
|
-
mouseY = e.clientY - rect.top;
|
|
273
386
|
};
|
|
274
387
|
|
|
275
388
|
const findNearest = (plotX: number, plotY: number): { si: number; pi: number } | null => {
|
|
276
389
|
if (series.length === 0) {
|
|
277
390
|
return null;
|
|
278
391
|
}
|
|
279
|
-
// Use the longest series as the index reference so hover still works
|
|
280
|
-
// the first series is empty or shorter than the others
|
|
281
|
-
|
|
392
|
+
// Use the longest VISIBLE series as the index reference so hover still works
|
|
393
|
+
// when the first series is empty or shorter than the others, and never
|
|
394
|
+
// anchors to a series the user has toggled off via the legend.
|
|
395
|
+
const visibleEntries = series
|
|
396
|
+
.map((s, si) => ({ s, si }))
|
|
397
|
+
.filter((entry) => !hiddenSeries.has(entry.si));
|
|
398
|
+
if (visibleEntries.length === 0) {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
const referenceEntry = visibleEntries.reduce((a, b) =>
|
|
402
|
+
b.s.data.length > a.s.data.length ? b : a
|
|
403
|
+
);
|
|
404
|
+
const reference = referenceEntry.s.data;
|
|
282
405
|
if (reference.length === 0) {
|
|
283
406
|
return null;
|
|
284
407
|
}
|
|
@@ -292,9 +415,14 @@
|
|
|
292
415
|
nearestPi = i;
|
|
293
416
|
}
|
|
294
417
|
}
|
|
295
|
-
|
|
418
|
+
// The reference series is visible and owns nearestPi by construction, so
|
|
419
|
+
// it's a safe default if no other visible series' point is nearer in y.
|
|
420
|
+
let nearestSi = referenceEntry.si;
|
|
296
421
|
let nearestYDist = Infinity;
|
|
297
422
|
for (let si = 0; si < series.length; si++) {
|
|
423
|
+
if (hiddenSeries.has(si)) {
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
298
426
|
const p = series[si].data[nearestPi];
|
|
299
427
|
if (!p) {
|
|
300
428
|
continue;
|
|
@@ -309,7 +437,7 @@
|
|
|
309
437
|
return { si: nearestSi, pi: nearestPi };
|
|
310
438
|
};
|
|
311
439
|
|
|
312
|
-
const handleOverlayMove = (e:
|
|
440
|
+
const handleOverlayMove = (e: PointerEvent): void => {
|
|
313
441
|
trackMouse(e);
|
|
314
442
|
const plotX = mouseX - dims.margin.left;
|
|
315
443
|
const plotY = mouseY - dims.margin.top;
|
|
@@ -341,6 +469,15 @@
|
|
|
341
469
|
onpointclick?.({ seriesIndex: hovered.si, pointIndex: hovered.pi, point });
|
|
342
470
|
}
|
|
343
471
|
};
|
|
472
|
+
|
|
473
|
+
// Touch taps have no pointerleave: dismiss when a pointerdown lands outside.
|
|
474
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
475
|
+
$effect(() => {
|
|
476
|
+
if (hovered === null) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
return dismissOnOutsidePointerDown(containerEl, handleLeave);
|
|
480
|
+
});
|
|
344
481
|
</script>
|
|
345
482
|
|
|
346
483
|
<div
|
|
@@ -351,177 +488,224 @@
|
|
|
351
488
|
{#if isEmpty && typeof empty === 'function'}
|
|
352
489
|
<div class="chart-empty">{@render empty()}</div>
|
|
353
490
|
{:else}
|
|
354
|
-
{#if showLegend && series.length > 1}
|
|
355
|
-
|
|
491
|
+
{#if showLegend && series.length > 1 && (chartWidth === 0 || hideLegendBelow === 0 || chartWidth >= hideLegendBelow)}
|
|
492
|
+
{#if interactiveLegend}
|
|
493
|
+
<Legend items={legendItems} position="top" onToggle={toggleSeries} />
|
|
494
|
+
{:else}
|
|
495
|
+
<Legend items={legendItems} position="top" />
|
|
496
|
+
{/if}
|
|
356
497
|
{/if}
|
|
357
498
|
|
|
358
|
-
<
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
{#
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
499
|
+
<div class="chart-plot" bind:this={plotEl}>
|
|
500
|
+
<ChartContainer
|
|
501
|
+
bind:width={chartWidth}
|
|
502
|
+
bind:height={chartHeight}
|
|
503
|
+
{aspectRatio}
|
|
504
|
+
{minHeight}
|
|
505
|
+
{maxHeight}
|
|
506
|
+
>
|
|
507
|
+
{#if gradientFill || showArea}
|
|
508
|
+
<defs>
|
|
509
|
+
{#each lines as line, si (si)}
|
|
510
|
+
{#if gradientFill}
|
|
511
|
+
<linearGradient
|
|
512
|
+
id="line-grad-{uid}-{si}"
|
|
513
|
+
x1="0"
|
|
514
|
+
y1="0"
|
|
515
|
+
x2="0"
|
|
516
|
+
y2={dims.innerHeight}
|
|
517
|
+
gradientUnits="userSpaceOnUse"
|
|
518
|
+
>
|
|
519
|
+
<!-- The gradient top stop is fillOpacity + 0.3 (clamped to 1), giving a richer
|
|
378
520
|
anchor at the top that fades to transparent at the bottom. This intentionally
|
|
379
521
|
exceeds the base fillOpacity so that gradient-fill areas appear more vivid
|
|
380
522
|
than a flat solid-fill at fillOpacity alone. -->
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
523
|
+
<stop
|
|
524
|
+
offset="0%"
|
|
525
|
+
stop-color={line.color}
|
|
526
|
+
stop-opacity={Math.min(
|
|
527
|
+
(hovered?.si === si ? fillOpacity + 0.2 : fillOpacity) + 0.3,
|
|
528
|
+
1
|
|
529
|
+
)}
|
|
530
|
+
/>
|
|
531
|
+
<stop offset="100%" stop-color={line.color} stop-opacity={0} />
|
|
532
|
+
</linearGradient>
|
|
533
|
+
{/if}
|
|
534
|
+
{#if showArea}
|
|
535
|
+
<linearGradient
|
|
536
|
+
id="line-area-{uid}-{si}"
|
|
537
|
+
x1="0"
|
|
538
|
+
y1="0"
|
|
539
|
+
x2="0"
|
|
540
|
+
y2={dims.innerHeight}
|
|
541
|
+
gradientUnits="userSpaceOnUse"
|
|
542
|
+
>
|
|
543
|
+
<stop
|
|
544
|
+
offset="0%"
|
|
545
|
+
stop-color={areaGradient ? areaGradient.from : line.color}
|
|
546
|
+
stop-opacity={areaGradient ? 1 : 0.35}
|
|
547
|
+
/>
|
|
548
|
+
<stop
|
|
549
|
+
offset="100%"
|
|
550
|
+
stop-color={areaGradient ? areaGradient.to : line.color}
|
|
551
|
+
stop-opacity={areaGradient ? 1 : 0}
|
|
552
|
+
/>
|
|
553
|
+
</linearGradient>
|
|
554
|
+
{/if}
|
|
555
|
+
{/each}
|
|
556
|
+
</defs>
|
|
557
|
+
{/if}
|
|
558
|
+
<g transform="translate({dims.margin.left}, {dims.margin.top})">
|
|
559
|
+
{#if showYAxis}
|
|
560
|
+
<Axis
|
|
561
|
+
orientation="left"
|
|
562
|
+
scale={yScale}
|
|
563
|
+
tickCount={yTickCount}
|
|
564
|
+
{showGridlines}
|
|
565
|
+
gridlineLength={dims.innerWidth}
|
|
566
|
+
label={yAxisLabel}
|
|
567
|
+
tickFormat={yTickFormat}
|
|
568
|
+
/>
|
|
569
|
+
{/if}
|
|
570
|
+
{#if showXAxis}
|
|
571
|
+
<g transform="translate(0, {dims.innerHeight})">
|
|
572
|
+
<Axis
|
|
573
|
+
orientation="bottom"
|
|
574
|
+
scale={xScale}
|
|
575
|
+
tickCount={xTickCount}
|
|
576
|
+
integerTicks={xIntegerTicks}
|
|
577
|
+
rotateTicks={layout.xRotate}
|
|
578
|
+
tickEvery={layout.xEvery}
|
|
579
|
+
labelOffset={layout.xLabelOffset}
|
|
580
|
+
label={xAxisLabel}
|
|
581
|
+
tickFormat={resolvedXTickFormat}
|
|
582
|
+
/>
|
|
583
|
+
</g>
|
|
584
|
+
{/if}
|
|
585
|
+
|
|
586
|
+
{#each haloPoints as hp, i (i)}
|
|
587
|
+
<circle class="dot-halo" cx={hp.x} cy={hp.y} r={dotRadius + 6} fill={hp.color} />
|
|
588
|
+
{/each}
|
|
589
|
+
|
|
590
|
+
{#each lines as line, si (si)}
|
|
591
|
+
{#if !line.hidden}
|
|
592
|
+
{#if showArea}
|
|
593
|
+
<path
|
|
594
|
+
class="line-area-fill"
|
|
595
|
+
class:dimmed={isLineDimmed(si)}
|
|
596
|
+
d={line.areaD}
|
|
597
|
+
fill="url(#line-area-{uid}-{si})"
|
|
388
598
|
/>
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
x1="0"
|
|
396
|
-
y1="0"
|
|
397
|
-
x2="0"
|
|
398
|
-
y2={dims.innerHeight}
|
|
399
|
-
gradientUnits="userSpaceOnUse"
|
|
400
|
-
>
|
|
401
|
-
<stop
|
|
402
|
-
offset="0%"
|
|
403
|
-
stop-color={areaGradient ? areaGradient.from : line.color}
|
|
404
|
-
stop-opacity={areaGradient ? 1 : 0.35}
|
|
599
|
+
{:else if gradientFill}
|
|
600
|
+
<path
|
|
601
|
+
class="line-area-fill"
|
|
602
|
+
class:dimmed={hovered !== null && hovered.si !== si}
|
|
603
|
+
d={line.areaD}
|
|
604
|
+
fill="url(#line-grad-{uid}-{si})"
|
|
405
605
|
/>
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
606
|
+
{/if}
|
|
607
|
+
<path
|
|
608
|
+
class="line-path"
|
|
609
|
+
class:dimmed={isLineDimmed(si)}
|
|
610
|
+
d={line.path}
|
|
611
|
+
stroke={line.color}
|
|
612
|
+
stroke-width={strokeWidth}
|
|
613
|
+
fill="none"
|
|
614
|
+
/>
|
|
615
|
+
{#if line.points.length === 1 && !showDots}
|
|
616
|
+
<circle
|
|
617
|
+
class="single-point"
|
|
618
|
+
class:dimmed={isLineDimmed(si)}
|
|
619
|
+
cx={line.points[0].x}
|
|
620
|
+
cy={line.points[0].y}
|
|
621
|
+
r={dotRadius * 1.5}
|
|
622
|
+
fill={line.color}
|
|
410
623
|
/>
|
|
411
|
-
|
|
624
|
+
{/if}
|
|
625
|
+
{#if showDots}
|
|
626
|
+
{#each line.points as point, pi (pi)}
|
|
627
|
+
<circle
|
|
628
|
+
class="dot"
|
|
629
|
+
class:dimmed={isDotDimmed(si, pi)}
|
|
630
|
+
class:highlighted={isHighlightedDot(si, pi)}
|
|
631
|
+
cx={point.x}
|
|
632
|
+
cy={point.y}
|
|
633
|
+
r={isHighlightedDot(si, pi) ? dotRadius * 1.5 : dotRadius}
|
|
634
|
+
fill={line.color}
|
|
635
|
+
/>
|
|
636
|
+
{/each}
|
|
637
|
+
{/if}
|
|
638
|
+
{#if showValues && pointLabelPlacements[si]}
|
|
639
|
+
{#each line.points as _point, pi (pi)}
|
|
640
|
+
{@const pl = pointLabelPlacements[si][pi]}
|
|
641
|
+
{#if pl?.visible}
|
|
642
|
+
<text
|
|
643
|
+
class="point-value"
|
|
644
|
+
x={pl.x}
|
|
645
|
+
y={pl.y}
|
|
646
|
+
text-anchor="middle"
|
|
647
|
+
dominant-baseline={pl.dominantBaseline}
|
|
648
|
+
>{formatNumber(series[si].data[pi].y)}</text
|
|
649
|
+
>
|
|
650
|
+
{/if}
|
|
651
|
+
{/each}
|
|
652
|
+
{/if}
|
|
412
653
|
{/if}
|
|
413
654
|
{/each}
|
|
414
|
-
</defs>
|
|
415
|
-
{/if}
|
|
416
|
-
<g transform="translate({dims.margin.left}, {dims.margin.top})">
|
|
417
|
-
{#if showYAxis}
|
|
418
|
-
<Axis
|
|
419
|
-
orientation="left"
|
|
420
|
-
scale={yScale}
|
|
421
|
-
{showGridlines}
|
|
422
|
-
gridlineLength={dims.innerWidth}
|
|
423
|
-
label={yAxisLabel}
|
|
424
|
-
tickFormat={yTickFormat}
|
|
425
|
-
/>
|
|
426
|
-
{/if}
|
|
427
|
-
{#if showXAxis}
|
|
428
|
-
<g transform="translate(0, {dims.innerHeight})">
|
|
429
|
-
<Axis
|
|
430
|
-
orientation="bottom"
|
|
431
|
-
scale={xScale}
|
|
432
|
-
label={xAxisLabel}
|
|
433
|
-
tickFormat={resolvedXTickFormat}
|
|
434
|
-
/>
|
|
435
|
-
</g>
|
|
436
|
-
{/if}
|
|
437
655
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
656
|
+
{#if activeLineX !== null}
|
|
657
|
+
<line
|
|
658
|
+
class="hover-line"
|
|
659
|
+
x1={activeLineX}
|
|
660
|
+
x2={activeLineX}
|
|
661
|
+
y1={0}
|
|
662
|
+
y2={dims.innerHeight}
|
|
445
663
|
/>
|
|
446
|
-
{:else if gradientFill}
|
|
447
|
-
<path
|
|
448
|
-
class="line-area-fill"
|
|
449
|
-
class:dimmed={hovered !== null && hovered.si !== si}
|
|
450
|
-
d={line.areaD}
|
|
451
|
-
fill="url(#line-grad-{uid}-{si})"
|
|
452
|
-
/>
|
|
453
|
-
{/if}
|
|
454
|
-
<path
|
|
455
|
-
class="line-path"
|
|
456
|
-
class:dimmed={isLineDimmed(si)}
|
|
457
|
-
d={line.path}
|
|
458
|
-
stroke={line.color}
|
|
459
|
-
stroke-width={strokeWidth}
|
|
460
|
-
fill="none"
|
|
461
|
-
/>
|
|
462
|
-
{#if line.points.length === 1 && !showDots}
|
|
463
|
-
<circle
|
|
464
|
-
class="single-point"
|
|
465
|
-
class:dimmed={isLineDimmed(si)}
|
|
466
|
-
cx={line.points[0].x}
|
|
467
|
-
cy={line.points[0].y}
|
|
468
|
-
r={dotRadius * 1.5}
|
|
469
|
-
fill={line.color}
|
|
470
|
-
/>
|
|
471
|
-
{/if}
|
|
472
|
-
{#if showDots}
|
|
473
|
-
{#each line.points as point, pi (pi)}
|
|
474
|
-
<circle
|
|
475
|
-
class="dot"
|
|
476
|
-
class:dimmed={isDotDimmed(si, pi)}
|
|
477
|
-
class:highlighted={isHighlightedDot(si, pi)}
|
|
478
|
-
cx={point.x}
|
|
479
|
-
cy={point.y}
|
|
480
|
-
r={isHighlightedDot(si, pi) ? dotRadius * 1.5 : dotRadius}
|
|
481
|
-
fill={line.color}
|
|
482
|
-
/>
|
|
483
|
-
{/each}
|
|
484
664
|
{/if}
|
|
485
|
-
{#if showValues}
|
|
486
|
-
{#each line.points as point, pi (pi)}
|
|
487
|
-
<text
|
|
488
|
-
class="point-value"
|
|
489
|
-
x={point.x}
|
|
490
|
-
y={point.y - 8}
|
|
491
|
-
text-anchor="middle"
|
|
492
|
-
dominant-baseline="auto">{formatNumber(series[si].data[pi].y)}</text
|
|
493
|
-
>
|
|
494
|
-
{/each}
|
|
495
|
-
{/if}
|
|
496
|
-
{/each}
|
|
497
|
-
|
|
498
|
-
{#if activeLineX !== null}
|
|
499
|
-
<line class="hover-line" x1={activeLineX} x2={activeLineX} y1={0} y2={dims.innerHeight} />
|
|
500
|
-
{/if}
|
|
501
665
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
666
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
667
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
668
|
+
<rect
|
|
669
|
+
class="hover-overlay"
|
|
670
|
+
x={0}
|
|
671
|
+
y={0}
|
|
672
|
+
width={dims.innerWidth}
|
|
673
|
+
height={dims.innerHeight}
|
|
674
|
+
fill="transparent"
|
|
675
|
+
onpointermove={handleOverlayMove}
|
|
676
|
+
onpointerleave={handleLeave}
|
|
677
|
+
onclick={handleClick}
|
|
678
|
+
/>
|
|
679
|
+
</g>
|
|
680
|
+
</ChartContainer>
|
|
681
|
+
|
|
682
|
+
{#if typeof tooltipSnippet === 'function'}
|
|
683
|
+
<ChartTooltip
|
|
684
|
+
data={tooltipData}
|
|
685
|
+
{mouseX}
|
|
686
|
+
{mouseY}
|
|
687
|
+
{anchor}
|
|
688
|
+
portal={tooltipPortal}
|
|
689
|
+
originEl={plotEl}
|
|
690
|
+
unstyled
|
|
691
|
+
>
|
|
692
|
+
{#snippet content()}
|
|
693
|
+
{#if tooltipContext !== null}
|
|
694
|
+
{@render tooltipSnippet(tooltipContext)}
|
|
695
|
+
{/if}
|
|
696
|
+
{/snippet}
|
|
697
|
+
</ChartTooltip>
|
|
698
|
+
{:else}
|
|
699
|
+
<ChartTooltip
|
|
700
|
+
data={tooltipData}
|
|
701
|
+
{mouseX}
|
|
702
|
+
{mouseY}
|
|
703
|
+
{anchor}
|
|
704
|
+
portal={tooltipPortal}
|
|
705
|
+
originEl={plotEl}
|
|
514
706
|
/>
|
|
515
|
-
|
|
516
|
-
</
|
|
517
|
-
|
|
518
|
-
{#if typeof tooltipSnippet === 'function' && tooltipContext !== null}
|
|
519
|
-
<div class="chart-tooltip-slot" style="left: {mouseX + 12}px; top: {mouseY - 12}px;">
|
|
520
|
-
{@render tooltipSnippet(tooltipContext)}
|
|
521
|
-
</div>
|
|
522
|
-
{:else}
|
|
523
|
-
<ChartTooltip data={tooltipData} {mouseX} {mouseY} />
|
|
524
|
-
{/if}
|
|
707
|
+
{/if}
|
|
708
|
+
</div>
|
|
525
709
|
{/if}
|
|
526
710
|
</div>
|
|
527
711
|
|
|
@@ -530,6 +714,9 @@
|
|
|
530
714
|
width: 100%;
|
|
531
715
|
position: relative;
|
|
532
716
|
}
|
|
717
|
+
.chart-plot {
|
|
718
|
+
position: relative;
|
|
719
|
+
}
|
|
533
720
|
.line-area-fill {
|
|
534
721
|
transition:
|
|
535
722
|
fill-opacity var(--chart-transition-duration, 0.2s) ease,
|
|
@@ -558,7 +745,7 @@
|
|
|
558
745
|
transition:
|
|
559
746
|
r var(--chart-transition-duration, 0.2s) ease,
|
|
560
747
|
opacity var(--chart-transition-duration, 0.2s) ease;
|
|
561
|
-
stroke: var(--chart-
|
|
748
|
+
stroke: var(--chart-dot-stroke, light-dark(#fff, #111827));
|
|
562
749
|
stroke-width: 2;
|
|
563
750
|
pointer-events: none;
|
|
564
751
|
}
|
|
@@ -566,17 +753,21 @@
|
|
|
566
753
|
opacity: var(--linechart-dimmed-opacity, 0.2);
|
|
567
754
|
}
|
|
568
755
|
.dot.highlighted {
|
|
569
|
-
stroke: var(--linechart-highlight-ring-color, #fff);
|
|
756
|
+
stroke: var(--linechart-highlight-ring-color, light-dark(#fff, #111827));
|
|
570
757
|
stroke-width: var(--linechart-highlight-ring-width, 2.5);
|
|
571
758
|
}
|
|
759
|
+
.dot-halo {
|
|
760
|
+
opacity: 0.25;
|
|
761
|
+
pointer-events: none;
|
|
762
|
+
}
|
|
572
763
|
.point-value {
|
|
573
|
-
fill: var(--linechart-value-color, #333);
|
|
764
|
+
fill: var(--linechart-value-color, light-dark(#333, #e5e7eb));
|
|
574
765
|
font-size: var(--linechart-value-font-size, 11px);
|
|
575
766
|
font-family: var(--chart-font-family, inherit);
|
|
576
767
|
pointer-events: none;
|
|
577
768
|
}
|
|
578
769
|
.hover-line {
|
|
579
|
-
stroke: var(--linechart-hover-line-color, #ccc);
|
|
770
|
+
stroke: var(--linechart-hover-line-color, light-dark(#ccc, #4b5563));
|
|
580
771
|
stroke-width: 1;
|
|
581
772
|
stroke-dasharray: var(--linechart-hover-line-dash, 4 4);
|
|
582
773
|
pointer-events: none;
|
|
@@ -584,14 +775,9 @@
|
|
|
584
775
|
.hover-overlay {
|
|
585
776
|
cursor: crosshair;
|
|
586
777
|
}
|
|
587
|
-
.chart-tooltip-slot {
|
|
588
|
-
position: absolute;
|
|
589
|
-
z-index: 10;
|
|
590
|
-
pointer-events: none;
|
|
591
|
-
}
|
|
592
778
|
.chart-empty {
|
|
593
779
|
padding: var(--chart-empty-padding, 32px 24px);
|
|
594
|
-
color: var(--chart-empty-color, #9ca3af);
|
|
780
|
+
color: var(--chart-empty-color, light-dark(#9ca3af, #6b7280));
|
|
595
781
|
text-align: center;
|
|
596
782
|
}
|
|
597
783
|
</style>
|