Add picker for best charts
This commit is contained in:
parent
6c728b7bfd
commit
da936c7799
58
Chart.tsx
Normal file
58
Chart.tsx
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import * as shape from 'd3-shape';
|
||||||
|
import React from 'react';
|
||||||
|
import {useColorScheme, View} from 'react-native';
|
||||||
|
import {Grid, LineChart, XAxis, YAxis} from 'react-native-svg-charts';
|
||||||
|
import {CombinedDarkTheme, CombinedDefaultTheme} from './App';
|
||||||
|
import Set from './set';
|
||||||
|
|
||||||
|
export default function Chart({
|
||||||
|
yData,
|
||||||
|
xFormat,
|
||||||
|
xData,
|
||||||
|
yFormat,
|
||||||
|
}: {
|
||||||
|
yData: number[];
|
||||||
|
xData: Set[];
|
||||||
|
xFormat: (value: any, index: number) => string;
|
||||||
|
yFormat: (value: any) => string;
|
||||||
|
}) {
|
||||||
|
const dark = useColorScheme() === 'dark';
|
||||||
|
const axesSvg = {fontSize: 10, fill: 'grey'};
|
||||||
|
const verticalContentInset = {top: 10, bottom: 10};
|
||||||
|
const xAxisHeight = 30;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<View style={{height: 300, padding: 20, flexDirection: 'row'}}>
|
||||||
|
<YAxis
|
||||||
|
data={yData}
|
||||||
|
style={{marginBottom: xAxisHeight}}
|
||||||
|
contentInset={verticalContentInset}
|
||||||
|
svg={axesSvg}
|
||||||
|
formatLabel={yFormat}
|
||||||
|
/>
|
||||||
|
<View style={{flex: 1, marginLeft: 10}}>
|
||||||
|
<LineChart
|
||||||
|
style={{flex: 1}}
|
||||||
|
data={yData}
|
||||||
|
contentInset={verticalContentInset}
|
||||||
|
curve={shape.curveBasis}
|
||||||
|
svg={{
|
||||||
|
stroke: dark
|
||||||
|
? CombinedDarkTheme.colors.primary
|
||||||
|
: CombinedDefaultTheme.colors.primary,
|
||||||
|
}}>
|
||||||
|
<Grid />
|
||||||
|
</LineChart>
|
||||||
|
<XAxis
|
||||||
|
style={{marginHorizontal: -10, height: xAxisHeight}}
|
||||||
|
data={xData}
|
||||||
|
formatLabel={xFormat}
|
||||||
|
contentInset={{left: 10, right: 10}}
|
||||||
|
svg={axesSvg}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
163
ViewBest.tsx
163
ViewBest.tsx
|
@ -4,7 +4,6 @@ import {
|
||||||
useNavigation,
|
useNavigation,
|
||||||
useRoute,
|
useRoute,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import * as shape from 'd3-shape';
|
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
|
@ -12,14 +11,13 @@ import React, {
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import {useColorScheme, View} from 'react-native';
|
|
||||||
import {FileSystem} from 'react-native-file-access';
|
import {FileSystem} from 'react-native-file-access';
|
||||||
import {Text, IconButton} from 'react-native-paper';
|
import {IconButton} from 'react-native-paper';
|
||||||
|
import RNPickerSelect from 'react-native-picker-select';
|
||||||
import Share from 'react-native-share';
|
import Share from 'react-native-share';
|
||||||
import {Grid, LineChart, XAxis, YAxis} from 'react-native-svg-charts';
|
|
||||||
import ViewShot from 'react-native-view-shot';
|
import ViewShot from 'react-native-view-shot';
|
||||||
import {CombinedDarkTheme, CombinedDefaultTheme} from './App';
|
|
||||||
import {BestPageParams} from './BestPage';
|
import {BestPageParams} from './BestPage';
|
||||||
|
import Chart from './Chart';
|
||||||
import {DatabaseContext} from './Routes';
|
import {DatabaseContext} from './Routes';
|
||||||
import Set from './set';
|
import Set from './set';
|
||||||
import {formatMonth} from './time';
|
import {formatMonth} from './time';
|
||||||
|
@ -31,13 +29,25 @@ interface Volume {
|
||||||
unit: string;
|
unit: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Metrics {
|
||||||
|
Weight = 'Best weight per day',
|
||||||
|
Volume = 'Best volume per day',
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Periods {
|
||||||
|
Weekly = 'This week',
|
||||||
|
Monthly = 'This month',
|
||||||
|
Yearly = 'This year',
|
||||||
|
}
|
||||||
|
|
||||||
export default function ViewBest() {
|
export default function ViewBest() {
|
||||||
const {params} = useRoute<RouteProp<BestPageParams, 'ViewBest'>>();
|
const {params} = useRoute<RouteProp<BestPageParams, 'ViewBest'>>();
|
||||||
const [weights, setWeights] = useState<Set[]>([]);
|
const [weights, setWeights] = useState<Set[]>([]);
|
||||||
const [volumes, setVolumes] = useState<Volume[]>([]);
|
const [volumes, setVolumes] = useState<Volume[]>([]);
|
||||||
|
const [metric, setMetric] = useState(Metrics.Weight);
|
||||||
|
const [period, setPeriod] = useState(Periods.Monthly);
|
||||||
const db = useContext(DatabaseContext);
|
const db = useContext(DatabaseContext);
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const dark = useColorScheme() === 'dark';
|
|
||||||
const viewShot = useRef<ViewShot>(null);
|
const viewShot = useRef<ViewShot>(null);
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
|
@ -69,114 +79,91 @@ export default function ViewBest() {
|
||||||
}, [navigation, params.best]),
|
}, [navigation, params.best]),
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const refreshWeight = useCallback(async () => {
|
||||||
console.log(`${ViewBest.name}.useEffect`);
|
const select = `
|
||||||
const selectWeights = `
|
|
||||||
SELECT max(weight) AS weight,
|
SELECT max(weight) AS weight,
|
||||||
STRFTIME('%Y-%m-%d', created) as created, unit
|
STRFTIME('%Y-%m-%d', created) as created, unit
|
||||||
FROM sets
|
FROM sets
|
||||||
WHERE name = ? AND NOT hidden
|
WHERE name = ? AND NOT hidden
|
||||||
|
AND DATE(created) >= DATE('now', 'weekday 0', ?)
|
||||||
GROUP BY name, STRFTIME('%Y-%m-%d', created)
|
GROUP BY name, STRFTIME('%Y-%m-%d', created)
|
||||||
`;
|
`;
|
||||||
const selectVolumes = `
|
let difference = '-7 days';
|
||||||
|
if (period === Periods.Monthly) difference = '-1 months';
|
||||||
|
else if (period === Periods.Yearly) difference = '-1 years';
|
||||||
|
const [result] = await db.executeSql(select, [
|
||||||
|
params.best.name,
|
||||||
|
difference,
|
||||||
|
]);
|
||||||
|
if (result.rows.length === 0) return;
|
||||||
|
setWeights(result.rows.raw());
|
||||||
|
}, [params.best.name, db, period]);
|
||||||
|
|
||||||
|
const refreshVolume = useCallback(async () => {
|
||||||
|
const select = `
|
||||||
SELECT sum(weight * reps) AS value,
|
SELECT sum(weight * reps) AS value,
|
||||||
STRFTIME('%Y-%m-%d', created) as created, unit
|
STRFTIME('%Y-%m-%d', created) as created, unit
|
||||||
FROM sets
|
FROM sets
|
||||||
WHERE name = ? AND NOT hidden
|
WHERE name = ? AND NOT hidden
|
||||||
|
AND DATE(created) >= DATE('now', 'weekday 0', ?)
|
||||||
GROUP BY name, STRFTIME('%Y-%m-%d', created)
|
GROUP BY name, STRFTIME('%Y-%m-%d', created)
|
||||||
`;
|
`;
|
||||||
const refresh = async () => {
|
let difference = '-7 days';
|
||||||
const [weightsResult] = await db.executeSql(selectWeights, [
|
if (period === Periods.Monthly) difference = '-1 months';
|
||||||
|
else if (period === Periods.Yearly) difference = '-1 years';
|
||||||
|
const [result] = await db.executeSql(select, [
|
||||||
params.best.name,
|
params.best.name,
|
||||||
|
difference,
|
||||||
]);
|
]);
|
||||||
if (weightsResult.rows.length === 0) return;
|
if (result.rows.length === 0) return;
|
||||||
setWeights(weightsResult.rows.raw());
|
setVolumes(result.rows.raw());
|
||||||
const [volumesResult] = await db.executeSql(selectVolumes, [
|
}, [db, params.best.name, period]);
|
||||||
params.best.name,
|
|
||||||
]);
|
|
||||||
console.log(volumesResult.rows.raw());
|
|
||||||
if (volumesResult.rows.length === 0) return;
|
|
||||||
setVolumes(volumesResult.rows.raw());
|
|
||||||
};
|
|
||||||
refresh();
|
|
||||||
}, [params.best.name, db]);
|
|
||||||
|
|
||||||
const axesSvg = {fontSize: 10, fill: 'grey'};
|
useEffect(() => {
|
||||||
const verticalContentInset = {top: 10, bottom: 10};
|
if (metric === Metrics.Weight) refreshWeight();
|
||||||
const xAxisHeight = 30;
|
else if (metric === Metrics.Volume) refreshVolume();
|
||||||
|
console.log(`${ViewBest.name}.useEffect`, {metric, period});
|
||||||
|
}, [params.best.name, db, metric, period, refreshVolume, refreshWeight]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ViewShot style={{padding: 10}} ref={viewShot}>
|
<ViewShot style={{padding: 10}} ref={viewShot}>
|
||||||
<Text>Best weight per day</Text>
|
<RNPickerSelect
|
||||||
<View style={{height: 300, padding: 20, flexDirection: 'row'}}>
|
onValueChange={setMetric}
|
||||||
<YAxis
|
items={[
|
||||||
data={weights.map(set => set.weight)}
|
{label: Metrics.Weight, value: Metrics.Weight},
|
||||||
style={{marginBottom: xAxisHeight}}
|
{label: Metrics.Volume, value: Metrics.Volume},
|
||||||
contentInset={verticalContentInset}
|
]}
|
||||||
svg={axesSvg}
|
value={metric}
|
||||||
formatLabel={value => `${value}${weights[0].unit}`}
|
|
||||||
/>
|
/>
|
||||||
<View style={{flex: 1, marginLeft: 10}}>
|
<RNPickerSelect
|
||||||
<LineChart
|
onValueChange={setPeriod}
|
||||||
style={{flex: 1}}
|
items={[
|
||||||
data={weights.map(set => set.weight)}
|
{label: Periods.Weekly, value: Periods.Weekly},
|
||||||
contentInset={verticalContentInset}
|
{label: Periods.Monthly, value: Periods.Monthly},
|
||||||
curve={shape.curveBasis}
|
{label: Periods.Yearly, value: Periods.Yearly},
|
||||||
svg={{
|
]}
|
||||||
stroke: dark
|
value={period}
|
||||||
? CombinedDarkTheme.colors.primary
|
|
||||||
: CombinedDefaultTheme.colors.primary,
|
|
||||||
}}>
|
|
||||||
<Grid />
|
|
||||||
</LineChart>
|
|
||||||
<XAxis
|
|
||||||
style={{marginHorizontal: -10, height: xAxisHeight}}
|
|
||||||
data={weights}
|
|
||||||
formatLabel={(_value, index) =>
|
|
||||||
formatMonth(weights[index].created!)
|
|
||||||
}
|
|
||||||
contentInset={{left: 10, right: 10}}
|
|
||||||
svg={axesSvg}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
{metric === Metrics.Volume && (
|
||||||
</View>
|
<Chart
|
||||||
<Text>Volume per day</Text>
|
yData={volumes.map(v => v.value)}
|
||||||
<View style={{height: 300, padding: 20, flexDirection: 'row'}}>
|
yFormat={(value: number) =>
|
||||||
<YAxis
|
|
||||||
data={volumes.map(volume => volume.value)}
|
|
||||||
style={{marginBottom: xAxisHeight}}
|
|
||||||
contentInset={verticalContentInset}
|
|
||||||
svg={axesSvg}
|
|
||||||
formatLabel={(value: number) =>
|
|
||||||
`${value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')}${
|
`${value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')}${
|
||||||
volumes[0].unit
|
volumes[0].unit
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
|
xData={weights}
|
||||||
|
xFormat={(_value, index) => formatMonth(weights[index].created!)}
|
||||||
/>
|
/>
|
||||||
<View style={{flex: 1, marginLeft: 10}}>
|
)}
|
||||||
<LineChart
|
{metric === Metrics.Weight && (
|
||||||
style={{flex: 1}}
|
<Chart
|
||||||
data={volumes.map(volume => volume.value)}
|
yData={weights.map(set => set.weight)}
|
||||||
contentInset={verticalContentInset}
|
yFormat={value => `${value}${weights[0].unit}`}
|
||||||
curve={shape.curveBasis}
|
xData={weights}
|
||||||
svg={{
|
xFormat={(_value, index) => formatMonth(weights[index].created!)}
|
||||||
stroke: dark
|
|
||||||
? CombinedDarkTheme.colors.primary
|
|
||||||
: CombinedDefaultTheme.colors.primary,
|
|
||||||
}}>
|
|
||||||
<Grid />
|
|
||||||
</LineChart>
|
|
||||||
<XAxis
|
|
||||||
style={{marginHorizontal: -10, height: xAxisHeight}}
|
|
||||||
data={weights}
|
|
||||||
formatLabel={(_value, index) =>
|
|
||||||
formatMonth(volumes[index]?.created)
|
|
||||||
}
|
|
||||||
contentInset={{left: 10, right: 10}}
|
|
||||||
svg={axesSvg}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
)}
|
||||||
</View>
|
|
||||||
</ViewShot>
|
</ViewShot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/preset-env": "^7.1.6",
|
"@babel/preset-env": "^7.1.6",
|
||||||
"@react-native-masked-view/masked-view": "^0.2.6",
|
"@react-native-masked-view/masked-view": "^0.2.6",
|
||||||
|
"@react-native-picker/picker": "^2.4.4",
|
||||||
"@react-navigation/drawer": "^6.4.3",
|
"@react-navigation/drawer": "^6.4.3",
|
||||||
"@react-navigation/native": "^6.0.10",
|
"@react-navigation/native": "^6.0.10",
|
||||||
"@react-navigation/stack": "^6.2.2",
|
"@react-navigation/stack": "^6.2.2",
|
||||||
|
@ -29,6 +30,7 @@
|
||||||
"react-native-linear-gradient": "^2.6.2",
|
"react-native-linear-gradient": "^2.6.2",
|
||||||
"react-native-pager-view": "^5.4.24",
|
"react-native-pager-view": "^5.4.24",
|
||||||
"react-native-paper": "^4.12.2",
|
"react-native-paper": "^4.12.2",
|
||||||
|
"react-native-picker-select": "^8.0.4",
|
||||||
"react-native-reanimated": "^2.9.0",
|
"react-native-reanimated": "^2.9.0",
|
||||||
"react-native-safe-area-context": "^4.3.1",
|
"react-native-safe-area-context": "^4.3.1",
|
||||||
"react-native-screens": "^3.14.0",
|
"react-native-screens": "^3.14.0",
|
||||||
|
|
32
yarn.lock
32
yarn.lock
|
@ -1913,6 +1913,26 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@react-native-picker/picker@npm:^1.8.3":
|
||||||
|
version: 1.16.8
|
||||||
|
resolution: "@react-native-picker/picker@npm:1.16.8"
|
||||||
|
peerDependencies:
|
||||||
|
react: 16 || 17
|
||||||
|
react-native: ">=0.57"
|
||||||
|
checksum: c0176e41ba7486bf0a27ab5471315848cb166330313bd12e29ee65b7d2e1dfde22383de6a89be15770d132b33bca1764e64feee3acf22c67c3733fd877d3e91f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@react-native-picker/picker@npm:^2.4.4":
|
||||||
|
version: 2.4.4
|
||||||
|
resolution: "@react-native-picker/picker@npm:2.4.4"
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=16"
|
||||||
|
react-native: ">=0.57"
|
||||||
|
checksum: 8a122707e39f3f6a97536bd7800bdb2a326bdcff85d7e03472250454974a14789c8963c0d99397410955a9bbe31a67cef38bccfd0834b161e8a169e25ecc2304
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@react-native/assets@npm:1.0.0":
|
"@react-native/assets@npm:1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "@react-native/assets@npm:1.0.0"
|
resolution: "@react-native/assets@npm:1.0.0"
|
||||||
|
@ -6001,6 +6021,7 @@ __metadata:
|
||||||
"@babel/runtime": ^7.12.5
|
"@babel/runtime": ^7.12.5
|
||||||
"@react-native-community/eslint-config": ^2.0.0
|
"@react-native-community/eslint-config": ^2.0.0
|
||||||
"@react-native-masked-view/masked-view": ^0.2.6
|
"@react-native-masked-view/masked-view": ^0.2.6
|
||||||
|
"@react-native-picker/picker": ^2.4.4
|
||||||
"@react-navigation/drawer": ^6.4.3
|
"@react-navigation/drawer": ^6.4.3
|
||||||
"@react-navigation/native": ^6.0.10
|
"@react-navigation/native": ^6.0.10
|
||||||
"@react-navigation/stack": ^6.2.2
|
"@react-navigation/stack": ^6.2.2
|
||||||
|
@ -6023,6 +6044,7 @@ __metadata:
|
||||||
react-native-linear-gradient: ^2.6.2
|
react-native-linear-gradient: ^2.6.2
|
||||||
react-native-pager-view: ^5.4.24
|
react-native-pager-view: ^5.4.24
|
||||||
react-native-paper: ^4.12.2
|
react-native-paper: ^4.12.2
|
||||||
|
react-native-picker-select: ^8.0.4
|
||||||
react-native-reanimated: ^2.9.0
|
react-native-reanimated: ^2.9.0
|
||||||
react-native-safe-area-context: ^4.3.1
|
react-native-safe-area-context: ^4.3.1
|
||||||
react-native-screens: ^3.14.0
|
react-native-screens: ^3.14.0
|
||||||
|
@ -7434,6 +7456,16 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"react-native-picker-select@npm:^8.0.4":
|
||||||
|
version: 8.0.4
|
||||||
|
resolution: "react-native-picker-select@npm:8.0.4"
|
||||||
|
dependencies:
|
||||||
|
"@react-native-picker/picker": ^1.8.3
|
||||||
|
lodash.isequal: ^4.5.0
|
||||||
|
checksum: 9656a2f7b330a9e5a8b6b90393140fc5370762d2a14de4c5d31ce43de8abc677870c7f2ee8a493f48535157ddcbc3311cf3f41dac262bd84e50e913f0d4a24ff
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"react-native-reanimated@npm:^2.9.0":
|
"react-native-reanimated@npm:^2.9.0":
|
||||||
version: 2.9.0
|
version: 2.9.0
|
||||||
resolution: "react-native-reanimated@npm:2.9.0"
|
resolution: "react-native-reanimated@npm:2.9.0"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user