@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
@@ -2,6 +2,9 @@ import { DataItem, StyleProps, Style } from './LineChartProps'
2
2
  import { PreliminaryDataItem } from '../../types/ChartConfig'
3
3
 
4
4
  import _ from 'lodash'
5
+
6
+ const isCalculable = value => !isNaN(parseFloat(value)) && isFinite(value)
7
+
5
8
  export const createStyles = (props: StyleProps): Style[] => {
6
9
  const {
7
10
  preliminaryData,
@@ -15,17 +18,18 @@ export const createStyles = (props: StyleProps): Style[] => {
15
18
  originalSeriesKey
16
19
  } = props
17
20
 
18
- const dynamicSeriesKey = dynamicCategory ? originalSeriesKey : seriesKey
19
21
  const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(
20
22
  pd => pd.seriesKeys?.length && pd.column && pd.value && pd.type && pd.style && pd.type === 'effect'
21
23
  )
22
24
  const isEffectLine = (pd, dataPoint) => {
23
25
  if (dynamicCategory) {
26
+ // In dynamic category mode, check the attribute column (pd.column) for the effect marker
27
+ // seriesKey is the runtime series key derived from the category column
24
28
  return (
25
29
  pd.type === 'effect' &&
26
30
  pd.style !== 'Open Circles' &&
27
31
  pd.seriesKeys.includes(seriesKey) &&
28
- String(dataPoint[dynamicSeriesKey]) === String(pd.value)
32
+ dataPoint[pd.column] === pd.value
29
33
  )
30
34
  } else {
31
35
  return (
@@ -41,24 +45,29 @@ export const createStyles = (props: StyleProps): Style[] => {
41
45
  validPreliminaryData.find(pd => isEffectLine(pd, point))
42
46
 
43
47
  const styles: Style[] = []
44
- const createStyle = (lineStyle): Style => ({
45
- stroke: stroke,
46
- strokeWidth: strokeWidth,
47
- strokeDasharray: lineStyle
48
- })
48
+ const createStyle = (lineStyle, customWeight?: number | string): Style => {
49
+ const parsedWeight = Number(customWeight)
50
+ const hasValidWeight =
51
+ customWeight !== '' && customWeight != null && Number.isFinite(parsedWeight) && parsedWeight >= 1
52
+ return {
53
+ stroke: stroke,
54
+ strokeWidth: hasValidWeight ? parsedWeight : strokeWidth,
55
+ strokeDasharray: lineStyle
56
+ }
57
+ }
49
58
 
50
59
  data.forEach((d, index) => {
51
60
  const matchingPd: PreliminaryDataItem = getMatchingPd(d)
52
61
 
53
62
  let style: Style = matchingPd
54
- ? createStyle(handleLineType(matchingPd.style))
63
+ ? createStyle(handleLineType(matchingPd.style), matchingPd.weight)
55
64
  : createStyle(handleLineType(lineType))
56
65
 
57
66
  styles.push(style)
58
67
 
59
68
  // If matchingPd exists, update the previous style if there is a previous element
60
69
  if (matchingPd && index > 0) {
61
- styles[index - 1] = createStyle(handleLineType(matchingPd.style))
70
+ styles[index - 1] = createStyle(handleLineType(matchingPd.style), matchingPd.weight)
62
71
  }
63
72
  })
64
73
  return styles as Style[]
@@ -67,8 +76,17 @@ export const createStyles = (props: StyleProps): Style[] => {
67
76
  export const filterCircles = (
68
77
  preliminaryData: PreliminaryDataItem[],
69
78
  data: DataItem[],
70
- seriesKey: string
79
+ seriesKey: string,
80
+ dynamicCategory?: string,
81
+ originalSeriesKey?: string
71
82
  ): DataItem[] => {
83
+ // In dynamic category mode:
84
+ // - seriesKey is the runtime series key for matching against seriesKeys
85
+ // - originalSeriesKey is the data value column (e.g., "Value") for checking if data exists
86
+ // In standard mode:
87
+ // - seriesKey is used for both purposes
88
+ const valueKey = dynamicCategory ? originalSeriesKey : seriesKey
89
+
72
90
  // Filter and map preliminaryData to get circlesFiltered
73
91
  const circlesFiltered = preliminaryData
74
92
  ?.filter(item => item.style.includes('Circles') && item.type === 'effect' && item.seriesKeys?.length)
@@ -86,7 +104,7 @@ export const filterCircles = (
86
104
  if (
87
105
  item[fc.column] === fc.value &&
88
106
  fc.seriesKeys.includes(seriesKey) &&
89
- item[seriesKey] &&
107
+ isCalculable(item[valueKey]) &&
90
108
  fc.style === 'Open Circles'
91
109
  ) {
92
110
  const result = {
@@ -99,7 +117,7 @@ export const filterCircles = (
99
117
  if (
100
118
  (!fc.value || item[fc.column] === fc.value) &&
101
119
  fc.seriesKeys.includes(seriesKey) &&
102
- item[seriesKey] &&
120
+ isCalculable(item[valueKey]) &&
103
121
  fc.style === 'Filled Circles'
104
122
  ) {
105
123
  const result = {
@@ -115,7 +133,6 @@ export const filterCircles = (
115
133
  return filteredData
116
134
  }
117
135
 
118
- const isCalculable = value => !isNaN(parseFloat(value)) && isFinite(value)
119
136
  const handleFirstIndex = ({
120
137
  data,
121
138
  seriesKey,
@@ -129,7 +146,8 @@ const handleFirstIndex = ({
129
146
  const result = {
130
147
  data: { '0': [] },
131
148
  style: '',
132
- color: ''
149
+ color: '',
150
+ weight: undefined
133
151
  }
134
152
 
135
153
  // If data is empty, return the empty result
@@ -150,6 +168,7 @@ const handleFirstIndex = ({
150
168
 
151
169
  result.data[pairCount].push(modifiedItem)
152
170
  result.style = suppressionData.style
171
+ result.weight = suppressionData.weight
153
172
  result.color = dynamicCategory && modifiedItem ? colorScale(modifiedItem[dynamicCategory]) : ''
154
173
 
155
174
  // Find the next calculable item index
@@ -180,7 +199,8 @@ const handleLastIndex = ({
180
199
  const result = {
181
200
  data: { '0': [] },
182
201
  style: '',
183
- color: ''
202
+ color: '',
203
+ weight: undefined
184
204
  }
185
205
  const lastIndexDataItem = data[data.length - 1]
186
206
 
@@ -203,6 +223,7 @@ const handleLastIndex = ({
203
223
  lastAddedIndex = prevIndex
204
224
  }
205
225
  result.style = pd.style
226
+ result.weight = pd.weight
206
227
  result.color = colorScale(modifiedItem[dynamicCategory])
207
228
  }
208
229
  })
@@ -222,7 +243,8 @@ const handleMiddleIndices = ({
222
243
  let result = {
223
244
  data: {},
224
245
  style: '',
225
- color: 'red'
246
+ color: 'red',
247
+ weight: undefined
226
248
  }
227
249
 
228
250
  //skip processing if data or preliminaryData is not an array
@@ -260,6 +282,7 @@ const handleMiddleIndices = ({
260
282
  // Only add siblings to results if both siblings are found
261
283
  if (siblingBefore && siblingAfter) {
262
284
  result.style = pd.style
285
+ result.weight = pd.weight
263
286
  result.color = colorScale(item[dynamicCategory])
264
287
  result.data[pairCount++] = [siblingBefore, siblingAfter]
265
288
  }
@@ -18,6 +18,7 @@ import { filterCircles, createStyles, createDataSegments } from './helpers'
18
18
  import { getLabelPositionForDataPoint } from './helpers/labelPositioning'
19
19
  import LineChartCircle from './components/LineChart.Circle'
20
20
  import LineChartBumpCircle from './components/LineChart.BumpCircle'
21
+ import { getYAxisDomainData } from '../../helpers/getYAxisDomainData'
21
22
  import isNumber from '@cdc/core/helpers/isNumber'
22
23
 
23
24
  // Types
@@ -39,9 +40,25 @@ const LineChart = (props: LineChartProps) => {
39
40
  yAxisWidth,
40
41
  } = props
41
42
 
42
- // prettier-ignore
43
- const { colorScale, config, formatNumber, handleLineType, parseDate, seriesHighlight, tableData, transformedData:data, updateConfig, } = useContext<ChartContext>(ConfigContext)
44
- const { yScaleRight } = useRightAxis({ config, yMax, data, updateConfig })
43
+ const {
44
+ colorScale,
45
+ config,
46
+ formatNumber,
47
+ handleLineType,
48
+ parseDate,
49
+ seriesHighlight,
50
+ tableData,
51
+ transformedData: data,
52
+ updateConfig,
53
+ yAxisDomainData
54
+ } = useContext<ChartContext>(ConfigContext)
55
+ const dataForRightAxis = getYAxisDomainData({
56
+ config,
57
+ data,
58
+ tableData,
59
+ fullEligibleDomainData: yAxisDomainData
60
+ })
61
+ const { yScaleRight } = useRightAxis({ config, yMax, data: dataForRightAxis, updateConfig })
45
62
  const showSingleSeries = config.tooltips.singleSeries
46
63
  if (!handleTooltipMouseOver) return
47
64
 
@@ -80,7 +97,13 @@ const LineChart = (props: LineChartProps) => {
80
97
  const _data = seriesData.dynamicCategory
81
98
  ? data.filter(d => d[seriesData.dynamicCategory] === seriesKey)
82
99
  : data
83
- const circleData = filterCircles(config?.preliminaryData, tableData, _seriesKey)
100
+ const circleData = filterCircles(
101
+ config?.preliminaryData,
102
+ tableData,
103
+ seriesKey,
104
+ seriesData.dynamicCategory,
105
+ _seriesKey
106
+ )
84
107
 
85
108
  // Prepare sorted data for line and area rendering
86
109
  const sortedData =
@@ -296,7 +319,7 @@ const LineChart = (props: LineChartProps) => {
296
319
  ? segment.color
297
320
  : colorScale(config.runtime.seriesLabels[seriesKey])
298
321
  }
299
- strokeWidth={seriesData[0]?.weight || 2}
322
+ strokeWidth={Number(segment.weight) || seriesData.weight || 2}
300
323
  strokeOpacity={1}
301
324
  shapeRendering='geometricPrecision'
302
325
  strokeDasharray={handleLineType(segment.style)}
@@ -5,6 +5,7 @@ import LinearChart from '../../LinearChart'
5
5
  import ConfigContext from '../../../ConfigContext'
6
6
  import { createMockChartContext } from './mockConfigContext'
7
7
  import forestPlotConfig from '../../../../examples/feature/forest-plot/forest-plot.json'
8
+ import * as suppressionHelpers from '../../../helpers/getHasBoundarySuppression'
8
9
 
9
10
  // Mock ResizeObserver
10
11
  vi.stubGlobal(
@@ -72,7 +73,51 @@ const getLeftAxisLabelTransforms = container =>
72
73
  .map(label => label.getAttribute('transform'))
73
74
  .filter(Boolean)
74
75
 
76
+ const sidePlacementYAxis = {
77
+ hideAxis: false,
78
+ hideLabel: false,
79
+ hideTicks: false,
80
+ size: '50',
81
+ gridLines: true,
82
+ label: 'Y-Axis',
83
+ titlePlacement: 'side',
84
+ tickRotation: 0,
85
+ anchors: [],
86
+ axisPadding: 0,
87
+ labelPlacement: 'On Date/Category Axis',
88
+ rightAxisSize: 0
89
+ }
90
+
75
91
  describe('LinearChart', () => {
92
+ describe('suppression domain data source', () => {
93
+ it('uses excludedData fallback for suppression detection when Y-axis domain is stable and yAxisDomainData is not provided', () => {
94
+ const suppressionSpy = vi.spyOn(suppressionHelpers, 'getHasBoundarySuppression').mockReturnValue(false)
95
+ const excludedRows = [{ Date: '2020-01-01', Cases: 'SUPP' }]
96
+ const tableRows = [{ Date: '2020-01-01', Cases: 12 }]
97
+
98
+ renderLinearChart(
99
+ {
100
+ visualizationType: 'Line',
101
+ yAxis: {
102
+ ...createMockChartContext().config.yAxis,
103
+ filterDomainBehavior: 'stable'
104
+ }
105
+ },
106
+ {
107
+ transformedData: tableRows as any,
108
+ tableData: tableRows as any,
109
+ excludedData: excludedRows as any,
110
+ yAxisDomainData: undefined
111
+ }
112
+ )
113
+
114
+ expect(suppressionSpy).toHaveBeenCalled()
115
+ expect(suppressionSpy.mock.calls[0][0].rows).toBe(excludedRows)
116
+
117
+ suppressionSpy.mockRestore()
118
+ })
119
+ })
120
+
76
121
  describe('rendering', () => {
77
122
  it('renders without crashing', () => {
78
123
  const { container } = renderLinearChart()
@@ -238,26 +283,103 @@ describe('LinearChart', () => {
238
283
  expect(leftAxis).toBeTruthy()
239
284
  })
240
285
 
286
+ it('does not render the side y-axis title inside the svg when titlePlacement is top', () => {
287
+ const { container } = renderLinearChart()
288
+
289
+ expect(container.querySelector('.left-axis text.y-label')).toBeFalsy()
290
+ })
291
+
292
+ it('does not render the top y-axis title inside LinearChart when titlePlacement is top', () => {
293
+ const { container } = renderLinearChart()
294
+
295
+ expect(container.querySelector('.y-axis-top-title')).toBeFalsy()
296
+ })
297
+
298
+ it('renders the side y-axis title inside the svg when titlePlacement is side', () => {
299
+ const { container } = renderLinearChart({
300
+ yAxis: sidePlacementYAxis
301
+ })
302
+
303
+ expect(container.querySelector('.y-axis-top-title')).toBeFalsy()
304
+ expect(container.querySelector('.left-axis text.y-label')).toBeTruthy()
305
+ })
306
+
241
307
  it('renders bottom axis group', () => {
242
308
  const { container } = renderLinearChart()
243
309
  const bottomAxis = container.querySelector('.bottom-axis')
244
310
  expect(bottomAxis).toBeTruthy()
245
311
  })
246
312
 
313
+ it('uses vizViewport for x-axis viewport tick overrides', () => {
314
+ const data = [
315
+ { Date: '2024-01-01', Value: 1 },
316
+ { Date: '2024-02-01', Value: 2 },
317
+ { Date: '2024-03-01', Value: 3 },
318
+ { Date: '2024-04-01', Value: 4 },
319
+ { Date: '2024-05-01', Value: 5 },
320
+ { Date: '2024-06-01', Value: 6 }
321
+ ]
322
+
323
+ const { container } = renderLinearChart(
324
+ {
325
+ data,
326
+ preliminaryData: [],
327
+ series: [{ dataKey: 'Value', axis: 'Left' }],
328
+ xAxis: {
329
+ type: 'date',
330
+ dataKey: 'Date',
331
+ label: 'X-Axis',
332
+ hideAxis: false,
333
+ hideLabel: false,
334
+ hideTicks: false,
335
+ size: '50',
336
+ tickRotation: 0,
337
+ maxTickRotation: 90,
338
+ anchors: [],
339
+ axisPadding: 0,
340
+ dateParseFormat: '%Y-%m-%d',
341
+ dateDisplayFormat: '%b %Y'
342
+ },
343
+ runtime: {
344
+ xAxis: {
345
+ type: 'date',
346
+ dataKey: 'Date',
347
+ label: 'X-Axis',
348
+ numTicks: 6,
349
+ viewportNumTicks: { xxs: 2 }
350
+ },
351
+ yAxis: {
352
+ size: 50,
353
+ label: 'Y-Axis',
354
+ gridLines: true
355
+ },
356
+ originalXAxis: {
357
+ dataKey: 'Date'
358
+ },
359
+ series: [{ dataKey: 'Value', axis: 'Left' }],
360
+ seriesKeys: ['Value'],
361
+ seriesLabels: { Value: 'Value' },
362
+ seriesLabelsAll: ['Value'],
363
+ uniqueId: 'test-chart'
364
+ }
365
+ },
366
+ {
367
+ currentViewport: 'lg',
368
+ vizViewport: 'xxs',
369
+ colorScale: () => '#000',
370
+ transformedData: data,
371
+ tableData: data
372
+ }
373
+ )
374
+
375
+ expect(container.querySelectorAll('.bottom-axis .vx-axis-tick')).toHaveLength(2)
376
+ })
377
+
247
378
  it('hides Y axis when hideAxis is true', () => {
248
379
  const { container } = renderLinearChart({
249
380
  yAxis: {
250
- hideAxis: true,
251
- hideLabel: false,
252
- hideTicks: false,
253
- size: '50',
254
- gridLines: true,
255
- label: 'Y-Axis',
256
- tickRotation: 0,
257
- anchors: [],
258
- axisPadding: 0,
259
- labelPlacement: 'On Date/Category Axis',
260
- rightAxisSize: 0
381
+ ...sidePlacementYAxis,
382
+ hideAxis: true
261
383
  }
262
384
  })
263
385
  // The axis line should be hidden, but grid lines may still render
@@ -288,34 +410,14 @@ describe('LinearChart', () => {
288
410
 
289
411
  const baseRender = renderLinearChart({
290
412
  yAxis: {
291
- hideAxis: false,
292
- hideLabel: false,
293
- hideTicks: false,
294
- size: '50',
295
- gridLines: true,
296
- label: 'Y-Axis',
297
- tickRotation: 0,
298
- anchors: [],
299
- axisPadding: 0,
300
- labelPlacement: 'On Date/Category Axis',
301
- rightAxisSize: 0,
413
+ ...sidePlacementYAxis,
302
414
  labelOffset: 0
303
415
  },
304
416
  runtime: baseRuntime
305
417
  })
306
418
  const offsetRender = renderLinearChart({
307
419
  yAxis: {
308
- hideAxis: false,
309
- hideLabel: false,
310
- hideTicks: false,
311
- size: '50',
312
- gridLines: true,
313
- label: 'Y-Axis',
314
- tickRotation: 0,
315
- anchors: [],
316
- axisPadding: 0,
317
- labelPlacement: 'On Date/Category Axis',
318
- rightAxisSize: 0,
420
+ ...sidePlacementYAxis,
319
421
  labelOffset: 240
320
422
  },
321
423
  runtime: {
@@ -372,6 +474,7 @@ describe('LinearChart', () => {
372
474
  axisPadding: 0,
373
475
  labelOffset: 0
374
476
  },
477
+ yAxis: sidePlacementYAxis,
375
478
  runtime: baseRuntime
376
479
  })
377
480
  const offsetRender = renderLinearChart({
@@ -390,6 +493,7 @@ describe('LinearChart', () => {
390
493
  axisPadding: 0,
391
494
  labelOffset: 240
392
495
  },
496
+ yAxis: sidePlacementYAxis,
393
497
  runtime: {
394
498
  ...baseRuntime,
395
499
  yAxis: {
@@ -34,6 +34,7 @@ export const createMockConfig = (overrides: Partial<ChartConfig> = {}): ChartCon
34
34
  size: '50',
35
35
  gridLines: true,
36
36
  label: 'Y-Axis',
37
+ titlePlacement: 'top',
37
38
  tickRotation: 0,
38
39
  anchors: [],
39
40
  axisPadding: 0,