svelteplot 0.8.1-pr-283.12 → 0.8.1-pr-298.1
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/BoxX.svelte
CHANGED
|
@@ -4,14 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
<script lang="ts">
|
|
6
6
|
interface BoxXMarkProps extends ComponentProps<typeof BoxY> {}
|
|
7
|
-
import GroupMultiple from './helpers/GroupMultiple.svelte';
|
|
8
|
-
import { BarX, TickX, RuleY, Dot, groupY } from '../index.js';
|
|
9
|
-
import { resolveChannel } from '../helpers/resolve.js';
|
|
10
7
|
import { type ComponentProps } from 'svelte';
|
|
8
|
+
import Box from './helpers/Box.svelte';
|
|
11
9
|
import type BoxY from './BoxY.svelte';
|
|
12
10
|
import { getPlotDefaults } from '../hooks/plotDefaults.js';
|
|
13
|
-
import { IS_SORTED } from '../transforms/sort';
|
|
14
|
-
import { sort } from 'd3-array';
|
|
15
11
|
|
|
16
12
|
let markProps: BoxXMarkProps = $props();
|
|
17
13
|
|
|
@@ -22,141 +18,10 @@
|
|
|
22
18
|
...getPlotDefaults().boxX
|
|
23
19
|
};
|
|
24
20
|
|
|
25
|
-
const {
|
|
26
|
-
data = [{}],
|
|
27
|
-
bar,
|
|
28
|
-
rule,
|
|
29
|
-
tickMedian,
|
|
30
|
-
tickMinMax,
|
|
31
|
-
dot,
|
|
32
|
-
x,
|
|
33
|
-
y,
|
|
34
|
-
fx,
|
|
35
|
-
fy,
|
|
36
|
-
fill,
|
|
37
|
-
stroke,
|
|
38
|
-
class: className = ''
|
|
39
|
-
}: BoxXMarkProps = $derived({
|
|
21
|
+
const props: BoxXMarkProps & { class?: string } = $derived({
|
|
40
22
|
...DEFAULTS,
|
|
41
23
|
...markProps
|
|
42
24
|
});
|
|
43
|
-
|
|
44
|
-
const { data: grouped, ...groupChannels } = $derived(
|
|
45
|
-
groupY(
|
|
46
|
-
{
|
|
47
|
-
data: data.filter((d) => resolveChannel('x', d, { x, y }) != null),
|
|
48
|
-
x,
|
|
49
|
-
y,
|
|
50
|
-
x1: x,
|
|
51
|
-
x2: x,
|
|
52
|
-
fx,
|
|
53
|
-
fy
|
|
54
|
-
},
|
|
55
|
-
{ x: 'median', x1: 'p25', x2: 'p75', fill: (rows) => rows }
|
|
56
|
-
)
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
const X = Symbol('x'),
|
|
60
|
-
Y = Symbol('y'),
|
|
61
|
-
FX = Symbol('fx'),
|
|
62
|
-
FY = Symbol('fy'),
|
|
63
|
-
P25 = Symbol('p25'),
|
|
64
|
-
P75 = Symbol('p75'),
|
|
65
|
-
MEDIAN = Symbol('median'),
|
|
66
|
-
MIN = Symbol('min'),
|
|
67
|
-
MAX = Symbol('max'),
|
|
68
|
-
OUTLIERS = Symbol('outliers');
|
|
69
|
-
|
|
70
|
-
const facets = $derived({
|
|
71
|
-
...(fx != null && { fx: FX }),
|
|
72
|
-
...(fy != null && { fy: FY })
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const sortProps = { [IS_SORTED]: true };
|
|
76
|
-
|
|
77
|
-
const boxData = $derived(
|
|
78
|
-
grouped
|
|
79
|
-
.map((row) => {
|
|
80
|
-
const { x: median, x1: p25, x2: p75, fill: fill, y: ry } = groupChannels;
|
|
81
|
-
|
|
82
|
-
const iqr = row[p75] - row[p25];
|
|
83
|
-
const whisker = iqr * 1.5;
|
|
84
|
-
const lower = row[p25] - whisker;
|
|
85
|
-
const upper = row[p75] + whisker;
|
|
86
|
-
const data = row[fill].map((d) => ({
|
|
87
|
-
...d,
|
|
88
|
-
[X]: resolveChannel('x', d, { x, y })
|
|
89
|
-
}));
|
|
90
|
-
const outliers = data.filter((d) => d[X] < lower || d[X] > upper);
|
|
91
|
-
const inside = data
|
|
92
|
-
.filter((d) => d[X] >= lower && d[X] <= upper)
|
|
93
|
-
.sort((a, b) => a[X] - b[X]);
|
|
94
|
-
|
|
95
|
-
// if (inside.length === 0) console.log('No data inside boxplot', data, row, lower, upper);
|
|
96
|
-
return {
|
|
97
|
-
...data[0],
|
|
98
|
-
[Y]: row[ry],
|
|
99
|
-
[P25]: row[p25],
|
|
100
|
-
[MEDIAN]: row[median],
|
|
101
|
-
[P75]: row[p75],
|
|
102
|
-
[MIN]: inside[0][X],
|
|
103
|
-
[MAX]: inside.at(-1)[X],
|
|
104
|
-
[FX]: resolveChannel('fx', data[0], { fx }, null),
|
|
105
|
-
[FY]: resolveChannel('fy', data[0], { fy }, null),
|
|
106
|
-
[OUTLIERS]: outliers
|
|
107
|
-
};
|
|
108
|
-
})
|
|
109
|
-
.sort((a, b) => b[MEDIAN] - a[MEDIAN])
|
|
110
|
-
);
|
|
111
25
|
</script>
|
|
112
26
|
|
|
113
|
-
<
|
|
114
|
-
<RuleY
|
|
115
|
-
data={boxData}
|
|
116
|
-
y={Y}
|
|
117
|
-
x1={MIN}
|
|
118
|
-
x2={P25}
|
|
119
|
-
{stroke}
|
|
120
|
-
{...rule || {}}
|
|
121
|
-
{...facets}
|
|
122
|
-
{...sortProps} />
|
|
123
|
-
<RuleY data={boxData} y={Y} x1={P75} x2={MAX} {stroke} {...rule || {}} {...facets} />
|
|
124
|
-
<BarX data={boxData} y={Y} x1={P25} x2={P75} {fill} {stroke} {...facets} {...bar || {}} />
|
|
125
|
-
{#if tickMedian}
|
|
126
|
-
<TickX
|
|
127
|
-
data={boxData}
|
|
128
|
-
y={Y}
|
|
129
|
-
x={MEDIAN}
|
|
130
|
-
{...facets}
|
|
131
|
-
{stroke}
|
|
132
|
-
strokeWidth={2}
|
|
133
|
-
{...typeof tickMedian === 'object' ? tickMedian : {}} />
|
|
134
|
-
{/if}
|
|
135
|
-
{#if tickMinMax}
|
|
136
|
-
<TickX
|
|
137
|
-
data={boxData}
|
|
138
|
-
x={MIN}
|
|
139
|
-
y={Y}
|
|
140
|
-
{stroke}
|
|
141
|
-
{...facets}
|
|
142
|
-
inset="20%"
|
|
143
|
-
{...typeof tickMinMax === 'object' ? tickMinMax : {}} />
|
|
144
|
-
<TickX
|
|
145
|
-
data={boxData}
|
|
146
|
-
x={MAX}
|
|
147
|
-
y={Y}
|
|
148
|
-
{stroke}
|
|
149
|
-
{...facets}
|
|
150
|
-
inset="20%"
|
|
151
|
-
{...typeof tickMinMax === 'object' ? tickMinMax : {}} />
|
|
152
|
-
{/if}
|
|
153
|
-
<Dot
|
|
154
|
-
data={boxData.map((d) => d[OUTLIERS]).flat()}
|
|
155
|
-
{x}
|
|
156
|
-
{y}
|
|
157
|
-
{fx}
|
|
158
|
-
{fy}
|
|
159
|
-
{fill}
|
|
160
|
-
{stroke}
|
|
161
|
-
{...dot || {}} />
|
|
162
|
-
</GroupMultiple>
|
|
27
|
+
<Box {...props} orientation="x" />
|
package/dist/marks/BoxY.svelte
CHANGED
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
data: Datum[];
|
|
10
10
|
x: ChannelAccessor;
|
|
11
11
|
y: ChannelAccessor;
|
|
12
|
+
/**
|
|
13
|
+
* Custom sort order for grouped box plot data
|
|
14
|
+
*/
|
|
15
|
+
sort?: 'min' | 'max' | 'median' | 'p25' | 'p75' | ((d: Datum) => RawValue);
|
|
12
16
|
/**
|
|
13
17
|
* Options for the rule marks that represent the min/max range
|
|
14
18
|
*/
|
|
@@ -30,12 +34,9 @@
|
|
|
30
34
|
*/
|
|
31
35
|
dot: Record<string, ChannelAccessor<Datum>>;
|
|
32
36
|
}
|
|
33
|
-
import GroupMultiple from './helpers/GroupMultiple.svelte';
|
|
34
|
-
import { groupX, BarY, TickY, RuleX, Dot } from '../index.js';
|
|
35
|
-
import { resolveChannel } from '../helpers/resolve.js';
|
|
36
37
|
import { getPlotDefaults } from '../hooks/plotDefaults.js';
|
|
37
|
-
import
|
|
38
|
-
import {
|
|
38
|
+
import Box from './helpers/Box.svelte';
|
|
39
|
+
import type { BaseMarkProps, ChannelAccessor, DataRecord, RawValue } from '../types';
|
|
39
40
|
|
|
40
41
|
let markProps: BoxYMarkProps = $props();
|
|
41
42
|
|
|
@@ -46,140 +47,10 @@
|
|
|
46
47
|
...getPlotDefaults().boxY
|
|
47
48
|
};
|
|
48
49
|
|
|
49
|
-
const {
|
|
50
|
-
data = [{}],
|
|
51
|
-
bar,
|
|
52
|
-
rule,
|
|
53
|
-
tickMedian,
|
|
54
|
-
tickMinMax,
|
|
55
|
-
dot,
|
|
56
|
-
x,
|
|
57
|
-
y,
|
|
58
|
-
fx,
|
|
59
|
-
fy,
|
|
60
|
-
fill,
|
|
61
|
-
stroke,
|
|
62
|
-
class: className = ''
|
|
63
|
-
}: BoxYMarkProps = $derived({
|
|
50
|
+
const props: BoxYMarkProps & { class?: string } = $derived({
|
|
64
51
|
...DEFAULTS,
|
|
65
52
|
...markProps
|
|
66
53
|
});
|
|
67
|
-
|
|
68
|
-
const { data: grouped, ...groupChannels } = $derived(
|
|
69
|
-
groupX(
|
|
70
|
-
{
|
|
71
|
-
data: data.filter((d) => resolveChannel('x', d, { x, y }) != null),
|
|
72
|
-
x,
|
|
73
|
-
y,
|
|
74
|
-
y1: y,
|
|
75
|
-
y2: y,
|
|
76
|
-
fx,
|
|
77
|
-
fy
|
|
78
|
-
},
|
|
79
|
-
{ y: 'median', y1: 'p25', y2: 'p75', fill: (rows) => rows }
|
|
80
|
-
)
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const X = Symbol('x'),
|
|
84
|
-
Y = Symbol('y'),
|
|
85
|
-
FX = Symbol('fx'),
|
|
86
|
-
FY = Symbol('fy'),
|
|
87
|
-
P25 = Symbol('p25'),
|
|
88
|
-
P75 = Symbol('p75'),
|
|
89
|
-
MEDIAN = Symbol('median'),
|
|
90
|
-
MIN = Symbol('min'),
|
|
91
|
-
MAX = Symbol('max'),
|
|
92
|
-
OUTLIERS = Symbol('outliers');
|
|
93
|
-
|
|
94
|
-
const facets = $derived({
|
|
95
|
-
...(fx != null && { fx: FX }),
|
|
96
|
-
...(fy != null && { fy: FY })
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const sortProps = { [IS_SORTED]: true };
|
|
100
|
-
|
|
101
|
-
const boxData = $derived(
|
|
102
|
-
grouped
|
|
103
|
-
.map((row) => {
|
|
104
|
-
const { y: median, y1: p25, y2: p75, fill: fill, x: rx } = groupChannels;
|
|
105
|
-
|
|
106
|
-
const iqr = row[p75] - row[p25];
|
|
107
|
-
const whisker = iqr * 1.5;
|
|
108
|
-
const lower = row[p25] - whisker;
|
|
109
|
-
const upper = row[p75] + whisker;
|
|
110
|
-
const data = row[fill].map((d) => ({
|
|
111
|
-
...d,
|
|
112
|
-
[Y]: resolveChannel('y', d, { x, y })
|
|
113
|
-
}));
|
|
114
|
-
const outliers = data.filter((d) => d[Y] < lower || d[Y] > upper);
|
|
115
|
-
const inside = data
|
|
116
|
-
.filter((d) => d[Y] >= lower && d[Y] <= upper)
|
|
117
|
-
.sort((a, b) => a[Y] - b[Y]);
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
...data[0],
|
|
121
|
-
[X]: row[rx],
|
|
122
|
-
[P25]: row[p25],
|
|
123
|
-
[MEDIAN]: row[median],
|
|
124
|
-
[P75]: row[p75],
|
|
125
|
-
[MIN]: inside[0][Y],
|
|
126
|
-
[MAX]: inside.at(-1)[Y],
|
|
127
|
-
[FX]: resolveChannel('fx', data[0], { fx }, null),
|
|
128
|
-
[FY]: resolveChannel('fy', data[0], { fy }, null),
|
|
129
|
-
[OUTLIERS]: outliers
|
|
130
|
-
};
|
|
131
|
-
})
|
|
132
|
-
.sort((a, b) => b[MEDIAN] - a[MEDIAN])
|
|
133
|
-
);
|
|
134
54
|
</script>
|
|
135
55
|
|
|
136
|
-
<
|
|
137
|
-
<RuleX
|
|
138
|
-
data={boxData}
|
|
139
|
-
x={X}
|
|
140
|
-
y1={MIN}
|
|
141
|
-
y2={P25}
|
|
142
|
-
{stroke}
|
|
143
|
-
{...sortProps}
|
|
144
|
-
{...rule || {}}
|
|
145
|
-
{...facets} />
|
|
146
|
-
<RuleX data={boxData} x={X} y1={P75} y2={MAX} {stroke} {...rule || {}} {...facets} />
|
|
147
|
-
<BarY data={boxData} x={X} y1={P25} y2={P75} {fill} {stroke} {...facets} {...bar || {}} />
|
|
148
|
-
{#if tickMedian}
|
|
149
|
-
<TickY
|
|
150
|
-
data={boxData}
|
|
151
|
-
x={X}
|
|
152
|
-
y={MEDIAN}
|
|
153
|
-
{...facets}
|
|
154
|
-
{stroke}
|
|
155
|
-
strokeWidth={2}
|
|
156
|
-
{...typeof tickMedian === 'object' ? tickMedian : {}} />
|
|
157
|
-
{/if}
|
|
158
|
-
{#if tickMinMax}
|
|
159
|
-
<TickY
|
|
160
|
-
data={boxData}
|
|
161
|
-
x={X}
|
|
162
|
-
y={MIN}
|
|
163
|
-
{stroke}
|
|
164
|
-
{...facets}
|
|
165
|
-
inset="20%"
|
|
166
|
-
{...typeof tickMinMax === 'object' ? tickMinMax : {}} />
|
|
167
|
-
<TickY
|
|
168
|
-
data={boxData}
|
|
169
|
-
x={X}
|
|
170
|
-
y={MAX}
|
|
171
|
-
{stroke}
|
|
172
|
-
{...facets}
|
|
173
|
-
inset="20%"
|
|
174
|
-
{...typeof tickMinMax === 'object' ? tickMinMax : {}} />
|
|
175
|
-
{/if}
|
|
176
|
-
<Dot
|
|
177
|
-
data={boxData.map((d) => d[OUTLIERS]).flat()}
|
|
178
|
-
{x}
|
|
179
|
-
{y}
|
|
180
|
-
{fx}
|
|
181
|
-
{fy}
|
|
182
|
-
{fill}
|
|
183
|
-
{stroke}
|
|
184
|
-
{...dot || {}} />
|
|
185
|
-
</GroupMultiple>
|
|
56
|
+
<Box {...props} orientation="y" />
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import type { ChannelAccessor, DataRecord } from '../types';
|
|
1
|
+
import type { ChannelAccessor, DataRecord, RawValue } from '../types';
|
|
2
2
|
declare function $$render<Datum extends DataRecord>(): {
|
|
3
3
|
props: Pick<BaseMarkProps<Datum>, "fill" | "stroke" | "fx" | "fy" | "class"> & {
|
|
4
4
|
data: Datum[];
|
|
5
5
|
x: ChannelAccessor;
|
|
6
6
|
y: ChannelAccessor;
|
|
7
|
+
/**
|
|
8
|
+
* Custom sort order for grouped box plot data
|
|
9
|
+
*/
|
|
10
|
+
sort?: "min" | "max" | "median" | "p25" | "p75" | ((d: Datum) => RawValue);
|
|
7
11
|
/**
|
|
8
12
|
* Options for the rule marks that represent the min/max range
|
|
9
13
|
*/
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
Internal shared box plot implementation for BoxX and BoxY
|
|
3
|
+
-->
|
|
4
|
+
<script lang="ts" generics="Datum extends DataRecord">
|
|
5
|
+
type Orientation = 'x' | 'y';
|
|
6
|
+
|
|
7
|
+
interface BoxMarkProps extends Pick<
|
|
8
|
+
BaseMarkProps<Datum>,
|
|
9
|
+
'class' | 'fill' | 'stroke' | 'fx' | 'fy'
|
|
10
|
+
> {
|
|
11
|
+
data: Datum[];
|
|
12
|
+
x: ChannelAccessor;
|
|
13
|
+
y: ChannelAccessor;
|
|
14
|
+
/**
|
|
15
|
+
* Custom sort order for grouped box plot data
|
|
16
|
+
*/
|
|
17
|
+
sort?: 'min' | 'max' | 'median' | 'p25' | 'p75' | ((d: Datum) => RawValue);
|
|
18
|
+
/**
|
|
19
|
+
* Options for the rule marks that represent the min/max range
|
|
20
|
+
*/
|
|
21
|
+
rule: Record<string, ChannelAccessor<Datum>>;
|
|
22
|
+
/**
|
|
23
|
+
* Options for the bar marks that represent the IQR range
|
|
24
|
+
*/
|
|
25
|
+
bar: Record<string, ChannelAccessor<Datum>>;
|
|
26
|
+
/**
|
|
27
|
+
* Options for the tick marks that represent the median
|
|
28
|
+
*/
|
|
29
|
+
tickMedian: Record<string, ChannelAccessor<Datum>> | boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Options for the tick marks that represent the min/max range
|
|
32
|
+
*/
|
|
33
|
+
tickMinMax: Record<string, ChannelAccessor<Datum>> | boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Options for the dot marks that represent the outliers
|
|
36
|
+
*/
|
|
37
|
+
dot: Record<string, ChannelAccessor<Datum>>;
|
|
38
|
+
orientation: Orientation;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
import GroupMultiple from './GroupMultiple.svelte';
|
|
42
|
+
import { groupX, groupY, BarY, TickY, RuleX, BarX, TickX, RuleY, Dot } from '../../index.js';
|
|
43
|
+
import { resolveChannel } from '../../helpers/resolve.js';
|
|
44
|
+
import type { BaseMarkProps, ChannelAccessor, DataRecord, RawValue } from '../../types';
|
|
45
|
+
import { IS_SORTED } from '../../transforms/sort';
|
|
46
|
+
|
|
47
|
+
let markProps: BoxMarkProps = $props();
|
|
48
|
+
|
|
49
|
+
const {
|
|
50
|
+
data = [{}],
|
|
51
|
+
bar,
|
|
52
|
+
rule,
|
|
53
|
+
tickMedian,
|
|
54
|
+
tickMinMax,
|
|
55
|
+
dot,
|
|
56
|
+
x,
|
|
57
|
+
y,
|
|
58
|
+
sort,
|
|
59
|
+
fx,
|
|
60
|
+
fy,
|
|
61
|
+
fill,
|
|
62
|
+
stroke,
|
|
63
|
+
orientation,
|
|
64
|
+
class: className = ''
|
|
65
|
+
}: BoxMarkProps = $derived(markProps);
|
|
66
|
+
|
|
67
|
+
const groupFn = $derived(orientation === 'y' ? groupX : groupY);
|
|
68
|
+
const BarMark = $derived(orientation === 'y' ? BarY : BarX);
|
|
69
|
+
const RuleMark = $derived(orientation === 'y' ? RuleX : RuleY);
|
|
70
|
+
const TickMark = $derived(orientation === 'y' ? TickY : TickX);
|
|
71
|
+
|
|
72
|
+
// the channels as if this would be a BoxX
|
|
73
|
+
const xChannel = $derived(orientation === 'y' ? y : x);
|
|
74
|
+
const yChannel = $derived(orientation === 'y' ? x : y);
|
|
75
|
+
const xProp = $derived(orientation === 'x' ? 'x' : 'y');
|
|
76
|
+
const x1Prop = $derived(`${xProp}1`);
|
|
77
|
+
const x2Prop = $derived(`${xProp}2`);
|
|
78
|
+
const yProp = $derived(orientation === 'x' ? 'y' : 'x');
|
|
79
|
+
|
|
80
|
+
const { data: grouped, ...groupChannels } = $derived(
|
|
81
|
+
groupFn(
|
|
82
|
+
{
|
|
83
|
+
data: data.filter((d) => resolveChannel(yProp, d, { x, y }) != null),
|
|
84
|
+
x,
|
|
85
|
+
y,
|
|
86
|
+
[x1Prop]: xChannel,
|
|
87
|
+
[x2Prop]: xChannel,
|
|
88
|
+
fx,
|
|
89
|
+
fy
|
|
90
|
+
},
|
|
91
|
+
{ [xProp]: 'median', [x1Prop]: 'p25', [x2Prop]: 'p75', fill: (rows) => rows }
|
|
92
|
+
)
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const X = Symbol('x'),
|
|
96
|
+
Y = Symbol('y'),
|
|
97
|
+
FX = Symbol('fx'),
|
|
98
|
+
FY = Symbol('fy'),
|
|
99
|
+
P25 = Symbol('p25'),
|
|
100
|
+
P75 = Symbol('p75'),
|
|
101
|
+
MEDIAN = Symbol('median'),
|
|
102
|
+
MIN = Symbol('min'),
|
|
103
|
+
MAX = Symbol('max'),
|
|
104
|
+
OUTLIERS = Symbol('outliers'),
|
|
105
|
+
SORT_REF = Symbol('sortRef');
|
|
106
|
+
|
|
107
|
+
const facets = $derived({
|
|
108
|
+
...(fx != null && { fx: FX }),
|
|
109
|
+
...(fy != null && { fy: FY })
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const sortProps = { [IS_SORTED]: true };
|
|
113
|
+
|
|
114
|
+
const compareValues = (a: RawValue, b: RawValue) =>
|
|
115
|
+
(typeof a === 'string' && typeof b === 'string'
|
|
116
|
+
? a.localeCompare(b)
|
|
117
|
+
: a > b
|
|
118
|
+
? 1
|
|
119
|
+
: a < b
|
|
120
|
+
? -1
|
|
121
|
+
: 0) || 0;
|
|
122
|
+
|
|
123
|
+
const boxData = $derived.by(() => {
|
|
124
|
+
const boxes = grouped.map((row) => {
|
|
125
|
+
const medianKey = groupChannels[xProp];
|
|
126
|
+
const p25Key = groupChannels[x1Prop];
|
|
127
|
+
const p75Key = groupChannels[x2Prop];
|
|
128
|
+
const groupKey = groupChannels[yProp];
|
|
129
|
+
|
|
130
|
+
const iqr = row[p75Key] - row[p25Key];
|
|
131
|
+
const whisker = iqr * 1.5;
|
|
132
|
+
const lower = row[p25Key] - whisker;
|
|
133
|
+
const upper = row[p75Key] + whisker;
|
|
134
|
+
const data = row[groupChannels.fill].map((d) => ({
|
|
135
|
+
...d,
|
|
136
|
+
[orientation === 'y' ? Y : X]: resolveChannel(xProp, d, {
|
|
137
|
+
x,
|
|
138
|
+
y
|
|
139
|
+
})
|
|
140
|
+
}));
|
|
141
|
+
const valueSym = orientation === 'y' ? Y : X;
|
|
142
|
+
const groupSym = orientation === 'y' ? X : Y;
|
|
143
|
+
const outliers = data.filter((d) => d[valueSym] < lower || d[valueSym] > upper);
|
|
144
|
+
const inside = data
|
|
145
|
+
.filter((d) => d[valueSym] >= lower && d[valueSym] <= upper)
|
|
146
|
+
.sort((a, b) => a[valueSym] - b[valueSym]);
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
...data[0],
|
|
150
|
+
[SORT_REF]: row[groupChannels.fill]?.[0],
|
|
151
|
+
[groupSym]: row[groupKey],
|
|
152
|
+
[P25]: row[p25Key],
|
|
153
|
+
[MEDIAN]: row[medianKey],
|
|
154
|
+
[P75]: row[p75Key],
|
|
155
|
+
[MIN]: inside[0][valueSym],
|
|
156
|
+
[MAX]: inside.at(-1)[valueSym],
|
|
157
|
+
[FX]: resolveChannel('fx', data[0], { fx }, null),
|
|
158
|
+
[FY]: resolveChannel('fy', data[0], { fy }, null),
|
|
159
|
+
[OUTLIERS]: outliers
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const stripSortRef = ({ [SORT_REF]: _, ...rest }) => rest;
|
|
164
|
+
|
|
165
|
+
if (!sort) return boxes.map(stripSortRef);
|
|
166
|
+
|
|
167
|
+
const sortAccessor =
|
|
168
|
+
typeof sort === 'function'
|
|
169
|
+
? (d) => sort(d[SORT_REF])
|
|
170
|
+
: (d) => {
|
|
171
|
+
switch (sort) {
|
|
172
|
+
case 'min':
|
|
173
|
+
return d[MIN];
|
|
174
|
+
case 'max':
|
|
175
|
+
return d[MAX];
|
|
176
|
+
case 'p25':
|
|
177
|
+
return d[P25];
|
|
178
|
+
case 'p75':
|
|
179
|
+
return d[P75];
|
|
180
|
+
case 'median':
|
|
181
|
+
default:
|
|
182
|
+
return d[MEDIAN];
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const direction = typeof sort === 'string' ? -1 : 1;
|
|
187
|
+
|
|
188
|
+
return boxes
|
|
189
|
+
.toSorted((a, b) => compareValues(sortAccessor(a), sortAccessor(b)) * direction)
|
|
190
|
+
.map(stripSortRef);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const valueSymbol = $derived(orientation === 'y' ? Y : X);
|
|
194
|
+
const groupSymbol = $derived(orientation === 'y' ? X : Y);
|
|
195
|
+
const length = $derived(className ? 2 : grouped.length);
|
|
196
|
+
const baseClass = $derived(`box-${orientation} ${className || ''}`);
|
|
197
|
+
</script>
|
|
198
|
+
|
|
199
|
+
<GroupMultiple class={baseClass} {length}>
|
|
200
|
+
<RuleMark
|
|
201
|
+
data={boxData}
|
|
202
|
+
{...{ [yProp]: groupSymbol, [x1Prop]: MIN, [x2Prop]: P25 }}
|
|
203
|
+
{stroke}
|
|
204
|
+
{...rule || {}}
|
|
205
|
+
{...facets}
|
|
206
|
+
{...sortProps} />
|
|
207
|
+
<RuleMark
|
|
208
|
+
data={boxData}
|
|
209
|
+
{...{ [yProp]: groupSymbol, [x1Prop]: P75, [x2Prop]: MAX }}
|
|
210
|
+
{stroke}
|
|
211
|
+
{...rule || {}}
|
|
212
|
+
{...facets} />
|
|
213
|
+
<BarMark
|
|
214
|
+
data={boxData}
|
|
215
|
+
{...{ [yProp]: groupSymbol, [x1Prop]: P25, [x2Prop]: P75 }}
|
|
216
|
+
{fill}
|
|
217
|
+
{stroke}
|
|
218
|
+
{...facets}
|
|
219
|
+
{...bar || {}} />
|
|
220
|
+
{#if tickMedian}
|
|
221
|
+
<TickMark
|
|
222
|
+
data={boxData}
|
|
223
|
+
{...{ [yProp]: groupSymbol, [xProp]: MEDIAN }}
|
|
224
|
+
{...facets}
|
|
225
|
+
{stroke}
|
|
226
|
+
strokeWidth={2}
|
|
227
|
+
{...typeof tickMedian === 'object' ? tickMedian : {}} />
|
|
228
|
+
{/if}
|
|
229
|
+
{#if tickMinMax}
|
|
230
|
+
<TickMark
|
|
231
|
+
data={boxData}
|
|
232
|
+
{...{ [yProp]: groupSymbol, [xProp]: MIN }}
|
|
233
|
+
{stroke}
|
|
234
|
+
{...facets}
|
|
235
|
+
inset="20%"
|
|
236
|
+
{...typeof tickMinMax === 'object' ? tickMinMax : {}} />
|
|
237
|
+
<TickMark
|
|
238
|
+
data={boxData}
|
|
239
|
+
{...{ [yProp]: groupSymbol, [xProp]: MAX }}
|
|
240
|
+
{stroke}
|
|
241
|
+
{...facets}
|
|
242
|
+
inset="20%"
|
|
243
|
+
{...typeof tickMinMax === 'object' ? tickMinMax : {}} />
|
|
244
|
+
{/if}
|
|
245
|
+
<Dot
|
|
246
|
+
data={boxData.map((d) => d[OUTLIERS]).flat()}
|
|
247
|
+
{x}
|
|
248
|
+
{y}
|
|
249
|
+
{fx}
|
|
250
|
+
{fy}
|
|
251
|
+
{fill}
|
|
252
|
+
{stroke}
|
|
253
|
+
{...dot || {}} />
|
|
254
|
+
</GroupMultiple>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ChannelAccessor, DataRecord, RawValue } from '../../types';
|
|
2
|
+
declare function $$render<Datum extends DataRecord>(): {
|
|
3
|
+
props: Pick<BaseMarkProps<Datum>, "fill" | "stroke" | "fx" | "fy" | "class"> & {
|
|
4
|
+
data: Datum[];
|
|
5
|
+
x: ChannelAccessor;
|
|
6
|
+
y: ChannelAccessor;
|
|
7
|
+
/**
|
|
8
|
+
* Custom sort order for grouped box plot data
|
|
9
|
+
*/
|
|
10
|
+
sort?: "min" | "max" | "median" | "p25" | "p75" | ((d: Datum) => RawValue);
|
|
11
|
+
/**
|
|
12
|
+
* Options for the rule marks that represent the min/max range
|
|
13
|
+
*/
|
|
14
|
+
rule: Record<string, ChannelAccessor<Datum>>;
|
|
15
|
+
/**
|
|
16
|
+
* Options for the bar marks that represent the IQR range
|
|
17
|
+
*/
|
|
18
|
+
bar: Record<string, ChannelAccessor<Datum>>;
|
|
19
|
+
/**
|
|
20
|
+
* Options for the tick marks that represent the median
|
|
21
|
+
*/
|
|
22
|
+
tickMedian: Record<string, ChannelAccessor<Datum>> | boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Options for the tick marks that represent the min/max range
|
|
25
|
+
*/
|
|
26
|
+
tickMinMax: Record<string, ChannelAccessor<Datum>> | boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Options for the dot marks that represent the outliers
|
|
29
|
+
*/
|
|
30
|
+
dot: Record<string, ChannelAccessor<Datum>>;
|
|
31
|
+
orientation: "x" | "y";
|
|
32
|
+
};
|
|
33
|
+
exports: {};
|
|
34
|
+
bindings: "";
|
|
35
|
+
slots: {};
|
|
36
|
+
events: {};
|
|
37
|
+
};
|
|
38
|
+
declare class __sveltets_Render<Datum extends DataRecord> {
|
|
39
|
+
props(): ReturnType<typeof $$render<Datum>>['props'];
|
|
40
|
+
events(): ReturnType<typeof $$render<Datum>>['events'];
|
|
41
|
+
slots(): ReturnType<typeof $$render<Datum>>['slots'];
|
|
42
|
+
bindings(): "";
|
|
43
|
+
exports(): {};
|
|
44
|
+
}
|
|
45
|
+
interface $$IsomorphicComponent {
|
|
46
|
+
new <Datum extends DataRecord>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Datum>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Datum>['props']>, ReturnType<__sveltets_Render<Datum>['events']>, ReturnType<__sveltets_Render<Datum>['slots']>> & {
|
|
47
|
+
$$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
|
|
48
|
+
} & ReturnType<__sveltets_Render<Datum>['exports']>;
|
|
49
|
+
<Datum extends DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
|
|
50
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
51
|
+
}
|
|
52
|
+
/** Internal shared box plot implementation for BoxX and BoxY */
|
|
53
|
+
declare const Box: $$IsomorphicComponent;
|
|
54
|
+
type Box<Datum extends DataRecord> = InstanceType<typeof Box<Datum>>;
|
|
55
|
+
export default Box;
|