@cdc/chart 4.25.10 → 4.26.1

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 (135) hide show
  1. package/dist/{cdcchart-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
  2. package/dist/cdcchart.js +44003 -43518
  3. package/examples/feature/__data__/planet-example-data.json +1 -1
  4. package/examples/feature/boxplot/valid-boxplot.csv +38 -17
  5. package/examples/feature/pie/planet-pie-example-config.json +48 -2
  6. package/examples/private/DEV-11825.json +573 -0
  7. package/examples/private/DEV-12100.json +1303 -0
  8. package/examples/private/cat-y.json +1235 -0
  9. package/examples/private/data-points.json +228 -0
  10. package/examples/private/height.json +3915 -0
  11. package/examples/private/links.json +569 -0
  12. package/examples/private/na.json +913 -0
  13. package/examples/private/quadrant.txt +30 -0
  14. package/examples/private/test-data.csv +28 -0
  15. package/examples/private/test-forecast.json +5510 -0
  16. package/examples/private/warming-stripe-test.json +2578 -0
  17. package/examples/private/warming-stripes.json +4763 -0
  18. package/examples/tech-adoption-with-links.json +560 -0
  19. package/index.html +16 -140
  20. package/package.json +6 -5
  21. package/preview.html +1616 -0
  22. package/src/CdcChart.tsx +8 -11
  23. package/src/CdcChartComponent.tsx +329 -124
  24. package/src/_stories/Chart.Combo.stories.tsx +18 -0
  25. package/src/_stories/Chart.Forecast.stories.tsx +36 -0
  26. package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
  27. package/src/_stories/Chart.Patterns.stories.tsx +2 -1
  28. package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
  29. package/src/_stories/Chart.Regions.Categorical.stories.tsx +148 -0
  30. package/src/_stories/Chart.Regions.DateScale.stories.tsx +197 -0
  31. package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +297 -0
  32. package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
  33. package/src/_stories/Chart.stories.tsx +8 -0
  34. package/src/_stories/ChartAnnotation.stories.tsx +6 -3
  35. package/src/_stories/ChartBar.Editor.stories.tsx +3585 -0
  36. package/src/_stories/ChartBrush.Editor.stories.tsx +295 -0
  37. package/src/_stories/ChartBrush.stories.tsx +50 -0
  38. package/src/_stories/ChartEditor.Editor.stories.tsx +656 -0
  39. package/src/_stories/ChartEditor.stories.tsx +1 -2
  40. package/src/_stories/TechAdoptionWithLinks.stories.tsx +27 -0
  41. package/src/_stories/_mock/brush_enabled.json +326 -0
  42. package/src/_stories/_mock/brush_mock.json +2 -69
  43. package/src/_stories/_mock/combo.json +451 -0
  44. package/src/_stories/_mock/editor-test-configs.json +376 -0
  45. package/src/_stories/_mock/editor-test-datasets.json +477 -0
  46. package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
  47. package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
  48. package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
  49. package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
  50. package/src/_stories/_mock/horizontal-bars-dynamic-y-axis.json +413 -0
  51. package/src/_stories/_mock/pie_config.json +257 -62
  52. package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
  53. package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
  54. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
  55. package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
  56. package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
  57. package/src/components/Annotations/components/findNearestDatum.ts +6 -41
  58. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -7
  59. package/src/components/AreaChart/index.tsx +1 -2
  60. package/src/components/Axis/Categorical.Axis.tsx +6 -7
  61. package/src/components/BarChart/components/BarChart.Horizontal.tsx +181 -27
  62. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +3 -1
  63. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -0
  64. package/src/components/BarChart/components/BarChart.Vertical.tsx +8 -9
  65. package/src/components/BarChart/components/context.tsx +1 -0
  66. package/src/components/BarChart/helpers/useBarChart.ts +14 -2
  67. package/src/components/BoxPlot/helpers/index.ts +3 -3
  68. package/src/components/Brush/BrushSelector.tsx +1258 -0
  69. package/src/components/Brush/MiniChartPreview.tsx +283 -0
  70. package/src/components/DeviationBar.jsx +9 -7
  71. package/src/components/EditorPanel/EditorPanel.tsx +2720 -2586
  72. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
  73. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +56 -34
  74. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +76 -31
  75. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +104 -55
  76. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +54 -49
  77. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +427 -0
  78. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +96 -48
  79. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  80. package/src/components/EditorPanel/editor-panel.scss +0 -20
  81. package/src/components/EditorPanel/useEditorPermissions.ts +36 -31
  82. package/src/components/Forecasting/Forecasting.tsx +139 -21
  83. package/src/components/Legend/Legend.Component.tsx +16 -9
  84. package/src/components/Legend/Legend.tsx +3 -2
  85. package/src/components/Legend/helpers/createFormatLabels.tsx +325 -176
  86. package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
  87. package/src/components/Legend/helpers/index.ts +10 -6
  88. package/src/components/LineChart/LineChartProps.ts +0 -3
  89. package/src/components/LineChart/helpers.ts +1 -1
  90. package/src/components/LineChart/index.tsx +36 -13
  91. package/src/components/LinearChart.tsx +559 -499
  92. package/src/components/PairedBarChart.jsx +20 -3
  93. package/src/components/Regions/components/Regions.tsx +366 -144
  94. package/src/components/Sankey/types/index.ts +1 -1
  95. package/src/components/ScatterPlot/ScatterPlot.jsx +2 -2
  96. package/src/components/SmallMultiples/SmallMultipleTile.tsx +202 -0
  97. package/src/components/SmallMultiples/SmallMultiples.css +32 -0
  98. package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
  99. package/src/components/SmallMultiples/index.ts +2 -0
  100. package/src/components/WarmingStripes/WarmingStripes.tsx +160 -0
  101. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +35 -0
  102. package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +104 -0
  103. package/src/components/WarmingStripes/index.tsx +3 -0
  104. package/src/data/initial-state.js +16 -2
  105. package/src/helpers/buildForecastPaletteOptions.ts +0 -38
  106. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +57 -0
  107. package/src/helpers/getColorScale.ts +10 -0
  108. package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +26 -14
  109. package/src/helpers/getYAxisAutoPadding.ts +53 -0
  110. package/src/helpers/sizeHelpers.ts +0 -20
  111. package/src/helpers/smallMultiplesHelpers.ts +529 -0
  112. package/src/hooks/useChartHoverAnalytics.tsx +10 -9
  113. package/src/hooks/useProgrammaticTooltip.ts +96 -0
  114. package/src/hooks/useScales.ts +98 -34
  115. package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
  116. package/src/hooks/useTooltip.tsx +91 -25
  117. package/src/scss/DataTable.scss +0 -4
  118. package/src/scss/main.scss +18 -83
  119. package/src/store/chart.actions.ts +2 -0
  120. package/src/store/chart.reducer.ts +4 -0
  121. package/src/test/CdcChart.test.jsx +1 -1
  122. package/src/types/ChartConfig.ts +27 -6
  123. package/src/types/ChartContext.ts +3 -0
  124. package/src/types/Label.ts +1 -0
  125. package/src/utils/analyticsTracking.ts +19 -0
  126. package/LICENSE +0 -201
  127. package/src/_stories/_mock/pie_data.json +0 -218
  128. package/src/components/AreaChart/components/AreaChart.jsx +0 -109
  129. package/src/components/Brush/BrushChart.tsx +0 -128
  130. package/src/components/Brush/BrushController.tsx +0 -71
  131. package/src/components/Brush/types.tsx +0 -8
  132. package/src/components/BrushChart.tsx +0 -223
  133. package/src/helpers/sort.ts +0 -7
  134. package/src/hooks/useActiveElement.js +0 -19
  135. package/src/hooks/useChartClasses.js +0 -41
@@ -1,41 +1,3 @@
1
- import { timeParse } from 'd3-time-format'
2
-
3
- const getXValueFromCoordinate = (x, isClick = false) => {
4
- if (visualizationType === 'Pie') return
5
- if (orientation === 'horizontal') return
6
-
7
- // Check the type of x equal to point or if the type of xAxis is equal to continuous or date
8
- if (config.xAxis.type === 'categorical' || (visualizationType === 'Combo' && orientation !== 'horizontal' && visualizationType !== 'Forest Plot')) {
9
- let range = xScale.range()[1] - xScale.range()[0]
10
- let eachBand = range / (xScale.domain().length + 1)
11
-
12
- let numerator = x
13
- const index = Math.floor((Number(numerator) - eachBand / 2) / eachBand)
14
- return xScale.domain()[index] // fixes off by 1 error
15
- }
16
-
17
- if (config.xAxis.type === 'date') {
18
- const xValue = x // Assuming x is the coordinate on the chart
19
- const xTimestamp = convertXValueToTimestamp(x, 0, xMax, xScale.domain())
20
-
21
- // Calculate the closest date to the x coordinate
22
- let closestDate = null
23
- let minDistance = Number.MAX_VALUE
24
-
25
- xScale.domain().forEach(timestamp => {
26
- const distance = Math.abs(xTimestamp - timestamp)
27
- if (distance < minDistance) {
28
- minDistance = distance
29
- closestDate = timestamp
30
- }
31
- })
32
-
33
- return closestDate
34
- }
35
-
36
- return x
37
- }
38
-
39
1
  const findNearestDatum = ({ data, xScale, yScale, config, xMax, annotationSeriesKey }, xPosition) => {
40
2
  const { xAxis, visualizationType, orientation } = config
41
3
 
@@ -62,7 +24,7 @@ const findNearestDatum = ({ data, xScale, yScale, config, xMax, annotationSeries
62
24
  return domain[index]
63
25
  }
64
26
 
65
- const getXValueFromCoordinate = (x, isClick = false) => {
27
+ const getXValueFromCoordinate = x => {
66
28
  if (visualizationType === 'Pie') return
67
29
  if (orientation === 'horizontal') return
68
30
 
@@ -85,7 +47,10 @@ const findNearestDatum = ({ data, xScale, yScale, config, xMax, annotationSeries
85
47
  }
86
48
 
87
49
  // Check the type of x equal to point or if the type of xAxis is equal to continuous or date
88
- if (config.xAxis.type === 'categorical' || (visualizationType === 'Combo' && orientation !== 'horizontal' && visualizationType !== 'Forest Plot')) {
50
+ if (
51
+ config.xAxis.type === 'categorical' ||
52
+ (visualizationType === 'Combo' && orientation !== 'horizontal' && visualizationType !== 'Forest Plot')
53
+ ) {
89
54
  const range = xScale.range()[1] - xScale.range()[0]
90
55
  const eachBand = range / (xScale.domain().length + 1)
91
56
 
@@ -135,4 +100,4 @@ const findNearestDatum = ({ data, xScale, yScale, config, xMax, annotationSeries
135
100
  return { x, y }
136
101
  }
137
102
 
138
- export { findNearestDatum, getXValueFromCoordinate }
103
+ export { findNearestDatum }
@@ -13,17 +13,20 @@ import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
13
13
 
14
14
  const AreaChartStacked = ({ xScale, yScale, yMax, xMax, handleTooltipMouseOver, handleTooltipMouseOff }) => {
15
15
  // import data from context
16
- let { transformedData, config, seriesHighlight, colorScale, rawData } = useContext(ConfigContext)
17
- const data = config.brush?.active && config.brush.data?.length ? config.brush.data : transformedData
16
+ let { transformedData: data, config, seriesHighlight, colorScale, rawData, parseDate } = useContext(ConfigContext)
18
17
  // Draw transparent bars over the chart to get tooltip data
19
18
  // Turn DEBUG on for additional context.
20
19
  if (!data) return
21
20
 
22
21
  const handleDateCategory = value => {
23
- if (config.xAxis.type === 'categorical') return xScale(value)
22
+ if (config.xAxis.type === 'categorical') {
23
+ return xScale(value) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
24
+ }
24
25
  if (isDateScale(config.xAxis)) {
25
- let date = new Date(value)
26
- return xScale(date)
26
+ const scaledValue = xScale(parseDate(value, false))
27
+ // Add bandwidth offset to center on band scales (date type)
28
+ // For date-time (time scale), bandwidth doesn't exist so no offset added
29
+ return scaledValue + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
27
30
  }
28
31
  }
29
32
 
@@ -65,7 +68,7 @@ const AreaChartStacked = ({ xScale, yScale, yMax, xMax, handleTooltipMouseOver,
65
68
  key={stack.key}
66
69
  d={path(stack) || ''}
67
70
  strokeWidth={2}
68
- stroke={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[stack.key] : stack.key) : '#000' : 'transparent'}
71
+ stroke={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[stack.key] : stack.key) : '#000' : 'transparent'}
69
72
  fillOpacity={transparentArea ? 0.2 : 1}
70
73
  fill={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[stack.key] : stack.key) : '#000' : 'transparent'}
71
74
  />
@@ -74,7 +77,7 @@ const AreaChartStacked = ({ xScale, yScale, yMax, xMax, handleTooltipMouseOver,
74
77
  }}
75
78
  </AreaStack>
76
79
  {/* prettier-ignore */}
77
- <Bar width={Number(xMax)} height={Number(yMax)} fill={'transparent'} onMouseMove={e => handleTooltipMouseOver(e, rawData)} onMouseLeave={handleTooltipMouseOff} />
80
+ <Bar width={Number(xMax)} height={Number(yMax)} fill={'transparent'} onMouseMove={e => handleTooltipMouseOver(e, rawData)} onMouseLeave={handleTooltipMouseOff} />
78
81
  </Group>
79
82
  </ErrorBoundary>
80
83
  </svg>
@@ -1,4 +1,3 @@
1
- import AreaChart from './components/AreaChart'
2
1
  import AreaChartStacked from './components/AreaChart.Stacked'
3
2
 
4
- export { AreaChart, AreaChartStacked }
3
+ export { AreaChartStacked }
@@ -1,6 +1,6 @@
1
1
  import React, { useContext } from 'react'
2
2
  import { BarStack, Line } from '@visx/shape'
3
- import { scaleBand, scaleLinear, scaleOrdinal } from '@visx/scale'
3
+ import { scaleBand, scaleOrdinal } from '@visx/scale'
4
4
  import { Group } from '@visx/group'
5
5
  import { Text } from '@visx/text'
6
6
  import ConfigContext from '../../ConfigContext'
@@ -9,7 +9,7 @@ import createBarElement from '@cdc/core/components/createBarElement'
9
9
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
10
10
  import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
11
11
 
12
- const CategoricalYAxis = ({ yMax, leftSize, max, xMax }) => {
12
+ const CategoricalYAxis = ({ yScale, yMax, leftSize, xMax }) => {
13
13
  const { config } = useContext(ConfigContext)
14
14
 
15
15
  const { orientation } = config
@@ -24,6 +24,9 @@ const CategoricalYAxis = ({ yMax, leftSize, max, xMax }) => {
24
24
 
25
25
  const categories = config.yAxis?.categories
26
26
 
27
+ // Get max from the yScale domain
28
+ const max = yScale.domain()[1]
29
+
27
30
  const createDataShape = categories => {
28
31
  const categoryObj = [...categories].reduce((acc, item) => {
29
32
  acc[item.label] = item.height
@@ -68,11 +71,7 @@ const CategoricalYAxis = ({ yMax, leftSize, max, xMax }) => {
68
71
  range: [0, leftSize]
69
72
  })
70
73
 
71
- const yScale = scaleLinear({
72
- domain: [0, max],
73
- range: [yMax, 0],
74
- clamp: true
75
- })
74
+ // Use the yScale passed from useScales instead of creating a new one
76
75
 
77
76
  const colorScale = scaleOrdinal({
78
77
  domain: categories.map(d => d?.label),
@@ -1,4 +1,4 @@
1
- import React, { useContext } from 'react'
1
+ import { useContext } from 'react'
2
2
 
3
3
  // Local context and hooks
4
4
  import ConfigContext from '../../../ConfigContext'
@@ -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'
@@ -26,7 +25,7 @@ import _ from 'lodash'
26
25
  import { getBarData } from '../helpers/getBarData'
27
26
  import { getHorizontalBarHeights } from '../helpers/getBarHeights'
28
27
 
29
- export const BarChartHorizontal = () => {
28
+ const BarChartHorizontal = () => {
30
29
  const { xScale, yScale, yMax, seriesScale, barChart } = useContext<BarChartContextValues>(BarChartContext)
31
30
  const {
32
31
  isHorizontal,
@@ -36,6 +35,7 @@ export const BarChartHorizontal = () => {
36
35
  isLabelBelowBar,
37
36
  lollipopBarWidth,
38
37
  lollipopShapeSize,
38
+ labelFontSize,
39
39
  getHighlightedBarColorByValue,
40
40
  getHighlightedBarByValue,
41
41
  getAdditionalColumn,
@@ -54,13 +54,11 @@ export const BarChartHorizontal = () => {
54
54
  formatDate,
55
55
  parseDate,
56
56
  setSharedFilter,
57
- currentViewport
57
+ vizViewport
58
58
  } = useContext<ChartContext>(ConfigContext)
59
59
 
60
60
  const { HighLightedBarUtils } = useHighlightedBars(config)
61
61
 
62
- const LABEL_FONT_SIZE = isMobileFontViewport(currentViewport) ? 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 @@ export 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 @@ export 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 @@ export 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 @@ export 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 @@ export 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 @@ export 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 @@ export 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,13 +17,12 @@ 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'
24
23
  import { getBarData } from '../helpers/getBarData'
25
24
 
26
- export const BarChartVertical = () => {
25
+ const BarChartVertical = () => {
27
26
  const { xScale, yScale, xMax, yMax, seriesScale, convertLineToBarGraph, barChart } =
28
27
  useContext<BarChartContextValues>(BarChartContext)
29
28
  const {
@@ -32,6 +31,7 @@ export const BarChartVertical = () => {
32
31
  getAdditionalColumn,
33
32
  getHighlightedBarByValue,
34
33
  getHighlightedBarColorByValue,
34
+ labelFontSize,
35
35
  lollipopBarWidth,
36
36
  lollipopShapeSize,
37
37
  onMouseLeaveBar,
@@ -46,6 +46,7 @@ export const BarChartVertical = () => {
46
46
  colorScale,
47
47
  config,
48
48
  currentViewport,
49
+ vizViewport,
49
50
  dashboardConfig,
50
51
  tableData,
51
52
  formatDate,
@@ -58,8 +59,6 @@ export const BarChartVertical = () => {
58
59
 
59
60
  const { HighLightedBarUtils } = useHighlightedBars(config)
60
61
 
61
- const LABEL_FONT_SIZE = isMobileFontViewport(currentViewport) ? 13 : 16
62
-
63
62
  const root = document.documentElement
64
63
 
65
64
  let data = transformedData
@@ -241,7 +240,7 @@ export const BarChartVertical = () => {
241
240
  barWidth,
242
241
  isVertical: true,
243
242
  yAxisValue,
244
- labelFontSize: LABEL_FONT_SIZE
243
+ labelFontSize: labelFontSize
245
244
  })
246
245
  // configure colors
247
246
  let labelColor = APP_FONT_COLOR
@@ -480,7 +479,7 @@ export const BarChartVertical = () => {
480
479
  verticalAnchor={verticalAnchor}
481
480
  fill={fillColor}
482
481
  textAnchor='middle'
483
- fontSize={LABEL_FONT_SIZE}
482
+ fontSize={labelFontSize}
484
483
  >
485
484
  {pd.iconCode}
486
485
  </Text>
@@ -493,7 +492,7 @@ export const BarChartVertical = () => {
493
492
  y={barY - BAR_LABEL_PADDING}
494
493
  fill={labelColor}
495
494
  textAnchor='middle'
496
- fontSize={LABEL_FONT_SIZE}
495
+ fontSize={labelFontSize}
497
496
  >
498
497
  {testZeroValue(bar.value) ? '' : barDefaultLabel}
499
498
  </Text>
@@ -504,7 +503,7 @@ export const BarChartVertical = () => {
504
503
  y={barY - BAR_LABEL_PADDING}
505
504
  fill={labelColor}
506
505
  textAnchor='middle'
507
- fontSize={config.isLollipopChart ? null : LABEL_FONT_SIZE}
506
+ fontSize={config.isLollipopChart ? null : labelFontSize}
508
507
  >
509
508
  {absentDataLabel}
510
509
  </Text>
@@ -567,7 +566,7 @@ export const BarChartVertical = () => {
567
566
  }}
568
567
  </BarGroup>
569
568
 
570
- <Regions xScale={xScale} yMax={yMax} barWidth={barWidth} totalBarsInGroup={totalBarsInGroup} />
569
+ <Regions xScale={xScale} yMax={yMax} barWidth={barWidth} totalBarsInGroup={totalBarsInGroup} xMax={xMax} />
571
570
  </Group>
572
571
  )
573
572
  )
@@ -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
@@ -5,10 +5,20 @@ import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
5
5
  import { getPaletteColors } from '@cdc/core/helpers/palettes/utils'
6
6
  import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
7
7
  import { getVizSubType, getVizTitle } from '@cdc/core/helpers/metrics/utils'
8
+ import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
8
9
 
9
10
  export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, configContext) => {
10
- const { config, colorPalettes, tableData, updateConfig, parseDate, formatDate, seriesHighlight, interactionLabel } =
11
- configContext
11
+ const {
12
+ config,
13
+ colorPalettes,
14
+ tableData,
15
+ updateConfig,
16
+ parseDate,
17
+ formatDate,
18
+ seriesHighlight,
19
+ interactionLabel,
20
+ vizViewport
21
+ } = configContext
12
22
  const { orientation } = config
13
23
  const dispatch = useContext(ChartDispatchContext)
14
24
  const [hoveredBar, setHoveredBar] = useState(null)
@@ -20,6 +30,7 @@ export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, confi
20
30
  const isLabelBelowBar = config.yAxis.labelPlacement === 'Below Bar'
21
31
  const displayNumbersOnBar = config.yAxis.displayNumbersOnBar
22
32
  const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
33
+ const labelFontSize = isMobileFontViewport(vizViewport) ? 13 : 16
23
34
 
24
35
  const isRounded = config.barStyle === 'rounded'
25
36
  const isStacked = config.visualizationSubType === 'stacked'
@@ -241,6 +252,7 @@ export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, confi
241
252
  stackCount,
242
253
  barStackedSeriesKeys,
243
254
  hasMultipleSeries,
255
+ labelFontSize,
244
256
  applyRadius,
245
257
  assignColorsToValues,
246
258
  getHighlightedBarColorByValue,
@@ -29,7 +29,7 @@ export const handleTooltip = (boxplot, columnCategory, key, q1, q3, median, iqr,
29
29
  `
30
30
  }
31
31
 
32
- export const calculateBoxPlotStats = (values: number[]) => {
32
+ const calculateBoxPlotStats = (values: number[]) => {
33
33
  if (!values || values.length === 0) return {}
34
34
 
35
35
  // Sort the values
@@ -82,7 +82,7 @@ const getValuesBySeriesKey = (group: string, config, data) => {
82
82
  }
83
83
 
84
84
  // Helper to calculate outliers based on IQR
85
- export const calculateOutliers = (values: number[], firstQuartile: number, thirdQuartile: number) => {
85
+ const calculateOutliers = (values: number[], firstQuartile: number, thirdQuartile: number) => {
86
86
  const iqr = thirdQuartile - firstQuartile
87
87
  const lowerBound = firstQuartile - 1.5 * iqr
88
88
  const upperBound = thirdQuartile + 1.5 * iqr
@@ -90,7 +90,7 @@ export const calculateOutliers = (values: number[], firstQuartile: number, third
90
90
  }
91
91
 
92
92
  // Helper to calculate non-outliers based on IQR
93
- export const calculateNonOutliers = (values: number[], firstQuartile: number, thirdQuartile: number): number[] => {
93
+ const calculateNonOutliers = (values: number[], firstQuartile: number, thirdQuartile: number): number[] => {
94
94
  const iqr = thirdQuartile - firstQuartile
95
95
  const lowerBound = firstQuartile - 1.5 * iqr
96
96
  const upperBound = thirdQuartile + 1.5 * iqr