@cdc/chart 4.25.8 → 4.25.11

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 (145) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/dist/{cdcchart-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
  3. package/dist/cdcchart.js +44236 -40355
  4. package/examples/feature/__data__/planet-example-data.json +0 -30
  5. package/examples/feature/boxplot/valid-boxplot.csv +38 -17
  6. package/examples/grouped-bar-test.json +400 -0
  7. package/examples/private/DEV-11825.json +573 -0
  8. package/examples/private/d.json +382 -0
  9. package/examples/private/example-2.json +49784 -0
  10. package/examples/private/f2.json +1 -0
  11. package/examples/private/f4.json +1577 -0
  12. package/examples/private/forecast.json +1180 -0
  13. package/examples/private/lollipop.json +468 -0
  14. package/examples/private/na.json +913 -0
  15. package/examples/private/new.json +48756 -0
  16. package/examples/private/pie-chart-legend.json +904 -0
  17. package/examples/private/test-data.csv +28 -0
  18. package/examples/suppressed_tooltip.json +480 -0
  19. package/index.html +2 -133
  20. package/package.json +25 -7
  21. package/src/CdcChart.tsx +9 -13
  22. package/src/CdcChartComponent.tsx +403 -92
  23. package/src/_stories/Chart.Anchors.stories.tsx +2 -2
  24. package/src/_stories/Chart.BoxPlot.stories.tsx +1 -1
  25. package/src/_stories/Chart.CI.stories.tsx +1 -1
  26. package/src/_stories/Chart.Combo.stories.tsx +18 -0
  27. package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
  28. package/src/_stories/Chart.DynamicSeries.stories.tsx +2 -2
  29. package/src/_stories/Chart.Filters.stories.tsx +2 -2
  30. package/src/_stories/Chart.Forecast.stories.tsx +36 -0
  31. package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
  32. package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
  33. package/src/_stories/Chart.Patterns.stories.tsx +20 -0
  34. package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
  35. package/src/_stories/Chart.ScatterPlot.stories.tsx +1 -1
  36. package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
  37. package/src/_stories/Chart.stories.tsx +8 -5
  38. package/src/_stories/Chart.tooltip.stories.tsx +1 -1
  39. package/src/_stories/ChartAnnotation.stories.tsx +7 -4
  40. package/src/_stories/ChartAxisLabels.stories.tsx +2 -2
  41. package/src/_stories/ChartAxisTitles.stories.tsx +2 -2
  42. package/src/_stories/ChartBar.Editor.stories.tsx +3580 -0
  43. package/src/_stories/ChartEditor.Editor.stories.tsx +658 -0
  44. package/src/_stories/ChartEditor.stories.tsx +59 -60
  45. package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
  46. package/src/_stories/ChartLine.Symbols.stories.tsx +1 -1
  47. package/src/_stories/ChartPrefixSuffix.stories.tsx +2 -2
  48. package/src/_stories/_mock/combo.json +451 -0
  49. package/src/_stories/_mock/editor-test-configs.json +376 -0
  50. package/src/_stories/_mock/editor-test-datasets.json +477 -0
  51. package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
  52. package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
  53. package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
  54. package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
  55. package/src/_stories/_mock/pie_config.json +257 -62
  56. package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
  57. package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
  58. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
  59. package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
  60. package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
  61. package/src/_stories/_mock/stacked-pattern-test.json +520 -0
  62. package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -0
  63. package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
  64. package/src/components/Annotations/components/findNearestDatum.ts +6 -41
  65. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -6
  66. package/src/components/AreaChart/index.tsx +1 -2
  67. package/src/components/BarChart/components/BarChart.Horizontal.tsx +161 -22
  68. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +138 -5
  69. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +215 -73
  70. package/src/components/BarChart/components/BarChart.Vertical.tsx +155 -22
  71. package/src/components/BarChart/helpers/index.ts +43 -4
  72. package/src/components/BarChart/helpers/lollipopColors.ts +27 -0
  73. package/src/components/BarChart/helpers/useBarChart.ts +25 -3
  74. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +2 -1
  75. package/src/components/BoxPlot/helpers/index.ts +3 -3
  76. package/src/components/Brush/BrushChart.tsx +1 -1
  77. package/src/components/DeviationBar.jsx +9 -6
  78. package/src/components/EditorPanel/EditorPanel.tsx +563 -229
  79. package/src/components/EditorPanel/EditorPanelContext.ts +3 -0
  80. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
  81. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -1
  82. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +461 -0
  83. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +80 -67
  84. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +422 -0
  85. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +188 -139
  86. package/src/components/EditorPanel/components/Panels/index.tsx +5 -1
  87. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +0 -8
  88. package/src/components/EditorPanel/editor-panel.scss +0 -20
  89. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +49 -48
  90. package/src/components/EditorPanel/useEditorPermissions.ts +7 -15
  91. package/src/components/Forecasting/Forecasting.tsx +175 -27
  92. package/src/components/ForestPlot/ForestPlot.tsx +11 -7
  93. package/src/components/ForestPlot/ForestPlotProps.ts +1 -1
  94. package/src/components/Legend/Legend.Component.tsx +114 -14
  95. package/src/components/Legend/helpers/createFormatLabels.tsx +230 -171
  96. package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
  97. package/src/components/LegendWrapper.tsx +1 -1
  98. package/src/components/LineChart/LineChartProps.ts +0 -3
  99. package/src/components/LineChart/components/LineChart.Circle.tsx +2 -2
  100. package/src/components/LineChart/helpers.ts +1 -1
  101. package/src/components/LineChart/index.tsx +38 -15
  102. package/src/components/LinearChart.tsx +96 -84
  103. package/src/components/PairedBarChart.jsx +6 -4
  104. package/src/components/PieChart/PieChart.tsx +170 -54
  105. package/src/components/Regions/components/Regions.tsx +3 -24
  106. package/src/components/Sankey/components/Sankey.tsx +7 -1
  107. package/src/components/Sankey/types/index.ts +1 -1
  108. package/src/components/ScatterPlot/ScatterPlot.jsx +32 -4
  109. package/src/components/SmallMultiples/SmallMultipleTile.tsx +198 -0
  110. package/src/components/SmallMultiples/SmallMultiples.css +32 -0
  111. package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
  112. package/src/components/SmallMultiples/index.ts +2 -0
  113. package/src/data/initial-state.js +327 -293
  114. package/src/helpers/buildForecastPaletteMappings.ts +112 -0
  115. package/src/helpers/buildForecastPaletteOptions.ts +71 -0
  116. package/src/helpers/getColorScale.ts +82 -8
  117. package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +14 -7
  118. package/src/helpers/getNewRuntime.ts +1 -1
  119. package/src/helpers/getTransformedData.ts +1 -1
  120. package/src/helpers/getYAxisAutoPadding.ts +53 -0
  121. package/src/helpers/smallMultiplesHelpers.ts +529 -0
  122. package/src/hooks/useChartHoverAnalytics.tsx +44 -0
  123. package/src/hooks/useProgrammaticTooltip.ts +96 -0
  124. package/src/hooks/useReduceData.ts +105 -70
  125. package/src/hooks/useScales.ts +88 -34
  126. package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
  127. package/src/hooks/useTooltip.tsx +116 -29
  128. package/src/index.jsx +0 -2
  129. package/src/scss/main.scss +13 -80
  130. package/src/store/chart.actions.ts +2 -0
  131. package/src/store/chart.reducer.ts +5 -1
  132. package/src/test/CdcChart.test.jsx +8 -3
  133. package/src/types/ChartConfig.ts +53 -11
  134. package/src/types/ChartContext.ts +4 -0
  135. package/vite.config.js +1 -1
  136. package/vitest.config.ts +16 -0
  137. package/src/_stories/_mock/pie_data.json +0 -218
  138. package/src/components/AreaChart/components/AreaChart.jsx +0 -109
  139. package/src/coreStyles_chart.scss +0 -3
  140. package/src/helpers/configHelpers.ts +0 -28
  141. package/src/helpers/generateColorsArray.ts +0 -8
  142. package/src/helpers/sort.ts +0 -7
  143. package/src/hooks/useActiveElement.js +0 -19
  144. package/src/hooks/useChartClasses.js +0 -41
  145. package/src/hooks/useColorPalette.js +0 -76
@@ -1,37 +1,74 @@
1
1
  import isNumber from '@cdc/core/helpers/isNumber'
2
+ import { useMemo } from 'react'
2
3
 
3
4
  function useReduceData(config, data) {
4
- const isBar = config.series.every(({ type }) => type === 'Bar')
5
- const isAllLine = config.series.every(({ type }) => ['Line', 'dashed-sm', 'dashed-md', 'dashed-lg'].includes(type))
6
- const sumYValues = seriesKeys => xValue =>
7
- seriesKeys.reduce((yTotal, k) => (isNaN(Number(xValue[k])) ? yTotal : yTotal + Number(xValue[k])), 0)
8
- const getSeriesKey = seriesKey => {
9
- const series = config.runtime.series.find(item => item.dataKey === seriesKey)
10
- return series?.dynamicCategory ? series.originalDataKey : seriesKey
11
- }
12
- const getMaxValueFromData = () => {
13
- let max = Math.max(
14
- ...data?.map(d =>
15
- Math.max(
16
- ...config.runtime.seriesKeys.map(key => {
17
- const seriesKey = getSeriesKey(key)
18
- return isNumber(d[seriesKey]) ? Number(cleanChars(d[seriesKey])) : 0
19
- })
20
- )
21
- )
22
- )
5
+ return useMemo(() => {
6
+ if (!data || !config?.runtime?.seriesKeys) {
7
+ return { minValue: 0, maxValue: 0, existPositiveValue: false, isAllLine: false }
8
+ }
9
+
10
+ const isBar = config.series.every(({ type }) => type === 'Bar')
11
+ const isAllLine = config.series.every(({ type }) => ['Line', 'dashed-sm', 'dashed-md', 'dashed-lg'].includes(type))
12
+
13
+ const cleanChars = value => {
14
+ if (value === null || value === '') {
15
+ return ''
16
+ }
17
+ return typeof value === 'string' ? value.replace(/[,$]/g, '') : value
18
+ }
19
+
20
+ const getSeriesKey = seriesKey => {
21
+ const series = config.runtime.series.find(item => item.dataKey === seriesKey)
22
+ return series?.dynamicCategory ? series.originalDataKey : seriesKey
23
+ }
24
+
25
+ const seriesKeysMap = new Map()
26
+ config.runtime.seriesKeys.forEach(key => {
27
+ seriesKeysMap.set(key, getSeriesKey(key))
28
+ })
29
+
30
+ let minValue = Infinity
31
+ let maxValue = -Infinity
32
+ let existPositiveValue = false
33
+ const stackedTotals = []
34
+
35
+ for (let i = 0; i < data.length; i++) {
36
+ const row = data[i]
37
+ let rowMax = -Infinity
38
+ let rowMin = Infinity
39
+ let stackedSum = 0
40
+
41
+ for (const key of config.runtime.seriesKeys) {
42
+ const seriesKey = seriesKeysMap.get(key)
43
+ const cleanValue = cleanChars(row[seriesKey])
44
+
45
+ if (isNumber(cleanValue)) {
46
+ const numValue = Number(cleanValue)
47
+
48
+ if (numValue > rowMax) rowMax = numValue
49
+ if (numValue < rowMin) rowMin = numValue
50
+
51
+ if (numValue >= 0) existPositiveValue = true
52
+
53
+ if (!isNaN(numValue)) stackedSum += numValue
54
+ }
55
+ }
56
+
57
+ if (rowMax > maxValue) maxValue = rowMax
58
+ if (rowMin < minValue) minValue = rowMin
59
+
60
+ if (!isNaN(stackedSum)) stackedTotals.push(stackedSum)
61
+ }
23
62
 
24
63
  if (
25
64
  (config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && isBar)) &&
26
65
  config.visualizationSubType === 'stacked'
27
66
  ) {
28
- const yTotals = data.map(sumYValues(config.runtime.seriesKeys)).filter(num => !isNaN(num))
29
- max = Math.max(...yTotals)
67
+ maxValue = Math.max(...stackedTotals)
30
68
  }
31
69
 
32
70
  if (config.visualizationSubType === 'stacked' && config.visualizationType === 'Area Chart') {
33
- const yTotals = data.map(sumYValues(config.runtime.seriesKeys))
34
- max = Math.max(...yTotals)
71
+ maxValue = Math.max(...stackedTotals)
35
72
  }
36
73
 
37
74
  if (
@@ -39,62 +76,60 @@ function useReduceData(config, data) {
39
76
  config.series &&
40
77
  config.series.dataKey
41
78
  ) {
42
- max = Math.max(
43
- ...data.map(d => (isNumber(d[config.series.dataKey]) ? Number(cleanChars(d[config.series.dataKey])) : 0))
44
- )
79
+ let specialMax = -Infinity
80
+ for (const row of data) {
81
+ const cleanValue = cleanChars(row[config.series.dataKey])
82
+ if (isNumber(cleanValue)) {
83
+ const numValue = Number(cleanValue)
84
+ if (numValue > specialMax) specialMax = numValue
85
+ }
86
+ }
87
+ maxValue = specialMax
45
88
  }
46
89
 
47
90
  if (config.visualizationType === 'Combo' && config.visualizationSubType === 'stacked' && !isBar) {
48
91
  if (config.runtime.barSeriesKeys && config.runtime.lineSeriesKeys) {
49
- const yTotals = data.map(sumYValues(config.runtime.barSeriesKeys))
50
-
51
- const lineMax = Math.max(
52
- ...data.map(d => Math.max(...config.runtime.lineSeriesKeys.map(key => Number(cleanChars(d[key])))))
53
- )
54
- const barMax = Math.max(...yTotals)
55
-
56
- max = Math.max(barMax, lineMax)
92
+ let barMax = -Infinity
93
+ let lineMax = -Infinity
94
+
95
+ for (const row of data) {
96
+ let barSum = 0
97
+ let rowLineMax = -Infinity
98
+
99
+ for (const key of config.runtime.barSeriesKeys) {
100
+ const cleanValue = cleanChars(row[key])
101
+ if (isNumber(cleanValue)) {
102
+ const numValue = Number(cleanValue)
103
+ if (!isNaN(numValue)) barSum += numValue
104
+ }
105
+ }
106
+
107
+ for (const key of config.runtime.lineSeriesKeys) {
108
+ const cleanValue = cleanChars(row[key])
109
+ if (isNumber(cleanValue)) {
110
+ const numValue = Number(cleanValue)
111
+ if (numValue > rowLineMax) rowLineMax = numValue
112
+ }
113
+ }
114
+
115
+ if (barSum > barMax) barMax = barSum
116
+ if (rowLineMax > lineMax) lineMax = rowLineMax
117
+ }
118
+
119
+ maxValue = Math.max(barMax, lineMax)
57
120
  }
58
121
  }
59
122
 
60
- return max
61
- }
62
-
63
- const getMinValueFromData = () => {
64
- const minNumberFromData = Math.min(
65
- ...data.map(d =>
66
- Math.min(
67
- ...config.runtime.seriesKeys.map(key => {
68
- const seriesKey = getSeriesKey(key)
69
- return isNumber(d[seriesKey]) ? Number(cleanChars(d[seriesKey])) : Infinity
70
- })
71
- )
72
- )
73
- )
74
-
75
- return String(minNumberFromData)
76
- }
77
-
78
- const findPositiveNum = () => {
79
- if (!config.runtime.seriesKeys) {
80
- return false
81
- }
82
- return config.runtime.seriesKeys.some(key => data.some(d => d[getSeriesKey(key)] >= 0))
83
- }
123
+ if (minValue === Infinity) minValue = 0
124
+ if (maxValue === -Infinity) maxValue = 0
84
125
 
85
- const cleanChars = value => {
86
- if (value === null || value === '') {
87
- return ''
126
+ return {
127
+ minValue: Number(minValue),
128
+ maxValue: Number(maxValue),
129
+ existPositiveValue,
130
+ isAllLine
88
131
  }
89
-
90
- return typeof value === 'string' ? value.replace(/[,$]/g, '') : value
91
- }
92
-
93
- const maxValue = Number(getMaxValueFromData())
94
- const minValue = Number(getMinValueFromData())
95
- const existPositiveValue = findPositiveNum()
96
-
97
- return { minValue, maxValue, existPositiveValue, isAllLine }
132
+ }, [config, data])
98
133
  }
99
134
 
100
135
  export default useReduceData
@@ -13,6 +13,9 @@ import ConfigContext from '../ConfigContext'
13
13
  import { ChartConfig } from '../types/ChartConfig'
14
14
  import { ChartContext } from '../types/ChartContext'
15
15
  import _ from 'lodash'
16
+ import { getYAxisAutoPadding } from '../helpers/getYAxisAutoPadding'
17
+ import getMinMax from '../helpers/getMinMax'
18
+ import { countNumOfTicks } from '../helpers/countNumOfTicks'
16
19
 
17
20
  const scaleTypes = {
18
21
  TIME: 'time',
@@ -27,23 +30,84 @@ export const TOP_PADDING = 10
27
30
  type useScaleProps = {
28
31
  config: ChartConfig // standard chart config
29
32
  data: Object[] // standard data array
30
- max: number // maximum value from useMinMax hook
31
- min: number // minimum value from useMinMax hook
33
+ tableData: Object[] // table data for getMinMax
34
+ minValue: number // raw minimum value from data
35
+ maxValue: number // raw maximum value from data
36
+ existPositiveValue: boolean // whether data contains positive values
37
+ isAllLine: boolean // whether all series are line type
32
38
  xAxisDataMapped: Object[] // array of x axis date/category items
33
39
  xMax: number // chart svg width
34
40
  yMax: number // chart svg height
41
+ needsYAxisAutoPadding?: boolean // whether Y-axis needs auto padding for label overflow
42
+ currentViewport?: string // current viewport for tick calculation
35
43
  }
36
44
 
37
45
  const useScales = (properties: useScaleProps) => {
38
- let { xAxisDataMapped, xMax, yMax, min, max, config, data } = properties
39
-
40
- const { rawData, dimensions } = useContext<ChartContext>(ConfigContext)
46
+ let {
47
+ xAxisDataMapped,
48
+ xMax,
49
+ yMax,
50
+ config,
51
+ data,
52
+ tableData,
53
+ minValue,
54
+ maxValue,
55
+ existPositiveValue,
56
+ isAllLine,
57
+ needsYAxisAutoPadding,
58
+ currentViewport
59
+ } = properties
60
+
61
+ const context = useContext<ChartContext>(ConfigContext)
62
+ const { rawData, dimensions, convertLineToBarGraph = false } = context
41
63
 
42
64
  const [screenWidth] = dimensions
65
+ const isHorizontal = config.orientation === 'horizontal'
66
+ const { visualizationType, xAxis, forestPlot, runtime } = config
67
+ const isForestPlot = visualizationType === 'Forest Plot'
68
+
69
+ const minMaxProps = {
70
+ config,
71
+ minValue,
72
+ maxValue,
73
+ existPositiveValue,
74
+ data,
75
+ isAllLine,
76
+ tableData,
77
+ convertLineToBarGraph
78
+ }
79
+ let { min, max, leftMax, rightMax } = getMinMax(minMaxProps)
80
+
81
+ const yTickCount = countNumOfTicks({
82
+ axis: 'yAxis',
83
+ max,
84
+ runtime,
85
+ currentViewport,
86
+ isHorizontal,
87
+ data,
88
+ config,
89
+ min
90
+ })
91
+ const handleNumTicks = isForestPlot ? config.data.length : yTickCount
92
+
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
+ }
106
+ }
107
+ }
108
+
43
109
  const seriesDomain = config.runtime.barSeriesKeys || config.runtime.seriesKeys
44
110
  const xAxisType = config.runtime.xAxis.type
45
- const isHorizontal = config.orientation === 'horizontal'
46
- const { visualizationType, xAxis, forestPlot } = config
47
111
  const paddingRange = ['Area Chart', 'Forecasting'].includes(config.visualizationType) ? 1 : 1 - config.barThickness
48
112
  // define scales
49
113
  let xScale = null
@@ -59,7 +123,7 @@ const useScales = (properties: useScaleProps) => {
59
123
 
60
124
  // handle Horizontal bars
61
125
  if (isHorizontal) {
62
- xScale = composeXScale({ min: min * 1.03, ...properties })
126
+ xScale = composeXScale({ min: min * 1.03, max, xMax, config })
63
127
  xScale.type = config.yAxis.type === 'logarithmic' ? scaleTypes.LOG : scaleTypes.LINEAR
64
128
  yScale = getYScaleFunction(xAxisType, xAxisDataMapped)
65
129
  yScale.rangeRound([0, yMax])
@@ -69,7 +133,7 @@ const useScales = (properties: useScaleProps) => {
69
133
  // handle Vertical bars
70
134
  if (!isHorizontal) {
71
135
  xScale = composeScaleBand(xAxisDataMapped, [0, xMax], paddingRange)
72
- yScale = composeYScale(properties)
136
+ yScale = composeYScale({ min, max, yMax, config, leftMax })
73
137
  seriesScale = composeScaleBand(seriesDomain, [0, xScale.bandwidth()], 0)
74
138
  }
75
139
 
@@ -290,17 +354,29 @@ const useScales = (properties: useScaleProps) => {
290
354
  }
291
355
  }
292
356
  }
293
- return { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding, xScaleAnnotation }
357
+ return {
358
+ xScale,
359
+ yScale,
360
+ seriesScale,
361
+ g1xScale,
362
+ g2xScale,
363
+ xScaleNoPadding,
364
+ xScaleAnnotation,
365
+ min,
366
+ max,
367
+ leftMax,
368
+ rightMax
369
+ }
294
370
  }
295
371
 
296
372
  export default useScales
297
373
 
298
- export const getFirstDayOfMonth = ms => {
374
+ const getFirstDayOfMonth = ms => {
299
375
  const date = new Date(ms)
300
376
  return new Date(date.getFullYear(), date.getMonth(), 1).getTime()
301
377
  }
302
378
 
303
- export const dateFormatHasMonthButNoDays = dateFormat => {
379
+ const dateFormatHasMonthButNoDays = dateFormat => {
304
380
  return (
305
381
  (dateFormat.includes('%b') ||
306
382
  dateFormat.includes('%B') ||
@@ -352,28 +428,6 @@ export const getTickValues = (xAxisDataMapped, xScale, num, config) => {
352
428
  }
353
429
  }
354
430
 
355
- // Ensure that the last tick is shown for charts with a "Date (Linear Scale)" scale
356
- export const filterAndShiftLinearDateTicks = (config, axisProps, xAxisDataMapped, formatDate) => {
357
- let ticks = axisProps.ticks
358
- const filteredTickValues = getTicks(axisProps.scale, axisProps.numTicks)
359
- if (filteredTickValues.length < xAxisDataMapped.length) {
360
- let shift = 0
361
- const lastIdx = xAxisDataMapped.indexOf(filteredTickValues[filteredTickValues.length - 1])
362
- if (lastIdx < xAxisDataMapped.length - 1) {
363
- shift = !config.xAxis.sortByRecentDate
364
- ? xAxisDataMapped.length - 1 - lastIdx
365
- : xAxisDataMapped.indexOf(filteredTickValues[0]) * -1
366
- }
367
- ticks = filteredTickValues.map(value => {
368
- return axisProps.ticks[axisProps.ticks.findIndex(tick => tick.value === value) + shift]
369
- })
370
- }
371
- ticks.forEach((tick, i) => {
372
- tick.formattedValue = formatDate(tick.value, i, ticks)
373
- })
374
- return ticks
375
- }
376
-
377
431
  /// helper functions
378
432
  const composeXScale = ({ min, max, xMax, config }) => {
379
433
  // Adjust min value if using logarithmic scale
@@ -0,0 +1,59 @@
1
+ import { useContext } from 'react'
2
+ import ConfigContext from '../ConfigContext'
3
+
4
+ /**
5
+ * Custom hook to handle synchronized tooltips in small multiples.
6
+ * This hook provides mouse event handlers that coordinate tooltip display across multiple chart tiles.
7
+ *
8
+ * @param xMax - The maximum x coordinate of the chart area
9
+ * @param yMax - The maximum y coordinate of the chart area
10
+ * @param getXValueFromCoordinate - Function to convert pixel x-coordinate to data value
11
+ * @returns Object with onMouseMove and onMouseLeave handlers, or null if not in small multiples
12
+ */
13
+ export const useSmallMultipleSynchronization = (
14
+ xMax: number,
15
+ yMax: number,
16
+ getXValueFromCoordinate: (x: number) => any
17
+ ) => {
18
+ const { config, handleSmallMultipleHover } = useContext(ConfigContext)
19
+
20
+ // If not in small multiples mode, return null handlers
21
+ if (!handleSmallMultipleHover) {
22
+ return {
23
+ onMouseMove: null,
24
+ onMouseLeave: null
25
+ }
26
+ }
27
+
28
+ const yAxisSize = Number(config.yAxis.size || 0)
29
+
30
+ const onMouseMove = (event: any) => {
31
+ const svgRect = event.currentTarget.getBoundingClientRect()
32
+ const x = event.clientX - svgRect.left
33
+ const y = event.clientY - svgRect.top
34
+
35
+ // Only trigger synchronized tooltips when mouse is over the valid chart area
36
+ // (to the right of the Y-axis and within chart bounds)
37
+ const isOverChartArea = x >= yAxisSize && x <= yAxisSize + xMax && y >= 0 && y <= yMax
38
+
39
+ if (isOverChartArea) {
40
+ const xAxisValue = getXValueFromCoordinate(x - yAxisSize)
41
+ if (xAxisValue !== null && xAxisValue !== undefined) {
42
+ handleSmallMultipleHover(xAxisValue, y)
43
+ return
44
+ }
45
+ }
46
+
47
+ // If we're not over a valid area or couldn't get a value, hide synchronized tooltips
48
+ handleSmallMultipleHover(null, null)
49
+ }
50
+
51
+ const onMouseLeave = () => {
52
+ handleSmallMultipleHover(null, null)
53
+ }
54
+
55
+ return {
56
+ onMouseMove,
57
+ onMouseLeave
58
+ }
59
+ }