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.
@@ -42,7 +42,6 @@
42
42
  const plot = $derived(getPlotState());
43
43
 
44
44
  const DEFAULTS = {
45
- fill: 'currentColor',
46
45
  ...getContext<PlotDefaults>('svelteplot/_defaults').bar,
47
46
  ...getContext<PlotDefaults>('svelteplot/_defaults').barY
48
47
  };
@@ -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 iqr = row.__x2 - row.__x1;
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.__x1 - whisker;
55
- const upper = row.__x2 + whisker;
56
- const data = row.__fill.map((d) => ({
80
+ const lower = row[p25] - whisker;
81
+ const upper = row[p75] + whisker;
82
+ const data = row[fill].map((d) => ({
57
83
  ...d,
58
- __x: resolveChannel('x', d, { x, y })
84
+ [X]: resolveChannel('x', d, { x, y })
59
85
  }));
60
- const outliers = data.filter((d) => d.__x < lower || d.__x > upper);
86
+ const outliers = data.filter((d) => d[X] < lower || d[X] > upper);
61
87
  const inside = data
62
- .filter((d) => d.__x >= lower && d.__x <= upper)
63
- .sort((a, b) => a.__x - b.__x);
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
- [y]: row[y],
67
- __y: row[y],
68
- p25: row.__x1,
69
- p75: row.__x2,
70
- median: row.__x,
71
- min: inside[0].__x,
72
- max: inside.at(-1).__x,
73
- outliers
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.median - a.median)
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="__y" x1="min" x2="max" {...rule || {}} />
82
- <BarX data={boxData} y="__y" x1="p25" x2="p75" fill="#ddd" {...bar || {}} />
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="__y"
87
- x="median"
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="min"
95
- y="__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="max"
101
- y="__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 data={boxData.map((d) => d.outliers).flat()} {x} {y} {...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>
@@ -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 extends Pick<BaseMarkProps<Datum>, 'class'> {
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 = [{} as Datum],
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
- let { data: grouped } = $derived(
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
- let boxData = $derived(
73
- grouped.map((row) => {
74
- const iqr = row.__y2 - row.__y1;
75
- const whisker = iqr * 1.5;
76
- const lower = row.__y1 - whisker;
77
- const upper = row.__y2 + whisker;
78
- const data = row.__fill.map((d) => ({ ...d, __y: resolveChannel('y', d, { x, y }) }));
79
- const outliers = data.filter((d) => d.__y < lower || d.__y > upper);
80
- const inside = data
81
- .filter((d) => d.__y >= lower && d.__y <= upper)
82
- .sort((a, b) => a.__y - b.__y);
83
- return {
84
- __x: row[x],
85
- p25: row.__y1,
86
- p75: row.__y2,
87
- median: row.__y,
88
- min: inside[0].__y,
89
- max: inside.at(-1).__y,
90
- outliers
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="__x" y1="min" y2="max" {...rule || {}} />
98
- <BarY data={boxData} x="__x" y1="p25" y2="p75" fill="#ddd" {...bar || {}} />
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="__x"
103
- y="median"
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="__x"
111
- y="min"
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="__x"
117
- y="max"
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 data={boxData.map((d) => d.outliers).flat()} {x} {y} {...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 = 'fill'
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(plot, datum, options, fallbackStyle, usedScales)
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteplot",
3
- "version": "0.4.2-pr-194.3",
3
+ "version": "0.4.3-pr-198.0",
4
4
  "license": "ISC",
5
5
  "author": {
6
6
  "name": "Gregor Aisch",