import React from 'react'; import { ViewProps, StyleSheet, Text, TextStyle, ViewStyle } from 'react-native'; import { Line, Svg } from 'react-native-svg'; import Animated, { useAnimatedStyle } from 'react-native-reanimated'; import { LineChartDimensionsContext } from './Chart'; import { useLineChart } from './useLineChart'; export type LineChartAxisPosition = 'left' | 'right' | 'top' | 'bottom'; export type LineChartAxisOrientation = 'horizontal' | 'vertical'; export type LineChartAxisProps = ViewProps & { position: LineChartAxisPosition; orientation: LineChartAxisOrientation; color?: string; strokeWidth?: number; tickCount?: number; domain?: [number, number]; hideOnInteraction?: boolean; format?: (value: number | string) => string | number; textStyle?: TextStyle; labelPadding?: number; // Padding to prevent labels from going off-screen labelWidth?: number; // Width allocated for labels containerStyle?: ViewStyle; // Custom container style }; export const LineChartAxis = ({ position, orientation, color = '#666', strokeWidth, tickCount = 5, domain = [0, 100], hideOnInteraction = false, format = (value) => value, textStyle, labelPadding = 3, containerStyle, ...props }: LineChartAxisProps) => { const { width, height } = React.useContext(LineChartDimensionsContext); const { isActive } = useLineChart(); // Reserve space at the bottom for x-axis cursor labels const X_AXIS_LABEL_RESERVED_HEIGHT = 40; // For vertical axes, don't extend into the reserved cursor label space const effectiveHeight = orientation === 'vertical' ? height - X_AXIS_LABEL_RESERVED_HEIGHT : height; const padding = { left: 5, right: 5, top: 20, bottom: 20, }; const animatedStyle = useAnimatedStyle(() => { return { opacity: hideOnInteraction && isActive.value ? 0 : 1, }; }); const renderTicks = () => { const [min, max] = domain; const ticks = []; const labels = []; const tickLength = 5; const labelOffset = 8; // Calculate maximum label width needed dynamically let maxLabelWidth = 0; const fontSize = textStyle?.fontSize || 10; const charWidth = fontSize * 0.6; // Dynamic character width based on font size for (let i = 0; i <= tickCount; i++) { const value = min + (max - min) * (i / tickCount); const formattedValue = String(format(value)); // Dynamic width calculation based on actual font size const estimatedWidth = formattedValue.length * charWidth; maxLabelWidth = Math.max(maxLabelWidth, estimatedWidth); } // Dynamic width allocation - ensure we don't exceed available space const maxAllowableWidth = position === 'left' || position === 'right' ? Math.max(width * 0.3, 60) // Allow up to 30% of chart width for y-axis labels, minimum 60px : width * 0.9; // For horizontal labels, use most of the width const dynamicLabelWidth = Math.min( Math.max(maxLabelWidth + 16, 40), // Minimum 40px, add 16px padding maxAllowableWidth // Don't exceed the calculated max ); for (let i = 0; i <= tickCount; i++) { const value = min + (max - min) * (i / tickCount); const tickPosition = i / tickCount; if (orientation === 'vertical') { // Add padding to prevent labels from going off-screen const topPadding = labelPadding; const bottomPadding = labelPadding; // Calculate y position with padding to keep labels on screen const availableHeight = effectiveHeight - topPadding - bottomPadding; const y = topPadding + availableHeight * (1 - tickPosition); const x = position === 'left' ? padding.left : width - padding.right; if (strokeWidth) { ticks.push( ); } labels.push( {String(format(value))} ); } else { const x = width * tickPosition; const y = position === 'top' ? 15 : height - padding.bottom; if (strokeWidth) { ticks.push( ); } labels.push( {String(format(value))} ); } } return { ticks, labels }; }; const renderAxis = () => { const { ticks, labels } = renderTicks(); let axisLine = null; if (strokeWidth) { switch (position) { case 'left': axisLine = ( ); break; case 'right': axisLine = ( ); break; case 'top': axisLine = ( ); break; case 'bottom': axisLine = ( ); break; } } return { axisLine, ticks, labels }; }; const { axisLine, ticks, labels } = renderAxis(); return ( {axisLine} {ticks} {labels} ); }; const styles = StyleSheet.create({ container: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, pointerEvents: 'none', }, verticalLabel: { position: 'absolute', minHeight: 20, zIndex: 1000, overflow: 'visible', justifyContent: 'center', }, horizontalLabel: { position: 'absolute', minHeight: 20, zIndex: 1000, overflow: 'hidden', justifyContent: 'center', }, labelText: { fontSize: 10, }, labelsContainer: { zIndex: 1000, }, });