svelteplot 0.1.3-next.8 → 0.2.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 (40) hide show
  1. package/README.md +4 -2
  2. package/dist/Mark.svelte +25 -21
  3. package/dist/Plot.svelte +45 -29
  4. package/dist/core/Plot.svelte +4 -2
  5. package/dist/helpers/index.d.ts +1 -1
  6. package/dist/helpers/resolve.js +1 -1
  7. package/dist/helpers/scales.js +3 -1
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.js +3 -0
  10. package/dist/marks/BarX.svelte +15 -5
  11. package/dist/marks/BarY.svelte +20 -12
  12. package/dist/marks/BarY.svelte.d.ts +22 -1
  13. package/dist/marks/Brush.svelte +364 -0
  14. package/dist/marks/Brush.svelte.d.ts +32 -0
  15. package/dist/marks/BrushX.svelte +7 -0
  16. package/dist/marks/BrushX.svelte.d.ts +4 -0
  17. package/dist/marks/BrushY.svelte +7 -0
  18. package/dist/marks/BrushY.svelte.d.ts +4 -0
  19. package/dist/marks/Cell.svelte +0 -7
  20. package/dist/marks/Dot.svelte +9 -18
  21. package/dist/marks/Dot.svelte.d.ts +8 -8
  22. package/dist/marks/Frame.svelte +24 -12
  23. package/dist/marks/Frame.svelte.d.ts +6 -1
  24. package/dist/marks/GridX.svelte +15 -6
  25. package/dist/marks/GridY.svelte +15 -6
  26. package/dist/marks/Link.svelte +1 -1
  27. package/dist/marks/Pointer.svelte +2 -2
  28. package/dist/marks/Pointer.svelte.d.ts +2 -2
  29. package/dist/marks/Rect.svelte +12 -19
  30. package/dist/marks/Sphere.svelte.d.ts +14 -4
  31. package/dist/marks/Text.svelte +14 -6
  32. package/dist/marks/Text.svelte.d.ts +2 -2
  33. package/dist/marks/helpers/events.d.ts +13 -0
  34. package/dist/marks/helpers/events.js +32 -3
  35. package/dist/transforms/bin.d.ts +7 -7
  36. package/dist/transforms/recordize.d.ts +1 -0
  37. package/dist/transforms/recordize.js +4 -5
  38. package/dist/transforms/window.d.ts +2 -0
  39. package/dist/types.d.ts +29 -9
  40. package/package.json +9 -7
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # SveltePlot
2
2
 
3
- SveltePlot is a new reactive visualization framework based on the [layered grammar of graphics](https://vita.had.co.nz/papers/layered-grammar.html) ideas. It's API is heavily inspired by [Observable Plot](https://github.com/observablehq/plot). Created by Gregor Aisch.
3
+ ![Tests](https://github.com/svelteplot/svelteplot/actions/workflows/test.yml/badge.svg)
4
4
 
5
- <img src="static/logo.png" alt="logo" width="400" />
5
+ SveltePlot is a visualization framework based on the [layered grammar of graphics](https://vita.had.co.nz/papers/layered-grammar.html) ideas. It's API is heavily inspired by [Observable Plot](https://github.com/observablehq/plot). Created by Gregor Aisch.
6
+
7
+ <img src="static/logo.png" alt="SveltePlot logo" width="401" />
package/dist/Mark.svelte CHANGED
@@ -78,8 +78,6 @@
78
78
  }
79
79
  }
80
80
 
81
-
82
-
83
81
  const mark = new Mark(type);
84
82
 
85
83
  $effect(() => {
@@ -128,24 +126,26 @@
128
126
  const testFacet = $derived(getTestFacet());
129
127
 
130
128
  const resolvedData: ResolvedDataRecord[] = $derived(
131
- data.map((d,i) => ({...d, [INDEX]: i})).flatMap((row) => {
132
- const channels = options as Record<ChannelName, ChannelAccessor>;
133
- if (!testFacet(row, channels) || !testFilter(row, channels)) return [];
134
- const out: ResolvedDataRecord = {
135
- datum: row
136
- };
137
- for (const [channel] of Object.entries(CHANNEL_SCALE) as [
138
- ScaledChannelName,
139
- ScaleName
140
- ][]) {
141
- // check if the mark has defined an accessor for this channel
142
- if (options?.[channel] !== undefined && out[channel] === undefined) {
143
- // resolve value
144
- out[channel] = resolveChannel(channel, row, options);
129
+ data
130
+ .map((d, i) => ({ ...d, [INDEX]: i }))
131
+ .flatMap((row) => {
132
+ const channels = options as Record<ChannelName, ChannelAccessor>;
133
+ if (!testFacet(row, channels) || !testFilter(row, channels)) return [];
134
+ const out: ResolvedDataRecord = {
135
+ datum: row
136
+ };
137
+ for (const [channel] of Object.entries(CHANNEL_SCALE) as [
138
+ ScaledChannelName,
139
+ ScaleName
140
+ ][]) {
141
+ // check if the mark has defined an accessor for this channel
142
+ if (options?.[channel] !== undefined && out[channel] === undefined) {
143
+ // resolve value
144
+ out[channel] = resolveChannel(channel, row, options);
145
+ }
145
146
  }
146
- }
147
- return [out];
148
- })
147
+ return [out];
148
+ })
149
149
  );
150
150
 
151
151
  let prevResolvedData: ResolvedDataRecord[] = [];
@@ -158,7 +158,6 @@
158
158
  }
159
159
  });
160
160
 
161
-
162
161
  function isDifferent(array1: ResolvedDataRecord[], array2: ResolvedDataRecord[]) {
163
162
  if (array1.length !== array2.length) return true;
164
163
  for (let i = 0; i < array1.length; i++) {
@@ -228,7 +227,12 @@
228
227
  );
229
228
  out[`x${suffix}`] = x;
230
229
  out[`y${suffix}`] = y;
231
- out.valid = out.valid && isValid(row.x) && isValid(row.y);
230
+ out.valid =
231
+ out.valid &&
232
+ isValid(row.x) &&
233
+ isValid(row.y) &&
234
+ isValid(x) &&
235
+ isValid(y);
232
236
  }
233
237
  }
234
238
  }
package/dist/Plot.svelte CHANGED
@@ -116,39 +116,47 @@
116
116
  scales,
117
117
  ...restProps
118
118
  })}
119
- <!-- implicit axes -->
120
- {#if !hasProjection && !hasExplicitAxisX}
121
- {#if options.axes && (options.x.axis === 'top' || options.x.axis === 'both')}
122
- <AxisX anchor="top" automatic />
119
+ <svelte:boundary onerror={(err) => console.warn(err)}>
120
+ <!-- implicit axes -->
121
+ {#if !hasProjection && !hasExplicitAxisX}
122
+ {#if options.axes && (options.x.axis === 'top' || options.x.axis === 'both')}
123
+ <AxisX anchor="top" automatic />
124
+ {/if}
125
+ {#if options.axes && (options.x.axis === 'bottom' || options.x.axis === 'both')}
126
+ <AxisX anchor="bottom" automatic />
127
+ {/if}
123
128
  {/if}
124
- {#if options.axes && (options.x.axis === 'bottom' || options.x.axis === 'both')}
125
- <AxisX anchor="bottom" automatic />
129
+ {#if !hasProjection && !hasExplicitAxisY}
130
+ {#if options.axes && (options.y.axis === 'left' || options.y.axis === 'both')}
131
+ <AxisY anchor="left" automatic />
132
+ {/if}
133
+ {#if options.axes && (options.y.axis === 'right' || options.y.axis === 'both')}
134
+ <AxisY anchor="right" automatic />
135
+ {/if}
126
136
  {/if}
127
- {/if}
128
- {#if !hasProjection && !hasExplicitAxisY}
129
- {#if options.axes && (options.y.axis === 'left' || options.y.axis === 'both')}
130
- <AxisY anchor="left" automatic />
137
+ <!-- implicit grids -->
138
+ {#if !hasExplicitGridX && (options.grid || options.x.grid)}
139
+ <GridX automatic />
131
140
  {/if}
132
- {#if options.axes && (options.y.axis === 'right' || options.y.axis === 'both')}
133
- <AxisY anchor="right" automatic />
141
+ {#if !hasExplicitGridY && (options.grid || options.y.grid)}
142
+ <GridY automatic />
134
143
  {/if}
135
- {/if}
136
- <!-- implicit grids -->
137
- {#if !hasExplicitGridX && (options.grid || options.x.grid)}
138
- <GridX automatic />
139
- {/if}
140
- {#if !hasExplicitGridY && (options.grid || options.y.grid)}
141
- <GridY automatic />
142
- {/if}
143
- <!-- implicit frame -->
144
- {#if options.frame}
145
- <Frame automatic />
146
- {/if}
147
- {@render parentChildren?.({
148
- options,
149
- scales,
150
- ...restProps
151
- })}
144
+ <!-- implicit frame -->
145
+ {#if options.frame}
146
+ <Frame automatic />
147
+ {/if}
148
+ {@render parentChildren?.({
149
+ options,
150
+ scales,
151
+ ...restProps
152
+ })}
153
+ {#snippet failed(error, reset)}
154
+ <text class="error" transform="translate(10,10)">
155
+ {#each error.message.split('\n') as line, i}
156
+ <tspan x="0" dy={i ? 14 : 0}>{line}</tspan>
157
+ {/each}
158
+ </text>{/snippet}
159
+ </svelte:boundary>
152
160
  {/snippet}
153
161
  {#snippet facetAxes()}
154
162
  <FacetAxes />
@@ -160,4 +168,12 @@
160
168
  --plot-bg: white;
161
169
  --plot-fg: currentColor;
162
170
  }
171
+ text.error {
172
+ stroke: var(--plot-bg);
173
+ fill: crimson;
174
+ font-size: 11px;
175
+ stroke-width: 3px;
176
+ font-weight: bold;
177
+ paint-order: stroke fill;
178
+ }
163
179
  </style>
@@ -156,7 +156,7 @@
156
156
  explicitDomains,
157
157
  hasProjection: !!initialOpts.projection,
158
158
  margins: initialOpts.margins,
159
- inset: initialOpts.inset,
159
+ inset: initialOpts.inset
160
160
  })
161
161
  );
162
162
 
@@ -175,7 +175,9 @@
175
175
 
176
176
  const hasProjection = $derived(!!preScales.projection);
177
177
 
178
- const plotWidth = $derived((fixedWidth || width) - plotOptions.marginLeft - plotOptions.marginRight);
178
+ const plotWidth = $derived(
179
+ (fixedWidth || width) - plotOptions.marginLeft - plotOptions.marginRight
180
+ );
179
181
 
180
182
  // the facet and y domain counts are used for computing the automatic height
181
183
  const xFacetCount = $derived(Math.max(1, preScales.fx.domain.length));
@@ -4,7 +4,7 @@ import type { Snippet } from 'svelte';
4
4
  * Returns first argument that is not null or undefined
5
5
  */
6
6
  export declare function coalesce(...args: (RawValue | undefined | null)[]): RawValue | null;
7
- export declare function testFilter(datum: DataRecord, options: Record<ChannelName, ChannelAccessor>): string | number | boolean | Date | null;
7
+ export declare function testFilter(datum: DataRecord, options: Record<ChannelName, ChannelAccessor>): string | number | boolean | symbol | Date | null;
8
8
  export declare function randomId(): string;
9
9
  export declare function isSnippet(value: unknown): value is Snippet;
10
10
  export declare function isValid(value: RawValue | undefined): value is number | Date | string;
@@ -51,7 +51,7 @@ function resolve(datum, accessor, channel, scale) {
51
51
  // so we're passing the original value to accessor functions instead of our wrapped record
52
52
  return accessor(datum.___orig___ != null ? datum.___orig___ : datum);
53
53
  // use accessor string
54
- if (typeof accessor === 'string' && datum[accessor] !== undefined)
54
+ if ((typeof accessor === 'string' || typeof accessor === 'symbol') && datum[accessor] !== undefined)
55
55
  return datum[accessor];
56
56
  // fallback to channel name as accessor
57
57
  if (accessor === null && datum[channel] !== undefined)
@@ -147,7 +147,9 @@ export function createScale(name, scaleOptions, marks, plotOptions, plotWidth, p
147
147
  valueArr.sort(ascending);
148
148
  }
149
149
  const domain = scaleOptions.domain
150
- ? extent(scaleOptions.zero ? [0, ...scaleOptions.domain] : scaleOptions.domain)
150
+ ? isOrdinal
151
+ ? scaleOptions.domain
152
+ : extent(scaleOptions.zero ? [0, ...scaleOptions.domain] : scaleOptions.domain)
151
153
  : type === 'band' ||
152
154
  type === 'point' ||
153
155
  type === 'ordinal' ||
package/dist/index.d.ts CHANGED
@@ -10,6 +10,9 @@ export { default as BollingerX } from './marks/BollingerX.svelte';
10
10
  export { default as BollingerY } from './marks/BollingerY.svelte';
11
11
  export { default as BoxX } from './marks/BoxX.svelte';
12
12
  export { default as BoxY } from './marks/BoxY.svelte';
13
+ export { default as Brush } from './marks/Brush.svelte';
14
+ export { default as BrushX } from './marks/BrushX.svelte';
15
+ export { default as BrushY } from './marks/BrushY.svelte';
13
16
  export { default as Cell } from './marks/Cell.svelte';
14
17
  export { default as CellX } from './marks/CellX.svelte';
15
18
  export { default as CellY } from './marks/CellY.svelte';
package/dist/index.js CHANGED
@@ -11,6 +11,9 @@ export { default as BollingerX } from './marks/BollingerX.svelte';
11
11
  export { default as BollingerY } from './marks/BollingerY.svelte';
12
12
  export { default as BoxX } from './marks/BoxX.svelte';
13
13
  export { default as BoxY } from './marks/BoxY.svelte';
14
+ export { default as Brush } from './marks/Brush.svelte';
15
+ export { default as BrushX } from './marks/BrushX.svelte';
16
+ export { default as BrushY } from './marks/BrushY.svelte';
14
17
  export { default as Cell } from './marks/Cell.svelte';
15
18
  export { default as CellX } from './marks/CellX.svelte';
16
19
  export { default as CellY } from './marks/CellY.svelte';
@@ -46,8 +46,6 @@
46
46
  stack
47
47
  )
48
48
  );
49
-
50
- $inspect({args})
51
49
  </script>
52
50
 
53
51
  <Mark
@@ -61,16 +59,28 @@
61
59
  {@const bw = plot.scales.y.fn.bandwidth()}
62
60
  {@const minx = Math.min(d.x1, d.x2)}
63
61
  {@const maxx = Math.max(d.x1, d.x2)}
64
- {@const inset = resolveProp(args.inset, d.datum, 0)}
62
+ {@const insetLeft = resolveProp(args.insetLeft, d.datum, 0)}
63
+ {@const insetRight = resolveProp(args.insetRight, d.datum, 0)}
64
+ {@const insetTop = resolveProp(args.insetTop || args.inset, d.datum, 0)}
65
+ {@const insetBottom = resolveProp(args.insetBottom || args.inset, d.datum, 0)}
65
66
  {@const dx = resolveProp(args.dx, d.datum, 0)}
66
67
  {@const dy = resolveProp(args.dy, d.datum, 0)}
67
68
  {#if d.valid}
68
69
  {@const [style, styleClass] = resolveStyles(plot, d, args, 'fill', usedScales)}
69
70
  <path
70
- d={roundedRect(0, 0, maxx - minx, bw - inset * 2, options.borderRadius)}
71
+ d={roundedRect(
72
+ 0,
73
+ 0,
74
+ maxx - minx - insetLeft - insetRight,
75
+ bw - insetTop - insetBottom,
76
+ options.borderRadius
77
+ )}
71
78
  class={[styleClass, className]}
72
79
  {style}
73
- transform="translate({[minx + dx, d.y + inset + dy - bw * 0.5]})"
80
+ transform="translate({[
81
+ minx + dx + insetLeft,
82
+ d.y + insetTop + dy - bw * 0.5
83
+ ]})"
74
84
  use:addEventHandlers={{
75
85
  getPlotState,
76
86
  options: args,
@@ -11,10 +11,8 @@
11
11
  import {
12
12
  type PlotContext,
13
13
  type BaseMarkProps,
14
- type RectMarkProps,
15
14
  type ChannelAccessor,
16
- type DataRow,
17
- type FacetContext
15
+ type DataRow
18
16
  } from '../types.js';
19
17
  import type { StackOptions } from '../transforms/stack.js';
20
18
  import { maybeData } from '../helpers/index.js';
@@ -41,25 +39,23 @@
41
39
  bottomRight?: number;
42
40
  bottomLeft?: number;
43
41
  };
44
- } & RectMarkProps;
42
+ };
45
43
 
46
44
  let { data = [{}], class: className = null, stack, ...options }: BarYProps = $props();
47
45
 
48
46
  const { getPlotState } = getContext<PlotContext>('svelteplot');
49
- let plot = $derived(getPlotState());
47
+ const plot = $derived(getPlotState());
50
48
 
51
- let args = $derived(
49
+ const args = $derived(
52
50
  stackY(
53
51
  intervalY(
54
52
  // by default, sort by x channel (the ordinal labels)
55
- sort(recordizeY({ data: maybeData(data), sort: { channel: 'x' }, ...options })),
53
+ sort(recordizeY({ data, sort: { channel: 'x' }, ...options })),
56
54
  { plot }
57
55
  ),
58
56
  stack
59
57
  )
60
58
  );
61
-
62
- const { getTestFacet } = getContext<FacetContext>('svelteplot/facet');
63
59
  </script>
64
60
 
65
61
  <Mark
@@ -73,16 +69,28 @@
73
69
  {@const bw = plot.scales.x.fn.bandwidth()}
74
70
  {@const miny = Math.min(d.y1, d.y2)}
75
71
  {@const maxy = Math.max(d.y1, d.y2)}
76
- {@const inset = resolveProp(args.inset, d.datum, 0)}
72
+ {@const insetLeft = resolveProp(args.insetLeft || args.inset, d.datum, 0)}
73
+ {@const insetRight = resolveProp(args.insetRight || args.inset, d.datum, 0)}
74
+ {@const insetTop = resolveProp(args.insetTop, d.datum, 0)}
75
+ {@const insetBottom = resolveProp(args.insetBottom, d.datum, 0)}
77
76
  {@const dx = resolveProp(args.dx, d.datum, 0)}
78
77
  {@const dy = resolveProp(args.dy, d.datum, 0)}
79
78
  {#if d.valid}
80
79
  {@const [style, styleClass] = resolveStyles(plot, d, args, 'fill', usedScales)}
81
80
  <path
82
- d={roundedRect(0, 0, bw - inset * 2, maxy - miny, options.borderRadius)}
81
+ d={roundedRect(
82
+ 0,
83
+ 0,
84
+ bw - insetLeft - insetRight,
85
+ maxy - miny - insetTop - insetBottom,
86
+ options.borderRadius
87
+ )}
83
88
  class={[styleClass, className]}
84
89
  {style}
85
- transform="translate({[d.x + inset + dx - bw * 0.5, miny + dy]})"
90
+ transform="translate({[
91
+ d.x + insetLeft + dx - bw * 0.5,
92
+ miny + dy + insetTop
93
+ ]})"
86
94
  use:addEventHandlers={{
87
95
  getPlotState,
88
96
  options: args,
@@ -1,4 +1,25 @@
1
+ import { type BaseMarkProps, type ChannelAccessor, type DataRow } from '../types.js';
2
+ import type { StackOptions } from '../transforms/stack.js';
3
+ type BarYProps = BaseMarkProps & {
4
+ data: DataRow[];
5
+ x?: ChannelAccessor;
6
+ y?: ChannelAccessor;
7
+ y1?: ChannelAccessor;
8
+ y2?: ChannelAccessor;
9
+ stack?: StackOptions;
10
+ /**
11
+ * Converts y into y1/y2 ranges based on the provided interval. Disables the
12
+ * implicit stacking
13
+ */
14
+ interval?: number | string;
15
+ borderRadius?: number | {
16
+ topLeft?: number;
17
+ topRight?: number;
18
+ bottomRight?: number;
19
+ bottomLeft?: number;
20
+ };
21
+ };
1
22
  /** For vertical column charts using a band scale as x axis */
2
- declare const BarY: import("svelte").Component<any, {}, "">;
23
+ declare const BarY: import("svelte").Component<BarYProps, {}, "">;
3
24
  type BarY = ReturnType<typeof BarY>;
4
25
  export default BarY;