@cdc/core 4.25.3 → 4.25.6-1

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 (89) hide show
  1. package/assets/icon-close.svg +1 -1
  2. package/components/Alert/components/Alert.tsx +1 -1
  3. package/components/DataTable/DataTable.tsx +18 -16
  4. package/components/DataTable/DataTableStandAlone.tsx +15 -9
  5. package/components/DataTable/components/CellAnchor.tsx +1 -1
  6. package/components/DataTable/components/ChartHeader.tsx +8 -5
  7. package/components/DataTable/components/DataTableEditorPanel.tsx +25 -3
  8. package/components/DataTable/components/MapHeader.tsx +1 -0
  9. package/components/DataTable/helpers/chartCellMatrix.tsx +14 -10
  10. package/components/DataTable/helpers/getChartCellValue.ts +42 -26
  11. package/components/DataTable/helpers/mapCellMatrix.tsx +25 -7
  12. package/components/DownloadButton.tsx +17 -2
  13. package/components/EditorPanel/DataTableEditor.tsx +1 -1
  14. package/components/EditorPanel/FootnotesEditor.tsx +76 -22
  15. package/components/EditorPanel/Inputs.tsx +12 -4
  16. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +3 -2
  17. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +51 -35
  18. package/components/Filters/Filters.tsx +158 -461
  19. package/components/Filters/components/Dropdown.tsx +39 -0
  20. package/components/Filters/components/Tabs.tsx +82 -0
  21. package/components/Filters/helpers/getChangedFilters.ts +31 -0
  22. package/components/Filters/helpers/getNestedOptions.ts +2 -2
  23. package/components/Filters/helpers/handleSorting.ts +2 -2
  24. package/components/Filters/helpers/tests/getChangedFilters.test.ts +92 -0
  25. package/components/Filters/helpers/tests/getNestedOptions.test.ts +31 -0
  26. package/components/Filters/index.ts +1 -1
  27. package/components/Footnotes/Footnotes.tsx +1 -1
  28. package/components/Footnotes/FootnotesStandAlone.tsx +8 -33
  29. package/components/Layout/components/Visualization/index.tsx +4 -3
  30. package/components/Legend/Legend.Gradient.tsx +68 -24
  31. package/components/MultiSelect/MultiSelect.tsx +3 -6
  32. package/components/MultiSelect/multiselect.styles.css +2 -0
  33. package/components/NestedDropdown/NestedDropdown.tsx +21 -21
  34. package/components/RichTooltip/RichTooltip.tsx +37 -0
  35. package/components/RichTooltip/richTooltip.css +16 -0
  36. package/components/Table/Table.tsx +142 -142
  37. package/components/Table/components/Row.tsx +1 -1
  38. package/components/Table/table.styles.css +10 -0
  39. package/components/_stories/DataTable.stories.tsx +9 -2
  40. package/components/_stories/Table.stories.tsx +1 -1
  41. package/components/_stories/styles.scss +0 -4
  42. package/components/ui/Accordion.jsx +8 -1
  43. package/components/ui/Title/index.tsx +4 -1
  44. package/components/ui/Title/{Title.scss → title.styles.css} +0 -2
  45. package/components/ui/_stories/Colors.stories.mdx +220 -0
  46. package/components/ui/_stories/IconGallery.stories.mdx +14 -0
  47. package/components/ui/_stories/Title.stories.tsx +29 -4
  48. package/components/ui/accordion.styles.css +3 -0
  49. package/data/colorPalettes.js +0 -1
  50. package/dist/cove-main.css +3 -8
  51. package/dist/cove-main.css.map +1 -1
  52. package/helpers/constants.ts +6 -0
  53. package/helpers/cove/accessibility.ts +7 -8
  54. package/helpers/cove/number.ts +5 -3
  55. package/helpers/coveUpdateWorker.ts +9 -1
  56. package/helpers/filterOrderOptions.ts +17 -0
  57. package/helpers/formatConfigBeforeSave.ts +19 -32
  58. package/helpers/isNumber.ts +20 -0
  59. package/helpers/isRightAlignedTableValue.js +1 -1
  60. package/helpers/pivotData.ts +16 -11
  61. package/helpers/tests/pivotData.test.ts +74 -0
  62. package/helpers/updateFieldFactory.ts +1 -0
  63. package/helpers/ver/4.25.3.ts +25 -2
  64. package/helpers/ver/4.25.4.ts +110 -0
  65. package/helpers/ver/4.25.6.ts +36 -0
  66. package/helpers/ver/4.25.7.ts +26 -0
  67. package/helpers/ver/tests/4.25.4.test.ts +89 -0
  68. package/helpers/ver/tests/4.25.6.test.ts +84 -0
  69. package/helpers/viewports.ts +4 -0
  70. package/package.json +7 -6
  71. package/styles/_global-variables.scss +3 -0
  72. package/styles/_global.scss +0 -4
  73. package/styles/_reset.scss +0 -6
  74. package/styles/filters.scss +0 -4
  75. package/styles/v2/main.scss +0 -5
  76. package/types/Axis.ts +2 -0
  77. package/types/DataSet.ts +14 -0
  78. package/types/Footnotes.ts +5 -2
  79. package/types/General.ts +1 -0
  80. package/types/Legend.ts +1 -0
  81. package/types/Table.ts +1 -0
  82. package/types/Visualization.ts +3 -12
  83. package/types/VizFilter.ts +3 -0
  84. package/components/ui/_stories/Colors.stories.tsx +0 -92
  85. package/components/ui/_stories/Icon.stories.tsx +0 -29
  86. package/helpers/cove/fontSettings.ts +0 -2
  87. package/helpers/isNumber.js +0 -24
  88. package/helpers/isNumberLog.js +0 -18
  89. /package/helpers/{fetchRemoteData.js → fetchRemoteData.ts} +0 -0
@@ -1,3 +1,3 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
1
+ <svg xmlns="http://www.w3.org/2000/svg" height="1em" width="1em" viewBox="0 0 24 24" fill="currentColor">
2
2
  <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>
3
3
  </svg>
@@ -59,7 +59,7 @@ const Alert: React.FC<AlertProps> = ({
59
59
  <span dangerouslySetInnerHTML={sanitizedData()} />
60
60
  </div>
61
61
  {showCloseButton && (
62
- <button type='button' className='close pl-5' aria-label='Close' onClick={() => onDismiss()}>
62
+ <button type='button' className='close ps-5' aria-label='Close' onClick={() => onDismiss()}>
63
63
  X
64
64
  </button>
65
65
  )}
@@ -27,7 +27,6 @@ import _ from 'lodash'
27
27
  import { getDataSeriesColumns } from './helpers/getDataSeriesColumns'
28
28
 
29
29
  export type DataTableProps = {
30
- applyLegendToRow?: Function
31
30
  colorScale?: Function
32
31
  columns?: Record<string, Column>
33
32
  config: TableConfig
@@ -35,7 +34,7 @@ export type DataTableProps = {
35
34
  defaultSortBy?: string
36
35
  displayGeoName?: (row: string) => string
37
36
  expandDataTable: boolean
38
- formatLegendLocation?: (row: string) => string
37
+ formatLegendLocation?: (row: string, runtimeLookup: string) => string
39
38
  groupBy?: string
40
39
  headerColor?: string
41
40
  imageRef?: string
@@ -58,20 +57,20 @@ export type DataTableProps = {
58
57
  /* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
59
58
  const DataTable = (props: DataTableProps) => {
60
59
  const {
60
+ columns,
61
61
  config,
62
62
  dataConfig,
63
63
  defaultSortBy,
64
64
  displayGeoName,
65
- tableTitle,
66
- vizTitle,
67
- rawData,
68
- runtimeData: parentRuntimeData,
69
- headerColor,
70
65
  expandDataTable,
71
- columns,
72
- viewport,
73
66
  formatLegendLocation,
67
+ headerColor,
68
+ rawData,
69
+ runtimeData: parentRuntimeData,
74
70
  tabbingId,
71
+ tableTitle,
72
+ viewport,
73
+ vizTitle,
75
74
  wrapColumns
76
75
  } = props
77
76
  const runtimeData = useMemo(() => {
@@ -127,9 +126,6 @@ const DataTable = (props: DataTableProps) => {
127
126
  case 'Box Plot':
128
127
  if (!config.boxplot) return <Loading />
129
128
  break
130
- case 'Line' || 'Bar' || 'Combo' || 'Pie' || 'Deviation Bar' || 'Paired Bar' || 'Sankey' || 'Table':
131
- if (!runtimeData) return <Loading />
132
- break
133
129
  default:
134
130
  if (!runtimeData) return <Loading />
135
131
  break
@@ -248,9 +244,14 @@ const DataTable = (props: DataTableProps) => {
248
244
  const csvData = config.table?.downloadVisibleDataOnly ? visibleData : rawData
249
245
 
250
246
  // only use fullGeoName on County maps and no other
251
- if (config.general?.geoType === 'us-county') {
247
+ if (config.general?.geoType === 'us-county' || config.table.showFullGeoNameInCSV) {
252
248
  // Add column for full Geo name along with State
253
- return csvData.map(row => ({ FullGeoName: formatLegendLocation(row[config.columns.geo.name]), ...row }))
249
+ return csvData.map(row => {
250
+ return {
251
+ FullGeoName: formatLegendLocation(row[config.columns.geo.name]),
252
+ ...row
253
+ }
254
+ })
254
255
  } else {
255
256
  return csvData
256
257
  }
@@ -275,7 +276,7 @@ const DataTable = (props: DataTableProps) => {
275
276
 
276
277
  const childrenMatrix =
277
278
  config.type === 'map'
278
- ? mapCellMatrix({ rows, wrapColumns, ...props, runtimeData, viewport })
279
+ ? mapCellMatrix({ ...props, rows, wrapColumns, runtimeData, viewport })
279
280
  : chartCellMatrix({ rows, ...props, runtimeData, isVertical, sortBy, hasRowType, viewport })
280
281
 
281
282
  // If every value in a column is a number, record the column index so the header and cells can be right-aligned
@@ -352,7 +353,8 @@ const DataTable = (props: DataTableProps) => {
352
353
  }`,
353
354
  'aria-live': 'assertive',
354
355
  'aria-rowcount': config?.data?.length ? config.data.length : -1,
355
- hidden: !expanded
356
+ hidden: !expanded,
357
+ cellMinWidth: 100
356
358
  }}
357
359
  rightAlignedCols={rightAlignedCols}
358
360
  />
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useState } from 'react'
2
2
  import { ViewPort } from '../../types/ViewPort'
3
+ import Footnotes from '../../types/Footnotes'
3
4
  import EditorWrapper from '../EditorWrapper/EditorWrapper'
4
5
  import DataTable from './DataTable'
5
6
  import DataTableEditorPanel from './components/DataTableEditorPanel'
@@ -7,6 +8,8 @@ import Filters from '../Filters'
7
8
  import { TableConfig } from './types/TableConfig'
8
9
  import { filterVizData } from '../../helpers/filterVizData'
9
10
  import { addValuesToFilters } from '../../helpers/addValuesToFilters'
11
+ import FootnotesStandAlone from '../Footnotes/FootnotesStandAlone'
12
+ import { Datasets } from '@cdc/core/types/DataSet'
10
13
 
11
14
  type StandAloneProps = {
12
15
  visualizationKey: string
@@ -14,6 +17,7 @@ type StandAloneProps = {
14
17
  viewport?: ViewPort
15
18
  isEditor?: boolean
16
19
  updateConfig?: (Visualization) => void
20
+ datasets?: Datasets
17
21
  }
18
22
 
19
23
  const DataTableStandAlone: React.FC<StandAloneProps> = ({
@@ -21,7 +25,8 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({
21
25
  config,
22
26
  updateConfig,
23
27
  viewport,
24
- isEditor
28
+ isEditor,
29
+ datasets
25
30
  }) => {
26
31
  const [filteredData, setFilteredData] = useState<Record<string, any>[]>(
27
32
  filterVizData(config.filters, config.formattedData || config.data)
@@ -33,6 +38,12 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({
33
38
  setFilteredData(filterVizData(filters, config?.formattedData?.length > 0 ? config.formattedData : config.data))
34
39
  }, [config.filters])
35
40
 
41
+ const setFilters = (newFilters: any) => {
42
+ const filters = addValuesToFilters(newFilters, config.data)
43
+ updateConfig({ ...config, filters })
44
+ setFilteredData(filterVizData(filters, config?.formattedData?.length > 0 ? config.formattedData : config.data))
45
+ }
46
+
36
47
  if (isEditor)
37
48
  return (
38
49
  <EditorWrapper
@@ -43,19 +54,13 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({
43
54
  type={'Table'}
44
55
  viewport={viewport}
45
56
  >
46
- <DataTableEditorPanel key={visualizationKey} config={config} updateConfig={updateConfig} />
57
+ <DataTableEditorPanel key={visualizationKey} config={config} updateConfig={updateConfig} datasets={datasets} />
47
58
  </EditorWrapper>
48
59
  )
49
60
 
50
61
  return (
51
62
  <>
52
- <Filters
53
- config={config}
54
- setConfig={updateConfig}
55
- setFilteredData={setFilteredData}
56
- filteredData={filteredData}
57
- excludedData={config.formattedData}
58
- />
63
+ <Filters config={config} setFilters={setFilters} excludedData={config.formattedData} />
59
64
  <DataTable
60
65
  expandDataTable={config.table.expanded}
61
66
  config={config}
@@ -65,6 +70,7 @@ const DataTableStandAlone: React.FC<StandAloneProps> = ({
65
70
  tableTitle={config.table.label}
66
71
  viewport={viewport || 'lg'}
67
72
  />
73
+ <FootnotesStandAlone config={config.footnotes} filters={config.filters?.filter(f => f.filterFootnotes)} />
68
74
  </>
69
75
  )
70
76
  }
@@ -6,7 +6,7 @@ const CellAnchor = ({ markup, row, columns, navigationHandler, mapZoomHandler })
6
6
  if (columns.navigate && row[columns.navigate.name]) {
7
7
  return (
8
8
  <span
9
- onClick={() => navigationHandler(row[columns.navigate.name])}
9
+ onClick={() => navigationHandler('_blank', row[columns.navigate.name])}
10
10
  className='table-link'
11
11
  title='Click for more information (Opens in a new window)'
12
12
  role='link'
@@ -4,6 +4,7 @@ import { getDataSeriesColumns } from '../helpers/getDataSeriesColumns'
4
4
  import ScreenReaderText from '@cdc/core/components/elements/ScreenReaderText'
5
5
  import { SortIcon } from './SortIcon'
6
6
  import { getNewSortBy } from '../helpers/getNewSortBy'
7
+ import parse from 'html-react-parser'
7
8
 
8
9
  type ChartHeaderProps = { data; isVertical; config; setSortBy; sortBy; hasRowType?; viewport; rightAlignedCols }
9
10
 
@@ -47,9 +48,8 @@ const ChartHeader = ({
47
48
  if (columnHeaderText === notApplicableText) return
48
49
 
49
50
  return (
50
- <span className='cdcdataviz-sr-only'>{`Press command, modifier, or enter key to sort by ${columnHeaderText} in ${
51
- sortBy.column !== columnHeaderText ? 'ascending' : sortBy.column === 'desc' ? 'descending' : 'ascending'
52
- } order`}</span>
51
+ <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'
52
+ } order`}</span>
53
53
  )
54
54
  }
55
55
 
@@ -75,7 +75,7 @@ const ChartHeader = ({
75
75
  return (
76
76
  <tr>
77
77
  {dataSeriesColumns.map((column, index) => {
78
- const text = getSeriesName(column, config)
78
+ const text = parse(getSeriesName(column, config))
79
79
  const newSortBy = getNewSortBy(sortBy, column, index)
80
80
  const sortByAsc = sortBy.column === column ? sortBy.asc : undefined
81
81
  const isSortedCol = column === sortBy.column && !hasRowType
@@ -120,8 +120,11 @@ const ChartHeader = ({
120
120
  return (
121
121
  <tr>
122
122
  {['__series__', ...Object.keys(data)].slice(sliceVal).map((row, index) => {
123
+ const rightAxisItems = config.series?.filter(item => item?.axis === 'Right') || []
124
+ const rightAxisItemsMap = new Map(rightAxisItems.map(item => [item.dataKey, item]))
125
+
123
126
  let column = config.xAxis?.dataKey
124
- let text = row !== '__series__' ? getChartCellValue(row, column, config, data) : '__series__'
127
+ let text = row !== '__series__' ? getChartCellValue(row, column, config, data, rightAxisItemsMap) : '__series__'
125
128
  const newSortBy = getNewSortBy(sortBy, column, index)
126
129
  const sortByAsc = sortBy.colIndex === index ? sortBy.asc : undefined
127
130
  const isSortedCol = index === sortBy.colIndex && !hasRowType
@@ -1,4 +1,10 @@
1
- import { Accordion, AccordionItem, AccordionItemButton, AccordionItemHeading, AccordionItemPanel } from 'react-accessible-accordion'
1
+ import {
2
+ Accordion,
3
+ AccordionItem,
4
+ AccordionItemButton,
5
+ AccordionItemHeading,
6
+ AccordionItemPanel
7
+ } from 'react-accessible-accordion'
2
8
  import DataTableEditor from '../../EditorPanel/DataTableEditor'
3
9
  import { Visualization } from '@cdc/core/types/Visualization'
4
10
  import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
@@ -6,13 +12,16 @@ import { useMemo } from 'react'
6
12
  import ColumnsEditor from '../../EditorPanel/ColumnsEditor'
7
13
  import VizFilterEditor from '../../EditorPanel/VizFilterEditor'
8
14
  import _ from 'lodash'
15
+ import FootnotesEditor from '../../EditorPanel/FootnotesEditor'
16
+ import { Datasets } from '@cdc/core/types/DataSet'
9
17
 
10
18
  type DataTableEditorProps = {
11
19
  config: Visualization
12
20
  updateConfig: Function
21
+ datasets?: Datasets
13
22
  }
14
23
 
15
- const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateConfig }) => {
24
+ const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateConfig, datasets }) => {
16
25
  const updateField = useMemo(() => updateFieldFactory(config, updateConfig), [JSON.stringify(config)])
17
26
  const deleteColumn = columnName => {
18
27
  const newColumns = _.cloneDeep(config.columns)
@@ -33,7 +42,12 @@ const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateCo
33
42
  <AccordionItemButton>Filters</AccordionItemButton>
34
43
  </AccordionItemHeading>
35
44
  <AccordionItemPanel>
36
- <VizFilterEditor config={config} updateField={updateField} rawData={config.originalFormattedData} />
45
+ <VizFilterEditor
46
+ config={config}
47
+ updateField={updateField}
48
+ rawData={config.originalFormattedData}
49
+ hasFootnotes={true}
50
+ />
37
51
  </AccordionItemPanel>
38
52
  </AccordionItem>
39
53
  <AccordionItem>
@@ -52,6 +66,14 @@ const DataTableEditorPanel: React.FC<DataTableEditorProps> = ({ config, updateCo
52
66
  <DataTableEditor config={config} columns={columns} updateField={updateField} isDashboard={true} />
53
67
  </AccordionItemPanel>
54
68
  </AccordionItem>
69
+ <AccordionItem>
70
+ <AccordionItemHeading>
71
+ <AccordionItemButton>Footnotes</AccordionItemButton>
72
+ </AccordionItemHeading>
73
+ <AccordionItemPanel>
74
+ <FootnotesEditor config={config} updateField={updateField} datasets={datasets} />
75
+ </AccordionItemPanel>
76
+ </AccordionItem>
55
77
  </Accordion>
56
78
  )
57
79
  }
@@ -36,6 +36,7 @@ const MapHeader = ({ columns, config, indexTitle, sortBy, setSortBy, rightAligne
36
36
  return (
37
37
  <th
38
38
  style={{
39
+ minWidth: (config.table.cellMinWidth || 0) + 'px',
39
40
  textAlign: rightAlignedCols && rightAlignedCols[index] ? 'right' : '',
40
41
  paddingRight: '1.3em'
41
42
  }}
@@ -7,6 +7,7 @@ import { getDataSeriesColumns } from './getDataSeriesColumns'
7
7
  import { ReactNode } from 'react'
8
8
  import { CellMatrix, GroupCellMatrix } from '../../Table/types/CellMatrix'
9
9
  import { getRowType } from './getRowType'
10
+ import parse from 'html-react-parser'
10
11
 
11
12
  type ChartRowsProps = DataTableProps & {
12
13
  rows: string[]
@@ -25,9 +26,12 @@ const chartCellArray = ({
25
26
  colorScale,
26
27
  hasRowType
27
28
  }: ChartRowsProps): CellMatrix | GroupCellMatrix => {
29
+ const rightAxisItems = config.series?.filter(item => item?.axis === 'Right') || []
30
+ const rightAxisItemsMap = new Map(rightAxisItems.map(item => [item.dataKey, item]))
28
31
  const groupBy = config.table?.groupBy
29
32
  const dataSeriesColumns = getDataSeriesColumns(config, isVertical, runtimeData)
30
33
 
34
+
31
35
  const dataSeriesColumnsSorted = () => {
32
36
  if (!sortBy && sortBy.colIndex === null) return dataSeriesColumns
33
37
  return dataSeriesColumns.sort((a, b) => {
@@ -52,9 +56,9 @@ const chartCellArray = ({
52
56
  let groupValues = []
53
57
  dataSeriesColumns.forEach((column, j) => {
54
58
  if (groupBy === column) {
55
- groupKey = getChartCellValue(row, column, config, runtimeData)
59
+ groupKey = getChartCellValue(row, column, config, runtimeData, rightAxisItemsMap)
56
60
  } else {
57
- groupValues.push(getChartCellValue(row, column, config, runtimeData))
61
+ groupValues.push(getChartCellValue(row, column, config, runtimeData, rightAxisItemsMap))
58
62
  }
59
63
  })
60
64
  if (!cellMatrix.has(groupKey)) {
@@ -69,10 +73,10 @@ const chartCellArray = ({
69
73
  return rows.map(row => {
70
74
  if (hasRowType) {
71
75
  const rowType = getRowType(runtimeData[row])
72
- const rowValues = dataSeriesColumns.map(column => getChartCellValue(row, column, config, runtimeData))
76
+ const rowValues = dataSeriesColumns.map(column => getChartCellValue(row, column, config, runtimeData, rightAxisItemsMap))
73
77
  return [rowType, ...rowValues]
74
78
  } else {
75
- return dataSeriesColumns.map((column, j) => getChartCellValue(row, column, config, runtimeData))
79
+ return dataSeriesColumns.map((column, j) => getChartCellValue(row, column, config, runtimeData, rightAxisItemsMap))
76
80
  }
77
81
  })
78
82
  }
@@ -82,13 +86,13 @@ const chartCellArray = ({
82
86
  let nodes: ReactNode[] =
83
87
  config.visualizationType !== 'Pie'
84
88
  ? [
85
- <>
86
- {colorScale && colorScale(seriesName) && <LegendShape fill={colorScale(seriesName)} />}
87
- {seriesName}
88
- </>
89
- ]
89
+ <>
90
+ {colorScale && colorScale(seriesName) && <LegendShape fill={colorScale(seriesName)} />}
91
+ {parse(seriesName)}
92
+ </>
93
+ ]
90
94
  : []
91
- return nodes.concat(rows.map((row, i) => getChartCellValue(row, column, config, runtimeData)))
95
+ return nodes.concat(rows.map((row, i) => getChartCellValue(row, column, config, runtimeData, rightAxisItemsMap)))
92
96
  })
93
97
  }
94
98
  }
@@ -1,6 +1,7 @@
1
1
  import { parseDate, formatDate } from '@cdc/core/helpers/cove/date'
2
2
  import { formatNumber } from '../../../helpers/cove/number'
3
3
  import { TableConfig } from '../types/TableConfig'
4
+ import _ from 'lodash'
4
5
 
5
6
  const isPivotColumn = (columnName, config) => {
6
7
  const tableHasPivotColumnConfigured = config.table.pivot?.valueColumns?.length
@@ -30,16 +31,28 @@ const isAdditionalColumn = (column: string, config, rowData) => {
30
31
  return formattingParams
31
32
  }
32
33
 
33
- export const getChartCellValue = (row: string, column: string, config: TableConfig, runtimeData: Object[]) => {
34
- if (config.visualizationType === 'Sankey' || runtimeData?.[0]?.tableData) return runtimeData[row][column]
34
+ export const getChartCellValue = (row: string, column: string, config: TableConfig, runtimeData: Object[], rightAxisItemsMap) => {
35
35
 
36
+ // Variables for xAxis config
37
+ const { type, dateDisplayFormat, dateParseFormat, dataKey: xAxisDataKey } = config.xAxis || {}
38
+ const { showMissingDataLabel } = config.general || {}
39
+ const { visualizationType } = config || {}
40
+
41
+ // Early returns...
36
42
  const rowObj = runtimeData[row]
37
- let cellValue // placeholder for formatting below
38
- const labelValue = rowObj[column] // just raw X axis string
39
- if (column === config.xAxis?.dataKey) {
40
- const { type, dateDisplayFormat, dateParseFormat } = config.xAxis || {}
41
- const dateFormat = config.table?.dateDisplayFormat || dateDisplayFormat
43
+ if (!rowObj) return
44
+
45
+ if (visualizationType === 'Sankey' || runtimeData?.[0]?.tableData) {
46
+ return runtimeData?.[row]?.[column]
47
+ }
42
48
 
49
+ let cellValue
50
+ const rightMatch = rightAxisItemsMap.get(column)
51
+ const resolvedAxis = rightMatch ? 'right' : 'left'
52
+ const labelValue = rowObj[column]
53
+
54
+ if (column === xAxisDataKey) {
55
+ const dateFormat = config.table?.dateDisplayFormat || dateDisplayFormat
43
56
  if (type === 'date' || type === 'date-time') {
44
57
  cellValue = formatDate(dateFormat, parseDate(dateParseFormat, labelValue))
45
58
  } else if (type === 'continuous') {
@@ -48,27 +61,30 @@ export const getChartCellValue = (row: string, column: string, config: TableConf
48
61
  cellValue = labelValue
49
62
  }
50
63
  } else {
51
- let resolvedAxis = 'left'
52
- let leftAxisItems = config.series ? config.series.filter(item => item?.axis === 'Left') : []
53
- let rightAxisItems = config.series ? config.series.filter(item => item?.axis === 'Right') : []
54
64
 
55
- leftAxisItems.map(leftSeriesItem => {
56
- if (leftSeriesItem.dataKey === column) resolvedAxis = 'left'
57
- })
65
+ let addColParams = isAdditionalColumn(column, config, rowObj)
58
66
 
59
- rightAxisItems.map(rightSeriesItem => {
60
- if (rightSeriesItem.dataKey === column) resolvedAxis = 'right'
61
- })
67
+ let piePercent = 0
68
+ if (config.visualizationType === 'Pie' && !config.dataFormat.showPiePercent) {
69
+ piePercent = (_.toNumber(runtimeData[row][column]) / _.sumBy(runtimeData, d => _.toNumber(d[column]))) * 100 || 0
70
+ }
62
71
 
63
- let addColParams = isAdditionalColumn(column, config, rowObj)
64
- if (addColParams) {
65
- cellValue = config.dataFormat
66
- ? formatNumber(runtimeData[row][column], resolvedAxis, false, config, addColParams)
67
- : runtimeData[row][column]
72
+ const valueToFormat = config.visualizationType === 'Pie' && !config.dataFormat.showPiePercent
73
+ ? piePercent
74
+ : runtimeData[row][column]
75
+
76
+ const hasAdditionalParams = Object.keys(addColParams).length > 0
77
+
78
+ if (config.dataFormat) {
79
+ cellValue = formatNumber(
80
+ valueToFormat,
81
+ resolvedAxis,
82
+ false,
83
+ config,
84
+ hasAdditionalParams ? addColParams : undefined
85
+ )
68
86
  } else {
69
- cellValue = config.dataFormat
70
- ? formatNumber(runtimeData[row][column], resolvedAxis, false, config)
71
- : runtimeData[row][column]
87
+ cellValue = runtimeData[row][column]
72
88
  }
73
89
  }
74
90
 
@@ -101,6 +117,6 @@ export const getChartCellValue = (row: string, column: string, config: TableConf
101
117
  }
102
118
  }
103
119
  })
104
- const shoMissingDataCellValue = config.general?.showMissingDataLabel && (!labelValue || labelValue === 'null')
105
- return shoMissingDataCellValue ? 'N/A' : cellValue
120
+ const showMissingDataCellValue = showMissingDataLabel && (!labelValue || labelValue === 'null')
121
+ return showMissingDataCellValue ? 'N/A' : cellValue
106
122
  }
@@ -4,6 +4,8 @@ import { DataTableProps } from '../DataTable'
4
4
  import { ReactNode } from 'react'
5
5
  import { displayDataAsText } from '@cdc/core/helpers/displayDataAsText'
6
6
  import _ from 'lodash'
7
+ import { applyLegendToRow } from '@cdc/map/src/helpers/applyLegendToRow'
8
+ import { hashObj } from '@cdc/map/src/helpers'
7
9
 
8
10
  type MapRowsProps = DataTableProps & {
9
11
  rows: string[]
@@ -72,27 +74,43 @@ const mapCellArray = ({
72
74
  columns,
73
75
  runtimeData,
74
76
  config,
75
- applyLegendToRow,
76
77
  displayGeoName,
77
78
  formatLegendLocation,
78
79
  navigationHandler,
79
- setFilteredCountryCode
80
+ setFilteredCountryCode,
81
+ legendMemo,
82
+ legendSpecialClassLastMemo,
83
+ runtimeLegend
80
84
  }: MapRowsProps): ReactNode[][] => {
85
+ const { allowMapZoom, geoType, type } = config.general
81
86
  return rows.map(row =>
82
87
  Object.keys(columns)
83
88
  .filter(column => columns[column].dataTable === true && columns[column].name)
84
89
  .map(column => {
85
90
  if (column === 'geo') {
86
91
  const rowObj = runtimeData[row]
87
- const legendColor = applyLegendToRow(rowObj)
92
+ if (!rowObj) {
93
+ throw new Error('No row object found')
94
+ }
95
+
96
+ const legendColor = applyLegendToRow(rowObj, config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
97
+ const noColor = !legendMemo.current.has(hashObj(rowObj))
98
+
99
+ if (!legendColor) {
100
+ console.error('No legend color found') // eslint-disable-line no-console
101
+ }
88
102
  const labelValue = getGeoLabel(config, row, formatLegendLocation, displayGeoName)
89
103
  const mapZoomHandler =
90
- config.general.type === 'bubble' && config.general.allowMapZoom && config.general.geoType === 'world'
91
- ? () => setFilteredCountryCode(row)
92
- : undefined
104
+ type === 'bubble' && allowMapZoom && geoType === 'world' ? () => setFilteredCountryCode(row) : undefined
105
+
106
+ const validColor = legendColor && legendColor.length > 0 && !noColor
93
107
  return (
94
108
  <div className='col-12'>
95
- <LegendShape fill={legendColor[0]} />
109
+ {validColor ? (
110
+ <LegendShape fill={legendColor[0]} />
111
+ ) : (
112
+ <div className='d-inline-block me-2' style={{ width: '1rem', height: '1rem' }} />
113
+ )}
96
114
  <CellAnchor
97
115
  markup={labelValue}
98
116
  row={rowObj}
@@ -9,7 +9,12 @@ type DownloadButtonProps = {
9
9
 
10
10
  const DownloadButton = ({ rawData, fileName, headerColor, skipId }: DownloadButtonProps) => {
11
11
  const csvData = Papa.unparse(rawData)
12
- const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' })
12
+ // Prepend a Byte Order Mark (BOM) to the CSV data.
13
+ // The BOM is a special marker that helps applications like Excel recognize the file as UTF-8 encoded.
14
+ // Adding the BOM ensures that Excel interprets special characters correctly.
15
+ const bom = '\uFEFF'
16
+ const utf8EncodedCsvData = new TextEncoder().encode(bom + csvData)
17
+ const blob = new Blob([utf8EncodedCsvData], { type: 'text/csv;charset=utf-8;' })
13
18
 
14
19
  const saveBlob = () => {
15
20
  //@ts-ignore
@@ -20,7 +25,17 @@ const DownloadButton = ({ rawData, fileName, headerColor, skipId }: DownloadButt
20
25
  }
21
26
 
22
27
  return (
23
- <a download={fileName} type='button' onClick={saveBlob} href={URL.createObjectURL(blob)} aria-label='Download this data in a CSV file format.' className={`${headerColor} no-border`} id={`${skipId}`} data-html2canvas-ignore role='button'>
28
+ <a
29
+ download={fileName}
30
+ type='button'
31
+ onClick={saveBlob}
32
+ href={URL.createObjectURL(blob)}
33
+ aria-label='Download this data in a CSV file format.'
34
+ className={`${headerColor} no-border`}
35
+ id={`${skipId}`}
36
+ data-html2canvas-ignore
37
+ role='button'
38
+ >
24
39
  Download Data (CSV)
25
40
  </a>
26
41
  )
@@ -196,7 +196,7 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
196
196
  />
197
197
  )}
198
198
  {config?.visualizationType !== 'Sankey' && (
199
- <label>
199
+ <label onClick={e => e.preventDefault()}>
200
200
  <span className='edit-label column-heading mt-1'>Exclude Columns </span>
201
201
  <MultiSelect
202
202
  key={excludedColumns.join('') + 'excluded'}