@cdc/chart 4.26.3 → 4.26.5
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 +489 -0
- package/README.md +42 -17
- package/dist/cdcchart.js +39024 -36587
- package/examples/feature/bar/additional-column-tooltip.json +3 -3
- package/examples/feature/deviation/planet-deviation-config.json +2 -2
- package/examples/feature/heatmap/average-age-categorical.json +313 -0
- package/examples/feature/heatmap/calendar-heatmap.json +134 -0
- package/examples/feature/heatmap/cell-values-bottom-axis.json +181 -0
- package/examples/feature/heatmap/sparse-aggregation.json +158 -0
- package/examples/feature/paired-bar/paired-bar-example.json +2 -2
- package/examples/feature/tests-case-rate/case-rate-example-config.json +2 -2
- package/examples/feature/tests-covid/covid-confidence-example-config.json +2 -2
- package/examples/feature/tests-covid/covid-example-config.json +2 -2
- package/examples/feature/tests-cutoff/cutoff-example-config.json +2 -2
- package/examples/feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json +2 -2
- package/examples/metadata-variables.json +1 -1
- package/examples/minimal-example.json +24 -0
- package/examples/private/bump-chart.json +1116 -0
- package/examples/private/test-123.json +624 -0
- package/examples/private/test2.json +35012 -0
- package/package.json +5 -4
- package/src/CdcChartComponent.tsx +144 -60
- package/src/_stories/Chart.AltText.stories.tsx +136 -0
- package/src/_stories/{Chart.HTMLInDataTable.stories.tsx → Chart.HTMLInDataTable.smoke.stories.tsx} +10 -0
- package/src/_stories/Chart.HeatMap.stories.tsx +187 -0
- package/src/_stories/Chart.Minimal.smoke.stories.tsx +25 -0
- package/src/_stories/Chart.Sankey.smoke.stories.tsx +62 -0
- package/src/_stories/Chart.SmallMultiples.smoke.stories.tsx +108 -0
- package/src/_stories/{Chart.stories.tsx → Chart.smoke.stories.tsx} +12 -0
- package/src/_stories/{Chart.tooltip.stories.tsx → Chart.tooltip.smoke.stories.tsx} +68 -0
- package/src/_stories/{ChartAxisTitles.stories.tsx → ChartAxisTitles.smoke.stories.tsx} +13 -0
- package/src/_stories/ChartBar.Editor.stories.tsx +151 -85
- package/src/_stories/ChartBrush.Interaction.stories.tsx +112 -0
- package/src/_stories/{ChartBrush.stories.tsx → ChartBrush.smoke.stories.tsx} +13 -4
- package/src/_stories/ChartEditor.Editor.stories.tsx +23 -0
- package/src/_stories/ChartLine.DashedDates.smoke.stories.tsx +23 -0
- package/src/_stories/_mock/alt_text_metadata.json +38 -0
- package/src/_stories/_mock/heatmap-average-age-categorical.json +315 -0
- package/src/_stories/_mock/heatmap.json +156 -0
- package/src/_stories/_mock/line_chart_dashed_dates.json +276 -0
- package/src/_stories/_mock/paired-bar-abbr.json +1 -1
- package/src/_stories/_mock/paired-bar.json +1 -1
- package/src/_stories/_mock/reported_pertussis_cases.json +1643 -0
- package/src/_stories/_mock/sankey_mock.json +78 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines.json +1 -1
- package/src/components/Annotations/components/AnnotationDraggable.tsx +2 -2
- package/src/components/Annotations/components/AnnotationDropdown.tsx +5 -2
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -2
- package/src/components/Axis/BottomAxis.tsx +1 -1
- package/src/components/Axis/LeftAxis.tsx +15 -14
- package/src/components/Axis/LeftAxisGridlines.tsx +14 -13
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +9 -5
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +9 -5
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +9 -5
- package/src/components/BarChart/components/BarChart.Vertical.tsx +9 -6
- package/src/components/BarChart/components/BarChart.tsx +2 -6
- package/src/components/BarChart/helpers/useBarChart.ts +1 -1
- package/src/components/BoxPlot/BoxPlot.Vertical.tsx +3 -3
- package/src/components/Brush/BrushSelector.tsx +88 -187
- package/src/components/Brush/brushInteraction.ts +22 -0
- package/src/components/Brush/tests/brushInteraction.test.ts +38 -0
- package/src/components/EditorPanel/EditorPanel.tsx +946 -960
- package/src/components/EditorPanel/EditorPanelContext.ts +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +229 -165
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +105 -13
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +169 -116
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +159 -109
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +14 -28
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -1
- package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +57 -0
- package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +56 -0
- package/src/components/EditorPanel/useEditorPermissions.test.tsx +70 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +32 -21
- package/src/components/Forecasting/Forecasting.tsx +2 -2
- package/src/components/HeatMap/components/HeatMap.tsx +762 -0
- package/src/components/HeatMap/components/HeatMapGradientLegend.tsx +114 -0
- package/src/components/HeatMap/heatmap.constants.ts +13 -0
- package/src/components/HeatMap/heatmap.css +71 -0
- package/src/components/HeatMap/helpers/index.ts +261 -0
- package/src/components/HeatMap/index.tsx +5 -0
- package/src/components/HeatMap/tests/HeatMap.test.tsx +1040 -0
- package/src/components/HeatMap/tests/helpers.test.ts +167 -0
- package/src/components/HorizonChart/HorizonChart.tsx +10 -2
- package/src/components/HoverLine/HoverLine.tsx +6 -8
- package/src/components/Legend/Legend.Component.tsx +4 -4
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +5 -0
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +3 -1
- package/src/components/Legend/helpers/index.ts +0 -2
- package/src/components/LineChart/LineChartProps.ts +1 -0
- package/src/components/LineChart/components/LineChart.BumpCircle.tsx +22 -24
- package/src/components/LineChart/helpers.test.ts +345 -0
- package/src/components/LineChart/helpers.ts +15 -5
- package/src/components/LineChart/index.tsx +18 -5
- package/src/components/LinearChart/VisualizationRenderer.tsx +11 -1
- package/src/components/LinearChart/tests/LinearChart.test.tsx +228 -7
- package/src/components/LinearChart/tests/mockConfigContext.ts +1 -0
- package/src/components/LinearChart.tsx +155 -77
- package/src/components/PairedBarChart.jsx +2 -2
- package/src/components/PieChart/PieChart.tsx +15 -10
- package/src/components/RadarChart/RadarChart.tsx +3 -1
- package/src/components/Regions/components/Regions.tsx +199 -35
- package/src/components/Sankey/components/Sankey.tsx +7 -3
- package/src/components/Sankey/helpers/getSankeyTooltip.tsx +7 -6
- package/src/components/ScatterPlot/ScatterPlot.jsx +19 -17
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +31 -3
- package/src/components/SmallMultiples/SmallMultiples.css +12 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +15 -9
- package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +152 -0
- package/src/components/Sparkline/components/SparkLine.tsx +5 -10
- package/src/components/WarmingStripes/WarmingStripes.tsx +5 -3
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +1 -1
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +2 -2
- package/src/data/initial-state.js +12 -0
- package/src/helpers/axisLabelFontSize.ts +8 -0
- package/src/helpers/bumpChartTooltip.ts +54 -0
- package/src/helpers/calculateLeftYAxisWidth.ts +197 -0
- package/src/helpers/ensureSpecialChartAxisTypes.ts +11 -0
- package/src/helpers/getMinMax.ts +2 -3
- package/src/helpers/getYAxisAutoPadding.ts +8 -6
- package/src/helpers/getYAxisAutoPaddingMode.ts +17 -0
- package/src/helpers/handleChartAriaLabels.ts +23 -10
- package/src/helpers/seriesHighlight.ts +26 -0
- package/src/helpers/tests/axisLabelFontSize.test.ts +15 -0
- package/src/helpers/tests/bumpChartTooltip.test.ts +75 -0
- package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +30 -0
- package/src/helpers/tests/getYAxisAutoPadding.test.ts +37 -0
- package/src/helpers/tests/getYAxisAutoPaddingMode.test.ts +90 -0
- package/src/helpers/tests/handleChartAriaLabels.test.ts +71 -0
- package/src/helpers/tests/seriesHighlight.test.ts +61 -0
- package/src/helpers/tests/tooltipHelpers.test.ts +151 -0
- package/src/helpers/tooltipHelpers.ts +176 -0
- package/src/hooks/tests/useScales.test.tsx +105 -0
- package/src/hooks/useHighlightedBars.ts +9 -16
- package/src/hooks/useProgrammaticTooltip.ts +16 -2
- package/src/hooks/useScales.ts +19 -9
- package/src/hooks/useSmallMultipleSynchronization.ts +3 -4
- package/src/hooks/useTooltip.tsx +167 -38
- package/src/index.jsx +5 -2
- package/src/scss/main.scss +32 -49
- package/src/test/CdcChart.test.jsx +24 -0
- package/src/types/ChartConfig.ts +19 -0
- package/src/types/ChartContext.ts +2 -0
- package/examples/chart-regression-2.json +0 -2360
- package/examples/feature/__data__/age-adjusted-rates.json +0 -1486
- package/examples/feature/__data__/city-temperature.json +0 -2198
- package/examples/feature/bar/Barchart_with_negative.json +0 -34
- package/examples/feature/boxplot/box-plot-data.json +0 -71
- package/examples/feature/boxplot/boxplot-data.json +0 -156
- package/examples/feature/combo/combochart-categories_are_numbers .json +0 -18
- package/examples/feature/forest-plot/log.json +0 -26
- package/examples/feature/tests-date-exclusions/date-exclusions-data.json +0 -162
- package/examples/region-issue.json +0 -2065
- package/examples/xaxis.json +0 -493
- package/src/_stories/Chart.SmallMultiples.stories.tsx +0 -63
- /package/examples/{data → __data__}/data-with-metadata.json +0 -0
- /package/examples/feature/bar/{tall-data.json → __data__/tall-data.json} +0 -0
- /package/examples/feature/deviation/{planet-deviation-data.json → __data__/planet-deviation-data.json} +0 -0
- /package/examples/feature/paired-bar/{paired-bar-data.json → __data__/paired-bar-data.json} +0 -0
- /package/examples/feature/paired-bar/{paired-bar-formatted.json → __data__/paired-bar-formatted.json} +0 -0
- /package/examples/feature/tests-case-rate/{case-rate-example-data.json → __data__/case-rate-example-data.json} +0 -0
- /package/examples/feature/tests-covid/{covid-example-data-confidence.json → __data__/covid-example-data-confidence.json} +0 -0
- /package/examples/feature/tests-covid/{covid-example-data.json → __data__/covid-example-data.json} +0 -0
- /package/examples/feature/tests-cutoff/{cutoff-example-data.json → __data__/cutoff-example-data.json} +0 -0
- /package/examples/feature/tests-non-numerics/{planet-example-data-nonnumeric.json → __data__/planet-example-data-nonnumeric.json} +0 -0
- /package/src/_stories/{Chart.Anchors.stories.tsx → Chart.Anchors.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.BoxPlot.stories.tsx → Chart.BoxPlot.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.CI.stories.tsx → Chart.CI.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Combo.stories.tsx → Chart.Combo.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.CustomColors.stories.tsx → Chart.CustomColors.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Defaults.stories.tsx → Chart.Defaults.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.DynamicSeries.stories.tsx → Chart.DynamicSeries.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Filters.stories.tsx → Chart.Filters.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Forecast.stories.tsx → Chart.Forecast.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Legend.Gradient.stories.tsx → Chart.Legend.Gradient.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Patterns.stories.tsx → Chart.Patterns.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.PreserveDecimals.stories.tsx → Chart.PreserveDecimals.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Regions.Categorical.stories.tsx → Chart.Regions.Categorical.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Regions.DateScale.stories.tsx → Chart.Regions.DateScale.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Regions.DateTimeScale.stories.tsx → Chart.Regions.DateTimeScale.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.ScatterPlot.stories.tsx → Chart.ScatterPlot.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.SmallestLeftAxisMax.stories.tsx → Chart.SmallestLeftAxisMax.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartAnnotation.stories.tsx → ChartAnnotation.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartAxisLabels.stories.tsx → ChartAxisLabels.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartBrush.Matrix.Continuous.stories.tsx → ChartBrush.Matrix.Continuous.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartBrush.Matrix.Date.stories.tsx → ChartBrush.Matrix.Date.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartBrush.Matrix.DateTime.stories.tsx → ChartBrush.Matrix.DateTime.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartLine.QuadrantAngles.stories.tsx → ChartLine.QuadrantAngles.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartLine.Suppression.stories.tsx → ChartLine.Suppression.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartLine.Symbols.stories.tsx → ChartLine.Symbols.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartPrefixSuffix.stories.tsx → ChartPrefixSuffix.smoke.stories.tsx} +0 -0
- /package/src/_stories/{TechAdoptionWithLinks.stories.tsx → TechAdoptionWithLinks.smoke.stories.tsx} +0 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
4
|
+
import ConfigContext from '../../../ConfigContext'
|
|
5
|
+
import SmallMultipleTile from '../SmallMultipleTile'
|
|
6
|
+
import { createMockChartContext, createMockConfig } from '../../LinearChart/tests/mockConfigContext'
|
|
7
|
+
|
|
8
|
+
const linearChartConfigs = vi.hoisted(() => [] as any[])
|
|
9
|
+
|
|
10
|
+
vi.stubGlobal(
|
|
11
|
+
'ResizeObserver',
|
|
12
|
+
vi.fn(callback => ({
|
|
13
|
+
observe: vi.fn(() => callback([{ contentRect: { width: 200, height: 300 } }])),
|
|
14
|
+
unobserve: vi.fn(),
|
|
15
|
+
disconnect: vi.fn()
|
|
16
|
+
}))
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
vi.mock('@visx/responsive/lib/components/ParentSize', () => ({
|
|
20
|
+
default: ({ children }) => children({ width: 200, height: 300 })
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
vi.mock('../../LinearChart', async () => {
|
|
24
|
+
const React = await vi.importActual<typeof import('react')>('react')
|
|
25
|
+
const { default: ConfigContext } = await vi.importActual<any>('../../../ConfigContext')
|
|
26
|
+
|
|
27
|
+
const MockLinearChart = () => {
|
|
28
|
+
const { config } = React.useContext(ConfigContext)
|
|
29
|
+
linearChartConfigs.push(config)
|
|
30
|
+
return <div data-testid='mock-linear-chart' />
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
default: MockLinearChart
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('SmallMultipleTile', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
linearChartConfigs.length = 0
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('renders the top y-axis title below the tile title on the first tile in a row and sizes it from tile width', () => {
|
|
44
|
+
const config = createMockConfig({
|
|
45
|
+
smallMultiples: {
|
|
46
|
+
mode: 'by-series',
|
|
47
|
+
tilesPerRowDesktop: 3,
|
|
48
|
+
tilesPerRowMobile: 1
|
|
49
|
+
},
|
|
50
|
+
series: [{ dataKey: 'Value', type: 'Line' }] as any,
|
|
51
|
+
runtime: {
|
|
52
|
+
...createMockConfig().runtime,
|
|
53
|
+
series: [{ dataKey: 'Value', type: 'Line' }] as any,
|
|
54
|
+
seriesKeys: ['Value'],
|
|
55
|
+
seriesLabels: { Value: 'Value' },
|
|
56
|
+
seriesLabelsAll: ['Value']
|
|
57
|
+
} as any
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const context = createMockChartContext(config, {
|
|
61
|
+
vizViewport: 'lg'
|
|
62
|
+
} as any)
|
|
63
|
+
|
|
64
|
+
const { container } = render(
|
|
65
|
+
<ConfigContext.Provider value={context}>
|
|
66
|
+
<SmallMultipleTile mode='by-series' config={config} data={[]} tileKey='Value' seriesKey='Value' isFirstInRow />
|
|
67
|
+
</ConfigContext.Provider>
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const header = container.querySelector('.tile-header')
|
|
71
|
+
const title = screen.getByText('Value')
|
|
72
|
+
const topLabel = container.querySelector('.y-axis-top-title')
|
|
73
|
+
|
|
74
|
+
expect(header).toBeTruthy()
|
|
75
|
+
expect(title).toBeTruthy()
|
|
76
|
+
expect(topLabel).toBeTruthy()
|
|
77
|
+
expect(topLabel).toHaveTextContent('Y-Axis')
|
|
78
|
+
expect(topLabel).toHaveStyle({ fontSize: '14px' })
|
|
79
|
+
expect(header?.firstElementChild).toBe(title)
|
|
80
|
+
expect(header?.lastElementChild).toBe(topLabel)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('does not render the top y-axis title on non-leading tiles in a row', () => {
|
|
84
|
+
const config = createMockConfig({
|
|
85
|
+
smallMultiples: {
|
|
86
|
+
mode: 'by-series',
|
|
87
|
+
tilesPerRowDesktop: 3,
|
|
88
|
+
tilesPerRowMobile: 1
|
|
89
|
+
},
|
|
90
|
+
series: [{ dataKey: 'Value', type: 'Line' }] as any,
|
|
91
|
+
runtime: {
|
|
92
|
+
...createMockConfig().runtime,
|
|
93
|
+
series: [{ dataKey: 'Value', type: 'Line' }] as any,
|
|
94
|
+
seriesKeys: ['Value'],
|
|
95
|
+
seriesLabels: { Value: 'Value' },
|
|
96
|
+
seriesLabelsAll: ['Value']
|
|
97
|
+
} as any
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const context = createMockChartContext(config, {
|
|
101
|
+
vizViewport: 'lg'
|
|
102
|
+
} as any)
|
|
103
|
+
|
|
104
|
+
const { container } = render(
|
|
105
|
+
<ConfigContext.Provider value={context}>
|
|
106
|
+
<SmallMultipleTile
|
|
107
|
+
mode='by-series'
|
|
108
|
+
config={config}
|
|
109
|
+
data={[]}
|
|
110
|
+
tileKey='Value'
|
|
111
|
+
seriesKey='Value'
|
|
112
|
+
isFirstInRow={false}
|
|
113
|
+
/>
|
|
114
|
+
</ConfigContext.Provider>
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
expect(container.querySelector('.y-axis-top-title')).toBeFalsy()
|
|
118
|
+
expect(linearChartConfigs[0].hideYAxisLabel).toBe(false)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('does not render the top y-axis title when the author hides the y-axis label', () => {
|
|
122
|
+
const config = createMockConfig({
|
|
123
|
+
hideYAxisLabel: true,
|
|
124
|
+
smallMultiples: {
|
|
125
|
+
mode: 'by-series',
|
|
126
|
+
tilesPerRowDesktop: 3,
|
|
127
|
+
tilesPerRowMobile: 1
|
|
128
|
+
},
|
|
129
|
+
series: [{ dataKey: 'Value', type: 'Line' }] as any,
|
|
130
|
+
runtime: {
|
|
131
|
+
...createMockConfig().runtime,
|
|
132
|
+
series: [{ dataKey: 'Value', type: 'Line' }] as any,
|
|
133
|
+
seriesKeys: ['Value'],
|
|
134
|
+
seriesLabels: { Value: 'Value' },
|
|
135
|
+
seriesLabelsAll: ['Value']
|
|
136
|
+
} as any
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const context = createMockChartContext(config, {
|
|
140
|
+
vizViewport: 'lg'
|
|
141
|
+
} as any)
|
|
142
|
+
|
|
143
|
+
const { container } = render(
|
|
144
|
+
<ConfigContext.Provider value={context}>
|
|
145
|
+
<SmallMultipleTile mode='by-series' config={config} data={[]} tileKey='Value' seriesKey='Value' isFirstInRow />
|
|
146
|
+
</ConfigContext.Provider>
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
expect(container.querySelector('.y-axis-top-title')).toBeFalsy()
|
|
150
|
+
expect(linearChartConfigs[0].hideYAxisLabel).toBe(true)
|
|
151
|
+
})
|
|
152
|
+
})
|
|
@@ -28,13 +28,16 @@ const SparkLine: React.FC<SparkLineProps> = props => {
|
|
|
28
28
|
colorScale,
|
|
29
29
|
handleChartAriaLabels
|
|
30
30
|
} = useContext(ConfigContext)
|
|
31
|
+
|
|
32
|
+
const a11y = handleChartAriaLabels(config)
|
|
33
|
+
|
|
31
34
|
let width = Number(parentWidth)
|
|
32
35
|
const { minValue, maxValue } = useReduceData(config, data, ConfigContext)
|
|
33
36
|
|
|
34
37
|
const margin = { top: 5, right: 20, bottom: 10, left: 10 }
|
|
35
38
|
const height = Number(parentHeight)
|
|
36
39
|
|
|
37
|
-
const xMax = width -
|
|
40
|
+
const xMax = width - 50
|
|
38
41
|
const yMax = height - margin.top - 20
|
|
39
42
|
|
|
40
43
|
const getXAxisData = d =>
|
|
@@ -109,15 +112,7 @@ const SparkLine: React.FC<SparkLineProps> = props => {
|
|
|
109
112
|
|
|
110
113
|
return (
|
|
111
114
|
<ErrorBoundary component='SparkLine'>
|
|
112
|
-
<svg
|
|
113
|
-
role='img'
|
|
114
|
-
aria-label={handleChartAriaLabels(config)}
|
|
115
|
-
width={parentWidth}
|
|
116
|
-
height={100}
|
|
117
|
-
className={'sparkline'}
|
|
118
|
-
tabIndex={0}
|
|
119
|
-
>
|
|
120
|
-
<title>{`Spark line graphic with the title ${config.title ? config.title : 'No Title Found'}`}</title>
|
|
115
|
+
<svg role='img' aria-label={a11y} width={parentWidth} height={100} className={'sparkline'} tabIndex={0}>
|
|
121
116
|
{config.runtime.lineSeriesKeys?.length > 0
|
|
122
117
|
? config.runtime.lineSeriesKeys
|
|
123
118
|
: config.runtime.seriesKeys.map((seriesKey, index) => (
|
|
@@ -15,6 +15,7 @@ type WarmingStripesProps = {
|
|
|
15
15
|
yScale: any
|
|
16
16
|
xMax: number
|
|
17
17
|
yMax: number
|
|
18
|
+
yAxisWidth: number
|
|
18
19
|
synchronizedXValue?: any
|
|
19
20
|
showTooltip: (args: any) => void
|
|
20
21
|
handleTooltipMouseOff: () => void
|
|
@@ -23,6 +24,7 @@ type WarmingStripesProps = {
|
|
|
23
24
|
const WarmingStripes = ({
|
|
24
25
|
xMax,
|
|
25
26
|
yMax,
|
|
27
|
+
yAxisWidth,
|
|
26
28
|
synchronizedXValue,
|
|
27
29
|
showTooltip,
|
|
28
30
|
handleTooltipMouseOff
|
|
@@ -119,7 +121,7 @@ const WarmingStripes = ({
|
|
|
119
121
|
[valueKey, formattedValue, 'left']
|
|
120
122
|
]
|
|
121
123
|
|
|
122
|
-
const dataXPosition = index * stripeWidth + stripeWidth / 2 +
|
|
124
|
+
const dataXPosition = index * stripeWidth + stripeWidth / 2 + yAxisWidth
|
|
123
125
|
const dataYPosition = mouseY ?? yMax / 2
|
|
124
126
|
|
|
125
127
|
showTooltip({
|
|
@@ -132,7 +134,7 @@ const WarmingStripes = ({
|
|
|
132
134
|
}
|
|
133
135
|
})
|
|
134
136
|
},
|
|
135
|
-
[valueKey, xAxisDataKey, stripeWidth,
|
|
137
|
+
[valueKey, xAxisDataKey, stripeWidth, yAxisWidth, yMax, formatNumber, showTooltip]
|
|
136
138
|
)
|
|
137
139
|
|
|
138
140
|
// Handle incoming synchronized tooltip from sibling small multiple tiles
|
|
@@ -156,7 +158,7 @@ const WarmingStripes = ({
|
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
return (
|
|
159
|
-
<Group className='warming-stripes' left={
|
|
161
|
+
<Group className='warming-stripes' left={yAxisWidth}>
|
|
160
162
|
{displayData.map((item, index) => {
|
|
161
163
|
const value = Number(item[valueKey])
|
|
162
164
|
if (isNaN(value)) return null
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
.warming-stripes-gradient-legend {
|
|
2
|
+
color: var(--baseColor);
|
|
2
3
|
margin: 1rem 0;
|
|
3
4
|
width: 100%;
|
|
4
5
|
}
|
|
@@ -27,7 +28,6 @@
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
.warming-stripes-gradient-legend__series-label {
|
|
30
|
-
color: #333;
|
|
31
31
|
font-size: 14px;
|
|
32
32
|
font-weight: 500;
|
|
33
33
|
margin-top: 0.5rem;
|
|
@@ -84,12 +84,12 @@ const WarmingStripesGradientLegend = () => {
|
|
|
84
84
|
<rect x='1' y='1' width='calc(100% - 2px)' height='23' fill={`url(#${uniqueId})`} />
|
|
85
85
|
|
|
86
86
|
{/* Min label */}
|
|
87
|
-
<text x='0' y='40' fontSize='14' textAnchor='start' fill='
|
|
87
|
+
<text x='0' y='40' fontSize='14' textAnchor='start' fill='currentColor'>
|
|
88
88
|
{formatNumber(minValue, 'left')}
|
|
89
89
|
</text>
|
|
90
90
|
|
|
91
91
|
{/* Max label */}
|
|
92
|
-
<text x='100%' y='40' fontSize='14' textAnchor='end' fill='
|
|
92
|
+
<text x='100%' y='40' fontSize='14' textAnchor='end' fill='currentColor'>
|
|
93
93
|
{formatNumber(maxValue, 'left')}
|
|
94
94
|
</text>
|
|
95
95
|
</svg>
|
|
@@ -75,6 +75,7 @@ const createInitialState = () => {
|
|
|
75
75
|
rightAxisTickColor: '#1c1d1f',
|
|
76
76
|
numTicks: 4,
|
|
77
77
|
axisPadding: 0,
|
|
78
|
+
titlePlacement: 'top',
|
|
78
79
|
scalePadding: 10,
|
|
79
80
|
tickRotation: 0,
|
|
80
81
|
anchors: [],
|
|
@@ -144,6 +145,7 @@ const createInitialState = () => {
|
|
|
144
145
|
sortByRecentDate: false,
|
|
145
146
|
brushActive: false,
|
|
146
147
|
brushDefaultRecentDateCount: undefined,
|
|
148
|
+
brushDynamicYAxis: false,
|
|
147
149
|
viewportNumTicks: {
|
|
148
150
|
xs: 4,
|
|
149
151
|
xxs: 4
|
|
@@ -156,8 +158,11 @@ const createInitialState = () => {
|
|
|
156
158
|
height: '',
|
|
157
159
|
caption: '',
|
|
158
160
|
showDownloadUrl: false,
|
|
161
|
+
downloadUrlLabel: '',
|
|
159
162
|
showDataTableLink: true,
|
|
160
163
|
showDownloadLinkBelow: true,
|
|
164
|
+
search: false,
|
|
165
|
+
searchPlaceholder: '',
|
|
161
166
|
indexLabel: '',
|
|
162
167
|
download: false,
|
|
163
168
|
showVertical: true,
|
|
@@ -305,6 +310,13 @@ const createInitialState = () => {
|
|
|
305
310
|
leftLabel: '',
|
|
306
311
|
rightLabel: ''
|
|
307
312
|
},
|
|
313
|
+
heatmap: {
|
|
314
|
+
cellPadding: 1,
|
|
315
|
+
rowLabelGap: 32,
|
|
316
|
+
columnLabelGap: 56,
|
|
317
|
+
xAxisPosition: 'top',
|
|
318
|
+
showCellValues: false
|
|
319
|
+
},
|
|
308
320
|
area: {
|
|
309
321
|
isStacked: false
|
|
310
322
|
},
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
2
|
+
import { ViewportSize } from '../types/ChartConfig'
|
|
3
|
+
|
|
4
|
+
export const AXIS_LABEL_FONT_SIZE = 18
|
|
5
|
+
export const AXIS_LABEL_FONT_SIZE_SMALL = 14
|
|
6
|
+
|
|
7
|
+
export const getAxisLabelFontSize = (vizViewport: ViewportSize) =>
|
|
8
|
+
isMobileFontViewport(vizViewport) ? AXIS_LABEL_FONT_SIZE_SMALL : AXIS_LABEL_FONT_SIZE
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
2
|
+
import { buildTooltipListHtml, getTooltipSeriesMarker } from './tooltipHelpers'
|
|
3
|
+
import { type ChartConfig } from '../types/ChartConfig'
|
|
4
|
+
import { type ColorScale } from '../types/ChartContext'
|
|
5
|
+
|
|
6
|
+
type TooltipDateHelpers = {
|
|
7
|
+
formatDate: (date: Date) => string
|
|
8
|
+
formatTooltipsDate: (date: Date) => string
|
|
9
|
+
parseDate: (dateString: string, showError?: boolean) => Date
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type BumpTooltipParams = {
|
|
13
|
+
config: ChartConfig
|
|
14
|
+
colorScale?: ColorScale
|
|
15
|
+
dataRow: Record<string, any>
|
|
16
|
+
series: { dataKey: string; name?: string }
|
|
17
|
+
helpers: TooltipDateHelpers
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const formatXAxisValue = (
|
|
21
|
+
rawValue: unknown,
|
|
22
|
+
config: ChartConfig,
|
|
23
|
+
{ formatDate, formatTooltipsDate, parseDate }: TooltipDateHelpers
|
|
24
|
+
) => {
|
|
25
|
+
if (!isDateScale(config.xAxis) || rawValue === undefined || rawValue === null || rawValue === '') {
|
|
26
|
+
return rawValue
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const parsedDate = parseDate(String(rawValue), false)
|
|
30
|
+
return config.tooltips.dateDisplayFormat ? formatTooltipsDate(parsedDate) : formatDate(parsedDate)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const buildBumpChartTooltipHtml = ({ config, colorScale, dataRow, series, helpers }: BumpTooltipParams) => {
|
|
34
|
+
const formattedXAxisValue = formatXAxisValue(dataRow[config.xAxis.dataKey], config, helpers)
|
|
35
|
+
const xAxisLabel = config.runtime?.xAxis?.label || config.xAxis.dataKey
|
|
36
|
+
const heading = `${xAxisLabel ? `${xAxisLabel}: ` : ''}${formattedXAxisValue ?? ''}`
|
|
37
|
+
|
|
38
|
+
const seriesName = series.name || config.runtime?.seriesLabels?.[series.dataKey] || series.dataKey
|
|
39
|
+
const marker = getTooltipSeriesMarker(config, colorScale, series.dataKey)
|
|
40
|
+
const bodyRows = [
|
|
41
|
+
{
|
|
42
|
+
text: `${seriesName}: ${dataRow[series.dataKey]}`,
|
|
43
|
+
markerColor: marker?.markerColor,
|
|
44
|
+
markerShape: marker?.markerShape
|
|
45
|
+
},
|
|
46
|
+
...Object.values(config.columns || {})
|
|
47
|
+
.filter(column => column.tooltips && column.name !== config.xAxis.dataKey && column.name !== series.dataKey)
|
|
48
|
+
.map(column => ({
|
|
49
|
+
text: `${column.label || column.name}: ${dataRow[column.name]}`
|
|
50
|
+
}))
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
return buildTooltipListHtml({ heading, bodyRows })
|
|
54
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_TICK_LENGTH,
|
|
4
|
+
TICK_LABEL_MARGIN_RIGHT,
|
|
5
|
+
VALUE_ON_LINE_PADDING_NO_AXIS,
|
|
6
|
+
VALUE_ON_LINE_PADDING_WITH_AXIS
|
|
7
|
+
} from '../components/Axis/axis.constants'
|
|
8
|
+
|
|
9
|
+
type CalculateLeftYAxisWidthProps = {
|
|
10
|
+
config: any
|
|
11
|
+
data: any[]
|
|
12
|
+
yScale: any
|
|
13
|
+
numTicks: number
|
|
14
|
+
parentWidth: number
|
|
15
|
+
tickLabelFont: string
|
|
16
|
+
axisLabelFontSize: number
|
|
17
|
+
handleLeftTickFormatting: (tick: any, index: number, ticks: any[]) => string
|
|
18
|
+
categoryLabelSpace?: number
|
|
19
|
+
horizontalYAxisLabelSpace?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const AXIS_LABEL_PADDING = 5
|
|
23
|
+
const MIN_LEFT_AXIS_WIDTH = 0
|
|
24
|
+
const MAX_LEFT_AXIS_WIDTH_RATIO = 0.45
|
|
25
|
+
|
|
26
|
+
type TickValueProps = Omit<
|
|
27
|
+
CalculateLeftYAxisWidthProps,
|
|
28
|
+
'parentWidth' | 'tickLabelFont' | 'axisLabelFontSize' | 'handleLeftTickFormatting'
|
|
29
|
+
>
|
|
30
|
+
|
|
31
|
+
const getTickValues = ({ config, data, yScale, numTicks }: TickValueProps) => {
|
|
32
|
+
if (config.visualizationType === 'Forest Plot') {
|
|
33
|
+
return data.map((_, index) => index)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (typeof yScale?.ticks === 'function') {
|
|
37
|
+
return yScale.ticks(numTicks)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (typeof yScale?.domain === 'function') {
|
|
41
|
+
return yScale.domain()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return []
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const getAxisLabelSpace = (config: any, axisLabelFontSize: number) => {
|
|
48
|
+
if (config.yAxis?.titlePlacement === 'top') {
|
|
49
|
+
return 0
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return !config.hideYAxisLabel && config.runtime.yAxis.label ? axisLabelFontSize + AXIS_LABEL_PADDING : 0
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const measureWidestLabel = (labels: string[], tickLabelFont: string) => {
|
|
56
|
+
return labels.length ? Math.max(...labels.map(label => getTextWidth(label, tickLabelFont))) : 0
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const clampYAxisWidth = (calculatedWidth: number, parentWidth: number) => {
|
|
60
|
+
const maxAllowedWidth = parentWidth * MAX_LEFT_AXIS_WIDTH_RATIO
|
|
61
|
+
return Math.max(MIN_LEFT_AXIS_WIDTH, Math.min(Math.ceil(calculatedWidth), maxAllowedWidth))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const formatMeasuredTickLabels = ({
|
|
65
|
+
tickValues,
|
|
66
|
+
handleLeftTickFormatting,
|
|
67
|
+
inlineLabel,
|
|
68
|
+
labelsAboveGridlines
|
|
69
|
+
}: {
|
|
70
|
+
tickValues: any[]
|
|
71
|
+
handleLeftTickFormatting: (tick: any, index: number, ticks: any[]) => string
|
|
72
|
+
inlineLabel?: string
|
|
73
|
+
labelsAboveGridlines?: boolean
|
|
74
|
+
}) => {
|
|
75
|
+
const inlineLabelHasNoSpace = !inlineLabel?.includes(' ')
|
|
76
|
+
|
|
77
|
+
return tickValues.map((tick, index, ticks) => {
|
|
78
|
+
const formatted = handleLeftTickFormatting(tick, index, ticks)
|
|
79
|
+
const isLastTick = index === ticks.length - 1
|
|
80
|
+
|
|
81
|
+
if (isLastTick && inlineLabel && (labelsAboveGridlines || inlineLabelHasNoSpace)) {
|
|
82
|
+
return `${formatted}${inlineLabel}`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return String(formatted ?? '')
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const getHorizonChartWidth = ({
|
|
90
|
+
config,
|
|
91
|
+
parentWidth,
|
|
92
|
+
tickLabelFont,
|
|
93
|
+
axisLabelFontSize
|
|
94
|
+
}: Pick<CalculateLeftYAxisWidthProps, 'config' | 'parentWidth' | 'tickLabelFont' | 'axisLabelFontSize'>) => {
|
|
95
|
+
const seriesKeys =
|
|
96
|
+
config.runtime?.seriesKeys && config.runtime.seriesKeys.length > 0
|
|
97
|
+
? config.runtime.seriesKeys
|
|
98
|
+
: config.series?.map(series => series.dataKey) || []
|
|
99
|
+
const labels = seriesKeys.map(seriesKey => String(config.runtime?.seriesLabels?.[seriesKey] || seriesKey))
|
|
100
|
+
const labelWidth = config.yAxis.hideLabel ? 0 : measureWidestLabel(labels, tickLabelFont)
|
|
101
|
+
const calculatedWidth =
|
|
102
|
+
labelWidth +
|
|
103
|
+
DEFAULT_TICK_LENGTH +
|
|
104
|
+
TICK_LABEL_MARGIN_RIGHT +
|
|
105
|
+
getAxisLabelSpace(config, axisLabelFontSize) +
|
|
106
|
+
Number(config.yAxis.axisPadding || 0)
|
|
107
|
+
|
|
108
|
+
return clampYAxisWidth(calculatedWidth, parentWidth)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const getHorizontalAxisWidth = ({
|
|
112
|
+
config,
|
|
113
|
+
tickLabelWidth,
|
|
114
|
+
categoryLabelSpace,
|
|
115
|
+
horizontalYAxisLabelSpace
|
|
116
|
+
}: Pick<CalculateLeftYAxisWidthProps, 'config' | 'categoryLabelSpace' | 'horizontalYAxisLabelSpace'> & {
|
|
117
|
+
tickLabelWidth: number
|
|
118
|
+
}) => {
|
|
119
|
+
const horizontalTickWidth = tickLabelWidth + DEFAULT_TICK_LENGTH + TICK_LABEL_MARGIN_RIGHT
|
|
120
|
+
return Math.max(horizontalTickWidth + horizontalYAxisLabelSpace, categoryLabelSpace + horizontalYAxisLabelSpace)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const getCategoricalAxisWidth = ({
|
|
124
|
+
config,
|
|
125
|
+
baseWidth,
|
|
126
|
+
tickLabelFont
|
|
127
|
+
}: Pick<CalculateLeftYAxisWidthProps, 'config' | 'tickLabelFont'> & {
|
|
128
|
+
baseWidth: number
|
|
129
|
+
}) => {
|
|
130
|
+
if (config.yAxis.type !== 'categorical' || config.orientation !== 'vertical' || config.yAxis.hideLabel) {
|
|
131
|
+
return baseWidth
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const categoryLabels = (config.yAxis.categories || []).map(category => String(category?.label || ''))
|
|
135
|
+
const categoryLabelWidth = measureWidestLabel(categoryLabels, tickLabelFont)
|
|
136
|
+
|
|
137
|
+
return Math.max(baseWidth, categoryLabelWidth + DEFAULT_TICK_LENGTH + TICK_LABEL_MARGIN_RIGHT)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export const calculateLeftYAxisWidth = ({
|
|
141
|
+
config,
|
|
142
|
+
data,
|
|
143
|
+
yScale,
|
|
144
|
+
numTicks,
|
|
145
|
+
parentWidth,
|
|
146
|
+
tickLabelFont,
|
|
147
|
+
axisLabelFontSize,
|
|
148
|
+
handleLeftTickFormatting,
|
|
149
|
+
categoryLabelSpace = 0,
|
|
150
|
+
horizontalYAxisLabelSpace = 0
|
|
151
|
+
}: CalculateLeftYAxisWidthProps): number => {
|
|
152
|
+
if (config.visualizationType === 'Horizon Chart') {
|
|
153
|
+
return getHorizonChartWidth({ config, parentWidth, tickLabelFont, axisLabelFontSize })
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const tickValues = getTickValues({ config, data, yScale, numTicks })
|
|
157
|
+
const { hideAxis, inlineLabel, labelsAboveGridlines } = config.yAxis
|
|
158
|
+
const formattedTicks = formatMeasuredTickLabels({
|
|
159
|
+
tickValues,
|
|
160
|
+
handleLeftTickFormatting,
|
|
161
|
+
inlineLabel,
|
|
162
|
+
labelsAboveGridlines
|
|
163
|
+
})
|
|
164
|
+
const tickLabelWidth = config.yAxis.hideLabel ? 0 : measureWidestLabel(formattedTicks, tickLabelFont)
|
|
165
|
+
|
|
166
|
+
const valueOnLinePadding = hideAxis
|
|
167
|
+
? Math.abs(VALUE_ON_LINE_PADDING_NO_AXIS)
|
|
168
|
+
: Math.abs(VALUE_ON_LINE_PADDING_WITH_AXIS)
|
|
169
|
+
const tickPadding = labelsAboveGridlines ? valueOnLinePadding : TICK_LABEL_MARGIN_RIGHT + DEFAULT_TICK_LENGTH
|
|
170
|
+
const verticalAxisLabelSpace = getAxisLabelSpace(config, axisLabelFontSize)
|
|
171
|
+
const reservedTickLabelWidth = labelsAboveGridlines ? 0 : tickLabelWidth
|
|
172
|
+
const reservedTickPadding = labelsAboveGridlines || config.yAxis.hideLabel ? 0 : tickPadding
|
|
173
|
+
|
|
174
|
+
const defaultCalculatedWidth =
|
|
175
|
+
reservedTickLabelWidth + reservedTickPadding + verticalAxisLabelSpace + Number(config.yAxis.axisPadding || 0)
|
|
176
|
+
|
|
177
|
+
if (config.orientation === 'horizontal') {
|
|
178
|
+
const calculatedWidth = getHorizontalAxisWidth({
|
|
179
|
+
config,
|
|
180
|
+
tickLabelWidth,
|
|
181
|
+
categoryLabelSpace,
|
|
182
|
+
horizontalYAxisLabelSpace
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
return clampYAxisWidth(calculatedWidth, parentWidth)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const calculatedWidth = getCategoricalAxisWidth({
|
|
189
|
+
config,
|
|
190
|
+
baseWidth: defaultCalculatedWidth,
|
|
191
|
+
tickLabelFont
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
return clampYAxisWidth(calculatedWidth, parentWidth)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export default calculateLeftYAxisWidth
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ChartConfig } from '../types/ChartConfig'
|
|
2
|
+
|
|
3
|
+
export const ensureSpecialChartAxisTypes = <T extends Pick<ChartConfig, 'visualizationType' | 'xAxis'>>(
|
|
4
|
+
config: T
|
|
5
|
+
): T => {
|
|
6
|
+
if (config.visualizationType === 'Bump Chart' && config.xAxis) {
|
|
7
|
+
config.xAxis.type = 'date-time'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return config
|
|
11
|
+
}
|
package/src/helpers/getMinMax.ts
CHANGED
|
@@ -55,9 +55,8 @@ const getMinMax = ({
|
|
|
55
55
|
max = enteredMaxValue && isMaxValid ? Number(enteredMaxValue) : Number.MIN_VALUE
|
|
56
56
|
const { lower, upper } = config?.confidenceKeys || {}
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const dataForMinMax = config.xAxis.brushActive && tableData && tableData.length > 0 ? tableData : data
|
|
58
|
+
const useBrushFullRange = config.xAxis.brushActive && !config.xAxis.brushDynamicYAxis
|
|
59
|
+
const dataForMinMax = useBrushFullRange && tableData && tableData.length > 0 ? tableData : data
|
|
61
60
|
|
|
62
61
|
if (lower && upper && config.visualizationType === 'Bar') {
|
|
63
62
|
const buffer = min < 0 ? 1.1 : 0
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ChartConfig } from '../types/ChartConfig'
|
|
2
|
+
import { YAxisAutoPaddingMode } from './getYAxisAutoPaddingMode'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Calculates the Y-axis auto padding to prevent data labels from overlapping with axis tick labels.
|
|
@@ -16,7 +17,8 @@ export const getYAxisAutoPadding = (
|
|
|
16
17
|
handleNumTicks: number,
|
|
17
18
|
maxValue: number,
|
|
18
19
|
minValue: number,
|
|
19
|
-
config: ChartConfig
|
|
20
|
+
config: ChartConfig,
|
|
21
|
+
mode: Exclude<YAxisAutoPaddingMode, 'none'>
|
|
20
22
|
): number => {
|
|
21
23
|
// Early returns for cases where auto padding is not needed
|
|
22
24
|
if (!yScale?.ticks || config.orientation === 'horizontal' || config.yAxis?.max) {
|
|
@@ -41,13 +43,13 @@ export const getYAxisAutoPadding = (
|
|
|
41
43
|
const tickGap = ticks.length === 1 ? ticks[0] : ticks[1] - ticks[0]
|
|
42
44
|
const nextTick = Math.max(...ticks) + tickGap
|
|
43
45
|
const divideBy = minValue < 0 ? maxValue / 2 : maxValue
|
|
44
|
-
|
|
46
|
+
let calculatedPadding = (nextTick - maxValue) / divideBy
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
if (mode === 'inline-label' && calculatedPadding <= MINIMUM_DISTANCE_PERCENTAGE) {
|
|
49
|
+
calculatedPadding += tickGap / divideBy
|
|
50
|
+
}
|
|
49
51
|
|
|
50
52
|
/* sometimes even though the padding is getting to the next tick exactly,
|
|
51
53
|
d3 still doesn't show the tick. we add 0.1 to ensure to tip it over the edge */
|
|
52
|
-
return
|
|
54
|
+
return calculatedPadding * 100 + 0.1
|
|
53
55
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
2
|
+
|
|
3
|
+
export type YAxisAutoPaddingMode = 'none' | 'inline-label' | 'top-title'
|
|
4
|
+
|
|
5
|
+
export const getYAxisAutoPaddingMode = (config: ChartConfig): YAxisAutoPaddingMode => {
|
|
6
|
+
const inlineLabel = config.yAxis?.inlineLabel
|
|
7
|
+
if (inlineLabel && inlineLabel.includes(' ')) {
|
|
8
|
+
return 'inline-label'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const yAxisTitle = String(config.yAxis?.label || config.runtime?.yAxis?.label || '').trim()
|
|
12
|
+
if (config.yAxis?.titlePlacement === 'top' && yAxisTitle && !config.hideYAxisLabel) {
|
|
13
|
+
return 'top-title'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return 'none'
|
|
17
|
+
}
|
|
@@ -1,17 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
import type { AltTextConfig } from '@cdc/core/types/AltText'
|
|
2
|
+
import { resolveAltTextDescription } from '@cdc/core/helpers/resolveAltTextDescription'
|
|
3
|
+
|
|
4
|
+
const getAutoLabel = (state: { visualizationType?: string; title?: string }): string => {
|
|
5
|
+
let ariaLabel = ''
|
|
6
|
+
if (state.visualizationType) {
|
|
7
|
+
ariaLabel += `${state.visualizationType} chart`
|
|
8
|
+
}
|
|
9
|
+
if (state.title && state.visualizationType) {
|
|
10
|
+
ariaLabel += ` with the title: ${state.title}`
|
|
11
|
+
}
|
|
12
|
+
return ariaLabel
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const handleChartAriaLabels = (state: {
|
|
16
|
+
visualizationType?: string
|
|
17
|
+
title?: string
|
|
18
|
+
altText?: AltTextConfig
|
|
19
|
+
dataMetadata?: Record<string, string>
|
|
20
|
+
}): string => {
|
|
2
21
|
try {
|
|
3
22
|
if (!state.visualizationType) throw Error('handleChartAriaLabels: no visualization type found in state')
|
|
4
|
-
let ariaLabel = ''
|
|
5
|
-
|
|
6
|
-
if (state.visualizationType) {
|
|
7
|
-
ariaLabel += `${state.visualizationType} chart`
|
|
8
|
-
}
|
|
9
23
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
24
|
+
const title = getAutoLabel(state)
|
|
25
|
+
const description = resolveAltTextDescription(state.altText, state.dataMetadata)
|
|
13
26
|
|
|
14
|
-
return
|
|
27
|
+
return description ? `${title}. ${description}` : title
|
|
15
28
|
} catch (e) {
|
|
16
29
|
console.error('COVE: ', e.message) // eslint-disable-line
|
|
17
30
|
return 'Data visualization container'
|