diff --git a/client/src/components/mapPopup.tsx b/client/src/components/mapPopup.tsx index 2b47e1d5..1d9c1a66 100644 --- a/client/src/components/mapPopup.tsx +++ b/client/src/components/mapPopup.tsx @@ -1,11 +1,10 @@ import React, {useRef, useEffect, useState} from 'react'; import * as styles from './mapPopup.module.scss'; import Overlay from 'ol/Overlay'; -import {Table} from '@trussworks/react-uswds'; import {Coordinate} from 'ol/Coordinate'; import Map from 'ol/Map'; import {FeatureLike} from 'ol/Feature'; -import * as constants from '../data/constants'; +import PopupContent from './popupContent'; interface IMapPopupProps { map: Map, @@ -19,10 +18,6 @@ const MapPopup = ({map, selectedFeature, position}: IMapPopupProps) => { const popupContentElement = useRef(null); const [currentOverlay, setCurrentOverlay] = useState(); - const readablePercent = (percent: number) => { - return `${(percent * 100).toFixed(2)}%`; - }; - useEffect(() => { popupCloserElement.current!.onclick = function() { overlay.setPosition(undefined); @@ -47,81 +42,13 @@ const MapPopup = ({map, selectedFeature, position}: IMapPopupProps) => { } }, [position]); - const getCategorization = (percentile: number) => { - let categorization; - if (percentile >= 0.75 ) { - categorization = 'Prioritized'; - } else if (0.60 <= percentile && percentile < 0.75) { - categorization = 'Threshold'; - } else { - categorization = 'Non-prioritized'; - } - return categorization; - }; - - const getTitleContent = () => { - const properties = selectedFeature.getProperties(); - const blockGroup = properties['GEOID10']; - const score = properties[constants.SCORE_PROPERTY]; - return ( - - - - - - - - - - - - - - - -
Census Block Group:{blockGroup}
Just Progress Categorization:{getCategorization(score)}
Cumulative Index Score:{readablePercent(score)}
- ); - }; - - const getBodyContent = () => { - const properties = selectedFeature.getProperties(); - const rows = []; - for (const [key, value] of Object.entries(properties)) { - // Filter out all caps - if (!key.match(/^[A-Z0-9]+$/)) { - rows.push( - {key} - {value} - ); - } - } - return rows; - }; - - const popupContent = (selectedFeature) ? ( -
- {getTitleContent()} - - - - - - - - - {getBodyContent()} - -
IndicatorPercentile(0-100)
-
- ) : null; - return ( <>
- {popupContent} +
diff --git a/client/src/components/mapboxMap.module.scss b/client/src/components/mapboxMap.module.scss index 8f7ef12b..a36c30ec 100644 --- a/client/src/components/mapboxMap.module.scss +++ b/client/src/components/mapboxMap.module.scss @@ -20,3 +20,9 @@ $sidebar-color: #ffffff; margin: 12px; border-radius: 4px; } + +.mapboxgl-popup { + background-color: red; + max-height: 300px; + overflow: scroll; +} diff --git a/client/src/components/mapboxMap.module.scss.d.ts b/client/src/components/mapboxMap.module.scss.d.ts index a2339ddf..f79d08b6 100644 --- a/client/src/components/mapboxMap.module.scss.d.ts +++ b/client/src/components/mapboxMap.module.scss.d.ts @@ -2,6 +2,7 @@ declare namespace MapboxMapModuleScssNamespace { export interface IMapboxMapModuleScss { sidebar: string; mapContainer: string; + mapboxglPopup: string; } } diff --git a/client/src/components/mapboxMap.tsx b/client/src/components/mapboxMap.tsx index c9a1950f..e6001fbe 100644 --- a/client/src/components/mapboxMap.tsx +++ b/client/src/components/mapboxMap.tsx @@ -1,10 +1,18 @@ /* eslint-disable no-unused-vars */ import React, {useRef, useEffect, useState} from 'react'; -import {LngLatBoundsLike, Map, NavigationControl} from 'mapbox-gl'; +import {LngLatBoundsLike, + Map, + NavigationControl, + PopupOptions, + Popup} from 'mapbox-gl'; import mapStyle from '../data/mapStyle'; import ZoomWarning from './zoomWarning'; +import PopupContent from './popupContent'; import * as styles from './mapboxMap.module.scss'; import * as constants from '../data/constants'; +import ReactDOM from 'react-dom'; + +type ClickEvent = mapboxgl.MapMouseEvent & mapboxgl.EventData; const MapboxMap = () => { const mapContainer = React.useRef(null); @@ -26,11 +34,33 @@ const MapboxMap = () => { maxZoom: constants.GLOBAL_MAX_ZOOM, maxBounds: constants.GLOBAL_MAX_BOUNDS as LngLatBoundsLike, }); - + initialMap.on('click', handleClick); initialMap.addControl(new NavigationControl()); map.current = initialMap; }); + const handleClick = (e: ClickEvent) => { + const map = e.target; + const clickedCoord = e.point; + const features = map.queryRenderedFeatures(clickedCoord, { + layers: ['score-low'], + }); + + if (features.length && features[0].properties) { + const placeholder = document.createElement('div'); + ReactDOM.render(, placeholder); + const options : PopupOptions = { + offset: [0, 0], + className: styles.mapboxglPopup, + }; + new Popup(options) + .setLngLat(e.lngLat) + .setDOMContent(placeholder) + .setMaxWidth('300px') + .addTo(map); + } + }; + useEffect(() => { if (!map.current) return; // wait for map to initialize map.current.on('move', () => { diff --git a/client/src/components/popupContent.tsx b/client/src/components/popupContent.tsx new file mode 100644 index 00000000..49c765ab --- /dev/null +++ b/client/src/components/popupContent.tsx @@ -0,0 +1,87 @@ +import * as React from 'react'; +import {Table} from '@trussworks/react-uswds'; +import * as constants from '../data/constants'; + +interface IPopupContentProps { + properties: constants.J40Properties, +} + + +const PopupContent = ({properties}:IPopupContentProps) => { + const readablePercent = (percent: number) => { + return `${(percent * 100).toFixed(2)}%`; + }; + + const getCategorization = (percentile: number) => { + let categorization; + if (percentile >= 0.75 ) { + categorization = 'Prioritized'; + } else if (0.60 <= percentile && percentile < 0.75) { + categorization = 'Threshold'; + } else { + categorization = 'Non-prioritized'; + } + return categorization; + }; + + const getTitleContent = (properties: constants.J40Properties) => { + const blockGroup = properties[constants.GEOID_PROPERTY]; + const score = properties[constants.SCORE_PROPERTY] as number; + return ( + + + + + + + + + + + + + + + +
Census Block Group:{blockGroup}
Just Progress Categorization:{getCategorization(score)}
Cumulative Index Score:{readablePercent(score)}
+ ); + }; + + const getBodyContent = (properties: constants.J40Properties) => { + const rows = []; + for (const [key, value] of Object.entries(properties)) { + // Filter out all caps + if (!key.match(/^[A-Z0-9]+$/)) { + rows.push( + {key} + {value} + ); + } + } + return rows; + }; + + + return ( + <> + {properties ? +
+ {getTitleContent(properties)} + + + + + + + + + {getBodyContent(properties)} + +
IndicatorPercentile(0-100)
+
: + '' } + + ); +}; + +export default PopupContent; diff --git a/client/src/data/constants.tsx b/client/src/data/constants.tsx index 8fbdabe6..7b4d3ab0 100644 --- a/client/src/data/constants.tsx +++ b/client/src/data/constants.tsx @@ -1,4 +1,5 @@ export const SCORE_PROPERTY = 'Score D (percentile)'; +export const GEOID_PROPERTY = 'GEOID10'; export const GLOBAL_MIN_ZOOM = 3; export const GLOBAL_MAX_ZOOM = 11; export const GLOBAL_MIN_ZOOM_LOW = 3; @@ -7,3 +8,4 @@ export const GLOBAL_MIN_ZOOM_HIGH = 9; export const GLOBAL_MAX_ZOOM_HIGH = 11; export const GLOBAL_MAX_BOUNDS = [[-167.276413, 5.499550], [-52.233040, 83.162102]]; export const DEFAULT_CENTER = [32.4687126, -86.502136]; +export type J40Properties = { [key: string]: any };