@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.
Files changed (52) hide show
  1. package/dist/cdcmap.js +47146 -45979
  2. package/examples/annotation/index.json +1 -1
  3. package/examples/custom-map-layers.json +1 -1
  4. package/examples/default-geocode.json +2 -2
  5. package/examples/private/mmr.json +246 -0
  6. package/index.html +12 -14
  7. package/package.json +8 -3
  8. package/src/CdcMap.tsx +85 -362
  9. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +9 -0
  10. package/src/_stories/CdcMap.stories.tsx +1 -1
  11. package/src/_stories/GoogleMap.stories.tsx +19 -0
  12. package/src/_stories/_mock/DEV-10148.json +859 -0
  13. package/src/_stories/_mock/DEV-9989.json +229 -0
  14. package/src/_stories/_mock/example-city-state.json +1 -1
  15. package/src/_stories/_mock/google-map.json +819 -0
  16. package/src/components/Annotation/Annotation.Draggable.tsx +34 -43
  17. package/src/components/Annotation/AnnotationDropdown.tsx +4 -4
  18. package/src/components/CityList.tsx +2 -2
  19. package/src/components/DataTable.tsx +8 -9
  20. package/src/components/EditorPanel/components/EditorPanel.tsx +90 -17
  21. package/src/components/GoogleMap/components/GoogleMap.tsx +67 -0
  22. package/src/components/GoogleMap/index.tsx +3 -0
  23. package/src/components/Legend/components/Legend.tsx +40 -30
  24. package/src/components/Legend/components/LegendItem.Hex.tsx +7 -3
  25. package/src/components/Legend/components/index.scss +22 -16
  26. package/src/components/Modal.tsx +6 -5
  27. package/src/components/NavigationMenu.tsx +5 -4
  28. package/src/components/UsaMap/components/TerritoriesSection.tsx +56 -0
  29. package/src/components/UsaMap/components/UsaMap.County.tsx +1 -1
  30. package/src/components/UsaMap/components/UsaMap.Region.tsx +12 -8
  31. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +2 -2
  32. package/src/components/UsaMap/components/UsaMap.State.tsx +22 -28
  33. package/src/components/WorldMap/WorldMap.tsx +3 -5
  34. package/src/context.ts +0 -12
  35. package/src/data/initial-state.js +2 -2
  36. package/src/data/supported-geos.js +23 -3
  37. package/src/helpers/applyColorToLegend.ts +3 -3
  38. package/src/helpers/closeModal.ts +9 -0
  39. package/src/helpers/handleMapAriaLabels.ts +38 -0
  40. package/src/helpers/indexOfIgnoreType.ts +8 -0
  41. package/src/helpers/navigationHandler.ts +21 -0
  42. package/src/helpers/toTitleCase.ts +44 -0
  43. package/src/helpers/validateFipsCodeLength.ts +30 -0
  44. package/src/hooks/useResizeObserver.ts +42 -0
  45. package/src/hooks/useTooltip.ts +4 -2
  46. package/src/index.jsx +1 -0
  47. package/src/scss/editor-panel.scss +2 -1
  48. package/src/scss/filters.scss +0 -5
  49. package/src/scss/main.scss +57 -61
  50. package/src/scss/map.scss +1 -13
  51. package/src/types/MapConfig.ts +19 -11
  52. 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
- // Helpers
25
- import { hashObj } from './helpers/hashObj'
26
- import { generateRuntimeLegendHash } from './helpers/generateRuntimeLegendHash'
27
- import { generateColorsArray } from './helpers/generateColorsArray'
28
- import { getUniqueValues } from './helpers/getUniqueValues'
29
- import { publish } from '@cdc/core/helpers/events'
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
- supportedStates,
38
- supportedTerritories,
39
- supportedCountries,
40
- supportedCounties,
26
+ stateFipsToTwoDigit,
41
27
  supportedCities,
28
+ supportedCounties,
29
+ supportedCountries,
30
+ supportedRegions,
31
+ supportedStates,
42
32
  supportedStatesFipsCodes,
43
- stateFipsToTwoDigit,
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 { DataTransform } from '@cdc/core/helpers/DataTransform'
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 DataTable from '@cdc/core/components/DataTable' // Future: Lazy
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 Filters, { useFilters } from '@cdc/core/components/Filters'
70
- import Modal from './components/Modal'
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
- import EditorPanel from './components/EditorPanel' // Future: Lazy
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 { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
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 { changeFilterActive, handleSorting } = useFilters({ config: state, setConfig: setState })
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((row, index) => {
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 based the legend off ALL of the data maps received. Otherwise, it will use
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 seperating zero force it into breaks
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 seperating out zero, increment min to 1.
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 = (index, min) => {
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((row, dataIndex) => {
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.length) {
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 DONT titleCase state abbreviations like "AL"
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, displayDataAsText, supportedStatesFipsCodes })
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(row[state.columns.navigate.name])
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 ml-1' />}
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 -> childprop1
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.keyCode === 13) {
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 => 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