svelteplot 0.8.0-pr-282.2 → 0.8.1-pr-283.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 (46) hide show
  1. package/dist/Mark.svelte +1 -1
  2. package/dist/Mark.svelte.d.ts +1 -0
  3. package/dist/constants.d.ts +2 -0
  4. package/dist/constants.js +2 -0
  5. package/dist/helpers/projection.js +5 -2
  6. package/dist/marks/Area.svelte.d.ts +1 -0
  7. package/dist/marks/AreaX.svelte.d.ts +1 -0
  8. package/dist/marks/Arrow.svelte.d.ts +1 -0
  9. package/dist/marks/AxisX.svelte.d.ts +1 -0
  10. package/dist/marks/AxisY.svelte +1 -1
  11. package/dist/marks/AxisY.svelte.d.ts +1 -0
  12. package/dist/marks/BarX.svelte.d.ts +1 -0
  13. package/dist/marks/BarY.svelte.d.ts +1 -0
  14. package/dist/marks/Cell.svelte.d.ts +1 -0
  15. package/dist/marks/Dot.svelte.d.ts +1 -0
  16. package/dist/marks/DotX.svelte.d.ts +1 -0
  17. package/dist/marks/DotY.svelte.d.ts +1 -0
  18. package/dist/marks/Frame.svelte.d.ts +1 -0
  19. package/dist/marks/Geo.svelte +2 -0
  20. package/dist/marks/Geo.svelte.d.ts +2 -0
  21. package/dist/marks/GridX.svelte.d.ts +1 -0
  22. package/dist/marks/GridY.svelte.d.ts +1 -0
  23. package/dist/marks/Line.svelte +5 -2
  24. package/dist/marks/Line.svelte.d.ts +1 -0
  25. package/dist/marks/LineX.svelte.d.ts +1 -0
  26. package/dist/marks/LineY.svelte.d.ts +1 -0
  27. package/dist/marks/Link.svelte.d.ts +1 -0
  28. package/dist/marks/Rect.svelte.d.ts +1 -0
  29. package/dist/marks/RuleX.svelte.d.ts +1 -0
  30. package/dist/marks/RuleY.svelte.d.ts +1 -0
  31. package/dist/marks/Spike.svelte.d.ts +1 -0
  32. package/dist/marks/Text.svelte.d.ts +1 -0
  33. package/dist/marks/TickX.svelte.d.ts +1 -0
  34. package/dist/marks/TickY.svelte.d.ts +1 -0
  35. package/dist/marks/Trail.svelte +157 -0
  36. package/dist/marks/Trail.svelte.d.ts +44 -0
  37. package/dist/marks/Vector.svelte.d.ts +1 -0
  38. package/dist/marks/helpers/TrailCanvas.svelte +0 -0
  39. package/dist/marks/helpers/TrailCanvas.svelte.d.ts +26 -0
  40. package/dist/marks/helpers/trail.d.ts +23 -0
  41. package/dist/marks/helpers/trail.js +372 -0
  42. package/dist/marks/index.d.ts +1 -0
  43. package/dist/marks/index.js +1 -0
  44. package/dist/types/mark.d.ts +2 -1
  45. package/dist/types/plot.d.ts +5 -1
  46. package/package.json +16 -15
package/dist/Mark.svelte CHANGED
@@ -151,7 +151,7 @@
151
151
  // check if the mark has defined an accessor for this channel
152
152
  if (options?.[channel] !== undefined && out[channel] === undefined) {
153
153
  // resolve value
154
- out[channel] = resolveChannel(channel, row, options);
154
+ out[channel] = resolveChannel(channel, row, options, index);
155
155
  if (options[channel] === INDEX) {
156
156
  const scale = plot.scales[CHANNEL_SCALE[channel]];
157
157
  if (scale.type === 'band' || scale.type === 'point') {
@@ -29,6 +29,7 @@ declare function $$render<Datum extends DataRecord>(): {
29
29
  strokeDashoffset: import("./types/index.js").ConstantAccessor<number, Datum>;
30
30
  mixBlendMode: import("./types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
31
31
  clipPath: string;
32
+ mask: string;
32
33
  imageFilter: import("./types/index.js").ConstantAccessor<string, Datum>;
33
34
  shapeRendering: import("./types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
34
35
  paintOrder: import("./types/index.js").ConstantAccessor<string, Datum>;
@@ -14,3 +14,5 @@ export declare const CSS_COLOR_CONTRAST: RegExp;
14
14
  export declare const CSS_RGBA: RegExp;
15
15
  export declare const CSS_URL: RegExp;
16
16
  export declare const INDEX: any;
17
+ export declare const PI: number;
18
+ export declare const TAU: number;
package/dist/constants.js CHANGED
@@ -117,6 +117,8 @@ export const CSS_COLOR_CONTRAST = /^color-contrast\(/; // just check for prefix
117
117
  export const CSS_RGBA = /^rgba\(/; // just check for prefix
118
118
  export const CSS_URL = /^url\(#/; // just check for prefix
119
119
  export const INDEX = Symbol('index');
120
+ export const PI = Math.PI;
121
+ export const TAU = PI * 2;
120
122
  // export const CHANNEL_MAP: Record<ScaleName, ValueOf<typeof SCALE_TYPES>> = {
121
123
  // x: SCALE_TYPES.x,
122
124
  // y: SCALE_TYPES.y,
@@ -50,7 +50,7 @@ export function createProjection({ projOptions, inset: globalInset = 2, insetTop
50
50
  let transform;
51
51
  let invertTransform = (d) => d;
52
52
  // If a domain is specified, fit the projection to the frame.
53
- if (domain != null) {
53
+ if (domain != null && dx > 0 && dy > 0) {
54
54
  const [[x0, y0], [x1, y1]] = geoPath(projInstance).bounds(domain);
55
55
  const k = Math.min(dx / (x1 - x0), dy / (y1 - y0));
56
56
  aspectRatio = (y1 - y0) / (x1 - x0);
@@ -66,9 +66,12 @@ export function createProjection({ projOptions, inset: globalInset = 2, insetTop
66
66
  invertTransform = ([x, y]) => [(x - tx) / k, (y - ty) / k];
67
67
  }
68
68
  else {
69
- throw new Error(`Warning: the projection could not be fit to the specified domain; using the default scale.`);
69
+ console.warn(`Warning: the projection could not be fit to the specified domain; using the default scale.`);
70
70
  }
71
71
  }
72
+ else if (domain != null) {
73
+ console.warn(`Warning: the projection could not be fit to the specified domain; using the default scale.`);
74
+ }
72
75
  transform ??=
73
76
  tx === 0 && ty === 0
74
77
  ? identity()
@@ -29,6 +29,7 @@ declare function $$render<Datum extends DataRecord>(): {
29
29
  strokeDashoffset: ConstantAccessor<number, Datum>;
30
30
  mixBlendMode: ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
31
31
  clipPath: string;
32
+ mask: string;
32
33
  imageFilter: ConstantAccessor<string, Datum>;
33
34
  shapeRendering: ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
34
35
  paintOrder: ConstantAccessor<string, Datum>;
@@ -27,6 +27,7 @@ declare function $$render<Datum extends DataRow>(): {
27
27
  strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Record<string | symbol, import("../types/data").RawValue>>;
28
28
  mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Record<string | symbol, import("../types/data").RawValue>>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: import("../types/index.js").ConstantAccessor<string, Record<string | symbol, import("../types/data").RawValue>>;
31
32
  shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Record<string | symbol, import("../types/data").RawValue>>;
32
33
  paintOrder: import("../types/index.js").ConstantAccessor<string, Record<string | symbol, import("../types/data").RawValue>>;
@@ -28,6 +28,7 @@ declare function $$render<Datum extends DataRecord>(): {
28
28
  strokeDashoffset: ConstantAccessor<number, Datum>;
29
29
  mixBlendMode: ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
30
30
  clipPath: string;
31
+ mask: string;
31
32
  imageFilter: ConstantAccessor<string, Datum>;
32
33
  shapeRendering: ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
33
34
  paintOrder: ConstantAccessor<string, Datum>;
@@ -28,6 +28,7 @@ declare function $$render<Datum extends RawValue>(): {
28
28
  strokeDashoffset: ConstantAccessor<number, Datum>;
29
29
  mixBlendMode: ConstantAccessor<CSS.Property.MixBlendMode, Datum>;
30
30
  clipPath: string;
31
+ mask: string;
31
32
  imageFilter: ConstantAccessor<string, Datum>;
32
33
  shapeRendering: ConstantAccessor<CSS.Property.ShapeRendering, Datum>;
33
34
  paintOrder: ConstantAccessor<string, Datum>;
@@ -170,7 +170,7 @@
170
170
  : optionsLabel !== undefined
171
171
  ? optionsLabel
172
172
  : plot.scales.y.autoTitle
173
- ? `↑ ${plot.scales.y.autoTitle}${plot.options.y.percent ? ' (%)' : ''}`
173
+ ? `${!plot.options.y.reverse ? '↑' : '↓'} ${plot.scales.y.autoTitle}${plot.options.y.percent ? ' (%)' : ''}`
174
174
  : ''
175
175
  );
176
176
 
@@ -27,6 +27,7 @@ declare function $$render<Datum extends RawValue>(): {
27
27
  strokeDashoffset: ConstantAccessor<number, Datum>;
28
28
  mixBlendMode: ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: ConstantAccessor<string, Datum>;
31
32
  shapeRendering: ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
32
33
  paintOrder: ConstantAccessor<string, Datum>;
@@ -29,6 +29,7 @@ declare function $$render<Datum extends DataRow>(): {
29
29
  strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Datum>;
30
30
  mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
31
31
  clipPath: string;
32
+ mask: string;
32
33
  imageFilter: import("../types/index.js").ConstantAccessor<string, Datum>;
33
34
  shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
34
35
  paintOrder: import("../types/index.js").ConstantAccessor<string, Datum>;
@@ -28,6 +28,7 @@ declare function $$render<Datum extends DataRow>(): {
28
28
  strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Datum>;
29
29
  mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
30
30
  clipPath: string;
31
+ mask: string;
31
32
  imageFilter: import("../types/index.js").ConstantAccessor<string, Datum>;
32
33
  shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
33
34
  paintOrder: import("../types/index.js").ConstantAccessor<string, Datum>;
@@ -27,6 +27,7 @@ declare function $$render<Datum extends DataRecord>(): {
27
27
  strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Datum>;
28
28
  mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: import("../types/index.js").ConstantAccessor<string, Datum>;
31
32
  shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
32
33
  paintOrder: import("../types/index.js").ConstantAccessor<string, Datum>;
@@ -28,6 +28,7 @@ declare function $$render<Datum extends DataRecord>(): {
28
28
  strokeDashoffset: ConstantAccessor<number, Datum>;
29
29
  mixBlendMode: ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
30
30
  clipPath: string;
31
+ mask: string;
31
32
  imageFilter: ConstantAccessor<string, Datum>;
32
33
  shapeRendering: ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
33
34
  paintOrder: ConstantAccessor<string, Datum>;
@@ -27,6 +27,7 @@ declare function $$render<Datum extends DataRow>(): {
27
27
  strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Record<string | symbol, import("../types/data").RawValue>>;
28
28
  mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Record<string | symbol, import("../types/data").RawValue>>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: import("../types/index.js").ConstantAccessor<string, Record<string | symbol, import("../types/data").RawValue>>;
31
32
  shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Record<string | symbol, import("../types/data").RawValue>>;
32
33
  paintOrder: import("../types/index.js").ConstantAccessor<string, Record<string | symbol, import("../types/data").RawValue>>;
@@ -27,6 +27,7 @@ declare function $$render<Datum extends DataRow>(): {
27
27
  strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Record<string | symbol, import("../types/data").RawValue>>;
28
28
  mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Record<string | symbol, import("../types/data").RawValue>>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: import("../types/index.js").ConstantAccessor<string, Record<string | symbol, import("../types/data").RawValue>>;
31
32
  shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Record<string | symbol, import("../types/data").RawValue>>;
32
33
  paintOrder: import("../types/index.js").ConstantAccessor<string, Record<string | symbol, import("../types/data").RawValue>>;
@@ -27,6 +27,7 @@ declare function $$render<Datum extends DataRecord>(): {
27
27
  strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Datum>;
28
28
  mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: import("../types/index.js").ConstantAccessor<string, Datum>;
31
32
  shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
32
33
  paintOrder: import("../types/index.js").ConstantAccessor<string, Datum>;
@@ -21,6 +21,7 @@
21
21
  * radius for point features
22
22
  */
23
23
  r?: ChannelAccessor<Datum>;
24
+ svgFilter?: ConstantAccessor<string | undefined, Datum>;
24
25
  }
25
26
  import { getContext } from 'svelte';
26
27
  import type {
@@ -111,6 +112,7 @@
111
112
  d={path(geometry)}
112
113
  {style}
113
114
  class={[styleClass]}
115
+ filter={resolveProp(args.svgFilter, d.datum, undefined)}
114
116
  {@attach addEventHandlers({
115
117
  getPlotState,
116
118
  options: args,
@@ -27,6 +27,7 @@ declare function $$render<Datum = DataRecord | GeoJSON.GeoJsonObject>(): {
27
27
  strokeDashoffset: ConstantAccessor<number, Datum>;
28
28
  mixBlendMode: ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: ConstantAccessor<string, Datum>;
31
32
  shapeRendering: ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
32
33
  paintOrder: ConstantAccessor<string, Datum>;
@@ -84,6 +85,7 @@ declare function $$render<Datum = DataRecord | GeoJSON.GeoJsonObject>(): {
84
85
  * radius for point features
85
86
  */
86
87
  r?: ChannelAccessor<Datum>;
88
+ svgFilter?: ConstantAccessor<string | undefined, Datum>;
87
89
  };
88
90
  exports: {};
89
91
  bindings: "";
@@ -27,6 +27,7 @@ declare function $$render<Datum = RawValue>(): {
27
27
  strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Datum>;
28
28
  mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: import("../types/index.js").ConstantAccessor<string, Datum>;
31
32
  shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
32
33
  paintOrder: import("../types/index.js").ConstantAccessor<string, Datum>;
@@ -27,6 +27,7 @@ declare function $$render<Datum = RawValue>(): {
27
27
  strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Datum>;
28
28
  mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: import("../types/index.js").ConstantAccessor<string, Datum>;
31
32
  shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
32
33
  paintOrder: import("../types/index.js").ConstantAccessor<string, Datum>;
@@ -76,7 +76,10 @@
76
76
 
77
77
  const args = $derived(sort(recordizeXY({ data, ...options })));
78
78
 
79
- function groupIndex(data: ScaledDataRecord[], groupByKey) {
79
+ /**
80
+ * Groups the data by the specified key
81
+ */
82
+ function groupIndex(data: ScaledDataRecord[], groupByKey: ChannelAccessor<Datum> | null) {
80
83
  if (!groupByKey) return [data];
81
84
  let group = [];
82
85
  const groups = [group];
@@ -95,7 +98,7 @@
95
98
  return groups;
96
99
  }
97
100
 
98
- const groupByKey = $derived(args.z || args.stroke);
101
+ const groupByKey = $derived(args.z || args.stroke) as ChannelAccessor<Datum> | null;
99
102
 
100
103
  const { getPlotState } = getContext<PlotContext>('svelteplot');
101
104
  const plot = $derived(getPlotState());
@@ -29,6 +29,7 @@ declare function $$render<Datum extends DataRecord>(): {
29
29
  strokeDashoffset: ConstantAccessor<number, Datum>;
30
30
  mixBlendMode: ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
31
31
  clipPath: string;
32
+ mask: string;
32
33
  imageFilter: ConstantAccessor<string, Datum>;
33
34
  shapeRendering: ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
34
35
  paintOrder: ConstantAccessor<string, Datum>;
@@ -27,6 +27,7 @@ declare function $$render<Datum extends DataRow>(): {
27
27
  strokeDashoffset: import("../types").ConstantAccessor<number, Record<string | symbol, import("../types").RawValue>>;
28
28
  mixBlendMode: import("../types").ConstantAccessor<import("csstype").Property.MixBlendMode, Record<string | symbol, import("../types").RawValue>>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: import("../types").ConstantAccessor<string, Record<string | symbol, import("../types").RawValue>>;
31
32
  shapeRendering: import("../types").ConstantAccessor<import("csstype").Property.ShapeRendering, Record<string | symbol, import("../types").RawValue>>;
32
33
  paintOrder: import("../types").ConstantAccessor<string, Record<string | symbol, import("../types").RawValue>>;
@@ -27,6 +27,7 @@ declare function $$render<Datum extends DataRow>(): {
27
27
  strokeDashoffset: import("../types").ConstantAccessor<number, Record<string | symbol, import("../types").RawValue>>;
28
28
  mixBlendMode: import("../types").ConstantAccessor<import("csstype").Property.MixBlendMode, Record<string | symbol, import("../types").RawValue>>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: import("../types").ConstantAccessor<string, Record<string | symbol, import("../types").RawValue>>;
31
32
  shapeRendering: import("../types").ConstantAccessor<import("csstype").Property.ShapeRendering, Record<string | symbol, import("../types").RawValue>>;
32
33
  paintOrder: import("../types").ConstantAccessor<string, Record<string | symbol, import("../types").RawValue>>;
@@ -28,6 +28,7 @@ declare function $$render<Datum extends DataRecord>(): {
28
28
  strokeDashoffset: ConstantAccessor<number, Datum>;
29
29
  mixBlendMode: ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
30
30
  clipPath: string;
31
+ mask: string;
31
32
  imageFilter: ConstantAccessor<string, Datum>;
32
33
  shapeRendering: ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
33
34
  paintOrder: ConstantAccessor<string, Datum>;
@@ -27,6 +27,7 @@ declare function $$render<Datum extends DataRecord>(): {
27
27
  strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Datum>;
28
28
  mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: import("../types/index.js").ConstantAccessor<string, Datum>;
31
32
  shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
32
33
  paintOrder: import("../types/index.js").ConstantAccessor<string, Datum>;
@@ -27,6 +27,7 @@ declare function $$render<Datum = DataRecord | RawValue>(): {
27
27
  strokeDashoffset: ConstantAccessor<number, Datum>;
28
28
  mixBlendMode: ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: ConstantAccessor<string, Datum>;
31
32
  shapeRendering: ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
32
33
  paintOrder: ConstantAccessor<string, Datum>;
@@ -27,6 +27,7 @@ declare function $$render<Datum = DataRecord>(): {
27
27
  strokeDashoffset: ConstantAccessor<number, Datum>;
28
28
  mixBlendMode: ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: ConstantAccessor<string, Datum>;
31
32
  shapeRendering: ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
32
33
  paintOrder: ConstantAccessor<string, Datum>;
@@ -27,6 +27,7 @@ declare function $$render<Datum extends DataRecord>(): {
27
27
  strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Record<string | symbol, import("../types/data").RawValue>>;
28
28
  mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Record<string | symbol, import("../types/data").RawValue>>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: import("../types/index.js").ConstantAccessor<string, Record<string | symbol, import("../types/data").RawValue>>;
31
32
  shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Record<string | symbol, import("../types/data").RawValue>>;
32
33
  paintOrder: import("../types/index.js").ConstantAccessor<string, Record<string | symbol, import("../types/data").RawValue>>;
@@ -29,6 +29,7 @@ declare function $$render<Datum extends DataRecord>(): {
29
29
  strokeDashoffset: ConstantAccessor<number, Datum>;
30
30
  mixBlendMode: ConstantAccessor<CSS.Property.MixBlendMode, Datum>;
31
31
  clipPath: string;
32
+ mask: string;
32
33
  imageFilter: ConstantAccessor<string, Datum>;
33
34
  shapeRendering: ConstantAccessor<CSS.Property.ShapeRendering, Datum>;
34
35
  paintOrder: ConstantAccessor<string, Datum>;
@@ -27,6 +27,7 @@ declare function $$render<Datum extends DataRow>(): {
27
27
  strokeDashoffset: ConstantAccessor<number, Datum>;
28
28
  mixBlendMode: ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: ConstantAccessor<string, Datum>;
31
32
  shapeRendering: ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
32
33
  paintOrder: ConstantAccessor<string, Datum>;
@@ -27,6 +27,7 @@ declare function $$render<Datum extends DataRow>(): {
27
27
  strokeDashoffset: ConstantAccessor<number, Datum>;
28
28
  mixBlendMode: ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
29
29
  clipPath: string;
30
+ mask: string;
30
31
  imageFilter: ConstantAccessor<string, Datum>;
31
32
  shapeRendering: ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
32
33
  paintOrder: ConstantAccessor<string, Datum>;
@@ -0,0 +1,157 @@
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
+ PlotContext,
29
+ ScaledDataRecord,
30
+ CurveName
31
+ } from '../types';
32
+ import Mark from '../Mark.svelte';
33
+ import { getContext } from 'svelte';
34
+ import { path as d3Path } from 'd3-path';
35
+ import { resolveProp, resolveStyles } from '../helpers/resolve.js';
36
+ import { getPlotDefaults } from '../hooks/plotDefaults';
37
+ import { sort } from '../transforms';
38
+ import trailPath, { type TrailSample } from './helpers/trail.js';
39
+ import TrailCanvas from './helpers/TrailCanvas.svelte';
40
+ import { addEventHandlers } from './helpers/events';
41
+ import { last } from 'es-toolkit';
42
+ import type { CurveFactory } from 'd3-shape';
43
+
44
+ let markProps: TrailMarkProps = $props();
45
+
46
+ const DEFAULTS: TrailMarkProps = {
47
+ curve: 'linear',
48
+ r: 3,
49
+ canvas: false,
50
+ resolution: 'auto',
51
+ cap: 'round',
52
+ tension: 0,
53
+ ...getPlotDefaults().trail
54
+ };
55
+
56
+ const {
57
+ data = [{} as Datum],
58
+ curve,
59
+ resolution,
60
+ tension,
61
+ canvas,
62
+ cap,
63
+ class: className,
64
+ ...options
65
+ }: TrailMarkProps = $derived({
66
+ ...DEFAULTS,
67
+ ...markProps
68
+ });
69
+
70
+ const args = $derived(sort({ data, ...options })) as TrailMarkProps;
71
+
72
+ const { getPlotState } = getContext<PlotContext>('svelteplot');
73
+ const plot = $derived(getPlotState());
74
+
75
+ /**
76
+ * Groups the data by the specified key
77
+ */
78
+ function groupIndex(data: ScaledDataRecord[], groupByKey: ChannelAccessor<Datum> | null) {
79
+ if (!groupByKey) return [data];
80
+ let group: ScaledDataRecord[] = [];
81
+ const groups = [group];
82
+ let lastGroupValue;
83
+ for (const d of data) {
84
+ const groupValue = resolveProp(groupByKey, d.datum);
85
+ if (groupValue === lastGroupValue || group.length === 0) {
86
+ group.push(d);
87
+ lastGroupValue = groupValue;
88
+ } else {
89
+ // new group
90
+ group = [d];
91
+ groups.push(group);
92
+ lastGroupValue = groupValue;
93
+ }
94
+ }
95
+ return groups.filter((d) => d.length > 0);
96
+ }
97
+
98
+ const groupByKey = $derived(args.z || args.fill) as ChannelAccessor<Datum> | null;
99
+ </script>
100
+
101
+ <Mark
102
+ type="trail"
103
+ channels={['x', 'y', 'opacity', 'fill', 'fillOpacity', 'r']}
104
+ required={['x', 'y']}
105
+ {...args}>
106
+ {#snippet children({ mark, usedScales, scaledData })}
107
+ {#if scaledData.length > 0}
108
+ {@const groupedTrailData = groupIndex(scaledData, groupByKey)}
109
+ {#if canvas}
110
+ <!-- todo -->
111
+ <TrailCanvas />
112
+ {:else}
113
+ <g class={['trail', className]}>
114
+ {#each groupedTrailData as trailData, i (i)}
115
+ {@const samples = trailData.map((d) => ({
116
+ x: Number(d.x),
117
+ y: Number(d.y),
118
+ r: Number(d.r ?? 0)
119
+ })) satisfies TrailSample[]}
120
+ {@const defined = trailData.map(
121
+ (d) =>
122
+ d.valid &&
123
+ d.r >= 0 &&
124
+ (resolveProp(options.defined, d.datum, true) ?? true)
125
+ )}
126
+ {@const pathString = trailPath(samples, defined, d3Path(), {
127
+ curve,
128
+ cap,
129
+ tension,
130
+ ...(typeof resolution === 'number'
131
+ ? { samplesPerSegment: resolution }
132
+ : {})
133
+ })}
134
+ {@const [style, styleClass] = resolveStyles(
135
+ plot,
136
+ trailData[0],
137
+ {
138
+ ...args
139
+ },
140
+ 'fill',
141
+ usedScales
142
+ )}
143
+ <path
144
+ d={pathString}
145
+ {style}
146
+ class={styleClass}
147
+ {@attach addEventHandlers({
148
+ getPlotState,
149
+ options: mark.options,
150
+ datum: trailData[0].datum
151
+ })} />
152
+ {/each}
153
+ </g>
154
+ {/if}
155
+ {/if}
156
+ {/snippet}
157
+ </Mark>
@@ -0,0 +1,44 @@
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<BaseMarkProps<Datum>, "stroke" | "strokeDasharray" | "strokeWidth"> & {
5
+ data?: Datum[];
6
+ x?: ChannelAccessor<Datum>;
7
+ y?: ChannelAccessor<Datum>;
8
+ z?: ChannelAccessor<Datum>;
9
+ r?: ChannelAccessor<Datum>;
10
+ curve?: CurveName | CurveFactory;
11
+ tension?: number;
12
+ sort?: ConstantAccessor<RawValue, Datum> | {
13
+ channel: "stroke" | "fill";
14
+ };
15
+ defined?: ConstantAccessor<boolean, Datum>;
16
+ canvas?: boolean;
17
+ cap?: "butt" | "round";
18
+ /**
19
+ * Samples per segment for curve interpolation
20
+ */
21
+ resolution?: number | "auto";
22
+ };
23
+ exports: {};
24
+ bindings: "";
25
+ slots: {};
26
+ events: {};
27
+ };
28
+ declare class __sveltets_Render<Datum extends DataRecord> {
29
+ props(): ReturnType<typeof $$render<Datum>>['props'];
30
+ events(): ReturnType<typeof $$render<Datum>>['events'];
31
+ slots(): ReturnType<typeof $$render<Datum>>['slots'];
32
+ bindings(): "";
33
+ exports(): {};
34
+ }
35
+ interface $$IsomorphicComponent {
36
+ 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']>> & {
37
+ $$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
38
+ } & ReturnType<__sveltets_Render<Datum>['exports']>;
39
+ <Datum extends DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
40
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
41
+ }
42
+ declare const Trail: $$IsomorphicComponent;
43
+ type Trail<Datum extends DataRecord> = InstanceType<typeof Trail<Datum>>;
44
+ export default Trail;
@@ -32,6 +32,7 @@ declare function $$render<Datum extends DataRecord>(): {
32
32
  strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Datum>;
33
33
  mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
34
34
  clipPath: string;
35
+ mask: string;
35
36
  imageFilter: import("../types/index.js").ConstantAccessor<string, Datum>;
36
37
  shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
37
38
  paintOrder: import("../types/index.js").ConstantAccessor<string, Datum>;
File without changes
@@ -0,0 +1,26 @@
1
+ export default TrailCanvas;
2
+ type TrailCanvas = SvelteComponent<{
3
+ [x: string]: never;
4
+ }, {
5
+ [evt: string]: CustomEvent<any>;
6
+ }, {}> & {
7
+ $$bindings?: string | undefined;
8
+ };
9
+ declare const TrailCanvas: $$__sveltets_2_IsomorphicComponent<{
10
+ [x: string]: never;
11
+ }, {
12
+ [evt: string]: CustomEvent<any>;
13
+ }, {}, {}, string>;
14
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
15
+ new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
16
+ $$bindings?: Bindings;
17
+ } & Exports;
18
+ (internal: unknown, props: {
19
+ $$events?: Events;
20
+ $$slots?: Slots;
21
+ }): Exports & {
22
+ $set?: any;
23
+ $on?: any;
24
+ };
25
+ z_$$bindings?: Bindings;
26
+ }
@@ -0,0 +1,23 @@
1
+ import type { Path } from 'd3-path';
2
+ import type { CurveName } from '../../types/index.js';
3
+ import type { CurveFactory } from 'd3-shape';
4
+ export type TrailContext = CanvasRenderingContext2D | Path;
5
+ export type TrailCurve = CurveName | CurveFactory;
6
+ export type TrailCap = 'round' | 'butt';
7
+ export type TrailOptions = {
8
+ curve?: TrailCurve;
9
+ samplesPerSegment?: number;
10
+ cap?: TrailCap;
11
+ tension?: number;
12
+ };
13
+ export type TrailSample = {
14
+ x: number;
15
+ y: number;
16
+ r: number;
17
+ };
18
+ /**
19
+ * Draw a stroked capsule trail along successive points with varying widths.
20
+ * Adapted from Vega's trail mark implementation.
21
+ */
22
+ export declare function trailPath(samples: TrailSample[], defined: boolean[], context: TrailContext, options?: TrailOptions): string | void;
23
+ export default trailPath;
@@ -0,0 +1,372 @@
1
+ import { TAU } from '../../constants.js';
2
+ import { maybeCurve } from '../../helpers/curves.js';
3
+ /**
4
+ * Draw a stroked capsule trail along successive points with varying widths.
5
+ * Adapted from Vega's trail mark implementation.
6
+ */
7
+ export function trailPath(samples, defined, context, options = {}) {
8
+ const { curve = 'linear', cap = 'round', tension = 0.5 } = options;
9
+ const samplesPerSegment = options.samplesPerSegment ?? estimateSamplesPerSegment(samples, defined);
10
+ const curveFactory = maybeCurve(curve, tension);
11
+ let drawSamples = samples;
12
+ let drawDefined = defined;
13
+ if (curve !== 'linear' || tension !== 0) {
14
+ const smoothedSamples = [];
15
+ const smoothedDefined = [];
16
+ const len = Math.min(samples.length, defined.length);
17
+ let i = 0;
18
+ while (i < len) {
19
+ if (!defined[i]) {
20
+ smoothedSamples.push(samples[i]);
21
+ smoothedDefined.push(false);
22
+ i += 1;
23
+ continue;
24
+ }
25
+ const segment = [];
26
+ while (i < len && defined[i]) {
27
+ segment.push(samples[i]);
28
+ i += 1;
29
+ }
30
+ const resampled = resampleCurve(segment, curveFactory, Math.max(1, samplesPerSegment));
31
+ smoothedSamples.push(...resampled);
32
+ smoothedDefined.push(...new Array(resampled.length).fill(true));
33
+ // preserve a gap between defined segments
34
+ if (i < len) {
35
+ smoothedSamples.push(samples[i]);
36
+ smoothedDefined.push(false);
37
+ }
38
+ }
39
+ drawSamples = smoothedSamples;
40
+ drawDefined = smoothedDefined;
41
+ }
42
+ const len = Math.min(drawSamples.length, drawDefined.length);
43
+ if (len === 0)
44
+ return;
45
+ // Butt caps: build joined polygon offsets using angle bisectors to avoid gaps.
46
+ if (cap === 'butt') {
47
+ const normalizeVec = (x, y) => {
48
+ const lenVec = Math.hypot(x, y);
49
+ return lenVec === 0 ? [0, 0] : [x / lenVec, y / lenVec];
50
+ };
51
+ let i = 0;
52
+ while (i < len) {
53
+ if (!drawDefined[i]) {
54
+ i += 1;
55
+ continue;
56
+ }
57
+ const runStart = i;
58
+ while (i < len && drawDefined[i])
59
+ i += 1;
60
+ const runEnd = i - 1;
61
+ const left = [];
62
+ const right = [];
63
+ for (let j = runStart; j <= runEnd; j += 1) {
64
+ const curr = drawSamples[j];
65
+ const r = curr.r;
66
+ const hasPrev = j > runStart;
67
+ const hasNext = j < runEnd;
68
+ const prev = hasPrev ? drawSamples[j - 1] : curr;
69
+ const next = hasNext ? drawSamples[j + 1] : curr;
70
+ const dirPrev = hasPrev
71
+ ? normalizeVec(curr.x - prev.x, curr.y - prev.y)
72
+ : normalizeVec(next.x - curr.x, next.y - curr.y);
73
+ const dirNext = hasNext ? normalizeVec(next.x - curr.x, next.y - curr.y) : dirPrev;
74
+ const normPrev = [-dirPrev[1], dirPrev[0]];
75
+ const normNext = [-dirNext[1], dirNext[0]];
76
+ let nx = normPrev[0] + normNext[0];
77
+ let ny = normPrev[1] + normNext[1];
78
+ const nLen = Math.hypot(nx, ny);
79
+ if (nLen < 1e-6) {
80
+ // Straight/180-deg turn: fall back to current normal.
81
+ nx = normPrev[0];
82
+ ny = normPrev[1];
83
+ }
84
+ else {
85
+ nx /= nLen;
86
+ ny /= nLen;
87
+ }
88
+ // Scale to preserve half-width along the miter direction.
89
+ const dot = nx * normPrev[0] + ny * normPrev[1];
90
+ const safeDot = Math.abs(dot) < 1e-6 ? 1 : dot;
91
+ const scale = r / safeDot;
92
+ const ox = nx * scale;
93
+ const oy = ny * scale;
94
+ left.push([curr.x + ox, curr.y + oy]);
95
+ right.push([curr.x - ox, curr.y - oy]);
96
+ }
97
+ if (left.length > 0) {
98
+ context.moveTo(left[0][0], left[0][1]);
99
+ for (let j = 1; j < left.length; j += 1) {
100
+ context.lineTo(left[j][0], left[j][1]);
101
+ }
102
+ for (let j = right.length - 1; j >= 0; j -= 1) {
103
+ context.lineTo(right[j][0], right[j][1]);
104
+ }
105
+ context.closePath();
106
+ }
107
+ }
108
+ return typeof context.toString === 'function' ? context.toString() : undefined;
109
+ }
110
+ // Round caps: original capsule behavior.
111
+ let ready = false;
112
+ let x1 = 0;
113
+ let y1 = 0;
114
+ let r1 = 0;
115
+ function point(x2, y2, r2, isStartOfRun, isEndOfRun) {
116
+ if (ready) {
117
+ let ux = y1 - y2;
118
+ let uy = x2 - x1;
119
+ if (ux || uy) {
120
+ // compute normal vector scaled by radius
121
+ const ud = Math.hypot(ux, uy);
122
+ const rx = (ux /= ud) * r1;
123
+ const ry = (uy /= ud) * r1;
124
+ const t = Math.atan2(uy, ux);
125
+ // keep rounded joins for interior segments even when using butt caps
126
+ const drawStartCap = !isStartOfRun || cap === 'round';
127
+ const drawEndCap = !isEndOfRun || cap === 'round';
128
+ context.moveTo(x1 - rx, y1 - ry);
129
+ context.lineTo(x2 - ux * r2, y2 - uy * r2);
130
+ if (drawEndCap) {
131
+ context.arc(x2, y2, r2, t - Math.PI, t);
132
+ }
133
+ else {
134
+ context.lineTo(x2 + ux * r2, y2 + uy * r2);
135
+ }
136
+ context.lineTo(x1 + rx, y1 + ry);
137
+ if (drawStartCap) {
138
+ context.arc(x1, y1, r1, t, t + Math.PI);
139
+ }
140
+ }
141
+ else {
142
+ if (cap === 'round' || (!isStartOfRun && !isEndOfRun)) {
143
+ context.arc(x2, y2, r2, 0, TAU);
144
+ }
145
+ }
146
+ context.closePath();
147
+ }
148
+ else {
149
+ ready = true;
150
+ }
151
+ x1 = x2;
152
+ y1 = y2;
153
+ r1 = r2;
154
+ }
155
+ let i = 0;
156
+ while (i < len) {
157
+ // Skip gaps
158
+ if (!drawDefined[i]) {
159
+ i += 1;
160
+ ready = false;
161
+ continue;
162
+ }
163
+ const runStart = i;
164
+ while (i < len && drawDefined[i])
165
+ i += 1;
166
+ const runEnd = i - 1;
167
+ // Prime the first point of the run
168
+ const first = drawSamples[runStart];
169
+ ready = false;
170
+ x1 = first.x;
171
+ y1 = first.y;
172
+ r1 = first.r;
173
+ ready = true;
174
+ for (let j = runStart + 1; j <= runEnd; j += 1) {
175
+ const { x: x2, y: y2, r } = drawSamples[j];
176
+ const isStart = j - 1 === runStart;
177
+ const isEnd = j === runEnd;
178
+ point(Number(x2), Number(y2), Number(r), isStart, isEnd);
179
+ }
180
+ ready = false;
181
+ }
182
+ return typeof context.toString === 'function' ? context.toString() : undefined;
183
+ }
184
+ function resampleCurve(points, curveFactory, samplesPerSegment) {
185
+ if (points.length === 0)
186
+ return [];
187
+ const commands = [];
188
+ let pendingRadius = points[0].r;
189
+ let currentRadius = points[0].r;
190
+ let currentPoint = null;
191
+ const ctx = {
192
+ beginPath() { },
193
+ closePath() { },
194
+ moveTo(x, y) {
195
+ currentPoint = [x, y];
196
+ currentRadius = pendingRadius;
197
+ commands.push({ type: 'move', to: [x, y], r: currentRadius });
198
+ },
199
+ lineTo(x, y) {
200
+ const from = currentPoint ?? [x, y];
201
+ commands.push({
202
+ type: 'line',
203
+ from: [from[0], from[1], currentRadius],
204
+ to: [x, y, pendingRadius]
205
+ });
206
+ currentPoint = [x, y];
207
+ currentRadius = pendingRadius;
208
+ },
209
+ bezierCurveTo(x1, y1, x2, y2, x, y) {
210
+ const from = currentPoint ?? [x, y];
211
+ commands.push({
212
+ type: 'cubic',
213
+ from: [from[0], from[1], currentRadius],
214
+ cp1: [x1, y1],
215
+ cp2: [x2, y2],
216
+ to: [x, y, pendingRadius]
217
+ });
218
+ currentPoint = [x, y];
219
+ currentRadius = pendingRadius;
220
+ },
221
+ quadraticCurveTo(x1, y1, x, y) {
222
+ const from = currentPoint ?? [x, y];
223
+ commands.push({
224
+ type: 'quad',
225
+ from: [from[0], from[1], currentRadius],
226
+ cp: [x1, y1],
227
+ to: [x, y, pendingRadius]
228
+ });
229
+ currentPoint = [x, y];
230
+ currentRadius = pendingRadius;
231
+ },
232
+ arc() { },
233
+ rect() { }
234
+ };
235
+ const curve = curveFactory(ctx);
236
+ curve.lineStart();
237
+ for (let idx = 0; idx < points.length; idx += 1) {
238
+ const pt = points[idx];
239
+ pendingRadius = pt.r;
240
+ curve.point(pt.x, pt.y);
241
+ }
242
+ curve.lineEnd();
243
+ const geom = flattenCommands(commands, samplesPerSegment);
244
+ if (geom.length === 0)
245
+ return geom;
246
+ // Re-map radii along the resampled path using the original cumulative
247
+ // length as the parameter to avoid curve-specific radius drift.
248
+ const origCum = [0];
249
+ for (let i = 1; i < points.length; i += 1) {
250
+ const dx = points[i].x - points[i - 1].x;
251
+ const dy = points[i].y - points[i - 1].y;
252
+ origCum.push(origCum[i - 1] + Math.hypot(dx, dy));
253
+ }
254
+ const origTotal = origCum[origCum.length - 1] || 1;
255
+ const resCum = [0];
256
+ for (let i = 1; i < geom.length; i += 1) {
257
+ const dx = geom[i].x - geom[i - 1].x;
258
+ const dy = geom[i].y - geom[i - 1].y;
259
+ resCum.push(resCum[i - 1] + Math.hypot(dx, dy));
260
+ }
261
+ const resTotal = resCum[resCum.length - 1] || 1;
262
+ const radiusAt = (target) => {
263
+ let idx = 1;
264
+ while (idx < origCum.length && origCum[idx] < target)
265
+ idx += 1;
266
+ if (idx === origCum.length)
267
+ return points[points.length - 1].r;
268
+ const t0 = origCum[idx - 1];
269
+ const t1 = origCum[idx];
270
+ const r0 = points[idx - 1].r;
271
+ const r1 = points[idx].r;
272
+ const t = t1 === t0 ? 0 : (target - t0) / (t1 - t0);
273
+ return lerp(r0, r1, t);
274
+ };
275
+ for (let i = 0; i < geom.length; i += 1) {
276
+ const frac = resCum[i] / resTotal;
277
+ geom[i].r = Number(radiusAt(frac * origTotal).toFixed(2));
278
+ }
279
+ return geom;
280
+ }
281
+ function flattenCommands(commands, samplesPerSegment, precision = 2) {
282
+ const result = [];
283
+ let last = null;
284
+ const round = (v) => {
285
+ const m = 10 ** precision;
286
+ return Math.round(v * m) / m;
287
+ };
288
+ const pushPoint = (x, y, r) => {
289
+ x = round(x);
290
+ y = round(y);
291
+ r = round(r);
292
+ if (!last || last[0] !== x || last[1] !== y || last[2] !== r) {
293
+ result.push({ x, y, r });
294
+ last = [x, y, r];
295
+ }
296
+ };
297
+ for (const cmd of commands) {
298
+ if (cmd.type === 'move') {
299
+ pushPoint(cmd.to[0], cmd.to[1], cmd.r);
300
+ continue;
301
+ }
302
+ if (cmd.type === 'line') {
303
+ const [x1, y1, r1] = cmd.from;
304
+ const [x2, y2, r2] = cmd.to;
305
+ for (let step = 1; step <= samplesPerSegment; step += 1) {
306
+ const t = step / samplesPerSegment;
307
+ pushPoint(lerp(x1, x2, t), lerp(y1, y2, t), lerp(r1, r2, t));
308
+ }
309
+ continue;
310
+ }
311
+ if (cmd.type === 'cubic') {
312
+ const [x0, y0, r0] = cmd.from;
313
+ const [x1, y1] = cmd.cp1;
314
+ const [x2, y2] = cmd.cp2;
315
+ const [x3, y3, r3] = cmd.to;
316
+ for (let step = 1; step <= samplesPerSegment; step += 1) {
317
+ const t = step / samplesPerSegment;
318
+ pushPoint(cubic(x0, x1, x2, x3, t), cubic(y0, y1, y2, y3, t), lerp(r0, r3, t));
319
+ }
320
+ continue;
321
+ }
322
+ if (cmd.type === 'quad') {
323
+ const [x0, y0, r0] = cmd.from;
324
+ const [cx, cy] = cmd.cp;
325
+ const [x1, y1, r1] = cmd.to;
326
+ for (let step = 1; step <= samplesPerSegment; step += 1) {
327
+ const t = step / samplesPerSegment;
328
+ pushPoint(quad(x0, cx, x1, t), quad(y0, cy, y1, t), lerp(r0, r1, t));
329
+ }
330
+ }
331
+ }
332
+ return result;
333
+ }
334
+ function cubic(p0, p1, p2, p3, t) {
335
+ const it = 1 - t;
336
+ return it * it * it * p0 + 3 * it * it * t * p1 + 3 * it * t * t * p2 + t * t * t * p3;
337
+ }
338
+ function quad(p0, p1, p2, t) {
339
+ const it = 1 - t;
340
+ return it * it * p0 + 2 * it * t * p1 + t * t * p2;
341
+ }
342
+ function lerp(a, b, t) {
343
+ return a + (b - a) * t;
344
+ }
345
+ function estimateSamplesPerSegment(samples, defined) {
346
+ const n = Math.min(samples.length, defined.length);
347
+ let distSum = 0;
348
+ let distCount = 0;
349
+ let rSum = 0;
350
+ let rCount = 0;
351
+ for (let i = 0; i < n; i++) {
352
+ if (defined[i]) {
353
+ rSum += samples[i].r;
354
+ rCount += 1;
355
+ }
356
+ if (i === 0 || !defined[i] || !defined[i - 1])
357
+ continue;
358
+ const dx = samples[i].x - samples[i - 1].x;
359
+ const dy = samples[i].y - samples[i - 1].y;
360
+ const d = Math.hypot(dx, dy);
361
+ if (isFinite(d) && d > 0) {
362
+ distSum += d;
363
+ distCount += 1;
364
+ }
365
+ }
366
+ const meanDist = distCount ? distSum / distCount : 0;
367
+ const meanRadius = rCount ? rSum / rCount : 0;
368
+ const base = meanRadius > 0 ? meanDist / meanRadius : meanDist;
369
+ // Keep within a reasonable range to avoid excessive subdivision.
370
+ return Math.max(1, Math.min(32, Math.round(base || 1)));
371
+ }
372
+ export default trailPath;
@@ -45,6 +45,7 @@ export { default as Spike } from './Spike.svelte';
45
45
  export { default as Text } from './Text.svelte';
46
46
  export { default as TickX } from './TickX.svelte';
47
47
  export { default as TickY } from './TickY.svelte';
48
+ export { default as Trail } from './Trail.svelte';
48
49
  export { default as Vector } from './Vector.svelte';
49
50
  export { default as WaffleX } from './WaffleX.svelte';
50
51
  export { default as WaffleY } from './WaffleY.svelte';
@@ -45,6 +45,7 @@ export { default as Spike } from './Spike.svelte';
45
45
  export { default as Text } from './Text.svelte';
46
46
  export { default as TickX } from './TickX.svelte';
47
47
  export { default as TickY } from './TickY.svelte';
48
+ export { default as Trail } from './Trail.svelte';
48
49
  export { default as Vector } from './Vector.svelte';
49
50
  export { default as WaffleX } from './WaffleX.svelte';
50
51
  export { default as WaffleY } from './WaffleY.svelte';
@@ -6,7 +6,7 @@ export type Mark<T> = {
6
6
  data: DataRecord<T>[];
7
7
  options: T;
8
8
  };
9
- export type MarkType = 'area' | 'arrow' | 'barX' | 'barY' | 'cell' | 'custom' | 'dot' | 'vector' | 'frame' | 'geo' | 'gridX' | 'gridY' | 'line' | 'rect' | 'regression' | 'ruleX' | 'ruleY' | 'swoopyArrow' | 'text' | 'tickX' | 'tickY' | 'waffleX' | 'waffleY';
9
+ export type MarkType = 'area' | 'arrow' | 'barX' | 'barY' | 'cell' | 'custom' | 'dot' | 'vector' | 'frame' | 'geo' | 'gridX' | 'gridY' | 'line' | 'rect' | 'regression' | 'ruleX' | 'ruleY' | 'swoopyArrow' | 'text' | 'tickX' | 'tickY' | 'trail' | 'waffleX' | 'waffleY';
10
10
  export type MarkStyleProps = 'strokeDasharray' | 'strokeLinejoin' | 'strokeLinecap' | 'opacity' | 'cursor' | 'pointerEvents' | 'blend' | 'fill' | 'fillOpacity' | 'fontFamily' | 'fontWeight' | 'fontVariant' | 'fontSize' | 'fontStyle' | 'letterSpacing' | 'wordSpacing' | 'stroke' | 'strokeWidth' | 'strokeOpacity' | 'x' | 'y' | 'clipPath' | 'mask' | 'filter' | 'angle' | 'radius' | 'symbol' | 'textAnchor' | 'textTransform' | 'textDecoration' | 'width';
11
11
  import type { MouseEventHandler } from 'svelte/elements';
12
12
  import type { ChannelAccessor, ConstantAccessor, DataRecord, RawValue } from './index.js';
@@ -44,6 +44,7 @@ export type BaseMarkProps<T> = Partial<{
44
44
  strokeDashoffset: ConstantAccessor<number, T>;
45
45
  mixBlendMode: ConstantAccessor<CSS.Property.MixBlendMode, T>;
46
46
  clipPath: string;
47
+ mask: string;
47
48
  imageFilter: ConstantAccessor<string, T>;
48
49
  shapeRendering: ConstantAccessor<CSS.Property.ShapeRendering, T>;
49
50
  paintOrder: ConstantAccessor<string, T>;
@@ -3,7 +3,7 @@ import type { ColorScheme } from './colorScheme.js';
3
3
  import type { GeoProjection } from 'd3-geo';
4
4
  import type { ChannelAccessor, ChannelName, ColorScaleOptions, DataRecord, LegendScaleOptions, PlotScales, RawValue, ScaleOptions, XScaleOptions, YScaleOptions } from './index.js';
5
5
  import type { Snippet } from 'svelte';
6
- import type { Area, AreaX, AreaY, Arrow, AxisX, AxisY, BarX, BarY, BoxX, BoxY, Brush, BrushX, BrushY, Cell, DifferenceY, Dot, Frame, Geo, Graticule, GridX, GridY, Image, Line, Link, Pointer, Rect, RectX, RectY, RuleX, RuleY, Sphere, Spike, Text, TickX, TickY, Vector } from '../marks/index.js';
6
+ import type { Area, AreaX, AreaY, Arrow, AxisX, AxisY, BarX, BarY, BoxX, BoxY, Brush, BrushX, BrushY, Cell, DifferenceY, Dot, Frame, Geo, Graticule, GridX, GridY, Image, Line, Link, Pointer, Rect, RectX, RectY, RuleX, RuleY, Sphere, Spike, Text, TickX, TickY, Trail, Vector } from '../marks/index.js';
7
7
  import type WaffleX from '../marks/WaffleX.svelte';
8
8
  import type WaffleY from '../marks/WaffleY.svelte';
9
9
  export type PlotState = {
@@ -296,6 +296,10 @@ export type PlotDefaults = {
296
296
  * default props for tickY marks
297
297
  */
298
298
  tickY: Partial<Omit<ComponentProps<typeof TickY>, IgnoreDefaults>>;
299
+ /**
300
+ * default props for tickY marks
301
+ */
302
+ trail: Partial<Omit<ComponentProps<typeof Trail>, IgnoreDefaults>>;
299
303
  /**
300
304
  * default props for vector marks
301
305
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteplot",
3
- "version": "0.8.0-pr-282.2",
3
+ "version": "0.8.1-pr-283.0",
4
4
  "license": "ISC",
5
5
  "author": {
6
6
  "name": "Gregor Aisch",
@@ -54,12 +54,12 @@
54
54
  "devDependencies": {
55
55
  "@aitodotai/json-stringify-pretty-compact": "^1.3.0",
56
56
  "@emotion/css": "^11.13.5",
57
- "@shikijs/twoslash": "^3.18.0",
57
+ "@shikijs/twoslash": "^3.19.0",
58
58
  "@sveltejs/adapter-auto": "^7.0.0",
59
59
  "@sveltejs/adapter-static": "^3.0.10",
60
60
  "@sveltejs/enhanced-img": "^0.9.2",
61
61
  "@sveltejs/eslint-config": "^8.3.4",
62
- "@sveltejs/kit": "^2.49.1",
62
+ "@sveltejs/kit": "^2.49.2",
63
63
  "@sveltejs/package": "^2.5.7",
64
64
  "@sveltejs/vite-plugin-svelte": "6.2.1",
65
65
  "@sveltepress/twoslash": "^1.3.2",
@@ -80,8 +80,8 @@
80
80
  "@types/geojson": "^7946.0.16",
81
81
  "@types/topojson": "^3.2.6",
82
82
  "@types/topojson-client": "^3.1.5",
83
- "@typescript-eslint/eslint-plugin": "^8.48.1",
84
- "@typescript-eslint/parser": "^8.48.1",
83
+ "@typescript-eslint/eslint-plugin": "^8.49.0",
84
+ "@typescript-eslint/parser": "^8.49.0",
85
85
  "@unocss/extractor-svelte": "^66.5.10",
86
86
  "@vite-pwa/sveltekit": "^1.1.0",
87
87
  "csstype": "^3.2.3",
@@ -91,27 +91,27 @@
91
91
  "eslint": "^9.39.1",
92
92
  "eslint-config-prettier": "^10.1.8",
93
93
  "eslint-plugin-regexp": "^2.10.0",
94
- "eslint-plugin-svelte": "3.13.0",
94
+ "eslint-plugin-svelte": "3.13.1",
95
95
  "jqmath": "^0.4.9",
96
- "jsdom": "^27.2.0",
96
+ "jsdom": "^27.3.0",
97
97
  "log-update": "^7.0.2",
98
98
  "lru-cache": "^11.2.4",
99
99
  "mdast-util-from-markdown": "^2.0.2",
100
100
  "mdast-util-gfm": "^3.1.0",
101
- "prettier": "^3.7.3",
101
+ "pixelmatch": "^7.1.0",
102
+ "pngjs": "^7.0.0",
103
+ "prettier": "^3.7.4",
102
104
  "prettier-plugin-svelte": "^3.4.0",
103
- "puppeteer": "^24.31.0",
105
+ "puppeteer": "^24.32.1",
104
106
  "remark-code-extra": "^1.0.1",
105
107
  "remark-code-frontmatter": "^1.0.0",
106
108
  "remark-math": "^6.0.0",
107
109
  "resize-observer-polyfill": "^1.5.1",
108
- "sass": "^1.94.2",
109
- "shiki": "^3.18.0",
110
+ "sass": "^1.96.0",
111
+ "shiki": "^3.19.0",
110
112
  "svelte-check": "^4.3.4",
111
- "svelte-eslint-parser": "1.4.0",
113
+ "svelte-eslint-parser": "1.4.1",
112
114
  "svelte-highlight": "^7.9.0",
113
- "pixelmatch": "^5.3.0",
114
- "pngjs": "^7.0.0",
115
115
  "svg-path-parser": "^1.1.0",
116
116
  "temml": "^0.12.1",
117
117
  "topojson-client": "^3.1.0",
@@ -123,9 +123,10 @@
123
123
  "uid": "^2.0.2",
124
124
  "unist-util-visit": "^5.0.0",
125
125
  "unocss": "^66.5.10",
126
- "vite": "^7.2.6",
126
+ "vite": "^7.2.7",
127
127
  "vitest": "^4.0.15",
128
128
  "vitest-matchmedia-mock": "^2.0.3",
129
+ "wx-svelte-grid": "^2.4.0",
129
130
  "yoctocolors": "^2.1.2"
130
131
  },
131
132
  "types": "./dist/index.d.ts",