Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/src/content/components/GeoContext.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,15 @@ related: [Chart]
## Playground

<Example name="projection-playground" />

## Geojson Preview

<Example name="geojson-preview" />

## Topojson Preview

<Example name="topojson-preview" />

## Shapefile Preview

<Example name="shapefile-preview" />
34 changes: 34 additions & 0 deletions docs/src/content/components/LineChart.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,40 @@ related: [Chart, Spline]

<Example name="basic" showCode />

### Performance Wide Data

> Wide data (property per series)

<Example name="perf-wide-data" />

### Performance Wide Data Processed

> Wide data (property per series). Pre-processed before passed to LineChart

<Example name="perf-wide-data-processed" />

### Performance Series Arrays

> Array per series, each with `x` / `y` items

<Example name="perf-series-arrays" />

### Performance Dimension Arrays

> Individual arrays per dimension, similar to uplot

<Example name="perf-dimension-arrays" />

### Performance Dimension Arrays Processed

> Individual arrays per dimension, similar to uplot. Pre-processed before passed to LineChart

<Example name="perf-dimension-arrays-processed" />

### Performance Streaming

<Example name="perf-streaming" />

<!-- ## Examples

### Basic
Expand Down
164 changes: 164 additions & 0 deletions docs/src/examples/GeoContext/geojson-preview.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<script lang="ts">
import type { ComponentProps } from 'svelte';
import {
geoAlbersUsa,
geoAlbers,
geoEqualEarth,
geoEquirectangular,
geoMercator,
geoNaturalEarth1,
geoOrthographic,
geoIdentity,
type GeoProjection
} from 'd3-geo';
import { scaleOrdinal } from 'd3-scale';
import { schemeCategory10 } from 'd3-scale-chromatic';
import { color } from 'd3-color';

import { Chart, GeoPath, GeoTile, Layer, Tooltip } from 'layerchart';
import TransformControls from '$lib/components/controls/TransformContextControls.svelte';
import {
EmptyMessage,
RangeField,
SelectField,
TextField,
ToggleGroup,
ToggleOption
} from 'svelte-ux';

import TilesetField from '$lib/components/controls/GeoTileControls.svelte';
import Json from '$lib/components/Json.svelte';

let geojsonStr = $state('');
let geojson = $state<GeoJSON.FeatureCollection>();
let error = $state('');

let selectedTab: 'input' | 'geojson' = $state('input');

$effect.pre(() => {
if (geojsonStr) {
try {
geojson = JSON.parse(geojsonStr);
error = '';
} catch (e) {
error = 'Invalid object';
console.error(e);
}
}
});

let projection = $state(geoMercator);
const projections = [
{ label: 'Identity', value: geoIdentity as () => GeoProjection },
{ label: 'Albers', value: geoAlbers },
{ label: 'Albers USA', value: geoAlbersUsa },
{ label: 'Equal Earth', value: geoEqualEarth },
{ label: 'Equirectangular', value: geoEquirectangular },
{ label: 'Mercator', value: geoMercator },
{ label: 'Natural Earth', value: geoNaturalEarth1 },
{ label: 'Orthographic', value: geoOrthographic }
];

let serviceUrl = $state<ComponentProps<typeof GeoTile>['url']>();
let zoomDelta = $state(0);

const colorScale = scaleOrdinal<string>().range(
schemeCategory10.map((hex) => {
let c = color(hex)!;
c.opacity = 0.5;
return c.toString() ?? '';
})
);
</script>

<div class="grid gap-2">
<div class="grid grid-cols-3 gap-2">
<SelectField
label="Projections"
options={projections}
bind:value={projection}
clearable={false}
/>

<TilesetField bind:serviceUrl />
<RangeField label="Zoom delta" bind:value={zoomDelta} min={-5} max={5} />
</div>

<div class="h-[600px] bg-surface-100/50 border rounded-lg overflow-hidden">
{#if geojson}
<Chart
geo={{
projection,
fitGeojson: geojson,
applyTransform: ['translate', 'scale']
}}
transform={{
initialScrollMode: 'scale'
}}
padding={{ top: 8, bottom: 8, left: 8, right: 8 }}
>
{#snippet children({ context })}
{#if projection === geoMercator && serviceUrl}
<Layer>
<!-- technique: https://observablehq.com/@d3/seamless-zoomable-map-tiles -->
<GeoTile url={serviceUrl} zoomDelta={-100} />
<GeoTile url={serviceUrl} zoomDelta={-4} />
<GeoTile url={serviceUrl} zoomDelta={-1} />
<GeoTile url={serviceUrl} {zoomDelta} />
</Layer>
{/if}

<TransformControls />

<Layer>
{#if geojson?.features}
{#each geojson?.features as feature}
<GeoPath
geojson={feature}
fill={colorScale(String(feature.id))}
class="stroke-black"
tooltipContext={context.tooltip}
/>
{/each}
{/if}
</Layer>

<Tooltip.Root>
{#snippet children({ data })}
<Tooltip.List>
{#each Object.entries(data.properties) as [key, value]}
<Tooltip.Item label={key} {value} />
{/each}
</Tooltip.List>
{/snippet}
</Tooltip.Root>
{/snippet}
</Chart>
{:else}
<EmptyMessage class="h-full">Please enter input below</EmptyMessage>
{/if}
</div>

<ToggleGroup
bind:value={selectedTab}
variant="underline"
classes={{ options: 'justify-start h-10' }}
>
<ToggleOption value="input">Input</ToggleOption>
<ToggleOption value="geojson">Parsed</ToggleOption>
</ToggleGroup>

{#if selectedTab === 'input'}
<TextField
label="GeoJSON"
bind:value={geojsonStr}
placeholder={'{"type": "FeatureCollection", "features": [...] }'}
multiline
classes={{
input: 'h-[400px]'
}}
/>
{:else if selectedTab === 'geojson'}
<Json value={geojson} />
{/if}
</div>
117 changes: 117 additions & 0 deletions docs/src/examples/GeoContext/shapefile-preview.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<script lang="ts">
import {
geoAlbersUsa,
geoAlbers,
geoEqualEarth,
geoEquirectangular,
geoMercator,
geoNaturalEarth1,
geoOrthographic,
geoIdentity,
type GeoProjection
} from 'd3-geo';

import { Chart, GeoPath, Layer } from 'layerchart';
import {
Button,
ButtonGroup,
EmptyMessage,
Menu,
MenuItem,
SelectField,
TextField,
Toggle
} from 'svelte-ux';

import LucideChevronDown from '~icons/lucide/chevron-down';
import { getShapeData } from '$lib/data.remote.js';

import { goto } from '$app/navigation';

let data = await getShapeData(null);
let file = $state(data.file);

const geojson = $derived(data.geojson);

let projection = $state(geoIdentity as unknown as () => GeoProjection);
const projections = [
{ label: 'Identity', value: geoIdentity as () => GeoProjection },
{ label: 'Albers', value: geoAlbers },
{ label: 'Albers USA', value: geoAlbersUsa },
{ label: 'Equal Earth', value: geoEqualEarth },
{ label: 'Equirectangular', value: geoEquirectangular },
{ label: 'Mercator', value: geoMercator },
{ label: 'Natural Earth', value: geoNaturalEarth1 },
{ label: 'Orthographic', value: geoOrthographic }
];

function loadFile() {
goto(`?file=${file}`);
}
</script>

<div class="grid gap-2">
<div class="grid grid-cols-[1fr_auto] gap-2 items-center">
<TextField
label="File"
bind:value={file}
placeholder="Please specify a file or load an example"
>
<div slot="append">
<ButtonGroup variant="fill-outline" color="primary">
<Button on:click={() => loadFile()}>Load file</Button>
<Toggle let:on={open} let:toggle>
<span class="flex">
<Button icon={LucideChevronDown} on:click={toggle} rounded class="px-1" />
<Menu {open} on:close={toggle} placement="bottom-end">
<MenuItem
on:click={() => {
file = 'https://cdn.rawgit.com/mbostock/shapefile/master/test/points.shp';
loadFile();
}}
>
Load basic example
</MenuItem>
<MenuItem
on:click={() => {
file =
'https://cdn.rawgit.com/matplotlib/basemap/v1.1.0/lib/mpl_toolkits/basemap/data/UScounties.shp';
loadFile();
}}
>
Load complex example
</MenuItem>
</Menu>
</span>
</Toggle>
</ButtonGroup>
</div>
</TextField>

<SelectField
label="Projections"
options={projections}
bind:value={projection}
clearable={false}
toggleIcon={null}
stepper
/>
</div>

<div class="h-[600px]">
{#if geojson}
<Chart
geo={{
projection,
fitGeojson: geojson
}}
>
<Layer>
<GeoPath {geojson} fill="white" />
</Layer>
</Chart>
{:else}
<EmptyMessage class="h-full">Please specify a file</EmptyMessage>
{/if}
</div>
</div>
Loading
Loading