diff --git a/src/HistogramWindow.cpp b/src/HistogramWindow.cpp index 904153eeb..d115ef267 100644 --- a/src/HistogramWindow.cpp +++ b/src/HistogramWindow.cpp @@ -325,14 +325,14 @@ HistogramWindow::isCompare() const void HistogramWindow::compareIntervalsStateChanged(bool) { - // ... + // just redraw for now compareChanged(); } void HistogramWindow::compareIntervalsChanged() { - // ... + // just redraw for now compareChanged(); } @@ -341,11 +341,33 @@ HistogramWindow::compareChanged() { stale = true; // the 'standard' plots will need to be updated - // Now create / delete curves etc - // ... - + setUpdatesEnabled(false); + + if (isCompare()) { + + // is blank? + setIsBlank(false); // current ride irrelevant! + + // hide normal curves + powerHist->hideStandard(true); + + // set data and create empty curves + powerHist->setDataFromCompareIntervals(); + powerHist->recalcCompareIntervals(); + + } else { + + // show our normal curves and wipe rest + powerHist->hideStandard(false); + rideSelected(); // back to where we were + } + + setUpdatesEnabled(true); + // replot! powerHist->replot(); + + // repaint (in case optimised out) repaint(); } diff --git a/src/PowerHist.cpp b/src/PowerHist.cpp index 7f2d856fe..3ef988273 100644 --- a/src/PowerHist.cpp +++ b/src/PowerHist.cpp @@ -101,6 +101,9 @@ PowerHist::PowerHist(Context *context): canvasPicker = new LTMCanvasPicker(this); connect(canvasPicker, SIGNAL(pointHover(QwtPlotCurve*, int)), this, SLOT(pointHover(QwtPlotCurve*, int))); + // usually hidden, but shown for compare mode + insertLegend(new QwtLegend(), QwtPlot::BottomLegend); + setAxisMaxMinor(xBottom, 0); setAxisMaxMinor(yLeft, 0); @@ -200,6 +203,28 @@ PowerHist::configChanged() setAutoFillBackground(true); } +void +PowerHist::hideStandard(bool hide) +{ + bg->setVisible(!hide); + hrbg->setVisible(!hide); + curve->setVisible(!hide); + curveSelected->setVisible(!hide); + + if (!hide) { + + // wipe the compare data + compareData.clear(); + + // we want normal so zap any compare curves + foreach(QwtPlotCurve *x, compareCurves) { + x->detach(); + delete x; + } + compareCurves.clear(); + } +} + PowerHist::~PowerHist() { delete bg; delete hrbg; @@ -272,9 +297,237 @@ PowerHist::refreshHRZoneLabels() } } +void +PowerHist::recalcCompareIntervals() +{ + // Set curves .. they will always have been created + // in setDataFromCompareIntervals, but no samples set + + if (!isVisible() && !context->isCompareIntervals) return; + + double ncols = 0; + foreach(CompareInterval x, context->compareIntervals) { + if (x.isChecked()) ncols++; + } + int acol = 0; + for (int intervalNumber=0; intervalNumber < context->compareIntervals.count(); intervalNumber++) { + + HistData &cid = compareData[intervalNumber]; + QwtPlotCurve *curve = compareCurves[intervalNumber]; + QVector *array = NULL; + int arrayLength = 0; + + if (series == RideFile::watts && zoned == false) { + + array = &cid.wattsArray; + arrayLength = cid.wattsArray.size(); + + } else if ((series == RideFile::watts || series == RideFile::wattsKg) && zoned == true) { + + array = &cid.wattsZoneArray; + arrayLength = cid.wattsZoneArray.size(); + + } else if (series == RideFile::aPower && zoned == false) { + + array = &cid.aPowerArray; + arrayLength = cid.aPowerArray.size(); + + } else if (series == RideFile::wattsKg && zoned == false) { + + array = &cid.wattsKgArray; + arrayLength = cid.wattsKgArray.size(); + + } else if (series == RideFile::nm) { + + array = &cid.nmArray; + arrayLength = cid.nmArray.size(); + + } else if (series == RideFile::hr && zoned == false) { + + array = &cid.hrArray; + arrayLength = cid.hrArray.size(); + + } else if (series == RideFile::hr && zoned == true) { + + array = &cid.hrZoneArray; + arrayLength = cid.hrZoneArray.size(); + + } else if (series == RideFile::kph) { + + array = &cid.kphArray; + + } else if (series == RideFile::cad) { + array = &cid.cadArray; + arrayLength = cid.cadArray.size(); + } + + RideFile::SeriesType baseSeries = (series == RideFile::wattsKg) ? RideFile::watts : series; + + // null curve please -- we have no data! + if (!array || arrayLength == 0) { + // create empty curves when no data + const double zero = 0; + curve->setSamples(&zero, &zero, 0); + continue; + } + + if (zoned == false || (zoned == true && (series != RideFile::watts && series != RideFile::wattsKg + && series != RideFile::hr))) { + + // NOT ZONED + + // we add a bin on the end since the last "incomplete" bin + // will be dropped otherwise + int count = int(ceil((arrayLength - 1) / (binw)))+1; + + // allocate space for data, plus beginning and ending point + QVector parameterValue(count+2, 0.0); + QVector totalTime(count+2, 0.0); + int i; + for (i = 1; i <= count; ++i) { + double high = i * round(binw/delta); + double low = high - round(binw/delta); + if (low==0 && !withz) low++; + parameterValue[i] = high*delta; + totalTime[i] = 1e-9; // nonzero to accomodate log plot + while (low < high && lowsetSamples(parameterValue.data(), totalTime.data(), count + 2); + + QwtScaleDraw *sd = new QwtScaleDraw; + sd->setTickLength(QwtScaleDiv::MajorTick, 3); + setAxisScaleDraw(QwtPlot::xBottom, sd); + + // HR typically starts at 80 or so, rather than zero + // lets crop the chart so we can focus on the data + // if we're working with HR data... + minX=0; + if (!withz && series == RideFile::hr) { + for (int i=1; i 0.1) { + minX = i; + break; + } + } + } + setAxisScale(xBottom, minX, parameterValue[count + 1]); + + // we only do zone labels when using absolute values + refreshZoneLabels(); + refreshHRZoneLabels(); + + } else { // ZONED + + // 0.9 is used for the columns, so each column when comparing + // will have 0.9 / ncols available to it, but needs to leave + // 10% space between it and its neighbour too, that col needs + // to only be 0.9 / ncols * 0.9 wide + + double width = (0.90f / ncols) * 0.90f; + double jump = acol * (0.90f / ncols); + + // we're not binning instead we are prettyfing the columnar + // display in much the same way as the weekly summary workds + // Each zone column will have 4 points + QVector xaxis (array->size() * 4); + QVector yaxis (array->size() * 4); + + // samples to time + for (int i=0, offset=0; isize(); i++) { + + double x = (double) i - 0.5; + double y = dt * (double)(*array)[i]; + + xaxis[offset] = x +jump; + yaxis[offset] = 0; + offset++; + xaxis[offset] = x +jump; + yaxis[offset] = y; + offset++; + xaxis[offset] = x +jump +width; + yaxis[offset] = y; + offset++; + xaxis[offset] = x +jump +width; + yaxis[offset] = 0; + offset++; + } + + if (!absolutetime) { + percentify(yaxis, 2); + } + + // set those curves + curve->setPen(QPen(Qt::NoPen)); + curve->setSamples(xaxis.data(), yaxis.data(), xaxis.size()); + + // zone scale draw + if ((series == RideFile::watts || series == RideFile::wattsKg) && zoned && rideItem && rideItem->zones) { + setAxisScaleDraw(QwtPlot::xBottom, new ZoneScaleDraw(rideItem->zones, rideItem->zoneRange())); + if (rideItem->zoneRange() >= 0) + setAxisScale(QwtPlot::xBottom, -0.99, rideItem->zones->numZones(rideItem->zoneRange()), 1); + else + setAxisScale(QwtPlot::xBottom, -0.99, 0, 1); + } + + // hr scale draw + int hrRange; + if (series == RideFile::hr && zoned && rideItem && context->athlete->hrZones() && + (hrRange=context->athlete->hrZones()->whichRange(rideItem->dateTime.date())) != -1) { + setAxisScaleDraw(QwtPlot::xBottom, new HrZoneScaleDraw(context->athlete->hrZones(), hrRange)); + + if (hrRange >= 0) + setAxisScale(QwtPlot::xBottom, -0.99, context->athlete->hrZones()->numZones(hrRange), 1); + else + setAxisScale(QwtPlot::xBottom, -0.99, 0, 1); + } + + // watts zoned for a time range + if (source == Cache && zoned && (series == RideFile::watts || series == RideFile::wattsKg) && context->athlete->zones()) { + setAxisScaleDraw(QwtPlot::xBottom, new ZoneScaleDraw(context->athlete->zones(), 0)); + if (context->athlete->zones()->getRangeSize()) + setAxisScale(QwtPlot::xBottom, -0.99, context->athlete->zones()->numZones(0), 1); // use zones from first defined range + } + + // hr zoned for a time range + if (source == Cache && zoned && series == RideFile::hr && context->athlete->hrZones()) { + setAxisScaleDraw(QwtPlot::xBottom, new HrZoneScaleDraw(context->athlete->hrZones(), 0)); + if (context->athlete->hrZones()->getRangeSize()) + setAxisScale(QwtPlot::xBottom, -0.99, context->athlete->hrZones()->numZones(0), 1); // use zones from first defined range + } + + setAxisMaxMinor(QwtPlot::xBottom, 0); + + // keep track of columns visible + if (context->compareIntervals[intervalNumber].isChecked()) acol++; + } + } + + setYMax(); + updatePlot(); +} + void PowerHist::recalc(bool force) { + if (context->isCompareIntervals) { //XXX bodge for now + recalcCompareIntervals(); + return; + } + QVector *array = NULL; QVector *selectedArray = NULL; int arrayLength = 0; @@ -559,8 +812,21 @@ PowerHist::recalc(bool force) void PowerHist::setYMax() { - double MaxY = curve->maxYValue(); - if (MaxY < curveSelected->maxYValue()) MaxY = curveSelected->maxYValue(); + double MaxY=0; + + if (context->isCompareIntervals) { + + foreach (QwtPlotCurve *p, compareCurves) { + double my = p->maxYValue(); + if (my > MaxY) MaxY = my; + } + + } else { + + MaxY = curve->maxYValue(); + if (MaxY < curveSelected->maxYValue()) MaxY = curveSelected->maxYValue(); + + } static const double tmin = 1.0/60; setAxisScale(yLeft, (lny ? tmin : 0.0), MaxY * 1.1); @@ -647,7 +913,132 @@ PowerHist::setData(RideFileCache *cache) void PowerHist::setDataFromCompareIntervals() { + double width = appsettings->value(this, GC_LINEWIDTH, 2.0).toDouble(); + // set all the curves based upon whats in the compare intervals array + // first lets clear the old data + compareData.clear(); + + // and remove the old curves + foreach(QwtPlotCurve *x, compareCurves) { + x->detach(); + delete x; + } + compareCurves.clear(); + + // now lets setup a HistData for each CompareInterval + foreach(CompareInterval ci, context->compareIntervals) { + + // set the data even if not checked + HistData add; + + // Now go set all those tedious arrays from + // the ride cache + add.wattsArray.resize(0); + add.wattsZoneArray.resize(10); + add.wattsKgArray.resize(0); + add.aPowerArray.resize(0); + add.nmArray.resize(0); + add.hrArray.resize(0); + add.hrZoneArray.resize(10); + add.kphArray.resize(0); + add.cadArray.resize(0); + + longFromDouble(add.wattsArray, ci.rideFileCache()->distributionArray(RideFile::watts)); + longFromDouble(add.wattsKgArray, ci.rideFileCache()->distributionArray(RideFile::wattsKg)); + longFromDouble(add.aPowerArray, ci.rideFileCache()->distributionArray(RideFile::aPower)); + longFromDouble(add.hrArray, ci.rideFileCache()->distributionArray(RideFile::hr)); + longFromDouble(add.nmArray, ci.rideFileCache()->distributionArray(RideFile::nm)); + longFromDouble(add.cadArray, ci.rideFileCache()->distributionArray(RideFile::cad)); + longFromDouble(add.kphArray, ci.rideFileCache()->distributionArray(RideFile::kph)); + + // convert for metric imperial types + if (!context->athlete->useMetricUnits) { + double torque_factor = (context->athlete->useMetricUnits ? 1.0 : 0.73756215); + double speed_factor = (context->athlete->useMetricUnits ? 1.0 : 0.62137119); + + for(int i=0; iwattsZoneArray()[i]; + add.hrZoneArray[i] = ci.rideFileCache()->hrZoneArray()[i]; + } + + // add to the list + compareData << add; + + // now add a curve for recalc to play with + QwtPlotCurve *newCurve = new QwtPlotCurve(ci.name); + newCurve->setStyle(QwtPlotCurve::Steps); + + if (appsettings->value(this, GC_ANTIALIAS, false).toBool()==true) + newCurve->setRenderHint(QwtPlotItem::RenderAntialiased); + + // curve has no brush .. too confusing... + QPen pen; + pen.setColor(ci.color); + pen.setWidth(width); + newCurve->setPen(pen); + + QColor brush_color = ci.color; + brush_color.setAlpha(GColor(CPLOTBACKGROUND) == QColor(Qt::white) ? 120 : 200); + QColor brush_color1 = brush_color.darker(); + //QLinearGradient linearGradient(0, 0, 0, height()); + //linearGradient.setColorAt(0.0, brush_color); + //linearGradient.setColorAt(1.0, brush_color1); + //linearGradient.setSpread(QGradient::PadSpread); + newCurve->setBrush(brush_color1); // fill below the line + + // hide and show, but always attach + newCurve->setVisible(ci.isChecked()); + newCurve->attach(this); + + // we do want a legend in compare mode + newCurve->setItemAttribute(QwtPlotItem::Legend, true); + + // add to our collection + compareCurves << newCurve; + } + + // show legend in compare mode + legend()->show(); + updateLegend(); +} + +void +PowerHist::setComparePens() +{ + // no compare? don't bother + if (!context->isCompareIntervals) return; + + double width = appsettings->value(this, GC_LINEWIDTH, 2.0).toDouble(); + for (int i=0; icompareIntervals.count(); i++) { + + if (zoned == false || (zoned == true && (series != RideFile::watts && series != RideFile::wattsKg + && series != RideFile::hr))) { + + // NOT ZONED + if (compareCurves.count() > i) { + + // set pen back + QPen pen; + pen.setColor(context->compareIntervals[i].color); + pen.setWidth(width); + compareCurves[i]->setPen(pen); + } + + } else { + + if (compareCurves.count() > i) { + + // set pen back + compareCurves[i]->setPen(QPen(Qt::NoPen)); + } + } + } } void @@ -779,6 +1170,10 @@ PowerHist::setData(QList&results, QString totalMetric, QString d setAxisTitle(xBottom, QString(tr("%1 of Ride (%2)")).arg(m->name()).arg(xunits)); else setAxisTitle(xBottom, QString(tr("%1 of Ride")).arg(m->name())); + + // dont show legend in metric mode + legend()->hide(); + updateLegend(); } void @@ -997,6 +1392,10 @@ PowerHist::setData(RideItem *_rideItem, bool force) } curveSelected->show(); zoomer->setZoomBase(); + + // dont show legend in metric mode + legend()->hide(); + updateLegend(); } void @@ -1010,6 +1409,7 @@ void PowerHist::setZoned(bool value) { zoned = value; + setComparePens(); } void diff --git a/src/PowerHist.h b/src/PowerHist.h index f497a0db4..076e4af38 100644 --- a/src/PowerHist.h +++ b/src/PowerHist.h @@ -162,12 +162,17 @@ class PowerHist : public QwtPlot void pointHover(QwtPlotCurve *curve, int index); // get told to refresh - void recalc(bool force=false); + void recalc(bool force=false); // normal mode recalc + void recalcCompareIntervals(); // compare mode recalc void refreshZoneLabels(); // redraw, reset zoom base void updatePlot(); + // hide / show curves etc + void hideStandard(bool); + void setComparePens(); + protected: void refreshHRZoneLabels(); @@ -203,7 +208,7 @@ class PowerHist : public QwtPlot QwtPlotCurve *curve, *curveSelected; // curves when ARE in compare mode - QList*compareCurves; + QList compareCurves; // background shading QList zoneLabels;