Skip to content

Commit 9a4b917

Browse files
committed
better projection transitions
1 parent f0d3d33 commit 9a4b917

File tree

1 file changed

+65
-94
lines changed

1 file changed

+65
-94
lines changed

docs/ex/d3/projection-transitions.html

Lines changed: 65 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,56 @@
66
77
This notebook interpolates smoothly between projections; this is easiest when both projections are well-defined over the given viewport (here, the world).`
88
</script>
9-
<script id="6" type="application/vnd.observable.javascript">
10-
viewof projection = {
11-
const input = projectionInput({
12-
value: new URLSearchParams(location.search).get("projection"),
13-
name: "projection"
14-
});
15-
const interval = setInterval(() => {
16-
input.i.selectedIndex = (input.i.selectedIndex + 1) % projections.length;
17-
input.dispatchEvent(new CustomEvent("input"));
18-
}, 1500);
19-
input.addEventListener("change", () => clearInterval(interval));
20-
invalidation.then(() => clearInterval(interval));
21-
return input;
22-
}
9+
<script id="6" type="module">
10+
const input = Inputs.select(
11+
new Map(projections.map((p) => [p.name, p.value])),
12+
{
13+
key: new URLSearchParams(location.search).get("projection"),
14+
label: "Projection"
15+
}
16+
);
17+
const projection = view(input);
18+
const interval = setInterval(() => {
19+
input.input.selectedIndex = (input.input.selectedIndex + 1) % projections.length;
20+
input.dispatchEvent(new CustomEvent("input", {bubbles: true}));
21+
}, 1500);
22+
input.addEventListener("change", () => clearInterval(interval));
23+
invalidation.then(() => clearInterval(interval));
24+
</script>
25+
<script id="10" type="module" pinned="">
26+
const width = 960;
27+
const height = 600;
28+
const context = DOM.context2d(width, height);
29+
context.canvas.style.display = "block";
30+
context.canvas.style.maxWidth = "100%";
31+
context.canvas.value = context;
32+
context.projection = d3.geoEquirectangularRaw; // initial projection
33+
display(context.canvas);
34+
</script>
35+
<script id="38" type="module" pinned="" hidden="">
36+
(function* animate() {
37+
const r0 = context.projection;
38+
const r1 = context.projection = projection;
39+
if (r0 === r1) return;
40+
const interpolate = interpolateProjection(r0, r1);
41+
for (let j = 1, m = 45; true; ++j) {
42+
const t = Math.min(1, d3.easeCubicInOut(j / m));
43+
render(interpolate(t).rotate([performance.now() / 100, 0]));
44+
yield;
45+
}
46+
})()
2347
</script>
24-
<script id="10" type="application/vnd.observable.javascript" pinned="">
25-
viewof context = {
26-
const context = DOM.context2d(width, height);
27-
context.canvas.style.display = "block";
28-
context.canvas.style.maxWidth = "100%";
29-
context.canvas.value = context;
30-
return context.canvas;
31-
}
48+
<script id="21" type="module" pinned="">
49+
const outline = {type: "Sphere"};
50+
const graticule = d3.geoGraticule10();
51+
</script>
52+
<script id="345" type="module" pinned="">
53+
const world = FileAttachment("data/land-110m.json").json();
3254
</script>
33-
<script id="109" type="application/vnd.observable.javascript" pinned="">
55+
<script id="346" type="module" pinned="">
56+
const land = topojson.feature(world, world.objects.land);
57+
</script>
58+
<script id="109" type="module" pinned="">
3459
function render(projection) {
3560
const path = d3.geoPath(projection, context);
3661
context.clearRect(0, 0, width, height);
@@ -42,24 +67,7 @@
4267
context.beginPath(), path(outline), context.strokeStyle = "#000", context.stroke();
4368
}
4469
</script>
45-
<script id="38" type="application/vnd.observable.javascript" pinned="">
46-
update = {
47-
const r0 = mutable previousProjection;
48-
const r1 = projection;
49-
if (r0 === r1) return;
50-
mutable previousProjection = r1;
51-
const interpolate = interpolateProjection(r0, r1);
52-
for (let j = 1, m = 45; true; ++j) {
53-
const t = Math.min(1, ease(j / m));
54-
render(interpolate(t).rotate([performance.now() / 100, 0]));
55-
yield;
56-
}
57-
}
58-
</script>
59-
<script id="90" type="application/vnd.observable.javascript" pinned="">
60-
mutable previousProjection = d3.geoEquirectangularRaw
61-
</script>
62-
<script id="68" type="application/vnd.observable.javascript" pinned="">
70+
<script id="68" type="module" pinned="">
6371
function interpolateProjection(raw0, raw1) {
6472
const {scale: scale0, translate: translate0} = fit(raw0);
6573
const {scale: scale1, translate: translate1} = fit(raw1);
@@ -68,52 +76,32 @@
6876
.translate(lerp2(translate0, translate1, t))
6977
.precision(0.1);
7078
}
71-
</script>
72-
<script id="343" type="application/vnd.observable.javascript" pinned="">
79+
7380
function lerp1(x0, x1, t) {
7481
return (1 - t) * x0 + t * x1;
7582
}
76-
</script>
77-
<script id="342" type="application/vnd.observable.javascript" pinned="">
83+
7884
function lerp2([x0, y0], [x1, y1], t) {
7985
return [(1 - t) * x0 + t * x1, (1 - t) * y0 + t * y1];
8086
}
81-
</script>
82-
<script id="292" type="application/vnd.observable.javascript" pinned="">
87+
8388
function fit(raw) {
8489
const p = d3.geoProjection(raw).fitExtent([[0.5, 0.5], [width - 0.5, height - 0.5]], outline);
8590
return {scale: p.scale(), translate: p.translate()};
8691
}
8792
</script>
88-
<script id="113" type="application/vnd.observable.javascript" pinned="">
89-
ease = d3.easeCubicInOut
90-
</script>
91-
<script id="28" type="application/vnd.observable.javascript" pinned="">
92-
width = 954
93-
</script>
94-
<script id="24" type="application/vnd.observable.javascript" pinned="">
95-
height = 600
96-
</script>
97-
<script id="21" type="application/vnd.observable.javascript" pinned="">
98-
outline = ({type: "Sphere"})
99-
</script>
100-
<script id="19" type="application/vnd.observable.javascript" pinned="">
101-
graticule = d3.geoGraticule10()
102-
</script>
103-
<script id="18" type="application/vnd.observable.javascript" pinned="">
104-
land = topojson.feature(world, world.objects.land)
105-
</script>
106-
<script id="15" type="application/vnd.observable.javascript" pinned="">
107-
world = FileAttachment("data/land-110m.json").json()
108-
</script>
109-
<script id="13" type="application/vnd.observable.javascript" pinned="">
110-
topojson = require("topojson-client@3")
111-
</script>
112-
<script id="12" type="application/vnd.observable.javascript" pinned="">
113-
d3 = require("d3-geo@2", "d3-geo-projection@3", "d3-ease@2")
114-
</script>
115-
<script id="169" type="application/vnd.observable.javascript" pinned="">
116-
projections = [
93+
<script id="12" type="module" pinned="">
94+
const d3 = Object.assign(
95+
{},
96+
...(await Promise.all([
97+
import("npm:d3-geo@2"),
98+
import("npm:d3-geo-projection@3"),
99+
import("npm:d3-ease@2")
100+
]))
101+
);
102+
</script>
103+
<script id="169" type="module" pinned="">
104+
const projections = [
117105
{name: "Aitoff", value: d3.geoAitoffRaw},
118106
{name: "American polyconic", value: d3.geoPolyconicRaw},
119107
{name: "August", value: d3.geoAugustRaw},
@@ -179,23 +167,6 @@
179167
{name: "Wagner VIII", value: d3.geoWagnerRaw(65 / 180 * Math.PI, 60 / 180 * Math.PI, 20, 200)},
180168
{name: "Werner", value: d3.geoBonneRaw(Math.PI / 2)},
181169
{name: "Winkel tripel", value: d3.geoWinkel3Raw}
182-
]
183-
</script>
184-
<script id="5" type="application/vnd.observable.javascript" pinned="">
185-
function projectionInput({name = "", value} = {}) {
186-
const form = html`<form><select name=i>${projections.map((p) => {
187-
return Object.assign(html`<option>`, {
188-
textContent: p.name,
189-
selected: p.name === value
190-
});
191-
})}</select> <i style="font-size:smaller;">${name}</i>`;
192-
form.onchange = () => form.dispatchEvent(new CustomEvent("input")); // Safari
193-
form.oninput = (event) => {
194-
if (event && event.isTrusted) form.onchange = null;
195-
form.value = projections[form.i.selectedIndex].value;
196-
};
197-
form.oninput();
198-
return form;
199-
}
170+
];
200171
</script>
201172
</notebook>

0 commit comments

Comments
 (0)