@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
@@ -0,0 +1,38 @@
1
+ export const handleMapAriaLabels = (state: MapConfig = '', testing = false) => {
2
+ if (testing) console.log(`handleMapAriaLabels Testing On: ${state}`) // eslint-disable-line
3
+ try {
4
+ if (!state.general.geoType) throw Error('handleMapAriaLabels: no geoType found in state')
5
+ const {
6
+ general: { title, geoType, statePicked }
7
+ } = state
8
+ let ariaLabel = ''
9
+ switch (geoType) {
10
+ case 'world':
11
+ ariaLabel += 'World map'
12
+ break
13
+ case 'us':
14
+ ariaLabel += 'United States map'
15
+ break
16
+ case 'us-county':
17
+ ariaLabel += `United States county map`
18
+ break
19
+ case 'single-state':
20
+ ariaLabel += `${statePicked.stateName} county map`
21
+ break
22
+ case 'us-region':
23
+ ariaLabel += `United States HHS Region map`
24
+ break
25
+ default:
26
+ ariaLabel = 'Data visualization container'
27
+ break
28
+ }
29
+
30
+ if (title) {
31
+ ariaLabel += ` with the title: ${title}`
32
+ }
33
+
34
+ return ariaLabel
35
+ } catch (e) {
36
+ console.error('COVE: ', e.message) // eslint-disable-line
37
+ }
38
+ }
@@ -0,0 +1,8 @@
1
+ export const indexOfIgnoreType = (arr, item) => {
2
+ for (let i = 0; i < arr.length; i++) {
3
+ if (item === arr[i]) {
4
+ return i
5
+ }
6
+ }
7
+ return -1
8
+ }
@@ -0,0 +1,21 @@
1
+ export const navigationHandler = (
2
+ navigationTarget: '_self' | '_blank',
3
+ urlString: string,
4
+ customNavigationHandler?: Function
5
+ ): void => {
6
+ // Call custom navigation method if passed
7
+ if (customNavigationHandler) {
8
+ customNavigationHandler(urlString)
9
+ return
10
+ }
11
+
12
+ // Abort if value is blank
13
+ if (0 === urlString.length) {
14
+ throw Error('Blank string passed as URL. Navigation aborted.')
15
+ }
16
+
17
+ const urlObj = new URL(urlString, window.location.origin)
18
+
19
+ // Open constructed link in new tab/window
20
+ window.open(urlObj.toString(), navigationTarget)
21
+ }
@@ -0,0 +1,44 @@
1
+ // if city has a hyphen then in tooltip it ends up UPPER CASE instead of just regular Upper Case
2
+ // - this function is used to prevent that and instead give the formatting that is wanted
3
+ // Example: Desired city display in tooltip on map: "Inter-Tribal Indian Reservation"
4
+ export const titleCase = string => {
5
+ // guard clause else error in editor
6
+ if (!string) return
7
+
8
+ if (string !== undefined) {
9
+ const toTitleCase = word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase()
10
+
11
+ if (string.toUpperCase().includes('U.S.') || string.toUpperCase().includes('US')) {
12
+ return string
13
+ .split(' ')
14
+ .map(word => {
15
+ if (word.toUpperCase() === 'U.S.' || word.toUpperCase() === 'US') {
16
+ return word.toUpperCase()
17
+ } else {
18
+ return toTitleCase(word)
19
+ }
20
+ })
21
+ .join(' ')
22
+ }
23
+ // if hyphen found, then split, uppercase each word, and put back together
24
+ if (string.includes('–') || string.includes('-')) {
25
+ let dashSplit = string.includes('–') ? string.split('–') : string.split('-') // determine hyphen or en dash to split on
26
+ let splitCharacter = string.includes('–') ? '–' : '-' // print hyphen or en dash later on.
27
+ let frontSplit = dashSplit[0]
28
+ .split(' ')
29
+ .map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
30
+ .join(' ')
31
+ let backSplit = dashSplit[1]
32
+ .split(' ')
33
+ .map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
34
+ .join(' ')
35
+ return frontSplit + splitCharacter + backSplit
36
+ } else {
37
+ // just return with each word uppercase
38
+ return string
39
+ .split(' ')
40
+ .map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
41
+ .join(' ')
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,30 @@
1
+ import { MapConfig } from '../types/MapConfig'
2
+
3
+ /**
4
+ * Validates and ensures proper formatting of FIPS codes in the provided `MapConfig` object.
5
+ *
6
+ * - If the `geoType` is "us-county" or "us" with associated data:
7
+ * - Iterates through each data entry in `newState.data`.
8
+ * - Checks if the value in the `geo` column is numeric and has a length of 4.
9
+ * - If so, prepends a `0` to ensure a valid 5-digit FIPS code.
10
+ * - Converts the `geo` column value to a string for consistency.
11
+ *
12
+ * @param newState - The state object containing map data and configuration.
13
+ * @returns The updated state object with corrected FIPS code formatting.
14
+ */
15
+ export const validateFipsCodeLength = (newState: MapConfig) => {
16
+ if (newState.general.geoType === 'us-county' || (newState.general.geoType === 'us' && newState?.data)) {
17
+ newState?.data.forEach(dataPiece => {
18
+ if (dataPiece[newState.columns.geo.name]) {
19
+ if (
20
+ !isNaN(parseInt(dataPiece[newState.columns.geo.name])) &&
21
+ dataPiece[newState.columns.geo.name].length === 4
22
+ ) {
23
+ dataPiece[newState.columns.geo.name] = 0 + dataPiece[newState.columns.geo.name]
24
+ }
25
+ dataPiece[newState.columns.geo.name] = dataPiece[newState.columns.geo.name].toString()
26
+ }
27
+ })
28
+ }
29
+ return newState
30
+ }
@@ -0,0 +1,42 @@
1
+ import { useState, useCallback, useEffect } from 'react'
2
+ import { type ViewPort } from '@cdc/core/types/ViewPort'
3
+ import { type DimensionsType } from '@cdc/core/types/Dimensions'
4
+ import getViewport from '@cdc/core/helpers/getViewport'
5
+ import ResizeObserver from 'resize-observer-polyfill'
6
+
7
+ export const useResizeObserver = (isEditor: boolean) => {
8
+ const [dimensions, setDimensions] = useState<DimensionsType>([0, 0])
9
+ const [currentViewport, setCurrentViewport] = useState<ViewPort>(null)
10
+ const [container, setContainer] = useState<HTMLElement | null>(null)
11
+
12
+ const resizeObserver = new ResizeObserver(entries => {
13
+ for (let entry of entries) {
14
+ let { width, height } = entry.contentRect
15
+ let newViewport = getViewport(entry.contentRect.width)
16
+
17
+ let editorWidth = 350
18
+
19
+ setCurrentViewport(newViewport)
20
+
21
+ if (isEditor) {
22
+ width = width - editorWidth
23
+ }
24
+ setDimensions([width, height])
25
+ }
26
+ })
27
+
28
+ const outerContainerRef = useCallback(node => {
29
+ if (node !== null) {
30
+ resizeObserver.observe(node)
31
+ }
32
+ setContainer(node)
33
+
34
+ return () => {
35
+ resizeObserver.disconnect()
36
+ }
37
+ }, [])
38
+
39
+ return { resizeObserver, dimensions, currentViewport, outerContainerRef, container }
40
+ }
41
+
42
+ export default useResizeObserver
@@ -1,5 +1,7 @@
1
+ import { displayDataAsText } from '../../../core/helpers/displayDataAsText'
2
+
1
3
  const useTooltip = props => {
2
- const { state, displayGeoName, displayDataAsText, supportedStatesFipsCodes } = props
4
+ const { state, displayGeoName, supportedStatesFipsCodes } = props
3
5
 
4
6
  const config = state
5
7
 
@@ -105,7 +107,7 @@ const useTooltip = props => {
105
107
  let tooltipValue = handleTooltipSpecialClassText(specialClasses, column, row, '', columnKey)
106
108
 
107
109
  if (!tooltipValue) {
108
- tooltipValue = row ? displayDataAsText(row[column.name], columnKey) : 'No Data'
110
+ tooltipValue = row ? displayDataAsText(row[column.name], columnKey, state) : 'No Data'
109
111
  }
110
112
 
111
113
  toolTipText += handleTooltipPrimaryColumn(tooltipValue, column)
package/src/index.jsx CHANGED
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client'
3
3
 
4
4
  import CdcMap from './CdcMap'
5
5
 
6
+ import '@cdc/core/styles/cove-main.scss'
6
7
  import 'react-tooltip/dist/react-tooltip.css'
7
8
  import './coreStyles_map.scss'
8
9
 
@@ -17,8 +17,9 @@
17
17
  .cdc-open-viz-module {
18
18
  .geo-buttons {
19
19
  list-style: none;
20
- display: flex;
21
20
  color: var(--mediumGray);
21
+ display: grid;
22
+ button { width: 100% !important; }
22
23
  svg {
23
24
  display: block;
24
25
  max-width: 80px;
@@ -12,7 +12,6 @@
12
12
  label {
13
13
  display: inherit;
14
14
  margin-bottom: 5px;
15
- font-weight: 600;
16
15
  font-size: 16px;
17
16
  }
18
17
  }
@@ -22,10 +21,6 @@
22
21
  flex-wrap: wrap;
23
22
  }
24
23
 
25
- label:not(:empty) {
26
- margin-right: 0.4em;
27
- }
28
-
29
24
  .single-filter {
30
25
  margin-bottom: 0.5em;
31
26
  }
@@ -48,7 +48,6 @@
48
48
  .cdc-map-inner-container {
49
49
  @import './map';
50
50
  flex-grow: 1;
51
- text-rendering: geometricPrecision;
52
51
  color: #202020;
53
52
  border: 0;
54
53
  text-align: left;
@@ -81,10 +80,7 @@
81
80
  content: ' ';
82
81
  position: absolute;
83
82
  top: 0;
84
- left: -1em;
85
- right: -1em;
86
83
  bottom: 0;
87
- background: rgba(0, 0, 0, 0.05);
88
84
  z-index: 7;
89
85
  }
90
86
  .modal-content {
@@ -94,71 +90,72 @@
94
90
  top: 50%;
95
91
  left: 50%;
96
92
  display: flex;
97
- flex-direction: row;
98
- border-radius: 5px;
93
+ flex-direction: column;
99
94
  transform: translate(-50%, -50%);
100
- border: rgba(0, 0, 0, 0.3) 1px solid;
101
- box-shadow: rgba(0, 0, 0, 0.2) 3px 3px 7px;
102
- opacity: 1;
103
- line-height: 1.4em;
104
- font-size: 1rem;
105
- border-radius: 4px;
95
+ border: 1px solid rgba(0, 0, 0, 0.3);
96
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
97
+ border-radius: 5px;
98
+ padding: 16px 40px;
106
99
  min-width: 250px;
107
- padding: 16px 40px 16px 20px;
108
100
  width: auto;
109
- .content {
110
- flex-grow: 1;
111
- }
112
- .legend-item {
113
- margin-right: 0.75em;
114
- margin-top: 3px;
115
- flex-shrink: 0;
116
- }
117
- @include breakpointClass(sm) {
118
- transform: translate(-50%, -100%);
119
- }
120
- @include breakpointClass(md) {
121
- transform: translate(-50%, -120%);
122
- }
123
- @include breakpointClass(lg) {
124
- font-size: 0.9em;
125
- min-width: 300px;
126
- .legend-item {
127
- height: 1.3em;
128
- width: 1.3em;
129
- }
130
- }
131
- strong {
132
- font-weight: 600;
133
- font-size: 1.2em;
134
- }
135
- .modal-close {
136
- position: absolute;
137
- right: 20px;
138
- top: 18px;
139
- cursor: pointer;
140
- width: 1em;
141
- }
142
- a.navigation-link {
143
- text-decoration: underline;
144
- cursor: pointer;
145
- color: #075290;
146
- display: flex;
147
- svg {
148
- display: inline-block;
149
- max-width: 13px;
150
- margin-left: 0.5em;
151
- }
152
- }
153
- &.capitalize p {
154
- text-transform: capitalize;
101
+ max-height: 90vh; /* Constrain the modal's height to 90% of the viewport */
102
+ overflow-y: auto; /* Enable vertical scrolling if content overflows */
103
+ font-size: 1rem;
104
+ line-height: 1.4em;
105
+ }
106
+
107
+ .modal-content .content {
108
+ flex-grow: 1;
109
+ }
110
+
111
+ .modal-content .legend-item {
112
+ margin-right: 0.75em;
113
+ margin-top: 3px;
114
+ flex-shrink: 0;
115
+ }
116
+
117
+ .modal-content strong {
118
+ font-weight: 600;
119
+ font-size: 1.2em;
120
+ }
121
+
122
+ .modal-content .modal-close {
123
+ position: absolute;
124
+ right: 20px;
125
+ top: 18px;
126
+ cursor: pointer;
127
+ width: 1em;
128
+ }
129
+
130
+ .modal-content a.navigation-link {
131
+ text-decoration: underline;
132
+ cursor: pointer;
133
+ color: #075290;
134
+ display: flex;
135
+ }
136
+
137
+ .modal-content a.navigation-link svg {
138
+ display: inline-block;
139
+ max-width: 13px;
140
+ margin-left: 0.5em;
141
+ }
142
+
143
+ .modal-content.capitalize p {
144
+ text-transform: capitalize;
145
+ }
146
+
147
+ /* Responsive adjustments for smaller screens */
148
+ @media (max-width: 1048px) {
149
+ .modal-content {
150
+ width: 90%; /* Adjust width to fit smaller screens */
151
+ top: 10%; /* Offset from the top for better usability */
152
+ transform: translate(-50%, 0); /* Remove vertical centering */
155
153
  }
156
154
  }
157
155
  }
158
156
  }
159
157
 
160
158
  p.subtext {
161
- font-size: 0.9em;
162
159
  em {
163
160
  font-style: italic;
164
161
  }
@@ -168,7 +165,6 @@
168
165
  }
169
166
 
170
167
  span.legend-item {
171
- margin-right: 5px;
172
168
  border-radius: 300px;
173
169
  vertical-align: middle;
174
170
  display: inline-block;
package/src/scss/map.scss CHANGED
@@ -75,7 +75,6 @@ $medium: 768px;
75
75
  position: relative;
76
76
  flex-grow: 1;
77
77
  width: 100%;
78
- overflow: hidden;
79
78
  .geo-point {
80
79
  transition: 0.3s all;
81
80
  circle {
@@ -102,10 +101,7 @@ $medium: 768px;
102
101
  }
103
102
 
104
103
  .territories-label {
105
- color: black;
106
- font-size: 1.1em;
107
- display: block;
108
- margin-top: 15px;
104
+ font-size: 1em;
109
105
  }
110
106
 
111
107
  // Cities and Territories
@@ -194,7 +190,6 @@ $medium: 768px;
194
190
  transform: none;
195
191
  }
196
192
  .territories {
197
- font-size: 1em;
198
193
  > span {
199
194
  margin-left: 0;
200
195
  }
@@ -327,10 +322,3 @@ canvas {
327
322
  pointer-events: none;
328
323
  display: none;
329
324
  }
330
-
331
- .data-table-container {
332
- margin: 20px 0px 0px;
333
- &.download-link-above {
334
- margin-top: 0;
335
- }
336
- }
@@ -1,8 +1,8 @@
1
- import { ComponentThemes } from '@cdc/core/types/ComponentThemes'
2
- import { Visualization } from '@cdc/core/types/Visualization'
3
- import { EditorColumnProperties } from '@cdc/core/types/EditorColumnProperties'
4
- import { Version } from '@cdc/core/types/Version'
5
- import { VizFilter } from '@cdc/core/types/VizFilter'
1
+ import { type ComponentThemes } from '@cdc/core/types/ComponentThemes'
2
+ import { type Visualization } from '@cdc/core/types/Visualization'
3
+ import { type EditorColumnProperties } from '@cdc/core/types/EditorColumnProperties'
4
+ import { type Version } from '@cdc/core/types/Version'
5
+ import { type VizFilter } from '@cdc/core/types/VizFilter'
6
6
 
7
7
  export type MapVisualSettings = {
8
8
  /** minBubbleSize - Minimum Circle Size when the map has a type of bubble */
@@ -16,9 +16,8 @@ export type MapVisualSettings = {
16
16
  /** cityStyle - optional visual indicator of label on the Legend */
17
17
  cityStyleLabel: string
18
18
  /** geoCodeCircleSize - controls the size of the city style option (circle or pin) */
19
-
20
19
  geoCodeCircleSize: number
21
- /** showBubbleZeros - shows circles on maps when the data is provided even if its a zero value */
20
+ /** showBubbleZeros - shows circles on maps when the data is provided even if it's a zero value */
22
21
  showBubbleZeros: boolean
23
22
  /** additionalCityStyles - shows Circle, Square, Triangle, Rhombus/Diamond, Star, Map Pin on maps when the additionalCityStyles is added */
24
23
  additionalCityStyles: [] | [{ label: string; column: string; value: string; shape: string }]
@@ -47,7 +46,7 @@ export type PrimaryColumnProperties = Pick<
47
46
  EditorColumnProperties,
48
47
  'dataTable' | 'label' | 'name' | 'prefix' | 'suffix' | 'tooltip'
49
48
  >
50
- export type ViewportSize = 'xxs' | 'xs' | 'sm' | 'md' | 'lg'
49
+
51
50
  export type LegendShapeItem = {
52
51
  column: string
53
52
  key: string
@@ -78,7 +77,7 @@ export type MapConfig = Visualization & {
78
77
  navigate: NavigateColumnProperties
79
78
  latitude: LatitudeColumnProperties
80
79
  longitude: LongitudeColumnProperties
81
- categorical: { name }
80
+ categorical: { name: string }
82
81
  }
83
82
  dataUrl: string
84
83
  runtimeDataUrl: string
@@ -91,7 +90,16 @@ export type MapConfig = Visualization & {
91
90
  fullBorder: boolean
92
91
  geoBorderColor: string
93
92
  geoLabelOverride: string
94
- geoType: 'us' | 'us-county' | 'world'
93
+ geoType:
94
+ | 'us'
95
+ | 'us-region'
96
+ | 'us-county'
97
+ | 'world'
98
+ | 'us-geocode'
99
+ | 'world-geocode'
100
+ | 'bubble'
101
+ | 'single-state'
102
+ | 'google-map'
95
103
  hasRegions: boolean
96
104
  headerColor: ComponentThemes
97
105
  hideGeoColumnInTooltip: boolean
@@ -151,7 +159,7 @@ export type MapConfig = Visualization & {
151
159
  tooltips: {
152
160
  appearanceType: 'hover' | 'click'
153
161
  linkLabel: string
154
- capitalizeLabels: true
162
+ capitalizeLabels: boolean
155
163
  opacity: number
156
164
  }
157
165
  runtime: {
@@ -1,11 +1,10 @@
1
- import { type MapConfig, type ViewportSize } from './MapConfig'
1
+ import { type MapConfig } from './MapConfig'
2
+ import { type ViewPort } from '@cdc/core/types/ViewPort'
2
3
 
3
4
  export type MapContext = {
4
5
  applyLegendToRow
5
6
  applyTooltipsToGeo
6
- closeModal
7
- columnsInData
8
- currentViewport: ViewportSize
7
+ currentViewport: ViewPort
9
8
  data
10
9
  displayDataAsText
11
10
  displayGeoName
@@ -16,7 +15,6 @@ export type MapContext = {
16
15
  handleCircleClick: Function
17
16
  handleDragStateChange: Function
18
17
  isDraggingAnnotation: boolean
19
- handleMapAriaLabels
20
18
  hasZoom
21
19
  innerContainerRef
22
20
  isDashboard
@@ -25,7 +23,6 @@ export type MapContext = {
25
23
  isFilterValueSupported: boolean
26
24
  loadConfig
27
25
  logo: string
28
- navigationHandler
29
26
  position
30
27
  resetLegendToggles
31
28
  runtimeFilters
@@ -38,13 +35,8 @@ export type MapContext = {
38
35
  setRuntimeFilters
39
36
  setRuntimeLegend
40
37
  setSharedFilterValue
41
- setState
38
+ setState: (newState: MapConfig) => MapConfig
42
39
  state: MapConfig
43
- supportedCities
44
- supportedCounties
45
- supportedCountries
46
- supportedTerritories
47
- titleCase
48
40
  viewport
49
41
  setStateToShow: (string) => void
50
42
  stateToShow: string