@cdc/map 4.24.10 → 4.24.12-2

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 (57) hide show
  1. package/dist/cdcmap.js +33447 -32769
  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/DEV-9989.json +229 -0
  8. package/examples/private/ardi.json +180 -0
  9. package/examples/private/colors 2.json +416 -0
  10. package/examples/private/colors.json +416 -0
  11. package/examples/private/colors.json.zip +0 -0
  12. package/examples/private/customColors.json +45348 -0
  13. package/examples/private/default-patterns.json +867 -0
  14. package/examples/private/test.json +1632 -0
  15. package/index.html +4 -5
  16. package/package.json +3 -3
  17. package/src/CdcMap.tsx +93 -83
  18. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +67 -0
  19. package/src/_stories/CdcMap.Legend.stories.tsx +40 -0
  20. package/src/_stories/CdcMap.Patterns.stories.tsx +29 -0
  21. package/src/_stories/CdcMap.stories.tsx +59 -0
  22. package/src/_stories/UsaMap.NoData.stories.tsx +19 -0
  23. package/src/_stories/_mock/custom-layer-map.json +1117 -0
  24. package/src/_stories/_mock/default-patterns.json +865 -0
  25. package/src/_stories/_mock/example-city-state.json +858 -0
  26. package/src/_stories/_mock/usa-state-gradient.json +238 -0
  27. package/src/_stories/_mock/wastewater-map.json +208 -0
  28. package/src/components/CityList.tsx +5 -2
  29. package/src/components/EditorPanel/components/EditorPanel.tsx +68 -295
  30. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +27 -23
  31. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +75 -16
  32. package/src/components/Legend/components/Legend.tsx +42 -20
  33. package/src/components/Legend/components/index.scss +24 -24
  34. package/src/components/UsaMap/components/HexIcon.tsx +7 -1
  35. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +40 -6
  36. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +10 -2
  37. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +57 -12
  38. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +95 -21
  39. package/src/components/UsaMap/components/Territory/TerritoryShape.ts +13 -0
  40. package/src/components/UsaMap/components/UsaMap.County.tsx +11 -13
  41. package/src/components/UsaMap/components/UsaMap.Region.tsx +59 -16
  42. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +2 -1
  43. package/src/components/UsaMap/components/UsaMap.State.tsx +61 -63
  44. package/src/components/UsaMap/helpers/shapes.ts +5 -4
  45. package/src/components/WorldMap/WorldMap.tsx +77 -16
  46. package/src/data/initial-state.js +2 -1
  47. package/src/helpers/applyColorToLegend.ts +80 -0
  48. package/src/helpers/colors.ts +23 -0
  49. package/src/hooks/useTooltip.ts +9 -6
  50. package/src/scss/editor-panel.scss +0 -3
  51. package/src/scss/filters.scss +1 -9
  52. package/src/scss/main.scss +0 -5
  53. package/src/scss/map.scss +11 -63
  54. package/src/types/MapConfig.ts +8 -2
  55. package/src/types/MapContext.ts +1 -0
  56. package/examples/default-patterns.json +0 -579
  57. package/src/scss/datatable.scss +0 -6
@@ -1,5 +1,11 @@
1
1
  import { useContext } from 'react'
2
- import { Accordion, AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
2
+ import {
3
+ Accordion,
4
+ AccordionItem,
5
+ AccordionItemHeading,
6
+ AccordionItemPanel,
7
+ AccordionItemButton
8
+ } from 'react-accessible-accordion'
3
9
  import ConfigContext from '../../../../context'
4
10
  import { type MapContext } from '../../../../types/MapContext'
5
11
  import Button from '@cdc/core/components/elements/Button'
@@ -41,7 +47,11 @@ const PatternSettings = ({ name }: PanelProps) => {
41
47
  }
42
48
 
43
49
  /** Updates the map pattern at a given index */
44
- const handleUpdateGeoPattern = (value: string, index: number, keyToUpdate: 'dataKey' | 'pattern' | 'dataValue' | 'size' | 'label' | 'color') => {
50
+ const handleUpdateGeoPattern = (
51
+ value: string,
52
+ index: number,
53
+ keyToUpdate: 'dataKey' | 'pattern' | 'dataValue' | 'size' | 'label' | 'color'
54
+ ) => {
45
55
  const { features: unitedStates } = feature(topoJSON, topoJSON.objects.states)
46
56
  const updatedPatterns = [...state.map.patterns]
47
57
 
@@ -68,7 +78,9 @@ const PatternSettings = ({ name }: PanelProps) => {
68
78
 
69
79
  // Log a warning if the contrast check fails
70
80
  if (!contrastCheck) {
71
- console.warn(`COVE: pattern contrast check failed on ${geoData?.[state.columns.geo.name]} for ${patternData.dataKey} with:
81
+ console.warn(`COVE: pattern contrast check failed on ${geoData?.[state.columns.geo.name]} for ${
82
+ patternData.dataKey
83
+ } with:
72
84
  pattern color: ${patternColor}
73
85
  contrast: ${getColorContrast(currentFill, patternColor)}
74
86
  `)
@@ -78,7 +90,9 @@ const PatternSettings = ({ name }: PanelProps) => {
78
90
  })
79
91
  })
80
92
 
81
- const editorErrorMessage = updatedPatterns.some(pattern => pattern.contrastCheck === false) ? 'One or more patterns do not pass the WCAG 2.1 contrast ratio of 3:1.' : ''
93
+ const editorErrorMessage = updatedPatterns.some(pattern => pattern.contrastCheck === false)
94
+ ? 'One or more patterns do not pass the WCAG 2.1 contrast ratio of 3:1.'
95
+ : ''
82
96
 
83
97
  // Update the state with the new patterns and error message
84
98
  setState(prevState => ({
@@ -116,7 +130,12 @@ const PatternSettings = ({ name }: PanelProps) => {
116
130
  <AccordionItemButton>{name}</AccordionItemButton>
117
131
  </AccordionItemHeading>
118
132
  <AccordionItemPanel>
119
- {patterns.length > 0 && <Alert type={checkPatternContrasts() ? 'success' : 'danger'} message='Pattern colors must comply with <br /> <a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a> 3:1 contrast ratio.' />}
133
+ {patterns.length > 0 && (
134
+ <Alert
135
+ type={checkPatternContrasts() ? 'success' : 'danger'}
136
+ message='Pattern colors must comply with <br /> <a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a> 3:1 contrast ratio.'
137
+ />
138
+ )}
120
139
  <br />
121
140
 
122
141
  {patterns &&
@@ -133,13 +152,26 @@ const PatternSettings = ({ name }: PanelProps) => {
133
152
  <Accordion allowZeroExpanded key={`accordion-pattern--${patternIndex}`}>
134
153
  <AccordionItem>
135
154
  <AccordionItemHeading>
136
- <AccordionItemButton>{pattern.dataKey ? `${pattern.dataKey}: ${pattern.dataValue ?? 'No Value'}` : 'Select Column'}</AccordionItemButton>
155
+ <AccordionItemButton>
156
+ {pattern.dataKey ? `${pattern.dataKey}: ${pattern.dataValue ?? 'No Value'}` : 'Select Column'}
157
+ </AccordionItemButton>
137
158
  </AccordionItemHeading>
138
159
  <AccordionItemPanel>
139
160
  <>
140
- {pattern.contrastCheck ?? true ? <Alert type='success' message='This pattern passes contrast checks' /> : <Alert type='danger' message='Error: <a href="https://webaim.org/resources/contrastchecker/" target="_blank"> Review Color Contrast</a>' />}{' '}
161
+ {pattern.contrastCheck ?? true ? (
162
+ <Alert type='success' message='This pattern passes contrast checks' />
163
+ ) : (
164
+ <Alert
165
+ type='danger'
166
+ message='Error: <a href="https://webaim.org/resources/contrastchecker/" target="_blank"> Review Color Contrast</a>'
167
+ />
168
+ )}{' '}
141
169
  <label htmlFor={`pattern-dataKey--${patternIndex}`}>Data Key:</label>
142
- <select id={`pattern-dataKey--${patternIndex}`} value={pattern.dataKey !== '' ? pattern.dataKey : 'Select'} onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataKey')}>
170
+ <select
171
+ id={`pattern-dataKey--${patternIndex}`}
172
+ value={pattern.dataKey !== '' ? pattern.dataKey : 'Select'}
173
+ onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataKey')}
174
+ >
143
175
  {/* TODO: sort these? */}
144
176
  {dataKeyOptions.map((d, index) => {
145
177
  return (
@@ -151,14 +183,28 @@ const PatternSettings = ({ name }: PanelProps) => {
151
183
  </select>
152
184
  <label htmlFor={`pattern-dataValue--${patternIndex}`}>
153
185
  Data Value:
154
- <input type='text' onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataValue')} id={`pattern-dataValue--${patternIndex}`} value={pattern.dataValue === '' ? '' : pattern.dataValue} />
186
+ <input
187
+ type='text'
188
+ onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataValue')}
189
+ id={`pattern-dataValue--${patternIndex}`}
190
+ value={pattern.dataValue === '' ? '' : pattern.dataValue}
191
+ />
155
192
  </label>
156
193
  <label htmlFor={`pattern-label--${patternIndex}`}>
157
194
  Label (optional):
158
- <input type='text' onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'label')} id={`pattern-dataValue--${patternIndex}`} value={pattern.label === '' ? '' : pattern.label} />
195
+ <input
196
+ type='text'
197
+ onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'label')}
198
+ id={`pattern-dataValue--${patternIndex}`}
199
+ value={pattern.label === '' ? '' : pattern.label}
200
+ />
159
201
  </label>
160
202
  <label htmlFor={`pattern-type--${patternIndex}`}>Pattern Type:</label>
161
- <select id={`pattern-type--${patternIndex}`} value={pattern?.pattern} onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'pattern')}>
203
+ <select
204
+ id={`pattern-type--${patternIndex}`}
205
+ value={pattern?.pattern}
206
+ onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'pattern')}
207
+ >
162
208
  {patternTypes.map((patternName, index) => (
163
209
  <option value={patternName} key={index}>
164
210
  {patternName}
@@ -166,7 +212,11 @@ const PatternSettings = ({ name }: PanelProps) => {
166
212
  ))}
167
213
  </select>
168
214
  <label htmlFor={`pattern-size--${patternIndex}`}>Pattern Size:</label>
169
- <select id={`pattern-size--${patternIndex}`} value={pattern?.size} onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'size')}>
215
+ <select
216
+ id={`pattern-size--${patternIndex}`}
217
+ value={pattern?.size}
218
+ onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'size')}
219
+ >
170
220
  {['small', 'medium', 'large'].map((size, index) => (
171
221
  <option value={size} key={index}>
172
222
  {size}
@@ -178,16 +228,25 @@ const PatternSettings = ({ name }: PanelProps) => {
178
228
  Pattern Color
179
229
  <Tooltip style={{ textTransform: 'none' }}>
180
230
  <Tooltip.Target>
181
- <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
231
+ <Icon
232
+ display='question'
233
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
234
+ />
182
235
  </Tooltip.Target>
183
236
  <Tooltip.Content>
184
237
  <p>{`If this setting is used, it is the reponsibility of the visualization author to verify the visualization colors meet WCAG 3:1 contrast ratios.`}</p>
185
238
  </Tooltip.Content>
186
239
  </Tooltip>
187
- <input type='text' value={pattern.color || ''} id='patternColor' name='patternColor' onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'color')} />
240
+ <input
241
+ type='text'
242
+ value={pattern.color || ''}
243
+ id='patternColor'
244
+ name='patternColor'
245
+ onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'color')}
246
+ />
188
247
  </label>
189
248
  </div>
190
- <Button onClick={e => handleRemovePattern(patternIndex)} className='btn btn--remove warn'>
249
+ <Button onClick={e => handleRemovePattern(patternIndex)} className='btn btn-danger'>
191
250
  Remove Pattern
192
251
  </Button>
193
252
  </>
@@ -196,7 +255,7 @@ const PatternSettings = ({ name }: PanelProps) => {
196
255
  </Accordion>
197
256
  )
198
257
  })}
199
- <button className='btn full-width' onClick={handleAddGeoPattern}>
258
+ <button className='btn btn-primary full-width mt-2' onClick={handleAddGeoPattern}>
200
259
  Add Geo Pattern
201
260
  </button>
202
261
  </AccordionItemPanel>
@@ -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