@cdc/map 4.25.3 → 4.25.6

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 (119) hide show
  1. package/.idea/map.iml +12 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/vcs.xml +6 -0
  4. package/dist/cdcmap.js +31254 -32242
  5. package/examples/hex-colors.json +3 -3
  6. package/examples/m2.json +32904 -0
  7. package/examples/private/test.json +470 -1457
  8. package/examples/private/{mmr.json → wastewatermap.json} +86 -115
  9. package/index.html +36 -63
  10. package/package.json +7 -19
  11. package/src/CdcMap.tsx +56 -1552
  12. package/src/CdcMapComponent.tsx +608 -0
  13. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +10 -0
  14. package/src/_stories/CdcMap.Legend.stories.tsx +67 -0
  15. package/src/_stories/CdcMap.Table.stories.tsx +19 -0
  16. package/src/_stories/CdcMap.stories.tsx +12 -1
  17. package/src/_stories/UsaMap.NoData.stories.tsx +4 -4
  18. package/src/_stories/_mock/default-patterns.json +8 -5
  19. package/src/_stories/_mock/legend-bins.json +428 -0
  20. package/{examples/private/default-patterns.json → src/_stories/_mock/legends/legend-tests.json} +36 -131
  21. package/src/cdcMapComponent.styles.css +9 -0
  22. package/src/components/Annotation/Annotation.Draggable.tsx +27 -26
  23. package/src/components/Annotation/AnnotationDropdown.tsx +5 -6
  24. package/src/components/BubbleList.tsx +135 -49
  25. package/src/components/CityList.tsx +89 -87
  26. package/src/components/DataTable.tsx +8 -8
  27. package/src/components/EditorPanel/components/EditorPanel.tsx +823 -885
  28. package/src/components/EditorPanel/components/Error.tsx +9 -2
  29. package/src/components/EditorPanel/components/HexShapeSettings.tsx +127 -141
  30. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +55 -86
  31. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +89 -75
  32. package/src/components/EditorPanel/components/editorPanel.styles.css +95 -0
  33. package/src/components/Geo.tsx +9 -1
  34. package/src/components/GoogleMap/components/GoogleMap.tsx +1 -1
  35. package/src/components/Legend/components/Legend.tsx +92 -87
  36. package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +128 -0
  37. package/src/components/Legend/components/LegendGroup/legend.group.css +27 -0
  38. package/src/components/Legend/components/LegendItem.Hex.tsx +4 -1
  39. package/src/components/Legend/components/index.scss +74 -17
  40. package/src/components/Modal.tsx +17 -7
  41. package/src/components/NavigationMenu.tsx +11 -9
  42. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +12 -8
  43. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +4 -4
  44. package/src/components/UsaMap/components/TerritoriesSection.tsx +33 -10
  45. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +12 -10
  46. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +12 -14
  47. package/src/components/UsaMap/components/Territory/TerritoryShape.ts +2 -1
  48. package/src/components/UsaMap/components/UsaMap.County.tsx +138 -96
  49. package/src/components/UsaMap/components/UsaMap.Region.styles.css +72 -0
  50. package/src/components/UsaMap/components/UsaMap.Region.tsx +56 -103
  51. package/src/components/UsaMap/components/UsaMap.SingleState.styles.css +10 -0
  52. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +65 -74
  53. package/src/components/UsaMap/components/UsaMap.State.tsx +112 -91
  54. package/src/components/UsaMap/helpers/map.ts +1 -1
  55. package/src/components/UsaMap/helpers/shapes.ts +20 -7
  56. package/src/components/WorldMap/WorldMap.tsx +64 -118
  57. package/src/components/WorldMap/worldMap.styles.css +28 -0
  58. package/src/components/ZoomControls.tsx +15 -13
  59. package/src/components/zoomControls.styles.css +53 -0
  60. package/src/context.ts +17 -9
  61. package/src/data/initial-state.js +5 -2
  62. package/src/helpers/addUIDs.ts +150 -0
  63. package/src/helpers/applyColorToLegend.ts +39 -64
  64. package/src/helpers/applyLegendToRow.ts +51 -0
  65. package/src/helpers/colorDistributions.ts +12 -0
  66. package/src/helpers/constants.ts +44 -0
  67. package/src/helpers/displayGeoName.ts +9 -2
  68. package/src/helpers/formatLegendLocation.ts +3 -2
  69. package/src/helpers/generateColorsArray.ts +2 -1
  70. package/src/helpers/generateRuntimeData.ts +78 -0
  71. package/src/helpers/generateRuntimeFilters.ts +63 -0
  72. package/src/helpers/generateRuntimeLegend.ts +566 -0
  73. package/src/helpers/generateRuntimeLegendHash.ts +16 -15
  74. package/src/helpers/getColumnNames.ts +19 -0
  75. package/src/helpers/getMapContainerClasses.ts +23 -0
  76. package/src/helpers/getStatePicked.ts +8 -0
  77. package/src/helpers/handleMapTabbing.ts +31 -0
  78. package/src/helpers/hashObj.ts +1 -1
  79. package/src/helpers/index.ts +22 -0
  80. package/src/helpers/navigationHandler.ts +3 -3
  81. package/src/helpers/resetLegendToggles.ts +13 -0
  82. package/src/helpers/setBinNumbers.ts +5 -0
  83. package/src/helpers/sortSpecialClassesLast.ts +7 -0
  84. package/src/helpers/tests/getColumnNames.test.ts +52 -0
  85. package/src/helpers/titleCase.ts +1 -1
  86. package/src/helpers/toggleLegendActive.ts +25 -0
  87. package/src/hooks/useApplyTooltipsToGeo.tsx +51 -0
  88. package/src/hooks/useColumnsRequiredChecker.ts +51 -0
  89. package/src/hooks/useGeoClickHandler.ts +45 -0
  90. package/src/hooks/useLegendSeparators.ts +26 -0
  91. package/src/hooks/useMapLayers.tsx +34 -60
  92. package/src/hooks/useModal.ts +22 -0
  93. package/src/hooks/useResizeObserver.ts +4 -5
  94. package/src/hooks/useStateZoom.tsx +52 -75
  95. package/src/hooks/useTooltip.ts +2 -3
  96. package/src/index.jsx +3 -9
  97. package/src/scss/editor-panel.scss +3 -99
  98. package/src/scss/main.scss +1 -19
  99. package/src/scss/map.scss +15 -220
  100. package/src/store/map.actions.ts +46 -0
  101. package/src/store/map.reducer.ts +96 -0
  102. package/src/types/Annotations.ts +24 -0
  103. package/src/types/MapConfig.ts +23 -3
  104. package/src/types/MapContext.ts +36 -35
  105. package/src/types/Modal.ts +1 -0
  106. package/src/types/RuntimeData.ts +3 -0
  107. package/examples/private/DEV-9644.json +0 -184
  108. package/examples/private/DEV-9989.json +0 -229
  109. package/examples/private/ardi.json +0 -180
  110. package/examples/private/colors 2.json +0 -416
  111. package/examples/private/colors.json +0 -416
  112. package/examples/private/colors.json.zip +0 -0
  113. package/examples/private/customColors.json +0 -45348
  114. package/examples/test.json +0 -183
  115. package/src/helpers/closeModal.ts +0 -9
  116. package/src/scss/btn.scss +0 -69
  117. package/src/scss/filters.scss +0 -27
  118. package/src/scss/variables.scss +0 -1
  119. /package/src/hooks/{useActiveElement.js → useActiveElement.ts} +0 -0
@@ -13,25 +13,28 @@ import Tooltip from '@cdc/core/components/ui/Tooltip'
13
13
  import Icon from '@cdc/core/components/ui/Icon'
14
14
  import './Panel.PatternSettings-style.css'
15
15
  import Alert from '@cdc/core/components/Alert'
16
+ import _ from 'lodash'
16
17
 
17
18
  // topojson helpers for checking color contrasts
18
19
  import { feature } from 'topojson-client'
19
- import { checkColorContrast, getContrastColor, getColorContrast } from '@cdc/core/helpers/cove/accessibility'
20
- import topoJSON from '../../../UsaMap/data/us-topo.json'
20
+ import { checkColorContrast, getColorContrast } from '@cdc/core/helpers/cove/accessibility'
21
+ import { applyLegendToRow } from '../../../../helpers/applyLegendToRow'
22
+ import { PatternSelection } from '../../../../types/MapConfig'
21
23
 
22
24
  type PanelProps = {
23
25
  name: string
24
26
  }
25
27
 
26
28
  const PatternSettings = ({ name }: PanelProps) => {
27
- const { state, setState, applyLegendToRow, runtimeData } = useContext<MapContext>(ConfigContext)
29
+ const { config, setConfig, runtimeData, legendMemo, legendSpecialClassLastMemo, runtimeLegend } =
30
+ useContext<MapContext>(ConfigContext)
28
31
  const defaultPattern = 'circles'
29
32
  const patternTypes = ['circles', 'waves', 'lines']
30
33
 
31
34
  const {
32
35
  map: { patterns },
33
36
  data
34
- } = state
37
+ } = config
35
38
 
36
39
  const [unitedStates, setUnitedStates] = useState(null)
37
40
 
@@ -50,92 +53,103 @@ const PatternSettings = ({ name }: PanelProps) => {
50
53
 
51
54
  /** Updates the map config with a new pattern item */
52
55
  const handleAddGeoPattern = () => {
53
- let patterns = [...state.map.patterns]
56
+ const patterns = _.cloneDeep(config.map.patterns)
54
57
  patterns.push({ dataKey: '', pattern: defaultPattern, contrastCheck: true })
55
- setState({
56
- ...state,
58
+ setConfig({
59
+ ...config,
57
60
  map: {
58
- ...state.map,
61
+ ...config.map,
59
62
  patterns
60
63
  }
61
64
  })
62
65
  }
63
66
 
64
- /** Updates the map pattern at a given index */
65
- const handleUpdateGeoPattern = (
66
- value: string,
67
- index: number,
68
- keyToUpdate: 'dataKey' | 'pattern' | 'dataValue' | 'size' | 'label' | 'color'
69
- ) => {
70
- const updatedPatterns = [...state.map.patterns]
71
-
72
- // Update the specific pattern with the new value
73
- updatedPatterns[index] = { ...updatedPatterns[index], [keyToUpdate]: value }
67
+ // Checks contrast and logs warning if needed
68
+ const checkAndLogContrast = (fill: string, patternColor: string, geoName: string, dataKey: string): boolean => {
69
+ const contrastCheck = checkColorContrast(fill, patternColor)
74
70
 
75
- // Iterate over each state feature
76
- unitedStates.forEach(geo => {
77
- const geoKey = geo.properties.iso
78
- if (!geoKey || !runtimeData) return
71
+ if (!contrastCheck) {
72
+ console.error(
73
+ `COVE: pattern contrast check failed on ${geoName} for ${dataKey} with:
74
+ pattern color: ${patternColor}
75
+ contrast: ${getColorContrast(fill, patternColor)}`
76
+ )
77
+ }
79
78
 
80
- const legendColors = runtimeData[geoKey] ? applyLegendToRow(runtimeData[geoKey]) : undefined
81
- const geoData = runtimeData[geoKey]
82
- if (!geoData) return
79
+ return contrastCheck
80
+ }
83
81
 
84
- // Iterate over each pattern
85
- state.map.patterns.forEach((patternData, patternIndex) => {
86
- const hasMatchingValues = patternData.dataValue === geoData[patternData.dataKey]
87
- if (!hasMatchingValues) return
82
+ // Gets legend colors for a geo
83
+ const getGeoLegendColors = (geoKey: string, runtimeData: any) => {
84
+ return geoKey && runtimeData?.[geoKey]
85
+ ? applyLegendToRow(runtimeData[geoKey], config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
86
+ : null
87
+ }
88
88
 
89
- const currentFill = legendColors[0]
90
- const patternColor = keyToUpdate === 'color' && value !== '' ? value : getContrastColor('#000', currentFill)
91
- const contrastCheck = checkColorContrast(currentFill, patternColor)
89
+ // Processes contrast check for a single geo
90
+ const processGeoContrast = (
91
+ geo: any,
92
+ pattern: PatternSelection,
93
+ updatedPatterns: PatternSelection[],
94
+ patternIndex: number,
95
+ color: string
96
+ ) => {
97
+ const geoKey = geo.properties.iso
98
+ const legendColors = getGeoLegendColors(geoKey, runtimeData)
99
+ const currentFill = legendColors?.[0]
92
100
 
93
- // Log a warning if the contrast check fails
94
- if (!contrastCheck) {
95
- console.warn(`COVE: pattern contrast check failed on ${geoData?.[state.columns.geo.name]} for ${
96
- patternData.dataKey
97
- } with:
98
- pattern color: ${patternColor}
99
- contrast: ${getColorContrast(currentFill, patternColor)}
100
- `)
101
- }
101
+ if (!currentFill) return
102
102
 
103
- updatedPatterns[index] = { ...updatedPatterns[index], [keyToUpdate]: value, contrastCheck }
104
- })
105
- })
103
+ const hasMatchingValues = pattern.dataValue === runtimeData[geoKey]?.[pattern.dataKey]
106
104
 
107
- const editorErrorMessage = updatedPatterns.some(pattern => pattern.contrastCheck === false)
108
- ? 'One or more patterns do not pass the WCAG 2.1 contrast ratio of 3:1.'
109
- : ''
105
+ if (hasMatchingValues) {
106
+ const contrastCheck = checkAndLogContrast(
107
+ currentFill,
108
+ color,
109
+ runtimeData[geoKey]?.[config.columns.geo.name],
110
+ pattern.dataKey
111
+ )
112
+ updatedPatterns[patternIndex].contrastCheck = contrastCheck
113
+ }
114
+ }
110
115
 
111
- // Update the state with the new patterns and error message
112
- setState(prevState => ({
113
- ...prevState,
114
- map: {
115
- ...prevState.map,
116
- patterns: updatedPatterns
117
- },
118
- runtime: {
119
- ...prevState.runtime,
120
- editorErrorMessage
121
- }
122
- }))
116
+ const handlePatternFieldUpdate = (field: string, color: string, patternIndex: number) => {
117
+ const _newConfig = _.cloneDeep(config)
118
+ _newConfig.map.patterns[patternIndex][field] = color
119
+ reviewColorContrast(_newConfig, patternIndex)
120
+ setConfig(_newConfig)
123
121
  }
124
122
 
125
123
  const handleRemovePattern = index => {
126
- const updatedPatterns = state.map.patterns.filter((pattern, i) => i !== index)
127
-
128
- setState({
129
- ...state,
130
- map: {
131
- ...state.map,
132
- patterns: updatedPatterns
133
- }
134
- })
124
+ const _newConfig = _.cloneDeep(config)
125
+ const updatedPatterns = config.map.patterns.filter((pattern, i) => i !== index)
126
+ _newConfig.map.patterns = updatedPatterns
127
+ if (checkPatternContrasts()) {
128
+ _newConfig.runtime.editorErrorMessage = ''
129
+ }
130
+ setConfig(_newConfig)
135
131
  }
136
132
 
137
133
  const checkPatternContrasts = () => {
138
- return state.map.patterns.every(pattern => pattern.contrastCheck !== false)
134
+ return config.map.patterns.every(pattern => pattern.contrastCheck !== false)
135
+ }
136
+
137
+ const reviewColorContrast = (_newConfig, patternIndex) => {
138
+ // Process each geo's contrast
139
+ unitedStates.forEach(geo => {
140
+ processGeoContrast(
141
+ geo,
142
+ _newConfig.map.patterns[patternIndex],
143
+ _newConfig.map.patterns,
144
+ patternIndex,
145
+ _newConfig.map.patterns[patternIndex].color
146
+ )
147
+ })
148
+
149
+ // Update error message
150
+ _newConfig.runtime.editorErrorMessage = _newConfig.map.patterns.some(p => p.contrastCheck === false)
151
+ ? 'One or more patterns do not pass the WCAG 2.1 contrast ratio of 3:1.'
152
+ : ''
139
153
  }
140
154
 
141
155
  return (
@@ -184,7 +198,7 @@ const PatternSettings = ({ name }: PanelProps) => {
184
198
  <select
185
199
  id={`pattern-dataKey--${patternIndex}`}
186
200
  value={pattern.dataKey !== '' ? pattern.dataKey : 'Select'}
187
- onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataKey')}
201
+ onChange={e => handlePatternFieldUpdate('dataKey', e.target.value, patternIndex)}
188
202
  >
189
203
  {/* TODO: sort these? */}
190
204
  {dataKeyOptions.map((d, index) => {
@@ -199,7 +213,7 @@ const PatternSettings = ({ name }: PanelProps) => {
199
213
  Data Value:
200
214
  <input
201
215
  type='text'
202
- onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataValue')}
216
+ onChange={e => handlePatternFieldUpdate('dataValue', e.target.value, patternIndex)}
203
217
  id={`pattern-dataValue--${patternIndex}`}
204
218
  value={pattern.dataValue === '' ? '' : pattern.dataValue}
205
219
  />
@@ -208,7 +222,7 @@ const PatternSettings = ({ name }: PanelProps) => {
208
222
  Label (optional):
209
223
  <input
210
224
  type='text'
211
- onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'label')}
225
+ onChange={e => handlePatternFieldUpdate('label', e.target.value, patternIndex)}
212
226
  id={`pattern-dataValue--${patternIndex}`}
213
227
  value={pattern.label === '' ? '' : pattern.label}
214
228
  />
@@ -217,7 +231,7 @@ const PatternSettings = ({ name }: PanelProps) => {
217
231
  <select
218
232
  id={`pattern-type--${patternIndex}`}
219
233
  value={pattern?.pattern}
220
- onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'pattern')}
234
+ onChange={e => handlePatternFieldUpdate('pattern', e.target.value, patternIndex)}
221
235
  >
222
236
  {patternTypes.map((patternName, index) => (
223
237
  <option value={patternName} key={index}>
@@ -229,7 +243,7 @@ const PatternSettings = ({ name }: PanelProps) => {
229
243
  <select
230
244
  id={`pattern-size--${patternIndex}`}
231
245
  value={pattern?.size}
232
- onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'size')}
246
+ onChange={e => handlePatternFieldUpdate('size', e.target.value, patternIndex)}
233
247
  >
234
248
  {['small', 'medium', 'large'].map((size, index) => (
235
249
  <option value={size} key={index}>
@@ -256,7 +270,7 @@ const PatternSettings = ({ name }: PanelProps) => {
256
270
  value={pattern.color || ''}
257
271
  id='patternColor'
258
272
  name='patternColor'
259
- onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'color')}
273
+ onChange={e => handlePatternFieldUpdate('color', e.target.value, patternIndex)}
260
274
  />
261
275
  </label>
262
276
  </div>
@@ -0,0 +1,95 @@
1
+ .geo-buttons {
2
+ list-style: none;
3
+ color: var(--mediumGray);
4
+ display: grid;
5
+ button { width: 100% !important; }
6
+ svg {
7
+ display: block;
8
+ max-width: 80px;
9
+ max-height: 40px;
10
+ margin: 0.5em auto;
11
+ box-sizing: border-box;
12
+ path {
13
+ fill: currentColor;
14
+ }
15
+ }
16
+ button {
17
+ background: transparent;
18
+ padding: 0.3em 0.75em;
19
+ display: flex;
20
+ border: var(--lightGray) 1px solid;
21
+ width: 40%;
22
+ align-items: center;
23
+ margin-right: 1em;
24
+ cursor: pointer;
25
+ overflow: hidden;
26
+ flex-direction: column;
27
+ transition: 0.2s all;
28
+ svg {
29
+ display: block;
30
+ height: 25px;
31
+ margin: 0.5em auto;
32
+ max-width: 100%;
33
+ }
34
+ span {
35
+ text-transform: none;
36
+ font-size: 1em;
37
+ }
38
+ &:hover {
39
+ background: #f2f2f2;
40
+ transition: 0.2s all;
41
+ }
42
+ &.active {
43
+ background: #fff;
44
+ border-color: #005eaa;
45
+ color: #005eaa;
46
+ position: relative;
47
+ path {
48
+ fill: #005eaa;
49
+ }
50
+ &:before {
51
+ content: ' ';
52
+ width: 5px;
53
+ background: #005eaa;
54
+ left: 0;
55
+ top: 0;
56
+ bottom: 0;
57
+ position: absolute;
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ .editor-toggle {
64
+ background: #f2f2f2;
65
+ border-radius: 60px;
66
+ color: #000;
67
+ font-size: 1em;
68
+ border: 0;
69
+ position: fixed;
70
+ z-index: 100;
71
+ transition: 0.1s background;
72
+ cursor: pointer;
73
+ width: 25px;
74
+ height: 25px;
75
+ left: 307px;
76
+ box-shadow: rgba(0, 0, 0, 0.5) 0 1px 2px;
77
+ &:before {
78
+ top: 43%;
79
+ left: 50%;
80
+ transform: translate(-50%, -50%);
81
+ position: absolute;
82
+ content: '\00ab';
83
+ }
84
+ &.collapsed {
85
+ left: 1em;
86
+ margin-left: 0;
87
+ &:before {
88
+ content: '\00bb';
89
+ }
90
+ }
91
+ &:hover {
92
+ background: rgba(255, 255, 255, 1);
93
+ }
94
+ }
95
+
@@ -1,6 +1,14 @@
1
1
  import React, { memo } from 'react'
2
2
 
3
- const Geo = ({ path, styles, stroke, strokeWidth, ...props }) => {
3
+ type GeoProps = {
4
+ styles?: React.CSSProperties
5
+ stroke?: string
6
+ strokeWidth?: number
7
+ path?: string
8
+ className?: string
9
+ }
10
+
11
+ const Geo: React.FC<GeoProps> = ({ path, styles, stroke, strokeWidth, ...props }) => {
4
12
  const { className, ...restProps } = props
5
13
  const geoClassName = String(props.additionalData?.name)?.toLowerCase()?.replaceAll(' ', '') || 'country'
6
14
  return (
@@ -15,7 +15,7 @@ type GoogleMapComponentProps = {
15
15
 
16
16
  const GoogleMapComponent: React.FC<GoogleMapComponentProps> = ({ apiKey = '' }) => {
17
17
  const mapRef = useRef(null)
18
- const { state } = useContext(ConfigContext)
18
+ const { config: state } = useContext(ConfigContext)
19
19
 
20
20
  useEffect(() => {
21
21
  const loader = new Loader({