@cdc/core 4.25.3-6 → 4.25.5-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 (90) hide show
  1. package/assets/icon-close.svg +1 -1
  2. package/components/AdvancedEditor/AdvancedEditor.tsx +11 -9
  3. package/components/DataTable/DataTable.tsx +50 -31
  4. package/components/DataTable/components/CellAnchor.tsx +1 -1
  5. package/components/DataTable/components/ChartHeader.tsx +3 -2
  6. package/components/DataTable/components/MapHeader.tsx +1 -0
  7. package/components/DataTable/helpers/chartCellMatrix.tsx +2 -1
  8. package/components/DataTable/helpers/getChartCellValue.ts +11 -5
  9. package/components/DataTable/helpers/getDataSeriesColumns.ts +7 -3
  10. package/components/DataTable/helpers/mapCellMatrix.tsx +80 -39
  11. package/components/DataTable/helpers/tests/mapCellMatrix.test.ts +80 -0
  12. package/components/DownloadButton.tsx +17 -2
  13. package/components/EditorPanel/DataTableEditor.tsx +29 -19
  14. package/components/EditorPanel/Inputs.tsx +13 -4
  15. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +2 -1
  16. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +26 -1
  17. package/components/Filters/Filters.tsx +172 -421
  18. package/components/Filters/components/Dropdown.tsx +39 -0
  19. package/components/Filters/components/Tabs.tsx +82 -0
  20. package/components/Filters/helpers/getChangedFilters.ts +31 -0
  21. package/components/Filters/helpers/getNestedOptions.ts +2 -2
  22. package/components/Filters/helpers/getNewRuntime.ts +35 -0
  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/helpers/tests/getNewRuntime.test.ts +82 -0
  27. package/components/Filters/index.ts +1 -1
  28. package/components/Layout/components/Visualization/index.tsx +3 -3
  29. package/components/Layout/components/Visualization/visualizations.scss +1 -1
  30. package/components/Legend/Legend.Gradient.tsx +66 -23
  31. package/components/MediaControls.jsx +14 -7
  32. package/components/MultiSelect/multiselect.styles.css +2 -0
  33. package/components/NestedDropdown/NestedDropdown.tsx +2 -2
  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/elements/Button.jsx +4 -2
  43. package/components/ui/Accordion.jsx +8 -1
  44. package/components/ui/Title/index.tsx +4 -1
  45. package/components/ui/Title/{Title.scss → title.styles.css} +0 -2
  46. package/components/ui/_stories/Colors.stories.mdx +220 -0
  47. package/components/ui/_stories/IconGallery.stories.mdx +14 -0
  48. package/components/ui/_stories/Title.stories.tsx +29 -4
  49. package/components/ui/accordion.styles.css +3 -0
  50. package/data/colorPalettes.js +0 -1
  51. package/dist/cove-main.css +101 -159
  52. package/dist/cove-main.css.map +1 -1
  53. package/helpers/DataTransform.ts +2 -2
  54. package/helpers/addValuesToFilters.ts +1 -1
  55. package/helpers/constants.ts +6 -0
  56. package/helpers/cove/accessibility.ts +7 -8
  57. package/helpers/coveUpdateWorker.ts +17 -8
  58. package/helpers/filterOrderOptions.ts +17 -0
  59. package/helpers/formatConfigBeforeSave.ts +30 -8
  60. package/helpers/isNumber.ts +20 -0
  61. package/helpers/isRightAlignedTableValue.js +6 -2
  62. package/helpers/isSolr.ts +13 -0
  63. package/helpers/labelHash.ts +21 -0
  64. package/helpers/pivotData.ts +30 -18
  65. package/helpers/tests/formatConfigBeforeSave.test.ts +68 -0
  66. package/helpers/tests/pivotData.test.ts +96 -18
  67. package/helpers/ver/4.25.3.ts +43 -0
  68. package/helpers/ver/4.25.4.ts +33 -0
  69. package/helpers/ver/tests/4.25.4.test.ts +24 -0
  70. package/helpers/ver/tests/versionNeedsUpdate.test.ts +28 -0
  71. package/helpers/viewports.ts +4 -0
  72. package/package.json +2 -3
  73. package/styles/_global-variables.scss +5 -1
  74. package/styles/_global.scss +18 -9
  75. package/styles/_reset.scss +0 -6
  76. package/styles/base.scss +42 -0
  77. package/styles/filters.scss +5 -11
  78. package/styles/v2/components/button.scss +48 -12
  79. package/styles/v2/main.scss +0 -5
  80. package/styles/v2/themes/_color-definitions.scss +1 -4
  81. package/types/General.ts +1 -1
  82. package/types/Legend.ts +1 -0
  83. package/types/Table.ts +2 -0
  84. package/LICENSE +0 -201
  85. package/components/ui/_stories/Colors.stories.tsx +0 -92
  86. package/components/ui/_stories/Icon.stories.tsx +0 -29
  87. package/helpers/cove/fontSettings.ts +0 -2
  88. package/helpers/isNumber.js +0 -24
  89. package/helpers/isNumberLog.js +0 -18
  90. package/helpers/isSolr.js +0 -13
@@ -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>
@@ -35,20 +35,22 @@ export const AdvancedEditor = ({ loadConfig, config, convertStateToConfig, onExp
35
35
  }, [config])
36
36
 
37
37
  const typeLookup = {
38
- chart: ['Charts', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/bar-chart.html', <ChartIcon />],
39
- dashboard: ['Dashboard', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/bar-chart.html', <ChartIcon />],
40
- map: ['Maps', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/data-map.html', <MapIcon />],
41
- 'markup-include': [
42
- 'Markup Include',
43
- 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/Markup-Include.html',
44
- <MarkupIncludeIcon />
45
- ]
38
+ chart: ['Charts', 'https://www.cdc.gov/cove/index.html', <ChartIcon />],
39
+ dashboard: ['Dashboard', 'https://www.cdc.gov/cove/index.html', <ChartIcon />],
40
+ map: ['Maps', 'https://www.cdc.gov/cove/index.html', <MapIcon />],
41
+ 'markup-include': ['Markup Include', 'https://www.cdc.gov/cove/index.html', <MarkupIncludeIcon />]
46
42
  }
47
43
 
48
44
  if (!config.type) return <></>
49
45
  return (
50
46
  <>
51
- <a href={typeLookup[config.type][1]} target='_blank' rel='noopener noreferrer' className='guidance-link'>
47
+ <a
48
+ href={typeLookup[config.type][1]}
49
+ target='_blank'
50
+ rel='noopener noreferrer'
51
+ className='guidance-link'
52
+ style={{ cursor: 'pointer !important' }}
53
+ >
52
54
  {typeLookup[config.type][2]}
53
55
  <div>
54
56
  <span className='heading-3'>Get Help with {typeLookup[config.type][0]}</span>
@@ -11,7 +11,7 @@ import BoxplotHeader from './components/BoxplotHeader'
11
11
  import MapHeader from './components/MapHeader'
12
12
  import SkipTo from '../elements/SkipTo'
13
13
  import ExpandCollapse from './components/ExpandCollapse'
14
- import mapCellMatrix from './helpers/mapCellMatrix'
14
+ import mapCellMatrix, { getMapRowData } from './helpers/mapCellMatrix'
15
15
  import Table from '../Table'
16
16
  import chartCellMatrix from './helpers/chartCellMatrix'
17
17
  import regionCellMatrix from './helpers/regionCellMatrix'
@@ -24,17 +24,17 @@ import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
24
24
  import isRightAlignedTableValue from '@cdc/core/helpers/isRightAlignedTableValue'
25
25
  import './data-table.css'
26
26
  import _ from 'lodash'
27
+ import { getDataSeriesColumns } from './helpers/getDataSeriesColumns'
27
28
 
28
29
  export type DataTableProps = {
29
- applyLegendToRow?: Function
30
30
  colorScale?: Function
31
31
  columns?: Record<string, Column>
32
32
  config: TableConfig
33
33
  dataConfig?: Object
34
34
  defaultSortBy?: string
35
- displayGeoName?: Function
35
+ displayGeoName?: (row: string) => string
36
36
  expandDataTable: boolean
37
- formatLegendLocation?: Function
37
+ formatLegendLocation?: (row: string, runtimeLookup: string) => string
38
38
  groupBy?: string
39
39
  headerColor?: string
40
40
  imageRef?: string
@@ -45,8 +45,7 @@ export type DataTableProps = {
45
45
  outerContainerRef?: Function
46
46
  rawData: Object[]
47
47
  runtimeData: Object[] & Record<string, Object>
48
- setFilteredCountryCode?: Function // used for Maps only
49
- showDownloadButton?: boolean
48
+ setFilteredCountryCode?: string // used for Maps only
50
49
  tabbingId: string
51
50
  tableTitle: string
52
51
  viewport: 'lg' | 'md' | 'sm' | 'xs' | 'xxs'
@@ -58,36 +57,33 @@ 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
- tableTitle,
65
- vizTitle,
66
- rawData,
67
- runtimeData: parentRuntimeData,
68
- headerColor,
64
+ displayGeoName,
69
65
  expandDataTable,
70
- columns,
71
- viewport,
72
66
  formatLegendLocation,
67
+ headerColor,
68
+ rawData,
69
+ runtimeData: parentRuntimeData,
73
70
  tabbingId,
71
+ tableTitle,
72
+ viewport,
73
+ vizTitle,
74
74
  wrapColumns
75
75
  } = props
76
76
  const runtimeData = useMemo(() => {
77
77
  const data = removeNullColumns(parentRuntimeData)
78
- if (config.table.pivot) {
78
+ const { columnName, valueColumns } = config.table.pivot || {}
79
+ if (columnName && valueColumns) {
79
80
  const excludeColumns = Object.values(config.columns || {})
80
81
  .filter(column => column.dataTable === false)
81
82
  .map(col => col.name)
82
- const { columnName, valueColumns } = config.table.pivot
83
- if (columnName && valueColumns) {
84
- // remove excluded columns so that they aren't included in the pivot calculation
85
- const _data = data.map(row => _.omit(row, excludeColumns))
86
- return pivotData(_data, columnName, valueColumns)
87
- }
83
+ return pivotData(data, columnName, valueColumns, excludeColumns)
88
84
  }
89
85
  return data
90
- }, [parentRuntimeData, config.table.pivot?.columnName, config.table.pivot?.valueColumn])
86
+ }, [parentRuntimeData, config.table.pivot?.columnName, config.table.pivot?.valueColumns])
91
87
 
92
88
  const [expanded, setExpanded] = useState(expandDataTable)
93
89
  const [sortBy, setSortBy] = useState<any>({
@@ -208,13 +204,10 @@ const DataTable = (props: DataTableProps) => {
208
204
  [config.runtime?.seriesKeys]) // eslint-disable-line
209
205
 
210
206
  const hasNoData = runtimeData.length === 0
211
-
212
207
  const getClassNames = (): string => {
213
208
  const classes = ['data-table-container']
214
209
 
215
- const hasDownloadLinkAbove =
216
- (config.table.download || config.general?.showDownloadButton) && !config.table.showDownloadLinkBelow
217
-
210
+ const hasDownloadLinkAbove = config.table.download && !config.table.showDownloadLinkBelow
218
211
  const isStandaloneTable = config.type === 'table'
219
212
 
220
213
  if (!hasDownloadLinkAbove && !isStandaloneTable) {
@@ -233,12 +226,37 @@ const DataTable = (props: DataTableProps) => {
233
226
 
234
227
  if (config.visualizationType !== 'Box Plot') {
235
228
  const getDownloadData = () => {
229
+ const dataSeriesColumns = getDataSeriesColumns(config, isVertical, runtimeData)
230
+ const sharedFilterColumns = config.table?.sharedFilterColumns || []
231
+ const vizFilterColumns = (config.filters || []).map(filter => filter.columnName)
232
+ const filterColumns = [...sharedFilterColumns, ...vizFilterColumns]
233
+ const visibleData =
234
+ config.type === 'map'
235
+ ? getMapRowData(
236
+ rows,
237
+ columns,
238
+ config,
239
+ formatLegendLocation,
240
+ runtimeData as Record<string, Object>,
241
+ displayGeoName,
242
+ filterColumns
243
+ )
244
+ : runtimeData.map(d => {
245
+ return _.pick(d, [...filterColumns, ...dataSeriesColumns])
246
+ })
247
+ const csvData = config.table?.downloadVisibleDataOnly ? visibleData : rawData
248
+
236
249
  // only use fullGeoName on County maps and no other
237
- if (config.general?.geoType === 'us-county') {
250
+ if (config.general?.geoType === 'us-county' || config.table.showFullGeoNameInCSV) {
238
251
  // Add column for full Geo name along with State
239
- return rawData.map(row => ({ FullGeoName: formatLegendLocation(row[config.columns.geo.name]), ...row }))
252
+ return csvData.map(row => {
253
+ return {
254
+ FullGeoName: formatLegendLocation(row[config.columns.geo.name]),
255
+ ...row
256
+ }
257
+ })
240
258
  } else {
241
- return rawData
259
+ return csvData
242
260
  }
243
261
  }
244
262
 
@@ -261,7 +279,7 @@ const DataTable = (props: DataTableProps) => {
261
279
 
262
280
  const childrenMatrix =
263
281
  config.type === 'map'
264
- ? mapCellMatrix({ rows, wrapColumns, ...props, runtimeData, viewport })
282
+ ? mapCellMatrix({ ...props, rows, wrapColumns, runtimeData, viewport })
265
283
  : chartCellMatrix({ rows, ...props, runtimeData, isVertical, sortBy, hasRowType, viewport })
266
284
 
267
285
  // If every value in a column is a number, record the column index so the header and cells can be right-aligned
@@ -276,7 +294,7 @@ const DataTable = (props: DataTableProps) => {
276
294
  : {}
277
295
 
278
296
  const TableMediaControls = ({ belowTable }) => {
279
- const hasDownloadLink = config.table.download || config.general?.showDownloadButton
297
+ const hasDownloadLink = config.table.download
280
298
  return (
281
299
  <MediaControls.Section classes={getMediaControlsClasses(belowTable, hasDownloadLink)}>
282
300
  <MediaControls.Link config={config} dashboardDataConfig={dataConfig} />
@@ -338,7 +356,8 @@ const DataTable = (props: DataTableProps) => {
338
356
  }`,
339
357
  'aria-live': 'assertive',
340
358
  'aria-rowcount': config?.data?.length ? config.data.length : -1,
341
- hidden: !expanded
359
+ hidden: !expanded,
360
+ cellMinWidth: 100
342
361
  }}
343
362
  rightAlignedCols={rightAlignedCols}
344
363
  />
@@ -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
 
@@ -54,7 +55,7 @@ const ChartHeader = ({
54
55
  }
55
56
 
56
57
  const ColumnHeadingText = ({ column, text, config }) => {
57
- if (text === 'pivotColumn') return ''
58
+ if (text === '_pivotedFrom') return ''
58
59
  let notApplicableText = 'Not Applicable'
59
60
  if (text === '__series__' && config.table.indexLabel) return `${config.table.indexLabel} `
60
61
  if (text === '__series__' && !config.table.indexLabel)
@@ -75,7 +76,7 @@ const ChartHeader = ({
75
76
  return (
76
77
  <tr>
77
78
  {dataSeriesColumns.map((column, index) => {
78
- const text = getSeriesName(column, config)
79
+ const text = parse(getSeriesName(column, config))
79
80
  const newSortBy = getNewSortBy(sortBy, column, index)
80
81
  const sortByAsc = sortBy.column === column ? sortBy.asc : undefined
81
82
  const isSortedCol = column === sortBy.column && !hasRowType
@@ -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[]
@@ -84,7 +85,7 @@ const chartCellArray = ({
84
85
  ? [
85
86
  <>
86
87
  {colorScale && colorScale(seriesName) && <LegendShape fill={colorScale(seriesName)} />}
87
- {seriesName}
88
+ {parse(seriesName)}
88
89
  </>
89
90
  ]
90
91
  : []
@@ -2,16 +2,22 @@ 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
4
 
5
+ const isPivotColumn = (columnName, config) => {
6
+ const tableHasPivotColumnConfigured = config.table.pivot?.valueColumns?.length
7
+ const originalColumnNames = Object.keys(config.data?.[0] || {})
8
+ const columnIsPivot = originalColumnNames.length && !originalColumnNames.includes(columnName)
9
+ return tableHasPivotColumnConfigured && columnIsPivot
10
+ }
11
+
5
12
  // if its additional column, return formatting params
6
- const isAdditionalColumn = (column, config) => {
7
- let inthere = false
13
+ const isAdditionalColumn = (column: string, config, rowData) => {
14
+ const columnName = isPivotColumn(column, config) ? rowData._pivotedFrom : column
8
15
  let formattingParams = {}
9
16
  const { columns } = config
10
17
  if (columns) {
11
18
  Object.keys(columns).forEach(keycol => {
12
19
  const col = columns[keycol]
13
- if (col.name === column) {
14
- inthere = true
20
+ if (col.name === columnName) {
15
21
  formattingParams = {
16
22
  addColPrefix: col.prefix,
17
23
  addColSuffix: col.suffix,
@@ -54,7 +60,7 @@ export const getChartCellValue = (row: string, column: string, config: TableConf
54
60
  if (rightSeriesItem.dataKey === column) resolvedAxis = 'right'
55
61
  })
56
62
 
57
- let addColParams = isAdditionalColumn(column, config)
63
+ let addColParams = isAdditionalColumn(column, config, rowObj)
58
64
  if (addColParams) {
59
65
  cellValue = config.dataFormat
60
66
  ? formatNumber(runtimeData[row][column], resolvedAxis, false, config, addColParams)
@@ -57,9 +57,13 @@ export const getDataSeriesColumns = (config: TableConfig, isVertical: boolean, r
57
57
  })
58
58
 
59
59
  tmpSeriesColumns.sort((a, b) => {
60
- if (a === 'pivotColumn') return -1
61
- if (b === 'pivotColumn') return 1
60
+ if (a === '_pivotedFrom') return -1
61
+ if (b === '_pivotedFrom') return 1
62
62
  return columnOrderingHash[a] - columnOrderingHash[b]
63
63
  })
64
- return tmpSeriesColumns
64
+
65
+ return tmpSeriesColumns.filter(colName => {
66
+ // remove metadata added by pivotData function
67
+ return colName !== '_pivotedFrom'
68
+ })
65
69
  }
@@ -3,52 +3,106 @@ import CellAnchor from '../components/CellAnchor'
3
3
  import { DataTableProps } from '../DataTable'
4
4
  import { ReactNode } from 'react'
5
5
  import { displayDataAsText } from '@cdc/core/helpers/displayDataAsText'
6
+ import _ from 'lodash'
7
+ import { applyLegendToRow } from '@cdc/map/src/helpers/applyLegendToRow'
6
8
 
7
9
  type MapRowsProps = DataTableProps & {
8
10
  rows: string[]
9
11
  }
10
12
 
13
+ const getGeoLabel = (config, row, formatLegendLocation, displayGeoName) => {
14
+ const { geoType, type } = config.general
15
+ let labelValue
16
+ if (!['single-state', 'us-county'].includes(geoType) || type === 'us-geocode') {
17
+ labelValue = displayGeoName(row)
18
+ labelValue = String(labelValue).startsWith('region') ? _.capitalize(labelValue) : labelValue
19
+ } else {
20
+ labelValue = formatLegendLocation(row)
21
+ }
22
+ return labelValue
23
+ }
24
+
25
+ const getDataValue = (config, rowData, column) => {
26
+ // check for special classes
27
+ let specialValFound = ''
28
+ let columnName = config.columns[column]?.name
29
+ const { specialClasses } = config.legend
30
+ if (specialClasses && specialClasses.length && typeof specialClasses[0] === 'object') {
31
+ specialClasses.forEach(specialClass => {
32
+ if (specialClass.key === columnName) {
33
+ if (String(rowData[specialClass.key]) === specialClass.value) {
34
+ specialValFound = specialClass.label
35
+ }
36
+ }
37
+ })
38
+ }
39
+ return specialValFound || rowData[columnName]
40
+ }
41
+
42
+ export const getMapRowData = (
43
+ rows: string[],
44
+ columns: Record<string, { label; name?; dataTable }>,
45
+ config: Record<string, Object>,
46
+ formatLegendLocation: (row: string) => string,
47
+ runtimeData: Record<string, Object>,
48
+ displayGeoName: (row: string) => string,
49
+ filterColumns: string[]
50
+ ) => {
51
+ return rows.map((row: string) => {
52
+ const dataRow = {}
53
+ ;[
54
+ ...filterColumns,
55
+ ...Object.keys(columns).filter(column => columns[column].dataTable === true && columns[column].name)
56
+ ].map(column => {
57
+ const label = columns[column]?.label || columns[column]?.name || column
58
+ if (column === 'geo') {
59
+ dataRow[label] = getGeoLabel(config, row, formatLegendLocation, displayGeoName)
60
+ } else if (filterColumns.includes(column)) {
61
+ dataRow[label] = runtimeData[row][column]
62
+ } else {
63
+ const dataValue = getDataValue(config, runtimeData[row], column)
64
+ dataRow[label] = displayDataAsText(dataValue, column, config)
65
+ }
66
+ })
67
+ return dataRow
68
+ })
69
+ }
70
+
11
71
  const mapCellArray = ({
12
72
  rows,
13
73
  columns,
14
74
  runtimeData,
15
75
  config,
16
- applyLegendToRow,
17
76
  displayGeoName,
18
77
  formatLegendLocation,
19
78
  navigationHandler,
20
- setFilteredCountryCode
79
+ setFilteredCountryCode,
80
+ legendMemo,
81
+ legendSpecialClassLastMemo,
82
+ runtimeLegend
21
83
  }: MapRowsProps): ReactNode[][] => {
84
+ const { allowMapZoom, geoType, type } = config.general
22
85
  return rows.map(row =>
23
86
  Object.keys(columns)
24
87
  .filter(column => columns[column].dataTable === true && columns[column].name)
25
88
  .map(column => {
26
- let cellValue
27
-
28
89
  if (column === 'geo') {
29
90
  const rowObj = runtimeData[row]
30
- const legendColor = applyLegendToRow(rowObj)
91
+ if (!rowObj) {
92
+ throw new Error('No row object found')
93
+ }
31
94
 
32
- let labelValue
33
- const mapZoomHandler =
34
- config.general.type === 'bubble' && config.general.allowMapZoom && config.general.geoType === 'world'
35
- ? () => setFilteredCountryCode(row)
36
- : undefined
37
- if (
38
- (config.general.geoType !== 'single-state' && config.general.geoType !== 'us-county') ||
39
- config.general.type === 'us-geocode'
40
- ) {
41
- const capitalize = str => {
42
- return str.charAt(0).toUpperCase() + str.slice(1)
43
- }
44
- labelValue = displayGeoName(row)
45
- labelValue = String(labelValue).startsWith('region') ? capitalize(labelValue) : labelValue
46
- } else {
47
- labelValue = formatLegendLocation(row)
95
+ const legendColor = applyLegendToRow(rowObj, config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
96
+
97
+ if (!legendColor) {
98
+ console.error('No legend color found') // eslint-disable-line no-console
48
99
  }
49
- cellValue = (
100
+ const labelValue = getGeoLabel(config, row, formatLegendLocation, displayGeoName)
101
+ const mapZoomHandler =
102
+ type === 'bubble' && allowMapZoom && geoType === 'world' ? () => setFilteredCountryCode(row) : undefined
103
+ return (
50
104
  <div className='col-12'>
51
- <LegendShape fill={legendColor[0]} />
105
+ {legendColor && legendColor.length > 0 && <LegendShape fill={legendColor[0]} />}
52
106
  <CellAnchor
53
107
  markup={labelValue}
54
108
  row={rowObj}
@@ -59,23 +113,10 @@ const mapCellArray = ({
59
113
  </div>
60
114
  )
61
115
  } else {
62
- // check for special classes
63
- let specialValFound = ''
64
- let columnName = config.columns[column].name
65
- const { specialClasses } = config.legend
66
- if (specialClasses && specialClasses.length && typeof specialClasses[0] === 'object') {
67
- specialClasses.forEach(specialClass => {
68
- if (specialClass.key === columnName) {
69
- if (String(runtimeData[row][specialClass.key]) === specialClass.value) {
70
- specialValFound = specialClass.label
71
- }
72
- }
73
- })
74
- }
75
- cellValue = displayDataAsText(specialValFound || runtimeData[row][columnName], column, config)
116
+ const rowData = runtimeData[row]
117
+ const dataValue = getDataValue(config, rowData, column)
118
+ return displayDataAsText(dataValue, column, config)
76
119
  }
77
-
78
- return cellValue
79
120
  })
80
121
  )
81
122
  }
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { getMapRowData } from '../mapCellMatrix'
3
+
4
+ describe('getMapRowData', () => {
5
+ const columns = {
6
+ geo: { dataTable: true, name: 'geo', label: 'Geo' },
7
+ column1: { dataTable: true, name: 'column1', label: 'Column 1' },
8
+ column2: { dataTable: true, name: 'column2', label: 'Column 2' }
9
+ }
10
+ const config = {
11
+ general: { geoType: 'us-state', type: 'map' },
12
+ columns: {
13
+ geo: { name: 'geo' },
14
+ column1: { name: 'column1' },
15
+ column2: { name: 'column2' }
16
+ },
17
+ legend: { specialClasses: [] }
18
+ }
19
+ const formatLegendLocation = row => row
20
+ const rows = ['row2', 'row1']
21
+ const runtimeData = {
22
+ row1: { geo: 'region1', column1: 'data1', column2: 'data2', somethingElse: 'data5' },
23
+ row2: { geo: 'region2', column1: 'data3', column2: 'data4', somethingElse: 'data6' }
24
+ }
25
+ const displayGeoName = row => `displayGeoName -> ${row}`
26
+ const sharedFilterColumns = ['somethingElse']
27
+
28
+ it('should map rows to data rows correctly', () => {
29
+ const result = getMapRowData(
30
+ rows,
31
+ columns,
32
+ config,
33
+ formatLegendLocation,
34
+ runtimeData,
35
+ displayGeoName,
36
+ sharedFilterColumns
37
+ )
38
+
39
+ expect(result).toEqual([
40
+ {
41
+ Geo: 'displayGeoName -> row2',
42
+ 'Column 1': 'data3',
43
+ 'Column 2': 'data4',
44
+ somethingElse: 'data6'
45
+ },
46
+ {
47
+ Geo: 'displayGeoName -> row1',
48
+ 'Column 1': 'data1',
49
+ 'Column 2': 'data2',
50
+ somethingElse: 'data5'
51
+ }
52
+ ])
53
+
54
+ config.general.geoType = 'us-county'
55
+ const result2 = getMapRowData(
56
+ rows,
57
+ columns,
58
+ config,
59
+ formatLegendLocation,
60
+ runtimeData,
61
+ displayGeoName,
62
+ sharedFilterColumns
63
+ )
64
+
65
+ expect(result2).toEqual([
66
+ {
67
+ Geo: 'row2',
68
+ 'Column 1': 'data3',
69
+ 'Column 2': 'data4',
70
+ somethingElse: 'data6'
71
+ },
72
+ {
73
+ Geo: 'row1',
74
+ 'Column 1': 'data1',
75
+ 'Column 2': 'data2',
76
+ somethingElse: 'data5'
77
+ }
78
+ ])
79
+ })
80
+ })
@@ -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
  )