@cdc/chart 4.26.4 → 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 +349 -29
- package/LICENSE +201 -0
- package/dist/cdcchart.js +34247 -32805
- 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/private/bump-chart.json +1116 -0
- package/examples/private/test-123.json +624 -0
- package/package.json +4 -3
- package/src/CdcChartComponent.tsx +135 -54
- package/src/_stories/Chart.AltText.stories.tsx +136 -0
- package/src/_stories/Chart.HeatMap.stories.tsx +187 -0
- package/src/_stories/Chart.Sankey.smoke.stories.tsx +62 -0
- package/src/_stories/Chart.SmallMultiples.smoke.stories.tsx +29 -0
- package/src/_stories/ChartAxisTitles.smoke.stories.tsx +13 -0
- package/src/_stories/ChartBar.Editor.stories.tsx +63 -0
- package/src/_stories/ChartBrush.Interaction.stories.tsx +112 -0
- package/src/_stories/ChartBrush.smoke.stories.tsx +13 -4
- 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/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 +1 -1
- package/src/components/Annotations/components/AnnotationDropdown.tsx +5 -2
- package/src/components/Axis/LeftAxis.tsx +14 -11
- package/src/components/Axis/LeftAxisGridlines.tsx +14 -11
- 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 +310 -49
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +13 -0
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +105 -13
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +1 -0
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +5 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +2 -2
- 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 +42 -22
- 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/LineChart/components/LineChart.BumpCircle.tsx +19 -22
- package/src/components/LineChart/helpers.test.ts +345 -0
- package/src/components/LineChart/helpers.ts +15 -5
- package/src/components/LineChart/index.tsx +7 -1
- package/src/components/LinearChart/tests/LinearChart.test.tsx +107 -33
- package/src/components/LinearChart/tests/mockConfigContext.ts +1 -0
- package/src/components/LinearChart.tsx +58 -37
- package/src/components/PieChart/PieChart.tsx +4 -1
- package/src/components/RadarChart/RadarChart.tsx +3 -1
- package/src/components/Regions/components/Regions.tsx +195 -41
- package/src/components/Sankey/components/Sankey.tsx +7 -3
- package/src/components/Sankey/helpers/getSankeyTooltip.tsx +7 -6
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +30 -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 +4 -9
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +1 -1
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +2 -2
- package/src/data/initial-state.js +11 -0
- package/src/helpers/axisLabelFontSize.ts +8 -0
- package/src/helpers/bumpChartTooltip.ts +54 -0
- package/src/helpers/calculateLeftYAxisWidth.ts +4 -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/hooks/tests/useScales.test.tsx +105 -0
- package/src/hooks/useScales.ts +19 -9
- package/src/index.jsx +5 -2
- package/src/scss/main.scss +32 -49
- package/src/types/ChartConfig.ts +18 -0
- package/src/types/ChartContext.ts +2 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { getYAxisAutoPaddingMode } from '../getYAxisAutoPaddingMode'
|
|
3
|
+
import { createMockConfig } from '../../components/LinearChart/tests/mockConfigContext'
|
|
4
|
+
|
|
5
|
+
describe('getYAxisAutoPaddingMode', () => {
|
|
6
|
+
it('returns inline-label for multi-word inline labels', () => {
|
|
7
|
+
expect(
|
|
8
|
+
getYAxisAutoPaddingMode(
|
|
9
|
+
createMockConfig({
|
|
10
|
+
yAxis: {
|
|
11
|
+
...createMockConfig().yAxis,
|
|
12
|
+
titlePlacement: 'side',
|
|
13
|
+
inlineLabel: 'per 100k'
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
)
|
|
17
|
+
).toBe('inline-label')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('returns inline-label for inline labels with leading space', () => {
|
|
21
|
+
expect(
|
|
22
|
+
getYAxisAutoPaddingMode(
|
|
23
|
+
createMockConfig({
|
|
24
|
+
yAxis: {
|
|
25
|
+
...createMockConfig().yAxis,
|
|
26
|
+
titlePlacement: 'side',
|
|
27
|
+
inlineLabel: ' cases'
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
)
|
|
31
|
+
).toBe('inline-label')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('returns top-title for top y-axis titles with label content', () => {
|
|
35
|
+
expect(
|
|
36
|
+
getYAxisAutoPaddingMode(
|
|
37
|
+
createMockConfig({
|
|
38
|
+
yAxis: {
|
|
39
|
+
...createMockConfig().yAxis,
|
|
40
|
+
titlePlacement: 'top',
|
|
41
|
+
inlineLabel: ''
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
)
|
|
45
|
+
).toBe('top-title')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('returns none for hidden top y-axis titles', () => {
|
|
49
|
+
expect(
|
|
50
|
+
getYAxisAutoPaddingMode(
|
|
51
|
+
createMockConfig({
|
|
52
|
+
hideYAxisLabel: true,
|
|
53
|
+
yAxis: {
|
|
54
|
+
...createMockConfig().yAxis,
|
|
55
|
+
titlePlacement: 'top',
|
|
56
|
+
inlineLabel: ''
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
)
|
|
60
|
+
).toBe('none')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('returns none when neither trigger applies', () => {
|
|
64
|
+
expect(
|
|
65
|
+
getYAxisAutoPaddingMode(
|
|
66
|
+
createMockConfig({
|
|
67
|
+
yAxis: {
|
|
68
|
+
...createMockConfig().yAxis,
|
|
69
|
+
titlePlacement: 'side',
|
|
70
|
+
inlineLabel: 'Rate'
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
)
|
|
74
|
+
).toBe('none')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('returns inline-label when both triggers are present', () => {
|
|
78
|
+
expect(
|
|
79
|
+
getYAxisAutoPaddingMode(
|
|
80
|
+
createMockConfig({
|
|
81
|
+
yAxis: {
|
|
82
|
+
...createMockConfig().yAxis,
|
|
83
|
+
titlePlacement: 'top',
|
|
84
|
+
inlineLabel: 'per 100k'
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
)
|
|
88
|
+
).toBe('inline-label')
|
|
89
|
+
})
|
|
90
|
+
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { handleChartAriaLabels } from '../handleChartAriaLabels'
|
|
2
|
+
|
|
3
|
+
describe('handleChartAriaLabels', () => {
|
|
4
|
+
it('returns auto-generated title from visualizationType and title', () => {
|
|
5
|
+
const result = handleChartAriaLabels({
|
|
6
|
+
visualizationType: 'Line',
|
|
7
|
+
title: 'COVID-19 Cases'
|
|
8
|
+
})
|
|
9
|
+
expect(result).toBe('Line chart with the title: COVID-19 Cases')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('returns visualizationType-only title when chart title is absent', () => {
|
|
13
|
+
const result = handleChartAriaLabels({
|
|
14
|
+
visualizationType: 'Bar'
|
|
15
|
+
})
|
|
16
|
+
expect(result).toBe('Bar chart')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('returns fallback when visualizationType is missing', () => {
|
|
20
|
+
const result = handleChartAriaLabels({})
|
|
21
|
+
expect(result).toBe('Data visualization container')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('description', () => {
|
|
25
|
+
it('concatenates static description after title', () => {
|
|
26
|
+
const result = handleChartAriaLabels({
|
|
27
|
+
visualizationType: 'Line',
|
|
28
|
+
title: 'Trends',
|
|
29
|
+
altText: { type: 'static', value: 'Cases rose steadily over 8 weeks.' }
|
|
30
|
+
})
|
|
31
|
+
expect(result).toBe('Line chart with the title: Trends. Cases rose steadily over 8 weeks.')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('concatenates description from metadata after title', () => {
|
|
35
|
+
const result = handleChartAriaLabels({
|
|
36
|
+
visualizationType: 'Line',
|
|
37
|
+
title: 'Trends',
|
|
38
|
+
altText: { type: 'metadata', metadataKey: 'altDescription' },
|
|
39
|
+
dataMetadata: { altDescription: 'Positivity increased from 4% to 12%.' }
|
|
40
|
+
})
|
|
41
|
+
expect(result).toBe('Line chart with the title: Trends. Positivity increased from 4% to 12%.')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('returns title only when metadata key is missing', () => {
|
|
45
|
+
const result = handleChartAriaLabels({
|
|
46
|
+
visualizationType: 'Line',
|
|
47
|
+
title: 'Trends',
|
|
48
|
+
altText: { type: 'metadata', metadataKey: 'missing' },
|
|
49
|
+
dataMetadata: { other: 'value' }
|
|
50
|
+
})
|
|
51
|
+
expect(result).toBe('Line chart with the title: Trends')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('returns title only when not configured', () => {
|
|
55
|
+
const result = handleChartAriaLabels({
|
|
56
|
+
visualizationType: 'Line',
|
|
57
|
+
title: 'Trends'
|
|
58
|
+
})
|
|
59
|
+
expect(result).toBe('Line chart with the title: Trends')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('returns title only when altText is empty object', () => {
|
|
63
|
+
const result = handleChartAriaLabels({
|
|
64
|
+
visualizationType: 'Line',
|
|
65
|
+
title: 'Trends',
|
|
66
|
+
altText: {}
|
|
67
|
+
})
|
|
68
|
+
expect(result).toBe('Line chart with the title: Trends')
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { getLegendHighlightKey, shouldResetSeriesHighlight } from '../seriesHighlight'
|
|
3
|
+
|
|
4
|
+
describe('seriesHighlight helpers', () => {
|
|
5
|
+
const seriesLabels = {
|
|
6
|
+
nonfatal_rate_alldrugs: 'All drugs',
|
|
7
|
+
opioid_nonfatal_rate: 'All opioids',
|
|
8
|
+
stimulant_nonfatal_rate: 'All stimulants'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
it('normalizes a legend label to the stored series highlight key', () => {
|
|
12
|
+
expect(getLegendHighlightKey(seriesLabels, 'All opioids')).toBe('opioid_nonfatal_rate')
|
|
13
|
+
expect(getLegendHighlightKey(seriesLabels, 'unmapped_series')).toBe('unmapped_series')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('accepts legend label objects and legacy raw string callers', () => {
|
|
17
|
+
expect(
|
|
18
|
+
getLegendHighlightKey(seriesLabels, {
|
|
19
|
+
datum: 'All stimulants',
|
|
20
|
+
index: 2,
|
|
21
|
+
text: 'All stimulants',
|
|
22
|
+
value: '#075290'
|
|
23
|
+
})
|
|
24
|
+
).toBe('stimulant_nonfatal_rate')
|
|
25
|
+
|
|
26
|
+
expect(getLegendHighlightKey(seriesLabels, 'Highlighted bar')).toBe('Highlighted bar')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('does not reset when removing one selected item from a three-series legend', () => {
|
|
30
|
+
expect(
|
|
31
|
+
shouldResetSeriesHighlight(
|
|
32
|
+
['nonfatal_rate_alldrugs', 'opioid_nonfatal_rate'],
|
|
33
|
+
Object.keys(seriesLabels),
|
|
34
|
+
'opioid_nonfatal_rate',
|
|
35
|
+
'Line'
|
|
36
|
+
)
|
|
37
|
+
).toBe(false)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('resets when selecting the only remaining unselected item', () => {
|
|
41
|
+
expect(
|
|
42
|
+
shouldResetSeriesHighlight(
|
|
43
|
+
['nonfatal_rate_alldrugs', 'opioid_nonfatal_rate'],
|
|
44
|
+
Object.keys(seriesLabels),
|
|
45
|
+
'stimulant_nonfatal_rate',
|
|
46
|
+
'Line'
|
|
47
|
+
)
|
|
48
|
+
).toBe(true)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('does not reset when the clicked item is not a known series key', () => {
|
|
52
|
+
expect(
|
|
53
|
+
shouldResetSeriesHighlight(
|
|
54
|
+
['nonfatal_rate_alldrugs', 'opioid_nonfatal_rate'],
|
|
55
|
+
Object.keys(seriesLabels),
|
|
56
|
+
'suppressed-data-note',
|
|
57
|
+
'Line'
|
|
58
|
+
)
|
|
59
|
+
).toBe(false)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { renderHook } from '@testing-library/react'
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
4
|
+
import ConfigContext from '../../ConfigContext'
|
|
5
|
+
import useScales from '../useScales'
|
|
6
|
+
import { createMockChartContext, createMockConfig } from '../../components/LinearChart/tests/mockConfigContext'
|
|
7
|
+
|
|
8
|
+
const mockGetMinMax = vi.fn(() => ({
|
|
9
|
+
min: 0,
|
|
10
|
+
max: 100,
|
|
11
|
+
leftMax: 100,
|
|
12
|
+
rightMax: 0
|
|
13
|
+
}))
|
|
14
|
+
|
|
15
|
+
const mockGetYAxisAutoPadding = vi.fn(() => 10)
|
|
16
|
+
|
|
17
|
+
vi.mock('../../helpers/getMinMax', () => ({
|
|
18
|
+
default: (...args: any[]) => mockGetMinMax(...args)
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
vi.mock('../../helpers/getYAxisAutoPadding', () => ({
|
|
22
|
+
getYAxisAutoPadding: (...args: any[]) => mockGetYAxisAutoPadding(...args)
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
const config = createMockConfig({
|
|
26
|
+
runtime: {
|
|
27
|
+
...createMockConfig().runtime,
|
|
28
|
+
yAxis: {
|
|
29
|
+
...createMockConfig().runtime.yAxis,
|
|
30
|
+
numTicks: 5
|
|
31
|
+
}
|
|
32
|
+
} as any
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const createWrapper = () => {
|
|
36
|
+
const context = createMockChartContext(config)
|
|
37
|
+
|
|
38
|
+
return ({ children }: React.PropsWithChildren) => (
|
|
39
|
+
<ConfigContext.Provider value={context}>{children}</ConfigContext.Provider>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const baseProps = {
|
|
44
|
+
config,
|
|
45
|
+
data: [],
|
|
46
|
+
tableData: [],
|
|
47
|
+
minValue: 0,
|
|
48
|
+
maxValue: 100,
|
|
49
|
+
existPositiveValue: true,
|
|
50
|
+
isAllLine: true,
|
|
51
|
+
xAxisDataMapped: [1, 2, 3],
|
|
52
|
+
xMax: 300,
|
|
53
|
+
yMax: 200,
|
|
54
|
+
currentViewport: 'lg' as const
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
describe('useScales', () => {
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
mockGetMinMax.mockClear()
|
|
60
|
+
mockGetYAxisAutoPadding.mockClear()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('allows repeated padding passes for inline-label mode', () => {
|
|
64
|
+
renderHook(() => useScales({ ...baseProps, yAxisAutoPaddingMode: 'inline-label' }), {
|
|
65
|
+
wrapper: createWrapper()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
expect(mockGetYAxisAutoPadding).toHaveBeenCalledTimes(3)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('runs a single padding pass for top-title mode', () => {
|
|
72
|
+
renderHook(() => useScales({ ...baseProps, yAxisAutoPaddingMode: 'top-title' }), {
|
|
73
|
+
wrapper: createWrapper()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
expect(mockGetYAxisAutoPadding).toHaveBeenCalledTimes(1)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('ignores manual y-axis padding while calculating auto-padding', () => {
|
|
80
|
+
const configWithManualPadding = createMockConfig({
|
|
81
|
+
yAxis: {
|
|
82
|
+
...createMockConfig().yAxis,
|
|
83
|
+
enablePadding: true,
|
|
84
|
+
scalePadding: 90
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
renderHook(() => useScales({ ...baseProps, config: configWithManualPadding, yAxisAutoPaddingMode: 'top-title' }), {
|
|
89
|
+
wrapper: createWrapper()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
expect(mockGetMinMax.mock.calls[0][0].config.yAxis).toMatchObject({
|
|
93
|
+
enablePadding: false,
|
|
94
|
+
scalePadding: 0
|
|
95
|
+
})
|
|
96
|
+
expect(mockGetYAxisAutoPadding.mock.calls[0][4].yAxis).toMatchObject({
|
|
97
|
+
enablePadding: false,
|
|
98
|
+
scalePadding: 0
|
|
99
|
+
})
|
|
100
|
+
expect(mockGetMinMax.mock.calls[1][0].config.yAxis).toMatchObject({
|
|
101
|
+
enablePadding: true,
|
|
102
|
+
scalePadding: 10
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
})
|
package/src/hooks/useScales.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { ChartConfig } from '../types/ChartConfig'
|
|
|
15
15
|
import { ChartContext } from '../types/ChartContext'
|
|
16
16
|
import _ from 'lodash'
|
|
17
17
|
import { getYAxisAutoPadding } from '../helpers/getYAxisAutoPadding'
|
|
18
|
+
import { YAxisAutoPaddingMode } from '../helpers/getYAxisAutoPaddingMode'
|
|
18
19
|
import getMinMax from '../helpers/getMinMax'
|
|
19
20
|
import { countNumOfTicks } from '../helpers/countNumOfTicks'
|
|
20
21
|
|
|
@@ -39,7 +40,7 @@ type useScaleProps = {
|
|
|
39
40
|
xAxisDataMapped: Object[] // array of x axis date/category items
|
|
40
41
|
xMax: number // chart svg width
|
|
41
42
|
yMax: number // chart svg height
|
|
42
|
-
|
|
43
|
+
yAxisAutoPaddingMode?: YAxisAutoPaddingMode // why Y-axis auto padding should run
|
|
43
44
|
currentViewport?: string // current viewport for tick calculation
|
|
44
45
|
}
|
|
45
46
|
|
|
@@ -55,7 +56,7 @@ const useScales = (properties: useScaleProps) => {
|
|
|
55
56
|
maxValue,
|
|
56
57
|
existPositiveValue,
|
|
57
58
|
isAllLine,
|
|
58
|
-
|
|
59
|
+
yAxisAutoPaddingMode = 'none',
|
|
59
60
|
currentViewport
|
|
60
61
|
} = properties
|
|
61
62
|
|
|
@@ -65,9 +66,13 @@ const useScales = (properties: useScaleProps) => {
|
|
|
65
66
|
const isHorizontal = config.orientation === 'horizontal'
|
|
66
67
|
const { visualizationType, xAxis, forestPlot, runtime } = config
|
|
67
68
|
const isForestPlot = visualizationType === 'Forest Plot'
|
|
69
|
+
const shouldApplyYAxisAutoPadding = yAxisAutoPaddingMode !== 'none' && !isHorizontal
|
|
70
|
+
const minMaxConfig = shouldApplyYAxisAutoPadding
|
|
71
|
+
? { ...config, yAxis: { ...config.yAxis, enablePadding: false, scalePadding: 0 } }
|
|
72
|
+
: config
|
|
68
73
|
|
|
69
74
|
const minMaxProps = {
|
|
70
|
-
config,
|
|
75
|
+
config: minMaxConfig,
|
|
71
76
|
minValue,
|
|
72
77
|
maxValue,
|
|
73
78
|
existPositiveValue,
|
|
@@ -91,12 +96,17 @@ const useScales = (properties: useScaleProps) => {
|
|
|
91
96
|
const handleNumTicks = isForestPlot ? config.data.length : yTickCount
|
|
92
97
|
|
|
93
98
|
// Apply auto-padding if needed
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
if (shouldApplyYAxisAutoPadding) {
|
|
100
|
+
const maxPasses = yAxisAutoPaddingMode === 'inline-label' ? 3 : 1
|
|
101
|
+
|
|
102
|
+
for (let i = 0; i < maxPasses; i++) {
|
|
103
|
+
const scale = composeYScale({ min, max, yMax, config: minMaxConfig, leftMax })
|
|
104
|
+
const padding = getYAxisAutoPadding(scale, handleNumTicks, maxValue, minValue, minMaxConfig, yAxisAutoPaddingMode)
|
|
105
|
+
if (padding > 0 || (maxPasses > 1 && i === 0)) {
|
|
106
|
+
const adjustedConfig = {
|
|
107
|
+
...minMaxConfig,
|
|
108
|
+
yAxis: { ...minMaxConfig.yAxis, scalePadding: padding, enablePadding: true }
|
|
109
|
+
}
|
|
100
110
|
const result = getMinMax({ ...minMaxProps, config: adjustedConfig })
|
|
101
111
|
min = result.min
|
|
102
112
|
max = result.max
|
package/src/index.jsx
CHANGED
|
@@ -9,12 +9,15 @@ let isEditor = window.location.href.includes('editor=true')
|
|
|
9
9
|
let isDebug = window.location.href.includes('debug=true')
|
|
10
10
|
|
|
11
11
|
let domContainer = document.getElementsByClassName('react-container')[0]
|
|
12
|
+
let configUrl = domContainer.dataset.configUrl
|
|
13
|
+
let injectedConfig = domContainer.coveConfig
|
|
12
14
|
|
|
13
15
|
ReactDOM.createRoot(domContainer).render(
|
|
14
16
|
<React.StrictMode>
|
|
15
17
|
<CdcChart
|
|
16
|
-
interactionLabel={
|
|
17
|
-
|
|
18
|
+
interactionLabel={configUrl}
|
|
19
|
+
config={injectedConfig}
|
|
20
|
+
configUrl={injectedConfig ? undefined : configUrl}
|
|
18
21
|
isEditor={isEditor}
|
|
19
22
|
isDebug={isDebug}
|
|
20
23
|
/>
|
package/src/scss/main.scss
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
@import '@cdc/core/styles/utils/breakpoints';
|
|
2
2
|
@import '@cdc/core/styles/layout/wrapper-padding';
|
|
3
|
+
@import '@cdc/core/styles/layout/callout';
|
|
3
4
|
|
|
4
5
|
.form-container {
|
|
5
6
|
overflow-y: auto;
|
|
@@ -24,33 +25,17 @@
|
|
|
24
25
|
background: transparent;
|
|
25
26
|
|
|
26
27
|
.cdc-callout {
|
|
27
|
-
|
|
28
|
-
border: 1px solid #dff2f6;
|
|
29
|
-
margin: 0;
|
|
30
|
-
padding: 1.25rem;
|
|
31
|
-
border-radius: 0.25rem;
|
|
32
|
-
position: relative;
|
|
33
|
-
background: transparent;
|
|
34
|
-
|
|
35
|
-
.cdc-callout__flag {
|
|
36
|
-
position: absolute;
|
|
37
|
-
top: -0.36rem;
|
|
38
|
-
right: 1.08rem;
|
|
39
|
-
width: 1.84rem;
|
|
40
|
-
height: auto;
|
|
41
|
-
}
|
|
28
|
+
--cdc-callout-background: transparent;
|
|
42
29
|
|
|
43
30
|
.cove-visualization__title,
|
|
44
31
|
.cove-visualization__header {
|
|
45
32
|
background: transparent;
|
|
46
33
|
border: 0;
|
|
47
|
-
color: var(--cool-gray-90, #1f2937);
|
|
48
34
|
margin: 0 0 1rem;
|
|
49
35
|
padding: 0;
|
|
50
36
|
border-radius: 0;
|
|
51
37
|
|
|
52
38
|
h2 {
|
|
53
|
-
color: var(--cool-gray-90, #1f2937);
|
|
54
39
|
font-family: var(--fonts-nunito, var(--app-font-secondary));
|
|
55
40
|
font-size: 1.1rem;
|
|
56
41
|
font-weight: 700;
|
|
@@ -64,11 +49,17 @@
|
|
|
64
49
|
margin-bottom: 0 !important;
|
|
65
50
|
}
|
|
66
51
|
|
|
52
|
+
.y-axis-top-title {
|
|
53
|
+
font-family: var(--fonts-nunito, var(--app-font-secondary));
|
|
54
|
+
font-weight: 700;
|
|
55
|
+
line-height: 1.4;
|
|
56
|
+
margin: 0 0 0.5rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
67
59
|
.legend-top {
|
|
68
60
|
.legend-wrapper .tooltip-boundary {
|
|
69
61
|
display: flex;
|
|
70
62
|
flex-wrap: wrap;
|
|
71
|
-
row-gap: var(--cove-visualization-section-gap, 1.5rem);
|
|
72
63
|
order: 2;
|
|
73
64
|
|
|
74
65
|
aside {
|
|
@@ -188,7 +179,7 @@
|
|
|
188
179
|
white-space: nowrap;
|
|
189
180
|
}
|
|
190
181
|
|
|
191
|
-
.legend-item
|
|
182
|
+
.legend-item > .legend-item {
|
|
192
183
|
display: inline-block;
|
|
193
184
|
flex: 0 0 auto;
|
|
194
185
|
}
|
|
@@ -222,11 +213,11 @@
|
|
|
222
213
|
left: 0%;
|
|
223
214
|
top: 108%;
|
|
224
215
|
|
|
225
|
-
|
|
216
|
+
& > * {
|
|
226
217
|
margin: 0;
|
|
227
218
|
}
|
|
228
219
|
|
|
229
|
-
|
|
220
|
+
& > p,
|
|
230
221
|
a {
|
|
231
222
|
margin-left: 5px;
|
|
232
223
|
}
|
|
@@ -235,7 +226,6 @@
|
|
|
235
226
|
}
|
|
236
227
|
|
|
237
228
|
.dynamic-legend-list {
|
|
238
|
-
|
|
239
229
|
// overide traditional legend item that uses !important
|
|
240
230
|
.legend-item {
|
|
241
231
|
align-items: flex-end !important;
|
|
@@ -268,7 +258,7 @@
|
|
|
268
258
|
font-size: 1em;
|
|
269
259
|
vertical-align: middle;
|
|
270
260
|
|
|
271
|
-
|
|
261
|
+
& > span {
|
|
272
262
|
display: flex;
|
|
273
263
|
justify-items: center;
|
|
274
264
|
align-items: center;
|
|
@@ -278,25 +268,25 @@
|
|
|
278
268
|
max-height: 1px;
|
|
279
269
|
}
|
|
280
270
|
|
|
281
|
-
|
|
271
|
+
& > span[class*='Asterisk'] {
|
|
282
272
|
transform: scale(1.8);
|
|
283
273
|
margin-top: 15px;
|
|
284
274
|
}
|
|
285
275
|
|
|
286
|
-
|
|
276
|
+
& > span[class*='Dagger'] {
|
|
287
277
|
margin-top: 2px;
|
|
288
278
|
}
|
|
289
279
|
|
|
290
|
-
|
|
280
|
+
& > span[class*='Sign'] {
|
|
291
281
|
margin-top: 1px;
|
|
292
282
|
}
|
|
293
283
|
|
|
294
|
-
|
|
284
|
+
& > svg {
|
|
295
285
|
width: 50px;
|
|
296
286
|
height: 23px;
|
|
297
287
|
}
|
|
298
288
|
|
|
299
|
-
|
|
289
|
+
& > p {
|
|
300
290
|
margin: 0;
|
|
301
291
|
}
|
|
302
292
|
}
|
|
@@ -325,8 +315,8 @@
|
|
|
325
315
|
min-width: 0;
|
|
326
316
|
max-width: 100%;
|
|
327
317
|
|
|
328
|
-
>div,
|
|
329
|
-
>aside {
|
|
318
|
+
> div,
|
|
319
|
+
> aside {
|
|
330
320
|
min-width: 0;
|
|
331
321
|
max-width: 100%;
|
|
332
322
|
}
|
|
@@ -339,7 +329,7 @@
|
|
|
339
329
|
|
|
340
330
|
&.legend-top:not(.legend-hidden) {
|
|
341
331
|
.legend-wrapper {
|
|
342
|
-
|
|
332
|
+
& > div {
|
|
343
333
|
order: 2;
|
|
344
334
|
width: 100%;
|
|
345
335
|
}
|
|
@@ -354,7 +344,7 @@
|
|
|
354
344
|
|
|
355
345
|
&.legend-left:not(.legend-hidden) {
|
|
356
346
|
.legend-wrapper {
|
|
357
|
-
|
|
347
|
+
& > div {
|
|
358
348
|
order: 2;
|
|
359
349
|
width: 75%;
|
|
360
350
|
}
|
|
@@ -369,7 +359,7 @@
|
|
|
369
359
|
|
|
370
360
|
&.legend-right:not(.legend-hidden) {
|
|
371
361
|
.legend-wrapper {
|
|
372
|
-
|
|
362
|
+
& > div {
|
|
373
363
|
order: 1;
|
|
374
364
|
width: 75%;
|
|
375
365
|
}
|
|
@@ -391,7 +381,7 @@
|
|
|
391
381
|
flex-wrap: nowrap;
|
|
392
382
|
flex-direction: column-reverse;
|
|
393
383
|
|
|
394
|
-
|
|
384
|
+
& > div.tooltip-boundary {
|
|
395
385
|
margin-top: 1.5em;
|
|
396
386
|
}
|
|
397
387
|
}
|
|
@@ -401,14 +391,10 @@
|
|
|
401
391
|
flex-direction: column;
|
|
402
392
|
}
|
|
403
393
|
|
|
404
|
-
&.legend-hidden>svg {
|
|
394
|
+
&.legend-hidden > svg {
|
|
405
395
|
width: 100% !important;
|
|
406
396
|
}
|
|
407
397
|
|
|
408
|
-
&.dashboard-brush {
|
|
409
|
-
margin-bottom: 2.5em;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
398
|
// Brush touch support
|
|
413
399
|
.brush-overlay {
|
|
414
400
|
touch-action: none;
|
|
@@ -434,7 +420,7 @@
|
|
|
434
420
|
pointer-events: none;
|
|
435
421
|
}
|
|
436
422
|
|
|
437
|
-
>svg {
|
|
423
|
+
> svg {
|
|
438
424
|
overflow: visible;
|
|
439
425
|
font-size: 14px;
|
|
440
426
|
margin: 1rem 0 2rem;
|
|
@@ -449,7 +435,7 @@
|
|
|
449
435
|
}
|
|
450
436
|
|
|
451
437
|
&.chart-line--hover {
|
|
452
|
-
>svg circle {
|
|
438
|
+
> svg circle {
|
|
453
439
|
opacity: 0;
|
|
454
440
|
|
|
455
441
|
&:hover {
|
|
@@ -459,12 +445,12 @@
|
|
|
459
445
|
}
|
|
460
446
|
|
|
461
447
|
&.chart-line--always {
|
|
462
|
-
>svg circle {
|
|
448
|
+
> svg circle {
|
|
463
449
|
opacity: 1;
|
|
464
450
|
}
|
|
465
451
|
|
|
466
452
|
// Animations for line chart and data points
|
|
467
|
-
>svg.animated {
|
|
453
|
+
> svg.animated {
|
|
468
454
|
circle {
|
|
469
455
|
opacity: 0;
|
|
470
456
|
animation: revealLolly 0.25s linear forwards;
|
|
@@ -525,7 +511,7 @@
|
|
|
525
511
|
|
|
526
512
|
@include breakpointClass(md) {
|
|
527
513
|
.visualization-container {
|
|
528
|
-
>svg {
|
|
514
|
+
> svg {
|
|
529
515
|
font-size: 16px;
|
|
530
516
|
width: 75%;
|
|
531
517
|
order: 1;
|
|
@@ -608,7 +594,6 @@
|
|
|
608
594
|
}
|
|
609
595
|
|
|
610
596
|
&.animated {
|
|
611
|
-
|
|
612
597
|
.vertical path,
|
|
613
598
|
.vertical rect,
|
|
614
599
|
.vertical foreignObject div {
|
|
@@ -626,7 +611,6 @@
|
|
|
626
611
|
}
|
|
627
612
|
|
|
628
613
|
&.animate {
|
|
629
|
-
|
|
630
614
|
path,
|
|
631
615
|
rect,
|
|
632
616
|
foreignObject div {
|
|
@@ -685,7 +669,7 @@
|
|
|
685
669
|
text-align: center;
|
|
686
670
|
transform: scale(1);
|
|
687
671
|
|
|
688
|
-
|
|
672
|
+
> .visx-group[style] {
|
|
689
673
|
transform: scale(1);
|
|
690
674
|
}
|
|
691
675
|
}
|
|
@@ -707,7 +691,7 @@
|
|
|
707
691
|
}
|
|
708
692
|
|
|
709
693
|
.cove-visualization.type-chart.type-sparkline {
|
|
710
|
-
&.lg .visualization-container>svg {
|
|
694
|
+
&.lg .visualization-container > svg {
|
|
711
695
|
width: 100%;
|
|
712
696
|
}
|
|
713
697
|
|
|
@@ -738,7 +722,6 @@
|
|
|
738
722
|
transform: translate(-20px, 0);
|
|
739
723
|
}
|
|
740
724
|
}
|
|
741
|
-
|
|
742
725
|
}
|
|
743
726
|
|
|
744
727
|
.subtext,
|