@cdc/map 4.24.11 → 4.24.12

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 (42) hide show
  1. package/dist/cdcmap.js +28291 -28264
  2. package/examples/default-geocode.json +13 -4
  3. package/examples/default-usa-regions.json +267 -117
  4. package/examples/example-city-state.json +6 -3
  5. package/examples/pattern.json +861 -0
  6. package/examples/private/default-patterns.json +867 -0
  7. package/index.html +4 -5
  8. package/package.json +3 -3
  9. package/src/CdcMap.tsx +42 -48
  10. package/src/_stories/{CdcMapLegend.stories.tsx → CdcMap.Legend.Gradient.stories.tsx} +1 -20
  11. package/src/_stories/CdcMap.Legend.stories.tsx +40 -0
  12. package/src/_stories/CdcMap.Patterns.stories.tsx +29 -0
  13. package/src/_stories/CdcMap.stories.tsx +59 -0
  14. package/src/_stories/UsaMap.NoData.stories.tsx +19 -0
  15. package/src/_stories/_mock/custom-layer-map.json +1117 -0
  16. package/src/_stories/_mock/default-patterns.json +865 -0
  17. package/src/_stories/_mock/example-city-state.json +858 -0
  18. package/src/components/CityList.tsx +5 -2
  19. package/src/components/EditorPanel/components/EditorPanel.tsx +51 -21
  20. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +1 -1
  21. package/src/components/Legend/components/Legend.tsx +22 -6
  22. package/src/components/Legend/components/index.scss +16 -23
  23. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +40 -6
  24. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +10 -2
  25. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +1 -1
  26. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +12 -11
  27. package/src/components/UsaMap/components/UsaMap.County.tsx +11 -13
  28. package/src/components/UsaMap/components/UsaMap.Region.tsx +59 -16
  29. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +2 -1
  30. package/src/components/UsaMap/components/UsaMap.State.tsx +57 -59
  31. package/src/components/WorldMap/WorldMap.tsx +77 -16
  32. package/src/data/initial-state.js +2 -1
  33. package/src/helpers/applyColorToLegend.ts +80 -0
  34. package/src/helpers/colors.ts +23 -0
  35. package/src/hooks/useTooltip.ts +9 -6
  36. package/src/scss/filters.scss +0 -3
  37. package/src/scss/main.scss +0 -1
  38. package/src/scss/map.scss +11 -59
  39. package/src/types/MapConfig.ts +5 -0
  40. package/src/types/MapContext.ts +1 -0
  41. package/examples/default-patterns.json +0 -579
  42. package/src/scss/datatable.scss +0 -6
@@ -11,6 +11,7 @@ import CityList from '../CityList'
11
11
  import BubbleList from '../BubbleList'
12
12
  import ConfigContext from '../../context'
13
13
  import ZoomControls from '../ZoomControls'
14
+ import { getGeoFillColor, getGeoStrokeColor } from '../../helpers/colors'
14
15
 
15
16
  const { features: world } = feature(topoJSON, topoJSON.objects.countries)
16
17
 
@@ -77,7 +78,12 @@ const WorldMap = () => {
77
78
  const geosJsx = geographies.map(({ feature: geo, path }, i) => {
78
79
  // If the geo.properties.state value is found in the data use that, otherwise fall back to geo.properties.iso
79
80
  const dataHasStateName = state.data.some(d => d[state.columns.geo.name] === geo.properties.state)
80
- const geoKey = geo.properties.state && data[geo.properties.state] ? geo.properties.state : geo.properties.name ? geo.properties.name : geo.properties.iso
81
+ const geoKey =
82
+ geo.properties.state && data[geo.properties.state]
83
+ ? geo.properties.state
84
+ : geo.properties.name
85
+ ? geo.properties.name
86
+ : geo.properties.iso
81
87
 
82
88
  const additionalData = {
83
89
  name: geo.properties.name
@@ -98,33 +104,36 @@ const WorldMap = () => {
98
104
  legendColors = applyLegendToRow(geoData)
99
105
  }
100
106
 
101
- const geoStrokeColor = state.general.geoBorderColor === 'darkGray' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.7)'
107
+ const geoStrokeColor = getGeoStrokeColor(state)
108
+ const geoFillColor = getGeoFillColor(state)
102
109
 
103
110
  let styles: Record<string, string | Record<string, string>> = {
104
- fill: '#E6E6E6',
111
+ fill: geoFillColor,
105
112
  cursor: 'default'
106
113
  }
107
114
 
108
115
  const strokeWidth = 0.9
109
116
 
110
117
  // If a legend applies, return it with appropriate information.
118
+ const toolTip = applyTooltipsToGeo(geoDisplayName, geoData)
111
119
  if (legendColors && legendColors[0] !== '#000000' && state.general.type !== 'bubble') {
112
- const toolTip = applyTooltipsToGeo(geoDisplayName, geoData)
113
-
114
120
  styles = {
115
121
  ...styles,
116
- fill: state.general.type !== 'world-geocode' ? legendColors[0] : '#E6E6E6',
122
+ fill: state.general.type !== 'world-geocode' ? legendColors[0] : geoFillColor,
117
123
  cursor: 'default',
118
124
  '&:hover': {
119
- fill: state.general.type !== 'world-geocode' ? legendColors[1] : '#E6E6E6'
125
+ fill: state.general.type !== 'world-geocode' ? legendColors[1] : geoFillColor
120
126
  },
121
127
  '&:active': {
122
- fill: state.general.type !== 'world-geocode' ? legendColors[2] : '#E6E6E6'
128
+ fill: state.general.type !== 'world-geocode' ? legendColors[2] : geoFillColor
123
129
  }
124
130
  }
125
131
 
126
132
  // When to add pointer cursor
127
- if ((state.columns.navigate && geoData[state.columns.navigate.name]) || state.tooltips.appearanceType === 'click') {
133
+ if (
134
+ (state.columns.navigate && geoData[state.columns.navigate.name]) ||
135
+ state.tooltips.appearanceType === 'click'
136
+ ) {
128
137
  styles.cursor = 'pointer'
129
138
  }
130
139
 
@@ -147,11 +156,38 @@ const WorldMap = () => {
147
156
  }
148
157
 
149
158
  // Default return state, just geo with no additional information
150
- return <Geo additionaldata={JSON.stringify(additionalData)} geodata={JSON.stringify(geoData)} state={state} key={i + '-geo'} stroke={geoStrokeColor} strokeWidth={strokeWidth} style={styles} path={path} />
159
+ return (
160
+ <Geo
161
+ additionaldata={JSON.stringify(additionalData)}
162
+ geodata={JSON.stringify(geoData)}
163
+ state={state}
164
+ key={i + '-geo'}
165
+ stroke={geoStrokeColor}
166
+ strokeWidth={strokeWidth}
167
+ style={styles}
168
+ styles={styles}
169
+ path={path}
170
+ data-tooltip-id={`tooltip__${tooltipId}`}
171
+ data-tooltip-html={toolTip}
172
+ />
173
+ )
151
174
  })
152
175
 
153
176
  // Cities
154
- geosJsx.push(<CityList applyLegendToRow={applyLegendToRow} applyTooltipsToGeo={applyTooltipsToGeo} data={data} displayGeoName={displayGeoName} geoClickHandler={geoClickHandler} key='cities' projection={projection} state={state} titleCase={titleCase} tooltipId={tooltipId} />)
177
+ geosJsx.push(
178
+ <CityList
179
+ applyLegendToRow={applyLegendToRow}
180
+ applyTooltipsToGeo={applyTooltipsToGeo}
181
+ data={data}
182
+ displayGeoName={displayGeoName}
183
+ geoClickHandler={geoClickHandler}
184
+ key='cities'
185
+ projection={projection}
186
+ state={state}
187
+ titleCase={titleCase}
188
+ tooltipId={tooltipId}
189
+ />
190
+ )
155
191
 
156
192
  // Bubbles
157
193
  if (state.general.type === 'bubble') {
@@ -166,7 +202,9 @@ const WorldMap = () => {
166
202
  applyTooltipsToGeo={applyTooltipsToGeo}
167
203
  displayGeoName={displayGeoName}
168
204
  tooltipId={tooltipId}
169
- handleCircleClick={country => handleCircleClick(country, state, setState, setRuntimeData, generateRuntimeData)}
205
+ handleCircleClick={country =>
206
+ handleCircleClick(country, state, setState, setRuntimeData, generateRuntimeData)
207
+ }
170
208
  />
171
209
  )
172
210
  }
@@ -178,19 +216,42 @@ const WorldMap = () => {
178
216
  <ErrorBoundary component='WorldMap'>
179
217
  {hasZoom ? (
180
218
  <svg viewBox='0 0 880 500' role='img' aria-label={handleMapAriaLabels(state)}>
181
- <rect height={500} width={880} onClick={() => handleReset(state, setState, setRuntimeData, generateRuntimeData)} fill='white' />
182
- <ZoomableGroup zoom={position.zoom} center={position.coordinates} onMoveEnd={handleMoveEnd} maxZoom={4} projection={projection} width={880} height={500}>
219
+ <rect
220
+ height={500}
221
+ width={880}
222
+ onClick={() => handleReset(state, setState, setRuntimeData, generateRuntimeData)}
223
+ fill='white'
224
+ />
225
+ <ZoomableGroup
226
+ zoom={position.zoom}
227
+ center={position.coordinates}
228
+ onMoveEnd={handleMoveEnd}
229
+ maxZoom={4}
230
+ projection={projection}
231
+ width={880}
232
+ height={500}
233
+ >
183
234
  <Mercator data={world}>{({ features }) => constructGeoJsx(features)}</Mercator>
184
235
  </ZoomableGroup>
185
236
  </svg>
186
237
  ) : (
187
238
  <svg viewBox='0 0 880 500'>
188
- <ZoomableGroup zoom={1} center={position.coordinates} onMoveEnd={handleMoveEnd} maxZoom={0} projection={projection} width={880} height={500}>
239
+ <ZoomableGroup
240
+ zoom={1}
241
+ center={position.coordinates}
242
+ onMoveEnd={handleMoveEnd}
243
+ maxZoom={0}
244
+ projection={projection}
245
+ width={880}
246
+ height={500}
247
+ >
189
248
  <Mercator data={world}>{({ features }) => constructGeoJsx(features)}</Mercator>
190
249
  </ZoomableGroup>
191
250
  </svg>
192
251
  )}
193
- {(state.general.type === 'data' || (state.general.type === 'world-geocode' && hasZoom) || (state.general.type === 'bubble' && hasZoom)) && (
252
+ {(state.general.type === 'data' ||
253
+ (state.general.type === 'world-geocode' && hasZoom) ||
254
+ (state.general.type === 'bubble' && hasZoom)) && (
194
255
  <ZoomControls
195
256
  // prettier-ignore
196
257
  generateRuntimeData={generateRuntimeData}
@@ -126,5 +126,6 @@ export default {
126
126
  }
127
127
  ]
128
128
  },
129
- filterBehavior: 'Filter Change'
129
+ filterBehavior: 'Filter Change',
130
+ filterIntro: ''
130
131
  }
@@ -0,0 +1,80 @@
1
+ import colorPalettes from '@cdc/core/data/colorPalettes'
2
+ import chroma from 'chroma-js'
3
+ import { isOlderVersion } from '@cdc/core/helpers/ver/versionNeedsUpdate'
4
+ import { type ChartConfig } from '@cdc/chart/src/types/ChartConfig'
5
+
6
+ /**
7
+ * applyColorToLegend
8
+ * @param legendIdx legend item index
9
+ * @param config chart config
10
+ * @param result hash of legend items
11
+ * @returns
12
+ */
13
+ export const applyColorToLegend = (legendIdx: number, config: ChartConfig, result = []) => {
14
+ try {
15
+ if (!config) throw new Error('Config is required')
16
+
17
+ const colorDistributions = {
18
+ 1: [1],
19
+ 2: [1, 3],
20
+ 3: [1, 3, 5],
21
+ 4: [0, 2, 4, 6],
22
+ 5: [0, 2, 4, 6, 7],
23
+ 6: [0, 2, 3, 4, 5, 7],
24
+ 7: [0, 2, 3, 4, 5, 6, 7],
25
+ 8: [0, 2, 3, 4, 5, 6, 7, 8],
26
+ 9: [0, 1, 2, 3, 4, 5, 6, 7, 8],
27
+ 10: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
28
+ }
29
+
30
+ const specialClasses = config?.legend?.specialClasses
31
+ const { general } = config || {}
32
+ // Default to "bluegreen" color scheme if the passed color isn't valid
33
+ let mapColorPalette = config.customColors || colorPalettes[config.color] || colorPalettes['bluegreen']
34
+
35
+ // Handle Region Maps need for a 10th color
36
+ if (general.geoType === 'us-region' && mapColorPalette.length < 10 && mapColorPalette.length > 8) {
37
+ if (!general.palette.isReversed) {
38
+ mapColorPalette.push(chroma(mapColorPalette[8]).darken(0.75).hex())
39
+ } else {
40
+ mapColorPalette.unshift(chroma(mapColorPalette[0]).darken(0.75).hex())
41
+ }
42
+ }
43
+
44
+ let colorIdx = legendIdx - specialClasses.length
45
+
46
+ // Special Classes (No Data)
47
+ if (result[legendIdx].special) {
48
+ if (isOlderVersion(config.version, '4.24.11')) {
49
+ const specialClassColors = chroma.scale(['#D4D4D4', '#939393']).colors(specialClasses)
50
+ return specialClassColors[legendIdx]
51
+ } else {
52
+ const specialClassColors = ['#A9AEB1', '#71767A']
53
+ return specialClassColors[legendIdx]
54
+ }
55
+ }
56
+
57
+ if (config.color.includes('qualitative')) return mapColorPalette[colorIdx]
58
+
59
+ // If the current version is newer than 4.24.10, use the color palette
60
+ if (!isOlderVersion(config.version, '4.24.12')) {
61
+ if (config.customColors) return mapColorPalette[legendIdx - specialClasses.length]
62
+ }
63
+
64
+ let amt = Math.max(result.length - specialClasses.length, 1)
65
+ let distributionArray = colorDistributions[amt]
66
+ let specificColor
67
+
68
+ if (distributionArray) {
69
+ specificColor = distributionArray[legendIdx - specialClasses.length]
70
+ } else if (mapColorPalette[colorIdx]) {
71
+ specificColor = colorIdx
72
+ } else {
73
+ specificColor = mapColorPalette.length - 1
74
+ }
75
+
76
+ return mapColorPalette[specificColor]
77
+ } catch (error) {
78
+ console.error('Error in applyColorToLegend', error)
79
+ }
80
+ }
@@ -0,0 +1,23 @@
1
+ import { MapConfig } from '../types/MapConfig'
2
+
3
+ /**
4
+ * Returns the stroke color for geographical borders based on the provided map configuration.
5
+ *
6
+ * @param {MapConfig} config - The map configuration object.
7
+ * @returns {string} The stroke color for geographical borders.
8
+ * @see DEV-9726 Map Border Colors and Fills
9
+ *
10
+ */
11
+ export const getGeoStrokeColor = (config: MapConfig) => {
12
+ const bodyStyles = getComputedStyle(document.body)
13
+ if (config.general.geoBorderColor === 'darkGray') {
14
+ return bodyStyles.getPropertyValue('--cool-gray-90')
15
+ } else {
16
+ return bodyStyles.getPropertyValue('--white')
17
+ }
18
+ }
19
+
20
+ export const getGeoFillColor = (config: MapConfig) => {
21
+ const bodyStyles = getComputedStyle(document.body)
22
+ return bodyStyles.getPropertyValue('--cool-gray-10')
23
+ }
@@ -14,7 +14,9 @@ const useTooltip = props => {
14
14
  if (geoType === 'us-county' && type !== 'us-geocode') {
15
15
  let stateFipsCode = row[config.columns.geo.name].substring(0, 2)
16
16
  const stateName = supportedStatesFipsCodes[stateFipsCode]
17
- toolTipText += hideGeoColumnInTooltip ? `<strong>${stateName}</strong><br/>` : `<strong>Location: ${stateName}</strong><br/>`
17
+ toolTipText += hideGeoColumnInTooltip
18
+ ? `<strong>${stateName}</strong><br/>`
19
+ : `<strong>Location: ${stateName}</strong><br/>`
18
20
  }
19
21
  return toolTipText
20
22
  }
@@ -61,7 +63,7 @@ const useTooltip = props => {
61
63
  const handleTooltipSpecialClassText = (specialClasses, column, row, value, columnKey) => {
62
64
  if (specialClasses && specialClasses.length && typeof specialClasses[0] === 'object') {
63
65
  for (const specialClass of specialClasses) {
64
- if (column.name === specialClass.key && String(row[specialClass.key]) === specialClass.value) {
66
+ if (column.name === specialClass.key && String(row?.[specialClass.key]) === specialClass.value) {
65
67
  value = displayDataAsText(specialClass.label, columnKey)
66
68
  break
67
69
  }
@@ -73,7 +75,8 @@ const useTooltip = props => {
73
75
  const handleTooltipPrimaryColumn = (tooltipValue, column) => {
74
76
  const { hidePrimaryColumnInTooltip } = config.general as { hidePrimaryColumnInTooltip: boolean }
75
77
  let tooltipPrefix = column.label?.length > 0 ? column.label : ''
76
- if ((column.name === config.columns.primary.name && hidePrimaryColumnInTooltip) || !tooltipPrefix) return `<li class="tooltip-body">${tooltipValue}</li>`
78
+ if ((column.name === config.columns.primary.name && hidePrimaryColumnInTooltip) || !tooltipPrefix)
79
+ return `<li class="tooltip-body">${tooltipValue}</li>`
77
80
  return `<li class="tooltip-body">${tooltipPrefix}: ${tooltipValue}</li>`
78
81
  }
79
82
 
@@ -91,7 +94,7 @@ const useTooltip = props => {
91
94
  legend: { specialClasses }
92
95
  } = config
93
96
 
94
- if (tooltipEnabledMaps.includes(currentMapType) && undefined !== row) {
97
+ if (tooltipEnabledMaps.includes(currentMapType) && (row !== undefined || config.general.geoType === 'world')) {
95
98
  toolTipText += `<ul>`
96
99
 
97
100
  // if tooltips are allowed, loop through each column
@@ -102,7 +105,7 @@ const useTooltip = props => {
102
105
  let tooltipValue = handleTooltipSpecialClassText(specialClasses, column, row, '', columnKey)
103
106
 
104
107
  if (!tooltipValue) {
105
- tooltipValue = displayDataAsText(row[column.name], columnKey)
108
+ tooltipValue = row ? displayDataAsText(row[column.name], columnKey) : 'No Data'
106
109
  }
107
110
 
108
111
  toolTipText += handleTooltipPrimaryColumn(tooltipValue, column)
@@ -115,7 +118,7 @@ const useTooltip = props => {
115
118
  }
116
119
 
117
120
  const buildTooltip = (row, geoName, toolTipText = '') => {
118
- if (!row) return
121
+ if (!row && config.general.geoType !== 'world') return
119
122
 
120
123
  // Handle County Location Columns
121
124
  toolTipText += handleTooltipStateNameColumn(toolTipText, row)
@@ -1,6 +1,5 @@
1
1
  .cdc-open-viz-module .cdc-map-inner-container .filters-section,
2
2
  .cove .cdc-map-inner-container .filters-section {
3
-
4
3
  &__title {
5
4
  margin: 15px 0;
6
5
  }
@@ -20,11 +19,9 @@
20
19
 
21
20
  @include breakpoint(md) {
22
21
  display: flex;
23
- gap: 30px;
24
22
  flex-wrap: wrap;
25
23
  }
26
24
 
27
-
28
25
  label:not(:empty) {
29
26
  margin-right: 0.4em;
30
27
  }
@@ -47,7 +47,6 @@
47
47
 
48
48
  .cdc-map-inner-container {
49
49
  @import './map';
50
- @import './datatable';
51
50
  flex-grow: 1;
52
51
  text-rendering: geometricPrecision;
53
52
  color: #202020;
package/src/scss/map.scss CHANGED
@@ -86,25 +86,10 @@ $medium: 768px;
86
86
  transition: 0.2s all;
87
87
  }
88
88
  }
89
- // make logo smaller on mobile
90
- @media screen and (max-width: $small) {
91
- .map-logo {
92
- position: absolute;
93
- bottom: 4em; // needed to align to top of Territories
94
- right: 1em;
95
- z-index: 3;
96
- width: 50px; // make it smaller
97
- }
98
- }
99
- // everything else but mobile
100
- @media screen and (min-width: $small) {
101
- .map-logo {
102
- position: absolute;
103
- bottom: 2em;
104
- right: 1em;
105
- z-index: 3;
106
- width: 75px;
107
- }
89
+ .map-logo {
90
+ display: block;
91
+ margin: 0 0 0 auto;
92
+ max-height: 35px;
108
93
  }
109
94
  }
110
95
 
@@ -116,61 +101,25 @@ $medium: 768px;
116
101
  }
117
102
  }
118
103
 
119
- // for Territories label in one col and Territory blocks wrapping in 2nd column
120
- .two-col {
121
- display: flex;
122
- margin-top: 0;
123
-
124
- justify-content: flex-start;
125
- > label {
126
- margin-top: 0;
127
- display: inline-block;
128
- }
129
- }
130
-
131
104
  .territories-label {
132
105
  color: black;
133
- margin: 2em 5px 2em 1em;
134
106
  font-size: 1.1em;
135
107
  display: block;
108
+ margin-top: 15px;
136
109
  }
137
110
 
138
111
  // Cities and Territories
139
112
  .territories {
140
- margin: 2em 200px 2em 0;
141
- font-size: 1.1em;
142
- width: 100%;
143
- display: block;
144
- align-items: center;
145
- > span {
146
- margin-left: 1em;
147
- margin-right: 0.5em;
148
- }
113
+ gap: 0.5em;
149
114
  svg {
150
115
  max-width: 35px;
151
116
  min-width: 25px;
152
- margin-left: 0.5em;
153
117
  transition: 0.3s all;
118
+
154
119
  text {
155
120
  font-size: 0.95em;
156
121
  }
157
122
  }
158
-
159
- &--mobile {
160
- @extend .territories;
161
- width: 60%;
162
- svg {
163
- margin-bottom: 0.5em;
164
- }
165
- }
166
-
167
- &--tablet {
168
- @extend .territories;
169
- width: 70%;
170
- svg {
171
- margin-bottom: 0.5em;
172
- }
173
- }
174
123
  }
175
124
 
176
125
  .zoom-controls {
@@ -380,5 +329,8 @@ canvas {
380
329
  }
381
330
 
382
331
  .data-table-container {
383
- margin: 5px 0px !important;
332
+ margin: 20px 0px 0px;
333
+ &.download-link-above {
334
+ margin-top: 0;
335
+ }
384
336
  }
@@ -1,6 +1,7 @@
1
1
  import { ComponentThemes } from '@cdc/core/types/ComponentThemes'
2
2
  import { Visualization } from '@cdc/core/types/Visualization'
3
3
  import { EditorColumnProperties } from '@cdc/core/types/EditorColumnProperties'
4
+ import { Version } from '@cdc/core/types/Version'
4
5
 
5
6
  export type MapVisualSettings = {
6
7
  /** minBubbleSize - Minimum Circle Size when the map has a type of bubble */
@@ -33,6 +34,7 @@ type PatternSelection = {
33
34
  label: string
34
35
  // size of pattern
35
36
  size: 'small' | 'medium' | 'large'
37
+ color: string
36
38
  contrastCheck: boolean
37
39
  }
38
40
 
@@ -163,5 +165,8 @@ export type MapConfig = Visualization & {
163
165
  }
164
166
  hexMap: HexMapSettings
165
167
  filterBehavior: string
168
+ filterIntro: string
166
169
  visual: MapVisualSettings
170
+ // version of the map
171
+ version: Version
167
172
  }
@@ -24,6 +24,7 @@ export type MapContext = {
24
24
  isEditor
25
25
  isFilterValueSupported: boolean
26
26
  loadConfig
27
+ logo: string
27
28
  navigationHandler
28
29
  position
29
30
  resetLegendToggles