Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement [DEV-9861] Improve map territory labels #1712

Merged
merged 9 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions packages/core/helpers/viewports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@ import { ViewportSize } from '@cdc/map/src/types/MapConfig'

const BREAKPOINTS = ['xxs', 'xs', 'sm', 'md', 'lg']

export function isBelowBreakpoint(breakpoint: ViewportSize, currentViewport: ViewportSize) {
return BREAKPOINTS.indexOf(currentViewport) < BREAKPOINTS.indexOf(breakpoint)
}
export const isBelowBreakpoint = (breakpoint: ViewportSize, currentViewport: ViewportSize) =>
BREAKPOINTS.indexOf(currentViewport) < BREAKPOINTS.indexOf(breakpoint)

export function isLegendWrapViewport(currentViewport) {
return isBelowBreakpoint('sm', currentViewport)
}
export const isLegendWrapViewport = currentViewport => isBelowBreakpoint('sm', currentViewport)

export function isMobileHeightViewport(currentViewport) {
return isBelowBreakpoint('sm', currentViewport)
}
export const isMobileHeightViewport = currentViewport => isBelowBreakpoint('sm', currentViewport)
4 changes: 3 additions & 1 deletion packages/map/src/CdcMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1724,6 +1724,7 @@ const CdcMap = ({
isDebug,
isEditor,
loadConfig,
logo,
navigationHandler,
position,
resetLegendToggles,
Expand Down Expand Up @@ -1867,7 +1868,8 @@ const CdcMap = ({
{'us-region' === geoType && <UsaMap.Region />}
{'us-county' === geoType && <UsaMap.County />}
{'world' === geoType && <WorldMap />}
{'data' === general.type && logo && (
{/* logo is handled in UsaMap.State when applicable */}
{'data' === general.type && logo && ('us' !== geoType || 'us-geocode' === state.general.type) && (
<img src={logo} alt='' className='map-logo' style={{ maxWidth: '50px' }} />
)}
</>
Expand Down
6 changes: 5 additions & 1 deletion packages/map/src/components/Legend/components/Legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
const { legend } = state
const isLegendGradient = legend.style === 'gradient'
const boxDynamicallyHidden = isBelowBreakpoint('md', currentViewport)
const legendWrapping =
(legend.position === 'left' || legend.position === 'right') && isBelowBreakpoint('md', currentViewport)
const legendOnBottom = legend.position === 'bottom' || legendWrapping
const needsTopMargin = legend.hideBorder && legendOnBottom

// Toggles if a legend is active and being applied to the map and data table.
const toggleLegendActive = (i, legendLabel) => {
Expand Down Expand Up @@ -246,7 +250,7 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {

return (
<ErrorBoundary component='Sidebar'>
<div className='legends'>
<div className={`legends ${needsTopMargin ? 'mt-1' : ''}`}>
<aside
id={skipId || 'legend'}
className={legendClasses.aside.join(' ') || ''}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ const TerritoryRectangle: React.FC<TerritoryShape> = ({
const { state, supportedTerritories } = useContext<MapContext>(ConfigContext)
const { territoryData, ...otherProps } = props
const rectanglePath =
'M40,0.5 C41.2426407,0.5 42.3676407,1.00367966 43.1819805,1.81801948 C43.9963203,2.63235931 44.5,3.75735931 44.5,5 L44.5,5 L44.5,23 C44.5,24.2426407 43.9963203,25.3676407 43.1819805,26.1819805 C42.3676407,26.9963203 41.2426407,27.5 40,27.5 L40,27.5 L5,27.5 C3.75735931,27.5 2.63235931,26.9963203 1.81801948,26.1819805 C1.00367966,25.3676407 0.5,24.2426407 0.5,23 L0.5,23 L0.5,5 C0.5,3.75735931 1.00367966,2.63235931 1.81801948,1.81801948 C2.63235931,1.00367966 3.75735931,0.5 5,0.5 L5,0.5 Z'
'M42,0.5 C42.8284271,0.5 43.5,1.17157288 43.5,2 L43.5,2 L43.5,26 C43.5,26.8284271 42.8284271,27.5 42,27.5 L42,27.5 L3,27.5 C2.17157288,27.5 1.5,26.8284271 1.5,26 L1.5,26 L1.5,2 C1.5,1.17157288 2.17157288,0.5 3,0.5 L3,0.5 Z'

return (
<svg viewBox='0 0 45 28' key={territory} className={territory}>
<svg viewBox='0 0 45 29' key={territory} className={territory}>
<g
{...otherProps}
strokeLinejoin='round'
Expand All @@ -41,7 +41,7 @@ const TerritoryRectangle: React.FC<TerritoryShape> = ({
x='50%'
y='54%'
fill={text}
style={{ stroke: textColor, strokeWidth: 1 }}
style={{ stroke: 'none' }}
className='territory-text'
paintOrder='stroke'
onClick={handleShapeClick}
Expand Down
71 changes: 29 additions & 42 deletions packages/map/src/components/UsaMap/components/UsaMap.State.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ const UsaMap = () => {
titleCase,
tooltipId,
handleDragStateChange,
mapId
mapId,
logo,
} = useContext<MapContext>(ConfigContext)

let isFilterValueSupported = false
const { general, columns, feature, tooltips, hexMap, map, annotations } = state

if (setSharedFilterValue) {
Object.keys(supportedStates).forEach(supportedState => {
Expand Down Expand Up @@ -101,36 +103,28 @@ const UsaMap = () => {
useEffect(() => {
setTranslate([455, 250])
setExtent(null)
}, [state.general.geoType])
}, [general.geoType])

const isHex = state.general.displayAsHex
const isHex = general.displayAsHex

const [territoriesData, setTerritoriesData] = useState([])

const territoriesKeys = Object.keys(supportedTerritories) // data will have already mapped abbreviated territories to their full names

useEffect(() => {
if (state.general.territoriesAlwaysShow) {
if (general.territoriesAlwaysShow) {
// show all Territories whether in the data or not
setTerritoriesData(territoriesKeys)
} else {
// Territories need to show up if they're in the data at all, not just if they're "active". That's why this is different from Cities
const territoriesList = territoriesKeys.filter(key => data[key])
setTerritoriesData(territoriesList)
}
}, [data, state.general.territoriesAlwaysShow])
}, [data, general.territoriesAlwaysShow])

const geoStrokeColor = getGeoStrokeColor(state)
const geoFillColor = getGeoFillColor(state)

const getTerritoriesClasses = () => {
const screenWidth = window?.visualViewport?.width
let className = 'territories'
if (screenWidth < 700) return 'territories--mobile'
if (screenWidth < 900) return 'territories--tablet'
return className
}

const territories = territoriesData.map((territory, territoryIndex) => {
const Shape = isHex ? Territory.Hexagon : Territory.Rectangle

Expand All @@ -157,26 +151,19 @@ const UsaMap = () => {
let needsPointer = false

// If we need to add a pointer cursor
if (
(state.columns.navigate && territoryData[state.columns.navigate.name]) ||
state.tooltips.appearanceType === 'click'
) {
if ((columns.navigate && territoryData[columns.navigate.name]) || tooltips.appearanceType === 'click') {
needsPointer = true
}

styles = {
color: textColor,
fill: legendColors[0],
opacity:
setSharedFilterValue &&
isFilterValueSupported &&
setSharedFilterValue !== territoryData[state.columns.geo.name]
setSharedFilterValue && isFilterValueSupported && setSharedFilterValue !== territoryData[columns.geo.name]
? 0.5
: 1,
stroke:
setSharedFilterValue &&
isFilterValueSupported &&
setSharedFilterValue === territoryData[state.columns.geo.name]
setSharedFilterValue && isFilterValueSupported && setSharedFilterValue === territoryData[columns.geo.name]
? 'rgba(0, 0, 0, 1)'
: geoStrokeColor,
cursor: needsPointer ? 'pointer' : 'default',
Expand Down Expand Up @@ -214,7 +201,7 @@ const UsaMap = () => {

// Constructs and displays markup for all geos on the map (except territories right now)
const constructGeoJsx = (geographies, projection) => {
let showLabel = state.general.displayStateLabels
let showLabel = general.displayStateLabels

// Order alphabetically. Important for accessibility if ever read out loud.
geographies.map(state => {
Expand Down Expand Up @@ -268,11 +255,11 @@ const UsaMap = () => {
styles = {
fill: state.general.type !== 'bubble' ? legendColors[0] : geoFillColor,
opacity:
setSharedFilterValue && isFilterValueSupported && setSharedFilterValue !== geoData[state.columns.geo.name]
setSharedFilterValue && isFilterValueSupported && setSharedFilterValue !== geoData[columns.geo.name]
? 0.5
: 1,
stroke:
setSharedFilterValue && isFilterValueSupported && setSharedFilterValue === geoData[state.columns.geo.name]
setSharedFilterValue && isFilterValueSupported && setSharedFilterValue === geoData[columns.geo.name]
? 'rgba(0, 0, 0, 1)'
: geoStrokeColor,
cursor: 'default',
Expand All @@ -285,10 +272,7 @@ const UsaMap = () => {
}

// When to add pointer cursor
if (
(state.columns.navigate && geoData[state.columns.navigate.name]) ||
state.tooltips.appearanceType === 'click'
) {
if ((columns.navigate && geoData[columns.navigate.name]) || tooltips.appearanceType === 'click') {
styles.cursor = 'pointer'
}

Expand All @@ -301,7 +285,7 @@ const UsaMap = () => {

return (
<>
{state.hexMap.shapeGroups.map((group, groupIndex) => {
{hexMap.shapeGroups.map((group, groupIndex) => {
return group.items.map((item, itemIndex) => {
switch (item.operator) {
case '=':
Expand Down Expand Up @@ -408,7 +392,7 @@ const UsaMap = () => {
<path tabIndex={-1} className='single-geo' strokeWidth={1.3} d={path} />

{/* apply patterns on top of state path*/}
{state.map.patterns.map((patternData, patternIndex) => {
{map.patterns.map((patternData, patternIndex) => {
const { pattern, dataKey, size } = patternData
const currentFill = styles.fill
const hasMatchingValues = patternData.dataValue === geoData[patternData.dataKey]
Expand Down Expand Up @@ -456,7 +440,7 @@ const UsaMap = () => {
)
})}
{(isHex || showLabel) && geoLabel(geo, legendColors[0], projection)}
{isHex && state.hexMap.type === 'shapes' && getArrowDirection(geoData, geo, legendColors[0])}
{isHex && hexMap.type === 'shapes' && getArrowDirection(geoData, geo, legendColors[0])}
</g>
</g>
)
Expand Down Expand Up @@ -494,7 +478,7 @@ const UsaMap = () => {
)

// Bubbles
if (state.general.type === 'bubble') {
if (general.type === 'bubble') {
geosJsx.push(
<BubbleList
key='bubbles'
Expand Down Expand Up @@ -528,12 +512,12 @@ const UsaMap = () => {
let textColor = getContrastColor('#FFF', bgColor)

// always make HI black since it is off to the side
if (abbr === 'US-HI' && !state.general.displayAsHex) {
if (abbr === 'US-HI' && !general.displayAsHex) {
textColor = '#000'
}

let x = 0,
y = state.hexMap.type === 'shapes' && state.general.displayAsHex ? -10 : 5
y = hexMap.type === 'shapes' && general.displayAsHex ? -10 : 5

// used to nudge/move some of the labels for better readability
if (nudges[abbr] && false === isHex) {
Expand Down Expand Up @@ -580,7 +564,7 @@ const UsaMap = () => {
return (
<ErrorBoundary component='UsaMap'>
<svg viewBox='0 0 880 500' role='img' aria-label={handleMapAriaLabels(state)}>
{state.general.displayAsHex ? (
{general.displayAsHex ? (
<Mercator data={unitedStatesHex} scale={650} translate={[1600, 775]}>
{({ features, projection }) => constructGeoJsx(features, projection)}
</Mercator>
Expand All @@ -589,18 +573,21 @@ const UsaMap = () => {
{({ features, projection }) => constructGeoJsx(features, projection)}
</AlbersUsa>
)}
{state.annotations.length > 0 && <Annotation.Draggable onDragStateChange={handleDragStateChange} />}
{annotations.length > 0 && <Annotation.Draggable onDragStateChange={handleDragStateChange} />}
</svg>

{territories.length > 0 && (
<>
{/* Temporarily make the max width fit the image width */}
<div className='two-col' style={{ maxWidth: 'calc(100% - 75px)' }}>
<div>
<span className='territories-label label'>{state.general.territoriesLabel}</span>
<div>
<div className='d-flex mt-2'>
<h5>{general.territoriesLabel}</h5>
{'data' === general.type && logo && (
<img src={logo} alt='' className='map-logo' style={{ maxWidth: '50px' }} />
)}
</div>
<div>
<span className={getTerritoriesClasses()}>{territories}</span>
<span className='mt-1 mb-2 d-flex flex-wrap territories'>{territories}</span>
</div>
</div>
</>
Expand Down
65 changes: 7 additions & 58 deletions packages/map/src/scss/map.scss
Original file line number Diff line number Diff line change
Expand Up @@ -86,25 +86,10 @@ $medium: 768px;
transition: 0.2s all;
}
}
// make logo smaller on mobile
@media screen and (max-width: $small) {
.map-logo {
position: absolute;
bottom: 4em; // needed to align to top of Territories
right: 1em;
z-index: 3;
width: 50px; // make it smaller
}
}
// everything else but mobile
@media screen and (min-width: $small) {
.map-logo {
position: absolute;
bottom: 2em;
right: 1em;
z-index: 3;
width: 75px;
}
.map-logo {
display: block;
margin: 0 0 0 auto;
max-height: 35px;
}
}

Expand All @@ -116,61 +101,25 @@ $medium: 768px;
}
}

// for Territories label in one col and Territory blocks wrapping in 2nd column
.two-col {
display: flex;
margin-top: 0;

justify-content: flex-start;
> label {
margin-top: 0;
display: inline-block;
}
}

.territories-label {
color: black;
margin: 2em 5px 2em 1em;
font-size: 1.1em;
display: block;
margin-top: 15px;
}

// Cities and Territories
.territories {
margin: 2em 200px 2em 0;
font-size: 1.1em;
width: 100%;
display: block;
align-items: center;
> span {
margin-left: 1em;
margin-right: 0.5em;
}
gap: 0.5em;
svg {
max-width: 35px;
min-width: 25px;
margin-left: 0.5em;
transition: 0.3s all;

text {
font-size: 0.95em;
}
}

&--mobile {
@extend .territories;
width: 60%;
svg {
margin-bottom: 0.5em;
}
}

&--tablet {
@extend .territories;
width: 70%;
svg {
margin-bottom: 0.5em;
}
}
}

.zoom-controls {
Expand Down
1 change: 1 addition & 0 deletions packages/map/src/types/MapContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type MapContext = {
isEditor
isFilterValueSupported: boolean
loadConfig
logo: string
navigationHandler
position
resetLegendToggles
Expand Down
Loading