block by patricksurry f425a2070c49f8404dee7fd66c7b552d

Gradient path stroke effect

Full Screen

Built with blockbuilder.org

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <script src="https://d3js.org/d3.v4.js"></script>
  <script src="https://d3js.org/topojson.v1.min.js"></script>
  <style>
    body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0;background: #000; }
  </style>
</head>

<body>
  <script>
var width = 1200,
    height = 700,
    originalScale = 300,
    color = d3.scaleLinear()
			.range(['#011002', '#010210']),
    scale = originalScale,
    scaleChange,
    rotation;

var sphere = {type: 'Sphere'};

var graticule = d3.geoGraticule();

// set up the main canvas and the projection

var canvas = d3.select('body').append('canvas')
    .attr('width', width)
    .attr('height', height);

var context = canvas.node().getContext('2d');

context.imageSmoothingEnabled = true;
context.globalCompositeOperation = 'lighter';  // blend overlapping colors towards white (vs default source-over)

context.translate(width / 2, height / 2);

var projection = d3.geoOrthographic()
    .scale(scale)
    .rotate([-182, -23])
    .translate([0, 0])
    .clipAngle(180);

var path = d3.geoPath()
    .projection(projection)
    .context(context);

d3.queue()
  .defer(d3.csv, 'routes.csv')
  .defer(d3.json, 'world-110m-simple.json')
  .await(load);

function load(error, routes, world) {
  if (error) { console.log(error); }

  var land = topojson.feature(world, world.objects.countries),
    	grid = graticule();

  function drawWorld() {

    context.clearRect(-width/2, -height/2, width, height);

    // draw grid
    context.save();

    context.lineWidth = 0.5;
    context.strokeStyle = '#202020';

    context.beginPath();
    path(sphere);
    context.stroke();

    context.beginPath();
    path(grid);
    context.stroke();

    // draw land
    context.beginPath();
    path(land);
    context.fillStyle = '#202020';
    context.fill();

    context.beginPath();
    path(land);
    context.lineWidth = .5;
    context.strokeStyle = '#000000';
    context.stroke();

    const c = projection.invert([0, 0]),
          r = projection.scale();

   	// draw routes
    routes.slice(0,1000).forEach(d => {
      const origin = [+d.olon, +d.olat], 
            destination = [+d.dlon, +d.dlat],
      			route = d3.geoInterpolate(origin, destination),
      			frac = d3.geoDistance(origin, destination)/Math.PI;
      context.beginPath();
      context.lineWidth = 0.5;
      steps = Math.ceil(20*frac+1);
      context.moveTo(...projection(route(0)));
      d3.range(0, 1, 1/steps).forEach(t => {
				context.strokeStyle = color(t);
        context.lineTo(...projection(route(t)))
	      context.stroke();
      });
    });

    context.restore();

  } // drawWorld()

  // First draw

  requestAnimationFrame(drawWorld);

  var zoom = d3.zoom()
    .scaleExtent([0.5, 4])
    .on("zoom", zoomed)

  canvas.call(zoom);

  var previousScaleFactor = 1;

  function zoomed() {

    var dx = d3.event.sourceEvent.movementX;
    var dy = d3.event.sourceEvent.movementY;

    var event = d3.event.sourceEvent.type;

    context.save();
    context.clearRect(0, 0, width, height);

    if (event === 'wheel') {

      scaleFactor = d3.event.transform.k;
      scaleChange = scaleFactor - previousScaleFactor;
      scale = scale + scaleChange * originalScale;
      projection.scale(scale);
      previousScaleFactor = scaleFactor;

    } else {

      var r = projection.rotate();
      rotation = [r[0] + dx * 0.4, r[1] - dy * 0.5, r[2]];
      projection.rotate(rotation);

    }

    requestAnimationFrame(drawWorld);

    context.restore();

  } // zoomed()

} // load()




  </script>
</body>