svelteplot 0.4.2-pr-194.3 → 0.4.3-pr-198.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/marks/BarY.svelte
CHANGED
package/dist/marks/BoxX.svelte
CHANGED
|
@@ -12,12 +12,14 @@
|
|
|
12
12
|
import type BoxY from './BoxY.svelte';
|
|
13
13
|
|
|
14
14
|
let markProps: BoxXMarkProps = $props();
|
|
15
|
+
|
|
15
16
|
const DEFAULTS = {
|
|
16
17
|
tickMedian: true,
|
|
17
18
|
tickMinMax: false,
|
|
18
19
|
...getContext<PlotDefaults>('svelteplot/_defaults').box,
|
|
19
20
|
...getContext<PlotDefaults>('svelteplot/_defaults').boxX
|
|
20
21
|
};
|
|
22
|
+
|
|
21
23
|
const {
|
|
22
24
|
data = [{}],
|
|
23
25
|
bar,
|
|
@@ -27,80 +29,122 @@
|
|
|
27
29
|
dot,
|
|
28
30
|
x,
|
|
29
31
|
y,
|
|
32
|
+
fx,
|
|
33
|
+
fy,
|
|
34
|
+
fill,
|
|
35
|
+
stroke,
|
|
30
36
|
class: className = ''
|
|
31
37
|
}: BoxXMarkProps = $derived({
|
|
32
38
|
...DEFAULTS,
|
|
33
39
|
...markProps
|
|
34
40
|
});
|
|
35
41
|
|
|
36
|
-
const { data: grouped } = $derived(
|
|
42
|
+
const { data: grouped, ...groupChannels } = $derived(
|
|
37
43
|
groupY(
|
|
38
44
|
{
|
|
39
45
|
data: data.filter((d) => resolveChannel('x', d, { x, y }) != null),
|
|
40
46
|
x,
|
|
41
47
|
y,
|
|
42
48
|
x1: x,
|
|
43
|
-
x2: x
|
|
49
|
+
x2: x,
|
|
50
|
+
fx,
|
|
51
|
+
fy
|
|
44
52
|
},
|
|
45
53
|
{ x: 'median', x1: 'p25', x2: 'p75', fill: (rows) => rows }
|
|
46
54
|
)
|
|
47
55
|
);
|
|
48
56
|
|
|
57
|
+
const X = Symbol('x'),
|
|
58
|
+
Y = Symbol('y'),
|
|
59
|
+
FX = Symbol('fx'),
|
|
60
|
+
FY = Symbol('fy'),
|
|
61
|
+
P25 = Symbol('p25'),
|
|
62
|
+
P75 = Symbol('p75'),
|
|
63
|
+
MEDIAN = Symbol('median'),
|
|
64
|
+
MIN = Symbol('min'),
|
|
65
|
+
MAX = Symbol('max'),
|
|
66
|
+
OUTLIERS = Symbol('outliers');
|
|
67
|
+
|
|
68
|
+
const facets = $derived({
|
|
69
|
+
...(fx != null && { fx: FX }),
|
|
70
|
+
...(fy != null && { fy: FY })
|
|
71
|
+
});
|
|
72
|
+
|
|
49
73
|
const boxData = $derived(
|
|
50
74
|
grouped
|
|
51
75
|
.map((row) => {
|
|
52
|
-
const
|
|
76
|
+
const { x: median, x1: p25, x2: p75, fill: fill, y: ry } = groupChannels;
|
|
77
|
+
|
|
78
|
+
const iqr = row[p75] - row[p25];
|
|
53
79
|
const whisker = iqr * 1.5;
|
|
54
|
-
const lower = row
|
|
55
|
-
const upper = row
|
|
56
|
-
const data = row.
|
|
80
|
+
const lower = row[p25] - whisker;
|
|
81
|
+
const upper = row[p75] + whisker;
|
|
82
|
+
const data = row[fill].map((d) => ({
|
|
57
83
|
...d,
|
|
58
|
-
|
|
84
|
+
[X]: resolveChannel('x', d, { x, y })
|
|
59
85
|
}));
|
|
60
|
-
const outliers = data.filter((d) => d
|
|
86
|
+
const outliers = data.filter((d) => d[X] < lower || d[X] > upper);
|
|
61
87
|
const inside = data
|
|
62
|
-
.filter((d) => d
|
|
63
|
-
.sort((a, b) => a
|
|
88
|
+
.filter((d) => d[X] >= lower && d[X] <= upper)
|
|
89
|
+
.sort((a, b) => a[X] - b[X]);
|
|
90
|
+
|
|
64
91
|
// if (inside.length === 0) console.log('No data inside boxplot', data, row, lower, upper);
|
|
65
92
|
return {
|
|
66
|
-
[
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
93
|
+
...data[0],
|
|
94
|
+
[Y]: row[ry],
|
|
95
|
+
[P25]: row[p25],
|
|
96
|
+
[MEDIAN]: row[median],
|
|
97
|
+
[P75]: row[p75],
|
|
98
|
+
[MIN]: inside[0][X],
|
|
99
|
+
[MAX]: inside.at(-1)[X],
|
|
100
|
+
[FX]: resolveChannel('fx', data[0], { fx }, null),
|
|
101
|
+
[FY]: resolveChannel('fy', data[0], { fy }, null),
|
|
102
|
+
[OUTLIERS]: outliers
|
|
74
103
|
};
|
|
75
104
|
})
|
|
76
|
-
.sort((a, b) => b
|
|
105
|
+
.sort((a, b) => b[MEDIAN] - a[MEDIAN])
|
|
77
106
|
);
|
|
78
107
|
</script>
|
|
79
108
|
|
|
80
109
|
<GroupMultiple class="box-x {className || ''}" length={className ? 2 : grouped.length}>
|
|
81
|
-
<RuleY data={boxData} y=
|
|
82
|
-
<
|
|
110
|
+
<RuleY data={boxData} y={Y} x1={MIN} x2={P25} {stroke} {...rule || {}} {...facets} />
|
|
111
|
+
<RuleY data={boxData} y={Y} x1={P75} x2={MAX} {stroke} {...rule || {}} {...facets} />
|
|
112
|
+
<BarX data={boxData} y={Y} x1={P25} x2={P75} {fill} {stroke} {...facets} {...bar || {}} />
|
|
83
113
|
{#if tickMedian}
|
|
84
114
|
<TickX
|
|
85
115
|
data={boxData}
|
|
86
|
-
y=
|
|
87
|
-
x=
|
|
116
|
+
y={Y}
|
|
117
|
+
x={MEDIAN}
|
|
118
|
+
{...facets}
|
|
119
|
+
{stroke}
|
|
88
120
|
strokeWidth={2}
|
|
89
121
|
{...typeof tickMedian === 'object' ? tickMedian : {}} />
|
|
90
122
|
{/if}
|
|
91
123
|
{#if tickMinMax}
|
|
92
124
|
<TickX
|
|
93
125
|
data={boxData}
|
|
94
|
-
x=
|
|
95
|
-
y=
|
|
126
|
+
x={MIN}
|
|
127
|
+
y={Y}
|
|
128
|
+
{stroke}
|
|
129
|
+
{...facets}
|
|
96
130
|
inset="20%"
|
|
97
131
|
{...typeof tickMinMax === 'object' ? tickMinMax : {}} />
|
|
98
132
|
<TickX
|
|
99
133
|
data={boxData}
|
|
100
|
-
x=
|
|
101
|
-
y=
|
|
134
|
+
x={MAX}
|
|
135
|
+
y={Y}
|
|
136
|
+
{stroke}
|
|
137
|
+
{...facets}
|
|
102
138
|
inset="20%"
|
|
103
139
|
{...typeof tickMinMax === 'object' ? tickMinMax : {}} />
|
|
104
140
|
{/if}
|
|
105
|
-
<Dot
|
|
141
|
+
<Dot
|
|
142
|
+
data={boxData.map((d) => d[OUTLIERS]).flat()}
|
|
143
|
+
{x}
|
|
144
|
+
{y}
|
|
145
|
+
{fx}
|
|
146
|
+
{fy}
|
|
147
|
+
{fill}
|
|
148
|
+
{stroke}
|
|
149
|
+
{...dot || {}} />
|
|
106
150
|
</GroupMultiple>
|
package/dist/marks/BoxY.svelte
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
Creates a vertical box plot for visualizing data distribution with quartiles and outliers
|
|
3
3
|
-->
|
|
4
4
|
<script lang="ts" generics="Datum extends DataRecord">
|
|
5
|
-
interface BoxYMarkProps
|
|
5
|
+
interface BoxYMarkProps
|
|
6
|
+
extends Pick<BaseMarkProps<Datum>, 'class' | 'fill' | 'stroke' | 'fx' | 'fy'> {
|
|
6
7
|
data: Datum[];
|
|
7
8
|
x: ChannelAccessor;
|
|
8
9
|
y: ChannelAccessor;
|
|
@@ -28,95 +29,144 @@
|
|
|
28
29
|
dot: Record<string, ChannelAccessor<Datum>>;
|
|
29
30
|
}
|
|
30
31
|
import GroupMultiple from './helpers/GroupMultiple.svelte';
|
|
31
|
-
import type { BaseMarkProps, ChannelAccessor, DataRecord } from '../types/index.js';
|
|
32
32
|
import { groupX, BarY, TickY, RuleX, Dot } from '../index.js';
|
|
33
33
|
import { resolveChannel } from '../helpers/resolve.js';
|
|
34
|
-
import { getContext } from 'svelte';
|
|
34
|
+
import { getContext, type ComponentProps } from 'svelte';
|
|
35
35
|
import type { PlotDefaults } from '../types/index.js';
|
|
36
36
|
|
|
37
37
|
let markProps: BoxYMarkProps = $props();
|
|
38
|
+
|
|
38
39
|
const DEFAULTS = {
|
|
39
40
|
tickMedian: true,
|
|
40
41
|
tickMinMax: false,
|
|
41
42
|
...getContext<PlotDefaults>('svelteplot/_defaults').box,
|
|
42
43
|
...getContext<PlotDefaults>('svelteplot/_defaults').boxY
|
|
43
44
|
};
|
|
45
|
+
|
|
44
46
|
const {
|
|
45
|
-
data = [{}
|
|
46
|
-
class: className = '',
|
|
47
|
+
data = [{}],
|
|
47
48
|
bar,
|
|
48
49
|
rule,
|
|
49
50
|
tickMedian,
|
|
50
51
|
tickMinMax,
|
|
51
52
|
dot,
|
|
52
53
|
x,
|
|
53
|
-
y
|
|
54
|
+
y,
|
|
55
|
+
fx,
|
|
56
|
+
fy,
|
|
57
|
+
fill,
|
|
58
|
+
stroke,
|
|
59
|
+
class: className = ''
|
|
54
60
|
}: BoxYMarkProps = $derived({
|
|
55
61
|
...DEFAULTS,
|
|
56
62
|
...markProps
|
|
57
63
|
});
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
const { data: grouped, ...groupChannels } = $derived(
|
|
60
66
|
groupX(
|
|
61
67
|
{
|
|
62
68
|
data: data.filter((d) => resolveChannel('x', d, { x, y }) != null),
|
|
63
69
|
x,
|
|
64
70
|
y,
|
|
65
71
|
y1: y,
|
|
66
|
-
y2: y
|
|
72
|
+
y2: y,
|
|
73
|
+
fx,
|
|
74
|
+
fy
|
|
67
75
|
},
|
|
68
76
|
{ y: 'median', y1: 'p25', y2: 'p75', fill: (rows) => rows }
|
|
69
77
|
)
|
|
70
78
|
);
|
|
71
79
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
80
|
+
const X = Symbol('x'),
|
|
81
|
+
Y = Symbol('y'),
|
|
82
|
+
FX = Symbol('fx'),
|
|
83
|
+
FY = Symbol('fy'),
|
|
84
|
+
P25 = Symbol('p25'),
|
|
85
|
+
P75 = Symbol('p75'),
|
|
86
|
+
MEDIAN = Symbol('median'),
|
|
87
|
+
MIN = Symbol('min'),
|
|
88
|
+
MAX = Symbol('max'),
|
|
89
|
+
OUTLIERS = Symbol('outliers');
|
|
90
|
+
|
|
91
|
+
const facets = $derived({
|
|
92
|
+
...(fx != null && { fx: FX }),
|
|
93
|
+
...(fy != null && { fy: FY })
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const boxData = $derived(
|
|
97
|
+
grouped
|
|
98
|
+
.map((row) => {
|
|
99
|
+
const { y: median, y1: p25, y2: p75, fill: fill, x: rx } = groupChannels;
|
|
100
|
+
|
|
101
|
+
const iqr = row[p75] - row[p25];
|
|
102
|
+
const whisker = iqr * 1.5;
|
|
103
|
+
const lower = row[p25] - whisker;
|
|
104
|
+
const upper = row[p75] + whisker;
|
|
105
|
+
const data = row[fill].map((d) => ({
|
|
106
|
+
...d,
|
|
107
|
+
[Y]: resolveChannel('y', d, { x, y })
|
|
108
|
+
}));
|
|
109
|
+
const outliers = data.filter((d) => d[Y] < lower || d[Y] > upper);
|
|
110
|
+
const inside = data
|
|
111
|
+
.filter((d) => d[Y] >= lower && d[Y] <= upper)
|
|
112
|
+
.sort((a, b) => a[Y] - b[Y]);
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
...data[0],
|
|
116
|
+
[X]: row[rx],
|
|
117
|
+
[P25]: row[p25],
|
|
118
|
+
[MEDIAN]: row[median],
|
|
119
|
+
[P75]: row[p75],
|
|
120
|
+
[MIN]: inside[0][Y],
|
|
121
|
+
[MAX]: inside.at(-1)[Y],
|
|
122
|
+
[FX]: resolveChannel('fx', data[0], { fx }, null),
|
|
123
|
+
[FY]: resolveChannel('fy', data[0], { fy }, null),
|
|
124
|
+
[OUTLIERS]: outliers
|
|
125
|
+
};
|
|
126
|
+
})
|
|
127
|
+
.sort((a, b) => b[MEDIAN] - a[MEDIAN])
|
|
93
128
|
);
|
|
94
129
|
</script>
|
|
95
130
|
|
|
96
131
|
<GroupMultiple class="box-y {className || ''}" length={className ? 2 : grouped.length}>
|
|
97
|
-
<RuleX data={boxData} x=
|
|
98
|
-
<
|
|
132
|
+
<RuleX data={boxData} x={X} y1={MIN} y2={P25} {stroke} {...rule || {}} {...facets} />
|
|
133
|
+
<RuleX data={boxData} x={X} y1={P75} y2={MAX} {stroke} {...rule || {}} {...facets} />
|
|
134
|
+
<BarY data={boxData} x={X} y1={P25} y2={P75} {fill} {stroke} {...facets} {...bar || {}} />
|
|
99
135
|
{#if tickMedian}
|
|
100
136
|
<TickY
|
|
101
137
|
data={boxData}
|
|
102
|
-
x=
|
|
103
|
-
y=
|
|
138
|
+
x={X}
|
|
139
|
+
y={MEDIAN}
|
|
140
|
+
{...facets}
|
|
141
|
+
{stroke}
|
|
104
142
|
strokeWidth={2}
|
|
105
143
|
{...typeof tickMedian === 'object' ? tickMedian : {}} />
|
|
106
144
|
{/if}
|
|
107
145
|
{#if tickMinMax}
|
|
108
146
|
<TickY
|
|
109
147
|
data={boxData}
|
|
110
|
-
x=
|
|
111
|
-
y=
|
|
148
|
+
x={X}
|
|
149
|
+
y={MIN}
|
|
150
|
+
{stroke}
|
|
151
|
+
{...facets}
|
|
112
152
|
inset="20%"
|
|
113
153
|
{...typeof tickMinMax === 'object' ? tickMinMax : {}} />
|
|
114
154
|
<TickY
|
|
115
155
|
data={boxData}
|
|
116
|
-
x=
|
|
117
|
-
y=
|
|
156
|
+
x={X}
|
|
157
|
+
y={MAX}
|
|
158
|
+
{stroke}
|
|
159
|
+
{...facets}
|
|
118
160
|
inset="20%"
|
|
119
161
|
{...typeof tickMinMax === 'object' ? tickMinMax : {}} />
|
|
120
162
|
{/if}
|
|
121
|
-
<Dot
|
|
163
|
+
<Dot
|
|
164
|
+
data={boxData.map((d) => d[OUTLIERS]).flat()}
|
|
165
|
+
{x}
|
|
166
|
+
{y}
|
|
167
|
+
{fx}
|
|
168
|
+
{fy}
|
|
169
|
+
{fill}
|
|
170
|
+
{stroke}
|
|
171
|
+
{...dot || {}} />
|
|
122
172
|
</GroupMultiple>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { ChannelAccessor, DataRecord } from '../types/index.js';
|
|
2
1
|
declare class __sveltets_Render<Datum extends DataRecord> {
|
|
3
|
-
props(): Pick<BaseMarkProps<Datum_1>, "class"> & {
|
|
2
|
+
props(): Pick<BaseMarkProps<Datum_1>, "fill" | "stroke" | "fx" | "fy" | "class"> & {
|
|
4
3
|
data: Datum[];
|
|
5
4
|
x: ChannelAccessor;
|
|
6
5
|
y: ChannelAccessor;
|
|
@@ -34,7 +34,6 @@ Helper component for rendering rectangular marks in SVG
|
|
|
34
34
|
import type { BaseMarkProps, BaseRectMarkProps, BorderRadius } from '../../types/mark.js';
|
|
35
35
|
import type { DataRecord, ScaledDataRecord } from '../../types/data.js';
|
|
36
36
|
import type { PlotContext, UsedScales } from '../../types/index.js';
|
|
37
|
-
import { RAW_VALUE } from '../../transforms/recordize.js';
|
|
38
37
|
|
|
39
38
|
let {
|
|
40
39
|
datum,
|
|
@@ -47,7 +46,7 @@ Helper component for rendering rectangular marks in SVG
|
|
|
47
46
|
useInsetAsFallbackVertically = true,
|
|
48
47
|
useInsetAsFallbackHorizontally = true,
|
|
49
48
|
usedScales,
|
|
50
|
-
fallbackStyle
|
|
49
|
+
fallbackStyle
|
|
51
50
|
}: RectPathProps = $props();
|
|
52
51
|
|
|
53
52
|
const { getPlotState } = getContext<PlotContext>('svelteplot');
|
|
@@ -94,7 +93,13 @@ Helper component for rendering rectangular marks in SVG
|
|
|
94
93
|
) > 0)
|
|
95
94
|
);
|
|
96
95
|
const [style, styleClass] = $derived(
|
|
97
|
-
resolveStyles(
|
|
96
|
+
resolveStyles(
|
|
97
|
+
plot,
|
|
98
|
+
datum,
|
|
99
|
+
options,
|
|
100
|
+
!fallbackStyle ? (options.stroke && !options.fill ? 'stroke' : 'fill') : fallbackStyle,
|
|
101
|
+
usedScales
|
|
102
|
+
)
|
|
98
103
|
);
|
|
99
104
|
</script>
|
|
100
105
|
|