Commit af7436ba authored by Administrator's avatar Administrator

vertical oscillation on lap and activity level

parent a9860724
......@@ -96,6 +96,7 @@ const tableActivity = SqfEntityTable(
SqfEntityField('sdevFormPower', DbType.real),
SqfEntityField('avgStrydCadence', DbType.real),
SqfEntityField('sdevStrydCadence', DbType.real),
SqfEntityField('sdevVerticalOscillation', DbType.real),
SqfEntityFieldRelationship(
parentTable: tableAthlete,
......@@ -192,6 +193,7 @@ const tableLap = SqfEntityTable(
SqfEntityField('sdevFormPower', DbType.real),
SqfEntityField('avgStrydCadence', DbType.real),
SqfEntityField('sdevStrydCadence', DbType.real),
SqfEntityField('sdevVerticalOscillation', DbType.real),
SqfEntityFieldRelationship(
parentTable: tableEvent,
......
This diff is collapsed.
......@@ -160,6 +160,27 @@ class Activity extends ChangeNotifier {
return db.sdevGroundTime;
}
Future<double> get avgVerticalOscillation async {
if (db.avgVerticalOscillation == null || db.avgVerticalOscillation == 6553.5) {
List<Event> records = await this.records;
db.avgVerticalOscillation = Lap.calculateAverageVerticalOscillation(records: records);
await db.save();
notifyListeners();
}
return db.avgVerticalOscillation;
}
Future<double> get sdevVerticalOscillation async {
if (db.sdevVerticalOscillation == null) {
List<Event> records = await this.records;
db.sdevVerticalOscillation = Lap.calculateSdevVerticalOscillation(records: records);
await db.save();
notifyListeners();
}
return db.sdevVerticalOscillation;
}
Future<double> get avgStrydCadence async {
if (db.avgStrydCadence == null) {
List<Event> records = await this.records;
......
......@@ -115,6 +115,25 @@ class Lap {
return db.sdevGroundTime;
}
Future<double> get avgVerticalOscillation async {
if (db.avgVerticalOscillation == null || db.avgVerticalOscillation == 6553.5) {
List<Event> records = await this.records;
db.avgVerticalOscillation = calculateAverageVerticalOscillation(records: records);
await db.save();
}
return db.avgVerticalOscillation;
}
Future<double> get sdevVerticalOscillation async {
if (db.sdevVerticalOscillation == null) {
List<Event> records = await this.records;
db.sdevVerticalOscillation = calculateSdevVerticalOscillation(records: records);
await db.save();
}
return db.sdevVerticalOscillation;
}
Future<double> get avgStrydCadence async {
if (db.avgStrydCadence == null) {
List<Event> records = await this.records;
......@@ -277,6 +296,22 @@ class Lap {
return legSpringStiffnesses.sdev();
}
static double calculateAverageVerticalOscillation({List<Event> records}) {
var verticalOscillation =
records.map((record) => record.db.verticalOscillation).nonZeroDoubles();
if (verticalOscillation.length > 0) {
return verticalOscillation.mean();
} else
return -1;
}
static double calculateSdevVerticalOscillation({List<Event> records}) {
var verticalOscillation =
records.map((record) => record.db.verticalOscillation).nonZeroDoubles();
return verticalOscillation.sdev();
}
static double calculateAverageFormPower({List<Event> records}) {
var legSpringStiffnesses =
records.map((record) => record.db.formPower).nonZeroInts();
......
......@@ -8,6 +8,7 @@ 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:encrateia/widgets/activity_vertical_oscillation_widget.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/activity.dart';
......@@ -22,7 +23,7 @@ class ShowActivityScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 10,
length: 11,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
......@@ -64,6 +65,10 @@ class ShowActivityScreen extends StatelessWidget {
icon: Icon(Icons.pets),
text: "Cadence",
),
Tab(
icon: Icon(Icons.unfold_more),
text: "Vertical Oscillation",
),
Tab(
icon: Icon(Icons.storage),
text: "Metadata",
......@@ -85,6 +90,7 @@ class ShowActivityScreen extends StatelessWidget {
ActivityLegSpringStiffnessWidget(activity: activity),
ActivityFormPowerWidget(activity: activity),
ActivityStrydCadenceWidget(activity: activity),
ActivityVerticalOscillationWidget(activity: activity),
ActivityMetadataWidget(activity: activity),
]),
),
......
......@@ -7,6 +7,7 @@ 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:encrateia/widgets/lap_vertical_oscillation_widget.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/lap.dart';
......@@ -21,7 +22,7 @@ class ShowLapScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 9,
length: 10,
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.green,
......@@ -60,6 +61,10 @@ class ShowLapScreen extends StatelessWidget {
icon: Icon(Icons.pets),
text: "Cadence",
),
Tab(
icon: Icon(Icons.unfold_more),
text: "Vertical Oscillation",
),
Tab(
icon: Icon(Icons.storage),
text: "Metadata",
......@@ -80,6 +85,7 @@ class ShowLapScreen extends StatelessWidget {
LapLegSpringStiffnessWidget(lap: lap),
LapFormPowerWidget(lap: lap),
LapStrydCadenceWidget(lap: lap),
LapVerticalOscillationWidget(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 ActivityVerticalOscillationChart extends StatelessWidget {
final List<Event> records;
final Activity activity;
final colorArray = [
MaterialPalette.white,
MaterialPalette.gray.shade200,
];
ActivityVerticalOscillationChart({this.records, @required this.activity});
@override
Widget build(BuildContext context) {
var nonZero = records.where((value) => value.db.power > 100);
var smoothedRecords = Event.toDoubleDataPoints(
attribute: "verticalOscillation",
records: nonZero,
amount: 30,
);
List<Series<dynamic, num>> data = [
new Series<DoublePlotPoint, int>(
id: 'Vertical Oscillation',
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(
'Vertical Oscillation (cm)',
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_vertical_oscillation_chart.dart';
class ActivityVerticalOscillationWidget extends StatefulWidget {
final Activity activity;
ActivityVerticalOscillationWidget({this.activity});
@override
_ActivityVerticalOscillationWidgetState createState() => _ActivityVerticalOscillationWidgetState();
}
class _ActivityVerticalOscillationWidgetState extends State<ActivityVerticalOscillationWidget> {
List<Event> records = [];
String avgVerticalOscillationString = "Loading ...";
String sdevVerticalOscillationString = "Loading ...";
@override
void initState() {
getData();
super.initState();
}
@override
Widget build(context) {
if (records.length > 0) {
var powerValues = records.map((value) => value.db.verticalOscillation).nonZeroDoubles();
if (powerValues.length > 0) {
return ListTileTheme(
iconColor: Colors.deepOrange,
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
ActivityVerticalOscillationChart(records: records, activity: widget.activity),
ListTile(
leading: Icon(Icons.ev_station),
title: Text(avgVerticalOscillationString),
subtitle: Text("average vertical oscillation"),
),
ListTile(
leading: Icon(Icons.unfold_more),
title: Text(sdevVerticalOscillationString),
subtitle: Text("standard deviation vertical oscillation"),
),
ListTile(
leading: Icon(Icons.playlist_add),
title: Text(records.length.toString()),
subtitle: Text("number of measurements"),
),
],
),
);
} else {
return Center(
child: Text("No power data available."),
);
}
} else {
return Center(
child: Text("Loading"),
);
}
}
getData() async {
Activity activity = widget.activity;
records = await activity.records;
double avg = await activity.avgVerticalOscillation;
setState(() {
avgVerticalOscillationString = avg.toStringOrDashes(1) + " cm";
});
double sdev = await activity.sdevVerticalOscillation;
setState(() {
sdevVerticalOscillationString = sdev.toStringOrDashes(2) + " cm";
});
}
}
import 'package:charts_flutter/flutter.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/event.dart';
class LapVerticalOscillationChart extends StatelessWidget {
final List<Event> records;
LapVerticalOscillationChart({this.records});
@override
Widget build(BuildContext context) {
var nonZero = records
.where((value) => value.db.verticalOscillation != null && value.db.verticalOscillation > 0)
.toList();
var offset = nonZero.first.db.distance.round();
List<Series<dynamic, num>> data = [
new Series<Event, int>(
id: 'Vertical Oscillation',
colorFn: (_, __) => MaterialPalette.green.shadeDefault,
domainFn: (Event record, _) => record.db.distance.round() - offset,
measureFn: (Event record, _) => record.db.verticalOscillation,
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(
'Vertical Oscillation (cm)',
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_vertical_oscillation_chart.dart';
class LapVerticalOscillationWidget extends StatefulWidget {
final Lap lap;
LapVerticalOscillationWidget({this.lap});
@override
_LapVerticalOscillationWidgetState createState() => _LapVerticalOscillationWidgetState();
}
class _LapVerticalOscillationWidgetState extends State<LapVerticalOscillationWidget> {
List<Event> records = [];
String avgVerticalOscillationString = "Loading ...";
String sdevVerticalOscillationString = "Loading ...";
@override
void initState() {
getData();
super.initState();
}
@override
Widget build(context) {
if (records.length > 0) {
var powerValues =
records.map((value) => value.db.verticalOscillation).nonZeroDoubles();
if (powerValues.length > 0) {
return ListTileTheme(
iconColor: Colors.lightGreen,
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
LapVerticalOscillationChart(records: records),
ListTile(
leading: Icon(Icons.ev_station),
title: Text(avgVerticalOscillationString),
subtitle: Text("average vertical oscillation"),
),
ListTile(
leading: Icon(Icons.unfold_more),
title: Text(sdevVerticalOscillationString),
subtitle: Text("standard deviation vertical oscillation"),
),
ListTile(
leading: Icon(Icons.playlist_add),
title: Text(records.length.toString()),
subtitle: Text("number of measurements"),
),
],
),
);
} else {
return Center(
child: Text("No vertical oscillation data available."),
);
}
} else {
return Center(
child: Text("Loading"),
);
}
}
getData() async {
Lap lap = widget.lap;
records = await lap.records;
double avg = await lap.avgVerticalOscillation;
setState(() {
avgVerticalOscillationString = avg.toStringOrDashes(1) + " cm";
});
double sdev = await lap.sdevVerticalOscillation;
setState(() {
sdevVerticalOscillationString = sdev.toStringOrDashes(2) + " cm";
});
}
}
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