@cdc/map 4.25.10 → 4.26.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.
- package/.claude/agents/typescript-organizer.md +118 -0
- package/dist/{cdcmap-fce76882.es.js → cdcmap-BnB1QM5d.es.js} +6 -13
- package/dist/{cdcmap-c55ac1ea.es.js → cdcmap-D6CG2-Hb.es.js} +5 -12
- package/dist/{cdcmap-31a33da1.es.js → cdcmap-MXgURbdZ.es.js} +6 -13
- package/dist/{cdcmap-1a1724a1.es.js → cdcmap-dgT_1dIT.es.js} +136 -151
- package/dist/cdcmap.js +58397 -55987
- package/examples/example-city-state.json +9 -1
- package/examples/multi-country-centering.json +45 -0
- package/examples/private/city_styles_variable.json +877 -0
- package/examples/private/colors-2.json +221 -0
- package/examples/private/colors.json +221 -0
- package/examples/private/map-filter-issue.json +2260 -0
- package/examples/private/map-legend.json +5303 -0
- package/index.html +27 -36
- package/package.json +6 -5
- package/src/CdcMapComponent.tsx +86 -26
- package/src/_stories/CdcMap.ColumnWrap.stories.tsx +31 -0
- package/src/_stories/CdcMap.DistrictOfColumbia.stories.tsx +320 -0
- package/src/_stories/CdcMap.Editor.stories.tsx +3426 -0
- package/src/_stories/CdcMap.SmallMultiples.stories.tsx +35 -0
- package/src/_stories/CdcMap.stories.tsx +116 -4
- package/src/_stories/_mock/column-wrap-test.json +265 -0
- package/src/_stories/_mock/multi-country-hide.json +78 -0
- package/src/_stories/_mock/multi-country.json +95 -0
- package/src/_stories/_mock/multi-state.json +887 -20403
- package/src/_stories/_mock/small_multiples/multi-state-small-multiples.json +8399 -0
- package/src/_stories/_mock/small_multiples/region-small-multiples.json +657 -0
- package/src/_stories/_mock/small_multiples/wastewater-map-small-multiples.json +221 -0
- package/src/_stories/_mock/usa-state-gradient.json +3 -4
- package/src/components/BubbleList.tsx +1 -1
- package/src/components/CityList.tsx +24 -18
- package/src/components/EditorPanel/components/EditorPanel.tsx +2380 -2206
- package/src/components/EditorPanel/components/HexShapeSettings.tsx +55 -93
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +0 -19
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +27 -37
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +351 -0
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/Geo.tsx +20 -3
- package/src/components/Legend/components/Legend.tsx +58 -75
- package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +1 -1
- package/src/components/Legend/components/index.scss +23 -6
- package/src/components/NavigationMenu.tsx +16 -13
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +163 -0
- package/src/components/SmallMultiples/SmallMultiples.css +32 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +150 -0
- package/src/components/SmallMultiples/SynchronizedTooltip.tsx +105 -0
- package/src/components/SmallMultiples/index.tsx +3 -0
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +18 -3
- package/src/components/UsaMap/components/TerritoriesSection.tsx +26 -12
- package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +30 -4
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +29 -9
- package/src/components/UsaMap/components/Territory/TerritoryShape.ts +7 -0
- package/src/components/UsaMap/components/UsaMap.County.tsx +16 -4
- package/src/components/UsaMap/components/UsaMap.Region.tsx +14 -1
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +29 -12
- package/src/components/UsaMap/components/UsaMap.State.tsx +30 -5
- package/src/components/UsaMap/helpers/map.ts +2 -2
- package/src/components/UsaMap/helpers/shapes.ts +9 -6
- package/src/components/WorldMap/WorldMap.tsx +81 -11
- package/src/data/initial-state.js +11 -0
- package/src/data/supported-geos.js +8 -76
- package/src/helpers/addUIDs.ts +13 -2
- package/src/helpers/applyColorToLegend.ts +25 -1
- package/src/helpers/applyLegendToRow.ts +5 -3
- package/src/helpers/constants.ts +3 -15
- package/src/helpers/displayGeoName.ts +22 -4
- package/src/helpers/generateRuntimeFilters.ts +1 -1
- package/src/helpers/generateRuntimeLegend.ts +1 -3
- package/src/helpers/generateRuntimeLegendHash.ts +1 -1
- package/src/helpers/getCountriesPicked.ts +103 -0
- package/src/helpers/getMapContainerClasses.ts +7 -0
- package/src/helpers/getPatternForRow.ts +2 -5
- package/src/helpers/index.ts +2 -4
- package/src/helpers/isLegendItemDisabled.ts +2 -2
- package/src/helpers/resetLegendToggles.ts +1 -0
- package/src/helpers/smallMultiplesHelpers.ts +359 -0
- package/src/helpers/tests/hashObj.test.ts +1 -1
- package/src/helpers/tests/titleCase.test.ts +76 -0
- package/src/helpers/titleCase.ts +13 -13
- package/src/helpers/toggleLegendActive.ts +76 -8
- package/src/helpers/urlDataHelpers.ts +1 -1
- package/src/hooks/useCountryZoom.tsx +241 -0
- package/src/hooks/useGeoClickHandler.ts +1 -1
- package/src/hooks/useProgrammaticMapTooltip.ts +110 -0
- package/src/hooks/useResizeObserver.ts +8 -2
- package/src/hooks/useStateZoom.tsx +7 -4
- package/src/hooks/useSynchronizedGeographies.ts +56 -0
- package/src/index.jsx +1 -0
- package/src/scss/editor-panel.scss +4 -440
- package/src/scss/main.scss +1 -1
- package/src/scss/map.scss +12 -15
- package/src/store/map.actions.ts +7 -7
- package/src/test/CdcMap.test.jsx +1 -1
- package/src/types/MapConfig.ts +32 -11
- package/src/types/MapContext.ts +6 -0
- package/src/types/runtimeLegend.ts +2 -1
- package/LICENSE +0 -201
- package/src/components/DataTable.tsx +0 -413
- package/src/components/EditorPanel/components/Inputs.tsx +0 -59
- package/src/components/MapControls.tsx +0 -44
- package/src/helpers/getUniqueValues.ts +0 -19
- package/src/helpers/hashObj.ts +0 -25
- package/src/hooks/useActiveElement.ts +0 -19
- package/src/hooks/useLegendSeparators.ts +0 -26
- package/src/scss/mixins.scss +0 -47
- package/src/types/Annotations.ts +0 -24
- /package/dist/{cdcmap-548642e6.es.js → cdcmap-Ct2SB0vL.es.js} +0 -0
|
@@ -1,413 +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
|
-
import { getPatternForRow } from '../helpers/getPatternForRow'
|
|
17
|
-
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
18
|
-
|
|
19
|
-
const DataTable = props => {
|
|
20
|
-
const {
|
|
21
|
-
state,
|
|
22
|
-
tableTitle,
|
|
23
|
-
indexTitle,
|
|
24
|
-
mapTitle,
|
|
25
|
-
rawData,
|
|
26
|
-
runtimeData,
|
|
27
|
-
headerColor,
|
|
28
|
-
expandDataTable,
|
|
29
|
-
columns,
|
|
30
|
-
displayDataAsText,
|
|
31
|
-
applyLegendToRow,
|
|
32
|
-
displayGeoName,
|
|
33
|
-
formatLegendLocation,
|
|
34
|
-
tabbingId,
|
|
35
|
-
interactionLabel
|
|
36
|
-
} = props
|
|
37
|
-
|
|
38
|
-
const dispatch = useContext(MapDispatchContext)
|
|
39
|
-
const { currentViewport: viewport, mapId } = useContext(ConfigContext)
|
|
40
|
-
const [expanded, setExpanded] = useState(expandDataTable)
|
|
41
|
-
const [sortBy, setSortBy] = useState({ column: 'geo', asc: false })
|
|
42
|
-
const [accessibilityLabel, setAccessibilityLabel] = useState('')
|
|
43
|
-
const fileName = `${mapTitle || 'data-table'}.csv`
|
|
44
|
-
|
|
45
|
-
// Catch all sorting method used on load by default but also on user click
|
|
46
|
-
// Having a custom method means we can add in any business logic we want going forward
|
|
47
|
-
const customSort = (a, b) => {
|
|
48
|
-
const digitRegex = /\d+/
|
|
49
|
-
|
|
50
|
-
const hasNumber = value => digitRegex.test(value)
|
|
51
|
-
|
|
52
|
-
// force null and undefined to the bottom
|
|
53
|
-
a = a === null || a === undefined ? '' : a
|
|
54
|
-
b = b === null || b === undefined ? '' : b
|
|
55
|
-
|
|
56
|
-
// convert any strings that are actually numbers to proper data type
|
|
57
|
-
const aNum = Number(a)
|
|
58
|
-
|
|
59
|
-
if (!Number.isNaN(aNum)) {
|
|
60
|
-
a = aNum
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const bNum = Number(b)
|
|
64
|
-
|
|
65
|
-
if (!Number.isNaN(bNum)) {
|
|
66
|
-
b = bNum
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// remove iso code prefixes
|
|
70
|
-
if (typeof a === 'string') {
|
|
71
|
-
a = a.replace('us-', '')
|
|
72
|
-
a = displayGeoName(a)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (typeof b === 'string') {
|
|
76
|
-
b = b.replace('us-', '')
|
|
77
|
-
b = displayGeoName(b)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// force any string values to lowercase
|
|
81
|
-
a = typeof a === 'string' ? a.toLowerCase() : a
|
|
82
|
-
b = typeof b === 'string' ? b.toLowerCase() : b
|
|
83
|
-
|
|
84
|
-
// 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.
|
|
85
|
-
if (typeof a === 'string' && hasNumber(a) === true) {
|
|
86
|
-
a = a.match(digitRegex)[0]
|
|
87
|
-
|
|
88
|
-
a = Number(a)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (typeof b === 'string' && hasNumber(b) === true) {
|
|
92
|
-
b = b.match(digitRegex)[0]
|
|
93
|
-
|
|
94
|
-
b = Number(b)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// When comparing a number to a string, always send string to bottom
|
|
98
|
-
if (typeof a === 'number' && typeof b === 'string') {
|
|
99
|
-
return 1
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (typeof b === 'number' && typeof a === 'string') {
|
|
103
|
-
return -1
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Return either 1 or -1 to indicate a sort priority
|
|
107
|
-
if (a > b) {
|
|
108
|
-
return 1
|
|
109
|
-
}
|
|
110
|
-
if (a < b) {
|
|
111
|
-
return -1
|
|
112
|
-
}
|
|
113
|
-
// returning 0, undefined or any falsey value will use subsequent sorts or
|
|
114
|
-
// the index as a tiebreaker
|
|
115
|
-
return 0
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Optionally wrap cell with anchor if config defines a navigation url
|
|
119
|
-
const getCellAnchor = (markup, row) => {
|
|
120
|
-
if (columns.navigate && row[columns.navigate.name]) {
|
|
121
|
-
markup = (
|
|
122
|
-
<span
|
|
123
|
-
onClick={() => navigationHandler(state.general.navigationTarget, row[columns.navigate.name])}
|
|
124
|
-
className='table-link'
|
|
125
|
-
title='Click for more information (Opens in a new window)'
|
|
126
|
-
role='link'
|
|
127
|
-
tabIndex='0'
|
|
128
|
-
onKeyDown={e => {
|
|
129
|
-
if (e.key === 'Enter') {
|
|
130
|
-
navigationHandler(state.general.navigationTarget, row[columns.navigate.name])
|
|
131
|
-
}
|
|
132
|
-
}}
|
|
133
|
-
>
|
|
134
|
-
{markup}
|
|
135
|
-
<ExternalIcon className='inline-icon' />
|
|
136
|
-
</span>
|
|
137
|
-
)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return markup
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const rand = Math.random().toString(16).substr(2, 8)
|
|
144
|
-
const skipId = `btn__${rand}`
|
|
145
|
-
|
|
146
|
-
const mapLookup = {
|
|
147
|
-
'us-county': 'United States County Map',
|
|
148
|
-
'single-state': 'State Map',
|
|
149
|
-
us: 'United States Map',
|
|
150
|
-
world: 'World Map'
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const DownloadButton = memo(() => {
|
|
154
|
-
let csvData
|
|
155
|
-
if (state.general.type === 'bubble' || !state.table.showFullGeoNameInCSV) {
|
|
156
|
-
// Just Unparse
|
|
157
|
-
csvData = Papa.unparse(rawData)
|
|
158
|
-
} else if (state.general.geoType !== 'us-county' || state.general.type === 'us-geocode') {
|
|
159
|
-
// Unparse + Add column for full Geo name
|
|
160
|
-
csvData = Papa.unparse(rawData.map(row => ({ FullGeoName: displayGeoName(row[state.columns.geo.name]), ...row })))
|
|
161
|
-
} else {
|
|
162
|
-
// Unparse + Add column for full Geo name
|
|
163
|
-
csvData = Papa.unparse(
|
|
164
|
-
rawData.map(row => ({ FullGeoName: formatLegendLocation(row[state.columns.geo.name]), ...row }))
|
|
165
|
-
)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' })
|
|
169
|
-
|
|
170
|
-
const saveBlob = () => {
|
|
171
|
-
//@ts-ignore
|
|
172
|
-
if (typeof window.navigator.msSaveBlob === 'function') {
|
|
173
|
-
//@ts-ignore
|
|
174
|
-
navigator.msSaveBlob(blob, fileName)
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return (
|
|
179
|
-
<a
|
|
180
|
-
download={fileName}
|
|
181
|
-
type='button'
|
|
182
|
-
onClick={() => {
|
|
183
|
-
saveBlob
|
|
184
|
-
publishAnalyticsEvent({
|
|
185
|
-
vizType: state.type,
|
|
186
|
-
vizSubType: getVizSubType(state),
|
|
187
|
-
eventType: 'data_downloaded',
|
|
188
|
-
eventAction: 'click',
|
|
189
|
-
eventLabel: interactionLabel,
|
|
190
|
-
vizTitle: getVizTitle(state)
|
|
191
|
-
})
|
|
192
|
-
}}
|
|
193
|
-
href={URL.createObjectURL(blob)}
|
|
194
|
-
aria-label='Download this data in a CSV file format.'
|
|
195
|
-
className={`${headerColor} no-border`}
|
|
196
|
-
id={`${skipId}`}
|
|
197
|
-
data-html2canvas-ignore={true}
|
|
198
|
-
role='button'
|
|
199
|
-
>
|
|
200
|
-
Download Data (CSV)
|
|
201
|
-
</a>
|
|
202
|
-
)
|
|
203
|
-
}, [rawData, state.table])
|
|
204
|
-
|
|
205
|
-
const TableMediaControls = ({ belowTable }) => {
|
|
206
|
-
return (
|
|
207
|
-
<MediaControls.Section classes={['download-links']}>
|
|
208
|
-
<MediaControls.Link config={state} interactionLabel={interactionLabel} />
|
|
209
|
-
{state.table.download && <DownloadButton config={state} />}
|
|
210
|
-
</MediaControls.Section>
|
|
211
|
-
)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Change accessibility label depending on expanded status
|
|
215
|
-
useEffect(() => {
|
|
216
|
-
const expandedLabel = 'Accessible data table.'
|
|
217
|
-
const collapsedLabel =
|
|
218
|
-
'Accessible data table. This table is currently collapsed visually but can still be read using a screen reader.'
|
|
219
|
-
|
|
220
|
-
if (expanded === true && accessibilityLabel !== expandedLabel) {
|
|
221
|
-
setAccessibilityLabel(expandedLabel)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (expanded === false && accessibilityLabel !== collapsedLabel) {
|
|
225
|
-
setAccessibilityLabel(collapsedLabel)
|
|
226
|
-
}
|
|
227
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
228
|
-
}, [expanded])
|
|
229
|
-
|
|
230
|
-
if (!state.data) return <Loading />
|
|
231
|
-
|
|
232
|
-
const rows = Object.keys(runtimeData)
|
|
233
|
-
.filter(row => applyLegendToRow(runtimeData[row], state))
|
|
234
|
-
.sort((a, b) => {
|
|
235
|
-
const sortVal = customSort(
|
|
236
|
-
runtimeData[a][state.columns[sortBy.column].name],
|
|
237
|
-
runtimeData[b][state.columns[sortBy.column].name]
|
|
238
|
-
)
|
|
239
|
-
if (!sortBy.asc) return sortVal
|
|
240
|
-
if (sortVal === 0) return 0
|
|
241
|
-
if (sortVal < 0) return 1
|
|
242
|
-
return -1
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
return (
|
|
246
|
-
<ErrorBoundary component='DataTable'>
|
|
247
|
-
{!state.table.showDownloadLinkBelow && <TableMediaControls />}
|
|
248
|
-
<section
|
|
249
|
-
id={tabbingId.replace('#', '')}
|
|
250
|
-
className={`data-table-container ${viewport}`}
|
|
251
|
-
aria-label={accessibilityLabel}
|
|
252
|
-
>
|
|
253
|
-
<SkipTo skipId={skipId} skipMessage='Skip Data Table' />
|
|
254
|
-
<div
|
|
255
|
-
className={expanded ? 'data-table-heading' : 'collapsed data-table-heading'}
|
|
256
|
-
onClick={() => {
|
|
257
|
-
setExpanded(!expanded)
|
|
258
|
-
}}
|
|
259
|
-
tabIndex='0'
|
|
260
|
-
onKeyDown={e => {
|
|
261
|
-
if (e.key === 'Enter') {
|
|
262
|
-
setExpanded(!expanded)
|
|
263
|
-
}
|
|
264
|
-
}}
|
|
265
|
-
>
|
|
266
|
-
<Icon display={expanded ? 'minus' : 'plus'} base />
|
|
267
|
-
{tableTitle}
|
|
268
|
-
</div>
|
|
269
|
-
<div
|
|
270
|
-
className='table-container'
|
|
271
|
-
style={{ maxHeight: state.dataTable.limitHeight && `${state.dataTable.height}px`, overflowY: 'scroll' }}
|
|
272
|
-
>
|
|
273
|
-
<table
|
|
274
|
-
height={expanded ? null : 0}
|
|
275
|
-
role='table'
|
|
276
|
-
aria-live='assertive'
|
|
277
|
-
className={expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}
|
|
278
|
-
hidden={!expanded}
|
|
279
|
-
aria-rowcount={state?.data.length ? state.data.length : '-1'}
|
|
280
|
-
>
|
|
281
|
-
<caption className='cdcdataviz-sr-only'>
|
|
282
|
-
{state.dataTable.caption
|
|
283
|
-
? state.dataTable.caption
|
|
284
|
-
: `Datatable showing data for the ${mapLookup[state.general.geoType]} figure.`}
|
|
285
|
-
</caption>
|
|
286
|
-
<thead style={{ position: 'sticky', top: 0, zIndex: 999 }}>
|
|
287
|
-
<tr>
|
|
288
|
-
{Object.keys(columns)
|
|
289
|
-
.filter(column => columns[column].dataTable === true && columns[column].name)
|
|
290
|
-
.map(column => {
|
|
291
|
-
let text
|
|
292
|
-
if (column !== 'geo') {
|
|
293
|
-
text = columns[column].label ? columns[column].label : columns[column].name
|
|
294
|
-
} else {
|
|
295
|
-
text = indexTitle || 'Location'
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
return (
|
|
299
|
-
<th
|
|
300
|
-
key={`col-header-${column}`}
|
|
301
|
-
tabIndex={0}
|
|
302
|
-
title={text}
|
|
303
|
-
role='columnheader'
|
|
304
|
-
scope='col'
|
|
305
|
-
onClick={() => {
|
|
306
|
-
setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false })
|
|
307
|
-
}}
|
|
308
|
-
onKeyDown={e => {
|
|
309
|
-
if (e.key === 'Enter') {
|
|
310
|
-
setSortBy({ column, asc: sortBy.column === column ? !sortBy.asc : false })
|
|
311
|
-
}
|
|
312
|
-
}}
|
|
313
|
-
className={
|
|
314
|
-
sortBy.column === column ? (sortBy.asc ? 'sort sort-asc' : 'sort sort-desc') : 'sort'
|
|
315
|
-
}
|
|
316
|
-
{...(sortBy.column === column
|
|
317
|
-
? sortBy.asc
|
|
318
|
-
? { 'aria-sort': 'ascending' }
|
|
319
|
-
: { 'aria-sort': 'descending' }
|
|
320
|
-
: null)}
|
|
321
|
-
>
|
|
322
|
-
{text}
|
|
323
|
-
<span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${
|
|
324
|
-
sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'
|
|
325
|
-
} order`}</span>
|
|
326
|
-
</th>
|
|
327
|
-
)
|
|
328
|
-
})}
|
|
329
|
-
</tr>
|
|
330
|
-
</thead>
|
|
331
|
-
<tbody>
|
|
332
|
-
{rows.map(row => {
|
|
333
|
-
return (
|
|
334
|
-
<tr role='row'>
|
|
335
|
-
{Object.keys(columns)
|
|
336
|
-
.filter(column => columns[column].dataTable === true && columns[column].name)
|
|
337
|
-
.map(column => {
|
|
338
|
-
let cellValue
|
|
339
|
-
|
|
340
|
-
if (column === 'geo') {
|
|
341
|
-
const rowObj = runtimeData[row]
|
|
342
|
-
const legendColor = applyLegendToRow(rowObj, state)
|
|
343
|
-
|
|
344
|
-
var labelValue
|
|
345
|
-
if (state.general.geoType !== 'us-county' || state.general.type === 'us-geocode') {
|
|
346
|
-
labelValue = displayGeoName(row)
|
|
347
|
-
} else {
|
|
348
|
-
labelValue = formatLegendLocation(row)
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
labelValue = getCellAnchor(labelValue, rowObj)
|
|
352
|
-
|
|
353
|
-
// Check for pattern information
|
|
354
|
-
const patternInfo = getPatternForRow(rowObj, state)
|
|
355
|
-
|
|
356
|
-
const legendShape = patternInfo ? (
|
|
357
|
-
<LegendShape
|
|
358
|
-
fill={legendColor[0]}
|
|
359
|
-
patternInfo={{
|
|
360
|
-
pattern: patternInfo.pattern,
|
|
361
|
-
patternId: `${mapId}--${String(patternInfo.dataKey).replace(' ', '-')}--${
|
|
362
|
-
patternInfo.patternIndex
|
|
363
|
-
}--table`,
|
|
364
|
-
size: patternInfo.size,
|
|
365
|
-
color: patternInfo.color
|
|
366
|
-
}}
|
|
367
|
-
/>
|
|
368
|
-
) : (
|
|
369
|
-
<LegendShape fill={legendColor[0]} />
|
|
370
|
-
)
|
|
371
|
-
|
|
372
|
-
cellValue = (
|
|
373
|
-
<>
|
|
374
|
-
{legendShape}
|
|
375
|
-
{labelValue}
|
|
376
|
-
</>
|
|
377
|
-
)
|
|
378
|
-
} else {
|
|
379
|
-
cellValue = displayDataAsText(runtimeData[row][state.columns[column].name], column, state)
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
return (
|
|
383
|
-
<td
|
|
384
|
-
tabIndex={0}
|
|
385
|
-
role='gridcell'
|
|
386
|
-
onClick={() =>
|
|
387
|
-
state.general.type === 'bubble' &&
|
|
388
|
-
state.general.allowMapZoom &&
|
|
389
|
-
state.general.geoType === 'world'
|
|
390
|
-
? dispatch({ type: 'SET_FILTERED_COUNTRY_CODE', payload: row })
|
|
391
|
-
: true
|
|
392
|
-
}
|
|
393
|
-
>
|
|
394
|
-
{cellValue}
|
|
395
|
-
</td>
|
|
396
|
-
)
|
|
397
|
-
})}
|
|
398
|
-
</tr>
|
|
399
|
-
)
|
|
400
|
-
})}
|
|
401
|
-
</tbody>
|
|
402
|
-
</table>
|
|
403
|
-
</div>
|
|
404
|
-
</section>
|
|
405
|
-
{state.table.showDownloadLinkBelow && <TableMediaControls belowTable={true} />}
|
|
406
|
-
<div id={skipId} className='cdcdataviz-sr-only'>
|
|
407
|
-
Skipped data table.
|
|
408
|
-
</div>
|
|
409
|
-
</ErrorBoundary>
|
|
410
|
-
)
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
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,44 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import MediaControls from '@cdc/core/components/MediaControls'
|
|
3
|
-
import { MapConfig } from '../types/MapConfig'
|
|
4
|
-
|
|
5
|
-
interface MapControlsProps {
|
|
6
|
-
config: MapConfig
|
|
7
|
-
imageId: string
|
|
8
|
-
interactionLabel: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const MapControls: React.FC<MapControlsProps> = ({ config, imageId, interactionLabel }) => {
|
|
12
|
-
const { showDownloadImgButton, showDownloadPdfButton } = config.general
|
|
13
|
-
|
|
14
|
-
if (!showDownloadImgButton && !showDownloadPdfButton) {
|
|
15
|
-
return null
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<MediaControls.Section classes={['download-buttons']}>
|
|
20
|
-
{showDownloadImgButton && (
|
|
21
|
-
<MediaControls.Button
|
|
22
|
-
text='Download Image'
|
|
23
|
-
title='Download Chart as Image'
|
|
24
|
-
type='image'
|
|
25
|
-
state={config}
|
|
26
|
-
elementToCapture={imageId}
|
|
27
|
-
interactionLabel={interactionLabel}
|
|
28
|
-
/>
|
|
29
|
-
)}
|
|
30
|
-
{showDownloadPdfButton && (
|
|
31
|
-
<MediaControls.Button
|
|
32
|
-
text='Download PDF'
|
|
33
|
-
title='Download Chart as PDF'
|
|
34
|
-
type='pdf'
|
|
35
|
-
state={config}
|
|
36
|
-
interactionLabel={interactionLabel}
|
|
37
|
-
elementToCapture={imageId}
|
|
38
|
-
/>
|
|
39
|
-
)}
|
|
40
|
-
</MediaControls.Section>
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export default MapControls
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Get unique values from a column in a dataset
|
|
3
|
-
* @returns {Array} - The unique values
|
|
4
|
-
*/
|
|
5
|
-
export const getUniqueValues = (data: Array<Record<string, any>>, columnName: string) => {
|
|
6
|
-
let result = {}
|
|
7
|
-
|
|
8
|
-
for (let i = 0; i < data.length; i++) {
|
|
9
|
-
let val = data[i][columnName]
|
|
10
|
-
|
|
11
|
-
if (undefined === val) continue
|
|
12
|
-
|
|
13
|
-
if (undefined === result[val]) {
|
|
14
|
-
result[val] = true
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return Object.keys(result)
|
|
19
|
-
}
|
package/src/helpers/hashObj.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hashes an object
|
|
3
|
-
* @param {Object} row - The object to hash
|
|
4
|
-
* @returns {number} - The hash of the object
|
|
5
|
-
*/
|
|
6
|
-
export const hashObj = row => {
|
|
7
|
-
try {
|
|
8
|
-
if (!row || row === undefined) return null
|
|
9
|
-
|
|
10
|
-
let str = JSON.stringify(row)
|
|
11
|
-
let hash = 0
|
|
12
|
-
|
|
13
|
-
if (str.length === 0) return hash
|
|
14
|
-
|
|
15
|
-
for (let i = 0; i < str.length; i++) {
|
|
16
|
-
let char = str.charCodeAt(i)
|
|
17
|
-
hash = (hash << 5) - hash + char
|
|
18
|
-
hash = hash & hash
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return hash
|
|
22
|
-
} catch (e) {
|
|
23
|
-
console.error({ state: 'COVE: ' + e.message }) // eslint-disable-line
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react'
|
|
2
|
-
// Use for accessibility testing
|
|
3
|
-
const useActiveElement = () => {
|
|
4
|
-
const [active, setActive] = useState(document.activeElement)
|
|
5
|
-
|
|
6
|
-
const handleFocusIn = e => {
|
|
7
|
-
setActive(document.activeElement)
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
document.addEventListener('focusin', handleFocusIn)
|
|
12
|
-
return () => {
|
|
13
|
-
document.removeEventListener('focusin', handleFocusIn)
|
|
14
|
-
}
|
|
15
|
-
}, [])
|
|
16
|
-
|
|
17
|
-
return active
|
|
18
|
-
}
|
|
19
|
-
export default useActiveElement
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { clamp } from 'lodash'
|
|
2
|
-
|
|
3
|
-
// TODO: generalize this to be used in legends other than linear block gradient
|
|
4
|
-
|
|
5
|
-
const LEGEND_SEPARATOR_SIZE = 0.02
|
|
6
|
-
const LEGEND_SEPARATOR_SIZE_MAX = 20
|
|
7
|
-
const LEGEND_SEPARATOR_SIZE_MIN = 8
|
|
8
|
-
|
|
9
|
-
const useLegendSeparators = (separators, legendWidth, allowsLegendSeparators) => {
|
|
10
|
-
const legendSeparators = allowsLegendSeparators
|
|
11
|
-
? separators?.replace(' ', '').split(',').map(Number).filter(Boolean) || []
|
|
12
|
-
: []
|
|
13
|
-
const separatorSize = clamp(legendWidth * LEGEND_SEPARATOR_SIZE, LEGEND_SEPARATOR_SIZE_MIN, LEGEND_SEPARATOR_SIZE_MAX)
|
|
14
|
-
const legendSeparatorsToSubtract = legendSeparators.length * separatorSize
|
|
15
|
-
const getTickSeparatorsAdjustment = (index: number) =>
|
|
16
|
-
legendSeparators.reduce((acc, separators) => (index >= separators ? acc + separatorSize : acc), 0)
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
legendSeparators,
|
|
20
|
-
separatorSize,
|
|
21
|
-
legendSeparatorsToSubtract,
|
|
22
|
-
getTickSeparatorsAdjustment
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export default useLegendSeparators
|
package/src/scss/mixins.scss
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
@mixin breakpoint($class) {
|
|
2
|
-
@if $class == xs {
|
|
3
|
-
@media (max-width: 767px) {
|
|
4
|
-
@content;
|
|
5
|
-
}
|
|
6
|
-
} @else if $class == sm {
|
|
7
|
-
@media (min-width: 768px) {
|
|
8
|
-
@content;
|
|
9
|
-
}
|
|
10
|
-
} @else if $class == md {
|
|
11
|
-
@media (min-width: 960px) {
|
|
12
|
-
@content;
|
|
13
|
-
}
|
|
14
|
-
} @else if $class == lg {
|
|
15
|
-
@media (min-width: 1300px) {
|
|
16
|
-
@content;
|
|
17
|
-
}
|
|
18
|
-
} @else {
|
|
19
|
-
@warn "Breakpoint mixin supports: xs, sm, md, lg";
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
@mixin breakpointClass($class) {
|
|
24
|
-
@if $class == xs {
|
|
25
|
-
&.xs,
|
|
26
|
-
&.xxs {
|
|
27
|
-
@content;
|
|
28
|
-
}
|
|
29
|
-
} @else if $class == sm {
|
|
30
|
-
&.sm,
|
|
31
|
-
&.md,
|
|
32
|
-
&.lg {
|
|
33
|
-
@content;
|
|
34
|
-
}
|
|
35
|
-
} @else if $class == md {
|
|
36
|
-
&.md,
|
|
37
|
-
&.lg {
|
|
38
|
-
@content;
|
|
39
|
-
}
|
|
40
|
-
} @else if $class == lg {
|
|
41
|
-
&.lg {
|
|
42
|
-
@content;
|
|
43
|
-
}
|
|
44
|
-
} @else {
|
|
45
|
-
@warn "Breakpoint Class mixin supports: xs, sm, md, lg";
|
|
46
|
-
}
|
|
47
|
-
}
|
package/src/types/Annotations.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
type Annotation = {
|
|
2
|
-
/** The x-coordinate of the annotation */
|
|
3
|
-
x: number
|
|
4
|
-
/** The y-coordinate of the annotation */
|
|
5
|
-
y: number
|
|
6
|
-
/** The x-offset for the annotation's text */
|
|
7
|
-
dx: number
|
|
8
|
-
/** The y-offset for the annotation's text */
|
|
9
|
-
dy: number
|
|
10
|
-
/** The opacity level of the annotation */
|
|
11
|
-
opacity: number
|
|
12
|
-
/** The text content of the annotation */
|
|
13
|
-
text: string
|
|
14
|
-
/** The type of connection for the annotation */
|
|
15
|
-
connectionType: string
|
|
16
|
-
edit: {
|
|
17
|
-
/** Indicates if the label can be edited */
|
|
18
|
-
label: boolean
|
|
19
|
-
/** Indicates if the subject can be edited */
|
|
20
|
-
subject: boolean
|
|
21
|
-
}
|
|
22
|
-
/** The type of marker used for the annotation */
|
|
23
|
-
marker: 'arrow' | 'circle'
|
|
24
|
-
}
|
|
File without changes
|