@cdc/chart 4.26.6 → 4.26.10

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 (276) hide show
  1. package/CONFIG.md +504 -368
  2. package/dist/cdcchart-CYc-Kayd.es.js +22488 -0
  3. package/dist/cdcchart-DCKn8NmI.es.js +20695 -0
  4. package/dist/cdcchart-Dyui9nk_.es.js +45828 -0
  5. package/dist/cdcchart.js +4 -96498
  6. package/examples/__data__/vega-remote-data.json +6 -0
  7. package/examples/dendrogram.json +18 -0
  8. package/examples/feature/bar/bar-chart-race-annual-change.json +68 -0
  9. package/examples/feature/heatmap/average-age-categorical.json +2 -2
  10. package/examples/feature/heatmap/calendar-heatmap.json +2 -2
  11. package/examples/feature/heatmap/cell-values-bottom-axis.json +2 -2
  12. package/examples/feature/heatmap/sparse-aggregation.json +2 -2
  13. package/examples/feature/line/line-chart-race.json +50 -0
  14. package/examples/feature/sankey/sankey-example-data.json +40 -1441
  15. package/examples/feature/sankey/sankey_chart_data.csv +9 -20
  16. package/examples/network.json +31 -0
  17. package/examples/private/box-plot.json +573 -0
  18. package/examples/private/custom-color-issue.json +625 -0
  19. package/examples/private/custom-colors.json +522 -0
  20. package/examples/private/cutoff.json +10750 -0
  21. package/examples/private/flueview.json +22051 -0
  22. package/examples/private/overlay-legend.json +485 -0
  23. package/examples/private/pie-test.json +469 -0
  24. package/examples/vega-remote.json +22 -0
  25. package/package.json +11 -13
  26. package/src/CdcChart.tsx +2 -2
  27. package/src/CdcChartComponent.tsx +444 -217
  28. package/src/_stories/Chart.BarRace.stories.tsx +443 -0
  29. package/src/_stories/Chart.BoxPlotNumericSort.stories.tsx +189 -0
  30. package/src/_stories/Chart.BoxPlotStatsParity.stories.tsx +207 -0
  31. package/src/_stories/Chart.ColorDistribution.stories.tsx +299 -0
  32. package/src/_stories/Chart.DataCutoff.stories.tsx +147 -0
  33. package/src/_stories/Chart.DataTableDownloadFilename.stories.tsx +60 -0
  34. package/src/_stories/Chart.Dendrogram.stories.tsx +373 -0
  35. package/src/_stories/Chart.Forecast.CILegend.stories.tsx +236 -0
  36. package/src/_stories/Chart.HeatMap.stories.tsx +170 -4
  37. package/src/_stories/Chart.Legend.AxisAlign.stories.tsx +119 -0
  38. package/src/_stories/Chart.LineRace.stories.tsx +149 -0
  39. package/src/_stories/Chart.Network.stories.tsx +432 -0
  40. package/src/_stories/Chart.PaletteConversion.Editor.stories.tsx +208 -0
  41. package/src/_stories/Chart.Patterns.smoke.stories.tsx +31 -0
  42. package/src/_stories/Chart.PercentageValue.stories.tsx +311 -0
  43. package/src/_stories/Chart.PieLabels.stories.tsx +49 -0
  44. package/src/_stories/Chart.RankByValueMutation.stories.tsx +206 -0
  45. package/src/_stories/Chart.RequiredFields.stories.tsx +681 -0
  46. package/src/_stories/Chart.Sankey.stories.tsx +417 -0
  47. package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +47 -0
  48. package/src/_stories/Chart.SmallMultiplesNumericTileOrder.stories.tsx +137 -0
  49. package/src/_stories/Chart.TooltipSeriesLabels.stories.tsx +43 -0
  50. package/src/_stories/Chart.VegaRemoteData.stories.tsx +31 -0
  51. package/src/_stories/Chart.smoke.stories.tsx +14 -1
  52. package/src/_stories/ChartAnnotation.Editor.stories.tsx +78 -0
  53. package/src/_stories/ChartAxisTitles.smoke.stories.tsx +48 -0
  54. package/src/_stories/ChartBar.Editor.stories.tsx +499 -57
  55. package/src/_stories/ChartBrush.Editor.stories.tsx +46 -67
  56. package/src/_stories/ChartBrush.Regression.stories.tsx +135 -0
  57. package/src/_stories/ChartEditor.Editor.stories.tsx +33 -3
  58. package/src/_stories/ChartLine.EffectCompatibility.stories.tsx +120 -0
  59. package/src/_stories/ChartPrefixSuffix.smoke.stories.tsx +28 -1
  60. package/src/_stories/_mock/annotation_event-line_mock.json +2 -2
  61. package/src/_stories/_mock/editor-test-configs.json +20 -10
  62. package/src/_stories/_mock/editor-test-datasets.json +38 -42
  63. package/src/_stories/_mock/heatmap-average-age-categorical.json +2 -2
  64. package/src/_stories/_mock/heatmap.json +2 -2
  65. package/src/_stories/_mock/legend-axis-align-stacked-bar.json +125 -0
  66. package/src/_stories/_mock/sankey_mock.json +23 -27
  67. package/src/components/Annotations/components/AnnotationDraggable.styles.css +52 -0
  68. package/src/components/Annotations/components/AnnotationDraggable.tsx +639 -139
  69. package/src/components/Annotations/components/AnnotationDropdown.styles.css +44 -4
  70. package/src/components/Annotations/components/AnnotationDropdown.tsx +10 -32
  71. package/src/components/Annotations/helpers/resolveAnnotationLayout.ts +212 -0
  72. package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +612 -19
  73. package/src/components/Annotations/tests/AnnotationDropdown.test.tsx +50 -0
  74. package/src/components/Annotations/tests/resolveAnnotationLayout.test.ts +203 -0
  75. package/src/components/Axis/BottomAxis.tsx +1 -1
  76. package/src/components/Axis/Categorical.Axis.tsx +3 -3
  77. package/src/components/Axis/RightAxis.tsx +21 -13
  78. package/src/components/BarChart/components/BarChart.Horizontal.tsx +39 -8
  79. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +13 -4
  80. package/src/components/BarChart/components/BarChart.StackedVertical.test.ts +28 -0
  81. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +30 -26
  82. package/src/components/BarChart/components/BarChart.Vertical.tsx +38 -28
  83. package/src/components/BarChart/components/PortionPatternOverlay.tsx +61 -0
  84. package/src/components/BarChart/components/context.tsx +6 -0
  85. package/src/components/BarChart/helpers/getBarData.ts +28 -28
  86. package/src/components/BarChart/helpers/getBarHeights.ts +2 -2
  87. package/src/components/BarChart/helpers/getPatternUrl.ts +2 -5
  88. package/src/components/BarChart/helpers/index.ts +8 -0
  89. package/src/components/BarChart/helpers/portionPattern.ts +243 -0
  90. package/src/components/BarChart/helpers/tests/addMinimumBarHeights.test.ts +42 -0
  91. package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +13 -0
  92. package/src/components/BarChart/helpers/tests/portionPattern.test.ts +280 -0
  93. package/src/components/BarChart/helpers/tests/useBarChart.test.tsx +202 -0
  94. package/src/components/BarChart/helpers/useBarChart.ts +60 -60
  95. package/src/components/BarChartRace/BarChartRace.test.tsx +199 -0
  96. package/src/components/BarChartRace/BarChartRace.tsx +230 -0
  97. package/src/components/BarChartRace/BarChartRaceFallback.test.tsx +61 -0
  98. package/src/components/BarChartRace/BarChartRaceFallback.tsx +48 -0
  99. package/src/components/BarChartRace/bar-chart-race.css +160 -0
  100. package/src/components/BarChartRace/helpers.test.ts +114 -0
  101. package/src/components/BarChartRace/helpers.ts +185 -0
  102. package/src/components/BarChartRace/index.ts +3 -0
  103. package/src/components/BoxPlot/BoxPlot.Horizontal.tsx +7 -6
  104. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +9 -7
  105. package/src/components/BoxPlot/helpers/index.ts +13 -83
  106. package/src/components/Brush/BrushSelector.tsx +10 -24
  107. package/src/components/Brush/MiniChartPreview.tsx +33 -1
  108. package/src/components/Dendrogram/components/Dendrogram.tsx +374 -0
  109. package/src/components/Dendrogram/dendrogram.css +113 -0
  110. package/src/components/Dendrogram/helpers/layoutDendrogram.test.ts +95 -0
  111. package/src/components/Dendrogram/helpers/layoutDendrogram.ts +56 -0
  112. package/src/components/Dendrogram/helpers/prepareDendrogramData.test.ts +135 -0
  113. package/src/components/Dendrogram/helpers/prepareDendrogramData.ts +166 -0
  114. package/src/components/Dendrogram/helpers/resolveDendrogramLabels.test.ts +196 -0
  115. package/src/components/Dendrogram/helpers/resolveDendrogramLabels.ts +418 -0
  116. package/src/components/Dendrogram/index.tsx +1 -0
  117. package/src/components/Dendrogram/types/index.ts +66 -0
  118. package/src/components/EditorPanel/EditorPanel.tsx +1111 -805
  119. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +50 -5
  120. package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.css +10 -0
  121. package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.tsx +211 -0
  122. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +19 -8
  123. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +104 -61
  124. package/src/components/EditorPanel/components/Panels/Panel.Network.css +9 -0
  125. package/src/components/EditorPanel/components/Panels/Panel.Network.tsx +202 -0
  126. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +236 -36
  127. package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +6 -1
  128. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +89 -105
  129. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +21 -6
  130. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +1 -1
  131. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +8 -66
  132. package/src/components/EditorPanel/components/Panels/RacingControls.test.tsx +131 -0
  133. package/src/components/EditorPanel/components/Panels/RacingControls.tsx +185 -0
  134. package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +2 -2
  135. package/src/components/EditorPanel/components/Panels/index.tsx +4 -0
  136. package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +20 -7
  137. package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +25 -2
  138. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +5 -4
  139. package/src/components/EditorPanel/useEditorPermissions.test.tsx +94 -0
  140. package/src/components/EditorPanel/useEditorPermissions.ts +75 -25
  141. package/src/components/Forecasting/Forecasting.tsx +16 -7
  142. package/src/components/HeatMap/components/HeatMap.tsx +511 -178
  143. package/src/components/HeatMap/heatmap.constants.ts +7 -3
  144. package/src/components/HeatMap/heatmap.css +101 -2
  145. package/src/components/HeatMap/helpers/layout.ts +80 -0
  146. package/src/components/HeatMap/tests/HeatMap.test.tsx +339 -16
  147. package/src/components/HeatMap/tests/helpers.test.ts +79 -0
  148. package/src/components/Legend/Legend.Component.tsx +163 -148
  149. package/src/components/Legend/Legend.Suppression.tsx +3 -3
  150. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +7 -0
  151. package/src/components/Legend/LegendGroup/LegendGroup.tsx +47 -35
  152. package/src/components/Legend/helpers/createFormatLabels.test.ts +226 -0
  153. package/src/components/Legend/helpers/createFormatLabels.tsx +45 -51
  154. package/src/components/LineChart/components/LineChart.Circle.tsx +3 -3
  155. package/src/components/LineChart/helpers.test.ts +126 -38
  156. package/src/components/LineChart/helpers.ts +9 -6
  157. package/src/components/LineChart/index.tsx +71 -27
  158. package/src/components/LineChart/tests/LineChart.test.tsx +630 -0
  159. package/src/components/LineChartRace/LineChartRace.test.tsx +275 -0
  160. package/src/components/LineChartRace/LineChartRace.tsx +134 -0
  161. package/src/components/LineChartRace/LineChartRaceFallback.tsx +28 -0
  162. package/src/components/LineChartRace/helpers.test.ts +88 -0
  163. package/src/components/LineChartRace/helpers.ts +157 -0
  164. package/src/components/LineChartRace/index.ts +3 -0
  165. package/src/components/LineChartRace/line-chart-race.css +16 -0
  166. package/src/components/LinearChart/ValueAxisAnchors.tsx +58 -0
  167. package/src/components/LinearChart/VisualizationRenderer.tsx +18 -23
  168. package/src/components/LinearChart/tests/LinearChart.test.tsx +412 -13
  169. package/src/components/LinearChart/tests/VisualizationRenderer.test.tsx +141 -0
  170. package/src/components/LinearChart/tests/mockConfigContext.ts +2 -1
  171. package/src/components/LinearChart.tsx +85 -54
  172. package/src/components/Network/components/Network.tsx +372 -0
  173. package/src/components/Network/helpers/layoutNetwork.test.ts +68 -0
  174. package/src/components/Network/helpers/layoutNetwork.ts +87 -0
  175. package/src/components/Network/helpers/prepareNetworkData.test.ts +116 -0
  176. package/src/components/Network/helpers/prepareNetworkData.ts +101 -0
  177. package/src/components/Network/helpers/resolveNetworkLabels.test.ts +112 -0
  178. package/src/components/Network/helpers/resolveNetworkLabels.ts +206 -0
  179. package/src/components/Network/index.tsx +3 -0
  180. package/src/components/Network/network.css +107 -0
  181. package/src/components/Network/types/index.ts +56 -0
  182. package/src/components/PairedBarChart.jsx +5 -10
  183. package/src/components/PairedBarChart.test.tsx +71 -0
  184. package/src/components/PieChart/PieChart.tsx +121 -13
  185. package/src/components/PieChart/helpers/labelPlacement.test.ts +88 -0
  186. package/src/components/PieChart/helpers/labelPlacement.ts +95 -0
  187. package/src/components/RacingChartRenderer.tsx +44 -0
  188. package/src/components/Regions/components/Regions.test.tsx +179 -0
  189. package/src/components/Regions/components/Regions.tsx +104 -69
  190. package/src/components/Regions/index.tsx +2 -3
  191. package/src/components/Sankey/components/Sankey.tsx +264 -437
  192. package/src/components/Sankey/helpers/getSankeyTooltip.ts +12 -0
  193. package/src/components/Sankey/helpers/prepareSankeyData.test.ts +71 -0
  194. package/src/components/Sankey/helpers/prepareSankeyData.ts +90 -0
  195. package/src/components/Sankey/sankey.scss +57 -116
  196. package/src/components/Sankey/types/index.ts +41 -4
  197. package/src/components/ScatterPlot/ScatterPlot.jsx +28 -12
  198. package/src/components/ScatterPlot/ScatterPlot.test.tsx +60 -0
  199. package/src/components/SmallMultiples/SmallMultiples.tsx +12 -12
  200. package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +7 -5
  201. package/src/components/raceTiming.ts +18 -0
  202. package/src/data/initial-state.js +79 -31
  203. package/src/helpers/applyChartTypeContract.ts +71 -0
  204. package/src/helpers/axisLabelFontSize.ts +2 -2
  205. package/src/helpers/boxPlotStats.ts +60 -0
  206. package/src/helpers/bumpChartTooltip.ts +2 -1
  207. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.test.ts +37 -0
  208. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +3 -2
  209. package/src/helpers/calculateRightYAxisWidth.ts +60 -0
  210. package/src/helpers/categoryOrder.ts +60 -0
  211. package/src/helpers/colorAssignmentHelpers.ts +2 -2
  212. package/src/helpers/createNewChartConfig.ts +56 -0
  213. package/src/helpers/dataDrivenYAxisCategories.ts +18 -0
  214. package/src/helpers/forecastLegend.ts +88 -0
  215. package/src/helpers/getBoxPlotConfig.ts +26 -38
  216. package/src/helpers/getChartTypeDefaultPalette.ts +29 -0
  217. package/src/helpers/getColorCodeCategoryColorMap.ts +26 -0
  218. package/src/helpers/getColorScale.ts +31 -22
  219. package/src/helpers/getComboChartConfig.ts +0 -1
  220. package/src/helpers/getDynamicYAxisCategories.ts +69 -0
  221. package/src/helpers/getExcludedData.ts +0 -1
  222. package/src/helpers/getHasBoundarySuppression.ts +4 -3
  223. package/src/helpers/getMinMax.ts +11 -8
  224. package/src/helpers/getNewRuntime.ts +4 -3
  225. package/src/helpers/getPiePercent.ts +3 -3
  226. package/src/helpers/getStackedSeriesMax.ts +51 -0
  227. package/src/helpers/getV21ChartDistributionColors.ts +57 -0
  228. package/src/helpers/handleRankByValue.ts +15 -15
  229. package/src/helpers/isConvertLineToBarGraph.ts +2 -2
  230. package/src/helpers/seriesColumnSettings.ts +31 -3
  231. package/src/helpers/sizeHelpers.ts +4 -5
  232. package/src/helpers/smallMultiplesHelpers.ts +10 -13
  233. package/src/helpers/tests/applyChartTypeContract.test.ts +37 -0
  234. package/src/helpers/tests/boxPlotStats.test.ts +31 -0
  235. package/src/helpers/tests/bumpChartTooltip.test.ts +29 -0
  236. package/src/helpers/tests/calculateRightYAxisWidth.test.ts +105 -0
  237. package/src/helpers/tests/categoryOrder.test.ts +57 -0
  238. package/src/helpers/tests/countNumOfTicks.test.ts +11 -0
  239. package/src/helpers/tests/createNewChartConfig.test.ts +182 -0
  240. package/src/helpers/tests/getBoxPlotConfig.test.ts +70 -0
  241. package/src/helpers/tests/getColorCodeCategoryColorMap.test.ts +113 -0
  242. package/src/helpers/tests/getColorScale.test.ts +226 -1
  243. package/src/helpers/tests/getDynamicYAxisCategories.test.ts +61 -0
  244. package/src/helpers/tests/getMinMax.test.ts +41 -0
  245. package/src/helpers/tests/getStackedSeriesMax.test.ts +26 -0
  246. package/src/helpers/tests/handleRankByValue.test.ts +55 -37
  247. package/src/helpers/tests/seriesColumnSettings.test.ts +83 -0
  248. package/src/helpers/tests/smallMultiplesHelpers.test.ts +37 -1
  249. package/src/helpers/tests/tooltipHelpers.test.ts +80 -0
  250. package/src/helpers/tooltipHelpers.ts +16 -0
  251. package/src/hooks/tests/useRightAxis.test.tsx +143 -2
  252. package/src/hooks/tests/useScales.test.tsx +3 -6
  253. package/src/hooks/tests/useTooltip.test.tsx +484 -0
  254. package/src/hooks/usePrefersReducedMotion.ts +22 -0
  255. package/src/hooks/useReduceData.ts +4 -36
  256. package/src/hooks/useRightAxis.ts +19 -26
  257. package/src/hooks/useScales.ts +15 -0
  258. package/src/hooks/useTooltip.tsx +106 -71
  259. package/src/scss/main.scss +74 -2
  260. package/src/store/chart.reducer.ts +4 -4
  261. package/src/test/CdcChart.dataTable.test.tsx +582 -14
  262. package/src/test/CdcChart.dendrogram.test.tsx +277 -0
  263. package/src/test/CdcChart.finalization.test.tsx +143 -0
  264. package/src/test/CdcChart.network.test.tsx +271 -0
  265. package/src/types/ChartConfig.ts +91 -54
  266. package/src/types/ChartContext.ts +5 -0
  267. package/src/types/Horizon.ts +1 -1
  268. package/LICENSE +0 -201
  269. package/dist/cdcchart-BWVIUcAF.es.js +0 -4802
  270. package/src/_stories/Chart.Sankey.smoke.stories.tsx +0 -62
  271. package/src/components/Sankey/components/ColumnList.tsx +0 -19
  272. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +0 -34
  273. package/src/components/Sankey/useSankeyAlert.tsx +0 -60
  274. package/src/data/legacy-defaults.ts +0 -18
  275. package/src/helpers/ensureSpecialChartAxisTypes.ts +0 -11
  276. package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +0 -30
@@ -0,0 +1,202 @@
1
+ import { useContext, FC } from 'react'
2
+ import {
3
+ AccordionItem,
4
+ AccordionItemHeading,
5
+ AccordionItemPanel,
6
+ AccordionItemButton
7
+ } from 'react-accessible-accordion'
8
+ import { CheckBox, Select, TextField } from '@cdc/core/components/EditorPanel/Inputs'
9
+
10
+ import ConfigContext from '../../../../ConfigContext'
11
+ import type { ChartContext } from '../../../../types/ChartContext'
12
+ import { useEditorPanelContext } from '../../EditorPanelContext'
13
+ import { type PanelProps } from '../PanelProps'
14
+ import WarningImage from '../../../../images/warning.svg'
15
+ import './Panel.Network.css'
16
+
17
+ const NetworkSettings: FC<PanelProps> = ({ name }) => {
18
+ const { config, updateConfig } = useContext<ChartContext>(ConfigContext)
19
+ const { getColumns } = useEditorPanelContext()
20
+
21
+ if (config.visualizationType !== 'Network') return null
22
+
23
+ const network = config.network || ({} as NonNullable<typeof config.network>)
24
+ const columns = network.columns || {
25
+ source: '',
26
+ target: '',
27
+ weight: '',
28
+ style: '',
29
+ nodeColor: ''
30
+ }
31
+ const columnOptions = getColumns?.(false) || []
32
+ const isMissingColumn = (column: unknown) =>
33
+ !column || (columnOptions.length > 0 && !columnOptions.includes(String(column)))
34
+
35
+ const updateColumn = (_section: string, _subsection: string, fieldName: string, value: string) => {
36
+ updateConfig?.({
37
+ ...config,
38
+ network: { ...network, columns: { ...columns, [fieldName]: value } }
39
+ })
40
+ }
41
+
42
+ const updateLinkStyleLabel = (_section: string, _subsection: string, fieldName: string, value: string) => {
43
+ updateConfig?.({
44
+ ...config,
45
+ network: {
46
+ ...network,
47
+ linkStyleLabels: {
48
+ solid: network.linkStyleLabels?.solid ?? 'Solid',
49
+ dashed: network.linkStyleLabels?.dashed ?? 'Dashed',
50
+ [fieldName]: value
51
+ }
52
+ }
53
+ })
54
+ }
55
+
56
+ const updateNetworkField = (_section: string, _subsection: string, fieldName: string, value: string | boolean) => {
57
+ const numericFields = ['height', 'nodeRadius', 'linkDistance', 'chargeStrength']
58
+ let nextValue: string | boolean | number =
59
+ fieldName === 'height' && value === '' ? 500 : numericFields.includes(fieldName) ? Number(value) : value
60
+ if (fieldName === 'chargeStrength') nextValue = Math.min(0, Number(nextValue))
61
+ updateConfig?.({ ...config, network: { ...network, [fieldName]: nextValue } })
62
+ }
63
+
64
+ return (
65
+ <AccordionItem>
66
+ <AccordionItemHeading>
67
+ <AccordionItemButton data-required-field-section='network-columns'>
68
+ {name}
69
+ {(isMissingColumn(columns.source) || isMissingColumn(columns.target)) && (
70
+ <WarningImage width='25' className='warning-icon' />
71
+ )}
72
+ </AccordionItemButton>
73
+ </AccordionItemHeading>
74
+ <AccordionItemPanel>
75
+ <Select
76
+ value={columns.source || ''}
77
+ section='network'
78
+ subsection='columns'
79
+ fieldName='source'
80
+ label='Source Column'
81
+ data-required-field-control='network-source'
82
+ initial='Select'
83
+ required
84
+ updateField={updateColumn}
85
+ options={columnOptions}
86
+ />
87
+ <Select
88
+ value={columns.target || ''}
89
+ section='network'
90
+ subsection='columns'
91
+ fieldName='target'
92
+ label='Target Column'
93
+ data-required-field-control='network-target'
94
+ initial='Select'
95
+ required
96
+ updateField={updateColumn}
97
+ options={columnOptions}
98
+ />
99
+ <Select
100
+ value={columns.weight || ''}
101
+ section='network'
102
+ subsection='columns'
103
+ fieldName='weight'
104
+ label='Weight Column (Optional)'
105
+ initial='None'
106
+ updateField={updateColumn}
107
+ options={columnOptions}
108
+ />
109
+ <Select
110
+ value={columns.style || ''}
111
+ section='network'
112
+ subsection='columns'
113
+ fieldName='style'
114
+ label='Link Style Column (Optional)'
115
+ initial='None'
116
+ updateField={updateColumn}
117
+ options={columnOptions}
118
+ />
119
+ <TextField
120
+ value={network.linkStyleLabels?.solid ?? 'Solid'}
121
+ section='network'
122
+ subsection='linkStyleLabels'
123
+ fieldName='solid'
124
+ label='Solid Link Legend Label'
125
+ updateField={updateLinkStyleLabel}
126
+ />
127
+ <TextField
128
+ value={network.linkStyleLabels?.dashed ?? 'Dashed'}
129
+ section='network'
130
+ subsection='linkStyleLabels'
131
+ fieldName='dashed'
132
+ label='Dashed Link Legend Label'
133
+ updateField={updateLinkStyleLabel}
134
+ />
135
+ <Select
136
+ value={columns.nodeColor || ''}
137
+ section='network'
138
+ subsection='columns'
139
+ fieldName='nodeColor'
140
+ label='Node Color Column (Optional)'
141
+ initial='None'
142
+ updateField={updateColumn}
143
+ options={columnOptions}
144
+ />
145
+ <CheckBox
146
+ value={Boolean(network.directed)}
147
+ fieldName='directed'
148
+ label='Directed Network'
149
+ updateField={updateNetworkField}
150
+ />
151
+ <TextField
152
+ value={network.height ?? 500}
153
+ type='number'
154
+ section='network'
155
+ fieldName='height'
156
+ label='Height'
157
+ min={160}
158
+ updateField={updateNetworkField}
159
+ />
160
+ <TextField
161
+ value={network.nodeRadius ?? 8}
162
+ type='number'
163
+ section='network'
164
+ fieldName='nodeRadius'
165
+ label='Node Radius'
166
+ min={2}
167
+ updateField={updateNetworkField}
168
+ />
169
+ <label className='network-settings__range'>
170
+ <span className='edit-label column-heading'>
171
+ Link Distance: <output>{network.linkDistance ?? 90}</output>
172
+ </span>
173
+ <input
174
+ type='range'
175
+ aria-label='Link Distance'
176
+ min={20}
177
+ max={300}
178
+ step={5}
179
+ value={network.linkDistance ?? 90}
180
+ onChange={event => updateNetworkField('network', '', 'linkDistance', event.target.value)}
181
+ />
182
+ </label>
183
+ <label className='network-settings__range'>
184
+ <span className='edit-label column-heading'>
185
+ Repulsion Strength: <output>{network.chargeStrength ?? -240}</output>
186
+ </span>
187
+ <input
188
+ type='range'
189
+ aria-label='Repulsion Strength'
190
+ min={-1000}
191
+ max={0}
192
+ step={10}
193
+ value={network.chargeStrength ?? -240}
194
+ onChange={event => updateNetworkField('network', '', 'chargeStrength', event.target.value)}
195
+ />
196
+ </label>
197
+ </AccordionItemPanel>
198
+ </AccordionItem>
199
+ )
200
+ }
201
+
202
+ export default NetworkSettings
@@ -15,24 +15,22 @@ import Alert from '@cdc/core/components/Alert'
15
15
  import { Select } from '@cdc/core/components/EditorPanel/Inputs'
16
16
  import ConfigContext from '../../../../ConfigContext'
17
17
  import { ChartContext } from '../../../../types/ChartContext'
18
+ import { type LegendPattern } from '../../../../types/ChartConfig'
18
19
  import { PanelProps } from '../PanelProps'
19
20
  import { checkColorContrast, getColorContrast } from '@cdc/core/helpers/cove/accessibility'
20
21
  import { getColorScale } from '../../../../helpers/getColorScale'
21
22
  import { sanitizeToSvgId } from '@cdc/core/helpers/cove/string'
23
+ import {
24
+ ensurePatternColumnConfig,
25
+ findColumnConfigKey,
26
+ isPortionPatternSupported,
27
+ removeUnusedPatternColumnConfig,
28
+ type ChartColumns
29
+ } from '../../../BarChart/helpers/portionPattern'
22
30
 
23
31
  const PanelPatternSettings: FC<PanelProps> = props => {
24
32
  const { config, updateConfig, transformedData } = useContext<ChartContext>(ConfigContext)
25
- if (config.visualizationType === 'HeatMap') return null
26
-
27
- type LegendPattern = {
28
- label?: string
29
- color?: string
30
- shape?: 'circles' | 'lines' | 'diagonalLines' | 'waves'
31
- dataKey?: string
32
- dataValue?: string
33
- contrastCheck?: boolean
34
- patternSize?: number
35
- }
33
+ if (['HeatMap', 'Network', 'Dendrogram'].includes(config.visualizationType)) return null
36
34
 
37
35
  // Safe legend reference with defaults to avoid crashes when legend is undefined
38
36
  const legendCfg = (config.legend || { patterns: {} }) as {
@@ -68,7 +66,7 @@ const PanelPatternSettings: FC<PanelProps> = props => {
68
66
  case 'large':
69
67
  return 16
70
68
  default:
71
- return 8
69
+ return 10
72
70
  }
73
71
  }
74
72
 
@@ -155,6 +153,33 @@ const PanelPatternSettings: FC<PanelProps> = props => {
155
153
 
156
154
  const fieldOptions = getFieldOptions()
157
155
  const currentPatterns: Record<string, LegendPattern> = legendCfg.patterns || {}
156
+ const protectedColumnNames = [
157
+ ...(config.series || []).map(series => series.dataKey),
158
+ config.xAxis?.dataKey,
159
+ config.yAxis?.dataKey
160
+ ]
161
+
162
+ const portionPatternsSupported = isPortionPatternSupported(config)
163
+ const eligibleTargetKeys = portionPatternsSupported
164
+ ? config.visualizationType === 'Combo'
165
+ ? (config.series || []).filter(series => series.type === 'Bar').map(series => series.dataKey)
166
+ : (config.series || []).map(series => series.dataKey)
167
+ : []
168
+ const eligibleTargetOptions = eligibleTargetKeys.map(dataKey => ({
169
+ value: dataKey,
170
+ label: config.runtime?.seriesLabels?.[dataKey] || dataKey
171
+ }))
172
+
173
+ const getAvailableTargetOptions = (patternKey: string) => {
174
+ const targetsUsedByOtherPatterns = new Set(
175
+ Object.entries(currentPatterns)
176
+ .filter(([key, pattern]) => key !== patternKey && pattern.application === 'portion')
177
+ .map(([, pattern]) => pattern.dataKey)
178
+ .filter(Boolean)
179
+ )
180
+
181
+ return eligibleTargetOptions.filter(option => !targetsUsedByOtherPatterns.has(option.value))
182
+ }
158
183
 
159
184
  const handlePatternReorder = (sourceIndex: number, destinationIndex: number) => {
160
185
  const patternEntries = Object.entries(currentPatterns)
@@ -180,8 +205,12 @@ const PanelPatternSettings: FC<PanelProps> = props => {
180
205
  const currentPatterns = legendCfg.patterns || {}
181
206
 
182
207
  // For charts, we'll add a default pattern that users can configure
183
- const newPatternKey = `Pattern${Object.keys(currentPatterns).length + 1}`
184
- const defaultColor = '#000000'
208
+ let nextPatternNumber = 1
209
+ while (Object.prototype.hasOwnProperty.call(currentPatterns, `Pattern${nextPatternNumber}`)) {
210
+ nextPatternNumber += 1
211
+ }
212
+ const newPatternKey = `Pattern${nextPatternNumber}`
213
+ const defaultColor = '#1c1d1f'
185
214
  const defaultDataKey = fieldOptions.length > 0 ? fieldOptions[0].value : ''
186
215
 
187
216
  const newPatterns = {
@@ -189,10 +218,10 @@ const PanelPatternSettings: FC<PanelProps> = props => {
189
218
  [newPatternKey]: {
190
219
  label: newPatternKey,
191
220
  color: defaultColor,
192
- shape: 'circles' as const,
221
+ shape: 'diagonalLines',
193
222
  dataKey: defaultDataKey,
194
223
  dataValue: '',
195
- patternSize: 8, // Default pattern size
224
+ patternSize: 10, // Default pattern size
196
225
  contrastCheck: performContrastCheck(newPatternKey, defaultColor)
197
226
  }
198
227
  }
@@ -219,10 +248,21 @@ const PanelPatternSettings: FC<PanelProps> = props => {
219
248
 
220
249
  const handleRemovePattern = (patternKey: string) => {
221
250
  const newPatterns = { ...(legendCfg.patterns || {}) }
251
+ const removedPattern = newPatterns[patternKey]
222
252
  delete newPatterns[patternKey]
253
+ const columns =
254
+ removedPattern?.application === 'portion'
255
+ ? removeUnusedPatternColumnConfig({
256
+ columns: { ...(config.columns || {}) },
257
+ columnName: removedPattern.patternValueKey,
258
+ patterns: newPatterns,
259
+ protectedColumnNames
260
+ })
261
+ : config.columns
223
262
 
224
263
  const updatedConfig = {
225
264
  ...config,
265
+ columns,
226
266
  legend: {
227
267
  ...(config.legend || {}),
228
268
  patterns: newPatterns
@@ -282,6 +322,16 @@ const PanelPatternSettings: FC<PanelProps> = props => {
282
322
  }
283
323
 
284
324
  const handlePatternUpdate = (patternKey: string, field: string, value: any) => {
325
+ if (
326
+ field === 'dataKey' &&
327
+ legendCfg.patterns?.[patternKey]?.application === 'portion' &&
328
+ Object.entries(legendCfg.patterns || {}).some(
329
+ ([key, pattern]) => key !== patternKey && pattern.application === 'portion' && pattern.dataKey === value
330
+ )
331
+ ) {
332
+ return
333
+ }
334
+
285
335
  const updatedPattern = {
286
336
  ...(legendCfg.patterns?.[patternKey] || {}),
287
337
  [field]: value
@@ -292,8 +342,23 @@ const PanelPatternSettings: FC<PanelProps> = props => {
292
342
  [patternKey]: updatedPattern
293
343
  }
294
344
 
345
+ let columns: ChartColumns = { ...(config.columns || {}) }
346
+ if (field === 'patternValueKey') {
347
+ const previousColumnName = legendCfg.patterns?.[patternKey]?.patternValueKey
348
+ if (previousColumnName !== value) {
349
+ columns = removeUnusedPatternColumnConfig({
350
+ columns,
351
+ columnName: previousColumnName,
352
+ patterns: newPatterns,
353
+ protectedColumnNames
354
+ })
355
+ }
356
+ columns = ensurePatternColumnConfig(columns, value)
357
+ }
358
+
295
359
  const updatedConfig = {
296
360
  ...config,
361
+ columns,
297
362
  legend: {
298
363
  ...(config.legend || {}),
299
364
  patterns: newPatterns
@@ -311,6 +376,52 @@ const PanelPatternSettings: FC<PanelProps> = props => {
311
376
  updateConfig(updatedConfig)
312
377
  }
313
378
 
379
+ const handleApplicationUpdate = (patternKey: string, application: 'value' | 'portion') => {
380
+ const currentPattern = legendCfg.patterns?.[patternKey] || {}
381
+ const availableTargets = getAvailableTargetOptions(patternKey)
382
+ const dataKey =
383
+ application === 'portion' && !availableTargets.some(option => option.value === currentPattern.dataKey)
384
+ ? availableTargets[0]?.value || ''
385
+ : currentPattern.dataKey
386
+
387
+ const updatedPattern: LegendPattern = {
388
+ ...currentPattern,
389
+ application,
390
+ dataKey
391
+ }
392
+
393
+ if (application === 'portion') {
394
+ updatedPattern.placement = currentPattern.placement === 'start' ? 'start' : 'end'
395
+ updatedPattern.patternValueKey = currentPattern.patternValueKey || ''
396
+ }
397
+
398
+ const newPatterns = {
399
+ ...(legendCfg.patterns || {}),
400
+ [patternKey]: updatedPattern
401
+ }
402
+ let columns: ChartColumns = { ...(config.columns || {}) }
403
+
404
+ if (application === 'portion') {
405
+ columns = ensurePatternColumnConfig(columns, updatedPattern.patternValueKey)
406
+ } else if (currentPattern.application === 'portion') {
407
+ columns = removeUnusedPatternColumnConfig({
408
+ columns,
409
+ columnName: currentPattern.patternValueKey,
410
+ patterns: newPatterns,
411
+ protectedColumnNames
412
+ })
413
+ }
414
+
415
+ updateConfig({
416
+ ...config,
417
+ columns,
418
+ legend: {
419
+ ...(config.legend || {}),
420
+ patterns: newPatterns
421
+ }
422
+ })
423
+ }
424
+
314
425
  if (config.visualizationType === 'Warming Stripes' || config.visualizationType === 'Radar') return
315
426
 
316
427
  return (
@@ -360,7 +471,9 @@ const PanelPatternSettings: FC<PanelProps> = props => {
360
471
  <AccordionItemHeading className='series-item__title'>
361
472
  <AccordionItemButton className='accordion__button'>
362
473
  <Icon display='move' size={15} style={{ cursor: 'default' }} />
363
- {p.dataKey && p.dataValue
474
+ {p.application === 'portion' && p.dataKey
475
+ ? `${p.dataKey}: Portion`
476
+ : p.dataKey && p.dataValue
364
477
  ? `${p.dataKey}: ${p.dataValue}`
365
478
  : p.dataValue
366
479
  ? `All Series: ${p.dataValue}`
@@ -394,29 +507,116 @@ const PanelPatternSettings: FC<PanelProps> = props => {
394
507
  )}
395
508
 
396
509
  <Select
397
- label='Data Key:'
398
- value={p.dataKey || ''}
399
- options={fieldOptions}
400
- initial='Select Data Key'
401
- fieldName={`pattern-datakey-${domPatternKey}`}
510
+ label='Pattern Application'
511
+ value={p.application || 'value'}
512
+ options={[
513
+ { value: 'value', label: 'Match a data value' },
514
+ { value: 'portion', label: 'Cover a portion of a bar' }
515
+ ]}
516
+ fieldName={`pattern-application-${domPatternKey}`}
402
517
  updateField={(section, subsection, fieldName, value) =>
403
- handlePatternUpdate(patternKey, 'dataKey', value)
518
+ handleApplicationUpdate(patternKey, value as 'value' | 'portion')
404
519
  }
405
520
  />
406
521
 
407
- <label htmlFor={`pattern-datavalue-${domPatternKey}`}>
408
- Data Value:
409
- <input
410
- type='text'
411
- id={`pattern-datavalue-${domPatternKey}`}
412
- value={p.dataValue || ''}
413
- onChange={e => handlePatternUpdate(patternKey, 'dataValue', e.target.value)}
414
- placeholder='Enter data value'
522
+ {p.application === 'portion' && !portionPatternsSupported && (
523
+ <Alert
524
+ type='danger'
525
+ message='Portion patterns are supported only for regular unstacked bar charts and Combo charts with linear axes and without lollipops.'
526
+ showCloseButton={false}
415
527
  />
416
- </label>
528
+ )}
529
+
530
+ {(p.application || 'value') === 'value' ? (
531
+ <>
532
+ <Select
533
+ label='Data Key'
534
+ value={p.dataKey || ''}
535
+ options={fieldOptions}
536
+ initial='Select Data Key'
537
+ fieldName={`pattern-datakey-${domPatternKey}`}
538
+ updateField={(section, subsection, fieldName, value) =>
539
+ handlePatternUpdate(patternKey, 'dataKey', value)
540
+ }
541
+ />
542
+
543
+ <label htmlFor={`pattern-datavalue-${domPatternKey}`}>
544
+ Data Value
545
+ <input
546
+ type='text'
547
+ id={`pattern-datavalue-${domPatternKey}`}
548
+ value={p.dataValue || ''}
549
+ onChange={e => handlePatternUpdate(patternKey, 'dataValue', e.target.value)}
550
+ placeholder='Enter data value'
551
+ />
552
+ </label>
553
+ </>
554
+ ) : (
555
+ <>
556
+ <Select
557
+ label='Target Bar Series'
558
+ value={p.dataKey || ''}
559
+ options={getAvailableTargetOptions(patternKey)}
560
+ initial='Select Target Bar Series'
561
+ fieldName={`pattern-target-series-${domPatternKey}`}
562
+ updateField={(section, subsection, fieldName, value) =>
563
+ handlePatternUpdate(patternKey, 'dataKey', value)
564
+ }
565
+ />
566
+
567
+ <Select
568
+ label='Pattern Value Column'
569
+ tooltip={
570
+ <Tooltip style={{ textTransform: 'none' }}>
571
+ <Tooltip.Target>
572
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
573
+ </Tooltip.Target>
574
+ <Tooltip.Content>
575
+ <p>
576
+ Select the data column containing the absolute portion amount for each bar. For
577
+ a row to receive an overlay, its value must be numeric, greater than zero, and
578
+ no greater than the corresponding value in the Target Bar Series. Use
579
+ quantities, not percentages.
580
+ </p>
581
+ </Tooltip.Content>
582
+ </Tooltip>
583
+ }
584
+ value={p.patternValueKey || ''}
585
+ options={fieldOptions}
586
+ initial='Select Pattern Value Column'
587
+ fieldName={`pattern-value-key-${domPatternKey}`}
588
+ updateField={(section, subsection, fieldName, value) =>
589
+ handlePatternUpdate(patternKey, 'patternValueKey', value)
590
+ }
591
+ />
592
+
593
+ {p.patternValueKey && findColumnConfigKey(config.columns || {}, p.patternValueKey) && (
594
+ <div
595
+ className='border rounded p-2 mt-2 small'
596
+ data-testid='portion-pattern-column-note'
597
+ >
598
+ <strong>Note:</strong> Configure this pattern&apos;s tooltip and data table settings
599
+ in Columns.
600
+ </div>
601
+ )}
602
+
603
+ <Select
604
+ label='Placement'
605
+ value={p.placement === 'start' ? 'start' : 'end'}
606
+ options={[
607
+ { value: 'start', label: 'Beginning' },
608
+ { value: 'end', label: 'End' }
609
+ ]}
610
+ fieldName={`pattern-placement-${domPatternKey}`}
611
+ updateField={(section, subsection, fieldName, value) =>
612
+ handlePatternUpdate(patternKey, 'placement', value)
613
+ }
614
+ />
615
+ </>
616
+ )}
417
617
 
418
618
  <label htmlFor={`pattern-label-${domPatternKey}`}>
419
- Label (optional):
619
+ Label (optional)
420
620
  <input
421
621
  type='text'
422
622
  id={`pattern-label-${domPatternKey}`}
@@ -426,7 +626,7 @@ const PanelPatternSettings: FC<PanelProps> = props => {
426
626
  </label>
427
627
 
428
628
  <Select
429
- label='Pattern Type:'
629
+ label='Pattern Type'
430
630
  value={p.shape || 'circles'}
431
631
  options={patternTypes}
432
632
  fieldName={`pattern-type-${domPatternKey}`}
@@ -436,8 +636,8 @@ const PanelPatternSettings: FC<PanelProps> = props => {
436
636
  />
437
637
 
438
638
  <Select
439
- label='Pattern Size:'
440
- value={getPatternSizeText(p.patternSize || 8)}
639
+ label='Pattern Size'
640
+ value={getPatternSizeText(p.patternSize || 10)}
441
641
  options={patternSizes}
442
642
  fieldName={`pattern-size-${domPatternKey}`}
443
643
  updateField={(section, subsection, fieldName, value) =>
@@ -12,6 +12,7 @@ import ConfigContext from '../../../../ConfigContext'
12
12
  import { useEditorPanelContext } from '../../EditorPanelContext'
13
13
  import { type PanelProps } from './../PanelProps'
14
14
  import { ChartContext } from '../../../../types/ChartContext'
15
+ import WarningImage from '../../../../images/warning.svg'
15
16
 
16
17
  const PanelRadar: FC<PanelProps> = props => {
17
18
  const { config, rawData } = useContext<ChartContext>(ConfigContext)
@@ -30,7 +31,10 @@ const PanelRadar: FC<PanelProps> = props => {
30
31
  return (
31
32
  <AccordionItem>
32
33
  <AccordionItemHeading>
33
- <AccordionItemButton>{props.name}</AccordionItemButton>
34
+ <AccordionItemButton data-required-field-section='radar-category'>
35
+ {props.name}
36
+ {!config.xAxis?.dataKey && <WarningImage width='25' className='warning-icon' />}
37
+ </AccordionItemButton>
34
38
  </AccordionItemHeading>
35
39
  <AccordionItemPanel>
36
40
  {/* Data Settings */}
@@ -42,6 +46,7 @@ const PanelRadar: FC<PanelProps> = props => {
42
46
  section='xAxis'
43
47
  fieldName='dataKey'
44
48
  label='Category Column'
49
+ data-required-field-control='radar-category'
45
50
  initial='Select'
46
51
  required={true}
47
52
  updateField={updateField}