@cdc/chart 4.23.8 → 4.23.10-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/cdcchart.js +43990 -44283
  2. package/examples/feature/__data__/area-chart-date-apple.json +1 -5073
  3. package/examples/feature/area/area-chart-date-apple.json +73 -10316
  4. package/examples/feature/area/area-chart-date-city-temperature.json +204 -80
  5. package/examples/{private/confidence_interval_test.json → feature/area/area-chart-stacked.json} +65 -74
  6. package/examples/feature/bar/lollipop.json +156 -0
  7. package/examples/feature/combo/planet-combo-example-config.json +99 -9
  8. package/examples/feature/filters/bar-filter.json +5027 -0
  9. package/examples/feature/legend-highlights/highlights.json +567 -0
  10. package/examples/private/TESTING.json +0 -0
  11. package/examples/private/forest-plot.json +356 -0
  12. package/examples/private/{tooltip-issue.json → full.json} +25288 -25239
  13. package/examples/private/missing-color.json +333 -0
  14. package/index.html +30 -8
  15. package/package.json +3 -2
  16. package/src/{CdcChart.jsx → CdcChart.tsx} +81 -74
  17. package/src/_stories/Chart.stories.tsx +188 -0
  18. package/src/components/AreaChart.Stacked.jsx +73 -0
  19. package/src/components/AreaChart.jsx +24 -26
  20. package/src/components/BarChart.StackedVertical.jsx +2 -0
  21. package/src/components/DeviationBar.jsx +67 -13
  22. package/src/components/EditorPanel.jsx +493 -454
  23. package/src/components/Forecasting.jsx +5 -5
  24. package/src/components/ForestPlotSettings.jsx +5 -6
  25. package/src/components/Legend.jsx +18 -9
  26. package/src/components/LineChart.Circle.tsx +102 -0
  27. package/src/components/{LineChart.jsx → LineChart.tsx} +9 -48
  28. package/src/components/LinearChart.jsx +460 -443
  29. package/src/components/PieChart.jsx +54 -25
  30. package/src/components/Series.jsx +63 -17
  31. package/src/components/SparkLine.jsx +7 -19
  32. package/src/data/initial-state.js +10 -1
  33. package/src/hooks/useBarChart.js +1 -1
  34. package/src/hooks/useEditorPermissions.js +87 -24
  35. package/src/hooks/useLegendClasses.js +14 -11
  36. package/src/hooks/useReduceData.js +6 -1
  37. package/src/hooks/useScales.js +4 -4
  38. package/src/hooks/useTooltip.jsx +21 -8
  39. package/src/scss/legend.scss +206 -0
  40. package/src/scss/main.scss +25 -24
  41. package/src/components/DataTable.jsx +0 -374
  42. /package/src/{components → hooks}/useIntersectionObserver.jsx +0 -0
@@ -1,374 +0,0 @@
1
- import React, { useContext, useEffect, useState, useMemo } from 'react'
2
- import { useTable, useSortBy, useResizeColumns, useBlockLayout } from 'react-table'
3
- import Papa from 'papaparse'
4
- import { Base64 } from 'js-base64'
5
- import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
6
-
7
- import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
8
- import LegendCircle from '@cdc/core/components/LegendCircle'
9
- import Icon from '@cdc/core/components/ui/Icon'
10
- import { DataTransform } from '@cdc/core/helpers/DataTransform'
11
-
12
- import ConfigContext from '../ConfigContext'
13
-
14
- import MediaControls from '@cdc/core/components/MediaControls'
15
-
16
- export default function DataTable() {
17
- const { rawData, tableData: data, config, colorScale, parseDate, formatDate, formatNumber: numberFormatter, colorPalettes, currentViewport } = useContext(ConfigContext)
18
-
19
- const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
20
- const [tableExpanded, setTableExpanded] = useState(config.table.expanded)
21
- const [accessibilityLabel, setAccessibilityLabel] = useState('')
22
- const isLegendBottom = ['sm', 'xs', 'xxs'].includes(currentViewport)
23
- const transform = new DataTransform()
24
-
25
- const DownloadButton = ({ data }, type) => {
26
- const fileName = `${config.title.substring(0, 50)}.csv`
27
-
28
- const csvData = Papa.unparse(data)
29
-
30
- const saveBlob = () => {
31
- //@ts-ignore
32
- if (typeof window.navigator.msSaveBlob === 'function') {
33
- const dataBlob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' })
34
- //@ts-ignore
35
- window.navigator.msSaveBlob(dataBlob, fileName)
36
- }
37
- }
38
-
39
- // - trying to eliminate console error that occurs if formatted with prettier
40
- // prettier-ignore
41
- switch (type) {
42
- case 'download':
43
- return (<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 margin-sm`}>Download Data (CSV)</a>)
44
- default:
45
- return (<a download={fileName} onClick={saveBlob} href={`data:text/csv;base64,${Base64.encode(csvData)}`} aria-label='Download this data in a CSV file format.' className={`no-border`}>Download Data (CSV)</a>)
46
- }
47
- }
48
-
49
- // Creates columns structure for the table
50
- const tableColumns = useMemo(() => {
51
- const newTableColumns =
52
- config.visualizationType === 'Pie'
53
- ? []
54
- : config.visualizationType === 'Box Plot'
55
- ? [
56
- {
57
- Header: 'Measures',
58
- Cell: props => {
59
- const resolveName = () => {
60
- let {
61
- boxplot: { labels }
62
- } = config
63
- const columnLookup = {
64
- columnMean: labels.mean,
65
- columnMax: labels.maximum,
66
- columnMin: labels.minimum,
67
- columnIqr: labels.iqr,
68
- columnCategory: 'Category',
69
- columnMedian: labels.median,
70
- columnFirstQuartile: labels.q1,
71
- columnThirdQuartile: labels.q3,
72
- columnOutliers: labels.outliers,
73
- values: labels.values,
74
- columnTotal: labels.total,
75
- columnSd: 'Standard Deviation',
76
- nonOutlierValues: 'Non Outliers',
77
- columnLowerBounds: labels.lowerBounds,
78
- columnUpperBounds: labels.upperBounds
79
- }
80
-
81
- let resolvedName = columnLookup[props.row.original[0]]
82
-
83
- return resolvedName
84
- }
85
-
86
- return resolveName()
87
- }
88
- }
89
- ]
90
- : [
91
- {
92
- Header: ' ',
93
- Cell: ({ row }) => {
94
- const getSeriesLabel = () => {
95
- let userUpdatedSeriesName = config.series.filter(series => series.dataKey === row.original)?.[0]?.name
96
-
97
- if (userUpdatedSeriesName) return userUpdatedSeriesName
98
- if (config.runtimeSeriesLabels) return config.runtime.seriesLabels[row.original]
99
- return row.original
100
- }
101
- return (
102
- <>
103
- {config.visualizationType !== 'Pie' && (
104
- <LegendCircle
105
- fill={
106
- // non-dynamic legend
107
- !config.legend.dynamicLegend && config.visualizationType !== 'Forecasting'
108
- ? colorScale(getSeriesLabel())
109
- : config.legend.dynamicLegend
110
- ? colorPalettes[config.palette][row.index]
111
- : // fallback
112
- '#000'
113
- }
114
- />
115
- )}
116
- <span>{getSeriesLabel()}</span>
117
- </>
118
- )
119
- },
120
- id: 'series-label',
121
- sortType: 'custom',
122
- canSort: true
123
- }
124
- ]
125
- if (config.visualizationType !== 'Box Plot') {
126
- data.forEach((d, index) => {
127
- const resolveTableHeader = () => {
128
- if (config.runtime[section].type === 'date') return formatDate(parseDate(d[config.runtime.originalXAxis.dataKey]))
129
- if (config.runtime[section].type === 'continuous') return numberFormatter(d[config.runtime.originalXAxis.dataKey], 'bottom')
130
- return d[config.runtime.originalXAxis.dataKey]
131
- }
132
- const newCol = {
133
- Header: resolveTableHeader(),
134
- Cell: ({ row }) => {
135
- let leftAxisItems = config.series.filter(item => item?.axis === 'Left')
136
- let rightAxisItems = config.series.filter(item => item?.axis === 'Right')
137
- let resolvedAxis = ''
138
-
139
- leftAxisItems.map(leftSeriesItem => {
140
- if (leftSeriesItem.dataKey === row.original) resolvedAxis = 'left'
141
- })
142
-
143
- rightAxisItems.map(rightSeriesItem => {
144
- if (rightSeriesItem.dataKey === row.original) resolvedAxis = 'right'
145
- })
146
-
147
- if (config.visualizationType !== 'Combo') resolvedAxis = 'left'
148
-
149
- return <>{numberFormatter(d[row.original], resolvedAxis)}</>
150
- },
151
- id: `${d[config.runtime.originalXAxis.dataKey]}--${index}`,
152
- sortType: 'custom',
153
- canSort: true
154
- }
155
-
156
- newTableColumns.push(newCol)
157
- })
158
- }
159
-
160
- if (config.visualizationType === 'Box Plot') {
161
- config.boxplot.tableData.map((plot, index) => {
162
- const newCol = {
163
- Header: plot.columnCategory,
164
- Cell: props => {
165
- let resolveCell = () => {
166
- if (Number(props.row.id) === 0) return true
167
- if (Number(props.row.id) === 1) return plot.columnMax
168
- if (Number(props.row.id) === 2) return plot.columnThirdQuartile
169
- if (Number(props.row.id) === 3) return plot.columnMedian
170
- if (Number(props.row.id) === 4) return plot.columnFirstQuartile
171
- if (Number(props.row.id) === 5) return plot.columnMin
172
- if (Number(props.row.id) === 6) return plot.columnTotal
173
- if (Number(props.row.id) === 7) return plot.columnSd
174
- if (Number(props.row.id) === 8) return plot.columnMean
175
- if (Number(props.row.id) === 9) return plot.columnOutliers.length > 0 ? plot.columnOutliers.toString() : '-'
176
- if (Number(props.row.id) === 10) return plot.values.length > 0 ? plot.values.toString() : '-'
177
- return <p>-</p>
178
- }
179
- return resolveCell()
180
- },
181
- id: `${index}`,
182
- canSort: false
183
- }
184
-
185
- return newTableColumns.push(newCol)
186
- })
187
- }
188
-
189
- return newTableColumns
190
- }, [config, colorScale]) // eslint-disable-line
191
-
192
- // prettier-ignore
193
- const tableData = useMemo(() => (
194
- config.visualizationType === 'Pie'
195
- ? [config.yAxis.dataKey]
196
- : config.visualizationType === 'Box Plot'
197
- ? Object.entries(config.boxplot.tableData[0])
198
- : config.runtime.seriesKeys),
199
- [config.runtime.seriesKeys]) // eslint-disable-line
200
-
201
- // Change accessibility label depending on expanded status
202
- useEffect(() => {
203
- const expandedLabel = 'Accessible data table.'
204
- const collapsedLabel = 'Accessible data table. This table is currently collapsed visually but can still be read using a screen reader.'
205
-
206
- if (tableExpanded === true && accessibilityLabel !== expandedLabel) {
207
- setAccessibilityLabel(expandedLabel)
208
- }
209
-
210
- if (tableExpanded === false && accessibilityLabel !== collapsedLabel) {
211
- setAccessibilityLabel(collapsedLabel)
212
- }
213
- // eslint-disable-next-line react-hooks/exhaustive-deps
214
- }, [tableExpanded])
215
-
216
- const defaultColumn = useMemo(
217
- () => ({
218
- minWidth: 150,
219
- width: 200,
220
- maxWidth: 400
221
- }),
222
- []
223
- )
224
- const upIcon = (
225
- <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 5'>
226
- <path d='M0 5l5-5 5 5z' />
227
- </svg>
228
- )
229
- const downIcon = (
230
- <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 5'>
231
- <path d='M0 0l5 5 5-5z' />
232
- </svg>
233
- )
234
-
235
- const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable(
236
- {
237
- columns: tableColumns,
238
- data: tableData,
239
- defaultColumn,
240
- disableSortRemove: true, // otherwise 3rd click on header removes sorting entirely
241
- sortTypes: {
242
- custom: (rowA, rowB, columnId) => {
243
- // rowA.original - is the row data field name to access the value
244
- // columnId = the column indicator
245
- let dataKey = config.xAxis.dataKey
246
- let colObj = config.data.filter(obj => {
247
- return obj[dataKey] === columnId.split('--')[0] // have to remove index
248
- })
249
- if (colObj === undefined || colObj[0] === undefined) {
250
- return -1
251
- }
252
- // NOW we can get the sort values
253
- const a = transform.cleanDataPoint(colObj[0][rowA.original]) // issue was that a was UNDEFINED therefore it CANT SORT
254
- const b = transform.cleanDataPoint(colObj[0][rowB.original])
255
-
256
- if (a === undefined) {
257
- return -1
258
- }
259
- if (!isNaN(Number(a)) && !isNaN(Number(b))) {
260
- return Number(a) - Number(b)
261
- }
262
- return a.localeCompare(b)
263
- }
264
- }
265
- },
266
- useSortBy,
267
- useBlockLayout,
268
- useResizeColumns
269
- )
270
-
271
- // sort continuous x axis scaling for data tables, ie. xAxis should read 1,2,3,4,5
272
- if (config.xAxis.type === 'continuous' && headerGroups) {
273
- data.sort((a, b) => a[config.xAxis.dataKey] - b[config.xAxis.dataKey])
274
- }
275
-
276
- return (
277
- <ErrorBoundary component='DataTable'>
278
- <MediaControls.Section classes={['download-links']}>
279
- <MediaControls.Link config={config} />
280
- {config.table.download && <DownloadButton data={rawData} type='link' />}
281
- </MediaControls.Section>
282
-
283
- <section style={{ marginTop: !isLegendBottom ? config.dynamicMarginTop / 4 + 'px' : '0px' }} id={config?.title ? `dataTableSection__${config?.title.replace(/\s/g, '')}` : `dataTableSection`} className={`data-table-container`} aria-label={accessibilityLabel}>
284
- <div
285
- role='button'
286
- className={tableExpanded ? 'data-table-heading' : 'collapsed data-table-heading'}
287
- tabIndex={0}
288
- onClick={() => {
289
- setTableExpanded(!tableExpanded)
290
- }}
291
- onKeyDown={e => {
292
- if (e.keyCode === 13) {
293
- setTableExpanded(!tableExpanded)
294
- }
295
- }}
296
- >
297
- <Icon display={tableExpanded ? 'minus' : 'plus'} base />
298
- {config.table.label}
299
- </div>
300
- <div className='table-container' hidden={!tableExpanded} style={{ maxHeight: config.table.limitHeight && `${config.table.height}px`, overflowY: 'scroll' }}>
301
- <table className={tableExpanded ? 'data-table' : 'data-table cdcdataviz-sr-only'} {...getTableProps()} aria-rowcount={config?.series?.length ? config?.series?.length : '-1'}>
302
- <caption className='cdcdataviz-sr-only visually-hidden'>{config.table.caption ? config.table.caption : config.table.label ? config.table.label : 'Data Table'}</caption>
303
- <thead>
304
- {headerGroups.map((headerGroup, index) => (
305
- <tr {...headerGroup.getHeaderGroupProps()} key={`headerGroups--${index}`}>
306
- {' '}
307
- {headerGroup.headers.map((column, index) => (
308
- <th
309
- tabIndex='0'
310
- title={column.Header}
311
- key={`trth--${index}`}
312
- role='columnheader'
313
- scope='col'
314
- {...column.getHeaderProps(column.getSortByToggleProps())}
315
- className={column.isSorted && column.isSortedDesc ? 'sort sort-desc' : 'sort sort-asc'}
316
- {...(column.isSorted && column.isSortedDesc ? { 'aria-sort': 'descending' } : { 'aria-sort': 'ascending' })}
317
- >
318
- {column.render('Header')}
319
- {column.isSorted && <span className={'sort-icon'}>{column.isSortedDesc ? downIcon : upIcon}</span>}
320
- </th>
321
- ))}
322
- </tr>
323
- ))}
324
- </thead>
325
- <tbody {...getTableBodyProps()}>
326
- {rows.map((row, index) => {
327
- prepareRow(row)
328
- return (
329
- <tr {...row.getRowProps()} key={`tbody__tr-${index}`} className={`row-${String(config.visualizationType).replace(' ', '-')}--${index}`}>
330
- {row.cells.map((cell, index) => {
331
- return (
332
- <td tabIndex='0' {...cell.getCellProps()} key={`tbody__tr__td-${index}`} role='gridcell'>
333
- {cell.render('Cell')}
334
- </td>
335
- )
336
- })}
337
- </tr>
338
- )
339
- })}
340
- </tbody>
341
- </table>
342
- {config.regions && config.regions.length > 0 && config.visualizationType !== 'Box Plot' ? (
343
- <table className='region-table data-table'>
344
- <caption className='visually-hidden'>Table of the highlighted regions in the visualization</caption>
345
- <thead>
346
- <tr>
347
- <th>Region Name</th>
348
- <th>Start Date</th>
349
- <th>End Date</th>
350
- </tr>
351
- </thead>
352
- <tbody>
353
- {config.regions.map((region, index) => {
354
- if (config.visualizationType === 'Box Plot') return false
355
- if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
356
-
357
- return (
358
- <tr key={`row-${region.label}--${index}`}>
359
- <td>{region.label}</td>
360
- <td>{formatDate(parseDate(region.from))}</td>
361
- <td>{formatDate(parseDate(region.to))}</td>
362
- </tr>
363
- )
364
- })}
365
- </tbody>
366
- </table>
367
- ) : (
368
- ''
369
- )}
370
- </div>
371
- </section>
372
- </ErrorBoundary>
373
- )
374
- }