@cdc/chart 4.26.3 → 4.26.5

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 (192) hide show
  1. package/CONFIG.md +489 -0
  2. package/README.md +42 -17
  3. package/dist/cdcchart.js +39024 -36587
  4. package/examples/feature/bar/additional-column-tooltip.json +3 -3
  5. package/examples/feature/deviation/planet-deviation-config.json +2 -2
  6. package/examples/feature/heatmap/average-age-categorical.json +313 -0
  7. package/examples/feature/heatmap/calendar-heatmap.json +134 -0
  8. package/examples/feature/heatmap/cell-values-bottom-axis.json +181 -0
  9. package/examples/feature/heatmap/sparse-aggregation.json +158 -0
  10. package/examples/feature/paired-bar/paired-bar-example.json +2 -2
  11. package/examples/feature/tests-case-rate/case-rate-example-config.json +2 -2
  12. package/examples/feature/tests-covid/covid-confidence-example-config.json +2 -2
  13. package/examples/feature/tests-covid/covid-example-config.json +2 -2
  14. package/examples/feature/tests-cutoff/cutoff-example-config.json +2 -2
  15. package/examples/feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json +2 -2
  16. package/examples/metadata-variables.json +1 -1
  17. package/examples/minimal-example.json +24 -0
  18. package/examples/private/bump-chart.json +1116 -0
  19. package/examples/private/test-123.json +624 -0
  20. package/examples/private/test2.json +35012 -0
  21. package/package.json +5 -4
  22. package/src/CdcChartComponent.tsx +144 -60
  23. package/src/_stories/Chart.AltText.stories.tsx +136 -0
  24. package/src/_stories/{Chart.HTMLInDataTable.stories.tsx → Chart.HTMLInDataTable.smoke.stories.tsx} +10 -0
  25. package/src/_stories/Chart.HeatMap.stories.tsx +187 -0
  26. package/src/_stories/Chart.Minimal.smoke.stories.tsx +25 -0
  27. package/src/_stories/Chart.Sankey.smoke.stories.tsx +62 -0
  28. package/src/_stories/Chart.SmallMultiples.smoke.stories.tsx +108 -0
  29. package/src/_stories/{Chart.stories.tsx → Chart.smoke.stories.tsx} +12 -0
  30. package/src/_stories/{Chart.tooltip.stories.tsx → Chart.tooltip.smoke.stories.tsx} +68 -0
  31. package/src/_stories/{ChartAxisTitles.stories.tsx → ChartAxisTitles.smoke.stories.tsx} +13 -0
  32. package/src/_stories/ChartBar.Editor.stories.tsx +151 -85
  33. package/src/_stories/ChartBrush.Interaction.stories.tsx +112 -0
  34. package/src/_stories/{ChartBrush.stories.tsx → ChartBrush.smoke.stories.tsx} +13 -4
  35. package/src/_stories/ChartEditor.Editor.stories.tsx +23 -0
  36. package/src/_stories/ChartLine.DashedDates.smoke.stories.tsx +23 -0
  37. package/src/_stories/_mock/alt_text_metadata.json +38 -0
  38. package/src/_stories/_mock/heatmap-average-age-categorical.json +315 -0
  39. package/src/_stories/_mock/heatmap.json +156 -0
  40. package/src/_stories/_mock/line_chart_dashed_dates.json +276 -0
  41. package/src/_stories/_mock/paired-bar-abbr.json +1 -1
  42. package/src/_stories/_mock/paired-bar.json +1 -1
  43. package/src/_stories/_mock/reported_pertussis_cases.json +1643 -0
  44. package/src/_stories/_mock/sankey_mock.json +78 -0
  45. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +1 -1
  46. package/src/components/Annotations/components/AnnotationDraggable.tsx +2 -2
  47. package/src/components/Annotations/components/AnnotationDropdown.tsx +5 -2
  48. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -2
  49. package/src/components/Axis/BottomAxis.tsx +1 -1
  50. package/src/components/Axis/LeftAxis.tsx +15 -14
  51. package/src/components/Axis/LeftAxisGridlines.tsx +14 -13
  52. package/src/components/BarChart/components/BarChart.Horizontal.tsx +9 -5
  53. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +9 -5
  54. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +9 -5
  55. package/src/components/BarChart/components/BarChart.Vertical.tsx +9 -6
  56. package/src/components/BarChart/components/BarChart.tsx +2 -6
  57. package/src/components/BarChart/helpers/useBarChart.ts +1 -1
  58. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +3 -3
  59. package/src/components/Brush/BrushSelector.tsx +88 -187
  60. package/src/components/Brush/brushInteraction.ts +22 -0
  61. package/src/components/Brush/tests/brushInteraction.test.ts +38 -0
  62. package/src/components/EditorPanel/EditorPanel.tsx +946 -960
  63. package/src/components/EditorPanel/EditorPanelContext.ts +1 -1
  64. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +229 -165
  65. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +105 -13
  66. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +169 -116
  67. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +159 -109
  68. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
  69. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +14 -28
  70. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -1
  71. package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +57 -0
  72. package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +56 -0
  73. package/src/components/EditorPanel/useEditorPermissions.test.tsx +70 -0
  74. package/src/components/EditorPanel/useEditorPermissions.ts +32 -21
  75. package/src/components/Forecasting/Forecasting.tsx +2 -2
  76. package/src/components/HeatMap/components/HeatMap.tsx +762 -0
  77. package/src/components/HeatMap/components/HeatMapGradientLegend.tsx +114 -0
  78. package/src/components/HeatMap/heatmap.constants.ts +13 -0
  79. package/src/components/HeatMap/heatmap.css +71 -0
  80. package/src/components/HeatMap/helpers/index.ts +261 -0
  81. package/src/components/HeatMap/index.tsx +5 -0
  82. package/src/components/HeatMap/tests/HeatMap.test.tsx +1040 -0
  83. package/src/components/HeatMap/tests/helpers.test.ts +167 -0
  84. package/src/components/HorizonChart/HorizonChart.tsx +10 -2
  85. package/src/components/HoverLine/HoverLine.tsx +6 -8
  86. package/src/components/Legend/Legend.Component.tsx +4 -4
  87. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +5 -0
  88. package/src/components/Legend/LegendGroup/LegendGroup.tsx +3 -1
  89. package/src/components/Legend/helpers/index.ts +0 -2
  90. package/src/components/LineChart/LineChartProps.ts +1 -0
  91. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +22 -24
  92. package/src/components/LineChart/helpers.test.ts +345 -0
  93. package/src/components/LineChart/helpers.ts +15 -5
  94. package/src/components/LineChart/index.tsx +18 -5
  95. package/src/components/LinearChart/VisualizationRenderer.tsx +11 -1
  96. package/src/components/LinearChart/tests/LinearChart.test.tsx +228 -7
  97. package/src/components/LinearChart/tests/mockConfigContext.ts +1 -0
  98. package/src/components/LinearChart.tsx +155 -77
  99. package/src/components/PairedBarChart.jsx +2 -2
  100. package/src/components/PieChart/PieChart.tsx +15 -10
  101. package/src/components/RadarChart/RadarChart.tsx +3 -1
  102. package/src/components/Regions/components/Regions.tsx +199 -35
  103. package/src/components/Sankey/components/Sankey.tsx +7 -3
  104. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +7 -6
  105. package/src/components/ScatterPlot/ScatterPlot.jsx +19 -17
  106. package/src/components/SmallMultiples/SmallMultipleTile.tsx +31 -3
  107. package/src/components/SmallMultiples/SmallMultiples.css +12 -0
  108. package/src/components/SmallMultiples/SmallMultiples.tsx +15 -9
  109. package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +152 -0
  110. package/src/components/Sparkline/components/SparkLine.tsx +5 -10
  111. package/src/components/WarmingStripes/WarmingStripes.tsx +5 -3
  112. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +1 -1
  113. package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +2 -2
  114. package/src/data/initial-state.js +12 -0
  115. package/src/helpers/axisLabelFontSize.ts +8 -0
  116. package/src/helpers/bumpChartTooltip.ts +54 -0
  117. package/src/helpers/calculateLeftYAxisWidth.ts +197 -0
  118. package/src/helpers/ensureSpecialChartAxisTypes.ts +11 -0
  119. package/src/helpers/getMinMax.ts +2 -3
  120. package/src/helpers/getYAxisAutoPadding.ts +8 -6
  121. package/src/helpers/getYAxisAutoPaddingMode.ts +17 -0
  122. package/src/helpers/handleChartAriaLabels.ts +23 -10
  123. package/src/helpers/seriesHighlight.ts +26 -0
  124. package/src/helpers/tests/axisLabelFontSize.test.ts +15 -0
  125. package/src/helpers/tests/bumpChartTooltip.test.ts +75 -0
  126. package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +30 -0
  127. package/src/helpers/tests/getYAxisAutoPadding.test.ts +37 -0
  128. package/src/helpers/tests/getYAxisAutoPaddingMode.test.ts +90 -0
  129. package/src/helpers/tests/handleChartAriaLabels.test.ts +71 -0
  130. package/src/helpers/tests/seriesHighlight.test.ts +61 -0
  131. package/src/helpers/tests/tooltipHelpers.test.ts +151 -0
  132. package/src/helpers/tooltipHelpers.ts +176 -0
  133. package/src/hooks/tests/useScales.test.tsx +105 -0
  134. package/src/hooks/useHighlightedBars.ts +9 -16
  135. package/src/hooks/useProgrammaticTooltip.ts +16 -2
  136. package/src/hooks/useScales.ts +19 -9
  137. package/src/hooks/useSmallMultipleSynchronization.ts +3 -4
  138. package/src/hooks/useTooltip.tsx +167 -38
  139. package/src/index.jsx +5 -2
  140. package/src/scss/main.scss +32 -49
  141. package/src/test/CdcChart.test.jsx +24 -0
  142. package/src/types/ChartConfig.ts +19 -0
  143. package/src/types/ChartContext.ts +2 -0
  144. package/examples/chart-regression-2.json +0 -2360
  145. package/examples/feature/__data__/age-adjusted-rates.json +0 -1486
  146. package/examples/feature/__data__/city-temperature.json +0 -2198
  147. package/examples/feature/bar/Barchart_with_negative.json +0 -34
  148. package/examples/feature/boxplot/box-plot-data.json +0 -71
  149. package/examples/feature/boxplot/boxplot-data.json +0 -156
  150. package/examples/feature/combo/combochart-categories_are_numbers .json +0 -18
  151. package/examples/feature/forest-plot/log.json +0 -26
  152. package/examples/feature/tests-date-exclusions/date-exclusions-data.json +0 -162
  153. package/examples/region-issue.json +0 -2065
  154. package/examples/xaxis.json +0 -493
  155. package/src/_stories/Chart.SmallMultiples.stories.tsx +0 -63
  156. /package/examples/{data → __data__}/data-with-metadata.json +0 -0
  157. /package/examples/feature/bar/{tall-data.json → __data__/tall-data.json} +0 -0
  158. /package/examples/feature/deviation/{planet-deviation-data.json → __data__/planet-deviation-data.json} +0 -0
  159. /package/examples/feature/paired-bar/{paired-bar-data.json → __data__/paired-bar-data.json} +0 -0
  160. /package/examples/feature/paired-bar/{paired-bar-formatted.json → __data__/paired-bar-formatted.json} +0 -0
  161. /package/examples/feature/tests-case-rate/{case-rate-example-data.json → __data__/case-rate-example-data.json} +0 -0
  162. /package/examples/feature/tests-covid/{covid-example-data-confidence.json → __data__/covid-example-data-confidence.json} +0 -0
  163. /package/examples/feature/tests-covid/{covid-example-data.json → __data__/covid-example-data.json} +0 -0
  164. /package/examples/feature/tests-cutoff/{cutoff-example-data.json → __data__/cutoff-example-data.json} +0 -0
  165. /package/examples/feature/tests-non-numerics/{planet-example-data-nonnumeric.json → __data__/planet-example-data-nonnumeric.json} +0 -0
  166. /package/src/_stories/{Chart.Anchors.stories.tsx → Chart.Anchors.smoke.stories.tsx} +0 -0
  167. /package/src/_stories/{Chart.BoxPlot.stories.tsx → Chart.BoxPlot.smoke.stories.tsx} +0 -0
  168. /package/src/_stories/{Chart.CI.stories.tsx → Chart.CI.smoke.stories.tsx} +0 -0
  169. /package/src/_stories/{Chart.Combo.stories.tsx → Chart.Combo.smoke.stories.tsx} +0 -0
  170. /package/src/_stories/{Chart.CustomColors.stories.tsx → Chart.CustomColors.smoke.stories.tsx} +0 -0
  171. /package/src/_stories/{Chart.Defaults.stories.tsx → Chart.Defaults.smoke.stories.tsx} +0 -0
  172. /package/src/_stories/{Chart.DynamicSeries.stories.tsx → Chart.DynamicSeries.smoke.stories.tsx} +0 -0
  173. /package/src/_stories/{Chart.Filters.stories.tsx → Chart.Filters.smoke.stories.tsx} +0 -0
  174. /package/src/_stories/{Chart.Forecast.stories.tsx → Chart.Forecast.smoke.stories.tsx} +0 -0
  175. /package/src/_stories/{Chart.Legend.Gradient.stories.tsx → Chart.Legend.Gradient.smoke.stories.tsx} +0 -0
  176. /package/src/_stories/{Chart.Patterns.stories.tsx → Chart.Patterns.smoke.stories.tsx} +0 -0
  177. /package/src/_stories/{Chart.PreserveDecimals.stories.tsx → Chart.PreserveDecimals.smoke.stories.tsx} +0 -0
  178. /package/src/_stories/{Chart.Regions.Categorical.stories.tsx → Chart.Regions.Categorical.smoke.stories.tsx} +0 -0
  179. /package/src/_stories/{Chart.Regions.DateScale.stories.tsx → Chart.Regions.DateScale.smoke.stories.tsx} +0 -0
  180. /package/src/_stories/{Chart.Regions.DateTimeScale.stories.tsx → Chart.Regions.DateTimeScale.smoke.stories.tsx} +0 -0
  181. /package/src/_stories/{Chart.ScatterPlot.stories.tsx → Chart.ScatterPlot.smoke.stories.tsx} +0 -0
  182. /package/src/_stories/{Chart.SmallestLeftAxisMax.stories.tsx → Chart.SmallestLeftAxisMax.smoke.stories.tsx} +0 -0
  183. /package/src/_stories/{ChartAnnotation.stories.tsx → ChartAnnotation.smoke.stories.tsx} +0 -0
  184. /package/src/_stories/{ChartAxisLabels.stories.tsx → ChartAxisLabels.smoke.stories.tsx} +0 -0
  185. /package/src/_stories/{ChartBrush.Matrix.Continuous.stories.tsx → ChartBrush.Matrix.Continuous.smoke.stories.tsx} +0 -0
  186. /package/src/_stories/{ChartBrush.Matrix.Date.stories.tsx → ChartBrush.Matrix.Date.smoke.stories.tsx} +0 -0
  187. /package/src/_stories/{ChartBrush.Matrix.DateTime.stories.tsx → ChartBrush.Matrix.DateTime.smoke.stories.tsx} +0 -0
  188. /package/src/_stories/{ChartLine.QuadrantAngles.stories.tsx → ChartLine.QuadrantAngles.smoke.stories.tsx} +0 -0
  189. /package/src/_stories/{ChartLine.Suppression.stories.tsx → ChartLine.Suppression.smoke.stories.tsx} +0 -0
  190. /package/src/_stories/{ChartLine.Symbols.stories.tsx → ChartLine.Symbols.smoke.stories.tsx} +0 -0
  191. /package/src/_stories/{ChartPrefixSuffix.stories.tsx → ChartPrefixSuffix.smoke.stories.tsx} +0 -0
  192. /package/src/_stories/{TechAdoptionWithLinks.stories.tsx → TechAdoptionWithLinks.smoke.stories.tsx} +0 -0
@@ -0,0 +1,176 @@
1
+ import { type ChartConfig } from '../types/ChartConfig'
2
+ import { type ColorScale } from '../types/ChartContext'
3
+
4
+ export type TooltipRowKind = 'heading' | 'series' | 'extra'
5
+
6
+ export type TooltipRow = {
7
+ key: string
8
+ value: string | number
9
+ axisPosition?: string
10
+ kind: TooltipRowKind
11
+ markerColor?: string | null
12
+ markerShape?: 'circle' | 'square'
13
+ seriesKey?: string
14
+ }
15
+
16
+ export type TooltipDisplayData = {
17
+ data: TooltipRow[]
18
+ dataXPosition: number
19
+ dataYPosition: number
20
+ useMarkerColumn: boolean
21
+ }
22
+
23
+ type RawTooltipBodyRow = {
24
+ text: string
25
+ markerColor?: string | null
26
+ markerShape?: 'circle' | 'square'
27
+ }
28
+
29
+ export type TooltipMarker = {
30
+ markerColor: string
31
+ markerShape: 'circle' | 'square'
32
+ }
33
+
34
+ const TOOLTIP_MARKER_SUPPORTED_TYPES = new Set(['Line', 'Area Chart', 'Combo', 'Bar', 'Scatter Plot'])
35
+
36
+ export const getTooltipMarkerShape = (config: ChartConfig): 'circle' | 'square' =>
37
+ config.legend?.style === 'boxes' ? 'square' : 'circle'
38
+
39
+ export const shouldUseTooltipLegendMarkers = (config: ChartConfig): boolean => {
40
+ const legendItemCount =
41
+ config.runtime?.seriesLabelsAll?.length || config.runtime?.seriesKeys?.length || config.series?.length || 0
42
+ const sourceLegendVisible = config.legend?.tooltipLegendVisible ?? !config.legend?.hide
43
+
44
+ return (
45
+ TOOLTIP_MARKER_SUPPORTED_TYPES.has(config.visualizationType) &&
46
+ legendItemCount > 1 &&
47
+ sourceLegendVisible &&
48
+ config.legend?.style !== 'gradient' &&
49
+ !config.legend?.groupBy
50
+ )
51
+ }
52
+
53
+ export const getTooltipMarkerColor = (
54
+ config: ChartConfig,
55
+ colorScale: ColorScale | undefined,
56
+ seriesKey?: string
57
+ ): string | null => {
58
+ if (!colorScale || !seriesKey) return null
59
+
60
+ const colorInput = config.runtime?.seriesLabels?.[seriesKey] || seriesKey
61
+ const color = colorScale(colorInput)
62
+ return typeof color === 'string' ? color : null
63
+ }
64
+
65
+ export const getTooltipSeriesMarker = (
66
+ config: ChartConfig,
67
+ colorScale: ColorScale | undefined,
68
+ seriesKey?: string
69
+ ): TooltipMarker | null => {
70
+ if (!shouldUseTooltipLegendMarkers(config)) {
71
+ return null
72
+ }
73
+
74
+ const markerColor = getTooltipMarkerColor(config, colorScale, seriesKey)
75
+ if (!markerColor) {
76
+ return null
77
+ }
78
+
79
+ return {
80
+ markerColor,
81
+ markerShape: getTooltipMarkerShape(config)
82
+ }
83
+ }
84
+
85
+ export const buildTooltipRow = (row: TooltipRow): TooltipRow => row
86
+
87
+ const getMarkerSignature = ({
88
+ markerColor,
89
+ markerShape = 'circle'
90
+ }: {
91
+ markerColor?: string | null
92
+ markerShape?: 'circle' | 'square'
93
+ }): string | null => {
94
+ if (!markerColor) return null
95
+ return `${markerColor}::${markerShape}`
96
+ }
97
+
98
+ export const tooltipShouldUseMarkerColumn = (
99
+ rows: Array<{ markerColor?: string | null; markerShape?: 'circle' | 'square' }>
100
+ ): boolean => {
101
+ const signatures = rows.map(getMarkerSignature).filter(Boolean)
102
+
103
+ if (signatures.length === 0) {
104
+ return false
105
+ }
106
+
107
+ return new Set(signatures).size === signatures.length
108
+ }
109
+
110
+ export const tooltipHasMarkerColumn = (rows: TooltipRow[]): boolean =>
111
+ tooltipShouldUseMarkerColumn(rows.filter(row => row.kind === 'series'))
112
+
113
+ export const buildTooltipBodyRowHtml = ({
114
+ text,
115
+ markerColor,
116
+ markerShape = 'circle',
117
+ useMarkerColumn
118
+ }: RawTooltipBodyRow & {
119
+ useMarkerColumn: boolean
120
+ }): string => {
121
+ if (!useMarkerColumn) {
122
+ return `<li class="tooltip-body">${text}</li>`
123
+ }
124
+
125
+ const marker = markerColor
126
+ ? `<span class="tooltip-marker-swatch tooltip-marker-swatch--${markerShape}" style="background-color: ${markerColor};" aria-hidden="true"></span>`
127
+ : ''
128
+
129
+ return `<li class="tooltip-body tooltip-body--marker-layout"><span class="tooltip-marker-slot" aria-hidden="true">${marker}</span><span class="tooltip-body-content">${text}</span></li>`
130
+ }
131
+
132
+ export const buildTooltipListHtml = ({
133
+ heading,
134
+ bodyRows
135
+ }: {
136
+ heading: string
137
+ bodyRows: RawTooltipBodyRow[]
138
+ }): string => {
139
+ const hasMarkerColumn = tooltipShouldUseMarkerColumn(bodyRows)
140
+ const renderedRows = bodyRows
141
+ .filter(row => row.text !== undefined && row.text !== null && row.text !== '')
142
+ .map(row => buildTooltipBodyRowHtml({ ...row, useMarkerColumn: hasMarkerColumn }))
143
+ .join('')
144
+
145
+ return `<ul><li class="tooltip-heading">${heading}</li>${renderedRows}</ul>`
146
+ }
147
+
148
+ export const buildSeriesTooltipListHtml = ({
149
+ config,
150
+ colorScale,
151
+ heading,
152
+ seriesKey,
153
+ seriesText,
154
+ extraRows = []
155
+ }: {
156
+ config: ChartConfig
157
+ colorScale?: ColorScale
158
+ heading: string
159
+ seriesKey?: string
160
+ seriesText: string
161
+ extraRows?: string[]
162
+ }): string => {
163
+ const marker = getTooltipSeriesMarker(config, colorScale, seriesKey)
164
+
165
+ return buildTooltipListHtml({
166
+ heading,
167
+ bodyRows: [
168
+ {
169
+ text: seriesText,
170
+ markerColor: marker?.markerColor,
171
+ markerShape: marker?.markerShape
172
+ },
173
+ ...extraRows.filter(Boolean).map(text => ({ text }))
174
+ ]
175
+ })
176
+ }
@@ -0,0 +1,105 @@
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
+ const mockGetYAxisAutoPadding = vi.fn(() => 10)
16
+
17
+ vi.mock('../../helpers/getMinMax', () => ({
18
+ default: (...args: any[]) => mockGetMinMax(...args)
19
+ }))
20
+
21
+ vi.mock('../../helpers/getYAxisAutoPadding', () => ({
22
+ getYAxisAutoPadding: (...args: any[]) => mockGetYAxisAutoPadding(...args)
23
+ }))
24
+
25
+ const config = createMockConfig({
26
+ runtime: {
27
+ ...createMockConfig().runtime,
28
+ yAxis: {
29
+ ...createMockConfig().runtime.yAxis,
30
+ numTicks: 5
31
+ }
32
+ } as any
33
+ })
34
+
35
+ const createWrapper = () => {
36
+ const context = createMockChartContext(config)
37
+
38
+ return ({ children }: React.PropsWithChildren) => (
39
+ <ConfigContext.Provider value={context}>{children}</ConfigContext.Provider>
40
+ )
41
+ }
42
+
43
+ const baseProps = {
44
+ config,
45
+ data: [],
46
+ tableData: [],
47
+ minValue: 0,
48
+ maxValue: 100,
49
+ existPositiveValue: true,
50
+ isAllLine: true,
51
+ xAxisDataMapped: [1, 2, 3],
52
+ xMax: 300,
53
+ yMax: 200,
54
+ currentViewport: 'lg' as const
55
+ }
56
+
57
+ describe('useScales', () => {
58
+ beforeEach(() => {
59
+ mockGetMinMax.mockClear()
60
+ mockGetYAxisAutoPadding.mockClear()
61
+ })
62
+
63
+ it('allows repeated padding passes for inline-label mode', () => {
64
+ renderHook(() => useScales({ ...baseProps, yAxisAutoPaddingMode: 'inline-label' }), {
65
+ wrapper: createWrapper()
66
+ })
67
+
68
+ expect(mockGetYAxisAutoPadding).toHaveBeenCalledTimes(3)
69
+ })
70
+
71
+ it('runs a single padding pass for top-title mode', () => {
72
+ renderHook(() => useScales({ ...baseProps, yAxisAutoPaddingMode: 'top-title' }), {
73
+ wrapper: createWrapper()
74
+ })
75
+
76
+ expect(mockGetYAxisAutoPadding).toHaveBeenCalledTimes(1)
77
+ })
78
+
79
+ it('ignores manual y-axis padding while calculating auto-padding', () => {
80
+ const configWithManualPadding = createMockConfig({
81
+ yAxis: {
82
+ ...createMockConfig().yAxis,
83
+ enablePadding: true,
84
+ scalePadding: 90
85
+ }
86
+ })
87
+
88
+ renderHook(() => useScales({ ...baseProps, config: configWithManualPadding, yAxisAutoPaddingMode: 'top-title' }), {
89
+ wrapper: createWrapper()
90
+ })
91
+
92
+ expect(mockGetMinMax.mock.calls[0][0].config.yAxis).toMatchObject({
93
+ enablePadding: false,
94
+ scalePadding: 0
95
+ })
96
+ expect(mockGetYAxisAutoPadding.mock.calls[0][4].yAxis).toMatchObject({
97
+ enablePadding: false,
98
+ scalePadding: 0
99
+ })
100
+ expect(mockGetMinMax.mock.calls[1][0].config.yAxis).toMatchObject({
101
+ enablePadding: true,
102
+ scalePadding: 10
103
+ })
104
+ })
105
+ })
@@ -5,21 +5,14 @@ import { ChartConfig } from '../types/ChartConfig'
5
5
  export const useHighlightedBars = (config: ChartConfig, updateConfig: (config) => void) => {
6
6
  const { formatDate, parseDate } = useContext(ConfigContext)
7
7
 
8
- let highlightedSeries = [] // only allow single series for highlights
9
- let highlightedSeriesKey = ''
10
- let highlightedBarValues = []
11
- let highlightedSeriesValues = []
12
-
13
- if (config.series?.length > 0 && config.data) {
14
- highlightedSeries = config.series[0] // only allow single series for highlights
15
- highlightedSeriesKey = config.series[0].dataKey
16
- highlightedBarValues = config.highlightedBarValues
17
- highlightedSeriesValues = config.data.map(item => item[config.xAxis.dataKey])
18
- } else {
19
- highlightedSeries = [] // only allow single series for highlights
20
- highlightedSeriesKey = ''
21
- highlightedBarValues = []
22
- highlightedSeriesValues = []
8
+ const highlightedSeries = config.series?.length > 0 && config.data ? config.series[0] : [] // only allow single series for highlights
9
+ const highlightedSeriesKey = config.series?.length > 0 && config.data ? config.series[0].dataKey : ''
10
+ const highlightedBarValues = config.series?.length > 0 && config.data ? config.highlightedBarValues : []
11
+
12
+ const getHighlightedSeriesValues = () => {
13
+ if (!(config.series?.length > 0 && config.data)) return []
14
+
15
+ return config.data.map(item => item[config.xAxis.dataKey])
23
16
  }
24
17
 
25
18
  const handleUpdateHighlightedBorderWidth = (e, index) => {
@@ -144,7 +137,7 @@ export const useHighlightedBars = (config: ChartConfig, updateConfig: (config) =
144
137
  highlightedSeries,
145
138
  highlightedSeriesKey,
146
139
  highlightedBarValues,
147
- highlightedSeriesValues,
140
+ getHighlightedSeriesValues,
148
141
  handleUpdateHighlightedBar,
149
142
  handleAddNewHighlightedBar,
150
143
  handleRemoveHighlightedBar,
@@ -3,7 +3,9 @@ import { useRef, useImperativeHandle, ForwardedRef } from 'react'
3
3
  interface UseProgrammaticTooltipProps {
4
4
  svgRef: ForwardedRef<SVGAElement>
5
5
  getCoordinateFromXValue: (xAxisValue: any) => number
6
+ getXValueFromCoordinate: (xCoordinate: number) => any
6
7
  config: any
8
+ yAxisWidth: number
7
9
  setPoint: (point: { x: number; y: number }) => void
8
10
  setShowHoverLine: (show: boolean) => void
9
11
  handleTooltipMouseOver: (event: MouseEvent, additionalChartData?: any) => void
@@ -18,7 +20,9 @@ interface UseProgrammaticTooltipProps {
18
20
  export const useProgrammaticTooltip = ({
19
21
  svgRef,
20
22
  getCoordinateFromXValue,
23
+ getXValueFromCoordinate,
21
24
  config,
25
+ yAxisWidth,
22
26
  setPoint,
23
27
  setShowHoverLine,
24
28
  handleTooltipMouseOver,
@@ -62,7 +66,16 @@ export const useProgrammaticTooltip = ({
62
66
  }
63
67
 
64
68
  const pixelX = getCoordinateFromXValue(xAxisValue)
65
- const adjustedX = pixelX + Number(config.yAxis.size || 0)
69
+ const resolvedXValue = Number.isFinite(pixelX) ? getXValueFromCoordinate(pixelX) : null
70
+
71
+ if (!Number.isFinite(pixelX) || resolvedXValue !== xAxisValue) {
72
+ hideTooltip()
73
+ setShowHoverLine(false)
74
+ setSynchronizedXValue?.(null)
75
+ return
76
+ }
77
+
78
+ const adjustedX = pixelX + yAxisWidth
66
79
 
67
80
  const svgRect = internalSvgRef.current!.getBoundingClientRect()
68
81
 
@@ -103,7 +116,8 @@ export const useProgrammaticTooltip = ({
103
116
  },
104
117
  [
105
118
  getCoordinateFromXValue,
106
- config.yAxis.size,
119
+ getXValueFromCoordinate,
120
+ yAxisWidth,
107
121
  config.visualizationType,
108
122
  setPoint,
109
123
  setShowHoverLine,
@@ -15,6 +15,7 @@ import { ChartConfig } from '../types/ChartConfig'
15
15
  import { ChartContext } from '../types/ChartContext'
16
16
  import _ from 'lodash'
17
17
  import { getYAxisAutoPadding } from '../helpers/getYAxisAutoPadding'
18
+ import { YAxisAutoPaddingMode } from '../helpers/getYAxisAutoPaddingMode'
18
19
  import getMinMax from '../helpers/getMinMax'
19
20
  import { countNumOfTicks } from '../helpers/countNumOfTicks'
20
21
 
@@ -39,7 +40,7 @@ type useScaleProps = {
39
40
  xAxisDataMapped: Object[] // array of x axis date/category items
40
41
  xMax: number // chart svg width
41
42
  yMax: number // chart svg height
42
- needsYAxisAutoPadding?: boolean // whether Y-axis needs auto padding for label overflow
43
+ yAxisAutoPaddingMode?: YAxisAutoPaddingMode // why Y-axis auto padding should run
43
44
  currentViewport?: string // current viewport for tick calculation
44
45
  }
45
46
 
@@ -55,7 +56,7 @@ const useScales = (properties: useScaleProps) => {
55
56
  maxValue,
56
57
  existPositiveValue,
57
58
  isAllLine,
58
- needsYAxisAutoPadding,
59
+ yAxisAutoPaddingMode = 'none',
59
60
  currentViewport
60
61
  } = properties
61
62
 
@@ -65,9 +66,13 @@ const useScales = (properties: useScaleProps) => {
65
66
  const isHorizontal = config.orientation === 'horizontal'
66
67
  const { visualizationType, xAxis, forestPlot, runtime } = config
67
68
  const isForestPlot = visualizationType === 'Forest Plot'
69
+ const shouldApplyYAxisAutoPadding = yAxisAutoPaddingMode !== 'none' && !isHorizontal
70
+ const minMaxConfig = shouldApplyYAxisAutoPadding
71
+ ? { ...config, yAxis: { ...config.yAxis, enablePadding: false, scalePadding: 0 } }
72
+ : config
68
73
 
69
74
  const minMaxProps = {
70
- config,
75
+ config: minMaxConfig,
71
76
  minValue,
72
77
  maxValue,
73
78
  existPositiveValue,
@@ -91,12 +96,17 @@ const useScales = (properties: useScaleProps) => {
91
96
  const handleNumTicks = isForestPlot ? config.data.length : yTickCount
92
97
 
93
98
  // 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 } }
99
+ if (shouldApplyYAxisAutoPadding) {
100
+ const maxPasses = yAxisAutoPaddingMode === 'inline-label' ? 3 : 1
101
+
102
+ for (let i = 0; i < maxPasses; i++) {
103
+ const scale = composeYScale({ min, max, yMax, config: minMaxConfig, leftMax })
104
+ const padding = getYAxisAutoPadding(scale, handleNumTicks, maxValue, minValue, minMaxConfig, yAxisAutoPaddingMode)
105
+ if (padding > 0 || (maxPasses > 1 && i === 0)) {
106
+ const adjustedConfig = {
107
+ ...minMaxConfig,
108
+ yAxis: { ...minMaxConfig.yAxis, scalePadding: padding, enablePadding: true }
109
+ }
100
110
  const result = getMinMax({ ...minMaxProps, config: adjustedConfig })
101
111
  min = result.min
102
112
  max = result.max
@@ -13,6 +13,7 @@ import ConfigContext from '../ConfigContext'
13
13
  export const useSmallMultipleSynchronization = (
14
14
  xMax: number,
15
15
  yMax: number,
16
+ yAxisWidth: number,
16
17
  getXValueFromCoordinate: (x: number) => any
17
18
  ) => {
18
19
  const { config, handleSmallMultipleHover } = useContext(ConfigContext)
@@ -25,8 +26,6 @@ export const useSmallMultipleSynchronization = (
25
26
  }
26
27
  }
27
28
 
28
- const yAxisSize = Number(config.yAxis.size || 0)
29
-
30
29
  const onMouseMove = (event: any) => {
31
30
  const svgRect = event.currentTarget.getBoundingClientRect()
32
31
  const x = event.clientX - svgRect.left
@@ -34,10 +33,10 @@ export const useSmallMultipleSynchronization = (
34
33
 
35
34
  // Only trigger synchronized tooltips when mouse is over the valid chart area
36
35
  // (to the right of the Y-axis and within chart bounds)
37
- const isOverChartArea = x >= yAxisSize && x <= yAxisSize + xMax && y >= 0 && y <= yMax
36
+ const isOverChartArea = x >= yAxisWidth && x <= yAxisWidth + xMax && y >= 0 && y <= yMax
38
37
 
39
38
  if (isOverChartArea) {
40
- const xAxisValue = getXValueFromCoordinate(x - yAxisSize)
39
+ const xAxisValue = getXValueFromCoordinate(x - yAxisWidth)
41
40
  if (xAxisValue !== null && xAxisValue !== undefined) {
42
41
  handleSmallMultipleHover(xAxisValue, y)
43
42
  return