@cdc/chart 4.25.8 → 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 (89) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/dist/cdcchart.js +37524 -35243
  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 +1 -2
  17. package/src/CdcChartComponent.tsx +174 -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 +159 -20
  40. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +138 -5
  41. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +215 -73
  42. package/src/components/BarChart/components/BarChart.Vertical.tsx +153 -21
  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/DeviationBar.jsx +9 -6
  48. package/src/components/EditorPanel/EditorPanel.tsx +364 -39
  49. package/src/components/EditorPanel/EditorPanelContext.ts +3 -0
  50. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +414 -0
  51. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +28 -20
  52. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +115 -120
  53. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  54. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +0 -8
  55. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +49 -48
  56. package/src/components/Forecasting/Forecasting.tsx +36 -6
  57. package/src/components/ForestPlot/ForestPlot.tsx +11 -7
  58. package/src/components/ForestPlot/ForestPlotProps.ts +1 -1
  59. package/src/components/Legend/Legend.Component.tsx +106 -13
  60. package/src/components/Legend/helpers/createFormatLabels.tsx +230 -171
  61. package/src/components/LegendWrapper.tsx +1 -1
  62. package/src/components/LineChart/components/LineChart.Circle.tsx +2 -2
  63. package/src/components/LineChart/index.tsx +2 -2
  64. package/src/components/LinearChart.tsx +22 -5
  65. package/src/components/PairedBarChart.jsx +6 -4
  66. package/src/components/PieChart/PieChart.tsx +170 -54
  67. package/src/components/Sankey/components/Sankey.tsx +7 -1
  68. package/src/components/ScatterPlot/ScatterPlot.jsx +32 -4
  69. package/src/data/initial-state.js +315 -293
  70. package/src/helpers/buildForecastPaletteMappings.ts +112 -0
  71. package/src/helpers/buildForecastPaletteOptions.ts +109 -0
  72. package/src/helpers/getColorScale.ts +72 -8
  73. package/src/helpers/getNewRuntime.ts +1 -1
  74. package/src/helpers/getTransformedData.ts +1 -1
  75. package/src/hooks/useChartHoverAnalytics.tsx +44 -0
  76. package/src/hooks/useReduceData.ts +105 -70
  77. package/src/hooks/useTooltip.tsx +57 -15
  78. package/src/index.jsx +0 -2
  79. package/src/scss/main.scss +12 -0
  80. package/src/store/chart.reducer.ts +1 -1
  81. package/src/test/CdcChart.test.jsx +8 -3
  82. package/src/types/ChartConfig.ts +30 -6
  83. package/src/types/ChartContext.ts +1 -0
  84. package/vite.config.js +1 -1
  85. package/vitest.config.ts +16 -0
  86. package/src/coreStyles_chart.scss +0 -3
  87. package/src/helpers/configHelpers.ts +0 -28
  88. package/src/helpers/generateColorsArray.ts +0 -8
  89. 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,9 @@ 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'
19
23
  import { ChartContext } from './../../../../types/ChartContext.js'
20
24
 
21
25
  import { useEditorPermissions } from '../../useEditorPermissions.js'
@@ -23,12 +27,15 @@ import { useEditorPanelContext } from '../../EditorPanelContext.js'
23
27
  import ConfigContext from '../../../../ConfigContext.js'
24
28
  import { PanelProps } from '../PanelProps'
25
29
  import { LineChartConfig } from '../../../../types/ChartConfig'
30
+ import { PaletteSelector, DeveloperPaletteRollback } from '@cdc/core/components/PaletteSelector'
26
31
  import './panelVisual.styles.css'
27
32
 
28
33
  const PanelVisual: FC<PanelProps> = props => {
29
34
  const { config, updateConfig, colorPalettes, twoColorPalette } = useContext<ChartContext>(ConfigContext)
30
35
  const { visual } = config
31
- const { setLollipopShape, updateField } = useEditorPanelContext()
36
+
37
+ const { setLollipopShape, updateField, handlePaletteSelection, handleTwoColorPaletteSelection } =
38
+ useEditorPanelContext()
32
39
  const {
33
40
  visHasBarBorders,
34
41
  visCanAnimate,
@@ -45,6 +52,14 @@ const PanelVisual: FC<PanelProps> = props => {
45
52
  } = useEditorPermissions()
46
53
  const { twoColorPalettes, sequential, nonSequential, accessibleColors } = useColorPalette(config, updateConfig)
47
54
 
55
+ const currentPaletteName = getCurrentPaletteName(config)
56
+
57
+ const versionedTwoColorPalette = useMemo(() => {
58
+ const version = getColorPaletteVersion(config)
59
+ const versionKey = `v${version}`
60
+ return twoColorPalette[versionKey] || twoColorPalette.v2
61
+ }, [config, twoColorPalette])
62
+
48
63
  const updateColor = (property, _value) => {
49
64
  console.error('value', _value)
50
65
  if (property === 'storyNodeFontColor') {
@@ -255,117 +270,78 @@ const PanelVisual: FC<PanelProps> = props => {
255
270
  <label>
256
271
  <span className='edit-label'>Chart Color Palette</span>
257
272
  </label>
273
+ <div className='mb-2'>
274
+ <small className='text-muted'>
275
+ Review color contrasts{' '}
276
+ <a href='https://webaim.org/resources/contrastchecker/' target='_blank' rel='noopener noreferrer'>
277
+ here
278
+ </a>
279
+ </small>
280
+ </div>
281
+ <DeveloperPaletteRollback config={config} updateConfig={updateConfig} />
258
282
  {visSupportsReverseColorPalette() && (
259
283
  <InputToggle
260
- fieldName='isPaletteReversed'
284
+ section='general'
285
+ subsection='palette'
286
+ fieldName='isReversed'
261
287
  size='small'
262
288
  label='Use selected palette in reverse order'
263
289
  updateField={updateField}
264
- value={config.isPaletteReversed}
290
+ onClick={() => {
291
+ const _state = cloneConfig(config)
292
+ const currentPaletteName = getCurrentPaletteName(config)
293
+ _state.general.palette.isReversed = !_state.general.palette.isReversed
294
+ let paletteName = ''
295
+ if (_state.general.palette.isReversed && !currentPaletteName.endsWith('reverse')) {
296
+ paletteName = currentPaletteName + 'reverse'
297
+ }
298
+ if (!_state.general.palette.isReversed && currentPaletteName.endsWith('reverse')) {
299
+ paletteName = currentPaletteName.slice(0, -7)
300
+ }
301
+ if (paletteName) {
302
+ _state.general.palette.name = paletteName
303
+ }
304
+ updateConfig(_state)
305
+ }}
306
+ value={config.general?.palette?.isReversed}
265
307
  />
266
308
  )}
267
309
  {visSupportsSequentialPallete() && (
268
310
  <>
269
311
  <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>
312
+ <PaletteSelector
313
+ palettes={sequential}
314
+ colorPalettes={colorPalettes}
315
+ config={config}
316
+ onPaletteSelect={handlePaletteSelection}
317
+ selectedPalette={currentPaletteName}
318
+ colorIndices={[2, 3, 5]}
319
+ className='color-palette'
320
+ />
301
321
  </>
302
322
  )}
303
323
  {visSupportsNonSequentialPallete() && (
304
324
  <>
305
325
  <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>
326
+ <PaletteSelector
327
+ palettes={nonSequential}
328
+ colorPalettes={colorPalettes}
329
+ config={config}
330
+ onPaletteSelect={handlePaletteSelection}
331
+ selectedPalette={getCurrentPaletteName(config)}
332
+ colorIndices={[2, 4, 6]}
333
+ className='color-palette'
334
+ />
337
335
  <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
- }
347
-
348
- const colorThree = {
349
- backgroundColor: colorPalettes[palette][5]
350
- }
351
-
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>
336
+ <PaletteSelector
337
+ palettes={accessibleColors}
338
+ colorPalettes={colorPalettes}
339
+ config={config}
340
+ onPaletteSelect={handlePaletteSelection}
341
+ selectedPalette={getCurrentPaletteName(config)}
342
+ colorIndices={[2, 3, 5]}
343
+ className='color-palette'
344
+ />
369
345
  </>
370
346
  )}
371
347
  </>
@@ -406,6 +382,7 @@ const PanelVisual: FC<PanelProps> = props => {
406
382
  )}
407
383
  {(config.visualizationType === 'Paired Bar' || config.visualizationType === 'Deviation Bar') && (
408
384
  <>
385
+ <DeveloperPaletteRollback config={config} updateConfig={updateConfig} className='mt-3' />
409
386
  <InputToggle
410
387
  section='twoColor'
411
388
  fieldName='isPaletteReversed'
@@ -415,30 +392,48 @@ const PanelVisual: FC<PanelProps> = props => {
415
392
  value={config.twoColor.isPaletteReversed}
416
393
  />
417
394
  <ul className='color-palette'>
418
- {twoColorPalettes.map(palette => {
419
- const colorOne = {
420
- backgroundColor: twoColorPalette[palette][0]
421
- }
395
+ {twoColorPalettes
396
+ .map(palette => {
397
+ const paletteColors = versionedTwoColorPalette[palette]
422
398
 
423
- const colorTwo = {
424
- backgroundColor: twoColorPalette[palette][1]
425
- }
399
+ if (!paletteColors || paletteColors.length < 2) {
400
+ console.warn(
401
+ `Two-color palette "${palette}" not found or incomplete in version ${getColorPaletteVersion(
402
+ config
403
+ )}`
404
+ )
405
+ return null
406
+ }
407
+
408
+ const colorOne = {
409
+ backgroundColor: paletteColors[0]
410
+ }
426
411
 
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
- })}
412
+ const colorTwo = {
413
+ backgroundColor: paletteColors[1]
414
+ }
415
+
416
+ return (
417
+ <button
418
+ title={palette}
419
+ key={palette}
420
+ onClick={e => {
421
+ e.preventDefault()
422
+ if (handleTwoColorPaletteSelection) {
423
+ handleTwoColorPaletteSelection(palette)
424
+ } else {
425
+ // Fallback to direct update if handler not available
426
+ updateConfig({ ...config, twoColor: { ...config.twoColor, palette } })
427
+ }
428
+ }}
429
+ className={config.twoColor.palette === palette ? 'selected' : ''}
430
+ >
431
+ <span className='two-color' style={colorOne}></span>
432
+ <span className='two-color' style={colorTwo}></span>
433
+ </button>
434
+ )
435
+ })
436
+ .filter(Boolean)}
442
437
  </ul>
443
438
  </>
444
439
  )}
@@ -6,6 +6,7 @@ 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'
9
10
 
10
11
  const Panels = {
11
12
  ForestPlot: ForestPlotSettings,
@@ -15,7 +16,8 @@ const Panels = {
15
16
  BoxPlot,
16
17
  Visual,
17
18
  Sankey,
18
- Annotate
19
+ Annotate,
20
+ PatternSettings
19
21
  }
20
22
 
21
23
  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
- }
@@ -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
+ }
@@ -1,10 +1,13 @@
1
- import React, { useContext } from 'react'
1
+ import React, { useContext, useMemo } from 'react'
2
2
  import { replace } from 'lodash'
3
3
  // cdc
4
4
  import ConfigContext from '../../ConfigContext'
5
5
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
6
- import { colorPalettesChart, sequentialPalettes } from '@cdc/core/data/colorPalettes'
6
+ import { colorPalettesChartV2, sequentialPalettes } from '@cdc/core/data/colorPalettes'
7
+ import { updatePaletteNames } from '@cdc/core/helpers/updatePaletteNames'
8
+ import { getColorPaletteVersion } from '@cdc/core/helpers/getColorPaletteVersion'
7
9
  import { getBridgedData } from '../../helpers/getBridgedData'
10
+ import { buildForecastPaletteMappings } from '../../helpers/buildForecastPaletteMappings'
8
11
 
9
12
  // visx & d3
10
13
  import { curveMonotoneX } from '@visx/curve'
@@ -16,6 +19,31 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
16
19
  const { xAxis, yAxis, legend, runtime } = config
17
20
  const DEBUG = false
18
21
 
22
+ // Memoize processed palettes - use version-specific palettes
23
+ const forecastingPalettes = useMemo(() => {
24
+ // Determine palette version from config
25
+ // Forecasting charts use sequentialPalettes for v1, sequential-only palettes for v2
26
+ const paletteVersion = getColorPaletteVersion(config)
27
+
28
+ let forecastPalettes
29
+ if (paletteVersion === 1) {
30
+ // V1: Use original sequential palettes
31
+ forecastPalettes = sequentialPalettes
32
+ } else {
33
+ // V2: Only use sequential palettes (filter out divergent and qualitative)
34
+ const allV2Palettes = colorPalettesChartV2
35
+ forecastPalettes = {}
36
+ Object.keys(allV2Palettes).forEach(key => {
37
+ if (key.startsWith('sequential')) {
38
+ forecastPalettes[key] = allV2Palettes[key]
39
+ }
40
+ })
41
+ }
42
+
43
+ const processedPalettes = updatePaletteNames(forecastPalettes)
44
+ return buildForecastPaletteMappings(processedPalettes, paletteVersion)
45
+ }, [config])
46
+
19
47
  return (
20
48
  data && (
21
49
  <ErrorBoundary component='ForecastingChart'>
@@ -36,16 +64,18 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
36
64
  key={`forecasting-areas--stage-${replace(stage.key, / /g, '—')}-${index}`}
37
65
  >
38
66
  {group.confidenceIntervals?.map((ciGroup, ciGroupIndex) => {
39
- const palette = sequentialPalettes[stage.color] || colorPalettesChart[stage.color] || false
67
+ const palette = forecastingPalettes[stage.color] || false
68
+ const isReversed = stage.color?.toLowerCase().includes('reverse')
40
69
 
41
70
  const getFill = () => {
42
- if (displayArea) return palette[2] ? palette[2] : 'transparent'
71
+ if (displayArea) return palette?.[2] || 'transparent'
43
72
  return 'transparent'
44
73
  }
45
74
 
46
75
  const getStroke = () => {
47
- if (displayArea) return palette[1] ? palette[1] : 'transparent'
48
- return 'transparent'
76
+ if (!displayArea) return 'transparent'
77
+ // Use darker colors: index 1 for reversed (dark at start), index 4 for non-reversed (dark at end)
78
+ return isReversed ? palette?.[1] || 'transparent' : palette?.[4] || 'transparent'
49
79
  }
50
80
 
51
81
  if (ciGroup.high === '' || ciGroup.low === '') return
@@ -8,9 +8,9 @@ import { scaleLinear } from '@visx/scale'
8
8
  import { curveLinearClosed } from '@visx/curve'
9
9
 
10
10
  // types
11
- import { type ForestPlotProps } from '@cdc/chart/src/components/ForestPlot/ForestPlotProps'
12
- import { type ChartConfig } from '@cdc/chart/src/types/ChartConfig'
13
- import { type ChartContext } from '@cdc/chart/src/types/ChartContext'
11
+ import { type ForestPlotProps } from './ForestPlotProps'
12
+ import { type ChartConfig } from '../../types/ChartConfig'
13
+ import { type ChartContext } from '../../types/ChartContext'
14
14
 
15
15
  // cdc
16
16
  import ConfigContext from '../../ConfigContext'
@@ -73,7 +73,7 @@ const ForestPlot = ({
73
73
 
74
74
  updateConfig(newConfig)
75
75
  } catch (e) {
76
- console.log(e.message)
76
+ console.error(e.message)
77
77
  }
78
78
  }, [])
79
79
 
@@ -184,7 +184,7 @@ const ForestPlot = ({
184
184
  const isTotalColumn = d[config.xAxis.dataKey] === forestPlot.pooledResult.column
185
185
 
186
186
  return !isTotalColumn ? (
187
- <Group>
187
+ <Group key={`forest-plot-row-${i}-${d[config.xAxis.dataKey]}`}>
188
188
  {/* Confidence Interval Paths */}
189
189
  <path
190
190
  stroke={lineColor}
@@ -252,6 +252,7 @@ const ForestPlot = ({
252
252
  </Group>
253
253
  ) : (
254
254
  <LinePath
255
+ key={`forest-plot-regression-${i}-${d[config.xAxis.dataKey]}`}
255
256
  data={regressionPoints}
256
257
  x={d => d.x}
257
258
  y={d => d.y - APP_FONT_SIZE / 2}
@@ -314,10 +315,11 @@ const ForestPlot = ({
314
315
  />
315
316
 
316
317
  {/* column data */}
317
- {columnsOnChart.map(column => {
318
+ {columnsOnChart.map((column, colIndex) => {
318
319
  return data.map((d, i) => {
319
320
  return (
320
321
  <Text
322
+ key={`forest-plot-column-${colIndex}-${i}-${d[config.xAxis.dataKey]}`}
321
323
  className={`${d[column.name]}`}
322
324
  x={column.forestPlotAlignRight ? width : column.forestPlotStartingPoint}
323
325
  y={yScale(i)}
@@ -336,6 +338,7 @@ const ForestPlot = ({
336
338
  data.map((d, i) => {
337
339
  return (
338
340
  <Text
341
+ key={`forest-plot-xaxis-${i}-${d[config.xAxis.dataKey]}`}
339
342
  className={`${d[config.xAxis.dataKey]}`}
340
343
  x={0}
341
344
  y={yScale(i)}
@@ -356,9 +359,10 @@ const ForestPlot = ({
356
359
  )}
357
360
 
358
361
  {/* column headers */}
359
- {columnsOnChart.map(column => {
362
+ {columnsOnChart.map((column, colIndex) => {
360
363
  return (
361
364
  <Text
365
+ key={`forest-plot-header-${colIndex}-${column.label}`}
362
366
  className={`${column.label}`}
363
367
  x={column.forestPlotAlignRight ? width : column.forestPlotStartingPoint}
364
368
  y={0}
@@ -1,4 +1,4 @@
1
- import { type ChartConfig } from '@cdc/chart/src/types/ChartConfig'
1
+ import { type ChartConfig } from '../../types/ChartConfig'
2
2
 
3
3
  export type ForestPlotProps = {
4
4
  /** xScale - scaling function for bottom axis */