layerchart 0.73.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/Bar.svelte +3 -3
- package/dist/components/Bars.svelte +3 -2
- package/dist/components/Circle.svelte +1 -0
- package/dist/components/Highlight.svelte +6 -1
- 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 +83 -18
- 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/dist/utils/rect.d.ts +18 -1
- package/dist/utils/rect.js +25 -17
- 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;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import Rect from './Rect.svelte';
|
|
6
6
|
import Spline from './Spline.svelte';
|
|
7
7
|
|
|
8
|
-
import { createDimensionGetter } from '../utils/rect.js';
|
|
8
|
+
import { createDimensionGetter, type Insets } from '../utils/rect.js';
|
|
9
9
|
import { isScaleBand } from '../utils/scales.js';
|
|
10
10
|
import { accessor, type Accessor } from '../utils/common.js';
|
|
11
11
|
import { greatestAbs } from '@layerstack/utils';
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
| 'bottom-left'
|
|
55
55
|
| 'bottom-right' = 'all';
|
|
56
56
|
|
|
57
|
-
export let
|
|
57
|
+
export let insets: Insets | undefined = undefined;
|
|
58
58
|
|
|
59
59
|
export let spring: ComponentProps<Rect>['spring'] = undefined;
|
|
60
60
|
export let tweened: ComponentProps<Rect>['tweened'] = undefined;
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
y,
|
|
67
67
|
x1,
|
|
68
68
|
y1,
|
|
69
|
-
|
|
69
|
+
insets,
|
|
70
70
|
});
|
|
71
71
|
$: dimensions = $getDimensions(bar) ?? { x: 0, y: 0, width: 0, height: 0 };
|
|
72
72
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import Bar from './Bar.svelte';
|
|
6
6
|
import Rect from './Rect.svelte';
|
|
7
7
|
import { chartDataArray, type Accessor } from '../utils/common.js';
|
|
8
|
+
import type { Insets } from '../index.js/utils/rect.js';
|
|
8
9
|
|
|
9
10
|
const { data: contextData, cGet, config } = chartContext();
|
|
10
11
|
|
|
@@ -39,7 +40,7 @@
|
|
|
39
40
|
export let fill: string | undefined = undefined;
|
|
40
41
|
|
|
41
42
|
/** Inset the rect for amount of padding. Useful with multiple bars (bullet, overlap, etc) */
|
|
42
|
-
export let
|
|
43
|
+
export let insets: Insets | undefined = undefined;
|
|
43
44
|
|
|
44
45
|
/** Define unique value for {#each} `(key)` expressions to improve transitions. `index` position used by default */
|
|
45
46
|
export let key: (d: any, index: number) => any = (d, i) => i;
|
|
@@ -66,7 +67,7 @@
|
|
|
66
67
|
{stroke}
|
|
67
68
|
{strokeWidth}
|
|
68
69
|
{radius}
|
|
69
|
-
{
|
|
70
|
+
{insets}
|
|
70
71
|
{spring}
|
|
71
72
|
{tweened}
|
|
72
73
|
on:click={() => onBarClick({ data: d })}
|
|
@@ -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);
|
|
@@ -361,7 +364,7 @@
|
|
|
361
364
|
spring={motion}
|
|
362
365
|
x={typeof bar === 'object' ? bar.x : undefined}
|
|
363
366
|
y={typeof bar === 'object' ? bar.y : undefined}
|
|
364
|
-
|
|
367
|
+
insets={typeof bar === 'object' ? bar.insets : undefined}
|
|
365
368
|
stroke={typeof bar === 'object' ? bar.stroke : undefined}
|
|
366
369
|
strokeWidth={typeof bar === 'object' ? bar.strokeWidth : undefined}
|
|
367
370
|
radius={typeof bar === 'object' ? bar.radius : undefined}
|
|
@@ -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';
|
|
@@ -25,6 +27,7 @@
|
|
|
25
27
|
type Accessor,
|
|
26
28
|
} from '../../utils/common.js';
|
|
27
29
|
import { asAny } from '../../utils/types.js';
|
|
30
|
+
import type { Insets } from '../../index.js/utils/rect.js';
|
|
28
31
|
|
|
29
32
|
type ChartProps = ComponentProps<Chart<TData>>;
|
|
30
33
|
|
|
@@ -33,9 +36,11 @@
|
|
|
33
36
|
grid?: typeof grid;
|
|
34
37
|
bandPadding?: typeof bandPadding;
|
|
35
38
|
groupPadding?: typeof groupPadding;
|
|
39
|
+
stackPadding?: typeof stackPadding;
|
|
36
40
|
labels?: typeof labels;
|
|
37
41
|
legend?: typeof legend;
|
|
38
42
|
orientation?: typeof orientation;
|
|
43
|
+
profile?: typeof profile;
|
|
39
44
|
props?: typeof props;
|
|
40
45
|
rule?: typeof rule;
|
|
41
46
|
series?: typeof series;
|
|
@@ -84,6 +89,8 @@
|
|
|
84
89
|
export let bandPadding = 0.4;
|
|
85
90
|
/** Padding between group/series items when using 'seriesLayout="group"', applied to scaleBand().padding() */
|
|
86
91
|
export let groupPadding = 0;
|
|
92
|
+
/** Padding between series items within bars when using 'seriesLayout="stack"' */
|
|
93
|
+
export let stackPadding = 0;
|
|
87
94
|
|
|
88
95
|
/** Event dispatched with current tooltip data */
|
|
89
96
|
export let onTooltipClick: (e: { data: any }) => void = () => {};
|
|
@@ -109,11 +116,11 @@
|
|
|
109
116
|
$: if (seriesLayout === 'group') {
|
|
110
117
|
if (isVertical) {
|
|
111
118
|
x1Scale = scaleBand().padding(groupPadding);
|
|
112
|
-
x1Domain =
|
|
119
|
+
x1Domain = visibleSeries.map((s) => s.key);
|
|
113
120
|
x1Range = ({ xScale }) => [0, xScale.bandwidth?.()];
|
|
114
121
|
} else {
|
|
115
122
|
y1Scale = scaleBand().padding(groupPadding);
|
|
116
|
-
y1Domain =
|
|
123
|
+
y1Domain = visibleSeries.map((s) => s.key);
|
|
117
124
|
y1Range = ({ yScale }) => [0, yScale.bandwidth?.()];
|
|
118
125
|
}
|
|
119
126
|
}
|
|
@@ -138,7 +145,10 @@
|
|
|
138
145
|
|
|
139
146
|
export let renderContext: 'svg' | 'canvas' = 'svg';
|
|
140
147
|
|
|
141
|
-
|
|
148
|
+
/** Log initial render performance using `console.time` */
|
|
149
|
+
export let profile = false;
|
|
150
|
+
|
|
151
|
+
$: allSeriesData = visibleSeries
|
|
142
152
|
.flatMap((s) =>
|
|
143
153
|
s.data?.map((d) => {
|
|
144
154
|
return { seriesKey: s.key, ...d };
|
|
@@ -151,7 +161,7 @@
|
|
|
151
161
|
>;
|
|
152
162
|
|
|
153
163
|
$: if (stackSeries) {
|
|
154
|
-
const seriesKeys =
|
|
164
|
+
const seriesKeys = visibleSeries.map((s) => s.key);
|
|
155
165
|
// const stackData = stack().keys(seriesKeys)(chartDataArray(data)) as any[];
|
|
156
166
|
|
|
157
167
|
const offset =
|
|
@@ -176,20 +186,49 @@
|
|
|
176
186
|
});
|
|
177
187
|
}
|
|
178
188
|
|
|
189
|
+
let highlightSeriesKey: (typeof series)[number]['key'] | null = null;
|
|
190
|
+
|
|
179
191
|
function getBarsProps(s: (typeof series)[number], i: number) {
|
|
180
|
-
const
|
|
192
|
+
const isFirst = i == 0;
|
|
193
|
+
const isLast = i == visibleSeries.length - 1;
|
|
194
|
+
|
|
195
|
+
const isStackLayout = seriesLayout.startsWith('stack');
|
|
196
|
+
|
|
197
|
+
let stackInsets: Insets | undefined = undefined;
|
|
198
|
+
|
|
199
|
+
if (isStackLayout) {
|
|
200
|
+
const stackInset = stackPadding / 2;
|
|
201
|
+
if (isVertical) {
|
|
202
|
+
stackInsets = {
|
|
203
|
+
bottom: isFirst ? undefined : stackInset,
|
|
204
|
+
top: isLast ? undefined : stackInset,
|
|
205
|
+
};
|
|
206
|
+
} else {
|
|
207
|
+
stackInsets = {
|
|
208
|
+
left: isFirst ? undefined : stackInset,
|
|
209
|
+
right: isLast ? undefined : stackInset,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const valueAccessor = stackSeries
|
|
181
215
|
? (d: any) => d.stackData[i]
|
|
182
216
|
: (s.value ?? (s.data ? undefined : s.key));
|
|
183
217
|
const barsProps: ComponentProps<Bars> = {
|
|
184
218
|
data: s.data,
|
|
185
|
-
x: !isVertical ?
|
|
186
|
-
y: isVertical ?
|
|
219
|
+
x: !isVertical ? valueAccessor : undefined,
|
|
220
|
+
y: isVertical ? valueAccessor : undefined,
|
|
187
221
|
x1: isVertical && groupSeries ? (d) => s.value ?? s.key : undefined,
|
|
188
222
|
y1: !isVertical && groupSeries ? (d) => s.value ?? s.key : undefined,
|
|
189
|
-
rounded:
|
|
223
|
+
rounded: isStackLayout && i !== visibleSeries.length - 1 ? 'none' : 'edge',
|
|
190
224
|
radius: 4,
|
|
191
225
|
strokeWidth: 1,
|
|
226
|
+
insets: stackInsets,
|
|
192
227
|
fill: s.color,
|
|
228
|
+
class: cls(
|
|
229
|
+
'transition-opacity',
|
|
230
|
+
highlightSeriesKey && highlightSeriesKey !== s.key && 'opacity-10'
|
|
231
|
+
),
|
|
193
232
|
onBarClick: (e) => onBarClick({ data: e.data, series: s }),
|
|
194
233
|
...props.bars,
|
|
195
234
|
...s.props,
|
|
@@ -197,14 +236,30 @@
|
|
|
197
236
|
|
|
198
237
|
return barsProps;
|
|
199
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
|
+
}
|
|
200
255
|
</script>
|
|
201
256
|
|
|
202
257
|
<Chart
|
|
203
258
|
data={chartData}
|
|
204
259
|
x={x ??
|
|
205
260
|
(stackSeries
|
|
206
|
-
? (d) =>
|
|
207
|
-
:
|
|
261
|
+
? (d) => visibleSeries.flatMap((s, i) => d.stackData[i])
|
|
262
|
+
: visibleSeries.map((s) => s.value ?? s.key))}
|
|
208
263
|
{xScale}
|
|
209
264
|
{xBaseline}
|
|
210
265
|
xNice={orientation === 'horizontal'}
|
|
@@ -213,8 +268,8 @@
|
|
|
213
268
|
{x1Range}
|
|
214
269
|
y={y ??
|
|
215
270
|
(stackSeries
|
|
216
|
-
? (d) =>
|
|
217
|
-
:
|
|
271
|
+
? (d) => visibleSeries.flatMap((s, i) => d.stackData[i])
|
|
272
|
+
: visibleSeries.map((s) => s.value ?? s.key))}
|
|
218
273
|
{yScale}
|
|
219
274
|
{yBaseline}
|
|
220
275
|
yNice={orientation === 'vertical'}
|
|
@@ -267,7 +322,7 @@
|
|
|
267
322
|
<slot name="belowMarks" {...slotProps} />
|
|
268
323
|
|
|
269
324
|
<slot name="marks" {...slotProps}>
|
|
270
|
-
{#each
|
|
325
|
+
{#each visibleSeries as s, i (s.key)}
|
|
271
326
|
<Bars {...getBarsProps(s, i)} />
|
|
272
327
|
{/each}
|
|
273
328
|
</slot>
|
|
@@ -332,11 +387,21 @@
|
|
|
332
387
|
scale={isDefaultSeries
|
|
333
388
|
? undefined
|
|
334
389
|
: scaleOrdinal(
|
|
335
|
-
series.map((s) => s.
|
|
390
|
+
series.map((s) => s.key),
|
|
336
391
|
series.map((s) => s.color)
|
|
337
392
|
)}
|
|
393
|
+
tickFormat={(key) => series.find((s) => s.key === key)?.label ?? key}
|
|
338
394
|
placement="bottom"
|
|
339
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
|
+
}}
|
|
340
405
|
{...props.legend}
|
|
341
406
|
{...typeof legend === 'object' ? legend : null}
|
|
342
407
|
/>
|
|
@@ -350,7 +415,7 @@
|
|
|
350
415
|
>
|
|
351
416
|
<Tooltip.List {...props.tooltip?.list}>
|
|
352
417
|
<!-- Reverse series order so tooltip items match stacks -->
|
|
353
|
-
{@const seriesItems = stackSeries ? [...
|
|
418
|
+
{@const seriesItems = stackSeries ? [...visibleSeries].reverse() : visibleSeries}
|
|
354
419
|
{#each seriesItems as s}
|
|
355
420
|
{@const seriesTooltipData = s.data ? findRelatedData(s.data, data, x) : data}
|
|
356
421
|
{@const valueAccessor = accessor(s.value ?? (s.data ? asAny(y) : s.key))}
|
|
@@ -364,12 +429,12 @@
|
|
|
364
429
|
/>
|
|
365
430
|
{/each}
|
|
366
431
|
|
|
367
|
-
{#if stackSeries || groupSeries}
|
|
432
|
+
{#if (stackSeries || groupSeries) && visibleSeries.length > 1}
|
|
368
433
|
<Tooltip.Separator {...props.tooltip?.separator} />
|
|
369
434
|
|
|
370
435
|
<Tooltip.Item
|
|
371
436
|
label="total"
|
|
372
|
-
value={sum(
|
|
437
|
+
value={sum(visibleSeries, (s) => {
|
|
373
438
|
const seriesTooltipData = s.data ? findRelatedData(s.data, data, x) : data;
|
|
374
439
|
const valueAccessor = accessor(s.value ?? (s.data ? asAny(y) : s.key));
|
|
375
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
|
/>
|
package/dist/utils/rect.d.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import type { ChartContext } from '../components/ChartContext.svelte';
|
|
2
2
|
import { type Accessor } from './common.js';
|
|
3
|
+
/** A set of inset distances, applied to a rectangle to shrink or expand the area represented by that rectangle. */
|
|
4
|
+
export type Insets = {
|
|
5
|
+
/** Applies an inset all sides of a rectangle: `left`, `right`, `bottom`, and `top` */
|
|
6
|
+
all?: number;
|
|
7
|
+
/** Applies an inset all horizontal sides of a rectangle: `left`, and `right`, overriding `all` */
|
|
8
|
+
x?: number;
|
|
9
|
+
/** Applies an inset all vertical sides of a rectangle: `top`, and `bottom`, overriding `all` */
|
|
10
|
+
y?: number;
|
|
11
|
+
/** Applies an inset the left side of a rectangle, overriding `x` */
|
|
12
|
+
left?: number;
|
|
13
|
+
/** Applies an inset the right side of a rectangle, overriding `x` */
|
|
14
|
+
right?: number;
|
|
15
|
+
/** Applies an inset the top side of a rectangle, overriding `y` */
|
|
16
|
+
top?: number;
|
|
17
|
+
/** Applies an inset the bottom side of a rectangle, overriding `y` */
|
|
18
|
+
bottom?: number;
|
|
19
|
+
};
|
|
3
20
|
type DimensionGetterOptions = {
|
|
4
21
|
/** Override `x` accessor from context */
|
|
5
22
|
x?: Accessor;
|
|
@@ -9,7 +26,7 @@ type DimensionGetterOptions = {
|
|
|
9
26
|
x1?: Accessor;
|
|
10
27
|
/** Override `y1` accessor from context */
|
|
11
28
|
y1?: Accessor;
|
|
12
|
-
|
|
29
|
+
insets?: Insets;
|
|
13
30
|
};
|
|
14
31
|
export declare function createDimensionGetter<TData>(context: ChartContext<TData>, options?: DimensionGetterOptions): import("svelte/store").Readable<(item: any) => {
|
|
15
32
|
x: any;
|
package/dist/utils/rect.js
CHANGED
|
@@ -4,8 +4,8 @@ import { isScaleBand } from './scales.js';
|
|
|
4
4
|
import { accessor } from './common.js';
|
|
5
5
|
export function createDimensionGetter(context, options) {
|
|
6
6
|
const { xScale, yScale, x: xAccessor, y: yAccessor, x1: x1Accessor, y1: y1Accessor, x1Scale, y1Scale, } = context;
|
|
7
|
-
const inset = options?.inset ?? 0;
|
|
8
7
|
return derived([xScale, x1Scale, yScale, y1Scale, xAccessor, yAccessor, x1Accessor, y1Accessor], ([$xScale, $x1Scale, $yScale, $y1Scale, $xAccessor, $yAccessor, $x1Accessor, $y1Accessor]) => {
|
|
8
|
+
const insets = resolveInsets(options?.insets);
|
|
9
9
|
// Use `xscale.domain()` instead of `$xDomain` to include `nice()` being applied
|
|
10
10
|
const [minXDomain, maxXDomain] = $xScale.domain();
|
|
11
11
|
const [minYDomain, maxYDomain] = $yScale.domain();
|
|
@@ -17,9 +17,11 @@ export function createDimensionGetter(context, options) {
|
|
|
17
17
|
return function getter(item) {
|
|
18
18
|
if (isScaleBand($yScale)) {
|
|
19
19
|
// Horizontal band
|
|
20
|
-
const y = firstValue($yScale(_y(item)) ?? 0) + ($y1Scale ? $y1Scale(_y1(item)) : 0) +
|
|
20
|
+
const y = firstValue($yScale(_y(item)) ?? 0) + ($y1Scale ? $y1Scale(_y1(item)) : 0) + insets.top;
|
|
21
21
|
const height = Math.max(0, $yScale.bandwidth
|
|
22
|
-
? ($y1Scale ? ($y1Scale.bandwidth?.() ?? 0) : $yScale.bandwidth()) -
|
|
22
|
+
? ($y1Scale ? ($y1Scale.bandwidth?.() ?? 0) : $yScale.bandwidth()) -
|
|
23
|
+
insets.bottom -
|
|
24
|
+
insets.top
|
|
23
25
|
: 0);
|
|
24
26
|
const xValue = _x(item);
|
|
25
27
|
let left = 0;
|
|
@@ -44,18 +46,17 @@ export function createDimensionGetter(context, options) {
|
|
|
44
46
|
left = xValue;
|
|
45
47
|
right = min([0, maxXDomain]);
|
|
46
48
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
width: $xScale(right) - $xScale(left),
|
|
51
|
-
height,
|
|
52
|
-
};
|
|
49
|
+
const x = $xScale(left) + insets.left;
|
|
50
|
+
const width = Math.max(0, $xScale(right) - $xScale(left) - insets.left - insets.right);
|
|
51
|
+
return { x, y, width, height };
|
|
53
52
|
}
|
|
54
53
|
else {
|
|
55
54
|
// Vertical band or linear
|
|
56
|
-
const x = firstValue($xScale(_x(item))) + ($x1Scale ? $x1Scale(_x1(item)) : 0) +
|
|
55
|
+
const x = firstValue($xScale(_x(item))) + ($x1Scale ? $x1Scale(_x1(item)) : 0) + insets.left;
|
|
57
56
|
const width = Math.max(0, $xScale.bandwidth
|
|
58
|
-
? ($x1Scale ? ($x1Scale.bandwidth?.() ?? 0) : $xScale.bandwidth()) -
|
|
57
|
+
? ($x1Scale ? ($x1Scale.bandwidth?.() ?? 0) : $xScale.bandwidth()) -
|
|
58
|
+
insets.left -
|
|
59
|
+
insets.right
|
|
59
60
|
: 0);
|
|
60
61
|
const yValue = _y(item);
|
|
61
62
|
let top = 0;
|
|
@@ -80,12 +81,9 @@ export function createDimensionGetter(context, options) {
|
|
|
80
81
|
top = min([0, maxYDomain]);
|
|
81
82
|
bottom = yValue;
|
|
82
83
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
width,
|
|
87
|
-
height: $yScale(bottom) - $yScale(top),
|
|
88
|
-
};
|
|
84
|
+
const y = $yScale(top) + insets.top;
|
|
85
|
+
const height = $yScale(bottom) - $yScale(top) - insets.bottom - insets.top;
|
|
86
|
+
return { x, y, width, height };
|
|
89
87
|
}
|
|
90
88
|
};
|
|
91
89
|
});
|
|
@@ -97,3 +95,13 @@ export function createDimensionGetter(context, options) {
|
|
|
97
95
|
export function firstValue(value) {
|
|
98
96
|
return Array.isArray(value) ? value[0] : value;
|
|
99
97
|
}
|
|
98
|
+
function resolveInsets(insets) {
|
|
99
|
+
const all = insets?.all ?? 0;
|
|
100
|
+
const x = insets?.x ?? all;
|
|
101
|
+
const y = insets?.y ?? all;
|
|
102
|
+
const left = insets?.left ?? x;
|
|
103
|
+
const right = insets?.right ?? x;
|
|
104
|
+
const top = insets?.top ?? y;
|
|
105
|
+
const bottom = insets?.bottom ?? y;
|
|
106
|
+
return { left, right, bottom, top };
|
|
107
|
+
}
|