@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
@@ -1,15 +1,15 @@
1
- import { ChartConfig } from '../types/ChartConfig'
2
-
3
- const getNumericValue = number => {
4
- if (typeof number === 'string') return parseFloat(number.replace(/,/g, ''))
5
- return Number(number)
6
- }
7
-
8
- export const handleRankByValue = (data, passedConfig: ChartConfig) => {
9
- if (passedConfig.rankByValue) {
10
- const series = passedConfig.series[0].dataKey
11
- const sorted = data.sort((a, b) => getNumericValue(a[series]) - getNumericValue(b[series]))
12
- return passedConfig.rankByValue === 'asc' ? sorted : sorted.reverse()
13
- }
14
- return data
15
- }
1
+ import { ChartConfig } from '../types/ChartConfig'
2
+
3
+ const getNumericValue = number => {
4
+ if (typeof number === 'string') return parseFloat(number.replace(/,/g, ''))
5
+ return Number(number)
6
+ }
7
+
8
+ export const handleRankByValue = (data, passedConfig: ChartConfig) => {
9
+ if (passedConfig.rankByValue) {
10
+ const series = passedConfig.series[0].dataKey
11
+ const sorted = data.toSorted((a, b) => getNumericValue(a[series]) - getNumericValue(b[series]))
12
+ return passedConfig.rankByValue === 'asc' ? sorted : sorted.reverse()
13
+ }
14
+ return data
15
+ }
@@ -1,4 +1,4 @@
1
- import _ from 'lodash'
1
+ import uniq from 'lodash/uniq'
2
2
 
3
3
  export const isConvertLineToBarGraph = (configObj, filteredData) => {
4
4
  const { allowLineToBarGraph, series, visualizationType, xAxis } = configObj
@@ -6,6 +6,6 @@ export const isConvertLineToBarGraph = (configObj, filteredData) => {
6
6
  const lineWithLessThanThreePoints = visualizationType === 'Line' && filteredData?.length < 3
7
7
  const isDynamicSeries = series?.some(series => series.dynamicCategory)
8
8
  const isDynamicWithLessThanThreePoints =
9
- isDynamicSeries && _.uniq(filteredData?.map(data => data[xAxis.dataKey])).length <= 2
9
+ isDynamicSeries && uniq(filteredData?.map(data => data[xAxis.dataKey])).length <= 2
10
10
  return lineWithLessThanThreePoints || isDynamicWithLessThanThreePoints
11
11
  }
@@ -12,13 +12,22 @@ type ColumnFormattingParams = {
12
12
 
13
13
  const hasOwn = (object: object, key: keyof Column) => Object.prototype.hasOwnProperty.call(object, key)
14
14
  const isNonEmptyString = (value: unknown) => typeof value === 'string' && value !== ''
15
+ const getNumericRoundToPlace = (value: Column['roundToPlace']): number | undefined => {
16
+ if (value === undefined || value === null || (typeof value === 'string' && value.trim() === '')) {
17
+ return undefined
18
+ }
19
+
20
+ const numericValue = Number(value)
21
+ if (!Number.isFinite(numericValue)) return undefined
22
+
23
+ return Math.max(0, Math.min(20, Math.round(numericValue)))
24
+ }
15
25
 
16
26
  export const createDefaultSeriesColumnConfig = (columnName: string): Column => ({
17
27
  name: columnName,
18
28
  label: columnName,
19
29
  prefix: '',
20
30
  suffix: '',
21
- roundToPlace: 0,
22
31
  commas: false,
23
32
  dataTable: true,
24
33
  order: undefined,
@@ -70,12 +79,18 @@ export const upsertSeriesColumnConfig = (
70
79
  ): ChartColumns => {
71
80
  const existingEntry = findColumnConfigByName(columns, seriesKey)
72
81
  const columnKey = existingEntry?.columnKey || seriesKey
73
- const nextColumnConfig = {
82
+ const nextColumnConfig: Partial<Column> = {
74
83
  ...(existingEntry?.columnConfig || {}),
75
84
  ...updates,
76
85
  name: seriesKey
77
86
  }
78
87
 
88
+ Object.entries(updates).forEach(([key, value]) => {
89
+ if (value === undefined) {
90
+ delete nextColumnConfig[key as keyof Column]
91
+ }
92
+ })
93
+
79
94
  if (
80
95
  nextColumnConfig.label === undefined &&
81
96
  !hasOwn(existingEntry?.columnConfig || {}, 'label') &&
@@ -104,7 +119,10 @@ export const getSeriesColumnFormattingParams = (columnConfig?: Partial<Column>):
104
119
  }
105
120
 
106
121
  if (hasOwn(columnConfig, 'roundToPlace')) {
107
- formattingParams.addColRoundTo = columnConfig.roundToPlace ?? 0
122
+ const numericRoundToPlace = getNumericRoundToPlace(columnConfig.roundToPlace)
123
+ if (numericRoundToPlace !== undefined) {
124
+ formattingParams.addColRoundTo = numericRoundToPlace
125
+ }
108
126
  }
109
127
 
110
128
  if (hasOwn(columnConfig, 'commas')) {
@@ -113,3 +131,13 @@ export const getSeriesColumnFormattingParams = (columnConfig?: Partial<Column>):
113
131
 
114
132
  return Object.keys(formattingParams).length ? formattingParams : undefined
115
133
  }
134
+
135
+ export const getAdditionalColumnFormattingParams = (columnConfig?: Partial<Column>): ColumnFormattingParams => {
136
+ const explicitFormattingParams = getSeriesColumnFormattingParams(columnConfig)
137
+
138
+ return {
139
+ addColPrefix: '',
140
+ addColSuffix: '',
141
+ ...(explicitFormattingParams || {})
142
+ }
143
+ }
@@ -1,12 +1,11 @@
1
- import { clamp } from 'lodash'
2
-
3
1
  import { isMobileHeightViewport } from '@cdc/core/helpers/viewports'
4
- import { ChartConfig, ViewportSize } from '../types/ChartConfig'
2
+ import { ChartConfig } from '../types/ChartConfig'
5
3
  import { EDITOR_WIDTH } from '@cdc/core/helpers/constants'
4
+ import type { ViewPort } from '@cdc/core/types/ViewPort'
6
5
 
7
6
  export function getOrientation(
8
7
  { orientation, heights, visualizationType }: Pick<ChartConfig, 'orientation' | 'heights' | 'visualizationType'>,
9
- currentViewport: ViewportSize
8
+ currentViewport: ViewPort
10
9
  ): 'vertical' | 'horizontal' | 'mobileVertical' {
11
10
  const isForestPlot = visualizationType === 'Forest Plot'
12
11
  const useVertical = orientation === 'vertical' || isForestPlot
@@ -17,7 +16,7 @@ export function getOrientation(
17
16
  }
18
17
  export function calcInitialHeight(
19
18
  { heights, orientation, visualizationType }: Pick<ChartConfig, 'heights' | 'orientation' | 'visualizationType'>,
20
- currentViewport: ViewportSize
19
+ currentViewport: ViewPort
21
20
  ): number {
22
21
  // if no heights are provided assume config has not been loaded
23
22
  if (!heights) return 0
@@ -1,3 +1,4 @@
1
+ import { sortByNumericText } from '@cdc/core/helpers/sorting'
1
2
  import { scaleOrdinal } from '@visx/scale'
2
3
  import { getColorScale } from './getColorScale'
3
4
  import { ColorScale } from '../types/ChartContext'
@@ -105,18 +106,16 @@ export const applyTileOrder = (tileItems, orderType, customOrder, config) => {
105
106
 
106
107
  switch (orderType) {
107
108
  case 'asc':
108
- return [...tileItems].sort((a, b) => {
109
- const titleA = String(getTileDisplayTitle(a.mode, a.seriesKey, a.tileValue, a.key, config)).toLowerCase()
110
- const titleB = String(getTileDisplayTitle(b.mode, b.seriesKey, b.tileValue, b.key, config)).toLowerCase()
111
- return titleA.localeCompare(titleB)
112
- })
109
+ return sortByNumericText(tileItems, item =>
110
+ getTileDisplayTitle(item.mode, item.seriesKey, item.tileValue, item.key, config)
111
+ )
113
112
 
114
113
  case 'desc':
115
- return [...tileItems].sort((a, b) => {
116
- const titleA = String(getTileDisplayTitle(a.mode, a.seriesKey, a.tileValue, a.key, config)).toLowerCase()
117
- const titleB = String(getTileDisplayTitle(b.mode, b.seriesKey, b.tileValue, b.key, config)).toLowerCase()
118
- return titleB.localeCompare(titleA)
119
- })
114
+ return sortByNumericText(
115
+ tileItems,
116
+ item => getTileDisplayTitle(item.mode, item.seriesKey, item.tileValue, item.key, config),
117
+ 'desc'
118
+ )
120
119
 
121
120
  case 'custom':
122
121
  if (!customOrder || customOrder.length === 0) {
@@ -154,9 +153,7 @@ export const getTileKeys = (config, data) => {
154
153
  const tileColumn = config.smallMultiples.tileColumn
155
154
  if (!tileColumn) return []
156
155
 
157
- return Array.from(new Set(data.map(row => row[tileColumn])))
158
- .filter(val => val != null)
159
- .sort()
156
+ return sortByNumericText(Array.from(new Set(data.map(row => row[tileColumn]))).filter(val => val != null))
160
157
  }
161
158
  return []
162
159
  }
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { applyChartTypeContract } from '../applyChartTypeContract'
3
+
4
+ const chart = (visualizationType: string) => ({
5
+ type: 'chart',
6
+ visualizationType,
7
+ orientation: 'vertical',
8
+ series: [{ dataKey: 'value' }],
9
+ xAxis: { type: 'categorical', dataKey: 'date' },
10
+ yAxis: {},
11
+ legend: {},
12
+ general: { paletteColorCount: 2 },
13
+ table: {}
14
+ })
15
+
16
+ describe('applyChartTypeContract', () => {
17
+ it.each(['Area Chart', 'Horizon Chart', 'Box Plot', 'Bump Chart', 'HeatMap', 'Forecasting'])(
18
+ 'does not mutate the input for %s transitions',
19
+ visualizationType => {
20
+ const input = chart(visualizationType)
21
+ const before = structuredClone(input)
22
+
23
+ const result = applyChartTypeContract(input as any)
24
+
25
+ expect(input).toEqual(before)
26
+ expect(result).not.toBe(input)
27
+ expect(result.xAxis).not.toBe(input.xAxis)
28
+ expect(result.series).not.toBe(input.series)
29
+ }
30
+ )
31
+
32
+ it('authors the stacked Area Chart subtype', () => {
33
+ const result = applyChartTypeContract(chart('Area Chart') as any)
34
+
35
+ expect(result.visualizationSubType).toBe('stacked')
36
+ })
37
+ })
@@ -0,0 +1,31 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { calculateBoxPlotStats, getSortedBoxPlotValues } from '../boxPlotStats'
4
+
5
+ describe('boxPlotStats', () => {
6
+ it('normalizes numeric strings once for all box plot stats consumers', () => {
7
+ expect(getSortedBoxPlotValues(['1,000', '20', '', null, undefined, 'n/a', '3'])).toEqual([3, 20, 1000])
8
+ })
9
+
10
+ it('calculates quartiles, fences, whiskers, and outliers from sorted numeric values', () => {
11
+ const stats = calculateBoxPlotStats(['10', '20', '30', '40', '1,000'])
12
+
13
+ expect(stats).toMatchObject({
14
+ values: [10, 20, 30, 40, 1000],
15
+ q1: 20,
16
+ q3: 40,
17
+ median: 30,
18
+ iqr: 20,
19
+ lowerBound: -10,
20
+ upperBound: 70,
21
+ nonOutliers: [10, 20, 30, 40],
22
+ outliers: [1000],
23
+ whiskerMin: 10,
24
+ whiskerMax: 40
25
+ })
26
+ })
27
+
28
+ it('returns null when no finite values can be calculated', () => {
29
+ expect(calculateBoxPlotStats(['', null, undefined, 'n/a'])).toBeNull()
30
+ })
31
+ })
@@ -72,4 +72,33 @@ describe('buildBumpChartTooltipHtml', () => {
72
72
  expect(html).not.toContain('2014-01-01T00:00:00.000Z')
73
73
  expect(html).toContain('Frequency: 149')
74
74
  })
75
+
76
+ it('uses a customized column label instead of the active runtime series name', () => {
77
+ const html = buildBumpChartTooltipHtml({
78
+ config: buildConfig({
79
+ columns: {
80
+ Rank: { name: 'Rank', label: 'Rank Label' }
81
+ }
82
+ }),
83
+ colorScale: undefined,
84
+ dataRow: { Timestamp: '2014-01-01T00:00:00.000Z', Rank: '1' },
85
+ series: { dataKey: 'Rank', name: 'Runtime Rank' },
86
+ helpers
87
+ })
88
+
89
+ expect(html).toContain('Rank Label: 1')
90
+ expect(html).not.toContain('Runtime Rank: 1')
91
+ })
92
+
93
+ it('falls back to the series name when the column label matches the data key', () => {
94
+ const html = buildBumpChartTooltipHtml({
95
+ config: buildConfig(),
96
+ colorScale: undefined,
97
+ dataRow: { Timestamp: '2014-01-01T00:00:00.000Z', Rank: '1' },
98
+ series: { dataKey: 'Rank', name: 'Runtime Rank' },
99
+ helpers
100
+ })
101
+
102
+ expect(html).toContain('Runtime Rank: 1')
103
+ })
75
104
  })
@@ -0,0 +1,105 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import { createMockConfig } from '../../components/LinearChart/tests/mockConfigContext'
3
+
4
+ vi.mock('@cdc/core/helpers/getTextWidth', () => ({
5
+ getTextWidth: (text: string) => text.length
6
+ }))
7
+
8
+ import { calculateRightYAxisWidth } from '../calculateRightYAxisWidth'
9
+
10
+ const formatNumber = (value: any) => String(value)
11
+ const tickLabelFont = 'normal 16px sans-serif'
12
+ const axisLabelFontSize = 16
13
+
14
+ const createRightAxisConfig = (yAxisOverrides = {}, runtimeYAxisOverrides = {}) => {
15
+ const baseConfig = createMockConfig()
16
+
17
+ return createMockConfig({
18
+ yAxis: {
19
+ ...baseConfig.yAxis,
20
+ rightAxisSize: 0,
21
+ rightHideAxis: false,
22
+ rightHideLabel: false,
23
+ rightHideTicks: false,
24
+ rightLabel: '',
25
+ rightLabelOffsetSize: 0,
26
+ rightTitlePlacement: 'top',
27
+ ...yAxisOverrides
28
+ },
29
+ runtime: {
30
+ ...baseConfig.runtime,
31
+ yAxis: {
32
+ ...baseConfig.runtime.yAxis,
33
+ rightHideTicks: false,
34
+ ...runtimeYAxisOverrides
35
+ }
36
+ }
37
+ })
38
+ }
39
+
40
+ describe('calculateRightYAxisWidth', () => {
41
+ it('uses measured right tick label space when saved rightAxisSize is zero', () => {
42
+ const width = calculateRightYAxisWidth({
43
+ axisLabelFontSize,
44
+ config: createRightAxisConfig(),
45
+ formatNumber,
46
+ tickLabelFont,
47
+ tickValues: [0, 1000],
48
+ yScaleRight: undefined
49
+ })
50
+
51
+ expect(width).toBe(17)
52
+ })
53
+
54
+ it('reserves measured side-title space after right tick labels while ignoring saved offset', () => {
55
+ const width = calculateRightYAxisWidth({
56
+ axisLabelFontSize,
57
+ config: createRightAxisConfig({
58
+ rightLabel: 'New Participants',
59
+ rightLabelOffsetSize: 36,
60
+ rightTitlePlacement: 'side'
61
+ }),
62
+ formatNumber,
63
+ tickLabelFont,
64
+ tickValues: [0, 10],
65
+ yScaleRight: undefined
66
+ })
67
+
68
+ expect(width).toBe(36)
69
+ })
70
+
71
+ it('ignores a larger saved rightAxisSize', () => {
72
+ const width = calculateRightYAxisWidth({
73
+ axisLabelFontSize,
74
+ config: createRightAxisConfig({ rightAxisSize: 100 }),
75
+ formatNumber,
76
+ tickLabelFont,
77
+ tickValues: [0, 1000],
78
+ yScaleRight: undefined
79
+ })
80
+
81
+ expect(width).toBe(17)
82
+ })
83
+
84
+ it('allows no gutter when all right-axis content is hidden', () => {
85
+ const width = calculateRightYAxisWidth({
86
+ axisLabelFontSize,
87
+ config: createRightAxisConfig(
88
+ {
89
+ rightHideAxis: true,
90
+ rightHideLabel: true,
91
+ rightTitlePlacement: 'top'
92
+ },
93
+ {
94
+ rightHideTicks: true
95
+ }
96
+ ),
97
+ formatNumber,
98
+ tickLabelFont,
99
+ tickValues: [],
100
+ yScaleRight: undefined
101
+ })
102
+
103
+ expect(width).toBe(0)
104
+ })
105
+ })
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { getOrderedCategoryValues, isCustomCategoryOrderActive, sortByCategoryOrder } from '../categoryOrder'
3
+
4
+ describe('categoryOrder helpers', () => {
5
+ const data = [
6
+ { category: 'A', value: 1 },
7
+ { category: 'D', value: 4 },
8
+ { category: 'C', value: 3 },
9
+ { category: 'D', value: 5 },
10
+ { category: 'L', value: 12 }
11
+ ]
12
+
13
+ it('detects custom category order only for categorical axes with configured values', () => {
14
+ expect(
15
+ isCustomCategoryOrderActive({
16
+ xAxis: { type: 'categorical', dataKey: 'category', categoryOrderType: 'custom', categoryOrder: ['C'] } as any
17
+ })
18
+ ).toBe(true)
19
+
20
+ expect(
21
+ isCustomCategoryOrderActive({
22
+ xAxis: { type: 'date', dataKey: 'category', categoryOrderType: 'custom', categoryOrder: ['C'] } as any
23
+ })
24
+ ).toBe(false)
25
+ })
26
+
27
+ it('sorts rows by configured category order and keeps duplicate rows stable', () => {
28
+ const sorted = sortByCategoryOrder(data, {
29
+ xAxis: {
30
+ type: 'categorical',
31
+ dataKey: 'category',
32
+ categoryOrderType: 'custom',
33
+ categoryOrder: ['A', 'C', 'D', 'L']
34
+ } as any
35
+ })
36
+
37
+ expect(sorted.map(row => row.category)).toEqual(['A', 'C', 'D', 'D', 'L'])
38
+ expect(sorted.map(row => row.value)).toEqual([1, 3, 4, 5, 12])
39
+ })
40
+
41
+ it('appends values missing from custom order in source order', () => {
42
+ const sorted = sortByCategoryOrder(data, {
43
+ xAxis: {
44
+ type: 'categorical',
45
+ dataKey: 'category',
46
+ categoryOrderType: 'custom',
47
+ categoryOrder: ['C']
48
+ } as any
49
+ })
50
+
51
+ expect(sorted.map(row => row.category)).toEqual(['C', 'A', 'D', 'D', 'L'])
52
+ })
53
+
54
+ it('merges configured category order with newly discovered values', () => {
55
+ expect(getOrderedCategoryValues(data, 'category', ['C', 'A'])).toEqual(['C', 'A', 'D', 'L'])
56
+ })
57
+ })
@@ -31,6 +31,17 @@ describe('countNumOfTicks', () => {
31
31
  expect(result).toBe(6)
32
32
  })
33
33
 
34
+ it('treats a null viewport override like the historical missing value', () => {
35
+ const result = countNumOfTicks({
36
+ ...baseArgs,
37
+ axis: 'xAxis',
38
+ runtime: { xAxis: { numTicks: 6, viewportNumTicks: null } },
39
+ currentViewport: 'xs',
40
+ isHorizontal: false
41
+ })
42
+ expect(result).toBe(6)
43
+ })
44
+
34
45
  it('falls back to numTicks when current viewport has no entry in viewportNumTicks', () => {
35
46
  const result = countNumOfTicks({
36
47
  ...baseArgs,
@@ -0,0 +1,182 @@
1
+ import defaults from '../../data/initial-state'
2
+ import { createNewChartConfig } from '../createNewChartConfig'
3
+ import { CURRENT_COVE_CONFIG_VERSION } from '@cdc/core/helpers/coveUpdateWorker'
4
+
5
+ describe('createNewChartConfig', () => {
6
+ it('deeply combines starter settings with current chart defaults', () => {
7
+ const starterConfig = {
8
+ type: 'chart',
9
+ visualizationType: 'Bar',
10
+ xAxis: { type: 'categorical', size: 100 },
11
+ filters: []
12
+ }
13
+
14
+ const config = createNewChartConfig(starterConfig)
15
+
16
+ expect(config).toMatchObject({
17
+ type: 'chart',
18
+ visualizationType: 'Bar',
19
+ version: CURRENT_COVE_CONFIG_VERSION,
20
+ titleStyle: 'small',
21
+ barThickness: defaults.barThickness,
22
+ xAxis: {
23
+ type: 'categorical',
24
+ size: 100,
25
+ numTicks: 6,
26
+ viewportNumTicks: { xs: 4, xxs: 4 }
27
+ },
28
+ yAxis: {
29
+ titlePlacement: 'top',
30
+ labelPlacement: 'On Date/Category Axis',
31
+ numTicks: 4,
32
+ gridLines: true,
33
+ hideAxis: true,
34
+ hideTicks: true,
35
+ autoMaxStrategy: 'clean-top-tick'
36
+ },
37
+ legend: { position: 'top', unified: true, expandForecastConfidenceIntervals: true },
38
+ table: {
39
+ download: true,
40
+ expanded: false,
41
+ show: true,
42
+ showDataTableLink: false,
43
+ stickyFirstColumn: false
44
+ },
45
+ dataFormat: { commas: true }
46
+ })
47
+ expect(config.filters).toEqual([])
48
+ expect(starterConfig).toEqual({
49
+ type: 'chart',
50
+ visualizationType: 'Bar',
51
+ xAxis: { type: 'categorical', size: 100 },
52
+ filters: []
53
+ })
54
+ })
55
+
56
+ it.each([
57
+ ['Area Chart', { visualizationSubType: 'stacked' }],
58
+ ['Forecasting', { xAxis: { type: 'date', dateParseFormat: '%Y-%m-%d', dateDisplayFormat: '%b. %-d %Y' } }],
59
+ ['HeatMap', { yAxis: { type: 'categorical', titlePlacement: 'top' }, legend: { position: 'top' } }],
60
+ ['Horizon Chart', { horizon: { numLayers: 4, mode: 'offset', bandGap: 15, bottomPadding: 15 } }],
61
+ ['Box Plot', { yAxis: { labelPlacement: 'On Date/Category Axis' } }],
62
+ ['Paired Bar', { orientation: 'horizontal' }],
63
+ ['Deviation Bar', { orientation: 'horizontal' }],
64
+ ['Bump Chart', { xAxis: { type: 'date-time' } }]
65
+ ])('authors the %s stable type contract', (visualizationType, expected) => {
66
+ expect(createNewChartConfig({ type: 'chart', visualizationType })).toMatchObject(expected)
67
+ })
68
+
69
+ it('uses horizontal axis visibility defaults while preserving explicit starter values', () => {
70
+ const config = createNewChartConfig({
71
+ type: 'chart',
72
+ visualizationType: 'Bar',
73
+ orientation: 'horizontal'
74
+ })
75
+
76
+ expect(config.xAxis).toMatchObject({ hideAxis: true, hideTicks: true })
77
+ expect(config.yAxis).toMatchObject({ hideAxis: false, hideTicks: false })
78
+
79
+ const explicitlyConfigured = createNewChartConfig({
80
+ type: 'chart',
81
+ visualizationType: 'Bar',
82
+ orientation: 'horizontal',
83
+ xAxis: { hideAxis: false, hideTicks: false },
84
+ yAxis: { hideAxis: true, hideTicks: true }
85
+ })
86
+
87
+ expect(explicitlyConfigured.xAxis).toMatchObject({ hideAxis: false, hideTicks: false })
88
+ expect(explicitlyConfigured.yAxis).toMatchObject({ hideAxis: true, hideTicks: true })
89
+ })
90
+
91
+ it('uses dashboard-specific table visibility', () => {
92
+ const config = createNewChartConfig({ type: 'chart', visualizationType: 'Bar' }, { isDashboard: true })
93
+
94
+ expect(config.table.show).toBe(false)
95
+ expect(config.table.download).toBe(false)
96
+ expect(config.table.showDataTableLink).toBe(false)
97
+ })
98
+
99
+ it('preserves an explicit dashboard data table link opt-in', () => {
100
+ const config = createNewChartConfig({
101
+ type: 'chart',
102
+ visualizationType: 'Bar',
103
+ table: { showDataTableLink: true }
104
+ })
105
+
106
+ expect(config.table.showDataTableLink).toBe(true)
107
+ })
108
+
109
+ it('preserves an explicit table download opt-out', () => {
110
+ const config = createNewChartConfig({
111
+ type: 'chart',
112
+ visualizationType: 'Bar',
113
+ table: { download: false }
114
+ })
115
+
116
+ expect(config.table.download).toBe(false)
117
+ })
118
+
119
+ it('preserves an explicit unified legend opt-out', () => {
120
+ const config = createNewChartConfig({
121
+ type: 'chart',
122
+ visualizationType: 'Bar',
123
+ legend: { unified: false }
124
+ })
125
+
126
+ expect(config.legend.unified).toBe(false)
127
+ })
128
+
129
+ it('enables expanded forecast confidence interval legends for new charts', () => {
130
+ const config = createNewChartConfig({ type: 'chart', visualizationType: 'Forecasting' })
131
+
132
+ expect(config.legend.expandForecastConfidenceIntervals).toBe(true)
133
+ })
134
+
135
+ it('preserves an explicit expanded forecast confidence interval legend opt-out', () => {
136
+ const config = createNewChartConfig({
137
+ type: 'chart',
138
+ visualizationType: 'Forecasting',
139
+ legend: { expandForecastConfidenceIntervals: false }
140
+ })
141
+
142
+ expect(config.legend.expandForecastConfidenceIntervals).toBe(false)
143
+ })
144
+
145
+ it('uses compact label gaps and automatic ticks for a new HeatMap', () => {
146
+ const config = createNewChartConfig({ type: 'chart', visualizationType: 'HeatMap' })
147
+
148
+ expect(config.heatmap).toMatchObject({
149
+ rowLabelGap: 0,
150
+ columnLabelGap: 20,
151
+ horizontalScroll: false,
152
+ minColumnWidth: 44
153
+ })
154
+ expect(config.xAxis.manual).toBe(false)
155
+ expect(config.xAxis.numTicks).toBeUndefined()
156
+ expect(config.xAxis.viewportNumTicks).toEqual({})
157
+ })
158
+
159
+ it.each([
160
+ ['Bar', 'sequential_blue', false],
161
+ ['Line', 'qualitative_standard', false],
162
+ ['Horizon Chart', 'sequential_blue', false],
163
+ ['HeatMap', 'sequential_blue', false],
164
+ ['Sankey', 'sequential_bluereverse', true]
165
+ ])('uses the new-chart palette for %s charts', (visualizationType, name, isReversed) => {
166
+ const config = createNewChartConfig({ type: 'chart', visualizationType })
167
+
168
+ expect(config.general.palette).toMatchObject({ name, isReversed, version: '2.1' })
169
+ expect(defaults.general.palette.version).toBe('2.1')
170
+ })
171
+
172
+ it('preserves an explicit palette instead of replacing it with the chart-type default', () => {
173
+ const config = createNewChartConfig({
174
+ type: 'chart',
175
+ visualizationType: 'Line',
176
+ general: { palette: { name: 'qualitative_bold', isReversed: true, version: '2.1' } }
177
+ })
178
+
179
+ expect(config.general.palette).toMatchObject({ name: 'qualitative_bold', isReversed: true, version: '2.1' })
180
+ expect(defaults.general.palette.name).toBe('sequential_blue')
181
+ })
182
+ })