svelteplot 0.2.8 → 0.2.9

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.
package/dist/Plot.svelte CHANGED
@@ -91,89 +91,101 @@
91
91
  <!-- There's a bug triggering RangeError: Maximum call stack size exceeded
92
92
  when using SveltePlot in ssr, so for now, we're disabling it -->
93
93
 
94
- <Plot
95
- {overlay}
96
- {underlay}
97
- {...restOptions}
98
- header={userHeader ||
99
- restOptions.title ||
100
- restOptions.subtitle ||
101
- restOptions.color?.legend ||
102
- restOptions.symbol?.legend
103
- ? header
104
- : null}
105
- footer={userFooter || restOptions?.caption ? footer : null}
106
- projection={projectionOpts}
107
- implicitScales
108
- {...scales}>
109
- {#snippet children({
110
- hasProjection,
111
- hasExplicitAxisX,
112
- hasExplicitAxisY,
113
- hasExplicitGridX,
114
- hasExplicitGridY,
115
- options,
116
- scales,
117
- ...restProps
118
- })}
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 />
94
+ <svelte:boundary>
95
+ <Plot
96
+ {overlay}
97
+ {underlay}
98
+ {...restOptions}
99
+ header={userHeader ||
100
+ restOptions.title ||
101
+ restOptions.subtitle ||
102
+ restOptions.color?.legend ||
103
+ restOptions.symbol?.legend
104
+ ? header
105
+ : null}
106
+ footer={userFooter || restOptions?.caption ? footer : null}
107
+ projection={projectionOpts}
108
+ implicitScales
109
+ {...scales}>
110
+ {#snippet children({
111
+ hasProjection,
112
+ hasExplicitAxisX,
113
+ hasExplicitAxisY,
114
+ hasExplicitGridX,
115
+ hasExplicitGridY,
116
+ options,
117
+ scales,
118
+ ...restProps
119
+ })}
120
+ <svelte:boundary onerror={(err) => console.warn(err)}>
121
+ <!-- implicit axes -->
122
+ {#if !hasProjection && !hasExplicitAxisX}
123
+ {#if options.axes && (options.x.axis === 'top' || options.x.axis === 'both')}
124
+ <AxisX anchor="top" automatic />
125
+ {/if}
126
+ {#if options.axes && (options.x.axis === 'bottom' || options.x.axis === 'both')}
127
+ <AxisX anchor="bottom" automatic />
128
+ {/if}
124
129
  {/if}
125
- {#if options.axes && (options.x.axis === 'bottom' || options.x.axis === 'both')}
126
- <AxisX anchor="bottom" automatic />
130
+ {#if !hasProjection && !hasExplicitAxisY}
131
+ {#if options.axes && (options.y.axis === 'left' || options.y.axis === 'both')}
132
+ <AxisY anchor="left" automatic />
133
+ {/if}
134
+ {#if options.axes && (options.y.axis === 'right' || options.y.axis === 'both')}
135
+ <AxisY anchor="right" automatic />
136
+ {/if}
127
137
  {/if}
128
- {/if}
129
- {#if !hasProjection && !hasExplicitAxisY}
130
- {#if options.axes && (options.y.axis === 'left' || options.y.axis === 'both')}
131
- <AxisY anchor="left" automatic />
138
+ <!-- implicit grids -->
139
+ {#if !hasExplicitGridX && (options.grid || options.x.grid)}
140
+ <GridX automatic />
132
141
  {/if}
133
- {#if options.axes && (options.y.axis === 'right' || options.y.axis === 'both')}
134
- <AxisY anchor="right" automatic />
142
+ {#if !hasExplicitGridY && (options.grid || options.y.grid)}
143
+ <GridY automatic />
135
144
  {/if}
136
- {/if}
137
- <!-- implicit grids -->
138
- {#if !hasExplicitGridX && (options.grid || options.x.grid)}
139
- <GridX automatic />
140
- {/if}
141
- {#if !hasExplicitGridY && (options.grid || options.y.grid)}
142
- <GridY automatic />
143
- {/if}
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 (i)}
156
- <tspan x="0" dy={i ? 14 : 0}>{line}</tspan>
157
- {/each}
158
- </text>{/snippet}
159
- </svelte:boundary>
160
- {/snippet}
161
- {#snippet facetAxes()}
162
- <FacetAxes />
145
+ <!-- implicit frame -->
146
+ {#if options.frame}
147
+ <Frame automatic />
148
+ {/if}
149
+ {@render parentChildren?.({
150
+ options,
151
+ scales,
152
+ ...restProps
153
+ })}
154
+ {#snippet failed(error, reset)}
155
+ <text class="error" transform="translate(10,10)">
156
+ {#each error.message.split('\n') as line, i (i)}
157
+ <tspan x="0" dy={i ? 14 : 0}>{line}</tspan>
158
+ {/each}
159
+ </text>{/snippet}
160
+ </svelte:boundary>
161
+ {/snippet}
162
+ {#snippet facetAxes()}
163
+ <FacetAxes />
164
+ {/snippet}
165
+ </Plot>
166
+ {#snippet failed(error)}
167
+ <div class="error">Error: {error.message}</div>
163
168
  {/snippet}
164
- </Plot>
169
+ </svelte:boundary>
165
170
 
166
171
  <style>
167
172
  :root {
168
173
  --plot-bg: white;
169
174
  --plot-fg: currentColor;
170
175
  }
171
- text.error {
172
- stroke: var(--plot-bg);
173
- fill: crimson;
176
+ .error {
174
177
  font-size: 11px;
175
178
  stroke-width: 3px;
176
179
  font-weight: bold;
180
+ }
181
+ text.error {
182
+ stroke: var(--plot-bg);
183
+ fill: crimson;
177
184
  paint-order: stroke fill;
178
185
  }
186
+ div.error {
187
+ color: crimson;
188
+ white-space: pre-wrap;
189
+ line-height: 1.1;
190
+ }
179
191
  </style>
@@ -73,7 +73,7 @@
73
73
  locale: 'en-US',
74
74
  numberFormat: {
75
75
  style: 'decimal',
76
- notation: 'compact',
76
+ // notation: 'compact',
77
77
  compactDisplay: 'short'
78
78
  },
79
79
  markerDotRadius: 3,
@@ -1,5 +1,5 @@
1
1
  import { maybeTimeInterval } from './time.js';
2
- import { range as rangei } from 'd3-array';
2
+ import { extent, range as rangei } from 'd3-array';
3
3
  export function maybeInterval(interval) {
4
4
  if (interval == null)
5
5
  return;
@@ -30,11 +30,12 @@ export function maybeInterval(interval) {
30
30
  return interval;
31
31
  }
32
32
  export function autoTicks(type, ticks, interval, domain, scaleFn, count) {
33
- return ticks
34
- ? ticks
35
- : interval
36
- ? maybeInterval(interval, type).range(domain[0], domain[1])
37
- : typeof scaleFn.ticks === 'function'
38
- ? scaleFn.ticks(count)
39
- : [];
33
+ if (ticks)
34
+ return ticks;
35
+ if (interval) {
36
+ const [lo, hi] = extent(domain);
37
+ const I = maybeInterval(interval, type);
38
+ return I.range(lo, I.offset(hi));
39
+ }
40
+ return typeof scaleFn.ticks === 'function' ? scaleFn.ticks(count) : [];
40
41
  }
@@ -5,6 +5,7 @@ import { isSymbolOrNull } from './typeChecks.js';
5
5
  import { resolveProp, toChannelOption } from './resolve.js';
6
6
  import isDataRecord from './isDataRecord.js';
7
7
  import { createProjection } from './projection.js';
8
+ import { maybeInterval } from './autoTicks.js';
8
9
  /**
9
10
  * compute the plot scales
10
11
  */
@@ -148,7 +149,7 @@ export function createScale(name, scaleOptions, marks, plotOptions, plotWidth, p
148
149
  valueArr.sort(ascending);
149
150
  }
150
151
  const valueArray = type === 'quantile' || type === 'quantile-cont' ? allDataValues.toSorted() : valueArr;
151
- const domain = scaleOptions.domain
152
+ let domain = scaleOptions.domain
152
153
  ? isOrdinal
153
154
  ? scaleOptions.domain
154
155
  : extent(scaleOptions.zero ? [0, ...scaleOptions.domain] : scaleOptions.domain)
@@ -162,6 +163,16 @@ export function createScale(name, scaleOptions, marks, plotOptions, plotWidth, p
162
163
  ? valueArray.toReversed()
163
164
  : valueArray
164
165
  : extent(scaleOptions.zero ? [0, ...valueArray] : valueArray);
166
+ if (scaleOptions.interval) {
167
+ if (isOrdinal) {
168
+ domain = domainFromInterval(domain, scaleOptions.interval, name);
169
+ }
170
+ else {
171
+ if (markTypes.size > 0) {
172
+ console.warn('Setting interval via axis options is only supported for ordinal scales');
173
+ }
174
+ }
175
+ }
165
176
  if (!scaleOptions.scale) {
166
177
  throw new Error(`No scale function defined for ${name}`);
167
178
  }
@@ -192,6 +203,12 @@ export function createScale(name, scaleOptions, marks, plotOptions, plotWidth, p
192
203
  : null
193
204
  };
194
205
  }
206
+ function domainFromInterval(domain, interval, name) {
207
+ const interval_ = maybeInterval(interval);
208
+ const [lo, hi] = extent(domain);
209
+ const out = interval_.range(lo, interval_.offset(hi));
210
+ return name === 'y' ? out.toReversed() : out;
211
+ }
195
212
  /**
196
213
  * Infer a scale type based on the scale name, the data values mapped to it and
197
214
  * the mark types that are bound to the scale
@@ -2,6 +2,7 @@
2
2
  Renders a horizontal axis with labels and tick marks
3
3
  -->
4
4
  <script module lang="ts">
5
+ import type { XOR } from 'ts-essentials';
5
6
  export type AxisXMarkProps = Omit<
6
7
  BaseMarkProps,
7
8
  'fill' | 'fillOpacity' | 'paintOrder' | 'title' | 'href' | 'target'
@@ -22,7 +23,18 @@
22
23
  | Intl.NumberFormatOptions
23
24
  | ((d: RawValue) => string);
24
25
  tickClass?: ConstantAccessor<string>;
25
- };
26
+ /** ticks is a shorthand for defining data, tickCount or interval */
27
+ ticks?: number | string | RawValue[];
28
+ } & XOR<
29
+ {
30
+ /** approximate number of ticks to be generated */
31
+ tickCount?: number;
32
+ },
33
+ {
34
+ /** approximate number of pixels between generated ticks */
35
+ tickSpacing?: number;
36
+ }
37
+ >;
26
38
  </script>
27
39
 
28
40
  <script lang="ts">
@@ -51,12 +63,13 @@
51
63
  };
52
64
 
53
65
  let {
54
- data = [],
66
+ ticks: magicTicks,
67
+ data = Array.isArray(magicTicks) ? magicTicks : [],
55
68
  automatic = false,
56
69
  title,
57
70
  anchor = DEFAULTS.axisXAnchor as 'top' | 'bottom',
58
71
  facetAnchor = 'auto',
59
- interval,
72
+ interval = typeof magicTicks === 'string' ? magicTicks : undefined,
60
73
  tickSize = DEFAULTS.tickSize,
61
74
  tickFontSize = DEFAULTS.tickFontSize,
62
75
  tickPadding = DEFAULTS.tickPadding,
@@ -64,6 +77,8 @@
64
77
  tickFormat,
65
78
  tickClass,
66
79
  class: className,
80
+ tickCount = typeof magicTicks === 'number' ? magicTicks : undefined,
81
+ tickSpacing,
67
82
  ...options
68
83
  }: AxisXMarkProps = $props();
69
84
 
@@ -71,7 +86,11 @@
71
86
  const plot = $derived(getPlotState());
72
87
 
73
88
  const autoTickCount = $derived(
74
- Math.max(3, Math.round(plot.facetWidth / plot.options.x.tickSpacing))
89
+ tickCount != null
90
+ ? tickCount
91
+ : tickSpacing != null
92
+ ? Math.max(3, Math.round(plot.facetWidth / tickSpacing))
93
+ : Math.max(3, Math.round(plot.facetWidth / plot.options.x.tickSpacing))
75
94
  );
76
95
 
77
96
  const ticks: RawValue[] = $derived(
@@ -89,9 +108,9 @@
89
108
  )
90
109
  );
91
110
 
92
- let tickFmt = $derived(tickFormat || plot.options.x.tickFormat);
111
+ const tickFmt = $derived(tickFormat || plot.options.x.tickFormat);
93
112
 
94
- let useTickFormat = $derived(
113
+ const useTickFormat = $derived(
95
114
  typeof tickFmt === 'function'
96
115
  ? tickFmt
97
116
  : plot.scales.x.type === 'band' || plot.scales.x.type === 'point'
@@ -107,6 +126,10 @@
107
126
  : // auto
108
127
  (d: RawValue) =>
109
128
  Intl.NumberFormat(plot.options.locale, {
129
+ // use compact notation if range covers multipe magnitudes
130
+ ...(new Set(ticks.map(Math.log10).map(Math.round)).size > 1
131
+ ? { notation: 'compact' }
132
+ : {}),
110
133
  ...DEFAULTS.numberFormat,
111
134
  style: plot.options.x.percent ? 'percent' : 'decimal'
112
135
  }).format(d)
@@ -1,3 +1,4 @@
1
+ import type { XOR } from 'ts-essentials';
1
2
  export type AxisXMarkProps = Omit<BaseMarkProps, 'fill' | 'fillOpacity' | 'paintOrder' | 'title' | 'href' | 'target'> & {
2
3
  data?: RawValue[];
3
4
  automatic?: boolean;
@@ -11,7 +12,15 @@ export type AxisXMarkProps = Omit<BaseMarkProps, 'fill' | 'fillOpacity' | 'paint
11
12
  tickPadding?: number;
12
13
  tickFormat?: 'auto' | Intl.DateTimeFormatOptions | Intl.NumberFormatOptions | ((d: RawValue) => string);
13
14
  tickClass?: ConstantAccessor<string>;
14
- };
15
+ /** ticks is a shorthand for defining data, tickCount or interval */
16
+ ticks?: number | string | RawValue[];
17
+ } & XOR<{
18
+ /** approximate number of ticks to be generated */
19
+ tickCount?: number;
20
+ }, {
21
+ /** approximate number of pixels between generated ticks */
22
+ tickSpacing?: number;
23
+ }>;
15
24
  import type { BaseMarkProps, RawValue, ConstantAccessor } from '../types.js';
16
25
  /** Renders a horizontal axis with labels and tick marks */
17
26
  declare const AxisX: import("svelte").Component<AxisXMarkProps, {}, "">;
@@ -2,6 +2,7 @@
2
2
  Renders a vertical axis with labels and tick marks
3
3
  -->
4
4
  <script module lang="ts">
5
+ import type { XOR } from 'ts-essentials';
5
6
  export type AxisYMarkProps = Omit<
6
7
  BaseMarkProps,
7
8
  'fill' | 'fillOpacity' | 'paintOrder' | 'title' | 'href' | 'target'
@@ -23,7 +24,18 @@
23
24
  | Intl.NumberFormatOptions
24
25
  | ((d: RawValue) => string);
25
26
  tickClass?: ConstantAccessor<string>;
26
- };
27
+ /** ticks is a shorthand for defining data, tickCount or interval */
28
+ ticks?: number | string | RawValue[];
29
+ } & XOR<
30
+ {
31
+ /** approximate number of ticks to be generated */
32
+ tickCount?: number;
33
+ },
34
+ {
35
+ /** approximate number of pixels between generated ticks */
36
+ tickSpacing?: number;
37
+ }
38
+ >;
27
39
  </script>
28
40
 
29
41
  <script lang="ts">
@@ -51,17 +63,21 @@
51
63
  };
52
64
 
53
65
  let {
54
- data = [],
66
+ ticks: magicTicks,
67
+ data = Array.isArray(magicTicks) ? magicTicks : [],
55
68
  automatic = false,
56
69
  title,
57
70
  anchor = DEFAULTS.axisYAnchor as 'left' | 'right',
58
71
  facetAnchor = 'auto',
72
+ interval = typeof magicTicks === 'string' ? magicTicks : undefined,
59
73
  lineAnchor = 'center',
60
74
  tickSize = DEFAULTS.tickSize,
61
75
  tickFontSize = DEFAULTS.tickFontSize,
62
76
  tickPadding = DEFAULTS.tickPadding,
63
77
  tickFormat,
64
78
  tickClass,
79
+ tickCount = typeof magicTicks === 'number' ? magicTicks : undefined,
80
+ tickSpacing,
65
81
  ...options
66
82
  }: AxisYMarkProps = $props();
67
83
 
@@ -69,7 +85,11 @@
69
85
  const plot = $derived(getPlotState());
70
86
 
71
87
  const autoTickCount = $derived(
72
- Math.max(2, Math.round(plot.facetHeight / plot.options.y.tickSpacing))
88
+ tickCount != null
89
+ ? tickCount
90
+ : tickSpacing != null
91
+ ? Math.max(3, Math.round(plot.facetHeight / tickSpacing))
92
+ : Math.max(2, Math.round(plot.facetHeight / plot.options.y.tickSpacing))
73
93
  );
74
94
 
75
95
  const ticks: RawValue[] = $derived(
@@ -80,7 +100,7 @@
80
100
  autoTicks(
81
101
  plot.scales.y.type,
82
102
  plot.options.y.ticks,
83
- plot.options.y.interval,
103
+ interval || plot.options.y.interval,
84
104
  plot.scales.y.domain,
85
105
  plot.scales.y.fn,
86
106
  autoTickCount
@@ -105,6 +125,10 @@
105
125
  : // auto
106
126
  (d: RawValue) =>
107
127
  Intl.NumberFormat(plot.options.locale, {
128
+ // use compact notation if range covers multipe magnitudes
129
+ ...(new Set(ticks.map(Math.log10).map(Math.round)).size > 1
130
+ ? { notation: 'compact' }
131
+ : {}),
108
132
  ...DEFAULTS.numberFormat,
109
133
  style: plot.options.y.percent ? 'percent' : 'decimal'
110
134
  }).format(d)
@@ -1,3 +1,4 @@
1
+ import type { XOR } from 'ts-essentials';
1
2
  export type AxisYMarkProps = Omit<BaseMarkProps, 'fill' | 'fillOpacity' | 'paintOrder' | 'title' | 'href' | 'target'> & {
2
3
  data?: RawValue[];
3
4
  automatic?: boolean;
@@ -12,7 +13,15 @@ export type AxisYMarkProps = Omit<BaseMarkProps, 'fill' | 'fillOpacity' | 'paint
12
13
  tickPadding?: number;
13
14
  tickFormat?: 'auto' | Intl.DateTimeFormatOptions | Intl.NumberFormatOptions | ((d: RawValue) => string);
14
15
  tickClass?: ConstantAccessor<string>;
15
- };
16
+ /** ticks is a shorthand for defining data, tickCount or interval */
17
+ ticks?: number | string | RawValue[];
18
+ } & XOR<{
19
+ /** approximate number of ticks to be generated */
20
+ tickCount?: number;
21
+ }, {
22
+ /** approximate number of pixels between generated ticks */
23
+ tickSpacing?: number;
24
+ }>;
16
25
  import type { BaseMarkProps, RawValue } from '../types.js';
17
26
  import type { ConstantAccessor } from '../types.js';
18
27
  /** Renders a vertical axis with labels and tick marks */
@@ -0,0 +1,64 @@
1
+ <script>
2
+ let { examples } = $props();
3
+ </script>
4
+
5
+ <div class="list">
6
+ {#each examples as page, i (i)}
7
+ <a href={page.url}>
8
+ <div>
9
+ {#if page.screenshot}
10
+ <img src={page.screenshot} alt={page.title} />{/if}
11
+ </div>
12
+ <h4>
13
+ {page.title}
14
+ </h4>
15
+ </a>
16
+ {/each}
17
+ </div>
18
+
19
+ <style>
20
+ .list {
21
+ display: grid;
22
+ grid-template-columns: repeat(3, 1fr);
23
+ gap: 1rem;
24
+ width: 100%;
25
+ margin: 2rem 0;
26
+ }
27
+
28
+ .list > a {
29
+ display: flex;
30
+ flex-direction: column;
31
+ align-items: left;
32
+ row-gap: 0.3rem;
33
+ text-decoration: none;
34
+
35
+ > div {
36
+ border: 1px solid #88888822;
37
+ border-radius: 2px;
38
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
39
+ padding: 1.5ex;
40
+ }
41
+
42
+ &:hover {
43
+ text-decoration: underline;
44
+ color: var(--svp-text);
45
+ > div {
46
+ border: 1px solid var(--svp-text);
47
+ }
48
+ }
49
+ }
50
+
51
+ .list img {
52
+ width: 100%;
53
+ box-sizing: border-box;
54
+ border-radius: 3px;
55
+ transition: transform 0.2s ease-in-out;
56
+ }
57
+
58
+ .list h4 {
59
+ margin: 0rem;
60
+ font-weight: normal;
61
+ font-size: 13px;
62
+ line-height: 1;
63
+ }
64
+ </style>
@@ -0,0 +1,11 @@
1
+ export default ExamplesGrid;
2
+ type ExamplesGrid = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ declare const ExamplesGrid: import("svelte").Component<{
7
+ examples: any;
8
+ }, {}, "">;
9
+ type $$ComponentProps = {
10
+ examples: any;
11
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteplot",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "license": "ISC",
5
5
  "author": {
6
6
  "name": "Gregor Aisch",
@@ -69,6 +69,7 @@
69
69
  "jsdom": "^26.1.0",
70
70
  "prettier": "^3.5.3",
71
71
  "prettier-plugin-svelte": "^3.4.0",
72
+ "puppeteer": "^24.9.0",
72
73
  "remark-code-extra": "^1.0.1",
73
74
  "remark-code-frontmatter": "^1.0.0",
74
75
  "resize-observer-polyfill": "^1.5.1",
@@ -78,6 +79,7 @@
78
79
  "svelte-highlight": "^7.8.3",
79
80
  "svg-path-parser": "^1.1.0",
80
81
  "topojson-client": "^3.1.0",
82
+ "ts-essentials": "^10.0.4",
81
83
  "tslib": "^2.8.1",
82
84
  "typedoc": "^0.28.5",
83
85
  "typedoc-plugin-markdown": "^4.6.3",
@@ -118,6 +120,7 @@
118
120
  "format": "prettier --write .",
119
121
  "test:unit": "vitest",
120
122
  "release-next": "npm version prerelease --preid next && npm publish && git push && git push --tags && sleep 1 && npm dist-tag add svelteplot@$(npm view . version) next",
121
- "docs": "npm run build && cd build && rsync --recursive . vis4.net:svelteplot/alpha0/"
123
+ "docs": "npm run build && cd build && rsync --recursive . vis4.net:svelteplot/alpha0/",
124
+ "screenshots": "node screenshot-examples.js"
122
125
  }
123
126
  }