@cdc/chart 4.25.11 → 4.26.2

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.
Files changed (181) hide show
  1. package/CLAUDE.local.md +79 -0
  2. package/dist/{cdcchart-dgT_1dIT.es.js → cdcchart-DQ00cQCm.es.js} +1 -20
  3. package/dist/cdcchart.js +51401 -50814
  4. package/examples/default.json +378 -0
  5. package/examples/feature/__data__/horizon-chart-data.json +373 -0
  6. package/examples/feature/annotations/index.json +3 -6
  7. package/examples/feature/horizon/horizon-chart.json +395 -0
  8. package/examples/feature/pie/planet-pie-example-config.json +48 -2
  9. package/examples/line-chart-states.json +1085 -0
  10. package/examples/private/123.json +694 -0
  11. package/examples/private/DEV-12100.json +1303 -0
  12. package/examples/private/anchor-issue.json +4094 -0
  13. package/examples/private/backwards-slider.json +10430 -0
  14. package/examples/private/cat-y.json +1235 -0
  15. package/examples/private/data-points.json +228 -0
  16. package/examples/private/georgia.csv +160 -0
  17. package/examples/private/height.json +3915 -0
  18. package/examples/private/links.json +569 -0
  19. package/examples/private/quadrant.txt +30 -0
  20. package/examples/private/test-forecast.json +5510 -0
  21. package/examples/private/timeline-data.json +1 -0
  22. package/examples/private/timeline.json +389 -0
  23. package/examples/private/warming-stripe-test.json +2578 -0
  24. package/examples/private/warming-stripes.json +4763 -0
  25. package/examples/radar-chart-simple.json +133 -0
  26. package/examples/radar-chart.json +148 -0
  27. package/examples/tech-adoption-with-links.json +560 -0
  28. package/index.html +1 -36
  29. package/package.json +59 -60
  30. package/src/CdcChartComponent.tsx +206 -89
  31. package/src/_stories/Chart.Anchors.stories.tsx +10 -0
  32. package/src/_stories/Chart.BoxPlot.stories.tsx +7 -0
  33. package/src/_stories/Chart.CI.stories.tsx +13 -0
  34. package/src/_stories/Chart.Combo.stories.tsx +17 -0
  35. package/src/_stories/Chart.CustomColors.stories.tsx +4 -0
  36. package/src/_stories/Chart.DynamicSeries.stories.tsx +19 -0
  37. package/src/_stories/Chart.Filters.stories.tsx +4 -0
  38. package/src/_stories/Chart.Forecast.stories.tsx +4 -0
  39. package/src/_stories/Chart.HTMLInDataTable.stories.tsx +22 -0
  40. package/src/_stories/Chart.Legend.Gradient.stories.tsx +28 -0
  41. package/src/_stories/Chart.Patterns.stories.tsx +4 -0
  42. package/src/_stories/Chart.PreserveDecimals.stories.tsx +25 -0
  43. package/src/_stories/Chart.Regions.Categorical.stories.tsx +161 -0
  44. package/src/_stories/Chart.Regions.DateScale.stories.tsx +216 -0
  45. package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +312 -0
  46. package/src/_stories/Chart.ScatterPlot.stories.tsx +4 -0
  47. package/src/_stories/Chart.SmallMultiples.stories.tsx +16 -0
  48. package/src/_stories/Chart.stories.tsx +45 -0
  49. package/src/_stories/Chart.tooltip.stories.tsx +7 -0
  50. package/src/_stories/ChartAnnotation.stories.tsx +10 -0
  51. package/src/_stories/ChartAxisLabels.stories.tsx +4 -0
  52. package/src/_stories/ChartAxisTitles.stories.tsx +10 -0
  53. package/src/_stories/ChartBar.Editor.stories.tsx +11 -6
  54. package/src/_stories/ChartBrush.Editor.stories.tsx +295 -0
  55. package/src/_stories/ChartBrush.Matrix.Continuous.stories.tsx +41 -0
  56. package/src/_stories/ChartBrush.Matrix.Date.stories.tsx +114 -0
  57. package/src/_stories/ChartBrush.Matrix.DateTime.stories.tsx +78 -0
  58. package/src/_stories/ChartBrush.stories.tsx +57 -0
  59. package/src/_stories/ChartEditor.Editor.stories.tsx +3 -5
  60. package/src/_stories/ChartEditor.stories.tsx +7 -0
  61. package/src/_stories/ChartLine.QuadrantAngles.stories.tsx +89 -0
  62. package/src/_stories/ChartLine.Suppression.stories.tsx +7 -0
  63. package/src/_stories/ChartLine.Symbols.stories.tsx +4 -0
  64. package/src/_stories/ChartPrefixSuffix.stories.tsx +46 -1
  65. package/src/_stories/TechAdoptionWithLinks.stories.tsx +34 -0
  66. package/src/_stories/_mock/brush_continuous.json +86 -0
  67. package/src/_stories/_mock/brush_date_large.json +176 -0
  68. package/src/_stories/_mock/brush_enabled.json +326 -0
  69. package/src/_stories/_mock/brush_mock.json +2 -69
  70. package/src/_stories/_mock/horizontal-bars-dynamic-y-axis.json +413 -0
  71. package/src/_stories/_mock/line_chart_angle_near_zero_fall.json +195 -0
  72. package/src/_stories/_mock/line_chart_angle_near_zero_rise.json +195 -0
  73. package/src/_stories/_mock/line_chart_angle_q1_steep_upward.json +195 -0
  74. package/src/_stories/_mock/line_chart_angle_q2_gentle_downward.json +195 -0
  75. package/src/_stories/_mock/line_chart_angle_q3_steep_downward.json +195 -0
  76. package/src/_stories/_mock/line_chart_angle_q4_gentle_upward.json +195 -0
  77. package/src/_stories/_mock/line_chart_quadrant_angles.json +264 -0
  78. package/src/components/Annotations/components/AnnotationDraggable.styles.css +11 -17
  79. package/src/components/Annotations/components/AnnotationDraggable.tsx +240 -116
  80. package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -2
  81. package/src/components/Annotations/components/AnnotationDropdown.tsx +8 -12
  82. package/src/components/Annotations/components/AnnotationList.styles.css +4 -10
  83. package/src/components/Annotations/components/AnnotationList.tsx +5 -4
  84. package/src/components/Annotations/components/findNearestDatum.ts +75 -85
  85. package/src/components/Annotations/helpers/getVisibleAnnotations.ts +38 -0
  86. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -2
  87. package/src/components/Axis/BottomAxis.tsx +270 -0
  88. package/src/components/Axis/Categorical.Axis.tsx +6 -7
  89. package/src/components/Axis/LeftAxis.tsx +404 -0
  90. package/src/components/Axis/LeftAxisGridlines.tsx +77 -0
  91. package/src/components/Axis/PairedBarAxis.tsx +186 -0
  92. package/src/components/Axis/README.md +94 -0
  93. package/src/components/Axis/RightAxis.tsx +108 -0
  94. package/src/components/Axis/axis.constants.ts +21 -0
  95. package/src/components/Axis/index.ts +7 -0
  96. package/src/components/BarChart/components/BarChart.Horizontal.tsx +178 -24
  97. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +3 -1
  98. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -0
  99. package/src/components/BarChart/components/BarChart.Vertical.tsx +6 -8
  100. package/src/components/BarChart/components/BarChart.tsx +7 -1
  101. package/src/components/BarChart/components/context.tsx +1 -0
  102. package/src/components/BarChart/helpers/useBarChart.ts +14 -2
  103. package/src/components/Brush/BrushSelector.tsx +1390 -0
  104. package/src/components/Brush/MiniChartPreview.tsx +400 -0
  105. package/src/components/DeviationBar.jsx +9 -7
  106. package/src/components/EditorPanel/EditorPanel.tsx +2734 -2595
  107. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +60 -22
  108. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +56 -34
  109. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +137 -30
  110. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +2 -0
  111. package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +353 -0
  112. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +0 -1
  113. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +30 -25
  114. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +42 -28
  115. package/src/components/EditorPanel/components/Panels/index.tsx +2 -0
  116. package/src/components/EditorPanel/useEditorPermissions.ts +81 -39
  117. package/src/components/HorizonChart/HorizonChart.tsx +131 -0
  118. package/src/components/HorizonChart/components/HorizonBand.tsx +160 -0
  119. package/src/components/HorizonChart/helpers/calculateHorizonBands.ts +27 -0
  120. package/src/components/HorizonChart/helpers/getHorizonLayerColors.ts +40 -0
  121. package/src/components/HorizonChart/index.tsx +3 -0
  122. package/src/components/Legend/Legend.Component.tsx +52 -4
  123. package/src/components/Legend/Legend.tsx +4 -3
  124. package/src/components/Legend/LegendValueRange.tsx +77 -0
  125. package/src/components/Legend/helpers/createFormatLabels.tsx +164 -2
  126. package/src/components/Legend/helpers/generateValueRanges.ts +92 -0
  127. package/src/components/Legend/helpers/index.ts +10 -6
  128. package/src/components/LineChart/helpers/README.md +292 -0
  129. package/src/components/LineChart/helpers/labelPositioning.test.ts +245 -0
  130. package/src/components/LineChart/helpers/labelPositioning.ts +304 -0
  131. package/src/components/LineChart/index.tsx +44 -8
  132. package/src/components/LinearChart/README.md +109 -0
  133. package/src/components/LinearChart/VisualizationRenderer.tsx +267 -0
  134. package/src/components/LinearChart/linearChart.constants.ts +84 -0
  135. package/src/components/LinearChart/tests/LinearChart.test.tsx +201 -0
  136. package/src/components/LinearChart/tests/mockConfigContext.ts +129 -0
  137. package/src/components/LinearChart/utils/tickFormatting.ts +146 -0
  138. package/src/components/LinearChart.tsx +338 -1082
  139. package/src/components/PairedBarChart.jsx +20 -3
  140. package/src/components/PieChart/PieChart.tsx +1 -1
  141. package/src/components/RadarChart/RadarAxis.tsx +78 -0
  142. package/src/components/RadarChart/RadarChart.tsx +298 -0
  143. package/src/components/RadarChart/RadarGrid.tsx +64 -0
  144. package/src/components/RadarChart/RadarPolygon.tsx +91 -0
  145. package/src/components/RadarChart/helpers.ts +83 -0
  146. package/src/components/RadarChart/index.tsx +3 -0
  147. package/src/components/Regions/components/Regions.tsx +365 -122
  148. package/src/components/ScatterPlot/ScatterPlot.jsx +2 -2
  149. package/src/components/SmallMultiples/SmallMultipleTile.tsx +5 -1
  150. package/src/components/WarmingStripes/WarmingStripes.tsx +230 -0
  151. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +35 -0
  152. package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +104 -0
  153. package/src/components/WarmingStripes/index.tsx +3 -0
  154. package/src/data/initial-state.js +17 -2
  155. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +57 -0
  156. package/src/helpers/getExcludedData.ts +4 -0
  157. package/src/helpers/getMinMax.ts +12 -7
  158. package/src/helpers/handleChartAriaLabels.ts +19 -19
  159. package/src/helpers/handleLineType.ts +22 -18
  160. package/src/helpers/sizeHelpers.ts +0 -20
  161. package/src/helpers/smallMultiplesHelpers.ts +1 -1
  162. package/src/hooks/useChartHoverAnalytics.tsx +10 -9
  163. package/src/hooks/useProgrammaticTooltip.ts +23 -2
  164. package/src/hooks/useScales.ts +18 -1
  165. package/src/hooks/useTooltip.tsx +34 -10
  166. package/src/scss/DataTable.scss +0 -4
  167. package/src/scss/main.scss +22 -3
  168. package/src/selectors/README.md +68 -0
  169. package/src/store/chart.reducer.ts +2 -0
  170. package/src/test/CdcChart.test.jsx +1 -1
  171. package/src/types/ChartConfig.ts +21 -0
  172. package/src/types/ChartContext.ts +1 -0
  173. package/src/types/Horizon.ts +64 -0
  174. package/src/types/Label.ts +1 -0
  175. package/src/utils/analyticsTracking.ts +19 -0
  176. package/LICENSE +0 -201
  177. package/src/components/Annotations/components/helpers/index.tsx +0 -46
  178. package/src/components/Brush/BrushChart.tsx +0 -128
  179. package/src/components/Brush/BrushController.tsx +0 -71
  180. package/src/components/Brush/types.tsx +0 -8
  181. package/src/components/BrushChart.tsx +0 -223
@@ -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'
@@ -13,7 +13,6 @@ import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
13
13
  // CDC core components and helpers
14
14
  import { getColorContrast, getContrastColor } from '@cdc/core/helpers/cove/accessibility'
15
15
  import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
16
- import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
17
16
  import createBarElement from '@cdc/core/components/createBarElement'
18
17
  import { getBarConfig, testZeroValue, getLollipopStemColor, getLollipopHeadColor } from '../helpers'
19
18
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
@@ -36,6 +35,7 @@ const BarChartHorizontal = () => {
36
35
  isLabelBelowBar,
37
36
  lollipopBarWidth,
38
37
  lollipopShapeSize,
38
+ labelFontSize,
39
39
  getHighlightedBarColorByValue,
40
40
  getHighlightedBarByValue,
41
41
  getAdditionalColumn,
@@ -59,8 +59,6 @@ const BarChartHorizontal = () => {
59
59
 
60
60
  const { HighLightedBarUtils } = useHighlightedBars(config)
61
61
 
62
- const LABEL_FONT_SIZE = isMobileFontViewport(vizViewport) ? 13 : 16
63
-
64
62
  const hasConfidenceInterval = [config.confidenceKeys?.upper, config.confidenceKeys?.lower].every(
65
63
  v => v != null && String(v).trim() !== ''
66
64
  )
@@ -209,7 +207,7 @@ const BarChartHorizontal = () => {
209
207
  isVertical: false,
210
208
  yAxisValue,
211
209
  barWidth: 0,
212
- labelFontSize: LABEL_FONT_SIZE
210
+ labelFontSize: labelFontSize
213
211
  })
214
212
 
215
213
  const barPosition = !isPositiveBar ? 'below' : 'above'
@@ -217,7 +215,7 @@ const BarChartHorizontal = () => {
217
215
  const barDefaultLabel = !config.yAxis.displayNumbersOnBar || absentDataLabel ? '' : yAxisValue
218
216
 
219
217
  // check if bar text/value string fits into each bars.
220
- const textWidth = getTextWidth(barDefaultLabel)
218
+ const textWidth = getTextWidth(barDefaultLabel, `normal ${labelFontSize}px sans-serif`)
221
219
  const textFits = Number(textWidth) < defaultBarWidth - 5
222
220
 
223
221
  // control text position
@@ -447,7 +445,7 @@ const BarChartHorizontal = () => {
447
445
  return (
448
446
  <Text // prettier-ignore
449
447
  key={index}
450
- fontSize={LABEL_FONT_SIZE}
448
+ fontSize={labelFontSize}
451
449
  display={displayBar ? 'block' : 'none'}
452
450
  opacity={transparentBar ? 0.5 : 1}
453
451
  x={barX}
@@ -473,6 +471,7 @@ const BarChartHorizontal = () => {
473
471
  dx={textPadding}
474
472
  verticalAnchor='middle'
475
473
  textAnchor={textAnchor}
474
+ fontSize={labelFontSize}
476
475
  >
477
476
  {testZeroValue(bar.value) ? '' : barDefaultLabel}
478
477
  </Text>
@@ -488,6 +487,7 @@ const BarChartHorizontal = () => {
488
487
  dx={-textPadding}
489
488
  verticalAnchor='middle'
490
489
  textAnchor={bar.value < 0 ? 'end' : 'start'}
490
+ fontSize={labelFontSize}
491
491
  >
492
492
  {testZeroValue(bar.value) ? '' : barDefaultLabel}
493
493
  </Text>
@@ -496,12 +496,17 @@ const BarChartHorizontal = () => {
496
496
  display={displayBar ? 'block' : 'none'}
497
497
  x={bar.y}
498
498
  opacity={transparentBar ? 0.5 : 1}
499
- y={config.barHeight / 2 + config.barHeight * bar.index}
499
+ y={
500
+ config.isLollipopChart
501
+ ? barHeight * bar.index + lollipopBarWidth / 2
502
+ : config.barHeight / 2 + config.barHeight * bar.index
503
+ }
500
504
  fill={labelColor}
501
505
  dx={absentDataLabel === 'N/A' ? 20 : textPadding}
502
- dy={config.isLollipopChart ? -10 : 0}
506
+ dy={0}
503
507
  verticalAnchor='middle'
504
508
  textAnchor={absentDataLabel === 'N/A' ? 'middle' : textAnchor}
509
+ fontSize={labelFontSize}
505
510
  >
506
511
  {absentDataLabel}
507
512
  </Text>
@@ -510,31 +515,180 @@ const BarChartHorizontal = () => {
510
515
  <Text // prettier-ignore
511
516
  display={displayBar ? 'block' : 'none'}
512
517
  x={bar.y}
513
- y={0}
518
+ y={barHeight * bar.index + lollipopBarWidth / 2}
514
519
  fill={APP_FONT_COLOR}
515
520
  dx={textPaddingLollipop}
516
521
  textAnchor={textAnchorLollipop}
517
522
  verticalAnchor='middle'
518
523
  fontWeight={'normal'}
524
+ fontSize={labelFontSize}
519
525
  >
520
526
  {testZeroValue(bar.value) ? '' : barDefaultLabel}
521
527
  </Text>
522
528
  )}
523
- {isLabelBelowBar && !config.yAxis.hideLabel && (
524
- <Text
525
- x={config.yAxis.hideAxis ? 0 : 5}
526
- y={barGroup.height}
527
- dy={4}
528
- verticalAnchor={'start'}
529
- textAnchor={'start'}
530
- >
531
- {config.runtime.yAxis.type === 'date'
532
- ? formatDate(parseDate(dataValue))
533
- : isHorizontal
534
- ? dataValue
535
- : formatNumber(dataValue)}
536
- </Text>
537
- )}
529
+ {isLabelBelowBar &&
530
+ !config.yAxis.hideLabel &&
531
+ (() => {
532
+ const label =
533
+ config.runtime.yAxis.type === 'date'
534
+ ? formatDate(parseDate(dataValue))
535
+ : isHorizontal
536
+ ? dataValue
537
+ : formatNumber(dataValue)
538
+ if (typeof label === 'string') {
539
+ // 1. Check for HTML <a> tag and extract its text and href
540
+ const aTagMatch = label.match(/<a [^>]*href=["']?([^"'>\s]+)["']?[^>]*>(.*?)<\/a>/i)
541
+ if (aTagMatch) {
542
+ const href = aTagMatch[1].startsWith('http') ? aTagMatch[1] : `https://${aTagMatch[1]}`
543
+ const linkText = aTagMatch[2]
544
+ return (
545
+ <foreignObject
546
+ x={config.yAxis.hideAxis ? 0 : 5}
547
+ y={barGroup.height}
548
+ width={120}
549
+ height={24}
550
+ style={{ overflow: 'visible' }}
551
+ >
552
+ <a
553
+ href={href}
554
+ target='_blank'
555
+ rel='noopener noreferrer'
556
+ style={
557
+ config.tooltips.singleSeries
558
+ ? {
559
+ color: '#0071bc',
560
+ textDecoration: 'underline',
561
+ fontSize: 12,
562
+ fontFamily: 'inherit',
563
+ display: 'inline-block',
564
+ width: '100%'
565
+ }
566
+ : {
567
+ color: 'inherit',
568
+ textDecoration: 'none',
569
+ fontSize: 12,
570
+ fontFamily: 'inherit',
571
+ display: 'inline-block',
572
+ width: '100%'
573
+ }
574
+ }
575
+ >
576
+ {linkText}
577
+ </a>
578
+ </foreignObject>
579
+ )
580
+ }
581
+ // 2. Check for markdown link
582
+ const mdMatch = label.match(/\[([^\]]+)\]\((https?:\/\/[^\s]+|www\.[^\s]+)\)/i)
583
+ if (mdMatch) {
584
+ const href = mdMatch[2].startsWith('http') ? mdMatch[2] : `https://${mdMatch[2]}`
585
+ const linkText = mdMatch[1]
586
+ return (
587
+ <foreignObject
588
+ x={config.yAxis.hideAxis ? 0 : 5}
589
+ y={barGroup.height}
590
+ width={120}
591
+ height={24}
592
+ style={{ overflow: 'visible' }}
593
+ >
594
+ <a
595
+ href={href}
596
+ target='_blank'
597
+ rel='noopener noreferrer'
598
+ style={
599
+ config.tooltips.singleSeries
600
+ ? {
601
+ color: '#0071bc',
602
+ textDecoration: 'underline',
603
+ fontSize: 12,
604
+ fontFamily: 'inherit',
605
+ display: 'inline-block',
606
+ width: '100%'
607
+ }
608
+ : {
609
+ color: 'inherit',
610
+ textDecoration: 'none',
611
+ fontSize: 12,
612
+ fontFamily: 'inherit',
613
+ display: 'inline-block',
614
+ width: '100%'
615
+ }
616
+ }
617
+ >
618
+ {linkText}
619
+ </a>
620
+ </foreignObject>
621
+ )
622
+ }
623
+ // 3. Check for plain URL
624
+ if (/(https?:\/\/|www\.)/i.test(label)) {
625
+ try {
626
+ const urlObj = new URL(label.startsWith('http') ? label : `https://${label}`)
627
+ const linkText = urlObj.hostname.replace(/^www\./, '')
628
+ return (
629
+ <foreignObject
630
+ x={config.yAxis.hideAxis ? 0 : 5}
631
+ y={barGroup.height}
632
+ width={120}
633
+ height={24}
634
+ style={{ overflow: 'visible' }}
635
+ >
636
+ <a
637
+ href={urlObj.href}
638
+ target='_blank'
639
+ rel='noopener noreferrer'
640
+ style={
641
+ config.tooltips.singleSeries
642
+ ? {
643
+ color: '#0071bc',
644
+ textDecoration: 'underline',
645
+ fontSize: 12,
646
+ fontFamily: 'inherit',
647
+ display: 'inline-block',
648
+ width: '100%'
649
+ }
650
+ : {
651
+ color: 'inherit',
652
+ textDecoration: 'none',
653
+ fontSize: 12,
654
+ fontFamily: 'inherit',
655
+ display: 'inline-block',
656
+ width: '100%'
657
+ }
658
+ }
659
+ >
660
+ {linkText}
661
+ </a>
662
+ </foreignObject>
663
+ )
664
+ } catch {
665
+ return (
666
+ <Text
667
+ x={config.yAxis.hideAxis ? 0 : 5}
668
+ y={barGroup.height}
669
+ dy={4}
670
+ verticalAnchor={'start'}
671
+ textAnchor={'start'}
672
+ >
673
+ {label}
674
+ </Text>
675
+ )
676
+ }
677
+ }
678
+ }
679
+ // Not a link, render as normal
680
+ return (
681
+ <Text
682
+ x={config.yAxis.hideAxis ? 0 : 5}
683
+ y={barGroup.height}
684
+ dy={4}
685
+ verticalAnchor={'start'}
686
+ textAnchor={'start'}
687
+ >
688
+ {label}
689
+ </Text>
690
+ )
691
+ })()}
538
692
 
539
693
  {config.isLollipopChart && config.lollipopShape === 'circle' && (
540
694
  <circle
@@ -38,6 +38,7 @@ const BarChartStackedHorizontal = () => {
38
38
  hoveredBar,
39
39
  isHorizontal,
40
40
  isLabelBelowBar,
41
+ labelFontSize,
41
42
  onMouseLeaveBar,
42
43
  onMouseOverBar,
43
44
  barStackedSeriesKeys
@@ -155,7 +156,7 @@ const BarChartStackedHorizontal = () => {
155
156
  const yAxisTooltip = config.runtime.yAxis.label
156
157
  ? `${config.runtime.yAxis.label}: ${yAxisValue}`
157
158
  : yAxisValue
158
- const textWidth = getTextWidth(xAxisValue)
159
+ const textWidth = getTextWidth(xAxisValue, `normal ${labelFontSize}px sans-serif`)
159
160
  const additionalColTooltip = getAdditionalColumn(bar.key, hoveredBar)
160
161
  const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${xAxisValue}`
161
162
  const tooltip = `<ul>
@@ -286,6 +287,7 @@ const BarChartStackedHorizontal = () => {
286
287
  fill={labelColor}
287
288
  textAnchor='middle'
288
289
  verticalAnchor='middle'
290
+ fontSize={labelFontSize}
289
291
  >
290
292
  {xAxisValue}
291
293
  </Text>
@@ -259,6 +259,7 @@ const BarChartStackedVertical = () => {
259
259
  yMax={yMax}
260
260
  barWidth={barWidth}
261
261
  totalBarsInGroup={1}
262
+ xMax={xMax}
262
263
  handleTooltipMouseOff={() => {}}
263
264
  handleTooltipMouseOver={() => {}}
264
265
  handleTooltipClick={() => {}}
@@ -17,7 +17,6 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
17
17
  import isNumber from '@cdc/core/helpers/isNumber'
18
18
  import createBarElement from '@cdc/core/components/createBarElement'
19
19
  import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
20
- import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
21
20
  // Types
22
21
  import { type ChartContext } from '../../../types/ChartContext'
23
22
  import _ from 'lodash'
@@ -32,6 +31,7 @@ const BarChartVertical = () => {
32
31
  getAdditionalColumn,
33
32
  getHighlightedBarByValue,
34
33
  getHighlightedBarColorByValue,
34
+ labelFontSize,
35
35
  lollipopBarWidth,
36
36
  lollipopShapeSize,
37
37
  onMouseLeaveBar,
@@ -59,8 +59,6 @@ const BarChartVertical = () => {
59
59
 
60
60
  const { HighLightedBarUtils } = useHighlightedBars(config)
61
61
 
62
- const LABEL_FONT_SIZE = isMobileFontViewport(vizViewport) ? 13 : 16
63
-
64
62
  const root = document.documentElement
65
63
 
66
64
  let data = transformedData
@@ -242,7 +240,7 @@ const BarChartVertical = () => {
242
240
  barWidth,
243
241
  isVertical: true,
244
242
  yAxisValue,
245
- labelFontSize: LABEL_FONT_SIZE
243
+ labelFontSize: labelFontSize
246
244
  })
247
245
  // configure colors
248
246
  let labelColor = APP_FONT_COLOR
@@ -481,7 +479,7 @@ const BarChartVertical = () => {
481
479
  verticalAnchor={verticalAnchor}
482
480
  fill={fillColor}
483
481
  textAnchor='middle'
484
- fontSize={LABEL_FONT_SIZE}
482
+ fontSize={labelFontSize}
485
483
  >
486
484
  {pd.iconCode}
487
485
  </Text>
@@ -494,7 +492,7 @@ const BarChartVertical = () => {
494
492
  y={barY - BAR_LABEL_PADDING}
495
493
  fill={labelColor}
496
494
  textAnchor='middle'
497
- fontSize={LABEL_FONT_SIZE}
495
+ fontSize={labelFontSize}
498
496
  >
499
497
  {testZeroValue(bar.value) ? '' : barDefaultLabel}
500
498
  </Text>
@@ -505,7 +503,7 @@ const BarChartVertical = () => {
505
503
  y={barY - BAR_LABEL_PADDING}
506
504
  fill={labelColor}
507
505
  textAnchor='middle'
508
- fontSize={config.isLollipopChart ? null : LABEL_FONT_SIZE}
506
+ fontSize={config.isLollipopChart ? null : labelFontSize}
509
507
  >
510
508
  {absentDataLabel}
511
509
  </Text>
@@ -568,7 +566,7 @@ const BarChartVertical = () => {
568
566
  }}
569
567
  </BarGroup>
570
568
 
571
- <Regions xScale={xScale} yMax={yMax} barWidth={barWidth} totalBarsInGroup={totalBarsInGroup} />
569
+ <Regions xScale={xScale} yMax={yMax} barWidth={barWidth} totalBarsInGroup={totalBarsInGroup} xMax={xMax} />
572
570
  </Group>
573
571
  )
574
572
  )
@@ -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={parseFloat(config.runtime.yAxis.size)}>
59
+ <Group left={leftOffset}>
54
60
  <BarChartType.StackedVertical />
55
61
  <BarChartType.StackedHorizontal />
56
62
  <BarChartType.Vertical />
@@ -18,6 +18,7 @@ export type BarChartContextValues = {
18
18
  getHighlightedBarColorByValue: Function
19
19
  lollipopBarWidth: number
20
20
  lollipopShapeSize: number
21
+ labelFontSize: number
21
22
  onMouseLeaveBar: Function
22
23
  onMouseOverBar: Function
23
24
  section: string