@cdc/chart 1.3.4 → 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.
Files changed (75) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +5 -5
  3. package/dist/cdcchart.js +6 -6
  4. package/examples/age-adjusted-rates.json +1486 -1218
  5. package/examples/case-rate-example-config.json +1 -1
  6. package/examples/covid-confidence-example-config.json +33 -33
  7. package/examples/covid-example-config.json +34 -34
  8. package/examples/covid-example-data-confidence.json +30 -30
  9. package/examples/covid-example-data.json +20 -20
  10. package/examples/cutoff-example-config.json +36 -34
  11. package/examples/cutoff-example-data.json +36 -36
  12. package/examples/date-exclusions-config.json +1 -1
  13. package/examples/dynamic-legends.json +125 -0
  14. package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart-with-numbers-on-bar.json +192 -0
  15. package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +231 -0
  16. package/examples/gallery/bar-chart-horizontal/horizontal-stacked.json +240 -0
  17. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +137 -0
  18. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +80 -0
  19. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +81 -0
  20. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +68 -0
  21. package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +111 -0
  22. package/examples/gallery/lollipop/lollipop-style-horizontal.json +216 -0
  23. package/examples/gallery/paired-bar/paired-bar-chart.json +196 -0
  24. package/examples/horizontal-chart.json +36 -33
  25. package/examples/horizontal-stacked-bar-chart.json +34 -34
  26. package/examples/line-chart.json +75 -75
  27. package/examples/paired-bar-data.json +16 -14
  28. package/examples/paired-bar-example.json +48 -46
  29. package/examples/paired-bar-formatted.json +36 -36
  30. package/examples/planet-chart-horizontal-example-config.json +33 -33
  31. package/examples/planet-combo-example-config.json +34 -29
  32. package/examples/planet-example-config.json +35 -33
  33. package/examples/planet-example-data.json +56 -56
  34. package/examples/planet-pie-example-config.json +28 -26
  35. package/examples/private/filters.json +170 -0
  36. package/examples/private/line-test-data.json +22 -0
  37. package/examples/private/line-test-two.json +210 -0
  38. package/examples/private/line-test.json +102 -0
  39. package/examples/private/new.json +48800 -0
  40. package/examples/private/shawn.json +1106 -0
  41. package/examples/private/test.json +10123 -10123
  42. package/examples/private/yaxis-test.json +133 -0
  43. package/examples/private/yaxis-testing.csv +27 -0
  44. package/examples/private/yaxis.json +28 -0
  45. package/examples/stacked-vertical-bar-example.json +228 -0
  46. package/examples/temp-example-config.json +61 -54
  47. package/examples/temp-example-data.json +1 -1
  48. package/package.json +2 -2
  49. package/src/CdcChart.tsx +370 -458
  50. package/src/components/BarChart.tsx +449 -441
  51. package/src/components/DataTable.tsx +164 -180
  52. package/src/components/EditorPanel.js +1066 -663
  53. package/src/components/Legend.js +284 -0
  54. package/src/components/LineChart.tsx +114 -63
  55. package/src/components/LinearChart.tsx +394 -358
  56. package/src/components/PairedBarChart.tsx +216 -135
  57. package/src/components/PieChart.tsx +106 -135
  58. package/src/components/SparkLine.js +184 -205
  59. package/src/components/useIntersectionObserver.tsx +27 -0
  60. package/src/context.tsx +3 -3
  61. package/src/data/initial-state.js +44 -7
  62. package/src/hooks/useActiveElement.js +13 -13
  63. package/src/hooks/useChartClasses.js +41 -0
  64. package/src/hooks/useColorPalette.ts +56 -63
  65. package/src/hooks/useLegendClasses.js +28 -0
  66. package/src/hooks/useReduceData.ts +69 -37
  67. package/src/hooks/useRightAxis.js +25 -0
  68. package/src/hooks/useTopAxis.js +6 -0
  69. package/src/index.html +54 -55
  70. package/src/index.tsx +13 -16
  71. package/src/scss/DataTable.scss +5 -4
  72. package/src/scss/editor-panel.scss +103 -71
  73. package/src/scss/main.scss +277 -38
  74. package/src/scss/variables.scss +1 -1
  75. package/src/components/BarStackVertical.js +0 -0
@@ -1,229 +1,213 @@
1
- import React, {
2
- useContext,
3
- useEffect,
4
- useState,
5
- useMemo,
6
- memo,
7
- Fragment} from 'react';
8
- import {
9
- useTable,
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 } = 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: "text/csv;charset=utf-8;" });
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 = config.visualizationType === 'Pie' ? [] : [{
61
- Header: '',
62
- Cell: ({ row }) => {
63
- const seriesLabel = config.runtime.seriesLabels ? config.runtime.seriesLabels[row.original] : row.original;
64
- return (
65
- <Fragment>
66
- {config.visualizationType !== 'Pie' && <LegendCircle fill={colorScale(seriesLabel)} />}
67
- <span>{seriesLabel}</span>
68
- </Fragment>
69
- )
70
- },
71
- id: 'series-label'
72
- }];
73
-
74
- data.forEach((d) => {
75
- const newCol = {
76
- Header: config.runtime[section].type === 'date' ? formatDate(parseDate(d[config.runtime.originalXAxis.dataKey])) : d[config.runtime.originalXAxis.dataKey],
77
- Cell: ({ row }) => {
78
- return (
79
- <>
80
- {numberFormatter(d[row.original])}
81
- </>
82
- );
83
- },
84
- id: d[config.runtime.originalXAxis.dataKey],
85
- canSort: true
86
- };
87
-
88
- newTableColumns.push(newCol);
89
- });
90
-
91
- return newTableColumns;
92
- }, [config,colorScale]);
93
-
94
-
95
-
96
- const tableData = useMemo(
97
- () => config.visualizationType === 'Pie' ? [config.yAxis.dataKey] : config.runtime.seriesKeys,
98
- [config.runtime.seriesKeys]
99
- );
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])
100
92
 
101
93
  // Change accessibility label depending on expanded status
102
94
  useEffect(() => {
103
- const expandedLabel = 'Accessible data table.';
104
- 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.'
105
97
 
106
98
  if (tableExpanded === true && accessibilityLabel !== expandedLabel) {
107
- setAccessibilityLabel(expandedLabel);
99
+ setAccessibilityLabel(expandedLabel)
108
100
  }
109
101
 
110
102
  if (tableExpanded === false && accessibilityLabel !== collapsedLabel) {
111
- setAccessibilityLabel(collapsedLabel);
103
+ setAccessibilityLabel(collapsedLabel)
112
104
  }
113
105
  // eslint-disable-next-line react-hooks/exhaustive-deps
114
- }, [tableExpanded]);
106
+ }, [tableExpanded])
115
107
 
116
108
  const defaultColumn = useMemo(
117
109
  () => ({
118
110
  minWidth: 150,
119
111
  width: 200,
120
- maxWidth: 400,
112
+ maxWidth: 400
121
113
  }),
122
114
  []
123
- );
124
-
125
- const {
126
- getTableProps,
127
- getTableBodyProps,
128
- headerGroups,
129
- rows,
130
- prepareRow,
131
- } = 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)
132
118
  return (
133
- <ErrorBoundary component="DataTable">
134
- <section id={config?.title ? `dataTableSection__${config?.title.replace(/\s/g, '')}` : `dataTableSection`} className={`data-table-container`} aria-label={accessibilityLabel}>
135
- <div
136
- role="button"
137
- className={tableExpanded ? 'data-table-heading' : 'collapsed data-table-heading'}
138
- tabIndex={0}
139
- onClick={() => { setTableExpanded(!tableExpanded); }}
140
- onKeyDown={(e) => { if (e.keyCode === 13) { setTableExpanded(!tableExpanded); } }}
141
- >
142
- {config.table.label}
143
- </div>
144
- <div className="table-container">
145
- <table
146
- className={tableExpanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}
147
- hidden={!tableExpanded}
148
- {...getTableProps()}
149
- aria-rowcount={ config?.series?.length ? config?.series?.length : '-1' }
150
- >
151
- <caption className="visually-hidden">{config.table.label}</caption>
152
- <thead>
153
- {headerGroups.map((headerGroup,index) => (
154
- <tr {...headerGroup.getHeaderGroupProps()} key={`headerGroups--${index}`}>
155
- {headerGroup.headers.map((column, index) => (
156
- <th
157
- tabIndex="0"
158
- title={column.Header}
159
- key={`trth--${index}`}
160
- role="columnheader"
161
- scope="col"
162
- {...column.getHeaderProps(column.getSortByToggleProps())}
163
- className={column.isSorted ? column.isSortedDesc ? 'sort sort-desc' : 'sort sort-asc' : 'sort'}
164
- {...(column.isSorted ? column.isSortedDesc ? { 'aria-sort': 'descending' } : { 'aria-sort': 'ascending' } : null)}
165
- >
166
- {index === 0
167
- ? config.table.indexLabel
168
- ? config.table.indexLabel : column.render('Header')
169
- : column.render('Header')
170
- }
171
- <button>
172
- <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>
173
- </button>
174
- <div {...column.getResizerProps()} className="resizer" />
175
- </th>
176
- ))}
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
+ })}
177
176
  </tr>
178
- ))}
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>
179
190
  </thead>
180
- <tbody {...getTableBodyProps()}>
181
- {rows.map((row, index) => {
182
- prepareRow(row);
191
+ <tbody>
192
+ {config.regions.map((region, index) => {
193
+ if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
194
+
183
195
  return (
184
- <tr {...row.getRowProps()} key={`tbody__tr-${index}`}>
185
- {row.cells.map((cell, index) => (
186
- <td
187
- tabIndex="0"
188
- {...cell.getCellProps()}
189
- key={`tbody__tr__td-${index}`}
190
- role="gridcell">
191
- {cell.render('Cell')}
192
- </td>
193
- ))}
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>
194
200
  </tr>
195
- );
201
+ )
196
202
  })}
197
203
  </tbody>
198
204
  </table>
199
- {config.regions && config.regions.length > 0 ? (
200
- <table className="region-table data-table">
201
- <caption className="visually-hidden">Table of the highlighted regions in the visualization</caption>
202
- <thead>
203
- <tr>
204
- <th>Region Name</th>
205
- <th>Start Date</th>
206
- <th>End Date</th>
207
- </tr>
208
- </thead>
209
- <tbody>
210
- {config.regions.map((region,index) => {
211
- if(!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
212
-
213
- return (
214
- <tr key={`row-${region.label}--${index}`}>
215
- <td>{region.label}</td>
216
- <td>{formatDate(parseDate(region.from))}</td>
217
- <td>{formatDate(parseDate(region.to))}</td>
218
- </tr>
219
- )
220
- })}
221
- </tbody>
222
- </table>
223
- ) : ''}
224
- </div>
225
- {config.table.download && <DownloadButton data={rawData} />}
205
+ ) : (
206
+ ''
207
+ )}
208
+ </div>
209
+ {config.table.download && <DownloadButton data={rawData} />}
226
210
  </section>
227
211
  </ErrorBoundary>
228
- );
212
+ )
229
213
  }