mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 16:18:42 +00:00
More configurable realtime plot
Allow data smoothing, which is particularly useful during bike fit sessions to watch trends rather than micro changes. Also allow the user to select which data series to plot. This allows you to open multiple plots for each series.
This commit is contained in:
@@ -78,7 +78,15 @@ void RealtimeHrData::addData(double v) { hrData[hrCur++] = v; if (hrCur==MAXSAMP
|
||||
//void RealtimeLodData::addData(double v) { lodData[lodCur++] = v; if (lodCur==50) lodCur=0; }
|
||||
|
||||
|
||||
RealtimePlot::RealtimePlot() : pwrCurve(NULL)
|
||||
RealtimePlot::RealtimePlot() :
|
||||
pwrCurve(NULL),
|
||||
showPowerState(Qt::Checked),
|
||||
showPow30sState(Qt::Checked),
|
||||
showHrState(Qt::Checked),
|
||||
showSpeedState(Qt::Checked),
|
||||
showCadState(Qt::Checked),
|
||||
showAltState(Qt::Checked),
|
||||
smooth(0)
|
||||
{
|
||||
setInstanceName("Realtime Plot");
|
||||
|
||||
@@ -233,3 +241,63 @@ RealtimePlot::configChanged()
|
||||
spdpen.setWidth(width);
|
||||
spdCurve->setPen(spdpen);
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlot::showPower(int state)
|
||||
{
|
||||
showPowerState = state;
|
||||
pwrCurve->setVisible(state == Qt::Checked);
|
||||
enableAxis(yLeft, showAltState == Qt::Checked || showPowerState == Qt::Checked || showPow30sState == Qt::Checked);
|
||||
replot();
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlot::showPow30s(int state)
|
||||
{
|
||||
showPow30sState = state;
|
||||
pwr30Curve->setVisible(state == Qt::Checked);
|
||||
enableAxis(yLeft, showAltState == Qt::Checked || showPowerState == Qt::Checked || showPow30sState == Qt::Checked);
|
||||
replot();
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlot::showHr(int state)
|
||||
{
|
||||
showHrState = state;
|
||||
hrCurve->setVisible(state == Qt::Checked);
|
||||
enableAxis(yRight, showCadState == Qt::Checked || showHrState == Qt::Checked);
|
||||
replot();
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlot::showSpeed(int state)
|
||||
{
|
||||
showSpeedState = state;
|
||||
spdCurve->setVisible(state == Qt::Checked);
|
||||
enableAxis(yRight2, state == Qt::Checked);
|
||||
replot();
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlot::showCad(int state)
|
||||
{
|
||||
showCadState = state;
|
||||
cadCurve->setVisible(state == Qt::Checked);
|
||||
enableAxis(yRight, showCadState == Qt::Checked || showHrState == Qt::Checked);
|
||||
replot();
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlot::showAlt(int state)
|
||||
{
|
||||
showAltState = state;
|
||||
altPwrCurve->setVisible(state == Qt::Checked);
|
||||
enableAxis(yLeft, showAltState == Qt::Checked || showPowerState == Qt::Checked || showPow30sState == Qt::Checked);
|
||||
replot();
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlot::setSmoothing(int value)
|
||||
{
|
||||
smooth = value;
|
||||
}
|
||||
|
||||
@@ -167,6 +167,13 @@ class RealtimePlot : public QwtPlot
|
||||
QwtPlotCurve *hrCurve;
|
||||
//QwtPlotCurve *lodCurve;
|
||||
|
||||
int showPowerState;
|
||||
int showPow30sState;
|
||||
int showHrState;
|
||||
int showSpeedState;
|
||||
int showCadState;
|
||||
int showAltState;
|
||||
|
||||
#if 0
|
||||
// power stores last 30 seconds for 30 second rolling avg, all else
|
||||
// just the last 30 seconds
|
||||
@@ -183,9 +190,17 @@ class RealtimePlot : public QwtPlot
|
||||
//RealtimeLodData lodData;
|
||||
|
||||
RealtimePlot();
|
||||
int smooth;
|
||||
|
||||
public slots:
|
||||
void configChanged();
|
||||
void showPower(int state);
|
||||
void showPow30s(int state);
|
||||
void showHr(int state);
|
||||
void showSpeed(int state);
|
||||
void showCad(int state);
|
||||
void showAlt(int state);
|
||||
void setSmoothing(int value);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -20,20 +20,82 @@
|
||||
#include "RealtimePlotWindow.h"
|
||||
|
||||
RealtimePlotWindow::RealtimePlotWindow(MainWindow *mainWindow) :
|
||||
GcWindow(mainWindow), mainWindow(mainWindow)
|
||||
GcWindow(mainWindow), mainWindow(mainWindow), active(false)
|
||||
{
|
||||
setContentsMargins(0,0,0,0);
|
||||
setInstanceName("RT Plot");
|
||||
setControls(NULL);
|
||||
setProperty("color", GColor(CRIDEPLOTBACKGROUND));
|
||||
|
||||
QWidget *c = new QWidget;
|
||||
QVBoxLayout *cl = new QVBoxLayout(c);
|
||||
setControls(c);
|
||||
|
||||
// setup the controls
|
||||
QLabel *showLabel = new QLabel(tr("Show"), c);
|
||||
cl->addWidget(showLabel);
|
||||
|
||||
showHr = new QCheckBox(tr("Heart Rate"), this);
|
||||
showHr->setCheckState(Qt::Checked);
|
||||
cl->addWidget(showHr);
|
||||
|
||||
showSpeed = new QCheckBox(tr("Speed"), this);
|
||||
showSpeed->setCheckState(Qt::Checked);
|
||||
cl->addWidget(showSpeed);
|
||||
|
||||
showCad = new QCheckBox(tr("Cadence"), this);
|
||||
showCad->setCheckState(Qt::Checked);
|
||||
cl->addWidget(showCad);
|
||||
|
||||
showPower = new QCheckBox(tr("Power"), this);
|
||||
showPower->setCheckState(Qt::Checked);
|
||||
cl->addWidget(showPower);
|
||||
|
||||
showAlt = new QCheckBox(tr("Alternate Power"), this);
|
||||
showAlt->setCheckState(Qt::Checked);
|
||||
cl->addWidget(showAlt);
|
||||
|
||||
showPow30s = new QCheckBox(tr("30s Power"), this);
|
||||
showPow30s->setCheckState(Qt::Checked);
|
||||
cl->addWidget(showPow30s);
|
||||
|
||||
QLabel *smoothLabel = new QLabel(tr("Smoothing (5Hz Samples)"), this);
|
||||
smoothLineEdit = new QLineEdit(this);
|
||||
smoothLineEdit->setFixedWidth(40);
|
||||
|
||||
cl->addWidget(smoothLabel);
|
||||
cl->addWidget(smoothLineEdit);
|
||||
smoothSlider = new QSlider(Qt::Horizontal);
|
||||
smoothSlider->setTickPosition(QSlider::TicksBelow);
|
||||
smoothSlider->setTickInterval(10);
|
||||
smoothSlider->setMinimum(1);
|
||||
smoothSlider->setMaximum(150);
|
||||
smoothLineEdit->setValidator(new QIntValidator(smoothSlider->minimum(),
|
||||
smoothSlider->maximum(),
|
||||
smoothLineEdit));
|
||||
cl->addWidget(smoothSlider);
|
||||
cl->addStretch();
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
rtPlot = new RealtimePlot();
|
||||
layout->addWidget(rtPlot);
|
||||
|
||||
// common controls
|
||||
connect(showPower, SIGNAL(stateChanged(int)), this, SLOT(setShowPower(int)));
|
||||
connect(showPow30s, SIGNAL(stateChanged(int)), this, SLOT(setShowPow30s(int)));
|
||||
connect(showHr, SIGNAL(stateChanged(int)), this, SLOT(setShowHr(int)));
|
||||
connect(showSpeed, SIGNAL(stateChanged(int)), this, SLOT(setShowSpeed(int)));
|
||||
connect(showCad, SIGNAL(stateChanged(int)), this, SLOT(setShowCad(int)));
|
||||
connect(showAlt, SIGNAL(stateChanged(int)), this, SLOT(setShowAlt(int)));
|
||||
connect(smoothSlider, SIGNAL(valueChanged(int)), this, SLOT(setSmoothingFromSlider()));
|
||||
connect(smoothLineEdit, SIGNAL(editingFinished()), this, SLOT(setSmoothingFromLineEdit()));
|
||||
|
||||
// get updates..
|
||||
connect(mainWindow, SIGNAL(telemetryUpdate(RealtimeData)), this, SLOT(telemetryUpdate(RealtimeData)));
|
||||
|
||||
// lets initialise all the smoothing variables
|
||||
hrtot = hrindex = cadtot = cadindex = spdtot = spdindex = alttot = altindex = powtot = powindex = 0;
|
||||
for(int i=0; i<150; i++) powHist[i] = altHist[i] = spdHist[i] = cadHist[i] = hrHist[i] = 0;
|
||||
|
||||
// set to zero
|
||||
telemetryUpdate(RealtimeData());
|
||||
}
|
||||
@@ -41,13 +103,17 @@ RealtimePlotWindow::RealtimePlotWindow(MainWindow *mainWindow) :
|
||||
void
|
||||
RealtimePlotWindow::start()
|
||||
{
|
||||
//resetValues();
|
||||
// lets initialise all the smoothing variables
|
||||
hrtot = hrindex = cadtot = cadindex = spdtot = spdindex = alttot = altindex = powtot = powindex = 0;
|
||||
for(int i=0; i<150; i++) powHist[i] = altHist[i] = spdHist[i] = cadHist[i] = hrHist[i] = 0;
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlotWindow::stop()
|
||||
{
|
||||
//resetValues();
|
||||
// lets initialise all the smoothing variables
|
||||
hrtot = hrindex = cadtot = cadindex = spdtot = spdindex = alttot = altindex = powtot = powindex = 0;
|
||||
for(int i=0; i<150; i++) powHist[i] = altHist[i] = spdHist[i] = cadHist[i] = hrHist[i] = 0;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -58,11 +124,146 @@ RealtimePlotWindow::pause()
|
||||
void
|
||||
RealtimePlotWindow::telemetryUpdate(RealtimeData rtData)
|
||||
{
|
||||
rtPlot->pwrData.addData(rtData.value(RealtimeData::Watts));
|
||||
rtPlot->altPwrData.addData(rtData.value(RealtimeData::AltWatts));
|
||||
rtPlot->pwr30Data.addData(rtData.value(RealtimeData::Watts));
|
||||
rtPlot->cadData.addData(rtData.value(RealtimeData::Cadence));
|
||||
rtPlot->spdData.addData(rtData.value(RealtimeData::Speed));
|
||||
rtPlot->hrData.addData(rtData.value(RealtimeData::HeartRate));
|
||||
// lets apply smoothing if we have to
|
||||
if (rtPlot->smooth) {
|
||||
|
||||
// Heartrate
|
||||
double hr = rtData.value(RealtimeData::HeartRate);
|
||||
hrtot += hr;
|
||||
hrtot -= hrHist[hrindex];
|
||||
hrHist[hrindex] = hr;
|
||||
hrindex++;
|
||||
if (hrindex >= rtPlot->smooth) hrindex = 0;
|
||||
hr = hrtot / rtPlot->smooth;
|
||||
rtPlot->hrData.addData(hr);
|
||||
|
||||
// Speed
|
||||
double spd= rtData.value(RealtimeData::Speed);
|
||||
spdtot += spd; spdtot -= spdHist[spdindex]; spdHist[spdindex] = spd;
|
||||
spdindex++; if (spdindex >= rtPlot->smooth) spdindex = 0;
|
||||
spd = spdtot / rtPlot->smooth;
|
||||
rtPlot->spdData.addData(spd);
|
||||
|
||||
// Power
|
||||
double pow = rtData.value(RealtimeData::Watts);
|
||||
powtot += pow; powtot -= powHist[powindex]; powHist[powindex] = pow;
|
||||
powindex++; if (powindex >= rtPlot->smooth) powindex = 0;
|
||||
pow = powtot / rtPlot->smooth;
|
||||
rtPlot->pwrData.addData(pow);
|
||||
|
||||
// Alternate Power
|
||||
double alt = rtData.value(RealtimeData::AltWatts);
|
||||
alttot += alt; alttot -= altHist[altindex]; altHist[altindex] = alt;
|
||||
altindex++; if (altindex >= rtPlot->smooth) altindex = 0;
|
||||
alt = alttot / rtPlot->smooth;
|
||||
rtPlot->altPwrData.addData(alt);
|
||||
|
||||
// Cadence
|
||||
double cad = rtData.value(RealtimeData::Cadence);
|
||||
cadtot += cad; cadtot -= cadHist[cadindex]; cadHist[cadindex] = cad;
|
||||
cadindex++; if (cadindex >= rtPlot->smooth) cadindex = 0;
|
||||
cad = cadtot / rtPlot->smooth;
|
||||
rtPlot->cadData.addData(cad);
|
||||
|
||||
// its smoothed to 30s anyway
|
||||
rtPlot->pwr30Data.addData(rtData.value(RealtimeData::Watts));
|
||||
|
||||
} else {
|
||||
rtPlot->pwrData.addData(rtData.value(RealtimeData::Watts));
|
||||
rtPlot->altPwrData.addData(rtData.value(RealtimeData::AltWatts));
|
||||
rtPlot->pwr30Data.addData(rtData.value(RealtimeData::Watts));
|
||||
rtPlot->cadData.addData(rtData.value(RealtimeData::Cadence));
|
||||
rtPlot->spdData.addData(rtData.value(RealtimeData::Speed));
|
||||
rtPlot->hrData.addData(rtData.value(RealtimeData::HeartRate));
|
||||
}
|
||||
rtPlot->replot(); // redraw
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlotWindow::setSmoothingFromSlider()
|
||||
{
|
||||
// active tells us we have been triggered by
|
||||
// the setSmoothingFromLineEdit which will also
|
||||
// recalculates smoothing, lets not double up...
|
||||
if (active) return;
|
||||
else active = true;
|
||||
|
||||
if (rtPlot->smooth != smoothSlider->value()) {
|
||||
setSmoothing(smoothSlider->value());
|
||||
smoothLineEdit->setText(QString("%1").arg(rtPlot->smooth));
|
||||
}
|
||||
active = false;
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlotWindow::setSmoothingFromLineEdit()
|
||||
{
|
||||
// active tells us we have been triggered by
|
||||
// the setSmoothingFromSlider which will also
|
||||
// recalculates smoothing, lets not double up...
|
||||
if (active) return;
|
||||
else active = true;
|
||||
|
||||
int value = smoothLineEdit->text().toInt();
|
||||
if (value != rtPlot->smooth) {
|
||||
smoothSlider->setValue(value);
|
||||
setSmoothing(value);
|
||||
}
|
||||
active = false;
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlotWindow::setShowPow30s(int value)
|
||||
{
|
||||
showPow30s->setChecked(value);
|
||||
rtPlot->showPow30s(value);
|
||||
rtPlot->replot();
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlotWindow::setShowPower(int value)
|
||||
{
|
||||
showPower->setChecked(value);
|
||||
rtPlot->showPower(value);
|
||||
rtPlot->replot();
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlotWindow::setShowHr(int value)
|
||||
{
|
||||
showHr->setChecked(value);
|
||||
rtPlot->showHr(value);
|
||||
rtPlot->replot();
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlotWindow::setShowSpeed(int value)
|
||||
{
|
||||
showSpeed->setChecked(value);
|
||||
rtPlot->showSpeed(value);
|
||||
rtPlot->replot();
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlotWindow::setShowCad(int value)
|
||||
{
|
||||
showCad->setChecked(value);
|
||||
rtPlot->showCad(value);
|
||||
rtPlot->replot();
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlotWindow::setShowAlt(int value)
|
||||
{
|
||||
showAlt->setChecked(value);
|
||||
rtPlot->showAlt(value);
|
||||
rtPlot->replot();
|
||||
}
|
||||
|
||||
void
|
||||
RealtimePlotWindow::setSmoothing(int value)
|
||||
{
|
||||
hrtot = hrindex = cadtot = cadindex = spdtot = spdindex = alttot = altindex = powtot = powindex = 0;
|
||||
smoothSlider->setValue(value);
|
||||
rtPlot->setSmoothing(value);
|
||||
}
|
||||
|
||||
@@ -37,10 +37,27 @@ class RealtimePlotWindow : public GcWindow
|
||||
Q_OBJECT
|
||||
G_OBJECT
|
||||
|
||||
Q_PROPERTY(int showHr READ isShowHr WRITE setShowHr USER true)
|
||||
Q_PROPERTY(int showSpeed READ isShowSpeed WRITE setShowSpeed USER true)
|
||||
Q_PROPERTY(int showCad READ isShowCad WRITE setShowCad USER true)
|
||||
Q_PROPERTY(int showAlt READ isShowAlt WRITE setShowAlt USER true)
|
||||
Q_PROPERTY(int showPower READ isShowPower WRITE setShowPower USER true)
|
||||
Q_PROPERTY(int showPow30s READ isShowPow30s WRITE setShowPow30s USER true)
|
||||
Q_PROPERTY(int smoothing READ smoothing WRITE setSmoothing USER true)
|
||||
|
||||
public:
|
||||
|
||||
RealtimePlotWindow(MainWindow *mainWindow);
|
||||
|
||||
// get properties - the setters are below
|
||||
int isShowHr() const { return showHr->checkState(); }
|
||||
int isShowSpeed() const { return showSpeed->checkState(); }
|
||||
int isShowCad() const { return showCad->checkState(); }
|
||||
int isShowAlt() const { return showAlt->checkState(); }
|
||||
int isShowPower() const { return showPower->checkState(); }
|
||||
int isShowPow30s() const { return showPow30s->checkState(); }
|
||||
int smoothing() const { return smoothSlider->value(); }
|
||||
|
||||
public slots:
|
||||
|
||||
// trap signals
|
||||
@@ -49,10 +66,50 @@ class RealtimePlotWindow : public GcWindow
|
||||
void stop();
|
||||
void pause();
|
||||
|
||||
// set properties
|
||||
void setSmoothingFromSlider();
|
||||
void setSmoothingFromLineEdit();
|
||||
void setShowPower(int state);
|
||||
void setShowPow30s(int state);
|
||||
void setShowHr(int state);
|
||||
void setShowSpeed(int state);
|
||||
void setShowCad(int state);
|
||||
void setShowAlt(int state);
|
||||
void setSmoothing(int value);
|
||||
|
||||
private:
|
||||
|
||||
MainWindow *mainWindow;
|
||||
RealtimePlot *rtPlot;
|
||||
bool active;
|
||||
|
||||
// Common controls
|
||||
QGridLayout *controlsLayout;
|
||||
QCheckBox *showHr;
|
||||
QCheckBox *showSpeed;
|
||||
QCheckBox *showCad;
|
||||
QCheckBox *showAlt;
|
||||
QCheckBox *showPower;
|
||||
QCheckBox *showPow30s;
|
||||
QSlider *smoothSlider;
|
||||
QLineEdit *smoothLineEdit;
|
||||
|
||||
// for smoothing charts
|
||||
double powHist[150];
|
||||
double powtot;
|
||||
int powindex;
|
||||
double altHist[150];
|
||||
double alttot;
|
||||
int altindex;
|
||||
double spdHist[150];
|
||||
double spdtot;
|
||||
int spdindex;
|
||||
double cadHist[150];
|
||||
double cadtot;
|
||||
int cadindex;
|
||||
double hrHist[150];
|
||||
double hrtot;
|
||||
int hrindex;
|
||||
};
|
||||
|
||||
#endif // _GC_RealtimePlotWindow_h
|
||||
|
||||
Reference in New Issue
Block a user