@cdc/chart 4.25.7 → 4.25.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 (95) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/dist/cdcchart.js +39551 -37016
  3. package/examples/feature/__data__/planet-example-data.json +0 -30
  4. package/examples/grouped-bar-test.json +400 -0
  5. package/examples/private/d.json +382 -0
  6. package/examples/private/example-2.json +49784 -0
  7. package/examples/private/f2.json +1 -0
  8. package/examples/private/f4.json +1577 -0
  9. package/examples/private/forecast.json +1180 -0
  10. package/examples/private/lollipop.json +468 -0
  11. package/examples/private/new.json +48756 -0
  12. package/examples/private/pie-chart-legend.json +904 -0
  13. package/examples/suppressed_tooltip.json +480 -0
  14. package/index.html +10 -22
  15. package/package.json +25 -7
  16. package/src/CdcChart.tsx +10 -4
  17. package/src/CdcChartComponent.tsx +188 -32
  18. package/src/_stories/Chart.Anchors.stories.tsx +2 -2
  19. package/src/_stories/Chart.BoxPlot.stories.tsx +1 -1
  20. package/src/_stories/Chart.CI.stories.tsx +1 -1
  21. package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
  22. package/src/_stories/Chart.DynamicSeries.stories.tsx +2 -2
  23. package/src/_stories/Chart.Filters.stories.tsx +2 -2
  24. package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
  25. package/src/_stories/Chart.Patterns.stories.tsx +19 -0
  26. package/src/_stories/Chart.ScatterPlot.stories.tsx +1 -1
  27. package/src/_stories/Chart.stories.tsx +8 -5
  28. package/src/_stories/Chart.tooltip.stories.tsx +1 -1
  29. package/src/_stories/ChartAnnotation.stories.tsx +1 -1
  30. package/src/_stories/ChartAxisLabels.stories.tsx +2 -2
  31. package/src/_stories/ChartAxisTitles.stories.tsx +2 -2
  32. package/src/_stories/ChartEditor.stories.tsx +60 -60
  33. package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
  34. package/src/_stories/ChartLine.Symbols.stories.tsx +1 -1
  35. package/src/_stories/ChartPrefixSuffix.stories.tsx +2 -2
  36. package/src/_stories/_mock/stacked-pattern-test.json +520 -0
  37. package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -0
  38. package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
  39. package/src/components/BarChart/components/BarChart.Horizontal.tsx +170 -25
  40. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +139 -6
  41. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +215 -73
  42. package/src/components/BarChart/components/BarChart.Vertical.tsx +172 -23
  43. package/src/components/BarChart/helpers/index.ts +43 -4
  44. package/src/components/BarChart/helpers/lollipopColors.ts +27 -0
  45. package/src/components/BarChart/helpers/useBarChart.ts +25 -3
  46. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +2 -1
  47. package/src/components/Brush/BrushChart.tsx +65 -10
  48. package/src/components/Brush/BrushController.tsx +37 -5
  49. package/src/components/Brush/types.tsx +8 -0
  50. package/src/components/DeviationBar.jsx +9 -6
  51. package/src/components/EditorPanel/EditorPanel.tsx +364 -39
  52. package/src/components/EditorPanel/EditorPanelContext.ts +3 -0
  53. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +2 -2
  54. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +414 -0
  55. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +30 -54
  56. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +115 -120
  57. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  58. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +0 -8
  59. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +49 -48
  60. package/src/components/Forecasting/Forecasting.tsx +36 -6
  61. package/src/components/ForestPlot/ForestPlot.tsx +11 -7
  62. package/src/components/ForestPlot/ForestPlotProps.ts +1 -1
  63. package/src/components/Legend/Legend.Component.tsx +110 -2
  64. package/src/components/Legend/Legend.tsx +3 -1
  65. package/src/components/Legend/helpers/createFormatLabels.tsx +230 -171
  66. package/src/components/LegendWrapper.tsx +1 -1
  67. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +27 -26
  68. package/src/components/LineChart/components/LineChart.Circle.tsx +2 -2
  69. package/src/components/LineChart/index.tsx +2 -2
  70. package/src/components/LinearChart.tsx +26 -9
  71. package/src/components/PairedBarChart.jsx +6 -4
  72. package/src/components/PieChart/PieChart.tsx +170 -54
  73. package/src/components/Sankey/components/Sankey.tsx +7 -1
  74. package/src/components/ScatterPlot/ScatterPlot.jsx +32 -4
  75. package/src/data/initial-state.js +315 -292
  76. package/src/helpers/buildForecastPaletteMappings.ts +112 -0
  77. package/src/helpers/buildForecastPaletteOptions.ts +109 -0
  78. package/src/helpers/getColorScale.ts +72 -8
  79. package/src/helpers/getNewRuntime.ts +1 -1
  80. package/src/helpers/getTransformedData.ts +1 -1
  81. package/src/hooks/useChartHoverAnalytics.tsx +44 -0
  82. package/src/hooks/useReduceData.ts +105 -70
  83. package/src/hooks/useTooltip.tsx +58 -16
  84. package/src/index.jsx +6 -3
  85. package/src/scss/main.scss +12 -0
  86. package/src/store/chart.reducer.ts +1 -1
  87. package/src/test/CdcChart.test.jsx +8 -3
  88. package/src/types/ChartConfig.ts +30 -6
  89. package/src/types/ChartContext.ts +1 -0
  90. package/vite.config.js +1 -1
  91. package/vitest.config.ts +16 -0
  92. package/src/coreStyles_chart.scss +0 -3
  93. package/src/helpers/configHelpers.ts +0 -28
  94. package/src/helpers/generateColorsArray.ts +0 -8
  95. package/src/hooks/useColorPalette.js +0 -76
@@ -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'
@@ -45,9 +47,13 @@ import EditorPanelContext from './EditorPanelContext'
45
47
  import _ from 'lodash'
46
48
  import { adjustedSymbols as symbolCodes } from '@cdc/core/helpers/footnoteSymbols'
47
49
  import { updateFieldRankByValue } from './helpers/updateFieldRankByValue'
50
+ import cloneConfig from '@cdc/core/helpers/cloneConfig'
48
51
  import FootnotesEditor from '@cdc/core/components/EditorPanel/FootnotesEditor'
49
52
  import { Datasets } from '@cdc/core/types/DataSet'
50
53
  import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
54
+ import { paletteMigrationMap, twoColorPaletteMigrationMap } from '@cdc/core/helpers/palettes/migratePaletteName'
55
+ import { isV1Palette, migratePaletteWithMap } from '@cdc/core/helpers/palettes/utils'
56
+ import { USE_V2_MIGRATION } from '@cdc/core/helpers/constants'
51
57
 
52
58
  interface PreliminaryProps {
53
59
  config: ChartConfig
@@ -825,6 +831,14 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
825
831
 
826
832
  const [displayPanel, setDisplayPanel] = useState(true)
827
833
  const [displayViewportOverrides, setDisplayViewportOverrides] = useState(false)
834
+ const [showConversionModal, setShowConversionModal] = useState(false)
835
+ const [pendingPaletteSelection, setPendingPaletteSelection] = useState<{
836
+ palette: string
837
+ action: () => void
838
+ seriesIndex?: number
839
+ stageIndex?: number
840
+ type?: 'general' | 'twoColor' | 'forecast'
841
+ } | null>(null)
828
842
 
829
843
  const setLollipopShape = shape => {
830
844
  updateConfig({
@@ -892,8 +906,36 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
892
906
 
893
907
  const getColumns = (filter = true) => {
894
908
  let columns = {}
895
- unfilteredData.forEach(row => {
896
- Object.keys(row).forEach(columnName => (columns[columnName] = true))
909
+
910
+ // Try multiple data sources in order of preference
911
+ let dataToUse = []
912
+
913
+ if (unfilteredData && unfilteredData.length > 0) {
914
+ // First preference: unfilteredData from context
915
+ dataToUse = unfilteredData
916
+ } else if (isDashboard && datasets && config.dataKey && datasets[config.dataKey]?.data?.length > 0) {
917
+ // Second preference: data from datasets in dashboard mode
918
+ dataToUse = datasets[config.dataKey].data
919
+ } else if (rawData && rawData.length > 0) {
920
+ // Third preference: rawData from context
921
+ dataToUse = rawData
922
+ } else if (data && data.length > 0) {
923
+ // Fourth preference: transformedData from context
924
+ dataToUse = data
925
+ } else if (config.data && config.data.length > 0) {
926
+ // Fifth preference: data directly from config
927
+ dataToUse = config.data
928
+ }
929
+
930
+ // If we still don't have data, return empty array
931
+ if (!dataToUse || dataToUse.length === 0) {
932
+ return []
933
+ }
934
+
935
+ dataToUse.forEach(row => {
936
+ if (row && typeof row === 'object') {
937
+ Object.keys(row).forEach(columnName => (columns[columnName] = true))
938
+ }
897
939
  })
898
940
 
899
941
  if (filter) {
@@ -975,18 +1017,18 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
975
1017
 
976
1018
  // prettier-ignore
977
1019
  const {
978
- highlightedBarValues,
979
- highlightedSeriesValues,
980
- handleUpdateHighlightedBar,
981
- handleAddNewHighlightedBar,
982
- handleRemoveHighlightedBar,
983
- handleUpdateHighlightedBarColor,
984
- handleHighlightedBarLegendLabel,
985
- handleUpdateHighlightedBorderWidth
986
- } = useHighlightedBars(config, updateConfig)
1020
+ highlightedBarValues,
1021
+ highlightedSeriesValues,
1022
+ handleUpdateHighlightedBar,
1023
+ handleAddNewHighlightedBar,
1024
+ handleRemoveHighlightedBar,
1025
+ handleUpdateHighlightedBarColor,
1026
+ handleHighlightedBarLegendLabel,
1027
+ handleUpdateHighlightedBorderWidth
1028
+ } = useHighlightedBars(config, updateConfig)
987
1029
 
988
1030
  const convertStateToConfig = () => {
989
- let strippedState = _.cloneDeep(config)
1031
+ let strippedState = cloneConfig(config)
990
1032
  if (false === missingRequiredSections(config)) {
991
1033
  delete strippedState.newViz
992
1034
  }
@@ -1093,6 +1135,289 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1093
1135
  const section = config.orientation === 'horizontal' ? 'xAxis' : 'yAxis'
1094
1136
  const [warningMsg, setWarningMsg] = useState({ maxMsg: '', minMsg: '', rightMaxMessage: '', minMsgRight: '' })
1095
1137
 
1138
+ // Palette migration functions
1139
+ const handlePaletteSelection = (palette: string) => {
1140
+ try {
1141
+ // Check if config exists and has basic structure
1142
+ if (!config) {
1143
+ console.error('COVE: Config is undefined in handlePaletteSelection')
1144
+ return
1145
+ }
1146
+
1147
+ // Check if it's a v1 palette configuration
1148
+ const isV1PaletteConfig = isV1Palette(config)
1149
+
1150
+ const executeSelection = () => {
1151
+ const _newConfig = cloneConfig(config)
1152
+ if (!_newConfig.general.palette) {
1153
+ _newConfig.general.palette = {}
1154
+ }
1155
+
1156
+ // If v2 migration is disabled, use the original palette name and keep v1 version
1157
+ if (!USE_V2_MIGRATION) {
1158
+ _newConfig.general.palette.name = palette
1159
+ _newConfig.general.palette.version = '1.0'
1160
+ } else {
1161
+ // V2 migration logic
1162
+ const migratedName = palette ? migratePaletteWithMap(palette, paletteMigrationMap, false) : undefined
1163
+ _newConfig.general.palette.name = migratedName
1164
+ if (isV1PaletteConfig) {
1165
+ _newConfig.general.palette.version = '2.0'
1166
+ }
1167
+ }
1168
+ updateConfig(_newConfig)
1169
+ }
1170
+
1171
+ if (isV1PaletteConfig) {
1172
+ setPendingPaletteSelection({ palette, action: executeSelection, type: 'general' })
1173
+ setShowConversionModal(true)
1174
+ } else {
1175
+ executeSelection()
1176
+ }
1177
+ } catch (error) {
1178
+ console.error('COVE: Error in handlePaletteSelection:', error)
1179
+ }
1180
+ }
1181
+
1182
+ // Two-color palette migration function
1183
+ const handleTwoColorPaletteSelection = (palette: string) => {
1184
+ try {
1185
+ // Check if config exists and has basic structure
1186
+ if (!config) {
1187
+ console.error('COVE: Config is undefined in handleTwoColorPaletteSelection')
1188
+ return
1189
+ }
1190
+
1191
+ // Check if it's a v1 palette configuration
1192
+ const isV1PaletteConfig = isV1Palette(config)
1193
+
1194
+ const executeSelection = () => {
1195
+ const _newConfig = cloneConfig(config)
1196
+ if (!_newConfig.twoColor) {
1197
+ _newConfig.twoColor = { palette: '', isPaletteReversed: false }
1198
+ }
1199
+
1200
+ // If v2 migration is disabled, use the original palette name and keep v1 version
1201
+ if (!USE_V2_MIGRATION) {
1202
+ _newConfig.twoColor.palette = palette
1203
+ if (!_newConfig.general) {
1204
+ _newConfig.general = {}
1205
+ }
1206
+ if (!_newConfig.general.palette) {
1207
+ _newConfig.general.palette = {}
1208
+ }
1209
+ _newConfig.general.palette.version = '1.0'
1210
+ } else {
1211
+ // V2 migration logic
1212
+ const migratedPaletteName = isV1PaletteConfig
1213
+ ? migratePaletteWithMap(palette, twoColorPaletteMigrationMap, false)
1214
+ : palette
1215
+
1216
+ _newConfig.twoColor.palette = migratedPaletteName
1217
+
1218
+ if (isV1PaletteConfig) {
1219
+ if (!_newConfig.general) {
1220
+ _newConfig.general = {}
1221
+ }
1222
+ if (!_newConfig.general.palette) {
1223
+ _newConfig.general.palette = {}
1224
+ }
1225
+ _newConfig.general.palette.version = '2.0'
1226
+
1227
+ // Create backup for rollback functionality (consistent with standard format)
1228
+ if (!_newConfig.general.palette.backups) {
1229
+ _newConfig.general.palette.backups = []
1230
+ }
1231
+ _newConfig.general.palette.backups.push({
1232
+ name: config.twoColor?.palette || palette,
1233
+ version: '1.0',
1234
+ isReversed: false,
1235
+ type: 'twoColor'
1236
+ })
1237
+ }
1238
+ }
1239
+ updateConfig(_newConfig)
1240
+ }
1241
+
1242
+ if (isV1PaletteConfig) {
1243
+ setPendingPaletteSelection({ palette, action: executeSelection, type: 'twoColor' })
1244
+ setShowConversionModal(true)
1245
+ } else {
1246
+ executeSelection()
1247
+ }
1248
+ } catch (error) {
1249
+ console.error('COVE: Error in handleTwoColorPaletteSelection:', error)
1250
+ }
1251
+ }
1252
+
1253
+ // Forecast palette selection - includes v1/v2 migration modal logic
1254
+ const handleForecastPaletteSelection = (palette: string, seriesIndex: number, stageIndex: number) => {
1255
+ try {
1256
+ if (!config) {
1257
+ console.error('COVE: Config is undefined in handleForecastPaletteSelection')
1258
+ return
1259
+ }
1260
+
1261
+ // Check if it's a v1 palette configuration
1262
+ const isV1PaletteConfig = isV1Palette(config)
1263
+
1264
+ const executeSelection = () => {
1265
+ const copyOfSeries = [...config.series]
1266
+ const copyOfStages = [...(copyOfSeries[seriesIndex].stages || [])]
1267
+ copyOfStages[stageIndex] = { ...copyOfStages[stageIndex], color: palette }
1268
+ copyOfSeries[seriesIndex] = { ...copyOfSeries[seriesIndex], stages: copyOfStages }
1269
+
1270
+ const _newConfig = cloneConfig(config)
1271
+ _newConfig.series = copyOfSeries
1272
+
1273
+ // If this is the first v2 palette selection, upgrade to v2
1274
+ if (isV1PaletteConfig && USE_V2_MIGRATION) {
1275
+ if (!_newConfig.general) {
1276
+ _newConfig.general = {}
1277
+ }
1278
+ if (!_newConfig.general.palette) {
1279
+ _newConfig.general.palette = {}
1280
+ }
1281
+ _newConfig.general.palette.version = '2.0'
1282
+
1283
+ // Forecast-specific migration map for v1 → v2 palette names (all lowercase-hyphen format)
1284
+ const forecastPaletteMigrationMap: Record<string, string> = {
1285
+ // Sequential Blue variants → sequential-blue
1286
+ 'sequential-blue': 'sequential-blue',
1287
+ 'sequential-blue-two': 'sequential-blue',
1288
+ 'sequential-blue-three': 'sequential-blue',
1289
+ 'sequential-blue-2-(mpx)': 'sequential-blue',
1290
+ 'sequential-blue-2-mpx': 'sequential-blue',
1291
+ // Sequential Orange variants → sequential-orange
1292
+ 'sequential-orange': 'sequential-orange',
1293
+ 'sequential-orange-two': 'sequential-orange',
1294
+ 'sequential-orange-(mpx)': 'sequential-orange',
1295
+ 'sequential-orange-mpx': 'sequential-orange',
1296
+ // Other sequential palettes (no variants, just normalize)
1297
+ 'sequential-green': 'sequential-green',
1298
+ 'sequential-purple': 'sequential-purple',
1299
+ 'sequential-teal': 'sequential-teal',
1300
+ // Reverse variants - Sequential Blue
1301
+ 'sequential-bluereverse': 'sequential-bluereverse',
1302
+ 'sequential-blue-reverse': 'sequential-bluereverse',
1303
+ 'sequential-blue-tworeverse': 'sequential-bluereverse',
1304
+ 'sequential-blue-two-reverse': 'sequential-bluereverse',
1305
+ 'sequential-blue-threereverse': 'sequential-bluereverse',
1306
+ 'sequential-blue-three-reverse': 'sequential-bluereverse',
1307
+ 'sequential-blue-2-(mpx)reverse': 'sequential-bluereverse',
1308
+ 'sequential-blue-2-(mpx)-reverse': 'sequential-bluereverse',
1309
+ 'sequential-blue-2-mpxreverse': 'sequential-bluereverse',
1310
+ 'sequential-blue-2-mpx-reverse': 'sequential-bluereverse',
1311
+ // Reverse variants - Sequential Orange
1312
+ 'sequential-orangereverse': 'sequential-orangereverse',
1313
+ 'sequential-orange-reverse': 'sequential-orangereverse',
1314
+ 'sequential-orange-tworeverse': 'sequential-orangereverse',
1315
+ 'sequential-orange-two-reverse': 'sequential-orangereverse',
1316
+ 'sequential-orange-(mpx)reverse': 'sequential-orangereverse',
1317
+ 'sequential-orange-(mpx)-reverse': 'sequential-orangereverse',
1318
+ 'sequential-orange-mpxreverse': 'sequential-orangereverse',
1319
+ 'sequential-orange-mpx-reverse': 'sequential-orangereverse',
1320
+ // Reverse variants - Other sequential palettes
1321
+ 'sequential-greenreverse': 'sequential-greenreverse',
1322
+ 'sequential-green-reverse': 'sequential-greenreverse',
1323
+ 'sequential-purplereverse': 'sequential-purplereverse',
1324
+ 'sequential-purple-reverse': 'sequential-purplereverse',
1325
+ 'sequential-tealreverse': 'sequential-tealreverse',
1326
+ 'sequential-teal-reverse': 'sequential-tealreverse'
1327
+ }
1328
+
1329
+ // Migrate and normalize all forecast stage colors to v2 format
1330
+ _newConfig.series.forEach((series: any) => {
1331
+ if (series.type === 'Forecasting' && series.stages) {
1332
+ series.stages.forEach((stage: any) => {
1333
+ if (stage.color) {
1334
+ // First, try to migrate using the map
1335
+ const migrated = forecastPaletteMigrationMap[stage.color] || stage.color
1336
+ // Then normalize to lowercase with hyphens
1337
+ stage.color = migrated.toLowerCase().replace(/ /g, '-').replace(/_/g, '-')
1338
+ }
1339
+ })
1340
+ }
1341
+ })
1342
+ }
1343
+
1344
+ updateConfig(_newConfig)
1345
+ }
1346
+
1347
+ if (isV1PaletteConfig) {
1348
+ setPendingPaletteSelection({ palette, action: executeSelection, type: 'forecast', seriesIndex, stageIndex })
1349
+ setShowConversionModal(true)
1350
+ } else {
1351
+ executeSelection()
1352
+ }
1353
+ } catch (error) {
1354
+ console.error('COVE: Error in handleForecastPaletteSelection:', error)
1355
+ }
1356
+ }
1357
+
1358
+ // Modal handlers
1359
+ const handleConversionConfirm = () => {
1360
+ if (pendingPaletteSelection) {
1361
+ pendingPaletteSelection.action()
1362
+ }
1363
+ setShowConversionModal(false)
1364
+ setPendingPaletteSelection(null)
1365
+ }
1366
+
1367
+ const handleConversionCancel = () => {
1368
+ // Don't update config - just close modal and discard pending selection
1369
+ setShowConversionModal(false)
1370
+ setPendingPaletteSelection(null)
1371
+ }
1372
+
1373
+ const handleReturnToV1 = () => {
1374
+ if (pendingPaletteSelection) {
1375
+ const _newConfig = cloneConfig(config)
1376
+ const { palette, type } = pendingPaletteSelection
1377
+
1378
+ // Handle based on palette type
1379
+ if (type === 'forecast') {
1380
+ // Forecast palette selection
1381
+ const { seriesIndex, stageIndex } = pendingPaletteSelection
1382
+ if (seriesIndex !== undefined && stageIndex !== undefined) {
1383
+ const copyOfSeries = [..._newConfig.series]
1384
+ const copyOfStages = [...copyOfSeries[seriesIndex].stages]
1385
+ copyOfStages[stageIndex] = { ...copyOfStages[stageIndex], color: palette }
1386
+ copyOfSeries[seriesIndex] = { ...copyOfSeries[seriesIndex], stages: copyOfStages }
1387
+ _newConfig.series = copyOfSeries
1388
+ }
1389
+ } else if (type === 'twoColor') {
1390
+ // Two-color palette selection
1391
+ if (!_newConfig.twoColor) {
1392
+ _newConfig.twoColor = { palette: '', isPaletteReversed: false }
1393
+ }
1394
+ _newConfig.twoColor.palette = palette
1395
+ } else {
1396
+ // General palette selection (type === 'general' or undefined for backwards compatibility)
1397
+ if (!_newConfig.general) {
1398
+ _newConfig.general = {}
1399
+ }
1400
+ if (!_newConfig.general.palette) {
1401
+ _newConfig.general.palette = {}
1402
+ }
1403
+ _newConfig.general.palette.name = palette
1404
+ }
1405
+
1406
+ // Set version to V1
1407
+ if (!_newConfig.general) {
1408
+ _newConfig.general = {}
1409
+ }
1410
+ if (!_newConfig.general.palette) {
1411
+ _newConfig.general.palette = {}
1412
+ }
1413
+ _newConfig.general.palette.version = '1.0'
1414
+
1415
+ updateConfig(_newConfig)
1416
+ }
1417
+ setShowConversionModal(false)
1418
+ setPendingPaletteSelection(null)
1419
+ }
1420
+
1096
1421
  const validateMaxValue = () => {
1097
1422
  const enteredValue = config[section].max
1098
1423
  const enteredRightMax = config[section].rightMax
@@ -1232,18 +1557,6 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1232
1557
  )
1233
1558
  }
1234
1559
  })
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
1560
  }
1248
1561
 
1249
1562
  // for pie charts
@@ -1261,18 +1574,6 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1261
1574
  )
1262
1575
  }
1263
1576
  })
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
1577
  }
1277
1578
 
1278
1579
  const removeAdditionalColumn = columnName => {
@@ -1379,11 +1680,14 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1379
1680
  handleHighlightedBarLegendLabel,
1380
1681
  handleUpdateHighlightedBar,
1381
1682
  handleRemoveHighlightedBar,
1382
- isPaletteReversed: config.isPaletteReversed,
1683
+ isPaletteReversed: config.general?.palette?.isReversed,
1383
1684
  highlightedSeriesValues,
1384
1685
  handleUpdateHighlightedBorderWidth,
1385
1686
  handleUpdateHighlightedBarColor,
1386
- setLollipopShape
1687
+ setLollipopShape,
1688
+ handlePaletteSelection,
1689
+ handleTwoColorPaletteSelection,
1690
+ handleForecastPaletteSelection
1387
1691
  }
1388
1692
  if (isLoading) {
1389
1693
  return <></>
@@ -1478,7 +1782,10 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1478
1782
  options={getColumns()}
1479
1783
  />
1480
1784
  {config.series && config.series.length !== 0 && (
1481
- <Panels.Series.Wrapper getColumns={getColumns}>
1785
+ <Panels.Series.Wrapper
1786
+ getColumns={getColumns}
1787
+ handleForecastPaletteSelection={handleForecastPaletteSelection}
1788
+ >
1482
1789
  <fieldset>
1483
1790
  <legend className='edit-label float-left'>Displaying</legend>
1484
1791
  <Tooltip style={{ textTransform: 'none' }}>
@@ -4126,6 +4433,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4126
4433
  </>
4127
4434
  )}
4128
4435
  <Panels.Visual name='Visual' />
4436
+ <Panels.PatternSettings name='PatternSettings' />
4129
4437
  {/* Spark Line has no data table */}
4130
4438
  {config.visualizationType !== 'Spark Line' && (
4131
4439
  <AccordionItem>
@@ -4145,11 +4453,28 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
4145
4453
  )}
4146
4454
  <Panels.Annotate name='Text Annotations' />
4147
4455
  {/* {(config.visualizationType === 'Bar' || config.visualizationType === 'Line') && <Panels.DateHighlighting name='Date Highlighting' />} */}
4456
+ <PanelMarkup
4457
+ name='Markup Variables'
4458
+ markupVariables={config.markupVariables || []}
4459
+ data={rawData}
4460
+ enableMarkupVariables={config.enableMarkupVariables || false}
4461
+ onMarkupVariablesChange={variables => updateField(null, null, 'markupVariables', variables)}
4462
+ onToggleEnable={enabled => updateField(null, null, 'enableMarkupVariables', enabled)}
4463
+ />
4148
4464
  </Accordion>
4149
4465
  {config.type !== 'Spark Line' && (
4150
4466
  <AdvancedEditor loadConfig={updateConfig} config={config} convertStateToConfig={convertStateToConfig} />
4151
4467
  )}
4152
4468
  </Layout.Sidebar>
4469
+
4470
+ {showConversionModal && (
4471
+ <PaletteConversionModal
4472
+ onConfirm={handleConversionConfirm}
4473
+ onCancel={handleConversionCancel}
4474
+ onReturnToV1={handleReturnToV1}
4475
+ paletteName={pendingPaletteSelection?.palette}
4476
+ />
4477
+ )}
4153
4478
  </ErrorBoundary>
4154
4479
  </EditorPanelContext.Provider>
4155
4480
  )
@@ -23,6 +23,9 @@ export type EditorPanelContext = {
23
23
  isPaletteReversed?: boolean
24
24
  handleRemoveHighlightedBar?: Function
25
25
  setLollipopShape?: Function
26
+ handlePaletteSelection?: (palette: string) => void
27
+ handleTwoColorPaletteSelection?: (palette: string) => void
28
+ handleForecastPaletteSelection?: (palette: string, seriesIndex: number, stageIndex: number) => void
26
29
  }
27
30
 
28
31
  const EditorPanelContext = createContext<EditorPanelContext>(null)
@@ -67,8 +67,8 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
67
67
  config.xAxis.type === 'date'
68
68
  ? new Date(config?.data?.[0]?.[config.xAxis.dataKey]).getTime()
69
69
  : config.xAxis.type === 'categorical'
70
- ? '1/15/2016'
71
- : '',
70
+ ? '1/15/2016'
71
+ : '',
72
72
  yKey: '',
73
73
  dx: 20,
74
74
  dy: -20,