UI Nits: Daily/Weekly/Monthly Summary

You can now add the summary chart to the diary
view to get a summary of the date range currently
being summarised on that view.

Once the Home view has its own sidebar that selects
date ranges you will be able to add it there too
and summarise seasons etc.
This commit is contained in:
Mark Liversedge
2012-11-13 13:27:36 +00:00
parent a3a49d8a3d
commit d22777ed74
19 changed files with 355 additions and 209 deletions

View File

@@ -62,8 +62,15 @@ class DBAccess
// Query Records
QList<SummaryMetrics> getAllMetricsFor(QDateTime start, QDateTime end);
QList<SummaryMetrics> getAllMetricsFor(DateRange dr) {
return getAllMetricsFor(QDateTime(dr.from,QTime(0,0,0)), QDateTime(dr.to, QTime(23,59,59)));
}
bool getRide(QString filename, SummaryMetrics &metrics, QColor&color);
QList<SummaryMetrics> getAllMeasuresFor(QDateTime start, QDateTime end);
QList<SummaryMetrics> getAllMeasuresFor(DateRange dr) {
return getAllMeasuresFor(QDateTime(dr.from,QTime(0,0,0)), QDateTime(dr.to, QTime(23,59,59)));
}
SummaryMetrics getRideMetrics(QString filename); // for a filename

View File

@@ -640,7 +640,7 @@ GcCalendar::setSummary()
const RideMetric *metric = RideMetricFactory::instance().rideMetric(metricname);
QString value = getAggregated(metricname, results);
QString value = SummaryMetrics::getAggregated(metricname, results, useMetricUnits);
// don't show units for time values
@@ -670,72 +670,15 @@ GcCalendar::setSummary()
// set webview contents
summary->page()->mainFrame()->setHtml(summaryText);
}
}
QString GcCalendar::getAggregated(QString name, QList<SummaryMetrics> &results)
{
// get the metric details, so we can convert etc
const RideMetric *metric = RideMetricFactory::instance().rideMetric(name);
if (!metric) return QString("%1 unknown").arg(name);
// do we need to convert from metric to imperial?
bool useMetricUnits = (appsettings->value(this, GC_UNIT).toString() == "Metric");
// what we will return
double rvalue = 0;
double rcount = 0; // using double to avoid rounding issues with int when dividing
// loop through and aggregate
foreach (SummaryMetrics rideMetrics, results) {
// get this value
double value = rideMetrics.getForSymbol(name);
double count = rideMetrics.getForSymbol("workout_time"); // for averaging
// tell everyone the date range changed
QString name;
if (summarySelect->currentIndex() == 0) name = "Day of " + from.toString("dddd MMMM d");
else if (summarySelect->currentIndex() == 1) name = QString("Week Commencing %1").arg(from.toString("dddd MMMM d"));
else if (summarySelect->currentIndex() == 2) name = from.toString("MMMM yyyy");
// check values are bounded, just in case
if (isnan(value) || isinf(value)) value = 0;
emit dateRangeChanged(DateRange(from, to, name));
// imperial / metric conversion
if (useMetricUnits == false) {
value *= metric->conversion();
value += metric->conversionSum();
}
switch (metric->type()) {
case RideMetric::Total:
rvalue += value;
break;
case RideMetric::Average:
{
// average should be calculated taking into account
// the duration of the ride, otherwise high value but
// short rides will skew the overall average
rvalue += value*count;
rcount += count;
break;
}
case RideMetric::Peak:
{
if (value > rvalue) rvalue = value;
break;
}
}
}
// now compute the average
if (metric->type() == RideMetric::Average) {
if (rcount) rvalue = rvalue / rcount;
}
// Format appropriately
QString result;
if (metric->units(useMetricUnits) == "seconds") result = time_to_string(rvalue);
else result = QString("%1").arg(rvalue, 0, 'f', metric->precision());
return result;
}
void

View File

@@ -25,6 +25,7 @@
#include <QWebView>
#include "MainWindow.h"
#include "TimeUtils.h"
#include "GcCalendarModel.h"
#include "RideItem.h"
#include "RideNavigator.h"
@@ -82,9 +83,11 @@ class GcCalendar : public QWidget // not a GcWindow - belongs on sidebar
void setSummary(); // set the summary at the bottom
// summary metrics aggregator -- refactor later
QString getAggregated(QString name, QList<SummaryMetrics> &results);
void splitterMoved(int pos, int index);
signals:
void dateRangeChanged(DateRange);
protected:
MainWindow *main;
RideItem *_ride;

View File

@@ -67,7 +67,7 @@ GcWindowRegistry GcWindows[] = {
{ VIEW_HOME, "Performance Manager",GcWindowTypes::PerformanceManager },
{ VIEW_HOME, "Collection TreeMap",GcWindowTypes::TreeMap },
{ VIEW_HOME, "Weekly Summary",GcWindowTypes::WeeklySummary },
{ VIEW_ANALYSIS, "Summary",GcWindowTypes::RideSummary },
{ VIEW_ANALYSIS, "Activity Summary",GcWindowTypes::RideSummary },
{ VIEW_ANALYSIS, "Details",GcWindowTypes::MetadataWindow },
{ VIEW_ANALYSIS, "Summary and Details",GcWindowTypes::Summary },
{ VIEW_ANALYSIS, "Editor",GcWindowTypes::RideEditor },
@@ -83,6 +83,7 @@ GcWindowRegistry GcWindows[] = {
{ VIEW_ANALYSIS, "Aerolab Chung Analysis",GcWindowTypes::Aerolab },
{ VIEW_DIARY, "Calendar",GcWindowTypes::Diary },
{ VIEW_DIARY, "Navigator", GcWindowTypes::ActivityNavigator },
{ VIEW_DIARY, "Summary", GcWindowTypes::DateRangeSummary },
{ VIEW_TRAIN, "Telemetry",GcWindowTypes::DialWindow },
{ VIEW_TRAIN, "Workout",GcWindowTypes::WorkoutPlot },
{ VIEW_TRAIN, "Realtime",GcWindowTypes::RealtimePlot },
@@ -119,7 +120,8 @@ GcWindowRegistry::newGcWindow(GcWinID id, MainWindow *main) //XXX mainWindow wil
case GcWindowTypes::PfPv: returning = new PfPvWindow(main); break;
case GcWindowTypes::HrPw: returning = new HrPwWindow(main); break;
case GcWindowTypes::RideEditor: returning = new RideEditor(main); break;
case GcWindowTypes::RideSummary: returning = new RideSummaryWindow(main); break;
case GcWindowTypes::RideSummary: returning = new RideSummaryWindow(main, true); break;
case GcWindowTypes::DateRangeSummary: returning = new RideSummaryWindow(main, false); break;
case GcWindowTypes::Scatter: returning = new ScatterWindow(main, main->home); break;
case GcWindowTypes::Summary: returning = new SummaryWindow(main); break;
case GcWindowTypes::TreeMap: returning = new TreeMapWindow(main, main->useMetricUnits, main->home); break;

View File

@@ -57,6 +57,7 @@ enum gcwinid {
RealtimeControls = 29,
ActivityNavigator = 30,
SpinScanPlot = 31,
DateRangeSummary = 32
};
};
typedef enum GcWindowTypes::gcwinid GcWinID;

View File

@@ -90,6 +90,7 @@ void GcWindow::setRideItem(RideItem* x)
void GcWindow::setDateRange(DateRange dr)
{
_dr = dr;
emit dateRangeChanged(_dr);
}
DateRange GcWindow::dateRange() const
@@ -154,6 +155,7 @@ GcWindow::GcWindow()
qRegisterMetaType<RideItem*>("ride");
qRegisterMetaType<GcWinID>("type");
qRegisterMetaType<QColor>("color");
qRegisterMetaType<DateRange>("dateRange");
setControls(NULL);
setRideItem(NULL);
setTitle("");
@@ -185,6 +187,7 @@ GcWindow::GcWindow(QWidget *parent) : QFrame(parent), dragState(None) {
qRegisterMetaType<RideItem*>("ride");
qRegisterMetaType<GcWinID>("type");
qRegisterMetaType<QColor>("color");
qRegisterMetaType<DateRange>("dateRange");
setParent(parent);
setControls(NULL);
setRideItem(NULL);

View File

@@ -23,6 +23,7 @@
#define G_OBJECT Q_PROPERTY(QString instanceName READ instanceName WRITE setInstanceName)
#define setInstanceName(x) setProperty("instanceName", x)
#define myRideItem property("ride").value<RideItem*>()
#define myDateRange property("dateRange").value<DateRange>()
#include <QString>
#include <QMenu>

View File

@@ -202,6 +202,7 @@ HomeWindow::HomeWindow(MainWindow *mainWindow, QString name, QString /* windowti
styleChanged(2);
connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideSelected()));
connect(this, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateRangeChanged(DateRange)));
connect(mainWindow, SIGNAL(configChanged()), this, SLOT(configChanged()));
connect(tabbed, SIGNAL(currentChanged(int)), this, SLOT(tabSelected(int)));
connect(tabbed, SIGNAL(tabCloseRequested(int)), this, SLOT(removeChart(int)));
@@ -359,6 +360,27 @@ HomeWindow::rideSelected()
}
}
void
HomeWindow::dateRangeChanged(DateRange dr)
{
if (amVisible()) {
for (int i=0; i < charts.count(); i++) {
// show if its not a tab
if (currentStyle) charts[i]->show(); // keep tabs hidden, show the rest
// if we are tabbed AND its the current tab then mimic tabselected
// to force the tabwidget to refresh AND set its ride property (see below)
//otherwise just go ahead and notify it of a new ride
if (!currentStyle && charts.count() && i==tabbed->currentIndex())
tabSelected(tabbed->currentIndex());
else
charts[i]->setProperty("dateRange", property("dateRange"));
}
}
}
void
HomeWindow::tabSelected(int index)
{
@@ -367,6 +389,7 @@ HomeWindow::tabSelected(int index)
if (index >= 0) {
charts[index]->show();
charts[index]->setProperty("ride", property("ride"));
charts[index]->setProperty("dateRange", property("dateRange"));
controlStack->setCurrentIndex(index);
titleEdit->setText(charts[index]->property("title").toString());
}
@@ -430,6 +453,7 @@ HomeWindow::styleChanged(int id)
tabbed->addTab(charts[i], charts[i]->property("title").toString());
charts[i]->setContentsMargins(0,25,0,0);
charts[i]->setResizable(false); // we need to show on tab selection!
charts[i]->setProperty("dateRange", property("dateRange"));
charts[i]->hide(); // we need to show on tab selection!
break;
case 1 : // they are lists in a GridLayout
@@ -437,6 +461,7 @@ HomeWindow::styleChanged(int id)
charts[i]->setContentsMargins(0,25,0,0);
charts[i]->setResizable(false); // we need to show on tab selection!
charts[i]->show();
charts[i]->setProperty("dateRange", property("dateRange"));
charts[i]->setProperty("ride", property("ride"));
break;
case 2 : // thet are in a FlowLayout
@@ -444,6 +469,7 @@ HomeWindow::styleChanged(int id)
charts[i]->setContentsMargins(0,15,0,0);
charts[i]->setResizable(true); // we need to show on tab selection!
charts[i]->show();
charts[i]->setProperty("dateRange", property("dateRange"));
charts[i]->setProperty("ride", property("ride"));
default:
break;
@@ -571,6 +597,7 @@ HomeWindow::addChart(GcWindow* newone)
RideItem *notconst = (RideItem*)mainWindow->currentRideItem();
newone->setProperty("ride", QVariant::fromValue<RideItem*>(notconst));
newone->setProperty("dateRange", property("dateRange"));
// add to tabs
switch (currentStyle) {
@@ -1219,6 +1246,7 @@ GcWindowDialog::GcWindowDialog(GcWinID type, MainWindow *mainWindow) : mainWindo
RideItem *notconst = (RideItem*)mainWindow->currentRideItem();
win->setProperty("ride", QVariant::fromValue<RideItem*>(notconst));
win->setProperty("dateRange", property("dateRange"));
layout->setStretch(0, 100);
layout->setStretch(1, 50);

View File

@@ -55,6 +55,7 @@ class HomeWindow : public GcWindow
// GC signals
void rideSelected();
void dateRangeChanged(DateRange);
void configChanged();
// QT Widget events and signals

View File

@@ -517,10 +517,6 @@ MainWindow::MainWindow(const QDir &home) :
chartTool = new GcWindowTool(this);
#endif
// RIGHT SIDEBAR
gcCalendar = new GcCalendar(this);
//gcCalendar->setStyleSheet("background: #B3B4BA;");
//gcCalendar->setStyleSheet("background: white;");
// TOOLBOX
toolBox = new QToolBox(this);
@@ -584,6 +580,10 @@ MainWindow::MainWindow(const QDir &home) :
// POPULATE TOOLBOX
// do controllers after home windows -- they need their first signals caught
gcCalendar = new GcCalendar(this);
connect(gcCalendar, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateRangeChanged(DateRange)));
toolBox->addItem(intervalSplitter, QIcon(":images/activity.png"), "Activity History");
toolBox->addItem(gcCalendar, QIcon(":images/toolbar/main/diary.png"), "Calendar");
toolBox->addItem(trainTool->controls(), QIcon(":images/library.png"), "Workout Library");
@@ -1037,6 +1037,15 @@ MainWindow::rideTreeWidgetSelectionChanged()
}
}
void
MainWindow::dateRangeChanged(DateRange dr)
{
// we got signalled date range changed, tell the current view
// when we have multiple sidebars that change date we need to connect
// them up individually.... i.e. LTM....
diaryWindow->setProperty("dateRange", QVariant::fromValue<DateRange>(dr));
}
void
MainWindow::enableSaveButton()
{
@@ -1326,6 +1335,7 @@ MainWindow::selectDiary()
analButtons->hide();
trainTool->getToolbarButtons()->hide();
toolBox->setCurrentIndex(1);
gcCalendar->refresh(); // get that signal with the date range...
setStyle();
}

View File

@@ -323,6 +323,8 @@ class MainWindow : public QMainWindow
void toggleFullScreen();
#endif
void dateRangeChanged(DateRange);
protected:
static QString notesFileName(QString rideFileName);

View File

@@ -306,6 +306,12 @@ MetricAggregator::writeAsCSV(QString filename)
file.close();
}
QList<SummaryMetrics>
MetricAggregator::getAllMetricsFor(DateRange dr)
{
return getAllMetricsFor(QDateTime(dr.from, QTime(0,0,0)), QDateTime(dr.to, QTime(23,59,59)));
}
QList<SummaryMetrics>
MetricAggregator::getAllMetricsFor(QDateTime start, QDateTime end)
{
@@ -349,6 +355,12 @@ MetricAggregator::getAllMetricsFor(QString filename)
return results;
}
QList<SummaryMetrics>
MetricAggregator::getAllMeasuresFor(DateRange dr)
{
return getAllMeasuresFor(QDateTime(dr.from, QTime(0,0,0)), QDateTime(dr.to, QTime(23,59,59)));
}
QList<SummaryMetrics>
MetricAggregator::getAllMeasuresFor(QDateTime start, QDateTime end)
{

View File

@@ -48,7 +48,9 @@ class MetricAggregator : public QObject
DBAccess *db() { return dbaccess; }
SummaryMetrics getAllMetricsFor(QString filename); // for a single ride
QList<SummaryMetrics> getAllMetricsFor(QDateTime start, QDateTime end);
QList<SummaryMetrics> getAllMetricsFor(DateRange);
QList<SummaryMetrics> getAllMeasuresFor(QDateTime start, QDateTime end);
QList<SummaryMetrics> getAllMeasuresFor(DateRange);
SummaryMetrics getRideMetrics(QString filename);
void writeAsCSV(QString filename); // export all...

View File

@@ -32,8 +32,8 @@
#include <assert.h>
#include <math.h>
RideSummaryWindow::RideSummaryWindow(MainWindow *mainWindow) :
GcWindow(mainWindow), mainWindow(mainWindow)
RideSummaryWindow::RideSummaryWindow(MainWindow *mainWindow, bool ridesummary) :
GcWindow(mainWindow), mainWindow(mainWindow), ridesummary(ridesummary)
{
setInstanceName("Ride Summary Window");
setControls(NULL);
@@ -54,9 +54,15 @@ RideSummaryWindow::RideSummaryWindow(MainWindow *mainWindow) :
vlayout->addWidget(rideSummary);
connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideItemChanged()));
connect(mainWindow, SIGNAL(zonesChanged()), this, SLOT(refresh()));
connect(mainWindow, SIGNAL(intervalsChanged()), this, SLOT(refresh()));
if (ridesummary) {
connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideItemChanged()));
connect(mainWindow, SIGNAL(zonesChanged()), this, SLOT(refresh()));
connect(mainWindow, SIGNAL(intervalsChanged()), this, SLOT(refresh()));
} else {
connect(this, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateRangeChanged(DateRange)));
connect(mainWindow, SIGNAL(rideAdded(RideItem*)), this, SLOT(refresh()));
connect(mainWindow, SIGNAL(rideDeleted(RideItem*)), this, SLOT(refresh()));
}
setLayout(vlayout);
}
@@ -93,15 +99,27 @@ RideSummaryWindow::refresh()
{
// XXX: activeTab is never equaly to RideSummaryWindow right now because
// it's wrapped in the summarySplitter in MainWindow.
if (!myRideItem) {
if (ridesummary && !myRideItem) {
rideSummary->page()->mainFrame()->setHtml("");
return;
}
RideItem *rideItem = myRideItem;
setSubTitle(rideItem->dateTime.toString(tr("dddd MMMM d, yyyy, h:mm AP")));
if (ridesummary) {
RideItem *rideItem = myRideItem;
setSubTitle(rideItem->dateTime.toString(tr("dddd MMMM d, yyyy, h:mm AP")));
} else {
if (myDateRange.name != "") setSubTitle(myDateRange.name);
else {
setSubTitle(myDateRange.from.toString("dddd MMMM d") +
" - " +
myDateRange.to.toString("dddd MMMM d"));
}
}
rideSummary->page()->mainFrame()->setHtml(htmlSummary());
}
QString
RideSummaryWindow::htmlSummary() const
{
@@ -109,9 +127,11 @@ RideSummaryWindow::htmlSummary() const
RideItem *rideItem = myRideItem;
RideFile *ride = rideItem->ride();
QVariant unit = appsettings->value(this, GC_UNIT);
bool useMetricUnits = (unit.toString() == "Metric");
// ridefile read errors?
if (!ride) {
// ride summary and there were ridefile read errors?
if (ridesummary && !ride) {
summary = tr("<p>Couldn't read file \"");
summary += rideItem->fileName + "\":";
QListIterator<QString> i(rideItem->errors());
@@ -120,58 +140,48 @@ RideSummaryWindow::htmlSummary() const
return summary;
}
summary = ("<center><p><h3>" + tr("Device Type: ") + ride->deviceType() + "</h3>");
// always centered
summary = "<center>";
QVariant unit = appsettings->value(this, GC_UNIT);
// device summary for ride summary, otherwise how many activities?
if (ridesummary) summary += ("<p><h3>" + tr("Device Type: ") + ride->deviceType() + "</h3><p>");
else summary += ("<p><h3>" +
QString("%1").arg(data.count()) + (data.count() == 1 ? tr(" activity") : tr(" activities")) +
"</h3><p>");
summary += "<p>";
bool metricUnits = (unit.toString() == "Metric");
QStringList columnNames = QStringList() << "Totals" <<"Averages" <<"Maximums" <<"Metrics*";
QStringList totalColumn = QStringList()
// All the metrics we will display
static const QStringList columnNames = QStringList() << "Totals" <<"Averages" <<"Maximums" <<"Metrics*";
static const QStringList totalColumn = QStringList()
<< "workout_time"
<< "time_riding"
<< "total_distance"
<< "total_work"
<< "elevation_gain";
QStringList averageColumn = QStringList()
static QStringList averageColumn = QStringList() // not const as modified below..
<< "average_speed"
<< "average_power"
<< "average_hr"
<< "average_cad";
// show temp if it is available
if (ride->areDataPresent()->temp || ride->getTag("Temperature", "-") != "-") averageColumn << "average_temp";
QStringList maximumColumn = QStringList()
static QStringList maximumColumn = QStringList() // not const as modified below..
<< "max_speed"
<< "max_power"
<< "max_heartrate"
<< "max_cadence";
// show temp if it is available
if (ride->areDataPresent()->temp || ride->getTag("Temperature", "-") != "-") maximumColumn << "max_temp";
// show average and max temp if it is available (in ride summary mode)
if (ridesummary && (ride->areDataPresent()->temp || ride->getTag("Temperature", "-") != "-")) {
averageColumn << "average_temp";
maximumColumn << "max_temp";
}
// users determine the metrics to display
QString s = appsettings->value(this, GC_SETTINGS_SUMMARY_METRICS, GC_SETTINGS_SUMMARY_METRICS_DEFAULT).toString();
// in case they were set tand then unset
if (s == "") s = GC_SETTINGS_SUMMARY_METRICS_DEFAULT;
QStringList metricColumn = s.split(",");
/* ORIGINAL HARDCODED (AND NOW DEFAULT METRICS)
QStringList metricColumn = QStringList()
<< "skiba_xpower"
<< "skiba_relative_intensity"
<< "skiba_bike_score"
<< "daniels_points"
<< "daniels_equivalent_power"
<< "trimp_points"
<< "aerobic_decoupling";
*/
QStringList timeInZones = QStringList()
static const QStringList timeInZones = QStringList()
<< "time_in_zone_L1"
<< "time_in_zone_L2"
<< "time_in_zone_L3"
@@ -183,7 +193,7 @@ RideSummaryWindow::htmlSummary() const
<< "time_in_zone_L9"
<< "time_in_zone_L10";
QStringList timeInZonesHR = QStringList()
static const QStringList timeInZonesHR = QStringList()
<< "time_in_zone_H1"
<< "time_in_zone_H2"
<< "time_in_zone_H3"
@@ -195,35 +205,41 @@ RideSummaryWindow::htmlSummary() const
// Use pre-computed and saved metric values if the ride has not
// been edited. Otherwise we need to re-compute every time.
// this is only for ride summary, when showing for a date range
// we already have a summary metrics array
SummaryMetrics metrics;
RideMetricFactory &factory = RideMetricFactory::instance();
if (rideItem->isDirty()) {
// make a list if the metrics we want computed
// instead of calculating them all, just do the
// ones we display
QStringList worklist;
worklist += totalColumn;
worklist += averageColumn;
worklist += maximumColumn;
worklist += metricColumn;
worklist += timeInZones;
worklist += timeInZonesHR;
// go calculate them then...
QHash<QString, RideMetricPtr> computed = RideMetric::computeMetrics(mainWindow, ride, mainWindow->zones(), mainWindow->hrZones(), worklist);
for(int i = 0; i < worklist.count(); ++i) {
if (worklist[i] != "") {
RideMetricPtr m = computed.value(worklist[i]);
if (m) metrics.setForSymbol(worklist[i], m->value(true));
else metrics.setForSymbol(worklist[i], 0.00);
if (ridesummary) {
if (rideItem->isDirty()) {
// make a list if the metrics we want computed
// instead of calculating them all, just do the
// ones we display
QStringList worklist;
worklist += totalColumn;
worklist += averageColumn;
worklist += maximumColumn;
worklist += metricColumn;
worklist += timeInZones;
worklist += timeInZonesHR;
// go calculate them then...
QHash<QString, RideMetricPtr> computed = RideMetric::computeMetrics(mainWindow, ride, mainWindow->zones(), mainWindow->hrZones(), worklist);
for(int i = 0; i < worklist.count(); ++i) {
if (worklist[i] != "") {
RideMetricPtr m = computed.value(worklist[i]);
if (m) metrics.setForSymbol(worklist[i], m->value(true));
else metrics.setForSymbol(worklist[i], 0.00);
}
}
}
} else {
} else {
// just use the metricDB versions, nice 'n fast
metrics = mainWindow->metricDB->getRideMetrics(rideItem->fileName);
// just use the metricDB versions, nice 'n fast
metrics = mainWindow->metricDB->getRideMetrics(rideItem->fileName);
}
}
//
// 3 top columns - total, average, maximums and metrics for entire ride
//
@@ -257,19 +273,28 @@ RideSummaryWindow::htmlSummary() const
s = s.arg(m->name().replace(QRegExp(tr("^(Average|Max) ")), ""));
// Add units (if needed) and value (with right precision)
if (m->units(metricUnits) == "seconds") {
if (m->units(useMetricUnits) == "seconds") {
s = s.arg(""); // no units
s = s.arg(time_to_string(metrics.getForSymbol(symbol)));
// get the value - from metrics or from data array
if (ridesummary) s = s.arg(time_to_string(metrics.getForSymbol(symbol)));
else s = s.arg(SummaryMetrics::getAggregated(symbol, data, useMetricUnits));
} else {
if (m->units(metricUnits) != "") s = s.arg(" (" + m->units(metricUnits) + ")");
if (m->units(useMetricUnits) != "") s = s.arg(" (" + m->units(useMetricUnits) + ")");
else s = s.arg("");
// temperature is a special case, if it is not present fall back to metadata tag
// if that is not present then just display '-'
if ((symbol == "average_temp" || symbol == "max_temp") && metrics.getForSymbol(symbol) == RideFile::noTemp)
s = s.arg(ride->getTag("Temperature", "-"));
else
s = s.arg(metrics.getForSymbol(symbol) * (metricUnits ? 1 : m->conversion()) + (metricUnits ? 0 : m->conversionSum()), 0, 'f', m->precision());
else {
// get the value - from metrics or from data array
if (ridesummary) s = s.arg(metrics.getForSymbol(symbol) * (useMetricUnits ? 1 : m->conversion())
+ (useMetricUnits ? 0 : m->conversionSum()), 0, 'f', m->precision());
else s = s.arg(SummaryMetrics::getAggregated(symbol, data, useMetricUnits));
}
}
summary += s;
@@ -283,10 +308,14 @@ RideSummaryWindow::htmlSummary() const
//
if (rideItem->numZones() > 0) {
QVector<double> time_in_zone(rideItem->numZones());
for (int i = 0; i < rideItem->numZones(); ++i)
time_in_zone[i] = metrics.getForSymbol(timeInZones[i]);
for (int i = 0; i < rideItem->numZones(); ++i) {
// if using metrics or data
if (ridesummary) time_in_zone[i] = metrics.getForSymbol(timeInZones[i]);
else time_in_zone[i] = SummaryMetrics::getAggregated(timeInZones[i], data, useMetricUnits, true).toDouble();
}
summary += tr("<h3>Power Zones</h3>");
summary += mainWindow->zones()->summarize(rideItem->zoneRange(), time_in_zone);
summary += mainWindow->zones()->summarize(rideItem->zoneRange(), time_in_zone); //XXX aggregating?
}
//
@@ -294,84 +323,95 @@ RideSummaryWindow::htmlSummary() const
//
if (rideItem->numHrZones() > 0) {
QVector<double> time_in_zone(rideItem->numHrZones());
for (int i = 0; i < rideItem->numHrZones(); ++i)
time_in_zone[i] = metrics.getForSymbol(timeInZonesHR[i]);
for (int i = 0; i < rideItem->numHrZones(); ++i) {
// if using metrics or data
if (ridesummary) time_in_zone[i] = metrics.getForSymbol(timeInZonesHR[i]);
else time_in_zone[i] = SummaryMetrics::getAggregated(timeInZonesHR[i], data, useMetricUnits, true).toDouble();
}
summary += tr("<h3>Heart Rate Zones</h3>");
summary += mainWindow->hrZones()->summarize(rideItem->hrZoneRange(), time_in_zone);
summary += mainWindow->hrZones()->summarize(rideItem->hrZoneRange(), time_in_zone); //XXX aggregating
}
//
// Interval Summary (recalculated on every refresh since they are not cached at present)
//
if (ride->intervals().size() > 0) {
bool firstRow = true;
QString s;
if (appsettings->contains(GC_SETTINGS_INTERVAL_METRICS))
s = appsettings->value(this, GC_SETTINGS_INTERVAL_METRICS).toString();
else
s = GC_SETTINGS_INTERVAL_METRICS_DEFAULT;
QStringList intervalMetrics = s.split(",");
summary += "<p><h3>"+tr("Intervals")+"</h3>\n<p>\n";
summary += "<table align=\"center\" width=\"90%\" ";
summary += "cellspacing=0 border=0>";
bool even = false;
foreach (RideFileInterval interval, ride->intervals()) {
RideFile f(ride->startTime(), ride->recIntSecs());
for (int i = ride->intervalBegin(interval); i < ride->dataPoints().size(); ++i) {
const RideFilePoint *p = ride->dataPoints()[i];
if (p->secs >= interval.stop)
break;
f.appendPoint(p->secs, p->cad, p->hr, p->km, p->kph, p->nm,
p->watts, p->alt, p->lon, p->lat, p->headwind,
p->slope, p->temp, p->lrbalance, 0);
}
if (f.dataPoints().size() == 0) {
// Interval empty, do not compute any metrics
continue;
}
// Only get interval summary for a ride summary
if (ridesummary) {
QHash<QString,RideMetricPtr> metrics =
RideMetric::computeMetrics(mainWindow, &f, mainWindow->zones(), mainWindow->hrZones(), intervalMetrics);
if (firstRow) {
summary += "<tr>";
summary += "<td align=\"center\" valign=\"bottom\">Interval Name</td>";
//
// Interval Summary (recalculated on every refresh since they are not cached at present)
//
if (ride->intervals().size() > 0) {
bool firstRow = true;
QString s;
if (appsettings->contains(GC_SETTINGS_INTERVAL_METRICS))
s = appsettings->value(this, GC_SETTINGS_INTERVAL_METRICS).toString();
else
s = GC_SETTINGS_INTERVAL_METRICS_DEFAULT;
QStringList intervalMetrics = s.split(",");
summary += "<p><h3>"+tr("Intervals")+"</h3>\n<p>\n";
summary += "<table align=\"center\" width=\"90%\" ";
summary += "cellspacing=0 border=0>";
bool even = false;
foreach (RideFileInterval interval, ride->intervals()) {
RideFile f(ride->startTime(), ride->recIntSecs());
for (int i = ride->intervalBegin(interval); i < ride->dataPoints().size(); ++i) {
const RideFilePoint *p = ride->dataPoints()[i];
if (p->secs >= interval.stop)
break;
f.appendPoint(p->secs, p->cad, p->hr, p->km, p->kph, p->nm,
p->watts, p->alt, p->lon, p->lat, p->headwind,
p->slope, p->temp, p->lrbalance, 0);
}
if (f.dataPoints().size() == 0) {
// Interval empty, do not compute any metrics
continue;
}
QHash<QString,RideMetricPtr> metrics =
RideMetric::computeMetrics(mainWindow, &f, mainWindow->zones(), mainWindow->hrZones(), intervalMetrics);
if (firstRow) {
summary += "<tr>";
summary += "<td align=\"center\" valign=\"bottom\">Interval Name</td>";
foreach (QString symbol, intervalMetrics) {
RideMetricPtr m = metrics.value(symbol);
if (!m) continue;
summary += "<td align=\"center\" valign=\"bottom\">" + m->name();
if (m->units(useMetricUnits) == "seconds")
; // don't do anything
else if (m->units(useMetricUnits).size() > 0)
summary += " (" + m->units(useMetricUnits) + ")";
summary += "</td>";
}
summary += "</tr>";
firstRow = false;
}
if (even)
summary += "<tr>";
else {
QColor color = QApplication::palette().alternateBase().color();
color = QColor::fromHsv(color.hue(), color.saturation() * 2, color.value());
summary += "<tr bgcolor='" + color.name() + "'>";
}
even = !even;
summary += "<td align=\"center\">" + interval.name + "</td>";
foreach (QString symbol, intervalMetrics) {
RideMetricPtr m = metrics.value(symbol);
if (!m) continue;
summary += "<td align=\"center\" valign=\"bottom\">" + m->name();
if (m->units(metricUnits) == "seconds")
; // don't do anything
else if (m->units(metricUnits).size() > 0)
summary += " (" + m->units(metricUnits) + ")";
summary += "</td>";
QString s("<td align=\"center\">%1</td>");
if (m->units(useMetricUnits) == "seconds")
summary += s.arg(time_to_string(m->value(useMetricUnits)));
else
summary += s.arg(m->value(useMetricUnits), 0, 'f', m->precision());
}
summary += "</tr>";
firstRow = false;
}
if (even)
summary += "<tr>";
else {
QColor color = QApplication::palette().alternateBase().color();
color = QColor::fromHsv(color.hue(), color.saturation() * 2, color.value());
summary += "<tr bgcolor='" + color.name() + "'>";
}
even = !even;
summary += "<td align=\"center\">" + interval.name + "</td>";
foreach (QString symbol, intervalMetrics) {
RideMetricPtr m = metrics.value(symbol);
if (!m) continue;
QString s("<td align=\"center\">%1</td>");
if (m->units(metricUnits) == "seconds")
summary += s.arg(time_to_string(m->value(metricUnits)));
else
summary += s.arg(m->value(metricUnits), 0, 'f', m->precision());
}
summary += "</tr>";
summary += "</table>";
}
summary += "</table>";
}
if (!rideItem->errors().empty()) {
// sumarise errors reading file if it was a ride summary
if (ridesummary && !rideItem->errors().empty()) {
summary += tr("<p><h2>Errors reading file:</h2><ul>");
QStringListIterator i(rideItem->errors());
while(i.hasNext())
@@ -389,3 +429,14 @@ RideSummaryWindow::htmlSummary() const
return summary;
}
void RideSummaryWindow::dateRangeChanged(DateRange dr)
{
if (!amVisible()) return;
// range didnt change ignore it...
if (dr.from == current.from && dr.to == current.to) return;
else current = dr;
data = mainWindow->metricDB->getAllMetricsFor(myDateRange);
refresh();
}

View File

@@ -25,6 +25,8 @@
#include <QWebView>
#include <QWebFrame>
#include "SummaryMetrics.h"
class RideSummaryWindow : public GcWindow
{
@@ -34,12 +36,14 @@ class RideSummaryWindow : public GcWindow
public:
RideSummaryWindow(MainWindow *parent);
// two modes - summarise ride or summarise date range
RideSummaryWindow(MainWindow *parent, bool ridesummary = true);
protected slots:
void refresh();
void rideSelected();
void dateRangeChanged(DateRange);
void rideItemChanged();
void metadataChanged();
@@ -51,6 +55,10 @@ class RideSummaryWindow : public GcWindow
QWebView *rideSummary;
RideItem *_connected;
bool ridesummary; // do we summarise ride or daterange?
QList<SummaryMetrics> data; // when in date range mode
DateRange current;
};
#endif // _GC_RideSummaryWindow_h

View File

@@ -99,3 +99,68 @@ SummaryMetrics::getUnitsForSymbol(QString symbol, bool UseMetric) const
if (m) return m->units(UseMetric);
else return QString("units");
}
QString SummaryMetrics::getAggregated(QString name, const QList<SummaryMetrics> &results, bool useMetricUnits, bool nofmt)
{
// get the metric details, so we can convert etc
const RideMetric *metric = RideMetricFactory::instance().rideMetric(name);
if (!metric) return QString("%1 unknown").arg(name);
// what we will return
double rvalue = 0;
double rcount = 0; // using double to avoid rounding issues with int when dividing
// loop through and aggregate
foreach (SummaryMetrics rideMetrics, results) {
// get this value
double value = rideMetrics.getForSymbol(name);
double count = rideMetrics.getForSymbol("workout_time"); // for averaging
// check values are bounded, just in case
if (isnan(value) || isinf(value)) value = 0;
// imperial / metric conversion
if (useMetricUnits == false) {
value *= metric->conversion();
value += metric->conversionSum();
}
switch (metric->type()) {
case RideMetric::Total:
rvalue += value;
break;
case RideMetric::Average:
{
// average should be calculated taking into account
// the duration of the ride, otherwise high value but
// short rides will skew the overall average
rvalue += value*count;
rcount += count;
break;
}
case RideMetric::Peak:
{
if (value > rvalue) rvalue = value;
break;
}
}
}
// now compute the average
if (metric->type() == RideMetric::Average) {
if (rcount) rvalue = rvalue / rcount;
}
// Format appropriately
QString result;
if (metric->units(useMetricUnits) == "seconds") {
if (nofmt) result = QString("%1").arg(rvalue);
else result = time_to_string(rvalue);
} else result = QString("%1").arg(rvalue, 0, 'f', metric->precision());
return result;
}

View File

@@ -61,6 +61,9 @@ class SummaryMetrics
// get unit string to use for this symbol
QString getUnitsForSymbol(QString symbol, bool UseMetric) const;
// when passed a list of summary metrics and a name return aggregated value as a string
static QString getAggregated(QString name, const QList<SummaryMetrics> &results, bool useMetricUnits, bool nofmt = false);
QMap<QString, double> &values() { return value; }
QMap<QString, QString> &texts() { return text; }

View File

@@ -97,22 +97,25 @@ QDateTime convertToLocalTime(QString timestamp)
}
}
DateRange::DateRange(QDate from, QDate to) : QObject()
DateRange::DateRange(QDate from, QDate to, QString name) : QObject()
{
this->from=from;
this->to=to;
this->name=name;
}
DateRange::DateRange(const DateRange &other) : QObject()
{
from=other.from;
to=other.to;
name=other.name;
}
DateRange& DateRange::operator=(const DateRange &other)
{
from=other.from;
to=other.to;
name=other.name;
emit changed(from, to);
return *this;

View File

@@ -38,9 +38,10 @@ class DateRange : QObject
public:
DateRange(const DateRange& other);
DateRange(QDate from = QDate(), QDate to = QDate());
DateRange(QDate from = QDate(), QDate to = QDate(), QString name ="");
DateRange& operator=(const DateRange &);
QDate from, to;
QString name;
signals:
void changed(QDate from, QDate to);