svelteplot 0.9.2 → 0.10.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.
@@ -19,8 +19,10 @@
19
19
  * length of the tick. Defaults to 10 pixel
20
20
  */
21
21
  tickLength?: ConstantAccessor<number, Datum>;
22
+ canvas?: boolean;
22
23
  }
23
24
  import Mark from '../Mark.svelte';
25
+ import TickCanvas from './helpers/TickCanvas.svelte';
24
26
  import { getContext } from 'svelte';
25
27
  import { resolveChannel, resolveProp, resolveScaledStyles } from '../helpers/resolve.js';
26
28
  import type {
@@ -47,6 +49,7 @@
47
49
  const {
48
50
  data = [{}],
49
51
  class: className = '',
52
+ canvas = false,
50
53
  ...options
51
54
  }: TickYMarkProps = $derived({
52
55
  ...DEFAULTS,
@@ -64,40 +67,44 @@
64
67
  channels={['x', 'y', 'stroke', 'opacity', 'strokeOpacity']}
65
68
  {...markProps}
66
69
  {...args}>
67
- {#snippet children({ mark, usedScales })}
68
- <g class="tick-y">
69
- {#each args.data as datum, i (i)}
70
- {#if testFacet(datum, mark.options) && testFilter(datum, args)}
71
- {@const y_ = resolveChannel('y', datum, args)}
72
- {@const x_ = resolveChannel('x', datum, args)}
73
- {@const inset_ = resolveProp(args.inset, datum, 0)}
74
- {@const tickLength_ = resolveProp(args.tickLength, datum, 10)}
75
- {@const dx_ = resolveProp(args.dx, datum, 0)}
76
- {@const dy_ = resolveProp(args.dy, datum, 0)}
77
- {#if isValid(y_) && (isValid(x_) || args.x == null)}
78
- {@const y = usedScales.y ? projectY('y', plot.scales, y_) : y_}
79
- {@const x1 =
80
- args.x != null
81
- ? usedScales.x
82
- ? projectX('x1', plot.scales, x_)
83
- : x_
84
- : plot.options.marginLeft}
85
- {@const x2 =
86
- args.x != null
87
- ? usedScales.x
88
- ? projectX('x2', plot.scales, x_)
89
- : x_
90
- : plot.options.marginLeft + plot.facetWidth}
91
- {@const inset = parseInset(inset_, Math.abs(x2 - x1))}
92
- <line
93
- transform="translate({dx_}, {y + dy_})"
94
- style={resolveScaledStyles(datum, args, usedScales, plot, 'stroke')}
95
- x1={x1 + inset + (x1 === x2 ? tickLength_ * 0.5 : 0)}
96
- x2={x2 - inset - (x1 === x2 ? tickLength_ * 0.5 : 0)} />
70
+ {#snippet children({ mark, usedScales, scaledData })}
71
+ {#if canvas}
72
+ <TickCanvas data={scaledData} options={args} {usedScales} orientation="horizontal" />
73
+ {:else}
74
+ <g class="tick-y">
75
+ {#each args.data as datum, i (i)}
76
+ {#if testFacet(datum, mark.options) && testFilter(datum, args)}
77
+ {@const y_ = resolveChannel('y', datum, args)}
78
+ {@const x_ = resolveChannel('x', datum, args)}
79
+ {@const inset_ = resolveProp(args.inset, datum, 0)}
80
+ {@const tickLength_ = resolveProp(args.tickLength, datum, 10)}
81
+ {@const dx_ = resolveProp(args.dx, datum, 0)}
82
+ {@const dy_ = resolveProp(args.dy, datum, 0)}
83
+ {#if isValid(y_) && (isValid(x_) || args.x == null)}
84
+ {@const y = usedScales.y ? projectY('y', plot.scales, y_) : y_}
85
+ {@const x1 =
86
+ args.x != null
87
+ ? usedScales.x
88
+ ? projectX('x1', plot.scales, x_)
89
+ : x_
90
+ : plot.options.marginLeft}
91
+ {@const x2 =
92
+ args.x != null
93
+ ? usedScales.x
94
+ ? projectX('x2', plot.scales, x_)
95
+ : x_
96
+ : plot.options.marginLeft + plot.facetWidth}
97
+ {@const inset = parseInset(inset_, Math.abs(x2 - x1))}
98
+ <line
99
+ transform="translate({dx_}, {y + dy_})"
100
+ style={resolveScaledStyles(datum, args, usedScales, plot, 'stroke')}
101
+ x1={x1 + inset + (x1 === x2 ? tickLength_ * 0.5 : 0)}
102
+ x2={x2 - inset - (x1 === x2 ? tickLength_ * 0.5 : 0)} />
103
+ {/if}
97
104
  {/if}
98
- {/if}
99
- {/each}
100
- </g>
105
+ {/each}
106
+ </g>
107
+ {/if}
101
108
  {/snippet}
102
109
  </Mark>
103
110
 
@@ -79,6 +79,7 @@ declare function $$render<Datum extends DataRow>(): {
79
79
  * length of the tick. Defaults to 10 pixel
80
80
  */
81
81
  tickLength?: ConstantAccessor<number, Datum>;
82
+ canvas?: boolean;
82
83
  };
83
84
  exports: {};
84
85
  bindings: "";
@@ -48,7 +48,7 @@
48
48
  import { getPlotDefaults } from '../hooks/plotDefaults.js';
49
49
  import { usePlot } from '../hooks/usePlot.svelte.js';
50
50
 
51
- const defaultRadius = 3.5;
51
+ const defaultRadius = 3;
52
52
 
53
53
  // The size of the arrowhead is proportional to its length, but we still allow
54
54
  // the relative size of the head to be controlled via the mark's width option;
@@ -58,14 +58,14 @@
58
58
 
59
59
  let markProps: VectorMarkProps = $props();
60
60
  const DEFAULTS = {
61
- ...getPlotDefaults().vector
61
+ ...getPlotDefaults().vector,
62
+ r: defaultRadius
62
63
  };
63
64
  const {
64
65
  data = [{}],
65
66
  canvas,
66
67
  shape = 'arrow',
67
68
  anchor = 'middle',
68
- r = defaultRadius,
69
69
  ...options
70
70
  }: VectorMarkProps = $derived({
71
71
  ...DEFAULTS,
@@ -74,9 +74,6 @@
74
74
 
75
75
  const plot = usePlot();
76
76
 
77
- const { getTestFacet } = getContext<FacetContext>('svelteplot/facet');
78
- const testFacet = $derived(getTestFacet());
79
-
80
77
  const shapeArrow: ShapeRenderer = {
81
78
  draw(context: D3Path, l: number, r: number) {
82
79
  const wing = (l * r) / wingRatio;
@@ -168,15 +165,14 @@
168
165
  'strokeOpacity'
169
166
  ]}
170
167
  {...args}>
171
- {#snippet children({ mark, scaledData, usedScales })}
168
+ {#snippet children({ scaledData, usedScales })}
172
169
  <g class="vector" data-l={usedScales.length}>
173
170
  {#if canvas}
174
171
  <text x="30" y="30" style="color:red"
175
172
  >implement canvas rendering for vector mark</text>
176
173
  {:else}
177
174
  {#each scaledData as d, i (i)}
178
- {@const r = resolveChannel('r', d.datum, { r: 3, ...args })}
179
- {#if d.valid && isValid(r)}
175
+ {#if d.valid && isValid(d.r)}
180
176
  {@const [style, styleClass] = resolveStyles(
181
177
  plot,
182
178
  d,
@@ -190,7 +186,7 @@
190
186
  usedScales
191
187
  )}
192
188
  <path
193
- d={shapePath(shape, d.length, r)}
189
+ d={shapePath(shape, d.length, d.r)}
194
190
  transform="translate({d.x}, {d.y}) rotate({resolveProp(
195
191
  args.rotate,
196
192
  d.datum,
@@ -22,9 +22,10 @@
22
22
  id: string;
23
23
  shape: MarkerShape;
24
24
  color: string;
25
+ markerScale?: number;
25
26
  };
26
27
 
27
- let { id, shape, color }: MarkerProps = $props();
28
+ let { id, shape, color, markerScale = 1 }: MarkerProps = $props();
28
29
 
29
30
  const tickMarker = (orient: number | 'auto') => ({
30
31
  viewBox: '-3 -3 6 6',
@@ -81,10 +82,10 @@
81
82
  <marker
82
83
  {id}
83
84
  viewBox={MARKERS[shape].viewBox || '-5 -5 10 10'}
84
- markerWidth={MARKERS[shape].width}
85
+ markerWidth={MARKERS[shape].width * markerScale}
85
86
  orient={MARKERS[shape].orient}
86
- markerHeight={MARKERS[shape].height}
87
- stroke-width="1.5"
87
+ markerHeight={MARKERS[shape].height * markerScale}
88
+ stroke-width={Math.max(0, Math.min(100, 1.5 / markerScale))}
88
89
  {...markerColors}>
89
90
  {#if shape === 'dot' || shape === 'circle' || shape === 'circle-stroke'}
90
91
  <circle r={defaultDotRadius} />
@@ -3,6 +3,7 @@ type MarkerProps = {
3
3
  id: string;
4
4
  shape: MarkerShape;
5
5
  color: string;
6
+ markerScale?: number;
6
7
  };
7
8
  /** Marker is a helper component that creates a marker for use in a line or path. */
8
9
  declare const Marker: import("svelte").Component<MarkerProps, {}, "">;
@@ -39,6 +39,10 @@
39
39
  * shorthand for setting all markers
40
40
  */
41
41
  marker?: boolean | MarkerShape;
42
+ /**
43
+ * scale factor for marker size, relative to the line stroke width
44
+ */
45
+ markerScale?: ConstantAccessor<number>;
42
46
  /**
43
47
  * path string
44
48
  */
@@ -61,6 +65,7 @@
61
65
  markerMid,
62
66
  markerEnd,
63
67
  marker,
68
+ markerScale,
64
69
  d,
65
70
  dInv,
66
71
  style,
@@ -88,6 +93,7 @@
88
93
  // use reversed path if the path is not left to right
89
94
  const textPath = $derived(!text || leftToRight ? d : dInv);
90
95
  const strokeWidth_ = $derived(resolveProp(strokeWidth, datum, 1.4));
96
+ const markerScale_ = $derived(resolveProp(markerScale, datum, 1));
91
97
  </script>
92
98
 
93
99
  <g
@@ -107,7 +113,8 @@
107
113
  <Marker
108
114
  id={markerId}
109
115
  shape={marker === true ? 'circle' : resolveProp(marker, datum)}
110
- {color} />
116
+ {color}
117
+ markerScale={markerScale_} />
111
118
  {/if}
112
119
  {/each}
113
120
  {#if mark.options.onmouseenter || mark.options.onclick}
@@ -87,6 +87,10 @@ declare function $$render<Datum extends DataRecord>(): {
87
87
  * shorthand for setting all markers
88
88
  */
89
89
  marker?: boolean | MarkerShape;
90
+ /**
91
+ * scale factor for marker size, relative to the line stroke width
92
+ */
93
+ markerScale?: ConstantAccessor<number>;
90
94
  /**
91
95
  * path string
92
96
  */
@@ -0,0 +1,175 @@
1
+ <!--
2
+ @component
3
+ Helper component for rendering Rule marks (RuleX and RuleY) in canvas
4
+ -->
5
+ <script lang="ts" generics="Datum extends DataRecord">
6
+ interface RuleCanvasProps {
7
+ data: ScaledDataRecord<Datum>[];
8
+ options: BaseMarkProps<Datum> & {
9
+ inset?: ConstantAccessor<number, Datum>;
10
+ insetTop?: ConstantAccessor<number, Datum>;
11
+ insetBottom?: ConstantAccessor<number, Datum>;
12
+ insetLeft?: ConstantAccessor<number, Datum>;
13
+ insetRight?: ConstantAccessor<number, Datum>;
14
+ };
15
+ usedScales: UsedScales;
16
+ orientation: 'vertical' | 'horizontal';
17
+ marginTop?: number;
18
+ marginLeft?: number;
19
+ facetWidth?: number;
20
+ facetHeight?: number;
21
+ }
22
+
23
+ import type {
24
+ BaseMarkProps,
25
+ ConstantAccessor,
26
+ DataRecord,
27
+ ScaledDataRecord,
28
+ UsedScales
29
+ } from '../../types/index.js';
30
+ import { resolveProp, resolveScaledStyleProps } from '../../helpers/resolve.js';
31
+ import type { Attachment } from 'svelte/attachments';
32
+ import { devicePixelRatio } from 'svelte/reactivity/window';
33
+ import CanvasLayer from './CanvasLayer.svelte';
34
+ import { resolveColor } from './canvas.js';
35
+ import { usePlot } from '../../hooks/usePlot.svelte.js';
36
+
37
+ const plot = usePlot();
38
+
39
+ let {
40
+ data,
41
+ options,
42
+ usedScales,
43
+ orientation,
44
+ marginTop = plot.options.marginTop,
45
+ marginLeft = plot.options.marginLeft,
46
+ facetWidth = plot.facetWidth,
47
+ facetHeight = plot.facetHeight
48
+ }: RuleCanvasProps = $props();
49
+
50
+ function maybeOpacity(value: unknown) {
51
+ return value == null ? 1 : +value;
52
+ }
53
+
54
+ const render: Attachment = (canvasEl: Element) => {
55
+ const canvas = canvasEl as HTMLCanvasElement;
56
+ const context = canvas.getContext('2d');
57
+
58
+ $effect(() => {
59
+ if (context) {
60
+ context.resetTransform();
61
+ context.scale(devicePixelRatio.current ?? 1, devicePixelRatio.current ?? 1);
62
+ for (const datum of data) {
63
+ if (!datum.valid) continue;
64
+
65
+ let { stroke, ...restStyles } = resolveScaledStyleProps(
66
+ datum.datum,
67
+ options,
68
+ usedScales,
69
+ plot,
70
+ 'stroke'
71
+ );
72
+
73
+ const opacity = maybeOpacity(restStyles['opacity']);
74
+ const strokeOpacity = maybeOpacity(restStyles['stroke-opacity']);
75
+
76
+ stroke = resolveColor(stroke || 'currentColor', canvas);
77
+
78
+ if (stroke && stroke !== 'none') {
79
+ const resolvedLinecap = restStyles['stroke-linecap'] as
80
+ | CanvasLineCap
81
+ | undefined
82
+ | null;
83
+ const strokeWidth = resolveProp(
84
+ options.strokeWidth,
85
+ datum.datum,
86
+ 1
87
+ ) as number;
88
+ context.lineCap =
89
+ resolvedLinecap === 'round' ||
90
+ resolvedLinecap === 'square' ||
91
+ resolvedLinecap === 'butt'
92
+ ? resolvedLinecap
93
+ : 'butt';
94
+ context.lineWidth = strokeWidth;
95
+ context.strokeStyle = stroke;
96
+ context.globalAlpha = opacity * strokeOpacity;
97
+
98
+ context.beginPath();
99
+
100
+ if (orientation === 'vertical') {
101
+ // RuleX: vertical line
102
+ const x = datum.x;
103
+ const inset = +(resolveProp(options.inset, datum.datum, 0) as number);
104
+ const insetTop = +(resolveProp(
105
+ options.insetTop,
106
+ datum.datum,
107
+ 0
108
+ ) as number);
109
+ const insetBottom = +(resolveProp(
110
+ options.insetBottom,
111
+ datum.datum,
112
+ 0
113
+ ) as number);
114
+
115
+ const y1 =
116
+ (inset || insetTop) +
117
+ (datum.y1 != null ? datum.y1 : marginTop + (datum.dy ?? 0));
118
+ const y2 =
119
+ (datum.y2 != null
120
+ ? datum.y2
121
+ : facetHeight + marginTop + (datum.dy ?? 0)) -
122
+ (inset || insetBottom);
123
+
124
+ if (x != null) {
125
+ context.moveTo(x, y1);
126
+ context.lineTo(x, y2);
127
+ }
128
+ } else {
129
+ // RuleY: horizontal line
130
+ const y = datum.y;
131
+ const inset = +(resolveProp(options.inset, datum.datum, 0) as number);
132
+ const insetLeft = +(resolveProp(
133
+ options.insetLeft,
134
+ datum.datum,
135
+ 0
136
+ ) as number);
137
+ const insetRight = +(resolveProp(
138
+ options.insetRight,
139
+ datum.datum,
140
+ 0
141
+ ) as number);
142
+
143
+ const x1 =
144
+ (inset || insetLeft) +
145
+ (datum.x1 != null ? datum.x1 : marginLeft + (datum.dx ?? 0));
146
+ const x2 =
147
+ (datum.x2 != null
148
+ ? datum.x2
149
+ : facetWidth + marginLeft + (datum.dx ?? 0)) -
150
+ (inset || insetRight);
151
+
152
+ if (y != null) {
153
+ context.moveTo(x1, y);
154
+ context.lineTo(x2, y);
155
+ }
156
+ }
157
+
158
+ context.stroke();
159
+ }
160
+ }
161
+ }
162
+
163
+ return () => {
164
+ context?.clearRect(
165
+ 0,
166
+ 0,
167
+ plot.width * (devicePixelRatio.current ?? 1),
168
+ plot.height * (devicePixelRatio.current ?? 1)
169
+ );
170
+ };
171
+ });
172
+ };
173
+ </script>
174
+
175
+ <CanvasLayer {@attach render} />
@@ -0,0 +1,41 @@
1
+ import type { BaseMarkProps, ConstantAccessor, DataRecord, ScaledDataRecord, UsedScales } from '../../types/index.js';
2
+ declare function $$render<Datum extends DataRecord>(): {
3
+ props: {
4
+ data: ScaledDataRecord<Datum>[];
5
+ options: BaseMarkProps<Datum> & {
6
+ inset?: ConstantAccessor<number, Datum>;
7
+ insetTop?: ConstantAccessor<number, Datum>;
8
+ insetBottom?: ConstantAccessor<number, Datum>;
9
+ insetLeft?: ConstantAccessor<number, Datum>;
10
+ insetRight?: ConstantAccessor<number, Datum>;
11
+ };
12
+ usedScales: UsedScales;
13
+ orientation: "vertical" | "horizontal";
14
+ marginTop?: number;
15
+ marginLeft?: number;
16
+ facetWidth?: number;
17
+ facetHeight?: number;
18
+ };
19
+ exports: {};
20
+ bindings: "";
21
+ slots: {};
22
+ events: {};
23
+ };
24
+ declare class __sveltets_Render<Datum extends DataRecord> {
25
+ props(): ReturnType<typeof $$render<Datum>>['props'];
26
+ events(): ReturnType<typeof $$render<Datum>>['events'];
27
+ slots(): ReturnType<typeof $$render<Datum>>['slots'];
28
+ bindings(): "";
29
+ exports(): {};
30
+ }
31
+ interface $$IsomorphicComponent {
32
+ 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']>> & {
33
+ $$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
34
+ } & ReturnType<__sveltets_Render<Datum>['exports']>;
35
+ <Datum extends DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
36
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
37
+ }
38
+ /** Helper component for rendering Rule marks (RuleX and RuleY) in canvas */
39
+ declare const RuleCanvas: $$IsomorphicComponent;
40
+ type RuleCanvas<Datum extends DataRecord> = InstanceType<typeof RuleCanvas<Datum>>;
41
+ export default RuleCanvas;