/* * Copyright (c) 2009 Sean C. Rhea (srhea@srhea.net) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "WeeklySummaryWindow.h" #include "DaysScaleDraw.h" #include "MainWindow.h" #include "RideItem.h" #include "RideMetric.h" #include "Zones.h" #include #include #include #include #include WeeklySummaryWindow::WeeklySummaryWindow(bool useMetricUnits, MainWindow *mainWindow) : QWidget(mainWindow), mainWindow(mainWindow), useMetricUnits(useMetricUnits) { QGridLayout *glayout = new QGridLayout; // set up the weekly distance / duration plot: weeklyPlot = new QwtPlot(); weeklyPlot->enableAxis(QwtPlot::yRight, true); weeklyPlot->setAxisMaxMinor(QwtPlot::xBottom,0); weeklyPlot->setAxisScaleDraw(QwtPlot::xBottom, new DaysScaleDraw()); QFont weeklyPlotAxisFont = weeklyPlot->axisFont(QwtPlot::yLeft); weeklyPlotAxisFont.setPointSize(weeklyPlotAxisFont.pointSize() * 0.9f); weeklyPlot->setAxisFont(QwtPlot::xBottom, weeklyPlotAxisFont); weeklyPlot->setAxisFont(QwtPlot::yLeft, weeklyPlotAxisFont); weeklyPlot->setAxisFont(QwtPlot::yRight, weeklyPlotAxisFont); weeklyDistCurve = new QwtPlotCurve(); weeklyDistCurve->setStyle(QwtPlotCurve::Steps); QPen pen(Qt::SolidLine); weeklyDistCurve->setPen(pen); weeklyDistCurve->setBrush(Qt::red); weeklyDistCurve->setRenderHint(QwtPlotItem::RenderAntialiased); weeklyDistCurve->setCurveAttribute(QwtPlotCurve::Inverted, true); // inverted, right-to-left weeklyDistCurve->attach(weeklyPlot); weeklyDurationCurve = new QwtPlotCurve(); weeklyDurationCurve->setStyle(QwtPlotCurve::Steps); weeklyDurationCurve->setPen(pen); weeklyDurationCurve->setBrush(QColor(255,200,0,255)); weeklyDurationCurve->setRenderHint(QwtPlotItem::RenderAntialiased); weeklyDurationCurve->setCurveAttribute(QwtPlotCurve::Inverted, true); // inverted, right-to-left weeklyDurationCurve->setYAxis(QwtPlot::yRight); weeklyDurationCurve->attach(weeklyPlot); // set up the weekly bike score plot: weeklyBSPlot = new QwtPlot(); weeklyBSPlot->enableAxis(QwtPlot::yRight, true); weeklyBSPlot->setAxisMaxMinor(QwtPlot::xBottom,0); weeklyBSPlot->setAxisScaleDraw(QwtPlot::xBottom, new DaysScaleDraw()); QwtText textLabel = QwtText(); weeklyBSPlot->setAxisFont(QwtPlot::xBottom, weeklyPlotAxisFont); weeklyBSPlot->setAxisFont(QwtPlot::yLeft, weeklyPlotAxisFont); weeklyBSPlot->setAxisFont(QwtPlot::yRight, weeklyPlotAxisFont); weeklyBSCurve = new QwtPlotCurve(); weeklyBSCurve->setStyle(QwtPlotCurve::Steps); weeklyBSCurve->setPen(pen); weeklyBSCurve->setBrush(Qt::blue); weeklyBSCurve->setRenderHint(QwtPlotItem::RenderAntialiased); weeklyBSCurve->setCurveAttribute(QwtPlotCurve::Inverted, true); // inverted, right-to-left weeklyBSCurve->attach(weeklyBSPlot); weeklyRICurve = new QwtPlotCurve(); weeklyRICurve->setStyle(QwtPlotCurve::Steps); weeklyRICurve->setPen(pen); weeklyRICurve->setBrush(Qt::green); weeklyRICurve->setRenderHint(QwtPlotItem::RenderAntialiased); weeklyRICurve->setCurveAttribute(QwtPlotCurve::Inverted, true); // inverted, right-to-left weeklyRICurve->setYAxis(QwtPlot::yRight); weeklyRICurve->attach(weeklyBSPlot); // set baseline curves to obscure linewidth variations along baseline pen.setWidth(2); weeklyBaselineCurve = new QwtPlotCurve(); weeklyBaselineCurve->setPen(pen); weeklyBaselineCurve->attach(weeklyPlot); weeklyBSBaselineCurve = new QwtPlotCurve(); weeklyBSBaselineCurve->setPen(pen); weeklyBSBaselineCurve->attach(weeklyBSPlot); QwtPlotGrid *grid = new QwtPlotGrid(); grid->enableX(false); QPen gridPen; gridPen.setStyle(Qt::DotLine); grid->setPen(gridPen); grid->attach(weeklyPlot); QwtPlotGrid *grid1 = new QwtPlotGrid(); grid1->enableX(false); gridPen.setStyle(Qt::DotLine); grid1->setPen(gridPen); grid1->attach(weeklyBSPlot); weeklySummary = new QTextEdit; weeklySummary->setReadOnly(true); glayout->addWidget(weeklySummary,0,0,1,-1); // row, col, rowspan, colspan. -1 == fill to edge glayout->addWidget(weeklyPlot,1,0); glayout->addWidget(weeklyBSPlot,1,1); glayout->setRowStretch(0, 3); // stretch factor of summary glayout->setRowStretch(1, 2); // stretch factor of weekly plots glayout->setColumnStretch(0, 1); // stretch first column glayout->setColumnStretch(1, 1); // stretch second column glayout->setRowMinimumHeight(0, 180); // minimum height of weekly summary glayout->setRowMinimumHeight(1, 120); // minimum height of weekly plots setLayout(glayout); connect(mainWindow, SIGNAL(rideSelected()), this, SLOT(refresh())); connect(mainWindow, SIGNAL(zonesChanged()), this, SLOT(refresh())); } void WeeklySummaryWindow::refresh() { if (mainWindow->activeTab() != this) return; const RideItem *ride = mainWindow->rideItem(); if (!ride) return; const QTreeWidgetItem *allRides = mainWindow->allRideItems(); const Zones *zones = mainWindow->zones(); QDate wstart = ride->dateTime.date(); wstart = wstart.addDays(Qt::Monday - wstart.dayOfWeek()); assert(wstart.dayOfWeek() == Qt::Monday); QDate wend = wstart.addDays(7); const RideMetricFactory &factory = RideMetricFactory::instance(); QSharedPointer weeklySeconds(factory.newMetric("time_riding")); assert(weeklySeconds); QSharedPointer weeklyDistance(factory.newMetric("total_distance")); assert(weeklyDistance); QSharedPointer weeklyWork(factory.newMetric("total_work")); assert(weeklyWork); QSharedPointer weeklyBS(factory.newMetric("skiba_bike_score")); assert(weeklyBS); QSharedPointer weeklyRelIntensity(factory.newMetric("skiba_relative_intensity")); assert(weeklyRelIntensity); QSharedPointer weeklyCS(factory.newMetric("daniels_points")); assert(weeklyCS); QSharedPointer dailySeconds[7]; QSharedPointer dailyDistance[7]; QSharedPointer dailyBS[7]; QSharedPointer dailyRI[7]; QSharedPointer dailyW[7]; QSharedPointer dailyXP[7]; for (int i = 0; i < 7; i++) { dailySeconds[i] = QSharedPointer(factory.newMetric("time_riding")); assert(dailySeconds[i]); dailyDistance[i] = QSharedPointer(factory.newMetric("total_distance")); assert(dailyDistance[i]); dailyBS[i] = QSharedPointer(factory.newMetric("skiba_bike_score")); assert(dailyBS[i]); dailyRI[i] = QSharedPointer(factory.newMetric("skiba_relative_intensity")); assert(dailyRI[i]); dailyW[i] = QSharedPointer(factory.newMetric("total_work")); assert(dailyW[i]); dailyXP[i] = QSharedPointer(factory.newMetric("skiba_xpower")); assert(dailyXP[i]); } int zone_range = -1; QVector time_in_zone; int num_zones = -1; bool zones_ok = true; for (int i = 0; i < allRides->childCount(); ++i) { RideItem *item = (RideItem*) allRides->child(i); int day; if ( (item->type() == RIDE_TYPE) && ((day = wstart.daysTo(item->dateTime.date())) >= 0) && (day < 7) ) { item->computeMetrics(); // generates item->ride if (!item->ride()) continue; RideMetricPtr m; if ((m = item->metrics.value(weeklySeconds->symbol()))) { weeklySeconds->aggregateWith(*m); dailySeconds[day]->aggregateWith(*m); } if ((m = item->metrics.value(weeklyDistance->symbol()))) { weeklyDistance->aggregateWith(*m); dailyDistance[day]->aggregateWith(*m); } if ((m = item->metrics.value(weeklyWork->symbol()))) { weeklyWork->aggregateWith(*m); dailyW[day]->aggregateWith(*m); } if ((m = item->metrics.value(weeklyCS->symbol()))) weeklyCS->aggregateWith(*m); if ((m = item->metrics.value(weeklyBS->symbol()))) { weeklyBS->aggregateWith(*m); dailyBS[day]->aggregateWith(*m); } if ((m = item->metrics.value(weeklyRelIntensity->symbol()))) { weeklyRelIntensity->aggregateWith(*m); dailyRI[day]->aggregateWith(*m); } if ((m = item->metrics.value("skiba_xpower"))) dailyXP[day]->aggregateWith(*m); // compute time in zones if (zones) { if (zone_range == -1) { zone_range = item->zoneRange(); num_zones = item->numZones(); time_in_zone.clear(); time_in_zone.resize(num_zones); } else if (item->zoneRange() != zone_range) { zones_ok = false; } if (zone_range != -1) { for (int j = 0; j < num_zones; ++j) time_in_zone[j] += item->timeInZone(j); } } } } int seconds = ((int) round(weeklySeconds->value(true))); int minutes = seconds / 60; seconds %= 60; int hours = minutes / 60; minutes %= 60; QString summary; summary = tr( "
" "

Week of %1 through %2

" "

Summary

" "

" "" "" " " "" " " "" " " "" " " ) .arg(wstart.toString(tr("MM/dd/yyyy"))) .arg(wstart.addDays(6).toString(tr("MM/dd/yyyy"))) .arg(hours) .arg(minutes, 2, 10, QLatin1Char('0')) .arg(seconds, 2, 10, QLatin1Char('0')) .arg(useMetricUnits ? "km" : "miles") .arg(weeklyDistance->value(useMetricUnits), 0, 'f', 1) .arg((unsigned) round(weeklyWork->value(useMetricUnits))) .arg((unsigned) round(weeklyWork->value(useMetricUnits) / 7)); double weeklyBSValue = weeklyBS->value(useMetricUnits); bool useBikeScore = (zone_range != -1) && (weeklyBSValue > 0); if (zone_range != -1) { if (useBikeScore) summary += tr( "" " " "" " " "" " " ) .arg((unsigned) round(weeklyBSValue)) .arg(weeklyCS->value(useMetricUnits), 0, 'f', 1) .arg(weeklyRelIntensity->value(useMetricUnits), 0, 'f', 3); summary += tr( "
Total time riding:%3:%4:%5
Total distance (%6):%7
Total work (kJ):%8
Daily Average work (kJ):%9
Total BikeScore:%1
Total Daniels Points:%2
Net Relative Intensity:%3
" "

Power Zones

" ); if (!zones_ok) summary += "Error: Week spans more than one zone range."; else { summary += zones->summarize(zone_range, time_in_zone); } } summary += "
"; // set the axis labels of the weekly plots QwtText textLabel = QwtText(useMetricUnits ? "km" : "miles"); QFont weeklyPlotAxisTitleFont = textLabel.font(); weeklyPlotAxisTitleFont.setPointSize(10); weeklyPlotAxisTitleFont.setBold(true); textLabel.setFont(weeklyPlotAxisTitleFont); weeklyPlot->setAxisTitle(QwtPlot::yLeft, textLabel); textLabel.setText("Minutes"); weeklyPlot->setAxisTitle(QwtPlot::yRight, textLabel); textLabel.setText(useBikeScore ? "BikeScore" : "kJoules"); weeklyBSPlot->setAxisTitle(QwtPlot::yLeft, textLabel); textLabel.setText(useBikeScore ? "Intensity" : "xPower"); weeklyBSPlot->setAxisTitle(QwtPlot::yRight, textLabel); // for the daily distance/duration and bikescore plots: // first point: establish zero position // points 2N, 2N+1: Nth day (N from 1 to 7), up then down // 16th point: move to draw baseline off right of plot double xdist[16]; double xdur[16]; double xbsorw[16]; double xriorxp[16]; double ydist[16]; // daily distance double ydur[16]; // daily total time double ybsorw[16]; // daily minutes double yriorxp[16]; // daily relative intensity // data for a "baseline" curve to draw a baseline double xbaseline[] = {0, 8}; double ybaseline[] = {0, 0}; weeklyBaselineCurve->setData(xbaseline, ybaseline, 2); weeklyBSBaselineCurve->setData(xbaseline, ybaseline, 2); const double bar_width = 0.3; int i = 0; xdist[i] = xdur[i] = xbsorw[i] = xriorxp[i] = 0; ydist[i] = ydur[i] = ybsorw[i] = yriorxp[i] = 0; for(int day = 0; day < 7; day++){ double x; i++; xdist[i] = x = day + 1 - bar_width; xdist[i + 1] = x += bar_width; xdur[i] = x; xdur[i + 1] = x += bar_width; xbsorw[i] = x = day + 1 - bar_width; xbsorw[i + 1] = x += bar_width; xriorxp[i] = x; xriorxp[i + 1] = x += bar_width; ydist[i] = dailyDistance[day]->value(useMetricUnits); ydur[i] = dailySeconds[day]->value(useMetricUnits) / 60; ybsorw[i] = useBikeScore ? dailyBS[day]->value(useMetricUnits) : dailyW[day]->value(useMetricUnits) / 1000; yriorxp[i] = useBikeScore ? dailyRI[day]->value(useMetricUnits) : dailyXP[day]->value(useMetricUnits); i++; ydist[i] = 0; ydur[i] = 0; ybsorw[i] = 0; yriorxp[i] = 0; } // sweep a baseline off the right of the plot i++; xdist[i] = xdur[i] = xbsorw[i] = xriorxp[i] = 8; ydist[i] = ydur[i] = ybsorw[i] = yriorxp[i] = 0; // Distance/Duration plot: weeklyDistCurve->setData(xdist, ydist, 16); weeklyPlot->setAxisScale(QwtPlot::yLeft, 0, weeklyDistCurve->maxYValue()*1.1, 0); weeklyPlot->setAxisScale(QwtPlot::xBottom, 0.5, 7.5, 0); weeklyPlot->setAxisTitle(QwtPlot::yLeft, useMetricUnits ? "Kilometers" : "Miles"); weeklyDurationCurve->setData(xdur, ydur, 16); weeklyPlot->setAxisScale(QwtPlot::yRight, 0, weeklyDurationCurve->maxYValue()*1.1, 0); weeklyPlot->replot(); // BikeScore/Relative Intensity plot weeklyBSCurve->setData(xbsorw, ybsorw, 16); weeklyBSPlot->setAxisScale(QwtPlot::yLeft, 0, weeklyBSCurve->maxYValue()*1.1, 0); weeklyBSPlot->setAxisScale(QwtPlot::xBottom, 0.5, 7.5, 0); // set axis minimum for relative intensity double RImin = -1; for(int i = 1; i < 16; i += 2) if (yriorxp[i] > 0 && ((RImin < 0) || (yriorxp[i] < RImin))) RImin = yriorxp[i]; if (RImin < 0) RImin = 0; RImin *= 0.8; for (int i = 0; i < 16; i ++) if (yriorxp[i] < RImin) yriorxp[i] = RImin; weeklyRICurve->setBaseline(RImin); weeklyRICurve->setData(xriorxp, yriorxp, 16); weeklyBSPlot->setAxisScale(QwtPlot::yRight, RImin, weeklyRICurve->maxYValue()*1.1, 0); weeklyBSPlot->replot(); weeklySummary->setHtml(summary); }