@cdc/chart 4.26.5 → 4.26.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONFIG.md +20 -14
- package/dist/{cdcchart-DQ00cQCm.es.js → cdcchart-BWVIUcAF.es.js} +5 -55
- package/dist/cdcchart.js +36554 -34320
- package/examples/feature/heatmap/average-age-categorical.json +1 -1
- package/examples/feature/heatmap/calendar-heatmap.json +1 -1
- package/examples/feature/heatmap/sparse-aggregation.json +1 -1
- package/examples/private/bad-y-axis.json +543 -0
- package/examples/private/bar-chart-negative-value.json +455 -0
- package/examples/private/flueview.json +22051 -0
- package/package.json +3 -11
- package/src/CdcChart.tsx +7 -6
- package/src/CdcChartComponent.tsx +71 -18
- package/src/_stories/Chart.PieLabels.stories.tsx +47 -0
- package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +183 -0
- package/src/_stories/Chart.YAxisAutoMaxStrategy.stories.tsx +52 -0
- package/src/_stories/Chart.YAxisFilterDomain.stories.tsx +90 -0
- package/src/_stories/Chart.smoke.stories.tsx +14 -1
- package/src/_stories/ChartAnnotation.smoke.stories.tsx +10 -0
- package/src/_stories/ChartPrefixSuffix.smoke.stories.tsx +28 -1
- package/src/_stories/_mock/annotation_event-line_mock.json +1064 -0
- package/src/_stories/_mock/heatmap-average-age-categorical.json +1 -1
- package/src/_stories/_mock/heatmap.json +1 -1
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +22 -1
- package/src/components/Annotations/components/AnnotationDraggable.tsx +219 -119
- package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +198 -0
- package/src/components/Axis/LeftAxis.tsx +3 -0
- package/src/components/Axis/LeftAxisGridlines.tsx +8 -1
- package/src/components/Axis/RightAxis.tsx +3 -0
- package/src/components/Axis/tests/AxisTickValues.test.tsx +86 -0
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +1 -2
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -1
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -1
- package/src/components/BarChart/components/BarChart.Vertical.tsx +1 -2
- package/src/components/BarChart/helpers/getBarData.ts +28 -28
- package/src/components/BarChart/helpers/getBarHeights.ts +2 -2
- package/src/components/BarChart/helpers/tests/useBarChart.test.tsx +98 -0
- package/src/components/BarChart/helpers/useBarChart.ts +4 -5
- package/src/components/BoxPlot/BoxPlot.Horizontal.tsx +7 -6
- package/src/components/BoxPlot/BoxPlot.Vertical.tsx +9 -7
- package/src/components/BoxPlot/helpers/index.ts +4 -3
- package/src/components/Brush/BrushSelector.tsx +1 -1
- package/src/components/EditorPanel/EditorPanel.tsx +457 -165
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +101 -52
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +16 -7
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +19 -4
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.test.tsx +98 -0
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +10 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.test.tsx +484 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +3 -0
- package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +262 -0
- package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +32 -0
- package/src/components/EditorPanel/components/panels.scss +10 -0
- package/src/components/EditorPanel/editor-panel.scss +47 -0
- package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +6 -2
- package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +20 -2
- package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +3 -3
- package/src/components/EditorPanel/useEditorPermissions.test.tsx +288 -11
- package/src/components/EditorPanel/useEditorPermissions.ts +46 -6
- package/src/components/Forecasting/Forecasting.tsx +1 -1
- package/src/components/HeatMap/components/HeatMap.tsx +22 -17
- package/src/components/HeatMap/components/HeatMapGradientLegend.tsx +29 -15
- package/src/components/HeatMap/heatmap.css +15 -1
- package/src/components/HeatMap/tests/HeatMap.test.tsx +39 -7
- package/src/components/Legend/Legend.Component.tsx +56 -61
- package/src/components/Legend/Legend.Suppression.tsx +3 -3
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +0 -1
- package/src/components/Legend/helpers/createFormatLabels.test.ts +88 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +6 -1
- package/src/components/LineChart/helpers.test.ts +230 -1
- package/src/components/LineChart/helpers.ts +25 -14
- package/src/components/LineChart/index.tsx +23 -7
- package/src/components/LinearChart/tests/LinearChart.test.tsx +30 -0
- package/src/components/LinearChart.tsx +51 -15
- package/src/components/PieChart/PieChart.tsx +117 -10
- package/src/components/PieChart/helpers/labelPlacement.test.ts +88 -0
- package/src/components/PieChart/helpers/labelPlacement.ts +95 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +5 -13
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +3 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +38 -9
- package/src/components/SmallMultiples/tests/SmallMultiples.test.tsx +170 -0
- package/src/data/initial-state.js +3 -0
- package/src/helpers/calculateLeftYAxisWidth.ts +9 -3
- package/src/helpers/categoryOrder.ts +60 -0
- package/src/helpers/colorAssignmentHelpers.ts +102 -0
- package/src/helpers/dataHelpers.ts +11 -1
- package/src/helpers/getAxisMaxOverride.ts +16 -0
- package/src/helpers/getBoxPlotConfig.ts +19 -12
- package/src/helpers/getCleanTopTickMax.ts +64 -0
- package/src/helpers/getColorScale.ts +18 -9
- package/src/helpers/getComboChartConfig.ts +0 -1
- package/src/helpers/getExcludedData.ts +0 -1
- package/src/helpers/getHasBoundarySuppression.ts +27 -0
- package/src/helpers/getMinMax.ts +25 -22
- package/src/helpers/getNewRuntime.ts +0 -2
- package/src/helpers/getPiePercent.ts +3 -3
- package/src/helpers/getYAxisDomainData.ts +38 -0
- package/src/helpers/getYAxisFinalizationEligibility.ts +40 -0
- package/src/helpers/hasSpacedInlineLabel.ts +10 -0
- package/src/helpers/isConvertLineToBarGraph.ts +2 -2
- package/src/helpers/seriesColumnSettings.ts +26 -7
- package/src/helpers/sizeHelpers.ts +0 -2
- package/src/helpers/smallMultiplesHelpers.ts +5 -0
- package/src/helpers/tests/calculateLeftYAxisWidth.test.ts +38 -0
- package/src/helpers/tests/categoryOrder.test.ts +57 -0
- package/src/helpers/tests/dataHelpers.test.ts +135 -0
- package/src/helpers/tests/getAxisMaxOverride.test.ts +36 -0
- package/src/helpers/tests/getCleanTopTickMax.test.ts +165 -0
- package/src/helpers/tests/getColorScale.test.ts +197 -0
- package/src/helpers/tests/getMinMax.test.ts +211 -0
- package/src/helpers/tests/getYAxisDomainData.test.ts +119 -0
- package/src/helpers/tests/getYAxisFinalizationEligibility.test.ts +185 -0
- package/src/helpers/tests/{getYAxisAutoPaddingMode.test.ts → hasSpacedInlineLabel.test.ts} +17 -32
- package/src/helpers/tests/seriesColumnSettings.test.ts +76 -2
- package/src/helpers/tests/smallMultiplesHelpers.test.ts +99 -0
- package/src/hooks/tests/useRightAxis.test.tsx +268 -0
- package/src/hooks/tests/useScales.test.tsx +302 -27
- package/src/hooks/tests/useTooltip.test.tsx +132 -0
- package/src/hooks/useRightAxis.ts +35 -25
- package/src/hooks/useScales.ts +45 -28
- package/src/hooks/useTooltip.tsx +17 -11
- package/src/store/chart.reducer.ts +0 -1
- package/src/test/CdcChart.dataTable.test.tsx +216 -0
- package/src/types/ChartConfig.ts +6 -1
- package/src/types/ChartContext.ts +3 -0
- package/src/helpers/getYAxisAutoPadding.ts +0 -55
- package/src/helpers/getYAxisAutoPaddingMode.ts +0 -17
- package/src/helpers/tests/getYAxisAutoPadding.test.ts +0 -37
|
@@ -28,6 +28,7 @@ interface LeftAxisProps {
|
|
|
28
28
|
xMax: number
|
|
29
29
|
yAxisWidth: number
|
|
30
30
|
numTicks: number
|
|
31
|
+
tickValues?: number[]
|
|
31
32
|
tickLabelFontSize: number
|
|
32
33
|
axisLabelFontSize: number
|
|
33
34
|
handleLeftTickFormatting: (tick: any, index: number, ticks: any[]) => string
|
|
@@ -45,6 +46,7 @@ const LeftAxis: React.FC<LeftAxisProps> = ({
|
|
|
45
46
|
xMax,
|
|
46
47
|
yAxisWidth,
|
|
47
48
|
numTicks,
|
|
49
|
+
tickValues,
|
|
48
50
|
tickLabelFontSize,
|
|
49
51
|
axisLabelFontSize,
|
|
50
52
|
handleLeftTickFormatting,
|
|
@@ -71,6 +73,7 @@ const LeftAxis: React.FC<LeftAxisProps> = ({
|
|
|
71
73
|
stroke='#333'
|
|
72
74
|
tickFormat={handleLeftTickFormatting}
|
|
73
75
|
numTicks={numTicks}
|
|
76
|
+
{...(tickValues ? { tickValues } : {})}
|
|
74
77
|
>
|
|
75
78
|
{props => {
|
|
76
79
|
const axisCenter =
|
|
@@ -10,6 +10,7 @@ interface LeftAxisGridlinesProps {
|
|
|
10
10
|
xMax: number
|
|
11
11
|
yAxisWidth: number
|
|
12
12
|
numTicks: number
|
|
13
|
+
tickValues?: number[]
|
|
13
14
|
axisLabelFontSize: number
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -18,6 +19,7 @@ const LeftAxisGridlines: React.FC<LeftAxisGridlinesProps> = ({
|
|
|
18
19
|
xMax,
|
|
19
20
|
yAxisWidth,
|
|
20
21
|
numTicks,
|
|
22
|
+
tickValues,
|
|
21
23
|
axisLabelFontSize
|
|
22
24
|
}) => {
|
|
23
25
|
const { config } = useContext(ConfigContext)
|
|
@@ -27,7 +29,12 @@ const LeftAxisGridlines: React.FC<LeftAxisGridlinesProps> = ({
|
|
|
27
29
|
const showSideTitle = config.yAxis.titlePlacement !== 'top'
|
|
28
30
|
|
|
29
31
|
return (
|
|
30
|
-
<AxisLeft
|
|
32
|
+
<AxisLeft
|
|
33
|
+
scale={yScale}
|
|
34
|
+
left={yAxisWidth - config.yAxis.axisPadding}
|
|
35
|
+
numTicks={numTicks}
|
|
36
|
+
{...(tickValues ? { tickValues } : {})}
|
|
37
|
+
>
|
|
31
38
|
{props => {
|
|
32
39
|
const axisCenter =
|
|
33
40
|
config.orientation === 'horizontal'
|
|
@@ -15,6 +15,7 @@ type RightAxisProps = {
|
|
|
15
15
|
yMax: number
|
|
16
16
|
xMax: number
|
|
17
17
|
yAxisWidth: number
|
|
18
|
+
tickValues?: number[]
|
|
18
19
|
tickLabelFontSize: number
|
|
19
20
|
axisLabelFontSize: number
|
|
20
21
|
}
|
|
@@ -29,6 +30,7 @@ export const RightAxis: React.FC<RightAxisProps> = ({
|
|
|
29
30
|
yMax,
|
|
30
31
|
xMax,
|
|
31
32
|
yAxisWidth,
|
|
33
|
+
tickValues,
|
|
32
34
|
tickLabelFontSize,
|
|
33
35
|
axisLabelFontSize
|
|
34
36
|
}) => {
|
|
@@ -45,6 +47,7 @@ export const RightAxis: React.FC<RightAxisProps> = ({
|
|
|
45
47
|
label={config.yAxis.rightLabel}
|
|
46
48
|
tickFormat={tick => formatNumber(tick, 'right')}
|
|
47
49
|
numTicks={runtime.yAxis.rightNumTicks || undefined}
|
|
50
|
+
{...(tickValues ? { tickValues } : {})}
|
|
48
51
|
labelOffset={45}
|
|
49
52
|
>
|
|
50
53
|
{props => {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
4
|
+
import ConfigContext from '../../../ConfigContext'
|
|
5
|
+
import { createMockChartContext, createMockConfig } from '../../LinearChart/tests/mockConfigContext'
|
|
6
|
+
|
|
7
|
+
vi.mock('@visx/axis', () => ({
|
|
8
|
+
AxisLeft: ({ tickValues }: { tickValues?: number[] }) => (
|
|
9
|
+
<div data-testid='axis-left' data-tick-values={JSON.stringify(tickValues ?? null)} />
|
|
10
|
+
),
|
|
11
|
+
AxisRight: ({ tickValues }: { tickValues?: number[] }) => (
|
|
12
|
+
<div data-testid='axis-right' data-tick-values={JSON.stringify(tickValues ?? null)} />
|
|
13
|
+
)
|
|
14
|
+
}))
|
|
15
|
+
|
|
16
|
+
import LeftAxis from '../LeftAxis'
|
|
17
|
+
import LeftAxisGridlines from '../LeftAxisGridlines'
|
|
18
|
+
import RightAxis from '../RightAxis'
|
|
19
|
+
|
|
20
|
+
const tickValues = [0, 5, 10, 15]
|
|
21
|
+
|
|
22
|
+
const renderWithContext = (children: React.ReactNode) => {
|
|
23
|
+
const config = createMockConfig({
|
|
24
|
+
runtime: {
|
|
25
|
+
...createMockConfig().runtime,
|
|
26
|
+
yAxis: {
|
|
27
|
+
...createMockConfig().runtime.yAxis,
|
|
28
|
+
rightNumTicks: 4
|
|
29
|
+
}
|
|
30
|
+
} as any
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return render(<ConfigContext.Provider value={createMockChartContext(config)}>{children}</ConfigContext.Provider>)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('axis tick values', () => {
|
|
37
|
+
it('passes supplied tick values to the left axis', () => {
|
|
38
|
+
renderWithContext(
|
|
39
|
+
<LeftAxis
|
|
40
|
+
yScale={{}}
|
|
41
|
+
xScale={{}}
|
|
42
|
+
yMax={100}
|
|
43
|
+
xMax={300}
|
|
44
|
+
yAxisWidth={50}
|
|
45
|
+
numTicks={4}
|
|
46
|
+
tickValues={tickValues}
|
|
47
|
+
tickLabelFontSize={16}
|
|
48
|
+
axisLabelFontSize={16}
|
|
49
|
+
handleLeftTickFormatting={tick => String(tick)}
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
expect(JSON.parse(screen.getByTestId('axis-left').dataset.tickValues || 'null')).toEqual(tickValues)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('passes supplied tick values to left-axis gridlines', () => {
|
|
57
|
+
renderWithContext(
|
|
58
|
+
<LeftAxisGridlines
|
|
59
|
+
yScale={{}}
|
|
60
|
+
xMax={300}
|
|
61
|
+
yAxisWidth={50}
|
|
62
|
+
numTicks={4}
|
|
63
|
+
tickValues={tickValues}
|
|
64
|
+
axisLabelFontSize={16}
|
|
65
|
+
/>
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
expect(JSON.parse(screen.getByTestId('axis-left').dataset.tickValues || 'null')).toEqual(tickValues)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('passes supplied tick values to the right axis', () => {
|
|
72
|
+
renderWithContext(
|
|
73
|
+
<RightAxis
|
|
74
|
+
yScaleRight={{} as any}
|
|
75
|
+
yMax={100}
|
|
76
|
+
xMax={300}
|
|
77
|
+
yAxisWidth={50}
|
|
78
|
+
tickValues={tickValues}
|
|
79
|
+
tickLabelFontSize={16}
|
|
80
|
+
axisLabelFontSize={16}
|
|
81
|
+
/>
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
expect(JSON.parse(screen.getByTestId('axis-right').dataset.tickValues || 'null')).toEqual(tickValues)
|
|
85
|
+
})
|
|
86
|
+
})
|
|
@@ -21,7 +21,6 @@ import isNumber from '@cdc/core/helpers/isNumber'
|
|
|
21
21
|
// Local context and types
|
|
22
22
|
import BarChartContext, { BarChartContextValues } from './context'
|
|
23
23
|
import { ChartContext } from '../../../types/ChartContext'
|
|
24
|
-
import _ from 'lodash'
|
|
25
24
|
import { getBarData } from '../helpers/getBarData'
|
|
26
25
|
import { getHorizontalBarHeights } from '../helpers/getBarHeights'
|
|
27
26
|
import { getPatternUrl as getPatternUrlForBar } from '../helpers/getPatternUrl'
|
|
@@ -76,7 +75,7 @@ const BarChartHorizontal = () => {
|
|
|
76
75
|
<defs>
|
|
77
76
|
{Object.entries(config.legend.patterns).map(([key, pattern]) => {
|
|
78
77
|
const patternId = getChartPatternId(key)
|
|
79
|
-
const size = pattern.patternSize ||
|
|
78
|
+
const size = pattern.patternSize || 10
|
|
80
79
|
|
|
81
80
|
switch (pattern.shape) {
|
|
82
81
|
case 'circles':
|
|
@@ -59,7 +59,7 @@ const BarChartStackedHorizontal = () => {
|
|
|
59
59
|
<defs>
|
|
60
60
|
{Object.entries(config.legend.patterns).map(([key, pattern]) => {
|
|
61
61
|
const patternId = getChartPatternId(key)
|
|
62
|
-
const size = pattern.patternSize ||
|
|
62
|
+
const size = pattern.patternSize || 10
|
|
63
63
|
|
|
64
64
|
switch (pattern.shape) {
|
|
65
65
|
case 'circles':
|
|
@@ -44,7 +44,7 @@ const BarChartStackedVertical = () => {
|
|
|
44
44
|
<defs>
|
|
45
45
|
{Object.entries(config.legend.patterns).map(([key, pattern]) => {
|
|
46
46
|
const patternId = getChartPatternId(key)
|
|
47
|
-
const size = pattern.patternSize ||
|
|
47
|
+
const size = pattern.patternSize || 10
|
|
48
48
|
|
|
49
49
|
switch (pattern.shape) {
|
|
50
50
|
case 'circles':
|
|
@@ -19,7 +19,6 @@ import createBarElement from '@cdc/core/components/createBarElement'
|
|
|
19
19
|
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
20
20
|
// Types
|
|
21
21
|
import { type ChartContext } from '../../../types/ChartContext'
|
|
22
|
-
import _ from 'lodash'
|
|
23
22
|
import { getBarData } from '../helpers/getBarData'
|
|
24
23
|
import { getPatternUrl as getPatternUrlForBar } from '../helpers/getPatternUrl'
|
|
25
24
|
import { getChartPatternId } from '../../../helpers/getChartPatternId'
|
|
@@ -90,7 +89,7 @@ const BarChartVertical = () => {
|
|
|
90
89
|
<defs>
|
|
91
90
|
{Object.entries(config.legend.patterns).map(([key, pattern]) => {
|
|
92
91
|
const patternId = getChartPatternId(key)
|
|
93
|
-
const size = pattern.patternSize ||
|
|
92
|
+
const size = pattern.patternSize || 10
|
|
94
93
|
|
|
95
94
|
switch (pattern.shape) {
|
|
96
95
|
case 'circles':
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { TransformedData } from '../../../types/ChartContext'
|
|
3
|
-
import { ChartConfig } from '../../../types/ChartConfig'
|
|
4
|
-
|
|
5
|
-
export const getBarData = (config: ChartConfig, data: TransformedData[], hasConfidenceInterval: boolean) => {
|
|
6
|
-
const dynamicSeries = config.series.find(s => s.dynamicCategory)
|
|
7
|
-
if (!dynamicSeries) return data
|
|
8
|
-
const { dynamicCategory, dataKey } = dynamicSeries
|
|
9
|
-
const xAxisKey = config.runtime.originalXAxis.dataKey
|
|
10
|
-
const xAxisGroupDataLookup =
|
|
11
|
-
return Object.values(xAxisGroupDataLookup).map(group => {
|
|
12
|
-
return group.reduce((acc, datum) => {
|
|
13
|
-
const dataValue = datum[dataKey]
|
|
14
|
-
const dataCategory = datum[dynamicCategory]
|
|
15
|
-
if (hasConfidenceInterval) {
|
|
16
|
-
const { lower, upper } = config.confidenceKeys
|
|
17
|
-
if (!acc.CI) acc.CI = {}
|
|
18
|
-
const lowerValue = datum[lower]
|
|
19
|
-
const upperValue = datum[upper]
|
|
20
|
-
acc.CI[dataCategory] = { lower: lowerValue, upper: upperValue }
|
|
21
|
-
}
|
|
22
|
-
acc[dataCategory] = dataValue
|
|
23
|
-
acc[xAxisKey] = datum[xAxisKey]
|
|
24
|
-
acc.dynamicData = true
|
|
25
|
-
return acc
|
|
26
|
-
}, {})
|
|
27
|
-
})
|
|
28
|
-
}
|
|
1
|
+
import groupBy from 'lodash/groupBy'
|
|
2
|
+
import { TransformedData } from '../../../types/ChartContext'
|
|
3
|
+
import { ChartConfig } from '../../../types/ChartConfig'
|
|
4
|
+
|
|
5
|
+
export const getBarData = (config: ChartConfig, data: TransformedData[], hasConfidenceInterval: boolean) => {
|
|
6
|
+
const dynamicSeries = config.series.find(s => s.dynamicCategory)
|
|
7
|
+
if (!dynamicSeries) return data
|
|
8
|
+
const { dynamicCategory, dataKey } = dynamicSeries
|
|
9
|
+
const xAxisKey = config.runtime.originalXAxis.dataKey
|
|
10
|
+
const xAxisGroupDataLookup = groupBy(data, xAxisKey)
|
|
11
|
+
return Object.values(xAxisGroupDataLookup).map(group => {
|
|
12
|
+
return group.reduce((acc, datum) => {
|
|
13
|
+
const dataValue = datum[dataKey]
|
|
14
|
+
const dataCategory = datum[dynamicCategory]
|
|
15
|
+
if (hasConfidenceInterval) {
|
|
16
|
+
const { lower, upper } = config.confidenceKeys
|
|
17
|
+
if (!acc.CI) acc.CI = {}
|
|
18
|
+
const lowerValue = datum[lower]
|
|
19
|
+
const upperValue = datum[upper]
|
|
20
|
+
acc.CI[dataCategory] = { lower: lowerValue, upper: upperValue }
|
|
21
|
+
}
|
|
22
|
+
acc[dataCategory] = dataValue
|
|
23
|
+
acc[xAxisKey] = datum[xAxisKey]
|
|
24
|
+
acc.dynamicData = true
|
|
25
|
+
return acc
|
|
26
|
+
}, {})
|
|
27
|
+
})
|
|
28
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
|
|
2
|
-
import
|
|
2
|
+
import round from 'lodash/round'
|
|
3
3
|
import { ChartConfig } from '../../../types/ChartConfig'
|
|
4
4
|
|
|
5
5
|
export const getHorizontalBarHeights = <T>(config: Partial<ChartConfig>, bars: (T & { index })[]) => {
|
|
@@ -41,7 +41,7 @@ export const getHorizontalBarHeights = <T>(config: Partial<ChartConfig>, bars: (
|
|
|
41
41
|
// return new updated bars/groupes
|
|
42
42
|
return bars.map((bar, i) => {
|
|
43
43
|
// set bars Y dynamically to handle space between bars
|
|
44
|
-
const y = bar.index === 0 ? 0 :
|
|
44
|
+
const y = bar.index === 0 ? 0 : round(totalBarHeight * i)
|
|
45
45
|
return { ...bar, y, height: barHeight }
|
|
46
46
|
})
|
|
47
47
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { createMockChartContext, createMockConfig } from '../../../LinearChart/tests/mockConfigContext'
|
|
4
|
+
import { useBarChart } from '../useBarChart'
|
|
5
|
+
|
|
6
|
+
const baseConfig = createMockConfig()
|
|
7
|
+
|
|
8
|
+
const makeConfigContext = (countColumnOverrides: Record<string, unknown> = {}) => {
|
|
9
|
+
const config = createMockConfig({
|
|
10
|
+
visualizationType: 'Bar',
|
|
11
|
+
orientation: 'horizontal',
|
|
12
|
+
barHeight: 25,
|
|
13
|
+
xAxis: {
|
|
14
|
+
...baseConfig.xAxis,
|
|
15
|
+
type: 'categorical',
|
|
16
|
+
dataKey: 'Cause of death'
|
|
17
|
+
},
|
|
18
|
+
yAxis: {
|
|
19
|
+
...baseConfig.yAxis,
|
|
20
|
+
labelPlacement: 'Below Bar'
|
|
21
|
+
},
|
|
22
|
+
series: [{ dataKey: 'Percentage', type: 'Bar', axis: 'Left', tooltip: true }] as any,
|
|
23
|
+
columns: {
|
|
24
|
+
Count: {
|
|
25
|
+
name: 'Count',
|
|
26
|
+
label: 'Count',
|
|
27
|
+
prefix: '',
|
|
28
|
+
suffix: '',
|
|
29
|
+
roundToPlace: 0,
|
|
30
|
+
commas: true,
|
|
31
|
+
dataTable: true,
|
|
32
|
+
tooltips: true,
|
|
33
|
+
...countColumnOverrides
|
|
34
|
+
}
|
|
35
|
+
} as any,
|
|
36
|
+
dataFormat: {
|
|
37
|
+
abbreviated: false,
|
|
38
|
+
bottomAbbreviated: false,
|
|
39
|
+
bottomPrefix: '',
|
|
40
|
+
bottomRoundTo: 0,
|
|
41
|
+
bottomSuffix: '',
|
|
42
|
+
bottomCommas: false,
|
|
43
|
+
commas: false,
|
|
44
|
+
prefix: '',
|
|
45
|
+
preserveOriginalDecimals: false,
|
|
46
|
+
rightPrefix: '',
|
|
47
|
+
rightRoundTo: 0,
|
|
48
|
+
rightSuffix: '',
|
|
49
|
+
roundTo: 1,
|
|
50
|
+
suffix: '%'
|
|
51
|
+
} as any,
|
|
52
|
+
runtime: {
|
|
53
|
+
...baseConfig.runtime,
|
|
54
|
+
xAxis: {
|
|
55
|
+
...baseConfig.runtime.xAxis,
|
|
56
|
+
type: 'categorical',
|
|
57
|
+
dataKey: 'Cause of death'
|
|
58
|
+
},
|
|
59
|
+
yAxis: {
|
|
60
|
+
...baseConfig.runtime.yAxis,
|
|
61
|
+
dataKey: 'Percentage'
|
|
62
|
+
},
|
|
63
|
+
series: [{ dataKey: 'Percentage', type: 'Bar', axis: 'Left', tooltip: true }],
|
|
64
|
+
seriesKeys: ['Percentage'],
|
|
65
|
+
seriesLabels: { Percentage: 'Percentage' },
|
|
66
|
+
seriesLabelsAll: ['Percentage']
|
|
67
|
+
} as any
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
return createMockChartContext(config, {
|
|
71
|
+
tableData: [
|
|
72
|
+
{
|
|
73
|
+
'Cause of death': 'Cardiovascular conditions',
|
|
74
|
+
Percentage: 22,
|
|
75
|
+
Count: 146
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
updateConfig: vi.fn()
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
describe('useBarChart', () => {
|
|
83
|
+
it('does not apply the left-axis suffix to an additional single-series tooltip column', () => {
|
|
84
|
+
const configContext = makeConfigContext()
|
|
85
|
+
const { result } = renderHook(() => useBarChart(vi.fn(), vi.fn(), configContext))
|
|
86
|
+
|
|
87
|
+
expect(result.current.getAdditionalColumn('Percentage', 'Cardiovascular conditions')).toBe('Count : 146 <br/>')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('applies an additional column suffix when one is configured', () => {
|
|
91
|
+
const configContext = makeConfigContext({ suffix: ' deaths' })
|
|
92
|
+
const { result } = renderHook(() => useBarChart(vi.fn(), vi.fn(), configContext))
|
|
93
|
+
|
|
94
|
+
expect(result.current.getAdditionalColumn('Percentage', 'Cardiovascular conditions')).toBe(
|
|
95
|
+
'Count : 146 deaths <br/>'
|
|
96
|
+
)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
@@ -6,7 +6,7 @@ import { getPaletteColors } from '@cdc/core/helpers/palettes/utils'
|
|
|
6
6
|
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
7
7
|
import { getVizSubType, getVizTitle } from '@cdc/core/helpers/metrics/utils'
|
|
8
8
|
import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
9
|
-
import { getSeriesOwnedColumnNames } from '../../../helpers/seriesColumnSettings'
|
|
9
|
+
import { getSeriesColumnFormattingParams, getSeriesOwnedColumnNames } from '../../../helpers/seriesColumnSettings'
|
|
10
10
|
|
|
11
11
|
export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, configContext) => {
|
|
12
12
|
const {
|
|
@@ -184,10 +184,9 @@ export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, confi
|
|
|
184
184
|
if (seriesOwnedColumnNames.includes(colConfig.name || colKeys)) return
|
|
185
185
|
if (series && colConfig.series && colConfig.series !== series && !colConfig.tooltips) return
|
|
186
186
|
const formattingParams = {
|
|
187
|
-
addColPrefix:
|
|
188
|
-
addColSuffix:
|
|
189
|
-
|
|
190
|
-
addColCommas: config.columns[colKeys].commas
|
|
187
|
+
addColPrefix: '',
|
|
188
|
+
addColSuffix: '',
|
|
189
|
+
...getSeriesColumnFormattingParams(config.columns[colKeys])
|
|
191
190
|
}
|
|
192
191
|
|
|
193
192
|
const formattedValue = formatColNumber(
|
|
@@ -5,7 +5,8 @@ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
|
5
5
|
import ConfigContext from '../../ConfigContext'
|
|
6
6
|
import { handleTooltip, createPlots } from './helpers/index'
|
|
7
7
|
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
8
|
-
import
|
|
8
|
+
import map from 'lodash/map'
|
|
9
|
+
import round from 'lodash/round'
|
|
9
10
|
|
|
10
11
|
const BoxPlotHorizontal = ({ xScale, yScale, seriesScale }) => {
|
|
11
12
|
const { config, transformedData: data, colorScale, seriesHighlight } = useContext(ConfigContext)
|
|
@@ -63,7 +64,7 @@ const BoxPlotHorizontal = ({ xScale, yScale, seriesScale }) => {
|
|
|
63
64
|
maxProps={{ stroke: defaultColor, strokeWidth: 1, opacity: fillOpacity }}
|
|
64
65
|
medianProps={{ stroke: defaultColor, strokeWidth: 1, opacity: fillOpacity }}
|
|
65
66
|
outliers={
|
|
66
|
-
config.boxplot.plotOutlierValues ?
|
|
67
|
+
config.boxplot.plotOutlierValues ? map(plot.columnOutliers[item.dataKey], item => item) : []
|
|
67
68
|
}
|
|
68
69
|
outlierProps={{
|
|
69
70
|
style: {
|
|
@@ -78,10 +79,10 @@ const BoxPlotHorizontal = ({ xScale, yScale, seriesScale }) => {
|
|
|
78
79
|
config.boxplot,
|
|
79
80
|
plot.columnCategory,
|
|
80
81
|
item.dataKey,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
round(plot.q1[item.dataKey], config.dataFormat.roundTo),
|
|
83
|
+
round(plot.q3[item.dataKey], config.dataFormat.roundTo),
|
|
84
|
+
round(plot.median[item.dataKey], config.dataFormat.roundTo),
|
|
85
|
+
round(plot.iqr[item.dataKey], config.dataFormat.roundTo),
|
|
85
86
|
config.xAxis.label,
|
|
86
87
|
defaultColor
|
|
87
88
|
),
|
|
@@ -7,7 +7,9 @@ import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
|
|
|
7
7
|
import { getCurrentPaletteName } from '@cdc/core/helpers/palettes/utils'
|
|
8
8
|
import { handleTooltip, createPlots } from './helpers/index'
|
|
9
9
|
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
10
|
-
import
|
|
10
|
+
import get from 'lodash/get'
|
|
11
|
+
import map from 'lodash/map'
|
|
12
|
+
import round from 'lodash/round'
|
|
11
13
|
|
|
12
14
|
const BoxPlotVertical = ({ xScale, yScale, seriesScale, yAxisWidth }) => {
|
|
13
15
|
const { config, colorScale, seriesHighlight, transformedData: data } = useContext(ConfigContext)
|
|
@@ -19,7 +21,7 @@ const BoxPlotVertical = ({ xScale, yScale, seriesScale, yAxisWidth }) => {
|
|
|
19
21
|
const bodyStyles = getComputedStyle(document.body)
|
|
20
22
|
const defaultColor = APP_FONT_COLOR
|
|
21
23
|
const constrainedWidth = Math.min(40, boxWidth)
|
|
22
|
-
const color_0 =
|
|
24
|
+
const color_0 = get(colorPalettesChart, [getCurrentPaletteName(config), 0], '#000')
|
|
23
25
|
const plots = createPlots(data, config)
|
|
24
26
|
return (
|
|
25
27
|
<ErrorBoundary component='BoxPlot Vertical'>
|
|
@@ -76,7 +78,7 @@ const BoxPlotVertical = ({ xScale, yScale, seriesScale, yAxisWidth }) => {
|
|
|
76
78
|
fillOpacity={1}
|
|
77
79
|
stroke={defaultColor}
|
|
78
80
|
valueScale={yScale}
|
|
79
|
-
outliers={boxplot.plotOutlierValues ?
|
|
81
|
+
outliers={boxplot.plotOutlierValues ? map(d.columnOutliers[item.dataKey], item => item) : []}
|
|
80
82
|
outlierProps={{
|
|
81
83
|
style: {
|
|
82
84
|
fill: defaultColor,
|
|
@@ -109,10 +111,10 @@ const BoxPlotVertical = ({ xScale, yScale, seriesScale, yAxisWidth }) => {
|
|
|
109
111
|
boxplot,
|
|
110
112
|
d.columnCategory,
|
|
111
113
|
item.dataKey,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
round(d.q1[item.dataKey], config.dataFormat.roundTo),
|
|
115
|
+
round(d.q3[item.dataKey], config.dataFormat.roundTo),
|
|
116
|
+
round(d.median[item.dataKey], config.dataFormat.roundTo),
|
|
117
|
+
round(d.iqr[item.dataKey], config.dataFormat.roundTo),
|
|
116
118
|
config.xAxis.label,
|
|
117
119
|
defaultColor
|
|
118
120
|
),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import sortBy from 'lodash/sortBy'
|
|
2
|
+
import uniq from 'lodash/uniq'
|
|
2
3
|
import * as d3 from 'd3-array'
|
|
3
4
|
|
|
4
5
|
interface Plot {
|
|
@@ -33,7 +34,7 @@ const calculateBoxPlotStats = (values: number[]) => {
|
|
|
33
34
|
if (!values || values.length === 0) return {}
|
|
34
35
|
|
|
35
36
|
// Sort the values
|
|
36
|
-
const sortedValues =
|
|
37
|
+
const sortedValues = sortBy(values.map(v => Number(v)))
|
|
37
38
|
|
|
38
39
|
// Quartiles
|
|
39
40
|
const firstQuartile = d3.quantile(sortedValues, 0.25) ?? 0
|
|
@@ -103,7 +104,7 @@ const calculateNonOutliers = (values: number[], firstQuartile: number, thirdQuar
|
|
|
103
104
|
export const createPlots = (data, config) => {
|
|
104
105
|
const dataKeys = data.map(d => d[config.xAxis.dataKey])
|
|
105
106
|
const plots: Plot[] = []
|
|
106
|
-
const groups: string[] =
|
|
107
|
+
const groups: string[] = uniq(dataKeys)
|
|
107
108
|
|
|
108
109
|
if (groups && groups.length > 0) {
|
|
109
110
|
groups.forEach(group => {
|
|
@@ -128,7 +128,7 @@ const BrushSelector: FC<BrushSelectorProps> = ({ xMax, yMax }) => {
|
|
|
128
128
|
<>
|
|
129
129
|
{Object.entries(config.legend.patterns).map(([key, pattern]) => {
|
|
130
130
|
const patternId = getChartPatternId(key)
|
|
131
|
-
const size = pattern.patternSize ||
|
|
131
|
+
const size = pattern.patternSize || 10
|
|
132
132
|
|
|
133
133
|
switch (pattern.shape) {
|
|
134
134
|
case 'circles':
|