@cdc/core 4.25.7 → 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 +63 -11
- package/components/DataTable/DataTableStandAlone.tsx +4 -1
- package/components/DataTable/components/ChartHeader.tsx +58 -9
- package/components/DataTable/components/ExpandCollapse.tsx +21 -1
- package/components/DataTable/components/MapHeader.tsx +35 -7
- 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 +42 -13
- package/components/EditorPanel/DataTableEditor.tsx +10 -1
- 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 +35 -11
- package/components/Filters/components/Tabs.tsx +1 -0
- package/components/Footnotes/FootnotesStandAlone.tsx +2 -1
- package/components/Legend/Legend.Gradient.tsx +3 -6
- package/components/LegendShape.tsx +121 -3
- package/components/{MediaControls.jsx → MediaControls.tsx} +80 -16
- 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 -13
- 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/cove/number.ts +6 -2
- package/helpers/coveUpdateWorker.ts +15 -3
- package/helpers/events.ts +32 -0
- 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 +75 -0
- package/helpers/metrics/types.ts +82 -0
- 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 +62 -0
- 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 +86 -0
- package/helpers/ver/tests/4.25.9.test.ts +51 -0
- package/helpers/viewports.ts +2 -0
- package/hooks/useColorPalette.ts +79 -0
- package/package.json +12 -4
- package/styles/_button-section.scss +0 -2
- 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/ForecastingSeriesKey.ts +0 -1
- package/types/MarkupInclude.ts +6 -1
- package/types/MarkupVariable.ts +19 -0
- package/types/Series.ts +4 -0
- package/types/Table.ts +1 -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/events.js +0 -14
- 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'
|
|
@@ -52,9 +52,13 @@ export type DataTableProps = {
|
|
|
52
52
|
vizTitle?: string
|
|
53
53
|
// determines if columns should be wrapped in the table
|
|
54
54
|
wrapColumns?: boolean
|
|
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
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
58
62
|
const DataTable = (props: DataTableProps) => {
|
|
59
63
|
const {
|
|
60
64
|
columns,
|
|
@@ -71,7 +75,8 @@ const DataTable = (props: DataTableProps) => {
|
|
|
71
75
|
tableTitle,
|
|
72
76
|
viewport,
|
|
73
77
|
vizTitle,
|
|
74
|
-
wrapColumns
|
|
78
|
+
wrapColumns,
|
|
79
|
+
interactionLabel = ''
|
|
75
80
|
} = props
|
|
76
81
|
const runtimeData = useMemo(() => {
|
|
77
82
|
const data = removeNullColumns(parentRuntimeData)
|
|
@@ -94,6 +99,11 @@ const DataTable = (props: DataTableProps) => {
|
|
|
94
99
|
|
|
95
100
|
const [accessibilityLabel, setAccessibilityLabel] = useState('')
|
|
96
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
|
+
|
|
97
107
|
const isVertical = !(config.type === 'chart' && !config.table?.showVertical)
|
|
98
108
|
|
|
99
109
|
const rand = Math.random().toString(16).substr(2, 8)
|
|
@@ -276,9 +286,20 @@ const DataTable = (props: DataTableProps) => {
|
|
|
276
286
|
|
|
277
287
|
const childrenMatrix =
|
|
278
288
|
config.type === 'map'
|
|
279
|
-
? 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
|
+
})
|
|
280
299
|
: chartCellMatrix({ rows, ...props, runtimeData, isVertical, sortBy, hasRowType, viewport })
|
|
281
300
|
|
|
301
|
+
const useBottomExpandCollapse = config.table.showBottomCollapse && expanded && Array.isArray(childrenMatrix)
|
|
302
|
+
|
|
282
303
|
// If every value in a column is a number, record the column index so the header and cells can be right-aligned
|
|
283
304
|
const rightAlignedCols = childrenMatrix.length
|
|
284
305
|
? Object.fromEntries(
|
|
@@ -290,16 +311,19 @@ const DataTable = (props: DataTableProps) => {
|
|
|
290
311
|
)
|
|
291
312
|
: {}
|
|
292
313
|
|
|
314
|
+
const showCollapseButton = config.table.collapsible !== false && useBottomExpandCollapse
|
|
293
315
|
const TableMediaControls = ({ belowTable }) => {
|
|
294
316
|
const hasDownloadLink = config.table.download
|
|
295
317
|
return (
|
|
296
318
|
<MediaControls.Section classes={getMediaControlsClasses(belowTable, hasDownloadLink)}>
|
|
297
|
-
<MediaControls.Link config={config} dashboardDataConfig={dataConfig} />
|
|
319
|
+
<MediaControls.Link config={config} dashboardDataConfig={dataConfig} interactionLabel={interactionLabel} />
|
|
298
320
|
{hasDownloadLink && (
|
|
299
321
|
<DownloadButton
|
|
300
322
|
rawData={getDownloadData()}
|
|
301
323
|
fileName={`${vizTitle || 'data-table'}.csv`}
|
|
302
324
|
headerColor={headerColor}
|
|
325
|
+
interactionLabel={interactionLabel}
|
|
326
|
+
config={config}
|
|
303
327
|
/>
|
|
304
328
|
)}
|
|
305
329
|
</MediaControls.Section>
|
|
@@ -308,11 +332,21 @@ const DataTable = (props: DataTableProps) => {
|
|
|
308
332
|
|
|
309
333
|
return (
|
|
310
334
|
<ErrorBoundary component='DataTable'>
|
|
311
|
-
{!config.table.showDownloadLinkBelow &&
|
|
335
|
+
{!config.table.showDownloadLinkBelow && (
|
|
336
|
+
<div className='w-100 d-flex justify-content-end'>
|
|
337
|
+
<TableMediaControls />
|
|
338
|
+
</div>
|
|
339
|
+
)}
|
|
312
340
|
<section id={tabbingId.replace('#', '')} className={getClassNames()} aria-label={accessibilityLabel}>
|
|
313
341
|
<SkipTo skipId={skipId} skipMessage='Skip Data Table' />
|
|
314
342
|
{config.table.collapsible !== false && (
|
|
315
|
-
<ExpandCollapse
|
|
343
|
+
<ExpandCollapse
|
|
344
|
+
expanded={expanded}
|
|
345
|
+
setExpanded={setExpanded}
|
|
346
|
+
tableTitle={tableTitle}
|
|
347
|
+
config={config}
|
|
348
|
+
interactionLabel={interactionLabel}
|
|
349
|
+
/>
|
|
316
350
|
)}
|
|
317
351
|
<div className='table-container' style={limitHeight}>
|
|
318
352
|
<Table
|
|
@@ -333,6 +367,7 @@ const DataTable = (props: DataTableProps) => {
|
|
|
333
367
|
sortBy={sortBy}
|
|
334
368
|
setSortBy={setSortBy}
|
|
335
369
|
rightAlignedCols={rightAlignedCols}
|
|
370
|
+
interactionLabel={interactionLabel}
|
|
336
371
|
/>
|
|
337
372
|
) : (
|
|
338
373
|
<ChartHeader
|
|
@@ -344,12 +379,13 @@ const DataTable = (props: DataTableProps) => {
|
|
|
344
379
|
setSortBy={setSortBy}
|
|
345
380
|
viewport={viewport}
|
|
346
381
|
rightAlignedCols={rightAlignedCols}
|
|
382
|
+
interactionLabel={interactionLabel}
|
|
347
383
|
/>
|
|
348
384
|
)
|
|
349
385
|
}
|
|
350
386
|
tableOptions={{
|
|
351
|
-
className: `table table-striped table-width-unset ${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'
|
|
352
|
-
}`,
|
|
387
|
+
className: `table table-striped table-width-unset ${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'
|
|
388
|
+
}${isVertical ? '' : ' horizontal'}`,
|
|
353
389
|
'aria-live': 'assertive',
|
|
354
390
|
'aria-rowcount': config?.data?.length ? config.data.length : -1,
|
|
355
391
|
hidden: !expanded,
|
|
@@ -382,7 +418,18 @@ const DataTable = (props: DataTableProps) => {
|
|
|
382
418
|
)}
|
|
383
419
|
</div>
|
|
384
420
|
</section>
|
|
385
|
-
|
|
421
|
+
<div className={`w-100 d-flex ${showCollapseButton ? 'justify-content-between' : 'justify-content-end'}`}>
|
|
422
|
+
{showCollapseButton && (
|
|
423
|
+
<button
|
|
424
|
+
className='border-0 bg-transparent text-decoration-underline mt-2'
|
|
425
|
+
style={{ color: 'var(--colors-link-blue)', fontSize: '0.772rem', textUnderlineOffset: '6px' }}
|
|
426
|
+
onClick={() => setExpanded(false)}
|
|
427
|
+
>
|
|
428
|
+
- Collapse table
|
|
429
|
+
</button>
|
|
430
|
+
)}
|
|
431
|
+
{config.table.showDownloadLinkBelow && <TableMediaControls belowTable={true} />}
|
|
432
|
+
</div>
|
|
386
433
|
<div id={skipId} className='cdcdataviz-sr-only'>
|
|
387
434
|
Skipped data table.
|
|
388
435
|
</div>
|
|
@@ -394,7 +441,12 @@ const DataTable = (props: DataTableProps) => {
|
|
|
394
441
|
<ErrorBoundary component='DataTable'>
|
|
395
442
|
<section id={tabbingId.replace('#', '')} className={getClassNames()} aria-label={accessibilityLabel}>
|
|
396
443
|
<SkipTo skipId={skipId} skipMessage='Skip Data Table' />
|
|
397
|
-
<ExpandCollapse
|
|
444
|
+
<ExpandCollapse
|
|
445
|
+
expanded={expanded}
|
|
446
|
+
setExpanded={setExpanded}
|
|
447
|
+
tableTitle={tableTitle}
|
|
448
|
+
interactionLabel={interactionLabel}
|
|
449
|
+
/>
|
|
398
450
|
<div className='table-container' style={limitHeight}>
|
|
399
451
|
<Table
|
|
400
452
|
viewport={viewport}
|
|
@@ -18,6 +18,7 @@ type StandAloneProps = {
|
|
|
18
18
|
isEditor?: boolean
|
|
19
19
|
updateConfig?: (Visualization) => void
|
|
20
20
|
datasets?: Datasets
|
|
21
|
+
interactionLabel?: string
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
const DataTableStandAlone: React.FC<StandAloneProps> = ({
|
|
@@ -26,7 +27,8 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({
|
|
|
26
27
|
updateConfig,
|
|
27
28
|
viewport,
|
|
28
29
|
isEditor,
|
|
29
|
-
datasets
|
|
30
|
+
datasets,
|
|
31
|
+
interactionLabel = ''
|
|
30
32
|
}) => {
|
|
31
33
|
const [filteredData, setFilteredData] = useState<Record<string, any>[]>(
|
|
32
34
|
filterVizData(config.filters, config.formattedData || config.data)
|
|
@@ -69,6 +71,7 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({
|
|
|
69
71
|
tabbingId={visualizationKey}
|
|
70
72
|
tableTitle={config.table.label}
|
|
71
73
|
viewport={viewport || 'lg'}
|
|
74
|
+
interactionLabel={interactionLabel}
|
|
72
75
|
/>
|
|
73
76
|
<FootnotesStandAlone config={config.footnotes} filters={config.filters?.filter(f => f.filterFootnotes)} />
|
|
74
77
|
</>
|
|
@@ -6,8 +6,20 @@ import { SortIcon } from './SortIcon'
|
|
|
6
6
|
import { getNewSortBy } from '../helpers/getNewSortBy'
|
|
7
7
|
import parse from 'html-react-parser'
|
|
8
8
|
import { ChartConfig } from '@cdc/chart/src/types/ChartConfig'
|
|
9
|
+
import { publishAnalyticsEvent } from '../../../helpers/metrics/helpers'
|
|
10
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
9
11
|
|
|
10
|
-
type ChartHeaderProps = {
|
|
12
|
+
type ChartHeaderProps = {
|
|
13
|
+
data
|
|
14
|
+
isVertical
|
|
15
|
+
config
|
|
16
|
+
setSortBy
|
|
17
|
+
sortBy
|
|
18
|
+
hasRowType?
|
|
19
|
+
viewport
|
|
20
|
+
rightAlignedCols
|
|
21
|
+
interactionLabel: string
|
|
22
|
+
}
|
|
11
23
|
|
|
12
24
|
const ChartHeader = ({
|
|
13
25
|
data,
|
|
@@ -17,7 +29,8 @@ const ChartHeader = ({
|
|
|
17
29
|
sortBy,
|
|
18
30
|
hasRowType,
|
|
19
31
|
viewport,
|
|
20
|
-
rightAlignedCols
|
|
32
|
+
rightAlignedCols,
|
|
33
|
+
interactionLabel
|
|
21
34
|
}: ChartHeaderProps) => {
|
|
22
35
|
const groupBy = config.table?.groupBy
|
|
23
36
|
if (!data) return
|
|
@@ -49,9 +62,8 @@ const ChartHeader = ({
|
|
|
49
62
|
if (columnHeaderText === notApplicableText) return
|
|
50
63
|
|
|
51
64
|
return (
|
|
52
|
-
<span className='cdcdataviz-sr-only'>{`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${
|
|
53
|
-
|
|
54
|
-
} 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>
|
|
55
67
|
)
|
|
56
68
|
}
|
|
57
69
|
|
|
@@ -104,11 +116,29 @@ const ChartHeader = ({
|
|
|
104
116
|
scope='col'
|
|
105
117
|
onClick={() => {
|
|
106
118
|
if (hasRowType) return
|
|
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
|
+
})
|
|
107
128
|
setSortBy(newSortBy)
|
|
108
129
|
}}
|
|
109
130
|
onKeyDown={e => {
|
|
110
131
|
if (hasRowType) return
|
|
111
|
-
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
|
+
})
|
|
112
142
|
setSortBy(newSortBy)
|
|
113
143
|
}
|
|
114
144
|
}}
|
|
@@ -118,7 +148,7 @@ const ChartHeader = ({
|
|
|
118
148
|
: { 'aria-sort': 'descending' }
|
|
119
149
|
: null)}
|
|
120
150
|
>
|
|
121
|
-
<ColumnHeadingText text={text}
|
|
151
|
+
<ColumnHeadingText text={text} config={config} />
|
|
122
152
|
{isSortedCol && <SortIcon ascending={sortByAsc} />}
|
|
123
153
|
<ScreenReaderSortByText sortBy={sortBy} config={config} text={text} />
|
|
124
154
|
</th>
|
|
@@ -152,10 +182,29 @@ const ChartHeader = ({
|
|
|
152
182
|
role='columnheader'
|
|
153
183
|
scope='col'
|
|
154
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
|
+
})
|
|
155
195
|
setSortBy(newSortBy)
|
|
156
196
|
}}
|
|
157
197
|
onKeyDown={e => {
|
|
158
|
-
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
|
+
})
|
|
159
208
|
setSortBy(newSortBy)
|
|
160
209
|
}
|
|
161
210
|
}}
|
|
@@ -165,7 +214,7 @@ const ChartHeader = ({
|
|
|
165
214
|
: { 'aria-sort': 'descending' }
|
|
166
215
|
: null)}
|
|
167
216
|
>
|
|
168
|
-
<ColumnHeadingText text={text}
|
|
217
|
+
<ColumnHeadingText text={text} config={config} />
|
|
169
218
|
{isSortedCol && <SortIcon ascending={sortByAsc} />}
|
|
170
219
|
|
|
171
220
|
<ScreenReaderSortByText text={text} config={config} sortBy={sortBy} />
|
|
@@ -1,12 +1,32 @@
|
|
|
1
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
2
|
+
import { publishAnalyticsEvent } from '../../../helpers/metrics/helpers'
|
|
3
|
+
import { Visualization } from '../../../types/Visualization'
|
|
1
4
|
import Icon from '../../ui/Icon'
|
|
2
5
|
import parse from 'html-react-parser'
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
interface ExpandCollapseProps {
|
|
8
|
+
expanded: boolean
|
|
9
|
+
setExpanded: (expanded: boolean) => void
|
|
10
|
+
tableTitle: string
|
|
11
|
+
config?: Visualization
|
|
12
|
+
interactionLabel?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ExpandCollapse = ({ expanded, setExpanded, tableTitle, config, interactionLabel = '' }: ExpandCollapseProps) => {
|
|
5
16
|
return (
|
|
6
17
|
<div
|
|
7
18
|
role='button'
|
|
8
19
|
className={expanded ? 'data-table-heading p-3' : 'collapsed data-table-heading p-3'}
|
|
9
20
|
onClick={() => {
|
|
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
|
+
})
|
|
10
30
|
setExpanded(!expanded)
|
|
11
31
|
}}
|
|
12
32
|
tabIndex={0}
|
|
@@ -2,10 +2,13 @@ import { DataTableProps } from '../DataTable'
|
|
|
2
2
|
import ScreenReaderText from '../../elements/ScreenReaderText'
|
|
3
3
|
import { SortIcon } from './SortIcon'
|
|
4
4
|
import { getNewSortBy } from '../helpers/getNewSortBy'
|
|
5
|
+
import { publishAnalyticsEvent } from '../../../helpers/metrics/helpers'
|
|
6
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
5
7
|
|
|
6
8
|
type MapHeaderProps = DataTableProps & {
|
|
7
9
|
sortBy: { column; asc }
|
|
8
10
|
setSortBy: Function
|
|
11
|
+
interactionLabel: string
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
const ColumnHeadingText = ({ text, config }) => {
|
|
@@ -16,7 +19,15 @@ const ColumnHeadingText = ({ text, config }) => {
|
|
|
16
19
|
return text
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
const MapHeader = ({
|
|
22
|
+
const MapHeader = ({
|
|
23
|
+
columns,
|
|
24
|
+
config,
|
|
25
|
+
indexTitle,
|
|
26
|
+
sortBy,
|
|
27
|
+
setSortBy,
|
|
28
|
+
rightAlignedCols,
|
|
29
|
+
interactionLabel = ''
|
|
30
|
+
}: MapHeaderProps) => {
|
|
20
31
|
return (
|
|
21
32
|
<tr>
|
|
22
33
|
{Object.keys(columns)
|
|
@@ -46,10 +57,28 @@ const MapHeader = ({ columns, config, indexTitle, sortBy, setSortBy, rightAligne
|
|
|
46
57
|
role='columnheader'
|
|
47
58
|
scope='col'
|
|
48
59
|
onClick={() => {
|
|
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
|
+
})
|
|
49
69
|
setSortBy(newSortBy)
|
|
50
70
|
}}
|
|
51
71
|
onKeyDown={e => {
|
|
52
|
-
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
|
+
})
|
|
53
82
|
setSortBy(newSortBy)
|
|
54
83
|
}
|
|
55
84
|
}}
|
|
@@ -60,15 +89,14 @@ const MapHeader = ({ columns, config, indexTitle, sortBy, setSortBy, rightAligne
|
|
|
60
89
|
: { 'aria-sort': 'descending' }
|
|
61
90
|
: null)}
|
|
62
91
|
>
|
|
63
|
-
<ColumnHeadingText text={text} config={config}
|
|
92
|
+
<ColumnHeadingText text={text} config={config} />
|
|
64
93
|
<SortIcon ascending={sortByAsc} />
|
|
65
|
-
<span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${
|
|
66
|
-
|
|
67
|
-
} order`}</span>
|
|
94
|
+
<span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'
|
|
95
|
+
} order`}</span>
|
|
68
96
|
</th>
|
|
69
97
|
)
|
|
70
98
|
})}
|
|
71
|
-
</tr>
|
|
99
|
+
</tr >
|
|
72
100
|
)
|
|
73
101
|
}
|
|
74
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,40 +1,69 @@
|
|
|
1
|
+
import { useRef } from 'react'
|
|
1
2
|
import Papa from 'papaparse'
|
|
3
|
+
import { publishAnalyticsEvent } from '../helpers/metrics/helpers'
|
|
4
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
2
5
|
|
|
3
6
|
type DownloadButtonProps = {
|
|
4
|
-
rawData:
|
|
7
|
+
rawData: any[]
|
|
5
8
|
fileName: string
|
|
6
9
|
headerColor: string
|
|
7
10
|
skipId: string | number
|
|
11
|
+
configUrl?: string
|
|
12
|
+
interactionLabel?: string
|
|
13
|
+
title?: string
|
|
8
14
|
}
|
|
9
15
|
|
|
10
|
-
const DownloadButton = ({ rawData, fileName, headerColor, skipId }: DownloadButtonProps) => {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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)
|
|
18
32
|
|
|
19
|
-
const saveBlob = () => {
|
|
20
33
|
//@ts-ignore
|
|
21
34
|
if (typeof window.navigator.msSaveBlob === 'function') {
|
|
22
35
|
//@ts-ignore
|
|
23
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)
|
|
24
44
|
}
|
|
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
|
+
})
|
|
25
54
|
}
|
|
26
55
|
|
|
27
56
|
return (
|
|
28
57
|
<a
|
|
29
|
-
|
|
58
|
+
ref={linkRef}
|
|
30
59
|
type='button'
|
|
31
|
-
onClick={
|
|
32
|
-
href={URL.createObjectURL(blob)}
|
|
60
|
+
onClick={handleDownload}
|
|
33
61
|
aria-label='Download this data in a CSV file format.'
|
|
34
62
|
className={`${headerColor} no-border`}
|
|
35
63
|
id={`${skipId}`}
|
|
36
64
|
data-html2canvas-ignore
|
|
37
65
|
role='button'
|
|
66
|
+
style={{ cursor: 'pointer' }}
|
|
38
67
|
>
|
|
39
68
|
Download Data (CSV)
|
|
40
69
|
</a>
|