├── .gitignore ├── LICENSE ├── Makefile ├── README ├── VERSION ├── js ├── controllers │ ├── controller.js │ └── realtime.js ├── datasources │ └── datasource.js ├── end.js ├── inheritance.js ├── layers │ ├── bubble.js │ └── layer.js ├── license.js ├── maps │ ├── canvas.js │ ├── gmap.js │ ├── gmap2.js │ ├── map.js │ └── raphael.js ├── marks │ ├── gmap │ │ └── mark.js │ ├── gmap2 │ │ └── mark.js │ ├── gmap2_shape.js │ ├── raphael │ │ └── mark.js │ └── start.js ├── options.js ├── raphael_1.5.2.js ├── start.js └── util │ ├── colors.js │ ├── gmap_styles.js │ └── styles.js └── release └── rotarymaps_min.0.2.js /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Thumbtack 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted 5 | provided that the following conditions are met: 6 | 7 | 1) Redistributions of source code must retain the above copyright notice, this list of conditions 8 | and the following disclaimer. 9 | 10 | 2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions 11 | and the following disclaimer in the documentation and/or other materials provided with the 12 | distribution. 13 | 14 | 3) Neither the name of the copyright holder nor the names of its contributors may be used to 15 | endorse or promote products derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 18 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 19 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 23 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 24 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # RotaryMaps Makefile 2 | # 3 | # Requires YUI compressor and RaphaelJS 4 | 5 | VERSION = $(shell head -n 1 VERSION) 6 | 7 | JS_RM_FILES = \ 8 | js/raphael_1.5.2.js \ 9 | js/start.js \ 10 | js/inheritance.js \ 11 | js/options.js \ 12 | js/util/colors.js \ 13 | js/util/styles.js \ 14 | js/util/gmap_styles.js \ 15 | js/controllers/controller.js \ 16 | js/controllers/realtime.js \ 17 | js/datasources/datasource.js \ 18 | js/maps/map.js \ 19 | js/maps/canvas.js \ 20 | js/maps/raphael.js \ 21 | js/maps/gmap.js \ 22 | js/maps/gmap2.js \ 23 | js/marks/start.js \ 24 | js/marks/raphael/mark.js \ 25 | js/marks/gmap/mark.js \ 26 | js/marks/gmap2/mark.js \ 27 | js/layers/layer.js \ 28 | js/layers/bubble.js \ 29 | js/end.js 30 | 31 | JS_FILES = \ 32 | js/license.js \ 33 | $(JS_RM_FILES) 34 | 35 | all: build/rotarymaps.js 36 | 37 | min: build/rotarymaps_min.js 38 | 39 | release: release/rotarymaps_min.$(VERSION).js 40 | 41 | build/rotarymaps.js: $(JS_FILES) Makefile 42 | mkdir -p build 43 | cat $(JS_FILES) > $@ 44 | 45 | build/rotarymaps_min.js: build/rotarymaps.js 46 | rm -f $@ 47 | cat js/license.js >> $@ 48 | curl -s --data-urlencode 'js_code@build/rotarymaps.js' --data-urlencode 'output_format=text' \ 49 | --data-urlencode 'output_info=compiled_code' http://closure-compiler.appspot.com/compile \ 50 | >> $@ 51 | 52 | release/rotarymaps_min.$(VERSION).js: build/rotarymaps_min.js 53 | mkdir -p release 54 | cp build/rotarymaps_min.js $@ 55 | 56 | clean: 57 | rm -rf build/* 58 | 59 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | RotaryMaps 2 | ---------------- 3 | Integrating RaphaelJS with Google Maps. 4 | 5 | Created by Chris Mueller at Thumbtack (www.thumbtack.com) 6 | 7 | See example at: 8 | http://thumbtack.github.com/rotarymaps/ 9 | 10 | Sample code also at: 11 | https://gist.github.com/987822 12 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.2 2 | -------------------------------------------------------------------------------- /js/controllers/controller.js: -------------------------------------------------------------------------------- 1 | cg.Controller = cg.Class.extend({ 2 | init: function(map, datasource, layer, options) { 3 | this.options = cg.extend(OPTIONS.CONTROLLER, options); 4 | this.map = map; 5 | this.datasource = datasource; 6 | this.layer = layer; 7 | this.datasource.addController(this); 8 | this.layer.setMap(this.map).setController(this); 9 | }, 10 | refresh: function() { 11 | // Total, naive refresh 12 | var layer = this.layer; 13 | cg.each(this.datasource.getDataItems(), function(data_item) { 14 | layer.addDataItem(data_item); 15 | }); 16 | return this; 17 | } 18 | }); 19 | 20 | -------------------------------------------------------------------------------- /js/controllers/realtime.js: -------------------------------------------------------------------------------- 1 | cg.Realtime = cg.Controller.extend({ 2 | init: function(map, datasource, layer, options) { 3 | this._super(map, datasource, layer, cg.extend(OPTIONS.REALTIME, options)); 4 | setInterval(this.deleteExpired, 5000); 5 | }, 6 | updateOne: function(new_data_item) { 7 | var that = this, 8 | time = this.convertTimeIntoFutureMs(new_data_item.timestamp); 9 | window.setTimeout(function() { 10 | that.layer.addDataItem(new_data_item); 11 | }, time); 12 | return this; 13 | }, 14 | updateMany: function(new_data_items) { 15 | var that = this; 16 | cg.each(new_data_items, function(new_data_item) { 17 | that.updateOne(new_data_item); 18 | }); 19 | return this; 20 | }, 21 | convertTimeIntoFutureMs: function(desired_time) { 22 | var now = new Date().getTime(); 23 | return Math.max(0, (desired_time + this.options.expected_delay) - now); 24 | }, 25 | deleteExpired: function() { 26 | // TODO 27 | } 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /js/datasources/datasource.js: -------------------------------------------------------------------------------- 1 | cg.Datasource = cg.Class.extend({ 2 | init: function(raw_data) { 3 | this.data = raw_data && this.formatData(raw_data) || []; 4 | this.controllers = []; 5 | this.dirty = true; 6 | 7 | this.stats = {}; 8 | this.analyze(); 9 | }, 10 | addController: function(controller) { 11 | this.controllers.push(controller); 12 | }, 13 | load: function() { 14 | // Total refresh of the data 15 | cg.each(this.controllers, function(controller) { 16 | controller.refresh(); 17 | }); 18 | }, 19 | push: function(raw_data_item) { 20 | this.data.push(this.formatDataItem(raw_data_item)); 21 | cg.each(this.controllers, function(controller) { 22 | controller.updateOne(raw_data_item); 23 | }); 24 | return this; 25 | }, 26 | pushMany: function(raw_data_items) { 27 | var formatted = this.formatData(raw_data_items) 28 | i = 0 29 | ii = formatted.length; 30 | for (; i < ii; i++) { 31 | this.data.push(formatted[i]); 32 | } 33 | cg.each(this.controllers, function(controller) { 34 | controller.updateMany(formatted); 35 | }); 36 | return this; 37 | }, 38 | formatData: function(raw_data) { 39 | var results = [], 40 | that = this; 41 | 42 | cg.each(raw_data, function(raw_data_item) { 43 | that.formatDataItem(raw_data_item); 44 | results.push(raw_data_item); 45 | }); 46 | 47 | return results; 48 | }, 49 | formatDataItem: function(raw_data_item) { 50 | raw_data_item.__id = cg.uniqueDataId(); 51 | raw_data_item.__store = this; 52 | raw_data_item.__relative_value = function() { 53 | cg.convert(that.min(), that.max(), 0, 1, data_item.val); 54 | }; 55 | }, 56 | getDataItems: function() { 57 | return this.data; 58 | }, 59 | analyze: function(force) { 60 | if (this.dirty || force) { 61 | this.stats.max = this.reduce(function(item, current) { 62 | return (current === null || item.val > current) ? item.val : current; 63 | }); 64 | 65 | this.stats.min = this.reduce(function(item, current) { 66 | return (current === null || item.val < current) ? item.val : current; 67 | }); 68 | 69 | this.dirty = false; 70 | } 71 | return this; 72 | }, 73 | reduce: function(callback) { 74 | var result = null, key; 75 | for (key in this.data) { 76 | result = callback(this.data[key], result); 77 | } 78 | return result; 79 | } 80 | }); 81 | -------------------------------------------------------------------------------- /js/end.js: -------------------------------------------------------------------------------- 1 | return cg; 2 | })(Raphael.ninja()); 3 | -------------------------------------------------------------------------------- /js/inheritance.js: -------------------------------------------------------------------------------- 1 | // Simple JavaScript Inheritance 2 | // By John Resig http://ejohn.org/ 3 | // MIT Licensed. 4 | // 5 | // Inspired by base2 and Prototype 6 | (function() { 7 | var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 8 | // The base Class implementation (does nothing) 9 | cg.Class = function() {}; 10 | 11 | // Create a new Class that inherits from this class 12 | cg.Class.extend = function(properties) { 13 | var _super = this.prototype; 14 | 15 | // Instantiate a base class (but only create the instance, 16 | // don't run the init constructor) 17 | initializing = true; 18 | var prototype = new this(); 19 | initializing = false; 20 | 21 | // Copy the properties over onto the new prototype 22 | for (var name in properties) { 23 | // Check if we're overwriting an existing function 24 | prototype[name] = typeof properties[name] == "function" && 25 | typeof _super[name] == "function" && fnTest.test(properties[name]) ? 26 | (function(name, fn) { 27 | return function() { 28 | var tmp = this._super; 29 | 30 | // Add a new ._super() method that is the same method 31 | // but on the super-class 32 | this._super = _super[name]; 33 | 34 | // The method only need to be bound temporarily, so we 35 | // remove it when we're done executing 36 | var ret = fn.apply(this, arguments); 37 | this._super = tmp; 38 | 39 | return ret; 40 | }; 41 | })(name, properties[name]) : 42 | properties[name]; 43 | } 44 | 45 | // The dummy class constructor 46 | function Class() { 47 | // All construction is actually done in the init method 48 | if (!initializing && this.init) { 49 | this.init.apply(this, arguments); 50 | } 51 | } 52 | 53 | // Populate our constructed prototype object 54 | Class.prototype = prototype; 55 | 56 | // Enforce the constructor to be what we expect 57 | Class.constructor = Class; 58 | 59 | // And make this class extendable 60 | Class.extend = arguments.callee; 61 | 62 | return Class; 63 | }; 64 | })(); 65 | 66 | -------------------------------------------------------------------------------- /js/layers/bubble.js: -------------------------------------------------------------------------------- 1 | cg.Bubble = cg.Layer.extend({ 2 | init: function(options) { 3 | this._super(cg.extend(OPTIONS.BUBBLE, options)); 4 | this.markers = {}; 5 | }, 6 | addDataItem: function(data_item) { 7 | var fadeout_after = 0, 8 | fadeout_duration = 0, 9 | fadeout_callback = null, 10 | style = cg.extend(this.options.circle), 11 | opacity = style.opacity, 12 | radius = cg.callable(this.options.radius) ? 13 | this.options.radius(data_item) : this.options.radius, 14 | marker = this.map.circle(data_item.lat, data_item.lng, radius); 15 | 16 | style.opacity = this.options.fade_in ? 0 : style.opacity; 17 | 18 | marker.applyStyle(style); 19 | this.markers[data_item.__id] = marker; 20 | 21 | if (this.options.fade_in) { 22 | marker.node.animate({opacity: opacity}, this.options.fade_in); 23 | } 24 | 25 | // Is there a callback for when the marker was created 26 | if (this.options.marker && cg.callable(this.options.marker)) { 27 | this.options.marker(this, marker, data_item); 28 | } 29 | 30 | // Should we fade out the marker after it was created? 31 | if (this.options.fade_out) { 32 | if (this.options.fade_out instanceof Object) { 33 | fadeout_after = this.options.fade_out.after ? this.options.fade_out.after : 0; 34 | fadeout_duration = this.options.fade_out.duration ? 35 | this.options.fade_out.duration : 0; 36 | } else { 37 | fadeout_duration = parseInt(this.options.fade_out); 38 | } 39 | 40 | fadeout_callback = function() { 41 | marker.node.animate({opacity: 0}, fadeout_duration, function() { 42 | marker.remove(); 43 | }); 44 | }; 45 | 46 | if (fadeout_after) { 47 | setTimeout(fadeout_callback, fadeout_after); 48 | } else if (fadeout_duration) { 49 | fadeout_callback(); 50 | } 51 | } 52 | return this; 53 | }, 54 | removeDataItem: function(id) { 55 | this._super(id); 56 | this.map.removeMarker(this.markers[id]); 57 | delete this.markers[id]; 58 | return this; 59 | } 60 | }); 61 | -------------------------------------------------------------------------------- /js/layers/layer.js: -------------------------------------------------------------------------------- 1 | cg.Layer = cg.Class.extend({ 2 | init: function(options) { 3 | this.options = options; 4 | this.map = null; 5 | this.controller = null; 6 | this.listeners = ["moveEnd", "zoomEnd"]; 7 | }, 8 | getListeners: function() { 9 | return this.listeners; 10 | }, 11 | setController: function(controller) { 12 | this.controller = controller; 13 | }, 14 | setMap: function(map) { 15 | var that = this; 16 | this.map = map; 17 | cg.each(this.listeners, function(listener) { 18 | map.bind(listener, that[listener]); 19 | }); 20 | return this; 21 | }, 22 | addDataItem: function(data_item) { }, 23 | removeDataItem: function(id) { }, 24 | moveEnd: function() { }, 25 | zoomEnd: function(old_zoom, new_zoom) { } 26 | }); 27 | 28 | -------------------------------------------------------------------------------- /js/license.js: -------------------------------------------------------------------------------- 1 | /* 2 | * RotaryMaps.js 3 | * 4 | * Distributed under the MIT License 5 | * http://www.opensource.org/licenses/mit-license.php 6 | * 7 | * Created at thumbtack.com 8 | * 9 | */ 10 | 11 | -------------------------------------------------------------------------------- /js/maps/canvas.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thumbtack/rotarymaps/b3bf60d275489b657bf22a2d74749a0bded96bd3/js/maps/canvas.js -------------------------------------------------------------------------------- /js/maps/gmap.js: -------------------------------------------------------------------------------- 1 | cg.GMap = cg.Map.extend({ 2 | init: function(container) { 3 | this._super(container); 4 | }, 5 | addMarker: function(marker) { 6 | // no-op in this case 7 | return marker; 8 | }, 9 | circle: function(lat, lng, radius, options) { 10 | return this.addMarker(new cg.marks.gmap.Circle(this, lat, lng, radius)); 11 | }, 12 | removeMarker: function(marker) { 13 | marker.gmap_marker.setMap(null); 14 | marker.gmap_marker = null; 15 | marker = null; 16 | return this; 17 | } 18 | }); 19 | 20 | -------------------------------------------------------------------------------- /js/maps/gmap2.js: -------------------------------------------------------------------------------- 1 | cg.GMap2 = cg.Map.extend({ 2 | init: function(container) { 3 | var that = this; 4 | this._super(container); 5 | GEvent.addListener(container, "zoomend", function(old_level, new_level) { 6 | that.trigger("zoomEnd", old_level, new_level); 7 | }); 8 | GEvent.addListener(container, "moveend", function() { 9 | that.trigger("moveEnd"); 10 | }); 11 | GEvent.addListener(container, "maptypechanged", function() { 12 | that.trigger("mapTypeChanged"); 13 | }); 14 | }, 15 | colorize: function(color, opacity) { 16 | var rects = []; 17 | rects.push([ 18 | new GLatLng(-85,0), 19 | new GLatLng(85,0), 20 | new GLatLng(85,90), 21 | new GLatLng(-85,90)]); 22 | rects.push([ 23 | new GLatLng(-85,90), 24 | new GLatLng(85,90), 25 | new GLatLng(85,180), 26 | new GLatLng(-85,180)]); 27 | rects.push([ 28 | new GLatLng(-85,180.000001), 29 | new GLatLng(85,180.000001), 30 | new GLatLng(85,270), 31 | new GLatLng(-85,270)]); 32 | rects.push([ 33 | new GLatLng(-85,270), 34 | new GLatLng(85,270), 35 | new GLatLng(85,360), 36 | new GLatLng(-85,360)]); 37 | for (var i in rects) { 38 | this.container.addOverlay(new GPolygon(rects[i], null, 0, 0, color, opacity)); 39 | } 40 | }, 41 | addMarker: function(marker) { 42 | this.container.addOverlay(marker.getGMapMarker()); 43 | return marker; 44 | }, 45 | circle: function(lat, lng, radius, options) { 46 | return this.addMarker(new cg.marks.gmap2.Circle(this, lat, lng, radius)); 47 | }, 48 | removeMarker: function(marker) { 49 | marker.gmap_marker.remove(); 50 | marker = null; 51 | return this; 52 | } 53 | }); 54 | 55 | -------------------------------------------------------------------------------- /js/maps/map.js: -------------------------------------------------------------------------------- 1 | cg.Map = cg.Class.extend({ 2 | init: function(container, options) { 3 | this.id = cg.map_counter++; 4 | this.container = container; 5 | this.listeners = []; 6 | this.controllers = []; 7 | this.options = options; 8 | }, 9 | attach: function() { 10 | var controller, datasource, layer; 11 | if (arguments[0] instanceof cg.Controller) { 12 | controller = arguments[0]; 13 | } else { 14 | datasource = arguments[0]; 15 | layer = arguments[1]; 16 | controller = new cg.Controller(this, datasource, layer); 17 | } 18 | this.controllers.push(controller); 19 | return this; 20 | }, 21 | bind: function(event_type, callback) { 22 | if (!this.listeners[event_type]) { 23 | this.listeners[event_type] = []; 24 | } 25 | this.listeners[event_type].push(callback); 26 | return this; 27 | }, 28 | trigger: function(event_type) { 29 | var args = Array.prototype.slice.call(arguments, 1); 30 | if (this.listeners[event_type]) { 31 | for (var i in this.listeners[event_type]) { 32 | var listener = this.listeners[event_type][i]; 33 | listener.apply(listener, args); 34 | } 35 | } 36 | return this; 37 | }, 38 | addMarker: function(options) { }, 39 | removeMarker: function(marker) { }, 40 | circle: function(options) { } 41 | }); 42 | 43 | -------------------------------------------------------------------------------- /js/maps/raphael.js: -------------------------------------------------------------------------------- 1 | cg.RaphaelMap = cg.Map.extend({ 2 | init: function(container, options) { 3 | this._super(container, options); 4 | this.width = this.options.width; 5 | this.height = this.options.height; 6 | this.paper = raphael(container, this.width, this.height); 7 | }, 8 | bind: function(event_type, callback) { 9 | if (!this.listeners[event_type]) { 10 | this.listeners[event_type] = []; 11 | } 12 | this.listeners[event_type].push(callback); 13 | return this; 14 | }, 15 | handle: function(event_type) { 16 | var args = arguments.slice(1); 17 | if (this.listeners[event_type]) { 18 | for (var i in this.listeners[event_type]) { 19 | this.listeners[event_type][i](args); 20 | } 21 | } 22 | return this; 23 | }, 24 | layer: function(layer_object) { 25 | var listeners = layer_object.listensOn(); 26 | layer_object.setMap(this); 27 | for (var i in listeners) { 28 | this.bind(listeners[i], layer_object[listeners[i]]); 29 | } 30 | return this; 31 | }, 32 | circle: function(lat, lng, radius, options) { 33 | return new cg.marks.raphael.Circle(this, this.lngToX(lng), this.latToY(lat), radius); 34 | }, 35 | removeMarker: function(marker) { 36 | // assume marker is a valid Raphael SVG/VML object 37 | marker.remove(); 38 | return this; 39 | }, 40 | lngToX: function(lng) { 41 | return cg.convert(-180, 180, 0, this.width, lng); 42 | }, 43 | latToY: function(lat) { 44 | return this.height - cg.convert(-90, 90, 0, this.height, lat); 45 | } 46 | }); 47 | 48 | 49 | -------------------------------------------------------------------------------- /js/marks/gmap/mark.js: -------------------------------------------------------------------------------- 1 | cg.marks.gmap = {}; 2 | 3 | cg.marks.gmap.Mark = cg.Mark.extend({ 4 | init: function(map) { 5 | this._super(map); 6 | this.gmap_marker = null; 7 | initGMapMarks(); 8 | }, 9 | getGMapMarker: function() { 10 | return this.gmap_marker; 11 | } 12 | }); 13 | 14 | cg.marks.gmap.Circle = cg.marks.gmap.Mark.extend({ 15 | init: function(map, lat, lng, radius) { 16 | this._super(map); 17 | this.gmap_marker = new GMapPaper(map.container, lat, lng, radius * 2 + 2, radius * 2 + 2); 18 | this.node = this.gmap_marker.paper.circle(radius + 1, radius + 1, radius); 19 | } 20 | }); 21 | 22 | var GMapPaper = null; 23 | 24 | // Separate out the initilization, parent classes wouldn't be available otherwise 25 | function initGMapMarks() { 26 | if (GMapPaper !== null) { 27 | return; 28 | } 29 | 30 | GMapPaper = function(gmap, lat, lng, width, height) { 31 | this.bounds_ = null; 32 | this.map_ = null; 33 | this.div_ = null; 34 | this.image_ = null; 35 | 36 | this.point = new google.maps.LatLng(lat, lng); 37 | this.width = width; 38 | this.height = height; 39 | this.div_ = document.createElement("div"), 40 | this.div_.style.position = "absolute"; 41 | this.paper = raphael(this.div_, width, height); 42 | 43 | this.setMap(gmap); 44 | }; 45 | 46 | GMapPaper.prototype = new google.maps.OverlayView(); 47 | 48 | GMapPaper.prototype.onAdd = function() { 49 | var panes = this.getPanes(); 50 | panes.overlayLayer.appendChild(this.div_); 51 | }; 52 | 53 | GMapPaper.prototype.onRemove = function() { 54 | this.div_.parentNode.removeChild(this.div_); 55 | this.div_ = null; 56 | }; 57 | 58 | GMapPaper.prototype.draw = function() { 59 | var coord, height, projection = this.getProjection(); 60 | coord = projection.fromLatLngToDivPixel(this.point), 61 | height = parseInt(this.div_.clientHeight); 62 | this.div_.style.left = (coord.x - (this.width * .5)) + "px"; 63 | this.div_.style.top = (coord.y + (this.height * .5) - height) + "px"; 64 | }; 65 | } 66 | 67 | -------------------------------------------------------------------------------- /js/marks/gmap2/mark.js: -------------------------------------------------------------------------------- 1 | cg.marks.gmap2 = {}; 2 | 3 | cg.marks.gmap2.Mark = cg.Mark.extend({ 4 | init: function(map) { 5 | this._super(map); 6 | this.gmap_marker = null; 7 | initGMap2Marks(); 8 | }, 9 | getGMapMarker: function() { 10 | return this.gmap_marker; 11 | } 12 | }); 13 | 14 | cg.marks.gmap2.Circle = cg.marks.gmap2.Mark.extend({ 15 | init: function(map, lat, lng, radius) { 16 | this._super(map); 17 | this.gmap_marker = new GMap2Paper(lat, lng, radius * 2 + 2, radius * 2 + 2); 18 | this.node = this.gmap_marker.paper.circle(radius + 1, radius + 1, radius); 19 | } 20 | }); 21 | 22 | var GMap2Paper = null; 23 | 24 | // Separate out the initilization, parent classes wouldn't be available otherwise 25 | function initGMap2Marks() { 26 | if (GMap2Paper !== null) { 27 | return; 28 | } 29 | 30 | GMap2Paper = function(lat, lng, width, height) { 31 | this.point = new GLatLng(lat, lng); 32 | this.width = width; 33 | this.height = height; 34 | this.div_ = document.createElement("div"), 35 | this.div_.style.position = "absolute"; 36 | this.paper = raphael(this.div_, width, height); 37 | }; 38 | 39 | GMap2Paper.prototype = cg.inheritFrom(GOverlay); 40 | 41 | GMap2Paper.prototype.initialize = function(map) { 42 | this.map_ = map; 43 | map.getPane(G_MAP_MARKER_PANE).appendChild(this.div_); 44 | }; 45 | 46 | GMap2Paper.prototype.remove = function() { 47 | this.div_.parentNode.removeChild(this.div_); 48 | this.div_ = null; 49 | }; 50 | 51 | GMap2Paper.prototype.copy = function() { 52 | return new GMap2Paper(this.point, width, height); 53 | }; 54 | 55 | GMap2Paper.prototype.redraw = function(force) { 56 | var coord, height; 57 | if (force) { 58 | coord = this.map_.fromLatLngToDivPixel(this.point), 59 | height = parseInt(this.div_.clientHeight); 60 | this.div_.style.left = (coord.x - (this.width * .5)) + "px"; 61 | this.div_.style.top = (coord.y + (this.height * .5) - height) + "px"; 62 | } 63 | }; 64 | 65 | GMap2Paper.prototype.setPoint = function(point) { 66 | this.point = point; 67 | this.redraw(true); 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /js/marks/gmap2_shape.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Basic vector shape 3 | */ 4 | 5 | Shape = function( point, radius, options ) { 6 | if( !point ) { return; } 7 | this.point = point; 8 | this.pixelOffset = new GSize(-1*radius,radius); 9 | this.radius = radius; 10 | this.overlap = false; 11 | this.hidden = false; 12 | 13 | var defaults = { 14 | "shape": "circle", // or "square" 15 | "color": "#fc8d59", 16 | "stroke": "#000", 17 | "colorHover": "#ffffbf", 18 | "colorActive": "#ffffbf", 19 | "opacity":0.8, 20 | 21 | /* location in google map pane hierarchy */ 22 | "isBackground": false, // set to "true" to have this be unclickable in the background 23 | "zIndexProcess": null, 24 | 25 | /* interaction */ 26 | "animate": false, 27 | "onclick": null, 28 | "onmouseover": null, 29 | "onmouseout": null, 30 | "infoWindow": null 31 | }; 32 | this.opts = cg.parseOptions( defaults, options ); 33 | if( this.opts.opacity < 1 ) { 34 | this.opts.opacity *= 100.0; 35 | } 36 | 37 | this.percentOpacity = this.opts.opacity; 38 | }; 39 | 40 | Shape.prototype = cg.extend( GOverlay ); 41 | 42 | Shape.prototype.makeNode = function() { 43 | // default is circle 44 | var div = document.createElement("div"), 45 | radius = ( this.opts.animate ) ? this.radius * .5 : this.radius - 1, 46 | color = this.opts.color, 47 | bcolor = Raphael.rgb2hsb( color ); 48 | div.style.position = "absolute"; 49 | this.paper = Raphael( div, this.radius*2, this.radius*2 ); 50 | var c = this.paper.circle( this.radius, this.radius, radius ).attr({ 51 | gradient:"90-hsb(" + bcolor.h + "," + bcolor.s + "," + Math.max(0,bcolor.b-.20) + ")-" + color, stroke:this.opts.stroke 52 | }); 53 | this.div_ = div; 54 | if( this.opts.animate ) { 55 | c.animate({r:this.radius - 1 }, 2000, ">"); 56 | } 57 | return c; 58 | }; 59 | 60 | Shape.prototype.initialize = function(map) { 61 | var that = this; 62 | var c = this.makeNode(); 63 | this.maintainOver = false; 64 | 65 | if( this.opts.isBackground ) { 66 | map.getPane( G_MAP_FLOAT_SHADOW_PANE ).appendChild( this.div_ ); 67 | } else { 68 | if( cg.callable( that.opts.onclick ) || that.opts.infoWindow ) { 69 | c.node.style.cursor = "pointer"; 70 | } 71 | 72 | try { 73 | c.node.onclick = function() { 74 | if( that.opts.infoWindow ) { 75 | var info = map.openInfoWindow( that.point, that.opts.infoWindow, 76 | { onCloseFn: function() { 77 | that.maintainOver = false; 78 | var color = that.opts.color, 79 | bcolor = Raphael.rgb2hsb( color ); 80 | c.attr({ gradient:"90-hsb(" + bcolor.h + "," + bcolor.s + "," + Math.max(0,bcolor.b-.20) + ")-" + color }); 81 | }}); 82 | if( !that.maintainOver ) { 83 | var color = that.opts.colorHover, 84 | bcolor = Raphael.rgb2hsb( color ); 85 | c.attr({ gradient:"90-hsb(" + bcolor.h + "," + bcolor.s + "," + Math.max(0,bcolor.b-.20) + ")-" + color }); 86 | } 87 | that.maintainOver = !that.maintainOver; 88 | } 89 | if( cg.callable( that.opts.onclick ) ) { that.opts.onclick(); } 90 | }; 91 | c.node.onmouseover = function() { 92 | var color = that.opts.colorHover, 93 | bcolor = Raphael.rgb2hsb( color ); 94 | c.attr({ gradient:"90-hsb(" + bcolor.h + "," + bcolor.s + "," + Math.max(0,bcolor.b-.20) + ")-" + color }); 95 | if( cg.callable( that.opts.onmouseover ) ) { that.opts.onmouseover(); } 96 | }; 97 | c.node.onmouseout = function() { 98 | if(!that.maintainOver) { 99 | var color = that.opts.color, 100 | bcolor = Raphael.rgb2hsb( color ); 101 | c.attr({ gradient:"90-hsb(" + bcolor.h + "," + bcolor.s + "," + Math.max(0,bcolor.b-.20) + ")-" + color }); 102 | } 103 | if( cg.callable( that.opts.onmouseout ) ) { that.opts.onmouseout(); } 104 | }; 105 | } catch(e) { } 106 | 107 | map.getPane( G_MAP_MARKER_PANE ).appendChild( this.div_ ); 108 | } 109 | 110 | this.map_ = map; 111 | 112 | if (this.percentOpacity) { 113 | if(typeof(this.div_.style.filter)=='string'){this.div_.style.filter='alpha(opacity:'+this.percentOpacity+')';} 114 | if(typeof(this.div_.style.KHTMLOpacity)=='string'){this.div_.style.KHTMLOpacity=this.percentOpacity/100;} 115 | if(typeof(this.div_.style.MozOpacity)=='string'){this.div_.style.MozOpacity=this.percentOpacity/100;} 116 | if(typeof(this.div_.style.opacity)=='string'){this.div_.style.opacity=this.percentOpacity/100;} 117 | } 118 | if( this.opts.zIndexProcess ) { 119 | try { 120 | this.div_.style.zIndex = this.opts.zIndexProcess(); 121 | } catch(e) { 122 | // TODO fix this in IE; non-essential for basic rendering 123 | } 124 | } 125 | if( this.hidden ) { 126 | this.hide(); 127 | } 128 | }; 129 | 130 | Shape.prototype.remove = function() { 131 | this.div_.parentNode.removeChild(this.div_); 132 | }; 133 | 134 | Shape.prototype.copy = function() { 135 | return new Shape(this.point, this.pixelOffset, this.percentOpacity, this.overlap); 136 | }; 137 | 138 | Shape.prototype.redraw = function(force) { 139 | var p = this.map_.fromLatLngToDivPixel(this.point); 140 | var h = parseInt(this.div_.clientHeight); 141 | this.div_.style.left = (p.x + this.pixelOffset.width) + "px"; 142 | this.div_.style.top = (p.y +this.pixelOffset.height - h) + "px"; 143 | }; 144 | 145 | Shape.prototype.show = function() { 146 | if (this.div_) { 147 | this.div_.style.display=""; 148 | this.redraw(); 149 | } 150 | this.hidden = false; 151 | }; 152 | 153 | Shape.prototype.hide = function() { 154 | if (this.div_) { 155 | this.div_.style.display="none"; 156 | } 157 | this.hidden = true; 158 | }; 159 | 160 | Shape.prototype.isHidden = function() { 161 | return this.hidden; 162 | }; 163 | 164 | Shape.prototype.supportsHide = function() { 165 | return true; 166 | }; 167 | 168 | Shape.prototype.setContents = function(html) { 169 | this.html = html; 170 | this.div_.innerHTML = '
' + this.html + '
' ; 171 | this.redraw(true); 172 | }; 173 | 174 | Shape.prototype.setPoint = function(point) { 175 | this.point = point; 176 | if( this.overlap ) { 177 | var z = GOverlay.getZIndex( this.point.lat() ); 178 | this.div_.style.zIndex = z; 179 | } 180 | this.redraw( true ); 181 | }; 182 | 183 | Shape.prototype.setOpacity = function( percentOpacity ) { 184 | if( percentOpacity ) { 185 | if( percentOpacity<0 ) { percentOpacity = 0; } 186 | if(percentOpacity>100){ percentOpacity=100; } 187 | } 188 | this.percentOpacity = percentOpacity; 189 | if ( this.percentOpacity ) { 190 | if( typeof( this.div_.style.filter ) == 'string' ){ this.div_.style.filter = 'alpha(opacity:' + this.percentOpacity + ')'; } 191 | if( typeof( this.div_.style.KHTMLOpacity ) == 'string' ){ this.div_.style.KHTMLOpacity = this.percentOpacity/100; } 192 | if( typeof( this.div_.style.MozOpacity ) == 'string' ){ this.div_.style.MozOpacity = this.percentOpacity/100; } 193 | if( typeof( this.div_.style.opacity ) == 'string' ){ this.div_.style.opacity = this.percentOpacity/100; } 194 | } 195 | }; 196 | 197 | Shape.prototype.getPoint = function() { 198 | return this.point; 199 | }; 200 | 201 | -------------------------------------------------------------------------------- /js/marks/raphael/mark.js: -------------------------------------------------------------------------------- 1 | cg.marks.raphael = {}; 2 | 3 | cg.marks.raphael.Mark = cg.Mark.extend({ 4 | init: function(map) { 5 | this._super(map); 6 | } 7 | }); 8 | 9 | cg.marks.raphael.Circle = cg.marks.raphael.Mark.extend({ 10 | init: function(map, x, y, radius) { 11 | this._super(map); 12 | this.node = this.map.paper.circle(x, y, radius); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /js/marks/start.js: -------------------------------------------------------------------------------- 1 | cg.marks = {}; 2 | 3 | cg.Mark = cg.Class.extend({ 4 | init: function(map, id) { 5 | // Rotary Map class 6 | this.map = map; 7 | // SVG node 8 | this.node = null; 9 | }, 10 | remove: function() { 11 | this.map.removeMarker(this); 12 | }, 13 | applyStyle: function(options, override) { 14 | cg.applySvgStyle(this.node, options, override); 15 | } 16 | }); 17 | 18 | -------------------------------------------------------------------------------- /js/options.js: -------------------------------------------------------------------------------- 1 | var OPTIONS = {}; 2 | 3 | // How to style a Raphael SVG circle 4 | OPTIONS.CIRCLE = { 5 | // SVG attributes, see http://raphaeljs.com/reference.html#attr 6 | cursor: "pointer", 7 | fill: "#ff0000", 8 | opacity: 1, 9 | stroke: "#333", 10 | "stroke-width": 1, 11 | 12 | // Custom attributes 13 | emboss: true 14 | }; 15 | 16 | OPTIONS.CLUSTER = { 17 | enable_dots: true, 18 | enable_grid: false, 19 | combine: function(a, b) { return a + b; }, 20 | radius_from_value: function(val) { return val; }, 21 | 22 | // colors for dots 23 | color: "#fc8d59", 24 | stroke: "#000", 25 | hover_color: "#ffffbf", 26 | active_color: "#ffffbf", 27 | opacity: 0.8, 28 | 29 | // grid 30 | grid_color: "#5e4fa2", 31 | grid_size: 24.0, 32 | 33 | // when user clicks on a grid cell 34 | onclick: function(grid_cell) { 35 | cg.log("onclick"); 36 | } 37 | }; 38 | 39 | OPTIONS.CHOROPLETH = { 40 | color_scheme: "spectral", 41 | reverse_colors: false 42 | }; 43 | 44 | OPTIONS.BUBBLE = { 45 | radius: function(data_item) { 46 | return data_item && data_item.val ? data_item.val : 1; 47 | }, 48 | // could be a callback function to return the color 49 | color_by: "value", 50 | // Number of seconds to fade in 51 | fade_in: null, 52 | // Fade the marker after it is created 53 | // - Integer: simple fade, starts immediately, duration is in ms 54 | // - Object: {after: N, duration: M} N, M are milliseconds 55 | fade_out: { 56 | after: null, 57 | duration: null 58 | }, 59 | // When a marker is created, call this callback 60 | marker: null, 61 | circle: OPTIONS.CIRCLE 62 | }; 63 | 64 | OPTIONS.CONTROLLER = {}; 65 | 66 | OPTIONS.REALTIME = { 67 | expected_delay: 60000 68 | }; 69 | 70 | OPTIONS.TIMELINE = { 71 | something: true 72 | }; 73 | -------------------------------------------------------------------------------- /js/start.js: -------------------------------------------------------------------------------- 1 | var RotaryMaps = window.RotaryMaps = (function(raphael) { 2 | 3 | var cg = {}, 4 | map_counter = 0, 5 | data_id = 0, 6 | fudge = 0.0001; 7 | 8 | cg.log = function() { 9 | if (window.console) { 10 | console.log(arguments); 11 | } 12 | }; 13 | 14 | cg.callable = function(val) { 15 | return typeof val == "function"; 16 | }; 17 | 18 | cg.isObject = function(val) { 19 | return typeof val == "object"; 20 | }; 21 | 22 | cg.convert = function(min_in, max_in, min_out, max_out, val) { 23 | val = parseFloat(Math.min(max_in, Math.max(min_in, val))) 24 | return parseFloat(val - min_in) / parseFloat(max_in - min_in) * (max_out - min_out) + min_out; 25 | }; 26 | 27 | cg.extend = function() { 28 | var rtn = {}, 29 | i = 0, 30 | ii = arguments.length; 31 | 32 | for (; i < ii; i++) { 33 | cg.each(arguments[i], function(val, key) { 34 | if (val && cg.isObject(val) && !cg.callable(val)) { 35 | rtn[key] = cg.extend(rtn[key] && cg.isObject(rtn[key]) ? rtn[key] : {}, val); 36 | } else { 37 | rtn[key] = val; 38 | } 39 | }); 40 | } 41 | return rtn; 42 | }; 43 | 44 | cg.config = function(option, value) { 45 | if (option instanceof Object) { 46 | for (var key in option) { 47 | cg.config(key, option[key]); 48 | } 49 | return; 50 | } 51 | switch (option) { 52 | case "fudge": 53 | fudge = value; 54 | break; 55 | case "logging": 56 | if (!value) { 57 | // turn off logging 58 | cg.log = function() {}; 59 | } 60 | break; 61 | case "raphael": 62 | // Use a custom Raphael (e.g. a globally scoped one instead of the local one) 63 | raphael = value; 64 | break; 65 | default: 66 | cg.log(option, "Not available"); 67 | }; 68 | }; 69 | 70 | cg.uniqueDataId = function() { 71 | return data_id++; 72 | }; 73 | 74 | cg.each = function(arr, callback) { 75 | for (var i in arr) { 76 | callback(arr[i], i); 77 | } 78 | }; 79 | 80 | cg.inheritFrom = function(f) { 81 | function g() {} 82 | g.prototype = f.prototype || f; 83 | return new g(); 84 | }; 85 | 86 | 87 | // TODO - build AUTO or TEMPLATE methods to make construction of maps simpler 88 | 89 | cg.autoController = function(dom_element, options) { 90 | var controller = new cg.Controller(options), 91 | map = new cg.SvgMap(dom_element); 92 | return controller.addMap(map); 93 | }; 94 | 95 | cg.auto = { 96 | bubble: function(dom_element, data, options) { 97 | // etc etc 98 | } 99 | }; 100 | -------------------------------------------------------------------------------- /js/util/colors.js: -------------------------------------------------------------------------------- 1 | cg.GRADIENTS = { 2 | "spectral": ['#9e0142','#d53e4f','#f46d43','#fdae61','#fee08b','#ffffbf','#e6f598','#abdda4','#66c2a5','#3288bd','#5e4fa2'], 3 | "stoplight": ['#a50026','#d73027','#f46d43','#fdae61','#fee08b','#ffffbf','#d9ef8b','#a6d96a','#66bd63','#1a9850','#006837'], 4 | "accent": ['#7fc97f','#beaed4','#fdc086','#ffff99','#386cb0','#f0027f','#bf5b17','#666666'], 5 | "sunrise": ['#fff7ec','#fee8c8','#fdd49e','#fdbb84','#fc8d59','#ef6548','#d7301f','#b30000','#7f0000'], 6 | "floyd": ['#fff7fb','#ece7f2','#d0d1e6','#a6bddb','#74a9cf','#3690c0','#0570b0','#045a8d','#023858'], 7 | "majesty": ['#f7fcfd','#e0ecf4','#bfd3e6','#9ebcda','#8c96c6','#8c6bb1','#88419d','#810f7c','#4d004b'], 8 | "stoic" : ['#67001f','#b2182b','#d6604d','#f4a582','#fddbc7','#ffffff','#e0e0e0','#bababa','#878787','#4d4d4d','#1a1a1a'], 9 | "meadow": ['#fff7fb','#ece2f0','#d0d1e6','#a6bddb','#67a9cf','#3690c0','#02818a','#016c59','#014636'], 10 | "mars": ['#7f3b08','#b35806','#e08214','#fdb863','#fee0b6','#f7f7f7','#d8daeb','#b2abd2','#8073ac','#542788','#2d004b'], 11 | "jupiter": ['#ffffcc','#ffeda0','#fed976','#feb24c','#fd8d3c','#fc4e2a','#e31a1c','#bd0026','#800026'], 12 | "neptune": ['#f7fbff','#deebf7','#c6dbef','#9ecae1','#6baed6','#4292c6','#2171b5','#08519c','#08306b'], 13 | "pluto": ['#f7f4f9','#e7e1ef','#d4b9da','#c994c7','#df65b0','#e7298a','#ce1256','#980043','#67001f'], 14 | "ink": ['#ffffff','#f0f0f0','#d9d9d9','#bdbdbd','#969696','#737373','#525252','#252525','#000000'], 15 | "reef": ['#f7fcf0','#e0f3db','#ccebc5','#a8ddb5','#7bccc4','#4eb3d3','#2b8cbe','#0868ac','#084081'] 16 | }; 17 | 18 | cg.COLOR_BLOCKS = { 19 | // Non-gradient colors schemes. Must be simple Arrays 20 | "oneset" : ['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33','#a65628','#f781bf','#999999'], 21 | "twoset": ['#a50026','#d73027','#f46d43','#fdae61','#fee08b','#ffffbf','#d9ef8b','#a6d96a','#66bd63','#1a9850','#006837'], 22 | "threeset": ['#8dd3c7','#ffffb3','#bebada','#fb8072','#80b1d3','#fdb462','#b3de69','#fccde5','#d9d9d9','#bc80bd','#ccebc5','#ffed6f'], 23 | "falcon": ['#1b9e77','#d95f02','#7570b3','#e7298a','#66a61e','#e6ab02','#a6761d','#666666'], 24 | "paired": ['#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a','#ffff99'] 25 | }; 26 | 27 | cg.colorScheme = function(name) { 28 | var gradient_min = null, 29 | gradient_max = null, 30 | scaled_gradient = {}, 31 | num, pos; 32 | 33 | if (cg.GRADIENTS.hasOwnProperty(name)) { 34 | return cg.gradientColorScheme(name); 35 | } else if (cg.COLOR_BLOCKS.hasOwnProperty(name)) { 36 | return cg.blockColorScheme(name); 37 | } else { 38 | throw "Invalid color scheme"; 39 | } 40 | }; 41 | 42 | cg.gradientColorScheme = function(name) { 43 | var gradient = null, 44 | gradient_min = null, 45 | gradient_max = null, 46 | scaled_gradient = {}, 47 | num, pos; 48 | 49 | if (cg.GRADIENTS.hasOwnProperty(name)) { 50 | gradient = cg.GRADIENTS[name]; 51 | } else { 52 | throw "Invalid color scheme."; 53 | } 54 | 55 | for (num in gradient) { 56 | if (gradient_min === null || num < gradient_min) { 57 | gradient_min = num; 58 | } 59 | if (gradient_max === null || num > gradient_max) { 60 | gradient_max = num; 61 | } 62 | } 63 | 64 | for (num in gradient) { 65 | pos = cg.convert(gradient_min, gradient_max, 0, 1, num); 66 | scaled_gradient[pos] = gradient[num]; 67 | } 68 | 69 | return function(val, lock_to_color) { 70 | // val should be from 0 to 1, and we'll return the color on the gradient 71 | // that matches 72 | var previous_step = null, 73 | pos = null; 74 | 75 | if (val - fudge < 0) { 76 | // return the first gradient element 77 | for (var key in scaled_gradient) { 78 | return scaled_gradient[key]; 79 | } 80 | } else if (val + fudge > 1) { 81 | // return the last gradient element 82 | for (var key in scaled_gradient) { 83 | previous_step = scaled_gradient[key]; 84 | } 85 | return previous_step; 86 | } else { 87 | // find something in the middle 88 | for (var key in scaled_gradient) { 89 | if (key == val) { 90 | return val; 91 | } 92 | if (val > key) { 93 | previous_step = key; 94 | continue; 95 | } else { 96 | pos = cg.convert(previous_step, key, 0, 1, val); 97 | // TODO Now in theory we find the color in between the two colors 98 | return previous_step; 99 | } 100 | } 101 | return previous_step; 102 | } 103 | }; 104 | }; 105 | 106 | cg.blockColorScheme = function(scheme_name) { 107 | // these color schemes aren't meant to be gradations 108 | var scheme = cg.COLOR_BLOCKS[scheme_name]; 109 | 110 | return function(val) { 111 | var pos = parseInt(Math.floor(cg.scale(0, 1, 0, scheme.length + 1, val))); 112 | return scheme[Math.max(0, Math.min(pos, scheme.length))]; 113 | }; 114 | }; 115 | 116 | -------------------------------------------------------------------------------- /js/util/gmap_styles.js: -------------------------------------------------------------------------------- 1 | // Some of these are from http://www.41latitude.com/post/1268734799/google-styled-maps 2 | // Some are custom built 3 | 4 | cg.GMAP_STYLES = {}; 5 | 6 | cg.GMAP_STYLES.Neutral = [ 7 | { 8 | featureType: "administrative", 9 | elementType: "all", 10 | stylers: [ 11 | { saturation: -100 } 12 | ] 13 | },{ 14 | featureType: "landscape", 15 | elementType: "all", 16 | stylers: [ 17 | { saturation: -100 } 18 | ] 19 | },{ 20 | featureType: "poi", 21 | elementType: "all", 22 | stylers: [ 23 | { saturation: -100 } 24 | ] 25 | },{ 26 | featureType: "road", 27 | elementType: "all", 28 | stylers: [ 29 | { saturation: -100 } 30 | ] 31 | },{ 32 | featureType: "transit", 33 | elementType: "all", 34 | stylers: [ 35 | { saturation: -100 } 36 | ] 37 | },{ 38 | featureType: "water", 39 | elementType: "all", 40 | stylers: [ 41 | { saturation: -100 } 42 | ] 43 | } 44 | ]; 45 | 46 | cg.GMAP_STYLES.Subtle = [ 47 | { 48 | featureType: "all", 49 | elementType: "all", 50 | stylers: [ 51 | { saturation: -47 } 52 | ] 53 | },{ 54 | featureType: "poi", 55 | elementType: "all", 56 | stylers: [ 57 | { visibility: "off" } 58 | ] 59 | },{ 60 | featureType: "water", 61 | elementType: "all", 62 | stylers: [ 63 | { saturation: -68 }, 64 | { visibility: "on" }, 65 | { lightness: 50 } 66 | ] 67 | },{ 68 | featureType: "road.local", 69 | elementType: "all", 70 | stylers: [ 71 | { visibility: "simplified" } 72 | ] 73 | },{ 74 | featureType: "road.arterial", 75 | elementType: "all", 76 | stylers: [ 77 | { visibility: "simplified" } 78 | ] 79 | },{ 80 | featureType: "road.highway", 81 | elementType: "all", 82 | stylers: [ 83 | { visibility: "simplified" } 84 | ] 85 | },{ 86 | featureType: "administrative.province", 87 | elementType: "all", 88 | stylers: [ 89 | { visibility: "simplified" } 90 | ] 91 | },{ 92 | featureType: "transit", 93 | elementType: "all", 94 | stylers: [ 95 | 96 | ] 97 | } 98 | ]; 99 | 100 | cg.setGMapStyle = function(gmap, name) { 101 | var options = { 102 | name: name 103 | }, 104 | map_type = new google.maps.StyledMapType(cg.GMAP_STYLES[name], options); 105 | 106 | gmap.mapTypes.set(name, map_type); 107 | gmap.setMapTypeId(name); 108 | 109 | }; 110 | -------------------------------------------------------------------------------- /js/util/styles.js: -------------------------------------------------------------------------------- 1 | cg.applySvgStyle = function(svg_object, options, override) { 2 | var bcolor = null, 3 | style = cg.extend(options, override || {}); 4 | 5 | // Custom options 6 | if (style.emboss) { 7 | bcolor = raphael.rgb2hsb(style.fill); 8 | style.fill = "90-hsb(" + bcolor.h + "," + bcolor.s + "," + 9 | Math.max(0, bcolor.b - .20) + ")-" + style.fill; 10 | } 11 | 12 | svg_object.attr(style); 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /release/rotarymaps_min.0.2.js: -------------------------------------------------------------------------------- 1 | /* 2 | * RotaryMaps.js 3 | * 4 | * Distributed under the MIT License 5 | * http://www.opensource.org/licenses/mit-license.php 6 | * 7 | * Created at thumbtack.com 8 | * 9 | */ 10 | 11 | /* 12 | * Raphael 1.5.2 - JavaScript Vector Library 13 | * 14 | * Copyright (c) 2010 Dmitry Baranovskiy (http://raphaeljs.com) 15 | * Licensed under the MIT (http://raphaeljs.com/license.html) license. 16 | */ 17 | (function(){function aI(){if(aI.is(arguments[0],a7)){var b=arguments[0],d=F[bB](aI,b.splice(0,3+aI.is(b[0],aF))),R=d.set();for(var E=0,S=b[s];E';aB=ax.firstChild;aB.style.behavior="url(#default#VML)";if(!(aB&&typeof aB.adj=="object")){return aI.type=null}ax=null}aI.svg=!(aI.vml=aI.type=="VML");bz[bE]=aI[bE];aZ=bz[bE];aI._id=0;aI._oid=0;aI.fn={};aI.is=function(d,b){b=bG.call(b);if(b=="finite"){return !ap[ag](+d)}return(b=="null"&&d===null)||(b==typeof d)||(b=="object"&&d===Object(d))||(b=="array"&&Array.isArray&&Array.isArray(d))||aT.call(d).slice(8,-1).toLowerCase()==b};aI.angle=function(R,bH,e,S,d,E){if(d==null){var b=R-e,bI=bH-S;if(!b&&!bI){return 0}return((b<0)*180+ao.atan(-bI/-b)*180/aM+360)%360}else{return aI.angle(R,bH,d,E)-aI.angle(e,S,d,E)}};aI.rad=function(b){return b%360*aM/180};aI.deg=function(b){return b*180/aM%360};aI.snapTo=function(d,E,b){b=aI.is(b,"finite")?b:10;if(aI.is(d,a7)){var e=d.length;while(e--){if(aq(d[e]-E)<=b){return d[e]}}}else{d=+d;var R=E%d;if(Rd-b){return E-R+d}}return E};function j(){var d=[],b=0;for(;b<32;b++){d[b]=(~~(ao.random()*16))[a0](16)}d[12]=4;d[16]=((d[16]&3)|8)[a0](16);return"r-"+d[aW]("")}aI.setWindow=function(b){aQ=b;aa=aQ.document};var a9=function(E){if(aI.vml){var b=/^\s+|\s+$/g;var S;try{var bH=new ActiveXObject("htmlfile");bH.write("");bH.close();S=bH.body}catch(bI){S=createPopup().document.body}var d=S.createTextRange();a9=aA(function(bJ){try{S.style.color=bC(bJ)[bs](b,aP);var bK=d.queryCommandValue("ForeColor");bK=((bK&255)<<16)|(bK&65280)|((bK&16711680)>>>16);return"#"+("000000"+bK[a0](16)).slice(-6)}catch(bL){return"none"}})}else{var R=aa.createElement("i");R.title="Rapha\xebl Colour Picker";R.style.display="none";aa.body[bk](R);a9=aA(function(e){R.style.color=e;return aa.defaultView.getComputedStyle(R,aP).getPropertyValue("color")})}return a9(E)},aC=function(){return"hsb("+[this.h,this.s,this.b]+")"},M=function(){return"hsl("+[this.h,this.s,this.l]+")"},B=function(){return this.hex};aI.hsb2rgb=function(E,e,d,R){if(aI.is(E,"object")&&"h" in E&&"s" in E&&"b" in E){d=E.b;e=E.s;E=E.h;R=E.o}return aI.hsl2rgb(E,e,d/2,R)};aI.hsl2rgb=function(bH,bO,E,e){if(aI.is(bH,"object")&&"h" in bH&&"s" in bH&&"l" in bH){E=bH.l;bO=bH.s;bH=bH.h}if(bH>1||bO>1||E>1){bH/=360;bO/=100;E/=100}var bM={},bJ=["r","g","b"],bI,bL,S,d,bK,bN;if(!bO){bM={r:E,g:E,b:E}}else{if(E<0.5){bI=E*(1+bO)}else{bI=E+bO-E*bO}bL=2*E-bI;for(var R=0;R<3;R++){S=bH+1/3*-(R-1);S<0&&S++;S>1&&S--;if(S*6<1){bM[bJ[R]]=bL+(bI-bL)*6*S}else{if(S*2<1){bM[bJ[R]]=bI}else{if(S*3<2){bM[bJ[R]]=bL+(bI-bL)*(2/3-S)*6}else{bM[bJ[R]]=bL}}}}}bM.r*=255;bM.g*=255;bM.b*=255;bM.hex="#"+(16777216|bM.b|(bM.g<<8)|(bM.r<<16)).toString(16).slice(1);aI.is(e,"finite")&&(bM.opacity=e);bM.toString=B;return bM};aI.rgb2hsb=function(b,d,bI){if(d==null&&aI.is(b,"object")&&"r" in b&&"g" in b&&"b" in b){bI=b.b;d=b.g;b=b.r}if(d==null&&aI.is(b,af)){var bK=aI.getRGB(b);b=bK.r;d=bK.g;bI=bK.b}if(b>1||d>1||bI>1){b/=255;d/=255;bI/=255}var bH=m(b,d,bI),e=bi(b,d,bI),R,E,S=bH;if(e==bH){return{h:0,s:0,b:bH,toString:aC}}else{var bJ=(bH-e);E=bJ/bH;if(b==bH){R=(d-bI)/bJ}else{if(d==bH){R=2+((bI-b)/bJ)}else{R=4+((b-d)/bJ)}}R/=6;R<0&&R++;R>1&&R--}return{h:R,s:E,b:S,toString:aC}};aI.rgb2hsl=function(d,e,bH){if(e==null&&aI.is(d,"object")&&"r" in d&&"g" in d&&"b" in d){bH=d.b;e=d.g;d=d.r}if(e==null&&aI.is(d,af)){var bL=aI.getRGB(d);d=bL.r;e=bL.g;bH=bL.b}if(d>1||e>1||bH>1){d/=255;e/=255;bH/=255}var S=m(d,e,bH),E=bi(d,e,bH),R,bK,b=(S+E)/2,bJ;if(E==S){bJ={h:0,s:0,l:b}}else{var bI=S-E;bK=b<0.5?bI/(S+E):bI/(2-S-E);if(d==S){R=(e-bH)/bI}else{if(e==S){R=2+(bH-d)/bI}else{R=4+(d-e)/bI}}R/=6;R<0&&R++;R>1&&R--;bJ={h:R,s:bK,l:b}}bJ.toString=M;return bJ};aI._path2string=function(){return this.join(",")[bs](ba,"$1")};function aA(E,d,b){function e(){var R=Array[bE].slice.call(arguments,0),bH=R[aW]("\u25ba"),S=e.cache=e.cache||{},bI=e.count=e.count||[];if(S[ag](bH)){return b?b(S[bH]):S[bH]}bI[s]>=1000&&delete S[bI.shift()];bI[k](bH);S[bH]=E[bB](d,R);return b?b(S[bH]):S[bH]}return e}aI.getRGB=aA(function(b){if(!b||!!((b=bC(b)).indexOf("-")+1)){return{r:-1,g:-1,b:-1,hex:"none",error:1}}if(b=="none"){return{r:-1,g:-1,b:-1,hex:"none"}}!(n[ag](b.toLowerCase().substring(0,2))||b.charAt()=="#")&&(b=a9(b));var R,d,e,bH,E,bJ,bI,S=b.match(G);if(S){if(S[2]){bH=T(S[2].substring(5),16);e=T(S[2].substring(3,5),16);d=T(S[2].substring(1,3),16)}if(S[3]){bH=T((bJ=S[3].charAt(3))+bJ,16);e=T((bJ=S[3].charAt(2))+bJ,16);d=T((bJ=S[3].charAt(1))+bJ,16)}if(S[4]){bI=S[4][I](bd);d=aj(bI[0]);bI[0].slice(-1)=="%"&&(d*=2.55);e=aj(bI[1]);bI[1].slice(-1)=="%"&&(e*=2.55);bH=aj(bI[2]);bI[2].slice(-1)=="%"&&(bH*=2.55);S[1].toLowerCase().slice(0,4)=="rgba"&&(E=aj(bI[3]));bI[3]&&bI[3].slice(-1)=="%"&&(E/=100)}if(S[5]){bI=S[5][I](bd);d=aj(bI[0]);bI[0].slice(-1)=="%"&&(d*=2.55);e=aj(bI[1]);bI[1].slice(-1)=="%"&&(e*=2.55);bH=aj(bI[2]);bI[2].slice(-1)=="%"&&(bH*=2.55);(bI[0].slice(-3)=="deg"||bI[0].slice(-1)=="\xb0")&&(d/=360);S[1].toLowerCase().slice(0,4)=="hsba"&&(E=aj(bI[3]));bI[3]&&bI[3].slice(-1)=="%"&&(E/=100);return aI.hsb2rgb(d,e,bH,E)}if(S[6]){bI=S[6][I](bd);d=aj(bI[0]);bI[0].slice(-1)=="%"&&(d*=2.55);e=aj(bI[1]);bI[1].slice(-1)=="%"&&(e*=2.55);bH=aj(bI[2]);bI[2].slice(-1)=="%"&&(bH*=2.55);(bI[0].slice(-3)=="deg"||bI[0].slice(-1)=="\xb0")&&(d/=360);S[1].toLowerCase().slice(0,4)=="hsla"&&(E=aj(bI[3]));bI[3]&&bI[3].slice(-1)=="%"&&(E/=100);return aI.hsl2rgb(d,e,bH,E)}S={r:d,g:e,b:bH};S.hex="#"+(16777216|bH|(e<<8)|(d<<16)).toString(16).slice(1);aI.is(E,"finite")&&(S.opacity=E);return S}return{r:-1,g:-1,b:-1,hex:"none",error:1}},aI);aI.getColor=function(d){var e=this.getColor.start=this.getColor.start||{h:0,s:1,b:d||0.75},b=this.hsb2rgb(e.h,e.s,e.b);e.h+=0.075;if(e.h>1){e.h=0;e.s-=0.2;e.s<=0&&(this.getColor.start={h:0,s:1,b:e.b})}return b.hex};aI.getColor.reset=function(){delete this.start};aI.parsePathString=aA(function(b){if(!b){return null}var e={a:7,c:6,h:1,l:2,m:2,q:4,s:4,t:2,v:1,z:0},d=[];if(aI.is(b,a7)&&aI.is(b[0],a7)){d=aS(b)}if(!d[s]){bC(b)[bs](aR,function(R,E,bI){var bH=[],S=bG.call(E);bI[bs](aG,function(bK,bJ){bJ&&bH[k](+bJ)});if(S=="m"&&bH[s]>2){d[k]([E][bw](bH.splice(0,2)));S="l";E=E=="m"?"l":"L"}while(bH[s]>=e[S]){d[k]([E][bw](bH.splice(0,e[S])));if(!e[S]){break}}})}d[a0]=aI._path2string;return d});aI.findDotsAtSegment=function(d,b,bV,bT,bH,R,bJ,bI,bP){var bN=1-bP,bM=bl(bN,3)*d+bl(bN,2)*3*bP*bV+bN*3*bP*bP*bH+bl(bP,3)*bJ,bK=bl(bN,3)*b+bl(bN,2)*3*bP*bT+bN*3*bP*bP*R+bl(bP,3)*bI,bR=d+2*bP*(bV-d)+bP*bP*(bH-2*bV+d),bQ=b+2*bP*(bT-b)+bP*bP*(R-2*bT+b),bU=bV+2*bP*(bH-bV)+bP*bP*(bJ-2*bH+bV),bS=bT+2*bP*(R-bT)+bP*bP*(bI-2*R+bT),bO=(1-bP)*d+bP*bV,bL=(1-bP)*b+bP*bT,E=(1-bP)*bH+bP*bJ,e=(1-bP)*R+bP*bI,S=(90-ao.atan((bR-bU)/(bQ-bS))*180/aM);(bR>bU||bQ1){cd=ao.sqrt(cd);bX=cd*bX;bV=cd*bV}var e=bX*bX,b6=bV*bV,b8=(bJ==E?-1:1)*ao.sqrt(aq((e*b6-e*b2*b2-b6*b3*b3)/(e*b2*b2+b6*b3*b3))),bS=b8*bX*b2/bV+(bO+bN)/2,bR=b8*-bV*b3/bX+(cj+ci)/2,bI=ao.asin(((cj-bR)/bV).toFixed(9)),bH=ao.asin(((ci-bR)/bV).toFixed(9));bI=bObH){bI=bI-aM*2}if(!E&&bH>bI){bH=bH-aM*2}}else{bI=bQ[0];bH=bQ[1];bS=bQ[2];bR=bQ[3]}var bM=bH-bI;if(aq(bM)>bU){var bT=bH,bW=bN,bK=ci;bH=bI+bU*(E&&bH>bI?1:-1);bN=bS+bX*ao.cos(bH);ci=bR+bV*ao.sin(bH);b1=Z(bN,ci,bX,bV,bP,0,E,bW,bK,[bH,bT,bS,bR])}bM=bH-bI;var S=ao.cos(bI),ch=ao.sin(bI),R=ao.cos(bH),cg=ao.sin(bH),b4=ao.tan(bM/4),b7=4/3*bX*b4,b5=4/3*bV*b4,ce=[bO,cj],cc=[bO+b7*ch,cj-b5*S],cb=[bN+b7*cg,ci-b5*R],b9=[bN,ci];cc[0]=2*ce[0]-cc[0];cc[1]=2*ce[1]-cc[1];if(bQ){return[cc,cb,b9][bw](b1)}else{b1=[cc,cb,b9][bw](b1)[aW]()[I](",");var bZ=[];for(var ca=0,b0=b1[s];ca"1e12"&&(bI=0.5);aq(bH)>"1e12"&&(bH=0.5);if(bI>0&&bI<1){e=ac(E,d,S,R,bR,bQ,bN,bK,bI);bO[k](e.x);bL[k](e.y)}if(bH>0&&bH<1){e=ac(E,d,S,R,bR,bQ,bN,bK,bH);bO[k](e.x);bL[k](e.y)}bP=(bQ-2*R+d)-(bK-2*bQ+R);bM=2*(R-d)-2*(bQ-R);bJ=d-R;bI=(-bM+ao.sqrt(bM*bM-4*bP*bJ))/2/bP;bH=(-bM-ao.sqrt(bM*bM-4*bP*bJ))/2/bP;aq(bI)>"1e12"&&(bI=0.5);aq(bH)>"1e12"&&(bH=0.5);if(bI>0&&bI<1){e=ac(E,d,S,R,bR,bQ,bN,bK,bI);bO[k](e.x);bL[k](e.y)}if(bH>0&&bH<1){e=ac(E,d,S,R,bR,bQ,bN,bK,bH);bO[k](e.x);bL[k](e.y)}return{min:{x:bi[bB](0,bO),y:bi[bB](0,bL)},max:{x:m[bB](0,bO),y:m[bB](0,bL)}}}),V=aA(function(bP,bK){var E=y(bP),bL=bK&&y(bK),bM={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},b={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},S=function(bR,bS){var bQ,bT;if(!bR){return["C",bS.x,bS.y,bS.x,bS.y,bS.x,bS.y]}!(bR[0] in {T:1,Q:1})&&(bS.qx=bS.qy=null);switch(bR[0]){case"M":bS.X=bR[1];bS.Y=bR[2];break;case"A":bR=["C"][bw](Z[bB](0,[bS.x,bS.y][bw](bR.slice(1))));break;case"S":bQ=bS.x+(bS.x-(bS.bx||bS.x));bT=bS.y+(bS.y-(bS.by||bS.y));bR=["C",bQ,bT][bw](bR.slice(1));break;case"T":bS.qx=bS.x+(bS.x-(bS.qx||bS.x));bS.qy=bS.y+(bS.y-(bS.qy||bS.y));bR=["C"][bw](bj(bS.x,bS.y,bS.qx,bS.qy,bR[1],bR[2]));break;case"Q":bS.qx=bR[1];bS.qy=bR[2];bR=["C"][bw](bj(bS.x,bS.y,bR[1],bR[2],bR[3],bR[4]));break;case"L":bR=["C"][bw](bD(bS.x,bS.y,bR[1],bR[2]));break;case"H":bR=["C"][bw](bD(bS.x,bS.y,bR[1],bS.y));break;case"V":bR=["C"][bw](bD(bS.x,bS.y,bS.x,bR[1]));break;case"Z":bR=["C"][bw](bD(bS.x,bS.y,bS.X,bS.Y));break}return bR},d=function(bQ,bR){if(bQ[bR][s]>7){bQ[bR].shift();var bS=bQ[bR];while(bS[s]){bQ.splice(bR++,0,["C"][bw](bS.splice(0,6)))}bQ.splice(bR,1);bN=m(E[s],bL&&bL[s]||0)}},e=function(bU,bT,bR,bQ,bS){if(bU&&bT&&bU[bS][0]=="M"&&bT[bS][0]!="M"){bT.splice(bS,0,["M",bQ.x,bQ.y]);bR.bx=0;bR.by=0;bR.x=bU[bS][1];bR.y=bU[bS][2];bN=m(E[s],bL&&bL[s]||0)}};for(var bI=0,bN=m(E[s],bL&&bL[s]||0);bI0.5)*2-1);bl(bI-0.5,2)+bl(S-0.5,2)>0.25&&(S=ao.sqrt(0.25-bl(bI-0.5,2))*bS+0.5)&&S!=0.5&&(S=S.toFixed(5)-0.00001*bS)}return aP});bO=bO[I](/\s*\-\s*/);if(bL=="linear"){var bH=bO.shift();bH=-aj(bH);if(isNaN(bH)){return null}var R=[0,0,ao.cos(bH*aM/180),ao.sin(bH*aM/180)],bN=1/(m(aq(R[2]),aq(R[3]))||1);R[2]*=bN;R[3]*=bN;if(R[2]<0){R[0]=-R[2];R[2]=0}if(R[3]<0){R[1]=-R[3];R[3]=0}}var bK=w(bO);if(!bK){return null}var d=E.getAttribute(a4);d=d.match(/^url\(#(.*)\)$/);d&&b.defs.removeChild(aa.getElementById(d[1]));var e=bh(bL+"Gradient");e.id=j();bh(e,bL=="radial"?{fx:bI,fy:S}:{x1:R[0],y1:R[1],x2:R[2],y2:R[3]});b.defs[bk](e);for(var bJ=0,bP=bK[s];bJ1?E.opacity/100:E.opacity});case"stroke":E=aI.getRGB(bQ);bT[D](bS,E.hex);bS=="stroke"&&E[ag]("opacity")&&bh(bT,{"stroke-opacity":E.opacity>1?E.opacity/100:E.opacity});break;case"gradient":(({circle:1,ellipse:1})[ag](bO.type)||bC(bQ).charAt()!="r")&&g(bT,bQ,bO.paper);break;case"opacity":if(bP.gradient&&!bP[ag]("stroke-opacity")){bh(bT,{"stroke-opacity":bQ>1?bQ/100:bQ})}case"fill-opacity":if(bP.gradient){var b=aa.getElementById(bT.getAttribute(a4)[bs](/^url\(#|\)$/g,aP));if(b){var bJ=b.getElementsByTagName("stop");bJ[bJ[s]-1][D]("stop-opacity",bQ)}break}default:bS=="font-size"&&(bQ=T(bQ,10)+"px");var bM=bS[bs](/(\-.)/g,function(bZ){return bo.call(bZ.substring(1))});bT.style[bM]=bQ;bT[D](bS,bQ);break}}}P(bO,bX);if(bK){bO.rotate(bK.join(aH))}else{aj(bL)&&bO.rotate(bL,true)}};var o=1.2,P=function(b,E){if(b.type!="text"||!(E[ag]("text")||E[ag]("font")||E[ag]("font-size")||E[ag]("x")||E[ag]("y"))){return}var bJ=b.attrs,d=b.node,bL=d.firstChild?T(aa.defaultView.getComputedStyle(d.firstChild,aP).getPropertyValue("font-size"),10):10;if(E[ag]("text")){bJ.text=E.text;while(d.firstChild){d.removeChild(d.firstChild)}var e=bC(E.text)[I]("\n");for(var R=0,bK=e[s];RbH.height)&&(bH.height=S.y+S.height-bH.y);(S.x+S.width-bH.x>bH.width)&&(bH.width=S.x+S.width-bH.x)}}d&&this.hide();return bH};aU[bE].attr=function(b,bJ){if(this.removed){return this}if(b==null){var bI={};for(var R in this.attrs){if(this.attrs[ag](R)){bI[R]=this.attrs[R]}}this._.rt.deg&&(bI.rotation=this.rotate());(this._.sx!=1||this._.sy!=1)&&(bI.scale=this.scale());bI.gradient&&bI.fill=="none"&&(bI.fill=bI.gradient)&&delete bI.gradient;return bI}if(bJ==null&&aI.is(b,af)){if(b=="translation"){return A.call(this)}if(b=="rotation"){return this.rotate()}if(b=="scale"){return this.scale()}if(b==a4&&this.attrs.fill=="none"&&this.attrs.gradient){return this.attrs.gradient}return this.attrs[b]}if(bJ==null&&aI.is(b,a7)){var bL={};for(var E=0,S=b.length;E"));bW.W=bS.w=bW.paper.span.offsetWidth;bW.H=bS.h=bW.paper.span.offsetHeight;bW.X=bS.x;bW.Y=bS.y+ad(bW.H/2);switch(bS["text-anchor"]){case"start":bW.node.style["v-text-align"]="left";bW.bbx=ad(bW.W/2);break;case"end":bW.node.style["v-text-align"]="right";bW.bbx=-ad(bW.W/2);break;default:bW.node.style["v-text-align"]="center";break}}};g=function(b,bH){b.attrs=b.attrs||{};var bI=b.attrs,bK,R="linear",S=".5 .5";b.attrs.gradient=bH;bH=bC(bH)[bs](aO,function(bN,bO,bM){R="radial";if(bO&&bM){bO=aj(bO);bM=aj(bM);bl(bO-0.5,2)+bl(bM-0.5,2)>0.25&&(bM=ao.sqrt(0.25-bl(bO-0.5,2))*((bM>0.5)*2-1)+0.5);S=bO+aH+bM}return aP});bH=bH[I](/\s*\-\s*/);if(R=="linear"){var d=bH.shift();d=-aj(d);if(isNaN(d)){return null}}var E=w(bH);if(!E){return null}b=b.shape||b.node;bK=b.getElementsByTagName(a4)[0]||ay(a4);!bK.parentNode&&b.appendChild(bK);if(E[s]){bK.on=true;bK.method="none";bK.color=E[0].color;bK.color2=E[E[s]-1].color;var bL=[];for(var e=0,bJ=E[s];e')}}catch(aw){ay=function(b){return aa.createElement("<"+b+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}F=function(){var e=aJ[bB](0,arguments),b=e.container,bJ=e.height,bK,d=e.width,bI=e.x,bH=e.y;if(!b){throw new Error("VML container not found.")}var R=new bz,S=R.canvas=aa.createElement("div"),E=S.style;bI=bI||0;bH=bH||0;d=d||512;bJ=bJ||342;d==+d&&(d+="px");bJ==+bJ&&(bJ+="px");R.width=1000;R.height=1000;R.coordsize=u*1000+aH+u*1000;R.coordorigin="0 0";R.span=aa.createElement("span");R.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";S[bk](R.span);E.cssText=aI.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",d,bJ);if(b==1){aa.body[bk](S);E.left=bI+"px";E.top=bH+"px";E.position="absolute"}else{if(b.firstChild){b.insertBefore(S,b.firstChild)}else{b[bk](S)}}be.call(R,R,aI.fn);return R};aZ.clear=function(){this.canvas.innerHTML=aP;this.span=aa.createElement("span");this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";this.canvas[bk](this.span);this.bottom=this.top=null};aZ.remove=function(){this.canvas.parentNode.removeChild(this.canvas);for(var b in this){this[b]=z(b)}return true}}var U=navigator.userAgent.match(/Version\/(.*?)\s/);if((navigator.vendor=="Apple Computer, Inc.")&&(U&&U[1]<4||navigator.platform.slice(0,2)=="iP")){aZ.safari=function(){var b=this.rect(-99,-99,this.width+99,this.height+99).attr({stroke:"none"});aQ.setTimeout(function(){b.remove()})}}else{aZ.safari=function(){}}var O=function(){this.returnValue=false},bv=function(){return this.originalEvent.preventDefault()},a3=function(){this.cancelBubble=true},aD=function(){return this.originalEvent.stopPropagation()},av=(function(){if(aa.addEventListener){return function(S,E,e,d){var b=W&&bq[E]?bq[E]:E;var R=function(bK){if(W&&bq[ag](E)){for(var bI=0,bJ=bK.targetTouches&&bK.targetTouches.length;bI1&&(b=Array[bE].splice.call(arguments,0,arguments[s]));return new ah(b)};aZ.setSize=bA;aZ.top=aZ.bottom=null;aZ.raphael=aI;function C(){return this.x+aH+this.y}bc.resetScale=function(){if(this.removed){return this}this._.sx=1;this._.sy=1;this.attrs.scale="1 1"};bc.scale=function(bZ,bY,bQ,bO){if(this.removed){return this}if(bZ==null&&bY==null){return{x:this._.sx,y:this._.sy,toString:C}}bY=bY||bZ;!+bY&&(bY=bZ);var d,b,b7,b6,ca=this.attrs;if(bZ!=0){var cb=this.getBBox(),E=cb.x+cb.width/2,e=cb.y+cb.height/2,b4=aq(bZ/this._.sx),b3=aq(bY/this._.sy);bQ=(+bQ||bQ==0)?bQ:E;bO=(+bO||bO==0)?bO:e;var bJ=this._.sx>0,bI=this._.sy>0,bP=~~(bZ/aq(bZ)),bN=~~(bY/aq(bY)),S=b4*bP,R=b3*bN,b0=this.node.style,bX=bQ+aq(E-bQ)*S*(E>bQ==bJ?1:-1),bW=bO+aq(e-bO)*R*(e>bO==bI?1:-1),bT=(bZ*bP>bY*bN?b3:b4);switch(this.type){case"rect":case"image":var bL=ca.width*b4,bU=ca.height*b3;this.attr({height:bU,r:ca.r*bT,width:bL,x:bX-bL/2,y:bW-bU/2});break;case"circle":case"ellipse":this.attr({rx:ca.rx*b4,ry:ca.ry*b3,r:ca.r*bT,cx:bX,cy:bW});break;case"text":this.attr({x:bX,y:bW});break;case"path":var b2=au(ca.path),bK=true,bS=bJ?S:b4,bR=bI?R:b3;for(var b9=0,bV=b2[s];b9bK){e=b.data[bK*bL]}else{e=aI.findDotsAtSegment(R,d,bJ,bI,bR,bQ,bP,bN,bK/bL);b.data[bK]=e}bK&&(bM+=bl(bl(bH.x-e.x,2)+bl(bH.y-e.y,2),0.5));if(S!=null&&bM>=S){return e}bH=e}if(S==null){return bM}},a1=function(b,d){return function(bP,R,S){bP=V(bP);var bL,bK,e,bH,E="",bO={},bM,bJ=0;for(var bI=0,bN=bP.length;bIR){if(d&&!bO.start){bM=l(bL,bK,e[1],e[2],e[3],e[4],e[5],e[6],R-bJ);E+=["C",bM.start.x,bM.start.y,bM.m.x,bM.m.y,bM.x,bM.y];if(S){return E}bO.start=E;E=["M",bM.x,bM.y+"C",bM.n.x,bM.n.y,bM.end.x,bM.end.y,e[5],e[6]][aW]();bJ+=bH;bL=+e[5];bK=+e[6];continue}if(!b&&!d){bM=l(bL,bK,e[1],e[2],e[3],e[4],e[5],e[6],R-bJ);return{x:bM.x,y:bM.y,alpha:bM.alpha}}}bJ+=bH;bL=+e[5];bK=+e[6]}E+=e}bO.end=E;bM=b?bJ:d?bO:aI.findDotsAtSegment(bL,bK,e[1],e[2],e[3],e[4],e[5],e[6],1);bM.alpha&&(bM={x:bM.x,y:bM.y,alpha:bM.alpha});return bM}};var aK=a1(1),L=a1(),Y=a1(0,1);bc.getTotalLength=function(){if(this.type!="path"){return}if(this.node.getTotalLength){return this.node.getTotalLength()}return aK(this.attrs.path)};bc.getPointAtLength=function(b){if(this.type!="path"){return}return L(this.attrs.path,b)};bc.getSubpath=function(e,d){if(this.type!="path"){return}if(aq(this.getTotalLength()-d)<"1e-6"){return Y(this.attrs.path,e).end}var b=Y(this.attrs.path,d,1);return e?Y(b,e).end:b};aI.easing_formulas={linear:function(b){return b},"<":function(b){return bl(b,3)},">":function(b){return bl(b-1,3)+1},"<>":function(b){b=b*2;if(b<1){return bl(b,3)/2}b-=2;return(bl(b,3)+2)/2},backIn:function(d){var b=1.70158;return d*d*((b+1)*d-b)},backOut:function(d){d=d-1;var b=1.70158;return d*d*((b+1)*d+b)+1},elastic:function(e){if(e==0||e==1){return e}var d=0.3,b=d/4;return bl(2,-10*e)*ao.sin((e-b)*(2*aM)/d)+1},bounce:function(E){var d=7.5625,e=2.75,b;if(E<(1/e)){b=d*E*E}else{if(E<(2/e)){E-=(1.5/e);b=d*E*E+0.75}else{if(E<(2.5/e)){E-=(2.25/e);b=d*E*E+0.9375}else{E-=(2.625/e);b=d*E*E+0.984375}}}return b}};var X=[],bu=function(){var bI=+new Date;for(var bT=0;bTbV){return bV}while(bWbR){bW=bT}else{bV=bT}bT=(bV-bW)/2+bW}return bT}return e(bO,1/(200*bI))}bc.onAnimation=function(b){this._run=b||0;return this};bc.animate=function(b0,bQ,bP,R){var d=this;d.timeouts=d.timeouts||[];if(aI.is(bP,"function")||!bP){R=bP||null}if(d.removed){R&&R.call(d);return d}var bU={},e={},S=false,bL={};for(var bR in b0){if(b0[ag](bR)){if(am[ag](bR)||d.paper.customAttributes[ag](bR)){S=true;bU[bR]=d.attr(bR);(bU[bR]==null)&&(bU[bR]=q[bR]);e[bR]=b0[bR];switch(am[bR]){case"along":var bY=aK(b0[bR]);var bS=L(b0[bR],bY*!!b0.back);var bH=d.getBBox();bL[bR]=bY/bQ;bL.tx=bH.x;bL.ty=bH.y;bL.sx=bS.x;bL.sy=bS.y;e.rot=b0.rot;e.back=b0.back;e.len=bY;b0.rot&&(bL.r=aj(d.rotate())||0);break;case aF:bL[bR]=(e[bR]-bU[bR])/bQ;break;case"colour":bU[bR]=aI.getRGB(bU[bR]);var bT=aI.getRGB(e[bR]);bL[bR]={r:(bT.r-bU[bR].r)/bQ,g:(bT.g-bU[bR].g)/bQ,b:(bT.b-bU[bR].b)/bQ};break;case"path":var bI=V(bU[bR],e[bR]);bU[bR]=bI[0];var bN=bI[1];bL[bR]=[];for(var bX=0,bK=bU[bR][s];bXl){l=m}}for(m in q){r=a.convert(o,l,0,1,m);p[r]=q[m]}return function(v,u){var s=null,w=null;if(v-c<0){for(var t in p){return p[t]}}else{if(v+c>1){for(var t in p){s=p[t]}return s}else{for(var t in p){if(t==v){return v}if(v>t){s=t;continue}else{w=a.convert(s,t,0,1,v);return s}}return s}}}};a.blockColorScheme=function(m){var l=a.COLOR_BLOCKS[m];return function(n){var o=parseInt(Math.floor(a.scale(0,1,0,l.length+1,n)));return l[Math.max(0,Math.min(o,l.length))]}};a.applySvgStyle=function(n,l,m){var p=null,o=a.extend(l,m||{});if(o.emboss){p=k.rgb2hsb(o.fill);o.fill="90-hsb("+p.h+","+p.s+","+Math.max(0,p.b-0.2)+")-"+o.fill}n.attr(o)};a.GMAP_STYLES={};a.GMAP_STYLES.Neutral=[{featureType:"administrative",elementType:"all",stylers:[{saturation:-100}]},{featureType:"landscape",elementType:"all",stylers:[{saturation:-100}]},{featureType:"poi",elementType:"all",stylers:[{saturation:-100}]},{featureType:"road",elementType:"all",stylers:[{saturation:-100}]},{featureType:"transit",elementType:"all",stylers:[{saturation:-100}]},{featureType:"water",elementType:"all",stylers:[{saturation:-100}]}];a.GMAP_STYLES.Subtle=[{featureType:"all",elementType:"all",stylers:[{saturation:-47}]},{featureType:"poi",elementType:"all",stylers:[{visibility:"off"}]},{featureType:"water",elementType:"all",stylers:[{saturation:-68},{visibility:"on"},{lightness:50}]},{featureType:"road.local",elementType:"all",stylers:[{visibility:"simplified"}]},{featureType:"road.arterial",elementType:"all",stylers:[{visibility:"simplified"}]},{featureType:"road.highway",elementType:"all",stylers:[{visibility:"simplified"}]},{featureType:"administrative.province",elementType:"all",stylers:[{visibility:"simplified"}]},{featureType:"transit",elementType:"all",stylers:[]}];a.setGMapStyle=function(o,m){var l={name:m},n=new google.maps.StyledMapType(a.GMAP_STYLES[m],l);o.mapTypes.set(m,n);o.setMapTypeId(m)};a.Controller=a.Class.extend({init:function(o,l,n,m){this.options=a.extend(e.CONTROLLER,m);this.map=o;this.datasource=l;this.layer=n;this.datasource.addController(this);this.layer.setMap(this.map).setController(this)},refresh:function(){var l=this.layer;a.each(this.datasource.getDataItems(),function(m){l.addDataItem(m)});return this}});a.Realtime=a.Controller.extend({init:function(o,l,n,m){this._super(o,l,n,a.extend(e.REALTIME,m));setInterval(this.deleteExpired,5000)},updateOne:function(l){var m=this,n=this.convertTimeIntoFutureMs(l.timestamp);window.setTimeout(function(){m.layer.addDataItem(l)},n);return this},updateMany:function(l){var m=this;a.each(l,function(n){m.updateOne(n)});return this},convertTimeIntoFutureMs:function(m){var l=new Date().getTime();return Math.max(0,(m+this.options.expected_delay)-l)},deleteExpired:function(){}});a.Datasource=a.Class.extend({init:function(l){this.data=l&&this.formatData(l)||[];this.controllers=[];this.dirty=true;this.stats={};this.analyze()},addController:function(l){this.controllers.push(l)},load:function(){a.each(this.controllers,function(l){l.refresh()})},push:function(l){this.data.push(this.formatDataItem(l));a.each(this.controllers,function(m){m.updateOne(l)});return this},pushMany:function(m){var l=this.formatData(m);i=0;ii=l.length;for(;in)?m.val:n});this.stats.min=this.reduce(function(m,n){return(n===null||m.val