@cdc/chart 4.26.1 → 4.26.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/CLAUDE.local.md +79 -0
  2. package/LICENSE +201 -0
  3. package/dist/{cdcchart-dgT_1dIT.es.js → cdcchart-DQ00cQCm.es.js} +1 -20
  4. package/dist/cdcchart.js +54742 -49796
  5. package/examples/data/data-with-metadata.json +10 -0
  6. package/examples/default.json +378 -0
  7. package/examples/feature/__data__/horizon-chart-data.json +373 -0
  8. package/examples/feature/annotations/index.json +3 -6
  9. package/examples/feature/horizon/horizon-chart.json +395 -0
  10. package/examples/feature/pie/planet-pie-example-config.json +2 -1
  11. package/examples/line-chart-states.json +1085 -0
  12. package/examples/metadata-variables.json +58 -0
  13. package/examples/private/123.json +694 -0
  14. package/examples/private/anchor-issue.json +4094 -0
  15. package/examples/private/backwards-slider.json +10430 -0
  16. package/examples/private/georgia.csv +160 -0
  17. package/examples/private/timeline-data.json +1 -0
  18. package/examples/private/timeline.json +389 -0
  19. package/examples/radar-chart-simple.json +133 -0
  20. package/examples/radar-chart.json +148 -0
  21. package/index.html +1 -31
  22. package/package.json +57 -59
  23. package/src/CdcChart.tsx +8 -4
  24. package/src/CdcChartComponent.tsx +398 -284
  25. package/src/_stories/Chart.Anchors.stories.tsx +10 -0
  26. package/src/_stories/Chart.BoxPlot.stories.tsx +7 -0
  27. package/src/_stories/Chart.CI.stories.tsx +13 -0
  28. package/src/_stories/Chart.Combo.stories.tsx +17 -0
  29. package/src/_stories/Chart.CustomColors.stories.tsx +78 -0
  30. package/src/_stories/Chart.Defaults.stories.tsx +95 -0
  31. package/src/_stories/Chart.DynamicSeries.stories.tsx +19 -0
  32. package/src/_stories/Chart.Filters.stories.tsx +4 -0
  33. package/src/_stories/Chart.Forecast.stories.tsx +4 -0
  34. package/src/_stories/Chart.HTMLInDataTable.stories.tsx +22 -0
  35. package/src/_stories/Chart.Legend.Gradient.stories.tsx +28 -0
  36. package/src/_stories/Chart.Patterns.stories.tsx +4 -0
  37. package/src/_stories/Chart.PreserveDecimals.stories.tsx +25 -0
  38. package/src/_stories/Chart.Regions.Categorical.stories.tsx +13 -0
  39. package/src/_stories/Chart.Regions.DateScale.stories.tsx +19 -0
  40. package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +25 -10
  41. package/src/_stories/Chart.ScatterPlot.stories.tsx +4 -0
  42. package/src/_stories/Chart.SmallMultiples.stories.tsx +16 -0
  43. package/src/_stories/Chart.SmallestLeftAxisMax.stories.tsx +64 -0
  44. package/src/_stories/Chart.stories.tsx +72 -1
  45. package/src/_stories/Chart.tooltip.stories.tsx +7 -0
  46. package/src/_stories/ChartAnnotation.stories.tsx +10 -0
  47. package/src/_stories/ChartAxisLabels.stories.tsx +4 -0
  48. package/src/_stories/ChartAxisTitles.stories.tsx +10 -0
  49. package/src/_stories/ChartBar.Editor.stories.tsx +97 -38
  50. package/src/_stories/ChartBrush.Editor.stories.tsx +11 -25
  51. package/src/_stories/ChartBrush.Matrix.Continuous.stories.tsx +41 -0
  52. package/src/_stories/ChartBrush.Matrix.Date.stories.tsx +114 -0
  53. package/src/_stories/ChartBrush.Matrix.DateTime.stories.tsx +78 -0
  54. package/src/_stories/ChartBrush.stories.tsx +7 -0
  55. package/src/_stories/ChartEditor.Editor.stories.tsx +1 -1
  56. package/src/_stories/ChartEditor.stories.tsx +7 -0
  57. package/src/_stories/ChartLine.QuadrantAngles.stories.tsx +89 -0
  58. package/src/_stories/ChartLine.Suppression.stories.tsx +7 -0
  59. package/src/_stories/ChartLine.Symbols.stories.tsx +4 -0
  60. package/src/_stories/ChartPrefixSuffix.stories.tsx +46 -1
  61. package/src/_stories/TechAdoptionWithLinks.stories.tsx +7 -0
  62. package/src/_stories/_mock/brush_continuous.json +86 -0
  63. package/src/_stories/_mock/brush_date_large.json +176 -0
  64. package/src/_stories/_mock/line_chart_angle_near_zero_fall.json +195 -0
  65. package/src/_stories/_mock/line_chart_angle_near_zero_rise.json +195 -0
  66. package/src/_stories/_mock/line_chart_angle_q1_steep_upward.json +195 -0
  67. package/src/_stories/_mock/line_chart_angle_q2_gentle_downward.json +195 -0
  68. package/src/_stories/_mock/line_chart_angle_q3_steep_downward.json +195 -0
  69. package/src/_stories/_mock/line_chart_angle_q4_gentle_upward.json +195 -0
  70. package/src/_stories/_mock/line_chart_quadrant_angles.json +264 -0
  71. package/src/_stories/_mock/paired-bar-abbr.json +421 -0
  72. package/src/_stories/_mock/pie_custom_colors.json +268 -0
  73. package/src/_stories/_mock/smallest_left_axis_max.json +104 -0
  74. package/src/components/Annotations/components/AnnotationDraggable.styles.css +14 -20
  75. package/src/components/Annotations/components/AnnotationDraggable.tsx +240 -116
  76. package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -2
  77. package/src/components/Annotations/components/AnnotationDropdown.tsx +8 -12
  78. package/src/components/Annotations/components/AnnotationList.styles.css +12 -18
  79. package/src/components/Annotations/components/AnnotationList.tsx +5 -4
  80. package/src/components/Annotations/components/findNearestDatum.ts +75 -85
  81. package/src/components/Annotations/helpers/getVisibleAnnotations.ts +38 -0
  82. package/src/components/Axis/BottomAxis.tsx +277 -0
  83. package/src/components/Axis/LeftAxis.tsx +404 -0
  84. package/src/components/Axis/LeftAxisGridlines.tsx +77 -0
  85. package/src/components/Axis/PairedBarAxis.tsx +192 -0
  86. package/src/components/Axis/README.md +94 -0
  87. package/src/components/Axis/RightAxis.tsx +108 -0
  88. package/src/components/Axis/axis.constants.ts +21 -0
  89. package/src/components/Axis/index.ts +7 -0
  90. package/src/components/BarChart/components/BarChart.Horizontal.tsx +12 -28
  91. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -30
  92. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +12 -31
  93. package/src/components/BarChart/components/BarChart.Vertical.tsx +12 -28
  94. package/src/components/BarChart/components/BarChart.tsx +7 -1
  95. package/src/components/BarChart/helpers/getPatternUrl.ts +94 -0
  96. package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +134 -0
  97. package/src/components/BarChart/helpers/useBarChart.ts +3 -0
  98. package/src/components/Brush/BrushSelector.tsx +155 -22
  99. package/src/components/Brush/MiniChartPreview.tsx +133 -21
  100. package/src/components/EditorPanel/EditorPanel.tsx +81 -54
  101. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +67 -29
  102. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +0 -78
  103. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +120 -2
  104. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +25 -43
  105. package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +353 -0
  106. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +83 -3
  107. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +66 -43
  108. package/src/components/EditorPanel/components/Panels/index.tsx +2 -0
  109. package/src/components/EditorPanel/editor-panel.scss +1 -1
  110. package/src/components/EditorPanel/useEditorPermissions.ts +55 -26
  111. package/src/components/ForestPlot/ForestPlot.tsx +26 -22
  112. package/src/components/HorizonChart/HorizonChart.tsx +131 -0
  113. package/src/components/HorizonChart/components/HorizonBand.tsx +160 -0
  114. package/src/components/HorizonChart/helpers/calculateHorizonBands.ts +27 -0
  115. package/src/components/HorizonChart/helpers/getHorizonLayerColors.ts +40 -0
  116. package/src/components/HorizonChart/index.tsx +3 -0
  117. package/src/components/Legend/Legend.Component.tsx +52 -4
  118. package/src/components/Legend/Legend.tsx +1 -1
  119. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +4 -4
  120. package/src/components/Legend/LegendValueRange.tsx +77 -0
  121. package/src/components/Legend/helpers/createFormatLabels.tsx +16 -2
  122. package/src/components/Legend/helpers/generateValueRanges.ts +92 -0
  123. package/src/components/LineChart/helpers/README.md +292 -0
  124. package/src/components/LineChart/helpers/labelPositioning.test.ts +245 -0
  125. package/src/components/LineChart/helpers/labelPositioning.ts +304 -0
  126. package/src/components/LineChart/index.tsx +44 -8
  127. package/src/components/LinearChart/README.md +109 -0
  128. package/src/components/LinearChart/VisualizationRenderer.tsx +267 -0
  129. package/src/components/LinearChart/linearChart.constants.ts +84 -0
  130. package/src/components/LinearChart/tests/LinearChart.test.tsx +278 -0
  131. package/src/components/LinearChart/tests/mockConfigContext.ts +131 -0
  132. package/src/components/LinearChart/utils/tickFormatting.ts +146 -0
  133. package/src/components/LinearChart.tsx +268 -1057
  134. package/src/components/PieChart/PieChart.tsx +20 -5
  135. package/src/components/RadarChart/RadarAxis.tsx +78 -0
  136. package/src/components/RadarChart/RadarChart.tsx +298 -0
  137. package/src/components/RadarChart/RadarGrid.tsx +64 -0
  138. package/src/components/RadarChart/RadarPolygon.tsx +91 -0
  139. package/src/components/RadarChart/helpers.ts +83 -0
  140. package/src/components/RadarChart/index.tsx +3 -0
  141. package/src/components/Regions/components/Regions.tsx +6 -6
  142. package/src/components/Sankey/components/Sankey.tsx +3 -3
  143. package/src/components/Sankey/sankey.scss +1 -1
  144. package/src/components/SmallMultiples/SmallMultiples.css +5 -5
  145. package/src/components/Sparkline/index.scss +4 -2
  146. package/src/components/WarmingStripes/WarmingStripes.tsx +95 -25
  147. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +8 -8
  148. package/src/data/initial-state.js +37 -15
  149. package/src/data/legacy-defaults.ts +18 -0
  150. package/src/helpers/abbreviateNumber.ts +24 -17
  151. package/src/helpers/getChartPatternId.ts +17 -0
  152. package/src/helpers/getExcludedData.ts +4 -0
  153. package/src/helpers/getMinMax.ts +16 -2
  154. package/src/helpers/handleChartAriaLabels.ts +19 -19
  155. package/src/helpers/handleLineType.ts +22 -18
  156. package/src/helpers/seriesColumnSettings.ts +114 -0
  157. package/src/helpers/tests/countNumOfTicks.test.ts +77 -0
  158. package/src/helpers/tests/seriesColumnSettings.test.ts +84 -0
  159. package/src/hooks/useProgrammaticTooltip.ts +23 -2
  160. package/src/hooks/useRightAxis.ts +14 -0
  161. package/src/hooks/useScales.ts +99 -56
  162. package/src/hooks/useTooltip.tsx +23 -3
  163. package/src/scss/main.scss +157 -79
  164. package/src/selectors/README.md +68 -0
  165. package/src/store/chart.reducer.ts +2 -0
  166. package/src/test/CdcChart.test.jsx +2 -2
  167. package/src/types/ChartConfig.ts +22 -0
  168. package/src/types/ChartContext.ts +1 -0
  169. package/src/types/Horizon.ts +64 -0
  170. package/tests/fixtures/chart-config-with-metadata.json +29 -0
  171. package/tests/fixtures/data-with-metadata.json +10 -0
  172. package/preview.html +0 -1616
  173. package/src/components/Annotations/components/helpers/index.tsx +0 -46
@@ -3,6 +3,7 @@ import { LinePath, AreaClosed, AreaStack } from '@visx/shape'
3
3
  import * as allCurves from '@visx/curve'
4
4
  import { handleLineType } from '../../helpers/handleLineType'
5
5
  import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
6
+ import { getPatternUrl as getPatternUrlForBar } from '../BarChart/helpers/getPatternUrl'
6
7
 
7
8
  interface MiniChartPreviewProps {
8
9
  series: any[]
@@ -10,25 +11,41 @@ interface MiniChartPreviewProps {
10
11
  dataKey: string
11
12
  xScale: any
12
13
  miniYScale: any
14
+ miniRightYScale?: any
13
15
  colorScale: any
14
16
  config: any
15
17
  xMax: number
16
18
  }
17
19
 
18
20
  const MiniChartPreview = memo<MiniChartPreviewProps>(
19
- ({ series, tableData, dataKey, xScale, miniYScale, colorScale, config }) => {
21
+ ({ series, tableData, dataKey, xScale, miniYScale, miniRightYScale, colorScale, config }) => {
20
22
  if (!series || !series.length || !tableData || !tableData.length || !xScale || !miniYScale) {
21
23
  return null
22
24
  }
23
25
 
24
26
  const bandwidth = xScale.bandwidth?.() || 0
27
+ const isComboChart = config?.visualizationType === 'Combo'
25
28
  const isBarChart = config?.visualizationType === 'Bar'
26
29
  const isStacked = config?.visualizationSubType === 'stacked'
27
30
  const isAreaChart = config?.visualizationType === 'Area Chart'
31
+ const barSeriesTypes = new Set(['Bar', 'Paired Bar', 'Deviation Bar', 'Combo'])
32
+ const barSeries = isComboChart ? series.filter(s => barSeriesTypes.has(s.type)) : series
33
+ const areaSeries = isComboChart ? series.filter(s => s.type === 'Area Chart') : series
34
+ const lineSeries = isComboChart
35
+ ? series.filter(s => !barSeriesTypes.has(s.type) && s.type !== 'Area Chart')
36
+ : series
37
+ const patternSeriesKeys = Array.isArray(series) ? series.map(_series => _series.dataKey) : []
38
+ const allowGroupedNonSeriesFieldMatch = !config.series || config.series.length <= 1
39
+
40
+ let barElements: React.ReactElement[] = []
28
41
 
29
42
  // For bar charts, render rectangles
30
- if (isBarChart) {
43
+ if (isBarChart || (isComboChart && barSeries.length > 0)) {
31
44
  const bars: React.ReactElement[] = []
45
+ const barSeriesToRender = isBarChart ? series : barSeries
46
+
47
+ const barStrokeColor = config?.barHasBorder === 'true' ? '#000' : 'transparent'
48
+ const barStrokeWidth = config?.barHasBorder === 'true' ? 1 : 0
32
49
 
33
50
  tableData.forEach((d, i) => {
34
51
  const xVal = xScale(d[dataKey])
@@ -47,11 +64,20 @@ const MiniChartPreview = memo<MiniChartPreviewProps>(
47
64
  let cumulativeValue = 0
48
65
  const zeroY = miniYScale(0)
49
66
 
50
- series.forEach((s, seriesIndex) => {
67
+ barSeriesToRender.forEach((s, seriesIndex) => {
51
68
  const value = parseFloat(d[s.dataKey])
52
69
  if (isNaN(value) || value === 0) return
53
70
 
54
71
  const seriesColor = colorScale?.(config.runtime.seriesLabels?.[s.dataKey] || s.dataKey) || '#666'
72
+ const patternUrl = getPatternUrlForBar({
73
+ patterns: config.legend?.patterns,
74
+ datum: d,
75
+ seriesKey: s.dataKey,
76
+ seriesValue: value,
77
+ seriesLabels: config.runtime?.seriesLabels,
78
+ seriesKeys: patternSeriesKeys,
79
+ allowNonSeriesFieldMatch: true
80
+ })
55
81
 
56
82
  // Calculate the bottom and top of this segment
57
83
  // For stacked bars, each segment sits on top of the previous one
@@ -72,22 +98,49 @@ const MiniChartPreview = memo<MiniChartPreviewProps>(
72
98
  width={barWidth}
73
99
  height={barHeight}
74
100
  fill={seriesColor}
75
- fillOpacity={0.7}
101
+ stroke={barStrokeColor}
102
+ strokeWidth={barStrokeWidth}
103
+ fillOpacity={1}
76
104
  pointerEvents='none'
77
105
  />
78
106
  )
107
+ if (patternUrl) {
108
+ bars.push(
109
+ <rect
110
+ key={`mini-bar-stacked-pattern-${i}-${seriesIndex}`}
111
+ x={x - barWidth / 2}
112
+ y={y}
113
+ width={barWidth}
114
+ height={barHeight}
115
+ fill={patternUrl}
116
+ stroke='transparent'
117
+ strokeWidth={0}
118
+ fillOpacity={1}
119
+ pointerEvents='none'
120
+ />
121
+ )
122
+ }
79
123
  })
80
124
  } else {
81
125
  // For grouped bars, render bars side by side
82
- const seriesCount = series.length
126
+ const seriesCount = barSeriesToRender.length
83
127
  const groupBarWidth = barWidth / seriesCount
84
128
  const zeroY = miniYScale(0)
85
129
 
86
- series.forEach((s, seriesIndex) => {
130
+ barSeriesToRender.forEach((s, seriesIndex) => {
87
131
  const value = parseFloat(d[s.dataKey])
88
132
  if (isNaN(value)) return
89
133
 
90
134
  const seriesColor = colorScale?.(config.runtime.seriesLabels?.[s.dataKey] || s.dataKey) || '#666'
135
+ const patternUrl = getPatternUrlForBar({
136
+ patterns: config.legend?.patterns,
137
+ datum: d,
138
+ seriesKey: s.dataKey,
139
+ seriesValue: value,
140
+ seriesLabels: config.runtime?.seriesLabels,
141
+ seriesKeys: patternSeriesKeys,
142
+ allowNonSeriesFieldMatch: allowGroupedNonSeriesFieldMatch
143
+ })
91
144
 
92
145
  // Calculate bar position and height
93
146
  const valueY = miniYScale(value)
@@ -100,18 +153,39 @@ const MiniChartPreview = memo<MiniChartPreviewProps>(
100
153
  key={`mini-bar-grouped-${i}-${seriesIndex}`}
101
154
  x={barX}
102
155
  y={y}
103
- width={groupBarWidth * 0.9} // Wider bars with small gap between them
156
+ width={groupBarWidth}
104
157
  height={barHeight}
105
158
  fill={seriesColor}
106
- fillOpacity={0.7}
159
+ stroke={barStrokeColor}
160
+ strokeWidth={barStrokeWidth}
161
+ fillOpacity={1}
107
162
  pointerEvents='none'
108
163
  />
109
164
  )
165
+ if (patternUrl) {
166
+ bars.push(
167
+ <rect
168
+ key={`mini-bar-grouped-pattern-${i}-${seriesIndex}`}
169
+ x={barX}
170
+ y={y}
171
+ width={groupBarWidth}
172
+ height={barHeight}
173
+ fill={patternUrl}
174
+ stroke='transparent'
175
+ strokeWidth={0}
176
+ fillOpacity={1}
177
+ pointerEvents='none'
178
+ />
179
+ )
180
+ }
110
181
  })
111
182
  }
112
183
  })
113
184
 
114
- return <>{bars}</>
185
+ barElements = bars
186
+ if (isBarChart && !isComboChart) {
187
+ return <>{barElements}</>
188
+ }
115
189
  }
116
190
 
117
191
  // For stacked area charts, use AreaStack
@@ -217,10 +291,10 @@ const MiniChartPreview = memo<MiniChartPreviewProps>(
217
291
  )
218
292
  }
219
293
 
220
- // For line/area charts, render lines or areas
294
+ // For line/area charts (and combo line/area series), render lines or areas
221
295
  return (
222
296
  <>
223
- {series.map((s, i) => {
297
+ {(isComboChart ? areaSeries : isAreaChart ? series : []).map((s, i) => {
224
298
  const seriesKey = s.dataKey
225
299
  const seriesColor = colorScale?.(config.runtime.seriesLabels?.[seriesKey] || seriesKey) || '#666'
226
300
 
@@ -231,25 +305,31 @@ const MiniChartPreview = memo<MiniChartPreviewProps>(
231
305
  const curve = allCurves[seriesLineType] || allCurves.curveLinear
232
306
  const strokeDasharray = handleLineType(seriesStyle)
233
307
 
234
- // Filter to only valid data points
235
- const validData = tableData.filter(d => {
236
- const xVal = xScale(d[dataKey])
237
- const yVal = parseFloat(d[s.dataKey])
238
- return xVal !== undefined && !isNaN(yVal)
239
- })
308
+ // Use the right-axis scale when the series is on the right axis
309
+ const yScaleForSeries = s.axis === 'Right' && miniRightYScale ? miniRightYScale : miniYScale
310
+
311
+ // Filter to only valid data points, then sort by X position so the area
312
+ // renders left-to-right regardless of the original data order.
313
+ const validData = tableData
314
+ .filter(d => {
315
+ const xVal = xScale(d[dataKey])
316
+ const yVal = parseFloat(d[s.dataKey])
317
+ return xVal !== undefined && !isNaN(yVal)
318
+ })
319
+ .sort((a, b) => (xScale(a[dataKey]) ?? 0) - (xScale(b[dataKey]) ?? 0))
240
320
 
241
321
  if (validData.length === 0) return null
242
322
 
243
323
  const getX = d => xScale(d[dataKey]) + bandwidth / 2
244
- const getY = d => miniYScale(parseFloat(d[s.dataKey]))
324
+ const getY = d => yScaleForSeries(parseFloat(d[s.dataKey]))
245
325
 
246
- return isAreaChart ? (
326
+ return (
247
327
  <AreaClosed
248
328
  key={`mini-area-${seriesKey}-${i}`}
249
329
  data={validData}
250
330
  x={getX}
251
331
  y={getY}
252
- yScale={miniYScale}
332
+ yScale={yScaleForSeries}
253
333
  fill={seriesColor}
254
334
  fillOpacity={1}
255
335
  stroke={seriesColor}
@@ -258,7 +338,39 @@ const MiniChartPreview = memo<MiniChartPreviewProps>(
258
338
  curve={curve}
259
339
  pointerEvents='none'
260
340
  />
261
- ) : (
341
+ )
342
+ })}
343
+ {barElements}
344
+ {(isAreaChart && !isComboChart ? [] : isComboChart ? lineSeries : series).map((s, i) => {
345
+ const seriesKey = s.dataKey
346
+ const seriesColor = colorScale?.(config.runtime.seriesLabels?.[seriesKey] || seriesKey) || '#666'
347
+
348
+ // Get series-specific styling
349
+ const seriesWeight = s.weight || 2
350
+ const seriesLineType = s.lineType || 'curveLinear'
351
+ const seriesStyle = s.type || 'solid'
352
+ const curve = allCurves[seriesLineType] || allCurves.curveLinear
353
+ const strokeDasharray = handleLineType(seriesStyle)
354
+
355
+ // Use the right-axis scale when the series is on the right axis
356
+ const yScaleForSeries = s.axis === 'Right' && miniRightYScale ? miniRightYScale : miniYScale
357
+
358
+ // Filter to only valid data points, then sort by X position so the line
359
+ // connects left-to-right regardless of the original data order.
360
+ const validData = tableData
361
+ .filter(d => {
362
+ const xVal = xScale(d[dataKey])
363
+ const yVal = parseFloat(d[s.dataKey])
364
+ return xVal !== undefined && !isNaN(yVal)
365
+ })
366
+ .sort((a, b) => (xScale(a[dataKey]) ?? 0) - (xScale(b[dataKey]) ?? 0))
367
+
368
+ if (validData.length === 0) return null
369
+
370
+ const getX = d => xScale(d[dataKey]) + bandwidth / 2
371
+ const getY = d => yScaleForSeries(parseFloat(d[s.dataKey]))
372
+
373
+ return (
262
374
  <LinePath
263
375
  key={`mini-line-${seriesKey}-${i}`}
264
376
  data={validData}
@@ -45,7 +45,10 @@ import '@cdc/core/components/EditorPanel/EditorPanel.styles.css'
45
45
  import './editor-panel.scss'
46
46
  import { Anchor } from '@cdc/core/types/Axis'
47
47
  import EditorPanelContext from './EditorPanelContext'
48
- import _ from 'lodash'
48
+ import cloneDeep from 'lodash/cloneDeep'
49
+ import flatMap from 'lodash/flatMap'
50
+ import keys from 'lodash/keys'
51
+ import uniq from 'lodash/uniq'
49
52
  import { adjustedSymbols as symbolCodes } from '@cdc/core/helpers/footnoteSymbols'
50
53
  import { updateFieldRankByValue } from './helpers/updateFieldRankByValue'
51
54
  import cloneConfig from '@cdc/core/helpers/cloneConfig'
@@ -55,6 +58,7 @@ import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
55
58
  import { paletteMigrationMap, twoColorPaletteMigrationMap } from '@cdc/core/helpers/palettes/migratePaletteName'
56
59
  import { isV1Palette, migratePaletteWithMap } from '@cdc/core/helpers/palettes/utils'
57
60
  import { USE_V2_MIGRATION } from '@cdc/core/helpers/constants'
61
+ import { getSeriesOwnedColumnNames } from '../../helpers/seriesColumnSettings'
58
62
 
59
63
  interface PreliminaryProps {
60
64
  config: ChartConfig
@@ -70,7 +74,7 @@ const PreliminaryData: React.FC<PreliminaryProps> = ({ config, updateConfig, dat
70
74
  const hasComboBarSeries = isCombo && barSeriesExists
71
75
 
72
76
  const getColumnOptions = () => {
73
- return _.uniq(_.flatMap(data, _.keys))
77
+ return uniq(flatMap(data, keys))
74
78
  }
75
79
 
76
80
  const getTypeOptions = () => {
@@ -653,6 +657,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
653
657
  visHasLegendAxisAlign,
654
658
  visHasLegendColorCategory,
655
659
  visHasSelectableLegendValues,
660
+ visSupportsClickingLegend,
656
661
  visSupportsDateCategoryAxis,
657
662
  visSupportsDateCategoryAxisLabel,
658
663
  visSupportsDateCategoryAxisLine,
@@ -756,6 +761,10 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
756
761
  if (isDateScale(updatedConfig.xAxis) && !updatedConfig.xAxis.padding) {
757
762
  updatedConfig.xAxis.padding = 0
758
763
  }
764
+ // Default Radar charts to a taller height
765
+ if (updatedConfig.visualizationType === 'Radar' && updatedConfig.heights?.vertical <= 400) {
766
+ updatedConfig.heights.vertical = 400
767
+ }
759
768
  // DEV-8008 - Remove Bar styling when Line is converted to Bar
760
769
  if (updatedConfig.visualizationType === 'Line') {
761
770
  updatedConfig.visualizationSubType = 'regular'
@@ -930,6 +939,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
930
939
 
931
940
  // Extract column names from data with memoization (replaces getColumns)
932
941
  const allColumns = useDataColumns(dataSourceForColumns)
942
+ const seriesOwnedColumnNames = useMemo(() => getSeriesOwnedColumnNames(config.series), [config.series])
933
943
 
934
944
  // Filter out series columns and confidence key columns (except lower and upper)
935
945
  const filteredColumns = useMemo(() => {
@@ -1514,46 +1524,8 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1514
1524
  'Deviation Bar'
1515
1525
  ]
1516
1526
 
1517
- const columnsOptions = [
1518
- <option value='' key={'Select Option'}>
1519
- - Select Option -
1520
- </option>
1521
- ]
1522
-
1523
- if (config.data && config.series) {
1524
- Object.keys(config.data?.[0] || []).map(colName => {
1525
- // OMIT ANY COLUMNS THAT ARE IN DATA SERIES!
1526
- const found = config?.series.some(series => series.dataKey === colName)
1527
- if (colName !== config.xAxis.dataKey && !found) {
1528
- // if not the index then add it
1529
- return columnsOptions.push(
1530
- <option value={colName} key={colName}>
1531
- {colName}
1532
- </option>
1533
- )
1534
- }
1535
- })
1536
- }
1537
-
1538
- // for pie charts
1539
- if (!config.data && data) {
1540
- if (!data[0]) return
1541
- Object.keys(data[0]).map(colName => {
1542
- // OMIT ANY COLUMNS THAT ARE IN DATA SERIES!
1543
- const found = data.some(el => el.dataKey === colName)
1544
- if (colName !== config.xAxis.dataKey && !found) {
1545
- // if not the index then add it
1546
- return columnsOptions.push(
1547
- <option value={colName} key={colName}>
1548
- {colName}
1549
- </option>
1550
- )
1551
- }
1552
- })
1553
- }
1554
-
1555
1527
  const removeAdditionalColumn = columnName => {
1556
- const newColumns = _.cloneDeep(config.columns)
1528
+ const newColumns = cloneDeep(config.columns)
1557
1529
 
1558
1530
  delete newColumns[columnName]
1559
1531
 
@@ -1892,6 +1864,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1892
1864
  </AccordionItem>
1893
1865
  )}
1894
1866
  <Panels.BoxPlot name='Measures' />
1867
+ <Panels.Radar name='Radar Chart Settings' />
1895
1868
  {/* Left Value Axis */}
1896
1869
  {visSupportsLeftValueAxis() && (
1897
1870
  <AccordionItem>
@@ -2481,6 +2454,28 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2481
2454
  <span style={{ color: 'red', display: 'block' }}>{warningMsg.minMsg}</span>
2482
2455
  </>
2483
2456
  )}
2457
+ <TextField
2458
+ value={config.yAxis.smallestLeftAxisMax}
2459
+ section='yAxis'
2460
+ fieldName='smallestLeftAxisMax'
2461
+ type='number'
2462
+ label='Smallest Left Axis Maximum'
2463
+ placeholder='Auto'
2464
+ tooltip={
2465
+ <Tooltip style={{ textTransform: 'none' }}>
2466
+ <Tooltip.Target>
2467
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2468
+ </Tooltip.Target>
2469
+ <Tooltip.Content>
2470
+ <p>
2471
+ Example: If your data only goes up to 1, the axis might show 0, 0.2, 0.4, 0.6,
2472
+ 0.8, 1. Setting this to 5 would make the axis show 0, 1, 2, 3, 4, 5 instead.
2473
+ </p>
2474
+ </Tooltip.Content>
2475
+ </Tooltip>
2476
+ }
2477
+ updateField={updateFieldDeprecated}
2478
+ />
2484
2479
  </>
2485
2480
  )
2486
2481
  )}
@@ -2880,7 +2875,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2880
2875
  />
2881
2876
 
2882
2877
  <TextField
2883
- value={config.yAxis.max}
2878
+ value={config.yAxis.rightMax}
2884
2879
  section='yAxis'
2885
2880
  fieldName='rightMax'
2886
2881
  type='number'
@@ -2890,7 +2885,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2890
2885
  />
2891
2886
  <span style={{ color: 'red', display: 'block' }}>{warningMsg.rightMaxMessage}</span>
2892
2887
  <TextField
2893
- value={config.yAxis.min}
2888
+ value={config.yAxis.rightMin}
2894
2889
  section='yAxis'
2895
2890
  fieldName='rightMin'
2896
2891
  type='number'
@@ -2899,6 +2894,28 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2899
2894
  updateField={updateFieldDeprecated}
2900
2895
  />
2901
2896
  <span style={{ color: 'red', display: 'block' }}>{warningMsg.minMsg}</span>
2897
+ <TextField
2898
+ value={config.yAxis.smallestRightAxisMax}
2899
+ section='yAxis'
2900
+ fieldName='smallestRightAxisMax'
2901
+ type='number'
2902
+ label='Smallest Right Axis Maximum'
2903
+ placeholder='Auto'
2904
+ tooltip={
2905
+ <Tooltip style={{ textTransform: 'none' }}>
2906
+ <Tooltip.Target>
2907
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2908
+ </Tooltip.Target>
2909
+ <Tooltip.Content>
2910
+ <p>
2911
+ Example: If your data only goes up to 1, the axis might show 0, 0.2, 0.4, 0.6, 0.8, 1.
2912
+ Setting this to 5 would make the axis show 0, 1, 2, 3, 4, 5 instead.
2913
+ </p>
2914
+ </Tooltip.Content>
2915
+ </Tooltip>
2916
+ }
2917
+ updateField={updateFieldDeprecated}
2918
+ />
2902
2919
  </AccordionItemPanel>
2903
2920
  </AccordionItem>
2904
2921
  )}
@@ -4137,6 +4154,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4137
4154
  config={config}
4138
4155
  updateField={updateFieldDeprecated}
4139
4156
  deleteColumn={removeAdditionalColumn}
4157
+ hiddenColumnNames={seriesOwnedColumnNames}
4140
4158
  />{' '}
4141
4159
  </AccordionItemPanel>
4142
4160
  </AccordionItem>
@@ -4250,7 +4268,10 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4250
4268
  />
4251
4269
 
4252
4270
  <CheckBox
4253
- display={config.preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value)}
4271
+ display={
4272
+ config.visualizationType !== 'Radar' &&
4273
+ config.preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value)
4274
+ }
4254
4275
  value={config.legend.hideSuppressedLabels}
4255
4276
  section='legend'
4256
4277
  fieldName='hideSuppressedLabels'
@@ -4274,7 +4295,10 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4274
4295
  }
4275
4296
  />
4276
4297
  <CheckBox
4277
- display={config.preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value)}
4298
+ display={
4299
+ config.visualizationType !== 'Radar' &&
4300
+ config.preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value)
4301
+ }
4278
4302
  value={config.legend.hideSuppressionLink}
4279
4303
  section='legend'
4280
4304
  fieldName='hideSuppressionLink'
@@ -4296,7 +4320,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4296
4320
  />
4297
4321
 
4298
4322
  <Select
4299
- display={hasDynamicCategory || hasMultipleSeries}
4323
+ display={visSupportsClickingLegend() && (hasDynamicCategory || hasMultipleSeries)}
4300
4324
  value={config.legend.behavior}
4301
4325
  section='legend'
4302
4326
  fieldName='behavior'
@@ -4580,14 +4604,17 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4580
4604
  )}
4581
4605
  <Panels.Annotate name='Text Annotations' />
4582
4606
  {/* {(config.visualizationType === 'Bar' || config.visualizationType === 'Line') && <Panels.DateHighlighting name='Date Highlighting' />} */}
4583
- <PanelMarkup
4584
- name='Markup Variables'
4585
- markupVariables={config.markupVariables || []}
4586
- data={rawData}
4587
- enableMarkupVariables={config.enableMarkupVariables || false}
4588
- onMarkupVariablesChange={variables => updateField(null, null, 'markupVariables', variables)}
4589
- onToggleEnable={enabled => updateField(null, null, 'enableMarkupVariables', enabled)}
4590
- />
4607
+ {config.visualizationType !== 'Radar' && (
4608
+ <PanelMarkup
4609
+ name='Markup Variables'
4610
+ markupVariables={config.markupVariables || []}
4611
+ data={rawData}
4612
+ enableMarkupVariables={config.enableMarkupVariables || false}
4613
+ onMarkupVariablesChange={variables => updateField(null, null, 'markupVariables', variables)}
4614
+ onToggleEnable={enabled => updateField(null, null, 'enableMarkupVariables', enabled)}
4615
+ dataMetadata={config.dataMetadata}
4616
+ />
4617
+ )}
4591
4618
  <Panels.SmallMultiples name='Small Multiples' />
4592
4619
  </Accordion>
4593
4620
  {config.type !== 'Spark Line' && (