Skip to content

Commit f00228c

Browse files
committed
Add interpolating projections examples
1 parent 1d888f8 commit f00228c

1 file changed

Lines changed: 184 additions & 0 deletions

File tree

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
<script lang="ts">
2+
import { cubicInOut } from 'svelte/easing';
3+
import {
4+
geoProjection,
5+
geoEquirectangularRaw,
6+
geoMercatorRaw,
7+
geoNaturalEarth1Raw,
8+
geoEqualEarthRaw,
9+
geoOrthographicRaw,
10+
geoStereographicRaw,
11+
geoGnomonicRaw,
12+
type GeoRawProjection
13+
} from 'd3-geo';
14+
import {
15+
geoAitoffRaw,
16+
geoAugustRaw,
17+
geoBakerRaw,
18+
geoBoggsRaw,
19+
geoBromleyRaw,
20+
geoCollignonRaw,
21+
geoCrasterRaw,
22+
geoEckert1Raw,
23+
geoEckert3Raw,
24+
geoEckert5Raw,
25+
geoFaheyRaw,
26+
geoKavrayskiy7Raw,
27+
geoLarriveeRaw,
28+
geoMillerRaw,
29+
geoNaturalEarth2Raw,
30+
geoPattersonRaw,
31+
geoRobinsonRaw,
32+
geoSinusoidalRaw,
33+
geoTimesRaw,
34+
geoVanDerGrintenRaw,
35+
geoWiechelRaw,
36+
geoWinkel3Raw
37+
} from 'd3-geo-projection';
38+
import { feature } from 'topojson-client';
39+
40+
import { Chart, GeoPath, Graticule, Layer } from 'layerchart';
41+
import { Button, ButtonGroup, Field, RangeField, SelectField, Switch } from 'svelte-ux';
42+
import { getCountriesTopology } from '$lib/geo.remote.js';
43+
44+
const topology = await getCountriesTopology();
45+
const land = feature(topology, topology.objects.land);
46+
47+
const projections = [
48+
{ label: 'Aitoff', value: geoAitoffRaw },
49+
{ label: 'August', value: geoAugustRaw },
50+
{ label: 'Baker', value: geoBakerRaw },
51+
{ label: 'Boggs', value: geoBoggsRaw },
52+
{ label: 'Bromley', value: geoBromleyRaw },
53+
{ label: 'Collignon', value: geoCollignonRaw },
54+
{ label: 'Craster', value: geoCrasterRaw },
55+
{ label: 'Eckert I', value: geoEckert1Raw },
56+
{ label: 'Eckert III', value: geoEckert3Raw },
57+
{ label: 'Eckert V', value: geoEckert5Raw },
58+
{ label: 'Equal Earth', value: geoEqualEarthRaw },
59+
{ label: 'Equirectangular', value: geoEquirectangularRaw },
60+
{ label: 'Fahey', value: geoFaheyRaw },
61+
{ label: 'Gnomonic', value: geoGnomonicRaw },
62+
{ label: 'Kavrayskiy VII', value: geoKavrayskiy7Raw },
63+
{ label: 'Larrivee', value: geoLarriveeRaw },
64+
{ label: 'Mercator', value: geoMercatorRaw },
65+
{ label: 'Miller', value: geoMillerRaw },
66+
{ label: 'Natural Earth 1', value: geoNaturalEarth1Raw },
67+
{ label: 'Natural Earth 2', value: geoNaturalEarth2Raw },
68+
{ label: 'Orthographic', value: geoOrthographicRaw },
69+
{ label: 'Patterson', value: geoPattersonRaw },
70+
{ label: 'Robinson', value: geoRobinsonRaw },
71+
{ label: 'Sinusoidal', value: geoSinusoidalRaw },
72+
{ label: 'Stereographic', value: geoStereographicRaw },
73+
{ label: 'Times', value: geoTimesRaw },
74+
{ label: 'Van der Grinten', value: geoVanDerGrintenRaw },
75+
{ label: 'Wiechel', value: geoWiechelRaw },
76+
{ label: 'Winkel Tripel', value: geoWinkel3Raw }
77+
];
78+
79+
const FRAMES = 480;
80+
81+
let rawFrom: GeoRawProjection = $state(geoMercatorRaw as unknown as GeoRawProjection);
82+
let rawTo: GeoRawProjection = $state(geoOrthographicRaw as unknown as GeoRawProjection);
83+
let showGraticule = $state(true);
84+
let animating = $state(true);
85+
let currentFrame = $state(0);
86+
let manualT = $state(0);
87+
let scale = $state(150);
88+
89+
// Ping-pong easing: 0 -> 1 -> 0 over 2*FRAMES
90+
const animatedT = $derived.by(() => {
91+
const frame = currentFrame % (FRAMES * 2);
92+
const phase = frame < FRAMES ? frame / FRAMES : 2 - frame / FRAMES;
93+
return cubicInOut(phase);
94+
});
95+
96+
const t = $derived(animating ? animatedT : manualT);
97+
98+
// Interpolated projection factory -- recreated each frame as t updates
99+
const projectionFactory = $derived.by(() => {
100+
const currentT = t;
101+
const r0 = rawFrom;
102+
const r1 = rawTo;
103+
return () => {
104+
const raw = (lambda: number, phi: number): [number, number] => {
105+
const [x0, y0] = r0(lambda, phi) as [number, number];
106+
const [x1, y1] = r1(lambda, phi) as [number, number];
107+
return [x0 + currentT * (x1 - x0), y0 + currentT * (y1 - y0)];
108+
};
109+
return geoProjection(raw).precision(0.1);
110+
};
111+
});
112+
113+
// Animation loop
114+
$effect(() => {
115+
if (!animating) return;
116+
117+
let rafId: number;
118+
function tick() {
119+
currentFrame++;
120+
rafId = requestAnimationFrame(tick);
121+
}
122+
rafId = requestAnimationFrame(tick);
123+
124+
return () => cancelAnimationFrame(rafId);
125+
});
126+
127+
const data = { topology };
128+
export { data };
129+
</script>
130+
131+
<div class="grid grid-cols-[1fr_1fr] gap-2 mb-4 screenshot-hidden">
132+
<SelectField
133+
label="From"
134+
options={projections}
135+
bind:value={rawFrom}
136+
clearable={false}
137+
toggleIcon={null}
138+
stepper
139+
/>
140+
<SelectField
141+
label="To"
142+
options={projections}
143+
bind:value={rawTo}
144+
clearable={false}
145+
toggleIcon={null}
146+
stepper
147+
/>
148+
</div>
149+
<div class="flex gap-4 items-center mb-4 screenshot-hidden">
150+
<Field label="Graticule" dense labelPlacement="left" let:id>
151+
<Switch {id} bind:checked={showGraticule} size="md" />
152+
</Field>
153+
<ButtonGroup size="sm" variant="fill-light">
154+
<Button on:click={() => (animating = true)} disabled={animating}>Play</Button>
155+
<Button on:click={() => (animating = false)} disabled={!animating}>Pause</Button>
156+
</ButtonGroup>
157+
<RangeField
158+
label="Blend"
159+
bind:value={manualT}
160+
min={0}
161+
max={1}
162+
step={0.001}
163+
disabled={animating}
164+
class="flex-1"
165+
/>
166+
<RangeField label="Scale" bind:value={scale} min={10} max={500} step={1} class="flex-1" />
167+
</div>
168+
169+
<Chart
170+
geo={{
171+
projection: projectionFactory,
172+
scale
173+
}}
174+
height={800}
175+
clip
176+
>
177+
<Layer>
178+
<GeoPath geojson={{ type: 'Sphere' }} class="stroke-surface-content/20 fill-none" />
179+
{#if showGraticule}
180+
<Graticule class="stroke-surface-content/10" />
181+
{/if}
182+
<GeoPath geojson={land} class="fill-surface-content/15" />
183+
</Layer>
184+
</Chart>

0 commit comments

Comments
 (0)