Metric Histogram Plot

Update to the histogram plot to now plot long term metrics.
It enables you to plot distribution of say, Intensity Factor
for a season or cycle.

You can select the y-axis metric too, so rather than just
plotting duration you could plot say, TSS accumulated for
different ride intensities.

Fixes #560
This commit is contained in:
Mark Liversedge
2013-04-20 10:35:44 +01:00
parent e71569043a
commit 1b16b034bd
14 changed files with 970 additions and 271 deletions

Binary file not shown.

View File

@@ -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

View File

@@ -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,

View File

@@ -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()) {

View File

@@ -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;

View File

@@ -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<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();
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<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()
{
@@ -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<RideFile::SeriesType>(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<RideFile::SeriesType>(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<RideFile::SeriesType>(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()<powerHist->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

View File

@@ -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<SummaryMetrics> results;
DateRange last;
};
#endif // _GC_HistogramWindow_h

View File

@@ -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);

View File

@@ -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();

View File

@@ -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<unsigned int> *array = NULL;
QVector<unsigned int> *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<SummaryMetrics>&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<min) min = v;
}
// lets truncate the data if there are very high
// or very low max/min values, to ensure we don't exhaust memory
if (max > 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)<min || (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);

View File

@@ -28,6 +28,7 @@
#include <qwt_plot.h>
#include <qwt_plot_zoomer.h>
#include <qwt_compat.h>
#include <qwt_scale_draw.h>
#include <qsettings.h>
#include <qvariant.h>
@@ -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<SummaryMetrics>&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 <PowerHistZoneLabel *> zoneLabels;
QList <HrHistZoneLabel *> hrzoneLabels;
QString metricX, metricY;
int digits;
double delta;
// source cache
RideFileCache *cache;
// storage for data counts
QVector<unsigned int> wattsArray, wattsZoneArray, wattsKgArray, nmArray, hrArray,
hrZoneArray, kphArray, cadArray;
hrZoneArray, kphArray, cadArray, metricArray;
// storage for data counts in interval selected
QVector<unsigned int> 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

View File

@@ -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];

View File

@@ -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
{

View File

@@ -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); }