Orange dots = landing
Blue dots = takeoff
var landingColor = '#fe761e';
var takeOffColor = '#3081dd';
var lineColor = '#5297ee';
var fps = 60;
var curveFactor = 50;
var speed = 0.99; // between 0 & 1
var drawLines = true;
var batchSize = 12;
forked from bradoyler‘s block: US Flights animation (Canvas2D)
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.2/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.min.js"></script>
<script src="routes.js"></script>
<script src="helpers.js"></script>
<script src="bundle.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
.states {
fill: #696969;
stroke: #6cb0e0;
stroke-width: 0.5px;
stroke-linecap: round;
stroke-linejoin: round;
}
.airports {
fill: #000;
stroke: transparent;
stroke-width: 0.5px;
stroke-linecap: round;
stroke-linejoin: round;
}
</style>
</head>
<body>
<div id="flightMap"></div>
<div>Blue dots = takeoff
<div>Orange dots = landing</div>
<script>
initMap('#flightMap')
</script>
</body>
'use strict';
var landingColor = '#fe761e';
var takeOffColor = '#3081dd';
var lineColor = '#5297ee';
var fps = 40;
var curveFactor = 40;
var speed = 0.9; // between 0 & 1
var drawLines = true;
var batchSize = 12;
function initMap(selector) {
var airportMap = {};
var $flightmap = d3.select(selector).node();
var width = 950;
var height = 500;
var mapScale = 1050;
var projection = d3.geoAlbersUsa()
.scale(mapScale).translate([width / 2, height / 2]);
var path = d3.geoPath().pointRadius(1.2).projection(projection);
var svg = d3.select(selector).append('svg')
.attr('preserveAspectRatio', 'xMidYMid')
.attr('viewBox', '0 0 ' + width + ' ' + height)
.attr('width', width).attr('height', height);
var canvasEl = d3.select(selector)
.append('canvas')
.attr('style', 'position:absolute; top:0; left:0')
.attr('width', width)
.attr('height', height);
var canvas = canvasEl.node();
var ctx = canvas.getContext('2d');
var points = [];
var batchCount = 0;
function animate() {
draw();
// request another frame
setTimeout(function () {
window.requestAnimationFrame(animate);
}, 1000 / fps);
}
// draw the current frame based on sliderValue
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < points.length; i++) {
if (i > batchCount) {
// for progressive animation
batchCount += batchSize;
break;
}
var point = points[i],
start = point.start,
end = point.end,
progress = point.progress;
var wayPoint = helpers.getWaypointXY(start, end, 90);
var controlPt = { x: wayPoint.x, y: wayPoint.y - curveFactor };
ctx.beginPath();
var percentFactor = progress / 100;
var xy = helpers.getQuadraticBezierXY(start, controlPt, end, percentFactor);
point.progress += speed;
// when to draw lines
if (drawLines && progress < 101) {
helpers.drawLine(ctx, start, end, controlPt, lineColor);
}
// when to draw dots
if (progress > 0 && progress < 10) {
helpers.drawDot(ctx, xy, 2.5, takeOffColor, takeOffColor);
} else if (progress < 90) {
helpers.drawDot(ctx, xy, 4);
} else if (progress > 90 && progress < 101) {
helpers.drawDot(ctx, xy, 2.5, landingColor, landingColor);
} else {
point.progress = 0; // reset progress
}
}
}
function ready(error, topo, airports) {
if (error) throw error;
svg.append('g').attr('class','states').selectAll('path')
.data(topojson.feature(topo, topo.objects.states).features).enter()
.append('path').attr('d', path);
svg.append('g').attr('class', 'airports').selectAll('path')
.data(topojson.feature(airports, airports.objects.airports).features).enter()
.append('path').attr('id', function (d) {
return d.id;
}).attr('d', path);
var geos = topojson.feature(airports, airports.objects.airports).features;
geos.forEach(function (geo) {
airportMap[geo.id] = geo.geometry.coordinates;
});
points = helpers.generatePointsFromMap(airportMap, window.routes, projection);
animate();
}
d3.queue()
.defer(d3.json, 'https://nodeassets.nbcnews.com/cdnassets/projects/2017/08/airplane-mode/us-states.json')
.defer(d3.json, 'https://nodeassets.nbcnews.com/cdnassets/projects/2017/08/airplane-mode/us-airports-major.topo.json')
.await(ready);
}
window.helpers = {}
helpers.drawDot = function drawDot(ctx, point, size) {
var fill = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '#ccc';
var stroke = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : '#ccc';
ctx.fillStyle = fill;
ctx.strokeStyle = stroke;
ctx.lineWidth = 0.2;
ctx.beginPath();
ctx.arc(point.x, point.y, size, 0, Math.PI * 2, false);
// var p = new window.Path2D('M10 10 h 80 v 80 h -80 Z')
ctx.closePath();
ctx.fill();
ctx.stroke();
}
helpers.drawLine = function drawLine(ctx, start, end, controlPt, color) {
var lineWidth = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0.2;
ctx.moveTo(start.x, start.y);
ctx.quadraticCurveTo(controlPt.x, controlPt.y, end.x, end.y); // curved lines
// ctx.lineTo(end.x, end.y) // straight lines
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.stroke();
}
helpers.generatePointsFromMap = function generatePointsFromMap(map, routes, projection) {
var points = [];
routes.forEach(function (route, idx) {
var origin = map[route[0]];
var dest = map[route[1]];
if (origin && dest && origin.length && dest.length) {
var startXY = projection(origin) || [0, 0];
var endXY = projection(dest) || [0, 0];
var start = { x: startXY[0], y: startXY[1] };
var end = { x: endXY[0], y: endXY[1] };
var name = route;
var onMap = end.x > 0 && start.x > 0;
if (onMap) {
points.push({ idx: idx, name: name, start: start, end: end, progress: 0 });
}
}
});
console.log('points:', points.length)
return points;
}
helpers.getWaypointXY = function getWaypointXY(startPoint, endPoint, percent) {
var factor = percent / 100;
var x = startPoint.x + (endPoint.x - startPoint.x) * factor;
var y = startPoint.y + (endPoint.y - startPoint.y) * factor;
return { x: x, y: y };
}
// quadratic bezier: percent is 0-1
helpers.getQuadraticBezierXY = function getQuadraticBezierXY(startPt, controlPt, endPt, percent) {
var x = Math.pow(1 - percent, 2) * startPt.x + 2 * (1 - percent) * percent * controlPt.x + Math.pow(percent, 2) * endPt.x;
var y = Math.pow(1 - percent, 2) * startPt.y + 2 * (1 - percent) * percent * controlPt.y + Math.pow(percent, 2) * endPt.y;
return { x: x, y: y };
}