@cdc/chart 4.26.1 → 4.26.3
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/CLAUDE.local.md +79 -0
- package/LICENSE +201 -0
- package/dist/{cdcchart-dgT_1dIT.es.js → cdcchart-DQ00cQCm.es.js} +1 -20
- package/dist/cdcchart.js +54742 -49796
- package/examples/data/data-with-metadata.json +10 -0
- package/examples/default.json +378 -0
- package/examples/feature/__data__/horizon-chart-data.json +373 -0
- package/examples/feature/annotations/index.json +3 -6
- package/examples/feature/horizon/horizon-chart.json +395 -0
- package/examples/feature/pie/planet-pie-example-config.json +2 -1
- package/examples/line-chart-states.json +1085 -0
- package/examples/metadata-variables.json +58 -0
- package/examples/private/123.json +694 -0
- package/examples/private/anchor-issue.json +4094 -0
- package/examples/private/backwards-slider.json +10430 -0
- package/examples/private/georgia.csv +160 -0
- package/examples/private/timeline-data.json +1 -0
- package/examples/private/timeline.json +389 -0
- package/examples/radar-chart-simple.json +133 -0
- package/examples/radar-chart.json +148 -0
- package/index.html +1 -31
- package/package.json +57 -59
- package/src/CdcChart.tsx +8 -4
- package/src/CdcChartComponent.tsx +398 -284
- package/src/_stories/Chart.Anchors.stories.tsx +10 -0
- package/src/_stories/Chart.BoxPlot.stories.tsx +7 -0
- package/src/_stories/Chart.CI.stories.tsx +13 -0
- package/src/_stories/Chart.Combo.stories.tsx +17 -0
- package/src/_stories/Chart.CustomColors.stories.tsx +78 -0
- package/src/_stories/Chart.Defaults.stories.tsx +95 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +19 -0
- package/src/_stories/Chart.Filters.stories.tsx +4 -0
- package/src/_stories/Chart.Forecast.stories.tsx +4 -0
- package/src/_stories/Chart.HTMLInDataTable.stories.tsx +22 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +28 -0
- package/src/_stories/Chart.Patterns.stories.tsx +4 -0
- package/src/_stories/Chart.PreserveDecimals.stories.tsx +25 -0
- package/src/_stories/Chart.Regions.Categorical.stories.tsx +13 -0
- package/src/_stories/Chart.Regions.DateScale.stories.tsx +19 -0
- package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +25 -10
- package/src/_stories/Chart.ScatterPlot.stories.tsx +4 -0
- package/src/_stories/Chart.SmallMultiples.stories.tsx +16 -0
- package/src/_stories/Chart.SmallestLeftAxisMax.stories.tsx +64 -0
- package/src/_stories/Chart.stories.tsx +72 -1
- package/src/_stories/Chart.tooltip.stories.tsx +7 -0
- package/src/_stories/ChartAnnotation.stories.tsx +10 -0
- package/src/_stories/ChartAxisLabels.stories.tsx +4 -0
- package/src/_stories/ChartAxisTitles.stories.tsx +10 -0
- package/src/_stories/ChartBar.Editor.stories.tsx +97 -38
- package/src/_stories/ChartBrush.Editor.stories.tsx +11 -25
- package/src/_stories/ChartBrush.Matrix.Continuous.stories.tsx +41 -0
- package/src/_stories/ChartBrush.Matrix.Date.stories.tsx +114 -0
- package/src/_stories/ChartBrush.Matrix.DateTime.stories.tsx +78 -0
- package/src/_stories/ChartBrush.stories.tsx +7 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +1 -1
- package/src/_stories/ChartEditor.stories.tsx +7 -0
- package/src/_stories/ChartLine.QuadrantAngles.stories.tsx +89 -0
- package/src/_stories/ChartLine.Suppression.stories.tsx +7 -0
- package/src/_stories/ChartLine.Symbols.stories.tsx +4 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +46 -1
- package/src/_stories/TechAdoptionWithLinks.stories.tsx +7 -0
- package/src/_stories/_mock/brush_continuous.json +86 -0
- package/src/_stories/_mock/brush_date_large.json +176 -0
- package/src/_stories/_mock/line_chart_angle_near_zero_fall.json +195 -0
- package/src/_stories/_mock/line_chart_angle_near_zero_rise.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q1_steep_upward.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q2_gentle_downward.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q3_steep_downward.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q4_gentle_upward.json +195 -0
- package/src/_stories/_mock/line_chart_quadrant_angles.json +264 -0
- package/src/_stories/_mock/paired-bar-abbr.json +421 -0
- package/src/_stories/_mock/pie_custom_colors.json +268 -0
- package/src/_stories/_mock/smallest_left_axis_max.json +104 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +14 -20
- package/src/components/Annotations/components/AnnotationDraggable.tsx +240 -116
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -2
- package/src/components/Annotations/components/AnnotationDropdown.tsx +8 -12
- package/src/components/Annotations/components/AnnotationList.styles.css +12 -18
- package/src/components/Annotations/components/AnnotationList.tsx +5 -4
- package/src/components/Annotations/components/findNearestDatum.ts +75 -85
- package/src/components/Annotations/helpers/getVisibleAnnotations.ts +38 -0
- package/src/components/Axis/BottomAxis.tsx +277 -0
- package/src/components/Axis/LeftAxis.tsx +404 -0
- package/src/components/Axis/LeftAxisGridlines.tsx +77 -0
- package/src/components/Axis/PairedBarAxis.tsx +192 -0
- package/src/components/Axis/README.md +94 -0
- package/src/components/Axis/RightAxis.tsx +108 -0
- package/src/components/Axis/axis.constants.ts +21 -0
- package/src/components/Axis/index.ts +7 -0
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +12 -28
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -30
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +12 -31
- package/src/components/BarChart/components/BarChart.Vertical.tsx +12 -28
- package/src/components/BarChart/components/BarChart.tsx +7 -1
- package/src/components/BarChart/helpers/getPatternUrl.ts +94 -0
- package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +134 -0
- package/src/components/BarChart/helpers/useBarChart.ts +3 -0
- package/src/components/Brush/BrushSelector.tsx +155 -22
- package/src/components/Brush/MiniChartPreview.tsx +133 -21
- package/src/components/EditorPanel/EditorPanel.tsx +81 -54
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +67 -29
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +0 -78
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +120 -2
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +25 -43
- package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +353 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +83 -3
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +66 -43
- package/src/components/EditorPanel/components/Panels/index.tsx +2 -0
- package/src/components/EditorPanel/editor-panel.scss +1 -1
- package/src/components/EditorPanel/useEditorPermissions.ts +55 -26
- package/src/components/ForestPlot/ForestPlot.tsx +26 -22
- package/src/components/HorizonChart/HorizonChart.tsx +131 -0
- package/src/components/HorizonChart/components/HorizonBand.tsx +160 -0
- package/src/components/HorizonChart/helpers/calculateHorizonBands.ts +27 -0
- package/src/components/HorizonChart/helpers/getHorizonLayerColors.ts +40 -0
- package/src/components/HorizonChart/index.tsx +3 -0
- package/src/components/Legend/Legend.Component.tsx +52 -4
- package/src/components/Legend/Legend.tsx +1 -1
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +4 -4
- package/src/components/Legend/LegendValueRange.tsx +77 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +16 -2
- package/src/components/Legend/helpers/generateValueRanges.ts +92 -0
- package/src/components/LineChart/helpers/README.md +292 -0
- package/src/components/LineChart/helpers/labelPositioning.test.ts +245 -0
- package/src/components/LineChart/helpers/labelPositioning.ts +304 -0
- package/src/components/LineChart/index.tsx +44 -8
- package/src/components/LinearChart/README.md +109 -0
- package/src/components/LinearChart/VisualizationRenderer.tsx +267 -0
- package/src/components/LinearChart/linearChart.constants.ts +84 -0
- package/src/components/LinearChart/tests/LinearChart.test.tsx +278 -0
- package/src/components/LinearChart/tests/mockConfigContext.ts +131 -0
- package/src/components/LinearChart/utils/tickFormatting.ts +146 -0
- package/src/components/LinearChart.tsx +268 -1057
- package/src/components/PieChart/PieChart.tsx +20 -5
- package/src/components/RadarChart/RadarAxis.tsx +78 -0
- package/src/components/RadarChart/RadarChart.tsx +298 -0
- package/src/components/RadarChart/RadarGrid.tsx +64 -0
- package/src/components/RadarChart/RadarPolygon.tsx +91 -0
- package/src/components/RadarChart/helpers.ts +83 -0
- package/src/components/RadarChart/index.tsx +3 -0
- package/src/components/Regions/components/Regions.tsx +6 -6
- package/src/components/Sankey/components/Sankey.tsx +3 -3
- package/src/components/Sankey/sankey.scss +1 -1
- package/src/components/SmallMultiples/SmallMultiples.css +5 -5
- package/src/components/Sparkline/index.scss +4 -2
- package/src/components/WarmingStripes/WarmingStripes.tsx +95 -25
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +8 -8
- package/src/data/initial-state.js +37 -15
- package/src/data/legacy-defaults.ts +18 -0
- package/src/helpers/abbreviateNumber.ts +24 -17
- package/src/helpers/getChartPatternId.ts +17 -0
- package/src/helpers/getExcludedData.ts +4 -0
- package/src/helpers/getMinMax.ts +16 -2
- package/src/helpers/handleChartAriaLabels.ts +19 -19
- package/src/helpers/handleLineType.ts +22 -18
- package/src/helpers/seriesColumnSettings.ts +114 -0
- package/src/helpers/tests/countNumOfTicks.test.ts +77 -0
- package/src/helpers/tests/seriesColumnSettings.test.ts +84 -0
- package/src/hooks/useProgrammaticTooltip.ts +23 -2
- package/src/hooks/useRightAxis.ts +14 -0
- package/src/hooks/useScales.ts +99 -56
- package/src/hooks/useTooltip.tsx +23 -3
- package/src/scss/main.scss +157 -79
- package/src/selectors/README.md +68 -0
- package/src/store/chart.reducer.ts +2 -0
- package/src/test/CdcChart.test.jsx +2 -2
- package/src/types/ChartConfig.ts +22 -0
- package/src/types/ChartContext.ts +1 -0
- package/src/types/Horizon.ts +64 -0
- package/tests/fixtures/chart-config-with-metadata.json +29 -0
- package/tests/fixtures/data-with-metadata.json +10 -0
- package/preview.html +0 -1616
- package/src/components/Annotations/components/helpers/index.tsx +0 -46
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Axis Components
|
|
2
|
+
|
|
3
|
+
This folder contains extracted axis components from `LinearChart.tsx` to improve maintainability and reduce complexity.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
### LeftAxis
|
|
8
|
+
**File:** `LeftAxis.tsx`
|
|
9
|
+
|
|
10
|
+
Renders the left (Y) axis for vertical charts. Handles:
|
|
11
|
+
- Tick formatting and positioning
|
|
12
|
+
- Labels above gridlines option
|
|
13
|
+
- Inline label (suffix) display
|
|
14
|
+
- Forest plot special rendering
|
|
15
|
+
|
|
16
|
+
**Props:**
|
|
17
|
+
- `yScale` - D3 scale for Y axis
|
|
18
|
+
- `xScale` - D3 scale for X axis
|
|
19
|
+
- `yMax`, `xMax` - Chart dimensions
|
|
20
|
+
- `yAxisWidth` - Width allocated for Y axis
|
|
21
|
+
- `numTicks` - Number of ticks to display
|
|
22
|
+
- `handleLeftTickFormatting` - Tick format function
|
|
23
|
+
|
|
24
|
+
### LeftAxisGridlines
|
|
25
|
+
**File:** `LeftAxisGridlines.tsx`
|
|
26
|
+
|
|
27
|
+
Renders horizontal gridlines separately from the axis itself. This separation allows gridlines to be drawn behind the visualization while the axis is drawn on top.
|
|
28
|
+
|
|
29
|
+
### BottomAxis
|
|
30
|
+
**File:** `BottomAxis.tsx`
|
|
31
|
+
|
|
32
|
+
Renders the bottom (X) axis. Handles:
|
|
33
|
+
- Date/time formatting
|
|
34
|
+
- Tick rotation for responsive layouts
|
|
35
|
+
- Manual step configuration
|
|
36
|
+
- Brush integration
|
|
37
|
+
|
|
38
|
+
**Props:**
|
|
39
|
+
- `xScale` - D3 scale for X axis
|
|
40
|
+
- `yMax` - Chart height
|
|
41
|
+
- `xTickCount` - Number of ticks
|
|
42
|
+
- `handleBottomTickFormatting` - Tick format function
|
|
43
|
+
- `useDateSpanMonths` - Date range calculation flag
|
|
44
|
+
|
|
45
|
+
### PairedBarAxis
|
|
46
|
+
**File:** `PairedBarAxis.tsx`
|
|
47
|
+
|
|
48
|
+
Specialized axis for Paired Bar charts with two mirrored AxisBottom components. Handles:
|
|
49
|
+
- Dual scale rendering (g1xScale, g2xScale)
|
|
50
|
+
- Responsive tick rotation
|
|
51
|
+
- Tick overlap detection
|
|
52
|
+
|
|
53
|
+
### RightAxis
|
|
54
|
+
**File:** `RightAxis.tsx`
|
|
55
|
+
|
|
56
|
+
Renders the right (Y) axis for dual-axis charts. Handles:
|
|
57
|
+
- Secondary Y scale
|
|
58
|
+
- Configurable tick/label colors
|
|
59
|
+
- Optional gridlines from right axis
|
|
60
|
+
|
|
61
|
+
### CategoricalYAxis
|
|
62
|
+
**File:** `Categorical.Axis.tsx`
|
|
63
|
+
|
|
64
|
+
Specialized Y axis for categorical data types.
|
|
65
|
+
|
|
66
|
+
## Constants
|
|
67
|
+
**File:** `axis.constants.ts`
|
|
68
|
+
|
|
69
|
+
Shared constants used across axis components.
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
All components are exported from `index.ts`:
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import {
|
|
77
|
+
LeftAxis,
|
|
78
|
+
LeftAxisGridlines,
|
|
79
|
+
BottomAxis,
|
|
80
|
+
PairedBarAxis,
|
|
81
|
+
RightAxis,
|
|
82
|
+
CategoricalYAxis
|
|
83
|
+
} from './Axis'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Architecture Notes
|
|
87
|
+
|
|
88
|
+
These components were extracted from `LinearChart.tsx` as part of a refactoring effort to:
|
|
89
|
+
1. Reduce the main component from 1,704 to ~845 lines
|
|
90
|
+
2. Improve testability through smaller, focused components
|
|
91
|
+
3. Enable reuse across different chart types
|
|
92
|
+
4. Simplify maintenance and debugging
|
|
93
|
+
|
|
94
|
+
Each component accesses the `ConfigContext` for chart configuration rather than receiving all config as props, keeping the prop interfaces focused on rendering-specific data.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
import { AxisRight as VisxAxisRight } from '@visx/axis'
|
|
3
|
+
import { Group } from '@visx/group'
|
|
4
|
+
import { Line } from '@visx/shape'
|
|
5
|
+
import { Text } from '@visx/text'
|
|
6
|
+
import { ScaleLinear } from 'd3-scale'
|
|
7
|
+
|
|
8
|
+
import ConfigContext from '../../ConfigContext'
|
|
9
|
+
|
|
10
|
+
// Constants
|
|
11
|
+
const HORIZONTAL_TICK_OFFSET_ADJUSTMENT = 5
|
|
12
|
+
|
|
13
|
+
type RightAxisProps = {
|
|
14
|
+
yScaleRight: ScaleLinear<number, number>
|
|
15
|
+
yMax: number
|
|
16
|
+
xMax: number
|
|
17
|
+
yAxisWidth: number
|
|
18
|
+
tickLabelFontSize: number
|
|
19
|
+
axisLabelFontSize: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Right Y-axis component for dual-axis charts.
|
|
24
|
+
* Renders a secondary y-axis on the right side with configurable styling.
|
|
25
|
+
* Extracted from LinearChart.tsx
|
|
26
|
+
*/
|
|
27
|
+
export const RightAxis: React.FC<RightAxisProps> = ({
|
|
28
|
+
yScaleRight,
|
|
29
|
+
yMax,
|
|
30
|
+
xMax,
|
|
31
|
+
yAxisWidth,
|
|
32
|
+
tickLabelFontSize,
|
|
33
|
+
axisLabelFontSize
|
|
34
|
+
}) => {
|
|
35
|
+
const { config, formatNumber } = useContext(ConfigContext)
|
|
36
|
+
const { runtime } = config
|
|
37
|
+
|
|
38
|
+
const horizontalTickOffset = (ticks: any[]) =>
|
|
39
|
+
yMax / ticks.length / 2 - (yMax / ticks.length) * (1 - config.barThickness) + HORIZONTAL_TICK_OFFSET_ADJUSTMENT
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<VisxAxisRight
|
|
43
|
+
scale={yScaleRight}
|
|
44
|
+
left={yAxisWidth + xMax}
|
|
45
|
+
label={config.yAxis.rightLabel}
|
|
46
|
+
tickFormat={tick => formatNumber(tick, 'right')}
|
|
47
|
+
numTicks={runtime.yAxis.rightNumTicks || undefined}
|
|
48
|
+
labelOffset={45}
|
|
49
|
+
>
|
|
50
|
+
{props => {
|
|
51
|
+
const axisCenter =
|
|
52
|
+
config.orientation === 'horizontal'
|
|
53
|
+
? (props.axisToPoint.y - props.axisFromPoint.y) / 2
|
|
54
|
+
: (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Group className='right-axis'>
|
|
58
|
+
{props.ticks.map((tick, i) => (
|
|
59
|
+
<Group key={`vx-tick-${tick.value}-${i}`} className='vx-axis-tick'>
|
|
60
|
+
{!runtime.yAxis.rightHideTicks && (
|
|
61
|
+
<Line
|
|
62
|
+
from={tick.from}
|
|
63
|
+
to={tick.to}
|
|
64
|
+
display={runtime.horizontal ? 'none' : 'block'}
|
|
65
|
+
stroke={config.yAxis.rightAxisTickColor}
|
|
66
|
+
/>
|
|
67
|
+
)}
|
|
68
|
+
|
|
69
|
+
{runtime.yAxis.rightGridLines && (
|
|
70
|
+
<Line from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='#d6d6d6' />
|
|
71
|
+
)}
|
|
72
|
+
|
|
73
|
+
{!config.yAxis.rightHideLabel && (
|
|
74
|
+
<Text
|
|
75
|
+
x={tick.to.x}
|
|
76
|
+
y={tick.to.y + (runtime.horizontal ? horizontalTickOffset(props.ticks) : 0)}
|
|
77
|
+
verticalAnchor={runtime.horizontal ? 'start' : 'middle'}
|
|
78
|
+
textAnchor='start'
|
|
79
|
+
fill={config.yAxis.rightAxisTickLabelColor}
|
|
80
|
+
fontSize={tickLabelFontSize}
|
|
81
|
+
>
|
|
82
|
+
{tick.formattedValue}
|
|
83
|
+
</Text>
|
|
84
|
+
)}
|
|
85
|
+
</Group>
|
|
86
|
+
))}
|
|
87
|
+
|
|
88
|
+
{!config.yAxis.rightHideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
89
|
+
|
|
90
|
+
<Text
|
|
91
|
+
className='y-label'
|
|
92
|
+
textAnchor='middle'
|
|
93
|
+
verticalAnchor='start'
|
|
94
|
+
transform={`translate(${config.yAxis.rightLabelOffsetSize || 0}, ${axisCenter}) rotate(-90)`}
|
|
95
|
+
fontWeight='bold'
|
|
96
|
+
fill={config.yAxis.rightAxisLabelColor}
|
|
97
|
+
fontSize={axisLabelFontSize}
|
|
98
|
+
>
|
|
99
|
+
{props.label}
|
|
100
|
+
</Text>
|
|
101
|
+
</Group>
|
|
102
|
+
)
|
|
103
|
+
}}
|
|
104
|
+
</VisxAxisRight>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default RightAxis
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Tick length constants
|
|
2
|
+
export const DEFAULT_TICK_LENGTH = 8
|
|
3
|
+
export const LOGARITHMIC_TICK_LENGTH = 6
|
|
4
|
+
export const MAJOR_LOG_TICK_LENGTH = 7
|
|
5
|
+
|
|
6
|
+
// Tick styling constants
|
|
7
|
+
export const TICK_LABEL_MARGIN_RIGHT = 4.5
|
|
8
|
+
export const MAJOR_LOG_TICK_STROKE_WIDTH = 1.3
|
|
9
|
+
|
|
10
|
+
// Label positioning constants
|
|
11
|
+
export const VALUE_ON_LINE_PADDING_NO_AXIS = -8
|
|
12
|
+
export const VALUE_ON_LINE_PADDING_WITH_AXIS = -12
|
|
13
|
+
export const LABEL_Y_PADDING_ABOVE_GRIDLINES = 4
|
|
14
|
+
export const HORIZONTAL_TICK_OFFSET_ADJUSTMENT = 5
|
|
15
|
+
|
|
16
|
+
// Chart-specific constants
|
|
17
|
+
export const ZERO_LINE_STROKE_WIDTH = 2
|
|
18
|
+
export const BAR_MIN_HEIGHT = 15
|
|
19
|
+
|
|
20
|
+
// Lollipop chart sizes
|
|
21
|
+
export const LOLLIPOP_SIZES = { large: 7, medium: 6, small: 5 } as const
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as CategoricalYAxis } from './Categorical.Axis'
|
|
2
|
+
export { default as LeftAxis } from './LeftAxis'
|
|
3
|
+
export { default as LeftAxisGridlines } from './LeftAxisGridlines'
|
|
4
|
+
export { default as BottomAxis } from './BottomAxis'
|
|
5
|
+
export { default as PairedBarAxis } from './PairedBarAxis'
|
|
6
|
+
export { default as RightAxis } from './RightAxis'
|
|
7
|
+
export * from './axis.constants'
|
|
@@ -24,6 +24,8 @@ import { ChartContext } from '../../../types/ChartContext'
|
|
|
24
24
|
import _ from 'lodash'
|
|
25
25
|
import { getBarData } from '../helpers/getBarData'
|
|
26
26
|
import { getHorizontalBarHeights } from '../helpers/getBarHeights'
|
|
27
|
+
import { getPatternUrl as getPatternUrlForBar } from '../helpers/getPatternUrl'
|
|
28
|
+
import { getChartPatternId } from '../../../helpers/getChartPatternId'
|
|
27
29
|
|
|
28
30
|
const BarChartHorizontal = () => {
|
|
29
31
|
const { xScale, yScale, yMax, seriesScale, barChart } = useContext<BarChartContextValues>(BarChartContext)
|
|
@@ -72,7 +74,7 @@ const BarChartHorizontal = () => {
|
|
|
72
74
|
return (
|
|
73
75
|
<defs>
|
|
74
76
|
{Object.entries(config.legend.patterns).map(([key, pattern]) => {
|
|
75
|
-
const patternId =
|
|
77
|
+
const patternId = getChartPatternId(key)
|
|
76
78
|
const size = pattern.patternSize || 8
|
|
77
79
|
|
|
78
80
|
switch (pattern.shape) {
|
|
@@ -324,33 +326,15 @@ const BarChartHorizontal = () => {
|
|
|
324
326
|
})
|
|
325
327
|
|
|
326
328
|
// Check if this bar should use a pattern
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
// and if the pattern's dataValue matches the current bar's value
|
|
337
|
-
if (pattern.dataKey === bar.key && String(bar.value) === String(pattern.dataValue)) {
|
|
338
|
-
return `url(#chart-pattern-${patternKey})`
|
|
339
|
-
}
|
|
340
|
-
// Fallback for non-grouped charts: check datum field value
|
|
341
|
-
else if (!config.series || config.series.length <= 1) {
|
|
342
|
-
const dataFieldValue = datum[pattern.dataKey]
|
|
343
|
-
if (String(dataFieldValue) === String(pattern.dataValue)) {
|
|
344
|
-
return `url(#chart-pattern-${patternKey})`
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return null
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const patternUrl = getPatternUrl()
|
|
329
|
+
const patternUrl = getPatternUrlForBar({
|
|
330
|
+
patterns: config.legend?.patterns,
|
|
331
|
+
datum,
|
|
332
|
+
seriesKey: bar.key,
|
|
333
|
+
seriesValue: bar.value,
|
|
334
|
+
seriesLabels: config.runtime?.seriesLabels,
|
|
335
|
+
seriesKeys: config.series?.map(series => series.dataKey),
|
|
336
|
+
allowNonSeriesFieldMatch: !config.series || config.series.length <= 1
|
|
337
|
+
})
|
|
354
338
|
const baseBackground = getBarBackgroundColor()
|
|
355
339
|
|
|
356
340
|
return (
|
|
@@ -14,6 +14,8 @@ import { type ChartContext } from '../../../types/ChartContext'
|
|
|
14
14
|
|
|
15
15
|
import createBarElement from '@cdc/core/components/createBarElement'
|
|
16
16
|
import { getHorizontalBarHeights } from '../helpers/getBarHeights'
|
|
17
|
+
import { getPatternUrl as getPatternUrlForBar } from '../helpers/getPatternUrl'
|
|
18
|
+
import { getChartPatternId } from '../../../helpers/getChartPatternId'
|
|
17
19
|
|
|
18
20
|
const BarChartStackedHorizontal = () => {
|
|
19
21
|
const { yMax, yScale, xScale, barChart } = useContext<BarChartContextValues>(BarChartContext)
|
|
@@ -55,7 +57,7 @@ const BarChartStackedHorizontal = () => {
|
|
|
55
57
|
return (
|
|
56
58
|
<defs>
|
|
57
59
|
{Object.entries(config.legend.patterns).map(([key, pattern]) => {
|
|
58
|
-
const patternId =
|
|
60
|
+
const patternId = getChartPatternId(key)
|
|
59
61
|
const size = pattern.patternSize || 8
|
|
60
62
|
|
|
61
63
|
switch (pattern.shape) {
|
|
@@ -166,35 +168,15 @@ const BarChartStackedHorizontal = () => {
|
|
|
166
168
|
</li></ul>`
|
|
167
169
|
|
|
168
170
|
// Check if this bar should use a pattern
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// and if the pattern's dataValue matches the current bar's value
|
|
179
|
-
const barValue = data[bar.index][bar.key]
|
|
180
|
-
if (pattern.dataKey === bar.key && String(barValue) === String(pattern.dataValue)) {
|
|
181
|
-
return `url(#chart-pattern-${patternKey})`
|
|
182
|
-
}
|
|
183
|
-
// Fallback for non-series pattern matching (like the original stacked pattern test)
|
|
184
|
-
// Only check this if the pattern dataKey is NOT a series key
|
|
185
|
-
else if (!config.runtime.seriesLabels || !config.runtime.seriesLabels[pattern.dataKey]) {
|
|
186
|
-
const dataFieldValue = data[bar.index][pattern.dataKey]
|
|
187
|
-
if (String(dataFieldValue) === String(pattern.dataValue)) {
|
|
188
|
-
return `url(#chart-pattern-${patternKey})`
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return null
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const patternUrl = getPatternUrl()
|
|
171
|
+
const patternUrl = getPatternUrlForBar({
|
|
172
|
+
patterns: config.legend?.patterns,
|
|
173
|
+
datum: data[bar.index],
|
|
174
|
+
seriesKey: bar.key,
|
|
175
|
+
seriesValue: data[bar.index][bar.key],
|
|
176
|
+
seriesLabels: config.runtime?.seriesLabels,
|
|
177
|
+
seriesKeys: config.series?.map(series => series.dataKey),
|
|
178
|
+
allowNonSeriesFieldMatch: true
|
|
179
|
+
})
|
|
198
180
|
|
|
199
181
|
return (
|
|
200
182
|
<React.Fragment key={`stack-${stackIndex}-bar-${index}-${barStack.index}`}>
|
|
@@ -8,6 +8,8 @@ import Regions from '../../Regions'
|
|
|
8
8
|
import { addMinimumBarHeights } from '../helpers'
|
|
9
9
|
|
|
10
10
|
import createBarElement from '@cdc/core/components/createBarElement'
|
|
11
|
+
import { getPatternUrl as getPatternUrlForBar } from '../helpers/getPatternUrl'
|
|
12
|
+
import { getChartPatternId } from '../../../helpers/getChartPatternId'
|
|
11
13
|
|
|
12
14
|
const BarChartStackedVertical = () => {
|
|
13
15
|
const [barWidth, setBarWidth] = useState(0)
|
|
@@ -40,7 +42,7 @@ const BarChartStackedVertical = () => {
|
|
|
40
42
|
return (
|
|
41
43
|
<defs>
|
|
42
44
|
{Object.entries(config.legend.patterns).map(([key, pattern]) => {
|
|
43
|
-
const patternId =
|
|
45
|
+
const patternId = getChartPatternId(key)
|
|
44
46
|
const size = pattern.patternSize || 8
|
|
45
47
|
|
|
46
48
|
switch (pattern.shape) {
|
|
@@ -150,36 +152,15 @@ const BarChartStackedVertical = () => {
|
|
|
150
152
|
setBarWidth(barThickness)
|
|
151
153
|
|
|
152
154
|
// Check if this bar should use a pattern
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// For stacked bar charts, check if the pattern's dataKey matches the current bar's series key
|
|
163
|
-
// and if the pattern's dataValue matches the current bar's value
|
|
164
|
-
const barValue = bar.bar.data[bar.key]
|
|
165
|
-
if (pattern.dataKey === bar.key && String(barValue) === String(pattern.dataValue)) {
|
|
166
|
-
return `url(#chart-pattern-${patternKey})`
|
|
167
|
-
}
|
|
168
|
-
// Fallback for non-series pattern matching (like the original stacked pattern test)
|
|
169
|
-
// Only check this if the pattern dataKey is NOT a series key
|
|
170
|
-
else if (!config.runtime.seriesLabels || !config.runtime.seriesLabels[pattern.dataKey]) {
|
|
171
|
-
const dataFieldValue = bar.bar.data[pattern.dataKey]
|
|
172
|
-
if (String(dataFieldValue) === String(pattern.dataValue)) {
|
|
173
|
-
return `url(#chart-pattern-${patternKey})`
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return null
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const patternUrl = getPatternUrl()
|
|
155
|
+
const patternUrl = getPatternUrlForBar({
|
|
156
|
+
patterns: config.legend?.patterns,
|
|
157
|
+
datum: bar.bar.data,
|
|
158
|
+
seriesKey: bar.key,
|
|
159
|
+
seriesValue: bar.bar.data[bar.key],
|
|
160
|
+
seriesLabels: config.runtime?.seriesLabels,
|
|
161
|
+
seriesKeys: config.series?.map(series => series.dataKey),
|
|
162
|
+
allowNonSeriesFieldMatch: true
|
|
163
|
+
})
|
|
183
164
|
|
|
184
165
|
return (
|
|
185
166
|
<Group key={`${barStack.index}--${bar.index}--${orientation}`}>
|
|
@@ -21,6 +21,8 @@ import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
|
21
21
|
import { type ChartContext } from '../../../types/ChartContext'
|
|
22
22
|
import _ from 'lodash'
|
|
23
23
|
import { getBarData } from '../helpers/getBarData'
|
|
24
|
+
import { getPatternUrl as getPatternUrlForBar } from '../helpers/getPatternUrl'
|
|
25
|
+
import { getChartPatternId } from '../../../helpers/getChartPatternId'
|
|
24
26
|
|
|
25
27
|
const BarChartVertical = () => {
|
|
26
28
|
const { xScale, yScale, xMax, yMax, seriesScale, convertLineToBarGraph, barChart } =
|
|
@@ -86,7 +88,7 @@ const BarChartVertical = () => {
|
|
|
86
88
|
return (
|
|
87
89
|
<defs>
|
|
88
90
|
{Object.entries(config.legend.patterns).map(([key, pattern]) => {
|
|
89
|
-
const patternId =
|
|
91
|
+
const patternId = getChartPatternId(key)
|
|
90
92
|
const size = pattern.patternSize || 8
|
|
91
93
|
|
|
92
94
|
switch (pattern.shape) {
|
|
@@ -325,33 +327,15 @@ const BarChartVertical = () => {
|
|
|
325
327
|
}
|
|
326
328
|
|
|
327
329
|
// Check if this bar should use a pattern
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
// and if the pattern's dataValue matches the current bar's value
|
|
338
|
-
if (pattern.dataKey === bar.key && String(bar.value) === String(pattern.dataValue)) {
|
|
339
|
-
return `url(#chart-pattern-${patternKey})`
|
|
340
|
-
}
|
|
341
|
-
// Fallback for non-grouped charts: check datum field value
|
|
342
|
-
else if (!config.series || config.series.length <= 1) {
|
|
343
|
-
const dataFieldValue = datum[pattern.dataKey]
|
|
344
|
-
if (String(dataFieldValue) === String(pattern.dataValue)) {
|
|
345
|
-
return `url(#chart-pattern-${patternKey})`
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return null
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const patternUrl = getPatternUrl()
|
|
330
|
+
const patternUrl = getPatternUrlForBar({
|
|
331
|
+
patterns: config.legend?.patterns,
|
|
332
|
+
datum,
|
|
333
|
+
seriesKey: bar.key,
|
|
334
|
+
seriesValue: bar.value,
|
|
335
|
+
seriesLabels: config.runtime?.seriesLabels,
|
|
336
|
+
seriesKeys: config.series?.map(series => series.dataKey),
|
|
337
|
+
allowNonSeriesFieldMatch: !config.series || config.series.length <= 1
|
|
338
|
+
})
|
|
355
339
|
const baseBackground = getBarBackgroundColor(colorScale(config.runtime.seriesLabels[bar.key]))
|
|
356
340
|
|
|
357
341
|
// Confidence Interval Variables
|
|
@@ -18,6 +18,7 @@ type BarChartProps = {
|
|
|
18
18
|
seriesScale: PositionScale
|
|
19
19
|
xMax: number
|
|
20
20
|
yMax: number
|
|
21
|
+
yAxisWidth?: number
|
|
21
22
|
handleTooltipMouseOver: MouseEventHandler<SVGRectElement>
|
|
22
23
|
handleTooltipMouseOff: MouseEventHandler<SVGRectElement>
|
|
23
24
|
handleTooltipClick: MouseEventHandler<SVGRectElement>
|
|
@@ -29,6 +30,7 @@ const BarChart: React.FC<BarChartProps> = ({
|
|
|
29
30
|
seriesScale,
|
|
30
31
|
xMax,
|
|
31
32
|
yMax,
|
|
33
|
+
yAxisWidth,
|
|
32
34
|
handleTooltipMouseOver,
|
|
33
35
|
handleTooltipMouseOff,
|
|
34
36
|
handleTooltipClick
|
|
@@ -47,10 +49,14 @@ const BarChart: React.FC<BarChartProps> = ({
|
|
|
47
49
|
barChart
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
// Use yAxisWidth prop if provided (for horizontal bar charts with dynamic labels)
|
|
53
|
+
// otherwise fall back to config value
|
|
54
|
+
const leftOffset = yAxisWidth ?? parseFloat(config.runtime.yAxis.size)
|
|
55
|
+
|
|
50
56
|
return (
|
|
51
57
|
<ErrorBoundary component='BarChart'>
|
|
52
58
|
<BarChartContext.Provider value={contextValues}>
|
|
53
|
-
<Group left={
|
|
59
|
+
<Group left={leftOffset}>
|
|
54
60
|
<BarChartType.StackedVertical />
|
|
55
61
|
<BarChartType.StackedHorizontal />
|
|
56
62
|
<BarChartType.Vertical />
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { getChartPatternId } from '../../../helpers/getChartPatternId'
|
|
2
|
+
|
|
3
|
+
type LegendPattern = {
|
|
4
|
+
dataKey?: string
|
|
5
|
+
dataValue?: string | number
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type SeriesLabels = Record<string, string> | undefined
|
|
9
|
+
|
|
10
|
+
type GetPatternUrlArgs = {
|
|
11
|
+
patterns?: Record<string, LegendPattern>
|
|
12
|
+
datum: Record<string, any>
|
|
13
|
+
seriesKey: string
|
|
14
|
+
seriesValue: string | number
|
|
15
|
+
seriesLabels?: SeriesLabels
|
|
16
|
+
seriesKeys?: string[]
|
|
17
|
+
allowNonSeriesFieldMatch?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const normalizeString = (value: unknown): string => String(value ?? '').trim()
|
|
21
|
+
|
|
22
|
+
const hasPatternValue = (value: unknown): boolean => normalizeString(value) !== ''
|
|
23
|
+
|
|
24
|
+
const isNumericLike = (value: string): boolean => value !== '' && !Number.isNaN(Number(value))
|
|
25
|
+
|
|
26
|
+
const valuesMatch = (left: unknown, right: unknown): boolean => {
|
|
27
|
+
const normalizedLeft = normalizeString(left)
|
|
28
|
+
const normalizedRight = normalizeString(right)
|
|
29
|
+
|
|
30
|
+
if (normalizedLeft === '' || normalizedRight === '') {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (isNumericLike(normalizedLeft) && isNumericLike(normalizedRight)) {
|
|
35
|
+
return Number(normalizedLeft) === Number(normalizedRight)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return normalizedLeft === normalizedRight
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const isSeriesDataKey = (dataKey: string, seriesLabels?: SeriesLabels, seriesKeys?: string[]): boolean => {
|
|
42
|
+
if (Array.isArray(seriesKeys) && seriesKeys.length > 0) {
|
|
43
|
+
return seriesKeys.includes(dataKey)
|
|
44
|
+
}
|
|
45
|
+
if (!seriesLabels) return false
|
|
46
|
+
return Object.prototype.hasOwnProperty.call(seriesLabels, dataKey)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const getPatternUrl = ({
|
|
50
|
+
patterns,
|
|
51
|
+
datum,
|
|
52
|
+
seriesKey,
|
|
53
|
+
seriesValue,
|
|
54
|
+
seriesLabels,
|
|
55
|
+
seriesKeys,
|
|
56
|
+
allowNonSeriesFieldMatch = true
|
|
57
|
+
}: GetPatternUrlArgs): string | null => {
|
|
58
|
+
if (!patterns) {
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let broadMatchUrl: string | null = null
|
|
63
|
+
|
|
64
|
+
for (const patternKey in patterns) {
|
|
65
|
+
if (!Object.prototype.hasOwnProperty.call(patterns, patternKey)) continue
|
|
66
|
+
const pattern = patterns[patternKey]
|
|
67
|
+
const dataKey = normalizeString(pattern.dataKey)
|
|
68
|
+
|
|
69
|
+
if (!hasPatternValue(pattern.dataValue)) {
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (dataKey === '') {
|
|
74
|
+
if (!broadMatchUrl && valuesMatch(seriesValue, pattern.dataValue)) {
|
|
75
|
+
broadMatchUrl = `url(#${getChartPatternId(patternKey)})`
|
|
76
|
+
}
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (dataKey === seriesKey && valuesMatch(seriesValue, pattern.dataValue)) {
|
|
81
|
+
return `url(#${getChartPatternId(patternKey)})`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (
|
|
85
|
+
allowNonSeriesFieldMatch &&
|
|
86
|
+
!isSeriesDataKey(dataKey, seriesLabels, seriesKeys) &&
|
|
87
|
+
valuesMatch(datum?.[dataKey], pattern.dataValue)
|
|
88
|
+
) {
|
|
89
|
+
return `url(#${getChartPatternId(patternKey)})`
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return broadMatchUrl
|
|
94
|
+
}
|