@cdc/chart 4.24.10 → 4.24.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/cdcchart.js +34618 -33995
  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 +0 -1
  6. package/examples/private/test.json +20092 -0
  7. package/index.html +3 -3
  8. package/package.json +2 -2
  9. package/src/CdcChart.tsx +86 -86
  10. package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
  11. package/src/_stories/Chart.DynamicSeries.stories.tsx +27 -0
  12. package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
  13. package/src/_stories/Chart.stories.tsx +7 -8
  14. package/src/_stories/ChartEditor.stories.tsx +27 -0
  15. package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
  16. package/src/_stories/ChartPrefixSuffix.stories.tsx +8 -0
  17. package/src/_stories/_mock/boxplot_multiseries.json +647 -0
  18. package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
  19. package/src/_stories/_mock/dynamic_series_config.json +979 -0
  20. package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
  21. package/src/_stories/_mock/suppression_mock.json +1549 -0
  22. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
  23. package/src/components/BarChart/components/BarChart.Vertical.tsx +60 -42
  24. package/src/components/BarChart/helpers/index.ts +1 -2
  25. package/src/components/BoxPlot/BoxPlot.tsx +189 -0
  26. package/src/components/EditorPanel/EditorPanel.tsx +64 -62
  27. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +3 -3
  28. package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
  29. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
  30. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
  31. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +121 -56
  32. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
  33. package/src/components/EditorPanel/useEditorPermissions.ts +15 -1
  34. package/src/components/Legend/Legend.Component.tsx +9 -10
  35. package/src/components/Legend/Legend.tsx +16 -16
  36. package/src/components/LineChart/helpers.ts +48 -43
  37. package/src/components/LineChart/index.tsx +88 -82
  38. package/src/components/LinearChart.tsx +17 -10
  39. package/src/components/Sankey/index.tsx +50 -32
  40. package/src/components/Sankey/sankey.scss +6 -5
  41. package/src/components/Sankey/useSankeyAlert.tsx +60 -0
  42. package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
  43. package/src/data/initial-state.js +3 -9
  44. package/src/hooks/useLegendClasses.ts +10 -23
  45. package/src/hooks/useMinMax.ts +27 -13
  46. package/src/hooks/useReduceData.ts +43 -10
  47. package/src/hooks/useScales.ts +56 -35
  48. package/src/hooks/useTooltip.tsx +54 -49
  49. package/src/scss/main.scss +0 -18
  50. package/src/types/ChartConfig.ts +6 -19
  51. package/src/types/ChartContext.ts +4 -1
  52. package/src/types/ForestPlot.ts +8 -0
  53. package/src/components/BoxPlot/BoxPlot.jsx +0 -111
package/index.html CHANGED
@@ -62,19 +62,19 @@
62
62
  <!-- <div class="react-container" data-config="/examples/feature/forest-plot/linear.json"></div> -->
63
63
  <!-- <div class="react-container" data-config="/examples/feature/forest-plot/logarithmic.json"></div> -->
64
64
  <!-- <div class="react-container" data-config="/examples/feature/forest-plot/forest-plot.json"></div> -->
65
- <div class="react-container" data-config="/examples/feature/pie/planet-pie-example-config.json"></div>
65
+ <!-- <div class="react-container" data-config="/examples/feature/pie/planet-pie-example-config.json"></div> -->
66
66
  <!-- <div class="react-container" data-config=https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/Line_Chart_Viz.json></div> -->
67
67
  <!-- <div class="react-container" data-config=/examples/feature/regions/index.json></div> -->
68
68
  <!-- <div class="react-container" data-config=https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/Line_Chart_Regions_Viz.json></div> -->
69
69
  <!-- <div class="react-container" data-config=https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/Line_Chart_Regions_Viz.json></div> -->
70
70
  <!-- <div class="react-container" data-config="/examples/feature/forecasting/forecasting.json"></div> -->
71
- <div class="react-container" data-config="/examples/feature/forecasting/combo-forecasting.json"></div>
71
+ <!-- <div class="react-container" data-config="/examples/feature/forecasting/combo-forecasting.json"></div> -->
72
72
  <!-- <div class="react-container" data-config="/examples/feature/forecasting/effective_reproduction.json"></div> -->
73
73
  <!-- <div class="react-container" data-config="/examples/feature/area/area-chart-date.json"></div> -->
74
74
  <!-- <div class="react-container" data-config="/examples/feature/area/area-chart-category.json"></div> -->
75
75
  <!-- <div class="react-container" data-config="/examples/feature/scatterplot/scatterplot.json"></div> -->
76
76
  <!-- <div class="react-container" data-config="/examples/feature/deviation/planet-deviation-config.json"></div> -->
77
- <!-- <div class="react-container" data-config="/examples/feature/boxplot/boxplot.json"></div> -->
77
+ <div class="react-container" data-config="/examples/feature/boxplot/boxplot.json"></div>
78
78
  <!-- <div class="react-container" data-config="/examples/feature/combo/planet-combo-example-config.json"></div> -->
79
79
  <!-- <div class="react-container" data-config="/examples/feature/combo/right-issues.json"></div> -->
80
80
  <!-- <div class="react-container" data-config="/examples/region-issue.json"></div> -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/chart",
3
- "version": "4.24.10",
3
+ "version": "4.24.11",
4
4
  "description": "React component for visualizing tabular data in various types of charts",
5
5
  "moduleName": "CdcChart",
6
6
  "main": "dist/cdcchart",
@@ -61,7 +61,7 @@
61
61
  "react": "^18.2.0",
62
62
  "react-dom": "^18.2.0"
63
63
  },
64
- "gitHead": "a4d88d1bc91f596e1b0307d8e25c57ad8c668b75",
64
+ "gitHead": "9ab5ee9b2b0ef7321a66a2104be6ce8899ec3808",
65
65
  "devDependencies": {
66
66
  "@types/d3-sankey": "^0.12.4",
67
67
  "resize-observer-polyfill": "^1.5.1"
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,30 +403,42 @@ 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
- }
438
+ const combinedData = filteredData || data
439
+ let allKeys = combinedData.map(d => d[newConfig.xAxis.dataKey])
440
+ const groups = _.uniq(allKeys)
439
441
 
440
- const groups = uniqueArray(allKeys)
441
442
  let tableData: any[] = []
442
443
  const plots: any[] = []
443
444
 
@@ -449,9 +450,8 @@ const CdcChart = ({
449
450
  if (!g) throw new Error('No groups resolved in box plots')
450
451
 
451
452
  // 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)
453
+ let filteredData = combinedData.filter(item => item[newConfig.xAxis.dataKey] === g)
454
+
455
455
  let filteredDataValues: number[] = filteredData.map(item => Number(item[newConfig?.series[0]?.dataKey]))
456
456
 
457
457
  // Sort the data for upcoming functions.
@@ -460,16 +460,18 @@ const CdcChart = ({
460
460
  // ! - Notice d3.quantile doesn't work here, and we had to take a custom route.
461
461
  const quartiles = getQuartiles(sortedData)
462
462
 
463
- if (!filteredData) throw new Error('boxplots dont have data yet')
464
- if (!plots) throw new Error('boxplots dont have plots yet')
463
+ const getValuesBySeriesKey = () => {
464
+ const allSeriesKeys = newConfig.series.map(item => item?.dataKey)
465
+ const result = {}
466
+ allSeriesKeys.forEach(key => {
467
+ result[key] = filteredData.map(item => item[key])
468
+ })
465
469
 
466
- if (newConfig.boxplot.firstQuartilePercentage === '') {
467
- newConfig.boxplot.firstQuartilePercentage = 0
470
+ return result
468
471
  }
469
472
 
470
- if (newConfig.boxplot.thirdQuartilePercentage === '') {
471
- newConfig.boxplot.thirdQuartilePercentage = 0
472
- }
473
+ if (!filteredData) throw new Error('boxplots dont have data yet')
474
+ if (!plots) throw new Error('boxplots dont have plots yet')
473
475
 
474
476
  const q1 = quartiles.q1
475
477
  const q3 = quartiles.q3
@@ -478,9 +480,7 @@ const CdcChart = ({
478
480
  const upperBounds = q3 + (q3 - q1) * 1.5
479
481
 
480
482
  const outliers = sortedData.filter(v => v < lowerBounds || v > upperBounds)
481
- let nonOutliers = filteredDataValues
482
483
 
483
- nonOutliers = nonOutliers.filter(item => !outliers.includes(item))
484
484
  const minValue: number = d3.min<number>(filteredDataValues) || 0
485
485
  const _colMin = d3.max<number>([minValue, q1 - 1.5 * iqr])
486
486
  plots.push({
@@ -490,7 +490,7 @@ const CdcChart = ({
490
490
  columnMedian: Number(d3.median(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
491
491
  columnFirstQuartile: q1.toFixed(newConfig.dataFormat.roundTo),
492
492
  columnMin: _colMin,
493
- columnTotal: filteredDataValues.reduce((partialSum, a) => partialSum + a, 0),
493
+ columnCount: filteredData.length,
494
494
  columnSd: Number(d3.deviation(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
495
495
  columnMean: Number(d3.mean(filteredDataValues)).toFixed(newConfig.dataFormat.roundTo),
496
496
  columnIqr: Number(iqr).toFixed(newConfig.dataFormat.roundTo),
@@ -498,7 +498,7 @@ const CdcChart = ({
498
498
  columnUpperBounds: d3.min([d3.max(sortedData), q1 + 1.5 * iqr]),
499
499
  columnOutliers: outliers,
500
500
  values: filteredDataValues,
501
- nonOutlierValues: nonOutliers
501
+ keyValues: getValuesBySeriesKey()
502
502
  })
503
503
  } catch (e) {
504
504
  console.error('COVE: ', e.message) // eslint-disable-line
@@ -516,8 +516,6 @@ const CdcChart = ({
516
516
  return null // resolve eslint
517
517
  })
518
518
 
519
- // any other data we can add to boxplots
520
- newConfig.boxplot['allValues'] = allValues
521
519
  newConfig.boxplot['categories'] = groups
522
520
  newConfig.boxplot.plots = plots
523
521
  newConfig.boxplot.tableData = tableData
@@ -616,21 +614,6 @@ const CdcChart = ({
616
614
  setConfig(newConfig)
617
615
  }
618
616
 
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
617
  // Sorts data series for horizontal bar charts
635
618
  const sortData = (a, b) => {
636
619
  let sortKey =
@@ -653,7 +636,6 @@ const CdcChart = ({
653
636
  const resizeObserver = new ResizeObserver(entries => {
654
637
  for (let entry of entries) {
655
638
  let { width, height } = entry.contentRect
656
- let svgMarginWidth = 30
657
639
  let editorWidth = 350
658
640
 
659
641
  width = isEditor ? width - editorWidth : width
@@ -666,7 +648,7 @@ const CdcChart = ({
666
648
  width = width - 2.5
667
649
  }
668
650
 
669
- width = width - svgMarginWidth
651
+ width = width
670
652
 
671
653
  setDimensions([width, height])
672
654
  }
@@ -865,14 +847,15 @@ const CdcChart = ({
865
847
  }
866
848
  }
867
849
 
868
- const formatDate = (date, prevDate) => {
850
+ const formatDate = (date, i, ticks) => {
869
851
  let formattedDate = timeFormat(config.runtime[section].dateDisplayFormat)(date)
870
852
  // Handle the case where all months work with '%b.' except for May
871
853
  if (config.runtime[section].dateDisplayFormat?.includes('%b.') && formattedDate.includes('May.')) {
872
854
  formattedDate = formattedDate.replace(/May\./g, 'May')
873
855
  }
874
856
  // Show years only once
875
- if (config.xAxis.showYearsOnce && config.runtime[section].dateDisplayFormat?.includes('%Y') && prevDate) {
857
+ if (config.xAxis.showYearsOnce && config.runtime[section].dateDisplayFormat?.includes('%Y') && ticks) {
858
+ const prevDate = ticks[i - 1] ? ticks[i - 1].value : null
876
859
  const prevFormattedDate = timeFormat(config.runtime[section].dateDisplayFormat)(prevDate)
877
860
  const year = formattedDate.match(/\d{4}/)
878
861
  const prevYear = prevFormattedDate.match(/\d{4}/)
@@ -1177,7 +1160,7 @@ const CdcChart = ({
1177
1160
  <h3>Finish Configuring</h3>
1178
1161
  <p>Set all required options to the left and confirm below to display a preview of the chart.</p>
1179
1162
  <Button
1180
- className='btn'
1163
+ className='btn btn-primary'
1181
1164
  style={{ margin: '1em auto' }}
1182
1165
  disabled={missingRequiredSections()}
1183
1166
  onClick={e => confirmDone(e)}
@@ -1234,12 +1217,31 @@ const CdcChart = ({
1234
1217
  // cleaning is deleting data we need in forecasting charts.
1235
1218
  if (!Array.isArray(data)) return []
1236
1219
  if (config.visualizationType === 'Forecasting') return data
1220
+ if (config.series?.some(series => !!series.dynamicCategory)) return data
1237
1221
  return config?.xAxis?.dataKey ? transform.cleanData(data, config.xAxis.dataKey) : data
1238
1222
  }
1239
1223
 
1240
- // required for DataTable
1241
- const displayGeoName = key => {
1242
- return key
1224
+ const getTableRuntimeData = () => {
1225
+ if (visualizationType === 'Sankey') return config?.data?.[0]?.tableData
1226
+ const data = filteredData || excludedData
1227
+ const dynamicSeries = config.series.find(series => !!series.dynamicCategory)
1228
+ if (!dynamicSeries) return data
1229
+ const usedColumns = Object.values(config.columns)
1230
+ .filter(col => col.dataTable)
1231
+ .map(col => col.name)
1232
+ .concat([dynamicSeries.dynamicCategory, dynamicSeries.dataKey])
1233
+ if (config.xAxis?.dataKey) usedColumns.push(config.xAxis.dataKey)
1234
+ return data.map(d => _.pick(d, usedColumns))
1235
+ }
1236
+
1237
+ const pivotDynamicSeries = (config: ChartConfig): TableConfig => {
1238
+ const tableConfig: TableConfig = _.cloneDeep(config)
1239
+ const dynamicSeries = tableConfig.series.find(series => !!series.dynamicCategory)
1240
+ if (dynamicSeries) {
1241
+ const pivot: Pivot = { columnName: dynamicSeries.dynamicCategory, valueColumns: [dynamicSeries.dataKey] }
1242
+ tableConfig.table.pivot = pivot
1243
+ }
1244
+ return tableConfig
1243
1245
  }
1244
1246
 
1245
1247
  // Prevent render if loading
@@ -1459,7 +1461,7 @@ const CdcChart = ({
1459
1461
  config.visualizationType !== 'Sankey') ||
1460
1462
  (config.visualizationType === 'Sankey' && config.table.show)) && (
1461
1463
  <DataTable
1462
- config={config}
1464
+ config={pivotDynamicSeries(config)}
1463
1465
  rawData={
1464
1466
  config.visualizationType === 'Sankey'
1465
1467
  ? config?.data?.[0]?.tableData
@@ -1467,15 +1469,11 @@ const CdcChart = ({
1467
1469
  ? filterVizData(config.filters, config.data)
1468
1470
  : config.data
1469
1471
  }
1470
- runtimeData={
1471
- config.visualizationType === 'Sankey'
1472
- ? config?.data?.[0]?.tableData
1473
- : filteredData || excludedData
1474
- }
1472
+ runtimeData={getTableRuntimeData()}
1475
1473
  expandDataTable={config.table.expanded}
1476
1474
  columns={config.columns}
1477
1475
  displayDataAsText={displayDataAsText}
1478
- displayGeoName={displayGeoName}
1476
+ displayGeoName={name => name}
1479
1477
  applyLegendToRow={applyLegendToRow}
1480
1478
  tableTitle={config.table.label}
1481
1479
  indexTitle={config.table.indexLabel}
@@ -1518,7 +1516,7 @@ const CdcChart = ({
1518
1516
  debugSvg: isDebug,
1519
1517
  dimensions,
1520
1518
  dynamicLegendItems,
1521
- excludedData: excludedData,
1519
+ excludedData,
1522
1520
  formatDate,
1523
1521
  formatNumber,
1524
1522
  formatTooltipsDate,
@@ -1526,6 +1524,7 @@ const CdcChart = ({
1526
1524
  getYAxisData,
1527
1525
  handleChartAriaLabels,
1528
1526
  handleLineType,
1527
+ handleChartTabbing,
1529
1528
  highlight,
1530
1529
  highlightReset,
1531
1530
  imageId,
@@ -1537,6 +1536,7 @@ const CdcChart = ({
1537
1536
  isEditor,
1538
1537
  isNumber,
1539
1538
  legend,
1539
+ legendId,
1540
1540
  legendRef,
1541
1541
  lineOptions,
1542
1542
  loading,
@@ -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,27 @@
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: Story = {
21
+ args: {
22
+ config: DynamicSeriesBarConfig,
23
+ isEditor: false
24
+ }
25
+ }
26
+
27
+ 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,7 @@ 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
12
 
13
13
  const meta: Meta<typeof Chart> = {
14
14
  title: 'Components/Templates/Chart',
@@ -37,13 +37,6 @@ export const Lollipop: Story = {
37
37
  }
38
38
  }
39
39
 
40
- export const Suppression: Story = {
41
- args: {
42
- config: SuppressedConfig,
43
- isEditor: false
44
- }
45
- }
46
-
47
40
  export const Forest_Plot: Story = {
48
41
  args: {
49
42
  config: forestPlot
@@ -67,5 +60,11 @@ export const Paired_Bar: Story = {
67
60
  config: pairedBar
68
61
  }
69
62
  }
63
+ export const BoxPlot_Multiseries: Story = {
64
+ args: {
65
+ config: boxPlotConfig,
66
+ isEditor: false
67
+ }
68
+ }
70
69
 
71
70
  export default meta
@@ -4,6 +4,7 @@ import Chart from '../CdcChart'
4
4
  import pieChartExample from './_mock/pie_config.json'
5
5
  import pieData from './_mock/pie_data.json'
6
6
  import urlFilterExample from './_mock/url_filter.json'
7
+ import mockScatterPlot from './_mock/scatterplot_mock.json'
7
8
 
8
9
  const meta: Meta<typeof Chart> = {
9
10
  title: 'Components/Templates/Chart/Editor',
@@ -30,4 +31,30 @@ export const Url_Filter: Story = {
30
31
  }
31
32
  }
32
33
 
34
+ export const ScatterPlot: Story = {
35
+ args: {
36
+ config: mockScatterPlot,
37
+ isEditor: true
38
+ },
39
+ play: async ({ canvasElement }) => {
40
+ const canvas = within(canvasElement)
41
+ const user = userEvent.setup()
42
+ // play is running before full rendering is complete so sleep function
43
+ // is needed to delay the execution.
44
+ // possible related bug: https://github.com/storybookjs/storybook/issues/18258
45
+ await sleep(3000)
46
+ const dateCategoryAccordion = await canvas.findByRole('button', { name: /Date\/Category Axis/i })
47
+ user.click(dateCategoryAccordion)
48
+ await sleep(1000)
49
+ const minValueInput = await canvas.findByLabelText('min value')
50
+ const maxValueInput = await canvas.findByLabelText('max value')
51
+ await user.type(minValueInput, '0')
52
+ await user.type(maxValueInput, '100')
53
+ await sleep(1000)
54
+ // Scroll to the top of the screen
55
+ user.click(dateCategoryAccordion)
56
+ await canvasElement.scrollTo(0, 0)
57
+ }
58
+ }
59
+
33
60
  export default meta
@@ -0,0 +1,25 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import Chart from '../CdcChart'
3
+ import suppressionConfig from './_mock/suppression_mock.json'
4
+ import SuppressedConfig from './_mock/bar-chart-suppressed.json'
5
+ const meta: Meta<typeof Chart> = {
6
+ title: 'Components/Templates/Chart/Suppression',
7
+ component: Chart
8
+ }
9
+
10
+ type Story = StoryObj<typeof Chart>
11
+
12
+ export const SuppressedLines: Story = {
13
+ args: {
14
+ config: suppressionConfig,
15
+ isEditor: false
16
+ }
17
+ }
18
+ export const SuppressedBars: Story = {
19
+ args: {
20
+ config: SuppressedConfig,
21
+ isEditor: false
22
+ }
23
+ }
24
+
25
+ export default meta
@@ -3,6 +3,7 @@ import barConfig from './_mock/line_chart_two_points_new_chart.json'
3
3
  import annotationConfig from './_mock/annotation_category_mock.json'
4
4
  import areaPrefix from './_mock/annotation_category_mock.json'
5
5
  import horizontalBarConfig from './_mock/horizontal_bar.json'
6
+ import scatterPlotConfig from './_mock/scatterplot_mock.json'
6
7
 
7
8
  import Chart from '../CdcChart'
8
9
  import { editConfigKeys } from '../helpers/configHelpers'
@@ -148,4 +149,11 @@ export const Top_Suffix_Above_Gridlines_With_Options: Story = {
148
149
  }
149
150
  }
150
151
 
152
+ export const ScatterPlot_Bottom_Commas: Story = {
153
+ args: {
154
+ config: editConfigKeys(scatterPlotConfig, [{ path: ['dataFormat', 'bottomCommas'], value: true }])
155
+ },
156
+ isEditor: true
157
+ }
158
+
151
159
  export default meta