@@ -3,50 +3,35 @@ import React, {useCallback, useMemo, useState} from 'react';
33import type { LayoutChangeEvent } from 'react-native' ;
44import { View } from 'react-native' ;
55import Animated , { useSharedValue } from 'react-native-reanimated' ;
6- import type { ChartBounds , PointsArray } from 'victory-native' ;
6+ import type { ChartBounds , PointsArray , Scale } from 'victory-native' ;
77import { Bar , CartesianChart } from 'victory-native' ;
88import ActivityIndicator from '@components/ActivityIndicator' ;
9- import { getChartColor } from '@components/Charts/chartColors' ;
10- import ChartHeader from '@components/Charts/ChartHeader' ;
11- import ChartTooltip from '@components/Charts/ChartTooltip' ;
12- import {
13- BAR_INNER_PADDING ,
14- BAR_ROUNDED_CORNERS ,
15- CHART_COLORS ,
16- CHART_CONTENT_MIN_HEIGHT ,
17- CHART_PADDING ,
18- DEFAULT_SINGLE_BAR_COLOR_INDEX ,
19- DOMAIN_PADDING ,
20- DOMAIN_PADDING_SAFETY_BUFFER ,
21- FRAME_LINE_WIDTH ,
22- X_AXIS_LINE_WIDTH ,
23- Y_AXIS_LABEL_OFFSET ,
24- Y_AXIS_LINE_WIDTH ,
25- Y_AXIS_TICK_COUNT ,
26- } from '@components/Charts/constants' ;
9+ import ChartHeader from '@components/Charts/components/ChartHeader' ;
10+ import ChartTooltip from '@components/Charts/components/ChartTooltip' ;
11+ import { CHART_CONTENT_MIN_HEIGHT , CHART_PADDING , X_AXIS_LINE_WIDTH , Y_AXIS_LABEL_OFFSET , Y_AXIS_LINE_WIDTH , Y_AXIS_TICK_COUNT } from '@components/Charts/constants' ;
2712import fontSource from '@components/Charts/font' ;
2813import type { HitTestArgs } from '@components/Charts/hooks' ;
29- import { useChartInteractions , useChartLabelFormats , useChartLabelLayout } from '@components/Charts/hooks' ;
30- import type { BarChartProps } from '@components/Charts/types' ;
14+ import { useChartInteractions , useChartLabelFormats , useChartLabelLayout , useDynamicYDomain , useTooltipData } from '@components/Charts/hooks' ;
15+ import type { CartesianChartProps , ChartDataPoint } from '@components/Charts/types' ;
16+ import { calculateMinDomainPadding , DEFAULT_CHART_COLOR , getChartColor } from '@components/Charts/utils' ;
3117import useResponsiveLayout from '@hooks/useResponsiveLayout' ;
3218import useTheme from '@hooks/useTheme' ;
3319import useThemeStyles from '@hooks/useThemeStyles' ;
3420import variables from '@styles/variables' ;
3521
36- /**
37- * Calculate minimum domainPadding required to prevent bars from overflowing chart edges.
38- *
39- * The issue: victory-native calculates bar width as (1 - innerPadding) * chartWidth / barCount,
40- * but positions bars at indices [0, 1, ..., n-1] scaled to the chart width with domainPadding.
41- * For small bar counts, the default padding is insufficient and bars overflow.
42- */
43- function calculateMinDomainPadding ( chartWidth : number , barCount : number , innerPadding : number ) : number {
44- if ( barCount <= 0 ) {
45- return 0 ;
46- }
47- const minPaddingRatio = ( 1 - innerPadding ) / ( 2 * ( barCount - 1 + innerPadding ) ) ;
48- return Math . ceil ( chartWidth * minPaddingRatio * DOMAIN_PADDING_SAFETY_BUFFER ) ;
49- }
22+ /** Inner padding between bars (0.3 = 30% of bar width) */
23+ const BAR_INNER_PADDING = 0.3 ;
24+
25+ /** Extra pixel spacing between the chart boundary and the data range, applied per side (Victory's `domainPadding` prop) */
26+ const BASE_DOMAIN_PADDING = { top : 32 , bottom : 0 , left : 0 , right : 0 } ;
27+
28+ type BarChartProps = CartesianChartProps & {
29+ /** Callback when a bar is pressed */
30+ onBarPress ?: ( dataPoint : ChartDataPoint , index : number ) => void ;
31+
32+ /** When true, all bars use the same color. When false (default), each bar uses a different color from the palette. */
33+ useSingleColor ?: boolean ;
34+ } ;
5035
5136function BarChartContent ( { data, title, titleIcon, isLoading, yAxisUnit, yAxisUnitPosition = 'left' , useSingleColor = false , onBarPress} : BarChartProps ) {
5237 const theme = useTheme ( ) ;
@@ -55,9 +40,7 @@ function BarChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUni
5540 const font = useFont ( fontSource , variables . iconSizeExtraSmall ) ;
5641 const [ chartWidth , setChartWidth ] = useState ( 0 ) ;
5742 const [ barAreaWidth , setBarAreaWidth ] = useState ( 0 ) ;
58- const [ containerHeight , setContainerHeight ] = useState ( 0 ) ;
59-
60- const defaultBarColor = CHART_COLORS . at ( DEFAULT_SINGLE_BAR_COLOR_INDEX ) ;
43+ const defaultBarColor = DEFAULT_CHART_COLOR ;
6144
6245 // prepare data for display
6346 const chartData = useMemo ( ( ) => {
@@ -67,9 +50,7 @@ function BarChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUni
6750 } ) ) ;
6851 } , [ data ] ) ;
6952
70- // Anchor Y-axis at zero so the baseline is always visible.
71- // When negative values are present, let victory-native auto-calculate the domain to avoid clipping.
72- const yAxisDomain = useMemo ( ( ) : [ number ] | undefined => ( data . some ( ( point ) => point . total < 0 ) ? undefined : [ 0 ] ) , [ data ] ) ;
53+ const yAxisDomain = useDynamicYDomain ( data ) ;
7354
7455 // Handle bar press callback
7556 const handleBarPress = useCallback (
@@ -86,25 +67,22 @@ function BarChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUni
8667 ) ;
8768
8869 const handleLayout = useCallback ( ( event : LayoutChangeEvent ) => {
89- const { width, height} = event . nativeEvent . layout ;
90- setChartWidth ( width ) ;
91- setContainerHeight ( height ) ;
70+ setChartWidth ( event . nativeEvent . layout . width ) ;
9271 } , [ ] ) ;
9372
94- const { labelRotation, labelSkipInterval, truncatedLabels, maxLabelLength } = useChartLabelLayout ( {
73+ const { labelRotation, labelSkipInterval, truncatedLabels, xAxisLabelHeight } = useChartLabelLayout ( {
9574 data,
9675 font,
97- chartWidth,
98- barAreaWidth,
99- containerHeight,
76+ tickSpacing : barAreaWidth > 0 ? barAreaWidth / data . length : 0 ,
77+ labelAreaWidth : barAreaWidth ,
10078 } ) ;
10179
10280 const domainPadding = useMemo ( ( ) => {
10381 if ( chartWidth === 0 ) {
104- return { left : 0 , right : 0 , top : DOMAIN_PADDING . top , bottom : DOMAIN_PADDING . bottom } ;
82+ return BASE_DOMAIN_PADDING ;
10583 }
10684 const horizontalPadding = calculateMinDomainPadding ( chartWidth , data . length , BAR_INNER_PADDING ) ;
107- return { left : horizontalPadding , right : horizontalPadding + DOMAIN_PADDING . right , top : DOMAIN_PADDING . top , bottom : DOMAIN_PADDING . bottom } ;
85+ return { ... BASE_DOMAIN_PADDING , left : horizontalPadding , right : horizontalPadding } ;
10886 } , [ chartWidth , data . length ] ) ;
10987
11088 const { formatXAxisLabel, formatYAxisLabel} = useChartLabelFormats ( {
@@ -134,7 +112,7 @@ function BarChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUni
134112 ) ;
135113
136114 const handleScaleChange = useCallback (
137- ( _xScale : unknown , yScale : ( value : number ) => number ) => {
115+ ( _xScale : Scale , yScale : Scale ) => {
138116 barGeometry . set ( {
139117 ...barGeometry . get ( ) ,
140118 yZero : yScale ( 0 ) ,
@@ -169,29 +147,7 @@ function BarChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUni
169147 barGeometry,
170148 } ) ;
171149
172- const tooltipData = useMemo ( ( ) => {
173- if ( activeDataIndex < 0 || activeDataIndex >= data . length ) {
174- return null ;
175- }
176- const dataPoint = data . at ( activeDataIndex ) ;
177- if ( ! dataPoint ) {
178- return null ;
179- }
180- const formatted = dataPoint . total . toLocaleString ( ) ;
181- let formattedAmount = formatted ;
182- if ( yAxisUnit ) {
183- // Add space for multi-character codes (e.g., "PLN 100") but not for symbols (e.g., "$100")
184- const separator = yAxisUnit . length > 1 ? ' ' : '' ;
185- formattedAmount = yAxisUnitPosition === 'left' ? `${ yAxisUnit } ${ separator } ${ formatted } ` : `${ formatted } ${ separator } ${ yAxisUnit } ` ;
186- }
187- const totalSum = data . reduce ( ( sum , point ) => sum + Math . abs ( point . total ) , 0 ) ;
188- const percent = totalSum > 0 ? Math . round ( ( Math . abs ( dataPoint . total ) / totalSum ) * 100 ) : 0 ;
189- return {
190- label : dataPoint . label ,
191- amount : formattedAmount ,
192- percentage : percent < 1 ? '<1%' : `${ percent } %` ,
193- } ;
194- } , [ activeDataIndex , data , yAxisUnit , yAxisUnitPosition ] ) ;
150+ const tooltipData = useTooltipData ( activeDataIndex , data , yAxisUnit , yAxisUnitPosition ) ;
195151
196152 const renderBar = useCallback (
197153 ( point : PointsArray [ number ] , chartBounds : ChartBounds , barCount : number ) => {
@@ -207,7 +163,7 @@ function BarChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUni
207163 color = { barColor }
208164 barCount = { barCount }
209165 innerPadding = { BAR_INNER_PADDING }
210- roundedCorners = { BAR_ROUNDED_CORNERS }
166+ roundedCorners = { { topLeft : 8 , topRight : 8 , bottomLeft : 8 , bottomRight : 8 } }
211167 />
212168 ) ;
213169 } ,
@@ -218,9 +174,9 @@ function BarChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUni
218174 // This keeps bar area at ~250px while giving labels their needed vertical space
219175 const dynamicChartStyle = useMemo (
220176 ( ) => ( {
221- height : CHART_CONTENT_MIN_HEIGHT + ( maxLabelLength ?? 0 ) ,
177+ height : CHART_CONTENT_MIN_HEIGHT + ( xAxisLabelHeight ?? 0 ) ,
222178 } ) ,
223- [ maxLabelLength ] ,
179+ [ xAxisLabelHeight ] ,
224180 ) ;
225181
226182 if ( isLoading || ! font ) {
@@ -242,7 +198,7 @@ function BarChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUni
242198 titleIcon = { titleIcon }
243199 />
244200 < View
245- style = { [ styles . barChartChartContainer , labelRotation === - 90 ? dynamicChartStyle : undefined ] }
201+ style = { [ styles . barChartChartContainer , dynamicChartStyle ] }
246202 onLayout = { handleLayout }
247203 >
248204 { chartWidth > 0 && (
@@ -276,7 +232,7 @@ function BarChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUni
276232 domain : yAxisDomain ,
277233 } ,
278234 ] }
279- frame = { { lineWidth : FRAME_LINE_WIDTH } }
235+ frame = { { lineWidth : 0 } }
280236 data = { chartData }
281237 >
282238 { ( { points, chartBounds} ) => < > { points . y . map ( ( point ) => renderBar ( point , chartBounds , points . y . length ) ) } </ > }
@@ -297,3 +253,4 @@ function BarChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUni
297253}
298254
299255export default BarChartContent ;
256+ export type { BarChartProps } ;
0 commit comments