Various map styling fixes (#278)

Addresses #182, #291, #273 and #191 

In particular:
* Removes zoom fading for now
* adds legend
* Styles mapbox popup correctly
* Styles zoom control
* Adds feature borders at higher zoom levels
This commit is contained in:
Nat Hillard 2021-07-01 12:57:59 -04:00 committed by GitHub
commit beac44ef20
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 236 additions and 187 deletions

View file

@ -1,19 +0,0 @@
import React from 'react';
const MapLegend = () => {
return (
<>
<dl className={'j40-maplegend'}>
<h4>Color Key</h4>
<dt className={'mapsquare-a'}>&nbsp;</dt>
<dd>Prioritized community</dd>
<dt className={'mapsquare-b'}>&nbsp;</dt>
<dd>Threshold community</dd>
<dt className={'mapsquare-c'}>$nbsp;</dt>
<dd>Non-Prioritized community</dd>
</dl>
</>
);
};
export default MapLegend;

View file

@ -0,0 +1,41 @@
$min-color: #fafaf8;
$med-color: rgba(26, 68, 128, 0.2);
$max-color: rgba(26, 68, 128, 0.6);
.legendContainer {
margin-top: 19px;
font-size: 0.8em;
}
.swatchContainer {
display: flex;
flex-direction: row;
justify-content: flex-start;
}
.legendItem {
display: flex;
flex-direction: row;
align-items: center;
margin-right: 29px;
}
.colorSwatch {
box-sizing: border-box;
height: 20px;
width: 20px;
border: 1px solid #1a4480;
margin-right: 10px;
}
#prioritized {
background-color: $max-color;
}
#threshold {
background-color: $med-color;
}
#nonPrioritized {
background-color: $min-color;
}

View file

@ -0,0 +1,19 @@
declare namespace HowYouCanHelpModuleScssNamespace {
export interface IHowYouCanHelpModuleScss {
legendContainer: string;
legendHeader: string;
swatchContainer: string;
colorSwatch: string;
prioritized: string,
threshold: string,
nonPrioritized: string,
legendItem: string
}
}
declare const HowYouCanHelpModuleScssModule: HowYouCanHelpModuleScssNamespace.IHowYouCanHelpModuleScss & {
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
locals: HowYouCanHelpModuleScssNamespace.IHowYouCanHelpModuleScss;
};
export = HowYouCanHelpModuleScssModule;

View file

@ -0,0 +1,26 @@
import React from 'react';
import * as styles from './mapLegend.module.scss';
const MapLegend = () => {
return (
<div className={styles.legendContainer}>
<h3 className={styles.legendHeader}>COLOR KEY</h3>
<div className={styles.swatchContainer}>
<div className={styles.legendItem}>
<div className={styles.colorSwatch} id={styles.prioritized} />
<span>Prioritized Community</span>
</div>
<div className={styles.legendItem}>
<div className={styles.colorSwatch} id={styles.threshold} />
<span>Threshold Community</span>
</div>
<div className={styles.legendItem}>
<div className={styles.colorSwatch} id={styles.nonPrioritized} />
<span>Non-Prioritized Community</span>
</div>
</div>
</div>
);
};
export default MapLegend;

View file

@ -2,7 +2,6 @@ import * as React from 'react';
import {useFlags} from '../contexts/FlagContext';
import MapboxMap from './mapboxMap';
import OpenLayersMap from './openlayersMap';
import * as constants from '../data/constants';
const MapWrapper = () => {
const flags = useFlags();
@ -13,7 +12,6 @@ const MapWrapper = () => {
<MapboxMap /> :
<OpenLayersMap features={[]}/>
}
<p>Current Score Property: {constants.SCORE_PROPERTY}</p>
</div>
);
};

View file

@ -3,31 +3,12 @@ $sidebar-color: #ffffff;
.mapContainer {
height: 676px;
margin-bottom: 29px;
max-width: revert;
margin-top: 50px;
}
.sidebar {
background-color: $sidebar-background;
color: $sidebar-color;
padding: 6px 12px;
font-family: monospace;
z-index: 1;
position: absolute;
top: 300px;
left: 0;
margin: 12px;
border-radius: 4px;
}
.j40Popup {
max-height: 300px;
max-width: 300px;
overflow: scroll;
min-width: 36%;
max-width: 36%;
overflow-y: scroll;
pointer-events: all !important;
}
.j40Popup .mapboxgl-popup-content {
pointer-events: all;
}

View file

@ -1,6 +1,5 @@
declare namespace MapboxMapModuleScssNamespace {
export interface IMapboxMapModuleScss {
sidebar: string;
mapContainer: string;
j40Popup: string;
}

View file

@ -9,10 +9,10 @@ import {LngLatBoundsLike,
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';
import 'mapbox-gl/dist/mapbox-gl.css';
import * as styles from './mapboxMap.module.scss';
type ClickEvent = mapboxgl.MapMouseEvent & mapboxgl.EventData;
@ -34,8 +34,14 @@ const MapboxMap = () => {
maxZoom: constants.GLOBAL_MAX_ZOOM,
maxBounds: constants.GLOBAL_MAX_BOUNDS as LngLatBoundsLike,
});
// disable map rotation using right click + drag
initialMap.dragRotate.disable();
// disable map rotation using touch rotation gesture
initialMap.touchZoomRotate.disableRotation();
initialMap.on('click', handleClick);
initialMap.addControl(new NavigationControl());
initialMap.addControl(new NavigationControl({showCompass: false}), 'top-left');
map.current = initialMap;
});
@ -43,7 +49,7 @@ const MapboxMap = () => {
const map = e.target;
const clickedCoord = e.point;
const features = map.queryRenderedFeatures(clickedCoord, {
layers: ['score-low'],
layers: ['score'],
});
if (features.length && features[0].properties) {
@ -52,11 +58,11 @@ const MapboxMap = () => {
const options : PopupOptions = {
offset: [0, 0],
className: styles.j40Popup,
focusAfterOpen: false,
};
new Popup(options)
.setLngLat(e.lngLat)
.setDOMContent(placeholder)
.setMaxWidth('300px')
.addTo(map);
}
};
@ -66,10 +72,10 @@ const MapboxMap = () => {
map.current.on('move', () => {
setZoom(map.current.getZoom());
});
map.current.on('mouseenter', 'score-low', () => {
map.current.on('mouseenter', 'score', () => {
map.current.getCanvas().style.cursor = 'pointer';
});
map.current.on('mouseleave', 'score-low', () => {
map.current.on('mouseleave', 'score', () => {
map.current.getCanvas().style.cursor = '';
});
});

View file

@ -10,7 +10,7 @@ import {Coordinate} from 'ol/coordinate';
import olms from 'ol-mapbox-style';
import mapStyle from '../data/mapStyle';
import ZoomWarning from './zoomWarning';
import MapPopup from './mapPopup';
import OpenlayersPopup from './openlayersPopup';
import {transformExtent} from 'ol/src/proj';
import * as styles from './openlayersMap.module.scss';
import * as constants from '../data/constants';
@ -121,7 +121,7 @@ const MapWrapper = ({features}: IMapWrapperProps) => {
<>
<div ref={mapElement} className={styles.mapContainer}/>
{map?
<MapPopup selectedFeature={selectedFeature!} map={map!} position={currentOverlayPosition} /> :
<OpenlayersPopup selectedFeature={selectedFeature!} map={map!} position={currentOverlayPosition} /> :
''
}
<ZoomWarning zoomLevel={currentZoom} />

View file

@ -1,18 +1,18 @@
import React, {useRef, useEffect, useState} from 'react';
import * as styles from './mapPopup.module.scss';
import * as styles from './openlayersPopup.module.scss';
import Overlay from 'ol/Overlay';
import {Coordinate} from 'ol/Coordinate';
import Map from 'ol/Map';
import {FeatureLike} from 'ol/Feature';
import PopupContent from './popupContent';
interface IMapPopupProps {
interface IOpenlayersPopupProps {
map: Map,
selectedFeature: FeatureLike;
position: Coordinate;
}
const MapPopup = ({map, selectedFeature, position}: IMapPopupProps) => {
const OpenlayersPopup = ({map, selectedFeature, position}: IOpenlayersPopupProps) => {
const popupContainerElement = useRef<HTMLDivElement>(null);
const popupCloserElement = useRef<HTMLAnchorElement>(null);
const popupContentElement = useRef<HTMLDivElement>(null);
@ -55,4 +55,4 @@ const MapPopup = ({map, selectedFeature, position}: IMapPopupProps) => {
);
};
export default MapPopup;
export default OpenlayersPopup;

View file

@ -0,0 +1,16 @@
.popupContentTable {
min-width: 400px;
}
.popupContentTable > thead {
background-color: #edeff0;
}
.titleContainer {
display: flex;
flex-direction: column;
}
.titleIndicatorName {
font-weight: bold;
}

View file

@ -0,0 +1,15 @@
declare namespace MapModuleScssNamespace {
export interface IMapModuleScss {
popupContainer: string;
popupContentTable:string;
titleContainer:string;
titleIndicatorName:string;
}
}
declare const MapModuleScssModule: MapModuleScssNamespace.IMapModuleScss & {
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
locals: MapModuleScssNamespace.IMapModuleScss;
};
export = MapModuleScssModule;

View file

@ -1,6 +1,6 @@
import * as React from 'react';
import {Table} from '@trussworks/react-uswds';
import * as constants from '../data/constants';
import * as styles from './popupContent.module.scss';
interface IPopupContentProps {
properties: constants.J40Properties,
@ -9,7 +9,7 @@ interface IPopupContentProps {
const PopupContent = ({properties}:IPopupContentProps) => {
const readablePercent = (percent: number) => {
return `${(percent * 100).toFixed(2)}%`;
return `${(percent * 100).toFixed(2)}`;
};
const getCategorization = (percentile: number) => {
@ -28,28 +28,32 @@ const PopupContent = ({properties}:IPopupContentProps) => {
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>
<div className={styles.titleContainer}>
<div>
<span className={styles.titleIndicatorName}>Census Block Group: </span>
<span>{blockGroup}</span>
</div>
<div>
<span className={styles.titleIndicatorName}>Just Progress Categorization: </span>
<span>{getCategorization(score)}</span>
</div>
<div>
<span className={styles.titleIndicatorName}>Cumulative Index Score: </span>
<span>{readablePercent(score)}</span>
</div>
</div>
);
};
const getBodyContent = (properties: constants.J40Properties) => {
const rows = [];
for (const [key, value] of Object.entries(properties)) {
const sortedKeys = Object.entries(properties).sort();
for (let [key, value] of sortedKeys) {
// We should only format floats
if (typeof value === 'number' && value % 1 !== 0) {
value = readablePercent(value);
}
// Filter out all caps
if (!key.match(/^[A-Z0-9]+$/)) {
rows.push(<tr key={key} >
@ -65,19 +69,19 @@ const PopupContent = ({properties}:IPopupContentProps) => {
return (
<>
{properties ?
<div>
<div id='popupContainer'>
{getTitleContent(properties)}
<Table bordered={false}>
<table className={'usa-table usa-table--borderless ' + styles.popupContentTable}>
<thead>
<tr>
<th scope="col">Indicator</th>
<th scope="col">Percentile(0-100)</th>
<th scope="col">INDICATOR</th>
<th scope="col">VALUE</th>
</tr>
</thead>
<tbody>
{getBodyContent(properties)}
</tbody>
</Table>
</table>
</div> :
'' }
</>

View file

@ -1,6 +1,6 @@
.zoomWarning {
background-color: #953a10;
height: 5.5%;
height: 4.3%;
width: 66%;
margin: auto;
color: white;
@ -9,7 +9,7 @@
justify-content: center;
position: absolute;
top: 50%;
left: 20%;
left: 15%;
}
.zoomWarning > img {