@cdc/map 4.24.12 → 4.25.1

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 (60) hide show
  1. package/dist/cdcmap.js +51010 -49337
  2. package/examples/annotation/index.json +1 -1
  3. package/examples/custom-map-layers.json +1 -1
  4. package/examples/default-geocode.json +2 -2
  5. package/examples/private/DEV-9989.json +229 -0
  6. package/examples/private/ardi.json +180 -0
  7. package/examples/private/colors 2.json +416 -0
  8. package/examples/private/colors.json +416 -0
  9. package/examples/private/colors.json.zip +0 -0
  10. package/examples/private/customColors.json +45348 -0
  11. package/examples/private/mmr.json +246 -0
  12. package/examples/private/test.json +1632 -0
  13. package/index.html +12 -14
  14. package/package.json +8 -3
  15. package/src/CdcMap.tsx +126 -394
  16. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +9 -0
  17. package/src/_stories/CdcMap.stories.tsx +1 -1
  18. package/src/_stories/GoogleMap.stories.tsx +19 -0
  19. package/src/_stories/_mock/DEV-10148.json +859 -0
  20. package/src/_stories/_mock/DEV-9989.json +229 -0
  21. package/src/_stories/_mock/example-city-state.json +1 -1
  22. package/src/_stories/_mock/google-map.json +819 -0
  23. package/src/components/Annotation/Annotation.Draggable.tsx +34 -43
  24. package/src/components/Annotation/AnnotationDropdown.tsx +4 -4
  25. package/src/components/CityList.tsx +2 -2
  26. package/src/components/DataTable.tsx +8 -9
  27. package/src/components/EditorPanel/components/EditorPanel.tsx +96 -270
  28. package/src/components/GoogleMap/components/GoogleMap.tsx +67 -0
  29. package/src/components/GoogleMap/index.tsx +3 -0
  30. package/src/components/Legend/components/Legend.tsx +40 -30
  31. package/src/components/Legend/components/LegendItem.Hex.tsx +7 -3
  32. package/src/components/Legend/components/index.scss +22 -16
  33. package/src/components/Modal.tsx +6 -5
  34. package/src/components/NavigationMenu.tsx +5 -4
  35. package/src/components/UsaMap/components/TerritoriesSection.tsx +56 -0
  36. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +3 -3
  37. package/src/components/UsaMap/components/UsaMap.County.tsx +1 -1
  38. package/src/components/UsaMap/components/UsaMap.Region.tsx +12 -8
  39. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +2 -2
  40. package/src/components/UsaMap/components/UsaMap.State.tsx +23 -29
  41. package/src/components/WorldMap/WorldMap.tsx +3 -5
  42. package/src/context.ts +0 -12
  43. package/src/data/initial-state.js +2 -2
  44. package/src/data/supported-geos.js +23 -3
  45. package/src/helpers/applyColorToLegend.ts +3 -3
  46. package/src/helpers/closeModal.ts +9 -0
  47. package/src/helpers/handleMapAriaLabels.ts +38 -0
  48. package/src/helpers/indexOfIgnoreType.ts +8 -0
  49. package/src/helpers/navigationHandler.ts +21 -0
  50. package/src/helpers/toTitleCase.ts +44 -0
  51. package/src/helpers/validateFipsCodeLength.ts +30 -0
  52. package/src/hooks/useResizeObserver.ts +42 -0
  53. package/src/hooks/useTooltip.ts +4 -2
  54. package/src/index.jsx +1 -0
  55. package/src/scss/editor-panel.scss +2 -1
  56. package/src/scss/filters.scss +0 -5
  57. package/src/scss/main.scss +57 -61
  58. package/src/scss/map.scss +1 -13
  59. package/src/types/MapConfig.ts +20 -11
  60. package/src/types/MapContext.ts +4 -12
@@ -26,6 +26,7 @@ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
26
26
  import Icon from '@cdc/core/components/ui/Icon'
27
27
  import InputToggle from '@cdc/core/components/inputs/InputToggle'
28
28
  import Tooltip from '@cdc/core/components/ui/Tooltip'
29
+ import VizFilterEditor from '@cdc/core/components/EditorPanel/VizFilterEditor'
29
30
 
30
31
  // Assets
31
32
  import UsaGraphic from '@cdc/core/assets/icon-map-usa.svg'
@@ -37,28 +38,24 @@ import usaDefaultConfig from '../../../../examples/default-usa.json'
37
38
  import countyDefaultConfig from '../../../../examples/default-county.json'
38
39
  import useMapLayers from '../../../hooks/useMapLayers.tsx'
39
40
 
40
- import { useFilters } from '@cdc/core/components/Filters'
41
-
42
41
  import HexSetting from './HexShapeSettings.jsx'
43
42
  import ConfigContext from '../../../context.ts'
44
43
  import { MapContext } from '../../../types/MapContext.js'
45
44
  import { TextField } from './Inputs'
46
45
  import Alert from '@cdc/core/components/Alert'
46
+ import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
47
+ import { Select } from '@cdc/core/components/EditorPanel/Inputs'
47
48
 
48
49
  // Todo: move to useReducer, seperate files out.
49
50
  const EditorPanel = ({ columnsRequiredChecker }) => {
50
51
  // prettier-ignore
51
52
  const {
52
- changeFilterActive,
53
- columnsInData = [],
54
53
  isDashboard,
55
54
  isDebug,
56
- isEditor,
57
55
  loadConfig,
58
56
  runtimeFilters,
59
57
  runtimeLegend,
60
58
  setParentConfig,
61
- setRuntimeFilters,
62
59
  setState,
63
60
  state,
64
61
  tooltipId,
@@ -70,6 +67,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
70
67
  } = useContext<MapContext>(ConfigContext)
71
68
 
72
69
  const { general, columns, legend, table, tooltips } = state
70
+ const columnsInData = state?.data?.[0] ? Object.keys(state.data[0]) : []
73
71
 
74
72
  const [configTextboxValue, setConfigTextbox] = useState({}) // eslint-disable-line
75
73
 
@@ -79,13 +77,6 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
79
77
 
80
78
  const [activeFilterValueForDescription, setActiveFilterValueForDescription] = useState([0, 0])
81
79
 
82
- const { handleFilterOrder, filterOrderOptions, filterStyleOptions } = useFilters({
83
- config: state,
84
- setConfig: setState,
85
- filteredData: runtimeFilters,
86
- setFilteredData: setRuntimeFilters
87
- })
88
-
89
80
  const headerColors = [
90
81
  'theme-blue',
91
82
  'theme-purple',
@@ -241,6 +232,15 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
241
232
 
242
233
  const handleEditorChanges = async (property, value) => {
243
234
  switch (property) {
235
+ case 'navigationTarget':
236
+ setState({
237
+ ...state,
238
+ general: {
239
+ ...state.general,
240
+ navigationTarget: value
241
+ }
242
+ })
243
+ break
244
244
  // change these to be more generic.
245
245
  // updateVisualPropertyValue
246
246
  // updateGeneralPropertyValue, etc.
@@ -725,6 +725,14 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
725
725
  }
726
726
  })
727
727
  break
728
+ case 'google-map':
729
+ setState({
730
+ ...state,
731
+ general: {
732
+ ...state.general,
733
+ geoType: 'google-map'
734
+ }
735
+ })
728
736
  default:
729
737
  break
730
738
  }
@@ -1073,42 +1081,6 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1073
1081
  })
1074
1082
  }
1075
1083
 
1076
- const MapFilters = () => {
1077
- return (
1078
- <>
1079
- <label>
1080
- Filter Behavior
1081
- <select
1082
- value={state.filterBehavior}
1083
- onChange={e => {
1084
- setState({
1085
- ...state,
1086
- filterBehavior: e.target.value
1087
- })
1088
- }}
1089
- >
1090
- <option key='Apply Button' value='Apply Button'>
1091
- Apply Button
1092
- </option>
1093
- <option key='Filter Change' value='Filter Change'>
1094
- Filter Change
1095
- </option>
1096
- </select>
1097
- </label>
1098
- <label>
1099
- <TextField
1100
- type='textarea'
1101
- value={state.filterIntro}
1102
- fieldName='filterIntro'
1103
- label='Filter Intro text'
1104
- updateField={updateField}
1105
- />
1106
- </label>
1107
- {filtersJSX}
1108
- </>
1109
- )
1110
- }
1111
-
1112
1084
  const removeAdditionalColumn = columnName => {
1113
1085
  const newColumns = state.columns
1114
1086
 
@@ -1271,38 +1243,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1271
1243
  return true
1272
1244
  })
1273
1245
 
1274
- const updateField = (section, subsection, fieldName, newValue) => {
1275
- if (!section) {
1276
- setState({
1277
- ...state,
1278
- [fieldName]: newValue
1279
- })
1280
- return
1281
- }
1282
-
1283
- const isArray = Array.isArray(state[section])
1284
-
1285
- let sectionValue = isArray ? [...state[section], newValue] : { ...state[section], [fieldName]: newValue }
1286
-
1287
- if (null !== subsection) {
1288
- if (isArray) {
1289
- sectionValue = [...state[section]]
1290
- sectionValue[subsection] = { ...sectionValue[subsection], [fieldName]: newValue }
1291
- } else {
1292
- sectionValue = {
1293
- ...state[section],
1294
- [subsection]: { ...state[section][subsection], [fieldName]: newValue }
1295
- }
1296
- }
1297
- }
1298
-
1299
- let updatedState = {
1300
- ...state,
1301
- [section]: sectionValue
1302
- }
1303
-
1304
- setState(updatedState)
1305
- }
1246
+ const updateField = updateFieldFactory(state, setState)
1306
1247
 
1307
1248
  const onBackClick = () => {
1308
1249
  setDisplayPanel(!displayPanel)
@@ -1314,163 +1255,6 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1314
1255
 
1315
1256
  const usedFilterColumns = {}
1316
1257
 
1317
- const filtersJSX = state.filters.map((filter, index) => {
1318
- if (filter.type === 'url') return <></>
1319
-
1320
- if (filter.columnName) {
1321
- usedFilterColumns[filter.columnName] = true
1322
- }
1323
-
1324
- return (
1325
- <>
1326
- <fieldset className='edit-block' key={`filter-${index}`}>
1327
- <button
1328
- className='remove-column'
1329
- onClick={e => {
1330
- e.preventDefault()
1331
- changeFilter(index, 'remove')
1332
- }}
1333
- >
1334
- Remove
1335
- </button>
1336
- <TextField
1337
- value={state.filters[index].label}
1338
- section='filters'
1339
- subsection={index}
1340
- fieldName='label'
1341
- label='Label'
1342
- updateField={updateField}
1343
- />
1344
- <label>
1345
- <span className='edit-label column-heading'>
1346
- Filter Column
1347
- <Tooltip style={{ textTransform: 'none' }}>
1348
- <Tooltip.Target>
1349
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1350
- </Tooltip.Target>
1351
- <Tooltip.Content>
1352
- <p>
1353
- Selecting a column will add a dropdown menu below the map legend and allow users to filter based on
1354
- the values in this column.
1355
- </p>
1356
- </Tooltip.Content>
1357
- </Tooltip>
1358
- </span>
1359
- <select
1360
- value={filter.columnName}
1361
- onChange={event => {
1362
- changeFilter(index, 'columnName', event.target.value)
1363
- }}
1364
- >
1365
- {columnsOptions.filter(({ key }) => undefined === usedFilterColumns[key] || filter.columnName === key)}
1366
- </select>
1367
- </label>
1368
-
1369
- <label>
1370
- <span className='edit-showDropdown column-heading'>Show Filter Input</span>
1371
- <input
1372
- type='checkbox'
1373
- checked={filter.showDropdown === undefined ? true : filter.showDropdown}
1374
- onChange={e => {
1375
- changeFilter(index, 'showDropdown', e.target.checked)
1376
- }}
1377
- />
1378
- </label>
1379
-
1380
- <label>
1381
- <span className='edit-filterOrder column-heading'>Filter Style</span>
1382
- <select
1383
- value={filter.filterStyle}
1384
- onChange={e => {
1385
- changeFilter(index, 'filterStyle', e.target.value)
1386
- }}
1387
- >
1388
- {filterStyleOptions.map((option, index) => {
1389
- return (
1390
- <option value={option} key={`filter-${option}--${index}`}>
1391
- {option}
1392
- </option>
1393
- )
1394
- })}
1395
- </select>
1396
- </label>
1397
-
1398
- <label>
1399
- <span className='edit-filterOrder column-heading'>Filter Order</span>
1400
- <select
1401
- value={filter.order}
1402
- onChange={e => {
1403
- changeFilter(index, 'filterOrder', e.target.value)
1404
- changeFilterActive(index, filter.values[0])
1405
- }}
1406
- >
1407
- {filterOrderOptions.map((option, index) => {
1408
- return (
1409
- <option value={option.value} key={`filter-${index}`}>
1410
- {option.label}
1411
- </option>
1412
- )
1413
- })}
1414
- </select>
1415
- </label>
1416
-
1417
- <TextField
1418
- value={state.filters[index].setByQueryParameter}
1419
- section='filters'
1420
- subsection={index}
1421
- fieldName='setByQueryParameter'
1422
- label='Default Value Set By Query String Parameter'
1423
- updateField={updateField}
1424
- />
1425
-
1426
- {filter.order === 'cust' && (
1427
- <DragDropContext
1428
- onDragEnd={({ source, destination }) =>
1429
- handleFilterOrder(source.index, destination?.index, index, state.filters?.[index])
1430
- }
1431
- >
1432
- <Droppable droppableId='filter_order'>
1433
- {provided => (
1434
- <ul
1435
- {...provided.droppableProps}
1436
- className='sort-list'
1437
- ref={provided.innerRef}
1438
- style={{ marginTop: '1em' }}
1439
- >
1440
- {state.filters[index]?.values.map((value, index) => {
1441
- return (
1442
- <Draggable key={value} draggableId={`draggableFilter-${value}`} index={index}>
1443
- {(provided, snapshot) => (
1444
- <li>
1445
- <div
1446
- className={snapshot.isDragging ? 'currently-dragging' : ''}
1447
- style={getItemStyle(
1448
- snapshot.isDragging,
1449
- provided.draggableProps.style,
1450
- sortableItemStyles
1451
- )}
1452
- ref={provided.innerRef}
1453
- {...provided.draggableProps}
1454
- {...provided.dragHandleProps}
1455
- >
1456
- {value}
1457
- </div>
1458
- </li>
1459
- )}
1460
- </Draggable>
1461
- )
1462
- })}
1463
- {provided.placeholder}
1464
- </ul>
1465
- )}
1466
- </Droppable>
1467
- </DragDropContext>
1468
- )}
1469
- </fieldset>
1470
- </>
1471
- )
1472
- })
1473
-
1474
1258
  const StateOptionList = () => {
1475
1259
  const arrOfArrays = Object.entries(supportedStatesFipsCodes)
1476
1260
 
@@ -1603,13 +1387,29 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1603
1387
  </AccordionItemHeading>
1604
1388
  <AccordionItemPanel>
1605
1389
  {/* Geography */}
1390
+ {/*<Select*/}
1391
+ {/* options={[*/}
1392
+ {/* { value: 'us', label: 'United States' },*/}
1393
+ {/* { value: 'us-region', label: 'U.S. Region' },*/}
1394
+ {/* { value: 'world', label: 'World' },*/}
1395
+ {/* { value: 'single-state', label: 'U.S. State' },*/}
1396
+ {/* { value: 'google-map', label: 'Google Map API' }*/}
1397
+ {/* ]}*/}
1398
+ {/* section={'general'}*/}
1399
+ {/* fieldName={'geoType'}*/}
1400
+ {/* label='Geography'*/}
1401
+ {/* updateField={updateField}*/}
1402
+ {/*/>*/}
1403
+
1606
1404
  <label>
1607
1405
  <span className='edit-label column-heading'>
1608
1406
  <span>Geography</span>
1609
1407
  </span>
1610
- <ul className='geo-buttons'>
1408
+ <ul className='geo-buttons d-grid' style={{ gridTemplateColumns: 'repeat(2, 1fr)', gap: '8px' }}>
1611
1409
  <button
1612
- className={state.general.geoType === 'us' || state.general.geoType === 'us-county' ? 'active' : ''}
1410
+ className={`${
1411
+ state.general.geoType === 'us' || state.general.geoType === 'us-county' ? 'active' : ''
1412
+ } full-width`}
1613
1413
  onClick={e => {
1614
1414
  e.preventDefault()
1615
1415
  handleEditorChanges('geoType', 'us')
@@ -1619,7 +1419,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1619
1419
  <span>United States</span>
1620
1420
  </button>
1621
1421
  <button
1622
- className={state.general.geoType === 'us-region' ? 'active' : ''}
1422
+ className={`${state.general.geoType === 'us-region' ? 'active' : ''} full-width`}
1623
1423
  onClick={e => {
1624
1424
  e.preventDefault()
1625
1425
  handleEditorChanges('geoType', 'us-region')
@@ -1629,7 +1429,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1629
1429
  <span>U.S. Region</span>
1630
1430
  </button>
1631
1431
  <button
1632
- className={state.general.geoType === 'world' ? 'active' : ''}
1432
+ className={`${state.general.geoType === 'world' ? 'active' : ''} full-width`}
1633
1433
  onClick={e => {
1634
1434
  e.preventDefault()
1635
1435
  handleEditorChanges('geoType', 'world')
@@ -1639,7 +1439,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1639
1439
  <span>World</span>
1640
1440
  </button>
1641
1441
  <button
1642
- className={state.general.geoType === 'single-state' ? 'active' : ''}
1442
+ className={`${state.general.geoType === 'single-state' ? 'active' : ''} full-width`}
1643
1443
  onClick={e => {
1644
1444
  e.preventDefault()
1645
1445
  handleEditorChanges('geoType', 'single-state')
@@ -1648,6 +1448,16 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1648
1448
  <AlabamaGraphic />
1649
1449
  <span>U.S. State</span>
1650
1450
  </button>
1451
+ {/* <button
1452
+ className={`${state.general.geoType === 'google-map' ? 'active' : ''} full-width`}
1453
+ onClick={e => {
1454
+ e.preventDefault()
1455
+ handleEditorChanges('geoType', 'google-map')
1456
+ }}
1457
+ >
1458
+ <UsaGraphic />
1459
+ <span>Google Map Api</span>
1460
+ </button> */}
1651
1461
  </ul>
1652
1462
  </label>
1653
1463
  {/* Select > State or County Map */}
@@ -1760,6 +1570,23 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1760
1570
  )}
1761
1571
  </select>
1762
1572
  </label>
1573
+
1574
+ {/* Navigation Behavior */}
1575
+ {(state.general.type === 'navigation' || state.general.type === 'data') && (
1576
+ <label>
1577
+ <span className='edit-label column-heading'>Navigation Behavior</span>
1578
+ <select
1579
+ value={state.general.navigationTarget}
1580
+ onChange={event => {
1581
+ event.preventDefault()
1582
+ handleEditorChanges('navigationTarget', event.target.value)
1583
+ }}
1584
+ >
1585
+ <option value='_self'>Same Window</option>
1586
+ <option value='_blank'>New Window</option>
1587
+ </select>
1588
+ </label>
1589
+ )}
1763
1590
  <label>
1764
1591
  <span className='edit-label'>Data Classification Type</span>
1765
1592
  <div>
@@ -1801,7 +1628,17 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1801
1628
  handleEditorChanges('displayStateLabels', event.target.checked)
1802
1629
  }}
1803
1630
  />
1804
- <span className='edit-label'>Show state labels</span>
1631
+ <span className='edit-label'>
1632
+ Show state labels
1633
+ <Tooltip style={{ textTransform: 'none' }}>
1634
+ <Tooltip.Target>
1635
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1636
+ </Tooltip.Target>
1637
+ <Tooltip.Content>
1638
+ <p>Recommended set to display for Section 508 compliance.</p>
1639
+ </Tooltip.Content>
1640
+ </Tooltip>
1641
+ </span>
1805
1642
  </label>
1806
1643
  )}
1807
1644
  </AccordionItemPanel>
@@ -1919,16 +1756,6 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1919
1756
  </Tooltip>
1920
1757
  }
1921
1758
  />
1922
- {'us' === state.general.geoType && (
1923
- <TextField
1924
- value={general.territoriesLabel}
1925
- updateField={updateField}
1926
- section='general'
1927
- fieldName='territoriesLabel'
1928
- label='Territories Label'
1929
- placeholder='Territories'
1930
- />
1931
- )}
1932
1759
  {'us' === state.general.geoType && (
1933
1760
  <label className='checkbox'>
1934
1761
  <input
@@ -2857,7 +2684,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
2857
2684
  </label>
2858
2685
  </React.Fragment>
2859
2686
  )}
2860
- {filtersJSX.length > 0 && (
2687
+ {state.filters.length > 0 && (
2861
2688
  <label className='checkbox'>
2862
2689
  <input
2863
2690
  type='checkbox'
@@ -2885,7 +2712,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
2885
2712
  </span>
2886
2713
  </label>
2887
2714
  )}
2888
- {(filtersJSX.length > 0 || state.general.type === 'bubble' || state.general.geoType === 'us') && (
2715
+ {(state.filters.length > 0 || state.general.type === 'bubble' || state.general.geoType === 'us') && (
2889
2716
  <label className='checkbox'>
2890
2717
  <input
2891
2718
  type='checkbox'
@@ -2922,20 +2749,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
2922
2749
  <AccordionItemButton>Filters</AccordionItemButton>
2923
2750
  </AccordionItemHeading>
2924
2751
  <AccordionItemPanel>
2925
- {filtersJSX.length > 0 ? (
2926
- <MapFilters />
2927
- ) : (
2928
- <p style={{ textAlign: 'center' }}>There are currently no filters.</p>
2929
- )}
2930
- <button
2931
- className={'btn btn-primary full-width'}
2932
- onClick={event => {
2933
- event.preventDefault()
2934
- changeFilter(null, 'addNew')
2935
- }}
2936
- >
2937
- Add Filter
2938
- </button>
2752
+ <VizFilterEditor config={state} updateField={updateField} rawData={state.data} />
2939
2753
  </AccordionItemPanel>
2940
2754
  </AccordionItem>
2941
2755
  )}
@@ -3563,6 +3377,18 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
3563
3377
  updateField={updateField}
3564
3378
  />
3565
3379
  </label>
3380
+ {/* Leaflet Map Type */}
3381
+ {state.general.geoType === 'leaflet' && (
3382
+ <>
3383
+ <Select
3384
+ label='Leaflet Theme'
3385
+ options={layerOptions}
3386
+ section={'leaflet'}
3387
+ fieldName='theme'
3388
+ updateField={updateField}
3389
+ />
3390
+ </>
3391
+ )}
3566
3392
  </AccordionItemPanel>
3567
3393
  </AccordionItem>
3568
3394
  <AccordionItem>
@@ -0,0 +1,67 @@
1
+ import React, { useContext, useEffect, useRef } from 'react'
2
+ import { Loader } from '@googlemaps/js-api-loader'
3
+ import { MarkerClusterer } from '@googlemaps/markerclusterer'
4
+ import ConfigContext from '../../../context'
5
+
6
+ // center on USA
7
+ const center = {
8
+ lat: 37.09024,
9
+ lng: -95.712891
10
+ }
11
+
12
+ type GoogleMapComponentProps = {
13
+ apiKey?: string
14
+ }
15
+
16
+ const GoogleMapComponent: React.FC<GoogleMapComponentProps> = ({ apiKey = '' }) => {
17
+ const mapRef = useRef(null)
18
+ const { state } = useContext(ConfigContext)
19
+
20
+ useEffect(() => {
21
+ const loader = new Loader({
22
+ apiKey: apiKey,
23
+ version: 'weekly'
24
+ })
25
+
26
+ loader
27
+ .load()
28
+ .then(() => {
29
+ if (mapRef.current) {
30
+ const map = new window.google.maps.Map(mapRef.current, {
31
+ center: center,
32
+ zoom: 4
33
+ })
34
+
35
+ const markers = state.data.map(d => {
36
+ const markerContent = document.createElement('div')
37
+ markerContent.style.backgroundColor = 'orange' // Set the background color
38
+ markerContent.style.width = '25px'
39
+ markerContent.style.height = '25px'
40
+ markerContent.style.borderRadius = '50%'
41
+ markerContent.style.display = 'flex'
42
+ markerContent.style.alignItems = 'center'
43
+ markerContent.style.justifyContent = 'center'
44
+ markerContent.style.color = 'white'
45
+ markerContent.innerText = d[state.columns.geo.name]
46
+
47
+ const marker = new google.maps.Marker({
48
+ position: { lat: Number(d[state.columns.latitude.name]), lng: Number(d[state.columns.longitude.name]) },
49
+ title: d[state.columns.geo.name],
50
+ map: map
51
+ })
52
+
53
+ return marker
54
+ })
55
+
56
+ new MarkerClusterer({ markers, map })
57
+ }
58
+ })
59
+ .catch(e => {
60
+ console.error('Error loading Google Maps API:', e)
61
+ })
62
+ }, [apiKey, state])
63
+
64
+ return <div ref={mapRef} style={{ height: '500px', width: '100%' }} />
65
+ }
66
+
67
+ export default GoogleMapComponent
@@ -0,0 +1,3 @@
1
+ import GoogleMap from './components/GoogleMap'
2
+
3
+ export default GoogleMap