From 749a21d9bacb9b39dacecd344c0bf5e1cbc33c4d Mon Sep 17 00:00:00 2001 From: Joachim Kohlhammer Date: Sat, 17 Jan 2026 17:03:59 +0100 Subject: [PATCH] Train mode: Improved readability for ErgFilePlot (#4806) * Any curve in ErgFilePlot can be hidden (W'bal, Power, Speed, ...) * Configurable plot width for curves * When using colored power zones: Zone color can be dimmed to enhance visibility of the curves --- src/Train/ErgFilePlot.cpp | 161 ++++++++++++++++++++++++++------ src/Train/ErgFilePlot.h | 19 ++++ src/Train/WorkoutPlotWindow.cpp | 120 ++++++++++++++++++++++++ src/Train/WorkoutPlotWindow.h | 20 ++++ 4 files changed, 290 insertions(+), 30 deletions(-) diff --git a/src/Train/ErgFilePlot.cpp b/src/Train/ErgFilePlot.cpp index 7a48b1fcf..0aa788591 100644 --- a/src/Train/ErgFilePlot.cpp +++ b/src/Train/ErgFilePlot.cpp @@ -184,17 +184,11 @@ ErgFilePlot::ErgFilePlot(Context *context) : context(context) wbalCurvePredict = new QwtPlotCurve("W'bal Predict"); wbalCurvePredict->attach(this); wbalCurvePredict->setYAxis(QwtAxisId(QwtAxis::YRight, 3)); - QColor predict = GColor(CWBAL).darker(); - predict.setAlpha(200); - QPen wbalPen = QPen(predict, 2.0); // predict darker... - wbalCurvePredict->setPen(wbalPen); wbalCurvePredict->setVisible(true); wbalCurve = new QwtPlotCurve("W'bal Actual"); wbalCurve->attach(this); wbalCurve->setYAxis(QwtAxisId(QwtAxis::YRight, 3)); - QPen wbalPenA = QPen(GColor(CWBAL), 1.0); // actual lighter - wbalCurve->setPen(wbalPenA); wbalData = new CurveData; wbalCurve->setSamples(wbalData->x(), wbalData->y(), wbalData->count()); @@ -215,8 +209,6 @@ ErgFilePlot::ErgFilePlot(Context *context) : context(context) // telemetry history wattsCurve = new QwtPlotCurve("Power"); - QPen wattspen = QPen(GColor(CPOWER)); - wattsCurve->setPen(wattspen); wattsCurve->attach(this); wattsCurve->setYAxis(QwtAxis::YLeft); // dgr wattsCurve->setPaintAttribute(QwtPlotCurve::PaintFiltered); @@ -225,8 +217,6 @@ ErgFilePlot::ErgFilePlot(Context *context) : context(context) // telemetry history hrCurve = new QwtPlotCurve("Heartrate"); - QPen hrpen = QPen(GColor(CHEARTRATE)); - hrCurve->setPen(hrpen); hrCurve->attach(this); hrCurve->setYAxis(QwtAxis::YRight); hrData = new CurveData; @@ -234,8 +224,6 @@ ErgFilePlot::ErgFilePlot(Context *context) : context(context) // telemetry history cadCurve = new QwtPlotCurve("Cadence"); - QPen cadpen = QPen(GColor(CCADENCE)); - cadCurve->setPen(cadpen); cadCurve->attach(this); cadCurve->setYAxis(QwtAxis::YRight); cadData = new CurveData; @@ -243,8 +231,6 @@ ErgFilePlot::ErgFilePlot(Context *context) : context(context) // telemetry history speedCurve = new QwtPlotCurve("Speed"); - QPen speedpen = QPen(GColor(CSPEED)); - speedCurve->setPen(speedpen); speedCurve->attach(this); speedCurve->setYAxis(QwtAxisId(QwtAxis::YRight,2).id); speedData = new CurveData; @@ -322,7 +308,7 @@ ErgFilePlot::configChanged(qint32) CPMarker->show(); } else CPMarker->hide(); - replot(); + updateCurves(); } @@ -655,13 +641,78 @@ ErgFilePlot::eventFilter (QObject *obj, QEvent *event) { if (obj == canvas() && event->type() == QEvent::Leave) { - highlightSectionCurve(nullptr); + if (! workoutActive) { + highlightSectionCurve(nullptr); + } tooltip->setText(""); } return false; } +void +ErgFilePlot::setPlotLineWidth +(double width) +{ + _plotLineWidth = width; + updateCurves(); +} + + +void +ErgFilePlot::showWbalCurvePredict +(bool showCurve) +{ + _showWbalCurvePredict = showCurve; + updateCurves(); +} + + +void +ErgFilePlot::showWbalCurve +(bool showCurve) +{ + _showWbalCurve = showCurve; + updateCurves(); +} + + +void +ErgFilePlot::showWattsCurve +(bool showCurve) +{ + _showWattsCurve = showCurve; + updateCurves(); +} + + +void +ErgFilePlot::showHrCurve +(bool showCurve) +{ + _showHrCurve = showCurve; + updateCurves(); +} + + +void +ErgFilePlot::showCadCurve +(bool showCurve) +{ + _showCadCurve = showCurve; + updateCurves(); +} + + +void +ErgFilePlot::showSpeedCurve +(bool showCurve) +{ + _showSpeedCurve = showCurve; + updateCurves(); +} + + int ErgFilePlot::showColorZones () const @@ -696,6 +747,15 @@ ErgFilePlot::setShowTooltip } +void +ErgFilePlot::setActiveCurveAlpha +(int alpha) +{ + _activeCurveAlpha = alpha; + updateSectionCurveAlpha(); +} + + void ErgFilePlot::performancePlot(RealtimeData rtdata) { @@ -895,6 +955,7 @@ ErgFilePlot::startWorkout () { workoutActive = true; + updateSectionCurveAlpha(); selectTooltip(); selectCurves(); } @@ -905,6 +966,7 @@ ErgFilePlot::stopWorkout () { workoutActive = false; + updateSectionCurveAlpha(); selectCurves(); selectTooltip(); } @@ -918,16 +980,54 @@ ErgFilePlot::selectCurves && ! bydist && ( _showColorZones == 1 || (_showColorZones == 2 && ! workoutActive)); - if (showColored) { - LodCurve->hide(); - for (int i = 0; i < powerSectionCurves.size(); ++i) { - powerSectionCurves[i]->show(); - } - } else { - LodCurve->show(); - for (int i = 0; i < powerSectionCurves.size(); ++i) { - powerSectionCurves[i]->hide(); - } + LodCurve->setVisible(! showColored); + for (int i = 0; i < powerSectionCurves.size(); ++i) { + powerSectionCurves[i]->setVisible(showColored); + } + replot(); +} + + +void +ErgFilePlot::updateCurves +() +{ + bool antialias = appsettings->value(this, GC_ANTIALIAS, true).toBool(); + LodCurve->setRenderHint(QwtPlotItem::RenderAntialiased, antialias); + QColor predict = GColor(CWBAL).darker(); + predict.setAlpha(200); + wbalCurvePredict->setPen(QPen(predict, _plotLineWidth)); + wbalCurvePredict->setRenderHint(QwtPlotItem::RenderAntialiased, antialias); + wbalCurvePredict->setVisible(_showWbalCurvePredict && ergFile && ergFile->hasWatts()); + wbalCurve->setPen(QPen(GColor(CWBAL), _plotLineWidth)); + wbalCurve->setRenderHint(QwtPlotItem::RenderAntialiased, antialias); + wbalCurve->setVisible(_showWbalCurve); + wattsCurve->setPen(QPen(GColor(CPOWER), _plotLineWidth)); + wattsCurve->setRenderHint(QwtPlotItem::RenderAntialiased, antialias); + wattsCurve->setVisible(_showWattsCurve); + hrCurve->setPen(QPen(GColor(CHEARTRATE), _plotLineWidth)); + hrCurve->setRenderHint(QwtPlotItem::RenderAntialiased, antialias); + hrCurve->setVisible(_showHrCurve); + cadCurve->setPen(QPen(GColor(CCADENCE), _plotLineWidth)); + cadCurve->setRenderHint(QwtPlotItem::RenderAntialiased, antialias); + cadCurve->setVisible(_showCadCurve); + speedCurve->setPen(QPen(GColor(CSPEED), _plotLineWidth)); + speedCurve->setRenderHint(QwtPlotItem::RenderAntialiased, antialias); + speedCurve->setVisible(_showSpeedCurve); + replot(); +} + + +void +ErgFilePlot::updateSectionCurveAlpha +() +{ + for (QwtPlotCurve*& curve : powerSectionCurves) { + QBrush brush = curve->brush(); + QColor color = brush.color(); + color.setAlpha(workoutActive ? _activeCurveAlpha : sectionAlphaNeutral); + brush.setColor(color); + curve->setBrush(brush); } replot(); } @@ -1028,7 +1128,7 @@ ErgFilePlot::updateWBalCurvePredict // and the values ... but avoid sharing! wbalCurvePredict->setSamples(calculator.xdata(false), calculator.ydata()); - wbalCurvePredict->setVisible(true); + wbalCurvePredict->setVisible(_showWbalCurvePredict); } else { wbalCurvePredict->setVisible(false); } @@ -1045,13 +1145,14 @@ ErgFilePlot::createSectionCurve } powerSectionCurves.clear(); QList zoneSections = ergFile->ZoneSections(); - bool antiAlias = appsettings->value(this, GC_ANTIALIAS, false).toBool(); + bool antialias = appsettings->value(this, GC_ANTIALIAS, true).toBool(); + int alpha = workoutActive ? _activeCurveAlpha : sectionAlphaNeutral; for (int i = 0; i < zoneSections.length(); ++i) { QVector sectionData; sectionData << QPointF(zoneSections[i].start, zoneSections[i].startValue) << QPointF(zoneSections[i].end, zoneSections[i].endValue); QColor color = QColor(zoneColor(zoneSections[i].zone, 0)); - color.setAlpha(sectionAlphaNeutral); + color.setAlpha(alpha); QwtPlotCurve *sectionCurve = new QwtPlotCurve("Course Load"); sectionCurve->setSamples(sectionData); sectionCurve->setBaseline(-1000); @@ -1059,7 +1160,7 @@ ErgFilePlot::createSectionCurve sectionCurve->setZ(-100); sectionCurve->setPen(QColor(0, 0, 0, 0)); sectionCurve->setBrush(color); - sectionCurve->setRenderHint(QwtPlotItem::RenderAntialiased, antiAlias); + sectionCurve->setRenderHint(QwtPlotItem::RenderAntialiased, antialias); sectionCurve->attach(this); sectionCurve->hide(); powerSectionCurves.append(sectionCurve); diff --git a/src/Train/ErgFilePlot.h b/src/Train/ErgFilePlot.h index d37017c87..3d4df9c26 100644 --- a/src/Train/ErgFilePlot.h +++ b/src/Train/ErgFilePlot.h @@ -160,13 +160,23 @@ class ErgFilePlot : public QwtPlot void startWorkout(); void stopWorkout(); void selectCurves(); + void updateCurves(); + void updateSectionCurveAlpha(); void selectTooltip(); void intensityChanged(int intensity); + void setPlotLineWidth(double width); + void showWbalCurvePredict(bool showCurve); + void showWbalCurve(bool showCurve); + void showWattsCurve(bool showCurve); + void showHrCurve(bool showCurve); + void showCadCurve(bool showCurve); + void showSpeedCurve(bool showCurve); int showColorZones() const; void setShowColorZones(int index); int showTooltip() const; void setShowTooltip(int index); + void setActiveCurveAlpha(int alpha); private: WPrime calculator; @@ -175,7 +185,16 @@ class ErgFilePlot : public QwtPlot ErgFile *ergFile; QwtPlotMarker *CPMarker; + double _plotLineWidth = 1.0; + bool _curveAntialias = true; + bool _showWbalCurvePredict = true; + bool _showWbalCurve = true; + bool _showWattsCurve = true; + bool _showHrCurve = true; + bool _showCadCurve = true; + bool _showSpeedCurve = true; int _showColorZones = 0; + int _activeCurveAlpha = 255; int _showTooltip = 0; QwtPlotGrid *grid; diff --git a/src/Train/WorkoutPlotWindow.cpp b/src/Train/WorkoutPlotWindow.cpp index d2e7b9f8e..4b8226356 100644 --- a/src/Train/WorkoutPlotWindow.cpp +++ b/src/Train/WorkoutPlotWindow.cpp @@ -31,6 +31,24 @@ WorkoutPlotWindow::WorkoutPlotWindow(Context *context) : ctrlsShowNotification = new QCheckBox(); connect(ctrlsShowNotification, &QCheckBox::toggled, this, &WorkoutPlotWindow::setShowNotifications); + ctrlsLineWidthLabel = new QLabel(); + ctrlsLineWidth = new QSlider(Qt::Horizontal); + ctrlsLineWidth->setRange(0, 8); + + ctrlsShowCurveLabel = new QLabel(); + ctrlsShowWbalCurvePredict = new QCheckBox(); + ctrlsShowWbalCurvePredict->setChecked(true); + ctrlsShowWbalCurve = new QCheckBox(); + ctrlsShowWbalCurve->setChecked(true); + ctrlsShowWattsCurve = new QCheckBox(); + ctrlsShowWattsCurve->setChecked(true); + ctrlsShowHrCurve = new QCheckBox(); + ctrlsShowHrCurve->setChecked(true); + ctrlsShowCadCurve = new QCheckBox(); + ctrlsShowCadCurve->setChecked(true); + ctrlsShowSpeedCurve = new QCheckBox(); + ctrlsShowSpeedCurve->setChecked(true); + ctrlsCommonLabel = new QLabel(); ctrlsErgmodeLabel = new QLabel(); @@ -41,6 +59,10 @@ WorkoutPlotWindow::WorkoutPlotWindow(Context *context) : ctrlsSituation->addItem(""); connect(ctrlsSituation, SIGNAL(currentIndexChanged(int)), this, SLOT(setShowColorZones(int))); + ctrlsTransparencyLabel = new QLabel(); + ctrlsTransparencySlider = new QSlider(Qt::Horizontal); + ctrlsTransparencySlider->setRange(0, 100); + ctrlsShowTooltipLabel = new QLabel(); ctrlsShowTooltip = new QComboBox(); ctrlsShowTooltip->addItem(""); @@ -50,9 +72,17 @@ WorkoutPlotWindow::WorkoutPlotWindow(Context *context) : QFormLayout *settingsLayout = newQFormLayout(); settingsLayout->addRow(ctrlsCommonLabel); settingsLayout->addRow("", ctrlsShowNotification); + settingsLayout->addRow(ctrlsLineWidthLabel, ctrlsLineWidth); + settingsLayout->addRow(ctrlsShowCurveLabel, ctrlsShowWbalCurvePredict); + settingsLayout->addRow("", ctrlsShowWbalCurve); + settingsLayout->addRow("", ctrlsShowWattsCurve); + settingsLayout->addRow("", ctrlsShowHrCurve); + settingsLayout->addRow("", ctrlsShowCadCurve); + settingsLayout->addRow("", ctrlsShowSpeedCurve); settingsLayout->addItem(new QSpacerItem(0, 15 * dpiYFactor)); settingsLayout->addRow(ctrlsErgmodeLabel); settingsLayout->addRow(ctrlsSituationLabel, ctrlsSituation); + settingsLayout->addRow(ctrlsTransparencyLabel, ctrlsTransparencySlider); settingsLayout->addRow(ctrlsShowTooltipLabel, ctrlsShowTooltip); setContentsMargins(0,0,0,0); @@ -94,6 +124,18 @@ WorkoutPlotWindow::WorkoutPlotWindow(Context *context) : connect(context, &Context::clearNotification, this, [this]() { setProperty("subtitle", title); }); + connect(ctrlsShowWbalCurvePredict, &QCheckBox::toggled, ergPlot, &ErgFilePlot::showWbalCurvePredict); + connect(ctrlsShowWbalCurve, &QCheckBox::toggled, ergPlot, &ErgFilePlot::showWbalCurve); + connect(ctrlsShowWattsCurve, &QCheckBox::toggled, ergPlot, &ErgFilePlot::showWattsCurve); + connect(ctrlsShowHrCurve, &QCheckBox::toggled, ergPlot, &ErgFilePlot::showHrCurve); + connect(ctrlsShowCadCurve, &QCheckBox::toggled, ergPlot, &ErgFilePlot::showCadCurve); + connect(ctrlsShowSpeedCurve, &QCheckBox::toggled, ergPlot, &ErgFilePlot::showSpeedCurve); + connect(ctrlsLineWidth, &QSlider::valueChanged, this, [this](int value) { + ergPlot->setPlotLineWidth(1 + value / 2.0); + }); + connect(ctrlsTransparencySlider, &QSlider::valueChanged, this, [this](int value) { + ergPlot->setActiveCurveAlpha(255 - value * 2.5); + }); configChanged(0); @@ -129,12 +171,24 @@ WorkoutPlotWindow::configChanged(qint32) ctrlsShowNotification->setText(tr("Show notifications and textcues in title")); + ctrlsLineWidthLabel->setText(tr("Plot line width")); + + ctrlsShowCurveLabel->setText(tr("Show Curve")); + ctrlsShowWbalCurvePredict->setText(tr("W'bal Prediction")); + ctrlsShowWbalCurve->setText(tr("W'bal")); + ctrlsShowWattsCurve->setText(tr("Power")); + ctrlsShowHrCurve->setText(tr("Heartrate")); + ctrlsShowCadCurve->setText(tr("Cadence")); + ctrlsShowSpeedCurve->setText(tr("Speed")); + ctrlsErgmodeLabel->setText("" + tr("Ergmode specific settings") + ""); ctrlsSituationLabel->setText(tr("Color power zones")); ctrlsSituation->setItemText(0, tr("Never")); ctrlsSituation->setItemText(1, tr("Always")); ctrlsSituation->setItemText(2, tr("When stopped")); + ctrlsTransparencyLabel->setText(tr("Power zones transparency (when active)")); + ctrlsShowTooltipLabel->setText(tr("Show tooltip")); ctrlsShowTooltip->setItemText(0, tr("Never")); ctrlsShowTooltip->setItemText(1, tr("When stopped")); @@ -162,6 +216,55 @@ WorkoutPlotWindow::setShowNotifications } +double +WorkoutPlotWindow::lineWidth +() const +{ + return ctrlsLineWidth->value(); +} + + +void +WorkoutPlotWindow::setLineWidth +(double width) +{ + ctrlsLineWidth->setValue(width); +} + + +int +WorkoutPlotWindow::showCurves +() const +{ + int ret = (ctrlsShowWbalCurvePredict->isChecked() ? 1 : 0) << 0 + | (ctrlsShowWbalCurve->isChecked() ? 1 : 0) << 1 + | (ctrlsShowWattsCurve->isChecked() ? 1 : 0) << 2 + | (ctrlsShowHrCurve->isChecked() ? 1 : 0) << 3 + | (ctrlsShowCadCurve->isChecked() ? 1 : 0) << 4 + | (ctrlsShowSpeedCurve->isChecked() ? 1 : 0) << 5; + return ret; +} + + +void +WorkoutPlotWindow::setShowCurves +(int curves) +{ + ctrlsShowWbalCurvePredict->setChecked((curves & (1 << 0)) > 0); + ctrlsShowWbalCurve->setChecked((curves & (1 << 1)) > 0); + ctrlsShowWattsCurve->setChecked((curves & (1 << 2)) > 0); + ctrlsShowHrCurve->setChecked((curves & (1 << 3)) > 0); + ctrlsShowCadCurve->setChecked((curves & (1 << 4)) > 0); + ctrlsShowSpeedCurve->setChecked((curves & (1 << 5)) > 0); + ergPlot->showWbalCurvePredict(ctrlsShowWbalCurvePredict->isChecked()); + ergPlot->showWbalCurve(ctrlsShowWbalCurve->isChecked()); + ergPlot->showWattsCurve(ctrlsShowWattsCurve->isChecked()); + ergPlot->showHrCurve(ctrlsShowHrCurve->isChecked()); + ergPlot->showCadCurve(ctrlsShowCadCurve->isChecked()); + ergPlot->showSpeedCurve(ctrlsShowSpeedCurve->isChecked()); +} + + int WorkoutPlotWindow::showColorZones () const @@ -176,6 +279,23 @@ WorkoutPlotWindow::setShowColorZones { ctrlsSituation->setCurrentIndex(index); ergPlot->setShowColorZones(index); + ctrlsTransparencySlider->setEnabled(index == 1); +} + + +int +WorkoutPlotWindow::colorZonesTransparency +() const +{ + return ctrlsTransparencySlider->value(); +} + + +void +WorkoutPlotWindow::setColorZonesTransparency +(int transparency) +{ + ctrlsTransparencySlider->setValue(transparency); } diff --git a/src/Train/WorkoutPlotWindow.h b/src/Train/WorkoutPlotWindow.h index aeef4a8a5..5e177b6ab 100644 --- a/src/Train/WorkoutPlotWindow.h +++ b/src/Train/WorkoutPlotWindow.h @@ -40,7 +40,10 @@ class WorkoutPlotWindow : public GcChartWindow Q_OBJECT Q_PROPERTY(int showNotifications READ showNotifications WRITE setShowNotifications USER true) + Q_PROPERTY(int lineWidth READ lineWidth WRITE setLineWidth USER true) + Q_PROPERTY(int showCurves READ showCurves WRITE setShowCurves USER true) Q_PROPERTY(int showColorZones READ showColorZones WRITE setShowColorZones USER true) + Q_PROPERTY(int colorZonesTransparency READ colorZonesTransparency WRITE setColorZonesTransparency USER true) Q_PROPERTY(int showTooltip READ showTooltip WRITE setShowTooltip USER true) public: @@ -56,8 +59,14 @@ class WorkoutPlotWindow : public GcChartWindow bool showNotifications() const; void setShowNotifications(bool show); + double lineWidth() const; + void setLineWidth(double width); + int showCurves() const; + void setShowCurves(int curves); int showColorZones() const; void setShowColorZones(int index); + int colorZonesTransparency() const; + void setColorZonesTransparency(int transparency); int showTooltip() const; void setShowTooltip(int index); @@ -69,9 +78,20 @@ class WorkoutPlotWindow : public GcChartWindow QLabel *ctrlsCommonLabel; QCheckBox *ctrlsShowNotification; + QLabel *ctrlsLineWidthLabel; + QSlider *ctrlsLineWidth; + QLabel *ctrlsShowCurveLabel; + QCheckBox *ctrlsShowWbalCurvePredict; + QCheckBox *ctrlsShowWbalCurve; + QCheckBox *ctrlsShowWattsCurve; + QCheckBox *ctrlsShowHrCurve; + QCheckBox *ctrlsShowCadCurve; + QCheckBox *ctrlsShowSpeedCurve; QLabel *ctrlsErgmodeLabel; QLabel *ctrlsSituationLabel; QComboBox *ctrlsSituation; + QLabel *ctrlsTransparencyLabel; + QSlider *ctrlsTransparencySlider; QLabel *ctrlsShowTooltipLabel; QComboBox *ctrlsShowTooltip; };