diff --git a/.gitignore b/.gitignore index 81d6203b7e..ed9a4b3229 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ Gemfile.lock test/SpecRunner.html .rvmrc .idea/ +themes/css/cartodb.css +cartodb.js-bower/ \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index 49c9cd237d..8b90113f2c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -4,15 +4,24 @@ * framework * */ - - module.exports = function(grunt) { - + require('load-grunt-tasks')(grunt); require('time-grunt')(grunt); + var semver = require('semver'); var pkg = grunt.file.readJSON('package.json'); + + if (!pkg.version || !semver.valid(pkg.version)) { + grunt.fail.fatal('package.json version is not valid' , 1); + } + var version = pkg.version.split('.'); + var VERSION_OBJ = { + major: version[0], + minor: version[0] + '.' + version[1], + bugfixing: pkg.version + } var config = { dist: 'dist', @@ -22,11 +31,20 @@ module.exports = function(grunt) { minor: version[0] + '.' + version[1], bugfixing: pkg.version }, - pkg: pkg + pkg: pkg }; grunt.initConfig({ + secrets: {}, config: config, + dist: 'dist', + app: 'www', + version: { + major: version[0], + minor: version[0] + '.' + version[1], + bugfixing: pkg.version + }, + pkg: pkg, gitinfo: {}, s3: require('./grunt/tasks/s3').task(grunt, config), prompt: require('./grunt/tasks/prompt').task(grunt, config), @@ -87,6 +105,7 @@ module.exports = function(grunt) { grunt.registerTask('test', [ 'jasmine' ]); grunt.registerTask('release', [ + 'prompt:bump', 'build' ]); @@ -96,7 +115,7 @@ module.exports = function(grunt) { grunt.fail.fatal('secrets.json file does not exist, copy secrets.example.json and rename it' , 1); } - // Read secrets + // Read secrets grunt.config.set('secrets', grunt.file.readJSON('secrets.json')); if ( @@ -114,12 +133,35 @@ module.exports = function(grunt) { ]); }); + grunt.registerTask('set_current_version', function() { + var version = pkg.version; + var minor = version.split('.'); + minor.pop() + minor = minor.join('.'); + var options = { + version: version, + minor: minor, + increment: 'build', + bugfixing: version + }; + + // Check if version was set via prompt, and + // use that version and not the package version + var bump = grunt.config.get('bump'); + if (bump) { + options = bump; + options.bugfixing = bump.version; + } + + grunt.config.set('bump', options); + }); + grunt.registerTask('invalidate', function(){ if (!grunt.file.exists('secrets.json')) { grunt.fail.fatal('secrets.json file does not exist, copy secrets.example.json and rename it' , 1); } - // Read secrets + // Read secrets grunt.config.set('secrets', grunt.file.readJSON('secrets.json')); if (!grunt.config('secrets') || @@ -137,29 +179,37 @@ module.exports = function(grunt) { grunt.registerTask('pages', [ 'buildcontrol:pages' ]); grunt.registerTask('build', [ - 'prompt:bump', - 'replace', - 'gitinfo', - 'clean:dist', - 'concurrent:dist', - 'useminPrepare', - 'concat', - 'autoprefixer:dist', - 'cssmin', - 'copy:distStatic', - 'imagemin', - 'svgmin', - 'filerev', - 'usemin', - 'htmlmin', - 'uglify' + 'dist_js', + 'useminPrepare', + 'cssmin', + 'imagemin', + 'svgmin', + 'filerev', + 'usemin', + 'htmlmin', + 'uglify' + ]); + + grunt.registerTask('dist_js', [ + 'set_current_version', + 'js' + ]) + + grunt.registerTask('js', [ + 'replace', + 'gitinfo', + 'clean:dist', + 'concurrent:dist', + 'concat', + 'autoprefixer:dist' ]); grunt.registerTask('dist', [ + 'set_current_version', 'build' ]); grunt.registerTask('default', [ - 'build' + 'dist' ]); -} \ No newline at end of file +} diff --git a/Makefile b/Makefile index 8b7de2cca7..1835a96e7d 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,9 @@ dist/cartodb.noleaflet.js: dist/_cartodb_noleaflet.js $(UGLIFYJS) dist/_cartodb_noleaflet.js > dist/cartodb.noleaflet.js rm dist/_cartodb_noleaflet.js +dist/cartodb.mod.odyssey.uncompressed.js: + grunt dist_js + dist/cartodb.css: css cp themes/css/cartodb.css dist @@ -80,7 +83,7 @@ publish_develop: release #./scripts/publish.sh node scripts/publish.js --current_version -cartodb: dist/cartodb.full.uncompressed.js dist/cartodb.mod.torque.uncompressed.js +cartodb: dist/cartodb.mod.torque.uncompressed.js dist/cartodb.mod.odyssey.uncompressed.js dist/cartodb.full.uncompressed.js diff --git a/NEWS b/NEWS index 1b035a22b6..85ad40f0ce 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,136 @@ +3.X () + +3.15.2 (01//09//2015) +* Take `visible` attribute into account when determining visibility of layers and serializing maps (#546) +* Only show legends if the layer is visible (#651) +* Extracted pecan code to separate module, https://github.com/CartoDB/pecan/ (#649,#654) +* Search control will show the result of the search with a pin and infowindow (cartodb/#4914). + +3.15 (24//06//2015) +* cartodb.js knows how to work with multiple types of sublayers (#508): + * cartodb.createLayer accepts a `filter` option to specify wich types of layers must + be rendered in the tiles. WARNING: all non-torque layers will be rendered by default. + * cartodb.js uses metadata from Windshaft to determine what layers are present in the + map and specify the layer indexes in the tile URLs. More about this + [here](https://github.com/CartoDB/Windshaft-cartodb/blob/488c2462229474db21ba40b61a93edf83e6493b5/docs/Map-API.md#blending-and-layer-selection) + * New subclasses of SubLayer for different types of sublayers. +* Handle hidden layers properly when fetching attributes from tiler +* Make the torque slider have the correct range every time it changes +* Remove check for http-beginng vizjson addresses +* Use local time in timeslider instead of UTC +* New sublayer.isVisible() function +* `cartodb.createLayer` selects the first data layer instead of assuming that it's in position 1 + +3.14.6 (16//06//2015) +* Use the right indexes when fetching grids and attributes (#518) + +3.14.4 (10//06//2015) +* Do not enable layer interaction if tooltip is empty (#513) +* Replaces minified carto.js with uncompressed version (#516) + +3.14.3 (29//05//2015) +* Hide tag of infowindow covers when the url is invalid. +* Expose legend model in sublayers/layers so that users can customize legends (#480). +* Handle tooltip overflow (#482). +* Only show tooltips when they have fields (#486). +* Updated Torque to 2.11.3 +* Fix scrolling of infowindows with images (#490). +* Fix dropdown bind events not being unbound on clean (#493) + +3.14.2 (06//05//2015) +* Allow to specify a template for the items of a custom legend. +* The NOKIA geocoder doesn't encode the whitespaces anymore. +* Adds documentation for the Static Map API + +3.14.1 (30//04//2015) +* Fixes a bug that prevented setting the maxZoom and minZoom of a map. +* Updates Torque to 2.11.2 + +3.14.0 (23//04//2015) +* Infowindow in anonymous maps are requested by attributes endpoint in maps api so SQL API is not used anymore +* Changed the way remote host is set for maps and sql API. +* Fixed error management when map instanciation fails +* Instead of showing a single date, Torque's timeslider shows the date range that a single step comprises. +* Fixed enabling or disabling the torque loop property not working from cartodb.js +* Allows to specify a step when generating a static map of a Torque layer +* Deprecation warning: + - tiler_host, tiler_prototol, tiler_port, sql_api_domain, sql_api_protocol are deprecated, use sql_api_template and maps_api_template instead. https://github.com/CartoDB/cartodb.js/blob/develop/doc/API.md#how-to-set-a-different-host-than-cartodbcom + +3.13.3 (09//04//2015) +* Fixes default styles for header titles in infowindows. + +3.13.2 (07//04//2015) +* Fix double escaping on infowindows +* Fix a-tag's target attribute not working + +3.13.1 (06//04//2015) +* Allows to request a Static Map of a password protected visualization + +3.13.0 (31//03//2015) +* Breaking Changes + - Sanitize output by default (#2972), see doc change and example below how to override: + - docs: https://github.com/CartoDB/cartodb.js/blob/v3.13.0/doc/API.md#arguments-11 + - example: https://github.com/CartoDB/cartodb.js/blob/v3.13.0/examples/infowindow_with_graph.html + +3.12.14 (30//03//2015) +- Fixes fullscreen button is throwing errors (#412) +- Updates Torque.js to 2.11 + +3.12.13 (18//03//2015) +- Changes how infowindows handle null values (#406) +- Updates the version of wax and upgrades mustache.js to v1.1.0 (403) +- Fixes a bug with fullscreen in Safari (#361) + +3.12.12 (12//03//2015) +- Fixes a bug that prevented generating previews of torque layers with named maps + +3.12.11 (04//03//2015) +- LayerDefinition now trusts the tiler and uses whatever CDN configuration it gets (or nothing, if cdn_url is empty). +- Fixes bootstrap collisions (#87, #107) + +3.12.10 (02//03//2015) +- Don't send the urlTemplate to generate a Static Map if we don't have it. +- Disables the CDN if the server doesn't send us the configuration. + +3.12.9 (26//02//2015) +- Updates Static Map module to use the CDN URL from the layergroup. + +3.12.8 (26//02//2015) +- Allows to override the default use of the bounding box to generate an image, using the center instead. +- Fixes the static map module to avoid using hidden layers to generate images. +- Extracts the CDN host configuration from the vizjson. +- Removes cdbui bower dependency. + +3.12.7 (23//02//2015) +- By default we now serve the Static API images through CartoDB's CDN. + +3.12.6 (23//02//2015) +- Fixes mobile and IE interaction issues (#346, #313, #223, #139). + +3.12.5 (20//02/2015) +- Fixes request to generate an image when the vizjson contains a named map and a torque layer with a named map + +3.12.4 (18//02/2015) +- Fixes leaflet point generation on events when using touch devices + +3.12.3 (17//02/2015) +- Fixes a case were having an empty bbox would end up generatign an erroneous bounding box URL. + +3.12.2 (17//02/2015) +- Fixes error generating a map preview of a visualization with a torque layer. +- Fixed use of https parameter in torque layer +- Fixed change of play/pause state in timeslider +- Fixed legend values named 0 being evaluated as NULL + +3.12.1 (13//02/2015) +- Allows to force the https protocol when requesting a vizjson to generate a static image + +3.12.0 (09//02/2015) +- Added Odyssey support for visualizations +- Adds new API to generate static images (https://github.com/CartoDB/cartodb.js/wiki/CartoDB-Map-API) +- Fixes the hiding of the tile loader in mobile +- Adds heatmap support for torque + 3.11.36 (09//02//2014) - Fixes slider style problem in narrower devices. @@ -10,13 +143,13 @@ 3.11.33 (05//02//2014) - Fixes tooltip style. -3.11.32 (29//01//2014) +3.11.32 (29//01//2015) - Fixed touch events on mobile (Android) -3.11.31 (23//01//2014) +3.11.31 (23//01//2015) - #291 - Removes padding and margin reset for webkit browsers -3.11.30 (13//01//2014) +3.11.30 (13//01//2015) - #264 - Fix addTo (when the second param specifies index) 3.11.29 (30//12//2014) @@ -27,8 +160,8 @@ - #255 - Adds new fonts for the overlays 3.11.27 (19//12//2014) -- #245 - Fixed a bug with error messages named map instantiation -- #224 - Public method close infowindow +- #245 - Fixed a bug with error messages named map instantiation +- #224 - Public method close infowindow 3.11.26 (17//12//2014) - #235 - Allows to use the input fields in fullscreen on Chrome @@ -55,7 +188,7 @@ - fixes a bug that made the hidden Torque layers visible 3.11.21 (24//10//2014) -- enabled dynamic_cdn to route layergroup calls though the CDN +- enabled dynamic_cdn to route layergroup calls through the CDN 3.11.20 (24//10//2014) - enabled fixed callback for layergroups and infowindows @@ -109,7 +242,7 @@ - Fixed problem breaking words in infowindow content. 3.11.06 (12//09/2014) -- Fixed problem in infowindow showing horizontal scrollbar when it was not needed +- Fixed problem in infowindow showing horizontal scrollbar when it was not needed - Fixed creating search overlay 3.11.05 (20//08/2014) @@ -247,9 +380,9 @@ - fixed problems with infowindow when there are hidden layers 3.7.04 (27//02//2014) -- fixed layer update in gmaps -- when jsonp is used errors are not reported to the layer -- updated torque, fix problem with some cartocss options (step) +- fixed layer update in gmaps +- when jsonp is used errors are not reported to the layer +- updated torque, fix problem with some cartocss options (step) 3.7.03 (25//02//2014) - Fixed https in torque tiles diff --git a/README.md b/README.md index 7be572531e..c56ce12af9 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -CartoDB.js (v3.11) +CartoDB.js (v3.15) =========== [![Build Status](http://clinker.cartodb.net/desktop/plugin/public/status/CartoDB-js-develop-testing)] (http://clinker.cartodb.net/jenkins/job/CartoDB-js-develop-testing) -This library allows to embed you visualizations created with CartoDB in your map or website in a simple way. +This library allows to embed visualizations created with CartoDB in your map or website in a simple way. ## Quick start @@ -13,54 +13,62 @@ This library allows to embed you visualizations created with CartoDB in your map ```html - - + + - + - + ``` - 2. Create the map and add the layer - + 2. Create the map and add the layer + ```javascript var map = L.map('map').setView([0, 0], 3); - // set a base layer + // set a base layer L.tileLayer('http://a.tile.stamen.com/toner/{z}/{x}/{y}.png', { attribution: 'stamen http://maps.stamen.com/' }).addTo(map); - + // add the cartodb layer var layerUrl = 'http://documentation.cartodb.com/api/v2/viz/2b13c956-e7c1-11e2-806b-5404a6a683d5/viz.json'; cartodb.createLayer(map, layerUrl).addTo(map); ``` +### Usage with Bower + +You can install **cartodb.js** with [bower](http://bower.io/) by running + +```sh +bower install cartodb.js +``` + + ## Documentation You can find the documentation online [here](http://docs.cartodb.com/cartodb-platform/cartodb-js.html) and the [source](https://github.com/CartoDB/cartodb.js/blob/develop/doc/API.md) inside this repository. ## Examples - - [Load a layer with google maps](http://cartodb.github.com/cartodb.js/examples/gmaps.html) + - [Load a layer with google maps](http://cartodb.github.com/cartodb.js/examples/gmaps_force_basemap.html) - [Load a layer with Leaflet](http://cartodb.github.com/cartodb.js/examples/leaflet.html) - [Show a complete visualization](http://cartodb.github.com/cartodb.js/examples/easy.html) - - [A visulization with a layer selector](http://cartodb.github.com/cartodb.js/examples/layer_selector.html) + - [A visualization with a layer selector](http://cartodb.github.com/cartodb.js/examples/layer_selector.html) - [How to create a custom infowindow](http://cartodb.github.com/cartodb.js/examples/custom_infowindow.html) - [The Hobbit filming location paths](http://cartodb.github.com/cartodb.js/examples/TheHobbitLocations/) a full example with some widgets ## How to build Build CartoDB.js library: - + - Install [node.js](http://nodejs.org/download/), from 0.10 version - - Install grunt: ```npm install -g grunt-cli``` - - Install node dependencies: ```npm install``` - - Install bower: ```npm install -g bower``` - - Install bower dependencies: ```bower install``` + - Install grunt & bower: `npm install -g grunt-cli bower` + - Install node dependencies: `npm install` + - Install bower dependencies: `bower install` - Install [ruby](https://www.ruby-lang.org/en/installation/) and [bundler](https://github.com/bundler/bundler) - - Install ruby dependencies: ```bundle install``` (necessary for compass gem) - - Start the server: ```grunt build``` + - Install ruby dependencies: `bundle install` (necessary for compass gem) + - Start the server: `grunt build` - Happy mapping! diff --git a/RELEASING.md b/RELEASING.md index 701afee07d..306a53e6e3 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -8,24 +8,24 @@ ### Release a new version - First of all: **MAKE SURE ALL THE TESTS ARE GREEN.** -- Then install the dependencies, follow main README.md instructions. +- Then install the dependencies, follow main README.md instructions, + [git flow](https://github.com/nvie/gitflow/wiki/Installation) - Be sure you have a valid secrets.json file (DON'T SHARE IT). -- Create a new branch to prepare the release. +- Create a new branch to prepare the release: ``` -git flow release start 3.11.36 +git flow release start 3.15.2 ``` -- Build CartoDB.js files choosing the new version: +- Build CartoDB.js files, choosing the new version: ``` -grunt build +grunt release ``` - Update the NEWS file and commit the changes. Take into account that new CartoDB.js version will be replaced in ```API.md```, ```RELEASING.md```, ```README.md```, ```package.json```, ```cartodb.js``` and ```examples``` files. ``` -git commit -am "Files changed for version 3.11.36" +git commit -am "Files changed for version 3.15.2" ``` - Release it. @@ -36,21 +36,27 @@ grunt publish - Check if those files have been updated in the CDN: ``` -http://libs.cartocdn.com.s3.amazonaws.com/cartodb.js/v3/3.11.36/cartodb.js -http://libs.cartocdn.com/cartodb.js/v3/3.11.36/cartodb.js -http://libs.cartocdn.com.s3.amazonaws.com/cartodb.js/v3/3.11/cartodb.js -http://libs.cartocdn.com/cartodb.js/v3/3.11/cartodb.js +http://libs.cartocdn.com.s3.amazonaws.com/cartodb.js/v3/3.15.2/cartodb.js +http://libs.cartocdn.com/cartodb.js/v3/3.15.2/cartodb.js +http://libs.cartocdn.com.s3.amazonaws.com/cartodb.js/v3/3.15/cartodb.js +http://libs.cartocdn.com/cartodb.js/v3/3.15/cartodb.js ``` - Sometimes It takes more than 10 minutes, if it is not updated, execute ```grunt invalidate```. - And to finish: close the release and push it. ``` -git flow release finish 3.11.36 +git flow release finish 3.15.2 git push --all git push --tags ``` +- Publish to the [cartodb.js bower repo](https://github.com/CartoDB/cartodb.js-bower) + +``` +./bower.sh +``` + - If possible, don't forget to change CartoDB.js docs. - Done. Celebrate! :) @@ -65,15 +71,14 @@ In case you screw up all things, don't worry, rollback cartodb.js to a previous ``` git checkout PREVIOUS_VERSION_TAG -grunt build +grunt grunt publish ``` -For example, if we are in 3.11.36 and we want to go back to 3.11.22 +For example, if we are in 3.15.2 and we want to go back to 3.13.4 ``` -git checkout 3.11.22 -grunt build +git checkout 3.13.4 +grunt grunt publish ``` - diff --git a/bower.json b/bower.json index 0732eb3286..6a1120fd4b 100644 --- a/bower.json +++ b/bower.json @@ -1,11 +1,20 @@ { "name": "cartodb.js", - "version": "0.0.1", - "dependencies": {}, - "devDependencies": { - "backbone": "~1.1.2", - "modernizr": "~2.8.3", - "jquery": "~2.1.1", - "cdbui": "git@github.com:CartoDB/cdbui.git#develop" - } + "main": [ + "cartodb.js", + "themes/css/cartodb.css" + ], + "version": "3.15.1", + "homepage": "https://github.com/CartoDB/cartodb.js", + "authors": [ + "CartoDB " + ], + "description": "CartoDB javascript library", + "license": "BSD", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test" + ] } diff --git a/bower.sh b/bower.sh new file mode 100755 index 0000000000..1244dfaef6 --- /dev/null +++ b/bower.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Script for updating the cartodb.js bower repo from current local build. + +echo "#################################" +echo "#### Update bower ###############" +echo "#################################" + +ORG=CartoDB +REPO=cartodb.js-bower + +# prepare repo folder +if [ -d $REPO ] + then + rm -rf $REPO +fi + +# clone repo +echo "-- Cloning $REPO" +git clone git@github.com:$ORG/$REPO.git + +# clean up cloned files +rm -rf $REPO/* + +# move js files from the build +cp -r dist/cartodb*.js $REPO/ + +# move css and images files from the build +mkdir $REPO/themes/ && cp -r dist/themes/* $REPO/themes/ + +cp -R bower.json $REPO/bower.json + +cp -R LICENSE $REPO/LICENSE.md + +# commit and tag repo +echo "-- Committing and tagging $REPO" +cd $REPO +git add -A +CARTODBJS_VER=$(git diff bower.json | grep version | cut -d':' -f2 | cut -d'"' -f2 | sort -g -r | head -1) && if [ $(echo $CARTODBJS_VER | wc -m | tr -d ' ') = '1' ]; then echo 'VERSION DID NOT CHANGE'; else git tag -a $CARTODBJS_VER -m "Version $CARTODBJS_VER"; fi +git tag -a $CARTODBJS_VER -m "Version $CARTODBJS_VER" +git commit -m "v$CARTODBJS_VER" + +echo "-- Pushing $REPO" +git push -fq origin master +git push -fq origin --tags + +cd .. + +echo "-- Finished" diff --git a/doc/API.md b/doc/API.md index 9e8b380358..72f7024deb 100644 --- a/doc/API.md +++ b/doc/API.md @@ -1,17 +1,17 @@ ## CartoDB.js -CartoDB offers a simple unified JavaScript library called CartoDB.js that let you interact with the CartoDB service. This library allows you to connect to your stored visualizations, create new visualizations, add custom interaction, or access and query your raw data from a web browser; meaning, your applications just got a whole lot more powerful with a lot less code. +CartoDB offers a simple unified JavaScript library called CartoDB.js that lets you interact with the CartoDB service. This library allows you to connect to your stored visualizations, create new visualizations, add custom interaction, and access or query your raw data from a web browser; meaning, your applications just got a whole lot more powerful with a lot less code. When you add CartoDB.js to your websites you get some great new tools to make maps or power your content with data. Let’s take a look. ## Getting started -The simplest way to use a visualization created in CartoDB on an external site is at follows... +The simplest way to use a visualization created in CartoDB on an external site is as follows:
Create a simple visualization
```html - + ... @@ -19,7 +19,7 @@ The simplest way to use a visualization created in CartoDB on an external site i ... - + ``` -With a similar source code you can create a visualization like this one: - -
-
- [Grab the complete example source code](https://github.com/CartoDB/cartodb.js/blob/develop/examples/easy.html) ## Using the library -CartoDB.js can be used when you want to embed and use a visualization you have designed using CartoDB user interface, or to create visualizations from scratch dynamically using your data. If you want to create new maps on your webpage, jump to “using CartoDB visualizations in your webpage”. If you already have maps on your webpage and want to add CartoDB visualizations to them, read “Add CartoDB layer to an existing map”. +CartoDB.js can be used when you want to embed and use a visualization you have designed using CartoDB's user interface, or to dynamically create visualizations from scratch using your data. If you want to create new maps on your webpage, jump to [Creating a visualization from scratch](#creating-a-visualization-from-scratch). If you already have maps on your webpage and want to add CartoDB visualizations to them, read [Adding CartoDB layers to an existing map](#adding-cartodb-layers-to-an-existing-map). -You can also use CartoDB API to create visualization without having to define them using the UI. This can be useful when the visualizations react to user interactions. To read more about it jump to, create [create visualizations at runtime](#creating-visualizations-at-runtime). +You can also use the CartoDB API to create visualizations programmatically. This can be useful when the visualizations react to user interactions. To read more about it jump to [Creating visualizations at runtime](#creating-visualizations-at-runtime). -We’ve also made it easier than ever for you to build maps using the mapping library of your choice. Whether you are using Leaflet or something else, our CartoDB.js code remains the same. This makes our API documentation simple and straightforward. It also makes it easy for you to remember and keep consistent if you development or maintain multiple maps online. +We’ve also made it easier than ever for you to build maps using the mapping library of your choice. Whether you are using Leaflet or something else, our CartoDB.js code remains the same. This makes our API documentation simple and straightforward. It also makes it easy for you to remember and be consistent if you develop or maintain multiple maps online. To start using CartoDB.js just paste this piece of code within the HEAD tags of your HTML:
Linking cartodb.js on your html file
```html - - + + ``` -### Create a visualization from scratch +### Creating a visualization from scratch The easiest way to quickly get a CartoDB map onto your webpage. Use this when there is no map in your application and you want to add the visualization to hack over it. With this method, CartoDB.js handles all the details of loading a map interface, basemap, and your CartoDB visualization. -You can start by giving cartodb.js the DIV ID from your HTML where you want to place your map, and the viz.json URL of your visualization, which you can get from the share window. +You can start by giving cartodb.js the DIV ID from your HTML where you want to place your map, and the viz.json URL of your visualization, which you can get from the share window.
Simplest way to add your map to a webpage ever!
```javascript cartodb.createVis('map', 'http://documentation.cartodb.com/api/v2/viz/2b13c956-e7c1-11e2-806b-5404a6a683d5/viz.json'); ``` -That’s it! No need to create the map instance, insert controls, or load layers, it handles it all for you. If you want to modify the result after instantiating your map with this method, take a look at the CartoDB.js API [available methods](#api-methods). For example, you can also use the returned layer to build more functionality (show/hide, click, hover, custom infowindows): +That’s it! No need to create the map instance, insert controls, or load layers. CartoDB.js takes care of this for you. If you want to modify the result after instantiating your map with this method, take a look at the CartoDB.js API [available methods](#api-methods). For example, you can also use the returned layer to build more functionality (show/hide, click, hover, custom infowindows):
Simplest way to add your map to a webpage ever!
```javascript @@ -79,7 +74,7 @@ cartodb.createVis('map', 'http://documentation.cartodb.com/api/v2/viz/2b13c956-e // when setInteraction is disabled featureOver is triggered layers[1].setInteraction(true); layers[1].on('featureOver', function(e, latlng, pos, data, layerNumber) { - cartodb.log.log(e, latlng, pos, data, layerNumber); + console.log(e, latlng, pos, data, layerNumber); }); // you can get the native map to work with it @@ -91,7 +86,7 @@ cartodb.createVis('map', 'http://documentation.cartodb.com/api/v2/viz/2b13c956-e }); ``` -### Adding cartodb layers to an existing map +### Adding CartoDB layers to an existing map In case you already have a map instantiated on your page, you can simply use the [createLayer](#cartodbcreatelayermap-layersource--options--callback) method to add new CartoDB layers to it. This is particullary useful when you have more things on your map apart from CartoDB layers or you have an application where you want to integrate CartoDB layers. @@ -118,13 +113,13 @@ Below, you have an example using a previously instatiated Leaflet map. ``` -[Grab the complete example source code](https://github.com/CartoDB/cartodb.js/blob/develop/examples/easy.html) +[Grab the complete example source code](https://github.com/CartoDB/cartodb.js/blob/develop/examples/leaflet.html) -### Creating visualizations at runtime +### Creating visualizations at runtime All CartoDB services are available through the API, which basically means that you can create a new visualization without doing it before through the CartoDB UI. This is particularly useful when you are modifying the visualization depending on user interactions that change the SQL to get the data or CartoCSS to style it. Although this method requires more programming skills, it provides all the flexibility you might need to create more dynamic visualizations. -When you create a visualization using the CartoDB website, you automatically get a viz.json URL defining it. When you want to create the visualization via JavaScript you don't always have a viz.json, so you will need to pass all the required parameters to the library so that it can create the visualization at runtime and display it on your map. It is pretty simple. +When you create a visualization using the CartoDB website, you automatically get a viz.json URL that defines it. When you want to create the visualization via JavaScript, you don't always have a viz.json. You will need to pass all the required parameters to the library so that it can create the visualization at runtime and display it on your map. It is pretty simple.
Creating visualizations at runtime
```javascript @@ -151,12 +146,12 @@ cartodb.createLayer(map, { }); ``` -Want further information? [Check out the complete API method list](#api-methods). +Want further information? [Check out the complete list of API methods](#api-methods). ## Usage examples -If you want to start playing with the library, the best way to do it might be to take a look to some of the examples below: +The best way to start learning about the library is by taking a look at some of the examples below: + An easy example using the library - ([view live](http://cartodb.github.com/cartodb.js/examples/easy.html) / [source code](https://github.com/CartoDB/cartodb.js/blob/develop/examples/easy.html)). + Leaflet integration - ([view live](http://cartodb.github.com/cartodb.js/examples/leaflet.html) / [source code](https://github.com/CartoDB/cartodb.js/blob/develop/examples/leaflet.html)). @@ -167,7 +162,7 @@ If you want to start playing with the library, the best way to do it might be to ## API methods -The documentation below reflects CartoDB.js for the v3 library versions. For major changes in the library we will update the documentation here. This documentation is meant to help developers find specific methods for using the CartoDB.js library. +The documentation below refers to CartoDB.js v3. For major changes in the library we will update the documentation here. This documentation is meant to help developers find specific methods from the CartoDB.js library. ### Visualization @@ -192,7 +187,7 @@ cartodb.createVis('map', url) - **shareable**: add facebook and twitter share buttons. - **title**: adds a header with the title of the visualization. - **description**: adds description to the header (as you set in the UI). - - **searchControl**: adds a search control (default: false). + - **search**: adds a search control (default: true). - **zoomControl**: adds zoom control (default: true). - **loaderControl**: adds loading control (default: true). - **center_lat**: latitude where the map is initializated. @@ -203,21 +198,22 @@ cartodb.createVis('map', url) - **time_slider**: show time slider with torque layers (enabled by default) - **layer_selector**: show layer selector (default: false) - **legends**: if it's true legends are shown in the map. - - **https**: if true forces tiles to be fetched using https. If false it uses the predefined method + - **https**: if true, it makes sure that basemaps are converted to https when possible. If explicitly false, converts https maps to http when possible. If undefined, the basemap template is left as declared at `urlTemplate` in the viz.json. - **scrollwheel**: enable/disable the ability of zooming using scrollwheel (default enabled) - **fullscreen**: if true adds a button to toggle the map fullscreen - **mobile_layout**: if true enables a custom layout for mobile devices (default: false) - **force_mobile**: forces enabling/disabling the mobile layout (it has priority over mobile_layout argument) - - **gmaps_base_type**: Use Google Maps as map provider whatever is the one specified in the viz.json". Available types: 'roadmap', 'gray_roadmap', 'dark_roadmap', 'hybrid', 'satellite', 'terrain'. + - **gmaps_base_type**: Use Google Maps as map provider whatever is the one specified in the viz.json". Available types: 'roadmap', 'gray_roadmap', 'dark_roadmap', 'hybrid', 'satellite', 'terrain'. - **gmaps_style**: Google Maps styled maps. See [documentation](https://developers.google.com/maps/documentation/javascript/styling). + - **no_cdn**: true to disable CDN when fetching tiles - **callback(vis,layers)**: if a function is specified, it is called once the visualization is created, passing vis and layers as arguments ##### Returns -Promise object. You can listen for the following events: +A promise object. You can listen for the following events: - **done**: triggered when the visualization is created, `vis` is passed as the first argument and `layers` is passed as the second argument. Each layer type has different options, see layers section. - **error**: triggered when the layer couldn't be created. The error string is the first argument. ++ **done**: triggered when the visualization is created, `vis` is passed as the first argument and `layers` is passed as the second argument. Each layer type has different options, see layers section. ++ **error**: triggered when the layer couldn't be created. The error string is the first argument. ### cartodb.Vis @@ -232,18 +228,18 @@ Adds an overlay to the map that can be either a zoom control, a tooltip or an in ##### Arguments - **options** - - **layer** layer from the visualization where apply the overlay (optional) + - **layer** layer from the visualization where the overlay should be applied (optional) - **type** zoom / tooltip / infobox -Extra options are available depending on the UI component selected before +If no layer is provided, the overlay will be added to the first layer of the visualization. Extra options are available based on the specific UI component. ##### Returns -An overlay object, see [vis.Overlays](#visoverlays) +An overlay object, see [vis.Overlays](#visoverlays). #### vis.getOverlay(_type_) -Return the first overlay with the specified **type**. +Returns the first overlay with the specified **type**.
vis.getOverlay
```javascript @@ -253,7 +249,7 @@ zoom.clean() // remove it from the screen #### vis.getOverlays() -Returns a list of overlays currently on the screen (see overlays description). +Returns a list of the overlays that are currently on the screen (see overlays description). #### vis.getNativeMap() @@ -263,16 +259,16 @@ Returns the native map object being used (e.g. a L.Map object for Leaflet). An overlay is a control shown on top of the map. -Overlay objects are always created using method **addOverlay** of cartodb.Vis object. +Overlay objects are always created using the **addOverlay** method of a cartodb.Vis object. -An overlay is internally a **Backbone.View** so if you know how backbone works you can use it. If you want to use plain DOM objects you can access to **overlay.el** (**overlay.$el** for jQuery object). +An overlay is internally a [**Backbone.View**](http://backbonejs.org/#View) so if you know how Backbone works you can use it. If you want to use plain DOM objects you can access **overlay.el** (**overlay.$el** for jQuery object). -#### vis.addInfoWindow(_map, layer, fields [, options]_) +#### vis.addInfowindow(_map, layer, fields [, options]_) -Adds an infowindow to the map controlled by layer events. It enables interaction and overrides the layer interacivity. +Adds an infowindow to the map controlled by layer events. It enables interaction and overrides the layer interactivity. ##### Arguments - + - **map**: native map object or leaflet - **layer**: cartodb layer (or sublayer) - **fields**: array of column names @@ -301,36 +297,34 @@ cartodb.createLayer(map, 'http://myserver.com/layerdata.json') cartodb.createLayer(map, { layermetadata }) ``` -Layer metadata is always in the form: `{ type: 'LAYER_TYPE_NAME', options: {...} }` - -See [cartodb.CartoDBLayer](#cartodbcartodblayer) too see an example. - -- **options**: +- **options**: - **https**: force https - **refreshTime**: if is set, the layer is refreshed each refreshTime milliseconds. - **infowindow**: set to false if you want to disable the infowindow (enabled by default). - **tooltip**: set to false if you want to disable the tooltip (enabled by default). - **legends**: if it's true legends are shown in the map. - **time_slider**: show time slider with torque layers (enabled by default) - - **layerIndex**: when the visualization contains more than one layer this index allow to select + - **layerIndex**: when the visualization contains more than one layer this index allows you to select what layer is created. Take into account that `layerIndex == 0` is the base layer and that all the tiled layers (non animated ones) are merged into a single one. The default value for this option is 1 (usually tiled layers). + - **filter**: a string or array of strings to specify the type(s) of sublayers that will be rendered (eg: `['http', 'mapnik']`). All non-torque layers (http and mapnik) will be rendered if this option is not present. -- **callback(_layer_)**: if a function is specified is called when the layer is created passing it as argument. +- **callback(_layer_)**: if a function is specified, it will be invoked after the layer has been created. The layer will be passed as an argument. ##### Returns -Promise object. You can listen for the following events: +A promise object. You can listen for the following events: + **done**: triggered when the layer is created, the layer is passed as first argument. Each layer type has different options, see layers section. + **error**: triggered when the layer couldn't be created. The error string is the first argument. -You can call to `addTo(map[, position])` in the promise so when the layer is ready it will be added to the map. +You can call to `addTo(map[, position])` in the promise so when the layer is ready it will be added to the map. ##### Example -
cartodb.createLayer
+
cartodb.createLayer using a url
+ ```javascript var map; var mapOptions = { @@ -354,21 +348,116 @@ cartodb.createLayer(map, 'http://documentation.cartodb.com/api/v2/viz/2b13c956-e }); ``` +Layer metadata must take one of the following forms: + +#### Standard Layer Source Object (`type: 'cartodb'`) + +Used for most maps with tables that are set to public or public with link. + +```javascript +{ + user_name: 'your_user_name', // Required + type: 'cartodb', // Required + sublayers: [{ + sql: "SELECT * FROM table_name", // Required + cartocss: '#table_name {marker-fill: #F0F0F0;}', // Required + interactivity: "column1, column2, ...", // Optional + }, + { + sql: "SELECT * FROM table_name", // Required + cartocss: '#table_name {marker-fill: #F0F0F0;}', // Required + interactivity: "column1, column2, ...", // Optional + }, + ... + ] +} +``` + +#### Torque Layer Source Object (`type: 'torque'`) + +Used for [Torque maps](https://github.com/CartoDB/torque). Note that it does not allow sublayers. + +```javascript +{ + type: 'torque', // Required + order: 1, // Optional + options: { + query: "SQL statement", // Required if table_name is not given + table_name: "table_name", // Required if query is not given + user_name: "your_user_name", // Required + cartocss: "CartoCSS styles" // Required + } +} +``` + +#### Named Maps Layer Source Object (`type: 'namedmap'`) + +Used for making public maps with private data. See [Named Maps](http://docs.cartodb.com/cartodb-platform/maps-api.html#named-maps-1) for more information. + + +```javascript +{ + user_name: 'your_user_name', // Required + type: 'namedmap', // Required + named_map: { + name: 'name_of_map', // Required + // Optional + layers: [{ + layer_name: "sublayer0", // Optional + interactivity: "column1, column2, ..." // Optional + }, + { + layer_name: "sublayer1", + interactivity: "column1, column2, ..." + }, + ... + ], + // Optional + params: { + color: "hex_value", + num: 2 + } + } +} +``` + +##### Example + +
cartodb.createLayer combining multiple types of layers and setting a filter
+ +```javascript +cartodb.createLayer(map, { + user_name: 'examples', + type: 'cartodb', + sublayers: [ + { + type: "http", + urlTemplate: "http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png", + subdomains: [ "a", "b", "c" ] + }, + { + sql: 'select * from country_boundaries', + cartocss: '#layer { polygon-fill: #F00; polygon-opacity: 0.3; line-color: #F00; }' + }, + ], +}, { filter: ['http', 'mapnik'] }) +``` + ### cartodb.CartoDBLayer CartoDBLayer allows you to manage tiled layers from CartoDB. It manages the sublayers. #### layer.clear() -Should be called after removing the layer from the map. +Clears the layer. It should be invoked after removing the layer from the map. #### layer.hide() -Hides the cartodb layer from the map. +Hides the layer from the map. #### layer.show() -Show the cartodb layer in the map if it was previously added. +Shows the layer in the map if it was previously added. #### layer.toggle() @@ -376,7 +465,7 @@ Toggles the visibility of the layer and returns a boolean that indicates the new #### layer.setOpacity(_opacity_) -Change the opacity of the layer. +Changes the opacity of the layer. ##### Arguments @@ -384,7 +473,7 @@ Change the opacity of the layer. #### layer.getSubLayer(_layerIndex_) -Get a previously created sublayer. And exception is raised if not exists +Gets a previously created sublayer. And exception is raised if no sublayer exists. ##### Arguments @@ -392,7 +481,7 @@ Get a previously created sublayer. And exception is raised if not exists ##### Returns -SubLayer object +A SubLayer object. ##### Example @@ -407,11 +496,11 @@ sublayer.setSQL('SELECT * FROM table_name limit 10'); #### layer.getSubLayerCount() -Get the number of sublayers in layer. +Gets the number of sublayers in layer. ##### Returns -Number of sublayers. +The number of sublayers. ##### Example @@ -426,7 +515,7 @@ for (var i = 0; i < num_sublayers; i++) { #### layer.createSubLayer(_layerDefinition_) -Adds a new data to the current layer. With this method data from multiple tables can be easily visualized. New in V3. +Adds a new data to the current layer. With this method, data from multiple tables can be easily visualized. New in V3. ##### Arguments @@ -441,14 +530,14 @@ Adds a new data to the current layer. With this method data from multiple tables } ``` -sql and cartocss are mandatory, an exception is raised if any of them are not present. If the interactivity is not set, there is no interactivity enabled for that layer (better performance). SQL and CartoCSS syntax should be correct, see Postgres and CartoCSS reference. There are some restrictions in the SQL queries: +`sql` and `cartocss` are mandatory. An exception is raised if either of them are not present. If the interactivity is not set, there is no interactivity enabled for that layer (better performance). SQL and CartoCSS syntax should be correct. Look at the documentation for [PostgreSQL](http://www.postgresql.org/docs/9.3/interactive/sql-syntax.html) and [CartoCSS](https://github.com/mapbox/carto/blob/master/docs/latest.md) for more information. There are some restrictions in the SQL queries: -- must not write. INSERT, DELETE, UPDATE, ALTER and so on are not allowed (the query will fail) -- must not contain trialing semicolon +- Must not write. INSERT, DELETE, UPDATE, ALTER and so on are not allowed (the query will fail) +- Must not contain trialing semicolon ##### Returns -SubLayer object +A SubLayer object. ##### Example @@ -465,31 +554,30 @@ cartodb.createLayer(map, 'http://examples.cartodb.com/api/v2/viz/european_countr #### layer.invalidate() -Refresh the data. If the data has been changed in CartoDB server it is displayed. If not nothing happens. Every time a parameter is changed in a sublayer the layer is refreshed so this method don't need to be called manually. New in V3. +Refreshes the data. If the data has been changed in the CartoDB server those changes will be displayed. Nothing happens otherwise. Every time a parameter is changed in a sublayer, the layer is refreshed automatically, so there's no need to call this method manually. New in V3. #### layer.setAuthToken(_auth_token_) -Sets the auth token to create the layer. Only available for private visualizations. An exception is -raised if the layer is not being loaded with HTTPS. +Sets the auth token that will be used to create the layer. Only available for private visualizations. An exception is +raised if the layer is not being loaded with HTTPS. See [Named Maps](http://docs.cartodb.com/cartodb-platform/maps-api.html#named-maps-1) for more information. ##### Returns -the layer itself +The layer itself. ##### Arguments -- auth_token: string +- **auth_token:** string #### layer.setParams(_key, value_) -Using named maps this function changes the layer confuguration. This could be called in different -ways: +Sets the configuration of a layer when using [named maps](http://docs.cartodb.com/cartodb-platform/maps-api.html#named-maps-1). It can be invoked in different ways: -
layer.createSubLayer
+
layer.setParams
```javascript layer.setParams('test', 10); // sets test = 10 layer.setParams('test', null); // unset test -layer.setParams({'test': 1, 'color': '#F00'}); // unset test +layer.setParams({'test': 1, 'color': '#F00'}); // set more than one parameter at once ``` ##### Arguments @@ -499,17 +587,17 @@ layer.setParams({'test': 1, 'color': '#F00'}); // unset test ##### Returns -the layer itself +The layer itself. ### cartodb.CartoDBLayer.SubLayer #### sublayer.set(_layerDefinition_) -Sets sublayer parameters. Useful when more than one parameter need to be changed. See setSQL and setCartoCSS +Sets sublayer parameters. Useful when more than one parameter needs to be changed. ##### Arguments -- **layerDefinition**: an object with the sql and cartocss that defines the data, should be like +- **layerDefinition**: an object with the sql and cartocss that defines the data, like:
layerDefinition
```javascript @@ -520,9 +608,9 @@ Sets sublayer parameters. Useful when more than one parameter need to be changed } ``` -##### Return +##### Returns -self object +The layer itself. ##### Example @@ -541,7 +629,29 @@ Gets the attribute for the sublayer, for example 'sql', 'cartocss'. ##### Returns -The requested attribute of undefined if it's not present. +The requested attribute or undefined if it's not present. + +#### sublayer.remove() + +Removes the sublayer. An exception will be thrown if a method is called and the layer has been removed. + +#### sublayer.show() + +Shows a previously hidden sublayer. The layer is refreshed after calling this function. + +#### sublayer.hide() + +Removes the sublayer from the layer temporarily. The layer is refreshed after calling this function. + +#### sublayer.toggle() + +Toggles the visibility of the sublayer and returns a boolean that indicates the new status (true if the sublayer is visible, false if it is hidden) + +#### sublayer.isVisible() + +It returns `true` if the sublayer is visible. + +### cartodb.CartoDBLayer.CartoDBSubLayer #### sublayer.getSQL() @@ -559,45 +669,31 @@ Shortcut for `set({'sql': 'SELECT * FROM table_name'})` Shortcut for `set({'cartocss': '#layer {...}' })` -#### sublayer.remove - -Remove the sublayer. If a method is called after removing it an exception is thrown. - -#### sublayer.setInteraction(_true_) - -Sets the interaction of your layer to true (enabled) or false (disabled). When is disabled **featureOver**, **featureClick**, **featureOut**, **mouseover** and **mouseout** are **not** triggered. - #### sublayer.setInteractivity('cartodb_id, name, ...') Shortcut for `set({'interactivity': 'cartodb_id, name, ...' })` Sets the columns which data will be available via the interaction with the sublayer. -##### Arguments - -+ **enable**: true if the interaction needs to be enabled. - -#### sublayer.show - -Show a previously hidden sublayer. The layer is refreshed after calling this function. - -#### sublayer.hide +#### sublayer.setInteraction(_true_) -Remove temporally the sublayer from the layer. The layer is refreshed after calling this function. +Enables (true) or disables (false) the interaction of the layer. When disabled, **featureOver**, **featureClick**, **featureOut**, **mouseover** and **mouseout** are **not** triggered. -#### sublayer.toggle() +##### Arguments -Toggles the visibility of the sublayer and returns a boolean that indicates the new status (true if the sublayer is shown, false if it is hidden) ++ **enable**: true if the interaction needs to be enabled. #### sublayer.infowindow -**sublayer.infowindow** is a Backbone model where we modify the parameters of the infowindow +**sublayer.infowindow** is a Backbone model where we modify the parameters of the infowindow. -##### Arguments +##### Attributes -- **template**: Set the custom infowindow template defined on the html. You can write simple html or use [Mustache templates](http://mustache.github.com/). -- **width**: Set the width of the infowindow (value must be a number). -- **maxHeight**: Set the max height of the scrolled content (value must be a number). +- **template**: Custom HTML template for the infowindow. You can write simple HTML or use [Mustache templates](http://mustache.github.com/). +- **sanitizeTemplate**: By default all templates are sanitized from unsafe tags/attrs (e.g. ` +``` + +#### Result +```html + +``` + +#### cartodb.Image(_layerSource_[, options]) + +##### Arguments + +- **layerSource**: can be either a viz.json object or a [layer source object](http://docs.cartodb.com/cartodb-platform/cartodb-js.html#standard-layer-source-object-type-cartodb) + +##### Options + +Options take the form of a JavaScript object. + +- **options**: + - **basemap**: change the basemap specified in the layer definition. Type: Object defining base map properties (see example below). + - **no_cdn**: Disable CDN usage. Type: Boolean. Default: `false` (use CDN) + - **override_bbox**: Override default of using the bounding box of the visualization. This is needed to use `Image.center` and `Image.zoom`. Type: Boolean. Default: `false` (use bounding box) + +```javascript + +``` + +##### Returns +An _Image_ object + +### cartodb.Image + +#### Image.size(_width_,_height_) + +Sets the size of the image. + +##### Arguments + +- **width**: the width of the resulting image in pixels +- **height**: the height of the resulting image in pixels + +##### Returns +An _Image_ object + +#### Image.center(_latLng_) + +Sets the center of the map. + +##### Arguments + +- **latLng**: an array of the latitude and longitude of the center of the map. Example: `[40.4378271,-3.6795367]` + +##### Returns + +An _Image_ object + +#### Image.zoom(zoomLevel) + +Sets the zoom level of the static map. Must be used with the option `override_bbox: true` if not using `Image.center` or `Image.bbox`. + +##### Arguments + +- **zoomLevel**: the zoom of the resulting static map. `zoomLevel` must be an integer in the range [0,24]. + +##### Returns + +An _Image_ object + +#### Image.bbox(_boundingBox_) + +If you set `bbox`, `center` and `zoom` will be overridden. + +##### Arguments + +- **boundingBox**: an array of coordinates making up the bounding box for your map. `boundingBox` takes the form: `[sw_lat, sw_lon, ne_lat, ne_lon]`. + +##### Returns + +An _Image_ object + +#### Image.into(HTMLImageElement) + +Inserts the image into the HTML DOM element specified. + +##### Arguments + +- **HTMLImageElement**: the DOM element where your image is to be located. + +##### Returns + +An _Image_ object + +
Image.into
+```javascript +cartodb.Image(vizjson_url).into(document.getElementById('map_preview')) +``` -In case you are not using Leaflet, or you want to implement your own layer object, cartodb provide a way to get the tiles url for a layer definition. +#### Image.write(_attributes_) -If you want to use this functionallity you only need to load cartodb.core.js from our cdn, no css is needed: +Adds an `img` tag in the same place script is executed. It's possible to specify a class name (`class`) and/or an id attribute (`id`) for the resulting image: + +
Image.write
+```javascript + +``` + +##### Arguments + +- **attributes**: + + **class**: the DOM class applied to the resulting `img` tag + + **id**: the DOM id applied to the resulting `img` tag + + **src**: path to a temporary image that acts as a placeholder while the static map is retrieved + +##### Returns + +An _Image_ object + + +#### Image.getURL(_callback(err, url)_) + +Gets the URL for the image requested. + +
Image.getURL
+```javascript + +``` + +##### Callback Arguments + +- **err**: error associated with the image request, if any +- **url**: URL of the generated image + +##### Returns + +An _Image_ object + +#### Image.format(_format_) + +Gets the URL for the image requested. + +##### Argument + +- **format**: image format of resulting image. One of `png` (default) or `jpg` (which have a quality of 85 dpi) + +##### Returns + +An _Image_ object + +## Core API functionality + +In case you are not using Leaflet, or you want to implement your own layer object, CartoDB provides a way to get the tiles url for a layer definition. + +If you want to use this functionality, you only need to load cartodb.core.js from our cdn. No CSS is needed:
Core API functionallity
```html - + ``` -An example using this funcionallity can be found in modestmaps example: [view live](http://cartodb.github.com/cartodb.js/examples/modestmaps.html) / [source code](https://github.com/CartoDB/cartodb.js/blob/develop/examples/modestmaps.html). +An example using this funcionality can be found in a ModestMaps example: [view live](http://cartodb.github.com/cartodb.js/examples/modestmaps.html) / [source code](https://github.com/CartoDB/cartodb.js/blob/develop/examples/modestmaps.html). -Notice that cartodb.SQL is also included in that javascript file +Notice that cartodb.SQL is also included in that JavaScript file ### cartodb.Tiles #### cartodb.Tiles.getTiles(_layerOptions, callback_) -Fetch the tile template for the layerdefinition. +Fetch the tile template for the layer definition. ##### Arguments -+ **layerOptions**: the data that defines the layer, it should contain at least user_name and sublayer list. There are the available options: ++ **layerOptions**: the data that defines the layer. It should contain at least user_name and sublayer list. These are the available options:
cartodb.Tiles.getTiles
```javascript -user_name: 'mycartodbuser', -sublayers: [{ - sql: "SELECT * FROM table_name"; - cartocss: '#layer { marker-fill: #F0F0F0; }' -}], -tiler_protocol: 'https', // not required -tiler_host: 'cartodb.com', // not required -tiler_port: 80 // not required +{ + user_name: 'mycartodbuser', + sublayers: [{ + sql: "SELECT * FROM table_name"; + cartocss: '#layer { marker-fill: #F0F0F0; }' + }], + maps_api_template: 'https://{user}.cartodb.com' // Optional +} ``` -+ **callback(tilesUrl, error)**: a function that recieves the tiles templates. In case of an error the first param is null and second one is an object with errors attribute witch is a list of errors. The tilesUrl object contains url template for tiles and for interactivity grids: ++ **callback(tilesUrl, error)**: a function that recieves the tiles templates. In case of an error, the first param is null and the second one will be an object with an errors attribute that contains the list of errors. The tilesUrl object contains url template for tiles and interactivity grids:
cartodb.Tiles.getTiles
```javascript @@ -952,9 +1277,9 @@ tiler_port: 80 // not required } ``` -##### Example +##### Example -In this example a layer is created with one sublayer with renders all the content from table. +In this example, a layer with one sublayer is created. The sublayer renders all the content from a table.
cartodb.Tiles.getTiles
```javascript @@ -977,31 +1302,48 @@ cartodb.Tiles.getTiles(layerData, function(tiles, err) { ## Versions -Keep in mind the version of CartoDB.js you are using for development. For any live code, we recommend you link directly to the tested CartoDB.js version from your development. You can find the version at anytime as follows: +Keep in mind the version of CartoDB.js you are using for development. For any live code, we recommend you to link directly to the tested CartoDB.js version from your development environment. You can check the version of CartoDB.js as follows: ### cartodb.VERSION -Contains the library version, should be something like `3.0.1`. +Returns the version of the library. It should be something like `3.0.1`. ## Other important stuff -The CartoDB.js has many great features for you to use in your applications. Let’s take a look at the most important for your application development. +CartoDB.js has many great features for you to use in your applications. Let’s take a look at some of the most important ones: ### Viz JSON support The Viz.JSON document tells CartoDB.js all the information about your map, including the style you want to use for your data and the filters you want to apply with SQL. The Viz JSON file is served with each map you create in your CartoDB account. -Although the Viz JSON file stores all your map settings, all the values are also easy to customize with CartoDB.js if you want to do something completely different than what you designed in your console. Loading the Viz JSON is as simple as: +Although the Viz JSON file stores all your map settings, all these settings can be easily customized with CartoDB.js. For example, if you want to do something completely different than what you initially designed it for. Loading the Viz JSON is as simple as:
Viz JSON support
```javascript cartodb.createVis('map', 'http://examples.cartodb.com/api/v2/viz/ne_10m_populated_p_1/viz.json') ``` +### How to set a different host than cartodb.com +cartodb.js by default send all requests to cartodb.com domain but it you are running your own +instance of cartodb you can change the urls. + +The way to do it is using ``sql_api_template`` and ``maps_api_template`` in ``options`` paramater +for any ``cartodb`` function call. + +The format of those templates is like: + +```javascript +sql_api_template: 'https://{user}.test.com' +``` + +cartodb.js will replace ``{user}``. + +Notice you don't need to set the path to the endpoint, cartodb.js sets it + ### Bounds wrapper -We have added easy method to get the bounding box for any dataset or filtered query using the CartoDB.js library. The **getBounds** function can be useful for guiding users to the right location on a map or for loading only the right data at the right time based on user actions. +We have added an easy method to get the bounding box for any dataset or filtered query using the CartoDB.js library. The **getBounds** function can be useful for guiding users to the right location on a map or for loading only the right data at the right time based on user actions.
Bounds wrapper
```javascript @@ -1014,11 +1356,11 @@ sql.getBounds('SELECT * FROM table_name').done(function(bounds) { ### Event listener support -The CartoDB.js is highly asynchronous, meaning your application can get on with what it needs to do while the library efficiently does what you request in the background. This is useful for loading maps or getting query results. At the same time, we have made it very simple to add listeners and callbacks to the async portions of the library. +CartoDB.js is highly asynchronous. Your application can get on with what it needs to do while the library efficiently does what you request in the background. This is useful for loading maps or getting query results. At the same time, we have made it very simple to add listeners and callbacks to the async portions of the library. #### Loading events -The **createLayer** and **createVis** functions returns two important events for you to take advantage of: the first is **done**, which will let your code know that the library has successfully read the information from the Viz JSON and loaded the layer you requested. The second is ‘error’, which lets you know something did not go as expected when loading a requested layer: +The **createLayer** and **createVis** functions trigger two important events for you to take advantage of. The first one is **done**, which will let your code know that the library has successfully read the information from the Viz JSON and loaded the layer you requested. The second is **error**, which lets you know that something did not go as expected when trying to load the requested layer:
Loading events
```javascript @@ -1042,7 +1384,7 @@ layer.on('featureClick', function(e, latlng, pos, data, layer) { }); ``` -The second event is the **featureOver** event, which lets you listen for when the user’s mouse is over a feature. Be careful, as these functions can get costly if you have a lot of features on a map. +The second event is the **featureOver** event, which lets you listen for mouse hovers on any feature. Be careful, as these functions can get costly if you have a lot of features on a map.
featureOver
```javascript @@ -1051,7 +1393,7 @@ layer.on('featureOver', function(e, latlng, pos, data, layer) { }); ``` -Similarly, there is the **featureOut** event. This is best used if you do things like highlighting polygons on mouseover and need a way to know when to remove the highlighting after the mouse has left. +Finally, there is the **featureOut** event. This is best used if you do things like highlighting polygons on mouseover and need a way to know when to remove the highlighting after the mouse has left.
featureOut
```javascript @@ -1062,31 +1404,31 @@ layer.on('featureOut', function(e, latlng, pos, data, layer) { #### Leaflet integration -If you want to use [Leaflet](http://leafletjs.com) it gets even easier, CartoDB.js handles loading all the necessary libraries for you! just include CartoDB.js and CartoDB.css in the HEAD of your website and you are ready to go! The CartoDB.css document isn’t mandatory, however if you are making a map and are not familiar with writing your own CSS for the various needed elements, it can greatly help to jumpstart the process. Adding it is as simple as adding the main JavaScript library: +If you want to use [Leaflet](http://leafletjs.com) it gets even easier. CartoDB.js handles loading all the necessary libraries for you! Just include CartoDB.js and CartoDB.css in the HEAD of your website and you are ready to go! The CartoDB.css document isn’t mandatory. However, if you are making a map and are not familiar with writing your own CSS for the various needed elements, it can help you jumpstart the process. Using Leaflet is as simple as adding the main JavaScript library:
Leaflet integration
```html - - + + ``` #### HTTPS support -You can use all the functionality of cartodb.js with HTTPs support. Be sure to add use https when importing both the JS library and the CSS file. Next, you will specify HTTPs for your Viz.JSON URL and as a parameter when you initialize your visualizaiton. +You can use all the functionality of CartoDB.js with HTTPs support. Be sure to use https when importing both the JS library and the CSS file. You will also need to use HTTPs in the Viz.JSON URL you pass to **createVis**.
HTTPS support
```html
- - + + -``` +We are committed to making sure your website works as intended no matter what changes in the future. We may find more efficient or more useful features to add to the library as time progresses. But we never want to break things you have already developed. For this reason, we make versioned CartoDB.js libraries available to you. The way they function will never unexpectedly change on you. -Anytime you wish to push a stable version of your site to the web though, you can find the version of CartoDB.js you are using by looking at the first line of the library, here: +We recommend that you always develop against the most recent version of CartoDB.js: ```html - + ``` -Or, by running the following in your code: +Anytime you wish to push a stable version of your site to the web though, you can find the version of CartoDB.js you are using by looking at the first line of the library or running the following in your code: ```javascript alert(cartodb.VERSION) ``` -Now, that you have your CartoDB.js version, you can point your site at that release. If the current version of CartoDB.js is `3.9.06, the URL would be: +Once you know which version of CartoDB.js you're using, you can point your site to that release. If the current version of CartoDB.js is 3.15.2, the URL would be: ```html - + ``` You can do the same for the CSS documents we provide: ```html - + ``` diff --git a/doc/raster_howto.md b/doc/raster_howto.md new file mode 100644 index 0000000000..1d78de1b26 --- /dev/null +++ b/doc/raster_howto.md @@ -0,0 +1,83 @@ + +# how to use raster with cartodb.js + +The way to add a raster layer to your map with cartodb.js is similar to add a regular cartodb layer, +as everything in CartoDB it uses ``SQL`` + ``CartoCSS`` + + +## introduction to raster + +In [mapschool](http://mapschool.io/) you have a very good introduction to the basis of raster. Here +we are going to explain how raster works in CartoDB. + +Raster usually takes a lot of space in the database and therefore render tiles is a heavy task. +Luckily CartoDB solved this for you, when you import a raster using the editor or the [Import +API](http://docs.cartodb.com/cartodb-platform/import-api.html) it generates a series of overviews, +that's it, a bunch of tables with preprocessed information in order to speedup rendering. + +You don't need to care about that but there are special cases you should be aware of when you create +a raster based visualization + + +## creating a layer + +As always a layer is created using ``createLayer`` method: + +``` +cartodb.createLayer(map, { + user_name: 'doc', + type: 'cartodb', + sublayers: [{ + sql: 'select * from pop', + cartocss: '#pop { raster-opacity: 1.0; }', + raster: true, + }] +}) +.addTo(map) +``` + +The only special thing here is the ``raster`` flag, that tells Maps API that you are going to use a +raster table so all the optimizations and so on are enabled + +## working the the layer + +Change CartoCSS and so on it's the same than working with a regular layer, you can use methods like +``setCartoCSS``: + +``` +layer.getSubLayer(0).setCartoCSS('#layer {..... }'); +``` + +You can also use ``setSQL`` but if you use a query different than the identity (select * from table) +the raster optimizations are not going to work and you will get a timeout depending on the zoom +level you are working on. + +## using SQL for analysis + +You can also access raster tables using SQL API through cartodb.js, the following example gets the +average value for a raster in a radius of 100 meters with center in latlng 0, 0 + +``` + var sql = new cartodb.SQL({ user: 'doc' }); + var query = "SELECT avg((stats).mean) as m from (select st_summarystats(the_raster_webmercator, 1) as stats from pop where st_intersects(the_raster_webmercator, st_transform(st_buffer(cdb_latlon(0, 0)::geography, 100)::geometry, 3857) as foo"; + + sql.execute(q).done(function(data) { + if (data.rows && data.rows.length > 0) { + console.log("Average raster value inside the " + type + ": " + data.rows[0].m); + } +``` + +don't forget to use ``the_raster_webmercator`` column. + + + +## limitations + +- changing the SQL to something custom could avoid Maps API to use overviews and not rendering the + tiles due to timeout +- cartocss version should be 2.3.0. You usually don't need to do anything but if you are working + with specific versions take this into account +- interaction does not work for rasters + + + diff --git a/doc/vizjson_format.md b/doc/vizjson_format.md index 373f022a4c..089f2d6ab2 100644 --- a/doc/vizjson_format.md +++ b/doc/vizjson_format.md @@ -1,7 +1,7 @@ visjson ======= -this is the spec for visjson: +This is the spec for visjson: ``` { // required @@ -27,7 +27,7 @@ this is the spec for visjson: ], // optional - // visulization title + // visualization title // default: '' "title": "" @@ -146,12 +146,16 @@ this is the spec for visjson: type: 'namedmap', order: 1, options: { - type: "named-map", + type: "namedmap", tiler_domain: "cartodb.com", tiler_port: "443", tiler_protocol: "https", user_name: "javi", require_password: true/false, + cdn_url: { + http: "api.cartocdn.com", + https: "cartocdn.global.ssl.fastly.net" + }, named_map: { name: 'test', params: { @@ -160,11 +164,15 @@ this is the spec for visjson: other_var: 1 }, layers: [{ - infowindow: - legend: + infowindow: '', + legend: '', + layer_name: 'name_of_layer', + interactivity: 'column1, column2, ...', + visible: true/false }, {...} - ] + ], + stat_tag: "a5c626a0-a29f-11e4-bee0-010c4c326911" }, } }, diff --git a/examples/TheHobbitLocations/index.html b/examples/TheHobbitLocations/index.html index bc44c58582..fdce8084c4 100644 --- a/examples/TheHobbitLocations/index.html +++ b/examples/TheHobbitLocations/index.html @@ -2,7 +2,7 @@ THE HOBBIT FILMING LOCATIONS - A Cartodb.js map - + @@ -53,7 +53,7 @@

{{content.data.name_to_display}}

- + diff --git a/examples/annotations.html b/examples/annotations.html index 56078eaafa..91410e68d3 100644 --- a/examples/annotations.html +++ b/examples/annotations.html @@ -13,13 +13,13 @@ } - +
- + + + + +
+
+ +
+
+ + diff --git a/examples/bing.html b/examples/bing.html new file mode 100644 index 0000000000..dbd5d065fc --- /dev/null +++ b/examples/bing.html @@ -0,0 +1,52 @@ + + + + Bing Maps + CartoDB.js + + + + + + + +
+ + + diff --git a/examples/bounds.html b/examples/bounds.html index 0c4c571107..c92e1fae83 100644 --- a/examples/bounds.html +++ b/examples/bounds.html @@ -18,13 +18,13 @@ } - +
- + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + diff --git a/examples/cursor_interaction.html b/examples/cursor_interaction.html index b2cd756516..726813340a 100644 --- a/examples/cursor_interaction.html +++ b/examples/cursor_interaction.html @@ -16,13 +16,13 @@ background-color: #E5F5F7; } - +
- + + - + + - + \ No newline at end of file diff --git a/examples/filters-template/index.html b/examples/filters-template/index.html index c26a0f54ff..4f70c85c11 100644 --- a/examples/filters-template/index.html +++ b/examples/filters-template/index.html @@ -9,7 +9,7 @@ - +
@@ -20,7 +20,7 @@ - + \ No newline at end of file diff --git a/examples/format.html b/examples/format.html new file mode 100644 index 0000000000..d79a6d524c --- /dev/null +++ b/examples/format.html @@ -0,0 +1,41 @@ + + + + Single image example | CartoDB.js + + + + + + + + +
+
+ +
+
+ + diff --git a/examples/gmaps/gmaps_driving_directions_plus_sql_api.html b/examples/gmaps/gmaps_driving_directions_plus_sql_api.html index eb4a9bce1c..dff7cad3ba 100644 --- a/examples/gmaps/gmaps_driving_directions_plus_sql_api.html +++ b/examples/gmaps/gmaps_driving_directions_plus_sql_api.html @@ -13,7 +13,7 @@ } - +
@@ -22,7 +22,7 @@ - + - \ No newline at end of file + diff --git a/examples/gmaps/gmaps_heatmap_plus_sql_api.html b/examples/gmaps/gmaps_heatmap_plus_sql_api.html index 8bf43f066c..f5a687d2a2 100644 --- a/examples/gmaps/gmaps_heatmap_plus_sql_api.html +++ b/examples/gmaps/gmaps_heatmap_plus_sql_api.html @@ -13,7 +13,7 @@ } - +
@@ -22,7 +22,7 @@ - + - + + + + + + + + diff --git a/examples/gmaps_force_basemap.html b/examples/gmaps_force_basemap.html index e55cbce802..33870668b8 100644 --- a/examples/gmaps_force_basemap.html +++ b/examples/gmaps_force_basemap.html @@ -13,7 +13,7 @@ } - +
@@ -22,7 +22,7 @@ - + + + + + + + + \ No newline at end of file diff --git a/examples/gmaps_satellite.html b/examples/gmaps_satellite.html new file mode 100644 index 0000000000..b13bd0a372 --- /dev/null +++ b/examples/gmaps_satellite.html @@ -0,0 +1,39 @@ + + + + GMaps Satellite basemap | CartoDB.js + + + + + + + + +
+ + + + + + + + + + diff --git a/examples/gmaps_with_torque.html b/examples/gmaps_with_torque.html new file mode 100644 index 0000000000..acdb73b239 --- /dev/null +++ b/examples/gmaps_with_torque.html @@ -0,0 +1,39 @@ + + + + GMaps plus Torque | CartoDB.js + + + + + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/examples/image.html b/examples/image.html new file mode 100644 index 0000000000..f358de4ef8 --- /dev/null +++ b/examples/image.html @@ -0,0 +1,157 @@ + + + + Image loading methods example | CartoDB.js + + + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + diff --git a/examples/images/basemap.html b/examples/images/basemap.html new file mode 100644 index 0000000000..dc61a3d9ee --- /dev/null +++ b/examples/images/basemap.html @@ -0,0 +1,50 @@ + + + + Basemap | CartoDB.js + + + + + + + + + +
+
+ +
+
+ + diff --git a/examples/images/core.html b/examples/images/core.html new file mode 100644 index 0000000000..8b11c2e294 --- /dev/null +++ b/examples/images/core.html @@ -0,0 +1,152 @@ + + + + Core | CartoDB.js + + + + + + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + diff --git a/examples/images/format.html b/examples/images/format.html new file mode 100644 index 0000000000..a35cc39957 --- /dev/null +++ b/examples/images/format.html @@ -0,0 +1,41 @@ + + + + Single image example | CartoDB.js + + + + + + + + +
+
+ +
+
+ + diff --git a/examples/images/image.html b/examples/images/image.html new file mode 100644 index 0000000000..c75fd67892 --- /dev/null +++ b/examples/images/image.html @@ -0,0 +1,157 @@ + + + + Image loading methods example | CartoDB.js + + + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + diff --git a/examples/images/layer_definition.html b/examples/images/layer_definition.html new file mode 100644 index 0000000000..558b210089 --- /dev/null +++ b/examples/images/layer_definition.html @@ -0,0 +1,81 @@ + + + + Image example | CartoDB.js + + + + + + + + + +
+ + diff --git a/examples/images/plain-color.html b/examples/images/plain-color.html new file mode 100644 index 0000000000..28ea79bfb3 --- /dev/null +++ b/examples/images/plain-color.html @@ -0,0 +1,79 @@ + + + + Plain color | CartoDB.js + + + + + + + + + +
+ + diff --git a/examples/images/single-image.html b/examples/images/single-image.html new file mode 100644 index 0000000000..aa9927f058 --- /dev/null +++ b/examples/images/single-image.html @@ -0,0 +1,40 @@ + + + + Single image example | CartoDB.js + + + + + + + +
+
+ +
+
+ + diff --git a/examples/images/torque.html b/examples/images/torque.html new file mode 100644 index 0000000000..73353e467c --- /dev/null +++ b/examples/images/torque.html @@ -0,0 +1,58 @@ + + + + Image example | CartoDB.js + + + + + + + + + +
+ + diff --git a/examples/images/torque_step.html b/examples/images/torque_step.html new file mode 100644 index 0000000000..cdf4bb2b58 --- /dev/null +++ b/examples/images/torque_step.html @@ -0,0 +1,58 @@ + + + + Image example | CartoDB.js + + + + + + + + + +
+ + diff --git a/examples/index.html b/examples/index.html index 4c164c15b3..fcc135de68 100644 --- a/examples/index.html +++ b/examples/index.html @@ -4,9 +4,9 @@ Legends | CartoDB.js - + - + + - + + + + +
+ + diff --git a/examples/layer_selector.html b/examples/layer_selector.html index 91dfc57c80..6e361479ae 100644 --- a/examples/layer_selector.html +++ b/examples/layer_selector.html @@ -43,7 +43,7 @@ } - +
@@ -58,7 +58,7 @@ - + + + + + + + + \ No newline at end of file diff --git a/examples/leaflet_core_library.html b/examples/leaflet_core_library.html index 33669d82bc..6646838f73 100644 --- a/examples/leaflet_core_library.html +++ b/examples/leaflet_core_library.html @@ -19,7 +19,7 @@ - + + + + + + + + diff --git a/examples/leaflet_sandwich.html b/examples/leaflet_sandwich.html new file mode 100644 index 0000000000..836e2f9291 --- /dev/null +++ b/examples/leaflet_sandwich.html @@ -0,0 +1,79 @@ + + + + Leaflet multilayer example | CartoDB.js + + + + + + + + +
+ + + + + + + + diff --git a/examples/leaflet_vector.html b/examples/leaflet_vector.html index 662a4868b2..c76e0bfd66 100644 --- a/examples/leaflet_vector.html +++ b/examples/leaflet_vector.html @@ -21,7 +21,7 @@ - + - + - + + + + + - + + + + + + + + \ No newline at end of file diff --git a/examples/named-maps-template.html b/examples/named-maps-template.html new file mode 100644 index 0000000000..d06f6a624a --- /dev/null +++ b/examples/named-maps-template.html @@ -0,0 +1,32 @@ + + + + Named Maps Tutorial | CartoDB + + + + + + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/examples/new-infowindow.html b/examples/new-infowindow.html index 67a335a398..436d850cbe 100644 --- a/examples/new-infowindow.html +++ b/examples/new-infowindow.html @@ -7,7 +7,7 @@ - +
@@ -36,7 +36,7 @@

OH!

- + + + + + + + diff --git a/examples/openlayers.html b/examples/openlayers.html index bbb2a6d630..ca46761ea0 100644 --- a/examples/openlayers.html +++ b/examples/openlayers.html @@ -20,7 +20,7 @@
- + + + + +
+ + diff --git a/examples/search_with_pin.html b/examples/search_with_pin.html new file mode 100644 index 0000000000..2dbd0b358c --- /dev/null +++ b/examples/search_with_pin.html @@ -0,0 +1,46 @@ + + + + Search with Pin + Infowindow | CartoDB.js + + + + + + + + +
+ + + + + + diff --git a/examples/single-image.html b/examples/single-image.html new file mode 100644 index 0000000000..6fcd6323b8 --- /dev/null +++ b/examples/single-image.html @@ -0,0 +1,40 @@ + + + + Single image example | CartoDB.js + + + + + + + +
+
+ +
+
+ + diff --git a/examples/slider.html b/examples/slider.html new file mode 100644 index 0000000000..2a7809a43a --- /dev/null +++ b/examples/slider.html @@ -0,0 +1,88 @@ + + + + Slider | CartoDB.js + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + diff --git a/examples/slider/slider.html b/examples/slider/slider.html index 13cdd3bf7f..16eafb73f7 100644 --- a/examples/slider/slider.html +++ b/examples/slider/slider.html @@ -8,7 +8,7 @@ - + @@ -30,7 +30,7 @@ + + + + + + +
+ + diff --git a/examples/torque_step.html b/examples/torque_step.html new file mode 100644 index 0000000000..ecde4098a4 --- /dev/null +++ b/examples/torque_step.html @@ -0,0 +1,58 @@ + + + + Image example | CartoDB.js + + + + + + + + + +
+ + diff --git a/examples/tutorial-1.html b/examples/tutorial-1.html index 9015cf286d..6a2884c30f 100644 --- a/examples/tutorial-1.html +++ b/examples/tutorial-1.html @@ -1,7 +1,7 @@ - - + + + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/examples/tutorial-google-driving.html b/examples/tutorial-google-driving.html new file mode 100644 index 0000000000..e8e6db43ef --- /dev/null +++ b/examples/tutorial-google-driving.html @@ -0,0 +1,93 @@ + + + + Driving directions to clicked point | CartoDB.js + + + + + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/examples/tutorial-google-heatmap-2.html b/examples/tutorial-google-heatmap-2.html new file mode 100644 index 0000000000..96edbca9c6 --- /dev/null +++ b/examples/tutorial-google-heatmap-2.html @@ -0,0 +1,70 @@ + + + + GMaps Heatmap of bike trips | CartoDB.js + + + + + + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/examples/tutorial-google-heatmap.html b/examples/tutorial-google-heatmap.html new file mode 100644 index 0000000000..2da128b72f --- /dev/null +++ b/examples/tutorial-google-heatmap.html @@ -0,0 +1,74 @@ + + + + GMaps Heatmap of bike trips | CartoDB.js + + + + + + + + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/tutorial-query_by_distance-template.html b/examples/tutorial-query_by_distance-template.html index 24b81d9b35..6f0a0400e0 100644 --- a/examples/tutorial-query_by_distance-template.html +++ b/examples/tutorial-query_by_distance-template.html @@ -1,7 +1,7 @@ - - + + + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/examples/tutorials/named-maps-template.html b/examples/tutorials/named-maps-template.html new file mode 100644 index 0000000000..d06f6a624a --- /dev/null +++ b/examples/tutorials/named-maps-template.html @@ -0,0 +1,32 @@ + + + + Named Maps Tutorial | CartoDB + + + + + + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/examples/tutorials/tutorial-1.html b/examples/tutorials/tutorial-1.html index 9015cf286d..88a1f2f9a7 100644 --- a/examples/tutorials/tutorial-1.html +++ b/examples/tutorials/tutorial-1.html @@ -1,7 +1,7 @@ - - + + + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/examples/tutorials/tutorial-google-driving.html b/examples/tutorials/tutorial-google-driving.html new file mode 100644 index 0000000000..5a283c15df --- /dev/null +++ b/examples/tutorials/tutorial-google-driving.html @@ -0,0 +1,93 @@ + + + + Driving directions to clicked point | CartoDB.js + + + + + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/examples/tutorials/tutorial-google-heatmap-2.html b/examples/tutorials/tutorial-google-heatmap-2.html new file mode 100644 index 0000000000..849473cc15 --- /dev/null +++ b/examples/tutorials/tutorial-google-heatmap-2.html @@ -0,0 +1,70 @@ + + + + GMaps Heatmap of bike trips | CartoDB.js + + + + + + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/examples/tutorials/tutorial-google-heatmap.html b/examples/tutorials/tutorial-google-heatmap.html new file mode 100644 index 0000000000..199f31e20b --- /dev/null +++ b/examples/tutorials/tutorial-google-heatmap.html @@ -0,0 +1,74 @@ + + + + GMaps Heatmap of bike trips | CartoDB.js + + + + + + + + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/tutorials/tutorial-query_by_distance-template.html b/examples/tutorials/tutorial-query_by_distance-template.html index 24b81d9b35..1e12f438bd 100644 --- a/examples/tutorials/tutorial-query_by_distance-template.html +++ b/examples/tutorials/tutorial-query_by_distance-template.html @@ -1,7 +1,7 @@ - - + + '; -}; - -})(require('../tree')); - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"../tree":7,"underscore":undefined}],32:[function(require,module,exports){ -(function(tree) { - -tree.URL = function URL(val, paths) { - this.value = val; - this.paths = paths; -}; - -tree.URL.prototype = { - is: 'uri', - toString: function() { - return this.value.toString(); - }, - ev: function(ctx) { - return new tree.URL(this.value.ev(ctx), this.paths); - } -}; - -})(require('../tree')); - -},{"../tree":7}],33:[function(require,module,exports){ -(function(tree) { - -tree.Value = function Value(value) { - this.value = value; -}; - -tree.Value.prototype = { - is: 'value', - ev: function(env) { - if (this.value.length === 1) { - return this.value[0].ev(env); - } else { - return new tree.Value(this.value.map(function(v) { - return v.ev(env); - })); - } - }, - toString: function(env, selector, sep, format) { - return this.value.map(function(e) { - return e.toString(env, format); - }).join(sep || ', '); - }, - clone: function() { - var obj = Object.create(tree.Value.prototype); - if (Array.isArray(obj)) obj.value = this.value.slice(); - else obj.value = this.value; - obj.is = this.is; - return obj; - }, - - toJS: function(env) { - //var v = this.value[0].value[0]; - var val = this.ev(env); - var v = val.toString(); - if(val.is === "color" || val.is === 'uri' || val.is === 'string' || val.is === 'keyword') { - v = "'" + v + "'"; - } else if (val.is === 'field') { - // replace [variable] by ctx['variable'] - v = v.replace(/\[(.*)\]/g, "data['$1']"); - } - return "_value = " + v + ";"; - } - -}; - -})(require('../tree')); - -},{"../tree":7}],34:[function(require,module,exports){ -(function(tree) { - -tree.Variable = function Variable(name, index, filename) { - this.name = name; - this.index = index; - this.filename = filename; -}; - -tree.Variable.prototype = { - is: 'variable', - toString: function() { - return this.name; - }, - ev: function(env) { - var variable, - v, - name = this.name; - - if (this._css) return this._css; - - var thisframe = env.frames.filter(function(f) { - return f.name == this.name; - }.bind(this)); - if (thisframe.length) { - return thisframe[0].value.ev(env); - } else { - env.error({ - message: 'variable ' + this.name + ' is undefined', - index: this.index, - type: 'runtime', - filename: this.filename - }); - return { - is: 'undefined', - value: 'undefined' - }; - } - } -}; - -})(require('../tree')); - -},{"../tree":7}],35:[function(require,module,exports){ -var tree = require('../tree'); - -// Storage for zoom ranges. Only supports continuous ranges, -// and stores them as bit-sequences so that they can be combined, -// inverted, and compared quickly. -tree.Zoom = function(op, value, index) { - this.op = op; - this.value = value; - this.index = index; -}; - -tree.Zoom.prototype.setZoom = function(zoom) { - this.zoom = zoom; - return this; -}; - -tree.Zoom.prototype.ev = function(env) { - var start = 0, - end = Infinity, - value = parseInt(this.value.ev(env).toString(), 10), - zoom = 0; - - if (value > tree.Zoom.maxZoom || value < 0) { - env.error({ - message: 'Only zoom levels between 0 and ' + - tree.Zoom.maxZoom + ' supported.', - index: this.index - }); - } - - switch (this.op) { - case '=': - this.zoom = 1 << value; - return this; - case '>': - start = value + 1; - break; - case '>=': - start = value; - break; - case '<': - end = value - 1; - break; - case '<=': - end = value; - break; - } - for (var i = 0; i <= tree.Zoom.maxZoom; i++) { - if (i >= start && i <= end) { - zoom |= (1 << i); - } - } - this.zoom = zoom; - return this; -}; - -tree.Zoom.prototype.toString = function() { - return this.zoom; -}; - -// Covers all zoomlevels from 0 to 22 -tree.Zoom.all = 0x7FFFFF; - -tree.Zoom.maxZoom = 22; - -tree.Zoom.ranges = { - 0: 1000000000, - 1: 500000000, - 2: 200000000, - 3: 100000000, - 4: 50000000, - 5: 25000000, - 6: 12500000, - 7: 6500000, - 8: 3000000, - 9: 1500000, - 10: 750000, - 11: 400000, - 12: 200000, - 13: 100000, - 14: 50000, - 15: 25000, - 16: 12500, - 17: 5000, - 18: 2500, - 19: 1500, - 20: 750, - 21: 500, - 22: 250, - 23: 100 -}; - -// Only works for single range zooms. `[XXX....XXXXX.........]` is invalid. -tree.Zoom.prototype.toXML = function() { - var conditions = []; - if (this.zoom != tree.Zoom.all) { - var start = null, end = null; - for (var i = 0; i <= tree.Zoom.maxZoom; i++) { - if (this.zoom & (1 << i)) { - if (start === null) start = i; - end = i; - } - } - if (start > 0) conditions.push(' ' + - tree.Zoom.ranges[start] + '\n'); - if (end < 22) conditions.push(' ' + - tree.Zoom.ranges[end + 1] + '\n'); - } - return conditions; -}; - -tree.Zoom.prototype.toString = function() { - var str = ''; - for (var i = 0; i <= tree.Zoom.maxZoom; i++) { - str += (this.zoom & (1 << i)) ? 'X' : '.'; - } - return str; -}; - -},{"../tree":7}],36:[function(require,module,exports){ - -},{}],37:[function(require,module,exports){ -// http://wiki.commonjs.org/wiki/Unit_Testing/1.0 -// -// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! -// -// Originally from narwhal.js (http://narwhaljs.org) -// Copyright (c) 2009 Thomas Robinson <280north.com> -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the 'Software'), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -// when used in node, this will actually load the util module we depend on -// versus loading the builtin util module as happens otherwise -// this is a bug in node module loading as far as I am concerned -var util = require('util/'); - -var pSlice = Array.prototype.slice; -var hasOwn = Object.prototype.hasOwnProperty; - -// 1. The assert module provides functions that throw -// AssertionError's when particular conditions are not met. The -// assert module must conform to the following interface. - -var assert = module.exports = ok; - -// 2. The AssertionError is defined in assert. -// new assert.AssertionError({ message: message, -// actual: actual, -// expected: expected }) - -assert.AssertionError = function AssertionError(options) { - this.name = 'AssertionError'; - this.actual = options.actual; - this.expected = options.expected; - this.operator = options.operator; - if (options.message) { - this.message = options.message; - this.generatedMessage = false; - } else { - this.message = getMessage(this); - this.generatedMessage = true; - } - var stackStartFunction = options.stackStartFunction || fail; - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, stackStartFunction); - } - else { - // non v8 browsers so we can have a stacktrace - var err = new Error(); - if (err.stack) { - var out = err.stack; - - // try to strip useless frames - var fn_name = stackStartFunction.name; - var idx = out.indexOf('\n' + fn_name); - if (idx >= 0) { - // once we have located the function frame - // we need to strip out everything before it (and its line) - var next_line = out.indexOf('\n', idx + 1); - out = out.substring(next_line + 1); - } - - this.stack = out; - } - } -}; - -// assert.AssertionError instanceof Error -util.inherits(assert.AssertionError, Error); - -function replacer(key, value) { - if (util.isUndefined(value)) { - return '' + value; - } - if (util.isNumber(value) && (isNaN(value) || !isFinite(value))) { - return value.toString(); - } - if (util.isFunction(value) || util.isRegExp(value)) { - return value.toString(); - } - return value; -} - -function truncate(s, n) { - if (util.isString(s)) { - return s.length < n ? s : s.slice(0, n); - } else { - return s; - } -} - -function getMessage(self) { - return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' + - self.operator + ' ' + - truncate(JSON.stringify(self.expected, replacer), 128); -} - -// At present only the three keys mentioned above are used and -// understood by the spec. Implementations or sub modules can pass -// other keys to the AssertionError's constructor - they will be -// ignored. - -// 3. All of the following functions must throw an AssertionError -// when a corresponding condition is not met, with a message that -// may be undefined if not provided. All assertion methods provide -// both the actual and expected values to the assertion error for -// display purposes. - -function fail(actual, expected, message, operator, stackStartFunction) { - throw new assert.AssertionError({ - message: message, - actual: actual, - expected: expected, - operator: operator, - stackStartFunction: stackStartFunction - }); -} - -// EXTENSION! allows for well behaved errors defined elsewhere. -assert.fail = fail; - -// 4. Pure assertion tests whether a value is truthy, as determined -// by !!guard. -// assert.ok(guard, message_opt); -// This statement is equivalent to assert.equal(true, !!guard, -// message_opt);. To test strictly for the value true, use -// assert.strictEqual(true, guard, message_opt);. - -function ok(value, message) { - if (!value) fail(value, true, message, '==', assert.ok); -} -assert.ok = ok; - -// 5. The equality assertion tests shallow, coercive equality with -// ==. -// assert.equal(actual, expected, message_opt); - -assert.equal = function equal(actual, expected, message) { - if (actual != expected) fail(actual, expected, message, '==', assert.equal); -}; - -// 6. The non-equality assertion tests for whether two objects are not equal -// with != assert.notEqual(actual, expected, message_opt); - -assert.notEqual = function notEqual(actual, expected, message) { - if (actual == expected) { - fail(actual, expected, message, '!=', assert.notEqual); - } -}; - -// 7. The equivalence assertion tests a deep equality relation. -// assert.deepEqual(actual, expected, message_opt); - -assert.deepEqual = function deepEqual(actual, expected, message) { - if (!_deepEqual(actual, expected)) { - fail(actual, expected, message, 'deepEqual', assert.deepEqual); - } -}; - -function _deepEqual(actual, expected) { - // 7.1. All identical values are equivalent, as determined by ===. - if (actual === expected) { - return true; - - } else if (util.isBuffer(actual) && util.isBuffer(expected)) { - if (actual.length != expected.length) return false; - - for (var i = 0; i < actual.length; i++) { - if (actual[i] !== expected[i]) return false; - } - - return true; - - // 7.2. If the expected value is a Date object, the actual value is - // equivalent if it is also a Date object that refers to the same time. - } else if (util.isDate(actual) && util.isDate(expected)) { - return actual.getTime() === expected.getTime(); - - // 7.3 If the expected value is a RegExp object, the actual value is - // equivalent if it is also a RegExp object with the same source and - // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). - } else if (util.isRegExp(actual) && util.isRegExp(expected)) { - return actual.source === expected.source && - actual.global === expected.global && - actual.multiline === expected.multiline && - actual.lastIndex === expected.lastIndex && - actual.ignoreCase === expected.ignoreCase; - - // 7.4. Other pairs that do not both pass typeof value == 'object', - // equivalence is determined by ==. - } else if (!util.isObject(actual) && !util.isObject(expected)) { - return actual == expected; - - // 7.5 For all other Object pairs, including Array objects, equivalence is - // determined by having the same number of owned properties (as verified - // with Object.prototype.hasOwnProperty.call), the same set of keys - // (although not necessarily the same order), equivalent values for every - // corresponding key, and an identical 'prototype' property. Note: this - // accounts for both named and indexed properties on Arrays. - } else { - return objEquiv(actual, expected); - } -} - -function isArguments(object) { - return Object.prototype.toString.call(object) == '[object Arguments]'; -} - -function objEquiv(a, b) { - if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b)) - return false; - // an identical 'prototype' property. - if (a.prototype !== b.prototype) return false; - //~~~I've managed to break Object.keys through screwy arguments passing. - // Converting to array solves the problem. - if (isArguments(a)) { - if (!isArguments(b)) { - return false; - } - a = pSlice.call(a); - b = pSlice.call(b); - return _deepEqual(a, b); - } - try { - var ka = objectKeys(a), - kb = objectKeys(b), - key, i; - } catch (e) {//happens when one is a string literal and the other isn't - return false; - } - // having the same number of owned properties (keys incorporates - // hasOwnProperty) - if (ka.length != kb.length) - return false; - //the same set of keys (although not necessarily the same order), - ka.sort(); - kb.sort(); - //~~~cheap key test - for (i = ka.length - 1; i >= 0; i--) { - if (ka[i] != kb[i]) - return false; - } - //equivalent values for every corresponding key, and - //~~~possibly expensive deep test - for (i = ka.length - 1; i >= 0; i--) { - key = ka[i]; - if (!_deepEqual(a[key], b[key])) return false; - } - return true; -} - -// 8. The non-equivalence assertion tests for any deep inequality. -// assert.notDeepEqual(actual, expected, message_opt); - -assert.notDeepEqual = function notDeepEqual(actual, expected, message) { - if (_deepEqual(actual, expected)) { - fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); - } -}; - -// 9. The strict equality assertion tests strict equality, as determined by ===. -// assert.strictEqual(actual, expected, message_opt); - -assert.strictEqual = function strictEqual(actual, expected, message) { - if (actual !== expected) { - fail(actual, expected, message, '===', assert.strictEqual); - } -}; - -// 10. The strict non-equality assertion tests for strict inequality, as -// determined by !==. assert.notStrictEqual(actual, expected, message_opt); - -assert.notStrictEqual = function notStrictEqual(actual, expected, message) { - if (actual === expected) { - fail(actual, expected, message, '!==', assert.notStrictEqual); - } -}; - -function expectedException(actual, expected) { - if (!actual || !expected) { - return false; - } - - if (Object.prototype.toString.call(expected) == '[object RegExp]') { - return expected.test(actual); - } else if (actual instanceof expected) { - return true; - } else if (expected.call({}, actual) === true) { - return true; - } - - return false; -} - -function _throws(shouldThrow, block, expected, message) { - var actual; - - if (util.isString(expected)) { - message = expected; - expected = null; - } - - try { - block(); - } catch (e) { - actual = e; - } - - message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + - (message ? ' ' + message : '.'); - - if (shouldThrow && !actual) { - fail(actual, expected, 'Missing expected exception' + message); - } - - if (!shouldThrow && expectedException(actual, expected)) { - fail(actual, expected, 'Got unwanted exception' + message); - } - - if ((shouldThrow && actual && expected && - !expectedException(actual, expected)) || (!shouldThrow && actual)) { - throw actual; - } -} - -// 11. Expected to throw an error: -// assert.throws(block, Error_opt, message_opt); - -assert.throws = function(block, /*optional*/error, /*optional*/message) { - _throws.apply(this, [true].concat(pSlice.call(arguments))); -}; - -// EXTENSION! This is annoying to write outside this module. -assert.doesNotThrow = function(block, /*optional*/message) { - _throws.apply(this, [false].concat(pSlice.call(arguments))); -}; - -assert.ifError = function(err) { if (err) {throw err;}}; - -var objectKeys = Object.keys || function (obj) { - var keys = []; - for (var key in obj) { - if (hasOwn.call(obj, key)) keys.push(key); - } - return keys; -}; - -},{"util/":42}],38:[function(require,module,exports){ -if (typeof Object.create === 'function') { - // implementation from standard node.js 'util' module - module.exports = function inherits(ctor, superCtor) { - ctor.super_ = superCtor - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); - }; -} else { - // old school shim for old browsers - module.exports = function inherits(ctor, superCtor) { - ctor.super_ = superCtor - var TempCtor = function () {} - TempCtor.prototype = superCtor.prototype - ctor.prototype = new TempCtor() - ctor.prototype.constructor = ctor - } -} - -},{}],39:[function(require,module,exports){ -(function (process){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -// resolves . and .. elements in a path array with directory names there -// must be no slashes, empty elements, or device names (c:\) in the array -// (so also no leading and trailing slashes - it does not distinguish -// relative and absolute paths) -function normalizeArray(parts, allowAboveRoot) { - // if the path tries to go above the root, `up` ends up > 0 - var up = 0; - for (var i = parts.length - 1; i >= 0; i--) { - var last = parts[i]; - if (last === '.') { - parts.splice(i, 1); - } else if (last === '..') { - parts.splice(i, 1); - up++; - } else if (up) { - parts.splice(i, 1); - up--; - } - } - - // if the path is allowed to go above the root, restore leading ..s - if (allowAboveRoot) { - for (; up--; up) { - parts.unshift('..'); - } - } - - return parts; -} - -// Split a filename into [root, dir, basename, ext], unix version -// 'root' is just a slash, or nothing. -var splitPathRe = - /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; -var splitPath = function(filename) { - return splitPathRe.exec(filename).slice(1); -}; - -// path.resolve([from ...], to) -// posix version -exports.resolve = function() { - var resolvedPath = '', - resolvedAbsolute = false; - - for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { - var path = (i >= 0) ? arguments[i] : process.cwd(); - - // Skip empty and invalid entries - if (typeof path !== 'string') { - throw new TypeError('Arguments to path.resolve must be strings'); - } else if (!path) { - continue; - } - - resolvedPath = path + '/' + resolvedPath; - resolvedAbsolute = path.charAt(0) === '/'; - } - - // At this point the path should be resolved to a full absolute path, but - // handle relative paths to be safe (might happen when process.cwd() fails) - - // Normalize the path - resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { - return !!p; - }), !resolvedAbsolute).join('/'); - - return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; -}; - -// path.normalize(path) -// posix version -exports.normalize = function(path) { - var isAbsolute = exports.isAbsolute(path), - trailingSlash = substr(path, -1) === '/'; - - // Normalize the path - path = normalizeArray(filter(path.split('/'), function(p) { - return !!p; - }), !isAbsolute).join('/'); - - if (!path && !isAbsolute) { - path = '.'; - } - if (path && trailingSlash) { - path += '/'; - } - - return (isAbsolute ? '/' : '') + path; -}; - -// posix version -exports.isAbsolute = function(path) { - return path.charAt(0) === '/'; -}; - -// posix version -exports.join = function() { - var paths = Array.prototype.slice.call(arguments, 0); - return exports.normalize(filter(paths, function(p, index) { - if (typeof p !== 'string') { - throw new TypeError('Arguments to path.join must be strings'); - } - return p; - }).join('/')); -}; - - -// path.relative(from, to) -// posix version -exports.relative = function(from, to) { - from = exports.resolve(from).substr(1); - to = exports.resolve(to).substr(1); - - function trim(arr) { - var start = 0; - for (; start < arr.length; start++) { - if (arr[start] !== '') break; - } - - var end = arr.length - 1; - for (; end >= 0; end--) { - if (arr[end] !== '') break; - } - - if (start > end) return []; - return arr.slice(start, end - start + 1); - } - - var fromParts = trim(from.split('/')); - var toParts = trim(to.split('/')); - - var length = Math.min(fromParts.length, toParts.length); - var samePartsLength = length; - for (var i = 0; i < length; i++) { - if (fromParts[i] !== toParts[i]) { - samePartsLength = i; - break; - } - } - - var outputParts = []; - for (var i = samePartsLength; i < fromParts.length; i++) { - outputParts.push('..'); - } - - outputParts = outputParts.concat(toParts.slice(samePartsLength)); - - return outputParts.join('/'); -}; - -exports.sep = '/'; -exports.delimiter = ':'; - -exports.dirname = function(path) { - var result = splitPath(path), - root = result[0], - dir = result[1]; - - if (!root && !dir) { - // No dirname whatsoever - return '.'; - } - - if (dir) { - // It has a dirname, strip trailing slash - dir = dir.substr(0, dir.length - 1); - } - - return root + dir; -}; - - -exports.basename = function(path, ext) { - var f = splitPath(path)[2]; - // TODO: make this comparison case-insensitive on windows? - if (ext && f.substr(-1 * ext.length) === ext) { - f = f.substr(0, f.length - ext.length); - } - return f; -}; - - -exports.extname = function(path) { - return splitPath(path)[3]; -}; - -function filter (xs, f) { - if (xs.filter) return xs.filter(f); - var res = []; - for (var i = 0; i < xs.length; i++) { - if (f(xs[i], i, xs)) res.push(xs[i]); - } - return res; -} - -// String.prototype.substr - negative index don't work in IE8 -var substr = 'ab'.substr(-1) === 'b' - ? function (str, start, len) { return str.substr(start, len) } - : function (str, start, len) { - if (start < 0) start = str.length + start; - return str.substr(start, len); - } -; - -}).call(this,require('_process')) -},{"_process":40}],40:[function(require,module,exports){ -// shim for using process in browser - -var process = module.exports = {}; - -process.nextTick = (function () { - var canSetImmediate = typeof window !== 'undefined' - && window.setImmediate; - var canMutationObserver = typeof window !== 'undefined' - && window.MutationObserver; - var canPost = typeof window !== 'undefined' - && window.postMessage && window.addEventListener - ; - - if (canSetImmediate) { - return function (f) { return window.setImmediate(f) }; - } - - var queue = []; - - if (canMutationObserver) { - var hiddenDiv = document.createElement("div"); - var observer = new MutationObserver(function () { - var queueList = queue.slice(); - queue.length = 0; - queueList.forEach(function (fn) { - fn(); - }); - }); - - observer.observe(hiddenDiv, { attributes: true }); - - return function nextTick(fn) { - if (!queue.length) { - hiddenDiv.setAttribute('yes', 'no'); - } - queue.push(fn); - }; - } - - if (canPost) { - window.addEventListener('message', function (ev) { - var source = ev.source; - if ((source === window || source === null) && ev.data === 'process-tick') { - ev.stopPropagation(); - if (queue.length > 0) { - var fn = queue.shift(); - fn(); - } - } - }, true); - - return function nextTick(fn) { - queue.push(fn); - window.postMessage('process-tick', '*'); - }; - } - - return function nextTick(fn) { - setTimeout(fn, 0); - }; -})(); - -process.title = 'browser'; -process.browser = true; -process.env = {}; -process.argv = []; - -function noop() {} - -process.on = noop; -process.addListener = noop; -process.once = noop; -process.off = noop; -process.removeListener = noop; -process.removeAllListeners = noop; -process.emit = noop; - -process.binding = function (name) { - throw new Error('process.binding is not supported'); -}; - -// TODO(shtylman) -process.cwd = function () { return '/' }; -process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); -}; - -},{}],41:[function(require,module,exports){ -module.exports = function isBuffer(arg) { - return arg && typeof arg === 'object' - && typeof arg.copy === 'function' - && typeof arg.fill === 'function' - && typeof arg.readUInt8 === 'function'; -} -},{}],42:[function(require,module,exports){ -(function (process,global){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -var formatRegExp = /%[sdj%]/g; -exports.format = function(f) { - if (!isString(f)) { - var objects = []; - for (var i = 0; i < arguments.length; i++) { - objects.push(inspect(arguments[i])); - } - return objects.join(' '); - } - - var i = 1; - var args = arguments; - var len = args.length; - var str = String(f).replace(formatRegExp, function(x) { - if (x === '%%') return '%'; - if (i >= len) return x; - switch (x) { - case '%s': return String(args[i++]); - case '%d': return Number(args[i++]); - case '%j': - try { - return JSON.stringify(args[i++]); - } catch (_) { - return '[Circular]'; - } - default: - return x; - } - }); - for (var x = args[i]; i < len; x = args[++i]) { - if (isNull(x) || !isObject(x)) { - str += ' ' + x; - } else { - str += ' ' + inspect(x); - } - } - return str; -}; - - -// Mark that a method should not be used. -// Returns a modified function which warns once by default. -// If --no-deprecation is set, then it is a no-op. -exports.deprecate = function(fn, msg) { - // Allow for deprecating things in the process of starting up. - if (isUndefined(global.process)) { - return function() { - return exports.deprecate(fn, msg).apply(this, arguments); - }; - } - - if (process.noDeprecation === true) { - return fn; - } - - var warned = false; - function deprecated() { - if (!warned) { - if (process.throwDeprecation) { - throw new Error(msg); - } else if (process.traceDeprecation) { - console.trace(msg); - } else { - console.error(msg); - } - warned = true; - } - return fn.apply(this, arguments); - } - - return deprecated; -}; - - -var debugs = {}; -var debugEnviron; -exports.debuglog = function(set) { - if (isUndefined(debugEnviron)) - debugEnviron = process.env.NODE_DEBUG || ''; - set = set.toUpperCase(); - if (!debugs[set]) { - if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { - var pid = process.pid; - debugs[set] = function() { - var msg = exports.format.apply(exports, arguments); - console.error('%s %d: %s', set, pid, msg); - }; - } else { - debugs[set] = function() {}; - } - } - return debugs[set]; -}; - - -/** - * Echos the value of a value. Trys to print the value out - * in the best way possible given the different types. - * - * @param {Object} obj The object to print out. - * @param {Object} opts Optional options object that alters the output. - */ -/* legacy: obj, showHidden, depth, colors*/ -function inspect(obj, opts) { - // default options - var ctx = { - seen: [], - stylize: stylizeNoColor - }; - // legacy... - if (arguments.length >= 3) ctx.depth = arguments[2]; - if (arguments.length >= 4) ctx.colors = arguments[3]; - if (isBoolean(opts)) { - // legacy... - ctx.showHidden = opts; - } else if (opts) { - // got an "options" object - exports._extend(ctx, opts); - } - // set default options - if (isUndefined(ctx.showHidden)) ctx.showHidden = false; - if (isUndefined(ctx.depth)) ctx.depth = 2; - if (isUndefined(ctx.colors)) ctx.colors = false; - if (isUndefined(ctx.customInspect)) ctx.customInspect = true; - if (ctx.colors) ctx.stylize = stylizeWithColor; - return formatValue(ctx, obj, ctx.depth); -} -exports.inspect = inspect; - - -// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics -inspect.colors = { - 'bold' : [1, 22], - 'italic' : [3, 23], - 'underline' : [4, 24], - 'inverse' : [7, 27], - 'white' : [37, 39], - 'grey' : [90, 39], - 'black' : [30, 39], - 'blue' : [34, 39], - 'cyan' : [36, 39], - 'green' : [32, 39], - 'magenta' : [35, 39], - 'red' : [31, 39], - 'yellow' : [33, 39] -}; - -// Don't use 'blue' not visible on cmd.exe -inspect.styles = { - 'special': 'cyan', - 'number': 'yellow', - 'boolean': 'yellow', - 'undefined': 'grey', - 'null': 'bold', - 'string': 'green', - 'date': 'magenta', - // "name": intentionally not styling - 'regexp': 'red' -}; - - -function stylizeWithColor(str, styleType) { - var style = inspect.styles[styleType]; - - if (style) { - return '\u001b[' + inspect.colors[style][0] + 'm' + str + - '\u001b[' + inspect.colors[style][1] + 'm'; - } else { - return str; - } -} - - -function stylizeNoColor(str, styleType) { - return str; -} - - -function arrayToHash(array) { - var hash = {}; - - array.forEach(function(val, idx) { - hash[val] = true; - }); - - return hash; -} - - -function formatValue(ctx, value, recurseTimes) { - // Provide a hook for user-specified inspect functions. - // Check that value is an object with an inspect function on it - if (ctx.customInspect && - value && - isFunction(value.inspect) && - // Filter out the util module, it's inspect function is special - value.inspect !== exports.inspect && - // Also filter out any prototype objects using the circular check. - !(value.constructor && value.constructor.prototype === value)) { - var ret = value.inspect(recurseTimes, ctx); - if (!isString(ret)) { - ret = formatValue(ctx, ret, recurseTimes); - } - return ret; - } - - // Primitive types cannot have properties - var primitive = formatPrimitive(ctx, value); - if (primitive) { - return primitive; - } - - // Look up the keys of the object. - var keys = Object.keys(value); - var visibleKeys = arrayToHash(keys); - - if (ctx.showHidden) { - keys = Object.getOwnPropertyNames(value); - } - - // IE doesn't make error fields non-enumerable - // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx - if (isError(value) - && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { - return formatError(value); - } - - // Some type of object without properties can be shortcutted. - if (keys.length === 0) { - if (isFunction(value)) { - var name = value.name ? ': ' + value.name : ''; - return ctx.stylize('[Function' + name + ']', 'special'); - } - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } - if (isDate(value)) { - return ctx.stylize(Date.prototype.toString.call(value), 'date'); - } - if (isError(value)) { - return formatError(value); - } - } - - var base = '', array = false, braces = ['{', '}']; - - // Make Array say that they are Array - if (isArray(value)) { - array = true; - braces = ['[', ']']; - } - - // Make functions say that they are functions - if (isFunction(value)) { - var n = value.name ? ': ' + value.name : ''; - base = ' [Function' + n + ']'; - } - - // Make RegExps say that they are RegExps - if (isRegExp(value)) { - base = ' ' + RegExp.prototype.toString.call(value); - } - - // Make dates with properties first say the date - if (isDate(value)) { - base = ' ' + Date.prototype.toUTCString.call(value); - } - - // Make error with message first say the error - if (isError(value)) { - base = ' ' + formatError(value); - } - - if (keys.length === 0 && (!array || value.length == 0)) { - return braces[0] + base + braces[1]; - } - - if (recurseTimes < 0) { - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } else { - return ctx.stylize('[Object]', 'special'); - } - } - - ctx.seen.push(value); - - var output; - if (array) { - output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); - } else { - output = keys.map(function(key) { - return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); - }); - } - - ctx.seen.pop(); - - return reduceToSingleString(output, base, braces); -} - - -function formatPrimitive(ctx, value) { - if (isUndefined(value)) - return ctx.stylize('undefined', 'undefined'); - if (isString(value)) { - var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') - .replace(/'/g, "\\'") - .replace(/\\"/g, '"') + '\''; - return ctx.stylize(simple, 'string'); - } - if (isNumber(value)) - return ctx.stylize('' + value, 'number'); - if (isBoolean(value)) - return ctx.stylize('' + value, 'boolean'); - // For some reason typeof null is "object", so special case here. - if (isNull(value)) - return ctx.stylize('null', 'null'); -} - - -function formatError(value) { - return '[' + Error.prototype.toString.call(value) + ']'; -} - - -function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { - var output = []; - for (var i = 0, l = value.length; i < l; ++i) { - if (hasOwnProperty(value, String(i))) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, - String(i), true)); - } else { - output.push(''); - } - } - keys.forEach(function(key) { - if (!key.match(/^\d+$/)) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, - key, true)); - } - }); - return output; -} - - -function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { - var name, str, desc; - desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; - if (desc.get) { - if (desc.set) { - str = ctx.stylize('[Getter/Setter]', 'special'); - } else { - str = ctx.stylize('[Getter]', 'special'); - } - } else { - if (desc.set) { - str = ctx.stylize('[Setter]', 'special'); - } - } - if (!hasOwnProperty(visibleKeys, key)) { - name = '[' + key + ']'; - } - if (!str) { - if (ctx.seen.indexOf(desc.value) < 0) { - if (isNull(recurseTimes)) { - str = formatValue(ctx, desc.value, null); - } else { - str = formatValue(ctx, desc.value, recurseTimes - 1); - } - if (str.indexOf('\n') > -1) { - if (array) { - str = str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n').substr(2); - } else { - str = '\n' + str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n'); - } - } - } else { - str = ctx.stylize('[Circular]', 'special'); - } - } - if (isUndefined(name)) { - if (array && key.match(/^\d+$/)) { - return str; - } - name = JSON.stringify('' + key); - if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { - name = name.substr(1, name.length - 2); - name = ctx.stylize(name, 'name'); - } else { - name = name.replace(/'/g, "\\'") - .replace(/\\"/g, '"') - .replace(/(^"|"$)/g, "'"); - name = ctx.stylize(name, 'string'); - } - } - - return name + ': ' + str; -} - - -function reduceToSingleString(output, base, braces) { - var numLinesEst = 0; - var length = output.reduce(function(prev, cur) { - numLinesEst++; - if (cur.indexOf('\n') >= 0) numLinesEst++; - return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; - }, 0); - - if (length > 60) { - return braces[0] + - (base === '' ? '' : base + '\n ') + - ' ' + - output.join(',\n ') + - ' ' + - braces[1]; - } - - return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; -} - - -// NOTE: These type checking functions intentionally don't use `instanceof` -// because it is fragile and can be easily faked with `Object.create()`. -function isArray(ar) { - return Array.isArray(ar); -} -exports.isArray = isArray; - -function isBoolean(arg) { - return typeof arg === 'boolean'; -} -exports.isBoolean = isBoolean; - -function isNull(arg) { - return arg === null; -} -exports.isNull = isNull; - -function isNullOrUndefined(arg) { - return arg == null; -} -exports.isNullOrUndefined = isNullOrUndefined; - -function isNumber(arg) { - return typeof arg === 'number'; -} -exports.isNumber = isNumber; - -function isString(arg) { - return typeof arg === 'string'; -} -exports.isString = isString; - -function isSymbol(arg) { - return typeof arg === 'symbol'; -} -exports.isSymbol = isSymbol; - -function isUndefined(arg) { - return arg === void 0; -} -exports.isUndefined = isUndefined; - -function isRegExp(re) { - return isObject(re) && objectToString(re) === '[object RegExp]'; -} -exports.isRegExp = isRegExp; - -function isObject(arg) { - return typeof arg === 'object' && arg !== null; -} -exports.isObject = isObject; - -function isDate(d) { - return isObject(d) && objectToString(d) === '[object Date]'; -} -exports.isDate = isDate; - -function isError(e) { - return isObject(e) && - (objectToString(e) === '[object Error]' || e instanceof Error); -} -exports.isError = isError; - -function isFunction(arg) { - return typeof arg === 'function'; -} -exports.isFunction = isFunction; - -function isPrimitive(arg) { - return arg === null || - typeof arg === 'boolean' || - typeof arg === 'number' || - typeof arg === 'string' || - typeof arg === 'symbol' || // ES6 symbol - typeof arg === 'undefined'; -} -exports.isPrimitive = isPrimitive; - -exports.isBuffer = require('./support/isBuffer'); - -function objectToString(o) { - return Object.prototype.toString.call(o); -} - - -function pad(n) { - return n < 10 ? '0' + n.toString(10) : n.toString(10); -} - - -var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', - 'Oct', 'Nov', 'Dec']; - -// 26 Feb 16:19:34 -function timestamp() { - var d = new Date(); - var time = [pad(d.getHours()), - pad(d.getMinutes()), - pad(d.getSeconds())].join(':'); - return [d.getDate(), months[d.getMonth()], time].join(' '); -} - - -// log is just a thin wrapper to console.log that prepends a timestamp -exports.log = function() { - console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); -}; - - -/** - * Inherit the prototype methods from one constructor into another. - * - * The Function.prototype.inherits from lang.js rewritten as a standalone - * function (not on Function.prototype). NOTE: If this file is to be loaded - * during bootstrapping this function needs to be rewritten using some native - * functions as prototype setup using normal JavaScript does not work as - * expected during bootstrapping (see mirror.js in r114903). - * - * @param {function} ctor Constructor function which needs to inherit the - * prototype. - * @param {function} superCtor Constructor function to inherit prototype from. - */ -exports.inherits = require('inherits'); - -exports._extend = function(origin, add) { - // Don't do anything if add isn't an object - if (!add || !isObject(add)) return origin; - - var keys = Object.keys(add); - var i = keys.length; - while (i--) { - origin[keys[i]] = add[keys[i]]; - } - return origin; -}; - -function hasOwnProperty(obj, prop) { - return Object.prototype.hasOwnProperty.call(obj, prop); -} - -}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./support/isBuffer":41,"_process":40,"inherits":38}],43:[function(require,module,exports){ -(function (__dirname){ -var fs = require('fs'), - path = require('path'), - existsSync = require('fs').existsSync || require('path').existsSync; - -// Load all stated versions into the module exports -module.exports.version = {}; - -var refs = [ - '2.0.0', - '2.0.1', - '2.0.2', - '2.1.0', - '2.1.1', - '2.2.0', - '2.3.0', - '3.0.0' -]; - -refs.map(function(version) { - module.exports.version[version] = require(path.join(__dirname, version, 'reference.json')); - var ds_path = path.join(__dirname, version, 'datasources.json'); - if (existsSync(ds_path)) { - module.exports.version[version].datasources = require(ds_path).datasources; - } -}); - -}).call(this,"/node_modules/mapnik-reference") -},{"fs":36,"path":39}],44:[function(require,module,exports){ -module.exports={ - "name": "carto", - "version": "0.15.1", - "description": "CartoCSS Stylesheet Compiler", - "url": "https://github.com/cartodb/carto", - "repository": { - "type": "git", - "url": "http://github.com/cartodb/carto.git" - }, - "author": { - "name": "CartoDB", - "url": "http://cartodb.com/" - }, - "keywords": [ - "maps", - "css", - "stylesheets" - ], - "contributors": [ - "Tom MacWright ", - "Konstantin Käfer", - "Alexis Sellier ", - "Raul Ochoa ", - "Javi Santana " - ], - "licenses": [ - { - "type": "Apache" - } - ], - "bin": { - "carto": "./bin/carto" - }, - "man": "./man/carto.1", - "main": "./lib/carto/index", - "engines": { - "node": ">=0.4.x" - }, - "dependencies": { - "underscore": "~1.6.0", - "mapnik-reference": "~6.0.2", - "optimist": "~0.6.0" - }, - "devDependencies": { - "mocha": "1.12.x", - "jshint": "0.2.x", - "sax": "0.1.x", - "istanbul": "~0.2.14", - "coveralls": "~2.10.1", - "browserify": "~7.0.0", - "uglify-js": "1.3.3" - }, - "scripts": { - "pretest": "npm install", - "test": "mocha -R spec", - "coverage": "istanbul cover ./node_modules/.bin/_mocha && coveralls < ./coverage/lcov.info" - } -} - -},{}]},{},[2])(2) -}); \ No newline at end of file +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;"undefined"!=typeof window?t=window:"undefined"!=typeof global?t=global:"undefined"!=typeof self&&(t=self),t.carto=e()}}(function(){var define,module,exports;return function e(t,n,r){function i(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(s)return s(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return i(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var s=typeof require=="function"&&require;for(var o=0;o1&&(t=arguments[1]),arguments.length>2&&(n=arguments[2]),{is:"tag",val:e,color:t,mode:n,toString:function(r){return'\n "}}},hsl:function(e,t,n){return this.hsla(e,t,n,1)},hsla:function(e,t,n,r){function u(e){return e=e<0?e+1:e>1?e-1:e,e*6<1?o+(i-o)*e*6:e*2<1?i:e*3<2?o+(i-o)*(2/3-e)*6:o}e=s(e)%360/360,t=s(t),n=s(n),r=s(r);if([e,t,n,r].some(isNaN))return null;var i=n<=.5?n*(t+1):n+t-n*t,o=n*2-i;return this.rgba(u(e+1/3)*255,u(e)*255,u(e-1/3)*255,r)},hue:function(t){return"toHSL"in t?new e.Dimension(Math.round(t.toHSL().h)):null},saturation:function(t){return"toHSL"in t?new e.Dimension(Math.round(t.toHSL().s*100),"%"):null},lightness:function(t){return"toHSL"in t?new e.Dimension(Math.round(t.toHSL().l*100),"%"):null},alpha:function(t){return"toHSL"in t?new e.Dimension(t.toHSL().a):null},saturate:function(e,t){if("toHSL"in e){var n=e.toHSL();return n.s+=t.value/100,n.s=o(n.s),i(n)}return null},desaturate:function(e,t){if("toHSL"in e){var n=e.toHSL();return n.s-=t.value/100,n.s=o(n.s),i(n)}return null},lighten:function(e,t){if("toHSL"in e){var n=e.toHSL();return n.l+=t.value/100,n.l=o(n.l),i(n)}return null},darken:function(e,t){if("toHSL"in e){var n=e.toHSL();return n.l-=t.value/100,n.l=o(n.l),i(n)}return null},fadein:function(e,t){if("toHSL"in e){var n=e.toHSL();return n.a+=t.value/100,n.a=o(n.a),i(n)}return null},fadeout:function(e,t){if("toHSL"in e){var n=e.toHSL();return n.a-=t.value/100,n.a=o(n.a),i(n)}return null},spin:function(e,t){if("toHSL"in e){var n=e.toHSL(),r=(n.h+t.value)%360;return n.h=r<0?360+r:r,i(n)}return null},replace:function(e,t,n){return e.is==="field"?e.toString+".replace("+t.toString()+", "+n.toString()+")":e.replace(t,n)},mix:function(t,n,r){var i=r.value/100,s=i*2-1,o=t.toHSL().a-n.toHSL().a,u=((s*o==-1?s:(s+o)/(1+s*o))+1)/2,a=1-u,f=[t.rgb[0]*u+n.rgb[0]*a,t.rgb[1]*u+n.rgb[1]*a,t.rgb[2]*u+n.rgb[2]*a],l=t.alpha*i+n.alpha*(1-i);return new e.Color(f,l)},greyscale:function(t){return this.desaturate(t,new e.Dimension(100))},"%":function(t){var n=Array.prototype.slice.call(arguments,1),r=t.value;for(var i=0;i4)return e("../../package.json").version.split(".");var n=JSON.parse(s.readFileSync(o.join(r,"../../package.json")));return n.version.split(".")}function l(e,t){var n={bold:[1,22],inverse:[7,27],underline:[4,24],yellow:[33,39],green:[32,39],red:[31,39],grey:[90,39]};return"["+n[t][0]+"m"+e+"["+n[t][1]+"m"}var i=e("util"),s=e("fs"),o=e("path"),a={version:u(),Parser:e("./parser").Parser,Renderer:e("./renderer").Renderer,tree:e("./tree"),RendererJS:e("./renderer_js"),default_reference:e("./torque-reference"),writeError:function(e,t){var n="",r=e.extract,s=[];t=t||{};if(t.silent)return;t.indent=t.indent||"";if(!("index"in e)||!r)return i.error(t.indent+(e.stack||e.message));typeof r[0]=="string"&&s.push(l(e.line-1+" "+r[0],"grey")),r[1]===""&&typeof r[2]=="undefined"&&(r[1]="¶"),s.push(e.line+" "+r[1].slice(0,e.column)+l(l(r[1][e.column],"bold")+r[1].slice(e.column+1),"yellow")),typeof r[2]=="string"&&s.push(l(e.line+1+" "+r[2],"grey")),s=t.indent+s.join("\n"+t.indent)+"\n",n=t.indent+n+l(e.message,"red"),e.filename&&(n+=l(" in ","red")+e.filename),i.error(n,s),e.callLine&&(i.error(l("from ","red")+(e.filename||"")),i.error(l(e.callLine,"grey")+" "+e.callExtract)),e.stack&&i.error(l(e.stack,"red"))}};e("./tree/call"),e("./tree/color"),e("./tree/comment"),e("./tree/definition"),e("./tree/dimension"),e("./tree/element"),e("./tree/expression"),e("./tree/filterset"),e("./tree/filter"),e("./tree/field"),e("./tree/keyword"),e("./tree/layer"),e("./tree/literal"),e("./tree/operation"),e("./tree/quoted"),e("./tree/imagefilter"),e("./tree/reference"),e("./tree/rule"),e("./tree/ruleset"),e("./tree/selector"),e("./tree/style"),e("./tree/url"),e("./tree/value"),e("./tree/variable"),e("./tree/zoom"),e("./tree/invalid"),e("./tree/fontset"),e("./tree/frame_offset"),e("./functions");for(var f in a)n[f]=a[f]}).call(this,e("_process"),"/lib/carto")},{"../../package.json":44,"./functions":1,"./parser":3,"./renderer":4,"./renderer_js":5,"./torque-reference":6,"./tree":7,"./tree/call":8,"./tree/color":9,"./tree/comment":10,"./tree/definition":11,"./tree/dimension":12,"./tree/element":13,"./tree/expression":14,"./tree/field":15,"./tree/filter":16,"./tree/filterset":17,"./tree/fontset":18,"./tree/frame_offset":19,"./tree/imagefilter":20,"./tree/invalid":21,"./tree/keyword":22,"./tree/layer":23,"./tree/literal":24,"./tree/operation":25,"./tree/quoted":26,"./tree/reference":27,"./tree/rule":28,"./tree/ruleset":29,"./tree/selector":30,"./tree/style":31,"./tree/url":32,"./tree/value":33,"./tree/variable":34,"./tree/zoom":35,_process:40,fs:36,path:39,util:42}],3:[function(e,t,n){(function(t){var r=n,i=e("./tree"),s=t._||e("underscore");r.Parser=function(t){function v(){u=l[o],a=r,c=r}function m(){l[o]=u,r=a,c=r}function g(){r>c&&(l[o]=l[o].slice(r-c),c=r)}function y(e){var t,i,s,u,a,f,p;if(e instanceof Function)return e.call(h.parsers);if(typeof e=="string")t=n.charAt(r)===e?e:null,s=1,g();else{g(),t=e.exec(l[o]);if(!t)return null;s=t[0].length}if(t){var d=r+=s;f=r+l[o].length-s;while(r=0&&r.charAt(i)!=="\n";i--)e.column++;return new Error(s("<%=filename%>:<%=line%>:<%=column%> <%=message%>").template(e))}var n,r,o,u,a,f,l,c,h,p=this,d=function(){};return this.env=t=t||{},this.env.filename=this.env.filename||null,this.env.inputs=this.env.inputs||{},h={extractErrorLine:w,parse:function(e){var s,u,a,h,d,v,m=[],g,b=null;r=o=c=f=0,l=[],n=e.replace(/\r\n/g,"\n"),t.filename&&(p.env.inputs[t.filename]=n);var w=!1;l=function(e){var t=0,r=/(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g,i=/\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,s=/"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g,o=0,u,a=e[0],f;for(var l=0,c,h;l0?"missing closing `}`":"missing opening `{`"}),e.map(function(e){return e.join("")})}([[]]);if(b)throw E(b);s=new i.Ruleset([],y(this.parsers.primary)),s.root=!0,s.toList=function(){var e,t,n;return function(e){e.error=function(t){e.errors||(e.errors=new Error("")),e.errors.message?e.errors.message+="\n"+E(t).message:e.errors.message=E(t).message},e.frames=e.frames||[];var t=this.flatten([],[],e);return t.sort(S),t}}();var S=function(e,t){var n=e.specificity,r=t.specificity;return n[0]!=r[0]?r[0]-n[0]:n[1]!=r[1]?r[1]-n[1]:n[2]!=r[2]?r[2]-n[2]:r[3]-n[3]};return s},parsers:{primary:function(){var e,t=[];while((e=y(this.rule)||y(this.ruleset)||y(this.comment))||y(/^[\s\n]+/)||(e=y(this.invalid)))e&&t.push(e);return t},invalid:function(){var e=y(/^[^;\n]*[;\n]/);if(e)return new i.Invalid(e,a)},comment:function(){var e;if(n.charAt(r)!=="/")return;if(n.charAt(r+1)==="/")return new i.Comment(y(/^\/\/.*/),!0);if(e=y(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/))return new i.Comment(e)},entities:{quoted:function(){if(n.charAt(r)!=='"'&&n.charAt(r)!=="'")return;var e=y(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/);if(e)return new i.Quoted(e[1]||e[2])},field:function(){if(!y("["))return;var e=y(/(^[^\]]+)/);if(!y("]"))return;if(e)return new i.Field(e[1])},comparison:function(){var e=y(/^=~|=|!=|<=|>=|<|>/);if(e)return e},keyword:function(){var e=y(/^[A-Za-z-]+[A-Za-z-0-9_]*/);if(e)return new i.Keyword(e)},call:function(){var e,t;if(!(e=/^([\w\-]+|%)\(/.exec(l[o])))return;e=e[1];if(e==="url")return null;r+=e.length,y("("),t=y(this.entities.arguments);if(!y(")"))return;if(e)return new i.Call(e,t,r)},arguments:function(){var e=[],t;while(t=y(this.expression)){e.push(t);if(!y(","))break}return e},literal:function(){return y(this.entities.dimension)||y(this.entities.keywordcolor)||y(this.entities.hexcolor)||y(this.entities.quoted)},url:function(){var e;if(n.charAt(r)!=="u"||!y(/^url\(/))return;return e=y(this.entities.quoted)||y(this.entities.variable)||y(/^[\-\w%@$\/.&=:;#+?~]+/)||"",y(")")?new i.URL(typeof e.value!="undefined"||e instanceof i.Variable?e:new i.Quoted(e)):new i.Invalid(e,a,"Missing closing ) in URL.")},variable:function(){var e,s=r;if(n.charAt(r)==="@"&&(e=y(/^@[\w-]+/)))return new i.Variable(e,s,t.filename)},hexcolor:function(){var e;if(n.charAt(r)==="#"&&(e=y(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/)))return new i.Color(e[1])},keywordcolor:function(){var e=l[o].match(/^[a-z]+/);if(e&&e[0]in i.Reference.data.colors)return new i.Color(i.Reference.data.colors[y(/^[a-z]+/)])},dimension:function(){var e=n.charCodeAt(r);if(e>57||e<45||e===47)return;var t=y(/^(-?\d*\.?\d+(?:[eE][-+]?\d+)?)(\%|\w+)?/);if(t)return new i.Dimension(t[1],t[2],a)}},variable:function(){var e;if(n.charAt(r)==="@"&&(e=y(/^(@[\w-]+)\s*:/)))return e[1]},entity:function(){return y(this.entities.call)||y(this.entities.literal)||y(this.entities.field)||y(this.entities.variable)||y(this.entities.url)||y(this.entities.keyword)},end:function(){return y(";")||b("}")},element:function(){var e=y(/^(?:[.#][\w\-]+|\*|Map)/);if(e)return new i.Element(e)},attachment:function(){var e=y(/^::([\w\-]+(?:\/[\w\-]+)*)/);if(e)return e[1]},selector:function(){var e,t,s,o=[],u,f=new i.Filterset,l,c=[],h=i.FrameOffset.none;segments=0,conditions=0;while((s=y(this.element))||(l=y(this.zoom))||(fo=y(this.frame_offset))||(u=y(this.filter))||(e=y(this.attachment))){segments++;if(s)o.push(s);else if(l)c.push(l),conditions++;else if(fo)h=fo,conditions++;else if(u){var p=f.add(u);if(p)throw E({message:p,index:r-1});conditions++}else{if(t)throw E({message:"Encountered second attachment name.",index:r-1});t=e}var d=n.charAt(r);if(d==="{"||d==="}"||d===";"||d===",")break}if(segments)return new i.Selector(f,c,h,o,t,conditions,a)},filter:function(){v();var e,n,r;if(!y("["))return;if(e=y(/^[a-zA-Z0-9\-_]+/)||y(this.entities.quoted)||y(this.entities.variable)||y(this.entities.keyword)||y(this.entities.field)){e instanceof i.Quoted&&(e=new i.Field(e.toString()));if((n=y(this.entities.comparison))&&(r=y(this.entities.quoted)||y(this.entities.variable)||y(this.entities.dimension)||y(this.entities.keyword)||y(this.entities.field))){if(!y("]"))throw E({message:"Missing closing ] of filter.",index:a-1});return e.is||(e=new i.Field(e)),new i.Filter(e,n,r,a,t.filename)}}},frame_offset:function(){v();var e,t;if(y(/^\[\s*frame-offset/g)&&(e=y(this.entities.comparison))&&(t=y(/^\d+/))&&y("]"))return i.FrameOffset(e,t,a)},zoom:function(){v();var e,t;if(y(/^\[\s*zoom/g)&&(e=y(this.entities.comparison))&&(t=y(this.entities.variable)||y(this.entities.dimension))&&y("]"))return new i.Zoom(e,t,a);m()},block:function(){var e;if(y("{")&&(e=y(this.primary))&&y("}"))return e},ruleset:function(){var e=[],t,n,r,s,o=[];v();while(t=y(this.selector)){e.push(t);while(y(this.comment));if(!y(","))break;while(y(this.comment));}if(t)while(y(this.comment));if(e.length>0&&(s=y(this.block))){if(e.length===1&&e[0].elements.length&&e[0].elements[0].value==="Map"){var u=new i.Ruleset(e,s);return u.isMap=!0,u}return new i.Ruleset(e,s)}m()},rule:function(){var e,s,o=n.charAt(r);v();if(o==="."||o==="#")return;if(e=y(this.variable)||y(this.property)){s=y(this.value);if(s&&y(this.end))return new i.Rule(e,s,a,t.filename);f=r,m()}},font:function(){var e=[],t=[],n,r,s;while(s=y(this.entity))t.push(s);e.push(new i.Expression(t));if(y(","))while(s=y(this.expression)){e.push(s);if(!y(","))break}return new i.Value(e)},value:function(){var e,t=[];while(e=y(this.expression)){t.push(e);if(!y(","))break}if(t.length>1)return new i.Value(t.map(function(e){return e.value[0]}));if(t.length===1)return new i.Value(t)},sub:function(){var e;if(y("(")&&(e=y(this.expression))&&y(")"))return e},multiplication:function(){var e,t,n,r;if(e=y(this.operand)){while((n=y("/")||y("*")||y("%"))&&(t=y(this.operand)))r=new i.Operation(n,[r||e,t],a);return r||e}},addition:function(){var e,t,s,o;if(e=y(this.multiplication)){while((s=y(/^[-+]\s+/)||n.charAt(r-1)!=" "&&(y("+")||y("-")))&&(t=y(this.multiplication)))o=new i.Operation(s,[o||e,t],a);return o||e}},operand:function(){return y(this.sub)||y(this.entity)},expression:function(){var e,t,n=[],r;while(e=y(this.addition)||y(this.entity))n.push(e);if(n.length>0)return new i.Expression(n)},property:function(){var e=y(/^(([a-z][-a-z_0-9]*\/)?\*?-?[-a-z_0-9]+)\s*:/);if(e)return e[1]}}},h}}).call(this,typeof global!="undefined"?global:typeof self!="undefined"?self:typeof window!="undefined"?window:{})},{"./tree":7,underscore:undefined}],4:[function(e,t,n){(function(n){function s(e,t,n,r){var i=t.filters,s=t.rules,o,u,a;for(var f=0;f'+t+"");break;case"name":case"description":case"legend":case"attribution":case"template":e.push(' ");break;case"format":e.push(' '+t+"");break;case"interactivity":e.push(' '+t.layer+""),e.push(' '+t.fields+"");break;default:"string"==typeof t?e.push(' "):"number"==typeof t?e.push(' '+t+""):"boolean"==typeof t&&e.push(' '+t+"")}return e},[]);N.length&&s.unshift("\n"+N.join("\n")+"\n\n");var C=r(T).map(function(e){return" "+e}).join("");return s.unshift('\n\n\n"),s.push(""),s.join("\n")},t.exports=i,t.exports.addRules=s,t.exports.inheritDefinitions=o,t.exports.sortStyles=a}).call(this,typeof global!="undefined"?global:typeof self!="undefined"?self:typeof window!="undefined"?window:{})},{"./index":2,underscore:undefined}],5:[function(require,module,exports){(function(global){(function(carto){function CartoCSS(e,t){this.options=t||{},this.imageURLs=[],e&&this.setStyle(e)}var tree=require("./tree"),_=global._||require("underscore");CartoCSS.Layer=function(e,t){this.options=t,this.shader=e},CartoCSS.Layer.prototype={fullName:function(){return this.shader.attachment},name:function(){return this.fullName().split("::")[0]},frames:function(){return this.shader.frames},attachment:function(){return this.fullName().split("::")[1]},eval:function(e){var t=this.shader[e];if(!t||!t.style)return;return t.style({},{zoom:0,"frame-offset":0})},getStyle:function(e,t){var n={};for(var r in this.shader)r!=="attachment"&&r!=="zoom"&&r!=="frames"&&r!=="symbolizers"&&(n[r]=this.shader[r].style(e,t));return n},getSymbolizers:function(){return this.shader.symbolizers},isVariable:function(){for(var e in this.shader)if(e!=="attachment"&&e!=="zoom"&&e!=="frames"&&e!=="symbolizers"&&!this.shader[e].constant)return!0;return!1},getShader:function(){return this.shader},filter:function(e,t,n){for(var r in this.shader){var i=this.shader[r](t,n);if(i)return!0}return!1},transformGeometry:function(e){return e},transformGeometries:function(e){return e}},CartoCSS.prototype={setStyle:function(e){var t=this.parse(e);if(!t)throw new Error(this.parse_env.errors);this.layers=t.map(function(e){return new CartoCSS.Layer(e)})},getLayers:function(){return this.layers},getDefault:function(){return this.findLayer({attachment:"__default__"})},findLayer:function(e){return _.find(this.layers,function(t){for(var n in e){var r=t[n];typeof r=="function"&&(r=r.call(t));if(e[n]!==r)return!1}return!0})},_createFn:function(e){var t=e.join("\n");return this.options.debug&&console.log(t),Function("data","ctx","var _value = null; "+t+"; return _value; ")},_compile:function(shader){typeof shader=="string"&&(shader=eval("(function() { return "+shader+"; })()")),this.shader_src=shader;for(var attr in shader){var c=mapper[attr];c&&(this.compiled[c]=eval("(function() { return shader[attr]; })();"))}},getImageURLs:function(){return this.imageURLs},parse:function(e){var t={frames:[],errors:[],error:function(e){this.errors.push(e)}};this.parse_env=t;var n=null;try{n=(new carto.Parser(t)).parse(e)}catch(r){t.errors.push(r.message);return}if(n){function i(e){return e.elements[0]+"::"+e.attachment}var s=n.toList(t);s.reverse();var o={};for(var u=0;u= minzoom - 1e-6 and scale < maxzoom + 1e-6"},maxzoom:{"default-value":"1.79769e+308",type:"float","default-meaning":"The layer will be visible at the maximum possible scale",doc:"The maximum scale denominator that this layer will be visible at. The default is the numeric limit of the C++ double type, which may vary slightly by system, but is likely a massive number like 1.79769e+308 and ensures that this layer will always be visible unless the value is reduced. A layer's visibility is determined by whether its status is true and if the Map scale >= minzoom - 1e-6 and scale < maxzoom + 1e-6"},queryable:{"default-value":!1,type:"boolean","default-meaning":"The layer will not be available for the direct querying of data values",doc:"This property was added for GetFeatureInfo/WMS compatibility and is rarely used. It is off by default meaning that in a WMS context the layer will not be able to be queried unless the property is explicitly set to true"},"clear-label-cache":{"default-value":!1,type:"boolean","default-meaning":"The renderer's collision detector cache (used for avoiding duplicate labels and overlapping markers) will not be cleared immediately before processing this layer",doc:"This property, by default off, can be enabled to allow a user to clear the collision detector cache before a given layer is processed. This may be desirable to ensure that a given layers data shows up on the map even if it normally would not because of collisions with previously rendered labels or markers"},"group-by":{"default-value":"",type:"string","default-meaning":"No special layer grouping will be used during rendering",doc:"https://github.com/mapnik/mapnik/wiki/Grouped-rendering"},"buffer-size":{"default-value":"0",type:"float","default-meaning":"No buffer will be used",doc:"Extra tolerance around the Layer extent (in pixels) used to when querying and (potentially) clipping the layer data during rendering"},"maximum-extent":{"default-value":"none",type:"bbox","default-meaning":"No clipping extent will be used",doc:"An extent to be used to limit the bounds used to query this specific layer data during rendering. Should be minx, miny, maxx, maxy in the coordinates of the Layer."}},symbolizers:{"*":{"image-filters":{css:"image-filters","default-value":"none","default-meaning":"no filters",type:"functions",functions:[["agg-stack-blur",2],["emboss",0],["blur",0],["gray",0],["sobel",0],["edge-detect",0],["x-gradient",0],["y-gradient",0],["invert",0],["sharpen",0],["colorize-alpha",-1],["color-to-alpha",1],["scale-hsla",8]],doc:"A list of image filters."},"comp-op":{css:"comp-op","default-value":"src-over","default-meaning":"add the current layer on top of other layers",doc:"Composite operation. This defines how this layer should behave relative to layers atop or below it.",type:["clear","src","dst","src-over","source-over","dst-over","src-in","dst-in","src-out","dst-out","src-atop","dst-atop","xor","plus","minus","multiply","screen","overlay","darken","lighten","lighter","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","contrast","invert","invert-rgb","grain-merge","grain-extract","hue","saturation","color","value"]},opacity:{css:"opacity",type:"float",doc:"An alpha value for the style (which means an alpha applied to all features in separate buffer and then composited back to main buffer)","default-value":1,"default-meaning":"no separate buffer will be used and no alpha will be applied to the style after rendering"}},map:{"background-color":{css:"background-color","default-value":"none","default-meaning":"transparent",type:"color",doc:"Map Background color"},"background-image":{css:"background-image",type:"uri","default-value":"","default-meaning":"transparent",doc:"An image that is repeated below all features on a map as a background.",description:"Map Background image"},srs:{css:"srs",type:"string","default-value":"+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs","default-meaning":"The proj4 literal of EPSG:4326 is assumed to be the Map's spatial reference and all data from layers within this map will be plotted using this coordinate system. If any layers do not declare an srs value then they will be assumed to be in the same srs as the Map and not transformations will be needed to plot them in the Map's coordinate space",doc:"Map spatial reference (proj4 string)"},"buffer-size":{css:"buffer-size","default-value":"0",type:"float","default-meaning":"No buffer will be used",doc:'Extra tolerance around the map (in pixels) used to ensure labels crossing tile boundaries are equally rendered in each tile (e.g. cut in each tile). Not intended to be used in combination with "avoid-edges".'},"maximum-extent":{css:"","default-value":"none",type:"bbox","default-meaning":"No clipping extent will be used",doc:"An extent to be used to limit the bounds used to query all layers during rendering. Should be minx, miny, maxx, maxy in the coordinates of the Map."},base:{css:"base","default-value":"","default-meaning":"This base path defaults to an empty string meaning that any relative paths to files referenced in styles or layers will be interpreted relative to the application process." +,type:"string",doc:"Any relative paths used to reference files will be understood as relative to this directory path if the map is loaded from an in memory object rather than from the filesystem. If the map is loaded from the filesystem and this option is not provided it will be set to the directory of the stylesheet."},"paths-from-xml":{css:"","default-value":!0,"default-meaning":"Paths read from XML will be interpreted from the location of the XML",type:"boolean",doc:"value to control whether paths in the XML will be interpreted from the location of the XML or from the working directory of the program that calls load_map()"},"minimum-version":{css:"","default-value":"none","default-meaning":"Mapnik version will not be detected and no error will be thrown about compatibility",type:"string",doc:"The minumum Mapnik version (e.g. 0.7.2) needed to use certain functionality in the stylesheet"},"font-directory":{css:"font-directory",type:"uri","default-value":"none","default-meaning":"No map-specific fonts will be registered",doc:"Path to a directory which holds fonts which should be registered when the Map is loaded (in addition to any fonts that may be automatically registered)."}},polygon:{fill:{css:"polygon-fill",type:"color","default-value":"rgba(128,128,128,1)","default-meaning":"gray and fully opaque (alpha = 1), same as rgb(128,128,128)",doc:"Fill color to assign to a polygon"},"fill-opacity":{css:"polygon-opacity",type:"float",doc:"The opacity of the polygon","default-value":1,"default-meaning":"opaque"},gamma:{css:"polygon-gamma",type:"float","default-value":1,"default-meaning":"fully antialiased",range:"0-1",doc:"Level of antialiasing of polygon edges"},"gamma-method":{css:"polygon-gamma-method",type:["power","linear","none","threshold","multiply"],"default-value":"power","default-meaning":"pow(x,gamma) is used to calculate pixel gamma, which produces slightly smoother line and polygon antialiasing than the 'linear' method, while other methods are usually only used to disable AA",doc:"An Antigrain Geometry specific rendering hint to control the quality of antialiasing. Under the hood in Mapnik this method is used in combination with the 'gamma' value (which defaults to 1). The methods are in the AGG source at https://github.com/mapnik/mapnik/blob/master/deps/agg/include/agg_gamma_functions.h"},clip:{css:"polygon-clip",type:"boolean","default-value":!0,"default-meaning":"geometry will be clipped to map bounds before rendering",doc:"geometries are clipped to map bounds by default for best rendering performance. In some cases users may wish to disable this to avoid rendering artifacts."},smooth:{css:"polygon-smooth",type:"float","default-value":0,"default-meaning":"no smoothing",range:"0-1",doc:"Smooths out geometry angles. 0 is no smoothing, 1 is fully smoothed. Values greater than 1 will produce wild, looping geometries."},"geometry-transform":{css:"polygon-geometry-transform",type:"functions","default-value":"none","default-meaning":"geometry will not be transformed",doc:"Allows transformation functions to be applied to the geometry.",functions:[["matrix",6],["translate",2],["scale",2],["rotate",3],["skewX",1],["skewY",1]]},"comp-op":{css:"polygon-comp-op","default-value":"src-over","default-meaning":"add the current symbolizer on top of other symbolizer",doc:"Composite operation. This defines how this symbolizer should behave relative to symbolizers atop or below it.",type:["clear","src","dst","src-over","dst-over","src-in","dst-in","src-out","dst-out","src-atop","dst-atop","xor","plus","minus","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","contrast","invert","invert-rgb","grain-merge","grain-extract","hue","saturation","color","value"]}},line:{stroke:{css:"line-color","default-value":"rgba(0,0,0,1)",type:"color","default-meaning":"black and fully opaque (alpha = 1), same as rgb(0,0,0)",doc:"The color of a drawn line"},"stroke-width":{css:"line-width","default-value":1,type:"float",doc:"The width of a line in pixels"},"stroke-opacity":{css:"line-opacity","default-value":1,type:"float","default-meaning":"opaque",doc:"The opacity of a line"},"stroke-linejoin":{css:"line-join","default-value":"miter",type:["miter","round","bevel"],doc:"The behavior of lines when joining"},"stroke-linecap":{css:"line-cap","default-value":"butt",type:["butt","round","square"],doc:"The display of line endings"},"stroke-gamma":{css:"line-gamma",type:"float","default-value":1,"default-meaning":"fully antialiased",range:"0-1",doc:"Level of antialiasing of stroke line"},"stroke-gamma-method":{css:"line-gamma-method",type:["power","linear","none","threshold","multiply"],"default-value":"power","default-meaning":"pow(x,gamma) is used to calculate pixel gamma, which produces slightly smoother line and polygon antialiasing than the 'linear' method, while other methods are usually only used to disable AA",doc:"An Antigrain Geometry specific rendering hint to control the quality of antialiasing. Under the hood in Mapnik this method is used in combination with the 'gamma' value (which defaults to 1). The methods are in the AGG source at https://github.com/mapnik/mapnik/blob/master/deps/agg/include/agg_gamma_functions.h"},"stroke-dasharray":{css:"line-dasharray",type:"numbers",doc:"A pair of length values [a,b], where (a) is the dash length and (b) is the gap length respectively. More than two values are supported for more complex patterns.","default-value":"none","default-meaning":"solid line"},"stroke-dashoffset":{css:"line-dash-offset",type:"numbers",doc:"valid parameter but not currently used in renderers (only exists for experimental svg support in Mapnik which is not yet enabled)","default-value":"none","default-meaning":"solid line"},"stroke-miterlimit":{css:"line-miterlimit",type:"float",doc:"The limit on the ratio of the miter length to the stroke-width. Used to automatically convert miter joins to bevel joins for sharp angles to avoid the miter extending beyond the thickness of the stroking path. Normally will not need to be set, but a larger value can sometimes help avoid jaggy artifacts.","default-value":4,"default-meaning":"Will auto-convert miters to bevel line joins when theta is less than 29 degrees as per the SVG spec: 'miterLength / stroke-width = 1 / sin ( theta / 2 )'"},clip:{css:"line-clip",type:"boolean","default-value":!0,"default-meaning":"geometry will be clipped to map bounds before rendering",doc:"geometries are clipped to map bounds by default for best rendering performance. In some cases users may wish to disable this to avoid rendering artifacts."},smooth:{css:"line-smooth",type:"float","default-value":0,"default-meaning":"no smoothing",range:"0-1",doc:"Smooths out geometry angles. 0 is no smoothing, 1 is fully smoothed. Values greater than 1 will produce wild, looping geometries."},offset:{css:"line-offset",type:"float","default-value":0,"default-meaning":"no offset",doc:"Offsets a line a number of pixels parallel to its actual path. Postive values move the line left, negative values move it right (relative to the directionality of the line)."},rasterizer:{css:"line-rasterizer",type:["full","fast"],"default-value":"full",doc:"Exposes an alternate AGG rendering method that sacrifices some accuracy for speed."},"geometry-transform":{css:"line-geometry-transform",type:"functions","default-value":"none","default-meaning":"geometry will not be transformed",doc:"Allows transformation functions to be applied to the geometry.",functions:[["matrix",6],["translate",2],["scale",2],["rotate",3],["skewX",1],["skewY",1]]},"comp-op":{css:"line-comp-op","default-value":"src-over","default-meaning":"add the current symbolizer on top of other symbolizer",doc:"Composite operation. This defines how this symbolizer should behave relative to symbolizers atop or below it.",type:["clear","src","dst","src-over","dst-over","src-in","dst-in","src-out","dst-out","src-atop","dst-atop","xor","plus","minus","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","contrast","invert","invert-rgb","grain-merge","grain-extract","hue","saturation","color","value"]}},markers:{file:{css:"marker-file",doc:"An SVG file that this marker shows at each placement. If no file is given, the marker will show an ellipse.","default-value":"","default-meaning":"An ellipse or circle, if width equals height",type:"uri"},opacity:{css:"marker-opacity",doc:"The overall opacity of the marker, if set, overrides both the opacity of both the fill and stroke","default-value":1,"default-meaning":"The stroke-opacity and fill-opacity will be used",type:"float"},"fill-opacity":{css:"marker-fill-opacity",doc:"The fill opacity of the marker","default-value":1,"default-meaning":"opaque",type:"float"},stroke:{css:"marker-line-color",doc:"The color of the stroke around a marker shape.","default-value":"black",type:"color"},"stroke-width":{css:"marker-line-width",doc:"The width of the stroke around a marker shape, in pixels. This is positioned on the boundary, so high values can cover the area itself.",type:"float"},"stroke-opacity":{css:"marker-line-opacity","default-value":1,"default-meaning":"opaque",doc:"The opacity of a line",type:"float"},placement:{css:"marker-placement",type:["point","line","interior"],"default-value":"point","default-meaning":"Place markers at the center point (centroid) of the geometry",doc:"Attempt to place markers on a point, in the center of a polygon, or if markers-placement:line, then multiple times along a line. 'interior' placement can be used to ensure that points placed on polygons are forced to be inside the polygon interior"},"multi-policy":{css:"marker-multi-policy",type:["each","whole","largest"],"default-value":"each","default-meaning":"If a feature contains multiple geometries and the placement type is either point or interior then a marker will be rendered for each",doc:"A special setting to allow the user to control rendering behavior for 'multi-geometries' (when a feature contains multiple geometries). This setting does not apply to markers placed along lines. The 'each' policy is default and means all geometries will get a marker. The 'whole' policy means that the aggregate centroid between all geometries will be used. The 'largest' policy means that only the largest (by bounding box areas) feature will get a rendered marker (this is how text labeling behaves by default)."},"marker-type":{css:"marker-type",type:["arrow","ellipse","rectangle"],"default-value":"ellipse",doc:"The default marker-type. If a SVG file is not given as the marker-file parameter, the renderer provides either an arrow or an ellipse (a circle if height is equal to width)"},width:{css:"marker-width","default-value":10,doc:"The width of the marker, if using one of the default types.",type:"float",expression:!0},height:{css:"marker-height","default-value":10,doc:"The height of the marker, if using one of the default types.",type:"float",expression:!0},fill:{css:"marker-fill","default-value":"blue",doc:"The color of the area of the marker.",type:"color"},"allow-overlap":{css:"marker-allow-overlap",type:"boolean","default-value":!1,doc:"Control whether overlapping markers are shown or hidden.","default-meaning":"Do not allow makers to overlap with each other - overlapping markers will not be shown."},"ignore-placement":{css:"marker-ignore-placement",type:"boolean","default-value":!1,"default-meaning":"do not store the bbox of this geometry in the collision detector cache",doc:"value to control whether the placement of the feature will prevent the placement of other features"},spacing:{css:"marker-spacing",doc:"Space between repeated labels","default-value":100,type:"float"},"max-error":{css:"marker-max-error",type:"float","default-value":.2,doc:"The maximum difference between actual marker placement and the marker-spacing parameter. Setting a high value can allow the renderer to try to resolve placement conflicts with other symbolizers."},transform:{css:"marker-transform",type:"functions",functions:[["matrix",6],["translate",2],["scale",2],["rotate",3],["skewX",1],["skewY",1]],"default-value":"","default-meaning":"No transformation",doc:"SVG transformation definition"},clip:{css:"marker-clip",type:"boolean","default-value":!0,"default-meaning":"geometry will be clipped to map bounds before rendering",doc:"geometries are clipped to map bounds by default for best rendering performance. In some cases users may wish to disable this to avoid rendering artifacts."},smooth:{css:"marker-smooth",type:"float","default-value":0,"default-meaning":"no smoothing",range:"0-1",doc:"Smooths out geometry angles. 0 is no smoothing, 1 is fully smoothed. Values greater than 1 will produce wild, looping geometries."},"geometry-transform":{css:"marker-geometry-transform",type:"functions","default-value":"none","default-meaning":"geometry will not be transformed",doc:"Allows transformation functions to be applied to the geometry.",functions:[["matrix",6],["translate",2],["scale",2],["rotate",3],["skewX",1],["skewY",1]]},"comp-op":{css:"marker-comp-op","default-value":"src-over","default-meaning":"add the current symbolizer on top of other symbolizer",doc:"Composite operation. This defines how this symbolizer should behave relative to symbolizers atop or below it.",type:["clear","src","dst","src-over","dst-over","src-in","dst-in","src-out","dst-out","src-atop","dst-atop","xor","plus","minus","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","contrast","invert","invert-rgb","grain-merge","grain-extract","hue","saturation","color","value"]}},shield:{name:{css:"shield-name",type:"string",expression:!0,serialization:"content",doc:'Value to use for a shield"s text label. Data columns are specified using brackets like [column_name]'},file:{css:"shield-file",required:!0,type:"uri","default-value":"none",doc:"Image file to render behind the shield text"},"face-name":{css:"shield-face-name",type:"string",validate:"font",doc:"Font name and style to use for the shield text","default-value":"",required:!0},"unlock-image":{css:"shield-unlock-image",type:"boolean",doc:"This parameter should be set to true if you are trying to position text beside rather than on top of the shield image","default-value":!1,"default-meaning":"text alignment relative to the shield image uses the center of the image as the anchor for text positioning."},size:{css:"shield-size",type:"float",doc:"The size of the shield text in pixels"},fill:{css:"shield-fill",type:"color",doc:"The color of the shield text"},placement:{css:"shield-placement",type:["point","line","vertex","interior"],"default-value":"point",doc:"How this shield should be placed. Point placement attempts to place it on top of points, line places along lines multiple times per feature, vertex places on the vertexes of polygons, and interior attempts to place inside of polygons."},"avoid-edges":{css:"shield-avoid-edges",doc:"Tell positioning algorithm to avoid labeling near intersection edges.",type:"boolean","default-value":!1},"allow-overlap":{css:"shield-allow-overlap",type:"boolean","default-value":!1,doc:"Control whether overlapping shields are shown or hidden.","default-meaning":"Do not allow shields to overlap with other map elements already placed."},"minimum-distance":{css:"shield-min-distance",type:"float","default-value":0,doc:"Minimum distance to the next shield symbol, not necessarily the same shield."},spacing:{css:"shield-spacing",type:"float","default-value":0,doc:"The spacing between repeated occurrences of the same shield on a line"},"minimum-padding":{css:"shield-min-padding","default-value":0,doc:"Determines the minimum amount of padding that a shield gets relative to other shields",type:"float"},"wrap-width":{css:"shield-wrap-width",type:"unsigned","default-value":0,doc:"Length of a chunk of text in characters before wrapping text"},"wrap-before":{css:"shield-wrap-before",type:"boolean","default-value":!1,doc:"Wrap text before wrap-width is reached. If false, wrapped lines will be a bit longer than wrap-width."},"wrap-character":{css:"shield-wrap-character",type:"string","default-value":" ",doc:"Use this character instead of a space to wrap long names."},"halo-fill":{css:"shield-halo-fill",type:"color","default-value":"#FFFFFF","default-meaning":"white",doc:"Specifies the color of the halo around the text."},"halo-radius":{css:"shield-halo-radius",doc:"Specify the radius of the halo in pixels","default-value":0,"default-meaning":"no halo",type:"float"},"character-spacing":{css:"shield-character-spacing",type:"unsigned","default-value":0,doc:"Horizontal spacing between characters (in pixels). Currently works for point placement only, not line placement."},"line-spacing":{css:"shield-line-spacing",doc:"Vertical spacing between lines of multiline labels (in pixels)",type:"unsigned"},dx:{css:"shield-text-dx",type:"float",doc:"Displace text within shield by fixed amount, in pixels, +/- along the X axis. A positive value will shift the text right","default-value":0},dy:{css:"shield-text-dy",type:"float",doc:"Displace text within shield by fixed amount, in pixels, +/- along the Y axis. A positive value will shift the text down","default-value":0},"shield-dx":{css:"shield-dx",type:"float",doc:"Displace shield by fixed amount, in pixels, +/- along the X axis. A positive value will shift the text right","default-value":0},"shield-dy":{css:"shield-dy",type:"float",doc:"Displace shield by fixed amount, in pixels, +/- along the Y axis. A positive value will shift the text down","default-value":0},opacity:{css:"shield-opacity",type:"float",doc:"(Default 1.0) - opacity of the image used for the shield","default-value":1},"text-opacity":{css:"shield-text-opacity",type:"float",doc:"(Default 1.0) - opacity of the text placed on top of the shield","default-value":1},"horizontal-alignment":{css:"shield-horizontal-alignment",type:["left","middle","right","auto"],doc:"The shield's horizontal alignment from its centerpoint","default-value":"auto"},"vertical-alignment":{css:"shield-vertical-alignment",type:["top","middle","bottom","auto"],doc:"The shield's vertical alignment from its centerpoint","default-value":"middle"},"text-transform":{css:"shield-text-transform",type:["none","uppercase","lowercase","capitalize"],doc:"Transform the case of the characters","default-value":"none"},"justify-alignment":{css:"shield-justify-alignment",type:["left","center","right","auto"],doc:"Define how text in a shield's label is justified","default-value":"auto"},clip:{css:"shield-clip",type:"boolean","default-value":!0,"default-meaning":"geometry will be clipped to map bounds before rendering",doc:"geometries are clipped to map bounds by default for best rendering performance. In some cases users may wish to disable this to avoid rendering artifacts."},"comp-op":{css:"shield-comp-op","default-value":"src-over","default-meaning":"add the current symbolizer on top of other symbolizer",doc:"Composite operation. This defines how this symbolizer should behave relative to symbolizers atop or below it.",type:["clear","src","dst","src-over","dst-over","src-in","dst-in","src-out","dst-out","src-atop","dst-atop","xor","plus","minus","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","contrast","invert","invert-rgb","grain-merge","grain-extract","hue","saturation","color","value"]}},"line-pattern":{file:{css:"line-pattern-file",type:"uri","default-value":"none",required:!0,doc:"An image file to be repeated and warped along a line"},clip:{css:"line-pattern-clip",type:"boolean","default-value":!0,"default-meaning":"geometry will be clipped to map bounds before rendering",doc:"geometries are clipped to map bounds by default for best rendering performance. In some cases users may wish to disable this to avoid rendering artifacts."},smooth:{css:"line-pattern-smooth",type:"float","default-value":0,"default-meaning":"no smoothing",range:"0-1",doc:"Smooths out geometry angles. 0 is no smoothing, 1 is fully smoothed. Values greater than 1 will produce wild, looping geometries."},"geometry-transform":{css:"line-pattern-geometry-transform",type:"functions","default-value":"none","default-meaning":"geometry will not be transformed",doc:"Allows transformation functions to be applied to the geometry.",functions:[["matrix",6],["translate",2],["scale",2],["rotate",3],["skewX",1],["skewY",1]]},"comp-op":{css:"line-pattern-comp-op","default-value":"src-over","default-meaning":"add the current symbolizer on top of other symbolizer",doc:"Composite operation. This defines how this symbolizer should behave relative to symbolizers atop or below it.",type:["clear","src","dst","src-over","dst-over","src-in","dst-in","src-out","dst-out","src-atop","dst-atop","xor","plus","minus","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","contrast","invert","invert-rgb","grain-merge","grain-extract","hue","saturation","color","value"]}},"polygon-pattern":{file:{css:"polygon-pattern-file",type:"uri","default-value":"none",required:!0,doc:"Image to use as a repeated pattern fill within a polygon"},alignment:{css:"polygon-pattern-alignment",type:["local","global"],"default-value":"local",doc:"Specify whether to align pattern fills to the layer or to the map."},gamma:{css:"polygon-pattern-gamma",type:"float","default-value":1,"default-meaning":"fully antialiased",range:"0-1",doc:"Level of antialiasing of polygon pattern edges"},opacity:{css:"polygon-pattern-opacity",type:"float",doc:"(Default 1.0) - Apply an opacity level to the image used for the pattern","default-value":1,"default-meaning":"The image is rendered without modifications"},clip:{css:"polygon-pattern-clip",type:"boolean","default-value":!0,"default-meaning":"geometry will be clipped to map bounds before rendering",doc:"geometries are clipped to map bounds by default for best rendering performance. In some cases users may wish to disable this to avoid rendering artifacts."},smooth:{css:"polygon-pattern-smooth",type:"float","default-value":0,"default-meaning":"no smoothing",range:"0-1",doc:"Smooths out geometry angles. 0 is no smoothing, 1 is fully smoothed. Values greater than 1 will produce wild, looping geometries."},"geometry-transform":{css:"polygon-pattern-geometry-transform",type:"functions","default-value":"none","default-meaning":"geometry will not be transformed",doc:"Allows transformation functions to be applied to the geometry.",functions:[["matrix",6],["translate",2],["scale",2],["rotate",3],["skewX",1],["skewY",1]]},"comp-op":{css:"polygon-pattern-comp-op","default-value":"src-over","default-meaning":"add the current symbolizer on top of other symbolizer",doc:"Composite operation. This defines how this symbolizer should behave relative to symbolizers atop or below it.",type:["clear","src","dst","src-over","dst-over","src-in","dst-in","src-out","dst-out","src-atop","dst-atop","xor","plus","minus","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","contrast","invert","invert-rgb","grain-merge","grain-extract","hue","saturation","color","value"]}},raster:{opacity:{css:"raster-opacity","default-value":1,"default-meaning":"opaque",type:"float",doc:"The opacity of the raster symbolizer on top of other symbolizers."},"filter-factor":{css:"raster-filter-factor","default-value":-1,"default-meaning":"Allow the datasource to choose appropriate downscaling.",type:"float",doc:"This is used by the Raster or Gdal datasources to pre-downscale images using overviews. Higher numbers can sometimes cause much better scaled image output, at the cost of speed."},scaling:{css:"raster-scaling",type:["near","fast","bilinear","bilinear8","bicubic","spline16","spline36","hanning","hamming","hermite","kaiser","quadric","catrom","gaussian","bessel","mitchell","sinc","lanczos","blackman"],"default-value":"near",doc:"The scaling algorithm used to making different resolution versions of this raster layer. Bilinear is a good compromise between speed and accuracy, while lanczos gives the highest quality."},"mesh-size":{css:"raster-mesh-size","default-value":16,"default-meaning":"Reprojection mesh will be 1/16 of the resolution of the source image",type:"unsigned",doc:"A reduced resolution mesh is used for raster reprojection, and the total image size is divided by the mesh-size to determine the quality of that mesh. Values for mesh-size larger than the default will result in faster reprojection but might lead to distortion."},"comp-op":{css:"raster-comp-op","default-value":"src-over","default-meaning":"add the current symbolizer on top of other symbolizer",doc:"Composite operation. This defines how this symbolizer should behave relative to symbolizers atop or below it.",type:["clear","src","dst","src-over","dst-over","src-in","dst-in","src-out","dst-out","src-atop","dst-atop","xor","plus","minus","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","contrast","invert","invert-rgb","grain-merge","grain-extract","hue","saturation","color","value"]}},point:{file:{css:"point-file",type:"uri",required:!1,"default-value":"none",doc:"Image file to represent a point"},"allow-overlap":{css:"point-allow-overlap",type:"boolean","default-value":!1,doc:"Control whether overlapping points are shown or hidden.","default-meaning":"Do not allow points to overlap with each other - overlapping markers will not be shown."},"ignore-placement":{css:"point-ignore-placement",type:"boolean","default-value":!1,"default-meaning":"do not store the bbox of this geometry in the collision detector cache",doc:"value to control whether the placement of the feature will prevent the placement of other features"},opacity:{css:"point-opacity",type:"float","default-value":1,"default-meaning":"Fully opaque",doc:"A value from 0 to 1 to control the opacity of the point"},placement:{css:"point-placement",type:["centroid","interior"],doc:"How this point should be placed. Centroid calculates the geometric center of a polygon, which can be outside of it, while interior always places inside of a polygon.","default-value":"centroid"},transform:{css:"point-transform",type:"functions",functions:[["matrix",6],["translate",2],["scale",2],["rotate",3],["skewX",1],["skewY",1]],"default-value":"","default-meaning":"No transformation",doc:"SVG transformation definition"},"comp-op":{css:"point-comp-op","default-value":"src-over","default-meaning":"add the current symbolizer on top of other symbolizer",doc:"Composite operation. This defines how this symbolizer should behave relative to symbolizers atop or below it.",type:["clear","src","dst","src-over","dst-over","src-in","dst-in","src-out","dst-out","src-atop","dst-atop","xor","plus","minus","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","contrast","invert","invert-rgb","grain-merge","grain-extract","hue","saturation","color","value"]}},text:{name:{css:"text-name",type:"string",expression:!0,required:!0,"default-value":"",serialization:"content",doc:"Value to use for a text label. Data columns are specified using brackets like [column_name]"},"face-name":{css:"text-face-name",type:"string",validate:"font",doc:"Font name and style to render a label in",required:!0},size:{css:"text-size",type:"float","default-value":10,doc:"Text size in pixels"},"text-ratio":{css:"text-ratio",doc:"Define the amount of text (of the total) present on successive lines when wrapping occurs","default-value":0,type:"unsigned"},"wrap-width":{css:"text-wrap-width",doc:"Length of a chunk of text in characters before wrapping text","default-value":0,type:"unsigned"},"wrap-before":{css:"text-wrap-before",type:"boolean","default-value":!1,doc:"Wrap text before wrap-width is reached. If false, wrapped lines will be a bit longer than wrap-width."},"wrap-character":{css:"text-wrap-character",type:"string","default-value":" ",doc:"Use this character instead of a space to wrap long text."},spacing:{css:"text-spacing",type:"unsigned",doc:"Distance between repeated text labels on a line (aka. label-spacing)"},"character-spacing":{css:"text-character-spacing",type:"float","default-value":0,doc:"Horizontal spacing adjustment between characters in pixels"},"line-spacing":{css:"text-line-spacing","default-value":0,type:"unsigned",doc:"Vertical spacing adjustment between lines in pixels"},"label-position-tolerance":{css:"text-label-position-tolerance","default-value":0,type:"unsigned",doc:"Allows the label to be displaced from its ideal position by a number of pixels (only works with placement:line)"},"max-char-angle-delta":{css:"text-max-char-angle-delta",type:"float","default-value":"22.5",doc:"The maximum angle change, in degrees, allowed between adjacent characters in a label. This value internally is converted to radians to the default is 22.5*math.pi/180.0. The higher the value the fewer labels will be placed around around sharp corners."},fill:{css:"text-fill",doc:"Specifies the color for the text","default-value":"#000000",type:"color"},opacity:{css:"text-opacity",doc:"A number from 0 to 1 specifying the opacity for the text","default-value":1,"default-meaning":"Fully opaque",type:"float"},"halo-fill":{css:"text-halo-fill",type:"color","default-value":"#FFFFFF","default-meaning":"white",doc:"Specifies the color of the halo around the text."},"halo-radius":{css:"text-halo-radius",doc:"Specify the radius of the halo in pixels","default-value":0,"default-meaning":"no halo",type:"float"},dx:{css:"text-dx",type:"float",doc:"Displace text by fixed amount, in pixels, +/- along the X axis. A positive value will shift the text right","default-value":0},dy:{css:"text-dy",type:"float",doc:"Displace text by fixed amount, in pixels, +/- along the Y axis. A positive value will shift the text down","default-value":0},"vertical-alignment":{css:"text-vertical-alignment",type:["top","middle","bottom","auto"],doc:"Position of label relative to point position.","default-value":"auto","default-meaning":'Default affected by value of dy; "bottom" for dy>0, "top" for dy<0.'},"avoid-edges":{css:"text-avoid-edges",doc:"Tell positioning algorithm to avoid labeling near intersection edges.","default-value":!1,type:"boolean"},"minimum-distance":{css:"text-min-distance",doc:"Minimum permitted distance to the next text symbolizer.",type:"float"},"minimum-padding":{css:"text-min-padding",doc:"Determines the minimum amount of padding that a text symbolizer gets relative to other text",type:"float"},"minimum-path-length":{css:"text-min-path-length",type:"float","default-value":0,"default-meaning":"place labels on all paths",doc:"Place labels only on paths longer than this value."},"allow-overlap":{css:"text-allow-overlap",type:"boolean","default-value":!1,doc:"Control whether overlapping text is shown or hidden.","default-meaning":"Do not allow text to overlap with other text - overlapping markers will not be shown."},orientation:{css:"text-orientation",type:"float",expression:!0,doc:"Rotate the text."},placement:{css:"text-placement",type:["point","line","vertex","interior"],"default-value":"point",doc:"Control the style of placement of a point versus the geometry it is attached to."},"placement-type":{css:"text-placement-type",doc:'Re-position and/or re-size text to avoid overlaps. "simple" for basic algorithm (using text-placements string,) "dummy" to turn this feature off.',type:["dummy","simple"],"default-value":"dummy"},placements:{css:"text-placements",type:"string","default-value":"",doc:'If "placement-type" is set to "simple", use this "POSITIONS,[SIZES]" string. An example is `text-placements: "E,NE,SE,W,NW,SW";` '},"text-transform":{css:"text-transform",type:["none","uppercase","lowercase","capitalize"],doc:"Transform the case of the characters","default-value":"none"},"horizontal-alignment":{css:"text-horizontal-alignment",type:["left","middle","right","auto"],doc:"The text's horizontal alignment from its centerpoint","default-value":"auto"},"justify-alignment":{css:"text-align",type:["left","right","center","auto"],doc:"Define how text is justified","default-value":"auto","default-meaning":"Auto alignment means that text will be centered by default except when using the `placement-type` parameter - in that case either right or left justification will be used automatically depending on where the text could be fit given the `text-placements` directives"},clip:{css:"text-clip" +,type:"boolean","default-value":!0,"default-meaning":"geometry will be clipped to map bounds before rendering",doc:"geometries are clipped to map bounds by default for best rendering performance. In some cases users may wish to disable this to avoid rendering artifacts."},"comp-op":{css:"text-comp-op","default-value":"src-over","default-meaning":"add the current symbolizer on top of other symbolizer",doc:"Composite operation. This defines how this symbolizer should behave relative to symbolizers atop or below it.",type:["clear","src","dst","src-over","dst-over","src-in","dst-in","src-out","dst-out","src-atop","dst-atop","xor","plus","minus","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","contrast","invert","invert-rgb","grain-merge","grain-extract","hue","saturation","color","value"]}},building:{fill:{css:"building-fill","default-value":"#FFFFFF",doc:"The color of the buildings walls.",type:"color"},"fill-opacity":{css:"building-fill-opacity",type:"float",doc:"The opacity of the building as a whole, including all walls.","default-value":1},height:{css:"building-height",doc:"The height of the building in pixels.",type:"float",expression:!0,"default-value":"0"}},torque:{"-torque-clear-color":{css:"-torque-clear-color",type:"color","default-value":"rgba(255, 255, 255, 0)","default-meaning":"full clear",doc:"color used to clear canvas on each frame"},"-torque-frame-count":{css:"-torque-frame-count","default-value":"128",type:"float","default-meaning":"the data is broken into 128 time frames",doc:"Number of animation steps/frames used in the animation. If the data contains a fewere number of total frames, the lesser value will be used."},"-torque-resolution":{css:"-torque-resolution","default-value":"2",type:"float","default-meaning":"",doc:"Spatial resolution in pixels. A resolution of 1 means no spatial aggregation of the data. Any other resolution of N results in spatial aggregation into cells of NxN pixels. The value N must be power of 2"},"-torque-animation-duration":{css:"-torque-animation-duration","default-value":"30",type:"float","default-meaning":"the animation lasts 30 seconds",doc:"Animation duration in seconds"},"-torque-aggregation-function":{css:"-torque-aggregation-function","default-value":"count(cartodb_id)",type:"string","default-meaning":"the value for each cell is the count of points in that cell",doc:"A function used to calculate a value from the aggregate data for each cell. See -torque-resolution"},"-torque-time-attribute":{css:"-torque-time-attribute","default-value":"time",type:"string","default-meaning":"the data column in your table that is of a time based type",doc:"The table column that contains the time information used create the animation"},"-torque-data-aggregation":{css:"-torque-data-aggregation","default-value":"linear",type:["linear","cumulative"],"default-meaning":"previous values are discarded",doc:"A linear animation will discard previous values while a cumulative animation will accumulate them until it restarts"}}},colors:{aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],grey:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50],transparent:[0,0,0,0]},filter:{value:["true","false","null","point","linestring","polygon","collection"]}};t.exports={version:{latest:r,"2.1.1":r}}},{}],7:[function(e,t,n){typeof t!="undefined"&&(t.exports.find=function(e,t){for(var n=0,r;n255?255:e<0?0:e).toString(16),e.length===1?"0"+e:e}).join("")},operate:function(t,n,r){var i=[];r instanceof e.Color||(r=r.toColor());for(var s=0;s<3;s++)i[s]=e.operate(n,this.rgb[s],r.rgb[s]);return new e.Color(i)},toHSL:function(){var e=this.rgb[0]/255,t=this.rgb[1]/255,n=this.rgb[2]/255,r=this.alpha,i=Math.max(e,t,n),s=Math.min(e,t,n),o,u,a=(i+s)/2,f=i-s;if(i===s)o=u=0;else{u=a>.5?f/(2-i-s):f/(i+s);switch(i){case e:o=(t-n)/f+(t"},ev:function(){return this}}})(e("../tree"))},{"../tree":7}],11:[function(e,t,n){(function(t){(function(n){function s(e){function t(e){return e[1].toUpperCase()}return e.charAt(0).toUpperCase()+e.slice(1).replace(/\-./,t)+"Symbolizer"}function o(e){return e.sort(function(e,t){return e[1]-t[1]}).map(function(e){return e[0]})}var r=e("assert"),i=t._||e("underscore");n.Definition=function(t,i){this.elements=t.elements,r.ok(t.filters instanceof n.Filterset),this.rules=i,this.ruleIndex={};for(var s=0;s"+w+"\n":i+=">\n")}return!h||!i?"":" \n"+i+" \n"},n.Definition.prototype.collectSymbolizers=function(e,t){var n={},r;for(var i=t;i1?new e.Expression(this.value.map(function(e){return e.ev(t)})):this.value[0].ev(t)},toString:function(e){return this.value.map(function(t){return t.toString(e)}).join(" ")}}})(e("../tree"))},{"../tree":7}],15:[function(e,t,n){(function(e){e.Field=function(t){this.value=t||""},e.Field.prototype={is:"field",toString:function(){return"["+this.value+"]"},ev:function(){return this}}})(e("../tree"))},{"../tree":7}],16:[function(e,t,n){(function(e){e.Filter=function(t,n,r,i,s){this.key=t,this.op=n,this.val=r,this.index=i,this.filename=s,this.id=this.key+this.op+this.val};var t={"<":[" < ","numeric"],">":[" > ","numeric"],"=":[" = ","both"],"!=":[" != ","both"],"<=":[" <= ","numeric"],">=":[" >= ","numeric"],"=~":[".match(","string",")"]};e.Filter.prototype.ev=function(e){return this.key=this.key.ev(e),this.val=this.val.ev(e),this},e.Filter.prototype.toXML=function(n){e.Reference.data.filter&&(this.key.is==="keyword"&&-1===e.Reference.data.filter.value.indexOf(this.key.toString())&&n.error({message:this.key.toString()+" is not a valid keyword in a filter expression",index:this.index,filename:this.filename}),this.val.is==="keyword"&&-1===e.Reference.data.filter.value.indexOf(this.val.toString())&&n.error({message:this.val.toString()+" is not a valid keyword in a filter expression",index:this.index,filename:this.filename}));var r=this.key.toString(!1),i=this.val.toString(this.val.is=="string");return(t[this.op][1]=="numeric"&&isNaN(i)&&this.val.is!=="field"||t[this.op][1]=="string"&&i[0]!="'")&&n.error({message:'Cannot use operator "'+this.op+'" with value '+this.val,index:this.index,filename:this.filename}),r+t[this.op][0]+i+(t[this.op][2]||"")},e.Filter.prototype.toString=function(){return"["+this.id+"]"}})(e("../tree"))},{"../tree":7}],17:[function(e,t,n){(function(t){var n=e("../tree"),r=t._||e("underscore");n.Filterset=function(){this.filters={}},n.Filterset.prototype.toXML=function(e){var t=[];for(var n in this.filters)t.push("("+this.filters[n].toXML(e).trim()+")");return t.length?" "+t.join(" and ")+"\n":""},n.Filterset.prototype.toString=function(){var e=[];for(var t in this.filters)e.push(this.filters[t].id);return e.sort().join(" ")},n.Filterset.prototype.ev=function(e){for(var t in this.filters)this.filters[t].ev(e);return this},n.Filterset.prototype.clone=function(){var e=new n.Filterset;for(var t in this.filters)e.filters[t]=this.filters[t];return e},n.Filterset.prototype.cloneWith=function(e){var t=[];for(var r in e.filters){var i=this.addable(e.filters[r]);if(i===!1)return!1;i===!0&&t.push(e.filters[r])}if(!t.length)return null;var s=new n.Filterset;for(r in this.filters)s.filters[r]=this.filters[r];while(r=t.shift())s.add(r);return s},n.Filterset.prototype.toJS=function(e){var t={"=":"==="};return r.map(this.filters,function(e){var n=e.op;n in t&&(n=t[n]);var r=e.val;e._val!==undefined&&(r=e._val.toString(!0));var i="data";return i+"."+e.key.value+" "+n+" "+(r.is==="string"?"'"+r+"'":r)}).join(" && ")},n.Filterset.prototype.addable=function(e){var t=e.key.toString(),n=e.val.toString();n.match(/^[0-9]+(\.[0-9]*)?$/)&&(n=parseFloat(n));switch(e.op){case"=":if(this.filters[t+"="]!==undefined)return this.filters[t+"="].val.toString()!=n?!1:null;if(this.filters[t+"!="+n]!==undefined)return!1;if(this.filters[t+">"]!==undefined&&this.filters[t+">"].val>=n)return!1;if(this.filters[t+"<"]!==undefined&&this.filters[t+"<"].val<=n)return!1;if(this.filters[t+">="]!==undefined&&this.filters[t+">="].val>n)return!1;if(this.filters[t+"<="]!==undefined&&this.filters[t+"<="].val"]!==undefined&&this.filters[t+">"].val>=n)return null;if(this.filters[t+"<"]!==undefined&&this.filters[t+"<"].val<=n)return null;if(this.filters[t+">="]!==undefined&&this.filters[t+">="].val>n)return null;if(this.filters[t+"<="]!==undefined&&this.filters[t+"<="].val":if(t+"="in this.filters)return this.filters[t+"="].val<=n?!1:null;if(this.filters[t+"<"]!==undefined&&this.filters[t+"<"].val<=n)return!1;if(this.filters[t+"<="]!==undefined&&this.filters[t+"<="].val<=n)return!1;if(this.filters[t+">"]!==undefined&&this.filters[t+">"].val>=n)return null;if(this.filters[t+">="]!==undefined&&this.filters[t+">="].val>n)return null;return!0;case">=":if(this.filters[t+"="]!==undefined)return this.filters[t+"="].val"]!==undefined&&this.filters[t+">"].val>=n)return null;if(this.filters[t+">="]!==undefined&&this.filters[t+">="].val>=n)return null;return!0;case"<":if(this.filters[t+"="]!==undefined)return this.filters[t+"="].val>=n?!1:null;if(this.filters[t+">"]!==undefined&&this.filters[t+">"].val>=n)return!1;if(this.filters[t+">="]!==undefined&&this.filters[t+">="].val>=n)return!1;if(this.filters[t+"<"]!==undefined&&this.filters[t+"<"].val<=n)return null;if(this.filters[t+"<="]!==undefined&&this.filters[t+"<="].val"]!==undefined&&this.filters[t+">"].val>=n)return!1;if(this.filters[t+">="]!==undefined&&this.filters[t+">="].val>n)return!1;if(this.filters[t+"<"]!==undefined&&this.filters[t+"<"].val<=n)return null;if(this.filters[t+"<="]!==undefined&&this.filters[t+"<="].val<=n)return null;return!0}},n.Filterset.prototype.conflict=function(e){var t=e.key.toString(),n=e.val.toString();return isNaN(parseFloat(n))||(n=parseFloat(n)),e.op==="="&&this.filters[t+"="]!==undefined&&n!=this.filters[t+"="].val.toString()||e.op==="!="&&this.filters[t+"="]!==undefined&&n==this.filters[t+"="].val.toString()||e.op==="="&&this.filters[t+"!="]!==undefined&&n==this.filters[t+"!="].val.toString()?e.toString()+" added to "+this.toString()+" produces an invalid filter":!1},n.Filterset.prototype.add=function(e,t){var n=e.key.toString(),r,i=e.op,s=this.conflict(e),o;if(s)return s;if(i==="="){for(var u in this.filters)this.filters[u].key==n&&delete this.filters[u];this.filters[n+"="]=e}else if(i==="!=")this.filters[n+"!="+e.val]=e;else if(i==="=~")this.filters[n+"=~"+e.val]=e;else if(i===">"){for(var a in this.filters)this.filters[a].key==n&&this.filters[a].val<=e.val&&delete this.filters[a];this.filters[n+">"]=e}else if(i===">="){for(var f in this.filters)o=+this.filters[f].val.toString(),this.filters[f].key==n&&o",this.filters[n+">"]=e):this.filters[n+">="]=e}else if(i==="<"){for(var l in this.filters)o=+this.filters[l].val.toString(),this.filters[l].key==n&&o>=e.val&&delete this.filters[l];this.filters[n+"<"]=e}else if(i==="<="){for(var c in this.filters)o=+this.filters[c].val.toString(),this.filters[c].key==n&&o>e.val&&delete this.filters[c];this.filters[n+"!="+e.val]!==undefined?(delete this.filters[n+"!="+e.val],e.op="<",this.filters[n+"<"]=e):this.filters[n+"<="]=e}}}).call(this,typeof global!="undefined"?global:typeof self!="undefined"?self:typeof window!="undefined"?window:{})},{"../tree":7,underscore:undefined}],18:[function(e,t,n){(function(e){e._getFontSet=function(t,n){var r=n.join("");if(t._fontMap&&t._fontMap[r])return t._fontMap[r];var i=new e.FontSet(t,n);return t.effects.push(i),t._fontMap||(t._fontMap={}),t._fontMap[r]=i,i},e.FontSet=function(t,n){this.fonts=n,this.name="fontset-"+t.effects.length},e.FontSet.prototype.toXML=function(e){return'\n'+this.fonts.map(function(e){return' '}).join("\n")+"\n"}})(e("../tree"))},{"../tree":7}],19:[function(e,t,n){var r=e("../tree");r.FrameOffset=function(e,t,n){t=parseInt(t,10);if(t>r.FrameOffset.max||t<=0)throw{message:"Only frame-offset levels between 1 and "+r.FrameOffset.max+" supported.",index:n};if(e!=="=")throw{message:"only = operator is supported for frame-offset",index:n};return t},r.FrameOffset.max=32,r.FrameOffset.none=0},{"../tree":7}],20:[function(e,t,n){(function(e){e.ImageFilter=function(t,n){this.filter=t,this.args=n||null},e.ImageFilter.prototype={is:"imagefilter",ev:function(){return this},toString:function(){return this.args?this.filter+"("+this.args.join(",")+")":this.filter}}})(e("../tree"))},{"../tree":7}],21:[function(e,t,n){(function(e){e.Invalid=function(t,n,r){this.chunk=t,this.index=n,this.type="syntax",this.message=r||"Invalid code: "+this.chunk},e.Invalid.prototype.is="invalid",e.Invalid.prototype.ev=function(e){return e.error({chunk:this.chunk,index:this.index,type:"syntax",message:this.message||"Invalid code: "+this.chunk}),{is:"undefined"}}})(e("../tree"))},{"../tree":7}],22:[function(e,t,n){(function(e){e.Keyword=function(t){this.value=t;var n={transparent:"color","true":"boolean","false":"boolean"};this.is=n[t]?n[t]:"keyword"},e.Keyword.prototype={ev:function(){return this},toString:function(){return this.value}}})(e("../tree"))},{"../tree":7}],23:[function(e,t,n){(function(e){e.LayerXML=function(t,n){var r=[];for(var i in t.Datasource)r.push('");var s="";for(var o in t.properties)o==="minzoom"?s+=' maxzoom="'+e.Zoom.ranges[t.properties[o]]+'"\n':o==="maxzoom"?s+=' minzoom="'+e.Zoom.ranges[t.properties[o]+1]+'"\n':s+=" "+o+'="'+t.properties[o]+'"\n';return'\n "+n.reverse().map(function(e){return""+e+""}).join("\n ")+(r.length?"\n \n "+r.join("\n ")+"\n \n":"")+" \n"}})(e("../tree"))},{"../tree":7}],24:[function(e,t,n){(function(e){e.Literal=function(t){this.value=t||"",this.is="field"},e.Literal.prototype={toString:function(){return this.value},ev:function(){return this}}})(e("../tree"))},{"../tree":7}],25:[function(e,t,n){(function(e){e.Operation=function(t,n,r){this.op=t.trim(),this.operands=n,this.index=r},e.Operation.prototype.is="operation",e.Operation.prototype.ev=function(t){var n=this.operands[0].ev(t),r=this.operands[1].ev(t),i;return n.is==="undefined"||r.is==="undefined"?{is:"undefined",value:"undefined"}:(n instanceof e.Dimension&&r instanceof e.Color&&(this.op==="*"||this.op==="+"?(i=r,r=n,n=i):t.error({name:"OperationError",message:"Can't substract or divide a color from a number",index:this.index})),n instanceof e.Quoted&&r instanceof e.Quoted&&this.op!=="+"?(t.error({message:"Can't subtract, divide, or multiply strings.",index:this.index,type:"runtime",filename:this.filename}),{is:"undefined",value:"undefined"}):n instanceof e.Field||r instanceof e.Field||n instanceof e.Literal||r instanceof e.Literal?n.is==="color"||r.is==="color"?(t.error({message:"Can't subtract, divide, or multiply colors in expressions.",index:this.index,type:"runtime",filename:this.filename}),{is:"undefined",value:"undefined"}):new e.Literal(n.ev(t).toString(!0)+this.op+r.ev(t).toString(!0)):n.operate===undefined?(t.error({message:"Cannot do math with type "+n.is+".",index:this.index,type:"runtime",filename:this.filename}),{is:"undefined",value:"undefined"}):n.operate(t,this.op,r))},e.operate=function(e,t,n){switch(e){case"+":return t+n;case"-":return t-n;case"*":return t*n;case"%":return t%n;case"/":return t/n}}})(e("../tree"))},{"../tree":7}],26:[function(e,t,n){(function(e){e.Quoted=function(t){this.value=t||""},e.Quoted.prototype={is:"string",toString:function(e){var t=this.value.replace(/&/g,"&"),n=t.replace(/\'/g,"\\'").replace(/\"/g,""").replace(//g,">");return e===!0?"'"+n+"'":t},ev:function(){return this},operate:function(t,n,r){return new e.Quoted(e.operate(n,this.toString(),r.toString(this.contains_field)))}}})(e("../tree"))},{"../tree":7}],27:[function(e,t,n){(function(t){(function(n){function s(e){var t={};for(var n in e.symbolizers)for(var r in e.symbolizers[n])e.symbolizers[n][r].hasOwnProperty("css")&&(t[e.symbolizers[n][r].css]=[e.symbolizers[n][r],n,r]);return t}function o(e){var t={};for(var n in e.symbolizers)for(var r in e.symbolizers[n])if(e.symbolizers[n][r].type==="functions")for(var i=0;i1){var f=e._getFontSet(n,this.value.value);return'fontset-name="'+f.name+'"'}return r?this.value.toString(n,this.name,i):e.Reference.selectorName(this.name)+'="'+this.value.toString(n,this.name)+'"'},e.Rule.prototype.ev=function(t){return new e.Rule(this.name,this.value.ev(t),this.index,this.filename)}})(e("../tree"))},{"../tree":7}],29:[function(e,t,n){(function(e){e.Ruleset=function(t,n){this.selectors=t,this.rules=n,this._lookups={}},e.Ruleset.prototype={is:"ruleset",ev:function(t){var n,r=new e.Ruleset(this.selectors,this.rules.slice(0));r.root=this.root,t.frames.unshift(r);for(n=0,rule;n1?Array.prototype.push.apply(r,i.find(new e.Selector(null,null,null,t.elements.slice(1)),n)):r.push(i);break}}}),this._lookups[o]=r)},evZooms:function(t){for(var n=0;n\n"+v+""}})(e("../tree"))}).call(this,typeof global!="undefined"?global:typeof self!="undefined"?self:typeof window!="undefined"?window:{})},{"../tree":7,underscore:undefined}],32:[function(e,t,n){(function(e){e.URL=function(t,n){this.value=t,this.paths=n},e.URL.prototype={is:"uri",toString:function(){return this.value.toString()},ev:function(t){return new e.URL(this.value.ev(t),this.paths)}}})(e("../tree"))},{"../tree":7}],33:[function(e,t,n){(function(e){e.Value=function(t){this.value=t},e.Value.prototype={is:"value",ev:function(t){return this.value.length===1?this.value[0].ev(t):new e.Value(this.value.map(function(e){return e.ev(t)}))},toString:function(e,t,n,r){return this.value.map(function(t){return t.toString(e,r)}).join(n||", ")},clone:function(){var t=Object.create(e.Value.prototype);return Array.isArray(t)?t.value=this.value.slice():t.value=this.value,t.is=this.is,t},toJS:function(e){var t=this.ev(e),n=t.toString();return t.is==="color"||t.is==="uri"||t.is==="string"||t.is==="keyword"?n="'"+n+"'":t.is==="field"?n=n.replace(/\[(.*)\]/g,"data['$1']"):t.is==="call"&&(n=JSON.stringify({name:t.name,args:t.args})),"_value = "+n+";"}}})(e("../tree"))},{"../tree":7}],34:[function(e,t,n){(function(e){e.Variable=function(t,n,r){this.name=t,this.index=n,this.filename=r},e.Variable.prototype={is:"variable",toString:function(){return this.name},ev:function(e){var t,n,r=this.name;if(this._css)return this._css;var i=e.frames.filter(function(e){return e.name==this.name}.bind(this));return i.length?i[0].value.ev(e):(e.error({message:"variable "+this.name+" is undefined",index:this.index,type:"runtime",filename:this.filename}),{is:"undefined",value:"undefined"})}}})(e("../tree"))},{"../tree":7}],35:[function(e,t,n){var r=e("../tree");r.Zoom=function(e,t,n){this.op=e,this.value=t,this.index=n},r.Zoom.prototype.setZoom=function(e){return this.zoom=e,this},r.Zoom.prototype.ev=function(e){var t=0,n=Infinity,i=parseInt(this.value.ev(e).toString(),10),s=0;(i>r.Zoom.maxZoom||i<0)&&e.error({message:"Only zoom levels between 0 and "+r.Zoom.maxZoom+" supported.",index:this.index});switch(this.op){case"=":return this.zoom=1<":t=i+1;break;case">=":t=i;break;case"<":n=i-1;break;case"<=":n=i}for(var o=0;o<=r.Zoom.maxZoom;o++)o>=t&&o<=n&&(s|=1<0&&e.push(" "+r.Zoom.ranges[t]+"\n"),n<22&&e.push(" "+r.Zoom.ranges[n+1]+"\n")}return e},r.Zoom.prototype.toString=function(){var e="";for(var t=0;t<=r.Zoom.maxZoom;t++)e+=this.zoom&1<=0;u--)if(n[u]!=s[u])return!1;for(u=n.length-1;u>=0;u--){o=n[u];if(!h(e[o],t[o]))return!1}return!0}function v(e,t){return!e||!t?!1:Object.prototype.toString.call(t)=="[object RegExp]"?t.test(e):e instanceof t?!0:t.call({},e)===!0?!0:!1}function m(e,t,n,i){var s;r.isString(n)&&(i=n,n=null);try{t()}catch(o){s=o}i=(n&&n.name?" ("+n.name+").":".")+(i?" "+i:"."),e&&!s&&l(s,n,"Missing expected exception"+i),!e&&v(s,n)&&l(s,n,"Got unwanted exception"+i);if(e&&s&&n&&!v(s,n)||!e&&s)throw s}var r=e("util/"),i=Array.prototype.slice,s=Object.prototype.hasOwnProperty,o=t.exports=c;o.AssertionError=function(t){this.name="AssertionError",this.actual=t.actual,this.expected=t.expected,this.operator=t.operator,t.message?(this.message=t.message,this.generatedMessage=!1):(this.message=f(this),this.generatedMessage=!0);var n=t.stackStartFunction||l;if(Error.captureStackTrace)Error.captureStackTrace(this,n);else{var r=new Error;if(r.stack){var i=r.stack,s=n.name,o=i.indexOf("\n"+s);if(o>=0){var u=i.indexOf("\n",o+1);i=i.substring(u+1)}this.stack=i}}},r.inherits(o.AssertionError,Error),o.fail=l,o.ok=c,o.equal=function(t,n,r){t!=n&&l(t,n,r,"==",o.equal)},o.notEqual=function(t,n,r){t==n&&l(t,n,r,"!=",o.notEqual)},o.deepEqual=function(t,n,r){h(t,n)||l(t,n,r,"deepEqual",o.deepEqual)},o.notDeepEqual=function(t,n,r){h(t,n)&&l(t,n,r,"notDeepEqual",o.notDeepEqual)},o.strictEqual=function(t,n,r){t!==n&&l(t,n,r,"===",o.strictEqual)},o.notStrictEqual=function(t,n,r){t===n&&l(t,n,r,"!==",o.notStrictEqual)},o.throws=function(e,t,n){m.apply(this,[!0].concat(i.call(arguments)))},o.doesNotThrow=function(e,t){m.apply(this,[!1].concat(i.call(arguments)))},o.ifError=function(e){if(e)throw e};var g=Object.keys||function(e){var t=[];for(var n in e)s.call(e,n)&&t.push(n);return t}},{"util/":42}],38:[function(e,t,n){typeof Object.create=="function"?t.exports=function(t,n){t.super_=n,t.prototype=Object.create(n.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}})}:t.exports=function(t,n){t.super_=n;var r=function(){};r.prototype=n.prototype,t.prototype=new r,t.prototype.constructor=t}},{}],39:[function(e,t,n){(function(e){function t(e,t){var n=0;for(var r=e.length-1;r>=0;r--){var i=e[r];i==="."?e.splice(r,1):i===".."?(e.splice(r,1),n++):n&&(e.splice(r,1),n--)}if(t)for(;n--;n)e.unshift("..");return e}function s(e,t){if(e.filter)return e.filter(t);var n=[];for(var r=0;r=-1&&!r;i--){var o=i>=0?arguments[i]:e.cwd();if(typeof o!="string")throw new TypeError("Arguments to path.resolve must be strings");if(!o)continue;n=o+"/"+n,r=o.charAt(0)==="/"}return n=t(s(n.split("/"),function(e){return!!e}),!r).join("/"),(r?"/":"")+n||"."},n.normalize=function(e){var r=n.isAbsolute(e),i=o(e,-1)==="/";return e=t(s(e.split("/"),function(e){return!!e}),!r).join("/"),!e&&!r&&(e="."),e&&i&&(e+="/"),(r?"/":"")+e},n.isAbsolute=function(e){return e.charAt(0)==="/"},n.join=function(){var e=Array.prototype.slice.call(arguments,0);return n.normalize(s(e,function(e,t){if(typeof e!="string")throw new TypeError("Arguments to path.join must be strings");return e}).join("/"))},n.relative=function(e,t){function r(e){var t=0;for(;t=0;n--)if(e[n]!=="")break;return t>n?[]:e.slice(t,n-t+1)}e=n.resolve(e).substr(1),t=n.resolve(t).substr(1);var i=r(e.split("/")),s=r(t.split("/")),o=Math.min(i.length,s.length),u=o;for(var a=0;a0){var n=r.shift();n()}}},!0),function(t){r.push(t),window.postMessage("process-tick","*")}):function(t){setTimeout(t,0)}}(),r.title="browser",r.browser=!0,r.env={},r.argv=[],r.on=i,r.addListener=i,r.once=i,r.off=i,r.removeListener=i,r.removeAllListeners=i,r.emit=i,r.binding=function(e){throw new Error("process.binding is not supported")},r.cwd=function(){return"/"},r.chdir=function(e){throw new Error("process.chdir is not supported")}},{}],41:[function(e,t,n){t.exports=function(t){return t&&typeof t=="object"&&typeof t.copy=="function"&&typeof t.fill=="function"&&typeof t.readUInt8=="function"}},{}],42:[function(e,t,n){(function(t,r){function u(e,t){var r={seen:[],stylize:f};return arguments.length>=3&&(r.depth=arguments[2]),arguments.length>=4&&(r.colors=arguments[3]),y(t)?r.showHidden=t:t&&n._extend(r,t),T(r.showHidden)&&(r.showHidden=!1),T(r.depth)&&(r.depth=2),T(r.colors)&&(r.colors=!1),T(r.customInspect)&&(r.customInspect=!0),r.colors&&(r.stylize=a),c(r,e,r.depth)}function a(e,t){var n=u.styles[t];return n?"["+u.colors[n][0]+"m"+e+"["+u.colors[n][1]+"m":e}function f(e,t){return e}function l(e){var t={};return e.forEach(function(e,n){t[e]=!0}),t}function c(e,t,r){if(e.customInspect&&t&&A(t.inspect)&&t.inspect!==n.inspect&&(!t.constructor||t.constructor.prototype!==t)){var i=t.inspect(r,e);return S(i)||(i=c(e,i,r)),i}var s=h(e,t);if(s)return s;var o=Object.keys(t),u=l(o);e.showHidden&&(o=Object.getOwnPropertyNames(t));if(L(t)&&(o.indexOf("message")>=0||o.indexOf("description")>=0))return p(t);if(o.length===0){if(A(t)){var a=t.name?": "+t.name:"";return e.stylize("[Function"+a+"]","special")}if(N(t))return e.stylize(RegExp.prototype.toString.call(t),"regexp");if(k(t))return e.stylize(Date.prototype.toString.call(t),"date");if(L(t))return p(t)}var f="",y=!1,b=["{","}"];g(t)&&(y=!0,b=["[","]"]);if(A(t)){var w=t.name?": "+t.name:"";f=" [Function"+w+"]"}N(t)&&(f=" "+RegExp.prototype.toString.call(t)),k(t)&&(f=" "+Date.prototype.toUTCString.call(t)),L(t)&&(f=" "+p(t));if(o.length!==0||!!y&&t.length!=0){if(r<0)return N(t)?e.stylize(RegExp.prototype.toString.call(t),"regexp"):e.stylize("[Object]","special");e.seen.push(t);var E;return y?E=d(e,t,r,u,o):E=o.map(function(n){return v(e,t,r,u,n,y)}),e.seen.pop(),m(E,f,b)}return b[0]+f+b[1]}function h(e,t){if(T(t))return e.stylize("undefined","undefined");if(S(t)){var n="'"+JSON.stringify(t).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return e.stylize(n,"string")}if(E(t))return e.stylize(""+t,"number");if(y(t))return e.stylize(""+t,"boolean");if(b(t))return e.stylize("null","null")}function p(e){return"["+Error.prototype.toString.call(e)+"]"}function d(e,t,n,r,i){var s=[];for(var o=0,u=t.length;o-1&&(s?u=u.split("\n").map(function(e){return" "+e}).join("\n").substr(2):u="\n"+u.split("\n").map(function(e){return" "+e}).join("\n"))):u=e.stylize("[Circular]","special"));if(T(o)){if(s&&i.match(/^\d+$/))return u;o=JSON.stringify(""+i),o.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(o=o.substr(1,o.length-2),o=e.stylize(o,"name")):(o=o.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),o=e.stylize(o,"string"))}return o+": "+u}function m(e,t,n){var r=0,i=e.reduce(function(e,t){return r++,t.indexOf("\n")>=0&&r++,e+t.replace(/\u001b\[\d\d?m/g,"").length+1},0);return i>60?n[0]+(t===""?"":t+"\n ")+" "+e.join(",\n ")+" "+n[1]:n[0]+t+" "+e.join(", ")+" "+n[1]}function g(e){return Array.isArray(e)}function y(e){return typeof e=="boolean"}function b(e){return e===null}function w(e){return e==null}function E(e){return typeof e=="number"}function S(e){return typeof e=="string"}function x(e){return typeof e=="symbol"}function T(e){return e===void 0}function N(e){return C(e)&&M(e)==="[object RegExp]"}function C(e){return typeof e=="object"&&e!==null}function k(e){return C(e)&&M(e)==="[object Date]"}function L(e){return C(e)&&(M(e)==="[object Error]"||e instanceof Error)}function A(e){return typeof e=="function"}function O(e){return e===null||typeof e=="boolean"||typeof e=="number"||typeof e=="string"||typeof e=="symbol"||typeof e=="undefined"}function M(e){return Object.prototype.toString.call(e)}function _(e){return e<10?"0"+e.toString(10):e.toString(10)}function P(){var e=new Date,t=[_(e.getHours()),_(e.getMinutes()),_(e.getSeconds())].join(":");return[e.getDate(),D[e.getMonth()],t].join(" ")}function H(e,t){return Object.prototype.hasOwnProperty.call(e,t)}var i=/%[sdj%]/g;n.format=function(e){if(!S(e)){var t=[];for(var n=0;n=s)return e;switch(e){case"%s":return String(r[n++]);case"%d":return Number(r[n++]);case"%j":try{return JSON.stringify(r[n++])}catch(t){return"[Circular]"};default:return e}});for(var a=r[n];n","Konstantin Käfer","Alexis Sellier ","Raul Ochoa ","Javi Santana "],licenses:[{type:"Apache"}],bin:{carto:"./bin/carto"},man:"./man/carto.1",main:"./lib/carto/index",engines:{node:">=0.4.x"},dependencies:{underscore:"~1.6.0","mapnik-reference":"~6.0.2",optimist:"~0.6.0"},devDependencies:{mocha:"1.12.x",jshint:"0.2.x",sax:"0.1.x",istanbul:"~0.2.14",coveralls:"~2.10.1",browserify:"~7.0.0","uglify-js":"1.3.3"},scripts:{pretest:"npm install",test:"mocha -R spec",coverage:"istanbul cover ./node_modules/.bin/_mocha && coveralls < ./coverage/lcov.info"}}},{}]},{},[2])(2)}); \ No newline at end of file diff --git a/vendor/mod/odyssey.js b/vendor/mod/odyssey.js new file mode 100644 index 0000000000..2ec40105b0 --- /dev/null +++ b/vendor/mod/odyssey.js @@ -0,0 +1,6735 @@ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.O=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o", _, arguments); + }, + + update: function() { + console.log("STATE (.)", _, arguments); + }, + + exit: function() { + console.log("STATE <=", _, arguments); + } + + }); + + }; + + return _debug; +} + +module.exports = Debug; + +},{"../story":14}],4:[function(_dereq_,module,exports){ + +var Action = _dereq_('../story').Action; + +var Audio = function(el){ + return { + play: function() { + return Action(function() { + el.play() + }); + }, + pause: function() { + return Action(function() { + el.pause() + }); + }, + setCurrentTime: function(t) { + return Action(function() { + el.currentTime = t; + }); + } + } + +}; +module.exports = Audio; + + +},{"../story":14}],5:[function(_dereq_,module,exports){ + +module.exports = { + Sleep: _dereq_('./sleep'), + Debug: _dereq_('./debug'), + Location: _dereq_('./location'), + Audio: _dereq_('./html5audio'), + Leaflet: { + Marker: _dereq_('./leaflet/marker'), + Map: _dereq_('./leaflet/map'), + Popup: _dereq_('./leaflet/popup') + }, + CSS: _dereq_('./css'), + Slides: _dereq_('./slides'), + MiniProgressBar: _dereq_('./mini_progressbar') +}; + +},{"./css":2,"./debug":3,"./html5audio":4,"./leaflet/map":6,"./leaflet/marker":7,"./leaflet/popup":8,"./location":9,"./mini_progressbar":10,"./sleep":11,"./slides":12}],6:[function(_dereq_,module,exports){ + +var Action = _dereq_('../../story').Action; + +function MapActions(map) { + + function _map() {} + + // helper method to translate leaflet methods to actions + function leaflet_method(name) { + _map[name] = function() { + var args = arguments; + return Action(function() { + map[name].apply(map, args); + }); + }; + } + + function leaflet_move_method(name, signal) { + _map[name] = function() { + var args = arguments; + return Action({ + enter: function() { + this.moveEnd = function() { + map.off(signal, this.moveEnd, this); + this.finish(); + }; + map.on(signal, this.moveEnd, this); + map[name].apply(map, args); + return true; + }, + + clear: function() { + map.off(signal, this.moveEnd, this); + } + }); + }; + } + + _map.moveLinearTo = function(from, to, options) { + var opt = options || { k: 2 }; + var animationTimer; + var posTarget; + var delta = 20; + + // leaflet map animation is not being used because + // when the action is updated the map just stop the animatin + // and start again + // the animtion should be smooth + return Action({ + + enter: function () { + posTarget = map.project(map.getCenter()); + animationTimer = setInterval(function() { + var c = map.project(map.getCenter()); + var px = c.x + (posTarget.x - c.x)*delta*0.001* opt.k; + var py = c.y + (posTarget.y - c.y)*delta*0.001* opt.k; + map.panTo(map.unproject(L.point(px, py)), { animate: false }); + }, delta); + }, + + update: function (t) { + var p0 = map.project(from); + var p1 = map.project(to); + posTarget = p0.add(p1.subtract(p0).multiplyBy(t)); + }, + + exit: function() { + clearInterval(animationTimer); + }, + + clean: function() { + this.exit(); + } + + }); + }; + + + // leaflet methods + leaflet_move_method('panTo', 'moveend'); + leaflet_move_method('setView', 'moveend'); + leaflet_move_method('setZoom', 'zoomend'); + + return _map; +} + + +if (typeof window.L !== 'undefined') { + L.Map.addInitHook(function () { + this.actions = MapActions(this); + }); +} +module.exports = MapActions; + + +},{"../../story":14}],7:[function(_dereq_,module,exports){ + +var Action = _dereq_('../../story').Action; + +function MarkerActions(marker) { + + function _marker() {} + + _marker.addTo = function(map) { + return Action(function() { + marker.addTo(map); + }); + }; + + _marker.addRemove = function(map) { + return Action({ + enter: function() { + marker.addTo(map); + }, + exit: function() { + map.removeLayer(marker); + }, + clear: function() { + map.removeLayer(marker); + } + }); + }; + + _marker.icon = function(iconEnabled, iconDisabled) { + iconEnabled = L.icon({ + iconUrl: iconEnabled + }); + iconDisabled = L.icon({ + iconUrl: iconDisabled + }); + return Action({ + enter: function() { + marker.setIcon(iconEnabled); + }, + exit: function() { + marker.setIcon(iconDisabled); + }, + clear: function() { } + }); + } + + return _marker; +} + +function PathActions(path) { + function _path() {}; + + _path.toggleStyle = function(styleDisabled, styleEnabled) { + return Action({ + enter: function() { + path.setStyle(styleEnabled); + }, + exit: function() { + path.setStyle(styleDisabled); + }, + clear: function() { } + }); + } + + return _path; +} + + +if (typeof window.L !== 'undefined') { + L.Marker.addInitHook(function () { + this.actions = MarkerActions(this); + }); + L.Path.addInitHook(function () { + this.actions = PathActions(this); + }) +} +module.exports = MarkerActions; + +//marker.actions.addTo(map); +//addState(, map.actions.moveTo(..).addMarker(m) + +},{"../../story":14}],8:[function(_dereq_,module,exports){ + +/** +directional popup allows to create popups in the left and the right of a point, +not only on the top. + +Same api than L.popup but you can spcify ``potision`` to 'left' or 'right' + +*/ + +var Action = _dereq_('../../story').Action; + + +if (typeof window.L !== 'undefined') { + +var DirectionalPopup = L.Popup.extend({ + _updatePosition: function() { + L.Popup.prototype._updatePosition.call(this); + var offset = L.point(this.options.offset), + animated = this._animated; + + switch(this.options.position) { + case 'left': + this._container.style.bottom = 'auto'; + this._container.style.left = 'auto'; + this._container.style.right = offset.x + (animated ? 0 : pos.x) + "px"; + break; + case 'right': + this._container.style.bottom = 'auto'; + this._container.style.left = offset.x + "px"; + break; + } + } +}); + +L.DirectionalPopup = DirectionalPopup; +L.directionalPopup = function (options, source) { + return new L.DirectionalPopup(options, source); +}; + +function PopupActions(popup) { + + function _popup() {} + + // helper method to translate leaflet methods to actions + function leaflet_method(name) { + _popup[name] = function() { + var args = arguments; + return Action(function() { + popup[name].apply(popup, args); + }); + }; + } + + // leaflet methods + leaflet_method('openOn'); + _popup.openClose = function(map) { + if (!map) { + throw new Error("openClose gets map as first param"); + } + return Action({ + enter: function() { + popup.openOn(map); + }, + + exit: function() { + map.closePopup(popup); + } + + }); + }; + + return _popup; +} + + + L.Popup.addInitHook(function () { + this.actions = PopupActions(this); + }); +} + + +},{"../../story":14}],9:[function(_dereq_,module,exports){ + +var Action = _dereq_('../story').Action; + +var loc = window.location; +var Location = { + + // changes the browser url hash + changeHash: function(hash) { + if (hash === undefined) throw new Error("hash should be a string"); + return Action(function() { + loc.hash = hash; + }); + } + +}; + + +module.exports = Location; + +},{"../story":14}],10:[function(_dereq_,module,exports){ + +var Action = _dereq_('../story').Action; + +/** + * mini progress var adds a small line to the top of the browser + * and moves according to story progress + * usage: + * var pg = MiniProgressBar() + * story.addAction(trigger, pg.percent(10)) //goes to 10% + */ + +var MiniProgressBar = function(el) { + + var defaultStyle = { + position: 'fixed', + left: 0, + top: 0, + height: '3px', + display: 'inline-block', + background: '#ff7373', + 'z-index': 2 + }; + + var pg = {}; + + // create an element and apply default style + var div = document.createElement('div'); + div.setAttribute('class', 'oddysey-miniprogressbar'); + for (var s in defaultStyle) { + div.style[s] = defaultStyle[s]; + } + + // append to element or to tge body + (el || document.body).appendChild(div); + + /** + * returns an action that moves the percentaje bar to the specified one + */ + pg.percent = function(p) { + return Action(function() { + div.style.width = p + "%"; + }); + }; + + return pg; +} + +module.exports = MiniProgressBar; + +},{"../story":14}],11:[function(_dereq_,module,exports){ + +var Action = _dereq_('../story').Action; + +function Sleep(ms) { + + return Action({ + + enter: function() { + setTimeout(this.finish, ms); + return true; + } + + }); +} + +module.exports = Sleep; + + +},{"../story":14}],12:[function(_dereq_,module,exports){ + +var Action = _dereq_('../story').Action; +var Core = _dereq_('../core'); +var classList = _dereq_('../util/classList'); + +function Slides(el) { + + + function slides() {}; + + function _activate(idx) { + var slideElements = Core.getElement(el).children; + for(var i = 0; i < slideElements.length; ++i) { + if (i === idx) { + slideElements[i].classList.add("selected", "selected_slide"); + } else { + slideElements[i].classList.remove("selected", "selected_slide"); + } + } + } + + slides.activate = function(i) { + return Action(function() { + _activate(i); + }); + }; + + _activate(-1); + + return slides; + +} + +module.exports = Slides; +},{"../core":13,"../story":14,"../util/classList":24}],13:[function(_dereq_,module,exports){ + +function getElement(el) { + if(typeof jQuery !== 'undefined') { + if (el instanceof jQuery) { + return el[0]; + } else if(typeof el === 'string') { + if (el[0] === '#' || el[0] === '.') { + return getElement($(el)); + } + } + } + if (el instanceof NodeList || el instanceof HTMLCollection) { + return el[0]; + } else if (el instanceof Element) { + return el; + } + return document.getElementById(el); +} + +module.exports = { + getElement: getElement +}; + +},{}],14:[function(_dereq_,module,exports){ + +_dereq_('../../vendor/d3.custom'); + +function Story() { + + var triggers = []; + var events = []; + var currentState = null; + var prevState = null; + + function story(t) { + } + + // event non attached to states + story.addEvent = function(trigger, action) { + trigger._story(story, function() { + action.enter(); + }); + events.push({ + a: trigger, + b: action + }); + return story; + }; + + story.clear = function() { + var all = triggers.concat(events); + for(var i = 0; i < all.length; ++i) { + var a = all[i]; + a.a.story = null; + a.a.trigger = null; + a.a.clear && a.a.clear(); + a.b.clear && a.b.clear(); + } + + triggers = []; + events = []; + currentState = null; + prevState = null; + + }; + + // go to state index + story.go = function(index, opts) { + opts = opts || {}; + if(index < 0 && index > triggers.length) { + throw new Error("index should be less than states length"); + } + if (story.state() !== index) { + + if (opts.reverse) { + var a = triggers[index].a; + if (a.reverse) { + a.reverse(); + } + } + // current state + story.state(index); + + // raise exit + if (prevState !== null) { + var prev = triggers[prevState].b; + if (prev.exit) { + prev.exit(); + } + } + + var b = triggers[index].b; + + // enter in current state + b.enter(); + } + + }; + + story.addState = function(a, b, opts) { + var i = triggers.length; + + if(!a || !b) { + throw new Error("action and trigger must be defined"); + } + + triggers.push({ + a: a, + b: b, + opts: opts + }); + + a._story(story, function() { + story.go(i); + }); + + return story; + }; + + story.addLinearState = function(a, b, opts) { + var j; + var i = triggers.length; + + triggers.push({ + a: a, + b: b, + opts: opts + }); + + a._story(story, function(t) { + if (story.state() !== i) { + story.go(i); + } else { + if (b.update) { + b.update(t); + } + } + }); + + return story; + }; + + story.state = function(_) { + if(_ === undefined) return currentState; + prevState = currentState; + currentState = _; + return; + }; + + return story; + + +} + + +// +// basic action +// t can be a function or an object +// if is a function it's called on ``enter`` event +// if t is an object with enter/exit/update methods +// they're called on state changes +function Action(t) { + + var evt = d3.dispatch('finish'); + var action = t; + if (t.enter === undefined && !(typeof(t) === 'function' && t.prototype.enter !== undefined)) { + action = { + enter: t + } + } + + return d3.rebind(action, evt, 'on', 'finish'); + +} + +function Trigger(t) { + t = t || {} + t._story = function(story, trigger) { + this.trigger = trigger; + this.story = story; + }; + + t.then = function(t, context) { + this.trigger = function() { + if (t.trigger) { + t.trigger(); + if (t.reverse) t.reverse(); + } else if (t.call) { + t.call(context || self); + } else { + throw new Error("then first param should be either function or trigger"); + } + }; + }; + return t; +} + +/// +// executes actions in parallel +// usage: +// Parallel(action1, action2, action3) +// +// raises finish when all the tasks has been completed +// +function Parallel () { + var actions = Array.prototype.slice.call(arguments); + var tasksLeft; + + function _Parallel() {} + + function start() { + tasksLeft = actions.length; + } + + function done() { + if (--tasksLeft === 0) { + _Parallel.finish(); + } + } + + function wait(action) { + action.on('finish.parallel', function() { + action.on('finish.parallel', null); + done(); + }); + } + + + _Parallel.enter = function() { + start(); + for(var i = 0, len = actions.length; i < len; ++i) { + var a = actions[i]; + if (a.enter) { + if (a.enter()) { + wait(a); + } else { + done(); + } + } + } + }; + + _Parallel.exit = function() { + start(); + for(var i = actions.length - 1; i >= 0; --i) { + var a = actions[i]; + if (a.exit) { + if (a.exit()) { + wait(a); + } else { + done(); + } + } + } + }; + + _Parallel.clear = function() { + for(var i = 0, len = actions.length; i < len; ++i) { + var a = actions[i]; + a.clear && a.clear(); + } + } + + _Parallel = Action(_Parallel); + return _Parallel; +} + + + +/// +// executes actions serially, waits until the previous task +// is completed to start with the second and so on +// usage: +// Step(action1, action2, action3) +// +// raises finish when all the tasks has been completed +// +function Step() { + + var actions = Array.prototype.slice.call(arguments); + var queue; + + function _Step() {} + + function next(method) { + if (queue.length === 0) { + _Step.finish(); + return; + } + var a = queue.pop(); + if (a.on) { + a.on('finish.chain', function() { + a.on('finish.chain', null); + next(method); + }); + } + if (!a[method] || !a[method]()) { + next(method); + if(a.on) a.on('finish.chain', null); + } + } + + _Step.enter = function() { + // call enter on each action + queue = actions.slice().reverse(); + next('enter'); + return true; + } + + _Step.exit = function() { + // call exit on each action + queue = actions.slice(); + next('exit'); + return true; + } + + _Step.clear = function() { + for(var i = 0, len = actions.length; i < len; ++i) { + var a = actions[i]; + a.clear && a.clear(); + } + } + + _Step = Action(_Step); + return _Step; +} + +// check change between two states and triggers +function Edge(a, b) { + var s = 0; + function t() {} + a._story(null, function() { + if(s !== 0) { + t.trigger(); + } + s = 0; + }); + b._story(null, function() { + if(s !== 1) { + t.trigger(); + } + s = 1; + }); + return Trigger(t); +} + + + +module.exports = { + Story: Story, + Action: Action, + Trigger: Trigger, + Step: Step, + Parallel: Parallel, + Edge: Edge +}; + + +},{"../../vendor/d3.custom":32}],15:[function(_dereq_,module,exports){ + +_dereq_('../../vendor/markdown'); +var mapActions = { + 'move map to current position': function() { + var center = this.map.getCenter() + return '- center: [' + center.lat.toFixed(4) + ', ' + center.lng.toFixed(4) + ']\n- zoom: ' + this.map.getZoom() + }, + 'show marker at current position': function() { + var center = this.map.getCenter() + return 'L.marker([' + center.lat.toFixed(4) + ', ' + center.lng.toFixed(4) + ']).actions.addRemove(S.map)'; + }, + 'sleep': function() { + return "O.Actions.Sleep(1000)"; + } +}; + +var Template = function(template) { + var initialized = false; + + function readMessage(event) { + var msg = JSON.parse(event.data); + template.editor = true; + + if (!initialized) { + configureEditor(); + initialized = true; + } + + function sendMsg(_) { + event.source.postMessage(JSON.stringify({ + id: msg.id, + data: _ + }), event.currentTarget ? event.currentTarget.location : event.source.location); + } + + if (msg.type === 'md') { + var actions = actionsFromMarkdown(msg.code); + try { + template.update(actions); + sendMsg(null); + } catch(e) { + sendMsg(e.message); + } + } else if (msg.type === 'actions') { + var actions = []; + if (template.map && template.map instanceof L.Map) { + for (var k in mapActions) { + actions.push(k); + } + } + if (template.actions) { + for (var k in template.actions) { + actions.push(k); + } + } + sendMsg(actions); + } else if (msg.type === 'get_action') { + if (msg.code in mapActions) { + sendMsg(mapActions[msg.code].call(template)); + } else { + sendMsg(template.actions[msg.code].call(template)); + } + } else if (msg.type === 'code') { + sendMsg(eval(msg.code)); + } else if (msg.type === 'change_slide') { + template.changeSlide && template.changeSlide(msg.slide); + } + } + + if (!window.addEventListener) { + window.attachEvent("message", function load(event) { + readMessage(event); + }); + } else { + window.addEventListener("message", function load(event) { + readMessage(event); + }); + } + + template.init(function() { }); + + function configureEditor() { + // add helpers + if (template.map && template.map instanceof L.Map) { + new L.CrossHair('//cartodb.github.io/odyssey.js/img/crosshair.png').addTo(template.map); + } + } + + window.onload = function() { + var origin = location.pathname.split('/')[1]; + + Template.Storage.load(function(md) { + template.update(actionsFromMarkdown(md)); + }); + } +}; + +Template.Storage = { + + save: function(md, template) { + location.hash = "md/" + template + "/" + btoa(md); + }, + + load: function(done) { + if (document.getElementById('md_template')) { + done(document.getElementById('md_template').text); + return; + } + + var h = location.hash; + if (done && h) { + // #md/template/base_64_markdown + var tk = h.split('/'); + if (tk[0] === '#md') { + done(atob(tk.slice(2).join('/')), tk[1]); + } + } + } +}; + +function Slide(tree, actions, prop) { + + var html; + var md_tree = tree; + var properties = prop; + + var action; + + function compile(context) { + propertiesToActions(); + //sort actions by order + actions.sort(function(a, b) { + return a.order - b.order; + }); + + if (!('map' in Array.prototype)) { + Array.prototype.map= function(mapper, that /*opt*/) { + var other= new Array(this.length); + for (var i= 0, n= this.length; i= offset) { + var t = (offset - bounds.top)/(bounds.bottom - bounds.top); + return t; + } + }); + return r; + }; + + scroll.less = function(el, opt) { + opt = opt || {}; + var r = scroll.reach(el); + var fixedBoundsTop; + if (opt.fixed) { + var e = Core.getElement(el); + fixedBoundsTop = scroller.scrollY + e.getBoundingClientRect().top; + } + r.condition(function(bounds, offset, scrollY) { + var t = opt.fixed ? fixedBoundsTop: bounds.top; + var o = opt.fixed ? scrollY: offset; + if(t >= o) { + return 0; + } + }); + return r; + }; + + scroll.greater = function(el, opt) { + opt = opt || {}; + var r = scroll.reach(el); + var fixedBoundsTop; + if (opt.fixed) { + var e = Core.getElement(el); + fixedBoundsTop = scroller.scrollY + e.getBoundingClientRect().top; + } + r.condition(function(bounds, offset, scrollY) { + var t = opt.fixed ? fixedBoundsTop: bounds.top; + var o = opt.fixed ? scrollY: offset; + if(t <= o) { + return 0; + } + }); + return r; + }; + + function register(s) { + scrolls.push(s); + initScroll(); + } + + function unregister(s) { + var i = scrolls.indexOf(s); + if (i >= 0) { + scrolls.splice(i, 1); + } + } + + function initScroll() { + if (!initialized) { + initialized = true; + + if (!Array.prototype.forEach) { + + Array.prototype.forEach = function (callback, thisArg) { + + var T, k; + + if (this == null) { + throw new TypeError(" this is null or not defined"); + } + + // 1. Let O be the result of calling ToObject passing the |this| value as the argument. + var O = Object(this); + + // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length". + // 3. Let len be ToUint32(lenValue). + var len = O.length >>> 0; + + // 4. If IsCallable(callback) is false, throw a TypeError exception. + // See: http://es5.github.com/#x9.11 + if (typeof callback !== "function") { + throw new TypeError(callback + " is not a function"); + } + + // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. + if (thisArg) { + T = thisArg; + } + + // 6. Let k be 0 + k = 0; + + // 7. Repeat, while k < len + while (k < len) { + + var kValue; + + // a. Let Pk be ToString(k). + // This is implicit for LHS operands of the in operator + // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. + // This step can be combined with c + // c. If kPresent is true, then + if (k in O) { + + // i. Let kValue be the result of calling the Get internal method of O with argument Pk. + kValue = O[k]; + + // ii. Call the Call internal method of callback with T as the this value and + // argument list containing kValue, k, and O. + callback.call(T, kValue, k, O); + } + // d. Increase k by 1. + k++; + } + // 8. return undefined + }; + } + + function scrollEach() { + scrolls.forEach(function(s) { + s.scroll(window.scrollY); + }); + } + + if (!window.addEventListener) { + scroller.attachEvent("onscroll", function load(event) { + scrollEach(); + }); + } else { + window.addEventListener("scroll", function load(event) { + scrollEach(); + }); + } + } + } + + return scroll; +} + +Scroll._scrolls = []; +module.exports = Scroll; + +},{"../core":13,"../story":14}],20:[function(_dereq_,module,exports){ + +var Trigger = _dereq_('../story').Trigger; + +function Sequential() { + var current = 0; + var steps = []; + var max = 0; + var triggers = {}; + + function seq() {} + + function update() { + for (var i = 0; i < steps.length; ++i) { + steps[i].check(); + } + } + + seq.state = seq.step = function(n) { + if (n in triggers) { + return triggers[n]; + } + var t = Trigger({ + check: function() { + if (n === current && this.trigger) this.trigger(); + }, + reverse: function() { + current = n; + } + }); + max = Math.max(max, n); + steps.push(t); + return triggers[n] = t; + }; + + seq.next = function() { + current += 1; + if (current > max) { + current = 0; + } + update(); + }; + + seq.clear = function() { + steps = []; + max = 0; + current = 0; + return seq; + }; + + seq.current = function(_) { + if (_ !== undefined) { + var c = Math.max(Math.min(max, _), 0); + if (c !== current) { + current = c; + update(); + } + return this; + } + return current; + }; + + seq.prev = function() { + current -= 1; + if (current < 0) { + current = max; + } + update(); + }; + + return seq; +} + +module.exports = Sequential; + +},{"../story":14}],21:[function(_dereq_,module,exports){ + +function Video(player) { + if (typeof YT === 'undefined' || !(player instanceof YT.Player)) { + throw new Error("player should be a YT.Player instance, see youtube API"); + } + + var triggers = []; + + var i = setInterval(function() { + var seconds = player.getCurrentTime(); + for (var i = 0; i < triggers.length; ++i) { + var t = triggers[i]; + if (t.start <= seconds && t.end > seconds) { + t.trigger((seconds - t.start)/(t.end - t.start)); + } + } + }, 100); + + var clear = function(t) { + if (triggers.length === 0) { + clearInterval(i); + } + }; + + return { + between: function(start, end) { + var t = O.Trigger(); + t.start = start; + t.end = end; + triggers.push(t); + t.clear = function() { + triggers.splice(triggers.indexOf(t), 1); + clear(); + }; + return t; + } + } + +} + +module.exports = Video + +},{}],22:[function(_dereq_,module,exports){ +/** +# dot progress +ui widget that controls dot progress + +## usage +in order to use it you need to instanciate using a container, so for example: + +
+ + ... + + var progress = DotProgress('dots') + + // we set the number of slides + progress.count(10); + + // we can activate it as an action + // then the story enters in this state the second dot + // will be activated + story.addState(trigger, progress.activate(1)) + + // when an user clicks on the dot it can trigger an action + story.addState(progress.step(1), action); + +## styling +the html rendered is the following: +``` +
+
    +
  • +
  • +
  • +
  • +
+
+``` + +so you can use active class to style the active one + + */ + +var Core = _dereq_('../core'); + +function DotProgress(el) { + var count = 0; + var element = Core.getElement(el); + var triggers = {}; + + function _progress() { + return _progress; + } + + function render() { + var html = '
    '; + for(var i = 0; i < count; ++i) { + html += '
  • '; + } + html += "
"; + element.innerHTML = html; + } + + _progress.count = function(_) { + count = _; + render(); + return _progress; + }; + + // returns an action to activate the index + _progress.activate = function(activeIndex) { + return O.Action(function () { + var children = element.children[0].children; + for(var i = 0; i < children.length; ++i) { + children[i].children[0].setAttribute('class', ''); + } + children[activeIndex].children[0].setAttribute('class', 'active'); + }); + }; + + element.onclick = function(e) { + e = e || window.event; + + var idx = (e.target || e.srcElement).getAttribute('slide-index'); + var t = triggers[idx]; + if (t) { + t.trigger(); + } + }; + + _progress.step = function(i) { + var t = O.Trigger(); + triggers[i] = t; + return t; + }; + + return _progress; + +} + +module.exports = DotProgress; + +},{"../core":13}],23:[function(_dereq_,module,exports){ + +module.exports = { + DotProgress: _dereq_('./dotprogress') +} + +},{"./dotprogress":22}],24:[function(_dereq_,module,exports){ +/* + * classList.js: Cross-browser full element.classList implementation. + * 2014-01-31 + * + * By Eli Grey, http://eligrey.com + * Public Domain. + * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + */ + +/*global self, document, DOMException */ + +/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/ + +if ("document" in self && !("classList" in document.createElement("_"))) { + +(function (view) { + +"use strict"; + +if (!('Element' in view)) return; + +var + classListProp = "classList" + , protoProp = "prototype" + , elemCtrProto = view.Element[protoProp] + , objCtr = Object + , strTrim = String[protoProp].trim || function () { + return this.replace(/^\s+|\s+$/g, ""); + } + , arrIndexOf = Array[protoProp].indexOf || function (item) { + var + i = 0 + , len = this.length + ; + for (; i < len; i++) { + if (i in this && this[i] === item) { + return i; + } + } + return -1; + } + // Vendors: please allow content code to instantiate DOMExceptions + , DOMEx = function (type, message) { + this.name = type; + this.code = DOMException[type]; + this.message = message; + } + , checkTokenAndGetIndex = function (classList, token) { + if (token === "") { + throw new DOMEx( + "SYNTAX_ERR" + , "An invalid or illegal string was specified" + ); + } + if (/\s/.test(token)) { + throw new DOMEx( + "INVALID_CHARACTER_ERR" + , "String contains an invalid character" + ); + } + return arrIndexOf.call(classList, token); + } + , ClassList = function (elem) { + var + trimmedClasses = strTrim.call(elem.getAttribute("class") || "") + , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [] + , i = 0 + , len = classes.length + ; + for (; i < len; i++) { + this.push(classes[i]); + } + this._updateClassName = function () { + elem.setAttribute("class", this.toString()); + }; + } + , classListProto = ClassList[protoProp] = [] + , classListGetter = function () { + return new ClassList(this); + } +; +// Most DOMException implementations don't allow calling DOMException's toString() +// on non-DOMExceptions. Error's toString() is sufficient here. +DOMEx[protoProp] = Error[protoProp]; +classListProto.item = function (i) { + return this[i] || null; +}; +classListProto.contains = function (token) { + token += ""; + return checkTokenAndGetIndex(this, token) !== -1; +}; +classListProto.add = function () { + var + tokens = arguments + , i = 0 + , l = tokens.length + , token + , updated = false + ; + do { + token = tokens[i] + ""; + if (checkTokenAndGetIndex(this, token) === -1) { + this.push(token); + updated = true; + } + } + while (++i < l); + + if (updated) { + this._updateClassName(); + } +}; +classListProto.remove = function () { + var + tokens = arguments + , i = 0 + , l = tokens.length + , token + , updated = false + ; + do { + token = tokens[i] + ""; + var index = checkTokenAndGetIndex(this, token); + if (index !== -1) { + this.splice(index, 1); + updated = true; + } + } + while (++i < l); + + if (updated) { + this._updateClassName(); + } +}; +classListProto.toggle = function (token, force) { + token += ""; + + var + result = this.contains(token) + , method = result ? + force !== true && "remove" + : + force !== false && "add" + ; + + if (method) { + this[method](token); + } + + return !result; +}; +classListProto.toString = function () { + return this.join(" "); +}; + +if (objCtr.defineProperty) { + var classListPropDesc = { + get: classListGetter + , enumerable: true + , configurable: true + }; + try { + objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); + } catch (ex) { // IE 8 doesn't support enumerable:true + if (ex.number === -0x7FF5EC54) { + classListPropDesc.enumerable = false; + objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); + } + } +} else if (objCtr[protoProp].__defineGetter__) { + elemCtrProto.__defineGetter__(classListProp, classListGetter); +} + +}(self)); + +} + +},{}],25:[function(_dereq_,module,exports){ +/** + * new L.CrossHair('http://image.com/image', { x: 10, y :10 }).addTo(map) + */ +if (typeof window.L !== 'undefined') { + +L.CrossHair = L.Control.extend({ + + initialize: function(img, size) { + this.image = new Image(); + this.image.onload = L.bind(this._updatePos, this); + this.image.src = img; + if (size) { + this.image.width = size.x; + this.image.height = size.y; + } + }, + + _updatePos: function() { + if (this._map) { + var w = this.image.width >> 1; + var h = this.image.height >> 1; + w = this._map.getSize().x/2.0 - w; + h = this._map.getSize().y/2.0 - h; + L.DomUtil.setPosition(this.image, { x: w, y: h }); + } + }, + + addTo: function(map) { + var r = L.Control.prototype.addTo.call(this, map); + // remove leaflet-top and leaflet-right classes + this.image.parentNode.setAttribute('class', ''); + map.on('resize', L.bind(this._updatePos, this)); + return r; + }, + + onRemove: function(map) { + map.off('resize', null, this); + }, + + onAdd: function(map) { + this._updatePos(); + return this.image; + } + +}); + +} + +},{}],26:[function(_dereq_,module,exports){ + +module.exports = { + CrossHair: _dereq_('./crosshair') +} + +},{"./crosshair":25}],27:[function(_dereq_,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + +},{}],28:[function(_dereq_,module,exports){ +// shim for using process in browser + +var process = module.exports = {}; + +process.nextTick = (function () { + var canSetImmediate = typeof window !== 'undefined' + && window.setImmediate; + var canPost = typeof window !== 'undefined' + && window.postMessage && window.addEventListener + ; + + if (canSetImmediate) { + return function (f) { return window.setImmediate(f) }; + } + + if (canPost) { + var queue = []; + window.addEventListener('message', function (ev) { + var source = ev.source; + if ((source === window || source === null) && ev.data === 'process-tick') { + ev.stopPropagation(); + if (queue.length > 0) { + var fn = queue.shift(); + fn(); + } + } + }, true); + + return function nextTick(fn) { + queue.push(fn); + window.postMessage('process-tick', '*'); + }; + } + + return function nextTick(fn) { + setTimeout(fn, 0); + }; +})(); + +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +} + +// TODO(shtylman) +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; + +},{}],29:[function(_dereq_,module,exports){ +module.exports = function isBuffer(arg) { + return arg && typeof arg === 'object' + && typeof arg.copy === 'function' + && typeof arg.fill === 'function' + && typeof arg.readUInt8 === 'function'; +} +},{}],30:[function(_dereq_,module,exports){ +(function (process,global){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; + } else { + str += ' ' + inspect(x); + } + } + return str; +}; + + +// Mark that a method should not be used. +// Returns a modified function which warns once by default. +// If --no-deprecation is set, then it is a no-op. +exports.deprecate = function(fn, msg) { + // Allow for deprecating things in the process of starting up. + if (isUndefined(global.process)) { + return function() { + return exports.deprecate(fn, msg).apply(this, arguments); + }; + } + + if (process.noDeprecation === true) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (process.throwDeprecation) { + throw new Error(msg); + } else if (process.traceDeprecation) { + console.trace(msg); + } else { + console.error(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +}; + + +var debugs = {}; +var debugEnviron; +exports.debuglog = function(set) { + if (isUndefined(debugEnviron)) + debugEnviron = process.env.NODE_DEBUG || ''; + set = set.toUpperCase(); + if (!debugs[set]) { + if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { + var pid = process.pid; + debugs[set] = function() { + var msg = exports.format.apply(exports, arguments); + console.error('%s %d: %s', set, pid, msg); + }; + } else { + debugs[set] = function() {}; + } + } + return debugs[set]; +}; + + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* legacy: obj, showHidden, depth, colors*/ +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} +exports.inspect = inspect; + + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] +}; + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return '\u001b[' + inspect.colors[style][0] + 'm' + str + + '\u001b[' + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + + +function stylizeNoColor(str, styleType) { + return str; +} + + +function arrayToHash(array) { + var hash = {}; + + array.forEach(function(val, idx) { + hash[val] = true; + }); + + return hash; +} + + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && + value && + isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes, ctx); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = Object.getOwnPropertyNames(value); + } + + // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + if (isError(value) + && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + return formatError(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +function isArray(ar) { + return Array.isArray(ar); +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = _dereq_('./support/isBuffer'); + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + + +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} + + +// log is just a thin wrapper to console.log that prepends a timestamp +exports.log = function() { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ +exports.inherits = _dereq_('inherits'); + +exports._extend = function(origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + + var keys = Object.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; +}; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +}).call(this,_dereq_("FWaASH"),typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./support/isBuffer":29,"FWaASH":28,"inherits":27}],31:[function(_dereq_,module,exports){ +/*! Hammer.JS - v1.1.3 - 2014-05-20 + * http://eightmedia.github.io/hammer.js + * + * Copyright (c) 2014 Jorik Tangelder ; + * Licensed under the MIT license */ + +(function(window, undefined) { + 'use strict'; + +/** + * @main + * @module hammer + * + * @class Hammer + * @static + */ + +/** + * Hammer, use this to create instances + * ```` + * var hammertime = new Hammer(myElement); + * ```` + * + * @method Hammer + * @param {HTMLElement} element + * @param {Object} [options={}] + * @return {Hammer.Instance} + */ +var Hammer = function Hammer(element, options) { + return new Hammer.Instance(element, options || {}); +}; + +/** + * version, as defined in package.json + * the value will be set at each build + * @property VERSION + * @final + * @type {String} + */ +Hammer.VERSION = '1.1.3'; + +/** + * default settings. + * more settings are defined per gesture at `/gestures`. Each gesture can be disabled/enabled + * by setting it's name (like `swipe`) to false. + * You can set the defaults for all instances by changing this object before creating an instance. + * @example + * ```` + * Hammer.defaults.drag = false; + * Hammer.defaults.behavior.touchAction = 'pan-y'; + * delete Hammer.defaults.behavior.userSelect; + * ```` + * @property defaults + * @type {Object} + */ +Hammer.defaults = { + /** + * this setting object adds styles and attributes to the element to prevent the browser from doing + * its native behavior. The css properties are auto prefixed for the browsers when needed. + * @property defaults.behavior + * @type {Object} + */ + behavior: { + /** + * Disables text selection to improve the dragging gesture. When the value is `none` it also sets + * `onselectstart=false` for IE on the element. Mainly for desktop browsers. + * @property defaults.behavior.userSelect + * @type {String} + * @default 'none' + */ + userSelect: 'none', + + /** + * Specifies whether and how a given region can be manipulated by the user (for instance, by panning or zooming). + * Used by Chrome 35> and IE10>. By default this makes the element blocking any touch event. + * @property defaults.behavior.touchAction + * @type {String} + * @default: 'pan-y' + */ + touchAction: 'pan-y', + + /** + * Disables the default callout shown when you touch and hold a touch target. + * On iOS, when you touch and hold a touch target such as a link, Safari displays + * a callout containing information about the link. This property allows you to disable that callout. + * @property defaults.behavior.touchCallout + * @type {String} + * @default 'none' + */ + touchCallout: 'none', + + /** + * Specifies whether zooming is enabled. Used by IE10> + * @property defaults.behavior.contentZooming + * @type {String} + * @default 'none' + */ + contentZooming: 'none', + + /** + * Specifies that an entire element should be draggable instead of its contents. + * Mainly for desktop browsers. + * @property defaults.behavior.userDrag + * @type {String} + * @default 'none' + */ + userDrag: 'none', + + /** + * Overrides the highlight color shown when the user taps a link or a JavaScript + * clickable element in Safari on iPhone. This property obeys the alpha value, if specified. + * + * If you don't specify an alpha value, Safari on iPhone applies a default alpha value + * to the color. To disable tap highlighting, set the alpha value to 0 (invisible). + * If you set the alpha value to 1.0 (opaque), the element is not visible when tapped. + * @property defaults.behavior.tapHighlightColor + * @type {String} + * @default 'rgba(0,0,0,0)' + */ + tapHighlightColor: 'rgba(0,0,0,0)' + } +}; + +/** + * hammer document where the base events are added at + * @property DOCUMENT + * @type {HTMLElement} + * @default window.document + */ +Hammer.DOCUMENT = document; + +/** + * detect support for pointer events + * @property HAS_POINTEREVENTS + * @type {Boolean} + */ +Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled; + +/** + * detect support for touch events + * @property HAS_TOUCHEVENTS + * @type {Boolean} + */ +Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window); + +/** + * detect mobile browsers + * @property IS_MOBILE + * @type {Boolean} + */ +Hammer.IS_MOBILE = /mobile|tablet|ip(ad|hone|od)|android|silk/i.test(navigator.userAgent); + +/** + * detect if we want to support mouseevents at all + * @property NO_MOUSEEVENTS + * @type {Boolean} + */ +Hammer.NO_MOUSEEVENTS = (Hammer.HAS_TOUCHEVENTS && Hammer.IS_MOBILE) || Hammer.HAS_POINTEREVENTS; + +/** + * interval in which Hammer recalculates current velocity/direction/angle in ms + * @property CALCULATE_INTERVAL + * @type {Number} + * @default 25 + */ +Hammer.CALCULATE_INTERVAL = 25; + +/** + * eventtypes per touchevent (start, move, end) are filled by `Event.determineEventTypes` on `setup` + * the object contains the DOM event names per type (`EVENT_START`, `EVENT_MOVE`, `EVENT_END`) + * @property EVENT_TYPES + * @private + * @writeOnce + * @type {Object} + */ +var EVENT_TYPES = {}; + +/** + * direction strings, for safe comparisons + * @property DIRECTION_DOWN|LEFT|UP|RIGHT + * @final + * @type {String} + * @default 'down' 'left' 'up' 'right' + */ +var DIRECTION_DOWN = Hammer.DIRECTION_DOWN = 'down'; +var DIRECTION_LEFT = Hammer.DIRECTION_LEFT = 'left'; +var DIRECTION_UP = Hammer.DIRECTION_UP = 'up'; +var DIRECTION_RIGHT = Hammer.DIRECTION_RIGHT = 'right'; + +/** + * pointertype strings, for safe comparisons + * @property POINTER_MOUSE|TOUCH|PEN + * @final + * @type {String} + * @default 'mouse' 'touch' 'pen' + */ +var POINTER_MOUSE = Hammer.POINTER_MOUSE = 'mouse'; +var POINTER_TOUCH = Hammer.POINTER_TOUCH = 'touch'; +var POINTER_PEN = Hammer.POINTER_PEN = 'pen'; + +/** + * eventtypes + * @property EVENT_START|MOVE|END|RELEASE|TOUCH + * @final + * @type {String} + * @default 'start' 'change' 'move' 'end' 'release' 'touch' + */ +var EVENT_START = Hammer.EVENT_START = 'start'; +var EVENT_MOVE = Hammer.EVENT_MOVE = 'move'; +var EVENT_END = Hammer.EVENT_END = 'end'; +var EVENT_RELEASE = Hammer.EVENT_RELEASE = 'release'; +var EVENT_TOUCH = Hammer.EVENT_TOUCH = 'touch'; + +/** + * if the window events are set... + * @property READY + * @writeOnce + * @type {Boolean} + * @default false + */ +Hammer.READY = false; + +/** + * plugins namespace + * @property plugins + * @type {Object} + */ +Hammer.plugins = Hammer.plugins || {}; + +/** + * gestures namespace + * see `/gestures` for the definitions + * @property gestures + * @type {Object} + */ +Hammer.gestures = Hammer.gestures || {}; + +/** + * setup events to detect gestures on the document + * this function is called when creating an new instance + * @private + */ +function setup() { + if(Hammer.READY) { + return; + } + + // find what eventtypes we add listeners to + Event.determineEventTypes(); + + // Register all gestures inside Hammer.gestures + Utils.each(Hammer.gestures, function(gesture) { + Detection.register(gesture); + }); + + // Add touch events on the document + Event.onTouch(Hammer.DOCUMENT, EVENT_MOVE, Detection.detect); + Event.onTouch(Hammer.DOCUMENT, EVENT_END, Detection.detect); + + // Hammer is ready...! + Hammer.READY = true; +} + +/** + * @module hammer + * + * @class Utils + * @static + */ +var Utils = Hammer.utils = { + /** + * extend method, could also be used for cloning when `dest` is an empty object. + * changes the dest object + * @method extend + * @param {Object} dest + * @param {Object} src + * @param {Boolean} [merge=false] do a merge + * @return {Object} dest + */ + extend: function extend(dest, src, merge) { + for(var key in src) { + if(!src.hasOwnProperty(key) || (dest[key] !== undefined && merge)) { + continue; + } + dest[key] = src[key]; + } + return dest; + }, + + /** + * simple addEventListener wrapper + * @method on + * @param {HTMLElement} element + * @param {String} type + * @param {Function} handler + */ + on: function on(element, type, handler) { + element.addEventListener(type, handler, false); + }, + + /** + * simple removeEventListener wrapper + * @method off + * @param {HTMLElement} element + * @param {String} type + * @param {Function} handler + */ + off: function off(element, type, handler) { + element.removeEventListener(type, handler, false); + }, + + /** + * forEach over arrays and objects + * @method each + * @param {Object|Array} obj + * @param {Function} iterator + * @param {any} iterator.item + * @param {Number} iterator.index + * @param {Object|Array} iterator.obj the source object + * @param {Object} context value to use as `this` in the iterator + */ + each: function each(obj, iterator, context) { + var i, len; + + // native forEach on arrays + if('forEach' in obj) { + obj.forEach(iterator, context); + // arrays + } else if(obj.length !== undefined) { + for(i = 0, len = obj.length; i < len; i++) { + if(iterator.call(context, obj[i], i, obj) === false) { + return; + } + } + // objects + } else { + for(i in obj) { + if(obj.hasOwnProperty(i) && + iterator.call(context, obj[i], i, obj) === false) { + return; + } + } + } + }, + + /** + * find if a string contains the string using indexOf + * @method inStr + * @param {String} src + * @param {String} find + * @return {Boolean} found + */ + inStr: function inStr(src, find) { + return src.indexOf(find) > -1; + }, + + /** + * find if a array contains the object using indexOf or a simple polyfill + * @method inArray + * @param {String} src + * @param {String} find + * @return {Boolean|Number} false when not found, or the index + */ + inArray: function inArray(src, find) { + if(src.indexOf) { + var index = src.indexOf(find); + return (index === -1) ? false : index; + } else { + for(var i = 0, len = src.length; i < len; i++) { + if(src[i] === find) { + return i; + } + } + return false; + } + }, + + /** + * convert an array-like object (`arguments`, `touchlist`) to an array + * @method toArray + * @param {Object} obj + * @return {Array} + */ + toArray: function toArray(obj) { + return Array.prototype.slice.call(obj, 0); + }, + + /** + * find if a node is in the given parent + * @method hasParent + * @param {HTMLElement} node + * @param {HTMLElement} parent + * @return {Boolean} found + */ + hasParent: function hasParent(node, parent) { + while(node) { + if(node == parent) { + return true; + } + node = node.parentNode; + } + return false; + }, + + /** + * get the center of all the touches + * @method getCenter + * @param {Array} touches + * @return {Object} center contains `pageX`, `pageY`, `clientX` and `clientY` properties + */ + getCenter: function getCenter(touches) { + var pageX = [], + pageY = [], + clientX = [], + clientY = [], + min = Math.min, + max = Math.max; + + // no need to loop when only one touch + if(touches.length === 1) { + return { + pageX: touches[0].pageX, + pageY: touches[0].pageY, + clientX: touches[0].clientX, + clientY: touches[0].clientY + }; + } + + Utils.each(touches, function(touch) { + pageX.push(touch.pageX); + pageY.push(touch.pageY); + clientX.push(touch.clientX); + clientY.push(touch.clientY); + }); + + return { + pageX: (min.apply(Math, pageX) + max.apply(Math, pageX)) / 2, + pageY: (min.apply(Math, pageY) + max.apply(Math, pageY)) / 2, + clientX: (min.apply(Math, clientX) + max.apply(Math, clientX)) / 2, + clientY: (min.apply(Math, clientY) + max.apply(Math, clientY)) / 2 + }; + }, + + /** + * calculate the velocity between two points. unit is in px per ms. + * @method getVelocity + * @param {Number} deltaTime + * @param {Number} deltaX + * @param {Number} deltaY + * @return {Object} velocity `x` and `y` + */ + getVelocity: function getVelocity(deltaTime, deltaX, deltaY) { + return { + x: Math.abs(deltaX / deltaTime) || 0, + y: Math.abs(deltaY / deltaTime) || 0 + }; + }, + + /** + * calculate the angle between two coordinates + * @method getAngle + * @param {Touch} touch1 + * @param {Touch} touch2 + * @return {Number} angle + */ + getAngle: function getAngle(touch1, touch2) { + var x = touch2.clientX - touch1.clientX, + y = touch2.clientY - touch1.clientY; + + return Math.atan2(y, x) * 180 / Math.PI; + }, + + /** + * do a small comparision to get the direction between two touches. + * @method getDirection + * @param {Touch} touch1 + * @param {Touch} touch2 + * @return {String} direction matches `DIRECTION_LEFT|RIGHT|UP|DOWN` + */ + getDirection: function getDirection(touch1, touch2) { + var x = Math.abs(touch1.clientX - touch2.clientX), + y = Math.abs(touch1.clientY - touch2.clientY); + + if(x >= y) { + return touch1.clientX - touch2.clientX > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; + } + return touch1.clientY - touch2.clientY > 0 ? DIRECTION_UP : DIRECTION_DOWN; + }, + + /** + * calculate the distance between two touches + * @method getDistance + * @param {Touch}touch1 + * @param {Touch} touch2 + * @return {Number} distance + */ + getDistance: function getDistance(touch1, touch2) { + var x = touch2.clientX - touch1.clientX, + y = touch2.clientY - touch1.clientY; + + return Math.sqrt((x * x) + (y * y)); + }, + + /** + * calculate the scale factor between two touchLists + * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out + * @method getScale + * @param {Array} start array of touches + * @param {Array} end array of touches + * @return {Number} scale + */ + getScale: function getScale(start, end) { + // need two fingers... + if(start.length >= 2 && end.length >= 2) { + return this.getDistance(end[0], end[1]) / this.getDistance(start[0], start[1]); + } + return 1; + }, + + /** + * calculate the rotation degrees between two touchLists + * @method getRotation + * @param {Array} start array of touches + * @param {Array} end array of touches + * @return {Number} rotation + */ + getRotation: function getRotation(start, end) { + // need two fingers + if(start.length >= 2 && end.length >= 2) { + return this.getAngle(end[1], end[0]) - this.getAngle(start[1], start[0]); + } + return 0; + }, + + /** + * find out if the direction is vertical * + * @method isVertical + * @param {String} direction matches `DIRECTION_UP|DOWN` + * @return {Boolean} is_vertical + */ + isVertical: function isVertical(direction) { + return direction == DIRECTION_UP || direction == DIRECTION_DOWN; + }, + + /** + * set css properties with their prefixes + * @param {HTMLElement} element + * @param {String} prop + * @param {String} value + * @param {Boolean} [toggle=true] + * @return {Boolean} + */ + setPrefixedCss: function setPrefixedCss(element, prop, value, toggle) { + var prefixes = ['', 'Webkit', 'Moz', 'O', 'ms']; + prop = Utils.toCamelCase(prop); + + for(var i = 0; i < prefixes.length; i++) { + var p = prop; + // prefixes + if(prefixes[i]) { + p = prefixes[i] + p.slice(0, 1).toUpperCase() + p.slice(1); + } + + // test the style + if(p in element.style) { + element.style[p] = (toggle == null || toggle) && value || ''; + break; + } + } + }, + + /** + * toggle browser default behavior by setting css properties. + * `userSelect='none'` also sets `element.onselectstart` to false + * `userDrag='none'` also sets `element.ondragstart` to false + * + * @method toggleBehavior + * @param {HtmlElement} element + * @param {Object} props + * @param {Boolean} [toggle=true] + */ + toggleBehavior: function toggleBehavior(element, props, toggle) { + if(!props || !element || !element.style) { + return; + } + + // set the css properties + Utils.each(props, function(value, prop) { + Utils.setPrefixedCss(element, prop, value, toggle); + }); + + var falseFn = toggle && function() { + return false; + }; + + // also the disable onselectstart + if(props.userSelect == 'none') { + element.onselectstart = falseFn; + } + // and disable ondragstart + if(props.userDrag == 'none') { + element.ondragstart = falseFn; + } + }, + + /** + * convert a string with underscores to camelCase + * so prevent_default becomes preventDefault + * @param {String} str + * @return {String} camelCaseStr + */ + toCamelCase: function toCamelCase(str) { + return str.replace(/[_-]([a-z])/g, function(s) { + return s[1].toUpperCase(); + }); + } +}; + + +/** + * @module hammer + */ +/** + * @class Event + * @static + */ +var Event = Hammer.event = { + /** + * when touch events have been fired, this is true + * this is used to stop mouse events + * @property prevent_mouseevents + * @private + * @type {Boolean} + */ + preventMouseEvents: false, + + /** + * if EVENT_START has been fired + * @property started + * @private + * @type {Boolean} + */ + started: false, + + /** + * when the mouse is hold down, this is true + * @property should_detect + * @private + * @type {Boolean} + */ + shouldDetect: false, + + /** + * simple event binder with a hook and support for multiple types + * @method on + * @param {HTMLElement} element + * @param {String} type + * @param {Function} handler + * @param {Function} [hook] + * @param {Object} hook.type + */ + on: function on(element, type, handler, hook) { + var types = type.split(' '); + Utils.each(types, function(type) { + Utils.on(element, type, handler); + hook && hook(type); + }); + }, + + /** + * simple event unbinder with a hook and support for multiple types + * @method off + * @param {HTMLElement} element + * @param {String} type + * @param {Function} handler + * @param {Function} [hook] + * @param {Object} hook.type + */ + off: function off(element, type, handler, hook) { + var types = type.split(' '); + Utils.each(types, function(type) { + Utils.off(element, type, handler); + hook && hook(type); + }); + }, + + /** + * the core touch event handler. + * this finds out if we should to detect gestures + * @method onTouch + * @param {HTMLElement} element + * @param {String} eventType matches `EVENT_START|MOVE|END` + * @param {Function} handler + * @return onTouchHandler {Function} the core event handler + */ + onTouch: function onTouch(element, eventType, handler) { + var self = this; + + var onTouchHandler = function onTouchHandler(ev) { + var srcType = ev.type.toLowerCase(), + isPointer = Hammer.HAS_POINTEREVENTS, + isMouse = Utils.inStr(srcType, 'mouse'), + triggerType; + + // if we are in a mouseevent, but there has been a touchevent triggered in this session + // we want to do nothing. simply break out of the event. + if(isMouse && self.preventMouseEvents) { + return; + + // mousebutton must be down + } else if(isMouse && eventType == EVENT_START && ev.button === 0) { + self.preventMouseEvents = false; + self.shouldDetect = true; + } else if(isPointer && eventType == EVENT_START) { + self.shouldDetect = (ev.buttons === 1 || PointerEvent.matchType(POINTER_TOUCH, ev)); + // just a valid start event, but no mouse + } else if(!isMouse && eventType == EVENT_START) { + self.preventMouseEvents = true; + self.shouldDetect = true; + } + + // update the pointer event before entering the detection + if(isPointer && eventType != EVENT_END) { + PointerEvent.updatePointer(eventType, ev); + } + + // we are in a touch/down state, so allowed detection of gestures + if(self.shouldDetect) { + triggerType = self.doDetect.call(self, ev, eventType, element, handler); + } + + // ...and we are done with the detection + // so reset everything to start each detection totally fresh + if(triggerType == EVENT_END) { + self.preventMouseEvents = false; + self.shouldDetect = false; + PointerEvent.reset(); + // update the pointerevent object after the detection + } + + if(isPointer && eventType == EVENT_END) { + PointerEvent.updatePointer(eventType, ev); + } + }; + + this.on(element, EVENT_TYPES[eventType], onTouchHandler); + return onTouchHandler; + }, + + /** + * the core detection method + * this finds out what hammer-touch-events to trigger + * @method doDetect + * @param {Object} ev + * @param {String} eventType matches `EVENT_START|MOVE|END` + * @param {HTMLElement} element + * @param {Function} handler + * @return {String} triggerType matches `EVENT_START|MOVE|END` + */ + doDetect: function doDetect(ev, eventType, element, handler) { + var touchList = this.getTouchList(ev, eventType); + var touchListLength = touchList.length; + var triggerType = eventType; + var triggerChange = touchList.trigger; // used by fakeMultitouch plugin + var changedLength = touchListLength; + + // at each touchstart-like event we want also want to trigger a TOUCH event... + if(eventType == EVENT_START) { + triggerChange = EVENT_TOUCH; + // ...the same for a touchend-like event + } else if(eventType == EVENT_END) { + triggerChange = EVENT_RELEASE; + + // keep track of how many touches have been removed + changedLength = touchList.length - ((ev.changedTouches) ? ev.changedTouches.length : 1); + } + + // after there are still touches on the screen, + // we just want to trigger a MOVE event. so change the START or END to a MOVE + // but only after detection has been started, the first time we actualy want a START + if(changedLength > 0 && this.started) { + triggerType = EVENT_MOVE; + } + + // detection has been started, we keep track of this, see above + this.started = true; + + // generate some event data, some basic information + var evData = this.collectEventData(element, triggerType, touchList, ev); + + // trigger the triggerType event before the change (TOUCH, RELEASE) events + // but the END event should be at last + if(eventType != EVENT_END) { + handler.call(Detection, evData); + } + + // trigger a change (TOUCH, RELEASE) event, this means the length of the touches changed + if(triggerChange) { + evData.changedLength = changedLength; + evData.eventType = triggerChange; + + handler.call(Detection, evData); + + evData.eventType = triggerType; + delete evData.changedLength; + } + + // trigger the END event + if(triggerType == EVENT_END) { + handler.call(Detection, evData); + + // ...and we are done with the detection + // so reset everything to start each detection totally fresh + this.started = false; + } + + return triggerType; + }, + + /** + * we have different events for each device/browser + * determine what we need and set them in the EVENT_TYPES constant + * the `onTouch` method is bind to these properties. + * @method determineEventTypes + * @return {Object} events + */ + determineEventTypes: function determineEventTypes() { + var types; + if(Hammer.HAS_POINTEREVENTS) { + if(window.PointerEvent) { + types = [ + 'pointerdown', + 'pointermove', + 'pointerup pointercancel lostpointercapture' + ]; + } else { + types = [ + 'MSPointerDown', + 'MSPointerMove', + 'MSPointerUp MSPointerCancel MSLostPointerCapture' + ]; + } + } else if(Hammer.NO_MOUSEEVENTS) { + types = [ + 'touchstart', + 'touchmove', + 'touchend touchcancel' + ]; + } else { + types = [ + 'touchstart mousedown', + 'touchmove mousemove', + 'touchend touchcancel mouseup' + ]; + } + + EVENT_TYPES[EVENT_START] = types[0]; + EVENT_TYPES[EVENT_MOVE] = types[1]; + EVENT_TYPES[EVENT_END] = types[2]; + return EVENT_TYPES; + }, + + /** + * create touchList depending on the event + * @method getTouchList + * @param {Object} ev + * @param {String} eventType + * @return {Array} touches + */ + getTouchList: function getTouchList(ev, eventType) { + // get the fake pointerEvent touchlist + if(Hammer.HAS_POINTEREVENTS) { + return PointerEvent.getTouchList(); + } + + // get the touchlist + if(ev.touches) { + if(eventType == EVENT_MOVE) { + return ev.touches; + } + + var identifiers = []; + var concat = [].concat(Utils.toArray(ev.touches), Utils.toArray(ev.changedTouches)); + var touchList = []; + + Utils.each(concat, function(touch) { + if(Utils.inArray(identifiers, touch.identifier) === false) { + touchList.push(touch); + } + identifiers.push(touch.identifier); + }); + + return touchList; + } + + // make fake touchList from mouse position + ev.identifier = 1; + return [ev]; + }, + + /** + * collect basic event data + * @method collectEventData + * @param {HTMLElement} element + * @param {String} eventType matches `EVENT_START|MOVE|END` + * @param {Array} touches + * @param {Object} ev + * @return {Object} ev + */ + collectEventData: function collectEventData(element, eventType, touches, ev) { + // find out pointerType + var pointerType = POINTER_TOUCH; + if(Utils.inStr(ev.type, 'mouse') || PointerEvent.matchType(POINTER_MOUSE, ev)) { + pointerType = POINTER_MOUSE; + } else if(PointerEvent.matchType(POINTER_PEN, ev)) { + pointerType = POINTER_PEN; + } + + return { + center: Utils.getCenter(touches), + timeStamp: Date.now(), + target: ev.target, + touches: touches, + eventType: eventType, + pointerType: pointerType, + srcEvent: ev, + + /** + * prevent the browser default actions + * mostly used to disable scrolling of the browser + */ + preventDefault: function() { + var srcEvent = this.srcEvent; + srcEvent.preventManipulation && srcEvent.preventManipulation(); + srcEvent.preventDefault && srcEvent.preventDefault(); + }, + + /** + * stop bubbling the event up to its parents + */ + stopPropagation: function() { + this.srcEvent.stopPropagation(); + }, + + /** + * immediately stop gesture detection + * might be useful after a swipe was detected + * @return {*} + */ + stopDetect: function() { + return Detection.stopDetect(); + } + }; + } +}; + + +/** + * @module hammer + * + * @class PointerEvent + * @static + */ +var PointerEvent = Hammer.PointerEvent = { + /** + * holds all pointers, by `identifier` + * @property pointers + * @type {Object} + */ + pointers: {}, + + /** + * get the pointers as an array + * @method getTouchList + * @return {Array} touchlist + */ + getTouchList: function getTouchList() { + var touchlist = []; + // we can use forEach since pointerEvents only is in IE10 + Utils.each(this.pointers, function(pointer) { + touchlist.push(pointer); + }); + return touchlist; + }, + + /** + * update the position of a pointer + * @method updatePointer + * @param {String} eventType matches `EVENT_START|MOVE|END` + * @param {Object} pointerEvent + */ + updatePointer: function updatePointer(eventType, pointerEvent) { + if(eventType == EVENT_END || (eventType != EVENT_END && pointerEvent.buttons !== 1)) { + delete this.pointers[pointerEvent.pointerId]; + } else { + pointerEvent.identifier = pointerEvent.pointerId; + this.pointers[pointerEvent.pointerId] = pointerEvent; + } + }, + + /** + * check if ev matches pointertype + * @method matchType + * @param {String} pointerType matches `POINTER_MOUSE|TOUCH|PEN` + * @param {PointerEvent} ev + */ + matchType: function matchType(pointerType, ev) { + if(!ev.pointerType) { + return false; + } + + var pt = ev.pointerType, + types = {}; + + types[POINTER_MOUSE] = (pt === (ev.MSPOINTER_TYPE_MOUSE || POINTER_MOUSE)); + types[POINTER_TOUCH] = (pt === (ev.MSPOINTER_TYPE_TOUCH || POINTER_TOUCH)); + types[POINTER_PEN] = (pt === (ev.MSPOINTER_TYPE_PEN || POINTER_PEN)); + return types[pointerType]; + }, + + /** + * reset the stored pointers + * @method reset + */ + reset: function resetList() { + this.pointers = {}; + } +}; + + +/** + * @module hammer + * + * @class Detection + * @static + */ +var Detection = Hammer.detection = { + // contains all registred Hammer.gestures in the correct order + gestures: [], + + // data of the current Hammer.gesture detection session + current: null, + + // the previous Hammer.gesture session data + // is a full clone of the previous gesture.current object + previous: null, + + // when this becomes true, no gestures are fired + stopped: false, + + /** + * start Hammer.gesture detection + * @method startDetect + * @param {Hammer.Instance} inst + * @param {Object} eventData + */ + startDetect: function startDetect(inst, eventData) { + // already busy with a Hammer.gesture detection on an element + if(this.current) { + return; + } + + this.stopped = false; + + // holds current session + this.current = { + inst: inst, // reference to HammerInstance we're working for + startEvent: Utils.extend({}, eventData), // start eventData for distances, timing etc + lastEvent: false, // last eventData + lastCalcEvent: false, // last eventData for calculations. + futureCalcEvent: false, // last eventData for calculations. + lastCalcData: {}, // last lastCalcData + name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc + }; + + this.detect(eventData); + }, + + /** + * Hammer.gesture detection + * @method detect + * @param {Object} eventData + * @return {any} + */ + detect: function detect(eventData) { + if(!this.current || this.stopped) { + return; + } + + // extend event data with calculations about scale, distance etc + eventData = this.extendEventData(eventData); + + // hammer instance and instance options + var inst = this.current.inst, + instOptions = inst.options; + + // call Hammer.gesture handlers + Utils.each(this.gestures, function triggerGesture(gesture) { + // only when the instance options have enabled this gesture + if(!this.stopped && inst.enabled && instOptions[gesture.name]) { + gesture.handler.call(gesture, eventData, inst); + } + }, this); + + // store as previous event event + if(this.current) { + this.current.lastEvent = eventData; + } + + if(eventData.eventType == EVENT_END) { + this.stopDetect(); + } + + return eventData; + }, + + /** + * clear the Hammer.gesture vars + * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected + * to stop other Hammer.gestures from being fired + * @method stopDetect + */ + stopDetect: function stopDetect() { + // clone current data to the store as the previous gesture + // used for the double tap gesture, since this is an other gesture detect session + this.previous = Utils.extend({}, this.current); + + // reset the current + this.current = null; + this.stopped = true; + }, + + /** + * calculate velocity, angle and direction + * @method getVelocityData + * @param {Object} ev + * @param {Object} center + * @param {Number} deltaTime + * @param {Number} deltaX + * @param {Number} deltaY + */ + getCalculatedData: function getCalculatedData(ev, center, deltaTime, deltaX, deltaY) { + var cur = this.current, + recalc = false, + calcEv = cur.lastCalcEvent, + calcData = cur.lastCalcData; + + if(calcEv && ev.timeStamp - calcEv.timeStamp > Hammer.CALCULATE_INTERVAL) { + center = calcEv.center; + deltaTime = ev.timeStamp - calcEv.timeStamp; + deltaX = ev.center.clientX - calcEv.center.clientX; + deltaY = ev.center.clientY - calcEv.center.clientY; + recalc = true; + } + + if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) { + cur.futureCalcEvent = ev; + } + + if(!cur.lastCalcEvent || recalc) { + calcData.velocity = Utils.getVelocity(deltaTime, deltaX, deltaY); + calcData.angle = Utils.getAngle(center, ev.center); + calcData.direction = Utils.getDirection(center, ev.center); + + cur.lastCalcEvent = cur.futureCalcEvent || ev; + cur.futureCalcEvent = ev; + } + + ev.velocityX = calcData.velocity.x; + ev.velocityY = calcData.velocity.y; + ev.interimAngle = calcData.angle; + ev.interimDirection = calcData.direction; + }, + + /** + * extend eventData for Hammer.gestures + * @method extendEventData + * @param {Object} ev + * @return {Object} ev + */ + extendEventData: function extendEventData(ev) { + var cur = this.current, + startEv = cur.startEvent, + lastEv = cur.lastEvent || startEv; + + // update the start touchlist to calculate the scale/rotation + if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) { + startEv.touches = []; + Utils.each(ev.touches, function(touch) { + startEv.touches.push({ + clientX: touch.clientX, + clientY: touch.clientY + }); + }); + } + + var deltaTime = ev.timeStamp - startEv.timeStamp, + deltaX = ev.center.clientX - startEv.center.clientX, + deltaY = ev.center.clientY - startEv.center.clientY; + + this.getCalculatedData(ev, lastEv.center, deltaTime, deltaX, deltaY); + + Utils.extend(ev, { + startEvent: startEv, + + deltaTime: deltaTime, + deltaX: deltaX, + deltaY: deltaY, + + distance: Utils.getDistance(startEv.center, ev.center), + angle: Utils.getAngle(startEv.center, ev.center), + direction: Utils.getDirection(startEv.center, ev.center), + scale: Utils.getScale(startEv.touches, ev.touches), + rotation: Utils.getRotation(startEv.touches, ev.touches) + }); + + return ev; + }, + + /** + * register new gesture + * @method register + * @param {Object} gesture object, see `gestures/` for documentation + * @return {Array} gestures + */ + register: function register(gesture) { + // add an enable gesture options if there is no given + var options = gesture.defaults || {}; + if(options[gesture.name] === undefined) { + options[gesture.name] = true; + } + + // extend Hammer default options with the Hammer.gesture options + Utils.extend(Hammer.defaults, options, true); + + // set its index + gesture.index = gesture.index || 1000; + + // add Hammer.gesture to the list + this.gestures.push(gesture); + + // sort the list by index + this.gestures.sort(function(a, b) { + if(a.index < b.index) { + return -1; + } + if(a.index > b.index) { + return 1; + } + return 0; + }); + + return this.gestures; + } +}; + + +/** + * @module hammer + */ + +/** + * create new hammer instance + * all methods should return the instance itself, so it is chainable. + * + * @class Instance + * @constructor + * @param {HTMLElement} element + * @param {Object} [options={}] options are merged with `Hammer.defaults` + * @return {Hammer.Instance} + */ +Hammer.Instance = function(element, options) { + var self = this; + + // setup HammerJS window events and register all gestures + // this also sets up the default options + setup(); + + /** + * @property element + * @type {HTMLElement} + */ + this.element = element; + + /** + * @property enabled + * @type {Boolean} + * @protected + */ + this.enabled = true; + + /** + * options, merged with the defaults + * options with an _ are converted to camelCase + * @property options + * @type {Object} + */ + Utils.each(options, function(value, name) { + delete options[name]; + options[Utils.toCamelCase(name)] = value; + }); + + this.options = Utils.extend(Utils.extend({}, Hammer.defaults), options || {}); + + // add some css to the element to prevent the browser from doing its native behavoir + if(this.options.behavior) { + Utils.toggleBehavior(this.element, this.options.behavior, true); + } + + /** + * event start handler on the element to start the detection + * @property eventStartHandler + * @type {Object} + */ + this.eventStartHandler = Event.onTouch(element, EVENT_START, function(ev) { + if(self.enabled && ev.eventType == EVENT_START) { + Detection.startDetect(self, ev); + } else if(ev.eventType == EVENT_TOUCH) { + Detection.detect(ev); + } + }); + + /** + * keep a list of user event handlers which needs to be removed when calling 'dispose' + * @property eventHandlers + * @type {Array} + */ + this.eventHandlers = []; +}; + +Hammer.Instance.prototype = { + /** + * bind events to the instance + * @method on + * @chainable + * @param {String} gestures multiple gestures by splitting with a space + * @param {Function} handler + * @param {Object} handler.ev event object + */ + on: function onEvent(gestures, handler) { + var self = this; + Event.on(self.element, gestures, handler, function(type) { + self.eventHandlers.push({ gesture: type, handler: handler }); + }); + return self; + }, + + /** + * unbind events to the instance + * @method off + * @chainable + * @param {String} gestures + * @param {Function} handler + */ + off: function offEvent(gestures, handler) { + var self = this; + + Event.off(self.element, gestures, handler, function(type) { + var index = Utils.inArray({ gesture: type, handler: handler }); + if(index !== false) { + self.eventHandlers.splice(index, 1); + } + }); + return self; + }, + + /** + * trigger gesture event + * @method trigger + * @chainable + * @param {String} gesture + * @param {Object} [eventData] + */ + trigger: function triggerEvent(gesture, eventData) { + // optional + if(!eventData) { + eventData = {}; + } + + // create DOM event + var event = Hammer.DOCUMENT.createEvent('Event'); + event.initEvent(gesture, true, true); + event.gesture = eventData; + + // trigger on the target if it is in the instance element, + // this is for event delegation tricks + var element = this.element; + if(Utils.hasParent(eventData.target, element)) { + element = eventData.target; + } + + element.dispatchEvent(event); + return this; + }, + + /** + * enable of disable hammer.js detection + * @method enable + * @chainable + * @param {Boolean} state + */ + enable: function enable(state) { + this.enabled = state; + return this; + }, + + /** + * dispose this hammer instance + * @method dispose + * @return {Null} + */ + dispose: function dispose() { + var i, eh; + + // undo all changes made by stop_browser_behavior + Utils.toggleBehavior(this.element, this.options.behavior, false); + + // unbind all custom event handlers + for(i = -1; (eh = this.eventHandlers[++i]);) { + Utils.off(this.element, eh.gesture, eh.handler); + } + + this.eventHandlers = []; + + // unbind the start event listener + Event.off(this.element, EVENT_TYPES[EVENT_START], this.eventStartHandler); + + return null; + } +}; + + +/** + * @module gestures + */ +/** + * Move with x fingers (default 1) around on the page. + * Preventing the default browser behavior is a good way to improve feel and working. + * ```` + * hammertime.on("drag", function(ev) { + * console.log(ev); + * ev.gesture.preventDefault(); + * }); + * ```` + * + * @class Drag + * @static + */ +/** + * @event drag + * @param {Object} ev + */ +/** + * @event dragstart + * @param {Object} ev + */ +/** + * @event dragend + * @param {Object} ev + */ +/** + * @event drapleft + * @param {Object} ev + */ +/** + * @event dragright + * @param {Object} ev + */ +/** + * @event dragup + * @param {Object} ev + */ +/** + * @event dragdown + * @param {Object} ev + */ + +/** + * @param {String} name + */ +(function(name) { + var triggered = false; + + function dragGesture(ev, inst) { + var cur = Detection.current; + + // max touches + if(inst.options.dragMaxTouches > 0 && + ev.touches.length > inst.options.dragMaxTouches) { + return; + } + + switch(ev.eventType) { + case EVENT_START: + triggered = false; + break; + + case EVENT_MOVE: + // when the distance we moved is too small we skip this gesture + // or we can be already in dragging + if(ev.distance < inst.options.dragMinDistance && + cur.name != name) { + return; + } + + var startCenter = cur.startEvent.center; + + // we are dragging! + if(cur.name != name) { + cur.name = name; + if(inst.options.dragDistanceCorrection && ev.distance > 0) { + // When a drag is triggered, set the event center to dragMinDistance pixels from the original event center. + // Without this correction, the dragged distance would jumpstart at dragMinDistance pixels instead of at 0. + // It might be useful to save the original start point somewhere + var factor = Math.abs(inst.options.dragMinDistance / ev.distance); + startCenter.pageX += ev.deltaX * factor; + startCenter.pageY += ev.deltaY * factor; + startCenter.clientX += ev.deltaX * factor; + startCenter.clientY += ev.deltaY * factor; + + // recalculate event data using new start point + ev = Detection.extendEventData(ev); + } + } + + // lock drag to axis? + if(cur.lastEvent.dragLockToAxis || + ( inst.options.dragLockToAxis && + inst.options.dragLockMinDistance <= ev.distance + )) { + ev.dragLockToAxis = true; + } + + // keep direction on the axis that the drag gesture started on + var lastDirection = cur.lastEvent.direction; + if(ev.dragLockToAxis && lastDirection !== ev.direction) { + if(Utils.isVertical(lastDirection)) { + ev.direction = (ev.deltaY < 0) ? DIRECTION_UP : DIRECTION_DOWN; + } else { + ev.direction = (ev.deltaX < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT; + } + } + + // first time, trigger dragstart event + if(!triggered) { + inst.trigger(name + 'start', ev); + triggered = true; + } + + // trigger events + inst.trigger(name, ev); + inst.trigger(name + ev.direction, ev); + + var isVertical = Utils.isVertical(ev.direction); + + // block the browser events + if((inst.options.dragBlockVertical && isVertical) || + (inst.options.dragBlockHorizontal && !isVertical)) { + ev.preventDefault(); + } + break; + + case EVENT_RELEASE: + if(triggered && ev.changedLength <= inst.options.dragMaxTouches) { + inst.trigger(name + 'end', ev); + triggered = false; + } + break; + + case EVENT_END: + triggered = false; + break; + } + } + + Hammer.gestures.Drag = { + name: name, + index: 50, + handler: dragGesture, + defaults: { + /** + * minimal movement that have to be made before the drag event gets triggered + * @property dragMinDistance + * @type {Number} + * @default 10 + */ + dragMinDistance: 10, + + /** + * Set dragDistanceCorrection to true to make the starting point of the drag + * be calculated from where the drag was triggered, not from where the touch started. + * Useful to avoid a jerk-starting drag, which can make fine-adjustments + * through dragging difficult, and be visually unappealing. + * @property dragDistanceCorrection + * @type {Boolean} + * @default true + */ + dragDistanceCorrection: true, + + /** + * set 0 for unlimited, but this can conflict with transform + * @property dragMaxTouches + * @type {Number} + * @default 1 + */ + dragMaxTouches: 1, + + /** + * prevent default browser behavior when dragging occurs + * be careful with it, it makes the element a blocking element + * when you are using the drag gesture, it is a good practice to set this true + * @property dragBlockHorizontal + * @type {Boolean} + * @default false + */ + dragBlockHorizontal: false, + + /** + * same as `dragBlockHorizontal`, but for vertical movement + * @property dragBlockVertical + * @type {Boolean} + * @default false + */ + dragBlockVertical: false, + + /** + * dragLockToAxis keeps the drag gesture on the axis that it started on, + * It disallows vertical directions if the initial direction was horizontal, and vice versa. + * @property dragLockToAxis + * @type {Boolean} + * @default false + */ + dragLockToAxis: false, + + /** + * drag lock only kicks in when distance > dragLockMinDistance + * This way, locking occurs only when the distance has become large enough to reliably determine the direction + * @property dragLockMinDistance + * @type {Number} + * @default 25 + */ + dragLockMinDistance: 25 + } + }; +})('drag'); + +/** + * @module gestures + */ +/** + * trigger a simple gesture event, so you can do anything in your handler. + * only usable if you know what your doing... + * + * @class Gesture + * @static + */ +/** + * @event gesture + * @param {Object} ev + */ +Hammer.gestures.Gesture = { + name: 'gesture', + index: 1337, + handler: function releaseGesture(ev, inst) { + inst.trigger(this.name, ev); + } +}; + +/** + * @module gestures + */ +/** + * Touch stays at the same place for x time + * + * @class Hold + * @static + */ +/** + * @event hold + * @param {Object} ev + */ + +/** + * @param {String} name + */ +(function(name) { + var timer; + + function holdGesture(ev, inst) { + var options = inst.options, + current = Detection.current; + + switch(ev.eventType) { + case EVENT_START: + clearTimeout(timer); + + // set the gesture so we can check in the timeout if it still is + current.name = name; + + // set timer and if after the timeout it still is hold, + // we trigger the hold event + timer = setTimeout(function() { + if(current && current.name == name) { + inst.trigger(name, ev); + } + }, options.holdTimeout); + break; + + case EVENT_MOVE: + if(ev.distance > options.holdThreshold) { + clearTimeout(timer); + } + break; + + case EVENT_RELEASE: + clearTimeout(timer); + break; + } + } + + Hammer.gestures.Hold = { + name: name, + index: 10, + defaults: { + /** + * @property holdTimeout + * @type {Number} + * @default 500 + */ + holdTimeout: 500, + + /** + * movement allowed while holding + * @property holdThreshold + * @type {Number} + * @default 2 + */ + holdThreshold: 2 + }, + handler: holdGesture + }; +})('hold'); + +/** + * @module gestures + */ +/** + * when a touch is being released from the page + * + * @class Release + * @static + */ +/** + * @event release + * @param {Object} ev + */ +Hammer.gestures.Release = { + name: 'release', + index: Infinity, + handler: function releaseGesture(ev, inst) { + if(ev.eventType == EVENT_RELEASE) { + inst.trigger(this.name, ev); + } + } +}; + +/** + * @module gestures + */ +/** + * triggers swipe events when the end velocity is above the threshold + * for best usage, set `preventDefault` (on the drag gesture) to `true` + * ```` + * hammertime.on("dragleft swipeleft", function(ev) { + * console.log(ev); + * ev.gesture.preventDefault(); + * }); + * ```` + * + * @class Swipe + * @static + */ +/** + * @event swipe + * @param {Object} ev + */ +/** + * @event swipeleft + * @param {Object} ev + */ +/** + * @event swiperight + * @param {Object} ev + */ +/** + * @event swipeup + * @param {Object} ev + */ +/** + * @event swipedown + * @param {Object} ev + */ +Hammer.gestures.Swipe = { + name: 'swipe', + index: 40, + defaults: { + /** + * @property swipeMinTouches + * @type {Number} + * @default 1 + */ + swipeMinTouches: 1, + + /** + * @property swipeMaxTouches + * @type {Number} + * @default 1 + */ + swipeMaxTouches: 1, + + /** + * horizontal swipe velocity + * @property swipeVelocityX + * @type {Number} + * @default 0.6 + */ + swipeVelocityX: 0.6, + + /** + * vertical swipe velocity + * @property swipeVelocityY + * @type {Number} + * @default 0.6 + */ + swipeVelocityY: 0.6 + }, + + handler: function swipeGesture(ev, inst) { + if(ev.eventType == EVENT_RELEASE) { + var touches = ev.touches.length, + options = inst.options; + + // max touches + if(touches < options.swipeMinTouches || + touches > options.swipeMaxTouches) { + return; + } + + // when the distance we moved is too small we skip this gesture + // or we can be already in dragging + if(ev.velocityX > options.swipeVelocityX || + ev.velocityY > options.swipeVelocityY) { + // trigger swipe events + inst.trigger(this.name, ev); + inst.trigger(this.name + ev.direction, ev); + } + } + } +}; + +/** + * @module gestures + */ +/** + * Single tap and a double tap on a place + * + * @class Tap + * @static + */ +/** + * @event tap + * @param {Object} ev + */ +/** + * @event doubletap + * @param {Object} ev + */ + +/** + * @param {String} name + */ +(function(name) { + var hasMoved = false; + + function tapGesture(ev, inst) { + var options = inst.options, + current = Detection.current, + prev = Detection.previous, + sincePrev, + didDoubleTap; + + switch(ev.eventType) { + case EVENT_START: + hasMoved = false; + break; + + case EVENT_MOVE: + hasMoved = hasMoved || (ev.distance > options.tapMaxDistance); + break; + + case EVENT_END: + if(!Utils.inStr(ev.srcEvent.type, 'cancel') && ev.deltaTime < options.tapMaxTime && !hasMoved) { + // previous gesture, for the double tap since these are two different gesture detections + sincePrev = prev && prev.lastEvent && ev.timeStamp - prev.lastEvent.timeStamp; + didDoubleTap = false; + + // check if double tap + if(prev && prev.name == name && + (sincePrev && sincePrev < options.doubleTapInterval) && + ev.distance < options.doubleTapDistance) { + inst.trigger('doubletap', ev); + didDoubleTap = true; + } + + // do a single tap + if(!didDoubleTap || options.tapAlways) { + current.name = name; + inst.trigger(current.name, ev); + } + } + break; + } + } + + Hammer.gestures.Tap = { + name: name, + index: 100, + handler: tapGesture, + defaults: { + /** + * max time of a tap, this is for the slow tappers + * @property tapMaxTime + * @type {Number} + * @default 250 + */ + tapMaxTime: 250, + + /** + * max distance of movement of a tap, this is for the slow tappers + * @property tapMaxDistance + * @type {Number} + * @default 10 + */ + tapMaxDistance: 10, + + /** + * always trigger the `tap` event, even while double-tapping + * @property tapAlways + * @type {Boolean} + * @default true + */ + tapAlways: true, + + /** + * max distance between two taps + * @property doubleTapDistance + * @type {Number} + * @default 20 + */ + doubleTapDistance: 20, + + /** + * max time between two taps + * @property doubleTapInterval + * @type {Number} + * @default 300 + */ + doubleTapInterval: 300 + } + }; +})('tap'); + +/** + * @module gestures + */ +/** + * when a touch is being touched at the page + * + * @class Touch + * @static + */ +/** + * @event touch + * @param {Object} ev + */ +Hammer.gestures.Touch = { + name: 'touch', + index: -Infinity, + defaults: { + /** + * call preventDefault at touchstart, and makes the element blocking by disabling the scrolling of the page, + * but it improves gestures like transforming and dragging. + * be careful with using this, it can be very annoying for users to be stuck on the page + * @property preventDefault + * @type {Boolean} + * @default false + */ + preventDefault: false, + + /** + * disable mouse events, so only touch (or pen!) input triggers events + * @property preventMouse + * @type {Boolean} + * @default false + */ + preventMouse: false + }, + handler: function touchGesture(ev, inst) { + if(inst.options.preventMouse && ev.pointerType == POINTER_MOUSE) { + ev.stopDetect(); + return; + } + + if(inst.options.preventDefault) { + ev.preventDefault(); + } + + if(ev.eventType == EVENT_TOUCH) { + inst.trigger('touch', ev); + } + } +}; + +/** + * @module gestures + */ +/** + * User want to scale or rotate with 2 fingers + * Preventing the default browser behavior is a good way to improve feel and working. This can be done with the + * `preventDefault` option. + * + * @class Transform + * @static + */ +/** + * @event transform + * @param {Object} ev + */ +/** + * @event transformstart + * @param {Object} ev + */ +/** + * @event transformend + * @param {Object} ev + */ +/** + * @event pinchin + * @param {Object} ev + */ +/** + * @event pinchout + * @param {Object} ev + */ +/** + * @event rotate + * @param {Object} ev + */ + +/** + * @param {String} name + */ +(function(name) { + var triggered = false; + + function transformGesture(ev, inst) { + switch(ev.eventType) { + case EVENT_START: + triggered = false; + break; + + case EVENT_MOVE: + // at least multitouch + if(ev.touches.length < 2) { + return; + } + + var scaleThreshold = Math.abs(1 - ev.scale); + var rotationThreshold = Math.abs(ev.rotation); + + // when the distance we moved is too small we skip this gesture + // or we can be already in dragging + if(scaleThreshold < inst.options.transformMinScale && + rotationThreshold < inst.options.transformMinRotation) { + return; + } + + // we are transforming! + Detection.current.name = name; + + // first time, trigger dragstart event + if(!triggered) { + inst.trigger(name + 'start', ev); + triggered = true; + } + + inst.trigger(name, ev); // basic transform event + + // trigger rotate event + if(rotationThreshold > inst.options.transformMinRotation) { + inst.trigger('rotate', ev); + } + + // trigger pinch event + if(scaleThreshold > inst.options.transformMinScale) { + inst.trigger('pinch', ev); + inst.trigger('pinch' + (ev.scale < 1 ? 'in' : 'out'), ev); + } + break; + + case EVENT_RELEASE: + if(triggered && ev.changedLength < 2) { + inst.trigger(name + 'end', ev); + triggered = false; + } + break; + } + } + + Hammer.gestures.Transform = { + name: name, + index: 45, + defaults: { + /** + * minimal scale factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1 + * @property transformMinScale + * @type {Number} + * @default 0.01 + */ + transformMinScale: 0.01, + + /** + * rotation in degrees + * @property transformMinRotation + * @type {Number} + * @default 1 + */ + transformMinRotation: 1 + }, + + handler: transformGesture + }; +})('transform'); + +/** + * @module hammer + */ + +// AMD export +if(typeof define == 'function' && define.amd) { + define(function() { + return Hammer; + }); +// commonjs export +} else if(typeof module !== 'undefined' && module.exports) { + module.exports = Hammer; +// browser export +} else { + window.Hammer = Hammer; +} + +})(window); +},{}],32:[function(_dereq_,module,exports){ +if (!('indexOf' in Array.prototype)) { + Array.prototype.indexOf= function(find, i /*opt*/) { + if (i===undefined) i= 0; + if (i<0) i+= this.length; + if (i<0) i= 0; + for (var n= this.length; i= 0) { + name = type.substring(i + 1); + type = type.substring(0, i); + } + + if (type) return arguments.length < 2 + ? this[type].on(name) + : this[type].on(name, listener); + + if (arguments.length === 2) { + if (listener == null) for (type in this) { + if (this.hasOwnProperty(type)) this[type].on(name, null); + } + return this; + } +}; + +function d3_dispatch_event(dispatch) { + var listeners = [], + listenerByName = new d3_Map; + + function event() { + var z = listeners, // defensive reference + i = -1, + n = z.length, + l; + while (++i < n) if (l = z[i].on) l.apply(this, arguments); + return dispatch; + } + + event.on = function(name, listener) { + var l = listenerByName.get(name), + i; + + // return the current listener, if any + if (arguments.length < 2) return l && l.on; + + // remove the old listener, if any (with copy-on-write) + if (l) { + l.on = null; + listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); + listenerByName.remove(name); + } + + // add the new listener, if any + if (listener) listeners.push(listenerByName.set(name, {on: listener})); + + return dispatch; + }; + + return event; +} +// Copies a variable number of methods from source to target. +d3.rebind = function(target, source) { + var i = 1, n = arguments.length, method; + while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); + return target; +}; + +// Method is assumed to be a standard D3 getter-setter: +// If passed with no arguments, gets the value. +// If passed with arguments, sets the value and returns the target. +function d3_rebind(target, source, method) { + return function() { + var value = method.apply(source, arguments); + return value === source ? target : value; + }; +} + return d3; +})(); + +},{}],33:[function(_dereq_,module,exports){ +// Released under MIT license +// Copyright (c) 2009-2010 Dominic Baggott +// Copyright (c) 2009-2010 Ash Berlin +// Copyright (c) 2011 Christoph Dorn (http://www.christophdorn.com) +// Date: 2013-09-15T16:12Z + +(function(expose) { + + + + + var MarkdownHelpers = {}; + + // For Spidermonkey based engines + function mk_block_toSource() { + return "Markdown.mk_block( " + + uneval(this.toString()) + + ", " + + uneval(this.trailing) + + ", " + + uneval(this.lineNumber) + + " )"; + } + + // node + function mk_block_inspect() { + var util = _dereq_("util"); + return "Markdown.mk_block( " + + util.inspect(this.toString()) + + ", " + + util.inspect(this.trailing) + + ", " + + util.inspect(this.lineNumber) + + " )"; + + } + + MarkdownHelpers.mk_block = function(block, trail, line) { + // Be helpful for default case in tests. + if ( arguments.length === 1 ) + trail = "\n\n"; + + // We actually need a String object, not a string primitive + /* jshint -W053 */ + var s = new String(block); + s.trailing = trail; + // To make it clear its not just a string + s.inspect = mk_block_inspect; + s.toSource = mk_block_toSource; + + if ( line !== undefined ) + s.lineNumber = line; + + return s; + }; + + + var isArray = MarkdownHelpers.isArray = Array.isArray || function(obj) { + return Object.prototype.toString.call(obj) === "[object Array]"; + }; + + // Don't mess with Array.prototype. Its not friendly + if ( Array.prototype.forEach ) { + MarkdownHelpers.forEach = function forEach( arr, cb, thisp ) { + return arr.forEach( cb, thisp ); + }; + } + else { + MarkdownHelpers.forEach = function forEach(arr, cb, thisp) { + for (var i = 0; i < arr.length; i++) + cb.call(thisp || arr, arr[i], i, arr); + }; + } + + MarkdownHelpers.isEmpty = function isEmpty( obj ) { + for ( var key in obj ) { + if ( hasOwnProperty.call( obj, key ) ) + return false; + } + return true; + }; + + MarkdownHelpers.extract_attr = function extract_attr( jsonml ) { + return isArray(jsonml) + && jsonml.length > 1 + && typeof jsonml[ 1 ] === "object" + && !( isArray(jsonml[ 1 ]) ) + ? jsonml[ 1 ] + : undefined; + }; + + + + + /** + * class Markdown + * + * Markdown processing in Javascript done right. We have very particular views + * on what constitutes 'right' which include: + * + * - produces well-formed HTML (this means that em and strong nesting is + * important) + * + * - has an intermediate representation to allow processing of parsed data (We + * in fact have two, both as [JsonML]: a markdown tree and an HTML tree). + * + * - is easily extensible to add new dialects without having to rewrite the + * entire parsing mechanics + * + * - has a good test suite + * + * This implementation fulfills all of these (except that the test suite could + * do with expanding to automatically run all the fixtures from other Markdown + * implementations.) + * + * ##### Intermediate Representation + * + * *TODO* Talk about this :) Its JsonML, but document the node names we use. + * + * [JsonML]: http://jsonml.org/ "JSON Markup Language" + **/ + var Markdown = function(dialect) { + switch (typeof dialect) { + case "undefined": + this.dialect = Markdown.dialects.Gruber; + break; + case "object": + this.dialect = dialect; + break; + default: + if ( dialect in Markdown.dialects ) + this.dialect = Markdown.dialects[dialect]; + else + throw new Error("Unknown Markdown dialect '" + String(dialect) + "'"); + break; + } + this.em_state = []; + this.strong_state = []; + this.debug_indent = ""; + }; + + /** + * Markdown.dialects + * + * Namespace of built-in dialects. + **/ + Markdown.dialects = {}; + + + + + // Imported functions + var mk_block = Markdown.mk_block = MarkdownHelpers.mk_block, + isArray = MarkdownHelpers.isArray; + + /** + * parse( markdown, [dialect] ) -> JsonML + * - markdown (String): markdown string to parse + * - dialect (String | Dialect): the dialect to use, defaults to gruber + * + * Parse `markdown` and return a markdown document as a Markdown.JsonML tree. + **/ + Markdown.parse = function( source, dialect ) { + // dialect will default if undefined + var md = new Markdown( dialect ); + return md.toTree( source ); + }; + + function count_lines( str ) { + var n = 0, + i = -1; + while ( ( i = str.indexOf("\n", i + 1) ) !== -1 ) + n++; + return n; + } + + // Internal - split source into rough blocks + Markdown.prototype.split_blocks = function splitBlocks( input ) { + input = input.replace(/(\r\n|\n|\r)/g, "\n"); + // [\s\S] matches _anything_ (newline or space) + // [^] is equivalent but doesn't work in IEs. + var re = /([\s\S]+?)($|\n#|\n(?:\s*\n|$)+)/g, + blocks = [], + m; + + var line_no = 1; + + if ( ( m = /^(\s*\n)/.exec(input) ) !== null ) { + // skip (but count) leading blank lines + line_no += count_lines( m[0] ); + re.lastIndex = m[0].length; + } + + while ( ( m = re.exec(input) ) !== null ) { + if (m[2] === "\n#") { + m[2] = "\n"; + re.lastIndex--; + } + blocks.push( mk_block( m[1], m[2], line_no ) ); + line_no += count_lines( m[0] ); + } + + return blocks; + }; + + /** + * Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ] + * - block (String): the block to process + * - next (Array): the following blocks + * + * Process `block` and return an array of JsonML nodes representing `block`. + * + * It does this by asking each block level function in the dialect to process + * the block until one can. Succesful handling is indicated by returning an + * array (with zero or more JsonML nodes), failure by a false value. + * + * Blocks handlers are responsible for calling [[Markdown#processInline]] + * themselves as appropriate. + * + * If the blocks were split incorrectly or adjacent blocks need collapsing you + * can adjust `next` in place using shift/splice etc. + * + * If any of this default behaviour is not right for the dialect, you can + * define a `__call__` method on the dialect that will get invoked to handle + * the block processing. + */ + Markdown.prototype.processBlock = function processBlock( block, next ) { + var cbs = this.dialect.block, + ord = cbs.__order__; + + if ( "__call__" in cbs ) + return cbs.__call__.call(this, block, next); + + for ( var i = 0; i < ord.length; i++ ) { + //D:this.debug( "Testing", ord[i] ); + var res = cbs[ ord[i] ].call( this, block, next ); + if ( res ) { + //D:this.debug(" matched"); + if ( !isArray(res) || ( res.length > 0 && !( isArray(res[0]) ) ) ) + this.debug(ord[i], "didn't return a proper array"); + //D:this.debug( "" ); + return res; + } + } + + // Uhoh! no match! Should we throw an error? + return []; + }; + + Markdown.prototype.processInline = function processInline( block ) { + return this.dialect.inline.__call__.call( this, String( block ) ); + }; + + /** + * Markdown#toTree( source ) -> JsonML + * - source (String): markdown source to parse + * + * Parse `source` into a JsonML tree representing the markdown document. + **/ + // custom_tree means set this.tree to `custom_tree` and restore old value on return + Markdown.prototype.toTree = function toTree( source, custom_root ) { + var blocks = source instanceof Array ? source : this.split_blocks( source ); + + // Make tree a member variable so its easier to mess with in extensions + var old_tree = this.tree; + try { + this.tree = custom_root || this.tree || [ "markdown" ]; + + blocks_loop: + while ( blocks.length ) { + var b = this.processBlock( blocks.shift(), blocks ); + + // Reference blocks and the like won't return any content + if ( !b.length ) + continue blocks_loop; + + this.tree.push.apply( this.tree, b ); + } + return this.tree; + } + finally { + if ( custom_root ) + this.tree = old_tree; + } + }; + + // Noop by default + Markdown.prototype.debug = function () { + var args = Array.prototype.slice.call( arguments); + args.unshift(this.debug_indent); + if ( typeof print !== "undefined" ) + print.apply( print, args ); + if ( typeof console !== "undefined" && typeof console.log !== "undefined" ) + console.log.apply( null, args ); + }; + + Markdown.prototype.loop_re_over_block = function( re, block, cb ) { + // Dont use /g regexps with this + var m, + b = block.valueOf(); + + while ( b.length && (m = re.exec(b) ) !== null ) { + b = b.substr( m[0].length ); + cb.call(this, m); + } + return b; + }; + + // Build default order from insertion order. + Markdown.buildBlockOrder = function(d) { + var ord = []; + for ( var i in d ) { + if ( i === "__order__" || i === "__call__" ) + continue; + ord.push( i ); + } + d.__order__ = ord; + }; + + // Build patterns for inline matcher + Markdown.buildInlinePatterns = function(d) { + var patterns = []; + + for ( var i in d ) { + // __foo__ is reserved and not a pattern + if ( i.match( /^__.*__$/) ) + continue; + var l = i.replace( /([\\.*+?|()\[\]{}])/g, "\\$1" ) + .replace( /\n/, "\\n" ); + patterns.push( i.length === 1 ? l : "(?:" + l + ")" ); + } + + patterns = patterns.join("|"); + d.__patterns__ = patterns; + //print("patterns:", uneval( patterns ) ); + + var fn = d.__call__; + d.__call__ = function(text, pattern) { + if ( pattern !== undefined ) + return fn.call(this, text, pattern); + else + return fn.call(this, text, patterns); + }; + }; + + + + + var extract_attr = MarkdownHelpers.extract_attr; + + /** + * renderJsonML( jsonml[, options] ) -> String + * - jsonml (Array): JsonML array to render to XML + * - options (Object): options + * + * Converts the given JsonML into well-formed XML. + * + * The options currently understood are: + * + * - root (Boolean): wether or not the root node should be included in the + * output, or just its children. The default `false` is to not include the + * root itself. + */ + Markdown.renderJsonML = function( jsonml, options ) { + options = options || {}; + // include the root element in the rendered output? + options.root = options.root || false; + + var content = []; + + if ( options.root ) { + content.push( render_tree( jsonml ) ); + } + else { + jsonml.shift(); // get rid of the tag + if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) + jsonml.shift(); // get rid of the attributes + + while ( jsonml.length ) + content.push( render_tree( jsonml.shift() ) ); + } + + return content.join( "\n\n" ); + }; + + + /** + * toHTMLTree( markdown, [dialect] ) -> JsonML + * toHTMLTree( md_tree ) -> JsonML + * - markdown (String): markdown string to parse + * - dialect (String | Dialect): the dialect to use, defaults to gruber + * - md_tree (Markdown.JsonML): parsed markdown tree + * + * Turn markdown into HTML, represented as a JsonML tree. If a string is given + * to this function, it is first parsed into a markdown tree by calling + * [[parse]]. + **/ + Markdown.toHTMLTree = function toHTMLTree( input, dialect , options ) { + + // convert string input to an MD tree + if ( typeof input === "string" ) + input = this.parse( input, dialect ); + + // Now convert the MD tree to an HTML tree + + // remove references from the tree + var attrs = extract_attr( input ), + refs = {}; + + if ( attrs && attrs.references ) + refs = attrs.references; + + var html = convert_tree_to_html( input, refs , options ); + merge_text_nodes( html ); + return html; + }; + + /** + * toHTML( markdown, [dialect] ) -> String + * toHTML( md_tree ) -> String + * - markdown (String): markdown string to parse + * - md_tree (Markdown.JsonML): parsed markdown tree + * + * Take markdown (either as a string or as a JsonML tree) and run it through + * [[toHTMLTree]] then turn it into a well-formated HTML fragment. + **/ + Markdown.toHTML = function toHTML( source , dialect , options ) { + var input = this.toHTMLTree( source , dialect , options ); + + return this.renderJsonML( input ); + }; + + + function escapeHTML( text ) { + return text.replace( /&/g, "&" ) + .replace( //g, ">" ) + .replace( /"/g, """ ) + .replace( /'/g, "'" ); + } + + function render_tree( jsonml ) { + // basic case + if ( typeof jsonml === "string" ) + return escapeHTML( jsonml ); + + var tag = jsonml.shift(), + attributes = {}, + content = []; + + if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) + attributes = jsonml.shift(); + + while ( jsonml.length ) + content.push( render_tree( jsonml.shift() ) ); + + var tag_attrs = ""; + for ( var a in attributes ) + tag_attrs += " " + a + '="' + escapeHTML( attributes[ a ] ) + '"'; + + // be careful about adding whitespace here for inline elements + if ( tag === "img" || tag === "br" || tag === "hr" ) + return "<"+ tag + tag_attrs + "/>"; + else + return "<"+ tag + tag_attrs + ">" + content.join( "" ) + ""; + } + + function convert_tree_to_html( tree, references, options ) { + var i; + options = options || {}; + + // shallow clone + var jsonml = tree.slice( 0 ); + + if ( typeof options.preprocessTreeNode === "function" ) + jsonml = options.preprocessTreeNode(jsonml, references); + + // Clone attributes if they exist + var attrs = extract_attr( jsonml ); + if ( attrs ) { + jsonml[ 1 ] = {}; + for ( i in attrs ) { + jsonml[ 1 ][ i ] = attrs[ i ]; + } + attrs = jsonml[ 1 ]; + } + + // basic case + if ( typeof jsonml === "string" ) + return jsonml; + + // convert this node + switch ( jsonml[ 0 ] ) { + case "header": + jsonml[ 0 ] = "h" + jsonml[ 1 ].level; + delete jsonml[ 1 ].level; + break; + case "bulletlist": + jsonml[ 0 ] = "ul"; + break; + case "numberlist": + jsonml[ 0 ] = "ol"; + break; + case "listitem": + jsonml[ 0 ] = "li"; + break; + case "para": + jsonml[ 0 ] = "p"; + break; + case "markdown": + jsonml[ 0 ] = "html"; + if ( attrs ) + delete attrs.references; + break; + case "code_block": + jsonml[ 0 ] = "pre"; + i = attrs ? 2 : 1; + var code = [ "code" ]; + code.push.apply( code, jsonml.splice( i, jsonml.length - i ) ); + jsonml[ i ] = code; + break; + case "inlinecode": + jsonml[ 0 ] = "code"; + break; + case "img": + jsonml[ 1 ].src = jsonml[ 1 ].href; + delete jsonml[ 1 ].href; + break; + case "linebreak": + jsonml[ 0 ] = "br"; + break; + case "link": + jsonml[ 0 ] = "a"; + break; + case "link_ref": + jsonml[ 0 ] = "a"; + + // grab this ref and clean up the attribute node + var ref = references[ attrs.ref ]; + + // if the reference exists, make the link + if ( ref ) { + delete attrs.ref; + + // add in the href and title, if present + attrs.href = ref.href; + if ( ref.title ) + attrs.title = ref.title; + + // get rid of the unneeded original text + delete attrs.original; + } + // the reference doesn't exist, so revert to plain text + else { + return attrs.original; + } + break; + case "img_ref": + jsonml[ 0 ] = "img"; + + // grab this ref and clean up the attribute node + var ref = references[ attrs.ref ]; + + // if the reference exists, make the link + if ( ref ) { + delete attrs.ref; + + // add in the href and title, if present + attrs.src = ref.href; + if ( ref.title ) + attrs.title = ref.title; + + // get rid of the unneeded original text + delete attrs.original; + } + // the reference doesn't exist, so revert to plain text + else { + return attrs.original; + } + break; + } + + // convert all the children + i = 1; + + // deal with the attribute node, if it exists + if ( attrs ) { + // if there are keys, skip over it + for ( var key in jsonml[ 1 ] ) { + i = 2; + break; + } + // if there aren't, remove it + if ( i === 1 ) + jsonml.splice( i, 1 ); + } + + for ( ; i < jsonml.length; ++i ) { + jsonml[ i ] = convert_tree_to_html( jsonml[ i ], references, options ); + } + + return jsonml; + } + + + // merges adjacent text nodes into a single node + function merge_text_nodes( jsonml ) { + // skip the tag name and attribute hash + var i = extract_attr( jsonml ) ? 2 : 1; + + while ( i < jsonml.length ) { + // if it's a string check the next item too + if ( typeof jsonml[ i ] === "string" ) { + if ( i + 1 < jsonml.length && typeof jsonml[ i + 1 ] === "string" ) { + // merge the second string into the first and remove it + jsonml[ i ] += jsonml.splice( i + 1, 1 )[ 0 ]; + } + else { + ++i; + } + } + // if it's not a string recurse + else { + merge_text_nodes( jsonml[ i ] ); + ++i; + } + } + }; + + + + var DialectHelpers = {}; + DialectHelpers.inline_until_char = function( text, want ) { + var consumed = 0, + nodes = []; + + while ( true ) { + if ( text.charAt( consumed ) === want ) { + // Found the character we were looking for + consumed++; + return [ consumed, nodes ]; + } + + if ( consumed >= text.length ) { + // No closing char found. Abort. + return null; + } + + var res = this.dialect.inline.__oneElement__.call(this, text.substr( consumed ) ); + consumed += res[ 0 ]; + // Add any returned nodes. + nodes.push.apply( nodes, res.slice( 1 ) ); + } + }; + + // Helper function to make sub-classing a dialect easier + DialectHelpers.subclassDialect = function( d ) { + function Block() {} + Block.prototype = d.block; + function Inline() {} + Inline.prototype = d.inline; + + return { block: new Block(), inline: new Inline() }; + }; + + + + + var forEach = MarkdownHelpers.forEach, + extract_attr = MarkdownHelpers.extract_attr, + mk_block = MarkdownHelpers.mk_block, + isEmpty = MarkdownHelpers.isEmpty, + inline_until_char = DialectHelpers.inline_until_char; + + /** + * Gruber dialect + * + * The default dialect that follows the rules set out by John Gruber's + * markdown.pl as closely as possible. Well actually we follow the behaviour of + * that script which in some places is not exactly what the syntax web page + * says. + **/ + var Gruber = { + block: { + atxHeader: function atxHeader( block, next ) { + var m = block.match( /^(#{1,6})\s*(.*?)\s*#*\s*(?:\n|$)/ ); + + if ( !m ) + return undefined; + + var header = [ "header", { level: m[ 1 ].length } ]; + Array.prototype.push.apply(header, this.processInline(m[ 2 ])); + + if ( m[0].length < block.length ) + next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); + + return [ header ]; + }, + + setextHeader: function setextHeader( block, next ) { + var m = block.match( /^(.*)\n([-=])\2\2+(?:\n|$)/ ); + + if ( !m ) + return undefined; + + var level = ( m[ 2 ] === "=" ) ? 1 : 2, + header = [ "header", { level : level }, m[ 1 ] ]; + + if ( m[0].length < block.length ) + next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); + + return [ header ]; + }, + + code: function code( block, next ) { + // | Foo + // |bar + // should be a code block followed by a paragraph. Fun + // + // There might also be adjacent code block to merge. + + var ret = [], + re = /^(?: {0,3}\t| {4})(.*)\n?/; + + // 4 spaces + content + if ( !block.match( re ) ) + return undefined; + + block_search: + do { + // Now pull out the rest of the lines + var b = this.loop_re_over_block( + re, block.valueOf(), function( m ) { ret.push( m[1] ); } ); + + if ( b.length ) { + // Case alluded to in first comment. push it back on as a new block + next.unshift( mk_block(b, block.trailing) ); + break block_search; + } + else if ( next.length ) { + // Check the next block - it might be code too + if ( !next[0].match( re ) ) + break block_search; + + // Pull how how many blanks lines follow - minus two to account for .join + ret.push ( block.trailing.replace(/[^\n]/g, "").substring(2) ); + + block = next.shift(); + } + else { + break block_search; + } + } while ( true ); + + return [ [ "code_block", ret.join("\n") ] ]; + }, + + horizRule: function horizRule( block, next ) { + // this needs to find any hr in the block to handle abutting blocks + var m = block.match( /^(?:([\s\S]*?)\n)?[ \t]*([-_*])(?:[ \t]*\2){2,}[ \t]*(?:\n([\s\S]*))?$/ ); + + if ( !m ) + return undefined; + + var jsonml = [ [ "hr" ] ]; + + // if there's a leading abutting block, process it + if ( m[ 1 ] ) { + var contained = mk_block( m[ 1 ], "", block.lineNumber ); + jsonml.unshift.apply( jsonml, this.toTree( contained, [] ) ); + } + + // if there's a trailing abutting block, stick it into next + if ( m[ 3 ] ) + next.unshift( mk_block( m[ 3 ], block.trailing, block.lineNumber + 1 ) ); + + return jsonml; + }, + + // There are two types of lists. Tight and loose. Tight lists have no whitespace + // between the items (and result in text just in the
  • ) and loose lists, + // which have an empty line between list items, resulting in (one or more) + // paragraphs inside the
  • . + // + // There are all sorts weird edge cases about the original markdown.pl's + // handling of lists: + // + // * Nested lists are supposed to be indented by four chars per level. But + // if they aren't, you can get a nested list by indenting by less than + // four so long as the indent doesn't match an indent of an existing list + // item in the 'nest stack'. + // + // * The type of the list (bullet or number) is controlled just by the + // first item at the indent. Subsequent changes are ignored unless they + // are for nested lists + // + lists: (function( ) { + // Use a closure to hide a few variables. + var any_list = "[*+-]|\\d+\\.", + bullet_list = /[*+-]/, + // Capture leading indent as it matters for determining nested lists. + is_list_re = new RegExp( "^( {0,3})(" + any_list + ")[ \t]+" ), + indent_re = "(?: {0,3}\\t| {4})"; + + // TODO: Cache this regexp for certain depths. + // Create a regexp suitable for matching an li for a given stack depth + function regex_for_depth( depth ) { + + return new RegExp( + // m[1] = indent, m[2] = list_type + "(?:^(" + indent_re + "{0," + depth + "} {0,3})(" + any_list + ")\\s+)|" + + // m[3] = cont + "(^" + indent_re + "{0," + (depth-1) + "}[ ]{0,4})" + ); + } + function expand_tab( input ) { + return input.replace( / {0,3}\t/g, " " ); + } + + // Add inline content `inline` to `li`. inline comes from processInline + // so is an array of content + function add(li, loose, inline, nl) { + if ( loose ) { + li.push( [ "para" ].concat(inline) ); + return; + } + // Hmmm, should this be any block level element or just paras? + var add_to = li[li.length -1] instanceof Array && li[li.length - 1][0] === "para" + ? li[li.length -1] + : li; + + // If there is already some content in this list, add the new line in + if ( nl && li.length > 1 ) + inline.unshift(nl); + + for ( var i = 0; i < inline.length; i++ ) { + var what = inline[i], + is_str = typeof what === "string"; + if ( is_str && add_to.length > 1 && typeof add_to[add_to.length-1] === "string" ) + add_to[ add_to.length-1 ] += what; + else + add_to.push( what ); + } + } + + // contained means have an indent greater than the current one. On + // *every* line in the block + function get_contained_blocks( depth, blocks ) { + + var re = new RegExp( "^(" + indent_re + "{" + depth + "}.*?\\n?)*$" ), + replace = new RegExp("^" + indent_re + "{" + depth + "}", "gm"), + ret = []; + + while ( blocks.length > 0 ) { + if ( re.exec( blocks[0] ) ) { + var b = blocks.shift(), + // Now remove that indent + x = b.replace( replace, ""); + + ret.push( mk_block( x, b.trailing, b.lineNumber ) ); + } + else + break; + } + return ret; + } + + // passed to stack.forEach to turn list items up the stack into paras + function paragraphify(s, i, stack) { + var list = s.list; + var last_li = list[list.length-1]; + + if ( last_li[1] instanceof Array && last_li[1][0] === "para" ) + return; + if ( i + 1 === stack.length ) { + // Last stack frame + // Keep the same array, but replace the contents + last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ) ); + } + else { + var sublist = last_li.pop(); + last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ), sublist ); + } + } + + // The matcher function + return function( block, next ) { + var m = block.match( is_list_re ); + if ( !m ) + return undefined; + + function make_list( m ) { + var list = bullet_list.exec( m[2] ) + ? ["bulletlist"] + : ["numberlist"]; + + stack.push( { list: list, indent: m[1] } ); + return list; + } + + + var stack = [], // Stack of lists for nesting. + list = make_list( m ), + last_li, + loose = false, + ret = [ stack[0].list ], + i; + + // Loop to search over block looking for inner block elements and loose lists + loose_search: + while ( true ) { + // Split into lines preserving new lines at end of line + var lines = block.split( /(?=\n)/ ); + + // We have to grab all lines for a li and call processInline on them + // once as there are some inline things that can span lines. + var li_accumulate = "", nl = ""; + + // Loop over the lines in this block looking for tight lists. + tight_search: + for ( var line_no = 0; line_no < lines.length; line_no++ ) { + nl = ""; + var l = lines[line_no].replace(/^\n/, function(n) { nl = n; return ""; }); + + + // TODO: really should cache this + var line_re = regex_for_depth( stack.length ); + + m = l.match( line_re ); + //print( "line:", uneval(l), "\nline match:", uneval(m) ); + + // We have a list item + if ( m[1] !== undefined ) { + // Process the previous list item, if any + if ( li_accumulate.length ) { + add( last_li, loose, this.processInline( li_accumulate ), nl ); + // Loose mode will have been dealt with. Reset it + loose = false; + li_accumulate = ""; + } + + m[1] = expand_tab( m[1] ); + var wanted_depth = Math.floor(m[1].length/4)+1; + //print( "want:", wanted_depth, "stack:", stack.length); + if ( wanted_depth > stack.length ) { + // Deep enough for a nested list outright + //print ( "new nested list" ); + list = make_list( m ); + last_li.push( list ); + last_li = list[1] = [ "listitem" ]; + } + else { + // We aren't deep enough to be strictly a new level. This is + // where Md.pl goes nuts. If the indent matches a level in the + // stack, put it there, else put it one deeper then the + // wanted_depth deserves. + var found = false; + for ( i = 0; i < stack.length; i++ ) { + if ( stack[ i ].indent !== m[1] ) + continue; + + list = stack[ i ].list; + stack.splice( i+1, stack.length - (i+1) ); + found = true; + break; + } + + if (!found) { + //print("not found. l:", uneval(l)); + wanted_depth++; + if ( wanted_depth <= stack.length ) { + stack.splice(wanted_depth, stack.length - wanted_depth); + //print("Desired depth now", wanted_depth, "stack:", stack.length); + list = stack[wanted_depth-1].list; + //print("list:", uneval(list) ); + } + else { + //print ("made new stack for messy indent"); + list = make_list(m); + last_li.push(list); + } + } + + //print( uneval(list), "last", list === stack[stack.length-1].list ); + last_li = [ "listitem" ]; + list.push(last_li); + } // end depth of shenegains + nl = ""; + } + + // Add content + if ( l.length > m[0].length ) + li_accumulate += nl + l.substr( m[0].length ); + } // tight_search + + if ( li_accumulate.length ) { + add( last_li, loose, this.processInline( li_accumulate ), nl ); + // Loose mode will have been dealt with. Reset it + loose = false; + li_accumulate = ""; + } + + // Look at the next block - we might have a loose list. Or an extra + // paragraph for the current li + var contained = get_contained_blocks( stack.length, next ); + + // Deal with code blocks or properly nested lists + if ( contained.length > 0 ) { + // Make sure all listitems up the stack are paragraphs + forEach( stack, paragraphify, this); + + last_li.push.apply( last_li, this.toTree( contained, [] ) ); + } + + var next_block = next[0] && next[0].valueOf() || ""; + + if ( next_block.match(is_list_re) || next_block.match( /^ / ) ) { + block = next.shift(); + + // Check for an HR following a list: features/lists/hr_abutting + var hr = this.dialect.block.horizRule( block, next ); + + if ( hr ) { + ret.push.apply(ret, hr); + break; + } + + // Make sure all listitems up the stack are paragraphs + forEach( stack, paragraphify, this); + + loose = true; + continue loose_search; + } + break; + } // loose_search + + return ret; + }; + })(), + + blockquote: function blockquote( block, next ) { + if ( !block.match( /^>/m ) ) + return undefined; + + var jsonml = []; + + // separate out the leading abutting block, if any. I.e. in this case: + // + // a + // > b + // + if ( block[ 0 ] !== ">" ) { + var lines = block.split( /\n/ ), + prev = [], + line_no = block.lineNumber; + + // keep shifting lines until you find a crotchet + while ( lines.length && lines[ 0 ][ 0 ] !== ">" ) { + prev.push( lines.shift() ); + line_no++; + } + + var abutting = mk_block( prev.join( "\n" ), "\n", block.lineNumber ); + jsonml.push.apply( jsonml, this.processBlock( abutting, [] ) ); + // reassemble new block of just block quotes! + block = mk_block( lines.join( "\n" ), block.trailing, line_no ); + } + + + // if the next block is also a blockquote merge it in + while ( next.length && next[ 0 ][ 0 ] === ">" ) { + var b = next.shift(); + block = mk_block( block + block.trailing + b, b.trailing, block.lineNumber ); + } + + // Strip off the leading "> " and re-process as a block. + var input = block.replace( /^> ?/gm, "" ), + old_tree = this.tree, + processedBlock = this.toTree( input, [ "blockquote" ] ), + attr = extract_attr( processedBlock ); + + // If any link references were found get rid of them + if ( attr && attr.references ) { + delete attr.references; + // And then remove the attribute object if it's empty + if ( isEmpty( attr ) ) + processedBlock.splice( 1, 1 ); + } + + jsonml.push( processedBlock ); + return jsonml; + }, + + referenceDefn: function referenceDefn( block, next) { + var re = /^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/; + // interesting matches are [ , ref_id, url, , title, title ] + + if ( !block.match(re) ) + return undefined; + + // make an attribute node if it doesn't exist + if ( !extract_attr( this.tree ) ) + this.tree.splice( 1, 0, {} ); + + var attrs = extract_attr( this.tree ); + + // make a references hash if it doesn't exist + if ( attrs.references === undefined ) + attrs.references = {}; + + var b = this.loop_re_over_block(re, block, function( m ) { + + if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" ) + m[2] = m[2].substring( 1, m[2].length - 1 ); + + var ref = attrs.references[ m[1].toLowerCase() ] = { + href: m[2] + }; + + if ( m[4] !== undefined ) + ref.title = m[4]; + else if ( m[5] !== undefined ) + ref.title = m[5]; + + } ); + + if ( b.length ) + next.unshift( mk_block( b, block.trailing ) ); + + return []; + }, + + para: function para( block ) { + // everything's a para! + return [ ["para"].concat( this.processInline( block ) ) ]; + } + }, + + inline: { + + __oneElement__: function oneElement( text, patterns_or_re, previous_nodes ) { + var m, + res; + + patterns_or_re = patterns_or_re || this.dialect.inline.__patterns__; + var re = new RegExp( "([\\s\\S]*?)(" + (patterns_or_re.source || patterns_or_re) + ")" ); + + m = re.exec( text ); + if (!m) { + // Just boring text + return [ text.length, text ]; + } + else if ( m[1] ) { + // Some un-interesting text matched. Return that first + return [ m[1].length, m[1] ]; + } + + var res; + if ( m[2] in this.dialect.inline ) { + res = this.dialect.inline[ m[2] ].call( + this, + text.substr( m.index ), m, previous_nodes || [] ); + } + // Default for now to make dev easier. just slurp special and output it. + res = res || [ m[2].length, m[2] ]; + return res; + }, + + __call__: function inline( text, patterns ) { + + var out = [], + res; + + function add(x) { + //D:self.debug(" adding output", uneval(x)); + if ( typeof x === "string" && typeof out[out.length-1] === "string" ) + out[ out.length-1 ] += x; + else + out.push(x); + } + + while ( text.length > 0 ) { + res = this.dialect.inline.__oneElement__.call(this, text, patterns, out ); + text = text.substr( res.shift() ); + forEach(res, add ); + } + + return out; + }, + + // These characters are intersting elsewhere, so have rules for them so that + // chunks of plain text blocks don't include them + "]": function () {}, + "}": function () {}, + + __escape__ : /^\\[\\`\*_{}\[\]()#\+.!\-]/, + + "\\": function escaped( text ) { + // [ length of input processed, node/children to add... ] + // Only esacape: \ ` * _ { } [ ] ( ) # * + - . ! + if ( this.dialect.inline.__escape__.exec( text ) ) + return [ 2, text.charAt( 1 ) ]; + else + // Not an esacpe + return [ 1, "\\" ]; + }, + + "![": function image( text ) { + + // Unlike images, alt text is plain text only. no other elements are + // allowed in there + + // ![Alt text](/path/to/img.jpg "Optional title") + // 1 2 3 4 <--- captures + var m = text.match( /^!\[(.*?)\][ \t]*\([ \t]*([^")]*?)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ ); + + if ( m ) { + if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" ) + m[2] = m[2].substring( 1, m[2].length - 1 ); + + m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; + + var attrs = { alt: m[1], href: m[2] || "" }; + if ( m[4] !== undefined) + attrs.title = m[4]; + + return [ m[0].length, [ "img", attrs ] ]; + } + + // ![Alt text][id] + m = text.match( /^!\[(.*?)\][ \t]*\[(.*?)\]/ ); + + if ( m ) { + // We can't check if the reference is known here as it likely wont be + // found till after. Check it in md tree->hmtl tree conversion + return [ m[0].length, [ "img_ref", { alt: m[1], ref: m[2].toLowerCase(), original: m[0] } ] ]; + } + + // Just consume the '![' + return [ 2, "![" ]; + }, + + "[": function link( text ) { + + var orig = String(text); + // Inline content is possible inside `link text` + var res = inline_until_char.call( this, text.substr(1), "]" ); + + // No closing ']' found. Just consume the [ + if ( !res ) + return [ 1, "[" ]; + + var consumed = 1 + res[ 0 ], + children = res[ 1 ], + link, + attrs; + + // At this point the first [...] has been parsed. See what follows to find + // out which kind of link we are (reference or direct url) + text = text.substr( consumed ); + + // [link text](/path/to/img.jpg "Optional title") + // 1 2 3 <--- captures + // This will capture up to the last paren in the block. We then pull + // back based on if there a matching ones in the url + // ([here](/url/(test)) + // The parens have to be balanced + var m = text.match( /^\s*\([ \t]*([^"']*)(?:[ \t]+(["'])(.*?)\2)?[ \t]*\)/ ); + if ( m ) { + var url = m[1]; + consumed += m[0].length; + + if ( url && url[0] === "<" && url[url.length-1] === ">" ) + url = url.substring( 1, url.length - 1 ); + + // If there is a title we don't have to worry about parens in the url + if ( !m[3] ) { + var open_parens = 1; // One open that isn't in the capture + for ( var len = 0; len < url.length; len++ ) { + switch ( url[len] ) { + case "(": + open_parens++; + break; + case ")": + if ( --open_parens === 0) { + consumed -= url.length - len; + url = url.substring(0, len); + } + break; + } + } + } + + // Process escapes only + url = this.dialect.inline.__call__.call( this, url, /\\/ )[0]; + + attrs = { href: url || "" }; + if ( m[3] !== undefined) + attrs.title = m[3]; + + link = [ "link", attrs ].concat( children ); + return [ consumed, link ]; + } + + // [Alt text][id] + // [Alt text] [id] + m = text.match( /^\s*\[(.*?)\]/ ); + + if ( m ) { + + consumed += m[ 0 ].length; + + // [links][] uses links as its reference + attrs = { ref: ( m[ 1 ] || String(children) ).toLowerCase(), original: orig.substr( 0, consumed ) }; + + link = [ "link_ref", attrs ].concat( children ); + + // We can't check if the reference is known here as it likely wont be + // found till after. Check it in md tree->hmtl tree conversion. + // Store the original so that conversion can revert if the ref isn't found. + return [ consumed, link ]; + } + + // [id] + // Only if id is plain (no formatting.) + if ( children.length === 1 && typeof children[0] === "string" ) { + + attrs = { ref: children[0].toLowerCase(), original: orig.substr( 0, consumed ) }; + link = [ "link_ref", attrs, children[0] ]; + return [ consumed, link ]; + } + + // Just consume the "[" + return [ 1, "[" ]; + }, + + + "<": function autoLink( text ) { + var m; + + if ( ( m = text.match( /^<(?:((https?|ftp|mailto):[^>]+)|(.*?@.*?\.[a-zA-Z]+))>/ ) ) !== null ) { + if ( m[3] ) + return [ m[0].length, [ "link", { href: "mailto:" + m[3] }, m[3] ] ]; + else if ( m[2] === "mailto" ) + return [ m[0].length, [ "link", { href: m[1] }, m[1].substr("mailto:".length ) ] ]; + else + return [ m[0].length, [ "link", { href: m[1] }, m[1] ] ]; + } + + return [ 1, "<" ]; + }, + + "`": function inlineCode( text ) { + // Inline code block. as many backticks as you like to start it + // Always skip over the opening ticks. + var m = text.match( /(`+)(([\s\S]*?)\1)/ ); + + if ( m && m[2] ) + return [ m[1].length + m[2].length, [ "inlinecode", m[3] ] ]; + else { + // TODO: No matching end code found - warn! + return [ 1, "`" ]; + } + }, + + " \n": function lineBreak() { + return [ 3, [ "linebreak" ] ]; + } + + } + }; + + // Meta Helper/generator method for em and strong handling + function strong_em( tag, md ) { + + var state_slot = tag + "_state", + other_slot = tag === "strong" ? "em_state" : "strong_state"; + + function CloseTag(len) { + this.len_after = len; + this.name = "close_" + md; + } + + return function ( text ) { + + if ( this[state_slot][0] === md ) { + // Most recent em is of this type + //D:this.debug("closing", md); + this[state_slot].shift(); + + // "Consume" everything to go back to the recrusion in the else-block below + return[ text.length, new CloseTag(text.length-md.length) ]; + } + else { + // Store a clone of the em/strong states + var other = this[other_slot].slice(), + state = this[state_slot].slice(); + + this[state_slot].unshift(md); + + //D:this.debug_indent += " "; + + // Recurse + var res = this.processInline( text.substr( md.length ) ); + //D:this.debug_indent = this.debug_indent.substr(2); + + var last = res[res.length - 1]; + + //D:this.debug("processInline from", tag + ": ", uneval( res ) ); + + var check = this[state_slot].shift(); + if ( last instanceof CloseTag ) { + res.pop(); + // We matched! Huzzah. + var consumed = text.length - last.len_after; + return [ consumed, [ tag ].concat(res) ]; + } + else { + // Restore the state of the other kind. We might have mistakenly closed it. + this[other_slot] = other; + this[state_slot] = state; + + // We can't reuse the processed result as it could have wrong parsing contexts in it. + return [ md.length, md ]; + } + } + }; // End returned function + } + + Gruber.inline["**"] = strong_em("strong", "**"); + Gruber.inline["__"] = strong_em("strong", "__"); + Gruber.inline["*"] = strong_em("em", "*"); + Gruber.inline["_"] = strong_em("em", "_"); + + Markdown.dialects.Gruber = Gruber; + Markdown.buildBlockOrder ( Markdown.dialects.Gruber.block ); + Markdown.buildInlinePatterns( Markdown.dialects.Gruber.inline ); + + + + var Maruku = DialectHelpers.subclassDialect( Gruber ), + extract_attr = MarkdownHelpers.extract_attr, + forEach = MarkdownHelpers.forEach; + + Maruku.processMetaHash = function processMetaHash( meta_string ) { + var meta = split_meta_hash( meta_string ), + attr = {}; + + for ( var i = 0; i < meta.length; ++i ) { + // id: #foo + if ( /^#/.test( meta[ i ] ) ) + attr.id = meta[ i ].substring( 1 ); + // class: .foo + else if ( /^\./.test( meta[ i ] ) ) { + // if class already exists, append the new one + if ( attr["class"] ) + attr["class"] = attr["class"] + meta[ i ].replace( /./, " " ); + else + attr["class"] = meta[ i ].substring( 1 ); + } + // attribute: foo=bar + else if ( /\=/.test( meta[ i ] ) ) { + var s = meta[ i ].split( /\=/ ); + attr[ s[ 0 ] ] = s[ 1 ]; + } + } + + return attr; + }; + + function split_meta_hash( meta_string ) { + var meta = meta_string.split( "" ), + parts = [ "" ], + in_quotes = false; + + while ( meta.length ) { + var letter = meta.shift(); + switch ( letter ) { + case " " : + // if we're in a quoted section, keep it + if ( in_quotes ) + parts[ parts.length - 1 ] += letter; + // otherwise make a new part + else + parts.push( "" ); + break; + case "'" : + case '"' : + // reverse the quotes and move straight on + in_quotes = !in_quotes; + break; + case "\\" : + // shift off the next letter to be used straight away. + // it was escaped so we'll keep it whatever it is + letter = meta.shift(); + /* falls through */ + default : + parts[ parts.length - 1 ] += letter; + break; + } + } + + return parts; + } + + Maruku.block.document_meta = function document_meta( block ) { + // we're only interested in the first block + if ( block.lineNumber > 1 ) + return undefined; + + // document_meta blocks consist of one or more lines of `Key: Value\n` + if ( ! block.match( /^(?:\w+:.*\n)*\w+:.*$/ ) ) + return undefined; + + // make an attribute node if it doesn't exist + if ( !extract_attr( this.tree ) ) + this.tree.splice( 1, 0, {} ); + + var pairs = block.split( /\n/ ); + for ( var p in pairs ) { + var m = pairs[ p ].match( /(\w+):\s*(.*)$/ ), + key = m[ 1 ].toLowerCase(), + value = m[ 2 ]; + + this.tree[ 1 ][ key ] = value; + } + + // document_meta produces no content! + return []; + }; + + Maruku.block.block_meta = function block_meta( block ) { + // check if the last line of the block is an meta hash + var m = block.match( /(^|\n) {0,3}\{:\s*((?:\\\}|[^\}])*)\s*\}$/ ); + if ( !m ) + return undefined; + + // process the meta hash + var attr = this.dialect.processMetaHash( m[ 2 ] ), + hash; + + // if we matched ^ then we need to apply meta to the previous block + if ( m[ 1 ] === "" ) { + var node = this.tree[ this.tree.length - 1 ]; + hash = extract_attr( node ); + + // if the node is a string (rather than JsonML), bail + if ( typeof node === "string" ) + return undefined; + + // create the attribute hash if it doesn't exist + if ( !hash ) { + hash = {}; + node.splice( 1, 0, hash ); + } + + // add the attributes in + for ( var a in attr ) + hash[ a ] = attr[ a ]; + + // return nothing so the meta hash is removed + return []; + } + + // pull the meta hash off the block and process what's left + var b = block.replace( /\n.*$/, "" ), + result = this.processBlock( b, [] ); + + // get or make the attributes hash + hash = extract_attr( result[ 0 ] ); + if ( !hash ) { + hash = {}; + result[ 0 ].splice( 1, 0, hash ); + } + + // attach the attributes to the block + for ( var a in attr ) + hash[ a ] = attr[ a ]; + + return result; + }; + + Maruku.block.definition_list = function definition_list( block, next ) { + // one or more terms followed by one or more definitions, in a single block + var tight = /^((?:[^\s:].*\n)+):\s+([\s\S]+)$/, + list = [ "dl" ], + i, m; + + // see if we're dealing with a tight or loose block + if ( ( m = block.match( tight ) ) ) { + // pull subsequent tight DL blocks out of `next` + var blocks = [ block ]; + while ( next.length && tight.exec( next[ 0 ] ) ) + blocks.push( next.shift() ); + + for ( var b = 0; b < blocks.length; ++b ) { + var m = blocks[ b ].match( tight ), + terms = m[ 1 ].replace( /\n$/, "" ).split( /\n/ ), + defns = m[ 2 ].split( /\n:\s+/ ); + + // print( uneval( m ) ); + + for ( i = 0; i < terms.length; ++i ) + list.push( [ "dt", terms[ i ] ] ); + + for ( i = 0; i < defns.length; ++i ) { + // run inline processing over the definition + list.push( [ "dd" ].concat( this.processInline( defns[ i ].replace( /(\n)\s+/, "$1" ) ) ) ); + } + } + } + else { + return undefined; + } + + return [ list ]; + }; + + // splits on unescaped instances of @ch. If @ch is not a character the result + // can be unpredictable + + Maruku.block.table = function table ( block ) { + + var _split_on_unescaped = function( s, ch ) { + ch = ch || '\\s'; + if ( ch.match(/^[\\|\[\]{}?*.+^$]$/) ) + ch = '\\' + ch; + var res = [ ], + r = new RegExp('^((?:\\\\.|[^\\\\' + ch + '])*)' + ch + '(.*)'), + m; + while ( ( m = s.match( r ) ) ) { + res.push( m[1] ); + s = m[2]; + } + res.push(s); + return res; + }; + + var leading_pipe = /^ {0,3}\|(.+)\n {0,3}\|\s*([\-:]+[\-| :]*)\n((?:\s*\|.*(?:\n|$))*)(?=\n|$)/, + // find at least an unescaped pipe in each line + no_leading_pipe = /^ {0,3}(\S(?:\\.|[^\\|])*\|.*)\n {0,3}([\-:]+\s*\|[\-| :]*)\n((?:(?:\\.|[^\\|])*\|.*(?:\n|$))*)(?=\n|$)/, + i, + m; + if ( ( m = block.match( leading_pipe ) ) ) { + // remove leading pipes in contents + // (header and horizontal rule already have the leading pipe left out) + m[3] = m[3].replace(/^\s*\|/gm, ''); + } else if ( ! ( m = block.match( no_leading_pipe ) ) ) { + return undefined; + } + + var table = [ "table", [ "thead", [ "tr" ] ], [ "tbody" ] ]; + + // remove trailing pipes, then split on pipes + // (no escaped pipes are allowed in horizontal rule) + m[2] = m[2].replace(/\|\s*$/, '').split('|'); + + // process alignment + var html_attrs = [ ]; + forEach (m[2], function (s) { + if (s.match(/^\s*-+:\s*$/)) + html_attrs.push({align: "right"}); + else if (s.match(/^\s*:-+\s*$/)) + html_attrs.push({align: "left"}); + else if (s.match(/^\s*:-+:\s*$/)) + html_attrs.push({align: "center"}); + else + html_attrs.push({}); + }); + + // now for the header, avoid escaped pipes + m[1] = _split_on_unescaped(m[1].replace(/\|\s*$/, ''), '|'); + for (i = 0; i < m[1].length; i++) { + table[1][1].push(['th', html_attrs[i] || {}].concat( + this.processInline(m[1][i].trim()))); + } + + // now for body contents + forEach (m[3].replace(/\|\s*$/mg, '').split('\n'), function (row) { + var html_row = ['tr']; + row = _split_on_unescaped(row, '|'); + for (i = 0; i < row.length; i++) + html_row.push(['td', html_attrs[i] || {}].concat(this.processInline(row[i].trim()))); + table[2].push(html_row); + }, this); + + return [table]; + }; + + Maruku.inline[ "{:" ] = function inline_meta( text, matches, out ) { + if ( !out.length ) + return [ 2, "{:" ]; + + // get the preceeding element + var before = out[ out.length - 1 ]; + + if ( typeof before === "string" ) + return [ 2, "{:" ]; + + // match a meta hash + var m = text.match( /^\{:\s*((?:\\\}|[^\}])*)\s*\}/ ); + + // no match, false alarm + if ( !m ) + return [ 2, "{:" ]; + + // attach the attributes to the preceeding element + var meta = this.dialect.processMetaHash( m[ 1 ] ), + attr = extract_attr( before ); + + if ( !attr ) { + attr = {}; + before.splice( 1, 0, attr ); + } + + for ( var k in meta ) + attr[ k ] = meta[ k ]; + + // cut out the string and replace it with nothing + return [ m[ 0 ].length, "" ]; + }; + + + Markdown.dialects.Maruku = Maruku; + Markdown.dialects.Maruku.inline.__escape__ = /^\\[\\`\*_{}\[\]()#\+.!\-|:]/; + Markdown.buildBlockOrder ( Markdown.dialects.Maruku.block ); + Markdown.buildInlinePatterns( Markdown.dialects.Maruku.inline ); + + +// Include all our depndencies and; + expose.Markdown = Markdown; + expose.parse = Markdown.parse; + expose.toHTML = Markdown.toHTML; + expose.toHTMLTree = Markdown.toHTMLTree; + expose.renderJsonML = Markdown.renderJsonML; + +})(function() { + window.markdown = {}; + return window.markdown; +}()); + +},{"util":30}]},{},[1]) +(1) +}); \ No newline at end of file diff --git a/vendor/mod/torque.uncompressed.js b/vendor/mod/torque.uncompressed.js index bedcc63ab3..f76b445c6c 100644 --- a/vendor/mod/torque.uncompressed.js +++ b/vendor/mod/torque.uncompressed.js @@ -1,3 +1,10 @@ +/** +Torque 2.11.4 +Temporal mapping for CartoDB +https://github.com/cartodb/torque +**/ + + !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.torque=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= this.options.steps) { - this._time = 0; if(!this.options.loop){ - this.stop(); + // set time to max time + this.time(this.options.animationDuration); + this.pause(); + } else { + this._time = 0; } } if(this.running) { @@ -661,7 +676,9 @@ module.exports.TorqueLayer = TorqueLayer; // types var types = { Uint8Array: typeof(global['Uint8Array']) !== 'undefined' ? global.Uint8Array : Array, + Uint8ClampedArray: typeof(global['Uint8ClampedArray']) !== 'undefined' ? global.Uint8ClampedArray: Array, Uint32Array: typeof(global['Uint32Array']) !== 'undefined' ? global.Uint32Array : Array, + Int16Array: typeof(global['Int16Array']) !== 'undefined' ? global.Int16Array : Array, Int32Array: typeof(global['Int32Array']) !== 'undefined' ? global.Int32Array: Array }; @@ -674,7 +691,7 @@ module.exports.TorqueLayer = TorqueLayer; } var flags = { - sprites_to_images: userAgent().indexOf('Safari') === -1 + sprites_to_images: userAgent().indexOf('Safari') === -1 && userAgent().indexOf('Firefox') === -1 }; module.exports = { @@ -979,6 +996,24 @@ CanvasLayer.prototype.setPaneName = function(paneName) { this.setPane_(); }; +/** + * Set the opacity for the canvas. + * + * @param {number} opacity The opacity of the canvas + */ +CanvasLayer.prototype.setOpacity = function (opacity) { + this.canvas.style.opacity = opacity; +}; + +/** + * Get the canvases opacity. + * + * @return {number} The opacity of the canvas + */ +CanvasLayer.prototype.getOpacity = function () { + return this.canvas.style.opacity; +}; + /** * @return {string} The name of the current container pane. */ @@ -1318,16 +1353,17 @@ GMapsTileLoader.prototype = { }, _removeTileLoader: function() { - for(var i in this._listeners) { - google.maps.event.removeListener(this._listeners[i]); - } + this._listeners.forEach(function (listener) { + google.maps.event.removeListener(listener); + }); + this._removeTiles(); }, _removeTiles: function () { - for (var key in this._tiles) { - this._removeTile(key); - } + for (var key in this._tiles) { + this._removeTile(key); + } }, _reloadTiles: function() { @@ -1576,7 +1612,7 @@ GMapsTorqueLayer.prototype = torque.extend({}, providers: { 'sql_api': torque.providers.json, - 'url_template': torque.providers.jsonarray, + 'url_template': torque.providers.JsonArray, 'windshaft': torque.providers.windshaft }, @@ -1604,6 +1640,7 @@ GMapsTorqueLayer.prototype = torque.extend({}, this.provider = new this.providers[this.options.provider](this.options); this.renderer = new this.renderers[this.options.renderer](this.getCanvas(), this.options); + this.renderer.options.errorCallback = this.options.errorCallback; // this listener should be before tile loader this._cacheListener = google.maps.event.addListener(this.options.map, 'zoom_changed', function() { @@ -1630,10 +1667,14 @@ GMapsTorqueLayer.prototype = torque.extend({}, if(!this.hidden) return this; this.hidden = false; this.play(); + if (this.options.steps === 1){ + this.redraw(); + } return this; }, setSQL: function(sql) { + if (this.provider.options.named_map) throw new Error("SQL queries on named maps are read-only"); if (!this.provider || !this.provider.setSQL) { throw new Error("this provider does not support SQL"); } @@ -1702,9 +1743,10 @@ GMapsTorqueLayer.prototype = torque.extend({}, if (tile) { pos = this.getTilePos(tile.coord); ctx.setTransform(1, 0, 0, 1, pos.x, pos.y); - this.renderer.renderTile(tile, this.key, pos.x, pos.y); + this.renderer.renderTile(tile, this.key); } } + this.renderer.applyFilters(); }, getActivePointsBBox: function(step) { @@ -1753,6 +1795,14 @@ GMapsTorqueLayer.prototype = torque.extend({}, return new Date(time); }, + timeToStep: function(timestamp) { + if (typeof timestamp === "Date") timestamp = timestamp.getTime(); + if (!this.provider) return 0; + var times = this.provider.getKeySpan(); + var step = (this.provider.getSteps() * (timestamp - times.start)) / (times.end - times.start); + return step; + }, + getStep: function() { return this.key; }, @@ -1769,6 +1819,7 @@ GMapsTorqueLayer.prototype = torque.extend({}, * set the cartocss for the current renderer */ setCartoCSS: function(cartocss) { + if (this.provider && this.provider.options.named_map) throw new Error("CartoCSS style on named maps is read-only"); var shader = new carto.RendererJS().render(cartocss); this.shader = shader; if (this.renderer) { @@ -1797,10 +1848,52 @@ GMapsTorqueLayer.prototype = torque.extend({}, }, onRemove: function() { + this.fire('remove'); CanvasLayer.prototype.onRemove.call(this); this.animator.stop(); this._removeTileLoader(); google.maps.event.removeListener(this._cacheListener); + }, + + getValueForPos: function(x, y, step) { + step = step === undefined ? this.key: step; + var t, tile, pos, value = null, xx, yy; + for(t in this._tiles) { + tile = this._tiles[t]; + pos = this.getTilePos(tile.coord); + xx = x - pos.x; + yy = y - pos.y; + if (xx >= 0 && yy >= 0 && xx < this.renderer.TILE_SIZE && yy <= this.renderer.TILE_SIZE) { + value = this.renderer.getValueFor(tile, step, xx, yy); + } + if (value !== null) { + return value; + } + } + return null; + }, + getValueForBBox: function(x, y, w, h) { + var xf = x + w, yf = y + h; + var sum = 0; + for(_y = y; y "hello, rambo" function format(str) { @@ -3796,7 +3934,7 @@ var Profiler = require('../profiler'); return str; } - var json = function (options) { + var windshaft = function (options) { this._ready = false; this._tileQueue = []; this.options = options; @@ -3806,6 +3944,15 @@ var Profiler = require('../profiler'); this.options.tiler_domain = options.tiler_domain || 'cartodb.com'; this.options.tiler_port = options.tiler_port || 80; + // backwards compatible + if (!options.maps_api_template) { + this._buildMapsApiTemplate(this.options); + } else { + this.options.maps_api_template = options.maps_api_template; + } + + this.options.coordinates_data_type = this.options.coordinates_data_type || Uint8Array; + if (this.options.data_aggregation) { this.options.cumulative = this.options.data_aggregation === 'cumulative'; } @@ -3818,7 +3965,7 @@ var Profiler = require('../profiler'); } }; - json.prototype = { + windshaft.prototype = { /** * return the torque tile encoded in an efficient javascript @@ -3831,8 +3978,8 @@ var Profiler = require('../profiler'); */ proccessTile: function(rows, coord, zoom) { var r; - var x = new Uint8Array(rows.length); - var y = new Uint8Array(rows.length); + var x = new this.options.coordinates_data_type(rows.length); + var y = new this.options.coordinates_data_type(rows.length); var prof_mem = Profiler.metric('torque.provider.windshaft.mem'); var prof_point_count = Profiler.metric('torque.provider.windshaft.points'); @@ -3853,7 +4000,7 @@ var Profiler = require('../profiler'); dates = (1 + maxDateSlots) * rows.length; } - var type = this.options.cumulative ? Uint32Array: Uint8Array; + var type = this.options.cumulative ? Uint32Array: Uint8ClampedArray; // reserve memory for all the dates var timeIndex = new Int32Array(maxDateSlots + 1); //index-size @@ -3876,13 +4023,7 @@ var Profiler = require('../profiler'); for (var r = 0; r < rows.length; ++r) { var row = rows[r]; x[r] = row.x__uint8 * this.options.resolution; - // fix value when it's in the tile EDGE - // TODO: this should be fixed in SQL query - if (row.y__uint8 === -1) { - y[r] = 0; - } else { - y[r] = row.y__uint8 * this.options.resolution; - } + y[r] = row.y__uint8 * this.options.resolution; var dates = row.dates__uint16; var vals = row.vals__uint8; @@ -4061,9 +4202,11 @@ var Profiler = require('../profiler'); var self = this; var prof_fetch_time = Profiler.metric('torque.provider.windshaft.tile.fetch').start(); var subdomains = this.options.subdomains || '0123'; - var index = Math.abs(coord.x + coord.y) % subdomains.length; + var limit_x = Math.pow(2, zoom); + var corrected_x = ((coord.x % limit_x) + limit_x) % limit_x; + var index = Math.abs(corrected_x + coord.y) % subdomains.length; var url = this.templateUrl - .replace('{x}', coord.x) + .replace('{x}', corrected_x) .replace('{y}', coord.y) .replace('{z}', zoom) .replace('{s}', subdomains[index]) @@ -4121,31 +4264,53 @@ var Profiler = require('../profiler'); } }, - _tilerHost: function() { - var opts = this.options; - var user = (opts.user_name || opts.user); - return opts.tiler_protocol + - "://" + (user ? user + "." : "") + + _buildMapsApiTemplate: function(opts) { + var user = opts.user_name || opts.user; + opts.maps_api_template = opts.tiler_protocol + + "://" + ((user) ? "{user}.":"") + opts.tiler_domain + ((opts.tiler_port != "") ? (":" + opts.tiler_port) : ""); }, - url: function() { + _tilerHost: function() { + var opts = this.options; + var user = opts.user_name || opts.user; + return opts.maps_api_template.replace('{user}', user); + }, + + url: function () { var opts = this.options; - var protocol = opts.tiler_protocol || 'http'; - if (!this.options.cdn_url || this.options.no_cdn) { - return this._tilerHost(); - } - var h = protocol + "://" - if (protocol === 'http') { - h += "{s}."; - } var cdn_host = opts.cdn_url; - if(!cdn_host.http && !cdn_host.https) { - throw new Error("cdn_host should contain http and/or https entries"); + var has_empty_cdn = !cdn_host || (cdn_host && (!cdn_host.http && !cdn_host.https)); + + if (opts.no_cdn || has_empty_cdn) { + return this._tilerHost(); + } else { + var protocol = this.isHttps() ? 'https': 'http'; + var h = protocol + "://"; + if (!this.isHttps()) { + h += "{s}."; + } + var cdn_url = cdn_host[protocol]; + // build default template url if the cdn url is not templatized + // this is for backwards compatiblity, ideally we should use the url + // that tiler sends to us right away + if (!this._isUserTemplateUrl(cdn_url)) { + cdn_url = cdn_url + "/{user}"; + } + var user = opts.user_name || opts.user; + h += cdn_url.replace('{user}', user) + return h; } - h += cdn_host[protocol] + "/" + (opts.user_name || opts.user); - return h; + + }, + + _isUserTemplateUrl: function(t) { + return t && t.indexOf('{user}') !== -1; + }, + + isHttps: function() { + return this.options.maps_api_template.indexOf('https') === 0; }, _generateCartoCSS: function() { @@ -4169,10 +4334,14 @@ var Profiler = require('../profiler'); var host = this.options.dynamic_cdn ? this.url().replace('{s}', '0'): this._tilerHost(); var url = host + "/api/v1/map"; var named = this.options.named_map; + var allParams = {}; if(named) { //tiles/template url = host + "/api/v1/map/named/" + named.name + "/jsonp"; + if(typeof named.params !== "undefined"){ + layergroup = named.params; + } } else { layergroup = { "version": "1.0.1", @@ -4187,7 +4356,12 @@ var Profiler = require('../profiler'); }] }; } - var extra = this._extraParams(this.options.stat_tag ? { stat_tag: this.options.stat_tag }: {} ); + + if(this.options.stat_tag){ + allParams["stat_tag"] = this.options.stat_tag; + } + + extra = this._extraParams(allParams); // tiler needs map_key instead of api_key // so replace it @@ -4203,6 +4377,10 @@ var Profiler = require('../profiler'); torque.net.jsonp(url, function (data) { map_instance_time.end(); if (data) { + if (data.errors){ + self.options.errorCallback && self.options.errorCallback(data.errors); + return; + } var torque_key = Object.keys(data.metadata.torque)[0] var opt = data.metadata.torque[torque_key]; for(var k in opt) { @@ -4224,13 +4402,14 @@ var Profiler = require('../profiler'); }; - module.exports = json; + module.exports = windshaft; },{"../":10,"../profiler":17}],22:[function(require,module,exports){ var TAU = Math.PI*2; // min value to render a line. // it does not make sense to render a line of a width is not even visible var LINEWIDTH_MIN_VALUE = 0.05; + var MAX_SPRITE_RADIUS = 255; function renderPoint(ctx, st) { ctx.fillStyle = st['marker-fill']; @@ -4244,19 +4423,20 @@ var Profiler = require('../profiler'); ctx.beginPath(); ctx.arc(0, 0, pixel_size, 0, TAU, true, true); ctx.closePath(); + + if (st['marker-opacity'] !== undefined ) st['marker-fill-opacity'] = st['marker-line-opacity'] = st['marker-opacity']; + if (st['marker-fill']) { - if (st['marker-fill-opacity'] !== undefined || st['marker-opacity'] !== undefined) { - ctx.globalAlpha = st['marker-fill-opacity'] || st['marker-opacity']; + ctx.globalAlpha = st['marker-fill-opacity'] >= 0? st['marker-fill-opacity']: 1; + + if (ctx.globalAlpha > 0) { + ctx.fill(); } - ctx.fill(); } // stroke - ctx.globalAlpha = 1.0; if (st['marker-line-color'] && st['marker-line-width'] && st['marker-line-width'] > LINEWIDTH_MIN_VALUE) { - if (st['marker-line-opacity'] !== undefined) { - ctx.globalAlpha = st['marker-line-opacity']; - } + ctx.globalAlpha = st['marker-line-opacity'] >= 0? st['marker-line-opacity']: 1; if (st['marker-line-width'] !== undefined) { ctx.lineWidth = st['marker-line-width']; } @@ -4300,18 +4480,21 @@ var Profiler = require('../profiler'); } } - function renderSprite(ctx, st) { - var img = st['point-file'] || st['marker-file']; - var ratio = img.height/img.width; - var w = st['marker-width'] || img.width; - var h = st['marker-width'] || st['marker-height'] || w*ratio; - ctx.drawImage(img, 0, 0, w, h); + function renderSprite(ctx, img, st) { + + if(img.complete){ + if (st['marker-fill-opacity'] !== undefined || st['marker-opacity'] !== undefined) { + ctx.globalAlpha = st['marker-fill-opacity'] || st['marker-opacity']; + } + ctx.drawImage(img, 0, 0, Math.min(img.width, MAX_SPRITE_RADIUS), Math.min(img.height, MAX_SPRITE_RADIUS)); + } } module.exports = { renderPoint: renderPoint, renderSprite: renderSprite, - renderRectangle: renderRectangle + renderRectangle: renderRectangle, + MAX_SPRITE_RADIUS: MAX_SPRITE_RADIUS }; },{}],23:[function(require,module,exports){ @@ -4326,6 +4509,7 @@ var torque = require('../'); var cartocss = require('./cartocss_render'); var Profiler = require('../profiler'); var carto = global.carto || require('carto'); +var Filters = require('./torque_filters'); var TAU = Math.PI * 2; var DEFAULT_CARTOCSS = [ @@ -4373,11 +4557,18 @@ var carto = global.carto || require('carto'); this._ctx = canvas.getContext('2d'); this._sprites = []; // sprites per layer this._shader = null; + this._icons = {}; + this._iconsToLoad = 0; + this._filters = new Filters(this._canvas, {canvasClass: options.canvasClass}); this.setCartoCSS(this.options.cartocss || DEFAULT_CARTOCSS); this.TILE_SIZE = 256; + this._style = null; + this._gradients = {}; + + this._forcePoints = false; } - PointRenderer.prototype = { + torque.extend(PointRenderer.prototype, torque.Event, { clearCanvas: function() { var canvas = this._canvas; @@ -4413,6 +4604,8 @@ var carto = global.carto || require('carto'); this._sprites = []; this._shader = shader; this._Map = this._shader.getDefault().getStyle({}, { zoom: 0 }); + var img_names = this._shader.getImageURLs(); + this._preloadIcons(img_names); }, clearSpriteCache: function() { @@ -4424,10 +4617,14 @@ var carto = global.carto || require('carto'); // generate sprite based on cartocss style // generateSprite: function(shader, value, shaderVars) { + var self = this; var prof = Profiler.metric('torque.renderer.point.generateSprite').start(); var st = shader.getStyle({ value: value }, shaderVars); + if(this._style === null || this._style !== st){ + this._style = st; + } var pointSize = st['marker-width']; if (!pointSize) { @@ -4439,15 +4636,29 @@ var carto = global.carto || require('carto'); } var canvas = this._createCanvas(); - // take into account the exterior ring to calculate the size - var canvasSize = (st['marker-line-width'] || 0) + pointSize*2; var ctx = canvas.getContext('2d'); - var w = ctx.width = canvas.width = ctx.height = canvas.height = Math.ceil(canvasSize); - ctx.translate(w/2, w/2); - if(st['point-file'] || st['marker-file']) { - cartocss.renderSprite(ctx, st); + var markerFile = st["marker-file"] || st["point-file"]; + var qualifiedUrl = markerFile && this._qualifyURL(markerFile); + + if (qualifiedUrl && this._iconsToLoad <= 0 && this._icons[qualifiedUrl]) { + var img = this._icons[qualifiedUrl]; + + var dWidth = Math.min(st['marker-width'] * 2 || img.width, cartocss.MAX_SPRITE_RADIUS * 2); + var dHeight = Math.min((st['marker-height'] || dWidth) * (img.width / img.height), cartocss.MAX_SPRITE_RADIUS * 2); + + canvas.width = ctx.width = dWidth; + canvas.height = ctx.height = dHeight; + + ctx.scale(dWidth/img.width, dHeight/img.height); + + cartocss.renderSprite(ctx, img, st); } else { + // take into account the exterior ring to calculate the size + var canvasSize = (st['marker-line-width'] || 0) + pointSize*2; + var w = ctx.width = canvas.width = ctx.height = canvas.height = Math.ceil(canvasSize); + ctx.translate(w/2, w/2); + var mt = st['marker-type']; if (mt && mt === 'rectangle') { cartocss.renderRectangle(ctx, st); @@ -4461,13 +4672,20 @@ var carto = global.carto || require('carto'); i.src = canvas.toDataURL(); return i; } + return canvas; }, // // renders all the layers (and frames for each layer) from cartocss // - renderTile: function(tile, key) { + renderTile: function(tile, key, callback) { + if (this._iconsToLoad > 0) { + this.on('allIconsLoaded', function() { + this.renderTile.apply(this, [tile, key, callback]); + }); + return false; + } var prof = Profiler.metric('torque.renderer.point.renderLayers').start(); var layers = this._shader.getLayers(); for(var i = 0, n = layers.length; i < n; ++i ) { @@ -4482,7 +4700,10 @@ var carto = global.carto || require('carto'); } } } + prof.end(true); + + return callback && callback(null); }, _createCanvas: function() { @@ -4497,17 +4718,42 @@ var carto = global.carto || require('carto'); : new Image(); }, + _setImageSrc: function(img, url, callback) { + if (this.options.setImageSrc) { + this.options.setImageSrc(img, url, callback); + } else { + img.onload = function(){ + callback(null); + }; + img.onerror = function(){ + callback(new Error('Could not load image')); + }; + img.src = url; + } + }, + + _qualifyURL: function(url) { + if (typeof this.options.qualifyURL !== "undefined"){ + return this.options.qualifyURL(url); + } + else{ + var a = document.createElement('a'); + a.href = url; + return a.href; + } + }, + // // renders a tile in the canvas for key defined in // the torque tile // _renderTile: function(tile, key, frame_offset, sprites, shader, shaderVars) { - if(!this._canvas) return; + if (!this._canvas) return; var prof = Profiler.metric('torque.renderer.point.renderTile').start(); var ctx = this._ctx; var blendMode = compop2canvas(shader.eval('comp-op')) || this.options.blendmode; - if(blendMode) { + if (blendMode) { ctx.globalCompositeOperation = blendMode; } if (this.options.cumulative && key > tile.maxDate) { @@ -4516,24 +4762,27 @@ var carto = global.carto || require('carto'); } var tileMax = this.options.resolution * (this.TILE_SIZE/this.options.resolution - 1) var activePixels = tile.timeCount[key]; - if(activePixels) { + var anchor = this.options.resolution/2; + if (activePixels) { var pixelIndex = tile.timeIndex[key]; for(var p = 0; p < activePixels; ++p) { var posIdx = tile.renderDataPos[pixelIndex + p]; var c = tile.renderData[pixelIndex + p]; - if(c) { + if (c) { var sp = sprites[c]; - if(sp === undefined) { + if (sp === undefined) { sp = sprites[c] = this.generateSprite(shader, c, torque.extend({ zoom: tile.z, 'frame-offset': frame_offset }, shaderVars)); } if (sp) { - var x = tile.x[posIdx]- (sp.width >> 1); - var y = tileMax - tile.y[posIdx]; // flip mercator + var x = tile.x[posIdx]- (sp.width >> 1) + anchor; + var y = tileMax - tile.y[posIdx] + anchor; // flip mercator ctx.drawImage(sp, x, y - (sp.height >> 1)); } } } } + + prof.end(true); }, @@ -4604,16 +4853,108 @@ var carto = global.carto || require('carto'); } } return null; - } + }, - }; + _preloadIcons: function(img_names) { + var self = this; + + if (img_names.length > 0 && !this._forcePoints) { + + var qualifiedImageUrlSet = Object.keys(img_names.reduce(function(imgNamesMap, imgName) { + var qualifiedUrl = self._qualifyURL(imgName); + if (!self._icons[qualifiedUrl]) { + imgNamesMap[qualifiedUrl] = true; + } + return imgNamesMap; + }, {})); + + var filtered = self._shader.getLayers().some(function(layer) { + return typeof layer.shader["image-filters"] !== "undefined"; + }); + + this._iconsToLoad += qualifiedImageUrlSet.length; + + qualifiedImageUrlSet.forEach(function(qualifiedImageUrl) { + self._icons[qualifiedImageUrl] = null; + + var img = self._createImage(); + + if (filtered) { + img.crossOrigin = 'Anonymous'; + } + + self._setImageSrc(img, qualifiedImageUrl, function(err) { + if (err) { + self._forcePoints = true; + self.clearSpriteCache(); + self._iconsToLoad = 0; + self.fire("allIconsLoaded"); + if(filtered) { + console.info("Only CORS-enabled, or same domain image-files can be used in combination with image-filters"); + } + console.error("Couldn't get marker-file " + qualifiedImageUrl); + } else { + self._icons[qualifiedImageUrl] = img; + self._iconsToLoad--; + + if (self._iconsToLoad <= 0){ + self.clearSpriteCache(); + self.fire("allIconsLoaded"); + } + } + }); + }); + } else { + this.fire("allIconsLoaded"); + } + }, + + applyFilters: function(){ + if(this._style){ + if(this._style['image-filters']){ + function gradientKey(imf){ + var hash = "" + for(var i = 0; i < imf.args.length; i++){ + var rgb = imf.args[i].rgb; + hash += rgb[0] + ":" + rgb[1] + ":" + rgb[2]; + } + return hash; + } + var gradient = this._gradients[gradientKey(this._style['image-filters'])]; + if(!gradient){ + function componentToHex(c) { + var hex = c.toString(16); + return hex.length == 1 ? "0" + hex : hex; + } + + function rgbToHex(r, g, b) { + return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); + } + gradient = {}; + var colorize = this._style['image-filters'].args; + + var increment = 1/colorize.length; + for (var i = 0; i < colorize.length; i++){ + var key = increment * i + increment; + var rgb = colorize[i].rgb; + var formattedColor = rgbToHex(rgb[0], rgb[1], rgb[2]); + gradient[key] = formattedColor; + } + this._gradients[gradientKey(this._style['image-filters'])] = gradient; + } + this._filters.gradient(gradient); + this._filters.draw(); + } + } + } +}); // exports public api module.exports = PointRenderer; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"../":10,"../profiler":17,"./cartocss_render":22,"carto":undefined}],25:[function(require,module,exports){ +},{"../":10,"../profiler":17,"./cartocss_render":22,"./torque_filters":26,"carto":undefined}],25:[function(require,module,exports){ (function (global){ var carto = global.carto || require('carto'); @@ -4726,7 +5067,7 @@ var carto = global.carto || require('carto'); // renders a tile in the canvas for key defined in // the torque tile // - renderTile: function(tile, key, px, py) { + renderTile: function(tile, key, callback) { if(!this._canvas) return; var res = this.options.resolution; @@ -4768,6 +5109,7 @@ var carto = global.carto || require('carto'); //ctx.putImageData(imageData, 0, 0); } //prof.end(); + return callback && callback(null); } }; @@ -4777,6 +5119,98 @@ module.exports = RectanbleRenderer; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"carto":undefined}],26:[function(require,module,exports){ +/* + Based on simpleheat, a tiny JavaScript library for drawing heatmaps with Canvas, + by Vladimir Agafonkin + https://github.com/mourner/simpleheat +*/ + +'use strict'; + +function torque_filters(canvas, options) { + // jshint newcap: false, validthis: true + if (!(this instanceof torque_filters)) { return new torque_filters(canvas, options); } + + options = options || {}; + + this._canvas = canvas = typeof canvas === 'string' ? document.getElementById(canvas) : canvas; + + this._ctx = canvas.getContext('2d'); + this._width = canvas.width; + this._height = canvas.height; + + this._max = 1; + this._data = []; + + this.canvasClass = options.canvasClass; +} + +torque_filters.prototype = { + + defaultGradient: { + 0.4: 'blue', + 0.6: 'cyan', + 0.7: 'lime', + 0.8: 'yellow', + 1.0: 'red' + }, + + gradient: function (grad) { + // create a 256x1 gradient that we'll use to turn a grayscale heatmap into a colored one + var canvas = this._createCanvas(), + ctx = canvas.getContext('2d'), + gradient = ctx.createLinearGradient(0, 0, 0, 256); + + canvas.width = 1; + canvas.height = 256; + + for (var i in grad) { + gradient.addColorStop(+i, grad[i]); + } + + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, 1, 256); + + this._grad = ctx.getImageData(0, 0, 1, 256).data; + + return this; + }, + + draw: function () { + if (!this._grad) { + this.gradient(this.defaultGradient); + } + + var ctx = this._ctx; + var colored = ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); + this._colorize(colored.data, this._grad); + ctx.putImageData(colored, 0, 0); + + return this; + }, + + _colorize: function (pixels, gradient) { + for (var i = 3, len = pixels.length, j; i < len; i += 4) { + j = pixels[i] * 4; // get gradient color from opacity value + + if (j) { + pixels[i - 3] = gradient[j]; + pixels[i - 2] = gradient[j + 1]; + pixels[i - 1] = gradient[j + 2]; + } + } + }, + + _createCanvas: function() { + return this.canvasClass + ? new this.canvasClass() + : document.createElement('canvas'); + } +}; + +module.exports = torque_filters; + +},{}],27:[function(require,module,exports){ (function (global){ var torque = require('./core'); diff --git a/vendor/mustache.js b/vendor/mustache.js index 9e20b87133..40aa31bf95 100644 --- a/vendor/mustache.js +++ b/vendor/mustache.js @@ -5,78 +5,39 @@ /*global define: false*/ -var Mustache; - -(function (exports) { - if (typeof module !== "undefined" && typeof module.exports !== "undefined") { - module.exports = exports; // CommonJS - } else if (typeof define === "function") { - define(exports); // AMD +(function (global, factory) { + if (typeof exports === "object" && exports) { + factory(exports); // CommonJS + } else if (typeof define === "function" && define.amd) { + define(['exports'], factory); // AMD } else { - Mustache = exports; //