@cdc/map 4.25.8 → 4.25.11

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 (137) hide show
  1. package/.claude/agents/typescript-organizer.md +118 -0
  2. package/.claude/settings.local.json +30 -0
  3. package/dist/{cdcmap-fce76882.es.js → cdcmap-BnB1QM5d.es.js} +6 -13
  4. package/dist/{cdcmap-c55ac1ea.es.js → cdcmap-D6CG2-Hb.es.js} +5 -12
  5. package/dist/{cdcmap-31a33da1.es.js → cdcmap-MXgURbdZ.es.js} +6 -13
  6. package/dist/{cdcmap-1a1724a1.es.js → cdcmap-dgT_1dIT.es.js} +136 -151
  7. package/dist/cdcmap.js +56991 -53706
  8. package/examples/example-city-state.json +9 -1
  9. package/examples/multi-country-centering.json +45 -0
  10. package/examples/private/c.json +290 -0
  11. package/examples/private/canvas-city-hover.json +787 -0
  12. package/examples/private/colors-2.json +221 -0
  13. package/examples/private/colors.json +221 -0
  14. package/examples/private/d.json +345 -0
  15. package/examples/private/g.json +1 -0
  16. package/examples/private/h.json +105911 -0
  17. package/examples/private/measles-data.json +378 -0
  18. package/examples/private/measles.json +211 -0
  19. package/examples/private/north-dakota.json +1132 -0
  20. package/examples/private/state-with-pattern.json +883 -0
  21. package/index.html +36 -34
  22. package/package.json +26 -5
  23. package/src/CdcMap.tsx +23 -8
  24. package/src/CdcMapComponent.tsx +238 -308
  25. package/src/_stories/CdcMap.ColumnWrap.stories.tsx +31 -0
  26. package/src/_stories/CdcMap.DistrictOfColumbia.stories.tsx +320 -0
  27. package/src/_stories/CdcMap.Editor.stories.tsx +3371 -0
  28. package/src/_stories/CdcMap.Filters.stories.tsx +2 -2
  29. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +3 -3
  30. package/src/_stories/CdcMap.Legend.stories.tsx +7 -4
  31. package/src/_stories/CdcMap.Patterns.stories.tsx +2 -2
  32. package/src/_stories/CdcMap.SmallMultiples.stories.tsx +35 -0
  33. package/src/_stories/CdcMap.Table.stories.tsx +2 -2
  34. package/src/_stories/CdcMap.stories.tsx +37 -9
  35. package/src/_stories/GoogleMap.stories.tsx +2 -2
  36. package/src/_stories/UsaMap.NoData.stories.tsx +2 -2
  37. package/src/_stories/_mock/column-wrap-test.json +265 -0
  38. package/src/_stories/_mock/equal-number.json +1109 -0
  39. package/src/_stories/_mock/multi-country-hide.json +78 -0
  40. package/src/_stories/_mock/multi-country.json +95 -0
  41. package/src/_stories/_mock/multi-state.json +887 -20403
  42. package/src/_stories/_mock/small_multiples/multi-state-small-multiples.json +8399 -0
  43. package/src/_stories/_mock/small_multiples/region-small-multiples.json +657 -0
  44. package/src/_stories/_mock/small_multiples/wastewater-map-small-multiples.json +221 -0
  45. package/src/_stories/_mock/us-bubble-cities.json +306 -0
  46. package/src/_stories/_mock/usa-state-gradient.json +2 -4
  47. package/src/components/BubbleList.tsx +17 -13
  48. package/src/components/CityList.tsx +85 -107
  49. package/src/components/EditorPanel/components/EditorPanel.tsx +787 -709
  50. package/src/components/EditorPanel/components/HexShapeSettings.tsx +58 -95
  51. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +34 -42
  52. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +354 -0
  53. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  54. package/src/components/Geo.tsx +22 -3
  55. package/src/components/Legend/components/Legend.tsx +76 -40
  56. package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +10 -7
  57. package/src/components/Legend/components/index.scss +1 -1
  58. package/src/components/MapContainer.tsx +52 -0
  59. package/src/components/MapControls.tsx +44 -0
  60. package/src/components/NavigationMenu.tsx +27 -15
  61. package/src/components/SmallMultiples/SmallMultipleTile.tsx +163 -0
  62. package/src/components/SmallMultiples/SmallMultiples.css +32 -0
  63. package/src/components/SmallMultiples/SmallMultiples.tsx +150 -0
  64. package/src/components/SmallMultiples/SynchronizedTooltip.tsx +105 -0
  65. package/src/components/SmallMultiples/index.tsx +3 -0
  66. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +36 -4
  67. package/src/components/UsaMap/components/TerritoriesSection.tsx +26 -12
  68. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +30 -4
  69. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +23 -4
  70. package/src/components/UsaMap/components/Territory/TerritoryShape.ts +6 -0
  71. package/src/components/UsaMap/components/UsaMap.County.tsx +123 -37
  72. package/src/components/UsaMap/components/UsaMap.Region.tsx +36 -5
  73. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +30 -10
  74. package/src/components/UsaMap/components/UsaMap.State.tsx +53 -12
  75. package/src/components/UsaMap/helpers/map.ts +4 -4
  76. package/src/components/UsaMap/helpers/shapes.ts +9 -6
  77. package/src/components/WorldMap/WorldMap.tsx +193 -35
  78. package/src/components/ZoomControls.tsx +6 -9
  79. package/src/context/LegendMemoContext.tsx +30 -0
  80. package/src/context.ts +1 -40
  81. package/src/data/initial-state.js +153 -130
  82. package/src/data/supported-geos.js +25 -78
  83. package/src/helpers/addUIDs.ts +13 -2
  84. package/src/helpers/applyColorToLegend.ts +140 -20
  85. package/src/helpers/applyLegendToRow.ts +10 -6
  86. package/src/helpers/componentHelpers.ts +8 -0
  87. package/src/helpers/constants.ts +12 -14
  88. package/src/helpers/dataTableHelpers.ts +6 -0
  89. package/src/helpers/displayGeoName.ts +18 -3
  90. package/src/helpers/generateRuntimeLegend.ts +44 -10
  91. package/src/helpers/generateRuntimeLegendHash.ts +4 -2
  92. package/src/helpers/getColumnNames.ts +1 -1
  93. package/src/helpers/getCountriesPicked.ts +103 -0
  94. package/src/helpers/getMapContainerClasses.ts +7 -0
  95. package/src/helpers/getPatternForRow.ts +33 -0
  96. package/src/helpers/getStatesPicked.ts +8 -5
  97. package/src/helpers/index.ts +3 -3
  98. package/src/helpers/isLegendItemDisabled.ts +16 -0
  99. package/src/helpers/mapObserverHelpers.ts +40 -0
  100. package/src/helpers/resetLegendToggles.ts +3 -2
  101. package/src/helpers/smallMultiplesHelpers.ts +359 -0
  102. package/src/helpers/tests/titleCase.test.ts +76 -0
  103. package/src/helpers/titleCase.ts +13 -13
  104. package/src/helpers/toggleLegendActive.ts +6 -11
  105. package/src/helpers/urlDataHelpers.ts +70 -0
  106. package/src/hooks/useCountryZoom.tsx +241 -0
  107. package/src/hooks/useGeoClickHandler.ts +36 -2
  108. package/src/hooks/useLegendMemo.ts +17 -0
  109. package/src/hooks/useMapLayers.tsx +5 -4
  110. package/src/hooks/useProgrammaticMapTooltip.ts +110 -0
  111. package/src/hooks/useResizeObserver.ts +5 -2
  112. package/src/hooks/useStateZoom.tsx +30 -8
  113. package/src/hooks/useSynchronizedGeographies.ts +56 -0
  114. package/src/hooks/useTooltip.ts +1 -2
  115. package/src/index.jsx +1 -2
  116. package/src/scss/editor-panel.scss +4 -440
  117. package/src/scss/main.scss +1 -1
  118. package/src/scss/map.scss +12 -15
  119. package/src/store/map.actions.ts +7 -7
  120. package/src/store/map.reducer.ts +17 -6
  121. package/src/test/CdcMap.test.jsx +11 -0
  122. package/src/types/MapConfig.ts +46 -18
  123. package/src/types/MapContext.ts +6 -7
  124. package/src/types/runtimeLegend.ts +17 -1
  125. package/vite.config.js +2 -7
  126. package/vitest.config.ts +16 -0
  127. package/src/components/DataTable.tsx +0 -385
  128. package/src/components/EditorPanel/components/Inputs.tsx +0 -59
  129. package/src/coreStyles_map.scss +0 -3
  130. package/src/helpers/colorDistributions.ts +0 -12
  131. package/src/helpers/generateColorsArray.ts +0 -14
  132. package/src/helpers/tests/generateColorsArray.test.ts +0 -18
  133. package/src/helpers/tests/generateRuntimeLegendHash.test.ts +0 -11
  134. package/src/hooks/useActiveElement.ts +0 -19
  135. package/src/scss/mixins.scss +0 -47
  136. package/src/types/Annotations.ts +0 -24
  137. /package/dist/{cdcmap-548642e6.es.js → cdcmap-Ct2SB0vL.es.js} +0 -0
@@ -3,8 +3,13 @@ import { type Visualization } from '@cdc/core/types/Visualization'
3
3
  import { type EditorColumnProperties } from '@cdc/core/types/EditorColumnProperties'
4
4
  import { type Version } from '@cdc/core/types/Version'
5
5
  import { type VizFilter } from '@cdc/core/types/VizFilter'
6
+ import { type Annotation } from '@cdc/core/types/Annotation'
7
+ import { MarkupConfig } from '@cdc/core/types/MarkupVariable'
6
8
 
7
- export type MapVisualSettings = {
9
+ // Runtime data types
10
+ export type RuntimeFilters = VizFilter[] & { fromHash?: number }
11
+
12
+ type MapVisualSettings = {
8
13
  /** minBubbleSize - Minimum Circle Size when the map has a type of bubble */
9
14
  minBubbleSize: number
10
15
  /** maxBubbleSize - Maximum Circle Size when the map has a type of bubble */
@@ -38,16 +43,21 @@ export type PatternSelection = {
38
43
  contrastCheck: boolean
39
44
  }
40
45
 
41
- export type GeoColumnProperties = Pick<EditorColumnProperties, 'name' | 'label' | 'tooltip' | 'dataTable'>
42
- export type LatitudeColumnProperties = Pick<EditorColumnProperties, 'name'>
43
- export type LongitudeColumnProperties = Pick<EditorColumnProperties, 'name'>
44
- export type NavigateColumnProperties = Pick<EditorColumnProperties, 'name'>
45
- export type PrimaryColumnProperties = Pick<
46
- EditorColumnProperties,
47
- 'dataTable' | 'label' | 'name' | 'prefix' | 'suffix' | 'tooltip'
48
- >
46
+ // Base column properties with name required, all others optional
47
+ type BaseColumnProperties = Pick<EditorColumnProperties, 'name'> &
48
+ Partial<Pick<EditorColumnProperties, 'label' | 'tooltip' | 'dataTable' | 'prefix' | 'suffix'>>
49
+
50
+ // Simple column type for name-only columns
51
+ type SimpleColumnProperties = Pick<EditorColumnProperties, 'name'>
49
52
 
50
- export type LegendShapeItem = {
53
+ // Specific column types for better semantics
54
+ type GeoColumnProperties = BaseColumnProperties
55
+ type LatitudeColumnProperties = SimpleColumnProperties
56
+ type LongitudeColumnProperties = SimpleColumnProperties
57
+ type NavigateColumnProperties = SimpleColumnProperties
58
+ type PrimaryColumnProperties = BaseColumnProperties
59
+
60
+ type LegendShapeItem = {
51
61
  column: string
52
62
  key: string
53
63
  operator: '=' | '≠' | '<' | '>' | '<=' | '>='
@@ -55,13 +65,13 @@ export type LegendShapeItem = {
55
65
  value: string
56
66
  }
57
67
 
58
- export type LegendGrouping = {
68
+ type LegendGrouping = {
59
69
  legendTitle: string
60
70
  legendDescription: string
61
71
  items: LegendShapeItem[]
62
72
  }
63
73
 
64
- export type HexMapSettings = {
74
+ type HexMapSettings = {
65
75
  type: 'shapes' | 'standard'
66
76
  shapeGroups: LegendGrouping[]
67
77
  }
@@ -70,15 +80,24 @@ export type Coordinate = [number, number]
70
80
 
71
81
  export type DataRow = {
72
82
  uid?: string // optional 'uid' property
73
- [key: string]: any // allowing any additional properties with a dynamic key (e.g., for `configPrimaryName`)
83
+ [key: string]: string | number | boolean | null | undefined // allowing primitive data types for dynamic columns
84
+ }
85
+
86
+ export type SmallMultiples = {
87
+ mode?: 'by-column'
88
+ tileColumn?: string
89
+ tilesPerRowDesktop?: number
90
+ tilesPerRowMobile?: number
91
+ tileOrderType?: 'asc' | 'desc' | 'custom'
92
+ tileOrder?: string[]
93
+ tileTitles?: { [key: string]: string }
94
+ synchronizedTooltips?: boolean
74
95
  }
75
96
 
76
97
  export type MapConfig = Visualization & {
77
98
  annotations: Annotation[]
78
99
  // map color palette
79
100
  color: string
80
- // custom color palette
81
- customColors: string[]
82
101
  columns: {
83
102
  geo: GeoColumnProperties
84
103
  primary: PrimaryColumnProperties
@@ -93,6 +112,7 @@ export type MapConfig = Visualization & {
93
112
  filters: VizFilter[]
94
113
  general: {
95
114
  navigationTarget: '_self' | '_blank'
115
+ noDataMessage: string // single-state no data message
96
116
  subtext: string
97
117
  introText: string
98
118
  allowMapZoom: boolean
@@ -121,6 +141,10 @@ export type MapConfig = Visualization & {
121
141
  language: string
122
142
  palette: {
123
143
  isReversed: boolean
144
+ name: string
145
+ version: string
146
+ customColors?: string[]
147
+ customColorsOrdered?: string[]
124
148
  }
125
149
  showDownloadMediaButton: boolean
126
150
  showDownloadImgButton: boolean
@@ -131,6 +155,11 @@ export type MapConfig = Visualization & {
131
155
  fipsCode: string
132
156
  stateName: string
133
157
  }[]
158
+ countriesPicked?: {
159
+ iso: string
160
+ name: string
161
+ }[]
162
+ hideUnselectedCountries?: boolean // When true, hide unselected countries; when false (default), gray them out
134
163
  territoriesAlwaysShow: boolean
135
164
  territoriesLabel: string
136
165
  title: string
@@ -180,8 +209,6 @@ export type MapConfig = Visualization & {
180
209
  }
181
210
  runtime: {
182
211
  editorErrorMessage: string[]
183
- // when a single state map doesn't include a fips code show a message...
184
- noStateFoundMessage: string
185
212
  }
186
213
  mapPosition: { coordinates: Coordinate; zoom: number }
187
214
  map: {
@@ -192,8 +219,9 @@ export type MapConfig = Visualization & {
192
219
  filterBehavior: string
193
220
  filterIntro: string
194
221
  visual: MapVisualSettings
222
+ smallMultiples?: SmallMultiples
195
223
  // visualization type
196
224
  type: 'map'
197
225
  // version of the map
198
226
  version: Version
199
- }
227
+ } & MarkupConfig
@@ -2,12 +2,13 @@ import { DataRow, type MapConfig } from './MapConfig'
2
2
  import { type ViewPort } from '@cdc/core/types/ViewPort'
3
3
  import { DimensionsType } from '@cdc/core/types/Dimensions'
4
4
  import { VizFilter } from '@cdc/core/types/VizFilter'
5
- import { type RefObject } from 'react'
5
+ import { MapRefInterface } from '../hooks/useProgrammaticMapTooltip'
6
+ import { MutableRefObject } from 'react'
6
7
 
7
8
  export type MapContext = {
8
9
  currentViewport: ViewPort
10
+ vizViewport?: ViewPort
9
11
  content: { geoName: string; keyedData: Record<string, any> }
10
- data: DataRow[]
11
12
  dimensions: DimensionsType
12
13
  displayDataAsText: string | number
13
14
  displayGeoName: (key: string, convertFipsCodes: boolean) => string
@@ -22,23 +23,19 @@ export type MapContext = {
22
23
  handleCircleClick: Function
23
24
  handleDragStateChange: Function
24
25
  isDraggingAnnotation: boolean
25
- innerContainerRef: RefObject<HTMLDivElement>
26
26
  isDashboard: boolean
27
27
  isEditor: boolean
28
28
  isFilterValueSupported: boolean
29
+ useDynamicViewbox?: boolean
29
30
  loadConfig: (configObj: MapConfig) => void
30
31
  logo: string
31
32
  mapId: string
32
33
  position: 'side' | 'top' | 'bottom'
33
34
  resetLegendToggles: Function
34
35
  runtimeFilters: Function
35
- legendMemo: Function
36
- legendSpecialClassLastMemo: Function
37
36
  runtimeLegend
38
37
  setParentConfig: Function
39
38
  setRuntimeData: Function
40
- setRuntimeFilters: Function
41
- setRuntimeLegend: Function
42
39
  setSharedFilterValue: Function
43
40
  setConfig: (newState: MapConfig) => MapConfig
44
41
  config: MapConfig
@@ -50,4 +47,6 @@ export type MapContext = {
50
47
  runtimeData: Object[]
51
48
  tooltipId: string
52
49
  interactionLabel?: string
50
+ handleSmallMultipleHover?: (geoId: string | null, yCoordinate?: number) => void
51
+ mapRefForSync?: MutableRefObject<MapRefInterface | null>
53
52
  }
@@ -1 +1,17 @@
1
- export type RuntimeLegend = { disabled; bin; color; special }[]
1
+ type RuntimeLegendItem = {
2
+ disabled?: boolean
3
+ bin?: number
4
+ color?: string
5
+ special?: boolean
6
+ value?: string | number
7
+ label?: string
8
+ min?: number
9
+ max?: number
10
+ }
11
+
12
+ export type RuntimeLegend = {
13
+ items: RuntimeLegendItem[]
14
+ disabledAmt?: number
15
+ fromHash?: number
16
+ runtimeDataHash?: number
17
+ }
package/vite.config.js CHANGED
@@ -1,9 +1,4 @@
1
- import GenerateViteConfig from '../../generateViteConfig.js'
1
+ import GenerateViteConfig from '@cdc/core/generateViteConfig.js'
2
2
  import { moduleName } from './package.json'
3
3
 
4
- export default GenerateViteConfig(moduleName, null, {
5
- jsxImportSource: '@emotion/react',
6
- babel: {
7
- plugins: ['@emotion/babel-plugin']
8
- }
9
- })
4
+ export default GenerateViteConfig(moduleName)
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'jsdom',
6
+ globals: true,
7
+ setupFiles: ['../../vitest.setup.ts'],
8
+ exclude: [
9
+ '**/node_modules/**',
10
+ '**/dist/**',
11
+ '**/.storybook/**',
12
+ '**/*.stories.*',
13
+ '**/storybook-static/**'
14
+ ]
15
+ }
16
+ })
@@ -1,385 +0,0 @@
1
- import React, { useEffect, useState, memo, useContext } from 'react'
2
-
3
- import Papa from 'papaparse'
4
- import ExternalIcon from '../images/external-link.svg' // TODO: Move to Icon component
5
- import Icon from '@cdc/core/components/ui/Icon'
6
-
7
- import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
8
- import LegendShape from '@cdc/core/components/LegendShape'
9
- import MediaControls from '@cdc/core/components/MediaControls'
10
- import SkipTo from '@cdc/core/components/elements/SkipTo'
11
-
12
- import Loading from '@cdc/core/components/Loading'
13
- import { navigationHandler } from '../helpers'
14
- import ConfigContext, { MapDispatchContext } from '../context'
15
- import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
16
-
17
- const DataTable = props => {
18
- const {
19
- state,
20
- tableTitle,
21
- indexTitle,
22
- mapTitle,
23
- rawData,
24
- runtimeData,
25
- headerColor,
26
- expandDataTable,
27
- columns,
28
- displayDataAsText,
29
- applyLegendToRow,
30
- displayGeoName,
31
- formatLegendLocation,
32
- tabbingId,
33
- interactionLabel
34
- } = props
35
-
36
- const dispatch = useContext(MapDispatchContext)
37
- const { currentViewport: viewport } = useContext(ConfigContext)
38
- const [expanded, setExpanded] = useState(expandDataTable)
39
- const [sortBy, setSortBy] = useState({ column: 'geo', asc: false })
40
- const [accessibilityLabel, setAccessibilityLabel] = useState('')
41
- const fileName = `${mapTitle || 'data-table'}.csv`
42
-
43
- // Catch all sorting method used on load by default but also on user click
44
- // Having a custom method means we can add in any business logic we want going forward
45
- const customSort = (a, b) => {
46
- const digitRegex = /\d+/
47
-
48
- const hasNumber = value => digitRegex.test(value)
49
-
50
- // force null and undefined to the bottom
51
- a = a === null || a === undefined ? '' : a
52
- b = b === null || b === undefined ? '' : b
53
-
54
- // convert any strings that are actually numbers to proper data type
55
- const aNum = Number(a)
56
-
57
- if (!Number.isNaN(aNum)) {
58
- a = aNum
59
- }
60
-
61
- const bNum = Number(b)
62
-
63
- if (!Number.isNaN(bNum)) {
64
- b = bNum
65
- }
66
-
67
- // remove iso code prefixes
68
- if (typeof a === 'string') {
69
- a = a.replace('us-', '')
70
- a = displayGeoName(a)
71
- }
72
-
73
- if (typeof b === 'string') {
74
- b = b.replace('us-', '')
75
- b = displayGeoName(b)
76
- }
77
-
78
- // force any string values to lowercase
79
- a = typeof a === 'string' ? a.toLowerCase() : a
80
- b = typeof b === 'string' ? b.toLowerCase() : b
81
-
82
- // If the string contains a number, remove the text from the value and only sort by the number. Only uses the first number it finds.
83
- if (typeof a === 'string' && hasNumber(a) === true) {
84
- a = a.match(digitRegex)[0]
85
-
86
- a = Number(a)
87
- }
88
-
89
- if (typeof b === 'string' && hasNumber(b) === true) {
90
- b = b.match(digitRegex)[0]
91
-
92
- b = Number(b)
93
- }
94
-
95
- // When comparing a number to a string, always send string to bottom
96
- if (typeof a === 'number' && typeof b === 'string') {
97
- return 1
98
- }
99
-
100
- if (typeof b === 'number' && typeof a === 'string') {
101
- return -1
102
- }
103
-
104
- // Return either 1 or -1 to indicate a sort priority
105
- if (a > b) {
106
- return 1
107
- }
108
- if (a < b) {
109
- return -1
110
- }
111
- // returning 0, undefined or any falsey value will use subsequent sorts or
112
- // the index as a tiebreaker
113
- return 0
114
- }
115
-
116
- // Optionally wrap cell with anchor if config defines a navigation url
117
- const getCellAnchor = (markup, row) => {
118
- if (columns.navigate && row[columns.navigate.name]) {
119
- markup = (
120
- <span
121
- onClick={() => navigationHandler(state.general.navigationTarget, row[columns.navigate.name])}
122
- className='table-link'
123
- title='Click for more information (Opens in a new window)'
124
- role='link'
125
- tabIndex='0'
126
- onKeyDown={e => {
127
- if (e.keyCode === 13) {
128
- navigationHandler(state.general.navigationTarget, row[columns.navigate.name])
129
- }
130
- }}
131
- >
132
- {markup}
133
- <ExternalIcon className='inline-icon' />
134
- </span>
135
- )
136
- }
137
-
138
- return markup
139
- }
140
-
141
- const rand = Math.random().toString(16).substr(2, 8)
142
- const skipId = `btn__${rand}`
143
-
144
- const mapLookup = {
145
- 'us-county': 'United States County Map',
146
- 'single-state': 'State Map',
147
- us: 'United States Map',
148
- world: 'World Map'
149
- }
150
-
151
- const DownloadButton = memo(() => {
152
- let csvData
153
- if (state.general.type === 'bubble' || !state.table.showFullGeoNameInCSV) {
154
- // Just Unparse
155
- csvData = Papa.unparse(rawData)
156
- } else if (state.general.geoType !== 'us-county' || state.general.type === 'us-geocode') {
157
- // Unparse + Add column for full Geo name
158
- csvData = Papa.unparse(rawData.map(row => ({ FullGeoName: displayGeoName(row[state.columns.geo.name]), ...row })))
159
- } else {
160
- // Unparse + Add column for full Geo name
161
- csvData = Papa.unparse(
162
- rawData.map(row => ({ FullGeoName: formatLegendLocation(row[state.columns.geo.name]), ...row }))
163
- )
164
- }
165
-
166
- const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' })
167
-
168
- const saveBlob = () => {
169
- //@ts-ignore
170
- if (typeof window.navigator.msSaveBlob === 'function') {
171
- //@ts-ignore
172
- navigator.msSaveBlob(blob, fileName)
173
- }
174
- }
175
-
176
- return (
177
- <a
178
- download={fileName}
179
- type='button'
180
- onClick={() => {
181
- saveBlob
182
- publishAnalyticsEvent('data_downloaded', 'click', interactionLabel)
183
- }}
184
- href={URL.createObjectURL(blob)}
185
- aria-label='Download this data in a CSV file format.'
186
- className={`${headerColor} no-border`}
187
- id={`${skipId}`}
188
- data-html2canvas-ignore={true}
189
- role='button'
190
- >
191
- Download Data (CSV)
192
- </a>
193
- )
194
- }, [rawData, state.table])
195
-
196
- const TableMediaControls = ({ belowTable }) => {
197
- return (
198
- <MediaControls.Section classes={['download-links']}>
199
- <MediaControls.Link config={state} interactionLabel={interactionLabel} />
200
- {state.table.download && <DownloadButton />}
201
- </MediaControls.Section>
202
- )
203
- }
204
-
205
- // Change accessibility label depending on expanded status
206
- useEffect(() => {
207
- const expandedLabel = 'Accessible data table.'
208
- const collapsedLabel =
209
- 'Accessible data table. This table is currently collapsed visually but can still be read using a screen reader.'
210
-
211
- if (expanded === true && accessibilityLabel !== expandedLabel) {
212
- setAccessibilityLabel(expandedLabel)
213
- }
214
-
215
- if (expanded === false && accessibilityLabel !== collapsedLabel) {
216
- setAccessibilityLabel(collapsedLabel)
217
- }
218
- // eslint-disable-next-line react-hooks/exhaustive-deps
219
- }, [expanded])
220
-
221
- if (!state.data) return <Loading />
222
-
223
- const rows = Object.keys(runtimeData)
224
- .filter(row => applyLegendToRow(runtimeData[row], state))
225
- .sort((a, b) => {
226
- const sortVal = customSort(
227
- runtimeData[a][state.columns[sortBy.column].name],
228
- runtimeData[b][state.columns[sortBy.column].name]
229
- )
230
- if (!sortBy.asc) return sortVal
231
- if (sortVal === 0) return 0
232
- if (sortVal < 0) return 1
233
- return -1
234
- })
235
-
236
- return (
237
- <ErrorBoundary component='DataTable'>
238
- {!state.table.showDownloadLinkBelow && <TableMediaControls />}
239
- <section
240
- id={tabbingId.replace('#', '')}
241
- className={`data-table-container ${viewport}`}
242
- aria-label={accessibilityLabel}
243
- >
244
- <SkipTo skipId={skipId} skipMessage='Skip Data Table' />
245
- <div
246
- className={expanded ? 'data-table-heading' : 'collapsed data-table-heading'}
247
- onClick={() => {
248
- setExpanded(!expanded)
249
- }}
250
- tabIndex='0'
251
- onKeyDown={e => {
252
- if (e.keyCode === 13) {
253
- setExpanded(!expanded)
254
- }
255
- }}
256
- >
257
- <Icon display={expanded ? 'minus' : 'plus'} base />
258
- {tableTitle}
259
- </div>
260
- <div
261
- className='table-container'
262
- style={{ maxHeight: state.dataTable.limitHeight && `${state.dataTable.height}px`, overflowY: 'scroll' }}
263
- >
264
- <table
265
- height={expanded ? null : 0}
266
- role='table'
267
- aria-live='assertive'
268
- className={expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}
269
- hidden={!expanded}
270
- aria-rowcount={state?.data.length ? state.data.length : '-1'}
271
- >
272
- <caption className='cdcdataviz-sr-only'>
273
- {state.dataTable.caption
274
- ? state.dataTable.caption
275
- : `Datatable showing data for the ${mapLookup[state.general.geoType]} figure.`}
276
- </caption>
277
- <thead style={{ position: 'sticky', top: 0, zIndex: 999 }}>
278
- <tr>
279
- {Object.keys(columns)
280
- .filter(column => columns[column].dataTable === true && columns[column].name)
281
- .map(column => {
282
- let text
283
- if (column !== 'geo') {
284
- text = columns[column].label ? columns[column].label : columns[column].name
285
- } else {
286
- text = indexTitle || 'Location'
287
- }
288
-
289
- return (
290
- <th
291
- key={`col-header-${column}`}
292
- tabIndex={0}
293
- title={text}
294
- role='columnheader'
295
- scope='col'
296
- onClick={() => {
297
- setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false })
298
- }}
299
- onKeyDown={e => {
300
- if (e.keyCode === 13) {
301
- setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false })
302
- }
303
- }}
304
- className={
305
- sortBy.column === column ? (sortBy.asc ? 'sort sort-asc' : 'sort sort-desc') : 'sort'
306
- }
307
- {...(sortBy.column === column
308
- ? sortBy.asc
309
- ? { 'aria-sort': 'ascending' }
310
- : { 'aria-sort': 'descending' }
311
- : null)}
312
- >
313
- {text}
314
- <span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${
315
- sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'
316
- } order`}</span>
317
- </th>
318
- )
319
- })}
320
- </tr>
321
- </thead>
322
- <tbody>
323
- {rows.map(row => {
324
- return (
325
- <tr role='row'>
326
- {Object.keys(columns)
327
- .filter(column => columns[column].dataTable === true && columns[column].name)
328
- .map(column => {
329
- let cellValue
330
-
331
- if (column === 'geo') {
332
- const rowObj = runtimeData[row]
333
- const legendColor = applyLegendToRow(rowObj, state)
334
-
335
- var labelValue
336
- if (state.general.geoType !== 'us-county' || state.general.type === 'us-geocode') {
337
- labelValue = displayGeoName(row)
338
- } else {
339
- labelValue = formatLegendLocation(row)
340
- }
341
-
342
- labelValue = getCellAnchor(labelValue, rowObj)
343
-
344
- cellValue = (
345
- <>
346
- <LegendShape fill={legendColor[0]} />
347
- {labelValue}
348
- </>
349
- )
350
- } else {
351
- cellValue = displayDataAsText(runtimeData[row][state.columns[column].name], column, state)
352
- }
353
-
354
- return (
355
- <td
356
- tabIndex='0'
357
- role='gridcell'
358
- onClick={e =>
359
- state.general.type === 'bubble' &&
360
- state.general.allowMapZoom &&
361
- state.general.geoType === 'world'
362
- ? dispatch({ type: 'SET_FILTERED_COUNTRY_CODE', payload: row })
363
- : true
364
- }
365
- >
366
- {cellValue}
367
- </td>
368
- )
369
- })}
370
- </tr>
371
- )
372
- })}
373
- </tbody>
374
- </table>
375
- </div>
376
- </section>
377
- {state.table.showDownloadLinkBelow && <TableMediaControls belowTable={true} />}
378
- <div id={skipId} className='cdcdataviz-sr-only'>
379
- Skipped data table.
380
- </div>
381
- </ErrorBoundary>
382
- )
383
- }
384
-
385
- export default DataTable
@@ -1,59 +0,0 @@
1
- import { memo, useState, useEffect } from 'react'
2
- import { useDebounce } from 'use-debounce'
3
-
4
- // todo: look into combining these with core
5
- const CheckBox = memo(({ label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes }) => (
6
- <label className='checkbox column-heading'>
7
- <input
8
- type='checkbox'
9
- name={fieldName}
10
- checked={value}
11
- onChange={e => {
12
- updateField(section, subsection, fieldName, !value)
13
- }}
14
- {...attributes}
15
- />
16
- <span className='edit-label'>
17
- {label}
18
- {tooltip}
19
- </span>
20
- </label>
21
- ))
22
-
23
- const TextField = ({ label, section = null, subsection = null, fieldName, updateField, value: stateValue, type = 'input', tooltip, ...attributes }) => {
24
- const [value, setValue] = useState(stateValue)
25
-
26
- const [debouncedValue] = useDebounce(value, 500)
27
-
28
- useEffect(() => {
29
- if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
30
- updateField(section, subsection, fieldName, debouncedValue)
31
- }
32
- }, [debouncedValue]) // eslint-disable-line
33
-
34
- let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
35
-
36
- const onChange = e => setValue(e.target.value)
37
-
38
- let formElement = <input type='text' name={name} onChange={onChange} {...attributes} value={value} />
39
-
40
- if ('textarea' === type) {
41
- formElement = <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
42
- }
43
-
44
- if ('number' === type) {
45
- formElement = <input type='number' name={name} onChange={onChange} {...attributes} value={value} />
46
- }
47
-
48
- return (
49
- <label>
50
- <span className='edit-label column-heading'>
51
- {label}
52
- {tooltip}
53
- </span>
54
- {formElement}
55
- </label>
56
- )
57
- }
58
-
59
- export { CheckBox, TextField }
@@ -1,3 +0,0 @@
1
- @import '@cdc/core/styles/base';
2
- @import '@cdc/core/styles/heading-colors';
3
- @import '@cdc/core/styles/v2/components/ui/tooltip';