@cdc/chart 4.25.10 → 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.
- package/dist/{cdcchart-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
- package/dist/cdcchart.js +36258 -34658
- package/examples/feature/__data__/planet-example-data.json +1 -1
- package/examples/feature/boxplot/valid-boxplot.csv +38 -17
- package/examples/private/DEV-11825.json +573 -0
- package/examples/private/na.json +913 -0
- package/examples/private/test-data.csv +28 -0
- package/index.html +2 -121
- package/package.json +4 -4
- package/src/CdcChart.tsx +8 -11
- package/src/CdcChartComponent.tsx +256 -87
- package/src/_stories/Chart.Combo.stories.tsx +18 -0
- package/src/_stories/Chart.Forecast.stories.tsx +36 -0
- package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
- package/src/_stories/Chart.Patterns.stories.tsx +2 -1
- package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
- package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
- package/src/_stories/ChartAnnotation.stories.tsx +6 -3
- package/src/_stories/ChartBar.Editor.stories.tsx +3580 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +658 -0
- package/src/_stories/ChartEditor.stories.tsx +1 -2
- package/src/_stories/_mock/combo.json +451 -0
- package/src/_stories/_mock/editor-test-configs.json +376 -0
- package/src/_stories/_mock/editor-test-datasets.json +477 -0
- package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
- package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
- package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
- package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
- package/src/_stories/_mock/pie_config.json +257 -62
- package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
- package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
- package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
- package/src/components/Annotations/components/findNearestDatum.ts +6 -41
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -6
- package/src/components/AreaChart/index.tsx +1 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +4 -4
- package/src/components/BarChart/components/BarChart.Vertical.tsx +3 -2
- package/src/components/BoxPlot/helpers/index.ts +3 -3
- package/src/components/Brush/BrushChart.tsx +1 -1
- package/src/components/EditorPanel/EditorPanel.tsx +199 -190
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -1
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +102 -55
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +54 -49
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +422 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +75 -21
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/EditorPanel/editor-panel.scss +0 -20
- package/src/components/EditorPanel/useEditorPermissions.ts +7 -15
- package/src/components/Forecasting/Forecasting.tsx +139 -21
- package/src/components/Legend/Legend.Component.tsx +16 -9
- package/src/components/Legend/helpers/createFormatLabels.tsx +181 -181
- package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
- package/src/components/LineChart/LineChartProps.ts +0 -3
- package/src/components/LineChart/helpers.ts +1 -1
- package/src/components/LineChart/index.tsx +36 -13
- package/src/components/LinearChart.tsx +75 -80
- package/src/components/Regions/components/Regions.tsx +3 -24
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +198 -0
- package/src/components/SmallMultiples/SmallMultiples.css +32 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
- package/src/components/SmallMultiples/index.ts +2 -0
- package/src/data/initial-state.js +13 -1
- package/src/helpers/buildForecastPaletteOptions.ts +0 -38
- package/src/helpers/getColorScale.ts +10 -0
- package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +14 -7
- package/src/helpers/getYAxisAutoPadding.ts +53 -0
- package/src/helpers/smallMultiplesHelpers.ts +529 -0
- package/src/hooks/useProgrammaticTooltip.ts +96 -0
- package/src/hooks/useScales.ts +88 -34
- package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
- package/src/hooks/useTooltip.tsx +60 -15
- package/src/scss/main.scss +1 -80
- package/src/store/chart.actions.ts +2 -0
- package/src/store/chart.reducer.ts +4 -0
- package/src/types/ChartConfig.ts +24 -6
- package/src/types/ChartContext.ts +3 -0
- package/src/_stories/_mock/pie_data.json +0 -218
- package/src/components/AreaChart/components/AreaChart.jsx +0 -109
- package/src/helpers/sort.ts +0 -7
- package/src/hooks/useActiveElement.js +0 -19
- package/src/hooks/useChartClasses.js +0 -41
|
@@ -75,6 +75,7 @@ import { getExcludedData } from './helpers/getExcludedData'
|
|
|
75
75
|
import { getColorScale } from './helpers/getColorScale'
|
|
76
76
|
import { getTransformedData } from './helpers/getTransformedData'
|
|
77
77
|
import { getPiePercent } from './helpers/getPiePercent'
|
|
78
|
+
import { prepareSmallMultiplesDataTable } from './helpers/smallMultiplesHelpers'
|
|
78
79
|
|
|
79
80
|
// styles
|
|
80
81
|
import './scss/main.scss'
|
|
@@ -275,30 +276,62 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
275
276
|
return newConfig
|
|
276
277
|
}
|
|
277
278
|
|
|
279
|
+
const getProcessedAxisLabels = useCallback(
|
|
280
|
+
(targetConfig: AllChartsConfig, dataSource: any[] = []) => {
|
|
281
|
+
let processedXAxis = targetConfig.xAxis?.label
|
|
282
|
+
let processedYAxis = targetConfig.yAxis?.label
|
|
283
|
+
|
|
284
|
+
if (targetConfig.enableMarkupVariables && targetConfig.markupVariables?.length) {
|
|
285
|
+
if (targetConfig.xAxis?.label) {
|
|
286
|
+
processedXAxis = processMarkupVariables(
|
|
287
|
+
targetConfig.xAxis.label,
|
|
288
|
+
dataSource || [],
|
|
289
|
+
targetConfig.markupVariables,
|
|
290
|
+
{
|
|
291
|
+
isEditor,
|
|
292
|
+
filters: targetConfig.filters || []
|
|
293
|
+
}
|
|
294
|
+
).processedContent
|
|
295
|
+
}
|
|
296
|
+
if (targetConfig.yAxis?.label) {
|
|
297
|
+
processedYAxis = processMarkupVariables(
|
|
298
|
+
targetConfig.yAxis.label,
|
|
299
|
+
dataSource || [],
|
|
300
|
+
targetConfig.markupVariables,
|
|
301
|
+
{
|
|
302
|
+
isEditor,
|
|
303
|
+
filters: targetConfig.filters || []
|
|
304
|
+
}
|
|
305
|
+
).processedContent
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const isHorizontalVariant =
|
|
310
|
+
((targetConfig.visualizationType === 'Bar' || targetConfig.visualizationType === 'Box Plot') &&
|
|
311
|
+
targetConfig.orientation === 'horizontal') ||
|
|
312
|
+
['Deviation Bar', 'Paired Bar', 'Forest Plot'].includes(targetConfig.visualizationType)
|
|
313
|
+
|
|
314
|
+
const runtimeXAxisLabel = isHorizontalVariant
|
|
315
|
+
? processedYAxis ?? (targetConfig.yAxis as any)?.yAxis?.label ?? targetConfig.yAxis?.label
|
|
316
|
+
: processedXAxis ?? targetConfig.xAxis?.label
|
|
317
|
+
|
|
318
|
+
const runtimeYAxisLabel = isHorizontalVariant
|
|
319
|
+
? processedXAxis ?? (targetConfig.xAxis as any)?.xAxis?.label ?? targetConfig.xAxis?.label
|
|
320
|
+
: processedYAxis ?? targetConfig.yAxis?.label
|
|
321
|
+
|
|
322
|
+
return { processedXAxis, processedYAxis, runtimeXAxisLabel, runtimeYAxisLabel, isHorizontalVariant }
|
|
323
|
+
},
|
|
324
|
+
[isEditor]
|
|
325
|
+
)
|
|
326
|
+
|
|
278
327
|
const updateConfig = (_config: AllChartsConfig, dataOverride?: any[]) => {
|
|
279
328
|
const newConfig = cloneConfig(_config)
|
|
280
329
|
let data = dataOverride || stateData
|
|
281
330
|
|
|
282
331
|
data = handleRankByValue(data, newConfig)
|
|
283
332
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
let processedYAxis = newConfig.yAxis?.label
|
|
287
|
-
|
|
288
|
-
if (newConfig.enableMarkupVariables && newConfig.markupVariables?.length) {
|
|
289
|
-
if (newConfig.xAxis?.label) {
|
|
290
|
-
processedXAxis = processMarkupVariables(newConfig.xAxis.label, data || [], newConfig.markupVariables, {
|
|
291
|
-
isEditor,
|
|
292
|
-
filters: newConfig.filters || []
|
|
293
|
-
}).processedContent
|
|
294
|
-
}
|
|
295
|
-
if (newConfig.yAxis?.label) {
|
|
296
|
-
processedYAxis = processMarkupVariables(newConfig.yAxis.label, data || [], newConfig.markupVariables, {
|
|
297
|
-
isEditor,
|
|
298
|
-
filters: newConfig.filters || []
|
|
299
|
-
}).processedContent
|
|
300
|
-
}
|
|
301
|
-
}
|
|
333
|
+
const { processedXAxis, processedYAxis, runtimeXAxisLabel, runtimeYAxisLabel, isHorizontalVariant } =
|
|
334
|
+
getProcessedAxisLabels(newConfig, data || [])
|
|
302
335
|
|
|
303
336
|
// Deeper copy
|
|
304
337
|
Object.keys(defaults).forEach(key => {
|
|
@@ -323,6 +356,14 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
323
356
|
}
|
|
324
357
|
|
|
325
358
|
//Enforce default values that need to be calculated at runtime
|
|
359
|
+
// Preserve error messages that were set outside of updateConfig (e.g., from pattern settings)
|
|
360
|
+
const existingErrorMessage = _config.runtime?.editorErrorMessage || ''
|
|
361
|
+
const isPieChartValidationError =
|
|
362
|
+
existingErrorMessage === 'Data column section must be set for pie charts.' ||
|
|
363
|
+
existingErrorMessage === 'Segment labels section must be set for pie charts.' ||
|
|
364
|
+
existingErrorMessage === 'Data column and Segment labels sections must be set for pie charts.'
|
|
365
|
+
const shouldPreserveError = existingErrorMessage && !isPieChartValidationError
|
|
366
|
+
|
|
326
367
|
newConfig.runtime = {} as Runtime
|
|
327
368
|
newConfig.runtime.series = _.cloneDeep(newConfig.series)
|
|
328
369
|
newConfig.runtime.seriesLabels = {}
|
|
@@ -384,6 +425,18 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
384
425
|
newConfig.runtime.forecastingSeriesKeys.push(series)
|
|
385
426
|
}
|
|
386
427
|
})
|
|
428
|
+
|
|
429
|
+
// Default to date scaling type for Forecasting charts
|
|
430
|
+
if (newConfig.xAxis.type === 'categorical') {
|
|
431
|
+
newConfig.xAxis.type = 'date'
|
|
432
|
+
// Initialize date parsing formats if they don't exist
|
|
433
|
+
if (!newConfig.xAxis.dateParseFormat) {
|
|
434
|
+
newConfig.xAxis.dateParseFormat = '%Y-%m-%d'
|
|
435
|
+
}
|
|
436
|
+
if (!newConfig.xAxis.dateDisplayFormat) {
|
|
437
|
+
newConfig.xAxis.dateDisplayFormat = '%Y-%m-%d'
|
|
438
|
+
}
|
|
439
|
+
}
|
|
387
440
|
}
|
|
388
441
|
|
|
389
442
|
if (newConfig.visualizationType === 'Area Chart' && newConfig.series) {
|
|
@@ -395,19 +448,17 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
395
448
|
newConfig.visualizationSubType = 'stacked'
|
|
396
449
|
}
|
|
397
450
|
|
|
398
|
-
if (
|
|
399
|
-
((newConfig.visualizationType === 'Bar' || newConfig.visualizationType === 'Box Plot') &&
|
|
400
|
-
newConfig.orientation === 'horizontal') ||
|
|
401
|
-
['Deviation Bar', 'Paired Bar', 'Forest Plot'].includes(newConfig.visualizationType)
|
|
402
|
-
) {
|
|
451
|
+
if (isHorizontalVariant) {
|
|
403
452
|
// For horizontal charts, axes are swapped, so processedYAxis goes to runtime.xAxis and vice versa
|
|
453
|
+
const horizontalXAxisSource = _.cloneDeep((newConfig.yAxis as any)?.yAxis || newConfig.yAxis)
|
|
454
|
+
const horizontalYAxisSource = _.cloneDeep((newConfig.xAxis as any)?.xAxis || newConfig.xAxis)
|
|
404
455
|
newConfig.runtime.xAxis = {
|
|
405
|
-
...
|
|
406
|
-
label:
|
|
456
|
+
...horizontalXAxisSource,
|
|
457
|
+
label: runtimeXAxisLabel ?? horizontalXAxisSource?.label
|
|
407
458
|
}
|
|
408
459
|
newConfig.runtime.yAxis = {
|
|
409
|
-
...
|
|
410
|
-
label:
|
|
460
|
+
...horizontalYAxisSource,
|
|
461
|
+
label: runtimeYAxisLabel ?? horizontalYAxisSource?.label
|
|
411
462
|
}
|
|
412
463
|
newConfig.runtime.yAxis.labelOffset *= -1
|
|
413
464
|
|
|
@@ -419,24 +470,40 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
419
470
|
['Scatter Plot', 'Area Chart', 'Line', 'Forecasting'].includes(newConfig.visualizationType) &&
|
|
420
471
|
!convertLineToBarGraph
|
|
421
472
|
) {
|
|
422
|
-
newConfig.runtime.xAxis = { ...newConfig.xAxis, label:
|
|
423
|
-
newConfig.runtime.yAxis = { ...newConfig.yAxis, label:
|
|
473
|
+
newConfig.runtime.xAxis = { ...newConfig.xAxis, label: runtimeXAxisLabel ?? newConfig.xAxis.label }
|
|
474
|
+
newConfig.runtime.yAxis = { ...newConfig.yAxis, label: runtimeYAxisLabel ?? newConfig.yAxis.label }
|
|
424
475
|
newConfig.runtime.horizontal = false
|
|
425
476
|
newConfig.orientation = 'vertical'
|
|
426
477
|
} else {
|
|
427
|
-
newConfig.runtime.xAxis = { ...newConfig.xAxis, label:
|
|
428
|
-
newConfig.runtime.yAxis = { ...newConfig.yAxis, label:
|
|
478
|
+
newConfig.runtime.xAxis = { ...newConfig.xAxis, label: runtimeXAxisLabel ?? newConfig.xAxis.label }
|
|
479
|
+
newConfig.runtime.yAxis = { ...newConfig.yAxis, label: runtimeYAxisLabel ?? newConfig.yAxis.label }
|
|
429
480
|
newConfig.runtime.horizontal = false
|
|
430
481
|
}
|
|
431
482
|
|
|
432
483
|
newConfig.runtime.uniqueId = Date.now()
|
|
433
|
-
newConfig.runtime.editorErrorMessage =
|
|
434
|
-
newConfig.visualizationType === 'Pie' && !newConfig.yAxis.dataKey
|
|
435
|
-
? 'Data Key property in Y Axis section must be set for pie charts.'
|
|
436
|
-
: ''
|
|
437
484
|
|
|
438
|
-
//
|
|
439
|
-
|
|
485
|
+
// Set error messages: preserve external errors (from pattern settings, etc.) or set validation errors
|
|
486
|
+
if (shouldPreserveError) {
|
|
487
|
+
// Preserve error messages set by editor panels (e.g., pattern contrast errors)
|
|
488
|
+
newConfig.runtime.editorErrorMessage = existingErrorMessage
|
|
489
|
+
} else if (newConfig.visualizationType === 'Pie') {
|
|
490
|
+
// Check for Pie chart validation errors
|
|
491
|
+
const missingDataColumn = !newConfig.yAxis.dataKey || newConfig.yAxis.dataKey === ''
|
|
492
|
+
const missingSegmentLabels = !newConfig.xAxis.dataKey || newConfig.xAxis.dataKey === ''
|
|
493
|
+
|
|
494
|
+
if (missingDataColumn && missingSegmentLabels) {
|
|
495
|
+
newConfig.runtime.editorErrorMessage = 'Data column and Segment labels sections must be set for pie charts.'
|
|
496
|
+
} else if (missingDataColumn) {
|
|
497
|
+
newConfig.runtime.editorErrorMessage = 'Data column section must be set for pie charts.'
|
|
498
|
+
} else if (missingSegmentLabels) {
|
|
499
|
+
newConfig.runtime.editorErrorMessage = 'Segment labels section must be set for pie charts.'
|
|
500
|
+
} else {
|
|
501
|
+
newConfig.runtime.editorErrorMessage = ''
|
|
502
|
+
}
|
|
503
|
+
} else {
|
|
504
|
+
// No errors
|
|
505
|
+
newConfig.runtime.editorErrorMessage = ''
|
|
506
|
+
}
|
|
440
507
|
|
|
441
508
|
if (newConfig.legend.seriesHighlight?.length) {
|
|
442
509
|
dispatch({ type: 'SET_SERIES_HIGHLIGHT', payload: newConfig.legend?.seriesHighlight })
|
|
@@ -492,11 +559,13 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
492
559
|
for (let entry of entries) {
|
|
493
560
|
let { width, height } = entry.contentRect
|
|
494
561
|
|
|
495
|
-
|
|
562
|
+
const editorIsOpen = isEditor && !!document.querySelector('.editor-panel:not(.hidden)')
|
|
563
|
+
width = editorIsOpen ? width - EDITOR_WIDTH : width
|
|
496
564
|
|
|
497
565
|
const newViewport = getViewport(width)
|
|
498
566
|
|
|
499
567
|
dispatch({ type: 'SET_VIEWPORT', payload: newViewport })
|
|
568
|
+
dispatch({ type: 'SET_VIZ_VIEWPORT', payload: newViewport })
|
|
500
569
|
|
|
501
570
|
if (entry.target.dataset.lollipop === 'true') {
|
|
502
571
|
width = width - 2.5
|
|
@@ -654,6 +723,49 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
654
723
|
}
|
|
655
724
|
}, [config, stateData]) // eslint-disable-line
|
|
656
725
|
|
|
726
|
+
// Updates runtime axis labels when config or data changes when using markup variables
|
|
727
|
+
useEffect(() => {
|
|
728
|
+
if (
|
|
729
|
+
!config?.runtime ||
|
|
730
|
+
_.isEmpty(config.runtime) ||
|
|
731
|
+
(!config.runtime.xAxis && !config.runtime.yAxis) ||
|
|
732
|
+
!config.markupVariables?.length
|
|
733
|
+
) {
|
|
734
|
+
return
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const dataSource = (stateData && stateData.length ? stateData : config.data) || []
|
|
738
|
+
const { runtimeXAxisLabel, runtimeYAxisLabel, isHorizontalVariant } = getProcessedAxisLabels(config, dataSource)
|
|
739
|
+
|
|
740
|
+
const runtimeClone = _.cloneDeep(config.runtime)
|
|
741
|
+
|
|
742
|
+
if (!runtimeClone?.xAxis || !runtimeClone?.yAxis) {
|
|
743
|
+
return
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
let shouldUpdateLabels = false
|
|
747
|
+
|
|
748
|
+
if (typeof runtimeXAxisLabel !== 'undefined' && runtimeClone.xAxis.label !== runtimeXAxisLabel) {
|
|
749
|
+
runtimeClone.xAxis = { ...runtimeClone.xAxis, label: runtimeXAxisLabel }
|
|
750
|
+
shouldUpdateLabels = true
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (typeof runtimeYAxisLabel !== 'undefined' && runtimeClone.yAxis.label !== runtimeYAxisLabel) {
|
|
754
|
+
runtimeClone.yAxis = { ...runtimeClone.yAxis, label: runtimeYAxisLabel }
|
|
755
|
+
shouldUpdateLabels = true
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if (shouldUpdateLabels) {
|
|
759
|
+
runtimeClone.uniqueId = Date.now()
|
|
760
|
+
const updatedConfig = { ...config, runtime: runtimeClone } as ChartConfig
|
|
761
|
+
dispatch({ type: 'SET_CONFIG', payload: updatedConfig })
|
|
762
|
+
|
|
763
|
+
if (isEditor && !isDashboard) {
|
|
764
|
+
editorContext.setTempConfig(updatedConfig)
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}, [config, stateData, getProcessedAxisLabels, dispatch, editorContext, isEditor, isDashboard])
|
|
768
|
+
|
|
657
769
|
// Called on legend click, highlights/unhighlights the data series with the given label
|
|
658
770
|
const highlight = (label: Label): void => {
|
|
659
771
|
if (
|
|
@@ -769,7 +881,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
769
881
|
rightSuffix,
|
|
770
882
|
bottomPrefix,
|
|
771
883
|
bottomSuffix,
|
|
772
|
-
bottomAbbreviated
|
|
884
|
+
bottomAbbreviated,
|
|
885
|
+
preserveOriginalDecimals
|
|
773
886
|
}
|
|
774
887
|
} = config
|
|
775
888
|
|
|
@@ -788,32 +901,52 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
788
901
|
} else {
|
|
789
902
|
roundToPlace = roundTo ? Number(roundTo) : 0
|
|
790
903
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
904
|
+
|
|
905
|
+
// If preserveOriginalDecimals is enabled, don't force decimal places
|
|
906
|
+
if (preserveOriginalDecimals) {
|
|
907
|
+
stringFormattingOptions = {
|
|
908
|
+
useGrouping: addColRoundTo ? true : config.dataFormat.commas ? true : false
|
|
909
|
+
}
|
|
910
|
+
} else {
|
|
911
|
+
stringFormattingOptions = {
|
|
912
|
+
useGrouping: addColRoundTo ? true : config.dataFormat.commas ? true : false,
|
|
913
|
+
minimumFractionDigits: roundToPlace,
|
|
914
|
+
maximumFractionDigits: roundToPlace
|
|
915
|
+
}
|
|
795
916
|
}
|
|
796
917
|
}
|
|
797
918
|
|
|
798
919
|
if (axis === 'right') {
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
920
|
+
if (preserveOriginalDecimals) {
|
|
921
|
+
stringFormattingOptions = {
|
|
922
|
+
useGrouping: config.dataFormat.rightCommas ? true : false
|
|
923
|
+
}
|
|
924
|
+
} else {
|
|
925
|
+
stringFormattingOptions = {
|
|
926
|
+
useGrouping: config.dataFormat.rightCommas ? true : false,
|
|
927
|
+
minimumFractionDigits: rightRoundTo ? Number(rightRoundTo) : 0,
|
|
928
|
+
maximumFractionDigits: rightRoundTo ? Number(rightRoundTo) : 0
|
|
929
|
+
}
|
|
803
930
|
}
|
|
804
931
|
}
|
|
805
932
|
|
|
806
933
|
const resolveBottomTickRounding = () => {
|
|
807
|
-
if (config.forestPlot
|
|
934
|
+
if (config.forestPlot?.type === 'Logarithmic' && !bottomRoundTo) return 2
|
|
808
935
|
if (Number(bottomRoundTo)) return Number(bottomRoundTo)
|
|
809
936
|
return 0
|
|
810
937
|
}
|
|
811
938
|
|
|
812
939
|
if (axis === 'bottom') {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
940
|
+
if (preserveOriginalDecimals) {
|
|
941
|
+
stringFormattingOptions = {
|
|
942
|
+
useGrouping: config.dataFormat.bottomCommas ? true : false
|
|
943
|
+
}
|
|
944
|
+
} else {
|
|
945
|
+
stringFormattingOptions = {
|
|
946
|
+
useGrouping: config.dataFormat.bottomCommas ? true : false,
|
|
947
|
+
minimumFractionDigits: resolveBottomTickRounding(),
|
|
948
|
+
maximumFractionDigits: resolveBottomTickRounding()
|
|
949
|
+
}
|
|
817
950
|
}
|
|
818
951
|
}
|
|
819
952
|
|
|
@@ -1008,9 +1141,6 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1008
1141
|
{isEditor && <EditorPanel datasets={datasets} />}
|
|
1009
1142
|
<Layout.Responsive isEditor={isEditor}>
|
|
1010
1143
|
{config.newViz && <Confirm updateConfig={updateConfig} config={config} />}
|
|
1011
|
-
{undefined === config.newViz && isEditor && config.runtime && config.runtime?.editorErrorMessage && (
|
|
1012
|
-
<Error errorMessage={config.runtime.editorErrorMessage} />
|
|
1013
|
-
)}
|
|
1014
1144
|
{!missingRequiredSections(config) && !config.newViz && (
|
|
1015
1145
|
<div
|
|
1016
1146
|
className={`cdc-chart-inner-container cove-component__content type-${makeClassName(
|
|
@@ -1029,6 +1159,14 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1029
1159
|
config={config}
|
|
1030
1160
|
/>
|
|
1031
1161
|
|
|
1162
|
+
{/* Error Message Display - Show at top before visualization wrapper */}
|
|
1163
|
+
{/* {(() => {
|
|
1164
|
+
const errorMessage = config.runtime?.editorErrorMessage
|
|
1165
|
+
const hasError = errorMessage && typeof errorMessage === 'string' && errorMessage.trim() !== ''
|
|
1166
|
+
const shouldShow = undefined === config.newViz && isEditor && config.runtime && hasError
|
|
1167
|
+
return shouldShow ? <Error errorMessage={errorMessage} /> : null
|
|
1168
|
+
})()} */}
|
|
1169
|
+
|
|
1032
1170
|
{/* Visualization Wrapper */}
|
|
1033
1171
|
<div className={getChartWrapperClasses().join(' ')}>
|
|
1034
1172
|
{/* Intro Text/Message */}
|
|
@@ -1067,18 +1205,27 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1067
1205
|
: 'w-75'
|
|
1068
1206
|
}
|
|
1069
1207
|
>
|
|
1070
|
-
{/*
|
|
1071
|
-
{!
|
|
1072
|
-
<div
|
|
1073
|
-
|
|
1074
|
-
{parent => (
|
|
1075
|
-
<LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
|
|
1076
|
-
)}
|
|
1077
|
-
</ParentSize>
|
|
1208
|
+
{/* Check if there is data to display */}
|
|
1209
|
+
{(!filteredData || filteredData.length === 0) && (
|
|
1210
|
+
<div className='no-data-message' style={{ padding: '2rem', textAlign: 'center', color: '#666' }}>
|
|
1211
|
+
{config.chartMessage?.noData || 'No Data Available'}
|
|
1078
1212
|
</div>
|
|
1079
1213
|
)}
|
|
1080
1214
|
|
|
1081
|
-
{
|
|
1215
|
+
{/* All charts with LinearChart */}
|
|
1216
|
+
{filteredData &&
|
|
1217
|
+
filteredData.length > 0 &&
|
|
1218
|
+
!['Spark Line', 'Line', 'Sankey', 'Pie', 'Sankey'].includes(config.visualizationType) && (
|
|
1219
|
+
<div ref={parentRef} style={{ width: `100%` }}>
|
|
1220
|
+
<ParentSize>
|
|
1221
|
+
{parent => (
|
|
1222
|
+
<LinearChart ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
|
|
1223
|
+
)}
|
|
1224
|
+
</ParentSize>
|
|
1225
|
+
</div>
|
|
1226
|
+
)}
|
|
1227
|
+
|
|
1228
|
+
{filteredData && filteredData.length > 0 && config.visualizationType === 'Pie' && (
|
|
1082
1229
|
<ParentSize className='justify-content-center d-flex' style={{ width: `100%` }}>
|
|
1083
1230
|
{parent => (
|
|
1084
1231
|
<PieChart
|
|
@@ -1091,7 +1238,9 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1091
1238
|
</ParentSize>
|
|
1092
1239
|
)}
|
|
1093
1240
|
{/* Line Chart */}
|
|
1094
|
-
{
|
|
1241
|
+
{filteredData &&
|
|
1242
|
+
filteredData.length > 0 &&
|
|
1243
|
+
config.visualizationType === 'Line' &&
|
|
1095
1244
|
(convertLineToBarGraph ? (
|
|
1096
1245
|
<div ref={parentRef} style={{ width: `100%` }}>
|
|
1097
1246
|
<ParentSize>
|
|
@@ -1202,34 +1351,51 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1202
1351
|
config.table.show &&
|
|
1203
1352
|
config.visualizationType !== 'Spark Line' &&
|
|
1204
1353
|
config.visualizationType !== 'Sankey') ||
|
|
1205
|
-
(config.visualizationType === 'Sankey' && config.table.show)) &&
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
rawData={
|
|
1354
|
+
(config.visualizationType === 'Sankey' && config.table.show)) &&
|
|
1355
|
+
(() => {
|
|
1356
|
+
let dataTableConfig = pivotDynamicSeries(config)
|
|
1357
|
+
let dataTableColumns = config.columns
|
|
1358
|
+
let dataTableRuntimeData = getTableRuntimeData()
|
|
1359
|
+
let dataTableRawData =
|
|
1212
1360
|
config.visualizationType === 'Sankey'
|
|
1213
1361
|
? config?.data?.[0]?.tableData
|
|
1214
1362
|
: config.table.customTableConfig
|
|
1215
1363
|
? filterVizData(config.filters, config.data)
|
|
1216
1364
|
: config.data
|
|
1365
|
+
|
|
1366
|
+
if (config.smallMultiples?.mode) {
|
|
1367
|
+
const prepared = prepareSmallMultiplesDataTable(config, config.columns, dataTableRuntimeData)
|
|
1368
|
+
dataTableConfig = prepared.config
|
|
1369
|
+
dataTableColumns = prepared.columns
|
|
1370
|
+
dataTableRuntimeData = prepared.runtimeData
|
|
1371
|
+
if (config.smallMultiples.mode === 'by-column') {
|
|
1372
|
+
dataTableRawData = prepared.config.data
|
|
1373
|
+
}
|
|
1217
1374
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1375
|
+
|
|
1376
|
+
return (
|
|
1377
|
+
<DataTable
|
|
1378
|
+
/* changing the "key" will force the table to re-render
|
|
1379
|
+
when the default sort changes while editing */
|
|
1380
|
+
key={dataTableDefaultSortBy}
|
|
1381
|
+
config={dataTableConfig}
|
|
1382
|
+
rawData={dataTableRawData}
|
|
1383
|
+
runtimeData={dataTableRuntimeData}
|
|
1384
|
+
expandDataTable={config.table.expanded}
|
|
1385
|
+
columns={dataTableColumns}
|
|
1386
|
+
defaultSortBy={dataTableDefaultSortBy}
|
|
1387
|
+
displayGeoName={name => name}
|
|
1388
|
+
applyLegendToRow={applyLegendToRow}
|
|
1389
|
+
tableTitle={config.table.label}
|
|
1390
|
+
indexTitle={config.table.indexLabel}
|
|
1391
|
+
vizTitle={title}
|
|
1392
|
+
viewport={currentViewport}
|
|
1393
|
+
tabbingId={handleChartTabbing(config, legendId)}
|
|
1394
|
+
colorScale={colorScale}
|
|
1395
|
+
interactionLabel={interactionLabel}
|
|
1396
|
+
/>
|
|
1397
|
+
)
|
|
1398
|
+
})()}
|
|
1233
1399
|
{config?.annotations?.length > 0 && <Annotation.Dropdown />}
|
|
1234
1400
|
{/* show pdf or image button */}
|
|
1235
1401
|
{processedLegacyFootnotes && (
|
|
@@ -1239,6 +1405,9 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1239
1405
|
<FootnotesStandAlone
|
|
1240
1406
|
config={configObj.footnotes}
|
|
1241
1407
|
filters={config.filters?.filter(f => f.filterFootnotes)}
|
|
1408
|
+
markupVariables={config.markupVariables}
|
|
1409
|
+
enableMarkupVariables={config.enableMarkupVariables}
|
|
1410
|
+
data={config.data}
|
|
1242
1411
|
/>
|
|
1243
1412
|
</div>
|
|
1244
1413
|
)}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// generate a combo chart story
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { Meta, Story } from '@storybook/react'
|
|
4
|
+
import CdcChart from '@cdc/chart/src/CdcChart'
|
|
5
|
+
import comboChartConfig from './_mock/combo.json'
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: 'Components/Templates/Chart/Combo Chart',
|
|
9
|
+
component: CdcChart
|
|
10
|
+
} as Meta
|
|
11
|
+
|
|
12
|
+
const Template: Story = args => <CdcChart {...args} />
|
|
13
|
+
|
|
14
|
+
export const ComboChart = Template.bind({})
|
|
15
|
+
ComboChart.args = {
|
|
16
|
+
config: comboChartConfig,
|
|
17
|
+
isEditor: true
|
|
18
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
|
|
3
|
+
import Chart from '../CdcChart'
|
|
4
|
+
import forecastComboWithGaps from './_mock/forecast_combo_with_gaps.json'
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Chart> = {
|
|
7
|
+
title: 'Components/Templates/Chart/Forecast Chart',
|
|
8
|
+
component: Chart,
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
'Forecast charts with gap handling. This story demonstrates how forecast charts properly handle gaps in data by filtering invalid values (NA) and splitting segments at gaps.'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type Story = StoryObj<typeof Chart>
|
|
20
|
+
|
|
21
|
+
export const Forecast_Combo_With_Gaps: Story = {
|
|
22
|
+
args: {
|
|
23
|
+
config: forecastComboWithGaps,
|
|
24
|
+
isEditor: true
|
|
25
|
+
},
|
|
26
|
+
parameters: {
|
|
27
|
+
docs: {
|
|
28
|
+
description: {
|
|
29
|
+
story:
|
|
30
|
+
'A combo chart with forecast confidence intervals that demonstrates proper gap handling. The forecast areas and lines are split into separate segments when gaps are detected, preventing misleading connections across missing data periods. Invalid data points (NA values) are automatically filtered out.'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default meta
|