@cdc/chart 4.24.9 → 4.24.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/cdcchart.js +45911 -41739
- package/examples/feature/boxplot/boxplot-data.json +88 -22
- package/examples/feature/boxplot/boxplot.json +540 -16
- package/examples/feature/boxplot/testing.csv +7 -7
- package/examples/feature/sankey/sankey-example-data.json +0 -1
- package/examples/private/test.json +20092 -0
- package/index.html +4 -4
- package/package.json +2 -2
- package/src/CdcChart.tsx +209 -188
- package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +27 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +74 -0
- package/src/_stories/Chart.stories.tsx +30 -3
- package/src/_stories/ChartAxisLabels.stories.tsx +20 -0
- package/src/_stories/ChartAxisTitles.stories.tsx +53 -0
- package/src/_stories/ChartEditor.stories.tsx +27 -0
- package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +159 -0
- package/src/_stories/_mock/boxplot_multiseries.json +647 -0
- package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
- package/src/_stories/_mock/dynamic_series_config.json +979 -0
- package/src/_stories/_mock/horizontal_bar.json +257 -0
- package/src/_stories/_mock/large_x_axis_labels.json +261 -0
- package/src/_stories/_mock/paired-bar.json +262 -0
- package/src/_stories/_mock/pie_with_data.json +255 -0
- package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
- package/src/_stories/_mock/simplified_line.json +1510 -0
- package/src/_stories/_mock/suppression_mock.json +1549 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +0 -3
- package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
- package/src/components/Axis/Categorical.Axis.tsx +22 -4
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +95 -16
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +41 -17
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
- package/src/components/BarChart/components/BarChart.Vertical.tsx +123 -47
- package/src/components/BarChart/helpers/index.ts +23 -5
- package/src/components/BoxPlot/BoxPlot.tsx +189 -0
- package/src/components/BrushChart.tsx +3 -2
- package/src/components/DeviationBar.jsx +58 -8
- package/src/components/EditorPanel/EditorPanel.tsx +127 -102
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +11 -28
- package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +21 -4
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +121 -56
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +296 -35
- package/src/components/EditorPanel/components/panels.scss +4 -6
- package/src/components/EditorPanel/editor-panel.scss +0 -8
- package/src/components/EditorPanel/helpers/tests/updateFieldRankByValue.test.ts +38 -0
- package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +42 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +16 -1
- package/src/components/ForestPlot/ForestPlot.tsx +2 -3
- package/src/components/ForestPlot/ForestPlotProps.ts +2 -0
- package/src/components/Legend/Legend.Component.tsx +23 -24
- package/src/components/Legend/Legend.Suppression.tsx +25 -20
- package/src/components/Legend/Legend.tsx +16 -18
- package/src/components/Legend/helpers/index.ts +16 -19
- package/src/components/LegendWrapper.tsx +3 -1
- package/src/components/LineChart/components/LineChart.Circle.tsx +10 -0
- package/src/components/LineChart/helpers.ts +48 -43
- package/src/components/LineChart/index.tsx +88 -82
- package/src/components/LinearChart.tsx +747 -562
- package/src/components/PairedBarChart.jsx +50 -10
- package/src/components/PieChart/PieChart.tsx +1 -6
- package/src/components/Regions/components/Regions.tsx +33 -19
- package/src/components/Sankey/index.tsx +50 -32
- package/src/components/Sankey/sankey.scss +6 -5
- package/src/components/Sankey/useSankeyAlert.tsx +60 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
- package/src/components/ZoomBrush.tsx +25 -6
- package/src/coreStyles_chart.scss +3 -0
- package/src/data/initial-state.js +8 -10
- package/src/helpers/configHelpers.ts +28 -0
- package/src/helpers/handleRankByValue.ts +15 -0
- package/src/helpers/sizeHelpers.ts +25 -0
- package/src/helpers/tests/handleRankByValue.test.ts +37 -0
- package/src/helpers/tests/sizeHelpers.test.ts +80 -0
- package/src/hooks/useColorPalette.js +10 -2
- package/src/hooks/useLegendClasses.ts +13 -22
- package/src/hooks/useMinMax.ts +27 -13
- package/src/hooks/useReduceData.ts +43 -10
- package/src/hooks/useScales.ts +87 -38
- package/src/hooks/useTooltip.tsx +62 -53
- package/src/index.jsx +1 -0
- package/src/scss/DataTable.scss +5 -4
- package/src/scss/main.scss +57 -70
- package/src/types/ChartConfig.ts +43 -34
- package/src/types/ChartContext.ts +22 -15
- package/src/types/ForestPlot.ts +8 -0
- package/src/_stories/Chart.Legend.Gradient.tsx +0 -19
- package/src/_stories/ChartBrush.stories.tsx +0 -19
- package/src/components/BoxPlot/BoxPlot.jsx +0 -111
- package/src/components/LinearChart.jsx +0 -817
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { cloneDeep } from 'lodash'
|
|
2
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
3
|
+
|
|
4
|
+
/* editConfigKeys
|
|
5
|
+
* Add edit or update config keys
|
|
6
|
+
* keyUpdates: { path: string[], value: any }[]
|
|
7
|
+
* path is the array of keys needed to reach the value to be updated
|
|
8
|
+
* value is the new value to be set
|
|
9
|
+
* if the key does not exist, it will be created
|
|
10
|
+
*/
|
|
11
|
+
export function editConfigKeys(config: ChartConfig, keyUpdates: { path: string[]; value: any }[]): ChartConfig {
|
|
12
|
+
const configDeepCopy = cloneDeep(config)
|
|
13
|
+
|
|
14
|
+
const newConfig = keyUpdates.reduce((acc, { path, value }) => {
|
|
15
|
+
const pathCopy = [...path]
|
|
16
|
+
const lastKey = pathCopy.pop()
|
|
17
|
+
const target = pathCopy.reduce((target, key) => {
|
|
18
|
+
if (!target[key]) {
|
|
19
|
+
target[key] = {}
|
|
20
|
+
}
|
|
21
|
+
return target[key]
|
|
22
|
+
}, acc)
|
|
23
|
+
target[lastKey] = value
|
|
24
|
+
return acc
|
|
25
|
+
}, configDeepCopy)
|
|
26
|
+
|
|
27
|
+
return newConfig
|
|
28
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
2
|
+
|
|
3
|
+
const getNumericValue = number => {
|
|
4
|
+
if (typeof number === 'string') return parseFloat(number.replace(/,/g, ''))
|
|
5
|
+
return Number(number)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const handleRankByValue = (data, passedConfig: ChartConfig) => {
|
|
9
|
+
if (passedConfig.rankByValue) {
|
|
10
|
+
const series = passedConfig.series[0].dataKey
|
|
11
|
+
const sorted = data.sort((a, b) => getNumericValue(a[series]) - getNumericValue(b[series]))
|
|
12
|
+
return passedConfig.rankByValue === 'asc' ? sorted : sorted.reverse()
|
|
13
|
+
}
|
|
14
|
+
return data
|
|
15
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { isMobileHeightViewport } from '@cdc/core/helpers/viewports'
|
|
2
|
+
import { ChartConfig, ViewportSize } from '../types/ChartConfig'
|
|
3
|
+
|
|
4
|
+
export function getOrientation(
|
|
5
|
+
{ orientation, heights, visualizationType }: Pick<ChartConfig, 'orientation' | 'heights' | 'visualizationType'>,
|
|
6
|
+
currentViewport: ViewportSize
|
|
7
|
+
): 'vertical' | 'horizontal' | 'mobileVertical' {
|
|
8
|
+
const isForestPlot = visualizationType === 'Forest Plot'
|
|
9
|
+
const useVertical = orientation === 'vertical' || isForestPlot
|
|
10
|
+
const useMobileVertical = heights?.mobileVertical && isMobileHeightViewport(currentViewport)
|
|
11
|
+
const responsiveVertical = useMobileVertical ? 'mobileVertical' : 'vertical'
|
|
12
|
+
|
|
13
|
+
return useVertical ? responsiveVertical : 'horizontal'
|
|
14
|
+
}
|
|
15
|
+
export function calcInitialHeight(
|
|
16
|
+
{ heights, orientation, visualizationType }: Pick<ChartConfig, 'heights' | 'orientation' | 'visualizationType'>,
|
|
17
|
+
currentViewport: ViewportSize
|
|
18
|
+
): number {
|
|
19
|
+
// if no heights are provided assume config has not been loaded
|
|
20
|
+
if (!heights) return 0
|
|
21
|
+
|
|
22
|
+
const renderedOrientation = getOrientation({ orientation, heights, visualizationType }, currentViewport)
|
|
23
|
+
const height = Number(heights?.[renderedOrientation])
|
|
24
|
+
return isNaN(height) ? 0 : height
|
|
25
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { handleRankByValue } from '../handleRankByValue'
|
|
2
|
+
import { ChartConfig } from '../../types/ChartConfig'
|
|
3
|
+
|
|
4
|
+
describe('handleRankByValue', () => {
|
|
5
|
+
it('should sort the data in ascending order when rankByValue is "asc"', () => {
|
|
6
|
+
const data = [{ value: 3 }, { value: 1 }, { value: 2 }]
|
|
7
|
+
const config: ChartConfig = {
|
|
8
|
+
rankByValue: 'asc',
|
|
9
|
+
series: [{ dataKey: 'value' }]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const result = handleRankByValue(data, config)
|
|
13
|
+
expect(result).toEqual([{ value: 1 }, { value: 2 }, { value: 3 }])
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should sort the data in descending order when rankByValue is "desc"', () => {
|
|
17
|
+
const data = [{ value: 3 }, { value: 1 }, { value: 2 }]
|
|
18
|
+
const config: ChartConfig = {
|
|
19
|
+
rankByValue: 'desc',
|
|
20
|
+
series: [{ dataKey: 'value' }]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const result = handleRankByValue(data, config)
|
|
24
|
+
expect(result).toEqual([{ value: 3 }, { value: 2 }, { value: 1 }])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should handle numeric strings correctly', () => {
|
|
28
|
+
const data = [{ value: '3' }, { value: '1' }, { value: '2' }]
|
|
29
|
+
const config: ChartConfig = {
|
|
30
|
+
rankByValue: 'asc',
|
|
31
|
+
series: [{ dataKey: 'value' }]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const result = handleRankByValue(data, config)
|
|
35
|
+
expect(result).toEqual([{ value: '1' }, { value: '2' }, { value: '3' }])
|
|
36
|
+
})
|
|
37
|
+
})
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { calcInitialHeight, getOrientation } from '../sizeHelpers'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { ChartOrientation, VisualizationType } from '../../types/ChartConfig'
|
|
4
|
+
|
|
5
|
+
describe('sizeHelpers', () => {
|
|
6
|
+
describe('getOrientation', () => {
|
|
7
|
+
it("should return 'vertical' when orientation is vertical", () => {
|
|
8
|
+
const config = {
|
|
9
|
+
orientation: 'vertical' as ChartOrientation,
|
|
10
|
+
heights: { mobileVertical: 0, vertical: 0, horizontal: 0 },
|
|
11
|
+
visualizationType: 'Bar' as VisualizationType
|
|
12
|
+
}
|
|
13
|
+
expect(getOrientation(config, 'md')).toBe('vertical')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it("should return 'horizontal' when orientation is horizontal", () => {
|
|
17
|
+
const config = {
|
|
18
|
+
orientation: 'horizontal' as ChartOrientation,
|
|
19
|
+
heights: { mobileVertical: 0, vertical: 0, horizontal: 0 },
|
|
20
|
+
visualizationType: 'Bar' as VisualizationType
|
|
21
|
+
}
|
|
22
|
+
expect(getOrientation(config, 'md')).toBe('horizontal')
|
|
23
|
+
})
|
|
24
|
+
it("should return 'vertical' when orientation is horizontal but visualizationType is 'Forest Plot'", () => {
|
|
25
|
+
const config = {
|
|
26
|
+
orientation: 'horizontal' as ChartOrientation,
|
|
27
|
+
heights: { mobileVertical: 0, vertical: 0, horizontal: 0 },
|
|
28
|
+
visualizationType: 'Forest Plot' as VisualizationType
|
|
29
|
+
}
|
|
30
|
+
expect(getOrientation(config, 'md')).toBe('vertical')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should return mobileVertical when viewport is mobile height', () => {
|
|
34
|
+
const config = {
|
|
35
|
+
orientation: 'vertical' as ChartOrientation,
|
|
36
|
+
heights: { mobileVertical: 100, vertical: 0, horizontal: 0 },
|
|
37
|
+
visualizationType: 'Bar' as VisualizationType
|
|
38
|
+
}
|
|
39
|
+
expect(getOrientation(config, 'xxs')).toBe('mobileVertical')
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('calcInitialHeight', () => {
|
|
45
|
+
it('should return 0 when no heights are provided', () => {
|
|
46
|
+
const config = {
|
|
47
|
+
heights: undefined,
|
|
48
|
+
orientation: 'vertical' as ChartOrientation,
|
|
49
|
+
visualizationType: 'Bar' as VisualizationType
|
|
50
|
+
}
|
|
51
|
+
expect(calcInitialHeight(config, 'md')).toBe(0)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('should return vertical height when orientation is vertical', () => {
|
|
55
|
+
const config = {
|
|
56
|
+
orientation: 'vertical' as ChartOrientation,
|
|
57
|
+
heights: { mobileVertical: 0, vertical: 100, horizontal: 0 },
|
|
58
|
+
visualizationType: 'Bar' as VisualizationType
|
|
59
|
+
}
|
|
60
|
+
expect(calcInitialHeight(config, 'md')).toBe(100)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should return horizontal height when orientation is horizontal', () => {
|
|
64
|
+
const config = {
|
|
65
|
+
orientation: 'horizontal' as ChartOrientation,
|
|
66
|
+
heights: { mobileVertical: 0, vertical: 0, horizontal: 100 },
|
|
67
|
+
visualizationType: 'Bar' as VisualizationType
|
|
68
|
+
}
|
|
69
|
+
expect(calcInitialHeight(config, 'md')).toBe(100)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should return mobileVertical height when viewport is mobile height', () => {
|
|
73
|
+
const config = {
|
|
74
|
+
heights: { mobileVertical: 100, vertical: 0, horizontal: 0 },
|
|
75
|
+
orientation: 'vertical' as ChartOrientation,
|
|
76
|
+
visualizationType: 'Bar' as VisualizationType
|
|
77
|
+
}
|
|
78
|
+
expect(calcInitialHeight(config, 'xxs')).toBe(100)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
@@ -5,11 +5,14 @@ export const useColorPalette = (config, updateConfig) => {
|
|
|
5
5
|
let twoColorPalettes = []
|
|
6
6
|
let sequential = []
|
|
7
7
|
let nonSequential = []
|
|
8
|
+
const accessibleColors = []
|
|
8
9
|
|
|
9
10
|
// Get two color palettes if visualization type is Paired Bar
|
|
10
11
|
if (config.visualizationType === 'Paired Bar' || config.visualizationType === 'Deviation Bar') {
|
|
11
12
|
const isReversed = config.twoColor.isPaletteReversed
|
|
12
|
-
twoColorPalettes = Object.keys(twoColorPalette).filter(name =>
|
|
13
|
+
twoColorPalettes = Object.keys(twoColorPalette).filter(name =>
|
|
14
|
+
isReversed ? name.endsWith('reverse') : !name.endsWith('reverse')
|
|
15
|
+
)
|
|
13
16
|
} else {
|
|
14
17
|
// Get sequential and non-sequential palettes for other visualization types
|
|
15
18
|
const seqPalettes = []
|
|
@@ -18,6 +21,7 @@ export const useColorPalette = (config, updateConfig) => {
|
|
|
18
21
|
for (const paletteName in colorPalettesChart) {
|
|
19
22
|
const isSequential = paletteName.startsWith('sequential')
|
|
20
23
|
const isQualitative = paletteName.startsWith('qualitative')
|
|
24
|
+
const colorblindsafe = paletteName.startsWith('colorblindsafe')
|
|
21
25
|
const isReversed = paletteName.endsWith('reverse')
|
|
22
26
|
|
|
23
27
|
if (isSequential && ((!config.isPaletteReversed && !isReversed) || (config.isPaletteReversed && isReversed))) {
|
|
@@ -27,6 +31,9 @@ export const useColorPalette = (config, updateConfig) => {
|
|
|
27
31
|
if (isQualitative && ((!config.isPaletteReversed && !isReversed) || (config.isPaletteReversed && isReversed))) {
|
|
28
32
|
nonSeqPalettes.push(paletteName)
|
|
29
33
|
}
|
|
34
|
+
if (colorblindsafe && ((!config.isPaletteReversed && !isReversed) || (config.isPaletteReversed && isReversed))) {
|
|
35
|
+
accessibleColors.push(paletteName)
|
|
36
|
+
}
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
sequential = seqPalettes
|
|
@@ -64,5 +71,6 @@ export const useColorPalette = (config, updateConfig) => {
|
|
|
64
71
|
}, [config.isPaletteReversed])
|
|
65
72
|
|
|
66
73
|
// Return all palettes
|
|
67
|
-
|
|
74
|
+
|
|
75
|
+
return { twoColorPalettes, sequential, nonSequential, accessibleColors }
|
|
68
76
|
}
|
|
@@ -1,22 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
legend: {
|
|
3
|
-
position: 'left' | 'bottom' | 'top' | 'right'
|
|
4
|
-
singleRow?: boolean
|
|
5
|
-
reverseLabelOrder?: boolean
|
|
6
|
-
verticalSorted?: boolean
|
|
7
|
-
hideBorder: {
|
|
8
|
-
side?: boolean
|
|
9
|
-
topBottom?: boolean
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
}
|
|
1
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
13
2
|
|
|
14
|
-
const useLegendClasses = (config:
|
|
3
|
+
const useLegendClasses = (config: ChartConfig) => {
|
|
4
|
+
const { position, singleRow, reverseLabelOrder, verticalSorted, hideBorder } = config.legend
|
|
15
5
|
const containerClasses = ['legend-container']
|
|
16
6
|
const innerClasses = ['legend-container__inner']
|
|
17
7
|
|
|
18
8
|
// Handle legend positioning
|
|
19
|
-
switch (
|
|
9
|
+
switch (position) {
|
|
20
10
|
case 'left':
|
|
21
11
|
containerClasses.push('left')
|
|
22
12
|
break
|
|
@@ -34,32 +24,33 @@ const useLegendClasses = (config: ConfigType) => {
|
|
|
34
24
|
}
|
|
35
25
|
|
|
36
26
|
// Handle single row configuration for 'bottom' and 'top' positions
|
|
37
|
-
if (['bottom', 'top'].includes(
|
|
27
|
+
if (['bottom', 'top'].includes(position) && singleRow) {
|
|
38
28
|
innerClasses.push('single-row')
|
|
39
29
|
}
|
|
40
30
|
|
|
41
31
|
// Reverse label order
|
|
42
|
-
if (
|
|
32
|
+
if (reverseLabelOrder) {
|
|
43
33
|
innerClasses.push('d-flex', 'flex-column-reverse')
|
|
44
34
|
}
|
|
45
35
|
|
|
46
36
|
// Vertical sorting for 'bottom' and 'top' positions
|
|
47
|
-
if (['bottom', 'top'].includes(
|
|
37
|
+
if (['bottom', 'top'].includes(position) && verticalSorted) {
|
|
48
38
|
innerClasses.push('vertical-sorted')
|
|
49
39
|
}
|
|
50
40
|
|
|
51
41
|
// Configure border classes
|
|
52
|
-
if (
|
|
53
|
-
config.legend.hideBorder.side &&
|
|
54
|
-
(['right', 'left'].includes(config.legend.position) || !config.legend.position)
|
|
55
|
-
) {
|
|
42
|
+
if (hideBorder.side && (['right', 'left'].includes(position) || !position)) {
|
|
56
43
|
containerClasses.push('no-border')
|
|
57
44
|
}
|
|
58
45
|
|
|
59
|
-
if (
|
|
46
|
+
if (hideBorder.topBottom && ['top', 'bottom'].includes(position)) {
|
|
60
47
|
containerClasses.push('no-border')
|
|
61
48
|
}
|
|
62
49
|
|
|
50
|
+
if (hideBorder.topBottom && ['top'].includes(position)) {
|
|
51
|
+
containerClasses.push('p-0')
|
|
52
|
+
}
|
|
53
|
+
|
|
63
54
|
return {
|
|
64
55
|
containerClasses,
|
|
65
56
|
innerClasses
|
package/src/hooks/useMinMax.ts
CHANGED
|
@@ -40,11 +40,13 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
40
40
|
const minRequiredCIPadding = 1.15 // regardless of Editor if CI data, there must be 10% padding added
|
|
41
41
|
const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
|
|
42
42
|
// do validation bafore applying t0 charts
|
|
43
|
-
const isMaxValid = existPositiveValue ? enteredMaxValue >= maxValue : enteredMaxValue >= 0
|
|
44
|
-
const isMinValid = isLogarithmicAxis
|
|
43
|
+
const isMaxValid = existPositiveValue ? Number(enteredMaxValue) >= maxValue : Number(enteredMaxValue) >= 0
|
|
44
|
+
const isMinValid = isLogarithmicAxis
|
|
45
|
+
? Number(enteredMinValue) >= 0
|
|
46
|
+
: (Number(enteredMinValue) <= 0 && minValue >= 0) || (Number(enteredMinValue) <= minValue && minValue < 0)
|
|
45
47
|
|
|
46
|
-
min = enteredMinValue && isMinValid ? enteredMinValue : minValue
|
|
47
|
-
max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
|
|
48
|
+
min = enteredMinValue && isMinValid ? Number(enteredMinValue) : minValue
|
|
49
|
+
max = enteredMaxValue && isMaxValid ? Number(enteredMaxValue) : Number.MIN_VALUE
|
|
48
50
|
|
|
49
51
|
const { lower, upper } = config?.confidenceKeys || {}
|
|
50
52
|
|
|
@@ -122,8 +124,8 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
122
124
|
leftMax = findMaxFromSeriesKeys(data, leftAxisSeriesItems, leftMax, 'left')
|
|
123
125
|
rightMax = findMaxFromSeriesKeys(data, rightAxisSeriesItems, rightMax, 'right')
|
|
124
126
|
|
|
125
|
-
if (leftMax < enteredMaxValue) {
|
|
126
|
-
leftMax = enteredMaxValue
|
|
127
|
+
if (leftMax < Number(enteredMaxValue)) {
|
|
128
|
+
leftMax = Number(enteredMaxValue)
|
|
127
129
|
}
|
|
128
130
|
} catch (e) {
|
|
129
131
|
console.error(e.message)
|
|
@@ -131,10 +133,18 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
// this should not apply to bar charts if there is negative CI data
|
|
134
|
-
if (
|
|
136
|
+
if (
|
|
137
|
+
(visualizationType === 'Bar' || checkLineToBarGraph() || (visualizationType === 'Combo' && !isAllLine)) &&
|
|
138
|
+
min > 0
|
|
139
|
+
) {
|
|
135
140
|
min = 0
|
|
136
141
|
}
|
|
137
|
-
if (
|
|
142
|
+
if (
|
|
143
|
+
(config.visualizationType === 'Bar' ||
|
|
144
|
+
checkLineToBarGraph() ||
|
|
145
|
+
(config.visualizationType === 'Combo' && !isAllLine)) &&
|
|
146
|
+
min < 0
|
|
147
|
+
) {
|
|
138
148
|
min = min * 1.1
|
|
139
149
|
}
|
|
140
150
|
|
|
@@ -143,18 +153,22 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
143
153
|
min = 0
|
|
144
154
|
}
|
|
145
155
|
if (enteredMinValue) {
|
|
146
|
-
const isMinValid = isLogarithmicAxis
|
|
147
|
-
|
|
156
|
+
const isMinValid = isLogarithmicAxis
|
|
157
|
+
? Number(enteredMinValue) >= 0 && Number(enteredMinValue) < minValue
|
|
158
|
+
: Number(enteredMinValue) < minValue
|
|
159
|
+
min = Number(enteredMinValue) && isMinValid ? Number(enteredMinValue) : minValue
|
|
148
160
|
}
|
|
149
161
|
}
|
|
150
162
|
|
|
151
163
|
if (config.visualizationType === 'Deviation Bar' && min > 0) {
|
|
152
164
|
const isMinValid = Number(enteredMinValue) < Math.min(minValue, Number(config.xAxis.target))
|
|
153
|
-
min = enteredMinValue && isMinValid ? enteredMinValue : 0
|
|
165
|
+
min = Number(enteredMinValue) && isMinValid ? Number(enteredMinValue) : 0
|
|
154
166
|
}
|
|
155
167
|
|
|
156
168
|
if (config.visualizationType === 'Line' && !checkLineToBarGraph()) {
|
|
157
|
-
const isMinValid = isLogarithmicAxis
|
|
169
|
+
const isMinValid = isLogarithmicAxis
|
|
170
|
+
? Number(enteredMinValue) >= 0 && Number(enteredMinValue) < minValue
|
|
171
|
+
: Number(enteredMinValue) < minValue
|
|
158
172
|
// update minValue for (0) Suppression points
|
|
159
173
|
const suppressedMinValue = tableData?.some((dataItem, index) => {
|
|
160
174
|
return config.preliminaryData?.some(pd => {
|
|
@@ -171,7 +185,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
171
185
|
return valueMatch && (index === 0 || index === tableData.length - 1)
|
|
172
186
|
})
|
|
173
187
|
})
|
|
174
|
-
min = enteredMinValue && isMinValid ? enteredMinValue : suppressedMinValue ? 0 : minValue
|
|
188
|
+
min = Number(enteredMinValue) && isMinValid ? Number(enteredMinValue) : suppressedMinValue ? 0 : minValue
|
|
175
189
|
}
|
|
176
190
|
//If data value max wasn't provided, calculate it
|
|
177
191
|
if (max === Number.MIN_VALUE) {
|
|
@@ -3,12 +3,28 @@ import isNumber from '@cdc/core/helpers/isNumber'
|
|
|
3
3
|
function useReduceData(config, data) {
|
|
4
4
|
const isBar = config.series.every(({ type }) => type === 'Bar')
|
|
5
5
|
const isAllLine = config.series.every(({ type }) => ['Line', 'dashed-sm', 'dashed-md', 'dashed-lg'].includes(type))
|
|
6
|
-
const sumYValues = seriesKeys => xValue =>
|
|
7
|
-
|
|
6
|
+
const sumYValues = seriesKeys => xValue =>
|
|
7
|
+
seriesKeys.reduce((yTotal, k) => (isNaN(Number(xValue[k])) ? yTotal : yTotal + Number(xValue[k])), 0)
|
|
8
|
+
const getSeriesKey = seriesKey => {
|
|
9
|
+
const series = config.runtime.series.find(item => item.dataKey === seriesKey)
|
|
10
|
+
return series?.dynamicCategory ? series.originalDataKey : seriesKey
|
|
11
|
+
}
|
|
8
12
|
const getMaxValueFromData = () => {
|
|
9
|
-
let max = Math.max(
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
let max = Math.max(
|
|
14
|
+
...data.map(d =>
|
|
15
|
+
Math.max(
|
|
16
|
+
...config.runtime.seriesKeys.map(key => {
|
|
17
|
+
const seriesKey = getSeriesKey(key)
|
|
18
|
+
return isNumber(d[seriesKey]) ? Number(cleanChars(d[seriesKey])) : 0
|
|
19
|
+
})
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
if (
|
|
25
|
+
(config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && isBar)) &&
|
|
26
|
+
config.visualizationSubType === 'stacked'
|
|
27
|
+
) {
|
|
12
28
|
const yTotals = data.map(sumYValues(config.runtime.seriesKeys)).filter(num => !isNaN(num))
|
|
13
29
|
max = Math.max(...yTotals)
|
|
14
30
|
}
|
|
@@ -18,15 +34,23 @@ function useReduceData(config, data) {
|
|
|
18
34
|
max = Math.max(...yTotals)
|
|
19
35
|
}
|
|
20
36
|
|
|
21
|
-
if (
|
|
22
|
-
|
|
37
|
+
if (
|
|
38
|
+
(config.visualizationType === 'Bar' || config.visualizationType === 'Deviation Bar') &&
|
|
39
|
+
config.series &&
|
|
40
|
+
config.series.dataKey
|
|
41
|
+
) {
|
|
42
|
+
max = Math.max(
|
|
43
|
+
...data.map(d => (isNumber(d[config.series.dataKey]) ? Number(cleanChars(d[config.series.dataKey])) : 0))
|
|
44
|
+
)
|
|
23
45
|
}
|
|
24
46
|
|
|
25
47
|
if (config.visualizationType === 'Combo' && config.visualizationSubType === 'stacked' && !isBar) {
|
|
26
48
|
if (config.runtime.barSeriesKeys && config.runtime.lineSeriesKeys) {
|
|
27
49
|
const yTotals = data.map(sumYValues(config.runtime.barSeriesKeys))
|
|
28
50
|
|
|
29
|
-
const lineMax = Math.max(
|
|
51
|
+
const lineMax = Math.max(
|
|
52
|
+
...data.map(d => Math.max(...config.runtime.lineSeriesKeys.map(key => Number(cleanChars(d[key])))))
|
|
53
|
+
)
|
|
30
54
|
const barMax = Math.max(...yTotals)
|
|
31
55
|
|
|
32
56
|
max = Math.max(barMax, lineMax)
|
|
@@ -37,7 +61,16 @@ function useReduceData(config, data) {
|
|
|
37
61
|
}
|
|
38
62
|
|
|
39
63
|
const getMinValueFromData = () => {
|
|
40
|
-
const minNumberFromData = Math.min(
|
|
64
|
+
const minNumberFromData = Math.min(
|
|
65
|
+
...data.map(d =>
|
|
66
|
+
Math.min(
|
|
67
|
+
...config.runtime.seriesKeys.map(key => {
|
|
68
|
+
const seriesKey = getSeriesKey(key)
|
|
69
|
+
return isNumber(d[seriesKey]) ? Number(cleanChars(d[seriesKey])) : Infinity
|
|
70
|
+
})
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
)
|
|
41
74
|
|
|
42
75
|
return String(minNumberFromData)
|
|
43
76
|
}
|
|
@@ -46,7 +79,7 @@ function useReduceData(config, data) {
|
|
|
46
79
|
if (!config.runtime.seriesKeys) {
|
|
47
80
|
return false
|
|
48
81
|
}
|
|
49
|
-
return config.runtime.seriesKeys.some(key => data.some(d => d[key] >= 0))
|
|
82
|
+
return config.runtime.seriesKeys.some(key => data.some(d => d[getSeriesKey(key)] >= 0))
|
|
50
83
|
}
|
|
51
84
|
|
|
52
85
|
const cleanChars = value => {
|