swimtracker-app/SwimTracker/components/Graph.js

108 lines
3.9 KiB
JavaScript
Raw Normal View History

2019-09-17 20:24:01 +02:00
import React from 'react';
2020-06-28 22:10:54 +02:00
import { View, StyleSheet } from 'react-native';
2020-07-15 15:53:16 +02:00
import Svg, { Polyline, Text, Circle } from 'react-native-svg';
2020-06-28 22:10:54 +02:00
import { connect } from 'react-redux';
2019-09-17 20:24:01 +02:00
2020-06-30 18:06:37 +02:00
function computeTickMarks(largest, mostTicks) {
2020-06-28 22:10:54 +02:00
const minimum = largest / mostTicks
const magnitude = 10 ** Math.floor(Math.log10(minimum))
const residual = minimum / magnitude
2020-06-30 18:06:37 +02:00
let tickInterval = 0;
2020-06-28 22:10:54 +02:00
if (residual > 5)
2020-06-30 18:06:37 +02:00
tickInterval = 10 * magnitude;
2020-06-28 22:10:54 +02:00
else if (residual > 2)
2020-06-30 18:06:37 +02:00
tickInterval = 5 * magnitude;
2020-06-28 22:10:54 +02:00
else if (residual > 1)
2020-06-30 18:06:37 +02:00
tickInterval = 2 * magnitude;
2020-06-28 22:10:54 +02:00
else
2020-06-30 18:06:37 +02:00
tickInterval = magnitude;
let result = [];
let nextTick = tickInterval;
while (nextTick < largest) {
result.push(nextTick);
nextTick += tickInterval;
}
return result;
2020-06-28 22:10:54 +02:00
}
2019-09-17 20:24:01 +02:00
const Graph = props => {
const graphHeight = 100;
2020-06-30 18:06:37 +02:00
const numPoints = 300;
const yLabelSpace = 40;
const graphWidth = numPoints + yLabelSpace;
2020-07-15 15:53:16 +02:00
const minKgScale = 4; // scale such that upper graph value is n kg
2019-09-17 20:24:01 +02:00
2020-06-30 18:06:37 +02:00
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);
2020-06-28 22:10:54 +02:00
2020-07-15 15:53:16 +02:00
const devCoordToSvgX = x => yLabelSpace + x;
const devCoordToSvgY = y => graphHeight - (y * 100 / maxValueDeviceCoord);
const dataInKgToSvgCoordX = devCoordToSvgX;
const dataInKgToSvgCoordY = y => devCoordToSvgY(y / props.kgFactor);
2020-06-28 22:10:54 +02:00
2020-07-15 15:53:16 +02:00
const coordStr = data.map((element, i) => `${devCoordToSvgX(i)}, ${devCoordToSvgY(element)}`).join(" ");
2020-06-30 18:06:37 +02:00
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)
2020-07-15 15:53:16 +02:00
const peaksXCoords = peaksToDisplay.map(p => devCoordToSvgX(p - cutOffIndex));
const peaksYCoords = peaksToDisplay.map(p => devCoordToSvgY(props.data.get(p)));
2019-09-17 20:24:01 +02:00
return (
2020-07-15 15:53:16 +02:00
<View style={{ aspectRatio: graphWidth / graphHeight }}>
2020-06-30 18:06:37 +02:00
<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)}`}
2020-06-28 22:10:54 +02:00
alignmentBaseline="center"
2020-06-30 18:06:37 +02:00
key={`label${tick}`}
2020-06-28 22:10:54 +02:00
fontSize="9pt">{tick} kg </Text>
2020-06-30 18:06:37 +02:00
</React.Fragment>
)
)}
{peaksXCoords.zip(peaksYCoords).map(peak => (
<Circle
cx={peak[0]}
key={`peak${peak[0]}`}
stroke="black"
fill="black"
cy={peak[1]}
r="3"
/>
))}
2019-09-17 20:24:01 +02:00
</Svg>
</View>
);
};
2020-06-28 22:10:54 +02:00
const mapStateToProps = (state) => {
return {
data: state.deviceState.measurements,
2020-06-30 18:06:37 +02:00
kgFactor: state.settings.analysis.kgFactor,
peaks: state.deviceState.analysis.peaks,
2020-06-28 22:10:54 +02:00
};
};
export default connect(mapStateToProps)(Graph);