@cdc/map 4.24.1 → 4.24.3

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 (43) hide show
  1. package/dist/cdcmap.js +53648 -47918
  2. package/examples/508.json +548 -0
  3. package/examples/default-county.json +0 -28
  4. package/examples/default-hex.json +110 -13
  5. package/examples/default-usa.json +69 -28
  6. package/examples/test.json +0 -9614
  7. package/examples/usa-special-class-legend.json +501 -0
  8. package/examples/{private/zika-issue.json → zika.json} +47 -51
  9. package/index.html +11 -5
  10. package/package.json +3 -3
  11. package/src/CdcMap.tsx +84 -32
  12. package/src/components/BubbleList.jsx +9 -1
  13. package/src/components/CityList.jsx +94 -31
  14. package/src/components/DataTable.jsx +7 -7
  15. package/src/components/EditorPanel/components/EditorPanel.tsx +181 -46
  16. package/src/components/EditorPanel/components/HexShapeSettings.tsx +18 -3
  17. package/src/components/Geo.jsx +4 -2
  18. package/src/components/Legend/components/Legend.tsx +67 -13
  19. package/src/components/Legend/components/LegendItem.Hex.tsx +5 -9
  20. package/src/components/Legend/components/index.scss +31 -5
  21. package/src/components/UsaMap/components/HexIcon.tsx +41 -0
  22. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +38 -19
  23. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +10 -21
  24. package/src/components/UsaMap/components/UsaMap.Region.tsx +11 -37
  25. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +0 -1
  26. package/src/components/UsaMap/components/UsaMap.State.tsx +62 -61
  27. package/src/components/UsaMap/helpers/patternSizes.tsx +5 -0
  28. package/src/components/WorldMap/components/WorldMap.jsx +16 -8
  29. package/src/components/WorldMap/data/world-topo-guiana-update.json +1 -0
  30. package/src/components/WorldMap/data/world-topo-old.json +1 -0
  31. package/{examples/private/new-world.json → src/components/WorldMap/data/world-topo-recent.json} +23137 -22280
  32. package/src/components/WorldMap/data/world-topo.json +1 -1
  33. package/src/data/initial-state.js +5 -2
  34. package/src/data/supported-geos.js +21 -1
  35. package/src/hooks/useTooltip.ts +4 -4
  36. package/src/scss/editor-panel.scss +5 -3
  37. package/src/scss/main.scss +2 -1
  38. package/src/scss/map.scss +22 -12
  39. package/src/types/MapConfig.ts +7 -0
  40. package/examples/private/map-text-wrap.json +0 -574
  41. package/examples/private/map-world-data.json +0 -1046
  42. package/examples/world-geocode-data.json +0 -18
  43. package/examples/world-geocode.json +0 -108
@@ -1,16 +1,12 @@
1
1
  {
2
2
  "general": {
3
- "title": "Default World Map",
4
- "subtext": "",
5
- "type": "data",
6
3
  "geoType": "world",
7
- "headerColor": "theme-blue",
8
4
  "geoBorderColor": "darkGray",
5
+ "headerColor": "theme-blue",
6
+ "title": "Countries and territories where Zika cases have been reported (as of October 5, 2023)*",
7
+ "showTitle": false,
9
8
  "showSidebar": true,
10
- "showTitle": true,
11
9
  "showDownloadButton": true,
12
- "expandDataTable": true,
13
- "equalNumberOptIn": true,
14
10
  "showDownloadMediaButton": false,
15
11
  "displayAsHex": false,
16
12
  "displayStateLabels": false,
@@ -20,9 +16,10 @@
20
16
  "geoLabelOverride": "",
21
17
  "hasRegions": false,
22
18
  "fullBorder": false,
19
+ "type": "data",
23
20
  "convertFipsCodes": true,
24
21
  "palette": {
25
- "isReversed": false
22
+ "isReversed": true
26
23
  },
27
24
  "allowMapZoom": true,
28
25
  "hideGeoColumnInTooltip": false,
@@ -30,10 +27,12 @@
30
27
  "statePicked": {
31
28
  "fipsCode": "01",
32
29
  "stateName": "Alabama"
33
- }
30
+ },
31
+ "footnotes": "*Does not include countries or territories where only imported cases have been documented. Small island nations might not display on the map but data are included in the table above.",
32
+ "introText": ""
34
33
  },
35
34
  "type": "map",
36
- "color": "pinkpurple",
35
+ "color": "sequential-blue-2(MPX)reverse",
37
36
  "columns": {
38
37
  "geo": {
39
38
  "name": "Country",
@@ -42,17 +41,15 @@
42
41
  "dataTable": true
43
42
  },
44
43
  "primary": {
45
- "name": "Category",
46
- "label": "Data Label",
47
- "prefix": "",
48
- "suffix": "%",
49
44
  "dataTable": true,
50
- "tooltip": true
45
+ "tooltip": true,
46
+ "prefix": "",
47
+ "suffix": "",
48
+ "name": "Category",
49
+ "label": ""
51
50
  },
52
51
  "navigate": {
53
- "name": "Link",
54
- "tooltip": false,
55
- "dataTable": false
52
+ "name": ""
56
53
  },
57
54
  "latitude": {
58
55
  "name": ""
@@ -62,30 +59,22 @@
62
59
  }
63
60
  },
64
61
  "legend": {
65
- "numberOfItems": 3,
66
- "position": "side",
67
- "title": "Legend Title",
68
- "description": "Legend Text",
69
- "type": "category",
70
- "specialClasses": [
71
- {
72
- "key": "Data",
73
- "value": "N/A",
74
- "label": "N/A"
75
- }
76
- ],
77
- "separateZero": true,
78
62
  "descriptions": {},
63
+ "specialClasses": [],
79
64
  "unified": false,
80
65
  "singleColumn": false,
81
66
  "singleRow": false,
82
67
  "verticalSorted": false,
83
68
  "showSpecialClassesLast": false,
84
69
  "dynamicDescription": false,
70
+ "type": "category",
71
+ "numberOfItems": 3,
72
+ "position": "bottom",
73
+ "title": "Legend",
85
74
  "categoryValuesOrder": [
75
+ "Current or past Zika transmission",
86
76
  "Known to have mosquito that transmits Zika, but no reported Zika cases ",
87
- "Not known to have mosquito that transmits Zika",
88
- "Current or past Zika transmission"
77
+ "Not known to have mosquito that transmits Zika"
89
78
  ]
90
79
  },
91
80
  "filters": [],
@@ -96,11 +85,12 @@
96
85
  "height": "",
97
86
  "caption": "",
98
87
  "showDownloadUrl": false,
99
- "showDataTableLink": true,
88
+ "showDataTableLink": false,
100
89
  "showFullGeoNameInCSV": false,
101
90
  "forceDisplay": true,
102
- "download": false,
103
- "indexLabel": ""
91
+ "download": true,
92
+ "indexLabel": "",
93
+ "wrapColumns": false
104
94
  },
105
95
  "tooltips": {
106
96
  "appearanceType": "hover",
@@ -108,9 +98,6 @@
108
98
  "capitalizeLabels": true,
109
99
  "opacity": 90
110
100
  },
111
- "runtime": {
112
- "editorErrorMessage": []
113
- },
114
101
  "visual": {
115
102
  "minBubbleSize": 1,
116
103
  "maxBubbleSize": 20,
@@ -127,7 +114,8 @@
127
114
  "zoom": 1
128
115
  },
129
116
  "map": {
130
- "layers": []
117
+ "layers": [],
118
+ "patterns": []
131
119
  },
132
120
  "hexMap": {
133
121
  "type": "",
@@ -148,6 +136,10 @@
148
136
  ]
149
137
  },
150
138
  "filterBehavior": "Filter Change",
139
+ "openModal": false,
140
+ "uid": "map1701275481219",
141
+ "editing": true,
142
+ "validated": 4.23,
151
143
  "data": [
152
144
  {
153
145
  "Country": "Afghanistan",
@@ -182,7 +174,7 @@
182
174
  "Category": "Current or past Zika transmission"
183
175
  },
184
176
  {
185
- "Country": "Antartica",
177
+ "Country": "Antarctica",
186
178
  "Category": "Not known to have mosquito that transmits Zika"
187
179
  },
188
180
  {
@@ -361,10 +353,6 @@
361
353
  "Country": "Comoros",
362
354
  "Category": "Not known to have mosquito that transmits Zika"
363
355
  },
364
- {
365
- "Country": "Continental United States",
366
- "Category": "Known to have mosquito that transmits Zika, but no reported Zika cases "
367
- },
368
356
  {
369
357
  "Country": "Cook Islands",
370
358
  "Category": "Current or past Zika transmission"
@@ -719,7 +707,7 @@
719
707
  },
720
708
  {
721
709
  "Country": "Mali",
722
- "Category": "Known to have mosquito that transmits Zika, but no reported Zika cases "
710
+ "Category": "Current or past Zika transmission"
723
711
  },
724
712
  {
725
713
  "Country": "Malta",
@@ -854,7 +842,7 @@
854
842
  "Category": "Current or past Zika transmission"
855
843
  },
856
844
  {
857
- "Country": "Palestinian Territories",
845
+ "Country": "Gaza/West Bank",
858
846
  "Category": "Not known to have mosquito that transmits Zika"
859
847
  },
860
848
  {
@@ -942,11 +930,11 @@
942
930
  "Category": "Current or past Zika transmission"
943
931
  },
944
932
  {
945
- "Country": "Saint Paul and New Amsterdam Islands",
933
+ "Country": "Sicily",
946
934
  "Category": "Not known to have mosquito that transmits Zika"
947
935
  },
948
936
  {
949
- "Country": "Saint Pierre and Miquelon",
937
+ "Country": "Saint Paul and Amsterdam Islands",
950
938
  "Category": "Not known to have mosquito that transmits Zika"
951
939
  },
952
940
  {
@@ -1141,6 +1129,10 @@
1141
1129
  "Country": "United States Minor Outlying Islands",
1142
1130
  "Category": "Not known to have mosquito that transmits Zika"
1143
1131
  },
1132
+ {
1133
+ "Country": "United States of America",
1134
+ "Category": "Known to have mosquito that transmits Zika, but no reported Zika cases "
1135
+ },
1144
1136
  {
1145
1137
  "Country": "Uruguay",
1146
1138
  "Category": "Known to have mosquito that transmits Zika, but no reported Zika cases "
@@ -1190,9 +1182,13 @@
1190
1182
  "Category": "Known to have mosquito that transmits Zika, but no reported Zika cases "
1191
1183
  },
1192
1184
  {
1193
- "Country": "Zimbabwe",
1185
+ "Country": "Dhekelia",
1194
1186
  "Category": "Known to have mosquito that transmits Zika, but no reported Zika cases "
1195
1187
  }
1196
1188
  ],
1197
- "validated": 4.23
1189
+ "dataDescription": {
1190
+ "series": false,
1191
+ "horizontal": false
1192
+ },
1193
+ "dataKey": "Map-data-1_23_24-wMali.csv"
1198
1194
  }
package/index.html CHANGED
@@ -11,12 +11,18 @@
11
11
  .cdc-map-outer-container {
12
12
  min-height: 100vh;
13
13
  }
14
+ /* .alaska,
15
+ .hawaii {
16
+ display: none;
17
+ } */
14
18
  </style>
15
19
  </head>
16
20
 
17
21
  <body>
18
22
  <!-- DEFAULT EXAMPLES -->
23
+ <div class="react-container" data-config="/examples/private/map.json"></div>
19
24
  <!-- <div class="react-container" data-config="/examples/private/zika-issue.json"></div> -->
25
+ <!-- <div class="react-container react-container--maps" data-config="/examples/usa-special-class-legend.json">/</div> -->
20
26
 
21
27
  <!-- <div class="react-container react-container--maps" data-config="/examples/private/tooltip-issue.json"></div> -->
22
28
  <!-- <div class="react-container react-container--maps" data-config="/examples/test.json"></div> -->
@@ -25,11 +31,10 @@
25
31
  <!-- <div class="react-container react-container--maps" data-config="/examples/private/map-text-wrap.json"></div> -->
26
32
  <!-- <div class="react-container react-container--maps" data-config="/examples/private/tooltip-issue.json"></div> -->
27
33
  <!-- <div class="react-container react-container--maps" data-config="/examples/test.json"></div> -->
28
- <!-- <div class="react-container react-container--maps" data-config="/examples/default-county.json"></div> -->
34
+ <!-- <div class="react-container react-container--maps" data-config="/examples/default-usa-regions.json"></div> -->
29
35
  <!-- <div class="react-container react-container--maps" data-config="/examples/default-usa.json"></div> -->
30
36
  <!-- <div class="react-container react-container--maps" data-config="https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/US-County-Level-Map.json"></div> -->
31
37
  <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-geocode.json"></div> -->
32
- <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-usa-regions.json"></div> -->
33
38
  <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-single-state.json"></div> -->
34
39
  <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/bubble-us.json"></div> -->
35
40
  <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/bubble-world.json"></div> -->
@@ -38,15 +43,16 @@
38
43
  <!-- <div class="react-container" data-config="/examples/private/wastewater.json"></div> -->
39
44
 
40
45
  <!-- TP4 EXAMPLES -->
41
- <!-- <div class="react-container react-container--maps" data-config="/examples/example-city-state.json"></div> -->
46
+ <!-- <div class="react-container react-container--maps" data-config="/examples/private/solr.json"></div> -->
42
47
  <!-- <div class="react-container react-container--maps" data-config="/examples/custom-map-layers.json"></div> -->
43
48
  <!-- <div class="react-container react-container--maps" data-config="/examples/example-city-stateBAD.json"></div> -->
44
- <div class="react-container react-container--maps" data-config="/examples/example-world-map.json"></div>
49
+ <!-- <div class="react-container react-container--maps" data-config="/examples/private/world-map.json"></div> -->
50
+ <!-- <div class="react-container react-container--maps" data-config="/examples/default-hex.json"></div> -->
45
51
  <!-- <div class="react-container react-container--maps" data-config="/examples/default-hex.json"></div> -->
46
52
  <!-- <div class="react-container react-container--maps" data-config="/examples/hex-with-arrows.json"></div> -->
47
53
 
48
54
  <!-- TP4 EXAMPLES -->
49
- <!-- <div class="react-container react-container--maps" data-config="/examples/example-city-state.json"></div> -->
55
+ <div class="react-container react-container--maps" data-config="/examples/example-city-state.json"></div>
50
56
  <!-- <div class="react-container react-container--maps" data-config="/examples/example-city-state-no-territories.json"></div> -->
51
57
  <!-- <div class="react-container react-container--maps" data-config="/examples/example-world-map.json"></div> -->
52
58
  <!-- <div class="react-container react-container--maps" data-config="/examples/default-hex.json"></div> -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/map",
3
- "version": "4.24.1",
3
+ "version": "4.24.3",
4
4
  "description": "React component for visualizing tabular data on a map of the United States or the world.",
5
5
  "moduleName": "CdcMap",
6
6
  "main": "dist/cdcmap",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "license": "Apache-2.0",
26
26
  "dependencies": {
27
- "@cdc/core": "^4.24.1",
27
+ "@cdc/core": "^4.24.3",
28
28
  "@emotion/core": "^10.0.28",
29
29
  "@emotion/react": "^11.1.5",
30
30
  "@hello-pangea/dnd": "^16.2.0",
@@ -51,5 +51,5 @@
51
51
  "react": "^18.2.0",
52
52
  "react-dom": "^18.2.0"
53
53
  },
54
- "gitHead": "a352a3f74f4b681191e3244061dbb3621f36eec3"
54
+ "gitHead": "9c7ef7ca74f2d2a1e04d923b133fe0fc557a62bf"
55
55
  }
package/src/CdcMap.tsx CHANGED
@@ -15,12 +15,13 @@ import 'react-tooltip/dist/react-tooltip.css'
15
15
  // Helpers
16
16
  import { publish } from '@cdc/core/helpers/events'
17
17
  import coveUpdateWorker from '@cdc/core/helpers/coveUpdateWorker'
18
+ import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
18
19
  import Title from '@cdc/core/components/ui/Title'
19
20
 
20
21
  // Data
21
22
  import { countryCoordinates } from './data/country-coordinates'
22
23
  import { supportedStates, supportedTerritories, supportedCountries, supportedCounties, supportedCities, supportedStatesFipsCodes, stateFipsToTwoDigit, supportedRegions } from './data/supported-geos'
23
- import colorPalettes from '../../core/data/colorPalettes'
24
+ import colorPalettes from '@cdc/core/data/colorPalettes'
24
25
  import initialState from './data/initial-state'
25
26
 
26
27
  // Assets
@@ -37,6 +38,7 @@ import { DataTransform } from '@cdc/core/helpers/DataTransform'
37
38
  import MediaControls from '@cdc/core/components/MediaControls'
38
39
  import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
39
40
  import getViewport from '@cdc/core/helpers/getViewport'
41
+ import isDomainExternal from '@cdc/core/helpers/isDomainExternal'
40
42
  import Loading from '@cdc/core/components/Loading'
41
43
  import numberFromString from '@cdc/core/helpers/numberFromString'
42
44
  import DataTable from '@cdc/core/components/DataTable' // Future: Lazy
@@ -52,6 +54,8 @@ import NavigationMenu from './components/NavigationMenu' // Future: Lazy
52
54
  import UsaMap from './components/UsaMap' // Future: Lazy
53
55
  import WorldMap from './components/WorldMap' // Future: Lazy
54
56
  import useTooltip from './hooks/useTooltip'
57
+ import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
58
+ import SkipTo from '@cdc/core/components/elements/SkipTo'
55
59
 
56
60
  // Data props
57
61
  const stateKeys = Object.keys(supportedStates)
@@ -131,9 +135,11 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
131
135
  const [container, setContainer] = useState()
132
136
  const [imageId, setImageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`) // eslint-disable-line
133
137
  const [dimensions, setDimensions] = useState()
138
+ const legendRef = useRef(null)
134
139
 
135
140
  const { changeFilterActive, handleSorting } = useFilters({ config: state, setConfig: setState })
136
141
  let legendMemo = useRef(new Map())
142
+ let legendSpecialClassLastMemo = useRef(new Map())
137
143
  let innerContainerRef = useRef()
138
144
 
139
145
  if (isDebug) console.log('CdcMap state=', state) // eslint-disable-line
@@ -239,8 +245,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
239
245
  }
240
246
 
241
247
  // Cities
242
- if (!uid) {
243
- uid = cityKeys.find(key => key === geoName)
248
+ if (!uid && geoName) {
249
+ uid = cityKeys.find(key => key === geoName.toUpperCase())
244
250
  }
245
251
  }
246
252
 
@@ -267,6 +273,11 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
267
273
  if (!uid && 'world-geocode' === state.general.type) {
268
274
  uid = cityKeys.find(key => key === geoName?.toUpperCase())
269
275
  }
276
+
277
+ // Cities
278
+ if (!uid && geoName) {
279
+ uid = cityKeys.find(key => key === geoName.toUpperCase())
280
+ }
270
281
  }
271
282
 
272
283
  // County Check
@@ -279,6 +290,10 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
279
290
  uid = row[state.columns.geo.name]
280
291
  }
281
292
 
293
+ if (!uid && state.columns.latitude?.name && state.columns.longitude?.name && row[state.columns.latitude?.name] && row[state.columns.longitude?.name]) {
294
+ uid = row[state.columns.geo.name]
295
+ }
296
+
282
297
  if (uid) {
283
298
  Object.defineProperty(row, 'uid', {
284
299
  value: uid,
@@ -293,6 +308,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
293
308
  // eslint-disable-next-line
294
309
  const generateRuntimeLegend = useCallback((obj, runtimeData, hash) => {
295
310
  const newLegendMemo = new Map() // Reset memoization
311
+ const newLegendSpecialClassLastMemo = new Map() // Reset bin memoization
296
312
  let primaryCol = obj.columns.primary.name,
297
313
  isBubble = obj.general.type === 'bubble',
298
314
  categoricalCol = obj.columns.categorical ? obj.columns.categorical.name : undefined,
@@ -514,6 +530,13 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
514
530
  result = [...otherRows, ...specialRows]
515
531
  }
516
532
 
533
+ const assignSpecialClassLastIndex = (value, key) => {
534
+ const newIndex = result.findIndex(d => d.bin === value)
535
+ newLegendSpecialClassLastMemo.set(key, newIndex)
536
+ }
537
+ newLegendMemo.forEach(assignSpecialClassLastIndex)
538
+ legendSpecialClassLastMemo.current = newLegendSpecialClassLastMemo
539
+
517
540
  return result
518
541
  }
519
542
 
@@ -687,12 +710,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
687
710
 
688
711
  dataSet.forEach((row, dataIndex) => {
689
712
  let number = row[state.columns.primary.name]
690
- let updated = 0
691
-
692
- // check if we're seperating zero out
693
- updated = state.legend.separateZero && hasZeroInData ? index : index
694
- // check for special classes
695
- updated = state.legend.specialClasses ? updated + state.legend.specialClasses.length : index
713
+ let updated = result.length - 1
696
714
 
697
715
  if (result[updated]?.min === (null || undefined) || result[updated]?.max === (null || undefined)) return
698
716
 
@@ -770,6 +788,13 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
770
788
  }
771
789
  //-----------
772
790
 
791
+ const assignSpecialClassLastIndex = (value, key) => {
792
+ const newIndex = result.findIndex(d => d.bin === value)
793
+ newLegendSpecialClassLastMemo.set(key, newIndex)
794
+ }
795
+ newLegendMemo.forEach(assignSpecialClassLastIndex)
796
+ legendSpecialClassLastMemo.current = newLegendSpecialClassLastMemo
797
+
773
798
  return result
774
799
  })
775
800
 
@@ -781,7 +806,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
781
806
 
782
807
  if (hash) filters.fromHash = hash
783
808
 
784
- obj?.filters.forEach(({ columnName, label, labels, queryParameter, orderedValues, active, values, type, showDropdown }, idx) => {
809
+ obj?.filters.forEach(({ columnName, label, labels, queryParameter, orderedValues, active, values, type, showDropdown, setByQueryParameter }, idx) => {
785
810
  let newFilter = runtimeFilters[idx]
786
811
 
787
812
  const sortAsc = (a, b) => {
@@ -824,6 +849,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
824
849
  newFilter.queryParameter = queryParameter
825
850
  newFilter.labels = labels
826
851
  newFilter.values = values
852
+ newFilter.setByQueryParameter = setByQueryParameter
827
853
  handleSorting(newFilter)
828
854
  newFilter.active = active ?? values[0] // Default to first found value
829
855
  newFilter.filterStyle = obj.filters[idx].filterStyle ? obj.filters[idx].filterStyle : 'dropdown'
@@ -983,7 +1009,13 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
983
1009
 
984
1010
  if (legendMemo.current.has(hash)) {
985
1011
  let idx = legendMemo.current.get(hash)
986
- if (runtimeLegend[idx]?.disabled) return false
1012
+ let disabledIdx = idx
1013
+
1014
+ if (state.legend.showSpecialClassesLast) {
1015
+ disabledIdx = legendSpecialClassLastMemo.current.get(hash)
1016
+ }
1017
+
1018
+ if (runtimeLegend[disabledIdx]?.disabled) return false
987
1019
 
988
1020
  // changed to use bin prop to get color instead of idx
989
1021
  // bc we re-order legend when showSpecialClassesLast is checked
@@ -1074,6 +1106,12 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1074
1106
  // Attempts to find the corresponding value
1075
1107
  const displayGeoName = key => {
1076
1108
  if (!state.general.convertFipsCodes) return key
1109
+
1110
+ // World Map
1111
+ // If we're returning a city name instead of a country ISO code, capitalize it for the data table.
1112
+ if (state.type === 'map' && state.general.geoType === 'world') {
1113
+ if (String(key).length > 3) return titleCase(key)
1114
+ }
1077
1115
  let value = key
1078
1116
  // Map to first item in values array which is the preferred label
1079
1117
  if (stateKeys.includes(value)) {
@@ -1085,7 +1123,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1085
1123
  }
1086
1124
 
1087
1125
  if (countryKeys.includes(value)) {
1088
- value = titleCase(supportedCountries[key][0])
1126
+ value = supportedCountries[key][0]
1089
1127
  }
1090
1128
 
1091
1129
  if (countyKeys.includes(value)) {
@@ -1124,11 +1162,19 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1124
1162
 
1125
1163
  if (state.columns.hasOwnProperty('navigate') && row[state.columns.navigate.name]) {
1126
1164
  toolTipText.push(
1127
- // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
1128
- <ul className='navigation-link' key='modal-navigation-link' onClick={() => navigationHandler(row[state.columns.navigate.name])}>
1165
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions,jsx-a11y/anchor-is-valid
1166
+ <a
1167
+ href='#'
1168
+ className='navigation-link'
1169
+ key='modal-navigation-link'
1170
+ onClick={e => {
1171
+ e.preventDefault()
1172
+ navigationHandler(row[state.columns.navigate.name])
1173
+ }}
1174
+ >
1129
1175
  {state.tooltips.linkLabel}
1130
- <ExternalIcon className='inline-icon ml-1' />
1131
- </ul>
1176
+ {isDomainExternal(row[state.columns.navigate.name]) && <ExternalIcon className='inline-icon ml-1' />}
1177
+ </a>
1132
1178
  )
1133
1179
  }
1134
1180
  }
@@ -1266,18 +1312,19 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1266
1312
  const regex = /(?:\.([^.]+))?$/
1267
1313
 
1268
1314
  const ext = regex.exec(dataUrl.pathname)[1]
1269
- if ('csv' === ext) {
1315
+ if ('csv' === ext || isSolrCsv(dataUrlFinal)) {
1270
1316
  data = await fetch(dataUrlFinal)
1271
1317
  .then(response => response.text())
1272
1318
  .then(responseText => {
1273
1319
  const parsedCsv = Papa.parse(responseText, {
1274
1320
  header: true,
1275
1321
  dynamicTyping: true,
1276
- skipEmptyLines: true
1322
+ skipEmptyLines: true,
1323
+ encoding: 'utf-8'
1277
1324
  })
1278
1325
  return parsedCsv.data
1279
1326
  })
1280
- } else if ('json' === ext) {
1327
+ } else if ('json' === ext || isSolrJson(dataUrlFinal)) {
1281
1328
  data = await fetch(dataUrlFinal).then(response => response.json())
1282
1329
  } else {
1283
1330
  data = []
@@ -1435,6 +1482,12 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1435
1482
  filters = generateRuntimeFilters(state, hashFilters, runtimeFilters)
1436
1483
 
1437
1484
  if (filters) {
1485
+ filters.forEach((filter, index) => {
1486
+ const queryStringFilterValue = getQueryStringFilterValue(filter)
1487
+ if (queryStringFilterValue) {
1488
+ filters[index].active = queryStringFilterValue
1489
+ }
1490
+ })
1438
1491
  setRuntimeFilters(filters)
1439
1492
  }
1440
1493
  }
@@ -1568,21 +1621,21 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1568
1621
 
1569
1622
  // 1) skip to legend
1570
1623
  if (general.showSidebar) {
1571
- tabbingID = '#legend'
1624
+ tabbingID = 'legend'
1572
1625
  }
1573
1626
 
1574
1627
  // 2) skip to data table if it exists and not a navigation map
1575
1628
  if (hasDataTable && !general.showSidebar) {
1576
- tabbingID = `#dataTableSection__${Date.now()}`
1629
+ tabbingID = `dataTableSection__${Date.now()}`
1577
1630
  }
1578
1631
 
1579
1632
  // 3) if it's a navigation map skip to the dropdown.
1580
1633
  if (state.general.type === 'navigation') {
1581
- tabbingID = `#dropdown-${Date.now()}`
1634
+ tabbingID = `dropdown-${Date.now()}`
1582
1635
  }
1583
1636
 
1584
1637
  // 4) handle other options
1585
- return tabbingID || '#!'
1638
+ return tabbingID || '!'
1586
1639
  }
1587
1640
 
1588
1641
  const tabId = handleMapTabbing()
@@ -1600,9 +1653,6 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1600
1653
  {isEditor && <EditorPanel />}
1601
1654
  {!runtimeData.init && (general.type === 'navigation' || runtimeLegend) && (
1602
1655
  <section className={`cdc-map-inner-container ${currentViewport}`} aria-label={'Map: ' + title} ref={innerContainerRef}>
1603
- {!window.matchMedia('(any-hover: none)').matches && 'hover' === tooltips.appearanceType && (
1604
- <ReactTooltip id='tooltip' float={true} className={`${tooltips.capitalizeLabels ? 'capitalize tooltip' : 'tooltip'}`} style={{ background: `rgba(255,255,255, ${state.tooltips.opacity / 100})`, color: 'black' }} />
1605
- )}
1606
1656
  {/* prettier-ignore */}
1607
1657
  <Title
1608
1658
  title={title}
@@ -1610,13 +1660,15 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1610
1660
  config={config}
1611
1661
  classes={['map-title', general.showTitle === true ? 'visible' : 'hidden', `${general.headerColor}`]}
1612
1662
  />
1663
+ <SkipTo skipId={tabId} skipMessage='Skip Over Map Container' />
1664
+
1613
1665
  {general.introText && <section className='introText'>{parse(general.introText)}</section>}
1614
1666
 
1615
1667
  {/* prettier-ignore */}
1616
1668
  {state?.filters?.length > 0 && <Filters config={state} setConfig={setState} filteredData={runtimeFilters} setFilteredData={setRuntimeFilters} dimensions={dimensions} />}
1617
1669
 
1618
1670
  <div
1619
- role='button'
1671
+ role='region'
1620
1672
  tabIndex='0'
1621
1673
  className={mapContainerClasses.join(' ')}
1622
1674
  onClick={e => closeModal(e)}
@@ -1626,10 +1678,6 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1626
1678
  }
1627
1679
  }}
1628
1680
  >
1629
- <a id='skip-geo-container' className='cdcdataviz-sr-only-focusable' href={tabId}>
1630
- Skip Over Map Container
1631
- </a>
1632
-
1633
1681
  {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
1634
1682
  <section className='outline-none geography-container' ref={mapSvg} tabIndex='0' style={{ width: '100%' }}>
1635
1683
  {currentViewport && (
@@ -1645,7 +1693,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1645
1693
  )}
1646
1694
  </section>
1647
1695
 
1648
- {general.showSidebar && 'navigation' !== general.type && <Legend />}
1696
+ {general.showSidebar && 'navigation' !== general.type && <Legend ref={legendRef} />}
1649
1697
  </div>
1650
1698
 
1651
1699
  {'navigation' === general.type && <NavigationMenu mapTabbingID={tabId} displayGeoName={displayGeoName} data={runtimeData} options={general} columns={state.columns} navigationHandler={val => navigationHandler(val)} />}
@@ -1699,6 +1747,10 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1699
1747
  <div aria-live='assertive' className='cdcdataviz-sr-only'>
1700
1748
  {accessibleStatus}
1701
1749
  </div>
1750
+
1751
+ {!window.matchMedia('(any-hover: none)').matches && 'hover' === tooltips.appearanceType && (
1752
+ <ReactTooltip id='tooltip' float={true} className={`${tooltips.capitalizeLabels ? 'capitalize tooltip' : 'tooltip'}`} style={{ background: `rgba(255,255,255, ${state.tooltips.opacity / 100})`, color: 'black' }} />
1753
+ )}
1702
1754
  </div>
1703
1755
  </ConfigContext.Provider>
1704
1756
  )
@@ -39,6 +39,7 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
39
39
  const circle = (
40
40
  <>
41
41
  <circle
42
+ tabIndex={-1}
42
43
  key={`circle-${countryName.replace(' ', '')}`}
43
44
  className={`bubble country--${countryName}`}
44
45
  cx={Number(projection(coordinates[1], coordinates[0])[0]) || 0} // || 0 handles error on loads where the data isn't ready
@@ -67,6 +68,7 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
67
68
 
68
69
  {state.visual.extraBubbleBorder && (
69
70
  <circle
71
+ tabIndex={-1}
70
72
  key={`circle-${countryName.replace(' ', '')}`}
71
73
  className='bubble'
72
74
  cx={Number(projection(coordinates[1], coordinates[0])[0]) || 0} // || 0 handles error on loads where the data isn't ready
@@ -95,7 +97,11 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
95
97
  </>
96
98
  )
97
99
 
98
- return <g key={`group-${countryName.replace(' ', '')}`}>{circle}</g>
100
+ return (
101
+ <g key={`group-${countryName.replace(' ', '')}`} tabIndex={-1}>
102
+ {circle}
103
+ </g>
104
+ )
99
105
  })
100
106
  return countries
101
107
  }
@@ -132,6 +138,7 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
132
138
  const circle = (
133
139
  <>
134
140
  <circle
141
+ tabIndex={-1}
135
142
  key={`circle-${stateName.replace(' ', '')}`}
136
143
  className='bubble'
137
144
  cx={projection(coordinates)[0] || 0} // || 0 handles error on loads where the data isn't ready
@@ -159,6 +166,7 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
159
166
  />
160
167
  {state.visual.extraBubbleBorder && (
161
168
  <circle
169
+ tabIndex={-1}
162
170
  key={`circle-${stateName.replace(' ', '')}`}
163
171
  className='bubble'
164
172
  cx={projection(coordinates)[0] || 0} // || 0 handles error on loads where the data isn't ready