@cdc/chart 4.24.10 → 4.24.12

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 (93) hide show
  1. package/dist/cdcchart.js +34651 -33978
  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/test.json +493 -0
  22. package/index.html +10 -7
  23. package/package.json +2 -2
  24. package/src/CdcChart.tsx +132 -152
  25. package/src/_stories/Chart.Anchors.stories.tsx +31 -0
  26. package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
  27. package/src/_stories/Chart.DynamicSeries.stories.tsx +34 -0
  28. package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
  29. package/src/_stories/Chart.stories.tsx +37 -6
  30. package/src/_stories/ChartAxisLabels.stories.tsx +4 -1
  31. package/src/_stories/ChartEditor.stories.tsx +27 -0
  32. package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
  33. package/src/_stories/ChartPrefixSuffix.stories.tsx +8 -0
  34. package/{examples/feature/area/area-chart-date-city-temperature.json → src/_stories/_mock/area_chart_stacked.json} +125 -27
  35. package/src/_stories/_mock/boxplot_multiseries.json +647 -0
  36. package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
  37. package/src/_stories/_mock/dynamic_series_config.json +979 -0
  38. package/src/_stories/_mock/line_chart_dynamic_ci.json +493 -0
  39. package/src/_stories/_mock/line_chart_non_dynamic_ci.json +522 -0
  40. package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
  41. package/src/_stories/_mock/short_dates.json +288 -0
  42. package/src/_stories/_mock/suppression_mock.json +1549 -0
  43. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +15 -3
  44. package/src/components/Axis/Categorical.Axis.tsx +2 -2
  45. package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -37
  46. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
  47. package/src/components/BarChart/components/BarChart.Vertical.tsx +53 -47
  48. package/src/components/BarChart/helpers/getBarData.ts +28 -0
  49. package/src/components/BarChart/helpers/index.ts +1 -2
  50. package/src/components/BarChart/helpers/tests/getBarData.test.ts +74 -0
  51. package/src/components/BoxPlot/BoxPlot.tsx +131 -0
  52. package/src/components/BoxPlot/helpers/index.ts +54 -0
  53. package/src/components/BrushChart.tsx +23 -26
  54. package/src/components/EditorPanel/EditorPanel.tsx +117 -139
  55. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +3 -3
  56. package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
  57. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
  58. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
  59. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +122 -56
  60. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
  61. package/src/components/EditorPanel/useEditorPermissions.ts +20 -2
  62. package/src/components/Legend/Legend.Component.tsx +11 -12
  63. package/src/components/Legend/Legend.tsx +16 -16
  64. package/src/components/Legend/helpers/getLegendClasses.ts +59 -0
  65. package/src/components/Legend/helpers/index.ts +2 -1
  66. package/src/components/Legend/tests/getLegendClasses.test.ts +115 -0
  67. package/src/components/LineChart/components/LineChart.Circle.tsx +1 -1
  68. package/src/components/LineChart/helpers.ts +49 -43
  69. package/src/components/LineChart/index.tsx +135 -83
  70. package/src/components/LinearChart.tsx +187 -181
  71. package/src/components/PieChart/PieChart.tsx +7 -1
  72. package/src/components/Sankey/components/ColumnList.tsx +19 -0
  73. package/src/components/Sankey/components/Sankey.tsx +479 -0
  74. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +33 -0
  75. package/src/components/Sankey/index.tsx +1 -492
  76. package/src/components/Sankey/sankey.scss +22 -21
  77. package/src/components/Sankey/types/index.ts +1 -1
  78. package/src/components/Sankey/useSankeyAlert.tsx +60 -0
  79. package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
  80. package/src/data/initial-state.js +7 -12
  81. package/src/helpers/countNumOfTicks.ts +57 -0
  82. package/src/helpers/getQuartiles.ts +15 -18
  83. package/src/hooks/useMinMax.ts +44 -16
  84. package/src/hooks/useReduceData.ts +43 -10
  85. package/src/hooks/useScales.ts +90 -35
  86. package/src/hooks/useTooltip.tsx +59 -50
  87. package/src/scss/DataTable.scss +5 -0
  88. package/src/scss/main.scss +6 -20
  89. package/src/types/ChartConfig.ts +6 -19
  90. package/src/types/ChartContext.ts +4 -1
  91. package/src/types/ForestPlot.ts +8 -0
  92. package/src/components/BoxPlot/BoxPlot.jsx +0 -111
  93. package/src/hooks/useLegendClasses.ts +0 -72
@@ -15,7 +15,8 @@ export const getMarginTop = (isBottomOrSmallViewport, config) => {
15
15
  return '0px'
16
16
  }
17
17
  if (isBottomOrSmallViewport && config.brush?.active) {
18
- return '35px'
18
+ const BRUSH_HEIGHT_MULTIPLIER = 1.5
19
+ return `${config.brush.height * BRUSH_HEIGHT_MULTIPLIER}px`
19
20
  }
20
21
  return '20px'
21
22
  }
@@ -0,0 +1,115 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { getLegendClasses } from './../helpers/getLegendClasses'
3
+ import { ChartConfig } from '../../../types/ChartConfig'
4
+
5
+ describe('getLegendClasses', () => {
6
+ it('should return correct classes for left position', () => {
7
+ const config: ChartConfig = {
8
+ legend: {
9
+ position: 'left',
10
+ singleRow: false,
11
+ reverseLabelOrder: false,
12
+ verticalSorted: false,
13
+ hideBorder: { side: false, topBottom: false }
14
+ }
15
+ }
16
+ const result = getLegendClasses(config)
17
+ expect(result.containerClasses).toContain('left')
18
+ // Left Position Charts can't have single row...
19
+ expect(result.innerClasses).not.toContain('single-row')
20
+ })
21
+
22
+ it('should return correct classes for right position', () => {
23
+ const config: ChartConfig = {
24
+ legend: {
25
+ position: 'right',
26
+ singleRow: false,
27
+ reverseLabelOrder: false,
28
+ verticalSorted: false,
29
+ hideBorder: { side: false, topBottom: false }
30
+ }
31
+ }
32
+ const result = getLegendClasses(config)
33
+ expect(result.containerClasses).toContain('right')
34
+ // Right Position Charts can't have single row...
35
+ expect(result.innerClasses).not.toContain('single-row')
36
+ })
37
+
38
+ it('should return correct classes for bottom position with single row', () => {
39
+ const config: ChartConfig = {
40
+ legend: {
41
+ position: 'bottom',
42
+ singleRow: true,
43
+ reverseLabelOrder: false,
44
+ verticalSorted: false,
45
+ hideBorder: { side: false, topBottom: false }
46
+ }
47
+ }
48
+ const result = getLegendClasses(config)
49
+ expect(result.containerClasses).toContain('bottom')
50
+ expect(result.innerClasses).toContain('double-column')
51
+ expect(result.innerClasses).toContain('bottom')
52
+ expect(result.innerClasses).toContain('single-row')
53
+ })
54
+
55
+ it('should return correct classes for top position with vertical sorting', () => {
56
+ const config: ChartConfig = {
57
+ legend: {
58
+ position: 'top',
59
+ singleRow: false,
60
+ reverseLabelOrder: false,
61
+ verticalSorted: true,
62
+ hideBorder: { side: false, topBottom: false }
63
+ }
64
+ }
65
+ const result = getLegendClasses(config)
66
+ expect(result.containerClasses).toContain('top')
67
+ expect(result.innerClasses).toContain('double-column')
68
+ expect(result.innerClasses).toContain('top')
69
+ expect(result.innerClasses).toContain('vertical-sorted')
70
+ })
71
+
72
+ it('should return correct classes for reverse label order', () => {
73
+ const config: ChartConfig = {
74
+ legend: {
75
+ position: 'bottom',
76
+ singleRow: false,
77
+ reverseLabelOrder: true,
78
+ verticalSorted: false,
79
+ hideBorder: { side: false, topBottom: false }
80
+ }
81
+ }
82
+ const result = getLegendClasses(config)
83
+ expect(result.innerClasses).toContain('d-flex')
84
+ expect(result.innerClasses).toContain('flex-column-reverse')
85
+ })
86
+
87
+ it('should return correct classes for hide border side', () => {
88
+ const config: ChartConfig = {
89
+ legend: {
90
+ position: 'left',
91
+ singleRow: false,
92
+ reverseLabelOrder: false,
93
+ verticalSorted: false,
94
+ hideBorder: { side: true, topBottom: false }
95
+ }
96
+ }
97
+ const result = getLegendClasses(config)
98
+ expect(result.containerClasses).toContain('border-0')
99
+ })
100
+
101
+ it('should return correct classes for hide border topBottom', () => {
102
+ const config: ChartConfig = {
103
+ legend: {
104
+ position: 'top',
105
+ singleRow: false,
106
+ reverseLabelOrder: false,
107
+ verticalSorted: false,
108
+ hideBorder: { side: false, topBottom: true }
109
+ }
110
+ }
111
+ const result = getLegendClasses(config)
112
+ expect(result.containerClasses).toContain('border-0')
113
+ expect(result.containerClasses).toContain('p-0')
114
+ })
115
+ })
@@ -82,7 +82,7 @@ const LineChartCircle = (props: LineChartCircleProps) => {
82
82
  const isMatch = circleData?.some(
83
83
  cd => cd[config.xAxis.dataKey] === d[config.xAxis.dataKey] && cd[seriesKey] === d[seriesKey]
84
84
  )
85
- if (isMatch) {
85
+ if (isMatch || !filtered) {
86
86
  return <></>
87
87
  }
88
88
  return (
@@ -1,5 +1,6 @@
1
1
  import { DataItem, StyleProps, Style } from './LineChartProps'
2
2
  import { PreliminaryDataItem } from '../../types/ChartConfig'
3
+ import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
3
4
  import _ from 'lodash'
4
5
  export const createStyles = (props: StyleProps): Style[] => {
5
6
  const { preliminaryData, data, stroke, strokeWidth, handleLineType, lineType, seriesKey } = props
@@ -92,8 +93,9 @@ export const filterCircles = (
92
93
 
93
94
  const isCalculable = value => !isNaN(parseFloat(value)) && isFinite(value)
94
95
  const handleFirstIndex = (data, seriesKey, preliminaryData) => {
96
+ let pairCount = '0'
95
97
  const result = {
96
- data: [],
98
+ data: { '0': [] },
97
99
  style: ''
98
100
  }
99
101
 
@@ -116,7 +118,7 @@ const handleFirstIndex = (data, seriesKey, preliminaryData) => {
116
118
  if (suppressionData && suppressionData.style) {
117
119
  // Modify first item and add to result
118
120
  const modifiedItem = { ...firstIndexDataItem, [seriesKey]: 0 }
119
- result.data.push(modifiedItem)
121
+ result.data[pairCount].push(modifiedItem)
120
122
  result.style = suppressionData.style
121
123
 
122
124
  // Find the next calculable item index
@@ -125,19 +127,20 @@ const handleFirstIndex = (data, seriesKey, preliminaryData) => {
125
127
  nextIndex++
126
128
  }
127
129
  if (nextIndex < data.length) {
128
- result.data.push(data[nextIndex])
130
+ result.data[pairCount].push(data[nextIndex])
129
131
  }
130
132
  } else {
131
133
  // If no suppression, just add the first item
132
- result.data.push(firstIndexDataItem)
134
+ result.data[pairCount].push(firstIndexDataItem)
133
135
  }
134
136
 
135
137
  return result
136
138
  }
137
139
 
138
140
  const handleLastIndex = (data, seriesKey, preliminaryData) => {
141
+ let pairCount = '0'
139
142
  const result = {
140
- data: [],
143
+ data: { '0': [] },
141
144
  style: ''
142
145
  }
143
146
  let lastAddedIndex = -1 // Tracks the last index added to the result
@@ -152,7 +155,7 @@ const handleLastIndex = (data, seriesKey, preliminaryData) => {
152
155
  ) {
153
156
  const lastIndex = data.length - 1
154
157
  const modifiedItem = { ...data[lastIndex], [seriesKey]: 0 }
155
- result.data.push(modifiedItem)
158
+ result.data[pairCount].push(modifiedItem)
156
159
 
157
160
  // Find previous calculable item
158
161
  let prevIndex = lastIndex - 1
@@ -160,7 +163,7 @@ const handleLastIndex = (data, seriesKey, preliminaryData) => {
160
163
  prevIndex--
161
164
  }
162
165
  if (prevIndex >= 0 && lastAddedIndex !== prevIndex) {
163
- result.data.push(data[prevIndex])
166
+ result.data[pairCount].push(data[prevIndex])
164
167
  lastAddedIndex = prevIndex
165
168
  }
166
169
  result.style = pd.style
@@ -170,47 +173,48 @@ const handleLastIndex = (data, seriesKey, preliminaryData) => {
170
173
  return result
171
174
  }
172
175
 
173
- function handleMiddleIndices(data, seriesKey, dataKey, preliminaryData) {
174
- const result = {
175
- data: [],
176
+ function handleMiddleIndices(data, seriesKey, preliminaryData) {
177
+ // slice data to remove first and last object these no need for handleMiddleIndices
178
+
179
+ let result = {
180
+ data: {},
176
181
  style: ''
177
182
  }
183
+ // Variable to count the number of sibling pairs found
184
+ let pairCount = 1
185
+
186
+ // Loop through the data array to find each occurrence of the target value
187
+ data.forEach((item, index) => {
188
+ preliminaryData.forEach(pd => {
189
+ const targetValue = pd.value
190
+ if (item[seriesKey] === targetValue) {
191
+ let siblingBefore = null
192
+ let siblingAfter = null
193
+
194
+ // Find the nearest numeric sibling before the current index
195
+ for (let i = index - 1; i >= 0; i--) {
196
+ if (isCalculable(data[i][seriesKey])) {
197
+ siblingBefore = data[i]
198
+ break // Stop searching once a valid sibling is found
199
+ }
200
+ }
178
201
 
179
- const isValidMiddleIndex = index => index > 0 && index < data.length - 1
180
-
181
- preliminaryData?.forEach(pd => {
182
- if (pd.type === 'effect' || pd.hideLineStyle) return
183
- const targetValue = pd.value
184
-
185
- // Find all indices
186
- const matchingIndices = data.reduce((indices, item, index) => {
187
- if (item[seriesKey] === targetValue && isValidMiddleIndex(index) && (!pd.column || pd.column === seriesKey)) {
188
- indices.push(index)
189
- }
190
- return indices
191
- }, [])
192
-
193
- // Process each valid index
194
- matchingIndices.forEach(i => {
195
- result.style = pd.style
196
- // Add previous object if calculable
197
- if (isCalculable(data[i - 1][seriesKey])) {
198
- result.data.push(data[i - 1])
199
- }
202
+ // Find the nearest numeric sibling after the current index
203
+ for (let j = index + 1; j < data.length; j++) {
204
+ if (isCalculable(data[j][seriesKey])) {
205
+ siblingAfter = data[j]
206
+ break // Stop searching once a valid sibling is found
207
+ }
208
+ }
200
209
 
201
- // Find and add the next calculable object
202
- const nextIndex = data
203
- .slice(i + 1)
204
- .findIndex(item => item[seriesKey] !== targetValue && isCalculable(item[seriesKey]))
205
- if (nextIndex !== -1) {
206
- result.data.push(data[i + 1 + nextIndex])
210
+ // Only add siblings to results if both siblings are found
211
+ if (siblingBefore && siblingAfter) {
212
+ result.style = pd.style
213
+ result.data[pairCount++] = [siblingBefore, siblingAfter]
214
+ }
207
215
  }
208
216
  })
209
217
  })
210
-
211
- // Deduplicate entries
212
- result.data = _.uniqWith(result.data, (a, b) => a[dataKey] === b[dataKey] && a[seriesKey] === b[seriesKey])
213
-
214
218
  return result
215
219
  }
216
220
 
@@ -221,7 +225,9 @@ export const createDataSegments = (data, seriesKey, preliminaryData, dataKey) =>
221
225
  // Process the last index if necessary
222
226
  const lastSegment = handleLastIndex(data, seriesKey, preliminaryData)
223
227
  // Process the middle segment
224
- const middleSegments = handleMiddleIndices(data, seriesKey, dataKey, preliminaryData)
228
+ const middleSegments = handleMiddleIndices(data, seriesKey, preliminaryData)
229
+
225
230
  // Combine all segments into a single array
226
- return [firstSegment, middleSegments, lastSegment].filter(segment => segment.data.length > 0 && segment.style !== '')
231
+ return [firstSegment, middleSegments, lastSegment]
232
+ // return [firstSegment, middleSegments, lastSegment].filter(segment => segment.data.length > 0 && segment.style !== '')
227
233
  }