Skip to content

Commit

Permalink
[Maps] Show spatial filters on map to provide context when for active…
Browse files Browse the repository at this point in the history
… filters (#63406) (#64250)

* [Maps] show spatial filters

* pass data into __dataRequests

* extractFeaturesFromFilters

* geo_shape support

* putting it all together

* lower alpha

* update removeOrphanedSourcesAndLayers to avoid removing spatialFiltersLayer

* change array iteration to forEach

* use less precision when distance filter covers larger distances

* fix double import

* add map settings for to configure spatial filters layer

* add map settings alpha slider

* finish rest of map settings

* review feedback

Co-authored-by: Elastic Machine <[email protected]>

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
nreese and elasticmachine authored Apr 23, 2020
1 parent fc6c0b9 commit 7aebc4f
Show file tree
Hide file tree
Showing 16 changed files with 463 additions and 81 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/maps/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,5 @@ export enum SCALING_TYPES {
}

export const RGBA_0000 = 'rgba(0,0,0,0)';

export const SPATIAL_FILTERS_LAYER_ID = 'SPATIAL_FILTERS_LAYER_ID';
46 changes: 46 additions & 0 deletions x-pack/plugins/maps/public/components/alpha_slider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 { EuiFormRow } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
// @ts-ignore
import { ValidatedRange } from './validated_range';

interface Props {
alpha: number;
onChange: (alpha: number) => void;
}

export function AlphaSlider({ alpha, onChange }: Props) {
const onAlphaChange = (newAlpha: number) => {
onChange(newAlpha / 100);
};

return (
<EuiFormRow
label={i18n.translate('xpack.maps.layerPanel.settingsPanel.layerTransparencyLabel', {
defaultMessage: 'Opacity',
})}
display="columnCompressed"
>
<ValidatedRange
min={0}
max={100}
step={1}
value={Math.round(alpha * 100)}
onChange={onAlphaChange}
showInput
showRange
compressed
append={i18n.translate('xpack.maps.layerPanel.settingsPanel.percentageLabel', {
defaultMessage: '%',
description: 'Percentage',
})}
/>
</EuiFormRow>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import React, { Fragment } from 'react';

import { EuiTitle, EuiPanel, EuiFormRow, EuiFieldText, EuiSpacer } from '@elastic/eui';

import { ValidatedRange } from '../../../components/validated_range';
import { AlphaSlider } from '../../../components/alpha_slider';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { ValidatedDualRange } from '../../../../../../../src/plugins/kibana_react/public';
Expand All @@ -24,8 +24,7 @@ export function LayerSettings(props) {
};

const onAlphaChange = alpha => {
const alphaDecimal = alpha / 100;
props.updateAlpha(props.layerId, alphaDecimal);
props.updateAlpha(props.layerId, alpha);
};

const renderZoomSliders = () => {
Expand Down Expand Up @@ -64,34 +63,6 @@ export function LayerSettings(props) {
);
};

const renderAlphaSlider = () => {
const alphaPercent = Math.round(props.alpha * 100);

return (
<EuiFormRow
label={i18n.translate('xpack.maps.layerPanel.settingsPanel.layerTransparencyLabel', {
defaultMessage: 'Opacity',
})}
display="columnCompressed"
>
<ValidatedRange
min={0}
max={100}
step={1}
value={alphaPercent}
onChange={onAlphaChange}
showInput
showRange
compressed
append={i18n.translate('xpack.maps.layerPanel.settingsPanel.percentageLabel', {
defaultMessage: '%',
description: 'Percentage',
})}
/>
</EuiFormRow>
);
};

return (
<Fragment>
<EuiPanel>
Expand All @@ -107,7 +78,7 @@ export function LayerSettings(props) {
<EuiSpacer size="m" />
{renderLabel()}
{renderZoomSliders()}
{renderAlphaSlider()}
<AlphaSlider alpha={props.alpha} onChange={onAlphaChange} />
</EuiPanel>

<EuiSpacer size="s" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,28 @@ export class DrawControl extends React.Component {

if (this.props.drawState.drawType === DRAW_TYPE.DISTANCE) {
const circle = e.features[0];
roundCoordinates(circle.properties.center);
const distanceKm = _.round(
circle.properties.radiusKm,
circle.properties.radiusKm > 10 ? 0 : 2
);
// Only include as much precision as needed for distance
let precision = 2;
if (distanceKm <= 1) {
precision = 5;
} else if (distanceKm <= 10) {
precision = 4;
} else if (distanceKm <= 100) {
precision = 3;
}
const filter = createDistanceFilterWithMeta({
alias: this.props.drawState.filterLabel,
distanceKm: _.round(circle.properties.radiusKm, circle.properties.radiusKm > 10 ? 0 : 2),
distanceKm,
geoFieldName: this.props.drawState.geoFieldName,
indexPatternId: this.props.drawState.indexPatternId,
point: circle.properties.center,
point: [
_.round(circle.properties.center[0], precision),
_.round(circle.properties.center[1], precision),
],
});
this.props.addFilters([filter]);
this.props.disableDrawState();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
isInteractiveDisabled,
isTooltipControlDisabled,
isViewControlHidden,
getSpatialFiltersLayer,
getMapSettings,
} from '../../../selectors/map_selectors';

Expand All @@ -33,6 +34,7 @@ function mapStateToProps(state = {}) {
isMapReady: getMapReady(state),
settings: getMapSettings(state),
layerList: getLayerList(state),
spatialFiltersLayer: getSpatialFiltersLayer(state),
goto: getGoto(state),
inspectorAdapters: getInspectorAdapters(state),
scrollZoom: getScrollZoom(state),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { removeOrphanedSourcesAndLayers, syncLayerOrderForSingleLayer } from './utils';
import { SPATIAL_FILTERS_LAYER_ID } from '../../../../common/constants';
import _ from 'lodash';

class MockMbMap {
Expand Down Expand Up @@ -121,7 +122,8 @@ function makeMultiSourceMockLayer(layerId) {
);
}

describe('mb/utils', () => {
describe('removeOrphanedSourcesAndLayers', () => {
const spatialFilterLayer = makeMultiSourceMockLayer(SPATIAL_FILTERS_LAYER_ID);
test('should remove foo and bar layer', async () => {
const bazLayer = makeSingleSourceMockLayer('baz');
const fooLayer = makeSingleSourceMockLayer('foo');
Expand All @@ -133,7 +135,7 @@ describe('mb/utils', () => {
const currentStyle = getMockStyle(currentLayerList);
const mockMbMap = new MockMbMap(currentStyle);

removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList);
removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList, spatialFilterLayer);
const removedStyle = mockMbMap.getStyle();

const nextStyle = getMockStyle(nextLayerList);
Expand All @@ -151,7 +153,7 @@ describe('mb/utils', () => {
const currentStyle = getMockStyle(currentLayerList);
const mockMbMap = new MockMbMap(currentStyle);

removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList);
removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList, spatialFilterLayer);
const removedStyle = mockMbMap.getStyle();

const nextStyle = getMockStyle(nextLayerList);
Expand All @@ -169,13 +171,23 @@ describe('mb/utils', () => {
const currentStyle = getMockStyle(currentLayerList);
const mockMbMap = new MockMbMap(currentStyle);

removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList);
removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList, spatialFilterLayer);
const removedStyle = mockMbMap.getStyle();

const nextStyle = getMockStyle(nextLayerList);
expect(removedStyle).toEqual(nextStyle);
});

test('should not remove spatial filter layer and sources when spatialFilterLayer is provided', async () => {
const styleWithSpatialFilters = getMockStyle([spatialFilterLayer]);
const mockMbMap = new MockMbMap(styleWithSpatialFilters);

removeOrphanedSourcesAndLayers(mockMbMap, [], spatialFilterLayer);
expect(mockMbMap.getStyle()).toEqual(styleWithSpatialFilters);
});
});

describe('syncLayerOrderForSingleLayer', () => {
test('should move bar layer in front of foo layer', async () => {
const fooLayer = makeSingleSourceMockLayer('foo');
const barLayer = makeSingleSourceMockLayer('bar');
Expand Down Expand Up @@ -250,40 +262,4 @@ describe('mb/utils', () => {
const nextStyle = getMockStyle(nextLayerListOrder);
expect(orderedStyle).toEqual(nextStyle);
});

test('should reorder foo and bar and remove baz', async () => {
const bazLayer = makeSingleSourceMockLayer('baz');
const fooLayer = makeSingleSourceMockLayer('foo');
const barLayer = makeSingleSourceMockLayer('bar');

const currentLayerOrder = [bazLayer, fooLayer, barLayer];
const nextLayerListOrder = [barLayer, fooLayer];

const currentStyle = getMockStyle(currentLayerOrder);
const mockMbMap = new MockMbMap(currentStyle);
removeOrphanedSourcesAndLayers(mockMbMap, nextLayerListOrder);
syncLayerOrderForSingleLayer(mockMbMap, nextLayerListOrder);
const orderedStyle = mockMbMap.getStyle();

const nextStyle = getMockStyle(nextLayerListOrder);
expect(orderedStyle).toEqual(nextStyle);
});

test('should reorder foo and bar and remove baz, when having multi-source multi-layer data', async () => {
const bazLayer = makeMultiSourceMockLayer('baz');
const fooLayer = makeSingleSourceMockLayer('foo');
const barLayer = makeMultiSourceMockLayer('bar');

const currentLayerOrder = [bazLayer, fooLayer, barLayer];
const nextLayerListOrder = [barLayer, fooLayer];

const currentStyle = getMockStyle(currentLayerOrder);
const mockMbMap = new MockMbMap(currentStyle);
removeOrphanedSourcesAndLayers(mockMbMap, nextLayerListOrder);
syncLayerOrderForSingleLayer(mockMbMap, nextLayerListOrder);
const orderedStyle = mockMbMap.getStyle();

const nextStyle = getMockStyle(nextLayerListOrder);
expect(orderedStyle).toEqual(nextStyle);
});
});
34 changes: 31 additions & 3 deletions x-pack/plugins/maps/public/connected_components/map/mb/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@
import _ from 'lodash';
import { RGBAImage } from './image_utils';

export function removeOrphanedSourcesAndLayers(mbMap, layerList) {
export function removeOrphanedSourcesAndLayers(mbMap, layerList, spatialFilterLayer) {
const mbStyle = mbMap.getStyle();

const mbLayerIdsToRemove = [];
mbStyle.layers.forEach(mbLayer => {
// ignore mapbox layers from spatial filter layer
if (spatialFilterLayer.ownsMbLayerId(mbLayer.id)) {
return;
}

const layer = layerList.find(layer => {
return layer.ownsMbLayerId(mbLayer.id);
});
Expand All @@ -24,6 +29,11 @@ export function removeOrphanedSourcesAndLayers(mbMap, layerList) {
const mbSourcesToRemove = [];
for (const mbSourceId in mbStyle.sources) {
if (mbStyle.sources.hasOwnProperty(mbSourceId)) {
// ignore mapbox sources from spatial filter layer
if (spatialFilterLayer.ownsMbSourceId(mbSourceId)) {
return;
}

const layer = layerList.find(layer => {
return layer.ownsMbSourceId(mbSourceId);
});
Expand All @@ -35,6 +45,21 @@ export function removeOrphanedSourcesAndLayers(mbMap, layerList) {
mbSourcesToRemove.forEach(mbSourceId => mbMap.removeSource(mbSourceId));
}

export function moveLayerToTop(mbMap, layer) {
const mbStyle = mbMap.getStyle();

if (!mbStyle.layers || mbStyle.layers.length === 0) {
return;
}

layer.getMbLayerIds().forEach(mbLayerId => {
const mbLayer = mbMap.getLayer(mbLayerId);
if (mbLayer) {
mbMap.moveLayer(mbLayerId);
}
});
}

/**
* This is function assumes only a single layer moved in the layerList, compared to mbMap
* It is optimized to minimize the amount of mbMap.moveLayer calls.
Expand All @@ -47,9 +72,12 @@ export function syncLayerOrderForSingleLayer(mbMap, layerList) {
}

const mbLayers = mbMap.getStyle().layers.slice();
const layerIds = mbLayers.map(mbLayer => {
const layerIds = [];
mbLayers.forEach(mbLayer => {
const layer = layerList.find(layer => layer.ownsMbLayerId(mbLayer.id));
return layer.getId();
if (layer) {
layerIds.push(layer.getId());
}
});

const currentLayerOrderLayerIds = _.uniq(layerIds);
Expand Down
11 changes: 9 additions & 2 deletions x-pack/plugins/maps/public/connected_components/map/mb/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
syncLayerOrderForSingleLayer,
removeOrphanedSourcesAndLayers,
addSpritesheetToMap,
moveLayerToTop,
} from './utils';
import { getGlyphUrl, isRetina } from '../../../meta';
import { DECIMAL_DEGREES_PRECISION, ZOOM_PRECISION } from '../../../../common/constants';
Expand Down Expand Up @@ -74,7 +75,7 @@ export class MBMapContainer extends React.Component {
}

_debouncedSync = _.debounce(() => {
if (this._isMounted || !this.props.isMapReady) {
if (this._isMounted && this.props.isMapReady) {
if (!this.state.hasSyncedLayerList) {
this.setState(
{
Expand All @@ -86,6 +87,7 @@ export class MBMapContainer extends React.Component {
}
);
}
this.props.spatialFiltersLayer.syncLayerWithMB(this.state.mbMap);
this._syncSettings();
}
}, 256);
Expand Down Expand Up @@ -260,9 +262,14 @@ export class MBMapContainer extends React.Component {
};

_syncMbMapWithLayerList = () => {
removeOrphanedSourcesAndLayers(this.state.mbMap, this.props.layerList);
removeOrphanedSourcesAndLayers(
this.state.mbMap,
this.props.layerList,
this.props.spatialFiltersLayer
);
this.props.layerList.forEach(layer => layer.syncLayerWithMB(this.state.mbMap));
syncLayerOrderForSingleLayer(this.state.mbMap, this.props.layerList);
moveLayerToTop(this.state.mbMap, this.props.spatialFiltersLayer);
};

_syncMbMapWithInspector = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { MapSettings } from '../../reducers/map';
import { NavigationPanel } from './navigation_panel';
import { SpatialFiltersPanel } from './spatial_filters_panel';

interface Props {
cancelChanges: () => void;
Expand Down Expand Up @@ -60,6 +61,8 @@ export function MapSettingsPanel({
<div className="mapLayerPanel__body">
<div className="mapLayerPanel__bodyOverflow">
<NavigationPanel settings={settings} updateMapSetting={updateMapSetting} />
<EuiSpacer size="s" />
<SpatialFiltersPanel settings={settings} updateMapSetting={updateMapSetting} />
</div>
</div>

Expand Down
Loading

0 comments on commit 7aebc4f

Please sign in to comment.