diff --git a/doc/user/GC3-Manual.pdf b/doc/user/GC3-Manual.pdf index 183f10a2b..1d07274a9 100644 Binary files a/doc/user/GC3-Manual.pdf and b/doc/user/GC3-Manual.pdf differ diff --git a/doc/user/GC3-Manual.texinfo b/doc/user/GC3-Manual.texinfo index f2aeffffb..994825eec 100644 --- a/doc/user/GC3-Manual.texinfo +++ b/doc/user/GC3-Manual.texinfo @@ -681,10 +681,20 @@ below in the secion @emph{Adding and adjusting charts}. @center @image{"image/2-chartmenu",180pt} @center @emph{Figure 9: The `+' Add Chart Menu} @vskip 6pt - +@strong{NOTE:} Only the windows that are relevant for the current view will be listed. You cannot, for example, add a long term +metric chart to the analysis view, or a performance chart (all plot) to the diary view. @end itemize @section Views & Sidebar +@vskip 12pt +@noindent @image{"image/2-sideactivity",70pt} +@noindent @image{"image/2-sideinterval",70pt} +@noindent @image{"image/2-sidediary",70pt} +@noindent @image{"image/2-sidehome",70pt} +@noindent @image{"image/2-sidesummary",70pt} +@noindent @image{"image/2-sidetrain",70pt} +@center @emph{Figure 10: Sidebars} +@vskip 6pt scopebar, sidebar, tab/tiled, add chart menu @section Searching and Filtering diff --git a/src/AerobicDecoupling.cpp b/src/AerobicDecoupling.cpp index 66d6ec042..5969e441a 100644 --- a/src/AerobicDecoupling.cpp +++ b/src/AerobicDecoupling.cpp @@ -53,7 +53,7 @@ class AerobicDecoupling : public RideMetric { setType(RideMetric::Average); setMetricUnits(tr("%")); setImperialUnits(tr("%")); - setPrecision(2); + setPrecision(1); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, diff --git a/src/ColorButton.cpp b/src/ColorButton.cpp index dd1de7673..41b7afcdf 100644 --- a/src/ColorButton.cpp +++ b/src/ColorButton.cpp @@ -56,7 +56,7 @@ ColorButton::clicked() // Color picker dialog QColorDialog picker(this); picker.setCurrentColor(color); - QColor rcolor = picker.getColor(); + QColor rcolor = picker.getColor(color, this, tr("Choose Color"), QColorDialog::DontUseNativeDialog); // if we got a good color use it and notify others if (rcolor.isValid()) { diff --git a/src/DBAccess.cpp b/src/DBAccess.cpp index ebf9e4888..ad894c36f 100644 --- a/src/DBAccess.cpp +++ b/src/DBAccess.cpp @@ -60,6 +60,7 @@ // 41 27 Oct 2012 Mark Liversedge Lucene switched to StandardAnalyzer and search all texts by default // 42 03 Dec 2012 Mark Liversedge W/KG ridefilecache changes - force a rebuild. // 43 24 Jan 2012 Mark Liversedge TRIMP update +// 44 19 Apr 2013 Mark Liversedge Aerobic Decoupling precision reduced to 1pt static int DBSchemaVersion = 43; diff --git a/src/HistogramWindow.cpp b/src/HistogramWindow.cpp index 54ad8bc27..a408eca9f 100644 --- a/src/HistogramWindow.cpp +++ b/src/HistogramWindow.cpp @@ -19,6 +19,10 @@ #include "HistogramWindow.h" #include "MainWindow.h" +#include "MetricAggregator.h" +#include "SummaryMetrics.h" +#include "ChartSettings.h" +#include "ColorButton.h" #include "PowerHist.h" #include "RideFile.h" #include "RideFileCache.h" @@ -30,13 +34,35 @@ #include "Zones.h" #include "HrZones.h" -HistogramWindow::HistogramWindow(MainWindow *mainWindow, bool rangemode) : GcChartWindow(mainWindow), mainWindow(mainWindow), stale(true), source(NULL), rangemode(rangemode), useCustom(false), useToToday(false) +// 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; + +// digits for text entry validator +static const int wattsDigits = 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; + + +// +// Constructor +// +HistogramWindow::HistogramWindow(MainWindow *mainWindow, bool rangemode) : GcChartWindow(mainWindow), mainWindow(mainWindow), stale(true), source(NULL), active(false), bactive(false), rangemode(rangemode), useCustom(false), useToToday(false), precision(99) { setInstanceName("Histogram Window"); QWidget *c = new QWidget; + c->setContentsMargins(0,0,0,0); QFormLayout *cl = new QFormLayout(c); cl->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); + cl->setSpacing(5); setControls(c); // @@ -46,7 +72,7 @@ HistogramWindow::HistogramWindow(MainWindow *mainWindow, bool rangemode) : GcCha // reveal controls rWidth = new QLabel(tr("Bin Width")); rBinEdit = new QLineEdit(); - rBinEdit->setFixedWidth(30); + rBinEdit->setFixedWidth(40); rBinSlider = new QSlider(Qt::Horizontal); rBinSlider->setTickPosition(QSlider::TicksBelow); rBinSlider->setTickInterval(10); @@ -84,27 +110,148 @@ HistogramWindow::HistogramWindow(MainWindow *mainWindow, bool rangemode) : GcCha searchBox = new SearchFilterBox(this, mainWindow); connect(searchBox, SIGNAL(searchClear()), this, SLOT(clearFilter())); connect(searchBox, SIGNAL(searchResults(QStringList)), this, SLOT(setFilter(QStringList))); - cl->addRow(new QLabel(tr("Filter")), searchBox); + if (!rangemode) searchBox->hide(); + else { + cl->addRow(new QLabel(tr("Filter")), searchBox); + cl->addWidget(new QLabel("")); + } #endif - // spacing if we have a range - if (rangemode) cl->addWidget(new QLabel("")); - // date selection dateSetting = new DateSettingsEdit(this); - if (rangemode) cl->addRow(new QLabel(tr("Date Range")), dateSetting); + + if (rangemode) { + + cl->addRow(new QLabel(tr("Date Range")), dateSetting); + cl->addWidget(new QLabel("")); // spacing + + // default to data series! + data = new QRadioButton(tr("Ride Data Samples")); + metric = new QRadioButton(tr("Ride 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 - cl->addWidget(new QLabel("")); // spacing seriesCombo = new QComboBox(); addSeries(); - cl->addRow(new QLabel(tr("Data Series")), seriesCombo); + 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); // bin width QHBoxLayout *binWidthLayout = new QHBoxLayout; QLabel *binWidthLabel = new QLabel(tr("Bin width"), this); binWidthLineEdit = new QLineEdit(this); - binWidthLineEdit->setFixedWidth(30); + binWidthLineEdit->setFixedWidth(40); binWidthLayout->addWidget(binWidthLineEdit); binWidthSlider = new QSlider(Qt::Horizontal); @@ -115,39 +262,20 @@ HistogramWindow::HistogramWindow(MainWindow *mainWindow, bool rangemode) : GcCha binWidthLayout->addWidget(binWidthSlider); cl->addRow(binWidthLabel, binWidthLayout); - showSumY = new QComboBox(); - showSumY->addItem(tr("Absolute Time")); - showSumY->addItem(tr("Percentage Time")); - - cl->addWidget(new QLabel("")); // spacing - cl->addRow(new QLabel("Show"), showSumY); - - showLnY = new QCheckBox; - showLnY->setText(tr("Log Y")); - cl->addRow(new QLabel(""), showLnY); - - showZeroes = new QCheckBox; - showZeroes->setText(tr("With zeros")); - cl->addRow(new QLabel(""), showZeroes); - - shadeZones = new QCheckBox; - shadeZones->setText(tr("Shade zones")); - cl->addRow(new QLabel(""), shadeZones); - - showInZones = new QCheckBox; - showInZones->setText(tr("Show in zones")); - cl->addRow(new QLabel(""), showInZones); - - // sort out default values - setHistTextValidator(); + 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); - setHistBinWidthText(); + + // fixup series selected by default + seriesChanged(); + + // hide/show according to default mode + switchMode(); // does nothing if not in rangemode // set the defaults etc updateChart(); @@ -167,6 +295,13 @@ HistogramWindow::HistogramWindow(MainWindow *mainWindow, bool rangemode) : GcCha 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())); + + lagger = new QTimer; + lagger->setSingleShot(true); + connect(lagger, SIGNAL(timeout()), this, SLOT(treeSelectionTimeout())); + } else { dateSetting->hide(); connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideSelected())); @@ -176,7 +311,7 @@ HistogramWindow::HistogramWindow(MainWindow *mainWindow, bool rangemode) : GcCha // 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(seriesCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(seriesChanged())); connect(showInZones, SIGNAL(stateChanged(int)), this, SLOT(setZoned(int))); connect(showInZones, SIGNAL(stateChanged(int)), this, SLOT(updateChart())); connect(shadeZones, SIGNAL(stateChanged(int)), this, SLOT(setShade(int))); @@ -191,6 +326,311 @@ HistogramWindow::HistogramWindow(MainWindow *mainWindow, bool rangemode) : GcCha connect(mainWindow, SIGNAL(filterChanged(QStringList&)), this, SLOT(forceReplot())); } +// +// 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 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 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 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 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(); + showSumY->show(); + showLabel->show(); + showLnY->show(); + showZeroes->show(); + shadeZones->show(); + showInZones->show(); + + // select the series.. + seriesChanged(); + + } else { + + // hide all the data series controls + comboLabel->hide(); + seriesCombo->hide(); + blankLabel3->hide(); + blankLabel4->hide(); + blankLabel5->hide(); + blankLabel6->hide(); + showSumY->hide(); + showLabel->hide(); + showLnY->hide(); + showZeroes->hide(); + shadeZones->hide(); + showInZones->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) + treeSelectionTimeout(); + } + mainWindow->chartSettings->adjustSize(); + + stale = true; + updateChart(); // to whatever is currently selected. +} + + +// +// When user selects a new metric +// +void +HistogramWindow::treeSelectionChanged() +{ + stale = true; + lagger->start(500); +} + +void +HistogramWindow::treeSelectionTimeout() +{ + // 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(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() { @@ -210,12 +650,9 @@ HistogramWindow::rideSelected() updateChart(); } -void -HistogramWindow::rideAddorRemove(RideItem *) -{ - stale = true; -} - +// +// User selected a new interval +// void HistogramWindow::intervalSelected() { @@ -231,6 +668,12 @@ HistogramWindow::intervalSelected() updateChart(); } +void +HistogramWindow::rideAddorRemove(RideItem *) +{ + stale = true; +} + void HistogramWindow::zonesChanged() { @@ -299,80 +742,29 @@ void HistogramWindow::addSeries() void HistogramWindow::setBinWidthFromSlider() { - if (powerHist->binWidth() != binWidthSlider->value()) { - //RideFile::SeriesType series = static_cast(seriesCombo->itemData(seriesCombo->currentIndex()).toInt()); - powerHist->setBinWidth(binWidthSlider->value()); - setHistBinWidthText(); - rBinEdit->setText(binWidthLineEdit->text()); - rBinSlider->setValue(binWidthSlider->value()); - updateChart(); - } + if (bactive) return; + setBin(binWidthSlider->value() * getDelta()); } void HistogramWindow::setrBinWidthFromSlider() { - if (powerHist->binWidth() != rBinSlider->value()) { - powerHist->setBinWidth(rBinSlider->value()); - setHistBinWidthText(); - rBinEdit->setText(binWidthLineEdit->text()); - updateChart(); - } -} - -void -HistogramWindow::setHistBinWidthText() -{ - binWidthLineEdit->setText(QString("%1").arg(powerHist->getBinWidthRealUnits(), 0, 'g', 3)); - rBinEdit->setText(QString("%1").arg(powerHist->getBinWidthRealUnits(), 0, 'g', 3)); - -} - -void -HistogramWindow::setHistTextValidator() -{ - double delta = powerHist->getDelta(); - int digits = powerHist->getDigits(); - - QValidator *validator; - if (digits == 0) { - - validator = new QIntValidator(binWidthSlider->minimum() * delta, - binWidthSlider->maximum() * delta, - binWidthLineEdit); - } else { - - validator = new QDoubleValidator(binWidthSlider->minimum() * delta, - binWidthSlider->maximum() * delta, - digits, - binWidthLineEdit); - } - binWidthLineEdit->setValidator(validator); - rBinEdit->setValidator(validator); + if (bactive) return; + setBin(rBinSlider->value() * getDelta()); } void HistogramWindow::setBinWidthFromLineEdit() { - double value = binWidthLineEdit->text().toDouble(); - if (value != powerHist->binWidth()) { - binWidthSlider->setValue(powerHist->setBinWidthRealUnits(value)); - rBinSlider->setValue(powerHist->setBinWidthRealUnits(value)); - setHistBinWidthText(); - - updateChart(); - } + if (bactive) return; + setBin(binWidthLineEdit->text().toDouble()); } void HistogramWindow::setrBinWidthFromLineEdit() { - double value = rBinEdit->text().toDouble(); - if (value != powerHist->binWidth()) { - rBinSlider->setValue(powerHist->setBinWidthRealUnits(value)); - binWidthSlider->setValue(powerHist->setBinWidthRealUnits(value)); - updateChart(); - } + if (bactive) return; + setBin(rBinEdit->text().toDouble()); } void @@ -400,13 +792,33 @@ HistogramWindow::forceReplot() void HistogramWindow::updateChart() { - // refresh the ridefile cache if it is stale + if (!amVisible()) { + stale = true; + return; + } + + // What is the selected series? + RideFile::SeriesType series = static_cast(seriesCombo->itemData(seriesCombo->currentIndex()).toInt()); + + // If no data present show the blank state page + if (!rangemode) { + RideFile::SeriesType baseSeries = (series == RideFile::wattsKg) ? 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 appropiate selection DateRange use; if (useCustom) { @@ -423,68 +835,84 @@ HistogramWindow::updateChart() use = myDateRange; } + if (data->isChecked()) { + + // plotting a data series, so refresh the ridefilecache + #ifdef GC_HAVE_LUCENE - source = new RideFileCache(mainWindow, use.from, use.to, isFiltered, files); + source = new RideFileCache(mainWindow, use.from, use.to, isFiltered, files); #else - source = new RideFileCache(mainWindow, use.from, use.to); + source = new RideFileCache(mainWindow, use.from, use.to); #endif - cfrom = use.from; - cto = use.to; - stale = false; + cfrom = use.from; + cto = use.to; + stale = false; - if (old) delete old; // guarantee source pointer changes - } - stale = false; // well we tried - } + if (old) delete old; // guarantee source pointer changes + stale = false; // well we tried - // set data - if (rangemode && source) { - powerHist->setData(source); - } else - powerHist->setData(myRideItem, interval); // intervals selected forces data to + // set the data on the plot + powerHist->setData(source); + + // 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->setlnY(showLnY->isChecked() ? true : false); + powerHist->setWithZeros(showZeroes->isChecked() ? true : false); + powerHist->setSumY(showSumY->currentIndex()== 0 ? true : false); + + } else { + + if (last.from != use.from || last.to != use.to) { + + // remember the last lot we collected + last = use; + + // plotting a metric, reread the metrics for the selected date range + results = mainWindow->metricDB->getAllMetricsFor(use); + + } + + // 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(results, totalMetric(), distMetric(), isFiltered, files); + powerHist->setColor(colorButton->getColor()); + + } + + } else { + + powerHist->setData(myRideItem, interval); // intervals selected forces data to // be recomputed since interval selection // has changed. + // 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->setlnY(showLnY->isChecked() ? true : false); - powerHist->setWithZeros(showZeroes->isChecked() ? true : false); - powerHist->setSumY(showSumY->currentIndex()== 0 ? true : false); + // 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); - // Selected series - RideFile::SeriesType series = static_cast(seriesCombo->itemData(seriesCombo->currentIndex()).toInt()); - // and which series to plot - powerHist->setSeries(series); + } - // is data present for selected series, when not in range mode - if (!rangemode) { - RideFile::SeriesType baseSeries = (series == RideFile::wattsKg) ? RideFile::watts : series; - if (rideItem() != NULL && rideItem()->ride()->isDataPresent(baseSeries)) - setIsBlank(false); - else - setIsBlank(true); - } else { - setIsBlank(false); - } + powerHist->recalc(true); // interval changed? force recalc + powerHist->replot(); - // Correct binWidth if not valid for the selected series - if (binWidthLineEdit->text().toDouble()getDelta()) - binWidthSlider->setValue(powerHist->getDelta()); - else - powerHist->setBinWidth(binWidthLineEdit->text().toDouble()); - - // 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'. + 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'. + } // if stale } #ifdef GC_HAVE_LUCENE @@ -505,4 +933,59 @@ HistogramWindow::setFilter(QStringList list) stale = true; updateChart(); } + +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; + 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; + default: return 1; + } + } +} #endif diff --git a/src/HistogramWindow.h b/src/HistogramWindow.h index a2122f021..379e03d58 100644 --- a/src/HistogramWindow.h +++ b/src/HistogramWindow.h @@ -20,6 +20,7 @@ #ifndef _GC_HistogramWindow_h #define _GC_HistogramWindow_h 1 #include "GoldenCheetah.h" +#include "SummaryMetrics.h" #include "Season.h" #include "SeasonParser.h" @@ -33,6 +34,7 @@ class MainWindow; class PowerHist; class RideItem; class RideFileCache; +class ColorButton; class HistogramWindow : public GcChartWindow { @@ -41,7 +43,6 @@ class HistogramWindow : public GcChartWindow Q_PROPERTY(int series READ series WRITE setSeries USER true) Q_PROPERTY(int percent READ percent WRITE setPercent USER true) - Q_PROPERTY(double bin READ bin WRITE setBin USER true) 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) @@ -55,6 +56,11 @@ class HistogramWindow : public GcChartWindow Q_PROPERTY(int lastN READ lastN WRITE setLastN USER true) Q_PROPERTY(int lastNX READ lastNX WRITE setLastNX USER true) Q_PROPERTY(int prevN READ prevN WRITE setPrevN USER true) + Q_PROPERTY(QString plotColor READ plotColor WRITE setPlotColor USER true) + Q_PROPERTY(QString distmetric READ distMetric WRITE setDistMetric USER true) + Q_PROPERTY(QString totalmetric READ totalMetric WRITE setTotalMetric USER true) + Q_PROPERTY(bool dataMode READ dataMode WRITE setDataMode USER true) + Q_PROPERTY(double bin READ bin WRITE setBin USER true) Q_PROPERTY(int useSelected READ useSelected WRITE setUseSelected USER true) // !! must be last property !! public: @@ -69,8 +75,8 @@ class HistogramWindow : public GcChartWindow 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); } + double bin() const { return binWidthLineEdit->text().toDouble(); } + void setBin(double x); bool logY() const { return showLnY->isChecked(); } void setLogY(bool x) { showLnY->setChecked(x); } bool zeroes() const { return showZeroes->isChecked(); } @@ -98,6 +104,21 @@ class HistogramWindow : public GcChartWindow void setLastNX(int x) { dateSetting->setLastNX(x); } int prevN() { return dateSetting->prevN(); } void setPrevN(int x) { dateSetting->setPrevN(x); } + bool dataMode() const; + void setDataMode(bool); + QString totalMetric() const; + void setTotalMetric(QString); + QString distMetric() const; + void setDistMetric(QString); + void setPlotColor(QString); + QString plotColor() const; + + // for metric or data series + double getDelta(); + int getDigits(); + + // bin width editor + void setBinEditors(); public slots: @@ -115,6 +136,14 @@ class HistogramWindow : public GcChartWindow void useThruToday(); void dateRangeChanged(DateRange); + // we changed the series to plot + void seriesChanged(); + + // in rangemode we choose data series or metric + void metricToggled(bool); + void dataToggled(bool); + void switchMode(); + void setZoned(int); void setShade(int); @@ -127,10 +156,10 @@ class HistogramWindow : public GcChartWindow void forceReplot(); void updateChart(); - private: + void treeSelectionChanged(); + void treeSelectionTimeout(); - void setHistTextValidator(); - void setHistBinWidthText(); + private: MainWindow *mainWindow; PowerHist *powerHist; @@ -165,11 +194,42 @@ class HistogramWindow : public GcChartWindow QStringList files; #endif + bool active, // active switching mode between data series and metric + bactive; // active setting binwidth bool rangemode; DateSettingsEdit *dateSetting; bool useCustom; bool useToToday; DateRange custom; + int precision; + + // labels we need to remember so we can show/hide + // when switching between data series and range mode + QLabel *comboLabel, *metricLabel1, *metricLabel2, *showLabel, + *blankLabel1, *blankLabel2, + *blankLabel3, *blankLabel4, *blankLabel5, *blankLabel6, + *colorLabel; + + // in range mode we can also plot a distribution chart + // based upon metrics and not just data series + QRadioButton *data, *metric; + + // total value (y-axis) + QTreeWidget *totalMetricTree; + void selectTotal(QString); + + // distribution value (y-axis) + QTreeWidget *distMetricTree; + void selectMetric(QString); + + // One shot timer.. delay before refreshing as user + // scrolls up and down metric/total treewidget. This is + // to have a slight lag before redrawing since it is expensive + // and users are likely to move up and down with the arrow keys + QTimer *lagger; + ColorButton *colorButton; + QList results; + DateRange last; }; #endif // _GC_HistogramWindow_h diff --git a/src/LTMTool.cpp b/src/LTMTool.cpp index 54f38932b..a3b2e7c39 100644 --- a/src/LTMTool.cpp +++ b/src/LTMTool.cpp @@ -76,6 +76,7 @@ LTMTool::LTMTool(MainWindow *parent, const QDir &home, bool multi) : QWidget(par presetPicker = new QComboBox; presetPicker->setSizeAdjustPolicy(QComboBox::AdjustToContents); QHBoxLayout *presetrow = new QHBoxLayout; + presetrow->setSpacing(5); presetrow->addWidget(presetLabel); presetrow->addWidget(presetPicker); presetrow->addStretch(); @@ -125,7 +126,7 @@ LTMTool::LTMTool(MainWindow *parent, const QDir &home, bool multi) : QWidget(par else metricTree->setSelectionMode(QAbstractItemView::SingleSelection); metricTree->header()->hide(); - metricTree->setFrameStyle(QFrame::NoFrame); + //metricTree->setFrameStyle(QFrame::NoFrame); //metricTree->setAlternatingRowColors (true); metricTree->setIndentation(5); allMetrics = new QTreeWidgetItem(metricTree, ROOT_TYPE); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index c8ef26afc..9c20d6f1c 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -764,6 +764,9 @@ MainWindow::MainWindow(const QDir &home) : // Chart Settings now in their own dialog box chartSettings = new ChartSettings(this, masterControls); + chartSettings->setMaximumWidth(450); + chartSettings->setMaximumHeight(600); + //toolBox->addItem(masterControls, QIcon(":images/settings.png"), "Chart Settings"); chartSettings->hide(); diff --git a/src/PowerHist.cpp b/src/PowerHist.cpp index 2551193b0..653b56b82 100644 --- a/src/PowerHist.cpp +++ b/src/PowerHist.cpp @@ -23,6 +23,7 @@ #include "IntervalItem.h" #include "RideFile.h" #include "RideFileCache.h" +#include "SummaryMetrics.h" #include "Settings.h" #include "Zones.h" #include "HrZones.h" @@ -42,24 +43,9 @@ #include "LTMCanvasPicker.h" // for tooltip -// discritized unit for smoothing -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; - -// digits for text entry validator -static const int wattsDigits = 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; - PowerHist::PowerHist(MainWindow *mainWindow): minX(0), + maxX(0), rideItem(NULL), mainWindow(mainWindow), series(RideFile::watts), @@ -129,29 +115,37 @@ PowerHist::configChanged() QPen pen; QColor brush_color; - switch (series) { - case RideFile::watts: - case RideFile::wattsKg: - pen.setColor(GColor(CPOWER).darker(200)); - brush_color = GColor(CPOWER); - break; - case RideFile::nm: - pen.setColor(GColor(CTORQUE).darker(200)); - brush_color = GColor(CTORQUE); - break; - case RideFile::kph: - pen.setColor(GColor(CSPEED).darker(200)); - brush_color = GColor(CSPEED); - break; - case RideFile::cad: - pen.setColor(GColor(CCADENCE).darker(200)); - brush_color = GColor(CCADENCE); - break; - default: - case RideFile::hr: - pen.setColor(GColor(CHEARTRATE).darker(200)); - brush_color = GColor(CHEARTRATE); - break; + if (source == Metric) { + + pen.setColor(metricColor.darker(200)); + brush_color = metricColor; + + } else { + + switch (series) { + case RideFile::watts: + case RideFile::wattsKg: + pen.setColor(GColor(CPOWER).darker(200)); + brush_color = GColor(CPOWER); + break; + case RideFile::nm: + pen.setColor(GColor(CTORQUE).darker(200)); + brush_color = GColor(CTORQUE); + break; + case RideFile::kph: + pen.setColor(GColor(CSPEED).darker(200)); + brush_color = GColor(CSPEED); + break; + case RideFile::cad: + pen.setColor(GColor(CCADENCE).darker(200)); + brush_color = GColor(CCADENCE); + break; + default: + case RideFile::hr: + pen.setColor(GColor(CHEARTRATE).darker(200)); + brush_color = GColor(CHEARTRATE); + break; + } } double width = appsettings->value(this, GC_LINEWIDTH, 2.0).toDouble(); @@ -270,7 +264,6 @@ PowerHist::recalc(bool force) QVector *array = NULL; QVector *selectedArray = NULL; int arrayLength = 0; - double delta = 0; // lets make sure we need to recalculate if (force == false && @@ -308,61 +301,60 @@ PowerHist::recalc(bool force) if (source == Ride && !rideItem) return; - // make sure the interval length is set - if (dt <= 0) return; + // make sure the interval length is set if not plotting metrics + if (source != Metric && dt <= 0) return; - if (series == RideFile::watts && zoned == false) { + if (source == Metric) { + + // we use the metricArray + array = &metricArray; + arrayLength = metricArray.size(); + selectedArray = NULL; + + } else if (series == RideFile::watts && zoned == false) { array = &wattsArray; - delta = wattsDelta; arrayLength = wattsArray.size(); selectedArray = &wattsSelectedArray; } else if ((series == RideFile::watts || series == RideFile::wattsKg) && zoned == true) { array = &wattsZoneArray; - delta = 1; arrayLength = wattsZoneArray.size(); selectedArray = &wattsZoneSelectedArray; } else if (series == RideFile::wattsKg && zoned == false) { array = &wattsKgArray; - delta = wattsKgDelta; arrayLength = wattsKgArray.size(); selectedArray = &wattsKgSelectedArray; } else if (series == RideFile::nm) { array = &nmArray; - delta = nmDelta; arrayLength = nmArray.size(); selectedArray = &nmSelectedArray; } else if (series == RideFile::hr && zoned == false) { array = &hrArray; - delta = hrDelta; arrayLength = hrArray.size(); selectedArray = &hrSelectedArray; } else if (series == RideFile::hr && zoned == true) { array = &hrZoneArray; - delta = 1; arrayLength = hrZoneArray.size(); selectedArray = &hrZoneSelectedArray; } else if (series == RideFile::kph) { array = &kphArray; - delta = kphDelta; arrayLength = kphArray.size(); selectedArray = &kphSelectedArray; } else if (series == RideFile::cad) { array = &cadArray; - delta = cadDelta; arrayLength = cadArray.size(); selectedArray = &cadSelectedArray; } @@ -549,6 +541,7 @@ PowerHist::setYMax() { double MaxY = curve->maxYValue(); if (MaxY < curveSelected->maxYValue()) MaxY = curveSelected->maxYValue(); + static const double tmin = 1.0/60; setAxisScale(yLeft, (lny ? tmin : 0.0), MaxY * 1.1); @@ -626,6 +619,136 @@ PowerHist::setData(RideFileCache *cache) curveSelected->hide(); } +void +PowerHist::setData(QList&results, QString totalMetric, QString distMetric, + bool isFiltered, QStringList files) +{ + // what metrics are we plotting? + source = Metric; + const RideMetricFactory &factory = RideMetricFactory::instance(); + const RideMetric *m = factory.rideMetric(distMetric); + const RideMetric *tm = factory.rideMetric(totalMetric); + if (m == NULL || tm == NULL) return; + + // metricX, metricY + metricX = distMetric; + metricY = totalMetric; + + // how big should the array be? + double multiplier = pow(10, m->precision()); + int max = 0, min = 0; + + // LOOP THRU VALUES -- REPEATED WITH CUT AND PASTE BELOW + // SO PLEASE MAKE SAME CHANGES TWICE (SORRY) + foreach(SummaryMetrics x, results) { + + // skip filtered values + if (isFiltered && !files.contains(x.getFileName())) continue; + + // and global filter too + if (mainWindow->isfiltered && !mainWindow->filters.contains(x.getFileName())) continue; + + // get computed value + double v = x.getForSymbol(distMetric, mainWindow->useMetricUnits); + + // ignore no temp files + if ((distMetric == "average_temp" || distMetric == "max_temp") && v == RideFile::noTemp) continue; + + // clean up dodgy values + if (isnan(v) || isinf(v)) v = 0; + + // seconds to minutes + if (m->units(mainWindow->useMetricUnits) == tr("seconds")) v /= 60; + + // apply multiplier + v *= multiplier; + + if (v>max) max = v; + if (v 100000) max = 100000; + if (min < -100000) min = -100000; + + // now run thru the data again, but this time + // populate the metricArray + metricArray.resize(max-min); + metricArray.fill(0); + + // LOOP THRU VALUES -- REPEATED WITH CUT AND PASTE ABOVE + // SO PLEASE MAKE SAME CHANGES TWICE (SORRY) + foreach(SummaryMetrics x, results) { + + // skip filtered values + if (isFiltered && !files.contains(x.getFileName())) continue; + + // and global filter too + if (mainWindow->isfiltered && !mainWindow->filters.contains(x.getFileName())) continue; + + // get computed value + double v = x.getForSymbol(distMetric, mainWindow->useMetricUnits); + + // ignore no temp files + if ((distMetric == "average_temp" || distMetric == "max_temp") && v == RideFile::noTemp) continue; + + // clean up dodgy values + if (isnan(v) || isinf(v)) v = 0; + + // seconds to minutes + if (m->units(mainWindow->useMetricUnits) == tr("seconds")) v /= 60; + + // apply multiplier + v *= multiplier; + + // ignore out of bounds data + if ((int)(v)max) continue; + + // increment value, are intitialised to zero above + // there will be some loss of precision due to totalising + // a double in an int, but frankly that should be minimal + // since most values of note are integer based anyway. + double t = x.getForSymbol(totalMetric, mainWindow->useMetricUnits); + + // totalise in minutes + if (tm->units(mainWindow->useMetricUnits) == tr("seconds")) t /= 60; + + // sum up + metricArray[(int)(v)-min] += t; + } + + // we certainly don't want the interval curve when plotting + // metrics across rides! + curveSelected->hide(); + + // now set all the plot paramaters to match the data + source = Metric; + zoned = false; + rideItem = NULL; + lny = false; + shade = false; + withz = false; + dt = 1; + absolutetime = true; + + // and the plot itself + QString yunits = tm->units(mainWindow->useMetricUnits); + if (yunits == tr("seconds")) yunits = tr("minutes"); + QString xunits = m->units(mainWindow->useMetricUnits); + if (xunits == tr("seconds")) xunits = tr("minutes"); + + if (tm->units(mainWindow->useMetricUnits) != "") + setAxisTitle(yLeft, QString(tr("Total %1 (%2)")).arg(tm->name()).arg(yunits)); + else + setAxisTitle(yLeft, QString(tr("Total %1")).arg(tm->name())); + + if (m->units(mainWindow->useMetricUnits) != "") + setAxisTitle(xBottom, QString(tr("%1 of Activity (%2)")).arg(m->name()).arg(xunits)); + else + setAxisTitle(xBottom, QString(tr("%1 of Activity")).arg(m->name())); +} + void PowerHist::updatePlot() { @@ -636,6 +759,14 @@ PowerHist::updatePlot() void PowerHist::setData(RideItem *_rideItem, bool force) { + // 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; + source = Ride; // we set with this data already @@ -832,48 +963,6 @@ PowerHist::setZoned(bool value) zoned = value; } -double -PowerHist::getDelta() -{ - switch (series) { - case RideFile::watts: return wattsDelta; - case RideFile::wattsKg: return wattsKgDelta; - case RideFile::nm: return nmDelta; - case RideFile::hr: return hrDelta; - case RideFile::kph: return kphDelta; - case RideFile::cad: return cadDelta; - default: return 1; - } -} - -int -PowerHist::getDigits() -{ - switch (series) { - case RideFile::watts: return wattsDigits; - case RideFile::wattsKg: return wattsKgDigits; - case RideFile::nm: return nmDigits; - case RideFile::hr: return hrDigits; - case RideFile::kph: return kphDigits; - case RideFile::cad: return cadDigits; - default: return 1; - } -} - -int -PowerHist::setBinWidthRealUnits(double value) -{ - setBinWidth(round(value / getDelta())); - if (!binw) binw = 1; // must be nonzero - return binw; -} - -double -PowerHist::getBinWidthRealUnits() -{ - return binw * getDelta(); -} - void PowerHist::setWithZeros(bool value) { @@ -967,7 +1056,8 @@ PowerHist::setAxisTitle(int axis, QString label) } void -PowerHist::setSeries(RideFile::SeriesType x) { +PowerHist::setSeries(RideFile::SeriesType x) +{ // user selected a different series to plot series = x; configChanged(); // set colors @@ -1017,12 +1107,20 @@ PowerHist::pointHover(QwtPlotCurve *curve, int index) } else if (yvalue > 0) { - // output the tooltip - text = QString("%1 %2\n%3 %4") - .arg(xvalue, 0, 'f', getDigits()) - .arg(this->axisTitle(curve->xAxis()).text()) - .arg(yvalue, 0, 'f', 1) - .arg(absolutetime ? tr("minutes") : tr("%")); + if (source != Metric) { + // output the tooltip + text = QString("%1 %2\n%3 %4") + .arg(xvalue, 0, 'f', digits) + .arg(this->axisTitle(curve->xAxis()).text()) + .arg(yvalue, 0, 'f', 1) + .arg(absolutetime ? tr("minutes") : tr("%")); + } else { + text = QString("%1 %2\n%3 %4") + .arg(xvalue, 0, 'f', digits) + .arg(this->axisTitle(curve->xAxis()).text()) + .arg(yvalue, 0, 'f', 1) + .arg(this->axisTitle(curve->yAxis()).text()); + } // set that text up zoomer->setText(text); diff --git a/src/PowerHist.h b/src/PowerHist.h index 999eebfcd..1df95c86a 100644 --- a/src/PowerHist.h +++ b/src/PowerHist.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -45,7 +46,7 @@ class HrHistBackground; class HrHistZoneLabel; class LTMCanvasPicker; class ZoneScaleDraw; - +class SummaryMetrics; class penTooltip: public QwtPlotZoomer { @@ -99,14 +100,24 @@ class PowerHist : public QwtPlot ~PowerHist(); double minX; + double maxX; public slots: // public setters void setShading(bool x) { shade=x; } void setSeries(RideFile::SeriesType series); + + // set data from a ride void setData(RideItem *_rideItem, bool force=false); + + // set data from a ridefile cache void setData(RideFileCache *source); + + // set data from metrics + void setData(QList&results, QString totalMetric, QString distMetric, + bool isFiltered, QStringList files); + void setlnY(bool value); void setWithZeros(bool value); void setZoned(bool value); @@ -115,12 +126,11 @@ class PowerHist : public QwtPlot void setAxisTitle(int axis, QString label); void setYMax(); void setBinWidth(double value); - int setBinWidthRealUnits(double value); + void setColor(QColor color) { metricColor = color; } // public getters - double getDelta(); - double getBinWidthRealUnits(); - int getDigits(); + void setDelta(double delta) { this->delta = delta; } + void setDigits(int digits) { this->digits = digits; } inline bool islnY() const { return lny; } inline bool withZeros() const { return withz; } inline double binWidth() const { return binw; } @@ -168,13 +178,16 @@ class PowerHist : public QwtPlot QwtPlotCurve *curve, *curveSelected; QList zoneLabels; QList hrzoneLabels; + QString metricX, metricY; + int digits; + double delta; // source cache RideFileCache *cache; // storage for data counts QVector wattsArray, wattsZoneArray, wattsKgArray, nmArray, hrArray, - hrZoneArray, kphArray, cadArray; + hrZoneArray, kphArray, cadArray, metricArray; // storage for data counts in interval selected QVector wattsSelectedArray, wattsZoneSelectedArray, @@ -183,7 +196,8 @@ class PowerHist : public QwtPlot hrZoneSelectedArray, kphSelectedArray, cadSelectedArray; - enum Source { Ride, Cache } source, LASTsource; + enum Source { Ride, Cache, Metric } source, LASTsource; + QColor metricColor; // last plot settings - to avoid lots of uneeded recalcs RideItem *LASTrideItem; @@ -493,4 +507,19 @@ public: } }; +class HTimeScaleDraw: public QwtScaleDraw +{ + + public: + + HTimeScaleDraw() : QwtScaleDraw() {} + + virtual QwtText label(double v) const + { + QTime t = QTime().addSecs(v*60.00); + if (scaleMap().sDist() > 5) + return t.toString("hh:mm"); + return t.toString("hh:mm:ss"); + } +}; #endif // _GC_PowerHist_h diff --git a/src/QtMacButton.mm b/src/QtMacButton.mm index 42894b01c..e30a51aed 100644 --- a/src/QtMacButton.mm +++ b/src/QtMacButton.mm @@ -147,6 +147,7 @@ public: switch(bezelStyle) { case QtMacButton::Recessed: [nsButton setButtonType:NSOnOffButton]; + [[nsButton cell] setGradientType:NSGradientConvexStrong ]; [nsButton setShowsBorderOnlyWhileMouseInside:true ]; [[nsButton cell] setBackgroundStyle:NSBackgroundStyleRaised]; //[nsButton setButtonType:NSPushOnPushOffButton]; diff --git a/src/SummaryMetrics.cpp b/src/SummaryMetrics.cpp index 9853fa19a..ded4f8b54 100644 --- a/src/SummaryMetrics.cpp +++ b/src/SummaryMetrics.cpp @@ -57,6 +57,19 @@ const RideMetric *metricForSymbol(QString symbol) return factory.rideMetric(symbol); } +double +SummaryMetrics::getForSymbol(QString symbol, bool metric) const +{ + if (metric) return value.value(symbol, 0.0); + else { + const RideMetric *m = metricForSymbol(symbol); + double metricValue = value.value(symbol, 0.0); + metricValue *= m->conversion(); + metricValue += m->conversionSum(); + return metricValue; + } +} + QString SummaryMetrics::getStringForSymbol(QString symbol, bool UseMetric) const { diff --git a/src/SummaryMetrics.h b/src/SummaryMetrics.h index 07d3bc1d2..2baffa90e 100644 --- a/src/SummaryMetrics.h +++ b/src/SummaryMetrics.h @@ -47,7 +47,7 @@ class SummaryMetrics // metric values void setForSymbol(QString symbol, double v) { value.insert(symbol, v); } - double getForSymbol(QString symbol) const { return value.value(symbol, 0.0); } + double getForSymbol(QString symbol, bool metric=true) const; void setText(QString name, QString v) { text.insert(name, v); } QString getText(QString name, QString fallback) { return text.value(name, fallback); }