@cdc/core 4.24.4 → 4.24.7

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 (80) hide show
  1. package/components/AdvancedEditor/AdvancedEditor.tsx +93 -0
  2. package/components/AdvancedEditor/advanced-editor-styles.css +3 -0
  3. package/components/AdvancedEditor/index.ts +1 -0
  4. package/components/DataTable/DataTable.tsx +33 -15
  5. package/components/DataTable/DataTableStandAlone.tsx +30 -4
  6. package/components/DataTable/components/ChartHeader.tsx +3 -2
  7. package/components/DataTable/components/DataTableEditorPanel.tsx +23 -6
  8. package/components/DataTable/components/ExpandCollapse.tsx +1 -1
  9. package/components/DataTable/helpers/chartCellMatrix.tsx +5 -10
  10. package/components/DataTable/helpers/getChartCellValue.ts +26 -2
  11. package/components/DataTable/helpers/getDataSeriesColumns.ts +40 -16
  12. package/components/DataTable/helpers/getRowType.ts +6 -0
  13. package/components/DataTable/helpers/getSeriesName.ts +2 -1
  14. package/components/DataTable/helpers/{customColumns.ts → removeNullColumns.ts} +3 -3
  15. package/components/DataTable/types/TableConfig.ts +12 -22
  16. package/components/EditorPanel/ColumnsEditor.tsx +278 -262
  17. package/components/EditorPanel/DataTableEditor.tsx +159 -60
  18. package/components/EditorPanel/FieldSetWrapper.tsx +51 -0
  19. package/components/EditorPanel/FootnotesEditor.tsx +77 -0
  20. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +227 -0
  21. package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +54 -0
  22. package/components/EditorPanel/VizFilterEditor/index.ts +1 -0
  23. package/components/EditorWrapper/EditorWrapper.tsx +47 -0
  24. package/components/EditorWrapper/editor-wrapper.style.css +14 -0
  25. package/components/EditorWrapper/index.ts +1 -0
  26. package/components/{Filters.jsx → Filters.tsx} +102 -70
  27. package/components/Footnotes/Footnotes.tsx +25 -0
  28. package/components/Footnotes/FootnotesStandAlone.tsx +45 -0
  29. package/components/Footnotes/footnotes.css +5 -0
  30. package/components/Footnotes/index.ts +1 -0
  31. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +8 -4
  32. package/components/Layout/components/Visualization/index.tsx +14 -5
  33. package/components/MediaControls.jsx +1 -1
  34. package/components/MultiSelect/MultiSelect.tsx +36 -9
  35. package/components/MultiSelect/multiselect.styles.css +0 -3
  36. package/components/_stories/DataTable.stories.tsx +1 -2
  37. package/components/_stories/EditorPanel.stories.tsx +1 -0
  38. package/components/_stories/Footnotes.stories.tsx +17 -0
  39. package/components/inputs/InputSelect.tsx +17 -6
  40. package/components/ui/Icon.tsx +1 -2
  41. package/helpers/DataTransform.ts +9 -32
  42. package/helpers/addValuesToFilters.ts +56 -0
  43. package/helpers/cove/accessibility.ts +1 -0
  44. package/helpers/cove/fontSettings.ts +2 -0
  45. package/helpers/coveUpdateWorker.ts +11 -2
  46. package/helpers/filterVizData.ts +30 -0
  47. package/helpers/footnoteSymbols.ts +11 -0
  48. package/helpers/formatConfigBeforeSave.ts +90 -0
  49. package/helpers/gatherQueryParams.ts +14 -7
  50. package/helpers/lineChartHelpers.js +2 -1
  51. package/helpers/pivotData.ts +18 -0
  52. package/helpers/queryStringUtils.ts +29 -0
  53. package/helpers/tests/updateFieldFactory.test.ts +1 -0
  54. package/helpers/updateFieldFactory.ts +1 -1
  55. package/helpers/useDataVizClasses.js +0 -4
  56. package/helpers/ver/4.23.4.ts +27 -0
  57. package/helpers/ver/4.24.5.ts +32 -0
  58. package/helpers/ver/4.24.7.ts +92 -0
  59. package/package.json +6 -4
  60. package/styles/_button-section.scss +6 -1
  61. package/styles/_data-table.scss +0 -1
  62. package/styles/_reset.scss +7 -6
  63. package/styles/base.scss +4 -0
  64. package/styles/v2/themes/_color-definitions.scss +1 -0
  65. package/types/Annotation.ts +46 -0
  66. package/types/Column.ts +1 -0
  67. package/types/ConfigureData.ts +1 -1
  68. package/types/Footnotes.ts +17 -0
  69. package/types/General.ts +5 -0
  70. package/types/Legend.ts +1 -0
  71. package/types/MarkupInclude.ts +26 -0
  72. package/types/Runtime.ts +3 -7
  73. package/types/Series.ts +1 -1
  74. package/types/Table.ts +21 -14
  75. package/types/Visualization.ts +40 -11
  76. package/types/VizFilter.ts +24 -0
  77. package/LICENSE +0 -201
  78. package/components/AdvancedEditor.jsx +0 -74
  79. package/helpers/queryStringUtils.js +0 -26
  80. package/types/BaseVisualizationType.ts +0 -1
@@ -0,0 +1,93 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import MapIcon from '../../assets/map-folded.svg'
3
+ import ChartIcon from '../../assets/icon-chart-bar.svg'
4
+ import MarkupIncludeIcon from '../../assets/icon-code.svg'
5
+ import { FilterFunction, JsonEditor, UpdateFunction } from 'json-edit-react'
6
+ import { formatConfigBeforeSave as stripConfig } from '../../helpers/formatConfigBeforeSave'
7
+ import './advanced-editor-styles.css'
8
+ import _ from 'lodash'
9
+
10
+ export const AdvancedEditor = ({ loadConfig, config, convertStateToConfig, onExpandCollapse = () => {} }) => {
11
+ const [advancedToggle, _setAdvancedToggle] = useState(false)
12
+ const [configTextboxValue, setConfigTextbox] = useState<Record<string, any>>({})
13
+ const setAdvancedToggle = val => {
14
+ _setAdvancedToggle(val)
15
+ onExpandCollapse()
16
+ }
17
+
18
+ const collapseFields: FilterFunction = input => {
19
+ if (['datasets', 'data', 'originalFormattedData', 'formattedData'].includes(String(input.key))) return true
20
+ return false
21
+ }
22
+
23
+ const onUpdate: UpdateFunction = val => {
24
+ setConfigTextbox(val.newData)
25
+ }
26
+
27
+ useEffect(() => {
28
+ let parsedConfig = stripConfig(config)
29
+ if (config.type !== 'dashboard') {
30
+ parsedConfig = convertStateToConfig()
31
+ }
32
+
33
+ setConfigTextbox(parsedConfig)
34
+ }, [config])
35
+
36
+ const typeLookup = {
37
+ chart: ['Charts', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/bar-chart.html', <ChartIcon />],
38
+ dashboard: ['Dashboard', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/bar-chart.html', <ChartIcon />],
39
+ map: ['Maps', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/data-map.html', <MapIcon />],
40
+ 'markup-include': ['Markup Include', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/Markup-Include.html', <MarkupIncludeIcon />]
41
+ }
42
+
43
+ if (!config.type) return <></>
44
+ return (
45
+ <>
46
+ <a href={typeLookup[config.type][1]} target='_blank' rel='noopener noreferrer' className='guidance-link'>
47
+ {typeLookup[config.type][2]}
48
+ <div>
49
+ <span className='heading-3'>Get Help with {typeLookup[config.type][0]}</span>
50
+ <p>Examples and documentation</p>
51
+ </div>
52
+ </a>
53
+ <div className='advanced'>
54
+ <span className='advanced-toggle-link' onClick={() => setAdvancedToggle(!advancedToggle)}>
55
+ <span>{advancedToggle ? `— ` : `+ `}</span>Advanced Options
56
+ </span>
57
+ {advancedToggle && (
58
+ <React.Fragment>
59
+ <section className='error-box py-2 px-3 my-2'>
60
+ <div>
61
+ <strong className='pt-1'>Warning</strong>
62
+ <p>This can cause serious errors in your visualization.</p>
63
+ </div>
64
+ </section>
65
+ <p className='pb-2'>
66
+ This tool displays the actual <acronym title='JavaScript Object Notation'>JSON</acronym> configuration that is generated by this editor and allows you to edit properties directly and apply them.
67
+ </p>
68
+ <button
69
+ className='btn '
70
+ onClick={() => {
71
+ navigator.clipboard.writeText(JSON.stringify(configTextboxValue))
72
+ }}
73
+ >
74
+ Copy to Clipboard
75
+ </button>
76
+ <JsonEditor className='advanced-json-editor' data={configTextboxValue} onUpdate={onUpdate} rootName='' collapse={collapseFields} />
77
+ <button
78
+ className='btn full-width'
79
+ onClick={() => {
80
+ loadConfig(configTextboxValue)
81
+ setAdvancedToggle(!advancedToggle)
82
+ }}
83
+ >
84
+ Apply
85
+ </button>
86
+ </React.Fragment>
87
+ )}
88
+ </div>
89
+ </>
90
+ )
91
+ }
92
+
93
+ export default AdvancedEditor
@@ -0,0 +1,3 @@
1
+ .advanced-json-editor {
2
+ width: 100vw;
3
+ }
@@ -0,0 +1 @@
1
+ export { default } from './AdvancedEditor'
@@ -16,9 +16,10 @@ import Table from '../Table'
16
16
  import chartCellMatrix from './helpers/chartCellMatrix'
17
17
  import regionCellMatrix from './helpers/regionCellMatrix'
18
18
  import boxplotCellMatrix from './helpers/boxplotCellMatrix'
19
- import customColumns from './helpers/customColumns'
19
+ import removeNullColumns from './helpers/removeNullColumns'
20
20
  import { TableConfig } from './types/TableConfig'
21
21
  import { Column } from '../../types/Column'
22
+ import { pivotData } from '../../helpers/pivotData'
22
23
 
23
24
  export type DataTableProps = {
24
25
  applyLegendToRow?: Function
@@ -52,7 +53,18 @@ export type DataTableProps = {
52
53
 
53
54
  /* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
54
55
  const DataTable = (props: DataTableProps) => {
55
- const { config, dataConfig, tableTitle, vizTitle, rawData, runtimeData, headerColor, expandDataTable, columns, viewport, formatLegendLocation, tabbingId, wrapColumns } = props
56
+ const { config, dataConfig, tableTitle, vizTitle, rawData, runtimeData: parentRuntimeData, headerColor, expandDataTable, columns, viewport, formatLegendLocation, tabbingId, wrapColumns } = props
57
+ const runtimeData = useMemo(() => {
58
+ const data = removeNullColumns(parentRuntimeData)
59
+ if (config.table.pivot) {
60
+ const { columnName, valueColumn } = config.table.pivot
61
+ if (columnName && valueColumn) {
62
+ return pivotData(data, columnName, valueColumn)
63
+ }
64
+ }
65
+ return data
66
+ }, [parentRuntimeData, config.table.pivot?.columnName, config.table.pivot?.valueColumn])
67
+
56
68
  const [expanded, setExpanded] = useState(expandDataTable)
57
69
 
58
70
  const [sortBy, setSortBy] = useState<any>({ column: config.type === 'map' ? 'geo' : 'date', asc: false, colIndex: null })
@@ -98,25 +110,23 @@ const DataTable = (props: DataTableProps) => {
98
110
  break
99
111
  }
100
112
 
101
- const _runtimeData = config.table.customTableConfig ? customColumns(rawData, config.table.excludeColumns) : runtimeData
102
-
103
- const rawRows = Object.keys(_runtimeData).filter(column => column != 'columns')
113
+ const rawRows = Object.keys(runtimeData).filter(column => column != 'columns')
104
114
  const rows = isVertical
105
115
  ? rawRows.sort((a, b) => {
106
116
  let dataA
107
117
  let dataB
108
118
  if (config.type === 'map' && config.columns) {
109
119
  const sortByColName = config.columns[sortBy.column].name
110
- dataA = _runtimeData[a][sortByColName]
111
- dataB = _runtimeData[b][sortByColName]
120
+ dataA = runtimeData[a][sortByColName]
121
+ dataB = runtimeData[b][sortByColName]
112
122
  }
113
123
  if (config.type === 'chart' || config.type === 'dashboard') {
114
- dataA = _runtimeData[a][sortBy.column]
115
- dataB = _runtimeData[b][sortBy.column]
124
+ dataA = runtimeData[a][sortBy.column]
125
+ dataB = runtimeData[b][sortBy.column]
116
126
  }
117
127
  if (!dataA && !dataB && config.type === 'chart' && config.xAxis && config.xAxis.type === 'date-time') {
118
- dataA = timeParse(config.runtime.xAxis.dateParseFormat)(_runtimeData[a][config.xAxis.dataKey])
119
- dataB = timeParse(config.runtime.xAxis.dateParseFormat)(_runtimeData[b][config.xAxis.dataKey])
128
+ dataA = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[a][config.xAxis.dataKey])
129
+ dataB = timeParse(config.runtime.xAxis.dateParseFormat)(runtimeData[b][config.xAxis.dataKey])
120
130
  }
121
131
  return dataA && dataB ? customSort(dataA, dataB, sortBy, config) : 0
122
132
  })
@@ -177,25 +187,33 @@ const DataTable = (props: DataTableProps) => {
177
187
  }
178
188
  }
179
189
 
190
+ const getMediaControlsClasses = () => {
191
+ const classes = ['download-links']
192
+ const isLegendOnBottom = config?.legend?.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(viewport)
193
+ if (config.brush?.active && !isLegendOnBottom) classes.push('brush-active')
194
+ if (config.brush?.active && config.legend.hide) classes.push('brush-active')
195
+ return classes
196
+ }
197
+
180
198
  return (
181
199
  <ErrorBoundary component='DataTable'>
182
- <MediaControls.Section classes={['download-links']}>
200
+ <MediaControls.Section classes={getMediaControlsClasses()}>
183
201
  <MediaControls.Link config={config} dashboardDataConfig={dataConfig} />
184
202
  {(config.table.download || config.general?.showDownloadButton) && <DownloadButton rawData={getDownloadData()} fileName={`${vizTitle || 'data-table'}.csv`} headerColor={headerColor} />}
185
203
  </MediaControls.Section>
186
204
  <section id={tabbingId.replace('#', '')} className={`data-table-container ${viewport}`} aria-label={accessibilityLabel}>
187
205
  <SkipTo skipId={skipId} skipMessage='Skip Data Table' />
188
- <ExpandCollapse expanded={expanded} setExpanded={setExpanded} tableTitle={tableTitle} fontSize={config.fontSize} viewport={viewport} />
206
+ {config.table.collapsible !== false && <ExpandCollapse expanded={expanded} setExpanded={setExpanded} fontSize={config.fontSize} tableTitle={tableTitle} viewport={viewport} />}
189
207
  <div className='table-container' style={limitHeight}>
190
208
  <Table
191
209
  viewport={viewport}
192
210
  wrapColumns={wrapColumns}
193
- childrenMatrix={config.type === 'map' ? mapCellMatrix({ rows, wrapColumns, ...props, runtimeData: _runtimeData, viewport }) : chartCellMatrix({ rows, ...props, runtimeData: _runtimeData, isVertical, sortBy, hasRowType, viewport })}
211
+ childrenMatrix={config.type === 'map' ? mapCellMatrix({ rows, wrapColumns, ...props, runtimeData, viewport }) : chartCellMatrix({ rows, ...props, runtimeData, isVertical, sortBy, hasRowType, viewport })}
194
212
  tableName={config.type}
195
213
  caption={caption}
196
214
  stickyHeader
197
215
  hasRowType={hasRowType}
198
- headContent={config.type === 'map' ? <MapHeader columns={columns} {...props} sortBy={sortBy} setSortBy={setSortBy} /> : <ChartHeader data={_runtimeData} {...props} hasRowType={hasRowType} isVertical={isVertical} sortBy={sortBy} setSortBy={setSortBy} />}
216
+ headContent={config.type === 'map' ? <MapHeader columns={columns} {...props} sortBy={sortBy} setSortBy={setSortBy} /> : <ChartHeader data={runtimeData} {...props} hasRowType={hasRowType} isVertical={isVertical} sortBy={sortBy} setSortBy={setSortBy} />}
199
217
  tableOptions={{ className: `${expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}${isVertical ? '' : ' horizontal'}`, 'aria-live': 'assertive', 'aria-rowcount': config?.data?.length ? config.data.length : -1, hidden: !expanded }}
200
218
  fontSize={config.fontSize}
201
219
  />
@@ -1,15 +1,41 @@
1
+ import { useEffect, useState } from 'react'
1
2
  import { ViewPort } from '../../types/ViewPort'
2
- import { Visualization } from '../../types/Visualization'
3
+ import EditorWrapper from '../EditorWrapper/EditorWrapper'
3
4
  import DataTable from './DataTable'
5
+ import DataTableEditorPanel from './components/DataTableEditorPanel'
6
+ import Filters from '../Filters'
7
+ import { TableConfig } from './types/TableConfig'
8
+ import { filterVizData } from '../../helpers/filterVizData'
4
9
 
5
10
  type StandAloneProps = {
6
11
  visualizationKey: string
7
- config: Visualization
12
+ config: TableConfig
8
13
  viewport?: ViewPort
14
+ isEditor?: boolean
15
+ updateConfig?: (Visualization) => void
9
16
  }
10
17
 
11
- const DataTableStandAlone: React.FC<StandAloneProps> = ({ visualizationKey, config, viewport }) => {
12
- return <DataTable expandDataTable={true} config={config} rawData={config.data} runtimeData={config.formattedData} tabbingId={visualizationKey} tableTitle={config.table.label} viewport={viewport || 'lg'} />
18
+ const DataTableStandAlone: React.FC<StandAloneProps> = ({ visualizationKey, config, updateConfig, viewport, isEditor }) => {
19
+ const [filteredData, setFilteredData] = useState<Record<string, any>[]>(filterVizData(config.filters, config.formattedData))
20
+
21
+ useEffect(() => {
22
+ // when using editor changes to filter should update the data
23
+ setFilteredData(filterVizData(config.filters, config?.formattedData?.length > 0 ? config.formattedData : config.data))
24
+ }, [config.filters])
25
+
26
+ if (isEditor)
27
+ return (
28
+ <EditorWrapper component={DataTableStandAlone} visualizationKey={visualizationKey} visualizationConfig={config} updateConfig={updateConfig} type={'Table'} viewport={viewport}>
29
+ <DataTableEditorPanel key={visualizationKey} config={config} updateConfig={updateConfig} />
30
+ </EditorWrapper>
31
+ )
32
+
33
+ return (
34
+ <>
35
+ <Filters config={config} setConfig={updateConfig} setFilteredData={setFilteredData} filteredData={filteredData} excludedData={config.formattedData} />
36
+ <DataTable expandDataTable={true} config={config} rawData={config.data} runtimeData={filteredData} tabbingId={visualizationKey} tableTitle={config.table.label} viewport={viewport || 'lg'} />
37
+ </>
38
+ )
13
39
  }
14
40
 
15
41
  export default DataTableStandAlone
@@ -4,9 +4,10 @@ import { getDataSeriesColumns } from '../helpers/getDataSeriesColumns'
4
4
  import { DownIcon, UpIcon } from './Icons'
5
5
  import ScreenReaderText from '@cdc/core/components/elements/ScreenReaderText'
6
6
 
7
- type ChartHeaderProps = { data; isVertical; config; setSortBy; sortBy; groupBy?; hasRowType? }
7
+ type ChartHeaderProps = { data; isVertical; config; setSortBy; sortBy; hasRowType? }
8
8
 
9
- const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, groupBy, hasRowType }: ChartHeaderProps) => {
9
+ const ChartHeader = ({ data, isVertical, config, setSortBy, sortBy, hasRowType }: ChartHeaderProps) => {
10
+ const groupBy = config.table?.groupBy
10
11
  if (!data) return
11
12
  let dataSeriesColumns = getDataSeriesColumns(config, isVertical, data)
12
13
  if (groupBy) {
@@ -1,9 +1,11 @@
1
- import { AccordionItem, AccordionItemButton, AccordionItemHeading, AccordionItemPanel } from 'react-accessible-accordion'
1
+ import { Accordion, AccordionItem, AccordionItemButton, AccordionItemHeading, AccordionItemPanel } from 'react-accessible-accordion'
2
2
  import DataTableEditor from '../../EditorPanel/DataTableEditor'
3
3
  import { Visualization } from '@cdc/core/types/Visualization'
4
4
  import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
5
5
  import { useMemo } from 'react'
6
6
  import ColumnsEditor from '../../EditorPanel/ColumnsEditor'
7
+ import VizFilterEditor from '../../EditorPanel/VizFilterEditor'
8
+ import _ from 'lodash'
7
9
 
8
10
  type DataTableEditorProps = {
9
11
  config: Visualization
@@ -13,7 +15,7 @@ type DataTableEditorProps = {
13
15
  const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateConfig }) => {
14
16
  const updateField = useMemo(() => updateFieldFactory(config, updateConfig), [JSON.stringify(config)])
15
17
  const deleteColumn = columnName => {
16
- const newColumns = config.columns
18
+ const newColumns = _.cloneDeep(config.columns)
17
19
 
18
20
  delete newColumns[columnName]
19
21
 
@@ -23,10 +25,25 @@ const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateCo
23
25
  })
24
26
  }
25
27
 
26
- const columns = Object.keys(config.columns || {})
28
+ const columns = Object.keys(config.originalFormattedData?.[0] || {})
27
29
  return (
28
- <>
29
- <ColumnsEditor config={config} updateField={updateField} deleteColumn={deleteColumn} />
30
+ <Accordion allowZeroExpanded={true}>
31
+ <AccordionItem>
32
+ <AccordionItemHeading>
33
+ <AccordionItemButton>Filters</AccordionItemButton>
34
+ </AccordionItemHeading>
35
+ <AccordionItemPanel>
36
+ <VizFilterEditor config={config} updateField={updateField} rawData={config.originalFormattedData} />
37
+ </AccordionItemPanel>
38
+ </AccordionItem>
39
+ <AccordionItem>
40
+ <AccordionItemHeading>
41
+ <AccordionItemButton>Columns</AccordionItemButton>
42
+ </AccordionItemHeading>
43
+ <AccordionItemPanel>
44
+ <ColumnsEditor config={config} updateField={updateField} deleteColumn={deleteColumn} />
45
+ </AccordionItemPanel>
46
+ </AccordionItem>
30
47
  <AccordionItem>
31
48
  <AccordionItemHeading>
32
49
  <AccordionItemButton>Data Table</AccordionItemButton>
@@ -35,7 +52,7 @@ const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateCo
35
52
  <DataTableEditor config={config} columns={columns} updateField={updateField} isDashboard={true} />
36
53
  </AccordionItemPanel>
37
54
  </AccordionItem>
38
- </>
55
+ </Accordion>
39
56
  )
40
57
  }
41
58
 
@@ -1,7 +1,7 @@
1
1
  import Icon from '../../ui/Icon'
2
+ import { fontSizes } from '../../../helpers/cove/fontSettings'
2
3
 
3
4
  const ExpandCollapse = ({ expanded, setExpanded, tableTitle, fontSize, viewport }) => {
4
- const fontSizes = { small: 16, medium: 18, large: 20 }
5
5
  const titleFontSize = ['sm', 'xs', 'xxs'].includes(viewport) ? '13px' : `${fontSizes[fontSize]}px`
6
6
  return (
7
7
  <div
@@ -6,6 +6,7 @@ import { getChartCellValue } from './getChartCellValue'
6
6
  import { getDataSeriesColumns } from './getDataSeriesColumns'
7
7
  import { ReactNode } from 'react'
8
8
  import { CellMatrix, GroupCellMatrix } from '../../Table/types/CellMatrix'
9
+ import { getRowType } from './getRowType'
9
10
 
10
11
  type ChartRowsProps = DataTableProps & {
11
12
  rows: string[]
@@ -15,7 +16,8 @@ type ChartRowsProps = DataTableProps & {
15
16
  hasRowType?: boolean
16
17
  }
17
18
 
18
- const chartCellArray = ({ rows, runtimeData, config, isVertical, sortBy, colorScale, groupBy, hasRowType, viewport }: ChartRowsProps): CellMatrix | GroupCellMatrix => {
19
+ const chartCellArray = ({ rows, runtimeData, config, isVertical, sortBy, colorScale, hasRowType, viewport }: ChartRowsProps): CellMatrix | GroupCellMatrix => {
20
+ const groupBy = config.table?.groupBy
19
21
  const dataSeriesColumns = getDataSeriesColumns(config, isVertical, runtimeData)
20
22
 
21
23
  const dataSeriesColumnsSorted = () => {
@@ -57,15 +59,8 @@ const chartCellArray = ({ rows, runtimeData, config, isVertical, sortBy, colorSc
57
59
  } else {
58
60
  return rows.map(row => {
59
61
  if (hasRowType) {
60
- let rowType
61
- let rowValues = []
62
- dataSeriesColumns.forEach((column, j) => {
63
- if (column.match(/row[_-]?type/i)) {
64
- rowType = getChartCellValue(row, column, config, runtimeData)
65
- } else {
66
- rowValues.push(getChartCellValue(row, column, config, runtimeData))
67
- }
68
- })
62
+ const rowType = getRowType(runtimeData[row])
63
+ const rowValues = dataSeriesColumns.map(column => getChartCellValue(row, column, config, runtimeData))
69
64
  return [rowType, ...rowValues]
70
65
  } else {
71
66
  return dataSeriesColumns.map((column, j) => getChartCellValue(row, column, config, runtimeData))
@@ -25,7 +25,7 @@ const isAdditionalColumn = (column, config) => {
25
25
  }
26
26
 
27
27
  export const getChartCellValue = (row: string, column: string, config: TableConfig, runtimeData: Object[]) => {
28
- if (config.table.customTableConfig || config.visualizationType === 'Sankey' || runtimeData?.[0]?.tableData) return runtimeData[row][column]
28
+ if (config.visualizationType === 'Sankey' || runtimeData?.[0]?.tableData) return runtimeData[row][column]
29
29
 
30
30
  const rowObj = runtimeData[row]
31
31
  let cellValue // placeholder for formatting below
@@ -54,5 +54,29 @@ export const getChartCellValue = (row: string, column: string, config: TableConf
54
54
  }
55
55
  }
56
56
 
57
- return cellValue
57
+ // suppress cell value
58
+ config.preliminaryData?.forEach(pd => {
59
+ // check entered suppression value against cell value
60
+ const isValueMatch = String(pd.value) === String(labelValue)
61
+ // check entered suppression column against table key
62
+ const isColumnMatch = !pd.column || pd.column === column
63
+ const barSeriesExist = config.runtime?.barSeriesKeys?.includes(column)
64
+ const lineSeriesExist = config.runtime?.lineSeriesKeys?.includes(column)
65
+ const showSymbol = config.general.showSuppressedSymbol
66
+ if (isValueMatch && isColumnMatch && pd.displayTable && pd.type === 'suppression') {
67
+ switch (config.visualizationType) {
68
+ case 'Combo':
69
+ cellValue = barSeriesExist && showSymbol ? pd.iconCode : lineSeriesExist && showSymbol ? pd.lineCode : ''
70
+ break
71
+ case 'Bar':
72
+ cellValue = !showSymbol ? '' : pd.iconCode
73
+ break
74
+ case 'Line':
75
+ cellValue = !showSymbol ? '' : pd.lineCode
76
+ break
77
+ }
78
+ }
79
+ })
80
+ const shoMissingDataCellValue = config.general?.showMissingDataLabel && (!labelValue || labelValue === 'null')
81
+ return shoMissingDataCellValue ? 'N/A' : cellValue
58
82
  }
@@ -1,14 +1,14 @@
1
1
  import { TableConfig } from '../types/TableConfig'
2
+ import _ from 'lodash'
3
+ import { Column } from '../../../types/Column'
2
4
 
3
5
  export const getDataSeriesColumns = (config: TableConfig, isVertical: boolean, runtimeData: Object[]): string[] => {
4
- if (config.table.customTableConfig) return runtimeData[0] ? Object.keys(runtimeData[0]) : []
5
- if (config.type === 'table') {
6
- const excludeColumns = Object.values(config.columns)
7
- .filter(column => column.dataTable === false)
8
- .map(column => column.name)
9
- return Object.keys(runtimeData[0]).filter(columnName => !excludeColumns.includes(columnName))
10
- }
11
- let tmpSeriesColumns
6
+ if (config.visualizationType === 'Sankey') return Object.keys(config?.data?.[0]?.tableData[0])
7
+ const configColumns = _.cloneDeep(config.columns) || ({} as Record<string, Column>)
8
+ const excludeColumns = Object.values(configColumns)
9
+ .filter(column => column.dataTable === false)
10
+ .map(column => column.name)
11
+ let tmpSeriesColumns: string[] = []
12
12
  if (config.visualizationType !== 'Pie') {
13
13
  tmpSeriesColumns = isVertical ? [config.xAxis?.dataKey] : [] //, ...config.runtime.seriesLabelsAll
14
14
  if (config.series) {
@@ -22,16 +22,40 @@ export const getDataSeriesColumns = (config: TableConfig, isVertical: boolean, r
22
22
  tmpSeriesColumns = isVertical ? [config.xAxis?.dataKey, config.yAxis?.dataKey] : [config.yAxis?.dataKey]
23
23
  }
24
24
 
25
+ const dataColumns = Object.keys(runtimeData[0] || {})
26
+
25
27
  // then add the additional Columns
26
- if (config.columns && Object.keys(config.columns).length > 0) {
27
- Object.keys(config.columns).forEach(function (key) {
28
- var value = config.columns[key]
29
- // add if not the index AND it is enabled to be added to data table
30
- if (value.name !== config.xAxis?.dataKey && value.dataTable === true) {
31
- tmpSeriesColumns.push(value.name)
28
+ Object.values(configColumns).forEach(function (value) {
29
+ if (!value.name) return
30
+ // add if not the index AND it is enabled to be added to data table
31
+ const alreadyAdded = tmpSeriesColumns.includes(value.name)
32
+ const columnIsInData = dataColumns.includes(value.name) // null columns are excluded from data
33
+ if (value.name !== config.xAxis?.dataKey && value.dataTable === true && !alreadyAdded && columnIsInData) {
34
+ tmpSeriesColumns.push(value.name)
35
+ }
36
+ })
37
+
38
+ const columnOrderingHash = Object.values(configColumns).reduce((acc, column) => {
39
+ // subtract 1 to switch from cardinal positioning to index
40
+ if (column.order !== undefined) {
41
+ acc[column.name] = column.order - 1
42
+ }
43
+ return acc
44
+ }, {})
45
+
46
+ tmpSeriesColumns = tmpSeriesColumns.filter(columnName => !excludeColumns.includes(columnName))
47
+
48
+ tmpSeriesColumns.forEach((columnName, index) => {
49
+ if (columnOrderingHash[columnName] === undefined) {
50
+ if (Object.values(columnOrderingHash).includes(index)) {
51
+ // add 1 to place unsorted columns behind sorted columns
52
+ columnOrderingHash[columnName] = index + 1
53
+ } else {
54
+ columnOrderingHash[columnName] = index
32
55
  }
33
- })
34
- }
56
+ }
57
+ })
35
58
 
59
+ tmpSeriesColumns.sort((a, b) => columnOrderingHash[a] - columnOrderingHash[b])
36
60
  return tmpSeriesColumns
37
61
  }
@@ -0,0 +1,6 @@
1
+ import { RowType } from '../../Table/types/RowType'
2
+
3
+ export const getRowType = (row: Record<string, any>): RowType => {
4
+ const rowTypeKey = Object.keys(row).find(key => key.match(/row[_-]?type/i))
5
+ return row[rowTypeKey]
6
+ }
@@ -17,5 +17,6 @@ export const getSeriesName = (column: string, config: TableConfig) => {
17
17
  }
18
18
  if (config.runtimeSeriesLabels && config.runtimeSeriesLabels[column]) return config.runtimeSeriesLabels[column]
19
19
  const columnIsDataKey = column === config.xAxis?.dataKey
20
- return columnIsDataKey ? config.table.indexLabel : getLabel(column, config)
20
+ const indexLabel = config.table?.indexLabel
21
+ return columnIsDataKey && indexLabel ? indexLabel : getLabel(column, config)
21
22
  }
@@ -1,7 +1,7 @@
1
1
  type RuntimeData = Object[] & Record<string, Object>
2
2
 
3
3
  // removes null and excluded columns
4
- const customColumns = (runtimeData: Object[] | RuntimeData, excludeColumns: string[] = []): RuntimeData => {
4
+ const removeNullColumns = (runtimeData: Object[] | RuntimeData): RuntimeData => {
5
5
  if (!Array.isArray(runtimeData)) {
6
6
  // currently we don't support Record types
7
7
  return runtimeData
@@ -10,7 +10,7 @@ const customColumns = (runtimeData: Object[] | RuntimeData, excludeColumns: stri
10
10
  runtimeData.forEach(row => {
11
11
  Object.keys(row).forEach(key => {
12
12
  if (runtimeDataMemo[key] === undefined) runtimeDataMemo[key] = null
13
- if (row[key] !== null && !excludeColumns.includes(key)) runtimeDataMemo[key] = true
13
+ if (row[key] !== null) runtimeDataMemo[key] = true
14
14
  })
15
15
  })
16
16
  return runtimeData.map(d => {
@@ -24,4 +24,4 @@ const customColumns = (runtimeData: Object[] | RuntimeData, excludeColumns: stri
24
24
  }
25
25
  }
26
26
 
27
- export default customColumns
27
+ export default removeNullColumns
@@ -1,28 +1,18 @@
1
- import { Axis } from '@cdc/core/types/Axis'
2
- import { Series } from '@cdc/core/types/Series'
3
- import { Runtime } from '@cdc/core/types/Runtime'
4
- import { Table } from '@cdc/core/types/Table'
5
- import { Region } from '@cdc/core/types/Region'
1
+ import { Axis } from '../../../types/Axis'
2
+ import { Runtime } from '../../../types/Runtime'
3
+ import { Region } from '../../../types/Region'
6
4
  import { BoxPlot } from '../../../types/BoxPlot'
7
- import { General } from '../../../types/General'
8
- import { Column } from '../../../types/Column'
9
- import { Legend } from '@cdc/core/types/Legend'
5
+ import { PreliminaryDataItem } from '@cdc/chart/src/components/LineChart/LineChartProps'
6
+ import { Visualization } from '../../../types/Visualization'
10
7
 
11
- export type TableConfig = {
12
- type?: string
13
- table: Table
14
- xAxis?: Axis
15
- yAxis?: Axis
8
+ export type TableConfig = Visualization & {
16
9
  boxplot?: BoxPlot
17
- visualizationType: string
18
- general?: General
19
- columns?: Record<string, Column>
20
- legend?: Legend
21
- series?: Series
10
+ fontSize: 'small' | 'medium' | 'large'
22
11
  regions?: Region[]
23
- runtimeSeriesLabels?: Object
24
- dataFormat?: Object
25
12
  runtime?: Runtime
26
- data: Object[]
27
- fontSize: 'small' | 'medium' | 'large'
13
+ runtimeSeriesLabels?: Object
14
+ xAxis?: Axis
15
+ yAxis?: Axis
16
+ preliminaryData: PreliminaryDataItem[]
17
+ brush: { active: boolean }
28
18
  }