|
| 1 | +// URL: https://beta.observablehq.com/@forresto/d3-azimuthal-cities-straight-line-routes |
| 2 | +// Title: Azimuthal Artifact |
| 3 | +// Author: Forrest Oliphant (@forresto) |
| 4 | +// Version: 280 |
| 5 | +// Runtime version: 1 |
| 6 | + |
| 7 | +const m0 = { |
| 8 | + id: "e6547e85ad478b83@280", |
| 9 | + variables: [ |
| 10 | + { |
| 11 | + inputs: ["md"], |
| 12 | + value: (function(md){return( |
| 13 | +md`# Azimuthal Artifact |
| 14 | +
|
| 15 | +Todo: |
| 16 | +- Make a new non-equidistant (logarithmic?) azimuthal projection, to give closer locations more room |
| 17 | +- Place names around edge |
| 18 | +- Experiment with design to make it suitable for laser engraving |
| 19 | +
|
| 20 | +Non-map version, designed as artifact to have spatial intuition of friends, family, memories... |
| 21 | + |
| 22 | +` |
| 23 | +)}) |
| 24 | + }, |
| 25 | + { |
| 26 | + name: "viewof center", |
| 27 | + inputs: ["html","cities","option"], |
| 28 | + value: (function(html,cities,option) |
| 29 | +{ |
| 30 | + const form = html` |
| 31 | +<form><select name=select> |
| 32 | + ${cities.map(option)} |
| 33 | + <!-- |
| 34 | + <option value="[0, 0]">Standard aspect</option> |
| 35 | + <option value="[0, 90]">North polar aspect</option> |
| 36 | + <option value="[0, -90]">South polar aspect</option> |
| 37 | + --> |
| 38 | +</select></form>`; |
| 39 | + form.oninput = () => (form.value = JSON.parse(form.select.value)); |
| 40 | + form.oninput(); |
| 41 | + return form; |
| 42 | +} |
| 43 | +) |
| 44 | + }, |
| 45 | + { |
| 46 | + name: "center", |
| 47 | + inputs: ["Generators","viewof center"], |
| 48 | + value: (G, _) => G.input(_) |
| 49 | + }, |
| 50 | + { |
| 51 | + name: "map", |
| 52 | + inputs: ["DOM","width","height","d3","projection","graticule","land","sphere","lines"], |
| 53 | + value: (function(DOM,width,height,d3,projection,graticule,land,sphere,lines) |
| 54 | +{ |
| 55 | + const context = DOM.context2d(width, height); |
| 56 | + const path = d3.geoPath(projection, context); |
| 57 | + context.beginPath(), |
| 58 | + path(graticule), |
| 59 | + (context.strokeStyle = "#eee"), |
| 60 | + context.stroke(); |
| 61 | + context.beginPath(), |
| 62 | + path(land), |
| 63 | + (context.strokeStyle = "#aaa"), |
| 64 | + context.stroke(); |
| 65 | + context.beginPath(), |
| 66 | + path(sphere), |
| 67 | + (context.strokeStyle = "#000"), |
| 68 | + context.stroke(); |
| 69 | + context.beginPath(), |
| 70 | + path(lines), |
| 71 | + (context.strokeStyle = "#000"), |
| 72 | + context.stroke(); |
| 73 | + return context.canvas; |
| 74 | +} |
| 75 | +) |
| 76 | + }, |
| 77 | + { |
| 78 | + name: "viewof zoom", |
| 79 | + inputs: ["html"], |
| 80 | + value: (function(html){return( |
| 81 | +html`<input type=range min=-500 max=500 value=1>` |
| 82 | +)}) |
| 83 | + }, |
| 84 | + { |
| 85 | + name: "zoom", |
| 86 | + inputs: ["Generators","viewof zoom"], |
| 87 | + value: (G, _) => G.input(_) |
| 88 | + }, |
| 89 | + { |
| 90 | + name: "viewof rotateOffset", |
| 91 | + inputs: ["html"], |
| 92 | + value: (function(html){return( |
| 93 | +html`<input type=range min=-360 max=360 value=0></input>` |
| 94 | +)}) |
| 95 | + }, |
| 96 | + { |
| 97 | + name: "rotateOffset", |
| 98 | + inputs: ["Generators","viewof rotateOffset"], |
| 99 | + value: (G, _) => G.input(_) |
| 100 | + }, |
| 101 | + { |
| 102 | + name: "rotate", |
| 103 | + inputs: ["center","rotateOffset"], |
| 104 | + value: (function(center,rotateOffset) |
| 105 | +{ |
| 106 | + const r = center.map(x => -x); |
| 107 | + r[0] += rotateOffset; |
| 108 | + return r; |
| 109 | +} |
| 110 | +) |
| 111 | + }, |
| 112 | + { |
| 113 | + name: "projection", |
| 114 | + inputs: ["d3","rotate","width","height","zoom","sphere"], |
| 115 | + value: (function(d3,rotate,width,height,zoom,sphere){return( |
| 116 | +d3 |
| 117 | + .geoAzimuthalEquidistant() |
| 118 | + .rotate(rotate) |
| 119 | + .translate([width / 2, height / 2]) |
| 120 | + .fitExtent([[zoom, zoom], [width - zoom, height - zoom]], sphere) |
| 121 | + .precision(0.1) |
| 122 | +)}) |
| 123 | + }, |
| 124 | + { |
| 125 | + name: "height", |
| 126 | + inputs: ["width"], |
| 127 | + value: (function(width){return( |
| 128 | +Math.max(640, width) |
| 129 | +)}) |
| 130 | + }, |
| 131 | + { |
| 132 | + name: "cities", |
| 133 | + value: (function(){return( |
| 134 | +[ |
| 135 | + ["Tokyo", 35.685, 139.7514], |
| 136 | + ["New York", 40.6943, -73.9249], |
| 137 | + ["Mexico City", 19.4424, -99.131], |
| 138 | + ["Mumbai", 19.017, 72.857], |
| 139 | + ["Sao Paulo", -23.5587, -46.625], |
| 140 | + ["Delhi", 28.67, 77.23], |
| 141 | + ["Shanghai", 31.2165, 121.4365], |
| 142 | + ["Kolkata", 22.495, 88.3247], |
| 143 | + ["Dhaka", 23.7231, 90.4086], |
| 144 | + ["Buenos Aires", -34.6025, -58.3975], |
| 145 | + ["Los Angeles", 34.114, -118.4068], |
| 146 | + ["Karachi", 24.87, 66.99], |
| 147 | + ["Cairo", 30.05, 31.25], |
| 148 | + ["Rio de Janeiro", -22.925, -43.225], |
| 149 | + ["Osaka", 34.75, 135.4601], |
| 150 | + ["Beijing", 39.9289, 116.3883], |
| 151 | + ["Manila", 14.6042, 120.9822], |
| 152 | + ["Moscow", 55.7522, 37.6155], |
| 153 | + ["Istanbul", 41.105, 29.01], |
| 154 | + ["Paris", 48.8667, 2.3333], |
| 155 | + ["Seoul", 37.5663, 126.9997], |
| 156 | + ["Lagos", 6.4433, 3.3915], |
| 157 | + ["Jakarta", -6.1744, 106.8294], |
| 158 | + ["Guangzhou", 23.145, 113.325], |
| 159 | + ["Chicago", 41.8373, -87.6861], |
| 160 | + ["London", 51.5, -0.1167] |
| 161 | +].map(([name, lat, lon]) => [name, lon, lat]) |
| 162 | +)}) |
| 163 | + }, |
| 164 | + { |
| 165 | + name: "lines", |
| 166 | + inputs: ["cities","center"], |
| 167 | + value: (function(cities,center){return( |
| 168 | +{ |
| 169 | + type: "MultiLineString", |
| 170 | + coordinates: cities.map(([_, lon, lat]) => [center, [lon, lat]]) |
| 171 | +} |
| 172 | +)}) |
| 173 | + }, |
| 174 | + { |
| 175 | + name: "option", |
| 176 | + inputs: ["html"], |
| 177 | + value: (function(html){return( |
| 178 | +([name, lon, lat]) => |
| 179 | + html`<option value="[${lon}, ${lat}]">${name}</option>` |
| 180 | +)}) |
| 181 | + }, |
| 182 | + { |
| 183 | + name: "sphere", |
| 184 | + value: (function(){return( |
| 185 | +{type: "Sphere"} |
| 186 | +)}) |
| 187 | + }, |
| 188 | + { |
| 189 | + name: "graticule", |
| 190 | + inputs: ["d3"], |
| 191 | + value: (function(d3){return( |
| 192 | +d3.geoGraticule10() |
| 193 | +)}) |
| 194 | + }, |
| 195 | + { |
| 196 | + name: "land", |
| 197 | + inputs: ["topojson","world"], |
| 198 | + value: (function(topojson,world){return( |
| 199 | +topojson.feature(world, world.objects.land) |
| 200 | +)}) |
| 201 | + }, |
| 202 | + { |
| 203 | + name: "world", |
| 204 | + value: (function(){return( |
| 205 | +fetch("https://unpkg.com/world-atlas@1/world/110m.json").then( |
| 206 | + response => response.json() |
| 207 | +) |
| 208 | +)}) |
| 209 | + }, |
| 210 | + { |
| 211 | + name: "topojson", |
| 212 | + inputs: ["require"], |
| 213 | + value: (function(require){return( |
| 214 | +require("topojson-client@3") |
| 215 | +)}) |
| 216 | + }, |
| 217 | + { |
| 218 | + name: "d3", |
| 219 | + inputs: ["require"], |
| 220 | + value: (function(require){return( |
| 221 | +require("d3-geo@1") |
| 222 | +)}) |
| 223 | + } |
| 224 | + ] |
| 225 | +}; |
| 226 | + |
| 227 | +const notebook = { |
| 228 | + id: "e6547e85ad478b83@280", |
| 229 | + modules: [m0] |
| 230 | +}; |
| 231 | + |
| 232 | +export default notebook; |
0 commit comments