Skip to content

Commit

Permalink
Refactor tooltip setup as component (#320)
Browse files Browse the repository at this point in the history
Added property to decide whether tooltip to display
features or not. This will be required if tooltip is
used to draw shape.

Signed-off-by: Vijayan Balasubramanian <balasvij@amazon.com>
  • Loading branch information
VijayanB authored Mar 6, 2023
1 parent 16fca69 commit eb0bc0d
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 75 deletions.
4 changes: 4 additions & 0 deletions common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,7 @@ export const LAYER_ICON_TYPE_MAP: { [key: string]: string } = {
[DASHBOARDS_MAPS_LAYER_TYPE.DOCUMENTS]: 'document',
[DASHBOARDS_MAPS_LAYER_TYPE.CUSTOM_MAP]: 'globe',
};

export enum TOOLTIP_STATE {
DISPLAY_FEATURES = 'DISPLAY_FEATURES',
}
86 changes: 11 additions & 75 deletions public/components/map_container/map_container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,20 @@
*/

import React, { useEffect, useRef, useState } from 'react';
import { Map as Maplibre, NavigationControl, Popup, MapEventType } from 'maplibre-gl';
import { Map as Maplibre, NavigationControl } from 'maplibre-gl';
import { debounce, throttle } from 'lodash';
import { LayerControlPanel } from '../layer_control_panel';
import './map_container.scss';
import { MAP_INITIAL_STATE, DASHBOARDS_MAPS_LAYER_TYPE } from '../../../common';
import { DASHBOARDS_MAPS_LAYER_TYPE, MAP_INITIAL_STATE } from '../../../common';
import { MapLayerSpecification } from '../../model/mapLayerType';
import {
Filter,
IndexPattern,
Query,
RefreshInterval,
TimeRange,
Filter,
Query,
} from '../../../../../src/plugins/data/public';
import { MapState } from '../../model/mapState';
import {
createPopup,
getPopupLocation,
isTooltipEnabledLayer,
isTooltipEnabledOnHover,
} from '../tooltip/create_tooltip';
import {
handleDataLayerRender,
handleReferenceLayerRender,
Expand All @@ -39,6 +33,8 @@ import {
referenceLayerTypeLookup,
} from '../../model/layersFunctions';
import { MapsFooter } from './maps_footer';
import { DisplayFeatures } from '../tooltip/display_features';
import { TOOLTIP_STATE } from '../../../common/index';

interface MapContainerProps {
setLayers: (layers: MapLayerSpecification[]) => void;
Expand Down Expand Up @@ -80,6 +76,8 @@ export const MapContainer = ({
const [selectedLayerConfig, setSelectedLayerConfig] = useState<
MapLayerSpecification | undefined
>();
// start with display feature
const [tooltipState, setTooltipState] = useState<TOOLTIP_STATE>(TOOLTIP_STATE.DISPLAY_FEATURES);

useEffect(() => {
if (!mapContainer.current) return;
Expand Down Expand Up @@ -129,71 +127,6 @@ export const MapContainer = ({
};
}, []);

// Create onClick tooltip for each layer features that has tooltip enabled
useEffect(() => {
let clickPopup: Popup | null = null;
let hoverPopup: Popup | null = null;

// We don't want to show layer information in the popup for the map tile layer
const tooltipEnabledLayers = layers.filter(isTooltipEnabledLayer);

function onClickMap(e: MapEventType['click']) {
// remove previous popup
clickPopup?.remove();

const features = maplibreRef.current?.queryRenderedFeatures(e.point);
if (features && maplibreRef.current) {
clickPopup = createPopup({ features, layers: tooltipEnabledLayers });
clickPopup
?.setLngLat(getPopupLocation(features[0].geometry, e.lngLat))
.addTo(maplibreRef.current);
}
}

function onMouseMoveMap(e: MapEventType['mousemove']) {
// remove previous popup
hoverPopup?.remove();

const tooltipEnabledLayersOnHover = layers.filter(isTooltipEnabledOnHover);
const features = maplibreRef.current?.queryRenderedFeatures(e.point);
if (features && maplibreRef.current) {
hoverPopup = createPopup({
features,
layers: tooltipEnabledLayersOnHover,
// enable close button to avoid occasional dangling tooltip that is not cleared during mouse leave action
showCloseButton: true,
showPagination: false,
showLayerSelection: false,
});
hoverPopup
?.setLngLat(getPopupLocation(features[0].geometry, e.lngLat))
.addTo(maplibreRef.current);
}
}

if (maplibreRef.current) {
const map = maplibreRef.current;
map.on('click', onClickMap);
// reset cursor to default when user is no longer hovering over a clickable feature
map.on('mouseleave', () => {
map.getCanvas().style.cursor = '';
hoverPopup?.remove();
});
map.on('mouseenter', () => {
map.getCanvas().style.cursor = 'pointer';
});
// add tooltip when users mouse move over a point
map.on('mousemove', onMouseMoveMap);
}

return () => {
if (maplibreRef.current) {
maplibreRef.current.off('click', onClickMap);
maplibreRef.current.off('mousemove', onMouseMoveMap);
}
};
}, [layers]);

// Handle map bounding box change, it should update the search if "request data around map extent" was enabled
useEffect(() => {
function renderLayers() {
Expand Down Expand Up @@ -317,6 +250,9 @@ export const MapContainer = ({
setIsUpdatingLayerRender={setIsUpdatingLayerRender}
/>
)}
{mounted && tooltipState === TOOLTIP_STATE.DISPLAY_FEATURES && (
<DisplayFeatures map={maplibreRef.current!} layers={layers} />
)}
<div className="map-container" ref={mapContainer} />
</div>
);
Expand Down
82 changes: 82 additions & 0 deletions public/components/tooltip/display_features.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { Map as Maplibre, MapEventType, Popup } from 'maplibre-gl';
import React, { memo, useEffect, Fragment } from 'react';
import {
createPopup,
getPopupLocation,
isTooltipEnabledLayer,
isTooltipEnabledOnHover,
} from './create_tooltip';
import { MapLayerSpecification } from '../../model/mapLayerType';

interface Props {
map: Maplibre;
layers: MapLayerSpecification[];
}

export const DisplayFeatures = memo(({ map, layers }: Props) => {
useEffect(() => {
let clickPopup: Popup | null = null;
let hoverPopup: Popup | null = null;

// We don't want to show layer information in the popup for the map tile layer
const tooltipEnabledLayers = layers.filter(isTooltipEnabledLayer);

function onClickMap(e: MapEventType['click']) {
// remove previous popup
clickPopup?.remove();

const features = map.queryRenderedFeatures(e.point);
if (features && map) {
clickPopup = createPopup({ features, layers: tooltipEnabledLayers });
clickPopup?.setLngLat(getPopupLocation(features[0].geometry, e.lngLat)).addTo(map);
}
}

function onMouseMoveMap(e: MapEventType['mousemove']) {
// remove previous popup
hoverPopup?.remove();

const tooltipEnabledLayersOnHover = layers.filter(isTooltipEnabledOnHover);
const features = map.queryRenderedFeatures(e.point);
if (features && map) {
hoverPopup = createPopup({
features,
layers: tooltipEnabledLayersOnHover,
// enable close button to avoid occasional dangling tooltip that is not cleared during mouse leave action
showCloseButton: true,
showPagination: false,
showLayerSelection: false,
});
hoverPopup?.setLngLat(getPopupLocation(features[0].geometry, e.lngLat)).addTo(map);
}
}

if (map) {
map.on('click', onClickMap);
// reset cursor to default when user is no longer hovering over a clickable feature
map.on('mouseleave', () => {
map.getCanvas().style.cursor = '';
hoverPopup?.remove();
});
map.on('mouseenter', () => {
map.getCanvas().style.cursor = 'pointer';
});
// add tooltip when users mouse move over a point
map.on('mousemove', onMouseMoveMap);
}

return () => {
if (map) {
map.off('click', onClickMap);
map.off('mousemove', onMouseMoveMap);
}
};
}, [layers]);

return <Fragment />;
});

0 comments on commit eb0bc0d

Please sign in to comment.