@naarni/design-system
Version:
Naarni React Native Design System for EV Fleet Apps
118 lines (110 loc) • 5.55 kB
JavaScript
import React, { useMemo, useState } from 'react';
import { View, TouchableWithoutFeedback, Text } from 'react-native';
import { Canvas, Rect, Line, Group } from '@shopify/react-native-skia';
import { useDeviceTheme } from '../../theme/deviceTheme';
import { styles } from './styles';
import { Tooltip } from './Tooltip';
import { getChartDimensions, generateGridLines, generateYAxisLabels, generateXAxisLabels, formatAxisLabel } from './utils';
export const BarChart = ({ data, width, height, showXAxis = true, showYAxis = true, isLabelOnXaxis = true, isLabelOnYaxis = true, color, showGrid = true, showTooltip = true, tooltipFormatter, }) => {
const { colors } = useDeviceTheme();
const [tooltipData, setTooltipData] = useState(null);
const [tooltipVisible, setTooltipVisible] = useState(false);
const [selectedBar, setSelectedBar] = useState(null);
const dimensions = getChartDimensions(width, height);
const barColor = color || colors.primary.main;
const barWidth = dimensions.chartWidth / data.length - 8;
const maxValue = Math.max(...data, 1);
const gridLines = useMemo(() => showGrid ? generateGridLines(data, dimensions) : [], [data, dimensions, showGrid]);
const yAxisLabels = useMemo(() => isLabelOnYaxis ? generateYAxisLabels(data, dimensions) : [], [data, dimensions, isLabelOnYaxis]);
const xAxisLabels = useMemo(() => isLabelOnXaxis ? generateXAxisLabels(data, dimensions) : [], [data, dimensions, isLabelOnXaxis]);
const handleBarPress = (event, index) => {
if (!showTooltip)
return;
const touchX = event.nativeEvent.locationX;
const x = dimensions.padding + index * (barWidth + 8);
const barHeight = (data[index] / maxValue) * dimensions.chartHeight;
const y = height - dimensions.padding - barHeight;
if (selectedBar === index) {
// Toggle tooltip off if same bar is tapped
setTooltipVisible(false);
setSelectedBar(null);
setTooltipData(null);
}
else {
// Show tooltip for new bar
setSelectedBar(index);
setTooltipData({
x: x + barWidth / 2,
y: y - 10,
value: data[index],
index,
});
setTooltipVisible(true);
}
};
return (<View style={[styles.container, { width, height }]}>
<View style={{ flex: 1 }}>
{/* Y-Axis Labels */}
{isLabelOnYaxis && yAxisLabels.map((label, index) => (<Text key={`y-label-${index}`} style={[
styles.yAxisLabel,
{
position: 'absolute',
left: 5,
top: label.y - 8,
color: colors.text.secondary,
fontSize: 10,
},
]}>
{formatAxisLabel(label.value)}
</Text>))}
{/* X-Axis Labels */}
{isLabelOnXaxis && xAxisLabels.map((label, index) => (<Text key={`x-label-${index}`} style={[
styles.xAxisLabel,
{
position: 'absolute',
left: label.x - 20,
top: height - dimensions.padding + 5,
color: colors.text.secondary,
fontSize: 10,
},
]}>
{label.label}
</Text>))}
<Canvas style={styles.canvas}>
{/* Grid Lines */}
{showGrid && gridLines.map((line, index) => (<Line key={`grid-${index}`} p1={{ x: dimensions.padding, y: line.y }} p2={{ x: width - dimensions.padding, y: line.y }} color={colors.surface.border} strokeWidth={0.5} opacity={0.3}/>))}
{/* X-Axis */}
{showXAxis && (<Line p1={{ x: dimensions.padding, y: height - dimensions.padding }} p2={{ x: width - dimensions.padding, y: height - dimensions.padding }} color={colors.surface.border} strokeWidth={1}/>)}
{/* Y-Axis */}
{showYAxis && (<Line p1={{ x: dimensions.padding, y: dimensions.padding }} p2={{ x: dimensions.padding, y: height - dimensions.padding }} color={colors.surface.border} strokeWidth={1}/>)}
{/* Bars */}
<Group>
{data.map((value, i) => {
const x = dimensions.padding + i * (barWidth + 8);
const barHeight = (value / maxValue) * dimensions.chartHeight;
const y = height - dimensions.padding - barHeight;
return (<Rect key={i} x={x} y={y} width={barWidth} height={barHeight} color={selectedBar === i ? colors.primary.light : barColor}/>);
})}
</Group>
</Canvas>
{/* Touchable overlay for bar interaction */}
{data.map((value, i) => {
const x = dimensions.padding + i * (barWidth + 8);
const barHeight = (value / maxValue) * dimensions.chartHeight;
const y = height - dimensions.padding - barHeight;
return (<TouchableWithoutFeedback key={`touch-${i}`} onPress={(event) => handleBarPress(event, i)}>
<View style={{
position: 'absolute',
left: x,
top: y,
width: barWidth,
height: barHeight,
backgroundColor: 'transparent',
}}/>
</TouchableWithoutFeedback>);
})}
</View>
{/* Tooltip */}
{tooltipData && (<Tooltip data={tooltipData} visible={tooltipVisible} width={width} height={height}/>)}
</View>);
};