layerchart 0.74.0 → 0.75.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/components/Area.svelte +3 -3
- package/dist/components/Circle.svelte +1 -0
- package/dist/components/Highlight.svelte +5 -0
- package/dist/components/Legend.svelte +10 -3
- package/dist/components/Legend.svelte.d.ts +4 -1
- package/dist/components/Spline.svelte +2 -2
- package/dist/components/charts/AreaChart.svelte +56 -11
- package/dist/components/charts/BarChart.svelte +54 -16
- package/dist/components/charts/LineChart.svelte +46 -7
- package/dist/components/charts/PieChart.svelte +45 -2
- package/dist/components/charts/PieChart.svelte.d.ts +5 -1
- package/dist/components/charts/ScatterChart.svelte +42 -4
- package/package.json +1 -1
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
export let stroke: string | undefined = undefined;
|
|
60
60
|
export let strokeWidth: number | undefined = undefined;
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
$: xAccessor = x ? accessor(x) : $contextX;
|
|
63
|
+
$: y0Accessor = y0 ? accessor(y0) : (d: any) => min($yDomain);
|
|
64
|
+
$: y1Accessor = y1 ? accessor(y1) : $y;
|
|
65
65
|
|
|
66
66
|
$: xOffset = isScaleBand($xScale) ? $xScale.bandwidth() / 2 : 0;
|
|
67
67
|
$: yOffset = isScaleBand($yScale) ? $yScale.bandwidth() / 2 : 0;
|
|
@@ -68,7 +68,10 @@
|
|
|
68
68
|
|
|
69
69
|
export let onAreaClick: (e: { data: any }) => void = () => {};
|
|
70
70
|
export let onBarClick: (e: { data: any }) => void = () => {};
|
|
71
|
+
|
|
71
72
|
export let onPointClick: (e: { point: (typeof _points)[number]; data: any }) => void = () => {};
|
|
73
|
+
export let onPointEnter: (e: { point: (typeof _points)[number]; data: any }) => void = () => {};
|
|
74
|
+
export let onPointLeave: (e: { point: (typeof _points)[number]; data: any }) => void = () => {};
|
|
72
75
|
|
|
73
76
|
const _x = accessor(x);
|
|
74
77
|
const _y = accessor(y);
|
|
@@ -411,6 +414,8 @@
|
|
|
411
414
|
typeof points === 'object' ? points.class : null
|
|
412
415
|
)}
|
|
413
416
|
on:click={() => onPointClick({ point, data: highlightData })}
|
|
417
|
+
on:pointerenter={() => onPointEnter({ point, data: highlightData })}
|
|
418
|
+
on:pointerleave={() => onPointLeave({ point, data: highlightData })}
|
|
414
419
|
/>
|
|
415
420
|
{/each}
|
|
416
421
|
</slot>
|
|
@@ -35,7 +35,9 @@
|
|
|
35
35
|
export let placement: Placement | undefined = undefined;
|
|
36
36
|
export let orientation: 'horizontal' | 'vertical' = 'horizontal';
|
|
37
37
|
|
|
38
|
-
export let onClick: ((
|
|
38
|
+
export let onClick: ((item: any) => any) | undefined = undefined;
|
|
39
|
+
export let onPointerEnter: ((item: any) => any) | undefined = undefined;
|
|
40
|
+
export let onPointerLeave: ((item: any) => any) | undefined = undefined;
|
|
39
41
|
|
|
40
42
|
/** Determine display ramp (individual color swatches or continuous ramp)*/
|
|
41
43
|
export let variant: 'ramp' | 'swatches' = 'ramp';
|
|
@@ -47,6 +49,7 @@
|
|
|
47
49
|
tick?: string;
|
|
48
50
|
swatches?: string;
|
|
49
51
|
swatch?: string;
|
|
52
|
+
item?: (item: any) => string;
|
|
50
53
|
} = {};
|
|
51
54
|
|
|
52
55
|
$: _scale = scale ?? (cScale ? $cScale : null);
|
|
@@ -137,6 +140,7 @@
|
|
|
137
140
|
{...$$restProps}
|
|
138
141
|
class={cls(
|
|
139
142
|
'inline-block',
|
|
143
|
+
'z-[1]', // stack above tooltip context layers (band rects, voronoi, ...)
|
|
140
144
|
placement && [
|
|
141
145
|
'absolute',
|
|
142
146
|
{
|
|
@@ -208,9 +212,12 @@
|
|
|
208
212
|
>
|
|
209
213
|
{#each tickValues ?? xScale?.ticks?.(ticks) ?? [] as tick}
|
|
210
214
|
{@const color = _scale(tick)}
|
|
215
|
+
{@const item = { value: tick, color }}
|
|
211
216
|
<button
|
|
212
|
-
class={cls('flex gap-1', !onClick && 'cursor-auto')}
|
|
213
|
-
on:click={() => onClick?.(
|
|
217
|
+
class={cls('flex gap-1', !onClick && 'cursor-auto', classes.item?.(item))}
|
|
218
|
+
on:click={() => onClick?.(item)}
|
|
219
|
+
on:pointerenter={() => onPointerEnter?.(item)}
|
|
220
|
+
on:pointerleave={() => onPointerLeave?.(item)}
|
|
214
221
|
>
|
|
215
222
|
<div
|
|
216
223
|
class={cls('h-4 w-4 rounded-full', classes.swatch)}
|
|
@@ -14,7 +14,9 @@ declare const __propDef: {
|
|
|
14
14
|
tickLength?: number | undefined;
|
|
15
15
|
placement?: ("center" | "bottom" | "left" | "right" | "top" | "top-left" | "top-right" | "bottom-left" | "bottom-right") | undefined;
|
|
16
16
|
orientation?: "horizontal" | "vertical" | undefined;
|
|
17
|
-
onClick?: ((
|
|
17
|
+
onClick?: ((item: any) => any) | undefined | undefined;
|
|
18
|
+
onPointerEnter?: ((item: any) => any) | undefined | undefined;
|
|
19
|
+
onPointerLeave?: ((item: any) => any) | undefined | undefined;
|
|
18
20
|
variant?: "ramp" | "swatches" | undefined;
|
|
19
21
|
classes?: {
|
|
20
22
|
root?: string;
|
|
@@ -23,6 +25,7 @@ declare const __propDef: {
|
|
|
23
25
|
tick?: string;
|
|
24
26
|
swatches?: string;
|
|
25
27
|
swatch?: string;
|
|
28
|
+
item?: (item: any) => string;
|
|
26
29
|
} | undefined;
|
|
27
30
|
};
|
|
28
31
|
events: {
|
|
@@ -101,8 +101,8 @@
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
$: xAccessor = x ? accessor(x) : $contextX;
|
|
105
|
+
$: yAccessor = y ? accessor(y) : $contextY;
|
|
106
106
|
|
|
107
107
|
$: xOffset = isScaleBand($xScale) ? $xScale.bandwidth() / 2 : 0;
|
|
108
108
|
$: yOffset = isScaleBand($yScale) ? $yScale.bandwidth() / 2 : 0;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script lang="ts" generics="TData">
|
|
2
|
-
import { type ComponentProps } from 'svelte';
|
|
2
|
+
import { onMount, type ComponentProps } from 'svelte';
|
|
3
3
|
import { scaleLinear, scaleOrdinal, scaleTime } from 'd3-scale';
|
|
4
4
|
import { stack, stackOffsetDiverging, stackOffsetExpand, stackOffsetNone } from 'd3-shape';
|
|
5
5
|
import { sum } from 'd3-array';
|
|
6
6
|
import { format } from '@layerstack/utils';
|
|
7
|
+
import { cls } from '@layerstack/tailwind';
|
|
8
|
+
import { selectionStore } from '@layerstack/svelte-stores';
|
|
7
9
|
|
|
8
10
|
import Area from '../Area.svelte';
|
|
9
11
|
import Axis from '../Axis.svelte';
|
|
@@ -34,6 +36,7 @@
|
|
|
34
36
|
labels?: typeof labels;
|
|
35
37
|
legend?: typeof legend;
|
|
36
38
|
points?: typeof points;
|
|
39
|
+
profile?: typeof profile;
|
|
37
40
|
props?: typeof props;
|
|
38
41
|
rule?: typeof rule;
|
|
39
42
|
series?: typeof series;
|
|
@@ -103,7 +106,10 @@
|
|
|
103
106
|
|
|
104
107
|
export let renderContext: 'svg' | 'canvas' = 'svg';
|
|
105
108
|
|
|
106
|
-
|
|
109
|
+
/** Log initial render performance using `console.time` */
|
|
110
|
+
export let profile = false;
|
|
111
|
+
|
|
112
|
+
$: allSeriesData = visibleSeries
|
|
107
113
|
.flatMap((s) => s.data?.map((d) => ({ seriesKey: s.key, ...d })))
|
|
108
114
|
.filter((d) => d) as Array<TData & { stackData?: any }>;
|
|
109
115
|
|
|
@@ -112,13 +118,14 @@
|
|
|
112
118
|
>;
|
|
113
119
|
|
|
114
120
|
$: if (stackSeries) {
|
|
115
|
-
const seriesKeys =
|
|
121
|
+
const seriesKeys = visibleSeries.map((s) => s.key);
|
|
116
122
|
const offset =
|
|
117
123
|
seriesLayout === 'stackExpand'
|
|
118
124
|
? stackOffsetExpand
|
|
119
125
|
: seriesLayout === 'stackDiverging'
|
|
120
126
|
? stackOffsetDiverging
|
|
121
127
|
: stackOffsetNone;
|
|
128
|
+
|
|
122
129
|
const stackData = stack()
|
|
123
130
|
.keys(seriesKeys)
|
|
124
131
|
.value((d, key) => {
|
|
@@ -139,6 +146,8 @@
|
|
|
139
146
|
$: xScale =
|
|
140
147
|
$$props.xScale ?? (accessor(x)(chartData[0]) instanceof Date ? scaleTime() : scaleLinear());
|
|
141
148
|
|
|
149
|
+
let highlightSeriesKey: (typeof series)[number]['key'] | null = null;
|
|
150
|
+
|
|
142
151
|
function getAreaProps(s: (typeof series)[number], i: number) {
|
|
143
152
|
const lineProps = {
|
|
144
153
|
...props.line,
|
|
@@ -156,10 +165,18 @@
|
|
|
156
165
|
: (s.value ?? (s.data ? undefined : s.key)),
|
|
157
166
|
fill: s.color,
|
|
158
167
|
fillOpacity: 0.3,
|
|
168
|
+
class: cls(
|
|
169
|
+
'transition-opacity',
|
|
170
|
+
highlightSeriesKey && highlightSeriesKey !== s.key && 'opacity-10'
|
|
171
|
+
),
|
|
159
172
|
...props.area,
|
|
160
173
|
...s.props,
|
|
161
174
|
line: {
|
|
162
|
-
class:
|
|
175
|
+
class: cls(
|
|
176
|
+
!('stroke-width' in lineProps) && 'stroke-2',
|
|
177
|
+
'transition-opacity',
|
|
178
|
+
highlightSeriesKey && highlightSeriesKey !== s.key && 'opacity-10'
|
|
179
|
+
),
|
|
163
180
|
stroke: s.color,
|
|
164
181
|
...lineProps,
|
|
165
182
|
},
|
|
@@ -167,6 +184,22 @@
|
|
|
167
184
|
|
|
168
185
|
return areaProps;
|
|
169
186
|
}
|
|
187
|
+
|
|
188
|
+
const selectedSeries = selectionStore();
|
|
189
|
+
$: visibleSeries = series.filter((s) => {
|
|
190
|
+
return (
|
|
191
|
+
// @ts-expect-error
|
|
192
|
+
$selectedSeries.selected.length === 0 || $selectedSeries.isSelected(s.key)
|
|
193
|
+
// || highlightSeriesKey == s.key
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (profile) {
|
|
198
|
+
console.time('AreaChart render');
|
|
199
|
+
onMount(() => {
|
|
200
|
+
console.timeEnd('AreaChart render');
|
|
201
|
+
});
|
|
202
|
+
}
|
|
170
203
|
</script>
|
|
171
204
|
|
|
172
205
|
<Chart
|
|
@@ -175,8 +208,8 @@
|
|
|
175
208
|
{xScale}
|
|
176
209
|
y={y ??
|
|
177
210
|
(stackSeries
|
|
178
|
-
? (d) =>
|
|
179
|
-
:
|
|
211
|
+
? (d) => visibleSeries.flatMap((s, i) => d.stackData[i])
|
|
212
|
+
: visibleSeries.map((s) => s.value ?? s.key))}
|
|
180
213
|
yBaseline={0}
|
|
181
214
|
yNice
|
|
182
215
|
{radial}
|
|
@@ -220,7 +253,7 @@
|
|
|
220
253
|
<slot name="belowMarks" {...slotProps} />
|
|
221
254
|
|
|
222
255
|
<slot name="marks" {...slotProps}>
|
|
223
|
-
{#each
|
|
256
|
+
{#each visibleSeries as s, i (s.key)}
|
|
224
257
|
<Area {...getAreaProps(s, i)} />
|
|
225
258
|
{/each}
|
|
226
259
|
</slot>
|
|
@@ -260,7 +293,7 @@
|
|
|
260
293
|
</slot>
|
|
261
294
|
|
|
262
295
|
{#if points}
|
|
263
|
-
{#each
|
|
296
|
+
{#each visibleSeries as s}
|
|
264
297
|
<Points
|
|
265
298
|
data={s.data}
|
|
266
299
|
fill={s.color}
|
|
@@ -272,7 +305,7 @@
|
|
|
272
305
|
{/if}
|
|
273
306
|
|
|
274
307
|
<slot name="highlight" {...slotProps}>
|
|
275
|
-
{#each
|
|
308
|
+
{#each visibleSeries as s, i (s.key)}
|
|
276
309
|
{@const seriesTooltipData =
|
|
277
310
|
s.data && tooltip.data ? findRelatedData(s.data, tooltip.data, x) : null}
|
|
278
311
|
|
|
@@ -282,6 +315,8 @@
|
|
|
282
315
|
points={{ fill: s.color }}
|
|
283
316
|
lines={i == 0}
|
|
284
317
|
onPointClick={(e) => onPointClick({ ...e, series: s })}
|
|
318
|
+
onPointEnter={() => (highlightSeriesKey = s.key)}
|
|
319
|
+
onPointLeave={() => (highlightSeriesKey = null)}
|
|
285
320
|
{...props.highlight}
|
|
286
321
|
/>
|
|
287
322
|
{/each}
|
|
@@ -298,11 +333,21 @@
|
|
|
298
333
|
scale={isDefaultSeries
|
|
299
334
|
? undefined
|
|
300
335
|
: scaleOrdinal(
|
|
301
|
-
series.map((s) => s.
|
|
336
|
+
series.map((s) => s.key),
|
|
302
337
|
series.map((s) => s.color)
|
|
303
338
|
)}
|
|
339
|
+
tickFormat={(key) => series.find((s) => s.key === key)?.label ?? key}
|
|
304
340
|
placement="bottom"
|
|
305
341
|
variant="swatches"
|
|
342
|
+
onClick={(item) => $selectedSeries.toggleSelected(item.value)}
|
|
343
|
+
onPointerEnter={(item) => (highlightSeriesKey = item.value)}
|
|
344
|
+
onPointerLeave={(item) => (highlightSeriesKey = null)}
|
|
345
|
+
classes={{
|
|
346
|
+
item: (item) =>
|
|
347
|
+
visibleSeries.length && !visibleSeries.some((s) => s.key === item.value)
|
|
348
|
+
? 'opacity-50'
|
|
349
|
+
: '',
|
|
350
|
+
}}
|
|
306
351
|
{...props.legend}
|
|
307
352
|
{...typeof legend === 'object' ? legend : null}
|
|
308
353
|
/>
|
|
@@ -314,7 +359,7 @@
|
|
|
314
359
|
<Tooltip.Header {...props.tooltip?.header}>{format(x(data))}</Tooltip.Header>
|
|
315
360
|
<Tooltip.List {...props.tooltip?.list}>
|
|
316
361
|
<!-- Reverse series order so tooltip items match stacks -->
|
|
317
|
-
{@const seriesItems = stackSeries ? [...
|
|
362
|
+
{@const seriesItems = stackSeries ? [...visibleSeries].reverse() : visibleSeries}
|
|
318
363
|
{#each seriesItems as s}
|
|
319
364
|
{@const seriesTooltipData = s.data ? findRelatedData(s.data, data, x) : data}
|
|
320
365
|
{@const valueAccessor = accessor(s.value ?? (s.data ? asAny(y) : s.key))}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script lang="ts" generics="TData">
|
|
2
|
-
import { type ComponentProps } from 'svelte';
|
|
2
|
+
import { onMount, type ComponentProps } from 'svelte';
|
|
3
3
|
import { scaleBand, scaleOrdinal, scaleLinear } from 'd3-scale';
|
|
4
4
|
import { stack, stackOffsetDiverging, stackOffsetExpand, stackOffsetNone } from 'd3-shape';
|
|
5
5
|
import { sum } from 'd3-array';
|
|
6
6
|
import { format } from '@layerstack/utils';
|
|
7
|
+
import { cls } from '@layerstack/tailwind';
|
|
8
|
+
import { selectionStore } from '@layerstack/svelte-stores';
|
|
7
9
|
|
|
8
10
|
import Axis from '../Axis.svelte';
|
|
9
11
|
import Bars from '../Bars.svelte';
|
|
@@ -38,6 +40,7 @@
|
|
|
38
40
|
labels?: typeof labels;
|
|
39
41
|
legend?: typeof legend;
|
|
40
42
|
orientation?: typeof orientation;
|
|
43
|
+
profile?: typeof profile;
|
|
41
44
|
props?: typeof props;
|
|
42
45
|
rule?: typeof rule;
|
|
43
46
|
series?: typeof series;
|
|
@@ -113,11 +116,11 @@
|
|
|
113
116
|
$: if (seriesLayout === 'group') {
|
|
114
117
|
if (isVertical) {
|
|
115
118
|
x1Scale = scaleBand().padding(groupPadding);
|
|
116
|
-
x1Domain =
|
|
119
|
+
x1Domain = visibleSeries.map((s) => s.key);
|
|
117
120
|
x1Range = ({ xScale }) => [0, xScale.bandwidth?.()];
|
|
118
121
|
} else {
|
|
119
122
|
y1Scale = scaleBand().padding(groupPadding);
|
|
120
|
-
y1Domain =
|
|
123
|
+
y1Domain = visibleSeries.map((s) => s.key);
|
|
121
124
|
y1Range = ({ yScale }) => [0, yScale.bandwidth?.()];
|
|
122
125
|
}
|
|
123
126
|
}
|
|
@@ -142,7 +145,10 @@
|
|
|
142
145
|
|
|
143
146
|
export let renderContext: 'svg' | 'canvas' = 'svg';
|
|
144
147
|
|
|
145
|
-
|
|
148
|
+
/** Log initial render performance using `console.time` */
|
|
149
|
+
export let profile = false;
|
|
150
|
+
|
|
151
|
+
$: allSeriesData = visibleSeries
|
|
146
152
|
.flatMap((s) =>
|
|
147
153
|
s.data?.map((d) => {
|
|
148
154
|
return { seriesKey: s.key, ...d };
|
|
@@ -155,7 +161,7 @@
|
|
|
155
161
|
>;
|
|
156
162
|
|
|
157
163
|
$: if (stackSeries) {
|
|
158
|
-
const seriesKeys =
|
|
164
|
+
const seriesKeys = visibleSeries.map((s) => s.key);
|
|
159
165
|
// const stackData = stack().keys(seriesKeys)(chartDataArray(data)) as any[];
|
|
160
166
|
|
|
161
167
|
const offset =
|
|
@@ -180,9 +186,11 @@
|
|
|
180
186
|
});
|
|
181
187
|
}
|
|
182
188
|
|
|
189
|
+
let highlightSeriesKey: (typeof series)[number]['key'] | null = null;
|
|
190
|
+
|
|
183
191
|
function getBarsProps(s: (typeof series)[number], i: number) {
|
|
184
192
|
const isFirst = i == 0;
|
|
185
|
-
const isLast = i ==
|
|
193
|
+
const isLast = i == visibleSeries.length - 1;
|
|
186
194
|
|
|
187
195
|
const isStackLayout = seriesLayout.startsWith('stack');
|
|
188
196
|
|
|
@@ -212,11 +220,15 @@
|
|
|
212
220
|
y: isVertical ? valueAccessor : undefined,
|
|
213
221
|
x1: isVertical && groupSeries ? (d) => s.value ?? s.key : undefined,
|
|
214
222
|
y1: !isVertical && groupSeries ? (d) => s.value ?? s.key : undefined,
|
|
215
|
-
rounded: isStackLayout && i !==
|
|
223
|
+
rounded: isStackLayout && i !== visibleSeries.length - 1 ? 'none' : 'edge',
|
|
216
224
|
radius: 4,
|
|
217
225
|
strokeWidth: 1,
|
|
218
226
|
insets: stackInsets,
|
|
219
227
|
fill: s.color,
|
|
228
|
+
class: cls(
|
|
229
|
+
'transition-opacity',
|
|
230
|
+
highlightSeriesKey && highlightSeriesKey !== s.key && 'opacity-10'
|
|
231
|
+
),
|
|
220
232
|
onBarClick: (e) => onBarClick({ data: e.data, series: s }),
|
|
221
233
|
...props.bars,
|
|
222
234
|
...s.props,
|
|
@@ -224,14 +236,30 @@
|
|
|
224
236
|
|
|
225
237
|
return barsProps;
|
|
226
238
|
}
|
|
239
|
+
|
|
240
|
+
const selectedSeries = selectionStore();
|
|
241
|
+
$: visibleSeries = series.filter((s) => {
|
|
242
|
+
return (
|
|
243
|
+
// @ts-expect-error
|
|
244
|
+
$selectedSeries.selected.length === 0 || $selectedSeries.isSelected(s.key)
|
|
245
|
+
// || highlightSeriesKey == s.key
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (profile) {
|
|
250
|
+
console.time('BarChart render');
|
|
251
|
+
onMount(() => {
|
|
252
|
+
console.timeEnd('BarChart render');
|
|
253
|
+
});
|
|
254
|
+
}
|
|
227
255
|
</script>
|
|
228
256
|
|
|
229
257
|
<Chart
|
|
230
258
|
data={chartData}
|
|
231
259
|
x={x ??
|
|
232
260
|
(stackSeries
|
|
233
|
-
? (d) =>
|
|
234
|
-
:
|
|
261
|
+
? (d) => visibleSeries.flatMap((s, i) => d.stackData[i])
|
|
262
|
+
: visibleSeries.map((s) => s.value ?? s.key))}
|
|
235
263
|
{xScale}
|
|
236
264
|
{xBaseline}
|
|
237
265
|
xNice={orientation === 'horizontal'}
|
|
@@ -240,8 +268,8 @@
|
|
|
240
268
|
{x1Range}
|
|
241
269
|
y={y ??
|
|
242
270
|
(stackSeries
|
|
243
|
-
? (d) =>
|
|
244
|
-
:
|
|
271
|
+
? (d) => visibleSeries.flatMap((s, i) => d.stackData[i])
|
|
272
|
+
: visibleSeries.map((s) => s.value ?? s.key))}
|
|
245
273
|
{yScale}
|
|
246
274
|
{yBaseline}
|
|
247
275
|
yNice={orientation === 'vertical'}
|
|
@@ -294,7 +322,7 @@
|
|
|
294
322
|
<slot name="belowMarks" {...slotProps} />
|
|
295
323
|
|
|
296
324
|
<slot name="marks" {...slotProps}>
|
|
297
|
-
{#each
|
|
325
|
+
{#each visibleSeries as s, i (s.key)}
|
|
298
326
|
<Bars {...getBarsProps(s, i)} />
|
|
299
327
|
{/each}
|
|
300
328
|
</slot>
|
|
@@ -359,11 +387,21 @@
|
|
|
359
387
|
scale={isDefaultSeries
|
|
360
388
|
? undefined
|
|
361
389
|
: scaleOrdinal(
|
|
362
|
-
series.map((s) => s.
|
|
390
|
+
series.map((s) => s.key),
|
|
363
391
|
series.map((s) => s.color)
|
|
364
392
|
)}
|
|
393
|
+
tickFormat={(key) => series.find((s) => s.key === key)?.label ?? key}
|
|
365
394
|
placement="bottom"
|
|
366
395
|
variant="swatches"
|
|
396
|
+
onClick={(item) => $selectedSeries.toggleSelected(item.value)}
|
|
397
|
+
onPointerEnter={(item) => (highlightSeriesKey = item.value)}
|
|
398
|
+
onPointerLeave={(item) => (highlightSeriesKey = null)}
|
|
399
|
+
classes={{
|
|
400
|
+
item: (item) =>
|
|
401
|
+
visibleSeries.length && !visibleSeries.some((s) => s.key === item.value)
|
|
402
|
+
? 'opacity-50'
|
|
403
|
+
: '',
|
|
404
|
+
}}
|
|
367
405
|
{...props.legend}
|
|
368
406
|
{...typeof legend === 'object' ? legend : null}
|
|
369
407
|
/>
|
|
@@ -377,7 +415,7 @@
|
|
|
377
415
|
>
|
|
378
416
|
<Tooltip.List {...props.tooltip?.list}>
|
|
379
417
|
<!-- Reverse series order so tooltip items match stacks -->
|
|
380
|
-
{@const seriesItems = stackSeries ? [...
|
|
418
|
+
{@const seriesItems = stackSeries ? [...visibleSeries].reverse() : visibleSeries}
|
|
381
419
|
{#each seriesItems as s}
|
|
382
420
|
{@const seriesTooltipData = s.data ? findRelatedData(s.data, data, x) : data}
|
|
383
421
|
{@const valueAccessor = accessor(s.value ?? (s.data ? asAny(y) : s.key))}
|
|
@@ -391,12 +429,12 @@
|
|
|
391
429
|
/>
|
|
392
430
|
{/each}
|
|
393
431
|
|
|
394
|
-
{#if stackSeries || groupSeries}
|
|
432
|
+
{#if (stackSeries || groupSeries) && visibleSeries.length > 1}
|
|
395
433
|
<Tooltip.Separator {...props.tooltip?.separator} />
|
|
396
434
|
|
|
397
435
|
<Tooltip.Item
|
|
398
436
|
label="total"
|
|
399
|
-
value={sum(
|
|
437
|
+
value={sum(visibleSeries, (s) => {
|
|
400
438
|
const seriesTooltipData = s.data ? findRelatedData(s.data, data, x) : data;
|
|
401
439
|
const valueAccessor = accessor(s.value ?? (s.data ? asAny(y) : s.key));
|
|
402
440
|
return valueAccessor(seriesTooltipData);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script lang="ts" generics="TData">
|
|
2
|
-
import { type ComponentProps } from 'svelte';
|
|
2
|
+
import { onMount, type ComponentProps } from 'svelte';
|
|
3
3
|
import { scaleLinear, scaleOrdinal, scaleTime } from 'd3-scale';
|
|
4
4
|
import { format } from '@layerstack/utils';
|
|
5
|
+
import { cls } from '@layerstack/tailwind';
|
|
6
|
+
import { selectionStore } from '@layerstack/svelte-stores';
|
|
5
7
|
|
|
6
8
|
import Axis from '../Axis.svelte';
|
|
7
9
|
import Canvas from '../layout/Canvas.svelte';
|
|
@@ -31,6 +33,7 @@
|
|
|
31
33
|
labels?: typeof labels;
|
|
32
34
|
legend?: typeof legend;
|
|
33
35
|
points?: typeof points;
|
|
36
|
+
profile?: typeof profile;
|
|
34
37
|
props?: typeof props;
|
|
35
38
|
rule?: typeof rule;
|
|
36
39
|
series?: typeof series;
|
|
@@ -94,6 +97,9 @@
|
|
|
94
97
|
|
|
95
98
|
export let renderContext: 'svg' | 'canvas' = 'svg';
|
|
96
99
|
|
|
100
|
+
/** Log initial render performance using `console.time` */
|
|
101
|
+
export let profile = false;
|
|
102
|
+
|
|
97
103
|
$: allSeriesData = series
|
|
98
104
|
.flatMap((s) => s.data?.map((d) => ({ seriesKey: s.key, ...d })))
|
|
99
105
|
.filter((d) => d) as Array<TData & { stackData?: any }>;
|
|
@@ -106,11 +112,16 @@
|
|
|
106
112
|
$: xScale =
|
|
107
113
|
$$props.xScale ?? (accessor(x)(chartData[0]) instanceof Date ? scaleTime() : scaleLinear());
|
|
108
114
|
|
|
115
|
+
let highlightSeriesKey: (typeof series)[number]['key'] | null = null;
|
|
116
|
+
|
|
109
117
|
function getSplineProps(s: (typeof series)[number], i: number) {
|
|
110
118
|
const splineProps: ComponentProps<Spline> = {
|
|
111
119
|
data: s.data,
|
|
112
120
|
y: s.value ?? (s.data ? undefined : s.key),
|
|
113
|
-
class:
|
|
121
|
+
class: cls(
|
|
122
|
+
'stroke-2 transition-opacity',
|
|
123
|
+
highlightSeriesKey && highlightSeriesKey !== s.key && 'opacity-10'
|
|
124
|
+
),
|
|
114
125
|
stroke: s.color,
|
|
115
126
|
...props.spline,
|
|
116
127
|
...s.props,
|
|
@@ -118,6 +129,22 @@
|
|
|
118
129
|
|
|
119
130
|
return splineProps;
|
|
120
131
|
}
|
|
132
|
+
|
|
133
|
+
const selectedSeries = selectionStore();
|
|
134
|
+
$: visibleSeries = series.filter((s) => {
|
|
135
|
+
return (
|
|
136
|
+
// @ts-expect-error
|
|
137
|
+
$selectedSeries.selected.length === 0 || $selectedSeries.isSelected(s.key)
|
|
138
|
+
// || highlightSeriesKey == s.key
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (profile) {
|
|
143
|
+
console.time('LineChart render');
|
|
144
|
+
onMount(() => {
|
|
145
|
+
console.timeEnd('LineChart render');
|
|
146
|
+
});
|
|
147
|
+
}
|
|
121
148
|
</script>
|
|
122
149
|
|
|
123
150
|
<Chart
|
|
@@ -167,7 +194,7 @@
|
|
|
167
194
|
<slot name="belowMarks" {...slotProps} />
|
|
168
195
|
|
|
169
196
|
<slot name="marks" {...slotProps}>
|
|
170
|
-
{#each
|
|
197
|
+
{#each visibleSeries as s, i (s.key)}
|
|
171
198
|
<Spline {...getSplineProps(s, i)} />
|
|
172
199
|
{/each}
|
|
173
200
|
</slot>
|
|
@@ -201,7 +228,7 @@
|
|
|
201
228
|
</slot>
|
|
202
229
|
|
|
203
230
|
{#if points}
|
|
204
|
-
{#each
|
|
231
|
+
{#each visibleSeries as s}
|
|
205
232
|
<Points
|
|
206
233
|
data={s.data}
|
|
207
234
|
fill={s.color}
|
|
@@ -217,7 +244,7 @@
|
|
|
217
244
|
{/if}
|
|
218
245
|
|
|
219
246
|
<slot name="highlight" {...slotProps}>
|
|
220
|
-
{#each
|
|
247
|
+
{#each visibleSeries as s, i (s.key)}
|
|
221
248
|
{@const seriesTooltipData =
|
|
222
249
|
s.data && tooltip.data ? findRelatedData(s.data, tooltip.data, x) : null}
|
|
223
250
|
<Highlight
|
|
@@ -226,6 +253,8 @@
|
|
|
226
253
|
points={{ fill: s.color }}
|
|
227
254
|
lines={i === 0}
|
|
228
255
|
onPointClick={(e) => onPointClick({ ...e, series: s })}
|
|
256
|
+
onPointEnter={() => (highlightSeriesKey = s.key)}
|
|
257
|
+
onPointLeave={() => (highlightSeriesKey = null)}
|
|
229
258
|
{...props.highlight}
|
|
230
259
|
/>
|
|
231
260
|
{/each}
|
|
@@ -238,11 +267,21 @@
|
|
|
238
267
|
scale={isDefaultSeries
|
|
239
268
|
? undefined
|
|
240
269
|
: scaleOrdinal(
|
|
241
|
-
series.map((s) => s.
|
|
270
|
+
series.map((s) => s.key),
|
|
242
271
|
series.map((s) => s.color)
|
|
243
272
|
)}
|
|
273
|
+
tickFormat={(key) => series.find((s) => s.key === key)?.label ?? key}
|
|
244
274
|
placement="bottom"
|
|
245
275
|
variant="swatches"
|
|
276
|
+
onClick={(item) => $selectedSeries.toggleSelected(item.value)}
|
|
277
|
+
onPointerEnter={(item) => (highlightSeriesKey = item.value)}
|
|
278
|
+
onPointerLeave={(item) => (highlightSeriesKey = null)}
|
|
279
|
+
classes={{
|
|
280
|
+
item: (item) =>
|
|
281
|
+
visibleSeries.length && !visibleSeries.some((s) => s.key === item.value)
|
|
282
|
+
? 'opacity-50'
|
|
283
|
+
: '',
|
|
284
|
+
}}
|
|
246
285
|
{...props.legend}
|
|
247
286
|
{...typeof legend === 'object' ? legend : null}
|
|
248
287
|
/>
|
|
@@ -253,7 +292,7 @@
|
|
|
253
292
|
<Tooltip.Root {...props.tooltip?.root} let:data>
|
|
254
293
|
<Tooltip.Header {...props.tooltip?.header}>{format(x(data))}</Tooltip.Header>
|
|
255
294
|
<Tooltip.List {...props.tooltip?.list}>
|
|
256
|
-
{#each
|
|
295
|
+
{#each visibleSeries as s}
|
|
257
296
|
{@const seriesTooltipData = s.data ? findRelatedData(s.data, data, x) : data}
|
|
258
297
|
{@const valueAccessor = accessor(s.value ?? (s.data ? asAny(y) : s.key))}
|
|
259
298
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script lang="ts" generics="TData">
|
|
2
|
-
import { type ComponentProps } from 'svelte';
|
|
2
|
+
import { onMount, type ComponentProps } from 'svelte';
|
|
3
3
|
import { sum } from 'd3-array';
|
|
4
4
|
import { format } from '@layerstack/utils';
|
|
5
|
+
import { cls } from '@layerstack/tailwind';
|
|
6
|
+
import { selectionStore } from '@layerstack/svelte-stores';
|
|
5
7
|
|
|
6
8
|
import Arc from '../Arc.svelte';
|
|
7
9
|
import Canvas from '../layout/Canvas.svelte';
|
|
@@ -27,6 +29,7 @@
|
|
|
27
29
|
padAngle?: typeof padAngle;
|
|
28
30
|
center?: typeof center;
|
|
29
31
|
placement?: typeof placement;
|
|
32
|
+
profile?: typeof profile;
|
|
30
33
|
props?: typeof props;
|
|
31
34
|
range?: typeof range;
|
|
32
35
|
series?: typeof series;
|
|
@@ -118,6 +121,9 @@
|
|
|
118
121
|
|
|
119
122
|
export let renderContext: 'svg' | 'canvas' = 'svg';
|
|
120
123
|
|
|
124
|
+
/** Log initial render performance using `console.time` */
|
|
125
|
+
export let profile = false;
|
|
126
|
+
|
|
121
127
|
$: allSeriesData = series
|
|
122
128
|
.flatMap((s) => s.data?.map((d) => ({ seriesKey: s.key, ...d })))
|
|
123
129
|
.filter((d) => d) as Array<TData>;
|
|
@@ -125,13 +131,33 @@
|
|
|
125
131
|
$: chartData = (allSeriesData.length ? allSeriesData : chartDataArray(data)) as Array<TData>;
|
|
126
132
|
|
|
127
133
|
$: seriesColors = series.map((s) => s.color).filter((d) => d != null);
|
|
134
|
+
|
|
135
|
+
let highlightKey: (typeof series)[number]['key'] | null = null;
|
|
136
|
+
|
|
137
|
+
const selectedKeys = selectionStore();
|
|
138
|
+
$: visibleData = chartData.filter((d) => {
|
|
139
|
+
const dataKey = keyAccessor(d);
|
|
140
|
+
return (
|
|
141
|
+
// @ts-expect-error
|
|
142
|
+
$selectedKeys.selected.length === 0 || $selectedKeys.isSelected(dataKey)
|
|
143
|
+
// || highlightKey == dataKey
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (profile) {
|
|
148
|
+
console.time('PieChart render');
|
|
149
|
+
onMount(() => {
|
|
150
|
+
console.timeEnd('PieChart render');
|
|
151
|
+
});
|
|
152
|
+
}
|
|
128
153
|
</script>
|
|
129
154
|
|
|
130
155
|
<Chart
|
|
131
|
-
data={
|
|
156
|
+
data={visibleData}
|
|
132
157
|
x={value}
|
|
133
158
|
y={key}
|
|
134
159
|
c={key}
|
|
160
|
+
cDomain={chartData.map(keyAccessor)}
|
|
135
161
|
cRange={seriesColors.length
|
|
136
162
|
? seriesColors
|
|
137
163
|
: [
|
|
@@ -205,6 +231,10 @@
|
|
|
205
231
|
// Workaround for `tooltip={{ mode: 'manual' }}
|
|
206
232
|
onTooltipClick({ data: d });
|
|
207
233
|
}}
|
|
234
|
+
class={cls(
|
|
235
|
+
'transition-opacity',
|
|
236
|
+
highlightKey && highlightKey !== keyAccessor(d) && 'opacity-50'
|
|
237
|
+
)}
|
|
208
238
|
{...props.arc}
|
|
209
239
|
{...s.props}
|
|
210
240
|
/>
|
|
@@ -235,6 +265,10 @@
|
|
|
235
265
|
// Workaround for `tooltip={{ mode: 'manual' }}
|
|
236
266
|
onTooltipClick({ data: arc.data });
|
|
237
267
|
}}
|
|
268
|
+
class={cls(
|
|
269
|
+
'transition-opacity',
|
|
270
|
+
highlightKey && highlightKey !== keyAccessor(arc.data) && 'opacity-50'
|
|
271
|
+
)}
|
|
238
272
|
{...props.arc}
|
|
239
273
|
{...s.props}
|
|
240
274
|
/>
|
|
@@ -257,6 +291,15 @@
|
|
|
257
291
|
}}
|
|
258
292
|
placement="bottom"
|
|
259
293
|
variant="swatches"
|
|
294
|
+
onClick={(item) => $selectedKeys.toggleSelected(item.value)}
|
|
295
|
+
onPointerEnter={(item) => (highlightKey = item.value)}
|
|
296
|
+
onPointerLeave={(item) => (highlightKey = null)}
|
|
297
|
+
classes={{
|
|
298
|
+
item: (item) =>
|
|
299
|
+
visibleData.length && !visibleData.some((d) => keyAccessor(d) === item.value)
|
|
300
|
+
? 'opacity-50'
|
|
301
|
+
: '',
|
|
302
|
+
}}
|
|
260
303
|
{...props.legend}
|
|
261
304
|
{...typeof legend === 'object' ? legend : null}
|
|
262
305
|
/>
|
|
@@ -210,7 +210,9 @@ declare class __sveltets_Render<TData> {
|
|
|
210
210
|
tickLength?: number | undefined;
|
|
211
211
|
placement?: ("center" | "bottom" | "left" | "right" | "top" | "top-left" | "top-right" | "bottom-left" | "bottom-right") | undefined;
|
|
212
212
|
orientation?: "horizontal" | "vertical" | undefined;
|
|
213
|
-
onClick?: ((
|
|
213
|
+
onClick?: ((item: any) => any) | undefined | undefined;
|
|
214
|
+
onPointerEnter?: ((item: any) => any) | undefined | undefined;
|
|
215
|
+
onPointerLeave?: ((item: any) => any) | undefined | undefined;
|
|
214
216
|
variant?: "ramp" | "swatches" | undefined;
|
|
215
217
|
classes?: {
|
|
216
218
|
root?: string;
|
|
@@ -219,6 +221,7 @@ declare class __sveltets_Render<TData> {
|
|
|
219
221
|
tick?: string;
|
|
220
222
|
swatches?: string;
|
|
221
223
|
swatch?: string;
|
|
224
|
+
item?: (item: any) => string;
|
|
222
225
|
} | undefined;
|
|
223
226
|
};
|
|
224
227
|
maxValue?: number | undefined;
|
|
@@ -226,6 +229,7 @@ declare class __sveltets_Render<TData> {
|
|
|
226
229
|
padAngle?: number;
|
|
227
230
|
center?: boolean;
|
|
228
231
|
placement?: "center" | "left" | "right";
|
|
232
|
+
profile?: boolean;
|
|
229
233
|
props?: {
|
|
230
234
|
pie?: Partial<ComponentProps<Pie>>;
|
|
231
235
|
group?: Partial<ComponentProps<Group>>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script lang="ts" generics="TData">
|
|
2
|
-
import { type ComponentProps } from 'svelte';
|
|
2
|
+
import { onMount, type ComponentProps } from 'svelte';
|
|
3
3
|
import { scaleLinear, scaleOrdinal, scaleTime } from 'd3-scale';
|
|
4
4
|
import { format } from '@layerstack/utils';
|
|
5
|
+
import { cls } from '@layerstack/tailwind';
|
|
6
|
+
import { selectionStore } from '@layerstack/svelte-stores';
|
|
5
7
|
|
|
6
8
|
import Axis from '../Axis.svelte';
|
|
7
9
|
import Canvas from '../layout/Canvas.svelte';
|
|
@@ -27,6 +29,7 @@
|
|
|
27
29
|
grid?: typeof grid;
|
|
28
30
|
labels?: typeof labels;
|
|
29
31
|
legend?: typeof legend;
|
|
32
|
+
profile?: typeof profile;
|
|
30
33
|
props?: typeof props;
|
|
31
34
|
series?: typeof series;
|
|
32
35
|
renderContext?: typeof renderContext;
|
|
@@ -75,6 +78,9 @@
|
|
|
75
78
|
|
|
76
79
|
export let renderContext: 'svg' | 'canvas' = 'svg';
|
|
77
80
|
|
|
81
|
+
/** Log initial render performance using `console.time` */
|
|
82
|
+
export let profile = false;
|
|
83
|
+
|
|
78
84
|
// Default xScale based on first data's `x` value
|
|
79
85
|
$: xScale =
|
|
80
86
|
$$props.xScale ??
|
|
@@ -85,22 +91,44 @@
|
|
|
85
91
|
$$props.yScale ??
|
|
86
92
|
(accessor(y)(chartDataArray(data)[0]) instanceof Date ? scaleTime() : scaleLinear());
|
|
87
93
|
|
|
88
|
-
$: chartData =
|
|
94
|
+
$: chartData = visibleSeries
|
|
89
95
|
.flatMap((s) => s.data?.map((d) => ({ seriesKey: s.key, ...d })))
|
|
90
96
|
.filter((d) => d) as Array<TData>;
|
|
91
97
|
|
|
98
|
+
let highlightSeriesKey: (typeof series)[number]['key'] | null = null;
|
|
99
|
+
|
|
92
100
|
function getPointsProps(s: (typeof series)[number], i: number) {
|
|
93
101
|
const pointsProps: ComponentProps<Points> = {
|
|
94
102
|
data: s.data,
|
|
95
103
|
stroke: s.color,
|
|
96
104
|
fill: s.color,
|
|
97
105
|
fillOpacity: 0.3,
|
|
106
|
+
class: cls(
|
|
107
|
+
'transition-opacity',
|
|
108
|
+
highlightSeriesKey && highlightSeriesKey !== s.key && 'opacity-10'
|
|
109
|
+
),
|
|
98
110
|
...props.points,
|
|
99
111
|
...s.props,
|
|
100
112
|
};
|
|
101
113
|
|
|
102
114
|
return pointsProps;
|
|
103
115
|
}
|
|
116
|
+
|
|
117
|
+
const selectedSeries = selectionStore();
|
|
118
|
+
$: visibleSeries = series.filter((s) => {
|
|
119
|
+
return (
|
|
120
|
+
// @ts-expect-error
|
|
121
|
+
$selectedSeries.selected.length === 0 || $selectedSeries.isSelected(s.key)
|
|
122
|
+
// || highlightSeriesKey == s.key
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (profile) {
|
|
127
|
+
console.time('ScatterChart render');
|
|
128
|
+
onMount(() => {
|
|
129
|
+
console.timeEnd('ScatterChart render');
|
|
130
|
+
});
|
|
131
|
+
}
|
|
104
132
|
</script>
|
|
105
133
|
|
|
106
134
|
<Chart
|
|
@@ -142,7 +170,7 @@
|
|
|
142
170
|
<slot name="belowMarks" {...slotProps} />
|
|
143
171
|
|
|
144
172
|
<slot name="marks" {...slotProps}>
|
|
145
|
-
{#each
|
|
173
|
+
{#each visibleSeries as s, i (s.key)}
|
|
146
174
|
<Points {...getPointsProps(s, i)} />
|
|
147
175
|
{/each}
|
|
148
176
|
</slot>
|
|
@@ -194,11 +222,21 @@
|
|
|
194
222
|
scale={isDefaultSeries
|
|
195
223
|
? undefined
|
|
196
224
|
: scaleOrdinal(
|
|
197
|
-
series.map((s) => s.
|
|
225
|
+
series.map((s) => s.key),
|
|
198
226
|
series.map((s) => s.color)
|
|
199
227
|
)}
|
|
228
|
+
tickFormat={(key) => series.find((s) => s.key === key)?.label ?? key}
|
|
200
229
|
placement="bottom"
|
|
201
230
|
variant="swatches"
|
|
231
|
+
onClick={(item) => $selectedSeries.toggleSelected(item.value)}
|
|
232
|
+
onPointerEnter={(item) => (highlightSeriesKey = item.value)}
|
|
233
|
+
onPointerLeave={(item) => (highlightSeriesKey = null)}
|
|
234
|
+
classes={{
|
|
235
|
+
item: (item) =>
|
|
236
|
+
visibleSeries.length && !visibleSeries.some((s) => s.key === item.value)
|
|
237
|
+
? 'opacity-50'
|
|
238
|
+
: '',
|
|
239
|
+
}}
|
|
202
240
|
{...props.legend}
|
|
203
241
|
{...typeof legend === 'object' ? legend : null}
|
|
204
242
|
/>
|