').appendTo('body').get(0);
+ teardown.push(() => $(el).remove());
+ return el;
+ };
+
+ const createChecker = el => {
+ const checker = new ResizeChecker(el);
+ teardown.push(() => checker.destroy());
+ return checker;
+ };
+
+ const createListener = () => {
+ let resolveFirstCallPromise;
+ const listener = sinon.spy(() => resolveFirstCallPromise());
+ listener.firstCallPromise = new Promise(resolve => (resolveFirstCallPromise = resolve));
+ return listener;
+ };
+
+ return { EventEmitter, createEl, createChecker, createListener };
+ };
+ }));
+
+ afterEach(() => {
+ teardown.splice(0).forEach(fn => {
+ fn();
+ });
+ });
+
+ describe('contruction', () => {
+ it('accepts a jQuery wrapped element', () => {
+ const { createChecker } = setup();
+
+ createChecker($('
'));
+ });
+ });
+
+ describe('events', () => {
+ it('is an event emitter', () => {
+ const { createEl, createChecker, EventEmitter } = setup();
+
+ const checker = createChecker(createEl());
+ expect(checker).to.be.a(EventEmitter);
+ });
+
+ it('emits a "resize" event asynchronously', async () => {
+ const { createEl, createChecker, createListener } = setup();
+
+ const el = createEl();
+ const checker = createChecker(el);
+ const listener = createListener();
+
+ checker.on('resize', listener);
+ $(el).height(100);
+ sinon.assert.notCalled(listener);
+ await listener.firstCallPromise;
+ sinon.assert.calledOnce(listener);
+ });
+ });
+
+ describe('#modifySizeWithoutTriggeringResize()', () => {
+ it(`does not emit "resize" events caused by the block`, async () => {
+ const { createChecker, createEl, createListener } = setup();
+
+ const el = createEl();
+ const checker = createChecker(el);
+ const listener = createListener();
+
+ checker.on('resize', listener);
+ checker.modifySizeWithoutTriggeringResize(() => {
+ $(el).height(100);
+ });
+ await delay(1000);
+ sinon.assert.notCalled(listener);
+ });
+
+ it('does emit "resize" when modification is made between the block and resize notification', async () => {
+ const { createChecker, createEl, createListener } = setup();
+
+ const el = createEl();
+ const checker = createChecker(el);
+ const listener = createListener();
+
+ checker.on('resize', listener);
+ checker.modifySizeWithoutTriggeringResize(() => {
+ $(el).height(100);
+ });
+ sinon.assert.notCalled(listener);
+ $(el).height(200);
+ await listener.firstCallPromise;
+ sinon.assert.calledOnce(listener);
+ });
+ });
+
+ describe('#destroy()', () => {
+ it('destroys internal observer instance', () => {
+ const { createChecker, createEl, createListener } = setup();
+
+ const checker = createChecker(createEl());
+ createListener();
+
+ checker.destroy();
+ expect(!checker._observer).to.be(true);
+ });
+
+ it('does not emit future resize events', async () => {
+ const { createChecker, createEl, createListener } = setup();
+
+ const el = createEl();
+ const checker = createChecker(el);
+ const listener = createListener();
+
+ checker.on('resize', listener);
+ checker.destroy();
+
+ $(el).height(100);
+ await delay(1000);
+ sinon.assert.notCalled(listener);
+ });
+ });
+});
diff --git a/src/ui/public/resize_checker/index.js b/src/ui/public/resize_checker/index.js
new file mode 100644
index 0000000000000..fbc47d1f9afd7
--- /dev/null
+++ b/src/ui/public/resize_checker/index.js
@@ -0,0 +1 @@
+export { ResizeCheckerProvider } from './resize_checker';
diff --git a/src/ui/public/resize_checker/resize_checker.js b/src/ui/public/resize_checker/resize_checker.js
new file mode 100644
index 0000000000000..c112a3f801060
--- /dev/null
+++ b/src/ui/public/resize_checker/resize_checker.js
@@ -0,0 +1,94 @@
+import $ from 'jquery';
+import ResizeObserver from 'resize-observer-polyfill';
+import { isEqual } from 'lodash';
+
+import EventsProvider from 'ui/events';
+
+export function ResizeCheckerProvider(Private) {
+ const EventEmitter = Private(EventsProvider);
+
+ function validateElArg(el) {
+ // the ResizeChecker historically accepted jquery elements,
+ // so we wrap in jQuery then extract the element
+ const $el = $(el);
+
+ if ($el.size() !== 1) {
+ throw new TypeError('ResizeChecker must be constructed with a single DOM element.');
+ }
+
+ return $el.get(0);
+ }
+
+ function getSize(el) {
+ return [el.clientWidth, el.clientHeight];
+ }
+
+ /**
+ * ResizeChecker receives an element and emits a "resize"
+ * event every time it changes size. Used by the vislib to re-render
+ * visualizations on resize as well as the console for the
+ * same reason, but for the editors.
+ */
+ return class ResizeChecker extends EventEmitter {
+ constructor(el) {
+ super();
+
+ this._el = validateElArg(el);
+
+ // the width and height of the element that we expect to see
+ // on the next resize notification. If it matches the size at
+ // the time of the notifications then it we will be ignored.
+ this._expectedSize = getSize(this._el);
+
+ this._observer = new ResizeObserver(() => {
+ if (this._expectedSize) {
+ const sameSize = isEqual(getSize(this._el), this._expectedSize);
+ this._expectedSize = null;
+
+ if (sameSize) {
+ // don't trigger resize notification if the size is what we expect
+ return;
+ }
+ }
+
+ this.emit('resize');
+ });
+
+ this._observer.observe(this._el);
+ }
+
+ /**
+ * Run a function and ignore all resizes that occur
+ * while it's running.
+ *
+ * @return {undefined}
+ */
+ modifySizeWithoutTriggeringResize(block) {
+ try {
+ block();
+ } finally {
+ this._expectedSize = getSize(this._el);
+ }
+ }
+
+ /**
+ * Tell the ResizeChecker to shutdown, stop listenings, and never
+ * emit another resize event.
+ *
+ * Cleans up it's listeners and timers.
+ *
+ * @method destroy
+ * @return {void}
+ */
+ destroy() {
+ if (this._destroyed) return;
+ this._destroyed = true;
+
+ this._observer.disconnect();
+ this._observer = null;
+ this._expectedSize = null;
+ this._el = null;
+ this.removeAllListeners();
+ }
+ };
+}
diff --git a/src/ui/public/utils/zoom_to_precision.js b/src/ui/public/utils/zoom_to_precision.js
new file mode 100644
index 0000000000000..375a032d63613
--- /dev/null
+++ b/src/ui/public/utils/zoom_to_precision.js
@@ -0,0 +1,38 @@
+import { geohashColumns } from 'ui/utils/decode_geo_hash';
+
+const maxPrecision = 12;
+/**
+ * Map Leaflet zoom levels to geohash precision levels.
+ * The size of a geohash column-width on the map should be at least `minGeohashPixels` pixels wide.
+ */
+
+
+
+
+const zoomPrecisionMap = {};
+const minGeohashPixels = 16;
+
+function calculateZoomToPrecisionMap(maxZoom) {
+
+ for (let zoom = 0; zoom <= maxZoom; zoom += 1) {
+ if (typeof zoomPrecisionMap[zoom] === 'number') {
+ continue;
+ }
+ const worldPixels = 256 * Math.pow(2, zoom);
+ zoomPrecisionMap[zoom] = 1;
+ for (let precision = 2; precision <= maxPrecision; precision += 1) {
+ const columns = geohashColumns(precision);
+ if ((worldPixels / columns) >= minGeohashPixels) {
+ zoomPrecisionMap[zoom] = precision;
+ } else {
+ break;
+ }
+ }
+ }
+}
+
+
+export default function zoomToPrecision(mapZoom, maxPrecision, maxZoom) {
+ calculateZoomToPrecisionMap(typeof maxZoom === 'number' ? maxZoom : 21);
+ return Math.min(zoomPrecisionMap[mapZoom], maxPrecision);
+}
diff --git a/src/ui/public/vis/agg_config.js b/src/ui/public/vis/agg_config.js
index 310ec5546ce34..6bfc0179aeb37 100644
--- a/src/ui/public/vis/agg_config.js
+++ b/src/ui/public/vis/agg_config.js
@@ -266,6 +266,11 @@ export default function AggConfigFactory(Private, fieldTypeFilter) {
);
};
+ AggConfig.prototype.getRequestAggs = function () {
+ if (!this.type) return;
+ return this.type.getRequestAggs(this) || [this];
+ };
+
AggConfig.prototype.getResponseAggs = function () {
if (!this.type) return;
return this.type.getResponseAggs(this) || [this];
diff --git a/src/ui/public/vis/agg_configs.js b/src/ui/public/vis/agg_configs.js
index 3099a243d0feb..0797ceb8f4810 100644
--- a/src/ui/public/vis/agg_configs.js
+++ b/src/ui/public/vis/agg_configs.js
@@ -108,7 +108,6 @@ export default function AggConfigsFactory(Private) {
})
.value();
}
-
this.getRequestAggs()
.filter(function (config) {
return !config.type.hasNoDsl;
@@ -145,12 +144,17 @@ export default function AggConfigsFactory(Private) {
});
removeParentAggs(dslTopLvl);
-
return dslTopLvl;
};
AggConfigs.prototype.getRequestAggs = function () {
- return _.sortBy(this, function (agg) {
+ //collect all the aggregations
+ const aggregations = this.reduce((requestValuesAggs, agg) => {
+ const aggs = agg.getRequestAggs();
+ return aggs ? requestValuesAggs.concat(aggs) : requestValuesAggs;
+ }, []);
+ //move metrics to the end
+ return _.sortBy(aggregations, function (agg) {
return agg.schema.group === 'metrics' ? 1 : 0;
});
};
diff --git a/src/ui/public/vis_maps/__tests__/geohash_layer.js b/src/ui/public/vis_maps/__tests__/geohash_layer.js
new file mode 100644
index 0000000000000..9502107253aac
--- /dev/null
+++ b/src/ui/public/vis_maps/__tests__/geohash_layer.js
@@ -0,0 +1,494 @@
+import expect from 'expect.js';
+import KibanaMap from 'ui/vis_maps/kibana_map';
+import GeohashLayer from 'ui/vis_maps/geohash_layer';
+import sampleData from './geohash_sample_data';
+
+describe('kibana_map tests', function () {
+
+ let domNode;
+ let kibanaMap;
+
+
+ function setupDOM() {
+ domNode = document.createElement('div');
+ domNode.style.top = '0';
+ domNode.style.left = '0';
+ domNode.style.width = '512px';
+ domNode.style.height = '512px';
+ domNode.style.position = 'fixed';
+ domNode.style['pointer-events'] = 'none';
+ document.body.appendChild(domNode);
+ }
+
+ function teardownDOM() {
+ domNode.innerHTML = '';
+ document.body.removeChild(domNode);
+ }
+
+
+ describe('GeohashGridLayer', function () {
+
+ beforeEach(async function () {
+ setupDOM();
+ kibanaMap = new KibanaMap(domNode, {
+ minZoom: 1,
+ maxZoom: 10
+ });
+ kibanaMap.setZoomLevel(3);
+ kibanaMap.setCenter({
+ lon: -100,
+ lat: 40
+ });
+ });
+
+ afterEach(function () {
+ kibanaMap.destroy();
+ teardownDOM();
+ });
+
+ [
+ {
+ options: { 'mapType': 'Scaled Circle Markers' },
+ expected: `[
+ {
+ "fill": "#bd0026",
+ "d": "M343,263.8A19.2,19.2,0,1,1,342.9,263.8 z"
+ },
+ {
+ "fill": "#bd0026",
+ "d": "M343,225.03843394373595A18.961566056264047,18.961566056264047,0,1,1,342.9,225.03843394373595 z"
+ },
+ {
+ "fill": "#bd0026",
+ "d": "M283,264.19815701843777A17.80184298156226,17.80184298156226,0,1,1,282.9,264.19815701843777 z"
+ },
+ {
+ "fill": "#f03b20",
+ "d": "M405,224.2748797495895A16.72512025041049,16.72512025041049,0,1,1,404.9,224.2748797495895 z"
+ },
+ {
+ "fill": "#f03b20",
+ "d": "M285,223.50180417608374A16.498195823916255,16.498195823916255,0,1,1,284.9,223.50180417608374 z"
+ },
+ {
+ "fill": "#f03b20",
+ "d": "M343,299.1036928470748A15.896307152925205,15.896307152925205,0,1,1,342.9,299.1036928470748 z"
+ },
+ {
+ "fill": "#f03b20",
+ "d": "M283,300.2846189453604A15.71538105463958,15.71538105463958,0,1,1,282.9,300.2846189453604 z"
+ },
+ {
+ "fill": "#fd8d3c",
+ "d": "M148,267.0272116156895A13.972788384310489,13.972788384310489,0,1,1,147.9,267.0272116156895 z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M219,270.4178825645856A11.582117435414355,11.582117435414355,0,1,1,218.9,270.4178825645856 z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M146,189.63311915018554A11.366880849814459,11.366880849814459,0,1,1,145.9,189.63311915018554 z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M281,191.96973262756177A11.030267372438226,11.030267372438226,0,1,1,280.9,191.96973262756177 z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M220,231.85362974571228A10.146370254287714,10.146370254287714,0,1,1,219.9,231.85362974571228 z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M144,231.1923722152369A9.807627784763092,9.807627784763092,0,1,1,143.9,231.1923722152369 z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M387,268.27221854599287A9.72778145400714,9.72778145400714,0,1,1,386.9,268.27221854599287 z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M217,191.09542834646925A8.90457165353074,8.90457165353074,0,1,1,216.9,191.09542834646925 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M218,300.40744573968243A8.592554260317598,8.592554260317598,0,1,1,217.9,300.40744573968243 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M363,339.5411821762003A7.458817823799684,7.458817823799684,0,1,1,362.9,339.5411821762003 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M331,205.43072931381437A6.569270686185644,6.569270686185644,0,1,1,330.9,205.43072931381437 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M163,299.9012571034098A5.098742896590189,5.098742896590189,0,1,1,162.9,299.9012571034098 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M34,77.6735731867532A4.326426813246795,4.326426813246795,0,1,1,33.9,77.6735731867532 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M268,341.7954688958982A4.204531104101819,4.204531104101819,0,1,1,267.9,341.7954688958982 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M71,118.82649906983305A4.173500930166947,4.173500930166947,0,1,1,70.9,118.82649906983305 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M119,235.1169130974434A3.8830869025566206,3.8830869025566206,0,1,1,118.9,235.1169130974434 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M451,396.15053353027315A3.849466469726874,3.849466469726874,0,1,1,450.9,396.15053353027315 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M64,104.18445019554242A3.815549804457569,3.815549804457569,0,1,1,63.9,104.18445019554242 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M7,15.430879972386867A3.5691200276131325,3.5691200276131325,0,1,1,6.9,15.430879972386867 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M434,206.8985557756997A3.1014442243003013,3.1014442243003013,0,1,1,433.9,206.8985557756997 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M119,201.2073035006183A2.792696499381677,2.792696499381677,0,1,1,118.9,201.2073035006183 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M-1,420.89773444794906A2.1022655520509095,2.1022655520509095,0,1,1,-1.1,420.89773444794906 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M443,217.859886428343A1.1401135716569843,1.1401135716569843,0,1,1,442.9,217.859886428343 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M121,260.85988642834303A1.1401135716569843,1.1401135716569843,0,1,1,120.9,260.85988642834303 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M-4,399.27892886445886A0.7210711355411324,0.7210711355411324,0,1,1,-4.1,399.27892886445886 z"
+ }
+]`
+ },
+ {
+ options: { 'mapType': 'Shaded Circle Markers' },
+ expected: `[
+ {
+ "fill": "#bd0026",
+ "d": "M343,267A16,16,0,1,1,342.9,267 z"
+ },
+ {
+ "fill": "#bd0026",
+ "d": "M343,226A18,18,0,1,1,342.9,226 z"
+ },
+ {
+ "fill": "#bd0026",
+ "d": "M283,266A16,16,0,1,1,282.9,266 z"
+ },
+ {
+ "fill": "#f03b20",
+ "d": "M405,223A18,18,0,1,1,404.9,223 z"
+ },
+ {
+ "fill": "#f03b20",
+ "d": "M285,222A18,18,0,1,1,284.9,222 z"
+ },
+ {
+ "fill": "#f03b20",
+ "d": "M343,300A15,15,0,1,1,342.9,300 z"
+ },
+ {
+ "fill": "#f03b20",
+ "d": "M283,301A15,15,0,1,1,282.9,301 z"
+ },
+ {
+ "fill": "#fd8d3c",
+ "d": "M148,265A16,16,0,1,1,147.9,265 z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M219,266A16,16,0,1,1,218.9,266 z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M146,183A18,18,0,1,1,145.9,183 z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M281,184A19,19,0,1,1,280.9,184 z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M220,225A17,17,0,1,1,219.9,225 z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M144,224A17,17,0,1,1,143.9,224 z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M387,262A16,16,0,1,1,386.9,262 z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M217,181A19,19,0,1,1,216.9,181 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M218,293A16,16,0,1,1,217.9,293 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M363,333A14,14,0,1,1,362.9,333 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M331,194A18,18,0,1,1,330.9,194 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M163,290A15,15,0,1,1,162.9,290 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M34,56A26,26,0,1,1,33.9,56 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M268,332A14,14,0,1,1,267.9,332 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M71,100A23,23,0,1,1,70.9,100 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M119,222A17,17,0,1,1,118.9,222 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M451,387A13,13,0,1,1,450.9,387 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M64,84A24,24,0,1,1,63.9,84 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M7,-7A26,26,0,1,1,6.9,-7 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M434,192A18,18,0,1,1,433.9,192 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M119,185A19,19,0,1,1,118.9,185 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M-1,410A13,13,0,1,1,-1.1,410 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M443,201A18,18,0,1,1,442.9,201 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M121,245A17,17,0,1,1,120.9,245 z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M-4,386A14,14,0,1,1,-4.1,386 z"
+ }
+]`
+ },
+ {
+ options: { 'mapType': 'Shaded Geohash Grid' },
+ expected: `[
+ {
+ "fill": "#bd0026",
+ "d": "M313 301L313 261L377 261L377 301z"
+ },
+ {
+ "fill": "#bd0026",
+ "d": "M313 261L313 218L377 218L377 261z"
+ },
+ {
+ "fill": "#bd0026",
+ "d": "M249 301L249 261L313 261L313 301z"
+ },
+ {
+ "fill": "#f03b20",
+ "d": "M377 261L377 218L441 218L441 261z"
+ },
+ {
+ "fill": "#f03b20",
+ "d": "M249 261L249 218L313 218L313 261z"
+ },
+ {
+ "fill": "#f03b20",
+ "d": "M313 338L313 301L377 301L377 338z"
+ },
+ {
+ "fill": "#f03b20",
+ "d": "M249 338L249 301L313 301L313 338z"
+ },
+ {
+ "fill": "#fd8d3c",
+ "d": "M121 301L121 261L185 261L185 301z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M185 301L185 261L249 261L249 301z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M121 218L121 170L185 170L185 218z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M249 218L249 170L313 170L313 218z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M185 261L185 218L249 218L249 261z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M121 261L121 218L185 218L185 261z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M377 301L377 261L441 261L441 301z"
+ },
+ {
+ "fill": "#feb24c",
+ "d": "M185 218L185 170L249 170L249 218z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M185 338L185 301L249 301L249 338z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M313 374L313 338L377 338L377 374z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M313 218L313 170L377 170L377 218z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M121 338L121 301L185 301L185 338z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M-7 116L-7 54L57 54L57 116z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M249 374L249 338L313 338L313 374z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M57 170L57 116L121 116L121 170z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M57 261L57 218L121 218L121 261z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M441 408L441 374L505 374L505 408z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M57 116L57 54L121 54L121 116z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M-7 54L-7 -21L57 -21L57 54z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M377 218L377 170L441 170L441 218z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M57 218L57 170L121 170L121 218z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M-7 441L-7 408L57 408L57 441z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M441 261L441 218L505 218L505 261z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M57 301L57 261L121 261L121 301z"
+ },
+ {
+ "fill": "#fed976",
+ "d": "M-7 408L-7 374L57 374L57 408z"
+ }
+]`
+ }
+ ].forEach(function (test) {
+
+ it(test.options.mapType, function () {
+
+ const geohashGridOptions = test.options;
+ const geohashLayer = new GeohashLayer(sampleData, geohashGridOptions, kibanaMap.getZoomLevel(), kibanaMap);
+ kibanaMap.addLayer(geohashLayer);
+ const markersNodeList = domNode.querySelectorAll('path.leaflet-clickable');
+ const markerArray = [];
+ for (let i = 0; i < markersNodeList.length; i++) {
+ markerArray.push(markersNodeList[i]);
+ }
+
+ const expectedGeohashGridMarkers = test.expected;
+ const expectedMarkers = JSON.parse(expectedGeohashGridMarkers).map(path => {
+ return {
+ fill: path.fill,
+ coords: path.d.match(/[0-9\.]+/g).map(parseFloat)
+ };
+ });
+ const actualMarkers = markerArray.map(a => {
+ return {
+ fill: a.getAttribute('fill'),
+ coords: a.getAttribute('d').match(/[0-9\.]+/g).map(parseFloat)
+ };
+ });
+ expect(actualMarkers.length).to.equal(expectedMarkers.length);
+ for (let i = 0; i < expectedMarkers.length; i++) {
+ expect(actualMarkers[i].fill).to.equal(expectedMarkers[i].fill);
+ actualMarkers[i].coords.forEach((coord, c) => {
+ closeTo(actualMarkers[i].coords[c], expectedMarkers[i].coords[c]);
+ });
+ }
+ });
+ });
+
+
+ });
+
+});
+
+
+function closeTo(actual, expected) {
+ const epsilon = 1;//allow 2px slack
+ expect(actual - epsilon < expected && expected < actual + epsilon).to.equal(true);
+}
diff --git a/src/ui/public/vis_maps/__tests__/geohash_sample_data.js b/src/ui/public/vis_maps/__tests__/geohash_sample_data.js
new file mode 100644
index 0000000000000..d814298d131aa
--- /dev/null
+++ b/src/ui/public/vis_maps/__tests__/geohash_sample_data.js
@@ -0,0 +1,1341 @@
+const sampleData = `{
+ "type": "FeatureCollection",
+ "features": [{
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-84.81215765699744, 36.289477944374084]},
+ "properties": {
+ "geohash": "dn",
+ "value": 1418,
+ "aggConfigResult": {
+ "key": 1418,
+ "value": 1418,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "dn",
+ "value": "dn",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 4,
+ "type": "bucket"
+ },
+ "$order": 5,
+ "type": "metric"
+ },
+ "center": [36.5625, -84.375],
+ "rectangle": [[33.75, -90], [33.75, -78.75], [39.375, -78.75], [39.375, -90]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-84.8004243336618, 41.63311270996928]},
+ "properties": {
+ "geohash": "dp",
+ "value": 1383,
+ "aggConfigResult": {
+ "key": 1383,
+ "value": 1383,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "dp",
+ "value": "dp",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 7,
+ "type": "bucket"
+ },
+ "$order": 8,
+ "type": "metric"
+ },
+ "center": [42.1875, -84.375],
+ "rectangle": [[39.375, -90], [39.375, -78.75], [45, -78.75], [45, -90]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-95.20564651116729, 36.4947619009763]},
+ "properties": {
+ "geohash": "9y",
+ "value": 1219,
+ "aggConfigResult": {
+ "key": 1219,
+ "value": 1219,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "9y",
+ "value": "9y",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 10,
+ "type": "bucket"
+ },
+ "$order": 11,
+ "type": "metric"
+ },
+ "center": [36.5625, -95.625],
+ "rectangle": [[33.75, -101.25], [33.75, -90], [39.375, -90], [39.375, -101.25]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-73.8917376101017, 42.086046701297164]},
+ "properties": {
+ "geohash": "dr",
+ "value": 1076,
+ "aggConfigResult": {
+ "key": 1076,
+ "value": 1076,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "dr",
+ "value": "dr",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 13,
+ "type": "bucket"
+ },
+ "$order": 14,
+ "type": "metric"
+ },
+ "center": [42.1875, -73.125],
+ "rectangle": [[39.375, -78.75], [39.375, -67.5], [45, -67.5], [45, -78.75]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-94.9999794177711, 42.19089978374541]},
+ "properties": {
+ "geohash": "9z",
+ "value": 1047,
+ "aggConfigResult": {
+ "key": 1047,
+ "value": 1047,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "9z",
+ "value": "9z",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 16,
+ "type": "bucket"
+ },
+ "$order": 17,
+ "type": "metric"
+ },
+ "center": [42.1875, -95.625],
+ "rectangle": [[39.375, -101.25], [39.375, -90], [45, -90], [45, -101.25]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-84.72070790827274, 31.68308235704899]},
+ "properties": {
+ "geohash": "dj",
+ "value": 972,
+ "aggConfigResult": {
+ "key": 972,
+ "value": 972,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "dj",
+ "value": "dj",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 19,
+ "type": "bucket"
+ },
+ "$order": 20,
+ "type": "metric"
+ },
+ "center": [30.9375, -84.375],
+ "rectangle": [[28.125, -90], [28.125, -78.75], [33.75, -78.75], [33.75, -90]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-95.22422080859542, 31.44715240225196]},
+ "properties": {
+ "geohash": "9v",
+ "value": 950,
+ "aggConfigResult": {
+ "key": 950,
+ "value": 950,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "9v",
+ "value": "9v",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 22,
+ "type": "bucket"
+ },
+ "$order": 23,
+ "type": "metric"
+ },
+ "center": [30.9375, -95.625],
+ "rectangle": [[28.125, -101.25], [28.125, -90], [33.75, -90], [33.75, -101.25]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-119.02438038960099, 36.617594081908464]},
+ "properties": {
+ "geohash": "9q",
+ "value": 751,
+ "aggConfigResult": {
+ "key": 751,
+ "value": 751,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "9q",
+ "value": "9q",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 25,
+ "type": "bucket"
+ },
+ "$order": 26,
+ "type": "metric"
+ },
+ "center": [36.5625, -118.125],
+ "rectangle": [[33.75, -123.75], [33.75, -112.5], [39.375, -112.5], [39.375, -123.75]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-106.54198246076703, 36.47509602829814]},
+ "properties": {
+ "geohash": "9w",
+ "value": 516,
+ "aggConfigResult": {
+ "key": 516,
+ "value": 516,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "9w",
+ "value": "9w",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 28,
+ "type": "bucket"
+ },
+ "$order": 29,
+ "type": "metric"
+ },
+ "center": [36.5625, -106.875],
+ "rectangle": [[33.75, -112.5], [33.75, -101.25], [39.375, -101.25], [39.375, -112.5]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-119.28373273462057, 47.07595920190215]},
+ "properties": {
+ "geohash": "c2",
+ "value": 497,
+ "aggConfigResult": {
+ "key": 497,
+ "value": 497,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "c2",
+ "value": "c2",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 31,
+ "type": "bucket"
+ },
+ "$order": 32,
+ "type": "metric"
+ },
+ "center": [47.8125, -118.125],
+ "rectangle": [[45, -123.75], [45, -112.5], [50.625, -112.5], [50.625, -123.75]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-95.67718841135502, 46.75232579000294]},
+ "properties": {
+ "geohash": "cb",
+ "value": 468,
+ "aggConfigResult": {
+ "key": 468,
+ "value": 468,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "cb",
+ "value": "cb",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 34,
+ "type": "bucket"
+ },
+ "$order": 35,
+ "type": "metric"
+ },
+ "center": [47.8125, -95.625],
+ "rectangle": [[45, -101.25], [45, -90], [50.625, -90], [50.625, -101.25]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-106.2923239544034, 41.907251570373774]},
+ "properties": {
+ "geohash": "9x",
+ "value": 396,
+ "aggConfigResult": {
+ "key": 396,
+ "value": 396,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "9x",
+ "value": "9x",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 37,
+ "type": "bucket"
+ },
+ "$order": 38,
+ "type": "metric"
+ },
+ "center": [42.1875, -106.875],
+ "rectangle": [[39.375, -112.5], [39.375, -101.25], [45, -101.25], [45, -112.5]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-119.63544443249702, 42.04197423532605]},
+ "properties": {
+ "geohash": "9r",
+ "value": 370,
+ "aggConfigResult": {
+ "key": 370,
+ "value": 370,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "9r",
+ "value": "9r",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 40,
+ "type": "bucket"
+ },
+ "$order": 41,
+ "type": "metric"
+ },
+ "center": [42.1875, -118.125],
+ "rectangle": [[39.375, -123.75], [39.375, -112.5], [45, -112.5], [45, -123.75]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-76.97201896458864, 37.06826982088387]},
+ "properties": {
+ "geohash": "dq",
+ "value": 364,
+ "aggConfigResult": {
+ "key": 364,
+ "value": 364,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "dq",
+ "value": "dq",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 43,
+ "type": "bucket"
+ },
+ "$order": 44,
+ "type": "metric"
+ },
+ "center": [36.5625, -73.125],
+ "rectangle": [[33.75, -78.75], [33.75, -67.5], [39.375, -67.5], [39.375, -78.75]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-106.92424703389406, 47.192871160805225]},
+ "properties": {
+ "geohash": "c8",
+ "value": 305,
+ "aggConfigResult": {
+ "key": 305,
+ "value": 305,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "c8",
+ "value": "c8",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 46,
+ "type": "bucket"
+ },
+ "$order": 47,
+ "type": "metric"
+ },
+ "center": [47.8125, -106.875],
+ "rectangle": [[45, -112.5], [45, -101.25], [50.625, -101.25], [50.625, -112.5]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-106.78505832329392, 32.50790253281593]},
+ "properties": {
+ "geohash": "9t",
+ "value": 284,
+ "aggConfigResult": {
+ "key": 284,
+ "value": 284,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "9t",
+ "value": "9t",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 49,
+ "type": "bucket"
+ },
+ "$order": 50,
+ "type": "metric"
+ },
+ "center": [30.9375, -106.875],
+ "rectangle": [[28.125, -112.5], [28.125, -101.25], [33.75, -101.25], [33.75, -112.5]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-152.9292524792254, 59.777277521789074]},
+ "properties": {
+ "geohash": "bd",
+ "value": 217,
+ "aggConfigResult": {
+ "key": 217,
+ "value": 217,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "bd",
+ "value": "bd",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 52,
+ "type": "bucket"
+ },
+ "$order": 53,
+ "type": "metric"
+ },
+ "center": [59.0625, -151.875],
+ "rectangle": [[56.25, -157.5], [56.25, -146.25], [61.875, -146.25], [61.875, -157.5]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-81.13159038126469, 26.815882762894034]},
+ "properties": {
+ "geohash": "dh",
+ "value": 214,
+ "aggConfigResult": {
+ "key": 214,
+ "value": 214,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "dh",
+ "value": "dh",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 55,
+ "type": "bucket"
+ },
+ "$order": 56,
+ "type": "metric"
+ },
+ "center": [25.3125, -84.375],
+ "rectangle": [[22.5, -90], [22.5, -78.75], [28.125, -78.75], [28.125, -90]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-162.1049272455275, 64.38826035708189]},
+ "properties": {
+ "geohash": "b7",
+ "value": 194,
+ "aggConfigResult": {
+ "key": 194,
+ "value": 194,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "b7",
+ "value": "b7",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 58,
+ "type": "bucket"
+ },
+ "$order": 59,
+ "type": "metric"
+ },
+ "center": [64.6875, -163.125],
+ "rectangle": [[61.875, -168.75], [61.875, -157.5], [67.5, -157.5], [67.5, -168.75]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-161.59194018691778, 60.06503529846668]},
+ "properties": {
+ "geohash": "b6",
+ "value": 168,
+ "aggConfigResult": {
+ "key": 168,
+ "value": 168,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "b6",
+ "value": "b6",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 61,
+ "type": "bucket"
+ },
+ "$order": 62,
+ "type": "metric"
+ },
+ "center": [59.0625, -163.125],
+ "rectangle": [[56.25, -168.75], [56.25, -157.5], [61.875, -157.5], [61.875, -168.75]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-86.82362716645002, 45.665992330759764]},
+ "properties": {
+ "geohash": "f0",
+ "value": 166,
+ "aggConfigResult": {
+ "key": 166,
+ "value": 166,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "f0",
+ "value": "f0",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 64,
+ "type": "bucket"
+ },
+ "$order": 65,
+ "type": "metric"
+ },
+ "center": [47.8125, -84.375],
+ "rectangle": [[45, -90], [45, -78.75], [50.625, -78.75], [50.625, -90]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-152.04110082238913, 65.17680524848402]},
+ "properties": {
+ "geohash": "be",
+ "value": 158,
+ "aggConfigResult": {
+ "key": 158,
+ "value": 158,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "be",
+ "value": "be",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 67,
+ "type": "bucket"
+ },
+ "$order": 68,
+ "type": "metric"
+ },
+ "center": [64.6875, -151.875],
+ "rectangle": [[61.875, -157.5], [61.875, -146.25], [67.5, -146.25], [67.5, -157.5]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-116.37748382985592, 33.16976627334952]},
+ "properties": {
+ "geohash": "9m",
+ "value": 100,
+ "aggConfigResult": {
+ "key": 100,
+ "value": 100,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "9m",
+ "value": "9m",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 70,
+ "type": "bucket"
+ },
+ "$order": 71,
+ "type": "metric"
+ },
+ "center": [30.9375, -118.125],
+ "rectangle": [[28.125, -123.75], [28.125, -112.5], [33.75, -112.5], [33.75, -123.75]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-139.12713261321187, 59.41271326504648]},
+ "properties": {
+ "geohash": "bf",
+ "value": 72,
+ "aggConfigResult": {
+ "key": 72,
+ "value": 72,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "bf",
+ "value": "bf",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 73,
+ "type": "bucket"
+ },
+ "$order": 74,
+ "type": "metric"
+ },
+ "center": [59.0625, -140.625],
+ "rectangle": [[56.25, -146.25], [56.25, -135], [61.875, -135], [61.875, -146.25]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-97.89513738825917, 26.928304536268115]},
+ "properties": {
+ "geohash": "9u",
+ "value": 68,
+ "aggConfigResult": {
+ "key": 68,
+ "value": 68,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "9u",
+ "value": "9u",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 76,
+ "type": "bucket"
+ },
+ "$order": 77,
+ "type": "metric"
+ },
+ "center": [25.3125, -95.625],
+ "rectangle": [[22.5, -101.25], [22.5, -90], [28.125, -90], [28.125, -101.25]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-132.52599561586976, 55.60743710026145]},
+ "properties": {
+ "geohash": "c1",
+ "value": 67,
+ "aggConfigResult": {
+ "key": 67,
+ "value": 67,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "c1",
+ "value": "c1",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 79,
+ "type": "bucket"
+ },
+ "$order": 80,
+ "type": "metric"
+ },
+ "center": [53.4375, -129.375],
+ "rectangle": [[50.625, -135], [50.625, -123.75], [56.25, -123.75], [56.25, -135]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-124.13590382784605, 42.24034773185849]},
+ "properties": {
+ "geohash": "9p",
+ "value": 58,
+ "aggConfigResult": {
+ "key": 58,
+ "value": 58,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "9p",
+ "value": "9p",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 82,
+ "type": "bucket"
+ },
+ "$order": 83,
+ "type": "metric"
+ },
+ "center": [42.1875, -129.375],
+ "rectangle": [[39.375, -135], [39.375, -123.75], [45, -123.75], [45, -135]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-65.72741221636534, 18.170374436303973]},
+ "properties": {
+ "geohash": "de",
+ "value": 57,
+ "aggConfigResult": {
+ "key": 57,
+ "value": 57,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "de",
+ "value": "de",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 85,
+ "type": "bucket"
+ },
+ "$order": 86,
+ "type": "metric"
+ },
+ "center": [19.6875, -61.875],
+ "rectangle": [[16.875, -67.5], [16.875, -56.25], [22.5, -56.25], [22.5, -67.5]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-133.79055473953485, 57.08371731452644]},
+ "properties": {
+ "geohash": "c4",
+ "value": 56,
+ "aggConfigResult": {
+ "key": 56,
+ "value": 56,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "c4",
+ "value": "c4",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 88,
+ "type": "bucket"
+ },
+ "$order": 89,
+ "type": "metric"
+ },
+ "center": [59.0625, -129.375],
+ "rectangle": [[56.25, -135], [56.25, -123.75], [61.875, -123.75], [61.875, -135]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-152.2658603824675, 69.64116730727255]},
+ "properties": {
+ "geohash": "bs",
+ "value": 51,
+ "aggConfigResult": {
+ "key": 51,
+ "value": 51,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "bs",
+ "value": "bs",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 91,
+ "type": "bucket"
+ },
+ "$order": 92,
+ "type": "metric"
+ },
+ "center": [70.3125, -151.875],
+ "rectangle": [[67.5, -157.5], [67.5, -146.25], [73.125, -146.25], [73.125, -157.5]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-143.8043469004333, 64.64996575377882]},
+ "properties": {
+ "geohash": "bg",
+ "value": 49,
+ "aggConfigResult": {
+ "key": 49,
+ "value": 49,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "bg",
+ "value": "bg",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 94,
+ "type": "bucket"
+ },
+ "$order": 95,
+ "type": "metric"
+ },
+ "center": [64.6875, -140.625],
+ "rectangle": [[61.875, -146.25], [61.875, -135], [67.5, -135], [67.5, -146.25]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-162.65227859839797, 54.967785738408566]},
+ "properties": {
+ "geohash": "b3",
+ "value": 43,
+ "aggConfigResult": {
+ "key": 43,
+ "value": 43,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "b3",
+ "value": "b3",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 97,
+ "type": "bucket"
+ },
+ "$order": 98,
+ "type": "metric"
+ },
+ "center": [53.4375, -163.125],
+ "rectangle": [[50.625, -168.75], [50.625, -157.5], [56.25, -157.5], [56.25, -168.75]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-156.20294423773885, 20.63592097721994]},
+ "properties": {
+ "geohash": "8e",
+ "value": 40,
+ "aggConfigResult": {
+ "key": 40,
+ "value": 40,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "8e",
+ "value": "8e",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 100,
+ "type": "bucket"
+ },
+ "$order": 101,
+ "type": "metric"
+ },
+ "center": [19.6875, -151.875],
+ "rectangle": [[16.875, -157.5], [16.875, -146.25], [22.5, -146.25], [22.5, -157.5]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-68.71966263279319, 45.89407338760793]},
+ "properties": {
+ "geohash": "f2",
+ "value": 37,
+ "aggConfigResult": {
+ "key": 37,
+ "value": 37,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "f2",
+ "value": "f2",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 103,
+ "type": "bucket"
+ },
+ "$order": 104,
+ "type": "metric"
+ },
+ "center": [47.8125, -73.125],
+ "rectangle": [[45, -78.75], [45, -67.5], [50.625, -67.5], [50.625, -78.75]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-159.04649432748556, 21.810192000120878]},
+ "properties": {
+ "geohash": "87",
+ "value": 31,
+ "aggConfigResult": {
+ "key": 31,
+ "value": 31,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "87",
+ "value": "87",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 106,
+ "type": "bucket"
+ },
+ "$order": 107,
+ "type": "metric"
+ },
+ "center": [19.6875, -163.125],
+ "rectangle": [[16.875, -168.75], [16.875, -157.5], [22.5, -157.5], [22.5, -168.75]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-124.07574724406004, 46.70505428686738]},
+ "properties": {
+ "geohash": "c0",
+ "value": 30,
+ "aggConfigResult": {
+ "key": 30,
+ "value": 30,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "c0",
+ "value": "c0",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 109,
+ "type": "bucket"
+ },
+ "$order": 110,
+ "type": "metric"
+ },
+ "center": [47.8125, -129.375],
+ "rectangle": [[45, -135], [45, -123.75], [50.625, -123.75], [50.625, -135]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-170.66843625158072, 64.42178352735937]},
+ "properties": {
+ "geohash": "b5",
+ "value": 18,
+ "aggConfigResult": {
+ "key": 18,
+ "value": 18,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "b5",
+ "value": "b5",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 112,
+ "type": "bucket"
+ },
+ "$order": 113,
+ "type": "metric"
+ },
+ "center": [64.6875, -174.375],
+ "rectangle": [[61.875, -180], [61.875, -168.75], [67.5, -168.75], [67.5, -180]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-164.1237143240869, 68.94954898394644]},
+ "properties": {
+ "geohash": "bk",
+ "value": 17,
+ "aggConfigResult": {
+ "key": 17,
+ "value": 17,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "bk",
+ "value": "bk",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 115,
+ "type": "bucket"
+ },
+ "$order": 116,
+ "type": "metric"
+ },
+ "center": [70.3125, -163.125],
+ "rectangle": [[67.5, -168.75], [67.5, -157.5], [73.125, -157.5], [73.125, -168.75]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-145.23947272449732, 14.257271960377693]},
+ "properties": {
+ "geohash": "8f",
+ "value": 17,
+ "aggConfigResult": {
+ "key": 17,
+ "value": 17,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "8f",
+ "value": "8f",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 118,
+ "type": "bucket"
+ },
+ "$order": 119,
+ "type": "metric"
+ },
+ "center": [14.0625, -140.625],
+ "rectangle": [[11.25, -146.25], [11.25, -135], [16.875, -135], [16.875, -146.25]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-169.90729674696922, 56.83546897955239]},
+ "properties": {
+ "geohash": "b4",
+ "value": 16,
+ "aggConfigResult": {
+ "key": 16,
+ "value": 16,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "b4",
+ "value": "b4",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 121,
+ "type": "bucket"
+ },
+ "$order": 122,
+ "type": "metric"
+ },
+ "center": [59.0625, -174.375],
+ "rectangle": [[56.25, -180], [56.25, -168.75], [61.875, -168.75], [61.875, -180]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-170.12874579057097, 14.265542635694146]},
+ "properties": {
+ "geohash": "84",
+ "value": 12,
+ "aggConfigResult": {
+ "key": 12,
+ "value": 12,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "84",
+ "value": "84",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 124,
+ "type": "bucket"
+ },
+ "$order": 125,
+ "type": "metric"
+ },
+ "center": [14.0625, -174.375],
+ "rectangle": [[11.25, -180], [11.25, -168.75], [16.875, -168.75], [16.875, -180]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-144.66744488105178, 69.03327229432762]},
+ "properties": {
+ "geohash": "bu",
+ "value": 11,
+ "aggConfigResult": {
+ "key": 11,
+ "value": 11,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "bu",
+ "value": "bu",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 127,
+ "type": "bucket"
+ },
+ "$order": 128,
+ "type": "metric"
+ },
+ "center": [70.3125, -140.625],
+ "rectangle": [[67.5, -146.25], [67.5, -135], [73.125, -135], [73.125, -146.25]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-67.10587805137038, 44.86871098168194]},
+ "properties": {
+ "geohash": "dx",
+ "value": 5,
+ "aggConfigResult": {
+ "key": 5,
+ "value": 5,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "dx",
+ "value": "dx",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 130,
+ "type": "bucket"
+ },
+ "$order": 131,
+ "type": "metric"
+ },
+ "center": [42.1875, -61.875],
+ "rectangle": [[39.375, -67.5], [39.375, -56.25], [45, -56.25], [45, -67.5]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-174.69428664073348, 52.15187128633261]},
+ "properties": {
+ "geohash": "b1",
+ "value": 5,
+ "aggConfigResult": {
+ "key": 5,
+ "value": 5,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "b1",
+ "value": "b1",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 133,
+ "type": "bucket"
+ },
+ "$order": 134,
+ "type": "metric"
+ },
+ "center": [53.4375, -174.375],
+ "rectangle": [[50.625, -180], [50.625, -168.75], [56.25, -168.75], [56.25, -180]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-123.75373480841517, 39.26203776150942]},
+ "properties": {
+ "geohash": "9n",
+ "value": 5,
+ "aggConfigResult": {
+ "key": 5,
+ "value": 5,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "9n",
+ "value": "9n",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 136,
+ "type": "bucket"
+ },
+ "$order": 137,
+ "type": "metric"
+ },
+ "center": [36.5625, -129.375],
+ "rectangle": [[33.75, -135], [33.75, -123.75], [39.375, -123.75], [39.375, -135]]
+ }
+ }, {
+ "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [-145.7686112076044, 18.124444372951984]},
+ "properties": {
+ "geohash": "8g",
+ "value": 2,
+ "aggConfigResult": {
+ "key": 2,
+ "value": 2,
+ "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}},
+ "$parent": {
+ "key": "8g",
+ "value": "8g",
+ "aggConfig": {
+ "id": "2",
+ "enabled": true,
+ "type": "geohash_grid",
+ "schema": "segment",
+ "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2}
+ },
+ "$order": 139,
+ "type": "bucket"
+ },
+ "$order": 140,
+ "type": "metric"
+ },
+ "center": [19.6875, -140.625],
+ "rectangle": [[16.875, -146.25], [16.875, -135], [22.5, -135], [22.5, -146.25]]
+ }
+ }],
+ "properties": {"min": 2, "max": 1418, "zoom": 3, "center": [39.57182223734374, -109.51171875]}
+}`;
+
+export default JSON.parse(sampleData);
diff --git a/src/ui/public/vis_maps/__tests__/kibana_map.js b/src/ui/public/vis_maps/__tests__/kibana_map.js
new file mode 100644
index 0000000000000..72f9401a7e538
--- /dev/null
+++ b/src/ui/public/vis_maps/__tests__/kibana_map.js
@@ -0,0 +1,137 @@
+import expect from 'expect.js';
+import KibanaMap from 'ui/vis_maps/kibana_map';
+
+describe('kibana_map tests', function () {
+
+ let domNode;
+ let kibanaMap;
+
+ function setupDOM() {
+ domNode = document.createElement('div');
+ domNode.style.top = '0';
+ domNode.style.left = '0';
+ domNode.style.width = '512px';
+ domNode.style.height = '512px';
+ domNode.style.position = 'fixed';
+ domNode.style['pointer-events'] = 'none';
+ document.body.appendChild(domNode);
+ }
+
+ function teardownDOM() {
+ domNode.innerHTML = '';
+ document.body.removeChild(domNode);
+ }
+
+
+ describe('KibanaMap - basics', function () {
+
+ beforeEach(async function () {
+ setupDOM();
+ kibanaMap = new KibanaMap(domNode, {
+ minZoom: 1,
+ maxZoom: 10
+ });
+ });
+
+ afterEach(function () {
+ kibanaMap.destroy();
+ teardownDOM();
+ });
+
+ it('should instantiate with world in view', function () {
+ const bounds = kibanaMap.getBounds();
+ expect(bounds.bottom_right.lon).to.equal(180);
+ expect(bounds.top_left.lon).to.equal(-180);
+ expect(kibanaMap.getCenter().lon).to.equal(0);
+ expect(kibanaMap.getCenter().lat).to.equal(0);
+ expect(kibanaMap.getZoomLevel()).to.equal(1);
+ });
+
+ it('should resize to fit container', function () {
+
+ kibanaMap.setZoomLevel(2);
+ expect(kibanaMap.getCenter().lon).to.equal(0);
+ expect(kibanaMap.getCenter().lat).to.equal(0);
+
+ domNode.style.width = '1024px';
+ domNode.style.height = '1024px';
+ kibanaMap.resize();
+
+ expect(kibanaMap.getCenter().lon).to.equal(0);
+ expect(kibanaMap.getCenter().lat).to.equal(0);
+ const bounds = kibanaMap.getBounds();
+ expect(bounds.bottom_right.lon).to.equal(180);
+ expect(bounds.top_left.lon).to.equal(-180);
+
+ });
+
+ });
+
+
+ describe('KibanaMap - baseLayer', function () {
+
+ beforeEach(async function () {
+ setupDOM();
+ kibanaMap = new KibanaMap(domNode, {
+ minZoom: 1,
+ maxZoom: 10
+ });
+ });
+
+ afterEach(function () {
+ kibanaMap.destroy();
+ teardownDOM();
+ });
+
+
+ it('TMS', async function () {
+
+ const options = {
+ 'url': 'https://tiles-stage.elastic.co/v2/default/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana',
+ 'minZoom': 0,
+ 'maxZoom': 12,
+ 'attribution': '© [Elastic Tile Service](https://www.elastic.co/elastic-tile-service)'
+ };
+
+
+ return new Promise(function (resolve) {
+ kibanaMap.on('baseLayer:loaded', () => {
+ resolve();
+ });
+ kibanaMap.setBaseLayer({
+ baseLayerType: 'tms',
+ options: options
+ });
+ });
+ });
+
+ it('WMS', async function () {
+
+ const options = {
+ url: 'https://basemap.nationalmap.gov/arcgis/services/USGSTopo/ MapServer/WMSServer',
+ version: '1.3.0',
+ layers: '0',
+ format: 'image/png',
+ transparent: true,
+ attribution: 'Maps provided by USGS',
+ styles: '',
+ minZoom: 1,
+ maxZoom: 18
+ };
+
+
+ return new Promise(function (resolve) {
+ kibanaMap.on('baseLayer:loaded', () => {
+ resolve();
+ });
+ kibanaMap.setBaseLayer({
+ baseLayerType: 'wms',
+ options: options
+ });
+ });
+ });
+
+ });
+
+
+});
diff --git a/src/ui/public/vis_maps/__tests__/tile_maps/map.js b/src/ui/public/vis_maps/__tests__/tile_maps/map.js
deleted file mode 100644
index b12634fca1327..0000000000000
--- a/src/ui/public/vis_maps/__tests__/tile_maps/map.js
+++ /dev/null
@@ -1,215 +0,0 @@
-import expect from 'expect.js';
-import ngMock from 'ng_mock';
-import _ from 'lodash';
-import L from 'leaflet';
-
-import sinon from 'auto-release-sinon';
-import geoJsonData from 'fixtures/vislib/mock_data/geohash/_geo_json';
-import $ from 'jquery';
-import VislibVisualizationsMapProvider from 'ui/vis_maps/visualizations/_map';
-
-// // Data
-// const dataArray = [
-// ['geojson', require('fixtures/vislib/mock_data/geohash/_geo_json')],
-// ['columns', require('fixtures/vislib/mock_data/geohash/_columns')],
-// ['rows', require('fixtures/vislib/mock_data/geohash/_rows')],
-// ];
-
-// TODO: Test the specific behavior of each these
-// const mapTypes = [
-// 'Scaled Circle Markers',
-// 'Shaded Circle Markers',
-// 'Shaded Geohash Grid',
-// 'Heatmap'
-// ];
-
-describe('tilemaptest - TileMap Map Tests', function () {
- const $mockMapEl = $('
');
- let TileMapMap;
- let tilemapSettings;
- const leafletStubs = {};
- const leafletMocks = {};
-
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private, $injector) {
- // mock parts of leaflet
- leafletMocks.tileLayer = { on: sinon.stub() };
- leafletMocks.map = { on: sinon.stub() };
- leafletStubs.tileLayer = sinon.stub(L, 'tileLayer', _.constant(leafletMocks.tileLayer));
- leafletStubs.tileLayer.wms = sinon.stub(L.tileLayer, 'wms', _.constant(leafletMocks.tileLayer));
-
- leafletStubs.map = sinon.stub(L, 'map', _.constant(leafletMocks.map));
-
- TileMapMap = Private(VislibVisualizationsMapProvider);
-
- tilemapSettings = $injector.get('tilemapSettings');
-
- }));
-
- async function loadTileMapSettings() {
- await tilemapSettings.loadSettings();
- }
-
- describe('instantiation', function () {
- let createStub;
-
- beforeEach(loadTileMapSettings);
-
- beforeEach(async function () {
- createStub = sinon.stub(TileMapMap.prototype, '_createMap', _.noop);
- new TileMapMap($mockMapEl, geoJsonData, {});
- });
-
- it('should create the map', function () {
- expect(createStub.callCount).to.equal(1);
- });
- });
-
- describe('createMap', function () {
- let map;
- let mapStubs;
-
- beforeEach(loadTileMapSettings);
-
- beforeEach(function () {
- mapStubs = {
- destroy: sinon.stub(TileMapMap.prototype, 'destroy'),
- attachEvents: sinon.stub(TileMapMap.prototype, '_attachEvents'),
- addMarkers: sinon.stub(TileMapMap.prototype, '_addMarkers'),
- };
- map = new TileMapMap($mockMapEl, geoJsonData, {});
- });
-
- it('should create leaflet objects for tileLayer and map', function () {
- expect(leafletStubs.tileLayer.callCount).to.equal(1);
- expect(leafletStubs.map.callCount).to.equal(1);
-
- const callArgs = leafletStubs.map.firstCall.args;
- const mapOptions = callArgs[1];
- expect(callArgs[0]).to.be($mockMapEl.get(0));
- expect(mapOptions).to.have.property('zoom');
- expect(mapOptions).to.have.property('center');
- });
-
- it('should attach events and add markers', function () {
- expect(mapStubs.attachEvents.callCount).to.equal(1);
- expect(mapStubs.addMarkers.callCount).to.equal(1);
- });
-
- it('should call destroy only if a map exists', function () {
- expect(mapStubs.destroy.callCount).to.equal(0);
- map._createMap();
- expect(mapStubs.destroy.callCount).to.equal(1);
- });
-
- it('should create a WMS layer if WMS is enabled', function () {
- expect(L.tileLayer.wms.called).to.be(false);
- map = new TileMapMap($mockMapEl, geoJsonData, { attr: { wms: { enabled: true } } });
- map._createMap();
- expect(L.tileLayer.wms.called).to.be(true);
- });
-
- it('should create layer with all options from `tilemapSettings.getOptions()`', () => {
- sinon.assert.calledOnce(L.tileLayer);
-
- const leafletOptions = tilemapSettings.getTMSOptions();
- expect(L.tileLayer.firstCall.args[1]).to.eql(leafletOptions);
- });
- });
-
- describe('attachEvents', function () {
- beforeEach(loadTileMapSettings);
-
- beforeEach(function () {
- sinon.stub(TileMapMap.prototype, '_createMap', function () {
- this._tileLayer = leafletMocks.tileLayer;
- this.map = leafletMocks.map;
- this._attachEvents();
- });
- new TileMapMap($mockMapEl, geoJsonData, {});
- });
-
- it('should attach interaction events', function () {
- const expectedTileEvents = ['tileload'];
- const expectedMapEvents = ['draw:created', 'moveend', 'zoomend', 'unload'];
- const matchedEvents = {
- tiles: 0,
- maps: 0,
- };
-
- _.times(leafletMocks.tileLayer.on.callCount, function (index) {
- const ev = leafletMocks.tileLayer.on.getCall(index).args[0];
- if (_.includes(expectedTileEvents, ev)) matchedEvents.tiles++;
- });
- expect(matchedEvents.tiles).to.equal(expectedTileEvents.length);
-
- _.times(leafletMocks.map.on.callCount, function (index) {
- const ev = leafletMocks.map.on.getCall(index).args[0];
- if (_.includes(expectedMapEvents, ev)) matchedEvents.maps++;
- });
- expect(matchedEvents.maps).to.equal(expectedMapEvents.length);
- });
- });
-
-
- describe('addMarkers', function () {
- let map;
- let createStub;
-
- beforeEach(loadTileMapSettings);
-
- beforeEach(function () {
- sinon.stub(TileMapMap.prototype, '_createMap');
- createStub = sinon.stub(TileMapMap.prototype, '_createMarkers', _.constant({ addLegend: _.noop }));
- map = new TileMapMap($mockMapEl, geoJsonData, {});
- });
- it('should pass the map options to the marker', function () {
- map._addMarkers();
-
- const args = createStub.firstCall.args[0];
- expect(args).to.have.property('tooltipFormatter');
- expect(args).to.have.property('valueFormatter');
- expect(args).to.have.property('attr');
- });
-
- it('should destroy existing markers', function () {
- const destroyStub = sinon.stub();
- map._markers = { destroy: destroyStub };
- map._addMarkers();
-
- expect(destroyStub.callCount).to.be(1);
- });
- });
-
- describe('getDataRectangles', function () {
- let map;
-
- beforeEach(loadTileMapSettings);
-
- beforeEach(function () {
- sinon.stub(TileMapMap.prototype, '_createMap');
- map = new TileMapMap($mockMapEl, geoJsonData, {});
- });
-
- it('should return an empty array if no data', function () {
- map = new TileMapMap($mockMapEl, {}, {});
- const rects = map._getDataRectangles();
- expect(rects).to.have.length(0);
- });
-
- it('should return an array of arrays of rectangles', function () {
- const rects = map._getDataRectangles();
- _.times(5, function () {
- const index = _.random(rects.length - 1);
- const rect = rects[index];
- const featureRect = geoJsonData.geoJson.features[index].properties.rectangle;
- expect(rect.length).to.equal(featureRect.length);
-
- // should swap the array
- const checkIndex = _.random(rect.length - 1);
- expect(rect[checkIndex]).to.eql(featureRect[checkIndex]);
- });
- });
- });
-});
diff --git a/src/ui/public/vis_maps/__tests__/tile_maps/markers.js b/src/ui/public/vis_maps/__tests__/tile_maps/markers.js
deleted file mode 100644
index 1422bd1a41918..0000000000000
--- a/src/ui/public/vis_maps/__tests__/tile_maps/markers.js
+++ /dev/null
@@ -1,362 +0,0 @@
-
-import angular from 'angular';
-import expect from 'expect.js';
-import ngMock from 'ng_mock';
-import _ from 'lodash';
-import L from 'leaflet';
-import sinon from 'auto-release-sinon';
-import geoJsonData from 'fixtures/vislib/mock_data/geohash/_geo_json';
-import VislibVisualizationsMarkerTypesBaseMarkerProvider from 'ui/vis_maps/visualizations/marker_types/base_marker';
-import VislibVisualizationsMarkerTypesShadedCirclesProvider from 'ui/vis_maps/visualizations/marker_types/shaded_circles';
-import VislibVisualizationsMarkerTypesScaledCirclesProvider from 'ui/vis_maps/visualizations/marker_types/scaled_circles';
-import VislibVisualizationsMarkerTypesHeatmapProvider from 'ui/vis_maps/visualizations/marker_types/heatmap';
-// defaults to roughly the lower 48 US states
-const defaultSWCoords = [13.496, -143.789];
-const defaultNECoords = [55.526, -57.919];
-const bounds = {};
-
-angular.module('MarkerFactory', ['kibana']);
-
-function setBounds(southWest, northEast) {
- bounds.southWest = L.latLng(southWest || defaultSWCoords);
- bounds.northEast = L.latLng(northEast || defaultNECoords);
-}
-
-function getBounds() {
- return L.latLngBounds(bounds.southWest, bounds.northEast);
-}
-
-const mockMap = {
- addLayer: _.noop,
- closePopup: _.noop,
- getBounds: getBounds,
- removeControl: _.noop,
- removeLayer: _.noop,
- getZoom: _.constant(5)
-};
-
-describe('tilemaptest - Marker Tests', function () {
- let mapData;
- let markerLayer;
-
- function createMarker(MarkerClass, geoJson, tooltipFormatter) {
- mapData = _.assign({}, geoJsonData.geoJson, geoJson || {});
- mapData.properties.allmin = mapData.properties.min;
- mapData.properties.allmax = mapData.properties.max;
-
- return new MarkerClass(mockMap, mapData, {
- valueFormatter: geoJsonData.valueFormatter,
- tooltipFormatter: tooltipFormatter || null
- });
- }
-
- beforeEach(function () {
- setBounds();
- });
-
- afterEach(function () {
- if (markerLayer) {
- markerLayer.destroy();
- markerLayer = undefined;
- }
- });
-
- describe('Base Methods', function () {
- let MarkerClass;
-
- beforeEach(ngMock.module('MarkerFactory'));
- beforeEach(ngMock.inject(function (Private) {
- MarkerClass = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider);
- markerLayer = createMarker(MarkerClass);
- }));
-
- describe('filterToMapBounds', function () {
- it('should not filter any features', function () {
- // set bounds to the entire world
- setBounds([-87.252, -343.828], [87.252, 343.125]);
- const boundFilter = markerLayer._filterToMapBounds();
- const mapFeature = mapData.features.filter(boundFilter);
-
- expect(mapFeature.length).to.equal(mapData.features.length);
- });
-
- it('should filter out data points that are outside of the map bounds', function () {
- // set bounds to roughly US southwest
- setBounds([31.690, -124.387], [42.324, -102.919]);
- const boundFilter = markerLayer._filterToMapBounds();
- const mapFeature = mapData.features.filter(boundFilter);
-
- expect(mapFeature.length).to.be.lessThan(mapData.features.length);
- });
- });
-
- describe('legendQuantizer', function () {
- it('should return a range of hex colors', function () {
- const minColor = markerLayer._legendQuantizer(mapData.properties.allmin);
- const maxColor = markerLayer._legendQuantizer(mapData.properties.allmax);
-
- expect(minColor.substring(0, 1)).to.equal('#');
- expect(minColor).to.have.length(7);
- expect(maxColor.substring(0, 1)).to.equal('#');
- expect(maxColor).to.have.length(7);
- expect(minColor).to.not.eql(maxColor);
- });
-
- it('should return a color with 1 color', function () {
- const geoJson = { properties: { min: 1, max: 1 } };
- markerLayer = createMarker(MarkerClass, geoJson);
-
- // ensure the quantizer domain is correct
- const color = markerLayer._legendQuantizer(1);
- expect(color).to.not.be(undefined);
- expect(color.substring(0, 1)).to.equal('#');
-
- // should always get the same color back
- _.times(5, function () {
- const randColor = markerLayer._legendQuantizer(0);
- expect(randColor).to.equal(color);
- });
- });
- });
-
- describe('applyShadingStyle', function () {
- it('should return a style object', function () {
- const style = markerLayer.applyShadingStyle(100);
- expect(style).to.be.an('object');
-
- const keys = _.keys(style);
- const expected = ['fillColor', 'color'];
- _.each(expected, function (key) {
- expect(keys).to.contain(key);
- });
- });
-
- it('should use the legendQuantizer', function () {
- const spy = sinon.spy(markerLayer, '_legendQuantizer');
- markerLayer.applyShadingStyle(100);
- expect(spy.callCount).to.equal(1);
- });
- });
-
- describe('showTooltip', function () {
- it('should use the tooltip formatter', function () {
- const sample = _.sample(mapData.features);
-
- markerLayer = createMarker(MarkerClass, null, Function.prototype);//create marker with tooltip
- markerLayer._attr.addTooltip = true;
- const stub = sinon.stub(markerLayer, '_tooltipFormatter', function () {
- return;
- });
- markerLayer._showTooltip(sample);
- expect(stub.callCount).to.equal(1);
- expect(stub.firstCall.calledWith(sample)).to.be(true);
- });
- });
-
- describe('addLegend', function () {
- let addToSpy;
- let leafletControlStub;
-
- beforeEach(function () {
- addToSpy = sinon.spy();
- leafletControlStub = sinon.stub(L, 'control', function () {
- return {
- addTo: addToSpy
- };
- });
- });
-
- it('should do nothing if there is already a legend', function () {
- markerLayer._legend = { legend: 'exists' }; // anything truthy
-
- markerLayer.addLegend();
- expect(leafletControlStub.callCount).to.equal(0);
- });
-
- it('should create a leaflet control', function () {
- markerLayer.addLegend();
- expect(leafletControlStub.callCount).to.equal(1);
- expect(addToSpy.callCount).to.equal(1);
- expect(addToSpy.firstCall.calledWith(markerLayer.map)).to.be(true);
- expect(markerLayer._legend).to.have.property('onAdd');
- });
-
- it('should use the value formatter', function () {
- const formatterSpy = sinon.spy(markerLayer, '_valueFormatter');
- // called twice for every legend color defined
- const expectedCallCount = markerLayer._legendColors.length * 2;
-
- markerLayer.addLegend();
- const legend = markerLayer._legend.onAdd();
-
- expect(formatterSpy.callCount).to.equal(expectedCallCount);
- expect(legend).to.be.a(HTMLDivElement);
- });
- });
- });
-
- describe('Shaded Circles', function () {
- beforeEach(ngMock.module('MarkerFactory'));
- beforeEach(ngMock.inject(function (Private) {
- const MarkerClass = Private(VislibVisualizationsMarkerTypesShadedCirclesProvider);
- markerLayer = createMarker(MarkerClass);
- }));
-
- describe('geohashMinDistance method', function () {
- it('should return a finite number', function () {
- const sample = _.sample(mapData.features);
- const distance = markerLayer._geohashMinDistance(sample);
-
- expect(distance).to.be.a('number');
- expect(_.isFinite(distance)).to.be(true);
- });
- });
- });
-
- describe('Scaled Circles', function () {
- let zoom;
-
- beforeEach(ngMock.module('MarkerFactory'));
- beforeEach(ngMock.inject(function (Private) {
- zoom = _.random(1, 18);
- sinon.stub(mockMap, 'getZoom', _.constant(zoom));
- const MarkerClass = Private(VislibVisualizationsMarkerTypesScaledCirclesProvider);
- markerLayer = createMarker(MarkerClass);
- }));
-
- describe('radiusScale method', function () {
- const valueArray = [10, 20, 30, 40, 50, 60];
- const max = _.max(valueArray);
-
- it('should return 0 for value of 0', function () {
- expect(markerLayer._radiusScale(0)).to.equal(0);
- });
-
- it('should return a scaled value for negative and positive numbers', function () {
- const upperBound = markerLayer._radiusScale(max);
- const results = [];
-
- function roundValue(value) {
- // round number to 6 decimal places
- const r = Math.pow(10, 6);
- return Math.round(value * r) / r;
- }
-
- _.each(valueArray, function (value, i) {
- const ratio = Math.pow(value / max, 0.5);
- const comparison = ratio * upperBound;
- const radius = markerLayer._radiusScale(value);
- const negRadius = markerLayer._radiusScale(value * -1);
- results.push(radius);
-
- expect(negRadius).to.equal(radius);
- expect(roundValue(radius)).to.equal(roundValue(comparison));
-
- // check that the radius is getting larger
- if (i > 0) {
- expect(radius).to.be.above(results[i - 1]);
- }
- });
- });
- });
- });
-
- describe('Heatmaps', function () {
- beforeEach(ngMock.module('MarkerFactory'));
- beforeEach(ngMock.inject(function (Private) {
- const MarkerClass = Private(VislibVisualizationsMarkerTypesHeatmapProvider);
- markerLayer = createMarker(MarkerClass);
- }));
-
- describe('dataToHeatArray', function () {
- let max;
-
- beforeEach(function () {
- max = mapData.properties.allmax;
- });
-
- it('should return an array or values for each feature', function () {
- const arr = markerLayer._dataToHeatArray(max);
- expect(arr).to.be.an('array');
- expect(arr).to.have.length(mapData.features.length);
-
- });
-
- it('should return an array item with lat, lng, metric for each feature', function () {
- _.times(3, function () {
- const arr = markerLayer._dataToHeatArray(max);
- const index = _.random(mapData.features.length - 1);
- const feature = mapData.features[index];
- const featureValue = feature.properties.value;
- const featureArr = feature.geometry.coordinates.slice(0).concat(featureValue);
- expect(arr[index]).to.eql(featureArr);
- });
- });
-
- it('should return an array item with lat, lng, normalized metric for each feature', function () {
- _.times(5, function () {
- markerLayer._attr.heatNormalizeData = true;
-
- const arr = markerLayer._dataToHeatArray(max);
- const index = _.random(mapData.features.length - 1);
- const feature = mapData.features[index];
- const featureValue = feature.properties.value / max;
- const featureArr = feature.geometry.coordinates.slice(0).concat(featureValue);
- expect(arr[index]).to.eql(featureArr);
- });
- });
- });
-
- describe('tooltipProximity', function () {
- it('should return true if feature is close enough to event latlng', function () {
- _.times(5, function () {
- const feature = _.sample(mapData.features);
- const point = markerLayer._getLatLng(feature);
- const arr = markerLayer._tooltipProximity(point, feature);
- expect(arr).to.be(true);
- });
- });
-
- it('should return false if feature is not close enough to event latlng', function () {
- _.times(5, function () {
- const feature = _.sample(mapData.features);
- const point = L.latLng(90, -180);
- const arr = markerLayer._tooltipProximity(point, feature);
- expect(arr).to.be(false);
- });
- });
- });
-
- describe('nearestFeature', function () {
- it('should return nearest geoJson feature object', function () {
- _.times(5, function () {
- const feature = _.sample(mapData.features);
- const point = markerLayer._getLatLng(feature);
- const nearestPoint = markerLayer._nearestFeature(point);
- expect(nearestPoint).to.equal(feature);
- });
- });
- });
-
- describe('getLatLng', function () {
- it('should return a leaflet latLng object', function () {
- const feature = _.sample(mapData.features);
- const latLng = markerLayer._getLatLng(feature);
- const compare = L.latLng(feature.geometry.coordinates.slice(0).reverse());
- expect(latLng).to.eql(compare);
- });
-
- it('should memoize the result', function () {
- const spy = sinon.spy(L, 'latLng');
- const feature = _.sample(mapData.features);
-
- markerLayer._getLatLng(feature);
- expect(spy.callCount).to.be(1);
-
- markerLayer._getLatLng(feature);
- expect(spy.callCount).to.be(1);
- });
- });
- });
-
-});
diff --git a/src/ui/public/vis_maps/__tests__/tile_maps/tile_map.js b/src/ui/public/vis_maps/__tests__/tile_maps/tile_map.js
deleted file mode 100644
index 9b942e22f3bd1..0000000000000
--- a/src/ui/public/vis_maps/__tests__/tile_maps/tile_map.js
+++ /dev/null
@@ -1,137 +0,0 @@
-import expect from 'expect.js';
-import ngMock from 'ng_mock';
-import _ from 'lodash';
-import sinon from 'auto-release-sinon';
-
-import geoJsonData from 'fixtures/vislib/mock_data/geohash/_geo_json';
-import MockMap from 'fixtures/tilemap_map';
-import $ from 'jquery';
-import VislibVisualizationsTileMapProvider from 'ui/vis_maps/visualizations/tile_map';
-const mockChartEl = $('
');
-
-let TileMap;
-let extentsStub;
-
-function createTileMap(handler, chartEl, chartData) {
- handler = handler || {
- visConfig: {
- get: function () {
- return '';
- }
- },
- uiState: {
- get: function () {
- return '';
- }
- }
- };
- chartEl = chartEl || mockChartEl;
- chartData = chartData || geoJsonData;
-
- return new TileMap(handler, chartEl, chartData);
-}
-
-describe('tilemaptest - TileMap Tests', function () {
- let tilemap;
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private) {
- Private.stub(require('ui/vis_maps/visualizations/_map'), MockMap);
- TileMap = Private(VislibVisualizationsTileMapProvider);
- extentsStub = sinon.stub(TileMap.prototype, '_appendGeoExtents', _.noop);
- }));
-
- beforeEach(function () {
- tilemap = createTileMap();
- });
-
- it('should inherit props from chartData', function () {
- _.each(geoJsonData, function (val, prop) {
- expect(tilemap).to.have.property(prop, val);
- });
- });
-
- it('should append geoExtents', function () {
- expect(extentsStub.callCount).to.equal(1);
- });
-
- describe('draw', function () {
- it('should return a function', function () {
- expect(tilemap.draw()).to.be.a('function');
- });
- });
-
- describe('appendMap', function () {
- let $selection;
-
- beforeEach(function () {
- $selection = $('
');
- expect(tilemap.maps).to.have.length(0);
- tilemap._appendMap($selection);
- });
-
- it('should add the tilemap class', function () {
- expect($selection.hasClass('tilemap')).to.equal(true);
- });
-
- it('should append maps and required controls', function () {
- expect(tilemap.maps).to.have.length(1);
- const map = tilemap.maps[0];
- expect(map.addTitle.callCount).to.equal(0);
- expect(map.addFitControl.callCount).to.equal(1);
- expect(map.addBoundingControl.callCount).to.equal(1);
- });
-
- it('should only add controls if data exists', function () {
- const noData = {
- geohashGridAgg: { vis: { params: {} } },
- geoJson: {
- features: [],
- properties: {},
- hits: 20
- }
- };
- tilemap = createTileMap(null, null, noData);
-
- tilemap._appendMap($selection);
- expect(tilemap.maps).to.have.length(1);
-
- const map = tilemap.maps[0];
- expect(map.addTitle.callCount).to.equal(0);
- expect(map.addFitControl.callCount).to.equal(0);
- expect(map.addBoundingControl.callCount).to.equal(0);
- });
-
- it('should append title if set in the data object', function () {
- const mapTitle = 'Test Title';
- tilemap = createTileMap(null, null, _.assign({ title: mapTitle }, geoJsonData));
- tilemap._appendMap($selection);
- const map = tilemap.maps[0];
-
- expect(map.addTitle.callCount).to.equal(1);
- expect(map.addTitle.firstCall.calledWith(mapTitle)).to.equal(true);
- });
- });
-
- describe('destroy', function () {
- const maps = [];
- const mapCount = 5;
-
- beforeEach(function () {
- _.times(mapCount, function () {
- maps.push(new MockMap());
- });
- tilemap.maps = maps;
- expect(tilemap.maps).to.have.length(mapCount);
- tilemap.destroy();
- });
-
- it('should destroy all the maps', function () {
- expect(tilemap.maps).to.have.length(0);
- expect(maps).to.have.length(mapCount);
- _.each(maps, function (map) {
- expect(map.destroy.callCount).to.equal(1);
- });
- });
- });
-});
diff --git a/src/ui/public/vis_maps/__tests__/tile_maps/tilemap_settings.js b/src/ui/public/vis_maps/__tests__/tilemap_settings.js
similarity index 100%
rename from src/ui/public/vis_maps/__tests__/tile_maps/tilemap_settings.js
rename to src/ui/public/vis_maps/__tests__/tilemap_settings.js
diff --git a/src/ui/public/vis_maps/__tests__/tile_maps/tilemap_settings_mocked.js b/src/ui/public/vis_maps/__tests__/tilemap_settings_mocked.js
similarity index 100%
rename from src/ui/public/vis_maps/__tests__/tile_maps/tilemap_settings_mocked.js
rename to src/ui/public/vis_maps/__tests__/tilemap_settings_mocked.js
diff --git a/src/ui/public/vis_maps/geohash_layer.js b/src/ui/public/vis_maps/geohash_layer.js
new file mode 100644
index 0000000000000..288cfcef4cd4f
--- /dev/null
+++ b/src/ui/public/vis_maps/geohash_layer.js
@@ -0,0 +1,90 @@
+import KibanaMapLayer from './kibana_map_layer';
+import _ from 'lodash';
+import Heatmap from './markers/heatmap';
+import ScaledCircles from './markers/scaled_circles';
+import ShadedCircles from './markers/shaded_circles';
+import GeohashGrid from './markers/geohash_grid';
+
+export default class GeohashLayer extends KibanaMapLayer {
+
+ constructor(featureCollection, options, zoom, kibanaMap) {
+
+ super();
+
+ this._geohashGeoJson = featureCollection;
+ this._geohashOptions = options;
+ this._zoom = zoom;
+ this._kibanaMap = kibanaMap;
+
+ this._createGeohashMarkers();
+ }
+
+ _createGeohashMarkers() {
+ const markerOptions = {
+ valueFormatter: this._geohashOptions.valueFormatter,
+ tooltipFormatter: this._geohashOptions.tooltipFormatter
+ };
+ switch (this._geohashOptions.mapType) {
+ case 'Scaled Circle Markers':
+ this._geohashMarkers = new ScaledCircles(this._geohashGeoJson, markerOptions, this._zoom, this._kibanaMap);
+ break;
+ case 'Shaded Circle Markers':
+ this._geohashMarkers = new ShadedCircles(this._geohashGeoJson, markerOptions, this._zoom, this._kibanaMap);
+ break;
+ case 'Shaded Geohash Grid':
+ this._geohashMarkers = new GeohashGrid(this._geohashGeoJson, markerOptions, this._zoom, this._kibanaMap);
+ break;
+ case 'Heatmap':
+ this._geohashMarkers = new Heatmap(this._geohashGeoJson, {
+ radius: parseFloat(this._geohashOptions.heatmap.heatRadius),
+ blur: parseFloat(this._geohashOptions.heatmap.heatBlur),
+ maxZoom: parseFloat(this._geohashOptions.heatmap.heatMaxZoom),
+ minOpaxity: parseFloat(this._geohashOptions.heatmap.heatMinOpacity),
+ heatNormalizeData: parseFloat(this._geohashOptions.heatmap.heatNormalizeData),
+ tooltipFormatter: this._geohashOptions.tooltipFormatter
+ }, this._zoom, this._kibanaMap);
+ break;
+ default:
+ throw new Error(`${this._geohashOptions.mapType} mapType not recognized`);
+
+ }
+
+ this._geohashMarkers.on('showTooltip', (event) => this.emit('showTooltip', event));
+ this._geohashMarkers.on('hideTooltip', (event) => this.emit('hideTooltip', event));
+ this._leafletLayer = this._geohashMarkers.getLeafletLayer();
+ }
+
+ appendLegendContents(jqueryDiv) {
+ return this._geohashMarkers.appendLegendContents(jqueryDiv);
+ }
+
+ movePointer(...args) {
+ this._geohashMarkers.movePointer(...args);
+ }
+
+ updateExtent() {
+ //this removal is required to trigger the bounds filter again
+ this._kibanaMap.removeLayer(this);
+ this._createGeohashMarkers();
+ this._kibanaMap.addLayer(this);
+ }
+
+
+ isReusable(options) {
+
+ if (_.isEqual(this._geohashOptions, options)) {
+ return true;
+ }
+
+ if (this._geohashOptions.mapType !== options.mapType) {
+ return false;
+ } else if (this._geohashOptions.mapType === 'Heatmap' && !_.isEqual(this._geohashOptions.heatmap, options)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+}
+
+
+
diff --git a/src/ui/public/vis_maps/kibana_map.js b/src/ui/public/vis_maps/kibana_map.js
new file mode 100644
index 0000000000000..1234b1d725d8a
--- /dev/null
+++ b/src/ui/public/vis_maps/kibana_map.js
@@ -0,0 +1,526 @@
+import { EventEmitter } from 'events';
+import L from 'leaflet';
+import $ from 'jquery';
+import _ from 'lodash';
+import zoomToPrecision from 'ui/utils/zoom_to_precision';
+
+const FitControl = L.Control.extend({
+ options: {
+ position: 'topleft'
+ },
+ initialize: function (fitContainer, kibanaMap) {
+ this._fitContainer = fitContainer;
+ this._kibanaMap = kibanaMap;
+ this._leafletMap = null;
+ },
+ onAdd: function (leafletMap) {
+ this._leafletMap = leafletMap;
+ $(this._fitContainer).html('
')
+ .on('click', e => {
+ e.preventDefault();
+ this._kibanaMap.fitToData();
+ });
+
+ return this._fitContainer;
+ },
+ onRemove: function () {
+ $(this._fitContainer).off('click');
+ }
+});
+
+
+const LegendControl = L.Control.extend({
+
+ options: {
+ position: 'topright'
+ },
+
+ updateContents() {
+ this._legendContainer.empty();
+ const $div = $('
').addClass('tilemap-legend');
+ this._legendContainer.append($div);
+ const layers = this._kibanaMap.getLayers();
+ layers.forEach((layer) =>layer.appendLegendContents($div));
+ },
+
+
+ initialize: function (container, kibanaMap, position) {
+ this._legendContainer = container;
+ this._kibanaMap = kibanaMap;
+ this.options.position = position;
+
+ },
+ onAdd: function () {
+ this._layerUpdateHandle = () => this.updateContents();
+ this._kibanaMap.on('layers:update', this._layerUpdateHandle);
+ this.updateContents();
+ return this._legendContainer.get(0);
+ },
+ onRemove: function () {
+ this._kibanaMap.removeListener('layers:update', this._layerUpdateHandle);
+ }
+
+});
+
+/**
+ * Collects map functionality required for Kibana.
+ * Serves as simple abstraction for leaflet as well.
+ */
+class KibanaMap extends EventEmitter {
+
+ constructor(containerNode, options) {
+
+ super();
+ this._containerNode = containerNode;
+ this._leafletBaseLayer = null;
+ this._baseLayerSettings = null;
+ this._baseLayerIsDesaturated = true;
+
+ this._leafletDrawControl = null;
+ this._leafletFitControl = null;
+ this._leafletLegendControl = null;
+ this._legendPosition = 'topright';
+
+ this._layers = [];
+ this._listeners = [];
+ this._showTooltip = false;
+
+ this._leafletMap = L.map(containerNode, {
+ minZoom: options.minZoom,
+ maxZoom: options.maxZoom
+ });
+ this._leafletMap.fitWorld();
+ const worldBounds = L.latLngBounds(L.latLng(-90, -180), L.latLng(90, 180));
+ this._leafletMap.setMaxBounds(worldBounds);
+
+ let previousZoom = this._leafletMap.getZoom();
+ this._leafletMap.on('zoomend', () => {
+ if (previousZoom !== this._leafletMap.getZoom()) {
+ previousZoom = this._leafletMap.getZoom();
+ this.emit('zoomchange');
+ }
+ });
+ this._leafletMap.on('zoomend', () => this.emit('zoomend'));
+ this._leafletMap.on('moveend', () => this.emit('moveend'));
+ this._leafletMap.on('dragend', e => this._layers.forEach(layer => layer.updateExtent('dragend', e)));
+ this._leafletMap.on('mousemove', e => this._layers.forEach(layer => layer.movePointer('mousemove', e)));
+ this._leafletMap.on('mouseout', e => this._layers.forEach(layer => layer.movePointer('mouseout', e)));
+ this._leafletMap.on('mousedown', e => this._layers.forEach(layer => layer.movePointer('mousedown', e)));
+ this._leafletMap.on('mouseup', e => this._layers.forEach(layer => layer.movePointer('mouseup', e)));
+ this._leafletMap.on('draw:created', event => {
+ const drawType = event.layerType;
+ if (drawType === 'rectangle') {
+ const bounds = event.layer.getBounds();
+
+ const southEast = bounds.getSouthEast();
+ const northWest = bounds.getNorthWest();
+ let southEastLng = southEast.lng;
+ if (southEastLng > 180) {
+ southEastLng -= 360;
+ }
+ let northWestLng = northWest.lng;
+ if (northWestLng < -180) {
+ northWestLng += 360;
+ }
+
+ const southEastLat = southEast.lat;
+ const northWestLat = northWest.lat;
+
+ //Bounds cannot be created unless they form a box with larger than 0 dimensions
+ //Invalid areas are rejected by ES.
+ if (southEastLat === northWestLat || southEastLng === northWestLng) {
+ return;
+ }
+
+ this.emit('drawCreated:rectangle', {
+ bounds: {
+ bottom_right: {
+ lat: southEastLat,
+ lon: southEastLng
+ },
+ top_left: {
+ lat: northWestLat,
+ lon: northWestLng
+ }
+ }
+ });
+ } else if (drawType === 'polygon') {
+ const latLongs = event.layer.getLatLngs();
+ this.emit('drawCreated:polygon', {
+ points: latLongs.map(leafletLatLng => {
+ return {
+ lat: leafletLatLng.lat,
+ lon: leafletLatLng.lng
+ };
+ })
+ });
+ }
+ });
+
+ this.resize();
+
+ }
+
+ setShowTooltip(showTooltip) {
+ this._showTooltip = showTooltip;
+ }
+
+ getLayers() {
+ return this._layers.slice();
+ }
+
+
+ addLayer(kibanaLayer) {
+
+
+ this.emit('layers:invalidate');
+
+ const onshowTooltip = (event) => {
+
+ if (!this._showTooltip) {
+ return;
+ }
+
+ if (!this._popup) {
+ this._popup = L.popup({ autoPan: false });
+ this._popup.setLatLng(event.position);
+ this._popup.setContent(event.content);
+ this._popup.openOn(this._leafletMap);
+ } else {
+ if (!this._popup.getLatLng().equals(event.position)) {
+ this._popup.setLatLng(event.position);
+ }
+ if (this._popup.getContent() !== event.content) {
+ this._popup.setContent(event.content);
+ }
+ }
+
+
+ };
+
+ kibanaLayer.on('showTooltip', onshowTooltip);
+ this._listeners.push({ name: 'showTooltip', handle: onshowTooltip, layer: kibanaLayer });
+
+ const onHideTooltip = () => {
+ this._leafletMap.closePopup();
+ this._popup = null;
+ };
+ kibanaLayer.on('hideTooltip', onHideTooltip);
+ this._listeners.push({ name: 'hideTooltip', handle: onHideTooltip, layer: kibanaLayer });
+
+
+ const onStyleChanged = () => {
+ if (this._leafletLegendControl) {
+ this._leafletLegendControl.updateContents();
+ }
+ };
+ kibanaLayer.on('styleChanged', onStyleChanged);
+ this._listeners.push({ name: 'styleChanged', handle: onStyleChanged, layer: kibanaLayer });
+
+ this._layers.push(kibanaLayer);
+ kibanaLayer.addToLeafletMap(this._leafletMap);
+ this.emit('layers:update');
+ }
+
+ removeLayer(layer) {
+ const index = this._layers.indexOf(layer);
+ if (index >= 0) {
+ this._layers.splice(index, 1);
+ layer.removeFromLeafletMap(this._leafletMap);
+ }
+ this._listeners.forEach(listener => {
+ if (listener.layer === layer) {
+ listener.layer.removeListener(listener.name, listener.handle);
+ }
+ });
+ }
+
+ destroy() {
+ if (this._leafletFitControl) {
+ this._leafletMap.removeControl(this._leafletFitControl);
+ }
+ if (this._leafletDrawControl) {
+ this._leafletMap.removeControl(this._leafletDrawControl);
+ }
+ if (this._leafletLegendControl) {
+ this._leafletMap.removeControl(this._leafletLegendControl);
+ }
+ this.setBaseLayer(null);
+ for (const layer of this._layers) {
+ layer.removeFromLeafletMap(this._leafletMap);
+ }
+ this._leafletMap.remove();
+ this._containerNode.innerHTML = '';
+ this._listeners.forEach(listener => listener.layer.removeListener(listener.name, listener.handle));
+ }
+
+ getCenter() {
+ const center = this._leafletMap.getCenter();
+ return { lon: center.lng, lat: center.lat };
+ }
+
+ setCenter(latitude, longitude) {
+ const latLong = L.latLng(latitude, longitude);
+ if (latLong.equals && !latLong.equals(this._leafletMap.getCenter())) {
+ this._leafletMap.setView(latLong);
+ }
+ }
+
+ setZoomLevel(zoomLevel) {
+ if (this._leafletMap.getZoom() !== zoomLevel) {
+ this._leafletMap.setZoom(zoomLevel);
+ }
+ }
+
+ getZoomLevel() {
+ return this._leafletMap.getZoom();
+ }
+
+ getAutoPrecision() {
+ return zoomToPrecision(this._leafletMap.getZoom(), 12, this._leafletMap.getMaxZoom());
+ }
+
+ getBounds() {
+
+ const bounds = this._leafletMap.getBounds();
+ if (!bounds) {
+ return null;
+ }
+
+ const southEast = bounds.getSouthEast();
+ const northWest = bounds.getNorthWest();
+ let southEastLng = southEast.lng;
+ if (southEastLng > 180) {
+ southEastLng -= 360;
+ }
+ let northWestLng = northWest.lng;
+ if (northWestLng < -180) {
+ northWestLng += 360;
+ }
+
+ const southEastLat = southEast.lat;
+ const northWestLat = northWest.lat;
+
+ //Bounds cannot be created unless they form a box with larger than 0 dimensions
+ //Invalid areas are rejected by ES.
+ if (southEastLat === northWestLat || southEastLng === northWestLng) {
+ return;
+ }
+
+ return {
+ bottom_right: {
+ lat: southEastLat,
+ lon: southEastLng
+ },
+ top_left: {
+ lat: northWestLat,
+ lon: northWestLng
+ }
+ };
+ }
+
+
+ setDesaturateBaseLayer(isDesaturated) {
+ if (isDesaturated === this._baseLayerIsDesaturated) {
+ return;
+ }
+ this._baseLayerIsDesaturated = isDesaturated;
+ this._updateDesaturation();
+ this._leafletBaseLayer.redraw();
+ }
+
+ addDrawControl() {
+ const drawOptions = {
+ draw: {
+ polyline: false,
+ marker: false,
+ circle: false,
+ polygon: false,
+ rectangle: {
+ shapeOptions: {
+ stroke: false,
+ color: '#000'
+ }
+ }
+ }
+ };
+ this._leafletDrawControl = new L.Control.Draw(drawOptions);
+ this._leafletMap.addControl(this._leafletDrawControl);
+ }
+
+ addFitControl() {
+
+ if (this._leafletFitControl || !this._leafletMap) {
+ return;
+ }
+
+ const fitContainer = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-control-fit');
+ this._leafletFitControl = new FitControl(fitContainer, this);
+ this._leafletMap.addControl(this._leafletFitControl);
+ }
+
+ addLegendControl() {
+ if (this._leafletLegendControl || !this._leafletMap) {
+ return;
+ }
+ this._updateLegend();
+ }
+
+ setLegendPosition(position) {
+ this._legendPosition = position;
+ if (this._leafletLegendControl) {
+ this._leafletMap.removeControl(this._leafletLegendControl);
+ this._updateLegend();
+ }
+ }
+
+ _updateLegend() {
+ const $wrapper = $('
').addClass('tilemap-legend-wrapper');
+ this._leafletLegendControl = new LegendControl($wrapper, this, this._legendPosition);
+ this._leafletMap.addControl(this._leafletLegendControl);
+ }
+
+ resize() {
+ this._leafletMap.invalidateSize();
+ this._updateExtent();
+ }
+
+
+ setBaseLayer(settings) {
+
+ if (_.isEqual(settings, this._baseLayerSettings)) {
+ return;
+ }
+
+ this._baseLayerSettings = settings;
+ if (settings === null) {
+ if (this._leafletBaseLayer && this._leafletMap) {
+ this._leafletMap.removeLayer(this._leafletBaseLayer);
+ this._leafletBaseLayer = null;
+ }
+ return;
+ }
+
+ if (this._leafletBaseLayer) {
+ this._leafletMap.removeLayer(this._leafletBaseLayer);
+ this._leafletBaseLayer = null;
+ }
+
+ let baseLayer;
+ if (settings.baseLayerType === 'wms') {
+ baseLayer = this._getWMSBaseLayer(settings.options);
+ } else if (settings.baseLayerType === 'tms') {
+ baseLayer = this._getTMSBaseLayer((settings.options));
+ }
+
+ baseLayer.on('tileload', () => this._updateDesaturation());
+ baseLayer.on('load', () => { this.emit('baseLayer:loaded');});
+ baseLayer.on('loading', () => {this.emit('baseLayer:loading');});
+
+ this._leafletBaseLayer = baseLayer;
+ this._leafletBaseLayer.addTo(this._leafletMap);
+ this._leafletBaseLayer.bringToBack();
+ if (settings.options.minZoom > this._leafletMap.getZoom()) {
+ this._leafletMap.setZoom(settings.options.minZoom);
+ }
+ this.resize();
+
+ }
+
+ isInside(bucketRectBounds) {
+ const mapBounds = this._leafletMap.getBounds();
+ return mapBounds.intersects(bucketRectBounds);
+ }
+
+ fitToData() {
+
+ if (!this._leafletMap) {
+ return;
+ }
+
+ let bounds = null;
+ this._layers.forEach(layer => {
+ const leafletLayer = layer.getLeafletLayer();
+ const b = leafletLayer.getBounds();
+ if (bounds) {
+ bounds.extend(b);
+ } else {
+ bounds = b;
+ }
+ });
+
+ if (bounds) {
+ this._leafletMap.fitBounds(bounds);
+ }
+ }
+
+ _getTMSBaseLayer(options) {
+ return L.tileLayer(options.url, {
+ minZoom: options.minZoom,
+ maxZoom: options.maxZoom,
+ subdomains: options.subdomains || [],
+ attribution: options.attribution
+ });
+ }
+
+ _getWMSBaseLayer(options) {
+ return L.tileLayer.wms(options.url, {
+ attribution: options.attribution,
+ format: options.format,
+ layers: options.layers,
+ minZoom: options.minZoom,
+ maxZoom: options.maxZoom,
+ styles: options.styles,
+ transparent: options.transparent,
+ version: options.version
+ });
+ }
+
+ _updateExtent() {
+ this._layers.forEach(layer => layer.updateExtent());
+ }
+
+ _updateDesaturation() {
+ const tiles = $('img.leaflet-tile-loaded');
+ if (this._baseLayerIsDesaturated) {
+ tiles.removeClass('filters-off');
+ } else if (!this._baseLayerIsDesaturated) {
+ tiles.addClass('filters-off');
+ }
+ }
+
+ persistUiStateForVisualization(visualization) {
+ this.on('moveend', () => {
+ const uiState = visualization.getUiState();
+ const centerFromUIState = uiState.get('mapCenter');
+ const zoomFromUiState = parseInt(uiState.get('mapZoom'));
+ if (isNaN(zoomFromUiState) || this.getZoomLevel() !== zoomFromUiState) {
+ uiState.set('mapZoom', this.getZoomLevel());
+ }
+ const centerFromMap = this.getCenter();
+ if (!centerFromUIState || centerFromMap.lon !== centerFromUIState[1] || centerFromMap.lat !== centerFromUIState[0]) {
+ uiState.set('mapCenter', [centerFromMap.lat, centerFromMap.lon]);
+ }
+ });
+ }
+
+ useUiStateFromVisualization(visualization) {
+ const uiState = visualization.getUiState();
+ const zoomFromUiState = parseInt(uiState.get('mapZoom'));
+ const centerFromUIState = uiState.get('mapCenter');
+ if (!isNaN(zoomFromUiState)) {
+ this.setZoomLevel(zoomFromUiState);
+ }
+ if (centerFromUIState) {
+ this.setCenter(centerFromUIState[0], centerFromUIState[1]);
+ }
+ }
+
+
+}
+
+
+
+
+export default KibanaMap;
+
diff --git a/src/ui/public/vis_maps/kibana_map_layer.js b/src/ui/public/vis_maps/kibana_map_layer.js
new file mode 100644
index 0000000000000..97a068ca25013
--- /dev/null
+++ b/src/ui/public/vis_maps/kibana_map_layer.js
@@ -0,0 +1,40 @@
+import { EventEmitter } from 'events';
+
+
+export default class KibanaMapLayer extends EventEmitter {
+ constructor() {
+ super();
+ this._leafletLayer = null;
+ }
+ getLeafletLayer() {
+ return this._leafletLayer;
+ }
+
+ addToLeafletMap(leafletMap) {
+ this._leafletLayer.addTo(leafletMap);
+ }
+
+ removeFromLeafletMap(leafletMap) {
+ leafletMap.removeLayer(this._leafletLayer);
+ }
+
+ appendLegendContents() {
+ }
+
+ updateExtent() {
+ }
+
+ movePointer() {
+ }
+
+ getBounds() {
+ }
+}
+
+
+
+
+
+
+
+
diff --git a/src/ui/public/vis_maps/lib/data.js b/src/ui/public/vis_maps/lib/data.js
deleted file mode 100644
index fea6e2363dbd8..0000000000000
--- a/src/ui/public/vis_maps/lib/data.js
+++ /dev/null
@@ -1,200 +0,0 @@
-import d3 from 'd3';
-import _ from 'lodash';
-export default function DataFactory() {
- /**
- * Provides an API for pulling values off the data
- * and calculating values using the data
- *
- * @class Data
- * @constructor
- * @param data {Object} Elasticsearch query results
- * @param attr {Object|*} Visualization options
- */
- class Data {
- constructor(data, uiState) {
- this.uiState = uiState;
- this.data = this.copyDataObj(data);
- this._normalizeOrdered();
- }
-
- copyDataObj(data) {
- const copyChart = data => {
- const newData = {};
- Object.keys(data).forEach(key => {
- if (key !== 'series') {
- newData[key] = data[key];
- } else {
- newData[key] = data[key].map(seri => {
- return {
- label: seri.label,
- values: seri.values.map(val => {
- const newVal = _.clone(val);
- newVal.aggConfig = val.aggConfig;
- newVal.aggConfigResult = val.aggConfigResult;
- newVal.extraMetrics = val.extraMetrics;
- return newVal;
- })
- };
- });
- }
- });
- return newData;
- };
-
- if (!data.series) {
- const newData = {};
- Object.keys(data).forEach(key => {
- if (!['rows', 'columns'].includes(key)) {
- newData[key] = data[key];
- }
- else {
- newData[key] = data[key].map(chart => {
- return copyChart(chart);
- });
- }
- });
- return newData;
- }
- return copyChart(data);
- }
-
- /**
- * Returns an array of the actual x and y data value objects
- * from data with series keys
- *
- * @method chartData
- * @returns {*} Array of data objects
- */
- chartData() {
- if (!this.data.series) {
- const arr = this.data.rows ? this.data.rows : this.data.columns;
- return _.toArray(arr);
- }
- return [this.data];
- }
-
- /**
- * Returns an array of chart data objects
- *
- * @method getVisData
- * @returns {*} Array of chart data objects
- */
- getVisData() {
- let visData;
-
- if (this.data.rows) {
- visData = this.data.rows;
- } else if (this.data.columns) {
- visData = this.data.columns;
- } else {
- visData = [this.data];
- }
-
- return visData;
- }
-
- /**
- * get min and max for all cols, rows of data
- *
- * @method getMaxMin
- * @return {Object}
- */
- getGeoExtents() {
- const visData = this.getVisData();
-
- return _.reduce(_.pluck(visData, 'geoJson.properties'), function (minMax, props) {
- return {
- min: Math.min(props.min, minMax.min),
- max: Math.max(props.max, minMax.max)
- };
- }, { min: Infinity, max: -Infinity });
- }
-
- /**
- * Get attributes off the data, e.g. `tooltipFormatter` or `xAxisFormatter`
- * pulls the value off the first item in the array
- * these values are typically the same between data objects of the same chart
- * TODO: May need to verify this or refactor
- *
- * @method get
- * @param thing {String} Data object key
- * @returns {*} Data object value
- */
- get(thing, def) {
- const source = (this.data.rows || this.data.columns || [this.data])[0];
- return _.get(source, thing, def);
- }
-
- /**
- * Return an array of all value objects
- * Pluck the data.series array from each data object
- * Create an array of all the value objects from the series array
- *
- * @method flatten
- * @returns {Array} Value objects
- */
- flatten() {
- return _(this.chartData())
- .pluck('series')
- .flattenDeep()
- .pluck('values')
- .flattenDeep()
- .value();
- }
-
- /**
- * ensure that the datas ordered property has a min and max
- * if the data represents an ordered date range.
- *
- * @return {undefined}
- */
- _normalizeOrdered() {
- const data = this.getVisData();
- const self = this;
-
- data.forEach(function (d) {
- if (!d.ordered || !d.ordered.date) return;
-
- const missingMin = d.ordered.min == null;
- const missingMax = d.ordered.max == null;
-
- if (missingMax || missingMin) {
- const extent = d3.extent(self.xValues());
- if (missingMin) d.ordered.min = extent[0];
- if (missingMax) d.ordered.max = extent[1];
- }
- });
- }
-
- /**
- * Calculates min and max values for all map data
- * series.rows is an array of arrays
- * each row is an array of values
- * last value in row array is bucket count
- *
- * @method mapDataExtents
- * @param series {Array} Array of data objects
- * @returns {Array} min and max values
- */
- mapDataExtents(series) {
- const values = _.map(series.rows, function (row) {
- return row[row.length - 1];
- });
- return [_.min(values), _.max(values)];
- }
-
- /**
- * Get the maximum number of series, considering each chart
- * individually.
- *
- * @return {number} - the largest number of series from all charts
- */
- maxNumberOfSeries() {
- return this.chartData().reduce(function (max, chart) {
- return Math.max(max, chart.series.length);
- }, 0);
- }
- }
-
- return Data;
-}
diff --git a/src/ui/public/vis_maps/lib/dispatch.js b/src/ui/public/vis_maps/lib/dispatch.js
deleted file mode 100644
index 68eec629892d9..0000000000000
--- a/src/ui/public/vis_maps/lib/dispatch.js
+++ /dev/null
@@ -1,299 +0,0 @@
-import d3 from 'd3';
-import _ from 'lodash';
-import $ from 'jquery';
-import SimpleEmitter from 'ui/utils/simple_emitter';
-
-export default function DispatchClass(Private, config) {
-
- /**
- * Handles event responses
- *
- * @class Dispatch
- * @constructor
- * @param handler {Object} Reference to Handler Class Object
- */
-
- class Dispatch extends SimpleEmitter {
- constructor(handler) {
- super();
- this.handler = handler;
- this._listeners = {};
- }
-
- /**
- * Response to click and hover events
- *
- * @param d {Object} Data point
- * @param i {Number} Index number of data point
- * @returns {{value: *, point: *, label: *, color: *, pointIndex: *,
- * series: *, config: *, data: (Object|*),
- * e: (d3.event|*), handler: (Object|*)}} Event response object
- */
- eventResponse(d, i) {
- const datum = d._input || d;
- const data = d3.event.target.nearestViewportElement ?
- d3.event.target.nearestViewportElement.__data__ : d3.event.target.__data__;
- const label = d.label ? d.label : (d.series || 'Count');
- const isSeries = !!(data && data.series);
- const isSlices = !!(data && data.slices);
- const series = isSeries ? data.series : undefined;
- const slices = isSlices ? data.slices : undefined;
- const handler = this.handler;
- const color = _.get(handler, 'data.color');
- const isPercentage = (handler && handler.visConfig.get('mode', 'normal') === 'percentage');
-
- const eventData = {
- value: d.y,
- point: datum,
- datum: datum,
- label: label,
- color: color ? color(label) : undefined,
- pointIndex: i,
- series: series,
- slices: slices,
- config: handler && handler.visConfig,
- data: data,
- e: d3.event,
- handler: handler
- };
-
- if (isSeries) {
- // Find object with the actual d value and add it to the point object
- const object = _.find(series, { 'label': label });
- if (object) {
- eventData.value = +object.values[i].y;
-
- if (isPercentage) {
- // Add the formatted percentage to the point object
- eventData.percent = (100 * d.y).toFixed(1) + '%';
- }
- }
- }
-
- return eventData;
- }
-
- /**
- * Returns a function that adds events and listeners to a D3 selection
- *
- * @method addEvent
- * @param event {String}
- * @param callback {Function}
- * @returns {Function}
- */
- addEvent(event, callback) {
- return function (selection) {
- selection.each(function () {
- const element = d3.select(this);
-
- if (typeof callback === 'function') {
- return element.on(event, callback);
- }
- });
- };
- }
-
- /**
- *
- * @method addHoverEvent
- * @returns {Function}
- */
- addHoverEvent() {
- const self = this;
- const isClickable = this.listenerCount('click') > 0;
- const addEvent = this.addEvent;
- const $el = this.handler.el;
- if (!this.handler.highlight) {
- this.handler.highlight = self.highlight;
- }
-
- function hover(d, i) {
- // Add pointer if item is clickable
- if (isClickable) {
- self.addMousePointer.call(this, arguments);
- }
-
- self.handler.highlight.call(this, $el);
- self.emit('hover', self.eventResponse(d, i));
- }
-
- return addEvent('mouseover', hover);
- }
-
- /**
- *
- * @method addMouseoutEvent
- * @returns {Function}
- */
- addMouseoutEvent() {
- const self = this;
- const addEvent = this.addEvent;
- const $el = this.handler.el;
- if (!this.handler.unHighlight) {
- this.handler.unHighlight = self.unHighlight;
- }
-
- function mouseout() {
- self.handler.unHighlight.call(this, $el);
- }
-
- return addEvent('mouseout', mouseout);
- }
-
- /**
- *
- * @method addClickEvent
- * @returns {Function}
- */
- addClickEvent() {
- const self = this;
- const addEvent = this.addEvent;
-
- function click(d, i) {
- self.emit('click', self.eventResponse(d, i));
- }
-
- return addEvent('click', click);
- }
-
- /**
- * Determine if we will allow brushing
- *
- * @method allowBrushing
- * @returns {Boolean}
- */
- allowBrushing() {
- const xAxis = this.handler.categoryAxes[0];
-
- //Allow brushing for ordered axis - date histogram and histogram
- return Boolean(xAxis.ordered);
- }
-
- /**
- * Determine if brushing is currently enabled
- *
- * @method isBrushable
- * @returns {Boolean}
- */
- isBrushable() {
- return this.allowBrushing() && this.listenerCount('brush') > 0;
- }
-
- /**
- * Mouseover Behavior
- *
- * @method addMousePointer
- * @returns {d3.Selection}
- */
- addMousePointer() {
- return d3.select(this).style('cursor', 'pointer');
- }
-
- /**
- * Highlight the element that is under the cursor
- * by reducing the opacity of all the elements on the graph.
- * @param element {d3.Selection}
- * @method highlight
- */
- highlight(element) {
- const label = this.getAttribute('data-label');
- if (!label) return;
-
- const dimming = config.get('visualization:dimmingOpacity');
- $(element).parent().find('[data-label]')
- .css('opacity', 1)//Opacity 1 is needed to avoid the css application
- .not((els, el) => String($(el).data('label')) === label)
- .css('opacity', justifyOpacity(dimming));
- }
-
- /**
- * Mouseout Behavior
- *
- * @param element {d3.Selection}
- * @method unHighlight
- */
- unHighlight(element) {
- $('[data-label]', element.parentNode).css('opacity', 1);
- }
-
- /**
- * Adds D3 brush to SVG and returns the brush function
- *
- * @param xScale {Function} D3 xScale function
- * @param svg {HTMLElement} Reference to SVG
- * @returns {*} Returns a D3 brush function and a SVG with a brush group attached
- */
- createBrush(xScale, svg) {
- const self = this;
- const visConfig = self.handler.visConfig;
- const { width, height } = svg.node().getBBox();
- const isHorizontal = self.handler.categoryAxes[0].axisConfig.isHorizontal();
-
- // Brush scale
- const brush = d3.svg.brush();
- if (isHorizontal) {
- brush.x(xScale);
- } else {
- brush.y(xScale);
- }
-
- brush.on('brushend', function brushEnd() {
-
- // Assumes data is selected at the chart level
- // In this case, the number of data objects should always be 1
- const data = d3.select(this).data()[0];
- const isTimeSeries = (data.ordered && data.ordered.date);
-
- // Allows for brushing on d3.scale.ordinal()
- const selected = xScale.domain().filter(function (d) {
- return (brush.extent()[0] <= xScale(d)) && (xScale(d) <= brush.extent()[1]);
- });
- const range = isTimeSeries ? brush.extent() : selected;
-
- return self.emit('brush', {
- range: range,
- config: visConfig,
- e: d3.event,
- data: data
- });
- });
-
- // if `addBrushing` is true, add brush canvas
- if (self.listenerCount('brush')) {
- const rect = svg.insert('g', 'g')
- .attr('class', 'brush')
- .call(brush)
- .call(function (brushG) {
- // hijack the brush start event to filter out right/middle clicks
- const brushHandler = brushG.on('mousedown.brush');
- if (!brushHandler) return; // touch events in use
- brushG.on('mousedown.brush', function () {
- if (validBrushClick(d3.event)) brushHandler.apply(this, arguments);
- });
- })
- .selectAll('rect');
-
- if (isHorizontal) {
- rect.attr('height', height);
- } else {
- rect.attr('width', width);
- }
-
- return brush;
- }
- }
- }
-
- function validBrushClick(event) {
- return event.button === 0;
- }
-
-
- function justifyOpacity(opacity) {
- const decimalNumber = parseFloat(opacity, 10);
- const fallbackOpacity = 0.5;
- return (0 <= decimalNumber && decimalNumber <= 1) ? decimalNumber : fallbackOpacity;
- }
-
- return Dispatch;
-}
diff --git a/src/ui/public/vis_maps/lib/layout.js b/src/ui/public/vis_maps/lib/layout.js
deleted file mode 100644
index 24402f960c136..0000000000000
--- a/src/ui/public/vis_maps/lib/layout.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import d3 from 'd3';
-import MapSplitProvider from './splits/map_split';
-
-export default function LayoutFactory(Private) {
- const mapSplit = Private(MapSplitProvider);
- class Layout {
- constructor(el, config, data) {
- this.el = el;
- this.config = config;
- this.data = data;
- }
-
- render() {
- this.removeAll();
- this.createLayout();
- }
-
- createLayout() {
- const wrapper = this.appendElem(this.el, 'div', 'vis-wrapper');
- wrapper.datum(this.data.data);
- const colWrapper = this.appendElem(wrapper.node(), 'div', 'vis-col-wrapper');
- const chartWrapper = this.appendElem(colWrapper.node(), 'div', 'chart-wrapper');
- chartWrapper.call(mapSplit, colWrapper.node(), this.config);
- }
-
- appendElem(el, type, className) {
- if (!el || !type || !className) {
- throw new Error('Function requires that an el, type, and class be provided');
- }
-
- if (typeof el === 'string') {
- // Create a DOM reference with a d3 selection
- // Need to make sure that the `el` is bound to this object
- // to prevent it from being appended to another Layout
- el = d3.select(this.el)
- .select(el)[0][0];
- }
-
- return d3.select(el)
- .append(type)
- .attr('class', className);
- }
-
- removeAll() {
- return d3.select(this.el).selectAll('*').remove();
- }
- }
-
- return Layout;
-}
diff --git a/src/ui/public/vis_maps/lib/maps_config.js b/src/ui/public/vis_maps/lib/maps_config.js
deleted file mode 100644
index 3e5a036a4ca55..0000000000000
--- a/src/ui/public/vis_maps/lib/maps_config.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * Provides vislib configuration, throws error if invalid property is accessed without providing defaults
- */
-import _ from 'lodash';
-
-export default function MapsConfigFactory() {
-
- const DEFAULT_VIS_CONFIG = {
- style: {
- margin : { top: 10, right: 3, bottom: 5, left: 3 }
- },
- alerts: {},
- categoryAxes: [],
- valueAxes: []
- };
-
-
- class MapsConfig {
- constructor(mapsConfigArgs) {
- this._values = _.defaultsDeep({}, mapsConfigArgs, DEFAULT_VIS_CONFIG);
- }
-
- get(property, defaults) {
- if (_.has(this._values, property) || typeof defaults !== 'undefined') {
- return _.get(this._values, property, defaults);
- } else {
- throw new Error(`Accessing invalid config property: ${property}`);
- return defaults;
- }
- }
-
- set(property, value) {
- return _.set(this._values, property, value);
- }
- }
-
- return MapsConfig;
-}
diff --git a/src/ui/public/vis_maps/lib/splits/map_split.js b/src/ui/public/vis_maps/lib/splits/map_split.js
deleted file mode 100644
index 710c77083927a..0000000000000
--- a/src/ui/public/vis_maps/lib/splits/map_split.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import d3 from 'd3';
-define(function () {
- return function ChartSplitFactory() {
-
- /*
- * Adds div DOM elements to the `.chart-wrapper` element based on the data layout.
- * For example, if the data has rows, it returns the same number of
- * `.chart` elements as row objects.
- */
- return function split(selection) {
- selection.each(function (data) {
- const div = d3.select(this)
- .attr('class', function () {
- // Determine the parent class
- if (data.rows) {
- return 'chart-wrapper-row';
- } else if (data.columns) {
- return 'chart-wrapper-column';
- } else {
- return 'chart-wrapper';
- }
- });
- let divClass;
-
- const charts = div.selectAll('charts')
- .append('div')
- .data(function (d) {
- // Determine the child class
- if (d.rows) {
- divClass = 'chart-row';
- return d.rows;
- } else if (d.columns) {
- divClass = 'chart-column';
- return d.columns;
- } else {
- divClass = 'chart';
- return [d];
- }
- })
- .enter()
- .append('div')
- .attr('class', function () {
- return divClass;
- });
-
- if (!data.geoJson) {
- charts.call(split);
- }
- });
- };
- };
-});
diff --git a/src/ui/public/vis_maps/lib/tilemap_settings.js b/src/ui/public/vis_maps/lib/tilemap_settings.js
index 0cda8efd7444e..610c4e480f197 100644
--- a/src/ui/public/vis_maps/lib/tilemap_settings.js
+++ b/src/ui/public/vis_maps/lib/tilemap_settings.js
@@ -1,7 +1,6 @@
import uiModules from 'ui/modules';
import _ from 'lodash';
import marked from 'marked';
-import uiRoutes from 'ui/routes';
import { modifyUrl } from 'ui/url';
marked.setOptions({
@@ -9,16 +8,6 @@ marked.setOptions({
sanitize: true // Sanitize HTML tags
});
-/**
- * Reloads the setting for each route,
- * This is to ensure, that if the license changed during the lifecycle of the application,
- * we get an update.
- * tilemapSettings itself will take care that the manifest-service is not queried when not necessary.
- */
-uiRoutes.afterSetupWork(function (tilemapSettings) {
- return tilemapSettings.loadSettings();
-});
-
uiModules.get('kibana')
.service('tilemapSettings', function ($http, tilemapsConfig, $sanitize, kbnVersion) {
const attributionFromConfig = $sanitize(marked(tilemapsConfig.deprecated.config.options.attribution || ''));
@@ -190,6 +179,10 @@ uiModules.get('kibana')
}
+ isInitialized() {
+ return this._settingsInitialized;
+ }
+
/**
* Checks if there was an error during initialization of the parameters
diff --git a/src/ui/public/vis_maps/maps.js b/src/ui/public/vis_maps/maps.js
deleted file mode 100644
index 5a23cf13a48d5..0000000000000
--- a/src/ui/public/vis_maps/maps.js
+++ /dev/null
@@ -1,123 +0,0 @@
-import _ from 'lodash';
-import $ from 'jquery';
-import d3 from 'd3';
-import MapsConfigProvider from './lib/maps_config';
-import TileMapChartProvider from './visualizations/tile_map';
-import EventsProvider from 'ui/events';
-import MapsDataProvider from './lib/data';
-import LayoutProvider from './lib/layout';
-import './styles/_tilemap.less';
-
-export default function MapsFactory(Private) {
- const Events = Private(EventsProvider);
- const MapsConfig = Private(MapsConfigProvider);
- const TileMapChart = Private(TileMapChartProvider);
- const Data = Private(MapsDataProvider);
- const Layout = Private(LayoutProvider);
-
- class Maps extends Events {
- constructor($el, vis, mapsConfigArgs) {
- super(arguments);
- this.el = $el.get ? $el.get(0) : $el;
- this.vis = vis;
- this.mapsConfigArgs = mapsConfigArgs;
-
- // memoize so that the same function is returned every time,
- // allowing us to remove/re-add the same function
- this.getProxyHandler = _.memoize(function (event) {
- const self = this;
- return function (e) {
- self.emit(event, e);
- };
- });
-
- this.enable = this.chartEventProxyToggle('on');
- this.disable = this.chartEventProxyToggle('off');
- }
-
- chartEventProxyToggle(method) {
- return function (event, chart) {
- const proxyHandler = this.getProxyHandler(event);
-
- _.each(chart ? [chart] : this.charts, function (chart) {
- chart.events[method](event, proxyHandler);
- });
- };
- }
-
- on(event, listener) {
- const first = this.listenerCount(event) === 0;
- const ret = Events.prototype.on.call(this, event, listener);
- const added = this.listenerCount(event) > 0;
-
- // if this is the first listener added for the event
- // enable the event in the handler
- if (first && added && this.handler) this.handler.enable(event);
-
- return ret;
- }
-
- off(event, listener) {
- const last = this.listenerCount(event) === 1;
- const ret = Events.prototype.off.call(this, event, listener);
- const removed = this.listenerCount(event) === 0;
-
- // Once all listeners are removed, disable the events in the handler
- if (last && removed && this.handler) this.handler.disable(event);
- return ret;
- }
-
- render(data, uiState) {
- if (!data) {
- throw new Error('No valid data!');
- }
-
- this.uiState = uiState;
- this.data = new Data(data, this.uiState);
- this.visConfig = new MapsConfig(this.mapsConfigArgs, this.data, this.uiState);
- this.layout = new Layout(this.el, this.visConfig, this.data);
- this.draw();
- }
-
- destroy() {
- this.charts.forEach(chart => chart.destroy());
- d3.select(this.el).selectAll('*').remove();
- }
-
- draw() {
- // Destroy the charts before they get removed from the DOM on the new
- // layout render.
- if(this.charts !== undefined) {
- this.charts.forEach(chart => chart.destroy());
- }
-
- this.layout.render();
- const self = this;
- this.charts = [];
-
-
- let loadedCount = 0;
- const chartSelection = d3.select(this.el).selectAll('.chart');
- chartSelection.each(function (chartData) {
- const chart = new TileMapChart(self, this, chartData);
-
- self.activeEvents().forEach(function (event) {
- self.enable(event, chart);
- });
-
- self.charts.push(chart);
- chart.render();
-
- chart.events.on('rendered', function () {
- loadedCount++;
- if (loadedCount === chartSelection.length) {
- $(self.el).trigger('renderComplete');
- }
- });
- });
- }
-
- }
-
- return Maps;
-}
diff --git a/src/ui/public/vis_maps/maps_renderbot.js b/src/ui/public/vis_maps/maps_renderbot.js
index 19a3cd7921655..b2c34c5dc9aaa 100644
--- a/src/ui/public/vis_maps/maps_renderbot.js
+++ b/src/ui/public/vis_maps/maps_renderbot.js
@@ -1,86 +1,237 @@
+import $ from 'jquery';
import _ from 'lodash';
-import MapsProvider from 'ui/vis_maps/maps';
import VisRenderbotProvider from 'ui/vis/renderbot';
import MapsVisTypeBuildChartDataProvider from 'ui/vislib_vis_type/build_chart_data';
+import FilterBarPushFilterProvider from 'ui/filter_bar/push_filter';
+import KibanaMap from './kibana_map';
+import GeohashLayer from './geohash_layer';
+import './lib/tilemap_settings';
+import './styles/_tilemap.less';
+import { ResizeCheckerProvider } from 'ui/resize_checker';
-module.exports = function MapsRenderbotFactory(Private, $injector, tilemapSettings, Notifier) {
- const AngularPromise = $injector.get('Promise');
- const Maps = Private(MapsProvider);
+
+module.exports = function MapsRenderbotFactory(Private, $injector, tilemapSettings, Notifier, courier, getAppState) {
+
+ const ResizeChecker = Private(ResizeCheckerProvider);
const Renderbot = Private(VisRenderbotProvider);
const buildChartData = Private(MapsVisTypeBuildChartDataProvider);
- const notify = new Notifier({
- location: 'Tilemap'
- });
-
- _.class(MapsRenderbot).inherits(Renderbot);
- function MapsRenderbot(vis, $el, uiState) {
- MapsRenderbot.Super.call(this, vis, $el, uiState);
- this._createVis();
- }
+ const notify = new Notifier({ location: 'Tilemap' });
+
+ class MapsRenderbot extends Renderbot {
+
+ constructor(vis, $el, uiState) {
+ super(vis, $el, uiState);
+ this._buildChartData = buildChartData.bind(this);
+ this._geohashLayer = null;
+ this._kibanaMap = null;
+ this._kibanaMapReady = this._makeKibanaMap($el);
+
+ this._baseLayerDirty = true;
+ this._dataDirty = true;
+ this._paramsDirty = true;
- MapsRenderbot.prototype._createVis = function () {
- if (tilemapSettings.getError()) {
- //Still allow the visualization to be build, but show a toast that there was a problem retrieving map settings
- //Even though the basemap will not display, the user will at least still see the overlay data
- notify.warning(tilemapSettings.getError().message);
+
+ this._resizeChecker = new ResizeChecker($el);
+ this._resizeChecker.on('resize', () => {
+ if (this._kibanaMap) {
+ this._kibanaMap.resize();
+ }
+ });
}
- if (this.mapsVis) this.destroy();
- this.mapsParams = this._getMapsParams();
- this.mapsVis = new Maps(this.$el[0], this.vis, this.mapsParams);
- _.each(this.vis.listeners, (listener, event) => {
- this.mapsVis.on(event, listener);
- });
+ async _makeKibanaMap($el) {
+
+ if (!tilemapSettings.isInitialized()) {
+ await tilemapSettings.loadSettings();
+ }
+
+ if (tilemapSettings.getError()) {
+ //Still allow the visualization to be built, but show a toast that there was a problem retrieving map settings
+ //Even though the basemap will not display, the user will at least still see the overlay data
+ notify.warning(tilemapSettings.getError().message);
+ }
+
+ const containerElement = $($el)[0];
+ const minMaxZoom = tilemapSettings.getMinMaxZoom(false);
+ this._kibanaMap = new KibanaMap(containerElement, minMaxZoom);
+ this._kibanaMap.addDrawControl();
+ this._kibanaMap.addFitControl();
+ this._kibanaMap.addLegendControl();
+
+ this._kibanaMap.persistUiStateForVisualization(this.vis);
+ this._kibanaMap.useUiStateFromVisualization(this.vis);
+
+ let previousPrecision = this._kibanaMap.getAutoPrecision();
+ let precisionChange = false;
+ this._kibanaMap.on('zoomchange', () => {
+ precisionChange = (previousPrecision !== this._kibanaMap.getAutoPrecision());
+ previousPrecision = this._kibanaMap.getAutoPrecision();
+ });
+ this._kibanaMap.on('zoomend', () => {
+
+ const isAutoPrecision = _.get(this._chartData, 'geohashGridAgg.params.autoPrecision', true);
+ if (!isAutoPrecision) {
+ return;
+ }
+
+ this._dataDirty = true;
+ if (precisionChange) {
+ courier.fetch();
+ } else {
+ this._recreateGeohashLayer();
+ this._dataDirty = false;
+ this._doRenderComplete();
+ }
+ });
+
+
+ this._kibanaMap.on('drawCreated:rectangle', event => {
+ addSpatialFilter(_.get(this._chartData, 'geohashGridAgg'), 'geo_bounding_box', event.bounds);
+ });
+ this._kibanaMap.on('baseLayer:loaded', () => {
+ this._baseLayerDirty = false;
+ this._doRenderComplete();
+ });
+ this._kibanaMap.on('baseLayer:loading', () => {
+ this._baseLayerDirty = true;
+ });
+ }
- if (this.mapsData) {
- this.mapsVis.render(this.mapsData, this.uiState);
+ _recreateGeohashLayer() {
+ if (this._geohashLayer) {
+ this._kibanaMap.removeLayer(this._geohashLayer);
+ }
+ if (!this._geohashGeoJson) {
+ return;
+ }
+ const geohashOptions = this._getGeohashOptions();
+ this._geohashLayer = new GeohashLayer(this._chartData.geoJson, geohashOptions, this._kibanaMap.getZoomLevel(), this._kibanaMap);
+ this._kibanaMap.addLayer(this._geohashLayer);
}
- };
- MapsRenderbot.prototype._getMapsParams = function () {
- const self = this;
- return _.assign(
- {},
- self.vis.type.params.defaults,
- {
- type: self.vis.type.name,
- // Add attribute which determines whether an index is time based or not.
- hasTimeField: self.vis.indexPattern && self.vis.indexPattern.hasTimeField()
- },
- self.vis.params
- );
- };
+ /**
+ * called on data change
+ * @param esResponse
+ */
+ render(esResponse) {
+ this._dataDirty = true;
+ this._kibanaMapReady.then(() => {
+ this._chartData = this._buildChartData(esResponse);
+ this._geohashGeoJson = this._chartData.geoJson;
+ this._recreateGeohashLayer();
+ this._kibanaMap.useUiStateFromVisualization(this.vis);
+ this._kibanaMap.resize();
+ this._dataDirty = false;
+ this._doRenderComplete();
+ });
+ }
- MapsRenderbot.prototype.buildChartData = buildChartData;
- MapsRenderbot.prototype.render = function (esResponse) {
- this.mapsData = this.buildChartData(esResponse);
- return AngularPromise.delay(1).then(() => {
- this.mapsVis.render(this.mapsData, this.uiState);
- });
- };
+ destroy() {
+ if (this._kibanaMap) {
+ this._kibanaMap.destroy();
+ }
+ }
- MapsRenderbot.prototype.destroy = function () {
- const self = this;
+ /**
+ * called on options change (vis.params change)
+ */
+ updateParams() {
+
+ this._paramsDirty = true;
+ this._kibanaMapReady.then(() => {
+ const mapParams = this._getMapsParams();
+ if (mapParams.wms.enabled) {
+ const { minZoom, maxZoom } = tilemapSettings.getMinMaxZoom(true);
+ this._kibanaMap.setBaseLayer({
+ baseLayerType: 'wms',
+ options: {
+ minZoom: minZoom,
+ maxZoom: maxZoom,
+ url: mapParams.wms.url,
+ ...mapParams.wms.options
+ }
+ });
+ } else {
+ if (!tilemapSettings.hasError()) {
+ const url = tilemapSettings.getUrl();
+ const options = tilemapSettings.getTMSOptions();
+ this._kibanaMap.setBaseLayer({
+ baseLayerType: 'tms',
+ options: { url, ...options }
+ });
+ }
+ }
+ const geohashOptions = this._getGeohashOptions();
+ if (!this._geohashLayer || !this._geohashLayer.isReusable(geohashOptions)) {
+ this._recreateGeohashLayer();
+ }
+
+ this._kibanaMap.setDesaturateBaseLayer(mapParams.isDesaturated);
+ this._kibanaMap.setShowTooltip(mapParams.addTooltip);
+ this._kibanaMap.setLegendPosition(mapParams.legendPosition);
+
+ this._kibanaMap.useUiStateFromVisualization(this.vis);
+ this._kibanaMap.resize();
+ this._paramsDirty = false;
+ this._doRenderComplete();
+ });
+ }
- const mapsVis = self.mapsVis;
+ _getMapsParams() {
+ return _.assign(
+ {},
+ this.vis.type.params.defaults,
+ {
+ type: this.vis.type.name,
+ hasTimeField: this.vis.indexPattern && this.vis.indexPattern.hasTimeField()// Add attribute which determines whether an index is time based or not.
+ },
+ this.vis.params
+ );
+ }
+
+ _getGeohashOptions() {
+ const newParams = this._getMapsParams();
+ return {
+ valueFormatter: this._chartData ? this._chartData.valueFormatter : null,
+ tooltipFormatter: this._chartData ? this._chartData.tooltipFormatter : null,
+ mapType: newParams.mapType,
+ heatmap: {
+ heatBlur: newParams.heatBlur,
+ heatMaxZoom: newParams.heatMaxZoom,
+ heatMinOpacity: newParams.heatMinOpacity,
+ heatNormalizeData: newParams.heatNormalizeData,
+ heatRadius: newParams.heatRadius
+ }
+ };
+ }
+
+ _doRenderComplete() {
+ if (this._paramsDirty || this._dataDirty || this._baseLayerDirty) {
+ return;
+ }
+ $(this.el).trigger('renderComplete');
+ }
- _.forOwn(self.vis.listeners, function (listener, event) {
- mapsVis.off(event, listener);
- });
+ }
- mapsVis.destroy();
- };
+ function addSpatialFilter(agg, filterName, filterData) {
+ if (!agg) {
+ return;
+ }
- MapsRenderbot.prototype.updateParams = function () {
- const self = this;
+ const indexPatternName = agg.vis.indexPattern.id;
+ const field = agg.fieldName();
+ const filter = {};
+ filter[filterName] = {};
+ filter[filterName][field] = filterData;
- // get full maps params object
- const newParams = self._getMapsParams();
+ const putFilter = Private(FilterBarPushFilterProvider)(getAppState());
+ return putFilter(filter, false, indexPatternName);
+ }
- // if there's been a change, replace the vis
- if (!_.isEqual(newParams, self.mapsParams)) self._createVis();
- };
return MapsRenderbot;
};
+
+
diff --git a/src/ui/public/vis_maps/markers/geohash_grid.js b/src/ui/public/vis_maps/markers/geohash_grid.js
new file mode 100644
index 0000000000000..753125384c5ba
--- /dev/null
+++ b/src/ui/public/vis_maps/markers/geohash_grid.js
@@ -0,0 +1,17 @@
+import L from 'leaflet';
+import ScaledCircles from './scaled_circles';
+
+export default class GeohashGrid extends ScaledCircles {
+ getMarkerFunction() {
+ return function (feature) {
+ const geohashRect = feature.properties.rectangle;
+ // get bounds from northEast[3] and southWest[1]
+ // corners in geohash rectangle
+ const corners = [
+ [geohashRect[3][0], geohashRect[3][1]],
+ [geohashRect[1][0], geohashRect[1][1]]
+ ];
+ return L.rectangle(corners);
+ };
+ }
+}
diff --git a/src/ui/public/vis_maps/markers/heatmap.js b/src/ui/public/vis_maps/markers/heatmap.js
new file mode 100644
index 0000000000000..769899ff35096
--- /dev/null
+++ b/src/ui/public/vis_maps/markers/heatmap.js
@@ -0,0 +1,197 @@
+import L from 'leaflet';
+import _ from 'lodash';
+import d3 from 'd3';
+import { EventEmitter } from 'events';
+
+/**
+ * Map overlay: canvas layer with leaflet.heat plugin
+ *
+ * @param map {Leaflet Object}
+ * @param geoJson {geoJson Object}
+ * @param params {Object}
+ */
+export default class Heatmap extends EventEmitter {
+
+ constructor(featureCollection, options, zoom) {
+
+ super();
+ this._geojsonFeatureCollection = featureCollection;
+ const max = _.get(featureCollection, 'properties.max');
+ const points = dataToHeatArray(max, options.heatNormalizeData, featureCollection);
+ this._leafletLayer = L.heatLayer(points, options);
+ this._tooltipFormatter = options.tooltipFormatter;
+ this._zoom = zoom;
+ this._disableTooltips = false;
+ this._getLatLng = _.memoize(function (feature) {
+ return L.latLng(
+ feature.geometry.coordinates[1],
+ feature.geometry.coordinates[0]
+ );
+ }, function (feature) {
+ // turn coords into a string for the memoize cache
+ return [feature.geometry.coordinates[1], feature.geometry.coordinates[0]].join(',');
+ });
+
+ this._currentFeature = null;
+ this._addTooltips();
+ }
+
+ getBounds() {
+ return this._leafletLayer.getBounds();
+ }
+
+ getLeafletLayer() {
+ return this._leafletLayer;
+ }
+
+ appendLegendContents() {
+ }
+
+
+ movePointer(type, event) {
+ if (type === 'mousemove') {
+ this._deboundsMoveMoveLocation(event);
+ } else if (type === 'mouseout') {
+ this.emit('hideTooltip');
+ } else if (type === 'mousedown') {
+ this._disableTooltips = true;
+ this.emit('hideTooltip');
+ } else if (type === 'mouseup') {
+ this._disableTooltips = false;
+ }
+ }
+
+
+ _addTooltips() {
+
+ const mouseMoveLocation = (e) => {
+
+
+
+ if (!this._geojsonFeatureCollection.features.length || this._disableTooltips) {
+ this.emit('hideTooltip');
+ return;
+ }
+
+ const feature = this._nearestFeature(e.latlng);
+ if (this._tooltipProximity(e.latlng, feature)) {
+ const content = this._tooltipFormatter(feature);
+ if (!content) {
+ return;
+ }
+ this.emit('showTooltip', {
+ content: content,
+ position: e.latlng
+ });
+ } else { this.emit('hideTooltip');
+ }
+ };
+
+ this._deboundsMoveMoveLocation = _.debounce(mouseMoveLocation.bind(this), 15, {
+ 'leading': true,
+ 'trailing': false
+ });
+ }
+
+ /**
+ * Finds nearest feature in mapData to event latlng
+ *
+ * @method _nearestFeature
+ * @param latLng {Leaflet latLng}
+ * @return nearestPoint {Leaflet latLng}
+ */
+ _nearestFeature(latLng) {
+ const self = this;
+ let nearest;
+
+ if (latLng.lng < -180 || latLng.lng > 180) {
+ return;
+ }
+
+ _.reduce(this._geojsonFeatureCollection.features, function (distance, feature) {
+ const featureLatLng = self._getLatLng(feature);
+ const dist = latLng.distanceTo(featureLatLng);
+
+ if (dist < distance) {
+ nearest = feature;
+ return dist;
+ }
+
+ return distance;
+ }, Infinity);
+
+ return nearest;
+ }
+
+ /**
+ * display tooltip if feature is close enough to event latlng
+ *
+ * @method _tooltipProximity
+ * @param latlng {Leaflet latLng Object}
+ * @param feature {geoJson Object}
+ * @return {Boolean}
+ */
+ _tooltipProximity(latlng, feature) {
+ if (!feature) return;
+
+ let showTip = false;
+ const featureLatLng = this._getLatLng(feature);
+
+ // zoomScale takes map zoom and returns proximity value for tooltip display
+ // domain (input values) is map zoom (min 1 and max 18)
+ // range (output values) is distance in meters
+ // used to compare proximity of event latlng to feature latlng
+ const zoomScale = d3.scale.linear()
+ .domain([1, 4, 7, 10, 13, 16, 18])
+ .range([1000000, 300000, 100000, 15000, 2000, 150, 50]);
+
+ const proximity = zoomScale(this._zoom);
+ const distance = latlng.distanceTo(featureLatLng);
+
+ // maxLngDif is max difference in longitudes
+ // to prevent feature tooltip from appearing 360°
+ // away from event latlng
+ const maxLngDif = 40;
+ const lngDif = Math.abs(latlng.lng - featureLatLng.lng);
+
+ if (distance < proximity && lngDif < maxLngDif) {
+ showTip = true;
+ }
+
+ d3.scale.pow().exponent(0.2)
+ .domain([1, 18])
+ .range([1500000, 50]);
+ return showTip;
+ }
+
+}
+
+
+
+/**
+ * returns data for data for heat map intensity
+ * if heatNormalizeData attribute is checked/true
+ • normalizes data for heat map intensity
+ *
+ * @method _dataToHeatArray
+ * @param max {Number}
+ * @return {Array}
+ */
+function dataToHeatArray(max, heatNormalizeData, featureCollection) {
+
+ return featureCollection.features.map((feature) => {
+ const lat = feature.geometry.coordinates[1];
+ const lng = feature.geometry.coordinates[0];
+ let heatIntensity;
+ if (!heatNormalizeData) {
+ // show bucket value on heatmap
+ heatIntensity = feature.properties.value;
+ } else {
+ // show bucket value normalized to max value
+ heatIntensity = feature.properties.value / max;
+ }
+
+ return [lat, lng, heatIntensity];
+ });
+}
+
diff --git a/src/ui/public/vis_maps/markers/scaled_circles.js b/src/ui/public/vis_maps/markers/scaled_circles.js
new file mode 100644
index 0000000000000..cc1c2504128de
--- /dev/null
+++ b/src/ui/public/vis_maps/markers/scaled_circles.js
@@ -0,0 +1,231 @@
+import L from 'leaflet';
+import _ from 'lodash';
+import d3 from 'd3';
+import $ from 'jquery';
+import { EventEmitter } from 'events';
+
+export default class ScaledCircles extends EventEmitter {
+
+ constructor(featureCollection, options, targetZoom, kibanaMap) {
+ super();
+ this._geohashGeoJson = featureCollection;
+ this._zoom = targetZoom;
+
+ this._valueFormatter = options.valueFormatter;
+ this._tooltipFormatter = options.tooltipFormatter;
+ this._map = options.map;
+
+ this._legendColors = null;
+ this._legendQuantizer = null;
+
+ this._popups = [];
+ this._leafletLayer = L.geoJson(null, {
+ pointToLayer: this.getMarkerFunction(),
+ style: this.getStyleFunction(),
+ onEachFeature: (feature, layer) => {
+ this._bindPopup(feature, layer);
+ },
+ filter: (feature) => {
+ const bucketRectBounds = _.get(feature, 'properties.rectangle');
+ return kibanaMap.isInside(bucketRectBounds);
+ }
+ });
+ this._leafletLayer.addData(this._geohashGeoJson);
+ }
+
+ getLeafletLayer() {
+ return this._leafletLayer;
+ }
+
+
+ getStyleFunction() {
+ const min = _.get(this._geohashGeoJson, 'properties.min', 0);
+ const max = _.get(this._geohashGeoJson, 'properties.max', 1);
+
+ const quantizeDomain = (min !== max) ? [min, max] : d3.scale.quantize().domain();
+ this._legendColors = makeCircleMarkerLegendColors(min, max);
+ this._legendQuantizer = d3.scale.quantize().domain(quantizeDomain).range(this._legendColors);
+
+ return makeStyleFunction(min, max, this._legendColors, quantizeDomain);
+ }
+
+
+ movePointer() {
+ }
+
+ getLabel() {
+ if (this._popups.length) {
+ return this._popups[0].feature.properties.aggConfigResult.aggConfig.makeLabel();
+ }
+ return '';
+ }
+
+
+ appendLegendContents(jqueryDiv) {
+
+ if (!this._legendColors || !this._legendQuantizer) {
+ return;
+ }
+
+ const titleText = this.getLabel();
+ const $title = $('
').addClass('tilemap-legend-title').text(titleText);
+ jqueryDiv.append($title);
+
+ this._legendColors.forEach((color) => {
+ const labelText = this._legendQuantizer
+ .invertExtent(color)
+ .map(this._valueFormatter)
+ .join(' – ');
+
+ const label = $('
');
+ const icon = $('
').css({
+ background: color,
+ 'border-color': makeColorDarker(color)
+ });
+
+ const text = $('').text(labelText);
+ label.append(icon);
+ label.append(text);
+
+ jqueryDiv.append(label);
+ });
+
+ }
+
+
+ /**
+ * Binds popup and events to each feature on map
+ *
+ * @method bindPopup
+ * @param feature {Object}
+ * @param layer {Object}
+ * return {undefined}
+ */
+ _bindPopup(feature, layer) {
+ const popup = layer.on({
+ mouseover: (e) => {
+ const layer = e.target;
+ // bring layer to front if not older browser
+ if (!L.Browser.ie && !L.Browser.opera) {
+ layer.bringToFront();
+ }
+ this._showTooltip(feature);
+ },
+ mouseout: () => {
+ this.emit('hideTooltip');
+ }
+ });
+
+ this._popups.push(popup);
+ }
+
+ /**
+ * Checks if event latlng is within bounds of mapData
+ * features and shows tooltip for that feature
+ *
+ * @method _showTooltip
+ * @param feature {LeafletFeature}
+ * @param latLng? {Leaflet latLng}
+ * @return undefined
+ */
+ _showTooltip(feature, latLng) {
+
+ const lat = _.get(feature, 'geometry.coordinates.1');
+ const lng = _.get(feature, 'geometry.coordinates.0');
+ latLng = latLng || L.latLng(lat, lng);
+
+ const content = this._tooltipFormatter(feature);
+ if (!content) {
+ return;
+ }
+
+ this.emit('showTooltip', {
+ content: content,
+ position: latLng
+ });
+ }
+
+ getMarkerFunction() {
+ const scaleFactor = 0.6;
+ return (feature, latlng) => {
+ const value = feature.properties.value;
+ const scaledRadius = this._radiusScale(value) * scaleFactor;
+ return L.circleMarker(latlng).setRadius(scaledRadius);
+ };
+ }
+
+ /**
+ * radiusScale returns a number for scaled circle markers
+ * for relative sizing of markers
+ *
+ * @method _radiusScale
+ * @param value {Number}
+ * @return {Number}
+ */
+ _radiusScale(value) {
+
+ //magic numbers
+ const precisionBiasBase = 5;
+ const precisionBiasNumerator = 200;
+
+ const precision = _.max(this._geohashGeoJson.features.map((feature) => {
+ return String(feature.properties.geohash).length;
+ }));
+
+ const pct = Math.abs(value) / Math.abs(this._geohashGeoJson.properties.max);
+ const zoomRadius = 0.5 * Math.pow(2, this._zoom);
+ const precisionScale = precisionBiasNumerator / Math.pow(precisionBiasBase, precision);
+
+ // square root value percentage
+ return Math.pow(pct, 0.5) * zoomRadius * precisionScale;
+ }
+
+ getBounds() {
+ return this._leafletLayer.getBounds();
+ }
+
+}
+
+
+/**
+ * d3 quantize scale returns a hex color, used for marker fill color
+ *
+ * @method quantizeLegendColors
+ * return {undefined}
+ */
+function makeCircleMarkerLegendColors(min, max) {
+ const reds1 = ['#ff6128'];
+ const reds3 = ['#fecc5c', '#fd8d3c', '#e31a1c'];
+ const reds5 = ['#fed976', '#feb24c', '#fd8d3c', '#f03b20', '#bd0026'];
+ const bottomCutoff = 2;
+ const middleCutoff = 24;
+ let legendColors;
+ if (max - min <= bottomCutoff) {
+ legendColors = reds1;
+ } else if (max - min <= middleCutoff) {
+ legendColors = reds3;
+ } else {
+ legendColors = reds5;
+ }
+ return legendColors;
+}
+
+function makeColorDarker(color) {
+ const amount = 1.3;//magic number, carry over from earlier
+ return d3.hcl(color).darker(amount).toString();
+}
+
+function makeStyleFunction(min, max, legendColors, quantizeDomain) {
+ const legendQuantizer = d3.scale.quantize().domain(quantizeDomain).range(legendColors);
+ return (feature) => {
+ const value = _.get(feature, 'properties.value');
+ const color = legendQuantizer(value);
+ return {
+ fillColor: color,
+ color: makeColorDarker(color),
+ weight: 1.5,
+ opacity: 1,
+ fillOpacity: 0.75
+ };
+ };
+}
diff --git a/src/ui/public/vis_maps/markers/shaded_circles.js b/src/ui/public/vis_maps/markers/shaded_circles.js
new file mode 100644
index 0000000000000..8c67f4fcdf396
--- /dev/null
+++ b/src/ui/public/vis_maps/markers/shaded_circles.js
@@ -0,0 +1,46 @@
+import L from 'leaflet';
+import _ from 'lodash';
+import ScaledCircles from './scaled_circles';
+
+export default class ShadedCircles extends ScaledCircles {
+ getMarkerFunction() {
+ // multiplier to reduce size of all circles
+ const scaleFactor = 0.8;
+ return (feature, latlng) => {
+ const radius = this._geohashMinDistance(feature) * scaleFactor;
+ return L.circle(latlng, radius);
+ };
+ }
+
+
+ /**
+ * _geohashMinDistance returns a min distance in meters for sizing
+ * circle markers to fit within geohash grid rectangle
+ *
+ * @method _geohashMinDistance
+ * @param feature {Object}
+ * @return {Number}
+ */
+ _geohashMinDistance(feature) {
+ const centerPoint = _.get(feature, 'properties.center');
+ const geohashRect = _.get(feature, 'properties.rectangle');
+
+ // centerPoint is an array of [lat, lng]
+ // geohashRect is the 4 corners of the geoHash rectangle
+ // an array that starts at the southwest corner and proceeds
+ // clockwise, each value being an array of [lat, lng]
+
+ // center lat and southeast lng
+ const east = L.latLng([centerPoint[0], geohashRect[2][1]]);
+ // southwest lat and center lng
+ const north = L.latLng([geohashRect[3][0], centerPoint[1]]);
+
+ // get latLng of geohash center point
+ const center = L.latLng([centerPoint[0], centerPoint[1]]);
+
+ // get smallest radius at center of geohash grid rectangle
+ const eastRadius = Math.floor(center.distanceTo(east));
+ const northRadius = Math.floor(center.distanceTo(north));
+ return _.min([eastRadius, northRadius]);
+ }
+}
diff --git a/src/ui/public/vis_maps/visualizations/_chart.js b/src/ui/public/vis_maps/visualizations/_chart.js
deleted file mode 100644
index 59fec8338b314..0000000000000
--- a/src/ui/public/vis_maps/visualizations/_chart.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import d3 from 'd3';
-import VislibLibDispatchProvider from '../lib/dispatch';
-import TooltipProvider from 'ui/vis/components/tooltip';
-export default function ChartBaseClass(Private) {
-
- const Dispatch = Private(VislibLibDispatchProvider);
- const Tooltip = Private(TooltipProvider);
- /**
- * The Base Class for all visualizations.
- *
- * @class Chart
- * @constructor
- * @param handler {Object} Reference to the Handler Class Constructor
- * @param el {HTMLElement} HTML element to which the chart will be appended
- * @param chartData {Object} Elasticsearch query results for this specific chart
- */
- class Chart {
- constructor(handler, el, chartData) {
- this.handler = handler;
- this.chartEl = el;
- this.chartData = chartData;
- this.tooltips = [];
-
- const events = this.events = new Dispatch(handler);
-
- if (this.handler.visConfig && this.handler.visConfig.get('addTooltip', false)) {
- const $el = this.handler.el;
- const formatter = this.handler.data.get('tooltipFormatter');
-
- // Add tooltip
- this.tooltip = new Tooltip('chart', $el, formatter, events);
- this.tooltips.push(this.tooltip);
- }
- }
-
- render() {
- const selection = d3.select(this.chartEl);
- selection.selectAll('*').remove();
- selection.call(this.draw());
- }
-
-
- /**
- * Removes all DOM elements from the root element
- *
- * @method destroy
- */
- destroy() {
- const selection = d3.select(this.chartEl);
- this.events.removeAllListeners();
- this.tooltips.forEach(function (tooltip) {
- tooltip.destroy();
- });
- selection.remove();
- }
- }
-
- return Chart;
-}
diff --git a/src/ui/public/vis_maps/visualizations/_map.js b/src/ui/public/vis_maps/visualizations/_map.js
deleted file mode 100644
index f77279dec63c5..0000000000000
--- a/src/ui/public/vis_maps/visualizations/_map.js
+++ /dev/null
@@ -1,362 +0,0 @@
-import _ from 'lodash';
-import $ from 'jquery';
-import L from 'leaflet';
-import VislibVisualizationsMarkerTypesScaledCirclesProvider from './marker_types/scaled_circles';
-import VislibVisualizationsMarkerTypesShadedCirclesProvider from './marker_types/shaded_circles';
-import VislibVisualizationsMarkerTypesGeohashGridProvider from './marker_types/geohash_grid';
-import VislibVisualizationsMarkerTypesHeatmapProvider from './marker_types/heatmap';
-import '../lib/tilemap_settings';
-
-export default function MapFactory(Private, tilemapSettings) {
- const defaultMapZoom = 2;
- const defaultMapCenter = [15, 5];
- const defaultMarkerType = 'Scaled Circle Markers';
-
- const markerTypes = {
- 'Scaled Circle Markers': Private(VislibVisualizationsMarkerTypesScaledCirclesProvider),
- 'Shaded Circle Markers': Private(VislibVisualizationsMarkerTypesShadedCirclesProvider),
- 'Shaded Geohash Grid': Private(VislibVisualizationsMarkerTypesGeohashGridProvider),
- 'Heatmap': Private(VislibVisualizationsMarkerTypesHeatmapProvider),
- };
-
- /**
- * Tile Map Maps
- *
- * @class Map
- * @constructor
- * @param container {HTML Element} Element to render map into
- * @param chartData {Object} Elasticsearch query results for this map
- * @param params {Object} Parameters used to build a map
- */
- class TileMapMap {
- constructor(container, chartData, params) {
-
- this._container = $(container).get(0);
- this._chartData = chartData;
-
- // keep a reference to all of the optional params
- this._events = _.get(params, 'events');
- this._markerType = markerTypes[params.markerType] ? params.markerType : defaultMarkerType;
- this._valueFormatter = params.valueFormatter || _.identity;
- this._tooltipFormatter = params.tooltipFormatter;
- this._geoJson = _.get(this._chartData, 'geoJson');
- this._attr = params.attr || {};
-
- const { minZoom, maxZoom } = tilemapSettings.getMinMaxZoom(this._isWMSEnabled());
- const zoom = typeof params.zoom === 'number' ? params.zoom : defaultMapZoom;
- this._mapZoom = Math.max(Math.min(zoom, maxZoom), minZoom);
- this._mapCenter = params.center || defaultMapCenter;
- this._createMap();
- }
-
- addBoundingControl() {
- if (this._boundingControl) return;
-
- const self = this;
- const drawOptions = { draw: {} };
-
- _.each(['polyline', 'polygon', 'circle', 'marker', 'rectangle'], function (drawShape) {
- if (self._events && !self._events.listenerCount(drawShape)) {
- drawOptions.draw[drawShape] = false;
- } else {
- drawOptions.draw[drawShape] = {
- shapeOptions: {
- stroke: false,
- color: '#000'
- }
- };
- }
- });
-
- this._boundingControl = new L.Control.Draw(drawOptions);
- this.map.addControl(this._boundingControl);
- }
-
- addFitControl() {
- if (this._fitControl) return;
-
- const self = this;
- const fitContainer = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-control-fit');
-
- // Add button to fit container to points
- const FitControl = L.Control.extend({
- options: {
- position: 'topleft'
- },
- onAdd: function () {
- $(fitContainer).html('')
- .on('click', function (e) {
- e.preventDefault();
- self._fitBounds();
- });
-
- return fitContainer;
- },
- onRemove: function () {
- $(fitContainer).off('click');
- }
- });
-
- this._fitControl = new FitControl();
- this.map.addControl(this._fitControl);
- }
-
- /**
- * Adds label div to each map when data is split
- *
- * @method addTitle
- * @param mapLabel {String}
- * @return {undefined}
- */
- addTitle(mapLabel) {
- if (this._label) return;
-
- const label = this._label = L.control();
-
- label.onAdd = function () {
- this._div = L.DomUtil.create('div', 'tilemap-info tilemap-label');
- this.update();
- return this._div;
- };
- label.update = function () {
- this._div.innerHTML = '' + _.escape(mapLabel) + '
';
- };
-
- // label.addTo(this.map);
- this.map.addControl(label);
- }
-
- /**
- * remove css class for desat filters on map tiles
- *
- * @method saturateTiles
- * @return undefined
- */
- saturateTiles() {
- if (!this._attr.isDesaturated) {
- $('img.leaflet-tile-loaded').addClass('filters-off');
- }
- }
-
- updateSize() {
- this.map.invalidateSize({
- debounceMoveend: true
- });
- }
-
- destroy() {
- if (this._label) this._label.removeFrom(this.map);
- if (this._fitControl) this._fitControl.removeFrom(this.map);
- if (this._boundingControl) this._boundingControl.removeFrom(this.map);
- if (this._markers) this._markers.destroy();
- this.map.remove();
- this.map = undefined;
- }
-
- /**
- * Switch type of data overlay for map:
- * creates featurelayer from mapData (geoJson)
- *
- * @method _addMarkers
- */
- _addMarkers() {
- if (!this._geoJson) return;
- if (this._markers) this._markers.destroy();
-
- this._markers = this._createMarkers({
- tooltipFormatter: this._tooltipFormatter,
- valueFormatter: this._valueFormatter,
- attr: this._attr
- });
-
- if (this._geoJson.features.length > 1) {
- this._markers.addLegend();
- }
- }
-
- /**
- * Create the marker instance using the given options
- *
- * @method _createMarkers
- * @param options {Object} options to give to marker class
- * @return {Object} marker layer
- */
- _createMarkers(options) {
- const MarkerType = markerTypes[this._markerType];
- return new MarkerType(this.map, this._geoJson, options);
- }
-
- _attachEvents() {
- const self = this;
- const saturateTiles = self.saturateTiles.bind(self);
-
- this._tileLayer.on('tileload', saturateTiles);
- this._tileLayer.on('load', () => {
-
- if (!self._events) {
- return;
- }
-
- self._events.emit('rendered', {
- chart: self._chartData,
- map: self.map,
- center: self._mapCenter,
- zoom: self._mapZoom,
- });
- });
-
- this.map.on('unload', function () {
- self._tileLayer.off('tileload', saturateTiles);
- });
-
- this.map.on('moveend', function setZoomCenter() {
- if (!self.map) return;
- // update internal center and zoom references
- const uglyCenter = self.map.getCenter();
- self._mapCenter = [uglyCenter.lat, uglyCenter.lng];
- self._mapZoom = self.map.getZoom();
- self._addMarkers();
-
- if (!self._events) return;
-
- self._events.emit('mapMoveEnd', {
- chart: self._chartData,
- map: self.map,
- center: self._mapCenter,
- zoom: self._mapZoom,
- });
- });
-
- this.map.on('draw:created', function (e) {
- const drawType = e.layerType;
- if (!self._events || !self._events.listenerCount(drawType)) return;
-
- // TODO: Different drawTypes need differ info. Need a switch on the object creation
- const bounds = e.layer.getBounds();
-
- const southEast = bounds.getSouthEast();
- const northWest = bounds.getNorthWest();
- let southEastLng = southEast.lng;
- if (southEastLng > 180) {
- southEastLng -= 360;
- }
- let northWestLng = northWest.lng;
- if (northWestLng < -180) {
- northWestLng += 360;
- }
-
- const southEastLat = southEast.lat;
- const northWestLat = northWest.lat;
-
- //Bounds cannot be created unless they form a box with larger than 0 dimensions
- //Invalid areas are rejected by ES.
- if (southEastLat === northWestLat || southEastLng === northWestLng) {
- return;
- }
-
- self._events.emit(drawType, {
- e: e,
- chart: self._chartData,
- bounds: {
- bottom_right: {
- lat: southEastLat,
- lon: southEastLng
- },
- top_left: {
- lat: northWestLat,
- lon: northWestLng
- }
- }
- });
- });
-
- this.map.on('zoomend', function () {
- if (!self.map) return;
- self._mapZoom = self.map.getZoom();
- if (!self._events) return;
-
- self._events.emit('mapZoomEnd', {
- chart: self._chartData,
- map: self.map,
- zoom: self._mapZoom,
- });
- });
- }
-
- _isWMSEnabled() {
- return this._attr.wms ? this._attr.wms.enabled : false;
- }
-
- _createTileLayer() {
- if (this._isWMSEnabled()) {
- const wmsOpts = this._attr.wms;
- const { minZoom, maxZoom } = tilemapSettings.getMinMaxZoom(true);
- // http://leafletjs.com/reference.html#tilelayer-wms-options
- return L.tileLayer.wms(wmsOpts.url, {
- // user settings
- ...wmsOpts.options,
- minZoom: minZoom,
- maxZoom: maxZoom,
- });
- }
-
- const tileUrl = tilemapSettings.hasError() ? '' : tilemapSettings.getUrl();
- const leafletOptions = tilemapSettings.getTMSOptions();
- return L.tileLayer(tileUrl, leafletOptions);
- }
-
- /**
- * Create the leaflet Map object. In our implementation this is basically just
- * a container for the layer created by `this._createTileLayer()`. User settings
- * are passed as options to the layer and inherited by the map so we can keep
- * this function pretty generic.
- *
- * The map is responsible for the current center and zoom level though, as those
- * are global to each map.
- *
- * @return undefined
- */
- _createMap() {
- if (this.map) this.destroy();
-
- // expose at `this._tileLayer`, `this._attachEvents()` accesses it this way
- this._tileLayer = this._createTileLayer();
-
- // http://leafletjs.com/reference.html#map-options
- this.map = L.map(this._container, {
- center: this._mapCenter,
- zoom: this._mapZoom,
- layers: [this._tileLayer],
- maxBounds: L.latLngBounds([-90, -220], [90, 220]),
- scrollWheelZoom: false,
- fadeAnimation: true,
- });
-
- this._attachEvents();
- this._addMarkers();
- }
-
- /**
- * zoom map to fit all features in featureLayer
- *
- * @method _fitBounds
- * @param map {Leaflet Object}
- * @return {boolean}
- */
- _fitBounds() {
- this.map.fitBounds(this._getDataRectangles());
- }
-
- /**
- * Get the Rectangles representing the geohash grid
- *
- * @return {LatLngRectangles[]}
- */
- _getDataRectangles() {
- if (!this._geoJson) return [];
- return _.pluck(this._geoJson.features, 'properties.rectangle');
- }
- }
-
- return TileMapMap;
-}
diff --git a/src/ui/public/vis_maps/visualizations/marker_types/base_marker.js b/src/ui/public/vis_maps/visualizations/marker_types/base_marker.js
deleted file mode 100644
index 7a6e1deeffc10..0000000000000
--- a/src/ui/public/vis_maps/visualizations/marker_types/base_marker.js
+++ /dev/null
@@ -1,286 +0,0 @@
-import d3 from 'd3';
-import _ from 'lodash';
-import $ from 'jquery';
-import L from 'leaflet';
-export default function MarkerFactory() {
-
- /**
- * Base map marker overlay, all other markers inherit from this class
- *
- * @param map {Leaflet Object}
- * @param geoJson {geoJson Object}
- * @param params {Object}
- */
- class BaseMarker {
- constructor(map, geoJson, params) {
- this.map = map;
- this.geoJson = geoJson;
- this.popups = [];
-
- this._tooltipFormatter = params.tooltipFormatter || null;
- this._valueFormatter = params.valueFormatter || _.identity;
- this._attr = params.attr || {};
-
- // set up the default legend colors
- this.quantizeLegendColors();
- }
-
- getLabel() {
- if (this.popups.length) {
- return this.popups[0].feature.properties.aggConfigResult.aggConfig.makeLabel();
- }
- return '';
- }
- /**
- * Adds legend div to each map when data is split
- * uses d3 scale from BaseMarker.prototype.quantizeLegendColors
- *
- * @method addLegend
- * @return {undefined}
- */
- addLegend() {
- // ensure we only ever create 1 legend
- if (this._legend) return;
-
- const self = this;
-
- // create the legend control, keep a reference
- self._legend = L.control({ position: this._attr.legendPosition });
-
- self._legend.onAdd = function () {
- // creates all the neccessary DOM elements for the control, adds listeners
- // on relevant map events, and returns the element containing the control
- const $wrapper = $('').addClass('tilemap-legend-wrapper');
- const $div = $('
').addClass('tilemap-legend');
- $wrapper.append($div);
-
- const titleText = self.getLabel();
- const $title = $('
').addClass('tilemap-legend-title').text(titleText);
- $div.append($title);
-
- _.each(self._legendColors, function (color) {
- const labelText = self._legendQuantizer
- .invertExtent(color)
- .map(self._valueFormatter)
- .join(' – ');
-
- const label = $('
');
-
- const icon = $('').css({
- background: color,
- 'border-color': self.darkerColor(color)
- });
-
- const text = $('').text(labelText);
-
- label.append(icon);
- label.append(text);
- $div.append(label);
- });
-
- return $wrapper.get(0);
- };
-
- self._legend.addTo(self.map);
- }
-
- /**
- * Apply style with shading to feature
- *
- * @method applyShadingStyle
- * @param value {Object}
- * @return {Object}
- */
- applyShadingStyle(value) {
- const color = this._legendQuantizer(value);
-
- return {
- fillColor: color,
- color: this.darkerColor(color),
- weight: 1.5,
- opacity: 1,
- fillOpacity: 0.75
- };
- }
-
- /**
- * Binds popup and events to each feature on map
- *
- * @method bindPopup
- * @param feature {Object}
- * @param layer {Object}
- * return {undefined}
- */
- bindPopup(feature, layer) {
- const self = this;
-
- const popup = layer.on({
- mouseover: function (e) {
- const layer = e.target;
- // bring layer to front if not older browser
- if (!L.Browser.ie && !L.Browser.opera) {
- layer.bringToFront();
- }
- self._showTooltip(feature);
- },
- mouseout: function () {
- self._hidePopup();
- }
- });
-
- self.popups.push(popup);
- }
-
- /**
- * d3 method returns a darker hex color,
- * used for marker stroke color
- *
- * @method darkerColor
- * @param color {String} hex color
- * @param amount? {Number} amount to darken by
- * @return {String} hex color
- */
- darkerColor(color, amount) {
- amount = amount || 1.3;
- return d3.hcl(color).darker(amount).toString();
- }
-
- destroy() {
- const self = this;
-
- // remove popups
- self.popups = self.popups.filter(function (popup) {
- popup.off('mouseover').off('mouseout');
- });
-
- if (self._legend) {
- self.map.removeControl(self._legend);
- self._legend = undefined;
- }
-
- // remove marker layer from map
- if (self._markerGroup) {
- self.map.removeLayer(self._markerGroup);
- self._markerGroup = undefined;
- }
- }
-
- _addToMap() {
- this.map.addLayer(this._markerGroup);
- }
-
- /**
- * Creates leaflet marker group, passing options to L.geoJson
- *
- * @method _createMarkerGroup
- * @param options {Object} Options to pass to L.geoJson
- */
- _createMarkerGroup(options) {
- const self = this;
- const defaultOptions = {
- onEachFeature: function (feature, layer) {
- self.bindPopup(feature, layer);
- },
- style: function (feature) {
- const value = _.get(feature, 'properties.value');
- return self.applyShadingStyle(value);
- },
- filter: self._filterToMapBounds()
- };
-
- this._markerGroup = L.geoJson(this.geoJson, _.defaults(defaultOptions, options));
- this._addToMap();
- }
-
- /**
- * return whether feature is within map bounds
- *
- * @method _filterToMapBounds
- * @param map {Leaflet Object}
- * @return {boolean}
- */
- _filterToMapBounds() {
- const self = this;
- return function (feature) {
- const mapBounds = self.map.getBounds();
- const bucketRectBounds = _.get(feature, 'properties.rectangle');
- return mapBounds.intersects(bucketRectBounds);
- };
- }
-
- /**
- * Checks if event latlng is within bounds of mapData
- * features and shows tooltip for that feature
- *
- * @method _showTooltip
- * @param feature {LeafletFeature}
- * @param latLng? {Leaflet latLng}
- * @return undefined
- */
- _showTooltip(feature, latLng) {
- const hasMap = !!this.map;
- const hasTooltip = !!this._attr.addTooltip;
- if (!hasMap || !hasTooltip) {
- return;
- }
- const lat = _.get(feature, 'geometry.coordinates.1');
- const lng = _.get(feature, 'geometry.coordinates.0');
- latLng = latLng || L.latLng(lat, lng);
-
- const content = this._tooltipFormatter(feature);
-
- if (!content) return;
- this._createTooltip(content, latLng);
- }
-
- _createTooltip(content, latLng) {
- this._popup = L.popup({ autoPan: false });
- this._popup.setLatLng(latLng)
- .setContent(content)
- .openOn(this.map);
- }
-
- /**
- * Closes the tooltip on the map
- *
- * @method _hidePopup
- * @return undefined
- */
- _hidePopup() {
- if (!this.map) return;
-
- this._popup = null;
- this.map.closePopup();
- }
-
- /**
- * d3 quantize scale returns a hex color, used for marker fill color
- *
- * @method quantizeLegendColors
- * return {undefined}
- */
- quantizeLegendColors() {
- const min = _.get(this.geoJson, 'properties.allmin', 0);
- const max = _.get(this.geoJson, 'properties.allmax', 1);
- const quantizeDomain = (min !== max) ? [min, max] : d3.scale.quantize().domain();
-
- const reds1 = ['#ff6128'];
- const reds3 = ['#fecc5c', '#fd8d3c', '#e31a1c'];
- const reds5 = ['#fed976', '#feb24c', '#fd8d3c', '#f03b20', '#bd0026'];
- const bottomCutoff = 2;
- const middleCutoff = 24;
-
- if (max - min <= bottomCutoff) {
- this._legendColors = reds1;
- } else if (max - min <= middleCutoff) {
- this._legendColors = reds3;
- } else {
- this._legendColors = reds5;
- }
-
- this._legendQuantizer = d3.scale.quantize().domain(quantizeDomain).range(this._legendColors);
- }
- }
-
- return BaseMarker;
-}
diff --git a/src/ui/public/vis_maps/visualizations/marker_types/geohash_grid.js b/src/ui/public/vis_maps/visualizations/marker_types/geohash_grid.js
deleted file mode 100644
index 99f63311b6873..0000000000000
--- a/src/ui/public/vis_maps/visualizations/marker_types/geohash_grid.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import L from 'leaflet';
-import VislibVisualizationsMarkerTypesBaseMarkerProvider from './base_marker';
-export default function GeohashGridMarkerFactory(Private) {
-
- const BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider);
-
- /**
- * Map overlay: rectangles that show the geohash grid bounds
- *
- * @param map {Leaflet Object}
- * @param geoJson {geoJson Object}
- * @param params {Object}
- */
- class GeohashGridMarker extends BaseMarker {
- constructor(map, geoJson, params) {
- super(map, geoJson, params);
-
- this._createMarkerGroup({
- pointToLayer: function (feature) {
- const geohashRect = feature.properties.rectangle;
- // get bounds from northEast[3] and southWest[1]
- // corners in geohash rectangle
- const corners = [
- [geohashRect[3][0], geohashRect[3][1]],
- [geohashRect[1][0], geohashRect[1][1]]
- ];
- return L.rectangle(corners);
- }
- });
- }
- }
-
- return GeohashGridMarker;
-}
diff --git a/src/ui/public/vis_maps/visualizations/marker_types/heatmap.js b/src/ui/public/vis_maps/visualizations/marker_types/heatmap.js
deleted file mode 100644
index 46507c47c9607..0000000000000
--- a/src/ui/public/vis_maps/visualizations/marker_types/heatmap.js
+++ /dev/null
@@ -1,206 +0,0 @@
-import d3 from 'd3';
-import _ from 'lodash';
-import L from 'leaflet';
-import VislibVisualizationsMarkerTypesBaseMarkerProvider from './base_marker';
-export default function HeatmapMarkerFactory(Private) {
-
- const BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider);
-
- /**
- * Map overlay: canvas layer with leaflet.heat plugin
- *
- * @param map {Leaflet Object}
- * @param geoJson {geoJson Object}
- * @param params {Object}
- */
- class HeatmapMarker extends BaseMarker {
- constructor(map, geoJson, params) {
- super(map, geoJson, params);
- this._disableTooltips = false;
-
- this._createMarkerGroup({
- radius: +this._attr.heatRadius,
- blur: +this._attr.heatBlur,
- maxZoom: +this._attr.heatMaxZoom,
- minOpacity: +this._attr.heatMinOpacity
- });
-
- this.addLegend = _.noop;
-
- this._getLatLng = _.memoize(function (feature) {
- return L.latLng(
- feature.geometry.coordinates[1],
- feature.geometry.coordinates[0]
- );
- }, function (feature) {
- // turn coords into a string for the memoize cache
- return [feature.geometry.coordinates[1], feature.geometry.coordinates[0]].join(',');
- });
- }
-
- _createMarkerGroup(options) {
- const max = _.get(this.geoJson, 'properties.allmax');
- const points = this._dataToHeatArray(max);
-
- this._markerGroup = L.heatLayer(points, options);
- this._fixTooltips();
- this._addToMap();
- }
-
- _fixTooltips() {
- const self = this;
- const debouncedMouseMoveLocation = _.debounce(mouseMoveLocation.bind(this), 15, {
- 'leading': true,
- 'trailing': false
- });
-
- if (!this._disableTooltips && this._attr.addTooltip) {
- this.map.on('mousemove', debouncedMouseMoveLocation);
- this.map.on('mouseout', function () {
- self.map.closePopup();
- });
- this.map.on('mousedown', function () {
- self._disableTooltips = true;
- self.map.closePopup();
- });
- this.map.on('mouseup', function () {
- self._disableTooltips = false;
- });
- }
-
- function mouseMoveLocation(e) {
- const latlng = e.latlng;
- // unhighlight all svgs
- d3.selectAll('path.geohash', this.chartEl).classed('geohash-hover', false);
-
- if (!this.geoJson.features.length || this._disableTooltips) {
- this._hidePopup();
- return;
- }
-
- // find nearest feature to event latlng
- const feature = this._nearestFeature(latlng);
-
- // show tooltip if close enough to event latlng
- if (this._tooltipProximity(latlng, feature)) {
- if (this.currentFeature !== feature) {
- this._hidePopup();
- this.currentFeature = feature;
- this._showTooltip(feature, latlng);
- } else {
- if (this._popup) {
- this._popup.setLatLng(latlng);
- }
- }
- } else {
- this._hidePopup();
- this.currentFeature = null;
- }
- }
- }
-
- /**
- * Finds nearest feature in mapData to event latlng
- *
- * @method _nearestFeature
- * @param latLng {Leaflet latLng}
- * @return nearestPoint {Leaflet latLng}
- */
- _nearestFeature(latLng) {
- const self = this;
- let nearest;
-
- if (latLng.lng < -180 || latLng.lng > 180) {
- return;
- }
-
- _.reduce(this.geoJson.features, function (distance, feature) {
- const featureLatLng = self._getLatLng(feature);
- const dist = latLng.distanceTo(featureLatLng);
-
- if (dist < distance) {
- nearest = feature;
- return dist;
- }
-
- return distance;
- }, Infinity);
-
- return nearest;
- }
-
- /**
- * display tooltip if feature is close enough to event latlng
- *
- * @method _tooltipProximity
- * @param latlng {Leaflet latLng Object}
- * @param feature {geoJson Object}
- * @return {Boolean}
- */
- _tooltipProximity(latlng, feature) {
- if (!feature) return;
-
- let showTip = false;
- const featureLatLng = this._getLatLng(feature);
-
- // zoomScale takes map zoom and returns proximity value for tooltip display
- // domain (input values) is map zoom (min 1 and max 18)
- // range (output values) is distance in meters
- // used to compare proximity of event latlng to feature latlng
- const zoomScale = d3.scale.linear()
- .domain([1, 4, 7, 10, 13, 16, 18])
- .range([1000000, 300000, 100000, 15000, 2000, 150, 50]);
-
- const proximity = zoomScale(this.map.getZoom());
- const distance = latlng.distanceTo(featureLatLng);
-
- // maxLngDif is max difference in longitudes
- // to prevent feature tooltip from appearing 360°
- // away from event latlng
- const maxLngDif = 40;
- const lngDif = Math.abs(latlng.lng - featureLatLng.lng);
-
- if (distance < proximity && lngDif < maxLngDif) {
- showTip = true;
- }
-
- d3.scale.pow().exponent(0.2)
- .domain([1, 18])
- .range([1500000, 50]);
- return showTip;
- }
-
-
- /**
- * returns data for data for heat map intensity
- * if heatNormalizeData attribute is checked/true
- • normalizes data for heat map intensity
- *
- * @method _dataToHeatArray
- * @param max {Number}
- * @return {Array}
- */
- _dataToHeatArray(max) {
- const self = this;
-
- return this.geoJson.features.map(function (feature) {
- const lat = feature.properties.center[0];
- const lng = feature.properties.center[1];
- let heatIntensity;
-
- if (!self._attr.heatNormalizeData) {
- // show bucket value on heatmap
- heatIntensity = feature.properties.value;
- } else {
- // show bucket value normalized to max value
- heatIntensity = feature.properties.value / max;
- }
-
- return [lat, lng, heatIntensity];
- });
- }
- }
-
-
- return HeatmapMarker;
-}
diff --git a/src/ui/public/vis_maps/visualizations/marker_types/scaled_circles.js b/src/ui/public/vis_maps/visualizations/marker_types/scaled_circles.js
deleted file mode 100644
index 7eed1d6653977..0000000000000
--- a/src/ui/public/vis_maps/visualizations/marker_types/scaled_circles.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import _ from 'lodash';
-import L from 'leaflet';
-import VislibVisualizationsMarkerTypesBaseMarkerProvider from './base_marker';
-export default function ScaledCircleMarkerFactory(Private) {
-
- const BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider);
-
- /**
- * Map overlay: circle markers that are scaled to illustrate values
- *
- * @param map {Leaflet Object}
- * @param mapData {geoJson Object}
- * @param params {Object}
- */
- class ScaledCircleMarker extends BaseMarker {
- constructor(map, geoJson, params) {
- super(map, geoJson, params);
-
- // multiplier to reduce size of all circles
- const scaleFactor = 0.6;
-
- this._createMarkerGroup({
- pointToLayer: (feature, latlng) => {
- const value = feature.properties.value;
- const scaledRadius = this._radiusScale(value) * scaleFactor;
- return L.circleMarker(latlng).setRadius(scaledRadius);
- }
- });
- }
-
- /**
- * radiusScale returns a number for scaled circle markers
- * for relative sizing of markers
- *
- * @method _radiusScale
- * @param value {Number}
- * @return {Number}
- */
- _radiusScale(value) {
- const precisionBiasBase = 5;
- const precisionBiasNumerator = 200;
- const zoom = this.map.getZoom();
- const maxValue = this.geoJson.properties.allmax;
- const precision = _.max(this.geoJson.features.map(function (feature) {
- return String(feature.properties.geohash).length;
- }));
-
- const pct = Math.abs(value) / Math.abs(maxValue);
- const zoomRadius = 0.5 * Math.pow(2, zoom);
- const precisionScale = precisionBiasNumerator / Math.pow(precisionBiasBase, precision);
-
- // square root value percentage
- return Math.pow(pct, 0.5) * zoomRadius * precisionScale;
- }
- }
-
-
- return ScaledCircleMarker;
-}
diff --git a/src/ui/public/vis_maps/visualizations/marker_types/shaded_circles.js b/src/ui/public/vis_maps/visualizations/marker_types/shaded_circles.js
deleted file mode 100644
index a498056af1070..0000000000000
--- a/src/ui/public/vis_maps/visualizations/marker_types/shaded_circles.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import _ from 'lodash';
-import L from 'leaflet';
-import VislibVisualizationsMarkerTypesBaseMarkerProvider from './base_marker';
-export default function ShadedCircleMarkerFactory(Private) {
-
- const BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider);
-
- /**
- * Map overlay: circle markers that are shaded to illustrate values
- *
- * @param map {Leaflet Object}
- * @param mapData {geoJson Object}
- * @return {Leaflet object} featureLayer
- */
- class ShadedCircleMarker extends BaseMarker {
- constructor(map, geoJson, params) {
- super(map, geoJson, params);
-
- // multiplier to reduce size of all circles
- const scaleFactor = 0.8;
-
- this._createMarkerGroup({
- pointToLayer: (feature, latlng) => {
- const radius = this._geohashMinDistance(feature) * scaleFactor;
- return L.circle(latlng, radius);
- }
- });
- }
-
-
- /**
- * _geohashMinDistance returns a min distance in meters for sizing
- * circle markers to fit within geohash grid rectangle
- *
- * @method _geohashMinDistance
- * @param feature {Object}
- * @return {Number}
- */
- _geohashMinDistance(feature) {
- const centerPoint = _.get(feature, 'properties.center');
- const geohashRect = _.get(feature, 'properties.rectangle');
-
- // centerPoint is an array of [lat, lng]
- // geohashRect is the 4 corners of the geoHash rectangle
- // an array that starts at the southwest corner and proceeds
- // clockwise, each value being an array of [lat, lng]
-
- // center lat and southeast lng
- const east = L.latLng([centerPoint[0], geohashRect[2][1]]);
- // southwest lat and center lng
- const north = L.latLng([geohashRect[3][0], centerPoint[1]]);
-
- // get latLng of geohash center point
- const center = L.latLng([centerPoint[0], centerPoint[1]]);
-
- // get smallest radius at center of geohash grid rectangle
- const eastRadius = Math.floor(center.distanceTo(east));
- const northRadius = Math.floor(center.distanceTo(north));
- return _.min([eastRadius, northRadius]);
- }
- }
-
-
- return ShadedCircleMarker;
-}
diff --git a/src/ui/public/vis_maps/visualizations/tile_map.js b/src/ui/public/vis_maps/visualizations/tile_map.js
deleted file mode 100644
index 1c423d6fee24a..0000000000000
--- a/src/ui/public/vis_maps/visualizations/tile_map.js
+++ /dev/null
@@ -1,128 +0,0 @@
-import _ from 'lodash';
-import $ from 'jquery';
-import VislibVisualizationsChartProvider from './_chart';
-import VislibVisualizationsMapProvider from './_map';
-export default function TileMapFactory(Private) {
-
- const Chart = Private(VislibVisualizationsChartProvider);
- const TileMapMap = Private(VislibVisualizationsMapProvider);
-
- /**
- * Tile Map Visualization: renders maps
- *
- * @class TileMap
- * @constructor
- * @extends Chart
- * @param handler {Object} Reference to the Handler Class Constructor
- * @param chartEl {HTMLElement} HTML element to which the map will be appended
- * @param chartData {Object} Elasticsearch query results for this map
- */
- class TileMap extends Chart {
- constructor(handler, chartEl, chartData) {
- super(handler, chartEl, chartData);
-
- // track the map objects
- this.maps = [];
- this._chartData = chartData || {};
- _.assign(this, this._chartData);
-
- this._appendGeoExtents();
- }
-
- /**
- * Draws tile map, called on chart render
- *
- * @method draw
- * @return {Function} - function to add a map to a selection
- */
- draw() {
- const self = this;
-
- return function (selection) {
- selection.each(function () {
- self._appendMap(this);
- });
- };
- }
-
- /**
- * Invalidate the size of the map, so that leaflet will resize to fit.
- * then moves to center
- *
- * @method resizeArea
- * @return {undefined}
- */
- resizeArea() {
- this.maps.forEach(function (map) {
- map.updateSize();
- });
- }
-
- /**
- * clean up the maps
- *
- * @method destroy
- * @return {undefined}
- */
- destroy() {
- this.maps = this.maps.filter(function (map) {
- map.destroy();
- });
- }
-
- /**
- * Adds allmin and allmax properties to geoJson data
- *
- * @method _appendMap
- * @param selection {Object} d3 selection
- */
- _appendGeoExtents() {
- // add allmin and allmax to geoJson
- const geoMinMax = this.handler.data.getGeoExtents();
- this.geoJson.properties.allmin = geoMinMax.min;
- this.geoJson.properties.allmax = geoMinMax.max;
- }
-
- /**
- * Renders map
- *
- * @method _appendMap
- * @param selection {Object} d3 selection
- */
- _appendMap(selection) {
- const container = $(selection).addClass('tilemap');
- const uiStateParams = {
- mapCenter: this.handler.uiState.get('mapCenter'),
- mapZoom: this.handler.uiState.get('mapZoom')
- };
-
- const params = _.assign({}, _.get(this._chartData, 'geoAgg.vis.params'), uiStateParams);
-
- const tooltipFormatter = this.handler.visConfig.get('addTooltip') ? this.tooltipFormatter : null;
- const map = new TileMapMap(container, this._chartData, {
- center: params.mapCenter,
- zoom: params.mapZoom,
- events: this.events,
- markerType: this.handler.visConfig.get('mapType'),
- tooltipFormatter: tooltipFormatter,
- valueFormatter: this.valueFormatter,
- attr: this.handler.visConfig._values
- });
-
- // add title for splits
- if (this.title) {
- map.addTitle(this.title);
- }
-
- // add fit to bounds control
- if (_.get(this.geoJson, 'features.length') > 0) {
- map.addFitControl();
- map.addBoundingControl();
- }
-
- this.maps.push(map);
- }
- }
-
- return TileMap;
-}
diff --git a/test/functional/apps/visualize/_tile_map.js b/test/functional/apps/visualize/_tile_map.js
deleted file mode 100644
index 586e121049797..0000000000000
--- a/test/functional/apps/visualize/_tile_map.js
+++ /dev/null
@@ -1,330 +0,0 @@
-
-import expect from 'expect.js';
-
-import { bdd } from '../../../support';
-
-import PageObjects from '../../../support/page_objects';
-
-bdd.describe('visualize app', function describeIndexTests() {
- const fromTime = '2015-09-19 06:31:44.000';
- const toTime = '2015-09-23 18:31:44.000';
-
- bdd.before(function () {
-
- PageObjects.common.debug('navigateToApp visualize');
- return PageObjects.common.navigateToUrl('visualize', 'new')
- .then(function () {
- PageObjects.common.debug('clickTileMap');
- return PageObjects.visualize.clickTileMap();
- })
- .then(function () {
- return PageObjects.visualize.clickNewSearch();
- })
- .then(function () {
- PageObjects.common.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"');
- return PageObjects.header.setAbsoluteRange(fromTime, toTime);
- })
- .then(function () {
- PageObjects.common.debug('select bucket Geo Coordinates');
- return PageObjects.visualize.clickBucket('Geo Coordinates');
- })
- .then(function () {
- PageObjects.common.debug('Click aggregation Geohash');
- return PageObjects.visualize.selectAggregation('Geohash');
- })
- .then(function () {
- PageObjects.common.debug('Click field geo.coordinates');
- return PageObjects.common.try(function tryingForTime() {
- return PageObjects.visualize.selectField('geo.coordinates');
- });
- })
- .then(function () {
- return PageObjects.visualize.clickGo();
- })
- .then(function () {
- return PageObjects.header.waitUntilLoadingHasFinished();
- });
- });
-
- bdd.describe('tile map chart', function indexPatternCreation() {
-
- bdd.it('should show correct tile map data on default zoom level', function () {
- const expectedTableData = [ 'dn 1,429', 'dp 1,418', '9y 1,215', '9z 1,099', 'dr 1,076',
- 'dj 982', '9v 938', '9q 722', '9w 475', 'cb 457', 'c2 453', '9x 420', 'dq 399',
- '9r 396', '9t 274', 'c8 271', 'dh 214', 'b6 207', 'bd 206', 'b7 167', 'f0 141',
- 'be 128', '9m 126', 'bf 85', 'de 73', 'bg 71', '9p 71', 'c1 57', 'c4 50', '9u 48',
- 'f2 46', '8e 45', 'b3 38', 'bs 36', 'c0 31', '87 28', 'bk 23', '8f 18', 'b5 14',
- '84 14', 'dx 9', 'bu 9', 'b1 9', 'b4 6', '9n 3', '8g 3'
- ];
-
- return PageObjects.visualize.collapseChart()
- .then(function () {
- return PageObjects.settings.setPageSize('All');
- })
- .then(function getDataTableData() {
- return PageObjects.visualize.getDataTableData()
- .then(function showData(data) {
- expect(data.trim().split('\n')).to.eql(expectedTableData);
- return PageObjects.visualize.collapseChart();
- });
- });
- });
-
-
- bdd.it('should zoom out to level 1 from default level 2', function () {
- const expectedPrecision2Circles = [ { color: '#750000', radius: 48 },
- { color: '#750000', radius: 48 },
- { color: '#750000', radius: 44 },
- { color: '#a40000', radius: 42 },
- { color: '#a40000', radius: 42 },
- { color: '#a40000', radius: 40 },
- { color: '#a40000', radius: 39 },
- { color: '#b45100', radius: 34 },
- { color: '#b67501', radius: 28 },
- { color: '#b67501', radius: 27 },
- { color: '#b67501', radius: 27 },
- { color: '#b67501', radius: 26 },
- { color: '#b67501', radius: 25 },
- { color: '#b67501', radius: 25 },
- { color: '#b99939', radius: 21 },
- { color: '#b99939', radius: 21 },
- { color: '#b99939', radius: 19 },
- { color: '#b99939', radius: 18 },
- { color: '#b99939', radius: 18 },
- { color: '#b99939', radius: 16 },
- { color: '#b99939', radius: 15 },
- { color: '#b99939', radius: 14 },
- { color: '#b99939', radius: 14 },
- { color: '#b99939', radius: 12 },
- { color: '#b99939', radius: 11 },
- { color: '#b99939', radius: 11 },
- { color: '#b99939', radius: 11 },
- { color: '#b99939', radius: 10 },
- { color: '#b99939', radius: 9 },
- { color: '#b99939', radius: 9 },
- { color: '#b99939', radius: 9 },
- { color: '#b99939', radius: 9 },
- { color: '#b99939', radius: 8 },
- { color: '#b99939', radius: 8 },
- { color: '#b99939', radius: 7 },
- { color: '#b99939', radius: 7 },
- { color: '#b99939', radius: 6 },
- { color: '#b99939', radius: 5 },
- { color: '#b99939', radius: 5 },
- { color: '#b99939', radius: 5 },
- { color: '#b99939', radius: 4 },
- { color: '#b99939', radius: 4 },
- { color: '#b99939', radius: 4 },
- { color: '#b99939', radius: 3 },
- { color: '#b99939', radius: 2 },
- { color: '#b99939', radius: 2 }
- ];
-
- return PageObjects.visualize.clickMapZoomOut()
- .then(function () {
- return PageObjects.visualize.getMapZoomOutEnabled();
- })
- // we can tell we're at level 1 because zoom out is disabled
- .then(function () {
- return PageObjects.common.try(function tryingForTime() {
- return PageObjects.visualize.getMapZoomOutEnabled()
- .then(function (enabled) {
- //should be able to zoom more as current config has 0 as min level.
- expect(enabled).to.be(true);
- });
- });
- })
- .then(function () {
- return PageObjects.common.try(function tryingForTime() {
- return PageObjects.visualize.getTileMapData()
- .then(function (data) {
- expect(data).to.eql(expectedPrecision2Circles);
- });
- });
- })
- .then(function takeScreenshot() {
- PageObjects.common.debug('Take screenshot (success)');
- PageObjects.common.saveScreenshot('map-after-zoom-from-1-to-2');
- });
- });
-
- bdd.it('Fit data bounds should zoom to level 3', function () {
- const expectedPrecision2ZoomCircles = [ { color: '#750000', radius: 192 },
- { color: '#750000', radius: 191 },
- { color: '#750000', radius: 177 },
- { color: '#a40000', radius: 168 },
- { color: '#a40000', radius: 167 },
- { color: '#a40000', radius: 159 },
- { color: '#a40000', radius: 156 },
- { color: '#b45100', radius: 136 },
- { color: '#b67501', radius: 111 },
- { color: '#b67501', radius: 109 },
- { color: '#b67501', radius: 108 },
- { color: '#b67501', radius: 104 },
- { color: '#b67501', radius: 101 },
- { color: '#b67501', radius: 101 },
- { color: '#b99939', radius: 84 },
- { color: '#b99939', radius: 84 },
- { color: '#b99939', radius: 74 },
- { color: '#b99939', radius: 73 },
- { color: '#b99939', radius: 73 },
- { color: '#b99939', radius: 66 },
- { color: '#b99939', radius: 60 },
- { color: '#b99939', radius: 57 },
- { color: '#b99939', radius: 57 },
- { color: '#b99939', radius: 47 },
- { color: '#b99939', radius: 43 },
- { color: '#b99939', radius: 43 },
- { color: '#b99939', radius: 43 },
- { color: '#b99939', radius: 38 },
- { color: '#b99939', radius: 36 },
- { color: '#b99939', radius: 35 },
- { color: '#b99939', radius: 34 },
- { color: '#b99939', radius: 34 },
- { color: '#b99939', radius: 31 },
- { color: '#b99939', radius: 30 },
- { color: '#b99939', radius: 28 },
- { color: '#b99939', radius: 27 },
- { color: '#b99939', radius: 24 },
- { color: '#b99939', radius: 22 },
- { color: '#b99939', radius: 19 },
- { color: '#b99939', radius: 19 },
- { color: '#b99939', radius: 15 },
- { color: '#b99939', radius: 15 },
- { color: '#b99939', radius: 15 },
- { color: '#b99939', radius: 12 },
- { color: '#b99939', radius: 9 },
- { color: '#b99939', radius: 9 }
- ];
-
- return PageObjects.visualize.clickMapFitDataBounds()
- .then(function () {
- return PageObjects.visualize.getTileMapData();
- })
- .then(function (data) {
- expect(data).to.eql(expectedPrecision2ZoomCircles);
- });
- });
-
- /*
- ** NOTE: Since we don't have a reliable way to know the zoom level, we can
- ** check some data after we save the viz, then zoom in and check that the data
- ** changed, then open the saved viz and check that it's back to the original data.
- */
- bdd.it('should save with zoom level and load, take screenshot', function () {
- const vizName1 = 'Visualization TileMap';
- const expectedTableData = [ 'dr4 127', 'dr7 92', '9q5 91', '9qc 89', 'drk 87',
- 'dps 82', 'dph 82', 'dp3 79', 'dpe 78', 'dp8 77'
- ];
-
- const expectedTableDataZoomed = [ 'dr5r 21', 'dps8 20', '9q5b 19', 'b6uc 17',
- '9y63 17', 'c20g 16', 'dqfz 15', 'dr8h 14', 'dp8p 14', 'dp3k 14'
- ];
-
- return PageObjects.visualize.clickMapZoomIn()
- .then(function () {
- return PageObjects.visualize.clickMapZoomIn();
- })
- .then(function () {
- return PageObjects.visualize.saveVisualization(vizName1);
- })
- .then(function (message) {
- PageObjects.common.debug('Saved viz message = ' + message);
- expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"');
- })
- .then(function testVisualizeWaitForToastMessageGone() {
- return PageObjects.visualize.waitForToastMessageGone();
- })
- .then(function () {
- return PageObjects.visualize.collapseChart();
- })
- // we're not selecting page size all, so we only have to verify the first page of data
- .then(function getDataTableData() {
- PageObjects.common.debug('first get the zoom level 5 page data and verify it');
- return PageObjects.visualize.getDataTableData();
- })
- .then(function showData(data) {
- expect(data.trim().split('\n')).to.eql(expectedTableData);
- return PageObjects.visualize.collapseChart();
- })
- .then(function () {
- // zoom to level 6, and make sure we go back to the saved level 5
- return PageObjects.visualize.clickMapZoomIn();
- })
- .then(function () {
- return PageObjects.visualize.collapseChart();
- })
- .then(function getDataTableData() {
- PageObjects.common.debug('second get the zoom level 6 page data and verify it');
- return PageObjects.visualize.getDataTableData();
- })
- .then(function showData(data) {
- expect(data.trim().split('\n')).to.eql(expectedTableDataZoomed);
- return PageObjects.visualize.collapseChart();
- })
- .then(function () {
- return PageObjects.visualize.loadSavedVisualization(vizName1);
- })
- .then(function waitForVisualization() {
- return PageObjects.visualize.waitForVisualization();
- })
- // sleep a bit before taking the screenshot or it won't show data
- .then(function sleep() {
- return PageObjects.common.sleep(4000);
- })
- .then(function () {
- return PageObjects.visualize.collapseChart();
- })
- .then(function getDataTableData() {
- PageObjects.common.debug('third get the zoom level 5 page data and verify it');
- return PageObjects.visualize.getDataTableData();
- })
- .then(function showData(data) {
- expect(data.trim().split('\n')).to.eql(expectedTableData);
- return PageObjects.visualize.collapseChart();
- })
- .then(function takeScreenshot() {
- PageObjects.common.debug('Take screenshot');
- PageObjects.common.saveScreenshot('Visualize-site-map');
- });
- });
-
-
- bdd.it('should zoom in to level 10', function () {
- // 6
- return PageObjects.visualize.clickMapZoomIn()
- .then(function () {
- // 7
- return PageObjects.visualize.clickMapZoomIn();
- })
- .then(function () {
- // 8
- return PageObjects.visualize.clickMapZoomIn();
- })
- .then(function () {
- // 9
- return PageObjects.visualize.clickMapZoomIn();
- })
- .then(function () {
- return PageObjects.common.try(function tryingForTime() {
- return PageObjects.visualize.getMapZoomInEnabled()
- .then(function (enabled) {
- expect(enabled).to.be(true);
- });
- });
- })
- .then(function () {
- return PageObjects.visualize.clickMapZoomIn();
- })
- .then(function () {
- return PageObjects.visualize.getMapZoomInEnabled();
- })
- // now we're at level 10 and zoom out should be disabled
- .then(function (enabled) {
- expect(enabled).to.be(false);
- });
- });
-
-
- });
-});
diff --git a/test/functional/apps/visualize/index.js b/test/functional/apps/visualize/index.js
index d4cf50d7b7fe3..f37c917bd870d 100644
--- a/test/functional/apps/visualize/index.js
+++ b/test/functional/apps/visualize/index.js
@@ -36,7 +36,6 @@ bdd.describe('visualize app', function () {
require('./_data_table');
require('./_metric_chart');
require('./_pie_chart');
- require('./_tile_map');
require('./_vertical_bar_chart');
require('./_heatmap_chart');
require('./_point_series_options');