@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
package/src/CdcMap.tsx
CHANGED
|
@@ -1,47 +1,36 @@
|
|
|
1
|
+
// Vendor
|
|
1
2
|
import React, { useState, useEffect, useRef, useCallback, useId } from 'react'
|
|
2
3
|
import * as d3 from 'd3'
|
|
3
|
-
import Layout from '@cdc/core/components/Layout'
|
|
4
|
-
import Waiting from '@cdc/core/components/Waiting'
|
|
5
|
-
import Annotation from './components/Annotation'
|
|
6
|
-
import Error from './components/EditorPanel/components/Error'
|
|
7
4
|
import _ from 'lodash'
|
|
8
|
-
import { applyColorToLegend } from './helpers/applyColorToLegend'
|
|
9
|
-
|
|
10
|
-
// types
|
|
11
|
-
import { type ViewportSize } from './types/MapConfig'
|
|
12
|
-
import { type DimensionsType } from '@cdc/core/types/Dimensions'
|
|
13
|
-
|
|
14
|
-
// IE11
|
|
15
5
|
import 'whatwg-fetch'
|
|
16
|
-
import ResizeObserver from 'resize-observer-polyfill'
|
|
17
|
-
|
|
18
|
-
// Third party
|
|
19
6
|
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
20
7
|
import Papa from 'papaparse'
|
|
21
8
|
import parse from 'html-react-parser'
|
|
22
9
|
import 'react-tooltip/dist/react-tooltip.css'
|
|
23
10
|
|
|
24
|
-
//
|
|
25
|
-
import
|
|
26
|
-
import {
|
|
27
|
-
import
|
|
28
|
-
import
|
|
29
|
-
import
|
|
30
|
-
import coveUpdateWorker from '@cdc/core/helpers/coveUpdateWorker'
|
|
31
|
-
import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
|
|
11
|
+
// Core Components
|
|
12
|
+
import DataTable from '@cdc/core/components/DataTable'
|
|
13
|
+
import Filters, { useFilters } from '@cdc/core/components/Filters'
|
|
14
|
+
import Layout from '@cdc/core/components/Layout'
|
|
15
|
+
import MediaControls from '@cdc/core/components/MediaControls'
|
|
16
|
+
import SkipTo from '@cdc/core/components/elements/SkipTo'
|
|
32
17
|
import Title from '@cdc/core/components/ui/Title'
|
|
18
|
+
import Waiting from '@cdc/core/components/Waiting'
|
|
19
|
+
|
|
20
|
+
// types
|
|
21
|
+
import { type Coordinate, type MapConfig } from './types/MapConfig'
|
|
33
22
|
|
|
34
23
|
// Data
|
|
35
24
|
import { countryCoordinates } from './data/country-coordinates'
|
|
36
25
|
import {
|
|
37
|
-
|
|
38
|
-
supportedTerritories,
|
|
39
|
-
supportedCountries,
|
|
40
|
-
supportedCounties,
|
|
26
|
+
stateFipsToTwoDigit,
|
|
41
27
|
supportedCities,
|
|
28
|
+
supportedCounties,
|
|
29
|
+
supportedCountries,
|
|
30
|
+
supportedRegions,
|
|
31
|
+
supportedStates,
|
|
42
32
|
supportedStatesFipsCodes,
|
|
43
|
-
|
|
44
|
-
supportedRegions
|
|
33
|
+
supportedTerritories
|
|
45
34
|
} from './data/supported-geos'
|
|
46
35
|
import colorPalettes from '@cdc/core/data/colorPalettes'
|
|
47
36
|
import initialState from './data/initial-state'
|
|
@@ -51,34 +40,48 @@ import ExternalIcon from './images/external-link.svg'
|
|
|
51
40
|
|
|
52
41
|
// Sass
|
|
53
42
|
import './scss/main.scss'
|
|
54
|
-
|
|
55
|
-
// TODO: combine in scss.
|
|
56
43
|
import './scss/btn.scss'
|
|
57
44
|
|
|
58
|
-
// Core
|
|
59
|
-
import
|
|
60
|
-
import MediaControls from '@cdc/core/components/MediaControls'
|
|
45
|
+
// Core Helpers
|
|
46
|
+
import coveUpdateWorker from '@cdc/core/helpers/coveUpdateWorker'
|
|
61
47
|
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
62
|
-
import getViewport from '@cdc/core/helpers/getViewport'
|
|
63
48
|
import isDomainExternal from '@cdc/core/helpers/isDomainExternal'
|
|
64
49
|
import numberFromString from '@cdc/core/helpers/numberFromString'
|
|
65
|
-
import
|
|
50
|
+
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
51
|
+
import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
|
|
52
|
+
import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
|
|
53
|
+
import { publish } from '@cdc/core/helpers/events'
|
|
54
|
+
|
|
55
|
+
// Map Helpers
|
|
56
|
+
import { applyColorToLegend } from './helpers/applyColorToLegend'
|
|
57
|
+
import { closeModal } from './helpers/closeModal'
|
|
58
|
+
import { generateColorsArray } from './helpers/generateColorsArray'
|
|
59
|
+
import { generateRuntimeLegendHash } from './helpers/generateRuntimeLegendHash'
|
|
60
|
+
import { getGeoFillColor } from './helpers/colors'
|
|
61
|
+
import { getUniqueValues } from './helpers/getUniqueValues'
|
|
62
|
+
import { hashObj } from './helpers/hashObj'
|
|
63
|
+
import { navigationHandler } from './helpers/navigationHandler'
|
|
64
|
+
import { validateFipsCodeLength } from './helpers/validateFipsCodeLength'
|
|
65
|
+
import { titleCase } from './helpers/titleCase'
|
|
66
|
+
import { indexOfIgnoreType } from './helpers/indexOfIgnoreType'
|
|
66
67
|
|
|
67
68
|
// Child Components
|
|
69
|
+
import Annotation from './components/Annotation'
|
|
68
70
|
import ConfigContext from './context'
|
|
69
|
-
import
|
|
70
|
-
import
|
|
71
|
+
import EditorPanel from './components/EditorPanel'
|
|
72
|
+
import Error from './components/EditorPanel/components/Error'
|
|
71
73
|
import Legend from './components/Legend'
|
|
74
|
+
import Modal from './components/Modal'
|
|
75
|
+
import NavigationMenu from './components/NavigationMenu'
|
|
76
|
+
import UsaMap from './components/UsaMap'
|
|
77
|
+
import WorldMap from './components/WorldMap'
|
|
78
|
+
import GoogleMap from './components/GoogleMap'
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
import NavigationMenu from './components/NavigationMenu' // Future: Lazy
|
|
75
|
-
import UsaMap from './components/UsaMap' // Future: Lazy
|
|
76
|
-
import WorldMap from './components/WorldMap' // Future: Lazy
|
|
80
|
+
// hooks
|
|
77
81
|
import useTooltip from './hooks/useTooltip'
|
|
78
|
-
import
|
|
79
|
-
import SkipTo from '@cdc/core/components/elements/SkipTo'
|
|
80
|
-
import { getGeoFillColor } from './helpers/colors'
|
|
82
|
+
import useResizeObserver from './hooks/useResizeObserver'
|
|
81
83
|
import { SubGrouping } from '@cdc/core/types/VizFilter'
|
|
84
|
+
import { ViewPort } from '@cdc/core/types/ViewPort'
|
|
82
85
|
|
|
83
86
|
// Data props
|
|
84
87
|
const stateKeys = Object.keys(supportedStates)
|
|
@@ -88,17 +91,7 @@ const countryKeys = Object.keys(supportedCountries)
|
|
|
88
91
|
const countyKeys = Object.keys(supportedCounties)
|
|
89
92
|
const cityKeys = Object.keys(supportedCities)
|
|
90
93
|
|
|
91
|
-
const indexOfIgnoreType = (arr, item) => {
|
|
92
|
-
for (let i = 0; i < arr.length; i++) {
|
|
93
|
-
if (item === arr[i]) {
|
|
94
|
-
return i
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return -1
|
|
98
|
-
}
|
|
99
|
-
|
|
100
94
|
const CdcMap = ({
|
|
101
|
-
className,
|
|
102
95
|
config,
|
|
103
96
|
navigationHandler: customNavigationHandler,
|
|
104
97
|
isDashboard = false,
|
|
@@ -114,11 +107,10 @@ const CdcMap = ({
|
|
|
114
107
|
const transform = new DataTransform()
|
|
115
108
|
const [translate, setTranslate] = useState([0, 0])
|
|
116
109
|
const [scale, setScale] = useState(1)
|
|
117
|
-
const [state, setState] = useState({ ...initialState })
|
|
110
|
+
const [state, setState] = useState<MapConfig>({ ...initialState })
|
|
118
111
|
const [isDraggingAnnotation, setIsDraggingAnnotation] = useState(false)
|
|
119
112
|
const [loading, setLoading] = useState(true)
|
|
120
113
|
const [displayPanel, setDisplayPanel] = useState(true)
|
|
121
|
-
const [currentViewport, setCurrentViewport] = useState<ViewportSize>('lg')
|
|
122
114
|
const [topoData, setTopoData] = useState<{}>({})
|
|
123
115
|
const [runtimeFilters, setRuntimeFilters] = useState([])
|
|
124
116
|
const [runtimeData, setRuntimeData] = useState({ init: true })
|
|
@@ -136,11 +128,10 @@ const CdcMap = ({
|
|
|
136
128
|
const [filteredCountryCode, setFilteredCountryCode] = useState()
|
|
137
129
|
const [position, setPosition] = useState(state.mapPosition)
|
|
138
130
|
const [coveLoadedHasRan, setCoveLoadedHasRan] = useState(false)
|
|
139
|
-
const [container, setContainer] = useState()
|
|
140
131
|
const [imageId, setImageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`) // eslint-disable-line
|
|
141
|
-
const [dimensions, setDimensions] = useState<DimensionsType>([0, 0])
|
|
142
132
|
const [requiredColumns, setRequiredColumns] = useState(null) // Simple state so we know if we need more information before parsing the map
|
|
143
133
|
const [projection, setProjection] = useState(null)
|
|
134
|
+
const { currentViewport, dimensions, container, outerContainerRef } = useResizeObserver(isEditor)
|
|
144
135
|
|
|
145
136
|
const legendRef = useRef(null)
|
|
146
137
|
const tooltipRef = useRef(null)
|
|
@@ -149,7 +140,7 @@ const CdcMap = ({
|
|
|
149
140
|
const tooltipId = `${Math.random().toString(16).slice(-4)}`
|
|
150
141
|
const mapId = useId()
|
|
151
142
|
|
|
152
|
-
const {
|
|
143
|
+
const { handleSorting } = useFilters({ config: state, setConfig: setState })
|
|
153
144
|
let legendMemo = useRef(new Map())
|
|
154
145
|
let legendSpecialClassLastMemo = useRef(new Map())
|
|
155
146
|
let innerContainerRef = useRef()
|
|
@@ -174,10 +165,7 @@ const CdcMap = ({
|
|
|
174
165
|
}
|
|
175
166
|
|
|
176
167
|
// Navigate is required for navigation maps
|
|
177
|
-
if (
|
|
178
|
-
'navigation' === state.general.type &&
|
|
179
|
-
('' === state.columns.navigate.name || undefined === state.columns.navigate)
|
|
180
|
-
) {
|
|
168
|
+
if ('navigation' === state.general.type && '' === state.columns.navigate.name) {
|
|
181
169
|
columnList.push('Navigation')
|
|
182
170
|
}
|
|
183
171
|
|
|
@@ -206,7 +194,7 @@ const CdcMap = ({
|
|
|
206
194
|
const coordinates = countryCoordinates[filteredCountryCode]
|
|
207
195
|
const long = coordinates[1]
|
|
208
196
|
const lat = coordinates[0]
|
|
209
|
-
const reversedCoordinates = [long, lat]
|
|
197
|
+
const reversedCoordinates: Coordinate = [long, lat]
|
|
210
198
|
|
|
211
199
|
setState({
|
|
212
200
|
...state,
|
|
@@ -236,27 +224,11 @@ const CdcMap = ({
|
|
|
236
224
|
}
|
|
237
225
|
}, [state.mapPosition, setPosition])
|
|
238
226
|
|
|
239
|
-
const resizeObserver = new ResizeObserver(entries => {
|
|
240
|
-
for (let entry of entries) {
|
|
241
|
-
let { width, height } = entry.contentRect
|
|
242
|
-
let newViewport = getViewport(entry.contentRect.width)
|
|
243
|
-
|
|
244
|
-
let editorWidth = 350
|
|
245
|
-
|
|
246
|
-
setCurrentViewport(newViewport)
|
|
247
|
-
|
|
248
|
-
if (isEditor) {
|
|
249
|
-
width = width - editorWidth
|
|
250
|
-
}
|
|
251
|
-
setDimensions([width, height])
|
|
252
|
-
}
|
|
253
|
-
})
|
|
254
|
-
|
|
255
227
|
// Tag each row with a UID. Helps with filtering/placing geos. Not enumerable so doesn't show up in loops/console logs except when directly addressed ex row.uid
|
|
256
228
|
// We are mutating state in place here (depending on where called) - but it's okay, this isn't used for rerender
|
|
257
229
|
// eslint-disable-next-line
|
|
258
230
|
const addUIDs = useCallback((obj, fromColumn) => {
|
|
259
|
-
obj.data.forEach(
|
|
231
|
+
obj.data.forEach(row => {
|
|
260
232
|
let uid = null
|
|
261
233
|
|
|
262
234
|
if (row.uid) row.uid = null // Wipe existing UIDs
|
|
@@ -383,22 +355,8 @@ const CdcMap = ({
|
|
|
383
355
|
|
|
384
356
|
result.runtimeDataHash = runtimeFilters?.fromHash
|
|
385
357
|
|
|
386
|
-
// Unified will
|
|
358
|
+
// Unified will base the legend off ALL of the data maps received. Otherwise, it will use
|
|
387
359
|
let dataSet = obj.legend.unified ? obj.data : Object.values(runtimeData)
|
|
388
|
-
|
|
389
|
-
const colorDistributions = {
|
|
390
|
-
1: [1],
|
|
391
|
-
2: [1, 3],
|
|
392
|
-
3: [1, 3, 5],
|
|
393
|
-
4: [0, 2, 4, 6],
|
|
394
|
-
5: [0, 2, 4, 6, 7],
|
|
395
|
-
6: [0, 2, 3, 4, 5, 7],
|
|
396
|
-
7: [0, 2, 3, 4, 5, 6, 7],
|
|
397
|
-
8: [0, 2, 3, 4, 5, 6, 7, 8],
|
|
398
|
-
9: [0, 1, 2, 3, 4, 5, 6, 7, 8],
|
|
399
|
-
10: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
400
|
-
}
|
|
401
|
-
|
|
402
360
|
let specialClasses = 0
|
|
403
361
|
let specialClassesHash = {}
|
|
404
362
|
|
|
@@ -424,7 +382,7 @@ const CdcMap = ({
|
|
|
424
382
|
specialClasses += 1
|
|
425
383
|
}
|
|
426
384
|
|
|
427
|
-
let specialColor
|
|
385
|
+
let specialColor: number
|
|
428
386
|
|
|
429
387
|
// color the state if val is in row
|
|
430
388
|
specialColor = result.findIndex(p => p.value === val)
|
|
@@ -656,7 +614,6 @@ const CdcMap = ({
|
|
|
656
614
|
}
|
|
657
615
|
} else {
|
|
658
616
|
// get nums
|
|
659
|
-
let hasZeroInData = dataSet.filter(obj => obj[state.columns.primary.name] === 0).length > 0
|
|
660
617
|
let domainNums = new Set(dataSet.map(item => item[state.columns.primary.name]))
|
|
661
618
|
|
|
662
619
|
domainNums = d3.extent(domainNums)
|
|
@@ -691,7 +648,7 @@ const CdcMap = ({
|
|
|
691
648
|
|
|
692
649
|
const breaks = getBreaks(scale)
|
|
693
650
|
|
|
694
|
-
// if
|
|
651
|
+
// if separating zero force it into breaks
|
|
695
652
|
if (breaks[0] !== 0) {
|
|
696
653
|
breaks.unshift(0)
|
|
697
654
|
}
|
|
@@ -706,7 +663,7 @@ const CdcMap = ({
|
|
|
706
663
|
min = 0
|
|
707
664
|
}
|
|
708
665
|
|
|
709
|
-
// if we're on the second break, and
|
|
666
|
+
// if we're on the second break, and separating out zero, increment min to 1.
|
|
710
667
|
if (index === 1 && state.legend.separateZero) {
|
|
711
668
|
min = 1
|
|
712
669
|
}
|
|
@@ -722,20 +679,12 @@ const CdcMap = ({
|
|
|
722
679
|
return Math.pow(10, -n)
|
|
723
680
|
}
|
|
724
681
|
|
|
725
|
-
const setMax =
|
|
682
|
+
const setMax = index => {
|
|
726
683
|
let max = Number(breaks[index + 1]) - getDecimalPlace(Number(state?.columns?.primary?.roundToPlace))
|
|
727
684
|
|
|
728
|
-
// check if min and max range are the same
|
|
729
|
-
// if (min === max + 1) {
|
|
730
|
-
// max = breaks[index + 1]
|
|
731
|
-
// }
|
|
732
|
-
|
|
733
685
|
if (index === 0 && state.legend.separateZero) {
|
|
734
686
|
max = 0
|
|
735
687
|
}
|
|
736
|
-
// if ((index === state.legend.specialClasses.length && state.legend.specialClasses.length !== 0) && !state.legend.separateZero && hasZeroInData) {
|
|
737
|
-
// max = 0;
|
|
738
|
-
// }
|
|
739
688
|
|
|
740
689
|
if (index + 1 === breaks.length) {
|
|
741
690
|
max = domainNums[1]
|
|
@@ -753,7 +702,7 @@ const CdcMap = ({
|
|
|
753
702
|
color: scale(item)
|
|
754
703
|
})
|
|
755
704
|
|
|
756
|
-
dataSet.forEach(
|
|
705
|
+
dataSet.forEach(row => {
|
|
757
706
|
let number = row[state.columns.primary.name]
|
|
758
707
|
let updated = result.length - 1
|
|
759
708
|
|
|
@@ -958,7 +907,7 @@ const CdcMap = ({
|
|
|
958
907
|
// Strip hidden characters before we check length
|
|
959
908
|
navigateUrl = navigateUrl.replace(/(\r\n|\n|\r)/gm, '')
|
|
960
909
|
}
|
|
961
|
-
if (0 === navigateUrl
|
|
910
|
+
if (0 === navigateUrl?.length) {
|
|
962
911
|
return false
|
|
963
912
|
}
|
|
964
913
|
}
|
|
@@ -989,86 +938,8 @@ const CdcMap = ({
|
|
|
989
938
|
}
|
|
990
939
|
})
|
|
991
940
|
|
|
992
|
-
const outerContainerRef = useCallback(node => {
|
|
993
|
-
if (node !== null) {
|
|
994
|
-
resizeObserver.observe(node)
|
|
995
|
-
}
|
|
996
|
-
setContainer(node)
|
|
997
|
-
}, []) // eslint-disable-line
|
|
998
|
-
|
|
999
941
|
const mapSvg = useRef(null)
|
|
1000
942
|
|
|
1001
|
-
const closeModal = ({ target }) => {
|
|
1002
|
-
if (
|
|
1003
|
-
'string' === typeof target.className &&
|
|
1004
|
-
(target.className.includes('modal-close') || target.className.includes('modal-background')) &&
|
|
1005
|
-
null !== modal
|
|
1006
|
-
) {
|
|
1007
|
-
setModal(null)
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
const displayDataAsText = (value, columnName) => {
|
|
1012
|
-
if (value === null || value === '' || value === undefined) {
|
|
1013
|
-
return ''
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
// if string of letters like 'Home' then dont need to format as a number
|
|
1017
|
-
if (
|
|
1018
|
-
typeof value === 'string' &&
|
|
1019
|
-
value.length > 0 &&
|
|
1020
|
-
/[a-zA-Z]/.test(value) &&
|
|
1021
|
-
state.legend.type === 'equalnumber'
|
|
1022
|
-
) {
|
|
1023
|
-
return value
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
let formattedValue = value
|
|
1027
|
-
|
|
1028
|
-
let columnObj = state.columns[columnName]
|
|
1029
|
-
|
|
1030
|
-
if (columnObj === undefined) {
|
|
1031
|
-
// then use left axis config
|
|
1032
|
-
columnObj = state.columns.primary
|
|
1033
|
-
// NOTE: Left Value Axis uses different names
|
|
1034
|
-
// so map them below so the code below works
|
|
1035
|
-
// - copy commas to useCommas to work below
|
|
1036
|
-
columnObj['useCommas'] = columnObj.commas
|
|
1037
|
-
// - copy roundTo to roundToPlace to work below
|
|
1038
|
-
columnObj['roundToPlace'] = columnObj.roundTo ? columnObj.roundTo : ''
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
if (columnObj) {
|
|
1042
|
-
// If value is a number, apply specific formattings
|
|
1043
|
-
if (Number(value)) {
|
|
1044
|
-
const hasDecimal = columnObj.roundToPlace && (columnObj.roundToPlace !== '' || columnObj.roundToPlace !== null)
|
|
1045
|
-
const decimalPoint = columnObj.roundToPlace ? Number(columnObj.roundToPlace) : 0
|
|
1046
|
-
|
|
1047
|
-
// Rounding
|
|
1048
|
-
if (columnObj.hasOwnProperty('roundToPlace') && hasDecimal) {
|
|
1049
|
-
formattedValue = Number(value).toFixed(decimalPoint)
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
if (columnObj.hasOwnProperty('useCommas') && columnObj.useCommas === true) {
|
|
1053
|
-
// Formats number to string with commas - allows up to 5 decimal places, if rounding is not defined.
|
|
1054
|
-
// Otherwise, uses the rounding value set at 'columnObj.roundToPlace'.
|
|
1055
|
-
formattedValue = Number(value).toLocaleString('en-US', {
|
|
1056
|
-
style: 'decimal',
|
|
1057
|
-
minimumFractionDigits: hasDecimal ? decimalPoint : 0,
|
|
1058
|
-
maximumFractionDigits: hasDecimal ? decimalPoint : 5
|
|
1059
|
-
})
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
// Check if it's a special value. If it is not, apply the designated prefix and suffix
|
|
1064
|
-
if (false === state.legend.specialClasses.includes(String(value))) {
|
|
1065
|
-
formattedValue = (columnObj.prefix || '') + formattedValue + (columnObj.suffix || '')
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
return formattedValue
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
943
|
// this is passed DOWN into the various components
|
|
1073
944
|
// then they do a lookup based on the bin number as index into here
|
|
1074
945
|
const applyLegendToRow = rowObj => {
|
|
@@ -1105,50 +976,6 @@ const CdcMap = ({
|
|
|
1105
976
|
}
|
|
1106
977
|
}
|
|
1107
978
|
|
|
1108
|
-
// if city has a hyphen then in tooltip it ends up UPPER CASE instead of just regular Upper Case
|
|
1109
|
-
// - this function is used to prevent that and instead give the formatting that is wanted
|
|
1110
|
-
// Example: Desired city display in tooltip on map: "Inter-Tribal Indian Reservation"
|
|
1111
|
-
const titleCase = string => {
|
|
1112
|
-
// guard clause else error in editor
|
|
1113
|
-
if (!string) return
|
|
1114
|
-
if (string !== undefined) {
|
|
1115
|
-
const toTitleCase = word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase()
|
|
1116
|
-
|
|
1117
|
-
if (string.toUpperCase().includes('U.S.') || string.toUpperCase().includes('US')) {
|
|
1118
|
-
return string
|
|
1119
|
-
.split(' ')
|
|
1120
|
-
.map(word => {
|
|
1121
|
-
if (word.toUpperCase() === 'U.S.' || word.toUpperCase() === 'US') {
|
|
1122
|
-
return word.toUpperCase()
|
|
1123
|
-
} else {
|
|
1124
|
-
return toTitleCase(word)
|
|
1125
|
-
}
|
|
1126
|
-
})
|
|
1127
|
-
.join(' ')
|
|
1128
|
-
}
|
|
1129
|
-
// if hyphen found, then split, uppercase each word, and put back together
|
|
1130
|
-
if (string.includes('–') || string.includes('-')) {
|
|
1131
|
-
let dashSplit = string.includes('–') ? string.split('–') : string.split('-') // determine hyphen or en dash to split on
|
|
1132
|
-
let splitCharacter = string.includes('–') ? '–' : '-' // print hyphen or en dash later on.
|
|
1133
|
-
let frontSplit = dashSplit[0]
|
|
1134
|
-
.split(' ')
|
|
1135
|
-
.map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
|
|
1136
|
-
.join(' ')
|
|
1137
|
-
let backSplit = dashSplit[1]
|
|
1138
|
-
.split(' ')
|
|
1139
|
-
.map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
|
|
1140
|
-
.join(' ')
|
|
1141
|
-
return frontSplit + splitCharacter + backSplit
|
|
1142
|
-
} else {
|
|
1143
|
-
// just return with each word uppercase
|
|
1144
|
-
return string
|
|
1145
|
-
.split(' ')
|
|
1146
|
-
.map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
|
|
1147
|
-
.join(' ')
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
979
|
// This resets all active legend toggles.
|
|
1153
980
|
const resetLegendToggles = async () => {
|
|
1154
981
|
let newLegend = [...runtimeLegend]
|
|
@@ -1221,7 +1048,7 @@ const CdcMap = ({
|
|
|
1221
1048
|
value = dict[value]
|
|
1222
1049
|
}
|
|
1223
1050
|
|
|
1224
|
-
// if you get here and it's 2 letters then
|
|
1051
|
+
// if you get here and it's 2 letters then dont titleCase state abbreviations like "AL"
|
|
1225
1052
|
if (value.length === 2) {
|
|
1226
1053
|
return value
|
|
1227
1054
|
} else {
|
|
@@ -1230,7 +1057,7 @@ const CdcMap = ({
|
|
|
1230
1057
|
}
|
|
1231
1058
|
|
|
1232
1059
|
// todo: convert to store or context eventually.
|
|
1233
|
-
const { buildTooltip } = useTooltip({ state, displayGeoName,
|
|
1060
|
+
const { buildTooltip } = useTooltip({ state, displayGeoName, supportedStatesFipsCodes })
|
|
1234
1061
|
|
|
1235
1062
|
const applyTooltipsToGeo = (geoName, row, returnType = 'string') => {
|
|
1236
1063
|
let toolTipText = buildTooltip(row, geoName, '')
|
|
@@ -1248,11 +1075,15 @@ const CdcMap = ({
|
|
|
1248
1075
|
key='modal-navigation-link'
|
|
1249
1076
|
onClick={e => {
|
|
1250
1077
|
e.preventDefault()
|
|
1251
|
-
navigationHandler(
|
|
1078
|
+
navigationHandler(
|
|
1079
|
+
state.general.navigationTarget,
|
|
1080
|
+
row[state.columns.navigate.name],
|
|
1081
|
+
customNavigationHandler
|
|
1082
|
+
)
|
|
1252
1083
|
}}
|
|
1253
1084
|
>
|
|
1254
1085
|
{state.tooltips.linkLabel}
|
|
1255
|
-
{isDomainExternal(row[state.columns.navigate.name]) && <ExternalIcon className='inline-icon
|
|
1086
|
+
{isDomainExternal(row[state.columns.navigate.name]) && <ExternalIcon className='inline-icon ms-1' />}
|
|
1256
1087
|
</a>
|
|
1257
1088
|
)
|
|
1258
1089
|
}
|
|
@@ -1261,24 +1092,6 @@ const CdcMap = ({
|
|
|
1261
1092
|
return toolTipText
|
|
1262
1093
|
}
|
|
1263
1094
|
|
|
1264
|
-
const navigationHandler = urlString => {
|
|
1265
|
-
// Call custom navigation method if passed
|
|
1266
|
-
if (customNavigationHandler) {
|
|
1267
|
-
customNavigationHandler(urlString)
|
|
1268
|
-
return
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
// Abort if value is blank
|
|
1272
|
-
if (0 === urlString.length) {
|
|
1273
|
-
throw Error('Blank string passed as URL. Navigation aborted.')
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
const urlObj = new URL(urlString, window.location.origin)
|
|
1277
|
-
|
|
1278
|
-
// Open constructed link in new tab/window
|
|
1279
|
-
window.open(urlObj.toString(), '_blank')
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
1095
|
const geoClickHandler = (key, value) => {
|
|
1283
1096
|
if (setSharedFilter) {
|
|
1284
1097
|
setSharedFilter(state.uid, value)
|
|
@@ -1295,7 +1108,7 @@ const CdcMap = ({
|
|
|
1295
1108
|
})
|
|
1296
1109
|
}
|
|
1297
1110
|
|
|
1298
|
-
// If modals are set or we are on a mobile viewport, display modal
|
|
1111
|
+
// If modals are set, or we are on a mobile viewport, display modal
|
|
1299
1112
|
if (window.matchMedia('(any-hover: none)').matches || 'click' === state.tooltips.appearanceType) {
|
|
1300
1113
|
setModal({
|
|
1301
1114
|
geoName: key,
|
|
@@ -1307,64 +1120,7 @@ const CdcMap = ({
|
|
|
1307
1120
|
|
|
1308
1121
|
// Otherwise if this item has a link specified for it, do regular navigation.
|
|
1309
1122
|
if (state.columns.navigate && value[state.columns.navigate.name]) {
|
|
1310
|
-
navigationHandler(value[state.columns.navigate.name])
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
const validateFipsCodeLength = newState => {
|
|
1315
|
-
if (
|
|
1316
|
-
newState.general.geoType === 'us-county' ||
|
|
1317
|
-
newState.general.geoType === 'single-state' ||
|
|
1318
|
-
(newState.general.geoType === 'us' && newState?.data)
|
|
1319
|
-
) {
|
|
1320
|
-
newState?.data.forEach(dataPiece => {
|
|
1321
|
-
if (dataPiece[newState.columns.geo.name]) {
|
|
1322
|
-
if (
|
|
1323
|
-
!isNaN(parseInt(dataPiece[newState.columns.geo.name])) &&
|
|
1324
|
-
dataPiece[newState.columns.geo.name].length === 4
|
|
1325
|
-
) {
|
|
1326
|
-
dataPiece[newState.columns.geo.name] = 0 + dataPiece[newState.columns.geo.name]
|
|
1327
|
-
}
|
|
1328
|
-
dataPiece[newState.columns.geo.name] = dataPiece[newState.columns.geo.name].toString()
|
|
1329
|
-
}
|
|
1330
|
-
})
|
|
1331
|
-
}
|
|
1332
|
-
return newState
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
const handleMapAriaLabels = (state = '', testing = false) => {
|
|
1336
|
-
if (testing) console.log(`handleMapAriaLabels Testing On: ${state}`) // eslint-disable-line
|
|
1337
|
-
try {
|
|
1338
|
-
if (!state.general.geoType) throw Error('handleMapAriaLabels: no geoType found in state')
|
|
1339
|
-
let ariaLabel = ''
|
|
1340
|
-
switch (state.general.geoType) {
|
|
1341
|
-
case 'world':
|
|
1342
|
-
ariaLabel += 'World map'
|
|
1343
|
-
break
|
|
1344
|
-
case 'us':
|
|
1345
|
-
ariaLabel += 'United States map'
|
|
1346
|
-
break
|
|
1347
|
-
case 'us-county':
|
|
1348
|
-
ariaLabel += `United States county map`
|
|
1349
|
-
break
|
|
1350
|
-
case 'single-state':
|
|
1351
|
-
ariaLabel += `${state.general.statePicked.stateName} county map`
|
|
1352
|
-
break
|
|
1353
|
-
case 'us-region':
|
|
1354
|
-
ariaLabel += `United States HHS Region map`
|
|
1355
|
-
break
|
|
1356
|
-
default:
|
|
1357
|
-
ariaLabel = 'Data visualization container'
|
|
1358
|
-
break
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
if (state.general.title) {
|
|
1362
|
-
ariaLabel += ` with the title: ${state.general.title}`
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
return ariaLabel
|
|
1366
|
-
} catch (e) {
|
|
1367
|
-
console.error('COVE: ', e.message) // eslint-disable-line
|
|
1123
|
+
navigationHandler(state.general.navigationTarget, value[state.columns.navigate.name], customNavigationHandler)
|
|
1368
1124
|
}
|
|
1369
1125
|
}
|
|
1370
1126
|
|
|
@@ -1468,7 +1224,7 @@ const CdcMap = ({
|
|
|
1468
1224
|
|
|
1469
1225
|
// This code goes through and adds the defaults for every property declaring in the initial state at the top.
|
|
1470
1226
|
// This allows you to easily add new properties to the config without having to worry about accounting for backwards compatibility.
|
|
1471
|
-
// Right now this does not work recursively -- only on first and second level properties. So state -> prop1 ->
|
|
1227
|
+
// Right now this does not work recursively -- only on first and second level properties. So state -> prop1 -> childPropOne
|
|
1472
1228
|
Object.keys(newState).forEach(key => {
|
|
1473
1229
|
if ('object' === typeof newState[key] && false === Array.isArray(newState[key])) {
|
|
1474
1230
|
if (initialState[key]) {
|
|
@@ -1539,25 +1295,6 @@ const CdcMap = ({
|
|
|
1539
1295
|
}
|
|
1540
1296
|
}, [state]) // eslint-disable-line
|
|
1541
1297
|
|
|
1542
|
-
// DEV-769 make "Data Table" both a required field and default value
|
|
1543
|
-
useEffect(() => {
|
|
1544
|
-
if (state.table?.label === '' || state.table?.label === undefined) {
|
|
1545
|
-
setState({
|
|
1546
|
-
...state,
|
|
1547
|
-
table: {
|
|
1548
|
-
...state.table,
|
|
1549
|
-
title: 'Data Table'
|
|
1550
|
-
}
|
|
1551
|
-
})
|
|
1552
|
-
}
|
|
1553
|
-
}, [state.table]) // eslint-disable-line
|
|
1554
|
-
|
|
1555
|
-
// When geo label override changes
|
|
1556
|
-
// - redo the tooltips
|
|
1557
|
-
useEffect(() => {
|
|
1558
|
-
applyTooltipsToGeo()
|
|
1559
|
-
}, [state.general.geoLabelOverride]) // eslint-disable-line
|
|
1560
|
-
|
|
1561
1298
|
useEffect(() => {
|
|
1562
1299
|
// UID
|
|
1563
1300
|
if (state.data && state.columns.geo.name && state.columns.geo.name !== state.data.fromColumn) {
|
|
@@ -1678,21 +1415,14 @@ const CdcMap = ({
|
|
|
1678
1415
|
handleDragStateChange,
|
|
1679
1416
|
applyLegendToRow,
|
|
1680
1417
|
applyTooltipsToGeo,
|
|
1681
|
-
capitalize: state.tooltips?.capitalizeLabels,
|
|
1682
|
-
closeModal,
|
|
1683
|
-
columnsInData: state?.data?.[0] ? Object.keys(state.data[0]) : [],
|
|
1684
1418
|
container,
|
|
1685
1419
|
content: modal,
|
|
1686
|
-
currentViewport,
|
|
1687
1420
|
data: runtimeData,
|
|
1688
|
-
dimensions,
|
|
1689
|
-
displayDataAsText,
|
|
1690
1421
|
displayGeoName,
|
|
1691
1422
|
filteredCountryCode,
|
|
1692
1423
|
generateColorsArray,
|
|
1693
1424
|
generateRuntimeData,
|
|
1694
1425
|
geoClickHandler,
|
|
1695
|
-
handleMapAriaLabels,
|
|
1696
1426
|
hasZoom: state.general.allowMapZoom,
|
|
1697
1427
|
innerContainerRef,
|
|
1698
1428
|
isDashboard,
|
|
@@ -1700,7 +1430,6 @@ const CdcMap = ({
|
|
|
1700
1430
|
isEditor,
|
|
1701
1431
|
loadConfig,
|
|
1702
1432
|
logo,
|
|
1703
|
-
navigationHandler,
|
|
1704
1433
|
position,
|
|
1705
1434
|
resetLegendToggles,
|
|
1706
1435
|
runtimeFilters,
|
|
@@ -1716,18 +1445,14 @@ const CdcMap = ({
|
|
|
1716
1445
|
setSharedFilterValue,
|
|
1717
1446
|
setState,
|
|
1718
1447
|
state,
|
|
1719
|
-
supportedCities,
|
|
1720
|
-
supportedCounties,
|
|
1721
|
-
supportedCountries,
|
|
1722
|
-
supportedTerritories,
|
|
1723
|
-
titleCase,
|
|
1724
|
-
type: general.type,
|
|
1725
|
-
viewport: currentViewport,
|
|
1726
1448
|
tooltipId,
|
|
1727
1449
|
tooltipRef,
|
|
1728
1450
|
topoData,
|
|
1729
1451
|
setTopoData,
|
|
1730
|
-
mapId
|
|
1452
|
+
mapId,
|
|
1453
|
+
outerContainerRef,
|
|
1454
|
+
dimensions,
|
|
1455
|
+
currentViewport
|
|
1731
1456
|
}
|
|
1732
1457
|
|
|
1733
1458
|
if (!mapProps.data || !state.data) return <></>
|
|
@@ -1809,11 +1534,7 @@ const CdcMap = ({
|
|
|
1809
1534
|
<SkipTo skipId={tabId} skipMessage={`Skip over annotations`} key={`skip-annotations`} />
|
|
1810
1535
|
)}
|
|
1811
1536
|
|
|
1812
|
-
{general.introText && (
|
|
1813
|
-
<section className='introText' style={{ padding: '15px', margin: '0px' }}>
|
|
1814
|
-
{parse(general.introText)}
|
|
1815
|
-
</section>
|
|
1816
|
-
)}
|
|
1537
|
+
{general.introText && <section className='introText mb-4'>{parse(general.introText)}</section>}
|
|
1817
1538
|
|
|
1818
1539
|
{state?.filters?.length > 0 && (
|
|
1819
1540
|
<Filters
|
|
@@ -1830,10 +1551,10 @@ const CdcMap = ({
|
|
|
1830
1551
|
role='region'
|
|
1831
1552
|
tabIndex='0'
|
|
1832
1553
|
className={mapContainerClasses.join(' ')}
|
|
1833
|
-
onClick={e => closeModal(e)}
|
|
1554
|
+
onClick={e => closeModal(e, modal, setModal)}
|
|
1834
1555
|
onKeyDown={e => {
|
|
1835
|
-
if (e.
|
|
1836
|
-
closeModal(e)
|
|
1556
|
+
if (e.key === 'Enter') {
|
|
1557
|
+
closeModal(e, modal, setModal)
|
|
1837
1558
|
}
|
|
1838
1559
|
}}
|
|
1839
1560
|
style={{ padding: '15px 0px', margin: '0px' }}
|
|
@@ -1849,6 +1570,7 @@ const CdcMap = ({
|
|
|
1849
1570
|
{'us-county' === geoType && <UsaMap.County />}
|
|
1850
1571
|
{'world' === geoType && <WorldMap />}
|
|
1851
1572
|
{/* logo is handled in UsaMap.State when applicable */}
|
|
1573
|
+
{'google-map' === geoType && <GoogleMap />}
|
|
1852
1574
|
{'data' === general.type && logo && ('us' !== geoType || 'us-geocode' === state.general.type) && (
|
|
1853
1575
|
<img src={logo} alt='' className='map-logo' style={{ maxWidth: '50px' }} />
|
|
1854
1576
|
)}
|
|
@@ -1874,14 +1596,16 @@ const CdcMap = ({
|
|
|
1874
1596
|
data={runtimeData}
|
|
1875
1597
|
options={general}
|
|
1876
1598
|
columns={state.columns}
|
|
1877
|
-
navigationHandler={val =>
|
|
1599
|
+
navigationHandler={val =>
|
|
1600
|
+
navigationHandler(state.general.navigationBehavior, val, customNavigationHandler)
|
|
1601
|
+
}
|
|
1878
1602
|
/>
|
|
1879
1603
|
)}
|
|
1880
1604
|
|
|
1881
1605
|
{/* Link */}
|
|
1882
1606
|
{isDashboard && config.table?.forceDisplay && config.table.showDataTableLink ? tableLink : link && link}
|
|
1883
1607
|
|
|
1884
|
-
{subtext.length > 0 && <p className='subtext'>{parse(subtext)}</p>}
|
|
1608
|
+
{subtext.length > 0 && <p className='subtext mt-4'>{parse(subtext)}</p>}
|
|
1885
1609
|
|
|
1886
1610
|
<MediaControls.Section classes={['download-buttons']}>
|
|
1887
1611
|
{state.general.showDownloadImgButton && (
|
|
@@ -1919,7 +1643,6 @@ const CdcMap = ({
|
|
|
1919
1643
|
showFullGeoNameInCSV={table.showFullGeoNameInCSV}
|
|
1920
1644
|
runtimeLegend={runtimeLegend}
|
|
1921
1645
|
runtimeData={runtimeData}
|
|
1922
|
-
displayDataAsText={displayDataAsText}
|
|
1923
1646
|
displayGeoName={displayGeoName}
|
|
1924
1647
|
applyLegendToRow={applyLegendToRow}
|
|
1925
1648
|
tableTitle={table.label}
|
|
@@ -1941,7 +1664,7 @@ const CdcMap = ({
|
|
|
1941
1664
|
|
|
1942
1665
|
{state.annotations.length > 0 && <Annotation.Dropdown />}
|
|
1943
1666
|
|
|
1944
|
-
{general.footnotes && <section className='footnotes'>{parse(general.footnotes)}</section>}
|
|
1667
|
+
{general.footnotes && <section className='footnotes pt-2 mt-4'>{parse(general.footnotes)}</section>}
|
|
1945
1668
|
</section>
|
|
1946
1669
|
)}
|
|
1947
1670
|
|