@cdc/map 4.26.2 → 4.26.4

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 (118) hide show
  1. package/CONFIG.md +235 -0
  2. package/README.md +70 -24
  3. package/dist/cdcmap-CY9IcPSi.es.js +6 -0
  4. package/dist/cdcmap-DlpiY3fQ.es.js +4 -0
  5. package/dist/cdcmap.js +31260 -27946
  6. package/examples/{testing-layer-2.json → __data__/testing-layer-2.json} +1 -1
  7. package/examples/{testing-layer.json → __data__/testing-layer.json} +1 -1
  8. package/examples/county-hsa-toggle.json +51993 -0
  9. package/examples/custom-map-layers.json +2 -2
  10. package/examples/default-county.json +3 -3
  11. package/examples/minimal-example.json +69 -0
  12. package/examples/private/annotation-bug.json +642 -0
  13. package/examples/private/css-issue.json +314 -0
  14. package/examples/private/region-breaking.json +1639 -0
  15. package/examples/private/test1.json +27247 -0
  16. package/package.json +4 -4
  17. package/src/CdcMap.tsx +3 -14
  18. package/src/CdcMapComponent.tsx +302 -164
  19. package/src/_stories/CdcMap.Defaults.smoke.stories.tsx +76 -0
  20. package/src/_stories/CdcMap.Editor.ColumnsSectionTests.stories.tsx +601 -0
  21. package/src/_stories/CdcMap.Editor.DataTableSectionTests.stories.tsx +404 -0
  22. package/src/_stories/CdcMap.Editor.FiltersSectionTests.stories.tsx +229 -0
  23. package/src/_stories/CdcMap.Editor.GeneralSectionTests.stories.tsx +262 -0
  24. package/src/_stories/CdcMap.Editor.LegendSectionTests.stories.tsx +541 -0
  25. package/src/_stories/CdcMap.Editor.MultiCountryWorldMapTests.stories.tsx +359 -0
  26. package/src/_stories/CdcMap.Editor.PatternSettingsSectionTests.stories.tsx +516 -0
  27. package/src/_stories/CdcMap.Editor.SmallMultiplesSectionTests.stories.tsx +165 -0
  28. package/src/_stories/CdcMap.Editor.TextAnnotationsSectionTests.stories.tsx +145 -0
  29. package/src/_stories/CdcMap.Editor.TypeSectionTests.stories.tsx +312 -0
  30. package/src/_stories/CdcMap.Editor.VisualSectionTests.stories.tsx +359 -0
  31. package/src/_stories/CdcMap.Editor.ZoomControlsTests.stories.tsx +88 -0
  32. package/src/_stories/{CdcMap.stories.tsx → CdcMap.smoke.stories.tsx} +23 -1
  33. package/src/_stories/Map.HTMLInDataTable.stories.tsx +385 -0
  34. package/src/_stories/_mock/legends/legend-tests.json +3 -3
  35. package/src/_stories/_mock/multi-state-show-unselected.json +82 -0
  36. package/src/cdcMapComponent.styles.css +2 -2
  37. package/src/components/Annotation/Annotation.Draggable.styles.css +4 -4
  38. package/src/components/Annotation/AnnotationDropdown.styles.css +1 -1
  39. package/src/components/Annotation/AnnotationList.styles.css +13 -13
  40. package/src/components/Annotation/AnnotationList.tsx +1 -1
  41. package/src/components/EditorPanel/components/EditorPanel.tsx +905 -416
  42. package/src/components/EditorPanel/components/HexShapeSettings.tsx +1 -1
  43. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +112 -117
  44. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings-style.css +1 -1
  45. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +31 -15
  46. package/src/components/EditorPanel/components/editorPanel.styles.css +55 -25
  47. package/src/components/Legend/components/Legend.tsx +12 -7
  48. package/src/components/Legend/components/LegendGroup/legend.group.css +5 -5
  49. package/src/components/Legend/components/LegendItem.Hex.tsx +4 -2
  50. package/src/components/Legend/components/index.scss +2 -3
  51. package/src/components/NavigationMenu.tsx +2 -1
  52. package/src/components/SmallMultiples/SmallMultiples.css +5 -5
  53. package/src/components/SmallMultiples/SynchronizedTooltip.tsx +1 -1
  54. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +32 -17
  55. package/src/components/UsaMap/components/TerritoriesSection.tsx +3 -2
  56. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +13 -8
  57. package/src/components/UsaMap/components/UsaMap.County.tsx +629 -231
  58. package/src/components/UsaMap/components/UsaMap.Region.styles.css +1 -1
  59. package/src/components/UsaMap/components/UsaMap.SingleState.styles.css +2 -2
  60. package/src/components/UsaMap/components/UsaMap.State.tsx +14 -9
  61. package/src/components/UsaMap/data/cb_2019_us_county_20m.json +75817 -1
  62. package/src/components/UsaMap/data/hsa_fips_mapping.json +3144 -0
  63. package/src/components/WorldMap/WorldMap.tsx +10 -13
  64. package/src/components/WorldMap/data/world-topo-updated.json +1 -0
  65. package/src/components/WorldMap/data/world-topo.json +1 -1
  66. package/src/components/WorldMap/worldMap.styles.css +1 -1
  67. package/src/components/ZoomControls.tsx +49 -18
  68. package/src/components/zoomControls.styles.css +27 -11
  69. package/src/data/initial-state.js +15 -5
  70. package/src/data/legacy-defaults.ts +8 -0
  71. package/src/data/supported-counties.json +1 -1
  72. package/src/data/supported-geos.js +19 -0
  73. package/src/helpers/colors.ts +2 -1
  74. package/src/helpers/countyTerritories.ts +38 -0
  75. package/src/helpers/dataTableHelpers.ts +85 -0
  76. package/src/helpers/displayGeoName.ts +19 -11
  77. package/src/helpers/getMapContainerClasses.ts +8 -2
  78. package/src/helpers/getMatchingPatternForRow.ts +67 -0
  79. package/src/helpers/getPatternForRow.ts +11 -18
  80. package/src/helpers/tests/countyTerritories.test.ts +87 -0
  81. package/src/helpers/tests/dataTableHelpers.test.ts +78 -0
  82. package/src/helpers/tests/displayGeoName.test.ts +17 -0
  83. package/src/helpers/tests/getMatchingPatternForRow.test.ts +150 -0
  84. package/src/helpers/tests/getPatternForRow.test.ts +140 -2
  85. package/src/helpers/urlDataHelpers.ts +7 -1
  86. package/src/hooks/useApplyTooltipsToGeo.tsx +7 -4
  87. package/src/hooks/useMapLayers.tsx +1 -1
  88. package/src/hooks/useResizeObserver.ts +36 -22
  89. package/src/hooks/useTooltip.test.tsx +64 -0
  90. package/src/hooks/useTooltip.ts +46 -15
  91. package/src/scss/editor-panel.scss +1 -1
  92. package/src/scss/main.scss +140 -6
  93. package/src/scss/map.scss +9 -4
  94. package/src/store/map.actions.ts +5 -0
  95. package/src/store/map.reducer.ts +13 -0
  96. package/src/test/CdcMap.test.jsx +26 -2
  97. package/src/types/MapConfig.ts +28 -4
  98. package/src/types/MapContext.ts +5 -1
  99. package/topojson-updater/README.txt +1 -1
  100. package/dist/cdcmap-Cf9_fbQf.es.js +0 -6
  101. package/examples/__data__/city-state-data.json +0 -668
  102. package/examples/city-state.json +0 -434
  103. package/examples/default-world-data.json +0 -1450
  104. package/examples/new-cities.json +0 -656
  105. package/src/_stories/CdcMap.Editor.stories.tsx +0 -3475
  106. package/src/helpers/componentHelpers.ts +0 -8
  107. package/topojson-updater/package-lock.json +0 -223
  108. /package/src/_stories/{CdcMap.ColumnWrap.stories.tsx → CdcMap.ColumnWrap.smoke.stories.tsx} +0 -0
  109. /package/src/_stories/{CdcMap.DistrictOfColumbia.stories.tsx → CdcMap.DistrictOfColumbia.smoke.stories.tsx} +0 -0
  110. /package/src/_stories/{CdcMap.Filters.stories.tsx → CdcMap.Filters.smoke.stories.tsx} +0 -0
  111. /package/src/_stories/{CdcMap.Legend.Gradient.stories.tsx → CdcMap.Legend.Gradient.smoke.stories.tsx} +0 -0
  112. /package/src/_stories/{CdcMap.Legend.stories.tsx → CdcMap.Legend.smoke.stories.tsx} +0 -0
  113. /package/src/_stories/{CdcMap.Patterns.stories.tsx → CdcMap.Patterns.smoke.stories.tsx} +0 -0
  114. /package/src/_stories/{CdcMap.SmallMultiples.stories.tsx → CdcMap.SmallMultiples.smoke.stories.tsx} +0 -0
  115. /package/src/_stories/{CdcMap.Table.stories.tsx → CdcMap.Table.smoke.stories.tsx} +0 -0
  116. /package/src/_stories/{CdcMap.ZeroColor.stories.tsx → CdcMap.ZeroColor.smoke.stories.tsx} +0 -0
  117. /package/src/_stories/{GoogleMap.stories.tsx → GoogleMap.smoke.stories.tsx} +0 -0
  118. /package/src/_stories/{UsaMap.NoData.stories.tsx → UsaMap.NoData.smoke.stories.tsx} +0 -0
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@cdc/map",
3
- "version": "4.26.2",
3
+ "version": "4.26.4",
4
4
  "description": "React component for visualizing tabular data on a map of the United States or the world.",
5
5
  "license": "Apache-2.0",
6
6
  "bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
7
7
  "dependencies": {
8
- "@cdc/core": "^4.26.2",
8
+ "@cdc/core": "^4.26.4",
9
9
  "@googlemaps/markerclusterer": "^2.5.3",
10
10
  "@hello-pangea/dnd": "^16.2.0",
11
11
  "@react-google-maps/api": "^2.20.8",
@@ -24,7 +24,7 @@
24
24
  "d3-scale": "^4.0.2",
25
25
  "d3-selection": "^3.0.0",
26
26
  "d3-zoom": "^3.0.0",
27
- "dompurify": "^3.3.1",
27
+ "dompurify": "^3.4.0",
28
28
  "html-react-parser": "^5.2.3",
29
29
  "leaflet": "^1.9.4",
30
30
  "lodash": "^4.17.23",
@@ -42,7 +42,7 @@
42
42
  "vite-plugin-svgr": "^4.2.0",
43
43
  "whatwg-fetch": "^3.6.20"
44
44
  },
45
- "gitHead": "be3413e8e1149abf94225108f86a7910f56e0616",
45
+ "gitHead": "432a2d1acab22915fafe793cb9da1f10318ff793",
46
46
  "main": "dist/cdcmap",
47
47
  "moduleName": "CdcMap",
48
48
  "peerDependencies": {
package/src/CdcMap.tsx CHANGED
@@ -58,7 +58,8 @@ const CdcMap: React.FC<CdcMapProps> = ({
58
58
  ...configToLoad
59
59
  }
60
60
  if (newState.dataUrl) {
61
- let newData = await fetchRemoteData(newState.dataUrl, 'map')
61
+ let { data: newData, dataMetadata } = await fetchRemoteData(newState.dataUrl)
62
+ newState.dataMetadata = dataMetadata
62
63
 
63
64
  if (newState.vegaConfig) {
64
65
  newData = extractCoveData(updateVegaData(newState.vegaConfig, newData))
@@ -79,18 +80,6 @@ const CdcMap: React.FC<CdcMapProps> = ({
79
80
  newState.data = transform.developerStandardize(newState.data, newState.dataDescription)
80
81
  }
81
82
 
82
- Object.keys(newState).forEach(key => {
83
- if ('object' === typeof newState[key] && false === Array.isArray(newState[key])) {
84
- if (initialState[key]) {
85
- Object.keys(initialState[key]).forEach(property => {
86
- if (undefined === newState[key][property]) {
87
- newState[key][property] = initialState[key][property]
88
- }
89
- })
90
- }
91
- }
92
- })
93
-
94
83
  if (newState.columns.geo.name || newState.columns.geo.fips) {
95
84
  addUIDs(newState, newState.columns.geo.name || newState.columns.geo.fips)
96
85
  }
@@ -113,7 +102,7 @@ const CdcMap: React.FC<CdcMapProps> = ({
113
102
  let _newConfig = cloneConfig(config ?? initialState)
114
103
 
115
104
  if (configUrl) {
116
- _newConfig = await fetchRemoteData(configUrl)
105
+ _newConfig = await fetch(configUrl).then(r => r.json())
117
106
  }
118
107
  if ('object' === typeof _newConfig) {
119
108
  loadConfig(_newConfig)
@@ -8,12 +8,13 @@ import 'react-tooltip/dist/react-tooltip.css'
8
8
  // Core Components
9
9
  import DataTable from '@cdc/core/components/DataTable'
10
10
  import Filters from '@cdc/core/components/Filters'
11
- import Layout from '@cdc/core/components/Layout'
11
+ import { VisualizationContainer, VisualizationContent } from '@cdc/core/components/Layout'
12
12
  import MediaControls from '@cdc/core/components/MediaControls'
13
13
  import SkipTo from '@cdc/core/components/elements/SkipTo'
14
14
  import Title from '@cdc/core/components/ui/Title'
15
15
  import Waiting from '@cdc/core/components/Waiting'
16
16
  import FootnotesStandAlone from '@cdc/core/components/Footnotes/FootnotesStandAlone'
17
+ import { supportedStatesFipsCodes, supportedCounties } from './data/supported-geos'
17
18
 
18
19
  // types
19
20
  import { type MapConfig } from './types/MapConfig'
@@ -24,7 +25,13 @@ import './scss/main.scss'
24
25
  import './cdcMapComponent.styles.css'
25
26
 
26
27
  // Core Helpers
27
- import { getQueryStringFilterValue, isFilterHiddenByQuery } from '@cdc/core/helpers/queryStringUtils'
28
+ import {
29
+ getQueryStringFilterValue,
30
+ isFilterHiddenByQuery,
31
+ removeQueryParam,
32
+ updateQueryParam,
33
+ updateQueryParams
34
+ } from '@cdc/core/helpers/queryStringUtils'
28
35
  import { generateRuntimeFilters } from './helpers/generateRuntimeFilters'
29
36
  import { type MapReducerType, MapState } from './store/map.reducer'
30
37
  import { addValuesToFilters } from '@cdc/core/helpers/addValuesToFilters'
@@ -47,8 +54,7 @@ import { generateRuntimeLegend } from './helpers/generateRuntimeLegend'
47
54
  import generateRuntimeData from './helpers/generateRuntimeData'
48
55
  import { reloadURLData } from './helpers/urlDataHelpers'
49
56
  import { observeMapSvgLoaded } from './helpers/mapObserverHelpers'
50
- import { buildSectionClassNames } from './helpers/componentHelpers'
51
- import { shouldShowDataTable } from './helpers/dataTableHelpers'
57
+ import { shouldShowDataTable, filterCountyTableRuntimeDataByStateCode } from './helpers/dataTableHelpers'
52
58
  import { prepareSmallMultiplesDataTable } from './helpers/smallMultiplesHelpers'
53
59
 
54
60
  // Child Components
@@ -66,7 +72,9 @@ import useLegendMemo from './hooks/useLegendMemo'
66
72
  import { LegendMemoProvider } from './context/LegendMemoContext'
67
73
  import { VizFilter } from '@cdc/core/types/VizFilter'
68
74
  import { getInitialState, mapReducer } from './store/map.reducer'
69
- import { RuntimeData } from './types/RuntimeData'
75
+ import defaults from './data/initial-state'
76
+ import { LEGACY_MAP_DEFAULTS } from './data/legacy-defaults'
77
+ import { backfillDefaults } from '@cdc/core/helpers/backfillDefaults'
70
78
  import EditorContext from '@cdc/core/contexts/EditorContext'
71
79
  import MapActions from './store/map.actions'
72
80
  import _ from 'lodash'
@@ -74,6 +82,9 @@ import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
74
82
  import useModal from './hooks/useModal'
75
83
  import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
76
84
  import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
85
+ import { ENABLE_CHART_MAP_TP5_TREATMENT } from '@cdc/core/helpers/constants'
86
+ import CalloutFlag from '@cdc/core/assets/callout-flag.svg?url'
87
+ import { useQueryParamsListener } from '@cdc/core/hooks/useQueryParamsListener'
77
88
 
78
89
  type CdcMapComponent = {
79
90
  config: MapConfig
@@ -104,6 +115,7 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
104
115
  datasets,
105
116
  interactionLabel = 'no link provided'
106
117
  }) => {
118
+ backfillDefaults(configObj, defaults, LEGACY_MAP_DEFAULTS)
107
119
  const initialState = getInitialState(configObj)
108
120
 
109
121
  const [mapState, dispatch] = useReducer<MapReducerType<MapState, MapActions>>(mapReducer, initialState as MapState)
@@ -118,6 +130,8 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
118
130
  modal,
119
131
  accessibleStatus,
120
132
  filteredCountryCode,
133
+ filteredCountyCode,
134
+ filteredStateCode,
121
135
  position,
122
136
  scale,
123
137
  translate,
@@ -139,7 +153,9 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
139
153
  }
140
154
 
141
155
  useEffect(() => {
142
- const _newConfig = getInitialState(cloneConfig(configObj)).config
156
+ const configClone = cloneConfig(configObj)
157
+ backfillDefaults(configClone, defaults, LEGACY_MAP_DEFAULTS)
158
+ const _newConfig = getInitialState(configClone).config
143
159
  if (configObj.data) {
144
160
  _newConfig.data = configObj.data
145
161
  }
@@ -156,8 +172,24 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
156
172
  }
157
173
  }
158
174
 
175
+ const setFilters = (filters: VizFilter[]) => {
176
+ const filterCopy = _.cloneDeep(filters)
177
+ if (config.general.showStateDropdown) {
178
+ const [stateFilter, countyFilter] = filterCopy.filter(
179
+ f => f.staticFilter && ['state', 'county'].includes(f.columnName)
180
+ )
181
+ const stateCode = (stateFilter?.active as string) || ''
182
+ const countyCode = (countyFilter?.active as string) || ''
183
+
184
+ setFilteredStateCountyCode(stateCode, countyCode)
185
+ if (countyFilter) filterCopy.pop() // remove county filter
186
+ filterCopy.pop() // remove state filter
187
+ }
188
+ _setRuntimeData(filterCopy)
189
+ }
190
+
159
191
  // Refs
160
- const innerContainerRef = useRef()
192
+ const innerContainerRef = useRef<HTMLDivElement | null>(null)
161
193
  const legendRef = useRef(null)
162
194
  const mapSvg = useRef(null)
163
195
  const tooltipRef = useRef(null)
@@ -290,7 +322,12 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
290
322
  // Combine viz filters with dashboard filters for markup processing
291
323
  const combinedFilters = [...(config.filters || []), ...(config.dashboardFilters || [])]
292
324
 
293
- const markupOptions = { isEditor, filters: combinedFilters }
325
+ const markupOptions = {
326
+ isEditor,
327
+ filters: combinedFilters,
328
+ locale: config.locale,
329
+ dataMetadata: config.dataMetadata
330
+ }
294
331
 
295
332
  if (title) {
296
333
  title = processMarkupVariables(title, config.data || [], config.markupVariables, markupOptions).processedContent
@@ -334,6 +371,43 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
334
371
  }
335
372
 
336
373
  if (!table.label || table.label === '') table.label = 'Data Table'
374
+ const isTp5Treatment = ENABLE_CHART_MAP_TP5_TREATMENT && config.visual?.tp5Treatment
375
+ const mapTitle = (
376
+ <Title
377
+ title={title}
378
+ superTitle={processedSuperTitle}
379
+ titleStyle={isTp5Treatment ? 'small' : general.titleStyle}
380
+ showTitle={general.showTitle}
381
+ config={config}
382
+ classes={['map-title', general.showTitle === true ? 'visible' : 'hidden', `${headerColor}`]}
383
+ />
384
+ )
385
+
386
+ const STATE_CODE = 'state-code'
387
+ const COUNTY_CODE = 'county-code'
388
+ const setFilteredStateCountyCode = (stateCode: string, countyCode?: string) => {
389
+ const stateCodePattern = /^\d\d$/
390
+ const normalizedStateCode = stateCodePattern.test(stateCode) ? stateCode : ''
391
+ let _countyCode = ''
392
+ if (countyCode) {
393
+ const countyCodePattern = /^\d{5}$/
394
+ _countyCode = countyCodePattern.test(countyCode) ? countyCode : ''
395
+ }
396
+ if (!normalizedStateCode) {
397
+ updateQueryParams({ [STATE_CODE]: '', [COUNTY_CODE]: '' })
398
+ } else {
399
+ updateQueryParams({ [STATE_CODE]: normalizedStateCode, [COUNTY_CODE]: _countyCode })
400
+ }
401
+ }
402
+
403
+ const setFilteredStateCodeFromQuery = ({
404
+ [STATE_CODE]: stateCode,
405
+ [COUNTY_CODE]: countyCode
406
+ }: Record<string, string>) => {
407
+ dispatch({ type: 'SET_FILTERED_STATE_COUNTY_CODE', payload: { stateCode, countyCode } })
408
+ }
409
+
410
+ useQueryParamsListener([STATE_CODE, COUNTY_CODE], setFilteredStateCodeFromQuery)
337
411
 
338
412
  const mapProps = {
339
413
  setParentConfig,
@@ -344,6 +418,8 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
344
418
  customNavigationHandler,
345
419
  dimensions,
346
420
  filteredCountryCode,
421
+ filteredCountyCode,
422
+ filteredStateCode,
347
423
  isDashboard,
348
424
  isEditor,
349
425
  logo,
@@ -355,6 +431,7 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
355
431
  runtimeLegend,
356
432
  scale,
357
433
  setConfig,
434
+ setFilteredStateCountyCode,
358
435
  setSharedFilter,
359
436
  setSharedFilterValue,
360
437
  config,
@@ -368,6 +445,34 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
368
445
  interactionLabel
369
446
  }
370
447
 
448
+ // Memoize data table preparation and county filtering to avoid recomputing on unrelated renders.
449
+ const { dataTableConfig, dataTableColumns, dataTableRuntimeData } = useMemo(() => {
450
+ let preparedConfig = config
451
+ let preparedColumns = columns
452
+ let preparedRuntimeData = runtimeData
453
+
454
+ if (config.smallMultiples?.mode) {
455
+ const prepared = prepareSmallMultiplesDataTable(config, columns, runtimeData)
456
+ preparedConfig = prepared.config
457
+ preparedColumns = prepared.columns
458
+ preparedRuntimeData = prepared.runtimeData
459
+ }
460
+
461
+ if (config.general.geoType === 'us-county' && filteredStateCode) {
462
+ preparedRuntimeData = filterCountyTableRuntimeDataByStateCode(
463
+ preparedRuntimeData,
464
+ filteredStateCode,
465
+ preparedConfig
466
+ )
467
+ }
468
+
469
+ return {
470
+ dataTableConfig: preparedConfig,
471
+ dataTableColumns: preparedColumns,
472
+ dataTableRuntimeData: preparedRuntimeData
473
+ }
474
+ }, [config, columns, runtimeData, filteredStateCode])
475
+
371
476
  if (!config.data) return <></>
372
477
 
373
478
  const tabId = handleMapTabbing(config, loading, legendId)
@@ -393,76 +498,181 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
393
498
  </a>
394
499
  )
395
500
 
396
- // Prepare data table props (pivot if small multiples mode is enabled)
397
- let dataTableConfig = config
398
- let dataTableColumns = columns
399
- let dataTableRuntimeData = runtimeData
400
- if (config.smallMultiples?.mode) {
401
- const prepared = prepareSmallMultiplesDataTable(config, columns, runtimeData)
402
- dataTableConfig = prepared.config
403
- dataTableColumns = prepared.columns
404
- dataTableRuntimeData = prepared.runtimeData
501
+ const applyStateFilter = (config: MapConfig): MapConfig => {
502
+ if (config.general.showStateDropdown && config.general.geoType === 'us-county') {
503
+ const stateFilter: VizFilter = {
504
+ columnName: 'state',
505
+ label: 'Select Location',
506
+ filterStyle: 'dropdown',
507
+ labels: supportedStatesFipsCodes,
508
+ values: Object.keys(supportedStatesFipsCodes),
509
+ resetLabel: 'United States',
510
+ staticFilter: true,
511
+ active: filteredStateCode
512
+ }
513
+ let countyFilter: VizFilter | undefined
514
+ if (filteredStateCode) {
515
+ const counties = Object.keys(supportedCounties).filter(countyCode => countyCode.startsWith(filteredStateCode))
516
+ countyFilter = {
517
+ columnName: 'county',
518
+ label: 'Select County',
519
+ filterStyle: 'dropdown',
520
+ labels: supportedCounties,
521
+ values: counties,
522
+ resetLabel: 'All Counties',
523
+ staticFilter: true,
524
+ active: filteredCountyCode
525
+ }
526
+ }
527
+ return {
528
+ ...config,
529
+ filters: [...(config.filters || []), stateFilter, ...(countyFilter ? [countyFilter] : [])]
530
+ }
531
+ }
532
+ return config
405
533
  }
406
534
 
407
535
  return (
408
536
  <LegendMemoProvider legendMemo={legendMemo} legendSpecialClassLastMemo={legendSpecialClassLastMemo}>
409
537
  <ConfigContext.Provider value={mapProps}>
410
538
  <MapDispatchContext.Provider value={dispatch}>
411
- <Layout.VisualizationWrapper
539
+ <VisualizationContainer
412
540
  config={config}
413
541
  isEditor={isEditor}
414
542
  ref={outerContainerRef}
415
543
  currentViewport={currentViewport}
416
544
  imageId={imageId}
417
- showEditorPanel={config.showEditorPanel}
545
+ editorPanel={<EditorPanel datasets={datasets} />}
418
546
  >
419
- {isEditor && <EditorPanel datasets={datasets} />}
420
- <Layout.Responsive isEditor={isEditor}>
421
- {requiredColumns?.length > 0 && (
422
- <Waiting requiredColumns={requiredColumns} className={displayPanel ? `waiting` : `waiting collapsed`} />
423
- )}
424
- {!runtimeData.init && (general.type === 'navigation' || runtimeLegend) && (
425
- <section
426
- className={buildSectionClassNames(
427
- currentViewport,
428
- headerColor,
429
- config?.runtime?.editorErrorMessage.length > 0
430
- )}
431
- aria-label={'Map: ' + title}
432
- ref={innerContainerRef}
433
- >
434
- {config?.runtime?.editorErrorMessage.length > 0 && <Error />}
435
- <Title
436
- title={title}
437
- superTitle={processedSuperTitle}
438
- titleStyle={general.titleStyle}
439
- showTitle={general.showTitle}
440
- config={config}
441
- classes={['map-title', general.showTitle === true ? 'visible' : 'hidden', `${headerColor}`]}
442
- />
443
- <SkipTo skipId={tabId} skipMessage='Skip Over Map Container' />
444
- {config?.annotations?.length > 0 && (
445
- <SkipTo skipId={tabId} skipMessage={`Skip over annotations`} key={`skip-annotations`} />
446
- )}
447
-
448
- {processedIntroText && <section className='introText mb-4'>{parse(processedIntroText)}</section>}
449
-
450
- {config?.filters?.length > 0 && (
547
+ {requiredColumns?.length > 0 && (
548
+ <Waiting requiredColumns={requiredColumns} className={displayPanel ? `waiting` : `waiting collapsed`} />
549
+ )}
550
+ {!runtimeData.init && (general.type === 'navigation' || runtimeLegend) && (
551
+ <VisualizationContent
552
+ innerClassName={[
553
+ 'cdc-map-inner-container',
554
+ currentViewport,
555
+ config?.runtime?.editorErrorMessage.length > 0 ? 'type-map--has-error' : ''
556
+ ]
557
+ .filter(Boolean)
558
+ .join(' ')}
559
+ innerProps={{ 'aria-label': 'Map: ' + title, ref: innerContainerRef }}
560
+ bodyWrapClassName={isTp5Treatment ? 'cdc-callout d-flex flex-column' : ''}
561
+ bodyClassName={[
562
+ !config.visual?.border || isTp5Treatment ? 'no-borders' : '',
563
+ config.visual?.border && !isTp5Treatment ? 'component--has-legacy-border' : '',
564
+ config.visual?.borderColorTheme ? 'component--has-border-color-theme' : '',
565
+ config.visual?.accent ? 'component--has-accent' : '',
566
+ config.visual?.background ? 'component--has-background' : '',
567
+ config.visual?.hideBackgroundColor ? 'component--hide-background-color' : '',
568
+ isTp5Treatment ? 'component--tp5-treatment' : ''
569
+ ]
570
+ .filter(Boolean)
571
+ .join(' ')}
572
+ filters={
573
+ config?.filters?.length > 0 || config.general.showStateDropdown ? (
451
574
  <Filters
452
- config={config}
453
- setConfig={setConfig}
454
- filteredData={runtimeFilters}
455
- setFilters={_setRuntimeData}
575
+ config={applyStateFilter(config)}
576
+ setFilters={setFilters}
456
577
  dimensions={dimensions}
457
- standaloneMap={!config}
458
578
  interactionLabel={interactionLabel}
459
579
  />
580
+ ) : undefined
581
+ }
582
+ bodySubtext={
583
+ processedSubtext.length > 0 ? <p className='subtext cove-prose'>{parse(processedSubtext)}</p> : null
584
+ }
585
+ bodyFooter={
586
+ <>
587
+ {isDashboard && config.table?.forceDisplay && config.table.showDataTableLink
588
+ ? tableLink
589
+ : link && link}
590
+
591
+ {shouldShowDataTable(config, table, general, loading) ? (
592
+ <DataTable
593
+ columns={dataTableColumns}
594
+ config={dataTableConfig}
595
+ currentViewport={currentViewport}
596
+ displayGeoName={displayGeoName}
597
+ expandDataTable={table.expanded}
598
+ formatLegendLocation={key =>
599
+ formatLegendLocation(key, dataTableRuntimeData?.[key]?.[config.columns.geo.name])
600
+ }
601
+ imageRef={imageId}
602
+ indexTitle={table.indexLabel}
603
+ innerContainerRef={innerContainerRef}
604
+ legendMemo={legendMemo}
605
+ legendSpecialClassLastMemo={legendSpecialClassLastMemo}
606
+ navigationHandler={navigationHandler}
607
+ outerContainerRef={outerContainerRef}
608
+ rawData={dataTableConfig.data}
609
+ runtimeData={dataTableRuntimeData}
610
+ runtimeLegend={runtimeLegend}
611
+ showDownloadImgButton={showDownloadImgButton}
612
+ showDownloadPdfButton={showDownloadPdfButton}
613
+ includeContextInDownload={config.general?.includeContextInDownload}
614
+ tabbingId={tabId}
615
+ tableTitle={table.label}
616
+ vizTitle={general.title}
617
+ applyLegendToRow={applyLegendToRow}
618
+ getPatternForRow={getPatternForRow}
619
+ wrapColumns={table.wrapColumns}
620
+ hasSubtextAbove={processedSubtext.length > 0}
621
+ interactionLabel={interactionLabel}
622
+ />
623
+ ) : (
624
+ (showDownloadImgButton || showDownloadPdfButton) && (
625
+ <div className='w-100 d-flex justify-content-end'>
626
+ <MediaControls.Section classes={['download-links', 'mt-4', 'mb-2']}>
627
+ {showDownloadImgButton && (
628
+ <MediaControls.DownloadLink
629
+ type='image'
630
+ title='Download Map as Image'
631
+ state={config}
632
+ elementToCapture={imageId}
633
+ interactionLabel={interactionLabel}
634
+ includeContextInDownload={config.general?.includeContextInDownload}
635
+ />
636
+ )}
637
+ {showDownloadPdfButton && (
638
+ <MediaControls.DownloadLink
639
+ type='pdf'
640
+ title='Download Map as PDF'
641
+ state={config}
642
+ elementToCapture={imageId}
643
+ interactionLabel={interactionLabel}
644
+ includeContextInDownload={config.general?.includeContextInDownload}
645
+ />
646
+ )}
647
+ </MediaControls.Section>
648
+ </div>
649
+ )
650
+ )}
651
+
652
+ {config.annotations?.length > 0 && <Annotation.Dropdown />}
653
+
654
+ {processedFootnotes && (
655
+ <section className='footnotes cove-prose pt-2 mt-4'>{parse(processedFootnotes)}</section>
656
+ )}
657
+ </>
658
+ }
659
+ header={isTp5Treatment ? null : mapTitle}
660
+ messageIsIntroText={!!processedIntroText}
661
+ message={processedIntroText ? <div className='cove-prose'>{parse(processedIntroText)}</div> : null}
662
+ >
663
+ <>
664
+ {isTp5Treatment && <img src={CalloutFlag} alt='' className='cdc-callout__flag' aria-hidden='true' />}
665
+ {isTp5Treatment && mapTitle}
666
+ {config?.runtime?.editorErrorMessage.length > 0 && <Error />}
667
+ <SkipTo skipId={tabId} skipMessage='Skip Over Map Container' />
668
+ {config?.annotations?.length > 0 && (
669
+ <SkipTo skipId={tabId} skipMessage={`Skip over annotations`} key={`skip-annotations`} />
460
670
  )}
461
671
 
462
672
  <div
463
673
  role='region'
464
674
  tabIndex={0}
465
- className={getMapContainerClasses(config, modal).join(' ')}
675
+ className={getMapContainerClasses(config, modal, currentViewport).join(' ')}
466
676
  onClick={e => closeModal(e, modal)}
467
677
  onKeyDown={e => {
468
678
  if (e.key === 'Enter') {
@@ -502,114 +712,42 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
502
712
  navigationHandler={val => navigationHandler('_blank', val, customNavigationHandler)}
503
713
  />
504
714
  )}
505
-
506
- {/* Link (to data table?) */}
507
- {isDashboard && config.table?.forceDisplay && config.table.showDataTableLink
508
- ? tableLink
509
- : link && link}
510
-
511
- {processedSubtext.length > 0 && <p className='subtext mt-4'>{parse(processedSubtext)}</p>}
512
-
513
- {/* Data Table or Download Links */}
514
- {shouldShowDataTable(config, table, general, loading) ? (
515
- <DataTable
516
- columns={dataTableColumns}
517
- config={dataTableConfig}
518
- currentViewport={currentViewport}
519
- displayGeoName={displayGeoName}
520
- expandDataTable={table.expanded}
521
- formatLegendLocation={key =>
522
- formatLegendLocation(key, dataTableRuntimeData?.[key]?.[config.columns.geo.name])
523
- }
524
- headerColor={general.headerColor}
525
- imageRef={imageId}
526
- indexTitle={table.indexLabel}
527
- innerContainerRef={innerContainerRef}
528
- legendMemo={legendMemo}
529
- legendSpecialClassLastMemo={legendSpecialClassLastMemo}
530
- navigationHandler={navigationHandler}
531
- outerContainerRef={outerContainerRef}
532
- rawData={dataTableConfig.data}
533
- runtimeData={dataTableRuntimeData}
534
- runtimeLegend={runtimeLegend}
535
- showDownloadImgButton={showDownloadImgButton}
536
- showDownloadPdfButton={showDownloadPdfButton}
537
- includeContextInDownload={config.general?.includeContextInDownload}
538
- tabbingId={tabId}
539
- tableTitle={table.label}
540
- vizTitle={general.title}
541
- applyLegendToRow={applyLegendToRow}
542
- getPatternForRow={getPatternForRow}
543
- wrapColumns={table.wrapColumns}
544
- interactionLabel={interactionLabel}
545
- />
546
- ) : (
547
- (showDownloadImgButton || showDownloadPdfButton) && (
548
- <div className='w-100 d-flex justify-content-end'>
549
- <MediaControls.Section classes={['download-links', 'mt-4', 'mb-2']}>
550
- {showDownloadImgButton && (
551
- <MediaControls.DownloadLink
552
- type='image'
553
- title='Download Map as Image'
554
- state={config}
555
- elementToCapture={imageId}
556
- interactionLabel={interactionLabel}
557
- includeContextInDownload={config.general?.includeContextInDownload}
558
- />
559
- )}
560
- {showDownloadPdfButton && (
561
- <MediaControls.DownloadLink
562
- type='pdf'
563
- title='Download Map as PDF'
564
- state={config}
565
- elementToCapture={imageId}
566
- interactionLabel={interactionLabel}
567
- includeContextInDownload={config.general?.includeContextInDownload}
568
- />
569
- )}
570
- </MediaControls.Section>
571
- </div>
572
- )
573
- )}
574
-
575
- {config.annotations?.length > 0 && <Annotation.Dropdown />}
576
-
577
- {processedFootnotes && <section className='footnotes pt-2 mt-4'>{parse(processedFootnotes)}</section>}
578
- </section>
579
- )}
580
-
581
- <div aria-live='assertive' className='cdcdataviz-sr-only'>
582
- {accessibleStatus}
583
- </div>
584
-
585
- {!isDraggingAnnotation && 'hover' === tooltips.appearanceType && (
586
- <ReactTooltip
587
- id={`tooltip__${tooltipId}`}
588
- float={true}
589
- className={`tooltip tooltip-test`}
590
- style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }}
591
- />
592
- )}
593
- <div
594
- ref={tooltipRef}
595
- id={`tooltip__${tooltipId}-canvas`}
596
- className='tooltip'
597
- style={{
598
- background: `rgba(255,255,255,${config.tooltips.opacity / 100})`,
599
- position: 'absolute',
600
- whiteSpace: 'nowrap',
601
- display: 'none' // can't use d-none here
602
- }}
603
- ></div>
604
- <FootnotesStandAlone
605
- config={config.footnotes}
606
- filters={config.filters?.filter(f => f.filterFootnotes)}
607
- markupVariables={config.markupVariables}
608
- enableMarkupVariables={config.enableMarkupVariables}
609
- data={config.data}
715
+ </>
716
+ </VisualizationContent>
717
+ )}
718
+
719
+ <div aria-live='assertive' className='cdcdataviz-sr-only'>
720
+ {accessibleStatus}
721
+ </div>
722
+
723
+ {!isDraggingAnnotation && 'hover' === tooltips.appearanceType && (
724
+ <ReactTooltip
725
+ id={`tooltip__${tooltipId}`}
726
+ float={true}
727
+ className={`tooltip tooltip-test`}
728
+ style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }}
610
729
  />
611
- </Layout.Responsive>
612
- </Layout.VisualizationWrapper>
730
+ )}
731
+ <div
732
+ ref={tooltipRef}
733
+ id={`tooltip__${tooltipId}-canvas`}
734
+ className='tooltip'
735
+ style={{
736
+ background: `rgba(255,255,255,${config.tooltips.opacity / 100})`,
737
+ position: 'absolute',
738
+ whiteSpace: 'nowrap',
739
+ display: 'none' // can't use d-none here
740
+ }}
741
+ ></div>
742
+ <FootnotesStandAlone
743
+ config={config.footnotes}
744
+ filters={config.filters?.filter(f => f.filterFootnotes)}
745
+ markupVariables={config.markupVariables}
746
+ enableMarkupVariables={config.enableMarkupVariables}
747
+ data={config.data}
748
+ dataMetadata={config.dataMetadata}
749
+ />
750
+ </VisualizationContainer>
613
751
  </MapDispatchContext.Provider>
614
752
  </ConfigContext.Provider>
615
753
  </LegendMemoProvider>