diff --git a/dist/planetaryjs-noplugins.js b/dist/planetaryjs-noplugins.js index 3d65f34..5426e8d 100644 --- a/dist/planetaryjs-noplugins.js +++ b/dist/planetaryjs-noplugins.js @@ -2,7 +2,7 @@ * Copyright (c) 2013 Brandon Tilley * * Released under the MIT license - * Date: 2013-12-21T19:00:54.881Z + * Date: 2013-12-21T20:06:15.792Z */ (function (root, factory) { if (typeof define === 'function' && define.amd) { diff --git a/dist/planetaryjs.js b/dist/planetaryjs.js index 5d96ccd..6ab22fe 100644 --- a/dist/planetaryjs.js +++ b/dist/planetaryjs.js @@ -2,7 +2,7 @@ * Copyright (c) 2013 Brandon Tilley * * Released under the MIT license - * Date: 2013-12-21T19:00:54.985Z + * Date: 2013-12-21T20:06:15.617Z */ (function (root, factory) { if (typeof define === 'function' && define.amd) { @@ -249,7 +249,7 @@ var options = options || {}; options.color = options.color || 'white'; options.ttl = options.ttl || 2000; - options.size = options.size || 500; + options.angle = options.angle || 5; pings.push({ lat: lat, lng: lng, time: new Date(), options: options }); }; @@ -267,13 +267,13 @@ }; var drawPing = function(planet, context, now, ping) { - var dT = now - ping.time; - var alpha = 1 - (dT / ping.options.ttl); + var alive = now - ping.time; + 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(dT / ping.options.size)(); + .angle(alive / ping.options.ttl * ping.options.angle)(); context.beginPath(); planet.path.context(context)(circle); context.stroke(); diff --git a/dist/planetaryjs.min.js b/dist/planetaryjs.min.js index 4d3a9fd..bbdfb32 100644 --- a/dist/planetaryjs.min.js +++ b/dist/planetaryjs.min.js @@ -1,2 +1,2 @@ /*! Planetary.js 0.0.0 | (c) 2013 Brandon Tilley | Released under MIT License */ -!function(n,t){"function"==typeof define&&define.amd?define(["d3","topojson"],t):"object"==typeof exports?module.exports=t(require("d3"),require("topojson")):n.planetaryjs=t(n.d3,n.topojson,n)}(this,function(n,t,o){"use strict";var e=null;o&&(e=o.planetaryjs);var i=[],r=function(t,o,e){n.timer(function(){t.context.clearRect(0,0,o.width,o.height);for(var n=0;n=o.onInit.length?r(n,t,o):i(l)};i(l)}else r(n,t,o)},a=function(n,t,o,e){l(n,o),n.canvas=t,n.context=t.getContext("2d"),u(n,t,e)},s={plugins:{},noConflict:function(){return o.planetaryjs=e,s},loadPlugin:function(n){i.push(n)},planet:function(){var t=[],o={onInit:[],onDraw:[]},e={draw:function(n){a(e,n,t,o)},onInit:function(n){o.onInit.push(n)},onDraw:function(n){o.onDraw.push(n)},loadPlugin:function(n){t.push(n)},withSavedContext:function(n){if(!this.context)throw new Error("No canvas to fetch context for");this.context.save(),n(this.context),this.context.restore()}};return e.projection=n.geo.orthographic().clipAngle(90).precision(0),e.path=n.geo.path().projection(e.projection),e}};return s.plugins.topojson=function(t){return function(o){o.onInit(function(e){if(t.world)o.world=t.world,setTimeout(e,0);else{var i=t.file||"world-110m.json";n.json(i,function(n,t){if(n)throw new Error("Could not load JSON "+i);o.world=t,e()})}})}},s.plugins.oceans=function(n){return function(t){t.onDraw(function(){t.withSavedContext(function(o){o.beginPath(),t.path.context(o)({type:"Sphere"}),o.fillStyle=n.fill||"black",o.fill(),0!=n.stroke&&(o.strokeStyle=n.stroke,o.stroke())})})}},s.plugins.land=function(n){return function(o){var e=null;o.onInit(function(){e=t.feature(o.world,o.world.objects.land)}),o.onDraw(function(){o.withSavedContext(function(t){t.beginPath(),o.path.context(t)(e),0!=n.fill&&(t.fillStyle=n.fill||"white",t.fill()),n.stroke&&(t.strokeStyle=n.stroke,t.stroke())})})}},s.plugins.borders=function(n){return function(o){var e=null;o.onInit(function(){var n=o.world.objects.countries;e=t.mesh(o.world,n,function(n,t){return n.id!==t.id})}),o.onDraw(function(){o.withSavedContext(function(t){t.beginPath(),o.path.context(t)(e),t.strokeStyle=n.stroke||"gray",t.stroke()})})}},s.plugins.earth=function(n){var n=n||{},t=n.topojson||{},o=n.oceans||{},e=n.land||{},i=n.borders||{};return function(n){s.plugins.topojson(t)(n),s.plugins.oceans(o)(n),s.plugins.land(e)(n),s.plugins.borders(i)(n)}},s.plugins.pings=function(){var t=[],o=function(n,o,e){var e=e||{};e.color=e.color||"white",e.ttl=e.ttl||2e3,e.size=e.size||500,t.push({lat:n,lng:o,time:new Date,options:e})},e=function(n,o,e){for(var r=[],l=0;l=o.onInit.length?r(n,t,o):i(l)};i(l)}else r(n,t,o)},a=function(n,t,o,e){l(n,o),n.canvas=t,n.context=t.getContext("2d"),u(n,t,e)},c={plugins:{},noConflict:function(){return o.planetaryjs=e,c},loadPlugin:function(n){i.push(n)},planet:function(){var t=[],o={onInit:[],onDraw:[]},e={draw:function(n){a(e,n,t,o)},onInit:function(n){o.onInit.push(n)},onDraw:function(n){o.onDraw.push(n)},loadPlugin:function(n){t.push(n)},withSavedContext:function(n){if(!this.context)throw new Error("No canvas to fetch context for");this.context.save(),n(this.context),this.context.restore()}};return e.projection=n.geo.orthographic().clipAngle(90).precision(0),e.path=n.geo.path().projection(e.projection),e}};return c.plugins.topojson=function(t){return function(o){o.onInit(function(e){if(t.world)o.world=t.world,setTimeout(e,0);else{var i=t.file||"world-110m.json";n.json(i,function(n,t){if(n)throw new Error("Could not load JSON "+i);o.world=t,e()})}})}},c.plugins.oceans=function(n){return function(t){t.onDraw(function(){t.withSavedContext(function(o){o.beginPath(),t.path.context(o)({type:"Sphere"}),o.fillStyle=n.fill||"black",o.fill(),0!=n.stroke&&(o.strokeStyle=n.stroke,o.stroke())})})}},c.plugins.land=function(n){return function(o){var e=null;o.onInit(function(){e=t.feature(o.world,o.world.objects.land)}),o.onDraw(function(){o.withSavedContext(function(t){t.beginPath(),o.path.context(t)(e),0!=n.fill&&(t.fillStyle=n.fill||"white",t.fill()),n.stroke&&(t.strokeStyle=n.stroke,t.stroke())})})}},c.plugins.borders=function(n){return function(o){var e=null;o.onInit(function(){var n=o.world.objects.countries;e=t.mesh(o.world,n,function(n,t){return n.id!==t.id})}),o.onDraw(function(){o.withSavedContext(function(t){t.beginPath(),o.path.context(t)(e),t.strokeStyle=n.stroke||"gray",t.stroke()})})}},c.plugins.earth=function(n){var n=n||{},t=n.topojson||{},o=n.oceans||{},e=n.land||{},i=n.borders||{};return function(n){c.plugins.topojson(t)(n),c.plugins.oceans(o)(n),c.plugins.land(e)(n),c.plugins.borders(i)(n)}},c.plugins.pings=function(){var t=[],o=function(n,o,e){var e=e||{};e.color=e.color||"white",e.ttl=e.ttl||2e3,e.angle=e.angle||5,t.push({lat:n,lng:o,time:new Date,options:e})},e=function(n,o,e){for(var r=[],l=0;l - - @@ -18,7 +16,7 @@ Planetary.js - + Download @@ -35,6 +33,9 @@
<%- yield %>
- + + + + diff --git a/site/public/css/planetaryjs.less b/site/public/css/planetaryjs.less index 28a18f1..a3fca38 100644 --- a/site/public/css/planetaryjs.less +++ b/site/public/css/planetaryjs.less @@ -100,3 +100,7 @@ canvas { text-align: center; } } + +.no-underline { + text-decoration: none; +} diff --git a/site/public/index.ejs b/site/public/index.ejs index baa5cbc..81fc508 100644 --- a/site/public/index.ejs +++ b/site/public/index.ejs @@ -27,7 +27,7 @@
@@ -35,18 +35,18 @@ - - - - - - -
diff --git a/site/public/js/homepage.js b/site/public/js/homepage.js new file mode 100644 index 0000000..4c3cceb --- /dev/null +++ b/site/public/js/homepage.js @@ -0,0 +1,64 @@ +(function() { + var earth = planetaryjs.planet(); + // The `earth` plugin draws the oceans and the land; it's actually + // a combination of several separate built-in plugins. + earth.loadPlugin(planetaryjs.plugins.earth({ + topojson: { file: 'world-110m.json' }, + oceans: { fill: '#000080' }, + land: { fill: '#339966' }, + borders: { stroke: '#008000' } + })); + // The `pings` plugin draws animated pings on the globe. + earth.loadPlugin(planetaryjs.plugins.pings()); + // Load our custom `autorotate` plugin; see below. + earth.loadPlugin(autorotate(10)); + // Set up the globe's initial scale, offset, and rotation. + earth.projection.scale(175).translate([175, 175]).rotate([0, -10, 0]); + + // Every few hundred milliseconds, we'll draw another random ping. + var colors = ['red', 'yellow', 'white', 'orange', 'purple', 'cyan']; + setInterval(function() { + var lat = Math.random() * 170 - 85; + var lng = Math.random() * 360 - 180; + var color = colors[Math.floor(Math.random() * colors.length)]; + earth.addPing(lat, lng, { color: color, ttl: 2000, angle: Math.random() * 10 }); + }, 250); + + var canvas = document.getElementById('homepage-globe-canvas'); + // Special code to handle high-density displays (e.g. retina, some phones) + // In the future, Planetary.js will handle this by itself (or via a plugin). + if (window.devicePixelRatio == 2) { + canvas.width = 700; + canvas.height = 700; + context = canvas.getContext('2d'); + context.scale(2, 2); + } + // Draw that globe! + earth.draw(canvas); + + // This plugin will automatically rotate the globe around its vertical + // axis a configured number of degrees every second. + function autorotate(degPerSec) { + // Planetary.js plugins are functions that take a `planet` instance + // as an argument... + return function(planet) { + var lastTick = null; + // ...and configure hooks into certain pieces of its lifecycle. + planet.onDraw(function() { + if (!lastTick) { + lastTick = new Date(); + } else { + var now = new Date(); + var delta = now - lastTick; + // This plugin uses the built-in projection (provided by D3) + // to rotate the globe each time we draw it. + var rotation = planet.projection.rotate(); + rotation[0] += degPerSec * delta / 1000; + if (rotation[0] >= 180) rotation[0] -= 360; + planet.projection.rotate(rotation); + lastTick = now; + } + }); + }; + }; +})(); diff --git a/site/public/js/d3.v3.min.js b/site/public/js/lib/d3.v3.min.js similarity index 100% rename from site/public/js/d3.v3.min.js rename to site/public/js/lib/d3.v3.min.js diff --git a/site/public/js/lib/planetaryjs.min.js b/site/public/js/lib/planetaryjs.min.js new file mode 100644 index 0000000..bbdfb32 --- /dev/null +++ b/site/public/js/lib/planetaryjs.min.js @@ -0,0 +1,2 @@ +/*! Planetary.js 0.0.0 | (c) 2013 Brandon Tilley | Released under MIT License */ +!function(n,t){"function"==typeof define&&define.amd?define(["d3","topojson"],t):"object"==typeof exports?module.exports=t(require("d3"),require("topojson")):n.planetaryjs=t(n.d3,n.topojson,n)}(this,function(n,t,o){"use strict";var e=null;o&&(e=o.planetaryjs);var i=[],r=function(t,o,e){n.timer(function(){t.context.clearRect(0,0,o.width,o.height);for(var n=0;n=o.onInit.length?r(n,t,o):i(l)};i(l)}else r(n,t,o)},a=function(n,t,o,e){l(n,o),n.canvas=t,n.context=t.getContext("2d"),u(n,t,e)},c={plugins:{},noConflict:function(){return o.planetaryjs=e,c},loadPlugin:function(n){i.push(n)},planet:function(){var t=[],o={onInit:[],onDraw:[]},e={draw:function(n){a(e,n,t,o)},onInit:function(n){o.onInit.push(n)},onDraw:function(n){o.onDraw.push(n)},loadPlugin:function(n){t.push(n)},withSavedContext:function(n){if(!this.context)throw new Error("No canvas to fetch context for");this.context.save(),n(this.context),this.context.restore()}};return e.projection=n.geo.orthographic().clipAngle(90).precision(0),e.path=n.geo.path().projection(e.projection),e}};return c.plugins.topojson=function(t){return function(o){o.onInit(function(e){if(t.world)o.world=t.world,setTimeout(e,0);else{var i=t.file||"world-110m.json";n.json(i,function(n,t){if(n)throw new Error("Could not load JSON "+i);o.world=t,e()})}})}},c.plugins.oceans=function(n){return function(t){t.onDraw(function(){t.withSavedContext(function(o){o.beginPath(),t.path.context(o)({type:"Sphere"}),o.fillStyle=n.fill||"black",o.fill(),0!=n.stroke&&(o.strokeStyle=n.stroke,o.stroke())})})}},c.plugins.land=function(n){return function(o){var e=null;o.onInit(function(){e=t.feature(o.world,o.world.objects.land)}),o.onDraw(function(){o.withSavedContext(function(t){t.beginPath(),o.path.context(t)(e),0!=n.fill&&(t.fillStyle=n.fill||"white",t.fill()),n.stroke&&(t.strokeStyle=n.stroke,t.stroke())})})}},c.plugins.borders=function(n){return function(o){var e=null;o.onInit(function(){var n=o.world.objects.countries;e=t.mesh(o.world,n,function(n,t){return n.id!==t.id})}),o.onDraw(function(){o.withSavedContext(function(t){t.beginPath(),o.path.context(t)(e),t.strokeStyle=n.stroke||"gray",t.stroke()})})}},c.plugins.earth=function(n){var n=n||{},t=n.topojson||{},o=n.oceans||{},e=n.land||{},i=n.borders||{};return function(n){c.plugins.topojson(t)(n),c.plugins.oceans(o)(n),c.plugins.land(e)(n),c.plugins.borders(i)(n)}},c.plugins.pings=function(){var t=[],o=function(n,o,e){var e=e||{};e.color=e.color||"white",e.ttl=e.ttl||2e3,e.angle=e.angle||5,t.push({lat:n,lng:o,time:new Date,options:e})},e=function(n,o,e){for(var r=[],l=0;l - projection.scale(d3.event.scale) - -canvas = d3.select('.homepage-globe-canvas').selectAll('canvas') - .data(d3.range(1)) - .enter().append('canvas') - .attr('width', 350) - .attr('height', 350) - .call (canvas) -> - if window.devicePixelRatio == 2 - canvas[0][0].width = 700 - canvas[0][0].height = 700 - context = canvas[0][0].getContext('2d') - context.scale(2, 2) - -path = d3.geo.path() - .projection(projection) - -pings = [] -addPing = (pos) -> - pings.push { pos: pos, time: Date.now() } - -setInterval (-> - lat = Math.random() * 180 - 90 - lng = Math.random() * 360 - 180 - - addPing([lat, lng]) -), 300 - -d3.json "world-110m.json", (error, world) -> - land = topojson.feature(world, world.objects.land) - borders = topojson.mesh world, world.objects.countries, (a, b) -> - a.id != b.id - globe = type: "Sphere" - - lastRotation = Date.now() - rotate = [0, -5, 0] - - velocity = -0.50 - d3.timer -> - canvas.each (i) -> - now = Date.now() - difference = now - lastRotation - lastRotation = now - rotate[i] -= velocity * difference / 100 - context = @getContext('2d') - projection.rotate(rotate) - - context.clearRect(0, 0, window.innerWidth, window.innerHeight) - - context.beginPath() - path(globe) - context.fillStyle = '#000080' - context.fill() - - context.beginPath() - context.fillStyle = '#339966' - path.context(context)(land) - context.fill() - - context.strokeStyle = '#008000' - context.beginPath() - path.context(context)(borders) - context.stroke() - - context.strokeStyle = '#fff' - context.lineWidth = 1 - currentTime = Date.now() - for ping in pings - difference = (currentTime - ping.time) # milliseconds - alpha = 1 - (difference / 2000) - context.strokeStyle = "rgba(255,255,255,#{alpha})" - circle = d3.geo.circle().origin(ping.pos).angle(difference / 500)() - context.beginPath() - path.context(context)(circle) - context.stroke() - pings = (ping for ping in pings when currentTime - ping.time <= 2000) - null diff --git a/src/plugins.js b/src/plugins.js index 31d0b18..c04ec11 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -107,7 +107,7 @@ var options = options || {}; options.color = options.color || 'white'; options.ttl = options.ttl || 2000; - options.size = options.size || 500; + options.angle = options.angle || 5; pings.push({ lat: lat, lng: lng, time: new Date(), options: options }); }; @@ -125,13 +125,13 @@ }; var drawPing = function(planet, context, now, ping) { - var dT = now - ping.time; - var alpha = 1 - (dT / ping.options.ttl); + var alive = now - ping.time; + 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(dT / ping.options.size)(); + .angle(alive / ping.options.ttl * ping.options.angle)(); context.beginPath(); planet.path.context(context)(circle); context.stroke();