@cdc/map 4.26.2 → 4.26.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcmap-vr9HZwRt.es.js +6 -0
  3. package/dist/cdcmap.js +26781 -24615
  4. package/examples/private/annotation-bug.json +642 -0
  5. package/package.json +3 -3
  6. package/src/CdcMap.tsx +3 -14
  7. package/src/CdcMapComponent.tsx +214 -159
  8. package/src/_stories/CdcMap.Defaults.stories.tsx +76 -0
  9. package/src/_stories/CdcMap.Editor.stories.tsx +187 -14
  10. package/src/_stories/CdcMap.stories.tsx +11 -1
  11. package/src/_stories/Map.HTMLInDataTable.stories.tsx +385 -0
  12. package/src/_stories/_mock/multi-state-show-unselected.json +82 -0
  13. package/src/cdcMapComponent.styles.css +2 -2
  14. package/src/components/Annotation/Annotation.Draggable.styles.css +4 -4
  15. package/src/components/Annotation/AnnotationDropdown.styles.css +1 -1
  16. package/src/components/Annotation/AnnotationList.styles.css +13 -13
  17. package/src/components/EditorPanel/components/EditorPanel.tsx +426 -58
  18. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings-style.css +1 -1
  19. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +5 -2
  20. package/src/components/EditorPanel/components/editorPanel.styles.css +34 -24
  21. package/src/components/Legend/components/Legend.tsx +9 -4
  22. package/src/components/Legend/components/LegendGroup/legend.group.css +5 -5
  23. package/src/components/Legend/components/index.scss +2 -3
  24. package/src/components/NavigationMenu.tsx +2 -1
  25. package/src/components/SmallMultiples/SmallMultiples.css +5 -5
  26. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +32 -17
  27. package/src/components/UsaMap/components/TerritoriesSection.tsx +3 -2
  28. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +13 -8
  29. package/src/components/UsaMap/components/UsaMap.County.tsx +410 -183
  30. package/src/components/UsaMap/components/UsaMap.Region.styles.css +1 -1
  31. package/src/components/UsaMap/components/UsaMap.SingleState.styles.css +2 -2
  32. package/src/components/UsaMap/components/UsaMap.State.tsx +13 -8
  33. package/src/components/WorldMap/WorldMap.tsx +10 -13
  34. package/src/components/WorldMap/data/world-topo-updated.json +1 -0
  35. package/src/components/WorldMap/data/world-topo.json +1 -1
  36. package/src/components/WorldMap/worldMap.styles.css +1 -1
  37. package/src/components/ZoomControls.tsx +49 -18
  38. package/src/components/zoomControls.styles.css +27 -11
  39. package/src/data/initial-state.js +14 -5
  40. package/src/data/legacy-defaults.ts +8 -0
  41. package/src/data/supported-geos.js +19 -0
  42. package/src/helpers/colors.ts +2 -1
  43. package/src/helpers/dataTableHelpers.ts +56 -0
  44. package/src/helpers/displayGeoName.ts +19 -11
  45. package/src/helpers/getMapContainerClasses.ts +8 -2
  46. package/src/helpers/getMatchingPatternForRow.ts +67 -0
  47. package/src/helpers/getPatternForRow.ts +11 -18
  48. package/src/helpers/tests/dataTableHelpers.test.ts +78 -0
  49. package/src/helpers/tests/displayGeoName.test.ts +17 -0
  50. package/src/helpers/tests/getMatchingPatternForRow.test.ts +150 -0
  51. package/src/helpers/tests/getPatternForRow.test.ts +140 -2
  52. package/src/helpers/urlDataHelpers.ts +7 -1
  53. package/src/hooks/useResizeObserver.ts +36 -22
  54. package/src/hooks/useTooltip.test.tsx +64 -0
  55. package/src/hooks/useTooltip.ts +28 -8
  56. package/src/scss/editor-panel.scss +1 -1
  57. package/src/scss/main.scss +140 -6
  58. package/src/scss/map.scss +9 -4
  59. package/src/store/map.actions.ts +2 -0
  60. package/src/store/map.reducer.ts +4 -0
  61. package/src/test/CdcMap.test.jsx +2 -2
  62. package/src/types/MapConfig.ts +22 -4
  63. package/src/types/MapContext.ts +3 -1
  64. package/dist/cdcmap-Cf9_fbQf.es.js +0 -6
  65. package/src/helpers/componentHelpers.ts +0 -8
@@ -1,4 +1,4 @@
1
- .cdc-open-viz-module {
1
+ .cove-visualization {
2
2
  .pattern-input__color {
3
3
  margin-top: 1rem;
4
4
  }
@@ -15,7 +15,7 @@ import Icon from '@cdc/core/components/ui/Icon'
15
15
  import { Select } from '@cdc/core/components/EditorPanel/Inputs'
16
16
  import './Panel.PatternSettings-style.css'
17
17
  import Alert from '@cdc/core/components/Alert'
18
- import _ from 'lodash'
18
+ import cloneDeep from 'lodash/cloneDeep'
19
19
  import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
20
20
 
21
21
  // topojson helpers for checking color contrasts
@@ -65,7 +65,7 @@ const PatternSettings = ({ name }: PanelProps) => {
65
65
 
66
66
  /** Updates the map config with a new pattern item */
67
67
  const handleAddGeoPattern = () => {
68
- const patterns = _.cloneDeep(config.map.patterns)
68
+ const patterns = cloneDeep(config.map.patterns)
69
69
  patterns.push({ dataKey: '', pattern: defaultPattern, contrastCheck: true })
70
70
  setConfig({
71
71
  ...config,
@@ -217,6 +217,9 @@ const PatternSettings = ({ name }: PanelProps) => {
217
217
  handlePatternFieldUpdate('dataKey', value, patternIndex)
218
218
  }
219
219
  />
220
+ <p className='edit-label mb-2'>
221
+ Leave Data Key as &quot;Select&quot; to match this value across all columns.
222
+ </p>
220
223
  <label htmlFor={`pattern-dataValue--${patternIndex}`}>
221
224
  Data Value:
222
225
  <input
@@ -1,30 +1,30 @@
1
1
  .geo-buttons {
2
- list-style: none;
3
2
  color: var(--mediumGray);
4
3
  display: grid;
4
+ list-style: none;
5
5
  button { width: 100% !important; }
6
6
  svg {
7
+ box-sizing: border-box;
7
8
  display: block;
8
- max-width: 80px;
9
- max-height: 40px;
10
9
  margin: 0.5em auto;
11
- box-sizing: border-box;
10
+ max-height: 40px;
11
+ max-width: 80px;
12
12
  path {
13
13
  fill: currentColor;
14
14
  }
15
15
  }
16
16
  button {
17
+ align-items: center;
17
18
  background: transparent;
18
- padding: 0.3em 0.75em;
19
- display: flex;
20
19
  border: var(--lightGray) 1px solid;
21
- width: 40%;
22
- align-items: center;
23
- margin-right: 1em;
24
20
  cursor: pointer;
25
- overflow: hidden;
21
+ display: flex;
26
22
  flex-direction: column;
23
+ margin-right: 1em;
24
+ overflow: hidden;
25
+ padding: 0.3em 0.75em;
27
26
  transition: 0.2s all;
27
+ width: 40%;
28
28
  svg {
29
29
  display: block;
30
30
  height: 25px;
@@ -32,8 +32,8 @@
32
32
  max-width: 100%;
33
33
  }
34
34
  span {
35
- text-transform: none;
36
35
  font-size: 1em;
36
+ text-transform: none;
37
37
  }
38
38
  &:hover {
39
39
  background: #f2f2f2;
@@ -48,13 +48,13 @@
48
48
  fill: #005eaa;
49
49
  }
50
50
  &:before {
51
- content: ' ';
52
- width: 5px;
53
51
  background: #005eaa;
54
- left: 0;
55
- top: 0;
56
52
  bottom: 0;
53
+ content: ' ';
54
+ left: 0;
57
55
  position: absolute;
56
+ top: 0;
57
+ width: 5px;
58
58
  }
59
59
  }
60
60
  }
@@ -62,24 +62,24 @@
62
62
 
63
63
  .editor-toggle {
64
64
  background: #f2f2f2;
65
+ border: 0;
65
66
  border-radius: 60px;
67
+ box-shadow: rgba(0, 0, 0, 0.5) 0 1px 2px;
66
68
  color: #000;
69
+ cursor: pointer;
67
70
  font-size: 1em;
68
- border: 0;
71
+ height: 25px;
72
+ left: 307px;
69
73
  position: fixed;
70
- z-index: 100;
71
74
  transition: 0.1s background;
72
- cursor: pointer;
73
75
  width: 25px;
74
- height: 25px;
75
- left: 307px;
76
- box-shadow: rgba(0, 0, 0, 0.5) 0 1px 2px;
76
+ z-index: 100;
77
77
  &:before {
78
- top: 43%;
78
+ content: '\00ab';
79
79
  left: 50%;
80
- transform: translate(-50%, -50%);
81
80
  position: absolute;
82
- content: '\00ab';
81
+ top: 43%;
82
+ transform: translate(-50%, -50%);
83
83
  }
84
84
  &.collapsed {
85
85
  left: 1em;
@@ -93,3 +93,13 @@
93
93
  }
94
94
  }
95
95
 
96
+ .column-section__header {
97
+ align-items: center;
98
+ display: flex;
99
+ gap: 0.5rem;
100
+ margin-bottom: 1rem;
101
+ }
102
+
103
+ .column-section__title {
104
+ font-weight: 600;
105
+ }
@@ -56,7 +56,8 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
56
56
  const isLegendGradient = legend.style === 'gradient'
57
57
  const boxDynamicallyHidden = isBelowBreakpoint('md', viewport)
58
58
  const legendWrapping =
59
- (legend.position === 'left' || legend.position === 'right') && isBelowBreakpoint('md', viewport)
59
+ (legend.position === 'left' || legend.position === 'right') &&
60
+ (viewport === 'md' || isBelowBreakpoint('md', viewport))
60
61
  const legendOnBottom = legend.position === 'bottom' || legendWrapping
61
62
  const needsTopMargin = legend.hideBorder && legendOnBottom
62
63
 
@@ -274,7 +275,7 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
274
275
 
275
276
  return (
276
277
  <ErrorBoundary component='Sidebar'>
277
- <div className={`legends ${needsTopMargin ? 'mt-4' : ''}`}>
278
+ <div className={`legends ${needsTopMargin ? 'mt-4' : ''} ${legendWrapping ? 'legend-wrapped-bottom' : ''}`}>
278
279
  <aside
279
280
  id={skipId || 'legend'}
280
281
  className={legendClasses.aside.join(' ') || ''}
@@ -292,7 +293,9 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
292
293
  config.enableMarkupVariables && config.markupVariables?.length > 0
293
294
  ? processMarkupVariables(legend.title, config.data || [], config.markupVariables, {
294
295
  isEditor: false,
295
- filters: config.filters || []
296
+ filters: config.filters || [],
297
+ locale: config.locale,
298
+ dataMetadata: config.dataMetadata
296
299
  }).processedContent
297
300
  : legend.title
298
301
  )}
@@ -304,7 +307,9 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
304
307
  config.enableMarkupVariables && config.markupVariables?.length > 0
305
308
  ? processMarkupVariables(legend.description, config.data || [], config.markupVariables, {
306
309
  isEditor: false,
307
- filters: config.filters || []
310
+ filters: config.filters || [],
311
+ locale: config.locale,
312
+ dataMetadata: config.dataMetadata
308
313
  }).processedContent
309
314
  : legend.description
310
315
  )}
@@ -1,15 +1,15 @@
1
1
  .group-label {
2
- font-weight: 500;
3
2
  font-family: Nunito, sans-serif;
4
3
  font-size: 1rem;
4
+ font-weight: 500;
5
5
  margin-bottom: 0.5rem;
6
6
  }
7
7
  .group-list-item {
8
- list-style: none;
9
- font-weight: 400;
8
+ cursor: pointer;
10
9
  font-size: 0.889rem;
10
+ font-weight: 400;
11
+ list-style: none;
11
12
  margin-top: 0.5rem !important;
12
- cursor: pointer;
13
13
  }
14
14
 
15
15
  .group-container {
@@ -21,7 +21,7 @@
21
21
  }
22
22
 
23
23
  .legend-group-item-not-disable {
24
+ border-radius: 1px;
24
25
  outline: 1px solid #005ea2;
25
26
  outline-offset: 5px;
26
- border-radius: 1px;
27
27
  }
@@ -1,4 +1,4 @@
1
- @import '@cdc/core/styles/v2/utils/breakpoints';
1
+ @import '@cdc/core/styles/utils/breakpoints';
2
2
 
3
3
  .cdc-map-inner-container {
4
4
  .map-container.world aside.side {
@@ -30,8 +30,6 @@
30
30
  z-index: 1;
31
31
  box-sizing: content-box;
32
32
  max-width: 450px;
33
- margin-top: 2em;
34
- margin-bottom: 2em;
35
33
  align-self: flex-start;
36
34
  z-index: 4;
37
35
  right: 1em;
@@ -126,6 +124,7 @@
126
124
  &--pattern {
127
125
  cursor: default;
128
126
  }
127
+
129
128
  &:focus {
130
129
  outline: none;
131
130
  }
@@ -47,7 +47,8 @@ const NavigationMenu = ({ data, navigationHandler, options, columns, displayGeoN
47
47
  const processedDropdown = {}
48
48
 
49
49
  Object.keys(data).forEach(val => {
50
- const fullName = displayGeoName(val)
50
+ const displayOverride = data[val]?.[columns.geo?.displayColumn]
51
+ const fullName = displayGeoName(val, displayOverride)
51
52
 
52
53
  processedDropdown[fullName] = val
53
54
  })
@@ -1,13 +1,13 @@
1
1
  .small-multiples-container {
2
- width: 100%;
3
2
  display: flex;
4
3
  flex-direction: column;
4
+ width: 100%;
5
5
  }
6
6
 
7
7
  .small-multiples-grid {
8
8
  display: grid;
9
- width: 100%;
10
9
  flex: 1;
10
+ width: 100%;
11
11
  }
12
12
 
13
13
  .small-multiple-tile {
@@ -20,13 +20,13 @@
20
20
  }
21
21
 
22
22
  .tile-title {
23
- margin: 0;
24
23
  font-weight: 700;
25
- text-align: left;
26
24
  line-height: 1.3;
25
+ margin: 0;
26
+ text-align: left;
27
27
  }
28
28
 
29
29
  .tile-map {
30
- width: 100%;
31
30
  flex-shrink: 0;
31
+ width: 100%;
32
32
  }
@@ -4,6 +4,8 @@ import ConfigContext from '../../../../context'
4
4
  import { getGeoStrokeColor } from '../../../../helpers/colors'
5
5
  import { getStatesPicked } from '../../../../helpers/getStatesPicked'
6
6
 
7
+ const GRAYED_OUT_COLOR = '#d3d3d3'
8
+
7
9
  type StateOutputProps = {
8
10
  topoData: Topology
9
11
  path: any
@@ -15,29 +17,42 @@ const StateOutput: React.FC<StateOutputProps> = ({ topoData, path, scale, runtim
15
17
  const { config } = useContext(ConfigContext)
16
18
  if (!topoData?.states) return null
17
19
 
18
- // Use filter-aware state selection instead of direct config access
19
20
  const statesPickedData = getStatesPicked(config, runtimeData)
20
21
  const stateNames = statesPickedData.map(sp => sp.stateName)
21
22
 
22
- const statesPicked = topoData.states.filter(s => {
23
- return stateNames.includes(s.properties.name)
24
- })
23
+ const showUnselected = config.general.hideUnselectedStates === false
24
+
25
+ const selectedStates = topoData.states.filter(s => stateNames.includes(s.properties.name))
26
+ const unselectedStates = showUnselected ? topoData.states.filter(s => !stateNames.includes(s.properties.name)) : []
25
27
 
26
28
  const geoStrokeColor = getGeoStrokeColor(config)
27
29
 
28
- const stateLines = statesPicked.map(s => path(s.geometry))
29
-
30
- return stateLines.map((line, index) => (
31
- <g
32
- key={`single-state-${index}`}
33
- className='single-state'
34
- style={{ fill: 'transparent', pointerEvents: 'none' }}
35
- stroke={geoStrokeColor}
36
- strokeWidth={2 / scale}
37
- >
38
- <path tabIndex={-1} className='state-path' d={line} />
39
- </g>
40
- ))
30
+ return (
31
+ <>
32
+ {unselectedStates.map((s, index) => (
33
+ <g
34
+ key={`unselected-state-${index}`}
35
+ className='single-state unselected'
36
+ style={{ fill: GRAYED_OUT_COLOR, opacity: 0.3, pointerEvents: 'none' }}
37
+ stroke={geoStrokeColor}
38
+ strokeWidth={1 / scale}
39
+ >
40
+ <path tabIndex={-1} className='state-path' d={path(s.geometry)} />
41
+ </g>
42
+ ))}
43
+ {selectedStates.map((s, index) => (
44
+ <g
45
+ key={`single-state-${index}`}
46
+ className='single-state'
47
+ style={{ fill: 'transparent', pointerEvents: 'none' }}
48
+ stroke={geoStrokeColor}
49
+ strokeWidth={2 / scale}
50
+ >
51
+ <path tabIndex={-1} className='state-path' d={path(s.geometry)} />
52
+ </g>
53
+ ))}
54
+ </>
55
+ )
41
56
  }
42
57
 
43
58
  export default StateOutput
@@ -39,6 +39,7 @@ const TerritoriesSection: React.FC<TerritoriesSectionProps> = ({ territories, lo
39
39
  })
40
40
 
41
41
  const isMobileViewport = isMobileTerritoryViewport(vizViewport)
42
+ const useCompactTerritorySpacing = isMobileViewport || currentViewport === 'sm' || currentViewport === 'md'
42
43
  const SVG_GAP = 9
43
44
  const SVG_WIDTH = isMobileViewport ? 30 : 45
44
45
 
@@ -59,7 +60,7 @@ const TerritoriesSection: React.FC<TerritoriesSectionProps> = ({ territories, lo
59
60
  U.S. territories
60
61
  </span>
61
62
  <span
62
- className={`${isMobileViewport ? 'mt-1 mb-3' : 'mt-2 mb-4'} d-flex territories`}
63
+ className={`${useCompactTerritorySpacing ? 'mt-1 mb-3' : 'mt-2 '} d-flex territories`}
63
64
  style={
64
65
  {
65
66
  minWidth: `${usTerritories.length * SVG_WIDTH + (usTerritories.length - 1) * SVG_GAP}px`,
@@ -78,7 +79,7 @@ const TerritoriesSection: React.FC<TerritoriesSectionProps> = ({ territories, lo
78
79
  Freely associated states
79
80
  </span>
80
81
  <span
81
- className={`${isMobileViewport ? 'mt-1 mb-3' : 'mt-2 mb-4'} d-flex territories`}
82
+ className={`${useCompactTerritorySpacing ? 'mt-1 mb-3' : 'mt-2'} d-flex territories`}
82
83
  style={
83
84
  {
84
85
  minWidth: `${
@@ -6,7 +6,7 @@ import { patternSizes } from '../../helpers/patternSizes'
6
6
  import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
7
7
  import { sanitizeToSvgId } from '@cdc/core/helpers/cove/string'
8
8
  import { type TerritoryShape } from './TerritoryShape'
9
- import { patternValuesMatch } from '../../../../helpers/patternMatching'
9
+ import { getMatchingPatternForRow } from '../../../../helpers/getMatchingPatternForRow'
10
10
 
11
11
  const TerritoryRectangle: React.FC<TerritoryShape> = ({
12
12
  dataTooltipId,
@@ -73,14 +73,19 @@ const TerritoryRectangle: React.FC<TerritoryShape> = ({
73
73
  {label}
74
74
  </text>
75
75
 
76
- {config.map?.patterns?.map((patternData, patternIndex) => {
76
+ {(() => {
77
+ const matchedPattern = getMatchingPatternForRow(territoryData, config.map?.patterns)
78
+
79
+ if (!matchedPattern) {
80
+ return null
81
+ }
82
+
83
+ const { pattern: patternData, patternIndex, matchedDataKey } = matchedPattern
77
84
  const patternColor = patternData.color || getContrastColor('#FFF', backgroundColor)
78
- const hasMatchingValues = patternValuesMatch(patternData.dataValue, territoryData?.[patternData.dataKey])
79
85
  const sanitizedTerritory = sanitizeToSvgId(territory || label)
80
- const sanitizedDataKey = sanitizeToSvgId(patternData?.dataKey || '')
86
+ const sanitizedDataKey = sanitizeToSvgId(matchedDataKey)
81
87
  const patternId = `${mapId}--territory-${sanitizedTerritory}-${sanitizedDataKey}--${patternIndex}`
82
88
 
83
- if (!hasMatchingValues) return null
84
89
  if (!patternData.pattern) return null
85
90
 
86
91
  return (
@@ -123,8 +128,8 @@ const TerritoryRectangle: React.FC<TerritoryShape> = ({
123
128
  fill={`url(#${patternId})`}
124
129
  style={{ pointerEvents: 'none' }}
125
130
  className={[
126
- `territory-pattern-${patternData?.dataKey}`,
127
- `territory-pattern-${patternData?.dataKey}--${patternData.dataValue}`
131
+ `territory-pattern-${matchedDataKey}`,
132
+ `territory-pattern-${matchedDataKey}--${patternData.dataValue}`
128
133
  ].join(' ')}
129
134
  />
130
135
  <text
@@ -144,7 +149,7 @@ const TerritoryRectangle: React.FC<TerritoryShape> = ({
144
149
  </text>
145
150
  </>
146
151
  )
147
- })}
152
+ })()}
148
153
  </g>
149
154
  </svg>
150
155
  )