@cdc/chart 4.26.4 → 4.26.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 (141) hide show
  1. package/CONFIG.md +353 -29
  2. package/LICENSE +201 -0
  3. package/dist/{cdcchart-DQ00cQCm.es.js → cdcchart-BWVIUcAF.es.js} +5 -55
  4. package/dist/cdcchart.js +37885 -35117
  5. package/examples/feature/heatmap/average-age-categorical.json +313 -0
  6. package/examples/feature/heatmap/calendar-heatmap.json +134 -0
  7. package/examples/feature/heatmap/cell-values-bottom-axis.json +181 -0
  8. package/examples/feature/heatmap/sparse-aggregation.json +158 -0
  9. package/examples/private/bad-y-axis.json +543 -0
  10. package/examples/private/bar-chart-negative-value.json +455 -0
  11. package/examples/private/bump-chart.json +1116 -0
  12. package/examples/private/test-123.json +624 -0
  13. package/package.json +4 -3
  14. package/src/CdcChart.tsx +5 -4
  15. package/src/CdcChartComponent.tsx +166 -62
  16. package/src/_stories/Chart.AltText.stories.tsx +136 -0
  17. package/src/_stories/Chart.HeatMap.stories.tsx +187 -0
  18. package/src/_stories/Chart.Sankey.smoke.stories.tsx +62 -0
  19. package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +183 -0
  20. package/src/_stories/Chart.SmallMultiples.smoke.stories.tsx +29 -0
  21. package/src/_stories/Chart.YAxisAutoMaxStrategy.stories.tsx +52 -0
  22. package/src/_stories/Chart.YAxisFilterDomain.stories.tsx +90 -0
  23. package/src/_stories/ChartAnnotation.smoke.stories.tsx +10 -0
  24. package/src/_stories/ChartAxisTitles.smoke.stories.tsx +13 -0
  25. package/src/_stories/ChartBar.Editor.stories.tsx +63 -0
  26. package/src/_stories/ChartBrush.Interaction.stories.tsx +112 -0
  27. package/src/_stories/ChartBrush.smoke.stories.tsx +13 -4
  28. package/src/_stories/_mock/alt_text_metadata.json +38 -0
  29. package/src/_stories/_mock/annotation_event-line_mock.json +1064 -0
  30. package/src/_stories/_mock/heatmap-average-age-categorical.json +315 -0
  31. package/src/_stories/_mock/heatmap.json +156 -0
  32. package/src/_stories/_mock/reported_pertussis_cases.json +1643 -0
  33. package/src/_stories/_mock/sankey_mock.json +78 -0
  34. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +1 -1
  35. package/src/components/Annotations/components/AnnotationDraggable.styles.css +22 -1
  36. package/src/components/Annotations/components/AnnotationDraggable.tsx +220 -120
  37. package/src/components/Annotations/components/AnnotationDropdown.tsx +5 -2
  38. package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +198 -0
  39. package/src/components/Axis/LeftAxis.tsx +17 -11
  40. package/src/components/Axis/LeftAxisGridlines.tsx +22 -12
  41. package/src/components/Axis/RightAxis.tsx +3 -0
  42. package/src/components/Axis/tests/AxisTickValues.test.tsx +86 -0
  43. package/src/components/Brush/BrushSelector.tsx +88 -187
  44. package/src/components/Brush/brushInteraction.ts +22 -0
  45. package/src/components/Brush/tests/brushInteraction.test.ts +38 -0
  46. package/src/components/EditorPanel/EditorPanel.tsx +677 -185
  47. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +114 -52
  48. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +105 -13
  49. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +1 -0
  50. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +5 -0
  51. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +2 -2
  52. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.test.tsx +98 -0
  53. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +10 -0
  54. package/src/components/EditorPanel/components/Panels/Panel.Visual.test.tsx +484 -0
  55. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +4 -1
  56. package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +262 -0
  57. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +32 -0
  58. package/src/components/EditorPanel/components/panels.scss +10 -0
  59. package/src/components/EditorPanel/editor-panel.scss +47 -0
  60. package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +61 -0
  61. package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +74 -0
  62. package/src/components/EditorPanel/useEditorPermissions.test.tsx +315 -0
  63. package/src/components/EditorPanel/useEditorPermissions.ts +77 -21
  64. package/src/components/HeatMap/components/HeatMap.tsx +767 -0
  65. package/src/components/HeatMap/components/HeatMapGradientLegend.tsx +128 -0
  66. package/src/components/HeatMap/heatmap.constants.ts +13 -0
  67. package/src/components/HeatMap/heatmap.css +85 -0
  68. package/src/components/HeatMap/helpers/index.ts +261 -0
  69. package/src/components/HeatMap/index.tsx +5 -0
  70. package/src/components/HeatMap/tests/HeatMap.test.tsx +1072 -0
  71. package/src/components/HeatMap/tests/helpers.test.ts +167 -0
  72. package/src/components/Legend/Legend.Component.tsx +2 -2
  73. package/src/components/Legend/helpers/createFormatLabels.test.ts +88 -0
  74. package/src/components/Legend/helpers/createFormatLabels.tsx +6 -1
  75. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +19 -22
  76. package/src/components/LineChart/helpers.test.ts +574 -0
  77. package/src/components/LineChart/helpers.ts +39 -16
  78. package/src/components/LineChart/index.tsx +28 -5
  79. package/src/components/LinearChart/tests/LinearChart.test.tsx +137 -33
  80. package/src/components/LinearChart/tests/mockConfigContext.ts +1 -0
  81. package/src/components/LinearChart.tsx +103 -47
  82. package/src/components/PieChart/PieChart.tsx +4 -1
  83. package/src/components/RadarChart/RadarChart.tsx +3 -1
  84. package/src/components/Regions/components/Regions.tsx +195 -41
  85. package/src/components/Sankey/components/Sankey.tsx +7 -3
  86. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +7 -6
  87. package/src/components/ScatterPlot/ScatterPlot.jsx +3 -3
  88. package/src/components/SmallMultiples/SmallMultipleTile.tsx +33 -3
  89. package/src/components/SmallMultiples/SmallMultiples.css +12 -0
  90. package/src/components/SmallMultiples/SmallMultiples.tsx +50 -15
  91. package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +152 -0
  92. package/src/components/SmallMultiples/tests/SmallMultiples.test.tsx +170 -0
  93. package/src/components/Sparkline/components/SparkLine.tsx +4 -9
  94. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +1 -1
  95. package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +2 -2
  96. package/src/data/initial-state.js +12 -0
  97. package/src/helpers/axisLabelFontSize.ts +8 -0
  98. package/src/helpers/bumpChartTooltip.ts +54 -0
  99. package/src/helpers/calculateLeftYAxisWidth.ts +13 -3
  100. package/src/helpers/colorAssignmentHelpers.ts +102 -0
  101. package/src/helpers/dataHelpers.ts +11 -1
  102. package/src/helpers/ensureSpecialChartAxisTypes.ts +11 -0
  103. package/src/helpers/getAxisMaxOverride.ts +16 -0
  104. package/src/helpers/getCleanTopTickMax.ts +64 -0
  105. package/src/helpers/getColorScale.ts +18 -9
  106. package/src/helpers/getHasBoundarySuppression.ts +26 -0
  107. package/src/helpers/getMinMax.ts +26 -25
  108. package/src/helpers/getYAxisDomainData.ts +38 -0
  109. package/src/helpers/getYAxisFinalizationEligibility.ts +40 -0
  110. package/src/helpers/handleChartAriaLabels.ts +23 -10
  111. package/src/helpers/hasSpacedInlineLabel.ts +10 -0
  112. package/src/helpers/seriesColumnSettings.ts +5 -4
  113. package/src/helpers/seriesHighlight.ts +26 -0
  114. package/src/helpers/smallMultiplesHelpers.ts +5 -0
  115. package/src/helpers/tests/axisLabelFontSize.test.ts +15 -0
  116. package/src/helpers/tests/bumpChartTooltip.test.ts +75 -0
  117. package/src/helpers/tests/calculateLeftYAxisWidth.test.ts +38 -0
  118. package/src/helpers/tests/dataHelpers.test.ts +135 -0
  119. package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +30 -0
  120. package/src/helpers/tests/getAxisMaxOverride.test.ts +36 -0
  121. package/src/helpers/tests/getCleanTopTickMax.test.ts +165 -0
  122. package/src/helpers/tests/getColorScale.test.ts +197 -0
  123. package/src/helpers/tests/getMinMax.test.ts +211 -0
  124. package/src/helpers/tests/getYAxisDomainData.test.ts +119 -0
  125. package/src/helpers/tests/getYAxisFinalizationEligibility.test.ts +185 -0
  126. package/src/helpers/tests/handleChartAriaLabels.test.ts +71 -0
  127. package/src/helpers/tests/hasSpacedInlineLabel.test.ts +75 -0
  128. package/src/helpers/tests/seriesColumnSettings.test.ts +16 -2
  129. package/src/helpers/tests/seriesHighlight.test.ts +61 -0
  130. package/src/helpers/tests/smallMultiplesHelpers.test.ts +99 -0
  131. package/src/hooks/tests/useRightAxis.test.tsx +127 -0
  132. package/src/hooks/tests/useScales.test.tsx +383 -0
  133. package/src/hooks/useRightAxis.ts +30 -13
  134. package/src/hooks/useScales.ts +48 -21
  135. package/src/hooks/useTooltip.tsx +3 -1
  136. package/src/index.jsx +5 -2
  137. package/src/scss/main.scss +32 -49
  138. package/src/test/CdcChart.dataTable.test.tsx +122 -0
  139. package/src/types/ChartConfig.ts +24 -1
  140. package/src/types/ChartContext.ts +5 -0
  141. package/src/helpers/getYAxisAutoPadding.ts +0 -53
@@ -0,0 +1,383 @@
1
+ import React from 'react'
2
+ import { renderHook } from '@testing-library/react'
3
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
4
+ import ConfigContext from '../../ConfigContext'
5
+ import useScales from '../useScales'
6
+ import { createMockChartContext, createMockConfig } from '../../components/LinearChart/tests/mockConfigContext'
7
+
8
+ const mockGetMinMax = vi.fn(() => ({
9
+ min: 0,
10
+ max: 100,
11
+ leftMax: 100,
12
+ rightMax: 0
13
+ }))
14
+
15
+ vi.mock('../../helpers/getMinMax', () => ({
16
+ default: (...args: any[]) => mockGetMinMax(...args)
17
+ }))
18
+
19
+ const config = createMockConfig({
20
+ runtime: {
21
+ ...createMockConfig().runtime,
22
+ yAxis: {
23
+ ...createMockConfig().runtime.yAxis,
24
+ numTicks: 5
25
+ }
26
+ } as any
27
+ })
28
+
29
+ const createWrapper = () => {
30
+ const context = createMockChartContext(config)
31
+
32
+ return ({ children }: React.PropsWithChildren) => (
33
+ <ConfigContext.Provider value={context}>{children}</ConfigContext.Provider>
34
+ )
35
+ }
36
+
37
+ const baseProps = {
38
+ config,
39
+ data: [],
40
+ tableData: [],
41
+ minValue: 0,
42
+ maxValue: 100,
43
+ existPositiveValue: true,
44
+ isAllLine: true,
45
+ xAxisDataMapped: [1, 2, 3],
46
+ xMax: 300,
47
+ yMax: 200,
48
+ currentViewport: 'lg' as const
49
+ }
50
+
51
+ describe('useScales', () => {
52
+ beforeEach(() => {
53
+ mockGetMinMax.mockReset()
54
+ mockGetMinMax.mockReturnValue({
55
+ min: 0,
56
+ max: 100,
57
+ leftMax: 100,
58
+ rightMax: 0
59
+ })
60
+ })
61
+
62
+ it('uses spaced inline labels as an effective clean-top-tick trigger', () => {
63
+ mockGetMinMax.mockReturnValue({
64
+ min: 0,
65
+ max: 11.25,
66
+ leftMax: 0,
67
+ rightMax: 0
68
+ })
69
+
70
+ const result = renderHook(
71
+ () =>
72
+ useScales({
73
+ ...baseProps,
74
+ maxValue: 11.25,
75
+ config: createMockConfig({
76
+ yAxis: {
77
+ ...createMockConfig().yAxis,
78
+ autoMaxStrategy: 'default',
79
+ inlineLabel: ' percent'
80
+ },
81
+ runtime: {
82
+ ...createMockConfig().runtime,
83
+ yAxis: {
84
+ ...createMockConfig().runtime.yAxis,
85
+ numTicks: 3
86
+ }
87
+ } as any
88
+ }),
89
+ hasSpacedInlineLabel: true
90
+ }),
91
+ {
92
+ wrapper: createWrapper()
93
+ }
94
+ )
95
+
96
+ expect(result.result.current.max).toBe(15)
97
+ expect(result.result.current.yTickValues).toEqual([0, 5, 10, 15])
98
+ })
99
+
100
+ it('ignores manual y-axis padding while calculating inline-label headroom', () => {
101
+ const configWithManualPadding = createMockConfig({
102
+ yAxis: {
103
+ ...createMockConfig().yAxis,
104
+ enablePadding: true,
105
+ scalePadding: 90
106
+ }
107
+ })
108
+
109
+ renderHook(
110
+ () => useScales({ ...baseProps, config: configWithManualPadding, hasSpacedInlineLabel: true }),
111
+ {
112
+ wrapper: createWrapper()
113
+ }
114
+ )
115
+
116
+ expect(mockGetMinMax.mock.calls[0][0].config.yAxis).toMatchObject({
117
+ enablePadding: false,
118
+ scalePadding: 0
119
+ })
120
+ expect(mockGetMinMax).toHaveBeenCalledTimes(1)
121
+ })
122
+
123
+ it('does not finalize default strategy domains for single-word inline labels', () => {
124
+ mockGetMinMax.mockReturnValue({
125
+ min: 0,
126
+ max: 11.25,
127
+ leftMax: 0,
128
+ rightMax: 0
129
+ })
130
+
131
+ const result = renderHook(
132
+ () =>
133
+ useScales({
134
+ ...baseProps,
135
+ config: createMockConfig({
136
+ yAxis: {
137
+ ...createMockConfig().yAxis,
138
+ autoMaxStrategy: 'default',
139
+ inlineLabel: '%'
140
+ },
141
+ runtime: {
142
+ ...createMockConfig().runtime,
143
+ yAxis: {
144
+ ...createMockConfig().runtime.yAxis,
145
+ numTicks: 3
146
+ }
147
+ } as any
148
+ }),
149
+ hasSpacedInlineLabel: false
150
+ }),
151
+ {
152
+ wrapper: createWrapper()
153
+ }
154
+ )
155
+
156
+ expect(result.result.current.max).toBe(11.25)
157
+ expect(result.result.current.yTickValues).toBeUndefined()
158
+ })
159
+
160
+ it('keeps shared small-multiple Y domains on the finalized top tick instead of percent padding', () => {
161
+ mockGetMinMax.mockReturnValue({
162
+ min: 0,
163
+ max: 11.25,
164
+ leftMax: 0,
165
+ rightMax: 0
166
+ })
167
+
168
+ const result = renderHook(
169
+ () =>
170
+ useScales({
171
+ ...baseProps,
172
+ config: createMockConfig({
173
+ smallMultiples: {
174
+ mode: 'by-column',
175
+ tileColumn: 'group',
176
+ independentYAxis: false
177
+ },
178
+ yAxis: {
179
+ ...createMockConfig().yAxis,
180
+ autoMaxStrategy: 'default',
181
+ inlineLabel: ' percent'
182
+ },
183
+ runtime: {
184
+ ...createMockConfig().runtime,
185
+ yAxis: {
186
+ ...createMockConfig().runtime.yAxis,
187
+ numTicks: 3
188
+ }
189
+ } as any
190
+ }),
191
+ maxValue: 11.25,
192
+ hasSpacedInlineLabel: true
193
+ }),
194
+ {
195
+ wrapper: createWrapper()
196
+ }
197
+ )
198
+
199
+ expect(result.result.current.max).toBe(15)
200
+ expect(result.result.current.yTickValues).toEqual([0, 5, 10, 15])
201
+ expect(result.result.current.yScale.ticks(3).at(-1)).toBe(15)
202
+ })
203
+
204
+ it('does not return explicit tick values when the primary Y-axis max is explicit', () => {
205
+ mockGetMinMax.mockReturnValue({
206
+ min: 0,
207
+ max: 20,
208
+ leftMax: 0,
209
+ rightMax: 0
210
+ })
211
+
212
+ const result = renderHook(
213
+ () =>
214
+ useScales({
215
+ ...baseProps,
216
+ maxValue: 11.25,
217
+ config: createMockConfig({
218
+ yAxis: {
219
+ ...createMockConfig().yAxis,
220
+ autoMaxStrategy: 'clean-top-tick',
221
+ inlineLabel: ' percent'
222
+ },
223
+ runtime: {
224
+ ...createMockConfig().runtime,
225
+ yAxis: {
226
+ ...createMockConfig().runtime.yAxis,
227
+ max: '20',
228
+ numTicks: 3
229
+ }
230
+ } as any
231
+ }),
232
+ hasSpacedInlineLabel: true
233
+ }),
234
+ {
235
+ wrapper: createWrapper()
236
+ }
237
+ )
238
+
239
+ expect(result.result.current.max).toBe(20)
240
+ expect(result.result.current.yTickValues).toBeUndefined()
241
+ })
242
+
243
+ it('does not use spaced inline labels as an effective clean-top-tick trigger for Combo charts', () => {
244
+ mockGetMinMax.mockReturnValue({
245
+ min: 0,
246
+ max: 100,
247
+ leftMax: 11.25,
248
+ rightMax: 0
249
+ })
250
+
251
+ const result = renderHook(
252
+ () =>
253
+ useScales({
254
+ ...baseProps,
255
+ config: createMockConfig({
256
+ visualizationType: 'Combo',
257
+ yAxis: {
258
+ ...createMockConfig().yAxis,
259
+ autoMaxStrategy: 'default',
260
+ inlineLabel: ' percent'
261
+ },
262
+ runtime: {
263
+ ...createMockConfig().runtime,
264
+ yAxis: {
265
+ ...createMockConfig().runtime.yAxis,
266
+ numTicks: 3
267
+ }
268
+ } as any
269
+ }),
270
+ maxValue: 101,
271
+ hasSpacedInlineLabel: true
272
+ }),
273
+ {
274
+ wrapper: createWrapper()
275
+ }
276
+ )
277
+
278
+ expect(result.result.current.leftMax).toBe(11.25)
279
+ expect(result.result.current.yTickValues).toBeUndefined()
280
+ })
281
+
282
+ it('finalizes Combo left-axis ticks when clean-top-tick is explicitly enabled', () => {
283
+ mockGetMinMax.mockReturnValue({
284
+ min: 0,
285
+ max: 100,
286
+ leftMax: 12,
287
+ rightMax: 0
288
+ })
289
+
290
+ const result = renderHook(
291
+ () =>
292
+ useScales({
293
+ ...baseProps,
294
+ config: createMockConfig({
295
+ visualizationType: 'Combo',
296
+ yAxis: {
297
+ ...createMockConfig().yAxis,
298
+ autoMaxStrategy: 'clean-top-tick'
299
+ },
300
+ runtime: {
301
+ ...createMockConfig().runtime,
302
+ yAxis: {
303
+ ...createMockConfig().runtime.yAxis,
304
+ numTicks: 3
305
+ }
306
+ } as any
307
+ }),
308
+ maxValue: 101,
309
+ hasSpacedInlineLabel: false
310
+ }),
311
+ {
312
+ wrapper: createWrapper()
313
+ }
314
+ )
315
+
316
+ expect(result.result.current.leftMax).toBe(15)
317
+ expect(result.result.current.yTickValues).toEqual([0, 5, 10, 15])
318
+ })
319
+
320
+ it('uses the selected Y-axis domain rows for min/max calculation', () => {
321
+ const renderedRows = [{ Date: '2024-01-01', value: 3 }]
322
+ const domainRows = [
323
+ { Date: '2024-01-01', value: 3 },
324
+ { Date: '2024-01-02', value: 12 }
325
+ ]
326
+
327
+ renderHook(
328
+ () =>
329
+ useScales({
330
+ ...baseProps,
331
+ data: renderedRows,
332
+ tableData: domainRows,
333
+ yAxisDomainData: domainRows
334
+ }),
335
+ {
336
+ wrapper: createWrapper()
337
+ }
338
+ )
339
+
340
+ expect(mockGetMinMax.mock.calls[0][0].data).toBe(domainRows)
341
+ expect(mockGetMinMax.mock.calls[0][0].tableData).toBe(domainRows)
342
+ })
343
+
344
+ it('passes hasBoundarySuppression through to getMinMax', () => {
345
+ renderHook(
346
+ () =>
347
+ useScales({
348
+ ...baseProps,
349
+ config: createMockConfig({ visualizationType: 'Line' }),
350
+ data: [{ Cases: 10 }],
351
+ tableData: [{ Cases: 10 }],
352
+ hasBoundarySuppression: true
353
+ }),
354
+ {
355
+ wrapper: createWrapper()
356
+ }
357
+ )
358
+
359
+ expect(mockGetMinMax.mock.calls[0][0].hasBoundarySuppression).toBe(true)
360
+ })
361
+
362
+ it('passes hasBoundarySuppression=false when not set', () => {
363
+ const renderedRows = [{ Cases: 50 }]
364
+ const stableDomainRows = [{ Cases: 50 }, { Cases: 60 }]
365
+
366
+ renderHook(
367
+ () =>
368
+ useScales({
369
+ ...baseProps,
370
+ config: createMockConfig({ visualizationType: 'Line' }),
371
+ data: renderedRows,
372
+ tableData: renderedRows,
373
+ yAxisDomainData: stableDomainRows
374
+ }),
375
+ {
376
+ wrapper: createWrapper()
377
+ }
378
+ )
379
+
380
+ expect(mockGetMinMax.mock.calls[0][0].data).toBe(stableDomainRows)
381
+ expect(mockGetMinMax.mock.calls[0][0].hasBoundarySuppression).toBe(false)
382
+ })
383
+ })
@@ -1,6 +1,8 @@
1
1
  import { scaleLinear } from '@visx/scale'
2
2
  import useReduceData from './useReduceData'
3
3
  import { TOP_PADDING } from './useScales'
4
+ import { getCleanTopTickMax } from '../helpers/getCleanTopTickMax'
5
+ import { getAxisMaxOverride } from '../helpers/getAxisMaxOverride'
4
6
 
5
7
  export default function useRightAxis({ config, yMax = 0, data = [] }) {
6
8
  const hasRightAxis = config.visualizationType === 'Combo' && config.orientation === 'vertical'
@@ -17,14 +19,36 @@ export default function useRightAxis({ config, yMax = 0, data = [] }) {
17
19
  return rightAxisData
18
20
  }
19
21
 
20
- let max = Math.max.apply(null, allRightAxisData(rightSeriesKeys))
22
+ const rightAxisValues = allRightAxisData(rightSeriesKeys).map(Number).filter(Number.isFinite)
23
+ const rawRightMax = rightAxisValues.length ? Math.max(...rightAxisValues) : 0
24
+ let max = rawRightMax
25
+ const { hasValidMax: hasValidExplicitRightMax, maxNumber: rightMaxNumber } = getAxisMaxOverride({
26
+ value: config.yAxis.rightMax,
27
+ minimumValidMax: max
28
+ })
29
+ const rightMinRaw = config.yAxis.rightMin
30
+ const rightMinNumber = Number(rightMinRaw)
31
+ const hasExplicitRightMin =
32
+ rightMinRaw !== undefined && rightMinRaw !== null && rightMinRaw !== '' && Number.isFinite(rightMinNumber)
33
+
34
+ if (hasValidExplicitRightMax && rightMaxNumber > max) {
35
+ max = rightMaxNumber
36
+ }
37
+
38
+ if (hasExplicitRightMin && rightMinNumber < minValue) {
39
+ minValue = rightMinNumber
40
+ }
41
+
42
+ // if there is a bar series & the right axis doesn't include a negative number, default to zero
43
+ const hasBarSeries = config.runtime?.barSeriesKeys?.length > 0
44
+ const hasLineSeries = config.runtime?.lineSeriesKeys?.length > 0
21
45
 
22
- if (config.yAxis.rightMax > max) {
23
- max = config.yAxis.rightMax
46
+ if ((hasBarSeries || hasLineSeries) && minValue > 0) {
47
+ minValue = 0
24
48
  }
25
49
 
26
- if (config.yAxis.rightMin < minValue) {
27
- minValue = config.yAxis.rightMin
50
+ if (config.yAxis.autoMaxStrategy === 'clean-top-tick' && !hasValidExplicitRightMax) {
51
+ max = getCleanTopTickMax(max)
28
52
  }
29
53
 
30
54
  // Enforce smallest right axis max so small-data charts don't show misleading decimal ticks
@@ -41,18 +65,11 @@ export default function useRightAxis({ config, yMax = 0, data = [] }) {
41
65
  if (smallestRightAxisMax !== null && max < smallestRightAxisMax) {
42
66
  max = smallestRightAxisMax
43
67
  }
44
- // if there is a bar series & the right axis doesn't include a negative number, default to zero
45
- const hasBarSeries = config.runtime?.barSeriesKeys?.length > 0
46
- const hasLineSeries = config.runtime?.lineSeriesKeys?.length > 0
47
-
48
- if ((hasBarSeries || hasLineSeries) && minValue > 0) {
49
- minValue = 0
50
- }
51
68
 
52
69
  const yScaleRight = scaleLinear({
53
70
  domain: [minValue, max],
54
71
  range: [yMax, TOP_PADDING]
55
72
  })
56
73
 
57
- return { yScaleRight, hasRightAxis }
74
+ return { yScaleRight, hasRightAxis, rightTickValues: undefined }
58
75
  }
@@ -13,10 +13,11 @@ import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
13
13
  import ConfigContext from '../ConfigContext'
14
14
  import { ChartConfig } from '../types/ChartConfig'
15
15
  import { ChartContext } from '../types/ChartContext'
16
- import _ from 'lodash'
17
- import { getYAxisAutoPadding } from '../helpers/getYAxisAutoPadding'
18
16
  import getMinMax from '../helpers/getMinMax'
19
17
  import { countNumOfTicks } from '../helpers/countNumOfTicks'
18
+ import { getFinalTopTickMax } from '../helpers/getCleanTopTickMax'
19
+ import { getYAxisFinalizationEligibility } from '../helpers/getYAxisFinalizationEligibility'
20
+ import { getAxisMaxOverride } from '../helpers/getAxisMaxOverride'
20
21
 
21
22
  const scaleTypes = {
22
23
  TIME: 'time',
@@ -32,6 +33,8 @@ type useScaleProps = {
32
33
  config: ChartConfig // standard chart config
33
34
  data: Object[] // standard data array
34
35
  tableData: Object[] // table data for getMinMax
36
+ yAxisDomainData?: Object[] // data used to calculate Y-axis min/max
37
+ hasBoundarySuppression?: boolean // whether selected-domain boundaries include suppression markers
35
38
  minValue: number // raw minimum value from data
36
39
  maxValue: number // raw maximum value from data
37
40
  existPositiveValue: boolean // whether data contains positive values
@@ -39,7 +42,7 @@ type useScaleProps = {
39
42
  xAxisDataMapped: Object[] // array of x axis date/category items
40
43
  xMax: number // chart svg width
41
44
  yMax: number // chart svg height
42
- needsYAxisAutoPadding?: boolean // whether Y-axis needs auto padding for label overflow
45
+ hasSpacedInlineLabel?: boolean
43
46
  currentViewport?: string // current viewport for tick calculation
44
47
  }
45
48
 
@@ -51,11 +54,13 @@ const useScales = (properties: useScaleProps) => {
51
54
  config,
52
55
  data,
53
56
  tableData,
57
+ yAxisDomainData,
58
+ hasBoundarySuppression = false,
54
59
  minValue,
55
60
  maxValue,
56
61
  existPositiveValue,
57
62
  isAllLine,
58
- needsYAxisAutoPadding,
63
+ hasSpacedInlineLabel = false,
59
64
  currentViewport
60
65
  } = properties
61
66
 
@@ -66,15 +71,30 @@ const useScales = (properties: useScaleProps) => {
66
71
  const { visualizationType, xAxis, forestPlot, runtime } = config
67
72
  const isForestPlot = visualizationType === 'Forest Plot'
68
73
 
69
- const minMaxProps = {
74
+ const { hasValidMax: hasValidExplicitLeftMax } = getAxisMaxOverride({
75
+ value: config.runtime?.yAxis?.max,
76
+ minimumValidMax: existPositiveValue ? maxValue : 0
77
+ })
78
+ const yAxisFinalizationEligibility = getYAxisFinalizationEligibility({
70
79
  config,
80
+ hasSpacedInlineLabel,
81
+ hasValidExplicitLeftMax,
82
+ isHorizontal
83
+ })
84
+ const minMaxConfig = yAxisFinalizationEligibility.shouldUseInlineLabelHeadroom
85
+ ? { ...config, yAxis: { ...config.yAxis, enablePadding: false, scalePadding: 0 } }
86
+ : config
87
+
88
+ const minMaxProps = {
89
+ config: minMaxConfig,
71
90
  minValue,
72
91
  maxValue,
73
92
  existPositiveValue,
74
- data,
93
+ data: yAxisDomainData || data,
75
94
  isAllLine,
76
95
  tableData,
77
- convertLineToBarGraph
96
+ convertLineToBarGraph,
97
+ hasBoundarySuppression
78
98
  }
79
99
  let { min, max, leftMax, rightMax } = getMinMax(minMaxProps)
80
100
 
@@ -90,19 +110,25 @@ const useScales = (properties: useScaleProps) => {
90
110
  })
91
111
  const handleNumTicks = isForestPlot ? config.data.length : yTickCount
92
112
 
93
- // Apply auto-padding if needed
94
- if (needsYAxisAutoPadding && !isHorizontal) {
95
- for (let i = 0; i < 3; i++) {
96
- const scale = composeYScale({ min, max, yMax, config, leftMax })
97
- const padding = getYAxisAutoPadding(scale, handleNumTicks, maxValue, minValue, config)
98
- if (i === 0 || padding > 0) {
99
- const adjustedConfig = { ...config, yAxis: { ...config.yAxis, scalePadding: padding, enablePadding: true } }
100
- const result = getMinMax({ ...minMaxProps, config: adjustedConfig })
101
- min = result.min
102
- max = result.max
103
- leftMax = result.leftMax
104
- rightMax = result.rightMax
105
- }
113
+ let yTickValues: number[] | undefined
114
+
115
+ if (yAxisFinalizationEligibility.shouldFinalizePrimaryYAxis) {
116
+ const yScaleForTicks = composeYScale({ min, max, yMax, config: minMaxConfig, leftMax })
117
+ const ticks = yScaleForTicks?.ticks ? yScaleForTicks.ticks(handleNumTicks) : []
118
+ const currentMax = minMaxConfig.visualizationType === 'Combo' ? leftMax : max
119
+ const { max: finalizedMax, tickValues } = getFinalTopTickMax({
120
+ currentMax,
121
+ rawMax: minMaxConfig.visualizationType === 'Combo' ? currentMax : maxValue,
122
+ ticks,
123
+ shouldUseCleanTopTick: yAxisFinalizationEligibility.shouldUseCleanTopTick,
124
+ headroomMode: yAxisFinalizationEligibility.shouldUseInlineLabelHeadroom ? 'inline-label' : 'none'
125
+ })
126
+ yTickValues = tickValues
127
+
128
+ if (minMaxConfig.visualizationType === 'Combo') {
129
+ leftMax = finalizedMax
130
+ } else {
131
+ max = finalizedMax
106
132
  }
107
133
  }
108
134
 
@@ -354,7 +380,8 @@ const useScales = (properties: useScaleProps) => {
354
380
  min,
355
381
  max,
356
382
  leftMax,
357
- rightMax
383
+ rightMax,
384
+ yTickValues
358
385
  }
359
386
  }
360
387
 
@@ -847,7 +847,9 @@ export const useTooltip = props => {
847
847
  return (
848
848
  <li
849
849
  style={style}
850
- className={`tooltip-body mb-1 cove-prose ${shouldRenderMarkerSlot ? 'tooltip-body--marker-layout' : ''}`}
850
+ className={['tooltip-body', 'mb-1', shouldRenderMarkerSlot ? 'tooltip-body--marker-layout' : '']
851
+ .filter(Boolean)
852
+ .join(' ')}
851
853
  >
852
854
  {markerSlot}
853
855
  {shouldRenderMarkerSlot ? <span className='tooltip-body-content'>{content}</span> : content}
package/src/index.jsx CHANGED
@@ -9,12 +9,15 @@ let isEditor = window.location.href.includes('editor=true')
9
9
  let isDebug = window.location.href.includes('debug=true')
10
10
 
11
11
  let domContainer = document.getElementsByClassName('react-container')[0]
12
+ let configUrl = domContainer.dataset.configUrl
13
+ let injectedConfig = domContainer.coveConfig
12
14
 
13
15
  ReactDOM.createRoot(domContainer).render(
14
16
  <React.StrictMode>
15
17
  <CdcChart
16
- interactionLabel={domContainer.attributes['data-config']?.value}
17
- configUrl={domContainer.attributes['data-config'].value}
18
+ interactionLabel={configUrl}
19
+ config={injectedConfig}
20
+ configUrl={injectedConfig ? undefined : configUrl}
18
21
  isEditor={isEditor}
19
22
  isDebug={isDebug}
20
23
  />