svelteplot 0.12.0 → 0.14.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 (54) hide show
  1. package/dist/core/Plot.svelte +3 -6
  2. package/dist/helpers/group.d.ts +1 -1
  3. package/dist/helpers/group.js +3 -3
  4. package/dist/helpers/scales.js +8 -0
  5. package/dist/helpers/vectorShapes.d.ts +13 -0
  6. package/dist/helpers/vectorShapes.js +57 -0
  7. package/dist/marks/Arrow.svelte +70 -59
  8. package/dist/marks/Arrow.svelte.d.ts +2 -0
  9. package/dist/marks/ColorLegend.svelte +7 -3
  10. package/dist/marks/Contour.svelte +684 -0
  11. package/dist/marks/Contour.svelte.d.ts +152 -0
  12. package/dist/marks/DelaunayLink.svelte +127 -0
  13. package/dist/marks/DelaunayLink.svelte.d.ts +175 -0
  14. package/dist/marks/DelaunayMesh.svelte +102 -0
  15. package/dist/marks/DelaunayMesh.svelte.d.ts +172 -0
  16. package/dist/marks/Density.svelte +461 -0
  17. package/dist/marks/Density.svelte.d.ts +87 -0
  18. package/dist/marks/Hull.svelte +103 -0
  19. package/dist/marks/Hull.svelte.d.ts +175 -0
  20. package/dist/marks/Image.svelte +37 -27
  21. package/dist/marks/Image.svelte.d.ts +2 -0
  22. package/dist/marks/Link.svelte +68 -50
  23. package/dist/marks/Link.svelte.d.ts +2 -0
  24. package/dist/marks/Raster.svelte +6 -1
  25. package/dist/marks/Vector.svelte +12 -81
  26. package/dist/marks/Vector.svelte.d.ts +2 -4
  27. package/dist/marks/Voronoi.svelte +118 -0
  28. package/dist/marks/Voronoi.svelte.d.ts +172 -0
  29. package/dist/marks/VoronoiMesh.svelte +109 -0
  30. package/dist/marks/VoronoiMesh.svelte.d.ts +172 -0
  31. package/dist/marks/helpers/ArrowCanvas.svelte +132 -0
  32. package/dist/marks/helpers/ArrowCanvas.svelte.d.ts +39 -0
  33. package/dist/marks/helpers/BaseAxisX.svelte +5 -7
  34. package/dist/marks/helpers/DensityCanvas.svelte +118 -0
  35. package/dist/marks/helpers/DensityCanvas.svelte.d.ts +18 -0
  36. package/dist/marks/helpers/GeoPathCanvas.svelte +125 -0
  37. package/dist/marks/helpers/GeoPathCanvas.svelte.d.ts +24 -0
  38. package/dist/marks/helpers/GeoPathGroup.svelte +103 -0
  39. package/dist/marks/helpers/GeoPathGroup.svelte.d.ts +37 -0
  40. package/dist/marks/helpers/ImageCanvas.svelte +126 -0
  41. package/dist/marks/helpers/ImageCanvas.svelte.d.ts +34 -0
  42. package/dist/marks/helpers/LinkCanvas.svelte +103 -0
  43. package/dist/marks/helpers/LinkCanvas.svelte.d.ts +32 -0
  44. package/dist/marks/helpers/PathGroup.svelte +100 -0
  45. package/dist/marks/helpers/PathGroup.svelte.d.ts +16 -0
  46. package/dist/marks/helpers/PathItems.svelte +112 -0
  47. package/dist/marks/helpers/PathItems.svelte.d.ts +16 -0
  48. package/dist/marks/helpers/VectorCanvas.svelte +127 -0
  49. package/dist/marks/helpers/VectorCanvas.svelte.d.ts +36 -0
  50. package/dist/marks/index.d.ts +7 -0
  51. package/dist/marks/index.js +7 -0
  52. package/dist/types/mark.d.ts +1 -1
  53. package/dist/types/plot.d.ts +33 -1
  54. package/package.json +185 -181
@@ -0,0 +1,172 @@
1
+ import type { DataRecord, ChannelAccessor } from '../types/index.js';
2
+ declare function $$render<Datum = DataRecord>(): {
3
+ props: Partial<{
4
+ filter: import("../types/index.js").ConstantAccessor<boolean, Datum>;
5
+ facet: "auto" | "include" | "exclude";
6
+ fx: ChannelAccessor<Datum>;
7
+ fy: ChannelAccessor<Datum>;
8
+ dx: import("../types/index.js").ConstantAccessor<number, Datum>;
9
+ dy: import("../types/index.js").ConstantAccessor<number, Datum>;
10
+ dodgeX: import("../transforms/dodge.js").DodgeXOptions;
11
+ dodgeY: import("../transforms/dodge.js").DodgeYOptions;
12
+ fill: ChannelAccessor<Datum>;
13
+ fillOpacity: import("../types/index.js").ConstantAccessor<number, Datum>;
14
+ fontFamily: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontFamily, Datum>;
15
+ fontSize: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontSize<number>, Datum>;
16
+ fontStyle: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontStyle, Datum>;
17
+ fontVariant: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontVariant, Datum>;
18
+ fontWeight: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontWeight, Datum>;
19
+ letterSpacing: import("../types/index.js").ConstantAccessor<import("csstype").Property.LetterSpacing<0 | (string & {})>, Datum>;
20
+ wordSpacing: import("../types/index.js").ConstantAccessor<import("csstype").Property.WordSpacing<0 | (string & {})>, Datum>;
21
+ textAnchor: import("../types/index.js").ConstantAccessor<import("csstype").Property.TextAnchor, Datum>;
22
+ textTransform: import("../types/index.js").ConstantAccessor<import("csstype").Property.TextTransform, Datum>;
23
+ textDecoration: import("../types/index.js").ConstantAccessor<import("csstype").Property.TextDecoration<0 | (string & {})>, Datum>;
24
+ sort: ((a: import("../types/data.js").RawValue, b: import("../types/data.js").RawValue) => number) | {
25
+ channel: string;
26
+ order?: "ascending" | "descending";
27
+ } | import("../types/index.js").ConstantAccessor<import("../types/data.js").RawValue, Datum>;
28
+ stroke: ChannelAccessor<Datum>;
29
+ strokeWidth: import("../types/index.js").ConstantAccessor<number, Datum>;
30
+ strokeOpacity: import("../types/index.js").ConstantAccessor<number, Datum>;
31
+ strokeLinejoin: import("../types/index.js").ConstantAccessor<import("csstype").Property.StrokeLinejoin, Datum>;
32
+ strokeLinecap: import("../types/index.js").ConstantAccessor<import("csstype").Property.StrokeLinecap, Datum>;
33
+ strokeMiterlimit: import("../types/index.js").ConstantAccessor<number, Datum>;
34
+ opacity: ChannelAccessor<Datum>;
35
+ strokeDasharray: import("../types/index.js").ConstantAccessor<string, Datum>;
36
+ strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Datum>;
37
+ blend: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
38
+ mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
39
+ clipPath: string;
40
+ mask: string;
41
+ imageFilter: import("../types/index.js").ConstantAccessor<string, Datum>;
42
+ shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
43
+ paintOrder: import("../types/index.js").ConstantAccessor<string, Datum>;
44
+ onclick: (event: Event & {
45
+ currentTarget: SVGPathElement;
46
+ }, datum: Datum, index: number) => void;
47
+ ondblclick: (event: Event & {
48
+ currentTarget: SVGPathElement;
49
+ }, datum: Datum, index: number) => void;
50
+ onmouseup: (event: Event & {
51
+ currentTarget: SVGPathElement;
52
+ }, datum: Datum, index: number) => void;
53
+ onmousedown: (event: Event & {
54
+ currentTarget: SVGPathElement;
55
+ }, datum: Datum, index: number) => void;
56
+ onmouseenter: (event: Event & {
57
+ currentTarget: SVGPathElement;
58
+ }, datum: Datum, index: number) => void;
59
+ onmousemove: (event: Event & {
60
+ currentTarget: SVGPathElement;
61
+ }, datum: Datum, index: number) => void;
62
+ onmouseleave: (event: Event & {
63
+ currentTarget: SVGPathElement;
64
+ }, datum: Datum, index: number) => void;
65
+ onmouseout: (event: Event & {
66
+ currentTarget: SVGPathElement;
67
+ }, datum: Datum, index: number) => void;
68
+ onmouseover: (event: Event & {
69
+ currentTarget: SVGPathElement;
70
+ }, datum: Datum, index: number) => void;
71
+ onpointercancel: (event: Event & {
72
+ currentTarget: SVGPathElement;
73
+ }, datum: Datum, index: number) => void;
74
+ onpointerdown: (event: Event & {
75
+ currentTarget: SVGPathElement;
76
+ }, datum: Datum, index: number) => void;
77
+ onpointerup: (event: Event & {
78
+ currentTarget: SVGPathElement;
79
+ }, datum: Datum, index: number) => void;
80
+ onpointerenter: (event: Event & {
81
+ currentTarget: SVGPathElement;
82
+ }, datum: Datum, index: number) => void;
83
+ onpointerleave: (event: Event & {
84
+ currentTarget: SVGPathElement;
85
+ }, datum: Datum, index: number) => void;
86
+ onpointermove: (event: Event & {
87
+ currentTarget: SVGPathElement;
88
+ }, datum: Datum, index: number) => void;
89
+ onpointerover: (event: Event & {
90
+ currentTarget: SVGPathElement;
91
+ }, datum: Datum, index: number) => void;
92
+ onpointerout: (event: Event & {
93
+ currentTarget: SVGPathElement;
94
+ }, datum: Datum, index: number) => void;
95
+ ondrag: (event: Event & {
96
+ currentTarget: SVGPathElement;
97
+ }, datum: Datum, index: number) => void;
98
+ ondrop: (event: Event & {
99
+ currentTarget: SVGPathElement;
100
+ }, datum: Datum, index: number) => void;
101
+ ondragstart: (event: Event & {
102
+ currentTarget: SVGPathElement;
103
+ }, datum: Datum, index: number) => void;
104
+ ondragenter: (event: Event & {
105
+ currentTarget: SVGPathElement;
106
+ }, datum: Datum, index: number) => void;
107
+ ondragleave: (event: Event & {
108
+ currentTarget: SVGPathElement;
109
+ }, datum: Datum, index: number) => void;
110
+ ondragover: (event: Event & {
111
+ currentTarget: SVGPathElement;
112
+ }, datum: Datum, index: number) => void;
113
+ ondragend: (event: Event & {
114
+ currentTarget: SVGPathElement;
115
+ }, datum: Datum, index: number) => void;
116
+ ontouchstart: (event: Event & {
117
+ currentTarget: SVGPathElement;
118
+ }, datum: Datum, index: number) => void;
119
+ ontouchmove: (event: Event & {
120
+ currentTarget: SVGPathElement;
121
+ }, datum: Datum, index: number) => void;
122
+ ontouchend: (event: Event & {
123
+ currentTarget: SVGPathElement;
124
+ }, datum: Datum, index: number) => void;
125
+ ontouchcancel: (event: Event & {
126
+ currentTarget: SVGPathElement;
127
+ }, datum: Datum, index: number) => void;
128
+ oncontextmenu: (event: Event & {
129
+ currentTarget: SVGPathElement;
130
+ }, datum: Datum, index: number) => void;
131
+ onwheel: (event: Event & {
132
+ currentTarget: SVGPathElement;
133
+ }, datum: Datum, index: number) => void;
134
+ class: string;
135
+ style: string;
136
+ cursor: import("../types/index.js").ConstantAccessor<import("csstype").Property.Cursor, Datum>;
137
+ title: import("../types/index.js").ConstantAccessor<string, Datum>;
138
+ }> & {
139
+ /** the input data array */
140
+ data?: Datum[];
141
+ /** the horizontal position channel */
142
+ x?: ChannelAccessor<Datum>;
143
+ /** the vertical position channel */
144
+ y?: ChannelAccessor<Datum>;
145
+ /** the grouping channel; separate diagrams per group */
146
+ z?: ChannelAccessor<Datum>;
147
+ /** Render using a canvas element instead of SVG paths. */
148
+ canvas?: boolean;
149
+ };
150
+ exports: {};
151
+ bindings: "";
152
+ slots: {};
153
+ events: {};
154
+ };
155
+ declare class __sveltets_Render<Datum = DataRecord> {
156
+ props(): ReturnType<typeof $$render<Datum>>['props'];
157
+ events(): ReturnType<typeof $$render<Datum>>['events'];
158
+ slots(): ReturnType<typeof $$render<Datum>>['slots'];
159
+ bindings(): "";
160
+ exports(): {};
161
+ }
162
+ interface $$IsomorphicComponent {
163
+ new <Datum = 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']>> & {
164
+ $$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
165
+ } & ReturnType<__sveltets_Render<Datum>['exports']>;
166
+ <Datum = DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
167
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
168
+ }
169
+ /** Renders the full Voronoi diagram as a single SVG path. */
170
+ declare const VoronoiMesh: $$IsomorphicComponent;
171
+ type VoronoiMesh<Datum = DataRecord> = InstanceType<typeof VoronoiMesh<Datum>>;
172
+ export default VoronoiMesh;
@@ -0,0 +1,132 @@
1
+ <!--
2
+ @component
3
+ Helper component for rendering Arrow marks in canvas
4
+ -->
5
+ <script lang="ts" generics="Datum extends DataRecord">
6
+ interface ArrowCanvasProps {
7
+ data: ScaledDataRecord<Datum>[];
8
+ options: BaseMarkProps<Datum> & {
9
+ headAngle?: ConstantAccessor<number, Datum>;
10
+ headLength?: ConstantAccessor<number, Datum>;
11
+ bend?: ConstantAccessor<number, Datum> | true;
12
+ inset?: ConstantAccessor<number, Datum>;
13
+ insetStart?: ConstantAccessor<number, Datum>;
14
+ insetEnd?: ConstantAccessor<number, Datum>;
15
+ sweep?: SweepOption;
16
+ };
17
+ usedScales: UsedScales;
18
+ }
19
+
20
+ import type {
21
+ BaseMarkProps,
22
+ ConstantAccessor,
23
+ DataRecord,
24
+ ScaledDataRecord,
25
+ UsedScales
26
+ } from '../../types/index.js';
27
+ import { resolveProp, resolveScaledStyleProps } from '../../helpers/resolve.js';
28
+ import { coalesce, maybeNumber } from '../../helpers/index.js';
29
+ import {
30
+ arrowPath,
31
+ maybeSweep,
32
+ type SweepFunc,
33
+ type SweepOption
34
+ } from '../../helpers/arrowPath.js';
35
+ import type { Attachment } from 'svelte/attachments';
36
+ import { devicePixelRatio } from 'svelte/reactivity/window';
37
+ import CanvasLayer from './CanvasLayer.svelte';
38
+ import { resolveColor } from './canvas.js';
39
+ import { usePlot } from '../../hooks/usePlot.svelte.js';
40
+
41
+ const plot = usePlot();
42
+
43
+ let { data, options, usedScales }: ArrowCanvasProps = $props();
44
+
45
+ const render: Attachment = (canvasEl: Element) => {
46
+ const canvas = canvasEl as HTMLCanvasElement;
47
+ const context = canvas.getContext('2d');
48
+
49
+ $effect(() => {
50
+ if (context) {
51
+ context.resetTransform();
52
+ context.scale(devicePixelRatio.current ?? 1, devicePixelRatio.current ?? 1);
53
+
54
+ const sweep = maybeSweep(options.sweep) as SweepFunc;
55
+
56
+ for (const d of data) {
57
+ if (!d.valid) continue;
58
+
59
+ const datum = d.datum as unknown as Datum;
60
+
61
+ const styleProps = resolveScaledStyleProps(
62
+ d.datum,
63
+ options,
64
+ usedScales,
65
+ plot,
66
+ 'stroke'
67
+ ) as Record<string, unknown>;
68
+
69
+ const opacity = +(styleProps['opacity'] ?? 1);
70
+ const strokeOpacity = +(styleProps['stroke-opacity'] ?? 1);
71
+ const stroke = resolveColor(
72
+ String(styleProps.stroke || 'currentColor'),
73
+ canvas
74
+ );
75
+
76
+ if (!stroke || stroke === 'none') continue;
77
+
78
+ const strokeWidth = (resolveProp(options.strokeWidth, datum, 1) ?? 1) as number;
79
+ const inset = resolveProp(options.inset, datum, 0);
80
+ const insetStart = resolveProp(options.insetStart, datum);
81
+ const insetEnd = resolveProp(options.insetEnd, datum);
82
+ const headAngle = (resolveProp(options.headAngle, datum, 60) ?? 60) as number;
83
+ const headLength = (resolveProp(options.headLength, datum, 8) ?? 8) as number;
84
+ const bendVal =
85
+ options.bend === true
86
+ ? 22.5
87
+ : ((resolveProp(
88
+ options.bend as ConstantAccessor<number, Datum>,
89
+ datum,
90
+ 0
91
+ ) ?? 0) as number);
92
+
93
+ const pathStr = arrowPath(
94
+ d.x1 ?? 0,
95
+ d.y1 ?? 0,
96
+ d.x2 ?? 0,
97
+ d.y2 ?? 0,
98
+ maybeNumber(coalesce(insetStart, inset)) ?? 0,
99
+ maybeNumber(coalesce(insetEnd, inset)) ?? 0,
100
+ headAngle,
101
+ headLength,
102
+ bendVal,
103
+ strokeWidth,
104
+ sweep
105
+ );
106
+
107
+ if (!pathStr) continue;
108
+
109
+ const path = new Path2D(pathStr);
110
+
111
+ context.lineWidth = strokeWidth;
112
+ context.lineCap = 'round';
113
+ context.lineJoin = 'round';
114
+ context.strokeStyle = stroke;
115
+ context.globalAlpha = opacity * strokeOpacity;
116
+ context.stroke(path);
117
+ }
118
+ }
119
+
120
+ return () => {
121
+ context?.clearRect(
122
+ 0,
123
+ 0,
124
+ plot.width * (devicePixelRatio.current ?? 1),
125
+ plot.height * (devicePixelRatio.current ?? 1)
126
+ );
127
+ };
128
+ });
129
+ };
130
+ </script>
131
+
132
+ <CanvasLayer {@attach render} />
@@ -0,0 +1,39 @@
1
+ import type { BaseMarkProps, ConstantAccessor, DataRecord, ScaledDataRecord, UsedScales } from '../../types/index.js';
2
+ import { type SweepOption } from '../../helpers/arrowPath.js';
3
+ declare function $$render<Datum extends DataRecord>(): {
4
+ props: {
5
+ data: ScaledDataRecord<Datum>[];
6
+ options: BaseMarkProps<Datum> & {
7
+ headAngle?: ConstantAccessor<number, Datum>;
8
+ headLength?: ConstantAccessor<number, Datum>;
9
+ bend?: ConstantAccessor<number, Datum> | true;
10
+ inset?: ConstantAccessor<number, Datum>;
11
+ insetStart?: ConstantAccessor<number, Datum>;
12
+ insetEnd?: ConstantAccessor<number, Datum>;
13
+ sweep?: SweepOption;
14
+ };
15
+ usedScales: UsedScales;
16
+ };
17
+ exports: {};
18
+ bindings: "";
19
+ slots: {};
20
+ events: {};
21
+ };
22
+ declare class __sveltets_Render<Datum extends DataRecord> {
23
+ props(): ReturnType<typeof $$render<Datum>>['props'];
24
+ events(): ReturnType<typeof $$render<Datum>>['events'];
25
+ slots(): ReturnType<typeof $$render<Datum>>['slots'];
26
+ bindings(): "";
27
+ exports(): {};
28
+ }
29
+ interface $$IsomorphicComponent {
30
+ 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']>> & {
31
+ $$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
32
+ } & ReturnType<__sveltets_Render<Datum>['exports']>;
33
+ <Datum extends DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
34
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
35
+ }
36
+ /** Helper component for rendering Arrow marks in canvas */
37
+ declare const ArrowCanvas: $$IsomorphicComponent;
38
+ type ArrowCanvas<Datum extends DataRecord> = InstanceType<typeof ArrowCanvas<Datum>>;
39
+ export default ArrowCanvas;
@@ -206,6 +206,8 @@
206
206
  {@const moveDown =
207
207
  (tickSize + tickPadding + (tickRotate !== 0 ? tickFontSize_ * 0.35 : 0)) *
208
208
  (anchor === 'bottom' ? 1 : -1)}
209
+ {@const tickBaseline =
210
+ tickRotate !== 0 ? 'central' : anchor === 'bottom' ? 'hanging' : 'auto'}
209
211
  {@const [textStyle, textClass] = resolveStyles(
210
212
  plot,
211
213
  toScaledDatum(tick),
@@ -230,22 +232,18 @@
230
232
  )}
231
233
  <text
232
234
  bind:this={tickTextElements[t]}
233
- transform="translate(0, {moveDown}) rotate({tickRotate})"
235
+ transform="translate(0, {moveDown}) rotate({tickRotate})"
234
236
  style={textStyle}
235
237
  class={textClass}
236
238
  x={0}
237
239
  y={0}
238
- dominant-baseline={tickRotate !== 0
239
- ? 'central'
240
- : anchor === 'bottom'
241
- ? 'hanging'
242
- : 'auto'}>
240
+ dominant-baseline={tickBaseline}>
243
241
  {#if ticks.length > 0 || t === 0 || t === ticks.length - 1}
244
242
  {#if textLines.length === 1}
245
243
  {textLines[0]}
246
244
  {:else}
247
245
  {#each textLines as line, i (i)}
248
- <tspan x="0" dy={i ? 12 : 0}
246
+ <tspan x="0" dy={i ? 12 : 0} dominant-baseline={tickBaseline}
249
247
  >{!prevTextLines ||
250
248
  prevTextLines[i] !== line ||
251
249
  options.removeDuplicateTicks === false
@@ -0,0 +1,118 @@
1
+ <script lang="ts">
2
+ import type { ScaledDataRecord } from '../../types/index.js';
3
+ import type { GeoPath } from 'd3-geo';
4
+ import type { Attachment } from 'svelte/attachments';
5
+ import { devicePixelRatio } from 'svelte/reactivity/window';
6
+ import { CSS_VAR } from '../../constants.js';
7
+ import CanvasLayer from './CanvasLayer.svelte';
8
+ import { usePlot } from '../../hooks/usePlot.svelte.js';
9
+ import { RAW_VALUE } from '../../transforms/recordize.js';
10
+
11
+ let {
12
+ scaledData,
13
+ path,
14
+ geomKey,
15
+ fill,
16
+ stroke,
17
+ strokeWidth,
18
+ strokeOpacity,
19
+ fillOpacity,
20
+ opacity,
21
+ strokeMiterlimit
22
+ }: {
23
+ scaledData: ScaledDataRecord[];
24
+ path: GeoPath;
25
+ /** Symbol key used to retrieve the DensityGeometry from each datum. */
26
+ geomKey: symbol;
27
+ fill: string;
28
+ stroke: string;
29
+ strokeWidth?: number;
30
+ strokeOpacity?: number;
31
+ fillOpacity?: number;
32
+ opacity?: number;
33
+ strokeMiterlimit?: number;
34
+ } = $props();
35
+
36
+ const plot = usePlot();
37
+
38
+ /** Resolve a fill/stroke string that may be the "density" keyword. */
39
+ function resolveColorProp(prop: string, densityValue: number): string {
40
+ if (/^density$/i.test(prop)) {
41
+ return (plot.scales.color?.fn(densityValue) as string) ?? 'currentColor';
42
+ }
43
+ return prop;
44
+ }
45
+
46
+ const render: Attachment = (canvasEl: Element) => {
47
+ const canvas = canvasEl as HTMLCanvasElement;
48
+ const context = canvas.getContext('2d');
49
+
50
+ $effect(() => {
51
+ if (!context) return;
52
+
53
+ path.context(context);
54
+ context.resetTransform();
55
+ context.scale(devicePixelRatio.current ?? 1, devicePixelRatio.current ?? 1);
56
+
57
+ let currentColor: string | undefined;
58
+
59
+ const resolveCanvasColor = (color: string): string => {
60
+ if (color.toLowerCase() === 'currentcolor') {
61
+ return (
62
+ currentColor ||
63
+ (currentColor = getComputedStyle(
64
+ canvas.parentElement?.parentElement ?? canvas
65
+ ).getPropertyValue('color'))
66
+ );
67
+ }
68
+ if (CSS_VAR.test(color)) {
69
+ return getComputedStyle(canvas).getPropertyValue(color.slice(4, -1));
70
+ }
71
+ return color;
72
+ };
73
+
74
+ const globalOpacity = opacity ?? 1;
75
+ if (strokeMiterlimit != null) context.miterLimit = strokeMiterlimit;
76
+
77
+ for (const d of scaledData) {
78
+ const geom = d.datum[geomKey as any] as any;
79
+ if (!geom?.coordinates?.length) continue;
80
+
81
+ const densityValue = (d.datum[RAW_VALUE as any] as number) ?? 0;
82
+ const fillColor = resolveCanvasColor(resolveColorProp(fill, densityValue));
83
+ const strokeColor = resolveCanvasColor(resolveColorProp(stroke, densityValue));
84
+
85
+ context.beginPath();
86
+ path(geom);
87
+ context.closePath();
88
+
89
+ if (fillColor && fillColor !== 'none') {
90
+ context.fillStyle = fillColor;
91
+ context.globalAlpha = globalOpacity * (fillOpacity ?? 1);
92
+ context.fill();
93
+ }
94
+
95
+ if (strokeColor && strokeColor !== 'none') {
96
+ context.strokeStyle = strokeColor;
97
+ context.lineWidth = strokeWidth ?? 1;
98
+ context.globalAlpha = globalOpacity * (strokeOpacity ?? 1);
99
+ context.stroke();
100
+ }
101
+ }
102
+
103
+ // Reset path context in case we switch back to SVG rendering.
104
+ path.context(null);
105
+
106
+ return () => {
107
+ context.clearRect(
108
+ 0,
109
+ 0,
110
+ plot.width * (devicePixelRatio.current ?? 1),
111
+ plot.height * (devicePixelRatio.current ?? 1)
112
+ );
113
+ };
114
+ });
115
+ };
116
+ </script>
117
+
118
+ <CanvasLayer {@attach render} />
@@ -0,0 +1,18 @@
1
+ import type { ScaledDataRecord } from '../../types/index.js';
2
+ import type { GeoPath } from 'd3-geo';
3
+ type $$ComponentProps = {
4
+ scaledData: ScaledDataRecord[];
5
+ path: GeoPath;
6
+ /** Symbol key used to retrieve the DensityGeometry from each datum. */
7
+ geomKey: symbol;
8
+ fill: string;
9
+ stroke: string;
10
+ strokeWidth?: number;
11
+ strokeOpacity?: number;
12
+ fillOpacity?: number;
13
+ opacity?: number;
14
+ strokeMiterlimit?: number;
15
+ };
16
+ declare const DensityCanvas: import("svelte").Component<$$ComponentProps, {}, "">;
17
+ type DensityCanvas = ReturnType<typeof DensityCanvas>;
18
+ export default DensityCanvas;
@@ -0,0 +1,125 @@
1
+ <script lang="ts">
2
+ import type { ScaledDataRecord } from '../../types/index.js';
3
+ import type { GeoPath } from 'd3-geo';
4
+ import type { Attachment } from 'svelte/attachments';
5
+ import { devicePixelRatio } from 'svelte/reactivity/window';
6
+ import { CSS_VAR } from '../../constants.js';
7
+ import CanvasLayer from './CanvasLayer.svelte';
8
+ import { usePlot } from '../../hooks/usePlot.svelte.js';
9
+ import { RAW_VALUE } from '../../transforms/recordize.js';
10
+
11
+ let {
12
+ scaledData,
13
+ path,
14
+ geomKey,
15
+ colorKeyword,
16
+ fill,
17
+ stroke,
18
+ strokeWidth,
19
+ strokeOpacity,
20
+ fillOpacity,
21
+ opacity,
22
+ strokeMiterlimit
23
+ }: {
24
+ scaledData: ScaledDataRecord[];
25
+ path: GeoPath;
26
+ /** Symbol key used to retrieve the GeoJSON geometry from each datum. */
27
+ geomKey: symbol;
28
+ /**
29
+ * The color keyword that, when used as fill/stroke, maps the threshold
30
+ * value through the plot's color scale. E.g. "value" for Contour,
31
+ * "density" for Density.
32
+ */
33
+ colorKeyword: string;
34
+ fill: string;
35
+ stroke: string;
36
+ strokeWidth?: number;
37
+ strokeOpacity?: number;
38
+ fillOpacity?: number;
39
+ opacity?: number;
40
+ strokeMiterlimit?: number;
41
+ } = $props();
42
+
43
+ const plot = usePlot();
44
+
45
+ /** Resolve a fill/stroke string that may be the colorKeyword. */
46
+ function resolveColorProp(prop: string, value: number): string {
47
+ if (prop.toLowerCase() === colorKeyword.toLowerCase()) {
48
+ return (plot.scales.color?.fn(value) as string) ?? 'currentColor';
49
+ }
50
+ return prop;
51
+ }
52
+
53
+ const render: Attachment = (canvasEl: Element) => {
54
+ const canvas = canvasEl as HTMLCanvasElement;
55
+ const context = canvas.getContext('2d');
56
+
57
+ $effect(() => {
58
+ if (!context) return;
59
+
60
+ path.context(context);
61
+ context.resetTransform();
62
+ context.scale(devicePixelRatio.current ?? 1, devicePixelRatio.current ?? 1);
63
+
64
+ let currentColor: string | undefined;
65
+
66
+ const resolveCanvasColor = (color: string): string => {
67
+ if (color.toLowerCase() === 'currentcolor') {
68
+ return (
69
+ currentColor ||
70
+ (currentColor = getComputedStyle(
71
+ canvas.parentElement?.parentElement ?? canvas
72
+ ).getPropertyValue('color'))
73
+ );
74
+ }
75
+ if (CSS_VAR.test(color)) {
76
+ return getComputedStyle(canvas).getPropertyValue(color.slice(4, -1));
77
+ }
78
+ return color;
79
+ };
80
+
81
+ const globalOpacity = opacity ?? 1;
82
+ if (strokeMiterlimit != null) context.miterLimit = strokeMiterlimit;
83
+
84
+ for (const d of scaledData) {
85
+ const geom = d.datum[geomKey as any] as any;
86
+ if (!geom?.coordinates?.length) continue;
87
+
88
+ const thresholdValue = (d.datum[RAW_VALUE as any] as number) ?? 0;
89
+ const fillColor = resolveCanvasColor(resolveColorProp(fill, thresholdValue));
90
+ const strokeColor = resolveCanvasColor(resolveColorProp(stroke, thresholdValue));
91
+
92
+ context.beginPath();
93
+ path(geom);
94
+ context.closePath();
95
+
96
+ if (fillColor && fillColor !== 'none') {
97
+ context.fillStyle = fillColor;
98
+ context.globalAlpha = globalOpacity * (fillOpacity ?? 1);
99
+ context.fill();
100
+ }
101
+
102
+ if (strokeColor && strokeColor !== 'none') {
103
+ context.strokeStyle = strokeColor;
104
+ context.lineWidth = strokeWidth ?? 1;
105
+ context.globalAlpha = globalOpacity * (strokeOpacity ?? 1);
106
+ context.stroke();
107
+ }
108
+ }
109
+
110
+ // Reset path context in case we switch back to SVG rendering.
111
+ path.context(null);
112
+
113
+ return () => {
114
+ context.clearRect(
115
+ 0,
116
+ 0,
117
+ plot.width * (devicePixelRatio.current ?? 1),
118
+ plot.height * (devicePixelRatio.current ?? 1)
119
+ );
120
+ };
121
+ });
122
+ };
123
+ </script>
124
+
125
+ <CanvasLayer {@attach render} />
@@ -0,0 +1,24 @@
1
+ import type { ScaledDataRecord } from '../../types/index.js';
2
+ import type { GeoPath } from 'd3-geo';
3
+ type $$ComponentProps = {
4
+ scaledData: ScaledDataRecord[];
5
+ path: GeoPath;
6
+ /** Symbol key used to retrieve the GeoJSON geometry from each datum. */
7
+ geomKey: symbol;
8
+ /**
9
+ * The color keyword that, when used as fill/stroke, maps the threshold
10
+ * value through the plot's color scale. E.g. "value" for Contour,
11
+ * "density" for Density.
12
+ */
13
+ colorKeyword: string;
14
+ fill: string;
15
+ stroke: string;
16
+ strokeWidth?: number;
17
+ strokeOpacity?: number;
18
+ fillOpacity?: number;
19
+ opacity?: number;
20
+ strokeMiterlimit?: number;
21
+ };
22
+ declare const GeoPathCanvas: import("svelte").Component<$$ComponentProps, {}, "">;
23
+ type GeoPathCanvas = ReturnType<typeof GeoPathCanvas>;
24
+ export default GeoPathCanvas;