Skip to content

Commit e8b4cf5

Browse files
authored
Merge branch 'main' into docs/shift-map-example
2 parents de892c0 + 1a798d3 commit e8b4cf5

File tree

123 files changed

+185
-257
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

123 files changed

+185
-257
lines changed

src/lib/marks/Brush.svelte

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
@component
33
For creating a two-dimensional brush selection
44
-->
5+
<script module lang="ts">
6+
export type Brush = {
7+
x1?: number | Date;
8+
x2?: number | Date;
9+
y1?: number | Date;
10+
y2?: number | Date;
11+
enabled: boolean;
12+
};
13+
</script>
14+
515
<script lang="ts" generics="Datum extends DataRecord">
616
interface BrushMarkProps extends Pick<
717
BaseMarkProps<Datum>,
@@ -58,18 +68,9 @@
5868
...getPlotDefaults().brush
5969
};
6070
61-
type Brush = {
62-
x1?: number | Date;
63-
x2?: number | Date;
64-
y1?: number | Date;
65-
y2?: number | Date;
66-
enabled: boolean;
67-
};
68-
6971
type BrushEvent = MouseEvent & { brush: Brush };
7072
7173
const {
72-
data = [{} as Datum],
7374
stroke,
7475
strokeWidth,
7576
strokeDasharray,
@@ -85,7 +86,7 @@
8586
onbrushstart,
8687
onbrushend,
8788
onbrush
88-
}: BrushMarkProps = $derived({
89+
}: Omit<BrushMarkProps, 'brush'> = $derived({
8990
...DEFAULTS,
9091
...markProps
9192
});
@@ -282,10 +283,10 @@
282283
// draw new brush selection
283284
action = 'draw';
284285
if (typeof xScaleFn.invert === 'function' && limitDimension !== 'y') {
285-
x1 = x2 = xScaleFn.invert(dragStart[0]);
286+
x1 = x2 = xScaleFn.invert(dragStart[0]) as number | Date;
286287
}
287288
if (typeof yScaleFn.invert === 'function' && limitDimension !== 'x') {
288-
y1 = y2 = yScaleFn.invert(dragStart[1]);
289+
y1 = y2 = yScaleFn.invert(dragStart[1]) as number | Date;
289290
}
290291
}
291292
onbrushstart?.({ ...e, brush });
@@ -327,10 +328,10 @@
327328
const hasX = limitDimension !== 'y';
328329
const hasY = limitDimension !== 'x';
329330
330-
const dx1 = !hasX ? 0 : xScaleFn.invert(xScaleFn(x1) + px);
331-
const dx2 = !hasX ? 0 : xScaleFn.invert(xScaleFn(x2) + px);
332-
const dy1 = !hasY ? 0 : yScaleFn.invert(yScaleFn(y1) + py);
333-
const dy2 = !hasY ? 0 : yScaleFn.invert(yScaleFn(y2) + py);
331+
const dx1 = (!hasX ? 0 : xScaleFn.invert(xScaleFn(x1) + px)) as number | Date;
332+
const dx2 = (!hasX ? 0 : xScaleFn.invert(xScaleFn(x2) + px)) as number | Date;
333+
const dy1 = (!hasY ? 0 : yScaleFn.invert(yScaleFn(y1) + py)) as number | Date;
334+
const dy2 = (!hasY ? 0 : yScaleFn.invert(yScaleFn(y2) + py)) as number | Date;
334335
335336
if (action === 'move') {
336337
// move edges
@@ -339,8 +340,8 @@
339340
y1 = dy1;
340341
y2 = dy2;
341342
} else if (action === 'draw') {
342-
x2 = !hasX ? 0 : xScaleFn.invert(newPos[0]);
343-
y2 = !hasY ? 0 : yScaleFn.invert(newPos[1]);
343+
x2 = (!hasX ? 0 : xScaleFn.invert(newPos[0])) as number | Date;
344+
y2 = (!hasY ? 0 : yScaleFn.invert(newPos[1])) as number | Date;
344345
345346
if (constrainToDomain) {
346347
x2 = constrain(x2, xDomain as [typeof x2, typeof x2]);
@@ -383,7 +384,7 @@
383384
action: ActionType,
384385
swapDir1: string,
385386
swapDir2: string
386-
) {
387+
): [number | Date, number | Date, ActionType] {
387388
if (action && v2 < v1) {
388389
return [
389390
v2,
@@ -426,5 +427,5 @@
426427
stroke="transparent"
427428
inset={-20}
428429
{cursor}
429-
{onpointerdown}
430-
{onpointermove} />
430+
onpointerdown={onpointerdown as any}
431+
onpointermove={onpointermove as any} />

src/routes/examples/+page.svelte

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@
88

99
<script lang="ts">
1010
import { groupBy } from 'es-toolkit';
11-
import { getContext } from 'svelte';
1211
import { resolve } from '$app/paths';
13-
import ExamplesPagePreview from '$shared/ui/ExamplesPagePreview.svelte';
12+
import ExamplesPagePreview from '$shared/docs/ExamplesPagePreview.svelte';
1413
1514
const pages = import.meta.glob('./**/*.svelte', {
1615
eager: true

src/routes/examples/[group]/+page.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { page } from '$app/state';
44
55
import type { Component } from 'svelte';
6-
import ExamplesGrid from '$shared/ui/ExamplesGrid.svelte';
6+
import ExamplesGrid from '$shared/docs/ExamplesGrid.svelte';
77
88
type ExampleModule = {
99
default: Component<any>;

src/routes/examples/[group]/[page]/+page.svelte

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@
1010
import { useDark } from '$shared/ui/isDark.svelte';
1111
import CodeBlock from '../../../../theme/components/CodeBlock.svelte';
1212
13+
import {
14+
createREPLState,
15+
encodePlaygroundState
16+
} from './replUtils';
17+
1318
type ExampleModule = {
1419
default: Component<any>;
1520
title: string;
1621
description?: string;
1722
sortKey?: number;
1823
fullCode?: boolean;
19-
repl?: string;
2024
};
2125
2226
type NavLink = {
@@ -141,6 +145,18 @@
141145
return code.trim();
142146
}
143147
148+
const replHash = $derived(
149+
encodePlaygroundState(
150+
createREPLState(
151+
mod?.title,
152+
key,
153+
source,
154+
mod?.data ?? {},
155+
data ?? {}
156+
)
157+
)
158+
);
159+
144160
const ds = useDark();
145161
</script>
146162

@@ -192,13 +208,15 @@
192208
</div>
193209
{/key}
194210

195-
{#if mod?.repl}
211+
{#await replHash then hash}
196212
<p>
197213
<!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
198-
<a href={mod.repl} target="_blank"
214+
<a
215+
href="https://svelte.dev/playground/hello-world?version=5{hash}"
216+
target="_blank"
199217
>Open in Svelte playground</a>
200218
</p>
201-
{/if}
219+
{/await}
202220

203221
<!-- show links to prev and next page -->
204222
<div class="page-switcher">
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { csvFormat } from 'd3-dsv';
2+
3+
const uiComponents = Object.fromEntries(
4+
Object.entries(
5+
import.meta.glob('../../../../shared/ui/*.svelte', {
6+
eager: true,
7+
query: '?raw',
8+
import: 'default'
9+
}) as Record<string, string>
10+
).map(([path, src]) => [path.split('/').at(-1), src])
11+
);
12+
13+
export type REPLState = {
14+
name: string;
15+
tailwind: boolean;
16+
files: {
17+
type: 'file';
18+
name: string;
19+
basename: string;
20+
text: true;
21+
contents: string;
22+
}[];
23+
};
24+
25+
function bytesToBase64url(bytes: Uint8Array<ArrayBuffer>) {
26+
let bin = '';
27+
for (const b of bytes) bin += String.fromCharCode(b);
28+
const b64 = btoa(bin);
29+
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
30+
}
31+
32+
async function gzipStringToBytes(text: string) {
33+
const cs = new CompressionStream('gzip');
34+
const stream = new Blob([new TextEncoder().encode(text)]).stream().pipeThrough(cs);
35+
const out = await new Response(stream).arrayBuffer();
36+
return new Uint8Array(out);
37+
}
38+
39+
export async function encodePlaygroundState(state: REPLState) {
40+
// state is whatever the playground expects (see “reverse-engineer” tip below)
41+
const jsonText = JSON.stringify(state);
42+
const gzBytes = await gzipStringToBytes(jsonText);
43+
return '#' + bytesToBase64url(gzBytes);
44+
}
45+
46+
function firstPositive(a: number, b: number) {
47+
return a > -1 ? a : b;
48+
}
49+
50+
const UI_REGEX = /import \{\s*([a-z]+)(?:,\s*([a-z]+))*\s*\} from '\$shared\/ui'/i;
51+
52+
export function createREPLState(
53+
title: string,
54+
url: string,
55+
source: string,
56+
data: Record<string, string>,
57+
datasets: Record<string, object[]>
58+
): REPLState {
59+
const needSharedUIs: string[] = [];
60+
61+
const appCode = source
62+
.substring(firstPositive(source.indexOf('<script lang="ts">'), source.indexOf('<script>')))
63+
// add import statements for each dataset
64+
.replace(
65+
'import ',
66+
Object.keys(data ?? {}).length
67+
? `${Object.entries(data ?? {})
68+
.map(([key, url]) => `import ${key} from './${url.split('/').at(-1)}';`)
69+
.join('\n ')}\n import `
70+
: 'import '
71+
)
72+
.split(';')
73+
// remove data type imports for now
74+
.filter((line) => !line.trim().startsWith('import type'))
75+
// remove props since we're importing data
76+
.filter((line) => !line.trim().includes('$props'))
77+
// replace shared/ui imports
78+
.map((line) => {
79+
if (line.includes('$shared/ui')) {
80+
const m = line.match(UI_REGEX);
81+
if (!m) return '';
82+
const modules = m?.slice(1);
83+
return modules
84+
?.filter((d) => d)
85+
?.map((mod) => {
86+
needSharedUIs.push(mod);
87+
return `\n import ${mod} from './${mod}.svelte'`;
88+
})
89+
.join(';');
90+
}
91+
return line;
92+
})
93+
.join(';')
94+
.split('\n')
95+
.map((line) => {
96+
// convert from 4-spaces to 2-spaces
97+
const leadingSpaces = line?.match(/^ +/)?.[0]?.length ?? -1;
98+
if (leadingSpaces % 4 === 0) {
99+
const indent = leadingSpaces / 4;
100+
return `${Array.from({ length: indent }, () => ' ').join('')}${line.trim()}`;
101+
}
102+
return line;
103+
})
104+
.join('\n');
105+
return {
106+
name: title,
107+
tailwind: false,
108+
files: [
109+
{
110+
type: 'file',
111+
name: 'App.svelte',
112+
basename: 'App.svelte',
113+
text: true,
114+
contents: `<!--\n This is a SveltePlot example created from\n https://svelteplot.dev/examples/${url}\n-->\n${appCode}`
115+
},
116+
// add dataset files
117+
...Object.entries(data ?? {}).map(([key, url]) => ({
118+
type: 'file',
119+
text: true,
120+
name: `${url.split('/').at(-1)}.js`,
121+
basename: `${url.split('/').at(-1)}.js`,
122+
contents: url.endsWith('.csv')
123+
? `import { csvParse, autoType } from 'd3-dsv';\n\nexport default csvParse(\`${csvFormat(datasets[key])}\`, autoType);`
124+
: `export default ${JSON.stringify(datasets[key], null, 4)};`
125+
})),
126+
...needSharedUIs.map((mod) => ({
127+
type: 'file',
128+
text: true,
129+
name: `${mod}.svelte`,
130+
basename: `${mod}.svelte`,
131+
contents: uiComponents[`${mod}.svelte`]
132+
}))
133+
]
134+
} as REPLState;
135+
}

src/routes/examples/area/area-x.svelte

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
export const title = 'AreaX';
33
export const data = { aapl: '/data/aapl.csv' };
44
export const sortKey = 3;
5-
export const repl =
6-
'https://svelte.dev/playground/3a82dc73ca454af8a69feaa8629a37fa?version=latest';
75
</script>
86

97
<script lang="ts">

src/routes/examples/area/area-y.svelte

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
export const title = 'AreaY';
33
export const data = { aapl: '/data/aapl.csv' };
44
export const sortKey = 3;
5-
export const repl =
6-
'https://svelte.dev/playground/3a0e5ea3118e48378d9302ce35d992f8?version=latest';
75
</script>
86

97
<script lang="ts">

src/routes/examples/area/area.svelte

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
export const title = 'Simple area';
33
export const data = { aapl: '/data/aapl.csv' };
44
export const sortKey = 1;
5-
export const repl =
6-
'https://svelte.dev/playground/27639afc92e84d96a11c341fa16a7e6c?version=latest';
75
</script>
86

97
<script lang="ts">

src/routes/examples/area/density.svelte

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
'Density plot of the weights of Olympians, faceted by sex';
88
export const transforms = ['density'];
99
export const sortKey = 110;
10-
export const repl =
11-
'https://svelte.dev/playground/742e758c08fa4bb5af260e9fc2201607?version=latest';
1210
</script>
1311

1412
<script lang="ts">

src/routes/examples/area/events.svelte

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
<script module>
22
export const title = 'Events';
33
export const data = { riaa: '/data/riaa.csv' };
4-
export const repl =
5-
'https://svelte.dev/playground/1083c5aa80614da9b0d7b1c99b9ef505?version=latest';
64
</script>
75

86
<script lang="ts">

0 commit comments

Comments
 (0)