|
6 | 6 |
|
7 | 7 | This notebook interpolates smoothly between projections; this is easiest when both projections are well-defined over the given viewport (here, the world).`
|
8 | 8 | </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 | + })() |
23 | 47 | </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(); |
32 | 54 | </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=""> |
34 | 59 | function render(projection) {
|
35 | 60 | const path = d3.geoPath(projection, context);
|
36 | 61 | context.clearRect(0, 0, width, height);
|
|
42 | 67 | context.beginPath(), path(outline), context.strokeStyle = "#000", context.stroke();
|
43 | 68 | }
|
44 | 69 | </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=""> |
63 | 71 | function interpolateProjection(raw0, raw1) {
|
64 | 72 | const {scale: scale0, translate: translate0} = fit(raw0);
|
65 | 73 | const {scale: scale1, translate: translate1} = fit(raw1);
|
|
68 | 76 | .translate(lerp2(translate0, translate1, t))
|
69 | 77 | .precision(0.1);
|
70 | 78 | }
|
71 |
| - </script> |
72 |
| - <script id="343" type="application/vnd.observable.javascript" pinned=""> |
| 79 | + |
73 | 80 | function lerp1(x0, x1, t) {
|
74 | 81 | return (1 - t) * x0 + t * x1;
|
75 | 82 | }
|
76 |
| - </script> |
77 |
| - <script id="342" type="application/vnd.observable.javascript" pinned=""> |
| 83 | + |
78 | 84 | function lerp2([x0, y0], [x1, y1], t) {
|
79 | 85 | return [(1 - t) * x0 + t * x1, (1 - t) * y0 + t * y1];
|
80 | 86 | }
|
81 |
| - </script> |
82 |
| - <script id="292" type="application/vnd.observable.javascript" pinned=""> |
| 87 | + |
83 | 88 | function fit(raw) {
|
84 | 89 | const p = d3.geoProjection(raw).fitExtent([[0.5, 0.5], [width - 0.5, height - 0.5]], outline);
|
85 | 90 | return {scale: p.scale(), translate: p.translate()};
|
86 | 91 | }
|
87 | 92 | </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 = [ |
117 | 105 | {name: "Aitoff", value: d3.geoAitoffRaw},
|
118 | 106 | {name: "American polyconic", value: d3.geoPolyconicRaw},
|
119 | 107 | {name: "August", value: d3.geoAugustRaw},
|
|
179 | 167 | {name: "Wagner VIII", value: d3.geoWagnerRaw(65 / 180 * Math.PI, 60 / 180 * Math.PI, 20, 200)},
|
180 | 168 | {name: "Werner", value: d3.geoBonneRaw(Math.PI / 2)},
|
181 | 169 | {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 | + ]; |
200 | 171 | </script>
|
201 | 172 | </notebook>
|
0 commit comments