@cdc/chart 4.26.6 → 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 +504 -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 -96498
- 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/flueview.json +22051 -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 -13
- package/src/CdcChart.tsx +2 -2
- package/src/CdcChartComponent.tsx +444 -217
- 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 +49 -0
- 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/Chart.smoke.stories.tsx +14 -1
- 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/ChartPrefixSuffix.smoke.stories.tsx +28 -1
- 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 +39 -8
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +13 -4
- package/src/components/BarChart/components/BarChart.StackedVertical.test.ts +28 -0
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +30 -26
- package/src/components/BarChart/components/BarChart.Vertical.tsx +38 -28
- package/src/components/BarChart/components/PortionPatternOverlay.tsx +61 -0
- package/src/components/BarChart/components/context.tsx +6 -0
- package/src/components/BarChart/helpers/getBarData.ts +28 -28
- package/src/components/BarChart/helpers/getBarHeights.ts +2 -2
- 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 +202 -0
- package/src/components/BarChart/helpers/useBarChart.ts +60 -60
- 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/BoxPlot.Horizontal.tsx +7 -6
- package/src/components/BoxPlot/BoxPlot.Vertical.tsx +9 -7
- package/src/components/BoxPlot/helpers/index.ts +13 -83
- package/src/components/Brush/BrushSelector.tsx +10 -24
- 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 +1111 -805
- 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 +104 -61
- 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 +236 -36
- 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 +21 -6
- 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 +5 -4
- package/src/components/EditorPanel/useEditorPermissions.test.tsx +94 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +75 -25
- package/src/components/Forecasting/Forecasting.tsx +16 -7
- 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 +163 -148
- package/src/components/Legend/Legend.Suppression.tsx +3 -3
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +7 -0
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +47 -35
- 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 -6
- package/src/components/LineChart/index.tsx +71 -27
- 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 +85 -54
- 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 +121 -13
- package/src/components/PieChart/helpers/labelPlacement.test.ts +88 -0
- package/src/components/PieChart/helpers/labelPlacement.ts +95 -0
- 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 -12
- 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 +79 -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/categoryOrder.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 +26 -38
- package/src/helpers/getChartTypeDefaultPalette.ts +29 -0
- package/src/helpers/getColorCodeCategoryColorMap.ts +26 -0
- package/src/helpers/getColorScale.ts +31 -22
- package/src/helpers/getComboChartConfig.ts +0 -1
- package/src/helpers/getDynamicYAxisCategories.ts +69 -0
- package/src/helpers/getExcludedData.ts +0 -1
- package/src/helpers/getHasBoundarySuppression.ts +4 -3
- package/src/helpers/getMinMax.ts +11 -8
- package/src/helpers/getNewRuntime.ts +4 -3
- package/src/helpers/getPiePercent.ts +3 -3
- package/src/helpers/getStackedSeriesMax.ts +51 -0
- package/src/helpers/getV21ChartDistributionColors.ts +57 -0
- package/src/helpers/handleRankByValue.ts +15 -15
- package/src/helpers/isConvertLineToBarGraph.ts +2 -2
- package/src/helpers/seriesColumnSettings.ts +31 -3
- package/src/helpers/sizeHelpers.ts +4 -5
- 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/categoryOrder.test.ts +57 -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 +83 -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/useRightAxis.test.tsx +143 -2
- package/src/hooks/tests/useScales.test.tsx +3 -6
- package/src/hooks/tests/useTooltip.test.tsx +484 -0
- package/src/hooks/usePrefersReducedMotion.ts +22 -0
- package/src/hooks/useReduceData.ts +4 -36
- package/src/hooks/useRightAxis.ts +19 -26
- package/src/hooks/useScales.ts +15 -0
- package/src/hooks/useTooltip.tsx +106 -71
- package/src/scss/main.scss +74 -2
- package/src/store/chart.reducer.ts +4 -4
- package/src/test/CdcChart.dataTable.test.tsx +582 -14
- 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,179 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render } from '@testing-library/react'
|
|
3
|
+
import { scaleBand, scaleTime } from '@visx/scale'
|
|
4
|
+
import { describe, expect, it } from 'vitest'
|
|
5
|
+
import ConfigContext from '../../../ConfigContext'
|
|
6
|
+
import { createMockChartContext } from '../../LinearChart/tests/mockConfigContext'
|
|
7
|
+
import { RegionFills, useRegionLayouts, type RegionBoundaryMode } from './Regions'
|
|
8
|
+
|
|
9
|
+
const regions = [
|
|
10
|
+
{
|
|
11
|
+
from: 'A',
|
|
12
|
+
to: 'B',
|
|
13
|
+
label: 'Region',
|
|
14
|
+
background: '#005ea8',
|
|
15
|
+
color: '#1b1b1b'
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
const RegionHarness = ({ xScale, seriesScale, boundaryMode, xMax = 300 }) => {
|
|
20
|
+
const regionLayout = useRegionLayouts({ xScale, seriesScale, boundaryMode, xMax })
|
|
21
|
+
return (
|
|
22
|
+
<svg>
|
|
23
|
+
<RegionFills layouts={regionLayout.layouts} yMax={200} />
|
|
24
|
+
</svg>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const renderRegion = ({
|
|
29
|
+
boundaryMode,
|
|
30
|
+
xScale,
|
|
31
|
+
seriesScale,
|
|
32
|
+
xMax = 300,
|
|
33
|
+
configOverrides = {}
|
|
34
|
+
}: {
|
|
35
|
+
boundaryMode: RegionBoundaryMode
|
|
36
|
+
xScale: any
|
|
37
|
+
seriesScale?: any
|
|
38
|
+
xMax?: number
|
|
39
|
+
configOverrides?: any
|
|
40
|
+
}) => {
|
|
41
|
+
const context = createMockChartContext({
|
|
42
|
+
regions,
|
|
43
|
+
orientation: 'vertical',
|
|
44
|
+
xAxis: {
|
|
45
|
+
...createMockChartContext().config.xAxis,
|
|
46
|
+
type: 'categorical'
|
|
47
|
+
},
|
|
48
|
+
...configOverrides
|
|
49
|
+
})
|
|
50
|
+
const result = render(
|
|
51
|
+
<ConfigContext.Provider value={context}>
|
|
52
|
+
<RegionHarness xScale={xScale} seriesScale={seriesScale} boundaryMode={boundaryMode} xMax={xMax} />
|
|
53
|
+
</ConfigContext.Provider>
|
|
54
|
+
)
|
|
55
|
+
return result.container.querySelector('.region-fill')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe('scale-owned region layouts', () => {
|
|
59
|
+
it('uses the complete outer category allocation for band-boundary regions', () => {
|
|
60
|
+
const xScale = scaleBand({ domain: ['A', 'B', 'C'], range: [0, 300], padding: 0 })
|
|
61
|
+
const fill = renderRegion({
|
|
62
|
+
boundaryMode: 'band',
|
|
63
|
+
xScale,
|
|
64
|
+
seriesScale: scaleBand({ domain: ['one', 'two'], range: [0, 100] })
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
expect(Number(fill?.getAttribute('x'))).toBe(0)
|
|
68
|
+
expect(Number(fill?.getAttribute('width'))).toBe(200)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('uses category centers for point-mark regions', () => {
|
|
72
|
+
const xScale = scaleBand({ domain: ['A', 'B', 'C'], range: [0, 300], padding: 0 })
|
|
73
|
+
const fill = renderRegion({ boundaryMode: 'point', xScale })
|
|
74
|
+
|
|
75
|
+
expect(Number(fill?.getAttribute('x'))).toBe(50)
|
|
76
|
+
expect(Number(fill?.getAttribute('width'))).toBe(100)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('uses the centered series-scale range for date-time bar groups', () => {
|
|
80
|
+
const first = new Date('2024-01-01').getTime()
|
|
81
|
+
const second = new Date('2024-01-08').getTime()
|
|
82
|
+
const xScale = scaleTime({ domain: [first, second], range: [50, 250] })
|
|
83
|
+
const seriesScale = scaleBand({ domain: ['one', 'two'], range: [0, 40], padding: 0 })
|
|
84
|
+
const pointFill = renderRegion({
|
|
85
|
+
boundaryMode: 'point',
|
|
86
|
+
xScale,
|
|
87
|
+
configOverrides: {
|
|
88
|
+
regions: [{ ...regions[0], from: '2024-01-01', to: '2024-01-08' }],
|
|
89
|
+
xAxis: {
|
|
90
|
+
...createMockChartContext().config.xAxis,
|
|
91
|
+
type: 'date-time',
|
|
92
|
+
dateParseFormat: '%Y-%m-%d'
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
const bandFill = renderRegion({
|
|
97
|
+
boundaryMode: 'band',
|
|
98
|
+
xScale,
|
|
99
|
+
seriesScale,
|
|
100
|
+
configOverrides: {
|
|
101
|
+
regions: [{ ...regions[0], from: '2024-01-01', to: '2024-01-08' }],
|
|
102
|
+
xAxis: {
|
|
103
|
+
...createMockChartContext().config.xAxis,
|
|
104
|
+
type: 'date-time',
|
|
105
|
+
dateParseFormat: '%Y-%m-%d'
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
expect(Number(bandFill?.getAttribute('x'))).toBe(Number(pointFill?.getAttribute('x')) - 20)
|
|
111
|
+
expect(Number(bandFill?.getAttribute('width'))).toBe(Number(pointFill?.getAttribute('width')) + 40)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('ends date-time Last Date bar regions at the final bar group edge', () => {
|
|
115
|
+
const first = new Date('2024-01-01').getTime()
|
|
116
|
+
const second = new Date('2024-01-08').getTime()
|
|
117
|
+
const xScale = scaleTime({ domain: [first, second], range: [50, 250] })
|
|
118
|
+
const seriesScale = scaleBand({ domain: ['one', 'two'], range: [0, 40], padding: 0 })
|
|
119
|
+
const fill = renderRegion({
|
|
120
|
+
boundaryMode: 'band',
|
|
121
|
+
xScale,
|
|
122
|
+
seriesScale,
|
|
123
|
+
configOverrides: {
|
|
124
|
+
regions: [
|
|
125
|
+
{
|
|
126
|
+
...regions[0],
|
|
127
|
+
from: '2024-01-01T00:00:00',
|
|
128
|
+
to: '',
|
|
129
|
+
toType: 'Last Date'
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
xAxis: {
|
|
133
|
+
...createMockChartContext().config.xAxis,
|
|
134
|
+
type: 'date-time',
|
|
135
|
+
dateParseFormat: '%Y-%m-%d'
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const expectedFrom = Number(xScale(first)) - 20
|
|
141
|
+
const expectedTo = Number(xScale(second)) + 20
|
|
142
|
+
|
|
143
|
+
expect(Number(fill?.getAttribute('x'))).toBe(expectedFrom)
|
|
144
|
+
expect(Number(fill?.getAttribute('width'))).toBe(expectedTo - expectedFrom)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('ends Last Date regions at the final date band edge instead of the plot edge', () => {
|
|
148
|
+
const first = new Date('2024-01-01').getTime()
|
|
149
|
+
const second = new Date('2024-01-08').getTime()
|
|
150
|
+
const third = new Date('2024-01-15').getTime()
|
|
151
|
+
const xScale = scaleBand({ domain: [first, second, third], range: [0, 300], padding: 0.2 })
|
|
152
|
+
const fill = renderRegion({
|
|
153
|
+
boundaryMode: 'band',
|
|
154
|
+
xScale,
|
|
155
|
+
configOverrides: {
|
|
156
|
+
regions: [
|
|
157
|
+
{
|
|
158
|
+
...regions[0],
|
|
159
|
+
from: '2024-01-01',
|
|
160
|
+
to: '',
|
|
161
|
+
toType: 'Last Date'
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
xAxis: {
|
|
165
|
+
...createMockChartContext().config.xAxis,
|
|
166
|
+
type: 'date',
|
|
167
|
+
dateParseFormat: '%Y-%m-%d'
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
const expectedFrom = Number(xScale(first))
|
|
173
|
+
const expectedTo = Number(xScale(third)) + xScale.bandwidth()
|
|
174
|
+
|
|
175
|
+
expect(Number(fill?.getAttribute('x'))).toBe(expectedFrom)
|
|
176
|
+
expect(Number(fill?.getAttribute('width'))).toBeCloseTo(expectedTo - expectedFrom)
|
|
177
|
+
expect(expectedTo).toBeLessThan(300)
|
|
178
|
+
})
|
|
179
|
+
})
|
|
@@ -7,15 +7,6 @@ import { formatDate } from '@cdc/core/helpers/cove/date.js'
|
|
|
7
7
|
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
8
8
|
import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
9
9
|
|
|
10
|
-
// Constants for visualization types
|
|
11
|
-
const VIZ_TYPES = {
|
|
12
|
-
BAR: 'Bar',
|
|
13
|
-
LINE: 'Line',
|
|
14
|
-
AREA: 'Area Chart',
|
|
15
|
-
COMBO: 'Combo',
|
|
16
|
-
HORIZON: 'Horizon Chart'
|
|
17
|
-
} as const
|
|
18
|
-
|
|
19
10
|
const DEFAULT_REGION_BACKGROUND = 'var(--cool-gray-50, #71767a)'
|
|
20
11
|
const TICK_LABEL_FONT_SIZE = 16
|
|
21
12
|
const TICK_LABEL_FONT_SIZE_SMALL = 13
|
|
@@ -25,6 +16,7 @@ const REGION_LABEL_MAX_PLOT_WIDTH_RATIO = 1 / 3
|
|
|
25
16
|
const REGION_LABEL_COMFORTABLE_MIN_WIDTH = 120
|
|
26
17
|
const REGION_LABEL_COMFORTABLE_MIN_WIDTH_SMALL = 96
|
|
27
18
|
const REGION_LABEL_FONT_FAMILY = 'Nunito, sans-serif'
|
|
19
|
+
const SUPPORTED_REGION_VISUALIZATION_TYPES: readonly string[] = ['Line', 'Area Chart', 'Horizon Chart', 'Bar', 'Combo']
|
|
28
20
|
|
|
29
21
|
type Region = {
|
|
30
22
|
from: string
|
|
@@ -42,14 +34,30 @@ type XScale = {
|
|
|
42
34
|
bandwidth?: () => number
|
|
43
35
|
}
|
|
44
36
|
|
|
45
|
-
type
|
|
37
|
+
type RegionScale = {
|
|
38
|
+
range: () => number[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type RegionBoundaryMode = 'point' | 'band'
|
|
42
|
+
|
|
43
|
+
type RegionLayoutProps = {
|
|
46
44
|
xScale: XScale
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
totalBarsInGroup?: number
|
|
45
|
+
seriesScale?: RegionScale
|
|
46
|
+
boundaryMode: RegionBoundaryMode
|
|
50
47
|
xMax?: number
|
|
51
48
|
}
|
|
52
49
|
|
|
50
|
+
type RegionFillProps = {
|
|
51
|
+
layouts: RegionLayout[]
|
|
52
|
+
yMax: number
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type RegionLabelsProps = {
|
|
56
|
+
layouts: RegionLayout[]
|
|
57
|
+
regionLabelFontSize: number
|
|
58
|
+
isMobileViewport: boolean
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
type HighlightedAreaProps = {
|
|
54
62
|
x: number
|
|
55
63
|
width: number
|
|
@@ -62,6 +70,13 @@ type RegionLabelLayout = {
|
|
|
62
70
|
width: number
|
|
63
71
|
}
|
|
64
72
|
|
|
73
|
+
export type RegionLayout = {
|
|
74
|
+
region: Region
|
|
75
|
+
clippedFrom: number
|
|
76
|
+
width: number
|
|
77
|
+
chartEnd: number
|
|
78
|
+
}
|
|
79
|
+
|
|
65
80
|
type RegionLabelProps = {
|
|
66
81
|
label: string
|
|
67
82
|
color: string
|
|
@@ -73,7 +88,15 @@ type RegionLabelProps = {
|
|
|
73
88
|
}
|
|
74
89
|
|
|
75
90
|
const HighlightedArea: React.FC<HighlightedAreaProps> = ({ x, width, yMax, background }) => (
|
|
76
|
-
<rect
|
|
91
|
+
<rect
|
|
92
|
+
className='region-fill'
|
|
93
|
+
x={x}
|
|
94
|
+
y={0}
|
|
95
|
+
width={width}
|
|
96
|
+
height={yMax}
|
|
97
|
+
fill={background || DEFAULT_REGION_BACKGROUND}
|
|
98
|
+
opacity={0.3}
|
|
99
|
+
/>
|
|
77
100
|
)
|
|
78
101
|
|
|
79
102
|
const clamp = (value: number, min: number, max: number): number => Math.min(Math.max(value, min), max)
|
|
@@ -147,6 +170,7 @@ const RegionLabel: React.FC<RegionLabelProps> = ({
|
|
|
147
170
|
|
|
148
171
|
return (
|
|
149
172
|
<Text
|
|
173
|
+
className='region-label'
|
|
150
174
|
x={labelLayout.x}
|
|
151
175
|
y={getRegionLabelY(fontSize)}
|
|
152
176
|
width={labelLayout.width}
|
|
@@ -177,22 +201,20 @@ const findClosestDate = <T,>(targetTime: number, domain: T[], getTime: (d: T) =>
|
|
|
177
201
|
return closest
|
|
178
202
|
}
|
|
179
203
|
|
|
180
|
-
/** Check if visualization type is line-like (Line, Area Chart, or Horizon Chart) */
|
|
181
|
-
const isLineLike = (type: string): boolean =>
|
|
182
|
-
type === VIZ_TYPES.LINE || type === VIZ_TYPES.AREA || type === VIZ_TYPES.HORIZON
|
|
183
|
-
|
|
184
|
-
/** Check if visualization type is bar-like (Bar or Combo) */
|
|
185
|
-
const isBarLike = (type: string): boolean => type === VIZ_TYPES.BAR || type === VIZ_TYPES.COMBO
|
|
186
|
-
|
|
187
204
|
// TODO: should regions be removed on categorical axis?
|
|
188
|
-
const
|
|
205
|
+
export const useRegionLayouts = ({ xScale, seriesScale, boundaryMode, xMax }: RegionLayoutProps) => {
|
|
189
206
|
const { parseDate, config, tableData, vizViewport } = useContext<ChartContext>(ConfigContext)
|
|
190
207
|
|
|
191
|
-
const { regions,
|
|
208
|
+
const { regions, orientation, xAxis } = config
|
|
192
209
|
const isMobileViewport = isMobileFontViewport(vizViewport || 'lg')
|
|
193
210
|
const regionLabelFontSize = isMobileViewport ? TICK_LABEL_FONT_SIZE_SMALL : TICK_LABEL_FONT_SIZE
|
|
194
211
|
|
|
195
|
-
const
|
|
212
|
+
const getBandWidth = (): number => xScale.bandwidth?.() || 0
|
|
213
|
+
const getDateTimeBandOffset = (): number => {
|
|
214
|
+
const range = seriesScale?.range?.() || []
|
|
215
|
+
if (range.length < 2) return 0
|
|
216
|
+
return Math.abs(Number(range[range.length - 1]) - Number(range[0])) / 2
|
|
217
|
+
}
|
|
196
218
|
|
|
197
219
|
// ============================================
|
|
198
220
|
// HELPER FUNCTIONS FOR PREVIOUS DAYS
|
|
@@ -374,7 +396,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
374
396
|
return calculateBarLastDatePosition_Categorical()
|
|
375
397
|
}
|
|
376
398
|
let to = xScale(region.to)
|
|
377
|
-
return to +
|
|
399
|
+
return to + getBandWidth()
|
|
378
400
|
}
|
|
379
401
|
|
|
380
402
|
const getBarFromValue_Date = (region: Region): number => {
|
|
@@ -403,7 +425,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
403
425
|
const closestDate = findClosestDate(parsedDate, domain, d => d)
|
|
404
426
|
let to = xScale(closestDate)
|
|
405
427
|
|
|
406
|
-
return to +
|
|
428
|
+
return to + getBandWidth()
|
|
407
429
|
}
|
|
408
430
|
|
|
409
431
|
const getBarFromValue_DateTime = (region: Region): number => {
|
|
@@ -415,7 +437,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
415
437
|
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date, config.locale)).getTime()
|
|
416
438
|
from = xScale(parsedDate)
|
|
417
439
|
}
|
|
418
|
-
return from -
|
|
440
|
+
return from - getDateTimeBandOffset()
|
|
419
441
|
}
|
|
420
442
|
|
|
421
443
|
const getBarToValue_DateTime = (region: Region): number => {
|
|
@@ -423,14 +445,10 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
423
445
|
return calculateBarLastDatePosition_DateTime()
|
|
424
446
|
}
|
|
425
447
|
let to = xScale(parseDate(region.to).getTime())
|
|
426
|
-
return to
|
|
448
|
+
return to + getDateTimeBandOffset()
|
|
427
449
|
}
|
|
428
450
|
|
|
429
451
|
const calculateBarLastDatePosition_Categorical = (): number => {
|
|
430
|
-
const domain = xScale.domain()
|
|
431
|
-
const lastDate = domain[domain.length - 1]
|
|
432
|
-
const lastDatePosition = xScale(lastDate)
|
|
433
|
-
const bandwidth = xScale.bandwidth ? xScale.bandwidth() : 0
|
|
434
452
|
// For categorical bars, extend to the end of the last bar
|
|
435
453
|
// Don't add chartStart - xScale already returns positions in the chart coordinate space
|
|
436
454
|
return xMax
|
|
@@ -440,18 +458,14 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
440
458
|
const domain = xScale.domain()
|
|
441
459
|
const lastDate = domain[domain.length - 1]
|
|
442
460
|
const lastDatePosition = xScale(lastDate)
|
|
443
|
-
|
|
444
|
-
// Don't add chartStart - xScale already returns positions in chart coordinate space
|
|
445
|
-
return Number(lastDatePosition + offset)
|
|
461
|
+
return Number(lastDatePosition) + getBandWidth()
|
|
446
462
|
}
|
|
447
463
|
|
|
448
464
|
const calculateBarLastDatePosition_DateTime = (): number => {
|
|
449
465
|
const domain = xScale.domain()
|
|
450
466
|
const lastDate = domain[domain.length - 1]
|
|
451
467
|
const lastDatePosition = xScale(lastDate)
|
|
452
|
-
|
|
453
|
-
// Also don't subtract barOffset since we want to extend to the edge
|
|
454
|
-
return Number(lastDatePosition)
|
|
468
|
+
return Number(lastDatePosition) + getDateTimeBandOffset()
|
|
455
469
|
}
|
|
456
470
|
|
|
457
471
|
const isRegionInVisibleDomain = (region: Region): boolean => {
|
|
@@ -484,11 +498,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
484
498
|
// ============================================
|
|
485
499
|
|
|
486
500
|
const getFromValue = (region: Region): number => {
|
|
487
|
-
|
|
488
|
-
const isBar = isBarLike(visualizationType)
|
|
489
|
-
|
|
490
|
-
// LINE/AREA CHARTS
|
|
491
|
-
if (isLine) {
|
|
501
|
+
if (boundaryMode === 'point') {
|
|
492
502
|
if (xAxis.type === 'categorical') {
|
|
493
503
|
return getLineFromValue_Categorical(region)
|
|
494
504
|
} else if (xAxis.type === 'date') {
|
|
@@ -498,8 +508,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
498
508
|
}
|
|
499
509
|
}
|
|
500
510
|
|
|
501
|
-
|
|
502
|
-
if (isBar) {
|
|
511
|
+
if (boundaryMode === 'band') {
|
|
503
512
|
if (xAxis.type === 'categorical') {
|
|
504
513
|
return getBarFromValue_Categorical(region)
|
|
505
514
|
} else if (xAxis.type === 'date') {
|
|
@@ -513,11 +522,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
513
522
|
}
|
|
514
523
|
|
|
515
524
|
const getToValue = (region: Region): number => {
|
|
516
|
-
|
|
517
|
-
const isBar = isBarLike(visualizationType)
|
|
518
|
-
|
|
519
|
-
// LINE/AREA CHARTS
|
|
520
|
-
if (isLine) {
|
|
525
|
+
if (boundaryMode === 'point') {
|
|
521
526
|
if (xAxis.type === 'categorical') {
|
|
522
527
|
return getLineToValue_Categorical(region)
|
|
523
528
|
} else if (xAxis.type === 'date') {
|
|
@@ -527,8 +532,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
527
532
|
}
|
|
528
533
|
}
|
|
529
534
|
|
|
530
|
-
|
|
531
|
-
if (isBar) {
|
|
535
|
+
if (boundaryMode === 'band') {
|
|
532
536
|
if (xAxis.type === 'categorical') {
|
|
533
537
|
return getBarToValue_Categorical(region)
|
|
534
538
|
} else if (xAxis.type === 'date') {
|
|
@@ -543,12 +547,18 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
543
547
|
|
|
544
548
|
const getWidth = (to: number, from: number): number => Math.max(0, to - from)
|
|
545
549
|
|
|
546
|
-
if (
|
|
550
|
+
if (
|
|
551
|
+
!SUPPORTED_REGION_VISUALIZATION_TYPES.includes(config.visualizationType) ||
|
|
552
|
+
!regions ||
|
|
553
|
+
orientation !== 'vertical'
|
|
554
|
+
) {
|
|
555
|
+
return { layouts: [], regionLabelFontSize, isMobileViewport }
|
|
556
|
+
}
|
|
547
557
|
|
|
548
558
|
const chartStart = 0
|
|
549
559
|
const chartEnd = xMax !== undefined ? xMax : 1000
|
|
550
560
|
|
|
551
|
-
|
|
561
|
+
const layouts = regions.map((region: Region): RegionLayout | null => {
|
|
552
562
|
if (!isRegionInVisibleDomain(region)) return null
|
|
553
563
|
|
|
554
564
|
const from = getFromValue(region)
|
|
@@ -566,21 +576,46 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
566
576
|
|
|
567
577
|
if (width <= 0) return null
|
|
568
578
|
|
|
569
|
-
return
|
|
570
|
-
<Group height={100} className='regions regions-group--line' key={region.label} pointerEvents='none'>
|
|
571
|
-
<HighlightedArea x={clippedFrom} width={width} yMax={yMax} background={region.background} />
|
|
572
|
-
<RegionLabel
|
|
573
|
-
label={region.label}
|
|
574
|
-
color={region.color}
|
|
575
|
-
clippedFrom={clippedFrom}
|
|
576
|
-
regionWidth={width}
|
|
577
|
-
plotWidth={chartEnd}
|
|
578
|
-
fontSize={regionLabelFontSize}
|
|
579
|
-
isMobileViewport={isMobileViewport}
|
|
580
|
-
/>
|
|
581
|
-
</Group>
|
|
582
|
-
)
|
|
579
|
+
return { region, clippedFrom, width, chartEnd }
|
|
583
580
|
})
|
|
581
|
+
|
|
582
|
+
return {
|
|
583
|
+
layouts: layouts.filter((layout): layout is RegionLayout => layout !== null),
|
|
584
|
+
regionLabelFontSize,
|
|
585
|
+
isMobileViewport
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export const RegionFills: React.FC<RegionFillProps> = ({ layouts, yMax }) => {
|
|
590
|
+
return layouts.map(({ region, clippedFrom, width }, index) => (
|
|
591
|
+
<Group
|
|
592
|
+
height={100}
|
|
593
|
+
className='regions regions-group--line regions-layer--fill'
|
|
594
|
+
key={`${region.label}-${index}`}
|
|
595
|
+
pointerEvents='none'
|
|
596
|
+
>
|
|
597
|
+
<HighlightedArea x={clippedFrom} width={width} yMax={yMax} background={region.background} />
|
|
598
|
+
</Group>
|
|
599
|
+
))
|
|
584
600
|
}
|
|
585
601
|
|
|
586
|
-
export
|
|
602
|
+
export const RegionLabels: React.FC<RegionLabelsProps> = ({ layouts, regionLabelFontSize, isMobileViewport }) => {
|
|
603
|
+
return layouts.map(({ region, clippedFrom, width, chartEnd }, index) => (
|
|
604
|
+
<Group
|
|
605
|
+
height={100}
|
|
606
|
+
className='regions regions-group--line regions-layer--label'
|
|
607
|
+
key={`${region.label}-${index}`}
|
|
608
|
+
pointerEvents='none'
|
|
609
|
+
>
|
|
610
|
+
<RegionLabel
|
|
611
|
+
label={region.label}
|
|
612
|
+
color={region.color}
|
|
613
|
+
clippedFrom={clippedFrom}
|
|
614
|
+
regionWidth={width}
|
|
615
|
+
plotWidth={chartEnd}
|
|
616
|
+
fontSize={regionLabelFontSize}
|
|
617
|
+
isMobileViewport={isMobileViewport}
|
|
618
|
+
/>
|
|
619
|
+
</Group>
|
|
620
|
+
))
|
|
621
|
+
}
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export default Regions
|
|
1
|
+
export { RegionFills, RegionLabels, useRegionLayouts } from './components/Regions'
|
|
2
|
+
export type { RegionBoundaryMode, RegionLayout } from './components/Regions'
|