+
+ {#snippet dropdown()}
+
+ {@render children()}
+
+ {/snippet}
+
+
+
diff --git a/packages/repl/src/lib/site-kit/components/index.ts b/packages/repl/src/lib/site-kit/components/index.ts
new file mode 100644
index 000000000..283996abc
--- /dev/null
+++ b/packages/repl/src/lib/site-kit/components/index.ts
@@ -0,0 +1,5 @@
+export { default as Checkbox } from './Checkbox.svelte';
+export { default as Dropdown } from './Dropdown.svelte';
+export { default as HoverMenu } from './HoverMenu.svelte';
+export { default as ScreenToggle } from './ScreenToggle.svelte';
+export { default as Toolbox } from './Toolbox.svelte';
diff --git a/packages/repl/src/lib/site-kit/polyfills/index.ts b/packages/repl/src/lib/site-kit/polyfills/index.ts
new file mode 100644
index 000000000..4a9c915c2
--- /dev/null
+++ b/packages/repl/src/lib/site-kit/polyfills/index.ts
@@ -0,0 +1,16 @@
+if (!Array.prototype.at) {
+ Array.prototype.at = function (index: number) {
+ return this[index >= 0 ? index : this.length + index];
+ };
+}
+
+if (!Promise.withResolvers) {
+ (Promise as any).withResolvers = function () {
+ let resolve: any, reject: any;
+ const promise = new Promise((res, rej) => {
+ resolve = res;
+ reject = rej;
+ });
+ return { resolve, reject, promise };
+ };
+}
diff --git a/packages/repl/src/lib/theme.ts b/packages/repl/src/lib/theme.ts
new file mode 100644
index 000000000..dea9cafd4
--- /dev/null
+++ b/packages/repl/src/lib/theme.ts
@@ -0,0 +1,55 @@
+import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
+import { tags as t } from '@lezer/highlight';
+
+export const theme = syntaxHighlighting(
+ HighlightStyle.define([
+ { tag: t.keyword, color: 'var(--shiki-token-keyword)' },
+ {
+ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
+ color: 'var(--shiki-color-text)'
+ },
+ { tag: [t.function(t.variableName), t.labelName], color: 'var(--shiki-token-function)' },
+ {
+ tag: [t.color, t.constant(t.name), t.standard(t.name)],
+ color: 'var(--shiki-color-text)'
+ },
+ { tag: [t.definition(t.name), t.separator], color: 'var(--shiki-color-text)' },
+ {
+ tag: [
+ t.typeName,
+ t.className,
+ t.number,
+ t.changed,
+ t.annotation,
+ t.modifier,
+ t.self,
+ t.namespace
+ ],
+ color: 'var(--shiki-token-function)'
+ },
+ {
+ tag: [
+ t.operator,
+ t.operatorKeyword,
+ t.url,
+ t.escape,
+ t.regexp,
+ t.link,
+ t.special(t.string)
+ ],
+ color: 'var(--shiki-color-text)'
+ },
+ { tag: [t.meta, t.comment], color: 'var(--shiki-token-comment)' },
+ { tag: t.strong, fontWeight: 'bold' },
+ { tag: t.emphasis, fontStyle: 'italic' },
+ { tag: t.strikethrough, textDecoration: 'line-through' },
+ { tag: t.link, color: 'var(--shiki-color-text)', textDecoration: 'underline' },
+ { tag: t.heading, fontWeight: 'bold', color: 'var(--sk-fg-1)' },
+ { tag: [t.atom, t.bool], color: 'var(--sk-code-atom)' },
+ {
+ tag: [t.processingInstruction, t.string, t.inserted],
+ color: 'var(--shiki-token-string)'
+ },
+ { tag: t.invalid, color: '#ff008c' }
+ ])
+);
diff --git a/packages/repl/src/lib/types.d.ts b/packages/repl/src/lib/types.d.ts
new file mode 100644
index 000000000..746c0aaa8
--- /dev/null
+++ b/packages/repl/src/lib/types.d.ts
@@ -0,0 +1,43 @@
+import type { EditorState } from '@codemirror/state';
+import { OutputChunk, RollupError } from '@rollup/browser';
+import type { Readable, Writable } from 'svelte/store';
+import type { CompileError } from 'svelte/compiler';
+import type { Workspace } from './Workspace.svelte';
+import type { BundleResult } from './public';
+
+export type Lang = 'js' | 'svelte' | 'json' | 'md' | 'css' | (string & Record);
+
+type StartOrEnd = {
+ line: number;
+ column: number;
+ character: number;
+};
+
+export type MessageDetails = {
+ start: StartOrEnd;
+ end: StartOrEnd;
+ filename: string;
+ message: string;
+};
+
+export type Warning = MessageDetails;
+
+export type File = {
+ name: string;
+ source: string;
+ type: Lang;
+ modified?: boolean;
+};
+
+export type ReplState = {
+ bundle: BundleResult | null;
+ bundler: import('./Bundler.svelte').default | null;
+ toggleable: boolean;
+};
+
+export type ReplContext = {
+ bundler: Bundler;
+ toggleable: Writable;
+ workspace: Workspace;
+ svelteVersion: string;
+};
diff --git a/packages/repl/src/lib/utils.ts b/packages/repl/src/lib/utils.ts
new file mode 100644
index 000000000..4c4a03c1a
--- /dev/null
+++ b/packages/repl/src/lib/utils.ts
@@ -0,0 +1,4 @@
+export const clamp = (min: number, max: number, value: number) =>
+ Math.max(min, Math.min(max, value));
+
+export const sleep = (ms: number) => new Promise((f) => setTimeout(f, ms));
diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts
new file mode 100644
index 000000000..5094d4ff9
--- /dev/null
+++ b/packages/repl/src/lib/workers/bundler/index.ts
@@ -0,0 +1,691 @@
+import '@sveltejs/site-kit/polyfills';
+import { walk } from 'zimmerframe';
+import '../patch_window';
+import { rollup } from '@rollup/browser';
+import { DEV } from 'esm-env';
+import typescript_strip_types from './plugins/typescript';
+import commonjs from './plugins/commonjs';
+import glsl from './plugins/glsl';
+import json from './plugins/json';
+import csv from './plugins/csv';
+import mp3 from './plugins/mp3';
+import image from './plugins/image';
+import svg from './plugins/svg';
+import replace from './plugins/replace';
+import loop_protect from './plugins/loop-protect';
+import alias_plugin, { resolve } from './plugins/alias';
+import type { Plugin, RollupCache, TransformResult } from '@rollup/browser';
+import type { BundleMessageData, BundleOptions } from '../workers';
+import type { Warning } from '../../types';
+import type { CompileError, CompileResult } from 'svelte/compiler';
+import type { File } from '../../Workspace.svelte';
+import type { Node } from 'estree';
+import { max } from './semver';
+import { NPM, VIRTUAL } from '../constants';
+import {
+ normalize_path,
+ fetch_package,
+ load_svelte,
+ parse_npm_url,
+ resolve_local,
+ resolve_subpath,
+ resolve_version,
+ type Package
+} from '../npm';
+import type { BundleResult } from '$lib/public';
+
+// hack for magic-string and rollup inline sourcemaps
+// do not put this into a separate module and import it, would be treeshaken in prod
+self.window = self;
+
+const ENTRYPOINT = '__entry.js';
+const WRAPPER = '__wrapper.svelte';
+const STYLES = '__styles.js';
+const ESM_ENV = '__esm-env.js';
+
+let current_id: number;
+
+self.addEventListener('message', async (event: MessageEvent) => {
+ switch (event.data.type) {
+ case 'init': {
+ get_svelte(event.data.svelte_version);
+ break;
+ }
+
+ case 'bundle': {
+ try {
+ const { uid, files, options } = event.data;
+ const {
+ svelte,
+ version: svelte_version,
+ can_use_experimental_async
+ } = await get_svelte(options.svelte_version);
+
+ current_id = uid;
+
+ setTimeout(async () => {
+ if (current_id !== uid) return;
+
+ const use_async = can_use_experimental_async && options.async;
+
+ const result = await bundle(
+ svelte,
+ svelte_version,
+ uid,
+ files,
+ options,
+ use_async
+ );
+
+ console.log('[bundle worker result]', result);
+
+ // error object might be augmented, see https://github.com/rollup/rollup/blob/76a3b8ede4729a71eb522fc29f7d550a4358827b/docs/plugin-development/index.md#thiserror,
+ // hence only check that the specific abort property we set is there
+ if (
+ (result.error as any)?.svelte_bundler_aborted ===
+ ABORT.svelte_bundler_aborted
+ ) {
+ return;
+ }
+ if (result && uid === current_id) postMessage(result);
+ });
+ } catch (e) {
+ self.postMessage({
+ type: 'error',
+ uid: event.data.uid,
+ message: `Error loading the compiler: ${(e as Error).message}`
+ });
+ }
+
+ break;
+ }
+ }
+});
+
+let ready: ReturnType;
+let ready_version: string;
+
+function get_svelte(svelte_version: string) {
+ if (ready_version === svelte_version) return ready;
+
+ self.postMessage({ type: 'status', message: `fetching svelte@${svelte_version}` });
+ ready_version = svelte_version;
+ ready = load_svelte(svelte_version || 'latest');
+ ready.then(({ version, can_use_experimental_async }) => {
+ ready_version = version;
+ self.postMessage({
+ type: 'version',
+ version,
+ supports_async: can_use_experimental_async
+ });
+ });
+ return ready;
+}
+
+const ABORT = { svelte_bundler_aborted: true };
+
+let previous: {
+ key: string;
+ cache: RollupCache | undefined;
+ /** Needed because if rollup cache hits then we won't be able to pick up all candidates in subsequent runs */
+ tailwind_candidates: Set;
+};
+
+let tailwind: Awaited>;
+
+async function init_tailwind(user_css = '') {
+ const tailwindcss = await import('tailwindcss');
+
+ const { default: tailwind_preflight } = await import('tailwindcss/preflight.css?raw');
+ const { default: tailwind_theme } = await import('tailwindcss/theme.css?raw');
+ const { default: tailwind_utilities } = await import('tailwindcss/utilities.css?raw');
+
+ const tailwind_files: Record = {
+ 'tailwindcss/theme.css': tailwind_theme,
+ 'tailwindcss/preflight.css': tailwind_preflight,
+ 'tailwindcss/utilities.css': tailwind_utilities
+ };
+
+ const tailwind_base = [
+ `@layer theme, base, components, utilities;`,
+ `@import "tailwindcss/theme.css" layer(theme);`,
+ `@import "tailwindcss/preflight.css" layer(base);`,
+ `@import "tailwindcss/utilities.css" layer(utilities);`,
+ user_css
+ ].join('\n');
+
+ return await tailwindcss.compile(tailwind_base, {
+ loadStylesheet: async (id, base) => {
+ return { content: tailwind_files[id], base, path: '' };
+ }
+ });
+}
+
+async function get_bundle(
+ svelte: typeof import('svelte/compiler'),
+ svelte_version: string,
+ uid: number,
+ mode: 'client' | 'server',
+ virtual: Map,
+ options: BundleOptions,
+ can_use_experimental_async: boolean
+) {
+ let bundle;
+
+ const key = JSON.stringify(options);
+ /** A set of package names (without subpaths) to include in pkg.devDependencies when downloading an app */
+ const imports: Set = new Set();
+ const warnings: Warning[] = [];
+ const all_warnings: Array<{ message: string }> = [];
+
+ const tailwind_candidates =
+ previous?.key === key ? previous.tailwind_candidates : new Set();
+
+ function add_tailwind_candidates(ast: Node | undefined) {
+ if (!ast) return;
+
+ walk(ast, null, {
+ ImportDeclaration() {
+ // don't descend into these nodes, so that we don't
+ // pick up import sources
+ },
+ Literal(node) {
+ if (typeof node.value === 'string' && node.value) {
+ for (const candidate of node.value.split(' ')) {
+ if (candidate) tailwind_candidates.add(candidate);
+ }
+ }
+ },
+ TemplateElement(node) {
+ if (node.value.raw) {
+ for (const candidate of node.value.raw.split(' ')) {
+ if (candidate) tailwind_candidates.add(candidate);
+ }
+ }
+ }
+ });
+ }
+
+ const repl_plugin: Plugin = {
+ name: 'svelte-repl',
+ async resolveId(importee, importer) {
+ if (uid !== current_id) throw ABORT;
+
+ // entrypoint
+ if (!importer) return `${VIRTUAL}/${ENTRYPOINT}`;
+
+ // special case
+ if (importee === 'esm-env') return `${VIRTUAL}/${ESM_ENV}`;
+
+ // importing from a URL
+ if (/^[a-z]+:/.test(importee)) return importee;
+
+ // importing an absolute path (e.g. /data/penguins.csv) — resolve against the origin
+ if (importee[0] === '/') {
+ return self.location.origin + importee;
+ }
+
+ /** The npm package we're importing from, if any */
+ let current: null | Package;
+
+ if (importer.startsWith(NPM)) {
+ const { name, version } = parse_npm_url(importer);
+ current = await fetch_package(name, name === 'svelte' ? svelte_version : version);
+ }
+
+ // importing a relative file
+ if (importee[0] === '.') {
+ if (importer.startsWith(VIRTUAL)) {
+ return resolve(virtual, importee, importer);
+ }
+
+ if (current) {
+ const { name, version } = current.meta;
+ const path = new URL(importee, importer).href.replace(
+ `${NPM}/${name}@${version}/`,
+ ''
+ );
+
+ return normalize_path(current, path, importee, importer);
+ }
+
+ return new URL(importee, importer).href;
+ }
+
+ // importing a file from the same package via pkg.imports
+ if (importee[0] === '#') {
+ if (current) {
+ const subpath = resolve_subpath(current, importee);
+ return normalize_path(current, subpath.slice(2), importee, importer);
+ }
+ return await resolve_local(importee);
+ }
+
+ // importing an external package -> `npm://$/@/`
+ const match = /^((?:@[^/]+\/)?[^/@]+)(?:@([^/]+))?(\/.+)?$/.exec(importee);
+ if (!match) throw new Error(`Invalid import "${importee}"`);
+
+ const pkg_name = match[1];
+
+ if (pkg_name === 'svelte' && svelte_version === 'local') {
+ return await resolve_local(importee);
+ }
+
+ let default_version = 'latest';
+
+ if (current) {
+ // use the version specified in importer's package.json, not `latest`
+ const { meta } = current;
+
+ if (meta.name === pkg_name) {
+ default_version = meta.version;
+ } else {
+ default_version = max(
+ meta.devDependencies?.[pkg_name] ??
+ meta.peerDependencies?.[pkg_name] ??
+ meta.dependencies?.[pkg_name]
+ );
+ }
+ }
+
+ if (importer.startsWith(VIRTUAL)) {
+ // if this was imported by one of our files, add it to the `imports` set
+ imports.add(pkg_name);
+ }
+
+ const v = await resolve_version(match[1], match[2] ?? default_version);
+ const pkg = await fetch_package(pkg_name, pkg_name === 'svelte' ? svelte_version : v);
+ const subpath = resolve_subpath(pkg, '.' + (match[3] ?? ''));
+
+ return normalize_path(pkg, subpath.slice(2), importee, importer);
+ },
+ async load(resolved) {
+ if (uid !== current_id) throw ABORT;
+
+ if (resolved.startsWith(VIRTUAL)) {
+ const file = virtual.get(resolved.slice(VIRTUAL.length + 1))!;
+ return file.contents;
+ }
+
+ if (resolved.startsWith(NPM)) {
+ let [, name, v, subpath] =
+ /^npm:\/\/\$\/((?:@[^/]+\/)?[^/@]+)(?:@([^/]+))?\/(.+)$/.exec(resolved)!;
+
+ const pkg = await fetch_package(name, name === 'svelte' ? svelte_version : v);
+
+ const file = pkg.contents[subpath];
+ if (file) return file.text;
+ }
+
+ const response = await fetch(resolved);
+ if (response.ok) return response.text();
+
+ throw new Error(`Could not load ${resolved}`);
+ },
+ transform(code, id) {
+ if (uid !== current_id) throw ABORT;
+
+ const name = id.replace(VIRTUAL + '/', '').replace(NPM + '/', '');
+
+ self.postMessage({ type: 'status', message: `bundling ${name}` });
+
+ if (!/\.(svelte|js|ts)$/.test(id)) return null;
+
+ let result: CompileResult;
+
+ if (id.endsWith('.svelte')) {
+ const is_gt_5 = Number(svelte.VERSION.split('.')[0]) >= 5;
+
+ const compilerOptions: any = {
+ filename: name,
+ generate: is_gt_5 ? 'client' : 'dom',
+ dev: true,
+ fragments: options.fragments
+ };
+
+ if (is_gt_5) {
+ compilerOptions.runes = options.runes;
+ }
+
+ if (can_use_experimental_async) {
+ compilerOptions.experimental = { async: true };
+ }
+
+ if (compilerOptions.fragments == null) {
+ // if fragments is not set it probably means we are using
+ // a version that doesn't support it, so we need to remove it
+ delete compilerOptions.fragments;
+ }
+
+ result = svelte.compile(code, compilerOptions);
+
+ walk(result.ast.html as import('svelte/compiler').AST.TemplateNode, null, {
+ Attribute(node) {
+ if (Array.isArray(node.value)) {
+ for (const chunk of node.value) {
+ if (chunk.type === 'Text') {
+ for (const candidate of chunk.data.split(' ')) {
+ if (candidate) tailwind_candidates.add(candidate);
+ }
+ }
+ }
+ }
+ }
+ });
+
+ add_tailwind_candidates(result.ast.module);
+ add_tailwind_candidates(result.ast.instance);
+ add_tailwind_candidates(result.ast.html);
+
+ if (result.css?.code) {
+ // resolve local files by inlining them
+ result.css.code = result.css.code.replace(
+ /url\(['"]?\.\/(.+?\.(svg|webp|png))['"]?\)/g,
+ (match, $1, $2) => {
+ if (virtual.has($1)) {
+ if ($2 === 'svg') {
+ return `url('data:image/svg+xml;base64,${btoa(virtual.get($1)!.contents)}')`;
+ } else {
+ return `url('data:image/${$2};base64,${virtual.get($1)!.contents}')`;
+ }
+ } else {
+ return match;
+ }
+ }
+ );
+ // add the CSS via injecting a style tag
+ result.js.code +=
+ '\n\n' +
+ `
+ import { styles as $$_styles } from '${VIRTUAL}/${STYLES}';
+ const $$__style = document.createElement('style');
+ $$__style.textContent = ${JSON.stringify(result.css.code)};
+ document.head.append($$__style);
+ $$_styles.push($$__style);
+ `.replace(/\t/g, '');
+ }
+ } else if (/\.svelte\.(js|ts)$/.test(id)) {
+ const compilerOptions: any = {
+ filename: name,
+ generate: 'client',
+ dev: true
+ };
+
+ if (can_use_experimental_async) {
+ compilerOptions.experimental = { async: true };
+ }
+
+ result = svelte.compileModule?.(code, compilerOptions);
+
+ if (!result) {
+ return null;
+ }
+ } else {
+ return null;
+ }
+
+ // @ts-expect-error
+ (result.warnings || result.stats?.warnings)?.forEach((warning) => {
+ // This is required, otherwise postMessage won't work
+ // @ts-ignore
+ delete warning.toString;
+ // TODO remove stats post-launch
+ // @ts-ignore
+ warnings.push(warning);
+ });
+
+ const transform_result: TransformResult = {
+ code: result.js.code,
+ map: result.js.map
+ };
+
+ return transform_result;
+ }
+ };
+
+ const handled_css_ids = new Set();
+ let user_css = '';
+
+ bundle = await rollup({
+ input: './__entry.js',
+ cache: previous?.key === key ? previous.cache : true,
+ plugins: [
+ alias_plugin(options.aliases, virtual),
+ typescript_strip_types,
+ repl_plugin,
+ commonjs,
+ json,
+ csv,
+ svg,
+ mp3,
+ image,
+ glsl,
+ loop_protect,
+ replace({
+ 'process.env.NODE_ENV': JSON.stringify('production')
+ }),
+ {
+ name: 'css',
+ transform(code, id) {
+ if (id.endsWith('.css')) {
+ if (!handled_css_ids.has(id)) {
+ handled_css_ids.add(id);
+ // We don't handle imports in the user CSS right now, so we remove them
+ // to avoid errors in e.g. the Tailwind compiler
+ user_css += '\n' + code.replace(/@import\s+["'][^"']+["'][^;]*;/g, '');
+ }
+ return {
+ code: '',
+ map: null
+ };
+ }
+ }
+ },
+ options.tailwind && {
+ name: 'tailwind-extract',
+ transform(code, id) {
+ // TODO tidy this up
+ if (id.startsWith(`${NPM}/svelte@`)) return;
+ if (id.startsWith(`${NPM}/clsx@`)) return;
+ if (id === `${VIRTUAL}/${ENTRYPOINT}`) return;
+ if (id === `${VIRTUAL}/${STYLES}`) return;
+ if (id === `${VIRTUAL}/${ESM_ENV}`) return;
+ if (id.endsWith('.svelte')) return;
+
+ try {
+ // Don't let a parser/tailwind error crash the bundler
+ // Can happen for files that begin with a bash shebang
+ add_tailwind_candidates(this.parse(code));
+ } catch {}
+ }
+ }
+ ],
+ onwarn(warning) {
+ all_warnings.push({
+ message: warning.message
+ });
+ }
+ });
+
+ previous = { key, cache: bundle.cache, tailwind_candidates };
+
+ return {
+ bundle,
+ css: options.tailwind
+ ? (tailwind ?? (await init_tailwind(user_css))).build([...tailwind_candidates])
+ : user_css
+ ? user_css
+ : null,
+ imports: Array.from(imports),
+ error: null,
+ warnings,
+ all_warnings
+ };
+}
+
+async function bundle(
+ svelte: typeof import('svelte/compiler'),
+ svelte_version: string,
+ uid: number,
+ files: File[],
+ options: BundleOptions,
+ can_use_experimental_async: boolean
+): Promise {
+ if (!DEV) {
+ console.log(`running Svelte compiler version %c${svelte.VERSION}`, 'font-weight: bold');
+ }
+
+ const lookup: Map = new Map();
+
+ lookup.set(ENTRYPOINT, {
+ type: 'file',
+ name: ENTRYPOINT,
+ basename: ENTRYPOINT,
+ contents:
+ svelte.VERSION.split('.')[0] >= '5'
+ ? `
+ import { unmount as u } from 'svelte';
+ import { styles } from '${VIRTUAL}/${STYLES}';
+ export { mount, untrack } from 'svelte';
+ export { default as App } from '${VIRTUAL}/${WRAPPER}';
+ export function unmount(component) {
+ u(component);
+ styles.forEach(style => style.remove());
+ }
+ `
+ : `
+ import { styles } from '${VIRTUAL}/${STYLES}';
+ export { default as App } from './App.svelte';
+ export function mount(component, options) {
+ return new component(options);
+ }
+ export function unmount(component) {
+ component.$destroy();
+ styles.forEach(style => style.remove());
+ }
+ export function untrack(fn) {
+ return fn();
+ }
+ `,
+ text: true
+ });
+
+ const wrapper = can_use_experimental_async
+ ? `
+
+
+
+
+
+ {#snippet pending()}{/snippet}
+
+ `
+ : `
+
+
+
+ `;
+
+ lookup.set(WRAPPER, {
+ type: 'file',
+ name: WRAPPER,
+ basename: WRAPPER,
+ contents: wrapper,
+ text: true
+ });
+
+ lookup.set(STYLES, {
+ type: 'file',
+ name: STYLES,
+ basename: STYLES,
+ contents: `
+ export let styles = [];
+ `,
+ text: true
+ });
+
+ lookup.set(ESM_ENV, {
+ type: 'file',
+ name: STYLES,
+ basename: STYLES,
+ contents: `
+ export const BROWSER = true;
+ export const DEV = true;
+ `,
+ text: true
+ });
+
+ files.forEach((file) => {
+ lookup.set(file.name, file);
+ });
+
+ try {
+ let client: Awaited> = await get_bundle(
+ svelte,
+ svelte_version,
+ uid,
+ 'client',
+ lookup,
+ options,
+ can_use_experimental_async
+ );
+
+ const client_result = (
+ await client.bundle?.generate({
+ format: 'iife',
+ exports: 'named',
+ inlineDynamicImports: true
+ // sourcemap: 'inline'
+ })
+ )?.output[0];
+
+ const server = false // TODO how can we do SSR?
+ ? await get_bundle(
+ svelte,
+ svelte_version,
+ uid,
+ 'server',
+ lookup,
+ options,
+ can_use_experimental_async
+ )
+ : null;
+
+ const server_result = server
+ ? (
+ await server.bundle?.generate({
+ format: 'iife',
+ name: 'SvelteComponent',
+ exports: 'named'
+ // sourcemap: 'inline'
+ })
+ )?.output?.[0]
+ : null;
+
+ return {
+ uid,
+ error: null,
+ client: client_result,
+ server: server_result,
+ css: client.css,
+ imports: client.imports
+ };
+ } catch (err) {
+ console.error(err);
+
+ const e = err as CompileError; // TODO could be a non-Svelte error?
+
+ return {
+ uid,
+ error: { ...e, message: e.message }, // not all Svelte versions return an enumerable message property
+ client: null,
+ server: null,
+ css: null,
+ imports: null
+ };
+ }
+}
diff --git a/packages/repl/src/lib/workers/bundler/plugins/alias.ts b/packages/repl/src/lib/workers/bundler/plugins/alias.ts
new file mode 100644
index 000000000..4aa97b8b9
--- /dev/null
+++ b/packages/repl/src/lib/workers/bundler/plugins/alias.ts
@@ -0,0 +1,75 @@
+import { VIRTUAL } from '../../constants';
+import type { File } from '$lib/Workspace.svelte';
+import type { Plugin } from '@rollup/browser';
+
+/**
+ * Alias plugin for resolving import aliases (e.g., $lib -> src/lib).
+ * This will also run on npm packages, so that e.g. SvelteKit libraries using $app/environment can also make use of this.
+ *
+ * @example
+ * // With aliases: { '$lib': 'src/lib' }
+ * // import foo from '$lib/foo' -> import foo from 'src/lib/foo'
+ * // import bar from '$lib' -> import bar from 'src/lib'
+ */
+function alias_plugin(aliases: Record = {}, virtual: Map): Plugin {
+ // Sort aliases by length (longest first) to avoid conflicts
+ const alias_entries = Object.entries(aliases).sort((a, b) => b[0].length - a[0].length);
+
+ return {
+ name: 'alias',
+ resolveId(importee, importer) {
+ // Skip if no aliases configured / this is a relative import
+ if (alias_entries.length === 0 || importee[0] === '.') {
+ return null;
+ }
+
+ // Check if the import matches any alias
+ for (const [alias_key, alias_path] of alias_entries) {
+ if (importee === alias_key) {
+ // Exact match - replace with alias path
+ return resolve(virtual, `${VIRTUAL}/${alias_path}`, importer);
+ } else if (importee.startsWith(alias_key + '/')) {
+ // Partial match - replace the prefix
+ const relative_path = importee.slice(alias_key.length + 1);
+ return resolve(virtual, `${VIRTUAL}/${alias_path}/${relative_path}`, importer);
+ }
+ }
+
+ // No alias match, let other plugins handle it
+ return null;
+ }
+ };
+}
+
+/**
+ * Tries to resolve the import path based on the virtual file map, trying different suffixes.
+ */
+export function resolve(virtual: Map, importee: string, importer: string): string {
+ const url = new URL(importee, importer);
+
+ for (const suffix of ['', '.js', '.json', '.ts', '/index.js', '/index.ts']) {
+ const with_suffix = `${url.href.slice(VIRTUAL.length + 1)}${suffix}`;
+ const file = virtual.get(with_suffix);
+
+ if (file) {
+ return url.href + suffix;
+ }
+ }
+
+ if (url.href.endsWith('.ts') || url.href.endsWith('.js')) {
+ // One can mean the other (TS encourages you to import .ts files with .js suffixes, and bundlers handle these cases)
+ const other_suffix = url.href.endsWith('.ts') ? '.js' : '.ts';
+ const with_other_suffix = `${url.href.slice(VIRTUAL.length + 1, -3)}${other_suffix}`;
+ const file = virtual.get(with_other_suffix);
+
+ if (file) {
+ return url.href.slice(0, -3) + other_suffix;
+ }
+ }
+
+ throw new Error(
+ `'${importee}' (imported by ${importer.replace(VIRTUAL + '/', '')}) does not exist`
+ );
+}
+
+export default alias_plugin;
diff --git a/packages/repl/src/lib/workers/bundler/plugins/commonjs.ts b/packages/repl/src/lib/workers/bundler/plugins/commonjs.ts
new file mode 100644
index 000000000..b795b910c
--- /dev/null
+++ b/packages/repl/src/lib/workers/bundler/plugins/commonjs.ts
@@ -0,0 +1,97 @@
+import type { Plugin } from '@rollup/browser';
+import { parse } from 'acorn';
+import type { Node } from 'estree';
+import { walk } from 'zimmerframe';
+
+const require = `function require(id) {
+ if (id in __repl_lookup) return __repl_lookup[id];
+ throw new Error(\`Cannot require modules dynamically (\${id})\`);
+}`;
+
+const plugin: Plugin = {
+ name: 'commonjs',
+
+ transform: (code, id) => {
+ if (
+ id.endsWith('.mjs') ||
+ id.endsWith('.esm.js') ||
+ code.startsWith('import ') ||
+ !/\b(require|module|exports)\b/.test(code)
+ ) {
+ return;
+ }
+
+ try {
+ const ast = parse(code, {
+ ecmaVersion: 'latest'
+ });
+
+ const requires: string[] = [];
+ const exports: Set = new Set();
+
+ walk(ast as Node, null, {
+ CallExpression: (node, context) => {
+ if (node.callee.type === 'Identifier' && node.callee.name === 'require') {
+ if (node.arguments.length !== 1) return;
+ const arg = node.arguments[0];
+ if (arg.type !== 'Literal' || typeof arg.value !== 'string') return;
+
+ requires.push(arg.value);
+ }
+
+ context.next();
+ },
+ AssignmentExpression: (node, context) => {
+ // walk children to find nested requires
+ context.next();
+
+ if (node.operator !== '=') return;
+ if (node.left.type !== 'MemberExpression') return;
+ if (
+ node.left.object.type !== 'Identifier' ||
+ node.left.object.name !== 'exports'
+ )
+ return;
+ if (node.left.computed || node.left.property.type !== 'Identifier') return;
+
+ // Default is a special case (and would result in invalid syntax) and kinda fucked up: https://github.com/evanw/esbuild/issues/1719#issuecomment-953470495
+ if (node.left.property.name !== 'default') {
+ exports.add(node.left.property.name);
+ }
+ }
+ });
+
+ const imports = requires.map((id, i) => `import __repl_${i} from '${id}';`).join('\n');
+ const lookup = `const __repl_lookup = { ${requires
+ .map((id, i) => `'${id}': __repl_${i}`)
+ .join(', ')} };`;
+
+ // Special case https://github.com/mathiasbynens/CSS.escape/issues/12
+ if (id.includes('css.escape')) {
+ code = code.replace("typeof global != 'undefined' ? global : this", 'globalThis');
+ }
+
+ const transformed = [
+ imports,
+ lookup,
+ require,
+ `const exports = {}; const module = { exports };`,
+ code,
+ `export default module.exports;`,
+ ...Array.from(exports).map((name) => {
+ const alias = `$$module_exports_${name}`;
+ return `const ${alias} = module.exports.${name}; export { ${alias} as ${name} };`;
+ })
+ ].join('\n\n');
+
+ return {
+ code: transformed,
+ map: null
+ };
+ } catch (err) {
+ return null;
+ }
+ }
+};
+
+export default plugin;
diff --git a/packages/repl/src/lib/workers/bundler/plugins/csv.ts b/packages/repl/src/lib/workers/bundler/plugins/csv.ts
new file mode 100644
index 000000000..baa7879b5
--- /dev/null
+++ b/packages/repl/src/lib/workers/bundler/plugins/csv.ts
@@ -0,0 +1,15 @@
+import type { Plugin } from '@rollup/browser';
+
+const plugin: Plugin = {
+ name: 'csv',
+ transform: (code, id) => {
+ if (!id.endsWith('.csv')) return;
+
+ return {
+ code: `import { csvParse, autoType } from 'd3-dsv';\nexport default csvParse(\`${code}\`, autoType);`,
+ map: null
+ };
+ }
+};
+
+export default plugin;
diff --git a/packages/repl/src/lib/workers/bundler/plugins/glsl.ts b/packages/repl/src/lib/workers/bundler/plugins/glsl.ts
new file mode 100644
index 000000000..e0af03c11
--- /dev/null
+++ b/packages/repl/src/lib/workers/bundler/plugins/glsl.ts
@@ -0,0 +1,15 @@
+import type { Plugin } from '@rollup/browser';
+
+const plugin: Plugin = {
+ name: 'glsl',
+ transform: (code, id) => {
+ if (!id.endsWith('.glsl')) return;
+
+ return {
+ code: `export default ${JSON.stringify(code)};`,
+ map: null
+ };
+ }
+};
+
+export default plugin;
diff --git a/packages/repl/src/lib/workers/bundler/plugins/image.ts b/packages/repl/src/lib/workers/bundler/plugins/image.ts
new file mode 100644
index 000000000..cbcbaa72d
--- /dev/null
+++ b/packages/repl/src/lib/workers/bundler/plugins/image.ts
@@ -0,0 +1,16 @@
+import type { Plugin } from '@rollup/browser';
+
+const plugin: Plugin = {
+ name: 'image',
+ transform: (code, id) => {
+ const match = id.match(/\.(png|webp)$/);
+ if (!match) return;
+
+ return {
+ code: `export default "data:image/${match[1]};base64,${code}";`,
+ map: null
+ };
+ }
+};
+
+export default plugin;
diff --git a/packages/repl/src/lib/workers/bundler/plugins/json.ts b/packages/repl/src/lib/workers/bundler/plugins/json.ts
new file mode 100644
index 000000000..cd09a5021
--- /dev/null
+++ b/packages/repl/src/lib/workers/bundler/plugins/json.ts
@@ -0,0 +1,15 @@
+import type { Plugin } from '@rollup/browser';
+
+const plugin: Plugin = {
+ name: 'json',
+ transform: (code, id) => {
+ if (!id.endsWith('.json')) return;
+
+ return {
+ code: `export default ${code};`,
+ map: null
+ };
+ }
+};
+
+export default plugin;
diff --git a/packages/repl/src/lib/workers/bundler/plugins/loop-protect.ts b/packages/repl/src/lib/workers/bundler/plugins/loop-protect.ts
new file mode 100644
index 000000000..1bdc6e37e
--- /dev/null
+++ b/packages/repl/src/lib/workers/bundler/plugins/loop-protect.ts
@@ -0,0 +1,113 @@
+import type { Plugin } from '@rollup/browser';
+import { parse } from 'acorn';
+import { print } from 'esrap';
+import ts from 'esrap/languages/ts';
+import type {
+ ArrowFunctionExpression,
+ BlockStatement,
+ DoWhileStatement,
+ ForStatement,
+ FunctionDeclaration,
+ FunctionExpression,
+ Node,
+ Statement,
+ WhileStatement
+} from 'estree';
+import { walk, type Context } from 'zimmerframe';
+
+const TIMEOUT = 100;
+
+const regex = /\b(for|while)\b/;
+
+function parse_statement(code: string): Statement {
+ return parse(code, { ecmaVersion: 'latest' }).body[0] as Statement;
+}
+
+const declaration = parse_statement(`
+ const __start = Date.now();
+`);
+
+const check = parse_statement(`
+ if (Date.now() > __start + ${TIMEOUT}) {
+ throw new Error('Infinite loop detected');
+ }
+`);
+
+export function get_current_function(
+ path: Node[]
+): null | FunctionExpression | FunctionDeclaration | ArrowFunctionExpression {
+ for (let i = path.length - 1; i >= 0; i--) {
+ const node = path[i];
+ if (
+ node.type === 'FunctionDeclaration' ||
+ node.type === 'FunctionExpression' ||
+ node.type === 'ArrowFunctionExpression'
+ ) {
+ return node;
+ }
+ }
+ return null;
+}
+
+function loop_protect(
+ node: Statement,
+ context: Context
+): Node | void {
+ const current_function = get_current_function(context.path);
+
+ if (current_function === null || (!current_function.async && !current_function.generator)) {
+ const body = context.visit(node.body) as import('estree').Statement;
+
+ const statements = body.type === 'BlockStatement' ? [...body.body] : [body];
+
+ const replacement: BlockStatement = {
+ type: 'BlockStatement',
+ body: [
+ declaration,
+ {
+ ...((context.next() ?? node) as Statement),
+ body: {
+ type: 'BlockStatement',
+ body: [...statements, check]
+ }
+ }
+ ]
+ };
+
+ return replacement;
+ }
+
+ context.next();
+}
+
+const plugin: Plugin = {
+ name: 'loop-protect',
+ transform: (code, id) => {
+ // only applies to local files, not imports
+ if (!id.startsWith('./')) return;
+
+ // only applies to JS and Svelte files
+ if (!id.endsWith('.js') && !id.endsWith('.svelte')) return;
+
+ // fast path
+ if (!regex.test(code)) return;
+
+ const ast = parse(code, {
+ ecmaVersion: 'latest',
+ sourceType: 'module'
+ });
+
+ const transformed = walk(ast as Node, null, {
+ WhileStatement: loop_protect,
+ DoWhileStatement: loop_protect,
+ ForStatement: loop_protect
+ });
+
+ // nothing changed
+ if (ast === transformed) return null;
+
+ return print(transformed, ts());
+ }
+};
+
+export default plugin;
diff --git a/packages/repl/src/lib/workers/bundler/plugins/mp3.ts b/packages/repl/src/lib/workers/bundler/plugins/mp3.ts
new file mode 100644
index 000000000..4271fd0a8
--- /dev/null
+++ b/packages/repl/src/lib/workers/bundler/plugins/mp3.ts
@@ -0,0 +1,15 @@
+import type { Plugin } from '@rollup/browser';
+
+const plugin: Plugin = {
+ name: 'mp3',
+ transform: (code, id) => {
+ if (!id.endsWith('.mp3')) return;
+
+ return {
+ code: `export default "data:audio/mp3;base64,${code}";`,
+ map: null
+ };
+ }
+};
+
+export default plugin;
diff --git a/packages/repl/src/lib/workers/bundler/plugins/replace.ts b/packages/repl/src/lib/workers/bundler/plugins/replace.ts
new file mode 100644
index 000000000..3908997ff
--- /dev/null
+++ b/packages/repl/src/lib/workers/bundler/plugins/replace.ts
@@ -0,0 +1,60 @@
+import type { Plugin } from '@rollup/browser';
+
+function escape(str: string) {
+ return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
+}
+
+function ensureFunction(functionOrValue: unknown) {
+ if (typeof functionOrValue === 'function') {
+ return functionOrValue;
+ }
+ return function () {
+ return functionOrValue;
+ };
+}
+
+function longest(a: string, b: string) {
+ return b.length - a.length;
+}
+
+function mapToFunctions(object: Record) {
+ return Object.keys(object).reduce(function (functions: Record, key) {
+ functions[key] = ensureFunction(object[key]);
+ return functions;
+ }, {});
+}
+
+function replace(options: Record): Plugin {
+ const functionValues = mapToFunctions(options);
+ const keys = Object.keys(functionValues).sort(longest).map(escape);
+
+ const pattern = new RegExp('\\b(' + keys.join('|') + ')\\b', 'g');
+
+ return {
+ name: 'replace',
+
+ transform: function transform(code, id) {
+ let hasReplacements = false;
+ let match;
+ let start;
+ let end;
+ let replacement;
+
+ code = code.replace(pattern, (_, key) => {
+ hasReplacements = true;
+ return String(functionValues[key](id));
+ });
+
+ if (!hasReplacements) {
+ return null;
+ }
+
+ return {
+ code,
+ map: null
+ };
+ }
+ };
+}
+
+export default replace;
diff --git a/packages/repl/src/lib/workers/bundler/plugins/svg.ts b/packages/repl/src/lib/workers/bundler/plugins/svg.ts
new file mode 100644
index 000000000..05b6179b6
--- /dev/null
+++ b/packages/repl/src/lib/workers/bundler/plugins/svg.ts
@@ -0,0 +1,15 @@
+import type { Plugin } from '@rollup/browser';
+
+const plugin: Plugin = {
+ name: 'svg',
+ transform: (code, id) => {
+ if (!id.endsWith('.svg')) return;
+
+ return {
+ code: `export default ${btoa(code)};`,
+ map: null
+ };
+ }
+};
+
+export default plugin;
diff --git a/packages/repl/src/lib/workers/bundler/plugins/typescript.ts b/packages/repl/src/lib/workers/bundler/plugins/typescript.ts
new file mode 100644
index 000000000..de520c948
--- /dev/null
+++ b/packages/repl/src/lib/workers/bundler/plugins/typescript.ts
@@ -0,0 +1,16 @@
+import { strip_types } from '../../typescript-strip-types';
+import type { Plugin } from '@rollup/browser';
+
+const plugin: Plugin = {
+ name: 'typescript-strip-types',
+ transform: (code, id) => {
+ const match = id.endsWith('.ts');
+ if (!match) return;
+
+ return {
+ code: strip_types(code)
+ };
+ }
+};
+
+export default plugin;
diff --git a/packages/repl/src/lib/workers/bundler/semver.ts b/packages/repl/src/lib/workers/bundler/semver.ts
new file mode 100644
index 000000000..17d57548a
--- /dev/null
+++ b/packages/repl/src/lib/workers/bundler/semver.ts
@@ -0,0 +1,49 @@
+// https://devhints.io/semver
+export function max(version: string) {
+ if (!version || version === '*' || version === 'x') {
+ return 'latest';
+ }
+
+ // strip any * parts, e.g. 1.2.x becomes 1.2
+ version = version.replace(/\.[x*].+/, '');
+
+ const match = /^([~^])?(\d+|[*x])(?:\.(\d+|[*x])(?:\.(\d+|[*x]))?)?(-.*)?$/.exec(version);
+
+ if (!match) {
+ // bail
+ console.warn(`Could not resolve version from ${version}`);
+ return 'latest';
+ }
+
+ const [_, qualifier, major, minor, _patch, prerelease] = match;
+
+ // prerelease versions (e.g. ^2.0.0-next.18) should be returned as-is
+ // since the stable version they'd resolve to may not exist yet
+ if (prerelease) {
+ return qualifier ? version.slice(qualifier.length) : version;
+ }
+
+ // ^ means 'same major', unless 0.x
+ if (qualifier === '^') {
+ if (major === '0') {
+ if (minor === '0') {
+ return version.slice(1);
+ }
+
+ return `${major}.${minor}`;
+ }
+
+ return major;
+ }
+
+ // ~ means 'same minor'
+ if (qualifier === '~') {
+ if (minor !== undefined) {
+ return `${major}.${minor}`;
+ }
+
+ return major;
+ }
+
+ return version;
+}
diff --git a/packages/repl/src/lib/workers/compiler/index.ts b/packages/repl/src/lib/workers/compiler/index.ts
new file mode 100644
index 000000000..8557fc1dd
--- /dev/null
+++ b/packages/repl/src/lib/workers/compiler/index.ts
@@ -0,0 +1,135 @@
+import '@sveltejs/site-kit/polyfills';
+import type { CompileResult } from 'svelte/compiler';
+import type { ExposedCompilerOptions, File } from '../../Workspace.svelte';
+import { load_svelte } from '../npm';
+import { strip_types } from '../typescript-strip-types';
+
+// hack for magic-string and Svelte 4 compiler
+// do not put this into a separate module and import it, would be treeshaken in prod
+self.window = self;
+
+declare var self: Window & typeof globalThis & { svelte: typeof import('svelte/compiler') };
+
+const cache: Record> = {};
+
+addEventListener('message', async (event) => {
+ const { id, file, version, options } = event.data as {
+ id: number;
+ file: File;
+ version: string;
+ options: ExposedCompilerOptions;
+ };
+
+ cache[version] ??= load_svelte(version);
+ cache[version].catch(() => {
+ delete cache[version];
+ });
+ const { can_use_experimental_async, svelte } = await cache[version];
+
+ if (!file.name.endsWith('.svelte') && !svelte.compileModule) {
+ // .svelte.js file compiled with Svelte 3/4 compiler
+ postMessage({
+ id,
+ filename: file.name,
+ payload: {
+ error: null,
+ result: null,
+ migration: null
+ }
+ });
+ return;
+ }
+
+ let migration = null;
+
+ if (svelte.migrate) {
+ try {
+ migration = svelte.migrate(file.contents, { filename: file.name });
+ } catch (e) {
+ // can this happen?
+ }
+ }
+
+ try {
+ let result: CompileResult;
+
+ if (file.name.endsWith('.svelte')) {
+ const is_svelte_3_or_4 = !svelte.compileModule;
+ const compilerOptions: any = {
+ generate: is_svelte_3_or_4
+ ? options.generate === 'client'
+ ? 'dom'
+ : 'ssr'
+ : options.generate,
+ dev: options.dev,
+ filename: file.name,
+ fragments: options.fragments
+ };
+
+ if (!is_svelte_3_or_4) {
+ compilerOptions.modernAst = options.modernAst; // else Svelte 3/4 will throw an "unknown option" error
+ }
+
+ if (can_use_experimental_async) {
+ compilerOptions.experimental = { async: true };
+ }
+
+ if (compilerOptions.fragments == null) {
+ // if fragments is not set it probably means we are using
+ // a version that doesn't support it, so we need to remove it
+ delete compilerOptions.fragments;
+ }
+
+ result = svelte.compile(file.contents, compilerOptions);
+ } else {
+ const compilerOptions: any = {
+ generate: options.generate,
+ dev: options.dev,
+ filename: file.name
+ };
+
+ if (can_use_experimental_async) {
+ compilerOptions.experimental = { async: true };
+ }
+
+ const content = file.basename.endsWith('.ts')
+ ? strip_types(file.contents)
+ : file.contents;
+ result = svelte.compileModule(content, compilerOptions);
+ }
+
+ postMessage({
+ id,
+ filename: file.name,
+ payload: {
+ error: null,
+ result: {
+ metadata: { runes: false },
+ ...result,
+ warnings: result.warnings.map((w) => {
+ delete w.toString;
+ return { message: w.message, ...w };
+ })
+ },
+ migration
+ }
+ });
+ } catch (e) {
+ if (!e.position && e.loc) {
+ // this came from tsBlankSpace. Workspace expects a
+ // `position` property from a Svelte compile error;
+ // this is a hacky but pragmatic way to solve it
+ e.position = [e.pos, e.raisedAt];
+ }
+
+ postMessage({
+ id,
+ filename: file.name,
+ payload: {
+ error: { message: e.message, ...e },
+ result: null,
+ migration: null
+ }
+ });
+ }
+});
diff --git a/packages/repl/src/lib/workers/constants.ts b/packages/repl/src/lib/workers/constants.ts
new file mode 100644
index 000000000..dd2fad619
--- /dev/null
+++ b/packages/repl/src/lib/workers/constants.ts
@@ -0,0 +1,2 @@
+export const VIRTUAL = 'virtual://$';
+export const NPM = 'npm://$';
diff --git a/packages/repl/src/lib/workers/npm.ts b/packages/repl/src/lib/workers/npm.ts
new file mode 100644
index 000000000..c9416addc
--- /dev/null
+++ b/packages/repl/src/lib/workers/npm.ts
@@ -0,0 +1,251 @@
+import * as resolve from 'resolve.exports';
+import { parseTar, type FileDescription } from 'tarparser';
+import { NPM } from './constants';
+
+export interface Package {
+ meta: any; // package.json contents
+ contents: Record;
+}
+
+/** map of `pkg-name@1` -> `1.2.3` */
+const versions = new Map>();
+const packages = new Map>();
+
+const pkg_pr_new_regex = /^(pr|commit|branch)-(.+)/;
+
+export async function load_svelte(version: string) {
+ if (version === 'local') {
+ await import(/* @vite-ignore */ `${location.origin}/svelte/compiler/index.js`);
+ } else {
+ if (!pkg_pr_new_regex.test(version)) {
+ const resolved_version = await resolve_version('svelte', version);
+ if (resolved_version) {
+ version = resolved_version;
+ } else {
+ throw new Error(`Failed to resolve svelte@${version}`);
+ }
+ }
+
+ const pkg = await fetch_package('svelte', version);
+
+ const entry = version.startsWith('3.')
+ ? 'compiler.js'
+ : version.startsWith('4.')
+ ? 'compiler.cjs'
+ : 'compiler/index.js';
+
+ const compiler = pkg.contents[entry].text;
+
+ (0, eval)(compiler + `\n//# sourceURL=${entry}@` + version);
+ }
+
+ console.log(`Using Svelte compiler version ${version}`);
+
+ let can_use_experimental_async = false;
+
+ try {
+ svelte.compileModule('', {
+ generate: 'client',
+ experimental: {
+ async: true
+ }
+ });
+
+ can_use_experimental_async = true;
+ } catch (e) {
+ // do nothing
+ }
+
+ return {
+ svelte,
+ version,
+ can_use_experimental_async
+ };
+}
+
+export async function resolve_version(name: string, version: string): Promise {
+ if (pkg_pr_new_regex.test(version)) {
+ return version;
+ }
+
+ const key = `${name}@${version}`;
+
+ if (!versions.has(key)) {
+ const promise = fetch(
+ `https://data.jsdelivr.com/v1/packages/npm/${name}/resolved?specifier=${version}`
+ ).then(async (r) => {
+ if (!r.ok) {
+ versions.delete(key);
+ throw new Error(`Failed to import ${key}. Are you sure the package exists?`);
+ }
+
+ return (await r.json()).version;
+ });
+
+ versions.set(key, promise);
+ }
+
+ return await versions.get(key)!;
+}
+
+export async function fetch_package(name: string, version: string): Promise {
+ const key = `${name}@${version}`;
+
+ if (!packages.has(key)) {
+ const match = pkg_pr_new_regex.exec(version);
+
+ const url = match
+ ? `https://pkg.pr.new/svelte@${match[2]}`
+ : `https://registry.npmjs.org/${name}/-/${name.split('/').pop()}-${version}.tgz`;
+
+ const promise = fetch(url).then(async (r) => {
+ if (!r.ok) {
+ packages.delete(url);
+ throw new Error(`Failed to fetch ${url}`);
+ }
+
+ const contents: Record = {};
+
+ for (const file of await parseTar(await r.arrayBuffer())) {
+ if (file.type === 'file') {
+ contents[file.name.slice(8)] = file; // remove `package/` prefix
+ }
+ }
+
+ const meta = JSON.parse(contents['package.json'].text);
+
+ return { meta, contents };
+ });
+
+ packages.set(key, promise);
+ }
+
+ return packages.get(key)!;
+}
+
+export function resolve_subpath(pkg: Package, subpath: string): string {
+ // match legacy Rollup logic — pkg.svelte takes priority over pkg.exports
+ if (typeof pkg.meta.svelte === 'string' && subpath === '.') {
+ return `./${pkg.meta.svelte.replace('./', '')}`;
+ }
+
+ if (subpath[0] === '#') {
+ try {
+ const resolved = resolve.imports(pkg.meta, subpath, {
+ browser: true,
+ conditions: ['svelte', 'module', 'browser', 'development']
+ });
+
+ return resolved?.[0] as string;
+ } catch {
+ throw new Error(
+ `No matched import path was found for "${subpath}" in "${pkg.meta.name}/package.json"`
+ );
+ }
+ }
+
+ // modern
+ if (pkg.meta.exports) {
+ try {
+ const resolved = resolve.exports(pkg.meta, subpath, {
+ browser: true,
+ conditions: ['svelte', 'module', 'browser', 'development']
+ });
+
+ return resolved?.[0] as string;
+ } catch {
+ throw new Error(
+ `No matched export path was found for "${subpath}" in "${pkg.meta.name}/package.json"`
+ );
+ }
+ }
+
+ // legacy
+ if (subpath === '.') {
+ let resolved_id = resolve.legacy(pkg.meta, {
+ fields: ['browser', 'module', 'main']
+ });
+
+ if (typeof resolved_id === 'object' && !Array.isArray(resolved_id)) {
+ const subpath = resolved_id['.'];
+ if (subpath === false) return 'data:text/javascript,export {}';
+
+ resolved_id =
+ subpath ??
+ resolve.legacy(pkg.meta, {
+ fields: ['module', 'main']
+ });
+ }
+
+ if (!resolved_id) {
+ // last ditch — try to match index.js/index.mjs
+ if (pkg.contents['index.mjs']) return './index.mjs';
+ if (pkg.contents['index.js']) return './index.js';
+
+ throw new Error(`Could not find entry point in "${pkg.meta.name}/package.json"`);
+ }
+
+ return resolved_id as string;
+ }
+
+ if (typeof pkg.meta.browser === 'object') {
+ // this will either return `pkg.browser[subpath]` or `subpath`
+ return resolve.legacy(pkg.meta, {
+ browser: subpath
+ }) as string;
+ }
+
+ return subpath;
+}
+
+export function normalize_path(pkg: Package, path: string, importee: string, importer: string) {
+ for (const suffix of ['', '.js', '.mjs', '.cjs', '/index.js', '/index.mjs', '/index.cjs']) {
+ let with_suffix = path + suffix;
+
+ if (pkg.meta.browser) {
+ with_suffix = pkg.meta.browser[`./${with_suffix}`]?.replace('./', '') ?? with_suffix;
+ }
+
+ const file = pkg.contents[with_suffix];
+
+ if (file && file.type === 'file') {
+ return `${NPM}/${pkg.meta.name}@${pkg.meta.version}/${with_suffix}`;
+ }
+ }
+
+ throw new Error(
+ `Could not find ${path} in ${pkg.meta.name}@${pkg.meta.version} (error occurred while trying to resolve ${importee} within ${importer})`
+ );
+}
+
+const LOCAL_PKG_URL = `${location.origin}/svelte/package.json`;
+let local_svelte_pkg: Promise;
+
+export async function resolve_local(specifier: string) {
+ const pkg = await (local_svelte_pkg ??= fetch(LOCAL_PKG_URL).then((r) => r.json()));
+
+ const subpath =
+ specifier[0] === '#'
+ ? (resolve.imports(pkg, specifier, {
+ browser: true
+ })![0] as string)
+ : (resolve.exports(pkg, specifier.replace('svelte', '.'), {
+ browser: true
+ })![0] as string);
+
+ return new URL(subpath, LOCAL_PKG_URL).href;
+}
+
+export function parse_npm_url(href: string) {
+ const match = /^npm:\/\/\$\/((?:@[^/]+\/)?[^/@]+)(?:@([^/]+))?(\/.+)?$/.exec(href);
+
+ if (!match) {
+ throw new Error(`${href} is not a valid npm URL`);
+ }
+
+ return {
+ name: match[1],
+ version: match[2],
+ subpath: match[3]
+ };
+}
diff --git a/packages/repl/src/lib/workers/patch_window.ts b/packages/repl/src/lib/workers/patch_window.ts
new file mode 100644
index 000000000..ff7057c9c
--- /dev/null
+++ b/packages/repl/src/lib/workers/patch_window.ts
@@ -0,0 +1 @@
+self.window = self; // hack for magic-sring and rollup inline sourcemaps
diff --git a/packages/repl/src/lib/workers/typescript-strip-types.ts b/packages/repl/src/lib/workers/typescript-strip-types.ts
new file mode 100644
index 000000000..3e66b5bc9
--- /dev/null
+++ b/packages/repl/src/lib/workers/typescript-strip-types.ts
@@ -0,0 +1,308 @@
+import * as acorn from 'acorn';
+import { walk, type Context, type Visitors } from 'zimmerframe';
+import { tsPlugin } from '@sveltejs/acorn-typescript';
+import MagicString from 'magic-string';
+
+const ParserWithTS = acorn.Parser.extend(tsPlugin());
+
+/**
+ * @param {FunctionExpression | FunctionDeclaration} node
+ * @param {Context} context
+ */
+function remove_this_param_and_optional(
+ node: acorn.FunctionExpression | acorn.FunctionDeclaration,
+ context: Context
+) {
+ for (const param of node.params as Array>) {
+ if (param?.type === 'Identifier') {
+ if (param.name === 'this') {
+ if (param.typeAnnotation) {
+ // the type annotation is blanked by another visitor, do it in two parts to prevent an overwrite error
+ ts_blank_space(context, {
+ start: param.start,
+ end: param.typeAnnotation.start
+ });
+ ts_blank_space(context, {
+ start: param.typeAnnotation.end,
+ end: node.params[1]?.start || param.end
+ });
+ } else {
+ ts_blank_space(context, {
+ start: param.start,
+ end: node.params[1]?.start || param.end
+ });
+ }
+ } else if (param.optional) {
+ const question_start = context.state.ms.original.indexOf('?', param.start);
+ if (question_start !== -1 && question_start < param.end) {
+ ts_blank_space(context, { start: question_start, end: question_start + 1 });
+ }
+ }
+ }
+ }
+ return context.next();
+}
+
+function typescript_invalid_feature(node: any, feature: string) {
+ const e = new Error(`The REPL does not support ${feature}. Please remove it from your code.`);
+ // @ts-expect-error Our REPL error handling needs this
+ e.position = [node.start, node.end];
+ throw e;
+}
+
+const empty = {
+ type: 'EmptyStatement'
+};
+
+function ts_blank_space(context: Context, node: any): void {
+ const { start, end } = node;
+ let i = start;
+ while (i < end) {
+ // Skip whitespace
+ while (i < end && /\s/.test(context.state.ms.original[i])) i++;
+ if (i >= end) break;
+ // Find next whitespace or end
+ let j = i + 1;
+ while (j < end && !/\s/.test(context.state.ms.original[j])) j++;
+ context.state.ms.overwrite(i, j, ' '.repeat(j - i));
+ i = j;
+ }
+}
+
+function specifier_end(node: any, i: number, s: any, context: Context) {
+ let end = node.specifiers[i + 1]?.start;
+ if (end === undefined) {
+ end = s.end;
+ // Look for a comma after s.end, skipping whitespace
+ let j = end;
+ const original = context.state.ms.original;
+ while (j < original.length && /\s/.test(original[j])) j++;
+ if (original[j] === ',') {
+ end = j + 1;
+ }
+ }
+ return end;
+}
+
+const visitors: Visitors = {
+ _(node, context) {
+ if (node.typeAnnotation) ts_blank_space(context, node.typeAnnotation);
+ if (node.typeParameters) ts_blank_space(context, node.typeParameters);
+ if (node.typeArguments) ts_blank_space(context, node.typeArguments);
+ if (node.returnType) ts_blank_space(context, node.returnType);
+ if (node.accessibility) {
+ ts_blank_space(context, {
+ start: node.start,
+ end: node.start + node.accessibility.length
+ });
+ }
+
+ delete node.typeAnnotation;
+ delete node.typeParameters;
+ delete node.typeArguments;
+ delete node.returnType;
+ delete node.accessibility;
+
+ context.next();
+ },
+ Decorator(node, context) {
+ ts_blank_space(context, node);
+ },
+ ImportDeclaration(node, context) {
+ if (node.importKind === 'type') {
+ ts_blank_space(context, node);
+ return empty;
+ }
+
+ if (node.specifiers?.length > 0) {
+ const specifiers = node.specifiers.filter((s: any, i: number) => {
+ if (s.importKind !== 'type') return true;
+
+ ts_blank_space(context, {
+ start: s.start,
+ end: specifier_end(node, i, s, context)
+ });
+ });
+
+ if (specifiers.length === 0) {
+ ts_blank_space(context, node);
+ }
+ }
+ },
+ ExportNamedDeclaration(node, context) {
+ if (node.exportKind === 'type') {
+ ts_blank_space(context, node);
+ return empty;
+ }
+
+ if (node.declaration) {
+ const result = context.next();
+ if (result?.declaration?.type === 'EmptyStatement') {
+ ts_blank_space(context, node);
+ return empty;
+ }
+ return result;
+ }
+
+ if (node.specifiers) {
+ const specifiers = node.specifiers.filter((s: any, i: number) => {
+ if (s.exportKind !== 'type') return true;
+
+ ts_blank_space(context, {
+ start: s.start,
+ end: specifier_end(node, i, s, context)
+ });
+ });
+
+ if (specifiers.length === 0) {
+ ts_blank_space(context, node);
+ }
+ return;
+ }
+ },
+ ExportDefaultDeclaration(node, context) {
+ if (node.exportKind === 'type') {
+ ts_blank_space(context, node);
+ return empty;
+ } else {
+ context.next();
+ }
+ },
+ ExportAllDeclaration(node, context) {
+ if (node.exportKind === 'type') {
+ ts_blank_space(context, node);
+ return empty;
+ } else {
+ context.next();
+ }
+ },
+ PropertyDefinition(node, context) {
+ if (node.accessor) {
+ typescript_invalid_feature(
+ node,
+ 'accessor fields (related TSC proposal is not stage 4 yet)'
+ );
+ } else {
+ context.next();
+ }
+ },
+ TSAsExpression(node, context) {
+ ts_blank_space(context, { start: node.expression.end, end: node.end });
+ context.visit(node.expression);
+ },
+ TSSatisfiesExpression(node, context) {
+ ts_blank_space(context, { start: node.expression.end, end: node.end });
+ context.visit(node.expression);
+ },
+ TSNonNullExpression(node, context) {
+ ts_blank_space(context, { start: node.expression.end, end: node.end });
+ context.visit(node.expression);
+ },
+ TSInterfaceDeclaration(node, context) {
+ ts_blank_space(context, node);
+ return empty;
+ },
+ TSTypeAliasDeclaration(node, context) {
+ ts_blank_space(context, node);
+ return empty;
+ },
+ TSTypeAssertion(node, context) {
+ ts_blank_space(context, { start: node.start, end: node.expression.start });
+ context.visit(node.expression);
+ },
+ TSEnumDeclaration(node, context) {
+ typescript_invalid_feature(node, 'enums');
+ },
+ TSParameterProperty(node, context) {
+ if ((node.readonly || node.accessibility) && context.path.at(-2)?.kind === 'constructor') {
+ typescript_invalid_feature(node, 'accessibility modifiers on constructor parameters');
+ }
+ ts_blank_space(context, { start: node.start, end: node.parameter.start });
+ context.visit(node.parameter);
+ },
+ TSInstantiationExpression(node, context) {
+ ts_blank_space(context, { start: node.start, end: node.expression.start });
+ context.visit(node.expression);
+ },
+ FunctionExpression: remove_this_param_and_optional,
+ FunctionDeclaration: remove_this_param_and_optional,
+ TSDeclareFunction(node, context) {
+ ts_blank_space(context, node);
+ return empty;
+ },
+ ClassDeclaration(node, context) {
+ if (node.declare || node.abstract) {
+ ts_blank_space(context, node);
+ return empty;
+ }
+
+ if (node.implements?.length) {
+ const implements_keyword_start = context.state.ms.original.lastIndexOf(
+ 'implements',
+ node.implements[0].start
+ );
+ ts_blank_space(context, {
+ start: implements_keyword_start,
+ end: node.implements[node.implements.length - 1].end
+ });
+ }
+ context.next();
+ },
+ MethodDefinition(node, context) {
+ if (node.abstract) {
+ ts_blank_space(context, { start: node.start, end: node.start + 'abstract'.length });
+ return empty;
+ }
+ context.next();
+ },
+ VariableDeclaration(node, context) {
+ if (node.declare) {
+ ts_blank_space(context, node);
+ return empty;
+ }
+ context.next();
+ },
+ VariableDeclarator(node, context) {
+ if (node.definite && node.id.type === 'Identifier') {
+ const definite_start = context.state.ms.original.indexOf(
+ '!',
+ node.id.start + node.id.name.length
+ );
+ ts_blank_space(context, { start: definite_start, end: definite_start + 1 });
+ }
+ context.next();
+ },
+ TSModuleDeclaration(node, context) {
+ if (!node.body) {
+ ts_blank_space(context, node);
+ return;
+ }
+ // namespaces can contain non-type nodes
+ const cleaned = node.body.body.map((entry) => context.visit(entry));
+ if (cleaned.some((entry) => entry !== empty)) {
+ typescript_invalid_feature(node, 'namespaces with non-type nodes');
+ }
+ ts_blank_space(context, node);
+ }
+};
+
+/**
+ * Strips type-only constructs from TypeScript code and replaces them with blank spaces.
+ * Errors on non-type constructs that are not supported in the REPL.
+ *
+ * This implementation closely follows the logic of https://github.com/sveltejs/svelte/blob/main/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js
+ *
+ * Used instead of`ts-blank-space` because the latter means we need to bundle all of TypeScript, which increases the worker bundles by 9x.
+ */
+export function strip_types(code: string): string {
+ const ms = new MagicString(code);
+ const ast = ParserWithTS.parse(code, {
+ sourceType: 'module',
+ ecmaVersion: 16,
+ locations: true
+ });
+
+ walk(ast, { ms }, visitors);
+
+ return ms.toString();
+}
diff --git a/packages/repl/src/lib/workers/workers.d.ts b/packages/repl/src/lib/workers/workers.d.ts
new file mode 100644
index 000000000..bde5d5929
--- /dev/null
+++ b/packages/repl/src/lib/workers/workers.d.ts
@@ -0,0 +1,72 @@
+import type { CompileError, CompileOptions, CompileResult, Warning } from 'svelte/compiler';
+import type { File } from '../Workspace.svelte';
+import type { MessageDetails } from '$lib/types';
+
+export type CompilerCommand =
+ | {
+ id: number;
+ type: 'init';
+ svelte_url: string;
+ }
+ | {
+ id: number;
+ type: 'compile';
+ payload: CompilerInput;
+ }
+ | {
+ id: number;
+ type: 'migrate';
+ payload: MigrateInput;
+ };
+
+export interface CompilerInput {
+ source: string;
+ options: CompileOptions;
+ is_entry: boolean;
+ return_ast: boolean;
+ svelte_url?: string;
+}
+
+export interface CompilerOutput {
+ js: string;
+ css: string;
+ ast?: CompileResult['ast'];
+ error?: CompileError;
+ warnings: Warning[];
+ metadata?: {
+ runes: boolean;
+ };
+}
+
+export interface MigrateInput {
+ source: string;
+}
+
+export interface MigrateOutput {
+ result: {
+ code: string;
+ };
+ error?: string;
+}
+
+export interface BundleOptions {
+ svelte_version: string;
+ tailwind?: boolean;
+ runes?: boolean;
+ fragments?: 'html' | 'tree';
+ async?: boolean;
+ aliases?: Record;
+}
+
+export type BundleMessageData = {
+ uid: number;
+ type: 'init' | 'bundle' | 'status' | 'error' | 'version';
+ message: string;
+ svelte_version: string;
+ files: File[];
+ options: BundleOptions;
+};
+
+declare global {
+ var svelte: typeof import('svelte/compiler');
+}
diff --git a/packages/repl/src/routes/+layout.server.ts b/packages/repl/src/routes/+layout.server.ts
new file mode 100644
index 000000000..a3d15781a
--- /dev/null
+++ b/packages/repl/src/routes/+layout.server.ts
@@ -0,0 +1 @@
+export const ssr = false;
diff --git a/packages/repl/src/routes/+page.svelte b/packages/repl/src/routes/+page.svelte
new file mode 100644
index 000000000..a509b9e38
--- /dev/null
+++ b/packages/repl/src/routes/+page.svelte
@@ -0,0 +1,44 @@
+
+
+
+
+
diff --git a/packages/repl/src/routes/v0.svelte b/packages/repl/src/routes/v0.svelte
new file mode 100644
index 000000000..8ef5aa659
--- /dev/null
+++ b/packages/repl/src/routes/v0.svelte
@@ -0,0 +1,34 @@
+
+
+
+
+ {
+ // @ts-expect-error so that v0 can react to REPL errors
+ window.__svelte_repl_onerror?.(error);
+ }} />
+
+
+
diff --git a/packages/repl/svelte.config.js b/packages/repl/svelte.config.js
new file mode 100644
index 000000000..07e6056c2
--- /dev/null
+++ b/packages/repl/svelte.config.js
@@ -0,0 +1,5 @@
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
+
+export default {
+ preprocess: [vitePreprocess()]
+};
diff --git a/packages/svelteplot/scripts/fix-js-imports.js b/packages/svelteplot/scripts/fix-js-imports.js
index 7c7d7f20f..aa7e08768 100644
--- a/packages/svelteplot/scripts/fix-js-imports.js
+++ b/packages/svelteplot/scripts/fix-js-imports.js
@@ -161,7 +161,11 @@ export async function fullySpecifyImportsInDirectory(rootDir = 'dist') {
const replacements = [];
for (const match of matches) {
- const replacement = await resolveRelativeSpecifierOnDisk(match.specifier, filePath, absoluteRoot);
+ const replacement = await resolveRelativeSpecifierOnDisk(
+ match.specifier,
+ filePath,
+ absoluteRoot
+ );
if (replacement && replacement !== match.specifier) {
replacements.push({
start: match.start,
diff --git a/packages/svelteplot/src/Mark.svelte b/packages/svelteplot/src/Mark.svelte
index 43060d2be..cc2acb63f 100644
--- a/packages/svelteplot/src/Mark.svelte
+++ b/packages/svelteplot/src/Mark.svelte
@@ -98,18 +98,21 @@
// let mark2 = $state(mark);
const facetMode = $derived(options.facet || 'auto');
- const optionsWithAutoFacet = $derived({
- ...options,
- __firstFacet: left && top,
- ...(facet &&
+ const globalFacetChannels = $derived(
+ facet &&
facet.data &&
((facetMode === 'auto' && facet.data === data) || facetMode === 'include')
- ? {
- fx: facet.x,
- fy: facet.y
- }
- : {})
- });
+ ? { fx: facet.x, fy: facet.y }
+ : {}
+ );
+
+ // Options merged with global facet channels, without __firstFacet.
+ // Used for data filtering and channel resolution so that global-facet
+ // marks are correctly filtered per panel and have fx/fy values resolved.
+ const optionsWithFacet = $derived({ ...options, ...globalFacetChannels });
+
+ // Same but includes __firstFacet for mark registration / getEmptyFacets.
+ const optionsWithAutoFacet = $derived({ ...optionsWithFacet, __firstFacet: left && top });
let added = false;
@@ -145,7 +148,7 @@
data
.map((d, i) => ({ ...d, [INDEX]: i }))
.flatMap((row, index) => {
- const channels = options as Record>;
+ const channels = optionsWithFacet as Record>;
if (!testFacet(row, channels) || !testFilter(row, channels)) return [];
const out: ResolvedDataRecord = {
datum: row,
@@ -156,10 +159,10 @@
ScaleName
][]) {
// check if the mark has defined an accessor for this channel
- if ((options as any)?.[channel] !== undefined && out[channel] === undefined) {
+ if ((optionsWithFacet as any)?.[channel] !== undefined && out[channel] === undefined) {
// resolve value
- out[channel] = resolveChannel(channel, row, options as any);
- if ((options as any)[channel] === INDEX) {
+ out[channel] = resolveChannel(channel, row, optionsWithFacet as any);
+ if ((optionsWithFacet as any)[channel] === INDEX) {
const scale = plot.scales[CHANNEL_SCALE[channel]];
if (scale.type === 'band' || scale.type === 'point') {
out[channel] = scale.domain[out[channel] % scale.domain.length];
@@ -280,7 +283,7 @@
][]) {
// check if the mark has defined an accessor for this channel
if (
- (options as any)?.[channel] != null &&
+ (optionsWithFacet as any)?.[channel] != null &&
(out as any)[channel] === undefined
) {
// resolve value
diff --git a/packages/svelteplot/src/Plot.svelte b/packages/svelteplot/src/Plot.svelte
index 08091f4cd..0635feb41 100644
--- a/packages/svelteplot/src/Plot.svelte
+++ b/packages/svelteplot/src/Plot.svelte
@@ -3,7 +3,7 @@
The Plot component is the container for plots. It collects the marks with
their data and channels and computes the shared scales.
- The Plot component is split into two parts. This is the outer Plot which
+ The Plot component is split into two parts. This is the outer Plot which
provides convenient defaults and automatically adds axes etc to the graphics.
The downside is that it adds a bunch of imports that you may not be using.
To help with this you can use the core/Plot component directly for a more
@@ -97,13 +97,14 @@
{#if userFooter}{@render userFooter?.()}{/if}
{/snippet}
-
= {
};
export const CSS_VAR = /^var\(--([a-z-0-9,\s]+)\)$/;
-export const CSS_COLOR = /^color\(/;
+export const CSS_COLOR = /^(?:color|light-dark)\(/;
export const CSS_COLOR_MIX = /^color-mix\(/; // just check for prefix
export const CSS_COLOR_CONTRAST = /^color-contrast\(/; // just check for prefix
export const CSS_RGBA = /^rgba\(/; // just check for prefix
diff --git a/packages/svelteplot/src/core/Plot.svelte b/packages/svelteplot/src/core/Plot.svelte
index 20f9cb8e6..7c82e1e69 100644
--- a/packages/svelteplot/src/core/Plot.svelte
+++ b/packages/svelteplot/src/core/Plot.svelte
@@ -5,7 +5,7 @@
you want to reduce the footprint of the plot to the bare minimum.
Keep in mind that you will have to create your own scales if you're using
- this component.
+ this component.
-->
+
+
diff --git a/src/content/tutorial/01-basics/01-getting-started/01-welcome/index.md b/src/content/tutorial/01-basics/01-getting-started/01-welcome/index.md
new file mode 100644
index 000000000..4cee296d2
--- /dev/null
+++ b/src/content/tutorial/01-basics/01-getting-started/01-welcome/index.md
@@ -0,0 +1,13 @@
+---
+title: Welcome to SveltePlot
+---
+
+Welcome to the SveltePlot tutorial! Here you will learn how to use SveltePlot to create beautiful, interactive data visualizations with ease.
+
+You can also look at the [Examples section](/examples) or consult the documentation.
+
+## What is SveltePlot?
+
+SveltePlot is a plotting library for Svelte. It provides a simple and intuitive API for creating a wide variety of charts and visualizations, following the grammar of graphics principles.
+
+This means that rather than relying on templates such as a "scatterplot", in SveltePlot you construct graphics from building blocks like the Dot mark, the same dot mark that you can use for a dot plot.
diff --git a/src/content/tutorial/01-basics/01-getting-started/02-first-plot/+assets/app-a/src/lib/App.svelte b/src/content/tutorial/01-basics/01-getting-started/02-first-plot/+assets/app-a/src/lib/App.svelte
new file mode 100644
index 000000000..64bdc6755
--- /dev/null
+++ b/src/content/tutorial/01-basics/01-getting-started/02-first-plot/+assets/app-a/src/lib/App.svelte
@@ -0,0 +1,5 @@
+
+
+
Loaded {data.length} penguins.
diff --git a/src/content/tutorial/01-basics/01-getting-started/02-first-plot/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/01-basics/01-getting-started/02-first-plot/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..42982149c
--- /dev/null
+++ b/src/content/tutorial/01-basics/01-getting-started/02-first-plot/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/01-getting-started/02-first-plot/index.md b/src/content/tutorial/01-basics/01-getting-started/02-first-plot/index.md
new file mode 100644
index 000000000..c6538ecfa
--- /dev/null
+++ b/src/content/tutorial/01-basics/01-getting-started/02-first-plot/index.md
@@ -0,0 +1,39 @@
+---
+title: Your first plot
+---
+
+Let's dive right in. We have penguin measurement data loaded — 343 birds, each with a bill length and body mass. You can switch to the `penguins.csv` tab to take a look at the raw CSV data. Now let's visualize it.
+
+## Your first plot
+
+The first step is to import the `Plot` and `Dot` componetents from `svelteplot`:
+
+```svelte
+
+```
+
+The [Plot](/features/plot) component is the root componenent for all SveltePlot graphics. The [Dot](/marks/dot) component is one of the many marks you can use to display data (more on marks later).
+
+To put Plot and Dot into work we replace the paragraph with a `` and a `` mark inside it, to create a dot plot:
+
+```svelte
+-
Loaded {data.length} penguins.
++
++
++
+```
+
+`x="body_mass_g"` and `y="species"` tell SveltePlot which columns to map to horizontal and vertical position. Axes and tick labels appear automatically.
+
+This simple dot plot tells us that Gentoo penguins are heavier than Adelie and Chinstrap.
+
+By default, dots show up as outlines (which makes it easier to see overlapping symbols), but you can pass the `fill` property to change that:
+
+```svelte
+
+
+
+```
diff --git a/src/content/tutorial/01-basics/01-getting-started/03-channels/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/01-basics/01-getting-started/03-channels/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..4c5e61d4d
--- /dev/null
+++ b/src/content/tutorial/01-basics/01-getting-started/03-channels/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/01-getting-started/03-channels/index.md b/src/content/tutorial/01-basics/01-getting-started/03-channels/index.md
new file mode 100644
index 000000000..ea0627f7c
--- /dev/null
+++ b/src/content/tutorial/01-basics/01-getting-started/03-channels/index.md
@@ -0,0 +1,38 @@
+---
+title: Channels
+---
+
+The props on a mark — `x`, `y`, `r`, `fill`, `opacity`, and others — are called **channels**. A channel maps a data column (e.g. body mass) to a visual property (e.g. the horizontal position). If we change the `y` channel to `bill_length_mm`, we turn the dot plot into a **scatterplot**!
+
+```svelte
+
+
+
+```
+
+It's still the same `Dot` component, just a different channel assignment! That's the magic of the grammar of graphics. Now use the `fill` channel to color each dot by species:
+
+```svelte
+
+
+
+```
+
+SveltePlot sees that `fill` maps to string values and assigns a categorical color scheme automatically.
+
+To see the meaning of each color we can pass a `color={{ legend: true }}` to the Plot:
+
+```svelte
+
+```
+
+We now see that while the Gentoo penguins are heavier, the Chinstrap penguins also have long bills.
diff --git a/src/content/tutorial/01-basics/01-getting-started/04-marks/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/01-basics/01-getting-started/04-marks/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..db7267b6e
--- /dev/null
+++ b/src/content/tutorial/01-basics/01-getting-started/04-marks/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/01-getting-started/04-marks/index.md b/src/content/tutorial/01-basics/01-getting-started/04-marks/index.md
new file mode 100644
index 000000000..f9bb3700f
--- /dev/null
+++ b/src/content/tutorial/01-basics/01-getting-started/04-marks/index.md
@@ -0,0 +1,44 @@
+---
+title: Marks
+---
+
+`Dot` is a **mark** — a visual component that maps data to geometric shapes. SveltePlot has many marks: `Dot`, `Line`, `BarY`, `RuleY`, and more.
+
+For instance, we can use the [Hull](/marks/delaunay#Hull) mark to add a convex hull around each species. First we import `Hull` alongside `Dot`:
+
+```js
+import { Plot, Dot+++, Hull+++ } from 'svelteplot';
+```
+
+Then we add it and pass the same data and channels as we're passing to `Dot`:
+
+```svelte
+
++
+
+
+```
+
+Not all marks need data. `RuleX` draws a vertical reference line at a fixed x value. Again, just import it:
+
+```js
+import { Plot, Dot, Hull+++, RuleX+++ } from 'svelteplot';
+```
+
+Then add it before the closing `` at the end:
+
+```svelte
++
+
+```
+
+The order in which we put the marks inside the Plot determines how they are drawn, first the hull, then the dots and the rule on top.
diff --git a/src/content/tutorial/01-basics/01-getting-started/05-colors/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/01-basics/01-getting-started/05-colors/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..e2360996f
--- /dev/null
+++ b/src/content/tutorial/01-basics/01-getting-started/05-colors/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/01-getting-started/05-colors/index.md b/src/content/tutorial/01-basics/01-getting-started/05-colors/index.md
new file mode 100644
index 000000000..a2aaaaefa
--- /dev/null
+++ b/src/content/tutorial/01-basics/01-getting-started/05-colors/index.md
@@ -0,0 +1,21 @@
+---
+title: Customizing colors
+---
+
+If we want to customize the colors we can create our own color scheme:
+
+```js
+const scheme = {
+ Adelie: 'hotpink',
+ Chinstrap: 'teal',
+ Gentoo: 'orange'
+};
+```
+
+and pass it to the `color` scale options:
+
+```svelte
+
+```
+
+Since the color scheme is managed by the Plot, we only have to define it once and it is applied everywhere consistently.
diff --git a/src/content/tutorial/01-basics/01-getting-started/06-implicit-marks/+assets/app-a/src/lib/App.svelte b/src/content/tutorial/01-basics/01-getting-started/06-implicit-marks/+assets/app-a/src/lib/App.svelte
new file mode 100644
index 000000000..3ecb665b5
--- /dev/null
+++ b/src/content/tutorial/01-basics/01-getting-started/06-implicit-marks/+assets/app-a/src/lib/App.svelte
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/01-getting-started/06-implicit-marks/+assets/app-a/src/lib/penguins.csv b/src/content/tutorial/01-basics/01-getting-started/06-implicit-marks/+assets/app-a/src/lib/penguins.csv
new file mode 100644
index 000000000..83f32630b
--- /dev/null
+++ b/src/content/tutorial/01-basics/01-getting-started/06-implicit-marks/+assets/app-a/src/lib/penguins.csv
@@ -0,0 +1,345 @@
+species,island,bill_length_mm,bill_depth_mm,flipper_length_mm,body_mass_g,sex
+Adelie,Torgersen,39.1,18.7,181,3750,MALE
+Adelie,Torgersen,39.5,17.4,186,3800,FEMALE
+Adelie,Torgersen,40.3,18,195,3250,FEMALE
+Adelie,Torgersen,NaN,NaN,NaN,NaN,
+Adelie,Torgersen,36.7,19.3,193,3450,FEMALE
+Adelie,Torgersen,39.3,20.6,190,3650,MALE
+Adelie,Torgersen,38.9,17.8,181,3625,FEMALE
+Adelie,Torgersen,39.2,19.6,195,4675,MALE
+Adelie,Torgersen,34.1,18.1,193,3475,
+Adelie,Torgersen,42,20.2,190,4250,
+Adelie,Torgersen,37.8,17.1,186,3300,
+Adelie,Torgersen,37.8,17.3,180,3700,
+Adelie,Torgersen,41.1,17.6,182,3200,FEMALE
+Adelie,Torgersen,38.6,21.2,191,3800,MALE
+Adelie,Torgersen,34.6,21.1,198,4400,MALE
+Adelie,Torgersen,36.6,17.8,185,3700,FEMALE
+Adelie,Torgersen,38.7,19,195,3450,FEMALE
+Adelie,Torgersen,42.5,20.7,197,4500,MALE
+Adelie,Torgersen,34.4,18.4,184,3325,FEMALE
+Adelie,Torgersen,46,21.5,194,4200,MALE
+Adelie,Biscoe,37.8,18.3,174,3400,FEMALE
+Adelie,Biscoe,37.7,18.7,180,3600,MALE
+Adelie,Biscoe,35.9,19.2,189,3800,FEMALE
+Adelie,Biscoe,38.2,18.1,185,3950,MALE
+Adelie,Biscoe,38.8,17.2,180,3800,MALE
+Adelie,Biscoe,35.3,18.9,187,3800,FEMALE
+Adelie,Biscoe,40.6,18.6,183,3550,MALE
+Adelie,Biscoe,40.5,17.9,187,3200,FEMALE
+Adelie,Biscoe,37.9,18.6,172,3150,FEMALE
+Adelie,Biscoe,40.5,18.9,180,3950,MALE
+Adelie,Dream,39.5,16.7,178,3250,FEMALE
+Adelie,Dream,37.2,18.1,178,3900,MALE
+Adelie,Dream,39.5,17.8,188,3300,FEMALE
+Adelie,Dream,40.9,18.9,184,3900,MALE
+Adelie,Dream,36.4,17,195,3325,FEMALE
+Adelie,Dream,39.2,21.1,196,4150,MALE
+Adelie,Dream,38.8,20,190,3950,MALE
+Adelie,Dream,42.2,18.5,180,3550,FEMALE
+Adelie,Dream,37.6,19.3,181,3300,FEMALE
+Adelie,Dream,39.8,19.1,184,4650,MALE
+Adelie,Dream,36.5,18,182,3150,FEMALE
+Adelie,Dream,40.8,18.4,195,3900,MALE
+Adelie,Dream,36,18.5,186,3100,FEMALE
+Adelie,Dream,44.1,19.7,196,4400,MALE
+Adelie,Dream,37,16.9,185,3000,FEMALE
+Adelie,Dream,39.6,18.8,190,4600,MALE
+Adelie,Dream,41.1,19,182,3425,MALE
+Adelie,Dream,37.5,18.9,179,2975,
+Adelie,Dream,36,17.9,190,3450,FEMALE
+Adelie,Dream,42.3,21.2,191,4150,MALE
+Adelie,Biscoe,39.6,17.7,186,3500,FEMALE
+Adelie,Biscoe,40.1,18.9,188,4300,MALE
+Adelie,Biscoe,35,17.9,190,3450,FEMALE
+Adelie,Biscoe,42,19.5,200,4050,MALE
+Adelie,Biscoe,34.5,18.1,187,2900,FEMALE
+Adelie,Biscoe,41.4,18.6,191,3700,MALE
+Adelie,Biscoe,39,17.5,186,3550,FEMALE
+Adelie,Biscoe,40.6,18.8,193,3800,MALE
+Adelie,Biscoe,36.5,16.6,181,2850,FEMALE
+Adelie,Biscoe,37.6,19.1,194,3750,MALE
+Adelie,Biscoe,35.7,16.9,185,3150,FEMALE
+Adelie,Biscoe,41.3,21.1,195,4400,MALE
+Adelie,Biscoe,37.6,17,185,3600,FEMALE
+Adelie,Biscoe,41.1,18.2,192,4050,MALE
+Adelie,Biscoe,36.4,17.1,184,2850,FEMALE
+Adelie,Biscoe,41.6,18,192,3950,MALE
+Adelie,Biscoe,35.5,16.2,195,3350,FEMALE
+Adelie,Biscoe,41.1,19.1,188,4100,MALE
+Adelie,Torgersen,35.9,16.6,190,3050,FEMALE
+Adelie,Torgersen,41.8,19.4,198,4450,MALE
+Adelie,Torgersen,33.5,19,190,3600,FEMALE
+Adelie,Torgersen,39.7,18.4,190,3900,MALE
+Adelie,Torgersen,39.6,17.2,196,3550,FEMALE
+Adelie,Torgersen,45.8,18.9,197,4150,MALE
+Adelie,Torgersen,35.5,17.5,190,3700,FEMALE
+Adelie,Torgersen,42.8,18.5,195,4250,MALE
+Adelie,Torgersen,40.9,16.8,191,3700,FEMALE
+Adelie,Torgersen,37.2,19.4,184,3900,MALE
+Adelie,Torgersen,36.2,16.1,187,3550,FEMALE
+Adelie,Torgersen,42.1,19.1,195,4000,MALE
+Adelie,Torgersen,34.6,17.2,189,3200,FEMALE
+Adelie,Torgersen,42.9,17.6,196,4700,MALE
+Adelie,Torgersen,36.7,18.8,187,3800,FEMALE
+Adelie,Torgersen,35.1,19.4,193,4200,MALE
+Adelie,Dream,37.3,17.8,191,3350,FEMALE
+Adelie,Dream,41.3,20.3,194,3550,MALE
+Adelie,Dream,36.3,19.5,190,3800,MALE
+Adelie,Dream,36.9,18.6,189,3500,FEMALE
+Adelie,Dream,38.3,19.2,189,3950,MALE
+Adelie,Dream,38.9,18.8,190,3600,FEMALE
+Adelie,Dream,35.7,18,202,3550,FEMALE
+Adelie,Dream,41.1,18.1,205,4300,MALE
+Adelie,Dream,34,17.1,185,3400,FEMALE
+Adelie,Dream,39.6,18.1,186,4450,MALE
+Adelie,Dream,36.2,17.3,187,3300,FEMALE
+Adelie,Dream,40.8,18.9,208,4300,MALE
+Adelie,Dream,38.1,18.6,190,3700,FEMALE
+Adelie,Dream,40.3,18.5,196,4350,MALE
+Adelie,Dream,33.1,16.1,178,2900,FEMALE
+Adelie,Dream,43.2,18.5,192,4100,MALE
+Adelie,Biscoe,35,17.9,192,3725,FEMALE
+Adelie,Biscoe,41,20,203,4725,MALE
+Adelie,Biscoe,37.7,16,183,3075,FEMALE
+Adelie,Biscoe,37.8,20,190,4250,MALE
+Adelie,Biscoe,37.9,18.6,193,2925,FEMALE
+Adelie,Biscoe,39.7,18.9,184,3550,MALE
+Adelie,Biscoe,38.6,17.2,199,3750,FEMALE
+Adelie,Biscoe,38.2,20,190,3900,MALE
+Adelie,Biscoe,38.1,17,181,3175,FEMALE
+Adelie,Biscoe,43.2,19,197,4775,MALE
+Adelie,Biscoe,38.1,16.5,198,3825,FEMALE
+Adelie,Biscoe,45.6,20.3,191,4600,MALE
+Adelie,Biscoe,39.7,17.7,193,3200,FEMALE
+Adelie,Biscoe,42.2,19.5,197,4275,MALE
+Adelie,Biscoe,39.6,20.7,191,3900,FEMALE
+Adelie,Biscoe,42.7,18.3,196,4075,MALE
+Adelie,Torgersen,38.6,17,188,2900,FEMALE
+Adelie,Torgersen,37.3,20.5,199,3775,MALE
+Adelie,Torgersen,35.7,17,189,3350,FEMALE
+Adelie,Torgersen,41.1,18.6,189,3325,MALE
+Adelie,Torgersen,36.2,17.2,187,3150,FEMALE
+Adelie,Torgersen,37.7,19.8,198,3500,MALE
+Adelie,Torgersen,40.2,17,176,3450,FEMALE
+Adelie,Torgersen,41.4,18.5,202,3875,MALE
+Adelie,Torgersen,35.2,15.9,186,3050,FEMALE
+Adelie,Torgersen,40.6,19,199,4000,MALE
+Adelie,Torgersen,38.8,17.6,191,3275,FEMALE
+Adelie,Torgersen,41.5,18.3,195,4300,MALE
+Adelie,Torgersen,39,17.1,191,3050,FEMALE
+Adelie,Torgersen,44.1,18,210,4000,MALE
+Adelie,Torgersen,38.5,17.9,190,3325,FEMALE
+Adelie,Torgersen,43.1,19.2,197,3500,MALE
+Adelie,Dream,36.8,18.5,193,3500,FEMALE
+Adelie,Dream,37.5,18.5,199,4475,MALE
+Adelie,Dream,38.1,17.6,187,3425,FEMALE
+Adelie,Dream,41.1,17.5,190,3900,MALE
+Adelie,Dream,35.6,17.5,191,3175,FEMALE
+Adelie,Dream,40.2,20.1,200,3975,MALE
+Adelie,Dream,37,16.5,185,3400,FEMALE
+Adelie,Dream,39.7,17.9,193,4250,MALE
+Adelie,Dream,40.2,17.1,193,3400,FEMALE
+Adelie,Dream,40.6,17.2,187,3475,MALE
+Adelie,Dream,32.1,15.5,188,3050,FEMALE
+Adelie,Dream,40.7,17,190,3725,MALE
+Adelie,Dream,37.3,16.8,192,3000,FEMALE
+Adelie,Dream,39,18.7,185,3650,MALE
+Adelie,Dream,39.2,18.6,190,4250,MALE
+Adelie,Dream,36.6,18.4,184,3475,FEMALE
+Adelie,Dream,36,17.8,195,3450,FEMALE
+Adelie,Dream,37.8,18.1,193,3750,MALE
+Adelie,Dream,36,17.1,187,3700,FEMALE
+Adelie,Dream,41.5,18.5,201,4000,MALE
+Chinstrap,Dream,46.5,17.9,192,3500,FEMALE
+Chinstrap,Dream,50,19.5,196,3900,MALE
+Chinstrap,Dream,51.3,19.2,193,3650,MALE
+Chinstrap,Dream,45.4,18.7,188,3525,FEMALE
+Chinstrap,Dream,52.7,19.8,197,3725,MALE
+Chinstrap,Dream,45.2,17.8,198,3950,FEMALE
+Chinstrap,Dream,46.1,18.2,178,3250,FEMALE
+Chinstrap,Dream,51.3,18.2,197,3750,MALE
+Chinstrap,Dream,46,18.9,195,4150,FEMALE
+Chinstrap,Dream,51.3,19.9,198,3700,MALE
+Chinstrap,Dream,46.6,17.8,193,3800,FEMALE
+Chinstrap,Dream,51.7,20.3,194,3775,MALE
+Chinstrap,Dream,47,17.3,185,3700,FEMALE
+Chinstrap,Dream,52,18.1,201,4050,MALE
+Chinstrap,Dream,45.9,17.1,190,3575,FEMALE
+Chinstrap,Dream,50.5,19.6,201,4050,MALE
+Chinstrap,Dream,50.3,20,197,3300,MALE
+Chinstrap,Dream,58,17.8,181,3700,FEMALE
+Chinstrap,Dream,46.4,18.6,190,3450,FEMALE
+Chinstrap,Dream,49.2,18.2,195,4400,MALE
+Chinstrap,Dream,42.4,17.3,181,3600,FEMALE
+Chinstrap,Dream,48.5,17.5,191,3400,MALE
+Chinstrap,Dream,43.2,16.6,187,2900,FEMALE
+Chinstrap,Dream,50.6,19.4,193,3800,MALE
+Chinstrap,Dream,46.7,17.9,195,3300,FEMALE
+Chinstrap,Dream,52,19,197,4150,MALE
+Chinstrap,Dream,50.5,18.4,200,3400,FEMALE
+Chinstrap,Dream,49.5,19,200,3800,MALE
+Chinstrap,Dream,46.4,17.8,191,3700,FEMALE
+Chinstrap,Dream,52.8,20,205,4550,MALE
+Chinstrap,Dream,40.9,16.6,187,3200,FEMALE
+Chinstrap,Dream,54.2,20.8,201,4300,MALE
+Chinstrap,Dream,42.5,16.7,187,3350,FEMALE
+Chinstrap,Dream,51,18.8,203,4100,MALE
+Chinstrap,Dream,49.7,18.6,195,3600,MALE
+Chinstrap,Dream,47.5,16.8,199,3900,FEMALE
+Chinstrap,Dream,47.6,18.3,195,3850,FEMALE
+Chinstrap,Dream,52,20.7,210,4800,MALE
+Chinstrap,Dream,46.9,16.6,192,2700,FEMALE
+Chinstrap,Dream,53.5,19.9,205,4500,MALE
+Chinstrap,Dream,49,19.5,210,3950,MALE
+Chinstrap,Dream,46.2,17.5,187,3650,FEMALE
+Chinstrap,Dream,50.9,19.1,196,3550,MALE
+Chinstrap,Dream,45.5,17,196,3500,FEMALE
+Chinstrap,Dream,50.9,17.9,196,3675,FEMALE
+Chinstrap,Dream,50.8,18.5,201,4450,MALE
+Chinstrap,Dream,50.1,17.9,190,3400,FEMALE
+Chinstrap,Dream,49,19.6,212,4300,MALE
+Chinstrap,Dream,51.5,18.7,187,3250,MALE
+Chinstrap,Dream,49.8,17.3,198,3675,FEMALE
+Chinstrap,Dream,48.1,16.4,199,3325,FEMALE
+Chinstrap,Dream,51.4,19,201,3950,MALE
+Chinstrap,Dream,45.7,17.3,193,3600,FEMALE
+Chinstrap,Dream,50.7,19.7,203,4050,MALE
+Chinstrap,Dream,42.5,17.3,187,3350,FEMALE
+Chinstrap,Dream,52.2,18.8,197,3450,MALE
+Chinstrap,Dream,45.2,16.6,191,3250,FEMALE
+Chinstrap,Dream,49.3,19.9,203,4050,MALE
+Chinstrap,Dream,50.2,18.8,202,3800,MALE
+Chinstrap,Dream,45.6,19.4,194,3525,FEMALE
+Chinstrap,Dream,51.9,19.5,206,3950,MALE
+Chinstrap,Dream,46.8,16.5,189,3650,FEMALE
+Chinstrap,Dream,45.7,17,195,3650,FEMALE
+Chinstrap,Dream,55.8,19.8,207,4000,MALE
+Chinstrap,Dream,43.5,18.1,202,3400,FEMALE
+Chinstrap,Dream,49.6,18.2,193,3775,MALE
+Chinstrap,Dream,50.8,19,210,4100,MALE
+Chinstrap,Dream,50.2,18.7,198,3775,FEMALE
+Gentoo,Biscoe,46.1,13.2,211,4500,FEMALE
+Gentoo,Biscoe,50,16.3,230,5700,MALE
+Gentoo,Biscoe,48.7,14.1,210,4450,FEMALE
+Gentoo,Biscoe,50,15.2,218,5700,MALE
+Gentoo,Biscoe,47.6,14.5,215,5400,MALE
+Gentoo,Biscoe,46.5,13.5,210,4550,FEMALE
+Gentoo,Biscoe,45.4,14.6,211,4800,FEMALE
+Gentoo,Biscoe,46.7,15.3,219,5200,MALE
+Gentoo,Biscoe,43.3,13.4,209,4400,FEMALE
+Gentoo,Biscoe,46.8,15.4,215,5150,MALE
+Gentoo,Biscoe,40.9,13.7,214,4650,FEMALE
+Gentoo,Biscoe,49,16.1,216,5550,MALE
+Gentoo,Biscoe,45.5,13.7,214,4650,FEMALE
+Gentoo,Biscoe,48.4,14.6,213,5850,MALE
+Gentoo,Biscoe,45.8,14.6,210,4200,FEMALE
+Gentoo,Biscoe,49.3,15.7,217,5850,MALE
+Gentoo,Biscoe,42,13.5,210,4150,FEMALE
+Gentoo,Biscoe,49.2,15.2,221,6300,MALE
+Gentoo,Biscoe,46.2,14.5,209,4800,FEMALE
+Gentoo,Biscoe,48.7,15.1,222,5350,MALE
+Gentoo,Biscoe,50.2,14.3,218,5700,MALE
+Gentoo,Biscoe,45.1,14.5,215,5000,FEMALE
+Gentoo,Biscoe,46.5,14.5,213,4400,FEMALE
+Gentoo,Biscoe,46.3,15.8,215,5050,MALE
+Gentoo,Biscoe,42.9,13.1,215,5000,FEMALE
+Gentoo,Biscoe,46.1,15.1,215,5100,MALE
+Gentoo,Biscoe,44.5,14.3,216,4100,
+Gentoo,Biscoe,47.8,15,215,5650,MALE
+Gentoo,Biscoe,48.2,14.3,210,4600,FEMALE
+Gentoo,Biscoe,50,15.3,220,5550,MALE
+Gentoo,Biscoe,47.3,15.3,222,5250,MALE
+Gentoo,Biscoe,42.8,14.2,209,4700,FEMALE
+Gentoo,Biscoe,45.1,14.5,207,5050,FEMALE
+Gentoo,Biscoe,59.6,17,230,6050,MALE
+Gentoo,Biscoe,49.1,14.8,220,5150,FEMALE
+Gentoo,Biscoe,48.4,16.3,220,5400,MALE
+Gentoo,Biscoe,42.6,13.7,213,4950,FEMALE
+Gentoo,Biscoe,44.4,17.3,219,5250,MALE
+Gentoo,Biscoe,44,13.6,208,4350,FEMALE
+Gentoo,Biscoe,48.7,15.7,208,5350,MALE
+Gentoo,Biscoe,42.7,13.7,208,3950,FEMALE
+Gentoo,Biscoe,49.6,16,225,5700,MALE
+Gentoo,Biscoe,45.3,13.7,210,4300,FEMALE
+Gentoo,Biscoe,49.6,15,216,4750,MALE
+Gentoo,Biscoe,50.5,15.9,222,5550,MALE
+Gentoo,Biscoe,43.6,13.9,217,4900,FEMALE
+Gentoo,Biscoe,45.5,13.9,210,4200,FEMALE
+Gentoo,Biscoe,50.5,15.9,225,5400,MALE
+Gentoo,Biscoe,44.9,13.3,213,5100,FEMALE
+Gentoo,Biscoe,45.2,15.8,215,5300,MALE
+Gentoo,Biscoe,46.6,14.2,210,4850,FEMALE
+Gentoo,Biscoe,48.5,14.1,220,5300,MALE
+Gentoo,Biscoe,45.1,14.4,210,4400,FEMALE
+Gentoo,Biscoe,50.1,15,225,5000,MALE
+Gentoo,Biscoe,46.5,14.4,217,4900,FEMALE
+Gentoo,Biscoe,45,15.4,220,5050,MALE
+Gentoo,Biscoe,43.8,13.9,208,4300,FEMALE
+Gentoo,Biscoe,45.5,15,220,5000,MALE
+Gentoo,Biscoe,43.2,14.5,208,4450,FEMALE
+Gentoo,Biscoe,50.4,15.3,224,5550,MALE
+Gentoo,Biscoe,45.3,13.8,208,4200,FEMALE
+Gentoo,Biscoe,46.2,14.9,221,5300,MALE
+Gentoo,Biscoe,45.7,13.9,214,4400,FEMALE
+Gentoo,Biscoe,54.3,15.7,231,5650,MALE
+Gentoo,Biscoe,45.8,14.2,219,4700,FEMALE
+Gentoo,Biscoe,49.8,16.8,230,5700,MALE
+Gentoo,Biscoe,46.2,14.4,214,4650,
+Gentoo,Biscoe,49.5,16.2,229,5800,MALE
+Gentoo,Biscoe,43.5,14.2,220,4700,FEMALE
+Gentoo,Biscoe,50.7,15,223,5550,MALE
+Gentoo,Biscoe,47.7,15,216,4750,FEMALE
+Gentoo,Biscoe,46.4,15.6,221,5000,MALE
+Gentoo,Biscoe,48.2,15.6,221,5100,MALE
+Gentoo,Biscoe,46.5,14.8,217,5200,FEMALE
+Gentoo,Biscoe,46.4,15,216,4700,FEMALE
+Gentoo,Biscoe,48.6,16,230,5800,MALE
+Gentoo,Biscoe,47.5,14.2,209,4600,FEMALE
+Gentoo,Biscoe,51.1,16.3,220,6000,MALE
+Gentoo,Biscoe,45.2,13.8,215,4750,FEMALE
+Gentoo,Biscoe,45.2,16.4,223,5950,MALE
+Gentoo,Biscoe,49.1,14.5,212,4625,FEMALE
+Gentoo,Biscoe,52.5,15.6,221,5450,MALE
+Gentoo,Biscoe,47.4,14.6,212,4725,FEMALE
+Gentoo,Biscoe,50,15.9,224,5350,MALE
+Gentoo,Biscoe,44.9,13.8,212,4750,FEMALE
+Gentoo,Biscoe,50.8,17.3,228,5600,MALE
+Gentoo,Biscoe,43.4,14.4,218,4600,FEMALE
+Gentoo,Biscoe,51.3,14.2,218,5300,MALE
+Gentoo,Biscoe,47.5,14,212,4875,FEMALE
+Gentoo,Biscoe,52.1,17,230,5550,MALE
+Gentoo,Biscoe,47.5,15,218,4950,FEMALE
+Gentoo,Biscoe,52.2,17.1,228,5400,MALE
+Gentoo,Biscoe,45.5,14.5,212,4750,FEMALE
+Gentoo,Biscoe,49.5,16.1,224,5650,MALE
+Gentoo,Biscoe,44.5,14.7,214,4850,FEMALE
+Gentoo,Biscoe,50.8,15.7,226,5200,MALE
+Gentoo,Biscoe,49.4,15.8,216,4925,MALE
+Gentoo,Biscoe,46.9,14.6,222,4875,FEMALE
+Gentoo,Biscoe,48.4,14.4,203,4625,FEMALE
+Gentoo,Biscoe,51.1,16.5,225,5250,MALE
+Gentoo,Biscoe,48.5,15,219,4850,FEMALE
+Gentoo,Biscoe,55.9,17,228,5600,MALE
+Gentoo,Biscoe,47.2,15.5,215,4975,FEMALE
+Gentoo,Biscoe,49.1,15,228,5500,MALE
+Gentoo,Biscoe,47.3,13.8,216,4725,
+Gentoo,Biscoe,46.8,16.1,215,5500,MALE
+Gentoo,Biscoe,41.7,14.7,210,4700,FEMALE
+Gentoo,Biscoe,53.4,15.8,219,5500,MALE
+Gentoo,Biscoe,43.3,14,208,4575,FEMALE
+Gentoo,Biscoe,48.1,15.1,209,5500,MALE
+Gentoo,Biscoe,50.5,15.2,216,5000,FEMALE
+Gentoo,Biscoe,49.8,15.9,229,5950,MALE
+Gentoo,Biscoe,43.5,15.2,213,4650,FEMALE
+Gentoo,Biscoe,51.5,16.3,230,5500,MALE
+Gentoo,Biscoe,46.2,14.1,217,4375,FEMALE
+Gentoo,Biscoe,55.1,16,230,5850,MALE
+Gentoo,Biscoe,44.5,15.7,217,4875,
+Gentoo,Biscoe,48.8,16.2,222,6000,MALE
+Gentoo,Biscoe,47.2,13.7,214,4925,FEMALE
+Gentoo,Biscoe,NaN,NaN,NaN,NaN,
+Gentoo,Biscoe,46.8,14.3,215,4850,FEMALE
+Gentoo,Biscoe,50.4,15.7,222,5750,MALE
+Gentoo,Biscoe,45.2,14.8,212,5200,FEMALE
+Gentoo,Biscoe,49.9,16.1,213,5400,MALE
\ No newline at end of file
diff --git a/src/content/tutorial/01-basics/01-getting-started/06-implicit-marks/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/01-basics/01-getting-started/06-implicit-marks/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..9aa296a82
--- /dev/null
+++ b/src/content/tutorial/01-basics/01-getting-started/06-implicit-marks/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/01-getting-started/06-implicit-marks/index.md b/src/content/tutorial/01-basics/01-getting-started/06-implicit-marks/index.md
new file mode 100644
index 000000000..7afc5cca2
--- /dev/null
+++ b/src/content/tutorial/01-basics/01-getting-started/06-implicit-marks/index.md
@@ -0,0 +1,30 @@
+---
+title: Implicit marks
+---
+
+You already noticed that SveltePlot adds **axes** to your plot automatically — they are _implicit_ marks that appear without you adding them explicitly. You can disable the implicit axes entirely with `axes={false}`.
+
+```svelte
+
+```
+
+If you want to add explict axes, just import the `AxisX` and `AxisY` marks and add them to the plot:
+
+```js
+import { Plot, Dot, +++AxisX, AxisY+++ } from 'svelteplot';
+```
+
+When adding axes explicitly, you can customize their appearance with props:
+
+```svelte
++
++
+
+````
+Two more implicit marks are available as shorthand props on ``: `grid` adds grid lines, and `frame` adds a border around the plot area:
+
+```svelte
+
+```
+
+This is the same as importing `GridX`, `GridY`, and `Frame` and adding them to the plot.
diff --git a/src/content/tutorial/01-basics/01-getting-started/index.md b/src/content/tutorial/01-basics/01-getting-started/index.md
new file mode 100644
index 000000000..f15cbd374
--- /dev/null
+++ b/src/content/tutorial/01-basics/01-getting-started/index.md
@@ -0,0 +1,3 @@
+---
+title: Getting Started
+---
diff --git a/src/content/tutorial/01-basics/02-scales/+assets/src/lib/aapl.csv b/src/content/tutorial/01-basics/02-scales/+assets/src/lib/aapl.csv
new file mode 100644
index 000000000..bcd538da1
--- /dev/null
+++ b/src/content/tutorial/01-basics/02-scales/+assets/src/lib/aapl.csv
@@ -0,0 +1,1261 @@
+Date,Open,High,Low,Close,Adj Close,Volume
+2013-05-13,64.501427,65.414284,64.5,64.96286,50.961628,79237200
+2013-05-14,64.835716,65.028572,63.164288,63.408573,49.742329,111779500
+2013-05-15,62.737144,63,60.337143,61.264286,48.060188,185403400
+2013-05-16,60.462856,62.549999,59.842857,62.082859,48.702328,150801000
+2013-05-17,62.721428,62.869999,61.572857,61.894287,48.554409,106976100
+2013-05-20,61.701427,63.685715,61.442856,63.275715,49.638096,112894600
+2013-05-21,62.592857,63.639999,62.028572,62.808571,49.271629,114005500
+2013-05-22,63.435715,64.050003,62.602856,63.049999,49.461025,110759600
+2013-05-23,62.278572,63.737144,62.255714,63.162857,49.549564,88255300
+2013-05-24,62.978573,63.665714,62.908573,63.592857,49.886887,69041700
+2013-05-28,64.271431,64.444283,62.978573,63.062859,49.471127,96536300
+2013-05-29,62.857143,63.92857,62.771427,63.564285,49.864471,82644100
+2013-05-30,63.664288,64.928574,63.501427,64.511429,50.607475,88379900
+2013-05-31,64.64286,65.300003,64.214287,64.247147,50.400158,96075700
+2013-06-03,64.389999,64.622856,63.21143,64.388573,50.511097,93088100
+2013-06-04,64.745712,64.918571,63.912857,64.187141,50.353092,73182200
+2013-06-05,63.664288,64.388573,63.387142,63.587143,49.882401,72647400
+2013-06-06,63.638573,63.857143,62.007141,62.637142,49.137157,104233500
+2013-06-07,62.357143,63.32,61.824287,63.115715,49.512577,101133900
+2013-06-10,63.532856,64.154289,62.400002,62.69857,49.185345,112538300
+2013-06-11,62.248573,63.251427,61.902859,62.514286,49.040783,71528100
+2013-06-12,62.785713,63.035713,61.642857,61.741428,48.434486,66306800
+2013-06-13,61.785713,62.44857,61.25,62.279999,48.856991,71458100
+2013-06-14,62.200001,62.327145,61.214287,61.435715,48.194675,67966500
+2013-06-17,61.634285,62.242859,61.48,61.714287,48.4132,64853600
+2013-06-18,61.651428,62.128571,61.458572,61.681427,48.387424,48756400
+2013-06-19,61.628571,61.665714,60.42857,60.42857,47.404587,77735000
+2013-06-20,59.900002,60.854286,59.310001,59.548573,46.714264,89327700
+2013-06-21,59.784286,60,58.299999,59.07143,46.339951,120279600
+2013-06-24,58.200001,58.380001,56.864285,57.505714,45.111698,120186500
+2013-06-25,57.957142,58.255714,56.975716,57.51857,45.121765,78540700
+2013-06-26,57.700001,57.827145,56.522858,56.867142,44.610741,91931000
+2013-06-27,57.035713,57.341427,56.220001,56.254284,44.129971,84311500
+2013-06-28,55.908573,57.181427,55.552856,56.647144,44.438164,144629100
+2013-07-01,57.527142,58.895714,57.317142,58.459999,45.860298,97763400
+2013-07-02,58.565716,60.232857,58.495716,59.784286,46.899162,117466300
+2013-07-03,60.122856,60.425713,59.635715,60.114285,47.158039,60232200
+2013-07-05,60.055714,60.470001,59.335712,59.631428,46.779251,68506200
+2013-07-08,60.015713,60.142857,58.664288,59.292858,46.51366,74534600
+2013-07-09,59.085712,60.5,58.625713,60.335712,47.331741,88146100
+2013-07-10,59.942856,60.685715,59.75,60.104286,47.150196,70351400
+2013-07-11,60.421429,61.17857,60.167141,61.041428,47.885357,81573100
+2013-07-12,61.092857,61.398571,60.487144,60.93,47.797943,69890800
+2013-07-15,60.715714,61.637142,60.685715,61.062859,47.902172,60479300
+2013-07-16,60.931427,61.529999,60.595715,61.457142,48.211479,54134500
+2013-07-17,61.385715,61.745716,61.174286,61.472858,48.223812,49747600
+2013-07-18,61.91143,62.124287,61.515713,61.68,48.386299,54719700
+2013-07-19,61.871429,61.997143,60.621429,60.707142,47.623127,67180400
+2013-07-22,61.351429,61.392857,60.781429,60.901428,47.775539,51949100
+2013-07-23,60.857143,60.994286,59.815716,59.855713,46.955208,92348900
+2013-07-24,62.704285,63.512856,62.18,62.93,49.366905,147984200
+2013-07-25,62.957142,63.057144,62.258572,62.642857,49.14164,57373400
+2013-07-26,62.185715,63.005714,62.048573,62.998573,49.420692,50038100
+2013-07-29,62.971428,64.284286,62.885715,63.970001,50.182755,62014400
+2013-07-30,64.279999,65.307144,64.175713,64.760002,50.802479,77355600
+2013-07-31,64.998573,65.334282,64.204285,64.647141,50.713947,80739400
+2013-08-01,65.10714,65.257141,64.751427,65.239998,51.17902,51562700
+2013-08-02,65.43,66.121429,65.237144,66.077141,51.835732,68695900
+2013-08-05,66.384285,67.238571,66.021431,67.064285,52.610134,79713900
+2013-08-06,66.860001,67.412857,66.024284,66.464287,52.139446,83714400
+2013-08-07,66.257141,66.714287,65.96714,66.425713,52.109192,74714500
+2013-08-08,66.265717,66.300003,65.421425,65.858574,54.150658,63944300
+2013-08-09,65.519997,65.779999,64.807144,64.921425,53.380112,66716300
+2013-08-12,65.265717,66.949997,65.232857,66.765717,54.896542,91108500
+2013-08-13,67.277145,70.665718,66.864288,69.938568,57.505344,220485300
+2013-08-14,71.125717,72.035713,70.485718,71.214287,58.554276,189093100
+2013-08-15,70.917145,71.771431,69.868568,71.129997,58.484966,122573500
+2013-08-16,71.449997,71.848572,71.265717,71.761429,59.004147,90576500
+2013-08-19,72.048569,73.391426,72,72.534286,59.639614,127629600
+2013-08-20,72.815712,72.938568,71.545715,71.581429,58.856155,89672100
+2013-08-21,71.941429,72.449997,71.599998,71.765717,59.007671,83969900
+2013-08-22,72.139999,72.227142,71.171425,71.851425,59.078148,61051900
+2013-08-23,71.895714,71.907143,71.335716,71.574287,58.850273,55682900
+2013-08-26,71.535713,72.885712,71.5,71.852859,59.079327,82741400
+2013-08-27,71.14286,71.78714,69.471428,69.798569,57.390232,106047200
+2013-08-28,69.428574,70.828575,69.428574,70.128571,57.661564,76902000
+2013-08-29,70.235718,70.928574,70.16143,70.242859,57.755524,59914400
+2013-08-30,70.285713,70.421425,69.5,69.602859,57.229309,68074300
+2013-09-03,70.442856,71.514282,69.621429,69.797142,57.389057,82982200
+2013-09-04,71.365715,71.748573,70.897141,71.241432,58.576588,86258200
+2013-09-05,71.464287,71.525711,70.519997,70.752853,58.174866,59091900
+2013-09-06,71.205711,71.339996,69.992859,71.174286,58.521381,89881400
+2013-09-09,72.14286,72.559998,71.925713,72.309998,59.455212,85171800
+2013-09-10,72.314285,72.492859,69.928574,70.662857,58.100861,185798900
+2013-09-11,66.715714,67.669998,66.401428,66.815712,54.937645,224674100
+2013-09-12,66.928574,67.914284,66.572861,67.527145,55.522602,101012800
+2013-09-13,67.048569,67.404289,66.385712,66.414284,54.607578,74708900
+2013-09-16,65.85714,65.944283,63.888573,64.302856,52.87151,135926700
+2013-09-17,63.994286,65.672859,63.92857,65.045715,53.4823,99845200
+2013-09-18,66.168571,66.621429,65.808571,66.382858,54.581741,114215500
+2013-09-19,67.242859,67.975716,67.035713,67.471428,55.476803,101135300
+2013-09-20,68.285713,68.364288,66.571426,66.772858,54.902412,174825700
+2013-09-23,70.871429,70.987144,68.942856,70.091431,57.631035,190526700
+2013-09-24,70.697144,70.781425,69.688568,69.871429,57.450146,91086100
+2013-09-25,69.885712,69.94857,68.775711,68.790001,56.560963,79239300
+2013-09-26,69.428574,69.794289,69.128571,69.459999,57.111843,59305400
+2013-09-27,69.111427,69.238571,68.674286,68.964287,56.704269,57010100
+2013-09-30,68.178574,68.808571,67.772858,68.10714,55.999493,65039100
+2013-10-01,68.349998,69.877144,68.339996,69.708572,57.316231,88470900
+2013-10-02,69.375717,70.257141,69.10714,69.937141,57.504166,72296000
+2013-10-03,70.072861,70.335716,68.677139,69.058571,56.781784,80688300
+2013-10-04,69.122856,69.228569,68.371429,69.004288,56.737148,64717100
+2013-10-07,69.508568,70.378571,69.335716,69.678574,57.291565,78073100
+2013-10-08,69.991432,70.091431,68.648575,68.705711,56.491661,72729300
+2013-10-09,69.234283,69.684288,68.325714,69.512856,57.155315,75431300
+2013-10-10,70.188568,70.339996,69.577141,69.94857,57.513561,69650700
+2013-10-11,69.57,70.548569,69.308571,70.401428,57.885921,66934700
+2013-10-14,69.975716,71.082855,69.907143,70.862854,58.265316,65474500
+2013-10-15,71.072861,71.714287,70.788574,71.239998,58.575417,80018400
+2013-10-16,71.541428,71.790001,71.318573,71.587143,58.860844,62775300
+2013-10-17,71.425713,72.111427,71.382858,72.071426,59.259037,63398300
+2013-10-18,72.284286,72.751427,72.244286,72.69857,59.774693,72635500
+2013-10-21,73.110001,74.900002,73.074287,74.480003,61.239429,99526700
+2013-10-22,75.201431,75.492859,72.575714,74.267143,61.0644,133515900
+2013-10-23,74.14286,75.095711,74.14286,74.994286,61.662289,78430800
+2013-10-24,75,76.067146,74.635712,75.987144,62.478638,96191200
+2013-10-25,75.902855,76.175713,75.015717,75.137146,61.779747,84448000
+2013-10-28,75.577141,75.85714,74.744286,75.697144,62.240196,137610200
+2013-10-29,76.610001,77.035713,73.505714,73.811432,60.689693,158951800
+2013-10-30,74.230003,75.360001,73.860001,74.985718,61.655243,88540900
+2013-10-31,75,75.355713,74.46714,74.671425,61.396824,68924100
+2013-11-01,74.860001,74.971428,73.691429,74.290001,61.08321,68722500
+2013-11-04,74.442856,75.260002,74.115715,75.25,61.872543,61156900
+2013-11-05,74.940002,75.555717,74.714287,75.064285,61.719852,66303300
+2013-11-06,74.878571,74.980003,74.028572,74.417145,63.779209,55843900
+2013-11-07,74.225716,74.741432,73.197144,73.21286,62.747082,65655100
+2013-11-08,73.511429,74.447144,73.227142,74.365715,63.735111,69829200
+2013-11-11,74.284286,74.524284,73.487144,74.150002,63.550251,56863100
+2013-11-12,73.952858,74.845711,73.85714,74.28714,63.667797,51069200
+2013-11-13,74,74.60714,73.851425,74.375717,63.743702,49305200
+2013-11-14,74.687141,75.611427,74.552856,75.451431,64.665649,70604800
+2013-11-15,75.225716,75.584282,74.927139,74.998573,64.277519,79480100
+2013-11-18,74.998573,75.312859,74.028572,74.089996,63.498821,61236000
+2013-11-19,74.147141,74.76857,73.995712,74.221428,63.611477,52234700
+2013-11-20,74.175713,74.345711,73.475716,73.571426,63.054394,48479200
+2013-11-21,73.942856,74.458572,73.381432,74.44857,63.806152,65506700
+2013-11-22,74.21714,74.594284,74.075714,74.257141,63.642082,55931400
+2013-11-25,74.431427,75.124283,74.428574,74.82,64.124481,57327900
+2013-11-26,74.874283,76.591431,74.85714,76.199997,65.307205,100345700
+2013-11-27,76.615715,78,76.199997,77.994286,66.845009,90862100
+2013-11-29,78.497147,79.761429,78.258568,79.438568,68.082802,79531900
+2013-12-02,79.714287,80.618568,78.688568,78.747147,67.490242,118136200
+2013-12-03,79.757141,80.91143,79.668571,80.902855,69.337791,112742000
+2013-12-04,80.785713,81.312859,80.117142,80.714287,69.17617,94452400
+2013-12-05,81.807144,82.162857,80.915718,81.128571,69.531235,111895000
+2013-12-06,80.827141,80.964287,79.938568,80.002853,68.566452,86088100
+2013-12-09,80.128571,81.368568,80.128571,80.918571,69.35125,80123400
+2013-12-10,80.511429,81.125717,80.171425,80.792854,69.24353,69567400
+2013-12-11,81,81.567146,79.955711,80.194283,68.730499,89929700
+2013-12-12,80.305717,80.762856,80.004288,80.077141,68.630119,65572500
+2013-12-13,80.407143,80.41143,79.095711,79.204285,67.882027,83205500
+2013-12-16,79.288574,80.377144,79.28714,79.64286,68.257927,70648200
+2013-12-17,79.401428,79.919998,79.054283,79.284286,67.950592,57475600
+2013-12-18,78.528572,78.778572,76.971428,78.681427,67.433922,141465800
+2013-12-19,78.5,78.571426,77.675713,77.779999,66.661339,80077200
+2013-12-20,77.918571,78.80143,77.831429,78.431427,67.21965,109103400
+2013-12-23,81.14286,81.531425,80.394287,81.441429,69.799385,125326600
+2013-12-24,81.412857,81.697144,80.861427,81.095711,69.50309,41888700
+2013-12-26,81.157143,81.35714,80.482857,80.557144,69.041496,51002000
+2013-12-27,80.545715,80.629997,79.928574,80.012856,68.574997,56471100
+2013-12-30,79.637146,80.012856,78.902855,79.21714,67.893066,63407400
+2013-12-31,79.167145,80.182854,79.14286,80.145714,68.688881,55771100
+2014-01-02,79.382858,79.575714,78.860001,79.01857,67.722862,58671200
+2014-01-03,78.980003,79.099998,77.204285,77.28286,66.235268,98116900
+2014-01-06,76.778572,78.114288,76.228569,77.704285,66.596466,103152700
+2014-01-07,77.760002,77.994286,76.845711,77.148575,66.120186,79302300
+2014-01-08,76.972855,77.937141,76.955711,77.637146,66.538902,64632400
+2014-01-09,78.114288,78.122856,76.478569,76.645714,65.689217,69787200
+2014-01-10,77.118568,77.257141,75.872856,76.134285,65.25087,76244000
+2014-01-13,75.701431,77.5,75.697144,76.53286,65.592491,94623200
+2014-01-14,76.888573,78.104286,76.808571,78.055717,66.897652,83140400
+2014-01-15,79.074287,80.028572,78.808571,79.622856,68.240753,97909700
+2014-01-16,79.271431,79.550003,78.811432,79.178574,67.859993,57319500
+2014-01-17,78.78286,78.867142,77.128571,77.238571,66.197319,106684900
+2014-01-21,77.284286,78.581429,77.202858,78.438568,67.225777,82131700
+2014-01-22,78.701431,79.612854,78.258568,78.78714,67.524521,94996300
+2014-01-23,78.562859,79.5,77.830002,79.454285,68.096291,100809800
+2014-01-24,79.14286,79.374283,77.821426,78.010002,66.858467,107338700
+2014-01-27,78.581429,79.257141,77.964287,78.64286,67.400864,138719700
+2014-01-28,72.68,73.571426,71.724289,72.35714,62.013683,266380800
+2014-01-29,71.992859,72.48143,71.23143,71.535713,61.309677,125702500
+2014-01-30,71.791428,72.35714,70.957146,71.397141,61.190922,169625400
+2014-01-31,70.739998,71.647141,70.507141,71.514282,61.291309,116199300
+2014-02-03,71.80143,72.53286,71.328575,71.647141,61.405186,100366000
+2014-02-04,72.264282,72.779999,71.822861,72.684288,62.294071,94170300
+2014-02-05,72.365715,73.611427,72.321426,73.227142,62.759319,82086200
+2014-02-06,72.865715,73.35714,72.544289,73.215714,65.476723,64441300
+2014-02-07,74.482857,74.704285,73.91143,74.239998,66.392738,92570100
+2014-02-10,74.094284,75.998573,74,75.57,67.582138,86389800
+2014-02-11,75.80143,76.821426,75.64286,76.565712,68.472603,70564200
+2014-02-12,76.707146,77.080002,76.177139,76.559998,68.467506,77025200
+2014-02-13,76.379997,77.835716,76.314285,77.775711,69.554703,76849500
+2014-02-14,77.495712,77.997147,77.315712,77.71286,69.49852,68231100
+2014-02-18,78,78.741432,77.944283,77.998573,69.754028,65062900
+2014-02-19,77.821426,78.127144,76.335716,76.767143,68.652756,78442000
+2014-02-20,76.141426,76.714287,75.571426,75.878571,67.858101,76464500
+2014-02-21,76.112854,76.367142,74.942856,75.035713,67.10434,69696200
+2014-02-24,74.735718,75.702858,74.631432,75.364288,67.398178,72227400
+2014-02-25,75.625717,75.652855,74.428574,74.580002,66.696777,57988000
+2014-02-26,74.80143,75,73.657143,73.907143,66.095062,69054300
+2014-02-27,73.877144,75.540001,73.721428,75.381432,67.413513,75470500
+2014-02-28,75.582855,76.10714,74.58857,75.177139,67.230804,92992200
+2014-03-03,74.774284,75.807144,74.687141,75.394287,67.424995,59695300
+2014-03-04,75.85714,76.091431,75.395714,75.891426,67.869591,64785000
+2014-03-05,75.845711,76.39286,75.589996,76.05143,68.01268,50015700
+2014-03-06,76.112854,76.348572,75.442856,75.821426,67.806984,46372200
+2014-03-07,75.870003,75.997147,75.150002,75.777145,67.767372,55182400
+2014-03-10,75.480003,76.190002,75.477142,75.845711,67.82872,44646000
+2014-03-11,76.492859,76.96286,76.084282,76.584282,68.48922,69806100
+2014-03-12,76.358574,76.764282,76,76.658569,68.555649,49831600
+2014-03-13,76.777145,77.094284,75.594284,75.807144,67.794228,64435700
+2014-03-14,75.541428,75.841431,74.714287,74.955711,67.032784,59299800
+2014-03-17,75.385712,75.709999,75.121429,75.248573,67.294701,49886200
+2014-03-18,75.128571,75.995712,75.028572,75.914284,67.89003,52411800
+2014-03-19,76.03714,76.605713,75.571426,75.894287,67.872169,56189000
+2014-03-20,75.69857,76.095711,75.335716,75.528572,67.54509,52099600
+2014-03-21,75.989998,76.25,75.190002,76.124283,68.077827,93511600
+2014-03-24,76.917145,77.214287,76.437141,77.027145,68.885262,88925200
+2014-03-25,77.35714,77.964287,77.084282,77.855713,69.626259,70573300
+2014-03-26,78.074287,78.428574,76.980003,77.111427,68.96064,74942000
+2014-03-27,77.145714,77.35714,76.445717,76.779999,68.664246,55507900
+2014-03-28,76.902855,76.991432,76.321426,76.694283,68.587601,50141000
+2014-03-31,77.03286,77.258568,76.561432,76.677139,68.572273,42167300
+2014-04-01,76.822861,77.410004,76.681427,77.378571,69.199539,50190000
+2014-04-02,77.482857,77.639999,77.18,77.507141,69.314529,45105200
+2014-04-03,77.341431,77.5,76.805717,76.970001,68.834145,40586000
+2014-04-04,77.115715,77.14286,75.797142,75.974289,67.943726,68812800
+2014-04-07,75.431427,75.842857,74.555717,74.781425,66.87693,72462600
+2014-04-08,75.027145,75.160004,74.099998,74.777145,66.8731,60972100
+2014-04-09,74.662857,75.784286,74.574287,75.760002,67.75206,51542400
+2014-04-10,75.811432,76.034286,74.738571,74.78286,66.878227,59913000
+2014-04-11,74.14286,74.690002,73.877144,74.230003,66.383789,67929400
+2014-04-14,74.557144,74.594284,73.887146,74.525711,66.648232,51418500
+2014-04-15,74.324287,74.519997,73.047142,73.994286,66.172997,66622500
+2014-04-16,74.007141,74.441429,73.44857,74.144287,66.307129,53691400
+2014-04-17,74.285713,75.394287,74.171425,74.991432,67.064743,71083600
+2014-04-21,75.048569,76.019997,74.851425,75.881432,67.860657,45637200
+2014-04-22,75.472855,75.975716,75.214287,75.957146,67.928375,50640800
+2014-04-23,75.580002,75.875717,74.921425,74.964287,67.040466,98735000
+2014-04-24,81.172859,81.428574,80.104286,81.110001,72.536575,189977900
+2014-04-25,80.647141,81.71286,80.565712,81.705711,73.069313,97568800
+2014-04-28,81.828575,85.10714,81.792854,84.870003,75.899139,167371400
+2014-04-29,84.82,85.139999,84.215714,84.618568,75.674263,84344400
+2014-04-30,84.662857,85.632858,84.257141,84.298569,75.3881,114160200
+2014-05-01,84.571426,84.971428,83.765717,84.497147,75.565689,61012000
+2014-05-02,84.620003,84.885712,84.244286,84.654289,75.706215,47878600
+2014-05-05,84.305717,85.85714,84.285713,85.851425,76.776825,71766800
+2014-05-06,85.971428,86.344284,84.915718,84.915718,75.940018,93641100
+2014-05-07,85.035713,85.327141,83.961426,84.618568,75.674263,70716100
+2014-05-08,84.035713,84.915718,83.771431,83.998573,78.15863,57574300
+2014-05-09,83.505714,83.75,82.904289,83.648575,77.832993,72899400
+2014-05-12,83.927139,84.808571,83.914284,84.690002,78.801994,53302200
+2014-05-13,84.571426,84.934288,84.385712,84.822861,78.925613,39934300
+2014-05-14,84.632858,85.342857,84.534286,84.83857,78.940239,41601000
+2014-05-15,84.957146,85.228569,84.005714,84.117142,78.268967,57711500
+2014-05-16,84.089996,85.361427,83.628571,85.358574,79.424072,69064100
+2014-05-19,85.407143,86.761429,85.332855,86.370003,80.365196,79438800
+2014-05-20,86.358574,86.628571,85.818573,86.387146,80.381149,58709000
+2014-05-21,86.261429,86.671425,86.008568,86.615715,80.593834,49214900
+2014-05-22,86.657143,87.121429,86.300003,86.752853,80.721428,50190000
+2014-05-23,86.75,87.818573,86.638573,87.732857,81.633316,58052400
+2014-05-27,87.982857,89.408569,87.947144,89.375717,83.161942,87216500
+2014-05-28,89.431427,89.975716,89.111427,89.144287,82.946617,78870400
+2014-05-29,89.692856,90.98143,89.681427,90.76857,84.457962,94118500
+2014-05-30,91.139999,92.024284,89.842857,90.428574,84.141617,141005200
+2014-06-02,90.565712,90.690002,88.928574,89.807144,83.563377,92337700
+2014-06-03,89.779999,91.248573,89.75,91.077141,84.745071,73177300
+2014-06-04,91.062859,92.555717,90.872856,92.117142,85.712769,83870500
+2014-06-05,92.314285,92.767143,91.80143,92.478569,86.049088,75951400
+2014-06-06,92.842857,93.03714,92.067146,92.224289,85.812477,87484600
+2014-06-09,92.699997,93.879997,91.75,93.699997,87.185577,75415000
+2014-06-10,94.730003,95.050003,93.57,94.25,87.697365,62777000
+2014-06-11,94.129997,94.760002,93.470001,93.860001,87.334457,45681000
+2014-06-12,94.040001,94.120003,91.900002,92.290001,85.873611,54749000
+2014-06-13,92.199997,92.440002,90.879997,91.279999,84.933846,54525000
+2014-06-16,91.510002,92.75,91.449997,92.199997,85.789871,35561000
+2014-06-17,92.309998,92.699997,91.800003,92.080002,85.678207,29726000
+2014-06-18,92.269997,92.290001,91.349998,92.18,85.771263,33514000
+2014-06-19,92.290001,92.300003,91.339996,91.860001,85.473503,35528000
+2014-06-20,91.849998,92.550003,90.900002,90.910004,84.589569,100898000
+2014-06-23,91.32,91.620003,90.599998,90.830002,84.515114,43694000
+2014-06-24,90.75,91.739998,90.190002,90.279999,84.003365,39036000
+2014-06-25,90.209999,90.699997,89.650002,90.360001,84.077789,36869000
+2014-06-26,90.370003,91.050003,89.800003,90.900002,84.580269,32629000
+2014-06-27,90.82,92,90.769997,91.980003,85.585167,64029000
+2014-06-30,92.099998,93.730003,92.089996,92.93,86.469116,49482300
+2014-07-01,93.519997,94.07,93.129997,93.519997,87.018105,38223000
+2014-07-02,93.870003,94.059998,93.089996,93.480003,86.980873,28465000
+2014-07-03,93.669998,94.099998,93.199997,94.029999,87.49263,22891800
+2014-07-07,94.139999,95.989998,94.099998,95.970001,89.297775,56468000
+2014-07-08,96.269997,96.800003,93.919998,95.349998,88.720863,65222000
+2014-07-09,95.440002,95.949997,94.760002,95.389999,88.758087,36436000
+2014-07-10,93.760002,95.550003,93.519997,95.040001,88.432434,39686000
+2014-07-11,95.360001,95.889999,94.860001,95.220001,88.599915,34018000
+2014-07-14,95.860001,96.889999,95.650002,96.449997,89.744392,42810000
+2014-07-15,96.800003,96.849998,95.029999,95.32,88.692963,45477900
+2014-07-16,96.970001,97.099998,94.739998,94.779999,88.190491,53396300
+2014-07-17,95.029999,95.279999,92.57,93.089996,86.618011,57298000
+2014-07-18,93.620003,94.739998,93.019997,94.43,87.864822,49988000
+2014-07-21,94.989998,95,93.720001,93.940002,87.408913,39079000
+2014-07-22,94.68,94.889999,94.120003,94.720001,88.134674,55197000
+2014-07-23,95.419998,97.879997,95.169998,97.190002,90.432945,92918000
+2014-07-24,97.040001,97.32,96.419998,97.029999,90.284065,45729000
+2014-07-25,96.849998,97.839996,96.639999,97.669998,90.87957,43469000
+2014-07-28,97.82,99.239998,97.550003,99.019997,92.135719,55318000
+2014-07-29,99.330002,99.440002,98.25,98.379997,91.540215,43143000
+2014-07-30,98.440002,98.699997,97.669998,98.150002,91.326195,33010000
+2014-07-31,97.160004,97.449997,95.330002,95.599998,88.953499,56843000
+2014-08-01,94.900002,96.620003,94.809998,96.129997,89.44664,48511000
+2014-08-04,96.370003,96.580002,95.169998,95.589996,88.944183,39958000
+2014-08-05,95.360001,95.68,94.360001,95.120003,88.506859,55933000
+2014-08-06,94.75,95.480003,94.709999,94.959999,88.357979,38558000
+2014-08-07,94.93,95.949997,94.099998,94.480003,88.348633,46711000
+2014-08-08,94.260002,94.82,93.279999,94.739998,88.591759,41865000
+2014-08-11,95.269997,96.080002,94.839996,95.989998,89.760643,36585000
+2014-08-12,96.040001,96.879997,95.610001,95.970001,89.741943,33795000
+2014-08-13,96.150002,97.239998,96.040001,97.239998,90.929512,31916000
+2014-08-14,97.330002,97.57,96.800003,97.5,91.172653,28116000
+2014-08-15,97.900002,98.190002,96.860001,97.980003,91.621513,48951000
+2014-08-18,98.489998,99.370003,97.980003,99.160004,92.724922,47572000
+2014-08-19,99.410004,100.68,99.32,100.529999,94.006012,69399000
+2014-08-20,100.440002,101.089996,99.949997,100.57,94.043434,52699000
+2014-08-21,100.57,100.940002,100.110001,100.580002,94.05278,33478000
+2014-08-22,100.290001,101.470001,100.190002,101.32,94.744743,44184000
+2014-08-25,101.790001,102.169998,101.279999,101.540001,94.95047,40270000
+2014-08-26,101.419998,101.5,100.860001,100.889999,94.342651,33152000
+2014-08-27,101.019997,102.57,100.699997,102.129997,95.502167,52369000
+2014-08-28,101.589996,102.779999,101.559998,102.25,95.614388,68460000
+2014-08-29,102.860001,102.900002,102.199997,102.5,95.848152,44595000
+2014-09-02,103.059998,103.739998,102.720001,103.300003,96.59626,53564000
+2014-09-03,103.099998,103.199997,98.580002,98.940002,92.519188,125421000
+2014-09-04,98.849998,100.089996,97.790001,98.120003,91.752419,85718000
+2014-09-05,98.800003,99.389999,98.309998,98.970001,92.547249,58457000
+2014-09-08,99.300003,99.309998,98.050003,98.360001,91.976845,46356700
+2014-09-09,99.080002,103.080002,96.139999,97.989998,91.630852,189846300
+2014-09-10,98.010002,101.110001,97.760002,101,94.445526,100869600
+2014-09-11,100.410004,101.440002,99.620003,101.43,94.847603,62353100
+2014-09-12,101.209999,102.190002,101.080002,101.660004,95.062691,62626100
+2014-09-15,102.809998,103.050003,101.440002,101.629997,95.034615,61316500
+2014-09-16,99.800003,101.260002,98.889999,100.860001,94.314598,66908100
+2014-09-17,101.269997,101.800003,100.589996,101.580002,94.987869,60926500
+2014-09-18,101.93,102.349998,101.559998,101.790001,95.18425,37299400
+2014-09-19,102.290001,102.349998,100.5,100.959999,94.408112,70902400
+2014-09-22,101.800003,102.139999,100.580002,101.059998,94.501625,52788400
+2014-09-23,100.599998,102.940002,100.540001,102.639999,95.979088,63402200
+2014-09-24,102.160004,102.849998,101.199997,101.75,95.146843,60171800
+2014-09-25,100.510002,100.709999,97.720001,97.870003,91.518661,100092000
+2014-09-26,98.529999,100.75,98.400002,100.75,94.211754,62370500
+2014-09-29,98.650002,100.440002,98.629997,100.110001,93.613274,49766300
+2014-09-30,100.809998,101.540001,100.529999,100.75,94.211754,55264100
+2014-10-01,100.589996,100.690002,98.699997,99.18,92.743637,51491300
+2014-10-02,99.269997,100.220001,98.040001,99.900002,93.416908,47757800
+2014-10-03,99.440002,100.209999,99.040001,99.620003,93.155067,43469600
+2014-10-06,99.949997,100.650002,99.419998,99.620003,93.155067,37051200
+2014-10-07,99.43,100.120003,98.730003,98.75,92.341522,42094200
+2014-10-08,98.760002,101.110001,98.309998,100.800003,94.258507,57404700
+2014-10-09,101.540001,102.379997,100.610001,101.019997,94.464211,77376500
+2014-10-10,100.690002,102.029999,100.300003,100.730003,94.193039,66331600
+2014-10-13,101.330002,101.779999,99.809998,99.809998,93.332733,53583400
+2014-10-14,100.389999,100.519997,98.57,98.75,92.341522,63688600
+2014-10-15,97.970001,99.150002,95.18,97.540001,91.210045,100933600
+2014-10-16,95.550003,97.720001,95.410004,96.260002,90.013115,72154500
+2014-10-17,97.5,99,96.809998,97.669998,91.331619,68179700
+2014-10-20,98.32,99.959999,98.220001,99.760002,93.285973,77517300
+2014-10-21,103.019997,103.019997,101.269997,102.470001,95.820122,94623900
+2014-10-22,102.839996,104.110001,102.599998,102.989998,96.306366,68263100
+2014-10-23,104.080002,105.050003,103.629997,104.830002,98.026962,71074700
+2014-10-24,105.18,105.489998,104.529999,105.220001,98.391663,47053900
+2014-10-27,104.849998,105.480003,104.699997,105.110001,98.28878,34187700
+2014-10-28,105.400002,106.739998,105.349998,106.739998,99.813004,48060900
+2014-10-29,106.650002,107.370003,106.360001,107.339996,100.374069,52687900
+2014-10-30,106.959999,107.349998,105.900002,106.980003,100.037453,40654800
+2014-10-31,108.010002,108.040001,107.209999,108,100.991234,44639300
+2014-11-03,108.220001,110.300003,108.010002,109.400002,102.300392,52282600
+2014-11-04,109.360001,109.489998,107.720001,108.599998,101.552292,41574400
+2014-11-05,109.099998,109.300003,108.129997,108.860001,101.795433,37435900
+2014-11-06,108.599998,108.790001,107.800003,108.699997,102.086563,34968500
+2014-11-07,108.75,109.32,108.550003,109.010002,102.377716,33691500
+2014-11-10,109.019997,109.330002,108.669998,108.830002,102.208649,27195500
+2014-11-11,108.699997,109.75,108.400002,109.699997,103.025711,27442300
+2014-11-12,109.379997,111.43,109.370003,111.25,104.481422,46942400
+2014-11-13,111.800003,113.449997,111.599998,112.82,105.955902,59522900
+2014-11-14,113.150002,114.190002,111.209999,114.18,107.23317,44063600
+2014-11-17,114.269997,117.279999,113.300003,113.989998,107.054726,46746700
+2014-11-18,113.940002,115.690002,113.889999,115.470001,108.444672,44224000
+2014-11-19,115.440002,115.739998,113.800003,114.669998,107.693336,41869200
+2014-11-20,114.910004,116.860001,114.849998,116.309998,109.233559,43395500
+2014-11-21,117.510002,117.57,116.029999,116.470001,109.383842,57179300
+2014-11-24,116.849998,118.769997,116.620003,118.629997,111.412415,47450800
+2014-11-25,119.07,119.75,117.449997,117.599998,110.445091,68840400
+2014-11-26,117.940002,119.099998,117.830002,119,111.759903,40768300
+2014-11-28,119.269997,119.400002,118.050003,118.93,111.69416,24814400
+2014-12-01,118.809998,119.25,111.269997,115.07,108.069008,83814000
+2014-12-02,113.5,115.75,112.75,114.629997,107.655785,59348900
+2014-12-03,115.75,116.349998,115.110001,115.93,108.876686,43063400
+2014-12-04,115.769997,117.199997,115.290001,115.489998,108.463455,42044500
+2014-12-05,115.989998,116.080002,114.639999,115,108.003265,38318900
+2014-12-08,114.099998,114.650002,111.620003,112.400002,105.561447,57664900
+2014-12-09,110.190002,114.300003,109.349998,114.120003,107.176811,60208000
+2014-12-10,114.410004,114.849998,111.540001,111.949997,105.13884,44565300
+2014-12-11,112.260002,113.800003,111.339996,111.620003,104.828918,41401700
+2014-12-12,110.459999,111.870003,109.580002,109.730003,103.053902,56028100
+2014-12-15,110.699997,111.599998,106.349998,108.230003,101.645164,67218100
+2014-12-16,106.370003,110.160004,106.260002,106.75,100.255203,60790700
+2014-12-17,107.120003,109.839996,106.82,109.410004,102.753387,53411800
+2014-12-18,111.870003,112.650002,110.660004,112.650002,105.796242,59006200
+2014-12-19,112.260002,113.239998,111.660004,111.779999,104.979164,88429800
+2014-12-22,112.160004,113.489998,111.970001,112.940002,106.068596,45167500
+2014-12-23,113.230003,113.330002,112.459999,112.540001,105.69294,26028400
+2014-12-24,112.580002,112.709999,112.010002,112.010002,105.19519,14479600
+2014-12-26,112.099998,114.519997,112.010002,113.989998,107.054726,33721000
+2014-12-29,113.790001,114.769997,113.699997,113.910004,106.979584,27598900
+2014-12-30,113.639999,113.919998,112.110001,112.519997,105.674149,29881500
+2014-12-31,112.82,113.129997,110.209999,110.379997,103.664352,41403400
+2015-01-02,111.389999,111.440002,107.349998,109.330002,102.67823,53204600
+2015-01-05,108.290001,108.650002,105.410004,106.25,99.785629,64285500
+2015-01-06,106.540001,107.43,104.629997,106.260002,99.795021,65797100
+2015-01-07,107.199997,108.199997,106.699997,107.75,101.194359,40105900
+2015-01-08,109.230003,112.150002,108.699997,111.889999,105.082497,59364500
+2015-01-09,112.669998,113.25,110.209999,112.010002,105.19519,53699500
+2015-01-12,112.599998,112.629997,108.800003,109.25,102.603104,49650800
+2015-01-13,111.43,112.800003,108.910004,110.220001,103.514084,67091900
+2015-01-14,109.040001,110.489998,108.5,109.800003,103.119652,48337000
+2015-01-15,110,110.059998,106.660004,106.82,100.320961,60014000
+2015-01-16,107.029999,107.580002,105.199997,105.989998,99.541428,78513300
+2015-01-20,107.839996,108.970001,106.5,108.720001,102.105339,49899900
+2015-01-21,108.949997,111.059998,108.269997,109.550003,102.884872,48575900
+2015-01-22,110.260002,112.470001,109.720001,112.400002,105.561447,53796400
+2015-01-23,112.300003,113.75,111.529999,112.980003,106.106171,46464800
+2015-01-26,113.739998,114.360001,112.800003,113.099998,106.218864,55615000
+2015-01-27,112.419998,112.480003,109.029999,109.139999,102.499809,95568700
+2015-01-28,117.629997,118.120003,115.309998,115.309998,108.294418,146477100
+2015-01-29,116.32,119.190002,115.559998,118.900002,111.665985,84436400
+2015-01-30,118.400002,120,116.849998,117.160004,110.03186,83745500
+2015-02-02,118.050003,119.169998,116.080002,118.629997,111.412415,62739100
+2015-02-03,118.5,119.089996,117.610001,118.650002,111.431198,51915700
+2015-02-04,118.5,120.510002,118.309998,119.559998,112.285835,70149700
+2015-02-05,120.019997,120.230003,119.25,119.940002,113.087288,42246200
+2015-02-06,120.019997,120.25,118.449997,118.93,112.134979,43706600
+2015-02-09,118.550003,119.839996,118.43,119.720001,112.879837,38889800
+2015-02-10,120.169998,122.150002,120.160004,122.019997,115.048439,62008500
+2015-02-11,122.769997,124.919998,122.5,124.879997,117.745018,73561800
+2015-02-12,126.059998,127.480003,125.57,126.459999,119.234741,74474500
+2015-02-13,127.279999,127.279999,125.650002,127.080002,119.819328,54272200
+2015-02-17,127.489998,128.880005,126.919998,127.830002,120.526489,63152400
+2015-02-18,127.629997,128.779999,127.449997,128.720001,121.365623,44891700
+2015-02-19,128.479996,129.029999,128.330002,128.449997,121.111046,37362400
+2015-02-20,128.619995,129.5,128.050003,129.5,122.101074,48948400
+2015-02-23,130.020004,133,129.660004,133,125.401093,70974100
+2015-02-24,132.940002,133.600006,131.169998,132.169998,124.6185,69228100
+2015-02-25,131.559998,131.600006,128.149994,128.789993,121.431618,74711700
+2015-02-26,128.789993,130.869995,126.610001,130.419998,122.968491,91287500
+2015-02-27,130,130.570007,128.240005,128.460007,121.120499,62014800
+2015-03-02,129.25,130.279999,128.300003,129.089996,121.714493,48096700
+2015-03-03,128.960007,129.520004,128.089996,129.360001,121.969078,37816300
+2015-03-04,129.100006,129.559998,128.320007,128.539993,121.195908,31666300
+2015-03-05,128.580002,128.75,125.760002,126.410004,119.187622,56517100
+2015-03-06,128.399994,129.369995,126.260002,126.599998,119.36676,72842100
+2015-03-09,127.959999,129.570007,125.059998,127.139999,119.875908,88528500
+2015-03-10,126.410004,127.220001,123.800003,124.510002,117.396172,68856600
+2015-03-11,124.75,124.769997,122.110001,122.239998,115.255852,68939000
+2015-03-12,122.309998,124.900002,121.629997,124.449997,117.3396,48362700
+2015-03-13,124.400002,125.400002,122.580002,123.589996,116.528725,51827300
+2015-03-16,123.879997,124.949997,122.870003,124.949997,117.811035,35874300
+2015-03-17,125.900002,127.32,125.650002,127.040001,119.781624,51023100
+2015-03-18,127,129.160004,126.370003,128.470001,121.129906,65270900
+2015-03-19,128.75,129.25,127.400002,127.5,120.21534,45809500
+2015-03-20,128.25,128.399994,125.160004,125.900002,118.706749,68695100
+2015-03-23,127.120003,127.849998,126.519997,127.209999,119.941895,37709700
+2015-03-24,127.230003,128.039993,126.559998,126.690002,119.451622,32842300
+2015-03-25,126.540001,126.82,123.379997,123.379997,116.330727,51655200
+2015-03-26,122.760002,124.879997,122.599998,124.239998,117.141594,47572900
+2015-03-27,124.57,124.699997,122.910004,123.25,116.20816,39546200
+2015-03-30,124.050003,126.400002,124,126.370003,119.14991,47099700
+2015-03-31,126.089996,126.489998,124.360001,124.43,117.32074,42090600
+2015-04-01,124.82,125.120003,123.099998,124.25,117.151024,40621400
+2015-04-02,125.029999,125.559998,124.190002,125.32,118.159889,32220100
+2015-04-06,124.470001,127.510002,124.330002,127.349998,120.073914,37194000
+2015-04-07,127.639999,128.119995,125.980003,126.010002,118.810463,35012300
+2015-04-08,125.849998,126.400002,124.970001,125.599998,118.423904,37329200
+2015-04-09,125.849998,126.580002,124.660004,126.559998,119.329041,32484000
+2015-04-10,125.949997,127.209999,125.260002,127.099998,119.838188,40188000
+2015-04-13,128.369995,128.570007,126.610001,126.849998,119.602478,36365100
+2015-04-14,127,127.290001,125.910004,126.300003,119.083893,25524600
+2015-04-15,126.410004,127.129997,126.010002,126.779999,119.536476,28970400
+2015-04-16,126.279999,127.099998,126.110001,126.169998,118.961311,28369000
+2015-04-17,125.550003,126.139999,124.459999,124.75,117.622444,51957000
+2015-04-20,125.57,128.119995,125.169998,127.599998,120.309624,47054300
+2015-04-21,128.100006,128.199997,126.669998,126.910004,119.65905,32435100
+2015-04-22,126.989998,128.869995,126.32,128.619995,121.271347,37654500
+2015-04-23,128.300003,130.419998,128.139999,129.669998,122.261353,45770900
+2015-04-24,130.490005,130.630005,129.229996,130.279999,122.836502,44525900
+2015-04-27,132.309998,133.130005,131.149994,132.649994,125.071068,96954200
+2015-04-28,134.460007,134.539993,129.570007,130.559998,123.100502,118924000
+2015-04-29,130.160004,131.589996,128.300003,128.639999,121.290192,63386100
+2015-04-30,128.639999,128.639999,124.580002,125.150002,117.999596,83195400
+2015-05-01,126.099998,130.130005,125.300003,128.949997,121.582474,58512600
+2015-05-04,129.5,130.570007,128.259995,128.699997,121.346771,50988300
+2015-05-05,128.149994,128.449997,125.779999,125.800003,118.612465,49271400
+2015-05-06,126.559998,126.75,123.360001,125.010002,117.867599,72141000
+2015-05-07,124.769997,126.080002,124.019997,125.260002,118.596642,43940900
+2015-05-08,126.68,127.620003,126.110001,127.620003,120.831108,55550400
+2015-05-11,127.389999,127.559998,125.629997,126.32,119.600243,42035800
+2015-05-12,125.599998,126.879997,124.82,125.870003,119.174187,48160000
+2015-05-13,126.150002,127.190002,125.870003,126.010002,119.306732,34694200
+2015-05-14,127.410004,128.949997,127.160004,128.949997,122.09034,45203500
+2015-05-15,129.070007,129.490005,128.210007,128.770004,121.919922,38208000
+2015-05-18,128.380005,130.720001,128.360001,130.190002,123.264397,50882900
+2015-05-19,130.690002,130.880005,129.639999,130.070007,123.150787,44633200
+2015-05-20,130,130.979996,129.339996,130.059998,123.141296,36454900
+2015-05-21,130.070007,131.630005,129.830002,131.389999,124.400558,39730400
+2015-05-22,131.600006,132.970001,131.399994,132.539993,125.489357,45596000
+2015-05-26,132.600006,132.910004,129.119995,129.619995,122.724678,70697600
+2015-05-27,130.339996,132.259995,130.050003,132.039993,125.015953,45833200
+2015-05-28,131.860001,131.949997,131.100006,131.779999,124.769791,30733300
+2015-05-29,131.229996,131.449997,129.899994,130.279999,123.349586,50884500
+2015-06-01,130.279999,131.389999,130.050003,130.539993,123.595764,32112800
+2015-06-02,129.860001,130.660004,129.320007,129.960007,123.046638,33667600
+2015-06-03,130.660004,130.940002,129.899994,130.119995,123.198097,30889400
+2015-06-04,129.580002,130.580002,128.910004,129.360001,122.478546,38450100
+2015-06-05,129.5,129.690002,128.360001,128.649994,121.806305,35626800
+2015-06-08,128.899994,129.210007,126.830002,127.800003,121.001518,52674800
+2015-06-09,126.699997,128.080002,125.620003,127.419998,120.641731,56075400
+2015-06-10,127.919998,129.339996,127.849998,128.880005,122.024086,39087300
+2015-06-11,129.179993,130.179993,128.479996,128.589996,121.749489,35390900
+2015-06-12,128.190002,128.330002,127.110001,127.169998,120.405037,36886200
+2015-06-15,126.099998,127.239998,125.709999,126.919998,120.168327,43988900
+2015-06-16,127.029999,127.849998,126.370003,127.599998,120.812149,31494100
+2015-06-17,127.720001,127.879997,126.739998,127.300003,120.528114,32918100
+2015-06-18,127.230003,128.309998,127.220001,127.879997,121.077255,35407200
+2015-06-19,127.709999,127.82,126.400002,126.599998,119.865356,54716900
+2015-06-22,127.489998,128.059998,127.080002,127.610001,120.821625,34039300
+2015-06-23,127.480003,127.610001,126.879997,127.029999,120.272484,30268900
+2015-06-24,127.209999,129.800003,127.120003,128.110001,121.295044,55280900
+2015-06-25,128.860001,129.199997,127.5,127.5,120.717476,31938100
+2015-06-26,127.669998,127.989998,126.510002,126.75,120.007362,44066800
+2015-06-29,125.459999,126.470001,124.480003,124.529999,117.905479,49161400
+2015-06-30,125.57,126.120003,124.860001,125.43,118.757591,44370700
+2015-07-01,126.900002,126.940002,125.989998,126.599998,119.865356,30238800
+2015-07-02,126.43,126.690002,125.769997,126.440002,119.713867,27211000
+2015-07-06,124.940002,126.230003,124.849998,126,119.297279,28060400
+2015-07-07,125.889999,126.150002,123.769997,125.690002,119.003761,46946800
+2015-07-08,124.480003,124.639999,122.540001,122.57,116.049744,60761600
+2015-07-09,123.849998,124.059998,119.220001,120.07,113.682739,77821600
+2015-07-10,121.940002,123.849998,121.209999,123.279999,116.721977,61354500
+2015-07-13,125.029999,125.760002,124.32,125.660004,118.975372,41440500
+2015-07-14,126.040001,126.370003,125.040001,125.610001,118.928009,31768100
+2015-07-15,125.720001,127.150002,125.580002,126.82,120.073647,33649200
+2015-07-16,127.739998,128.570007,127.349998,128.509995,121.673759,36222400
+2015-07-17,129.080002,129.619995,128.309998,129.619995,122.724678,46164700
+2015-07-20,130.970001,132.970001,130.699997,132.070007,125.044388,58900200
+2015-07-21,132.850006,132.919998,130.320007,130.75,123.794601,76756400
+2015-07-22,121.989998,125.5,121.989998,125.220001,118.558777,115450600
+2015-07-23,126.199997,127.089996,125.059998,125.160004,118.501961,50999500
+2015-07-24,125.32,125.739998,123.900002,124.5,117.877068,42162300
+2015-07-27,123.089996,123.610001,122.120003,122.769997,116.239098,44455500
+2015-07-28,123.379997,123.910004,122.550003,123.379997,116.816643,33618100
+2015-07-29,123.150002,123.5,122.269997,122.989998,116.447395,37011700
+2015-07-30,122.32,122.57,121.709999,122.370003,115.860397,33628300
+2015-07-31,122.599998,122.639999,120.910004,121.300003,114.847298,42885000
+2015-08-03,121.5,122.57,117.519997,118.440002,112.139442,69976000
+2015-08-04,117.419998,117.699997,113.25,114.639999,108.541588,124138600
+2015-08-05,112.949997,117.440002,112.099998,115.400002,109.261162,99312600
+2015-08-06,115.970001,116.5,114.120003,115.129997,109.498924,52903000
+2015-08-07,114.580002,116.25,114.5,115.519997,109.869843,38670400
+2015-08-10,116.529999,119.989998,116.529999,119.720001,113.864433,54951600
+2015-08-11,117.809998,118.18,113.330002,113.489998,107.93914,97082800
+2015-08-12,112.529999,115.419998,109.629997,115.239998,109.603546,101217500
+2015-08-13,116.040001,116.400002,114.540001,115.150002,109.517944,48535800
+2015-08-14,114.32,116.309998,114.010002,115.959999,110.288322,42929500
+2015-08-17,116.040001,117.650002,115.5,117.160004,111.429642,40884700
+2015-08-18,116.43,117.440002,116.010002,116.5,110.801933,34560700
+2015-08-19,116.099998,116.519997,114.68,115.010002,109.384789,47445700
+2015-08-20,114.080002,114.349998,111.629997,112.650002,107.140221,68501600
+2015-08-21,110.43,111.900002,105.650002,105.760002,100.587219,128275500
+2015-08-24,94.870003,108.800003,92,103.120003,98.076347,162206300
+2015-08-25,111.110001,111.110001,103.5,103.739998,98.666023,103601600
+2015-08-26,107.089996,109.889999,105.050003,109.690002,104.325012,96774600
+2015-08-27,112.230003,113.239998,110.019997,112.919998,107.397011,84616100
+2015-08-28,112.169998,113.309998,111.540001,113.290001,107.748924,53164400
+2015-08-31,112.029999,114.529999,112,112.760002,107.24485,56229300
+2015-09-01,110.150002,111.879997,107.360001,107.720001,102.451363,76845900
+2015-09-02,110.230003,112.339996,109.129997,112.339996,106.84539,61888800
+2015-09-03,112.489998,112.779999,110.040001,110.370003,104.971733,53233900
+2015-09-04,108.970001,110.449997,108.510002,109.269997,103.925537,49996300
+2015-09-08,111.75,112.559998,110.32,112.309998,106.816864,54843600
+2015-09-09,113.760002,114.019997,109.769997,110.150002,104.762497,85010800
+2015-09-10,110.269997,113.279999,109.900002,112.57,107.064133,62892800
+2015-09-11,111.790001,114.209999,111.760002,114.209999,108.623924,49915500
+2015-09-14,116.580002,116.889999,114.860001,115.309998,109.67012,58363400
+2015-09-15,115.93,116.529999,114.419998,116.279999,110.59269,43341200
+2015-09-16,116.25,116.540001,115.440002,116.410004,110.716324,37173500
+2015-09-17,115.660004,116.489998,113.720001,113.919998,108.348106,64112600
+2015-09-18,112.209999,114.300003,111.870003,113.449997,107.9011,74285300
+2015-09-21,113.669998,115.370003,113.660004,115.209999,109.575012,50222000
+2015-09-22,113.379997,114.18,112.519997,113.400002,107.853531,50346200
+2015-09-23,113.629997,114.720001,113.300003,114.32,108.728546,35756700
+2015-09-24,113.25,115.5,112.370003,115,109.375282,50219500
+2015-09-25,116.440002,116.690002,114.019997,114.709999,109.099472,56151900
+2015-09-28,113.849998,114.57,112.440002,112.440002,106.940506,52109000
+2015-09-29,112.830002,113.510002,107.860001,109.059998,103.7258,73365400
+2015-09-30,110.169998,111.540001,108.730003,110.300003,104.905174,66473000
+2015-10-01,109.07,109.620003,107.309998,109.580002,104.220398,63929100
+2015-10-02,108.010002,111.010002,107.550003,110.379997,104.981232,58019800
+2015-10-05,109.879997,111.370003,109.07,110.779999,105.361679,52064700
+2015-10-06,110.629997,111.739998,109.769997,111.309998,105.865768,48196800
+2015-10-07,111.739998,111.769997,109.410004,110.779999,105.361679,46765600
+2015-10-08,110.190002,110.190002,108.209999,109.5,104.144302,61979600
+2015-10-09,110,112.279999,109.489998,112.120003,106.636162,52766100
+2015-10-12,112.730003,112.75,111.440002,111.599998,106.141563,30467200
+2015-10-13,110.82,112.449997,110.68,111.790001,106.322281,33049300
+2015-10-14,111.290001,111.519997,109.559998,110.209999,104.819572,44462400
+2015-10-15,110.93,112.099998,110.489998,111.860001,106.38887,37673500
+2015-10-16,111.779999,112,110.529999,111.040001,105.608978,39232600
+2015-10-19,110.800003,111.75,110.110001,111.730003,106.265236,29759200
+2015-10-20,111.339996,114.169998,110.82,113.769997,108.205444,48778800
+2015-10-21,114,115.580002,113.699997,113.760002,108.195953,41795200
+2015-10-22,114.330002,115.5,114.099998,115.5,109.85083,41654100
+2015-10-23,116.699997,119.230003,116.330002,119.080002,113.255737,59366900
+2015-10-26,118.080002,118.129997,114.919998,115.279999,109.641594,66333800
+2015-10-27,115.400002,116.540001,113.989998,114.550003,108.947304,69884400
+2015-10-28,116.93,119.300003,116.059998,119.269997,113.436432,85551400
+2015-10-29,118.699997,120.690002,118.269997,120.529999,114.634811,51227300
+2015-10-30,120.989998,121.220001,119.449997,119.5,113.655197,49365300
+2015-11-02,120.800003,121.360001,119.610001,121.18,115.253029,32203300
+2015-11-03,120.790001,123.489998,120.699997,122.57,116.575035,45519000
+2015-11-04,123.129997,123.82,121.620003,122,116.032906,44886100
+2015-11-05,121.849998,122.690002,120.18,120.919998,115.498016,39552700
+2015-11-06,121.110001,121.809998,120.620003,121.059998,115.631737,33042300
+2015-11-09,120.959999,121.809998,120.050003,120.57,115.163704,33871400
+2015-11-10,116.900002,118.07,116.059998,116.769997,111.534096,59127900
+2015-11-11,116.370003,117.419998,115.209999,116.110001,110.903687,45218000
+2015-11-12,116.260002,116.82,115.650002,115.720001,110.531181,32525600
+2015-11-13,115.199997,115.57,112.269997,112.339996,107.30275,45812400
+2015-11-16,111.379997,114.239998,111,114.18,109.060242,38106700
+2015-11-17,114.919998,115.050003,113.32,113.690002,108.592209,27616900
+2015-11-18,115.760002,117.489998,115.5,117.290001,112.030785,46674700
+2015-11-19,117.639999,119.75,116.760002,118.779999,113.453979,43295800
+2015-11-20,119.199997,119.919998,118.849998,119.300003,113.950661,34287100
+2015-11-23,119.269997,119.730003,117.339996,117.75,112.470169,32482500
+2015-11-24,117.330002,119.349998,117.120003,118.879997,113.5495,42803200
+2015-11-25,119.209999,119.230003,117.919998,118.029999,112.737602,21388300
+2015-11-27,118.290001,118.410004,117.599998,117.809998,112.527473,13046400
+2015-11-30,117.989998,119.410004,117.75,118.300003,112.995499,39180300
+2015-12-01,118.75,118.809998,116.860001,117.339996,112.078529,34852400
+2015-12-02,117.339996,118.110001,116.080002,116.279999,111.066078,33386600
+2015-12-03,116.550003,116.790001,114.220001,115.199997,110.0345,41569500
+2015-12-04,115.290001,119.25,115.110001,119.029999,113.692772,57777000
+2015-12-07,118.980003,119.860001,117.809998,118.279999,112.976395,32084200
+2015-12-08,117.519997,118.599998,116.860001,118.230003,112.928642,34309500
+2015-12-09,117.639999,117.690002,115.080002,115.620003,110.435677,46361400
+2015-12-10,116.040001,116.940002,115.510002,116.169998,110.961006,29104200
+2015-12-11,115.190002,115.389999,112.849998,113.18,108.10508,46886200
+2015-12-14,112.18,112.68,109.790001,112.480003,107.436455,64318700
+2015-12-15,111.940002,112.800003,110.349998,110.489998,105.535706,53323100
+2015-12-16,111.07,111.989998,108.800003,111.339996,106.347572,56238500
+2015-12-17,112.019997,112.25,108.980003,108.980003,104.093407,44772800
+2015-12-18,108.910004,109.519997,105.809998,106.029999,101.275681,96453300
+2015-12-21,107.279999,107.370003,105.57,107.330002,102.51738,47590600
+2015-12-22,107.400002,107.720001,106.449997,107.230003,102.421867,32789400
+2015-12-23,107.269997,108.849998,107.199997,108.610001,103.740013,32657400
+2015-12-24,109,109,107.949997,108.029999,103.185997,13570400
+2015-12-28,107.589996,107.690002,106.18,106.82,102.030251,26704200
+2015-12-29,106.959999,109.43,106.860001,108.739998,103.864151,30931200
+2015-12-30,108.580002,108.699997,107.18,107.32,102.507828,25213800
+2015-12-31,107.010002,107.029999,104.82,105.260002,100.540207,40635300
+2016-01-04,102.610001,105.370003,102,105.349998,100.626175,67649400
+2016-01-05,105.75,105.849998,102.410004,102.709999,98.104546,55791000
+2016-01-06,100.559998,102.370003,99.870003,100.699997,96.184654,68457400
+2016-01-07,98.68,100.129997,96.43,96.449997,92.125244,81094400
+2016-01-08,98.550003,99.110001,96.760002,96.959999,92.612358,70798000
+2016-01-11,98.970001,99.059998,97.339996,98.529999,94.111984,49739400
+2016-01-12,100.550003,100.690002,98.839996,99.959999,95.477859,49154200
+2016-01-13,100.32,101.190002,97.300003,97.389999,93.023087,62439600
+2016-01-14,97.959999,100.480003,95.739998,99.519997,95.057579,63170100
+2016-01-15,96.199997,97.709999,95.360001,97.129997,92.77475,79010000
+2016-01-19,98.410004,98.650002,95.5,96.660004,92.325813,53087700
+2016-01-20,95.099998,98.190002,93.419998,96.790001,92.449989,72334400
+2016-01-21,97.059998,97.879997,94.940002,96.300003,91.981964,52161500
+2016-01-22,98.629997,101.459999,98.370003,101.419998,96.872398,65800500
+2016-01-25,101.519997,101.529999,99.209999,99.440002,94.981171,51794500
+2016-01-26,99.93,100.879997,98.07,99.989998,95.506508,75077000
+2016-01-27,96.040001,96.629997,93.339996,93.419998,89.231094,133369700
+2016-01-28,93.790001,94.519997,92.389999,94.089996,89.871048,55678800
+2016-01-29,94.790001,97.339996,94.349998,97.339996,92.975334,64416500
+2016-02-01,96.470001,96.709999,95.400002,96.43,92.10614,40943500
+2016-02-02,95.419998,96.040001,94.279999,94.480003,90.243576,37357200
+2016-02-03,95,96.839996,94.080002,96.349998,92.029724,45964300
+2016-02-04,95.860001,97.330002,95.190002,96.599998,92.76918,46471700
+2016-02-05,96.519997,96.919998,93.690002,94.019997,90.291489,46418100
+2016-02-08,93.129997,95.699997,93.040001,95.010002,91.242241,54021400
+2016-02-09,94.290001,95.940002,93.93,94.989998,91.223038,44331200
+2016-02-10,95.919998,96.349998,94.099998,94.269997,90.531593,42343600
+2016-02-11,93.790001,94.720001,92.589996,93.699997,89.984192,50074700
+2016-02-12,94.190002,94.5,93.010002,93.989998,90.262688,40351400
+2016-02-16,95.019997,96.849998,94.610001,96.639999,92.807602,49057900
+2016-02-17,96.669998,98.209999,96.150002,98.120003,94.228897,44863200
+2016-02-18,98.839996,98.889999,96.089996,96.260002,92.442665,39021000
+2016-02-19,96,96.760002,95.800003,96.040001,92.231384,35374200
+2016-02-22,96.309998,96.900002,95.919998,96.879997,93.038086,34280800
+2016-02-23,96.400002,96.5,94.550003,94.690002,90.934929,31942600
+2016-02-24,93.980003,96.379997,93.32,96.099998,92.289017,36255700
+2016-02-25,96.050003,96.760002,95.25,96.760002,92.922836,27582700
+2016-02-26,97.199997,98.019997,96.580002,96.910004,93.066895,28991100
+2016-02-29,96.860001,98.230003,96.650002,96.690002,92.855621,35216300
+2016-03-01,97.650002,100.769997,97.419998,100.529999,96.543343,50407100
+2016-03-02,100.510002,100.889999,99.639999,100.75,96.754623,33169600
+2016-03-03,100.580002,101.709999,100.449997,101.5,97.474876,36955700
+2016-03-04,102.370003,103.75,101.370003,103.010002,98.924988,46055100
+2016-03-07,102.389999,102.830002,100.959999,101.870003,97.8302,35828900
+2016-03-08,100.779999,101.760002,100.400002,101.029999,97.023506,31561900
+2016-03-09,101.309998,101.580002,100.269997,101.120003,97.109947,27201700
+2016-03-10,101.410004,102.239998,100.150002,101.169998,97.157944,33513600
+2016-03-11,102.239998,102.279999,101.5,102.260002,98.204727,27408200
+2016-03-14,101.910004,102.910004,101.779999,102.519997,98.454414,25076100
+2016-03-15,103.959999,105.18,103.849998,104.580002,100.432732,40067700
+2016-03-16,104.610001,106.309998,104.589996,105.970001,101.767609,38303500
+2016-03-17,105.519997,106.470001,104.959999,105.800003,101.60434,34420700
+2016-03-18,106.339996,106.5,105.190002,105.919998,101.719589,44205200
+2016-03-21,105.93,107.650002,105.139999,105.910004,101.709991,35502700
+2016-03-22,105.25,107.290001,105.209999,106.720001,102.487862,32444400
+2016-03-23,106.480003,107.07,105.900002,106.129997,101.921272,25703500
+2016-03-24,105.470001,106.25,104.889999,105.669998,101.479515,26133000
+2016-03-28,106,106.190002,105.059998,105.190002,101.018539,19411400
+2016-03-29,104.889999,107.790001,104.879997,107.68,103.409798,31190100
+2016-03-30,108.650002,110.419998,108.599998,109.559998,105.215225,45601100
+2016-03-31,109.720001,109.900002,108.879997,108.989998,104.667839,25888400
+2016-04-01,108.779999,110,108.199997,109.989998,105.628189,25874000
+2016-04-04,110.419998,112.190002,110.269997,111.120003,106.713379,37356200
+2016-04-05,109.510002,110.730003,109.419998,109.809998,105.455322,26578700
+2016-04-06,110.230003,110.980003,109.199997,110.959999,106.559723,26404100
+2016-04-07,109.949997,110.419998,108.120003,108.540001,104.235695,31801900
+2016-04-08,108.910004,109.769997,108.169998,108.660004,104.350922,23581700
+2016-04-11,108.970001,110.610001,108.830002,109.019997,104.69664,29407500
+2016-04-12,109.339996,110.5,108.660004,110.440002,106.060333,27232300
+2016-04-13,110.800003,112.339996,110.800003,112.040001,107.596893,33257300
+2016-04-14,111.620003,112.389999,111.330002,112.099998,107.65451,25473900
+2016-04-15,112.110001,112.300003,109.730003,109.849998,105.493736,46939000
+2016-04-18,108.889999,108.949997,106.940002,107.480003,103.217735,60834000
+2016-04-19,107.879997,108,106.230003,106.910004,102.670319,32384900
+2016-04-20,106.639999,108.089996,106.059998,107.129997,102.881599,30611000
+2016-04-21,106.93,106.93,105.519997,105.970001,101.767609,31552500
+2016-04-22,105.010002,106.480003,104.620003,105.68,101.489113,33683100
+2016-04-25,105,105.650002,104.510002,105.080002,100.912903,28031600
+2016-04-26,103.910004,105.300003,103.910004,104.349998,100.211845,56016200
+2016-04-27,96,98.709999,95.68,97.82,93.940796,114602100
+2016-04-28,97.610001,97.879997,94.25,94.830002,91.069382,82242700
+2016-04-29,93.989998,94.720001,92.510002,93.739998,90.022591,68531500
+2016-05-02,93.970001,94.080002,92.400002,93.639999,89.926582,48160100
+2016-05-03,94.199997,95.739998,93.68,95.18,91.405487,56831300
+2016-05-04,95.199997,95.900002,93.82,94.190002,90.454765,41025500
+2016-05-05,94,94.07,92.68,93.239998,90.087601,35890500
+2016-05-06,93.370003,93.449997,91.849998,92.720001,89.58519,43458200
+2016-05-09,93,93.769997,92.589996,92.790001,89.652824,32936400
+2016-05-10,93.330002,93.57,92.110001,93.419998,90.261513,33686800
+2016-05-11,93.480003,93.57,92.459999,92.510002,89.382286,28719100
+2016-05-12,92.720001,92.779999,89.470001,90.339996,87.285637,76314700
+2016-05-13,90,91.669998,90,90.519997,87.459572,44392800
+2016-05-16,92.389999,94.389999,91.650002,93.879997,90.705963,61259800
+2016-05-17,94.550003,94.699997,93.010002,93.489998,90.329147,46916900
+2016-05-18,94.160004,95.209999,93.889999,94.559998,91.362984,42062400
+2016-05-19,94.639999,94.639999,93.57,94.199997,91.015144,30442100
+2016-05-20,94.639999,95.43,94.519997,95.220001,92.000664,32026000
+2016-05-23,95.870003,97.190002,95.669998,96.43,93.169746,38018600
+2016-05-24,97.220001,98.089996,96.839996,97.900002,94.590057,35140200
+2016-05-25,98.669998,99.739998,98.110001,99.620003,96.251907,38168800
+2016-05-26,99.68,100.730003,98.639999,100.410004,97.01519,56331200
+2016-05-27,99.440002,100.470001,99.25,100.349998,96.957214,36229500
+2016-05-31,99.599998,100.400002,98.82,99.860001,96.48378,42307200
+2016-06-01,99.019997,99.540001,98.330002,98.459999,95.131126,29173300
+2016-06-02,97.599998,97.839996,96.629997,97.720001,94.416138,40191600
+2016-06-03,97.790001,98.269997,97.449997,97.919998,94.609375,28062900
+2016-06-06,97.989998,101.889999,97.550003,98.629997,95.295364,23292500
+2016-06-07,99.25,99.870003,98.959999,99.029999,95.681847,22409500
+2016-06-08,99.019997,99.559998,98.68,98.940002,95.594902,20848100
+2016-06-09,98.5,99.989998,98.459999,99.650002,96.280899,26601400
+2016-06-10,98.529999,99.349998,98.480003,98.830002,95.488617,31712900
+2016-06-13,98.690002,99.120003,97.099998,97.339996,94.048996,38020500
+2016-06-14,97.32,98.480003,96.75,97.459999,94.164932,31931900
+2016-06-15,97.82,98.410004,97.029999,97.139999,93.855766,29445200
+2016-06-16,96.449997,97.75,96.07,97.550003,94.251892,31326800
+2016-06-17,96.620003,96.650002,95.300003,95.330002,92.106941,61008200
+2016-06-20,96,96.57,95.029999,95.099998,91.88472,34411900
+2016-06-21,94.940002,96.349998,94.68,95.910004,92.667336,35546400
+2016-06-22,96.25,96.889999,95.349998,95.550003,92.319504,29219100
+2016-06-23,95.940002,96.290001,95.25,96.099998,92.850914,32240200
+2016-06-24,92.910004,94.660004,92.650002,93.400002,90.242188,75311400
+2016-06-27,93,93.050003,91.5,92.040001,88.928177,45489600
+2016-06-28,92.900002,93.660004,92.139999,93.589996,90.425781,40444900
+2016-06-29,93.970001,94.550003,93.629997,94.400002,91.208397,36531000
+2016-06-30,94.440002,95.769997,94.300003,95.599998,92.367805,35836400
+2016-07-01,95.489998,96.470001,95.330002,95.889999,92.64801,26026500
+2016-07-05,95.389999,95.400002,94.459999,94.989998,91.778442,27705200
+2016-07-06,94.599998,95.660004,94.370003,95.529999,92.300171,30949100
+2016-07-07,95.699997,96.5,95.620003,95.940002,92.696312,25139600
+2016-07-08,96.489998,96.889999,96.050003,96.68,93.411301,28912100
+2016-07-11,96.75,97.650002,96.730003,96.980003,93.701157,23794900
+2016-07-12,97.169998,97.699997,97.120003,97.419998,94.126282,24167500
+2016-07-13,97.410004,97.669998,96.839996,96.870003,93.594872,25892200
+2016-07-14,97.389999,98.989998,97.32,98.790001,95.449966,38919000
+2016-07-15,98.919998,99.300003,98.5,98.779999,95.4403,30137000
+2016-07-18,98.699997,100.129997,98.599998,99.830002,96.454803,36493900
+2016-07-19,99.559998,100,99.339996,99.870003,96.493454,23779900
+2016-07-20,100,100.459999,99.739998,99.959999,96.580414,26276000
+2016-07-21,99.830002,101,99.129997,99.43,96.068329,32702000
+2016-07-22,99.260002,99.300003,98.309998,98.660004,95.324364,28313700
+2016-07-25,98.25,98.839996,96.919998,97.339996,94.048996,40382900
+2016-07-26,96.82,97.970001,96.419998,96.669998,93.401634,56239800
+2016-07-27,104.269997,104.349998,102.75,102.949997,99.469307,92344800
+2016-07-28,102.830002,104.449997,102.82,104.339996,100.812317,39869800
+2016-07-29,104.190002,104.550003,103.68,104.209999,100.686714,27733700
+2016-08-01,104.410004,106.150002,104.410004,106.050003,102.464508,38167900
+2016-08-02,106.050003,106.07,104,104.480003,100.947594,33816600
+2016-08-03,104.809998,105.839996,104.769997,105.790001,102.213295,30202600
+2016-08-04,105.580002,106,105.279999,105.870003,102.844727,27408700
+2016-08-05,106.269997,107.650002,106.18,107.480003,104.408714,40553400
+2016-08-08,107.519997,108.370003,107.160004,108.370003,105.273285,28037200
+2016-08-09,108.230003,108.940002,108.010002,108.809998,105.700714,26315200
+2016-08-10,108.709999,108.900002,107.760002,108,104.913849,24008500
+2016-08-11,108.519997,108.93,107.849998,107.93,104.845863,27484500
+2016-08-12,107.779999,108.440002,107.779999,108.18,105.088715,18660400
+2016-08-15,108.139999,109.540001,108.080002,109.480003,106.35157,25868200
+2016-08-16,109.629997,110.230003,109.209999,109.379997,106.254425,33794400
+2016-08-17,109.099998,109.370003,108.339996,109.220001,106.098991,25356000
+2016-08-18,109.230003,109.599998,109.019997,109.080002,105.962997,21984700
+2016-08-19,108.769997,109.690002,108.360001,109.360001,106.234985,25368100
+2016-08-22,108.860001,109.099998,107.849998,108.510002,105.409286,25820200
+2016-08-23,108.589996,109.32,108.529999,108.849998,105.739555,21257700
+2016-08-24,108.57,108.75,107.68,108.029999,104.943001,23675100
+2016-08-25,107.389999,107.879997,106.68,107.57,104.496147,25086200
+2016-08-26,107.410004,107.949997,106.309998,106.940002,103.88414,27766300
+2016-08-29,106.620003,107.440002,106.290001,106.82,103.767578,24970300
+2016-08-30,105.800003,106.5,105.5,106,102.971016,24863900
+2016-08-31,105.660004,106.57,105.639999,106.099998,103.068153,29662400
+2016-09-01,106.139999,106.800003,105.620003,106.730003,103.680153,26701500
+2016-09-02,107.699997,108,106.82,107.730003,104.651573,26802500
+2016-09-06,107.900002,108.300003,107.510002,107.699997,104.622429,26880400
+2016-09-07,107.830002,108.760002,107.07,108.360001,105.263565,42364300
+2016-09-08,107.25,107.269997,105.239998,105.519997,102.504723,53002000
+2016-09-09,104.639999,105.720001,103.129997,103.129997,100.183022,46557000
+2016-09-12,102.650002,105.720001,102.529999,105.440002,102.42701,45292800
+2016-09-13,107.510002,108.790001,107.239998,107.949997,104.865273,62176200
+2016-09-14,108.730003,113.029999,108.599998,111.769997,108.576126,110888700
+2016-09-15,113.860001,115.730003,113.489998,115.57,112.26754,89983600
+2016-09-16,115.120003,116.129997,114.040001,114.919998,111.636124,79886900
+2016-09-19,115.190002,116.18,113.25,113.580002,110.334419,47023000
+2016-09-20,113.050003,114.120003,112.510002,113.57,110.324692,34514300
+2016-09-21,113.849998,113.989998,112.440002,113.550003,110.305275,36003200
+2016-09-22,114.349998,114.940002,114,114.620003,111.344696,31074000
+2016-09-23,114.419998,114.790001,111.550003,112.709999,109.489258,52481200
+2016-09-26,111.639999,113.389999,111.550003,112.879997,109.654411,29869400
+2016-09-27,113,113.18,112.339996,113.089996,109.858406,24607400
+2016-09-28,113.690002,114.639999,113.43,113.949997,110.69384,29641100
+2016-09-29,113.160004,113.800003,111.800003,112.18,108.974403,35887000
+2016-09-30,112.459999,113.370003,111.800003,113.050003,109.81955,36379100
+2016-10-03,112.709999,113.050003,112.279999,112.519997,109.304688,21701800
+2016-10-04,113.059998,114.309998,112.629997,113,109.770973,29736800
+2016-10-05,113.400002,113.660004,112.690002,113.050003,109.81955,21453100
+2016-10-06,113.699997,114.339996,113.129997,113.889999,110.635551,28779300
+2016-10-07,114.309998,114.559998,113.510002,114.059998,110.800682,24358400
+2016-10-10,115.019997,116.75,114.720001,116.050003,112.733818,36236000
+2016-10-11,117.699997,118.690002,116.199997,116.300003,112.976685,64041000
+2016-10-12,117.349998,117.980003,116.75,117.339996,113.986969,37586800
+2016-10-13,116.790001,117.440002,115.720001,116.980003,113.637253,35192400
+2016-10-14,117.879997,118.169998,117.129997,117.629997,114.268661,35652200
+2016-10-17,117.330002,117.839996,116.779999,117.550003,114.190964,23624900
+2016-10-18,118.18,118.209999,117.449997,117.470001,114.113251,24553500
+2016-10-19,117.25,117.760002,113.800003,117.120003,113.773247,20034600
+2016-10-20,116.860001,117.379997,116.330002,117.059998,113.714958,24125800
+2016-10-21,116.809998,116.910004,116.279999,116.599998,113.268097,23192700
+2016-10-24,117.099998,117.739998,117,117.650002,114.288109,23538700
+2016-10-25,117.949997,118.360001,117.309998,118.25,114.870956,48129000
+2016-10-26,114.309998,115.699997,113.309998,115.589996,112.286957,66134200
+2016-10-27,115.389999,115.860001,114.099998,114.480003,111.208687,34562000
+2016-10-28,113.870003,115.209999,113.449997,113.720001,110.470398,37861700
+2016-10-31,113.650002,114.230003,113.199997,113.540001,110.29554,26419400
+2016-11-01,113.459999,113.769997,110.529999,111.489998,108.304131,43825800
+2016-11-02,111.400002,112.349998,111.230003,111.589996,108.401276,28331700
+2016-11-03,110.980003,111.459999,109.550003,109.830002,107.239349,26932600
+2016-11-04,108.529999,110.25,108.110001,108.839996,106.272697,30837000
+2016-11-07,110.080002,110.510002,109.459999,110.410004,107.805664,32560000
+2016-11-08,110.309998,111.720001,109.699997,111.059998,108.440315,24054500
+2016-11-09,109.879997,111.32,108.050003,110.879997,108.264572,59176400
+2016-11-10,111.089996,111.089996,105.830002,107.790001,105.247467,57134500
+2016-11-11,107.120003,108.870003,106.550003,108.43,105.872368,34094100
+2016-11-14,107.709999,107.809998,104.080002,105.709999,103.216522,51175500
+2016-11-15,106.57,107.68,106.160004,107.110001,104.583504,32264500
+2016-11-16,106.699997,110.230003,106.599998,109.989998,107.395561,58840500
+2016-11-17,109.809998,110.349998,108.830002,109.949997,107.356506,27632000
+2016-11-18,109.720001,110.540001,109.660004,110.059998,107.463921,28428900
+2016-11-21,110.120003,111.989998,110.010002,111.730003,109.094528,29264600
+2016-11-22,111.949997,112.419998,111.400002,111.800003,109.162872,25965500
+2016-11-23,111.360001,111.510002,110.330002,111.230003,108.606323,27426400
+2016-11-25,111.129997,111.870003,110.949997,111.790001,109.153107,11475900
+2016-11-28,111.43,112.470001,111.389999,111.57,108.938301,27194000
+2016-11-29,110.779999,112.029999,110.07,111.459999,108.830894,28528800
+2016-11-30,111.599998,112.199997,110.269997,110.519997,107.913071,36162300
+2016-12-01,110.370003,110.940002,109.029999,109.489998,106.907356,37086900
+2016-12-02,109.169998,110.089996,108.849998,109.900002,107.307686,26528000
+2016-12-05,110,110.029999,108.25,109.110001,106.536316,34324500
+2016-12-06,109.5,110.360001,109.190002,109.949997,107.356506,26195500
+2016-12-07,109.260002,111.190002,109.160004,111.029999,108.411034,29998700
+2016-12-08,110.860001,112.43,110.599998,112.120003,109.475334,27068300
+2016-12-09,112.309998,114.699997,112.309998,113.949997,111.262154,34402600
+2016-12-12,113.290001,115,112.489998,113.300003,110.627495,26374400
+2016-12-13,113.839996,115.919998,113.75,115.190002,112.472916,43733800
+2016-12-14,115.040001,116.199997,114.980003,115.190002,112.472916,34031800
+2016-12-15,115.379997,116.730003,115.230003,115.82,113.088043,46524500
+2016-12-16,116.470001,116.5,115.650002,115.970001,113.234512,44351100
+2016-12-19,115.800003,117.379997,115.75,116.639999,113.88871,27779400
+2016-12-20,116.739998,117.5,116.68,116.949997,114.191406,21425000
+2016-12-21,116.800003,117.400002,116.779999,117.059998,114.298805,23783200
+2016-12-22,116.349998,116.510002,115.639999,116.290001,113.546967,26085900
+2016-12-23,115.589996,116.519997,115.589996,116.519997,113.77153,14181200
+2016-12-27,116.519997,117.800003,116.489998,117.260002,114.494087,18296900
+2016-12-28,117.519997,118.019997,116.199997,116.760002,114.005882,20905900
+2016-12-29,116.449997,117.110001,116.400002,116.730003,113.976585,15039500
+2016-12-30,116.650002,117.199997,115.43,115.82,113.088043,30586300
+2017-01-03,115.800003,116.330002,114.760002,116.150002,113.410263,28781900
+2017-01-04,115.849998,116.510002,115.75,116.019997,113.283333,21118100
+2017-01-05,115.919998,116.860001,115.809998,116.610001,113.859421,22193600
+2017-01-06,116.779999,118.160004,116.470001,117.910004,115.128761,31751900
+2017-01-09,117.949997,119.43,117.940002,118.989998,116.183273,33561900
+2017-01-10,118.769997,119.379997,118.300003,119.110001,116.300438,24462100
+2017-01-11,118.739998,119.93,118.599998,119.75,116.925346,27588600
+2017-01-12,118.900002,119.300003,118.209999,119.25,116.437141,27086200
+2017-01-13,119.110001,119.620003,118.809998,119.040001,116.232101,26111900
+2017-01-17,118.339996,120.239998,118.220001,120,117.169449,34439800
+2017-01-18,120,120.5,119.709999,119.989998,117.159683,23713000
+2017-01-19,119.400002,120.089996,119.370003,119.779999,116.954636,25597300
+2017-01-20,120.449997,120.449997,119.730003,120,117.169449,32597900
+2017-01-23,120,120.809998,119.769997,120.080002,117.247566,22050200
+2017-01-24,119.550003,120.099998,119.5,119.970001,117.14016,23211000
+2017-01-25,120.419998,122.099998,120.279999,121.879997,119.005104,32377600
+2017-01-26,121.669998,122.440002,121.599998,121.940002,119.063698,26337600
+2017-01-27,122.139999,122.349998,121.599998,121.949997,119.073456,20562900
+2017-01-30,120.93,121.629997,120.660004,121.629997,118.761009,30377500
+2017-01-31,121.150002,121.389999,120.620003,121.349998,118.487602,49201000
+2017-02-01,127.029999,130.490005,127.010002,128.75,125.713051,111985000
+2017-02-02,127.980003,129.389999,127.779999,128.529999,125.498253,33710400
+2017-02-03,128.309998,129.190002,128.160004,129.080002,126.035263,24507300
+2017-02-06,129.130005,130.5,128.899994,130.289993,127.216721,26845900
+2017-02-07,130.539993,132.089996,130.449997,131.529999,128.427475,38183800
+2017-02-08,131.350006,132.220001,131.220001,132.039993,128.925446,23004100
+2017-02-09,131.649994,132.449997,131.119995,132.419998,129.857056,28349900
+2017-02-10,132.460007,132.940002,132.050003,132.119995,129.562866,20065500
+2017-02-13,133.080002,133.820007,132.75,133.289993,130.71019,23035400
+2017-02-14,133.470001,135.089996,133.25,135.020004,132.406754,33226200
+2017-02-15,135.520004,136.270004,134.619995,135.509995,132.887253,35623100
+2017-02-16,135.669998,135.899994,134.839996,135.350006,132.730377,22584600
+2017-02-17,135.100006,135.830002,135.100006,135.720001,133.093216,22198200
+2017-02-21,136.229996,136.75,135.979996,136.699997,134.054214,24507200
+2017-02-22,136.429993,137.119995,136.110001,137.110001,134.456299,20836900
+2017-02-23,137.380005,137.479996,136.300003,136.529999,133.887512,20788200
+2017-02-24,135.910004,136.660004,135.279999,136.660004,134.015015,21776600
+2017-02-27,137.139999,137.440002,136.279999,136.929993,134.279785,20257400
+2017-02-28,137.080002,137.440002,136.699997,136.990005,134.338623,23482900
+2017-03-01,137.889999,140.149994,137.600006,139.789993,137.084412,36414600
+2017-03-02,140,140.279999,138.759995,138.960007,136.270508,26211000
+2017-03-03,138.779999,139.830002,138.589996,139.779999,137.074615,21108100
+2017-03-06,139.369995,139.770004,138.600006,139.339996,136.643127,21750000
+2017-03-07,139.059998,139.979996,138.789993,139.520004,136.819656,17446300
+2017-03-08,138.949997,139.800003,138.820007,139,136.309723,18707200
+2017-03-09,138.740005,138.789993,137.050003,138.679993,135.995895,22155900
+2017-03-10,139.25,139.360001,138.639999,139.139999,136.447006,19612800
+2017-03-13,138.850006,139.429993,138.820007,139.199997,136.505829,17421700
+2017-03-14,139.300003,139.649994,138.839996,138.990005,136.299911,15309100
+2017-03-15,139.410004,140.75,139.029999,140.460007,137.74147,25691800
+2017-03-16,140.720001,141.020004,140.259995,140.690002,137.96698,19232000
+2017-03-17,141,141,139.889999,139.990005,137.280563,43885000
+2017-03-20,140.399994,141.5,140.229996,141.460007,138.722122,21542000
+2017-03-21,142.110001,142.800003,139.729996,139.839996,137.133453,39529900
+2017-03-22,139.850006,141.600006,139.759995,141.419998,138.682877,25860200
+2017-03-23,141.259995,141.580002,140.610001,140.919998,138.192551,20346300
+2017-03-24,141.5,141.740005,140.350006,140.639999,137.917984,22395600
+2017-03-27,139.389999,141.220001,138.619995,140.880005,138.153336,23575100
+2017-03-28,140.910004,144.039993,140.619995,143.800003,141.016815,33374800
+2017-03-29,143.679993,144.490005,143.190002,144.119995,141.330612,29190000
+2017-03-30,144.190002,144.5,143.5,143.929993,141.144287,21207300
+2017-03-31,143.720001,144.270004,143.009995,143.660004,140.879517,19661700
+2017-04-03,143.710007,144.119995,143.050003,143.699997,140.918762,19985700
+2017-04-04,143.25,144.889999,143.169998,144.770004,141.968033,19891400
+2017-04-05,144.220001,145.460007,143.809998,144.020004,141.232559,27717900
+2017-04-06,144.289993,144.520004,143.449997,143.660004,140.879517,21149000
+2017-04-07,143.729996,144.179993,143.270004,143.339996,140.565704,16672200
+2017-04-10,143.600006,143.880005,142.899994,143.169998,140.399017,18933400
+2017-04-11,142.940002,143.350006,140.059998,141.630005,138.888809,30379400
+2017-04-12,141.600006,142.149994,141.009995,141.800003,139.055542,20350000
+2017-04-13,141.910004,142.380005,141.050003,141.050003,138.320038,17822900
+2017-04-17,141.479996,141.880005,140.869995,141.830002,139.084946,16582100
+2017-04-18,141.410004,142.039993,141.110001,141.199997,138.467133,14697500
+2017-04-19,141.880005,142,140.449997,140.679993,137.957184,17328400
+2017-04-20,141.220001,142.919998,141.160004,142.440002,139.683151,23319600
+2017-04-21,142.440002,142.679993,141.850006,142.270004,139.516418,17320900
+2017-04-24,143.5,143.949997,143.179993,143.639999,140.859909,17134300
+2017-04-25,143.910004,144.899994,143.869995,144.529999,141.732681,18871500
+2017-04-26,144.470001,144.600006,143.380005,143.679993,140.899139,20041200
+2017-04-27,143.919998,144.160004,143.309998,143.789993,141.007004,14246300
+2017-04-28,144.089996,144.300003,143.270004,143.649994,140.869705,20860400
+2017-05-01,145.100006,147.199997,144.960007,146.580002,143.743011,33602900
+2017-05-02,147.539993,148.089996,146.839996,147.509995,144.654999,45352200
+2017-05-03,145.589996,147.490005,144.270004,147.059998,144.213715,45697000
+2017-05-04,146.520004,147.139999,145.809998,146.529999,143.693954,23371900
+2017-05-05,146.759995,148.979996,146.759995,148.960007,146.076935,27327700
+2017-05-08,149.029999,153.699997,149.029999,153.009995,150.048538,48752400
+2017-05-09,153.869995,154.880005,153.449997,153.990005,151.009598,39130400
+2017-05-10,153.630005,153.940002,152.110001,153.259995,150.293716,25805700
+2017-05-11,152.449997,154.070007,152.309998,153.949997,151.593491,27255100
+2017-05-12,154.699997,156.419998,154.669998,156.100006,153.710602,32527000
+2017-05-15,156.009995,156.649994,155.050003,155.699997,153.316727,26009700
+2017-05-16,155.940002,156.059998,154.720001,155.470001,153.090256,20048500
+2017-05-17,153.600006,154.570007,149.710007,150.25,147.95015,50767700
+2017-05-18,151.270004,153.339996,151.130005,152.539993,150.205078,33568200
+2017-05-19,153.380005,153.979996,152.630005,153.059998,150.717133,26960800
+2017-05-22,154,154.580002,152.910004,153.990005,151.632904,22966400
+2017-05-23,154.899994,154.899994,153.309998,153.800003,151.445801,19918900
+2017-05-24,153.839996,154.169998,152.669998,153.339996,150.992844,19178000
+2017-05-25,153.729996,154.350006,153.029999,153.869995,151.514725,19235600
+2017-05-26,154,154.240005,153.309998,153.610001,151.258728,21701100
+2017-05-30,153.419998,154.429993,153.330002,153.669998,151.317795,20126900
+2017-05-31,153.970001,154.169998,152.380005,152.759995,150.421722,24451200
+2017-06-01,153.169998,153.330002,152.220001,153.179993,150.835281,16404100
+2017-06-02,153.580002,155.449997,152.889999,155.449997,153.070541,27770700
+2017-06-05,154.339996,154.449997,153.460007,153.929993,151.573807,25331700
+2017-06-06,153.899994,155.809998,153.779999,154.449997,152.085846,26624900
+2017-06-07,155.020004,155.979996,154.479996,155.369995,152.991776,21069600
+2017-06-08,155.25,155.539993,154.399994,154.990005,152.617584,21250800
+2017-06-09,155.190002,155.190002,146.020004,148.979996,146.69957,64882700
+2017-06-12,145.740005,146.089996,142.509995,145.419998,143.194092,72307300
+2017-06-13,147.160004,147.449997,145.149994,146.589996,144.346161,34165400
+2017-06-14,147.5,147.5,143.839996,145.160004,142.938065,31531200
+2017-06-15,143.320007,144.479996,142.210007,144.289993,142.08136,32165400
+2017-06-16,143.779999,144.5,142.199997,142.270004,140.0923,50361100
+2017-06-19,143.660004,146.740005,143.660004,146.339996,144.099991,32541400
+2017-06-20,146.869995,146.869995,144.940002,145.009995,142.790359,24900100
+2017-06-21,145.520004,146.070007,144.610001,145.869995,143.637177,21265800
+2017-06-22,145.770004,146.699997,145.119995,145.630005,143.400879,19106300
+2017-06-23,145.130005,147.160004,145.110001,146.279999,144.040924,35439400
+2017-06-26,147.169998,148.279999,145.380005,145.820007,143.587967,25692400
+2017-06-27,145.009995,146.160004,143.619995,143.729996,141.529938,24761900
+2017-06-28,144.490005,146.110001,143.160004,145.830002,143.597794,22082400
+2017-06-29,144.710007,145.130005,142.279999,143.679993,141.480713,31499400
+2017-06-30,144.449997,144.960007,143.779999,144.020004,141.815506,23024100
+2017-07-03,144.880005,145.300003,143.100006,143.5,141.303467,14258300
+2017-07-05,143.690002,144.789993,142.720001,144.089996,141.88443,21569600
+2017-07-06,143.020004,143.5,142.410004,142.729996,140.545258,24128800
+2017-07-07,142.899994,144.75,142.899994,144.179993,141.973053,19201700
+2017-07-10,144.110001,145.949997,143.369995,145.059998,142.839584,21090600
+2017-07-11,144.729996,145.850006,144.380005,145.529999,143.302399,19781800
+2017-07-12,145.869995,146.179993,144.820007,145.740005,143.509186,24884500
+2017-07-13,145.5,148.490005,145.440002,147.770004,145.508102,25199400
+2017-07-14,147.970001,149.330002,147.330002,149.039993,146.758636,20132100
+2017-07-17,148.820007,150.899994,148.570007,149.559998,147.270706,23793500
+2017-07-18,149.199997,150.130005,148.669998,150.080002,147.782761,17868800
+2017-07-19,150.479996,151.419998,149.949997,151.020004,148.708359,20923000
+2017-07-20,151.5,151.740005,150.190002,150.339996,148.038757,17243700
+2017-07-21,149.990005,150.440002,148.880005,150.270004,147.969849,26252600
+2017-07-24,150.580002,152.440002,149.899994,152.089996,149.761978,21493200
+2017-07-25,151.800003,153.839996,151.800003,152.740005,150.402039,18853900
+2017-07-26,153.350006,153.929993,153.059998,153.460007,151.111023,15781000
+2017-07-27,153.75,153.990005,147.300003,150.559998,148.255402,32476300
+2017-07-28,149.889999,150.229996,149.190002,149.5,147.211624,17213700
+2017-07-31,149.899994,150.330002,148.130005,148.729996,146.4534,19845900
+2017-08-01,149.100006,150.220001,148.410004,150.050003,147.753204,35368600
+2017-08-02,159.279999,159.75,156.160004,157.139999,154.734665,69936800
+2017-08-03,157.050003,157.210007,155.020004,155.570007,153.188705,27097300
+2017-08-04,156.070007,157.399994,155.690002,156.389999,153.99614,20559900
+2017-08-07,157.059998,158.919998,156.669998,158.809998,156.37912,21870300
+2017-08-08,158.600006,161.830002,158.270004,160.080002,157.629684,36205900
+2017-08-09,159.259995,161.270004,159.110001,161.059998,158.594666,26131500
+2017-08-10,159.899994,160,154.630005,155.320007,153.543152,40804300
+2017-08-11,156.600006,158.570007,156.070007,157.479996,155.678436,26257100
+2017-08-14,159.320007,160.210007,158.75,159.850006,158.021317,22122700
+2017-08-15,160.660004,162.199997,160.139999,161.600006,159.751297,29465500
+2017-08-16,161.940002,162.509995,160.149994,160.949997,159.108719,27671600
+2017-08-17,160.520004,160.710007,157.839996,157.860001,156.054092,27940600
+2017-08-18,157.860001,159.5,156.720001,157.5,155.698212,27428100
+2017-08-21,157.5,157.889999,155.110001,157.210007,155.41153,26368500
+2017-08-22,158.229996,160,158.020004,159.779999,157.952118,21604600
+2017-08-23,159.070007,160.470001,158.880005,159.979996,158.149826,19399100
+2017-08-24,160.429993,160.740005,158.550003,159.270004,157.447952,19818900
+2017-08-25,159.649994,160.559998,159.270004,159.860001,158.031189,25480100
+2017-08-28,160.139999,162,159.929993,161.470001,159.622787,25966000
+2017-08-29,160.100006,163.119995,160,162.910004,161.046326,29516900
+2017-08-30,163.800003,163.889999,162.610001,163.350006,161.481277,27269600
+2017-08-31,163.639999,164.520004,163.479996,164,162.12384,26785100
+2017-09-01,164.800003,164.940002,163.630005,164.050003,162.173279,16591100
+2017-09-05,163.75,164.25,160.559998,162.080002,160.225815,29468500
+2017-09-06,162.710007,162.990005,160.520004,161.910004,160.057755,21651700
+2017-09-07,162.089996,162.240005,160.360001,161.259995,159.415192,21928500
+2017-09-08,160.860001,161.149994,158.529999,158.630005,156.815277,28611500
+2017-09-11,160.5,162.050003,159.889999,161.5,159.652451,31085900
+2017-09-12,162.610001,163.960007,158.770004,160.860001,159.01976,71714000
+2017-09-13,159.869995,159.960007,157.910004,159.649994,157.823593,44907400
+2017-09-14,158.990005,159.399994,158.089996,158.279999,156.469269,23760700
+2017-09-15,158.470001,160.970001,158,159.880005,158.050964,49114600
+2017-09-18,160.110001,160.5,158,158.669998,156.854813,28269400
+2017-09-19,159.509995,159.770004,158.440002,158.729996,156.914124,20810600
+2017-09-20,157.899994,158.259995,153.830002,156.070007,154.284561,52951400
+2017-09-21,155.800003,155.800003,152.75,153.389999,151.635208,37511700
+2017-09-22,151.539993,152.270004,150.559998,151.889999,150.15239,46645400
+2017-09-25,149.990005,151.830002,149.160004,150.550003,148.827698,44387300
+2017-09-26,151.779999,153.919998,151.690002,153.139999,151.388062,36660000
+2017-09-27,153.800003,154.720001,153.539993,154.229996,152.465607,25504200
+2017-09-28,153.889999,154.279999,152.699997,153.279999,151.526474,22005500
+2017-09-29,153.210007,154.130005,152,154.119995,152.356857,26299800
+2017-10-02,154.259995,154.449997,152.720001,153.809998,152.050415,18698800
+2017-10-03,154.009995,155.089996,153.910004,154.479996,152.712738,16230300
+2017-10-04,153.630005,153.860001,152.460007,153.479996,151.724197,20163800
+2017-10-05,154.179993,155.440002,154.050003,155.389999,153.612335,21283800
+2017-10-06,154.970001,155.490005,154.559998,155.300003,153.523361,17407600
+2017-10-09,155.809998,156.729996,155.490005,155.839996,154.057175,16262900
+2017-10-10,156.059998,158,155.100006,155.899994,154.116486,15617000
+2017-10-11,155.970001,156.979996,155.75,156.550003,154.759079,16905600
+2017-10-12,156.350006,157.369995,155.729996,156,154.215363,16125100
+2017-10-13,156.729996,157.279999,156.410004,156.990005,155.194031,16394200
+2017-10-16,157.899994,160,157.649994,159.880005,158.050964,24121500
+2017-10-17,159.779999,160.869995,159.229996,160.470001,158.634216,18997300
+2017-10-18,160.419998,160.710007,159.600006,159.759995,157.932343,16374200
+2017-10-19,156.75,157.080002,155.020004,155.979996,154.195572,42584200
+2017-10-20,156.610001,157.75,155.960007,156.25,154.462494,23974100
+2017-10-23,156.889999,157.690002,155.5,156.169998,154.383408,21984300
+2017-10-24,156.289993,157.419998,156.199997,157.100006,155.302765,17757200
+2017-10-25,156.910004,157.550003,155.270004,156.410004,154.620682,21207100
+2017-10-26,157.229996,157.830002,156.779999,157.410004,155.609222,17000500
+2017-10-27,159.289993,163.600006,158.699997,163.050003,161.184708,44454200
+2017-10-30,163.889999,168.070007,163.720001,166.720001,164.812729,44700800
+2017-10-31,167.899994,169.649994,166.940002,169.039993,167.106186,36046800
+2017-11-01,169.869995,169.940002,165.610001,166.889999,164.980774,33637800
+2017-11-02,166.600006,168.5,165.279999,168.110001,166.186829,41393400
+2017-11-03,174,174.259995,171.119995,172.5,170.526596,59398600
+2017-11-06,172.369995,174.990005,171.720001,174.25,172.256577,35026300
+2017-11-07,173.910004,175.25,173.600006,174.809998,172.810165,24361500
+2017-11-08,174.660004,176.240005,174.330002,176.240005,174.223831,24409500
+2017-11-09,175.110001,176.100006,173.139999,175.880005,173.86795,29482600
+2017-11-10,175.110001,175.380005,174.270004,174.669998,173.292511,25145500
+2017-11-13,173.5,174.5,173.399994,173.970001,172.598022,16982100
+2017-11-14,173.039993,173.479996,171.179993,171.339996,169.988754,24782500
+2017-11-15,169.970001,170.320007,168.380005,169.080002,167.746582,29158100
+2017-11-16,171.179993,171.869995,170.300003,171.100006,169.750671,23637500
+2017-11-17,171.039993,171.389999,169.639999,170.149994,168.808151,21899500
+2017-11-20,170.289993,170.559998,169.559998,169.979996,168.639496,16262400
+2017-11-21,170.779999,173.699997,170.779999,173.139999,171.774567,25131300
+2017-11-22,173.360001,175,173.050003,174.960007,173.580231,25588900
+2017-11-24,175.100006,175.5,174.649994,174.970001,173.590149,14026700
+2017-11-27,175.050003,175.080002,173.339996,174.089996,172.717072,20716800
+2017-11-28,174.300003,174.869995,171.860001,173.070007,171.705139,26428800
+2017-11-29,172.630005,172.919998,167.160004,169.479996,168.143433,41666400
+2017-11-30,170.429993,172.139999,168.440002,171.850006,170.494751,41527200
+2017-12-01,169.949997,171.669998,168.5,171.050003,169.70105,39759300
+2017-12-04,172.479996,172.619995,169.630005,169.800003,168.460922,32542400
+2017-12-05,169.059998,171.520004,168.399994,169.639999,168.30217,27350200
+2017-12-06,167.5,170.199997,166.460007,169.009995,167.677139,28560000
+2017-12-07,169.029999,170.440002,168.910004,169.320007,167.984711,25673300
+2017-12-08,170.490005,171,168.820007,169.369995,168.034302,23355200
+2017-12-11,169.199997,172.889999,168.789993,172.669998,171.308289,35273800
+2017-12-12,172.149994,172.389999,171.460007,171.699997,170.345932,19409200
+2017-12-13,172.5,173.539993,172,172.270004,170.911438,23818400
+2017-12-14,172.399994,173.130005,171.649994,172.220001,170.861832,20476500
+2017-12-15,173.630005,174.169998,172.460007,173.970001,172.598022,40169300
+2017-12-18,174.880005,177.199997,174.860001,176.419998,175.028717,29421100
+2017-12-19,175.029999,175.389999,174.089996,174.539993,173.163528,27436400
+2017-12-20,174.869995,175.419998,173.25,174.350006,172.975037,23475600
+2017-12-21,174.169998,176.020004,174.100006,175.009995,173.629822,20949900
+2017-12-22,174.679993,175.419998,174.5,175.009995,173.629822,16349400
+2017-12-26,170.800003,171.470001,169.679993,170.570007,169.224838,33185500
+2017-12-27,170.100006,170.779999,169.710007,170.600006,169.254608,21498200
+2017-12-28,171,171.850006,170.479996,171.080002,169.73082,16480200
+2017-12-29,170.520004,170.589996,169.220001,169.229996,167.895416,25999900
+2018-01-02,170.160004,172.300003,169.259995,172.259995,170.901505,25555900
+2018-01-03,172.529999,174.550003,171.960007,172.229996,170.871735,29517900
+2018-01-04,172.539993,173.470001,172.080002,173.029999,171.665436,22434600
+2018-01-05,173.440002,175.369995,173.050003,175,173.619904,23660000
+2018-01-08,174.350006,175.610001,173.929993,174.350006,172.975037,20567800
+2018-01-09,174.550003,175.059998,173.410004,174.330002,172.9552,21584000
+2018-01-10,173.160004,174.300003,173,174.289993,172.915497,23959900
+2018-01-11,174.589996,175.490005,174.490005,175.279999,173.897705,18667700
+2018-01-12,176.179993,177.360001,175.649994,177.089996,175.69342,25418100
+2018-01-16,177.899994,179.389999,176.139999,176.190002,174.800537,29565900
+2018-01-17,176.149994,179.25,175.070007,179.100006,177.687576,34386800
+2018-01-18,179.369995,180.100006,178.25,179.259995,177.846313,31193400
+2018-01-19,178.610001,179.580002,177.410004,178.460007,177.052628,32425100
+2018-01-22,177.300003,177.779999,176.600006,177,175.604141,27108600
+2018-01-23,177.300003,179.440002,176.820007,177.039993,175.643814,32689100
+2018-01-24,177.25,177.300003,173.199997,174.220001,172.846054,51105100
+2018-01-25,174.509995,174.949997,170.529999,171.110001,169.760574,41529000
+2018-01-26,172,172,170.059998,171.509995,170.157425,39143000
+2018-01-29,170.160004,170.160004,167.070007,167.960007,166.635422,50640400
+2018-01-30,165.529999,167.369995,164.699997,166.970001,165.653244,46048200
+2018-01-31,166.869995,168.440002,166.5,167.429993,166.109604,32478900
+2018-02-01,167.169998,168.619995,166.759995,167.779999,166.456848,47230800
+2018-02-02,166,166.800003,160.100006,160.5,159.234253,86593800
+2018-02-05,159.100006,163.880005,156,156.490005,155.25589,72738500
+2018-02-06,154.830002,163.720001,154,163.029999,161.744293,68243800
+2018-02-07,163.089996,163.399994,159.070007,159.539993,158.281815,51608600
+2018-02-08,160.289993,161,155.029999,155.149994,153.926437,54390500
+2018-02-09,157.070007,157.889999,150.240005,156.410004,155.809189,70672600
+2018-02-12,158.5,163.889999,157.509995,162.710007,162.084991,60819500
+2018-02-13,161.949997,164.75,161.649994,164.339996,163.708725,32549200
+2018-02-14,163.039993,167.539993,162.880005,167.369995,166.727081,40644900
+2018-02-15,169.789993,173.089996,169,172.990005,172.3255,51147200
+2018-02-16,172.360001,174.820007,171.770004,172.429993,171.767639,40176100
+2018-02-20,172.050003,174.259995,171.419998,171.850006,171.18988,33930500
+2018-02-21,172.830002,174.119995,171.009995,171.070007,170.412872,37471600
+2018-02-22,171.800003,173.949997,171.710007,172.5,171.837372,30991900
+2018-02-23,173.669998,175.649994,173.539993,175.5,174.825851,33812400
+2018-02-26,176.350006,179.389999,176.210007,178.970001,178.282532,38162200
+2018-02-27,179.100006,180.479996,178.160004,178.389999,177.704758,38928100
+2018-02-28,179.259995,180.619995,178.050003,178.119995,177.435791,37782100
+2018-03-01,178.539993,179.779999,172.660004,175,174.327774,48802000
+2018-03-02,172.800003,176.300003,172.449997,176.210007,175.533142,38454000
+2018-03-05,175.210007,177.740005,174.520004,176.820007,176.140793,28401400
+2018-03-06,177.910004,178.25,176.130005,176.669998,175.991364,23788500
+2018-03-07,174.940002,175.850006,174.270004,175.029999,174.357666,31703500
+2018-03-08,175.479996,177.119995,175.070007,176.940002,176.26033,23774100
+2018-03-09,177.960007,180,177.389999,179.979996,179.288635,32185200
+2018-03-12,180.289993,182.389999,180.210007,181.720001,181.021957,32207100
+2018-03-13,182.589996,183.5,179.240005,179.970001,179.278687,31693500
+2018-03-14,180.320007,180.520004,177.809998,178.440002,177.754562,29368400
+2018-03-15,178.5,180.240005,178.070007,178.649994,177.963745,22743800
+2018-03-16,178.649994,179.119995,177.619995,178.020004,177.336182,39404700
+2018-03-19,177.320007,177.470001,173.660004,175.300003,174.626633,33446800
+2018-03-20,175.240005,176.800003,174.940002,175.240005,174.566864,19649400
+2018-03-21,175.039993,175.089996,171.259995,171.270004,170.612106,37054900
+2018-03-22,170,172.679993,168.600006,168.850006,168.201401,41490800
+2018-03-23,168.389999,169.919998,164.940002,164.940002,164.306427,41028800
+2018-03-26,168.070007,173.100006,166.440002,172.770004,172.106354,37541200
+2018-03-27,173.679993,175.149994,166.919998,168.339996,167.693359,40922600
+2018-03-28,167.25,170.020004,165.190002,166.479996,165.8405,41668500
+2018-03-29,167.809998,171.75,166.899994,167.779999,167.135513,38398500
+2018-04-02,166.639999,168.940002,164.470001,166.679993,166.039734,37586800
+2018-04-03,167.639999,168.75,164.880005,168.389999,167.743164,30278000
+2018-04-04,164.880005,172.009995,164.770004,171.610001,170.950806,34605500
+2018-04-05,172.580002,174.229996,172.080002,172.800003,172.13623,26933200
+2018-04-06,170.970001,172.479996,168.199997,168.380005,167.733215,35005300
+2018-04-09,169.880005,173.089996,169.850006,170.050003,169.39679,29017700
+2018-04-10,173,174,171.529999,173.25,172.584503,28408600
+2018-04-11,172.229996,173.919998,171.699997,172.440002,171.777618,22431600
+2018-04-12,173.410004,175,173.039993,174.139999,173.471085,22889300
+2018-04-13,174.779999,175.839996,173.850006,174.729996,174.058807,25124300
+2018-04-16,175.029999,176.190002,174.830002,175.820007,175.144638,21578400
+2018-04-17,176.490005,178.940002,176.410004,178.240005,177.555328,26605400
+2018-04-18,177.809998,178.820007,176.880005,177.839996,177.15686,20754500
+2018-04-19,173.759995,175.389999,172.660004,172.800003,172.13623,34808800
+2018-04-20,170.600006,171.220001,165.429993,165.720001,165.08342,65491100
+2018-04-23,166.830002,166.919998,164.089996,165.240005,164.60527,36515500
+2018-04-24,165.669998,166.330002,161.220001,162.940002,162.314102,33692000
+2018-04-25,162.619995,165.419998,162.410004,163.649994,163.021362,28382100
+2018-04-26,164.119995,165.729996,163.369995,164.220001,163.589188,27963000
+2018-04-27,164,164.330002,160.630005,162.320007,161.696487,35655800
+2018-04-30,162.130005,167.259995,161.839996,165.259995,164.625183,42427400
+2018-05-01,166.410004,169.199997,165.270004,169.100006,168.450439,53569400
+2018-05-02,175.229996,177.75,173.800003,176.570007,175.891754,66539400
+2018-05-03,175.880005,177.5,174.440002,176.889999,176.21051,34068200
+2018-05-04,178.25,184.25,178.169998,183.830002,183.123856,56201300
+2018-05-07,185.179993,187.669998,184.75,185.160004,184.448746,42451400
+2018-05-08,184.990005,186.220001,183.669998,186.050003,185.335327,28402800
+2018-05-09,186.550003,187.399994,185.220001,187.360001,186.640305,23211200
+2018-05-10,187.740005,190.369995,187.649994,190.039993,189.309998,27989300
+2018-05-11,189.490005,190.059998,187.449997,188.589996,188.589996,25806600
\ No newline at end of file
diff --git a/src/content/tutorial/01-basics/02-scales/01-plot/+assets/app-a/src/lib/App.svelte b/src/content/tutorial/01-basics/02-scales/01-plot/+assets/app-a/src/lib/App.svelte
new file mode 100644
index 000000000..13e9ba666
--- /dev/null
+++ b/src/content/tutorial/01-basics/02-scales/01-plot/+assets/app-a/src/lib/App.svelte
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/02-scales/01-plot/index.md b/src/content/tutorial/01-basics/02-scales/01-plot/index.md
new file mode 100644
index 000000000..2f7f565b3
--- /dev/null
+++ b/src/content/tutorial/01-basics/02-scales/01-plot/index.md
@@ -0,0 +1,5 @@
+---
+title: The Plot component
+---
+
+In this chapter we're going to learn more about the `Plot` component. It's purpose is to take care of all the complicated aspects of creating visualizations: setting up common scales, axes, legends, and so on. You can customize all of these things, but the defaults are designed to work well for most charts.
diff --git a/src/content/tutorial/01-basics/02-scales/02-extending-axes/+assets/app-a/src/lib/App.svelte b/src/content/tutorial/01-basics/02-scales/02-extending-axes/+assets/app-a/src/lib/App.svelte
new file mode 100644
index 000000000..13e9ba666
--- /dev/null
+++ b/src/content/tutorial/01-basics/02-scales/02-extending-axes/+assets/app-a/src/lib/App.svelte
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/02-scales/02-extending-axes/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/01-basics/02-scales/02-extending-axes/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..89e22b115
--- /dev/null
+++ b/src/content/tutorial/01-basics/02-scales/02-extending-axes/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/02-scales/02-extending-axes/index.md b/src/content/tutorial/01-basics/02-scales/02-extending-axes/index.md
new file mode 100644
index 000000000..be5f46929
--- /dev/null
+++ b/src/content/tutorial/01-basics/02-scales/02-extending-axes/index.md
@@ -0,0 +1,24 @@
+---
+title: Extending the domain
+---
+
+Here's a line chart of Apple's stock closing price from 2013 to 2018. Notice how the y axis starts around $60 — SveltePlot fits the scale tightly to the data by default.
+
+That's usually sensible, but for some charts you want to anchor the y axis at zero to show the true magnitude of values. You can do this simply by adding a reference line at zero:
+
+First we need to add `RuleY` to the list of imports:
+
+```js
+import { Plot, Line+++, RuleY+++ } from 'svelteplot';
+```
+
+Now we can add `` to the plot:
+
+```svelte
+
+
+ ++++++
+
+```
+
+`` draws a horizontal rule at y = 0. The Plot component "collects" the data from all marks and extends the y axis to the data range.
diff --git a/src/content/tutorial/01-basics/02-scales/03-scale-options/+assets/app-a/src/lib/App.svelte b/src/content/tutorial/01-basics/02-scales/03-scale-options/+assets/app-a/src/lib/App.svelte
new file mode 100644
index 000000000..13e9ba666
--- /dev/null
+++ b/src/content/tutorial/01-basics/02-scales/03-scale-options/+assets/app-a/src/lib/App.svelte
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/02-scales/03-scale-options/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/01-basics/02-scales/03-scale-options/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..13dbfc526
--- /dev/null
+++ b/src/content/tutorial/01-basics/02-scales/03-scale-options/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/02-scales/03-scale-options/index.md b/src/content/tutorial/01-basics/02-scales/03-scale-options/index.md
new file mode 100644
index 000000000..2aca3e390
--- /dev/null
+++ b/src/content/tutorial/01-basics/02-scales/03-scale-options/index.md
@@ -0,0 +1,13 @@
+---
+title: Scale options
+---
+
+In the previous lesson you extended the y scale by adding a `` mark. That adds a visible line. If you want to anchor the axis at zero _without_ drawing a reference line, use the `zero` scale option instead:
+
+```svelte
+
+```
+
+`zero: true` tells SveltePlot to always include zero in the y domain, no matter what the data looks like. The same option works on the x scale too.
+
+Scale options are passed as an object to the corresponding scale prop (`x`, `y`, `color`, `r`, etc.) on ``. You've already seen one: `grid: true` adds grid lines to that scale's axis.
diff --git a/src/content/tutorial/01-basics/02-scales/04-scale-type/+assets/app-a/src/lib/App.svelte b/src/content/tutorial/01-basics/02-scales/04-scale-type/+assets/app-a/src/lib/App.svelte
new file mode 100644
index 000000000..13e9ba666
--- /dev/null
+++ b/src/content/tutorial/01-basics/02-scales/04-scale-type/+assets/app-a/src/lib/App.svelte
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/02-scales/04-scale-type/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/01-basics/02-scales/04-scale-type/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..c0efff616
--- /dev/null
+++ b/src/content/tutorial/01-basics/02-scales/04-scale-type/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/02-scales/04-scale-type/index.md b/src/content/tutorial/01-basics/02-scales/04-scale-type/index.md
new file mode 100644
index 000000000..5797b0dab
--- /dev/null
+++ b/src/content/tutorial/01-basics/02-scales/04-scale-type/index.md
@@ -0,0 +1,30 @@
+---
+title: Scale types
+---
+
+SveltePlot infers the **type** of each scale from the values it receives across all marks. The `Date` column in our AAPL data contains date strings, so SveltePlot picks a **temporal** (time-based) x scale automatically.
+
+Let's add a vertical reference line at the start of 2016. Import `RuleX` and add it to the plot:
+
+```svelte
+import { Plot, Line, +++RuleX+++ } from 'svelteplot';
+```
+
+Let's add a rule at `2016`:
+
+```svelte
+
++
+
+
+
+
+```
diff --git a/src/content/tutorial/01-basics/02-scales/index.md b/src/content/tutorial/01-basics/02-scales/index.md
new file mode 100644
index 000000000..1ca62affd
--- /dev/null
+++ b/src/content/tutorial/01-basics/02-scales/index.md
@@ -0,0 +1,5 @@
+---
+title: Scales
+scope: { 'prefix': '/src/lib/', 'name': 'src' }
+focus: /src/lib/App.svelte
+---
diff --git a/src/content/tutorial/01-basics/03-transforms/01-stacking/+assets/app-a/src/lib/App.svelte b/src/content/tutorial/01-basics/03-transforms/01-stacking/+assets/app-a/src/lib/App.svelte
new file mode 100644
index 000000000..80d3963ac
--- /dev/null
+++ b/src/content/tutorial/01-basics/03-transforms/01-stacking/+assets/app-a/src/lib/App.svelte
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/03-transforms/01-stacking/+assets/app-a/src/lib/fruit-sales.csv b/src/content/tutorial/01-basics/03-transforms/01-stacking/+assets/app-a/src/lib/fruit-sales.csv
new file mode 100644
index 000000000..11a16c9d0
--- /dev/null
+++ b/src/content/tutorial/01-basics/03-transforms/01-stacking/+assets/app-a/src/lib/fruit-sales.csv
@@ -0,0 +1,9 @@
+Quarter,Fruit,Sales
+Q1,Apples,1448
+Q2,Apples,1212
+Q3,Apples,1565
+Q4,Apples,1645
+Q1,Bananas,767
+Q2,Bananas,853
+Q3,Bananas,819
+Q4,Bananas,700
diff --git a/src/content/tutorial/01-basics/03-transforms/01-stacking/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/01-basics/03-transforms/01-stacking/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..bab8de0aa
--- /dev/null
+++ b/src/content/tutorial/01-basics/03-transforms/01-stacking/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/03-transforms/01-stacking/index.md b/src/content/tutorial/01-basics/03-transforms/01-stacking/index.md
new file mode 100644
index 000000000..ee370d5b5
--- /dev/null
+++ b/src/content/tutorial/01-basics/03-transforms/01-stacking/index.md
@@ -0,0 +1,17 @@
+---
+title: Transforms
+---
+
+Transforms are useful when your dataset is not in the shape you need it to be visualized. In the chart you see fruit sales numbers represented as bars ranging from zero to more than 2000 sales units.
+
+But if you take a look at the dataset, you'll find the largest number to be the Q1 Apple sales at 1448 units. How is that?
+
+SveltePlot's BarY mark automatically (or implicitely) applied a **stackY transform**! It groups all dataset rows by the `x` values and stacks their `y` values on top of each other.
+
+This happens whenever you only pass a `y` channel instead of a `y1`/`y2` range, which is what the Bar mark actually visualizes.
+
+Try changing the `y="Sales"` to `y1={0} y2="Sales"` to see what happens without the stacking:
+
+```svelte
+
+```
diff --git a/src/content/tutorial/01-basics/03-transforms/06-jitter/+assets/src/lib/cars.csv b/src/content/tutorial/01-basics/03-transforms/06-jitter/+assets/src/lib/cars.csv
new file mode 100644
index 000000000..96e2b22b2
--- /dev/null
+++ b/src/content/tutorial/01-basics/03-transforms/06-jitter/+assets/src/lib/cars.csv
@@ -0,0 +1,407 @@
+manufactor,model,economy (mpg),cylinders,displacement (cc),power (hp),weight (lb),0-60 mph (s),year
+AMC,Ambassador Brougham,13,8,360,175,3821,11,73
+AMC,Ambassador DPL,15,8,390,190,3850,8.5,70
+AMC,Ambassador SST,17,8,304,150,3672,11.5,72
+AMC,Concord DL 6,20.2,6,232,90,3265,18.2,79
+AMC,Concord DL,18.1,6,258,120,3410,15.1,78
+AMC,Concord DL,23,4,151,NaN,3035,20.5,82
+AMC,Concord,19.4,6,232,90,3210,17.2,78
+AMC,Concord,24.3,4,151,90,3003,20.1,80
+AMC,Gremlin,18,6,232,100,2789,15,73
+AMC,Gremlin,19,6,232,100,2634,13,71
+AMC,Gremlin,20,6,232,100,2914,16,75
+AMC,Gremlin,21,6,199,90,2648,15,70
+AMC,Hornet Sportabout (Wagon),18,6,258,110,2962,13.5,71
+AMC,Hornet,18,6,199,97,2774,15.5,70
+AMC,Hornet,18,6,232,100,2945,16,73
+AMC,Hornet,19,6,232,100,2901,16,74
+AMC,Hornet,22.5,6,232,90,3085,17.6,76
+AMC,Matador (Wagon),14,8,304,150,4257,15.5,74
+AMC,Matador (Wagon),15,8,304,150,3892,12.5,72
+AMC,Matador,14,8,304,150,3672,11.5,73
+AMC,Matador,15,6,258,110,3730,19,75
+AMC,Matador,15.5,8,304,120,3962,13.9,76
+AMC,Matador,16,6,258,110,3632,18,74
+AMC,Matador,18,6,232,100,3288,15.5,71
+AMC,Pacer D/L,17.5,6,258,95,3193,17.8,76
+AMC,Pacer,19,6,232,90,3211,17,75
+AMC,Rebel SST (Wagon),NaN,8,360,175,3850,11,70
+AMC,Rebel SST,16,8,304,150,3433,12,70
+AMC,Spirit DL,27.4,4,121,80,2670,15,79
+Audi,100 LS,20,4,114,91,2582,14,73
+Audi,100 LS,23,4,115,95,2694,15,75
+Audi,100 LS,24,4,107,90,2430,14.5,70
+Audi,4000,34.3,4,97,78,2188,15.8,80
+Audi,5000,20.3,5,131,103,2830,15.9,78
+Audi,5000S (Diesel),36.4,5,121,67,2950,19.9,80
+Audi,Fox,29,4,98,83,2219,16.5,74
+BMW,2002,26,4,121,113,2234,12.5,70
+BMW,320i,21.5,4,121,110,2600,12.8,77
+Buick,Century 350,13,8,350,175,4100,13,73
+Buick,Century Limited,25,6,181,110,2945,16.4,82
+Buick,Century Luxus (Wagon),13,8,350,150,4699,14.5,74
+Buick,Century Special,20.6,6,231,105,3380,15.8,78
+Buick,Century,17,6,231,110,3907,21,75
+Buick,Century,22.4,6,231,110,3415,15.8,81
+Buick,Electra 225 Custom,12,8,455,225,4951,11,73
+Buick,Estate Wagon (Wagon),14,8,455,225,3086,10,70
+Buick,Estate Wagon (Wagon),16.9,8,350,155,4360,14.9,79
+Buick,Lesabre Custom,13,8,350,155,4502,13.5,72
+Buick,Opel Isuzu Deluxe,30,4,111,80,2155,14.8,77
+Buick,Regal Sport Coupe (Turbo),17.7,6,231,165,3445,13.4,78
+Buick,Skyhawk,21,6,231,110,3039,15,75
+Buick,Skylark 320,15,8,350,165,3693,11.5,70
+Buick,Skylark Limited,28.4,4,151,90,2670,16,79
+Buick,Skylark,20.5,6,231,105,3425,16.9,77
+Buick,Skylark,26.6,4,151,84,2635,16.4,81
+Cadillac,Eldorado,23,8,350,125,3900,17.4,79
+Cadillac,Seville,16.5,8,350,180,4380,12.1,76
+Chevrolet,Chevelle Malibu,16,6,250,105,3897,18.5,75
+Chevrolet,Bel Air,15,8,350,145,4440,14,75
+Chevrolet,Camaro,27,4,151,90,2950,17.3,82
+Chevrolet,Caprice Classic,13,8,400,150,4464,12,73
+Chevrolet,Caprice Classic,17,8,305,130,3840,15.4,79
+Chevrolet,Caprice Classic,17.5,8,305,145,3880,12.5,77
+Chevrolet,Cavalier 2-Door,34,4,112,88,2395,18,82
+Chevrolet,Cavalier Wagon,27,4,112,88,2640,18.6,82
+Chevrolet,Cavalier,28,4,112,88,2605,19.6,82
+Chevrolet,Chevelle Concours (Wagon),NaN,8,350,165,4142,11.5,70
+Chevrolet,Chevelle Concours (Wagon),13,8,307,130,4098,14,72
+Chevrolet,Chevelle Malibu Classic,16,6,250,100,3781,17,74
+Chevrolet,Chevelle Malibu Classic,17.5,8,305,140,4215,13,76
+Chevrolet,Chevelle Malibu,17,6,250,100,3329,15.5,71
+Chevrolet,Chevelle Malibu,18,8,307,130,3504,12,70
+Chevrolet,Chevette,29,4,85,52,2035,22.2,76
+Chevrolet,Chevette,30,4,98,68,2155,16.5,78
+Chevrolet,Chevette,30.5,4,98,63,2051,17,77
+Chevrolet,Chevette,32.1,4,98,70,2120,15.5,80
+Chevrolet,Citation,23.5,6,173,110,2725,12.6,81
+Chevrolet,Citation,28,4,151,90,2678,16.5,80
+Chevrolet,Citation,28.8,6,173,115,2595,11.3,79
+Chevrolet,Concours,17.5,6,250,110,3520,16.4,77
+Chevrolet,Impala,11,8,400,150,4997,14,73
+Chevrolet,Impala,13,8,350,165,4274,12,72
+Chevrolet,Impala,14,8,350,165,4209,12,71
+Chevrolet,Impala,14,8,454,220,4354,9,70
+Chevrolet,Malibu Classic (Wagon),19.2,8,267,125,3605,15,79
+Chevrolet,Malibu,13,8,350,145,3988,13,73
+Chevrolet,Malibu,20.5,6,200,95,3155,18.2,78
+Chevrolet,Monte Carlo Landau,15.5,8,350,170,4165,11.4,77
+Chevrolet,Monte Carlo Landau,19.2,8,305,145,3425,13.2,78
+Chevrolet,Monte Carlo S,15,8,350,145,4082,13,73
+Chevrolet,Monte Carlo,15,8,400,150,3761,9.5,70
+Chevrolet,Monza 2+2,20,8,262,110,3221,13.5,75
+Chevrolet,Nova Custom,16,6,250,100,3278,18,73
+Chevrolet,Nova,15,6,250,100,3336,17,74
+Chevrolet,Nova,18,6,250,105,3459,16,75
+Chevrolet,Nova,22,6,250,105,3353,14.5,76
+Chevrolet,Vega (Wagon),22,4,140,72,2408,19,71
+Chevrolet,Vega 2300,28,4,140,90,2264,15.5,71
+Chevrolet,Vega,20,4,140,90,2408,19.5,72
+Chevrolet,Vega,21,4,140,72,2401,19.5,73
+Chevrolet,Vega,25,4,140,75,2542,17,74
+Chevrolet,Woody,24.5,4,98,60,2164,22.1,76
+Chevy,C10,13,8,350,145,4055,12,76
+Chevy,C20,10,8,307,200,4376,15,70
+Chevy,S-10,31,4,119,82,2720,19.4,82
+Chrysler,Cordoba,15.5,8,400,190,4325,12.2,77
+Chrysler,Lebaron Medallion,26,4,156,92,2585,14.5,82
+Chrysler,Lebaron Salon,17.6,6,225,85,3465,16.6,81
+Chrysler,Lebaron Town & Country (Wagon),18.5,8,360,150,3940,13,79
+Chrysler,New Yorker Brougham,13,8,440,215,4735,11,73
+Chrysler,Newport Royal,13,8,400,190,4422,12.5,72
+Citroen,DS-21 Pallas,NaN,4,133,115,3090,17.5,70
+Datsun,1200,35,4,72,69,1613,18,71
+Datsun,200SX,23.9,4,119,97,2405,14.9,78
+Datsun,200SX,32.9,4,119,100,2615,14.8,81
+Datsun,210,31.8,4,85,65,2020,19.2,79
+Datsun,210,37,4,85,65,1975,19.4,81
+Datsun,210,40.8,4,85,65,2110,19.2,80
+Datsun,280ZX,32.7,6,168,132,2910,11.4,80
+Datsun,310 GX,38,4,91,67,1995,16.2,82
+Datsun,310,37.2,4,86,65,2019,16.4,80
+Datsun,510 (Wagon),28,4,97,92,2288,17,72
+Datsun,510 Hatchback,37,4,119,92,2434,15,80
+Datsun,510,27.2,4,119,97,2300,14.7,78
+Datsun,610,22,4,108,94,2379,16.5,73
+Datsun,710,24,4,119,97,2545,17,75
+Datsun,710,32,4,83,61,2003,19,74
+Datsun,810 Maxima,24.2,6,146,120,2930,13.8,81
+Datsun,810,22,6,146,97,2815,14.5,77
+Datsun,B-210,32,4,85,70,1990,17,76
+Datsun,B210 GX,39.4,4,85,70,2070,18.6,78
+Datsun,B210,31,4,79,67,1950,19,74
+Datsun,F-10 Hatchback,33.5,4,85,70,1945,16.8,77
+Datsun,PL510,27,4,97,88,2130,14.5,70
+Datsun,PL510,27,4,97,88,2130,14.5,71
+Dodge,Aries SE,29,4,135,84,2525,16,82
+Dodge,Aries Wagon (Wagon),25.8,4,156,92,2620,14.4,81
+Dodge,Aspen 6,20.6,6,225,110,3360,16.6,79
+Dodge,Aspen SE,20,6,225,100,3651,17.7,76
+Dodge,Aspen,18.6,6,225,110,3620,18.7,78
+Dodge,Aspen,19.1,6,225,90,3381,18.7,80
+Dodge,Challenger SE,15,8,383,170,3563,10,70
+Dodge,Charger 2.2,36,4,135,84,2370,13,82
+Dodge,Colt (Wagon),28,4,98,80,2164,15,72
+Dodge,Colt Hardtop,25,4,97.5,80,2126,17,72
+Dodge,Colt Hatchback Custom,35.7,4,98,80,1915,14.4,79
+Dodge,Colt M/M,33.5,4,98,83,2075,15.9,77
+Dodge,Colt,26,4,98,79,2255,17.7,76
+Dodge,Colt,27.9,4,156,105,2800,14.4,80
+Dodge,Colt,28,4,90,75,2125,14.5,74
+Dodge,Coronet Brougham,16,8,318,150,4190,13,76
+Dodge,Coronet Custom (Wagon),14,8,318,150,4457,13.5,74
+Dodge,Coronet Custom,15,8,318,150,3777,12.5,73
+Dodge,D100,13,8,318,150,3755,14,76
+Dodge,D200,11,8,318,210,4382,13.5,70
+Dodge,Dart Custom,15,8,318,150,3399,11,73
+Dodge,Diplomat,19.4,8,318,140,3735,13.2,78
+Dodge,Magnum XE,17.5,8,318,140,4080,13.7,78
+Dodge,Monaco (Wagon),12,8,383,180,4955,11.5,71
+Dodge,Monaco Brougham,15.5,8,318,145,4140,13.7,77
+Dodge,Omni,30.9,4,105,75,2230,14.5,78
+Dodge,Rampage,32,4,135,84,2295,11.6,82
+Dodge,St. Regis,18.2,8,318,135,3830,15.2,79
+Fiat,124 Sport Coupe,26,4,98,90,2265,15.5,73
+Fiat,124 TC,26,4,116,75,2246,14,74
+Fiat,124B,30,4,88,76,2065,14.5,71
+Fiat,128,24,4,90,75,2108,15.5,74
+Fiat,128,29,4,68,49,1867,19.5,73
+Fiat,131,28,4,107,86,2464,15.5,76
+Fiat,Strada Custom,37.3,4,91,69,2130,14.7,79
+Fiat,X1.9,31,4,79,67,2000,16,74
+Ford,Capri II,25,4,140,92,2572,14.9,76
+Ford,Country Squire (Wagon),13,8,400,170,4746,12,71
+Ford,Country Squire (Wagon),15.5,8,351,142,4054,14.3,79
+Ford,Country,12,8,400,167,4906,12.5,73
+Ford,Escort 2H,29.9,4,98,65,2380,20.7,81
+Ford,Escort 4W,34.4,4,98,65,2045,16.2,81
+Ford,F108,13,8,302,130,3870,15,76
+Ford,F250,10,8,360,215,4615,14,70
+Ford,Fairmont (Auto),20.2,6,200,85,2965,15.8,78
+Ford,Fairmont (Man),25.1,4,140,88,2720,15.4,78
+Ford,Fairmont 4,22.3,4,140,88,2890,17.3,79
+Ford,Fairmont Futura,24,4,140,92,2865,16.4,82
+Ford,Fairmont,26.4,4,140,88,2870,18.1,80
+Ford,Fiesta,36.1,4,98,66,1800,14.4,78
+Ford,Futura,18.1,8,302,139,3205,11.2,78
+Ford,Galaxie 500,14,8,351,153,4129,13,72
+Ford,Galaxie 500,14,8,351,153,4154,13.5,71
+Ford,Galaxie 500,15,8,429,198,4341,10,70
+Ford,Gran Torino (Wagon),13,8,302,140,4294,16,72
+Ford,Gran Torino (Wagon),14,8,302,140,4638,16,74
+Ford,Gran Torino,14,8,302,137,4042,14.5,73
+Ford,Gran Torino,14.5,8,351,152,4215,12.8,76
+Ford,Gran Torino,16,8,302,140,4141,14,74
+Ford,Granada Ghia,18,6,250,78,3574,21,76
+Ford,Granada GL,20.2,6,200,88,3060,17.1,81
+Ford,Granada L,22,6,232,112,2835,14.7,82
+Ford,Granada,18.5,6,250,98,3525,19,77
+Ford,LTD Landau,17.6,8,302,129,3725,13.4,79
+Ford,LTD,13,8,351,158,4363,13,73
+Ford,LTD,14,8,351,148,4657,13.5,75
+Ford,Maverick,15,6,250,72,3158,19.5,75
+Ford,Maverick,18,6,250,88,3021,16.5,73
+Ford,Maverick,21,6,200,NaN,2875,17,74
+Ford,Maverick,21,6,200,85,2587,16,70
+Ford,Maverick,24,6,200,81,3012,17.6,76
+Ford,Mustang Boss 302,NaN,8,302,140,3353,8,70
+Ford,Mustang Cobra,23.6,4,140,NaN,2905,14.3,80
+Ford,Mustang GL,27,4,140,86,2790,15.6,82
+Ford,Mustang II 2+2,25.5,4,140,89,2755,15.8,77
+Ford,Mustang II,13,8,302,129,3169,12,75
+Ford,Mustang,18,6,250,88,3139,14.5,71
+Ford,Pinto (Wagon),22,4,122,86,2395,16,72
+Ford,Pinto Runabout,21,4,122,86,2226,16.5,72
+Ford,Pinto,18,6,171,97,2984,14.5,75
+Ford,Pinto,19,4,122,85,2310,18.5,73
+Ford,Pinto,23,4,140,83,2639,17,75
+Ford,Pinto,25,4,98,NaN,2046,19,71
+Ford,Pinto,26,4,122,80,2451,16.5,74
+Ford,Pinto,26.5,4,140,72,2565,13.6,76
+Ford,Ranger,28,4,120,79,2625,18.6,82
+Ford,Thunderbird,16,8,351,149,4335,14.5,77
+Ford,Torino (Wagon),NaN,8,351,153,4034,11,70
+Ford,Torino 500,19,6,250,88,3302,15.5,71
+Ford,Torino,17,8,302,140,3449,10.5,70
+Hi,1200D,9,8,304,193,4732,18.5,70
+Honda,Accord CVCC,31.5,4,98,68,2045,18.5,77
+Honda,Accord LX,29.5,4,98,68,2135,16.6,78
+Honda,Accord,32.4,4,107,72,2290,17,80
+Honda,Accord,36,4,107,75,2205,14.5,82
+Honda,Civic (Auto),32,4,91,67,1965,15.7,82
+Honda,Civic 1300,35.1,4,81,60,1760,16.1,81
+Honda,Civic 1500 GL,44.6,4,91,67,1850,13.8,80
+Honda,Civic CVCC,33,4,91,53,1795,17.5,75
+Honda,Civic CVCC,36.1,4,91,60,1800,16.4,78
+Honda,Civic,24,4,120,97,2489,15,74
+Honda,Civic,33,4,91,53,1795,17.4,76
+Honda,Civic,38,4,91,67,1965,15,82
+Honda,Prelude,33.7,4,107,75,2210,14.4,81
+Mazda,GLC Deluxe,34.1,4,86,65,1975,15.2,79
+Mazda,RX-3,18,3,70,90,2124,13.5,73
+Mazda,626,31.3,4,120,75,2542,17.5,80
+Mazda,626,31.6,4,120,74,2635,18.3,81
+Mazda,GLC 4,34.1,4,91,68,1985,16,81
+Mazda,GLC Custom L,37,4,91,68,2025,18.2,82
+Mazda,GLC Custom,31,4,91,68,1970,17.6,82
+Mazda,GLC Deluxe,32.8,4,78,52,1985,19.4,78
+Mazda,GLC,46.6,4,86,65,2110,17.9,80
+Mazda,RX-2 Coupe,19,3,70,97,2330,13.5,72
+Mazda,RX-4,21.5,3,80,110,2720,13.5,77
+Mazda,RX-7 Gs,23.7,3,70,100,2420,12.5,80
+Mercedes-Benz,240D,30,4,146,67,3250,21.8,80
+Mercedes-Benz,280S,16.5,6,168,120,3820,16.7,76
+Mercedes-Benz,300D,25.4,5,183,77,3530,20.1,79
+Mercury,Capri 2000,23,4,122,86,2220,14,71
+Mercury,Capri V6,21,6,155,107,2472,14,73
+Mercury,Cougar Brougham,15,8,302,130,4295,14.9,77
+Mercury,Grand Marquis,16.5,8,351,138,3955,13.2,79
+Mercury,Lynx L,36,4,98,70,2125,17.3,82
+Mercury,Marquis Brougham,12,8,429,198,4952,11.5,73
+Mercury,Marquis,11,8,429,208,4633,11,72
+Mercury,Monarch Ghia,20.2,8,302,139,3570,12.8,78
+Mercury,Monarch,15,6,250,72,3432,21,75
+Mercury,Zephyr 6,19.8,6,200,85,2990,18.2,79
+Mercury,Zephyr,20.8,6,200,85,3070,16.7,78
+Nissan,Stanza XE,36,4,120,88,2160,14.5,82
+Oldsmobile,Cutlass Ciera (Diesel),38,6,262,85,3015,17,82
+Oldsmobile,Cutlass LS,26.6,8,350,105,3725,19,81
+Oldsmobile,Cutlass Salon Brougham,19.9,8,260,110,3365,15.5,78
+Oldsmobile,Cutlass Salon Brougham,23.9,8,260,90,3420,22.2,79
+Oldsmobile,Cutlass Supreme,17,8,260,110,4060,19,77
+Oldsmobile,Delta 88 Royale,12,8,350,160,4456,13.5,72
+Oldsmobile,Omega Brougham,26.8,6,173,115,2700,12.9,79
+Oldsmobile,Omega,11,8,350,180,3664,11,73
+Oldsmobile,Starfire SX,23.8,4,151,85,2855,17.6,78
+Oldsmobile,Vista Cruiser,12,8,350,180,4499,12.5,73
+Opel,1900,25,4,116,81,2220,16.9,76
+Opel,1900,28,4,116,90,2123,14,71
+Opel,Manta,24,4,116,75,2158,15.5,73
+Opel,Manta,26,4,97,78,2300,14.5,74
+Peugeot,304,30,4,79,70,2074,19.5,71
+Peugeot,504 (Wagon),21,4,120,87,2979,19.5,72
+Peugeot,504,19,4,120,88,3270,21.9,76
+Peugeot,504,23,4,120,88,2957,17,75
+Peugeot,504,25,4,110,87,2672,17.5,70
+Peugeot,504,27.2,4,141,71,3190,24.8,79
+Peugeot,505S Turbo Diesel,28.1,4,141,80,3230,20.4,81
+Peugeot,604SL,16.2,6,163,133,3410,15.8,78
+Plymouth,Arrow GS,25.5,4,122,96,2300,15.5,77
+Plymouth,Barracuda 340,14,8,340,160,3609,8,70
+Plymouth,Champ,39,4,86,64,1875,16.4,81
+Plymouth,Cricket,26,4,91,70,1955,20.5,71
+Plymouth,Custom Suburb,13,8,360,170,4654,13,73
+Plymouth,Duster,20,6,198,95,3102,16.5,74
+Plymouth,Duster,22,6,198,95,2833,15.5,70
+Plymouth,Duster,23,6,198,95,2904,16,73
+Plymouth,Fury Gran Sedan,14,8,318,150,4237,14.5,73
+Plymouth,Fury III,14,8,318,150,4096,13,71
+Plymouth,Fury III,14,8,440,215,4312,8.5,70
+Plymouth,Fury III,15,8,318,150,4135,13.5,72
+Plymouth,Fury,18,6,225,95,3785,19,75
+Plymouth,Grand Fury,16,8,318,150,4498,14.5,75
+Plymouth,Horizon 4,34.7,4,105,63,2215,14.9,81
+Plymouth,Horizon Miser,38,4,105,63,2125,14.7,82
+Plymouth,Horizon TC3,34.5,4,105,70,2150,14.9,79
+Plymouth,Horizon,34.2,4,105,70,2200,13.2,79
+Plymouth,Reliant,27.2,4,135,84,2490,15.7,81
+Plymouth,Reliant,30,4,135,84,2385,12.9,81
+Plymouth,Sapporo,23.2,4,156,105,2745,16.7,78
+Plymouth,Satellite (Wagon),NaN,8,383,175,4166,10.5,70
+Plymouth,Satellite Custom (Wagon),14,8,318,150,4077,14,72
+Plymouth,Satellite Custom,16,6,225,105,3439,15.5,71
+Plymouth,Satellite Sebring,18,6,225,105,3613,16.5,74
+Plymouth,Satellite,18,8,318,150,3436,11,70
+Plymouth,Valiant Custom,19,6,225,95,3264,16,75
+Plymouth,Valiant,18,6,225,105,3121,16.5,73
+Plymouth,Valiant,22,6,225,100,3233,15.4,76
+Plymouth,Volare Custom,19,6,225,100,3630,17.7,77
+Plymouth,Volare Premier V8,13,8,318,150,3940,13.2,76
+Plymouth,Volare,20.5,6,225,100,3430,17.2,78
+Pontiac,Astro,23,4,140,78,2592,18.5,75
+Pontiac,Catalina Brougham,14,8,400,175,4464,11.5,71
+Pontiac,Catalina,14,8,400,175,4385,12,72
+Pontiac,Catalina,14,8,455,225,4425,10,70
+Pontiac,Catalina,16,8,400,170,4668,11.5,75
+Pontiac,Firebird,19,6,250,100,3282,15,71
+Pontiac,Grand Prix Lj,16,8,400,180,4220,11.1,77
+Pontiac,Grand Prix,16,8,400,230,4278,9.5,73
+Pontiac,J2000 Se Hatchback,31,4,112,85,2575,16.2,82
+Pontiac,Lemans V6,21.5,6,231,115,3245,15.4,79
+Pontiac,Phoenix LJ,19.2,6,231,105,3535,19.2,78
+Pontiac,Phoenix,27,4,151,90,2735,18,82
+Pontiac,Phoenix,33.5,4,151,90,2556,13.2,79
+Pontiac,Safari (Wagon),13,8,400,175,5140,12,71
+Pontiac,Sunbird Coupe,24.5,4,151,88,2740,16,77
+Pontiac,Ventura Sj,18.5,6,250,110,3645,16.2,76
+Renault,12 (Wagon),26,4,96,69,2189,18,72
+Renault,12TL,27,4,101,83,2202,15.3,76
+Renault,18I,34.5,4,100,NaN,2320,15.8,81
+Renault,5 Gtl,36,4,79,58,1825,18.6,77
+Renault,Lecar Deluxe,40.9,4,85,NaN,1835,17.3,80
+Saab,900S,NaN,4,121,110,2800,15.4,81
+Saab,99E,25,4,104,95,2375,17.5,70
+Saab,99GLE,21.6,4,121,115,2795,15.7,78
+Saab,99LE,24,4,121,110,2660,14,73
+Saab,99LE,25,4,121,115,2671,13.5,75
+Subaru,DL,30,4,97,67,1985,16.4,77
+Subaru,DL,33.8,4,97,67,2145,18,80
+Subaru,,26,4,108,93,2391,15.5,74
+Subaru,,32.3,4,97,67,2065,17.8,81
+Toyota,Carina,20,4,97,88,2279,19,73
+Toyota,Celica GT Liftback,21.1,4,134,95,2515,14.8,78
+Toyota,Celica GT,32,4,144,96,2665,13.9,82
+Toyota,Corolla 1200,31,4,71,65,1773,19,71
+Toyota,Corolla 1200,32,4,71,65,1836,21,74
+Toyota,Corolla 1600 (Wagon),27,4,97,88,2100,16.5,72
+Toyota,Corolla Liftback,26,4,97,75,2265,18.2,77
+Toyota,Corolla Tercel,38.1,4,89,60,1968,18.8,80
+Toyota,Corolla,28,4,97,75,2155,16.4,76
+Toyota,Corolla,29,4,97,75,2171,16,75
+Toyota,Corolla,32.2,4,108,75,2265,15.2,80
+Toyota,Corolla,32.4,4,108,75,2350,16.8,81
+Toyota,Corolla,34,4,108,70,2245,16.9,82
+Toyota,Corona Hardtop,24,4,113,95,2278,15.5,72
+Toyota,Corona Liftback,29.8,4,134,90,2711,15.5,80
+Toyota,Corona Mark II,24,4,113,95,2372,15,70
+Toyota,Corona,24,4,134,96,2702,13.5,75
+Toyota,Corona,25,4,113,95,2228,14,71
+Toyota,Corona,27.5,4,134,95,2560,14.2,78
+Toyota,Corona,31,4,76,52,1649,16.5,74
+Toyota,Cressida,25.4,6,168,116,2900,12.6,81
+Toyota,Mark II,19,6,156,108,2930,15.5,76
+Toyota,Mark II,20,6,156,122,2807,13.5,73
+Toyota,Starlet,39.1,4,79,58,1755,16.9,81
+Toyota,Tercel,37.7,4,89,62,2050,17.3,81
+Toyota,Corona Mark II (Wagon),23,4,120,97,2506,14.5,72
+Triumph,TR7 Coupe,35,4,122,88,2500,15.1,80
+Volkswagen,Rabbit,29.8,4,89,62,1845,15.3,80
+Volkswagen,1131 Deluxe Sedan,26,4,97,46,1835,20.5,70
+Volkswagen,411 (Wagon),22,4,121,76,2511,18,72
+Volkswagen,Dasher (Diesel),43.4,4,90,48,2335,23.7,80
+Volkswagen,Dasher,25,4,90,71,2223,16.5,75
+Volkswagen,Dasher,26,4,79,67,1963,15.5,74
+Volkswagen,Dasher,30.5,4,97,78,2190,14.1,77
+Volkswagen,Jetta,33,4,105,74,2190,14.2,81
+Volkswagen,Model 111,27,4,97,60,1834,19,71
+Volkswagen,Pickup,44,4,97,52,2130,24.6,82
+Volkswagen,Rabbit C (Diesel),44.3,4,90,48,2085,21.7,80
+Volkswagen,Rabbit Custom Diesel,43.1,4,90,48,1985,21.5,78
+Volkswagen,Rabbit Custom,29,4,97,78,1940,14.5,77
+Volkswagen,Rabbit Custom,31.9,4,89,71,1925,14,79
+Volkswagen,Rabbit L,36,4,105,74,1980,15.3,82
+Volkswagen,Rabbit,29,4,90,70,1937,14,75
+Volkswagen,Rabbit,29,4,90,70,1937,14.2,76
+Volkswagen,Rabbit,29.5,4,97,71,1825,12.2,76
+Volkswagen,Rabbit,41.5,4,98,76,2144,14.7,80
+Volkswagen,Scirocco,31.5,4,89,71,1990,14.9,78
+Volkswagen,Super Beetle 117,NaN,4,97,48,1978,20,71
+Volkswagen,Super Beetle,26,4,97,46,1950,21,73
+Volkswagen,Type 3,23,4,97,54,2254,23.5,72
+Volvo,144EA,19,4,121,112,2868,15.5,73
+Volvo,145E (Wagon),18,4,121,112,2933,14.5,72
+Volvo,244DL,22,4,121,98,2945,14.5,75
+Volvo,245,20,4,130,102,3150,15.7,76
+Volvo,264GL,17,6,163,125,3140,13.6,78
+Volvo,Diesel,30.7,6,145,76,3160,19.6,81
\ No newline at end of file
diff --git a/src/content/tutorial/01-basics/03-transforms/06-jitter/01-jitter/+assets/app-a/src/lib/App.svelte b/src/content/tutorial/01-basics/03-transforms/06-jitter/01-jitter/+assets/app-a/src/lib/App.svelte
new file mode 100644
index 000000000..6ee7fa298
--- /dev/null
+++ b/src/content/tutorial/01-basics/03-transforms/06-jitter/01-jitter/+assets/app-a/src/lib/App.svelte
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/03-transforms/06-jitter/01-jitter/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/01-basics/03-transforms/06-jitter/01-jitter/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..6e91faf2d
--- /dev/null
+++ b/src/content/tutorial/01-basics/03-transforms/06-jitter/01-jitter/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/03-transforms/06-jitter/01-jitter/index.md b/src/content/tutorial/01-basics/03-transforms/06-jitter/01-jitter/index.md
new file mode 100644
index 000000000..47024b88f
--- /dev/null
+++ b/src/content/tutorial/01-basics/03-transforms/06-jitter/01-jitter/index.md
@@ -0,0 +1,27 @@
+---
+title: Jitter transform
+---
+
+When x values are discrete integers — like cylinder counts — points stack exactly on top of each other. Opacity helps a little, but you still can't tell how many points share a position.
+
+The `jitterX` transform adds a small random horizontal nudge to each point. Import it alongside your marks:
+
+```svelte
+---import { Plot, Dot } from 'svelteplot';---
++++import { Plot, Dot, jitterX } from 'svelteplot';+++
+```
+
+Compute the jittered channels in a `$derived` block, then spread the result onto ``:
+
+```svelte
++++const jittered = $derived(
+ jitterX({ data, x: 'cylinders', y: 'power (hp)' }, { type: 'uniform', width: 0.45 })
+);+++
+```
+
+```svelte
+------
+++++++
+```
+
+The `width` option controls how far points can spread (in data units — here ±0.45 cylinders). Only the positional channels go through the transform; any other props like `opacity` are passed directly to ``.
diff --git a/src/content/tutorial/01-basics/03-transforms/06-jitter/02-reactive-jitter/+assets/app-a/src/lib/App.svelte b/src/content/tutorial/01-basics/03-transforms/06-jitter/02-reactive-jitter/+assets/app-a/src/lib/App.svelte
new file mode 100644
index 000000000..6e91faf2d
--- /dev/null
+++ b/src/content/tutorial/01-basics/03-transforms/06-jitter/02-reactive-jitter/+assets/app-a/src/lib/App.svelte
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/03-transforms/06-jitter/02-reactive-jitter/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/01-basics/03-transforms/06-jitter/02-reactive-jitter/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..1cb8a0372
--- /dev/null
+++ b/src/content/tutorial/01-basics/03-transforms/06-jitter/02-reactive-jitter/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/03-transforms/06-jitter/02-reactive-jitter/index.md b/src/content/tutorial/01-basics/03-transforms/06-jitter/02-reactive-jitter/index.md
new file mode 100644
index 000000000..b8b4f019a
--- /dev/null
+++ b/src/content/tutorial/01-basics/03-transforms/06-jitter/02-reactive-jitter/index.md
@@ -0,0 +1,26 @@
+---
+title: Reactive jitter
+---
+
+Because `$derived` re-runs whenever its dependencies change, any reactive variable you reference inside the transform will update the chart automatically.
+
+Add a `$state` variable for the jitter width and wire it to a range input:
+
+```svelte
++++let width = $state(0.45);+++
+```
+
+```svelte
+const jittered = $derived(
+--- jitterX({ data, x: 'cylinders', y: 'power (hp)' }, { type: 'uniform', width: 0.45 })---
++++ jitterX({ data, x: 'cylinders', y: 'power (hp)' }, { type: 'uniform', width })+++
+);
+```
+
+```svelte
+++++++
+```
+
+Move the slider to control how spread out the jitter is. Since `$derived` calls `Math.random` anew on every re-run, the dot positions also reshuffle each time — the jitter is never the same twice.
diff --git a/src/content/tutorial/01-basics/03-transforms/06-jitter/index.md b/src/content/tutorial/01-basics/03-transforms/06-jitter/index.md
new file mode 100644
index 000000000..f44f2d143
--- /dev/null
+++ b/src/content/tutorial/01-basics/03-transforms/06-jitter/index.md
@@ -0,0 +1,5 @@
+---
+title: Jitter
+scope: { 'prefix': '/src/lib/', 'name': 'src' }
+focus: /src/lib/App.svelte
+---
diff --git a/src/content/tutorial/01-basics/03-transforms/index.md b/src/content/tutorial/01-basics/03-transforms/index.md
new file mode 100644
index 000000000..cb1103a53
--- /dev/null
+++ b/src/content/tutorial/01-basics/03-transforms/index.md
@@ -0,0 +1,5 @@
+---
+title: Transforms
+scope: { 'prefix': '/src/lib/', 'name': 'src' }
+focus: /src/lib/App.svelte
+---
diff --git a/src/content/tutorial/01-basics/05-axes-grids/+assets/src/lib/penguins.csv b/src/content/tutorial/01-basics/05-axes-grids/+assets/src/lib/penguins.csv
new file mode 100644
index 000000000..83f32630b
--- /dev/null
+++ b/src/content/tutorial/01-basics/05-axes-grids/+assets/src/lib/penguins.csv
@@ -0,0 +1,345 @@
+species,island,bill_length_mm,bill_depth_mm,flipper_length_mm,body_mass_g,sex
+Adelie,Torgersen,39.1,18.7,181,3750,MALE
+Adelie,Torgersen,39.5,17.4,186,3800,FEMALE
+Adelie,Torgersen,40.3,18,195,3250,FEMALE
+Adelie,Torgersen,NaN,NaN,NaN,NaN,
+Adelie,Torgersen,36.7,19.3,193,3450,FEMALE
+Adelie,Torgersen,39.3,20.6,190,3650,MALE
+Adelie,Torgersen,38.9,17.8,181,3625,FEMALE
+Adelie,Torgersen,39.2,19.6,195,4675,MALE
+Adelie,Torgersen,34.1,18.1,193,3475,
+Adelie,Torgersen,42,20.2,190,4250,
+Adelie,Torgersen,37.8,17.1,186,3300,
+Adelie,Torgersen,37.8,17.3,180,3700,
+Adelie,Torgersen,41.1,17.6,182,3200,FEMALE
+Adelie,Torgersen,38.6,21.2,191,3800,MALE
+Adelie,Torgersen,34.6,21.1,198,4400,MALE
+Adelie,Torgersen,36.6,17.8,185,3700,FEMALE
+Adelie,Torgersen,38.7,19,195,3450,FEMALE
+Adelie,Torgersen,42.5,20.7,197,4500,MALE
+Adelie,Torgersen,34.4,18.4,184,3325,FEMALE
+Adelie,Torgersen,46,21.5,194,4200,MALE
+Adelie,Biscoe,37.8,18.3,174,3400,FEMALE
+Adelie,Biscoe,37.7,18.7,180,3600,MALE
+Adelie,Biscoe,35.9,19.2,189,3800,FEMALE
+Adelie,Biscoe,38.2,18.1,185,3950,MALE
+Adelie,Biscoe,38.8,17.2,180,3800,MALE
+Adelie,Biscoe,35.3,18.9,187,3800,FEMALE
+Adelie,Biscoe,40.6,18.6,183,3550,MALE
+Adelie,Biscoe,40.5,17.9,187,3200,FEMALE
+Adelie,Biscoe,37.9,18.6,172,3150,FEMALE
+Adelie,Biscoe,40.5,18.9,180,3950,MALE
+Adelie,Dream,39.5,16.7,178,3250,FEMALE
+Adelie,Dream,37.2,18.1,178,3900,MALE
+Adelie,Dream,39.5,17.8,188,3300,FEMALE
+Adelie,Dream,40.9,18.9,184,3900,MALE
+Adelie,Dream,36.4,17,195,3325,FEMALE
+Adelie,Dream,39.2,21.1,196,4150,MALE
+Adelie,Dream,38.8,20,190,3950,MALE
+Adelie,Dream,42.2,18.5,180,3550,FEMALE
+Adelie,Dream,37.6,19.3,181,3300,FEMALE
+Adelie,Dream,39.8,19.1,184,4650,MALE
+Adelie,Dream,36.5,18,182,3150,FEMALE
+Adelie,Dream,40.8,18.4,195,3900,MALE
+Adelie,Dream,36,18.5,186,3100,FEMALE
+Adelie,Dream,44.1,19.7,196,4400,MALE
+Adelie,Dream,37,16.9,185,3000,FEMALE
+Adelie,Dream,39.6,18.8,190,4600,MALE
+Adelie,Dream,41.1,19,182,3425,MALE
+Adelie,Dream,37.5,18.9,179,2975,
+Adelie,Dream,36,17.9,190,3450,FEMALE
+Adelie,Dream,42.3,21.2,191,4150,MALE
+Adelie,Biscoe,39.6,17.7,186,3500,FEMALE
+Adelie,Biscoe,40.1,18.9,188,4300,MALE
+Adelie,Biscoe,35,17.9,190,3450,FEMALE
+Adelie,Biscoe,42,19.5,200,4050,MALE
+Adelie,Biscoe,34.5,18.1,187,2900,FEMALE
+Adelie,Biscoe,41.4,18.6,191,3700,MALE
+Adelie,Biscoe,39,17.5,186,3550,FEMALE
+Adelie,Biscoe,40.6,18.8,193,3800,MALE
+Adelie,Biscoe,36.5,16.6,181,2850,FEMALE
+Adelie,Biscoe,37.6,19.1,194,3750,MALE
+Adelie,Biscoe,35.7,16.9,185,3150,FEMALE
+Adelie,Biscoe,41.3,21.1,195,4400,MALE
+Adelie,Biscoe,37.6,17,185,3600,FEMALE
+Adelie,Biscoe,41.1,18.2,192,4050,MALE
+Adelie,Biscoe,36.4,17.1,184,2850,FEMALE
+Adelie,Biscoe,41.6,18,192,3950,MALE
+Adelie,Biscoe,35.5,16.2,195,3350,FEMALE
+Adelie,Biscoe,41.1,19.1,188,4100,MALE
+Adelie,Torgersen,35.9,16.6,190,3050,FEMALE
+Adelie,Torgersen,41.8,19.4,198,4450,MALE
+Adelie,Torgersen,33.5,19,190,3600,FEMALE
+Adelie,Torgersen,39.7,18.4,190,3900,MALE
+Adelie,Torgersen,39.6,17.2,196,3550,FEMALE
+Adelie,Torgersen,45.8,18.9,197,4150,MALE
+Adelie,Torgersen,35.5,17.5,190,3700,FEMALE
+Adelie,Torgersen,42.8,18.5,195,4250,MALE
+Adelie,Torgersen,40.9,16.8,191,3700,FEMALE
+Adelie,Torgersen,37.2,19.4,184,3900,MALE
+Adelie,Torgersen,36.2,16.1,187,3550,FEMALE
+Adelie,Torgersen,42.1,19.1,195,4000,MALE
+Adelie,Torgersen,34.6,17.2,189,3200,FEMALE
+Adelie,Torgersen,42.9,17.6,196,4700,MALE
+Adelie,Torgersen,36.7,18.8,187,3800,FEMALE
+Adelie,Torgersen,35.1,19.4,193,4200,MALE
+Adelie,Dream,37.3,17.8,191,3350,FEMALE
+Adelie,Dream,41.3,20.3,194,3550,MALE
+Adelie,Dream,36.3,19.5,190,3800,MALE
+Adelie,Dream,36.9,18.6,189,3500,FEMALE
+Adelie,Dream,38.3,19.2,189,3950,MALE
+Adelie,Dream,38.9,18.8,190,3600,FEMALE
+Adelie,Dream,35.7,18,202,3550,FEMALE
+Adelie,Dream,41.1,18.1,205,4300,MALE
+Adelie,Dream,34,17.1,185,3400,FEMALE
+Adelie,Dream,39.6,18.1,186,4450,MALE
+Adelie,Dream,36.2,17.3,187,3300,FEMALE
+Adelie,Dream,40.8,18.9,208,4300,MALE
+Adelie,Dream,38.1,18.6,190,3700,FEMALE
+Adelie,Dream,40.3,18.5,196,4350,MALE
+Adelie,Dream,33.1,16.1,178,2900,FEMALE
+Adelie,Dream,43.2,18.5,192,4100,MALE
+Adelie,Biscoe,35,17.9,192,3725,FEMALE
+Adelie,Biscoe,41,20,203,4725,MALE
+Adelie,Biscoe,37.7,16,183,3075,FEMALE
+Adelie,Biscoe,37.8,20,190,4250,MALE
+Adelie,Biscoe,37.9,18.6,193,2925,FEMALE
+Adelie,Biscoe,39.7,18.9,184,3550,MALE
+Adelie,Biscoe,38.6,17.2,199,3750,FEMALE
+Adelie,Biscoe,38.2,20,190,3900,MALE
+Adelie,Biscoe,38.1,17,181,3175,FEMALE
+Adelie,Biscoe,43.2,19,197,4775,MALE
+Adelie,Biscoe,38.1,16.5,198,3825,FEMALE
+Adelie,Biscoe,45.6,20.3,191,4600,MALE
+Adelie,Biscoe,39.7,17.7,193,3200,FEMALE
+Adelie,Biscoe,42.2,19.5,197,4275,MALE
+Adelie,Biscoe,39.6,20.7,191,3900,FEMALE
+Adelie,Biscoe,42.7,18.3,196,4075,MALE
+Adelie,Torgersen,38.6,17,188,2900,FEMALE
+Adelie,Torgersen,37.3,20.5,199,3775,MALE
+Adelie,Torgersen,35.7,17,189,3350,FEMALE
+Adelie,Torgersen,41.1,18.6,189,3325,MALE
+Adelie,Torgersen,36.2,17.2,187,3150,FEMALE
+Adelie,Torgersen,37.7,19.8,198,3500,MALE
+Adelie,Torgersen,40.2,17,176,3450,FEMALE
+Adelie,Torgersen,41.4,18.5,202,3875,MALE
+Adelie,Torgersen,35.2,15.9,186,3050,FEMALE
+Adelie,Torgersen,40.6,19,199,4000,MALE
+Adelie,Torgersen,38.8,17.6,191,3275,FEMALE
+Adelie,Torgersen,41.5,18.3,195,4300,MALE
+Adelie,Torgersen,39,17.1,191,3050,FEMALE
+Adelie,Torgersen,44.1,18,210,4000,MALE
+Adelie,Torgersen,38.5,17.9,190,3325,FEMALE
+Adelie,Torgersen,43.1,19.2,197,3500,MALE
+Adelie,Dream,36.8,18.5,193,3500,FEMALE
+Adelie,Dream,37.5,18.5,199,4475,MALE
+Adelie,Dream,38.1,17.6,187,3425,FEMALE
+Adelie,Dream,41.1,17.5,190,3900,MALE
+Adelie,Dream,35.6,17.5,191,3175,FEMALE
+Adelie,Dream,40.2,20.1,200,3975,MALE
+Adelie,Dream,37,16.5,185,3400,FEMALE
+Adelie,Dream,39.7,17.9,193,4250,MALE
+Adelie,Dream,40.2,17.1,193,3400,FEMALE
+Adelie,Dream,40.6,17.2,187,3475,MALE
+Adelie,Dream,32.1,15.5,188,3050,FEMALE
+Adelie,Dream,40.7,17,190,3725,MALE
+Adelie,Dream,37.3,16.8,192,3000,FEMALE
+Adelie,Dream,39,18.7,185,3650,MALE
+Adelie,Dream,39.2,18.6,190,4250,MALE
+Adelie,Dream,36.6,18.4,184,3475,FEMALE
+Adelie,Dream,36,17.8,195,3450,FEMALE
+Adelie,Dream,37.8,18.1,193,3750,MALE
+Adelie,Dream,36,17.1,187,3700,FEMALE
+Adelie,Dream,41.5,18.5,201,4000,MALE
+Chinstrap,Dream,46.5,17.9,192,3500,FEMALE
+Chinstrap,Dream,50,19.5,196,3900,MALE
+Chinstrap,Dream,51.3,19.2,193,3650,MALE
+Chinstrap,Dream,45.4,18.7,188,3525,FEMALE
+Chinstrap,Dream,52.7,19.8,197,3725,MALE
+Chinstrap,Dream,45.2,17.8,198,3950,FEMALE
+Chinstrap,Dream,46.1,18.2,178,3250,FEMALE
+Chinstrap,Dream,51.3,18.2,197,3750,MALE
+Chinstrap,Dream,46,18.9,195,4150,FEMALE
+Chinstrap,Dream,51.3,19.9,198,3700,MALE
+Chinstrap,Dream,46.6,17.8,193,3800,FEMALE
+Chinstrap,Dream,51.7,20.3,194,3775,MALE
+Chinstrap,Dream,47,17.3,185,3700,FEMALE
+Chinstrap,Dream,52,18.1,201,4050,MALE
+Chinstrap,Dream,45.9,17.1,190,3575,FEMALE
+Chinstrap,Dream,50.5,19.6,201,4050,MALE
+Chinstrap,Dream,50.3,20,197,3300,MALE
+Chinstrap,Dream,58,17.8,181,3700,FEMALE
+Chinstrap,Dream,46.4,18.6,190,3450,FEMALE
+Chinstrap,Dream,49.2,18.2,195,4400,MALE
+Chinstrap,Dream,42.4,17.3,181,3600,FEMALE
+Chinstrap,Dream,48.5,17.5,191,3400,MALE
+Chinstrap,Dream,43.2,16.6,187,2900,FEMALE
+Chinstrap,Dream,50.6,19.4,193,3800,MALE
+Chinstrap,Dream,46.7,17.9,195,3300,FEMALE
+Chinstrap,Dream,52,19,197,4150,MALE
+Chinstrap,Dream,50.5,18.4,200,3400,FEMALE
+Chinstrap,Dream,49.5,19,200,3800,MALE
+Chinstrap,Dream,46.4,17.8,191,3700,FEMALE
+Chinstrap,Dream,52.8,20,205,4550,MALE
+Chinstrap,Dream,40.9,16.6,187,3200,FEMALE
+Chinstrap,Dream,54.2,20.8,201,4300,MALE
+Chinstrap,Dream,42.5,16.7,187,3350,FEMALE
+Chinstrap,Dream,51,18.8,203,4100,MALE
+Chinstrap,Dream,49.7,18.6,195,3600,MALE
+Chinstrap,Dream,47.5,16.8,199,3900,FEMALE
+Chinstrap,Dream,47.6,18.3,195,3850,FEMALE
+Chinstrap,Dream,52,20.7,210,4800,MALE
+Chinstrap,Dream,46.9,16.6,192,2700,FEMALE
+Chinstrap,Dream,53.5,19.9,205,4500,MALE
+Chinstrap,Dream,49,19.5,210,3950,MALE
+Chinstrap,Dream,46.2,17.5,187,3650,FEMALE
+Chinstrap,Dream,50.9,19.1,196,3550,MALE
+Chinstrap,Dream,45.5,17,196,3500,FEMALE
+Chinstrap,Dream,50.9,17.9,196,3675,FEMALE
+Chinstrap,Dream,50.8,18.5,201,4450,MALE
+Chinstrap,Dream,50.1,17.9,190,3400,FEMALE
+Chinstrap,Dream,49,19.6,212,4300,MALE
+Chinstrap,Dream,51.5,18.7,187,3250,MALE
+Chinstrap,Dream,49.8,17.3,198,3675,FEMALE
+Chinstrap,Dream,48.1,16.4,199,3325,FEMALE
+Chinstrap,Dream,51.4,19,201,3950,MALE
+Chinstrap,Dream,45.7,17.3,193,3600,FEMALE
+Chinstrap,Dream,50.7,19.7,203,4050,MALE
+Chinstrap,Dream,42.5,17.3,187,3350,FEMALE
+Chinstrap,Dream,52.2,18.8,197,3450,MALE
+Chinstrap,Dream,45.2,16.6,191,3250,FEMALE
+Chinstrap,Dream,49.3,19.9,203,4050,MALE
+Chinstrap,Dream,50.2,18.8,202,3800,MALE
+Chinstrap,Dream,45.6,19.4,194,3525,FEMALE
+Chinstrap,Dream,51.9,19.5,206,3950,MALE
+Chinstrap,Dream,46.8,16.5,189,3650,FEMALE
+Chinstrap,Dream,45.7,17,195,3650,FEMALE
+Chinstrap,Dream,55.8,19.8,207,4000,MALE
+Chinstrap,Dream,43.5,18.1,202,3400,FEMALE
+Chinstrap,Dream,49.6,18.2,193,3775,MALE
+Chinstrap,Dream,50.8,19,210,4100,MALE
+Chinstrap,Dream,50.2,18.7,198,3775,FEMALE
+Gentoo,Biscoe,46.1,13.2,211,4500,FEMALE
+Gentoo,Biscoe,50,16.3,230,5700,MALE
+Gentoo,Biscoe,48.7,14.1,210,4450,FEMALE
+Gentoo,Biscoe,50,15.2,218,5700,MALE
+Gentoo,Biscoe,47.6,14.5,215,5400,MALE
+Gentoo,Biscoe,46.5,13.5,210,4550,FEMALE
+Gentoo,Biscoe,45.4,14.6,211,4800,FEMALE
+Gentoo,Biscoe,46.7,15.3,219,5200,MALE
+Gentoo,Biscoe,43.3,13.4,209,4400,FEMALE
+Gentoo,Biscoe,46.8,15.4,215,5150,MALE
+Gentoo,Biscoe,40.9,13.7,214,4650,FEMALE
+Gentoo,Biscoe,49,16.1,216,5550,MALE
+Gentoo,Biscoe,45.5,13.7,214,4650,FEMALE
+Gentoo,Biscoe,48.4,14.6,213,5850,MALE
+Gentoo,Biscoe,45.8,14.6,210,4200,FEMALE
+Gentoo,Biscoe,49.3,15.7,217,5850,MALE
+Gentoo,Biscoe,42,13.5,210,4150,FEMALE
+Gentoo,Biscoe,49.2,15.2,221,6300,MALE
+Gentoo,Biscoe,46.2,14.5,209,4800,FEMALE
+Gentoo,Biscoe,48.7,15.1,222,5350,MALE
+Gentoo,Biscoe,50.2,14.3,218,5700,MALE
+Gentoo,Biscoe,45.1,14.5,215,5000,FEMALE
+Gentoo,Biscoe,46.5,14.5,213,4400,FEMALE
+Gentoo,Biscoe,46.3,15.8,215,5050,MALE
+Gentoo,Biscoe,42.9,13.1,215,5000,FEMALE
+Gentoo,Biscoe,46.1,15.1,215,5100,MALE
+Gentoo,Biscoe,44.5,14.3,216,4100,
+Gentoo,Biscoe,47.8,15,215,5650,MALE
+Gentoo,Biscoe,48.2,14.3,210,4600,FEMALE
+Gentoo,Biscoe,50,15.3,220,5550,MALE
+Gentoo,Biscoe,47.3,15.3,222,5250,MALE
+Gentoo,Biscoe,42.8,14.2,209,4700,FEMALE
+Gentoo,Biscoe,45.1,14.5,207,5050,FEMALE
+Gentoo,Biscoe,59.6,17,230,6050,MALE
+Gentoo,Biscoe,49.1,14.8,220,5150,FEMALE
+Gentoo,Biscoe,48.4,16.3,220,5400,MALE
+Gentoo,Biscoe,42.6,13.7,213,4950,FEMALE
+Gentoo,Biscoe,44.4,17.3,219,5250,MALE
+Gentoo,Biscoe,44,13.6,208,4350,FEMALE
+Gentoo,Biscoe,48.7,15.7,208,5350,MALE
+Gentoo,Biscoe,42.7,13.7,208,3950,FEMALE
+Gentoo,Biscoe,49.6,16,225,5700,MALE
+Gentoo,Biscoe,45.3,13.7,210,4300,FEMALE
+Gentoo,Biscoe,49.6,15,216,4750,MALE
+Gentoo,Biscoe,50.5,15.9,222,5550,MALE
+Gentoo,Biscoe,43.6,13.9,217,4900,FEMALE
+Gentoo,Biscoe,45.5,13.9,210,4200,FEMALE
+Gentoo,Biscoe,50.5,15.9,225,5400,MALE
+Gentoo,Biscoe,44.9,13.3,213,5100,FEMALE
+Gentoo,Biscoe,45.2,15.8,215,5300,MALE
+Gentoo,Biscoe,46.6,14.2,210,4850,FEMALE
+Gentoo,Biscoe,48.5,14.1,220,5300,MALE
+Gentoo,Biscoe,45.1,14.4,210,4400,FEMALE
+Gentoo,Biscoe,50.1,15,225,5000,MALE
+Gentoo,Biscoe,46.5,14.4,217,4900,FEMALE
+Gentoo,Biscoe,45,15.4,220,5050,MALE
+Gentoo,Biscoe,43.8,13.9,208,4300,FEMALE
+Gentoo,Biscoe,45.5,15,220,5000,MALE
+Gentoo,Biscoe,43.2,14.5,208,4450,FEMALE
+Gentoo,Biscoe,50.4,15.3,224,5550,MALE
+Gentoo,Biscoe,45.3,13.8,208,4200,FEMALE
+Gentoo,Biscoe,46.2,14.9,221,5300,MALE
+Gentoo,Biscoe,45.7,13.9,214,4400,FEMALE
+Gentoo,Biscoe,54.3,15.7,231,5650,MALE
+Gentoo,Biscoe,45.8,14.2,219,4700,FEMALE
+Gentoo,Biscoe,49.8,16.8,230,5700,MALE
+Gentoo,Biscoe,46.2,14.4,214,4650,
+Gentoo,Biscoe,49.5,16.2,229,5800,MALE
+Gentoo,Biscoe,43.5,14.2,220,4700,FEMALE
+Gentoo,Biscoe,50.7,15,223,5550,MALE
+Gentoo,Biscoe,47.7,15,216,4750,FEMALE
+Gentoo,Biscoe,46.4,15.6,221,5000,MALE
+Gentoo,Biscoe,48.2,15.6,221,5100,MALE
+Gentoo,Biscoe,46.5,14.8,217,5200,FEMALE
+Gentoo,Biscoe,46.4,15,216,4700,FEMALE
+Gentoo,Biscoe,48.6,16,230,5800,MALE
+Gentoo,Biscoe,47.5,14.2,209,4600,FEMALE
+Gentoo,Biscoe,51.1,16.3,220,6000,MALE
+Gentoo,Biscoe,45.2,13.8,215,4750,FEMALE
+Gentoo,Biscoe,45.2,16.4,223,5950,MALE
+Gentoo,Biscoe,49.1,14.5,212,4625,FEMALE
+Gentoo,Biscoe,52.5,15.6,221,5450,MALE
+Gentoo,Biscoe,47.4,14.6,212,4725,FEMALE
+Gentoo,Biscoe,50,15.9,224,5350,MALE
+Gentoo,Biscoe,44.9,13.8,212,4750,FEMALE
+Gentoo,Biscoe,50.8,17.3,228,5600,MALE
+Gentoo,Biscoe,43.4,14.4,218,4600,FEMALE
+Gentoo,Biscoe,51.3,14.2,218,5300,MALE
+Gentoo,Biscoe,47.5,14,212,4875,FEMALE
+Gentoo,Biscoe,52.1,17,230,5550,MALE
+Gentoo,Biscoe,47.5,15,218,4950,FEMALE
+Gentoo,Biscoe,52.2,17.1,228,5400,MALE
+Gentoo,Biscoe,45.5,14.5,212,4750,FEMALE
+Gentoo,Biscoe,49.5,16.1,224,5650,MALE
+Gentoo,Biscoe,44.5,14.7,214,4850,FEMALE
+Gentoo,Biscoe,50.8,15.7,226,5200,MALE
+Gentoo,Biscoe,49.4,15.8,216,4925,MALE
+Gentoo,Biscoe,46.9,14.6,222,4875,FEMALE
+Gentoo,Biscoe,48.4,14.4,203,4625,FEMALE
+Gentoo,Biscoe,51.1,16.5,225,5250,MALE
+Gentoo,Biscoe,48.5,15,219,4850,FEMALE
+Gentoo,Biscoe,55.9,17,228,5600,MALE
+Gentoo,Biscoe,47.2,15.5,215,4975,FEMALE
+Gentoo,Biscoe,49.1,15,228,5500,MALE
+Gentoo,Biscoe,47.3,13.8,216,4725,
+Gentoo,Biscoe,46.8,16.1,215,5500,MALE
+Gentoo,Biscoe,41.7,14.7,210,4700,FEMALE
+Gentoo,Biscoe,53.4,15.8,219,5500,MALE
+Gentoo,Biscoe,43.3,14,208,4575,FEMALE
+Gentoo,Biscoe,48.1,15.1,209,5500,MALE
+Gentoo,Biscoe,50.5,15.2,216,5000,FEMALE
+Gentoo,Biscoe,49.8,15.9,229,5950,MALE
+Gentoo,Biscoe,43.5,15.2,213,4650,FEMALE
+Gentoo,Biscoe,51.5,16.3,230,5500,MALE
+Gentoo,Biscoe,46.2,14.1,217,4375,FEMALE
+Gentoo,Biscoe,55.1,16,230,5850,MALE
+Gentoo,Biscoe,44.5,15.7,217,4875,
+Gentoo,Biscoe,48.8,16.2,222,6000,MALE
+Gentoo,Biscoe,47.2,13.7,214,4925,FEMALE
+Gentoo,Biscoe,NaN,NaN,NaN,NaN,
+Gentoo,Biscoe,46.8,14.3,215,4850,FEMALE
+Gentoo,Biscoe,50.4,15.7,222,5750,MALE
+Gentoo,Biscoe,45.2,14.8,212,5200,FEMALE
+Gentoo,Biscoe,49.9,16.1,213,5400,MALE
\ No newline at end of file
diff --git a/src/content/tutorial/01-basics/05-axes-grids/+assets/src/routes/+error.svelte b/src/content/tutorial/01-basics/05-axes-grids/+assets/src/routes/+error.svelte
new file mode 100644
index 000000000..6706246e4
--- /dev/null
+++ b/src/content/tutorial/01-basics/05-axes-grids/+assets/src/routes/+error.svelte
@@ -0,0 +1,25 @@
+
+
+{#if page.status === 404}
+
+ Server-side rendering failed with HTTP status code
+ {page.status}
+
+{/if}
+
+
diff --git a/src/content/tutorial/01-basics/06-faceting/+assets/src/routes/+layout.js b/src/content/tutorial/01-basics/06-faceting/+assets/src/routes/+layout.js
new file mode 100644
index 000000000..a3d15781a
--- /dev/null
+++ b/src/content/tutorial/01-basics/06-faceting/+assets/src/routes/+layout.js
@@ -0,0 +1 @@
+export const ssr = false;
diff --git a/src/content/tutorial/01-basics/06-faceting/+assets/src/routes/+page.svelte b/src/content/tutorial/01-basics/06-faceting/+assets/src/routes/+page.svelte
new file mode 100644
index 000000000..3736ee076
--- /dev/null
+++ b/src/content/tutorial/01-basics/06-faceting/+assets/src/routes/+page.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/src/content/tutorial/01-basics/06-faceting/01-faceting/+assets/app-a/src/lib/App.svelte b/src/content/tutorial/01-basics/06-faceting/01-faceting/+assets/app-a/src/lib/App.svelte
new file mode 100644
index 000000000..a37f8daa7
--- /dev/null
+++ b/src/content/tutorial/01-basics/06-faceting/01-faceting/+assets/app-a/src/lib/App.svelte
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/06-faceting/01-faceting/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/01-basics/06-faceting/01-faceting/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..4b636f4da
--- /dev/null
+++ b/src/content/tutorial/01-basics/06-faceting/01-faceting/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/06-faceting/01-faceting/index.md b/src/content/tutorial/01-basics/06-faceting/01-faceting/index.md
new file mode 100644
index 000000000..fa34b06a7
--- /dev/null
+++ b/src/content/tutorial/01-basics/06-faceting/01-faceting/index.md
@@ -0,0 +1,27 @@
+---
+title: Faceting
+---
+
+Our scatter plot colours dots by island — three clusters are already visible. But what if we also want to see how penguins are distributed _across islands_?
+
+Encoding a second grouping as another channel would make the chart unreadable fast. **Faceting** solves this by splitting the plot into one panel per group.
+
+Add `fy="island"` to the `` mark to split horizontally by island:
+
+```svelte
+
+```
+
+SveltePlot creates one sub-plot per unique island value and lines them up side by side. The y-axis is shared across panels so values stay comparable.
+
+It helps to add a frame around each panel to make them visually distinct. Add `frame` to the `` component, and perhaps add a little bit of `inset`:
+
+```svelte
+
+```
diff --git a/src/content/tutorial/01-basics/06-faceting/02-unfaceted-data/+assets/app-a/src/lib/App.svelte b/src/content/tutorial/01-basics/06-faceting/02-unfaceted-data/+assets/app-a/src/lib/App.svelte
new file mode 100644
index 000000000..b90367eec
--- /dev/null
+++ b/src/content/tutorial/01-basics/06-faceting/02-unfaceted-data/+assets/app-a/src/lib/App.svelte
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/06-faceting/02-unfaceted-data/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/01-basics/06-faceting/02-unfaceted-data/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..c057eb40f
--- /dev/null
+++ b/src/content/tutorial/01-basics/06-faceting/02-unfaceted-data/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/06-faceting/02-unfaceted-data/+assets/lib/App.svelte b/src/content/tutorial/01-basics/06-faceting/02-unfaceted-data/+assets/lib/App.svelte
new file mode 100644
index 000000000..d850fdae8
--- /dev/null
+++ b/src/content/tutorial/01-basics/06-faceting/02-unfaceted-data/+assets/lib/App.svelte
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/src/content/tutorial/01-basics/06-faceting/02-unfaceted-data/index.md b/src/content/tutorial/01-basics/06-faceting/02-unfaceted-data/index.md
new file mode 100644
index 000000000..fd4bc77e9
--- /dev/null
+++ b/src/content/tutorial/01-basics/06-faceting/02-unfaceted-data/index.md
@@ -0,0 +1,29 @@
+---
+title: Unfaceted data
+---
+
+Each panel currently only shows the penguins from that island — the other rows are filtered out. That makes it easy to focus on one island, but harder to compare it against the whole population.
+
+You can add context to every panel at once by including a mark **without** a facet channel. A mark that has no `fy` (or `fx`) is drawn in every panel using the full dataset.
+
+Add a second `` before the existing one, without `fy`:
+
+```svelte
+
++
+
+
+```
+
+The gray dots appear in every panel, giving each island's cluster a reference against the full population.
diff --git a/src/content/tutorial/01-basics/06-faceting/index.md b/src/content/tutorial/01-basics/06-faceting/index.md
new file mode 100644
index 000000000..e78dc081e
--- /dev/null
+++ b/src/content/tutorial/01-basics/06-faceting/index.md
@@ -0,0 +1,5 @@
+---
+title: Faceting
+scope: { 'prefix': '/src/lib/', 'name': 'src' }
+focus: /src/lib/App.svelte
+---
diff --git a/src/content/tutorial/01-basics/index.md b/src/content/tutorial/01-basics/index.md
new file mode 100644
index 000000000..9967110a5
--- /dev/null
+++ b/src/content/tutorial/01-basics/index.md
@@ -0,0 +1,8 @@
+---
+title: SveltePlot
+label: Basics
+scope: { 'prefix': '/src/lib/', 'name': 'src' }
+focus: /src/lib/App.svelte
+---
+
+This tutorial assumes you are already familiar with Svelte. If not, we recommend taking a quick look at the [Svelte tutorial](https://svelte.dev/tutorial) first.
diff --git a/src/content/tutorial/02-advanced/01-tooltips/+assets/src/lib/penguins.csv b/src/content/tutorial/02-advanced/01-tooltips/+assets/src/lib/penguins.csv
new file mode 100644
index 000000000..83f32630b
--- /dev/null
+++ b/src/content/tutorial/02-advanced/01-tooltips/+assets/src/lib/penguins.csv
@@ -0,0 +1,345 @@
+species,island,bill_length_mm,bill_depth_mm,flipper_length_mm,body_mass_g,sex
+Adelie,Torgersen,39.1,18.7,181,3750,MALE
+Adelie,Torgersen,39.5,17.4,186,3800,FEMALE
+Adelie,Torgersen,40.3,18,195,3250,FEMALE
+Adelie,Torgersen,NaN,NaN,NaN,NaN,
+Adelie,Torgersen,36.7,19.3,193,3450,FEMALE
+Adelie,Torgersen,39.3,20.6,190,3650,MALE
+Adelie,Torgersen,38.9,17.8,181,3625,FEMALE
+Adelie,Torgersen,39.2,19.6,195,4675,MALE
+Adelie,Torgersen,34.1,18.1,193,3475,
+Adelie,Torgersen,42,20.2,190,4250,
+Adelie,Torgersen,37.8,17.1,186,3300,
+Adelie,Torgersen,37.8,17.3,180,3700,
+Adelie,Torgersen,41.1,17.6,182,3200,FEMALE
+Adelie,Torgersen,38.6,21.2,191,3800,MALE
+Adelie,Torgersen,34.6,21.1,198,4400,MALE
+Adelie,Torgersen,36.6,17.8,185,3700,FEMALE
+Adelie,Torgersen,38.7,19,195,3450,FEMALE
+Adelie,Torgersen,42.5,20.7,197,4500,MALE
+Adelie,Torgersen,34.4,18.4,184,3325,FEMALE
+Adelie,Torgersen,46,21.5,194,4200,MALE
+Adelie,Biscoe,37.8,18.3,174,3400,FEMALE
+Adelie,Biscoe,37.7,18.7,180,3600,MALE
+Adelie,Biscoe,35.9,19.2,189,3800,FEMALE
+Adelie,Biscoe,38.2,18.1,185,3950,MALE
+Adelie,Biscoe,38.8,17.2,180,3800,MALE
+Adelie,Biscoe,35.3,18.9,187,3800,FEMALE
+Adelie,Biscoe,40.6,18.6,183,3550,MALE
+Adelie,Biscoe,40.5,17.9,187,3200,FEMALE
+Adelie,Biscoe,37.9,18.6,172,3150,FEMALE
+Adelie,Biscoe,40.5,18.9,180,3950,MALE
+Adelie,Dream,39.5,16.7,178,3250,FEMALE
+Adelie,Dream,37.2,18.1,178,3900,MALE
+Adelie,Dream,39.5,17.8,188,3300,FEMALE
+Adelie,Dream,40.9,18.9,184,3900,MALE
+Adelie,Dream,36.4,17,195,3325,FEMALE
+Adelie,Dream,39.2,21.1,196,4150,MALE
+Adelie,Dream,38.8,20,190,3950,MALE
+Adelie,Dream,42.2,18.5,180,3550,FEMALE
+Adelie,Dream,37.6,19.3,181,3300,FEMALE
+Adelie,Dream,39.8,19.1,184,4650,MALE
+Adelie,Dream,36.5,18,182,3150,FEMALE
+Adelie,Dream,40.8,18.4,195,3900,MALE
+Adelie,Dream,36,18.5,186,3100,FEMALE
+Adelie,Dream,44.1,19.7,196,4400,MALE
+Adelie,Dream,37,16.9,185,3000,FEMALE
+Adelie,Dream,39.6,18.8,190,4600,MALE
+Adelie,Dream,41.1,19,182,3425,MALE
+Adelie,Dream,37.5,18.9,179,2975,
+Adelie,Dream,36,17.9,190,3450,FEMALE
+Adelie,Dream,42.3,21.2,191,4150,MALE
+Adelie,Biscoe,39.6,17.7,186,3500,FEMALE
+Adelie,Biscoe,40.1,18.9,188,4300,MALE
+Adelie,Biscoe,35,17.9,190,3450,FEMALE
+Adelie,Biscoe,42,19.5,200,4050,MALE
+Adelie,Biscoe,34.5,18.1,187,2900,FEMALE
+Adelie,Biscoe,41.4,18.6,191,3700,MALE
+Adelie,Biscoe,39,17.5,186,3550,FEMALE
+Adelie,Biscoe,40.6,18.8,193,3800,MALE
+Adelie,Biscoe,36.5,16.6,181,2850,FEMALE
+Adelie,Biscoe,37.6,19.1,194,3750,MALE
+Adelie,Biscoe,35.7,16.9,185,3150,FEMALE
+Adelie,Biscoe,41.3,21.1,195,4400,MALE
+Adelie,Biscoe,37.6,17,185,3600,FEMALE
+Adelie,Biscoe,41.1,18.2,192,4050,MALE
+Adelie,Biscoe,36.4,17.1,184,2850,FEMALE
+Adelie,Biscoe,41.6,18,192,3950,MALE
+Adelie,Biscoe,35.5,16.2,195,3350,FEMALE
+Adelie,Biscoe,41.1,19.1,188,4100,MALE
+Adelie,Torgersen,35.9,16.6,190,3050,FEMALE
+Adelie,Torgersen,41.8,19.4,198,4450,MALE
+Adelie,Torgersen,33.5,19,190,3600,FEMALE
+Adelie,Torgersen,39.7,18.4,190,3900,MALE
+Adelie,Torgersen,39.6,17.2,196,3550,FEMALE
+Adelie,Torgersen,45.8,18.9,197,4150,MALE
+Adelie,Torgersen,35.5,17.5,190,3700,FEMALE
+Adelie,Torgersen,42.8,18.5,195,4250,MALE
+Adelie,Torgersen,40.9,16.8,191,3700,FEMALE
+Adelie,Torgersen,37.2,19.4,184,3900,MALE
+Adelie,Torgersen,36.2,16.1,187,3550,FEMALE
+Adelie,Torgersen,42.1,19.1,195,4000,MALE
+Adelie,Torgersen,34.6,17.2,189,3200,FEMALE
+Adelie,Torgersen,42.9,17.6,196,4700,MALE
+Adelie,Torgersen,36.7,18.8,187,3800,FEMALE
+Adelie,Torgersen,35.1,19.4,193,4200,MALE
+Adelie,Dream,37.3,17.8,191,3350,FEMALE
+Adelie,Dream,41.3,20.3,194,3550,MALE
+Adelie,Dream,36.3,19.5,190,3800,MALE
+Adelie,Dream,36.9,18.6,189,3500,FEMALE
+Adelie,Dream,38.3,19.2,189,3950,MALE
+Adelie,Dream,38.9,18.8,190,3600,FEMALE
+Adelie,Dream,35.7,18,202,3550,FEMALE
+Adelie,Dream,41.1,18.1,205,4300,MALE
+Adelie,Dream,34,17.1,185,3400,FEMALE
+Adelie,Dream,39.6,18.1,186,4450,MALE
+Adelie,Dream,36.2,17.3,187,3300,FEMALE
+Adelie,Dream,40.8,18.9,208,4300,MALE
+Adelie,Dream,38.1,18.6,190,3700,FEMALE
+Adelie,Dream,40.3,18.5,196,4350,MALE
+Adelie,Dream,33.1,16.1,178,2900,FEMALE
+Adelie,Dream,43.2,18.5,192,4100,MALE
+Adelie,Biscoe,35,17.9,192,3725,FEMALE
+Adelie,Biscoe,41,20,203,4725,MALE
+Adelie,Biscoe,37.7,16,183,3075,FEMALE
+Adelie,Biscoe,37.8,20,190,4250,MALE
+Adelie,Biscoe,37.9,18.6,193,2925,FEMALE
+Adelie,Biscoe,39.7,18.9,184,3550,MALE
+Adelie,Biscoe,38.6,17.2,199,3750,FEMALE
+Adelie,Biscoe,38.2,20,190,3900,MALE
+Adelie,Biscoe,38.1,17,181,3175,FEMALE
+Adelie,Biscoe,43.2,19,197,4775,MALE
+Adelie,Biscoe,38.1,16.5,198,3825,FEMALE
+Adelie,Biscoe,45.6,20.3,191,4600,MALE
+Adelie,Biscoe,39.7,17.7,193,3200,FEMALE
+Adelie,Biscoe,42.2,19.5,197,4275,MALE
+Adelie,Biscoe,39.6,20.7,191,3900,FEMALE
+Adelie,Biscoe,42.7,18.3,196,4075,MALE
+Adelie,Torgersen,38.6,17,188,2900,FEMALE
+Adelie,Torgersen,37.3,20.5,199,3775,MALE
+Adelie,Torgersen,35.7,17,189,3350,FEMALE
+Adelie,Torgersen,41.1,18.6,189,3325,MALE
+Adelie,Torgersen,36.2,17.2,187,3150,FEMALE
+Adelie,Torgersen,37.7,19.8,198,3500,MALE
+Adelie,Torgersen,40.2,17,176,3450,FEMALE
+Adelie,Torgersen,41.4,18.5,202,3875,MALE
+Adelie,Torgersen,35.2,15.9,186,3050,FEMALE
+Adelie,Torgersen,40.6,19,199,4000,MALE
+Adelie,Torgersen,38.8,17.6,191,3275,FEMALE
+Adelie,Torgersen,41.5,18.3,195,4300,MALE
+Adelie,Torgersen,39,17.1,191,3050,FEMALE
+Adelie,Torgersen,44.1,18,210,4000,MALE
+Adelie,Torgersen,38.5,17.9,190,3325,FEMALE
+Adelie,Torgersen,43.1,19.2,197,3500,MALE
+Adelie,Dream,36.8,18.5,193,3500,FEMALE
+Adelie,Dream,37.5,18.5,199,4475,MALE
+Adelie,Dream,38.1,17.6,187,3425,FEMALE
+Adelie,Dream,41.1,17.5,190,3900,MALE
+Adelie,Dream,35.6,17.5,191,3175,FEMALE
+Adelie,Dream,40.2,20.1,200,3975,MALE
+Adelie,Dream,37,16.5,185,3400,FEMALE
+Adelie,Dream,39.7,17.9,193,4250,MALE
+Adelie,Dream,40.2,17.1,193,3400,FEMALE
+Adelie,Dream,40.6,17.2,187,3475,MALE
+Adelie,Dream,32.1,15.5,188,3050,FEMALE
+Adelie,Dream,40.7,17,190,3725,MALE
+Adelie,Dream,37.3,16.8,192,3000,FEMALE
+Adelie,Dream,39,18.7,185,3650,MALE
+Adelie,Dream,39.2,18.6,190,4250,MALE
+Adelie,Dream,36.6,18.4,184,3475,FEMALE
+Adelie,Dream,36,17.8,195,3450,FEMALE
+Adelie,Dream,37.8,18.1,193,3750,MALE
+Adelie,Dream,36,17.1,187,3700,FEMALE
+Adelie,Dream,41.5,18.5,201,4000,MALE
+Chinstrap,Dream,46.5,17.9,192,3500,FEMALE
+Chinstrap,Dream,50,19.5,196,3900,MALE
+Chinstrap,Dream,51.3,19.2,193,3650,MALE
+Chinstrap,Dream,45.4,18.7,188,3525,FEMALE
+Chinstrap,Dream,52.7,19.8,197,3725,MALE
+Chinstrap,Dream,45.2,17.8,198,3950,FEMALE
+Chinstrap,Dream,46.1,18.2,178,3250,FEMALE
+Chinstrap,Dream,51.3,18.2,197,3750,MALE
+Chinstrap,Dream,46,18.9,195,4150,FEMALE
+Chinstrap,Dream,51.3,19.9,198,3700,MALE
+Chinstrap,Dream,46.6,17.8,193,3800,FEMALE
+Chinstrap,Dream,51.7,20.3,194,3775,MALE
+Chinstrap,Dream,47,17.3,185,3700,FEMALE
+Chinstrap,Dream,52,18.1,201,4050,MALE
+Chinstrap,Dream,45.9,17.1,190,3575,FEMALE
+Chinstrap,Dream,50.5,19.6,201,4050,MALE
+Chinstrap,Dream,50.3,20,197,3300,MALE
+Chinstrap,Dream,58,17.8,181,3700,FEMALE
+Chinstrap,Dream,46.4,18.6,190,3450,FEMALE
+Chinstrap,Dream,49.2,18.2,195,4400,MALE
+Chinstrap,Dream,42.4,17.3,181,3600,FEMALE
+Chinstrap,Dream,48.5,17.5,191,3400,MALE
+Chinstrap,Dream,43.2,16.6,187,2900,FEMALE
+Chinstrap,Dream,50.6,19.4,193,3800,MALE
+Chinstrap,Dream,46.7,17.9,195,3300,FEMALE
+Chinstrap,Dream,52,19,197,4150,MALE
+Chinstrap,Dream,50.5,18.4,200,3400,FEMALE
+Chinstrap,Dream,49.5,19,200,3800,MALE
+Chinstrap,Dream,46.4,17.8,191,3700,FEMALE
+Chinstrap,Dream,52.8,20,205,4550,MALE
+Chinstrap,Dream,40.9,16.6,187,3200,FEMALE
+Chinstrap,Dream,54.2,20.8,201,4300,MALE
+Chinstrap,Dream,42.5,16.7,187,3350,FEMALE
+Chinstrap,Dream,51,18.8,203,4100,MALE
+Chinstrap,Dream,49.7,18.6,195,3600,MALE
+Chinstrap,Dream,47.5,16.8,199,3900,FEMALE
+Chinstrap,Dream,47.6,18.3,195,3850,FEMALE
+Chinstrap,Dream,52,20.7,210,4800,MALE
+Chinstrap,Dream,46.9,16.6,192,2700,FEMALE
+Chinstrap,Dream,53.5,19.9,205,4500,MALE
+Chinstrap,Dream,49,19.5,210,3950,MALE
+Chinstrap,Dream,46.2,17.5,187,3650,FEMALE
+Chinstrap,Dream,50.9,19.1,196,3550,MALE
+Chinstrap,Dream,45.5,17,196,3500,FEMALE
+Chinstrap,Dream,50.9,17.9,196,3675,FEMALE
+Chinstrap,Dream,50.8,18.5,201,4450,MALE
+Chinstrap,Dream,50.1,17.9,190,3400,FEMALE
+Chinstrap,Dream,49,19.6,212,4300,MALE
+Chinstrap,Dream,51.5,18.7,187,3250,MALE
+Chinstrap,Dream,49.8,17.3,198,3675,FEMALE
+Chinstrap,Dream,48.1,16.4,199,3325,FEMALE
+Chinstrap,Dream,51.4,19,201,3950,MALE
+Chinstrap,Dream,45.7,17.3,193,3600,FEMALE
+Chinstrap,Dream,50.7,19.7,203,4050,MALE
+Chinstrap,Dream,42.5,17.3,187,3350,FEMALE
+Chinstrap,Dream,52.2,18.8,197,3450,MALE
+Chinstrap,Dream,45.2,16.6,191,3250,FEMALE
+Chinstrap,Dream,49.3,19.9,203,4050,MALE
+Chinstrap,Dream,50.2,18.8,202,3800,MALE
+Chinstrap,Dream,45.6,19.4,194,3525,FEMALE
+Chinstrap,Dream,51.9,19.5,206,3950,MALE
+Chinstrap,Dream,46.8,16.5,189,3650,FEMALE
+Chinstrap,Dream,45.7,17,195,3650,FEMALE
+Chinstrap,Dream,55.8,19.8,207,4000,MALE
+Chinstrap,Dream,43.5,18.1,202,3400,FEMALE
+Chinstrap,Dream,49.6,18.2,193,3775,MALE
+Chinstrap,Dream,50.8,19,210,4100,MALE
+Chinstrap,Dream,50.2,18.7,198,3775,FEMALE
+Gentoo,Biscoe,46.1,13.2,211,4500,FEMALE
+Gentoo,Biscoe,50,16.3,230,5700,MALE
+Gentoo,Biscoe,48.7,14.1,210,4450,FEMALE
+Gentoo,Biscoe,50,15.2,218,5700,MALE
+Gentoo,Biscoe,47.6,14.5,215,5400,MALE
+Gentoo,Biscoe,46.5,13.5,210,4550,FEMALE
+Gentoo,Biscoe,45.4,14.6,211,4800,FEMALE
+Gentoo,Biscoe,46.7,15.3,219,5200,MALE
+Gentoo,Biscoe,43.3,13.4,209,4400,FEMALE
+Gentoo,Biscoe,46.8,15.4,215,5150,MALE
+Gentoo,Biscoe,40.9,13.7,214,4650,FEMALE
+Gentoo,Biscoe,49,16.1,216,5550,MALE
+Gentoo,Biscoe,45.5,13.7,214,4650,FEMALE
+Gentoo,Biscoe,48.4,14.6,213,5850,MALE
+Gentoo,Biscoe,45.8,14.6,210,4200,FEMALE
+Gentoo,Biscoe,49.3,15.7,217,5850,MALE
+Gentoo,Biscoe,42,13.5,210,4150,FEMALE
+Gentoo,Biscoe,49.2,15.2,221,6300,MALE
+Gentoo,Biscoe,46.2,14.5,209,4800,FEMALE
+Gentoo,Biscoe,48.7,15.1,222,5350,MALE
+Gentoo,Biscoe,50.2,14.3,218,5700,MALE
+Gentoo,Biscoe,45.1,14.5,215,5000,FEMALE
+Gentoo,Biscoe,46.5,14.5,213,4400,FEMALE
+Gentoo,Biscoe,46.3,15.8,215,5050,MALE
+Gentoo,Biscoe,42.9,13.1,215,5000,FEMALE
+Gentoo,Biscoe,46.1,15.1,215,5100,MALE
+Gentoo,Biscoe,44.5,14.3,216,4100,
+Gentoo,Biscoe,47.8,15,215,5650,MALE
+Gentoo,Biscoe,48.2,14.3,210,4600,FEMALE
+Gentoo,Biscoe,50,15.3,220,5550,MALE
+Gentoo,Biscoe,47.3,15.3,222,5250,MALE
+Gentoo,Biscoe,42.8,14.2,209,4700,FEMALE
+Gentoo,Biscoe,45.1,14.5,207,5050,FEMALE
+Gentoo,Biscoe,59.6,17,230,6050,MALE
+Gentoo,Biscoe,49.1,14.8,220,5150,FEMALE
+Gentoo,Biscoe,48.4,16.3,220,5400,MALE
+Gentoo,Biscoe,42.6,13.7,213,4950,FEMALE
+Gentoo,Biscoe,44.4,17.3,219,5250,MALE
+Gentoo,Biscoe,44,13.6,208,4350,FEMALE
+Gentoo,Biscoe,48.7,15.7,208,5350,MALE
+Gentoo,Biscoe,42.7,13.7,208,3950,FEMALE
+Gentoo,Biscoe,49.6,16,225,5700,MALE
+Gentoo,Biscoe,45.3,13.7,210,4300,FEMALE
+Gentoo,Biscoe,49.6,15,216,4750,MALE
+Gentoo,Biscoe,50.5,15.9,222,5550,MALE
+Gentoo,Biscoe,43.6,13.9,217,4900,FEMALE
+Gentoo,Biscoe,45.5,13.9,210,4200,FEMALE
+Gentoo,Biscoe,50.5,15.9,225,5400,MALE
+Gentoo,Biscoe,44.9,13.3,213,5100,FEMALE
+Gentoo,Biscoe,45.2,15.8,215,5300,MALE
+Gentoo,Biscoe,46.6,14.2,210,4850,FEMALE
+Gentoo,Biscoe,48.5,14.1,220,5300,MALE
+Gentoo,Biscoe,45.1,14.4,210,4400,FEMALE
+Gentoo,Biscoe,50.1,15,225,5000,MALE
+Gentoo,Biscoe,46.5,14.4,217,4900,FEMALE
+Gentoo,Biscoe,45,15.4,220,5050,MALE
+Gentoo,Biscoe,43.8,13.9,208,4300,FEMALE
+Gentoo,Biscoe,45.5,15,220,5000,MALE
+Gentoo,Biscoe,43.2,14.5,208,4450,FEMALE
+Gentoo,Biscoe,50.4,15.3,224,5550,MALE
+Gentoo,Biscoe,45.3,13.8,208,4200,FEMALE
+Gentoo,Biscoe,46.2,14.9,221,5300,MALE
+Gentoo,Biscoe,45.7,13.9,214,4400,FEMALE
+Gentoo,Biscoe,54.3,15.7,231,5650,MALE
+Gentoo,Biscoe,45.8,14.2,219,4700,FEMALE
+Gentoo,Biscoe,49.8,16.8,230,5700,MALE
+Gentoo,Biscoe,46.2,14.4,214,4650,
+Gentoo,Biscoe,49.5,16.2,229,5800,MALE
+Gentoo,Biscoe,43.5,14.2,220,4700,FEMALE
+Gentoo,Biscoe,50.7,15,223,5550,MALE
+Gentoo,Biscoe,47.7,15,216,4750,FEMALE
+Gentoo,Biscoe,46.4,15.6,221,5000,MALE
+Gentoo,Biscoe,48.2,15.6,221,5100,MALE
+Gentoo,Biscoe,46.5,14.8,217,5200,FEMALE
+Gentoo,Biscoe,46.4,15,216,4700,FEMALE
+Gentoo,Biscoe,48.6,16,230,5800,MALE
+Gentoo,Biscoe,47.5,14.2,209,4600,FEMALE
+Gentoo,Biscoe,51.1,16.3,220,6000,MALE
+Gentoo,Biscoe,45.2,13.8,215,4750,FEMALE
+Gentoo,Biscoe,45.2,16.4,223,5950,MALE
+Gentoo,Biscoe,49.1,14.5,212,4625,FEMALE
+Gentoo,Biscoe,52.5,15.6,221,5450,MALE
+Gentoo,Biscoe,47.4,14.6,212,4725,FEMALE
+Gentoo,Biscoe,50,15.9,224,5350,MALE
+Gentoo,Biscoe,44.9,13.8,212,4750,FEMALE
+Gentoo,Biscoe,50.8,17.3,228,5600,MALE
+Gentoo,Biscoe,43.4,14.4,218,4600,FEMALE
+Gentoo,Biscoe,51.3,14.2,218,5300,MALE
+Gentoo,Biscoe,47.5,14,212,4875,FEMALE
+Gentoo,Biscoe,52.1,17,230,5550,MALE
+Gentoo,Biscoe,47.5,15,218,4950,FEMALE
+Gentoo,Biscoe,52.2,17.1,228,5400,MALE
+Gentoo,Biscoe,45.5,14.5,212,4750,FEMALE
+Gentoo,Biscoe,49.5,16.1,224,5650,MALE
+Gentoo,Biscoe,44.5,14.7,214,4850,FEMALE
+Gentoo,Biscoe,50.8,15.7,226,5200,MALE
+Gentoo,Biscoe,49.4,15.8,216,4925,MALE
+Gentoo,Biscoe,46.9,14.6,222,4875,FEMALE
+Gentoo,Biscoe,48.4,14.4,203,4625,FEMALE
+Gentoo,Biscoe,51.1,16.5,225,5250,MALE
+Gentoo,Biscoe,48.5,15,219,4850,FEMALE
+Gentoo,Biscoe,55.9,17,228,5600,MALE
+Gentoo,Biscoe,47.2,15.5,215,4975,FEMALE
+Gentoo,Biscoe,49.1,15,228,5500,MALE
+Gentoo,Biscoe,47.3,13.8,216,4725,
+Gentoo,Biscoe,46.8,16.1,215,5500,MALE
+Gentoo,Biscoe,41.7,14.7,210,4700,FEMALE
+Gentoo,Biscoe,53.4,15.8,219,5500,MALE
+Gentoo,Biscoe,43.3,14,208,4575,FEMALE
+Gentoo,Biscoe,48.1,15.1,209,5500,MALE
+Gentoo,Biscoe,50.5,15.2,216,5000,FEMALE
+Gentoo,Biscoe,49.8,15.9,229,5950,MALE
+Gentoo,Biscoe,43.5,15.2,213,4650,FEMALE
+Gentoo,Biscoe,51.5,16.3,230,5500,MALE
+Gentoo,Biscoe,46.2,14.1,217,4375,FEMALE
+Gentoo,Biscoe,55.1,16,230,5850,MALE
+Gentoo,Biscoe,44.5,15.7,217,4875,
+Gentoo,Biscoe,48.8,16.2,222,6000,MALE
+Gentoo,Biscoe,47.2,13.7,214,4925,FEMALE
+Gentoo,Biscoe,NaN,NaN,NaN,NaN,
+Gentoo,Biscoe,46.8,14.3,215,4850,FEMALE
+Gentoo,Biscoe,50.4,15.7,222,5750,MALE
+Gentoo,Biscoe,45.2,14.8,212,5200,FEMALE
+Gentoo,Biscoe,49.9,16.1,213,5400,MALE
\ No newline at end of file
diff --git a/src/content/tutorial/02-advanced/01-tooltips/01-intro/+assets/app-a/src/lib/App.svelte b/src/content/tutorial/02-advanced/01-tooltips/01-intro/+assets/app-a/src/lib/App.svelte
new file mode 100644
index 000000000..86457d625
--- /dev/null
+++ b/src/content/tutorial/02-advanced/01-tooltips/01-intro/+assets/app-a/src/lib/App.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/src/content/tutorial/02-advanced/01-tooltips/01-intro/index.md b/src/content/tutorial/02-advanced/01-tooltips/01-intro/index.md
new file mode 100644
index 000000000..09b685fe7
--- /dev/null
+++ b/src/content/tutorial/02-advanced/01-tooltips/01-intro/index.md
@@ -0,0 +1,7 @@
+---
+title: Tooltips
+---
+
+Interactive charts often benefit from tooltips — small overlays that reveal additional details when the user points to an element.
+
+Over the next few steps we'll add progressively richer tooltip interactions to a simple bar chart, then move on to scatter plots.
diff --git a/src/content/tutorial/02-advanced/01-tooltips/02-pointer-events/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/02-advanced/01-tooltips/02-pointer-events/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..84c4429f2
--- /dev/null
+++ b/src/content/tutorial/02-advanced/01-tooltips/02-pointer-events/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,28 @@
+
+
+
+ (tooltip = d)}
+ onpointerleave={() => (tooltip = null)}
+ />
+
+
+
+{#if tooltip}
+
{tooltip.fruit}: {tooltip.sales} units
+{/if}
diff --git a/src/content/tutorial/02-advanced/01-tooltips/02-pointer-events/index.md b/src/content/tutorial/02-advanced/01-tooltips/02-pointer-events/index.md
new file mode 100644
index 000000000..c942a7570
--- /dev/null
+++ b/src/content/tutorial/02-advanced/01-tooltips/02-pointer-events/index.md
@@ -0,0 +1,33 @@
+---
+title: Pointer events
+---
+
+Every mark in SveltePlot supports native pointer events. The `onpointerenter` handler receives the browser event and the **datum** — the original data record for the element being pointed at.
+
+Add a state variable and wire it up to the bar mark:
+
+```svelte
+ const data = [...];
+
++ let tooltip = $state(null);
+```
+
+```svelte
+ (tooltip = d)}
++ onpointerleave={() => (tooltip = null)}
+ />
+```
+
+Finally, display the tooltip below the chart:
+
+```svelte
+
+
++{#if tooltip}
++
{tooltip.fruit}: {tooltip.sales} units
++{/if}
+```
diff --git a/src/content/tutorial/02-advanced/01-tooltips/03-text-labels/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/02-advanced/01-tooltips/03-text-labels/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..727782153
--- /dev/null
+++ b/src/content/tutorial/02-advanced/01-tooltips/03-text-labels/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,33 @@
+
+
+
+ (d === tooltip ? 1 : 0.3) : 1}
+ onpointerenter={(e, d) => (tooltip = d)}
+ onpointerleave={() => (tooltip = null)}
+ />
+
+ String(d.sales)}
+ lineAnchor="bottom"
+ dy={-4}
+ />
+
diff --git a/src/content/tutorial/02-advanced/01-tooltips/03-text-labels/index.md b/src/content/tutorial/02-advanced/01-tooltips/03-text-labels/index.md
new file mode 100644
index 000000000..b973d24c6
--- /dev/null
+++ b/src/content/tutorial/02-advanced/01-tooltips/03-text-labels/index.md
@@ -0,0 +1,44 @@
+---
+title: Text labels
+---
+
+Showing the hovered value below the chart works, but it would be better to show it directly inside the chart. We can replace the `
` tag with a `Text` mark.
+
+First, import `Text` and remove the paragraph below the chart:
+
+```svelte
+- import { Plot, BarY, RuleY } from 'svelteplot';
++ import { Plot, BarY, RuleY, Text } from 'svelteplot';
+```
+
+Then add the `Text` mark inside ``. Pass only the hovered datum as a single-element array — when nothing is hovered, pass an empty array:
+
+```svelte
+
++ String(d.sales)}
++ lineAnchor="bottom"
++ dy={-4}
++ />
+
+
+-{#if tooltip}
+-
+ {tooltip.species}: {tooltip.bill_length_mm} mm × {tooltip.bill_depth_mm}
+ mm
+
+{/if}
diff --git a/src/content/tutorial/02-advanced/01-tooltips/04-voronoi/index.md b/src/content/tutorial/02-advanced/01-tooltips/04-voronoi/index.md
new file mode 100644
index 000000000..3fee33475
--- /dev/null
+++ b/src/content/tutorial/02-advanced/01-tooltips/04-voronoi/index.md
@@ -0,0 +1,36 @@
+---
+title: Voronoi tooltips
+---
+
+The pointer-events approach works great for bars, but in a scatter plot the `Dot` symbols are often too small to hover over reliably. One solution is to overlay an invisible **Voronoi tessellation** — a set of polygons that partition the plot area so that every pixel belongs to the nearest data point.
+
+Import the `Voronoi` mark and add it on top of the dots:
+
+```js
+import { Plot, Dot+++, Voronoi+++ } from 'svelteplot';
+```
+
+Place the `Voronoi` mark after `` so it sits on top and receives the pointer events. Use `fill="transparent"` so the polygons are invisible but still catchable:
+
+```svelte
+
++ (tooltip = d)}
++ onpointerleave={() => (tooltip = null)}
++ />
+
+```
+
+```svelte
+
+
++{#if tooltip}
++
{tooltip.species}: {tooltip.bill_length_mm} mm × {tooltip.bill_depth_mm} mm
+ {tooltip.species}: {tooltip.bill_length_mm} mm × {tooltip.bill_depth_mm}
+ mm
+
+{/if}
diff --git a/src/content/tutorial/02-advanced/01-tooltips/05-pointer-mark/index.md b/src/content/tutorial/02-advanced/01-tooltips/05-pointer-mark/index.md
new file mode 100644
index 000000000..62ffaba69
--- /dev/null
+++ b/src/content/tutorial/02-advanced/01-tooltips/05-pointer-mark/index.md
@@ -0,0 +1,31 @@
+---
+title: Pointer mark
+---
+
+The `Voronoi` approach requires computing and rendering extra geometry. SveltePlot has a simpler alternative: the `Pointer` mark, which uses a **proximity quadtree** to find the nearest data point to the cursor without adding any visible elements.
+
+Replace `Voronoi` with `Pointer`. Instead of `onpointerenter`/`onpointerleave`, use the `onupdate` callback — it fires whenever the selection changes and receives the array of currently selected data points:
+
+```js
+import { Plot, Dot, ---Voronoi---+++Pointer+++ } from 'svelteplot';
+```
+
+```svelte
+- (tooltip = d)}
+- onpointerleave={() => (tooltip = null)}
+- />
++ (tooltip = selection[0] ?? null)}
++ />
+```
+
+The `maxDistance` prop (default `15px`) controls how close the cursor must be to select a point. When the cursor moves away, `onupdate` fires with an empty array, so `selection[0] ?? null` clears the tooltip.
diff --git a/src/content/tutorial/02-advanced/01-tooltips/06-html-tooltip/+assets/app-b/src/lib/App.svelte b/src/content/tutorial/02-advanced/01-tooltips/06-html-tooltip/+assets/app-b/src/lib/App.svelte
new file mode 100644
index 000000000..1ee8f2033
--- /dev/null
+++ b/src/content/tutorial/02-advanced/01-tooltips/06-html-tooltip/+assets/app-b/src/lib/App.svelte
@@ -0,0 +1,42 @@
+
+
+
+
+ {#snippet overlay()}
+
+ {#snippet children({ datum })}
+
+ {datum?.species}
+ bill: {datum?.bill_length_mm} × {datum?.bill_depth_mm}
+ mm
+
+ {/snippet}
+
+ {/snippet}
+
+
+
diff --git a/src/content/tutorial/02-advanced/01-tooltips/06-html-tooltip/index.md b/src/content/tutorial/02-advanced/01-tooltips/06-html-tooltip/index.md
new file mode 100644
index 000000000..7e455d1ed
--- /dev/null
+++ b/src/content/tutorial/02-advanced/01-tooltips/06-html-tooltip/index.md
@@ -0,0 +1,31 @@
+---
+title: HTML tooltip
+---
+
+Managing tooltip state and positioning manually works, but SveltePlot ships with a `HTMLTooltip` mark that handles both automatically. It uses the same quadtree proximity logic as `Pointer` but renders a floating HTML `
` positioned at the hovered data point's screen coordinates.
+
+Remove the `Pointer` mark, the `tooltip` state, and the `{#if}` block. Import `HTMLTooltip` instead:
+
+```svelte
+- import { Plot, Dot, Frame, Pointer } from 'svelteplot';
++ import { Plot, Dot, Frame, HTMLTooltip } from 'svelteplot';
+```
+
+Replace the `` mark and the `{#if}` block with ``. The `children` snippet receives `{ datum }` — the nearest data point:
+
+```svelte
+- (tooltip = selection[0] ?? null)}
+- />
++
++ {#snippet children({ datum })}
++ {datum?.species}
++ bill: {datum?.bill_length_mm} × {datum?.bill_depth_mm} mm
++ {/snippet}
++
+```
+
+The tooltip `
` is positioned absolutely inside the plot and hidden when no point is nearby. Style it with CSS to match your design.
diff --git a/src/content/tutorial/02-advanced/01-tooltips/index.md b/src/content/tutorial/02-advanced/01-tooltips/index.md
new file mode 100644
index 000000000..aee55df84
--- /dev/null
+++ b/src/content/tutorial/02-advanced/01-tooltips/index.md
@@ -0,0 +1,3 @@
+---
+title: Tooltips
+---
diff --git a/src/content/tutorial/02-advanced/index.md b/src/content/tutorial/02-advanced/index.md
new file mode 100644
index 000000000..5029da7ef
--- /dev/null
+++ b/src/content/tutorial/02-advanced/index.md
@@ -0,0 +1,6 @@
+---
+title: SveltePlot
+label: Advanced
+scope: { 'prefix': '/src/lib/', 'name': 'src' }
+focus: /src/lib/App.svelte
+---
diff --git a/src/content/tutorial/PLAN.md b/src/content/tutorial/PLAN.md
new file mode 100644
index 000000000..7d8912f2e
--- /dev/null
+++ b/src/content/tutorial/PLAN.md
@@ -0,0 +1,313 @@
+# Tutorial Plan
+
+## Goal
+
+Give new users a **playful, interactive introduction** to the mental model behind SveltePlot — not a complete feature reference. The tutorial is done when a user understands _why_ SveltePlot works the way it does and feels confident enough to read the docs on their own.
+
+**The tutorial's job:** concepts. **The docs' job:** every option on every mark.
+
+## Scope — what "done" looks like
+
+The basics tutorial is **already essentially complete**:
+
+- **01-basics** — getting-started + scales + axes/grids + faceting (≈15 lessons ✓)
+
+The marks and transforms chapters inside 01-basics are **deferred**. Instead of duplicating the docs, mark-specific tutorials (e.g. "working with the Line mark") should be linked directly from the relevant docs pages. This keeps the main tutorial focused and finite.
+
+**02-advanced** is a placeholder for future advanced tutorials.
+
+---
+
+Three levels map to the three directory levels: Part → Chapter → Lesson (REPL step).
+
+---
+
+## Tutorial structure
+
+### Directory layout
+
+```
+/ e.g. 01-basics/
+ index.md part title + optional intro text
+ +assets/ shared files for every lesson in the part
+ src/lib/penguins.csv
+ src/routes/+page.svelte
+ ...
+ / e.g. 01-scales/
+ index.md chapter title (body usually empty)
+ +assets/ shared files for every lesson in this chapter
+ src/lib/aapl.csv
+ / e.g. 01-zero-rule/
+ index.md lesson prose + code diffs (frontmatter: title)
+ +assets/
+ app-a/src/lib/App.svelte starting state shown to the user
+ app-b/src/lib/App.svelte solution state (shown when "Solve" is clicked)
+```
+
+### File merging
+
+The REPL's starting state (`a`) is built by merging three layers in order:
+
+1. **Part-level** `+assets/` — routes boilerplate, shared datasets
+2. **Chapter-level** `+assets/` — data files used across all lessons in the chapter
+3. **Lesson `app-a/`** — the editable `App.svelte` and any lesson-specific files
+
+When the user clicks **Solve**, only the `app-b/` files are merged on top of `a`. This means `app-b` only needs to contain files that actually change — typically just `App.svelte`. All other files (CSV data, routes, etc.) carry over automatically.
+
+### Lesson prose conventions
+
+- Full-line add/remove: prefix the line with `+` or `-` (single character).
+- Inline add/remove: wrap spans with `+++text+++` and `---text---`; multiple pairs on the same line are fine.
+- Show the import change in a separate code block when a new component is imported.
+- Keep prose short: one sentence of context, one instruction, one explanation of why.
+
+---
+
+## 01 · Basics (`01-basics/`)
+
+### Getting started (`01-getting-started/`)
+
+The idea of the basics section is to learn how to construct a plot with SveltePlot by combining the Plot component with marks and transforms.
+
+- [x] Your first plot → `01-getting-started/01-first-plot`
+- [x] Marks — what they are, swapping one for another → `01-getting-started/02-marks`
+- [x] Layering marks → `01-getting-started/03-layering`
+- [x] Channels (x, y, fill) → `01-getting-started/04-channels`
+- [x] Color channels — quantitative fill → `01-getting-started/05-color-channels`
+- [x] Reactivity → `01-getting-started/08-reactivity`
+
+### Scales (`02-scales/`)
+
+In this chapter we learn how SveltePlot automatically determines the scales for our plot and how to customize them.
+
+- Automatic scale domains
+- Setting scale options: log, nice, padding, domain, etc.
+- Bypassing scales
+ - change `` to `` to bypass scale
+
+### Transforms (`03-transforms/`) — DEFERRED
+
+Transforms are useful when your dataset is not in the shape you need it to be visualized. Technically, the BarY mark requires
+
+> See `## Transforms — DEFERRED` below.
+
+### Scales (`04-scales/`)
+
+The Plot component collects data from all marks and determines common scales.
+
+- [x] Extending the domain — add `` → `04-scales/01-zero-rule`
+- [x] The zero option — `y={{ zero: true }}` → `04-scales/02-zero-option`
+- [x] Scale types — add `` → `04-scales/03-scale-type`
+
+### Axes & grids (`05-axes-grids/`)
+
+Axes are auto-added; they can be disabled or overridden.
+
+- [x] Implicit axes → `05-axes-grids/01-implicit-marks`
+- [x] Explicit axes → `05-axes-grids/02-explicit-axes`
+- [x] Implicit grids → `05-axes-grids/03-implicit-grids`
+- [x] Explicit grids → `05-axes-grids/04-explicit-grids`
+
+### Faceting (`06-faceting/`)
+
+Plots can be split into multiple panels via `fx`/`fy` channels.
+
+- [x] Faceting basics → `06-faceting/01-faceting`
+- [x] Unfaceted data in every panel → `06-faceting/02-unfaceted-data`
+
+---
+
+## 02 · Advanced (`02-advanced/`) — PLACEHOLDER
+
+No lessons yet. Future candidates: plot defaults, overlays/underlays, HTML tooltips, image underlays.
+
+## Marks — DEFERRED (`01-basics/02-marks/`)
+
+> Not part of the main basics tutorial. Mark-specific tutorials (if written) should be short, self-contained, and linked from the corresponding docs page rather than chained into this tutorial. Answer to the question below: no.
+>
+> _Original question:_ do we really want to introduce all marks and their features? Isn't that duplicating the marks docs? Perhaps it should just touch on the different kinds of marks?
+
+- SVG vs HTML
+- Canvas rendering
+- Point vs range
+- One-row-per-element (dot, bar) vs. multiple-rows-per-element (line, area, ...)
+- Geo marks
+-
+
+- **Marks** - Short introduction into the marks concept. Data, Channels, Style properties, common properties
+- **Dot** (`01-basics/02-marks/01-dot/`)
+ - The dot mark is a very versatile mark. It can be used for scatterplots, dot plots, beeswarm plots (in combination with the [dodge transform]).
+ - [x] Symbol channel — shapes for categories → `01-dot/01-symbol`
+ - [x] DotX / DotY — one-dimensional strip → `01-dot/02-dotx-doty`
+ - [x] Size channel — bubble chart (r channel) → `01-dot/03-size-channel`
+ - [ ] Color & opacity
+
+- **Line** (`01-basics/02-marks/03-line/`) — Apple stock (aapl.csv)
+ - The line mark is useful for showing change. It accepts an array of positions that get connected through a line.
+ - [x] The Line mark — time series → `03-line/01-line`
+ - [x] Sorting — sort transform for non-temporal x → `03-line/02-sorting` - the line mark connects the data points in the order they appear in thet data. If your data comes in the wrong order, you can sort it before passing it to the line mark
+ - [ ] More convenient sorting using the built-in `sort` transform.
+ - [x] Multiple series — stroke channel + group → `03-line/03-grouping`
+ - [x] Markers — arrowhead / dot markers → `03-line/04-markers`
+ - [ ] Custom marker snippet
+ - [x] Curve — interpolation options → `03-line/05-curve`
+ - [x] Text along lines — inline labels → `03-line/06-text`
+
+- **Area** (TBD)
+ - [ ] AreaY — area below a line
+ - [ ] AreaX — horizontal area
+ - [ ] Band / range area (y1 and y2 channels)
+ - [ ] DifferenceY — difference / above-below chart
+
+- **Rect** (TBD)
+ - The rect mark is one of three marks that are rendering rectangles to a plot, depending on your x and y axis. Here's an overview on which marks to use in which scenario:
+ | | y scale is quantitative | y scale is qualitative |
+ | ------------------------------------ | :---------------------: | :--------------------: |
+ | **x scale is quantitative** | Rect | BarX |
+ | **x scale is qualitative** | BarY | Cell |
+
+ - [ ] Rect: no stacking - The `Rect` mark requires four channes x1, x2, y1, y2 that define the bounds of each rectangle. _Create a plot with custom rectangles_
+ - [ ] RectX and RectY for range annotations
+ - [ ] RectY + bin transform — basic histogram
+ - [ ] RectX — horizontal histogram
+
+- **Bar** (TBD)
+ - [ ] BarY — vertical bar chart
+ - [ ] BarX — horizontal bar chart
+ - [ ] Color channel on bars
+
+- **Cell** (TBD)
+ - [ ] Cell — basic heatmap (x/y as categories, fill as value)
+ - [ ] CellX / CellY — one axis is categorical
+
+- **Rule** (TBD)
+ - [ ] RuleY — horizontal reference line
+ - [ ] RuleX — vertical reference line
+
+- **Frame**
+ - [ ] Frames can be added implicitely by setting the frame property on the Plot component. But you can also add frames explicitely using the frame mark.
+ - [ ] Frames can be manipulated
+ - [ ] You can use a frame mark for clipping
+- **Tick** (`01-basics/02-marks/02-tick/`) — faceted by species (`fy="species"`)
+ - [x] TickX / TickY — tick strip / rug plot → `02-tick/01-tickx-ticky`
+
+- **Text** (TBD)
+ - [ ] Text mark — positioning, formatting, alignment
+
+- **Arrow / Link** (TBD)
+ - [ ] Arrow — curved labeled arrows between points
+ - [ ] Link — straight line between two data points
+
+- **Vector** (TBD)
+ - [ ] Vector — directional / wind field
+ - [ ] Spike — magnitude as spike height
+
+- **Box** (TBD)
+ - [ ] BoxY — vertical box plot
+ - [ ] BoxX — horizontal box plot
+
+- **Regression** (TBD)
+ - [ ] RegressionY — linear regression line
+ - [ ] RegressionX
+
+- **Other statistical marks** (TBD)
+ - [ ] BollingerY
+ - [ ] Trail — temporal path with varying stroke width
+
+- **Geographic** (TBD)
+ - [ ] Geo — rendering GeoJSON features with a projection
+ - [ ] Sphere and Graticule — globe outline and grid lines
+
+- **Interaction** (TBD)
+ - [ ] Pointer mark — nearest-point hover / tooltip
+ - [ ] Tooltip mark
+ - [ ] BrushX / BrushY / Brush — selection
+
+- **Advanced** (TBD)
+ - [ ] Density / Contour
+ - [ ] DelaunayLink / Hull / Voronoi
+ - [ ] Raster
+ - [ ] CustomMark / CustomMarkHTML
+
+---
+
+## Transforms — DEFERRED (`01-basics/03-transforms/`)
+
+> Not part of the main basics tutorial. Same rationale as 03-marks above.
+
+Transforms transform data into a shape that
+
+- **Stacking**
+ - [ ] Stacking means taking a single value and converting them into value ranges. Some marks implicitely stack your data, like AreaX or BarX, but you can also explicitely stack.
+ - [ ] Explicit stacking example
+ - [ ] Normalized stacks (offset: normalize)
+
+- **Jitter** (`01-basics/03-transforms/01-jitter/`) — Cars dataset
+ - [x] Jitter transform — spreading overlapping points → `01-jitter/01-jitter`
+ - [x] Reactive jitter — width control via $state → `01-jitter/02-reactive-jitter`
+
+- **Window** (TBD)
+ - [ ] Moving average — window transform
+ - [ ] Bollinger bands
+
+- **Bin** (TBD)
+ - [ ] Basic histogram (RectY + bin)
+ - [ ] Bin options — thresholds, step, domain
+ - [ ] 2D binning — frequency heatmap
+
+- **Group** (TBD)
+ - [ ] Grouped bar chart
+ - [ ] Aggregating into cells (Cell + group)
+
+- **Dodge** (TBD)
+ - [ ] Dodge — non-overlapping layout
+ - [ ] Beeswarm plot
+
+- **Normalize** (TBD)
+
+- **Density** (TBD)
+ - [ ] Density mark — kernel density estimation
+ - [ ] Contour mark — density contour lines
+
+## Mark families
+
+Single x or y position:
+
+- Rule
+- Tick
+- Axis
+- Grid
+
+x/y Position only:
+
+- Dot
+- Vector (x/y + rotation + length)
+- Image
+- Text
+- Custom
+
+Start and end positions
+
+- Link
+- Arrow
+- Rect
+
+Geographical marks
+
+- Geo
+- Sphere
+- Graticule
+
+Multiple data points to one element:
+
+- Line
+- Area
+- Trail
+- Density
+- Contour
+
+Composite marks:
+
+- Difference
+- Box
+- Regression
diff --git a/src/lib/server/shikiDiffNotation.ts b/src/lib/server/shikiDiffNotation.ts
new file mode 100644
index 000000000..53be9a83c
--- /dev/null
+++ b/src/lib/server/shikiDiffNotation.ts
@@ -0,0 +1,143 @@
+import type { DecorationItem, ShikiTransformer } from 'shiki';
+
+export function shikiFileHeader(className = 'line--file-header'): ShikiTransformer {
+ return {
+ name: 'shiki-file-header',
+ line(node) {
+ const text = node.children
+ .flatMap((child) => (child.type === 'element' ? child.children : []))
+ .filter((n) => n.type === 'text')
+ .map((n) => n.value)
+ .join('');
+ if (text.startsWith('/// file:')) {
+ this.addClassToHast(node, className);
+ }
+ }
+ };
+}
+
+interface ShikiDiffNotationOptions {
+ classLineAdd?: string;
+ classLineRemove?: string;
+ classInlineAdd?: string;
+ classInlineRemove?: string;
+ classActivePre?: string;
+}
+
+/** Strip inline +++text+++ or ---text--- markers from a line, returning the clean
+ * line text and any decoration ranges (positions are offsets into the clean text). */
+function processInlineMarkers(
+ line: string,
+ lineIndex: number,
+ marker: '+++' | '---',
+ className: string,
+ decorations: DecorationItem[]
+): string {
+ if (!line.includes(marker)) return line;
+ const parts = line.split(marker);
+ let cleanLine = '';
+ let charPos = 0;
+ for (let i = 0; i < parts.length; i++) {
+ cleanLine += parts[i];
+ if (i % 2 === 1 && parts[i].length > 0) {
+ decorations.push({
+ start: { line: lineIndex, character: charPos },
+ end: { line: lineIndex, character: charPos + parts[i].length },
+ properties: { class: className }
+ });
+ }
+ charPos += parts[i].length;
+ }
+ return cleanLine;
+}
+
+export function shikiDiffNotation(options: ShikiDiffNotationOptions = {}): ShikiTransformer {
+ const {
+ classLineAdd = 'add',
+ classLineRemove = 'remove',
+ classInlineAdd = 'inline-add',
+ classInlineRemove = 'inline-remove',
+ classActivePre = 'diff'
+ } = options;
+
+ return {
+ name: 'shiki-diff-notation',
+ preprocess(code) {
+ let hasDiff = false;
+ const decorations: DecorationItem[] = [];
+
+ const lines = code.split('\n').map((line, lineIndex) => {
+ // Whole-line additions/deletions: lines starting with + or -
+ // (the code hook strips the prefix and adds line classes)
+ if (
+ (line.startsWith('+') && !line.startsWith('+++')) ||
+ (line.startsWith('-') && !line.startsWith('---'))
+ ) {
+ hasDiff = true;
+ return line;
+ }
+
+ // Inline: strip +++text+++ / ---text--- markers and record decorations.
+ // Process --- first so that +++ positions are computed against the
+ // already-stripped text (correct offsets when both appear on one line).
+ if (line.includes('---')) {
+ line = processInlineMarkers(
+ line,
+ lineIndex,
+ '---',
+ classInlineRemove,
+ decorations
+ );
+ hasDiff = true;
+ }
+ if (line.includes('+++')) {
+ line = processInlineMarkers(
+ line,
+ lineIndex,
+ '+++',
+ classInlineAdd,
+ decorations
+ );
+ hasDiff = true;
+ }
+
+ return line;
+ });
+
+ if (hasDiff) {
+ (this.meta as Record).hasDiff = true;
+ if (decorations.length > 0) {
+ if (!this.options.decorations) this.options.decorations = [];
+ this.options.decorations.push(...decorations);
+ }
+ return lines.join('\n');
+ }
+ },
+ code(node) {
+ const active =
+ (this.meta as Record).hasDiff || this.options.meta?.diff;
+ if (!active) return;
+ this.addClassToHast(this.pre, classActivePre);
+
+ for (const line of node.children) {
+ if (line.type !== 'element') continue;
+ for (const child of line.children) {
+ if (child.type !== 'element') continue;
+ const text = child.children[0];
+ if (!text || text.type !== 'text') continue;
+
+ if (text.value.startsWith('+')) {
+ text.value = text.value.slice(1);
+ this.addClassToHast(line, classLineAdd);
+ break;
+ }
+ if (text.value.startsWith('-')) {
+ text.value = text.value.slice(1);
+ this.addClassToHast(line, classLineRemove);
+ break;
+ }
+ }
+ }
+ }
+ };
+}
diff --git a/src/lib/server/tutorial.ts b/src/lib/server/tutorial.ts
new file mode 100644
index 000000000..c2f50a4d5
--- /dev/null
+++ b/src/lib/server/tutorial.ts
@@ -0,0 +1,274 @@
+import { marked, type Tokens } from 'marked';
+import { getSingletonHighlighter } from 'shiki';
+import { shikiDiffNotation, shikiFileHeader } from './shikiDiffNotation.js';
+
+export interface ExerciseStub {
+ slug: string;
+ title: string;
+ chapter: string;
+ group: string;
+}
+
+export interface Exercise {
+ slug: string;
+ title: string;
+ chapter: string;
+ group: string;
+ html: string;
+ /** starting file state: path → content, paths relative to /src/lib/ */
+ a: Record;
+ /** solution file state (empty if no solution) */
+ b: Record;
+ prev: ExerciseStub | null;
+ next: ExerciseStub | null;
+ focus: string;
+}
+
+// Eagerly import all tutorial content at build time
+const mds = import.meta.glob('/src/content/tutorial/**/*.md', {
+ query: '?raw',
+ import: 'default',
+ eager: true
+}) as Record;
+
+const assets = import.meta.glob('/src/content/tutorial/**', {
+ query: '?raw',
+ import: 'default',
+ eager: true
+}) as Record;
+
+function get_files(prefix: string): Record {
+ const result: Record = {};
+ for (const [path, content] of Object.entries(assets)) {
+ if (path.startsWith(prefix) && !path.endsWith('.md')) {
+ result[path.slice(prefix.length)] = content as string;
+ }
+ }
+ return result;
+}
+
+function parse_front_matter(md: string): { meta: Record; body: string } {
+ const match = md.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
+ if (!match) return { meta: {}, body: md };
+ const meta: Record = {};
+ for (const line of match[1].split('\n')) {
+ const colon = line.indexOf(':');
+ if (colon !== -1) meta[line.slice(0, colon).trim()] = line.slice(colon + 1).trim();
+ }
+ return { meta, body: match[2] };
+}
+
+let hl_promise: ReturnType | null = null;
+
+function get_highlighter() {
+ if (!hl_promise) {
+ hl_promise = getSingletonHighlighter({
+ themes: ['github-light', 'github-dark'],
+ langs: ['javascript', 'typescript', 'svelte', 'css', 'html', 'bash', 'json', 'text']
+ });
+ }
+ return hl_promise;
+}
+
+let marked_configured = false;
+
+async function ensure_marked_configured() {
+ if (marked_configured) return;
+ const hl = await get_highlighter();
+ marked.use({
+ renderer: {
+ code({ text, lang }: Tokens.Code): string {
+ const language = lang?.toLowerCase() ?? '';
+ if (language) {
+ try {
+ return hl.codeToHtml(text, {
+ lang: language,
+ themes: { light: 'github-light', dark: 'github-dark' },
+ defaultColor: false,
+ transformers: [
+ shikiFileHeader(),
+ shikiDiffNotation({
+ classLineAdd: 'line--added',
+ classLineRemove: 'line--deleted'
+ })
+ ]
+ });
+ } catch {
+ // unknown language — fall through
+ }
+ }
+ const esc = (s: string) =>
+ s.replace(/&/g, '&').replace(//g, '>');
+ return `
${esc(text)}
`;
+ }
+ }
+ });
+ marked_configured = true;
+}
+
+async function render_markdown(body: string): Promise {
+ await ensure_marked_configured();
+ return (await marked(body)) as string;
+}
+
+const TUTORIAL_ROOT = '/src/content/tutorial';
+
+function sorted_keys(obj: Record, prefix: string) {
+ return Object.keys(obj)
+ .filter((k) => k.startsWith(prefix))
+ .sort();
+}
+
+function group_label(dir: string): string {
+ return dir
+ .replace(/^\d+-/, '')
+ .replace(/-/g, ' ')
+ .replace(/\b\w/g, (c) => c.toUpperCase());
+}
+
+interface RawExercise {
+ slug: string;
+ title: string;
+ chapter: string;
+ group: string;
+ mdPath: string;
+ assetPrefix: string;
+ chapterAssetPrefix: string;
+ groupAssetPrefix: string;
+}
+
+function build_exercise_list(): RawExercise[] {
+ const list: RawExercise[] = [];
+
+ // Find all group dirs (e.g. 01-basics, 02-marks) — dirs that contain chapter subdirs
+ const groups = sorted_keys(mds, TUTORIAL_ROOT + '/').reduce((acc, p) => {
+ const rel = p.slice(TUTORIAL_ROOT.length + 1);
+ const parts = rel.split('/');
+ if (parts.length >= 4 && parts[0] !== '+assets') {
+ if (!acc.includes(parts[0])) acc.push(parts[0]);
+ }
+ return acc;
+ }, [] as string[]);
+
+ for (const group of groups) {
+ const groupBase = `${TUTORIAL_ROOT}/${group}`;
+
+ // Chapter dirs within group
+ const chapters = sorted_keys(mds, groupBase + '/').reduce((acc, p) => {
+ const rel = p.slice(groupBase.length + 1);
+ const parts = rel.split('/');
+ if (parts.length >= 3 && parts[0] !== '+assets' && parts[1] !== '+assets') {
+ if (!acc.includes(parts[0])) acc.push(parts[0]);
+ }
+ return acc;
+ }, [] as string[]);
+
+ for (const chapter of chapters) {
+ const chapterBase = `${groupBase}/${chapter}`;
+ const chapterMeta = mds[`${chapterBase}/index.md`] ?? '';
+ const { meta: cm } = parse_front_matter(chapterMeta);
+ const chapterTitle = cm.title ?? chapter;
+
+ // Exercise dirs within chapter
+ const exercises = sorted_keys(mds, chapterBase + '/').reduce((acc, p) => {
+ const rel = p.slice(chapterBase.length + 1);
+ const parts = rel.split('/');
+ if (parts.length >= 2 && parts[0] !== '+assets' && parts[0] !== 'index.md') {
+ if (!acc.includes(parts[0])) acc.push(parts[0]);
+ }
+ return acc;
+ }, [] as string[]);
+
+ for (const ex of exercises) {
+ const mdPath = `${chapterBase}/${ex}/index.md`;
+ if (!mds[mdPath]) continue;
+ const { meta } = parse_front_matter(mds[mdPath]);
+ list.push({
+ slug: `${chapter}/${ex}`,
+ title: meta.title ?? ex,
+ chapter: chapterTitle,
+ group: group_label(group),
+ mdPath,
+ assetPrefix: `${chapterBase}/${ex}/+assets/`,
+ chapterAssetPrefix: `${chapterBase}/+assets/`,
+ groupAssetPrefix: `${groupBase}/+assets/`
+ });
+ }
+ }
+ }
+
+ return list;
+}
+
+const exercise_list = build_exercise_list();
+const exercise_map = new Map(exercise_list.map((e, i) => [e.slug, { ...e, index: i }]));
+
+export function get_exercise_stubs(): ExerciseStub[] {
+ return exercise_list.map(({ slug, title, chapter, group }) => ({
+ slug,
+ title,
+ chapter,
+ group
+ }));
+}
+
+export async function load_exercise(slug: string): Promise {
+ const entry = exercise_map.get(slug);
+ if (!entry) return null;
+
+ const { meta, body } = parse_front_matter(mds[entry.mdPath]);
+ const index = entry.index;
+
+ // Merge shared assets → chapter assets → exercise app-a
+ const shared = get_files(entry.groupAssetPrefix);
+ const chapter_shared = get_files(entry.chapterAssetPrefix);
+ const app_a_raw = get_files(`${entry.assetPrefix}app-a/`);
+ const app_b_raw = get_files(`${entry.assetPrefix}app-b/`);
+
+ // The REPL uses files prefixed with /src/lib/ as editable files
+ // Filter to only src/lib/ files for the editor
+ function only_lib(files: Record) {
+ const out: Record = {};
+ for (const [k, v] of Object.entries(files)) {
+ if (k.startsWith('src/lib/') || k.startsWith('src/routes/')) out[k] = v;
+ }
+ return out;
+ }
+
+ // If app-a is absent, use the previous step's solved state (app-a + app-b) as
+ // the starting point — the REPL merges a and b, so this matches where prev ended.
+ const app_a_layer =
+ Object.keys(app_a_raw).length === 0 && index > 0
+ ? (() => {
+ const prev = exercise_list[index - 1];
+ const prev_a = get_files(`${prev.assetPrefix}app-a/`);
+ const prev_b = get_files(`${prev.assetPrefix}app-b/`);
+ return { ...only_lib(prev_a), ...only_lib(prev_b) };
+ })()
+ : only_lib(app_a_raw);
+
+ const a = { ...only_lib(shared), ...only_lib(chapter_shared), ...app_a_layer };
+ const b = only_lib(app_b_raw);
+
+ const prev = index > 0 ? exercise_list[index - 1] : null;
+ const next = index < exercise_list.length - 1 ? exercise_list[index + 1] : null;
+
+ const focus = meta.focus ?? '/src/lib/App.svelte';
+
+ return {
+ slug,
+ title: meta.title ?? slug,
+ chapter: entry.chapter,
+ group: entry.group,
+ html: await render_markdown(body),
+ a,
+ b,
+ focus: focus.replace(/^\//, ''),
+ prev: prev
+ ? { slug: prev.slug, title: prev.title, chapter: prev.chapter, group: prev.group }
+ : null,
+ next: next
+ ? { slug: next.slug, title: next.title, chapter: next.chapter, group: next.group }
+ : null
+ };
+}
diff --git a/src/routes/+page.md b/src/routes/+page.md
index 1341ff659..5c7c199bf 100644
--- a/src/routes/+page.md
+++ b/src/routes/+page.md
@@ -4,120 +4,120 @@ description: The best visualizations are built with Svelte.
heroImage: /logo.svg
tagline: A Svelte-native visualization framework based on the layered grammar of graphics principles.
actions:
- - label: Getting started
- type: primary
- to: /getting-started
- - label: Why SveltePlot?
- to: /why-svelteplot
- type: flat
- - label: Examples
- to: /examples
- type: flat
+ - label: Getting started
+ type: primary
+ to: /getting-started
+ - label: Why SveltePlot?
+ to: /why-svelteplot
+ type: flat
+ - label: Examples
+ to: /examples
+ type: flat
examples:
- - area/smoothed-area
- - area/density
- - area/layered-density
- - area/streamgraph
- - area/violin
- - area/ridgeline
- - arrow/metro
- - axis/datawrapper-ticks
- - bar/faceted-bars
- - box/box-x-faceted
- - box/box-y
- - box/box-y-facet
- - brush/overview-detail
- - cell/temperatures-threshold
- - contour/volcano
- - contour/weather
- - contour/interactive
- - contour/faceted
- - contour/sampled
- - custom/histogram-topline
- - delaunay/delaunay-link
- - delaunay/hull-species
- - delaunay/voronoi-penguins
- - delaunay/voronoi-mesh-walmart
- - density/grouped
- - density/filled
- - difference/anomaly-baseline
- - difference/apple-yoy
- - difference/trade-balance
- - dot/1-colored-scatterplot
- - dot/beeswarm-bubbles
- - dot/dot-faceted
- - dot/dodge-faceted
- - dot/walmart-stores
- - geo/earthquakes
- - geo/us-choropleth
- - image/image-scatter
- - image/image-beeswarm
- - line/bls
- - line/geo-line
- - line/gradient-line
- - line/indexed-stocks
- - line/running-mean
- - line/parallel-x
- - line/penguins-cdf
- - line/parallel-y-hl
- - link/spherical-link
- - raster/volcano
- - raster/sampled
- - raster/weather
- - rect/binned
- - rect/marimekko
- - rect/marimekko-faceted
- - rect/stacked-histogram
- - regression/grouped
- - regression/log
- - rule/data-rules
- - text/beeswarm-country-codes
- - tick/tick-x
- - trail/countries
- - trail/napoleon
- - trail/tdf
- - vector/shift-map
- - vector/spike-map
- - vector/wind
- - waffle/custom-symbol
- - waffle/stacked-x
+ - area/smoothed-area
+ - area/density
+ - area/layered-density
+ - area/streamgraph
+ - area/violin
+ - area/ridgeline
+ - arrow/metro
+ - axis/datawrapper-ticks
+ - bar/faceted-bars
+ - box/box-x-faceted
+ - box/box-y
+ - box/box-y-facet
+ - brush/overview-detail
+ - cell/temperatures-threshold
+ - contour/volcano
+ - contour/weather
+ - contour/interactive
+ - contour/faceted
+ - contour/sampled
+ - custom/histogram-topline
+ - delaunay/delaunay-link
+ - delaunay/hull-species
+ - delaunay/voronoi-penguins
+ - delaunay/voronoi-mesh-walmart
+ - density/grouped
+ - density/filled
+ - difference/anomaly-baseline
+ - difference/apple-yoy
+ - difference/trade-balance
+ - dot/1-colored-scatterplot
+ - dot/beeswarm-bubbles
+ - dot/dot-faceted
+ - dot/dodge-faceted
+ - dot/walmart-stores
+ - geo/earthquakes
+ - geo/us-choropleth
+ - image/image-scatter
+ - image/image-beeswarm
+ - line/bls
+ - line/geo-line
+ - line/gradient-line
+ - line/indexed-stocks
+ - line/running-mean
+ - line/parallel-x
+ - line/penguins-cdf
+ - line/parallel-y-hl
+ - link/spherical-link
+ - raster/volcano
+ - raster/sampled
+ - raster/weather
+ - rect/binned
+ - rect/marimekko
+ - rect/marimekko-faceted
+ - rect/stacked-histogram
+ - regression/grouped
+ - regression/log
+ - rule/data-rules
+ - text/beeswarm-country-codes
+ - tick/tick-x
+ - trail/countries
+ - trail/napoleon
+ - trail/tdf
+ - vector/shift-map
+ - vector/spike-map
+ - vector/wind
+ - waffle/custom-symbol
+ - waffle/stacked-x
_features:
- - title: Marks
- description: SveltePlot comes with a powerful set of built-in marks for building for your visualizations
- icon:
- type: iconify
- collection: carbon
- name: roadmap
- - title: Automatic scales
- description: Scale types and domains are automatically inferred from your data, unless you customize them
- icon:
- type: iconify
- collection: ri
- name: ruler-line
- - title: Fully reactive
- description: Everything in SveltePlot is fully reactive, the plot just updates when the data or configuration changes
- icon:
- type: iconify
- collection: ri
- name: svelte-line
- - title: TypeScript
- description: All components are fully typed and documented to integrate with VSCode
- icon:
- type: iconify
- collection: nonicons
- name: typescript-16
- - title: Customizable
- description: All components are fully typed and documented to integrate with VSCode
- icon:
- type: iconify
- collection: nonicons
- name: typescript-16
- - title: Written in Svelte5 & TypeScript
- description: All components and props are fully reactive, typed and documented!
- icon:
- type: iconify
- collection: ri
- name: svelte-line
+ - title: Marks
+ description: SveltePlot comes with a powerful set of built-in marks for building for your visualizations
+ icon:
+ type: iconify
+ collection: carbon
+ name: roadmap
+ - title: Automatic scales
+ description: Scale types and domains are automatically inferred from your data, unless you customize them
+ icon:
+ type: iconify
+ collection: ri
+ name: ruler-line
+ - title: Fully reactive
+ description: Everything in SveltePlot is fully reactive, the plot just updates when the data or configuration changes
+ icon:
+ type: iconify
+ collection: ri
+ name: svelte-line
+ - title: TypeScript
+ description: All components are fully typed and documented to integrate with VSCode
+ icon:
+ type: iconify
+ collection: nonicons
+ name: typescript-16
+ - title: Customizable
+ description: All components are fully typed and documented to integrate with VSCode
+ icon:
+ type: iconify
+ collection: nonicons
+ name: typescript-16
+ - title: Written in Svelte5 & TypeScript
+ description: All components and props are fully reactive, typed and documented!
+ icon:
+ type: iconify
+ collection: ri
+ name: svelte-line
---
+```
+
+```svelte
+
+
+
+
+
+ {#snippet children({ data })}
+
+
+ d.Date)}
+ tickFormat={(d) => d.getFullYear()} />
+ d.Close)}
+ tickFormat={(d) => d.toFixed()} />
+ {/snippet}
+
```
@@ -140,50 +142,50 @@ If we only pass an **x** channel to the Pointer mark it will try to find the clo
```svelte live
-
-
- {#snippet children({ data })}
-
- d.Close.toFixed()}
- lineAnchor="bottom"
- fontWeight="bold"
- dy="-10" />
-
- {/snippet}
-
+
+
+ {#snippet children({ data })}
+
+ d.Close.toFixed()}
+ lineAnchor="bottom"
+ fontWeight="bold"
+ dy="-10" />
+
+ {/snippet}
+
```
```svelte
-
-
- {#snippet children({ data })}
-
-
-
- {/snippet}
-
+
+
+ {#snippet children({ data })}
+
+
+
+ {/snippet}
+
```
@@ -191,68 +193,68 @@ We can use the `onupdate` event to fade out marks whenever points are highlighte
```svelte live
-
+ (sel = e)}>
+ {#snippet children({ data })}
+
+
- (sel = e)}>
- {#snippet children({ data })}
-
- d.Close.toFixed()}
- lineAnchor="bottom"
- fontWeight="bold"
- dy="-10" />
-
- {/snippet}
-
+ text={(d) => d.Close.toFixed()}
+ lineAnchor="bottom"
+ fontWeight="bold"
+ dy="-10" />
+
+ {/snippet}
+
```
```svelte
- 0 ? 0.4 : 1} />
- (seletion = data)}>
- {#snippet children({ data })}
-
-
-
- {/snippet}
-
+ 0 ? 0.4 : 1} />
+ (seletion = data)}>
+ {#snippet children({ data })}
+
+
+
+ {/snippet}
+
```
@@ -260,47 +262,47 @@ This works for the **y** channel as well, but note that we only highlight one po
```svelte live
- 0 ? 0.4 : 1} />
- 0 ? 0.4 : 1} />
+ (sel = e)}>
+ {#snippet children({ data })}
+
+ (sel = e)}>
- {#snippet children({ data })}
-
- d.Close.toFixed()}
- lineAnchor="middle"
- textAnchor="end"
- dx={-5}
- frameAnchor="left"
- fontWeight="bold" />
-
- {/snippet}
-
+ text={(d) => d.Close.toFixed()}
+ lineAnchor="middle"
+ textAnchor="end"
+ dx={-5}
+ frameAnchor="left"
+ fontWeight="bold" />
+
+ {/snippet}
+
```
@@ -308,48 +310,48 @@ We can change this behavior by passing a **tolerance** option to the Pointer mar
```svelte live
- 0 ? 0.4 : 1} />
+ (sel = e)}>
+ {#snippet children({ data })}
+
+ 0 ? 0.4 : 1} />
- (sel = e)}>
- {#snippet children({ data })}
-
- d.Close.toFixed()}
- lineAnchor="middle"
- textAnchor="end"
- dx={-5}
- frameAnchor="left"
- fontWeight="bold" />
-
- {/snippet}
-
+ text={(d) => d.Close.toFixed()}
+ lineAnchor="middle"
+ textAnchor="end"
+ dx={-5}
+ frameAnchor="left"
+ fontWeight="bold" />
+
+ {/snippet}
+
```
@@ -357,59 +359,59 @@ Another common use case is to show one point per group. This is useful when you
```svelte live
-
+ 0 ? 0.4 : 1}
+ y="Close"
+ stroke="Symbol" />
+ (sel = e)}
+ maxDistance={30}>
+ {#snippet children({ data })}
+ 0 ? 0.4 : 1}
y="Close"
- stroke="Symbol" />
- d.Close.toFixed()}
+ lineAnchor="bottom"
+ fontWeight="bold"
+ dy="-7" />
+ (sel = e)}
- maxDistance={30}>
- {#snippet children({ data })}
- d.Close.toFixed()}
- lineAnchor="bottom"
- fontWeight="bold"
- dy="-7" />
-
- {/snippet}
-
+ y="Close"
+ fill="Symbol"
+ strokeWidth="0.7"
+ stroke="var(--svelteplot-bg)" />
+ {/snippet}
+
```
diff --git a/src/routes/marks/raster/+page.md b/src/routes/marks/raster/+page.md
index 1e8694633..ca4304409 100644
--- a/src/routes/marks/raster/+page.md
+++ b/src/routes/marks/raster/+page.md
@@ -1,11 +1,17 @@
---
title: Raster mark
+description: Raster mark renders a dense pixel grid by sampling a function or interpolating scattered data — ideal for continuous scalar fields.
+examples:
+ - raster/sampled
+ - raster/interpolated
+ - raster/volcano
+ - raster/weather
+links:
+ examples: /examples/raster
+ api: /api/marks#Raster
+addedIn: '0.12.0'
---
-:::info
-added in 0.12.0
-:::
-
[API Reference](/api/marks#Raster)
The **raster mark** renders data as an [SVG image](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/image), mapping values through the color scale onto a pixel grid. It supports three distinct modes depending on how data is supplied:
@@ -26,36 +32,36 @@ The volcano example below uses an 87 × 61 elevation grid of [Maungawhau (Mt. Ed
```svelte
-
+
```
```svelte live
-
+
```
@@ -63,32 +69,32 @@ By default, the image is stretched to fill the plot using smooth interpolation.
```svelte live
-
- String(d)}
- x={(d, i) => (i % width) + 0.5}
- y={(d, i) => Math.floor(i / width) + 0.5} />
+ x={{ domain: [0, width] }}
+ y={{ domain: [0, height] }}
+ color={{ scheme: 'turbo' }}>
+
+ String(d)}
+ x={(d, i) => (i % width) + 0.5}
+ y={(d, i) => Math.floor(i / width) + 0.5} />
```
@@ -102,39 +108,34 @@ Sometimes your data does not come in a gridded format, but at irregularly distri
```svelte live
-
-
+ projection={{
+ type: 'conic-conformal',
+ domain: land,
+ rotate: [96, 0],
+ center: [0, 56],
+ parallels: [49, 77]
+ }}
+ color={{
+ scheme: 'burd',
+ type: 'quantile',
+ legend: true
+ }}>
+
+
```
@@ -151,85 +152,80 @@ Four interpolation strategies are available:
```svelte live
-
+
-
-
+ projection={{
+ type: 'conic-conformal',
+ domain: land,
+ rotate: [96, 0],
+ center: [0, 56],
+ parallels: [49, 77]
+ }}
+ color={{
+ scheme: 'burd',
+ type: 'quantile'
+ }}>
+
+
```
```svelte
-
-
-
+ projection={{
+ type: 'conic-conformal',
+ domain: land
+ }}
+ color={{
+ scheme: 'burd',
+ type: 'quantile'
+ }}>
+
+
+
```
@@ -237,72 +233,64 @@ Clipping the raster to the land geometry removes those values outside the coastl
```svelte live
+ label="interpolate"
+ options={INTERPOLATIONS}
+ bind:value={interpolate} />
-
-
-
-
-
-
-
-
+ projection={{
+ type: 'conic-conformal',
+ domain: land,
+ rotate: [96, 0],
+ center: [0, 56],
+ parallels: [49, 77]
+ }}
+ color={{
+ scheme: 'burd',
+ type: 'quantile'
+ }}>
+
+
+
+
+
+
+
+
```
@@ -336,69 +324,61 @@ The `blur` option applies a Gaussian blur (in grid pixels) after rasterization.
```svelte live
+ label="blur"
+ bind:value={blur}
+ min={0}
+ max={10}
+ step={1} />
-
-
-
-
-
-
-
-
+ projection={{
+ type: 'conic-conformal',
+ domain: land,
+ rotate: [96, 0],
+ center: [0, 56],
+ parallels: [49, 77]
+ }}
+ color={{
+ scheme: 'burd',
+ type: 'quantile'
+ }}>
+
+
+
+
+
+
+
+
```
@@ -412,52 +392,49 @@ The `x1`, `y1`, `x2`, `y2` props define the data-space bounding box. The example
```svelte live
+ label="pixelSize"
+ bind:value={pixelSize}
+ min={1}
+ max={10}
+ step={1} />
-
+ color={{ scheme: 'turbo', domain: [0, 80] }}
+ aspectRatio={1}>
+
```
```svelte
-
+ color={{ scheme: 'turbo', domain: [0, 80] }}
+ aspectRatio={1}>
+
```
diff --git a/src/routes/marks/rect/+page.md b/src/routes/marks/rect/+page.md
index 3a661f8f3..c8a36ef03 100644
--- a/src/routes/marks/rect/+page.md
+++ b/src/routes/marks/rect/+page.md
@@ -1,5 +1,14 @@
---
title: Rect mark
+description: RectY and RectX draw axis-aligned rectangles defined by x1/x2 and y1/y2 — the foundation for histograms, bin plots, and mosaic charts.
+examples:
+ - rect/histogram
+ - rect/stacked-histogram
+ - rect/faceted-histogram
+ - rect/marimekko
+links:
+ examples: /examples/rect
+ api: /api/marks#RectY
---
The Rect mark can be used to add rectangles to the plot, defined by x1, y1, x2, and y2 coordinates. It is useful in cases where both the x and y axis are using quantitative scales.
@@ -12,40 +21,40 @@ In it's purest form, the `` mark will just add rectangles at the given coo
```svelte live
-
+
```
```svelte
-
+
```
@@ -55,30 +64,30 @@ If your data does not come with x1/x2 and y1/y2 pairs but x/y coordinates, you c
```svelte live
-
+
```
```svelte
-
+
```
@@ -86,50 +95,50 @@ The interval transform may be used to convert a single value in x or y (or both)
```svelte live
- new Intl.DateTimeFormat('en', {
- month: 'narrow'
- }).format(new Date(2000, d, 1))
- }}
- testid="seattle-temp">
- d.date.getUTCFullYear() === 2015}
- x={(d) => d.date.getUTCDate()}
- y={(d) => d.date.getUTCMonth()}
- interval={1}
- fill="temp_max"
- inset="0.5" />
+ aspectRatio={1}
+ y={{
+ ticks: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+ tickFormat: (d) =>
+ new Intl.DateTimeFormat('en', {
+ month: 'narrow'
+ }).format(new Date(2000, d, 1))
+ }}
+ testid="seattle-temp">
+ d.date.getUTCFullYear() === 2015}
+ x={(d) => d.date.getUTCDate()}
+ y={(d) => d.date.getUTCMonth()}
+ interval={1}
+ fill="temp_max"
+ inset="0.5" />
```
```svelte
- new Intl.DateTimeFormat('en', {
- month: 'narrow'
- }).format(new Date(2000, d, 1))
- }}>
- d.date.getUTCFullYear() === 2015}
- x={(d) => d.date.getUTCDate()}
- y={(d) => d.date.getUTCMonth()}
- interval={1}
- fill="temp_max"
- inset="0.5" />
+ aspectRatio={1}
+ y={{
+ ticks: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+ tickFormat: (d) =>
+ new Intl.DateTimeFormat('en', {
+ month: 'narrow'
+ }).format(new Date(2000, d, 1))
+ }}>
+ d.date.getUTCFullYear() === 2015}
+ x={(d) => d.date.getUTCDate()}
+ y={(d) => d.date.getUTCMonth()}
+ interval={1}
+ fill="temp_max"
+ inset="0.5" />
```
@@ -165,40 +174,40 @@ RectX can be used for range annotations:
```svelte live
-
-
+
+
```
```svelte
-
-
+
+
```
@@ -210,30 +219,30 @@ RectY can be used for range annotations:
```svelte live
-
-
+
+
```
```svelte
-
-
+
+
```
@@ -243,47 +252,47 @@ RectX marks can be stacked along the x dimension (over identical y1 values):
```svelte live
-
+
```
```svelte
-
+
```
@@ -293,35 +302,35 @@ RectY marks can be stacked along y (over identical x1 values).
```svelte live
-
+
```
```svelte
-
+
```
diff --git a/src/routes/marks/regression/+page.md b/src/routes/marks/regression/+page.md
index d73c723bf..5bee7f224 100644
--- a/src/routes/marks/regression/+page.md
+++ b/src/routes/marks/regression/+page.md
@@ -1,73 +1,82 @@
---
title: Regression mark
+description: RegressionY and RegressionX fit and draw a linear, polynomial, or loess regression line through a point cloud.
+examples:
+ - regression/cars
+ - regression/faceted
+ - regression/loess
+ - regression/grouped
+links:
+ examples: /examples/regression
+ api: /api/marks#RegressionY
---
Regressions are useful if you want to show the relationship between two variables. The following plot shows how the weight of cars depends on their power. The mark is using [d3-regression](https://github.com/harrystevens/d3-regression) for computing the regression lines.
```svelte live
{#if type === 'poly'}{/if}
+ label="order"
+ bind:value={order}
+ min={2}
+ max={6} />{/if}
{#if type.startsWith('loess')}
- {/if}
+ {/if}