refactoring popup into shared component

This commit is contained in:
Nat Hillard 2021-06-29 23:19:33 -04:00
parent ce56b1ce1f
commit 4dbe05275f
6 changed files with 130 additions and 77 deletions

View file

@ -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<HTMLDivElement>(null);
const [currentOverlay, setCurrentOverlay] = useState<Overlay>();
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 (
<table>
<tbody>
<tr>
<td><strong>Census Block Group:</strong></td>
<td>{blockGroup}</td>
</tr>
<tr>
<td><strong>Just Progress Categorization:</strong></td>
<td>{getCategorization(score)}</td>
</tr>
<tr>
<td><strong>Cumulative Index Score:</strong></td>
<td>{readablePercent(score)}</td>
</tr>
</tbody>
</table>
);
};
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(<tr key={key} >
<td>{key}</td>
<td>{value}</td>
</tr>);
}
}
return rows;
};
const popupContent = (selectedFeature) ? (
<div>
{getTitleContent()}
<Table bordered={false}>
<thead>
<tr>
<th scope="col">Indicator</th>
<th scope="col">Percentile(0-100)</th>
</tr>
</thead>
<tbody>
{getBodyContent()}
</tbody>
</Table>
</div>
) : null;
return (
<>
<div ref={popupContainerElement}
className={styles.popupContainer}>
<a href="#" ref={popupCloserElement} className={styles.popupCloser}></a>
<div ref={popupContentElement} className={styles.popupContent}>
{popupContent}
<PopupContent properties={selectedFeature?.getProperties()} />
</div>
</div>
</>

View file

@ -20,3 +20,9 @@ $sidebar-color: #ffffff;
margin: 12px;
border-radius: 4px;
}
.mapboxgl-popup {
background-color: red;
max-height: 300px;
overflow: scroll;
}

View file

@ -2,6 +2,7 @@ declare namespace MapboxMapModuleScssNamespace {
export interface IMapboxMapModuleScss {
sidebar: string;
mapContainer: string;
mapboxglPopup: string;
}
}

View file

@ -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<HTMLDivElement>(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(<PopupContent properties={features[0].properties} />, 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', () => {

View file

@ -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 (
<table>
<tbody>
<tr>
<td><strong>Census Block Group:</strong></td>
<td>{blockGroup}</td>
</tr>
<tr>
<td><strong>Just Progress Categorization:</strong></td>
<td>{getCategorization(score)}</td>
</tr>
<tr>
<td><strong>Cumulative Index Score:</strong></td>
<td>{readablePercent(score)}</td>
</tr>
</tbody>
</table>
);
};
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(<tr key={key} >
<td>{key}</td>
<td>{value}</td>
</tr>);
}
}
return rows;
};
return (
<>
{properties ?
<div>
{getTitleContent(properties)}
<Table bordered={false}>
<thead>
<tr>
<th scope="col">Indicator</th>
<th scope="col">Percentile(0-100)</th>
</tr>
</thead>
<tbody>
{getBodyContent(properties)}
</tbody>
</Table>
</div> :
'' }
</>
);
};
export default PopupContent;

View file

@ -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 };