mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 16:18:42 +00:00
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.
This commit is contained in:
committed by
Mark Liversedge
parent
edc8b412bb
commit
6ac75fda3a
@@ -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;
|
||||
|
||||
@@ -46,6 +46,7 @@ enum gcwinid {
|
||||
Train =17,
|
||||
TreeMap =18,
|
||||
WeeklySummary =19,
|
||||
HrPw =20,
|
||||
};
|
||||
};
|
||||
typedef enum GcWindowTypes::gcwinid GcWinID;
|
||||
|
||||
700
src/HrPwPlot.cpp
Normal file
700
src/HrPwPlot.cpp
Normal file
@@ -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 <assert.h>
|
||||
#include <qwt_plot_curve.h>
|
||||
#include <qwt_plot_grid.h>
|
||||
#include <qwt_plot_marker.h>
|
||||
#include <qwt_text.h>
|
||||
#include <qwt_symbol.h>
|
||||
#include <qwt_legend.h>
|
||||
#include <qwt_data.h>
|
||||
|
||||
|
||||
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 <HrPwPlotZoneLabel *>::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<DataPoint*> list;
|
||||
int i = 0;
|
||||
QVector<double> smoothWatts(rideTimeSecs + 1);
|
||||
QVector<double> smoothHr(rideTimeSecs + 1);
|
||||
QVector<double> 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<double> clipWatts(rideTimeSecs-delay);
|
||||
QVector<double> 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<double> finalWatts(nbpoints);
|
||||
QVector<double> 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*nbpoints2<nbpoints2?nbpoints-i*nbpoints2:nbpoints2));
|
||||
hrCurves[i]->setVisible(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<double> &finalWatts, int nbpoints)
|
||||
{
|
||||
QMap<double,double> 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<double,double> k(powerHist);
|
||||
while (k.hasNext()) {
|
||||
k.next();
|
||||
array[(int) round(k.key())] += k.value();
|
||||
}
|
||||
|
||||
int nbSteps = (int) ceil((maxPower - 1) / 10);
|
||||
QVector<double> smoothWattsStep(nbSteps+1);
|
||||
QVector<double> 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<double> &finalHr, int nbpoints)
|
||||
{
|
||||
QMap<double,double> 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<double,double> l(hrHist);
|
||||
while (l.hasNext()) {
|
||||
l.next();
|
||||
array[(int) round(l.key())] += l.value();
|
||||
}
|
||||
|
||||
|
||||
int nbSteps = (int) ceil((maxHr - 1) / 2);
|
||||
QVector<double> smoothHrStep(nbSteps+1);
|
||||
QVector<double> 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<double> &finalHr, QVector<double> &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<RideFilePoint*> 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 <int> 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 <int> zone_lows = zones->getZoneLows(zone_range);
|
||||
QList <QString> 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);
|
||||
}
|
||||
116
src/HrPwPlot.h
Normal file
116
src/HrPwPlot.h
Normal file
@@ -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 <qwt_plot.h>
|
||||
#include <QtGui>
|
||||
|
||||
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<QwtPlotCurve*> hrCurves;
|
||||
QwtPlotCurve *regCurve;
|
||||
QwtPlotCurve *wattsStepCurve;
|
||||
QwtPlotCurve *hrStepCurve;
|
||||
|
||||
QPainter *painter;
|
||||
|
||||
QVector<double> hrArray;
|
||||
QVector<double> wattsArray;
|
||||
QVector<double> timeArray;
|
||||
QVector<int> interArray;
|
||||
|
||||
int arrayLength;
|
||||
|
||||
double *array;
|
||||
int arrayLength2;
|
||||
|
||||
int smooth;
|
||||
int hrMin;
|
||||
|
||||
QList <HrPwPlotZoneLabel *> zoneLabels;
|
||||
bool shade_zones; // whether power should be shaded
|
||||
|
||||
void recalc();
|
||||
void setYMax();
|
||||
void setXTitle();
|
||||
|
||||
void addWattStepCurve(QVector<double> &finalWatts, int nbpoints);
|
||||
void addHrStepCurve(QVector<double> &finalHr, int nbpoints);
|
||||
void addRegLinCurve(QVector<double> &finalHr, QVector<double> &finalWatts, int nbpoints);
|
||||
|
||||
private:
|
||||
QSettings settings;
|
||||
QVariant unit;
|
||||
|
||||
LTMToolTip *tooltip;
|
||||
LTMCanvasPicker *_canvasPicker; // allow point selection/hover
|
||||
};
|
||||
|
||||
#endif // _GC_HrPwPlot_h
|
||||
510
src/HrPwWindow.cpp
Normal file
510
src/HrPwWindow.cpp
Normal file
@@ -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 <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <QVector>
|
||||
#include <QtGui>
|
||||
|
||||
// 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<double> &wattsArray, QVector<double> &hrArray, int rideTimeSecs)
|
||||
{
|
||||
int delay = 0;
|
||||
double maxr = 0;
|
||||
|
||||
for (int a = 10; a <=60; ++a) {
|
||||
|
||||
QVector<double> delayHr(rideTimeSecs);
|
||||
|
||||
for (int j = a; j<rideTimeSecs; ++j) {
|
||||
delayHr[j-a] = hrArray[j];
|
||||
}
|
||||
for (int j = rideTimeSecs-a; j<rideTimeSecs; ++j) {
|
||||
delayHr[j] = 0.0;
|
||||
}
|
||||
|
||||
double r = corr(wattsArray, delayHr, rideTimeSecs-a);
|
||||
//fprintf(stderr, "findDelay %d: %.2f \n", a, r);
|
||||
|
||||
|
||||
if (r>maxr) {
|
||||
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<double> &tab,int n)
|
||||
{
|
||||
for(int i=0;i<n;i++)
|
||||
{
|
||||
if(tab[i]==0)
|
||||
{ return i;}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Fonction de test de présence d'un négatif */
|
||||
|
||||
int HrPwWindow::test_negatif(QVector<double> &tab,int n)
|
||||
{
|
||||
for(int i=0;i<n;i++)
|
||||
{
|
||||
if(tab[i]<0)
|
||||
{ return i;}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*********************************/
|
||||
/* Fonctions de valeurs absolues */
|
||||
/*********************************/
|
||||
|
||||
/*fonction qui retourne la valeur absolue*/
|
||||
|
||||
double HrPwWindow::val_abs(double x)
|
||||
{
|
||||
if(x<0) {return -x;}
|
||||
else {return x;}
|
||||
}
|
||||
/********************************/
|
||||
/* Fonction de recherche du max */
|
||||
/********************************/
|
||||
|
||||
/* Fonction qui retourne celui qui est le max */
|
||||
|
||||
int HrPwWindow::rmax(QVector<double> &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<int> &tab,int n)
|
||||
{
|
||||
int somme=0;
|
||||
|
||||
for (int i=0;i<n;i++)
|
||||
{
|
||||
somme=((tab[i])+(somme));
|
||||
}
|
||||
|
||||
return(somme);
|
||||
}
|
||||
|
||||
/* Fonction de somme d'éléments d'un tableau de réel*/
|
||||
|
||||
double HrPwWindow::somme(QVector<double> &tab,int n)
|
||||
{
|
||||
double somme=0;
|
||||
|
||||
for (int i=0;i<n;i++)
|
||||
{
|
||||
somme=((tab[i])+(somme));
|
||||
}
|
||||
|
||||
return(somme);
|
||||
}
|
||||
|
||||
/**********************************/
|
||||
/* Fonctions de calcul de moyenne */
|
||||
/**********************************/
|
||||
|
||||
/* Fonction de calcul de moyenne d'éléments d'un tableau d'entier */
|
||||
|
||||
double HrPwWindow::moyenne(QVector<int> &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<double> &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<double> &tab1,QVector<double> &tab2,int n)
|
||||
{
|
||||
tab_temp.resize(n); //Réservation de l'espace mémoire
|
||||
|
||||
for (int i=0;i<n;i++)
|
||||
{
|
||||
tab_temp[i]=(tab1[i]*tab2[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
/* Fonctions de changement de variable */
|
||||
/***************************************/
|
||||
|
||||
/* Fonctions de calcul du ln des éléments d'un tableau de réel */
|
||||
|
||||
void HrPwWindow::lntab(QVector<double> &tab,QVector<double> &tabTemp,int n)
|
||||
{
|
||||
tab_temp.resize(n); //Réservation de l'espace mémoire
|
||||
|
||||
for (int i=0;i<n;i++)
|
||||
{
|
||||
tabTemp[i]=(log(tab[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/* Fonctions de calcul du log base 10 des éléments d'un tableau de réel */
|
||||
|
||||
void HrPwWindow::logtab(QVector<double> &tab,QVector<double> &tabTemp,int n)
|
||||
{
|
||||
tab_temp.resize(n); //Réservation de l'espace mémoire
|
||||
|
||||
for (int i=0;i<n;i++)
|
||||
{
|
||||
tabTemp[i]=(log10(tab[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/* Fonction d'inverstion des élements d'un tableau de réel */
|
||||
|
||||
void HrPwWindow::invtab(QVector<double> &tab,QVector<double> &tabTemp,int n)
|
||||
{
|
||||
tab_temp.resize(n); //Réservation de l'espace mémoire
|
||||
|
||||
for (int i=0;i<n;i++)
|
||||
{
|
||||
tabTemp[i]=(1/tab[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/********************/
|
||||
/* Autres fonctions */
|
||||
/********************/
|
||||
|
||||
/* Fonction de calcul des écarts à la moyenne */
|
||||
|
||||
void HrPwWindow::ecart_a_moyenne(QVector<double> &tab,double Moyenne,int n)
|
||||
{
|
||||
tab_temp.resize(n); //Réservation de l'espace mémoire
|
||||
|
||||
for (int i=0;i<n;i++)
|
||||
{
|
||||
tab_temp[i]=(tab[i] - Moyenne);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************/
|
||||
/* Fonctions de statistique */
|
||||
/****************************/
|
||||
|
||||
/* Fonction de calcul de la covariance */
|
||||
|
||||
double HrPwWindow::covariance(QVector<double> &Xi, QVector<double> &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<double> &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<double> &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<double> &Xi,QVector<double> &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<double> &Xi,QVector<double> &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<double> &Xi, QVector<double> &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<double> &Xi,QVector<double> &Yi,int n)
|
||||
{
|
||||
QVector<double> 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 */
|
||||
/*****************************/
|
||||
106
src/HrPwWindow.h
Normal file
106
src/HrPwWindow.h
Normal file
@@ -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 <QWidget>
|
||||
#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<double> &wattsArray, QVector<double> &hrArray, int rideTimeSecs);
|
||||
|
||||
// Maths functions used by HrPwPlot
|
||||
double pente(QVector<double> &Xi,QVector<double> &Yi,int n);
|
||||
double ordonnee(QVector<double> &Xi,QVector<double> &Yi,int n);
|
||||
double corr(QVector<double> &Xi, QVector<double> &Yi,int n);
|
||||
double moyenne(QVector<double> &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<double> tab_temp; //Déclaration d'un tableau temporaire
|
||||
int test_zero(QVector<double> &tab,int n);
|
||||
int test_negatif(QVector<double> &tab,int n);
|
||||
void logtab(QVector<double> &tab,QVector<double> &tabTemp,int n);
|
||||
void lntab(QVector<double> &tab,QVector<double> &tabTemp,int n);
|
||||
void invtab(QVector<double> &tab,QVector<double> &tabTemp,int n);
|
||||
int ajustement(QVector<double> &Xi,QVector<double> &Yi,int n);
|
||||
double moyenne(QVector<int> &tab,int n);
|
||||
double moyenne2(double somme,int n);
|
||||
double val_abs(double x);
|
||||
int rmax(QVector<double> &r);
|
||||
int somme(QVector<int> &tab,int n);
|
||||
double somme(QVector<double> &tab,int n);
|
||||
void produittab(QVector<double> &tab1,QVector<double> &tab2,int n);
|
||||
void ecart_a_moyenne(QVector<double> &tab,double Moyenne,int n);
|
||||
double covariance(QVector<double> &Xi, QVector<double> &Yi,int n);
|
||||
double variance(QVector<double> &val,int n);
|
||||
double ecarttype(QVector<double> &val,int n);
|
||||
};
|
||||
|
||||
#endif // _GC_HrPwWindow_h
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
293
src/SmallPlot.cpp
Normal file
293
src/SmallPlot.cpp
Normal file
@@ -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 <assert.h>
|
||||
#include <qwt_plot_curve.h>
|
||||
#include <qwt_plot_grid.h>
|
||||
#include <qwt_plot_marker.h>
|
||||
#include <qwt_text.h>
|
||||
#include <qwt_legend.h>
|
||||
#include <qwt_data.h>
|
||||
|
||||
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<double> 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);
|
||||
|
||||
QVector<double>datatime(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<DataPoint*> list;
|
||||
int i = 0;
|
||||
|
||||
QVector<double> smoothWatts(rideTimeSecs + 1);
|
||||
QVector<double> smoothHr(rideTimeSecs + 1);
|
||||
QVector<double> smoothTime(rideTimeSecs + 1);
|
||||
|
||||
QList<double> 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();
|
||||
}
|
||||
70
src/SmallPlot.h
Normal file
70
src/SmallPlot.h
Normal file
@@ -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 <qwt_plot.h>
|
||||
#include <QtGui>
|
||||
|
||||
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<double> hrArray;
|
||||
QVector<double> wattsArray;
|
||||
QVector<double> timeArray;
|
||||
QVector<QwtPlotCurve*> timeCurves;
|
||||
int arrayLength;
|
||||
|
||||
QVector<int> interArray;
|
||||
|
||||
int smooth;
|
||||
};
|
||||
|
||||
#endif // _GC_SmallPlot_h
|
||||
@@ -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 \
|
||||
|
||||
Reference in New Issue
Block a user