Touch up weight graph
This commit is contained in:
parent
9782d3642c
commit
444e20f5b1
@ -57,7 +57,7 @@ export default function AnalyticsView() {
|
|||||||
|
|
||||||
const [dateRange, setDateRange] = useState(initialDateRange);
|
const [dateRange, setDateRange] = useState(initialDateRange);
|
||||||
const [datePickerRange, setDatePickerRange] = useState(initialDateRange);
|
const [datePickerRange, setDatePickerRange] = useState(initialDateRange);
|
||||||
const [hideSleep, setHideSleep] = useState(true);
|
const [hideSleep, setHideSleep] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (datePickerRef.current?.getAttribute("aria-expanded") === "false") {
|
if (datePickerRef.current?.getAttribute("aria-expanded") === "false") {
|
||||||
@ -260,7 +260,7 @@ export default function AnalyticsView() {
|
|||||||
{
|
{
|
||||||
weightData.length === 0 ? <LoadingSpinner /> :
|
weightData.length === 0 ? <LoadingSpinner /> :
|
||||||
<div>
|
<div>
|
||||||
<TimeseriesChart data={weightData} />
|
<TimeseriesChart data={weightData} timeseriesName="Weight" />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,10 +3,17 @@ import { LineChart, Line, YAxis, XAxis, CartesianGrid, Tooltip, Area, Label } fr
|
|||||||
import spacetime from 'spacetime';
|
import spacetime from 'spacetime';
|
||||||
import regression from 'regression';
|
import regression from 'regression';
|
||||||
|
|
||||||
function polynomialTrendline(data, degree) {
|
interface DataPoint {
|
||||||
|
id: string; // measurement.id
|
||||||
|
datetime: Date;
|
||||||
|
value: number;
|
||||||
|
unit: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function polynomialTrendline(data: DataPoint[], degree: number) {
|
||||||
// Map your data to the form required by regression-js: [x, y]
|
// Map your data to the form required by regression-js: [x, y]
|
||||||
// Here we convert datetime to a numeric value (e.g., milliseconds since epoch)
|
// Here we convert datetime to a numeric value (e.g., milliseconds since epoch)
|
||||||
const regressionData = data.map((dp, i) => [i, dp.value]);
|
const regressionData = data.map((dp, i) => [i, dp.value]) as [number, number][];
|
||||||
|
|
||||||
const result = regression.polynomial(regressionData, { order: degree });
|
const result = regression.polynomial(regressionData, { order: degree });
|
||||||
// result.equation contains the polynomial coefficients,
|
// result.equation contains the polynomial coefficients,
|
||||||
@ -14,11 +21,24 @@ function polynomialTrendline(data, degree) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatXAxis(tickItem) {
|
function formatXAxis(tickItem: [number, number]) {
|
||||||
|
const month = spacetime(tickItem).format('{month-short}');
|
||||||
|
if (spacetime(tickItem).day() === 1) {
|
||||||
|
return spacetime(tickItem).format('{month-short} {date}');
|
||||||
|
} else {
|
||||||
return spacetime(tickItem).format('{date}');
|
return spacetime(tickItem).format('{date}');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function TimeseriesChart({ data }) {
|
function yAxisTicks(data: DataPoint[]) {
|
||||||
|
const bounds = data.map(dp => Math.round(dp.value)).sort().filter((_v, i, a) => i === 0 || i === a.length - 1);
|
||||||
|
const lowerBound = bounds[0] - (bounds[0] % 2);
|
||||||
|
const upperBound = bounds[1] + (2 - bounds[1] % 2);
|
||||||
|
// Create an array of ticks from lowerBound to upperBound in increments of 2
|
||||||
|
return Array.from({ length: (upperBound - lowerBound) / 2 + 1 }, (_, i) => lowerBound + 2 * i);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TimeseriesChart({ data, timeseriesName }: { data: DataPoint[], timeseriesName: string }) {
|
||||||
const unit = data[0] ? data[0].unit : '';
|
const unit = data[0] ? data[0].unit : '';
|
||||||
|
|
||||||
const polyResult = polynomialTrendline(data, 1)// 2 for quadratic
|
const polyResult = polynomialTrendline(data, 1)// 2 for quadratic
|
||||||
@ -33,17 +53,20 @@ export default function TimeseriesChart({ data }) {
|
|||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-center'>
|
<div className='flex flex-col items-center'>
|
||||||
<h2>
|
<h2>
|
||||||
Trend: {7 * polyResult.equation[0]} {unit}/week
|
Trend: {(7 * polyResult.equation[0]).toFixed(1)} {unit}/week
|
||||||
</h2>
|
</h2>
|
||||||
<LineChart width={800} height={400} data={dataWithFit}>
|
<LineChart width={800} height={400} data={dataWithFit}>
|
||||||
<CartesianGrid stroke="#999" strokeDasharray="1 5" />
|
<CartesianGrid stroke="#999" />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label="test"
|
labelFormatter={(d) => `${spacetime(d).format("{month-short} {date}, {hour}{ampm}")}`}
|
||||||
labelFormatter={(d) => `${spacetime(d).format("{month-short} {date}")}`}
|
labelStyle={{ color: 'white' }}
|
||||||
labelStyle={{ color: 'black' }}
|
formatter={(value, name, props) => [value, name === 'smoothed' ? 'Trend' : timeseriesName]}
|
||||||
|
contentStyle={{ backgroundColor: 'hsl(var(--border))', color: 'black' }}
|
||||||
/>
|
/>
|
||||||
<XAxis dataKey="datetime" tickFormatter={formatXAxis} tickMargin={10} />
|
<XAxis dataKey="datetime" tickFormatter={formatXAxis} tickMargin={10} tickSize={10} />
|
||||||
<YAxis unit={` ${unit}`} width={120} domain={['dataMin - 10', 'dataMax']} />
|
<YAxis unit={` ${unit}`} width={120} domain={yAxisTicks(data).filter((v, i) => i === 0 || i === yAxisTicks(data).length - 1)}
|
||||||
|
allowDecimals={false}
|
||||||
|
ticks={yAxisTicks(data)} />
|
||||||
<Line type="natural" dataKey="value" stroke="white" activeDot={{ stroke: 'pink', strokeWidth: 2, r: 5 }} dot={{ fill: 'red', stroke: 'red', strokeWidth: 1 }} />
|
<Line type="natural" dataKey="value" stroke="white" activeDot={{ stroke: 'pink', strokeWidth: 2, r: 5 }} dot={{ fill: 'red', stroke: 'red', strokeWidth: 1 }} />
|
||||||
|
|
||||||
<Line type="natural" dataKey="smoothed" stroke={polyResult.equation[0] > 0 ? "red" : "green"} dot={false} />
|
<Line type="natural" dataKey="smoothed" stroke={polyResult.equation[0] > 0 ? "red" : "green"} dot={false} />
|
||||||
|
|||||||
@ -116,6 +116,7 @@
|
|||||||
"@types/react": "^18.2.55",
|
"@types/react": "^18.2.55",
|
||||||
"@types/react-dom": "^18.2.19",
|
"@types/react-dom": "^18.2.19",
|
||||||
"@types/react-syntax-highlighter": "^15.5.13",
|
"@types/react-syntax-highlighter": "^15.5.13",
|
||||||
|
"@types/regression": "^2.0.6",
|
||||||
"@types/request-ip": "^0.0.41",
|
"@types/request-ip": "^0.0.41",
|
||||||
"autoprefixer": "^10.4.17",
|
"autoprefixer": "^10.4.17",
|
||||||
"postcss": "^8.4.35",
|
"postcss": "^8.4.35",
|
||||||
|
|||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@ -452,6 +452,9 @@ importers:
|
|||||||
'@types/react-syntax-highlighter':
|
'@types/react-syntax-highlighter':
|
||||||
specifier: ^15.5.13
|
specifier: ^15.5.13
|
||||||
version: 15.5.13
|
version: 15.5.13
|
||||||
|
'@types/regression':
|
||||||
|
specifier: ^2.0.6
|
||||||
|
version: 2.0.6
|
||||||
'@types/request-ip':
|
'@types/request-ip':
|
||||||
specifier: ^0.0.41
|
specifier: ^0.0.41
|
||||||
version: 0.0.41
|
version: 0.0.41
|
||||||
@ -4464,6 +4467,9 @@ packages:
|
|||||||
'@types/react@18.2.61':
|
'@types/react@18.2.61':
|
||||||
resolution: {integrity: sha512-NURTN0qNnJa7O/k4XUkEW2yfygA+NxS0V5h1+kp9jPwhzZy95q3ADoGMP0+JypMhrZBTTgjKAUlTctde1zzeQA==}
|
resolution: {integrity: sha512-NURTN0qNnJa7O/k4XUkEW2yfygA+NxS0V5h1+kp9jPwhzZy95q3ADoGMP0+JypMhrZBTTgjKAUlTctde1zzeQA==}
|
||||||
|
|
||||||
|
'@types/regression@2.0.6':
|
||||||
|
resolution: {integrity: sha512-sa+sHOUxh9fywFuAFLCcyupFN0CKX654QUZGW5fAZCmV51I4e5nQy1xL2g/JMUW/PeDoF3Yq2lDXb7MoC3KDNg==}
|
||||||
|
|
||||||
'@types/request-ip@0.0.41':
|
'@types/request-ip@0.0.41':
|
||||||
resolution: {integrity: sha512-Qzz0PM2nSZej4lsLzzNfADIORZhhxO7PED0fXpg4FjXiHuJ/lMyUg+YFF5q8x9HPZH3Gl6N+NOM8QZjItNgGKg==}
|
resolution: {integrity: sha512-Qzz0PM2nSZej4lsLzzNfADIORZhhxO7PED0fXpg4FjXiHuJ/lMyUg+YFF5q8x9HPZH3Gl6N+NOM8QZjItNgGKg==}
|
||||||
|
|
||||||
@ -18248,6 +18254,8 @@ snapshots:
|
|||||||
'@types/scheduler': 0.16.3
|
'@types/scheduler': 0.16.3
|
||||||
csstype: 3.1.2
|
csstype: 3.1.2
|
||||||
|
|
||||||
|
'@types/regression@2.0.6': {}
|
||||||
|
|
||||||
'@types/request-ip@0.0.41':
|
'@types/request-ip@0.0.41':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.11.24
|
'@types/node': 20.11.24
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user