@cdc/chart 4.26.5 → 4.26.7

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 (128) hide show
  1. package/CONFIG.md +20 -14
  2. package/dist/{cdcchart-DQ00cQCm.es.js → cdcchart-BWVIUcAF.es.js} +5 -55
  3. package/dist/cdcchart.js +36554 -34320
  4. package/examples/feature/heatmap/average-age-categorical.json +1 -1
  5. package/examples/feature/heatmap/calendar-heatmap.json +1 -1
  6. package/examples/feature/heatmap/sparse-aggregation.json +1 -1
  7. package/examples/private/bad-y-axis.json +543 -0
  8. package/examples/private/bar-chart-negative-value.json +455 -0
  9. package/examples/private/flueview.json +22051 -0
  10. package/package.json +3 -11
  11. package/src/CdcChart.tsx +7 -6
  12. package/src/CdcChartComponent.tsx +71 -18
  13. package/src/_stories/Chart.PieLabels.stories.tsx +47 -0
  14. package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +183 -0
  15. package/src/_stories/Chart.YAxisAutoMaxStrategy.stories.tsx +52 -0
  16. package/src/_stories/Chart.YAxisFilterDomain.stories.tsx +90 -0
  17. package/src/_stories/Chart.smoke.stories.tsx +14 -1
  18. package/src/_stories/ChartAnnotation.smoke.stories.tsx +10 -0
  19. package/src/_stories/ChartPrefixSuffix.smoke.stories.tsx +28 -1
  20. package/src/_stories/_mock/annotation_event-line_mock.json +1064 -0
  21. package/src/_stories/_mock/heatmap-average-age-categorical.json +1 -1
  22. package/src/_stories/_mock/heatmap.json +1 -1
  23. package/src/components/Annotations/components/AnnotationDraggable.styles.css +22 -1
  24. package/src/components/Annotations/components/AnnotationDraggable.tsx +219 -119
  25. package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +198 -0
  26. package/src/components/Axis/LeftAxis.tsx +3 -0
  27. package/src/components/Axis/LeftAxisGridlines.tsx +8 -1
  28. package/src/components/Axis/RightAxis.tsx +3 -0
  29. package/src/components/Axis/tests/AxisTickValues.test.tsx +86 -0
  30. package/src/components/BarChart/components/BarChart.Horizontal.tsx +1 -2
  31. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -1
  32. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -1
  33. package/src/components/BarChart/components/BarChart.Vertical.tsx +1 -2
  34. package/src/components/BarChart/helpers/getBarData.ts +28 -28
  35. package/src/components/BarChart/helpers/getBarHeights.ts +2 -2
  36. package/src/components/BarChart/helpers/tests/useBarChart.test.tsx +98 -0
  37. package/src/components/BarChart/helpers/useBarChart.ts +4 -5
  38. package/src/components/BoxPlot/BoxPlot.Horizontal.tsx +7 -6
  39. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +9 -7
  40. package/src/components/BoxPlot/helpers/index.ts +4 -3
  41. package/src/components/Brush/BrushSelector.tsx +1 -1
  42. package/src/components/EditorPanel/EditorPanel.tsx +457 -165
  43. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +101 -52
  44. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +16 -7
  45. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +3 -3
  46. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +19 -4
  47. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.test.tsx +98 -0
  48. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +10 -0
  49. package/src/components/EditorPanel/components/Panels/Panel.Visual.test.tsx +484 -0
  50. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +3 -0
  51. package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +262 -0
  52. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +32 -0
  53. package/src/components/EditorPanel/components/panels.scss +10 -0
  54. package/src/components/EditorPanel/editor-panel.scss +47 -0
  55. package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +6 -2
  56. package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +20 -2
  57. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +3 -3
  58. package/src/components/EditorPanel/useEditorPermissions.test.tsx +288 -11
  59. package/src/components/EditorPanel/useEditorPermissions.ts +46 -6
  60. package/src/components/Forecasting/Forecasting.tsx +1 -1
  61. package/src/components/HeatMap/components/HeatMap.tsx +22 -17
  62. package/src/components/HeatMap/components/HeatMapGradientLegend.tsx +29 -15
  63. package/src/components/HeatMap/heatmap.css +15 -1
  64. package/src/components/HeatMap/tests/HeatMap.test.tsx +39 -7
  65. package/src/components/Legend/Legend.Component.tsx +56 -61
  66. package/src/components/Legend/Legend.Suppression.tsx +3 -3
  67. package/src/components/Legend/LegendGroup/LegendGroup.tsx +0 -1
  68. package/src/components/Legend/helpers/createFormatLabels.test.ts +88 -0
  69. package/src/components/Legend/helpers/createFormatLabels.tsx +6 -1
  70. package/src/components/LineChart/helpers.test.ts +230 -1
  71. package/src/components/LineChart/helpers.ts +25 -14
  72. package/src/components/LineChart/index.tsx +23 -7
  73. package/src/components/LinearChart/tests/LinearChart.test.tsx +30 -0
  74. package/src/components/LinearChart.tsx +51 -15
  75. package/src/components/PieChart/PieChart.tsx +117 -10
  76. package/src/components/PieChart/helpers/labelPlacement.test.ts +88 -0
  77. package/src/components/PieChart/helpers/labelPlacement.ts +95 -0
  78. package/src/components/ScatterPlot/ScatterPlot.jsx +5 -13
  79. package/src/components/SmallMultiples/SmallMultipleTile.tsx +3 -0
  80. package/src/components/SmallMultiples/SmallMultiples.tsx +38 -9
  81. package/src/components/SmallMultiples/tests/SmallMultiples.test.tsx +170 -0
  82. package/src/data/initial-state.js +3 -0
  83. package/src/helpers/calculateLeftYAxisWidth.ts +9 -3
  84. package/src/helpers/categoryOrder.ts +60 -0
  85. package/src/helpers/colorAssignmentHelpers.ts +102 -0
  86. package/src/helpers/dataHelpers.ts +11 -1
  87. package/src/helpers/getAxisMaxOverride.ts +16 -0
  88. package/src/helpers/getBoxPlotConfig.ts +19 -12
  89. package/src/helpers/getCleanTopTickMax.ts +64 -0
  90. package/src/helpers/getColorScale.ts +18 -9
  91. package/src/helpers/getComboChartConfig.ts +0 -1
  92. package/src/helpers/getExcludedData.ts +0 -1
  93. package/src/helpers/getHasBoundarySuppression.ts +27 -0
  94. package/src/helpers/getMinMax.ts +25 -22
  95. package/src/helpers/getNewRuntime.ts +0 -2
  96. package/src/helpers/getPiePercent.ts +3 -3
  97. package/src/helpers/getYAxisDomainData.ts +38 -0
  98. package/src/helpers/getYAxisFinalizationEligibility.ts +40 -0
  99. package/src/helpers/hasSpacedInlineLabel.ts +10 -0
  100. package/src/helpers/isConvertLineToBarGraph.ts +2 -2
  101. package/src/helpers/seriesColumnSettings.ts +26 -7
  102. package/src/helpers/sizeHelpers.ts +0 -2
  103. package/src/helpers/smallMultiplesHelpers.ts +5 -0
  104. package/src/helpers/tests/calculateLeftYAxisWidth.test.ts +38 -0
  105. package/src/helpers/tests/categoryOrder.test.ts +57 -0
  106. package/src/helpers/tests/dataHelpers.test.ts +135 -0
  107. package/src/helpers/tests/getAxisMaxOverride.test.ts +36 -0
  108. package/src/helpers/tests/getCleanTopTickMax.test.ts +165 -0
  109. package/src/helpers/tests/getColorScale.test.ts +197 -0
  110. package/src/helpers/tests/getMinMax.test.ts +211 -0
  111. package/src/helpers/tests/getYAxisDomainData.test.ts +119 -0
  112. package/src/helpers/tests/getYAxisFinalizationEligibility.test.ts +185 -0
  113. package/src/helpers/tests/{getYAxisAutoPaddingMode.test.ts → hasSpacedInlineLabel.test.ts} +17 -32
  114. package/src/helpers/tests/seriesColumnSettings.test.ts +76 -2
  115. package/src/helpers/tests/smallMultiplesHelpers.test.ts +99 -0
  116. package/src/hooks/tests/useRightAxis.test.tsx +268 -0
  117. package/src/hooks/tests/useScales.test.tsx +302 -27
  118. package/src/hooks/tests/useTooltip.test.tsx +132 -0
  119. package/src/hooks/useRightAxis.ts +35 -25
  120. package/src/hooks/useScales.ts +45 -28
  121. package/src/hooks/useTooltip.tsx +17 -11
  122. package/src/store/chart.reducer.ts +0 -1
  123. package/src/test/CdcChart.dataTable.test.tsx +216 -0
  124. package/src/types/ChartConfig.ts +6 -1
  125. package/src/types/ChartContext.ts +3 -0
  126. package/src/helpers/getYAxisAutoPadding.ts +0 -55
  127. package/src/helpers/getYAxisAutoPaddingMode.ts +0 -17
  128. package/src/helpers/tests/getYAxisAutoPadding.test.ts +0 -37
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@cdc/chart",
3
- "version": "4.26.5",
3
+ "version": "4.26.7",
4
4
  "description": "React component for visualizing tabular data in various types of charts",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Matthew Pallansch <mpallansch@adittech.com>",
7
7
  "bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
8
8
  "dependencies": {
9
- "@cdc/core": "^4.26.5",
9
+ "@cdc/core": "^4.26.7",
10
10
  "@hello-pangea/dnd": "^16.2.0",
11
11
  "@react-spring/web": "^9.7.5",
12
12
  "@rollup/plugin-dsv": "^3.0.2",
@@ -14,14 +14,11 @@
14
14
  "@visx/axis": "^3.12.0",
15
15
  "@visx/brush": "^3.12.0",
16
16
  "@visx/curve": "^3.12.0",
17
- "@visx/drag": "^3.12.0",
18
17
  "@visx/event": "^3.12.0",
19
18
  "@visx/glyph": "^3.12.0",
20
- "@visx/gradient": "^3.12.0",
21
19
  "@visx/heatmap": "^3.12.0",
22
20
  "@visx/legend": "^3.12.0",
23
21
  "@visx/marker": "^3.12.0",
24
- "@visx/mock-data": "^3.12.0",
25
22
  "@visx/pattern": "^3.12.0",
26
23
  "@visx/responsive": "^3.12.0",
27
24
  "@visx/scale": "^3.12.0",
@@ -32,20 +29,16 @@
32
29
  "@vitejs/plugin-react": "^5.1.2",
33
30
  "chroma-js": "^3.1.2",
34
31
  "d3-array": "^3.2.4",
35
- "d3-format": "^3.1.2",
36
32
  "d3-sankey": "^0.12.3",
37
33
  "d3-time-format": "^4.1.0",
38
34
  "dompurify": "^3.4.0",
39
35
  "html-react-parser": "^5.2.3",
40
- "js-base64": "^2.5.2",
41
36
  "lodash": "^4.17.23",
42
- "papaparse": "^5.5.2",
43
37
  "react-accessible-accordion": "^5.0.1",
44
38
  "react-icons": "^5.5.0",
45
39
  "react-tooltip": "5.8.2-beta.3",
46
40
  "resize-observer-polyfill": "^1.5.1",
47
41
  "sass": "^1.89.2",
48
- "use-debounce": "^10.1.0",
49
42
  "vite": "^7.3.1",
50
43
  "vite-plugin-css-injected-by-js": "^2.4.0",
51
44
  "vite-plugin-svgr": "^4.2.0",
@@ -53,11 +46,10 @@
53
46
  },
54
47
  "devDependencies": {
55
48
  "@types/d3-array": "^3.2.1",
56
- "@types/d3-format": "^3.0.4",
57
49
  "@types/d3-sankey": "^0.12.4",
58
50
  "@types/d3-time-format": "^4.0.3"
59
51
  },
60
- "gitHead": "61c025165d96b45a6002c34582c5a622a9d865a9",
52
+ "gitHead": "ca89fe0eb083a52a425fa5971bbe74716b5bb0f9",
61
53
  "main": "dist/cdcchart",
62
54
  "moduleName": "CdcChart",
63
55
  "peerDependencies": {
package/src/CdcChart.tsx CHANGED
@@ -4,10 +4,10 @@ import { ChartConfig } from './types/ChartConfig'
4
4
  import { getFileExtension } from '@cdc/core/helpers/getFileExtension'
5
5
  import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
6
6
  import { parseCsvWithQuotes } from '@cdc/core/helpers/parseCsvWithQuotes'
7
- import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
7
+ import { addCsvCacheBuster } from '@cdc/core/helpers/csvCacheBuster'
8
8
  import { extractDataAndMetadata } from '@cdc/core/helpers/extractDataAndMetadata'
9
9
  import Loading from '@cdc/core/components/Loading'
10
- import _ from 'lodash'
10
+ import isEqual from 'lodash/isEqual'
11
11
  import EditorContext from '@cdc/core/contexts/EditorContext'
12
12
  interface CdcChartProps {
13
13
  configUrl?: string
@@ -90,7 +90,7 @@ const CdcChartWrapper: React.FC<CdcChartProps> = ({
90
90
  }, [configUrl, loadConfig])
91
91
 
92
92
  useEffect(() => {
93
- if (!_.isEqual(prevFiltersRef.current, config.filters)) {
93
+ if (!isEqual(prevFiltersRef.current, config.filters)) {
94
94
  prevFiltersRef.current = config.filters
95
95
  reloadFilteredData()
96
96
  }
@@ -112,11 +112,12 @@ const parseCsv = (responseText: string, delimiter = '|') => {
112
112
 
113
113
  const fetchAndParseData = async (url: string, ext: string) => {
114
114
  try {
115
- const response = await fetch(url)
116
- if (ext === 'csv' || isSolrCsv(url)) {
115
+ if (ext?.toLowerCase() === 'csv' || isSolrCsv(url)) {
116
+ const response = await fetch(addCsvCacheBuster(url))
117
117
  const responseText = await response.text()
118
118
  return { data: parseCsv(responseText), dataMetadata: {} }
119
- } else if (ext === 'json' || isSolrJson(url)) {
119
+ } else if (ext?.toLowerCase() === 'json' || isSolrJson(url)) {
120
+ const response = await fetch(url)
120
121
  const json = await response.json()
121
122
  return extractDataAndMetadata(json)
122
123
  }
@@ -101,6 +101,7 @@ import { prepareSmallMultiplesDataTable } from './helpers/smallMultiplesHelpers'
101
101
  import { calcInitialHeight } from './helpers/sizeHelpers'
102
102
  import { ensureSpecialChartAxisTypes } from './helpers/ensureSpecialChartAxisTypes'
103
103
  import { findColumnConfigByName } from './helpers/seriesColumnSettings'
104
+ import { sortByCategoryOrder } from './helpers/categoryOrder'
104
105
 
105
106
  // styles
106
107
  import './scss/main.scss'
@@ -231,6 +232,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
231
232
  config.enableMarkupVariables,
232
233
  config.markupVariables,
233
234
  config.data,
235
+ config.dataMetadata,
234
236
  config.filters,
235
237
  title,
236
238
  config.superTitle,
@@ -246,6 +248,10 @@ const CdcChart: React.FC<CdcChartProps> = ({
246
248
  const processedIntroText = processedTextFields.introText
247
249
  const processedLegacyFootnotes = processedTextFields.legacyFootnotes
248
250
  const processedDescription = processedTextFields.description
251
+ const chartDataTableIsRendered =
252
+ Boolean(config.table?.show) &&
253
+ config.visualizationType !== 'Spark Line' &&
254
+ ((Boolean(config.xAxis?.dataKey) && config.visualizationType !== 'Sankey') || config.visualizationType === 'Sankey')
249
255
  // Note: Axis labels are processed within updateConfig to ensure they use the correct data
250
256
 
251
257
  // set defaults on titles if blank AND only in editor
@@ -259,14 +265,30 @@ const CdcChart: React.FC<CdcChartProps> = ({
259
265
  const legendId = useId()
260
266
 
261
267
  const convertLineToBarGraph = isConvertLineToBarGraph(config, filteredData)
268
+ const xAxisDataKey = config.xAxis?.dataKey
269
+ const xAxisType = config.xAxis?.type
270
+ const xAxisCategoryOrderType = config.xAxis?.categoryOrderType
271
+ const xAxisCategoryOrder = config.xAxis?.categoryOrder
272
+ const categoryOrderConfig = useMemo(
273
+ () =>
274
+ ({
275
+ xAxis: {
276
+ dataKey: xAxisDataKey,
277
+ type: xAxisType,
278
+ categoryOrderType: xAxisCategoryOrderType,
279
+ categoryOrder: xAxisCategoryOrder
280
+ }
281
+ } as Pick<ChartConfig, 'xAxis'>),
282
+ [xAxisDataKey, xAxisType, xAxisCategoryOrderType, xAxisCategoryOrder]
283
+ )
262
284
 
263
285
  // Declaratively calculate series keys for pie charts based on filtered data
264
286
  const pieSeriesKeys = useMemo(() => {
265
- if (config.visualizationType !== 'Pie' || !config.xAxis?.dataKey) return null
287
+ if (visualizationType !== 'Pie' || !xAxisDataKey) return null
266
288
  const data = filteredData?.length > 0 ? filteredData : excludedData
267
289
  if (!data) return null
268
- return uniq(data.map(d => d[config.xAxis.dataKey]))
269
- }, [config.visualizationType, config.xAxis?.dataKey, filteredData, excludedData])
290
+ return uniq(sortByCategoryOrder(data, categoryOrderConfig).map(d => d[xAxisDataKey]))
291
+ }, [visualizationType, xAxisDataKey, categoryOrderConfig, filteredData, excludedData])
270
292
 
271
293
  const prepareConfig = (loadedConfig: ChartConfig) => {
272
294
  // Create defaults without version to avoid overriding legacy configs
@@ -454,13 +476,15 @@ const CdcChart: React.FC<CdcChartProps> = ({
454
476
  if (newConfig.visualizationType === 'Pie') {
455
477
  // Use the same data that will be passed to PieChart (after exclusions and filters)
456
478
  const pieData = currentData.length > 0 ? currentData : newExcludedData
457
- newConfig.runtime.seriesKeys = uniq(pieData.map(d => d[newConfig.xAxis.dataKey]))
479
+ newConfig.runtime.seriesKeys = uniq(sortByCategoryOrder(pieData, newConfig).map(d => d[newConfig.xAxis.dataKey]))
458
480
  newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
459
481
  newConfig.runtime.isPieChart = true // Flag to know when to use derived keys
460
482
  } else if (newConfig.visualizationType === 'Radar') {
461
483
  // Radar chart: seriesKeys are the entity names from xAxis.dataKey
462
484
  const radarData = currentData.length > 0 ? currentData : newExcludedData
463
- newConfig.runtime.seriesKeys = uniq(radarData.map(d => d[newConfig.xAxis.dataKey]))
485
+ newConfig.runtime.seriesKeys = uniq(
486
+ sortByCategoryOrder(radarData, newConfig).map(d => d[newConfig.xAxis.dataKey])
487
+ )
464
488
  newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
465
489
  } else {
466
490
  const finalData = dataOverride || newConfig.formattedData || newConfig.data
@@ -480,7 +504,9 @@ const CdcChart: React.FC<CdcChartProps> = ({
480
504
  lineType: series.lineType,
481
505
  originalDataKey: series.dataKey,
482
506
  dynamicCategory: series.dynamicCategory,
483
- tooltip: true
507
+ tooltip: true,
508
+ weight: series.weight,
509
+ axis: series.axis
484
510
  })
485
511
  })
486
512
  // return the series keys
@@ -565,7 +591,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
565
591
  ...newConfig.legend,
566
592
  position: newConfig.legend?.position || 'top',
567
593
  style: newConfig.legend?.style || 'gradient',
568
- subStyle: newConfig.legend?.subStyle || 'smooth'
594
+ subStyle:
595
+ newConfig.legend?.subStyle === 'smooth' ? 'linear blocks' : newConfig.legend?.subStyle || 'linear blocks'
569
596
  }
570
597
  newConfig.yAxis = {
571
598
  ...newConfig.yAxis,
@@ -769,6 +796,17 @@ const CdcChart: React.FC<CdcChartProps> = ({
769
796
  return newConfig
770
797
  }
771
798
 
799
+ const configDataDependency = configObj?.data?.length ? configObj.data : null
800
+ const configDataMetadataDependency = configObj?.dataMetadata ? JSON.stringify(configObj.dataMetadata) : null
801
+ const configExclusionsDependency = [
802
+ configObj?.xAxis?.type,
803
+ configObj?.xAxis?.dataKey,
804
+ configObj?.exclusions?.active,
805
+ configObj?.exclusions?.dateStart,
806
+ configObj?.exclusions?.dateEnd,
807
+ ...(configObj?.exclusions?.keys || [])
808
+ ].join('|')
809
+
772
810
  useEffect(() => {
773
811
  const load = async () => {
774
812
  try {
@@ -790,7 +828,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
790
828
  }
791
829
 
792
830
  load()
793
- }, [configObj?.data?.length ? configObj.data : null])
831
+ }, [configDataDependency, configDataMetadataDependency, configExclusionsDependency])
794
832
 
795
833
  /**
796
834
  * When cove has a config and container ref publish the cove_loaded event.
@@ -1041,6 +1079,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1041
1079
  rightSuffix,
1042
1080
  bottomPrefix,
1043
1081
  bottomSuffix,
1082
+ bottomCommas,
1044
1083
  bottomAbbreviated,
1045
1084
  preserveOriginalDecimals
1046
1085
  }
@@ -1136,7 +1175,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1136
1175
 
1137
1176
  if (
1138
1177
  (axis === 'left' && commas && abbreviated && shouldAbbreviate) ||
1139
- (axis === 'bottom' && commas && abbreviated && shouldAbbreviate)
1178
+ (axis === 'bottom' && bottomCommas && bottomAbbreviated && shouldAbbreviate)
1140
1179
  ) {
1141
1180
  num = num // eslint-disable-line
1142
1181
  } else {
@@ -1226,9 +1265,14 @@ const CdcChart: React.FC<CdcChartProps> = ({
1226
1265
  return config?.xAxis?.dataKey ? transform.cleanData(data, excludedKey, keysToClean) : data
1227
1266
  }
1228
1267
 
1268
+ const orderedTableData = useMemo(
1269
+ () => sortByCategoryOrder(filteredData || excludedData, categoryOrderConfig),
1270
+ [filteredData, excludedData, categoryOrderConfig]
1271
+ )
1272
+
1229
1273
  const getTableRuntimeData = () => {
1230
1274
  if (visualizationType === 'Sankey') return config?.data?.[0]?.tableData
1231
- const data = filteredData || excludedData
1275
+ const data = orderedTableData
1232
1276
  if (config.visualizationType === 'Pie' && !config.dataFormat?.showPiePercent) {
1233
1277
  return getPiePercent(data, config?.yAxis?.dataKey)
1234
1278
  }
@@ -1254,7 +1298,18 @@ const CdcChart: React.FC<CdcChartProps> = ({
1254
1298
  }
1255
1299
 
1256
1300
  // Transform and clean data for chart rendering
1257
- const transformedData = getTransformedData({ brushData: state.brushData, filteredData, excludedData, clean })
1301
+ const transformedData = sortByCategoryOrder(
1302
+ getTransformedData({ brushData: state.brushData, filteredData, excludedData, clean }),
1303
+ config
1304
+ )
1305
+ const configYAxisDomainData = (config as ChartConfig).yAxisDomainData
1306
+ const yAxisDomainData = useMemo(() => {
1307
+ if (Array.isArray(configYAxisDomainData) && configYAxisDomainData.length > 0) {
1308
+ return clean(getExcludedData(config, configYAxisDomainData))
1309
+ }
1310
+
1311
+ return clean(excludedData)
1312
+ }, [config, configYAxisDomainData, excludedData])
1258
1313
 
1259
1314
  // Filter annotations to only those visible in current data view
1260
1315
  const visibleAnnotations = getVisibleAnnotations(config.annotations, transformedData, config.xAxis?.dataKey)
@@ -1403,11 +1458,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1403
1458
  {isDashboard && config.table && config.table.show && config.table.showDataTableLink
1404
1459
  ? tableLink
1405
1460
  : link && link}
1406
- {(config.xAxis.dataKey &&
1407
- config.table.show &&
1408
- config.visualizationType !== 'Spark Line' &&
1409
- config.visualizationType !== 'Sankey') ||
1410
- (config.visualizationType === 'Sankey' && config.table.show)
1461
+ {chartDataTableIsRendered
1411
1462
  ? (() => {
1412
1463
  let dataTableConfig = pivotDynamicSeries(config)
1413
1464
  let dataTableColumns = config.columns
@@ -1433,6 +1484,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1433
1484
  <DataTable
1434
1485
  key={config.table?.defaultSort?.column || ''}
1435
1486
  config={dataTableConfig}
1487
+ dataConfig={config.dataKey ? datasets?.[config.dataKey] : undefined}
1436
1488
  rawData={dataTableRawData}
1437
1489
  runtimeData={dataTableRuntimeData}
1438
1490
  expandDataTable={config.table.expanded}
@@ -1701,12 +1753,13 @@ const CdcChart: React.FC<CdcChartProps> = ({
1701
1753
  setSharedFilter,
1702
1754
  setSharedFilterValue,
1703
1755
  svgRef,
1704
- tableData: filteredData || excludedData,
1756
+ tableData: orderedTableData,
1705
1757
  transformedData,
1706
1758
  twoColorPalette,
1707
1759
  unfilteredData: stateData,
1708
1760
  updateConfig,
1709
- visibleAnnotations
1761
+ visibleAnnotations,
1762
+ yAxisDomainData
1710
1763
  }
1711
1764
 
1712
1765
  return (
@@ -0,0 +1,47 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { expect } from 'storybook/test'
3
+ import Chart from '../CdcChart'
4
+ import pieConfig from './_mock/pie_with_data.json'
5
+ import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
6
+ import { assertVisualizationRendered, waitForPresence } from '@cdc/core/helpers/testing'
7
+
8
+ const meta: Meta<typeof Chart> = {
9
+ title: 'Components/Templates/Chart/Pie Labels',
10
+ component: Chart
11
+ }
12
+
13
+ type Story = StoryObj<typeof Chart>
14
+
15
+ export const CategoryPercentageLabels: Story = {
16
+ args: {
17
+ config: editConfigKeys(pieConfig, [
18
+ { path: ['labels'], value: true },
19
+ { path: ['legend', 'hide'], value: true }
20
+ ]),
21
+ isEditor: true
22
+ },
23
+ play: async ({ canvasElement }) => {
24
+ await assertVisualizationRendered(canvasElement)
25
+ await waitForPresence('.pie-label', canvasElement)
26
+ expect(canvasElement.querySelector('line')).toBeNull()
27
+ }
28
+ }
29
+
30
+ export const DonutCategoryPercentageLabels: Story = {
31
+ args: {
32
+ config: editConfigKeys(pieConfig, [
33
+ { path: ['labels'], value: true },
34
+ { path: ['legend', 'hide'], value: true },
35
+ { path: ['pieType'], value: 'Donut' }
36
+ ]),
37
+ isEditor: true
38
+ },
39
+ play: async ({ canvasElement }) => {
40
+ await assertVisualizationRendered(canvasElement)
41
+ await waitForPresence('.pie-label--outside', canvasElement)
42
+ expect(canvasElement.querySelector('.pie-label--inside')).toBeNull()
43
+ expect(canvasElement.querySelector('line')).toBeNull()
44
+ }
45
+ }
46
+
47
+ export default meta
@@ -0,0 +1,183 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import Chart from '../CdcChartComponent'
3
+ import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
4
+ import { expect, userEvent, waitFor, within } from 'storybook/test'
5
+
6
+ const meta: Meta<typeof Chart> = {
7
+ title: 'Components/Templates/Chart/Series Color Assignments',
8
+ component: Chart
9
+ }
10
+
11
+ type Story = StoryObj<typeof Chart>
12
+
13
+ const assignedColors = {
14
+ alpha: '#0057B7',
15
+ bravo: '#D94E5F',
16
+ charlie: '#5A8E3F'
17
+ }
18
+
19
+ const normalizeColor = (color: string) => {
20
+ const swatch = document.createElement('div')
21
+ swatch.style.color = color
22
+ document.body.appendChild(swatch)
23
+ const normalized = getComputedStyle(swatch).color
24
+ swatch.remove()
25
+ return normalized
26
+ }
27
+
28
+ const getVisiblePointColorsBySeries = (canvasElement: HTMLElement) => {
29
+ const groups = Array.from(canvasElement.querySelectorAll('.line-chart-group .visx-glyph-group')) as SVGGElement[]
30
+ const colorsBySeries = new Map<string, Set<string>>()
31
+
32
+ groups.forEach(group => {
33
+ const seriesIndex = group.getAttribute('data-seriesindex')
34
+ const glyph = group.querySelector('[fill]') as SVGElement | null
35
+ const fill = glyph?.getAttribute('fill')
36
+
37
+ if (!seriesIndex || !fill || fill === 'transparent') return
38
+
39
+ const seriesColors = colorsBySeries.get(seriesIndex) || new Set<string>()
40
+ seriesColors.add(normalizeColor(fill))
41
+ colorsBySeries.set(seriesIndex, seriesColors)
42
+ })
43
+
44
+ return colorsBySeries
45
+ }
46
+
47
+ const expectVisibleSeriesColors = async (
48
+ canvasElement: HTMLElement,
49
+ expected: Array<[seriesIndex: string, color: string]>
50
+ ) => {
51
+ await waitFor(() => {
52
+ const colorsBySeries = getVisiblePointColorsBySeries(canvasElement)
53
+ const expectedSeries = expected.map(([seriesIndex]) => seriesIndex)
54
+
55
+ expect(Array.from(colorsBySeries.keys()).sort()).toEqual(expectedSeries.sort())
56
+
57
+ expected.forEach(([seriesIndex, color]) => {
58
+ expect(Array.from(colorsBySeries.get(seriesIndex) || [])).toEqual([normalizeColor(color)])
59
+ })
60
+ })
61
+ }
62
+
63
+ const expectLegendLabels = async (canvasElement: HTMLElement, expectedLabels: string[]) => {
64
+ await waitFor(() => {
65
+ const labels = Array.from(canvasElement.querySelectorAll('.legend-container [role="button"]')).map(item =>
66
+ item.textContent?.trim()
67
+ )
68
+
69
+ expect(labels).toEqual(expectedLabels)
70
+ })
71
+ }
72
+
73
+ const config = {
74
+ type: 'chart',
75
+ version: '4.26.5',
76
+ title: 'Series Color Assignments With Filtered Series',
77
+ visualizationType: 'Line',
78
+ visualizationSubType: 'regular',
79
+ orientation: 'vertical',
80
+ filterBehavior: 'Filter Change',
81
+ lineDatapointStyle: 'always show',
82
+ lineDatapointColor: 'Same as Line',
83
+ general: {
84
+ palette: {
85
+ name: 'qualitative_standard',
86
+ version: '2.0',
87
+ colorAssignmentMode: 'by-value',
88
+ colorAssignments: [
89
+ { key: 'alpha', color: assignedColors.alpha },
90
+ { key: 'bravo', color: assignedColors.bravo },
91
+ { key: 'charlie', color: assignedColors.charlie }
92
+ ]
93
+ }
94
+ },
95
+ legend: {
96
+ hide: false,
97
+ behavior: 'isolate',
98
+ dynamicLegend: false,
99
+ position: 'top',
100
+ style: 'circles',
101
+ unified: false
102
+ },
103
+ xAxis: {
104
+ dataKey: 'week',
105
+ type: 'date-time',
106
+ dateParseFormat: '%Y-%m-%d',
107
+ dateDisplayFormat: '%b. %-d',
108
+ label: 'Week',
109
+ sortDates: false
110
+ },
111
+ yAxis: {
112
+ label: 'Rate',
113
+ min: '0',
114
+ max: '50',
115
+ numTicks: '5',
116
+ gridLines: true
117
+ },
118
+ visual: {
119
+ lineDatapointSymbol: 'none',
120
+ verticalHoverLine: false,
121
+ horizontalHoverLine: false
122
+ },
123
+ tooltips: {
124
+ singleSeries: false
125
+ },
126
+ series: [
127
+ { dataKey: 'alpha', name: 'Alpha', type: 'Line', axis: 'Left', tooltip: true },
128
+ { dataKey: 'bravo', name: 'Bravo', type: 'Line', axis: 'Left', tooltip: true },
129
+ { dataKey: 'charlie', name: 'Charlie', type: 'Line', axis: 'Left', tooltip: true }
130
+ ],
131
+ filters: [
132
+ {
133
+ values: ['Alpha + Bravo', 'Bravo + Charlie'],
134
+ filterStyle: 'dropdown',
135
+ id: 1726688167657,
136
+ columnName: 'view',
137
+ active: 'Alpha + Bravo',
138
+ label: 'Visible series'
139
+ }
140
+ ],
141
+ data: [
142
+ { week: '2024-01-01', view: 'Alpha + Bravo', alpha: 12, bravo: 20, charlie: null },
143
+ { week: '2024-01-08', view: 'Alpha + Bravo', alpha: 16, bravo: 23, charlie: null },
144
+ { week: '2024-01-15', view: 'Alpha + Bravo', alpha: 21, bravo: 28, charlie: null },
145
+ { week: '2024-01-22', view: 'Alpha + Bravo', alpha: 26, bravo: 31, charlie: null },
146
+ { week: '2024-01-01', view: 'Bravo + Charlie', alpha: null, bravo: 20, charlie: 30 },
147
+ { week: '2024-01-08', view: 'Bravo + Charlie', alpha: null, bravo: 23, charlie: 34 },
148
+ { week: '2024-01-15', view: 'Bravo + Charlie', alpha: null, bravo: 28, charlie: 39 },
149
+ { week: '2024-01-22', view: 'Bravo + Charlie', alpha: null, bravo: 31, charlie: 43 }
150
+ ],
151
+ dataDescription: {
152
+ horizontal: false,
153
+ series: false
154
+ }
155
+ }
156
+
157
+ export const FilteredSeriesKeepAssignedColors: Story = {
158
+ args: {
159
+ config,
160
+ isEditor: false
161
+ },
162
+ play: async ({ canvasElement }) => {
163
+ await assertVisualizationRendered(canvasElement)
164
+
165
+ await expectVisibleSeriesColors(canvasElement, [
166
+ ['0', assignedColors.alpha],
167
+ ['1', assignedColors.bravo]
168
+ ])
169
+ await expectLegendLabels(canvasElement, ['Alpha', 'Bravo'])
170
+
171
+ const canvas = within(canvasElement)
172
+ const filter = canvas.getByLabelText(/filter by visible series/i) as HTMLSelectElement
173
+ await userEvent.selectOptions(filter, 'Bravo + Charlie')
174
+
175
+ await expectVisibleSeriesColors(canvasElement, [
176
+ ['1', assignedColors.bravo],
177
+ ['2', assignedColors.charlie]
178
+ ])
179
+ await expectLegendLabels(canvasElement, ['Bravo', 'Charlie'])
180
+ }
181
+ }
182
+
183
+ export default meta
@@ -0,0 +1,52 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import Chart from '../CdcChartComponent'
3
+ import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
4
+ import smallestLeftAxisMaxConfig from './_mock/smallest_left_axis_max.json'
5
+ import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
6
+
7
+ const meta: Meta<typeof Chart> = {
8
+ title: 'Components/Templates/Chart/Y Axis Auto Max Strategy',
9
+ component: Chart
10
+ }
11
+
12
+ type Story = StoryObj<typeof Chart>
13
+
14
+ const baseConfig = editConfigKeys(smallestLeftAxisMaxConfig, [
15
+ { path: ['yAxis', 'smallestLeftAxisMax'], value: undefined },
16
+ { path: ['yAxis', 'filterDomainBehavior'], value: 'dynamic' },
17
+ { path: ['yAxis', 'numTicks'], value: 4 },
18
+ { path: ['filters', '0', 'active'], value: 'Region A' }
19
+ ])
20
+
21
+ export const Clean_Top_Tick_Strategy: Story = {
22
+ args: {
23
+ config: editConfigKeys(baseConfig, [{ path: ['yAxis', 'autoMaxStrategy'], value: 'clean-top-tick' }])
24
+ },
25
+ play: async ({ canvasElement }) => {
26
+ await assertVisualizationRendered(canvasElement)
27
+ }
28
+ }
29
+
30
+ export const Default_Strategy: Story = {
31
+ args: {
32
+ config: editConfigKeys(baseConfig, [{ path: ['yAxis', 'autoMaxStrategy'], value: 'default' }])
33
+ },
34
+ play: async ({ canvasElement }) => {
35
+ await assertVisualizationRendered(canvasElement)
36
+ }
37
+ }
38
+
39
+ export const Stable_Filter_Domain_With_Clean_Top_Tick: Story = {
40
+ args: {
41
+ config: editConfigKeys(baseConfig, [
42
+ { path: ['yAxis', 'filterDomainBehavior'], value: 'stable' },
43
+ { path: ['yAxis', 'autoMaxStrategy'], value: 'clean-top-tick' },
44
+ { path: ['filters', '0', 'active'], value: 'Region B' }
45
+ ])
46
+ },
47
+ play: async ({ canvasElement }) => {
48
+ await assertVisualizationRendered(canvasElement)
49
+ }
50
+ }
51
+
52
+ export default meta