@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
@@ -1,6 +1,6 @@
1
- import { useRef } from 'react'
2
1
  import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
3
2
  import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
3
+ import { hasTrackedHover, markHoverTracked } from '../utils/analyticsTracking'
4
4
 
5
5
  type UseChartHoverAnalyticsParams = {
6
6
  config: any
@@ -9,14 +9,16 @@ type UseChartHoverAnalyticsParams = {
9
9
 
10
10
  /**
11
11
  * Hook to track analytics when user enters the chart area
12
- * Fires once per chart entry, not on every hover interaction
12
+ * Fires once per visualization, persists across component remounts
13
13
  */
14
14
  export const useChartHoverAnalytics = ({ config, interactionLabel = '' }: UseChartHoverAnalyticsParams) => {
15
- const hasTrackedRef = useRef(false)
16
-
17
15
  const handleChartMouseEnter = () => {
18
- // Only track if we have an interaction label and haven't tracked yet
19
- if (!interactionLabel || hasTrackedRef.current) return
16
+ // Only track if we have an interaction label
17
+ if (!interactionLabel) return
18
+
19
+ // Use unique ID to track per visualization
20
+ const vizId = String(config.runtime.uniqueId)
21
+ if (hasTrackedHover(vizId)) return
20
22
 
21
23
  // Publish the analytics event
22
24
  publishAnalyticsEvent({
@@ -29,12 +31,11 @@ export const useChartHoverAnalytics = ({ config, interactionLabel = '' }: UseCha
29
31
  })
30
32
 
31
33
  // Mark as tracked so we don't fire again
32
- hasTrackedRef.current = true
34
+ markHoverTracked(vizId)
33
35
  }
34
36
 
35
37
  const handleChartMouseLeave = () => {
36
- // Reset tracking when mouse leaves so next entry will track
37
- hasTrackedRef.current = false
38
+ // No-op: We no longer reset tracking on mouse leave
38
39
  }
39
40
 
40
41
  return {
@@ -8,6 +8,7 @@ interface UseProgrammaticTooltipProps {
8
8
  setShowHoverLine: (show: boolean) => void
9
9
  handleTooltipMouseOver: (event: MouseEvent, additionalChartData?: any) => void
10
10
  hideTooltip: () => void
11
+ setSynchronizedXValue?: (value: any) => void
11
12
  }
12
13
 
13
14
  /**
@@ -21,7 +22,8 @@ export const useProgrammaticTooltip = ({
21
22
  setPoint,
22
23
  setShowHoverLine,
23
24
  handleTooltipMouseOver,
24
- hideTooltip
25
+ hideTooltip,
26
+ setSynchronizedXValue
25
27
  }: UseProgrammaticTooltipProps) => {
26
28
  // Internal SVG ref for DOM manipulation
27
29
  const internalSvgRef = useRef<SVGSVGElement>(null)
@@ -50,6 +52,15 @@ export const useProgrammaticTooltip = ({
50
52
  * @param {number} yCoordinate - Exact Y coordinate to use
51
53
  */
52
54
  triggerTooltipAtDataValue: (xAxisValue: any, yCoordinate: number) => {
55
+ // Warming Stripes positions rects by index (with data sampling), not via xScale,
56
+ // so synthetic mouse events won't map to the correct data points.
57
+ // Route through synchronizedXValue state instead, which WarmingStripes
58
+ // resolves to the matching stripe and calls showTooltip directly.
59
+ if (config.visualizationType === 'Warming Stripes') {
60
+ setSynchronizedXValue?.(xAxisValue)
61
+ return
62
+ }
63
+
53
64
  const pixelX = getCoordinateFromXValue(xAxisValue)
54
65
  const adjustedX = pixelX + Number(config.yAxis.size || 0)
55
66
 
@@ -86,10 +97,20 @@ export const useProgrammaticTooltip = ({
86
97
  hideTooltip: () => {
87
98
  hideTooltip()
88
99
  setShowHoverLine(false)
100
+ setSynchronizedXValue?.(null)
89
101
  }
90
102
  })
91
103
  },
92
- [getCoordinateFromXValue, config.yAxis.size, setPoint, setShowHoverLine, handleTooltipMouseOver, hideTooltip]
104
+ [
105
+ getCoordinateFromXValue,
106
+ config.yAxis.size,
107
+ config.visualizationType,
108
+ setPoint,
109
+ setShowHoverLine,
110
+ handleTooltipMouseOver,
111
+ hideTooltip,
112
+ setSynchronizedXValue
113
+ ]
93
114
  )
94
115
 
95
116
  return internalSvgRef
@@ -121,6 +121,11 @@ const useScales = (properties: useScaleProps) => {
121
121
  range: [0, xMax]
122
122
  })
123
123
 
124
+ let yScaleAnnotation = scaleLinear({
125
+ domain: [0, 100],
126
+ range: [0, yMax]
127
+ })
128
+
124
129
  // handle Horizontal bars
125
130
  if (isHorizontal) {
126
131
  xScale = composeXScale({ min: min * 1.03, max, xMax, config })
@@ -133,7 +138,17 @@ const useScales = (properties: useScaleProps) => {
133
138
  // handle Vertical bars
134
139
  if (!isHorizontal) {
135
140
  xScale = composeScaleBand(xAxisDataMapped, [0, xMax], paddingRange)
136
- yScale = composeYScale({ min, max, yMax, config, leftMax })
141
+ // For categorical y-axis, use [0, max] domain and [yMax, 0] range to match CategoricalYAxis
142
+ // This ensures line data aligns with categorical bars and bars go to 100% height
143
+ if (config.yAxis.type === 'categorical') {
144
+ yScale = scaleLinear({
145
+ domain: [0, max],
146
+ range: [yMax, 0],
147
+ clamp: true
148
+ })
149
+ } else {
150
+ yScale = composeYScale({ min, max, yMax, config, leftMax })
151
+ }
137
152
  seriesScale = composeScaleBand(seriesDomain, [0, xScale.bandwidth()], 0)
138
153
  }
139
154
 
@@ -362,6 +377,7 @@ const useScales = (properties: useScaleProps) => {
362
377
  g2xScale,
363
378
  xScaleNoPadding,
364
379
  xScaleAnnotation,
380
+ yScaleAnnotation,
365
381
  min,
366
382
  max,
367
383
  leftMax,
@@ -377,6 +393,7 @@ const getFirstDayOfMonth = ms => {
377
393
  }
378
394
 
379
395
  const dateFormatHasMonthButNoDays = dateFormat => {
396
+ if (!dateFormat) return false
380
397
  return (
381
398
  (dateFormat.includes('%b') ||
382
399
  dateFormat.includes('%B') ||
@@ -105,7 +105,7 @@ export const useTooltip = props => {
105
105
  addColCommas: column.commas
106
106
  }
107
107
 
108
- const pieColumnData = additionalChartData?.arc?.data[column.name]
108
+ const pieColumnData = additionalChartData?.data?.[column.name]
109
109
  const columnData =
110
110
  config.tooltips.singleSeries && visualizationType === 'Line'
111
111
  ? resolvedScaleValues.filter(
@@ -145,7 +145,7 @@ export const useTooltip = props => {
145
145
  tooltipItems.push(
146
146
  [config.xAxis.dataKey, pieData[config.xAxis.dataKey]],
147
147
  [
148
- config.runtime.yAxis.dataKey,
148
+ config.runtime.yAxis.label || config.runtime.yAxis.dataKey,
149
149
  showPiePercent ? pctString(actualPieValue) : formatNumber(pieData[config.runtime.yAxis.dataKey])
150
150
  ],
151
151
  showPiePercent ? [] : ['Percent', pctString(pctOf360)]
@@ -260,11 +260,30 @@ export const useTooltip = props => {
260
260
  const dataXPosition = eventSvgCoords.x + 10
261
261
  const dataYPosition = eventSvgCoords.y
262
262
 
263
+ // Helper to strip <a> tags and only show link text
264
+ function stripLinkTags(str) {
265
+ if (typeof str !== 'string') return str
266
+ // Remove HTML <a> tags, keep inner text
267
+ return str.replace(/<a [^>]*>(.*?)<\/a>/gi, '$1')
268
+ }
269
+
270
+ // Strip link tags from all tooltip values
271
+ const cleanTooltipItems = [...tooltipItems, ...additionalTooltipItems].map(item => {
272
+ // item can be [key, value] or [key, value, axisPosition]
273
+ if (Array.isArray(item)) {
274
+ // Only strip from value (item[1])
275
+ const newItem = [...item]
276
+ newItem[1] = stripLinkTags(newItem[1])
277
+ return newItem
278
+ }
279
+ return item
280
+ })
281
+
263
282
  const tooltipInformation = {
264
283
  tooltipLeft: dataXPosition,
265
284
  tooltipTop: dataYPosition,
266
285
  tooltipData: {
267
- data: [...tooltipItems, ...additionalTooltipItems],
286
+ data: cleanTooltipItems,
268
287
  dataXPosition,
269
288
  dataYPosition
270
289
  }
@@ -523,14 +542,16 @@ export const useTooltip = props => {
523
542
  includedSeries.push(...dynamicDataCategories)
524
543
 
525
544
  if (config.visualizationType === 'Forecasting') {
526
- config.runtime.series.map(s => {
527
- s.confidenceIntervals.map(c => {
528
- if (c.showInTooltip) {
529
- includedSeries.push(c.high)
530
- includedSeries.push(c.low)
531
- }
545
+ config.runtime.series
546
+ .filter(s => s.type === 'Forecasting' && s.confidenceIntervals)
547
+ .forEach(s => {
548
+ s.confidenceIntervals.forEach(c => {
549
+ if (c.showInTooltip) {
550
+ includedSeries.push(c.high)
551
+ includedSeries.push(c.low)
552
+ }
553
+ })
532
554
  })
533
- })
534
555
  }
535
556
 
536
557
  const colNames = Object.values(config.columns).map(column => column.name)
@@ -571,6 +592,7 @@ export const useTooltip = props => {
571
592
  case 'Line':
572
593
  case 'Area Chart':
573
594
  case 'Pie':
595
+ case 'Horizon Chart':
574
596
  return common
575
597
  case 'Combo':
576
598
  return [...common, ...ciItems]
@@ -579,6 +601,8 @@ export const useTooltip = props => {
579
601
 
580
602
  case 'Bar':
581
603
  return orientation === 'vertical' ? common : [runtime.yAxis.dataKey, ...runtime?.seriesKeys]
604
+ case 'Warming Stripes':
605
+ return common
582
606
  default:
583
607
  throw new Error('No visualization type found in handleTooltipMouseOver')
584
608
  }
@@ -1,8 +1,4 @@
1
1
  .data-table-container {
2
- &.brush-active {
3
- margin: 80px 0 0;
4
- }
5
-
6
2
  width: 100%;
7
3
  }
8
4
 
@@ -50,9 +50,6 @@
50
50
  .subtext,
51
51
  .subtext--responsive-ticks,
52
52
  .section-subtext {
53
- &--brush-active {
54
- margin-top: 3rem !important;
55
- }
56
53
  }
57
54
 
58
55
  .type-pie {
@@ -149,6 +146,11 @@
149
146
  cursor: pointer;
150
147
  transition: 0.2s all;
151
148
 
149
+ &.not-clickable,
150
+ &.not-clickable .legend-item {
151
+ cursor: default;
152
+ }
153
+
152
154
  &.inactive {
153
155
  opacity: 0.5;
154
156
  transition: 0.2s all;
@@ -325,6 +327,23 @@
325
327
  margin-bottom: 2.5em;
326
328
  }
327
329
 
330
+ // Brush touch support
331
+ .brush-overlay {
332
+ touch-action: none;
333
+ -webkit-touch-callout: none;
334
+ -webkit-user-select: none;
335
+ user-select: none;
336
+
337
+ .visx-brush,
338
+ .visx-brush-overlay,
339
+ .visx-brush-selection,
340
+ .visx-brush-handle-left,
341
+ .visx-brush-handle-right {
342
+ touch-action: none;
343
+ cursor: pointer;
344
+ }
345
+ }
346
+
328
347
  svg.dragging-annotation * {
329
348
  user-select: none;
330
349
  }
@@ -0,0 +1,68 @@
1
+ # Config Selectors
2
+
3
+ Typed selector functions and hooks for extracting memoizable slices of chart configuration.
4
+
5
+ ## Purpose
6
+
7
+ These selectors help:
8
+ 1. **Reduce coupling** - Components depend on specific config slices, not the entire config object
9
+ 2. **Improve memoization** - Hooks use specific dependencies instead of the full config
10
+ 3. **Enhance testability** - Selectors can be easily mocked in tests
11
+ 4. **Document config usage** - Selectors serve as documentation for which config properties are used together
12
+
13
+ ## Usage
14
+
15
+ ### Direct Selectors
16
+
17
+ ```typescript
18
+ import { selectAxisConfig, selectVisualizationConfig } from '../selectors'
19
+
20
+ // In a component
21
+ const axisConfig = selectAxisConfig(config)
22
+ // Returns: { xAxis, yAxis, runtime: { xAxis, yAxis, originalXAxis } }
23
+ ```
24
+
25
+ ### Hook Versions (Memoized)
26
+
27
+ ```typescript
28
+ import { useAxisConfig, useVisualizationConfig } from '../selectors'
29
+
30
+ // In a component - automatically memoized
31
+ const axisConfig = useAxisConfig(config)
32
+ const vizConfig = useVisualizationConfig(config)
33
+ ```
34
+
35
+ ## Available Selectors
36
+
37
+ | Selector | Hook | Description |
38
+ |----------|------|-------------|
39
+ | `selectAxisConfig` | `useAxisConfig` | X/Y axis configuration |
40
+ | `selectVisualizationConfig` | `useVisualizationConfig` | Chart type and orientation |
41
+ | `selectDisplayConfig` | `useDisplayConfig` | Animation, debug flags |
42
+ | `selectTooltipConfig` | `useTooltipConfig` | Tooltip settings |
43
+ | `selectLegendConfig` | - | Legend position and visibility |
44
+ | `selectDataFormatConfig` | - | Number formatting options |
45
+ | `selectChartMessages` | - | UI message strings |
46
+ | `selectBrushConfig` | - | Brush/filter settings |
47
+ | `selectForestPlotConfig` | - | Forest plot specific options |
48
+ | `selectSmallMultiplesConfig` | - | Small multiples mode |
49
+
50
+ ## Type Exports
51
+
52
+ Each selector has a corresponding type export:
53
+
54
+ ```typescript
55
+ import type { AxisConfig, VisualizationConfig } from '../selectors'
56
+ ```
57
+
58
+ ## Adding New Selectors
59
+
60
+ 1. Add the selector function that extracts config properties
61
+ 2. Optionally add a hook version with proper dependencies
62
+ 3. Export the type using `ReturnType<typeof selectXxx>`
63
+ 4. Update this README
64
+
65
+ ## Notes
66
+
67
+ - Selectors are created but integration into LinearChart is deferred
68
+ - Future work: gradually replace direct `config.x.y` accesses with selector usage
@@ -75,5 +75,7 @@ export const reducer = (state: ChartState, action: ChartActions): ChartState =>
75
75
  return { ...state, isDraggingAnnotation: action.payload }
76
76
  case 'SET_BRUSH_DATA':
77
77
  return { ...state, brushData: action.payload }
78
+ default:
79
+ return state
78
80
  }
79
81
  }
@@ -7,5 +7,5 @@ describe('Chart', () => {
7
7
  const pkgDir = path.join(__dirname, '..')
8
8
  const result = testStandaloneBuild(pkgDir)
9
9
  expect(result).toBe(true)
10
- })
10
+ }, 300000)
11
11
  })
@@ -1,6 +1,7 @@
1
1
  import { Axis } from '@cdc/core/types/Axis'
2
2
  import { MarkupConfig } from '@cdc/core/types/MarkupVariable'
3
3
  import { type ForestPlotConfigSettings } from './ForestPlot'
4
+ import { type HorizonConfigSettings } from './Horizon'
4
5
  import { type Column } from '@cdc/core/types/Column'
5
6
  import { type Series } from '@cdc/core/types/Series'
6
7
  import { Runtime } from '@cdc/core/types/Runtime'
@@ -18,6 +19,7 @@ type General = CoreGeneral & {
18
19
  customColors?: string[]
19
20
  customColorsOrdered?: string[]
20
21
  }
22
+ useIntelligentLineChartLabels?: boolean
21
23
  }
22
24
  import { type Link } from './../components/Sankey/types'
23
25
  import { type DataDescription } from '@cdc/core/types/DataDescription'
@@ -39,15 +41,18 @@ export type VisualizationType =
39
41
  | 'Box Plot'
40
42
  | 'Deviation Bar'
41
43
  | 'Forest Plot'
44
+ | 'Horizon Chart'
42
45
  | 'Line'
43
46
  | 'Paired Bar'
44
47
  | 'Pie'
48
+ | 'Radar'
45
49
  | 'Scatter Plot'
46
50
  | 'Spark Line'
47
51
  | 'Combo'
48
52
  | 'Forecasting'
49
53
  | 'Sankey'
50
54
  | 'Bump Chart'
55
+ | 'Warming Stripes'
51
56
  export interface PreliminaryDataItem {
52
57
  column: string
53
58
  displayLegend: boolean
@@ -103,6 +108,7 @@ type Legend = CoreLegend & {
103
108
  order: 'dataColumn' | 'asc' | 'desc'
104
109
  orderedValues: Label[]
105
110
  tickRotation: string
111
+ warmingStripesIntervals?: number
106
112
  hideBorder: {
107
113
  side: boolean
108
114
  topBottom: boolean
@@ -170,6 +176,7 @@ export type AllChartsConfig = {
170
176
  mobileVertical: number
171
177
  }
172
178
  highlightedBarValues: { value: any; color: string; borderWidth: number; legendLabel: string }[]
179
+ horizon?: HorizonConfigSettings
173
180
  introText: string
174
181
  isLollipopChart: boolean
175
182
  isLegendValue: boolean
@@ -218,6 +225,7 @@ export type AllChartsConfig = {
218
225
  table: Table
219
226
  tipRounding: string
220
227
  title: string
228
+ titleStyle?: 'legacy' | 'large' | 'small'
221
229
  tooltips: {
222
230
  singleSeries: boolean
223
231
  opacity: number
@@ -264,6 +272,19 @@ export type AllChartsConfig = {
264
272
  default: string
265
273
  }
266
274
  }
275
+ radar?: {
276
+ gridRings: number
277
+ showGridRings: boolean
278
+ gridRingStyle: 'polygons' | 'circles'
279
+ scaleMin: number
280
+ scaleMax: number | string
281
+ showFill: boolean
282
+ fillOpacity: number
283
+ showPoints: boolean
284
+ pointRadius: number
285
+ strokeWidth: number
286
+ axisLabelOffset: number
287
+ }
267
288
  } & MarkupConfig
268
289
 
269
290
  type ForestPlotConfig = {
@@ -36,6 +36,7 @@ type SharedChartContext = {
36
36
  setLegendIsolateValues?: Function
37
37
  svgRef?: React.RefObject<SVGSVGElement>
38
38
  handleSmallMultipleHover?: (xAxisValue: any, yCoordinate: number) => void
39
+ visibleAnnotations?: Annotation[]
39
40
  }
40
41
 
41
42
  // Line Chart Specific Context
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Configuration settings for Horizon Chart visualization
3
+ *
4
+ * Horizon charts display multiple time series as stacked rows,
5
+ * with each series shown as layered bands to compress vertical space.
6
+ */
7
+
8
+ /**
9
+ * Palette configuration structure (matches general.palette structure)
10
+ */
11
+ export type HorizonPalette = {
12
+ name?: string
13
+ version?: '1.0' | '2.0'
14
+ isReversed?: boolean
15
+ customColors?: string[]
16
+ customColorsOrdered?: string[]
17
+ }
18
+
19
+ export type HorizonConfigSettings = {
20
+ /**
21
+ * Number of horizon layers/bands per series
22
+ * More layers = finer granularity, but denser visual
23
+ * @default 4
24
+ */
25
+ numLayers: number
26
+
27
+ /**
28
+ * Rendering mode for data values
29
+ * - 'offset': All values treated as positive (absolute values)
30
+ * - 'mirror': Negative values mirrored with contrasting colors. Not currently used.
31
+ * @default 'offset'
32
+ */
33
+ mode: 'offset' | 'mirror'
34
+
35
+ /**
36
+ * Gap in pixels between series bands
37
+ * Bands will fill available chart height minus gaps
38
+ * @default 15
39
+ */
40
+ bandGap: number
41
+
42
+ /**
43
+ * Padding in pixels below the bottom band (above x-axis)
44
+ * @default 15
45
+ */
46
+ bottomPadding: number
47
+
48
+ /**
49
+ * Optional secondary palette for negative values (mirror mode only)
50
+ * Uses same structure as general.palette for consistency
51
+ * Not exposed in UI initially - scaffolded for future use
52
+ */
53
+ negativePalette?: HorizonPalette
54
+ }
55
+
56
+ /**
57
+ * Default configuration for Horizon charts
58
+ */
59
+ export const HORIZON_DEFAULTS: HorizonConfigSettings = {
60
+ numLayers: 4,
61
+ mode: 'offset',
62
+ bandGap: 15,
63
+ bottomPadding: 15
64
+ }
@@ -4,4 +4,5 @@ export type Label = {
4
4
  text: string
5
5
  value: string
6
6
  icon?: any
7
+ colors?: string[]
7
8
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Shared analytics tracking to ensure events only fire once per visualization
3
+ * Tracks at module level to persist across component remounts
4
+ */
5
+ const trackedHoverEvents = new Set<string>()
6
+
7
+ /**
8
+ * Check if a hover event has been tracked for a given visualization ID
9
+ */
10
+ export const hasTrackedHover = (vizId: string): boolean => {
11
+ return trackedHoverEvents.has(vizId)
12
+ }
13
+
14
+ /**
15
+ * Mark a visualization as having had its hover event tracked
16
+ */
17
+ export const markHoverTracked = (vizId: string): void => {
18
+ trackedHoverEvents.add(vizId)
19
+ }