From 823a938a4bf668acdcc3216b63a4a7795d3200dc Mon Sep 17 00:00:00 2001 From: Brandon Tilley Date: Sun, 22 Dec 2013 13:59:41 -0800 Subject: [PATCH] Add the 'drag' and 'zoom' plugins --- CHANGELOG.md | 2 + dist/planetaryjs-noplugins.js | 2 +- dist/planetaryjs.js | 67 ++++++++++++++++++++++- dist/planetaryjs.min.js | 2 +- site/public/css/planetaryjs.less | 1 + site/public/documentation/builtin_drag.md | 23 +++++++- site/public/documentation/builtin_zoom.md | 26 ++++++++- site/public/js/homepage.js | 22 +++++++- site/public/js/lib/planetaryjs.min.js | 2 +- src/plugins.js | 65 ++++++++++++++++++++++ 10 files changed, 205 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7ec47c..e3f89ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +* Implement the `drag` and `zoom` plugins + v0.2.0 ------ diff --git a/dist/planetaryjs-noplugins.js b/dist/planetaryjs-noplugins.js index 2fdab20..2999c58 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-22T07:35:17.700Z + * Date: 2013-12-22T21:59:27.746Z */ (function (root, factory) { if (typeof define === 'function' && define.amd) { diff --git a/dist/planetaryjs.js b/dist/planetaryjs.js index 3ec2867..9968dc8 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-22T07:35:17.783Z + * Date: 2013-12-22T21:59:27.872Z */ (function (root, factory) { if (typeof define === 'function' && define.amd) { @@ -306,5 +306,70 @@ }; }; + planetaryjs.plugins.zoom = function (options) { + var options = options || {}; + var noop = function() {}; + var onZoomStart = options.onZoomStart || noop; + var onZoomEnd = options.onZoomEnd || noop; + var onZoom = options.onZoom || noop; + var startScale = options.initialScale; + var scaleExtent = options.scaleExtent || [50, 2000]; + + return function(planet) { + planet.onInit(function() { + var zoom = d3.behavior.zoom() + .scaleExtent(scaleExtent); + if (startScale) { + zoom.scale(startScale); + } else { + zoom.scale(planet.projection.scale()); + } + zoom + .on('zoomstart', onZoomStart) + .on('zoomend', onZoomEnd) + .on('zoom', function() { + onZoom(); + planet.projection.scale(d3.event.scale); + }); + d3.select(planet.canvas).call(zoom); + }); + }; + }; + + planetaryjs.plugins.drag = function(options) { + var options = options || {}; + var noop = function() {}; + var onDragStart = options.onDragStart || noop; + var onDragEnd = options.onDragEnd || noop; + var onDrag = options.onDrag || noop; + + return function(planet) { + planet.onInit(function() { + var drag = d3.behavior.drag() + .on('dragstart', onDragStart) + .on('dragend', onDragEnd) + .on('drag', function() { + onDrag(); + var dx = d3.event.dx; + var dy = d3.event.dy; + var rotation = planet.projection.rotate(); + var radius = planet.projection.scale(); + var scale = d3.scale.linear() + .domain([-1 * radius, radius]) + .range([-90, 90]); + var degX = scale(dx); + var degY = scale(dy); + rotation[0] += degX; + rotation[1] -= degY; + if (rotation[1] > 90) rotation[1] = 90; + if (rotation[1] < -90) rotation[1] = -90; + if (rotation[0] >= 180) rotation[0] -= 360; + planet.projection.rotate(rotation); + }); + d3.select(planet.canvas).call(drag); + }); + }; + }; + return planetaryjs; })); diff --git a/dist/planetaryjs.min.js b/dist/planetaryjs.min.js index bbbbd47..2248ce1 100644 --- a/dist/planetaryjs.min.js +++ b/dist/planetaryjs.min.js @@ -1,2 +1,2 @@ /*! Planetary.js 0.2.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 i=null;o&&(i=o.planetaryjs);var e=[],r=function(t,o,i){n.timer(function(){t.context.clearRect(0,0,o.width,o.height);for(var n=0;n=o.onInit.length?r(n,t,o):e(l)};e(l)}else r(n,t,o)},a=function(n,t,o,i){l(n,o),n.canvas=t,n.context=t.getContext("2d"),u(n,t,i)},s={plugins:{},noConflict:function(){return o.planetaryjs=i,s},loadPlugin:function(n){e.push(n)},planet:function(){var t=[],o={onInit:[],onDraw:[]},i={plugins:{},draw:function(n){a(i,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 i.projection=n.geo.orthographic().clipAngle(90).precision(0),i.path=n.geo.path().projection(i.projection),i}};return s.plugins.topojson=function(t){return function(o){o.plugins.topojson={},o.onInit(function(i){if(t.world)o.plugins.topojson.world=t.world,setTimeout(i,0);else{var e=t.file||"world-110m.json";n.json(e,function(n,t){if(n)throw new Error("Could not load JSON "+e);o.plugins.topojson.world=t,i()})}})}},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()})})}},s.plugins.land=function(n){return function(o){var i=null;o.onInit(function(){var n=o.plugins.topojson.world;i=t.feature(n,n.objects.land)}),o.onDraw(function(){o.withSavedContext(function(t){t.beginPath(),o.path.context(t)(i),0!=n.fill&&(t.fillStyle=n.fill||"white",t.fill()),n.stroke&&(n.lineWidth&&(t.lineWidth=n.lineWidth),t.strokeStyle=n.stroke,t.stroke())})})}},s.plugins.borders=function(n){return function(o){var i=null,e={internal:function(n,t){return n.id!==t.id},external:function(n,t){return n.id===t.id},both:function(){return!0}};o.onInit(function(){var r=o.plugins.topojson.world,l=r.objects.countries,u=n.type||"internal";i=t.mesh(r,l,e[u])}),o.onDraw(function(){o.withSavedContext(function(t){t.beginPath(),o.path.context(t)(i),t.strokeStyle=n.stroke||"gray",n.lineWidth&&(t.lineWidth=n.lineWidth),t.stroke()})})}},s.plugins.earth=function(n){var n=n||{},t=n.topojson||{},o=n.oceans||{},i=n.land||{},e=n.borders||{};return function(n){s.plugins.topojson(t)(n),s.plugins.oceans(o)(n),s.plugins.land(i)(n),s.plugins.borders(e)(n)}},s.plugins.pings=function(t){var o=[],i=function(n,i,e){var e=e||{};e.color=e.color||t.color||"white",e.ttl=e.ttl||t.ttl||2e3,e.angle=e.angle||t.angle||5,o.push({lat:n,lng:i,time:new Date,options:e})},e=function(n,t,i){for(var e=[],l=0;l=o.onInit.length?r(n,t,o):e(l)};e(l)}else r(n,t,o)},u=function(n,t,o,i){l(n,o),n.canvas=t,n.context=t.getContext("2d"),a(n,t,i)},c={plugins:{},noConflict:function(){return o.planetaryjs=i,c},loadPlugin:function(n){e.push(n)},planet:function(){var t=[],o={onInit:[],onDraw:[]},i={plugins:{},draw:function(n){u(i,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 i.projection=n.geo.orthographic().clipAngle(90).precision(0),i.path=n.geo.path().projection(i.projection),i}};return c.plugins.topojson=function(t){return function(o){o.plugins.topojson={},o.onInit(function(i){if(t.world)o.plugins.topojson.world=t.world,setTimeout(i,0);else{var e=t.file||"world-110m.json";n.json(e,function(n,t){if(n)throw new Error("Could not load JSON "+e);o.plugins.topojson.world=t,i()})}})}},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()})})}},c.plugins.land=function(n){return function(o){var i=null;o.onInit(function(){var n=o.plugins.topojson.world;i=t.feature(n,n.objects.land)}),o.onDraw(function(){o.withSavedContext(function(t){t.beginPath(),o.path.context(t)(i),0!=n.fill&&(t.fillStyle=n.fill||"white",t.fill()),n.stroke&&(n.lineWidth&&(t.lineWidth=n.lineWidth),t.strokeStyle=n.stroke,t.stroke())})})}},c.plugins.borders=function(n){return function(o){var i=null,e={internal:function(n,t){return n.id!==t.id},external:function(n,t){return n.id===t.id},both:function(){return!0}};o.onInit(function(){var r=o.plugins.topojson.world,l=r.objects.countries,a=n.type||"internal";i=t.mesh(r,l,e[a])}),o.onDraw(function(){o.withSavedContext(function(t){t.beginPath(),o.path.context(t)(i),t.strokeStyle=n.stroke||"gray",n.lineWidth&&(t.lineWidth=n.lineWidth),t.stroke()})})}},c.plugins.earth=function(n){var n=n||{},t=n.topojson||{},o=n.oceans||{},i=n.land||{},e=n.borders||{};return function(n){c.plugins.topojson(t)(n),c.plugins.oceans(o)(n),c.plugins.land(i)(n),c.plugins.borders(e)(n)}},c.plugins.pings=function(t){var o=[],i=function(n,i,e){var e=e||{};e.color=e.color||t.color||"white",e.ttl=e.ttl||t.ttl||2e3,e.angle=e.angle||t.angle||5,o.push({lat:n,lng:i,time:new Date,options:e})},e=function(n,t,i){for(var e=[],l=0;l90&&(e[1]=90),e[1]<-90&&(e[1]=-90),e[0]>=180&&(e[0]-=360),t.projection.rotate(e)});n.select(t.canvas).call(o)})}},c}); \ No newline at end of file diff --git a/site/public/css/planetaryjs.less b/site/public/css/planetaryjs.less index 54ea031..c51dd34 100644 --- a/site/public/css/planetaryjs.less +++ b/site/public/css/planetaryjs.less @@ -67,6 +67,7 @@ a.ui.icon.header { canvas#homepage-globe-canvas { width: 350px; height: 350px; + cursor: move; } @media screen and (max-width: 768px) { diff --git a/site/public/documentation/builtin_drag.md b/site/public/documentation/builtin_drag.md index b6139cb..94aea80 100644 --- a/site/public/documentation/builtin_drag.md +++ b/site/public/documentation/builtin_drag.md @@ -1,4 +1,25 @@ Drag Plugin =========== -This plugin is not yet impelemented. +The `drag` plugin allows for modifying the planet's projection's rotation with the mouse wheel. It supports dragging vertically with hard stops at the north and south poles, and dragging horizontally without restriction. + +API +--- + +**`planetaryjs.plugins.drag([config])`** + +Valid keys for `config` are: + +* `onDrag`, `onDragStart`, `onDragEnd`: hooks to the `d3.behavior.drag` object's `drag`, `dragstart`, and `dragend` events; each defaults to a no-op + +
+
JavaScript
+ +```javascript +planetaryjs.plugins.drag({ + onDrag: function() { + console.log("The planet was dragged!", d3.event); + } +}); +``` +
diff --git a/site/public/documentation/builtin_zoom.md b/site/public/documentation/builtin_zoom.md index 6bdc953..930d714 100644 --- a/site/public/documentation/builtin_zoom.md +++ b/site/public/documentation/builtin_zoom.md @@ -1,4 +1,28 @@ Zoom Plugin =========== -This plugin is not yet impelemented. +The `zoom` plugin allows for modifying the planet's projection's scale with the mouse wheel. + +API +--- + +**`planetaryjs.plugins.zoom([config])`** + +Valid keys for `config` are: + +* `initialScale`: the value to initialize the [`d3.behavior.zoom`](https://github.com/mbostock/d3/wiki/Zoom-Behavior) object's scale to; defaults to the scale of the planet's projection at the time the planet is initialized +* `scaleExtent`: the value to use for the `d3.behavior.zoom` object's `scaleExtent` property, which defines how far in and out the planet can be zoomed; defaults to `[50, 2000]` +* `onZoom`, `onZoomStart`, `onZoomEnd`: hooks to the `d3.behavior.zoom` object's `zoom`, `zoomstart`, and `zoomend` events; each defaults to a no-op + +
+
JavaScript
+ +```javascript +planetaryjs.plugins.zoom({ + scaleExtent: [200, 1000], + onZoom: function() { + console.log("The planet was zoomed!", d3.event); + } +}); +``` +
diff --git a/site/public/js/homepage.js b/site/public/js/homepage.js index b313bf1..b0a490f 100644 --- a/site/public/js/homepage.js +++ b/site/public/js/homepage.js @@ -12,6 +12,21 @@ globe.loadPlugin(planetaryjs.plugins.pings()); // Load our custom `autorotate` plugin; see below. globe.loadPlugin(autorotate(10)); + // The `zoom` and `drag` plugins enable + // manipulating the globe with the mouse. + globe.loadPlugin(planetaryjs.plugins.zoom({ + scaleExtent: [100, 300] + })); + globe.loadPlugin(planetaryjs.plugins.drag({ + // Dragging the globe shoud pause the + // automatic rotation until we release the mouse. + onDragStart: function() { + globe.plugins.autorotate.pause(); + }, + onDragEnd: function() { + globe.plugins.autorotate.resume(); + } + })) // Set up the globe's initial scale, offset, and rotation. globe.projection.scale(175).translate([175, 175]).rotate([0, -10, 0]); @@ -43,9 +58,14 @@ // as an argument... return function(planet) { var lastTick = null; + var paused = false; + planet.plugins.autorotate = { + pause: function() { paused = true; }, + resume: function() { paused = false; } + }; // ...and configure hooks into certain pieces of its lifecycle. planet.onDraw(function() { - if (!lastTick) { + if (paused || !lastTick) { lastTick = new Date(); } else { var now = new Date(); diff --git a/site/public/js/lib/planetaryjs.min.js b/site/public/js/lib/planetaryjs.min.js index bbbbd47..2248ce1 100644 --- a/site/public/js/lib/planetaryjs.min.js +++ b/site/public/js/lib/planetaryjs.min.js @@ -1,2 +1,2 @@ /*! Planetary.js 0.2.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 i=null;o&&(i=o.planetaryjs);var e=[],r=function(t,o,i){n.timer(function(){t.context.clearRect(0,0,o.width,o.height);for(var n=0;n=o.onInit.length?r(n,t,o):e(l)};e(l)}else r(n,t,o)},a=function(n,t,o,i){l(n,o),n.canvas=t,n.context=t.getContext("2d"),u(n,t,i)},s={plugins:{},noConflict:function(){return o.planetaryjs=i,s},loadPlugin:function(n){e.push(n)},planet:function(){var t=[],o={onInit:[],onDraw:[]},i={plugins:{},draw:function(n){a(i,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 i.projection=n.geo.orthographic().clipAngle(90).precision(0),i.path=n.geo.path().projection(i.projection),i}};return s.plugins.topojson=function(t){return function(o){o.plugins.topojson={},o.onInit(function(i){if(t.world)o.plugins.topojson.world=t.world,setTimeout(i,0);else{var e=t.file||"world-110m.json";n.json(e,function(n,t){if(n)throw new Error("Could not load JSON "+e);o.plugins.topojson.world=t,i()})}})}},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()})})}},s.plugins.land=function(n){return function(o){var i=null;o.onInit(function(){var n=o.plugins.topojson.world;i=t.feature(n,n.objects.land)}),o.onDraw(function(){o.withSavedContext(function(t){t.beginPath(),o.path.context(t)(i),0!=n.fill&&(t.fillStyle=n.fill||"white",t.fill()),n.stroke&&(n.lineWidth&&(t.lineWidth=n.lineWidth),t.strokeStyle=n.stroke,t.stroke())})})}},s.plugins.borders=function(n){return function(o){var i=null,e={internal:function(n,t){return n.id!==t.id},external:function(n,t){return n.id===t.id},both:function(){return!0}};o.onInit(function(){var r=o.plugins.topojson.world,l=r.objects.countries,u=n.type||"internal";i=t.mesh(r,l,e[u])}),o.onDraw(function(){o.withSavedContext(function(t){t.beginPath(),o.path.context(t)(i),t.strokeStyle=n.stroke||"gray",n.lineWidth&&(t.lineWidth=n.lineWidth),t.stroke()})})}},s.plugins.earth=function(n){var n=n||{},t=n.topojson||{},o=n.oceans||{},i=n.land||{},e=n.borders||{};return function(n){s.plugins.topojson(t)(n),s.plugins.oceans(o)(n),s.plugins.land(i)(n),s.plugins.borders(e)(n)}},s.plugins.pings=function(t){var o=[],i=function(n,i,e){var e=e||{};e.color=e.color||t.color||"white",e.ttl=e.ttl||t.ttl||2e3,e.angle=e.angle||t.angle||5,o.push({lat:n,lng:i,time:new Date,options:e})},e=function(n,t,i){for(var e=[],l=0;l=o.onInit.length?r(n,t,o):e(l)};e(l)}else r(n,t,o)},u=function(n,t,o,i){l(n,o),n.canvas=t,n.context=t.getContext("2d"),a(n,t,i)},c={plugins:{},noConflict:function(){return o.planetaryjs=i,c},loadPlugin:function(n){e.push(n)},planet:function(){var t=[],o={onInit:[],onDraw:[]},i={plugins:{},draw:function(n){u(i,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 i.projection=n.geo.orthographic().clipAngle(90).precision(0),i.path=n.geo.path().projection(i.projection),i}};return c.plugins.topojson=function(t){return function(o){o.plugins.topojson={},o.onInit(function(i){if(t.world)o.plugins.topojson.world=t.world,setTimeout(i,0);else{var e=t.file||"world-110m.json";n.json(e,function(n,t){if(n)throw new Error("Could not load JSON "+e);o.plugins.topojson.world=t,i()})}})}},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()})})}},c.plugins.land=function(n){return function(o){var i=null;o.onInit(function(){var n=o.plugins.topojson.world;i=t.feature(n,n.objects.land)}),o.onDraw(function(){o.withSavedContext(function(t){t.beginPath(),o.path.context(t)(i),0!=n.fill&&(t.fillStyle=n.fill||"white",t.fill()),n.stroke&&(n.lineWidth&&(t.lineWidth=n.lineWidth),t.strokeStyle=n.stroke,t.stroke())})})}},c.plugins.borders=function(n){return function(o){var i=null,e={internal:function(n,t){return n.id!==t.id},external:function(n,t){return n.id===t.id},both:function(){return!0}};o.onInit(function(){var r=o.plugins.topojson.world,l=r.objects.countries,a=n.type||"internal";i=t.mesh(r,l,e[a])}),o.onDraw(function(){o.withSavedContext(function(t){t.beginPath(),o.path.context(t)(i),t.strokeStyle=n.stroke||"gray",n.lineWidth&&(t.lineWidth=n.lineWidth),t.stroke()})})}},c.plugins.earth=function(n){var n=n||{},t=n.topojson||{},o=n.oceans||{},i=n.land||{},e=n.borders||{};return function(n){c.plugins.topojson(t)(n),c.plugins.oceans(o)(n),c.plugins.land(i)(n),c.plugins.borders(e)(n)}},c.plugins.pings=function(t){var o=[],i=function(n,i,e){var e=e||{};e.color=e.color||t.color||"white",e.ttl=e.ttl||t.ttl||2e3,e.angle=e.angle||t.angle||5,o.push({lat:n,lng:i,time:new Date,options:e})},e=function(n,t,i){for(var e=[],l=0;l90&&(e[1]=90),e[1]<-90&&(e[1]=-90),e[0]>=180&&(e[0]-=360),t.projection.rotate(e)});n.select(t.canvas).call(o)})}},c}); \ No newline at end of file diff --git a/src/plugins.js b/src/plugins.js index 1e1346b..4aad5af 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -161,3 +161,68 @@ }); }; }; + + planetaryjs.plugins.zoom = function (options) { + var options = options || {}; + var noop = function() {}; + var onZoomStart = options.onZoomStart || noop; + var onZoomEnd = options.onZoomEnd || noop; + var onZoom = options.onZoom || noop; + var startScale = options.initialScale; + var scaleExtent = options.scaleExtent || [50, 2000]; + + return function(planet) { + planet.onInit(function() { + var zoom = d3.behavior.zoom() + .scaleExtent(scaleExtent); + if (startScale) { + zoom.scale(startScale); + } else { + zoom.scale(planet.projection.scale()); + } + zoom + .on('zoomstart', onZoomStart) + .on('zoomend', onZoomEnd) + .on('zoom', function() { + onZoom(); + planet.projection.scale(d3.event.scale); + }); + d3.select(planet.canvas).call(zoom); + }); + }; + }; + + planetaryjs.plugins.drag = function(options) { + var options = options || {}; + var noop = function() {}; + var onDragStart = options.onDragStart || noop; + var onDragEnd = options.onDragEnd || noop; + var onDrag = options.onDrag || noop; + + return function(planet) { + planet.onInit(function() { + var drag = d3.behavior.drag() + .on('dragstart', onDragStart) + .on('dragend', onDragEnd) + .on('drag', function() { + onDrag(); + var dx = d3.event.dx; + var dy = d3.event.dy; + var rotation = planet.projection.rotate(); + var radius = planet.projection.scale(); + var scale = d3.scale.linear() + .domain([-1 * radius, radius]) + .range([-90, 90]); + var degX = scale(dx); + var degY = scale(dy); + rotation[0] += degX; + rotation[1] -= degY; + if (rotation[1] > 90) rotation[1] = 90; + if (rotation[1] < -90) rotation[1] = -90; + if (rotation[0] >= 180) rotation[0] -= 360; + planet.projection.rotate(rotation); + }); + d3.select(planet.canvas).call(drag); + }); + }; + };