@cdc/core 4.24.1 → 4.24.2
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.
- package/components/DataTable/DataTable.tsx +17 -6
- package/components/DataTable/helpers/customSort.ts +9 -0
- package/components/DataTable/helpers/getChartCellValue.ts +1 -1
- package/components/DataTable/helpers/getDataSeriesColumns.ts +1 -1
- package/components/DataTable/helpers/getSeriesName.ts +15 -20
- package/components/DataTable/helpers/mapCellMatrix.tsx +4 -0
- package/components/DataTable/types/TableConfig.ts +6 -30
- package/components/Filters.jsx +14 -12
- package/components/MultiSelect/MultiSelect.tsx +3 -3
- package/components/ui/_stories/Colors.stories.tsx +92 -0
- package/components/ui/_stories/Icon.stories.tsx +17 -10
- package/helpers/fetchRemoteData.js +5 -5
- package/helpers/getViewport.ts +23 -0
- package/helpers/isSolr.js +13 -0
- package/helpers/withDevTools.ts +50 -0
- package/package.json +2 -2
- package/styles/_data-table.scss +2 -7
- package/styles/_global-variables.scss +75 -0
- package/styles/base.scss +89 -69
- package/types/Action.ts +1 -0
- package/types/Axis.ts +2 -0
- package/types/BoxPlot.ts +21 -0
- package/types/Column.ts +1 -0
- package/types/General.ts +9 -0
- package/types/Runtime.ts +1 -1
- package/types/Table.ts +5 -2
- package/types/ViewPort.ts +2 -0
- package/types/Visualization.ts +8 -2
- package/types/WCMSProps.ts +11 -0
- package/helpers/getViewport.js +0 -21
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState, useMemo } from 'react'
|
|
2
|
+
import { timeParse } from 'd3-time-format'
|
|
2
3
|
|
|
3
4
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
4
5
|
import MediaControls from '@cdc/core/components/MediaControls'
|
|
@@ -17,13 +18,12 @@ import regionCellMatrix from './helpers/regionCellMatrix'
|
|
|
17
18
|
import boxplotCellMatrix from './helpers/boxplotCellMatrix'
|
|
18
19
|
import customColumns from './helpers/customColumns'
|
|
19
20
|
import { TableConfig } from './types/TableConfig'
|
|
21
|
+
import { Column } from '../../types/Column'
|
|
20
22
|
|
|
21
23
|
export type DataTableProps = {
|
|
22
24
|
applyLegendToRow?: Function
|
|
23
25
|
colorScale?: Function
|
|
24
|
-
columns?:
|
|
25
|
-
// determines if columns should be wrapped in the table
|
|
26
|
-
wrapColumns?: boolean
|
|
26
|
+
columns?: Record<string, Column>
|
|
27
27
|
config: TableConfig
|
|
28
28
|
dataConfig?: Object
|
|
29
29
|
displayDataAsText?: Function
|
|
@@ -32,15 +32,22 @@ export type DataTableProps = {
|
|
|
32
32
|
formatLegendLocation?: Function
|
|
33
33
|
groupBy?: string
|
|
34
34
|
headerColor?: string
|
|
35
|
+
imageRef?: string
|
|
35
36
|
indexTitle?: string
|
|
37
|
+
isDebug?: boolean
|
|
38
|
+
isEditor?: boolean
|
|
36
39
|
navigationHandler?: Function
|
|
40
|
+
outerContainerRef?: Function
|
|
37
41
|
rawData: Object[]
|
|
38
42
|
runtimeData: Object[] | Record<string, Object> // UNSAFE
|
|
39
43
|
setFilteredCountryCode?: Function // used for Maps only
|
|
44
|
+
showDownloadButton?: boolean
|
|
40
45
|
tabbingId: string
|
|
41
46
|
tableTitle: string
|
|
42
47
|
viewport: string
|
|
43
48
|
vizTitle?: string
|
|
49
|
+
// determines if columns should be wrapped in the table
|
|
50
|
+
wrapColumns?: boolean
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
@@ -92,9 +99,9 @@ const DataTable = (props: DataTableProps) => {
|
|
|
92
99
|
break
|
|
93
100
|
}
|
|
94
101
|
|
|
95
|
-
const _runtimeData = config.table.customTableConfig ? customColumns(
|
|
102
|
+
const _runtimeData = config.table.customTableConfig ? customColumns(rawData, config.table.excludeColumns) : runtimeData
|
|
96
103
|
|
|
97
|
-
const rawRows = Object.keys(_runtimeData)
|
|
104
|
+
const rawRows = Object.keys(_runtimeData).filter(column => column != 'columns')
|
|
98
105
|
const rows = isVertical
|
|
99
106
|
? rawRows.sort((a, b) => {
|
|
100
107
|
let dataA
|
|
@@ -108,6 +115,10 @@ const DataTable = (props: DataTableProps) => {
|
|
|
108
115
|
dataA = _runtimeData[a][sortBy.column]
|
|
109
116
|
dataB = _runtimeData[b][sortBy.column]
|
|
110
117
|
}
|
|
118
|
+
if (!dataA && !dataB && config.type === 'chart' && config.xAxis && config.xAxis.type === 'date' && config.xAxis.sortDates) {
|
|
119
|
+
dataA = timeParse(config.runtime.xAxis.dateParseFormat)(_runtimeData[a][config.xAxis.dataKey])
|
|
120
|
+
dataB = timeParse(config.runtime.xAxis.dateParseFormat)(_runtimeData[b][config.xAxis.dataKey])
|
|
121
|
+
}
|
|
111
122
|
return dataA && dataB ? customSort(dataA, dataB, sortBy, config) : 0
|
|
112
123
|
})
|
|
113
124
|
: rawRows
|
|
@@ -117,7 +128,7 @@ const DataTable = (props: DataTableProps) => {
|
|
|
117
128
|
OverflowY: 'scroll'
|
|
118
129
|
}
|
|
119
130
|
|
|
120
|
-
const hasRowType = !!Object.keys(rawData[0] || {}).find((v: string) => v.match(/row[_-]?type/i))
|
|
131
|
+
const hasRowType = !!Object.keys(rawData?.[0] || {}).find((v: string) => v.match(/row[_-]?type/i))
|
|
121
132
|
|
|
122
133
|
const caption = useMemo(() => {
|
|
123
134
|
if (config.type === 'map') {
|
|
@@ -8,7 +8,16 @@ export const customSort = (a, b, sortBy, config) => {
|
|
|
8
8
|
if (config.type === 'map') {
|
|
9
9
|
valueA = standardizeStateName(a)
|
|
10
10
|
valueB = standardizeStateName(b)
|
|
11
|
+
// sort for Regions table for Map
|
|
12
|
+
if (String(valueA).toLowerCase().includes('region') && String(valueB).toLowerCase().includes('region')) {
|
|
13
|
+
const numberA = parseInt(a.match(/\d+$/)[0], 10)
|
|
14
|
+
const numberB = parseInt(b.match(/\d+$/)[0], 10)
|
|
15
|
+
|
|
16
|
+
// Compare the numeric parts
|
|
17
|
+
return !sortBy.asc ? Number(numberA) - Number(numberB) : Number(numberB) - Number(numberA)
|
|
18
|
+
}
|
|
11
19
|
}
|
|
20
|
+
|
|
12
21
|
// Treat booleans and nulls as an empty string
|
|
13
22
|
valueA = valueA === false || valueA === true || valueA === null ? '' : valueA
|
|
14
23
|
valueB = valueB === false || valueB === true || valueB === null ? '' : valueB
|
|
@@ -30,7 +30,7 @@ export const getChartCellValue = (row, column, config, runtimeData) => {
|
|
|
30
30
|
let labelValue = rowObj[column] // just raw X axis string
|
|
31
31
|
if (column === config.xAxis?.dataKey) {
|
|
32
32
|
// not the prettiest, but helper functions work nicely here.
|
|
33
|
-
cellValue = config.xAxis?.type === 'date' ? formatDate(config.xAxis?.dateDisplayFormat, parseDate(config.xAxis?.dateParseFormat, labelValue)) : labelValue
|
|
33
|
+
cellValue = config.xAxis?.type === 'date' ? formatDate(config.table?.dateDisplayFormat || config.xAxis?.dateDisplayFormat, parseDate(config.xAxis?.dateParseFormat, labelValue)) : labelValue
|
|
34
34
|
} else {
|
|
35
35
|
let resolvedAxis = 'left'
|
|
36
36
|
let leftAxisItems = config.series ? config.series.filter(item => item?.axis === 'Left') : []
|
|
@@ -11,7 +11,7 @@ export const getDataSeriesColumns = (config, isVertical, runtimeData): string[]
|
|
|
11
11
|
tmpSeriesColumns = Object.keys(runtimeData[0])
|
|
12
12
|
}
|
|
13
13
|
} else {
|
|
14
|
-
tmpSeriesColumns = [config.xAxis?.dataKey, config.yAxis?.dataKey]
|
|
14
|
+
tmpSeriesColumns = isVertical ? [config.xAxis?.dataKey, config.yAxis?.dataKey] : [config.yAxis?.dataKey]
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// then add the additional Columns
|
|
@@ -1,26 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
custLabel = tmpColumn.label
|
|
9
|
-
}
|
|
10
|
-
})
|
|
11
|
-
return custLabel
|
|
1
|
+
import { TableConfig } from '../types/TableConfig'
|
|
2
|
+
|
|
3
|
+
const getLabel = (name: string, config: TableConfig) => {
|
|
4
|
+
const columns = Object.values(config.columns || {})
|
|
5
|
+
const matchingConfiguredColumn = columns.find(col => col.name === name)
|
|
6
|
+
if (matchingConfiguredColumn?.label) {
|
|
7
|
+
return matchingConfiguredColumn.label
|
|
12
8
|
}
|
|
9
|
+
return name
|
|
13
10
|
}
|
|
14
11
|
|
|
15
|
-
export const getSeriesName = (column, config) => {
|
|
12
|
+
export const getSeriesName = (column: string, config: TableConfig) => {
|
|
16
13
|
// If a user sets the name on a series use that.
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
|
|
14
|
+
const userDefinedSeries = config.series?.find(series => series.dataKey === column)
|
|
15
|
+
if (userDefinedSeries?.name) {
|
|
16
|
+
return userDefinedSeries.name
|
|
17
|
+
}
|
|
20
18
|
if (config.runtimeSeriesLabels && config.runtimeSeriesLabels[column]) return config.runtimeSeriesLabels[column]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
let text = column === config.xAxis?.dataKey ? config.table.indexLabel : custLabel
|
|
24
|
-
|
|
25
|
-
return text
|
|
19
|
+
const columnIsDataKey = column === config.xAxis?.dataKey
|
|
20
|
+
return columnIsDataKey ? config.table.indexLabel : getLabel(column, config)
|
|
26
21
|
}
|
|
@@ -21,7 +21,11 @@ const mapCellArray = ({ rows, columns, runtimeData, config, applyLegendToRow, di
|
|
|
21
21
|
let labelValue
|
|
22
22
|
const mapZoomHandler = config.general.type === 'bubble' && config.general.allowMapZoom && config.general.geoType === 'world' ? () => setFilteredCountryCode(row) : undefined
|
|
23
23
|
if ((config.general.geoType !== 'single-state' && config.general.geoType !== 'us-county') || config.general.type === 'us-geocode') {
|
|
24
|
+
const capitalize = str => {
|
|
25
|
+
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
26
|
+
}
|
|
24
27
|
labelValue = displayGeoName(row)
|
|
28
|
+
labelValue = String(labelValue).startsWith('region') ? capitalize(labelValue) : labelValue
|
|
25
29
|
} else {
|
|
26
30
|
labelValue = formatLegendLocation(row)
|
|
27
31
|
}
|
|
@@ -2,43 +2,19 @@ import { Axis } from '@cdc/core/types/Axis'
|
|
|
2
2
|
import { Series } from '@cdc/core/types/Series'
|
|
3
3
|
import { Runtime } from '@cdc/core/types/Runtime'
|
|
4
4
|
import { Table } from '@cdc/core/types/Table'
|
|
5
|
+
import { BoxPlot } from '../../../types/BoxPlot'
|
|
6
|
+
import { General } from '../../../types/General'
|
|
7
|
+
import { Column } from '../../../types/Column'
|
|
5
8
|
|
|
6
9
|
export type TableConfig = {
|
|
7
10
|
type?: string
|
|
8
11
|
table: Table
|
|
9
12
|
xAxis?: Axis
|
|
10
13
|
yAxis?: Axis
|
|
11
|
-
boxplot?:
|
|
12
|
-
tableData: Object[]
|
|
13
|
-
labels: {
|
|
14
|
-
mean: string
|
|
15
|
-
maximum: string
|
|
16
|
-
minimum: string
|
|
17
|
-
iqr: string
|
|
18
|
-
median: string
|
|
19
|
-
q1: string
|
|
20
|
-
q3: string
|
|
21
|
-
outliers: string
|
|
22
|
-
values: string
|
|
23
|
-
total: string
|
|
24
|
-
lowerBounds: string
|
|
25
|
-
upperBounds: string
|
|
26
|
-
}
|
|
27
|
-
plots: []
|
|
28
|
-
categories: string[]
|
|
29
|
-
}
|
|
14
|
+
boxplot?: BoxPlot
|
|
30
15
|
visualizationType?: string
|
|
31
|
-
general?:
|
|
32
|
-
|
|
33
|
-
type: string
|
|
34
|
-
showDownloadButton: boolean
|
|
35
|
-
allowMapZoom?: boolean
|
|
36
|
-
}
|
|
37
|
-
columns?: {
|
|
38
|
-
geo: {
|
|
39
|
-
name: string
|
|
40
|
-
}
|
|
41
|
-
}
|
|
16
|
+
general?: General
|
|
17
|
+
columns?: Record<string, Column>
|
|
42
18
|
legend?: {
|
|
43
19
|
specialClasses?: { key: string; label: string; value: string }[]
|
|
44
20
|
hide?: boolean
|
package/components/Filters.jsx
CHANGED
|
@@ -74,21 +74,16 @@ export const useFilters = props => {
|
|
|
74
74
|
const changeFilterActive = (index, value) => {
|
|
75
75
|
let newFilters = visualizationConfig.type === 'map' ? [...filteredData] : [...visualizationConfig.filters]
|
|
76
76
|
|
|
77
|
-
newFilters[index].active = value
|
|
78
|
-
setConfig({ ...visualizationConfig })
|
|
79
|
-
|
|
80
|
-
// If this is a button filter type show the button.
|
|
81
77
|
if (visualizationConfig.filterBehavior === 'Apply Button') {
|
|
78
|
+
newFilters[index].queuedActive = value
|
|
82
79
|
setShowApplyButton(true)
|
|
80
|
+
} else {
|
|
81
|
+
newFilters[index].active = value
|
|
83
82
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
...visualizationConfig,
|
|
89
|
-
filters: newFilters
|
|
90
|
-
})
|
|
91
|
-
}
|
|
83
|
+
setConfig({
|
|
84
|
+
...visualizationConfig,
|
|
85
|
+
filters: newFilters
|
|
86
|
+
})
|
|
92
87
|
|
|
93
88
|
// Used for setting active filter, fromHash breaks the filteredData functionality.
|
|
94
89
|
if (visualizationConfig.type === 'map' && visualizationConfig.filterBehavior === 'Filter Change') {
|
|
@@ -102,6 +97,13 @@ export const useFilters = props => {
|
|
|
102
97
|
}
|
|
103
98
|
|
|
104
99
|
const handleApplyButton = newFilters => {
|
|
100
|
+
newFilters.forEach(newFilter => {
|
|
101
|
+
if(newFilter.queuedActive){
|
|
102
|
+
newFilter.active = newFilter.queuedActive
|
|
103
|
+
delete newFilter.queuedActive
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
105
107
|
setConfig({ ...visualizationConfig, filters: newFilters })
|
|
106
108
|
|
|
107
109
|
if (type === 'map') {
|
|
@@ -51,7 +51,7 @@ const MultiSelect: React.FC<MultiSelectProps> = ({ section = null, subsection =
|
|
|
51
51
|
update(newItems)
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
const handleItemRemove = (option: Option
|
|
54
|
+
const handleItemRemove = (option: Option) => {
|
|
55
55
|
const newItems = selectedItems.filter(item => item.value !== option.value)
|
|
56
56
|
setSelectedItems(newItems)
|
|
57
57
|
update(newItems)
|
|
@@ -68,9 +68,9 @@ const MultiSelect: React.FC<MultiSelectProps> = ({ section = null, subsection =
|
|
|
68
68
|
|
|
69
69
|
<div aria-labelledby={label ? multiID : undefined} className='selected'>
|
|
70
70
|
{selectedItems.map(item => (
|
|
71
|
-
<div key={item.value} role='button' tabIndex={0} onClick={() => handleItemRemove(item
|
|
71
|
+
<div key={item.value} role='button' tabIndex={0} onClick={() => handleItemRemove(item)} onKeyUp={() => handleItemRemove(item)}>
|
|
72
72
|
{item.label}
|
|
73
|
-
<button aria-label='Remove' onClick={() => handleItemRemove(item
|
|
73
|
+
<button aria-label='Remove' onClick={() => handleItemRemove(item)}>
|
|
74
74
|
x
|
|
75
75
|
</button>
|
|
76
76
|
</div>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
|
|
3
|
+
const COLORS = [
|
|
4
|
+
['baseColor', '#333'],
|
|
5
|
+
['blue', '#005eaa'],
|
|
6
|
+
['lightestGray', '#f2f2f2'],
|
|
7
|
+
['lightGray', '#c7c7c7'],
|
|
8
|
+
['mediumGray', '#565656'],
|
|
9
|
+
['darkGray', '#333'],
|
|
10
|
+
['red', '#d8000c'],
|
|
11
|
+
['white', '#fff'],
|
|
12
|
+
|
|
13
|
+
['primary', '#005eaa'],
|
|
14
|
+
['secondary', '#88c3ea'],
|
|
15
|
+
['tertiary', '#c0e9ff'],
|
|
16
|
+
['quaternary', '#edf9ff'],
|
|
17
|
+
|
|
18
|
+
['purple-primary', '#712177'],
|
|
19
|
+
['purple-secondary', '#b890bb'],
|
|
20
|
+
['purple-tertiary', '#e3d3e4'],
|
|
21
|
+
['purple-quaternary', '#f7f2f7'],
|
|
22
|
+
|
|
23
|
+
['brown-primary', '#705043'],
|
|
24
|
+
['brown-secondary', '#ad907b'],
|
|
25
|
+
['brown-tertiary', '#d7ccc8'],
|
|
26
|
+
['brown-quaternary', '#f2ebe8'],
|
|
27
|
+
|
|
28
|
+
['teal-primary', '#00695c'],
|
|
29
|
+
['teal-secondary', '#4ebaaa'],
|
|
30
|
+
['teal-tertiary', '#ceece7'],
|
|
31
|
+
['teal-quaternary', '#ebf7f5'],
|
|
32
|
+
|
|
33
|
+
['pink-primary', '#af4448'],
|
|
34
|
+
['pink-secondary', '#e57373'],
|
|
35
|
+
['pink-tertiary', '#ffc2c2'],
|
|
36
|
+
['pink-quaternary', '#ffe7e7'],
|
|
37
|
+
|
|
38
|
+
['orange-primary', '#bb4d00'],
|
|
39
|
+
['orange-secondary', '#ffad42'],
|
|
40
|
+
['orange-tertiary', '#ffe97d'],
|
|
41
|
+
['orange-quaternary', '#fff4cf'],
|
|
42
|
+
|
|
43
|
+
['slate-primary', '#29434e'],
|
|
44
|
+
['slate-secondary', '#7e9ba5'],
|
|
45
|
+
['slate-tertiary', '#b6c6d2'],
|
|
46
|
+
['slate-quaternary', '#e2e8ed'],
|
|
47
|
+
|
|
48
|
+
['indigo-primary', '#26418f'],
|
|
49
|
+
['indigo-secondary', '#92a6dd'],
|
|
50
|
+
['indigo-tertiary', '#dee8ff'],
|
|
51
|
+
['indigo-quaternary', '#f2f6ff'],
|
|
52
|
+
|
|
53
|
+
['cyan-primary', '#006778'],
|
|
54
|
+
['cyan-secondary', '#65b0bd'],
|
|
55
|
+
['cyan-tertiary', '#cce5e9'],
|
|
56
|
+
['cyan-quaternary', '#ebf5f6'],
|
|
57
|
+
|
|
58
|
+
['green-primary', '#4b830d'],
|
|
59
|
+
['green-secondary', '#84bc49'],
|
|
60
|
+
['green-tertiary', '#dcedc8'],
|
|
61
|
+
['green-quaternary', '#f1f8e9'],
|
|
62
|
+
|
|
63
|
+
['amber-primary', '#fbab18'],
|
|
64
|
+
['amber-secondary', '#ffd54f'],
|
|
65
|
+
['amber-tertiary', '#ffecb3'],
|
|
66
|
+
['amber-quaternary', '#fff7e1']
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
const ColorArray = () => {
|
|
70
|
+
return (
|
|
71
|
+
<table className='table'>
|
|
72
|
+
{COLORS.map(([name, hex]) => (
|
|
73
|
+
<tr>
|
|
74
|
+
<td style={{ background: hex, width: '60px' }}></td>
|
|
75
|
+
<td>{name}</td>
|
|
76
|
+
<td>{hex}</td>
|
|
77
|
+
</tr>
|
|
78
|
+
))}
|
|
79
|
+
</table>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const meta: Meta<typeof ColorArray> = {
|
|
84
|
+
title: 'Components/Atoms/Colors',
|
|
85
|
+
component: ColorArray
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type Story = StoryObj<typeof ColorArray>
|
|
89
|
+
|
|
90
|
+
export const Primary: Story = {}
|
|
91
|
+
|
|
92
|
+
export default meta
|
|
@@ -3,20 +3,27 @@ import type { Meta, StoryObj } from '@storybook/react'
|
|
|
3
3
|
|
|
4
4
|
import Icon, { ICON_TYPES } from '../Icon'
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const IconArray = () => {
|
|
7
|
+
return (
|
|
8
|
+
<>
|
|
9
|
+
{ICON_TYPES.map(name => (
|
|
10
|
+
<div>
|
|
11
|
+
<span>
|
|
12
|
+
<Icon display={name} /> {name}{' '}
|
|
13
|
+
</span>
|
|
14
|
+
</div>
|
|
15
|
+
))}
|
|
16
|
+
</>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const meta: Meta<typeof IconArray> = {
|
|
7
21
|
title: 'Components/Atoms/Icon',
|
|
8
|
-
component:
|
|
9
|
-
parameters: {
|
|
10
|
-
display: ICON_TYPES
|
|
11
|
-
}
|
|
22
|
+
component: IconArray
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
type Story = StoryObj<typeof Icon>
|
|
15
26
|
|
|
16
|
-
export const Primary: Story = {
|
|
17
|
-
args: {
|
|
18
|
-
display: 'question'
|
|
19
|
-
}
|
|
20
|
-
}
|
|
27
|
+
export const Primary: Story = {}
|
|
21
28
|
|
|
22
29
|
export default meta
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import Papa from 'papaparse'
|
|
2
|
+
import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
|
|
2
3
|
|
|
3
|
-
export default async function (
|
|
4
|
+
export default async function (_url, visualizationType = '') {
|
|
5
|
+
let url = new URL(_url, window.location.origin)
|
|
4
6
|
try {
|
|
5
|
-
url = new URL(url, window.location.origin)
|
|
6
|
-
|
|
7
7
|
const path = url.pathname
|
|
8
8
|
const regex = /(?:\.([^.]+))?$/
|
|
9
9
|
const ext = regex.exec(path)[1]
|
|
10
10
|
|
|
11
|
-
if ('csv' === ext) {
|
|
11
|
+
if ('csv' === ext || isSolrCsv(_url)) {
|
|
12
12
|
return await fetch(url.href)
|
|
13
13
|
.then(response => response.text())
|
|
14
14
|
.then(responseText => {
|
|
@@ -30,7 +30,7 @@ export default async function (url, visualizationType = '') {
|
|
|
30
30
|
return parsedCsv.data
|
|
31
31
|
})
|
|
32
32
|
} else {
|
|
33
|
-
return await fetch(url.href).then(response => response.json())
|
|
33
|
+
return await fetch(isSolrCsv(_url) ? _url : url.href).then(response => response.json())
|
|
34
34
|
}
|
|
35
35
|
} catch {
|
|
36
36
|
// If we can't parse it, still attempt to fetch it
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ViewPort } from '../types/ViewPort'
|
|
2
|
+
|
|
3
|
+
export const viewports = {
|
|
4
|
+
lg: 1200,
|
|
5
|
+
md: 992,
|
|
6
|
+
sm: 768,
|
|
7
|
+
xs: 576,
|
|
8
|
+
xxs: 350
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function getViewport(size): ViewPort {
|
|
12
|
+
let result: ViewPort = 'lg'
|
|
13
|
+
|
|
14
|
+
if (size > 1200) return result
|
|
15
|
+
|
|
16
|
+
for (let viewport in viewports) {
|
|
17
|
+
if (size <= viewports[viewport]) {
|
|
18
|
+
result = viewport as ViewPort
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return result
|
|
23
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// This package hooks into the Redux Dev Tools extension
|
|
2
|
+
// It works as a wrapper for any reducer function
|
|
3
|
+
|
|
4
|
+
// Based on: https://github.com/MacKentoch/react-bootstrap-webpack-starter/blob/master/front/src/contexts/withDevTools/index.ts
|
|
5
|
+
|
|
6
|
+
// types
|
|
7
|
+
type DevToolsMessageType = 'DISPATCH' | string
|
|
8
|
+
|
|
9
|
+
type DevToolsMessagePayload = {
|
|
10
|
+
type?: string
|
|
11
|
+
state?: any
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type DevToolsMessage = {
|
|
15
|
+
type?: DevToolsMessageType
|
|
16
|
+
payload?: DevToolsMessagePayload
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type Action = {
|
|
20
|
+
type: string
|
|
21
|
+
payload?: any
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// the only method the devToolsWrapper hooks into is .send()
|
|
25
|
+
// all other type definitions are kept for reference.
|
|
26
|
+
// it's possible there's a DevTools type in the Redux library
|
|
27
|
+
// however this allows us to avoid another dependency as this project
|
|
28
|
+
// doesn't use Redux.
|
|
29
|
+
type DevTools = {
|
|
30
|
+
init: () => void
|
|
31
|
+
connect: () => any
|
|
32
|
+
subscribe: (message: DevToolsMessage) => any
|
|
33
|
+
send: (action: Action, newState: any) => any
|
|
34
|
+
unsubscribe: () => any
|
|
35
|
+
dispatch: (action: Action) => any
|
|
36
|
+
disconnect: () => any
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// constants
|
|
40
|
+
const withDevTools = typeof window !== 'undefined' && (window as any).__REDUX_DEVTOOLS_EXTENSION__
|
|
41
|
+
const devTools: DevTools = !withDevTools ? null : (window as any).__REDUX_DEVTOOLS_EXTENSION__.connect()
|
|
42
|
+
export const devToolsStore = !withDevTools ? null : devTools
|
|
43
|
+
|
|
44
|
+
export const devToolsWrapper =
|
|
45
|
+
<StateType, ActionTypes>(_reducer: (s: StateType, a: ActionTypes) => StateType) =>
|
|
46
|
+
(state: StateType, action: ActionTypes): StateType => {
|
|
47
|
+
const newState = _reducer(state, action)
|
|
48
|
+
devToolsStore?.send(action as Action, newState)
|
|
49
|
+
return newState
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/core",
|
|
3
|
-
"version": "4.24.
|
|
3
|
+
"version": "4.24.2",
|
|
4
4
|
"description": "Core components, styles, hooks, and helpers, for the CDC Open Visualization project",
|
|
5
5
|
"moduleName": "CdcCore",
|
|
6
6
|
"main": "dist/cdccore",
|
|
@@ -30,5 +30,5 @@
|
|
|
30
30
|
"react": "^18.2.0",
|
|
31
31
|
"react-dom": "^18.2.0"
|
|
32
32
|
},
|
|
33
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "edde49c96dee146de5e3a4537880b1bcf4dbee08"
|
|
34
34
|
}
|
package/styles/_data-table.scss
CHANGED
|
@@ -31,9 +31,6 @@ div.data-table-heading {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
table.horizontal {
|
|
34
|
-
tr {
|
|
35
|
-
display: flex;
|
|
36
|
-
}
|
|
37
34
|
|
|
38
35
|
th,
|
|
39
36
|
td {
|
|
@@ -42,7 +39,7 @@ table.horizontal {
|
|
|
42
39
|
}
|
|
43
40
|
|
|
44
41
|
table.data-table {
|
|
45
|
-
width: 100%;
|
|
42
|
+
min-width: 100%;
|
|
46
43
|
background: #fff;
|
|
47
44
|
position: relative;
|
|
48
45
|
border: none;
|
|
@@ -51,7 +48,6 @@ table.data-table {
|
|
|
51
48
|
overflow: auto;
|
|
52
49
|
appearance: none;
|
|
53
50
|
table-layout: fixed;
|
|
54
|
-
display: grid;
|
|
55
51
|
* {
|
|
56
52
|
box-sizing: border-box;
|
|
57
53
|
}
|
|
@@ -138,7 +134,6 @@ table.data-table {
|
|
|
138
134
|
}
|
|
139
135
|
|
|
140
136
|
tbody {
|
|
141
|
-
display: table-cell;
|
|
142
137
|
tr {
|
|
143
138
|
width: 100%;
|
|
144
139
|
&:hover {
|
|
@@ -162,6 +157,7 @@ table.data-table {
|
|
|
162
157
|
|
|
163
158
|
th,
|
|
164
159
|
td {
|
|
160
|
+
width: 1% !important;
|
|
165
161
|
white-space: nowrap;
|
|
166
162
|
text-overflow: ellipsis;
|
|
167
163
|
overflow: hidden;
|
|
@@ -170,7 +166,6 @@ table.data-table {
|
|
|
170
166
|
}
|
|
171
167
|
}
|
|
172
168
|
tr {
|
|
173
|
-
display: flex;
|
|
174
169
|
& > * {
|
|
175
170
|
width: 100%;
|
|
176
171
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
$colors: (
|
|
2
|
+
'baseColor': #333,
|
|
3
|
+
'blue': #005eaa,
|
|
4
|
+
'lightestGray': #f2f2f2,
|
|
5
|
+
'lightGray': #c7c7c7,
|
|
6
|
+
'mediumGray': #565656,
|
|
7
|
+
'darkGray': #333,
|
|
8
|
+
'red': #d8000c,
|
|
9
|
+
'white': #fff,
|
|
10
|
+
|
|
11
|
+
'primary': #005eaa,
|
|
12
|
+
'secondary': #88c3ea,
|
|
13
|
+
'tertiary': #c0e9ff,
|
|
14
|
+
'quaternary': #edf9ff,
|
|
15
|
+
|
|
16
|
+
'purple-primary': #712177,
|
|
17
|
+
'purple-secondary': #b890bb,
|
|
18
|
+
'purple-tertiary': #e3d3e4,
|
|
19
|
+
'purple-quaternary': #f7f2f7,
|
|
20
|
+
|
|
21
|
+
'brown-primary': #705043,
|
|
22
|
+
'brown-secondary': #ad907b,
|
|
23
|
+
'brown-tertiary': #d7ccc8,
|
|
24
|
+
'brown-quaternary': #f2ebe8,
|
|
25
|
+
|
|
26
|
+
'teal-primary': #00695c,
|
|
27
|
+
'teal-secondary': #4ebaaa,
|
|
28
|
+
'teal-tertiary': #ceece7,
|
|
29
|
+
'teal-quaternary': #ebf7f5,
|
|
30
|
+
|
|
31
|
+
'pink-primary': #af4448,
|
|
32
|
+
'pink-secondary': #e57373,
|
|
33
|
+
'pink-tertiary': #ffc2c2,
|
|
34
|
+
'pink-quaternary': #ffe7e7,
|
|
35
|
+
|
|
36
|
+
'orange-primary': #bb4d00,
|
|
37
|
+
'orange-secondary': #ffad42,
|
|
38
|
+
'orange-tertiary': #ffe97d,
|
|
39
|
+
'orange-quaternary': #fff4cf,
|
|
40
|
+
|
|
41
|
+
'slate-primary': #29434e,
|
|
42
|
+
'slate-secondary': #7e9ba5,
|
|
43
|
+
'slate-tertiary': #b6c6d2,
|
|
44
|
+
'slate-quaternary': #e2e8ed,
|
|
45
|
+
|
|
46
|
+
'indigo-primary': #26418f,
|
|
47
|
+
'indigo-secondary': #92a6dd,
|
|
48
|
+
'indigo-tertiary': #dee8ff,
|
|
49
|
+
'indigo-quaternary': #f2f6ff,
|
|
50
|
+
|
|
51
|
+
'cyan-primary': #006778,
|
|
52
|
+
'cyan-secondary': #65b0bd,
|
|
53
|
+
'cyan-tertiary': #cce5e9,
|
|
54
|
+
'cyan-quaternary': #ebf5f6,
|
|
55
|
+
|
|
56
|
+
'green-primary': #4b830d,
|
|
57
|
+
'green-secondary': #84bc49,
|
|
58
|
+
'green-tertiary': #dcedc8,
|
|
59
|
+
'green-quaternary': #f1f8e9,
|
|
60
|
+
|
|
61
|
+
'amber-primary': #fbab18,
|
|
62
|
+
'amber-secondary': #ffd54f,
|
|
63
|
+
'amber-tertiary': #ffecb3,
|
|
64
|
+
'amber-quaternary': #fff7e1,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
@mixin theme() {
|
|
68
|
+
:root {
|
|
69
|
+
@each $key, $value in $colors {
|
|
70
|
+
--#{$key}: #{$value};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@include theme();
|
package/styles/base.scss
CHANGED
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
// Imports
|
|
52
52
|
@import 'reset';
|
|
53
53
|
@import 'variables';
|
|
54
|
+
@import 'global-variables';
|
|
54
55
|
@import 'mixins';
|
|
55
56
|
@import 'filters';
|
|
56
57
|
|
|
@@ -58,6 +59,10 @@ body.post-type-cdc_visualization .visx-tooltip {
|
|
|
58
59
|
z-index: 1000000;
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
body.post-type-cdc_visualization .cdc-editor .configure .editor-panel {
|
|
63
|
+
top: 0 !important;
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
.cdc-open-viz-module {
|
|
62
67
|
position: relative;
|
|
63
68
|
color: $baseColor;
|
|
@@ -71,73 +76,88 @@ body.post-type-cdc_visualization .visx-tooltip {
|
|
|
71
76
|
@import 'button-section';
|
|
72
77
|
@import 'series-list';
|
|
73
78
|
@import 'typography';
|
|
74
|
-
}
|
|
75
79
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
'
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
'
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
'
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
'
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
'
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
'
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
'
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
'
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
'
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
80
|
+
.editor-panel.cove {
|
|
81
|
+
position: absolute;
|
|
82
|
+
height: 100vh;
|
|
83
|
+
top: 0;
|
|
84
|
+
.editor-toggle {
|
|
85
|
+
position: absolute !important;
|
|
86
|
+
top: 10px !important;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.editor-panel {
|
|
90
|
+
position: absolute !important;
|
|
91
|
+
top: 0 !important;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
$theme: (
|
|
96
|
+
'amber': (
|
|
97
|
+
'#fbab18',
|
|
98
|
+
'#ffd54f',
|
|
99
|
+
'#ffecb3',
|
|
100
|
+
'#fff7e1'
|
|
101
|
+
),
|
|
102
|
+
'blue': (
|
|
103
|
+
'#005eaa',
|
|
104
|
+
'#88c3ea',
|
|
105
|
+
'#c0e9ff',
|
|
106
|
+
'#edf9ff'
|
|
107
|
+
),
|
|
108
|
+
'brown': (
|
|
109
|
+
'#705043',
|
|
110
|
+
'#ad907b',
|
|
111
|
+
'#d7ccc8',
|
|
112
|
+
'#f2ebe8'
|
|
113
|
+
),
|
|
114
|
+
'cyan': (
|
|
115
|
+
'#006778',
|
|
116
|
+
'#65b0bd',
|
|
117
|
+
'#cce5e9',
|
|
118
|
+
'#ebf5f6'
|
|
119
|
+
),
|
|
120
|
+
'green': (
|
|
121
|
+
'#4b830d',
|
|
122
|
+
'#84bc49',
|
|
123
|
+
'#dcedc8',
|
|
124
|
+
'#f1f8e9'
|
|
125
|
+
),
|
|
126
|
+
'indigo': (
|
|
127
|
+
'#26418f',
|
|
128
|
+
'#92a6dd',
|
|
129
|
+
'#dee8ff',
|
|
130
|
+
'#f2f6ff'
|
|
131
|
+
),
|
|
132
|
+
'orange': (
|
|
133
|
+
'#bb4d00',
|
|
134
|
+
'#ffad42',
|
|
135
|
+
'#ffe97d',
|
|
136
|
+
'#fff4cf'
|
|
137
|
+
),
|
|
138
|
+
'pink': (
|
|
139
|
+
'#af4448',
|
|
140
|
+
'#e57373',
|
|
141
|
+
'#ffc2c2',
|
|
142
|
+
'#ffe7e7'
|
|
143
|
+
),
|
|
144
|
+
'purple': (
|
|
145
|
+
'#712177',
|
|
146
|
+
'#b890bb',
|
|
147
|
+
'#e3d3e4',
|
|
148
|
+
'#f7f2f7'
|
|
149
|
+
),
|
|
150
|
+
'slate': (
|
|
151
|
+
'#29434e',
|
|
152
|
+
'#7e9ba5',
|
|
153
|
+
'#b6c6d2',
|
|
154
|
+
'#e2e8ed'
|
|
155
|
+
),
|
|
156
|
+
'teal': (
|
|
157
|
+
'#00695c',
|
|
158
|
+
'#4ebaaa',
|
|
159
|
+
'#ceece7',
|
|
160
|
+
'#ebf7f5'
|
|
161
|
+
)
|
|
162
|
+
);
|
|
163
|
+
}
|
package/types/Action.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Action<T, P> = { type: T; payload?: P }
|
package/types/Axis.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type Anchor = {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export type Axis = {
|
|
8
|
+
scalePadding: number
|
|
8
9
|
anchors?: Anchor[]
|
|
9
10
|
dataKey: string
|
|
10
11
|
dateDisplayFormat: string
|
|
@@ -31,6 +32,7 @@ export type Axis = {
|
|
|
31
32
|
rightMax?: string
|
|
32
33
|
rightNumTicks?: number
|
|
33
34
|
sortDates?: boolean
|
|
35
|
+
sortKey?: string
|
|
34
36
|
showTargetLabel?: boolean
|
|
35
37
|
size?: number
|
|
36
38
|
target?: number
|
package/types/BoxPlot.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type BoxPlot = {
|
|
2
|
+
tableData: Object[]
|
|
3
|
+
labels: {
|
|
4
|
+
mean: string
|
|
5
|
+
maximum: string
|
|
6
|
+
minimum: string
|
|
7
|
+
iqr: string
|
|
8
|
+
median: string
|
|
9
|
+
q1: string
|
|
10
|
+
q3: string
|
|
11
|
+
outliers: string
|
|
12
|
+
values: string
|
|
13
|
+
total: string
|
|
14
|
+
lowerBounds: string
|
|
15
|
+
upperBounds: string
|
|
16
|
+
}
|
|
17
|
+
plots: []
|
|
18
|
+
categories: string[]
|
|
19
|
+
firstQuartilePercentage: number
|
|
20
|
+
geoType: string
|
|
21
|
+
}
|
package/types/Column.ts
CHANGED
package/types/General.ts
ADDED
package/types/Runtime.ts
CHANGED
package/types/Table.ts
CHANGED
|
@@ -11,8 +11,11 @@ export type Table = {
|
|
|
11
11
|
showDataTableLink?: boolean
|
|
12
12
|
showDownloadUrl?: boolean
|
|
13
13
|
download?: boolean
|
|
14
|
-
showDownloadImgButton?: boolean
|
|
15
|
-
showDownloadPdfButton?: boolean
|
|
16
14
|
customTableConfig?: boolean
|
|
17
15
|
excludeColumns?: string[]
|
|
16
|
+
showDownloadImgButton?: boolean
|
|
17
|
+
showDownloadPdfButton?: boolean
|
|
18
|
+
downloadImageButton?: boolean
|
|
19
|
+
downloadPdfButton?: boolean
|
|
20
|
+
dateDisplayFormat: string
|
|
18
21
|
}
|
package/types/Visualization.ts
CHANGED
|
@@ -2,20 +2,26 @@ export type Visualization = {
|
|
|
2
2
|
autoLoad: boolean
|
|
3
3
|
data: any
|
|
4
4
|
dataDescription: Object
|
|
5
|
+
dataFileName: string
|
|
6
|
+
dataFileSourceType: string
|
|
5
7
|
dataKey: string
|
|
8
|
+
datasets: Record<string, any>
|
|
6
9
|
editing: boolean
|
|
7
|
-
formattedData
|
|
10
|
+
formattedData?: Object[]
|
|
8
11
|
general: any
|
|
9
12
|
hide: any[]
|
|
13
|
+
multiDashboards?: any[]
|
|
10
14
|
newViz: boolean
|
|
11
15
|
openModal: boolean
|
|
12
16
|
originalFormattedData: any
|
|
17
|
+
orientation: 'vertical' | 'horizontal'
|
|
13
18
|
table: {
|
|
14
19
|
showDataTableLink: boolean
|
|
15
20
|
}
|
|
16
21
|
title: string
|
|
17
|
-
type: 'chart' | 'map' | 'data-bite' | 'waffle-chart' | 'markup-include' | 'filtered-text' | 'filter-dropdowns'
|
|
22
|
+
type: 'dashboard' | 'chart' | 'map' | 'data-bite' | 'waffle-chart' | 'markup-include' | 'filtered-text' | 'filter-dropdowns'
|
|
18
23
|
uid: string
|
|
19
24
|
usesSharedFilter: any
|
|
20
25
|
visualizationType: string
|
|
26
|
+
visualizationSubType: string
|
|
21
27
|
}
|
package/helpers/getViewport.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export default function getViewport(size) {
|
|
2
|
-
let result = 'lg'
|
|
3
|
-
|
|
4
|
-
const viewports = {
|
|
5
|
-
lg: 1200,
|
|
6
|
-
md: 992,
|
|
7
|
-
sm: 768,
|
|
8
|
-
xs: 576,
|
|
9
|
-
xxs: 350
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
if (size > 1200) return result
|
|
13
|
-
|
|
14
|
-
for (let viewport in viewports) {
|
|
15
|
-
if (size <= viewports[viewport]) {
|
|
16
|
-
result = viewport
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return result
|
|
21
|
-
}
|