@cdc/chart 4.25.8 → 4.25.11

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 (145) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/dist/{cdcchart-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
  3. package/dist/cdcchart.js +44236 -40355
  4. package/examples/feature/__data__/planet-example-data.json +0 -30
  5. package/examples/feature/boxplot/valid-boxplot.csv +38 -17
  6. package/examples/grouped-bar-test.json +400 -0
  7. package/examples/private/DEV-11825.json +573 -0
  8. package/examples/private/d.json +382 -0
  9. package/examples/private/example-2.json +49784 -0
  10. package/examples/private/f2.json +1 -0
  11. package/examples/private/f4.json +1577 -0
  12. package/examples/private/forecast.json +1180 -0
  13. package/examples/private/lollipop.json +468 -0
  14. package/examples/private/na.json +913 -0
  15. package/examples/private/new.json +48756 -0
  16. package/examples/private/pie-chart-legend.json +904 -0
  17. package/examples/private/test-data.csv +28 -0
  18. package/examples/suppressed_tooltip.json +480 -0
  19. package/index.html +2 -133
  20. package/package.json +25 -7
  21. package/src/CdcChart.tsx +9 -13
  22. package/src/CdcChartComponent.tsx +403 -92
  23. package/src/_stories/Chart.Anchors.stories.tsx +2 -2
  24. package/src/_stories/Chart.BoxPlot.stories.tsx +1 -1
  25. package/src/_stories/Chart.CI.stories.tsx +1 -1
  26. package/src/_stories/Chart.Combo.stories.tsx +18 -0
  27. package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
  28. package/src/_stories/Chart.DynamicSeries.stories.tsx +2 -2
  29. package/src/_stories/Chart.Filters.stories.tsx +2 -2
  30. package/src/_stories/Chart.Forecast.stories.tsx +36 -0
  31. package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
  32. package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
  33. package/src/_stories/Chart.Patterns.stories.tsx +20 -0
  34. package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
  35. package/src/_stories/Chart.ScatterPlot.stories.tsx +1 -1
  36. package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
  37. package/src/_stories/Chart.stories.tsx +8 -5
  38. package/src/_stories/Chart.tooltip.stories.tsx +1 -1
  39. package/src/_stories/ChartAnnotation.stories.tsx +7 -4
  40. package/src/_stories/ChartAxisLabels.stories.tsx +2 -2
  41. package/src/_stories/ChartAxisTitles.stories.tsx +2 -2
  42. package/src/_stories/ChartBar.Editor.stories.tsx +3580 -0
  43. package/src/_stories/ChartEditor.Editor.stories.tsx +658 -0
  44. package/src/_stories/ChartEditor.stories.tsx +59 -60
  45. package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
  46. package/src/_stories/ChartLine.Symbols.stories.tsx +1 -1
  47. package/src/_stories/ChartPrefixSuffix.stories.tsx +2 -2
  48. package/src/_stories/_mock/combo.json +451 -0
  49. package/src/_stories/_mock/editor-test-configs.json +376 -0
  50. package/src/_stories/_mock/editor-test-datasets.json +477 -0
  51. package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
  52. package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
  53. package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
  54. package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
  55. package/src/_stories/_mock/pie_config.json +257 -62
  56. package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
  57. package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
  58. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
  59. package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
  60. package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
  61. package/src/_stories/_mock/stacked-pattern-test.json +520 -0
  62. package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -0
  63. package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
  64. package/src/components/Annotations/components/findNearestDatum.ts +6 -41
  65. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -6
  66. package/src/components/AreaChart/index.tsx +1 -2
  67. package/src/components/BarChart/components/BarChart.Horizontal.tsx +161 -22
  68. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +138 -5
  69. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +215 -73
  70. package/src/components/BarChart/components/BarChart.Vertical.tsx +155 -22
  71. package/src/components/BarChart/helpers/index.ts +43 -4
  72. package/src/components/BarChart/helpers/lollipopColors.ts +27 -0
  73. package/src/components/BarChart/helpers/useBarChart.ts +25 -3
  74. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +2 -1
  75. package/src/components/BoxPlot/helpers/index.ts +3 -3
  76. package/src/components/Brush/BrushChart.tsx +1 -1
  77. package/src/components/DeviationBar.jsx +9 -6
  78. package/src/components/EditorPanel/EditorPanel.tsx +563 -229
  79. package/src/components/EditorPanel/EditorPanelContext.ts +3 -0
  80. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
  81. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -1
  82. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +461 -0
  83. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +80 -67
  84. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +422 -0
  85. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +188 -139
  86. package/src/components/EditorPanel/components/Panels/index.tsx +5 -1
  87. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +0 -8
  88. package/src/components/EditorPanel/editor-panel.scss +0 -20
  89. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +49 -48
  90. package/src/components/EditorPanel/useEditorPermissions.ts +7 -15
  91. package/src/components/Forecasting/Forecasting.tsx +175 -27
  92. package/src/components/ForestPlot/ForestPlot.tsx +11 -7
  93. package/src/components/ForestPlot/ForestPlotProps.ts +1 -1
  94. package/src/components/Legend/Legend.Component.tsx +114 -14
  95. package/src/components/Legend/helpers/createFormatLabels.tsx +230 -171
  96. package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
  97. package/src/components/LegendWrapper.tsx +1 -1
  98. package/src/components/LineChart/LineChartProps.ts +0 -3
  99. package/src/components/LineChart/components/LineChart.Circle.tsx +2 -2
  100. package/src/components/LineChart/helpers.ts +1 -1
  101. package/src/components/LineChart/index.tsx +38 -15
  102. package/src/components/LinearChart.tsx +96 -84
  103. package/src/components/PairedBarChart.jsx +6 -4
  104. package/src/components/PieChart/PieChart.tsx +170 -54
  105. package/src/components/Regions/components/Regions.tsx +3 -24
  106. package/src/components/Sankey/components/Sankey.tsx +7 -1
  107. package/src/components/Sankey/types/index.ts +1 -1
  108. package/src/components/ScatterPlot/ScatterPlot.jsx +32 -4
  109. package/src/components/SmallMultiples/SmallMultipleTile.tsx +198 -0
  110. package/src/components/SmallMultiples/SmallMultiples.css +32 -0
  111. package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
  112. package/src/components/SmallMultiples/index.ts +2 -0
  113. package/src/data/initial-state.js +327 -293
  114. package/src/helpers/buildForecastPaletteMappings.ts +112 -0
  115. package/src/helpers/buildForecastPaletteOptions.ts +71 -0
  116. package/src/helpers/getColorScale.ts +82 -8
  117. package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +14 -7
  118. package/src/helpers/getNewRuntime.ts +1 -1
  119. package/src/helpers/getTransformedData.ts +1 -1
  120. package/src/helpers/getYAxisAutoPadding.ts +53 -0
  121. package/src/helpers/smallMultiplesHelpers.ts +529 -0
  122. package/src/hooks/useChartHoverAnalytics.tsx +44 -0
  123. package/src/hooks/useProgrammaticTooltip.ts +96 -0
  124. package/src/hooks/useReduceData.ts +105 -70
  125. package/src/hooks/useScales.ts +88 -34
  126. package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
  127. package/src/hooks/useTooltip.tsx +116 -29
  128. package/src/index.jsx +0 -2
  129. package/src/scss/main.scss +13 -80
  130. package/src/store/chart.actions.ts +2 -0
  131. package/src/store/chart.reducer.ts +5 -1
  132. package/src/test/CdcChart.test.jsx +8 -3
  133. package/src/types/ChartConfig.ts +53 -11
  134. package/src/types/ChartContext.ts +4 -0
  135. package/vite.config.js +1 -1
  136. package/vitest.config.ts +16 -0
  137. package/src/_stories/_mock/pie_data.json +0 -218
  138. package/src/components/AreaChart/components/AreaChart.jsx +0 -109
  139. package/src/coreStyles_chart.scss +0 -3
  140. package/src/helpers/configHelpers.ts +0 -28
  141. package/src/helpers/generateColorsArray.ts +0 -8
  142. package/src/helpers/sort.ts +0 -7
  143. package/src/hooks/useActiveElement.js +0 -19
  144. package/src/hooks/useChartClasses.js +0 -41
  145. package/src/hooks/useColorPalette.js +0 -76
@@ -1,5 +1,5 @@
1
1
  import { useState, useEffect, useCallback, memo, useContext } from 'react'
2
- import { DragDropContext, Droppable } from '@hello-pangea/dnd'
2
+ import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
3
3
  import chroma from 'chroma-js'
4
4
  import { isDateScale } from '@cdc/core/helpers/cove/date'
5
5
  import {
@@ -23,9 +23,11 @@ import { Select, TextField, CheckBox } from '@cdc/core/components/EditorPanel/In
23
23
  import MultiSelect from '@cdc/core/components/MultiSelect'
24
24
  import { viewports } from '@cdc/core/helpers/getViewport'
25
25
  import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
26
+ import PanelMarkup from '@cdc/core/components/EditorPanel/components/PanelMarkup'
26
27
 
27
28
  // chart components
28
29
  import Panels from './components/Panels'
30
+ import PaletteConversionModal from '@cdc/core/components/PaletteConversionModal'
29
31
 
30
32
  // cdc additional
31
33
  import { useEditorPermissions } from './useEditorPermissions'
@@ -34,20 +36,25 @@ import ConfigContext from '../../ConfigContext'
34
36
  import useReduceData from '../../hooks/useReduceData'
35
37
  import useRightAxis from '../../hooks/useRightAxis'
36
38
  import WarningImage from '../../images/warning.svg'
37
- import useMinMax from '../../hooks/useMinMax'
39
+ import getMinMax from '../../helpers/getMinMax'
38
40
 
39
41
  import { type ChartContext } from '../../types/ChartContext'
40
42
  import { type ChartConfig } from '../../types/ChartConfig'
41
43
 
44
+ import '@cdc/core/components/EditorPanel/EditorPanel.styles.css'
42
45
  import './editor-panel.scss'
43
46
  import { Anchor } from '@cdc/core/types/Axis'
44
47
  import EditorPanelContext from './EditorPanelContext'
45
48
  import _ from 'lodash'
46
49
  import { adjustedSymbols as symbolCodes } from '@cdc/core/helpers/footnoteSymbols'
47
50
  import { updateFieldRankByValue } from './helpers/updateFieldRankByValue'
51
+ import cloneConfig from '@cdc/core/helpers/cloneConfig'
48
52
  import FootnotesEditor from '@cdc/core/components/EditorPanel/FootnotesEditor'
49
53
  import { Datasets } from '@cdc/core/types/DataSet'
50
54
  import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
55
+ import { paletteMigrationMap, twoColorPaletteMigrationMap } from '@cdc/core/helpers/palettes/migratePaletteName'
56
+ import { isV1Palette, migratePaletteWithMap } from '@cdc/core/helpers/palettes/utils'
57
+ import { USE_V2_MIGRATION } from '@cdc/core/helpers/constants'
51
58
 
52
59
  interface PreliminaryProps {
53
60
  config: ChartConfig
@@ -627,8 +634,16 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
627
634
  } = useContext<ChartContext>(ConfigContext)
628
635
 
629
636
  const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, unfilteredData)
630
- const properties = { data, config }
631
- const { leftMax, rightMax } = useMinMax(properties)
637
+ const properties = {
638
+ data,
639
+ tableData: data,
640
+ config,
641
+ minValue,
642
+ maxValue,
643
+ existPositiveValue,
644
+ isAllLine
645
+ }
646
+ const { leftMax, rightMax } = getMinMax(properties)
632
647
 
633
648
  const {
634
649
  visHasAnchors,
@@ -825,6 +840,14 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
825
840
 
826
841
  const [displayPanel, setDisplayPanel] = useState(true)
827
842
  const [displayViewportOverrides, setDisplayViewportOverrides] = useState(false)
843
+ const [showConversionModal, setShowConversionModal] = useState(false)
844
+ const [pendingPaletteSelection, setPendingPaletteSelection] = useState<{
845
+ palette: string
846
+ action: () => void
847
+ seriesIndex?: number
848
+ stageIndex?: number
849
+ type?: 'general' | 'twoColor' | 'forecast'
850
+ } | null>(null)
828
851
 
829
852
  const setLollipopShape = shape => {
830
853
  updateConfig({
@@ -892,8 +915,36 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
892
915
 
893
916
  const getColumns = (filter = true) => {
894
917
  let columns = {}
895
- unfilteredData.forEach(row => {
896
- Object.keys(row).forEach(columnName => (columns[columnName] = true))
918
+
919
+ // Try multiple data sources in order of preference
920
+ let dataToUse = []
921
+
922
+ if (unfilteredData && unfilteredData.length > 0) {
923
+ // First preference: unfilteredData from context
924
+ dataToUse = unfilteredData
925
+ } else if (isDashboard && datasets && config.dataKey && datasets[config.dataKey]?.data?.length > 0) {
926
+ // Second preference: data from datasets in dashboard mode
927
+ dataToUse = datasets[config.dataKey].data
928
+ } else if (rawData && rawData.length > 0) {
929
+ // Third preference: rawData from context
930
+ dataToUse = rawData
931
+ } else if (data && data.length > 0) {
932
+ // Fourth preference: transformedData from context
933
+ dataToUse = data
934
+ } else if (config.data && config.data.length > 0) {
935
+ // Fifth preference: data directly from config
936
+ dataToUse = config.data
937
+ }
938
+
939
+ // If we still don't have data, return empty array
940
+ if (!dataToUse || dataToUse.length === 0) {
941
+ return []
942
+ }
943
+
944
+ dataToUse.forEach(row => {
945
+ if (row && typeof row === 'object') {
946
+ Object.keys(row).forEach(columnName => (columns[columnName] = true))
947
+ }
897
948
  })
898
949
 
899
950
  if (filter) {
@@ -975,18 +1026,18 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
975
1026
 
976
1027
  // prettier-ignore
977
1028
  const {
978
- highlightedBarValues,
979
- highlightedSeriesValues,
980
- handleUpdateHighlightedBar,
981
- handleAddNewHighlightedBar,
982
- handleRemoveHighlightedBar,
983
- handleUpdateHighlightedBarColor,
984
- handleHighlightedBarLegendLabel,
985
- handleUpdateHighlightedBorderWidth
986
- } = useHighlightedBars(config, updateConfig)
1029
+ highlightedBarValues,
1030
+ highlightedSeriesValues,
1031
+ handleUpdateHighlightedBar,
1032
+ handleAddNewHighlightedBar,
1033
+ handleRemoveHighlightedBar,
1034
+ handleUpdateHighlightedBarColor,
1035
+ handleHighlightedBarLegendLabel,
1036
+ handleUpdateHighlightedBorderWidth
1037
+ } = useHighlightedBars(config, updateConfig)
987
1038
 
988
1039
  const convertStateToConfig = () => {
989
- let strippedState = _.cloneDeep(config)
1040
+ let strippedState = cloneConfig(config)
990
1041
  if (false === missingRequiredSections(config)) {
991
1042
  delete strippedState.newViz
992
1043
  }
@@ -1093,6 +1144,289 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1093
1144
  const section = config.orientation === 'horizontal' ? 'xAxis' : 'yAxis'
1094
1145
  const [warningMsg, setWarningMsg] = useState({ maxMsg: '', minMsg: '', rightMaxMessage: '', minMsgRight: '' })
1095
1146
 
1147
+ // Palette migration functions
1148
+ const handlePaletteSelection = (palette: string) => {
1149
+ try {
1150
+ // Check if config exists and has basic structure
1151
+ if (!config) {
1152
+ console.error('COVE: Config is undefined in handlePaletteSelection')
1153
+ return
1154
+ }
1155
+
1156
+ // Check if it's a v1 palette configuration
1157
+ const isV1PaletteConfig = isV1Palette(config)
1158
+
1159
+ const executeSelection = () => {
1160
+ const _newConfig = cloneConfig(config)
1161
+ if (!_newConfig.general.palette) {
1162
+ _newConfig.general.palette = {}
1163
+ }
1164
+
1165
+ // If v2 migration is disabled, use the original palette name and keep v1 version
1166
+ if (!USE_V2_MIGRATION) {
1167
+ _newConfig.general.palette.name = palette
1168
+ _newConfig.general.palette.version = '1.0'
1169
+ } else {
1170
+ // V2 migration logic
1171
+ const migratedName = palette ? migratePaletteWithMap(palette, paletteMigrationMap, false) : undefined
1172
+ _newConfig.general.palette.name = migratedName
1173
+ if (isV1PaletteConfig) {
1174
+ _newConfig.general.palette.version = '2.0'
1175
+ }
1176
+ }
1177
+ updateConfig(_newConfig)
1178
+ }
1179
+
1180
+ if (isV1PaletteConfig) {
1181
+ setPendingPaletteSelection({ palette, action: executeSelection, type: 'general' })
1182
+ setShowConversionModal(true)
1183
+ } else {
1184
+ executeSelection()
1185
+ }
1186
+ } catch (error) {
1187
+ console.error('COVE: Error in handlePaletteSelection:', error)
1188
+ }
1189
+ }
1190
+
1191
+ // Two-color palette migration function
1192
+ const handleTwoColorPaletteSelection = (palette: string) => {
1193
+ try {
1194
+ // Check if config exists and has basic structure
1195
+ if (!config) {
1196
+ console.error('COVE: Config is undefined in handleTwoColorPaletteSelection')
1197
+ return
1198
+ }
1199
+
1200
+ // Check if it's a v1 palette configuration
1201
+ const isV1PaletteConfig = isV1Palette(config)
1202
+
1203
+ const executeSelection = () => {
1204
+ const _newConfig = cloneConfig(config)
1205
+ if (!_newConfig.twoColor) {
1206
+ _newConfig.twoColor = { palette: '', isPaletteReversed: false }
1207
+ }
1208
+
1209
+ // If v2 migration is disabled, use the original palette name and keep v1 version
1210
+ if (!USE_V2_MIGRATION) {
1211
+ _newConfig.twoColor.palette = palette
1212
+ if (!_newConfig.general) {
1213
+ _newConfig.general = {}
1214
+ }
1215
+ if (!_newConfig.general.palette) {
1216
+ _newConfig.general.palette = {}
1217
+ }
1218
+ _newConfig.general.palette.version = '1.0'
1219
+ } else {
1220
+ // V2 migration logic
1221
+ const migratedPaletteName = isV1PaletteConfig
1222
+ ? migratePaletteWithMap(palette, twoColorPaletteMigrationMap, false)
1223
+ : palette
1224
+
1225
+ _newConfig.twoColor.palette = migratedPaletteName
1226
+
1227
+ if (isV1PaletteConfig) {
1228
+ if (!_newConfig.general) {
1229
+ _newConfig.general = {}
1230
+ }
1231
+ if (!_newConfig.general.palette) {
1232
+ _newConfig.general.palette = {}
1233
+ }
1234
+ _newConfig.general.palette.version = '2.0'
1235
+
1236
+ // Create backup for rollback functionality (consistent with standard format)
1237
+ if (!_newConfig.general.palette.backups) {
1238
+ _newConfig.general.palette.backups = []
1239
+ }
1240
+ _newConfig.general.palette.backups.push({
1241
+ name: config.twoColor?.palette || palette,
1242
+ version: '1.0',
1243
+ isReversed: false,
1244
+ type: 'twoColor'
1245
+ })
1246
+ }
1247
+ }
1248
+ updateConfig(_newConfig)
1249
+ }
1250
+
1251
+ if (isV1PaletteConfig) {
1252
+ setPendingPaletteSelection({ palette, action: executeSelection, type: 'twoColor' })
1253
+ setShowConversionModal(true)
1254
+ } else {
1255
+ executeSelection()
1256
+ }
1257
+ } catch (error) {
1258
+ console.error('COVE: Error in handleTwoColorPaletteSelection:', error)
1259
+ }
1260
+ }
1261
+
1262
+ // Forecast palette selection - includes v1/v2 migration modal logic
1263
+ const handleForecastPaletteSelection = (palette: string, seriesIndex: number, stageIndex: number) => {
1264
+ try {
1265
+ if (!config) {
1266
+ console.error('COVE: Config is undefined in handleForecastPaletteSelection')
1267
+ return
1268
+ }
1269
+
1270
+ // Check if it's a v1 palette configuration
1271
+ const isV1PaletteConfig = isV1Palette(config)
1272
+
1273
+ const executeSelection = () => {
1274
+ const copyOfSeries = [...config.series]
1275
+ const copyOfStages = [...(copyOfSeries[seriesIndex].stages || [])]
1276
+ copyOfStages[stageIndex] = { ...copyOfStages[stageIndex], color: palette }
1277
+ copyOfSeries[seriesIndex] = { ...copyOfSeries[seriesIndex], stages: copyOfStages }
1278
+
1279
+ const _newConfig = cloneConfig(config)
1280
+ _newConfig.series = copyOfSeries
1281
+
1282
+ // If this is the first v2 palette selection, upgrade to v2
1283
+ if (isV1PaletteConfig && USE_V2_MIGRATION) {
1284
+ if (!_newConfig.general) {
1285
+ _newConfig.general = {}
1286
+ }
1287
+ if (!_newConfig.general.palette) {
1288
+ _newConfig.general.palette = {}
1289
+ }
1290
+ _newConfig.general.palette.version = '2.0'
1291
+
1292
+ // Forecast-specific migration map for v1 → v2 palette names (all lowercase-hyphen format)
1293
+ const forecastPaletteMigrationMap: Record<string, string> = {
1294
+ // Sequential Blue variants → sequential-blue
1295
+ 'sequential-blue': 'sequential-blue',
1296
+ 'sequential-blue-two': 'sequential-blue',
1297
+ 'sequential-blue-three': 'sequential-blue',
1298
+ 'sequential-blue-2-(mpx)': 'sequential-blue',
1299
+ 'sequential-blue-2-mpx': 'sequential-blue',
1300
+ // Sequential Orange variants → sequential-orange
1301
+ 'sequential-orange': 'sequential-orange',
1302
+ 'sequential-orange-two': 'sequential-orange',
1303
+ 'sequential-orange-(mpx)': 'sequential-orange',
1304
+ 'sequential-orange-mpx': 'sequential-orange',
1305
+ // Other sequential palettes (no variants, just normalize)
1306
+ 'sequential-green': 'sequential-green',
1307
+ 'sequential-purple': 'sequential-purple',
1308
+ 'sequential-teal': 'sequential-teal',
1309
+ // Reverse variants - Sequential Blue
1310
+ 'sequential-bluereverse': 'sequential-bluereverse',
1311
+ 'sequential-blue-reverse': 'sequential-bluereverse',
1312
+ 'sequential-blue-tworeverse': 'sequential-bluereverse',
1313
+ 'sequential-blue-two-reverse': 'sequential-bluereverse',
1314
+ 'sequential-blue-threereverse': 'sequential-bluereverse',
1315
+ 'sequential-blue-three-reverse': 'sequential-bluereverse',
1316
+ 'sequential-blue-2-(mpx)reverse': 'sequential-bluereverse',
1317
+ 'sequential-blue-2-(mpx)-reverse': 'sequential-bluereverse',
1318
+ 'sequential-blue-2-mpxreverse': 'sequential-bluereverse',
1319
+ 'sequential-blue-2-mpx-reverse': 'sequential-bluereverse',
1320
+ // Reverse variants - Sequential Orange
1321
+ 'sequential-orangereverse': 'sequential-orangereverse',
1322
+ 'sequential-orange-reverse': 'sequential-orangereverse',
1323
+ 'sequential-orange-tworeverse': 'sequential-orangereverse',
1324
+ 'sequential-orange-two-reverse': 'sequential-orangereverse',
1325
+ 'sequential-orange-(mpx)reverse': 'sequential-orangereverse',
1326
+ 'sequential-orange-(mpx)-reverse': 'sequential-orangereverse',
1327
+ 'sequential-orange-mpxreverse': 'sequential-orangereverse',
1328
+ 'sequential-orange-mpx-reverse': 'sequential-orangereverse',
1329
+ // Reverse variants - Other sequential palettes
1330
+ 'sequential-greenreverse': 'sequential-greenreverse',
1331
+ 'sequential-green-reverse': 'sequential-greenreverse',
1332
+ 'sequential-purplereverse': 'sequential-purplereverse',
1333
+ 'sequential-purple-reverse': 'sequential-purplereverse',
1334
+ 'sequential-tealreverse': 'sequential-tealreverse',
1335
+ 'sequential-teal-reverse': 'sequential-tealreverse'
1336
+ }
1337
+
1338
+ // Migrate and normalize all forecast stage colors to v2 format
1339
+ _newConfig.series.forEach((series: any) => {
1340
+ if (series.type === 'Forecasting' && series.stages) {
1341
+ series.stages.forEach((stage: any) => {
1342
+ if (stage.color) {
1343
+ // First, try to migrate using the map
1344
+ const migrated = forecastPaletteMigrationMap[stage.color] || stage.color
1345
+ // Then normalize to lowercase with hyphens
1346
+ stage.color = migrated.toLowerCase().replace(/ /g, '-').replace(/_/g, '-')
1347
+ }
1348
+ })
1349
+ }
1350
+ })
1351
+ }
1352
+
1353
+ updateConfig(_newConfig)
1354
+ }
1355
+
1356
+ if (isV1PaletteConfig) {
1357
+ setPendingPaletteSelection({ palette, action: executeSelection, type: 'forecast', seriesIndex, stageIndex })
1358
+ setShowConversionModal(true)
1359
+ } else {
1360
+ executeSelection()
1361
+ }
1362
+ } catch (error) {
1363
+ console.error('COVE: Error in handleForecastPaletteSelection:', error)
1364
+ }
1365
+ }
1366
+
1367
+ // Modal handlers
1368
+ const handleConversionConfirm = () => {
1369
+ if (pendingPaletteSelection) {
1370
+ pendingPaletteSelection.action()
1371
+ }
1372
+ setShowConversionModal(false)
1373
+ setPendingPaletteSelection(null)
1374
+ }
1375
+
1376
+ const handleConversionCancel = () => {
1377
+ // Don't update config - just close modal and discard pending selection
1378
+ setShowConversionModal(false)
1379
+ setPendingPaletteSelection(null)
1380
+ }
1381
+
1382
+ const handleReturnToV1 = () => {
1383
+ if (pendingPaletteSelection) {
1384
+ const _newConfig = cloneConfig(config)
1385
+ const { palette, type } = pendingPaletteSelection
1386
+
1387
+ // Handle based on palette type
1388
+ if (type === 'forecast') {
1389
+ // Forecast palette selection
1390
+ const { seriesIndex, stageIndex } = pendingPaletteSelection
1391
+ if (seriesIndex !== undefined && stageIndex !== undefined) {
1392
+ const copyOfSeries = [..._newConfig.series]
1393
+ const copyOfStages = [...copyOfSeries[seriesIndex].stages]
1394
+ copyOfStages[stageIndex] = { ...copyOfStages[stageIndex], color: palette }
1395
+ copyOfSeries[seriesIndex] = { ...copyOfSeries[seriesIndex], stages: copyOfStages }
1396
+ _newConfig.series = copyOfSeries
1397
+ }
1398
+ } else if (type === 'twoColor') {
1399
+ // Two-color palette selection
1400
+ if (!_newConfig.twoColor) {
1401
+ _newConfig.twoColor = { palette: '', isPaletteReversed: false }
1402
+ }
1403
+ _newConfig.twoColor.palette = palette
1404
+ } else {
1405
+ // General palette selection (type === 'general' or undefined for backwards compatibility)
1406
+ if (!_newConfig.general) {
1407
+ _newConfig.general = {}
1408
+ }
1409
+ if (!_newConfig.general.palette) {
1410
+ _newConfig.general.palette = {}
1411
+ }
1412
+ _newConfig.general.palette.name = palette
1413
+ }
1414
+
1415
+ // Set version to V1
1416
+ if (!_newConfig.general) {
1417
+ _newConfig.general = {}
1418
+ }
1419
+ if (!_newConfig.general.palette) {
1420
+ _newConfig.general.palette = {}
1421
+ }
1422
+ _newConfig.general.palette.version = '1.0'
1423
+
1424
+ updateConfig(_newConfig)
1425
+ }
1426
+ setShowConversionModal(false)
1427
+ setPendingPaletteSelection(null)
1428
+ }
1429
+
1096
1430
  const validateMaxValue = () => {
1097
1431
  const enteredValue = config[section].max
1098
1432
  const enteredRightMax = config[section].rightMax
@@ -1232,18 +1566,6 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1232
1566
  )
1233
1567
  }
1234
1568
  })
1235
-
1236
- let columnsByKey = {}
1237
- config.data.forEach(datum => {
1238
- Object.keys(datum).forEach(key => {
1239
- columnsByKey[key] = columnsByKey[key] || []
1240
- const value = typeof datum[key] === 'number' ? datum[key].toString() : datum[key]
1241
-
1242
- if (columnsByKey[key].indexOf(value) === -1) {
1243
- columnsByKey[key].push(value)
1244
- }
1245
- })
1246
- })
1247
1569
  }
1248
1570
 
1249
1571
  // for pie charts
@@ -1261,18 +1583,6 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1261
1583
  )
1262
1584
  }
1263
1585
  })
1264
-
1265
- let columnsByKey = {}
1266
- data.forEach(datum => {
1267
- Object.keys(datum).forEach(key => {
1268
- columnsByKey[key] = columnsByKey[key] || []
1269
- const value = typeof datum[key] === 'number' ? datum[key].toString() : datum[key]
1270
-
1271
- if (columnsByKey[key].indexOf(value) === -1) {
1272
- columnsByKey[key].push(value)
1273
- }
1274
- })
1275
- })
1276
1586
  }
1277
1587
 
1278
1588
  const removeAdditionalColumn = columnName => {
@@ -1379,11 +1689,14 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1379
1689
  handleHighlightedBarLegendLabel,
1380
1690
  handleUpdateHighlightedBar,
1381
1691
  handleRemoveHighlightedBar,
1382
- isPaletteReversed: config.isPaletteReversed,
1692
+ isPaletteReversed: config.general?.palette?.isReversed,
1383
1693
  highlightedSeriesValues,
1384
1694
  handleUpdateHighlightedBorderWidth,
1385
1695
  handleUpdateHighlightedBarColor,
1386
- setLollipopShape
1696
+ setLollipopShape,
1697
+ handlePaletteSelection,
1698
+ handleTwoColorPaletteSelection,
1699
+ handleForecastPaletteSelection
1387
1700
  }
1388
1701
  if (isLoading) {
1389
1702
  return <></>
@@ -1478,20 +1791,25 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1478
1791
  options={getColumns()}
1479
1792
  />
1480
1793
  {config.series && config.series.length !== 0 && (
1481
- <Panels.Series.Wrapper getColumns={getColumns}>
1794
+ <Panels.Series.Wrapper
1795
+ getColumns={getColumns}
1796
+ handleForecastPaletteSelection={handleForecastPaletteSelection}
1797
+ >
1482
1798
  <fieldset>
1483
- <legend className='edit-label float-left'>Displaying</legend>
1484
- <Tooltip style={{ textTransform: 'none' }}>
1485
- <Tooltip.Target>
1486
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1487
- </Tooltip.Target>
1488
- <Tooltip.Content>
1489
- <p>
1490
- A data series is a set of related data points plotted in a chart and typically
1491
- represented in the chart legend.
1492
- </p>
1493
- </Tooltip.Content>
1494
- </Tooltip>
1799
+ <legend className='edit-label d-flex align-items-center'>
1800
+ Displaying
1801
+ <Tooltip style={{ textTransform: 'none' }}>
1802
+ <Tooltip.Target>
1803
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1804
+ </Tooltip.Target>
1805
+ <Tooltip.Content>
1806
+ <p>
1807
+ A data series is a set of related data points plotted in a chart and typically
1808
+ represented in the chart legend.
1809
+ </p>
1810
+ </Tooltip.Content>
1811
+ </Tooltip>
1812
+ </legend>
1495
1813
  </fieldset>
1496
1814
 
1497
1815
  <DragDropContext
@@ -1606,9 +1924,30 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1606
1924
  )}
1607
1925
  {config.visualizationType !== 'Pie' && (
1608
1926
  <>
1609
- <label>
1610
- <span className='edit-label'>
1611
- Axis Type
1927
+ <Select
1928
+ label='Axis Type'
1929
+ value={config.yAxis.type}
1930
+ options={[
1931
+ { value: 'linear', label: 'Numeric (Linear Scale)' },
1932
+ ...(config.visualizationSubType !== 'stacked'
1933
+ ? [{ value: 'logarithmic', label: 'Numeric (Logarithmic Scale)' }]
1934
+ : []),
1935
+ ...(config.orientation !== 'horizontal'
1936
+ ? [{ value: 'categorical', label: 'Categorical' }]
1937
+ : [])
1938
+ ]}
1939
+ section='yAxis'
1940
+ fieldName='type'
1941
+ updateField={(_section, _subsection, _fieldName, value) => {
1942
+ updateConfig({
1943
+ ...config,
1944
+ yAxis: {
1945
+ ...config.yAxis,
1946
+ type: value
1947
+ }
1948
+ })
1949
+ }}
1950
+ tooltip={
1612
1951
  <Tooltip style={{ textTransform: 'none', display: 'inline-block' }}>
1613
1952
  <Tooltip.Target>
1614
1953
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
@@ -1618,26 +1957,8 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1618
1957
  exponential data, or 'Categorical' for discrete categories.
1619
1958
  </Tooltip.Content>
1620
1959
  </Tooltip>
1621
- </span>
1622
- <select
1623
- value={config.yAxis.type}
1624
- onChange={e =>
1625
- updateConfig({
1626
- ...config,
1627
- yAxis: {
1628
- ...config.yAxis,
1629
- type: e.target.value
1630
- }
1631
- })
1632
- }
1633
- >
1634
- <option value='linear'>Numeric (Linear Scale)</option>
1635
- {config.visualizationSubType !== 'stacked' && (
1636
- <option value='logarithmic'>Numeric (Logarithmic Scale)</option>
1637
- )}
1638
- {config.orientation !== 'horizontal' && <option value='categorical'>Categorical</option>}
1639
- </select>
1640
- </label>
1960
+ }
1961
+ />
1641
1962
  <CategoricalAxis
1642
1963
  config={config}
1643
1964
  updateConfig={updateConfig}
@@ -1683,25 +2004,6 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1683
2004
  </Tooltip>
1684
2005
  }
1685
2006
  />
1686
- <TextField
1687
- display={!visHasCategoricalAxis()}
1688
- value={config.yAxis.inlineLabel}
1689
- section='yAxis'
1690
- fieldName='inlineLabel'
1691
- label='Inline Label'
1692
- updateField={updateFieldDeprecated}
1693
- maxLength={35}
1694
- tooltip={
1695
- <Tooltip style={{ textTransform: 'none' }}>
1696
- <Tooltip.Target>
1697
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1698
- </Tooltip.Target>
1699
- <Tooltip.Content>
1700
- <p>35 character limit</p>
1701
- </Tooltip.Content>
1702
- </Tooltip>
1703
- }
1704
- />
1705
2007
  {config.runtime.seriesKeys &&
1706
2008
  config.runtime.seriesKeys.length === 1 &&
1707
2009
  !['Box Plot', 'Deviation Bar', 'Forest Plot'].includes(config.visualizationType) && (
@@ -1940,6 +2242,30 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1940
2242
  updateField={updateFieldDeprecated}
1941
2243
  min={0}
1942
2244
  />{' '}
2245
+ <CheckBox
2246
+ value={config.dataFormat.preserveOriginalDecimals}
2247
+ section='dataFormat'
2248
+ fieldName='preserveOriginalDecimals'
2249
+ label='Preserve Original Decimal Places'
2250
+ updateField={updateFieldDeprecated}
2251
+ tooltip={
2252
+ <Tooltip style={{ textTransform: 'none' }}>
2253
+ <Tooltip.Target>
2254
+ <Icon
2255
+ display='question'
2256
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2257
+ />
2258
+ </Tooltip.Target>
2259
+ <Tooltip.Content>
2260
+ <p>
2261
+ When enabled, numbers will display with their original decimal places from the data source,
2262
+ bypassing the "Round to decimal point" setting above. This is useful when you have mixed
2263
+ data (e.g., whole numbers in one column and percentages with decimals in another).
2264
+ </p>
2265
+ </Tooltip.Content>
2266
+ </Tooltip>
2267
+ }
2268
+ />
1943
2269
  <div className='two-col-inputs'>
1944
2270
  <TextField
1945
2271
  value={config.dataFormat.prefix}
@@ -2203,28 +2529,25 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2203
2529
  />
2204
2530
  </label>
2205
2531
 
2206
- <label>
2207
- Anchor Line Style
2208
- <select
2209
- value={config.yAxis.anchors[index].lineStyle || ''}
2210
- onChange={e => {
2211
- const copiedAnchors = [...config.yAxis.anchors]
2212
- copiedAnchors[index].lineStyle = e.target.value
2213
- updateConfig({
2214
- ...config,
2215
- yAxis: {
2216
- ...config.yAxis,
2217
- anchors: copiedAnchors
2218
- }
2219
- })
2220
- }}
2221
- >
2222
- <option>Select</option>
2223
- {lineOptions.map(line => (
2224
- <option key={line.key}>{line.value}</option>
2225
- ))}
2226
- </select>
2227
- </label>
2532
+ <Select
2533
+ value={config.yAxis.anchors[index].lineStyle || ''}
2534
+ label='Anchor Line Style'
2535
+ onChange={e => {
2536
+ const copiedAnchors = [...config.yAxis.anchors]
2537
+ copiedAnchors[index].lineStyle = e.target.value
2538
+ updateConfig({
2539
+ ...config,
2540
+ yAxis: {
2541
+ ...config.yAxis,
2542
+ anchors: copiedAnchors
2543
+ }
2544
+ })
2545
+ }}
2546
+ options={[
2547
+ { value: '', label: 'Select' },
2548
+ ...lineOptions.map(line => ({ value: line.value, label: line.value }))
2549
+ ]}
2550
+ />
2228
2551
  </AccordionItemPanel>
2229
2552
  </AccordionItem>
2230
2553
  ))}
@@ -2328,28 +2651,25 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2328
2651
  />
2329
2652
  </label>
2330
2653
 
2331
- <label>
2332
- Anchor Line Style
2333
- <select
2334
- value={config.xAxis.anchors[index].lineStyle || ''}
2335
- onChange={e => {
2336
- const copiedAnchors = [...config.xAxis.anchors]
2337
- copiedAnchors[index].lineStyle = e.target.value
2338
- updateConfig({
2339
- ...config,
2340
- xAxis: {
2341
- ...config.xAxis,
2342
- anchors: copiedAnchors
2343
- }
2344
- })
2345
- }}
2346
- >
2347
- <option>Select</option>
2348
- {lineOptions.map(line => (
2349
- <option key={line.key}>{line.value}</option>
2350
- ))}
2351
- </select>
2352
- </label>
2654
+ <Select
2655
+ value={config.xAxis.anchors[index].lineStyle || ''}
2656
+ label='Anchor Line Style'
2657
+ onChange={e => {
2658
+ const copiedAnchors = [...config.xAxis.anchors]
2659
+ copiedAnchors[index].lineStyle = e.target.value
2660
+ updateConfig({
2661
+ ...config,
2662
+ xAxis: {
2663
+ ...config.xAxis,
2664
+ anchors: copiedAnchors
2665
+ }
2666
+ })
2667
+ }}
2668
+ options={[
2669
+ { value: '', label: 'Select' },
2670
+ ...lineOptions.map(line => ({ value: line.value, label: line.value }))
2671
+ ]}
2672
+ />
2353
2673
  </AccordionItemPanel>
2354
2674
  </AccordionItem>
2355
2675
  ))}
@@ -2555,9 +2875,9 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2555
2875
  <>
2556
2876
  {config.visualizationType !== 'Forest Plot' && (
2557
2877
  <>
2558
- <label>
2559
- <span className='edit-label'>
2560
- Data Scaling Type
2878
+ <Select
2879
+ label='Data Scaling Type'
2880
+ tooltip={
2561
2881
  <Tooltip style={{ textTransform: 'none', display: 'inline-block' }}>
2562
2882
  <Tooltip.Target>
2563
2883
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
@@ -2567,31 +2887,32 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2567
2887
  time-series data.
2568
2888
  </Tooltip.Content>
2569
2889
  </Tooltip>
2570
- </span>
2571
- <select
2572
- value={config.xAxis.type}
2573
- onChange={e =>
2574
- updateConfig({
2575
- ...config,
2576
- xAxis: {
2577
- ...config.xAxis,
2578
- type: e.target.value
2579
- }
2580
- })
2581
- }
2582
- >
2583
- {config.visualizationType !== 'Bump Chart' && (
2584
- <option value='categorical'>Categorical (Linear Scale)</option>
2585
- )}
2586
- {config.visualizationType !== 'Bump Chart' && (
2587
- <option value='date'>Date (Linear Scale)</option>
2588
- )}
2589
- <option value='date-time'>Date (Date Time Scale)</option>
2590
- {config.visualizationType === 'Scatter Plot' && (
2591
- <option value={'continuous'}>Continuous</option>
2592
- )}
2593
- </select>
2594
- </label>
2890
+ }
2891
+ value={config.xAxis.type}
2892
+ options={[
2893
+ ...(!['Bump Chart', 'Forecasting'].includes(config.visualizationType)
2894
+ ? [{ label: 'Categorical (Linear Scale)', value: 'categorical' }]
2895
+ : []),
2896
+ ...(!['Bump Chart'].includes(config.visualizationType)
2897
+ ? [{ label: 'Date (Linear Scale)', value: 'date' }]
2898
+ : []),
2899
+ { label: 'Date (Date Time Scale)', value: 'date-time' },
2900
+ ...(config.visualizationType === 'Scatter Plot'
2901
+ ? [{ label: 'Continuous', value: 'continuous' }]
2902
+ : [])
2903
+ ]}
2904
+ section='xAxis'
2905
+ fieldName='type'
2906
+ updateField={(_section, _subsection, _fieldName, value) => {
2907
+ updateConfig({
2908
+ ...config,
2909
+ xAxis: {
2910
+ ...config.xAxis,
2911
+ type: value
2912
+ }
2913
+ })
2914
+ }}
2915
+ />
2595
2916
  <CheckBox
2596
2917
  value={config.xAxis.manual}
2597
2918
  section='xAxis'
@@ -2772,7 +3093,9 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2772
3093
  </>
2773
3094
  )}
2774
3095
 
2775
- {(isDateScale(config.xAxis) || config?.visualizationType === 'Bump Chart') && (
3096
+ {(isDateScale(config.xAxis) ||
3097
+ config?.visualizationType === 'Bump Chart' ||
3098
+ config?.visualizationType === 'Forecasting') && (
2776
3099
  <>
2777
3100
  <p style={{ padding: '1.5em 0 0.5em', fontSize: '.9rem', lineHeight: '1rem' }}>
2778
3101
  Format how charts should parse and display your dates using{' '}
@@ -3306,21 +3629,19 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3306
3629
  Remove
3307
3630
  </button>
3308
3631
  <p>Highlighted Bar {i + 1}</p>
3309
- <label>
3310
- <span className='edit-label column-heading'>Value</span>
3311
- <select
3312
- value={config.highlightedBarValues[i].value}
3313
- onChange={e => handleUpdateHighlightedBar(e, i)}
3314
- >
3315
- <option value=''>- Select Value -</option>
3316
- {highlightedSeriesValues &&
3317
- [...new Set(highlightedSeriesValues)]
3318
- .sort()
3319
- .map(option => (
3320
- <option key={`special-class-value-option-${i}-${option}`}>{option}</option>
3321
- ))}
3322
- </select>
3323
- </label>
3632
+ <Select
3633
+ value={config.highlightedBarValues[i].value}
3634
+ label='Value'
3635
+ onChange={e => handleUpdateHighlightedBar(e, i)}
3636
+ options={[
3637
+ { value: '', label: '- Select Value -' },
3638
+ ...(highlightedSeriesValues
3639
+ ? [...new Set(highlightedSeriesValues)]
3640
+ .sort()
3641
+ .map(option => ({ value: option, label: option }))
3642
+ : [])
3643
+ ]}
3644
+ />
3324
3645
  <label>
3325
3646
  <span className='edit-label column-heading'>Color</span>
3326
3647
  <input
@@ -3524,28 +3845,25 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3524
3845
  />
3525
3846
  </label>
3526
3847
 
3527
- <label>
3528
- Anchor Line Style
3529
- <select
3530
- value={config.xAxis.anchors[index].lineStyle || ''}
3531
- onChange={e => {
3532
- const copiedAnchors = [...config.xAxis.anchors]
3533
- copiedAnchors[index].lineStyle = e.target.value
3534
- updateConfig({
3535
- ...config,
3536
- xAxis: {
3537
- ...config.xAxis,
3538
- anchors: copiedAnchors
3539
- }
3540
- })
3541
- }}
3542
- >
3543
- <option>Select</option>
3544
- {lineOptions.map(line => (
3545
- <option key={line.key}>{line.value}</option>
3546
- ))}
3547
- </select>
3548
- </label>
3848
+ <Select
3849
+ value={config.xAxis.anchors[index].lineStyle || ''}
3850
+ label='Anchor Line Style'
3851
+ onChange={e => {
3852
+ const copiedAnchors = [...config.xAxis.anchors]
3853
+ copiedAnchors[index].lineStyle = e.target.value
3854
+ updateConfig({
3855
+ ...config,
3856
+ xAxis: {
3857
+ ...config.xAxis,
3858
+ anchors: copiedAnchors
3859
+ }
3860
+ })
3861
+ }}
3862
+ options={[
3863
+ { value: '', label: 'Select' },
3864
+ ...lineOptions.map(line => ({ value: line.value, label: line.value }))
3865
+ ]}
3866
+ />
3549
3867
  </AccordionItemPanel>
3550
3868
  </AccordionItem>
3551
3869
  ))}
@@ -3653,28 +3971,25 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
3653
3971
  />
3654
3972
  </label>
3655
3973
 
3656
- <label>
3657
- Anchor Line Style
3658
- <select
3659
- value={config.yAxis.anchors[index].lineStyle || ''}
3660
- onChange={e => {
3661
- const copiedAnchors = [...config.yAxis.anchors]
3662
- copiedAnchors[index].lineStyle = e.target.value
3663
- updateConfig({
3664
- ...config,
3665
- yAxis: {
3666
- ...config.yAxis,
3667
- anchors: copiedAnchors
3668
- }
3669
- })
3670
- }}
3671
- >
3672
- <option>Select</option>
3673
- {lineOptions.map(line => (
3674
- <option key={line.key}>{line.value}</option>
3675
- ))}
3676
- </select>
3677
- </label>
3974
+ <Select
3975
+ value={config.yAxis.anchors[index].lineStyle || ''}
3976
+ label='Anchor Line Style'
3977
+ onChange={e => {
3978
+ const copiedAnchors = [...config.yAxis.anchors]
3979
+ copiedAnchors[index].lineStyle = e.target.value
3980
+ updateConfig({
3981
+ ...config,
3982
+ yAxis: {
3983
+ ...config.yAxis,
3984
+ anchors: copiedAnchors
3985
+ }
3986
+ })
3987
+ }}
3988
+ options={[
3989
+ { value: '', label: 'Select' },
3990
+ ...lineOptions.map(line => ({ value: line.value, label: line.value }))
3991
+ ]}
3992
+ />
3678
3993
  </AccordionItemPanel>
3679
3994
  </AccordionItem>
3680
3995
  ))}
@@ -4126,6 +4441,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4126
4441
  </>
4127
4442
  )}
4128
4443
  <Panels.Visual name='Visual' />
4444
+ <Panels.PatternSettings name='PatternSettings' />
4129
4445
  {/* Spark Line has no data table */}
4130
4446
  {config.visualizationType !== 'Spark Line' && (
4131
4447
  <AccordionItem>
@@ -4145,11 +4461,29 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4145
4461
  )}
4146
4462
  <Panels.Annotate name='Text Annotations' />
4147
4463
  {/* {(config.visualizationType === 'Bar' || config.visualizationType === 'Line') && <Panels.DateHighlighting name='Date Highlighting' />} */}
4464
+ <PanelMarkup
4465
+ name='Markup Variables'
4466
+ markupVariables={config.markupVariables || []}
4467
+ data={rawData}
4468
+ enableMarkupVariables={config.enableMarkupVariables || false}
4469
+ onMarkupVariablesChange={variables => updateField(null, null, 'markupVariables', variables)}
4470
+ onToggleEnable={enabled => updateField(null, null, 'enableMarkupVariables', enabled)}
4471
+ />
4472
+ <Panels.SmallMultiples name='Small Multiples' />
4148
4473
  </Accordion>
4149
4474
  {config.type !== 'Spark Line' && (
4150
4475
  <AdvancedEditor loadConfig={updateConfig} config={config} convertStateToConfig={convertStateToConfig} />
4151
4476
  )}
4152
4477
  </Layout.Sidebar>
4478
+
4479
+ {showConversionModal && (
4480
+ <PaletteConversionModal
4481
+ onConfirm={handleConversionConfirm}
4482
+ onCancel={handleConversionCancel}
4483
+ onReturnToV1={handleReturnToV1}
4484
+ paletteName={pendingPaletteSelection?.palette}
4485
+ />
4486
+ )}
4153
4487
  </ErrorBoundary>
4154
4488
  </EditorPanelContext.Provider>
4155
4489
  )