svelteplot 0.8.1 → 0.9.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.
Files changed (114) hide show
  1. package/README.md +1 -1
  2. package/dist/Mark.svelte +1 -1
  3. package/dist/Mark.svelte.d.ts +1 -0
  4. package/dist/constants.d.ts +2 -0
  5. package/dist/constants.js +2 -0
  6. package/dist/core/Plot.svelte +4 -4
  7. package/dist/helpers/projection.js +7 -2
  8. package/dist/hooks/usePlot.svelte.d.ts +51 -0
  9. package/dist/hooks/usePlot.svelte.js +90 -0
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.js +1 -0
  12. package/dist/marks/Area.svelte +3 -5
  13. package/dist/marks/Area.svelte.d.ts +1 -0
  14. package/dist/marks/AreaX.svelte.d.ts +1 -0
  15. package/dist/marks/Arrow.svelte +3 -5
  16. package/dist/marks/Arrow.svelte.d.ts +1 -0
  17. package/dist/marks/AxisX.svelte +2 -3
  18. package/dist/marks/AxisX.svelte.d.ts +1 -0
  19. package/dist/marks/AxisY.svelte +3 -4
  20. package/dist/marks/AxisY.svelte.d.ts +1 -0
  21. package/dist/marks/BarX.svelte +2 -4
  22. package/dist/marks/BarX.svelte.d.ts +1 -0
  23. package/dist/marks/BarY.svelte +2 -4
  24. package/dist/marks/BarY.svelte.d.ts +1 -0
  25. package/dist/marks/BollingerX.svelte.d.ts +1 -0
  26. package/dist/marks/BollingerY.svelte.d.ts +1 -0
  27. package/dist/marks/BoxX.svelte +4 -138
  28. package/dist/marks/BoxY.svelte +20 -137
  29. package/dist/marks/BoxY.svelte.d.ts +8 -3
  30. package/dist/marks/Brush.svelte +3 -3
  31. package/dist/marks/Brush.svelte.d.ts +1 -0
  32. package/dist/marks/Cell.svelte +2 -4
  33. package/dist/marks/Cell.svelte.d.ts +1 -0
  34. package/dist/marks/ColorLegend.svelte +2 -4
  35. package/dist/marks/CustomMark.svelte.d.ts +1 -0
  36. package/dist/marks/CustomMarkHTML.svelte +5 -10
  37. package/dist/marks/DifferenceY.svelte +3 -5
  38. package/dist/marks/DifferenceY.svelte.d.ts +1 -0
  39. package/dist/marks/Dot.svelte +4 -5
  40. package/dist/marks/Dot.svelte.d.ts +1 -0
  41. package/dist/marks/DotX.svelte.d.ts +1 -0
  42. package/dist/marks/DotY.svelte.d.ts +1 -0
  43. package/dist/marks/Frame.svelte +3 -9
  44. package/dist/marks/Frame.svelte.d.ts +1 -0
  45. package/dist/marks/Geo.svelte +5 -5
  46. package/dist/marks/Geo.svelte.d.ts +2 -0
  47. package/dist/marks/GridX.svelte +3 -10
  48. package/dist/marks/GridX.svelte.d.ts +1 -0
  49. package/dist/marks/GridY.svelte +3 -4
  50. package/dist/marks/GridY.svelte.d.ts +1 -0
  51. package/dist/marks/HTMLTooltip.svelte +5 -5
  52. package/dist/marks/Image.svelte.d.ts +1 -0
  53. package/dist/marks/Line.svelte +7 -6
  54. package/dist/marks/Line.svelte.d.ts +1 -0
  55. package/dist/marks/LineX.svelte.d.ts +1 -0
  56. package/dist/marks/LineY.svelte.d.ts +1 -0
  57. package/dist/marks/Link.svelte +2 -4
  58. package/dist/marks/Link.svelte.d.ts +1 -0
  59. package/dist/marks/Pointer.svelte +4 -4
  60. package/dist/marks/Rect.svelte +2 -4
  61. package/dist/marks/Rect.svelte.d.ts +1 -0
  62. package/dist/marks/RectX.svelte +4 -4
  63. package/dist/marks/RectY.svelte +4 -4
  64. package/dist/marks/RuleX.svelte +2 -4
  65. package/dist/marks/RuleX.svelte.d.ts +1 -0
  66. package/dist/marks/RuleY.svelte +2 -4
  67. package/dist/marks/RuleY.svelte.d.ts +1 -0
  68. package/dist/marks/Spike.svelte.d.ts +1 -0
  69. package/dist/marks/SymbolLegend.svelte +2 -4
  70. package/dist/marks/SymbolLegend.svelte.d.ts +17 -2
  71. package/dist/marks/Text.svelte.d.ts +1 -0
  72. package/dist/marks/TickX.svelte +2 -3
  73. package/dist/marks/TickX.svelte.d.ts +1 -0
  74. package/dist/marks/TickY.svelte +2 -3
  75. package/dist/marks/TickY.svelte.d.ts +1 -0
  76. package/dist/marks/Trail.svelte +161 -0
  77. package/dist/marks/Trail.svelte.d.ts +107 -0
  78. package/dist/marks/Vector.svelte +3 -4
  79. package/dist/marks/Vector.svelte.d.ts +1 -0
  80. package/dist/marks/WaffleX.svelte +2 -3
  81. package/dist/marks/WaffleX.svelte.d.ts +1 -0
  82. package/dist/marks/WaffleY.svelte +2 -4
  83. package/dist/marks/WaffleY.svelte.d.ts +1 -0
  84. package/dist/marks/helpers/AreaCanvas.svelte +2 -4
  85. package/dist/marks/helpers/Box.svelte +271 -0
  86. package/dist/marks/helpers/Box.svelte.d.ts +118 -0
  87. package/dist/marks/helpers/CanvasLayer.svelte +2 -4
  88. package/dist/marks/helpers/DotCanvas.svelte +3 -5
  89. package/dist/marks/helpers/GeoCanvas.svelte +2 -4
  90. package/dist/marks/helpers/LineCanvas.svelte +2 -4
  91. package/dist/marks/helpers/LinearGradientX.svelte +3 -4
  92. package/dist/marks/helpers/LinearGradientY.svelte +3 -4
  93. package/dist/marks/helpers/MarkerPath.svelte +4 -5
  94. package/dist/marks/helpers/MarkerPath.svelte.d.ts +1 -0
  95. package/dist/marks/helpers/MultilineText.svelte +4 -4
  96. package/dist/marks/helpers/RectPath.svelte +5 -6
  97. package/dist/marks/helpers/Regression.svelte +4 -8
  98. package/dist/marks/helpers/TrailCanvas.svelte +138 -0
  99. package/dist/marks/helpers/TrailCanvas.svelte.d.ts +40 -0
  100. package/dist/marks/helpers/events.d.ts +2 -2
  101. package/dist/marks/helpers/events.js +4 -4
  102. package/dist/marks/helpers/trail.d.ts +23 -0
  103. package/dist/marks/helpers/trail.js +372 -0
  104. package/dist/marks/index.d.ts +1 -0
  105. package/dist/marks/index.js +1 -0
  106. package/dist/transforms/bollinger.d.ts +1 -0
  107. package/dist/transforms/interval.d.ts +2 -0
  108. package/dist/transforms/select.d.ts +7 -0
  109. package/dist/transforms/sort.d.ts +4 -0
  110. package/dist/transforms/window.d.ts +2 -0
  111. package/dist/types/mark.d.ts +2 -1
  112. package/dist/types/plot.d.ts +6 -1
  113. package/dist/ui/Spiral.svelte +4 -0
  114. package/package.json +24 -23
@@ -0,0 +1,161 @@
1
+ <script lang="ts" generics="Datum extends DataRecord">
2
+ interface TrailMarkProps extends Omit<
3
+ BaseMarkProps<Datum>,
4
+ 'stroke' | 'strokeWidth' | 'strokeDasharray'
5
+ > {
6
+ data?: Datum[];
7
+ x?: ChannelAccessor<Datum>;
8
+ y?: ChannelAccessor<Datum>;
9
+ z?: ChannelAccessor<Datum>;
10
+ r?: ChannelAccessor<Datum>;
11
+ curve?: CurveName | CurveFactory;
12
+ tension?: number;
13
+ sort?: ConstantAccessor<RawValue, Datum> | { channel: 'stroke' | 'fill' };
14
+ defined?: ConstantAccessor<boolean, Datum>;
15
+ canvas?: boolean;
16
+ cap?: 'butt' | 'round';
17
+ /**
18
+ * Samples per segment for curve interpolation
19
+ */
20
+ resolution?: number | 'auto';
21
+ }
22
+ import type {
23
+ DataRecord,
24
+ ChannelAccessor,
25
+ BaseMarkProps,
26
+ ConstantAccessor,
27
+ RawValue,
28
+ ScaledDataRecord,
29
+ CurveName
30
+ } from '../types';
31
+ import Mark from '../Mark.svelte';
32
+ import { path as d3Path } from 'd3-path';
33
+ import { resolveProp, resolveStyles } from '../helpers/resolve.js';
34
+ import { getPlotDefaults } from '../hooks/plotDefaults';
35
+ import { sort } from '../transforms';
36
+ import trailPath, { type TrailSample } from './helpers/trail.js';
37
+ import TrailCanvas from './helpers/TrailCanvas.svelte';
38
+ import { addEventHandlers } from './helpers/events';
39
+ import type { CurveFactory } from 'd3-shape';
40
+ import { usePlot } from '../hooks/usePlot.svelte.js';
41
+
42
+ let markProps: TrailMarkProps = $props();
43
+
44
+ const DEFAULTS: TrailMarkProps = {
45
+ curve: 'linear',
46
+ r: 3,
47
+ canvas: false,
48
+ resolution: 'auto',
49
+ cap: 'round',
50
+ tension: 0,
51
+ ...getPlotDefaults().trail
52
+ };
53
+
54
+ const {
55
+ data = [{} as Datum],
56
+ curve,
57
+ resolution,
58
+ tension,
59
+ canvas,
60
+ cap,
61
+ class: className,
62
+ ...options
63
+ }: TrailMarkProps = $derived({
64
+ ...DEFAULTS,
65
+ ...markProps
66
+ });
67
+
68
+ const args = $derived(sort({ data, ...options })) as TrailMarkProps;
69
+
70
+ const plot = usePlot();
71
+
72
+ /**
73
+ * Groups the data by the specified key
74
+ */
75
+ function groupIndex(data: ScaledDataRecord[], groupByKey: ChannelAccessor<Datum> | null) {
76
+ if (!groupByKey) return [data];
77
+ let group: ScaledDataRecord[] = [];
78
+ const groups = [group];
79
+ let lastGroupValue;
80
+ for (const d of data) {
81
+ const groupValue = resolveProp(groupByKey, d.datum);
82
+ if (groupValue === lastGroupValue || group.length === 0) {
83
+ group.push(d);
84
+ lastGroupValue = groupValue;
85
+ } else {
86
+ // new group
87
+ group = [d];
88
+ groups.push(group);
89
+ lastGroupValue = groupValue;
90
+ }
91
+ }
92
+ return groups.filter((d) => d.length > 0);
93
+ }
94
+
95
+ const groupByKey = $derived(args.z || args.fill) as ChannelAccessor<Datum> | null;
96
+ </script>
97
+
98
+ <Mark
99
+ type="trail"
100
+ channels={['x', 'y', 'opacity', 'fill', 'fillOpacity', 'r']}
101
+ required={['x', 'y']}
102
+ {...args}>
103
+ {#snippet children({ mark, usedScales, scaledData })}
104
+ {#if scaledData.length > 0}
105
+ {@const groupedTrailData = groupIndex(scaledData, groupByKey)}
106
+ {#if canvas}
107
+ <!-- todo -->
108
+ <TrailCanvas
109
+ {curve}
110
+ {cap}
111
+ {tension}
112
+ {resolution}
113
+ {usedScales}
114
+ data={groupedTrailData}
115
+ options={args} />
116
+ {:else}
117
+ <g class={['trail', className]}>
118
+ {#each groupedTrailData as trailData, i (i)}
119
+ {@const samples = trailData.map((d) => ({
120
+ x: Number(d.x),
121
+ y: Number(d.y),
122
+ r: Number(d.r ?? 0)
123
+ })) satisfies TrailSample[]}
124
+ {@const defined = trailData.map(
125
+ (d) =>
126
+ d.valid &&
127
+ d.r >= 0 &&
128
+ (resolveProp(options.defined, d.datum, true) ?? true)
129
+ )}
130
+ {@const pathString = trailPath(samples, defined, d3Path(), {
131
+ curve,
132
+ cap,
133
+ tension,
134
+ ...(typeof resolution === 'number'
135
+ ? { samplesPerSegment: resolution }
136
+ : {})
137
+ })}
138
+ {@const [style, styleClass] = resolveStyles(
139
+ plot,
140
+ trailData[0],
141
+ {
142
+ ...args
143
+ },
144
+ 'fill',
145
+ usedScales
146
+ )}
147
+ <path
148
+ d={pathString}
149
+ {style}
150
+ class={styleClass}
151
+ {@attach addEventHandlers({
152
+ plot,
153
+ options: mark.options,
154
+ datum: trailData[0].datum
155
+ })} />
156
+ {/each}
157
+ </g>
158
+ {/if}
159
+ {/if}
160
+ {/snippet}
161
+ </Mark>
@@ -0,0 +1,107 @@
1
+ import type { DataRecord, ChannelAccessor, ConstantAccessor, RawValue, CurveName } from '../types';
2
+ import type { CurveFactory } from 'd3-shape';
3
+ declare function $$render<Datum extends DataRecord>(): {
4
+ props: Omit<Partial<{
5
+ filter: ConstantAccessor<boolean, Datum>;
6
+ facet: "auto" | "include" | "exclude";
7
+ fx: ChannelAccessor<Datum>;
8
+ fy: ChannelAccessor<Datum>;
9
+ dx: ConstantAccessor<number, Datum>;
10
+ dy: ConstantAccessor<number, Datum>;
11
+ dodgeX: import("../transforms/dodge").DodgeXOptions;
12
+ dodgeY: import("../transforms/dodge").DodgeYOptions;
13
+ fill: ChannelAccessor<Datum>;
14
+ fillOpacity: ConstantAccessor<number, Datum>;
15
+ sort: ((a: RawValue, b: RawValue) => number) | {
16
+ channel: string;
17
+ order?: "ascending" | "descending";
18
+ } | ConstantAccessor<RawValue, Datum>;
19
+ stroke: ChannelAccessor<Datum>;
20
+ strokeWidth: ConstantAccessor<number, Datum>;
21
+ strokeOpacity: ConstantAccessor<number, Datum>;
22
+ strokeLinejoin: ConstantAccessor<import("csstype").Property.StrokeLinejoin, Datum>;
23
+ strokeLinecap: ConstantAccessor<import("csstype").Property.StrokeLinecap, Datum>;
24
+ strokeMiterlimit: ConstantAccessor<number, Datum>;
25
+ opacity: ChannelAccessor<Datum>;
26
+ strokeDasharray: ConstantAccessor<string, Datum>;
27
+ strokeDashoffset: ConstantAccessor<number, Datum>;
28
+ mixBlendMode: ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
29
+ clipPath: string;
30
+ mask: string;
31
+ imageFilter: ConstantAccessor<string, Datum>;
32
+ shapeRendering: ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
33
+ paintOrder: ConstantAccessor<string, Datum>;
34
+ onclick: import("svelte/elements").MouseEventHandler<SVGPathElement>;
35
+ ondblclick: import("svelte/elements").MouseEventHandler<SVGPathElement>;
36
+ onmouseup: import("svelte/elements").MouseEventHandler<SVGPathElement>;
37
+ onmousedown: import("svelte/elements").MouseEventHandler<SVGPathElement>;
38
+ onmouseenter: import("svelte/elements").MouseEventHandler<SVGPathElement>;
39
+ onmousemove: import("svelte/elements").MouseEventHandler<SVGPathElement>;
40
+ onmouseleave: import("svelte/elements").MouseEventHandler<SVGPathElement>;
41
+ onmouseout: import("svelte/elements").MouseEventHandler<SVGPathElement>;
42
+ onmouseover: import("svelte/elements").MouseEventHandler<SVGPathElement>;
43
+ onpointercancel: import("svelte/elements").MouseEventHandler<SVGPathElement>;
44
+ onpointerdown: import("svelte/elements").MouseEventHandler<SVGPathElement>;
45
+ onpointerup: import("svelte/elements").MouseEventHandler<SVGPathElement>;
46
+ onpointerenter: import("svelte/elements").MouseEventHandler<SVGPathElement>;
47
+ onpointerleave: import("svelte/elements").MouseEventHandler<SVGPathElement>;
48
+ onpointermove: import("svelte/elements").MouseEventHandler<SVGPathElement>;
49
+ onpointerover: import("svelte/elements").MouseEventHandler<SVGPathElement>;
50
+ onpointerout: import("svelte/elements").MouseEventHandler<SVGPathElement>;
51
+ ondrag: import("svelte/elements").MouseEventHandler<SVGPathElement>;
52
+ ondrop: import("svelte/elements").MouseEventHandler<SVGPathElement>;
53
+ ondragstart: import("svelte/elements").MouseEventHandler<SVGPathElement>;
54
+ ondragenter: import("svelte/elements").MouseEventHandler<SVGPathElement>;
55
+ ondragleave: import("svelte/elements").MouseEventHandler<SVGPathElement>;
56
+ ondragover: import("svelte/elements").MouseEventHandler<SVGPathElement>;
57
+ ondragend: import("svelte/elements").MouseEventHandler<SVGPathElement>;
58
+ ontouchstart: import("svelte/elements").MouseEventHandler<SVGPathElement>;
59
+ ontouchmove: import("svelte/elements").MouseEventHandler<SVGPathElement>;
60
+ ontouchend: import("svelte/elements").MouseEventHandler<SVGPathElement>;
61
+ ontouchcancel: import("svelte/elements").MouseEventHandler<SVGPathElement>;
62
+ oncontextmenu: import("svelte/elements").MouseEventHandler<SVGPathElement>;
63
+ onwheel: import("svelte/elements").MouseEventHandler<SVGPathElement>;
64
+ class: string;
65
+ style: string;
66
+ cursor: ConstantAccessor<import("csstype").Property.Cursor, Datum>;
67
+ }>, "stroke" | "strokeWidth" | "strokeDasharray"> & {
68
+ data?: Datum[];
69
+ x?: ChannelAccessor<Datum>;
70
+ y?: ChannelAccessor<Datum>;
71
+ z?: ChannelAccessor<Datum>;
72
+ r?: ChannelAccessor<Datum>;
73
+ curve?: CurveName | CurveFactory;
74
+ tension?: number;
75
+ sort?: ConstantAccessor<RawValue, Datum> | {
76
+ channel: "stroke" | "fill";
77
+ };
78
+ defined?: ConstantAccessor<boolean, Datum>;
79
+ canvas?: boolean;
80
+ cap?: "butt" | "round";
81
+ /**
82
+ * Samples per segment for curve interpolation
83
+ */
84
+ resolution?: number | "auto";
85
+ };
86
+ exports: {};
87
+ bindings: "";
88
+ slots: {};
89
+ events: {};
90
+ };
91
+ declare class __sveltets_Render<Datum extends DataRecord> {
92
+ props(): ReturnType<typeof $$render<Datum>>['props'];
93
+ events(): ReturnType<typeof $$render<Datum>>['events'];
94
+ slots(): ReturnType<typeof $$render<Datum>>['slots'];
95
+ bindings(): "";
96
+ exports(): {};
97
+ }
98
+ interface $$IsomorphicComponent {
99
+ 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']>> & {
100
+ $$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
101
+ } & ReturnType<__sveltets_Render<Datum>['exports']>;
102
+ <Datum extends DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
103
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
104
+ }
105
+ declare const Trail: $$IsomorphicComponent;
106
+ type Trail<Datum extends DataRecord> = InstanceType<typeof Trail<Datum>>;
107
+ export default Trail;
@@ -29,7 +29,6 @@
29
29
  canvas?: boolean;
30
30
  }
31
31
  import type {
32
- PlotContext,
33
32
  DataRecord,
34
33
  BaseMarkProps,
35
34
  ChannelAccessor,
@@ -47,6 +46,7 @@
47
46
  import { addEventHandlers } from './helpers/events.js';
48
47
  import { indexData } from '../transforms/recordize.js';
49
48
  import { getPlotDefaults } from '../hooks/plotDefaults.js';
49
+ import { usePlot } from '../hooks/usePlot.svelte.js';
50
50
 
51
51
  const defaultRadius = 3.5;
52
52
 
@@ -72,8 +72,7 @@
72
72
  ...markProps
73
73
  });
74
74
 
75
- const { getPlotState } = getContext<PlotContext>('svelteplot');
76
- const plot = $derived(getPlotState());
75
+ const plot = usePlot();
77
76
 
78
77
  const { getTestFacet } = getContext<FacetContext>('svelteplot/facet');
79
78
  const testFacet = $derived(getTestFacet());
@@ -203,7 +202,7 @@
203
202
  : `translate(0, ${d.length / 2})`}"
204
203
  {style}
205
204
  {@attach addEventHandlers({
206
- getPlotState,
205
+ plot,
207
206
  options: args,
208
207
  datum: d?.datum
209
208
  })}
@@ -31,6 +31,7 @@ declare function $$render<Datum extends DataRecord>(): {
31
31
  strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Datum>;
32
32
  mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
33
33
  clipPath: string;
34
+ mask: string;
34
35
  imageFilter: import("../types/index.js").ConstantAccessor<string, Datum>;
35
36
  shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
36
37
  paintOrder: import("../types/index.js").ConstantAccessor<string, Datum>;
@@ -15,9 +15,9 @@
15
15
  import { intervalX, recordizeX, sort, stackX } from '../transforms';
16
16
  import type { StackOptions } from '../transforms/stack';
17
17
  import Mark from '../Mark.svelte';
18
- import { getContext } from 'svelte';
19
18
  import { resolveProp, resolveStyles } from '../helpers/resolve';
20
19
  import { roundedRect } from '../helpers/roundedRect';
20
+ import { usePlot } from '../hooks/usePlot.svelte.js';
21
21
 
22
22
  interface WaffleXMarkProps
23
23
  extends BaseMarkProps<Datum>, LinkableMarkProps<Datum>, WaffleOptions<Datum> {
@@ -58,8 +58,7 @@
58
58
  ...options
59
59
  }: WaffleXMarkProps = $derived({ ...DEFAULTS, ...markProps });
60
60
 
61
- const { getPlotState } = getContext<PlotContext>('svelteplot');
62
- const plot = $derived(getPlotState());
61
+ const plot = usePlot();
63
62
 
64
63
  const args = $derived(
65
64
  stackX(
@@ -28,6 +28,7 @@ declare function $$render<Datum extends DataRecord>(): {
28
28
  strokeDashoffset: import("../types").ConstantAccessor<number, Datum>;
29
29
  mixBlendMode: import("../types").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
30
30
  clipPath: string;
31
+ mask: string;
31
32
  imageFilter: import("../types").ConstantAccessor<string, Datum>;
32
33
  shapeRendering: import("../types").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
33
34
  paintOrder: import("../types").ConstantAccessor<string, Datum>;
@@ -8,17 +8,16 @@
8
8
  ChannelAccessor,
9
9
  BaseMarkProps,
10
10
  LinkableMarkProps,
11
- PlotContext,
12
11
  BorderRadius
13
12
  } from '../types';
14
13
  import { wafflePolygon, type WaffleOptions } from './helpers/waffle';
15
14
  import { getPlotDefaults } from '../hooks/plotDefaults';
16
- import { getContext } from 'svelte';
17
15
  import { intervalY, recordizeY, sort, stackY } from '../transforms';
18
16
  import Mark from '../Mark.svelte';
19
17
  import { resolveProp, resolveStyles } from '../helpers/resolve';
20
18
  import { roundedRect } from '../helpers/roundedRect';
21
19
  import GroupMultiple from './helpers/GroupMultiple.svelte';
20
+ import { usePlot } from '../hooks/usePlot.svelte.js';
22
21
 
23
22
  interface WaffleYMarkProps
24
23
  extends BaseMarkProps<Datum>, LinkableMarkProps<Datum>, WaffleOptions<Datum> {
@@ -56,8 +55,7 @@
56
55
  ...options
57
56
  }: WaffleYMarkProps = $derived({ ...DEFAULTS, ...markProps });
58
57
 
59
- const { getPlotState } = getContext<PlotContext>('svelteplot');
60
- const plot = $derived(getPlotState());
58
+ const plot = usePlot();
61
59
 
62
60
  const args = $derived(
63
61
  stackY(
@@ -27,6 +27,7 @@ declare function $$render<Datum extends DataRecord>(): {
27
27
  strokeDashoffset: import("../types").ConstantAccessor<number, Datum>;
28
28
  mixBlendMode: import("../types").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: import("../types").ConstantAccessor<string, Datum>;
31
32
  shapeRendering: import("../types").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
32
33
  paintOrder: import("../types").ConstantAccessor<string, Datum>;
@@ -2,17 +2,16 @@
2
2
  import type {
3
3
  Mark,
4
4
  BaseMarkProps,
5
- PlotContext,
6
5
  ScaledDataRecord,
7
6
  UsedScales
8
7
  } from '../../types/index.js';
9
8
  import { resolveProp, resolveScaledStyleProps } from '../../helpers/resolve.js';
10
- import { getContext } from 'svelte';
11
9
  import { type Area } from 'd3-shape';
12
10
  import CanvasLayer from './CanvasLayer.svelte';
13
11
  import type { Attachment } from 'svelte/attachments';
14
12
  import { devicePixelRatio } from 'svelte/reactivity/window';
15
13
  import { resolveColor } from './canvas.js';
14
+ import { usePlot } from '../../hooks/usePlot.svelte.js';
16
15
 
17
16
  let {
18
17
  mark,
@@ -26,8 +25,7 @@
26
25
  areaPath: Area<ScaledDataRecord>;
27
26
  } = $props();
28
27
 
29
- const { getPlotState } = getContext<PlotContext>('svelteplot');
30
- const plot = $derived(getPlotState());
28
+ const plot = usePlot();
31
29
 
32
30
  function maybeOpacity(value: unknown) {
33
31
  return value == null ? 1 : +value;
@@ -0,0 +1,271 @@
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(xProp, 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
125
+ .map((row) => {
126
+ const medianKey = groupChannels[xProp];
127
+ const p25Key = groupChannels[x1Prop];
128
+ const p75Key = groupChannels[x2Prop];
129
+ const groupKey = groupChannels[yProp];
130
+
131
+ const iqr = row[p75Key] - row[p25Key];
132
+ const whisker = iqr * 1.5;
133
+ const lower = row[p25Key] - whisker;
134
+ const upper = row[p75Key] + whisker;
135
+ const data = row[groupChannels.fill].map((d) => ({
136
+ ...d,
137
+ [orientation === 'y' ? Y : X]: resolveChannel(xProp, d, {
138
+ x,
139
+ y
140
+ })
141
+ }));
142
+ const valueSym = orientation === 'y' ? Y : X;
143
+ const groupSym = orientation === 'y' ? X : Y;
144
+ const outliers = data.filter((d) => d[valueSym] < lower || d[valueSym] > upper);
145
+ const inside = data
146
+ .filter((d) => d[valueSym] >= lower && d[valueSym] <= upper)
147
+ .sort((a, b) => a[valueSym] - b[valueSym]);
148
+
149
+ return {
150
+ ...data[0],
151
+ [SORT_REF]: row[groupChannels.fill]?.[0],
152
+ [groupSym]: row[groupKey],
153
+ [P25]: row[p25Key],
154
+ [MEDIAN]: row[medianKey],
155
+ [P75]: row[p75Key],
156
+ [MIN]: inside.length ? inside[0][valueSym] : null,
157
+ [MAX]: inside.length ? inside.at(-1)[valueSym] : null,
158
+ [FX]: resolveChannel('fx', data[0], { fx }, null),
159
+ [FY]: resolveChannel('fy', data[0], { fy }, null),
160
+ [OUTLIERS]: outliers
161
+ };
162
+ })
163
+ .filter(Boolean);
164
+
165
+ const stripSortRef = ({ [SORT_REF]: _, ...rest }) => rest;
166
+
167
+ if (!sort) return boxes.map(stripSortRef);
168
+
169
+ const [sort_, direction] = maybeSort(sort);
170
+
171
+ const sortAccessor =
172
+ typeof sort === 'function'
173
+ ? (d) => sort(d[SORT_REF])
174
+ : (d) => {
175
+ switch (sort_) {
176
+ case 'min':
177
+ return d[MIN];
178
+ case 'max':
179
+ return d[MAX];
180
+ case 'p25':
181
+ return d[P25];
182
+ case 'p75':
183
+ return d[P75];
184
+ case 'median':
185
+ default:
186
+ return d[MEDIAN];
187
+ }
188
+ };
189
+
190
+ return boxes
191
+ .toSorted(
192
+ (a, b) =>
193
+ compareValues(sortAccessor(a), sortAccessor(b)) *
194
+ direction *
195
+ (orientation === 'x' ? -1 : 1)
196
+ )
197
+ .map(stripSortRef);
198
+ });
199
+
200
+ function maybeSort(
201
+ sort: string | ((d: Datum) => RawValue) | undefined
202
+ ): [string | ((d: Datum) => RawValue), 1 | -1] {
203
+ if (typeof sort !== 'string') return [sort, 1];
204
+ if (sort.startsWith('-')) {
205
+ return [sort.slice(1), -1];
206
+ }
207
+ return [sort, 1];
208
+ }
209
+
210
+ const valueSymbol = $derived(orientation === 'y' ? Y : X);
211
+ const groupSymbol = $derived(orientation === 'y' ? X : Y);
212
+ const length = $derived(className ? 2 : grouped.length);
213
+ const baseClass = $derived(`box-${orientation} ${className || ''}`);
214
+ </script>
215
+
216
+ <GroupMultiple class={baseClass} {length}>
217
+ <RuleMark
218
+ data={boxData}
219
+ {...{ [yProp]: groupSymbol, [x1Prop]: MIN, [x2Prop]: P25 }}
220
+ {stroke}
221
+ {...rule || {}}
222
+ {...facets}
223
+ {...sortProps} />
224
+ <RuleMark
225
+ data={boxData}
226
+ {...{ [yProp]: groupSymbol, [x1Prop]: P75, [x2Prop]: MAX }}
227
+ {stroke}
228
+ {...rule || {}}
229
+ {...facets} />
230
+ <BarMark
231
+ data={boxData}
232
+ {...{ [yProp]: groupSymbol, [x1Prop]: P25, [x2Prop]: P75 }}
233
+ {fill}
234
+ {stroke}
235
+ {...facets}
236
+ {...bar || {}} />
237
+ {#if tickMedian}
238
+ <TickMark
239
+ data={boxData}
240
+ {...{ [yProp]: groupSymbol, [xProp]: MEDIAN }}
241
+ {...facets}
242
+ {stroke}
243
+ strokeWidth={2}
244
+ {...typeof tickMedian === 'object' ? tickMedian : {}} />
245
+ {/if}
246
+ {#if tickMinMax}
247
+ <TickMark
248
+ data={boxData}
249
+ {...{ [yProp]: groupSymbol, [xProp]: MIN }}
250
+ {stroke}
251
+ {...facets}
252
+ inset="20%"
253
+ {...typeof tickMinMax === 'object' ? tickMinMax : {}} />
254
+ <TickMark
255
+ data={boxData}
256
+ {...{ [yProp]: groupSymbol, [xProp]: MAX }}
257
+ {stroke}
258
+ {...facets}
259
+ inset="20%"
260
+ {...typeof tickMinMax === 'object' ? tickMinMax : {}} />
261
+ {/if}
262
+ <Dot
263
+ data={boxData.map((d) => d[OUTLIERS]).flat()}
264
+ {x}
265
+ {y}
266
+ {fx}
267
+ {fy}
268
+ {fill}
269
+ {stroke}
270
+ {...dot || {}} />
271
+ </GroupMultiple>