@cdc/map 2.6.2 → 2.6.4

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 (67) hide show
  1. package/dist/cdcmap.js +37 -29
  2. package/examples/bubble-us.json +363 -0
  3. package/examples/bubble-world.json +427 -0
  4. package/examples/default-county.json +105 -0
  5. package/examples/default-hex.json +475 -0
  6. package/examples/default-single-state.json +109 -0
  7. package/examples/default-usa-regions.json +118 -0
  8. package/examples/default-usa.json +744 -603
  9. package/examples/default-world-data.json +1450 -0
  10. package/examples/default-world.json +5 -3
  11. package/examples/example-city-state.json +510 -0
  12. package/examples/example-world-map.json +1596 -0
  13. package/examples/gender-rate-map.json +1 -0
  14. package/examples/private/atsdr.json +439 -0
  15. package/examples/private/atsdr_new.json +436 -0
  16. package/examples/private/bubble.json +285 -0
  17. package/examples/private/default-world-data.json +1444 -0
  18. package/examples/private/default.json +968 -0
  19. package/examples/private/map.csv +60 -0
  20. package/examples/private/mdx.json +210 -0
  21. package/examples/private/regions.json +52 -0
  22. package/examples/private/wcmsrd-13881-data.json +2858 -0
  23. package/examples/private/wcmsrd-13881.json +5823 -0
  24. package/examples/private/wcmsrd-test.json +268 -0
  25. package/examples/private/world.json +1580 -0
  26. package/examples/private/worldmap.json +1490 -0
  27. package/package.json +11 -7
  28. package/src/CdcMap.js +726 -202
  29. package/src/components/BubbleList.js +240 -0
  30. package/src/components/CityList.js +22 -3
  31. package/src/components/CountyMap.js +557 -0
  32. package/src/components/DataTable.js +85 -24
  33. package/src/components/EditorPanel.js +2455 -1204
  34. package/src/components/Geo.js +1 -1
  35. package/src/components/Sidebar.js +5 -5
  36. package/src/components/SingleStateMap.js +326 -0
  37. package/src/components/UsaMap.js +41 -9
  38. package/src/components/UsaRegionMap.js +319 -0
  39. package/src/components/WorldMap.js +112 -35
  40. package/src/data/abbreviations.js +57 -0
  41. package/src/data/country-coordinates.js +250 -0
  42. package/src/data/county-map-halfquality.json +58453 -0
  43. package/src/data/county-map-quarterquality.json +1 -0
  44. package/src/data/county-topo.json +1 -0
  45. package/src/data/dfc-map.json +1 -0
  46. package/src/data/initial-state.js +21 -4
  47. package/src/data/newtest.json +1 -0
  48. package/src/data/state-abbreviations.js +60 -0
  49. package/src/data/state-coordinates.js +55 -0
  50. package/src/data/supported-geos.js +3592 -163
  51. package/src/data/test.json +1 -0
  52. package/src/data/us-regions-topo-2.json +360525 -0
  53. package/src/data/us-regions-topo.json +37894 -0
  54. package/src/hooks/useActiveElement.js +19 -0
  55. package/src/hooks/useColorPalette.ts +96 -0
  56. package/src/index.html +35 -20
  57. package/src/index.js +8 -4
  58. package/src/scss/datatable.scss +2 -1
  59. package/src/scss/editor-panel.scss +76 -55
  60. package/src/scss/main.scss +10 -1
  61. package/src/scss/map.scss +257 -121
  62. package/src/scss/sidebar.scss +0 -1
  63. package/uploads/upload-example-city-state.json +392 -0
  64. package/uploads/upload-example-world-data.json +1490 -0
  65. package/LICENSE +0 -201
  66. package/src/data/color-palettes.js +0 -191
  67. package/src/images/map-folded.svg +0 -1
@@ -0,0 +1,557 @@
1
+ import React, { useState, useEffect, memo, useRef } from 'react';
2
+ import Loading from '@cdc/core/components/Loading';
3
+ /** @jsx jsx */
4
+ import { jsx } from '@emotion/react';
5
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
6
+ import { geoCentroid, geoPath } from 'd3-geo';
7
+ import { feature, mesh } from 'topojson-client';
8
+ import { CustomProjection } from '@visx/geo';
9
+ import colorPalettes from '../../../core/data/colorPalettes'
10
+ import { geoAlbersUsaTerritories } from 'd3-composite-projections';
11
+ import testJSON from '../data/dfc-map.json';
12
+ import { abbrs } from '../data/abbreviations';
13
+
14
+ const offsets = {
15
+ Vermont: [50, -8],
16
+ 'New Hampshire': [34, 5],
17
+ Massachusetts: [30, -5],
18
+ 'Rhode Island': [28, 4],
19
+ Connecticut: [35, 16],
20
+ 'New Jersey': [42, 0],
21
+ Delaware: [33, 0],
22
+ Maryland: [47, 10],
23
+ 'District of Columbia': [30, 20],
24
+ 'Puerto Rico': [10, -20],
25
+ 'Virgin Islands': [10, -10],
26
+ Guam: [10, -5],
27
+ 'American Samoa': [10, 0],
28
+ };
29
+
30
+ // SVG ITEMS
31
+ const WIDTH = 880;
32
+ const HEIGHT = 500;
33
+ const PADDING = 25;
34
+
35
+ // DATA
36
+ let { features: counties } = feature(testJSON, testJSON.objects.counties);
37
+ let { features: states } = feature(testJSON, testJSON.objects.states);
38
+
39
+ // CONSTANTS
40
+ const STATE_BORDER = '#c0cad4';
41
+ const STATE_INACTIVE_FILL = '#F4F7FA';
42
+
43
+ // CREATE STATE LINES
44
+ const projection = geoAlbersUsaTerritories().translate([WIDTH / 2, HEIGHT / 2]);
45
+ const path = geoPath().projection(projection);
46
+ const stateLines = path(mesh(testJSON, testJSON.objects.states));
47
+
48
+ const nudges = {
49
+ 'US-FL': [15, 3],
50
+ 'US-AK': [0, -8],
51
+ 'US-CA': [-10, 0],
52
+ 'US-NY': [5, 0],
53
+ 'US-MI': [13, 20],
54
+ 'US-LA': [-10, -3],
55
+ 'US-HI': [-10, 10],
56
+ 'US-ID': [0, 10],
57
+ 'US-WV': [-2, 2],
58
+ };
59
+
60
+ function CountyMapChecks(prevState, nextState) {
61
+ const equalColumnName = prevState.state.general.type && nextState.state.general.type;
62
+ const equalNavColumn = prevState.state.columns.navigate && nextState.state.columns.navigate;
63
+ const equalLegend = prevState.runtimeLegend === nextState.runtimeLegend;
64
+ const equalBorderColors = prevState.state.general.geoBorderColor === nextState.state.general.geoBorderColor; // update when geoborder color changes
65
+ const equalMapColors = prevState.state.color === nextState.state.color; // update when map colors change
66
+ const equalData = prevState.data === nextState.data; // update when data changes
67
+ return equalMapColors && equalData && equalBorderColors && equalLegend && equalColumnName && equalNavColumn ? true : false;
68
+ }
69
+
70
+ const CountyMap = (props) => {
71
+
72
+ let mapData = states.concat(counties);
73
+
74
+ const {
75
+ state,
76
+ applyTooltipsToGeo,
77
+ data,
78
+ geoClickHandler,
79
+ applyLegendToRow,
80
+ displayGeoName,
81
+ rebuildTooltips,
82
+ containerEl,
83
+ } = props;
84
+
85
+ useEffect(() => {
86
+ if(containerEl) {
87
+ if (containerEl.className.indexOf('loaded') === -1) {
88
+ containerEl.className += ' loaded';
89
+ }
90
+ }
91
+ });
92
+
93
+ // Use State
94
+ const [scale, setScale] = useState(0.85);
95
+ const [startingLineWidth, setStartingLineWidth] = useState(1.3);
96
+ const [translate, setTranslate] = useState([0, 0]);
97
+ const [mapColorPalette, setMapColorPalette] = useState(colorPalettes[state.color] || '#fff');
98
+ const [focusedState, setFocusedState] = useState(null);
99
+ const [showLabel, setShowLabels] = useState(true);
100
+
101
+ const resetButton = useRef();
102
+ const focusedBorderPath = useRef();
103
+ const stateLinesPath = useRef();
104
+ const mapGroup = useRef();
105
+
106
+ let focusedBorderColor = mapColorPalette[3];
107
+ let geoStrokeColor = state.general.geoBorderColor === 'darkGray' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.7)';
108
+
109
+ // Use Effect
110
+ useEffect(() => rebuildTooltips());
111
+
112
+ const geoLabel = (geo, projection) => {
113
+ let [x, y] = projection(geoCentroid(geo));
114
+ let abbr = abbrs[geo.properties.name];
115
+ if (abbr === 'NJ') x += 3;
116
+ if (undefined === abbr) return null;
117
+ let [dx, dy] = offsets[geo.properties.name];
118
+
119
+ return (
120
+ <>
121
+ <line className='abbrLine' x1={x} y1={y} x2={x + dx} y2={y + dy} stroke='black' strokeWidth={0.85} />
122
+ <text
123
+ className='abbrText'
124
+ x={4}
125
+ strokeWidth='0'
126
+ fontSize={13}
127
+ style={{ fill: '#202020' }}
128
+ alignmentBaseline='middle'
129
+ transform={`translate(${x + dx}, ${y + dy})`}
130
+ >
131
+ {abbr}
132
+ </text>
133
+ </>
134
+ );
135
+ };
136
+
137
+ const focusGeo = (geoKey, geo) => {
138
+ if (!geoKey) {
139
+ console.log('County Map: no geoKey provided to focusGeo');
140
+ return;
141
+ }
142
+
143
+ // 1) Get the state the county is in.
144
+ let myState = states.find((s) => s.id === geoKey);
145
+
146
+ // 2) Set projections translation & scale to the geographic center of the passed geo.
147
+ const projection = geoAlbersUsaTerritories().translate([WIDTH / 2, HEIGHT / 2]);
148
+ const newProjection = projection.fitExtent(
149
+ [
150
+ [PADDING, PADDING],
151
+ [WIDTH - PADDING, HEIGHT - PADDING],
152
+ ],
153
+ myState
154
+ );
155
+
156
+ // 3) Gets the new scale
157
+ const newScale = newProjection.scale();
158
+ const hypot = Math.hypot(880, 500);
159
+ const newScaleWithHypot = newScale / 1070;
160
+
161
+ // 4) Pull the x & y out, divide by half the viewport for some reason
162
+ let [x, y] = newProjection.translate();
163
+ x = x - WIDTH / 2;
164
+ y = y - HEIGHT / 2;
165
+
166
+ // 5) Debug if needed
167
+ const debug = {
168
+ width: WIDTH,
169
+ height: HEIGHT,
170
+ beginX: 0,
171
+ beginY: 0,
172
+ hypot: hypot,
173
+ x: x,
174
+ y: y,
175
+ newScale: newScale,
176
+ newScaleWithHypot: newScaleWithHypot,
177
+ geoKey: geoKey,
178
+ geo: geo,
179
+ };
180
+ //console.table(debug)
181
+
182
+ mapGroup.current.setAttribute('transform', `translate(${[x, y]}) scale(${newScaleWithHypot})`);
183
+ resetButton.current.style.display = 'block';
184
+
185
+ // set the states border
186
+ let allStates = document.querySelectorAll('.state path');
187
+ let allCounties = document.querySelectorAll('.county path');
188
+ let currentState = document.querySelector(`.state--${myState.id}`);
189
+ let otherStates = document.querySelectorAll(`.state:not(.state--${myState.id})`);
190
+ let svgContainer = document.querySelector('.svg-container')
191
+ svgContainer.setAttribute('data-scaleZoom', newScaleWithHypot)
192
+
193
+ const state = testJSON.objects.states.geometries.filter((el, index) => {
194
+ return el.id === myState.id;
195
+ });
196
+
197
+ const focusedStateLine = path(mesh(testJSON, state[0]));
198
+
199
+ currentState.style.display = 'none';
200
+
201
+ allStates.forEach((state) => (state.style.strokeWidth = 0.75 / newScaleWithHypot));
202
+ allCounties.forEach((county) => (county.style.strokeWidth = 0.75 / newScaleWithHypot));
203
+ otherStates.forEach((el) => (el.style.display = 'block'));
204
+
205
+ // State Line Updates
206
+ stateLinesPath.current.setAttribute('stroke-width', 0.75 / newScaleWithHypot);
207
+ stateLinesPath.current.setAttribute('stroke', geoStrokeColor);
208
+
209
+ // Set Focus Border
210
+ focusedBorderPath.current.style.display = 'block';
211
+ focusedBorderPath.current.setAttribute('d', focusedStateLine);
212
+ //focusedBorderPath.current.setAttribute('stroke-width', 0.75 / newScaleWithHypot);
213
+ //focusedBorderPath.current.setAttribute('stroke', focusedBorderColor)
214
+ };
215
+
216
+ const onReset = (e) => {
217
+ e.preventDefault();
218
+ const svg = document.querySelector('.svg-container')
219
+
220
+ svg.setAttribute('data-scaleZoom', 0)
221
+
222
+
223
+ const allStates = document.querySelectorAll('.state path');
224
+ const allCounties = document.querySelectorAll('.county path');
225
+
226
+ stateLinesPath.current.setAttribute('stroke', geoStrokeColor);
227
+ stateLinesPath.current.setAttribute('stroke-width', startingLineWidth);
228
+
229
+ let otherStates = document.querySelectorAll(`.state--inactive`);
230
+ otherStates.forEach((el) => (el.style.display = 'none'));
231
+ allCounties.forEach((el) => (el.style.strokeWidth = 0.85));
232
+ allStates.forEach((state) => state.setAttribute('stroke-width', .75 / .85 ));
233
+
234
+ mapGroup.current.setAttribute('transform', `translate(${[0, 0]}) scale(${0.85})`);
235
+
236
+ // reset button
237
+ resetButton.current.style.display = 'none';
238
+ };
239
+
240
+ function setStateLeave() {
241
+ focusedBorderPath.current.setAttribute('d', '');
242
+ focusedBorderPath.current.setAttribute('stroke', '');
243
+ focusedBorderPath.current.setAttribute('stroke-width', 0.75 / scale);
244
+ }
245
+
246
+ function setStateEnter(id) {
247
+ const svg = document.querySelector('.svg-container')
248
+ const scale = svg.getAttribute('data-scaleZoom');
249
+
250
+ let myState = id.substring(0, 2);
251
+ const allStates = document.querySelectorAll('.state path');
252
+
253
+
254
+ let state = testJSON.objects.states.geometries.filter((el, index) => {
255
+ return el.id === myState;
256
+ });
257
+
258
+ let focusedStateLine = path(mesh(testJSON, state[0]));
259
+ focusedBorderPath.current.style.display = 'block';
260
+ focusedBorderPath.current.setAttribute('d', focusedStateLine);
261
+ focusedBorderPath.current.setAttribute('stroke', '#000');
262
+
263
+ if(scale) {
264
+ allStates.forEach( state => state.setAttribute('stroke-width', 0.75 / scale))
265
+ focusedBorderPath.current.setAttribute('stroke-width', 0.75 / scale );
266
+ }
267
+
268
+ }
269
+
270
+ const StateLines = memo(({ stateLines, lineWidth, geoStrokeColor }) => {
271
+ return (
272
+ <g className='stateLines' key='state-line'>
273
+ <path
274
+ id='stateLinesPath'
275
+ ref={stateLinesPath}
276
+ d={stateLines}
277
+ strokeWidth={lineWidth}
278
+ stroke={geoStrokeColor}
279
+ fill='none'
280
+ fillOpacity='1'
281
+ />
282
+ </g>
283
+ );
284
+ });
285
+
286
+ const FocusedStateBorder = memo(() => {
287
+ return (
288
+ <g id='focusedBorder' key='focusedStateBorder'>
289
+ <path
290
+ ref={focusedBorderPath}
291
+ d=''
292
+ strokeWidth=''
293
+ stroke={focusedBorderColor}
294
+ fill='none'
295
+ fillOpacity='1'
296
+ />
297
+ </g>
298
+ );
299
+ });
300
+
301
+ const CountyOutput = memo(({ geographies, counties }) => {
302
+ let output = [];
303
+ output.push(
304
+ counties.map(({ feature: geo, path = '' }) => {
305
+ const key = geo.id + '-group';
306
+
307
+ // COUNTY GROUPS
308
+ let styles = {
309
+ fillOpacity: '1',
310
+ fill: '#E6E6E6',
311
+ cursor: 'default',
312
+ };
313
+
314
+ // Map the name from the geo data with the appropriate key for the processed data
315
+ let geoKey = geo.id;
316
+
317
+ if (!geoKey) return null;
318
+
319
+ const geoData = data[geoKey];
320
+
321
+ let legendColors;
322
+
323
+ // Once we receive data for this geographic item, setup variables.
324
+ if (geoData !== undefined) {
325
+ legendColors = applyLegendToRow(geoData);
326
+ }
327
+
328
+ const geoDisplayName = displayGeoName(geoKey);
329
+
330
+ // For some reason, these two geos are breaking the display.
331
+ if (geoDisplayName === 'Franklin City' || geoDisplayName === 'Waynesboro') return null;
332
+
333
+ // If a legend applies, return it with appropriate information.
334
+ if (legendColors && legendColors[0] !== '#000000') {
335
+ const tooltip = applyTooltipsToGeo(geoDisplayName, geoData);
336
+
337
+ styles = {
338
+ fill: legendColors[0],
339
+ cursor: 'default',
340
+ '&:hover': {
341
+ fill: legendColors[1],
342
+ },
343
+ '&:active': {
344
+ fill: legendColors[2],
345
+ },
346
+ };
347
+
348
+ // When to add pointer cursor
349
+ if (
350
+ (state.columns.navigate && geoData[state.columns.navigate.name]) ||
351
+ state.tooltips.appearanceType === 'hover'
352
+ ) {
353
+ styles.cursor = 'pointer';
354
+ }
355
+ let stateFipsCode = geoData[state.columns.geo.name].substring(0, 2);
356
+
357
+ return (
358
+ <g
359
+ tabIndex="-1"
360
+ data-for='tooltip'
361
+ data-tip={tooltip}
362
+ key={`county--${key}`}
363
+ className={`county county--${geoDisplayName.split(' ').join('')} county--${
364
+ geoData[state.columns.geo.name]
365
+ }`}
366
+ css={styles}
367
+ onMouseEnter={() => {
368
+ setStateEnter(geo.id);
369
+ }}
370
+ onMouseLeave={() => {
371
+ setStateLeave();
372
+ }}
373
+ onClick={
374
+ // default
375
+ (e) => {
376
+ e.stopPropagation();
377
+ e.nativeEvent.stopImmediatePropagation();
378
+ geoClickHandler(geoDisplayName, geoData);
379
+ focusGeo(stateFipsCode, geo);
380
+ }
381
+ }
382
+ >
383
+ <path
384
+ tabIndex={-1}
385
+ className={`county county--${geoDisplayName}`}
386
+ stroke={geoStrokeColor}
387
+ d={path}
388
+ strokeWidth='.5'
389
+ />
390
+ </g>
391
+ );
392
+ }
393
+
394
+ // default county
395
+ return (
396
+ <g
397
+ key={`county--default-${key}`}
398
+ className={`county county--${geoDisplayName}`}
399
+ css={styles}
400
+ strokeWidth=''
401
+ onMouseEnter={() => {
402
+ setStateEnter(geo.id);
403
+ }}
404
+ onMouseLeave={() => {
405
+ setStateLeave();
406
+ }}
407
+ onClick={
408
+ // default
409
+ (e) => {
410
+ e.stopPropagation();
411
+ e.nativeEvent.stopImmediatePropagation();
412
+ let countyFipsCode = geo.id;
413
+ let stateFipsCode = countyFipsCode.substring(0, 2);
414
+ focusGeo(stateFipsCode, geo);
415
+ }
416
+ }
417
+ >
418
+ <path tabIndex={-1} className='single-geo' stroke={geoStrokeColor} d={path} strokeWidth='.85' />
419
+ </g>
420
+ );
421
+ })
422
+ );
423
+ return output;
424
+ });
425
+
426
+ const StateOutput = memo(({ geographies, states }) => {
427
+ let output = [];
428
+ output.push(
429
+ states.map(({ feature: geo, path = '' }) => {
430
+ const key = geo.id + '-group';
431
+
432
+ // Map the name from the geo data with the appropriate key for the processed data
433
+ let geoKey = geo.id;
434
+
435
+ if (!geoKey) return;
436
+
437
+ const geoData = data[geoKey];
438
+
439
+ let legendColors;
440
+
441
+ // Once we receive data for this geographic item, setup variables.
442
+ if (geoData !== undefined) {
443
+ legendColors = applyLegendToRow(geoData);
444
+ }
445
+
446
+ const geoDisplayName = displayGeoName(geoKey);
447
+
448
+ let stateStyles = {
449
+ cursor: 'default',
450
+ stroke: STATE_BORDER,
451
+ strokeWidth: 0.75 / scale,
452
+ display: !focusedState ? 'none' : focusedState && focusedState !== geo.id ? 'block' : 'none',
453
+ fill: focusedState && focusedState !== geo.id ? STATE_INACTIVE_FILL : 'none',
454
+ };
455
+
456
+ let stateSelectedStyles = {
457
+ fillOpacity: 1,
458
+ cursor: 'default',
459
+ };
460
+
461
+ let stateClasses = ['state', `state--${geo.properties.name}`, `state--${geo.id}`];
462
+ focusedState === geo.id ? stateClasses.push('state--focused') : stateClasses.push('state--inactive');
463
+
464
+ return (
465
+ <React.Fragment key={`state--${key}`}>
466
+ <g key={`state--${key}`} className={stateClasses.join(' ')} style={stateStyles} tabIndex="-1">
467
+ <>
468
+ <path
469
+ tabIndex={-1}
470
+ className='state-path'
471
+ d={path}
472
+ fillOpacity={`${focusedState !== geo.id ? '1' : '0'}`}
473
+ fill={STATE_INACTIVE_FILL}
474
+ css={stateSelectedStyles}
475
+ onClick={(e) => {
476
+ e.stopPropagation();
477
+ e.nativeEvent.stopImmediatePropagation();
478
+ focusGeo(geo.id, geo);
479
+ }}
480
+ onMouseEnter={(e) => {
481
+ e.target.attributes.fill.value = colorPalettes[state.color][3];
482
+ }}
483
+ onMouseLeave={(e) => {
484
+ e.target.attributes.fill.value = STATE_INACTIVE_FILL;
485
+ }}
486
+ />
487
+ </>
488
+ </g>
489
+ <g key={`label--${key}`}>
490
+ {offsets[geo.properties.name] &&
491
+ geoLabel(geo, geoAlbersUsaTerritories().translate([WIDTH / 2, HEIGHT / 2]))}
492
+ </g>
493
+ </React.Fragment>
494
+ );
495
+ })
496
+ );
497
+ return output;
498
+ });
499
+
500
+ // Constructs and displays markup for all geos on the map (except territories right now)
501
+ const constructGeoJsx = (geographies, projection) => {
502
+ const states = geographies.slice(0, 56);
503
+ const counties = geographies.slice(56);
504
+ let geosJsx = [];
505
+ geosJsx.push(<CountyOutput geographies={geographies} counties={counties} key="county-key" />);
506
+ geosJsx.push(<StateOutput geographies={geographies} states={states} key="state-key" />);
507
+ geosJsx.push(
508
+ <StateLines
509
+ key='stateLines'
510
+ lineWidth={startingLineWidth}
511
+ geoStrokeColor={geoStrokeColor}
512
+ stateLines={stateLines}
513
+ />
514
+ );
515
+ geosJsx.push(<FocusedStateBorder key="focused-border-key" />);
516
+ return geosJsx;
517
+ };
518
+ if(!data) <Loading />
519
+ return (
520
+ <ErrorBoundary component='CountyMap'>
521
+ <svg viewBox={`0 0 ${WIDTH} ${HEIGHT}`} preserveAspectRatio='xMinYMin' className='svg-container' data-scale={scale ? scale : ''} data-translate={translate ? translate : ''}>
522
+ <rect
523
+ className='background center-container ocean'
524
+ width={WIDTH}
525
+ height={HEIGHT}
526
+ fillOpacity={1}
527
+ fill='white'
528
+ onClick={(e) => onReset(e)}
529
+ tabIndex="0"
530
+ ></rect>
531
+ <CustomProjection
532
+ data={mapData}
533
+ translate={[WIDTH / 2, HEIGHT / 2]}
534
+ projection={geoAlbersUsaTerritories}
535
+ >
536
+ {({ features, projection }) => {
537
+ return (
538
+ <g
539
+ ref={mapGroup}
540
+ className='countyMapGroup'
541
+ transform={`translate(${translate}) scale(${scale})`}
542
+ key='countyMapGroup'
543
+ >
544
+ {constructGeoJsx(features, geoAlbersUsaTerritories)}
545
+ </g>
546
+ );
547
+ }}
548
+ </CustomProjection>
549
+ </svg>
550
+ <button className={`btn btn--reset`} onClick={onReset} ref={resetButton} style={{ display: 'none' }} tabIndex="0">
551
+ Reset Zoom
552
+ </button>
553
+ </ErrorBoundary>
554
+ );
555
+ };
556
+
557
+ export default memo(CountyMap, CountyMapChecks);