Commit a9860724 authored by Administrator's avatar Administrator

(stryd) cadence on lap and activity level

parent f15ff15c
......@@ -94,6 +94,8 @@ const tableActivity = SqfEntityTable(
SqfEntityField('sdevLegSpringStiffness', DbType.real),
SqfEntityField('avgFormPower', DbType.real),
SqfEntityField('sdevFormPower', DbType.real),
SqfEntityField('avgStrydCadence', DbType.real),
SqfEntityField('sdevStrydCadence', DbType.real),
SqfEntityFieldRelationship(
parentTable: tableAthlete,
......@@ -102,7 +104,6 @@ const tableActivity = SqfEntityTable(
],
);
const tableEvent = SqfEntityTable(
tableName: 'events',
primaryKeyName: 'id',
......@@ -189,6 +190,8 @@ const tableLap = SqfEntityTable(
SqfEntityField('sdevLegSpringStiffness', DbType.real),
SqfEntityField('avgFormPower', DbType.real),
SqfEntityField('sdevFormPower', DbType.real),
SqfEntityField('avgStrydCadence', DbType.real),
SqfEntityField('sdevStrydCadence', DbType.real),
SqfEntityFieldRelationship(
parentTable: tableEvent,
......@@ -198,8 +201,7 @@ const tableLap = SqfEntityTable(
parentTable: tableActivity,
deleteRule: DeleteRule.CASCADE,
defaultValue: 0),
]
);
]);
@SqfEntityBuilder(encrateia)
const encrateia = SqfEntityModel(
......
This diff is collapsed.
......@@ -160,6 +160,27 @@ class Activity extends ChangeNotifier {
return db.sdevGroundTime;
}
Future<double> get avgStrydCadence async {
if (db.avgStrydCadence == null) {
List<Event> records = await this.records;
db.avgStrydCadence = Lap.calculateAverageStrydCadence(records: records);
await db.save();
notifyListeners();
}
return db.avgStrydCadence;
}
Future<double> get sdevStrydCadence async {
if (db.sdevStrydCadence == null) {
List<Event> records = await this.records;
db.sdevStrydCadence = Lap.calculateSdevStrydCadence(records: records);
await db.save();
notifyListeners();
}
return db.sdevStrydCadence;
}
Future<double> get avgLegSpringStiffness async {
if (db.avgLegSpringStiffness == null) {
List<Event> records = await this.records;
......
......@@ -169,7 +169,7 @@ class Event {
sum = sum + record.db.groundTime;
break;
case "strydCadence":
sum = sum + record.db.strydCadence;
sum = sum + 2 * record.db.strydCadence;
break;
case "verticalOscillation":
sum = sum + record.db.verticalOscillation;
......
......@@ -115,6 +115,24 @@ class Lap {
return db.sdevGroundTime;
}
Future<double> get avgStrydCadence async {
if (db.avgStrydCadence == null) {
List<Event> records = await this.records;
db.avgStrydCadence = calculateAverageStrydCadence(records: records);
await db.save();
}
return db.avgStrydCadence;
}
Future<double> get sdevStrydCadence async {
if (db.sdevStrydCadence == null) {
List<Event> records = await this.records;
db.sdevStrydCadence = calculateSdevStrydCadence(records: records);
await db.save();
}
return db.sdevStrydCadence;
}
Future<double> get avgLegSpringStiffness async {
if (db.avgLegSpringStiffness == null) {
List<Event> records = await this.records;
......@@ -228,6 +246,22 @@ class Lap {
return groundTimes.sdev();
}
static double calculateAverageStrydCadence({List<Event> records}) {
var strydCadences =
records.map((record) => 2 * record.db.strydCadence).nonZeroDoubles();
if (strydCadences.length > 0) {
return strydCadences.mean();
} else
return -1;
}
static double calculateSdevStrydCadence({List<Event> records}) {
var strydCadences =
records.map((record) => 2 * record.db.strydCadence).nonZeroDoubles();
return strydCadences.sdev();
}
static double calculateAverageLegSpringStiffness({List<Event> records}) {
var legSpringStiffnesses =
records.map((record) => record.db.legSpringStiffness).nonZeroDoubles();
......
......@@ -7,6 +7,7 @@ import 'package:encrateia/widgets/activity_power_duration_widget.dart';
import 'package:encrateia/widgets/activity_ground_time_widget.dart';
import 'package:encrateia/widgets/activity_leg_spring_stiffness_widget.dart';
import 'package:encrateia/widgets/activity_form_power_widget.dart';
import 'package:encrateia/widgets/activity_stryd_cadence_widget.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/activity.dart';
......@@ -21,7 +22,7 @@ class ShowActivityScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 9,
length: 10,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
......@@ -59,6 +60,10 @@ class ShowActivityScreen extends StatelessWidget {
icon: Icon(Icons.accessibility_new),
text: "Form Power",
),
Tab(
icon: Icon(Icons.pets),
text: "Cadence",
),
Tab(
icon: Icon(Icons.storage),
text: "Metadata",
......@@ -79,6 +84,7 @@ class ShowActivityScreen extends StatelessWidget {
ActivityGroundTimeWidget(activity: activity),
ActivityLegSpringStiffnessWidget(activity: activity),
ActivityFormPowerWidget(activity: activity),
ActivityStrydCadenceWidget(activity: activity),
ActivityMetadataWidget(activity: activity),
]),
),
......
......@@ -6,6 +6,7 @@ import 'package:encrateia/widgets/lap_power_duration_widget.dart';
import 'package:encrateia/widgets/lap_ground_time_widget.dart';
import 'package:encrateia/widgets/lap_leg_spring_stiffness_widget.dart';
import 'package:encrateia/widgets/lap_form_power_widget.dart';
import 'package:encrateia/widgets/lap_stryd_cadence_widget.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/lap.dart';
......@@ -20,7 +21,7 @@ class ShowLapScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 8,
length: 9,
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.green,
......@@ -55,6 +56,10 @@ class ShowLapScreen extends StatelessWidget {
icon: Icon(Icons.accessibility_new),
text: "Form Power",
),
Tab(
icon: Icon(Icons.pets),
text: "Cadence",
),
Tab(
icon: Icon(Icons.storage),
text: "Metadata",
......@@ -74,6 +79,7 @@ class ShowLapScreen extends StatelessWidget {
LapGroundTimeWidget(lap: lap),
LapLegSpringStiffnessWidget(lap: lap),
LapFormPowerWidget(lap: lap),
LapStrydCadenceWidget(lap: lap),
LapMetadataWidget(lap: lap),
]),
),
......
import 'package:charts_flutter/flutter.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/models/event.dart';
import 'package:encrateia/models/lap.dart';
import 'package:encrateia/models/plot_point.dart';
class ActivityStrydCadenceChart extends StatelessWidget {
final List<Event> records;
final Activity activity;
final colorArray = [
MaterialPalette.white,
MaterialPalette.gray.shade200,
];
ActivityStrydCadenceChart({this.records, @required this.activity});
@override
Widget build(BuildContext context) {
var nonZero = records.where((value) => value.db.strydCadence > 0);
var smoothedRecords = Event.toDoubleDataPoints(
attribute: "strydCadence",
records: nonZero,
amount: 30,
);
List<Series<dynamic, num>> data = [
new Series<DoublePlotPoint, int>(
id: 'Cadence',
colorFn: (_, __) => MaterialPalette.green.shadeDefault,
domainFn: (DoublePlotPoint record, _) => record.domain,
measureFn: (DoublePlotPoint record, _) => record.measure,
data: smoothedRecords,
)
];
return FutureBuilder<List<Lap>>(
future: activity.laps,
builder: (BuildContext context, AsyncSnapshot<List<Lap>> snapshot) {
if (snapshot.hasData) {
var laps = snapshot.data;
return Container(
height: 300,
child: LineChart(
data,
domainAxis: NumericAxisSpec(
viewport: NumericExtents(0, nonZero.last.db.distance + 500),
tickProviderSpec: BasicNumericTickProviderSpec(
desiredTickCount: 6,
),
),
primaryMeasureAxis: NumericAxisSpec(
tickProviderSpec: BasicNumericTickProviderSpec(
zeroBound: false,
dataIsInWholeNumbers: false,
desiredTickCount: 5,
),
),
animate: false,
layoutConfig: LayoutConfig(
leftMarginSpec: MarginSpec.fixedPixel(60),
topMarginSpec: MarginSpec.fixedPixel(20),
rightMarginSpec: MarginSpec.fixedPixel(20),
bottomMarginSpec: MarginSpec.fixedPixel(40),
),
behaviors: [
RangeAnnotation(rangeAnnotations(laps: laps)),
ChartTitle(
'Cadence (s/min)',
titleStyleSpec: TextStyleSpec(fontSize: 13),
behaviorPosition: BehaviorPosition.start,
titleOutsideJustification: OutsideJustification.end,
),
ChartTitle(
'Distance (m)',
titleStyleSpec: TextStyleSpec(fontSize: 13),
behaviorPosition: BehaviorPosition.bottom,
titleOutsideJustification: OutsideJustification.end,
),
],
),
);
} else {
return Container(
height: 100,
child: Center(child: Text("Loading")),
);
}
},
);
}
rangeAnnotations({List<Lap> laps}) {
return [
for (int index = 0; index < laps.length; index++)
RangeAnnotationSegment(
laps
.sublist(0, index + 1)
.map((lap) => lap.db.totalDistance)
.reduce((a, b) => a + b) -
laps[index].db.totalDistance,
laps
.sublist(0, index + 1)
.map((lap) => lap.db.totalDistance)
.reduce((a, b) => a + b),
RangeAnnotationAxisType.domain,
color: colorArray[index % 2],
endLabel: 'Lap ${laps[index].index}',
)
];
}
}
import 'package:flutter/material.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/models/event.dart';
import 'package:encrateia/utils/list_utils.dart';
import 'package:encrateia/utils/num_utils.dart';
import 'activity_stryd_cadence_chart.dart';
class ActivityStrydCadenceWidget extends StatefulWidget {
final Activity activity;
ActivityStrydCadenceWidget({this.activity});
@override
_ActivityStrydCadenceWidgetState createState() => _ActivityStrydCadenceWidgetState();
}
class _ActivityStrydCadenceWidgetState extends State<ActivityStrydCadenceWidget> {
List<Event> records = [];
String avgStrydCadenceString = "Loading ...";
String sdevStrydCadenceString = "Loading ...";
@override
void initState() {
getData();
super.initState();
}
@override
Widget build(context) {
if (records.length > 0) {
var powerValues = records.map((value) => value.db.strydCadence).nonZeroDoubles();
if (powerValues.length > 0) {
return ListTileTheme(
iconColor: Colors.deepOrange,
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
ActivityStrydCadenceChart(records: records, activity: widget.activity),
ListTile(
leading: Icon(Icons.ev_station),
title: Text(avgStrydCadenceString),
subtitle: Text("average cadence"),
),
ListTile(
leading: Icon(Icons.unfold_more),
title: Text(sdevStrydCadenceString),
subtitle: Text("standard deviation cadence"),
),
ListTile(
leading: Icon(Icons.playlist_add),
title: Text(records.length.toString()),
subtitle: Text("number of measurements"),
),
],
),
);
} else {
return Center(
child: Text("No cadence data available."),
);
}
} else {
return Center(
child: Text("Loading"),
);
}
}
getData() async {
Activity activity = widget.activity;
records = await activity.records;
double avg = await activity.avgStrydCadence;
setState(() {
avgStrydCadenceString = avg.toStringOrDashes(1) + " spm";
});
double sdev = await activity.sdevStrydCadence;
setState(() {
sdevStrydCadenceString = sdev.toStringOrDashes(2) + " spm";
});
}
}
import 'package:charts_flutter/flutter.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/event.dart';
class LapStrydCadenceChart extends StatelessWidget {
final List<Event> records;
LapStrydCadenceChart({this.records});
@override
Widget build(BuildContext context) {
var nonZero = records
.where((value) => value.db.strydCadence != null && value.db.strydCadence > 0)
.toList();
var offset = nonZero.first.db.distance.round();
List<Series<dynamic, num>> data = [
new Series<Event, int>(
id: 'Stryd Cadence',
colorFn: (_, __) => MaterialPalette.green.shadeDefault,
domainFn: (Event record, _) => record.db.distance.round() - offset,
measureFn: (Event record, _) => 2 * record.db.strydCadence,
data: nonZero,
)
];
return new Container(
height: 300,
padding: EdgeInsets.all(2),
child: LineChart(
data,
animate: false,
primaryMeasureAxis: NumericAxisSpec(
tickProviderSpec: BasicNumericTickProviderSpec(
zeroBound: false,
dataIsInWholeNumbers: false,
desiredTickCount: 5,
),
),
behaviors: [
ChartTitle(
'Cadence (spm)',
titleStyleSpec: TextStyleSpec(fontSize: 13),
behaviorPosition: BehaviorPosition.start,
titleOutsideJustification: OutsideJustification.end,
),
ChartTitle(
'Distance (m)',
titleStyleSpec: TextStyleSpec(fontSize: 13),
behaviorPosition: BehaviorPosition.bottom,
titleOutsideJustification: OutsideJustification.end,
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:encrateia/models/lap.dart';
import 'package:encrateia/models/event.dart';
import 'package:encrateia/utils/list_utils.dart';
import 'package:encrateia/utils/num_utils.dart';
import 'lap_stryd_cadence_chart.dart';
class LapStrydCadenceWidget extends StatefulWidget {
final Lap lap;
LapStrydCadenceWidget({this.lap});
@override
_LapStrydCadenceWidgetState createState() => _LapStrydCadenceWidgetState();
}
class _LapStrydCadenceWidgetState extends State<LapStrydCadenceWidget> {
List<Event> records = [];
String avgStrydCadenceString = "Loading ...";
String sdevStrydCadenceString = "Loading ...";
@override
void initState() {
getData();
super.initState();
}
@override
Widget build(context) {
if (records.length > 0) {
var powerValues =
records.map((value) => value.db.strydCadence).nonZeroDoubles();
if (powerValues.length > 0) {
return ListTileTheme(
iconColor: Colors.lightGreen,
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
LapStrydCadenceChart(records: records),
ListTile(
leading: Icon(Icons.ev_station),
title: Text(avgStrydCadenceString),
subtitle: Text("average cadence"),
),
ListTile(
leading: Icon(Icons.unfold_more),
title: Text(sdevStrydCadenceString),
subtitle: Text("standard deviation cadence"),
),
ListTile(
leading: Icon(Icons.playlist_add),
title: Text(records.length.toString()),
subtitle: Text("number of measurements"),
),
],
),
);
} else {
return Center(
child: Text("No cadence available."),
);
}
} else {
return Center(
child: Text("Loading"),
);
}
}
getData() async {
Lap lap = widget.lap;
records = await lap.records;
double avg = await lap.avgStrydCadence;
setState(() {
avgStrydCadenceString = avg.toStringOrDashes(1) + " spm";
});
double sdev = await lap.sdevStrydCadence;
setState(() {
sdevStrydCadenceString = sdev.toStringOrDashes(2) + " spm";
});
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment