@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,230 @@
1
+ import { useContext, useState, useCallback, useEffect } from 'react'
2
+ import ConfigContext from '../../ConfigContext'
3
+ import { Group } from '@visx/group'
4
+ import { scaleSequential } from 'd3-scale'
5
+ import { interpolateRgbBasis } from 'd3-interpolate'
6
+ import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
7
+ import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
8
+ import { filterChartColorPalettes } from '@cdc/core/helpers/filterColorPalettes'
9
+ import { getFallbackColorPalette, migratePaletteWithMap } from '@cdc/core/helpers/palettes/utils'
10
+ import { paletteMigrationMap } from '@cdc/core/helpers/palettes/migratePaletteName'
11
+ import { hasTrackedHover, markHoverTracked } from '../../utils/analyticsTracking'
12
+
13
+ type WarmingStripesProps = {
14
+ xScale: any
15
+ yScale: any
16
+ xMax: number
17
+ yMax: number
18
+ synchronizedXValue?: any
19
+ showTooltip: (args: any) => void
20
+ handleTooltipMouseOff: () => void
21
+ }
22
+
23
+ const WarmingStripes = ({
24
+ xMax,
25
+ yMax,
26
+ synchronizedXValue,
27
+ showTooltip,
28
+ handleTooltipMouseOff
29
+ }: WarmingStripesProps) => {
30
+ const {
31
+ transformedData: data,
32
+ config,
33
+ formatNumber,
34
+ interactionLabel,
35
+ currentViewport,
36
+ handleSmallMultipleHover
37
+ } = useContext(ConfigContext)
38
+
39
+ const [currentHover, setCurrentHover] = useState<number | null>(null)
40
+
41
+ // Get the data key for the temperature/anomaly values
42
+ // Use the first series key as the value column
43
+ const valueKey = config.runtime.seriesKeys?.[0]
44
+ const xAxisDataKey = config.runtime.originalXAxis?.dataKey || config.xAxis?.dataKey
45
+
46
+ // Determine max stripes based on viewport
47
+ const isMobile = ['xxs', 'xs', 'sm', 'md'].includes(currentViewport)
48
+ const maxStripes = isMobile ? 60 : 200
49
+
50
+ // Sample data if we have more than the max allowed stripes
51
+ let displayData = data || []
52
+ if (displayData.length > maxStripes) {
53
+ const step = displayData.length / maxStripes
54
+ displayData = []
55
+ for (let i = 0; i < maxStripes; i++) {
56
+ const index = Math.floor(i * step)
57
+ displayData.push(data[index])
58
+ }
59
+ }
60
+
61
+ // Calculate the min and max values for the color scale
62
+ const values = displayData.map(d => Number(d[valueKey])).filter(v => !isNaN(v))
63
+ const minValue = Math.min(...values)
64
+ const maxValue = Math.max(...values)
65
+
66
+ // Get the color palette from config
67
+ const colorPalettes = filterChartColorPalettes(config)
68
+ const configPalette = config.general?.palette?.name
69
+ const migratedPaletteName = configPalette ? configPalette : getFallbackColorPalette(config)
70
+
71
+ // Check if the palette name ends with 'reverse' and get the base palette
72
+ const isReversedPalette = migratedPaletteName?.endsWith('reverse')
73
+ const basePaletteName = isReversedPalette ? migratedPaletteName.slice(0, -7) : migratedPaletteName
74
+
75
+ let palette =
76
+ colorPalettes[migratePaletteWithMap(basePaletteName, paletteMigrationMap, false)] ||
77
+ colorPalettes[basePaletteName] ||
78
+ colorPalettes[configPalette]
79
+
80
+ // Fallback to a default diverging palette if none found
81
+ if (!palette || palette.length < 2) {
82
+ console.warn(`Palette "${configPalette}" not found or invalid, falling back to default`)
83
+ // Use a default blue to red palette
84
+ palette = [
85
+ '#053061',
86
+ '#2166ac',
87
+ '#4393c3',
88
+ '#92c5de',
89
+ '#d1e5f0',
90
+ '#f7f7f7',
91
+ '#fddbc7',
92
+ '#f4a582',
93
+ '#d6604d',
94
+ '#b2182b',
95
+ '#67001f'
96
+ ]
97
+ }
98
+
99
+ // Create a sequential color scale using the palette colors
100
+ // Apply reverse if configured (either via isReversed flag or 'reverse' suffix in name)
101
+ const shouldReverse = config.general?.palette?.isReversed || isReversedPalette
102
+ const finalPalette = shouldReverse ? [...palette].reverse() : palette
103
+ const colorScale = scaleSequential(interpolateRgbBasis(finalPalette)).domain([minValue, maxValue])
104
+
105
+ // Calculate stripe width based on available space and display data
106
+ const stripeWidth = xMax / displayData.length
107
+
108
+ // Build tooltip data in COVE format and trigger showTooltip
109
+ const showStripeTooltip = useCallback(
110
+ (item: any, index: number, mouseY?: number) => {
111
+ const value = Number(item[valueKey])
112
+ if (isNaN(value)) return
113
+
114
+ const formattedValue = formatNumber(value, 'left')
115
+
116
+ // Pass raw x-axis value — TooltipListItem handles date formatting
117
+ const tooltipItems = [
118
+ [xAxisDataKey, item[xAxisDataKey]],
119
+ [valueKey, formattedValue, 'left']
120
+ ]
121
+
122
+ const dataXPosition = index * stripeWidth + stripeWidth / 2 + Number(config.yAxis.size)
123
+ const dataYPosition = mouseY ?? yMax / 2
124
+
125
+ showTooltip({
126
+ tooltipLeft: dataXPosition + 10,
127
+ tooltipTop: dataYPosition,
128
+ tooltipData: {
129
+ data: tooltipItems,
130
+ dataXPosition: dataXPosition + 10,
131
+ dataYPosition
132
+ }
133
+ })
134
+ },
135
+ [valueKey, xAxisDataKey, stripeWidth, config.yAxis.size, yMax, formatNumber, showTooltip]
136
+ )
137
+
138
+ // Handle incoming synchronized tooltip from sibling small multiple tiles
139
+ useEffect(() => {
140
+ if (synchronizedXValue === null || synchronizedXValue === undefined) {
141
+ setCurrentHover(null)
142
+ handleTooltipMouseOff()
143
+ return
144
+ }
145
+
146
+ const matchIndex = displayData.findIndex(item => String(item[xAxisDataKey]) === String(synchronizedXValue))
147
+
148
+ if (matchIndex >= 0) {
149
+ setCurrentHover(matchIndex)
150
+ showStripeTooltip(displayData[matchIndex], matchIndex)
151
+ }
152
+ }, [synchronizedXValue])
153
+
154
+ if (!valueKey || !xAxisDataKey || !data || data.length === 0) {
155
+ return null
156
+ }
157
+
158
+ return (
159
+ <Group className='warming-stripes' left={config.yAxis.size}>
160
+ {displayData.map((item, index) => {
161
+ const value = Number(item[valueKey])
162
+ if (isNaN(value)) return null
163
+
164
+ const xPosition = index * stripeWidth
165
+ const fillColor = colorScale(value) as unknown as string
166
+ const isHovered = currentHover === index
167
+ const isMuted = currentHover !== null && !isHovered
168
+
169
+ return (
170
+ <rect
171
+ key={`stripe-${index}`}
172
+ x={xPosition}
173
+ y={0}
174
+ width={stripeWidth}
175
+ height={yMax}
176
+ fill={fillColor}
177
+ fillOpacity={isMuted ? 0.5 : 1}
178
+ stroke='none'
179
+ tabIndex={-1}
180
+ style={{ cursor: 'pointer', transition: 'fill-opacity 0.2s ease' }}
181
+ onMouseEnter={e => {
182
+ if (currentHover !== index) {
183
+ const vizId = String(config.runtime.uniqueId)
184
+ if (!hasTrackedHover(vizId)) {
185
+ publishAnalyticsEvent({
186
+ vizType: config?.type,
187
+ vizSubType: getVizSubType(config),
188
+ eventType: 'chart_hover',
189
+ eventAction: 'hover',
190
+ eventLabel: interactionLabel || 'unknown',
191
+ vizTitle: getVizTitle(config),
192
+ series: valueKey
193
+ })
194
+ markHoverTracked(vizId)
195
+ }
196
+ setCurrentHover(index)
197
+ }
198
+
199
+ // Show COVE tooltip using mouse Y position
200
+ const svgEl = (e.currentTarget as SVGRectElement).ownerSVGElement
201
+ const svgRect = svgEl?.getBoundingClientRect()
202
+ const mouseY = svgRect ? e.clientY - svgRect.top : yMax / 2
203
+ showStripeTooltip(item, index, mouseY)
204
+
205
+ if (handleSmallMultipleHover) {
206
+ handleSmallMultipleHover(item[xAxisDataKey], yMax / 2)
207
+ }
208
+ }}
209
+ onMouseMove={e => {
210
+ // Update tooltip Y position as mouse moves within the stripe
211
+ const svgEl = (e.currentTarget as SVGRectElement).ownerSVGElement
212
+ const svgRect = svgEl?.getBoundingClientRect()
213
+ const mouseY = svgRect ? e.clientY - svgRect.top : yMax / 2
214
+ showStripeTooltip(item, index, mouseY)
215
+ }}
216
+ onMouseLeave={() => {
217
+ setCurrentHover(null)
218
+ handleTooltipMouseOff()
219
+ if (handleSmallMultipleHover) {
220
+ handleSmallMultipleHover(null, null)
221
+ }
222
+ }}
223
+ />
224
+ )
225
+ })}
226
+ </Group>
227
+ )
228
+ }
229
+
230
+ export default WarmingStripes
@@ -0,0 +1,35 @@
1
+ .warming-stripes-gradient-legend {
2
+ margin: 1rem 0;
3
+ width: 100%;
4
+ }
5
+
6
+ .warming-stripes-gradient-legend__title {
7
+ font-weight: 600;
8
+ margin-bottom: 0.5rem;
9
+ font-size: 16px;
10
+ }
11
+
12
+ .warming-stripes-gradient-legend__description {
13
+ margin-top: 0.5rem;
14
+ margin-bottom: 1rem;
15
+ font-size: 14px;
16
+ color: #555;
17
+ }
18
+
19
+ .warming-stripes-gradient-legend__container {
20
+ width: 100%;
21
+ }
22
+
23
+ .warming-stripes-gradient-legend__svg {
24
+ display: block;
25
+ width: 100%;
26
+ overflow: visible;
27
+ }
28
+
29
+ .warming-stripes-gradient-legend__series-label {
30
+ text-align: center;
31
+ margin-top: 0.5rem;
32
+ font-size: 14px;
33
+ font-weight: 500;
34
+ color: #333;
35
+ }
@@ -0,0 +1,104 @@
1
+ import { useContext } from 'react'
2
+ import ConfigContext from '../../ConfigContext'
3
+ import { filterChartColorPalettes } from '@cdc/core/helpers/filterColorPalettes'
4
+ import { getFallbackColorPalette, migratePaletteWithMap } from '@cdc/core/helpers/palettes/utils'
5
+ import { paletteMigrationMap } from '@cdc/core/helpers/palettes/migratePaletteName'
6
+ import './WarmingStripesGradientLegend.css'
7
+
8
+ const WarmingStripesGradientLegend = () => {
9
+ const { transformedData: data, config, formatNumber } = useContext(ConfigContext)
10
+
11
+ const valueKey = config.runtime.seriesKeys?.[0]
12
+
13
+ if (!valueKey || !data || data.length === 0 || config.legend?.hide) {
14
+ return null
15
+ }
16
+
17
+ // Calculate min and max values
18
+ const values = data.map(d => Number(d[valueKey])).filter(v => !isNaN(v))
19
+ const minValue = Math.min(...values)
20
+ const maxValue = Math.max(...values)
21
+
22
+ // Get the color palette from config (same logic as WarmingStripes component)
23
+ const colorPalettes = filterChartColorPalettes(config)
24
+ const configPalette = config.general?.palette?.name
25
+ const migratedPaletteName = configPalette ? configPalette : getFallbackColorPalette(config)
26
+
27
+ const isReversedPalette = migratedPaletteName?.endsWith('reverse')
28
+ const basePaletteName = isReversedPalette ? migratedPaletteName.slice(0, -7) : migratedPaletteName
29
+
30
+ let palette =
31
+ colorPalettes[migratePaletteWithMap(basePaletteName, paletteMigrationMap, false)] ||
32
+ colorPalettes[basePaletteName] ||
33
+ colorPalettes[configPalette]
34
+
35
+ if (!palette || palette.length < 2) {
36
+ palette = [
37
+ '#053061',
38
+ '#2166ac',
39
+ '#4393c3',
40
+ '#92c5de',
41
+ '#d1e5f0',
42
+ '#f7f7f7',
43
+ '#fddbc7',
44
+ '#f4a582',
45
+ '#d6604d',
46
+ '#b2182b',
47
+ '#67001f'
48
+ ]
49
+ }
50
+
51
+ const shouldReverse = config.general?.palette?.isReversed || isReversedPalette
52
+ const finalPalette = shouldReverse ? [...palette].reverse() : palette
53
+
54
+ // Create gradient stops for SVG
55
+ const gradientStops = finalPalette.map((color, index) => {
56
+ const offset = (index / (finalPalette.length - 1)) * 100
57
+ return { offset: `${offset}%`, color }
58
+ })
59
+
60
+ const seriesLabel = config.runtime.seriesLabels?.[valueKey] || valueKey
61
+ const uniqueId = `warming-stripes-gradient-${config.runtime.uniqueId}`
62
+
63
+ return (
64
+ <div className='warming-stripes-gradient-legend'>
65
+ {config.legend?.label && <h3 className='warming-stripes-gradient-legend__title'>{config.legend.label}</h3>}
66
+ {config.legend?.description && (
67
+ <p className='warming-stripes-gradient-legend__description'>{config.legend.description}</p>
68
+ )}
69
+
70
+ <div className='warming-stripes-gradient-legend__container'>
71
+ <svg className='warming-stripes-gradient-legend__svg' height='50' width='100%'>
72
+ <defs>
73
+ <linearGradient id={uniqueId} x1='0%' y1='0%' x2='100%' y2='0%'>
74
+ {gradientStops.map((stop, index) => (
75
+ <stop key={index} offset={stop.offset} stopColor={stop.color} />
76
+ ))}
77
+ </linearGradient>
78
+ </defs>
79
+
80
+ {/* Border */}
81
+ <rect x='0' y='0' width='100%' height='25' fill='#d3d3d3' />
82
+
83
+ {/* Gradient bar */}
84
+ <rect x='1' y='1' width='calc(100% - 2px)' height='23' fill={`url(#${uniqueId})`} />
85
+
86
+ {/* Min label */}
87
+ <text x='0' y='40' fontSize='14' textAnchor='start' fill='#333'>
88
+ {formatNumber(minValue, 'left')}
89
+ </text>
90
+
91
+ {/* Max label */}
92
+ <text x='100%' y='40' fontSize='14' textAnchor='end' fill='#333'>
93
+ {formatNumber(maxValue, 'left')}
94
+ </text>
95
+ </svg>
96
+
97
+ {/* Series name centered below gradient */}
98
+ <div className='warming-stripes-gradient-legend__series-label'>{seriesLabel}</div>
99
+ </div>
100
+ </div>
101
+ )
102
+ }
103
+
104
+ export default WarmingStripesGradientLegend
@@ -0,0 +1,3 @@
1
+ import WarmingStripes from './WarmingStripes'
2
+
3
+ export default WarmingStripes
@@ -23,6 +23,7 @@ const createInitialState = () => {
23
23
  noData: 'No Data Available'
24
24
  },
25
25
  title: '',
26
+ titleStyle: 'small',
26
27
  showTitle: true,
27
28
  showDownloadMediaButton: false,
28
29
  theme: 'theme-blue',
@@ -44,7 +45,8 @@ const createInitialState = () => {
44
45
  showSuppressedSymbol: true,
45
46
  showZeroValueData: true,
46
47
  hideNullValue: true,
47
- palette: paletteDefaults
48
+ palette: paletteDefaults,
49
+ useIntelligentLineChartLabels: false
48
50
  },
49
51
  padding: {
50
52
  left: 5,
@@ -139,7 +141,8 @@ const createInitialState = () => {
139
141
  padding: 5,
140
142
  showYearsOnce: false,
141
143
  sortByRecentDate: false,
142
- brushActive: false
144
+ brushActive: false,
145
+ brushDefaultRecentDateCount: undefined
143
146
  },
144
147
  table: {
145
148
  label: 'Data Table',
@@ -296,6 +299,18 @@ const createInitialState = () => {
296
299
  area: {
297
300
  isStacked: false
298
301
  },
302
+ radar: {
303
+ gridRings: 5,
304
+ showGridRings: true,
305
+ gridRingStyle: 'polygons',
306
+ scaleMin: 0,
307
+ scaleMax: '',
308
+ fillOpacity: 0.3,
309
+ showPoints: true,
310
+ pointRadius: 4,
311
+ strokeWidth: 2,
312
+ axisLabelOffset: 15
313
+ },
299
314
  sankey: {
300
315
  title: {
301
316
  defaultColor: 'black'
@@ -0,0 +1,57 @@
1
+ import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
2
+
3
+ interface CalculateHorizontalBarCategoryLabelWidthProps {
4
+ yScale: any
5
+ chartWidth: number
6
+ formatDate: Function
7
+ parseDate: Function
8
+ tickLabelFont: string
9
+ xAxisType?: string
10
+ labelPlacement?: string
11
+ }
12
+
13
+ /**
14
+ * Helper function to calculate category label space for horizontal bar charts
15
+ *
16
+ * @param props Configuration object with chart properties
17
+ * @returns Calculated category label space, capped at 30% of parent width
18
+ */
19
+ export const calculateHorizontalBarCategoryLabelWidth = ({
20
+ yScale,
21
+ chartWidth,
22
+ formatDate,
23
+ parseDate,
24
+ tickLabelFont,
25
+ xAxisType,
26
+ labelPlacement
27
+ }: CalculateHorizontalBarCategoryLabelWidthProps): number => {
28
+ if (labelPlacement !== 'On Date/Category Axis') return 0
29
+
30
+ const categoryValues = yScale.domain()
31
+
32
+ if (!categoryValues || categoryValues.length === 0) {
33
+ return chartWidth * 0.3
34
+ }
35
+
36
+ const formattedLabels = categoryValues.map(value => {
37
+ if (xAxisType === 'date') {
38
+ try {
39
+ return formatDate(parseDate(value))
40
+ } catch (e) {
41
+ return String(value)
42
+ }
43
+ }
44
+ return String(value)
45
+ })
46
+
47
+ const labelWidths = formattedLabels.map(label => getTextWidth(label, tickLabelFont))
48
+ const maxLabelWidth = Math.max(...labelWidths)
49
+
50
+ // We need some extra padding or visx will wrap labels too early
51
+ const paddedWidth = maxLabelWidth + Math.ceil(maxLabelWidth * 0.15)
52
+
53
+ // Allocate at most 30% of chart width to category labels
54
+ const maxAllowedWidth = chartWidth * 0.3
55
+
56
+ return Math.min(paddedWidth, maxAllowedWidth)
57
+ }
@@ -2,6 +2,10 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
2
2
  import _ from 'lodash'
3
3
  import { ChartConfig } from '../types/ChartConfig'
4
4
  export const getExcludedData = (newConfig: ChartConfig, data: object[]) => {
5
+ if (!Array.isArray(data)) {
6
+ console.warn('COVE: getExcludedData received non-array data:', typeof data)
7
+ return []
8
+ }
5
9
  let newExcludedData = data
6
10
  if (newConfig.exclusions && newConfig.exclusions.active) {
7
11
  if (newConfig.xAxis.type === 'categorical' && newConfig.exclusions.keys?.length > 0) {
@@ -55,10 +55,15 @@ const getMinMax = ({
55
55
  max = enteredMaxValue && isMaxValid ? Number(enteredMaxValue) : Number.MIN_VALUE
56
56
  const { lower, upper } = config?.confidenceKeys || {}
57
57
 
58
+ // When brush is active, use tableData (full dataset) for min/max calculations
59
+ // so the y-axis shows the full range, but still use filtered data for rendering
60
+ const dataForMinMax = config.xAxis.brushActive && tableData && tableData.length > 0 ? tableData : data
61
+
58
62
  if (lower && upper && config.visualizationType === 'Bar') {
59
63
  const buffer = min < 0 ? 1.1 : 0
60
- const maxValueWithCI = Math.max(...data.flatMap(d => [d[upper], d[lower]])) * paddingAddedToAxis
61
- const minValueWithCIPlusBuffer = Math.min(...data.flatMap(d => [d[upper], d[lower]])) * paddingAddedToAxis * buffer
64
+ const maxValueWithCI = Math.max(...dataForMinMax.flatMap(d => [d[upper], d[lower]])) * paddingAddedToAxis
65
+ const minValueWithCIPlusBuffer =
66
+ Math.min(...dataForMinMax.flatMap(d => [d[upper], d[lower]])) * paddingAddedToAxis * buffer
62
67
  max = max > maxValueWithCI ? max : maxValueWithCI
63
68
  min = min < minValueWithCIPlusBuffer ? min : minValueWithCIPlusBuffer
64
69
  }
@@ -79,7 +84,7 @@ const getMinMax = ({
79
84
  })
80
85
 
81
86
  // Using the columnNames or "keys" get the returned result
82
- const result = data.map(obj => columnNames.map(key => obj[key]))
87
+ const result = dataForMinMax.map(obj => columnNames.map(key => obj[key]))
83
88
 
84
89
  const highCIGroup = Math.max.apply(
85
90
  null,
@@ -102,7 +107,7 @@ const getMinMax = ({
102
107
 
103
108
  if (visualizationType === 'Combo') {
104
109
  try {
105
- if (!data) throw new Error('COVE: missing data while getting min/max for combo chart.')
110
+ if (!dataForMinMax) throw new Error('COVE: missing data while getting min/max for combo chart.')
106
111
  // seperate the left and right axis items & get each sides series keys
107
112
  let leftAxisSeriesItems = series.filter(s => s.axis === 'Left')
108
113
  let rightAxisSeriesItems = series.filter(s => s.axis === 'Right')
@@ -128,8 +133,8 @@ const getMinMax = ({
128
133
  })
129
134
  return max
130
135
  }
131
- leftMax = findMaxFromSeriesKeys(data, leftAxisSeriesItems, leftMax, 'left')
132
- rightMax = findMaxFromSeriesKeys(data, rightAxisSeriesItems, rightMax, 'right')
136
+ leftMax = findMaxFromSeriesKeys(dataForMinMax, leftAxisSeriesItems, leftMax, 'left')
137
+ rightMax = findMaxFromSeriesKeys(dataForMinMax, rightAxisSeriesItems, rightMax, 'right')
133
138
 
134
139
  if (leftMax < Number(enteredMaxValue)) {
135
140
  leftMax = Number(enteredMaxValue)
@@ -209,7 +214,7 @@ const getMinMax = ({
209
214
  }
210
215
 
211
216
  if (config.isLollipopChart && config.yAxis.displayNumbersOnBar) {
212
- const dataKey = data.map(item => item[config.series[0].dataKey])
217
+ const dataKey = dataForMinMax.map(item => item[config.series[0].dataKey])
213
218
  const maxDataVal = Math.max(...dataKey).toString().length
214
219
 
215
220
  switch (true) {
@@ -1,19 +1,19 @@
1
- export const handleChartAriaLabels = (state, testing = false) => {
2
- if (testing) console.log(`handleChartAriaLabels Testing On:`, state) // eslint-disable-line
3
- try {
4
- if (!state.visualizationType) throw Error('handleChartAriaLabels: no visualization type found in state')
5
- let ariaLabel = ''
6
-
7
- if (state.visualizationType) {
8
- ariaLabel += `${state.visualizationType} chart`
9
- }
10
-
11
- if (state.title && state.visualizationType) {
12
- ariaLabel += ` with the title: ${state.title}`
13
- }
14
-
15
- return ariaLabel
16
- } catch (e) {
17
- console.error('COVE: ', e.message) // eslint-disable-line
18
- }
19
- }
1
+ export const handleChartAriaLabels = state => {
2
+ try {
3
+ if (!state.visualizationType) throw Error('handleChartAriaLabels: no visualization type found in state')
4
+ let ariaLabel = ''
5
+
6
+ if (state.visualizationType) {
7
+ ariaLabel += `${state.visualizationType} chart`
8
+ }
9
+
10
+ if (state.title && state.visualizationType) {
11
+ ariaLabel += ` with the title: ${state.title}`
12
+ }
13
+
14
+ return ariaLabel
15
+ } catch (e) {
16
+ console.error('COVE: ', e.message) // eslint-disable-line
17
+ return 'Data visualization container'
18
+ }
19
+ }
@@ -1,18 +1,22 @@
1
- export const handleLineType = lineType => {
2
- switch (lineType) {
3
- case 'dashed-sm':
4
- return '5 5'
5
- case 'Dashed Small':
6
- return '5 5'
7
- case 'dashed-md':
8
- return '10 5'
9
- case 'Dashed Medium':
10
- return '10 5'
11
- case 'dashed-lg':
12
- return '15 5'
13
- case 'Dashed Large':
14
- return '15 5'
15
- default:
16
- return 0
17
- }
18
- }
1
+ const DASH_PATTERNS = {
2
+ SMALL: '5 5',
3
+ MEDIUM: '10 5',
4
+ LARGE: '15 5',
5
+ SOLID: 0
6
+ } as const
7
+
8
+ export const handleLineType = (lineType: string): string | number => {
9
+ switch (lineType) {
10
+ case 'dashed-sm':
11
+ case 'Dashed Small':
12
+ return DASH_PATTERNS.SMALL
13
+ case 'dashed-md':
14
+ case 'Dashed Medium':
15
+ return DASH_PATTERNS.MEDIUM
16
+ case 'dashed-lg':
17
+ case 'Dashed Large':
18
+ return DASH_PATTERNS.LARGE
19
+ default:
20
+ return DASH_PATTERNS.SOLID
21
+ }
22
+ }
@@ -26,23 +26,3 @@ export function calcInitialHeight(
26
26
  const height = Number(heights?.[renderedOrientation])
27
27
  return isNaN(height) ? 0 : height
28
28
  }
29
-
30
- export function handleAutoPaddingRight(parentRef, xAxisLabelRefs, parentWidth): [boolean, number] {
31
- const parentX = parentRef.current.getBoundingClientRect().x
32
- const editorIsOpen = !!document.querySelector('.editor-panel:not(.hidden)')
33
- const lastTickRect = xAxisLabelRefs.current?.[xAxisLabelRefs.current.length - 1]?.getBoundingClientRect()
34
- const lastBottomTickEnd = lastTickRect ? lastTickRect.x + lastTickRect.width : 0
35
- const editorWidth = editorIsOpen ? EDITOR_WIDTH : 0
36
- const calculatedOverhang = lastBottomTickEnd - parentX - editorWidth - parentWidth
37
-
38
- const paddingToAdd = clamp(calculatedOverhang, 0, 20)
39
- const currentPadding = Number(parentRef.current.style.paddingRight.replace('px', ''))
40
- const paddingDiff = Math.abs(currentPadding - paddingToAdd)
41
- const DIFF_THRESHOLD = 5
42
-
43
- const noChange = currentPadding === calculatedOverhang
44
- const insufficientDiff = (paddingDiff < DIFF_THRESHOLD && calculatedOverhang > 0) || Math.abs(calculatedOverhang) < 1
45
- const updatePadding = !noChange && !insufficientDiff
46
-
47
- return [updatePadding, paddingToAdd]
48
- }
@@ -181,7 +181,7 @@ export const getTileDisplayTitle = (mode, seriesKey, tileValue, tileKey, config)
181
181
  * Get the full color palette from config with exactly the number of colors needed
182
182
  * This creates a temporary colorScale with the right number of series to get the needed colors
183
183
  */
184
- const getFullColorPalette = (config, numberOfTiles) => {
184
+ export const getFullColorPalette = (config, numberOfTiles) => {
185
185
  // Create fake series keys for exactly the number of tiles needed
186
186
  const tempSeriesKeys = Array(numberOfTiles)
187
187
  .fill(null)