mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 16:39:57 +00:00
Plot more data on the CP plot and use a binary cache file
This patch enables more data series to be plotted on a CP plot. We can now show curves for heartrate, cadence, speed and torque as well as the original power and energy. The CP code is refactored into the plotting functions and a new RideFileCache that precomputes the mean-max as well as distribution data (for a later patch to show histograms across date ranges). The code for computing mean-max values has been re-written and significantly optimised by; * computing 1s intervals up to 5mins only * computing 20s intervals for the remainder of the ride * downsampling data to 5s samples for longer durations * using a binary file format (cpx) for faster read/aggregation * using multiple threads Testing on an old Athlon dual-core showed an increase in performance over the old cpi code of approximately x20, but since new data series are now computed it is only x4 faster. Quad/Octo core systems will show a greater performance increase though.
This commit is contained in:
@@ -73,16 +73,15 @@ CriticalPowerWindow::CriticalPowerWindow(const QDir &home, MainWindow *parent) :
|
||||
cl->addLayout(cpintPickerLayout);
|
||||
|
||||
// tools /properties
|
||||
seriesCombo = new QComboBox(this);
|
||||
addSeries();
|
||||
cComboSeason = new QComboBox(this);
|
||||
addSeasons();
|
||||
cpintSetCPButton = new QPushButton(tr("&Save CP value"), this);
|
||||
cpintSetCPButton->setEnabled(false);
|
||||
cl->addWidget(cpintSetCPButton);
|
||||
cl->addWidget(cComboSeason);
|
||||
yAxisCombo = new QComboBox(this);
|
||||
yAxisCombo->addItem(tr("Y Axis Shows Power"));
|
||||
yAxisCombo->addItem(tr("Y Axis Shows Energy"));
|
||||
cl->addWidget(yAxisCombo);
|
||||
cl->addWidget(seriesCombo);
|
||||
cl->addStretch();
|
||||
|
||||
picker = new QwtPlotPicker(QwtPlot::xBottom, QwtPlot::yLeft,
|
||||
@@ -91,16 +90,11 @@ CriticalPowerWindow::CriticalPowerWindow(const QDir &home, MainWindow *parent) :
|
||||
QwtPicker::AlwaysOff, cpintPlot->canvas());
|
||||
picker->setRubberBandPen(GColor(CPLOTTRACKER));
|
||||
|
||||
connect(picker, SIGNAL(moved(const QPoint &)),
|
||||
SLOT(pickerMoved(const QPoint &)));
|
||||
connect(cpintTimeValue, SIGNAL(editingFinished()),
|
||||
this, SLOT(cpintTimeValueEntered()));
|
||||
connect(cpintSetCPButton, SIGNAL(clicked()),
|
||||
this, SLOT(cpintSetCPButtonClicked()));
|
||||
connect(cComboSeason, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(seasonSelected(int)));
|
||||
connect(yAxisCombo, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(setEnergyMode(int)));
|
||||
connect(picker, SIGNAL(moved(const QPoint &)), SLOT(pickerMoved(const QPoint &)));
|
||||
connect(cpintTimeValue, SIGNAL(editingFinished()), this, SLOT(cpintTimeValueEntered()));
|
||||
connect(cpintSetCPButton, SIGNAL(clicked()), this, SLOT(cpintSetCPButtonClicked()));
|
||||
connect(cComboSeason, SIGNAL(currentIndexChanged(int)), this, SLOT(seasonSelected(int)));
|
||||
connect(seriesCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setSeries(int)));
|
||||
//connect(mainWindow, SIGNAL(rideSelected()), this, SLOT(rideSelected()));
|
||||
connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideSelected()));
|
||||
connect(mainWindow, SIGNAL(configChanged()), cpintPlot, SLOT(configChanged()));
|
||||
@@ -112,14 +106,7 @@ CriticalPowerWindow::CriticalPowerWindow(const QDir &home, MainWindow *parent) :
|
||||
void
|
||||
CriticalPowerWindow::newRideAdded()
|
||||
{
|
||||
cpintPlot->needToScanRides = true;
|
||||
}
|
||||
|
||||
void
|
||||
CriticalPowerWindow::deleteCpiFile(QString rideFilename)
|
||||
{
|
||||
cpintPlot->deleteCpiFile(home.absolutePath() + "/" +
|
||||
ride_filename_to_cpi_filename(rideFilename));
|
||||
// XXX
|
||||
}
|
||||
|
||||
void
|
||||
@@ -138,10 +125,12 @@ CriticalPowerWindow::rideSelected()
|
||||
}
|
||||
|
||||
void
|
||||
CriticalPowerWindow::setEnergyMode(int index)
|
||||
CriticalPowerWindow::setSeries(int index)
|
||||
{
|
||||
cpintPlot->setEnergyMode(index != 0);
|
||||
cpintPlot->calculate(currentRide);
|
||||
if (index >= 0) {
|
||||
cpintPlot->setSeries(static_cast<RideFile::SeriesType>(seriesCombo->itemData(index).toInt()));
|
||||
cpintPlot->calculate(currentRide);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -187,12 +176,43 @@ curve_to_point(double x, const QwtPlotCurve *curve)
|
||||
void
|
||||
CriticalPowerWindow::updateCpint(double minutes)
|
||||
{
|
||||
QString units;
|
||||
|
||||
switch (series()) {
|
||||
|
||||
case RideFile::none:
|
||||
units = "kJ";
|
||||
break;
|
||||
|
||||
case RideFile::cad:
|
||||
units = "rpm";
|
||||
break;
|
||||
|
||||
case RideFile::kph:
|
||||
units = "kph";
|
||||
break;
|
||||
|
||||
case RideFile::hr:
|
||||
units = "bpm";
|
||||
break;
|
||||
|
||||
case RideFile::nm:
|
||||
units = "nm";
|
||||
break;
|
||||
|
||||
default:
|
||||
case RideFile::watts:
|
||||
units = "Watts";
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// current ride
|
||||
{
|
||||
unsigned watts = curve_to_point(minutes, cpintPlot->getThisCurve());
|
||||
unsigned value = curve_to_point(minutes, cpintPlot->getThisCurve());
|
||||
QString label;
|
||||
if (watts > 0)
|
||||
label = QString(cpintPlot->energyMode() ? "%1 kJ" : "%1 watts").arg(watts);
|
||||
if (value > 0)
|
||||
label = QString("%1 %2").arg(value).arg(units);
|
||||
else
|
||||
label = tr("no data");
|
||||
cpintTodayValue->setText(label);
|
||||
@@ -200,10 +220,10 @@ CriticalPowerWindow::updateCpint(double minutes)
|
||||
|
||||
// cp line
|
||||
if (cpintPlot->getCPCurve()) {
|
||||
unsigned watts = curve_to_point(minutes, cpintPlot->getCPCurve());
|
||||
unsigned value = curve_to_point(minutes, cpintPlot->getCPCurve());
|
||||
QString label;
|
||||
if (watts > 0)
|
||||
label = QString(cpintPlot->energyMode() ? "%1 kJ" : "%1 watts").arg(watts);
|
||||
if (value > 0)
|
||||
label = QString("%1 %2").arg(value).arg(units);
|
||||
else
|
||||
label = tr("no data");
|
||||
cpintCPValue->setText(label);
|
||||
@@ -213,14 +233,14 @@ CriticalPowerWindow::updateCpint(double minutes)
|
||||
{
|
||||
QString label;
|
||||
int index = (int) ceil(minutes * 60);
|
||||
if (cpintPlot->getBests().count() > index) {
|
||||
if (index >= 0 && cpintPlot->getBests().count() > index) {
|
||||
QDate date = cpintPlot->getBestDates()[index];
|
||||
unsigned watts = cpintPlot->getBests()[index];
|
||||
if (cpintPlot->energyMode())
|
||||
unsigned value = cpintPlot->getBests()[index];
|
||||
#if 0
|
||||
label = QString("%1 kJ (%2)").arg(watts * minutes * 60.0 / 1000.0, 0, 'f', 0);
|
||||
else
|
||||
label = QString("%1 watts (%2)").arg(watts);
|
||||
label = label.arg(date.isValid() ? date.toString(tr("MM/dd/yyyy")) : tr("no date"));
|
||||
#endif
|
||||
label = QString("%1 %2 (%3)").arg(value).arg(units)
|
||||
.arg(date.isValid() ? date.toString(tr("MM/dd/yyyy")) : tr("no date"));
|
||||
}
|
||||
else {
|
||||
label = tr("no data");
|
||||
@@ -243,6 +263,27 @@ CriticalPowerWindow::pickerMoved(const QPoint &pos)
|
||||
cpintTimeValue->setText(interval_to_str(60.0*minutes));
|
||||
updateCpint(minutes);
|
||||
}
|
||||
void CriticalPowerWindow::addSeries()
|
||||
{
|
||||
// setup series list
|
||||
seriesList << RideFile::watts
|
||||
#if 0 // need to work out the best algorithm
|
||||
<< RideFile::xPower
|
||||
<< RideFile::NP
|
||||
#endif
|
||||
<< RideFile::hr
|
||||
<< RideFile::kph
|
||||
<< RideFile::cad
|
||||
<< RideFile::nm
|
||||
<< RideFile::none; // XXX actually this shows energy (hack)
|
||||
|
||||
foreach (RideFile::SeriesType x, seriesList) {
|
||||
if (x==RideFile::none)
|
||||
seriesCombo->addItem(tr("Energy"), static_cast<int>(x));
|
||||
else
|
||||
seriesCombo->addItem(RideFile::seriesName(x), static_cast<int>(x));
|
||||
}
|
||||
}
|
||||
|
||||
void CriticalPowerWindow::addSeasons()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user