@cdc/chart 4.26.7 → 4.26.10
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 +502 -368
- package/dist/cdcchart-CYc-Kayd.es.js +22488 -0
- package/dist/cdcchart-DCKn8NmI.es.js +20695 -0
- package/dist/cdcchart-Dyui9nk_.es.js +45828 -0
- package/dist/cdcchart.js +4 -97406
- package/examples/__data__/vega-remote-data.json +6 -0
- package/examples/dendrogram.json +18 -0
- package/examples/feature/bar/bar-chart-race-annual-change.json +68 -0
- package/examples/feature/heatmap/average-age-categorical.json +2 -2
- package/examples/feature/heatmap/calendar-heatmap.json +2 -2
- package/examples/feature/heatmap/cell-values-bottom-axis.json +2 -2
- package/examples/feature/heatmap/sparse-aggregation.json +2 -2
- package/examples/feature/line/line-chart-race.json +50 -0
- package/examples/feature/sankey/sankey-example-data.json +40 -1441
- package/examples/feature/sankey/sankey_chart_data.csv +9 -20
- package/examples/network.json +31 -0
- package/examples/private/box-plot.json +573 -0
- package/examples/private/custom-color-issue.json +625 -0
- package/examples/private/custom-colors.json +522 -0
- package/examples/private/cutoff.json +10750 -0
- package/examples/private/overlay-legend.json +485 -0
- package/examples/private/pie-test.json +469 -0
- package/examples/vega-remote.json +22 -0
- package/package.json +11 -5
- package/src/CdcChartComponent.tsx +405 -208
- package/src/_stories/Chart.BarRace.stories.tsx +443 -0
- package/src/_stories/Chart.BoxPlotNumericSort.stories.tsx +189 -0
- package/src/_stories/Chart.BoxPlotStatsParity.stories.tsx +207 -0
- package/src/_stories/Chart.ColorDistribution.stories.tsx +299 -0
- package/src/_stories/Chart.DataCutoff.stories.tsx +147 -0
- package/src/_stories/Chart.DataTableDownloadFilename.stories.tsx +60 -0
- package/src/_stories/Chart.Dendrogram.stories.tsx +373 -0
- package/src/_stories/Chart.Forecast.CILegend.stories.tsx +236 -0
- package/src/_stories/Chart.HeatMap.stories.tsx +170 -4
- package/src/_stories/Chart.Legend.AxisAlign.stories.tsx +119 -0
- package/src/_stories/Chart.LineRace.stories.tsx +149 -0
- package/src/_stories/Chart.Network.stories.tsx +432 -0
- package/src/_stories/Chart.PaletteConversion.Editor.stories.tsx +208 -0
- package/src/_stories/Chart.Patterns.smoke.stories.tsx +31 -0
- package/src/_stories/Chart.PercentageValue.stories.tsx +311 -0
- package/src/_stories/Chart.PieLabels.stories.tsx +4 -2
- package/src/_stories/Chart.RankByValueMutation.stories.tsx +206 -0
- package/src/_stories/Chart.RequiredFields.stories.tsx +681 -0
- package/src/_stories/Chart.Sankey.stories.tsx +417 -0
- package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +47 -0
- package/src/_stories/Chart.SmallMultiplesNumericTileOrder.stories.tsx +137 -0
- package/src/_stories/Chart.TooltipSeriesLabels.stories.tsx +43 -0
- package/src/_stories/Chart.VegaRemoteData.stories.tsx +31 -0
- package/src/_stories/ChartAnnotation.Editor.stories.tsx +78 -0
- package/src/_stories/ChartAxisTitles.smoke.stories.tsx +48 -0
- package/src/_stories/ChartBar.Editor.stories.tsx +499 -57
- package/src/_stories/ChartBrush.Editor.stories.tsx +46 -67
- package/src/_stories/ChartBrush.Regression.stories.tsx +135 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +33 -3
- package/src/_stories/ChartLine.EffectCompatibility.stories.tsx +120 -0
- package/src/_stories/_mock/annotation_event-line_mock.json +2 -2
- package/src/_stories/_mock/editor-test-configs.json +20 -10
- package/src/_stories/_mock/editor-test-datasets.json +38 -42
- package/src/_stories/_mock/heatmap-average-age-categorical.json +2 -2
- package/src/_stories/_mock/heatmap.json +2 -2
- package/src/_stories/_mock/legend-axis-align-stacked-bar.json +125 -0
- package/src/_stories/_mock/sankey_mock.json +23 -27
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +52 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +639 -139
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +44 -4
- package/src/components/Annotations/components/AnnotationDropdown.tsx +10 -32
- package/src/components/Annotations/helpers/resolveAnnotationLayout.ts +212 -0
- package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +612 -19
- package/src/components/Annotations/tests/AnnotationDropdown.test.tsx +50 -0
- package/src/components/Annotations/tests/resolveAnnotationLayout.test.ts +203 -0
- package/src/components/Axis/BottomAxis.tsx +1 -1
- package/src/components/Axis/Categorical.Axis.tsx +3 -3
- package/src/components/Axis/RightAxis.tsx +21 -13
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +38 -6
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -3
- package/src/components/BarChart/components/BarChart.StackedVertical.test.ts +28 -0
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +29 -25
- package/src/components/BarChart/components/BarChart.Vertical.tsx +37 -26
- package/src/components/BarChart/components/PortionPatternOverlay.tsx +61 -0
- package/src/components/BarChart/components/context.tsx +6 -0
- package/src/components/BarChart/helpers/getPatternUrl.ts +2 -5
- package/src/components/BarChart/helpers/index.ts +8 -0
- package/src/components/BarChart/helpers/portionPattern.ts +243 -0
- package/src/components/BarChart/helpers/tests/addMinimumBarHeights.test.ts +42 -0
- package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +13 -0
- package/src/components/BarChart/helpers/tests/portionPattern.test.ts +280 -0
- package/src/components/BarChart/helpers/tests/useBarChart.test.tsx +104 -0
- package/src/components/BarChart/helpers/useBarChart.ts +60 -59
- package/src/components/BarChartRace/BarChartRace.test.tsx +199 -0
- package/src/components/BarChartRace/BarChartRace.tsx +230 -0
- package/src/components/BarChartRace/BarChartRaceFallback.test.tsx +61 -0
- package/src/components/BarChartRace/BarChartRaceFallback.tsx +48 -0
- package/src/components/BarChartRace/bar-chart-race.css +160 -0
- package/src/components/BarChartRace/helpers.test.ts +114 -0
- package/src/components/BarChartRace/helpers.ts +185 -0
- package/src/components/BarChartRace/index.ts +3 -0
- package/src/components/BoxPlot/helpers/index.ts +11 -82
- package/src/components/Brush/BrushSelector.tsx +9 -23
- package/src/components/Brush/MiniChartPreview.tsx +33 -1
- package/src/components/Dendrogram/components/Dendrogram.tsx +374 -0
- package/src/components/Dendrogram/dendrogram.css +113 -0
- package/src/components/Dendrogram/helpers/layoutDendrogram.test.ts +95 -0
- package/src/components/Dendrogram/helpers/layoutDendrogram.ts +56 -0
- package/src/components/Dendrogram/helpers/prepareDendrogramData.test.ts +135 -0
- package/src/components/Dendrogram/helpers/prepareDendrogramData.ts +166 -0
- package/src/components/Dendrogram/helpers/resolveDendrogramLabels.test.ts +196 -0
- package/src/components/Dendrogram/helpers/resolveDendrogramLabels.ts +418 -0
- package/src/components/Dendrogram/index.tsx +1 -0
- package/src/components/Dendrogram/types/index.ts +66 -0
- package/src/components/EditorPanel/EditorPanel.tsx +1005 -760
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +50 -5
- package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.css +10 -0
- package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.tsx +211 -0
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +19 -8
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +88 -54
- package/src/components/EditorPanel/components/Panels/Panel.Network.css +9 -0
- package/src/components/EditorPanel/components/Panels/Panel.Network.tsx +202 -0
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +233 -33
- package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +6 -1
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +89 -105
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +8 -66
- package/src/components/EditorPanel/components/Panels/RacingControls.test.tsx +131 -0
- package/src/components/EditorPanel/components/Panels/RacingControls.tsx +185 -0
- package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/index.tsx +4 -0
- package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +20 -7
- package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +25 -2
- package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +2 -1
- package/src/components/EditorPanel/useEditorPermissions.test.tsx +62 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +70 -24
- package/src/components/Forecasting/Forecasting.tsx +15 -6
- package/src/components/HeatMap/components/HeatMap.tsx +511 -178
- package/src/components/HeatMap/heatmap.constants.ts +7 -3
- package/src/components/HeatMap/heatmap.css +101 -2
- package/src/components/HeatMap/helpers/layout.ts +80 -0
- package/src/components/HeatMap/tests/HeatMap.test.tsx +339 -16
- package/src/components/HeatMap/tests/helpers.test.ts +79 -0
- package/src/components/Legend/Legend.Component.tsx +110 -90
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +7 -0
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +47 -34
- package/src/components/Legend/helpers/createFormatLabels.test.ts +226 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +45 -51
- package/src/components/LineChart/components/LineChart.Circle.tsx +3 -3
- package/src/components/LineChart/helpers.test.ts +126 -38
- package/src/components/LineChart/helpers.ts +9 -4
- package/src/components/LineChart/index.tsx +69 -24
- package/src/components/LineChart/tests/LineChart.test.tsx +630 -0
- package/src/components/LineChartRace/LineChartRace.test.tsx +275 -0
- package/src/components/LineChartRace/LineChartRace.tsx +134 -0
- package/src/components/LineChartRace/LineChartRaceFallback.tsx +28 -0
- package/src/components/LineChartRace/helpers.test.ts +88 -0
- package/src/components/LineChartRace/helpers.ts +157 -0
- package/src/components/LineChartRace/index.ts +3 -0
- package/src/components/LineChartRace/line-chart-race.css +16 -0
- package/src/components/LinearChart/ValueAxisAnchors.tsx +58 -0
- package/src/components/LinearChart/VisualizationRenderer.tsx +18 -23
- package/src/components/LinearChart/tests/LinearChart.test.tsx +412 -13
- package/src/components/LinearChart/tests/VisualizationRenderer.test.tsx +141 -0
- package/src/components/LinearChart/tests/mockConfigContext.ts +2 -1
- package/src/components/LinearChart.tsx +81 -51
- package/src/components/Network/components/Network.tsx +372 -0
- package/src/components/Network/helpers/layoutNetwork.test.ts +68 -0
- package/src/components/Network/helpers/layoutNetwork.ts +87 -0
- package/src/components/Network/helpers/prepareNetworkData.test.ts +116 -0
- package/src/components/Network/helpers/prepareNetworkData.ts +101 -0
- package/src/components/Network/helpers/resolveNetworkLabels.test.ts +112 -0
- package/src/components/Network/helpers/resolveNetworkLabels.ts +206 -0
- package/src/components/Network/index.tsx +3 -0
- package/src/components/Network/network.css +107 -0
- package/src/components/Network/types/index.ts +56 -0
- package/src/components/PairedBarChart.jsx +5 -10
- package/src/components/PairedBarChart.test.tsx +71 -0
- package/src/components/PieChart/PieChart.tsx +9 -8
- package/src/components/RacingChartRenderer.tsx +44 -0
- package/src/components/Regions/components/Regions.test.tsx +179 -0
- package/src/components/Regions/components/Regions.tsx +104 -69
- package/src/components/Regions/index.tsx +2 -3
- package/src/components/Sankey/components/Sankey.tsx +264 -437
- package/src/components/Sankey/helpers/getSankeyTooltip.ts +12 -0
- package/src/components/Sankey/helpers/prepareSankeyData.test.ts +71 -0
- package/src/components/Sankey/helpers/prepareSankeyData.ts +90 -0
- package/src/components/Sankey/sankey.scss +57 -116
- package/src/components/Sankey/types/index.ts +41 -4
- package/src/components/ScatterPlot/ScatterPlot.jsx +28 -4
- package/src/components/ScatterPlot/ScatterPlot.test.tsx +60 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +12 -12
- package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +7 -5
- package/src/components/raceTiming.ts +18 -0
- package/src/data/initial-state.js +77 -31
- package/src/helpers/applyChartTypeContract.ts +71 -0
- package/src/helpers/axisLabelFontSize.ts +2 -2
- package/src/helpers/boxPlotStats.ts +60 -0
- package/src/helpers/bumpChartTooltip.ts +2 -1
- package/src/helpers/calculateHorizontalBarCategoryLabelWidth.test.ts +37 -0
- package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +3 -2
- package/src/helpers/calculateRightYAxisWidth.ts +60 -0
- package/src/helpers/colorAssignmentHelpers.ts +2 -2
- package/src/helpers/createNewChartConfig.ts +56 -0
- package/src/helpers/dataDrivenYAxisCategories.ts +18 -0
- package/src/helpers/forecastLegend.ts +88 -0
- package/src/helpers/getBoxPlotConfig.ts +18 -37
- package/src/helpers/getChartTypeDefaultPalette.ts +29 -0
- package/src/helpers/getColorCodeCategoryColorMap.ts +26 -0
- package/src/helpers/getColorScale.ts +31 -22
- package/src/helpers/getDynamicYAxisCategories.ts +69 -0
- package/src/helpers/getMinMax.ts +9 -7
- package/src/helpers/getNewRuntime.ts +5 -2
- package/src/helpers/getStackedSeriesMax.ts +51 -0
- package/src/helpers/getV21ChartDistributionColors.ts +57 -0
- package/src/helpers/handleRankByValue.ts +15 -15
- package/src/helpers/seriesColumnSettings.ts +10 -0
- package/src/helpers/sizeHelpers.ts +4 -3
- package/src/helpers/smallMultiplesHelpers.ts +10 -13
- package/src/helpers/tests/applyChartTypeContract.test.ts +37 -0
- package/src/helpers/tests/boxPlotStats.test.ts +31 -0
- package/src/helpers/tests/bumpChartTooltip.test.ts +29 -0
- package/src/helpers/tests/calculateRightYAxisWidth.test.ts +105 -0
- package/src/helpers/tests/countNumOfTicks.test.ts +11 -0
- package/src/helpers/tests/createNewChartConfig.test.ts +182 -0
- package/src/helpers/tests/getBoxPlotConfig.test.ts +70 -0
- package/src/helpers/tests/getColorCodeCategoryColorMap.test.ts +113 -0
- package/src/helpers/tests/getColorScale.test.ts +226 -1
- package/src/helpers/tests/getDynamicYAxisCategories.test.ts +61 -0
- package/src/helpers/tests/getMinMax.test.ts +41 -0
- package/src/helpers/tests/getStackedSeriesMax.test.ts +26 -0
- package/src/helpers/tests/handleRankByValue.test.ts +55 -37
- package/src/helpers/tests/seriesColumnSettings.test.ts +23 -0
- package/src/helpers/tests/smallMultiplesHelpers.test.ts +37 -1
- package/src/helpers/tests/tooltipHelpers.test.ts +80 -0
- package/src/helpers/tooltipHelpers.ts +16 -0
- package/src/hooks/tests/useTooltip.test.tsx +353 -1
- package/src/hooks/usePrefersReducedMotion.ts +22 -0
- package/src/hooks/useReduceData.ts +4 -36
- package/src/hooks/useScales.ts +15 -0
- package/src/hooks/useTooltip.tsx +95 -64
- package/src/scss/main.scss +74 -2
- package/src/store/chart.reducer.ts +4 -3
- package/src/test/CdcChart.dataTable.test.tsx +490 -16
- package/src/test/CdcChart.dendrogram.test.tsx +277 -0
- package/src/test/CdcChart.finalization.test.tsx +143 -0
- package/src/test/CdcChart.network.test.tsx +271 -0
- package/src/types/ChartConfig.ts +91 -54
- package/src/types/ChartContext.ts +5 -0
- package/src/types/Horizon.ts +1 -1
- package/LICENSE +0 -201
- package/dist/cdcchart-BWVIUcAF.es.js +0 -4802
- package/src/_stories/Chart.Sankey.smoke.stories.tsx +0 -62
- package/src/components/Sankey/components/ColumnList.tsx +0 -19
- package/src/components/Sankey/helpers/getSankeyTooltip.tsx +0 -34
- package/src/components/Sankey/useSankeyAlert.tsx +0 -60
- package/src/data/legacy-defaults.ts +0 -18
- package/src/helpers/ensureSpecialChartAxisTypes.ts +0 -11
- package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +0 -30
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react'
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
import ConfigContext from '../../../ConfigContext'
|
|
5
|
+
import AnnotationDropdown from '../components/AnnotationDropdown'
|
|
6
|
+
|
|
7
|
+
vi.mock('@cdc/core/components/ui/Icon', () => ({
|
|
8
|
+
default: ({ display }: { display: string }) => <span data-icon={display} />
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
describe('AnnotationDropdown', () => {
|
|
12
|
+
it('uses the shared toggle treatment and joins the expanded panel to its header', () => {
|
|
13
|
+
const annotation = { text: 'Important annotation' }
|
|
14
|
+
const context = {
|
|
15
|
+
config: {
|
|
16
|
+
annotations: [annotation],
|
|
17
|
+
general: {
|
|
18
|
+
annotationDropdownText: '',
|
|
19
|
+
mobileAnnotationDisplay: 'symbol',
|
|
20
|
+
showAnnotationDropdown: true
|
|
21
|
+
},
|
|
22
|
+
table: { height: 120, limitHeight: true }
|
|
23
|
+
},
|
|
24
|
+
currentViewport: 'lg',
|
|
25
|
+
visibleAnnotations: [annotation]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
render(
|
|
29
|
+
<ConfigContext.Provider value={context as any}>
|
|
30
|
+
<AnnotationDropdown />
|
|
31
|
+
</ConfigContext.Provider>
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const toggle = screen.getByRole('button', { name: 'Annotations' })
|
|
35
|
+
const section = toggle.closest('section')
|
|
36
|
+
|
|
37
|
+
expect(toggle).toHaveClass('data-table-heading', 'data-table-heading--toggle', 'annotation__dropdown-list')
|
|
38
|
+
expect(toggle).toHaveAttribute('aria-expanded', 'false')
|
|
39
|
+
expect(section).not.toHaveClass('my-4')
|
|
40
|
+
expect(section).not.toHaveClass('mt-4')
|
|
41
|
+
expect(section).not.toHaveClass('mb-4')
|
|
42
|
+
|
|
43
|
+
fireEvent.click(toggle)
|
|
44
|
+
|
|
45
|
+
const panel = section?.querySelector('.annotation-dropdown__panel') as HTMLElement
|
|
46
|
+
expect(toggle).toHaveAttribute('aria-expanded', 'true')
|
|
47
|
+
expect(toggle.nextElementSibling).toBe(panel)
|
|
48
|
+
expect(panel).toHaveStyle({ maxHeight: '120px', overflowY: 'auto' })
|
|
49
|
+
})
|
|
50
|
+
})
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
getAnnotationAttachment,
|
|
4
|
+
getAnnotationLabelRect,
|
|
5
|
+
getFacingAnnotationSide,
|
|
6
|
+
getPreferredAnnotationSide,
|
|
7
|
+
MIN_ANNOTATION_LABEL_WIDTH_EM,
|
|
8
|
+
MIN_EVENT_LINE_LABEL_WIDTH_EM,
|
|
9
|
+
resolveAnnotationLayout,
|
|
10
|
+
resolveAnnotationRectangleDrag
|
|
11
|
+
} from '../helpers/resolveAnnotationLayout'
|
|
12
|
+
|
|
13
|
+
const base = {
|
|
14
|
+
endpointX: 200,
|
|
15
|
+
endpointY: 150,
|
|
16
|
+
dx: 100,
|
|
17
|
+
dy: 10,
|
|
18
|
+
labelWidth: 120,
|
|
19
|
+
labelHeight: 40,
|
|
20
|
+
plotWidth: 400,
|
|
21
|
+
plotHeight: 300,
|
|
22
|
+
fontSize: 16,
|
|
23
|
+
labelPosition: 'auto' as const
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('resolveAnnotationLayout', () => {
|
|
27
|
+
it.each([
|
|
28
|
+
[100, 10, 'right'],
|
|
29
|
+
[-100, 10, 'left'],
|
|
30
|
+
[10, -100, 'above'],
|
|
31
|
+
[10, 100, 'below']
|
|
32
|
+
] as const)('derives %s/%s as the %s preferred side', (dx, dy, side) => {
|
|
33
|
+
expect(getPreferredAnnotationSide(dx, dy)).toBe(side)
|
|
34
|
+
expect(resolveAnnotationLayout({ ...base, dx, dy }).side).toBe(side)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('slides parallel to the preferred side without changing sides', () => {
|
|
38
|
+
const result = resolveAnnotationLayout({ ...base, endpointY: 5 })
|
|
39
|
+
|
|
40
|
+
expect(result.side).toBe('right')
|
|
41
|
+
expect(result.y).toBe(20)
|
|
42
|
+
expect(result.x).toBe(200)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('keeps a short label on its preferred side near an edge when its measured width fits', () => {
|
|
46
|
+
const result = resolveAnnotationLayout({ ...base, endpointX: 320, labelWidth: 70 })
|
|
47
|
+
|
|
48
|
+
expect(result.side).toBe('right')
|
|
49
|
+
expect(result.constrainedWidth).toBeUndefined()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('constrains a long label to the 6em minimum without changing sides', () => {
|
|
53
|
+
const result = resolveAnnotationLayout({ ...base, endpointX: 100, labelWidth: 180 })
|
|
54
|
+
|
|
55
|
+
expect(result.side).toBe('right')
|
|
56
|
+
expect(result.width).toBe(180)
|
|
57
|
+
|
|
58
|
+
const constrained = resolveAnnotationLayout({ ...base, endpointX: 304, labelWidth: 180 })
|
|
59
|
+
expect(constrained.side).toBe('right')
|
|
60
|
+
expect(constrained.width).toBe(96)
|
|
61
|
+
expect(constrained.constrainedWidth).toBe(96)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('accepts the 4em event-line minimum', () => {
|
|
65
|
+
const result = resolveAnnotationLayout({
|
|
66
|
+
...base,
|
|
67
|
+
endpointX: 336,
|
|
68
|
+
labelWidth: 180,
|
|
69
|
+
minWidthEm: MIN_EVENT_LINE_LABEL_WIDTH_EM,
|
|
70
|
+
labelPosition: 'right'
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
expect(result.width).toBe(64)
|
|
74
|
+
expect(result.constrainedWidth).toBe(64)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('keeps a persisted automatic side even when the scaled offsets favor another side', () => {
|
|
78
|
+
const result = resolveAnnotationLayout({
|
|
79
|
+
...base,
|
|
80
|
+
endpointX: 350,
|
|
81
|
+
endpointY: 30,
|
|
82
|
+
dx: 10,
|
|
83
|
+
dy: -200,
|
|
84
|
+
autoSide: 'right',
|
|
85
|
+
labelWidth: 140,
|
|
86
|
+
labelHeight: 60
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
expect(result.side).toBe('right')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('keeps the preferred side for unavoidable overflow', () => {
|
|
93
|
+
const result = resolveAnnotationLayout({
|
|
94
|
+
...base,
|
|
95
|
+
endpointX: 390,
|
|
96
|
+
endpointY: 150,
|
|
97
|
+
labelWidth: 240,
|
|
98
|
+
labelHeight: 310
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
expect(result.side).toBe('right')
|
|
102
|
+
expect(result.width).toBe(MIN_ANNOTATION_LABEL_WIDTH_EM * base.fontSize)
|
|
103
|
+
expect(result.constrainedWidth).toBe(MIN_ANNOTATION_LABEL_WIDTH_EM * base.fontSize)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it.each([
|
|
107
|
+
['left', -100, 150, 96, 150],
|
|
108
|
+
['right', 500, 150, 304, 150],
|
|
109
|
+
['above', 200, -100, 200, 40],
|
|
110
|
+
['below', 200, 500, 200, 260]
|
|
111
|
+
] as const)('clamps %s placement to the same-side boundary', (autoSide, endpointX, endpointY, x, y) => {
|
|
112
|
+
const result = resolveAnnotationLayout({ ...base, endpointX, endpointY, autoSide })
|
|
113
|
+
|
|
114
|
+
expect(result.side).toBe(autoSide)
|
|
115
|
+
expect(result.x).toBe(x)
|
|
116
|
+
expect(result.y).toBe(y)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it.each(['left', 'right', 'above', 'below'] as const)(
|
|
120
|
+
'keeps explicit %s placement even when it cannot fit',
|
|
121
|
+
labelPosition => {
|
|
122
|
+
const result = resolveAnnotationLayout({
|
|
123
|
+
...base,
|
|
124
|
+
endpointX: 5,
|
|
125
|
+
endpointY: 5,
|
|
126
|
+
labelWidth: 300,
|
|
127
|
+
labelHeight: 200,
|
|
128
|
+
labelPosition
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
expect(result.side).toBe(labelPosition)
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
describe('rectangle-based annotation dragging', () => {
|
|
137
|
+
const rect = { left: 100, top: 60, width: 120, height: 40 }
|
|
138
|
+
|
|
139
|
+
it.each(['left', 'right', 'above', 'below'] as const)(
|
|
140
|
+
'reconstructs the same rectangle from its %s attachment',
|
|
141
|
+
side => {
|
|
142
|
+
const attachment = getAnnotationAttachment(rect, side)
|
|
143
|
+
|
|
144
|
+
expect(getAnnotationLabelRect(attachment.x, attachment.y, rect.width, rect.height, side)).toEqual(rect)
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
it.each([
|
|
149
|
+
[20, 80, 'right'],
|
|
150
|
+
[300, 80, 'left'],
|
|
151
|
+
[160, 0, 'below'],
|
|
152
|
+
[160, 150, 'above']
|
|
153
|
+
] as const)('chooses the edge facing a subject at %s/%s', (subjectX, subjectY, side) => {
|
|
154
|
+
expect(getFacingAnnotationSide(subjectX, subjectY, rect)).toBe(side)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('normalizes distance by the rectangle dimensions when choosing the facing edge', () => {
|
|
158
|
+
const wideRect = { left: 0, top: 80, width: 200, height: 40 }
|
|
159
|
+
|
|
160
|
+
expect(getFacingAnnotationSide(160, 140, wideRect)).toBe('above')
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('changes only the attachment when the selected side changes', () => {
|
|
164
|
+
const right = resolveAnnotationRectangleDrag({
|
|
165
|
+
rect,
|
|
166
|
+
subjectX: 20,
|
|
167
|
+
subjectY: 80,
|
|
168
|
+
plotWidth: 400,
|
|
169
|
+
plotHeight: 300,
|
|
170
|
+
side: 'right'
|
|
171
|
+
})
|
|
172
|
+
const below = resolveAnnotationRectangleDrag({
|
|
173
|
+
rect,
|
|
174
|
+
subjectX: 160,
|
|
175
|
+
subjectY: 0,
|
|
176
|
+
plotWidth: 400,
|
|
177
|
+
plotHeight: 300,
|
|
178
|
+
side: 'below'
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
expect(right.rect).toEqual(rect)
|
|
182
|
+
expect(below.rect).toEqual(rect)
|
|
183
|
+
expect({ x: right.layout.x, y: right.layout.y }).toEqual({ x: 100, y: 80 })
|
|
184
|
+
expect({ x: below.layout.x, y: below.layout.y }).toEqual({ x: 160, y: 60 })
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('clamps the rectangle before deriving the persisted attachment', () => {
|
|
188
|
+
const result = resolveAnnotationRectangleDrag({
|
|
189
|
+
rect: { ...rect, left: 350, top: -20 },
|
|
190
|
+
subjectX: 200,
|
|
191
|
+
subjectY: 150,
|
|
192
|
+
plotWidth: 400,
|
|
193
|
+
plotHeight: 300,
|
|
194
|
+
side: 'left'
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
expect(result.rect).toEqual({ ...rect, left: 280, top: 0 })
|
|
198
|
+
expect(result.layout.x).toBe(400)
|
|
199
|
+
expect(result.layout.y).toBe(20)
|
|
200
|
+
expect(result.dx).toBe(200)
|
|
201
|
+
expect(result.dy).toBe(-130)
|
|
202
|
+
})
|
|
203
|
+
})
|
|
@@ -98,7 +98,7 @@ const BottomAxis: React.FC<BottomAxisProps> = ({
|
|
|
98
98
|
config.runtime.xAxis.manual
|
|
99
99
|
? getTickValues(xAxisDataMapped, xScale, isDateTime ? xTickCount : getManualStep(), config)
|
|
100
100
|
: config.runtime.xAxis.type === 'date'
|
|
101
|
-
?
|
|
101
|
+
? sortedUniqueXAxisData
|
|
102
102
|
: // For date-time with small datasets (e.g., brush-filtered), use explicit tick values
|
|
103
103
|
// to ensure each data point can have a tick. Otherwise, visx may generate too few.
|
|
104
104
|
// Use uniqueXAxisDataMapped to handle cases where multiple series share x-axis values
|
|
@@ -9,7 +9,7 @@ import createBarElement from '@cdc/core/components/createBarElement'
|
|
|
9
9
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
10
10
|
import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
|
|
11
11
|
|
|
12
|
-
const CategoricalYAxis = ({ yScale, yMax, leftSize, xMax }) => {
|
|
12
|
+
const CategoricalYAxis = ({ yScale, yMax, leftSize, xMax, categories }) => {
|
|
13
13
|
const { config } = useContext(ConfigContext)
|
|
14
14
|
|
|
15
15
|
const { orientation } = config
|
|
@@ -22,7 +22,7 @@ const CategoricalYAxis = ({ yScale, yMax, leftSize, xMax }) => {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
if (!categories?.length) return null
|
|
26
26
|
|
|
27
27
|
// Get max from the yScale domain
|
|
28
28
|
const max = yScale.domain()[1]
|
|
@@ -146,7 +146,7 @@ const CategoricalYAxis = ({ yScale, yMax, leftSize, xMax }) => {
|
|
|
146
146
|
{/* White background spacing between stackes */}
|
|
147
147
|
{!isLastIndex && <rect x={bar.x} y={bar.y} width={bar.width} height={1} fill={'#fff'}></rect>}
|
|
148
148
|
{/* Right side Axis line */}
|
|
149
|
-
<rect x={bar.x + bar.width} y={0} width={1} height={yMax} fill=
|
|
149
|
+
{!config.yAxis.hideAxis && <rect x={bar.x + bar.width} y={0} width={1} height={yMax} fill='#000' />}
|
|
150
150
|
</Group>
|
|
151
151
|
</Group>
|
|
152
152
|
)
|
|
@@ -6,6 +6,7 @@ import { Text } from '@visx/text'
|
|
|
6
6
|
import { ScaleLinear } from 'd3-scale'
|
|
7
7
|
|
|
8
8
|
import ConfigContext from '../../ConfigContext'
|
|
9
|
+
import { DEFAULT_TICK_LENGTH, TICK_LABEL_MARGIN_RIGHT } from './axis.constants'
|
|
9
10
|
|
|
10
11
|
// Constants
|
|
11
12
|
const HORIZONTAL_TICK_OFFSET_ADJUSTMENT = 5
|
|
@@ -15,6 +16,7 @@ type RightAxisProps = {
|
|
|
15
16
|
yMax: number
|
|
16
17
|
xMax: number
|
|
17
18
|
yAxisWidth: number
|
|
19
|
+
rightAxisWidth: number
|
|
18
20
|
tickValues?: number[]
|
|
19
21
|
tickLabelFontSize: number
|
|
20
22
|
axisLabelFontSize: number
|
|
@@ -30,12 +32,14 @@ export const RightAxis: React.FC<RightAxisProps> = ({
|
|
|
30
32
|
yMax,
|
|
31
33
|
xMax,
|
|
32
34
|
yAxisWidth,
|
|
35
|
+
rightAxisWidth,
|
|
33
36
|
tickValues,
|
|
34
37
|
tickLabelFontSize,
|
|
35
38
|
axisLabelFontSize
|
|
36
39
|
}) => {
|
|
37
40
|
const { config, formatNumber } = useContext(ConfigContext)
|
|
38
41
|
const { runtime } = config
|
|
42
|
+
const showSideTitle = config.yAxis.rightTitlePlacement !== 'top'
|
|
39
43
|
|
|
40
44
|
const horizontalTickOffset = (ticks: any[]) =>
|
|
41
45
|
yMax / ticks.length / 2 - (yMax / ticks.length) * (1 - config.barThickness) + HORIZONTAL_TICK_OFFSET_ADJUSTMENT
|
|
@@ -44,8 +48,9 @@ export const RightAxis: React.FC<RightAxisProps> = ({
|
|
|
44
48
|
<VisxAxisRight
|
|
45
49
|
scale={yScaleRight}
|
|
46
50
|
left={yAxisWidth + xMax}
|
|
47
|
-
label={config.yAxis.rightLabel}
|
|
51
|
+
label={runtime.yAxis.rightLabel ?? config.yAxis.rightLabel}
|
|
48
52
|
tickFormat={tick => formatNumber(tick, 'right')}
|
|
53
|
+
tickLength={DEFAULT_TICK_LENGTH}
|
|
49
54
|
numTicks={runtime.yAxis.rightNumTicks || undefined}
|
|
50
55
|
{...(tickValues ? { tickValues } : {})}
|
|
51
56
|
labelOffset={45}
|
|
@@ -55,6 +60,7 @@ export const RightAxis: React.FC<RightAxisProps> = ({
|
|
|
55
60
|
config.orientation === 'horizontal'
|
|
56
61
|
? (props.axisToPoint.y - props.axisFromPoint.y) / 2
|
|
57
62
|
: (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
63
|
+
const sideTitleX = Math.max(0, rightAxisWidth - axisLabelFontSize)
|
|
58
64
|
|
|
59
65
|
return (
|
|
60
66
|
<Group className='right-axis'>
|
|
@@ -75,7 +81,7 @@ export const RightAxis: React.FC<RightAxisProps> = ({
|
|
|
75
81
|
|
|
76
82
|
{!config.yAxis.rightHideLabel && (
|
|
77
83
|
<Text
|
|
78
|
-
x={tick.to.x}
|
|
84
|
+
x={tick.to.x + TICK_LABEL_MARGIN_RIGHT}
|
|
79
85
|
y={tick.to.y + (runtime.horizontal ? horizontalTickOffset(props.ticks) : 0)}
|
|
80
86
|
verticalAnchor={runtime.horizontal ? 'start' : 'middle'}
|
|
81
87
|
textAnchor='start'
|
|
@@ -90,17 +96,19 @@ export const RightAxis: React.FC<RightAxisProps> = ({
|
|
|
90
96
|
|
|
91
97
|
{!config.yAxis.rightHideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
{showSideTitle && (
|
|
100
|
+
<Text
|
|
101
|
+
className='y-label'
|
|
102
|
+
textAnchor='middle'
|
|
103
|
+
verticalAnchor='start'
|
|
104
|
+
transform={`translate(${sideTitleX}, ${axisCenter}) rotate(-90)`}
|
|
105
|
+
fontWeight='bold'
|
|
106
|
+
fill={config.yAxis.rightAxisLabelColor}
|
|
107
|
+
fontSize={axisLabelFontSize}
|
|
108
|
+
>
|
|
109
|
+
{props.label}
|
|
110
|
+
</Text>
|
|
111
|
+
)}
|
|
104
112
|
</Group>
|
|
105
113
|
)
|
|
106
114
|
}}
|
|
@@ -17,6 +17,7 @@ import createBarElement from '@cdc/core/components/createBarElement'
|
|
|
17
17
|
import { getBarConfig, testZeroValue, getLollipopStemColor, getLollipopHeadColor } from '../helpers'
|
|
18
18
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
19
19
|
import isNumber from '@cdc/core/helpers/isNumber'
|
|
20
|
+
import { getSeriesValueLabel } from '@cdc/core/helpers/getSeriesName'
|
|
20
21
|
|
|
21
22
|
// Local context and types
|
|
22
23
|
import BarChartContext, { BarChartContextValues } from './context'
|
|
@@ -24,8 +25,10 @@ import { ChartContext } from '../../../types/ChartContext'
|
|
|
24
25
|
import { getBarData } from '../helpers/getBarData'
|
|
25
26
|
import { getHorizontalBarHeights } from '../helpers/getBarHeights'
|
|
26
27
|
import { getPatternUrl as getPatternUrlForBar } from '../helpers/getPatternUrl'
|
|
28
|
+
import { getPortionPatternRenderData } from '../helpers/portionPattern'
|
|
27
29
|
import { getChartPatternId } from '../../../helpers/getChartPatternId'
|
|
28
30
|
import { buildSeriesTooltipListHtml } from '../../../helpers/tooltipHelpers'
|
|
31
|
+
import { BarPortionPatternOverlay } from './PortionPatternOverlay'
|
|
29
32
|
|
|
30
33
|
const BarChartHorizontal = () => {
|
|
31
34
|
const { xScale, yScale, yMax, seriesScale, barChart } = useContext<BarChartContextValues>(BarChartContext)
|
|
@@ -41,6 +44,7 @@ const BarChartHorizontal = () => {
|
|
|
41
44
|
getHighlightedBarColorByValue,
|
|
42
45
|
getHighlightedBarByValue,
|
|
43
46
|
getAdditionalColumn,
|
|
47
|
+
formatTooltipValue,
|
|
44
48
|
hoveredBar,
|
|
45
49
|
onMouseLeaveBar,
|
|
46
50
|
onMouseOverBar
|
|
@@ -48,7 +52,6 @@ const BarChartHorizontal = () => {
|
|
|
48
52
|
|
|
49
53
|
const {
|
|
50
54
|
transformedData: data,
|
|
51
|
-
tableData,
|
|
52
55
|
colorScale,
|
|
53
56
|
seriesHighlight,
|
|
54
57
|
config,
|
|
@@ -120,8 +123,9 @@ const BarChartHorizontal = () => {
|
|
|
120
123
|
id={patternId}
|
|
121
124
|
height={size}
|
|
122
125
|
width={size}
|
|
123
|
-
fill=
|
|
124
|
-
|
|
126
|
+
fill='transparent'
|
|
127
|
+
stroke={pattern.color}
|
|
128
|
+
strokeWidth={0.75}
|
|
125
129
|
/>
|
|
126
130
|
)
|
|
127
131
|
default:
|
|
@@ -141,7 +145,7 @@ const BarChartHorizontal = () => {
|
|
|
141
145
|
<Group>
|
|
142
146
|
{renderPatternDefs()}
|
|
143
147
|
<BarGroup
|
|
144
|
-
data={
|
|
148
|
+
data={_data}
|
|
145
149
|
keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
|
|
146
150
|
height={yMax}
|
|
147
151
|
x0={d => d[config.runtime.originalXAxis.dataKey]}
|
|
@@ -242,7 +246,8 @@ const BarChartHorizontal = () => {
|
|
|
242
246
|
? `${config.runtime.yAxis.label}: ${xAxisValue}`
|
|
243
247
|
: xAxisValue
|
|
244
248
|
const additionalColTooltip = getAdditionalColumn(bar.key, hoveredBar)
|
|
245
|
-
const
|
|
249
|
+
const tooltipValue = formatTooltipValue(bar.key, dataValue, yAxisValue, barGroup.index)
|
|
250
|
+
const tooltipBody = `${getSeriesValueLabel(bar.key, config)}: ${tooltipValue}`
|
|
246
251
|
const tooltip = buildSeriesTooltipListHtml({
|
|
247
252
|
config,
|
|
248
253
|
colorScale,
|
|
@@ -261,7 +266,7 @@ const BarChartHorizontal = () => {
|
|
|
261
266
|
: colorScale(bar.key)
|
|
262
267
|
const hasDynamicCategory = config.series.find(s => s.dynamicCategory)
|
|
263
268
|
if (!hasDynamicCategory && config.legend.colorCode) {
|
|
264
|
-
barColor = assignColorsToValues(
|
|
269
|
+
barColor = assignColorsToValues(barGroup.index, barColor) // Color code by category
|
|
265
270
|
}
|
|
266
271
|
const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
|
|
267
272
|
const isTwoToneLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'two-tone'
|
|
@@ -339,6 +344,20 @@ const BarChartHorizontal = () => {
|
|
|
339
344
|
allowNonSeriesFieldMatch: !config.series || config.series.length <= 1
|
|
340
345
|
})
|
|
341
346
|
const baseBackground = getBarBackgroundColor()
|
|
347
|
+
const portionPatternRenderData = getPortionPatternRenderData({
|
|
348
|
+
config,
|
|
349
|
+
orientation: 'horizontal',
|
|
350
|
+
bounds: {
|
|
351
|
+
x: barX,
|
|
352
|
+
y: barHeight * bar.index,
|
|
353
|
+
width: barWidth,
|
|
354
|
+
height: numbericBarHeight
|
|
355
|
+
},
|
|
356
|
+
patterns: config.legend?.patterns,
|
|
357
|
+
datum,
|
|
358
|
+
seriesKey: bar.key,
|
|
359
|
+
totalValue: bar.value
|
|
360
|
+
})
|
|
342
361
|
|
|
343
362
|
return (
|
|
344
363
|
<Group display={hideGroup} key={`${barGroup.index}--${index}`}>
|
|
@@ -400,6 +419,19 @@ const BarChartHorizontal = () => {
|
|
|
400
419
|
}
|
|
401
420
|
})}
|
|
402
421
|
|
|
422
|
+
{portionPatternRenderData && (
|
|
423
|
+
<BarPortionPatternOverlay
|
|
424
|
+
config={config}
|
|
425
|
+
index={newIndex}
|
|
426
|
+
renderData={portionPatternRenderData}
|
|
427
|
+
transformOrigin={`0 ${barY + barHeight}px`}
|
|
428
|
+
style={{
|
|
429
|
+
opacity: transparentBar ? 0.2 : 1,
|
|
430
|
+
display: displayBar ? 'block' : 'none'
|
|
431
|
+
}}
|
|
432
|
+
/>
|
|
433
|
+
)}
|
|
434
|
+
|
|
403
435
|
{(absentDataLabel || isSuppressed) && (
|
|
404
436
|
<rect
|
|
405
437
|
x={barX}
|
|
@@ -13,6 +13,7 @@ import BarChartContext, { type BarChartContextValues } from './context'
|
|
|
13
13
|
import { type ChartContext } from '../../../types/ChartContext'
|
|
14
14
|
|
|
15
15
|
import createBarElement from '@cdc/core/components/createBarElement'
|
|
16
|
+
import { getSeriesValueLabel } from '@cdc/core/helpers/getSeriesName'
|
|
16
17
|
import { getHorizontalBarHeights } from '../helpers/getBarHeights'
|
|
17
18
|
import { getPatternUrl as getPatternUrlForBar } from '../helpers/getPatternUrl'
|
|
18
19
|
import { getChartPatternId } from '../../../helpers/getChartPatternId'
|
|
@@ -38,6 +39,7 @@ const BarChartStackedHorizontal = () => {
|
|
|
38
39
|
barBorderWidth,
|
|
39
40
|
displayNumbersOnBar,
|
|
40
41
|
getAdditionalColumn,
|
|
42
|
+
formatTooltipValue,
|
|
41
43
|
hoveredBar,
|
|
42
44
|
isHorizontal,
|
|
43
45
|
isLabelBelowBar,
|
|
@@ -104,8 +106,9 @@ const BarChartStackedHorizontal = () => {
|
|
|
104
106
|
id={patternId}
|
|
105
107
|
height={size}
|
|
106
108
|
width={size}
|
|
107
|
-
fill=
|
|
108
|
-
|
|
109
|
+
fill='transparent'
|
|
110
|
+
stroke={pattern.color}
|
|
111
|
+
strokeWidth={0.75}
|
|
109
112
|
/>
|
|
110
113
|
)
|
|
111
114
|
default:
|
|
@@ -161,7 +164,13 @@ const BarChartStackedHorizontal = () => {
|
|
|
161
164
|
: yAxisValue
|
|
162
165
|
const textWidth = getTextWidth(xAxisValue, `normal ${labelFontSize}px sans-serif`)
|
|
163
166
|
const additionalColTooltip = getAdditionalColumn(bar.key, hoveredBar)
|
|
164
|
-
const
|
|
167
|
+
const tooltipValue = formatTooltipValue(
|
|
168
|
+
bar.key,
|
|
169
|
+
data[bar.index][config.runtime.originalXAxis.dataKey],
|
|
170
|
+
xAxisValue,
|
|
171
|
+
bar.index
|
|
172
|
+
)
|
|
173
|
+
const tooltipBody = `${getSeriesValueLabel(bar.key, config)}: ${tooltipValue}`
|
|
165
174
|
const tooltip = buildSeriesTooltipListHtml({
|
|
166
175
|
config,
|
|
167
176
|
colorScale,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { getStackedVerticalBarThickness } from './BarChart.StackedVertical'
|
|
3
|
+
|
|
4
|
+
describe('getStackedVerticalBarThickness', () => {
|
|
5
|
+
it.each(['date', 'date-time'])('does not apply bar thickness twice for a %s axis', () => {
|
|
6
|
+
expect(
|
|
7
|
+
getStackedVerticalBarThickness({
|
|
8
|
+
isDateAxisType: true,
|
|
9
|
+
dateScaleWidth: 40,
|
|
10
|
+
xMax: 400,
|
|
11
|
+
barCount: 4,
|
|
12
|
+
barThickness: 0.8
|
|
13
|
+
})
|
|
14
|
+
).toBe(40)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('applies bar thickness to categorical bars', () => {
|
|
18
|
+
expect(
|
|
19
|
+
getStackedVerticalBarThickness({
|
|
20
|
+
isDateAxisType: false,
|
|
21
|
+
dateScaleWidth: 40,
|
|
22
|
+
xMax: 400,
|
|
23
|
+
barCount: 4,
|
|
24
|
+
barThickness: 0.8
|
|
25
|
+
})
|
|
26
|
+
).toBe(80)
|
|
27
|
+
})
|
|
28
|
+
})
|
|
@@ -1,25 +1,39 @@
|
|
|
1
|
-
import React, { useContext
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
2
|
import ConfigContext from '../../../ConfigContext'
|
|
3
3
|
import { BarStack } from '@visx/shape'
|
|
4
4
|
import { Group } from '@visx/group'
|
|
5
5
|
import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
|
|
6
6
|
import BarChartContext from './context'
|
|
7
|
-
import Regions from '../../Regions'
|
|
8
7
|
import { addMinimumBarHeights } from '../helpers'
|
|
9
8
|
|
|
10
9
|
import createBarElement from '@cdc/core/components/createBarElement'
|
|
10
|
+
import { getSeriesValueLabel } from '@cdc/core/helpers/getSeriesName'
|
|
11
11
|
import { getPatternUrl as getPatternUrlForBar } from '../helpers/getPatternUrl'
|
|
12
12
|
import { getChartPatternId } from '../../../helpers/getChartPatternId'
|
|
13
13
|
import { buildSeriesTooltipListHtml } from '../../../helpers/tooltipHelpers'
|
|
14
14
|
|
|
15
|
+
export const getStackedVerticalBarThickness = ({
|
|
16
|
+
isDateAxisType,
|
|
17
|
+
dateScaleWidth,
|
|
18
|
+
xMax,
|
|
19
|
+
barCount,
|
|
20
|
+
barThickness
|
|
21
|
+
}: {
|
|
22
|
+
isDateAxisType: boolean
|
|
23
|
+
dateScaleWidth: number
|
|
24
|
+
xMax: number
|
|
25
|
+
barCount: number
|
|
26
|
+
barThickness: number
|
|
27
|
+
}) => (isDateAxisType ? dateScaleWidth : barThickness * (xMax / barCount))
|
|
28
|
+
|
|
15
29
|
const BarChartStackedVertical = () => {
|
|
16
|
-
const [barWidth, setBarWidth] = useState(0)
|
|
17
30
|
const { xScale, yScale, seriesScale, xMax, yMax, barChart } = useContext(BarChartContext)
|
|
18
31
|
const {
|
|
19
32
|
isHorizontal,
|
|
20
33
|
barBorderWidth,
|
|
21
34
|
hoveredBar,
|
|
22
35
|
getAdditionalColumn,
|
|
36
|
+
formatTooltipValue,
|
|
23
37
|
onMouseLeaveBar,
|
|
24
38
|
onMouseOverBar,
|
|
25
39
|
barStackedSeriesKeys
|
|
@@ -89,8 +103,9 @@ const BarChartStackedVertical = () => {
|
|
|
89
103
|
id={patternId}
|
|
90
104
|
height={size}
|
|
91
105
|
width={size}
|
|
92
|
-
fill=
|
|
93
|
-
|
|
106
|
+
fill='transparent'
|
|
107
|
+
stroke={pattern.color}
|
|
108
|
+
strokeWidth={0.75}
|
|
94
109
|
/>
|
|
95
110
|
)
|
|
96
111
|
default:
|
|
@@ -127,15 +142,19 @@ const BarChartStackedVertical = () => {
|
|
|
127
142
|
config.legend.behavior === 'highlight' ||
|
|
128
143
|
seriesHighlight.length === 0 ||
|
|
129
144
|
seriesHighlight.indexOf(bar.key) !== -1
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
:
|
|
133
|
-
|
|
145
|
+
const barThickness = getStackedVerticalBarThickness({
|
|
146
|
+
isDateAxisType,
|
|
147
|
+
dateScaleWidth: seriesScale.range()[1] - seriesScale.range()[0],
|
|
148
|
+
xMax,
|
|
149
|
+
barCount: barStack.bars.length,
|
|
150
|
+
barThickness: config.barThickness
|
|
151
|
+
})
|
|
134
152
|
// tooltips
|
|
135
153
|
const rawXValue = bar.bar.data[config.runtime.xAxis.dataKey]
|
|
136
154
|
const xAxisValue = isDateAxisType ? formatDate(parseDate(rawXValue)) : rawXValue
|
|
137
155
|
const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0, 'left')
|
|
138
156
|
if (!yAxisValue) return
|
|
157
|
+
const tooltipValue = formatTooltipValue(bar.key, rawXValue, yAxisValue, bar.index)
|
|
139
158
|
const barX =
|
|
140
159
|
xScale(isDateAxisType ? parseDate(rawXValue) : rawXValue) -
|
|
141
160
|
(isDateTimeScaleAxisType ? barThickness / 2 : 0)
|
|
@@ -143,7 +162,7 @@ const BarChartStackedVertical = () => {
|
|
|
143
162
|
? `${config.runtime.xAxis.label}: ${xAxisValue}`
|
|
144
163
|
: xAxisValue
|
|
145
164
|
const additionalColTooltip = getAdditionalColumn(bar.key, hoveredBar)
|
|
146
|
-
const tooltipBody = `${
|
|
165
|
+
const tooltipBody = `${getSeriesValueLabel(bar.key, config)}: ${tooltipValue}`
|
|
147
166
|
const tooltip = buildSeriesTooltipListHtml({
|
|
148
167
|
config,
|
|
149
168
|
colorScale,
|
|
@@ -153,8 +172,6 @@ const BarChartStackedVertical = () => {
|
|
|
153
172
|
extraRows: additionalColTooltip ? [additionalColTooltip] : []
|
|
154
173
|
})
|
|
155
174
|
|
|
156
|
-
setBarWidth(barThickness)
|
|
157
|
-
|
|
158
175
|
// Check if this bar should use a pattern
|
|
159
176
|
const patternUrl = getPatternUrlForBar({
|
|
160
177
|
patterns: config.legend?.patterns,
|
|
@@ -239,19 +256,6 @@ const BarChartStackedVertical = () => {
|
|
|
239
256
|
})
|
|
240
257
|
}}
|
|
241
258
|
</BarStack>
|
|
242
|
-
<Regions
|
|
243
|
-
xScale={xScale}
|
|
244
|
-
yMax={yMax}
|
|
245
|
-
barWidth={barWidth}
|
|
246
|
-
totalBarsInGroup={1}
|
|
247
|
-
xMax={xMax}
|
|
248
|
-
handleTooltipMouseOff={() => {}}
|
|
249
|
-
handleTooltipMouseOver={() => {}}
|
|
250
|
-
handleTooltipClick={() => {}}
|
|
251
|
-
tooltipData={null}
|
|
252
|
-
showTooltip={() => {}}
|
|
253
|
-
hideTooltip={() => {}}
|
|
254
|
-
/>
|
|
255
259
|
</>
|
|
256
260
|
)
|
|
257
261
|
)
|