@cdc/map 4.23.3 → 4.23.5

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.
package/src/CdcMap.jsx CHANGED
@@ -8,6 +8,7 @@ import ResizeObserver from 'resize-observer-polyfill'
8
8
  // Third party
9
9
  import { Tooltip as ReactTooltip } from 'react-tooltip'
10
10
  import chroma from 'chroma-js'
11
+ import Papa from 'papaparse'
11
12
  import parse from 'html-react-parser'
12
13
  import 'react-tooltip/dist/react-tooltip.css'
13
14
 
@@ -34,15 +35,15 @@ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
34
35
  import getViewport from '@cdc/core/helpers/getViewport'
35
36
  import Loading from '@cdc/core/components/Loading'
36
37
  import numberFromString from '@cdc/core/helpers/numberFromString'
38
+ import DataTable from '@cdc/core/components/DataTable' // Future: Lazy
37
39
 
38
40
  // Child Components
39
41
  import ConfigContext from './context'
40
- import Filters from './components/Filters'
42
+ import Filters, { useFilters } from '@cdc/core/components/Filters'
41
43
  import Modal from './components/Modal'
42
44
  import Sidebar from './components/Sidebar'
43
45
 
44
46
  import CountyMap from './components/CountyMap' // Future: Lazy
45
- import DataTable from './components/DataTable' // Future: Lazy
46
47
  import EditorPanel from './components/EditorPanel' // Future: Lazy
47
48
  import NavigationMenu from './components/NavigationMenu' // Future: Lazy
48
49
  import SingleStateMap from './components/SingleStateMap' // Future: Lazy
@@ -82,7 +83,7 @@ const hashObj = row => {
82
83
 
83
84
  return hash
84
85
  } catch (e) {
85
- console.error(e)
86
+ console.error('COVE: ', e) // eslint-disable-line
86
87
  }
87
88
  }
88
89
 
@@ -112,7 +113,7 @@ const getUniqueValues = (data, columnName) => {
112
113
  return Object.keys(result)
113
114
  }
114
115
 
115
- const CdcMap = ({ className, config, navigationHandler: customNavigationHandler, isDashboard = false, isEditor = false, configUrl, logo = null, setConfig, setSharedFilter, setSharedFilterValue, hostname = 'localhost:8080', link }) => {
116
+ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler, isDashboard = false, isEditor = false, isDebug = false, configUrl, logo = null, setConfig, setSharedFilter, setSharedFilterValue, hostname = 'localhost:8080', link }) => {
116
117
  const transform = new DataTransform()
117
118
  const [state, setState] = useState({ ...initialState })
118
119
  const [loading, setLoading] = useState(true)
@@ -127,10 +128,14 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
127
128
  const [coveLoadedHasRan, setCoveLoadedHasRan] = useState(false)
128
129
  const [container, setContainer] = useState()
129
130
  const [imageId, setImageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`) // eslint-disable-line
131
+ const [dimensions, setDimensions] = useState()
130
132
 
133
+ const { changeFilterActive, handleSorting } = useFilters({ config: state, setConfig: setState })
131
134
  let legendMemo = useRef(new Map())
132
135
  let innerContainerRef = useRef()
133
136
 
137
+ if (isDebug) console.log('CdcMap state=', state) // eslint-disable-line
138
+
134
139
  useEffect(() => {
135
140
  try {
136
141
  if (filteredCountryCode) {
@@ -145,7 +150,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
145
150
  })
146
151
  }
147
152
  } catch (e) {
148
- console.error('Failed to set world map zoom.')
153
+ console.error('COVE: Failed to set world map zoom.') // eslint-disable-line
149
154
  }
150
155
  }, [filteredCountryCode]) // eslint-disable-line
151
156
 
@@ -169,6 +174,9 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
169
174
 
170
175
  const generateRuntimeLegendHash = () => {
171
176
  return hashObj({
177
+ unified: state.legend.unified ?? false,
178
+ equalNumberOptIn: state.general.equalNumberOptIn ?? false,
179
+ specialClassesLast: state.legend.showSpecialClassesLast ?? false,
172
180
  color: state.color,
173
181
  customColors: state.customColors,
174
182
  numberOfItems: state.legend.numberOfItems,
@@ -179,15 +187,25 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
179
187
  specialClasses: state.legend.specialClasses,
180
188
  geoType: state.general.geoType,
181
189
  data: state.data,
182
- ...runtimeFilters
190
+ ...runtimeFilters,
191
+ filters: {
192
+ ...state.filters
193
+ }
183
194
  })
184
195
  }
185
196
 
186
197
  const resizeObserver = new ResizeObserver(entries => {
187
198
  for (let entry of entries) {
199
+ let { width, height } = entry.contentRect
188
200
  let newViewport = getViewport(entry.contentRect.width)
201
+ let editorWidth = 350
189
202
 
190
203
  setCurrentViewport(newViewport)
204
+
205
+ if (isEditor) {
206
+ width = width - editorWidth
207
+ }
208
+ setDimensions([width, height])
191
209
  }
192
210
  })
193
211
 
@@ -285,6 +303,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
285
303
  result.fromHash = hash
286
304
  }
287
305
 
306
+ result.runtimeDataHash = runtimeData.fromHash
307
+
288
308
  // Unified will based the legend off ALL of the data maps received. Otherwise, it will use
289
309
  let dataSet = obj.legend.unified ? obj.data : Object.values(runtimeData)
290
310
 
@@ -752,9 +772,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
752
772
 
753
773
  if (hash) filters.fromHash = hash
754
774
 
755
- obj?.filters.forEach(({ columnName, label, active, values }, idx) => {
756
- if (undefined === columnName) return
757
-
775
+ obj?.filters.forEach(({ columnName, label, labels, queryParameter, orderedValues, active, values, type }, idx) => {
758
776
  let newFilter = runtimeFilters[idx]
759
777
 
760
778
  const sortAsc = (a, b) => {
@@ -765,30 +783,41 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
765
783
  return b.toString().localeCompare(a.toString(), 'en', { numeric: true })
766
784
  }
767
785
 
768
- values = getUniqueValues(state.data, columnName)
786
+ if (type !== 'url') {
787
+ values = getUniqueValues(state.data, columnName)
769
788
 
770
- if (obj.filters[idx].order === 'asc') {
771
- values = values.sort(sortAsc)
772
- }
789
+ if (obj.filters[idx].order === 'asc') {
790
+ values = values.sort(sortAsc)
791
+ }
773
792
 
774
- if (obj.filters[idx].order === 'desc') {
775
- values = values.sort(sortDesc)
776
- }
793
+ if (obj.filters[idx].order === 'desc') {
794
+ values = values.sort(sortDesc)
795
+ }
777
796
 
778
- if (obj.filters[idx].order === 'cust') {
779
- if (obj.filters[idx]?.values.length > 0) {
780
- values = obj.filters[idx].values
797
+ if (obj.filters[idx].order === 'cust') {
798
+ if (obj.filters[idx]?.values.length > 0) {
799
+ values = obj.filters[idx].values
800
+ }
781
801
  }
802
+ } else {
803
+ values = values
782
804
  }
783
805
 
784
806
  if (undefined === newFilter) {
785
807
  newFilter = {}
786
808
  }
787
809
 
810
+ newFilter.order = obj.filters[idx].order ? obj.filters[idx].order : 'asc'
811
+ newFilter.type = type
788
812
  newFilter.label = label ?? ''
789
813
  newFilter.columnName = columnName
814
+ newFilter.orderedValues = orderedValues
815
+ newFilter.queryParameter = queryParameter
816
+ newFilter.labels = labels
790
817
  newFilter.values = values
791
- newFilter.active = active || values[0] // Default to first found value
818
+ handleSorting(newFilter)
819
+ newFilter.active = active ?? values[0] // Default to first found value
820
+ newFilter.filterStyle = obj.filters[idx].filterStyle ? obj.filters[idx].filterStyle : 'dropdown'
792
821
 
793
822
  filters.push(newFilter)
794
823
  })
@@ -810,8 +839,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
810
839
  addUIDs(obj, obj.columns.geo.name)
811
840
  obj.data.forEach(row => {
812
841
  if (test) {
813
- console.log('object', obj)
814
- console.log('row', row)
842
+ console.log('object', obj) // eslint-disable-line
843
+ console.log('row', row) // eslint-disable-line
815
844
  }
816
845
 
817
846
  if (undefined === row.uid) return false // No UID for this row, we can't use for mapping
@@ -841,8 +870,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
841
870
  // Filters
842
871
  if (filters?.length) {
843
872
  for (let i = 0; i < filters.length; i++) {
844
- const { columnName, active } = filters[i]
845
- if (String(row[columnName]) !== String(active)) return false // Bail out, not part of filter
873
+ const { columnName, active, type } = filters[i]
874
+ if (type !== 'url' && String(row[columnName]) !== String(active)) return false // Bail out, not part of filter
846
875
  }
847
876
  }
848
877
 
@@ -854,7 +883,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
854
883
 
855
884
  return result
856
885
  } catch (e) {
857
- console.error(e)
886
+ console.error('COVE: ', e) // eslint-disable-line
858
887
  }
859
888
  })
860
889
 
@@ -873,38 +902,12 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
873
902
  }
874
903
  }
875
904
 
876
- const changeFilterActive = async (idx, activeValue) => {
877
- // Reset active legend toggles
878
- resetLegendToggles()
879
-
880
- try {
881
- const isEmpty = obj => {
882
- return Object.keys(obj).length === 0
883
- }
884
-
885
- let filters = [...runtimeFilters]
886
-
887
- filters[idx] = { ...filters[idx] }
888
- filters[idx].active = activeValue
889
-
890
- const newData = generateRuntimeData(state, filters)
891
-
892
- // throw an error if newData is empty
893
- if (isEmpty(newData)) throw new Error('Cove Filter Error: No runtime data to set for this filter')
894
-
895
- // set the runtime filters and data
896
- setRuntimeData(newData)
897
- setRuntimeFilters(filters)
898
- } catch (e) {
899
- console.error(e.message)
900
- }
901
- }
902
-
903
905
  const displayDataAsText = (value, columnName) => {
904
906
  if (value === null || value === '' || value === undefined) {
905
907
  return ''
906
908
  }
907
909
 
910
+ // if string of letters like 'Home' then dont need to format as a number
908
911
  if (typeof value === 'string' && value.length > 0 && state.legend.type === 'equalnumber') {
909
912
  return value
910
913
  }
@@ -913,6 +916,17 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
913
916
 
914
917
  let columnObj = state.columns[columnName]
915
918
 
919
+ if (columnObj === undefined) {
920
+ // then use left axis config
921
+ columnObj = state.columns.primary
922
+ // NOTE: Left Value Axis uses different names
923
+ // so map them below so the code below works
924
+ // - copy commas to useCommas to work below
925
+ columnObj['useCommas'] = columnObj.commas
926
+ // - copy roundTo to roundToPlace to work below
927
+ columnObj['roundToPlace'] = columnObj.roundTo ? columnObj.roundTo : ''
928
+ }
929
+
916
930
  if (columnObj) {
917
931
  // If value is a number, apply specific formattings
918
932
  if (Number(value)) {
@@ -970,7 +984,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
970
984
  // Fail state
971
985
  return generateColorsArray()
972
986
  } catch (e) {
973
- console.error(e)
987
+ console.error('COVE: ', e) // eslint-disable-line
974
988
  }
975
989
  }
976
990
 
@@ -1000,16 +1014,16 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1000
1014
  const column = state.columns[columnKey]
1001
1015
 
1002
1016
  if (true === column.tooltip) {
1003
- let label = column.label.length > 0 ? column.label : ''
1017
+ let label = column.label?.length > 0 ? column.label : ''
1004
1018
 
1005
1019
  let value
1006
1020
 
1007
1021
  if (state.legend.specialClasses && state.legend.specialClasses.length && typeof state.legend.specialClasses[0] === 'object') {
1008
1022
  // THIS CODE SHOULD NOT ACT ON THE ENTIRE ROW OF KEYS BUT ONLY THE ONE KEY IN THE SPECIAL CLASS
1009
1023
  for (let i = 0; i < state.legend.specialClasses.length; i++) {
1010
- // DEV-3303 - Special Classes label in HOVERS should only apply to selected special class key
1024
+ // Special Classes label in HOVERS should only apply to selected special class key
1011
1025
  // - 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) {
1026
+ if (column.name === state.legend.specialClasses[i].key) {
1013
1027
  if (String(row[state.legend.specialClasses[i].key]) === state.legend.specialClasses[i].value) {
1014
1028
  value = displayDataAsText(state.legend.specialClasses[i].label, columnKey)
1015
1029
  break
@@ -1053,25 +1067,29 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1053
1067
  // - this function is used to prevent that and instead give the formatting that is wanted
1054
1068
  // Example: Desired city display in tooltip on map: "Inter-Tribal Indian Reservation"
1055
1069
  const titleCase = string => {
1056
- // if hyphen found, then split, uppercase each word, and put back together
1057
- if (string.includes('–') || string.includes('-')) {
1058
- let dashSplit = string.includes('–') ? string.split('–') : string.split('-') // determine hyphen or en dash to split on
1059
- let splitCharacter = string.includes('–') ? '–' : '-' // print hyphen or en dash later on.
1060
- let frontSplit = dashSplit[0]
1061
- .split(' ')
1062
- .map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
1063
- .join(' ')
1064
- let backSplit = dashSplit[1]
1065
- .split(' ')
1066
- .map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
1067
- .join(' ')
1068
- return frontSplit + splitCharacter + backSplit
1069
- } else {
1070
- // just return with each word uppercase
1071
- return string
1072
- .split(' ')
1073
- .map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
1074
- .join(' ')
1070
+ // guard clause else error in editor
1071
+ if (!string) return
1072
+ if (string !== undefined) {
1073
+ // if hyphen found, then split, uppercase each word, and put back together
1074
+ if (string.includes('–') || string.includes('-')) {
1075
+ let dashSplit = string.includes('–') ? string.split('–') : string.split('-') // determine hyphen or en dash to split on
1076
+ let splitCharacter = string.includes('–') ? '–' : '-' // print hyphen or en dash later on.
1077
+ let frontSplit = dashSplit[0]
1078
+ .split(' ')
1079
+ .map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
1080
+ .join(' ')
1081
+ let backSplit = dashSplit[1]
1082
+ .split(' ')
1083
+ .map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
1084
+ .join(' ')
1085
+ return frontSplit + splitCharacter + backSplit
1086
+ } else {
1087
+ // just return with each word uppercase
1088
+ return string
1089
+ .split(' ')
1090
+ .map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
1091
+ .join(' ')
1092
+ }
1075
1093
  }
1076
1094
  }
1077
1095
 
@@ -1083,6 +1101,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1083
1101
  delete legendItem.disabled
1084
1102
  })
1085
1103
 
1104
+ newLegend.runtimeDataHash = runtimeLegend.runtimeDataHash
1105
+
1086
1106
  setRuntimeLegend(newLegend)
1087
1107
  }
1088
1108
 
@@ -1201,7 +1221,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1201
1221
  }
1202
1222
 
1203
1223
  const handleMapAriaLabels = (state = '', testing = false) => {
1204
- if (testing) console.log(`handleMapAriaLabels Testing On: ${state}`)
1224
+ if (testing) console.log(`handleMapAriaLabels Testing On: ${state}`) // eslint-disable-line
1205
1225
  try {
1206
1226
  if (!state.general.geoType) throw Error('handleMapAriaLabels: no geoType found in state')
1207
1227
  let ariaLabel = ''
@@ -1232,7 +1252,68 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1232
1252
 
1233
1253
  return ariaLabel
1234
1254
  } catch (e) {
1235
- console.error(e.message)
1255
+ console.error('COVE: ', e.message) // eslint-disable-line
1256
+ }
1257
+ }
1258
+
1259
+ const reloadURLData = async () => {
1260
+ if (state.dataUrl) {
1261
+ const dataUrl = new URL(state.runtimeDataUrl || state.dataUrl)
1262
+ let qsParams = Object.fromEntries(new URLSearchParams(dataUrl.search))
1263
+
1264
+ let isUpdateNeeded = false
1265
+ state.filters.forEach(filter => {
1266
+ if (filter.type === 'url' && qsParams[filter.queryParameter] !== decodeURIComponent(filter.active)) {
1267
+ qsParams[filter.queryParameter] = filter.active
1268
+ isUpdateNeeded = true
1269
+ }
1270
+ })
1271
+
1272
+ if (!isUpdateNeeded) return
1273
+
1274
+ let dataUrlFinal = `${dataUrl.origin}${dataUrl.pathname}${Object.keys(qsParams)
1275
+ .map((param, i) => {
1276
+ let qs = i === 0 ? '?' : '&'
1277
+ qs += param + '='
1278
+ qs += qsParams[param]
1279
+ return qs
1280
+ })
1281
+ .join('')}`
1282
+
1283
+ let data
1284
+
1285
+ try {
1286
+ const regex = /(?:\.([^.]+))?$/
1287
+
1288
+ const ext = regex.exec(dataUrl.pathname)[1]
1289
+ if ('csv' === ext) {
1290
+ data = await fetch(dataUrlFinal)
1291
+ .then(response => response.text())
1292
+ .then(responseText => {
1293
+ const parsedCsv = Papa.parse(responseText, {
1294
+ header: true,
1295
+ dynamicTyping: true,
1296
+ skipEmptyLines: true
1297
+ })
1298
+ return parsedCsv.data
1299
+ })
1300
+ } else if ('json' === ext) {
1301
+ data = await fetch(dataUrlFinal).then(response => response.json())
1302
+ } else {
1303
+ data = []
1304
+ }
1305
+ } catch (e) {
1306
+ console.error(`Cannot parse URL: ${dataUrlFinal}`) // eslint-disable-line
1307
+ console.log(e) // eslint-disable-line
1308
+ data = []
1309
+ }
1310
+
1311
+ if (state.dataDescription) {
1312
+ data = transform.autoStandardize(data)
1313
+ data = transform.developerStandardize(data, state.dataDescription)
1314
+ }
1315
+
1316
+ setState({ ...state, runtimeDataUrl: dataUrlFinal, data })
1236
1317
  }
1237
1318
  }
1238
1319
 
@@ -1246,8 +1327,9 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1246
1327
  ...configObj
1247
1328
  }
1248
1329
 
1249
- // If a dataUrl property exists, always pull from that.
1250
- if (newState.dataUrl) {
1330
+ const urlFilters = newState.filters ? (newState.filters.filter(filter => filter.type === 'url').length > 0 ? true : false) : false
1331
+
1332
+ if (newState.dataUrl && !urlFilters) {
1251
1333
  if (newState.dataUrl[0] === '/') {
1252
1334
  newState.dataUrl = 'http://' + hostname + newState.dataUrl
1253
1335
  }
@@ -1286,8 +1368,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1286
1368
  addUIDs(newState, newState.columns.geo.name || newState.columns.geo.fips)
1287
1369
  }
1288
1370
 
1289
- if (newState.dataTable.forceDisplay === undefined) {
1290
- newState.dataTable.forceDisplay = !isDashboard
1371
+ if (newState.table.forceDisplay === undefined) {
1372
+ newState.table.forceDisplay = !isDashboard
1291
1373
  }
1292
1374
 
1293
1375
  validateFipsCodeLength(newState)
@@ -1342,16 +1424,16 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1342
1424
 
1343
1425
  // DEV-769 make "Data Table" both a required field and default value
1344
1426
  useEffect(() => {
1345
- if (state.dataTable?.title === '' || state.dataTable?.title === undefined) {
1427
+ if (state.table?.label === '' || state.table?.label === undefined) {
1346
1428
  setState({
1347
1429
  ...state,
1348
- dataTable: {
1349
- ...state.dataTable,
1430
+ table: {
1431
+ ...state.table,
1350
1432
  title: 'Data Table'
1351
1433
  }
1352
1434
  })
1353
1435
  }
1354
- }, [state.dataTable]) // eslint-disable-line
1436
+ }, [state.table]) // eslint-disable-line
1355
1437
 
1356
1438
  // When geo label override changes
1357
1439
  // - redo the tooltips
@@ -1391,16 +1473,14 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1391
1473
  })
1392
1474
 
1393
1475
  // Data
1394
- let newRuntimeData
1395
1476
  if (hashData !== runtimeData.fromHash && state.data?.fromColumn) {
1396
1477
  const newRuntimeData = generateRuntimeData(state, filters || runtimeFilters, hashData)
1397
1478
  setRuntimeData(newRuntimeData)
1398
- }
1399
-
1400
- // Legend
1401
- if (hashLegend !== runtimeLegend.fromHash && (undefined === runtimeData.init || newRuntimeData)) {
1402
- const legend = generateRuntimeLegend(state, newRuntimeData || runtimeData, hashLegend)
1403
- setRuntimeLegend(legend)
1479
+ } else {
1480
+ if (hashLegend !== runtimeLegend.fromHash && undefined === runtimeData.init) {
1481
+ const legend = generateRuntimeLegend(state, runtimeData, hashLegend)
1482
+ setRuntimeLegend(legend)
1483
+ }
1404
1484
  }
1405
1485
  }, [state]) // eslint-disable-line
1406
1486
 
@@ -1412,7 +1492,11 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1412
1492
  const legend = generateRuntimeLegend(state, runtimeData, hashLegend)
1413
1493
  setRuntimeLegend(legend)
1414
1494
  }
1415
- }, [runtimeData]) // eslint-disable-line
1495
+ }, [runtimeData, state.legend.unified, state.legend.showSpecialClassesLast, state.legend.separateZero, state.general.equalNumberOptIn, state.legend.numberOfItems, state.legend.specialClasses]) // eslint-disable-line
1496
+
1497
+ useEffect(() => {
1498
+ reloadURLData()
1499
+ }, [JSON.stringify(state.filters)])
1416
1500
 
1417
1501
  if (config) {
1418
1502
  // eslint-disable-next-line react-hooks/rules-of-hooks
@@ -1422,14 +1506,14 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1422
1506
  }
1423
1507
 
1424
1508
  // Destructuring for more readable JSX
1425
- const { general, tooltips, dataTable } = state
1509
+ const { general, tooltips, table } = state
1426
1510
  let { title, subtext = '' } = general
1427
1511
 
1428
1512
  // if no title AND in editor then set a default
1429
1513
  if (isEditor) {
1430
1514
  if (!title || title === '') title = 'Map Title'
1431
1515
  }
1432
- if (!dataTable.title || dataTable.title === '') dataTable.title = 'Data Table'
1516
+ if (!table.label || table.label === '') table.label = 'Data Table'
1433
1517
 
1434
1518
  // Outer container classes
1435
1519
  let outerContainerClasses = ['cdc-open-viz-module', 'cdc-map-outer-container', currentViewport]
@@ -1475,12 +1559,13 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1475
1559
  runtimeFilters,
1476
1560
  setRuntimeFilters,
1477
1561
  innerContainerRef,
1478
- currentViewport
1562
+ currentViewport,
1563
+ isDebug
1479
1564
  }
1480
1565
 
1481
1566
  if (!mapProps.data || !state.data) return <Loading />
1482
1567
 
1483
- const hasDataTable = state.runtime.editorErrorMessage.length === 0 && true === dataTable.forceDisplay && general.type !== 'navigation' && false === loading
1568
+ const hasDataTable = state.runtime.editorErrorMessage.length === 0 && true === table.forceDisplay && general.type !== 'navigation' && false === loading
1484
1569
 
1485
1570
  const handleMapTabbing = () => {
1486
1571
  let tabbingID
@@ -1506,12 +1591,20 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1506
1591
 
1507
1592
  const tabId = handleMapTabbing()
1508
1593
 
1594
+ // this only shows in Dashboard config mode and only if Show Table is also set
1595
+ const tableLink = (
1596
+ <a href={`#data-table-${state.general.dataKey}`} className='margin-left-href'>
1597
+ {state.general.dataKey} (Go to Table)
1598
+ </a>
1599
+ )
1600
+
1509
1601
  return (
1510
1602
  <ConfigContext.Provider value={mapProps}>
1511
1603
  <div className={outerContainerClasses.join(' ')} ref={outerContainerRef} data-download-id={imageId}>
1512
1604
  {isEditor && (
1513
1605
  <EditorPanel
1514
1606
  isDashboard={isDashboard}
1607
+ isDebug={isDebug}
1515
1608
  state={state}
1516
1609
  setState={setState}
1517
1610
  loadConfig={loadConfig}
@@ -1537,7 +1630,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1537
1630
  )}
1538
1631
  {general.introText && <section className='introText'>{parse(general.introText)}</section>}
1539
1632
 
1540
- <Filters />
1633
+ {/* prettier-ignore */}
1634
+ {state?.filters?.length > 0 && <Filters config={state} setConfig={setState} filteredData={runtimeFilters} setFilteredData={setRuntimeFilters} dimensions={dimensions} />}
1541
1635
 
1542
1636
  <div
1543
1637
  role='button'
@@ -1591,7 +1685,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1591
1685
 
1592
1686
  {'navigation' === general.type && <NavigationMenu mapTabbingID={tabId} displayGeoName={displayGeoName} data={runtimeData} options={general} columns={state.columns} navigationHandler={val => navigationHandler(val)} />}
1593
1687
 
1594
- {link && link}
1688
+ {/* Link */}
1689
+ {isDashboard && config.table?.forceDisplay && config.table.showDataTableLink ? tableLink : link && link}
1595
1690
 
1596
1691
  {subtext.length > 0 && <p className='subtext'>{parse(subtext)}</p>}
1597
1692
 
@@ -1600,12 +1695,12 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1600
1695
  {state.general.showDownloadPdfButton && <CoveMediaControls.Button text='Download PDF' title='Download Chart as PDF' type='pdf' state={state} elementToCapture={imageId} />}
1601
1696
  </CoveMediaControls.Section>
1602
1697
 
1603
- {state.runtime.editorErrorMessage.length === 0 && true === dataTable.forceDisplay && general.type !== 'navigation' && false === loading && (
1698
+ {state.runtime.editorErrorMessage.length === 0 && true === table.forceDisplay && general.type !== 'navigation' && false === loading && (
1604
1699
  <DataTable
1605
- state={state}
1700
+ config={state}
1606
1701
  rawData={state.data}
1607
1702
  navigationHandler={navigationHandler}
1608
- expandDataTable={general.expandDataTable}
1703
+ expandDataTable={general.expandDataTable ? general.expandDataTable : table.expanded ? table.expanded : false}
1609
1704
  headerColor={general.headerColor}
1610
1705
  columns={state.columns}
1611
1706
  showDownloadButton={general.showDownloadButton}
@@ -1614,9 +1709,9 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1614
1709
  displayDataAsText={displayDataAsText}
1615
1710
  displayGeoName={displayGeoName}
1616
1711
  applyLegendToRow={applyLegendToRow}
1617
- tableTitle={dataTable.title}
1618
- indexTitle={dataTable.indexLabel}
1619
- mapTitle={general.title}
1712
+ tableTitle={table.label}
1713
+ indexTitle={table.indexLabel}
1714
+ vizTitle={general.title}
1620
1715
  viewport={currentViewport}
1621
1716
  formatLegendLocation={formatLegendLocation}
1622
1717
  setFilteredCountryCode={setFilteredCountryCode}
@@ -1626,6 +1721,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1626
1721
  innerContainerRef={innerContainerRef}
1627
1722
  outerContainerRef={outerContainerRef}
1628
1723
  imageRef={imageId}
1724
+ isDebug={isDebug}
1629
1725
  />
1630
1726
  )}
1631
1727