diff --git a/x-pack/plugins/maps/public/actions/map_actions.d.ts b/x-pack/plugins/maps/public/actions/map_actions.d.ts
index 413b440279d77..3bda29964a9a1 100644
--- a/x-pack/plugins/maps/public/actions/map_actions.d.ts
+++ b/x-pack/plugins/maps/public/actions/map_actions.d.ts
@@ -13,6 +13,7 @@ import {
MapFilters,
MapCenterAndZoom,
MapRefreshConfig,
+ MapExtent,
} from '../../common/descriptor_types';
import { MapSettings } from '../reducers/map';
@@ -34,6 +35,9 @@ export function updateSourceProp(
): void;
export function setGotoWithCenter(config: MapCenterAndZoom): AnyAction;
+export function setGotoWithBounds(config: MapExtent): AnyAction;
+
+export function fitToDataBounds(): AnyAction;
export function replaceLayerList(layerList: unknown[]): AnyAction;
diff --git a/x-pack/plugins/maps/public/actions/map_actions.js b/x-pack/plugins/maps/public/actions/map_actions.js
index da6ba6b481054..ea2602397702b 100644
--- a/x-pack/plugins/maps/public/actions/map_actions.js
+++ b/x-pack/plugins/maps/public/actions/map_actions.js
@@ -19,6 +19,7 @@ import {
getOpenTooltips,
getQuery,
getDataRequestDescriptor,
+ getFittableLayers,
} from '../selectors/map_selectors';
import { FLYOUT_STATE } from '../reducers/ui';
@@ -567,6 +568,55 @@ export function fitToLayerExtent(layerId) {
};
}
+export function fitToDataBounds() {
+ return async function(dispatch, getState) {
+ const layerList = getFittableLayers(getState());
+
+ if (!layerList.length) {
+ return;
+ }
+
+ const dataFilters = getDataFilters(getState());
+ const boundsPromises = layerList.map(async layer => {
+ return layer.getBounds(dataFilters);
+ });
+
+ const bounds = await Promise.all(boundsPromises);
+ const corners = [];
+ for (let i = 0; i < bounds.length; i++) {
+ const b = bounds[i];
+
+ //filter out undefined bounds (uses Infinity due to turf responses)
+
+ if (
+ b.minLon === Infinity ||
+ b.maxLon === Infinity ||
+ b.minLat === -Infinity ||
+ b.maxLat === -Infinity
+ ) {
+ continue;
+ }
+
+ corners.push([b.minLon, b.minLat]);
+ corners.push([b.maxLon, b.maxLat]);
+ }
+
+ if (!corners.length) {
+ return;
+ }
+
+ const turfUnionBbox = turf.bbox(turf.multiPoint(corners));
+ const dataBounds = {
+ minLon: turfUnionBbox[0],
+ minLat: turfUnionBbox[1],
+ maxLon: turfUnionBbox[2],
+ maxLat: turfUnionBbox[3],
+ };
+
+ dispatch(setGotoWithBounds(dataBounds));
+ };
+}
+
export function setGotoWithBounds(bounds) {
return {
type: SET_GOTO,
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/__snapshots__/toolbar_overlay.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/__snapshots__/toolbar_overlay.test.tsx.snap
new file mode 100644
index 0000000000000..3407bcfd4f845
--- /dev/null
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/__snapshots__/toolbar_overlay.test.tsx.snap
@@ -0,0 +1,44 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Must render zoom tools 1`] = `
+
+
+
+
+
+
+
+
+`;
+
+exports[`Must zoom tools and draw filter tools 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_index.scss b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_index.scss
index 2754a3e204263..e92e89b170370 100644
--- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_index.scss
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_index.scss
@@ -12,6 +12,7 @@
// sass-lint:disable-block no-important
background-color: $euiColorEmptyShade !important;
pointer-events: all;
+ position: relative;
&:enabled,
&:enabled:hover,
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx
new file mode 100644
index 0000000000000..0b168badb2f3f
--- /dev/null
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { EuiButtonIcon } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { ILayer } from '../../../layers/layer';
+
+interface Props {
+ layerList: ILayer[];
+ fitToBounds: () => void;
+}
+
+export const FitToData: React.FunctionComponent = ({ layerList, fitToBounds }: Props) => {
+ if (layerList.length === 0) {
+ return null;
+ }
+
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/index.ts b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/index.ts
new file mode 100644
index 0000000000000..d6ded62f2f480
--- /dev/null
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/index.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { AnyAction, Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import { MapStoreState } from '../../../reducers/store';
+import { fitToDataBounds } from '../../../actions/map_actions';
+import { getFittableLayers } from '../../../selectors/map_selectors';
+import { FitToData } from './fit_to_data';
+
+function mapStateToProps(state: MapStoreState) {
+ return {
+ layerList: getFittableLayers(state),
+ };
+}
+
+function mapDispatchToProps(dispatch: Dispatch) {
+ return {
+ fitToBounds: () => {
+ dispatch(fitToDataBounds());
+ },
+ };
+}
+
+const connectedFitToData = connect(mapStateToProps, mapDispatchToProps)(FitToData);
+export { connectedFitToData as FitToData };
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.js b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.js
index 32668be8f8f67..a4f85163512f7 100644
--- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.js
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.js
@@ -8,6 +8,7 @@ import React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { SetViewControl } from './set_view_control';
import { ToolsControl } from './tools_control';
+import { FitToData } from './fit_to_data';
export class ToolbarOverlay extends React.Component {
_renderToolsControl() {
@@ -36,6 +37,10 @@ export class ToolbarOverlay extends React.Component {
+
+
+
+
{this._renderToolsControl()}
);
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.test.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.test.tsx
new file mode 100644
index 0000000000000..c03aa1e098540
--- /dev/null
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.test.tsx
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+
+// @ts-ignore
+import { ToolbarOverlay } from './toolbar_overlay';
+
+test('Must render zoom tools', async () => {
+ const component = shallow();
+ expect(component).toMatchSnapshot();
+});
+
+test('Must zoom tools and draw filter tools', async () => {
+ const component = shallow( {}} geoFields={['coordinates']} />);
+ expect(component).toMatchSnapshot();
+});
diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap
index b8c652909408a..388712e1ebcca 100644
--- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap
+++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap
@@ -72,7 +72,7 @@ exports[`TOCEntryActionsPopover is rendered 1`] = `
"disabled": false,
"icon": ,
"name": "Fit to data",
"onClick": [Function],
@@ -200,7 +200,7 @@ exports[`TOCEntryActionsPopover should disable fit to data when supportsFitToBou
"disabled": true,
"icon": ,
"name": "Fit to data",
"onClick": [Function],
@@ -328,7 +328,7 @@ exports[`TOCEntryActionsPopover should not show edit actions in read only mode 1
"disabled": false,
"icon": ,
"name": "Fit to data",
"onClick": [Function],
diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx
index e0fdff62829fb..dfc93c29263ee 100644
--- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx
+++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx
@@ -137,7 +137,7 @@ export class TOCEntryActionsPopover extends Component {
name: i18n.translate('xpack.maps.layerTocActions.fitToDataTitle', {
defaultMessage: 'Fit to data',
}),
- icon: ,
+ icon: ,
'data-test-subj': 'fitToBoundsButton',
toolTipContent: this.state.supportsFitToBounds
? null
diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.d.ts b/x-pack/plugins/maps/public/selectors/map_selectors.d.ts
index bc881d06f62ce..9caa151db6d5a 100644
--- a/x-pack/plugins/maps/public/selectors/map_selectors.d.ts
+++ b/x-pack/plugins/maps/public/selectors/map_selectors.d.ts
@@ -4,11 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { AnyAction } from 'redux';
import { MapCenter } from '../../common/descriptor_types';
import { MapStoreState } from '../reducers/store';
import { MapSettings } from '../reducers/map';
import { IVectorLayer } from '../layers/vector_layer';
+import { ILayer } from '../layers/layer';
export function getHiddenLayerIds(state: MapStoreState): string[];
@@ -25,3 +25,6 @@ export function hasMapSettingsChanges(state: MapStoreState): boolean;
export function isUsingSearch(state: MapStoreState): boolean;
export function getSpatialFiltersLayer(state: MapStoreState): IVectorLayer;
+
+export function getLayerList(state: MapStoreState): ILayer[];
+export function getFittableLayers(state: MapStoreState): ILayer[];
diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.js b/x-pack/plugins/maps/public/selectors/map_selectors.js
index f1b5371472970..d11c0887ba7a8 100644
--- a/x-pack/plugins/maps/public/selectors/map_selectors.js
+++ b/x-pack/plugins/maps/public/selectors/map_selectors.js
@@ -251,6 +251,19 @@ export const getLayerList = createSelector(
}
);
+export const getFittableLayers = createSelector(getLayerList, layerList => {
+ return layerList.filter(layer => {
+ //These are the only layer-types that implement bounding-box retrieval reliably
+ //This will _not_ work if Maps will allow register custom layer types
+ const isFittable =
+ layer.getType() === LAYER_TYPE.VECTOR ||
+ layer.getType() === LAYER_TYPE.BLENDED_VECTOR ||
+ layer.getType() === LAYER_TYPE.HEATMAP;
+
+ return isFittable && layer.isVisible();
+ });
+});
+
export const getHiddenLayerIds = createSelector(getLayerListRaw, layers =>
layers.filter(layer => !layer.visible).map(layer => layer.id)
);