303 lines
8.0 KiB
JavaScript
303 lines
8.0 KiB
JavaScript
/*! Planetary.js v0.1.1
|
|
* Copyright (c) 2013 Brandon Tilley
|
|
*
|
|
* Released under the MIT license
|
|
* Date: 2013-12-22T04:25:03.494Z
|
|
*/
|
|
(function (root, factory) {
|
|
if (typeof define === 'function' && define.amd) {
|
|
define(['d3', 'topojson'], factory);
|
|
} else if (typeof exports === 'object') {
|
|
module.exports = factory(require('d3'), require('topojson'));
|
|
} else {
|
|
root.planetaryjs = factory(root.d3, root.topojson, root);
|
|
}
|
|
}(this, function(d3, topojson, window) {
|
|
'use strict';
|
|
|
|
var originalPlanetaryjs = null;
|
|
if (window) originalPlanetaryjs = window.planetaryjs;
|
|
var plugins = [];
|
|
|
|
var doDrawLoop = function(planet, canvas, hooks) {
|
|
d3.timer(function() {
|
|
planet.context.clearRect(0, 0, canvas.width, canvas.height)
|
|
for (var i = 0; i < hooks.onDraw.length; i++) {
|
|
hooks.onDraw[i]();
|
|
}
|
|
});
|
|
};
|
|
|
|
var initPlugins = function(planet, localPlugins) {
|
|
// Add the global plugins to the beginning of the local ones
|
|
for (var i = 0; i < plugins.length; i++) {
|
|
localPlugins.unshift(plugins[i]);
|
|
}
|
|
|
|
// Load the default plugins if none have been loaded so far
|
|
if (localPlugins.length == 0) {
|
|
if (planetaryjs.plugins.earth)
|
|
planet.loadPlugin(planetaryjs.plugins.earth());
|
|
if (planetaryjs.plugins.pings)
|
|
planet.loadPlugin(planetaryjs.plugins.pings());
|
|
}
|
|
|
|
for (var i = 0; i < localPlugins.length; i++) {
|
|
localPlugins[i](planet);
|
|
}
|
|
};
|
|
|
|
var runOnInitHooks = function(planet, canvas, hooks) {
|
|
// onInit hooks can be asynchronous if they take a parameter;
|
|
// iterate through them one at a time
|
|
if (hooks.onInit.length) {
|
|
var completed = 0;
|
|
var doNext = function(callback) {
|
|
var next = hooks.onInit[completed];
|
|
if (next.length) {
|
|
next(function() {
|
|
completed++;
|
|
callback();
|
|
});
|
|
} else {
|
|
next();
|
|
completed++;
|
|
setTimeout(callback, 0);
|
|
}
|
|
};
|
|
var check = function() {
|
|
if (completed >= hooks.onInit.length) doDrawLoop(planet, canvas, hooks);
|
|
else doNext(check);
|
|
}
|
|
doNext(check);
|
|
} else {
|
|
doDrawLoop(planet, canvas, hooks);
|
|
}
|
|
};
|
|
|
|
var startDraw = function(planet, canvas, localPlugins, hooks) {
|
|
initPlugins(planet, localPlugins);
|
|
|
|
planet.canvas = canvas;
|
|
planet.context = canvas.getContext('2d');
|
|
|
|
runOnInitHooks(planet, canvas, hooks);
|
|
};
|
|
|
|
var planetaryjs = {
|
|
plugins: {},
|
|
|
|
noConflict: function() {
|
|
window.planetaryjs = originalPlanetaryjs;
|
|
return planetaryjs;
|
|
},
|
|
|
|
loadPlugin: function(plugin) {
|
|
plugins.push(plugin);
|
|
},
|
|
|
|
planet: function() {
|
|
var localPlugins = [];
|
|
var hooks = {
|
|
onInit: [],
|
|
onDraw: []
|
|
};
|
|
|
|
var planet = {
|
|
plugins: {},
|
|
|
|
draw: function(canvas) {
|
|
startDraw(planet, canvas, localPlugins, hooks);
|
|
},
|
|
|
|
onInit: function(fn) {
|
|
hooks.onInit.push(fn);
|
|
},
|
|
|
|
onDraw: function(fn) {
|
|
hooks.onDraw.push(fn);
|
|
},
|
|
|
|
loadPlugin: function(plugin) {
|
|
localPlugins.push(plugin);
|
|
},
|
|
|
|
withSavedContext: function(fn) {
|
|
if (!this.context) {
|
|
throw new Error("No canvas to fetch context for")
|
|
}
|
|
|
|
this.context.save();
|
|
fn(this.context);
|
|
this.context.restore();
|
|
}
|
|
};
|
|
|
|
planet.projection = d3.geo.orthographic()
|
|
.clipAngle(90)
|
|
.precision(0);
|
|
planet.path = d3.geo.path().projection(planet.projection);
|
|
|
|
return planet;
|
|
}
|
|
};
|
|
|
|
planetaryjs.plugins.topojson = function(config) {
|
|
return function(planet) {
|
|
planet.plugins.topojson = {};
|
|
|
|
planet.onInit(function(done) {
|
|
if (config.world) {
|
|
planet.plugins.topojson.world = config.world;
|
|
setTimeout(done, 0);
|
|
} else {
|
|
var file = config.file || 'world-110m.json'
|
|
d3.json(file, function(err, world) {
|
|
if (err) {
|
|
throw new Error("Could not load JSON " + file);
|
|
}
|
|
planet.plugins.topojson.world = world;
|
|
done();
|
|
});
|
|
}
|
|
});
|
|
};
|
|
};
|
|
|
|
planetaryjs.plugins.oceans = function(config) {
|
|
return function(planet) {
|
|
planet.onDraw(function() {
|
|
planet.withSavedContext(function(context) {
|
|
context.beginPath();
|
|
planet.path.context(context)({type: 'Sphere'});
|
|
|
|
context.fillStyle = config.fill || 'black';
|
|
context.fill();
|
|
|
|
if (config.stroke != false) {
|
|
context.strokeStyle = config.stroke;
|
|
context.stroke();
|
|
}
|
|
});
|
|
});
|
|
};
|
|
};
|
|
|
|
planetaryjs.plugins.land = function(config) {
|
|
return function(planet) {
|
|
var land = null;
|
|
|
|
planet.onInit(function() {
|
|
var world = planet.plugins.topojson.world;
|
|
land = topojson.feature(world, world.objects.land);
|
|
})
|
|
|
|
planet.onDraw(function() {
|
|
planet.withSavedContext(function(context) {
|
|
context.beginPath();
|
|
planet.path.context(context)(land);
|
|
|
|
if (config.fill != false) {
|
|
context.fillStyle = config.fill || 'white';
|
|
context.fill();
|
|
}
|
|
|
|
if (config.stroke) {
|
|
context.strokeStyle = config.stroke;
|
|
context.stroke();
|
|
}
|
|
});
|
|
});
|
|
};
|
|
};
|
|
|
|
planetaryjs.plugins.borders = function(config) {
|
|
return function(planet) {
|
|
var borders = null;
|
|
planet.onInit(function() {
|
|
var world = planet.plugins.topojson.world;
|
|
var countries = world.objects.countries;
|
|
borders = topojson.mesh(world, countries, function(a, b) {
|
|
return a.id !== b.id;
|
|
});
|
|
});
|
|
|
|
planet.onDraw(function() {
|
|
planet.withSavedContext(function(context) {
|
|
context.beginPath();
|
|
planet.path.context(context)(borders);
|
|
context.strokeStyle = config.stroke || 'gray';
|
|
context.stroke();
|
|
});
|
|
});
|
|
};
|
|
};
|
|
|
|
planetaryjs.plugins.earth = function(config) {
|
|
var config = config || {};
|
|
var topojsonOptions = config.topojson || {};
|
|
var oceanOptions = config.oceans || {};
|
|
var landOptions = config.land || {};
|
|
var bordersOptions = config.borders || {};
|
|
|
|
return function(planet) {
|
|
planetaryjs.plugins.topojson(topojsonOptions)(planet);
|
|
planetaryjs.plugins.oceans(oceanOptions)(planet);
|
|
planetaryjs.plugins.land(landOptions)(planet);
|
|
planetaryjs.plugins.borders(bordersOptions)(planet);
|
|
};
|
|
};
|
|
|
|
planetaryjs.plugins.pings = function(config) {
|
|
var pings = [];
|
|
|
|
var addPing = function(lat, lng, options) {
|
|
var options = options || {};
|
|
options.color = options.color || 'white';
|
|
options.ttl = options.ttl || 2000;
|
|
options.angle = options.angle || 5;
|
|
pings.push({ lat: lat, lng: lng, time: new Date(), options: options });
|
|
};
|
|
|
|
var drawPings = function(planet, context, now) {
|
|
var newPings = [];
|
|
for (var i = 0; i < pings.length; i++) {
|
|
var ping = pings[i];
|
|
var alive = now - ping.time;
|
|
if (alive < ping.options.ttl) {
|
|
newPings.push(ping);
|
|
drawPing(planet, context, now, alive, ping);
|
|
}
|
|
}
|
|
pings = newPings;
|
|
};
|
|
|
|
var drawPing = function(planet, context, now, alive, ping) {
|
|
var alpha = 1 - (alive / ping.options.ttl);
|
|
var color = d3.rgb(ping.options.color);
|
|
color = "rgba(" + color.r + "," + color.g + "," + color.b + "," + alpha + ")";
|
|
context.strokeStyle = color;
|
|
var circle = d3.geo.circle().origin([ping.lng, ping.lat])
|
|
.angle(alive / ping.options.ttl * ping.options.angle)();
|
|
context.beginPath();
|
|
planet.path.context(context)(circle);
|
|
context.stroke();
|
|
};
|
|
|
|
return function (planet) {
|
|
planet.plugins.pings = {
|
|
add: addPing
|
|
};
|
|
|
|
planet.onDraw(function() {
|
|
var now = new Date();
|
|
planet.withSavedContext(function(context) {
|
|
drawPings(planet, context, now);
|
|
});
|
|
});
|
|
};
|
|
};
|
|
|
|
return planetaryjs;
|
|
}));
|