Plot seasons / date ranges on Histogram Plot

The recent RideFileCache patches added functions to
pre-compute mean-max and distributions. This enabled
this patch to add plotting histograms for a date
range rather than a specific ride.

It supports all the same data series as before but will
allow you to select a season from a new combo box.

I have refactored a fair amount of the code, but kept the
original code in PowerHist as close to unchanged as I could
since I did not want to disturb existing functionality.

There is no support for Zoning historic data -- this requires
an update to the RideFileCache.
This commit is contained in:
Mark Liversedge
2011-05-02 19:39:39 +01:00
parent f686f2f262
commit f4fb11b9c2
5 changed files with 933 additions and 868 deletions

View File

@@ -1,5 +1,6 @@
/*
* Copyright (c) 2009 Sean C. Rhea (srhea@srhea.net)
* 2011 Mark Liversedge (liversedge@gmail.com)
*
* 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
@@ -29,8 +30,7 @@
#include "Zones.h"
#include "HrZones.h"
HistogramWindow::HistogramWindow(MainWindow *mainWindow) :
GcWindow(mainWindow), mainWindow(mainWindow)
HistogramWindow::HistogramWindow(MainWindow *mainWindow) : GcWindow(mainWindow), mainWindow(mainWindow), source(NULL)
{
setInstanceName("Histogram Window");
@@ -60,119 +60,141 @@ HistogramWindow::HistogramWindow(MainWindow *mainWindow) :
binWidthLayout->addWidget(binWidthSlider);
cl->addLayout(binWidthLayout);
lnYHistCheckBox = new QCheckBox;
lnYHistCheckBox->setText(tr("Log Y"));
cl->addWidget(lnYHistCheckBox);
showLnY = new QCheckBox;
showLnY->setText(tr("Log Y"));
cl->addWidget(showLnY);
withZerosCheckBox = new QCheckBox;
withZerosCheckBox->setText(tr("With zeros"));
cl->addWidget(withZerosCheckBox);
showZeroes = new QCheckBox;
showZeroes->setText(tr("With zeros"));
cl->addWidget(showZeroes);
histShadeZones = new QCheckBox;
histShadeZones->setText(tr("Shade zones"));
cl->addWidget(histShadeZones);
shadeZones = new QCheckBox;
shadeZones->setText(tr("Shade zones"));
shadeZones->setChecked(powerHist->shade);
cl->addWidget(shadeZones);
histParameterCombo = new QComboBox();
histParameterCombo->addItem(tr("Watts"));
histParameterCombo->addItem(tr("Watts (by Zone)"));
histParameterCombo->addItem(tr("Torque"));
histParameterCombo->addItem(tr("Heartrate"));
histParameterCombo->addItem(tr("Heartrate (by Zone)"));
histParameterCombo->addItem(tr("Speed"));
histParameterCombo->addItem(tr("Cadence"));
histParameterCombo->setCurrentIndex(0);
cl->addWidget(histParameterCombo);
showInZones = new QCheckBox;
showInZones->setText(tr("Show in zones"));
cl->addWidget(showInZones);
histSumY = new QComboBox();
histSumY->addItem(tr("Absolute Time"));
histSumY->addItem(tr("Percentage Time"));
cComboSeason = new QComboBox(this);
seriesCombo = new QComboBox();
addSeries();
cl->addWidget(seriesCombo);
showSumY = new QComboBox();
showSumY->addItem(tr("Absolute Time"));
showSumY->addItem(tr("Percentage Time"));
seasonCombo = new QComboBox(this);
addSeasons();
cl->addWidget(histSumY);
cl->addWidget(cComboSeason);
seasonCombo->setCurrentIndex(0); // default to current ride selected
cl->addWidget(showSumY);
cl->addWidget(seasonCombo);
cl->addStretch();
// sort out default values
setHistTextValidator();
lnYHistCheckBox->setChecked(powerHist->islnY());
withZerosCheckBox->setChecked(powerHist->withZeros());
showLnY->setChecked(powerHist->islnY());
showZeroes->setChecked(powerHist->withZeros());
binWidthSlider->setValue(powerHist->binWidth());
setHistBinWidthText();
connect(binWidthSlider, SIGNAL(valueChanged(int)),
this, SLOT(setBinWidthFromSlider()));
connect(binWidthLineEdit, SIGNAL(editingFinished()),
this, SLOT(setBinWidthFromLineEdit()));
connect(lnYHistCheckBox, SIGNAL(stateChanged(int)),
this, SLOT(setlnYHistFromCheckBox()));
connect(withZerosCheckBox, SIGNAL(stateChanged(int)),
this, SLOT(setWithZerosFromCheckBox()));
connect(histParameterCombo, SIGNAL(currentIndexChanged(int)),
this, SLOT(setHistSelection(int)));
connect(histShadeZones, SIGNAL(stateChanged(int)),
this, SLOT(setHistSelection(int)));
connect(histSumY, SIGNAL(currentIndexChanged(int)),
this, SLOT(setSumY(int)));
//connect(mainWindow, SIGNAL(rideSelected()), this, SLOT(rideSelected()));
// set the defaults etc
updateChart();
// the bin slider/input update each other
// only the input box triggers an update to the chart
connect(binWidthSlider, SIGNAL(valueChanged(int)), this, SLOT(setBinWidthFromSlider()));
connect(binWidthLineEdit, SIGNAL(editingFinished()), this, SLOT(setBinWidthFromLineEdit()));
// when season changes we need to retrieve data from the cache then update the chart
connect(seasonCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(seasonSelected(int)));
// if any of the controls change we pass the chart everything
connect(showLnY, SIGNAL(stateChanged(int)), this, SLOT(updateChart()));
connect(showZeroes, SIGNAL(stateChanged(int)), this, SLOT(updateChart()));
connect(seriesCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateChart()));
connect(showInZones, SIGNAL(stateChanged(int)), this, SLOT(updateChart()));
connect(shadeZones, SIGNAL(stateChanged(int)), this, SLOT(updateChart()));
connect(showSumY, SIGNAL(currentIndexChanged(int)), this, SLOT(updateChart()));
connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideSelected()));
connect(mainWindow, SIGNAL(intervalSelected()), this, SLOT(intervalSelected()));
connect(mainWindow, SIGNAL(zonesChanged()), this, SLOT(zonesChanged()));
connect(mainWindow, SIGNAL(configChanged()), powerHist, SLOT(configChanged()));
connect(cComboSeason, SIGNAL(currentIndexChanged(int)), this, SLOT(seasonSelected(int)));
}
void
HistogramWindow::rideSelected()
{
if (!amVisible())
return;
if (!amVisible()) return;
RideItem *ride = myRideItem;
if (!ride)
return;
if (!ride || seasonCombo->currentIndex() != 0) return;
// get range that applies to this ride
powerRange = mainWindow->zones()->whichRange(ride->dateTime.date());
hrRange = mainWindow->hrZones()->whichRange(ride->dateTime.date());
// set the histogram data
powerHist->setData(ride);
// update
updateChart();
}
void
HistogramWindow::intervalSelected()
{
if (!amVisible())
return;
RideItem *ride = myRideItem;
if (!ride) return;
if (!amVisible()) return;
// set the histogram data
powerHist->setData(ride);
RideItem *ride = myRideItem;
// null? or not plotting current ride, ignore signal
if (!ride || seasonCombo->currentIndex() != 0) return;
// update
interval = true;
updateChart();
}
void
HistogramWindow::zonesChanged()
{
if (!amVisible())
return;
if (!amVisible()) return;
powerHist->refreshZoneLabels();
powerHist->replot();
}
void HistogramWindow::seasonSelected(int index)
{
RideFileCache *old = source;
if (index > 0) {
index--; // it is now an index into the season array
// Set data from BESTS
Season season = seasons.at(index);
QDate start = season.getStart();
QDate end = season.getEnd();
if (end == QDate()) end = QDate(3000,12,31);
if (start == QDate()) start = QDate(1900,1,1);
source = new RideFileCache(mainWindow, start, end);
} else if (!index) {
// Set data from RIDE
if (old) delete old; // guarantee source pointer changes
}
updateChart();
}
void HistogramWindow::addSeries()
{
// setup series list
seriesList << RideFile::watts
<< RideFile::hr
<< RideFile::kph
<< RideFile::cad
<< RideFile::nm;
foreach (RideFile::SeriesType x, seriesList)
seriesCombo->addItem(RideFile::seriesName(x), static_cast<int>(x));
}
void HistogramWindow::addSeasons()
{
QFile seasonFile(mainWindow->home.absolutePath() + "/seasons.xml");
@@ -182,52 +204,26 @@ void HistogramWindow::addSeasons()
xmlReader.setContentHandler(&handler);
xmlReader.setErrorHandler(&handler);
bool ok = xmlReader.parse( source );
if(!ok)
qWarning("Failed to parse seasons.xml");
if(!ok) qWarning("Failed to parse seasons.xml");
seasons = handler.getSeasons();
Season season;
season.setName(tr("All Seasons"));
seasons.insert(0,season);
cComboSeason->addItem("Selected Ride");
seasonCombo->addItem("Selected Ride");
foreach (Season season, seasons)
cComboSeason->addItem(season.getName());
if (!seasons.empty()) {
cComboSeason->setCurrentIndex(cComboSeason->count() - 1);
Season season = seasons.last();
// set default parameters here
// XXX todo
}
seasonCombo->addItem(season.getName());
}
void
HistogramWindow::setBinWidthFromSlider()
{
if (powerHist->binWidth() != binWidthSlider->value()) {
powerHist->setBinWidth(binWidthSlider->value());
setHistBinWidthText();
}
}
setHistBinWidthText();
void
HistogramWindow::setlnYHistFromCheckBox()
{
if (powerHist->islnY() != lnYHistCheckBox->isChecked())
powerHist->setlnY(! powerHist->islnY());
}
void
HistogramWindow::setSumY(int index)
{
if (index < 0) return; // being destroyed
else powerHist->setSumY(index == 0);
}
void
HistogramWindow::setWithZerosFromCheckBox()
{
if (powerHist->withZeros() != withZerosCheckBox->isChecked()) {
powerHist->setWithZeros(withZerosCheckBox->isChecked());
updateChart();
}
}
@@ -242,67 +238,21 @@ HistogramWindow::setHistTextValidator()
{
double delta = powerHist->getDelta();
int digits = powerHist->getDigits();
binWidthLineEdit->setValidator(
(digits == 0) ?
(QValidator *) (
new QIntValidator(binWidthSlider->minimum() * delta,
binWidthSlider->maximum() * delta,
binWidthLineEdit
)
) :
(QValidator *) (
new QDoubleValidator(binWidthSlider->minimum() * delta,
binWidthSlider->maximum() * delta,
digits,
binWidthLineEdit
)
)
);
}
QValidator *validator;
if (digits == 0) {
void
HistogramWindow::setHistSelection(int /*id*/)
{
// Set shading first, since the dataseries selection
// below will trigger a redraw, and we need to have
// set the shading beforehand. OK, so we could make
// either change trigger it, but this makes for simpler
// code here and in powerhist.cpp
if (histShadeZones->isChecked()) powerHist->setShading(true);
else powerHist->setShading(false);
validator = new QIntValidator(binWidthSlider->minimum() * delta,
binWidthSlider->maximum() * delta,
binWidthLineEdit);
} else {
// Which data series are we plotting?
switch (histParameterCombo->currentIndex()) {
default:
case 0 :
powerHist->setSelection(PowerHist::watts);
break;
case 1 :
powerHist->setSelection(PowerHist::wattsZone);
break;
case 2 :
powerHist->setSelection(PowerHist::nm);
break;
case 3 :
powerHist->setSelection(PowerHist::hr);
break;
case 4 :
powerHist->setSelection(PowerHist::hrZone);
break;
case 5 :
powerHist->setSelection(PowerHist::kph);
break;
case 6 :
powerHist->setSelection(PowerHist::cad);
validator = new QDoubleValidator(binWidthSlider->minimum() * delta,
binWidthSlider->maximum() * delta,
digits,
binWidthLineEdit);
}
setHistBinWidthText();
setHistTextValidator();
binWidthLineEdit->setValidator(validator);
}
void
@@ -310,7 +260,43 @@ HistogramWindow::setBinWidthFromLineEdit()
{
double value = binWidthLineEdit->text().toDouble();
if (value != powerHist->binWidth()) {
binWidthSlider->setValue(powerHist->setBinWidthRealUnits(value));
setHistBinWidthText();
binWidthSlider->setValue(powerHist->setBinWidthRealUnits(value));
setHistBinWidthText();
updateChart();
}
}
void
HistogramWindow::updateChart()
{
// set data
if (seasonCombo->currentIndex() != 0 && source)
powerHist->setData(source);
else if (seasonCombo->currentIndex() == 0 && myRideItem)
powerHist->setData(myRideItem, interval); // intervals selected forces data to
// be recomputed since interval selection
// has changed.
// and now the controls
powerHist->setShading(shadeZones->isChecked() ? true : false);
powerHist->setZoned(showInZones->isChecked() ? true : false);
powerHist->setlnY(showLnY->isChecked() ? true : false);
powerHist->setWithZeros(showZeroes->isChecked() ? true : false);
powerHist->setSumY(showSumY->currentIndex()== 0 ? true : false);
powerHist->setBinWidth(binWidthLineEdit->text().toDouble());
// and which series to plot
powerHist->setSeries(static_cast<RideFile::SeriesType>(seriesCombo->itemData(seriesCombo->currentIndex()).toInt()));
// now go plot yourself
//powerHist->setAxisTitle(int axis, QString label);
powerHist->recalc(interval); // interval changed? force recalc
powerHist->replot();
interval = false;// we force a recalc whem called coz intervals
// have been selected. The recalc routine in
// powerhist optimises out, but doesn't keep track
// of interval selection -- simplifies the setters
// and getters, so worth this 'hack'.
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright (c) 2009 Sean C. Rhea (srhea@srhea.net)
* 2011 Mark Liversedge (liversedge@gmail.com)
*
* 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
@@ -28,6 +29,7 @@
class MainWindow;
class PowerHist;
class RideItem;
class RideFileCache;
class HistogramWindow : public GcWindow
{
@@ -40,24 +42,30 @@ class HistogramWindow : public GcWindow
Q_PROPERTY(bool logY READ logY WRITE setLogY USER true)
Q_PROPERTY(bool zeroes READ zeroes WRITE setZeroes USER true)
Q_PROPERTY(bool shade READ shade WRITE setShade USER true)
Q_PROPERTY(bool zoned READ zoned WRITE setZoned USER true)
Q_PROPERTY(bool scope READ scope WRITE setScope USER true)
public:
HistogramWindow(MainWindow *mainWindow);
// get/set properties
int series() const { return histParameterCombo->currentIndex(); }
void setSeries(int x) { histParameterCombo->setCurrentIndex(x); }
int percent() const { return histSumY->currentIndex(); }
void setPercent(int x) { histSumY->setCurrentIndex(x); }
int series() const { return seriesCombo->currentIndex(); }
void setSeries(int x) { seriesCombo->setCurrentIndex(x); }
int percent() const { return showSumY->currentIndex(); }
void setPercent(int x) { showSumY->setCurrentIndex(x); }
double bin() const { return binWidthSlider->value(); }
void setBin(double x) { binWidthSlider->setValue(x); }
bool logY() const { return lnYHistCheckBox->isChecked(); }
void setLogY(bool x) { lnYHistCheckBox->setChecked(x); }
bool zeroes() const { return withZerosCheckBox->isChecked(); }
void setZeroes(bool x) { withZerosCheckBox->setChecked(x); }
bool shade() const { return histShadeZones->isChecked(); }
void setShade(bool x) { histShadeZones->setChecked(x); }
bool logY() const { return showLnY->isChecked(); }
void setLogY(bool x) { showLnY->setChecked(x); }
bool zeroes() const { return showZeroes->isChecked(); }
void setZeroes(bool x) { showZeroes->setChecked(x); }
bool shade() const { return shadeZones->isChecked(); }
void setShade(bool x) { shadeZones->setChecked(x); }
bool zoned() const { return showInZones->isChecked(); }
void setZoned(bool x) { return showInZones->setChecked(x); }
int scope() const { return seasonCombo->currentIndex(); }
void setScope(int x) { seasonCombo->setCurrentIndex(x); }
public slots:
@@ -69,13 +77,10 @@ class HistogramWindow : public GcWindow
void setBinWidthFromSlider();
void setBinWidthFromLineEdit();
void setlnYHistFromCheckBox();
void setWithZerosFromCheckBox();
void setHistSelection(int id);
void setSumY(int);
void seasonSelected(int season);
void updateChart();
protected:
private:
QList<Season> seasons;
void setHistTextValidator();
@@ -83,17 +88,25 @@ class HistogramWindow : public GcWindow
MainWindow *mainWindow;
PowerHist *powerHist;
QSlider *binWidthSlider;
QLineEdit *binWidthLineEdit;
QCheckBox *lnYHistCheckBox;
QCheckBox *withZerosCheckBox;
QCheckBox *histShadeZones;
QComboBox *histParameterCombo;
QComboBox *histSumY;
QComboBox *cComboSeason;
QSlider *binWidthSlider; // seet Bin Width from a slider
QLineEdit *binWidthLineEdit; // set Bin Width from the line edit
QCheckBox *showLnY; // set show as Log(y)
QCheckBox *showZeroes; // Include zeroes
QComboBox *showSumY; // ??
QCheckBox *shadeZones; // Shade zone background
QCheckBox *showInZones; // Plot by Zone
QComboBox *seriesCombo; // Which data series to plot
QComboBox *seasonCombo; // Plot for Date range or current ride
QList<RideFile::SeriesType> seriesList;
void addSeasons();
void addSeries();
int powerRange, hrRange;
RideFileCache *source;
bool interval;
};
#endif // _GC_HistogramWindow_h

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
/*
* Copyright (c) 2006 Sean C. Rhea (srhea@srhea.net)
* 2011 Mark Liversedge (liversedge@gmail.com)
*
* 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
@@ -19,6 +20,10 @@
#ifndef _GC_PowerHist_h
#define _GC_PowerHist_h 1
#include "GoldenCheetah.h"
#include "RideFile.h"
#include "MainWindow.h"
#include "Zones.h"
#include "HrZones.h"
#include <qwt_plot.h>
#include <qwt_plot_zoomer.h>
@@ -31,6 +36,7 @@ class QwtPlotGrid;
class MainWindow;
class RideItem;
class RideFilePoint;
class RideFileCache;
class PowerHistBackground;
class PowerHistZoneLabel;
class HrHistBackground;
@@ -41,167 +47,437 @@ class ZoneScaleDraw;
class penTooltip: public QwtPlotZoomer
{
public:
penTooltip(QwtPlotCanvas *canvas):
QwtPlotZoomer(canvas), tip("")
{
// With some versions of Qt/Qwt, setting this to AlwaysOn
// causes an infinite recursion.
//setTrackerMode(AlwaysOn);
setTrackerMode(AlwaysOn);
penTooltip(QwtPlotCanvas *canvas): QwtPlotZoomer(canvas), tip("") {
// With some versions of Qt/Qwt, setting this to AlwaysOn
// causes an infinite recursion.
//setTrackerMode(AlwaysOn);
setTrackerMode(AlwaysOn);
}
virtual QwtText trackerText(const QwtDoublePoint &/*pos*/) const
{
QColor bg = QColor(255,255, 170); // toolyip yellow
virtual QwtText trackerText(const QwtDoublePoint &/*pos*/) const {
QColor bg = QColor(255,255, 170); // toolyip yellow
#if QT_VERSION >= 0x040300
bg.setAlpha(200);
bg.setAlpha(200);
#endif
QwtText text;
QFont def;
//def.setPointSize(8); // too small on low res displays (Mac)
//double val = ceil(pos.y()*100) / 100; // round to 2 decimal place
//text.setText(QString("%1 %2").arg(val).arg(format), QwtText::PlainText);
text.setText(tip);
text.setFont(def);
text.setBackgroundBrush( QBrush( bg ));
text.setRenderFlags(Qt::AlignLeft | Qt::AlignTop);
return text;
}
void setFormat(QString fmt) { format = fmt; }
void setText(QString txt) { tip = txt; }
QwtText text;
QFont def;
//def.setPointSize(8); // too small on low res displays (Mac)
//double val = ceil(pos.y()*100) / 100; // round to 2 decimal place
//text.setText(QString("%1 %2").arg(val).arg(format), QwtText::PlainText);
text.setText(tip);
text.setFont(def);
text.setBackgroundBrush( QBrush( bg ));
text.setRenderFlags(Qt::AlignLeft | Qt::AlignTop);
return text;
}
void setFormat(QString fmt) { format = fmt; }
void setText(QString txt) { tip = txt; }
private:
QString format;
QString tip;
};
QString format;
QString tip;
};
class PowerHist : public QwtPlot
{
Q_OBJECT
G_OBJECT
friend class ::HrHistBackground;
friend class ::HrHistZoneLabel;
friend class ::PowerHistBackground;
friend class ::PowerHistZoneLabel;
friend class ::HistogramWindow;
public:
QwtPlotCurve *curve, *curveSelected;
QList <PowerHistZoneLabel *> zoneLabels;
QList <HrHistZoneLabel *> hrzoneLabels;
PowerHist(MainWindow *mainWindow);
~PowerHist();
~PowerHist();
int binWidth() const { return binw; }
inline bool islnY() const { return lny; }
inline bool withZeros() const { return withz; }
bool shadeZones() const;
bool shadeHRZones() const;
enum Selection {
watts,
wattsZone,
nm,
hr,
hrZone,
kph,
cad
} selected;
inline Selection selection() { return selected; }
bool shade;
inline bool shaded() const { return shade; }
void setData(RideItem *_rideItem);
void setSelection(Selection selection);
void fixSelection();
void setShading(bool x) { shade=x; }
void setBinWidth(int value);
double getDelta();
int getDigits();
double getBinWidthRealUnits();
int setBinWidthRealUnits(double value);
void refreshZoneLabels();
void refreshHRZoneLabels();
RideItem *rideItem;
MainWindow *mainWindow;
public slots:
// public setters
void setShading(bool x) { shade=x; }
void setSeries(RideFile::SeriesType series);
void setData(RideItem *_rideItem, bool force=false);
void setData(RideFileCache *source);
void setlnY(bool value);
void setWithZeros(bool value);
void setZoned(bool value);
void setSumY(bool value);
void pointHover(QwtPlotCurve *curve, int index);
void configChanged();
void setAxisTitle(int axis, QString label);
void setYMax();
void setBinWidth(int value);
int setBinWidthRealUnits(double value);
// public getters
double getDelta();
double getBinWidthRealUnits();
int getDigits();
inline bool islnY() const { return lny; }
inline bool withZeros() const { return withz; }
inline int binWidth() const { return binw; }
// react to plot signals
void pointHover(QwtPlotCurve *curve, int index);
// get told to refresh
void recalc(bool force=false);
void refreshZoneLabels();
protected:
QwtPlotGrid *grid;
void refreshHRZoneLabels();
void setParameterAxisTitle();
bool isSelected(const RideFilePoint *p, double);
void percentify(QVector<double> &, double factor); // and a function to convert
// storage for data counts
QVector<unsigned int>
wattsArray,
wattsZoneArray,
nmArray,
hrArray,
hrZoneArray,
kphArray,
cadArray;
// storage for data counts in interval selected
QVector<unsigned int>
wattsSelectedArray,
wattsZoneSelectedArray,
nmSelectedArray,
hrSelectedArray,
hrZoneSelectedArray,
kphSelectedArray,
cadSelectedArray;
bool shadeZones() const; // check if zone shading is both wanted and possible
bool shadeHRZones() const; // check if zone shading is both wanted and possible
// plot settings
RideItem *rideItem;
MainWindow *mainWindow;
RideFile::SeriesType series;
bool useMetricUnits; // whether metric units are used (or imperial)
QVariant unit;
bool lny;
bool shade;
bool zoned; // show in zones
int binw;
bool withz; // whether zeros are included in histogram
double dt; // length of sample
double dt; // length of sample
bool absolutetime; // do we sum absolute or percentage?
void recalc();
void setYMax();
penTooltip *zoomer;
private:
QVariant unit;
PowerHistBackground *bg;
HrHistBackground *hrbg;
// plot objects
QwtPlotGrid *grid;
PowerHistBackground *bg;
HrHistBackground *hrbg;
penTooltip *zoomer;
LTMCanvasPicker *canvasPicker;
QwtPlotCurve *curve, *curveSelected;
QList <PowerHistZoneLabel *> zoneLabels;
QList <HrHistZoneLabel *> hrzoneLabels;
bool lny;
// source cache
RideFileCache *cache;
// discritized unit for smoothing
// discritized unit for smoothing
static const double wattsDelta = 1.0;
static const double nmDelta = 0.1;
static const double hrDelta = 1.0;
static const double kphDelta = 0.1;
static const double cadDelta = 1.0;
static const double nmDelta = 0.1;
static const double hrDelta = 1.0;
static const double kphDelta = 0.1;
static const double cadDelta = 1.0;
// digits for text entry validator
// digits for text entry validator
static const int wattsDigits = 0;
static const int nmDigits = 1;
static const int hrDigits = 0;
static const int kphDigits = 1;
static const int cadDigits = 0;
static const int nmDigits = 1;
static const int hrDigits = 0;
static const int kphDigits = 1;
static const int cadDigits = 0;
void setParameterAxisTitle();
bool isSelected(const RideFilePoint *p, double);
// storage for data counts
QVector<unsigned int> wattsArray, wattsZoneArray, nmArray, hrArray,
hrZoneArray, kphArray, cadArray;
bool useMetricUnits; // whether metric units are used (or imperial)
// storage for data counts in interval selected
QVector<unsigned int> wattsSelectedArray, wattsZoneSelectedArray,
nmSelectedArray, hrSelectedArray,
hrZoneSelectedArray, kphSelectedArray,
cadSelectedArray;
bool absolutetime; // do we sum absolute or percentage?
void percentify(QVector<double> &, double factor); // and a function to convert
enum Source { Ride, Cache } source, LASTsource;
LTMCanvasPicker *canvasPicker;
// last plot settings - to avoid lots of uneeded recalcs
RideItem *LASTrideItem;
RideFileCache *LASTcache;
RideFile::SeriesType LASTseries;
bool LASTshade;
bool LASTuseMetricUnits; // whether metric units are used (or imperial)
bool LASTlny;
bool LASTzoned; // show in zones
int LASTbinw;
bool LASTwithz; // whether zeros are included in histogram
double LASTdt; // length of sample
bool LASTabsolutetime; // do we sum absolute or percentage?
};
/*----------------------------------------------------------------------
* From here to the end of source file the routines for zone shading
*--------------------------------------------------------------------*/
// define a background class to handle shading of power zones
// draws power zone bands IF zones are defined and the option
// to draw bonds has been selected
class PowerHistBackground: public QwtPlotItem
{
private:
PowerHist *parent;
public:
PowerHistBackground(PowerHist *_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->shadeZones() && (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.right() >= r.left())
painter->fillRect(r, shading_color);
}
}
}
}
};
// Zone labels are drawn if power zone bands are enabled, automatically
// at the center of the plot
class PowerHistZoneLabel: public QwtPlotItem
{
private:
PowerHist *parent;
int zone_number;
double watts;
QwtText text;
public:
PowerHistZoneLabel(PowerHist *_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();
setZ(1.0 + zone_number / 100.0);
// create new zone labels if we're shading
if (parent->shadeZones() && (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);
}
}
}
virtual int rtti() const
{
return QwtPlotItem::Rtti_PlotUserItem;
}
void draw(QPainter *painter,
const QwtScaleMap &xMap, const QwtScaleMap &,
const QRect &rect) const
{
if (parent->shadeZones()) {
int x = xMap.transform(watts);
int y = (rect.bottom() + rect.top()) / 2;
// the following code based on source for QwtPlotMarker::draw()
QRect tr(QPoint(0, 0), text.textSize(painter->font()));
tr.moveCenter(QPoint(y, -x));
painter->rotate(90); // rotate text to avoid overlap: this needs to be fixed
text.draw(painter, tr);
}
}
};
// define a background class to handle shading of HR zones
// draws power zone bands IF zones are defined and the option
// to draw bonds has been selected
class HrHistBackground: public QwtPlotItem
{
private:
PowerHist *parent;
public:
HrHistBackground(PowerHist *_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 HrZones *zones = parent->mainWindow->hrZones();
int zone_range = rideItem->hrZoneRange();
if (parent->shadeHRZones() && (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 =
hrZoneColor(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.right() >= r.left())
painter->fillRect(r, shading_color);
}
}
}
}
};
// Zone labels are drawn if power zone bands are enabled, automatically
// at the center of the plot
class HrHistZoneLabel: public QwtPlotItem
{
private:
PowerHist *parent;
int zone_number;
double watts;
QwtText text;
public:
HrHistZoneLabel(PowerHist *_parent, int _zone_number)
{
parent = _parent;
zone_number = _zone_number;
RideItem *rideItem = parent->rideItem;
if (! rideItem)
return;
const HrZones *zones = parent->mainWindow->hrZones();
int zone_range = rideItem->hrZoneRange();
setZ(1.0 + zone_number / 100.0);
// create new zone labels if we're shading
if (parent->shadeHRZones() && (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 = hrZoneColor(zone_number, num_zones);
text_color.setAlpha(64);
text.setColor(text_color);
}
}
}
virtual int rtti() const
{
return QwtPlotItem::Rtti_PlotUserItem;
}
void draw(QPainter *painter,
const QwtScaleMap &xMap, const QwtScaleMap &,
const QRect &rect) const
{
if (parent->shadeHRZones()) {
int x = xMap.transform(watts);
int y = (rect.bottom() + rect.top()) / 2;
// the following code based on source for QwtPlotMarker::draw()
QRect tr(QPoint(0, 0), text.textSize(painter->font()));
tr.moveCenter(QPoint(y, -x));
painter->rotate(90); // rotate text to avoid overlap: this needs to be fixed
text.draw(painter, tr);
}
}
};
#endif // _GC_PowerHist_h

View File

@@ -161,6 +161,37 @@ RideFileCache::meanMaxArray(RideFile::SeriesType series)
}
}
QVector<double> &
RideFileCache::distributionArray(RideFile::SeriesType series)
{
switch (series) {
case RideFile::watts:
return wattsDistributionDouble;
break;
case RideFile::cad:
return cadDistributionDouble;
break;
case RideFile::hr:
return hrDistributionDouble;
break;
case RideFile::nm:
return nmDistributionDouble;
break;
case RideFile::kph:
return kphDistributionDouble;
break;
default:
//? dunno give em power anyway
return wattsMeanMaxDouble;
break;
}
}
//
// COMPUTATION
@@ -550,7 +581,7 @@ RideFileCache::computeDistribution(QVector<unsigned long> &array, RideFile::Seri
unsigned long lvalue = value * pow(10, decimals);
int offset = lvalue - min;
if (offset >= 0 && offset < array.size()) array[offset]++; // XXX recintsecs != 1
if (offset >= 0 && offset < array.size()) array[offset] += ride->recIntSecs();
}
}
@@ -584,7 +615,8 @@ static void meanMaxAggregate(QVector<double> &into, QVector<double> &other, QVec
// resize into and then sum the arrays
static void distAggregate(QVector<double> &into, QVector<double> &other)
{
for (int i=0; i<into.size(); i++) into[i] += other[i];
if (into.size() < other.size()) into.resize(other.size());
for (int i=0; i<other.size(); i++) into[i] += other[i];
}
RideFileCache::RideFileCache(MainWindow *main, QDate start, QDate end)