1var rotationDelay = -1 2var scaleFactor = .9 3var degPerSec = 6 4var angles = { x: -20, y: 40, z: 0} 5var colorWater = '#fff' 6var colorLand = '#fff' 7var colorGraticule = '#ccc' 8var colorCountry = '#080' 9var colorCountryFixed = '#0c0' 10var colorCountryDefault = "#000" 11var paddingTop = 180; 12 13const maxStoryAgeForHighlight = 7; 14 15function enter(thing) { 16 if (!thing) return; 17 var country = countryList.find(function(c) { 18 return parseInt(c.id) === parseInt(thing.id); 19 }) 20 let name = ''; 21 if (country) { 22 name = country.name + " " + country.data.flag; 23 } 24 countryName.text(name); 25 var storiesElem = $("#stories"); 26 storiesElem.html(""); 27 if (!country) return; 28 var stories = storiesByCountry[country.id] || []; 29 stories.forEach(story => { 30 let time = moment(story.pubDate); 31 let timeStr = "just now"; 32 if (time.isBefore(moment())) { 33 timeStr = time.fromNow(); 34 } 35 storiesElem.append(` 36 <div class="story"> 37 <a href="${story.link}">${story.title}</a> 38 <p class="time">${timeStr}</p> 39 <p class="snippet">${story.contentSnippet}</p> 40 </div> 41 `); 42 }) 43} 44 45var countryName = d3.select('#countryName') 46var canvas = d3.select('#globe') 47var context = canvas.node().getContext('2d') 48var water = {type: 'Sphere'} 49var projection = d3.geoOrthographic().precision(0.1) 50var graticule = d3.geoGraticule10() 51var path = d3.geoPath(projection).context(context) 52var v0 // Mouse position in Cartesian coordinates at start of drag gesture. 53var r0 // Projection rotation as Euler angles at start. 54var q0 // Projection rotation as versor at start. 55var lastTime = d3.now()
56var degPerMs = degPerSec / 1000 57var width, height 58var land, countries 59var countryList 60var autorotate, now, diff, roation 61var currentCountry, currentCountryFixed 62 63// 64// Functions 65// 66 67function setAngles() { 68 var rotation = projection.rotate() 69 rotation[0] = angles.y 70 rotation[1] = angles.x 71 rotation[2] = angles.z 72 projection.rotate(rotation) 73} 74 75function scale() { 76 container = document.getElementById('globe-container'); 77 width = container.offsetWidth; 78 height = container.offsetHeight - paddingTop; 79 canvas.attr('width', width).attr('height', height) 80 projection 81 .scale((scaleFactor * Math.min(width, height)) / 2) 82 .translate([width / 2, height / 2]) 83 render() 84} 85 86function startRotation(delay) { 87 autorotate.restart(rotate, delay || 0) 88} 89 90function stopRotation() { 91 autorotate.stop() 92} 93 94function dragstarted() { 95 v0 = versor.cartesian(projection.invert(d3.mouse(this))) 96 r0 = projection.rotate() 97 q0 = versor(r0) 98} 99 100function dragged() { 101 var v1 = versor.cartesian(projection.rotate(r0).invert(d3.mouse(this))) 102 var q1 = versor.multiply(q0, versor.delta(v0, v1)) 103 var r1 = versor.rotation(q1) 104 projection.rotate(r1) 105 render() 106} 107 108function dragended() { 109 if (rotationDelay === -1) return 110 startRotation(rotationDelay) 111} 112 113function render() { 114 context.clearRect(0, 0, width, height) 115 fill(water, colorWater) 116 stroke(graticule, colorGraticule) 117 fill(land, colorLand) 118 let now = moment(); 119 countries.features.forEach(country => { 120 let stories = storiesByCountry[parseInt(country.id).toString()]; 121 let filled = false; 122 if (stories) { 123 let latest = moment(stories[0].pubDate); 124 let daysAgo = Math.max(0, now.diff(latest, 'days')); 125 let intensity = 1 - Math.min(maxStoryAgeForHighlight, daysAgo) / maxStoryAgeForHighlight; 126 let blueness = Math.round(intensity * 255).toString(16) 127 if (blueness.length < 2) { 128 blueness = "0" + blueness; 129 } 130 let shade = "#0000" + blueness; 131 if (daysAgo < 7) { 132 fill(country, shade); 133 filled = true; 134 } 135 } 136 if (!filled) { 137 fill(country, colorCountryDefault); 138 } 139 }) 140 if (currentCountry && currentCountry !== currentCountryFixed) { 141 fill(currentCountry, colorCountry) 142 } 143 if (currentCountryFixed) { 144 fill(currentCountryFixed, colorCountryFixed) 145 } 146} 147 148function fill(obj, color) { 149 context.beginPath() 150 path(obj) 151 context.fillStyle = color 152 context.fill() 153} 154 155function stroke(obj, color) { 156 context.beginPath() 157 path(obj) 158 context.strokeStyle = color 159 context.stroke() 160} 161 162function rotate(elapsed) { 163 let now = d3.now() 164 diff = now - lastTime 165 if (diff < elapsed) { 166 rotation = projection.rotate() 167 rotation[0] += diff * degPerMs 168 projection.rotate(rotation) 169 render() 170 } 171 lastTime = now 172} 173 174function loadData(cb) { 175 d3.json('https://unpkg.com/world-atlas@1/world/110m.json', function(error, world) { 176 if (error) throw error 177 d3.tsv('https://gist.githubusercontent.com/mbostock/4090846/raw/07e73f3c2d21558489604a0bc434b3a5cf41a867/world-country-names.tsv', function(error, countries) { 178 if (error) throw error 179 cb(world, countries) 180 }) 181 }) 182} 183 184// https://github.com/d3/d3-polygon 185function polygonContains(polygon, point) { 186 var n = polygon.length 187 var p = polygon[n - 1] 188 var x = point[0], y = point[1] 189 var x0 = p[0], y0 = p[1] 190 var x1, y1 191 var inside = false 192 for (var i = 0; i < n; ++i) { 193 p = polygon[i], x1 = p[0], y1 = p[1] 194 if (((y1 > y) !== (y0 > y)) && (x < (x0 - x1) * (y - y1) / (y0 - y1) + x1)) inside = !inside 195 x0 = x1, y0 = y1 196 } 197 return inside 198} 199 200function onClick(e) { 201 setCountryByMouse(this); 202 currentCountryFixed = currentCountry; 203 enter(currentCountryFixed) 204 if (!currentCountryFixed) { 205 startRotation(); 206 } else { 207 stopRotation() 208 } 209 render() 210} 211 212function onMove() { 213 setCountryByMouse(this); 214 if (currentCountry) { 215 enter(currentCountry); 216 } else { 217 enter(currentCountryFixed); 218 } 219} 220 221function setCountryByMouse(el) { 222 var c = getCountry(el) 223 if (!c) { 224 if (currentCountry) { 225 currentCountry = undefined 226 render() 227 } 228 return 229 } 230 if (c === currentCountry) { 231 return 232 } 233 currentCountry = c 234 render() 235} 236 237function getCountry(event) { 238 var pos = projection.invert(d3.mouse(event)) 239 return countries.features.find(function(f) { 240 return f.geometry.coordinates.find(function(c1) { 241 return polygonContains(c1, pos) || c1.find(function(c2) { 242 return polygonContains(c2, pos) 243 }) 244 }) 245 }) 246} 247 248 249// 250// Initialization 251// 252 253setAngles() 254 255canvas 256 .on('click', onClick) 257 .on('mousemove', onMove) 258 .call(d3.drag() 259 .on('start', dragstarted) 260 .on('drag', dragged) 261 .on('end', dragended) 262 ) 263 264loadData(function(world, cList) { 265 land = topojson.feature(world, world.objects.land) 266 countries = topojson.feature(world, world.objects.countries) 267 countryList = cList 268 window.addEventListener('resize', scale) 269 scale() 270 autorotate = d3.timer(rotate) 271 loadStories().then(_ => { 272 console.log("Everything loaded") 273 }); 274}) 275
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.