@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,198 @@
1
+ import React from 'react'
2
+ import { render } from '@testing-library/react'
3
+ import { describe, expect, it, beforeAll, vi } from 'vitest'
4
+ import { scaleBand, scaleLinear } from '@visx/scale'
5
+ import AnnotationDraggable, { EVENT_LINE_LABEL_OFFSET, snapEventLineDx } from '../components/AnnotationDraggable'
6
+ import ConfigContext from '../../../ConfigContext'
7
+ import { createMockChartContext } from '../../LinearChart/tests/mockConfigContext'
8
+
9
+ // jsdom compat for visx (ResizeObserver + SVG bbox).
10
+ vi.stubGlobal(
11
+ 'ResizeObserver',
12
+ vi.fn(() => ({
13
+ observe: vi.fn(),
14
+ unobserve: vi.fn(),
15
+ disconnect: vi.fn()
16
+ }))
17
+ )
18
+
19
+ beforeAll(() => {
20
+ const mockBBox = { x: 0, y: 0, width: 100, height: 20 }
21
+ // @ts-expect-error mocking SVG method
22
+ SVGElement.prototype.getBBox = vi.fn(() => mockBBox)
23
+ // @ts-expect-error mocking SVG method
24
+ SVGElement.prototype.getBoundingClientRect = vi.fn(() => ({
25
+ x: 0,
26
+ y: 0,
27
+ width: 100,
28
+ height: 20,
29
+ top: 0,
30
+ left: 0,
31
+ right: 100,
32
+ bottom: 20
33
+ }))
34
+ })
35
+
36
+ const buildScales = (xMax: number, yMax: number) => ({
37
+ xScale: scaleBand({ domain: ['Jan', 'Feb'], range: [0, xMax], padding: 0 }),
38
+ yScale: scaleLinear({ domain: [0, 100], range: [yMax, 0] }),
39
+ xScaleAnnotation: scaleLinear({ domain: [0, 100], range: [0, xMax] }),
40
+ yScaleAnnotation: scaleLinear({ domain: [0, 100], range: [0, yMax] })
41
+ })
42
+
43
+ const baseEventLineAnnotation = {
44
+ text: 'Voting Rights Act',
45
+ style: 'event-line',
46
+ anchorMode: 'data',
47
+ dataX: 'Jan',
48
+ x: 50,
49
+ y: 50,
50
+ dx: 12,
51
+ dy: 0,
52
+ opacity: 100,
53
+ colors: { connector: '#444', label: '#000' },
54
+ edit: { subject: false, label: true },
55
+ anchor: { horizontal: false, vertical: false },
56
+ connectionType: 'line' as const,
57
+ marker: 'arrow' as const,
58
+ snapToSubject: false,
59
+ lineType: 'curveLinear'
60
+ }
61
+
62
+ const renderAnnotationDraggable = (annotation: any, xMax = 800, yMax = 400) => {
63
+ const data = [
64
+ { month: 'Jan', value: 50 },
65
+ { month: 'Feb', value: 75 }
66
+ ]
67
+ const context = createMockChartContext(
68
+ {
69
+ annotations: [annotation],
70
+ visualizationType: 'Line',
71
+ xAxis: { type: 'categorical', dataKey: 'month' } as any,
72
+ series: [{ dataKey: 'value', type: 'Line' }] as any,
73
+ data,
74
+ general: { showAnnotationDropdown: false, mobileAnnotationDisplay: 'symbol' } as any
75
+ } as any,
76
+ {
77
+ transformedData: data,
78
+ visibleAnnotations: [annotation]
79
+ }
80
+ )
81
+
82
+ const scales = buildScales(xMax, yMax)
83
+ return render(
84
+ <ConfigContext.Provider value={context as any}>
85
+ <svg width={xMax} height={yMax}>
86
+ <AnnotationDraggable
87
+ {...scales}
88
+ xMax={xMax}
89
+ yMax={yMax}
90
+ seriesScale={undefined}
91
+ svgRef={{ current: null } as any}
92
+ onDragStateChange={() => {}}
93
+ />
94
+ </svg>
95
+ </ConfigContext.Provider>
96
+ )
97
+ }
98
+
99
+ describe('AnnotationDraggable - event-line style', () => {
100
+ it('renders a full-height vertical line that spans 0..yMax', () => {
101
+ const { container } = renderAnnotationDraggable(baseEventLineAnnotation)
102
+
103
+ const line = container.querySelector('line.annotation__event-line')
104
+ expect(line).toBeTruthy()
105
+ expect(line?.getAttribute('y1')).toBe('0')
106
+ expect(line?.getAttribute('y2')).toBe('400')
107
+ })
108
+
109
+ it('strokes the vertical line with the axis color (#333), ignoring colors.connector', () => {
110
+ const { container } = renderAnnotationDraggable({
111
+ ...baseEventLineAnnotation,
112
+ colors: { connector: '#ff0000', label: '#000' }
113
+ })
114
+
115
+ const line = container.querySelector('line.annotation__event-line')
116
+ expect(line?.getAttribute('stroke')).toBe('#333')
117
+ })
118
+
119
+ it('uses the right-side label class (text-align left) when dx >= 0', () => {
120
+ const { container } = renderAnnotationDraggable({
121
+ ...baseEventLineAnnotation,
122
+ dx: 12
123
+ })
124
+
125
+ expect(container.querySelector('.cove-annotation-event-line__label--right')).toBeTruthy()
126
+ expect(container.querySelector('.cove-annotation-event-line__label--left')).toBeFalsy()
127
+ })
128
+
129
+ it('uses the left-side label class (text-align right) when dx < 0', () => {
130
+ const { container } = renderAnnotationDraggable({
131
+ ...baseEventLineAnnotation,
132
+ dx: -12
133
+ })
134
+
135
+ expect(container.querySelector('.cove-annotation-event-line__label--left')).toBeTruthy()
136
+ expect(container.querySelector('.cove-annotation-event-line__label--right')).toBeFalsy()
137
+ })
138
+
139
+ it('does not render a callout marker (no .circle-subject) for event-line', () => {
140
+ const { container } = renderAnnotationDraggable({
141
+ ...baseEventLineAnnotation,
142
+ marker: 'circle' as const
143
+ })
144
+
145
+ expect(container.querySelector('.circle-subject')).toBeFalsy()
146
+ })
147
+ })
148
+
149
+ describe('snapEventLineDx', () => {
150
+ it('snaps any positive value to +EVENT_LINE_LABEL_OFFSET', () => {
151
+ expect(snapEventLineDx(0)).toBe(EVENT_LINE_LABEL_OFFSET)
152
+ expect(snapEventLineDx(1)).toBe(EVENT_LINE_LABEL_OFFSET)
153
+ expect(snapEventLineDx(47)).toBe(EVENT_LINE_LABEL_OFFSET)
154
+ expect(snapEventLineDx(9999)).toBe(EVENT_LINE_LABEL_OFFSET)
155
+ })
156
+
157
+ it('snaps any negative value to -EVENT_LINE_LABEL_OFFSET', () => {
158
+ expect(snapEventLineDx(-1)).toBe(-EVENT_LINE_LABEL_OFFSET)
159
+ expect(snapEventLineDx(-47)).toBe(-EVENT_LINE_LABEL_OFFSET)
160
+ expect(snapEventLineDx(-9999)).toBe(-EVENT_LINE_LABEL_OFFSET)
161
+ })
162
+ })
163
+
164
+ describe('AnnotationDraggable - event-line dx snapping at render', () => {
165
+ it('still uses right-side class when stored dx is a large positive value', () => {
166
+ const { container } = renderAnnotationDraggable({
167
+ ...baseEventLineAnnotation,
168
+ dx: 47
169
+ })
170
+
171
+ expect(container.querySelector('.cove-annotation-event-line__label--right')).toBeTruthy()
172
+ expect(container.querySelector('.cove-annotation-event-line__label--left')).toBeFalsy()
173
+ })
174
+
175
+ it('still uses left-side class when stored dx is a large negative value', () => {
176
+ const { container } = renderAnnotationDraggable({
177
+ ...baseEventLineAnnotation,
178
+ dx: -47
179
+ })
180
+
181
+ expect(container.querySelector('.cove-annotation-event-line__label--left')).toBeTruthy()
182
+ expect(container.querySelector('.cove-annotation-event-line__label--right')).toBeFalsy()
183
+ })
184
+ })
185
+
186
+ describe('AnnotationDraggable - callout style (regression)', () => {
187
+ it('still renders a callout annotation without a full-height event line', () => {
188
+ const calloutAnnotation = {
189
+ ...baseEventLineAnnotation,
190
+ style: 'callout' as const,
191
+ anchorMode: 'fixed' as const,
192
+ dataX: undefined
193
+ }
194
+ const { container } = renderAnnotationDraggable(calloutAnnotation)
195
+
196
+ expect(container.querySelector('line.annotation__event-line')).toBeFalsy()
197
+ })
198
+ })
@@ -28,6 +28,7 @@ interface LeftAxisProps {
28
28
  xMax: number
29
29
  yAxisWidth: number
30
30
  numTicks: number
31
+ tickValues?: number[]
31
32
  tickLabelFontSize: number
32
33
  axisLabelFontSize: number
33
34
  handleLeftTickFormatting: (tick: any, index: number, ticks: any[]) => string
@@ -45,6 +46,7 @@ const LeftAxis: React.FC<LeftAxisProps> = ({
45
46
  xMax,
46
47
  yAxisWidth,
47
48
  numTicks,
49
+ tickValues,
48
50
  tickLabelFontSize,
49
51
  axisLabelFontSize,
50
52
  handleLeftTickFormatting,
@@ -60,6 +62,7 @@ const LeftAxis: React.FC<LeftAxisProps> = ({
60
62
  const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
61
63
  const { labelsAboveGridlines, hideAxis, inlineLabel } = config.yAxis
62
64
  const inlineLabelHasNoSpace = !inlineLabel?.includes(' ')
65
+ const showSideTitle = config.yAxis.titlePlacement !== 'top'
63
66
 
64
67
  return (
65
68
  <VisxAxisLeft
@@ -70,6 +73,7 @@ const LeftAxis: React.FC<LeftAxisProps> = ({
70
73
  stroke='#333'
71
74
  tickFormat={handleLeftTickFormatting}
72
75
  numTicks={numTicks}
76
+ {...(tickValues ? { tickValues } : {})}
73
77
  >
74
78
  {props => {
75
79
  const axisCenter =
@@ -381,17 +385,19 @@ const LeftAxis: React.FC<LeftAxisProps> = ({
381
385
  })
382
386
  })()}
383
387
 
384
- <Text
385
- className='y-label'
386
- textAnchor='middle'
387
- verticalAnchor='start'
388
- transform={`translate(${-1 * yAxisWidth}, ${axisCenter}) rotate(-90)`}
389
- fontWeight='bold'
390
- fill={config.yAxis.labelColor}
391
- fontSize={axisLabelFontSize}
392
- >
393
- {!config.hideYAxisLabel ? props.label : null}
394
- </Text>
388
+ {showSideTitle && (
389
+ <Text
390
+ className='y-label'
391
+ textAnchor='middle'
392
+ verticalAnchor='start'
393
+ transform={`translate(${-1 * yAxisWidth}, ${axisCenter}) rotate(-90)`}
394
+ fontWeight='bold'
395
+ fill={config.yAxis.labelColor}
396
+ fontSize={axisLabelFontSize}
397
+ >
398
+ {!config.hideYAxisLabel ? props.label : null}
399
+ </Text>
400
+ )}
395
401
  </Group>
396
402
  )
397
403
  }}
@@ -10,6 +10,7 @@ interface LeftAxisGridlinesProps {
10
10
  xMax: number
11
11
  yAxisWidth: number
12
12
  numTicks: number
13
+ tickValues?: number[]
13
14
  axisLabelFontSize: number
14
15
  }
15
16
 
@@ -18,15 +19,22 @@ const LeftAxisGridlines: React.FC<LeftAxisGridlinesProps> = ({
18
19
  xMax,
19
20
  yAxisWidth,
20
21
  numTicks,
22
+ tickValues,
21
23
  axisLabelFontSize
22
24
  }) => {
23
25
  const { config } = useContext(ConfigContext)
24
26
  const { runtime } = config
25
27
 
26
28
  const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
29
+ const showSideTitle = config.yAxis.titlePlacement !== 'top'
27
30
 
28
31
  return (
29
- <AxisLeft scale={yScale} left={yAxisWidth - config.yAxis.axisPadding} numTicks={numTicks}>
32
+ <AxisLeft
33
+ scale={yScale}
34
+ left={yAxisWidth - config.yAxis.axisPadding}
35
+ numTicks={numTicks}
36
+ {...(tickValues ? { tickValues } : {})}
37
+ >
30
38
  {props => {
31
39
  const axisCenter =
32
40
  config.orientation === 'horizontal'
@@ -54,17 +62,19 @@ const LeftAxisGridlines: React.FC<LeftAxisGridlinesProps> = ({
54
62
  </Group>
55
63
  )
56
64
  })}
57
- <Text
58
- className='y-label'
59
- textAnchor='middle'
60
- verticalAnchor='start'
61
- transform={`translate(${-1 * yAxisWidth}, ${axisCenter}) rotate(-90)`}
62
- fontWeight='bold'
63
- fill={config.yAxis.labelColor}
64
- fontSize={axisLabelFontSize}
65
- >
66
- {!config.hideYAxisLabel ? props.label : null}
67
- </Text>
65
+ {showSideTitle && (
66
+ <Text
67
+ className='y-label'
68
+ textAnchor='middle'
69
+ verticalAnchor='start'
70
+ transform={`translate(${-1 * yAxisWidth}, ${axisCenter}) rotate(-90)`}
71
+ fontWeight='bold'
72
+ fill={config.yAxis.labelColor}
73
+ fontSize={axisLabelFontSize}
74
+ >
75
+ {!config.hideYAxisLabel ? props.label : null}
76
+ </Text>
77
+ )}
68
78
  </Group>
69
79
  )
70
80
  }}
@@ -15,6 +15,7 @@ type RightAxisProps = {
15
15
  yMax: number
16
16
  xMax: number
17
17
  yAxisWidth: number
18
+ tickValues?: number[]
18
19
  tickLabelFontSize: number
19
20
  axisLabelFontSize: number
20
21
  }
@@ -29,6 +30,7 @@ export const RightAxis: React.FC<RightAxisProps> = ({
29
30
  yMax,
30
31
  xMax,
31
32
  yAxisWidth,
33
+ tickValues,
32
34
  tickLabelFontSize,
33
35
  axisLabelFontSize
34
36
  }) => {
@@ -45,6 +47,7 @@ export const RightAxis: React.FC<RightAxisProps> = ({
45
47
  label={config.yAxis.rightLabel}
46
48
  tickFormat={tick => formatNumber(tick, 'right')}
47
49
  numTicks={runtime.yAxis.rightNumTicks || undefined}
50
+ {...(tickValues ? { tickValues } : {})}
48
51
  labelOffset={45}
49
52
  >
50
53
  {props => {
@@ -0,0 +1,86 @@
1
+ import React from 'react'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { describe, expect, it, vi } from 'vitest'
4
+ import ConfigContext from '../../../ConfigContext'
5
+ import { createMockChartContext, createMockConfig } from '../../LinearChart/tests/mockConfigContext'
6
+
7
+ vi.mock('@visx/axis', () => ({
8
+ AxisLeft: ({ tickValues }: { tickValues?: number[] }) => (
9
+ <div data-testid='axis-left' data-tick-values={JSON.stringify(tickValues ?? null)} />
10
+ ),
11
+ AxisRight: ({ tickValues }: { tickValues?: number[] }) => (
12
+ <div data-testid='axis-right' data-tick-values={JSON.stringify(tickValues ?? null)} />
13
+ )
14
+ }))
15
+
16
+ import LeftAxis from '../LeftAxis'
17
+ import LeftAxisGridlines from '../LeftAxisGridlines'
18
+ import RightAxis from '../RightAxis'
19
+
20
+ const tickValues = [0, 5, 10, 15]
21
+
22
+ const renderWithContext = (children: React.ReactNode) => {
23
+ const config = createMockConfig({
24
+ runtime: {
25
+ ...createMockConfig().runtime,
26
+ yAxis: {
27
+ ...createMockConfig().runtime.yAxis,
28
+ rightNumTicks: 4
29
+ }
30
+ } as any
31
+ })
32
+
33
+ return render(<ConfigContext.Provider value={createMockChartContext(config)}>{children}</ConfigContext.Provider>)
34
+ }
35
+
36
+ describe('axis tick values', () => {
37
+ it('passes supplied tick values to the left axis', () => {
38
+ renderWithContext(
39
+ <LeftAxis
40
+ yScale={{}}
41
+ xScale={{}}
42
+ yMax={100}
43
+ xMax={300}
44
+ yAxisWidth={50}
45
+ numTicks={4}
46
+ tickValues={tickValues}
47
+ tickLabelFontSize={16}
48
+ axisLabelFontSize={16}
49
+ handleLeftTickFormatting={tick => String(tick)}
50
+ />
51
+ )
52
+
53
+ expect(JSON.parse(screen.getByTestId('axis-left').dataset.tickValues || 'null')).toEqual(tickValues)
54
+ })
55
+
56
+ it('passes supplied tick values to left-axis gridlines', () => {
57
+ renderWithContext(
58
+ <LeftAxisGridlines
59
+ yScale={{}}
60
+ xMax={300}
61
+ yAxisWidth={50}
62
+ numTicks={4}
63
+ tickValues={tickValues}
64
+ axisLabelFontSize={16}
65
+ />
66
+ )
67
+
68
+ expect(JSON.parse(screen.getByTestId('axis-left').dataset.tickValues || 'null')).toEqual(tickValues)
69
+ })
70
+
71
+ it('passes supplied tick values to the right axis', () => {
72
+ renderWithContext(
73
+ <RightAxis
74
+ yScaleRight={{} as any}
75
+ yMax={100}
76
+ xMax={300}
77
+ yAxisWidth={50}
78
+ tickValues={tickValues}
79
+ tickLabelFontSize={16}
80
+ axisLabelFontSize={16}
81
+ />
82
+ )
83
+
84
+ expect(JSON.parse(screen.getByTestId('axis-right').dataset.tickValues || 'null')).toEqual(tickValues)
85
+ })
86
+ })