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 (
{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,
},
});