@cdc/map 4.22.11 → 4.23.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 (49) hide show
  1. package/dist/495.js +3 -0
  2. package/dist/703.js +1 -0
  3. package/dist/856.js +3 -0
  4. package/dist/cdcmap.js +724 -120
  5. package/examples/bubble-us.json +362 -362
  6. package/examples/bubble-world.json +426 -426
  7. package/examples/{private/city-state2.json → city-state.json} +2 -2
  8. package/examples/example-city-state.json +202 -25
  9. package/package.json +3 -4
  10. package/src/CdcMap.js +142 -232
  11. package/src/components/BubbleList.js +2 -3
  12. package/src/components/CityList.js +1 -2
  13. package/src/components/DataTable.js +31 -3
  14. package/src/components/EditorPanel.js +73 -16
  15. package/src/components/Filters.js +114 -0
  16. package/src/components/Sidebar.js +0 -6
  17. package/src/components/UsaMap.js +4 -4
  18. package/src/context.js +5 -0
  19. package/src/data/initial-state.js +19 -15
  20. package/src/data/supported-geos.js +3201 -3175
  21. package/src/index.html +32 -28
  22. package/src/scss/datatable.scss +1 -2
  23. package/src/scss/filters.scss +42 -0
  24. package/src/scss/main.scss +4 -3
  25. package/examples/private/atsdr.json +0 -429
  26. package/examples/private/atsdr_new.json +0 -436
  27. package/examples/private/bubble.json +0 -283
  28. package/examples/private/city-state.json +0 -428
  29. package/examples/private/cty-issue.json +0 -42765
  30. package/examples/private/default-usa.json +0 -457
  31. package/examples/private/default-world-data.json +0 -1444
  32. package/examples/private/default.json +0 -968
  33. package/examples/private/diff.json +0 -226
  34. package/examples/private/filters.json +0 -1
  35. package/examples/private/legend-issue.json +0 -3271
  36. package/examples/private/map-issue.json +0 -166
  37. package/examples/private/map-rounding-error.json +0 -42756
  38. package/examples/private/map.csv +0 -60
  39. package/examples/private/mdx.json +0 -210
  40. package/examples/private/monkeypox.json +0 -376
  41. package/examples/private/regions.json +0 -52
  42. package/examples/private/valid-data-map.csv +0 -59
  43. package/examples/private/wcmsrd-13881-data.json +0 -2858
  44. package/examples/private/wcmsrd-13881.json +0 -5819
  45. package/examples/private/wcmsrd-14492-data.json +0 -292
  46. package/examples/private/wcmsrd-14492.json +0 -104
  47. package/examples/private/wcmsrd-test.json +0 -265
  48. package/examples/private/world.json +0 -1580
  49. package/examples/private/worldmap.json +0 -1490
package/src/CdcMap.js CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useRef, memo, useCallback } from 'react'
1
+ import React, { useState, useEffect, useRef, useCallback } from 'react'
2
2
  import * as d3 from 'd3'
3
3
 
4
4
  // IE11
@@ -10,9 +10,6 @@ import ResizeObserver from 'resize-observer-polyfill'
10
10
  import ReactTooltip from 'react-tooltip'
11
11
  import chroma from 'chroma-js'
12
12
  import parse from 'html-react-parser'
13
- import html2pdf from 'html2pdf.js'
14
- import html2canvas from 'html2canvas'
15
- import Canvg from 'canvg'
16
13
 
17
14
  // Data
18
15
  import colorPalettes from '../../core/data/colorPalettes'
@@ -20,23 +17,18 @@ import ExternalIcon from './images/external-link.svg'
20
17
  import { supportedStates, supportedTerritories, supportedCountries, supportedCounties, supportedCities, supportedStatesFipsCodes, stateFipsToTwoDigit, supportedRegions } from './data/supported-geos'
21
18
  import initialState from './data/initial-state'
22
19
  import { countryCoordinates } from './data/country-coordinates'
20
+ import CoveMediaControls from '@cdc/core/helpers/CoveMediaControls'
23
21
 
24
22
  // Sass
25
23
  import './scss/main.scss'
26
24
  import './scss/btn.scss'
27
25
 
28
- // Images
29
- // TODO: Move to Icon component
30
- import DownloadImg from './images/icon-download-img.svg'
31
- import DownloadPdf from './images/icon-download-pdf.svg'
32
-
33
26
  // Core
34
27
  import Loading from '@cdc/core/components/Loading'
35
28
  import { DataTransform } from '@cdc/core/helpers/DataTransform'
36
29
  import getViewport from '@cdc/core/helpers/getViewport'
37
30
  import numberFromString from '@cdc/core/helpers/numberFromString'
38
31
  import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
39
- import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
40
32
 
41
33
  // Child Components
42
34
  import Sidebar from './components/Sidebar'
@@ -49,6 +41,8 @@ import DataTable from './components/DataTable' // Future: Lazy
49
41
  import NavigationMenu from './components/NavigationMenu' // Future: Lazy
50
42
  import WorldMap from './components/WorldMap' // Future: Lazy
51
43
  import SingleStateMap from './components/SingleStateMap' // Future: Lazy
44
+ import Filters from './components/Filters'
45
+ import Context from './context'
52
46
 
53
47
  import { publish } from '@cdc/core/helpers/events'
54
48
 
@@ -106,7 +100,6 @@ const getUniqueValues = (data, columnName) => {
106
100
  }
107
101
 
108
102
  const CdcMap = ({ className, config, navigationHandler: customNavigationHandler, isDashboard = false, isEditor = false, configUrl, logo = null, setConfig, setSharedFilter, setSharedFilterValue, hostname = 'localhost:8080', link }) => {
109
- const [showLoadingMessage, setShowLoadingMessage] = useState(false)
110
103
  const transform = new DataTransform()
111
104
  const [state, setState] = useState({ ...initialState })
112
105
  const [loading, setLoading] = useState(true)
@@ -120,13 +113,14 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
120
113
  const [position, setPosition] = useState(state.mapPosition)
121
114
  const [coveLoadedHasRan, setCoveLoadedHasRan] = useState(false)
122
115
  const [container, setContainer] = useState()
116
+ const [imageId, setImageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`) // eslint-disable-line
123
117
 
124
118
  let legendMemo = useRef(new Map())
119
+ let innerContainerRef = useRef()
125
120
 
126
121
  useEffect(() => {
127
122
  try {
128
123
  if (filteredCountryCode) {
129
- const filteredCountryObj = runtimeData[filteredCountryCode]
130
124
  const coordinates = countryCoordinates[filteredCountryCode]
131
125
  const long = coordinates[1]
132
126
  const lat = coordinates[0]
@@ -160,13 +154,6 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
160
154
  }
161
155
  }, [state.mapPosition, setPosition])
162
156
 
163
- const setZoom = reversedCoordinates => {
164
- setState({
165
- ...state,
166
- mapPosition: { coordinates: reversedCoordinates, zoom: 3 }
167
- })
168
- }
169
-
170
157
  const resizeObserver = new ResizeObserver(entries => {
171
158
  for (let entry of entries) {
172
159
  let newViewport = getViewport(entry.contentRect.width)
@@ -251,7 +238,6 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
251
238
  const generateRuntimeLegend = useCallback((obj, runtimeData, hash) => {
252
239
  const newLegendMemo = new Map() // Reset memoization
253
240
  const primaryCol = obj.columns.primary.name,
254
- isData = obj.general.type === 'data',
255
241
  isBubble = obj.general.type === 'bubble',
256
242
  categoricalCol = obj.columns.categorical ? obj.columns.categorical.name : undefined,
257
243
  type = obj.legend.type,
@@ -410,7 +396,6 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
410
396
  // Apply custom sorting or regular sorting
411
397
  let configuredOrder = obj.legend.categoryValuesOrder ?? []
412
398
 
413
-
414
399
  if (configuredOrder.length) {
415
400
  sorted.sort((a, b) => {
416
401
  return configuredOrder.indexOf(a) - configuredOrder.indexOf(b)
@@ -558,7 +543,6 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
558
543
 
559
544
  breaks.map((item, index) => {
560
545
  const setMin = index => {
561
- //debugger;
562
546
  let min = breaks[index]
563
547
 
564
548
  // if first break is a seperated zero, min is zero
@@ -809,101 +793,6 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
809
793
  }
810
794
  }
811
795
 
812
- const saveImageAs = (uri, filename) => {
813
- const ie = navigator.userAgent.match(/MSIE\s([\d.]+)/)
814
- const ie11 = navigator.userAgent.match(/Trident\/7.0/) && navigator.userAgent.match(/rv:11/)
815
- const ieEdge = navigator.userAgent.match(/Edge/g)
816
- const ieVer = ie ? ie[1] : ie11 ? 11 : ieEdge ? 12 : -1
817
-
818
- if (ieVer > -1) {
819
- const fileAsBlob = new Blob([uri], {
820
- type: 'image/png'
821
- })
822
- window.navigator.msSaveBlob(fileAsBlob, filename)
823
- } else {
824
- const link = document.createElement('a')
825
- if (typeof link.download === 'string') {
826
- link.href = uri
827
- link.download = filename
828
- link.onclick = e => document.body.removeChild(e.target)
829
- document.body.appendChild(link)
830
- link.click()
831
- } else {
832
- window.open(uri)
833
- }
834
- }
835
- }
836
-
837
- const generateMedia = (target, type) => {
838
- // Convert SVG to canvas
839
- const baseSvg = mapSvg.current.querySelector('.rsm-svg')
840
-
841
- const ratio = baseSvg.getBoundingClientRect().height / baseSvg.getBoundingClientRect().width
842
- const calcHeight = ratio * 1440
843
- const xmlSerializer = new XMLSerializer()
844
- const svgStr = xmlSerializer.serializeToString(baseSvg)
845
- const options = { log: false, ignoreMouse: true }
846
- const canvas = document.createElement('canvas')
847
- const ctx = canvas.getContext('2d')
848
- ctx.canvas.width = 1440
849
- ctx.canvas.height = calcHeight
850
- const canvg = Canvg.fromString(ctx, svgStr, options)
851
- canvg.start()
852
-
853
- // Generate DOM <img> from svg data
854
- const generatedImage = document.createElement('img')
855
- generatedImage.src = canvas.toDataURL('image/png')
856
- generatedImage.style.width = '100%'
857
- generatedImage.style.height = 'auto'
858
-
859
- baseSvg.style.display = 'none' // Hide default SVG during media generation
860
- baseSvg.parentNode.insertBefore(generatedImage, baseSvg.nextSibling) // Insert png generated from canvas of svg
861
-
862
- // Construct filename with timestamp
863
- const date = new Date()
864
- const filename = state.general.title.replace(/\s+/g, '-').toLowerCase() + '-' + date.getDate() + date.getMonth() + date.getFullYear()
865
-
866
- switch (type) {
867
- case 'image':
868
- return html2canvas(target, {
869
- allowTaint: true,
870
- backgroundColor: '#ffffff',
871
- width: 1440,
872
- windowWidth: 1440,
873
- scale: 1,
874
- logging: false
875
- })
876
- .then(canvas => {
877
- saveImageAs(canvas.toDataURL(), filename + '.png')
878
- })
879
- .then(() => {
880
- generatedImage.remove() // Remove generated png
881
- baseSvg.style.display = null // Re-display initial svg map
882
- })
883
- case 'pdf':
884
- let opt = {
885
- margin: 0.2,
886
- filename: filename + '.pdf',
887
- image: { type: 'png' },
888
- html2canvas: { scale: 2, logging: false },
889
- jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
890
- }
891
-
892
- html2pdf()
893
- .set(opt)
894
- .from(target)
895
- .save()
896
- .then(() => {
897
- generatedImage.remove() // Remove generated png
898
- baseSvg.style.display = null // Re-display initial svg map
899
- })
900
- break
901
- default:
902
- console.warn("generateMedia param 2 type must be 'image' or 'pdf'")
903
- break
904
- }
905
- }
906
-
907
796
  const changeFilterActive = async (idx, activeValue) => {
908
797
  // Reset active legend toggles
909
798
  resetLegendToggles()
@@ -1066,10 +955,26 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1066
955
  }
1067
956
 
1068
957
  const titleCase = string => {
1069
- return string
1070
- .split(' ')
1071
- .map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
1072
- .join(' ')
958
+ // if hyphen found, then split, uppercase each word, and put back together
959
+ if (string.includes('–') || string.includes('-')) {
960
+ let dashSplit = string.includes('–') ? string.split('–') : string.split('-') // determine hyphen or en dash to split on
961
+ let splitCharacter = string.includes('–') ? '–' : '-' // print hyphen or en dash later on.
962
+ let frontSplit = dashSplit[0]
963
+ .split(' ')
964
+ .map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
965
+ .join(' ')
966
+ let backSplit = dashSplit[1]
967
+ .split(' ')
968
+ .map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
969
+ .join(' ')
970
+ return frontSplit + splitCharacter + backSplit
971
+ } else {
972
+ // just return with each word upper cased
973
+ return string
974
+ .split(' ')
975
+ .map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase())
976
+ .join(' ')
977
+ }
1073
978
  }
1074
979
 
1075
980
  // This resets all active legend toggles.
@@ -1102,7 +1007,6 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1102
1007
  // Attempts to find the corresponding value
1103
1008
  const displayGeoName = key => {
1104
1009
  let value = key
1105
-
1106
1010
  // Map to first item in values array which is the preferred label
1107
1011
  if (stateKeys.includes(value)) {
1108
1012
  value = titleCase(supportedStates[key][0])
@@ -1240,8 +1144,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1240
1144
  }
1241
1145
 
1242
1146
  // handle urls with spaces in the name.
1243
- if (newState.dataUrl) newState.dataUrl = encodeURI(`${newState.dataUrl}?v=${cacheBustingString()}`)
1244
- let newData = await fetchRemoteData(newState.dataUrl)
1147
+ if (newState.dataUrl) newState.dataUrl = encodeURI(`${newState.dataUrl}`)
1148
+ let newData = await fetchRemoteData(newState.dataUrl, 'map')
1245
1149
 
1246
1150
  if (newData && newState.dataDescription) {
1247
1151
  newData = transform.autoStandardize(newData)
@@ -1356,7 +1260,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1356
1260
  specialClasses: state.legend.specialClasses,
1357
1261
  geoType: state.general.geoType,
1358
1262
  data: state.data,
1359
- ...runtimeLegend
1263
+ ...runtimeLegend,
1264
+ ...runtimeFilters
1360
1265
  })
1361
1266
 
1362
1267
  const hashData = hashObj({
@@ -1367,7 +1272,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1367
1272
  primary: state.columns.primary.name,
1368
1273
  data: state.data,
1369
1274
  ...runtimeFilters,
1370
- mapPosition: state.mapPosition
1275
+ mapPosition: state.mapPosition,
1276
+ ...runtimeFilters
1371
1277
  })
1372
1278
 
1373
1279
  // Data
@@ -1455,7 +1361,10 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1455
1361
  setPosition,
1456
1362
  setSharedFilterValue,
1457
1363
  hasZoom: state.general.allowMapZoom,
1458
- handleMapAriaLabels
1364
+ handleMapAriaLabels,
1365
+ runtimeFilters,
1366
+ setRuntimeFilters,
1367
+ innerContainerRef
1459
1368
  }
1460
1369
 
1461
1370
  if (!mapProps.data || !state.data) return <Loading />
@@ -1487,123 +1396,124 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1487
1396
  const tabId = handleMapTabbing()
1488
1397
 
1489
1398
  return (
1490
- <div className={outerContainerClasses.join(' ')} ref={outerContainerRef}>
1491
- {isEditor && <EditorPanel isDashboard={isDashboard} state={state} setState={setState} loadConfig={loadConfig} setParentConfig={setConfig} setRuntimeFilters={setRuntimeFilters} runtimeFilters={runtimeFilters} runtimeLegend={runtimeLegend} columnsInData={Object.keys(state.data[0])} />}
1492
- {!runtimeData.init && (general.type === 'navigation' || runtimeLegend) && (
1493
- <section className={`cdc-map-inner-container ${currentViewport}`} aria-label={'Map: ' + title}>
1494
- {!window.matchMedia('(any-hover: none)').matches && 'hover' === tooltips.appearanceType && <ReactTooltip id='tooltip' place='right' type='light' html={true} className={tooltips.capitalizeLabels ? 'capitalize tooltip' : 'tooltip'} />}
1495
- {state.general.title && (
1496
- <header className={general.showTitle === true ? 'visible' : 'hidden'} {...(!general.showTitle || !state.general.title ? { 'aria-hidden': true } : { 'aria-hidden': false })}>
1497
- <div role='heading' className={'map-title ' + general.headerColor} tabIndex='0' aria-level='2'>
1498
- <sup>{general.superTitle}</sup>
1499
- <div>{parse(title)}</div>
1500
- </div>
1501
- </header>
1502
- )}
1503
-
1504
- <div>{general.introText && <section className='introText'>{parse(general.introText)}</section>}</div>
1505
-
1506
- <section
1507
- role='button'
1508
- tabIndex='0'
1509
- className={mapContainerClasses.join(' ')}
1510
- onClick={e => closeModal(e)}
1511
- onKeyDown={e => {
1512
- if (e.keyCode === 13) {
1513
- closeModal(e)
1514
- }
1515
- }}
1516
- >
1517
- {general.showDownloadMediaButton === true && (
1518
- <div className='map-downloads' data-html2canvas-ignore>
1519
- <div className='map-downloads__ui btn-group'>
1520
- <button className='btn' title='Download Map as Image' onClick={() => generateMedia(outerContainerRef.current, 'image')}>
1521
- <DownloadImg className='btn__icon' title='Download Map as Image' />
1522
- </button>
1523
- <button className='btn' title='Download Map as PDF' onClick={() => generateMedia(outerContainerRef.current, 'pdf')}>
1524
- <DownloadPdf className='btn__icon' title='Download Map as PDF' />
1525
- </button>
1399
+ <Context.Provider value={mapProps}>
1400
+ <div className={outerContainerClasses.join(' ')} ref={outerContainerRef} data-download-id={imageId}>
1401
+ {isEditor && <EditorPanel isDashboard={isDashboard} state={state} setState={setState} loadConfig={loadConfig} setParentConfig={setConfig} setRuntimeFilters={setRuntimeFilters} runtimeFilters={runtimeFilters} runtimeLegend={runtimeLegend} columnsInData={Object.keys(state.data[0])} />}
1402
+ {!runtimeData.init && (general.type === 'navigation' || runtimeLegend) && (
1403
+ <section className={`cdc-map-inner-container ${currentViewport}`} aria-label={'Map: ' + title} ref={innerContainerRef}>
1404
+ {!window.matchMedia('(any-hover: none)').matches && 'hover' === tooltips.appearanceType && <ReactTooltip id='tooltip' place='right' type='light' html={true} className={tooltips.capitalizeLabels ? 'capitalize tooltip' : 'tooltip'} />}
1405
+ {state.general.title && (
1406
+ <header className={general.showTitle === true ? 'visible' : 'hidden'} {...(!general.showTitle || !state.general.title ? { 'aria-hidden': true } : { 'aria-hidden': false })}>
1407
+ <div role='heading' className={'map-title ' + general.headerColor} tabIndex='0' aria-level='2'>
1408
+ <sup>{general.superTitle}</sup>
1409
+ <div>{parse(title)}</div>
1526
1410
  </div>
1527
- </div>
1411
+ </header>
1528
1412
  )}
1529
1413
 
1530
- <a id='skip-geo-container' className='cdcdataviz-sr-only-focusable' href={tabId}>
1531
- Skip Over Map Container
1532
- </a>
1533
-
1534
- <section className='geography-container outline-none' ref={mapSvg} tabIndex='0'>
1535
- {currentViewport && (
1536
- <section className='geography-container' ref={mapSvg}>
1537
- {modal && <Modal type={general.type} viewport={currentViewport} applyTooltipsToGeo={applyTooltipsToGeo} applyLegendToRow={applyLegendToRow} capitalize={state.tooltips.capitalizeLabels} content={modal} />}
1538
- {'single-state' === general.geoType && <SingleStateMap supportedTerritories={supportedTerritories} {...mapProps} />}
1539
- {'us' === general.geoType && 'us-geocode' !== state.general.type && <UsaMap supportedTerritories={supportedTerritories} {...mapProps} />}
1540
- {'us-region' === general.geoType && <UsaRegionMap supportedTerritories={supportedTerritories} {...mapProps} />}
1541
- {'world' === general.geoType && <WorldMap supportedCountries={supportedCountries} {...mapProps} />}
1542
- {'us-county' === general.geoType && <CountyMap supportedCountries={supportedCountries} {...mapProps} />}
1543
- {'data' === general.type && logo && <img src={logo} alt='' className='map-logo' />}
1544
- </section>
1414
+ <div>{general.introText && <section className='introText'>{parse(general.introText)}</section>}</div>
1415
+
1416
+ <Filters />
1417
+
1418
+ <div
1419
+ role='button'
1420
+ tabIndex='0'
1421
+ className={mapContainerClasses.join(' ')}
1422
+ onClick={e => closeModal(e)}
1423
+ onKeyDown={e => {
1424
+ if (e.keyCode === 13) {
1425
+ closeModal(e)
1426
+ }
1427
+ }}
1428
+ >
1429
+ <a id='skip-geo-container' className='cdcdataviz-sr-only-focusable' href={tabId}>
1430
+ Skip Over Map Container
1431
+ </a>
1432
+
1433
+ <section className='geography-container outline-none' ref={mapSvg} tabIndex='0'>
1434
+ {currentViewport && (
1435
+ <section className='geography-container' ref={mapSvg}>
1436
+ {modal && <Modal type={general.type} viewport={currentViewport} applyTooltipsToGeo={applyTooltipsToGeo} applyLegendToRow={applyLegendToRow} capitalize={state.tooltips.capitalizeLabels} content={modal} />}
1437
+ {'single-state' === general.geoType && <SingleStateMap supportedTerritories={supportedTerritories} {...mapProps} />}
1438
+ {'us' === general.geoType && 'us-geocode' !== state.general.type && <UsaMap supportedTerritories={supportedTerritories} {...mapProps} />}
1439
+ {'us-region' === general.geoType && <UsaRegionMap supportedTerritories={supportedTerritories} {...mapProps} />}
1440
+ {'world' === general.geoType && <WorldMap supportedCountries={supportedCountries} {...mapProps} />}
1441
+ {'us-county' === general.geoType && <CountyMap supportedCountries={supportedCountries} {...mapProps} />}
1442
+ {'data' === general.type && logo && <img src={logo} alt='' className='map-logo' />}
1443
+ </section>
1444
+ )}
1445
+ </section>
1446
+
1447
+ {general.showSidebar && 'navigation' !== general.type && (
1448
+ <Sidebar
1449
+ viewport={currentViewport}
1450
+ legend={state.legend}
1451
+ runtimeLegend={runtimeLegend}
1452
+ setRuntimeLegend={setRuntimeLegend}
1453
+ runtimeFilters={runtimeFilters}
1454
+ columns={state.columns}
1455
+ sharing={state.sharing}
1456
+ prefix={state.columns.primary.prefix}
1457
+ suffix={state.columns.primary.suffix}
1458
+ setState={setState}
1459
+ resetLegendToggles={resetLegendToggles}
1460
+ changeFilterActive={changeFilterActive}
1461
+ setAccessibleStatus={setAccessibleStatus}
1462
+ displayDataAsText={displayDataAsText}
1463
+ />
1545
1464
  )}
1546
- </section>
1465
+ </div>
1547
1466
 
1548
- {general.showSidebar && 'navigation' !== general.type && (
1549
- <Sidebar
1550
- viewport={currentViewport}
1551
- legend={state.legend}
1552
- runtimeLegend={runtimeLegend}
1553
- setRuntimeLegend={setRuntimeLegend}
1554
- runtimeFilters={runtimeFilters}
1467
+ {'navigation' === general.type && <NavigationMenu mapTabbingID={tabId} displayGeoName={displayGeoName} data={runtimeData} options={general} columns={state.columns} navigationHandler={val => navigationHandler(val)} />}
1468
+
1469
+ {link && link}
1470
+
1471
+ {subtext.length > 0 && <p className='subtext'>{parse(subtext)}</p>}
1472
+
1473
+ <CoveMediaControls.Section classes={['download-buttons']}>
1474
+ {state.general.showDownloadImgButton && <CoveMediaControls.Button text='Download Image' title='Download Chart as Image' type='image' state={state} elementToCapture={imageId} />}
1475
+ {state.general.showDownloadPdfButton && <CoveMediaControls.Button text='Download PDF' title='Download Chart as PDF' type='pdf' state={state} elementToCapture={imageId} />}
1476
+ </CoveMediaControls.Section>
1477
+
1478
+ {state.runtime.editorErrorMessage.length === 0 && true === dataTable.forceDisplay && general.type !== 'navigation' && false === loading && (
1479
+ <DataTable
1480
+ state={state}
1481
+ rawData={state.data}
1482
+ navigationHandler={navigationHandler}
1483
+ expandDataTable={general.expandDataTable}
1484
+ headerColor={general.headerColor}
1555
1485
  columns={state.columns}
1556
- sharing={state.sharing}
1557
- prefix={state.columns.primary.prefix}
1558
- suffix={state.columns.primary.suffix}
1559
- setState={setState}
1560
- resetLegendToggles={resetLegendToggles}
1561
- changeFilterActive={changeFilterActive}
1562
- setAccessibleStatus={setAccessibleStatus}
1486
+ showDownloadButton={general.showDownloadButton}
1487
+ runtimeLegend={runtimeLegend}
1488
+ runtimeData={runtimeData}
1563
1489
  displayDataAsText={displayDataAsText}
1490
+ displayGeoName={displayGeoName}
1491
+ applyLegendToRow={applyLegendToRow}
1492
+ tableTitle={dataTable.title}
1493
+ indexTitle={dataTable.indexLabel}
1494
+ mapTitle={general.title}
1495
+ viewport={currentViewport}
1496
+ formatLegendLocation={formatLegendLocation}
1497
+ setFilteredCountryCode={setFilteredCountryCode}
1498
+ tabbingId={tabId}
1499
+ showDownloadImgButton={state.general.showDownloadImgButton}
1500
+ showDownloadPdfButton={state.general.showDownloadPdfButton}
1501
+ innerContainerRef={innerContainerRef}
1502
+ outerContainerRef={outerContainerRef}
1503
+ imageRef={imageId}
1564
1504
  />
1565
1505
  )}
1506
+
1507
+ {general.footnotes && <section className='footnotes'>{parse(general.footnotes)}</section>}
1566
1508
  </section>
1509
+ )}
1567
1510
 
1568
- {'navigation' === general.type && <NavigationMenu mapTabbingID={tabId} displayGeoName={displayGeoName} data={runtimeData} options={general} columns={state.columns} navigationHandler={val => navigationHandler(val)} />}
1569
-
1570
- {link && link}
1571
-
1572
- {subtext.length > 0 && <p className='subtext'>{parse(subtext)}</p>}
1573
-
1574
- {state.runtime.editorErrorMessage.length === 0 && true === dataTable.forceDisplay && general.type !== 'navigation' && false === loading && (
1575
- <DataTable
1576
- state={state}
1577
- rawData={state.data}
1578
- navigationHandler={navigationHandler}
1579
- expandDataTable={general.expandDataTable}
1580
- headerColor={general.headerColor}
1581
- columns={state.columns}
1582
- showDownloadButton={general.showDownloadButton}
1583
- runtimeLegend={runtimeLegend}
1584
- runtimeData={runtimeData}
1585
- displayDataAsText={displayDataAsText}
1586
- displayGeoName={displayGeoName}
1587
- applyLegendToRow={applyLegendToRow}
1588
- tableTitle={dataTable.title}
1589
- indexTitle={dataTable.indexLabel}
1590
- mapTitle={general.title}
1591
- viewport={currentViewport}
1592
- formatLegendLocation={formatLegendLocation}
1593
- setFilteredCountryCode={setFilteredCountryCode}
1594
- tabbingId={tabId}
1595
- />
1596
- )}
1597
-
1598
- {general.footnotes && <section className='footnotes'>{parse(general.footnotes)}</section>}
1599
- </section>
1600
- )}
1601
-
1602
- <div aria-live='assertive' className='cdcdataviz-sr-only'>
1603
- {accessibleStatus}
1511
+ <div aria-live='assertive' className='cdcdataviz-sr-only'>
1512
+ {accessibleStatus}
1513
+ </div>
1604
1514
  </div>
1605
- </div>
1515
+ </Context.Provider>
1606
1516
  )
1607
1517
  }
1608
1518
 
1609
- export default memo(CdcMap)
1519
+ export default CdcMap
@@ -16,7 +16,6 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
16
16
  if (!sortedRuntimeData) return
17
17
 
18
18
  const clickTolerance = 10
19
-
20
19
  // Set bubble sizes
21
20
  var size = scaleLinear().domain([hasBubblesWithZeroOnMap, maxDataValue]).range([state.visual.minBubbleSize, state.visual.maxBubbleSize])
22
21
 
@@ -34,7 +33,7 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
34
33
  const legendColors = applyLegendToRow(country)
35
34
 
36
35
  let primaryKey = state.columns.primary.name
37
- if ((Math.floor(Number(size(country[primaryKey]))) === 0 || country[primaryKey] === '') && !state.visual.showBubbleZeros) return
36
+ if ((Math.floor(Number(country[primaryKey])) === 0 || country[primaryKey] === '') && !state.visual.showBubbleZeros) return
38
37
 
39
38
  let transform = `translate(${projection([coordinates[1], coordinates[0]])})`
40
39
 
@@ -117,7 +116,7 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
117
116
  if (item[primaryKey] === null) item[primaryKey] = ''
118
117
 
119
118
  // Return if hiding zeros on the map
120
- if ((Math.floor(Number(size(item[primaryKey]))) === 0 || item[primaryKey] === '') && !state.visual.showBubbleZeros) return
119
+ if ((Math.floor(Number(item[primaryKey])) === 0 || item[primaryKey] === '') && !state.visual.showBubbleZeros) return
121
120
 
122
121
  if (!stateData) return true
123
122
  let longitude = Number(stateData.Longitude)
@@ -42,7 +42,6 @@ const CityList = ({ data, state, geoClickHandler, applyTooltipsToGeo, displayGeo
42
42
  // Cities output
43
43
  const cities = cityList.map((city, i) => {
44
44
  const geoData = isGeoCodeMap ? state.data.filter(item => city === item[state.columns.geo.name])[0] : data[city]
45
-
46
45
  const cityDisplayName = isGeoCodeMap ? city : titleCase(displayGeoName(city))
47
46
 
48
47
  const legendColors = isGeoCodeMap && geoData ? applyLegendToRow(geoData) : data[city] ? applyLegendToRow(data[city]) : false
@@ -72,7 +71,7 @@ const CityList = ({ data, state, geoClickHandler, applyTooltipsToGeo, displayGeo
72
71
  styles.cursor = 'pointer'
73
72
  }
74
73
 
75
- const radius = state.general.geoType === 'us' && !isGeoCodeMap ? 8 : isGeoCodeMap ? 2 : 4
74
+ const radius = state.general.geoType === 'us' && !isGeoCodeMap ? 8 : isGeoCodeMap ? state.visual.geoCodeCircleSize : 4
76
75
 
77
76
  const additionalProps = {
78
77
  fillOpacity: state.general.type === 'bubble' ? 0.4 : 1
@@ -5,11 +5,36 @@ import ExternalIcon from '../images/external-link.svg' // TODO: Move to Icon com
5
5
 
6
6
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
7
7
  import LegendCircle from '@cdc/core/components/LegendCircle'
8
+ import CoveMediaControls from '@cdc/core/helpers/CoveMediaControls'
8
9
 
9
10
  import Loading from '@cdc/core/components/Loading'
10
11
 
11
12
  const DataTable = props => {
12
- const { state, tableTitle, indexTitle, mapTitle, rawData, showDownloadButton, runtimeData, runtimeLegend, headerColor, expandDataTable, columns, displayDataAsText, applyLegendToRow, displayGeoName, navigationHandler, viewport, formatLegendLocation, tabbingId, setFilteredCountryCode } = props
13
+ const {
14
+ state,
15
+ tableTitle,
16
+ indexTitle,
17
+ mapTitle,
18
+ rawData,
19
+ showDownloadImgButton,
20
+ showDownloadPdfButton,
21
+ showDownloadButton,
22
+ runtimeData,
23
+ runtimeLegend,
24
+ headerColor,
25
+ expandDataTable,
26
+ columns,
27
+ displayDataAsText,
28
+ applyLegendToRow,
29
+ displayGeoName,
30
+ navigationHandler,
31
+ viewport,
32
+ formatLegendLocation,
33
+ tabbingId,
34
+ setFilteredCountryCode,
35
+ innerContainerRef,
36
+ imageRef
37
+ } = props
13
38
 
14
39
  const [expanded, setExpanded] = useState(expandDataTable)
15
40
 
@@ -137,7 +162,7 @@ const DataTable = props => {
137
162
  }
138
163
 
139
164
  return (
140
- <a download={fileName} type='button' onClick={saveBlob} href={URL.createObjectURL(blob)} aria-label='Download this data in a CSV file format.' className={`${headerColor} btn btn-download no-border`} id={`${skipId}`} data-html2canvas-ignore role='button'>
165
+ <a download={fileName} type='button' onClick={saveBlob} href={URL.createObjectURL(blob)} aria-label='Download this data in a CSV file format.' className={`${headerColor} no-border`} id={`${skipId}`} data-html2canvas-ignore role='button'>
141
166
  Download Data (CSV)
142
167
  </a>
143
168
  )
@@ -255,6 +280,10 @@ const DataTable = props => {
255
280
  if (!state.data) return <Loading />
256
281
  return (
257
282
  <ErrorBoundary component='DataTable'>
283
+ <CoveMediaControls.Section classes={['download-links']}>
284
+ <CoveMediaControls.Link config={state} />
285
+ {state.general.showDownloadButton && <DownloadButton />}
286
+ </CoveMediaControls.Section>
258
287
  <section id={tabbingId.replace('#', '')} className={`data-table-container ${viewport}`} aria-label={accessibilityLabel}>
259
288
  <a id='skip-nav' className='cdcdataviz-sr-only-focusable' href={`#${skipId}`}>
260
289
  Skip Navigation or Skip to Content
@@ -323,7 +352,6 @@ const DataTable = props => {
323
352
  </tbody>
324
353
  </table>
325
354
  </div>
326
- {showDownloadButton === true && <DownloadButton />}
327
355
  </section>
328
356
  </ErrorBoundary>
329
357
  )