@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.
- package/.claude/settings.local.json +9 -0
- package/dist/cdcchart.js +37524 -35243
- package/examples/feature/__data__/planet-example-data.json +0 -30
- package/examples/grouped-bar-test.json +400 -0
- package/examples/private/d.json +382 -0
- package/examples/private/example-2.json +49784 -0
- package/examples/private/f2.json +1 -0
- package/examples/private/f4.json +1577 -0
- package/examples/private/forecast.json +1180 -0
- package/examples/private/lollipop.json +468 -0
- package/examples/private/new.json +48756 -0
- package/examples/private/pie-chart-legend.json +904 -0
- package/examples/suppressed_tooltip.json +480 -0
- package/index.html +10 -22
- package/package.json +25 -7
- package/src/CdcChart.tsx +1 -2
- package/src/CdcChartComponent.tsx +174 -32
- package/src/_stories/Chart.Anchors.stories.tsx +2 -2
- package/src/_stories/Chart.BoxPlot.stories.tsx +1 -1
- package/src/_stories/Chart.CI.stories.tsx +1 -1
- package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
- package/src/_stories/Chart.DynamicSeries.stories.tsx +2 -2
- package/src/_stories/Chart.Filters.stories.tsx +2 -2
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
- package/src/_stories/Chart.Patterns.stories.tsx +19 -0
- package/src/_stories/Chart.ScatterPlot.stories.tsx +1 -1
- package/src/_stories/Chart.stories.tsx +8 -5
- package/src/_stories/Chart.tooltip.stories.tsx +1 -1
- package/src/_stories/ChartAnnotation.stories.tsx +1 -1
- package/src/_stories/ChartAxisLabels.stories.tsx +2 -2
- package/src/_stories/ChartAxisTitles.stories.tsx +2 -2
- package/src/_stories/ChartEditor.stories.tsx +60 -60
- package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
- package/src/_stories/ChartLine.Symbols.stories.tsx +1 -1
- package/src/_stories/ChartPrefixSuffix.stories.tsx +2 -2
- package/src/_stories/_mock/stacked-pattern-test.json +520 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -0
- package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +159 -20
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +138 -5
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +215 -73
- package/src/components/BarChart/components/BarChart.Vertical.tsx +153 -21
- package/src/components/BarChart/helpers/index.ts +43 -4
- package/src/components/BarChart/helpers/lollipopColors.ts +27 -0
- package/src/components/BarChart/helpers/useBarChart.ts +25 -3
- package/src/components/BoxPlot/BoxPlot.Vertical.tsx +2 -1
- package/src/components/DeviationBar.jsx +9 -6
- package/src/components/EditorPanel/EditorPanel.tsx +364 -39
- package/src/components/EditorPanel/EditorPanelContext.ts +3 -0
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +414 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +28 -20
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +115 -120
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +0 -8
- package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +49 -48
- package/src/components/Forecasting/Forecasting.tsx +36 -6
- package/src/components/ForestPlot/ForestPlot.tsx +11 -7
- package/src/components/ForestPlot/ForestPlotProps.ts +1 -1
- package/src/components/Legend/Legend.Component.tsx +106 -13
- package/src/components/Legend/helpers/createFormatLabels.tsx +230 -171
- package/src/components/LegendWrapper.tsx +1 -1
- package/src/components/LineChart/components/LineChart.Circle.tsx +2 -2
- package/src/components/LineChart/index.tsx +2 -2
- package/src/components/LinearChart.tsx +22 -5
- package/src/components/PairedBarChart.jsx +6 -4
- package/src/components/PieChart/PieChart.tsx +170 -54
- package/src/components/Sankey/components/Sankey.tsx +7 -1
- package/src/components/ScatterPlot/ScatterPlot.jsx +32 -4
- package/src/data/initial-state.js +315 -293
- package/src/helpers/buildForecastPaletteMappings.ts +112 -0
- package/src/helpers/buildForecastPaletteOptions.ts +109 -0
- package/src/helpers/getColorScale.ts +72 -8
- package/src/helpers/getNewRuntime.ts +1 -1
- package/src/helpers/getTransformedData.ts +1 -1
- package/src/hooks/useChartHoverAnalytics.tsx +44 -0
- package/src/hooks/useReduceData.ts +105 -70
- package/src/hooks/useTooltip.tsx +57 -15
- package/src/index.jsx +0 -2
- package/src/scss/main.scss +12 -0
- package/src/store/chart.reducer.ts +1 -1
- package/src/test/CdcChart.test.jsx +8 -3
- package/src/types/ChartConfig.ts +30 -6
- package/src/types/ChartContext.ts +1 -0
- package/vite.config.js +1 -1
- package/vitest.config.ts +16 -0
- package/src/coreStyles_chart.scss +0 -3
- package/src/helpers/configHelpers.ts +0 -28
- package/src/helpers/generateColorsArray.ts +0 -8
- 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 '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
271
|
-
{sequential
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
<
|
|
307
|
-
{nonSequential
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
<
|
|
339
|
-
{accessibleColors
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
}
|
|
395
|
+
{twoColorPalettes
|
|
396
|
+
.map(palette => {
|
|
397
|
+
const paletteColors = versionedTwoColorPalette[palette]
|
|
422
398
|
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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,48 +1,49 @@
|
|
|
1
|
-
import DataTransform from '@cdc/core/helpers/DataTransform'
|
|
2
|
-
import { ChartConfig } from '../../../types/ChartConfig'
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
let
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
newConfig
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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 {
|
|
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 =
|
|
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]
|
|
71
|
+
if (displayArea) return palette?.[2] || 'transparent'
|
|
43
72
|
return 'transparent'
|
|
44
73
|
}
|
|
45
74
|
|
|
46
75
|
const getStroke = () => {
|
|
47
|
-
if (displayArea) return
|
|
48
|
-
|
|
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 '
|
|
12
|
-
import { type ChartConfig } from '
|
|
13
|
-
import { type ChartContext } from '
|
|
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.
|
|
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}
|