@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,4 +1,6 @@
1
- import { useContext, FC } from 'react'
1
+ import { useContext, FC, useMemo } from 'react'
2
+ import _ from 'lodash'
3
+ import cloneConfig from '@cdc/core/helpers/cloneConfig'
2
4
 
3
5
  // external libraries
4
6
  import {
@@ -15,7 +17,10 @@ import Icon from '@cdc/core/components/ui/Icon'
15
17
  import InputToggle from '@cdc/core/components/inputs/InputToggle'
16
18
 
17
19
  // contexts
18
- import { useColorPalette } from '../../../../hooks/useColorPalette'
20
+ import { useColorPalette } from '@cdc/core/hooks/useColorPalette'
21
+ import { getCurrentPaletteName } from '@cdc/core/helpers/palettes/utils'
22
+ import { getColorPaletteVersion } from '@cdc/core/helpers/getColorPaletteVersion'
23
+ import { isCoveDeveloperMode } from '@cdc/core/helpers/queryStringUtils'
19
24
  import { ChartContext } from './../../../../types/ChartContext.js'
20
25
 
21
26
  import { useEditorPermissions } from '../../useEditorPermissions.js'
@@ -23,17 +28,23 @@ import { useEditorPanelContext } from '../../EditorPanelContext.js'
23
28
  import ConfigContext from '../../../../ConfigContext.js'
24
29
  import { PanelProps } from '../PanelProps'
25
30
  import { LineChartConfig } from '../../../../types/ChartConfig'
31
+ import { PaletteSelector, DeveloperPaletteRollback } from '@cdc/core/components/PaletteSelector'
32
+ import { HeaderThemeSelector } from '@cdc/core/components/HeaderThemeSelector'
33
+ import { CustomColorsEditor } from '@cdc/core/components/CustomColorsEditor'
34
+ import { getColorScale } from '../../../../helpers/getColorScale'
35
+ import '@cdc/core/styles/v2/components/editor.scss'
26
36
  import './panelVisual.styles.css'
27
37
 
28
38
  const PanelVisual: FC<PanelProps> = props => {
29
39
  const { config, updateConfig, colorPalettes, twoColorPalette } = useContext<ChartContext>(ConfigContext)
30
40
  const { visual } = config
31
- const { setLollipopShape, updateField } = useEditorPanelContext()
41
+
42
+ const { setLollipopShape, updateField, handlePaletteSelection, handleTwoColorPaletteSelection } =
43
+ useEditorPanelContext()
32
44
  const {
33
45
  visHasBarBorders,
34
46
  visCanAnimate,
35
47
  visSupportsNonSequentialPallete,
36
- headerColors,
37
48
  visSupportsTooltipOpacity,
38
49
  visSupportsTooltipLines,
39
50
  visSupportsBarSpace,
@@ -45,6 +56,14 @@ const PanelVisual: FC<PanelProps> = props => {
45
56
  } = useEditorPermissions()
46
57
  const { twoColorPalettes, sequential, nonSequential, accessibleColors } = useColorPalette(config, updateConfig)
47
58
 
59
+ const currentPaletteName = getCurrentPaletteName(config)
60
+
61
+ const versionedTwoColorPalette = useMemo(() => {
62
+ const version = getColorPaletteVersion(config)
63
+ const versionKey = `v${version}`
64
+ return twoColorPalette[versionKey] || twoColorPalette.v2
65
+ }, [config, twoColorPalette])
66
+
48
67
  const updateColor = (property, _value) => {
49
68
  console.error('value', _value)
50
69
  if (property === 'storyNodeFontColor') {
@@ -71,7 +90,7 @@ const PanelVisual: FC<PanelProps> = props => {
71
90
  }
72
91
 
73
92
  return (
74
- <AccordionItem>
93
+ <AccordionItem className='panel-visual'>
75
94
  <AccordionItemHeading>
76
95
  <AccordionItemButton>Visual</AccordionItemButton>
77
96
  </AccordionItemHeading>
@@ -232,140 +251,152 @@ const PanelVisual: FC<PanelProps> = props => {
232
251
  />
233
252
  </>
234
253
  )}
235
- {/* eslint-disable */}
236
- <label className='header'>
237
- <span className='edit-label'>Header Theme</span>
238
- <ul className='color-palette'>
239
- {headerColors.map(palette => (
240
- <button
241
- title={palette}
242
- key={palette}
243
- onClick={e => {
244
- e.preventDefault()
245
- updateConfig({ ...config, theme: palette })
246
- }}
247
- className={config.theme === palette ? 'selected ' + palette : palette}
248
- ></button>
249
- ))}
250
- </ul>
251
- </label>
252
- {/* eslint-enable */}
254
+ <HeaderThemeSelector selectedTheme={config.theme} onThemeSelect={theme => updateConfig({ ...config, theme })} />
253
255
  {(visSupportsNonSequentialPallete() || visSupportsNonSequentialPallete()) && (
254
256
  <>
255
257
  <label>
256
258
  <span className='edit-label'>Chart Color Palette</span>
257
259
  </label>
260
+ <div className='mb-2'>
261
+ <small className='text-muted'>
262
+ Review color contrasts{' '}
263
+ <a href='https://webaim.org/resources/contrastchecker/' target='_blank' rel='noopener noreferrer'>
264
+ here
265
+ </a>
266
+ </small>
267
+ </div>
268
+ <DeveloperPaletteRollback config={config} updateConfig={updateConfig} />
258
269
  {visSupportsReverseColorPalette() && (
259
270
  <InputToggle
260
- fieldName='isPaletteReversed'
271
+ section='general'
272
+ subsection='palette'
273
+ fieldName='isReversed'
261
274
  size='small'
262
275
  label='Use selected palette in reverse order'
263
276
  updateField={updateField}
264
- value={config.isPaletteReversed}
277
+ onClick={() => {
278
+ const _state = cloneConfig(config)
279
+ const currentPaletteName = getCurrentPaletteName(config)
280
+ _state.general.palette.isReversed = !_state.general.palette.isReversed
281
+ let paletteName = ''
282
+ if (_state.general.palette.isReversed && !currentPaletteName.endsWith('reverse')) {
283
+ paletteName = currentPaletteName + 'reverse'
284
+ }
285
+ if (!_state.general.palette.isReversed && currentPaletteName.endsWith('reverse')) {
286
+ paletteName = currentPaletteName.slice(0, -7)
287
+ }
288
+ if (paletteName) {
289
+ _state.general.palette.name = paletteName
290
+ }
291
+ updateConfig(_state)
292
+ }}
293
+ value={config.general?.palette?.isReversed}
265
294
  />
266
295
  )}
267
296
  {visSupportsSequentialPallete() && (
268
297
  <>
269
298
  <span>Sequential</span>
270
- <ul className='color-palette'>
271
- {sequential.map(palette => {
272
- const colorOne = {
273
- backgroundColor: colorPalettes[palette][2]
274
- }
275
-
276
- const colorTwo = {
277
- backgroundColor: colorPalettes[palette][3]
278
- }
279
-
280
- const colorThree = {
281
- backgroundColor: colorPalettes[palette][5]
282
- }
283
-
284
- return (
285
- <button
286
- title={palette}
287
- key={palette}
288
- onClick={e => {
289
- e.preventDefault()
290
- updateConfig({ ...config, palette })
291
- }}
292
- className={config.palette === palette ? 'selected' : ''}
293
- >
294
- <span style={colorOne}></span>
295
- <span style={colorTwo}></span>
296
- <span style={colorThree}></span>
297
- </button>
298
- )
299
- })}
300
- </ul>
299
+ <PaletteSelector
300
+ palettes={sequential}
301
+ colorPalettes={colorPalettes}
302
+ config={config}
303
+ onPaletteSelect={handlePaletteSelection}
304
+ selectedPalette={currentPaletteName}
305
+ colorIndices={[2, 3, 5]}
306
+ className='color-palette'
307
+ />
301
308
  </>
302
309
  )}
303
310
  {visSupportsNonSequentialPallete() && (
304
311
  <>
305
312
  <span>Non-Sequential</span>
306
- <ul className='color-palette'>
307
- {nonSequential.map(palette => {
308
- const colorOne = {
309
- backgroundColor: colorPalettes[palette][2]
310
- }
311
-
312
- const colorTwo = {
313
- backgroundColor: colorPalettes[palette][4]
314
- }
315
-
316
- const colorThree = {
317
- backgroundColor: colorPalettes[palette][6]
318
- }
319
-
320
- return (
321
- <button
322
- title={palette}
323
- key={palette}
324
- onClick={e => {
325
- e.preventDefault()
326
- updateConfig({ ...config, palette })
327
- }}
328
- className={config.palette === palette ? 'selected' : ''}
329
- >
330
- <span style={colorOne}></span>
331
- <span style={colorTwo}></span>
332
- <span style={colorThree}></span>
333
- </button>
334
- )
335
- })}
336
- </ul>
313
+ <PaletteSelector
314
+ palettes={nonSequential}
315
+ colorPalettes={colorPalettes}
316
+ config={config}
317
+ onPaletteSelect={handlePaletteSelection}
318
+ selectedPalette={getCurrentPaletteName(config)}
319
+ colorIndices={[2, 4, 6]}
320
+ className='color-palette'
321
+ />
337
322
  <span>Colorblind Safe</span>
338
- <ul className='color-palette'>
339
- {accessibleColors.map(palette => {
340
- const colorOne = {
341
- backgroundColor: colorPalettes[palette][2]
342
- }
343
-
344
- const colorTwo = {
345
- backgroundColor: colorPalettes[palette][3]
346
- }
323
+ <PaletteSelector
324
+ palettes={accessibleColors}
325
+ colorPalettes={colorPalettes}
326
+ config={config}
327
+ onPaletteSelect={handlePaletteSelection}
328
+ selectedPalette={getCurrentPaletteName(config)}
329
+ colorIndices={[2, 3, 5]}
330
+ className='color-palette'
331
+ />
332
+ </>
333
+ )}
347
334
 
348
- const colorThree = {
349
- backgroundColor: colorPalettes[palette][5]
350
- }
335
+ {isCoveDeveloperMode() && (visSupportsSequentialPallete() || visSupportsNonSequentialPallete()) && (
336
+ <>
337
+ <div className='mt-3'>
338
+ <label className='checkbox'>
339
+ <input
340
+ type='checkbox'
341
+ checked={
342
+ !!(config.general?.palette?.customColorsOrdered || config.general?.palette?.customColors)
343
+ }
344
+ onChange={e => {
345
+ const _state = cloneConfig(config)
346
+ // Ensure palette object exists
347
+ if (!_state.general.palette) {
348
+ _state.general.palette = {}
349
+ }
350
+ if (e.target.checked) {
351
+ // Extract colors from current color scale if runtime data available
352
+ if (config.runtime?.seriesLabelsAll && config.runtime.seriesLabelsAll.length > 0) {
353
+ const colorScale = getColorScale(config)
354
+ const extractedColors = config.runtime.seriesLabelsAll.map((label: string) =>
355
+ colorScale(label)
356
+ )
357
+ _state.general.palette.customColorsOrdered =
358
+ extractedColors.length > 0
359
+ ? extractedColors
360
+ : ['#3366cc', '#5588dd', '#77aaee', '#99ccff']
361
+ } else {
362
+ // Fallback to default colors if runtime not available
363
+ _state.general.palette.customColorsOrdered = ['#3366cc', '#5588dd', '#77aaee', '#99ccff']
364
+ }
365
+ } else {
366
+ // Remove custom colors and revert to default palette
367
+ delete _state.general.palette.customColorsOrdered
368
+ delete _state.general.palette.customColors
369
+ // Set default palette if none exists
370
+ if (!_state.general.palette.name) {
371
+ _state.general.palette.name = 'qualitative_standard'
372
+ _state.general.palette.version = '2.0'
373
+ }
374
+ }
375
+ updateConfig(_state)
376
+ }}
377
+ />
378
+ Use Custom Colors
379
+ </label>
380
+ </div>
351
381
 
352
- return (
353
- <button
354
- title={palette}
355
- key={palette}
356
- onClick={e => {
357
- e.preventDefault()
358
- updateConfig({ ...config, palette })
359
- }}
360
- className={config.palette === palette ? 'selected' : ''}
361
- >
362
- <span style={colorOne}></span>
363
- <span style={colorTwo}></span>
364
- <span style={colorThree}></span>
365
- </button>
366
- )
367
- })}
368
- </ul>
382
+ {(config.general?.palette?.customColorsOrdered || config.general?.palette?.customColors) && (
383
+ <div className='mt-2'>
384
+ <CustomColorsEditor
385
+ colors={config.general.palette.customColorsOrdered || config.general.palette.customColors}
386
+ onChange={newColors => {
387
+ const _state = cloneConfig(config)
388
+ if (!_state.general.palette) {
389
+ _state.general.palette = {}
390
+ }
391
+ _state.general.palette.customColorsOrdered = newColors
392
+ updateConfig(_state)
393
+ }}
394
+ label='Custom Color Order'
395
+ minColors={1}
396
+ maxColors={20}
397
+ />
398
+ </div>
399
+ )}
369
400
  </>
370
401
  )}
371
402
  </>
@@ -406,6 +437,7 @@ const PanelVisual: FC<PanelProps> = props => {
406
437
  )}
407
438
  {(config.visualizationType === 'Paired Bar' || config.visualizationType === 'Deviation Bar') && (
408
439
  <>
440
+ <DeveloperPaletteRollback config={config} updateConfig={updateConfig} className='mt-3' />
409
441
  <InputToggle
410
442
  section='twoColor'
411
443
  fieldName='isPaletteReversed'
@@ -415,30 +447,48 @@ const PanelVisual: FC<PanelProps> = props => {
415
447
  value={config.twoColor.isPaletteReversed}
416
448
  />
417
449
  <ul className='color-palette'>
418
- {twoColorPalettes.map(palette => {
419
- const colorOne = {
420
- backgroundColor: twoColorPalette[palette][0]
421
- }
450
+ {twoColorPalettes
451
+ .map(palette => {
452
+ const paletteColors = versionedTwoColorPalette[palette]
422
453
 
423
- const colorTwo = {
424
- backgroundColor: twoColorPalette[palette][1]
425
- }
454
+ if (!paletteColors || paletteColors.length < 2) {
455
+ console.warn(
456
+ `Two-color palette "${palette}" not found or incomplete in version ${getColorPaletteVersion(
457
+ config
458
+ )}`
459
+ )
460
+ return null
461
+ }
462
+
463
+ const colorOne = {
464
+ backgroundColor: paletteColors[0]
465
+ }
466
+
467
+ const colorTwo = {
468
+ backgroundColor: paletteColors[1]
469
+ }
426
470
 
427
- return (
428
- <button
429
- title={palette}
430
- key={palette}
431
- onClick={e => {
432
- e.preventDefault()
433
- updateConfig({ ...config, twoColor: { ...config.twoColor, palette } })
434
- }}
435
- className={config.twoColor.palette === palette ? 'selected' : ''}
436
- >
437
- <span className='two-color' style={colorOne}></span>
438
- <span className='two-color' style={colorTwo}></span>
439
- </button>
440
- )
441
- })}
471
+ return (
472
+ <button
473
+ title={palette}
474
+ key={palette}
475
+ onClick={e => {
476
+ e.preventDefault()
477
+ if (handleTwoColorPaletteSelection) {
478
+ handleTwoColorPaletteSelection(palette)
479
+ } else {
480
+ // Fallback to direct update if handler not available
481
+ updateConfig({ ...config, twoColor: { ...config.twoColor, palette } })
482
+ }
483
+ }}
484
+ className={config.twoColor.palette === palette ? 'selected' : ''}
485
+ >
486
+ <span className='two-color' style={colorOne}></span>
487
+ <span className='two-color' style={colorTwo}></span>
488
+ </button>
489
+ )
490
+ })
491
+ .filter(Boolean)}
442
492
  </ul>
443
493
  </>
444
494
  )}
@@ -448,7 +498,6 @@ const PanelVisual: FC<PanelProps> = props => {
448
498
  value={config.dataCutoff}
449
499
  type='number'
450
500
  fieldName='dataCutoff'
451
- className='number-narrow'
452
501
  label='Data Cutoff'
453
502
  updateField={updateField}
454
503
  tooltip={
@@ -6,6 +6,8 @@ import BoxPlot from './Panel.BoxPlot'
6
6
  import Visual from './Panel.Visual'
7
7
  import Sankey from './Panel.Sankey'
8
8
  import Annotate from './Panel.Annotate'
9
+ import PatternSettings from './Panel.PatternSettings'
10
+ import SmallMultiples from './Panel.SmallMultiples'
9
11
 
10
12
  const Panels = {
11
13
  ForestPlot: ForestPlotSettings,
@@ -15,7 +17,9 @@ const Panels = {
15
17
  BoxPlot,
16
18
  Visual,
17
19
  Sankey,
18
- Annotate
20
+ Annotate,
21
+ PatternSettings,
22
+ SmallMultiples
19
23
  }
20
24
 
21
25
  export default Panels
@@ -1,8 +0,0 @@
1
- .type-chart .sidebar .color-palette button:not(.selected) {
2
- border: var(--cool-gray-30) 2px solid !important;
3
-
4
- }
5
-
6
- .type-chart .sidebar .color-palette button.selected {
7
- border: black 2px solid !important;
8
- }
@@ -7,26 +7,6 @@
7
7
  text-align: left;
8
8
 
9
9
  span {
10
- display: inline-block;
11
- float: right;
12
- }
13
- }
14
-
15
- .edit-block {
16
- margin-top: 0;
17
- padding: 1em;
18
- }
19
- }
20
-
21
- .viewport-overrides {
22
- button {
23
- width: 100%;
24
- padding: 1em;
25
- margin-top: 1em;
26
- text-align: left;
27
-
28
- span {
29
- display: inline-block;
30
10
  float: right;
31
11
  }
32
12
  }
@@ -1,48 +1,49 @@
1
- import DataTransform from '@cdc/core/helpers/DataTransform'
2
- import { ChartConfig } from '../../../types/ChartConfig'
3
- import _ from 'lodash'
4
-
5
- const transform = new DataTransform()
6
-
7
- const indexOfObj = (data, obj) => {
8
- for (let i = 0; i < data.length; i++) {
9
- let keys = Object.keys(data[i])
10
- let equal = true
11
- for (let j = 0; j < keys.length; j++) {
12
- if (data[i][keys[j]] !== obj[keys[j]]) {
13
- equal = false
14
- break
15
- }
16
- }
17
- if (equal) {
18
- return i
19
- }
20
- }
21
- return -1
22
- }
23
-
24
- export const updateFieldRankByValue = (
25
- config: ChartConfig,
26
- newValue: 'asc' | 'desc' | undefined,
27
- preTransformedData: Object[]
28
- ): [ChartConfig, Object[]?] => {
29
- const newConfig = _.cloneDeep(config)
30
- newConfig.rankByValue = newValue
31
-
32
- if (config.rankByValue && !newValue) {
33
- const CIkeys: string[] = Object.values(_.get(config, 'confidenceKeys', {})) as string[]
34
- const seriesKeys: string[] = _.get(config, 'series', []).map((s: any) => s.dataKey)
35
- const keysToClean: string[] = [...(seriesKeys ?? []), ...(CIkeys ?? [])]
36
-
37
- const cleanData = config?.xAxis?.dataKey
38
- ? transform.cleanData(config.data, config.xAxis.dataKey, keysToClean)
39
- : config.data
40
- const newData = preTransformedData.sort((a, b) => {
41
- const aIndex = indexOfObj(cleanData, a)
42
- const bIndex = indexOfObj(cleanData, b)
43
- return aIndex - bIndex
44
- })
45
- return [newConfig, newData]
46
- }
47
- return [newConfig]
48
- }
1
+ import DataTransform from '@cdc/core/helpers/DataTransform'
2
+ import { ChartConfig } from '../../../types/ChartConfig'
3
+ import cloneConfig from '@cdc/core/helpers/cloneConfig'
4
+ import _ from 'lodash'
5
+
6
+ const transform = new DataTransform()
7
+
8
+ const indexOfObj = (data, obj) => {
9
+ for (let i = 0; i < data.length; i++) {
10
+ let keys = Object.keys(data[i])
11
+ let equal = true
12
+ for (let j = 0; j < keys.length; j++) {
13
+ if (data[i][keys[j]] !== obj[keys[j]]) {
14
+ equal = false
15
+ break
16
+ }
17
+ }
18
+ if (equal) {
19
+ return i
20
+ }
21
+ }
22
+ return -1
23
+ }
24
+
25
+ export const updateFieldRankByValue = (
26
+ config: ChartConfig,
27
+ newValue: 'asc' | 'desc' | undefined,
28
+ preTransformedData: Object[]
29
+ ): [ChartConfig, Object[]?] => {
30
+ const newConfig = cloneConfig(config)
31
+ newConfig.rankByValue = newValue
32
+
33
+ if (config.rankByValue && !newValue) {
34
+ const CIkeys: string[] = Object.values(_.get(config, 'confidenceKeys', {})) as string[]
35
+ const seriesKeys: string[] = _.get(config, 'series', []).map((s: any) => s.dataKey)
36
+ const keysToClean: string[] = [...(seriesKeys ?? []), ...(CIkeys ?? [])]
37
+
38
+ const cleanData = config?.xAxis?.dataKey
39
+ ? transform.cleanData(config.data, config.xAxis.dataKey, keysToClean)
40
+ : config.data
41
+ const newData = preTransformedData.sort((a, b) => {
42
+ const aIndex = indexOfObj(cleanData, a)
43
+ const bIndex = indexOfObj(cleanData, b)
44
+ return aIndex - bIndex
45
+ })
46
+ return [newConfig, newData]
47
+ }
48
+ return [newConfig]
49
+ }
@@ -22,20 +22,6 @@ export const useEditorPermissions = () => {
22
22
  'Scatter Plot',
23
23
  'Spark Line',
24
24
  'Sankey'
25
- ]
26
-
27
- const headerColors = [
28
- 'theme-blue',
29
- 'theme-purple',
30
- 'theme-brown',
31
- 'theme-teal',
32
- 'theme-pink',
33
- 'theme-orange',
34
- 'theme-slate',
35
- 'theme-indigo',
36
- 'theme-cyan',
37
- 'theme-green',
38
- 'theme-amber'
39
25
  ]
40
26
 
41
27
  const visSupportsDateCategoryAxis = () => {
@@ -383,6 +369,12 @@ export const useEditorPermissions = () => {
383
369
  )
384
370
  }
385
371
 
372
+ const visSupportsSmallMultiples = () => {
373
+ const enabledCharts = ['Line', 'Bar', 'Area Chart', 'Combo', 'Box Plot', 'Scatter Plot']
374
+ if (enabledCharts.includes(visualizationType)) return true
375
+ return false
376
+ }
377
+
386
378
  const visSupportsYPadding = () => {
387
379
  return !config.yAxis.inlineLabel || !config.yAxis.inlineLabel?.includes(' ')
388
380
  }
@@ -411,7 +403,6 @@ export const useEditorPermissions = () => {
411
403
 
412
404
  return {
413
405
  enabledChartTypes,
414
- headerColors,
415
406
  visCanAnimate,
416
407
  visHasAnchors,
417
408
  visHasBarBorders,
@@ -460,6 +451,7 @@ export const useEditorPermissions = () => {
460
451
  visSupportsValueAxisMax,
461
452
  visSupportsValueAxisMin,
462
453
  visSupportsDynamicSeries,
454
+ visSupportsSmallMultiples,
463
455
  visSupportsYPadding,
464
456
  visHasSingleSeriesTooltip,
465
457
  visHasCategoricalAxis