@cdc/map 4.24.12-2 → 4.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcmap.js +47146 -45979
- package/examples/annotation/index.json +1 -1
- package/examples/custom-map-layers.json +1 -1
- package/examples/default-geocode.json +2 -2
- package/examples/private/mmr.json +246 -0
- package/index.html +12 -14
- package/package.json +8 -3
- package/src/CdcMap.tsx +85 -362
- package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +9 -0
- package/src/_stories/CdcMap.stories.tsx +1 -1
- package/src/_stories/GoogleMap.stories.tsx +19 -0
- package/src/_stories/_mock/DEV-10148.json +859 -0
- package/src/_stories/_mock/DEV-9989.json +229 -0
- package/src/_stories/_mock/example-city-state.json +1 -1
- package/src/_stories/_mock/google-map.json +819 -0
- package/src/components/Annotation/Annotation.Draggable.tsx +34 -43
- package/src/components/Annotation/AnnotationDropdown.tsx +4 -4
- package/src/components/CityList.tsx +2 -2
- package/src/components/DataTable.tsx +8 -9
- package/src/components/EditorPanel/components/EditorPanel.tsx +90 -17
- package/src/components/GoogleMap/components/GoogleMap.tsx +67 -0
- package/src/components/GoogleMap/index.tsx +3 -0
- package/src/components/Legend/components/Legend.tsx +40 -30
- package/src/components/Legend/components/LegendItem.Hex.tsx +7 -3
- package/src/components/Legend/components/index.scss +22 -16
- package/src/components/Modal.tsx +6 -5
- package/src/components/NavigationMenu.tsx +5 -4
- package/src/components/UsaMap/components/TerritoriesSection.tsx +56 -0
- package/src/components/UsaMap/components/UsaMap.County.tsx +1 -1
- package/src/components/UsaMap/components/UsaMap.Region.tsx +12 -8
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +2 -2
- package/src/components/UsaMap/components/UsaMap.State.tsx +22 -28
- package/src/components/WorldMap/WorldMap.tsx +3 -5
- package/src/context.ts +0 -12
- package/src/data/initial-state.js +2 -2
- package/src/data/supported-geos.js +23 -3
- package/src/helpers/applyColorToLegend.ts +3 -3
- package/src/helpers/closeModal.ts +9 -0
- package/src/helpers/handleMapAriaLabels.ts +38 -0
- package/src/helpers/indexOfIgnoreType.ts +8 -0
- package/src/helpers/navigationHandler.ts +21 -0
- package/src/helpers/toTitleCase.ts +44 -0
- package/src/helpers/validateFipsCodeLength.ts +30 -0
- package/src/hooks/useResizeObserver.ts +42 -0
- package/src/hooks/useTooltip.ts +4 -2
- package/src/index.jsx +1 -0
- package/src/scss/editor-panel.scss +2 -1
- package/src/scss/filters.scss +0 -5
- package/src/scss/main.scss +57 -61
- package/src/scss/map.scss +1 -13
- package/src/types/MapConfig.ts +19 -11
- package/src/types/MapContext.ts +4 -12
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import { useContext, useState,
|
|
2
|
-
|
|
3
|
-
// helpers
|
|
4
|
-
import { applyBandScaleOffset, handleConnectionHorizontalType, handleConnectionVerticalType, createPoints } from '@cdc/chart/src/components/Annotations/components/helpers'
|
|
1
|
+
import { useContext, useState, useRef } from 'react'
|
|
5
2
|
|
|
6
3
|
// visx
|
|
7
|
-
import { HtmlLabel, CircleSubject,
|
|
4
|
+
import { HtmlLabel, CircleSubject, EditableAnnotation, Connector, Annotation as VisxAnnotation } from '@visx/annotation'
|
|
8
5
|
import { Drag, raise } from '@visx/drag'
|
|
9
6
|
import { MarkerArrow } from '@visx/marker'
|
|
10
|
-
import { LinePath } from '@visx/shape'
|
|
11
|
-
import * as allCurves from '@visx/curve'
|
|
12
|
-
import { Annotation } from '@cdc/core/types/Annotation'
|
|
13
7
|
|
|
14
8
|
// styles
|
|
15
9
|
import './Annotation.Draggable.styles.css'
|
|
@@ -18,41 +12,18 @@ import { MapContext } from '../../types/MapContext'
|
|
|
18
12
|
|
|
19
13
|
const Annotations = ({ xScale, yScale, xMax, svgRef, onDragStateChange }) => {
|
|
20
14
|
const [draggingItems, setDraggingItems] = useState([])
|
|
21
|
-
const {
|
|
15
|
+
const {
|
|
16
|
+
state: config,
|
|
17
|
+
setState: updateConfig,
|
|
18
|
+
isDraggingAnnotation,
|
|
19
|
+
isEditor,
|
|
20
|
+
dimensions
|
|
21
|
+
} = useContext<MapContext>(ConfigContext)
|
|
22
22
|
const [width, height] = dimensions
|
|
23
23
|
const { annotations } = config
|
|
24
|
-
// const { colorScale } = useColorScale()
|
|
25
24
|
const prevDimensions = useRef(dimensions)
|
|
26
25
|
const AnnotationComponent = isEditor ? EditableAnnotation : VisxAnnotation
|
|
27
26
|
|
|
28
|
-
const handleMobileXPosition = annotation => {
|
|
29
|
-
if (annotation.snapToNearestPoint) {
|
|
30
|
-
return Number(annotation.dx) + xScale(annotation.xKey) + (config.xAxis.type !== 'date-time' ? xScale.bandwidth() / 2 : 0) + Number(config.yAxis.size)
|
|
31
|
-
}
|
|
32
|
-
return Number(annotation.x) + Number(annotation.dx)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const handleMobileYPosition = annotation => {
|
|
36
|
-
if (annotation.snapToNearestPoint) {
|
|
37
|
-
return yScale(annotation.yKey) + Number(annotation.dy)
|
|
38
|
-
}
|
|
39
|
-
return Number(annotation.dy) + Number(annotation.y)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const handleTextX = annotation => {
|
|
43
|
-
if (annotation.snapToNearestPoint) {
|
|
44
|
-
return Number(annotation.dx) + Number(xScale(annotation.xKey)) + (config.xAxis.type !== 'date-time' ? xScale.bandwidth() / 2 : 0) + Number(config.yAxis.size) - 16 / 3
|
|
45
|
-
}
|
|
46
|
-
return Number(annotation.dx) + Number(annotation.x) - 16 / 3
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const handleTextY = annotation => {
|
|
50
|
-
if (annotation.snapToNearestPoint) {
|
|
51
|
-
return yScale(annotation.yKey) + Number(annotation.dy) + 5
|
|
52
|
-
}
|
|
53
|
-
return Number(annotation.y) + Number(annotation.dy) + 16 / 3
|
|
54
|
-
}
|
|
55
|
-
|
|
56
27
|
return (
|
|
57
28
|
annotations &&
|
|
58
29
|
annotations.map((annotation, index) => {
|
|
@@ -118,7 +89,9 @@ const Annotations = ({ xScale, yScale, xMax, svgRef, onDragStateChange }) => {
|
|
|
118
89
|
style={{
|
|
119
90
|
padding: '10px',
|
|
120
91
|
borderRadius: 5, // Optional: set border radius
|
|
121
|
-
backgroundColor: `rgba(255, 255, 255, ${
|
|
92
|
+
backgroundColor: `rgba(255, 255, 255, ${
|
|
93
|
+
annotation?.opacity ? Number(annotation?.opacity) / 100 : 1
|
|
94
|
+
})`
|
|
122
95
|
}}
|
|
123
96
|
role='presentation'
|
|
124
97
|
// ! IMPORTANT: Workaround for 508
|
|
@@ -131,13 +104,31 @@ const Annotations = ({ xScale, yScale, xMax, svgRef, onDragStateChange }) => {
|
|
|
131
104
|
/>
|
|
132
105
|
</HtmlLabel>
|
|
133
106
|
|
|
134
|
-
{annotation.connectionType === 'line' &&
|
|
107
|
+
{annotation.connectionType === 'line' && (
|
|
108
|
+
<Connector type='line' pathProps={{ markerStart: 'url(#marker-start)' }} />
|
|
109
|
+
)}
|
|
135
110
|
|
|
136
|
-
{annotation.connectionType === 'elbow' &&
|
|
111
|
+
{annotation.connectionType === 'elbow' && (
|
|
112
|
+
<Connector type='elbow' pathProps={{ markerStart: 'url(#marker-start)' }} />
|
|
113
|
+
)}
|
|
137
114
|
|
|
138
115
|
{/* MARKERS */}
|
|
139
|
-
{annotation.marker === 'circle' &&
|
|
140
|
-
|
|
116
|
+
{annotation.marker === 'circle' && (
|
|
117
|
+
<CircleSubject className='circle-subject' stroke={'black'} radius={8} />
|
|
118
|
+
)}
|
|
119
|
+
{annotation.marker === 'arrow' && (
|
|
120
|
+
<MarkerArrow
|
|
121
|
+
fill='black'
|
|
122
|
+
id='marker-start'
|
|
123
|
+
x={annotation.x}
|
|
124
|
+
y={annotation.dy}
|
|
125
|
+
stroke='#333'
|
|
126
|
+
markerWidth={10}
|
|
127
|
+
size={10}
|
|
128
|
+
strokeWidth={1}
|
|
129
|
+
orient='auto-start-reverse'
|
|
130
|
+
/>
|
|
131
|
+
)}
|
|
141
132
|
</AnnotationComponent>
|
|
142
133
|
</>
|
|
143
134
|
)
|
|
@@ -2,14 +2,14 @@ import React, { useContext, useState } from 'react'
|
|
|
2
2
|
import ConfigContext from '../../context'
|
|
3
3
|
import './AnnotationDropdown.styles.css'
|
|
4
4
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
5
|
-
import {
|
|
5
|
+
import { appFontSize } from '@cdc/core/helpers/cove/fontSettings'
|
|
6
6
|
import AnnotationList from './AnnotationList'
|
|
7
7
|
|
|
8
8
|
const AnnotationDropdown = () => {
|
|
9
|
-
const {
|
|
9
|
+
const { state: config, isEditor, currentViewport: viewport } = useContext(ConfigContext)
|
|
10
10
|
const [expanded, setExpanded] = useState(false)
|
|
11
11
|
|
|
12
|
-
const titleFontSize = ['sm', 'xs', 'xxs'].includes(viewport) ? '13px' : `${
|
|
12
|
+
const titleFontSize = ['sm', 'xs', 'xxs'].includes(viewport) ? '13px' : `${appFontSize}px`
|
|
13
13
|
|
|
14
14
|
const annotations = config?.annotations || []
|
|
15
15
|
|
|
@@ -19,7 +19,7 @@ const AnnotationDropdown = () => {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const handleAccordionClassName = () => {
|
|
22
|
-
const classNames = ['data-table-heading', 'annotation__dropdown-list']
|
|
22
|
+
const classNames = ['data-table-heading', 'annotation__dropdown-list', 'p-3']
|
|
23
23
|
if (!expanded) {
|
|
24
24
|
classNames.push('collapsed')
|
|
25
25
|
}
|
|
@@ -5,6 +5,7 @@ import { supportedCities } from '../data/supported-geos'
|
|
|
5
5
|
import { scaleLinear } from 'd3-scale'
|
|
6
6
|
import { GlyphStar, GlyphTriangle, GlyphDiamond, GlyphSquare, GlyphCircle } from '@visx/glyph'
|
|
7
7
|
import { getFilterControllingStatePicked } from './UsaMap/helpers/map'
|
|
8
|
+
import { titleCase } from '../helpers/titleCase'
|
|
8
9
|
|
|
9
10
|
import ConfigContext from '../context'
|
|
10
11
|
import { getGeoStrokeColor } from '../helpers/colors'
|
|
@@ -15,14 +16,13 @@ const CityList = ({
|
|
|
15
16
|
applyTooltipsToGeo,
|
|
16
17
|
displayGeoName,
|
|
17
18
|
applyLegendToRow,
|
|
18
|
-
titleCase,
|
|
19
19
|
setSharedFilterValue,
|
|
20
20
|
isFilterValueSupported,
|
|
21
21
|
tooltipId,
|
|
22
22
|
projection
|
|
23
23
|
}) => {
|
|
24
24
|
const [citiesData, setCitiesData] = useState({})
|
|
25
|
-
const {
|
|
25
|
+
const { state, topoData, runtimeData, position } = useContext(ConfigContext)
|
|
26
26
|
if (!projection) return
|
|
27
27
|
|
|
28
28
|
useEffect(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useState, memo } from 'react'
|
|
1
|
+
import React, { useEffect, useState, memo, useContext } from 'react'
|
|
2
2
|
|
|
3
3
|
import Papa from 'papaparse'
|
|
4
4
|
import ExternalIcon from '../images/external-link.svg' // TODO: Move to Icon component
|
|
@@ -10,6 +10,8 @@ import MediaControls from '@cdc/core/components/MediaControls'
|
|
|
10
10
|
import SkipTo from '@cdc/core/components/elements/SkipTo'
|
|
11
11
|
|
|
12
12
|
import Loading from '@cdc/core/components/Loading'
|
|
13
|
+
import { navigationHandler } from '../helpers/navigationHandler'
|
|
14
|
+
import ConfigContext from '../context'
|
|
13
15
|
|
|
14
16
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
15
17
|
const DataTable = props => {
|
|
@@ -26,18 +28,15 @@ const DataTable = props => {
|
|
|
26
28
|
displayDataAsText,
|
|
27
29
|
applyLegendToRow,
|
|
28
30
|
displayGeoName,
|
|
29
|
-
navigationHandler,
|
|
30
|
-
viewport,
|
|
31
31
|
formatLegendLocation,
|
|
32
32
|
tabbingId,
|
|
33
33
|
setFilteredCountryCode
|
|
34
34
|
} = props
|
|
35
35
|
|
|
36
|
+
const { currentViewport: viewport } = useContext(ConfigContext)
|
|
36
37
|
const [expanded, setExpanded] = useState(expandDataTable)
|
|
37
38
|
const [sortBy, setSortBy] = useState({ column: 'geo', asc: false })
|
|
38
|
-
|
|
39
39
|
const [accessibilityLabel, setAccessibilityLabel] = useState('')
|
|
40
|
-
|
|
41
40
|
const fileName = `${mapTitle || 'data-table'}.csv`
|
|
42
41
|
|
|
43
42
|
// Catch all sorting method used on load by default but also on user click
|
|
@@ -118,14 +117,14 @@ const DataTable = props => {
|
|
|
118
117
|
if (columns.navigate && row[columns.navigate.name]) {
|
|
119
118
|
markup = (
|
|
120
119
|
<span
|
|
121
|
-
onClick={() => navigationHandler(row[columns.navigate.name])}
|
|
120
|
+
onClick={() => navigationHandler(state.general.navigationTarget, row[columns.navigate.name])}
|
|
122
121
|
className='table-link'
|
|
123
122
|
title='Click for more information (Opens in a new window)'
|
|
124
123
|
role='link'
|
|
125
124
|
tabIndex='0'
|
|
126
125
|
onKeyDown={e => {
|
|
127
126
|
if (e.keyCode === 13) {
|
|
128
|
-
navigationHandler(row[columns.navigate.name])
|
|
127
|
+
navigationHandler(state.general.navigationTarget, row[columns.navigate.name])
|
|
129
128
|
}
|
|
130
129
|
}}
|
|
131
130
|
>
|
|
@@ -192,7 +191,7 @@ const DataTable = props => {
|
|
|
192
191
|
|
|
193
192
|
const TableMediaControls = ({ belowTable }) => {
|
|
194
193
|
return (
|
|
195
|
-
<MediaControls.Section classes={['download-links']
|
|
194
|
+
<MediaControls.Section classes={['download-links']}>
|
|
196
195
|
<MediaControls.Link config={state} />
|
|
197
196
|
{state.general.showDownloadButton && <DownloadButton />}
|
|
198
197
|
</MediaControls.Section>
|
|
@@ -345,7 +344,7 @@ const DataTable = props => {
|
|
|
345
344
|
</>
|
|
346
345
|
)
|
|
347
346
|
} else {
|
|
348
|
-
cellValue = displayDataAsText(runtimeData[row][state.columns[column].name], column)
|
|
347
|
+
cellValue = displayDataAsText(runtimeData[row][state.columns[column].name], column, state)
|
|
349
348
|
}
|
|
350
349
|
|
|
351
350
|
return (
|
|
@@ -44,12 +44,12 @@ import { MapContext } from '../../../types/MapContext.js'
|
|
|
44
44
|
import { TextField } from './Inputs'
|
|
45
45
|
import Alert from '@cdc/core/components/Alert'
|
|
46
46
|
import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
|
|
47
|
+
import { Select } from '@cdc/core/components/EditorPanel/Inputs'
|
|
47
48
|
|
|
48
49
|
// Todo: move to useReducer, seperate files out.
|
|
49
50
|
const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
50
51
|
// prettier-ignore
|
|
51
52
|
const {
|
|
52
|
-
columnsInData = [],
|
|
53
53
|
isDashboard,
|
|
54
54
|
isDebug,
|
|
55
55
|
loadConfig,
|
|
@@ -67,6 +67,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
67
67
|
} = useContext<MapContext>(ConfigContext)
|
|
68
68
|
|
|
69
69
|
const { general, columns, legend, table, tooltips } = state
|
|
70
|
+
const columnsInData = state?.data?.[0] ? Object.keys(state.data[0]) : []
|
|
70
71
|
|
|
71
72
|
const [configTextboxValue, setConfigTextbox] = useState({}) // eslint-disable-line
|
|
72
73
|
|
|
@@ -231,6 +232,15 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
231
232
|
|
|
232
233
|
const handleEditorChanges = async (property, value) => {
|
|
233
234
|
switch (property) {
|
|
235
|
+
case 'navigationTarget':
|
|
236
|
+
setState({
|
|
237
|
+
...state,
|
|
238
|
+
general: {
|
|
239
|
+
...state.general,
|
|
240
|
+
navigationTarget: value
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
break
|
|
234
244
|
// change these to be more generic.
|
|
235
245
|
// updateVisualPropertyValue
|
|
236
246
|
// updateGeneralPropertyValue, etc.
|
|
@@ -715,6 +725,14 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
715
725
|
}
|
|
716
726
|
})
|
|
717
727
|
break
|
|
728
|
+
case 'google-map':
|
|
729
|
+
setState({
|
|
730
|
+
...state,
|
|
731
|
+
general: {
|
|
732
|
+
...state.general,
|
|
733
|
+
geoType: 'google-map'
|
|
734
|
+
}
|
|
735
|
+
})
|
|
718
736
|
default:
|
|
719
737
|
break
|
|
720
738
|
}
|
|
@@ -1369,13 +1387,29 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1369
1387
|
</AccordionItemHeading>
|
|
1370
1388
|
<AccordionItemPanel>
|
|
1371
1389
|
{/* Geography */}
|
|
1390
|
+
{/*<Select*/}
|
|
1391
|
+
{/* options={[*/}
|
|
1392
|
+
{/* { value: 'us', label: 'United States' },*/}
|
|
1393
|
+
{/* { value: 'us-region', label: 'U.S. Region' },*/}
|
|
1394
|
+
{/* { value: 'world', label: 'World' },*/}
|
|
1395
|
+
{/* { value: 'single-state', label: 'U.S. State' },*/}
|
|
1396
|
+
{/* { value: 'google-map', label: 'Google Map API' }*/}
|
|
1397
|
+
{/* ]}*/}
|
|
1398
|
+
{/* section={'general'}*/}
|
|
1399
|
+
{/* fieldName={'geoType'}*/}
|
|
1400
|
+
{/* label='Geography'*/}
|
|
1401
|
+
{/* updateField={updateField}*/}
|
|
1402
|
+
{/*/>*/}
|
|
1403
|
+
|
|
1372
1404
|
<label>
|
|
1373
1405
|
<span className='edit-label column-heading'>
|
|
1374
1406
|
<span>Geography</span>
|
|
1375
1407
|
</span>
|
|
1376
|
-
<ul className='geo-buttons'>
|
|
1408
|
+
<ul className='geo-buttons d-grid' style={{ gridTemplateColumns: 'repeat(2, 1fr)', gap: '8px' }}>
|
|
1377
1409
|
<button
|
|
1378
|
-
className={
|
|
1410
|
+
className={`${
|
|
1411
|
+
state.general.geoType === 'us' || state.general.geoType === 'us-county' ? 'active' : ''
|
|
1412
|
+
} full-width`}
|
|
1379
1413
|
onClick={e => {
|
|
1380
1414
|
e.preventDefault()
|
|
1381
1415
|
handleEditorChanges('geoType', 'us')
|
|
@@ -1385,7 +1419,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1385
1419
|
<span>United States</span>
|
|
1386
1420
|
</button>
|
|
1387
1421
|
<button
|
|
1388
|
-
className={state.general.geoType === 'us-region' ? 'active' : ''}
|
|
1422
|
+
className={`${state.general.geoType === 'us-region' ? 'active' : ''} full-width`}
|
|
1389
1423
|
onClick={e => {
|
|
1390
1424
|
e.preventDefault()
|
|
1391
1425
|
handleEditorChanges('geoType', 'us-region')
|
|
@@ -1395,7 +1429,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1395
1429
|
<span>U.S. Region</span>
|
|
1396
1430
|
</button>
|
|
1397
1431
|
<button
|
|
1398
|
-
className={state.general.geoType === 'world' ? 'active' : ''}
|
|
1432
|
+
className={`${state.general.geoType === 'world' ? 'active' : ''} full-width`}
|
|
1399
1433
|
onClick={e => {
|
|
1400
1434
|
e.preventDefault()
|
|
1401
1435
|
handleEditorChanges('geoType', 'world')
|
|
@@ -1405,7 +1439,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1405
1439
|
<span>World</span>
|
|
1406
1440
|
</button>
|
|
1407
1441
|
<button
|
|
1408
|
-
className={state.general.geoType === 'single-state' ? 'active' : ''}
|
|
1442
|
+
className={`${state.general.geoType === 'single-state' ? 'active' : ''} full-width`}
|
|
1409
1443
|
onClick={e => {
|
|
1410
1444
|
e.preventDefault()
|
|
1411
1445
|
handleEditorChanges('geoType', 'single-state')
|
|
@@ -1414,6 +1448,16 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1414
1448
|
<AlabamaGraphic />
|
|
1415
1449
|
<span>U.S. State</span>
|
|
1416
1450
|
</button>
|
|
1451
|
+
{/* <button
|
|
1452
|
+
className={`${state.general.geoType === 'google-map' ? 'active' : ''} full-width`}
|
|
1453
|
+
onClick={e => {
|
|
1454
|
+
e.preventDefault()
|
|
1455
|
+
handleEditorChanges('geoType', 'google-map')
|
|
1456
|
+
}}
|
|
1457
|
+
>
|
|
1458
|
+
<UsaGraphic />
|
|
1459
|
+
<span>Google Map Api</span>
|
|
1460
|
+
</button> */}
|
|
1417
1461
|
</ul>
|
|
1418
1462
|
</label>
|
|
1419
1463
|
{/* Select > State or County Map */}
|
|
@@ -1526,6 +1570,23 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1526
1570
|
)}
|
|
1527
1571
|
</select>
|
|
1528
1572
|
</label>
|
|
1573
|
+
|
|
1574
|
+
{/* Navigation Behavior */}
|
|
1575
|
+
{(state.general.type === 'navigation' || state.general.type === 'data') && (
|
|
1576
|
+
<label>
|
|
1577
|
+
<span className='edit-label column-heading'>Navigation Behavior</span>
|
|
1578
|
+
<select
|
|
1579
|
+
value={state.general.navigationTarget}
|
|
1580
|
+
onChange={event => {
|
|
1581
|
+
event.preventDefault()
|
|
1582
|
+
handleEditorChanges('navigationTarget', event.target.value)
|
|
1583
|
+
}}
|
|
1584
|
+
>
|
|
1585
|
+
<option value='_self'>Same Window</option>
|
|
1586
|
+
<option value='_blank'>New Window</option>
|
|
1587
|
+
</select>
|
|
1588
|
+
</label>
|
|
1589
|
+
)}
|
|
1529
1590
|
<label>
|
|
1530
1591
|
<span className='edit-label'>Data Classification Type</span>
|
|
1531
1592
|
<div>
|
|
@@ -1567,7 +1628,17 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1567
1628
|
handleEditorChanges('displayStateLabels', event.target.checked)
|
|
1568
1629
|
}}
|
|
1569
1630
|
/>
|
|
1570
|
-
<span className='edit-label'>
|
|
1631
|
+
<span className='edit-label'>
|
|
1632
|
+
Show state labels
|
|
1633
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
1634
|
+
<Tooltip.Target>
|
|
1635
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
1636
|
+
</Tooltip.Target>
|
|
1637
|
+
<Tooltip.Content>
|
|
1638
|
+
<p>Recommended set to display for Section 508 compliance.</p>
|
|
1639
|
+
</Tooltip.Content>
|
|
1640
|
+
</Tooltip>
|
|
1641
|
+
</span>
|
|
1571
1642
|
</label>
|
|
1572
1643
|
)}
|
|
1573
1644
|
</AccordionItemPanel>
|
|
@@ -1685,16 +1756,6 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1685
1756
|
</Tooltip>
|
|
1686
1757
|
}
|
|
1687
1758
|
/>
|
|
1688
|
-
{'us' === state.general.geoType && (
|
|
1689
|
-
<TextField
|
|
1690
|
-
value={general.territoriesLabel}
|
|
1691
|
-
updateField={updateField}
|
|
1692
|
-
section='general'
|
|
1693
|
-
fieldName='territoriesLabel'
|
|
1694
|
-
label='Territories Label'
|
|
1695
|
-
placeholder='Territories'
|
|
1696
|
-
/>
|
|
1697
|
-
)}
|
|
1698
1759
|
{'us' === state.general.geoType && (
|
|
1699
1760
|
<label className='checkbox'>
|
|
1700
1761
|
<input
|
|
@@ -3316,6 +3377,18 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
3316
3377
|
updateField={updateField}
|
|
3317
3378
|
/>
|
|
3318
3379
|
</label>
|
|
3380
|
+
{/* Leaflet Map Type */}
|
|
3381
|
+
{state.general.geoType === 'leaflet' && (
|
|
3382
|
+
<>
|
|
3383
|
+
<Select
|
|
3384
|
+
label='Leaflet Theme'
|
|
3385
|
+
options={layerOptions}
|
|
3386
|
+
section={'leaflet'}
|
|
3387
|
+
fieldName='theme'
|
|
3388
|
+
updateField={updateField}
|
|
3389
|
+
/>
|
|
3390
|
+
</>
|
|
3391
|
+
)}
|
|
3319
3392
|
</AccordionItemPanel>
|
|
3320
3393
|
</AccordionItem>
|
|
3321
3394
|
<AccordionItem>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React, { useContext, useEffect, useRef } from 'react'
|
|
2
|
+
import { Loader } from '@googlemaps/js-api-loader'
|
|
3
|
+
import { MarkerClusterer } from '@googlemaps/markerclusterer'
|
|
4
|
+
import ConfigContext from '../../../context'
|
|
5
|
+
|
|
6
|
+
// center on USA
|
|
7
|
+
const center = {
|
|
8
|
+
lat: 37.09024,
|
|
9
|
+
lng: -95.712891
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type GoogleMapComponentProps = {
|
|
13
|
+
apiKey?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const GoogleMapComponent: React.FC<GoogleMapComponentProps> = ({ apiKey = '' }) => {
|
|
17
|
+
const mapRef = useRef(null)
|
|
18
|
+
const { state } = useContext(ConfigContext)
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const loader = new Loader({
|
|
22
|
+
apiKey: apiKey,
|
|
23
|
+
version: 'weekly'
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
loader
|
|
27
|
+
.load()
|
|
28
|
+
.then(() => {
|
|
29
|
+
if (mapRef.current) {
|
|
30
|
+
const map = new window.google.maps.Map(mapRef.current, {
|
|
31
|
+
center: center,
|
|
32
|
+
zoom: 4
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const markers = state.data.map(d => {
|
|
36
|
+
const markerContent = document.createElement('div')
|
|
37
|
+
markerContent.style.backgroundColor = 'orange' // Set the background color
|
|
38
|
+
markerContent.style.width = '25px'
|
|
39
|
+
markerContent.style.height = '25px'
|
|
40
|
+
markerContent.style.borderRadius = '50%'
|
|
41
|
+
markerContent.style.display = 'flex'
|
|
42
|
+
markerContent.style.alignItems = 'center'
|
|
43
|
+
markerContent.style.justifyContent = 'center'
|
|
44
|
+
markerContent.style.color = 'white'
|
|
45
|
+
markerContent.innerText = d[state.columns.geo.name]
|
|
46
|
+
|
|
47
|
+
const marker = new google.maps.Marker({
|
|
48
|
+
position: { lat: Number(d[state.columns.latitude.name]), lng: Number(d[state.columns.longitude.name]) },
|
|
49
|
+
title: d[state.columns.geo.name],
|
|
50
|
+
map: map
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
return marker
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
new MarkerClusterer({ markers, map })
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
.catch(e => {
|
|
60
|
+
console.error('Error loading Google Maps API:', e)
|
|
61
|
+
})
|
|
62
|
+
}, [apiKey, state])
|
|
63
|
+
|
|
64
|
+
return <div ref={mapRef} style={{ height: '500px', width: '100%' }} />
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default GoogleMapComponent
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//TODO: Move legends to core
|
|
2
|
-
import { forwardRef, useContext
|
|
2
|
+
import { forwardRef, useContext } from 'react'
|
|
3
3
|
import parse from 'html-react-parser'
|
|
4
4
|
|
|
5
5
|
//types
|
|
@@ -17,8 +17,9 @@ import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
|
|
|
17
17
|
import { GlyphStar, GlyphTriangle, GlyphDiamond, GlyphSquare, GlyphCircle } from '@visx/glyph'
|
|
18
18
|
import { Group } from '@visx/group'
|
|
19
19
|
import './index.scss'
|
|
20
|
-
import {
|
|
20
|
+
import { type ViewPort } from '@cdc/core/types/ViewPort'
|
|
21
21
|
import { isBelowBreakpoint, isMobileHeightViewport } from '@cdc/core/helpers/viewports'
|
|
22
|
+
import { displayDataAsText } from '@cdc/core/helpers/displayDataAsText'
|
|
22
23
|
|
|
23
24
|
const LEGEND_PADDING = 30
|
|
24
25
|
|
|
@@ -26,22 +27,22 @@ type LegendProps = {
|
|
|
26
27
|
skipId: string
|
|
27
28
|
dimensions: DimensionsType
|
|
28
29
|
containerWidthPadding: number
|
|
29
|
-
currentViewport:
|
|
30
|
+
currentViewport: ViewPort
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
33
|
-
const { skipId,
|
|
34
|
+
const { skipId, containerWidthPadding } = props
|
|
35
|
+
const { isEditor, dimensions, currentViewport } = useContext(ConfigContext)
|
|
34
36
|
|
|
35
37
|
const {
|
|
36
38
|
// prettier-ignore
|
|
37
|
-
displayDataAsText,
|
|
38
39
|
resetLegendToggles,
|
|
39
40
|
runtimeFilters,
|
|
40
41
|
runtimeLegend,
|
|
41
42
|
setAccessibleStatus,
|
|
42
43
|
setRuntimeLegend,
|
|
43
44
|
state,
|
|
44
|
-
viewport,
|
|
45
|
+
currentViewport: viewport,
|
|
45
46
|
mapId
|
|
46
47
|
} = useContext(ConfigContext)
|
|
47
48
|
|
|
@@ -75,9 +76,9 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
75
76
|
}
|
|
76
77
|
const getFormattedLegendItems = () => {
|
|
77
78
|
return runtimeLegend.map((entry, idx) => {
|
|
78
|
-
const entryMax = displayDataAsText(entry.max, 'primary')
|
|
79
|
+
const entryMax = displayDataAsText(entry.max, 'primary', state)
|
|
79
80
|
|
|
80
|
-
const entryMin = displayDataAsText(entry.min, 'primary')
|
|
81
|
+
const entryMin = displayDataAsText(entry.min, 'primary', state)
|
|
81
82
|
let formattedText = `${entryMin}${entryMax !== entryMin ? ` - ${entryMax}` : ''}`
|
|
82
83
|
|
|
83
84
|
// If interval, add some formatting
|
|
@@ -86,7 +87,7 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
if (legend.type === 'category') {
|
|
89
|
-
formattedText = displayDataAsText(entry.value, 'primary')
|
|
90
|
+
formattedText = displayDataAsText(entry.value, 'primary', state)
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
if (entry.max === 0 && entry.min === 0) {
|
|
@@ -116,12 +117,14 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
116
117
|
const legendList = (patternsOnly = false) => {
|
|
117
118
|
const formattedItems = patternsOnly ? [] : getFormattedLegendItems()
|
|
118
119
|
const patternsOnlyFont = isMobileHeightViewport(currentViewport) ? '12px' : '14px'
|
|
120
|
+
const hasDisabledItems = formattedItems.some(item => item.disabled)
|
|
119
121
|
let legendItems
|
|
120
122
|
|
|
121
123
|
legendItems = formattedItems.map((item, idx) => {
|
|
122
124
|
const handleListItemClass = () => {
|
|
123
125
|
let classes = ['legend-container__li', 'd-flex', 'align-items-center']
|
|
124
126
|
if (item.disabled) classes.push('legend-container__li--disabled')
|
|
127
|
+
else if (hasDisabledItems) classes.push('legend-container__li--not-disabled')
|
|
125
128
|
if (item.special) classes.push('legend-container__li--special-class')
|
|
126
129
|
return classes.join(' ')
|
|
127
130
|
}
|
|
@@ -264,26 +267,33 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
264
267
|
ref={ref}
|
|
265
268
|
>
|
|
266
269
|
<section className={legendClasses.section.join(' ') || ''} aria-label='Map Legend'>
|
|
267
|
-
{legend.title
|
|
268
|
-
|
|
269
|
-
|
|
270
|
+
{(legend.title || legend.description || legend.dynamicDescription) && (
|
|
271
|
+
<div className='mb-3'>
|
|
272
|
+
{legend.title && <h3 className={legendClasses.title.join(' ') || ''}>{parse(legend.title)}</h3>}
|
|
273
|
+
{legend.dynamicDescription === false && legend.description && (
|
|
274
|
+
<p className={legendClasses.description.join(' ') || ''}>{parse(legend.description)}</p>
|
|
275
|
+
)}
|
|
276
|
+
{legend.dynamicDescription === true &&
|
|
277
|
+
runtimeFilters.map((filter, idx) => {
|
|
278
|
+
const lookupStr = `${idx},${filter.values.indexOf(String(filter.active))}`
|
|
279
|
+
|
|
280
|
+
// Do we have a custom description for this?
|
|
281
|
+
const desc = legend.descriptions[lookupStr] || ''
|
|
282
|
+
|
|
283
|
+
if (desc.length > 0) {
|
|
284
|
+
return (
|
|
285
|
+
<p
|
|
286
|
+
key={`dynamic-description-${lookupStr}`}
|
|
287
|
+
className={`dynamic-legend-description-${lookupStr} mt-2`}
|
|
288
|
+
>
|
|
289
|
+
{desc}
|
|
290
|
+
</p>
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
return true
|
|
294
|
+
})}
|
|
295
|
+
</div>
|
|
270
296
|
)}
|
|
271
|
-
{legend.dynamicDescription === true &&
|
|
272
|
-
runtimeFilters.map((filter, idx) => {
|
|
273
|
-
const lookupStr = `${idx},${filter.values.indexOf(String(filter.active))}`
|
|
274
|
-
|
|
275
|
-
// Do we have a custom description for this?
|
|
276
|
-
const desc = legend.descriptions[lookupStr] || ''
|
|
277
|
-
|
|
278
|
-
if (desc.length > 0) {
|
|
279
|
-
return (
|
|
280
|
-
<p key={`dynamic-description-${lookupStr}`} className={`dynamic-legend-description-${lookupStr}`}>
|
|
281
|
-
{desc}
|
|
282
|
-
</p>
|
|
283
|
-
)
|
|
284
|
-
}
|
|
285
|
-
return true
|
|
286
|
-
})}
|
|
287
297
|
|
|
288
298
|
<LegendGradient
|
|
289
299
|
labels={getFormattedLegendItems().map(item => item?.label) ?? []}
|
|
@@ -334,8 +344,8 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
334
344
|
</>
|
|
335
345
|
)}
|
|
336
346
|
{runtimeLegend.disabledAmt > 0 && (
|
|
337
|
-
<Button className={legendClasses.
|
|
338
|
-
|
|
347
|
+
<Button className={legendClasses.showAllButton.join(' ')} onClick={handleReset}>
|
|
348
|
+
Show All
|
|
339
349
|
</Button>
|
|
340
350
|
)}
|
|
341
351
|
</section>
|