@cdc/chart 4.25.3 → 4.25.6

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 (86) hide show
  1. package/dist/cdcchart.js +46641 -42561
  2. package/index.html +130 -129
  3. package/package.json +22 -27
  4. package/src/CdcChartComponent.tsx +75 -35
  5. package/src/_stories/Chart.CI.stories.tsx +10 -0
  6. package/src/_stories/Chart.DynamicSeries.stories.tsx +68 -49
  7. package/src/_stories/Chart.stories.tsx +99 -86
  8. package/src/_stories/ChartPrefixSuffix.stories.tsx +29 -32
  9. package/{examples/private/line-issue.json → src/_stories/_mock/barchart_labels.mock.json} +150 -35
  10. package/src/_stories/_mock/dynamic_series_bar_config.json +1 -1
  11. package/src/_stories/_mock/dynamic_series_suppression_mock.json +610 -0
  12. package/{examples/private/not-loading.json → src/_stories/_mock/pie_calculated_area.json} +152 -95
  13. package/src/components/Annotations/components/AnnotationDropdown.tsx +2 -2
  14. package/src/components/AreaChart/components/AreaChart.jsx +33 -5
  15. package/src/components/Axis/Categorical.Axis.tsx +2 -2
  16. package/src/components/BarChart/components/BarChart.Horizontal.tsx +38 -37
  17. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +18 -8
  18. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +8 -8
  19. package/src/components/BarChart/components/BarChart.Vertical.tsx +47 -36
  20. package/src/components/BarChart/components/{BarChart.jsx → BarChart.tsx} +23 -5
  21. package/src/components/BarChart/components/context.tsx +20 -2
  22. package/src/components/BarChart/helpers/getBarHeights.ts +47 -0
  23. package/src/components/BarChart/helpers/index.ts +5 -2
  24. package/src/components/BarChart/helpers/tests/getBarHeights.test.ts +83 -0
  25. package/src/{hooks → components/BarChart/helpers}/useBarChart.ts +9 -46
  26. package/src/components/BoxPlot/BoxPlot.tsx +2 -1
  27. package/src/components/Brush/BrushChart.tsx +73 -0
  28. package/src/components/Brush/BrushController..tsx +39 -0
  29. package/src/components/DeviationBar.jsx +2 -2
  30. package/src/components/EditorPanel/EditorPanel.tsx +232 -147
  31. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +36 -36
  32. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +52 -25
  33. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +12 -4
  34. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +8 -0
  35. package/src/components/EditorPanel/useEditorPermissions.ts +5 -5
  36. package/src/components/ForestPlot/ForestPlot.tsx +2 -2
  37. package/src/components/HoverLine/HoverLine.tsx +74 -0
  38. package/src/components/Legend/Legend.Component.tsx +1 -1
  39. package/src/components/Legend/Legend.Suppression.tsx +59 -25
  40. package/src/components/Legend/helpers/createFormatLabels.tsx +28 -0
  41. package/src/components/Legend/helpers/index.ts +1 -1
  42. package/src/components/LineChart/LineChartProps.ts +3 -1
  43. package/src/components/LineChart/components/LineChart.Circle.tsx +72 -119
  44. package/src/components/LineChart/helpers.ts +133 -56
  45. package/src/components/LineChart/index.tsx +106 -55
  46. package/src/components/LinearChart.tsx +178 -198
  47. package/src/components/PairedBarChart.jsx +3 -2
  48. package/src/components/PieChart/PieChart.tsx +127 -102
  49. package/src/components/ScatterPlot/ScatterPlot.jsx +5 -0
  50. package/src/components/Sparkline/components/SparkLine.tsx +80 -18
  51. package/src/data/initial-state.js +11 -6
  52. package/src/helpers/countNumOfTicks.ts +1 -1
  53. package/src/helpers/dataHelpers.ts +23 -2
  54. package/src/helpers/getNewRuntime.ts +35 -0
  55. package/src/helpers/getPiePercent.ts +22 -0
  56. package/src/helpers/getTransformedData.ts +22 -0
  57. package/src/helpers/sizeHelpers.ts +1 -1
  58. package/src/helpers/tests/getNewRuntime.test.ts +82 -0
  59. package/src/helpers/tests/getPiePercent.test.ts +38 -0
  60. package/src/hooks/useMinMax.ts +21 -28
  61. package/src/hooks/useRightAxis.ts +5 -3
  62. package/src/hooks/useScales.ts +15 -6
  63. package/src/hooks/useTooltip.tsx +218 -203
  64. package/src/index.jsx +2 -2
  65. package/src/scss/main.scss +13 -6
  66. package/src/store/chart.actions.ts +2 -6
  67. package/src/store/chart.reducer.ts +23 -23
  68. package/src/types/ChartConfig.ts +11 -3
  69. package/src/types/ChartContext.ts +0 -2
  70. package/examples/private/DEV-8850-2.json +0 -493
  71. package/examples/private/DEV-9822.json +0 -574
  72. package/examples/private/DEV-9840.json +0 -553
  73. package/examples/private/DEV-9850-3.json +0 -461
  74. package/examples/private/chart.json +0 -1084
  75. package/examples/private/ci_formatted.json +0 -202
  76. package/examples/private/ci_issue.json +0 -3016
  77. package/examples/private/completed.json +0 -634
  78. package/examples/private/dem-data-long.csv +0 -20
  79. package/examples/private/dem-data-long.json +0 -36
  80. package/examples/private/demographic_data.csv +0 -157
  81. package/examples/private/demographic_data.json +0 -2654
  82. package/examples/private/demographic_dynamic.json +0 -443
  83. package/examples/private/demographic_standard.json +0 -560
  84. package/examples/private/ehdi.json +0 -29939
  85. package/examples/private/test.json +0 -493
  86. package/src/components/ZoomBrush.tsx +0 -251
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { getNewRuntime } from '../getNewRuntime'
3
+
4
+ describe('getNewRuntime', () => {
5
+ it('should return a runtime object with default values when no data is provided', () => {
6
+ const visualizationConfig = { runtime: {} }
7
+ const newFilteredData = null
8
+
9
+ const result = getNewRuntime(visualizationConfig, newFilteredData)
10
+
11
+ expect(result.series).toEqual([])
12
+ expect(result.seriesLabels).toEqual({})
13
+ expect(result.seriesLabelsAll).toEqual([])
14
+ expect(result.seriesKeys).toEqual([])
15
+ })
16
+
17
+ it('should populate runtime.series with valid series from newFilteredData', () => {
18
+ const visualizationConfig = {
19
+ runtime: {},
20
+ filters: [],
21
+ columns: {},
22
+ dynamicSeriesType: 'bar',
23
+ dynamicSeriesLineType: 'solid',
24
+ xAxis: { dataKey: 'x' }
25
+ }
26
+ const newFilteredData = [
27
+ { x: 1, y: 10, z: 20 },
28
+ { x: 2, y: 15, z: 25 }
29
+ ]
30
+
31
+ const result = getNewRuntime(visualizationConfig, newFilteredData)
32
+
33
+ expect(result.series).toEqual([
34
+ { dataKey: 'y', type: 'bar', lineType: 'solid', tooltip: true },
35
+ { dataKey: 'z', type: 'bar', lineType: 'solid', tooltip: true }
36
+ ])
37
+ expect(result.seriesKeys).toEqual(['y', 'z'])
38
+ expect(result.seriesLabels).toEqual({ y: 'y', z: 'z' })
39
+ expect(result.seriesLabelsAll).toEqual(['y', 'z'])
40
+ })
41
+
42
+ it('should exclude series keys that match filters or columns', () => {
43
+ const visualizationConfig = {
44
+ runtime: {},
45
+ filters: [{ columnName: 'y' }],
46
+ columns: { z: {} },
47
+ dynamicSeriesType: 'bar',
48
+ dynamicSeriesLineType: 'solid',
49
+ xAxis: { dataKey: 'x' }
50
+ }
51
+ const newFilteredData = [
52
+ { x: 1, y: 10, z: 20, w: 30 },
53
+ { x: 2, y: 15, z: 25, w: 35 }
54
+ ]
55
+
56
+ const result = getNewRuntime(visualizationConfig, newFilteredData)
57
+
58
+ expect(result.series).toEqual([{ dataKey: 'w', type: 'bar', lineType: 'solid', tooltip: true }])
59
+ expect(result.seriesKeys).toEqual(['w'])
60
+ expect(result.seriesLabels).toEqual({ w: 'w' })
61
+ expect(result.seriesLabelsAll).toEqual(['w'])
62
+ })
63
+
64
+ it('should handle empty newFilteredData gracefully', () => {
65
+ const visualizationConfig = {
66
+ runtime: {},
67
+ filters: [],
68
+ columns: {},
69
+ dynamicSeriesType: 'bar',
70
+ dynamicSeriesLineType: 'solid',
71
+ xAxis: { dataKey: 'x' }
72
+ }
73
+ const newFilteredData = []
74
+
75
+ const result = getNewRuntime(visualizationConfig, newFilteredData)
76
+
77
+ expect(result.series).toEqual([])
78
+ expect(result.seriesKeys).toEqual([])
79
+ expect(result.seriesLabels).toEqual({})
80
+ expect(result.seriesLabelsAll).toEqual([])
81
+ })
82
+ })
@@ -0,0 +1,38 @@
1
+ // getPiePercent.test.ts
2
+ import { getPiePercent } from '../getPiePercent'
3
+
4
+ describe('getPiePercent', () => {
5
+ it('cgets percentages for purely numeric strings', () => {
6
+ const data = [{ A: '1' }, { A: '3' }, { A: '6' }]
7
+ const result = getPiePercent(data, 'A')
8
+
9
+ // sum = 1 + 3 + 6 = 10
10
+ expect(result[0].A).toBeCloseTo((1 / 10) * 100) // 10%
11
+ expect(result[1].A).toBeCloseTo((3 / 10) * 100) // 30%
12
+ expect(result[2].A).toBeCloseTo((6 / 10) * 100) // 60%
13
+ })
14
+
15
+ it('shandle non numbers like "ABC', () => {
16
+ const data = [{ A: '1' }, { A: 'ABC' }, { A: '2' }]
17
+ const result = getPiePercent(data, 'A')
18
+
19
+ expect(result[0].A).toBeCloseTo((1 / 3) * 100)
20
+ expect(result[1].A).toBe('ABC')
21
+ expect(result[2].A).toBeCloseTo((2 / 3) * 100)
22
+ })
23
+
24
+ it('handles all-zero total by producing 0%', () => {
25
+ const data = [{ A: '0' }, { A: '0' }]
26
+ const result = getPiePercent(data, 'A')
27
+ expect(result[0].A).toBe(0)
28
+ expect(result[1].A).toBe(0)
29
+ })
30
+
31
+ it('leaves rows missing the key entirely unchanged', () => {
32
+ const data = [{ A: '2' }, { B: 'foo' }]
33
+ const result = getPiePercent(data, 'A')
34
+
35
+ expect(result[0].A).toBeCloseTo(100)
36
+ expect(result[1]).toEqual({ B: 'foo' })
37
+ })
38
+ })
@@ -46,7 +46,6 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
46
46
 
47
47
  min = enteredMinValue && isMinValid ? Number(enteredMinValue) : minValue
48
48
  max = enteredMaxValue && isMaxValid ? Number(enteredMaxValue) : Number.MIN_VALUE
49
-
50
49
  const { lower, upper } = config?.confidenceKeys || {}
51
50
 
52
51
  if (lower && upper && config.visualizationType === 'Bar') {
@@ -167,35 +166,28 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
167
166
  }
168
167
 
169
168
  if (config.visualizationType === 'Line' && !convertLineToBarGraph) {
170
- const isMinValid = isLogarithmicAxis
171
- ? Number(enteredMinValue) >= 0 && Number(enteredMinValue) < minValue
172
- : Number(enteredMinValue) < minValue
173
- // update minValue for (0) Suppression points
174
- const suppressedMinValue = tableData?.some((dataItem, index) => {
175
- return config.preliminaryData?.some(pd => {
176
- if (pd.type !== 'suppression' || !pd.style) return false
177
-
178
- // Filter data item based on current series keys and check if pd.value is present
179
- const relevantData = _.pick(dataItem, config.runtime?.seriesKeys)
180
- const isValuePresent = _.values(relevantData).includes(pd.value)
181
-
182
- // Check for value match condition
183
- const valueMatch = pd.column ? dataItem[pd.column] === pd.value : isValuePresent
184
-
185
- // Return true if the value matches and it's either the first or the last item
186
- return valueMatch && (index === 0 || index === tableData.length - 1)
169
+ const numEnteredMin = Number(enteredMinValue)
170
+ const isMinValid = isLogarithmicAxis ? numEnteredMin >= 0 && numEnteredMin < minValue : numEnteredMin < minValue
171
+
172
+ const suppressedMinValue = tableData?.some((item, i, arr) =>
173
+ config.preliminaryData?.some(({ type, style, column, value }) => {
174
+ if (type !== 'suppression' || !style) return false
175
+
176
+ const values = _.values(_.pick(item, config.runtime?.seriesKeys))
177
+ const dynamicCategory = config.series[0].dynamicCategory
178
+
179
+ const match = column ? item[column] === value : values.includes(value)
180
+ const dynamic = dynamicCategory && (item[dynamicCategory] === column || !column)
181
+
182
+ return (match || dynamic) && (i === 0 || i === arr.length - 1)
187
183
  })
188
- })
189
- let isCategoricalAxis = config.yAxis.type === 'categorical'
190
- min =
191
- enteredMinValue !== '' && isMinValid
192
- ? Number(enteredMinValue)
193
- : suppressedMinValue
194
- ? 0
195
- : isCategoricalAxis
196
- ? 0
197
- : minValue
184
+ )
185
+
186
+ const isCategorical = config.yAxis.type === 'categorical'
187
+
188
+ min = enteredMinValue !== '' && isMinValid ? numEnteredMin : suppressedMinValue ? 0 : isCategorical ? 0 : minValue
198
189
  }
190
+
199
191
  //If data value max wasn't provided, calculate it
200
192
  if (max === Number.MIN_VALUE) {
201
193
  // if all values in data are negative set max = 0
@@ -241,6 +233,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
241
233
 
242
234
  if (config.visualizationType === 'Scatter Plot') {
243
235
  max = max * 1.1
236
+ min = min / 1.1
244
237
  }
245
238
 
246
239
  return { min, max, leftMax, rightMax }
@@ -1,9 +1,11 @@
1
1
  import { scaleLinear } from '@visx/scale'
2
2
  import useReduceData from './useReduceData'
3
+ import { TOP_PADDING } from './useScales'
3
4
 
4
- export default function useRightAxis({ config, yMax = 0, data = [], updateConfig }) {
5
+ export default function useRightAxis({ config, yMax = 0, data = [] }) {
5
6
  const hasRightAxis = config.visualizationType === 'Combo' && config.orientation === 'vertical'
6
- const rightSeriesKeys = config.series && config.series.filter(series => series.axis === 'Right').map(key => key.dataKey)
7
+ const rightSeriesKeys =
8
+ config.series && config.series.filter(series => series.axis === 'Right').map(key => key.dataKey)
7
9
  let { minValue } = useReduceData(config, data)
8
10
 
9
11
  const allRightAxisData = rightSeriesKeys => {
@@ -35,7 +37,7 @@ export default function useRightAxis({ config, yMax = 0, data = [], updateConfig
35
37
 
36
38
  const yScaleRight = scaleLinear({
37
39
  domain: [minValue, max],
38
- range: [yMax, 0]
40
+ range: [yMax, TOP_PADDING]
39
41
  })
40
42
 
41
43
  return { yScaleRight, hasRightAxis }
@@ -22,6 +22,8 @@ const scaleTypes = {
22
22
  BAND: 'band'
23
23
  }
24
24
 
25
+ export const TOP_PADDING = 10
26
+
25
27
  type useScaleProps = {
26
28
  config: ChartConfig // standard chart config
27
29
  data: Object[] // standard data array
@@ -77,14 +79,21 @@ const useScales = (properties: useScaleProps) => {
77
79
  xScale = composeScaleBand(xAxisDataMappedSorted, [0, xMax], 1 - config.barThickness)
78
80
  }
79
81
 
82
+ // handle Linear scaled viz
83
+ if (config.xAxis.type === 'date' && !isHorizontal) {
84
+ const sorted = sortXAxisData(xAxisDataMapped, config.xAxis.sortByRecentDate)
85
+
86
+ xScale = composeScaleBand(sorted, [0, xMax], 1 - config.barThickness)
87
+ xScale.type = scaleTypes.BAND
88
+ }
89
+
80
90
  if (xAxis.type === 'date-time' || xAxis.type === 'continuous') {
81
91
  let xAxisMin = Math.min(...xAxisDataMapped.map(Number))
82
92
  let xAxisMax = Math.max(...xAxisDataMapped.map(Number))
83
- xAxisMin -= (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
84
- xAxisMax +=
85
- visualizationType === 'Line'
86
- ? 0
87
- : (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
93
+ let paddingRatio = config.xAxis.padding ? config.xAxis.padding * 0.01 : 0
94
+
95
+ xAxisMin -= paddingRatio * (xAxisMax - xAxisMin)
96
+ xAxisMax += visualizationType === 'Line' ? 0 : paddingRatio * (xAxisMax - xAxisMin)
88
97
  const range = config.xAxis.sortByRecentDate ? [xMax, 0] : [0, xMax]
89
98
  xScale = scaleTime({
90
99
  domain: [xAxisMin, xAxisMax],
@@ -392,7 +401,7 @@ const composeYScale = ({ min, max, yMax, config, leftMax }) => {
392
401
 
393
402
  // If the visualization type is a bump chart then the domain and range need different values
394
403
  const domainSet = config.visualizationType === 'Bump Chart' ? [1, max] : [min, max]
395
- const yRange = config.visualizationType === 'Bump Chart' ? [30, yMax] : [yMax, 0]
404
+ const yRange = config.visualizationType === 'Bump Chart' ? [30, yMax] : [yMax, TOP_PADDING]
396
405
  // Return the configured scale function
397
406
  return scaleFunc({
398
407
  domain: domainSet,