diff --git a/src/kibana/components/vislib/lib/handler/handler.js b/src/kibana/components/vislib/lib/handler/handler.js index 1dbbe2f16fe36..7ff7236be53e4 100644 --- a/src/kibana/components/vislib/lib/handler/handler.js +++ b/src/kibana/components/vislib/lib/handler/handler.js @@ -162,6 +162,14 @@ define(function (require) { .text(message); }; + Handler.prototype.destroy = function () { + this.charts.forEach(function (chart) { + if (_.isFunction(chart.destroy)) { + chart.destroy(); + } + }); + }; + return Handler; }; }); diff --git a/src/kibana/components/vislib/lib/handler/types/tile_map.js b/src/kibana/components/vislib/lib/handler/types/tile_map.js index d413838b4fe7c..141e29031a1a4 100644 --- a/src/kibana/components/vislib/lib/handler/types/tile_map.js +++ b/src/kibana/components/vislib/lib/handler/types/tile_map.js @@ -12,6 +12,12 @@ define(function (require) { data: data }); + MapHandler.resize = function () { + this.charts.forEach(function (chart) { + chart.resizeArea(); + }); + }; + return MapHandler; }; }; diff --git a/src/kibana/components/vislib/styles/_tilemap.less b/src/kibana/components/vislib/styles/_tilemap.less index 930a1218225b1..50a6fed53f13f 100644 --- a/src/kibana/components/vislib/styles/_tilemap.less +++ b/src/kibana/components/vislib/styles/_tilemap.less @@ -59,6 +59,13 @@ color: #444; } +.leaflet-control-fit { + text-align: center; + background: #fff; + width: 26px; + height: 26px; + outline: 1px black; +} /* over-rides leaflet popup styles to look like kibana tooltip */ diff --git a/src/kibana/components/vislib/vis.js b/src/kibana/components/vislib/vis.js index 9ee07f9346180..3a9ff8c134f74 100644 --- a/src/kibana/components/vislib/vis.js +++ b/src/kibana/components/vislib/vis.js @@ -52,19 +52,7 @@ define(function (require) { this.data = data; this.handler = handlerTypes[chartType](this) || handlerTypes.column(this); - - try { - this.handler.render(); - } catch (error) { - // If involving height and width of the container, log error to screen. - // Because we have to wait for the DOM element to initialize, we do not - // want to throw an error when the DOM `el` is zero - if (error instanceof errors.ContainerTooSmall) { - this.handler.error(error.message); - } else { - console.error(error.stack); - } - } + this._runOnHandler('render'); }; /** @@ -77,7 +65,27 @@ define(function (require) { // TODO: need to come up with a solution for resizing when no data is available return; } - this.render(this.data); + + if (_.isFunction(this.handler.resize)) { + this._runOnHandler('resize'); + } else { + this.render(this.data); + } + }; + + Vis.prototype._runOnHandler = function (method) { + try { + this.handler[method](); + } catch (error) { + // If involving height and width of the container, log error to screen. + // Because we have to wait for the DOM element to initialize, we do not + // want to throw an error when the DOM `el` is zero + if (error instanceof errors.ContainerTooSmall) { + this.handler.error(error.message); + } else { + console.error(error.stack); + } + } }; /** @@ -89,9 +97,10 @@ define(function (require) { * @method destroy */ Vis.prototype.destroy = function () { - d3.select(this.el).selectAll('*').remove(); this.resizeChecker.off('resize', this.resize); this.resizeChecker.destroy(); + this._runOnHandler('destroy'); + d3.select(this.el).selectAll('*').remove(); }; /** diff --git a/src/kibana/components/vislib/visualizations/tile_map.js b/src/kibana/components/vislib/visualizations/tile_map.js index 57e9aed348546..f19308b1fe5c7 100644 --- a/src/kibana/components/vislib/visualizations/tile_map.js +++ b/src/kibana/components/vislib/visualizations/tile_map.js @@ -3,16 +3,15 @@ define(function (require) { var _ = require('lodash'); var $ = require('jquery'); var L = require('leaflet'); - + var Chart = Private(require('components/vislib/visualizations/_chart')); var errors = require('errors'); require('css!components/vislib/styles/main'); - + var mapData; var mapCenter = [15, 5]; var mapZoom = 2; - /** * Tile Map Visualization: renders maps @@ -30,15 +29,11 @@ define(function (require) { return new TileMap(handler, chartEl, chartData); } TileMap.Super.apply(this, arguments); - + // add allmin and allmax to geoJSON var mapDataExtents = handler.data.mapDataExtents(handler.data.data.raw); chartData.geoJSON.properties.allmin = mapDataExtents[0]; chartData.geoJSON.properties.allmax = mapDataExtents[1]; - - // turn off resizeChecker for tile maps - this.handler.vis.resizeChecker.off('resize', this.resize); - this.handler.vis.resizeChecker.destroy(); } /** @@ -53,7 +48,12 @@ define(function (require) { var $elem = $(this.chartEl); var div; var worldBounds = L.latLngBounds([-200, -220], [200, 220]); - + + // clean up old maps + _.invoke(self.maps, 'destroy'); + // create a new maps array + self.maps = []; + return function (selection) { selection.each(function (data) { div = $(this); @@ -88,16 +88,35 @@ define(function (require) { zoom: mapZoom, continuousWorld: true, noWrap: true, - maxBounds: worldBounds + maxBounds: worldBounds, + scrollWheelZoom: false, + fadeAnimation: false }; var map = L.map(div[0], mapOptions); + self.maps.push(map); - // switch map types - L.control.layers({ - 'Map': mapLayer, - 'Satellite': satLayer - }).addTo(map); + function fitBounds() { + if (data.geoJSON) { + map.fitBounds(_.map(data.geoJSON.features, function (feature) { + return _.clone(feature.geometry.coordinates).reverse(); + })); + } + } + + // Add button to fit container to points + var fitControl = L.Control.extend({ + options: { + position: 'topleft' + }, + onAdd: function (map) { + var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-control-zoom leaflet-control-fit'); + $(container).html(''); + $(container).on('click', fitBounds); + return container; + } + }); + map.addControl(new fitControl()); map.on('zoomend dragend', function () { mapZoom = self._attr.lastZoom = map.getZoom(); @@ -112,7 +131,6 @@ define(function (require) { } } - // if sub agg split, add labels if (data.geoJSON.properties.label) { self.addLabel(data.geoJSON.properties.label, map); } @@ -143,7 +161,7 @@ define(function (require) { var featureLayer = L.geoJson(mapData, { pointToLayer: function (feature, latlng) { var count = feature.properties.count; - + var rad = zoomScale * self.radiusScale(count, max, precision); return L.circleMarker(latlng, { radius: rad @@ -183,17 +201,17 @@ define(function (require) { */ TileMap.prototype.shadedCircleMarkers = function (map, mapData) { var self = this; - + // TODO: add UI to select local min max or all min max // min and max from chart data for this map // var min = mapData.properties.min; // var max = mapData.properties.max; - + // super min and max from all chart data var min = mapData.properties.allmin; var max = mapData.properties.allmax; - + var length = mapData.properties.length; var precision = mapData.properties.precision; var zoomScale = self.zoomScale(mapZoom); @@ -285,6 +303,20 @@ define(function (require) { legend.addTo(map); }; + /** + * Invalidate the size of the map, so that leaflet will resize to fit. + * then moves to center + * + * @return {undefined} + */ + TileMap.prototype.resizeArea = function () { + this.maps.forEach(function (map) { + map.invalidateSize({ + debounceMoveend: true + }); + }); + }; + /** * Redraws feature layer markers * @@ -299,10 +331,10 @@ define(function (require) { TileMap.prototype.resizeFeatures = function (map, min, max, precision, featureLayer) { var self = this; var zoomScale = self.zoomScale(mapZoom); - + featureLayer.eachLayer(function (layer) { var latlng = L.latLng(layer.feature.geometry.coordinates[1], layer.feature.geometry.coordinates[0]); - + var count = layer.feature.properties.count; var rad; if (self._attr.mapType === 'Shaded Circle Markers') { @@ -363,7 +395,7 @@ define(function (require) { /** * radiusScale returns a circle radius from - * approx. square root of count + * approx. square root of count * which is multiplied by a factor based on the geohash precision * for relative sizing of markers * @@ -413,7 +445,7 @@ define(function (require) { }; /** - * returns a number to scale circle markers + * returns a number to scale circle markers * based on the geohash precision * * @method quantRadiusScale @@ -496,6 +528,15 @@ define(function (require) { return darker; }; + /** + * tell leaflet that it's time to cleanup the map + */ + TileMap.prototype.destroy = function () { + this.maps.forEach(function (map) { + map.remove(); + }); + }; + return TileMap; };