@cdc/chart 4.25.10 → 4.26.1

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 (135) hide show
  1. package/dist/{cdcchart-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
  2. package/dist/cdcchart.js +44003 -43518
  3. package/examples/feature/__data__/planet-example-data.json +1 -1
  4. package/examples/feature/boxplot/valid-boxplot.csv +38 -17
  5. package/examples/feature/pie/planet-pie-example-config.json +48 -2
  6. package/examples/private/DEV-11825.json +573 -0
  7. package/examples/private/DEV-12100.json +1303 -0
  8. package/examples/private/cat-y.json +1235 -0
  9. package/examples/private/data-points.json +228 -0
  10. package/examples/private/height.json +3915 -0
  11. package/examples/private/links.json +569 -0
  12. package/examples/private/na.json +913 -0
  13. package/examples/private/quadrant.txt +30 -0
  14. package/examples/private/test-data.csv +28 -0
  15. package/examples/private/test-forecast.json +5510 -0
  16. package/examples/private/warming-stripe-test.json +2578 -0
  17. package/examples/private/warming-stripes.json +4763 -0
  18. package/examples/tech-adoption-with-links.json +560 -0
  19. package/index.html +16 -140
  20. package/package.json +6 -5
  21. package/preview.html +1616 -0
  22. package/src/CdcChart.tsx +8 -11
  23. package/src/CdcChartComponent.tsx +329 -124
  24. package/src/_stories/Chart.Combo.stories.tsx +18 -0
  25. package/src/_stories/Chart.Forecast.stories.tsx +36 -0
  26. package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
  27. package/src/_stories/Chart.Patterns.stories.tsx +2 -1
  28. package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
  29. package/src/_stories/Chart.Regions.Categorical.stories.tsx +148 -0
  30. package/src/_stories/Chart.Regions.DateScale.stories.tsx +197 -0
  31. package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +297 -0
  32. package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
  33. package/src/_stories/Chart.stories.tsx +8 -0
  34. package/src/_stories/ChartAnnotation.stories.tsx +6 -3
  35. package/src/_stories/ChartBar.Editor.stories.tsx +3585 -0
  36. package/src/_stories/ChartBrush.Editor.stories.tsx +295 -0
  37. package/src/_stories/ChartBrush.stories.tsx +50 -0
  38. package/src/_stories/ChartEditor.Editor.stories.tsx +656 -0
  39. package/src/_stories/ChartEditor.stories.tsx +1 -2
  40. package/src/_stories/TechAdoptionWithLinks.stories.tsx +27 -0
  41. package/src/_stories/_mock/brush_enabled.json +326 -0
  42. package/src/_stories/_mock/brush_mock.json +2 -69
  43. package/src/_stories/_mock/combo.json +451 -0
  44. package/src/_stories/_mock/editor-test-configs.json +376 -0
  45. package/src/_stories/_mock/editor-test-datasets.json +477 -0
  46. package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
  47. package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
  48. package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
  49. package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
  50. package/src/_stories/_mock/horizontal-bars-dynamic-y-axis.json +413 -0
  51. package/src/_stories/_mock/pie_config.json +257 -62
  52. package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
  53. package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
  54. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
  55. package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
  56. package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
  57. package/src/components/Annotations/components/findNearestDatum.ts +6 -41
  58. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -7
  59. package/src/components/AreaChart/index.tsx +1 -2
  60. package/src/components/Axis/Categorical.Axis.tsx +6 -7
  61. package/src/components/BarChart/components/BarChart.Horizontal.tsx +181 -27
  62. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +3 -1
  63. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -0
  64. package/src/components/BarChart/components/BarChart.Vertical.tsx +8 -9
  65. package/src/components/BarChart/components/context.tsx +1 -0
  66. package/src/components/BarChart/helpers/useBarChart.ts +14 -2
  67. package/src/components/BoxPlot/helpers/index.ts +3 -3
  68. package/src/components/Brush/BrushSelector.tsx +1258 -0
  69. package/src/components/Brush/MiniChartPreview.tsx +283 -0
  70. package/src/components/DeviationBar.jsx +9 -7
  71. package/src/components/EditorPanel/EditorPanel.tsx +2720 -2586
  72. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
  73. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +56 -34
  74. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +76 -31
  75. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +104 -55
  76. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +54 -49
  77. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +427 -0
  78. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +96 -48
  79. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  80. package/src/components/EditorPanel/editor-panel.scss +0 -20
  81. package/src/components/EditorPanel/useEditorPermissions.ts +36 -31
  82. package/src/components/Forecasting/Forecasting.tsx +139 -21
  83. package/src/components/Legend/Legend.Component.tsx +16 -9
  84. package/src/components/Legend/Legend.tsx +3 -2
  85. package/src/components/Legend/helpers/createFormatLabels.tsx +325 -176
  86. package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
  87. package/src/components/Legend/helpers/index.ts +10 -6
  88. package/src/components/LineChart/LineChartProps.ts +0 -3
  89. package/src/components/LineChart/helpers.ts +1 -1
  90. package/src/components/LineChart/index.tsx +36 -13
  91. package/src/components/LinearChart.tsx +559 -499
  92. package/src/components/PairedBarChart.jsx +20 -3
  93. package/src/components/Regions/components/Regions.tsx +366 -144
  94. package/src/components/Sankey/types/index.ts +1 -1
  95. package/src/components/ScatterPlot/ScatterPlot.jsx +2 -2
  96. package/src/components/SmallMultiples/SmallMultipleTile.tsx +202 -0
  97. package/src/components/SmallMultiples/SmallMultiples.css +32 -0
  98. package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
  99. package/src/components/SmallMultiples/index.ts +2 -0
  100. package/src/components/WarmingStripes/WarmingStripes.tsx +160 -0
  101. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +35 -0
  102. package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +104 -0
  103. package/src/components/WarmingStripes/index.tsx +3 -0
  104. package/src/data/initial-state.js +16 -2
  105. package/src/helpers/buildForecastPaletteOptions.ts +0 -38
  106. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +57 -0
  107. package/src/helpers/getColorScale.ts +10 -0
  108. package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +26 -14
  109. package/src/helpers/getYAxisAutoPadding.ts +53 -0
  110. package/src/helpers/sizeHelpers.ts +0 -20
  111. package/src/helpers/smallMultiplesHelpers.ts +529 -0
  112. package/src/hooks/useChartHoverAnalytics.tsx +10 -9
  113. package/src/hooks/useProgrammaticTooltip.ts +96 -0
  114. package/src/hooks/useScales.ts +98 -34
  115. package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
  116. package/src/hooks/useTooltip.tsx +91 -25
  117. package/src/scss/DataTable.scss +0 -4
  118. package/src/scss/main.scss +18 -83
  119. package/src/store/chart.actions.ts +2 -0
  120. package/src/store/chart.reducer.ts +4 -0
  121. package/src/test/CdcChart.test.jsx +1 -1
  122. package/src/types/ChartConfig.ts +27 -6
  123. package/src/types/ChartContext.ts +3 -0
  124. package/src/types/Label.ts +1 -0
  125. package/src/utils/analyticsTracking.ts +19 -0
  126. package/LICENSE +0 -201
  127. package/src/_stories/_mock/pie_data.json +0 -218
  128. package/src/components/AreaChart/components/AreaChart.jsx +0 -109
  129. package/src/components/Brush/BrushChart.tsx +0 -128
  130. package/src/components/Brush/BrushController.tsx +0 -71
  131. package/src/components/Brush/types.tsx +0 -8
  132. package/src/components/BrushChart.tsx +0 -223
  133. package/src/helpers/sort.ts +0 -7
  134. package/src/hooks/useActiveElement.js +0 -19
  135. package/src/hooks/useChartClasses.js +0 -41
@@ -0,0 +1,35 @@
1
+ .warming-stripes-gradient-legend {
2
+ margin: 1rem 0;
3
+ width: 100%;
4
+ }
5
+
6
+ .warming-stripes-gradient-legend__title {
7
+ font-weight: 600;
8
+ margin-bottom: 0.5rem;
9
+ font-size: 16px;
10
+ }
11
+
12
+ .warming-stripes-gradient-legend__description {
13
+ margin-top: 0.5rem;
14
+ margin-bottom: 1rem;
15
+ font-size: 14px;
16
+ color: #555;
17
+ }
18
+
19
+ .warming-stripes-gradient-legend__container {
20
+ width: 100%;
21
+ }
22
+
23
+ .warming-stripes-gradient-legend__svg {
24
+ display: block;
25
+ width: 100%;
26
+ overflow: visible;
27
+ }
28
+
29
+ .warming-stripes-gradient-legend__series-label {
30
+ text-align: center;
31
+ margin-top: 0.5rem;
32
+ font-size: 14px;
33
+ font-weight: 500;
34
+ color: #333;
35
+ }
@@ -0,0 +1,104 @@
1
+ import { useContext } from 'react'
2
+ import ConfigContext from '../../ConfigContext'
3
+ import { filterChartColorPalettes } from '@cdc/core/helpers/filterColorPalettes'
4
+ import { getFallbackColorPalette, migratePaletteWithMap } from '@cdc/core/helpers/palettes/utils'
5
+ import { paletteMigrationMap } from '@cdc/core/helpers/palettes/migratePaletteName'
6
+ import './WarmingStripesGradientLegend.css'
7
+
8
+ const WarmingStripesGradientLegend = () => {
9
+ const { transformedData: data, config, formatNumber } = useContext(ConfigContext)
10
+
11
+ const valueKey = config.runtime.seriesKeys?.[0]
12
+
13
+ if (!valueKey || !data || data.length === 0 || config.legend?.hide) {
14
+ return null
15
+ }
16
+
17
+ // Calculate min and max values
18
+ const values = data.map(d => Number(d[valueKey])).filter(v => !isNaN(v))
19
+ const minValue = Math.min(...values)
20
+ const maxValue = Math.max(...values)
21
+
22
+ // Get the color palette from config (same logic as WarmingStripes component)
23
+ const colorPalettes = filterChartColorPalettes(config)
24
+ const configPalette = config.general?.palette?.name
25
+ const migratedPaletteName = configPalette ? configPalette : getFallbackColorPalette(config)
26
+
27
+ const isReversedPalette = migratedPaletteName?.endsWith('reverse')
28
+ const basePaletteName = isReversedPalette ? migratedPaletteName.slice(0, -7) : migratedPaletteName
29
+
30
+ let palette =
31
+ colorPalettes[migratePaletteWithMap(basePaletteName, paletteMigrationMap, false)] ||
32
+ colorPalettes[basePaletteName] ||
33
+ colorPalettes[configPalette]
34
+
35
+ if (!palette || palette.length < 2) {
36
+ palette = [
37
+ '#053061',
38
+ '#2166ac',
39
+ '#4393c3',
40
+ '#92c5de',
41
+ '#d1e5f0',
42
+ '#f7f7f7',
43
+ '#fddbc7',
44
+ '#f4a582',
45
+ '#d6604d',
46
+ '#b2182b',
47
+ '#67001f'
48
+ ]
49
+ }
50
+
51
+ const shouldReverse = config.general?.palette?.isReversed || isReversedPalette
52
+ const finalPalette = shouldReverse ? [...palette].reverse() : palette
53
+
54
+ // Create gradient stops for SVG
55
+ const gradientStops = finalPalette.map((color, index) => {
56
+ const offset = (index / (finalPalette.length - 1)) * 100
57
+ return { offset: `${offset}%`, color }
58
+ })
59
+
60
+ const seriesLabel = config.runtime.seriesLabels?.[valueKey] || valueKey
61
+ const uniqueId = `warming-stripes-gradient-${config.runtime.uniqueId}`
62
+
63
+ return (
64
+ <div className='warming-stripes-gradient-legend'>
65
+ {config.legend?.label && <h3 className='warming-stripes-gradient-legend__title'>{config.legend.label}</h3>}
66
+ {config.legend?.description && (
67
+ <p className='warming-stripes-gradient-legend__description'>{config.legend.description}</p>
68
+ )}
69
+
70
+ <div className='warming-stripes-gradient-legend__container'>
71
+ <svg className='warming-stripes-gradient-legend__svg' height='50' width='100%'>
72
+ <defs>
73
+ <linearGradient id={uniqueId} x1='0%' y1='0%' x2='100%' y2='0%'>
74
+ {gradientStops.map((stop, index) => (
75
+ <stop key={index} offset={stop.offset} stopColor={stop.color} />
76
+ ))}
77
+ </linearGradient>
78
+ </defs>
79
+
80
+ {/* Border */}
81
+ <rect x='0' y='0' width='100%' height='25' fill='#d3d3d3' />
82
+
83
+ {/* Gradient bar */}
84
+ <rect x='1' y='1' width='calc(100% - 2px)' height='23' fill={`url(#${uniqueId})`} />
85
+
86
+ {/* Min label */}
87
+ <text x='0' y='40' fontSize='14' textAnchor='start' fill='#333'>
88
+ {formatNumber(minValue, 'left')}
89
+ </text>
90
+
91
+ {/* Max label */}
92
+ <text x='100%' y='40' fontSize='14' textAnchor='end' fill='#333'>
93
+ {formatNumber(maxValue, 'left')}
94
+ </text>
95
+ </svg>
96
+
97
+ {/* Series name centered below gradient */}
98
+ <div className='warming-stripes-gradient-legend__series-label'>{seriesLabel}</div>
99
+ </div>
100
+ </div>
101
+ )
102
+ }
103
+
104
+ export default WarmingStripesGradientLegend
@@ -0,0 +1,3 @@
1
+ import WarmingStripes from './WarmingStripes'
2
+
3
+ export default WarmingStripes
@@ -23,6 +23,7 @@ const createInitialState = () => {
23
23
  noData: 'No Data Available'
24
24
  },
25
25
  title: '',
26
+ titleStyle: 'small',
26
27
  showTitle: true,
27
28
  showDownloadMediaButton: false,
28
29
  theme: 'theme-blue',
@@ -139,7 +140,8 @@ const createInitialState = () => {
139
140
  padding: 5,
140
141
  showYearsOnce: false,
141
142
  sortByRecentDate: false,
142
- brushActive: false
143
+ brushActive: false,
144
+ brushDefaultRecentDateCount: undefined
143
145
  },
144
146
  table: {
145
147
  label: 'Data Table',
@@ -198,7 +200,19 @@ const createInitialState = () => {
198
200
  patterns: {},
199
201
  patternField: ''
200
202
  },
201
-
203
+ smallMultiples: {
204
+ mode: '',
205
+ tileColumn: '',
206
+ tilesPerRowDesktop: 3,
207
+ tilesPerRowMobile: 1,
208
+ tileOrder: [],
209
+ tileOrderType: 'asc',
210
+ tileTitles: {},
211
+ independentYAxis: false,
212
+ colorMode: 'same',
213
+ synchronizedTooltips: true,
214
+ showAreaUnderLine: true
215
+ },
202
216
  exclusions: {
203
217
  active: false,
204
218
  keys: []
@@ -69,41 +69,3 @@ export const buildForecastPaletteOptions = (
69
69
 
70
70
  return paletteOptions
71
71
  }
72
-
73
- /**
74
- * Normalizes a palette value to match the standardized hyphenated format
75
- * and migrates v1 palette names to v2 equivalents
76
- *
77
- * @param value - The palette name to normalize
78
- * @param paletteVersion - The palette version (1 or 2) from the config
79
- * @returns The normalized palette name in lowercase with hyphens, or 'Select' if empty
80
- */
81
- export const normalizePaletteValue = (value: string | undefined, paletteVersion: number = 1): string => {
82
- if (!value) return 'Select'
83
-
84
- // Convert to lowercase with hyphens for consistent matching
85
- const normalized = value.toLowerCase().replace(/ /g, '-').replace(/_/g, '-')
86
-
87
- // If v2, migrate v1-only palette names to their v2 equivalents
88
- if (paletteVersion === 2) {
89
- const V1_TO_V2_MIGRATION: Record<string, string> = {
90
- // Sequential Blue variants → sequential-blue
91
- 'sequential-blue-two': 'sequential-blue',
92
- 'sequential-blue-three': 'sequential-blue',
93
- 'sequential-blue-2-(mpx)': 'sequential-blue',
94
- 'sequential-blue-tworeverse': 'sequential-bluereverse',
95
- 'sequential-blue-threereverse': 'sequential-bluereverse',
96
- 'sequential-blue-2-(mpx)reverse': 'sequential-bluereverse',
97
-
98
- // Sequential Orange variants → sequential-orange
99
- 'sequential-orange-two': 'sequential-orange',
100
- 'sequential-orange-(mpx)': 'sequential-orange',
101
- 'sequential-orange-tworeverse': 'sequential-orangereverse',
102
- 'sequential-orange-(mpx)reverse': 'sequential-orangereverse'
103
- }
104
-
105
- return V1_TO_V2_MIGRATION[normalized] || normalized
106
- }
107
-
108
- return normalized
109
- }
@@ -0,0 +1,57 @@
1
+ import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
2
+
3
+ interface CalculateHorizontalBarCategoryLabelWidthProps {
4
+ yScale: any
5
+ chartWidth: number
6
+ formatDate: Function
7
+ parseDate: Function
8
+ tickLabelFont: string
9
+ xAxisType?: string
10
+ labelPlacement?: string
11
+ }
12
+
13
+ /**
14
+ * Helper function to calculate category label space for horizontal bar charts
15
+ *
16
+ * @param props Configuration object with chart properties
17
+ * @returns Calculated category label space, capped at 30% of parent width
18
+ */
19
+ export const calculateHorizontalBarCategoryLabelWidth = ({
20
+ yScale,
21
+ chartWidth,
22
+ formatDate,
23
+ parseDate,
24
+ tickLabelFont,
25
+ xAxisType,
26
+ labelPlacement
27
+ }: CalculateHorizontalBarCategoryLabelWidthProps): number => {
28
+ if (labelPlacement !== 'On Date/Category Axis') return 0
29
+
30
+ const categoryValues = yScale.domain()
31
+
32
+ if (!categoryValues || categoryValues.length === 0) {
33
+ return chartWidth * 0.3
34
+ }
35
+
36
+ const formattedLabels = categoryValues.map(value => {
37
+ if (xAxisType === 'date') {
38
+ try {
39
+ return formatDate(parseDate(value))
40
+ } catch (e) {
41
+ return String(value)
42
+ }
43
+ }
44
+ return String(value)
45
+ })
46
+
47
+ const labelWidths = formattedLabels.map(label => getTextWidth(label, tickLabelFont))
48
+ const maxLabelWidth = Math.max(...labelWidths)
49
+
50
+ // We need some extra padding or visx will wrap labels too early
51
+ const paddedWidth = maxLabelWidth + Math.ceil(maxLabelWidth * 0.15)
52
+
53
+ // Allocate at most 30% of chart width to category labels
54
+ const maxAllowedWidth = chartWidth * 0.3
55
+
56
+ return Math.min(paddedWidth, maxAllowedWidth)
57
+ }
@@ -32,6 +32,16 @@ export const getColorScale = (config: ChartConfig): ((value: string) => string)
32
32
  // Migrate old palette name if needed
33
33
  const migratedPaletteName = configPalette ? configPalette : getFallbackColorPalette(config)
34
34
 
35
+ // Check for customColorsOrdered first (direct 1-to-1 mapping, no distribution needed)
36
+ if (config.general?.palette?.customColorsOrdered && Array.isArray(config.general.palette.customColorsOrdered)) {
37
+ const customColorsOrdered = config.general.palette.customColorsOrdered
38
+ return scaleOrdinal({
39
+ domain: config.runtime.seriesLabelsAll,
40
+ range: customColorsOrdered,
41
+ unknown: null
42
+ })
43
+ }
44
+
35
45
  let palette =
36
46
  config.general?.palette?.customColors ||
37
47
  palettesSource[migratePaletteWithMap(migratedPaletteName, paletteMigrationMap, false)] ||
@@ -1,9 +1,7 @@
1
1
  import { ChartConfig } from '../types/ChartConfig'
2
2
  import _ from 'lodash'
3
- import ConfigContext from '../ConfigContext'
4
- import { useContext } from 'react'
5
3
 
6
- type UseMinMaxProps = {
4
+ type GetMinMaxProps = {
7
5
  /** config - standard chart config */
8
6
  config: ChartConfig
9
7
  /** minValue - starting minimum value */
@@ -18,9 +16,20 @@ type UseMinMaxProps = {
18
16
  tableData: Object[]
19
17
  /** isAllLine: if all series are line type including dashed lines */
20
18
  isAllLine: boolean
19
+ /** convertLineToBarGraph - whether line charts should be rendered as bar graphs */
20
+ convertLineToBarGraph?: boolean
21
21
  }
22
22
 
23
- const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAllLine, tableData }: UseMinMaxProps) => {
23
+ const getMinMax = ({
24
+ config,
25
+ minValue,
26
+ maxValue,
27
+ existPositiveValue,
28
+ data,
29
+ isAllLine,
30
+ tableData,
31
+ convertLineToBarGraph
32
+ }: GetMinMaxProps) => {
24
33
  let min = 0
25
34
  let max = 0
26
35
 
@@ -28,8 +37,6 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
28
37
  let leftMax = 0
29
38
  let rightMax = 0
30
39
 
31
- const { convertLineToBarGraph } = useContext(ConfigContext)
32
-
33
40
  if (!data) {
34
41
  return { min, max }
35
42
  }
@@ -48,10 +55,15 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
48
55
  max = enteredMaxValue && isMaxValid ? Number(enteredMaxValue) : Number.MIN_VALUE
49
56
  const { lower, upper } = config?.confidenceKeys || {}
50
57
 
58
+ // When brush is active, use tableData (full dataset) for min/max calculations
59
+ // so the y-axis shows the full range, but still use filtered data for rendering
60
+ const dataForMinMax = config.xAxis.brushActive && tableData && tableData.length > 0 ? tableData : data
61
+
51
62
  if (lower && upper && config.visualizationType === 'Bar') {
52
63
  const buffer = min < 0 ? 1.1 : 0
53
- const maxValueWithCI = Math.max(...data.flatMap(d => [d[upper], d[lower]])) * paddingAddedToAxis
54
- const minValueWithCIPlusBuffer = Math.min(...data.flatMap(d => [d[upper], d[lower]])) * paddingAddedToAxis * buffer
64
+ const maxValueWithCI = Math.max(...dataForMinMax.flatMap(d => [d[upper], d[lower]])) * paddingAddedToAxis
65
+ const minValueWithCIPlusBuffer =
66
+ Math.min(...dataForMinMax.flatMap(d => [d[upper], d[lower]])) * paddingAddedToAxis * buffer
55
67
  max = max > maxValueWithCI ? max : maxValueWithCI
56
68
  min = min < minValueWithCIPlusBuffer ? min : minValueWithCIPlusBuffer
57
69
  }
@@ -72,7 +84,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
72
84
  })
73
85
 
74
86
  // Using the columnNames or "keys" get the returned result
75
- const result = data.map(obj => columnNames.map(key => obj[key]))
87
+ const result = dataForMinMax.map(obj => columnNames.map(key => obj[key]))
76
88
 
77
89
  const highCIGroup = Math.max.apply(
78
90
  null,
@@ -95,7 +107,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
95
107
 
96
108
  if (visualizationType === 'Combo') {
97
109
  try {
98
- if (!data) throw new Error('COVE: missing data while getting min/max for combo chart.')
110
+ if (!dataForMinMax) throw new Error('COVE: missing data while getting min/max for combo chart.')
99
111
  // seperate the left and right axis items & get each sides series keys
100
112
  let leftAxisSeriesItems = series.filter(s => s.axis === 'Left')
101
113
  let rightAxisSeriesItems = series.filter(s => s.axis === 'Right')
@@ -121,8 +133,8 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
121
133
  })
122
134
  return max
123
135
  }
124
- leftMax = findMaxFromSeriesKeys(data, leftAxisSeriesItems, leftMax, 'left')
125
- rightMax = findMaxFromSeriesKeys(data, rightAxisSeriesItems, rightMax, 'right')
136
+ leftMax = findMaxFromSeriesKeys(dataForMinMax, leftAxisSeriesItems, leftMax, 'left')
137
+ rightMax = findMaxFromSeriesKeys(dataForMinMax, rightAxisSeriesItems, rightMax, 'right')
126
138
 
127
139
  if (leftMax < Number(enteredMaxValue)) {
128
140
  leftMax = Number(enteredMaxValue)
@@ -202,7 +214,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
202
214
  }
203
215
 
204
216
  if (config.isLollipopChart && config.yAxis.displayNumbersOnBar) {
205
- const dataKey = data.map(item => item[config.series[0].dataKey])
217
+ const dataKey = dataForMinMax.map(item => item[config.series[0].dataKey])
206
218
  const maxDataVal = Math.max(...dataKey).toString().length
207
219
 
208
220
  switch (true) {
@@ -238,4 +250,4 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
238
250
 
239
251
  return { min, max, leftMax, rightMax }
240
252
  }
241
- export default useMinMax
253
+ export default getMinMax
@@ -0,0 +1,53 @@
1
+ import { ChartConfig } from '../types/ChartConfig'
2
+
3
+ /**
4
+ * Calculates the Y-axis auto padding to prevent data labels from overlapping with axis tick labels.
5
+ * This is used when inline labels are enabled and there's potential for overlap.
6
+ *
7
+ * @param yScale - The D3 scale object for the Y-axis (must have .ticks() method)
8
+ * @param handleNumTicks - The number of ticks to display on the axis
9
+ * @param maxValue - The maximum data value (from useReduceData)
10
+ * @param minValue - The minimum data value (from useReduceData)
11
+ * @param config - The chart configuration object
12
+ * @returns The calculated auto padding percentage (0-100+), or 0 if no padding needed
13
+ */
14
+ export const getYAxisAutoPadding = (
15
+ yScale: any,
16
+ handleNumTicks: number,
17
+ maxValue: number,
18
+ minValue: number,
19
+ config: ChartConfig
20
+ ): number => {
21
+ // Early returns for cases where auto padding is not needed
22
+ if (!yScale?.ticks || config.orientation === 'horizontal' || config.yAxis?.max) {
23
+ return 0
24
+ }
25
+
26
+ const ticks = yScale.ticks(handleNumTicks)
27
+
28
+ if (!Array.isArray(ticks) || ticks.length === 0) {
29
+ return 0
30
+ }
31
+
32
+ // minimum percentage of the max value that the distance should be from the top grid line
33
+ const MINIMUM_DISTANCE_PERCENTAGE = 0.025
34
+
35
+ const topGridLine = Math.max(...ticks)
36
+ const needsPaddingThreshold = topGridLine - maxValue * MINIMUM_DISTANCE_PERCENTAGE
37
+ const maxValueIsGreaterThanThreshold = maxValue > needsPaddingThreshold
38
+
39
+ if (!maxValueIsGreaterThanThreshold) return 0
40
+
41
+ const tickGap = ticks.length === 1 ? ticks[0] : ticks[1] - ticks[0]
42
+ const nextTick = Math.max(...ticks) + tickGap
43
+ const divideBy = minValue < 0 ? maxValue / 2 : maxValue
44
+ const calculatedPadding = (nextTick - maxValue) / divideBy
45
+
46
+ // if auto padding is too close to next tick, add one more ticks worth of padding
47
+ const newPadding =
48
+ calculatedPadding > MINIMUM_DISTANCE_PERCENTAGE ? calculatedPadding : calculatedPadding + tickGap / divideBy
49
+
50
+ /* sometimes even though the padding is getting to the next tick exactly,
51
+ d3 still doesn't show the tick. we add 0.1 to ensure to tip it over the edge */
52
+ return newPadding * 100 + 0.1
53
+ }
@@ -26,23 +26,3 @@ export function calcInitialHeight(
26
26
  const height = Number(heights?.[renderedOrientation])
27
27
  return isNaN(height) ? 0 : height
28
28
  }
29
-
30
- export function handleAutoPaddingRight(parentRef, xAxisLabelRefs, parentWidth): [boolean, number] {
31
- const parentX = parentRef.current.getBoundingClientRect().x
32
- const editorIsOpen = !!document.querySelector('.editor-panel:not(.hidden)')
33
- const lastTickRect = xAxisLabelRefs.current?.[xAxisLabelRefs.current.length - 1]?.getBoundingClientRect()
34
- const lastBottomTickEnd = lastTickRect ? lastTickRect.x + lastTickRect.width : 0
35
- const editorWidth = editorIsOpen ? EDITOR_WIDTH : 0
36
- const calculatedOverhang = lastBottomTickEnd - parentX - editorWidth - parentWidth
37
-
38
- const paddingToAdd = clamp(calculatedOverhang, 0, 20)
39
- const currentPadding = Number(parentRef.current.style.paddingRight.replace('px', ''))
40
- const paddingDiff = Math.abs(currentPadding - paddingToAdd)
41
- const DIFF_THRESHOLD = 5
42
-
43
- const noChange = currentPadding === calculatedOverhang
44
- const insufficientDiff = (paddingDiff < DIFF_THRESHOLD && calculatedOverhang > 0) || Math.abs(calculatedOverhang) < 1
45
- const updatePadding = !noChange && !insufficientDiff
46
-
47
- return [updatePadding, paddingToAdd]
48
- }