@cdc/chart 4.22.10 → 4.22.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/cdcchart.js +4 -4
- package/examples/age-adjusted-rates.json +1486 -1218
- package/examples/case-rate-example-config.json +1 -1
- package/examples/covid-confidence-example-config.json +33 -33
- package/examples/covid-example-config.json +34 -34
- package/examples/covid-example-data-confidence.json +30 -30
- package/examples/covid-example-data.json +20 -20
- package/examples/cutoff-example-config.json +36 -36
- package/examples/cutoff-example-data.json +36 -36
- package/examples/date-exclusions-config.json +1 -1
- package/examples/dynamic-legends.json +124 -124
- package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart-with-numbers-on-bar.json +191 -197
- package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +230 -240
- package/examples/gallery/bar-chart-horizontal/horizontal-stacked.json +239 -247
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +136 -136
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +79 -79
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +80 -80
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +67 -67
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +110 -110
- package/examples/gallery/lollipop/lollipop-style-horizontal.json +215 -219
- package/examples/gallery/paired-bar/paired-bar-chart.json +195 -195
- package/examples/horizontal-chart.json +35 -35
- package/examples/horizontal-stacked-bar-chart.json +34 -34
- package/examples/line-chart.json +75 -75
- package/examples/paired-bar-data.json +16 -14
- package/examples/paired-bar-example.json +48 -48
- package/examples/paired-bar-formatted.json +36 -36
- package/examples/planet-chart-horizontal-example-config.json +33 -33
- package/examples/planet-combo-example-config.json +34 -31
- package/examples/planet-example-config.json +35 -33
- package/examples/planet-example-data.json +56 -56
- package/examples/planet-pie-example-config.json +28 -28
- package/examples/private/filters.json +170 -0
- package/examples/private/line-test-data.json +21 -21
- package/examples/private/line-test-two.json +209 -215
- package/examples/private/line-test.json +101 -101
- package/examples/private/new.json +48800 -0
- package/examples/private/shawn.json +1105 -1295
- package/examples/private/test.json +10123 -10123
- package/examples/private/yaxis-test.json +4 -3
- package/examples/private/yaxis.json +26 -26
- package/examples/stacked-vertical-bar-example.json +1 -1
- package/examples/temp-example-config.json +61 -54
- package/examples/temp-example-data.json +1 -1
- package/package.json +2 -2
- package/src/CdcChart.tsx +339 -380
- package/src/components/BarChart.tsx +425 -469
- package/src/components/DataTable.tsx +164 -195
- package/src/components/EditorPanel.js +1009 -710
- package/src/components/Legend.js +279 -329
- package/src/components/LineChart.tsx +90 -79
- package/src/components/LinearChart.tsx +376 -434
- package/src/components/PairedBarChart.tsx +197 -213
- package/src/components/PieChart.tsx +95 -151
- package/src/components/SparkLine.js +179 -201
- package/src/components/useIntersectionObserver.tsx +17 -20
- package/src/context.tsx +3 -3
- package/src/data/initial-state.js +37 -16
- package/src/hooks/useActiveElement.js +13 -13
- package/src/hooks/useChartClasses.js +34 -28
- package/src/hooks/useColorPalette.ts +56 -63
- package/src/hooks/useLegendClasses.js +18 -10
- package/src/hooks/useReduceData.ts +62 -78
- package/src/hooks/useRightAxis.js +25 -0
- package/src/hooks/useTopAxis.js +6 -0
- package/src/index.html +45 -45
- package/src/index.tsx +13 -16
- package/src/scss/DataTable.scss +5 -4
- package/src/scss/editor-panel.scss +71 -69
- package/src/scss/main.scss +157 -114
- package/src/scss/variables.scss +1 -1
|
@@ -1,244 +1,213 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
useSortBy,
|
|
11
|
-
useResizeColumns,
|
|
12
|
-
useBlockLayout
|
|
13
|
-
} from 'react-table';
|
|
14
|
-
import Papa from 'papaparse';
|
|
15
|
-
import { Base64 } from 'js-base64';
|
|
16
|
-
|
|
17
|
-
import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
|
|
18
|
-
import LegendCircle from '@cdc/core/components/LegendCircle';
|
|
19
|
-
|
|
20
|
-
import Context from '../context';
|
|
1
|
+
import React, { useContext, useEffect, useState, useMemo, memo, Fragment } from 'react'
|
|
2
|
+
import { useTable, useSortBy, useResizeColumns, useBlockLayout } from 'react-table'
|
|
3
|
+
import Papa from 'papaparse'
|
|
4
|
+
import { Base64 } from 'js-base64'
|
|
5
|
+
|
|
6
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
7
|
+
import LegendCircle from '@cdc/core/components/LegendCircle'
|
|
8
|
+
|
|
9
|
+
import Context from '../context'
|
|
21
10
|
|
|
22
11
|
export default function DataTable() {
|
|
23
|
-
const { rawData, transformedData: data, config, colorScale, parseDate, formatDate, formatNumber:numberFormatter, colorPalettes } = useContext<any>(Context)
|
|
12
|
+
const { rawData, transformedData: data, config, colorScale, parseDate, formatDate, formatNumber: numberFormatter, colorPalettes } = useContext<any>(Context)
|
|
24
13
|
|
|
25
|
-
const legendGlyphSize = 15
|
|
26
|
-
const legendGlyphSizeHalf = legendGlyphSize / 2
|
|
27
|
-
const section = config.orientation ==='horizontal' ? 'yAxis' :'xAxis'
|
|
28
|
-
const [tableExpanded, setTableExpanded] = useState<boolean>(config.table.expanded)
|
|
29
|
-
const [accessibilityLabel, setAccessibilityLabel] = useState('')
|
|
14
|
+
const legendGlyphSize = 15
|
|
15
|
+
const legendGlyphSizeHalf = legendGlyphSize / 2
|
|
16
|
+
const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
|
|
17
|
+
const [tableExpanded, setTableExpanded] = useState<boolean>(config.table.expanded)
|
|
18
|
+
const [accessibilityLabel, setAccessibilityLabel] = useState('')
|
|
30
19
|
|
|
31
20
|
const DownloadButton = ({ data }: any) => {
|
|
32
|
-
const fileName = `${config.title.substring(0, 50)}.csv
|
|
21
|
+
const fileName = `${config.title.substring(0, 50)}.csv`
|
|
33
22
|
|
|
34
|
-
const csvData = Papa.unparse(data)
|
|
23
|
+
const csvData = Papa.unparse(data)
|
|
35
24
|
|
|
36
25
|
const saveBlob = () => {
|
|
37
26
|
//@ts-ignore
|
|
38
27
|
if (typeof window.navigator.msSaveBlob === 'function') {
|
|
39
|
-
const dataBlob = new Blob([csvData], { type:
|
|
28
|
+
const dataBlob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' })
|
|
40
29
|
//@ts-ignore
|
|
41
|
-
window.navigator.msSaveBlob(dataBlob, fileName)
|
|
30
|
+
window.navigator.msSaveBlob(dataBlob, fileName)
|
|
42
31
|
}
|
|
43
32
|
}
|
|
44
33
|
|
|
45
34
|
return (
|
|
46
|
-
<a
|
|
47
|
-
download={fileName}
|
|
48
|
-
onClick={saveBlob}
|
|
49
|
-
href={`data:text/csv;base64,${Base64.encode(csvData)}`}
|
|
50
|
-
aria-label="Download this data in a CSV file format."
|
|
51
|
-
className={`btn btn-download no-border`}
|
|
52
|
-
>
|
|
35
|
+
<a download={fileName} onClick={saveBlob} href={`data:text/csv;base64,${Base64.encode(csvData)}`} aria-label='Download this data in a CSV file format.' className={`btn btn-download no-border`}>
|
|
53
36
|
Download Data (CSV)
|
|
54
37
|
</a>
|
|
55
38
|
)
|
|
56
|
-
}
|
|
39
|
+
}
|
|
57
40
|
|
|
58
41
|
// Creates columns structure for the table
|
|
59
42
|
const tableColumns = useMemo(() => {
|
|
60
|
-
const newTableColumns =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
43
|
+
const newTableColumns =
|
|
44
|
+
config.visualizationType === 'Pie'
|
|
45
|
+
? []
|
|
46
|
+
: [
|
|
47
|
+
{
|
|
48
|
+
Header: '',
|
|
49
|
+
Cell: ({ row }) => {
|
|
50
|
+
const seriesLabel = config.runtime.seriesLabels ? config.runtime.seriesLabels[row.original] : row.original
|
|
51
|
+
return (
|
|
52
|
+
<Fragment>
|
|
53
|
+
{config.visualizationType !== 'Pie' && (
|
|
54
|
+
<LegendCircle
|
|
55
|
+
fill={
|
|
56
|
+
// non dynamic leged
|
|
57
|
+
!config.legend.dynamicLegend
|
|
58
|
+
? colorScale(seriesLabel)
|
|
59
|
+
: // dynamic legend
|
|
60
|
+
config.legend.dynamicLegend
|
|
61
|
+
? colorPalettes[config.palette][row.index]
|
|
62
|
+
: // fallback
|
|
63
|
+
'#000'
|
|
64
|
+
}
|
|
65
|
+
/>
|
|
66
|
+
)}
|
|
67
|
+
<span>{seriesLabel}</span>
|
|
68
|
+
</Fragment>
|
|
69
|
+
)
|
|
70
|
+
},
|
|
71
|
+
id: 'series-label'
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
data.forEach(d => {
|
|
76
|
+
const newCol = {
|
|
77
|
+
Header: config.runtime[section].type === 'date' ? formatDate(parseDate(d[config.runtime.originalXAxis.dataKey])) : d[config.runtime.originalXAxis.dataKey],
|
|
78
|
+
Cell: ({ row }) => {
|
|
79
|
+
return <>{numberFormatter(d[row.original])}</>
|
|
80
|
+
},
|
|
81
|
+
id: d[config.runtime.originalXAxis.dataKey],
|
|
82
|
+
canSort: true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
newTableColumns.push(newCol)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
return newTableColumns
|
|
89
|
+
}, [config, colorScale])
|
|
90
|
+
|
|
91
|
+
const tableData = useMemo(() => (config.visualizationType === 'Pie' ? [config.yAxis.dataKey] : config.runtime.seriesKeys), [config.runtime.seriesKeys])
|
|
108
92
|
|
|
109
93
|
// Change accessibility label depending on expanded status
|
|
110
94
|
useEffect(() => {
|
|
111
|
-
const expandedLabel = 'Accessible data table.'
|
|
112
|
-
const collapsedLabel = 'Accessible data table. This table is currently collapsed visually but can still be read using a screen reader.'
|
|
95
|
+
const expandedLabel = 'Accessible data table.'
|
|
96
|
+
const collapsedLabel = 'Accessible data table. This table is currently collapsed visually but can still be read using a screen reader.'
|
|
113
97
|
|
|
114
98
|
if (tableExpanded === true && accessibilityLabel !== expandedLabel) {
|
|
115
|
-
setAccessibilityLabel(expandedLabel)
|
|
99
|
+
setAccessibilityLabel(expandedLabel)
|
|
116
100
|
}
|
|
117
101
|
|
|
118
102
|
if (tableExpanded === false && accessibilityLabel !== collapsedLabel) {
|
|
119
|
-
setAccessibilityLabel(collapsedLabel)
|
|
103
|
+
setAccessibilityLabel(collapsedLabel)
|
|
120
104
|
}
|
|
121
105
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
122
|
-
}, [tableExpanded])
|
|
106
|
+
}, [tableExpanded])
|
|
123
107
|
|
|
124
108
|
const defaultColumn = useMemo(
|
|
125
109
|
() => ({
|
|
126
110
|
minWidth: 150,
|
|
127
111
|
width: 200,
|
|
128
|
-
maxWidth: 400
|
|
112
|
+
maxWidth: 400
|
|
129
113
|
}),
|
|
130
114
|
[]
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
const {
|
|
134
|
-
getTableProps,
|
|
135
|
-
getTableBodyProps,
|
|
136
|
-
headerGroups,
|
|
137
|
-
rows,
|
|
138
|
-
prepareRow,
|
|
139
|
-
} = useTable({ columns: tableColumns, data: tableData, defaultColumn }, useSortBy, useBlockLayout, useResizeColumns);
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable({ columns: tableColumns, data: tableData, defaultColumn }, useSortBy, useBlockLayout, useResizeColumns)
|
|
140
118
|
return (
|
|
141
|
-
<ErrorBoundary component=
|
|
142
|
-
<section id={config?.title ? `dataTableSection__${config?.title.replace(/\s/g, '')}` : `dataTableSection`}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
{
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
119
|
+
<ErrorBoundary component='DataTable'>
|
|
120
|
+
<section id={config?.title ? `dataTableSection__${config?.title.replace(/\s/g, '')}` : `dataTableSection`} className={`data-table-container`} aria-label={accessibilityLabel}>
|
|
121
|
+
<div
|
|
122
|
+
role='button'
|
|
123
|
+
className={tableExpanded ? 'data-table-heading' : 'collapsed data-table-heading'}
|
|
124
|
+
tabIndex={0}
|
|
125
|
+
onClick={() => {
|
|
126
|
+
setTableExpanded(!tableExpanded)
|
|
127
|
+
}}
|
|
128
|
+
onKeyDown={e => {
|
|
129
|
+
if (e.keyCode === 13) {
|
|
130
|
+
setTableExpanded(!tableExpanded)
|
|
131
|
+
}
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
{config.table.label}
|
|
135
|
+
</div>
|
|
136
|
+
<div className='table-container' hidden={!tableExpanded} style={{ maxHeight: config.table.limitHeight && `${config.table.height}px`, overflowY: 'scroll' }}>
|
|
137
|
+
<table className={tableExpanded ? 'data-table' : 'data-table cdcdataviz-sr-only'} {...getTableProps()} aria-rowcount={config?.series?.length ? config?.series?.length : '-1'}>
|
|
138
|
+
<caption className='cdcdataviz-sr-only'>{config.table.caption ? config.table.caption : ''}</caption>
|
|
139
|
+
<caption className='visually-hidden'>{config.table.label}</caption>
|
|
140
|
+
<thead>
|
|
141
|
+
{headerGroups.map((headerGroup, index) => (
|
|
142
|
+
<tr {...headerGroup.getHeaderGroupProps()} key={`headerGroups--${index}`}>
|
|
143
|
+
{headerGroup.headers.map((column, index) => (
|
|
144
|
+
<th
|
|
145
|
+
tabIndex='0'
|
|
146
|
+
title={column.Header}
|
|
147
|
+
key={`trth--${index}`}
|
|
148
|
+
role='columnheader'
|
|
149
|
+
scope='col'
|
|
150
|
+
{...column.getHeaderProps(column.getSortByToggleProps())}
|
|
151
|
+
className={column.isSorted ? (column.isSortedDesc ? 'sort sort-desc' : 'sort sort-asc') : 'sort'}
|
|
152
|
+
{...(column.isSorted ? (column.isSortedDesc ? { 'aria-sort': 'descending' } : { 'aria-sort': 'ascending' }) : null)}
|
|
153
|
+
>
|
|
154
|
+
{index === 0 ? (config.table.indexLabel ? config.table.indexLabel : column.render('Header')) : column.render('Header')}
|
|
155
|
+
<button>
|
|
156
|
+
<span className='cdcdataviz-sr-only'>{`Sort by ${typeof column.render('Header') === 'string' ? column.render('Header').toLowerCase() : column.render('Header')} in ${column.isSorted ? (column.isSortedDesc ? 'descending' : 'ascending') : 'no'} `} order</span>
|
|
157
|
+
</button>
|
|
158
|
+
<div {...column.getResizerProps()} className='resizer' />
|
|
159
|
+
</th>
|
|
160
|
+
))}
|
|
161
|
+
</tr>
|
|
162
|
+
))}
|
|
163
|
+
</thead>
|
|
164
|
+
<tbody {...getTableBodyProps()}>
|
|
165
|
+
{rows.map((row, index) => {
|
|
166
|
+
prepareRow(row)
|
|
167
|
+
return (
|
|
168
|
+
<tr {...row.getRowProps()} key={`tbody__tr-${index}`}>
|
|
169
|
+
{row.cells.map((cell, index) => {
|
|
170
|
+
return (
|
|
171
|
+
<td tabIndex='0' {...cell.getCellProps()} key={`tbody__tr__td-${index}`} role='gridcell'>
|
|
172
|
+
{cell.render('Cell')}
|
|
173
|
+
</td>
|
|
174
|
+
)
|
|
175
|
+
})}
|
|
189
176
|
</tr>
|
|
190
|
-
)
|
|
177
|
+
)
|
|
178
|
+
})}
|
|
179
|
+
</tbody>
|
|
180
|
+
</table>
|
|
181
|
+
{config.regions && config.regions.length > 0 ? (
|
|
182
|
+
<table className='region-table data-table'>
|
|
183
|
+
<caption className='visually-hidden'>Table of the highlighted regions in the visualization</caption>
|
|
184
|
+
<thead>
|
|
185
|
+
<tr>
|
|
186
|
+
<th>Region Name</th>
|
|
187
|
+
<th>Start Date</th>
|
|
188
|
+
<th>End Date</th>
|
|
189
|
+
</tr>
|
|
191
190
|
</thead>
|
|
192
|
-
<tbody
|
|
193
|
-
{
|
|
194
|
-
|
|
191
|
+
<tbody>
|
|
192
|
+
{config.regions.map((region, index) => {
|
|
193
|
+
if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
|
|
194
|
+
|
|
195
195
|
return (
|
|
196
|
-
<tr
|
|
197
|
-
{
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
tabIndex="0"
|
|
201
|
-
{...cell.getCellProps()}
|
|
202
|
-
key={`tbody__tr__td-${index}`}
|
|
203
|
-
role="gridcell">
|
|
204
|
-
{ cell.render('Cell') }
|
|
205
|
-
</td>
|
|
206
|
-
)
|
|
207
|
-
}
|
|
208
|
-
)}
|
|
196
|
+
<tr key={`row-${region.label}--${index}`}>
|
|
197
|
+
<td>{region.label}</td>
|
|
198
|
+
<td>{formatDate(parseDate(region.from))}</td>
|
|
199
|
+
<td>{formatDate(parseDate(region.to))}</td>
|
|
209
200
|
</tr>
|
|
210
|
-
)
|
|
201
|
+
)
|
|
211
202
|
})}
|
|
212
203
|
</tbody>
|
|
213
204
|
</table>
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
<th>Region Name</th>
|
|
220
|
-
<th>Start Date</th>
|
|
221
|
-
<th>End Date</th>
|
|
222
|
-
</tr>
|
|
223
|
-
</thead>
|
|
224
|
-
<tbody>
|
|
225
|
-
{config.regions.map((region,index) => {
|
|
226
|
-
if(!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
|
|
227
|
-
|
|
228
|
-
return (
|
|
229
|
-
<tr key={`row-${region.label}--${index}`}>
|
|
230
|
-
<td>{region.label}</td>
|
|
231
|
-
<td>{formatDate(parseDate(region.from))}</td>
|
|
232
|
-
<td>{formatDate(parseDate(region.to))}</td>
|
|
233
|
-
</tr>
|
|
234
|
-
)
|
|
235
|
-
})}
|
|
236
|
-
</tbody>
|
|
237
|
-
</table>
|
|
238
|
-
) : ''}
|
|
239
|
-
</div>
|
|
240
|
-
{config.table.download && <DownloadButton data={rawData} />}
|
|
205
|
+
) : (
|
|
206
|
+
''
|
|
207
|
+
)}
|
|
208
|
+
</div>
|
|
209
|
+
{config.table.download && <DownloadButton data={rawData} />}
|
|
241
210
|
</section>
|
|
242
211
|
</ErrorBoundary>
|
|
243
|
-
)
|
|
212
|
+
)
|
|
244
213
|
}
|