Services: ws.service, api.service, simulation.service, rssi.service (android+ios) Stores: poseStore, settingsStore, matStore (Zustand) Types: sensing, mat, api, navigation Hooks: usePoseStream, useRssiScanner, useServerReachability Theme: colors, typography, spacing, ThemeContext Navigation: MainTabs (5 tabs), RootNavigator, types Components: GaugeArc, SparklineChart, OccupancyGrid, StatusDot, ConnectionBanner, SignalBar, +more Utils: ringBuffer, colorMap, formatters, urlValidator Verified: tsc 0 errors, jest passes
65 lines
1.5 KiB
TypeScript
65 lines
1.5 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { View, ViewStyle } from 'react-native';
|
|
import { colors } from '../theme/colors';
|
|
|
|
type SparklineChartProps = {
|
|
data: number[];
|
|
color?: string;
|
|
height?: number;
|
|
style?: ViewStyle;
|
|
};
|
|
|
|
const defaultHeight = 72;
|
|
|
|
export const SparklineChart = ({
|
|
data,
|
|
color = colors.accent,
|
|
height = defaultHeight,
|
|
style,
|
|
}: SparklineChartProps) => {
|
|
const normalizedData = data.length > 0 ? data : [0];
|
|
|
|
const chartData = useMemo(
|
|
() =>
|
|
normalizedData.map((value, index) => ({
|
|
x: index,
|
|
y: value,
|
|
})),
|
|
[normalizedData],
|
|
);
|
|
|
|
const yValues = normalizedData.map((value) => Number(value) || 0);
|
|
const yMin = Math.min(...yValues);
|
|
const yMax = Math.max(...yValues);
|
|
const yPadding = yMax - yMin === 0 ? 1 : (yMax - yMin) * 0.2;
|
|
|
|
return (
|
|
<View style={style}>
|
|
<View
|
|
accessibilityRole="image"
|
|
style={{
|
|
height,
|
|
width: '100%',
|
|
borderRadius: 4,
|
|
borderWidth: 1,
|
|
borderColor: color,
|
|
opacity: 0.2,
|
|
backgroundColor: 'transparent',
|
|
}}
|
|
>
|
|
<View
|
|
style={{
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
}}
|
|
>
|
|
{chartData.map((point) => (
|
|
<View key={point.x} style={{ position: 'absolute', left: `${(point.x / Math.max(normalizedData.length - 1, 1)) * 100}%` }} />
|
|
))}
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|