diff --git a/README.md b/README.md
index b68f837..d959df8 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ Add the dependency:
pl.itrack
gwt-leaflet-d3
- 0.3.0
+ 0.3.1
```
diff --git a/docs/dependencies/build-dependencies.sh b/docs/dependencies/build-dependencies.sh
new file mode 100755
index 0000000..723c99b
--- /dev/null
+++ b/docs/dependencies/build-dependencies.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+echo "-> Build dependencies"
+npm install
+
+echo "-> Copy built dependencies"
+rm -rf ./dist
+mkdir ./dist
+cp ./node_modules/@asymmetrik/leaflet-d3/dist/*.js ./dist
+rm -rf ./node_modules
+rm package-lock.json
+#git add ./dist
+
+echo "Success!"
diff --git a/docs/dependencies/dist/leaflet-d3.js b/docs/dependencies/dist/leaflet-d3.js
new file mode 100644
index 0000000..c131d4f
--- /dev/null
+++ b/docs/dependencies/dist/leaflet-d3.js
@@ -0,0 +1,980 @@
+/*! @asymmetrik/leaflet-d3 - 4.3.2 - Copyright (c) 2007-2018 Asymmetrik Ltd, a Maryland Corporation + */
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('d3-hexbin'), require('d3'), require('leaflet')) :
+ typeof define === 'function' && define.amd ? define(['d3-hexbin', 'd3', 'leaflet'], factory) :
+ (factory(global.d3.hexbin,global.d3));
+}(this, (function (d3Hexbin,d3) { 'use strict';
+
+ /**
+ * This is a convoluted way of getting ahold of the hexbin function.
+ * - When imported globally, d3 is exposed in the global namespace as 'd3'
+ * - When imported using a module system, it's a named import (and can't collide with d3)
+ * - When someone isn't importing d3-hexbin, the named import will be undefined
+ *
+ * As a result, we have to figure out how it's being imported and get the function reference
+ * (which is why we have this convoluted nested ternary statement
+ */
+ var d3_hexbin = (null != d3.hexbin)? d3.hexbin : (null != d3Hexbin)? d3Hexbin.hexbin : null;
+
+ /**
+ * L is defined by the Leaflet library, see git://github.com/Leaflet/Leaflet.git for documentation
+ * We extend L.SVG to take advantage of built-in zoom animations.
+ */
+ L.HexbinLayer = L.SVG.extend({
+ includes: L.Evented || L.Mixin.Events,
+
+ /**
+ * Default options
+ */
+ options : {
+ radius : 12,
+ opacity: 0.6,
+ duration: 200,
+
+ colorScaleExtent: [ 1, undefined ],
+ radiusScaleExtent: [ 1, undefined ],
+ colorDomain: null,
+ radiusDomain: null,
+ colorRange: [ '#f7fbff', '#08306b' ],
+ radiusRange: [ 4, 12 ],
+
+ pointerEvents: 'all'
+ },
+
+
+ /**
+ * Standard Leaflet initialize function, accepting an options argument provided by the
+ * user when they create the layer
+ * @param options Options object where the options override the defaults
+ */
+ initialize : function(options) {
+ L.setOptions(this, options);
+
+ // Set up the various overrideable functions
+ this._fn = {
+ lng: function(d) { return d[0]; },
+ lat: function(d) { return d[1]; },
+ colorValue: function(d) { return d.length; },
+ radiusValue: function(d) { return Number.MAX_VALUE; },
+
+ fill: function(d) {
+ var val = this._fn.colorValue(d);
+ return (null != val) ? this._scale.color(val) : 'none';
+ }
+ };
+
+ // Set up the customizable scale
+ this._scale = {
+ color: d3.scaleLinear(),
+ radius: d3.scaleLinear()
+ };
+
+ // Set up the Dispatcher for managing events and callbacks
+ this._dispatch = d3.dispatch('mouseover', 'mouseout', 'click');
+
+ // Set up the default hover handler
+ this._hoverHandler = L.HexbinHoverHandler.none();
+
+ // Create the hex layout
+ this._hexLayout = d3_hexbin()
+ .radius(this.options.radius)
+ .x(function(d) { return d.point[0]; })
+ .y(function(d) { return d.point[1]; });
+
+ // Initialize the data array to be empty
+ this._data = [];
+
+ this._scale.color
+ .range(this.options.colorRange)
+ .clamp(true);
+
+ this._scale.radius
+ .range(this.options.radiusRange)
+ .clamp(true);
+
+ },
+
+ /**
+ * Callback made by Leaflet when the layer is added to the map
+ * @param map Reference to the map to which this layer has been added
+ */
+ onAdd : function(map) {
+
+ L.SVG.prototype.onAdd.call(this);
+
+ // Store a reference to the map for later use
+ this._map = map;
+
+ // Redraw on moveend
+ map.on({ 'moveend': this.redraw }, this);
+
+ // Initial draw
+ this.redraw();
+
+ },
+
+ /**
+ * Callback made by Leaflet when the layer is removed from the map
+ * @param map Reference to the map from which this layer is being removed
+ */
+ onRemove : function(map) {
+
+ L.SVG.prototype.onRemove.call(this);
+
+ // Destroy the svg container
+ this._destroyContainer();
+
+ // Remove events
+ map.off({ 'moveend': this.redraw }, this);
+
+ this._map = null;
+
+ // Explicitly will leave the data array alone in case the layer will be shown again
+ //this._data = [];
+
+ },
+
+ /**
+ * Create the SVG container for the hexbins
+ * @private
+ */
+ _initContainer : function() {
+
+ L.SVG.prototype._initContainer.call(this);
+ this._d3Container = d3.select(this._container).select('g');
+ },
+
+ /**
+ * Clean up the svg container
+ * @private
+ */
+ _destroyContainer: function() {
+
+ // Don't do anything
+
+ },
+
+ /**
+ * (Re)draws the hexbins data on the container
+ * @private
+ */
+ redraw : function() {
+ var that = this;
+
+ if (!that._map) {
+ return;
+ }
+
+ // Generate the mapped version of the data
+ var data = that._data.map(function(d) {
+ var lng = that._fn.lng(d);
+ var lat = that._fn.lat(d);
+
+ var point = that._project([ lng, lat ]);
+ return { o: d, point: point };
+ });
+
+ // Select the hex group for the current zoom level. This has
+ // the effect of recreating the group if the zoom level has changed
+ var join = this._d3Container.selectAll('g.hexbin')
+ .data([ this._map.getZoom() ], function(d) { return d; });
+
+ // enter
+ var enter = join.enter().append('g')
+ .attr('class', function(d) { return 'hexbin zoom-' + d; });
+
+ // enter + update
+ var enterUpdate = enter.merge(join);
+
+ // exit
+ join.exit().remove();
+
+ // add the hexagons to the select
+ this._createHexagons(enterUpdate, data);
+
+ },
+
+ _createHexagons : function(g, data) {
+ var that = this;
+
+ // Create the bins using the hexbin layout
+
+ // Generate the map bounds (to be used to filter the hexes to what is visible)
+ var bounds = that._map.getBounds();
+ var size = that._map.getSize();
+ bounds = bounds.pad(that.options.radius * 2 / Math.max(size.x, size.y));
+
+ var bins = that._hexLayout(data);
+
+ // Derive the extents of the data values for each dimension
+ var colorExtent = that._getExtent(bins, that._fn.colorValue, that.options.colorScaleExtent);
+ var radiusExtent = that._getExtent(bins, that._fn.radiusValue, that.options.radiusScaleExtent);
+
+ // Match the domain cardinality to that of the color range, to allow for a polylinear scale
+ var colorDomain = this.options.colorDomain;
+ if (null == colorDomain) {
+ colorDomain = that._linearlySpace(colorExtent[0], colorExtent[1], that._scale.color.range().length);
+ }
+ var radiusDomain = this.options.radiusDomain || radiusExtent;
+
+ // Set the scale domains
+ that._scale.color.domain(colorDomain);
+ that._scale.radius.domain(radiusDomain);
+
+
+ /*
+ * Join
+ * Join the Hexagons to the data
+ * Use a deterministic id for tracking bins based on position
+ */
+ bins = bins.filter(function(d) {
+ return bounds.contains(that._map.layerPointToLatLng(L.point(d.x, d.y)));
+ });
+ var join = g.selectAll('g.hexbin-container')
+ .data(bins, function(d) {
+ return d.x + ':' + d.y;
+ });
+
+
+ /*
+ * Update
+ * Set the fill and opacity on a transition
+ * opacity is re-applied in case the enter transition was cancelled
+ * the path is applied as well to resize the bins
+ */
+ join.select('path.hexbin-hexagon').transition().duration(that.options.duration)
+ .attr('fill', that._fn.fill.bind(that))
+ .attr('fill-opacity', that.options.opacity)
+ .attr('stroke-opacity', that.options.opacity)
+ .attr('d', function(d) {
+ return that._hexLayout.hexagon(that._scale.radius(that._fn.radiusValue.call(that, d)));
+ });
+
+
+ /*
+ * Enter
+ * Establish the path, size, fill, and the initial opacity
+ * Transition to the final opacity and size
+ */
+ var enter = join.enter().append('g').attr('class', 'hexbin-container');
+
+ enter.append('path').attr('class', 'hexbin-hexagon')
+ .attr('transform', function(d) {
+ return 'translate(' + d.x + ',' + d.y + ')';
+ })
+ .attr('d', function(d) {
+ return that._hexLayout.hexagon(that._scale.radius.range()[0]);
+ })
+ .attr('fill', that._fn.fill.bind(that))
+ .attr('fill-opacity', 0.01)
+ .attr('stroke-opacity', 0.01)
+ .transition().duration(that.options.duration)
+ .attr('fill-opacity', that.options.opacity)
+ .attr('stroke-opacity', that.options.opacity)
+ .attr('d', function(d) {
+ return that._hexLayout.hexagon(that._scale.radius(that._fn.radiusValue.call(that, d)));
+ });
+
+ // Grid
+ var gridEnter = enter.append('path').attr('class', 'hexbin-grid')
+ .attr('transform', function(d) {
+ return 'translate(' + d.x + ',' + d.y + ')';
+ })
+ .attr('d', function(d) {
+ return that._hexLayout.hexagon(that.options.radius);
+ })
+ .attr('fill', 'none')
+ .attr('stroke', 'none')
+ .style('pointer-events', that.options.pointerEvents);
+
+ // Grid enter-update
+ gridEnter.merge(join.select('path.hexbin-grid'))
+ .on('mouseover', function(d, i) {
+ that._hoverHandler.mouseover.call(this, that, d, i);
+ that._dispatch.call('mouseover', this, d, i);
+ })
+ .on('mouseout', function(d, i) {
+ that._dispatch.call('mouseout', this, d, i);
+ that._hoverHandler.mouseout.call(this, that, d, i);
+ })
+ .on('click', function(d, i) {
+ that._dispatch.call('click', this, d, i);
+ });
+
+
+ // Exit
+ var exit = join.exit();
+
+ exit.select('path.hexbin-hexagon')
+ .transition().duration(that.options.duration)
+ .attr('fill-opacity', 0)
+ .attr('stroke-opacity', 0)
+ .attr('d', function(d) {
+ return that._hexLayout.hexagon(0);
+ });
+
+ exit.transition().duration(that.options.duration)
+ .remove();
+
+ },
+
+ _getExtent: function(bins, valueFn, scaleExtent) {
+
+ // Determine the extent of the values
+ var extent = d3.extent(bins, valueFn.bind(this));
+
+ // If either's null, initialize them to 0
+ if (null == extent[0]) extent[0] = 0;
+ if (null == extent[1]) extent[1] = 0;
+
+ // Now apply the optional clipping of the extent
+ if (null != scaleExtent[0]) extent[0] = scaleExtent[0];
+ if (null != scaleExtent[1]) extent[1] = scaleExtent[1];
+
+ return extent;
+
+ },
+
+ _project : function(coord) {
+ var point = this._map.latLngToLayerPoint([ coord[1], coord[0] ]);
+ return [ point.x, point.y ];
+ },
+
+ _getBounds: function(data) {
+ if(null == data || data.length < 1) {
+ return { min: [ 0, 0 ], max: [ 0, 0 ]};
+ }
+
+ // bounds is [[min long, min lat], [max long, max lat]]
+ var bounds = [ [ 999, 999 ], [ -999, -999 ] ];
+
+ data.forEach(function(element) {
+ var x = element.point[0];
+ var y = element.point[1];
+
+ bounds[0][0] = Math.min(bounds[0][0], x);
+ bounds[0][1] = Math.min(bounds[0][1], y);
+ bounds[1][0] = Math.max(bounds[1][0], x);
+ bounds[1][1] = Math.max(bounds[1][1], y);
+ });
+
+ return { min: bounds[0], max: bounds[1] };
+ },
+
+ _linearlySpace: function(from, to, length) {
+ var arr = new Array(length);
+ var step = (to - from) / Math.max(length - 1, 1);
+
+ for (var i = 0; i < length; ++i) {
+ arr[i] = from + (i * step);
+ }
+
+ return arr;
+ },
+
+
+ // ------------------------------------
+ // Public API
+ // ------------------------------------
+
+ radius: function(v) {
+ if (!arguments.length) { return this.options.radius; }
+
+ this.options.radius = v;
+ this._hexLayout.radius(v);
+
+ return this;
+ },
+
+ opacity: function(v) {
+ if (!arguments.length) { return this.options.opacity; }
+ this.options.opacity = v;
+
+ return this;
+ },
+
+ duration: function(v) {
+ if (!arguments.length) { return this.options.duration; }
+ this.options.duration = v;
+
+ return this;
+ },
+
+ colorScaleExtent: function(v) {
+ if (!arguments.length) { return this.options.colorScaleExtent; }
+ this.options.colorScaleExtent = v;
+
+ return this;
+ },
+
+ radiusScaleExtent: function(v) {
+ if (!arguments.length) { return this.options.radiusScaleExtent; }
+ this.options.radiusScaleExtent = v;
+
+ return this;
+ },
+
+ colorRange: function(v) {
+ if (!arguments.length) { return this.options.colorRange; }
+ this.options.colorRange = v;
+ this._scale.color.range(v);
+
+ return this;
+ },
+
+ radiusRange: function(v) {
+ if (!arguments.length) { return this.options.radiusRange; }
+ this.options.radiusRange = v;
+ this._scale.radius.range(v);
+
+ return this;
+ },
+
+ colorScale: function(v) {
+ if (!arguments.length) { return this._scale.color; }
+ this._scale.color = v;
+
+ return this;
+ },
+
+ radiusScale: function(v) {
+ if (!arguments.length) { return this._scale.radius; }
+ this._scale.radius = v;
+
+ return this;
+ },
+
+ lng: function(v) {
+ if (!arguments.length) { return this._fn.lng; }
+ this._fn.lng = v;
+
+ return this;
+ },
+
+ lat: function(v) {
+ if (!arguments.length) { return this._fn.lat; }
+ this._fn.lat = v;
+
+ return this;
+ },
+
+ colorValue: function(v) {
+ if (!arguments.length) { return this._fn.colorValue; }
+ this._fn.colorValue = v;
+
+ return this;
+ },
+
+ radiusValue: function(v) {
+ if (!arguments.length) { return this._fn.radiusValue; }
+ this._fn.radiusValue = v;
+
+ return this;
+ },
+
+ fill: function(v) {
+ if (!arguments.length) { return this._fn.fill; }
+ this._fn.fill = v;
+
+ return this;
+ },
+
+ data: function(v) {
+ if (!arguments.length) { return this._data; }
+ this._data = (null != v) ? v : [];
+
+ this.redraw();
+
+ return this;
+ },
+
+ /*
+ * Getter for the event dispatcher
+ */
+ dispatch: function() {
+ return this._dispatch;
+ },
+
+ hoverHandler: function(v) {
+ if (!arguments.length) { return this._hoverHandler; }
+ this._hoverHandler = (null != v) ? v : L.HexbinHoverHandler.none();
+
+ this.redraw();
+
+ return this;
+ },
+
+ /*
+ * Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
+ */
+ getLatLngs: function () {
+ var that = this;
+
+ // Map the data into an array of latLngs using the configured lat/lng accessors
+ return this._data.map(function(d) {
+ return L.latLng(that.options.lat(d), that.options.lng(d));
+ });
+ },
+
+ /*
+ * Get path geometry as GeoJSON
+ */
+ toGeoJSON: function () {
+ return L.GeoJSON.getFeature(this, {
+ type: 'LineString',
+ coordinates: L.GeoJSON.latLngsToCoords(this.getLatLngs(), 0)
+ });
+ }
+
+ });
+
+ // Hover Handlers modify the hexagon and can be combined
+ L.HexbinHoverHandler = {
+
+ tooltip: function(options) {
+
+ // merge options with defaults
+ options = options || {};
+ if (null == options.tooltipContent) { options.tooltipContent = function(d) { return 'Count: ' + d.length; }; }
+
+ // Generate the tooltip
+ var tooltip = d3.select('body').append('div')
+ .attr('class', 'hexbin-tooltip')
+ .style('z-index', 9999)
+ .style('pointer-events', 'none')
+ .style('visibility', 'hidden')
+ .style('position', 'fixed');
+
+ tooltip.append('div').attr('class', 'tooltip-content');
+
+ // return the handler instance
+ return {
+ mouseover: function (hexLayer, data) {
+ var event = d3.event;
+ var gCoords = d3.mouse(this);
+
+ tooltip
+ .style('visibility', 'visible')
+ .html(options.tooltipContent(data, hexLayer));
+
+ var div = null;
+ if (null != tooltip._groups && tooltip._groups.length > 0 && tooltip._groups[0].length > 0) {
+ div = tooltip._groups[0][0];
+ }
+ var h = div.clientHeight, w = div.clientWidth;
+
+ tooltip
+ .style('top', '' + event.clientY - gCoords[1] - h - 16 + 'px')
+ .style('left', '' + event.clientX - gCoords[0] - w/2 + 'px');
+
+ },
+ mouseout: function (hexLayer, data) {
+ tooltip
+ .style('visibility', 'hidden')
+ .html();
+ }
+ };
+
+ },
+
+ resizeFill: function() {
+
+ // return the handler instance
+ return {
+ mouseover: function (hexLayer, data) {
+ var o = d3.select(this.parentNode);
+ o.select('path.hexbin-hexagon')
+ .attr('d', function (d) {
+ return hexLayer._hexLayout.hexagon(hexLayer.options.radius);
+ });
+ },
+ mouseout: function (hexLayer, data) {
+ var o = d3.select(this.parentNode);
+ o.select('path.hexbin-hexagon')
+ .attr('d', function (d) {
+ return hexLayer._hexLayout.hexagon(hexLayer._scale.radius(hexLayer._fn.radiusValue.call(hexLayer, d)));
+ });
+ }
+ };
+
+ },
+
+ resizeScale: function(options) {
+
+ // merge options with defaults
+ options = options || {};
+ if (null == options.radiusScale) options.radiusScale = 0.5;
+
+ // return the handler instance
+ return {
+ mouseover: function (hexLayer, data) {
+ var o = d3.select(this.parentNode);
+ o.select('path.hexbin-hexagon')
+ .attr('d', function (d) {
+ return hexLayer._hexLayout.hexagon(hexLayer._scale.radius.range()[1] * (1 + options.radiusScale));
+ });
+ },
+ mouseout: function (hexLayer, data) {
+ var o = d3.select(this.parentNode);
+ o.select('path.hexbin-hexagon')
+ .attr('d', function (d) {
+ return hexLayer._hexLayout.hexagon(hexLayer._scale.radius(hexLayer._fn.radiusValue.call(hexLayer, d)));
+ });
+ }
+ };
+
+ },
+
+ compound: function(options) {
+
+ options = options || {};
+ if (null == options.handlers) options.handlers = [ L.HexbinHoverHandler.none() ];
+
+ return {
+ mouseover: function (hexLayer, data) {
+ var that = this;
+ options.handlers.forEach(function(h) { h.mouseover.call(that, hexLayer, data); });
+ },
+ mouseout: function (hexLayer, data) {
+ var that = this;
+ options.handlers.forEach(function(h) { h.mouseout.call(that, hexLayer, data); });
+ }
+ };
+
+ },
+
+ none: function() {
+ return {
+ mouseover: function () {},
+ mouseout: function () {}
+ };
+ }
+ };
+
+ L.hexbinLayer = function(options) {
+ return new L.HexbinLayer(options);
+ };
+
+ /**
+ * L is defined by the Leaflet library, see git://github.com/Leaflet/Leaflet.git for documentation
+ * We extend L.SVG to take advantage of built-in zoom animations.
+ */
+ L.PingLayer = L.SVG.extend({
+ includes: L.Evented || L.Mixin.Events,
+
+ /*
+ * Default options
+ */
+ options : {
+ duration: 800,
+ fps: 32,
+ opacityRange: [ 1, 0 ],
+ radiusRange: [ 3, 15 ]
+ },
+
+
+ // Initialization of the plugin
+ initialize : function(options) {
+ L.setOptions(this, options);
+
+ this._fn = {
+ lng: function(d) { return d[0]; },
+ lat: function(d) { return d[1]; },
+ radiusScaleFactor: function(d) { return 1; }
+ };
+
+ this._scale = {
+ radius: d3.scalePow().exponent(0.35),
+ opacity: d3.scaleLinear()
+ };
+
+ this._lastUpdate = Date.now();
+ this._fps = 0;
+
+ this._scale.radius
+ .domain([ 0, this.options.duration ])
+ .range(this.options.radiusRange)
+ .clamp(true);
+ this._scale.opacity
+ .domain([ 0, this.options.duration ])
+ .range(this.options.opacityRange)
+ .clamp(true);
+ },
+
+ // Called when the plugin layer is added to the map
+ onAdd : function(map) {
+
+ L.SVG.prototype.onAdd.call(this);
+
+ // Store a reference to the map for later use
+ this._map = map;
+
+ // Init the state of the simulation
+ this._running = false;
+
+ // Set up events
+ map.on({'move': this._updateContainer}, this);
+
+ },
+
+ // Called when the plugin layer is removed from the map
+ onRemove : function(map) {
+
+ L.SVG.prototype.onRemove.call(this);
+
+ // Destroy the svg container
+ this._destroyContainer();
+
+ // Remove events
+ map.off({'move': this._updateContainer}, this);
+
+ this._map = null;
+ this._data = null;
+
+ },
+
+
+ /*
+ * Private Methods
+ */
+
+ // Initialize the Container - creates the svg pane
+ _initContainer : function() {
+
+ L.SVG.prototype._initContainer.call(this);
+ this._d3Container = d3.select(this._container).select('g');
+
+ },
+
+ // Update the container - Updates the dimensions of the svg pane
+ _updateContainer : function() {
+
+ this._updatePings(true);
+
+ },
+
+ // Cleanup the svg pane
+ _destroyContainer: function() {
+
+ // Don't do anything
+
+ },
+
+
+ // Calculate the circle coordinates for the provided data
+ _getCircleCoords: function(geo) {
+ var point = this._map.latLngToLayerPoint(geo);
+ return { x: point.x, y: point.y };
+ },
+
+
+ // Add a ping to the map
+ _addPing : function(data, cssClass) {
+ // Lazy init the data array
+ if (null == this._data) this._data = [];
+
+ // Derive the spatial data
+ var geo = [ this._fn.lat(data), this._fn.lng(data) ];
+ var coords = this._getCircleCoords(geo);
+
+ // Add the data to the list of pings
+ var circle = {
+ data: data,
+ geo: geo,
+ ts: Date.now(),
+ nts: 0
+ };
+ circle.c = this._d3Container.append('circle')
+ .attr('class', (null != cssClass)? 'ping ' + cssClass : 'ping')
+ .attr('cx', coords.x)
+ .attr('cy', coords.y)
+ .attr('r', this._fn.radiusScaleFactor.call(this, data) * this._scale.radius.range()[0]);
+
+ // Push new circles
+ this._data.push(circle);
+ },
+
+ // Main update loop
+ _updatePings : function(immediate) {
+ var nowTs = Date.now();
+ if (null == this._data) this._data = [];
+
+ var maxIndex = -1;
+
+ // Update everything
+ for (var i=0; i < this._data.length; i++) {
+
+ var d = this._data[i];
+ var age = nowTs - d.ts;
+
+ if (this.options.duration < age) {
+
+ // If the blip is beyond it's life, remove it from the dom and track the lowest index to remove
+ d.c.remove();
+ maxIndex = i;
+
+ }
+ else {
+
+ // If the blip is still alive, process it
+ if (immediate || d.nts < nowTs) {
+
+ var coords = this._getCircleCoords(d.geo);
+
+ d.c.attr('cx', coords.x)
+ .attr('cy', coords.y)
+ .attr('r', this._fn.radiusScaleFactor.call(this, d.data) * this._scale.radius(age))
+ .attr('fill-opacity', this._scale.opacity(age))
+ .attr('stroke-opacity', this._scale.opacity(age));
+ d.nts = Math.round(nowTs + 1000/this.options.fps);
+
+ }
+ }
+ }
+
+ // Delete all the aged off data at once
+ if (maxIndex > -1) {
+ this._data.splice(0, maxIndex + 1);
+ }
+
+ // The return function dictates whether the timer loop will continue
+ this._running = (this._data.length > 0);
+
+ if (this._running) {
+ this._fps = 1000/(nowTs - this._lastUpdate);
+ this._lastUpdate = nowTs;
+ }
+
+ return !this._running;
+ },
+
+ // Expire old pings
+ _expirePings : function() {
+ var maxIndex = -1;
+ var nowTs = Date.now();
+
+ // Search from the front of the array
+ for (var i=0; i < this._data.length; i++) {
+ var d = this._data[i];
+ var age = nowTs - d.ts;
+
+ if(this.options.duration < age) {
+ // If the blip is beyond it's life, remove it from the dom and track the lowest index to remove
+ d.c.remove();
+ maxIndex = i;
+ }
+ else {
+ break;
+ }
+ }
+
+ // Delete all the aged off data at once
+ if (maxIndex > -1) {
+ this._data.splice(0, maxIndex + 1);
+ }
+ },
+
+ /*
+ * Public Methods
+ */
+
+ duration: function(v) {
+ if (!arguments.length) { return this.options.duration; }
+ this.options.duration = v;
+
+ return this;
+ },
+
+ fps: function(v) {
+ if (!arguments.length) { return this.options.fps; }
+ this.options.fps = v;
+
+ return this;
+ },
+
+ lng: function(v) {
+ if (!arguments.length) { return this._fn.lng; }
+ this._fn.lng = v;
+
+ return this;
+ },
+
+ lat: function(v) {
+ if (!arguments.length) { return this._fn.lat; }
+ this._fn.lat = v;
+
+ return this;
+ },
+
+ radiusRange: function(v) {
+ if (!arguments.length) { return this.options.radiusRange; }
+ this.options.radiusRange = v;
+ this._scale.radius().range(v);
+
+ return this;
+ },
+
+ opacityRange: function(v) {
+ if (!arguments.length) { return this.options.opacityRange; }
+ this.options.opacityRange = v;
+ this._scale.opacity().range(v);
+
+ return this;
+ },
+
+ radiusScale: function(v) {
+ if (!arguments.length) { return this._scale.radius; }
+ this._scale.radius = v;
+
+ return this;
+ },
+
+ opacityScale: function(v) {
+ if (!arguments.length) { return this._scale.opacity; }
+ this._scale.opacity = v;
+
+ return this;
+ },
+
+ radiusScaleFactor: function(v) {
+ if (!arguments.length) { return this._fn.radiusScaleFactor; }
+ this._fn.radiusScaleFactor = v;
+
+ return this;
+ },
+
+ /*
+ * Method by which to "add" pings
+ */
+ ping : function(data, cssClass) {
+ this._addPing(data, cssClass);
+ this._expirePings();
+
+ // Start timer if not active
+ if (!this._running && this._data.length > 0) {
+ this._running = true;
+ this._lastUpdate = Date.now();
+
+ var that = this;
+ d3.timer(function() { that._updatePings.call(that, false); });
+ }
+
+ return this;
+ },
+
+ getActualFps : function() {
+ return this._fps;
+ },
+
+ data : function() {
+ return this._data;
+ },
+
+ });
+
+ L.pingLayer = function(options) {
+ return new L.PingLayer(options);
+ };
+
+})));
+//# sourceMappingURL=leaflet-d3.js.map
diff --git a/docs/dependencies/dist/leaflet-d3.min.js b/docs/dependencies/dist/leaflet-d3.min.js
new file mode 100644
index 0000000..9a2084d
--- /dev/null
+++ b/docs/dependencies/dist/leaflet-d3.min.js
@@ -0,0 +1,239 @@
+/*! @asymmetrik/leaflet-d3 - 4.3.2 - Copyright (c) 2007-2018 Asymmetrik Ltd, a Maryland Corporation + */
+(function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?factory(require("d3-hexbin"),require("d3"),require("leaflet")):typeof define==="function"&&define.amd?define(["d3-hexbin","d3","leaflet"],factory):factory(global.d3.hexbin,global.d3)})(this,function(d3Hexbin,d3){"use strict";
+/**
+ * This is a convoluted way of getting ahold of the hexbin function.
+ * - When imported globally, d3 is exposed in the global namespace as 'd3'
+ * - When imported using a module system, it's a named import (and can't collide with d3)
+ * - When someone isn't importing d3-hexbin, the named import will be undefined
+ *
+ * As a result, we have to figure out how it's being imported and get the function reference
+ * (which is why we have this convoluted nested ternary statement
+ */var d3_hexbin=null!=d3.hexbin?d3.hexbin:null!=d3Hexbin?d3Hexbin.hexbin:null;
+/**
+ * L is defined by the Leaflet library, see git://github.com/Leaflet/Leaflet.git for documentation
+ * We extend L.SVG to take advantage of built-in zoom animations.
+ */L.HexbinLayer=L.SVG.extend({includes:L.Evented||L.Mixin.Events,
+/**
+ * Default options
+ */
+options:{radius:12,opacity:.6,duration:200,colorScaleExtent:[1,undefined],radiusScaleExtent:[1,undefined],colorDomain:null,radiusDomain:null,colorRange:["#f7fbff","#08306b"],radiusRange:[4,12],pointerEvents:"all"},
+/**
+ * Standard Leaflet initialize function, accepting an options argument provided by the
+ * user when they create the layer
+ * @param options Options object where the options override the defaults
+ */
+initialize:function(options){L.setOptions(this,options);
+// Set up the various overrideable functions
+this._fn={lng:function(d){return d[0]},lat:function(d){return d[1]},colorValue:function(d){return d.length},radiusValue:function(d){return Number.MAX_VALUE},fill:function(d){var val=this._fn.colorValue(d);return null!=val?this._scale.color(val):"none"}};
+// Set up the customizable scale
+this._scale={color:d3.scaleLinear(),radius:d3.scaleLinear()};
+// Set up the Dispatcher for managing events and callbacks
+this._dispatch=d3.dispatch("mouseover","mouseout","click");
+// Set up the default hover handler
+this._hoverHandler=L.HexbinHoverHandler.none();
+// Create the hex layout
+this._hexLayout=d3_hexbin().radius(this.options.radius).x(function(d){return d.point[0]}).y(function(d){return d.point[1]});
+// Initialize the data array to be empty
+this._data=[];this._scale.color.range(this.options.colorRange).clamp(true);this._scale.radius.range(this.options.radiusRange).clamp(true)},
+/**
+ * Callback made by Leaflet when the layer is added to the map
+ * @param map Reference to the map to which this layer has been added
+ */
+onAdd:function(map){L.SVG.prototype.onAdd.call(this);
+// Store a reference to the map for later use
+this._map=map;
+// Redraw on moveend
+map.on({moveend:this.redraw},this);
+// Initial draw
+this.redraw()},
+/**
+ * Callback made by Leaflet when the layer is removed from the map
+ * @param map Reference to the map from which this layer is being removed
+ */
+onRemove:function(map){L.SVG.prototype.onRemove.call(this);
+// Destroy the svg container
+this._destroyContainer();
+// Remove events
+map.off({moveend:this.redraw},this);this._map=null;
+// Explicitly will leave the data array alone in case the layer will be shown again
+//this._data = [];
+},
+/**
+ * Create the SVG container for the hexbins
+ * @private
+ */
+_initContainer:function(){L.SVG.prototype._initContainer.call(this);this._d3Container=d3.select(this._container).select("g")},
+/**
+ * Clean up the svg container
+ * @private
+ */
+_destroyContainer:function(){
+// Don't do anything
+},
+/**
+ * (Re)draws the hexbins data on the container
+ * @private
+ */
+redraw:function(){var that=this;if(!that._map){return}
+// Generate the mapped version of the data
+var data=that._data.map(function(d){var lng=that._fn.lng(d);var lat=that._fn.lat(d);var point=that._project([lng,lat]);return{o:d,point:point}});
+// Select the hex group for the current zoom level. This has
+// the effect of recreating the group if the zoom level has changed
+var join=this._d3Container.selectAll("g.hexbin").data([this._map.getZoom()],function(d){return d});
+// enter
+var enter=join.enter().append("g").attr("class",function(d){return"hexbin zoom-"+d});
+// enter + update
+var enterUpdate=enter.merge(join);
+// exit
+join.exit().remove();
+// add the hexagons to the select
+this._createHexagons(enterUpdate,data)},_createHexagons:function(g,data){var that=this;
+// Create the bins using the hexbin layout
+// Generate the map bounds (to be used to filter the hexes to what is visible)
+var bounds=that._map.getBounds();var size=that._map.getSize();bounds=bounds.pad(that.options.radius*2/Math.max(size.x,size.y));var bins=that._hexLayout(data);
+// Derive the extents of the data values for each dimension
+var colorExtent=that._getExtent(bins,that._fn.colorValue,that.options.colorScaleExtent);var radiusExtent=that._getExtent(bins,that._fn.radiusValue,that.options.radiusScaleExtent);
+// Match the domain cardinality to that of the color range, to allow for a polylinear scale
+var colorDomain=this.options.colorDomain;if(null==colorDomain){colorDomain=that._linearlySpace(colorExtent[0],colorExtent[1],that._scale.color.range().length)}var radiusDomain=this.options.radiusDomain||radiusExtent;
+// Set the scale domains
+that._scale.color.domain(colorDomain);that._scale.radius.domain(radiusDomain);
+/*
+ * Join
+ * Join the Hexagons to the data
+ * Use a deterministic id for tracking bins based on position
+ */bins=bins.filter(function(d){return bounds.contains(that._map.layerPointToLatLng(L.point(d.x,d.y)))});var join=g.selectAll("g.hexbin-container").data(bins,function(d){return d.x+":"+d.y});
+/*
+ * Update
+ * Set the fill and opacity on a transition
+ * opacity is re-applied in case the enter transition was cancelled
+ * the path is applied as well to resize the bins
+ */join.select("path.hexbin-hexagon").transition().duration(that.options.duration).attr("fill",that._fn.fill.bind(that)).attr("fill-opacity",that.options.opacity).attr("stroke-opacity",that.options.opacity).attr("d",function(d){return that._hexLayout.hexagon(that._scale.radius(that._fn.radiusValue.call(that,d)))});
+/*
+ * Enter
+ * Establish the path, size, fill, and the initial opacity
+ * Transition to the final opacity and size
+ */var enter=join.enter().append("g").attr("class","hexbin-container");enter.append("path").attr("class","hexbin-hexagon").attr("transform",function(d){return"translate("+d.x+","+d.y+")"}).attr("d",function(d){return that._hexLayout.hexagon(that._scale.radius.range()[0])}).attr("fill",that._fn.fill.bind(that)).attr("fill-opacity",.01).attr("stroke-opacity",.01).transition().duration(that.options.duration).attr("fill-opacity",that.options.opacity).attr("stroke-opacity",that.options.opacity).attr("d",function(d){return that._hexLayout.hexagon(that._scale.radius(that._fn.radiusValue.call(that,d)))});
+// Grid
+var gridEnter=enter.append("path").attr("class","hexbin-grid").attr("transform",function(d){return"translate("+d.x+","+d.y+")"}).attr("d",function(d){return that._hexLayout.hexagon(that.options.radius)}).attr("fill","none").attr("stroke","none").style("pointer-events",that.options.pointerEvents);
+// Grid enter-update
+gridEnter.merge(join.select("path.hexbin-grid")).on("mouseover",function(d,i){that._hoverHandler.mouseover.call(this,that,d,i);that._dispatch.call("mouseover",this,d,i)}).on("mouseout",function(d,i){that._dispatch.call("mouseout",this,d,i);that._hoverHandler.mouseout.call(this,that,d,i)}).on("click",function(d,i){that._dispatch.call("click",this,d,i)});
+// Exit
+var exit=join.exit();exit.select("path.hexbin-hexagon").transition().duration(that.options.duration).attr("fill-opacity",0).attr("stroke-opacity",0).attr("d",function(d){return that._hexLayout.hexagon(0)});exit.transition().duration(that.options.duration).remove()},_getExtent:function(bins,valueFn,scaleExtent){
+// Determine the extent of the values
+var extent=d3.extent(bins,valueFn.bind(this));
+// If either's null, initialize them to 0
+if(null==extent[0])extent[0]=0;if(null==extent[1])extent[1]=0;
+// Now apply the optional clipping of the extent
+if(null!=scaleExtent[0])extent[0]=scaleExtent[0];if(null!=scaleExtent[1])extent[1]=scaleExtent[1];return extent},_project:function(coord){var point=this._map.latLngToLayerPoint([coord[1],coord[0]]);return[point.x,point.y]},_getBounds:function(data){if(null==data||data.length<1){return{min:[0,0],max:[0,0]}}
+// bounds is [[min long, min lat], [max long, max lat]]
+var bounds=[[999,999],[-999,-999]];data.forEach(function(element){var x=element.point[0];var y=element.point[1];bounds[0][0]=Math.min(bounds[0][0],x);bounds[0][1]=Math.min(bounds[0][1],y);bounds[1][0]=Math.max(bounds[1][0],x);bounds[1][1]=Math.max(bounds[1][1],y)});return{min:bounds[0],max:bounds[1]}},_linearlySpace:function(from,to,length){var arr=new Array(length);var step=(to-from)/Math.max(length-1,1);for(var i=0;i0&&tooltip._groups[0].length>0){div=tooltip._groups[0][0]}var h=div.clientHeight,w=div.clientWidth;tooltip.style("top",""+event.clientY-gCoords[1]-h-16+"px").style("left",""+event.clientX-gCoords[0]-w/2+"px")},mouseout:function(hexLayer,data){tooltip.style("visibility","hidden").html()}}},resizeFill:function(){
+// return the handler instance
+return{mouseover:function(hexLayer,data){var o=d3.select(this.parentNode);o.select("path.hexbin-hexagon").attr("d",function(d){return hexLayer._hexLayout.hexagon(hexLayer.options.radius)})},mouseout:function(hexLayer,data){var o=d3.select(this.parentNode);o.select("path.hexbin-hexagon").attr("d",function(d){return hexLayer._hexLayout.hexagon(hexLayer._scale.radius(hexLayer._fn.radiusValue.call(hexLayer,d)))})}}},resizeScale:function(options){
+// merge options with defaults
+options=options||{};if(null==options.radiusScale)options.radiusScale=.5;
+// return the handler instance
+return{mouseover:function(hexLayer,data){var o=d3.select(this.parentNode);o.select("path.hexbin-hexagon").attr("d",function(d){return hexLayer._hexLayout.hexagon(hexLayer._scale.radius.range()[1]*(1+options.radiusScale))})},mouseout:function(hexLayer,data){var o=d3.select(this.parentNode);o.select("path.hexbin-hexagon").attr("d",function(d){return hexLayer._hexLayout.hexagon(hexLayer._scale.radius(hexLayer._fn.radiusValue.call(hexLayer,d)))})}}},compound:function(options){options=options||{};if(null==options.handlers)options.handlers=[L.HexbinHoverHandler.none()];return{mouseover:function(hexLayer,data){var that=this;options.handlers.forEach(function(h){h.mouseover.call(that,hexLayer,data)})},mouseout:function(hexLayer,data){var that=this;options.handlers.forEach(function(h){h.mouseout.call(that,hexLayer,data)})}}},none:function(){return{mouseover:function(){},mouseout:function(){}}}};L.hexbinLayer=function(options){return new L.HexbinLayer(options)};
+/**
+ * L is defined by the Leaflet library, see git://github.com/Leaflet/Leaflet.git for documentation
+ * We extend L.SVG to take advantage of built-in zoom animations.
+ */L.PingLayer=L.SVG.extend({includes:L.Evented||L.Mixin.Events,
+/*
+ * Default options
+ */
+options:{duration:800,fps:32,opacityRange:[1,0],radiusRange:[3,15]},
+// Initialization of the plugin
+initialize:function(options){L.setOptions(this,options);this._fn={lng:function(d){return d[0]},lat:function(d){return d[1]},radiusScaleFactor:function(d){return 1}};this._scale={radius:d3.scalePow().exponent(.35),opacity:d3.scaleLinear()};this._lastUpdate=Date.now();this._fps=0;this._scale.radius.domain([0,this.options.duration]).range(this.options.radiusRange).clamp(true);this._scale.opacity.domain([0,this.options.duration]).range(this.options.opacityRange).clamp(true)},
+// Called when the plugin layer is added to the map
+onAdd:function(map){L.SVG.prototype.onAdd.call(this);
+// Store a reference to the map for later use
+this._map=map;
+// Init the state of the simulation
+this._running=false;
+// Set up events
+map.on({move:this._updateContainer},this)},
+// Called when the plugin layer is removed from the map
+onRemove:function(map){L.SVG.prototype.onRemove.call(this);
+// Destroy the svg container
+this._destroyContainer();
+// Remove events
+map.off({move:this._updateContainer},this);this._map=null;this._data=null},
+/*
+ * Private Methods
+ */
+// Initialize the Container - creates the svg pane
+_initContainer:function(){L.SVG.prototype._initContainer.call(this);this._d3Container=d3.select(this._container).select("g")},
+// Update the container - Updates the dimensions of the svg pane
+_updateContainer:function(){this._updatePings(true)},
+// Cleanup the svg pane
+_destroyContainer:function(){
+// Don't do anything
+},
+// Calculate the circle coordinates for the provided data
+_getCircleCoords:function(geo){var point=this._map.latLngToLayerPoint(geo);return{x:point.x,y:point.y}},
+// Add a ping to the map
+_addPing:function(data,cssClass){
+// Lazy init the data array
+if(null==this._data)this._data=[];
+// Derive the spatial data
+var geo=[this._fn.lat(data),this._fn.lng(data)];var coords=this._getCircleCoords(geo);
+// Add the data to the list of pings
+var circle={data:data,geo:geo,ts:Date.now(),nts:0};circle.c=this._d3Container.append("circle").attr("class",null!=cssClass?"ping "+cssClass:"ping").attr("cx",coords.x).attr("cy",coords.y).attr("r",this._fn.radiusScaleFactor.call(this,data)*this._scale.radius.range()[0]);
+// Push new circles
+this._data.push(circle)},
+// Main update loop
+_updatePings:function(immediate){var nowTs=Date.now();if(null==this._data)this._data=[];var maxIndex=-1;
+// Update everything
+for(var i=0;i-1){this._data.splice(0,maxIndex+1)}
+// The return function dictates whether the timer loop will continue
+this._running=this._data.length>0;if(this._running){this._fps=1e3/(nowTs-this._lastUpdate);this._lastUpdate=nowTs}return!this._running},
+// Expire old pings
+_expirePings:function(){var maxIndex=-1;var nowTs=Date.now();
+// Search from the front of the array
+for(var i=0;i-1){this._data.splice(0,maxIndex+1)}},
+/*
+ * Public Methods
+ */
+duration:function(v){if(!arguments.length){return this.options.duration}this.options.duration=v;return this},fps:function(v){if(!arguments.length){return this.options.fps}this.options.fps=v;return this},lng:function(v){if(!arguments.length){return this._fn.lng}this._fn.lng=v;return this},lat:function(v){if(!arguments.length){return this._fn.lat}this._fn.lat=v;return this},radiusRange:function(v){if(!arguments.length){return this.options.radiusRange}this.options.radiusRange=v;this._scale.radius().range(v);return this},opacityRange:function(v){if(!arguments.length){return this.options.opacityRange}this.options.opacityRange=v;this._scale.opacity().range(v);return this},radiusScale:function(v){if(!arguments.length){return this._scale.radius}this._scale.radius=v;return this},opacityScale:function(v){if(!arguments.length){return this._scale.opacity}this._scale.opacity=v;return this},radiusScaleFactor:function(v){if(!arguments.length){return this._fn.radiusScaleFactor}this._fn.radiusScaleFactor=v;return this},
+/*
+ * Method by which to "add" pings
+ */
+ping:function(data,cssClass){this._addPing(data,cssClass);this._expirePings();
+// Start timer if not active
+if(!this._running&&this._data.length>0){this._running=true;this._lastUpdate=Date.now();var that=this;d3.timer(function(){that._updatePings.call(that,false)})}return this},getActualFps:function(){return this._fps},data:function(){return this._data}});L.pingLayer=function(options){return new L.PingLayer(options)}});
+//# sourceMappingURL=leaflet-d3.js.map
\ No newline at end of file
diff --git a/docs/dependencies/package.json b/docs/dependencies/package.json
new file mode 100644
index 0000000..c7cbf26
--- /dev/null
+++ b/docs/dependencies/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "gwt-leaflet-d3",
+ "version": "0.3.1",
+ "license": "MIT",
+ "dependencies": {
+ "@asymmetrik/leaflet-d3": "4.3.2"
+ }
+}
diff --git a/examples/simple-hexbinlayer-demo/pom.xml b/examples/simple-hexbinlayer-demo/pom.xml
index cc29594..583d2b9 100644
--- a/examples/simple-hexbinlayer-demo/pom.xml
+++ b/examples/simple-hexbinlayer-demo/pom.xml
@@ -40,7 +40,7 @@
pl.itrack
gwt-leaflet-d3
- 0.3.0
+ 0.3.1
diff --git a/pom.xml b/pom.xml
index dbf9179..825ffb4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
pl.itrack
gwt-leaflet-d3
- 0.3.0
+ 0.3.1
gwt-lib
GWT Leaflet plugin that allows integration with d3.js library
A GWT JsInterop wrapper for collection of plugins for using d3.js with Leaflet
@@ -129,4 +129,4 @@
-
\ No newline at end of file
+
diff --git a/src/main/java/pl/itrack/leafletd3/client/LeafletD3.java b/src/main/java/pl/itrack/leafletd3/client/LeafletD3.java
index c4854fa..2b87174 100644
--- a/src/main/java/pl/itrack/leafletd3/client/LeafletD3.java
+++ b/src/main/java/pl/itrack/leafletd3/client/LeafletD3.java
@@ -24,7 +24,7 @@ public static void init(LeafletD3LibInjector.CallbackFn callbackFn) {
Stream.of(
"https://d3js.org/d3.v4.min.js",
"https://d3js.org/d3-hexbin.v0.2.min.js",
- "https://rawgit.com/Asymmetrik/leaflet-d3/master/dist/leaflet-d3.js")
+ "https://raw.githubusercontent.com/baldram/gwt-leaflet-d3/master/docs/dependencies/dist/leaflet-d3.min.js")
.collect(Collectors.toList()),
callbackFn);
}