From 6ac75fda3ae10cfcd86d552bfe850db0ac0f1035 Mon Sep 17 00:00:00 2001 From: Damien Grauser Date: Fri, 7 Jan 2011 21:18:45 +0000 Subject: [PATCH] SRMWIN Style HrPw Plot Added a plot of heartrate to power showing histograms of power on y-axis, heartrate on the x-axis and overlaid hr/power scatter. --- src/GcWindowRegistry.cpp | 3 + src/GcWindowRegistry.h | 1 + src/HrPwPlot.cpp | 700 +++++++++++++++++++++++++++++++++++++++ src/HrPwPlot.h | 116 +++++++ src/HrPwWindow.cpp | 510 ++++++++++++++++++++++++++++ src/HrPwWindow.h | 106 ++++++ src/MainWindow.cpp | 6 + src/MainWindow.h | 3 + src/SmallPlot.cpp | 293 ++++++++++++++++ src/SmallPlot.h | 70 ++++ src/src.pro | 8 +- 11 files changed, 1815 insertions(+), 1 deletion(-) create mode 100644 src/HrPwPlot.cpp create mode 100644 src/HrPwPlot.h create mode 100644 src/HrPwWindow.cpp create mode 100644 src/HrPwWindow.h create mode 100644 src/SmallPlot.cpp create mode 100644 src/SmallPlot.h diff --git a/src/GcWindowRegistry.cpp b/src/GcWindowRegistry.cpp index 40994cb66..d04f5d79c 100644 --- a/src/GcWindowRegistry.cpp +++ b/src/GcWindowRegistry.cpp @@ -34,6 +34,7 @@ #endif #include "PerformanceManagerWindow.h" #include "PfPvWindow.h" +#include "HrPwWindow.h" #include "RaceWindow.h" // XXX not done #include "RealtimeWindow.h" #include "RideEditor.h" @@ -56,6 +57,7 @@ GcWindowRegistry GcWindows[] = { { "3d", GcWindowTypes::Model }, { "Performance Manager", GcWindowTypes::PerformanceManager }, { "PfPv", GcWindowTypes::PfPv }, + { "HrPw", GcWindowTypes::HrPw }, { "Training", GcWindowTypes::Training }, { "Ride Editor", GcWindowTypes::RideEditor }, { "Ride Summary", GcWindowTypes::RideSummary }, @@ -90,6 +92,7 @@ GcWindowRegistry::newGcWindow(GcWinID id, MainWindow *main) //XXX mainWindow wil #endif case GcWindowTypes::PerformanceManager: returning = new PerformanceManagerWindow(main); break; case GcWindowTypes::PfPv: returning = new PfPvWindow(main); break; + case GcWindowTypes::HrPw: returning = new HrPwWindow(main); break; case GcWindowTypes::Training: returning = new TrainWindow(main, main->home); break; case GcWindowTypes::RideEditor: returning = new RideEditor(main); break; case GcWindowTypes::RideSummary: returning = new RideSummaryWindow(main); break; diff --git a/src/GcWindowRegistry.h b/src/GcWindowRegistry.h index 1542aea88..aedec0d1e 100644 --- a/src/GcWindowRegistry.h +++ b/src/GcWindowRegistry.h @@ -46,6 +46,7 @@ enum gcwinid { Train =17, TreeMap =18, WeeklySummary =19, + HrPw =20, }; }; typedef enum GcWindowTypes::gcwinid GcWinID; diff --git a/src/HrPwPlot.cpp b/src/HrPwPlot.cpp new file mode 100644 index 000000000..663726f38 --- /dev/null +++ b/src/HrPwPlot.cpp @@ -0,0 +1,700 @@ +/* + * Copyright (c) 2011 Damien Grauser + * + * 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 "HrPwPlot.h" +#include "MainWindow.h" +#include "HrPwWindow.h" +#include "RideFile.h" +#include "RideItem.h" +#include "Zones.h" +#include "Settings.h" +#include "Colors.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +static const inline double +max(double a, double b) { if (a > b) return a; else return b; } + +HrPwPlot::HrPwPlot(MainWindow *mainWindow, HrPwWindow *hrPwWindow) : + QwtPlot(hrPwWindow), + hrPwWindow(hrPwWindow), + mainWindow(mainWindow), + bg(NULL), smooth(240), hrMin(50), + settings(GC_SETTINGS_CO, GC_SETTINGS_APP), + unit(settings.value(GC_UNIT)) +{ + setCanvasBackground(Qt::white); + setXTitle(); // Power (Watts) + + // Heart Rate Curve + + hrCurves.resize(36); + for (int i = 0; i < 36; ++i) { + hrCurves[i] = new QwtPlotCurve; + hrCurves[i]->attach(this); + } + + // Linear Regression Curve + regCurve = new QwtPlotCurve("reg"); + regCurve->setPen(QPen(GColor(CPLOTMARKER))); + regCurve->attach(this); + + // Power distribution + wattsStepCurve = new QwtPlotCurve("Power"); + wattsStepCurve->setStyle(QwtPlotCurve::Steps); + wattsStepCurve->setRenderHint(QwtPlotItem::RenderAntialiased); + QColor wattsColor = QColor(200,200,255); + QColor wattsColor2 = QColor(100,100,255); + wattsStepCurve->setPen(QPen(wattsColor2)); + wattsStepCurve->setBrush(QBrush(wattsColor)); + + wattsStepCurve->attach(this); + + // Hr distribution + hrStepCurve = new QwtPlotCurve("Hr"); + hrStepCurve->setStyle(QwtPlotCurve::Steps); + hrStepCurve->setRenderHint(QwtPlotItem::RenderAntialiased); + QColor hrColor = QColor(255,200,200); + QColor hrColor2 = QColor(255,100,100); + hrStepCurve->setPen(QPen(hrColor2)); + hrStepCurve->setBrush(QBrush(hrColor)); + hrStepCurve->attach(this); + + // Grid + grid = new QwtPlotGrid(); + grid->enableX(false); + QPen gridPen; + gridPen.setStyle(Qt::DotLine); + gridPen.setColor(GColor(CPLOTGRID)); + grid->setPen(gridPen); + grid->attach(this); + + + // axis markers + r_mrk1 = new QwtPlotMarker; + r_mrk2 = new QwtPlotMarker; + r_mrk1->attach(this); + r_mrk2->attach(this); + + zoneLabels = QList ::QList(); + shade_zones = true; +} + +struct DataPoint { + double time, hr, watts; + int inter; + DataPoint(double t, double h, double w, int i) : + time(t), hr(h), watts(w), inter(i) {} +}; + +void +HrPwPlot::setAxisTitle(int axis, QString label) +{ + // setup the default fonts + QFont stGiles; // hoho - Chart Font St. Giles ... ok you have to be British to get this joke + stGiles.fromString(appsettings->value(this, GC_FONT_CHARTLABELS, QFont().toString()).toString()); + stGiles.setPointSize(appsettings->value(NULL, GC_FONT_CHARTLABELS_SIZE, 8).toInt()); + + QwtText title(label); + title.setFont(stGiles); + QwtPlot::setAxisFont(axis, stGiles); + QwtPlot::setAxisTitle(axis, title); +} +void +HrPwPlot::recalc() +{ + if (timeArray.count() == 0) + return; + + int rideTimeSecs = (int) ceil(timeArray[arrayLength - 1]); + if (rideTimeSecs > 7*24*60*60) { + return; + } + + // Find Hr Delay + //int delayori = findDelay(wattsArray, hrArray, rideTimeSecs/5); + int delay = 0; + + + // ------ smoothing ----- + // ---------------------- + double totalWatts = 0.0; + double totalHr = 0.0; + QList list; + int i = 0; + QVector smoothWatts(rideTimeSecs + 1); + QVector smoothHr(rideTimeSecs + 1); + QVector smoothTime(rideTimeSecs + 1); + int decal=0; + + for (int secs = 0; ((secs < smooth) && (secs < rideTimeSecs)); ++secs) { + smoothWatts[secs] = 0.0; + smoothHr[secs] = 0.0; + } + + //int interval = 0; + + for (int secs = smooth; secs <= rideTimeSecs-delay; ++secs) { + while ((i < arrayLength) && (timeArray[i] <= secs)) { + DataPoint *dp = + new DataPoint(timeArray[i], hrArray[i+delay], wattsArray[i], interArray[i]); + + totalWatts += wattsArray[i]; + totalHr += hrArray[i+delay]; + list.append(dp); + + ++i; + } + while (!list.empty() && (list.front()->time < secs - smooth)) { + DataPoint *dp = list.front(); + list.removeFirst(); + totalWatts -= dp->watts; + totalHr -= dp->hr; + delete dp; + } + if (list.empty()) { + ++decal; + } + else { + smoothWatts[secs-decal] = totalWatts / list.size(); + smoothHr[secs-decal] = totalHr / list.size(); + // Utiliser interval du fichier + //if (smooth/list.size()>0) + // interval = smooth/list.size(); + } + smoothTime[secs] = secs / 60.0; + } + + rideTimeSecs = rideTimeSecs-delay-decal; + + // Find Hr Delay + delay = hrPwWindow->findDelay(smoothWatts, smoothHr, rideTimeSecs); + //delayori = delay; + + //double rpente = pente(smoothWatts, smoothHr, rideTimeSecs); + //double rordonnee = ordonnee(smoothWatts, smoothHr, rideTimeSecs); + + // Applique le delai + QVector clipWatts(rideTimeSecs-delay); + QVector clipHr(rideTimeSecs-delay); + + for (int secs = 0; secs < rideTimeSecs-delay; ++secs) { + clipWatts[secs] = 0.0; + clipHr[secs] = 0.0; + } + + decal = 0; + for (int secs = 0; secs < rideTimeSecs-delay; ++secs) { + if (smoothHr[secs+delay]>= 50 && smoothWatts[secs]>= 80 && smoothWatts[secs]<500) { + clipWatts[secs-decal] = smoothWatts[secs]; + clipHr[secs-decal] = smoothHr[secs+delay]; + } + else + decal ++; + } + rideTimeSecs = rideTimeSecs-delay-decal; + + // ----- limit points --- + // ---------------------- + int intpoints = 10;//(int)floor(rideTimeSecs/1000); + int nbpoints = (int)floor(rideTimeSecs/intpoints); + + QVector finalWatts(nbpoints); + QVector finalHr(nbpoints); + + decal = 0; + + for (int secs = 0; secs < nbpoints; ++secs) { + finalWatts[secs-decal] = clipWatts[secs*intpoints]; + finalHr[secs-decal] = clipHr[secs*intpoints]; + } + nbpoints = nbpoints-decal; + + + int nbpoints2 = (int)floor(nbpoints/36)+2; + + double *finalWattsArray[36]; + double *finalHrArray[36]; + + for (int i = 0; i < 36; ++i) { + finalWattsArray[i]= new double[nbpoints2]; + finalHrArray[i]= new double[nbpoints2]; + } + + for (int secs = 0; secs < nbpoints; ++secs) { + for (int i = 0; i < 36; ++i) { + if (secs >= i*nbpoints2 && secs< (i+1)*nbpoints2) { + finalWattsArray[i][secs-i*nbpoints2] = finalWatts[secs-i]; + finalHrArray[i][secs-i*nbpoints2] = finalHr[secs-i]; + } + } + } + + + // Calcul corr + double maxr = hrPwWindow->corr(smoothWatts, smoothHr, rideTimeSecs); + + for (int i = 0; i < 36; ++i) { + + if (nbpoints-i*nbpoints2>0) { + + hrCurves[i]->setData(finalWattsArray[i], finalHrArray[i], (nbpoints-i*nbpoints2setVisible(true); + } else + hrCurves[i]->setVisible(false); + } + + + setAxisScale(xBottom, 0.0, 500); + + setYMax(); + refreshZoneLabels(); + + QString labelp; + double rpente = hrPwWindow->pente(finalWatts, finalHr, nbpoints); + labelp.setNum(rpente); + QString labelo; + double rordonnee = hrPwWindow->ordonnee(finalWatts, finalHr, nbpoints); + labelo.setNum(rordonnee); + + QString labelr; + labelr.setNum(maxr); + QString labeldelay; + labeldelay.setNum(delay); + QwtText textr = QwtText(labelp+"*x+"+labelo+" : R "+labelr+" ("+labeldelay+")"); + textr.setFont(QFont("Helvetica", 10, QFont::Bold)); + textr.setColor(Qt::black); + + r_mrk1->setValue(0,0); + r_mrk1->setLineStyle(QwtPlotMarker::VLine); + r_mrk1->setLabelAlignment(Qt::AlignRight | Qt::AlignTop); + r_mrk1->setLinePen(QPen(Qt::black, 0, Qt::DashDotLine)); + double moyennewatt = hrPwWindow->moyenne(smoothWatts, rideTimeSecs); + r_mrk1->setValue(moyennewatt, 0.0); + r_mrk1->setLabel(textr); + + r_mrk2->setValue(0,0); + r_mrk2->setLineStyle(QwtPlotMarker::HLine); + r_mrk2->setLabelAlignment(Qt::AlignRight | Qt::AlignTop); + r_mrk2->setLinePen(QPen(Qt::black, 0, Qt::DashDotLine)); + double moyennehr = hrPwWindow->moyenne(smoothHr, rideTimeSecs); + r_mrk2->setValue(0.0,moyennehr); + + addWattStepCurve(finalWatts, nbpoints); + addHrStepCurve(finalHr, nbpoints); + + addRegLinCurve(finalHr, finalWatts, nbpoints); + + setJoinLine(joinLine); + replot(); +} + +void +HrPwPlot::setYMax() +{ + double ymax = 0; + QString ylabel = ""; + for (int i = 0; i < 36; ++i) { + if (hrCurves[i]->isVisible()) { + ymax = max(ymax, hrCurves[i]->maxYValue()); + //ylabel += QString((ylabel == "") ? "" : " / ") + "BPM"; + } + } + setAxisScale(yLeft, hrMin, ymax * 1.2); + setAxisTitle(yLeft, tr("Heart Rate(BPM)")); +} + +void +HrPwPlot::addWattStepCurve(QVector &finalWatts, int nbpoints) +{ + QMap powerHist; + + for (int h=0; h< nbpoints; ++h) { + if (powerHist.contains(finalWatts[h])) + powerHist[finalWatts[h]] += 1; + else + powerHist[finalWatts[h]] = 1; + } + int maxPower = 500; + double *array = new double[maxPower]; + + for (int i = 0; i < maxPower; ++i) + array[i] = 0.0; + + QMapIterator k(powerHist); + while (k.hasNext()) { + k.next(); + array[(int) round(k.key())] += k.value(); + } + + int nbSteps = (int) ceil((maxPower - 1) / 10); + QVector smoothWattsStep(nbSteps+1); + QVector smoothTimeStep(nbSteps+1); + + int t; + for (t = 1; t < nbSteps; ++t) { + int low = t * 10; + int high = low + 10; + + smoothWattsStep[t] = low; + smoothTimeStep[t] = hrMin; + while (low < high) { + smoothTimeStep[t] += array[low++]/ nbpoints * 300; + } + } + smoothTimeStep[t] = 0.0; + smoothWattsStep[t] = t * 10; + + wattsStepCurve->setData(smoothWattsStep.data(), smoothTimeStep.data(), nbSteps+1); +} + +void +HrPwPlot::addHrStepCurve(QVector &finalHr, int nbpoints) +{ + QMap hrHist; + for (int h=0; h< nbpoints; ++h) { + if (hrHist.contains(finalHr[h])) + hrHist[finalHr[h]] += 1; + else + hrHist[finalHr[h]] = 1; + } + int maxHr = 220; + + double *array = new double[maxHr]; + for (int i = 0; i < maxHr; ++i) + array[i] = 0.0; + QMapIterator l(hrHist); + while (l.hasNext()) { + l.next(); + array[(int) round(l.key())] += l.value(); + } + + + int nbSteps = (int) ceil((maxHr - 1) / 2); + QVector smoothHrStep(nbSteps+1); + QVector smoothTimeStep2(nbSteps+1); + + int t; + for (t = 1; t < nbSteps; ++t) { + int low = t * 2; + int high = low + 2; + + smoothHrStep[t] = low; + smoothTimeStep2[t] = 0.0; + while (low < high) { + smoothTimeStep2[t] += array[low++]/ nbpoints * 500; + } + } + smoothTimeStep2[t] = 0.0; + smoothHrStep[t] = t * 2; + + hrStepCurve->setData(smoothTimeStep2.data(), smoothHrStep.data(), nbSteps+1); +} + +void +HrPwPlot::addRegLinCurve(QVector &finalHr, QVector &finalWatts, int nbpoints) +{ + double regWatts[] = {0, 0}; + double regHr[] = {0, 500}; + double rpente = hrPwWindow->pente(finalWatts, finalHr, nbpoints); + double rordonnee = hrPwWindow->ordonnee(finalWatts, finalHr, nbpoints); + regWatts[0] = regHr[0]*rpente+rordonnee; + regWatts[1] = regHr[1]*rpente+rordonnee; + + regCurve->setData(regHr, regWatts, 2); +} + +void +HrPwPlot::setXTitle() +{ + setAxisTitle(xBottom, tr("Power (Watts)")); +} + +void +HrPwPlot::setDataFromRide(RideItem *_rideItem) +{ + rideItem = _rideItem; + + // ignore null / bad rides + if (!_rideItem || !_rideItem->ride()) return; + + RideFile *ride = rideItem->ride(); + + const RideFileDataPresent *dataPresent = ride->areDataPresent(); + int npoints = ride->dataPoints().size(); + + if (dataPresent->watts && dataPresent->hr) { + wattsArray.resize(npoints); + hrArray.resize(npoints); + timeArray.resize(npoints); + interArray.resize(npoints); + + arrayLength = 0; + //QListIterator i(ride->dataPoints()); + //while (i.hasNext()) { + foreach (const RideFilePoint *point, ride->dataPoints()) { + //RideFilePoint *point = i.next(); + if (!timeArray.empty()) + timeArray[arrayLength] = point->secs; + if (!wattsArray.empty()) + wattsArray[arrayLength] = max(0, point->watts); + if (!hrArray.empty()) + hrArray[arrayLength] = max(0, point->hr); + if (!interArray.empty()) + interArray[arrayLength] = point->interval; + ++arrayLength; + } + + + recalc(); + } +} + +void +HrPwPlot::setJoinLine(bool value) +{ + + joinLine = value; + + for (int i = 0; i < 36; ++i) { + QColor color = QColor(255,255,255); + color.setHsv(60+i*(360/36), 255,255,255); + if (value) { + QwtSymbol sym; + sym.setStyle(QwtSymbol::NoSymbol); + + QPen pen = QPen(color); + pen.setWidth(1); + hrCurves[i]->setPen(pen); + hrCurves[i]->setStyle(QwtPlotCurve::Lines); + hrCurves[i]->setSymbol(sym); + } else { + QwtSymbol sym; + sym.setStyle(QwtSymbol::Ellipse); + sym.setSize(5); + sym.setPen(QPen(color)); + sym.setBrush(QBrush(color)); + hrCurves[i]->setPen(Qt::NoPen); + hrCurves[i]->setStyle(QwtPlotCurve::Dots); + hrCurves[i]->setSymbol(sym); + } + //hrCurves[i].setRenderHint(QwtPlotItem::RenderAntialiased); + } + +} + +void +HrPwPlot::pointHover(QwtPlotCurve *curve, int index) +{ + if (index >= 0) { + + double yvalue = curve->y(index); + double xvalue = curve->x(index); + + // output the tooltip + QString text = QString("%1 %2\n%3 %4") + .arg(yvalue, 0, 'f', 0) + .arg(this->axisTitle(curve->yAxis()).text()) + .arg(xvalue, 0, 'f', 2) + .arg(this->axisTitle(curve->xAxis()).text()); + + // set that text up + tooltip->setText(text); + } else { + tooltip->setText(""); + } +} + +/*---------------------------------------------------------------------- + * Draw Power Zone Shading on Background (here to end of source file) + *--------------------------------------------------------------------*/ +class HrPwPlotBackground: public QwtPlotItem +{ + private: + HrPwPlot *parent; + + public: + HrPwPlotBackground(HrPwPlot *_parent) { + setZ(0.0); + parent = _parent; + } + + virtual int rtti() const { + return QwtPlotItem::Rtti_PlotUserItem; + } + + virtual void draw(QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &, + const QRect &rect) const { + + RideItem *rideItem = parent->rideItem; + + if (! rideItem) + return; + + const Zones *zones = rideItem->zones; + int zone_range = rideItem->zoneRange(); + + if (parent->isShadeZones() && zones && (zone_range >= 0)) { + QList zone_lows = zones->getZoneLows(zone_range); + int num_zones = zone_lows.size(); + if (num_zones > 0) { + for (int z = 0; z < num_zones; z ++) { + QRect r = rect; + + QColor shading_color = zoneColor(z, num_zones); + shading_color.setHsv( + shading_color.hue(), + shading_color.saturation() / 4, + shading_color.value() + ); + r.setLeft(xMap.transform(zone_lows[z])); + if (z + 1 < num_zones) + r.setRight(xMap.transform(zone_lows[z + 1])); + if (r.left() <= r.right()) + painter->fillRect(r, shading_color); + } + } + } + } +}; + +// Zone labels are drawn if power zone bands are enabled, automatically +// at the center of the plot +class HrPwPlotZoneLabel: public QwtPlotItem +{ + private: + HrPwPlot *parent; + int zone_number; + double watts; + QwtText text; + + public: + HrPwPlotZoneLabel(HrPwPlot *_parent, int _zone_number) { + parent = _parent; + zone_number = _zone_number; + + RideItem *rideItem = parent->rideItem; + + + if (! rideItem) + return; + + const Zones *zones = rideItem->zones; + int zone_range = rideItem->zoneRange(); + + // create new zone labels if we're shading + if (parent->isShadeZones() && zones && (zone_range >= 0)) { + QList zone_lows = zones->getZoneLows(zone_range); + QList zone_names = zones->getZoneNames(zone_range); + int num_zones = zone_lows.size(); + assert(zone_names.size() == num_zones); + if (zone_number < num_zones) { + watts = + ( + (zone_number + 1 < num_zones) ? + 0.5 * (zone_lows[zone_number] + zone_lows[zone_number + 1]) : + ( + (zone_number > 0) ? + (1.5 * zone_lows[zone_number] - 0.5 * zone_lows[zone_number - 1]) : + 2.0 * zone_lows[zone_number] + ) + ); + + text = QwtText(zone_names[zone_number]); + text.setFont(QFont("Helvetica",24, QFont::Bold)); + QColor text_color = zoneColor(zone_number, num_zones); + text_color.setAlpha(64); + text.setColor(text_color); + } + } + + setZ(1.0 + zone_number / 100.0); + } + + virtual int rtti() const { + return QwtPlotItem::Rtti_PlotUserItem; + } + + void draw(QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &, + const QRect &rect) const { + if (parent->isShadeZones()) { + int y = (rect.bottom() + rect.top()) / 2; + int x = xMap.transform(watts); + + // the following code based on source for QwtPlotMarker::draw() + QRect tr(QPoint(0, 0), text.textSize(painter->font())); + tr.moveCenter(QPoint(x, y)); + text.draw(painter, tr); + } + } +}; + +int +HrPwPlot::isShadeZones() const { + return (shadeZones && !wattsArray.empty()); +} + +void +HrPwPlot::setShadeZones(int x) +{ + shadeZones = x; +} + +void +HrPwPlot::refreshZoneLabels() { + foreach(HrPwPlotZoneLabel *label, zoneLabels) { + label->detach(); + delete label; + } + zoneLabels.clear(); + + if (bg) { + bg->detach(); + delete bg; + bg = NULL; + } + + if (rideItem) { + int zone_range = rideItem->zoneRange(); + const Zones *zones = rideItem->zones; + + // generate labels for existing zones + if (zones && (zone_range >= 0)) { + int num_zones = zones->numZones(zone_range); + for (int z = 0; z < num_zones; z ++) { + HrPwPlotZoneLabel *label = new HrPwPlotZoneLabel(this, z); + label->attach(this); + zoneLabels.append(label); + } + } + } + + // create a background object for shading + bg = new HrPwPlotBackground(this); + bg->attach(this); +} diff --git a/src/HrPwPlot.h b/src/HrPwPlot.h new file mode 100644 index 000000000..21f106d94 --- /dev/null +++ b/src/HrPwPlot.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2011 Damien Grauser + * + * 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 + */ + +#ifndef _GC_HrPwPlot_h +#define _GC_HrPwPlot_h 1 + +#include "GoldenCheetah.h" +#include +#include + +class QwtPlotCurve; +class QwtPlotGrid; +class QwtPlotMarker; +class MainWindow; +class HrPwWindow; +class RideItem; +class HrPwPlotBackground; +class HrPwPlotZoneLabel; + +// Tooltips +class LTMToolTip; +class LTMCanvasPicker; + +class HrPwPlot : public QwtPlot +{ + Q_OBJECT + + public: + + HrPwPlot(MainWindow *mainWindow, HrPwWindow *hrPwWindow); + + RideItem *rideItem; + QwtPlotMarker *r_mrk1; + QwtPlotMarker *r_mrk2; + bool joinLine; + int shadeZones; + + void setShadeZones(int); + int isShadeZones() const; + void refreshZoneLabels(); + + int smoothing() const { return smooth; } + void setDataFromRide(RideItem *ride); + void setJoinLine(bool value); + void setAxisTitle(int axis, QString label); + + public slots: + // for tooltip + void pointHover(QwtPlotCurve*, int); + + protected: + friend class ::HrPwPlotBackground; + friend class ::HrPwPlotZoneLabel; + friend class ::HrPwWindow; + + HrPwWindow *hrPwWindow; + MainWindow *mainWindow; + + HrPwPlotBackground *bg; + + QwtPlotGrid *grid; + QVector hrCurves; + QwtPlotCurve *regCurve; + QwtPlotCurve *wattsStepCurve; + QwtPlotCurve *hrStepCurve; + + QPainter *painter; + + QVector hrArray; + QVector wattsArray; + QVector timeArray; + QVector interArray; + + int arrayLength; + + double *array; + int arrayLength2; + + int smooth; + int hrMin; + + QList zoneLabels; + bool shade_zones; // whether power should be shaded + + void recalc(); + void setYMax(); + void setXTitle(); + + void addWattStepCurve(QVector &finalWatts, int nbpoints); + void addHrStepCurve(QVector &finalHr, int nbpoints); + void addRegLinCurve(QVector &finalHr, QVector &finalWatts, int nbpoints); + + private: + QSettings settings; + QVariant unit; + + LTMToolTip *tooltip; + LTMCanvasPicker *_canvasPicker; // allow point selection/hover +}; + +#endif // _GC_HrPwPlot_h diff --git a/src/HrPwWindow.cpp b/src/HrPwWindow.cpp new file mode 100644 index 000000000..a1ba970e6 --- /dev/null +++ b/src/HrPwWindow.cpp @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2011 Damien Grauser + * + * 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 "GoldenCheetah.h" +#include "HrPwWindow.h" +#include "MainWindow.h" +#include "LTMWindow.h" +#include "HrPwPlot.h" +#include "SmallPlot.h" +#include "RideItem.h" +#include +#include +#include +#include + +// tooltip + +HrPwWindow::HrPwWindow(MainWindow *mainWindow) : + GcWindow(mainWindow), mainWindow(mainWindow), current(NULL) +{ + setControls(NULL); + setInstanceName("HrPw"); + + QVBoxLayout *vlayout = new QVBoxLayout; + setLayout(vlayout); + + // just the hr and power as a plot + smallPlot = new SmallPlot(this); + smallPlot->setMaximumHeight(200); + smallPlot->setMinimumHeight(100); + vlayout->addWidget(smallPlot); + vlayout->setStretch(0, 20); + + // main plot + hrPwPlot = new HrPwPlot(mainWindow, this); + + + // tooltip on hover over point + hrPwPlot->tooltip = new LTMToolTip(QwtPlot::xBottom, QwtPlot::yLeft, + QwtPicker::PointSelection, + QwtPicker::VLineRubberBand, + QwtPicker::AlwaysOn, + hrPwPlot->canvas(), + ""); + + hrPwPlot->tooltip->setSelectionFlags(QwtPicker::PointSelection | QwtPicker::RectSelection + | QwtPicker::DragSelection); + hrPwPlot->tooltip->setRubberBand(QwtPicker::VLineRubberBand); + hrPwPlot->tooltip->setMousePattern(QwtEventPattern::MouseSelect1, + Qt::LeftButton, Qt::ShiftModifier); + hrPwPlot->tooltip->setTrackerPen(QColor(Qt::black)); + + hrPwPlot->_canvasPicker = new LTMCanvasPicker(hrPwPlot); + + // setup the plot + QColor inv(Qt::white); + inv.setAlpha(0); + hrPwPlot->tooltip->setRubberBandPen(inv); + hrPwPlot->tooltip->setEnabled(true); + vlayout->addWidget(hrPwPlot); + vlayout->setStretch(1, 100); + + // the controls + QWidget *c = new QWidget(this); + setControls(c); + QFormLayout *cl = new QFormLayout(c); + QLabel *delayLabel = new QLabel(tr("HR delay"), this); + delayEdit = new QLineEdit(this); + delayEdit->setFixedWidth(30); + cl->addRow(delayLabel, delayEdit); + delaySlider = new QSlider(Qt::Horizontal); + delaySlider->setTickPosition(QSlider::TicksBelow); + delaySlider->setTickInterval(1); + delaySlider->setMinimum(1); + delaySlider->setMaximum(100);; + delayEdit->setValidator(new QIntValidator(delaySlider->minimum(), + delaySlider->maximum(), + delayEdit)); + cl->addRow(delaySlider); + + joinlineCheckBox = new QCheckBox(this);; + joinlineCheckBox->setText(tr("Join points")); + joinlineCheckBox->setCheckState(Qt::Unchecked); + setJoinLineFromCheckBox(); + cl->addRow(joinlineCheckBox); + + shadeZones = new QCheckBox(this);; + shadeZones->setText(tr("Shade Zones")); + shadeZones->setCheckState(Qt::Checked); + setShadeZones(); + cl->addRow(shadeZones); + + // connect them all up now initialised + connect(hrPwPlot->_canvasPicker, SIGNAL(pointHover(QwtPlotCurve*, int)), + hrPwPlot, SLOT(pointHover(QwtPlotCurve*, int))); + connect(joinlineCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setJoinLineFromCheckBox())); + connect(shadeZones, SIGNAL(stateChanged(int)), this, SLOT(setShadeZones())); + connect(delayEdit, SIGNAL(editingFinished()), this, SLOT(setSmoothingFromLineEdit())); + //connect(mainWindow, SIGNAL(configChanged()), this, SLOT(configChanged())); + connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideSelected())); +} + +void +HrPwWindow::rideSelected() +{ + if (!amVisible()) + return; + + RideItem *ride = myRideItem; + if (!ride || !ride->ride()) return; + + setData(ride); +} + +void +HrPwWindow::setData(RideItem *ride) +{ + hrPwPlot->setDataFromRide(ride); + smallPlot->setData(ride); +} + +void +HrPwWindow::setJoinLineFromCheckBox() +{ + if (hrPwPlot->joinLine != joinlineCheckBox->isChecked()) { + hrPwPlot->setJoinLine(joinlineCheckBox->isChecked()); + hrPwPlot->replot(); + } +} + +void +HrPwWindow::setShadeZones() +{ + if (hrPwPlot->shadeZones != shadeZones->isChecked()) { + hrPwPlot->setShadeZones(shadeZones->isChecked()); + hrPwPlot->replot(); + } +} + +void +HrPwWindow::setSmoothingFromLineEdit() +{ + int value = delayEdit->text().toInt(); + //if (value != allPlot->smoothing()) { + //allPlot->setSmoothing(value); + delaySlider->setValue(value); + //} +} + +int +HrPwWindow::findDelay(QVector &wattsArray, QVector &hrArray, int rideTimeSecs) +{ + int delay = 0; + double maxr = 0; + + for (int a = 10; a <=60; ++a) { + + QVector delayHr(rideTimeSecs); + + for (int j = a; jmaxr) { + maxr = r; + delay = a; + } + } + delayEdit->setText(QString("%1").arg(delay)); + delaySlider->setValue(delay); + return delay; +} + +/**************************************/ +/* Fichier CPP de la librairie reglin */ +/* Réalisé par GONNELLA Stéphane */ +/**************************************/ + + +/* Déclaratin globale des variables */ + + +/*********************/ +/* Fonctions de test */ +/*********************/ + +/* Fonction de test de présence d'un zéro */ + +int HrPwWindow::test_zero(QVector &tab,int n) +{ + for(int i=0;i &tab,int n) +{ + for(int i=0;i &r) +{ + double temp=0; + int ajust=0; + + for(int i=0;i<5;i++) + { + if(r[i]>temp) + { + temp=r[i]; + ajust = i+1; + } + } + + return ajust; +} + +/**********************/ +/* Fonctions de somme */ +/**********************/ + +/* Fonction de somme d'éléments d'un tableau d'entier */ + +int HrPwWindow::somme(QVector &tab,int n) +{ + int somme=0; + + for (int i=0;i &tab,int n) +{ + double somme=0; + + for (int i=0;i &tab,int n) +{ + double moyenne = double(somme(tab,n))/n; + + return (moyenne); +} + +/* Fonction de calcul de moyenne d'éléments d'un tableau de réel */ + +double HrPwWindow::moyenne(QVector &tab,int n) +{ + double moyenne = somme(tab,n)/n; + + return (moyenne); +} + +/* Fonction de calcul de moyenne d'elements d'un tableau de réel(2) */ + +double HrPwWindow::moyenne2(double somme,int n) +{ + double moyenne = somme/n; + + return (moyenne); +} + +/***********************/ +/* Fonction de produit */ +/***********************/ + +/* Fonction de calcul du produit d'éléments de deux tableau ligne par ligne */ + +void HrPwWindow::produittab(QVector &tab1,QVector &tab2,int n) +{ + tab_temp.resize(n); //Réservation de l'espace mémoire + + for (int i=0;i &tab,QVector &tabTemp,int n) +{ + tab_temp.resize(n); //Réservation de l'espace mémoire + + for (int i=0;i &tab,QVector &tabTemp,int n) +{ + tab_temp.resize(n); //Réservation de l'espace mémoire + + for (int i=0;i &tab,QVector &tabTemp,int n) +{ + tab_temp.resize(n); //Réservation de l'espace mémoire + + for (int i=0;i &tab,double Moyenne,int n) +{ + tab_temp.resize(n); //Réservation de l'espace mémoire + + for (int i=0;i &Xi, QVector &Yi,int n) +{ + double cov; + + produittab(Xi,Yi,n); + cov = moyenne(tab_temp,n) - ( moyenne(Xi,n) * moyenne(Yi,n) ); + + return (cov); +} + +/* Fonction de calcul de la somme des carrés des écarts a la moyenne */ + +double HrPwWindow::variance(QVector &val,int n) +{ + double sce; + + produittab(val,val,n); + sce = moyenne(tab_temp,n) - ( moyenne(val,n) * moyenne(val,n)); + + return (sce); +} + +/* Fonction de calcul de l'écart-type */ + +double HrPwWindow::ecarttype(QVector &val,int n) +{ + double ect= sqrt(variance(val,n)); + + return (ect); +} +/******************************************************/ +/* Fonctions pour le calcul de la régression linéaire */ +/* par la méthode des moindres carré */ +/******************************************************/ + +/* Fonction de clacul de la pente (a) */ + +double HrPwWindow::pente(QVector &Xi,QVector &Yi,int n) +{ + double a = covariance(Xi,Yi,n)/variance(Xi,n); + + return (a); +} + +/* Fonction de clacul de l'ordonnée a l'origine (b) */ + +double HrPwWindow::ordonnee(QVector &Xi,QVector &Yi,int n) +{ + double b = moyenne(Yi,n) - ( pente(Xi,Yi,n) * moyenne(Xi,n) ); + + return (b); +} + +/* Fonction de calcul du coef de corrélation (r) */ + +double HrPwWindow::corr(QVector &Xi, QVector &Yi,int n) +{ + double r = covariance(Xi,Yi,n)/(ecarttype(Xi,n)*ecarttype(Yi,n)); + //double r=pente(Xi,Yi,n)*pente(Xi,Yi,n)*(variance(Xi,n)/variance(Yi,n)); + return (r); +} + +/* Fonction de détermination du meilleur ajustement */ + +int HrPwWindow::ajustement(QVector &Xi,QVector &Yi,int n) +{ + QVector r(5),lnXi(100),lnYi(100),logXi(100),logYi(100),invXi(100); + + //corrélation pour linéaire + + r[0]=val_abs(corr(Xi,Yi,n)); + + //corrélation pour exponetielle + + lntab(Yi,lnYi,n); + r[1]=val_abs(corr(Xi,lnYi,n)); + + //corrélation pour puissance + + logtab(Xi,logXi,n); + logtab(Yi,logYi,n); + r[2]=val_abs(corr(logXi,logYi,n)); + + //corrélation pour inverse + + invtab(Xi,invXi,n); + r[3]=val_abs(corr(invXi,Yi,n)); + + //corrélation pour logarithmique + + lntab(Xi,lnXi,n); + r[4]=val_abs(corr(lnXi,Yi,n)); + + //Test du meilleur ajustement + + return rmax(r); +} + +/*****************************/ +/* Fin du fichier reglin.cpp */ +/*****************************/ diff --git a/src/HrPwWindow.h b/src/HrPwWindow.h new file mode 100644 index 000000000..dbc2f03ca --- /dev/null +++ b/src/HrPwWindow.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2011 Damien Grauser + * + * 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 + */ + +#ifndef _GC_HrPwWindow_h +#define _GC_HrPwWindow_h 1 + +#include +#include "GoldenCheetah.h" + + +class QCheckBox; +class QLineEdit; +class RideItem; +class HrPwPlot; +class MainWindow; +class SmallPlot; +class QSlider; + +#include "LTMWindow.h" // for tooltip/canvaspicker + + +class HrPwWindow : public GcWindow +{ + Q_OBJECT + G_OBJECT + + Q_PROPERTY(int shadeZones READ isShadeZones WRITE setShadeZones USER true) + Q_PROPERTY(int joinLine READ isJoinLine WRITE setJoinLine USER true) + + int isJoinLine() const { return joinlineCheckBox->checkState(); } + void setJoinLine(int x) { joinlineCheckBox->setCheckState(x ? Qt::Checked : Qt::Unchecked); } + int isShadeZones() const { return shadeZones->checkState(); } + void setShadeZones(int x) { shadeZones->setCheckState(x ? Qt::Checked : Qt::Unchecked); } + + public: + + HrPwWindow(MainWindow *mainWindow); + void setData(RideItem *item); + int findDelay(QVector &wattsArray, QVector &hrArray, int rideTimeSecs); + + // Maths functions used by HrPwPlot + double pente(QVector &Xi,QVector &Yi,int n); + double ordonnee(QVector &Xi,QVector &Yi,int n); + double corr(QVector &Xi, QVector &Yi,int n); + double moyenne(QVector &Xi,int n); + + public slots: + void rideSelected(); + + protected slots: + void setJoinLineFromCheckBox(); + void setSmoothingFromLineEdit(); + void setShadeZones(); + + protected: + MainWindow *mainWindow; + HrPwPlot *hrPwPlot; + SmallPlot *smallPlot; + + RideItem *current; + + QCheckBox *joinlineCheckBox; + QCheckBox *shadeZones; + + QSlider *delaySlider; + QLineEdit *delayEdit; + + private: + + // Maths functions used by the plots + QVector tab_temp; //Déclaration d'un tableau temporaire + int test_zero(QVector &tab,int n); + int test_negatif(QVector &tab,int n); + void logtab(QVector &tab,QVector &tabTemp,int n); + void lntab(QVector &tab,QVector &tabTemp,int n); + void invtab(QVector &tab,QVector &tabTemp,int n); + int ajustement(QVector &Xi,QVector &Yi,int n); + double moyenne(QVector &tab,int n); + double moyenne2(double somme,int n); + double val_abs(double x); + int rmax(QVector &r); + int somme(QVector &tab,int n); + double somme(QVector &tab,int n); + void produittab(QVector &tab1,QVector &tab2,int n); + void ecart_a_moyenne(QVector &tab,double Moyenne,int n); + double covariance(QVector &Xi, QVector &Yi,int n); + double variance(QVector &val,int n); + double ecarttype(QVector &val,int n); +}; + +#endif // _GC_HrPwWindow_h diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 352f1bd75..cf812f89f 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -38,6 +38,7 @@ #endif #include "LTMWindow.h" #include "PfPvWindow.h" +#include "HrPwWindow.h" #include "DownloadRideDialog.h" #include "ManualRideDialog.h" #include "HistogramWindow.h" @@ -441,6 +442,11 @@ MainWindow::MainWindow(const QDir &home) : pfPvWindow = new PfPvWindow(this); tabs.append(TabInfo(pfPvWindow, tr("PF/PV"))); + //////////////////////// HrPw Plot //////////////////////// + + hrPwWindow = new HrPwWindow(this); + tabs.append(TabInfo(hrPwWindow, tr("HrPw"))); + //////////////////////// Scatter Plot //////////////////////// scatterWindow = new ScatterWindow(this, home); diff --git a/src/MainWindow.h b/src/MainWindow.h index b7b964c7d..c050509a4 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -36,6 +36,7 @@ class AllPlotWindow; class CriticalPowerWindow; class HistogramWindow; class PfPvWindow; +class HrPwWindow; class QwtPlotPanner; class QwtPlotPicker; class QwtPlotZoomer; @@ -292,6 +293,8 @@ class MainWindow : public QMainWindow // pedal force/pedal velocity scatter plot widgets PfPvWindow *pfPvWindow; + HrPwWindow *hrPwWindow; + RideItem *ride; // the currently selected ride diff --git a/src/SmallPlot.cpp b/src/SmallPlot.cpp new file mode 100644 index 000000000..9effa7010 --- /dev/null +++ b/src/SmallPlot.cpp @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2011 Damien Grauser + * + * 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 "SmallPlot.h" +#include "RideFile.h" +#include "RideItem.h" +#include "Settings.h" +#include "Colors.h" + +#include +#include +#include +#include +#include +#include +#include + +static double inline max(double a, double b) { if (a > b) return a; else return b; } + +#define MILES_PER_KM 0.62137119 + +SmallPlot::SmallPlot(QWidget *parent) : QwtPlot(parent), d_mrk(NULL), smooth(30) +{ + setCanvasBackground(GColor(CRIDEPLOTBACKGROUND)); + + setXTitle(); + + wattsCurve = new QwtPlotCurve("Power"); + + timeCurves.resize(36);// wattsCurve->setRenderHint(QwtPlotItem::RenderAntialiased); + wattsCurve->setPen(QPen(GColor(CPOWER))); + wattsCurve->attach(this); + + hrCurve = new QwtPlotCurve("Heart Rate"); + // hrCurve->setRenderHint(QwtPlotItem::RenderAntialiased); + hrCurve->setPen(QPen(GColor(CHEARTRATE))); + hrCurve->attach(this); + + grid = new QwtPlotGrid(); + grid->enableX(false); + QPen gridPen; + gridPen.setStyle(Qt::DotLine); + grid->setPen(QPen(GColor(CPLOTGRID))); + grid->attach(this); + + timeCurves.resize(36); + for (int i = 0; i < 36; ++i) { + QColor color = QColor(255,255,255); + color.setHsv(60+i*(360/36), 255,255,255); + + QPen pen = QPen(color); + pen.setWidth(3); + + timeCurves[i] = new QwtPlotCurve(); + timeCurves[i]->setPen(pen); + timeCurves[i]->setStyle(QwtPlotCurve::Lines); + timeCurves[i]->setRenderHint(QwtPlotItem::RenderAntialiased); + timeCurves[i]->attach(this); + QwtLegend *legend = new QwtLegend; + legend->setVisible(false); + legend->setDisabled(true); + timeCurves[i]->updateLegend(legend); + } +} + +struct DataPoint { + double time, hr, watts; + int inter; + DataPoint(double t, double h, double w, int i) : + time(t), hr(h), watts(w), inter(i) {} +}; + +void +SmallPlot::recalc() +{ + if (!timeArray.size()) return; + + int rideTimeSecs = (int) ceil(timeArray[arrayLength - 1]); + if (rideTimeSecs > 7*24*60*60) { + QwtArray data; + wattsCurve->setData(data, data); + hrCurve->setData(data, data); + return; + } + +#if 0 + int nbpoints2 = (int)floor(rideTimeSecs/60/36)+2; + //fprintf(stderr, "rideTimeSecs : %d, nbpoints2 : %d",rideTimeSecs/60, nbpoints2); + + QVectordatatime(nbpoints2); + double *time[36]; + + for (int i = 0; i < 36; ++i) { + time[i]= new double[nbpoints2]; + } + + for (int secs = 0; secs < nbpoints2; ++secs) { + datatime[secs] = 1; + + for (int i = 0; i < 36; ++i) { + //fprintf(stderr, "\ni : %d, time : %d",i, secs + i*(nbpoints2-1)); + time[i][secs] = secs + i*(nbpoints2-1); + } + } + + for (int i = 0; i < 36; ++i) { + timeCurves[i]->setData(time[i], datatime, nbpoints2); + } +#endif + + double totalWatts = 0.0; + double totalHr = 0.0; + QList list; + int i = 0; + + QVector smoothWatts(rideTimeSecs + 1); + QVector smoothHr(rideTimeSecs + 1); + QVector smoothTime(rideTimeSecs + 1); + + QList interList; //Just store the time that it happened. + //Intervals are sequential. + + int lastInterval = 0; //Detect if we hit a new interval + + for (int secs = 0; ((secs < smooth) && (secs < rideTimeSecs)); ++secs) { + smoothWatts[secs] = 0.0; + smoothHr[secs] = 0.0; + } + for (int secs = smooth; secs <= rideTimeSecs; ++secs) { + while ((i < arrayLength) && (timeArray[i] <= secs)) { + DataPoint *dp = + new DataPoint(timeArray[i], hrArray[i], wattsArray[i], interArray[i]); + totalWatts += wattsArray[i]; + totalHr += hrArray[i]; + list.append(dp); + //Figure out when and if we have a new interval.. + if(lastInterval != interArray[i]) { + lastInterval = interArray[i]; + interList.append(secs/60.0); + } + ++i; + } + while (!list.empty() && (list.front()->time < secs - smooth)) { + DataPoint *dp = list.front(); + list.removeFirst(); + totalWatts -= dp->watts; + totalHr -= dp->hr; + delete dp; + } + // TODO: this is wrong. We should do a weighted average over the + // seconds represented by each point... + if (list.empty()) { + smoothWatts[secs] = 0.0; + smoothHr[secs] = 0.0; + } + else { + smoothWatts[secs] = totalWatts / list.size(); + smoothHr[secs] = totalHr / list.size(); + } + smoothTime[secs] = secs / 60.0; + } + wattsCurve->setData(smoothTime.constData(), smoothWatts.constData(), rideTimeSecs + 1); + hrCurve->setData(smoothTime.constData(), smoothHr.constData(), rideTimeSecs + 1); + setAxisScale(xBottom, 0.0, smoothTime[rideTimeSecs]); + + setYMax(); + + +#if 0 + QString label[interList.size()]; + QwtText text[interList.size()]; + + // arrays are safe since not passed + // as a conyiguous array + if (d_mrk) delete [] d_mrk; + d_mrk = new QwtPlotMarker[interList.size()]; + for(int x = 0; x < interList.size(); x++) { + // marker + d_mrk[x].setValue(0,0); + d_mrk[x].setLineStyle(QwtPlotMarker::VLine); + d_mrk[x].setLabelAlignment(Qt::AlignRight | Qt::AlignTop); + d_mrk[x].setLinePen(QPen(Qt::black, 0, Qt::DashDotLine)); + d_mrk[x].attach(this); + label[x].setNum(x+1); + text[x] = QwtText(label[x]); + text[x].setFont(QFont("Helvetica", 10, QFont::Bold)); + text[x].setColor(Qt::black); + + d_mrk[x].setValue(interList.at(x), 0.0); + d_mrk[x].setLabel(text[x]); + } +#endif + + replot(); +} + +void +SmallPlot::setYMax() +{ + double ymax = 0; + QString ylabel = ""; + if (wattsCurve->isVisible()) { + ymax = max(ymax, wattsCurve->maxYValue()); + ylabel += QString((ylabel == "") ? "" : " / ") + "Watts"; + } + if (hrCurve->isVisible()) { + ymax = max(ymax, hrCurve->maxYValue()); + ylabel += QString((ylabel == "") ? "" : " / ") + "BPM"; + } + setAxisScale(yLeft, 0.0, ymax * 1.1); + setAxisTitle(yLeft, ylabel); +} + +void +SmallPlot::setXTitle() +{ + setAxisTitle(xBottom, tr("Time (minutes)")); +} + +void +SmallPlot::setAxisTitle(int axis, QString label) +{ + // setup the default fonts + QFont stGiles; // hoho - Chart Font St. Giles ... ok you have to be British to get this joke + stGiles.fromString(appsettings->value(this, GC_FONT_CHARTLABELS, QFont().toString()).toString()); + stGiles.setPointSize(appsettings->value(NULL, GC_FONT_CHARTLABELS_SIZE, 8).toInt()); + + QwtText title(label); + title.setFont(stGiles); + QwtPlot::setAxisFont(axis, stGiles); + QwtPlot::setAxisTitle(axis, title); +} + +void +SmallPlot::setData(RideItem *rideItem) +{ + RideFile *ride = rideItem->ride(); + + wattsArray.resize(ride->dataPoints().size()); + hrArray.resize(ride->dataPoints().size()); + timeArray.resize(ride->dataPoints().size()); + interArray.resize(ride->dataPoints().size()); + + arrayLength = 0; + foreach (const RideFilePoint *point, ride->dataPoints()) { + timeArray[arrayLength] = point->secs; + wattsArray[arrayLength] = max(0, point->watts); + hrArray[arrayLength] = max(0, point->hr); + interArray[arrayLength] = point->interval; + ++arrayLength; + } + recalc(); +} + +void +SmallPlot::showPower(int state) +{ + assert(state != Qt::PartiallyChecked); + wattsCurve->setVisible(state == Qt::Checked); + setYMax(); + replot(); +} + +void +SmallPlot::showHr(int state) +{ + assert(state != Qt::PartiallyChecked); + hrCurve->setVisible(state == Qt::Checked); + setYMax(); + replot(); +} + +void +SmallPlot::setSmoothing(int value) +{ + smooth = value; + recalc(); +} diff --git a/src/SmallPlot.h b/src/SmallPlot.h new file mode 100644 index 000000000..ce0274bf8 --- /dev/null +++ b/src/SmallPlot.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2011 Damien Grauser + * + * 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 + */ + +#ifndef _GC_SmallPlot_h +#define _GC_SmallPlot_h 1 + +#include +#include + +class QwtPlotCurve; +class QwtPlotGrid; +class QwtPlotMarker; +class RideItem; + +class SmallPlot : public QwtPlot +{ + Q_OBJECT + + public: + + SmallPlot(QWidget *parent=0); + + + int smoothing() const { return smooth; } + void setData(RideItem *ride); + void setAxisTitle(int axis, QString label); + void recalc(); + void setYMax(); + void setXTitle(); + + public slots: + + void showPower(int state); + void showHr(int state); + void setSmoothing(int value); + + protected: + + QwtPlotGrid *grid; + QwtPlotCurve *wattsCurve; + QwtPlotCurve *hrCurve; + + QwtPlotMarker* d_mrk; + QVector hrArray; + QVector wattsArray; + QVector timeArray; + QVector timeCurves; + int arrayLength; + + QVector interArray; + + int smooth; +}; + +#endif // _GC_SmallPlot_h diff --git a/src/src.pro b/src/src.pro index 66041b723..eac5d0cce 100644 --- a/src/src.pro +++ b/src/src.pro @@ -164,6 +164,8 @@ HEADERS += \ HistogramWindow.h \ HomeWindow.h \ HrZones.h \ + HrPwPlot.h \ + HrPwWindow.h \ IntervalItem.h \ JsonRideFile.h \ LogTimeScaleDraw.h \ @@ -217,6 +219,7 @@ HEADERS += \ RideNavigator.h \ RideNavigatorProxy.h \ SaveDialogs.h \ + SmallPlot.h \ RideSummaryWindow.h \ ScatterPlot.h \ ScatterWindow.h \ @@ -312,6 +315,8 @@ SOURCES += \ HomeWindow.cpp \ HrTimeInZone.cpp \ HrZones.cpp \ + HrPwPlot.cpp \ + HrPwWindow.cpp \ IntervalItem.cpp \ LogTimeScaleDraw.cpp \ LogTimeScaleEngine.cpp \ @@ -372,6 +377,7 @@ SOURCES += \ Settings.cpp \ SimpleNetworkController.cpp \ SimpleNetworkClient.cpp \ + SmallPlot.cpp \ SpecialFields.cpp \ SplitRideDialog.cpp \ SrdRideFile.cpp \ @@ -379,7 +385,7 @@ SOURCES += \ StressCalculator.cpp \ SummaryMetrics.cpp \ SummaryWindow.cpp \ - TacxCafRideFile.cpp \ + TacxCafRideFile.cpp \ TcxParser.cpp \ TcxRideFile.cpp \ TxtRideFile.cpp \