Files
GoldenCheetah/src/HistogramWindow.cpp
Alejandro Martinez 078d4ac5ad Add support for running power zones (Stryd) part 3
Final part: use the zones according to sport in metrics and charts
2015-12-19 19:08:33 -03:00

1139 lines
34 KiB
C++

/*
* 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
* 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 "HistogramWindow.h"
#include "Specification.h"
#include "HelpWhatsThis.h"
// predefined deltas for each series
static const double wattsDelta = 1.0;
static const double wattsKgDelta = 0.01;
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 wbalDelta = 1.0;
static const double gearDelta = 0.01; //RideFileCache creates POW(10) * decimals sections
// digits for text entry validator
static const int wattsDigits = 0;
static const int wbalDigits = 0;
static const int wattsKgDigits = 2;
static const int nmDigits = 1;
static const int hrDigits = 0;
static const int kphDigits = 1;
static const int cadDigits = 0;
static const int gearDigits = 2;
//
// Constructor
//
HistogramWindow::HistogramWindow(Context *context, bool rangemode) : GcChartWindow(context), context(context), stale(true), source(NULL), active(false), bactive(false), rangemode(rangemode), compareStale(false), useCustom(false), useToToday(false), precision(99)
{
QWidget *c = new QWidget;
c->setContentsMargins(0,0,0,0);
HelpWhatsThis *helpConfig = new HelpWhatsThis(c);
if (rangemode) c->setWhatsThis(helpConfig->getWhatsThisText(HelpWhatsThis::ChartTrends_Distribution));
else c->setWhatsThis(helpConfig->getWhatsThisText(HelpWhatsThis::ChartRides_Histogram));
QFormLayout *cl = new QFormLayout(c);
cl->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
cl->setSpacing(5);
setControls(c);
//
// reveal controls widget
//
// reveal controls
rWidth = new QLabel(tr("Bin Width"));
rBinEdit = new QLineEdit();
rBinEdit->setFixedWidth(40);
rBinSlider = new QSlider(Qt::Horizontal);
rBinSlider->setTickPosition(QSlider::TicksBelow);
rBinSlider->setTickInterval(10);
rBinSlider->setMinimum(1);
rBinSlider->setMaximum(100);
rShade = new QCheckBox(tr("Shade zones"));
rZones = new QCheckBox(tr("Show in zones"));
// layout reveal controls
QHBoxLayout *r = new QHBoxLayout;
r->setContentsMargins(0,0,0,0);
r->addStretch();
r->addWidget(rWidth);
r->addWidget(rBinEdit);
r->addWidget(rBinSlider);
QVBoxLayout *v = new QVBoxLayout;
v->addWidget(rShade);
v->addWidget(rZones);
r->addSpacing(20);
r->addLayout(v);
r->addStretch();
setRevealLayout(r);
// plot
QVBoxLayout *vlayout = new QVBoxLayout;
vlayout->setSpacing(10);
powerHist = new PowerHist(context, rangemode);
vlayout->addWidget(powerHist);
HelpWhatsThis *help = new HelpWhatsThis(powerHist);
if (rangemode) powerHist->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::ChartTrends_Distribution));
else powerHist->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::ChartRides_Histogram));
setChartLayout(vlayout);
// search filter box
isfiltered = false;
searchBox = new SearchFilterBox(this, context);
connect(searchBox, SIGNAL(searchClear()), this, SLOT(clearFilter()));
connect(searchBox, SIGNAL(searchResults(QStringList)), this, SLOT(setFilter(QStringList)));
if (!rangemode) searchBox->hide();
else {
cl->addRow(new QLabel(tr("Filter")), searchBox);
cl->addWidget(new QLabel(""));
}
HelpWhatsThis *searchHelp = new HelpWhatsThis(searchBox);
searchBox->setWhatsThis(searchHelp->getWhatsThisText(HelpWhatsThis::SearchFilterBox));
// date selection
dateSetting = new DateSettingsEdit(this);
HelpWhatsThis *dateSettingHelp = new HelpWhatsThis(dateSetting);
dateSetting->setWhatsThis(dateSettingHelp->getWhatsThisText(HelpWhatsThis::ChartTrends_DateRange));
if (rangemode) {
cl->addRow(new QLabel(tr("Date Range")), dateSetting);
cl->addWidget(new QLabel("")); // spacing
// default to data series!
data = new QRadioButton(tr("Data Samples"));
metric = new QRadioButton(tr("Metrics"));
data->setChecked(true);
metric->setChecked(false);
QHBoxLayout *radios = new QHBoxLayout;
radios->addWidget(data);
radios->addWidget(metric);
cl->addRow(new QLabel(tr("Plot")), radios);
connect(data, SIGNAL(toggled(bool)), this, SLOT(dataToggled(bool)));
connect(metric, SIGNAL(toggled(bool)), this, SLOT(metricToggled(bool)));
}
// data series
seriesCombo = new QComboBox();
addSeries();
if (rangemode) comboLabel = new QLabel("");
else comboLabel = new QLabel(tr("Data Series"));
cl->addRow(comboLabel, seriesCombo);
if (rangemode) {
// TOTAL METRIC
totalMetricTree = new QTreeWidget;
#ifdef Q_OS_MAC
totalMetricTree->setAttribute(Qt::WA_MacShowFocusRect, 0);
#endif
totalMetricTree->setColumnCount(2);
totalMetricTree->setColumnHidden(1, true);
totalMetricTree->setSelectionMode(QAbstractItemView::SingleSelection);
totalMetricTree->header()->hide();
//totalMetricTree->setFrameStyle(QFrame::NoFrame);
//totalMetricTree->setAlternatingRowColors (true);
totalMetricTree->setIndentation(5);
totalMetricTree->setContextMenuPolicy(Qt::CustomContextMenu);
// ALL METRIC
distMetricTree = new QTreeWidget;
#ifdef Q_OS_MAC
distMetricTree->setAttribute(Qt::WA_MacShowFocusRect, 0);
#endif
distMetricTree->setColumnCount(2);
distMetricTree->setColumnHidden(1, true);
distMetricTree->setSelectionMode(QAbstractItemView::SingleSelection);
distMetricTree->header()->hide();
//distMetricTree->setFrameStyle(QFrame::NoFrame);
distMetricTree->setIndentation(5);
distMetricTree->setContextMenuPolicy(Qt::CustomContextMenu);
// add them all
const RideMetricFactory &factory = RideMetricFactory::instance();
for (int i = 0; i < factory.metricCount(); ++i) {
const RideMetric *m = factory.rideMetric(factory.metricName(i));
QTextEdit processHTML(m->name()); // process html encoding of(TM)
QString processed = processHTML.toPlainText();
QTreeWidgetItem *add;
add = new QTreeWidgetItem(distMetricTree->invisibleRootItem());
add->setText(0, processed);
add->setText(1, m->symbol());
// we only want totalising metrics
if (m->type() != RideMetric::Total) continue;
add = new QTreeWidgetItem(totalMetricTree->invisibleRootItem());
add->setText(0, processed);
add->setText(1, m->symbol());
}
QHBoxLayout *labels = new QHBoxLayout;
metricLabel1 = new QLabel(tr("Total (x-axis)"));
labels->addWidget(metricLabel1);
metricLabel1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
metricLabel2 = new QLabel(tr("Distribution (y-axis)"));
metricLabel2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
labels->addWidget(metricLabel2);
cl->addRow((blankLabel1=new QLabel("")), labels);
QHBoxLayout *trees = new QHBoxLayout;
trees->addWidget(totalMetricTree);
trees->addWidget(distMetricTree);
cl->addRow((blankLabel2 = new QLabel("")), trees);
colorButton = new ColorButton(this, "Color", QColor(Qt::blue));
colorLabel = new QLabel(tr("Color"));
connect(colorButton, SIGNAL(colorChosen(QColor)), this, SLOT(updateChart()));
cl->addRow(colorLabel, colorButton);
// by default select number of rides by duration
// which are the metrics workout_time and ride_count
selectTotal("ride_count");
selectMetric("workout_time");
}
showSumY = new QComboBox();
showSumY->addItem(tr("Absolute Time"));
showSumY->addItem(tr("Percentage Time"));
showLabel = new QLabel(tr("Show"));
cl->addRow(showLabel, showSumY);
showLnY = new QCheckBox;
showLnY->setText(tr("Log Y"));
cl->addRow(blankLabel3 = new QLabel(""), showLnY);
showZeroes = new QCheckBox;
showZeroes->setText(tr("With zeros"));
cl->addRow(blankLabel4 = new QLabel(""), showZeroes);
shadeZones = new QCheckBox;
shadeZones->setText(tr("Shade zones"));
cl->addRow(blankLabel5 = new QLabel(""), shadeZones);
showInZones = new QCheckBox;
showInZones->setText(tr("Show in zones"));
cl->addRow(blankLabel6 = new QLabel(""), showInZones);
showInCPZones = new QCheckBox;
showInCPZones->setText(tr("Use polarised zones"));
cl->addRow(blankLabel7 = new QLabel(""), showInCPZones);
// bin width
QHBoxLayout *binWidthLayout = new QHBoxLayout;
QLabel *binWidthLabel = new QLabel(tr("Bin width"), this);
binWidthLineEdit = new QLineEdit(this);
binWidthLineEdit->setFixedWidth(40);
binWidthLayout->addWidget(binWidthLineEdit);
binWidthSlider = new QSlider(Qt::Horizontal);
binWidthSlider->setTickPosition(QSlider::TicksBelow);
binWidthSlider->setTickInterval(1);
binWidthSlider->setMinimum(1);
binWidthSlider->setMaximum(100);
binWidthLayout->addWidget(binWidthSlider);
cl->addRow(binWidthLabel, binWidthLayout);
// sort out default values
setBinEditors();
showLnY->setChecked(powerHist->islnY());
showZeroes->setChecked(powerHist->withZeros());
shadeZones->setChecked(powerHist->shade);
binWidthSlider->setValue(powerHist->binWidth());
rBinSlider->setValue(powerHist->binWidth());
rShade->setChecked(powerHist->shade);
// fixup series selected by default
seriesChanged();
// hide/show according to default mode
switchMode(); // does nothing if not in rangemode
// 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()));
connect(rBinSlider, SIGNAL(valueChanged(int)), this, SLOT(setrBinWidthFromSlider()));
connect(rBinEdit, SIGNAL(editingFinished()), this, SLOT(setrBinWidthFromLineEdit()));
connect(rZones, SIGNAL(stateChanged(int)), this, SLOT(setZoned(int)));
connect(rShade, SIGNAL(stateChanged(int)), this, SLOT(setShade(int)));
// when season changes we need to retrieve data from the cache then update the chart
if (rangemode) {
connect(this, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateRangeChanged(DateRange)));
connect(dateSetting, SIGNAL(useCustomRange(DateRange)), this, SLOT(useCustomRange(DateRange)));
connect(dateSetting, SIGNAL(useThruToday()), this, SLOT(useThruToday()));
connect(dateSetting, SIGNAL(useStandardRange()), this, SLOT(useStandardRange()));
connect(distMetricTree, SIGNAL(itemSelectionChanged()), this, SLOT(treeSelectionChanged()));
connect(totalMetricTree, SIGNAL(itemSelectionChanged()), this, SLOT(treeSelectionChanged()));
// replot when background refresh is progressing
connect(context, SIGNAL(refreshUpdate(QDate)), this, SLOT(refreshUpdate(QDate)));
// comparing things
connect(context, SIGNAL(compareDateRangesStateChanged(bool)), this, SLOT(compareChanged()));
connect(context, SIGNAL(compareDateRangesChanged()), this, SLOT(compareChanged()));
} else {
dateSetting->hide();
connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideSelected()));
connect(context, SIGNAL(rideChanged(RideItem*)), this, SLOT(forceReplot()));
connect(context, SIGNAL(intervalSelected()), this, SLOT(intervalSelected()));
connect(context, SIGNAL(intervalHover(IntervalItem*)), powerHist, SLOT(intervalHover(IntervalItem*)));
// comparing things
connect(context, SIGNAL(compareIntervalsStateChanged(bool)), this, SLOT(compareChanged()));
connect(context, SIGNAL(compareIntervalsChanged()), this, SLOT(compareChanged()));
}
// if any of the controls change we pass the chart everything
connect(showLnY, SIGNAL(stateChanged(int)), this, SLOT(forceReplot()));
connect(showZeroes, SIGNAL(stateChanged(int)), this, SLOT(forceReplot()));
connect(seriesCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(seriesChanged()));
connect(showInCPZones, SIGNAL(stateChanged(int)), this, SLOT(setCPZoned(int)));
connect(showInCPZones, SIGNAL(stateChanged(int)), this, SLOT(forceReplot()));
connect(showInZones, SIGNAL(stateChanged(int)), this, SLOT(setZoned(int)));
connect(showInZones, SIGNAL(stateChanged(int)), this, SLOT(forceReplot()));
connect(shadeZones, SIGNAL(stateChanged(int)), this, SLOT(setShade(int)));
connect(shadeZones, SIGNAL(stateChanged(int)), this, SLOT(forceReplot()));
connect(showSumY, SIGNAL(currentIndexChanged(int)), this, SLOT(forceReplot()));
connect(context->athlete, SIGNAL(zonesChanged()), this, SLOT(zonesChanged()));
connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32)));
connect(context, SIGNAL(rideAdded(RideItem*)), this, SLOT(rideAddorRemove(RideItem*)));
connect(context, SIGNAL(rideDeleted(RideItem*)), this, SLOT(rideAddorRemove(RideItem*)));
connect(context, SIGNAL(rideSaved(RideItem*)), this, SLOT(rideAddorRemove(RideItem*)));
connect(context, SIGNAL(filterChanged()), this, SLOT(forceReplot()));
connect(context, SIGNAL(homeFilterChanged()), this, SLOT(forceReplot()));
// set colors etc
configChanged(CONFIG_APPEARANCE);
}
void
HistogramWindow::configChanged(qint32 state)
{
if (!rangemode) setProperty("color", GColor(CPLOTBACKGROUND)); // called on config change
else setProperty("color", GColor(CTRENDPLOTBACKGROUND)); // called on config change
powerHist->configChanged(state);
}
bool
HistogramWindow::isCompare() const
{
// compare intervals ?
if (!rangemode && context->isCompareIntervals) return true;
// compare date ranges ?
if (rangemode && context->isCompareDateRanges) return true;
return false;
}
void
HistogramWindow::compareChanged()
{
stale = true; // the 'standard' plots will need to be updated
compareStale = true;
if (!isVisible()) return;
setUpdatesEnabled(false);
// to stop getting into an infinite loop
// when turning off coimpare mode and using
// rideSelected to refresh
compareStale = false;
if (isCompare()) {
// is blank?
setIsBlank(false); // current ride irrelevant!
// hide normal curves
powerHist->hideStandard(true);
// now set the controls
RideFile::SeriesType series = static_cast<RideFile::SeriesType>
(seriesCombo->itemData(seriesCombo->currentIndex()).toInt());
powerHist->setSeries(series);
// and now the controls
powerHist->setShading(shadeZones->isChecked() ? true : false);
powerHist->setZoned(showInZones->isChecked() ? true : false);
powerHist->setCPZoned(showInCPZones->isChecked() ? true : false);
powerHist->setlnY(showLnY->isChecked() ? true : false);
powerHist->setWithZeros(showZeroes->isChecked() ? true : false);
powerHist->setSumY(showSumY->currentIndex()== 0 ? true : false);
// set data and create empty curves
if (!rangemode || data->isChecked()) {
// using the bests (ride file cache)
powerHist->setDataFromCompare();
} else {
// using the metric arrays
powerHist->setDelta(getDelta());
powerHist->setDigits(getDigits());
powerHist->setDataFromCompare(totalMetric(), distMetric());
}
powerHist->recalcCompare();
} else {
// show our normal curves and wipe rest
powerHist->hideStandard(false);
rideSelected(); // back to where we were
}
// replot!
powerHist->replot();
// repaint (in case optimised out)
repaint();
// and we're done
setUpdatesEnabled(true);
}
//
// Colors (used by metric plotting)
//
QString HistogramWindow::plotColor() const
{
if (!rangemode) return QColor(Qt::blue).name();
else {
return colorButton->getColor().name();
}
}
void HistogramWindow::setPlotColor(QString color)
{
if (rangemode) colorButton->setColor(QColor(color));
}
//
// Set Bin Width property by setting the slider
//
void HistogramWindow::setBin(double x)
{
if (bactive) return;
bactive = true;
binWidthSlider->setValue(x/getDelta());
rBinSlider->setValue(x/getDelta());
binWidthLineEdit->setText(QString("%1").arg(x, 0, 'f', getDigits()));
rBinEdit->setText(QString("%1").arg(x, 0, 'f', getDigits()));
powerHist->setBinWidth(x);
bactive = false;
// redraw
stale = true;
updateChart();
}
//
// Get/Set the metric selections
//
QString HistogramWindow::distMetric() const
{
if (!rangemode) return "workout_time";
else {
// get current selection
QList<QTreeWidgetItem *> select = distMetricTree->selectedItems();
if (select.count() == 0) return "workout_time";
else return select[0]->text(1);
}
}
void HistogramWindow::setDistMetric(QString value)
{
if (!rangemode) return;
selectMetric(value);
}
QString HistogramWindow::totalMetric() const
{
if (!rangemode) return "ride_count";
else {
// get current selection
QList<QTreeWidgetItem *> select = totalMetricTree->selectedItems();
if (select.count() == 0) return "ride_count";
else return select[0]->text(1);
}
}
void HistogramWindow::setTotalMetric(QString value)
{
if (!rangemode) return;
selectTotal(value);
}
void
HistogramWindow::selectTotal(QString symbol)
{
QList<QTreeWidgetItem *> found = totalMetricTree->findItems(symbol, Qt::MatchRecursive|Qt::MatchExactly, 1);
if (found.count() == 0) return;
// clear the current selection
foreach(QTreeWidgetItem *selected, totalMetricTree->selectedItems()) selected->setSelected(false);
// select the first one (there shouldn't be more than that!!!
found[0]->setSelected(true);
// make sure it is the current item and visible
totalMetricTree->setCurrentItem(found[0]);
totalMetricTree->scrollToItem(totalMetricTree->currentItem());
}
void
HistogramWindow::selectMetric(QString symbol)
{
QList<QTreeWidgetItem *> found = distMetricTree->findItems(symbol, Qt::MatchRecursive|Qt::MatchExactly, 1);
if (found.count() == 0) return;
// clear the current selection
foreach(QTreeWidgetItem *selected, distMetricTree->selectedItems()) selected->setSelected(false);
// select the first one (there shouldn't be more than that!!!
found[0]->setSelected(true);
// make sure it is the current item and visible
distMetricTree->setCurrentItem(found[0]);
distMetricTree->scrollToItem(distMetricTree->currentItem());
}
//
// get set mode (data series or metric?) -- only in rangemode
//
bool HistogramWindow::dataMode() const
{
if (!rangemode) return true;
else return data->isChecked();
}
void HistogramWindow::setDataMode(bool value)
{
if (!rangemode) return;
else {
active = true;
data->setChecked(value);
metric->setChecked(!value);
active = false;
switchMode();
}
}
//
// When user changes from data series to metric
//
void
HistogramWindow::dataToggled(bool x)
{
if (active) return;
active = true;
stale = true;
metric->setChecked(!x);
switchMode();
active = false;
}
void
HistogramWindow::metricToggled(bool x)
{
if (active) return;
active = true;
stale = true;
data->setChecked(!x);
switchMode();
active = false;
}
void
HistogramWindow::switchMode()
{
if (!rangemode) return; // ! only valid in rangemode
if (data->isChecked()) {
// hide all the metric controls
blankLabel1->hide();
blankLabel2->hide();
distMetricTree->hide();
totalMetricTree->hide();
metricLabel1->hide();
metricLabel2->hide();
colorLabel->hide();
colorButton->hide();
// show all the data series controls
comboLabel->show();
seriesCombo->show();
blankLabel3->show();
blankLabel4->show();
blankLabel5->show();
blankLabel6->show();
blankLabel7->show();
showSumY->show();
showLabel->show();
showLnY->show();
showZeroes->show();
shadeZones->show();
showInZones->show();
showInCPZones->show();
// select the series..
seriesChanged();
} else {
// hide all the data series controls
comboLabel->hide();
seriesCombo->hide();
blankLabel3->hide();
blankLabel4->hide();
blankLabel5->hide();
blankLabel6->hide();
blankLabel7->hide();
showSumY->hide();
showLabel->hide();
showLnY->hide();
showZeroes->hide();
shadeZones->hide();
showInZones->hide();
showInCPZones->hide();
// show all the metric controls
blankLabel1->show();
blankLabel2->show();
metricLabel1->show();
metricLabel2->show();
distMetricTree->show();
totalMetricTree->show();
colorLabel->show();
colorButton->show();
// select the series.. (but without the half second delay)
treeSelectionChanged();
}
stale = true;
updateChart(); // to whatever is currently selected.
}
//
// When user selects a new metric
//
void
HistogramWindow::treeSelectionChanged()
{
// new metric chosen, so set up all the bin width, line edit
// delta, precision etc
powerHist->setSeries(RideFile::none);
powerHist->setDelta(getDelta());
powerHist->setDigits(getDigits());
// now update the slider stepper and linedit
setBinEditors();
// initial value -- but only if need to
double minbinw = getDelta();
double maxbinw = getDelta() * 100;
double current = binWidthLineEdit->text().toDouble();
if (current < minbinw || current > maxbinw) setBin(getDelta());
// replot
updateChart();
}
//
// When user selects a different data series
//
void
HistogramWindow::seriesChanged()
{
// series changed so tell power hist
powerHist->setSeries(static_cast<RideFile::SeriesType>(seriesCombo->itemData(seriesCombo->currentIndex()).toInt()));
powerHist->setDelta(getDelta());
powerHist->setDigits(getDigits());
// now update the slider stepper and linedit
setBinEditors();
// set an initial bin width value
setBin(getDelta());
// replot
stale = true;
updateChart();
}
//
// We need to config / update the controls when data series/metrics change
//
void
HistogramWindow::setBinEditors()
{
// the line edit validator
QValidator *validator;
if (getDigits() == 0) {
validator = new QIntValidator(binWidthSlider->minimum() * getDelta(),
binWidthSlider->maximum() * getDelta(),
binWidthLineEdit);
} else {
validator = new QDoubleValidator(binWidthSlider->minimum() * getDelta(),
binWidthSlider->maximum() * getDelta(),
getDigits(),
binWidthLineEdit);
}
binWidthLineEdit->setValidator(validator);
rBinEdit->setValidator(validator);
}
//
// A new ride was selected
//
void
HistogramWindow::rideSelected()
{
if (!amVisible()) return;
RideItem *ride = myRideItem;
// handle catch up to compare changed
if (compareStale) compareChanged();
if (!ride || isCompare() || (rangemode && !stale)) return;
if (rangemode) {
// get range that applies to this ride
powerRange = context->athlete->zones(ride->isRun)->whichRange(ride->dateTime.date());
hrRange = context->athlete->hrZones()->whichRange(ride->dateTime.date());
}
// update
updateChart();
}
//
// User selected a new interval
//
void
HistogramWindow::intervalSelected()
{
if (!amVisible()) return;
RideItem *ride = myRideItem;
// null? or not plotting current ride, ignore signal
if (!ride || isCompare() || rangemode) return;
// update
interval = true;
updateChart();
}
void
HistogramWindow::rideAddorRemove(RideItem *)
{
stale = true;
if (amVisible()) updateChart();
}
void
HistogramWindow::zonesChanged()
{
if (!amVisible()) return;
powerHist->refreshZoneLabels();
powerHist->replot();
}
void
HistogramWindow::useCustomRange(DateRange range)
{
// plot using the supplied range
useCustom = true;
useToToday = false;
custom = range;
dateRangeChanged(custom);
}
void
HistogramWindow::useStandardRange()
{
useToToday= useCustom = false;
dateRangeChanged(myDateRange);
}
void
HistogramWindow::useThruToday()
{
// plot using the supplied range
useCustom = false;
useToToday = true;
custom = myDateRange;
if (custom.to > QDate::currentDate()) custom.to = QDate::currentDate();
dateRangeChanged(custom);
}
void HistogramWindow::dateRangeChanged(DateRange dateRange)
{
// compare mode?
if (amVisible() && compareStale) compareChanged();
// if we're using a custom one lets keep it
if (rangemode && (useCustom || useToToday)) dateRange = custom;
// has it changed?
if (dateRange.from != cfrom || dateRange.to != cto)
stale = true;
// don't do it if we're invisible, in compare or
// nothing has changed since last time ..
if (!amVisible() || isCompare() || !stale) return;
updateChart();
}
void HistogramWindow::addSeries()
{
// setup series list
seriesList << RideFile::watts
<< RideFile::wattsKg
<< RideFile::hr
<< RideFile::kph
<< RideFile::cad
<< RideFile::nm
<< RideFile::aPower
<< RideFile::gear
<< RideFile::smo2
<< RideFile::wbal;
foreach (RideFile::SeriesType x, seriesList)
seriesCombo->addItem(RideFile::seriesName(x), static_cast<int>(x));
}
void
HistogramWindow::setBinWidthFromSlider()
{
if (bactive) return;
setBin(binWidthSlider->value() * getDelta());
}
void
HistogramWindow::setrBinWidthFromSlider()
{
if (bactive) return;
setBin(rBinSlider->value() * getDelta());
}
void
HistogramWindow::setBinWidthFromLineEdit()
{
if (bactive) return;
setBin(binWidthLineEdit->text().toDouble());
}
void
HistogramWindow::setrBinWidthFromLineEdit()
{
if (bactive) return;
setBin(rBinEdit->text().toDouble());
}
void
HistogramWindow::setCPZoned(int x)
{
showInCPZones->setCheckState((Qt::CheckState)x);
}
void
HistogramWindow::setZoned(int x)
{
rZones->setCheckState((Qt::CheckState)x);
showInZones->setCheckState((Qt::CheckState)x);
}
void
HistogramWindow::setShade(int x)
{
rShade->setCheckState((Qt::CheckState)x);
shadeZones->setCheckState((Qt::CheckState)x);
}
void
HistogramWindow::refreshUpdate(QDate past)
{
if (!rangemode || cfrom > past || (lastupdate != QTime() && lastupdate.secsTo(QTime::currentTime()) < 5)) return;
lastupdate = QTime::currentTime();
forceReplot();
}
void
HistogramWindow::forceReplot()
{
stale = true;
if (amVisible()) updateChart();
}
void
HistogramWindow::updateChart()
{
// What is the selected series?
RideFile::SeriesType series = static_cast<RideFile::SeriesType>(seriesCombo->itemData(seriesCombo->currentIndex()).toInt());
// compare mode does its own thing so ignore
// this request (but set stale) if we're comparing
// we WILL get called when compare ends
if (!amVisible()) {
stale = true;
return;
}
// just reflect chart setting changes
if (isCompare()) {
compareChanged();
return;
}
// If no data present show the blank state page
if (!rangemode) {
RideFile::SeriesType baseSeries = (series == RideFile::wattsKg || series == RideFile::wbal) ? RideFile::watts : series;
if (rideItem() != NULL && rideItem()->ride()->isDataPresent(baseSeries))
setIsBlank(false);
else
setIsBlank(true);
} else {
setIsBlank(false);
}
// Lets get the data then
if (stale) {
RideFileCache *old = source;
if (rangemode) {
// set the date range to the appropriate selection
DateRange use;
if (useCustom) {
use = custom;
} else if (useToToday) {
use = myDateRange;
QDate today = QDate::currentDate();
if (use.to > today) use.to = today;
} else {
use = myDateRange;
}
if (data->isChecked()) {
// plotting a data series, so refresh the ridefilecache
source = new RideFileCache(context, use.from, use.to, isfiltered, files, rangemode);
cfrom = use.from;
cto = use.to;
stale = false;
if (old) delete old; // guarantee source pointer changes
stale = false; // well we tried
// and which series to plot
powerHist->setSeries(series);
// and now the controls
powerHist->setShading(shadeZones->isChecked() ? true : false);
powerHist->setZoned(showInZones->isChecked() ? true : false);
powerHist->setCPZoned(showInCPZones->isChecked() ? true : false);
powerHist->setlnY(showLnY->isChecked() ? true : false);
powerHist->setWithZeros(showZeroes->isChecked() ? true : false);
powerHist->setSumY(showSumY->currentIndex()== 0 ? true : false);
// set the data on the plot
powerHist->setData(source);
} else {
if (last.from != use.from || last.to != use.to) {
// remember the last lot we collected
last = use;
}
FilterSet fs;
fs.addFilter(isfiltered, files);
fs.addFilter(context->isfiltered, context->filters);
fs.addFilter(context->ishomefiltered, context->homeFilters);
// setData using the summary metrics -- always reset since filters may
// have changed, or perhaps the bin width...
powerHist->setSeries(RideFile::none);
powerHist->setDelta(getDelta());
powerHist->setDigits(getDigits());
powerHist->setData(Specification(use,fs), totalMetric(), distMetric(), &powerHist->standard);
powerHist->setColor(colorButton->getColor());
}
} else {
// and which series to plot
powerHist->setSeries(series);
// and now the controls
powerHist->setShading(shadeZones->isChecked() ? true : false);
powerHist->setZoned(showInZones->isChecked() ? true : false);
powerHist->setCPZoned(showInCPZones->isChecked() ? true : false);
powerHist->setlnY(showLnY->isChecked() ? true : false);
powerHist->setWithZeros(showZeroes->isChecked() ? true : false);
powerHist->setSumY(showSumY->currentIndex()== 0 ? true : false);
// do once the controls are set
powerHist->setData(myRideItem, true); // intervals selected forces data to
// be recomputed since interval selection
// has changed.
}
powerHist->recalc(true); // interval changed? force recalc
powerHist->replot();
interval = false;// we force a recalc when 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'.
} // if stale
}
void
HistogramWindow::clearFilter()
{
isfiltered = false;
files.clear();
stale = true;
updateChart();
repaint();
}
void
HistogramWindow::setFilter(QStringList list)
{
isfiltered = true;
files = list;
stale = true;
updateChart();
repaint();
}
double
HistogramWindow::getDelta()
{
if (rangemode && !data->isChecked()) {
// based upon the metric chosen
const RideMetricFactory &factory = RideMetricFactory::instance();
const RideMetric *m = factory.rideMetric(distMetric());
if (m) return 1.00F / pow(10, m->precision());
else return 1;
} else {
// use the predefined delta
switch (seriesCombo->currentIndex()) {
case 0: return wattsDelta;
case 1: return wattsKgDelta;
case 2: return hrDelta;
case 3: return kphDelta;
case 4: return cadDelta;
case 5: return nmDelta;
case 6: return wattsDelta; //aPower
case 7: return gearDelta;
case 8: return wbalDelta;
default: return 1;
}
}
}
int
HistogramWindow::getDigits()
{
if (rangemode && !data->isChecked()) {
// based upon the metric chosen
const RideMetricFactory &factory = RideMetricFactory::instance();
const RideMetric *m = factory.rideMetric(distMetric());
if (m) return m->precision();
else return 0;
} else {
// use predefined for data series
switch (seriesCombo->currentIndex()) {
case 0: return wattsDigits;
case 1: return wattsKgDigits;
case 2: return hrDigits;
case 3: return kphDigits;
case 4: return cadDigits;
case 5: return nmDigits;
case 6: return wattsDigits; // aPower
case 7: return gearDigits;
case 8: return wbalDigits;
default: return 1;
}
}
}