|
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