@cdc/core 4.25.8 → 4.25.10
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/components/AdvancedEditor/AdvancedEditor.tsx +29 -8
- package/components/DataTable/DataTable.tsx +56 -38
- package/components/DataTable/components/ChartHeader.tsx +44 -14
- package/components/DataTable/components/ExpandCollapse.tsx +10 -1
- package/components/DataTable/components/MapHeader.tsx +24 -13
- package/components/DataTable/data-table.css +6 -0
- package/components/DataTable/helpers/chartCellMatrix.tsx +11 -8
- package/components/DataTable/helpers/mapCellMatrix.tsx +19 -1
- package/components/DownloadButton.tsx +40 -14
- package/components/EditorPanel/components/MarkupHighlightedTextField.tsx +227 -0
- package/components/EditorPanel/components/MarkupVariablesEditor.tsx +411 -0
- package/components/EditorPanel/components/PanelMarkup.tsx +59 -0
- package/components/ErrorBoundary.jsx +3 -1
- package/components/Filters/Filters.tsx +27 -20
- package/components/Filters/components/Tabs.tsx +1 -0
- package/components/Legend/Legend.Gradient.tsx +3 -6
- package/components/LegendShape.tsx +121 -3
- package/components/MediaControls.tsx +51 -3
- package/components/PaletteConversionModal.tsx +87 -0
- package/components/PaletteSelector/DeveloperPaletteRollback.tsx +114 -0
- package/components/PaletteSelector/PaletteSelector.css +51 -0
- package/components/PaletteSelector/PaletteSelector.tsx +112 -0
- package/components/PaletteSelector/index.ts +2 -0
- package/components/RichTooltip/RichTooltip.tsx +1 -0
- package/components/Table/Table.tsx +3 -1
- package/components/_stories/BlurStrokeTest.stories.tsx +1 -1
- package/components/_stories/DataTable.stories.tsx +1 -1
- package/components/_stories/Filters.stories.tsx +1 -1
- package/components/_stories/Footnotes.stories.tsx +1 -1
- package/components/_stories/Inputs.stories.tsx +1 -1
- package/components/_stories/MultiSelect.stories.tsx +3 -3
- package/components/_stories/NestedDropdown.stories.tsx +1 -1
- package/components/_stories/Table.stories.tsx +1 -1
- package/components/elements/_stories/Button.stories.tsx +1 -1
- package/components/elements/_stories/Card.stories.tsx +1 -1
- package/components/inputs/InputToggle.tsx +2 -0
- package/components/managers/DataDesigner.tsx +10 -9
- package/components/managers/_stories/DataDesigner.stories.tsx +1 -1
- package/components/ui/Tooltip.tsx +2 -1
- package/components/ui/_stories/Accordion.stories.tsx +1 -1
- package/components/ui/_stories/ColorPaletteMigration.stories.mdx +275 -0
- package/components/ui/_stories/Colors.stories.tsx +330 -0
- package/components/ui/_stories/IconGallery.stories.tsx +316 -0
- package/components/ui/_stories/Title.stories.tsx +1 -1
- package/contexts/EditorContext.ts +18 -0
- package/contexts/editor.actions.ts +28 -0
- package/contexts/editor.reducer.ts +94 -0
- package/data/chartColorPalettes.ts +118 -0
- package/data/colorPalettes.ts +9 -0
- package/data/mapColorPalettes.ts +45 -0
- package/data/sharedPalettes.ts +50 -0
- package/dist/cove-main.css +14 -11
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +80 -0
- package/helpers/addValuesToFilters.ts +2 -3
- package/helpers/cloneConfig.ts +31 -0
- package/helpers/configDataHelpers.ts +128 -0
- package/helpers/configHelpers.ts +27 -0
- package/helpers/constants.ts +5 -2
- package/helpers/coveUpdateWorker.ts +13 -3
- package/helpers/filterColorPalettes.ts +152 -0
- package/helpers/generateColorsArray.ts +13 -0
- package/helpers/getColorPaletteVersion.ts +33 -0
- package/helpers/getPaletteAccessor.ts +18 -0
- package/helpers/markupProcessor.ts +205 -0
- package/helpers/metrics/helpers.ts +42 -19
- package/helpers/metrics/types.ts +48 -9
- package/helpers/metrics/utils.ts +34 -0
- package/helpers/palettes/colorDistributions.ts +56 -0
- package/helpers/palettes/migratePaletteName.ts +150 -0
- package/helpers/palettes/standardizePaletteNames.ts +77 -0
- package/helpers/palettes/utils.ts +267 -0
- package/helpers/queryStringUtils.ts +13 -0
- package/helpers/testing.ts +345 -0
- package/helpers/tests/addValuesToFilters.test.ts +1 -2
- package/helpers/tests/generateColorsArray.test.ts +24 -0
- package/helpers/tests/markupProcessor.test.ts +538 -0
- package/helpers/tests/testStandaloneBuild.ts +44 -0
- package/helpers/useMarkupVariables.ts +31 -0
- package/helpers/vegaConfig.ts +0 -1
- package/helpers/ver/4.24.10.ts +2 -1
- package/helpers/ver/4.24.11.ts +2 -1
- package/helpers/ver/4.24.3.ts +2 -1
- package/helpers/ver/4.24.4.ts +2 -1
- package/helpers/ver/4.24.5.ts +2 -1
- package/helpers/ver/4.24.7.ts +2 -1
- package/helpers/ver/4.24.9.ts +2 -1
- package/helpers/ver/4.25.1.ts +2 -1
- package/helpers/ver/4.25.10.ts +36 -0
- package/helpers/ver/4.25.3.ts +2 -1
- package/helpers/ver/4.25.4.ts +2 -1
- package/helpers/ver/4.25.6.ts +2 -1
- package/helpers/ver/4.25.7.ts +2 -1
- package/helpers/ver/4.25.8.ts +2 -1
- package/helpers/ver/4.25.9.ts +293 -0
- package/helpers/ver/tests/4.25.10.test.ts +204 -0
- package/helpers/ver/tests/4.25.8.test.ts +1 -1
- package/helpers/ver/tests/4.25.9.test.ts +51 -0
- package/hooks/useColorPalette.ts +79 -0
- package/package.json +12 -4
- package/styles/_global.scss +7 -5
- package/styles/base.scss +8 -5
- package/styles/v2/components/button.scss +4 -3
- package/styles/v2/components/editor.scss +2 -1
- package/styles/v2/layout/_data-table.scss +3 -2
- package/styles/v2/themes/_color-definitions.scss +18 -17
- package/testBuild.js +0 -0
- package/testing-setup.js +32 -0
- package/types/MarkupInclude.ts +6 -1
- package/types/MarkupVariable.ts +19 -0
- package/types/VizFilter.ts +1 -0
- package/vitest.config.ts +16 -0
- package/components/ui/_stories/Colors.stories.mdx +0 -220
- package/components/ui/_stories/IconGallery.stories.mdx +0 -14
- package/data/colorPalettes.js +0 -171
- package/helpers/formatConfigBeforeSave.ts +0 -135
- package/helpers/tests/formatConfigBeforeSave.test.ts +0 -68
|
@@ -3,12 +3,17 @@ import MapIcon from '../../assets/map-folded.svg'
|
|
|
3
3
|
import ChartIcon from '../../assets/icon-chart-bar.svg'
|
|
4
4
|
import MarkupIncludeIcon from '../../assets/icon-code.svg'
|
|
5
5
|
import { FilterFunction, JsonEditor, UpdateFunction } from 'json-edit-react'
|
|
6
|
-
import { formatConfigBeforeSave as stripConfig } from '../../helpers/formatConfigBeforeSave'
|
|
7
6
|
import './advanced-editor-styles.css'
|
|
8
7
|
import _ from 'lodash'
|
|
9
8
|
import Tooltip from '../ui/Tooltip'
|
|
10
9
|
|
|
11
|
-
export const AdvancedEditor = ({
|
|
10
|
+
export const AdvancedEditor = ({
|
|
11
|
+
loadConfig,
|
|
12
|
+
config,
|
|
13
|
+
convertStateToConfig,
|
|
14
|
+
stripConfig = config => config,
|
|
15
|
+
onExpandCollapse = () => {}
|
|
16
|
+
}) => {
|
|
12
17
|
const [advancedToggle, _setAdvancedToggle] = useState(false)
|
|
13
18
|
const [configTextboxValue, setConfigTextbox] = useState<Record<string, any>>({})
|
|
14
19
|
const setAdvancedToggle = val => {
|
|
@@ -26,13 +31,29 @@ export const AdvancedEditor = ({ loadConfig, config, convertStateToConfig, onExp
|
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
useEffect(() => {
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
parsedConfig =
|
|
34
|
+
// Only process config when advanced editor is open to improve performance
|
|
35
|
+
if (advancedToggle) {
|
|
36
|
+
let parsedConfig = stripConfig(config)
|
|
37
|
+
if (config.type !== 'dashboard') {
|
|
38
|
+
parsedConfig = convertStateToConfig()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
setConfigTextbox(parsedConfig)
|
|
32
42
|
}
|
|
43
|
+
}, [config, advancedToggle])
|
|
33
44
|
|
|
34
|
-
|
|
35
|
-
|
|
45
|
+
// Initialize config when advanced editor is first opened
|
|
46
|
+
const handleToggleOpen = () => {
|
|
47
|
+
if (!advancedToggle) {
|
|
48
|
+
// Process config only when opening for the first time
|
|
49
|
+
let parsedConfig = stripConfig(config)
|
|
50
|
+
if (config.type !== 'dashboard') {
|
|
51
|
+
parsedConfig = convertStateToConfig()
|
|
52
|
+
}
|
|
53
|
+
setConfigTextbox(parsedConfig)
|
|
54
|
+
}
|
|
55
|
+
setAdvancedToggle(!advancedToggle)
|
|
56
|
+
}
|
|
36
57
|
|
|
37
58
|
const typeLookup = {
|
|
38
59
|
chart: ['Charts', 'https://www.cdc.gov/cove/index.html', <ChartIcon />],
|
|
@@ -58,7 +79,7 @@ export const AdvancedEditor = ({ loadConfig, config, convertStateToConfig, onExp
|
|
|
58
79
|
</div>
|
|
59
80
|
</a>
|
|
60
81
|
<div className='advanced'>
|
|
61
|
-
<span className='advanced-toggle-link' onClick={
|
|
82
|
+
<span className='advanced-toggle-link' onClick={handleToggleOpen}>
|
|
62
83
|
<span>{advancedToggle ? `— ` : `+ `}</span>Advanced Options
|
|
63
84
|
</span>
|
|
64
85
|
{advancedToggle && (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useState, useMemo } from 'react'
|
|
1
|
+
import { useEffect, useState, useMemo, useRef } from 'react'
|
|
2
2
|
import { timeParse } from 'd3-time-format'
|
|
3
3
|
|
|
4
4
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
@@ -53,6 +53,10 @@ export type DataTableProps = {
|
|
|
53
53
|
// determines if columns should be wrapped in the table
|
|
54
54
|
wrapColumns?: boolean
|
|
55
55
|
interactionLabel?: string
|
|
56
|
+
// Map-specific props (optional)
|
|
57
|
+
legendMemo?: React.MutableRefObject<Map<any, any>>
|
|
58
|
+
legendSpecialClassLastMemo?: React.MutableRefObject<Map<any, any>>
|
|
59
|
+
runtimeLegend?: any
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
const DataTable = (props: DataTableProps) => {
|
|
@@ -95,6 +99,11 @@ const DataTable = (props: DataTableProps) => {
|
|
|
95
99
|
|
|
96
100
|
const [accessibilityLabel, setAccessibilityLabel] = useState('')
|
|
97
101
|
|
|
102
|
+
// Create default refs for map-specific props when not provided
|
|
103
|
+
const defaultLegendMemo = useRef(new Map())
|
|
104
|
+
const defaultLegendSpecialClassLastMemo = useRef(new Map())
|
|
105
|
+
const defaultRuntimeLegend = null
|
|
106
|
+
|
|
98
107
|
const isVertical = !(config.type === 'chart' && !config.table?.showVertical)
|
|
99
108
|
|
|
100
109
|
const rand = Math.random().toString(16).substr(2, 8)
|
|
@@ -136,23 +145,23 @@ const DataTable = (props: DataTableProps) => {
|
|
|
136
145
|
const rows =
|
|
137
146
|
isVertical && sortBy.column
|
|
138
147
|
? rawRows.sort((a, b) => {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
148
|
+
let dataA
|
|
149
|
+
let dataB
|
|
150
|
+
if (config.type === 'map' && config.columns) {
|
|
151
|
+
const sortByColName = config.columns[sortBy.column].name
|
|
152
|
+
dataA = runtimeData[a][sortByColName]
|
|
153
|
+
dataB = runtimeData[b][sortByColName]
|
|
154
|
+
}
|
|
155
|
+
if (['chart', 'dashboard', 'table'].includes(config.type)) {
|
|
156
|
+
dataA = runtimeData[a][sortBy.column]
|
|
157
|
+
dataB = runtimeData[b][sortBy.column]
|
|
158
|
+
}
|
|
159
|
+
if (!dataA && !dataB && config.type === 'chart' && config.xAxis && config.xAxis.type === 'date-time') {
|
|
160
|
+
dataA = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[a][config.xAxis.dataKey])
|
|
161
|
+
dataB = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[b][config.xAxis.dataKey])
|
|
162
|
+
}
|
|
163
|
+
return dataA || dataB ? customSort(dataA, dataB, sortBy, config) : 0
|
|
164
|
+
})
|
|
156
165
|
: rawRows
|
|
157
166
|
|
|
158
167
|
const limitHeight = {
|
|
@@ -231,17 +240,17 @@ const DataTable = (props: DataTableProps) => {
|
|
|
231
240
|
const visibleData =
|
|
232
241
|
config.type === 'map'
|
|
233
242
|
? getMapRowData(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
243
|
+
rows,
|
|
244
|
+
columns,
|
|
245
|
+
config,
|
|
246
|
+
formatLegendLocation,
|
|
247
|
+
runtimeData as Record<string, Object>,
|
|
248
|
+
displayGeoName,
|
|
249
|
+
filterColumns
|
|
250
|
+
)
|
|
242
251
|
: runtimeData.map(d => {
|
|
243
|
-
|
|
244
|
-
|
|
252
|
+
return _.pick(d, [...filterColumns, ...dataSeriesColumns])
|
|
253
|
+
})
|
|
245
254
|
const csvData = config.table?.downloadVisibleDataOnly ? visibleData : rawData
|
|
246
255
|
|
|
247
256
|
// only use fullGeoName on County maps and no other
|
|
@@ -277,7 +286,16 @@ const DataTable = (props: DataTableProps) => {
|
|
|
277
286
|
|
|
278
287
|
const childrenMatrix =
|
|
279
288
|
config.type === 'map'
|
|
280
|
-
? mapCellMatrix({
|
|
289
|
+
? mapCellMatrix({
|
|
290
|
+
...props,
|
|
291
|
+
rows,
|
|
292
|
+
wrapColumns,
|
|
293
|
+
runtimeData,
|
|
294
|
+
viewport,
|
|
295
|
+
legendMemo: props.legendMemo || defaultLegendMemo,
|
|
296
|
+
legendSpecialClassLastMemo: props.legendSpecialClassLastMemo || defaultLegendSpecialClassLastMemo,
|
|
297
|
+
runtimeLegend: props.runtimeLegend || defaultRuntimeLegend
|
|
298
|
+
})
|
|
281
299
|
: chartCellMatrix({ rows, ...props, runtimeData, isVertical, sortBy, hasRowType, viewport })
|
|
282
300
|
|
|
283
301
|
const useBottomExpandCollapse = config.table.showBottomCollapse && expanded && Array.isArray(childrenMatrix)
|
|
@@ -285,12 +303,12 @@ const DataTable = (props: DataTableProps) => {
|
|
|
285
303
|
// If every value in a column is a number, record the column index so the header and cells can be right-aligned
|
|
286
304
|
const rightAlignedCols = childrenMatrix.length
|
|
287
305
|
? Object.fromEntries(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
306
|
+
Object.keys(childrenMatrix[0])
|
|
307
|
+
.filter(
|
|
308
|
+
i => childrenMatrix.filter(row => isRightAlignedTableValue(row[i])).length === childrenMatrix.length
|
|
309
|
+
)
|
|
310
|
+
.map(x => [x, true])
|
|
311
|
+
)
|
|
294
312
|
: {}
|
|
295
313
|
|
|
296
314
|
const showCollapseButton = config.table.collapsible !== false && useBottomExpandCollapse
|
|
@@ -305,6 +323,7 @@ const DataTable = (props: DataTableProps) => {
|
|
|
305
323
|
fileName={`${vizTitle || 'data-table'}.csv`}
|
|
306
324
|
headerColor={headerColor}
|
|
307
325
|
interactionLabel={interactionLabel}
|
|
326
|
+
config={config}
|
|
308
327
|
/>
|
|
309
328
|
)}
|
|
310
329
|
</MediaControls.Section>
|
|
@@ -365,9 +384,8 @@ const DataTable = (props: DataTableProps) => {
|
|
|
365
384
|
)
|
|
366
385
|
}
|
|
367
386
|
tableOptions={{
|
|
368
|
-
className: `table table-striped table-width-unset ${
|
|
369
|
-
|
|
370
|
-
}${isVertical ? '' : ' horizontal'}`,
|
|
387
|
+
className: `table table-striped table-width-unset ${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'
|
|
388
|
+
}${isVertical ? '' : ' horizontal'}`,
|
|
371
389
|
'aria-live': 'assertive',
|
|
372
390
|
'aria-rowcount': config?.data?.length ? config.data.length : -1,
|
|
373
391
|
hidden: !expanded,
|
|
@@ -7,6 +7,7 @@ import { getNewSortBy } from '../helpers/getNewSortBy'
|
|
|
7
7
|
import parse from 'html-react-parser'
|
|
8
8
|
import { ChartConfig } from '@cdc/chart/src/types/ChartConfig'
|
|
9
9
|
import { publishAnalyticsEvent } from '../../../helpers/metrics/helpers'
|
|
10
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
10
11
|
|
|
11
12
|
type ChartHeaderProps = {
|
|
12
13
|
data
|
|
@@ -61,9 +62,8 @@ const ChartHeader = ({
|
|
|
61
62
|
if (columnHeaderText === notApplicableText) return
|
|
62
63
|
|
|
63
64
|
return (
|
|
64
|
-
<span className='cdcdataviz-sr-only'>{`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${
|
|
65
|
-
|
|
66
|
-
} order`}</span>
|
|
65
|
+
<span className='cdcdataviz-sr-only'>{`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${sortBy.column !== columnHeaderText ? 'ascending' : sortBy.column === 'desc' ? 'descending' : 'ascending'
|
|
66
|
+
} order`}</span>
|
|
67
67
|
)
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -116,18 +116,29 @@ const ChartHeader = ({
|
|
|
116
116
|
scope='col'
|
|
117
117
|
onClick={() => {
|
|
118
118
|
if (hasRowType) return
|
|
119
|
-
publishAnalyticsEvent(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
'click',
|
|
124
|
-
interactionLabel
|
|
125
|
-
|
|
119
|
+
publishAnalyticsEvent({
|
|
120
|
+
vizType: config.type,
|
|
121
|
+
vizSubType: getVizSubType(config),
|
|
122
|
+
eventType: `data_table_sort`,
|
|
123
|
+
eventAction: 'click',
|
|
124
|
+
eventLabel: interactionLabel,
|
|
125
|
+
vizTitle: getVizTitle(config),
|
|
126
|
+
specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
|
|
127
|
+
})
|
|
126
128
|
setSortBy(newSortBy)
|
|
127
129
|
}}
|
|
128
130
|
onKeyDown={e => {
|
|
129
131
|
if (hasRowType) return
|
|
130
|
-
if (e.
|
|
132
|
+
if (e.key === 'Enter') {
|
|
133
|
+
publishAnalyticsEvent({
|
|
134
|
+
vizType: config.type,
|
|
135
|
+
vizSubType: getVizSubType(config),
|
|
136
|
+
eventType: `data_table_sort`,
|
|
137
|
+
eventAction: 'keyboard',
|
|
138
|
+
eventLabel: interactionLabel,
|
|
139
|
+
vizTitle: getVizTitle(config),
|
|
140
|
+
specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
|
|
141
|
+
})
|
|
131
142
|
setSortBy(newSortBy)
|
|
132
143
|
}
|
|
133
144
|
}}
|
|
@@ -137,7 +148,7 @@ const ChartHeader = ({
|
|
|
137
148
|
: { 'aria-sort': 'descending' }
|
|
138
149
|
: null)}
|
|
139
150
|
>
|
|
140
|
-
<ColumnHeadingText text={text}
|
|
151
|
+
<ColumnHeadingText text={text} config={config} />
|
|
141
152
|
{isSortedCol && <SortIcon ascending={sortByAsc} />}
|
|
142
153
|
<ScreenReaderSortByText sortBy={sortBy} config={config} text={text} />
|
|
143
154
|
</th>
|
|
@@ -171,10 +182,29 @@ const ChartHeader = ({
|
|
|
171
182
|
role='columnheader'
|
|
172
183
|
scope='col'
|
|
173
184
|
onClick={() => {
|
|
185
|
+
if (hasRowType) return
|
|
186
|
+
publishAnalyticsEvent({
|
|
187
|
+
vizType: config.type,
|
|
188
|
+
vizSubType: getVizSubType(config),
|
|
189
|
+
eventType: `data_table_sort`,
|
|
190
|
+
eventAction: 'click',
|
|
191
|
+
eventLabel: interactionLabel,
|
|
192
|
+
vizTitle: getVizTitle(config),
|
|
193
|
+
specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
|
|
194
|
+
})
|
|
174
195
|
setSortBy(newSortBy)
|
|
175
196
|
}}
|
|
176
197
|
onKeyDown={e => {
|
|
177
|
-
if (e.
|
|
198
|
+
if (e.key === 'Enter') {
|
|
199
|
+
publishAnalyticsEvent({
|
|
200
|
+
vizType: config.type,
|
|
201
|
+
vizSubType: getVizSubType(config),
|
|
202
|
+
eventType: `data_table_sort`,
|
|
203
|
+
eventAction: 'keyboard',
|
|
204
|
+
eventLabel: interactionLabel,
|
|
205
|
+
vizTitle: getVizTitle(config),
|
|
206
|
+
specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
|
|
207
|
+
})
|
|
178
208
|
setSortBy(newSortBy)
|
|
179
209
|
}
|
|
180
210
|
}}
|
|
@@ -184,7 +214,7 @@ const ChartHeader = ({
|
|
|
184
214
|
: { 'aria-sort': 'descending' }
|
|
185
215
|
: null)}
|
|
186
216
|
>
|
|
187
|
-
<ColumnHeadingText text={text}
|
|
217
|
+
<ColumnHeadingText text={text} config={config} />
|
|
188
218
|
{isSortedCol && <SortIcon ascending={sortByAsc} />}
|
|
189
219
|
|
|
190
220
|
<ScreenReaderSortByText text={text} config={config} sortBy={sortBy} />
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
1
2
|
import { publishAnalyticsEvent } from '../../../helpers/metrics/helpers'
|
|
2
3
|
import { Visualization } from '../../../types/Visualization'
|
|
3
4
|
import Icon from '../../ui/Icon'
|
|
@@ -17,7 +18,15 @@ const ExpandCollapse = ({ expanded, setExpanded, tableTitle, config, interaction
|
|
|
17
18
|
role='button'
|
|
18
19
|
className={expanded ? 'data-table-heading p-3' : 'collapsed data-table-heading p-3'}
|
|
19
20
|
onClick={() => {
|
|
20
|
-
publishAnalyticsEvent(
|
|
21
|
+
publishAnalyticsEvent({
|
|
22
|
+
vizType: config?.type,
|
|
23
|
+
vizSubType: getVizSubType(config),
|
|
24
|
+
eventType: 'expand_collapse_toggled',
|
|
25
|
+
eventAction: 'click',
|
|
26
|
+
eventLabel: interactionLabel,
|
|
27
|
+
vizTitle: getVizTitle(config),
|
|
28
|
+
specifics: expanded ? 'collapsed' : 'expanded'
|
|
29
|
+
})
|
|
21
30
|
setExpanded(!expanded)
|
|
22
31
|
}}
|
|
23
32
|
tabIndex={0}
|
|
@@ -3,6 +3,7 @@ import ScreenReaderText from '../../elements/ScreenReaderText'
|
|
|
3
3
|
import { SortIcon } from './SortIcon'
|
|
4
4
|
import { getNewSortBy } from '../helpers/getNewSortBy'
|
|
5
5
|
import { publishAnalyticsEvent } from '../../../helpers/metrics/helpers'
|
|
6
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
6
7
|
|
|
7
8
|
type MapHeaderProps = DataTableProps & {
|
|
8
9
|
sortBy: { column; asc }
|
|
@@ -56,17 +57,28 @@ const MapHeader = ({
|
|
|
56
57
|
role='columnheader'
|
|
57
58
|
scope='col'
|
|
58
59
|
onClick={() => {
|
|
59
|
-
publishAnalyticsEvent(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
'click',
|
|
64
|
-
interactionLabel
|
|
65
|
-
|
|
60
|
+
publishAnalyticsEvent({
|
|
61
|
+
vizType: config.type,
|
|
62
|
+
vizSubType: getVizSubType(config),
|
|
63
|
+
eventType: `data_table_sort`,
|
|
64
|
+
eventAction: 'click',
|
|
65
|
+
eventLabel: interactionLabel,
|
|
66
|
+
vizTitle: getVizTitle(config),
|
|
67
|
+
specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
|
|
68
|
+
})
|
|
66
69
|
setSortBy(newSortBy)
|
|
67
70
|
}}
|
|
68
71
|
onKeyDown={e => {
|
|
69
|
-
if (e.
|
|
72
|
+
if (e.key === 'Enter') {
|
|
73
|
+
publishAnalyticsEvent({
|
|
74
|
+
vizType: config.type,
|
|
75
|
+
vizSubType: getVizSubType(config),
|
|
76
|
+
eventType: `data_table_sort`,
|
|
77
|
+
eventAction: 'keyboard',
|
|
78
|
+
eventLabel: interactionLabel,
|
|
79
|
+
vizTitle: getVizTitle(config),
|
|
80
|
+
specifics: `column: ${newSortBy.column || 'none'}, order: ${newSortBy.asc === true ? 'asc' : newSortBy.asc === false ? 'desc' : 'none'}`
|
|
81
|
+
})
|
|
70
82
|
setSortBy(newSortBy)
|
|
71
83
|
}
|
|
72
84
|
}}
|
|
@@ -77,15 +89,14 @@ const MapHeader = ({
|
|
|
77
89
|
: { 'aria-sort': 'descending' }
|
|
78
90
|
: null)}
|
|
79
91
|
>
|
|
80
|
-
<ColumnHeadingText text={text} config={config}
|
|
92
|
+
<ColumnHeadingText text={text} config={config} />
|
|
81
93
|
<SortIcon ascending={sortByAsc} />
|
|
82
|
-
<span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${
|
|
83
|
-
|
|
84
|
-
} order`}</span>
|
|
94
|
+
<span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'
|
|
95
|
+
} order`}</span>
|
|
85
96
|
</th>
|
|
86
97
|
)
|
|
87
98
|
})}
|
|
88
|
-
</tr>
|
|
99
|
+
</tr >
|
|
89
100
|
)
|
|
90
101
|
}
|
|
91
102
|
|
|
@@ -31,7 +31,6 @@ const chartCellArray = ({
|
|
|
31
31
|
const groupBy = config.table?.groupBy
|
|
32
32
|
const dataSeriesColumns = getDataSeriesColumns(config, isVertical, runtimeData)
|
|
33
33
|
|
|
34
|
-
|
|
35
34
|
const dataSeriesColumnsSorted = () => {
|
|
36
35
|
if (!sortBy && sortBy.colIndex === null) return dataSeriesColumns
|
|
37
36
|
return dataSeriesColumns.sort((a, b) => {
|
|
@@ -73,10 +72,14 @@ const chartCellArray = ({
|
|
|
73
72
|
return rows.map(row => {
|
|
74
73
|
if (hasRowType) {
|
|
75
74
|
const rowType = getRowType(runtimeData[row])
|
|
76
|
-
const rowValues = dataSeriesColumns.map(column =>
|
|
75
|
+
const rowValues = dataSeriesColumns.map(column =>
|
|
76
|
+
getChartCellValue(row, column, config, runtimeData, rightAxisItemsMap)
|
|
77
|
+
)
|
|
77
78
|
return [rowType, ...rowValues]
|
|
78
79
|
} else {
|
|
79
|
-
return dataSeriesColumns.map((column, j) =>
|
|
80
|
+
return dataSeriesColumns.map((column, j) =>
|
|
81
|
+
getChartCellValue(row, column, config, runtimeData, rightAxisItemsMap)
|
|
82
|
+
)
|
|
80
83
|
}
|
|
81
84
|
})
|
|
82
85
|
}
|
|
@@ -86,11 +89,11 @@ const chartCellArray = ({
|
|
|
86
89
|
let nodes: ReactNode[] =
|
|
87
90
|
config.visualizationType !== 'Pie'
|
|
88
91
|
? [
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
<>
|
|
93
|
+
{colorScale && colorScale(seriesName) && <LegendShape fill={colorScale(seriesName)} />}
|
|
94
|
+
{parse(String(seriesName))}
|
|
95
|
+
</>
|
|
96
|
+
]
|
|
94
97
|
: []
|
|
95
98
|
return nodes.concat(rows.map((row, i) => getChartCellValue(row, column, config, runtimeData, rightAxisItemsMap)))
|
|
96
99
|
})
|
|
@@ -6,6 +6,7 @@ import { displayDataAsText } from '@cdc/core/helpers/displayDataAsText'
|
|
|
6
6
|
import _ from 'lodash'
|
|
7
7
|
import { applyLegendToRow } from '@cdc/map/src/helpers/applyLegendToRow'
|
|
8
8
|
import { hashObj } from '@cdc/map/src/helpers'
|
|
9
|
+
import { getPatternForRow } from '@cdc/map/src/helpers/getPatternForRow'
|
|
9
10
|
|
|
10
11
|
type MapRowsProps = DataTableProps & {
|
|
11
12
|
rows: string[]
|
|
@@ -104,10 +105,27 @@ const mapCellArray = ({
|
|
|
104
105
|
type === 'bubble' && allowMapZoom && geoType === 'world' ? () => setFilteredCountryCode(row) : undefined
|
|
105
106
|
|
|
106
107
|
const validColor = legendColor && legendColor.length > 0 && !noColor
|
|
108
|
+
|
|
109
|
+
// Check for pattern information
|
|
110
|
+
const patternInfo = getPatternForRow(rowObj, config)
|
|
111
|
+
const mapId = config.runtime?.uniqueId || 'map'
|
|
112
|
+
|
|
107
113
|
return (
|
|
108
114
|
<div className='col-12'>
|
|
109
115
|
{validColor ? (
|
|
110
|
-
|
|
116
|
+
patternInfo ? (
|
|
117
|
+
<LegendShape
|
|
118
|
+
fill={legendColor[0]}
|
|
119
|
+
patternInfo={{
|
|
120
|
+
pattern: patternInfo.pattern,
|
|
121
|
+
patternId: `${mapId}--${patternInfo.dataKey}--${patternInfo.patternIndex}--table`,
|
|
122
|
+
size: patternInfo.size,
|
|
123
|
+
color: patternInfo.color
|
|
124
|
+
}}
|
|
125
|
+
/>
|
|
126
|
+
) : (
|
|
127
|
+
<LegendShape fill={legendColor[0]} />
|
|
128
|
+
)
|
|
111
129
|
) : (
|
|
112
130
|
<div className='d-inline-block me-2' style={{ width: '1rem', height: '1rem' }} />
|
|
113
131
|
)}
|
|
@@ -1,43 +1,69 @@
|
|
|
1
|
+
import { useRef } from 'react'
|
|
1
2
|
import Papa from 'papaparse'
|
|
2
3
|
import { publishAnalyticsEvent } from '../helpers/metrics/helpers'
|
|
4
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
3
5
|
|
|
4
6
|
type DownloadButtonProps = {
|
|
5
|
-
rawData:
|
|
7
|
+
rawData: any[]
|
|
6
8
|
fileName: string
|
|
7
9
|
headerColor: string
|
|
8
10
|
skipId: string | number
|
|
9
11
|
configUrl?: string
|
|
12
|
+
interactionLabel?: string
|
|
13
|
+
title?: string
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
const DownloadButton = ({ rawData, fileName, headerColor, skipId, configUrl }: DownloadButtonProps) => {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const DownloadButton = ({ rawData, fileName, headerColor, skipId, interactionLabel, configUrl, title, config }: DownloadButtonProps) => {
|
|
17
|
+
const linkRef = useRef<HTMLAnchorElement>(null)
|
|
18
|
+
|
|
19
|
+
const handleDownload = (event: React.MouseEvent<HTMLAnchorElement>) => {
|
|
20
|
+
event.preventDefault()
|
|
21
|
+
|
|
22
|
+
const csvData = Papa.unparse(rawData)
|
|
23
|
+
|
|
24
|
+
// Prepend a Byte Order Mark (BOM) to the CSV data.
|
|
25
|
+
// The BOM is a special marker that helps applications like Excel recognize the file as UTF-8 encoded.
|
|
26
|
+
// Adding the BOM ensures that Excel interprets special characters correctly.
|
|
27
|
+
const bom = '\uFEFF'
|
|
28
|
+
const utf8EncodedCsvData = new TextEncoder().encode(bom + csvData)
|
|
29
|
+
const blob = new Blob([utf8EncodedCsvData], { type: 'text/csv;charset=utf-8;' })
|
|
30
|
+
|
|
31
|
+
const url = URL.createObjectURL(blob)
|
|
20
32
|
|
|
21
|
-
const saveBlob = () => {
|
|
22
33
|
//@ts-ignore
|
|
23
34
|
if (typeof window.navigator.msSaveBlob === 'function') {
|
|
24
35
|
//@ts-ignore
|
|
25
36
|
navigator.msSaveBlob(blob, fileName)
|
|
37
|
+
} else {
|
|
38
|
+
const downloadLink = document.createElement('a')
|
|
39
|
+
downloadLink.href = url
|
|
40
|
+
downloadLink.download = fileName
|
|
41
|
+
document.body.appendChild(downloadLink)
|
|
42
|
+
downloadLink.click()
|
|
43
|
+
document.body.removeChild(downloadLink)
|
|
26
44
|
}
|
|
27
|
-
|
|
45
|
+
URL.revokeObjectURL(url)
|
|
46
|
+
publishAnalyticsEvent({
|
|
47
|
+
vizType: config.type,
|
|
48
|
+
vizSubType: getVizSubType(config),
|
|
49
|
+
eventType: 'data_downloaded',
|
|
50
|
+
eventAction: 'click',
|
|
51
|
+
eventLabel: interactionLabel || configUrl,
|
|
52
|
+
vizTitle: getVizTitle(config)
|
|
53
|
+
})
|
|
28
54
|
}
|
|
29
55
|
|
|
30
56
|
return (
|
|
31
57
|
<a
|
|
32
|
-
|
|
58
|
+
ref={linkRef}
|
|
33
59
|
type='button'
|
|
34
|
-
onClick={
|
|
35
|
-
href={URL.createObjectURL(blob)}
|
|
60
|
+
onClick={handleDownload}
|
|
36
61
|
aria-label='Download this data in a CSV file format.'
|
|
37
62
|
className={`${headerColor} no-border`}
|
|
38
63
|
id={`${skipId}`}
|
|
39
64
|
data-html2canvas-ignore
|
|
40
65
|
role='button'
|
|
66
|
+
style={{ cursor: 'pointer' }}
|
|
41
67
|
>
|
|
42
68
|
Download Data (CSV)
|
|
43
69
|
</a>
|