@cdc/map 4.24.10 → 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 (50) hide show
  1. package/dist/cdcmap.js +27324 -27152
  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/DEV-9644.json +184 -0
  7. package/examples/private/default-patterns.json +867 -0
  8. package/index.html +4 -5
  9. package/package.json +3 -3
  10. package/src/CdcMap.tsx +53 -52
  11. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +67 -0
  12. package/src/_stories/CdcMap.Legend.stories.tsx +40 -0
  13. package/src/_stories/CdcMap.Patterns.stories.tsx +29 -0
  14. package/src/_stories/CdcMap.stories.tsx +59 -0
  15. package/src/_stories/UsaMap.NoData.stories.tsx +19 -0
  16. package/src/_stories/_mock/custom-layer-map.json +1117 -0
  17. package/src/_stories/_mock/default-patterns.json +865 -0
  18. package/src/_stories/_mock/example-city-state.json +858 -0
  19. package/src/_stories/_mock/usa-state-gradient.json +238 -0
  20. package/src/_stories/_mock/wastewater-map.json +208 -0
  21. package/src/components/CityList.tsx +5 -2
  22. package/src/components/EditorPanel/components/EditorPanel.tsx +81 -61
  23. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +27 -23
  24. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +75 -16
  25. package/src/components/Legend/components/Legend.tsx +42 -20
  26. package/src/components/Legend/components/index.scss +24 -24
  27. package/src/components/UsaMap/components/HexIcon.tsx +7 -1
  28. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +40 -6
  29. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +10 -2
  30. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +57 -12
  31. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +95 -21
  32. package/src/components/UsaMap/components/Territory/TerritoryShape.ts +13 -0
  33. package/src/components/UsaMap/components/UsaMap.County.tsx +11 -13
  34. package/src/components/UsaMap/components/UsaMap.Region.tsx +59 -16
  35. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +2 -1
  36. package/src/components/UsaMap/components/UsaMap.State.tsx +60 -62
  37. package/src/components/UsaMap/helpers/shapes.ts +5 -4
  38. package/src/components/WorldMap/WorldMap.tsx +77 -16
  39. package/src/data/initial-state.js +2 -1
  40. package/src/helpers/applyColorToLegend.ts +80 -0
  41. package/src/helpers/colors.ts +23 -0
  42. package/src/hooks/useTooltip.ts +9 -6
  43. package/src/scss/editor-panel.scss +0 -3
  44. package/src/scss/filters.scss +1 -9
  45. package/src/scss/main.scss +0 -5
  46. package/src/scss/map.scss +11 -63
  47. package/src/types/MapConfig.ts +6 -1
  48. package/src/types/MapContext.ts +1 -0
  49. package/examples/default-patterns.json +0 -579
  50. package/src/scss/datatable.scss +0 -6
@@ -15,18 +15,22 @@ import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
15
15
  import ConfigContext from '../../../context'
16
16
  import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
17
17
  import { GlyphStar, GlyphTriangle, GlyphDiamond, GlyphSquare, GlyphCircle } from '@visx/glyph'
18
- import { type ViewportSize } from '../../../types/MapConfig'
19
18
  import { Group } from '@visx/group'
20
19
  import './index.scss'
20
+ import { ViewportSize } from '@cdc/chart/src/types/ChartConfig'
21
+ import { isBelowBreakpoint, isMobileHeightViewport } from '@cdc/core/helpers/viewports'
22
+
23
+ const LEGEND_PADDING = 30
21
24
 
22
25
  type LegendProps = {
23
26
  skipId: string
24
- currentViewport: ViewportSize
25
27
  dimensions: DimensionsType
28
+ containerWidthPadding: number
29
+ currentViewport: ViewportSize
26
30
  }
27
31
 
28
32
  const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
29
- const { skipId, currentViewport, dimensions } = props
33
+ const { skipId, dimensions, containerWidthPadding, currentViewport } = props
30
34
 
31
35
  const {
32
36
  // prettier-ignore
@@ -42,6 +46,12 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
42
46
  } = useContext(ConfigContext)
43
47
 
44
48
  const { legend } = state
49
+ const isLegendGradient = legend.style === 'gradient'
50
+ const boxDynamicallyHidden = isBelowBreakpoint('md', currentViewport)
51
+ const legendWrapping =
52
+ (legend.position === 'left' || legend.position === 'right') && isBelowBreakpoint('md', currentViewport)
53
+ const legendOnBottom = legend.position === 'bottom' || legendWrapping
54
+ const needsTopMargin = legend.hideBorder && legendOnBottom
45
55
 
46
56
  // Toggles if a legend is active and being applied to the map and data table.
47
57
  const toggleLegendActive = (i, legendLabel) => {
@@ -83,6 +93,10 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
83
93
  formattedText = '0'
84
94
  }
85
95
 
96
+ if (entry.max === null && entry.min === null) {
97
+ formattedText = 'No data'
98
+ }
99
+
86
100
  let legendLabel = formattedText
87
101
 
88
102
  if (entry.hasOwnProperty('special')) {
@@ -99,13 +113,14 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
99
113
  })
100
114
  }
101
115
 
102
- const legendList = () => {
103
- const formattedItems = getFormattedLegendItems()
116
+ const legendList = (patternsOnly = false) => {
117
+ const formattedItems = patternsOnly ? [] : getFormattedLegendItems()
118
+ const patternsOnlyFont = isMobileHeightViewport(currentViewport) ? '12px' : '14px'
104
119
  let legendItems
105
120
 
106
121
  legendItems = formattedItems.map((item, idx) => {
107
122
  const handleListItemClass = () => {
108
- let classes = ['legend-container__li']
123
+ let classes = ['legend-container__li', 'd-flex', 'align-items-center']
109
124
  if (item.disabled) classes.push('legend-container__li--disabled')
110
125
  if (item.special) classes.push('legend-container__li--special-class')
111
126
  return classes.join(' ')
@@ -126,11 +141,7 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
126
141
  }}
127
142
  tabIndex={0}
128
143
  >
129
- <LegendShape
130
- shape={state.legend.style === 'boxes' ? 'square' : 'circle'}
131
- viewport={viewport}
132
- fill={item.color}
133
- />
144
+ <LegendShape shape={state.legend.style === 'boxes' ? 'square' : 'circle'} fill={item.color} />
134
145
  <span>{item.label}</span>
135
146
  </li>
136
147
  )
@@ -195,7 +206,9 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
195
206
  />
196
207
  </svg>
197
208
  </span>
198
- <p style={{ lineHeight: '22.4px' }}>{patternData.label || patternData.dataValue || ''}</p>
209
+ <p style={{ lineHeight: '22.4px', fontSize: patternsOnly ? patternsOnlyFont : '16px' }}>
210
+ {patternData.label || patternData.dataValue || ''}
211
+ </p>
199
212
  </li>
200
213
  </>
201
214
  )
@@ -204,6 +217,8 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
204
217
 
205
218
  return legendItems
206
219
  }
220
+ const legendListItems = legendList(isLegendGradient)
221
+
207
222
  const { legendClasses } = useDataVizClasses(state, viewport)
208
223
 
209
224
  const handleReset = e => {
@@ -239,13 +254,13 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
239
254
 
240
255
  return (
241
256
  <ErrorBoundary component='Sidebar'>
242
- <div className='legends'>
257
+ <div className={`legends ${needsTopMargin ? 'mt-1' : ''}`}>
243
258
  <aside
244
259
  id={skipId || 'legend'}
245
260
  className={legendClasses.aside.join(' ') || ''}
246
261
  role='region'
247
262
  aria-label='Legend'
248
- tabIndex={0}
263
+ tabIndex={isLegendGradient ? -1 : 0}
249
264
  ref={ref}
250
265
  >
251
266
  <section className={legendClasses.section.join(' ') || ''} aria-label='Map Legend'>
@@ -273,14 +288,17 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
273
288
  <LegendGradient
274
289
  labels={getFormattedLegendItems().map(item => item?.label) ?? []}
275
290
  colors={getFormattedLegendItems().map(item => item?.color) ?? []}
276
- values={getFormattedLegendItems().map(item => item?.value) ?? []}
277
291
  dimensions={dimensions}
278
- currentViewport={currentViewport}
292
+ parentPaddingToSubtract={
293
+ containerWidthPadding + (legend.hideBorder || boxDynamicallyHidden ? 0 : LEGEND_PADDING)
294
+ }
279
295
  config={state}
280
296
  />
281
- <ul className={legendClasses.ul.join(' ') || ''} aria-label='Legend items'>
282
- {state.legend.style === 'gradient' ? '' : legendList()}
283
- </ul>
297
+ {!!legendListItems.length && (
298
+ <ul className={legendClasses.ul.join(' ') || ''} aria-label='Legend items'>
299
+ {legendListItems}
300
+ </ul>
301
+ )}
284
302
  {(state.visual.additionalCityStyles.some(c => c.label) || state.visual.cityStyleLabel) && (
285
303
  <>
286
304
  <hr />
@@ -315,7 +333,11 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
315
333
  </div>
316
334
  </>
317
335
  )}
318
- {runtimeLegend.disabledAmt > 0 && <Button onClick={handleReset}>Reset</Button>}
336
+ {runtimeLegend.disabledAmt > 0 && (
337
+ <Button className={legendClasses.resetButton.join(' ')} onClick={handleReset}>
338
+ Reset
339
+ </Button>
340
+ )}
319
341
  </section>
320
342
  </aside>
321
343
  {state.hexMap.shapeGroups?.length > 0 && state.hexMap.type === 'shapes' && state.general.displayAsHex && (
@@ -6,7 +6,7 @@
6
6
  }
7
7
  @include breakpointClass(md) {
8
8
  .map-container.world aside.side {
9
- border-top: var(--lightGray) 1px solid;
9
+ border-top: var(--cool-gray-10) 1px solid;
10
10
  position: absolute;
11
11
  box-shadow: rgba(0, 0, 0, 0.2) 0 10px 18px;
12
12
  }
@@ -15,10 +15,15 @@
15
15
  aside {
16
16
  background-color: #fff;
17
17
  z-index: 6;
18
- border-top: var(--lightGray) 1px solid;
18
+ border-top: var(--cool-gray-10) 1px solid;
19
+
19
20
  @include breakpointClass(md) {
20
- &.bottom, &.top {
21
- border: var(--lightGray) 1px solid;
21
+ .legend-container.legend-padding {
22
+ padding: 15px;
23
+ }
24
+ &.bottom,
25
+ &.top {
26
+ border: var(--cool-gray-10) 1px solid;
22
27
  }
23
28
  &.side {
24
29
  z-index: 1;
@@ -29,7 +34,7 @@
29
34
  align-self: flex-start;
30
35
  z-index: 4;
31
36
  right: 1em;
32
- border: var(--lightGray) 1px solid;
37
+ border: var(--cool-gray-10) 1px solid;
33
38
  top: 2em;
34
39
  right: 1em;
35
40
 
@@ -65,8 +70,11 @@
65
70
  }
66
71
 
67
72
  .legend-container {
68
- padding: 1em;
73
+ --space-between-legend-items: 0.6em;
69
74
  position: relative;
75
+ &.legend-padding {
76
+ padding-top: 15px;
77
+ }
70
78
  .legend-container__title {
71
79
  font-size: 1.3em;
72
80
  padding-bottom: 0;
@@ -79,17 +87,7 @@
79
87
  padding-top: 1em;
80
88
  }
81
89
  .legend-container__reset-button {
82
- font-size: 0.75em;
83
- right: 1em;
84
- text-transform: uppercase;
85
- transition: 0.2s all;
86
- padding: 0.2em 0.5em;
87
- border: rgba(0, 0, 0, 0.2) 1px solid;
88
- padding: 0.375rem;
89
- &:hover {
90
- text-decoration: none;
91
- transition: 0.2s all;
92
- }
90
+ margin-top: 1em;
93
91
  }
94
92
  p {
95
93
  line-height: 1.4em;
@@ -114,23 +112,21 @@
114
112
 
115
113
  &:not(.vertical-sorted, .legend-container__ul--single-column, .single-row) {
116
114
  width: 100%;
117
- @include breakpoint(sm) {
118
- .legend-container__li {
119
- width: 50%;
120
- }
121
- }
122
115
  }
123
116
  .legend-container__li {
124
117
  flex-shrink: 0;
125
118
  display: inline-block;
126
119
  padding-right: 1em;
127
- padding-bottom: 1em;
120
+ margin-bottom: var(--space-between-legend-items);
128
121
  vertical-align: middle;
129
122
  transition: 0.1s opacity;
130
123
  display: flex;
131
124
  cursor: pointer;
132
125
  white-space: wrap;
133
126
  flex-grow: 1;
127
+ &:last-child {
128
+ margin-bottom: 0;
129
+ }
134
130
  @include breakpoint(md) {
135
131
  white-space: nowrap;
136
132
  }
@@ -142,16 +138,17 @@
142
138
  }
143
139
  .legend-container__ul.single-row {
144
140
  width: 100%;
141
+ cursor: pointer;
145
142
  list-style: none;
146
143
  display: flex;
147
144
  flex-direction: row;
148
145
  align-items: center;
149
146
  justify-content: flex-start;
150
147
  flex-wrap: wrap;
148
+ row-gap: var(--space-between-legend-items);
151
149
 
152
150
  & > li {
153
151
  margin-right: 1em;
154
- margin-bottom: 1em;
155
152
  white-space: wrap;
156
153
  display: flex;
157
154
  justify-content: center;
@@ -166,6 +163,9 @@
166
163
  }
167
164
  }
168
165
  }
166
+ .legend-container__ul.patterns-only {
167
+ margin-top: 10px;
168
+ }
169
169
  }
170
170
 
171
171
  .bottom .legend-container__ul--single-column:not(.vertical-sorted) {
@@ -30,7 +30,13 @@ const HexIcon: React.FC<HexIconProps> = props => {
30
30
  )
31
31
  }
32
32
  return (
33
- <Group top={centroid[1] - 5} left={centroid[0] - iconSize} color={textColor} textAnchor='start' key={`hex--${item.key}-${item.value}-${index}`}>
33
+ <Group
34
+ top={centroid[1] - 5}
35
+ left={centroid[0] - iconSize}
36
+ color={textColor}
37
+ textAnchor='start'
38
+ key={`hex--${item.key}-${item.value}-${index}`}
39
+ >
34
40
  {item.shape === 'Arrow Down' && <AiOutlineArrowDown />}
35
41
  {item.shape === 'Arrow Up' && <AiOutlineArrowUp />}
36
42
  {item.shape === 'Arrow Right' && <AiOutlineArrowRight />}
@@ -1,6 +1,7 @@
1
1
  import React, { useContext } from 'react'
2
2
  import ConfigContext from '../../../../context'
3
3
  import { MapContext } from '../../../../types/MapContext'
4
+ import { getGeoFillColor } from '../../../../helpers/colors'
4
5
 
5
6
  interface CountyOutputProps {
6
7
  counties: any[]
@@ -11,7 +12,10 @@ interface CountyOutputProps {
11
12
  }
12
13
 
13
14
  const CountyOutput: React.FC<CountyOutputProps> = ({ path, counties, scale, geoStrokeColor, tooltipId }) => {
14
- const { applyTooltipsToGeo, applyLegendToRow, displayGeoName, state, data, geoClickHandler } = useContext<MapContext>(ConfigContext)
15
+ const { applyTooltipsToGeo, applyLegendToRow, displayGeoName, state, data, geoClickHandler } =
16
+ useContext<MapContext>(ConfigContext)
17
+
18
+ const geoFillColor = getGeoFillColor(state)
15
19
  return (
16
20
  <>
17
21
  {counties.map(county => {
@@ -50,19 +54,49 @@ const CountyOutput: React.FC<CountyOutputProps> = ({ path, counties, scale, geoS
50
54
  }
51
55
 
52
56
  // When to add pointer cursor
53
- if ((state.columns.navigate && geoData[state.columns.navigate.name]) || state.tooltips.appearanceType === 'hover') {
57
+ if (
58
+ (state.columns.navigate && geoData[state.columns.navigate.name]) ||
59
+ state.tooltips.appearanceType === 'hover'
60
+ ) {
54
61
  styles.cursor = 'pointer'
55
62
  }
56
63
 
57
64
  return (
58
- <g key={`key--${county.id}`} className={`county county--${geoDisplayName.split(' ').join('')} county--${geoData[state.columns.geo.name]}`} style={styles} onClick={() => geoClickHandler(geoDisplayName, geoData)} data-tooltip-id={`tooltip__${tooltipId}`} data-tooltip-html={toolTip}>
59
- <path tabIndex={-1} className={`county`} stroke={geoStrokeColor} d={countyPath} strokeWidth={0.75 / scale} />
65
+ <g
66
+ key={`key--${county.id}`}
67
+ className={`county county--${geoDisplayName.split(' ').join('')} county--${
68
+ geoData[state.columns.geo.name]
69
+ }`}
70
+ style={styles}
71
+ onClick={() => geoClickHandler(geoDisplayName, geoData)}
72
+ data-tooltip-id={`tooltip__${tooltipId}`}
73
+ data-tooltip-html={toolTip}
74
+ >
75
+ <path
76
+ tabIndex={-1}
77
+ className={`county`}
78
+ stroke={geoStrokeColor}
79
+ d={countyPath}
80
+ strokeWidth={0.75 / scale}
81
+ />
60
82
  </g>
61
83
  )
62
84
  } else {
63
85
  return (
64
- <g key={`key--${county.id}`} className={`county county--${geoDisplayName.split(' ').join('')}`} style={{ fill: '#e6e6e6' }} data-tooltip-id={`tooltip__${tooltipId}`} data-tooltip-html={toolTip}>
65
- <path tabIndex={-1} className={`county`} stroke={geoStrokeColor} d={countyPath} strokeWidth={0.75 / scale} />
86
+ <g
87
+ key={`key--${county.id}`}
88
+ className={`county county--${geoDisplayName.split(' ').join('')}`}
89
+ style={{ fill: geoFillColor }}
90
+ data-tooltip-id={`tooltip__${tooltipId}`}
91
+ data-tooltip-html={toolTip}
92
+ >
93
+ <path
94
+ tabIndex={-1}
95
+ className={`county`}
96
+ stroke={geoStrokeColor}
97
+ d={countyPath}
98
+ strokeWidth={0.75 / scale}
99
+ />
66
100
  </g>
67
101
  )
68
102
  }
@@ -1,6 +1,7 @@
1
1
  import { useContext } from 'react'
2
2
  import { mesh, Topology } from 'topojson-client'
3
3
  import ConfigContext from '../../../../context'
4
+ import { getGeoFillColor, getGeoStrokeColor } from '../../../../helpers/colors'
4
5
 
5
6
  type StateOutputProps = {
6
7
  topoData: Topology
@@ -15,12 +16,19 @@ const StateOutput: React.FC<StateOutputProps> = ({ topoData, path, scale, stateT
15
16
  return s.properties.name === state.general.statePicked.stateName
16
17
  })
17
18
 
18
- const geoStrokeColor = state.general.geoBorderColor === 'darkGray' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.7)'
19
+ const geoStrokeColor = getGeoStrokeColor(state)
20
+ const geoFillColor = getGeoFillColor(state)
19
21
 
20
22
  let stateLines = path(mesh(topoData, geo[0]))
21
23
 
22
24
  return (
23
- <g key={'single-state'} className='single-state' style={{ fill: '#E6E6E6' }} stroke={geoStrokeColor} strokeWidth={0.95 / scale}>
25
+ <g
26
+ key={'single-state'}
27
+ className='single-state'
28
+ style={{ fill: geoFillColor }}
29
+ stroke={geoStrokeColor}
30
+ strokeWidth={0.95 / scale}
31
+ >
24
32
  <path tabIndex={-1} className='state-path' d={stateLines} />
25
33
  </g>
26
34
  )
@@ -1,5 +1,5 @@
1
1
  import { useContext } from 'react'
2
- import { geoCentroid, geoPath } from 'd3-geo'
2
+ import { geoCentroid } from 'd3-geo'
3
3
  import ConfigContext from './../../../../context'
4
4
  import { MapContext } from './../../../../types/MapContext'
5
5
  import HexIcon from '../HexIcon'
@@ -31,7 +31,19 @@ const nudges = {
31
31
 
32
32
  // todo: combine hexagonLabel & geoLabel functions
33
33
  // todo: move geoLabel functions outside of components for reusability
34
- const TerritoryHexagon = ({ label, text, stroke, strokeWidth, textColor, territory, territoryData, ...props }) => {
34
+ const TerritoryHexagon = ({
35
+ dataTooltipHtml,
36
+ dataTooltipId,
37
+ handleShapeClick,
38
+ label,
39
+ stroke,
40
+ strokeWidth,
41
+ territory,
42
+ territoryData,
43
+ text,
44
+ textColor,
45
+ ...props
46
+ }) => {
35
47
  const { state } = useContext<MapContext>(ConfigContext)
36
48
 
37
49
  const isHex = state.general.displayAsHex
@@ -115,7 +127,17 @@ const TerritoryHexagon = ({ label, text, stroke, strokeWidth, textColor, territo
115
127
  let y = state.hexMap.type === 'shapes' ? '30%' : '50%'
116
128
  return (
117
129
  <>
118
- <Text fontSize={14} x={'50%'} y={y} style={{ fill: 'currentColor', stroke: 'initial', fontWeight: 400, opacity: 1, fillOpacity: 1 }} textAnchor='middle' verticalAnchor='middle'>
130
+ <Text
131
+ fontSize={14}
132
+ x={'50%'}
133
+ y={y}
134
+ style={{ fill: 'currentColor', stroke: 'initial', fontWeight: 400, opacity: 1, fillOpacity: 1 }}
135
+ textAnchor='middle'
136
+ verticalAnchor='middle'
137
+ onClick={handleShapeClick}
138
+ data-tooltip-id={dataTooltipId}
139
+ data-tooltip-html={dataTooltipHtml}
140
+ >
119
141
  {abbr.substring(3)}
120
142
  </Text>
121
143
  {state.general.displayAsHex && state.hexMap.type === 'shapes' && getArrowDirection(territoryData, geo, true)}
@@ -127,21 +149,44 @@ const TerritoryHexagon = ({ label, text, stroke, strokeWidth, textColor, territo
127
149
 
128
150
  return (
129
151
  <g>
130
- <line x1={centroid[0]} y1={centroid[1]} x2={centroid[0] + dx} y2={centroid[1] + dy} stroke='rgba(0,0,0,.5)' strokeWidth={1} />
131
- <text x={4} strokeWidth='0' fontSize={13} style={{ fill: '#202020' }} alignmentBaseline='middle' transform={`translate(${centroid[0] + dx}, ${centroid[1] + dy})`}>
152
+ <line
153
+ x1={centroid[0]}
154
+ y1={centroid[1]}
155
+ x2={centroid[0] + dx}
156
+ y2={centroid[1] + dy}
157
+ stroke='rgba(0,0,0,.5)'
158
+ strokeWidth={1}
159
+ />
160
+ <text
161
+ x={4}
162
+ strokeWidth='0'
163
+ fontSize={13}
164
+ style={{ fill: '#202020' }}
165
+ alignmentBaseline='middle'
166
+ transform={`translate(${centroid[0] + dx}, ${centroid[1] + dy})`}
167
+ onClick={handleShapeClick}
168
+ data-tooltip-id={dataTooltipId}
169
+ data-tooltip-html={dataTooltipHtml}
170
+ >
132
171
  {abbr.substring(3)}
133
172
  </text>
134
173
  </g>
135
174
  )
136
175
  }
137
176
 
138
- return territoryData && (
139
- <svg viewBox='0 0 45 51' className='territory-wrapper--hex'>
140
- <g {...props}>
141
- <polygon stroke={stroke} strokeWidth={strokeWidth} points='22 0 44 12.702 44 38.105 22 50.807 0 38.105 0 12.702' />
142
- {state.general.displayAsHex && hexagonLabel(territoryData, stroke, false)}
143
- </g>
144
- </svg>
177
+ return (
178
+ territoryData && (
179
+ <svg viewBox='-1 -1 46 53' className='territory-wrapper--hex'>
180
+ <g {...props} data-tooltip-html={dataTooltipHtml} data-tooltip-id={dataTooltipId} onClick={handleShapeClick}>
181
+ <polygon
182
+ stroke={stroke}
183
+ strokeWidth={strokeWidth}
184
+ points='22 0 44 12.702 44 38.105 22 50.807 0 38.105 0 12.702'
185
+ />
186
+ {state.general.displayAsHex && hexagonLabel(territoryData, stroke, false)}
187
+ </g>
188
+ </svg>
189
+ )
145
190
  )
146
191
  }
147
192
 
@@ -2,47 +2,121 @@ import { useContext } from 'react'
2
2
  import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
3
3
  import ConfigContext from './../../../../context'
4
4
  import { type MapContext } from '../../../../types/MapContext'
5
- import { patternSizes } from './../../helpers/patternSizes'
5
+ import { patternSizes } from '../../helpers/patternSizes'
6
6
  import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
7
+ import { type TerritoryShape } from './TerritoryShape'
7
8
 
8
- const TerritoryRectangle = ({ label, text, stroke, strokeWidth, textColor, hasPattern, territory, ...props }) => {
9
- const { state, supportedTerritories } = useContext<MapContext>(ConfigContext)
9
+ const TerritoryRectangle: React.FC<TerritoryShape> = ({
10
+ dataTooltipId,
11
+ dataTooltipHtml,
12
+ handleShapeClick,
13
+ hasPattern,
14
+ label,
15
+ stroke,
16
+ strokeWidth,
17
+ territory,
18
+ text,
19
+ textColor,
20
+ backgroundColor,
21
+ ...props
22
+ }) => {
23
+ const { state } = useContext<MapContext>(ConfigContext)
10
24
  const { territoryData, ...otherProps } = props
25
+ const rectanglePath =
26
+ 'M42,0.5 C42.8284271,0.5 43.5,1.17157288 43.5,2 L43.5,2 L43.5,26 C43.5,26.8284271 42.8284271,27.5 42,27.5 L42,27.5 L3,27.5 C2.17157288,27.5 1.5,26.8284271 1.5,26 L1.5,26 L1.5,2 C1.5,1.17157288 2.17157288,0.5 3,0.5 L3,0.5 Z'
11
27
 
12
28
  return (
13
- <svg viewBox='0 0 45 28' key={territory} className={territory}>
14
- <g {...otherProps} strokeLinejoin='round' tabIndex={-1}>
15
- <path
16
- stroke={stroke}
17
- strokeWidth={strokeWidth}
18
- d='M40,0.5 C41.2426407,0.5 42.3676407,1.00367966 43.1819805,1.81801948 C43.9963203,2.63235931 44.5,3.75735931 44.5,5 L44.5,5 L44.5,23 C44.5,24.2426407 43.9963203,25.3676407 43.1819805,26.1819805 C42.3676407,26.9963203 41.2426407,27.5 40,27.5 L40,27.5 L5,27.5 C3.75735931,27.5 2.63235931,26.9963203 1.81801948,26.1819805 C1.00367966,25.3676407 0.5,24.2426407 0.5,23 L0.5,23 L0.5,5 C0.5,3.75735931 1.00367966,2.63235931 1.81801948,1.81801948 C2.63235931,1.00367966 3.75735931,0.5 5,0.5 L5,0.5 Z'
19
- {...otherProps}
20
- />
21
- <text textAnchor='middle' dominantBaseline='middle' x='50%' y='54%' fill={text} style={{ stroke: textColor, strokeWidth: 1 }} className='territory-text' paintOrder='stroke'>
29
+ <svg viewBox='0 0 45 29' key={territory} className={territory}>
30
+ <g
31
+ {...otherProps}
32
+ strokeLinejoin='round'
33
+ tabIndex={-1}
34
+ onClick={handleShapeClick}
35
+ data-tooltip-id={dataTooltipId}
36
+ data-tooltip-html={dataTooltipHtml}
37
+ >
38
+ <path stroke={stroke} strokeWidth={strokeWidth} d={rectanglePath} {...otherProps} />
39
+ <text
40
+ textAnchor='middle'
41
+ dominantBaseline='middle'
42
+ x='50%'
43
+ y='54%'
44
+ fill={text}
45
+ style={{ stroke: 'none' }}
46
+ className='territory-text'
47
+ paintOrder='stroke'
48
+ onClick={handleShapeClick}
49
+ data-tooltip-id={dataTooltipId}
50
+ data-tooltip-html={dataTooltipHtml}
51
+ >
22
52
  {label}
23
53
  </text>
24
54
 
25
55
  {state.map.patterns.map((patternData, patternIndex) => {
26
- const patternColor = getContrastColor('#FFF', props.style.fill)
27
- const hasMatchingValues = supportedTerritories[territory]?.includes(patternData?.dataValue)
56
+ const patternColor = patternData.color || getContrastColor('#FFF', backgroundColor)
57
+ const hasMatchingValues = patternData.dataValue === territoryData[patternData.dataKey]
28
58
 
29
59
  if (!hasMatchingValues) return null
30
60
  if (!patternData.pattern) return null
31
61
 
32
62
  return (
33
63
  <>
34
- {patternData?.pattern === 'waves' && <PatternWaves id={`territory-${patternData?.dataKey}--${patternIndex}`} height={patternSizes[patternData?.size] ?? 10} width={patternSizes[patternData?.size] ?? 10} fill={patternColor} complement />}
35
- {patternData?.pattern === 'circles' && <PatternCircles id={`territory-${patternData?.dataKey}--${patternIndex}`} height={patternSizes[patternData?.size] ?? 10} width={patternSizes[patternData?.size] ?? 10} fill={patternColor} complement />}
36
- {patternData?.pattern === 'lines' && <PatternLines id={`territory-${patternData?.dataKey}--${patternIndex}`} height={patternSizes[patternData?.size] ?? 6} width={patternSizes[patternData?.size] ?? 6} stroke={patternColor} strokeWidth={1} orientation={['diagonalRightToLeft']} />}
64
+ {patternData?.pattern === 'waves' && (
65
+ <PatternWaves
66
+ id={`territory-${territory}-${patternData?.dataKey}--${patternIndex}`}
67
+ height={patternSizes[patternData?.size] ?? 10}
68
+ width={patternSizes[patternData?.size] ?? 10}
69
+ fill={patternColor}
70
+ complement
71
+ />
72
+ )}
73
+ {patternData?.pattern === 'circles' && (
74
+ <PatternCircles
75
+ id={`territory-${territory}-${patternData?.dataKey}--${patternIndex}`}
76
+ height={patternSizes[patternData?.size] ?? 10}
77
+ width={patternSizes[patternData?.size] ?? 10}
78
+ fill={patternColor}
79
+ complement
80
+ />
81
+ )}
82
+ {patternData?.pattern === 'lines' && (
83
+ <PatternLines
84
+ id={`territory-${territory}-${patternData?.dataKey}--${patternIndex}`}
85
+ height={patternSizes[patternData?.size] ?? 6}
86
+ width={patternSizes[patternData?.size] ?? 6}
87
+ stroke={patternColor}
88
+ strokeWidth={1}
89
+ orientation={['diagonalRightToLeft']}
90
+ />
91
+ )}
37
92
  <path
38
93
  stroke={stroke}
39
94
  strokeWidth={strokeWidth}
40
- d='M40,0.5 C41.2426407,0.5 42.3676407,1.00367966 43.1819805,1.81801948 C43.9963203,2.63235931 44.5,3.75735931 44.5,5 L44.5,5 L44.5,23 C44.5,24.2426407 43.9963203,25.3676407 43.1819805,26.1819805 C42.3676407,26.9963203 41.2426407,27.5 40,27.5 L40,27.5 L5,27.5 C3.75735931,27.5 2.63235931,26.9963203 1.81801948,26.1819805 C1.00367966,25.3676407 0.5,24.2426407 0.5,23 L0.5,23 L0.5,5 C0.5,3.75735931 1.00367966,2.63235931 1.81801948,1.81801948 C2.63235931,1.00367966 3.75735931,0.5 5,0.5 L5,0.5 Z'
41
- fill={`url(#territory-${patternData?.dataKey}--${patternIndex})`}
95
+ d={rectanglePath}
96
+ fill={`url(#territory-${territory}-${patternData?.dataKey}--${patternIndex})`}
42
97
  color={patternData ? 'white' : textColor}
43
- className={[`territory-pattern-${patternData.dataKey}`, `territory-pattern-${patternData.dataKey}--${patternData.dataValue}`].join(' ')}
98
+ className={[
99
+ `territory-pattern-${patternData.dataKey}`,
100
+ `territory-pattern-${patternData.dataKey}--${patternData.dataValue}`
101
+ ].join(' ')}
44
102
  />
45
- <text textAnchor='middle' dominantBaseline='middle' x='50%' y='54%' fill={text} style={{ fill: patternData ? 'white' : 'black', stroke: patternData ? 'black' : textColor, strokeWidth: patternData ? 6 : 0 }} className='territory-text' paint-order='stroke'>
103
+ <text
104
+ textAnchor='middle'
105
+ dominantBaseline='middle'
106
+ x='50%'
107
+ y='54%'
108
+ fill={text}
109
+ style={{
110
+ fill: patternData ? 'white' : 'black',
111
+ stroke: patternData ? 'black' : textColor,
112
+ strokeWidth: patternData ? 6 : 0
113
+ }}
114
+ className='territory-text'
115
+ paint-order='stroke'
116
+ onClick={handleShapeClick}
117
+ data-tooltip-id={dataTooltipId}
118
+ data-tooltip-html={dataTooltipHtml}
119
+ >
46
120
  {label}
47
121
  </text>
48
122
  </>
@@ -0,0 +1,13 @@
1
+ export type TerritoryShape = {
2
+ handleShapeClick: () => void
3
+ dataTooltipHtml: string
4
+ dataTooltipId: string
5
+ hasPattern: boolean
6
+ label: string
7
+ stroke: string
8
+ strokeWidth: number
9
+ territory: string
10
+ territoryData: object
11
+ text: string
12
+ textColor: string
13
+ }