@cdc/map 4.24.5 → 4.24.7

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 (38) hide show
  1. package/dist/cdcmap.js +38987 -34368
  2. package/examples/annotation/index.json +552 -0
  3. package/examples/annotation/usa-map.json +900 -0
  4. package/index.html +4 -3
  5. package/package.json +6 -5
  6. package/src/CdcMap.tsx +54 -82
  7. package/src/_stories/CdcMap.stories.tsx +7 -0
  8. package/src/_stories/_mock/DEV-7286.json +165 -0
  9. package/src/components/Annotation/Annotation.Draggable.styles.css +18 -0
  10. package/src/components/Annotation/Annotation.Draggable.tsx +152 -0
  11. package/src/components/Annotation/AnnotationDropdown.styles.css +14 -0
  12. package/src/components/Annotation/AnnotationDropdown.tsx +69 -0
  13. package/src/components/Annotation/AnnotationList.styles.css +45 -0
  14. package/src/components/Annotation/AnnotationList.tsx +42 -0
  15. package/src/components/Annotation/index.tsx +11 -0
  16. package/src/components/EditorPanel/components/EditorPanel.tsx +3 -2
  17. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +358 -0
  18. package/src/components/EditorPanel/components/{Panel.PatternSettings.tsx → Panels/Panel.PatternSettings.tsx} +2 -2
  19. package/src/components/EditorPanel/components/{Panels.tsx → Panels/index.tsx} +3 -0
  20. package/src/components/Legend/components/Legend.tsx +3 -9
  21. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +4 -3
  22. package/src/components/UsaMap/components/UsaMap.County.tsx +48 -21
  23. package/src/components/UsaMap/components/UsaMap.Region.tsx +2 -0
  24. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +2 -0
  25. package/src/components/UsaMap/components/UsaMap.State.tsx +28 -19
  26. package/src/data/initial-state.js +4 -1
  27. package/src/helpers/generateColorsArray.ts +13 -0
  28. package/src/helpers/generateRuntimeLegendHash.ts +23 -0
  29. package/src/helpers/getUniqueValues.ts +19 -0
  30. package/src/helpers/hashObj.ts +25 -0
  31. package/src/helpers/tests/generateColorsArray.test.ts +18 -0
  32. package/src/helpers/tests/generateRuntimeLegendHash.test.ts +11 -0
  33. package/src/helpers/tests/hashObj.test.ts +10 -0
  34. package/src/scss/map.scss +6 -3
  35. package/src/types/MapContext.ts +2 -0
  36. package/LICENSE +0 -201
  37. package/src/test/CdcMap.test.jsx +0 -19
  38. /package/src/components/EditorPanel/components/{Panel.PatternSettings-style.css → Panels/Panel.PatternSettings-style.css} +0 -0
@@ -0,0 +1,358 @@
1
+ import React, { useContext } from 'react'
2
+
3
+ // CDC Core
4
+ import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
5
+ import Accordion from '@cdc/core/components/ui/Accordion'
6
+ import Button from '@cdc/core/components/elements/Button'
7
+ import { MapContext } from '../../../../types/MapContext'
8
+ import ConfigContext from '../../../../context'
9
+
10
+ // types
11
+ // styles
12
+
13
+ const PanelAnnotate: React.FC = props => {
14
+ const { state: config, setState: updateConfig, dimensions, isDraggingAnnotation } = useContext<MapContext>(ConfigContext)
15
+ const getColumns = (filter = true) => {
16
+ const columns = {}
17
+ config.data.forEach(row => {
18
+ Object.keys(row).forEach(columnName => (columns[columnName] = true))
19
+ })
20
+
21
+ if (filter) {
22
+ Object.keys(columns).forEach(key => {
23
+ if ((config.series && config.series.filter(series => series.dataKey === key).length > 0) || (config.confidenceKeys && Object.keys(config.confidenceKeys).includes(key))) {
24
+ delete columns[key]
25
+ }
26
+ })
27
+ }
28
+
29
+ return Object.keys(columns)
30
+ }
31
+
32
+ const handleAnnotationUpdate = (value, property, index) => {
33
+ const annotations = [...config?.annotations]
34
+ annotations[index][property] = value
35
+ annotations[index].savedDimensions = [dimensions[0] * 0.73, dimensions[1]]
36
+
37
+ updateConfig({
38
+ ...config,
39
+ annotations
40
+ })
41
+ }
42
+
43
+ const handleAddAnnotation = () => {
44
+ const svgContainer = document.querySelector('.map-container > section > svg, .map-container > section > canvas')?.getBoundingClientRect()
45
+ const newSvgDims = [svgContainer.width, svgContainer.height]
46
+
47
+ const newAnnotation = {
48
+ text: 'New Annotation',
49
+ snapToNearestPoint: false,
50
+ fontSize: 16,
51
+ show: {
52
+ desktop: true,
53
+ tablet: true,
54
+ mobile: true
55
+ },
56
+ markerType: 'arrow',
57
+ connectorType: 'line',
58
+ colors: {
59
+ label: 'black',
60
+ connector: 'black',
61
+ marker: 'black'
62
+ },
63
+ selected: true,
64
+ anchor: {
65
+ vertical: false,
66
+ horizontal: false
67
+ },
68
+ connectionType: 'line',
69
+ marker: 'arrow',
70
+ edit: {
71
+ subject: true,
72
+ label: true
73
+ },
74
+ seriesKey: '',
75
+ x: Number(newSvgDims?.[0]) / 2,
76
+ y: Number(newSvgDims?.[1] / 2),
77
+ xKey: null,
78
+ yKey: null,
79
+ dx: 0,
80
+ dy: 0,
81
+ opacity: '100',
82
+ savedDimensions: [dimensions[0] * 0.73, dimensions[1]]
83
+ }
84
+
85
+ const annotations = Array.isArray(config.annotations) ? config.annotations : []
86
+
87
+ updateConfig({
88
+ ...config,
89
+ annotations: [...annotations, newAnnotation]
90
+ })
91
+ }
92
+
93
+ const handleRemoveAnnotation = (annotationIndex: number) => {
94
+ const updated = config.annotations.filter((_, index) => index !== annotationIndex)
95
+ updateConfig({
96
+ ...config,
97
+ annotations: updated
98
+ })
99
+ }
100
+
101
+ return (
102
+ <Accordion>
103
+ <Accordion.Section title={props.name}>
104
+ <p>Dragging state: {isDraggingAnnotation ? 'Dragging' : 'Not dragging'}</p>
105
+
106
+ <label>
107
+ Show Annotation Dropdown
108
+ <input
109
+ type='checkbox'
110
+ checked={config?.general?.showAnnotationDropdown}
111
+ onClick={e => {
112
+ updateConfig({
113
+ ...config,
114
+ general: {
115
+ ...config.general,
116
+ showAnnotationDropdown: e.target.checked
117
+ }
118
+ })
119
+ }}
120
+ />
121
+ </label>
122
+
123
+ <label>
124
+ Annotation Dropdown Title:
125
+ <input
126
+ type='text'
127
+ style={{ marginBottom: '10px' }}
128
+ value={config?.general?.annotationDropdownText}
129
+ onChange={e => {
130
+ updateConfig({
131
+ ...config,
132
+ general: {
133
+ ...config.general,
134
+ annotationDropdownText: e.target.value
135
+ }
136
+ })
137
+ }}
138
+ />
139
+ </label>
140
+
141
+ {config?.annotations &&
142
+ config?.annotations.map((annotation, index) => (
143
+ <Accordion>
144
+ <Accordion.Section title={annotation.text ? annotation.text.substring(0, 15) + '...' : `Annotation ${index + 1}`}>
145
+ <div className='annotation-group'>
146
+ <label>
147
+ Annotation Text:
148
+ <textarea rows={5} value={annotation.text} onChange={e => handleAnnotationUpdate(e.target.value, 'text', index)} />
149
+ </label>
150
+ {/* <label>
151
+ Vertical Anchor
152
+ <input
153
+ type='checkbox'
154
+ checked={config?.annotations[index].anchor.vertical}
155
+ onClick={e => {
156
+ const updatedAnnotations = [...config?.annotations]
157
+ updatedAnnotations[index].anchor.vertical = e.target.checked
158
+ updateConfig({
159
+ ...config,
160
+ annotations: updatedAnnotations
161
+ })
162
+ }}
163
+ />
164
+ </label>
165
+ <label>
166
+ Horizontal Anchor
167
+ <input
168
+ type='checkbox'
169
+ checked={config?.annotations[index].anchor.horizontal}
170
+ onClick={e => {
171
+ const updatedAnnotations = [...config?.annotations]
172
+ updatedAnnotations[index].anchor.horizontal = e.target.checked
173
+ updateConfig({
174
+ ...config,
175
+ annotations: updatedAnnotations
176
+ })
177
+ }}
178
+ />
179
+ </label> */}
180
+
181
+ <label>
182
+ Opacity
183
+ <br />
184
+ <input
185
+ type='range'
186
+ onChange={e => {
187
+ const updatedAnnotations = [...config?.annotations]
188
+ updatedAnnotations[index].opacity = e.target.value
189
+ updateConfig({
190
+ ...config,
191
+ annotations: updatedAnnotations
192
+ })
193
+ }}
194
+ value={config?.annotations?.[index]?.opacity || '100'}
195
+ />
196
+ </label>
197
+
198
+ <label>
199
+ Edit Subject
200
+ <input
201
+ type='checkbox'
202
+ checked={config?.annotations[index]?.edit?.subject}
203
+ onClick={e => {
204
+ const updatedAnnotations = [...config?.annotations]
205
+ updatedAnnotations[index].edit.subject = e.target.checked
206
+ updateConfig({
207
+ ...config,
208
+ annotations: updatedAnnotations
209
+ })
210
+ }}
211
+ />
212
+ </label>
213
+ <label>
214
+ Edit Label
215
+ <input
216
+ type='checkbox'
217
+ checked={config?.annotations[index]?.edit?.label}
218
+ onClick={e => {
219
+ const updatedAnnotations = [...config?.annotations]
220
+ updatedAnnotations[index].edit.label = e.target.checked
221
+ updateConfig({
222
+ ...config,
223
+ annotations: updatedAnnotations
224
+ })
225
+ }}
226
+ />
227
+ </label>
228
+ <label>
229
+ Associated Series:
230
+ <select
231
+ onChange={e => {
232
+ const updatedAnnotations = [...config?.annotations]
233
+ updatedAnnotations[index].seriesKey = e.target.value
234
+ updateConfig({
235
+ ...config,
236
+ annotations: updatedAnnotations
237
+ })
238
+ }}
239
+ >
240
+ <option key='none' value='none'>
241
+ None
242
+ </option>
243
+ {getColumns(false).map((column, columnIndex) => {
244
+ return <option>{column}</option>
245
+ })}
246
+ </select>
247
+ </label>
248
+
249
+ <label>
250
+ Connection Type:
251
+ <select
252
+ onChange={e => {
253
+ const updatedAnnotations = [...config?.annotations]
254
+ updatedAnnotations[index].connectionType = e.target.value
255
+ updateConfig({
256
+ ...config,
257
+ annotations: updatedAnnotations
258
+ })
259
+ }}
260
+ >
261
+ {['curve', 'line', 'elbow', 'none'].map((side, index) => (
262
+ <option key={side} value={side}>
263
+ {side}
264
+ </option>
265
+ ))}
266
+ </select>
267
+ </label>
268
+
269
+ {annotation.connectionType === 'curve' && (
270
+ <label>
271
+ Line Type:
272
+ <select
273
+ onChange={e => {
274
+ const updatedAnnotations = [...config?.annotations]
275
+ updatedAnnotations[index].lineType = e.target.value
276
+ updateConfig({
277
+ ...config,
278
+ annotations: updatedAnnotations
279
+ })
280
+ }}
281
+ >
282
+ {Object.entries(approvedCurveTypes).map(([value, key]) => (
283
+ <option key={key} value={key}>
284
+ {value}
285
+ </option>
286
+ ))}
287
+ </select>
288
+ </label>
289
+ )}
290
+
291
+ {/* <label>
292
+ Connection Location:
293
+ <select
294
+ onChange={e => {
295
+ const updatedAnnotations = [...config?.annotations]
296
+ updatedAnnotations[index].connectionLocation = e.target.value
297
+ updateConfig({
298
+ ...config,
299
+ annotations: updatedAnnotations
300
+ })
301
+ }}
302
+ >
303
+ {['auto', 'left', 'top', 'bottom', 'right'].map((side, index) => (
304
+ <option key={side} value={side}>
305
+ {side}
306
+ </option>
307
+ ))}
308
+ </select>
309
+ </label> */}
310
+
311
+ <label>
312
+ Marker
313
+ <select
314
+ onChange={e => {
315
+ const updatedAnnotations = [...config?.annotations]
316
+ updatedAnnotations[index].marker = e.target.value
317
+ updateConfig({
318
+ ...config,
319
+ annotations: updatedAnnotations
320
+ })
321
+ }}
322
+ >
323
+ {['circle', 'arrow'].map((column, columnIndex) => {
324
+ return <option>{column}</option>
325
+ })}
326
+ </select>
327
+ </label>
328
+
329
+ {/* <label>
330
+ Snap to Nearest Point
331
+ <input
332
+ type='checkbox'
333
+ checked={config?.annotations[index]?.snapToNearestPoint}
334
+ onClick={e => {
335
+ const updatedAnnotations = [...config?.annotations]
336
+ updatedAnnotations[index].snapToNearestPoint = e.target.checked
337
+ updateConfig({
338
+ ...config,
339
+ annotations: updatedAnnotations
340
+ })
341
+ }}
342
+ />
343
+ </label> */}
344
+
345
+ <Button className='warn btn-warn btn btn-remove delete' onClick={() => handleRemoveAnnotation(index)}>
346
+ Delete Annotation
347
+ </Button>
348
+ </div>
349
+ </Accordion.Section>
350
+ </Accordion>
351
+ ))}
352
+ {config?.annotations?.length < 3 && <Button onClick={handleAddAnnotation}>Add Annotation</Button>}
353
+ </Accordion.Section>
354
+ </Accordion>
355
+ )
356
+ }
357
+
358
+ export default PanelAnnotate
@@ -1,7 +1,7 @@
1
1
  import { useContext } from 'react'
2
2
  import { Accordion, AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
3
- import ConfigContext from '../../../context'
4
- import { type MapContext } from '../../../types/MapContext'
3
+ import ConfigContext from '../../../../context'
4
+ import { type MapContext } from '../../../../types/MapContext'
5
5
  import Button from '@cdc/core/components/elements/Button'
6
6
  import Tooltip from '@cdc/core/components/ui/Tooltip'
7
7
  import Icon from '@cdc/core/components/ui/Icon'
@@ -1,6 +1,9 @@
1
+ import React from 'react'
2
+ import Annotate from './Panel.Annotate'
1
3
  import PatternSettings from './Panel.PatternSettings'
2
4
 
3
5
  const Panels = {
6
+ Annotate,
4
7
  PatternSettings
5
8
  }
6
9
 
@@ -34,7 +34,6 @@ const Legend = forwardRef((props, ref) => {
34
34
  } = useContext(ConfigContext)
35
35
 
36
36
  const { legend } = state
37
- const fontSize = ['sm', 'xs', 'xxs'].includes(viewport) ? { fontSize: '11px' } : null
38
37
 
39
38
  // Toggles if a legend is active and being applied to the map and data table.
40
39
  const toggleLegendActive = (i, legendLabel) => {
@@ -96,7 +95,6 @@ const Legend = forwardRef((props, ref) => {
96
95
  return (
97
96
  // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-noninteractive-element-interactions
98
97
  <li
99
- style={fontSize}
100
98
  className={handleListItemClass().join(' ')}
101
99
  key={idx}
102
100
  title={`Legend item ${legendLabel} - Click to disable`}
@@ -181,11 +179,7 @@ const Legend = forwardRef((props, ref) => {
181
179
  <aside id={skipId || 'legend'} className={legendClasses.aside.join(' ') || ''} role='region' aria-label='Legend' tabIndex={0} ref={ref}>
182
180
  <section className={legendClasses.section.join(' ') || ''} aria-label='Map Legend'>
183
181
  {legend.title && <h3 className={legendClasses.title.join(' ') || ''}>{parse(legend.title)}</h3>}
184
- {legend.dynamicDescription === false && legend.description && (
185
- <p style={fontSize} className={legendClasses.description.join(' ') || ''}>
186
- {parse(legend.description)}
187
- </p>
188
- )}
182
+ {legend.dynamicDescription === false && legend.description && <p className={legendClasses.description.join(' ') || ''}>{parse(legend.description)}</p>}
189
183
  {legend.dynamicDescription === true &&
190
184
  runtimeFilters.map((filter, idx) => {
191
185
  const lookupStr = `${idx},${filter.values.indexOf(String(filter.active))}`
@@ -195,7 +189,7 @@ const Legend = forwardRef((props, ref) => {
195
189
 
196
190
  if (desc.length > 0) {
197
191
  return (
198
- <p style={fontSize} key={`dynamic-description-${lookupStr}`} className={`dynamic-legend-description-${lookupStr}`}>
192
+ <p key={`dynamic-description-${lookupStr}`} className={`dynamic-legend-description-${lookupStr}`}>
199
193
  {desc}
200
194
  </p>
201
195
  )
@@ -229,7 +223,7 @@ const Legend = forwardRef((props, ref) => {
229
223
  {cityStyleShapes[shape.toLowerCase()]}
230
224
  </Group>
231
225
  </svg>
232
- <p style={fontSize}>{label}</p>
226
+ <p>{label}</p>
233
227
  </div>
234
228
  )
235
229
  )}
@@ -7,15 +7,16 @@ import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
7
7
 
8
8
  const TerritoryRectangle = ({ label, text, stroke, strokeWidth, textColor, hasPattern, territory, ...props }) => {
9
9
  const { state, supportedTerritories } = useContext<MapContext>(ConfigContext)
10
+ const { territoryData, ...otherProps } = props
10
11
 
11
12
  return (
12
- <svg viewBox='0 0 45 28'>
13
- <g {...props} strokeLinejoin='round' tabIndex={-1}>
13
+ <svg viewBox='0 0 45 28' key={territory} className={territory}>
14
+ <g {...otherProps} strokeLinejoin='round' tabIndex={-1}>
14
15
  <path
15
16
  stroke={stroke}
16
17
  strokeWidth={strokeWidth}
17
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'
18
- {...props}
19
+ {...otherProps}
19
20
  />
20
21
  <text textAnchor='middle' dominantBaseline='middle' x='50%' y='54%' fill={text} style={{ stroke: textColor, strokeWidth: 1 }} className='territory-text' paintOrder='stroke'>
21
22
  {label}
@@ -10,6 +10,7 @@ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
10
10
 
11
11
  import useMapLayers from '../../../hooks/useMapLayers'
12
12
  import ConfigContext from '../../../context'
13
+ import Annotation from '../../Annotation'
13
14
 
14
15
  const getCountyTopoURL = year => {
15
16
  return `https://www.cdc.gov/TemplatePackage/contrib/data/county-topography/cb_${year}_us_county_20m.json`
@@ -129,7 +130,8 @@ const CountyMap = props => {
129
130
  state,
130
131
  runtimeFilters,
131
132
  tooltipId,
132
- isEditor
133
+ tooltipRef,
134
+ container
133
135
  } = useContext(ConfigContext)
134
136
 
135
137
  // CREATE STATE LINES
@@ -186,7 +188,6 @@ const CountyMap = props => {
186
188
 
187
189
  const resetButton = useRef()
188
190
  const canvasRef = useRef()
189
- const tooltipRef = useRef()
190
191
 
191
192
  // If runtimeData is not defined, show loader
192
193
  if (!data || !isTopoReady(topoData, state, runtimeFilters)) {
@@ -238,8 +239,16 @@ const CountyMap = props => {
238
239
  }
239
240
  }
240
241
 
242
+ let focusIndex = -1;
243
+ for(let i = 0; i < topoData.mapData.length; i++){
244
+ if(topoData.mapData[i].id === clickedState.id){
245
+ focusIndex = i;
246
+ break;
247
+ }
248
+ }
249
+
241
250
  // Redraw with focus on state
242
- setFocus({ id: clickedState.id, center: geoCentroid(clickedState) })
251
+ setFocus({ id: clickedState.id, index: focusIndex, center: geoCentroid(clickedState) })
243
252
  }
244
253
 
245
254
  if (state.general.type === 'us-geocode') {
@@ -266,17 +275,21 @@ const CountyMap = props => {
266
275
  const canvasBounds = canvas.getBoundingClientRect()
267
276
  const x = e.clientX - canvasBounds.left
268
277
  const y = e.clientY - canvasBounds.top
278
+ const containerBounds = container?.getBoundingClientRect()
279
+ const tooltipX = e.clientX - (containerBounds?.left || 0)
280
+ const tooltipY = e.clientY - (containerBounds?.top || 0)
269
281
  let pointCoordinates = topoData.projection.invert([x, y])
270
282
 
271
283
  const currentTooltipIndex = parseInt(tooltipRef.current.getAttribute('data-index'))
272
284
  const geoRadius = (state.visual.geoCodeCircleSize || 5) * (focus.id ? 2 : 1)
273
285
 
286
+ const context = canvas.getContext('2d')
287
+ const path = geoPath(topoData.projection, context)
288
+
274
289
  // Handle standard county map hover
275
290
  if (state.general.type !== 'us-geocode') {
276
291
  //If no tooltip is shown, or if the current geo associated with the tooltip shown is no longer containing the mouse, then rerender the tooltip
277
292
  if (isNaN(currentTooltipIndex) || !geoContains(topoData.mapData[currentTooltipIndex], pointCoordinates)) {
278
- const context = canvas.getContext('2d')
279
- const path = geoPath(topoData.projection, context)
280
293
  if (!isNaN(currentTooltipIndex) && applyLegendToRow(data[topoData.mapData[currentTooltipIndex].id])) {
281
294
  context.fillStyle = applyLegendToRow(data[topoData.mapData[currentTooltipIndex].id])[0]
282
295
  context.strokeStyle = geoStrokeColor
@@ -323,8 +336,14 @@ const CountyMap = props => {
323
336
  }
324
337
 
325
338
  tooltipRef.current.style.display = 'block'
326
- tooltipRef.current.style.top = e.clientY + 'px'
327
- tooltipRef.current.style.left = isEditor ? Number(e.clientX - 350) + 'px' : e.clientX + 'px'
339
+ tooltipRef.current.style.top = tooltipY + 'px'
340
+ if(tooltipX > containerBounds.width / 2) {
341
+ tooltipRef.current.style.transform = 'translate(-100%, -50%)'
342
+ tooltipRef.current.style.left = (tooltipX - 5) + 'px'
343
+ } else {
344
+ tooltipRef.current.style.transform = 'translate(0, -50%)'
345
+ tooltipRef.current.style.left = (tooltipX + 5) + 'px'
346
+ }
328
347
  tooltipRef.current.innerHTML = applyTooltipsToGeo(displayGeoName(county.id), data[county.id])
329
348
  tooltipRef.current.setAttribute('data-index', countyIndex)
330
349
  } else {
@@ -349,7 +368,7 @@ const CountyMap = props => {
349
368
  let hoveredGeoIndex
350
369
  for (let i = 0; i < runtimeKeys.length; i++) {
351
370
  const pixelCoords = topoData.projection([data[runtimeKeys[i]][state.columns.longitude.name], data[runtimeKeys[i]][state.columns.latitude.name]])
352
- if (state.visual.cityStyle === 'circle' && pixelCoords && Math.sqrt(Math.pow(pixelCoords[0] - x, 2) + Math.pow(pixelCoords[1] - y, 2)) < geoRadius) {
371
+ if (state.visual.cityStyle === 'circle' && pixelCoords && Math.sqrt(Math.pow(pixelCoords[0] - x, 2) + Math.pow(pixelCoords[1] - y, 2)) < geoRadius && applyLegendToRow(data[runtimeKeys[i]])) {
353
372
  hoveredGeo = data[runtimeKeys[i]]
354
373
  hoveredGeoIndex = i
355
374
  break
@@ -357,7 +376,7 @@ const CountyMap = props => {
357
376
 
358
377
  if (state.visual.cityStyle === 'pin' && pixelCoords) {
359
378
  const distance = Math.hypot(pixelCoords[0] - x, pixelCoords[1] - y)
360
- if (distance < 15) {
379
+ if (distance < 15 && applyLegendToRow(data[runtimeKeys[i]])) {
361
380
  hoveredGeo = data[runtimeKeys[i]]
362
381
  hoveredGeoIndex = i
363
382
  break
@@ -365,10 +384,16 @@ const CountyMap = props => {
365
384
  }
366
385
  }
367
386
 
368
- if (hoveredGeo && applyLegendToRow(hoveredGeo)) {
387
+ if (hoveredGeo) {
369
388
  tooltipRef.current.style.display = 'block'
370
- tooltipRef.current.style.top = e.clientY + 'px'
371
- tooltipRef.current.style.left = e.clientX + 'px'
389
+ tooltipRef.current.style.top = tooltipY + 'px'
390
+ if(tooltipX > containerBounds.width / 2) {
391
+ tooltipRef.current.style.transform = 'translate(-100%, -50%)'
392
+ tooltipRef.current.style.left = (tooltipX - 5) + 'px'
393
+ } else {
394
+ tooltipRef.current.style.transform = 'translate(0, -50%)'
395
+ tooltipRef.current.style.left = (tooltipX + 5) + 'px'
396
+ }
372
397
  tooltipRef.current.innerHTML = applyTooltipsToGeo(displayGeoName(hoveredGeo[state.columns.geo.name]), hoveredGeo)
373
398
  tooltipRef.current.setAttribute('data-index', hoveredGeoIndex)
374
399
  } else {
@@ -376,6 +401,14 @@ const CountyMap = props => {
376
401
  tooltipRef.current.setAttribute('data-index', null)
377
402
  }
378
403
  }
404
+
405
+ if (focus.index !== -1) {
406
+ context.strokeStyle = 'black'
407
+ context.lineWidth = 1
408
+ context.beginPath()
409
+ path(topoData.mapData[focus.index])
410
+ context.stroke()
411
+ }
379
412
  }
380
413
 
381
414
  // Redraws canvas. Takes as parameters the fips id of a state to center on and the [lat,long] center of that state
@@ -411,7 +444,6 @@ const CountyMap = props => {
411
444
  context.strokeStyle = geoStrokeColor
412
445
  context.lineWidth = lineWidth
413
446
 
414
- let focusIndex = -1
415
447
  // Iterates through each state/county topo and renders it
416
448
  topoData.mapData.forEach((geo, i) => {
417
449
  // If invalid geo item, don't render
@@ -424,11 +456,6 @@ const CountyMap = props => {
424
456
  // Gets numeric data associated with the topo data for this state/county
425
457
  const geoData = data[geo.id]
426
458
 
427
- // Marks that the focused state was found for the logic below
428
- if (geo.id === focus.id) {
429
- focusIndex = i
430
- }
431
-
432
459
  // Renders state/county
433
460
  const legendValues = geoData !== undefined ? applyLegendToRow(geoData) : false
434
461
  context.fillStyle = legendValues && state.general.type !== 'us-geocode' ? legendValues[0] : '#EEE'
@@ -439,11 +466,11 @@ const CountyMap = props => {
439
466
  })
440
467
 
441
468
  // If the focused state is found in the geo data, render it with a thicker outline
442
- if (focusIndex !== -1) {
469
+ if (focus.index !== -1) {
443
470
  context.strokeStyle = 'black'
444
471
  context.lineWidth = 2
445
472
  context.beginPath()
446
- path(topoData.mapData[focusIndex])
473
+ path(topoData.mapData[focus.index])
447
474
  context.stroke()
448
475
  }
449
476
 
@@ -481,6 +508,7 @@ const CountyMap = props => {
481
508
  }
482
509
 
483
510
  const drawCircle = (circle, context) => {
511
+ context.lineWidth = lineWidth
484
512
  context.fillStyle = circle.color
485
513
  context.beginPath()
486
514
  context.arc(circle.x, circle.y, circle.geoRadius, 0, 2 * Math.PI)
@@ -524,7 +552,6 @@ const CountyMap = props => {
524
552
  onClick={canvasClick}
525
553
  className='county-map-canvas'
526
554
  ></canvas>
527
- <div ref={tooltipRef} id={`tooltip__${tooltipId}`} className='tooltip' style={{ background: `rgba(255,255,255,${state.tooltips.opacity / 100})` }}></div>
528
555
  <button className={`btn btn--reset`} onClick={onReset} ref={resetButton} tabIndex='0'>
529
556
  Reset Zoom
530
557
  </button>
@@ -10,6 +10,7 @@ import { Mercator } from '@visx/geo'
10
10
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
11
11
  import topoJSON from '../data/us-regions-topo-2.json'
12
12
  import ConfigContext from '../../../context'
13
+ import Annotation from '../../Annotation'
13
14
 
14
15
  const { features: unitedStates } = feature(topoJSON, topoJSON.objects.regions)
15
16
 
@@ -252,6 +253,7 @@ const UsaRegionMap = props => {
252
253
  <Mercator data={focusedStates} scale={620} translate={[1500, 735]}>
253
254
  {({ features, projection }) => constructGeoJsx(features, projection)}
254
255
  </Mercator>
256
+ {state.annotations.length > 0 && <Annotation.Draggable />}
255
257
  </svg>
256
258
  {territories.length > 0 && (
257
259
  <section className='territories'>
@@ -10,6 +10,7 @@ import colorPalettes from '@cdc/core/data/colorPalettes'
10
10
  import { geoAlbersUsaTerritories } from 'd3-composite-projections'
11
11
  import CityList from '../../CityList'
12
12
  import ConfigContext from '../../../context'
13
+ import Annotation from '../../Annotation'
13
14
 
14
15
  // SVG ITEMS
15
16
  const WIDTH = 880
@@ -296,6 +297,7 @@ const SingleStateMap = props => {
296
297
  )
297
298
  }}
298
299
  </CustomProjection>
300
+ {state.annotations.length > 0 && <Annotation.Draggable />}
299
301
  </svg>
300
302
  )}
301
303
  {!state.general.statePicked && 'No State Picked'}