Commit 89fd2846 authored by Administrator's avatar Administrator

ftp diagram on activity level (done) and athlete level (prepared)

parent 361b70cc
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_secure_storage","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_secure_storage-3.3.3/","dependencies":[]},{"name":"path_provider","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.8/","dependencies":[]},{"name":"shared_preferences","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences-0.5.7+2/","dependencies":[]},{"name":"sqflite","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-1.3.0+1/","dependencies":[]},{"name":"uni_links","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/uni_links-0.4.0/","dependencies":[]},{"name":"url_launcher","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-5.4.10/","dependencies":[]}],"android":[{"name":"flutter_secure_storage","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_secure_storage-3.3.3/","dependencies":[]},{"name":"path_provider","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.8/","dependencies":[]},{"name":"shared_preferences","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences-0.5.7+2/","dependencies":[]},{"name":"sqflite","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-1.3.0+1/","dependencies":[]},{"name":"uni_links","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/uni_links-0.4.0/","dependencies":[]},{"name":"url_launcher","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-5.4.10/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+2/","dependencies":[]},{"name":"shared_preferences_macos","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_macos-0.0.1+8/","dependencies":[]},{"name":"sqflite","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-1.3.0+1/","dependencies":[]},{"name":"url_launcher_macos","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.0.1+5/","dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"shared_preferences_web","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_web-0.1.2+5/","dependencies":[]},{"name":"url_launcher_web","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_web-0.1.1+5/","dependencies":[]}]},"dependencyGraph":[{"name":"flutter_secure_storage","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_macos","shared_preferences_web"]},{"name":"shared_preferences_macos","dependencies":[]},{"name":"shared_preferences_web","dependencies":[]},{"name":"sqflite","dependencies":[]},{"name":"uni_links","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_web","url_launcher_macos"]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]}],"date_created":"2020-07-16 10:44:52.595918","version":"1.17.3"}
\ No newline at end of file
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_secure_storage","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_secure_storage-3.3.3/","dependencies":[]},{"name":"path_provider","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.8/","dependencies":[]},{"name":"shared_preferences","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences-0.5.7+2/","dependencies":[]},{"name":"sqflite","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-1.3.0+1/","dependencies":[]},{"name":"uni_links","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/uni_links-0.4.0/","dependencies":[]},{"name":"url_launcher","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-5.4.10/","dependencies":[]}],"android":[{"name":"flutter_secure_storage","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_secure_storage-3.3.3/","dependencies":[]},{"name":"path_provider","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.8/","dependencies":[]},{"name":"shared_preferences","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences-0.5.7+2/","dependencies":[]},{"name":"sqflite","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-1.3.0+1/","dependencies":[]},{"name":"uni_links","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/uni_links-0.4.0/","dependencies":[]},{"name":"url_launcher","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-5.4.10/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+2/","dependencies":[]},{"name":"shared_preferences_macos","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_macos-0.0.1+8/","dependencies":[]},{"name":"sqflite","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-1.3.0+1/","dependencies":[]},{"name":"url_launcher_macos","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.0.1+5/","dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"shared_preferences_web","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_web-0.1.2+5/","dependencies":[]},{"name":"url_launcher_web","path":"/home/stefan/flutter/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_web-0.1.1+5/","dependencies":[]}]},"dependencyGraph":[{"name":"flutter_secure_storage","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_macos","shared_preferences_web"]},{"name":"shared_preferences_macos","dependencies":[]},{"name":"shared_preferences_web","dependencies":[]},{"name":"sqflite","dependencies":[]},{"name":"uni_links","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_web","url_launcher_macos"]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]}],"date_created":"2020-07-16 15:21:04.677272","version":"1.17.3"}
\ No newline at end of file
import 'athlete.dart';
class Ftp {
static void calculate({Athlete athlete}) {
}
}
......@@ -27,8 +27,7 @@ class PowerDuration {
for (int durationIndex = newDuration;
durationIndex > 0;
durationIndex--) {
if (newPower <= (powerMap[durationIndex] ?? 0))
break;
if (newPower <= (powerMap[durationIndex] ?? 0)) break;
powerMap[durationIndex] = newPower;
}
}
......@@ -41,8 +40,7 @@ class PowerDuration {
if (power > (powerMap[duration] ?? 0)) {
for (int durationIndex = duration; durationIndex > 0; durationIndex--) {
if (power <= (powerMap[durationIndex] ?? 0))
break;
if (power <= (powerMap[durationIndex] ?? 0)) break;
powerMap[durationIndex] = power.toDouble();
}
}
......@@ -61,10 +59,16 @@ class PowerDuration {
));
});
plotPoints
.sort((DoublePlotPoint a, DoublePlotPoint b) => a.domain.compareTo(b.domain));
plotPoints.sort(
(DoublePlotPoint a, DoublePlotPoint b) => a.domain.compareTo(b.domain));
return plotPoints;
}
PowerDuration normalize() {
powerMap.forEach((int duration, double power) =>
powerMap[duration] = power * pow(3600 / duration, -0.07));
return this;
}
static int scaled({int seconds}) => (200 * log(seconds)).round();
}
import 'package:encrateia/models/athlete.dart';
import 'package:encrateia/utils/my_color.dart';
import 'package:encrateia/widgets/activity_widgets/activity_bargraph_widget.dart';
import 'package:encrateia/widgets/activity_widgets/activity_ftp_widget.dart';
import 'package:encrateia/widgets/activity_widgets/activity_metadata_widget.dart';
import 'package:encrateia/widgets/activity_widgets/activity_overview_widget.dart';
import 'package:encrateia/widgets/activity_widgets/activity_pace_widget.dart';
......@@ -229,6 +230,16 @@ class _ShowActivityScreenState extends State<ShowActivityScreen> {
athlete: widget.athlete,
),
),
navigationButton(
title: 'FTP',
color: MyColor.navigate,
icon: MyIcon.ftp,
context: context,
nextWidget: ActivityFtpWidget(
activity: widget.activity,
athlete: widget.athlete,
),
),
navigationButton(
title: 'Metadata',
color: MyColor.settings,
......
......@@ -2,6 +2,7 @@ import 'package:encrateia/actions/analyse_activities.dart';
import 'package:encrateia/actions/import_activities_locally.dart';
import 'package:encrateia/utils/my_color.dart';
import 'package:encrateia/widgets/activities_list_widget.dart';
import 'package:encrateia/widgets/athlete_widgets/athlete_ftp_widget.dart';
import 'package:encrateia/widgets/athlete_widgets/athlete_heart_rate_widget.dart';
import 'package:encrateia/widgets/athlete_widgets/athlete_pace_widget.dart';
import 'package:encrateia/widgets/athlete_widgets/athlete_power_ratio_widget.dart';
......@@ -116,6 +117,12 @@ class _ShowAthleteScreenState extends State<ShowAthleteScreen> {
icon: MyIcon.cadence,
nextWidget: AthleteStrydCadenceWidget(athlete: widget.athlete),
),
navigationButton(
color: MyColor.navigate,
title: 'FTP',
icon: MyIcon.ftp,
nextWidget: AthleteFtpWidget(athlete: widget.athlete),
),
RaisedButton.icon(
color: MyColor.add,
textColor: MyColor.textColor(backgroundColor: MyColor.add),
......
......@@ -45,6 +45,7 @@ class MyIcon {
color: MyColor.white,
);
static final Icon formPower = Icon(Icons.ev_station);
static final Icon ftp = Icon(Icons.multiline_chart);
// G
static final Icon groundTime = Icon(Icons.vertical_align_bottom);
// H
......
import 'package:encrateia/models/athlete.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/models/event.dart';
import 'package:encrateia/widgets/charts/ftp_chart.dart';
class ActivityFtpWidget extends StatelessWidget {
const ActivityFtpWidget({
@required this.activity,
@required this.athlete,
});
final Activity activity;
final Athlete athlete;
@override
Widget build(BuildContext context) {
return FutureBuilder<List<Event>>(
future: activity.records,
builder: (BuildContext context, AsyncSnapshot<List<Event>> snapshot) {
if (snapshot.hasData) {
final List<Event> powerRecords = snapshot.data
.where((Event value) =>
value.power != null && value.power > 100)
.toList();
if (powerRecords.isNotEmpty) {
return SingleChildScrollView(
child: FtpChart(records: powerRecords),
);
} else {
return const Center(
child: Text('No power data available.'),
);
}
} else {
return Container(
height: 100,
child: const Center(
child: Text('Loading'),
),
);
}
},
);
}
}
import 'package:encrateia/models/activity_list.dart';
import 'package:encrateia/models/ftp.dart';
import 'package:encrateia/models/tag_group.dart';
import 'package:encrateia/utils/athlete_time_series_chart.dart';
import 'package:encrateia/utils/enums.dart';
import 'package:encrateia/utils/my_button.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/athlete.dart';
import 'package:encrateia/models/activity.dart';
import 'athlete_filter_widget.dart';
class AthleteFtpWidget extends StatefulWidget {
const AthleteFtpWidget({this.athlete});
final Athlete athlete;
@override
_AthleteFtpWidgetState createState() => _AthleteFtpWidgetState();
}
class _AthleteFtpWidgetState extends State<AthleteFtpWidget> {
ActivityList<Activity> activities = ActivityList<Activity>(<Activity>[]);
List<TagGroup> tagGroups = <TagGroup>[];
String loadingStatus = 'Loading ...';
@override
void initState() {
getData();
super.initState();
}
@override
Widget build(BuildContext context) {
if (activities.isNotEmpty) {
final List<Activity> ftpActivities = activities
.where(
(Activity activity) => activity.ftp != null && activity.ftp > 0)
.toList();
if (ftpActivities.isNotEmpty) {
return ListTileTheme(
iconColor: Colors.orange,
child: ListView(
padding: const EdgeInsets.only(left: 25),
children: <Widget>[
AthleteTimeSeriesChart(
activities: ftpActivities,
chartTitleText: 'FTP (W)',
activityAttr: ActivityAttr.avgPower,
athlete: widget.athlete,
),
AthleteFilterWidget(
athlete: widget.athlete,
tagGroups: tagGroups,
callBackFunction: getData,
)
]),
);
} else {
return ListView(children: <Widget>[
const Center(
child: Text('\nNo ftp data available.'),
),
Row(
children: <Widget>[
const Spacer(),
MyButton.activity(
child: const Text('Calculate FTP'),
onPressed: () => Ftp.calculate(athlete: widget.athlete),
),
const SizedBox(width: 20),
],
)
]);
}
} else {
return ListView(children: <Widget>[
const SizedBox(
height: 50,
),
Center(
child: Text(loadingStatus),
),
const SizedBox(
height: 50,
),
AthleteFilterWidget(
athlete: widget.athlete,
tagGroups: tagGroups,
callBackFunction: getData,
)
]);
}
}
Future<void> getData() async {
final Athlete athlete = widget.athlete;
final List<Activity> unfilteredActivities = await athlete.activities;
tagGroups = await widget.athlete.tagGroups;
activities = await ActivityList<Activity>(unfilteredActivities).applyFilter(
athlete: widget.athlete,
tagGroups: tagGroups,
);
loadingStatus = activities.length.toString() + ' activities found';
setState(() {});
}
}
import 'package:encrateia/models/power_duration.dart';
import 'package:charts_flutter/flutter.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/event.dart';
import 'package:encrateia/models/plot_point.dart';
class FtpChart extends StatelessWidget {
const FtpChart({this.records});
final List<Event> records;
@override
Widget build(BuildContext context) {
final PowerDuration powerDuration = PowerDuration(records: records);
final PowerDuration ftpCurve = powerDuration.normalize();
final List<Series<DoublePlotPoint, num>> data = <Series<DoublePlotPoint, num>>[
Series<DoublePlotPoint, int>(
id: 'Power Duration',
colorFn: (_, __) => MaterialPalette.green.shadeDefault,
domainFn: (DoublePlotPoint record, _) => record.domain,
measureFn: (DoublePlotPoint record, _) => record.measure,
data: ftpCurve.asList(),
)
];
final StaticNumericTickProviderSpec staticTicks =
StaticNumericTickProviderSpec(<TickSpec<int>>[
TickSpec<int>(PowerDuration.scaled(seconds: 1), label: '1s'),
TickSpec<int>(PowerDuration.scaled(seconds: 10), label: '10s'),
TickSpec<int>(PowerDuration.scaled(seconds: 60), label: '1min'),
TickSpec<int>(PowerDuration.scaled(seconds: 600), label: '10min'),
TickSpec<int>(PowerDuration.scaled(seconds: 3600), label: '1h'),
]);
final List<ChartTitle> chartTitles = <ChartTitle>[
ChartTitle(
'normalized FTP Power (W)',
titleStyleSpec: const TextStyleSpec(fontSize: 13),
behaviorPosition: BehaviorPosition.start,
titleOutsideJustification: OutsideJustification.end,
),
ChartTitle(
'Time',
titleStyleSpec: const TextStyleSpec(fontSize: 13),
behaviorPosition: BehaviorPosition.bottom,
titleOutsideJustification: OutsideJustification.end,
),
ChartTitle(
'FTP diagram created with Encrateia https://encreteia.informatom.com',
titleStyleSpec: const TextStyleSpec(fontSize: 10),
behaviorPosition: BehaviorPosition.top,
titleOutsideJustification: OutsideJustification.endDrawArea,
),
];
return Container(
height: 300,
padding: const EdgeInsets.all(2),
child: LineChart(
data,
defaultRenderer: LineRendererConfig<num>(
includeArea: true,
),
primaryMeasureAxis: const NumericAxisSpec(
tickProviderSpec: BasicNumericTickProviderSpec(
zeroBound: false,
dataIsInWholeNumbers: true,
desiredTickCount: 10,
desiredMinTickCount: 6),
),
domainAxis: NumericAxisSpec(tickProviderSpec: staticTicks),
animate: false,
behaviors: chartTitles,
),
);
}
}
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