diff --git a/src/Charts/LTMPlot.cpp b/src/Charts/LTMPlot.cpp index 3f873b8aa..476274835 100644 --- a/src/Charts/LTMPlot.cpp +++ b/src/Charts/LTMPlot.cpp @@ -3049,6 +3049,14 @@ void LTMPlot::createEstimateData(Context *context, LTMSettings *settings, MetricDetail metricDetail, QVector&x,QVector&y,int&n, bool) { + // curve specific filter, used to figure out if all activities are runs + Specification spec = settings->specification; + if (!SearchFilterBox::isNull(metricDetail.datafilter)) + spec.addMatches(SearchFilterBox::matches(context, metricDetail.datafilter)); + int nActivities, nRides, nRuns, nSwims; + context->athlete->rideCache->getRideTypeCounts(spec, nActivities, nRides, nRuns, nSwims); + metricDetail.run = (nRuns > 0 && nActivities == nRuns); + // resize the curve array to maximum possible size (even if we don't need it) int maxdays = groupForDate(settings->end.date(), settings->groupBy) - groupForDate(settings->start.date(), settings->groupBy); @@ -3739,6 +3747,14 @@ LTMPlot::createMeasureData(Context *context, LTMSettings *settings, MetricDetail void LTMPlot::createPerformanceData(Context *context, LTMSettings *settings, MetricDetail metricDetail, QVector&x,QVector&y,int&n, bool) { + // curve specific filter, used to figure out if all activities are runs + Specification spec = settings->specification; + if (!SearchFilterBox::isNull(metricDetail.datafilter)) + spec.addMatches(SearchFilterBox::matches(context, metricDetail.datafilter)); + int nActivities, nRides, nRuns, nSwims; + context->athlete->rideCache->getRideTypeCounts(spec, nActivities, nRides, nRuns, nSwims); + metricDetail.run = (nRuns > 0 && nActivities == nRuns); + int maxdays = groupForDate(settings->end.date(), settings->groupBy) - groupForDate(settings->start.date(), settings->groupBy); @@ -3757,6 +3773,9 @@ LTMPlot::createPerformanceData(Context *context, LTMSettings *settings, MetricDe // scan for performance tests and create a map so we can lookup quickly QHash tests; foreach (RideItem *item, context->athlete->rideCache->rides()) { + + if (!spec.pass(item)) continue; // skip filtered out activities + if (item->dateTime.date() >= settings->start.date() && item->dateTime.date() <= settings->end.date()) { foreach(IntervalItem *i, item->intervals()) { if (i->istest()) { @@ -3796,12 +3815,12 @@ LTMPlot::createPerformanceData(Context *context, LTMSettings *settings, MetricDe } if (metricDetail.perfs && value <= 0) { // is there a weekly performance today? - Performance p = context->athlete->rideCache->estimator->getPerformanceForDate(date); + Performance p = context->athlete->rideCache->estimator->getPerformanceForDate(date, metricDetail.run); if (!p.submaximal) value = p.powerIndex; } if (metricDetail.submax && value <= 0) { // is there a submax weekly performance today? - Performance p = context->athlete->rideCache->estimator->getPerformanceForDate(date); + Performance p = context->athlete->rideCache->estimator->getPerformanceForDate(date, metricDetail.run); if (p.submaximal) value = p.powerIndex; } diff --git a/src/Charts/LTMTool.cpp b/src/Charts/LTMTool.cpp index 039769809..5803cc81e 100644 --- a/src/Charts/LTMTool.cpp +++ b/src/Charts/LTMTool.cpp @@ -1794,29 +1794,11 @@ EditMetricDetailDialog::EditMetricDetailDialog(Context *context, LTMTool *ltmToo estwpk->addWidget(wpk); estwpk->addStretch(); - // estimate for rides or runs ? - bik = new QRadioButton(tr("Bike"), this); - run = new QRadioButton(tr("Run"), this); - run->setChecked(metricDetail->run); - bik->setChecked(!metricDetail->run); - // put them into a button group because we - // also have radio buttons for watts per kilo / absolute - QButtonGroup* runGroup = new QButtonGroup(this); - runGroup->addButton(bik); - runGroup->addButton(run); - - QHBoxLayout *estrun = new QHBoxLayout; - estrun->addStretch(); - estrun->addWidget(bik); - estrun->addWidget(run); - estrun->addStretch(); - estimateLayout->addStretch(); estimateLayout->addWidget(modelSelect); estimateLayout->addWidget(estimateSelect); estimateLayout->addLayout(estbestLayout); estimateLayout->addLayout(estwpk); - estimateLayout->addLayout(estrun); estimateLayout->addStretch(); // estimate selection @@ -2589,7 +2571,6 @@ EditMetricDetailDialog::applyClicked() metricDetail->datafilter = dataFilter->filter(); metricDetail->wpk = wpk->isChecked(); - metricDetail->run = run->isChecked(); metricDetail->series = seriesList.at(dataSeries->currentIndex()); metricDetail->model = models[modelSelect->currentIndex()]->code(); metricDetail->estimate = estimateSelect->currentIndex(); // 0 - 5 diff --git a/src/Charts/LTMTool.h b/src/Charts/LTMTool.h index fdb723f9d..31e65faf2 100644 --- a/src/Charts/LTMTool.h +++ b/src/Charts/LTMTool.h @@ -209,7 +209,6 @@ class EditMetricDetailDialog : public QDialog QDoubleSpinBox *estimateDuration; QComboBox *estimateDurationUnits; QRadioButton *abs, *wpk; - QRadioButton *bik, *run; // stress QComboBox *stressTypeSelect; // STS, LTS, SB, RR et al diff --git a/src/Metrics/Banister.cpp b/src/Metrics/Banister.cpp index ac2e7841f..b421c3089 100644 --- a/src/Metrics/Banister.cpp +++ b/src/Metrics/Banister.cpp @@ -188,7 +188,6 @@ Banister::getPeakCP(QDate from, QDate to, int &CP) } CP = max; - // RMSE return start.addDays(index); } @@ -279,39 +278,20 @@ Banister::refresh() if (score>0) { rides++; meanscore += score; // averaged at end - } - // get best scoring performance *test* in the ride - double todaybest=0; - foreach(IntervalItem *i, item->intervals()) { - if (i->istest()) { - double pix=i->getForSymbol("power_index"); - if (pix > todaybest) todaybest = pix; + // get best scoring performance *test* in the ride + double todaybest=0; + foreach(IntervalItem *i, item->intervals()) { + if (i->istest()) { + double pix=i->getForSymbol("power_index"); + if (pix > todaybest) todaybest = pix; + } } - } - // is there a performance test already there for today? - if (todaybest > 0) { - if (performances > 0 && performanceDay[performances-1] == day) { - if (performanceScore[performances-1] < todaybest) - performanceScore[performances-1] = todaybest; - } else { - performanceDay[performances] = day; - performanceScore[performances] = todaybest; - performances++; - } - } - - // if we didn't find a performance test in the ride lets see if there - // is a weekly performance already identified - if (!(todaybest > 0)) { - Performance p = context->athlete->rideCache->estimator->getPerformanceForDate(item->dateTime.date()); - if (!p.submaximal && p.powerIndex > 0) { - - // its not submax - todaybest = p.powerIndex; + // is there a performance test already there for today? + if (todaybest > 0) { if (performances > 0 && performanceDay[performances-1] == day) { - if (performanceScore[performances-1] < todaybest) // validating with '<=' not '<' is vital for 2-a-days + if (performanceScore[performances-1] < todaybest) performanceScore[performances-1] = todaybest; } else { performanceDay[performances] = day; @@ -319,12 +299,31 @@ Banister::refresh() performances++; } } - } - // add more space - if (performances == performanceDay.size()) { - performanceDay.resize(performanceDay.size() + (days/2)); - performanceScore.resize(performanceDay.size() + (days/2)); + // if we didn't find a performance test in the ride lets see if there + // is a weekly performance already identified + if (!(todaybest > 0)) { + Performance p = context->athlete->rideCache->estimator->getPerformanceForDate(item->dateTime.date(), item->isRun); + if (!p.submaximal && p.powerIndex > 0) { + + // its not submax + todaybest = p.powerIndex; + if (performances > 0 && performanceDay[performances-1] == day) { + if (performanceScore[performances-1] < todaybest) // validating with '<=' not '<' is vital for 2-a-days + performanceScore[performances-1] = todaybest; + } else { + performanceDay[performances] = day; + performanceScore[performances] = todaybest; + performances++; + } + } + } + + // add more space + if (performances == performanceDay.size()) { + performanceDay.resize(performanceDay.size() + (days/2)); + performanceScore.resize(performanceDay.size() + (days/2)); + } } } @@ -566,8 +565,10 @@ void Banister::fit() // // power index metric -double powerIndex(double averagepower, double duration) +double powerIndex(double averagepower, double duration, bool isRun) { + Q_UNUSED(isRun); // TODO: different parameters for Running + // so now lets work out what the 3p model says the // typical athlete would do for the same duration // @@ -635,7 +636,7 @@ class PowerIndex : public RideMetric { } // calculate power index, 0=out of bounds - double pix = powerIndex(averagepower, duration); + double pix = powerIndex(averagepower, duration, item->isRun); // we could convert to linear work time model before // indexing, but they cancel out so no value in doing so diff --git a/src/Metrics/Banister.h b/src/Metrics/Banister.h index c67fd0ba1..c4648670d 100644 --- a/src/Metrics/Banister.h +++ b/src/Metrics/Banister.h @@ -32,7 +32,7 @@ extern const double typical_CP, typical_WPrime, typical_Pmax; // calculate power index, used to model in cp plot too -extern double powerIndex(double averagepower, double duration); +extern double powerIndex(double averagepower, double duration, bool isRun=false); // gap with no training that constitutes break in seasons extern const int typical_SeasonBreak; diff --git a/src/Metrics/Estimator.cpp b/src/Metrics/Estimator.cpp index bf04b1801..380c67429 100644 --- a/src/Metrics/Estimator.cpp +++ b/src/Metrics/Estimator.cpp @@ -262,12 +262,13 @@ Estimator::run() double p = double(week[t]); if (week[t]<=0) continue; - double pix = powerIndex(p, t); + double pix = powerIndex(p, t, isRun); if (pix > bestperformance.powerIndex) { bestperformance.duration = t; bestperformance.power = p; bestperformance.powerIndex = pix; bestperformance.when = weekdates[t]; + bestperformance.run = isRun; // for filter, saves having to convert as we go bestperformance.x = bestperformance.when.toJulianDay(); @@ -350,23 +351,23 @@ Estimator::run() performances = perfs; } else { estimates.append(est); - // performances.append(perfs); // TODO running performances + performances.append(perfs); } lock.unlock(); // debug dump peak performances foreach(Performance p, performances) { - printd("%f Peak: %f for %f secs on %s\n", p.powerIndex, p.power, p.duration, p.when.toString().toStdString().c_str()); + printd("%s %f Peak: %f for %f secs on %s\n", p.run ? "Run" : "Bike", p.powerIndex, p.power, p.duration, p.when.toString().toStdString().c_str()); } printd("%s Estimates end.\n", isRun ? "Run" : "Bike"); } } -Performance Estimator::getPerformanceForDate(QDate date) +Performance Estimator::getPerformanceForDate(QDate date, bool wantrun) { // serial search is ok as low numberish - always takes first as should be no dupes foreach(Performance p, performances) { - if (p.when == date) return p; + if (p.when == date && p.run == wantrun) return p; } return Performance(QDate(),0,0,0); } diff --git a/src/Metrics/Estimator.h b/src/Metrics/Estimator.h index eac3a77ed..f8963d897 100644 --- a/src/Metrics/Estimator.h +++ b/src/Metrics/Estimator.h @@ -40,6 +40,7 @@ class Performance { QDate when, weekcommencing; double power, duration, powerIndex; bool submaximal; // set by the filter, user can choose to include. + bool run; // indicates a Running with power performance double x; // different units, but basically when as a julian day }; @@ -72,7 +73,7 @@ class Estimator : public QThread { void calculate(); // get a performance for a given day - Performance getPerformanceForDate(QDate date); + Performance getPerformanceForDate(QDate date, bool wantrun); protected: diff --git a/test/charts/Banister (Run).gchart b/test/charts/Banister (Run).gchart new file mode 100644 index 000000000..c51995256 --- /dev/null +++ b/test/charts/Banister (Run).gchart @@ -0,0 +1,34 @@ +{ + "CHART":{ + "VERSION":"1", + "VIEW":"home", + "TYPE":"7", + "PROPERTIES":{ + "title":"Banister (Run) ", + "subtitle":"Banister (Run) ", + "widthFactor":"2", + "heightFactor":"2", + "style":"0", + "resizable":"0", + "preset":"0", + "bin":"0", + "shade":"0", + "data":"0", + "stack":"0", + "stackWidth":"3", + "legend":"1", + "events":"0", + "banister":"1", + "filter":"search: ", + "fromDate":"Sat Jan 1 2000", + "toDate":"Sat Jan 1 2000", + "startDate":"Thu Oct 11 2018", + "lastN":"7", + "lastNX":"0", + "prevN":"0", + "settings":"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", + "useSelected":"0", + "__LAST__":"1", + } + } +} \ No newline at end of file