diff --git a/src/AllPlot.cpp b/src/AllPlot.cpp index d6dc110a5..9183bdf72 100644 --- a/src/AllPlot.cpp +++ b/src/AllPlot.cpp @@ -280,6 +280,10 @@ AllPlotObject::AllPlotObject(AllPlot *plot) : plot(plot) wattsCurve->setPaintAttribute(QwtPlotCurve::FilterPoints, true); wattsCurve->setYAxis(QwtAxisId(QwtAxis::yLeft, 0)); + atissCurve = new QwtPlotCurve(tr("aTISS")); + atissCurve->setPaintAttribute(QwtPlotCurve::FilterPoints, true); + atissCurve->setYAxis(QwtAxisId(QwtAxis::yRight, 3)); + npCurve = new QwtPlotCurve(tr("NP")); npCurve->setPaintAttribute(QwtPlotCurve::FilterPoints, true); npCurve->setYAxis(QwtAxisId(QwtAxis::yLeft, 0)); @@ -388,7 +392,7 @@ void AllPlotObject::setColor(QColor color) { QList worklist; - worklist << mCurve << wCurve << wattsCurve << npCurve << xpCurve << speedCurve << accelCurve + worklist << mCurve << wCurve << wattsCurve << atissCurve << npCurve << xpCurve << speedCurve << accelCurve << wattsDCurve << cadDCurve << nmDCurve << hrDCurve << apCurve << cadCurve << tempCurve << hrCurve << torqueCurve << balanceLCurve << balanceRCurve << altCurve; @@ -426,6 +430,7 @@ AllPlotObject::~AllPlotObject() mCurve->detach(); delete mCurve; wCurve->detach(); delete wCurve; wattsCurve->detach(); delete wattsCurve; + atissCurve->detach(); delete atissCurve; npCurve->detach(); delete npCurve; xpCurve->detach(); delete xpCurve; apCurve->detach(); delete apCurve; @@ -455,6 +460,7 @@ AllPlotObject::setVisible(bool show) wCurve->detach(); wattsCurve->detach(); npCurve->detach(); + atissCurve->detach(); xpCurve->detach(); apCurve->detach(); hrCurve->detach(); @@ -491,6 +497,7 @@ AllPlotObject::setVisible(bool show) wCurve->attach(plot); wattsCurve->attach(plot); npCurve->attach(plot); + atissCurve->attach(plot); xpCurve->attach(plot); apCurve->attach(plot); hrCurve->attach(plot); @@ -526,6 +533,7 @@ AllPlotObject::hideUnwanted() { if (plot->showPowerState>1) wattsCurve->detach(); if (!plot->showNP) npCurve->detach(); + if (!plot->showATISS) atissCurve->detach(); if (!plot->showXP) xpCurve->detach(); if (!plot->showAP) apCurve->detach(); if (!plot->showW) wCurve->detach(); @@ -551,6 +559,7 @@ AllPlot::AllPlot(AllPlotWindow *parent, Context *context, RideFile::SeriesType s rideItem(NULL), shade_zones(true), showPowerState(3), + showATISS(false), showNP(false), showXP(false), showAP(false), @@ -599,7 +608,7 @@ AllPlot::AllPlot(AllPlotWindow *parent, Context *context, RideFile::SeriesType s // set the axes that we use.. yLeft 3 is ALWAYS the highlighter axes and never visible setAxesCount(QwtAxis::yLeft, 3); - setAxesCount(QwtAxis::yRight, 3); + setAxesCount(QwtAxis::yRight, 4); setAxesCount(QwtAxis::xBottom, 1); setXTitle(); @@ -629,12 +638,15 @@ AllPlot::AllPlot(AllPlotWindow *parent, Context *context, RideFile::SeriesType s setAxisMaxMinor(QwtAxisId(QwtAxis::yLeft, 1), 0); setAxisMaxMinor(yRight, 0); setAxisMaxMinor(QwtAxisId(QwtAxis::yRight, 1), 0); + setAxisMaxMinor(QwtAxisId(QwtAxis::yRight, 2), 0); + setAxisMaxMinor(QwtAxisId(QwtAxis::yRight, 3), 0); axisWidget(QwtPlot::yLeft)->installEventFilter(this); axisWidget(QwtPlot::yRight)->installEventFilter(this); axisWidget(QwtAxisId(QwtAxis::yLeft, 1))->installEventFilter(this); axisWidget(QwtAxisId(QwtAxis::yRight, 1))->installEventFilter(this); axisWidget(QwtAxisId(QwtAxis::yRight, 2))->installEventFilter(this); + axisWidget(QwtAxisId(QwtAxis::yRight, 3))->installEventFilter(this); configChanged(); // set colors } @@ -660,6 +672,7 @@ AllPlot::configChanged() if (appsettings->value(this, GC_ANTIALIAS, false).toBool() == true) { standard->wattsCurve->setRenderHint(QwtPlotItem::RenderAntialiased); + standard->atissCurve->setRenderHint(QwtPlotItem::RenderAntialiased); standard->npCurve->setRenderHint(QwtPlotItem::RenderAntialiased); standard->xpCurve->setRenderHint(QwtPlotItem::RenderAntialiased); standard->apCurve->setRenderHint(QwtPlotItem::RenderAntialiased); @@ -691,6 +704,7 @@ AllPlot::configChanged() QPen npPen = QPen(GColor(CNPOWER)); npPen.setWidth(width); standard->npCurve->setPen(npPen); + standard->atissCurve->setPen(npPen); QPen xpPen = QPen(GColor(CXPOWER)); xpPen.setWidth(width); standard->xpCurve->setPen(xpPen); @@ -775,6 +789,7 @@ AllPlot::configChanged() p = standard->npCurve->pen().color(); p.setAlpha(64); standard->npCurve->setBrush(QBrush(p)); + standard->atissCurve->setBrush(QBrush(p)); p = standard->xpCurve->pen().color(); p.setAlpha(64); @@ -837,6 +852,7 @@ AllPlot::configChanged() standard->balanceRCurve->setBrush(QBrush(p));*/ } else { standard->wattsCurve->setBrush(Qt::NoBrush); + standard->atissCurve->setBrush(Qt::NoBrush); standard->npCurve->setBrush(Qt::NoBrush); standard->xpCurve->setBrush(Qt::NoBrush); standard->apCurve->setBrush(Qt::NoBrush); @@ -912,6 +928,15 @@ AllPlot::configChanged() pal.setColor(QPalette::WindowText, GColor(CWBAL)); pal.setColor(QPalette::Text, GColor(CWBAL)); axisWidget(QwtAxisId(QwtAxis::yRight, 2))->setPalette(pal); + + sd = new QwtScaleDraw; + sd->enableComponent(QwtScaleDraw::Ticks, false); + sd->enableComponent(QwtScaleDraw::Backbone, false); + sd->setTickLength(QwtScaleDiv::MajorTick, 3); + setAxisScaleDraw(QwtAxisId(QwtAxis::yRight, 3), sd); + pal.setColor(QPalette::WindowText, GColor(CNPOWER)); + pal.setColor(QPalette::Text, GColor(CNPOWER)); + axisWidget(QwtAxisId(QwtAxis::yRight, 3))->setPalette(pal); } void @@ -927,9 +952,9 @@ AllPlot::setHighlightIntervals(bool state) } struct DataPoint { - double time, hr, watts, np, ap, xp, speed, cad, alt, temp, wind, torque, lrbalance, kphd, wattsd, cadd, nmd, hrd; - DataPoint(double t, double h, double w, double n, double l, double x, double s, double c, double a, double te, double wi, double tq, double lrb, double kphd, double wattsd, double cadd, double nmd, double hrd) : - time(t), hr(h), watts(w), np(n), ap(l), xp(x), speed(s), cad(c), alt(a), temp(te), wind(wi), torque(tq), lrbalance(lrb), kphd(kphd), wattsd(wattsd), cadd(cadd), nmd(nmd), hrd(hrd) {} + double time, hr, watts, atiss, np, ap, xp, speed, cad, alt, temp, wind, torque, lrbalance, kphd, wattsd, cadd, nmd, hrd; + DataPoint(double t, double h, double w, double at, double n, double l, double x, double s, double c, double a, double te, double wi, double tq, double lrb, double kphd, double wattsd, double cadd, double nmd, double hrd) : + time(t), hr(h), watts(w), atiss(at), np(n), ap(l), xp(x), speed(s), cad(c), alt(a), temp(te), wind(wi), torque(tq), lrbalance(lrb), kphd(kphd), wattsd(wattsd), cadd(cadd), nmd(nmd), hrd(hrd) {} }; bool AllPlot::shadeZones() const @@ -993,6 +1018,8 @@ AllPlot::recalc(AllPlotObject *objects) objects->wCurve->setSamples(data,data); objects->mCurve->setSamples(data,data); + if (!objects->atissArray.empty()) + objects->atissCurve->setSamples(data, data); if (!objects->npArray.empty()) objects->npCurve->setSamples(data, data); if (!objects->xpArray.empty()) @@ -1036,6 +1063,7 @@ AllPlot::recalc(AllPlotObject *objects) double totalWatts = 0.0; double totalNP = 0.0; + double totalATISS = 0.0; double totalXP = 0.0; double totalAP = 0.0; double totalHr = 0.0; @@ -1057,6 +1085,7 @@ AllPlot::recalc(AllPlotObject *objects) objects->smoothWatts.resize(rideTimeSecs + 1); //(rideTimeSecs + 1); objects->smoothNP.resize(rideTimeSecs + 1); //(rideTimeSecs + 1); + objects->smoothAT.resize(rideTimeSecs + 1); //(rideTimeSecs + 1); objects->smoothXP.resize(rideTimeSecs + 1); //(rideTimeSecs + 1); objects->smoothAP.resize(rideTimeSecs + 1); //(rideTimeSecs + 1); objects->smoothHr.resize(rideTimeSecs + 1); @@ -1081,6 +1110,7 @@ AllPlot::recalc(AllPlotObject *objects) && (secs < rideTimeSecs)); ++secs) { objects->smoothWatts[secs] = 0.0; objects->smoothNP[secs] = 0.0; + objects->smoothAT[secs] = 0.0; objects->smoothXP[secs] = 0.0; objects->smoothAP[secs] = 0.0; objects->smoothHr[secs] = 0.0; @@ -1108,6 +1138,7 @@ AllPlot::recalc(AllPlotObject *objects) DataPoint dp(objects->timeArray[i], (!objects->hrArray.empty() ? objects->hrArray[i] : 0), (!objects->wattsArray.empty() ? objects->wattsArray[i] : 0), + (!objects->atissArray.empty() ? objects->atissArray[i] : 0), (!objects->npArray.empty() ? objects->npArray[i] : 0), (!objects->apArray.empty() ? objects->apArray[i] : 0), (!objects->xpArray.empty() ? objects->xpArray[i] : 0), @@ -1125,8 +1156,10 @@ AllPlot::recalc(AllPlotObject *objects) (!objects->hrDArray.empty() ? objects->hrDArray[i] : 0)); if (!objects->wattsArray.empty()) totalWatts += objects->wattsArray[i]; - if (!objects->npArray.empty()) - totalNP += objects->npArray[i]; + + if (!objects->npArray.empty()) totalNP += objects->npArray[i]; + if (!objects->atissArray.empty()) totalATISS += objects->atissArray[i]; + if (!objects->xpArray.empty()) totalXP += objects->xpArray[i]; if (!objects->apArray.empty()) @@ -1170,6 +1203,7 @@ AllPlot::recalc(AllPlotObject *objects) DataPoint &dp = list.front(); totalWatts -= dp.watts; totalNP -= dp.np; + totalATISS -= dp.atiss; totalAP -= dp.ap; totalXP -= dp.xp; totalHr -= dp.hr; @@ -1192,6 +1226,7 @@ AllPlot::recalc(AllPlotObject *objects) if (list.empty()) { objects->smoothWatts[secs] = 0.0; objects->smoothNP[secs] = 0.0; + objects->smoothAT[secs] = 0.0; objects->smoothXP[secs] = 0.0; objects->smoothAP[secs] = 0.0; objects->smoothHr[secs] = 0.0; @@ -1213,6 +1248,7 @@ AllPlot::recalc(AllPlotObject *objects) else { objects->smoothWatts[secs] = totalWatts / list.size(); objects->smoothNP[secs] = totalNP / list.size(); + objects->smoothAT[secs] = totalATISS / list.size(); objects->smoothXP[secs] = totalXP / list.size(); objects->smoothAP[secs] = totalAP / list.size(); objects->smoothHr[secs] = totalHr / list.size(); @@ -1251,6 +1287,7 @@ AllPlot::recalc(AllPlotObject *objects) // no standard->smoothing .. just raw data objects->smoothWatts.resize(0); objects->smoothNP.resize(0); + objects->smoothAT.resize(0); objects->smoothXP.resize(0); objects->smoothAP.resize(0); objects->smoothHr.resize(0); @@ -1274,6 +1311,7 @@ AllPlot::recalc(AllPlotObject *objects) foreach (RideFilePoint *dp, rideItem->ride()->dataPoints()) { objects->smoothWatts.append(dp->watts); objects->smoothNP.append(dp->np); + objects->smoothAT.append(dp->atiss); objects->smoothXP.append(dp->xp); objects->smoothAP.append(dp->apower); objects->smoothHr.append(dp->hr); @@ -1329,6 +1367,10 @@ AllPlot::recalc(AllPlotObject *objects) objects->wattsCurve->setSamples(xaxis.data() + startingIndex, objects->smoothWatts.data() + startingIndex, totalPoints); } + if (!objects->atissArray.empty()) { + objects->atissCurve->setSamples(xaxis.data() + startingIndex, objects->smoothAT.data() + startingIndex, totalPoints); + } + if (!objects->npArray.empty()) { objects->npCurve->setSamples(xaxis.data() + startingIndex, objects->smoothNP.data() + startingIndex, totalPoints); } @@ -1494,7 +1536,7 @@ AllPlot::refreshReferenceLines() if (context->isCompareIntervals) return; // only on power based charts - if (scope != RideFile::none && scope != RideFile::watts && + if (scope != RideFile::none && scope != RideFile::watts && scope != RideFile::aTISS && scope != RideFile::NP && scope != RideFile::aPower && scope != RideFile::xPower) return; foreach(QwtPlotCurve *referenceLine, standard->referenceLines) { @@ -1519,7 +1561,7 @@ AllPlot::plotReferenceLine(const RideFilePoint *referencePoint) if (context->isCompareIntervals) return NULL; // only on power based charts - if (scope != RideFile::none && scope != RideFile::watts && + if (scope != RideFile::none && scope != RideFile::watts && scope != RideFile::aTISS && scope != RideFile::NP && scope != RideFile::aPower && scope != RideFile::xPower) return NULL; QwtPlotCurve *referenceLine = NULL; @@ -1578,6 +1620,13 @@ AllPlot::setYMax() { // set axis scales + if (showATISS && standard->atissCurve->isVisible() && rideItem && rideItem->ride()) { + + setAxisTitle(QwtAxisId(QwtAxis::yRight, 3), tr("Aerobic TISS")); + setAxisScale(QwtAxisId(QwtAxis::yRight, 3),0,standard->atissCurve->maxYValue() * 1.05); + setAxisLabelAlignment(QwtAxisId(QwtAxis::yRight, 3),Qt::AlignVCenter); + } + if (showW && standard->wCurve->isVisible() && rideItem && rideItem->ride()) { setAxisTitle(QwtAxisId(QwtAxis::yRight, 2), tr("W' Balance (j)")); @@ -1743,11 +1792,13 @@ AllPlot::setYMax() if (wantaxis) { - setAxisVisible(yLeft, standard->wattsCurve->isVisible() || standard->npCurve->isVisible() || standard->xpCurve->isVisible() || standard->apCurve->isVisible()); + setAxisVisible(yLeft, standard->wattsCurve->isVisible() || standard->atissCurve->isVisible() || + standard->npCurve->isVisible() || standard->xpCurve->isVisible() || standard->apCurve->isVisible()); setAxisVisible(QwtAxisId(QwtAxis::yLeft, 1), standard->hrCurve->isVisible() || standard->cadCurve->isVisible()); setAxisVisible(yRight, standard->speedCurve->isVisible()); setAxisVisible(QwtAxisId(QwtAxis::yRight, 1), standard->altCurve->isVisible()); setAxisVisible(QwtAxisId(QwtAxis::yRight, 2), standard->wCurve->isVisible()); + setAxisVisible(QwtAxisId(QwtAxis::yRight, 3), standard->atissCurve->isVisible()); setAxisVisible(QwtAxisId(QwtAxis::yLeft, 2), false); setAxisVisible(xBottom, true); @@ -1812,6 +1863,7 @@ AllPlot::setDataFromPlot(AllPlot *plot, int startidx, int stopidx) double *smoothW = &plot->standard->smoothWatts[startidx]; double *smoothN = &plot->standard->smoothNP[startidx]; + double *smoothAT = &plot->standard->smoothAT[startidx]; double *smoothX = &plot->standard->smoothXP[startidx]; double *smoothL = &plot->standard->smoothAP[startidx]; double *smoothT = &plot->standard->smoothTime[startidx]; @@ -1876,6 +1928,7 @@ AllPlot::setDataFromPlot(AllPlot *plot, int startidx, int stopidx) standard->wCurve->detach(); standard->mCurve->detach(); standard->wattsCurve->detach(); + standard->atissCurve->detach(); standard->npCurve->detach(); standard->xpCurve->detach(); standard->apCurve->detach(); @@ -1895,6 +1948,7 @@ AllPlot::setDataFromPlot(AllPlot *plot, int startidx, int stopidx) standard->balanceRCurve->detach(); standard->wattsCurve->setVisible(rideItem->ride()->areDataPresent()->watts && showPowerState < 2); + standard->atissCurve->setVisible(rideItem->ride()->areDataPresent()->watts && showATISS); standard->npCurve->setVisible(rideItem->ride()->areDataPresent()->np && showNP); standard->xpCurve->setVisible(rideItem->ride()->areDataPresent()->xp && showXP); standard->apCurve->setVisible(rideItem->ride()->areDataPresent()->apower && showAP); @@ -1920,6 +1974,7 @@ AllPlot::setDataFromPlot(AllPlot *plot, int startidx, int stopidx) standard->mCurve->setSamples(rideItem->ride()->wprimeData()->mxdata().data(),rideItem->ride()->wprimeData()->mydata().data(),rideItem->ride()->wprimeData()->mxdata().count()); } standard->wattsCurve->setSamples(xaxis,smoothW,stopidx-startidx); + standard->atissCurve->setSamples(xaxis,smoothAT,stopidx-startidx); standard->npCurve->setSamples(xaxis,smoothN,stopidx-startidx); standard->xpCurve->setSamples(xaxis,smoothX,stopidx-startidx); standard->apCurve->setSamples(xaxis,smoothL,stopidx-startidx); @@ -1972,6 +2027,17 @@ AllPlot::setDataFromPlot(AllPlot *plot, int startidx, int stopidx) } standard->wattsCurve->setSymbol(sym); + sym = new QwtSymbol; + sym->setPen(QPen(GColor(CPLOTMARKER))); + if (stopidx-startidx < 150) { + sym->setStyle(QwtSymbol::Ellipse); + sym->setSize(3); + } else { + sym->setStyle(QwtSymbol::NoSymbol); + sym->setSize(0); + } + standard->atissCurve->setSymbol(sym); + sym = new QwtSymbol; sym->setPen(QPen(GColor(CPLOTMARKER))); if (stopidx-startidx < 150) { @@ -2164,6 +2230,9 @@ AllPlot::setDataFromPlot(AllPlot *plot, int startidx, int stopidx) if (!plot->standard->smoothWatts.empty()) { standard->wattsCurve->attach(this); } + if (!plot->standard->smoothAT.empty()) { + standard->atissCurve->attach(this); + } if (!plot->standard->smoothNP.empty()) { standard->npCurve->attach(this); } @@ -2243,6 +2312,7 @@ AllPlot::setDataFromPlot(AllPlot *plot) standard->wCurve->detach(); standard->mCurve->detach(); standard->wattsCurve->detach(); + standard->atissCurve->detach(); standard->npCurve->detach(); standard->xpCurve->detach(); standard->apCurve->detach(); @@ -2264,6 +2334,7 @@ AllPlot::setDataFromPlot(AllPlot *plot) standard->wCurve->setVisible(false); standard->mCurve->setVisible(false); standard->wattsCurve->setVisible(false); + standard->atissCurve->setVisible(false); standard->npCurve->setVisible(false); standard->xpCurve->setVisible(false); standard->apCurve->setVisible(false); @@ -2408,6 +2479,14 @@ AllPlot::setDataFromPlot(AllPlot *plot) } break; + case RideFile::aTISS: + { + ourCurve = standard->atissCurve; + thereCurve = referencePlot->standard->atissCurve; + title = tr("Aerobic TISS"); + } + break; + case RideFile::NP: { ourCurve = standard->npCurve; @@ -2567,6 +2646,7 @@ AllPlot::setDataFromPlot(AllPlot *plot) setAxisVisible(yRight, false); setAxisVisible(QwtAxisId(QwtAxis::yRight, 1), false); setAxisVisible(QwtAxisId(QwtAxis::yRight, 2), false); + setAxisVisible(QwtAxisId(QwtAxis::yRight, 3), false); // plot standard->grid standard->grid->setVisible(referencePlot->standard->grid->isVisible()); @@ -2596,6 +2676,7 @@ AllPlot::setDataFromPlots(QList plots) standard->wCurve->detach(); standard->mCurve->detach(); standard->wattsCurve->detach(); + standard->atissCurve->detach(); standard->npCurve->detach(); standard->xpCurve->detach(); standard->apCurve->detach(); @@ -2617,6 +2698,7 @@ AllPlot::setDataFromPlots(QList plots) standard->wCurve->setVisible(false); standard->mCurve->setVisible(false); standard->wattsCurve->setVisible(false); + standard->atissCurve->setVisible(false); standard->npCurve->setVisible(false); standard->xpCurve->setVisible(false); standard->apCurve->setVisible(false); @@ -2800,6 +2882,16 @@ AllPlot::setDataFromPlots(QList plots) } break; + case RideFile::aTISS: + { + ourCurve = new QwtPlotCurve(tr("Aerobic TISS")); + ourCurve->setPaintAttribute(QwtPlotCurve::FilterPoints, true); + ourCurve2->setYAxis(QwtAxisId(QwtAxis::yRight, 3)); + thereCurve = referencePlot->standard->atissCurve; + title = tr("Aerobic TISS"); + } + break; + case RideFile::NP: { ourCurve = new QwtPlotCurve(tr("NP")); @@ -2980,6 +3072,7 @@ AllPlot::setDataFromPlots(QList plots) setAxisVisible(yRight, false); setAxisVisible(QwtAxisId(QwtAxis::yRight, 1), false); setAxisVisible(QwtAxisId(QwtAxis::yRight, 2), false); + setAxisVisible(QwtAxisId(QwtAxis::yRight, 3), false); // refresh zone background (if needed) if (shade_zones) { @@ -3024,6 +3117,7 @@ AllPlot::setDataFromObject(AllPlotObject *object, AllPlot *reference) standard->wCurve->detach(); standard->mCurve->detach(); standard->wattsCurve->detach(); + standard->atissCurve->detach(); standard->npCurve->detach(); standard->xpCurve->detach(); standard->apCurve->detach(); @@ -3047,6 +3141,7 @@ AllPlot::setDataFromObject(AllPlotObject *object, AllPlot *reference) standard->wCurve->setVisible(false); standard->mCurve->setVisible(false); standard->wattsCurve->setVisible(false); + standard->atissCurve->setVisible(false); standard->npCurve->setVisible(false); standard->xpCurve->setVisible(false); standard->apCurve->setVisible(false); @@ -3081,6 +3176,12 @@ AllPlot::setDataFromObject(AllPlotObject *object, AllPlot *reference) standard->wattsCurve->setVisible(true); } + if (!object->atissArray.empty()) { + standard->atissCurve->setSamples(xaxis.data(), object->smoothAT.data(), totalPoints); + standard->atissCurve->attach(this); + standard->atissCurve->setVisible(true); + } + if (!object->npArray.empty()) { standard->npCurve->setSamples(xaxis.data(), object->smoothNP.data(), totalPoints); standard->npCurve->attach(this); @@ -3189,6 +3290,7 @@ AllPlot::setDataFromObject(AllPlotObject *object, AllPlot *reference) standard->mCurve->setVisible(referencePlot->showPowerState < 2 && referencePlot->showW); standard->wattsCurve->setVisible(referencePlot->showPowerState < 2); standard->npCurve->setVisible(referencePlot->showNP); + standard->atissCurve->setVisible(referencePlot->showATISS); standard->xpCurve->setVisible(referencePlot->showXP); standard->apCurve->setVisible(referencePlot->showAP); standard->hrCurve->setVisible(referencePlot->showHr); @@ -3257,6 +3359,7 @@ AllPlot::setDataFromRideFile(RideFile *ride, AllPlotObject *here) const RideFileDataPresent *dataPresent = ride->areDataPresent(); int npoints = ride->dataPoints().size(); here->wattsArray.resize(dataPresent->watts ? npoints : 0); + here->atissArray.resize(dataPresent->watts ? npoints : 0); here->npArray.resize(dataPresent->np ? npoints : 0); here->xpArray.resize(dataPresent->xp ? npoints : 0); here->apArray.resize(dataPresent->apower ? npoints : 0); @@ -3280,6 +3383,7 @@ AllPlot::setDataFromRideFile(RideFile *ride, AllPlotObject *here) here->wCurve->detach(); here->mCurve->detach(); here->wattsCurve->detach(); + here->atissCurve->detach(); here->npCurve->detach(); here->xpCurve->detach(); here->apCurve->detach(); @@ -3300,6 +3404,7 @@ AllPlot::setDataFromRideFile(RideFile *ride, AllPlotObject *here) if (!here->altArray.empty()) here->altCurve->attach(this); if (!here->wattsArray.empty()) here->wattsCurve->attach(this); + if (!here->atissArray.empty()) here->atissCurve->attach(this); if (!here->npArray.empty()) here->npCurve->attach(this); if (!here->xpArray.empty()) here->xpCurve->attach(this); if (!here->apArray.empty()) here->apCurve->attach(this); @@ -3329,6 +3434,7 @@ AllPlot::setDataFromRideFile(RideFile *ride, AllPlotObject *here) here->wCurve->setVisible(dataPresent->watts && showPowerState < 2 && showW); here->mCurve->setVisible(dataPresent->watts && showPowerState < 2 && showW); here->wattsCurve->setVisible(dataPresent->watts && showPowerState < 2); + here->atissCurve->setVisible(dataPresent->watts && showATISS); here->npCurve->setVisible(dataPresent->np && showNP); here->xpCurve->setVisible(dataPresent->xp && showXP); here->apCurve->setVisible(dataPresent->apower && showAP); @@ -3367,6 +3473,7 @@ AllPlot::setDataFromRideFile(RideFile *ride, AllPlotObject *here) here->timeArray[arrayLength] = secs + msecs/1000; if (!here->wattsArray.empty()) here->wattsArray[arrayLength] = max(0, point->watts); + if (!here->atissArray.empty()) here->atissArray[arrayLength] = max(0, point->atiss); if (!here->npArray.empty()) here->npArray[arrayLength] = max(0, point->np); if (!here->xpArray.empty()) here->xpArray[arrayLength] = max(0, point->xp); if (!here->apArray.empty()) here->apArray[arrayLength] = max(0, point->apower); @@ -3424,6 +3531,7 @@ AllPlot::setDataFromRideFile(RideFile *ride, AllPlotObject *here) here->wCurve->detach(); here->mCurve->detach(); here->wattsCurve->detach(); + here->atissCurve->detach(); here->npCurve->detach(); here->xpCurve->detach(); here->apCurve->detach(); @@ -3504,6 +3612,19 @@ AllPlot::setShowNP(bool show) replot(); } +void +AllPlot::setShowATISS(bool show) +{ + showATISS = show; + standard->atissCurve->setVisible(show); + setYMax(); + + // remember the curves and colors + isolation = false; + curveColors->saveState(); + replot(); +} + void AllPlot::setShowXP(bool show) { @@ -3745,6 +3866,7 @@ AllPlot::setPaintBrush(int state) p = standard->npCurve->pen().color(); p.setAlpha(64); standard->npCurve->setBrush(QBrush(p)); + standard->atissCurve->setBrush(QBrush(p)); p = standard->xpCurve->pen().color(); p.setAlpha(64); @@ -3805,6 +3927,7 @@ AllPlot::setPaintBrush(int state) standard->wCurve->setBrush(Qt::NoBrush); standard->wattsCurve->setBrush(Qt::NoBrush); standard->npCurve->setBrush(Qt::NoBrush); + standard->atissCurve->setBrush(Qt::NoBrush); standard->xpCurve->setBrush(Qt::NoBrush); standard->apCurve->setBrush(Qt::NoBrush); standard->hrCurve->setBrush(Qt::NoBrush); @@ -4206,7 +4329,7 @@ AllPlot::eventFilter(QObject *obj, QEvent *event) // if power is going on we worry about reference lines // otherwise not so much .. - if ((showPowerState<2 && scope == RideFile::none) || scope == RideFile::watts || + if ((showPowerState<2 && scope == RideFile::none) || scope == RideFile::watts || scope == RideFile::aTISS || scope == RideFile::NP || scope == RideFile::aPower || scope == RideFile::xPower) { int axis = -1; @@ -4254,6 +4377,9 @@ AllPlot::eventFilter(QObject *obj, QEvent *event) axes << axisWidget(QwtAxisId(QwtAxis::yRight, 2)); axesId << QwtAxisId(QwtAxis::yRight, 2); + axes << axisWidget(QwtAxisId(QwtAxis::yRight, 3)); + axesId << QwtAxisId(QwtAxis::yRight, 3); + if (axes.contains(obj)) { QwtAxisId id = axesId.at(axes.indexOf(obj)); @@ -4301,7 +4427,7 @@ AllPlot::plotTmpReference(int axis, int x, int y) if (context->isCompareIntervals) return; // only on power based charts - if (scope != RideFile::none && scope != RideFile::watts && + if (scope != RideFile::none && scope != RideFile::watts && scope != RideFile::aTISS && scope != RideFile::NP && scope != RideFile::aPower && scope != RideFile::xPower) return; if (x>0) { diff --git a/src/AllPlot.h b/src/AllPlot.h index e4850c440..956d9ab0a 100644 --- a/src/AllPlot.h +++ b/src/AllPlot.h @@ -296,6 +296,7 @@ class AllPlotObject : public QObject QVector tmpReferenceLines; QwtPlotCurve *wattsCurve; + QwtPlotCurve *atissCurve; QwtPlotCurve *npCurve; QwtPlotCurve *xpCurve; QwtPlotCurve *apCurve; @@ -319,6 +320,7 @@ class AllPlotObject : public QObject // source data QVector hrArray; QVector wattsArray; + QVector atissArray; QVector npArray; QVector xpArray; QVector apArray; @@ -339,6 +341,7 @@ class AllPlotObject : public QObject // smoothed data QVector smoothWatts; + QVector smoothAT; QVector smoothNP; QVector smoothAP; QVector smoothXP; @@ -431,6 +434,7 @@ class AllPlot : public QwtPlot void setShowHrD(bool show); void setShowPower(int id); void setShowNP(bool show); + void setShowATISS(bool show); void setShowXP(bool show); void setShowAP(bool show); void setShowHr(bool show); @@ -470,6 +474,7 @@ class AllPlot : public QwtPlot // controls bool shade_zones; int showPowerState; + bool showATISS; bool showNP; bool showXP; bool showAP; diff --git a/src/AllPlotWindow.cpp b/src/AllPlotWindow.cpp index f88d65ad0..260aac998 100644 --- a/src/AllPlotWindow.cpp +++ b/src/AllPlotWindow.cpp @@ -196,6 +196,10 @@ AllPlotWindow::AllPlotWindow(Context *context) : showTorque->setCheckState(Qt::Checked); cl2->addRow(new QLabel(""), showTorque); + showATISS = new QCheckBox(tr("Aerobic TISS"), this); + showATISS->setCheckState(Qt::Unchecked); + cl2->addRow(new QLabel(""), showATISS); + showNP = new QCheckBox(tr("Normalized Power"), this); showNP->setCheckState(Qt::Unchecked); cl2->addRow(new QLabel(""), showNP); @@ -495,6 +499,7 @@ AllPlotWindow::AllPlotWindow(Context *context) : connect(showTorqueD, SIGNAL(stateChanged(int)), this, SLOT(setShowTorqueD(int))); connect(showHrD, SIGNAL(stateChanged(int)), this, SLOT(setShowHrD(int))); connect(showNP, SIGNAL(stateChanged(int)), this, SLOT(setShowNP(int))); + connect(showATISS, SIGNAL(stateChanged(int)), this, SLOT(setShowATISS(int))); connect(showXP, SIGNAL(stateChanged(int)), this, SLOT(setShowXP(int))); connect(showAP, SIGNAL(stateChanged(int)), this, SLOT(setShowAP(int))); connect(showSpeed, SIGNAL(stateChanged(int)), this, SLOT(setShowSpeed(int))); @@ -738,6 +743,7 @@ AllPlotWindow::compareChanged() if (showTemp->isChecked()) wanted << RideFile::temp; if (showWind->isChecked()) wanted << RideFile::headwind; if (showNP->isChecked()) wanted << RideFile::NP; + if (showATISS->isChecked()) wanted << RideFile::aTISS; if (showXP->isChecked()) wanted << RideFile::xPower; if (showAP->isChecked()) wanted << RideFile::aPower; if (showW->isChecked()) wanted << RideFile::wprime; @@ -1794,6 +1800,29 @@ AllPlotWindow::setShowNP(int value) forceSetupSeriesStackPlots(); // scope changed so force redraw } +void +AllPlotWindow::setShowATISS(int value) +{ + showATISS->setChecked(value); + + // compare mode selfcontained update + if (isCompare()) { + compareChanged(); + return; + } + + bool checked = ( ( value == Qt::Checked ) && showATISS->isEnabled()) ? true : false; + + // recalc only does it if it needs to + if (value && current && current->ride()) current->ride()->recalculateDerivedSeries(); + + allPlot->setShowATISS(checked); + foreach (AllPlot *plot, allPlots) + plot->setShowATISS(checked); + // and the series stacks too + forceSetupSeriesStackPlots(); // scope changed so force redraw +} + void AllPlotWindow::setShowXP(int value) { @@ -2488,6 +2517,7 @@ AllPlotWindow::setupSeriesStackPlots() if (showTemp->isChecked() && rideItem->ride()->areDataPresent()->temp) serieslist << RideFile::temp; if (showWind->isChecked() && rideItem->ride()->areDataPresent()->headwind) addHeadwind=true; //serieslist << RideFile::headwind; if (showNP->isChecked() && rideItem->ride()->areDataPresent()->watts) serieslist << RideFile::NP; + if (showATISS->isChecked() && rideItem->ride()->areDataPresent()->watts) serieslist << RideFile::aTISS; if (showXP->isChecked() && rideItem->ride()->areDataPresent()->watts) serieslist << RideFile::xPower; if (showAP->isChecked() && rideItem->ride()->areDataPresent()->watts) serieslist << RideFile::aPower; if (showW->isChecked() && rideItem->ride()->areDataPresent()->watts) serieslist << RideFile::wprime; @@ -2666,6 +2696,7 @@ AllPlotWindow::setupStackPlots() _allPlot->setShowTemp((showTemp->isEnabled()) ? ( showTemp->checkState() == Qt::Checked ) : false ); _allPlot->setShowTorque((showTorque->isEnabled()) ? ( showTorque->checkState() == Qt::Checked ) : false ); _allPlot->setShowW((showW->isEnabled()) ? ( showW->checkState() == Qt::Checked ) : false ); + _allPlot->setShowATISS((showATISS->isEnabled()) ? ( showATISS->checkState() == Qt::Checked ) : false ); _allPlot->setShowGrid(showGrid->checkState() == Qt::Checked); _allPlot->setPaintBrush(paintBrush->checkState()); _allPlot->setSmoothing(smoothSlider->value()); diff --git a/src/AllPlotWindow.h b/src/AllPlotWindow.h index bf349d3d3..55b786da9 100644 --- a/src/AllPlotWindow.h +++ b/src/AllPlotWindow.h @@ -58,6 +58,7 @@ class AllPlotWindow : public GcChartWindow Q_PROPERTY(int stackWidth READ _stackWidth WRITE setStackWidth USER true) Q_PROPERTY(int showGrid READ isShowGrid WRITE setShowGrid USER true) Q_PROPERTY(int showFull READ isShowFull WRITE setShowFull USER true) + Q_PROPERTY(int showATISS READ isShowATISS WRITE setShowATISS USER true) Q_PROPERTY(int showNP READ isShowNP WRITE setShowNP USER true) Q_PROPERTY(int showXP READ isShowXP WRITE setShowXP USER true) Q_PROPERTY(int showAP READ isShowAP WRITE setShowAP USER true) @@ -99,6 +100,7 @@ class AllPlotWindow : public GcChartWindow int _stackWidth() const { return stackWidth; } int isShowGrid() const { return showGrid->checkState(); } int isShowFull() const { return showFull->checkState(); } + int isShowATISS() const { return showATISS->checkState(); } int isShowNP() const { return showNP->checkState(); } int isShowXP() const { return showXP->checkState(); } int isShowAP() const { return showAP->checkState(); } @@ -137,6 +139,7 @@ class AllPlotWindow : public GcChartWindow void setrSmoothingFromLineEdit(); void setStackWidth(int x); void setShowNP(int state); + void setShowATISS(int state); void setShowXP(int state); void setShowAP(int state); void setShowSpeed(int state); @@ -234,6 +237,7 @@ class AllPlotWindow : public GcChartWindow QCheckBox *showFull; QCheckBox *paintBrush; QCheckBox *showAlt; + QCheckBox *showATISS; QCheckBox *showNP; QCheckBox *showXP; QCheckBox *showAP; diff --git a/src/BikeScore.cpp b/src/BikeScore.cpp index f0429f282..32ff4dda9 100644 --- a/src/BikeScore.cpp +++ b/src/BikeScore.cpp @@ -223,6 +223,52 @@ class CriticalPower : public RideMetric { RideMetric *clone() const { return new CriticalPower(*this); } }; +class aTISS : public RideMetric { + Q_DECLARE_TR_FUNCTIONS(aTISS) + + public: + + aTISS() + { + setSymbol("atiss_score"); + setInternalName("Aerobic TISS"); + } + void initialize() { + setName(tr("Aerobic TISS")); + setMetricUnits(""); + setImperialUnits(""); + } + + void compute(const RideFile *r, const Zones *zones, int zoneRange, + const HrZones *, int, + const QHash &, + const Context *) { + + if (!zones || zoneRange < 0) + return; + + // aTISS - Aerobic Training Impact Scoring System + static const double a = 0.663788683661645f; + static const double b = -7.5095428451195; + static const double c = -0.86118031563782; + double aTISS = 0.0f; + + int cp = r->getTag("CP","0").toInt(); + if (!cp) cp = zones->getCP(zoneRange); + + if (cp && r->areDataPresent()->watts) { + foreach (RideFilePoint *p, r->dataPoints()) { + + // a * exp (b * exp (c * fraction of cp) ) + aTISS += a * exp(b * exp(c * (double(p->watts) / double(cp)))); + } + } + setValue(aTISS); + } + + RideMetric *clone() const { return new aTISS(*this); } +}; + class BikeScore : public RideMetric { Q_DECLARE_TR_FUNCTIONS(BikeScore) double score; @@ -301,6 +347,7 @@ class ResponseIndex : public RideMetric { }; static bool addAllSix() { + RideMetricFactory::instance().addMetric(aTISS()); RideMetricFactory::instance().addMetric(CriticalPower()); RideMetricFactory::instance().addMetric(XPower()); QVector deps; diff --git a/src/DBAccess.cpp b/src/DBAccess.cpp index ce009018a..5f1bbdd9a 100644 --- a/src/DBAccess.cpp +++ b/src/DBAccess.cpp @@ -82,8 +82,10 @@ // 61 15 Feb 2014 Mark Liversedge Fixed W' Work (for recintsecs not 1s!). // 62 06 Mar 2014 Mark Liversedge Fixed Fatigue Index to find peak then watch for decay, primarily useful in sprint intervals // 63 06 Mar 2014 Mark Liversedge Added Pacing Index AP as %age of Max Power +// 64 17 Mar 2014 Mark Liversedge Added W' and CP work to PMC metrics +// 65 17 Mar 2014 Mark Liversedge Added Aerobic TISS prototype -int DBSchemaVersion = 63; +int DBSchemaVersion = 65; DBAccess::DBAccess(Context* context) : context(context), db(NULL) { diff --git a/src/LTMPlot.cpp b/src/LTMPlot.cpp index 13b994d59..36d8aab5f 100644 --- a/src/LTMPlot.cpp +++ b/src/LTMPlot.cpp @@ -2367,6 +2367,8 @@ LTMPlot::createPMCCurveData(Context *context, LTMSettings *settings, MetricDetai // create a custom set of summary metric data! if (metricDetail.symbol.startsWith("skiba")) { scoreType = "skiba_bike_score"; + } else if (metricDetail.symbol.startsWith("atiss")) { + scoreType = "atiss_score"; } else if (metricDetail.symbol.startsWith("coggan")) { scoreType = "coggan_tss"; } else if (metricDetail.symbol.startsWith("daniels")) { @@ -2375,6 +2377,10 @@ LTMPlot::createPMCCurveData(Context *context, LTMSettings *settings, MetricDetai scoreType = "trimp_points"; } else if (metricDetail.symbol.startsWith("work")) { scoreType = "total_work"; + } else if (metricDetail.symbol.startsWith("cp_")) { + scoreType = "skiba_cp_exp"; + } else if (metricDetail.symbol.startsWith("wprime")) { + scoreType = "skiba_wprime_exp"; } else if (metricDetail.symbol.startsWith("distance")) { scoreType = "total_distance"; } @@ -2411,6 +2417,12 @@ LTMPlot::createPMCCurveData(Context *context, LTMSettings *settings, MetricDetai add.setForSymbol("skiba_sb", sc->getSBvalues()[i]); add.setForSymbol("skiba_sr", sc->getSRvalues()[i]); add.setForSymbol("skiba_lr", sc->getLRvalues()[i]); + } else if (scoreType == "atiss_score") { + add.setForSymbol("atiss_lts", sc->getLTSvalues()[i]); + add.setForSymbol("atiss_sts", sc->getSTSvalues()[i]); + add.setForSymbol("atiss_sb", sc->getSBvalues()[i]); + add.setForSymbol("atiss_sr", sc->getSRvalues()[i]); + add.setForSymbol("atiss_lr", sc->getLRvalues()[i]); } else if (scoreType == "coggan_tss") { add.setForSymbol("coggan_ctl", sc->getLTSvalues()[i]); add.setForSymbol("coggan_atl", sc->getSTSvalues()[i]); @@ -2429,6 +2441,18 @@ LTMPlot::createPMCCurveData(Context *context, LTMSettings *settings, MetricDetai add.setForSymbol("trimp_sb", sc->getSBvalues()[i]); add.setForSymbol("trimp_sr", sc->getSRvalues()[i]); add.setForSymbol("trimp_lr", sc->getLRvalues()[i]); + } else if (scoreType == "skiba_cp_exp") { + add.setForSymbol("cp_lts", sc->getLTSvalues()[i]); + add.setForSymbol("cp_sts", sc->getSTSvalues()[i]); + add.setForSymbol("cp_sb", sc->getSBvalues()[i]); + add.setForSymbol("cp_sr", sc->getSRvalues()[i]); + add.setForSymbol("cp_lr", sc->getLRvalues()[i]); + } else if (scoreType == "skiba_wprime_exp") { + add.setForSymbol("wprime_lts", sc->getLTSvalues()[i]); + add.setForSymbol("wprime_sts", sc->getSTSvalues()[i]); + add.setForSymbol("wprime_sb", sc->getSBvalues()[i]); + add.setForSymbol("wprime_sr", sc->getSRvalues()[i]); + add.setForSymbol("wprime_lr", sc->getLRvalues()[i]); } else if (scoreType == "total_work") { add.setForSymbol("work_lts", sc->getLTSvalues()[i]); add.setForSymbol("work_sts", sc->getSTSvalues()[i]); diff --git a/src/LTMTool.cpp b/src/LTMTool.cpp index 297a6fa63..912ed49d9 100644 --- a/src/LTMTool.cpp +++ b/src/LTMTool.cpp @@ -295,6 +295,82 @@ LTMTool::LTMTool(Context *context, LTMSettings *settings) : QWidget(context->mai skibaLTR.uunits = tr("Ramp"); metrics.append(skibaLTR); + // SKIBA Aerobic TISS LTS + MetricDetail atissLTS; + atissLTS.type = METRIC_PM; + atissLTS.symbol = "atiss_lts"; + atissLTS.metric = NULL; // not a factory metric + atissLTS.penColor = QColor(Qt::blue); + atissLTS.curveStyle = QwtPlotCurve::Lines; + atissLTS.symbolStyle = QwtSymbol::NoSymbol; + atissLTS.smooth = false; + atissLTS.trend = false; + atissLTS.topN = 1; + atissLTS.uname = atissLTS.name = tr("Aerobic TISS Long Term Stress"); + atissLTS.units = "Stress"; + atissLTS.uunits = tr("Stress"); + metrics.append(atissLTS); + + MetricDetail atissSTS; + atissSTS.type = METRIC_PM; + atissSTS.symbol = "atiss_sts"; + atissSTS.metric = NULL; // not a factory metric + atissSTS.penColor = QColor(Qt::magenta); + atissSTS.curveStyle = QwtPlotCurve::Lines; + atissSTS.symbolStyle = QwtSymbol::NoSymbol; + atissSTS.smooth = false; + atissSTS.trend = false; + atissSTS.topN = 1; + atissSTS.uname = atissSTS.name = tr("Aerobic TISS Short Term Stress"); + atissSTS.units = "Stress"; + atissSTS.uunits = tr("Stress"); + metrics.append(atissSTS); + + MetricDetail atissSB; + atissSB.type = METRIC_PM; + atissSB.symbol = "atiss_sb"; + atissSB.metric = NULL; // not a factory metric + atissSB.penColor = QColor(Qt::yellow); + atissSB.curveStyle = QwtPlotCurve::Steps; + atissSB.symbolStyle = QwtSymbol::NoSymbol; + atissSB.smooth = false; + atissSB.trend = false; + atissSB.topN = 1; + atissSB.uname = atissSB.name = tr("Aerobic TISS Stress Balance"); + atissSB.units = "Stress Balance"; + atissSB.uunits = tr("Stress Balance"); + metrics.append(atissSB); + + MetricDetail atissSTR; + atissSTR.type = METRIC_PM; + atissSTR.symbol = "atiss_sr"; + atissSTR.metric = NULL; // not a factory metric + atissSTR.penColor = QColor(Qt::darkGreen); + atissSTR.curveStyle = QwtPlotCurve::Steps; + atissSTR.symbolStyle = QwtSymbol::NoSymbol; + atissSTR.smooth = false; + atissSTR.trend = false; + atissSTR.topN = 1; + atissSTR.uname = atissSTR.name = tr("Aerobic TISS STS Ramp"); + atissSTR.units = "Ramp"; + atissSTR.uunits = tr("Ramp"); + metrics.append(atissSTR); + + MetricDetail atissLTR; + atissLTR.type = METRIC_PM; + atissLTR.symbol = "atiss_lr"; + atissLTR.metric = NULL; // not a factory metric + atissLTR.penColor = QColor(Qt::darkBlue); + atissLTR.curveStyle = QwtPlotCurve::Steps; + atissLTR.symbolStyle = QwtSymbol::NoSymbol; + atissLTR.smooth = false; + atissLTR.trend = false; + atissLTR.topN = 1; + atissLTR.uname = atissLTR.name = tr("Aerobic TISS LTS Ramp"); + atissLTR.units = "Ramp"; + atissLTR.uunits = tr("Ramp"); + metrics.append(atissLTR); + // DANIELS LTS MetricDetail danielsLTS; danielsLTS.type = METRIC_PM; @@ -447,6 +523,158 @@ LTMTool::LTMTool(Context *context, LTMSettings *settings) : QWidget(context->mai workLTR.uunits = tr("Ramp"); metrics.append(workLTR); + // total wprime work + MetricDetail wPrimeWorkLTS; + wPrimeWorkLTS.type = METRIC_PM; + wPrimeWorkLTS.symbol = "wprime_lts"; + wPrimeWorkLTS.metric = NULL; // not a factory metric + wPrimeWorkLTS.penColor = QColor(Qt::blue); + wPrimeWorkLTS.curveStyle = QwtPlotCurve::Lines; + wPrimeWorkLTS.symbolStyle = QwtSymbol::NoSymbol; + wPrimeWorkLTS.smooth = false; + wPrimeWorkLTS.trend = false; + wPrimeWorkLTS.topN = 1; + wPrimeWorkLTS.uname = wPrimeWorkLTS.name = tr("W' Work (Kj) Long Term Stress"); + wPrimeWorkLTS.units = "Stress (Kj)"; + wPrimeWorkLTS.uunits = tr("Stress (Kj)"); + metrics.append(wPrimeWorkLTS); + + MetricDetail wPrimeWorkSTS; + wPrimeWorkSTS.type = METRIC_PM; + wPrimeWorkSTS.symbol = "wprime_sts"; + wPrimeWorkSTS.metric = NULL; // not a factory metric + wPrimeWorkSTS.penColor = QColor(Qt::magenta); + wPrimeWorkSTS.curveStyle = QwtPlotCurve::Lines; + wPrimeWorkSTS.symbolStyle = QwtSymbol::NoSymbol; + wPrimeWorkSTS.smooth = false; + wPrimeWorkSTS.trend = false; + wPrimeWorkSTS.topN = 1; + wPrimeWorkSTS.uname = wPrimeWorkSTS.name = tr("W' Work (Kj) Short Term Stress"); + wPrimeWorkSTS.units = "Stress (Kj)"; + wPrimeWorkSTS.uunits = tr("Stress (Kj)"); + metrics.append(wPrimeWorkSTS); + + MetricDetail wPrimeWorkSB; + wPrimeWorkSB.type = METRIC_PM; + wPrimeWorkSB.symbol = "wprime_sb"; + wPrimeWorkSB.metric = NULL; // not a factory metric + wPrimeWorkSB.penColor = QColor(Qt::yellow); + wPrimeWorkSB.curveStyle = QwtPlotCurve::Steps; + wPrimeWorkSB.symbolStyle = QwtSymbol::NoSymbol; + wPrimeWorkSB.smooth = false; + wPrimeWorkSB.trend = false; + wPrimeWorkSB.topN = 1; + wPrimeWorkSB.uname = wPrimeWorkSB.name = tr("W' Work (Kj) Stress Balance"); + wPrimeWorkSB.units = "Stress Balance"; + wPrimeWorkSB.uunits = tr("Stress Balance"); + metrics.append(wPrimeWorkSB); + + MetricDetail wPrimeWorkSTR; + wPrimeWorkSTR.type = METRIC_PM; + wPrimeWorkSTR.symbol = "wprime_sr"; + wPrimeWorkSTR.metric = NULL; // not a factory metric + wPrimeWorkSTR.penColor = QColor(Qt::darkGreen); + wPrimeWorkSTR.curveStyle = QwtPlotCurve::Steps; + wPrimeWorkSTR.symbolStyle = QwtSymbol::NoSymbol; + wPrimeWorkSTR.smooth = false; + wPrimeWorkSTR.trend = false; + wPrimeWorkSTR.topN = 1; + wPrimeWorkSTR.uname = wPrimeWorkSTR.name = tr("W' Work (Kj) STS Ramp"); + wPrimeWorkSTR.units = "Ramp"; + wPrimeWorkSTR.uunits = tr("Ramp"); + metrics.append(wPrimeWorkSTR); + + MetricDetail wPrimeWorkLTR; + wPrimeWorkLTR.type = METRIC_PM; + wPrimeWorkLTR.symbol = "wprime_lr"; + wPrimeWorkLTR.metric = NULL; // not a factory metric + wPrimeWorkLTR.penColor = QColor(Qt::darkBlue); + wPrimeWorkLTR.curveStyle = QwtPlotCurve::Steps; + wPrimeWorkLTR.symbolStyle = QwtSymbol::NoSymbol; + wPrimeWorkLTR.smooth = false; + wPrimeWorkLTR.trend = false; + wPrimeWorkLTR.topN = 1; + wPrimeWorkLTR.uname = wPrimeWorkLTR.name = tr("W' Work (Kj) LTS Ramp"); + wPrimeWorkLTR.units = "Ramp"; + wPrimeWorkLTR.uunits = tr("Ramp"); + metrics.append(wPrimeWorkLTR); + + // total below CP work + MetricDetail cpWorkLTS; + cpWorkLTS.type = METRIC_PM; + cpWorkLTS.symbol = "cp_lts"; + cpWorkLTS.metric = NULL; // not a factory metric + cpWorkLTS.penColor = QColor(Qt::blue); + cpWorkLTS.curveStyle = QwtPlotCurve::Lines; + cpWorkLTS.symbolStyle = QwtSymbol::NoSymbol; + cpWorkLTS.smooth = false; + cpWorkLTS.trend = false; + cpWorkLTS.topN = 1; + cpWorkLTS.uname = cpWorkLTS.name = tr("Below CP Work (Kj) Long Term Stress"); + cpWorkLTS.units = "Stress (Kj)"; + cpWorkLTS.uunits = tr("Stress (Kj)"); + metrics.append(cpWorkLTS); + + MetricDetail cpWorkSTS; + cpWorkSTS.type = METRIC_PM; + cpWorkSTS.symbol = "cp_sts"; + cpWorkSTS.metric = NULL; // not a factory metric + cpWorkSTS.penColor = QColor(Qt::magenta); + cpWorkSTS.curveStyle = QwtPlotCurve::Lines; + cpWorkSTS.symbolStyle = QwtSymbol::NoSymbol; + cpWorkSTS.smooth = false; + cpWorkSTS.trend = false; + cpWorkSTS.topN = 1; + cpWorkSTS.uname = cpWorkSTS.name = tr("Below CP Work (Kj) Short Term Stress"); + cpWorkSTS.units = "Stress (Kj)"; + cpWorkSTS.uunits = tr("Stress (Kj)"); + metrics.append(cpWorkSTS); + + MetricDetail cpWorkSB; + cpWorkSB.type = METRIC_PM; + cpWorkSB.symbol = "cp_sb"; + cpWorkSB.metric = NULL; // not a factory metric + cpWorkSB.penColor = QColor(Qt::yellow); + cpWorkSB.curveStyle = QwtPlotCurve::Steps; + cpWorkSB.symbolStyle = QwtSymbol::NoSymbol; + cpWorkSB.smooth = false; + cpWorkSB.trend = false; + cpWorkSB.topN = 1; + cpWorkSB.uname = cpWorkSB.name = tr("Below CP Work (Kj) Stress Balance"); + cpWorkSB.units = "Stress Balance"; + cpWorkSB.uunits = tr("Stress Balance"); + metrics.append(cpWorkSB); + + MetricDetail cpWorkSTR; + cpWorkSTR.type = METRIC_PM; + cpWorkSTR.symbol = "cp_sr"; + cpWorkSTR.metric = NULL; // not a factory metric + cpWorkSTR.penColor = QColor(Qt::darkGreen); + cpWorkSTR.curveStyle = QwtPlotCurve::Steps; + cpWorkSTR.symbolStyle = QwtSymbol::NoSymbol; + cpWorkSTR.smooth = false; + cpWorkSTR.trend = false; + cpWorkSTR.topN = 1; + cpWorkSTR.uname = cpWorkSTR.name = tr("Below CP Work (Kj) STS Ramp"); + cpWorkSTR.units = "Ramp"; + cpWorkSTR.uunits = tr("Ramp"); + metrics.append(cpWorkSTR); + + MetricDetail cpWorkLTR; + cpWorkLTR.type = METRIC_PM; + cpWorkLTR.symbol = "cp_lr"; + cpWorkLTR.metric = NULL; // not a factory metric + cpWorkLTR.penColor = QColor(Qt::darkBlue); + cpWorkLTR.curveStyle = QwtPlotCurve::Steps; + cpWorkLTR.symbolStyle = QwtSymbol::NoSymbol; + cpWorkLTR.smooth = false; + cpWorkLTR.trend = false; + cpWorkLTR.topN = 1; + cpWorkLTR.uname = cpWorkLTR.name = tr("Below CP Work (Kj) LTS Ramp"); + cpWorkLTR.units = "Ramp"; + cpWorkLTR.uunits = tr("Ramp"); + metrics.append(cpWorkLTR); + // total distance MetricDetail distanceLTS; distanceLTS.type = METRIC_PM; diff --git a/src/PerformanceManagerWindow.cpp b/src/PerformanceManagerWindow.cpp index da195a4b5..67238fc3a 100644 --- a/src/PerformanceManagerWindow.cpp +++ b/src/PerformanceManagerWindow.cpp @@ -52,12 +52,14 @@ PerformanceManagerWindow::PerformanceManagerWindow(Context *context) : metricCombo = new QComboBox(this); metricCombo->addItem(tr("Use TSS"), "coggan_tss"); + metricCombo->addItem(tr("Use Aerobic TISS"), "atiss_score"); metricCombo->addItem(tr("Use BikeScore"), "skiba_bike_score"); metricCombo->addItem(tr("Use DanielsPoints"), "daniels_points"); metricCombo->addItem(tr("Use TRIMP"), "trimp_points"); metricCombo->addItem(tr("Use TRIMP 100"), "trimp_100_points"); metricCombo->addItem(tr("Use Trimp Zonal"), "trimp_zonal_points"); metricCombo->addItem(tr("Use Work (Kj)"), "total_work"); + metricCombo->addItem(tr("Use W' Work (Kj)"), "skiba_wprime_exp"); metricCombo->addItem(tr("Use Distance (km/mi)"), "total_distance"); QString metricName = appsettings->value(this, GC_PERF_MAN_METRIC, "skiba_bike_score").toString(); diff --git a/src/RideFile.cpp b/src/RideFile.cpp index e9acb3773..9cb21bc2b 100644 --- a/src/RideFile.cpp +++ b/src/RideFile.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2007 Sean C. Rhea (srhea@srhea.net) * 2009 Justin F. Knotzke (jknotzke@shampoo.ca) + * 2013 Mark Liversedge (liversedge@gmail.com) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free @@ -103,6 +104,7 @@ RideFile::seriesName(SeriesType series) case RideFile::watts: return QString(tr("Power")); case RideFile::xPower: return QString(tr("xPower")); case RideFile::aPower: return QString(tr("aPower")); + case RideFile::aTISS: return QString(tr("aTISS")); case RideFile::NP: return QString(tr("Normalized Power")); case RideFile::alt: return QString(tr("Altitude")); case RideFile::lon: return QString(tr("Longitude")); @@ -135,6 +137,7 @@ RideFile::colorFor(SeriesType series) case RideFile::wattsd: return GColor(CPOWER); case RideFile::xPower: return GColor(CXPOWER); case RideFile::aPower: return GColor(CAPOWER); + case RideFile::aTISS: return GColor(CNPOWER); case RideFile::NP: return GColor(CNPOWER); case RideFile::alt: return GColor(CALTITUDE); case RideFile::headwind: return GColor(CWINDSPEED); @@ -173,6 +176,7 @@ RideFile::unitName(SeriesType series, Context *context) case RideFile::wattsd: return QString(tr("watts/s")); case RideFile::xPower: return QString(tr("watts")); case RideFile::aPower: return QString(tr("watts")); + case RideFile::aTISS: return QString(tr("TISS")); case RideFile::NP: return QString(tr("watts")); case RideFile::alt: return QString(useMetricUnits ? tr("metres") : tr("feet")); case RideFile::lon: return QString(tr("lon")); @@ -663,6 +667,7 @@ RideFile::isDataPresent(SeriesType series) case nm : return dataPresent.nm; break; case watts : return dataPresent.watts; break; case aPower : return dataPresent.apower; break; + case aTISS : return dataPresent.atiss; break; case alt : return dataPresent.alt; break; case lon : return dataPresent.lon; break; case lat : return dataPresent.lat; break; @@ -727,6 +732,7 @@ RideFilePoint::value(RideFile::SeriesType series) const case RideFile::NP : return np; break; case RideFile::xPower : return xp; break; case RideFile::aPower : return apower; break; + case RideFile::aTISS : return atiss; break; default: case RideFile::none : break; @@ -787,6 +793,7 @@ RideFile::decimalsFor(SeriesType series) case watts : return 0; break; case xPower : return 0; break; case aPower : return 0; break; + case aTISS : return 0; break; case NP : return 0; break; case alt : return 3; break; case lon : return 6; break; @@ -818,6 +825,7 @@ RideFile::maximumFor(SeriesType series) case NP : return 2500; break; case xPower : return 2500; break; case aPower : return 2500; break; + case aTISS : return 1000; break; case alt : return 8850; break; // mt everest is highest point above sea level case lon : return 180; break; case lat : return 90; break; @@ -847,6 +855,7 @@ RideFile::minimumFor(SeriesType series) case watts : return 0; break; case xPower : return 0; break; case aPower : return 0; break; + case aTISS : return 0; break; case NP : return 0; break; case alt : return -413; break; // the Red Sea is lowest land point on earth case lon : return -180; break; @@ -1026,7 +1035,26 @@ RideFile::recalculateDerivedSeries() // APower Initialisation -- working variables double APtotal=0; double APcount=0; - + + // aTISS - Aerobic Training Impact Scoring System + static const double a = 0.663788683661645f; + static const double b = -7.5095428451195; + static const double c = -0.86118031563782; + static const double t = 2; + int CP = 0; + int WPRIME = 0; + double aTISS = 0.0f; + + // set WPrime and CP + if (context->athlete->zones()) { + int zoneRange = context->athlete->zones()->whichRange(startTime().date()); + CP = zoneRange >= 0 ? context->athlete->zones()->getCP(zoneRange) : 0; + WPRIME = zoneRange >= 0 ? context->athlete->zones()->getWprime(zoneRange) : 0; + + // did we override CP in metadata / metrics ? + int oCP = getTag("CP","0").toInt(); + if (oCP) CP=oCP; + } // last point looked at RideFilePoint *lastP = NULL; @@ -1160,6 +1188,14 @@ RideFile::recalculateDerivedSeries() APtotal += p->apower; APcount++; + // Aerobic TISS + if (CP && dataPresent.watts) { + + // a * exp (b * exp (c * fraction of cp) ) + aTISS += a * exp(b * exp(c * (double(p->watts) / double(CP)))); + p->atiss = aTISS; + } + // last point lastP = p; } diff --git a/src/RideFile.h b/src/RideFile.h index 060539bf8..65ba45169 100644 --- a/src/RideFile.h +++ b/src/RideFile.h @@ -61,14 +61,14 @@ struct RideFileDataPresent bool secs, cad, hr, km, kph, nm, watts, alt, lon, lat, headwind, slope, temp, lrbalance, interval; // derived - bool np,xp,apower,wprime; + bool np,xp,apower,wprime,atiss; // whether non-zero data of each field is present RideFileDataPresent(): secs(false), cad(false), hr(false), km(false), kph(false), nm(false), watts(false), alt(false), lon(false), lat(false), headwind(false), slope(false), temp(false), lrbalance(false), interval(false), - np(false), xp(false), apower(false), wprime(false) {} + np(false), xp(false), apower(false), wprime(false), atiss(false) {} }; struct RideFileInterval @@ -116,8 +116,7 @@ class RideFile : public QObject // QObject to emit signals virtual ~RideFile(); // Working with DATASERIES - enum seriestype { secs=0, cad, cadd, hr, hrd, km, kph, kphd, nm, nmd, watts, wattsd, alt, lon, lat, headwind, slope, temp, - interval, NP, xPower, vam, wattsKg, lrbalance, aPower, wprime, none }; + enum seriestype { secs=0, cad, cadd, hr, hrd, km, kph, kphd, nm, nmd, watts, wattsd, alt, lon, lat, headwind, slope, temp, interval, NP, xPower, vam, wattsKg, lrbalance, aPower, wprime, aTISS, none }; enum specialValues { noTemp = -255 }; typedef enum seriestype SeriesType; @@ -285,19 +284,24 @@ struct RideFilePoint // derived data (we calculate it) // xPower, normalised power, aPower - double xp, np, apower; + double xp, np, apower, atiss; // create blank point - RideFilePoint() : secs(0.0), cad(0.0), hr(0.0), km(0.0), kph(0.0), - nm(0.0), watts(0.0), alt(0.0), lon(0.0), lat(0.0), headwind(0.0), slope(0.0), temp(-255.0), lrbalance(0), hrd(0.0), cadd(0.0), kphd(0.0), nmd(0.0), wattsd(0.0), interval(0), xp(0), np(0), apower(0) {} + RideFilePoint() : secs(0.0), cad(0.0), hr(0.0), km(0.0), kph(0.0), nm(0.0), + watts(0.0), alt(0.0), lon(0.0), lat(0.0), headwind(0.0), + slope(0.0), temp(-255.0), lrbalance(0), hrd(0.0), cadd(0.0), + kphd(0.0), nmd(0.0), wattsd(0.0), interval(0), xp(0), np(0), + apower(0), atiss(0.0) {} // create point supplying all values RideFilePoint(double secs, double cad, double hr, double km, double kph, double nm, double watts, double alt, double lon, double lat, double headwind, double slope, double temp, double lrbalance, int interval) : - secs(secs), cad(cad), hr(hr), km(km), kph(kph), nm(nm), - watts(watts), alt(alt), lon(lon), lat(lat), headwind(headwind), slope(slope), temp(temp), lrbalance(lrbalance), - hrd(0.0), cadd(0.0), kphd(0.0), nmd(0.0), wattsd(0.0), interval(interval), xp(0), np(0), apower(0) {} + + secs(secs), cad(cad), hr(hr), km(km), kph(kph), nm(nm), watts(watts), alt(alt), lon(lon), + lat(lat), headwind(headwind), slope(slope), temp(temp), lrbalance(lrbalance), + hrd(0.0), cadd(0.0), kphd(0.0), nmd(0.0), wattsd(0.0), interval(interval), + xp(0), np(0), apower(0), atiss(0.0) {} // get the value via the series type rather than access direct to the values double value(RideFile::SeriesType series) const; diff --git a/src/WPrime.cpp b/src/WPrime.cpp index d5e75db41..455b350c7 100644 --- a/src/WPrime.cpp +++ b/src/WPrime.cpp @@ -735,6 +735,45 @@ class WPrimeExp : public RideMetric { RideMetric *clone() const { return new WPrimeExp(*this); } }; +class CPExp : public RideMetric { + Q_DECLARE_TR_FUNCTIONS(CPExp); + + public: + + CPExp() + { + setSymbol("skiba_cp_exp"); + setInternalName("Below CP Work"); + } + void initialize() { + setName(tr("Below CP Work")); + setType(RideMetric::Total); + setMetricUnits(tr("kJ")); + setImperialUnits(tr("kJ")); + setPrecision(0); + } + void compute(const RideFile *r, const Zones *zones, int zonerange, + const HrZones *, int, + const QHash &, + const Context *) { + + int cp = r->getTag("CP","0").toInt(); + if (!cp && zones && zonerange >=0) cp = zones->getCP(zonerange); + + double total = 0; + double secs = 0; + foreach(const RideFilePoint *point, r->dataPoints()) { + if (cp && point->watts <= cp) total += r->recIntSecs() * point->watts; + secs += r->recIntSecs(); + } + setValue(total/1000.00f); + setCount(secs); + } + + bool canAggregate() { return false; } + RideMetric *clone() const { return new CPExp(*this); } +}; + // add to catalogue static bool addMetrics() { RideMetricFactory::instance().addMetric(MinWPrime()); @@ -742,6 +781,7 @@ static bool addMetrics() { RideMetricFactory::instance().addMetric(MaxMatch()); RideMetricFactory::instance().addMetric(WPrimeTau()); RideMetricFactory::instance().addMetric(WPrimeExp()); + RideMetricFactory::instance().addMetric(CPExp()); return true; }