feat: Phase 3 — services, stores, navigation, design system
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
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user