PageSourceSearch

https://rbren.io/worldnews/globe.js

js rbren.io collected 2026-10-03 23:21:13 UTC 6,640 bytes, 275 lines download raw bytes

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.