diff --git a/src/routes/examples/link/_index.svelte b/src/routes/examples/link/_index.svelte
new file mode 100644
index 00000000..3c03d894
--- /dev/null
+++ b/src/routes/examples/link/_index.svelte
@@ -0,0 +1,10 @@
+
+
+Link examples
+
+
+ Here are examples related to the Link mark.
+
diff --git a/src/routes/examples/link/metros.svelte b/src/routes/examples/link/metros.svelte
new file mode 100644
index 00000000..e7c9794e
--- /dev/null
+++ b/src/routes/examples/link/metros.svelte
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+ !hl || hl.Metro === d.Metro ? 1 : 0.1
+ }}
+ onmouseenter={(event, d) => (hl = d)}
+ onmouseleave={() => (hl = null)}
+ stroke={(d) => d.R90_10_2015 - d.R90_10_1980} />
+
+ hl ? d.Metro === hl.Metro : d.highlight}
+ text="nyt_display"
+ fill="currentColor"
+ stroke="var(--svelteplot-bg)"
+ strokeWidth={4}
+ lineAnchor="bottom"
+ dy={-6} />
+
diff --git a/src/routes/examples/link/spherical-link.svelte b/src/routes/examples/link/spherical-link.svelte
new file mode 100644
index 00000000..09572c79
--- /dev/null
+++ b/src/routes/examples/link/spherical-link.svelte
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/routes/examples/rect/_index.svelte b/src/routes/examples/rect/_index.svelte
new file mode 100644
index 00000000..cce5c319
--- /dev/null
+++ b/src/routes/examples/rect/_index.svelte
@@ -0,0 +1,5 @@
+
+
+Rect mark examples
diff --git a/src/routes/examples/rect/simple-rects.svelte b/src/routes/examples/rect/simple-rects.svelte
new file mode 100644
index 00000000..7c3768b1
--- /dev/null
+++ b/src/routes/examples/rect/simple-rects.svelte
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
diff --git a/src/routes/examples/rect/stacked-rectx.svelte b/src/routes/examples/rect/stacked-rectx.svelte
new file mode 100644
index 00000000..17fdfcbe
--- /dev/null
+++ b/src/routes/examples/rect/stacked-rectx.svelte
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/src/routes/examples/rect/stacked-recty.svelte b/src/routes/examples/rect/stacked-recty.svelte
new file mode 100644
index 00000000..9b4d5523
--- /dev/null
+++ b/src/routes/examples/rect/stacked-recty.svelte
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/src/routes/examples/rule/_index.svelte b/src/routes/examples/rule/_index.svelte
new file mode 100644
index 00000000..03598ff9
--- /dev/null
+++ b/src/routes/examples/rule/_index.svelte
@@ -0,0 +1,10 @@
+
+
+Rule examples
+
+
+ Here are examples related to the rule mark.
+
diff --git a/src/routes/examples/rule/data-rules.svelte b/src/routes/examples/rule/data-rules.svelte
new file mode 100644
index 00000000..baefac6b
--- /dev/null
+++ b/src/routes/examples/rule/data-rules.svelte
@@ -0,0 +1,34 @@
+
+
+
+
+
+ monthNames[d.Date.getMonth()]} />
+
diff --git a/src/routes/examples/rule/min-max.svelte b/src/routes/examples/rule/min-max.svelte
new file mode 100644
index 00000000..381448e0
--- /dev/null
+++ b/src/routes/examples/rule/min-max.svelte
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/routes/examples/rule/y-baseline.svelte b/src/routes/examples/rule/y-baseline.svelte
new file mode 100644
index 00000000..66fac509
--- /dev/null
+++ b/src/routes/examples/rule/y-baseline.svelte
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
diff --git a/src/routes/examples/text/frame-anchor.svelte b/src/routes/examples/text/frame-anchor.svelte
index cd7a0913..8c4430d0 100644
--- a/src/routes/examples/text/frame-anchor.svelte
+++ b/src/routes/examples/text/frame-anchor.svelte
@@ -6,7 +6,7 @@
import { setContext } from 'svelte';
import { Plot, Text } from 'svelteplot';
- import type { PlotDefaults } from 'svelteplot/types';
+ import type { PlotDefaults } from 'svelteplot';
setContext>(
'svelteplot/defaults',
{
diff --git a/src/routes/examples/types.ts b/src/routes/examples/types.ts
new file mode 100644
index 00000000..b71116f8
--- /dev/null
+++ b/src/routes/examples/types.ts
@@ -0,0 +1,158 @@
+type AaplRow = {
+ Date: Date;
+ Open: number;
+ High: number;
+ Low: number;
+ Close: number;
+ Volume: number;
+ [`Adj Close`]: number;
+};
+
+type SimpsonsRow = {
+ episode: string;
+ season: string;
+ /**
+ * the imdb rating of the episode
+ */
+ imdb_rating: number;
+ title: string;
+};
+
+type PenguinsRow = {
+ species: string;
+ island: string;
+ bill_length_mm: number;
+ bill_depth_mm: number;
+ flipper_length_mm: number;
+ body_mass_g: number;
+};
+
+type LanguagesRow = {
+ Language: string;
+ Remarks: string;
+ Family: string;
+ Branch: string;
+ [`First-language`]: string;
+ [`Second-language`]: string;
+ [`Total speakers`]: number;
+};
+
+type EarthquakeFeature = {
+ type: 'Feature';
+ properties: {
+ mag: number;
+ place: string;
+ time: number;
+ updated: number;
+ url: string;
+ detail: string;
+ status: 'reviewed';
+ tsunami: number;
+ sig: number;
+ net: string;
+ code: string;
+ ids: string;
+ sources: string;
+ types: string;
+ nst: number;
+ dmin: number;
+ rms: number;
+ gap: number;
+ magType: string;
+ type: 'earthquake';
+ title: string;
+ };
+ geometry: {
+ type: 'Point';
+ coordinates: [number, number, number];
+ };
+ id: string;
+};
+
+type BeagleRow = {
+ lon: number;
+ lat: number;
+};
+
+type RiaaRow = {
+ year: Date;
+ format: string;
+ group: string;
+ revenue: number;
+};
+
+type MetrosRow = {
+ Metro: string;
+ POP_1980: number;
+ LPOP_1980: number;
+ R90_10_1980: number;
+ POP_2015: number;
+ LPOP_2015: number;
+ R90_10_2015: number;
+ nyt_display: string;
+ state_display: string;
+ highlight: number;
+};
+
+type WindRow = {
+ longitude: number;
+ latitude: number;
+ u: number;
+ v: number;
+};
+
+type ElectionRow = {
+ // state,fips,margin2020,margin2016,votes,votes2016
+ state: string;
+ fips: number;
+ margin2020: number;
+ margin2016: number;
+ votes: number;
+ votes2016: number;
+};
+
+interface WorldAtlas extends TopoJSON.Topology {
+ objects: {
+ countries: {
+ type: 'GeometryCollection';
+ geometries: Array;
+ };
+ land: TopoJSON.GeometryCollection;
+ };
+ bbox: [number, number, number, number];
+ transform: TopoJSON.Transform;
+}
+
+interface USAtlas extends TopoJSON.Topology {
+ objects: {
+ counties: {
+ type: 'GeometryCollection';
+ geometries: Array;
+ };
+ states: {
+ type: 'GeometryCollection';
+ geometries: Array;
+ };
+ nation: TopoJSON.GeometryCollection;
+ };
+ bbox: [number, number, number, number];
+ transform: TopoJSON.Transform;
+}
+
+export type ExamplesData = {
+ aapl: AaplRow[];
+ simpsons: SimpsonsRow[];
+ penguins: PenguinsRow[];
+ languages: LanguagesRow[];
+ earthquakes: {
+ type: 'FeatureCollection';
+ features: EarthquakeFeature[];
+ };
+ world: WorldAtlas;
+ us: USAtlas;
+ beagle: BeagleRow[];
+ riaa: RiaaRow[];
+ metros: MetrosRow[];
+ wind: WindRow[];
+ election: ElectionRow[];
+};
diff --git a/src/routes/examples/vector/_index.svelte b/src/routes/examples/vector/_index.svelte
new file mode 100644
index 00000000..06bbc2b6
--- /dev/null
+++ b/src/routes/examples/vector/_index.svelte
@@ -0,0 +1,5 @@
+
+
+Vector examples
diff --git a/src/routes/examples/vector/spike-map.svelte b/src/routes/examples/vector/spike-map.svelte
new file mode 100644
index 00000000..413411af
--- /dev/null
+++ b/src/routes/examples/vector/spike-map.svelte
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+ d.properties?.votes ?? 0} />
+
diff --git a/src/routes/examples/vector/wind.svelte b/src/routes/examples/vector/wind.svelte
new file mode 100644
index 00000000..225cc331
--- /dev/null
+++ b/src/routes/examples/vector/wind.svelte
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+ (Math.atan2(u, v) * 180) / Math.PI}
+ length={({ u, v }) => Math.hypot(u, v)}
+ stroke={({ u, v }) => Math.hypot(u, v)} />
+
diff --git a/src/routes/features/markers/+page.md b/src/routes/features/markers/+page.md
index 87f690a3..a4c724fa 100644
--- a/src/routes/features/markers/+page.md
+++ b/src/routes/features/markers/+page.md
@@ -88,7 +88,7 @@ Note that for the interpolation methods `basis`, `bundle`, and `step`, the marke
import { Plot, LineY, Dot } from 'svelteplot';
import Slider from '$lib/ui/Slider.svelte';
import Select from '$lib/ui/Select.svelte';
- import type { CurveName } from '$lib/types.js';
+ import type { CurveName } from '$lib/types/index.js';
// curve demo
const numbers = [
@@ -134,7 +134,7 @@ You can also specify a custom marker icon using the `marker` snippet:
```svelte live
-
-
-
-
-
-
+
- {#snippet children({ datum })}
-
+ y="culmen_depth_mm"
+ stroke="species">
+ {#snippet mark({ record })}
+
+
+
{/snippet}
```
```svelte
-
+
+ {#snippet mark({ record })}
+
+
+
+ {/snippet}
+
+```
+
+We can also pass the `marks` (plural) snippet to draw all symbols at once:
+
+```svelte
+
+ {#snippet marks({ records })}
+ [r.x, r.y])
+ .join(' ')} />
+ {/snippet}
+
+```
+
+```svelte live
+
+
+
- {#snippet children({ datum })}
-
-
+ {#snippet marks({ records })}
+ [r.x, r.y])
+ .join(' ')} />
+ {/snippet}
+
+
+```
+
+see [example](/examples/custom/multiple)
+
+Or we can use a custom mark to draw the topline of a histogram:
+
+```svelte live
+
+
+
+ {@const binned = binX(
+ { data: olympians, x: 'weight' },
+ { y: 'count', interval: 3 }
+ )}
+
+
+ {#snippet marks({ records })}
+
{/snippet}
```
+```svelte
+
+ {@const binned = binX(
+ { data: olympians, x: 'weight' },
+ { y: 'count', interval: 3 }
+ )}
+
+
+ {#snippet marks({ records })}
+
+ {/snippet}
+
+
+```
+
+## CustomMark
+
+```svelte
+
+ {#snippet mark({ record })}
+
+ {/snippet}
+
+```
+
## CustomMarkHTML
You can arrange custom HTML elements in the plot using the `CustomMarkHTML` mark (name subject to change)
@@ -176,54 +276,3 @@ You can arrange custom HTML elements in the plot using the `CustomMarkHTML` mark
- x
- y
- frameAnchor (see [Text](/marks/text) mark)
-
-## mapXY
-
-Another way to use custom marks is to position them yourself using the `mapXY` method:
-
-```svelte live
-
-
-
- {#snippet children({ mapXY })}
- {#each data as { val1, val2 }}
- {@const { x, y } = mapXY(val1, val2)}
-
-
-
- {/each}
- {/snippet}
-
-```
-
-```svelte
-
- {#snippet children({ mapXY })}
- {#each data as { val1, val2 }}
- {@const { x, y } = mapXY(val1, val2)}
-
-
-
- {/each}
- {/snippet}
-
-```
diff --git a/src/routes/marks/custom/+page.ts b/src/routes/marks/custom/+page.ts
index 2d0ac934..379085e1 100644
--- a/src/routes/marks/custom/+page.ts
+++ b/src/routes/marks/custom/+page.ts
@@ -3,6 +3,6 @@ import type { PageLoad } from './$types.js';
export const load: PageLoad = async ({ fetch }) => {
return {
- data: await loadDatasets(['penguins'], fetch)
+ data: await loadDatasets(['penguins', 'olympians'], fetch)
};
};
diff --git a/src/routes/marks/line/+page.md b/src/routes/marks/line/+page.md
index 75c6587d..ffc41134 100644
--- a/src/routes/marks/line/+page.md
+++ b/src/routes/marks/line/+page.md
@@ -39,7 +39,7 @@ If the **x** and **y** options are not defined, the line mark assumes that the d
```svelte live
diff --git a/src/routes/marks/line/CurveDemo.svelte b/src/routes/marks/line/CurveDemo.svelte
index d7c02569..5f01f467 100644
--- a/src/routes/marks/line/CurveDemo.svelte
+++ b/src/routes/marks/line/CurveDemo.svelte
@@ -2,7 +2,7 @@
import { Plot, LineY, Dot } from '$lib/index.js';
import Slider from '$lib/ui/Slider.svelte';
import Select from '$lib/ui/Select.svelte';
- import type { CurveName } from '$lib/types.js';
+ import type { CurveName } from 'svelteplot/types/index.js';
// curve demo
const numbers = [
diff --git a/src/routes/marks/link/+page.md b/src/routes/marks/link/+page.md
index d36ea9dc..a83526a5 100644
--- a/src/routes/marks/link/+page.md
+++ b/src/routes/marks/link/+page.md
@@ -24,8 +24,7 @@ For connecting two points with a line.
scheme: 'BuRd',
pivot: 1.5,
label: 'Change in inequality from 1980 to 2015',
- legend: true,
- tickFormat: '+f'
+ legend: true
}}>
(!hl || hl === d ? 1 : 0.1)
+ value: (d) =>
+ !hl || hl.Metro === d.Metro ? 1 : 0.1
}}
onmouseenter={(event, d) => (hl = d)}
onmouseleave={() => (hl = null)}
@@ -47,7 +47,8 @@ For connecting two points with a line.
data={metros}
x="POP_2015"
y="R90_10_2015"
- filter={(d) => (hl ? d === hl : d.highlight)}
+ filter={(d) =>
+ hl ? d.Metro === hl.Metro : d.highlight}
text="nyt_display"
fill="currentColor"
stroke="var(--svelteplot-bg)"
diff --git a/src/routes/marks/rect/+page.md b/src/routes/marks/rect/+page.md
index 03b9610d..618d3189 100644
--- a/src/routes/marks/rect/+page.md
+++ b/src/routes/marks/rect/+page.md
@@ -135,6 +135,26 @@ The interval transform may be used to convert a single value in x or y (or both)
## Rect
+The rect mark supports the following channels:
+
+- **data** - Array of data objects to be rendered as rectangles
+- **x1** - Channel accessor for left edge x coordinate
+- **x2** - Channel accessor for right edge x coordinate
+- **y1** - Channel accessor for bottom edge y coordinate
+- **y2** - Channel accessor for top edge y coordinate
+- **x** - Channel accessor for x position (used with interval transform)
+- **y** - Channel accessor for y position (used with interval transform)
+- **interval** - Number or string to convert single x/y values into ranges
+
+Aside from the default mark styling properties you can adjust the appearance of the rectangles using these props:
+
+- **inset** - Inset all sides of rectangles by this amount
+- **insetLeft** - Inset left side of rectangles
+- **insetRight** - Inset right side of rectangles
+- **insetTop** - Inset top side of rectangles
+- **insetBottom** - Inset bottom side of rectangles
+- **borderRadius** - Border radius for rounded corners, either a number or an object with `{ topLeft, topRight, bottomLeft, bottomRight }`.
+
## RectX
RectX can be used for range annotations:
diff --git a/src/routes/marks/rule/+page.md b/src/routes/marks/rule/+page.md
index be0ee46e..3facc9e7 100644
--- a/src/routes/marks/rule/+page.md
+++ b/src/routes/marks/rule/+page.md
@@ -39,7 +39,7 @@ Like most other marks, rules also accept data for displaying multiple lines at o
```svelte live
+
+
+ /[aeiouy]/i.test(d.letter)}
+ x="letter"
+ y="frequency" />
+
+```
+
+```svelte
+
+ /[aeiouy]/i.test(d.letter)}
+ x="letter"
+ y="frequency" />
+
+```
+
+[fork](https://svelte.dev/playground/2500feb83e8f42819dc1b4cfe9c8b1f7?version=latest)
diff --git a/src/routes/transforms/filter/+page.ts b/src/routes/transforms/filter/+page.ts
index d4cf4926..ee31f31c 100644
--- a/src/routes/transforms/filter/+page.ts
+++ b/src/routes/transforms/filter/+page.ts
@@ -1,5 +1,8 @@
+import { loadDatasets } from 'svelteplot/helpers/data.js';
import type { PageLoad } from './$types.js';
-export const load: PageLoad = () => {
- return { datasets: ['aapl'] };
+export const load: PageLoad = async ({ fetch }) => {
+ return {
+ data: await loadDatasets(['alphabet'], fetch)
+ };
};
diff --git a/src/routes/transforms/interval/+page.md b/src/routes/transforms/interval/+page.md
index 9914781f..1c205b83 100644
--- a/src/routes/transforms/interval/+page.md
+++ b/src/routes/transforms/interval/+page.md
@@ -33,7 +33,7 @@ In contrast, a [rectY](/marks/rect) mark with the interval option and the day in
```svelte live
diff --git a/src/tests/dot-faceted.test.svelte.ts b/src/tests/dot-faceted.test.svelte.ts
new file mode 100644
index 00000000..d9b9aac2
--- /dev/null
+++ b/src/tests/dot-faceted.test.svelte.ts
@@ -0,0 +1,62 @@
+import { describe, it, expect, test } from 'vitest';
+import { render } from '@testing-library/svelte';
+import DotTest from './dot.test.svelte';
+import { getTranslate } from './utils';
+import { symbol, symbolCircle } from 'd3-shape';
+import { tick } from 'svelte';
+
+const testData = [
+ { x: 10, y: 20, category: 'A', value: 5 },
+ { x: 15, y: 25, category: 'A', value: 10 },
+ { x: 15, y: 15, category: 'B', value: 10 },
+ { x: 10, y: 15, category: 'B', value: 10 },
+ { x: 15, y: 20, category: 'C', value: 10 }
+];
+
+describe('Faceted dot mark', () => {
+ it('renders dots in 3 facets', () => {
+ const { container } = render(DotTest, {
+ props: {
+ plotArgs: {},
+ dotArgs: {
+ data: testData,
+ x: 'x',
+ y: 'y',
+ fx: 'category',
+ r: 'value'
+ }
+ }
+ });
+ const facets = container.querySelectorAll('g.facet');
+ expect(facets.length).toBe(3); // 3 unique categories: A, B, C
+ const dotsInFacets = Array.from(facets).map(
+ (facet) => facet.querySelectorAll('g.dot > path').length
+ );
+ expect(dotsInFacets).toEqual([2, 2, 1]); // A has 2, B has 2, C has 1
+
+ const dots = container.querySelectorAll('g.dot > path');
+ expect(dots.length).toBe(5);
+ });
+
+ it('hides one facet after data changes', async () => {
+ const props = $state({
+ plotArgs: {},
+ dotArgs: {
+ data: testData,
+ x: 'x',
+ y: 'y',
+ fx: 'category',
+ r: 'value'
+ }
+ });
+ const { container } = render(DotTest, { props });
+ const facets = container.querySelectorAll('g.facet');
+ expect(facets.length).toBe(3); // 3 unique categories: A, B, C
+
+ props.dotArgs.data = testData.filter((d) => d.category !== 'C');
+ await tick();
+
+ const facets2 = container.querySelectorAll('g.facet');
+ expect(facets2.length).toBe(2); // 3 unique categories: A, B, C
+ });
+});
diff --git a/src/tests/dot.test.svelte b/src/tests/dot.test.svelte
index 07762711..a892a88d 100644
--- a/src/tests/dot.test.svelte
+++ b/src/tests/dot.test.svelte
@@ -1,5 +1,5 @@