@cdc/chart 4.24.10 → 4.24.12-2

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 (94) hide show
  1. package/dist/cdcchart.js +35019 -34301
  2. package/examples/feature/boxplot/boxplot-data.json +88 -22
  3. package/examples/feature/boxplot/boxplot.json +540 -16
  4. package/examples/feature/boxplot/testing.csv +7 -7
  5. package/examples/feature/sankey/sankey-example-data.json +126 -14
  6. package/examples/feature/tests-date-exclusions/date-exclusions-config.json +372 -12
  7. package/examples/private/DEV-8850-2.json +493 -0
  8. package/examples/private/DEV-9822.json +574 -0
  9. package/examples/private/DEV-9840.json +553 -0
  10. package/examples/private/DEV-9850-3.json +461 -0
  11. package/examples/private/chart.json +1084 -0
  12. package/examples/private/ci_formatted.json +202 -0
  13. package/examples/private/ci_issue.json +3016 -0
  14. package/examples/private/completed.json +634 -0
  15. package/examples/private/dem-data-long.csv +20 -0
  16. package/examples/private/dem-data-long.json +36 -0
  17. package/examples/private/demographic_data.csv +157 -0
  18. package/examples/private/demographic_data.json +2654 -0
  19. package/examples/private/demographic_dynamic.json +443 -0
  20. package/examples/private/demographic_standard.json +560 -0
  21. package/examples/private/ehdi.json +29939 -0
  22. package/examples/private/test.json +493 -0
  23. package/index.html +10 -7
  24. package/package.json +2 -2
  25. package/src/CdcChart.tsx +132 -152
  26. package/src/_stories/Chart.Anchors.stories.tsx +31 -0
  27. package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
  28. package/src/_stories/Chart.DynamicSeries.stories.tsx +34 -0
  29. package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
  30. package/src/_stories/Chart.stories.tsx +37 -6
  31. package/src/_stories/ChartAxisLabels.stories.tsx +4 -1
  32. package/src/_stories/ChartEditor.stories.tsx +27 -0
  33. package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
  34. package/src/_stories/ChartPrefixSuffix.stories.tsx +8 -0
  35. package/{examples/feature/area/area-chart-date-city-temperature.json → src/_stories/_mock/area_chart_stacked.json} +125 -27
  36. package/src/_stories/_mock/boxplot_multiseries.json +647 -0
  37. package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
  38. package/src/_stories/_mock/dynamic_series_config.json +979 -0
  39. package/src/_stories/_mock/line_chart_dynamic_ci.json +493 -0
  40. package/src/_stories/_mock/line_chart_non_dynamic_ci.json +522 -0
  41. package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
  42. package/src/_stories/_mock/short_dates.json +288 -0
  43. package/src/_stories/_mock/suppression_mock.json +1549 -0
  44. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +15 -3
  45. package/src/components/Axis/Categorical.Axis.tsx +2 -2
  46. package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -37
  47. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
  48. package/src/components/BarChart/components/BarChart.Vertical.tsx +53 -47
  49. package/src/components/BarChart/helpers/getBarData.ts +28 -0
  50. package/src/components/BarChart/helpers/index.ts +1 -2
  51. package/src/components/BarChart/helpers/tests/getBarData.test.ts +74 -0
  52. package/src/components/BoxPlot/BoxPlot.tsx +131 -0
  53. package/src/components/BoxPlot/helpers/index.ts +54 -0
  54. package/src/components/BrushChart.tsx +23 -26
  55. package/src/components/EditorPanel/EditorPanel.tsx +117 -139
  56. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +3 -3
  57. package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
  58. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
  59. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
  60. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +122 -56
  61. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
  62. package/src/components/EditorPanel/useEditorPermissions.ts +20 -2
  63. package/src/components/Legend/Legend.Component.tsx +11 -12
  64. package/src/components/Legend/Legend.tsx +16 -16
  65. package/src/components/Legend/helpers/getLegendClasses.ts +59 -0
  66. package/src/components/Legend/helpers/index.ts +2 -1
  67. package/src/components/Legend/tests/getLegendClasses.test.ts +115 -0
  68. package/src/components/LineChart/components/LineChart.Circle.tsx +1 -1
  69. package/src/components/LineChart/helpers.ts +49 -43
  70. package/src/components/LineChart/index.tsx +135 -83
  71. package/src/components/LinearChart.tsx +196 -181
  72. package/src/components/PieChart/PieChart.tsx +7 -1
  73. package/src/components/Sankey/components/ColumnList.tsx +19 -0
  74. package/src/components/Sankey/components/Sankey.tsx +479 -0
  75. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +33 -0
  76. package/src/components/Sankey/index.tsx +1 -492
  77. package/src/components/Sankey/sankey.scss +22 -21
  78. package/src/components/Sankey/types/index.ts +1 -1
  79. package/src/components/Sankey/useSankeyAlert.tsx +60 -0
  80. package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
  81. package/src/data/initial-state.js +7 -12
  82. package/src/helpers/countNumOfTicks.ts +57 -0
  83. package/src/helpers/getQuartiles.ts +15 -18
  84. package/src/hooks/useMinMax.ts +44 -16
  85. package/src/hooks/useReduceData.ts +43 -10
  86. package/src/hooks/useScales.ts +90 -35
  87. package/src/hooks/useTooltip.tsx +59 -50
  88. package/src/scss/DataTable.scss +5 -0
  89. package/src/scss/main.scss +6 -20
  90. package/src/types/ChartConfig.ts +6 -19
  91. package/src/types/ChartContext.ts +4 -1
  92. package/src/types/ForestPlot.ts +8 -0
  93. package/src/components/BoxPlot/BoxPlot.jsx +0 -111
  94. package/src/hooks/useLegendClasses.ts +0 -72
package/src/CdcChart.tsx CHANGED
@@ -35,7 +35,6 @@ import EditorPanel from './components/EditorPanel'
35
35
  import { abbreviateNumber } from './helpers/abbreviateNumber'
36
36
  import { handleChartTabbing } from './helpers/handleChartTabbing'
37
37
  import { getQuartiles } from './helpers/getQuartiles'
38
- import { sortAsc, sortDesc } from './helpers/sort'
39
38
  import { handleChartAriaLabels } from './helpers/handleChartAriaLabels'
40
39
  import { lineOptions } from './helpers/lineOptions'
41
40
  import { handleLineType } from './helpers/handleLineType'
@@ -55,17 +54,16 @@ import { DataTransform } from '@cdc/core/helpers/DataTransform'
55
54
  import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
56
55
  import isNumber from '@cdc/core/helpers/isNumber'
57
56
  import coveUpdateWorker from '@cdc/core/helpers/coveUpdateWorker'
58
- import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
59
57
  import { isConvertLineToBarGraph } from './helpers/isConvertLineToBarGraph'
60
- import { calcInitialHeight } from './helpers/sizeHelpers'
61
- import { isLegendWrapViewport, isMobileHeightViewport } from '@cdc/core/helpers/viewports'
58
+ import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
62
59
 
63
60
  import './scss/main.scss'
64
61
  // load both then config below determines which to use
65
62
  import DataTable from '@cdc/core/components/DataTable'
63
+ import type { TableConfig } from '@cdc/core/components/DataTable/types/TableConfig'
66
64
  import { getFileExtension } from '@cdc/core/helpers/getFileExtension'
67
65
  import Title from '@cdc/core/components/ui/Title'
68
- import { ChartConfig } from './types/ChartConfig'
66
+ import { AllChartsConfig, ChartConfig } from './types/ChartConfig'
69
67
  import { Label } from './types/Label'
70
68
  import { type ViewportSize } from './types/ChartConfig'
71
69
  import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
@@ -74,6 +72,8 @@ import { filterVizData } from '@cdc/core/helpers/filterVizData'
74
72
  import LegendWrapper from './components/LegendWrapper'
75
73
  import _ from 'lodash'
76
74
  import { addValuesToFilters } from '@cdc/core/helpers/addValuesToFilters'
75
+ import { Runtime } from '@cdc/core/types/Runtime'
76
+ import { Pivot } from '@cdc/core/types/Table'
77
77
 
78
78
  interface CdcChartProps {
79
79
  configUrl?: string
@@ -129,14 +129,6 @@ const CdcChart = ({
129
129
  })
130
130
 
131
131
  const { description, visualizationType } = config
132
- let [width] = dimensions
133
- const useVertical = config.orientation === 'vertical'
134
- const useMobileVertical = config.heights?.mobileVertical && ['xs', 'xxs'].includes(currentViewport)
135
- const responsiveVertical = useMobileVertical ? 'mobileVertical' : 'vertical'
136
- const renderedOrientation = useVertical ? responsiveVertical : 'horizontal'
137
- let height = config.aspectRatio ? width * config.aspectRatio : config?.heights?.[renderedOrientation]
138
- if (config.visualizationType === 'Pie') height = config?.heights?.[renderedOrientation]
139
- height = height + Number(config.orientation === 'horizontal' ? config.yAxis.size : config?.xAxis?.size) + 45
140
132
 
141
133
  const legendRef = useRef(null)
142
134
  const parentRef = useRef(null)
@@ -300,9 +292,6 @@ const CdcChart = ({
300
292
  }
301
293
  let newConfig = { ...defaults, ...response }
302
294
 
303
- if (newConfig.visualizationType === 'Box Plot') {
304
- newConfig.legend.hide = true
305
- }
306
295
  if (undefined === newConfig.table.show) newConfig.table.show = !isDashboard
307
296
 
308
297
  newConfig.series.forEach(series => {
@@ -321,7 +310,7 @@ const CdcChart = ({
321
310
  updateConfig(processedConfig, data)
322
311
  }
323
312
 
324
- const updateConfig = (_config, dataOverride?: any[]) => {
313
+ const updateConfig = (_config: AllChartsConfig, dataOverride?: any[]) => {
325
314
  const newConfig = _.cloneDeep(_config)
326
315
  let data = dataOverride || stateData
327
316
 
@@ -384,15 +373,15 @@ const CdcChart = ({
384
373
  }
385
374
 
386
375
  //Enforce default values that need to be calculated at runtime
387
- newConfig.runtime = {}
388
- newConfig.runtime.series = newConfig.dynamicSeries ? [] : newConfig.series
376
+ newConfig.runtime = {} as Runtime
377
+ newConfig.runtime.series = newConfig.dynamicSeries ? [] : _.cloneDeep(newConfig.series)
389
378
  newConfig.runtime.seriesLabels = {}
390
379
  newConfig.runtime.seriesLabelsAll = []
391
380
  newConfig.runtime.originalXAxis = newConfig.xAxis
392
381
 
393
382
  if (newConfig.dynamicSeries) {
394
383
  let finalData = dataOverride || newConfig.formattedData || newConfig.data
395
- if (finalData && finalData.length && finalData.length > 0) {
384
+ if (finalData?.length) {
396
385
  Object.keys(finalData[0]).forEach(seriesKey => {
397
386
  if (
398
387
  seriesKey !== newConfig.xAxis.dataKey &&
@@ -414,113 +403,104 @@ const CdcChart = ({
414
403
  newConfig.runtime.seriesKeys = (dataOverride || data).map(d => d[newConfig.xAxis.dataKey])
415
404
  newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
416
405
  } else {
417
- newConfig.runtime.seriesKeys = newConfig.runtime.series
418
- ? newConfig.runtime.series.map(series => {
419
- newConfig.runtime.seriesLabels[series.dataKey] = series.name || series.label || series.dataKey
420
- newConfig.runtime.seriesLabelsAll.push(series.name || series.dataKey)
421
- return series.dataKey
406
+ const finalData = dataOverride || newConfig.formattedData || newConfig.data
407
+ newConfig.runtime.seriesKeys = (newConfig.runtime.series || []).flatMap(series => {
408
+ if (series.dynamicCategory) {
409
+ _.remove(newConfig.runtime.seriesLabelsAll, label => label === series.dataKey)
410
+ _.remove(newConfig.runtime.series, s => s.dataKey === series.dataKey)
411
+ // grab the dynamic series keys from the data
412
+ const seriesKeys: string[] = _.uniq(finalData.map(d => d[series.dynamicCategory]))
413
+ // for each of those keys perform side effects
414
+ seriesKeys.forEach(dataKey => {
415
+ newConfig.runtime.seriesLabels[dataKey] = dataKey
416
+ newConfig.runtime.seriesLabelsAll.push(dataKey)
417
+ newConfig.runtime.series.push({
418
+ dataKey,
419
+ type: series.type,
420
+ lineType: series.lineType,
421
+ originalDataKey: series.dataKey,
422
+ dynamicCategory: series.dynamicCategory,
423
+ tooltip: true
424
+ })
422
425
  })
423
- : []
426
+ // return the series keys
427
+ return seriesKeys
428
+ } else {
429
+ newConfig.runtime.seriesLabels[series.dataKey] = series.name || series.label || series.dataKey
430
+ newConfig.runtime.seriesLabelsAll.push(series.name || series.dataKey)
431
+ // return the series keys
432
+ return [series.dataKey]
433
+ }
434
+ })
424
435
  }
425
436
 
426
437
  if (newConfig.visualizationType === 'Box Plot' && newConfig.series) {
427
- let allKeys = newExcludedData
428
- ? newExcludedData.map(d => d[newConfig.xAxis.dataKey])
429
- : data.map(d => d[newConfig.xAxis.dataKey])
430
- let allValues = newExcludedData
431
- ? newExcludedData.map(d => Number(d[newConfig?.series[0]?.dataKey]))
432
- : data.map(d => Number(d[newConfig?.series[0]?.dataKey]))
433
-
434
- const uniqueArray = function (arrArg) {
435
- return arrArg.filter(function (elem, pos, arr) {
436
- return arr.indexOf(elem) === pos
437
- })
438
- }
439
-
440
- const groups = uniqueArray(allKeys)
441
- let tableData: any[] = []
438
+ const combinedData = filteredData || data
439
+ const groups = _.uniq(_.map(combinedData, newConfig.xAxis.dataKey))
440
+ const seriesKeys = _.map(newConfig.series, 'dataKey')
442
441
  const plots: any[] = []
443
442
 
444
- // group specific statistics
445
- // prevent re-renders
446
- if (!groups) return
447
443
  groups.forEach(g => {
448
- try {
449
- if (!g) throw new Error('No groups resolved in box plots')
450
-
451
- // filter data by group
452
- let filteredData = newExcludedData
453
- ? newExcludedData.filter(item => item[newConfig.xAxis.dataKey] === g)
454
- : data.filter(item => item[newConfig.xAxis.dataKey] === g)
455
- let filteredDataValues: number[] = filteredData.map(item => Number(item[newConfig?.series[0]?.dataKey]))
456
-
457
- // Sort the data for upcoming functions.
458
- let sortedData = filteredDataValues.sort((a, b) => a - b)
459
-
460
- // ! - Notice d3.quantile doesn't work here, and we had to take a custom route.
461
- const quartiles = getQuartiles(sortedData)
462
-
463
- if (!filteredData) throw new Error('boxplots dont have data yet')
464
- if (!plots) throw new Error('boxplots dont have plots yet')
465
-
466
- if (newConfig.boxplot.firstQuartilePercentage === '') {
467
- newConfig.boxplot.firstQuartilePercentage = 0
468
- }
469
-
470
- if (newConfig.boxplot.thirdQuartilePercentage === '') {
471
- newConfig.boxplot.thirdQuartilePercentage = 0
444
+ seriesKeys.forEach(seriesKey => {
445
+ try {
446
+ if (!g) throw new Error('No groups resolved in box plots')
447
+
448
+ // Start handle operations on combinedData
449
+ const { count, sortedData } = _.chain(combinedData)
450
+ // Filter by xAxis data key
451
+ .filter(item => item[newConfig.xAxis.dataKey] === g)
452
+ // perform multiple operations on the filtered data
453
+ .thru(filteredData => ({
454
+ count: filteredData.length,
455
+ sortedData: _.map(filteredData, item => Number(item[seriesKey])).sort()
456
+ }))
457
+ // get the results from the chain
458
+ .value()
459
+
460
+ // ! - Notice d3.quantile doesn't work here, and we had to take a custom route.
461
+ const quartiles = getQuartiles(sortedData)
462
+
463
+ if (!sortedData) throw new Error('boxplots dont have data yet')
464
+ if (!plots) throw new Error('boxplots dont have plots yet')
465
+
466
+ const q1 = quartiles.q1
467
+ const q3 = quartiles.q3
468
+
469
+ const iqr = q3 - q1
470
+ const lowerBounds = q1 - 1.5 * iqr
471
+ const upperBounds = q3 + 1.5 * iqr
472
+ const filteredData = sortedData.filter(d => d <= upperBounds)
473
+ const max = d3.max(filteredData)
474
+ plots.push({
475
+ columnCategory: g,
476
+ columnMax: max,
477
+ columnThirdQuartile: _.round(q3, newConfig.dataFormat.roundTo),
478
+ columnMedian: Number(d3.median(sortedData)).toFixed(newConfig.dataFormat.roundTo),
479
+ columnFirstQuartile: _.round(q1, newConfig.dataFormat.roundTo),
480
+ columnMin: _.min(sortedData),
481
+ columnCount: count,
482
+ columnSd: Number(d3.deviation(sortedData)).toFixed(newConfig.dataFormat.roundTo),
483
+ columnMean: Number(d3.mean(sortedData)).toFixed(newConfig.dataFormat.roundTo),
484
+ columnIqr: _.round(iqr, newConfig.dataFormat.roundTo),
485
+ values: sortedData,
486
+ columnLowerBounds: lowerBounds,
487
+ columnUpperBounds: upperBounds,
488
+ columnOutliers: _.filter(sortedData, value => value < lowerBounds || value > upperBounds),
489
+ columnNonOutliers: _.filter(sortedData, value => value >= lowerBounds && value <= upperBounds)
490
+ })
491
+ } catch (e) {
492
+ console.error('COVE: ', e.message) // eslint-disable-line
472
493
  }
473
-
474
- const q1 = quartiles.q1
475
- const q3 = quartiles.q3
476
- const iqr = q3 - q1
477
- const lowerBounds = q1 - (q3 - q1) * 1.5
478
- const upperBounds = q3 + (q3 - q1) * 1.5
479
-
480
- const outliers = sortedData.filter(v => v < lowerBounds || v > upperBounds)
481
- let nonOutliers = filteredDataValues
482
-
483
- nonOutliers = nonOutliers.filter(item => !outliers.includes(item))
484
- const minValue: number = d3.min<number>(filteredDataValues) || 0
485
- const _colMin = d3.max<number>([minValue, q1 - 1.5 * iqr])
486
- plots.push({
487
- columnCategory: g,
488
- columnMax: d3.min([d3.max(filteredDataValues), q1 + 1.5 * iqr]),
489
- columnThirdQuartile: Number(q3).toFixed(newConfig.dataFormat.roundTo),
490
- columnMedian: Number(d3.median(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
491
- columnFirstQuartile: q1.toFixed(newConfig.dataFormat.roundTo),
492
- columnMin: _colMin,
493
- columnTotal: filteredDataValues.reduce((partialSum, a) => partialSum + a, 0),
494
- columnSd: Number(d3.deviation(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
495
- columnMean: Number(d3.mean(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
496
- columnIqr: Number(iqr).toFixed(newConfig.dataFormat.roundTo),
497
- columnLowerBounds: _colMin,
498
- columnUpperBounds: d3.min([d3.max(sortedData), q1 + 1.5 * iqr]),
499
- columnOutliers: outliers,
500
- values: filteredDataValues,
501
- nonOutlierValues: nonOutliers
502
- })
503
- } catch (e) {
504
- console.error('COVE: ', e.message) // eslint-disable-line
505
- }
506
- })
507
-
508
- // make deep copy so we can remove some fields for data
509
- // this appears to be the easiest option instead of running logic against the datatable cell...
510
- tableData = JSON.parse(JSON.stringify(plots))
511
- tableData.map(table => {
512
- table.columnIqr = undefined
513
- table.nonOutlierValues = undefined
514
- table.columnLowerBounds = undefined
515
- table.columnUpperBounds = undefined
516
- return null // resolve eslint
494
+ })
517
495
  })
496
+ // Generate a flat list of categories based on seriesKeys and groups
497
+ const categories =
498
+ seriesKeys.length > 1
499
+ ? _.flatMap(groups, value => _.map(seriesKeys, key => `${_.capitalize(key)} - ${_.capitalize(value)}`))
500
+ : groups
518
501
 
519
- // any other data we can add to boxplots
520
- newConfig.boxplot['allValues'] = allValues
521
- newConfig.boxplot['categories'] = groups
502
+ newConfig.boxplot['categories'] = categories
522
503
  newConfig.boxplot.plots = plots
523
- newConfig.boxplot.tableData = tableData
524
504
  }
525
505
 
526
506
  if (newConfig.visualizationType === 'Combo' && newConfig.series) {
@@ -604,10 +584,7 @@ const CdcChart = ({
604
584
  : ''
605
585
 
606
586
  // Sankey Description box error message
607
- newConfig.runtime.editorErrorMessage =
608
- newConfig.visualizationType === 'Sankey' && !newConfig.description
609
- ? 'SUBTEXT/CITATION field is empty: A description of the Sankey Diagram data must be inputted.'
610
- : ''
587
+ newConfig.runtime.editorErrorMessage = ''
611
588
 
612
589
  if (newConfig.legend.seriesHighlight?.length) {
613
590
  setSeriesHighlight(newConfig.legend?.seriesHighlight)
@@ -616,21 +593,6 @@ const CdcChart = ({
616
593
  setConfig(newConfig)
617
594
  }
618
595
 
619
- // Gets filter values from dataset
620
- const generateValuesForFilter = (columnName, data = this.state.data) => {
621
- const values: any[] = []
622
-
623
- data.forEach(row => {
624
- const value = row[columnName]
625
- //@ts-ignore
626
- if (value && false === values.includes(value)) {
627
- values.push(value)
628
- }
629
- })
630
-
631
- return values
632
- }
633
-
634
596
  // Sorts data series for horizontal bar charts
635
597
  const sortData = (a, b) => {
636
598
  let sortKey =
@@ -653,12 +615,12 @@ const CdcChart = ({
653
615
  const resizeObserver = new ResizeObserver(entries => {
654
616
  for (let entry of entries) {
655
617
  let { width, height } = entry.contentRect
656
- let svgMarginWidth = 30
657
- let editorWidth = 350
618
+ const svgMarginWidth = 15
619
+ const editorWidth = 350
658
620
 
659
621
  width = isEditor ? width - editorWidth : width
660
622
 
661
- let newViewport = getViewport(width)
623
+ const newViewport = getViewport(width)
662
624
 
663
625
  setCurrentViewport(newViewport)
664
626
 
@@ -865,14 +827,15 @@ const CdcChart = ({
865
827
  }
866
828
  }
867
829
 
868
- const formatDate = (date, prevDate) => {
830
+ const formatDate = (date, i, ticks) => {
869
831
  let formattedDate = timeFormat(config.runtime[section].dateDisplayFormat)(date)
870
832
  // Handle the case where all months work with '%b.' except for May
871
833
  if (config.runtime[section].dateDisplayFormat?.includes('%b.') && formattedDate.includes('May.')) {
872
834
  formattedDate = formattedDate.replace(/May\./g, 'May')
873
835
  }
874
836
  // Show years only once
875
- if (config.xAxis.showYearsOnce && config.runtime[section].dateDisplayFormat?.includes('%Y') && prevDate) {
837
+ if (config.xAxis.showYearsOnce && config.runtime[section].dateDisplayFormat?.includes('%Y') && ticks) {
838
+ const prevDate = ticks[i - 1] ? ticks[i - 1].value : null
876
839
  const prevFormattedDate = timeFormat(config.runtime[section].dateDisplayFormat)(prevDate)
877
840
  const year = formattedDate.match(/\d{4}/)
878
841
  const prevYear = prevFormattedDate.match(/\d{4}/)
@@ -1177,7 +1140,7 @@ const CdcChart = ({
1177
1140
  <h3>Finish Configuring</h3>
1178
1141
  <p>Set all required options to the left and confirm below to display a preview of the chart.</p>
1179
1142
  <Button
1180
- className='btn'
1143
+ className='btn btn-primary'
1181
1144
  style={{ margin: '1em auto' }}
1182
1145
  disabled={missingRequiredSections()}
1183
1146
  onClick={e => confirmDone(e)}
@@ -1234,12 +1197,31 @@ const CdcChart = ({
1234
1197
  // cleaning is deleting data we need in forecasting charts.
1235
1198
  if (!Array.isArray(data)) return []
1236
1199
  if (config.visualizationType === 'Forecasting') return data
1200
+ if (config.series?.some(series => !!series.dynamicCategory)) return data
1237
1201
  return config?.xAxis?.dataKey ? transform.cleanData(data, config.xAxis.dataKey) : data
1238
1202
  }
1239
1203
 
1240
- // required for DataTable
1241
- const displayGeoName = key => {
1242
- return key
1204
+ const getTableRuntimeData = () => {
1205
+ if (visualizationType === 'Sankey') return config?.data?.[0]?.tableData
1206
+ const data = filteredData || excludedData
1207
+ const dynamicSeries = config.series.find(series => !!series.dynamicCategory)
1208
+ if (!dynamicSeries) return data
1209
+ const usedColumns = Object.values(config.columns)
1210
+ .filter(col => col.dataTable)
1211
+ .map(col => col.name)
1212
+ .concat([dynamicSeries.dynamicCategory, dynamicSeries.dataKey])
1213
+ if (config.xAxis?.dataKey) usedColumns.push(config.xAxis.dataKey)
1214
+ return data.map(d => _.pick(d, usedColumns))
1215
+ }
1216
+
1217
+ const pivotDynamicSeries = (config: ChartConfig): TableConfig => {
1218
+ const tableConfig: TableConfig = _.cloneDeep(config)
1219
+ const dynamicSeries = tableConfig.series.find(series => !!series.dynamicCategory)
1220
+ if (dynamicSeries) {
1221
+ const pivot: Pivot = { columnName: dynamicSeries.dynamicCategory, valueColumns: [dynamicSeries.dataKey] }
1222
+ tableConfig.table.pivot = pivot
1223
+ }
1224
+ return tableConfig
1243
1225
  }
1244
1226
 
1245
1227
  // Prevent render if loading
@@ -1459,7 +1441,7 @@ const CdcChart = ({
1459
1441
  config.visualizationType !== 'Sankey') ||
1460
1442
  (config.visualizationType === 'Sankey' && config.table.show)) && (
1461
1443
  <DataTable
1462
- config={config}
1444
+ config={pivotDynamicSeries(config)}
1463
1445
  rawData={
1464
1446
  config.visualizationType === 'Sankey'
1465
1447
  ? config?.data?.[0]?.tableData
@@ -1467,15 +1449,11 @@ const CdcChart = ({
1467
1449
  ? filterVizData(config.filters, config.data)
1468
1450
  : config.data
1469
1451
  }
1470
- runtimeData={
1471
- config.visualizationType === 'Sankey'
1472
- ? config?.data?.[0]?.tableData
1473
- : filteredData || excludedData
1474
- }
1452
+ runtimeData={getTableRuntimeData()}
1475
1453
  expandDataTable={config.table.expanded}
1476
1454
  columns={config.columns}
1477
1455
  displayDataAsText={displayDataAsText}
1478
- displayGeoName={displayGeoName}
1456
+ displayGeoName={name => name}
1479
1457
  applyLegendToRow={applyLegendToRow}
1480
1458
  tableTitle={config.table.label}
1481
1459
  indexTitle={config.table.indexLabel}
@@ -1518,7 +1496,7 @@ const CdcChart = ({
1518
1496
  debugSvg: isDebug,
1519
1497
  dimensions,
1520
1498
  dynamicLegendItems,
1521
- excludedData: excludedData,
1499
+ excludedData,
1522
1500
  formatDate,
1523
1501
  formatNumber,
1524
1502
  formatTooltipsDate,
@@ -1526,6 +1504,7 @@ const CdcChart = ({
1526
1504
  getYAxisData,
1527
1505
  handleChartAriaLabels,
1528
1506
  handleLineType,
1507
+ handleChartTabbing,
1529
1508
  highlight,
1530
1509
  highlightReset,
1531
1510
  imageId,
@@ -1537,6 +1516,7 @@ const CdcChart = ({
1537
1516
  isEditor,
1538
1517
  isNumber,
1539
1518
  legend,
1519
+ legendId,
1540
1520
  legendRef,
1541
1521
  lineOptions,
1542
1522
  loading,
@@ -0,0 +1,31 @@
1
+ import React from 'react'
2
+ import { Meta, Story } from '@storybook/react'
3
+ import Chart from './../CdcChart'
4
+ import exampleComboBarNonNumeric from './../../examples/feature/tests-date-exclusions/date-exclusions-config.json'
5
+ import { editConfigKeys } from '../helpers/configHelpers'
6
+
7
+ export default {
8
+ title: 'Components/Templates/Chart/Anchors',
9
+ component: Chart
10
+ } as Meta
11
+
12
+ type Story = StoryObj<typeof Chart>
13
+
14
+ export const Anchor_DateLinear: Story = {
15
+ args: {
16
+ config: exampleComboBarNonNumeric,
17
+ isEditor: false
18
+ }
19
+ }
20
+
21
+ export const Anchor_Categorical: Story = {
22
+ args: {
23
+ config: editConfigKeys(exampleComboBarNonNumeric, [{ path: ['xAxis', 'type'], value: 'categorical' }])
24
+ }
25
+ }
26
+
27
+ export const Anchor_Date_Time: Story = {
28
+ args: {
29
+ config: editConfigKeys(exampleComboBarNonNumeric, [{ path: ['xAxis', 'type'], value: 'date-time' }])
30
+ }
31
+ }
@@ -0,0 +1,19 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import Chart from '../CdcChart'
3
+ import scatterPlotCustomColorConfig from './_mock/scatterplot_mock.json'
4
+
5
+ const meta: Meta<typeof Chart> = {
6
+ title: 'Components/Templates/Chart/Custom Colors',
7
+ component: Chart
8
+ }
9
+
10
+ type Story = StoryObj<typeof Chart>
11
+
12
+ export const ScatterPlot: Story = {
13
+ args: {
14
+ config: scatterPlotCustomColorConfig,
15
+ isEditor: false
16
+ }
17
+ }
18
+
19
+ export default meta
@@ -0,0 +1,34 @@
1
+ import DynamicSeriesConfig from './_mock/dynamic_series_config.json'
2
+ import DynamicSeriesBarConfig from './_mock/dynamic_series_bar_config.json'
3
+ import { Meta, StoryObj } from '@storybook/react'
4
+ import Chart from '../CdcChart'
5
+
6
+ const meta: Meta<typeof Chart> = {
7
+ title: 'Components/Templates/Chart/Dynamic Series',
8
+ component: Chart
9
+ }
10
+
11
+ type Story = StoryObj<typeof Chart>
12
+
13
+ export const Line: Story = {
14
+ args: {
15
+ config: DynamicSeriesConfig,
16
+ isEditor: false
17
+ }
18
+ }
19
+
20
+ export const Bar_Vertical: Story = {
21
+ args: {
22
+ config: DynamicSeriesBarConfig,
23
+ isEditor: false
24
+ }
25
+ }
26
+
27
+ export const Bar_Horizontal: Story = {
28
+ args: {
29
+ config: { ...DynamicSeriesBarConfig, orientation: 'horizontal' },
30
+ isEditor: false
31
+ }
32
+ }
33
+
34
+ export default meta
@@ -12,12 +12,18 @@ const meta: Meta<typeof Chart> = {
12
12
 
13
13
  type Story = StoryObj<typeof Chart>
14
14
 
15
- export const Legend_Gradient: Story = {
15
+ export const Legend_Gradient_Smooth: Story = {
16
16
  args: {
17
17
  config: chartGradientConfig
18
18
  }
19
19
  }
20
20
 
21
+ export const Legend_Gradient_Linear_Blocks: Story = {
22
+ args: {
23
+ config: editConfigKeys(chartGradientConfig, [{ path: ['legend', 'subStyle'], value: 'linear blocks' }])
24
+ }
25
+ }
26
+
21
27
  export const Labels_On_Line_Legend_On_Top: Story = {
22
28
  args: {
23
29
  config: editConfigKeys(chartGradientConfig, [{ path: ['yAxis', 'labelsAboveGridlines'], value: true }])
@@ -30,4 +36,39 @@ export const Legend_On_Right: Story = {
30
36
  }
31
37
  }
32
38
 
39
+ export const Legend_On_Right_With_Box: Story = {
40
+ args: {
41
+ config: editConfigKeys(SimplifiedLineConfig, [{ path: ['legend', 'hideBorder'], value: false }])
42
+ }
43
+ }
44
+
45
+ export const Legend_Gradient_With_box: Story = {
46
+ args: {
47
+ config: editConfigKeys(chartGradientConfig, [{ path: ['legend', 'hideBorder'], value: false }])
48
+ }
49
+ }
50
+
51
+ export const Legend_Gradient_With_Text: Story = {
52
+ args: {
53
+ config: editConfigKeys(chartGradientConfig, [
54
+ { path: ['legend', 'title'], value: 'Title' },
55
+ { path: ['legend', 'description'], value: 'Description' },
56
+ { path: ['legend', 'hideBorder'], value: false }
57
+ ])
58
+ }
59
+ }
60
+ export const Legend_Gradient_Wrapping_Label: Story = {
61
+ args: {
62
+ config: JSON.parse(
63
+ JSON.stringify(
64
+ editConfigKeys(chartGradientConfig, [
65
+ { path: ['legend', 'title'], value: 'Title' },
66
+ { path: ['legend', 'description'], value: 'Description' },
67
+ { path: ['legend', 'hideBorder'], value: false }
68
+ ])
69
+ ).replace(/Data 1/g, 'This is a long string that should wrap')
70
+ )
71
+ }
72
+ }
73
+
33
74
  export default meta
@@ -1,5 +1,4 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react'
2
- import SuppressedConfig from './_mock/bar-chart-suppressed.json'
3
2
 
4
3
  import Chart from '../CdcChart'
5
4
  import lineChartTwoPointsRegressionTest from './_mock/line_chart_two_points_regression_test.json'
@@ -9,6 +8,12 @@ import forestPlot from '../../examples/feature/forest-plot/forest-plot.json'
9
8
  import pairedBar from './_mock/paired-bar.json'
10
9
  import horizontalBarConfig from './_mock/horizontal_bar.json'
11
10
  import pieConfig from './_mock/pie_with_data.json'
11
+ import boxPlotConfig from './_mock/boxplot_multiseries.json'
12
+ import areaChartStacked from './_mock/area_chart_stacked.json'
13
+ import multipleLines from './_mock/short_dates.json'
14
+ import lineChartDynamicCI from './_mock/line_chart_dynamic_ci.json'
15
+ import lineChartNonDynamicCI from './_mock/line_chart_non_dynamic_ci.json'
16
+ import { editConfigKeys } from '../helpers/configHelpers'
12
17
 
13
18
  const meta: Meta<typeof Chart> = {
14
19
  title: 'Components/Templates/Chart',
@@ -17,6 +22,20 @@ const meta: Meta<typeof Chart> = {
17
22
 
18
23
  type Story = StoryObj<typeof Chart>
19
24
 
25
+ export const line_Chart_Dynamic_Confidence_Intervals: Story = {
26
+ args: {
27
+ config: lineChartDynamicCI,
28
+ isEditor: false
29
+ }
30
+ }
31
+
32
+ export const line_Chart_Non_Dynamic_Confidence_Intervals: Story = {
33
+ args: {
34
+ config: lineChartNonDynamicCI,
35
+ isEditor: false
36
+ }
37
+ }
38
+
20
39
  export const line_Chart_Two_Points_Regression_Test: Story = {
21
40
  args: {
22
41
  config: lineChartTwoPointsRegressionTest,
@@ -30,16 +49,15 @@ export const line_Chart_Two_Points_New_Chart: Story = {
30
49
  }
31
50
  }
32
51
 
33
- export const Lollipop: Story = {
52
+ export const multiple_lines: Story = {
34
53
  args: {
35
- config: lollipop,
36
- isEditor: false
54
+ config: editConfigKeys(multipleLines, [{ path: ['tooltips', 'dateDisplayFormat'], value: '%b. %d %Y' }])
37
55
  }
38
56
  }
39
57
 
40
- export const Suppression: Story = {
58
+ export const Lollipop: Story = {
41
59
  args: {
42
- config: SuppressedConfig,
60
+ config: lollipop,
43
61
  isEditor: false
44
62
  }
45
63
  }
@@ -67,5 +85,18 @@ export const Paired_Bar: Story = {
67
85
  config: pairedBar
68
86
  }
69
87
  }
88
+ export const BoxPlot_Multiseries: Story = {
89
+ args: {
90
+ config: boxPlotConfig,
91
+ isEditor: false
92
+ }
93
+ }
94
+
95
+ export const Area_Chart_stacked: Story = {
96
+ args: {
97
+ config: areaChartStacked,
98
+ isEditor: false
99
+ }
100
+ }
70
101
 
71
102
  export default meta