@cdc/map 4.23.2 → 4.23.3

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.
@@ -0,0 +1,18 @@
1
+ [
2
+ {
3
+ "Country": "Argentina",
4
+ "Cases": "100",
5
+ "Category": "Has not historically reported monkeypox",
6
+ "AsOf": "11 Jul 2022 5:00 PM EDT",
7
+ "longitude": "-63.6",
8
+ "latitude": "-38.4"
9
+ },
10
+ {
11
+ "Country": "New York City",
12
+ "Cases": "10",
13
+ "Category": "Has not historically reported monkeypox",
14
+ "AsOf": "11 Jul 2022 5:00 PM EDT",
15
+ "longitude": "-74.006",
16
+ "latitude": "40.712"
17
+ }
18
+ ]
@@ -0,0 +1,108 @@
1
+ {
2
+ "general": {
3
+ "title": "Default World Map",
4
+ "subtext": "",
5
+ "type": "world-geocode",
6
+ "geoType": "world",
7
+ "headerColor": "theme-blue",
8
+ "geoBorderColor": "darkGray",
9
+ "showSidebar": true,
10
+ "showTitle": true,
11
+ "showDownloadButton": true,
12
+ "expandDataTable": true,
13
+ "equalNumberOptIn": true,
14
+ "showDownloadMediaButton": false,
15
+ "displayAsHex": false,
16
+ "displayStateLabels": false,
17
+ "territoriesLabel": "Territories",
18
+ "language": "en",
19
+ "geoLabelOverride": "",
20
+ "hasRegions": false,
21
+ "fullBorder": false,
22
+ "palette": {
23
+ "isReversed": false
24
+ },
25
+ "allowMapZoom": true,
26
+ "hideGeoColumnInTooltip": false,
27
+ "hidePrimaryColumnInTooltip": false,
28
+ "statePicked": {
29
+ "fipsCode": "01",
30
+ "stateName": "Alabama"
31
+ }
32
+ },
33
+ "type": "map",
34
+ "color": "pinkpurple",
35
+ "columns": {
36
+ "geo": {
37
+ "name": "Country",
38
+ "label": "Location",
39
+ "tooltip": false,
40
+ "dataTable": true
41
+ },
42
+ "primary": {
43
+ "name": "Cases",
44
+ "label": "Data Label",
45
+ "prefix": "",
46
+ "suffix": "%",
47
+ "dataTable": true,
48
+ "tooltip": true
49
+ },
50
+ "navigate": {
51
+ "name": "Link",
52
+ "tooltip": false,
53
+ "dataTable": false
54
+ },
55
+ "latitude": {
56
+ "name": "latitude"
57
+ },
58
+ "longitude": {
59
+ "name": "longitude"
60
+ }
61
+ },
62
+ "legend": {
63
+ "numberOfItems": 3,
64
+ "position": "side",
65
+ "title": "Legend Title",
66
+ "description": "Legend Text",
67
+ "type": "equalnumber",
68
+ "specialClasses": [],
69
+ "separateZero": true,
70
+ "descriptions": {},
71
+ "unified": false,
72
+ "singleColumn": false,
73
+ "singleRow": false,
74
+ "dynamicDescription": false
75
+ },
76
+ "filters": [],
77
+ "dataTable": {
78
+ "title": "Data Table",
79
+ "forceDisplay": true
80
+ },
81
+ "table": {
82
+ "showDownloadUrl": false
83
+ },
84
+ "tooltips": {
85
+ "appearanceType": "hover",
86
+ "linkLabel": "Learn More",
87
+ "capitalizeLabels": true
88
+ },
89
+ "runtime": {
90
+ "editorErrorMessage": []
91
+ },
92
+ "visual": {
93
+ "minBubbleSize": 1,
94
+ "maxBubbleSize": 20,
95
+ "extraBubbleBorder": false,
96
+ "cityStyle": "circle",
97
+ "geoCodeCircleSize": 8,
98
+ "showBubbleZeros": false
99
+ },
100
+ "mapPosition": {
101
+ "coordinates": [
102
+ 0,
103
+ 30
104
+ ],
105
+ "zoom": 1
106
+ },
107
+ "dataUrl": "http://localhost:8080/examples/world-geocode-data.json"
108
+ }
package/index.html CHANGED
@@ -1,39 +1,44 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
6
+ <style type="text/css">
7
+ body {
8
+ margin: 0;
9
+ }
3
10
 
4
- <head>
5
- <meta charset="utf-8" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
7
- <style type="text/css">
8
- body {
9
- margin: 0;
10
- }
11
+ .cdc-map-outer-container {
12
+ min-height: 100vh;
13
+ }
14
+ </style>
15
+ </head>
11
16
 
12
- .cdc-map-outer-container {
13
- min-height: 100vh;
14
- }
15
- </style>
16
- </head>
17
-
18
- <body>
19
- <!-- DEFAULT EXAMPLES -->
20
- <!-- <div class="react-container react-container--maps" data-config="/examples/default-county.json"></div> -->
21
- <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-geocode.json"></div>-->
17
+ <body>
18
+ <!-- DEFAULT EXAMPLES -->
19
+ <!-- <div class="react-container react-container--maps" data-config="/examples/default-county.json"></div> -->
20
+ <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-geocode.json"></div> -->
22
21
  <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-usa-regions.json"></div> -->
23
- <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-single-state.json"></div>-->
24
- <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-world.json"></div>-->
25
- <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/bubble-us.json"></div>-->
26
- <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/bubble-world.json"></div> -->
22
+ <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-single-state.json"></div>-->
23
+ <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-world.json"></div>-->
24
+ <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/bubble-us.json"></div>-->
25
+ <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/bubble-world.json"></div> -->
27
26
 
28
- <!-- TP4 EXAMPLES -->
29
- <div class="react-container react-container--maps" data-config="/examples/example-city-state.json"></div>
30
- <!-- <div class="react-container react-container--maps" data-config="/examples/example-world-map.json"></div> -->
31
- <!-- <div class="react-container react-container--maps" data-config="/examples/default-hex.json"></div> -->
27
+ <!-- TP4 EXAMPLES -->
28
+ <div class="react-container react-container--maps" data-config="/examples/example-city-state.json"></div>
29
+ <!-- <div class="react-container react-container--maps" data-config="/examples/example-city-stateBAD.json"></div> -->
30
+ <!-- <div class="react-container react-container--maps" data-config="/examples/example-world-map.json"></div> -->
31
+ <!-- <div class="react-container react-container--maps" data-config="/examples/default-hex.json"></div> -->
32
32
 
33
- <!-- <div class="react-container" data-config="/examples/example-hex-map-with-filter.json"></div> -->
33
+ <!-- TP4 EXAMPLES -->
34
+ <!-- <div class="react-container react-container--maps" data-config="/examples/example-city-state.json"></div> -->
35
+ <!-- <div class="react-container react-container--maps" data-config="/examples/example-city-state-no-territories.json"></div> -->
36
+ <!-- <div class="react-container react-container--maps" data-config="/examples/example-world-map.json"></div> -->
37
+ <!-- <div class="react-container react-container--maps" data-config="/examples/default-hex.json"></div> -->
34
38
 
35
- <noscript>You need to enable JavaScript to run this app.</noscript>
36
- <script type="module" src="./src/index.jsx"></script>
37
- </body>
39
+ <!-- <div class="react-container" data-config="/examples/example-hex-map-with-filter.json"></div> -->
38
40
 
41
+ <noscript>You need to enable JavaScript to run this app.</noscript>
42
+ <script type="module" src="./src/index.jsx"></script>
43
+ </body>
39
44
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/map",
3
- "version": "4.23.2",
3
+ "version": "4.23.3",
4
4
  "description": "React component for visualizing tabular data on a map of the United States or the world.",
5
5
  "moduleName": "CdcMap",
6
6
  "main": "dist/cdcmap",
@@ -8,6 +8,8 @@
8
8
  "scripts": {
9
9
  "start": "vite --open",
10
10
  "build": "vite build",
11
+ "test": "vitest watch --reporter verbose",
12
+ "test:ui": "vitest --ui",
11
13
  "preview": "vite preview",
12
14
  "graph": "nx graph",
13
15
  "prepublishOnly": "lerna run --scope @cdc/map build"
@@ -22,7 +24,7 @@
22
24
  },
23
25
  "license": "Apache-2.0",
24
26
  "dependencies": {
25
- "@cdc/core": "^4.23.2",
27
+ "@cdc/core": "^4.23.3",
26
28
  "@emotion/core": "^10.0.28",
27
29
  "@emotion/react": "^11.1.5",
28
30
  "@hello-pangea/dnd": "^16.2.0",
@@ -35,6 +37,7 @@
35
37
  "d3-zoom": "^3.0.0",
36
38
  "html-react-parser": "^3.0.8",
37
39
  "html2canvas": "^1.0.0-rc.7",
40
+ "lodash.debounce": "^4.0.8",
38
41
  "papaparse": "^5.3.0",
39
42
  "react-accessible-accordion": "^3.0.1",
40
43
  "react-table": "^7.5.0",
@@ -48,5 +51,5 @@
48
51
  "react": "^18.2.0",
49
52
  "react-dom": "^18.2.0"
50
53
  },
51
- "gitHead": "cd4216f47b1c41bfbc1de3b704f70c52cc7293c2"
54
+ "gitHead": "6fa3b11db159d38538f18023fe70b67a29e7d327"
52
55
  }
package/src/CdcMap.jsx CHANGED
@@ -88,7 +88,7 @@ const hashObj = row => {
88
88
 
89
89
  const indexOfIgnoreType = (arr, item) => {
90
90
  for (let i = 0; i < arr.length; i++) {
91
- if (item == arr[i]) {
91
+ if (item === arr[i]) {
92
92
  return i
93
93
  }
94
94
  }
@@ -147,7 +147,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
147
147
  } catch (e) {
148
148
  console.error('Failed to set world map zoom.')
149
149
  }
150
- }, [filteredCountryCode])
150
+ }, [filteredCountryCode]) // eslint-disable-line
151
151
 
152
152
  useEffect(() => {
153
153
  setTimeout(() => {
@@ -159,7 +159,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
159
159
  setRuntimeData(tmpData)
160
160
  }
161
161
  }, 100)
162
- }, [filteredCountryCode])
162
+ }, [filteredCountryCode]) // eslint-disable-line
163
163
 
164
164
  useEffect(() => {
165
165
  if (state.mapPosition) {
@@ -167,6 +167,22 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
167
167
  }
168
168
  }, [state.mapPosition, setPosition])
169
169
 
170
+ const generateRuntimeLegendHash = () => {
171
+ return hashObj({
172
+ color: state.color,
173
+ customColors: state.customColors,
174
+ numberOfItems: state.legend.numberOfItems,
175
+ type: state.legend.type,
176
+ separateZero: state.legend.separateZero ?? false,
177
+ primary: state.columns.primary.name,
178
+ categoryValuesOrder: state.legend.categoryValuesOrder,
179
+ specialClasses: state.legend.specialClasses,
180
+ geoType: state.general.geoType,
181
+ data: state.data,
182
+ ...runtimeFilters
183
+ })
184
+ }
185
+
170
186
  const resizeObserver = new ResizeObserver(entries => {
171
187
  for (let entry of entries) {
172
188
  let newViewport = getViewport(entry.contentRect.width)
@@ -177,6 +193,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
177
193
 
178
194
  // Tag each row with a UID. Helps with filtering/placing geos. Not enumerable so doesn't show up in loops/console logs except when directly addressed ex row.uid
179
195
  // We are mutating state in place here (depending on where called) - but it's okay, this isn't used for rerender
196
+ // eslint-disable-next-line
180
197
  const addUIDs = useCallback((obj, fromColumn) => {
181
198
  obj.data.forEach(row => {
182
199
  let uid = null
@@ -225,6 +242,11 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
225
242
  const geoName = row[obj.columns.geo.name]
226
243
 
227
244
  uid = countryKeys.find(key => supportedCountries[key].includes(geoName))
245
+
246
+ // Cities
247
+ if (!uid && 'world-geocode' === state.general.type) {
248
+ uid = cityKeys.find(key => key === geoName?.toUpperCase())
249
+ }
228
250
  }
229
251
 
230
252
  // County Check
@@ -248,9 +270,10 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
248
270
  obj.data.fromColumn = fromColumn
249
271
  })
250
272
 
273
+ // eslint-disable-next-line
251
274
  const generateRuntimeLegend = useCallback((obj, runtimeData, hash) => {
252
275
  const newLegendMemo = new Map() // Reset memoization
253
- const primaryCol = obj.columns.primary.name,
276
+ let primaryCol = obj.columns.primary.name,
254
277
  isBubble = obj.general.type === 'bubble',
255
278
  categoricalCol = obj.columns.categorical ? obj.columns.categorical.name : undefined,
256
279
  type = obj.legend.type,
@@ -457,6 +480,11 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
457
480
  }
458
481
 
459
482
  legendMemo.current = newLegendMemo
483
+
484
+ result.forEach((row, i) => {
485
+ row.bin = i // set bin number to index
486
+ })
487
+
460
488
  return result
461
489
  }
462
490
 
@@ -536,6 +564,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
536
564
  let min = removedRows[0][primaryCol],
537
565
  max = removedRows[removedRows.length - 1][primaryCol]
538
566
 
567
+ // eslint-disable-next-line
539
568
  removedRows.forEach(row => {
540
569
  newLegendMemo.set(hashObj(row), result.length)
541
570
  })
@@ -574,6 +603,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
574
603
  breaks.unshift(0)
575
604
  }
576
605
 
606
+ // eslint-disable-next-line array-callback-return
577
607
  breaks.map((item, index) => {
578
608
  const setMin = index => {
579
609
  let min = breaks[index]
@@ -646,7 +676,6 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
646
676
  }
647
677
 
648
678
  // Equal Interval
649
-
650
679
  if (type === 'equalinterval' && dataSet?.length !== 0) {
651
680
  if (!dataSet || dataSet.length === 0) {
652
681
  setState({
@@ -695,11 +724,29 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
695
724
  })
696
725
 
697
726
  legendMemo.current = newLegendMemo
727
+
728
+ //----------
729
+ // DEV-784
730
+ // before returning the legend result
731
+ // add property for bin number and set to index location
732
+ result.forEach((row, i) => {
733
+ row.bin = i // set bin number to index
734
+ })
735
+
736
+ // Move all special legend items from "Special Classes" to the end of the legend
737
+ if (state.legend.showSpecialClassesLast) {
738
+ let specialRows = result.filter(d => d.special === true)
739
+ let otherRows = result.filter(d => !d.special)
740
+ result = [...otherRows, ...specialRows]
741
+ }
742
+ //-----------
743
+
698
744
  return result
699
745
  })
700
746
 
747
+ // eslint-disable-next-line
701
748
  const generateRuntimeFilters = useCallback((obj, hash, runtimeFilters) => {
702
- if (undefined === obj.filters || obj.filters.length === 0) return []
749
+ if (typeof obj === 'undefined' || undefined === obj.filters || obj.filters.length === 0) return []
703
750
 
704
751
  let filters = []
705
752
 
@@ -750,17 +797,17 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
750
797
  })
751
798
 
752
799
  // Calculates what's going to be displayed on the map and data table at render.
800
+ // eslint-disable-next-line
753
801
  const generateRuntimeData = useCallback((obj, filters, hash, test) => {
754
802
  try {
755
803
  const result = {}
756
804
 
757
- if (hash) {
758
- // Adding property this way prevents it from being enumerated
759
- Object.defineProperty(result, 'fromHash', {
760
- value: hash
761
- })
762
- }
805
+ // Adding property this way prevents it from being enumerated
806
+ Object.defineProperty(result, 'fromHash', {
807
+ value: hash
808
+ })
763
809
 
810
+ addUIDs(obj, obj.columns.geo.name)
764
811
  obj.data.forEach(row => {
765
812
  if (test) {
766
813
  console.log('object', obj)
@@ -816,7 +863,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
816
863
  resizeObserver.observe(node)
817
864
  }
818
865
  setContainer(node)
819
- }, [])
866
+ }, []) // eslint-disable-line
820
867
 
821
868
  const mapSvg = useRef(null)
822
869
 
@@ -897,6 +944,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
897
944
  return formattedValue
898
945
  }
899
946
 
947
+ // this is passed DOWN into the various components
948
+ // then they do a lookup based on the bin number as index into here (TT)
900
949
  const applyLegendToRow = rowObj => {
901
950
  try {
902
951
  if (!rowObj) throw new Error('COVE: No rowObj in applyLegendToRow')
@@ -911,7 +960,11 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
911
960
  if (legendMemo.current.has(hash)) {
912
961
  let idx = legendMemo.current.get(hash)
913
962
  if (runtimeLegend[idx]?.disabled) return false
914
- return generateColorsArray(runtimeLegend[idx]?.color, runtimeLegend[idx]?.special)
963
+
964
+ // DEV-784 changed to use bin prop to get color instead of idx
965
+ // bc we re-order legend when showSpecialClassesLast is checked
966
+ let legendBinColor = runtimeLegend.find(o => o.bin === idx)?.color
967
+ return generateColorsArray(legendBinColor, runtimeLegend[idx]?.special)
915
968
  }
916
969
 
917
970
  // Fail state
@@ -927,6 +980,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
927
980
 
928
981
  // Adds geo label, ie State: Georgia
929
982
  let stateOrCounty = state.general.geoType === 'us' ? 'State: ' : state.general.geoType === 'us-county' || state.general.geoType === 'single-state' ? 'County: ' : ''
983
+
930
984
  // check the override
931
985
  stateOrCounty = state.general.geoLabelOverride !== '' ? state.general.geoLabelOverride + ': ' : stateOrCounty
932
986
 
@@ -939,7 +993,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
939
993
 
940
994
  toolTipText += !state.general.hideGeoColumnInTooltip ? `<strong>${stateOrCounty}${displayGeoName(geoName)}</strong>` : `<strong>${displayGeoName(geoName)}</strong>`
941
995
 
942
- if (('data' === state.general.type || state.general.type === 'bubble' || state.general.type === 'us-geocode') && undefined !== row) {
996
+ if (('data' === state.general.type || state.general.type === 'bubble' || state.general.type === 'us-geocode' || state.general.type === 'world-geocode') && undefined !== row) {
943
997
  toolTipText += `<dl>`
944
998
 
945
999
  Object.keys(state.columns).forEach(columnKey => {
@@ -951,10 +1005,15 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
951
1005
  let value
952
1006
 
953
1007
  if (state.legend.specialClasses && state.legend.specialClasses.length && typeof state.legend.specialClasses[0] === 'object') {
1008
+ // THIS CODE SHOULD NOT ACT ON THE ENTIRE ROW OF KEYS BUT ONLY THE ONE KEY IN THE SPECIAL CLASS
954
1009
  for (let i = 0; i < state.legend.specialClasses.length; i++) {
955
- if (String(row[state.legend.specialClasses[i].key]) === state.legend.specialClasses[i].value) {
956
- value = displayDataAsText(state.legend.specialClasses[i].label, columnKey)
957
- break
1010
+ // DEV-3303 - Special Classes label in HOVERS should only apply to selected special class key
1011
+ // - you have to ALSO check that the key matches - putting here otherwise the if stmt too long
1012
+ if (columnKey === state.legend.specialClasses[i].key) {
1013
+ if (String(row[state.legend.specialClasses[i].key]) === state.legend.specialClasses[i].value) {
1014
+ value = displayDataAsText(state.legend.specialClasses[i].label, columnKey)
1015
+ break
1016
+ }
958
1017
  }
959
1018
  }
960
1019
  }
@@ -1100,6 +1159,17 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1100
1159
  setSharedFilter(state.uid, value)
1101
1160
  }
1102
1161
 
1162
+ // If world-geocode map zoom to geo point
1163
+ if ('world-geocode' === state.general.type) {
1164
+ let lat = value[state.columns.latitude.name]
1165
+ let long = value[state.columns.longitude.name]
1166
+
1167
+ setState({
1168
+ ...state,
1169
+ mapPosition: { coordinates: [long, lat], zoom: 3 }
1170
+ })
1171
+ }
1172
+
1103
1173
  // If modals are set or we are on a mobile viewport, display modal
1104
1174
  if (window.matchMedia('(any-hover: none)').matches || 'click' === state.tooltips.appearanceType) {
1105
1175
  setModal({
@@ -1247,28 +1317,28 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1247
1317
  // Initial load
1248
1318
  useEffect(() => {
1249
1319
  init()
1250
- }, [])
1320
+ }, []) // eslint-disable-line
1251
1321
 
1252
1322
  useEffect(() => {
1253
1323
  if (state && !coveLoadedHasRan && container) {
1254
1324
  publish('cove_loaded', { config: state })
1255
1325
  setCoveLoadedHasRan(true)
1256
1326
  }
1257
- }, [state, container])
1327
+ }, [state, container]) // eslint-disable-line
1258
1328
 
1259
1329
  useEffect(() => {
1260
1330
  if (state.data) {
1261
1331
  let newData = generateRuntimeData(state)
1262
1332
  setRuntimeData(newData)
1263
1333
  }
1264
- }, [state.general.statePicked])
1334
+ }, [state.general.statePicked]) // eslint-disable-line
1265
1335
 
1266
1336
  useEffect(() => {
1267
1337
  // When geotype changes - add UID
1268
1338
  if (state.data && state.columns.geo.name) {
1269
1339
  addUIDs(state, state.columns.geo.name)
1270
1340
  }
1271
- }, [state])
1341
+ }, [state]) // eslint-disable-line
1272
1342
 
1273
1343
  // DEV-769 make "Data Table" both a required field and default value
1274
1344
  useEffect(() => {
@@ -1281,13 +1351,13 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1281
1351
  }
1282
1352
  })
1283
1353
  }
1284
- }, [state.dataTable])
1354
+ }, [state.dataTable]) // eslint-disable-line
1285
1355
 
1286
1356
  // When geo label override changes
1287
1357
  // - redo the tooltips
1288
1358
  useEffect(() => {
1289
1359
  applyTooltipsToGeo()
1290
- }, [state.general.geoLabelOverride])
1360
+ }, [state.general.geoLabelOverride]) // eslint-disable-line
1291
1361
 
1292
1362
  useEffect(() => {
1293
1363
  // UID
@@ -1307,28 +1377,15 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1307
1377
  }
1308
1378
  }
1309
1379
 
1310
- const hashLegend = hashObj({
1311
- color: state.color,
1312
- customColors: state.customColors,
1313
- numberOfItems: state.legend.numberOfItems,
1314
- type: state.legend.type,
1315
- separateZero: state.legend.separateZero ?? false,
1316
- categoryValuesOrder: state.legend.categoryValuesOrder,
1317
- specialClasses: state.legend.specialClasses,
1318
- geoType: state.general.geoType,
1319
- data: state.data,
1320
- ...runtimeLegend,
1321
- ...runtimeFilters
1322
- })
1380
+ const hashLegend = generateRuntimeLegendHash()
1323
1381
 
1324
1382
  const hashData = hashObj({
1383
+ data: state.data,
1325
1384
  columns: state.columns,
1326
1385
  geoType: state.general.geoType,
1327
1386
  type: state.general.type,
1328
1387
  geo: state.columns.geo.name,
1329
1388
  primary: state.columns.primary.name,
1330
- data: state.data,
1331
- ...runtimeFilters,
1332
1389
  mapPosition: state.mapPosition,
1333
1390
  ...runtimeFilters
1334
1391
  })
@@ -1345,38 +1402,34 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1345
1402
  const legend = generateRuntimeLegend(state, newRuntimeData || runtimeData, hashLegend)
1346
1403
  setRuntimeLegend(legend)
1347
1404
  }
1348
- }, [state])
1405
+ }, [state]) // eslint-disable-line
1349
1406
 
1350
1407
  useEffect(() => {
1351
- const hashLegend = hashObj({
1352
- color: state.color,
1353
- customColors: state.customColors,
1354
- numberOfItems: state.legend.numberOfItems,
1355
- type: state.legend.type,
1356
- separateZero: state.legend.separateZero ?? false,
1357
- categoryValuesOrder: state.legend.categoryValuesOrder,
1358
- specialClasses: state.legend.specialClasses,
1359
- geoType: state.general.geoType,
1360
- data: state.data
1361
- })
1408
+ const hashLegend = generateRuntimeLegendHash()
1362
1409
 
1363
1410
  // Legend - Update when runtimeData does
1364
1411
  if (hashLegend !== runtimeLegend.fromHash && undefined === runtimeData.init) {
1365
- const legend = generateRuntimeLegend(state, runtimeData)
1412
+ const legend = generateRuntimeLegend(state, runtimeData, hashLegend)
1366
1413
  setRuntimeLegend(legend)
1367
1414
  }
1368
- }, [runtimeData])
1415
+ }, [runtimeData]) // eslint-disable-line
1369
1416
 
1370
1417
  if (config) {
1371
1418
  // eslint-disable-next-line react-hooks/rules-of-hooks
1372
1419
  useEffect(() => {
1373
1420
  loadConfig(config)
1374
- }, [config.data])
1421
+ }, [config.data]) // eslint-disable-line
1375
1422
  }
1376
1423
 
1377
1424
  // Destructuring for more readable JSX
1378
1425
  const { general, tooltips, dataTable } = state
1379
- const { title = '', subtext = '' } = general
1426
+ let { title, subtext = '' } = general
1427
+
1428
+ // if no title AND in editor then set a default
1429
+ if (isEditor) {
1430
+ if (!title || title === '') title = 'Map Title'
1431
+ }
1432
+ if (!dataTable.title || dataTable.title === '') dataTable.title = 'Data Table'
1380
1433
 
1381
1434
  // Outer container classes
1382
1435
  let outerContainerClasses = ['cdc-open-viz-module', 'cdc-map-outer-container', currentViewport]
@@ -1421,7 +1474,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1421
1474
  handleMapAriaLabels,
1422
1475
  runtimeFilters,
1423
1476
  setRuntimeFilters,
1424
- innerContainerRef
1477
+ innerContainerRef,
1478
+ currentViewport
1425
1479
  }
1426
1480
 
1427
1481
  if (!mapProps.data || !state.data) return <Loading />
@@ -1455,13 +1509,24 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1455
1509
  return (
1456
1510
  <ConfigContext.Provider value={mapProps}>
1457
1511
  <div className={outerContainerClasses.join(' ')} ref={outerContainerRef} data-download-id={imageId}>
1458
- {isEditor && <EditorPanel isDashboard={isDashboard} state={state} setState={setState} loadConfig={loadConfig} setParentConfig={setConfig} setRuntimeFilters={setRuntimeFilters} runtimeFilters={runtimeFilters} runtimeLegend={runtimeLegend} columnsInData={Object.keys(state.data[0])} />}
1512
+ {isEditor && (
1513
+ <EditorPanel
1514
+ isDashboard={isDashboard}
1515
+ state={state}
1516
+ setState={setState}
1517
+ loadConfig={loadConfig}
1518
+ setParentConfig={setConfig}
1519
+ setRuntimeFilters={setRuntimeFilters}
1520
+ runtimeFilters={runtimeFilters}
1521
+ runtimeLegend={runtimeLegend}
1522
+ columnsInData={Object.keys(state.data[0])}
1523
+ changeFilterActive={changeFilterActive}
1524
+ />
1525
+ )}
1459
1526
  {!runtimeData.init && (general.type === 'navigation' || runtimeLegend) && (
1460
1527
  <section className={`cdc-map-inner-container ${currentViewport}`} aria-label={'Map: ' + title} ref={innerContainerRef}>
1461
- {!window.matchMedia('(any-hover: none)').matches && 'hover' === tooltips.appearanceType &&
1462
- <ReactTooltip id="tooltip" variant="light" float={true} className={`${tooltips.capitalizeLabels ? 'capitalize tooltip' : 'tooltip'}`} />
1463
- }
1464
- {state.general.title && (
1528
+ {!window.matchMedia('(any-hover: none)').matches && 'hover' === tooltips.appearanceType && <ReactTooltip id='tooltip' variant='light' float={true} className={`${tooltips.capitalizeLabels ? 'capitalize tooltip' : 'tooltip'}`} />}
1529
+ {title && (
1465
1530
  <header className={general.showTitle === true ? 'visible' : 'hidden'} {...(!general.showTitle || !state.general.title ? { 'aria-hidden': true } : { 'aria-hidden': false })}>
1466
1531
  {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
1467
1532
  <div role='heading' className={'map-title ' + general.headerColor} tabIndex='0' aria-level='2'>
@@ -1470,9 +1535,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1470
1535
  </div>
1471
1536
  </header>
1472
1537
  )}
1473
- {general.introText &&
1474
- <section className='introText'>{parse(general.introText)}</section>
1475
- }
1538
+ {general.introText && <section className='introText'>{parse(general.introText)}</section>}
1476
1539
 
1477
1540
  <Filters />
1478
1541