Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Maps] Add global fit to data #64702

Merged
merged 7 commits into from
May 5, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions x-pack/plugins/maps/public/actions/map_actions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
MapFilters,
MapCenterAndZoom,
MapRefreshConfig,
MapExtent,
} from '../../common/descriptor_types';
import { MapSettings } from '../reducers/map';

Expand All @@ -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;

Expand Down
50 changes: 50 additions & 0 deletions x-pack/plugins/maps/public/actions/map_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
getOpenTooltips,
getQuery,
getDataRequestDescriptor,
getFittableLayers,
} from '../selectors/map_selectors';

import { FLYOUT_STATE } from '../reducers/ui';
Expand Down Expand Up @@ -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,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
background-color: $euiColorEmptyShade !important;
pointer-events: all;

display: inline-block;
position: relative;
vertical-align: middle;
max-width: 100%;
thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved

thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved
&:enabled,
&:enabled:hover,
&:enabled:focus {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Props> = ({ layerList, fitToBounds }: Props) => {
if (layerList.length === 0) {
return null;
}

return (
<EuiButtonIcon
className="mapToolbarOverlay__button"
onClick={fitToBounds}
data-test-subj="fitToData"
iconType="search"
thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved
color="text"
aria-label={i18n.translate('xpack.maps.fitToData.fitButtonLabel', {
defaultMessage: 'Fit to data bounds',
})}
title={i18n.translate('xpack.maps.fitToData.fitAriaLabel', {
defaultMessage: 'Fit to data bounds',
})}
/>
);
};
Original file line number Diff line number Diff line change
@@ -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<AnyAction>) {
return {
fitToBounds: () => {
dispatch(fitToDataBounds());
},
};
}

const connectedFitToData = connect(mapStateToProps, mapDispatchToProps)(FitToData);
export { connectedFitToData as FitToData };
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -36,6 +37,10 @@ export class ToolbarOverlay extends React.Component {
<SetViewControl />
</EuiFlexItem>

<EuiFlexItem>
<FitToData />
</EuiFlexItem>

{this._renderToolsControl()}
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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(<ToolbarOverlay />);
expect(component).toMatchSnapshot();
});

test('Must zoom tools and draw filter tools', async () => {
const component = shallow(<ToolbarOverlay addFilters={() => {}} geoFields={['coordinates']} />);
expect(component).toMatchSnapshot();
});
5 changes: 4 additions & 1 deletion x-pack/plugins/maps/public/selectors/map_selectors.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];

Expand All @@ -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[];
13 changes: 13 additions & 0 deletions x-pack/plugins/maps/public/selectors/map_selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
Expand Down