108 lines
3.9 KiB
JavaScript
108 lines
3.9 KiB
JavaScript
import React from 'react';
|
|
import { View, StyleSheet } from 'react-native';
|
|
import Svg, { Polyline, Text, Circle } from 'react-native-svg';
|
|
import { connect } from 'react-redux';
|
|
|
|
|
|
function computeTickMarks(largest, mostTicks) {
|
|
const minimum = largest / mostTicks
|
|
const magnitude = 10 ** Math.floor(Math.log10(minimum))
|
|
const residual = minimum / magnitude
|
|
let tickInterval = 0;
|
|
if (residual > 5)
|
|
tickInterval = 10 * magnitude;
|
|
else if (residual > 2)
|
|
tickInterval = 5 * magnitude;
|
|
else if (residual > 1)
|
|
tickInterval = 2 * magnitude;
|
|
else
|
|
tickInterval = magnitude;
|
|
|
|
let result = [];
|
|
let nextTick = tickInterval;
|
|
while (nextTick < largest) {
|
|
result.push(nextTick);
|
|
nextTick += tickInterval;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const Graph = props => {
|
|
const graphHeight = 100;
|
|
const numPoints = 300;
|
|
const yLabelSpace = 40;
|
|
const graphWidth = numPoints + yLabelSpace;
|
|
const minKgScale = 4; // scale such that upper graph value is n kg
|
|
|
|
const data = props.data.slice(-numPoints);
|
|
const maxValueDeviceCoord = data.reduce((running, x) => Math.max(x, running), minKgScale / props.kgFactor);
|
|
const maxValueKg = Math.max(maxValueDeviceCoord * props.kgFactor, minKgScale);
|
|
|
|
const devCoordToSvgX = x => yLabelSpace + x;
|
|
const devCoordToSvgY = y => graphHeight - (y * 100 / maxValueDeviceCoord);
|
|
const dataInKgToSvgCoordX = devCoordToSvgX;
|
|
const dataInKgToSvgCoordY = y => devCoordToSvgY(y / props.kgFactor);
|
|
|
|
const coordStr = data.map((element, i) => `${devCoordToSvgX(i)}, ${devCoordToSvgY(element)}`).join(" ");
|
|
const ticks = computeTickMarks(maxValueKg * 0.9, 4);
|
|
let viewBox = `0 0 ${graphWidth} ${graphHeight}`;
|
|
|
|
const cutOffIndex = Math.max(0, props.data.size - numPoints);
|
|
const peaksToDisplay = props.peaks.filter(p => p > cutOffIndex)
|
|
const peaksXCoords = peaksToDisplay.map(p => devCoordToSvgX(p - cutOffIndex));
|
|
const peaksYCoords = peaksToDisplay.map(p => devCoordToSvgY(props.data.get(p)));
|
|
|
|
return (
|
|
<View style={{ aspectRatio: graphWidth / graphHeight }}>
|
|
<Svg height="100%" width="100%" viewBox={viewBox} preserveAspectRatio="none" >
|
|
<Polyline
|
|
points={coordStr}
|
|
stroke="black"
|
|
strokeWidth="2"
|
|
strokeLinejoin="round"
|
|
fill="none"
|
|
/>
|
|
{ticks.map(tick => (
|
|
<React.Fragment key={`fragment${tick}`} >
|
|
<Polyline
|
|
points={`${yLabelSpace}, ${dataInKgToSvgCoordY(tick)} ${graphWidth}, ${dataInKgToSvgCoordY(tick)}`}
|
|
stroke="black"
|
|
key={`line${tick}`}
|
|
strokeWidth="1"
|
|
strokeDasharray="5,5"
|
|
strokeLinejoin="round"
|
|
fill="none"
|
|
/>
|
|
<Text x="5" y={`${dataInKgToSvgCoordY(tick)}`}
|
|
alignmentBaseline="center"
|
|
key={`label${tick}`}
|
|
fontSize="9pt">{tick} kg </Text>
|
|
</React.Fragment>
|
|
)
|
|
)}
|
|
{peaksXCoords.zip(peaksYCoords).map(peak => (
|
|
<Circle
|
|
cx={peak[0]}
|
|
key={`peak${peak[0]}`}
|
|
stroke="black"
|
|
fill="black"
|
|
cy={peak[1]}
|
|
r="3"
|
|
/>
|
|
))}
|
|
</Svg>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
|
|
const mapStateToProps = (state) => {
|
|
return {
|
|
data: state.deviceState.measurements,
|
|
kgFactor: state.settings.analysis.kgFactor,
|
|
peaks: state.deviceState.analysis.peaks,
|
|
};
|
|
};
|
|
|
|
export default connect(mapStateToProps)(Graph);
|