diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2ccbe46 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules/ diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..39c5712 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,39 @@ +var gulp = require('gulp'); +var uglify = require('gulp-uglify'); +var concat = require('gulp-concat'); +var rename = require('gulp-rename'); +var header = require('gulp-header'); +var gzip = require("gulp-gzip"); +var metadata = require('./package.json'); + +var shortHeader = "/*! Planetary.js {{version}} | (c) 2013 Brandon Tilley | Released under MIT License */" +var fullHeader = [ + "/*! Planetary.js v{{version}}", + " * Copyright (c) 2013 Brandon Tilley", + " *", + " * Released under the MIT license", + " * Date: {{now}}", + " */" +].join("\n"); + +var fullSource = gulp.src(['./src/_umd_header.js', './src/body.js', './src/plugins.js', './src/_umd_footer.js']); +var nonPluginSource = gulp.src(['./src/_umd_header.js', './src/body.js', './src/_umd_footer.js']); + +function build(source, name, headerText, minify, gzip) { + var js = source.pipe(concat(name)); + if (minify) { js = js.pipe(uglify()); } + js = js.pipe(header(headerText, { version: metadata.version })); + if (gzip) { js = js.pipe(gzip()); } + js.pipe(gulp.dest('./dist')); +} + +gulp.task('build', function() { + build(fullSource, 'planetaryjs.js', fullHeader, false); + build(fullSource, 'planetaryjs.min.js', shortHeader, true); + build(nonPluginSource, 'planetaryjs-noplugins.js', fullHeader, false); + build(nonPluginSource, 'planetaryjs-noplugins.min.js', shortHeader, true); +}); + +gulp.task('default', function() { + gulp.run('build'); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..ff04013 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "Planetary.js", + "version": "0.0.0", + "description": "Awesome interactive globes for the web", + "scripts": { + "build": "rm -r dist/ ; gulp", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git://github.com/BinaryMuse/planetary.js.git" + }, + "author": "Brandon Tilley ", + "license": "MIT", + "bugs": { + "url": "https://github.com/BinaryMuse/planetary.js/issues" + }, + "homepage": "http://planetaryjs.com", + "devDependencies": { + "gulp": "~3.2.0", + "gulp-concat": "BinaryMuse/gulp-concat", + "gulp-header": "~0.4.0", + "gulp-uglify": "~0.1.0", + "gulp-rename": "~0.2.1" + } +} diff --git a/src/_umd_footer.js b/src/_umd_footer.js new file mode 100644 index 0000000..2f2db63 --- /dev/null +++ b/src/_umd_footer.js @@ -0,0 +1,2 @@ + return planetaryjs; +})); diff --git a/src/_umd_header.js b/src/_umd_header.js new file mode 100644 index 0000000..cf27df7 --- /dev/null +++ b/src/_umd_header.js @@ -0,0 +1,10 @@ +(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'; diff --git a/src/body.js b/src/body.js new file mode 100644 index 0000000..39aba78 --- /dev/null +++ b/src/body.js @@ -0,0 +1,111 @@ + 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 startDraw = function(planet, canvas, localPlugins, hooks) { + for (var i = 0; i < plugins.length; i++) { + localPlugins.unshift(plugins[i]); + } + + if (localPlugins.length == 0 && planetaryjs.plugins.earth) { + planet.loadPlugin(planetaryjs.plugins.earth()); + } + + for (var i = 0; i < localPlugins.length; i++) { + var plugin = localPlugins[i][0]; + var config = localPlugins[i][1]; + plugin(planet, config); + } + + planet.canvas = canvas; + planet.context = canvas.getContext('2d'); + + 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 planetaryjs = { + plugins: {}, + + noConflict: function() { + window.planetaryjs = originalPlanetaryjs; + return planetaryjs; + }, + + loadPlugin: function(plugin, defaultOptions) { + plugins.push([plugin, defaultOptions || {}]); + }, + + planet: function() { + var localPlugins = []; + var hooks = { + onInit: [], + onDraw: [] + }; + + var planet = { + 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, defaultOptions) { + localPlugins.push([plugin, defaultOptions || {}]); + }, + + 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; + } + }; diff --git a/src/plugins.js b/src/plugins.js new file mode 100644 index 0000000..5fa6ce8 --- /dev/null +++ b/src/plugins.js @@ -0,0 +1,93 @@ + planetaryjs.plugins.topojson = function(planet, config) { + planet.onInit(function(done) { + if (config.world) { + planet.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.world = world; + done(); + }); + } + }) + }; + + planetaryjs.plugins.oceans = function(planet, config) { + 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(planet, config) { + var land = null; + + planet.onInit(function() { + land = topojson.feature(planet.world, planet.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(planet, config) { + var borders = null; + planet.onInit(function() { + var countries = planet.world.objects.countries; + borders = topojson.mesh(planet.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(options) { + var options = options || {}; + var topojsonOptions = options.topojson || {}; + var oceanOptions = options.oceans || {}; + var landOptions = options.land || {}; + var bordersOptions = options.borders || {}; + + return function(planet, options) { + planetaryjs.plugins.topojson(planet, topojsonOptions); + planetaryjs.plugins.oceans(planet, oceanOptions); + planetaryjs.plugins.land(planet, landOptions); + planetaryjs.plugins.borders(planet, bordersOptions); + }; + };