/*
 * Copyright (c) 2006 Sean C. Rhea (srhea@srhea.net)
 * Copyright (c) 2009 Dan Connelly (@djconnel)
 * Copyright (c) 2014 Damien Grauser (Damien.Grauser@pev-geneve.ch)
 * Copyright (c) 2014 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
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "Athlete.h"
#include "Zones.h"
#include "Colors.h"
#include "CPPlot.h"
#include "PowerProfile.h"
#include "RideCache.h"
#include "Banister.h"

#include <QDebug>
#include <qwt_series_data.h>
#include <qwt_legend.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_plot_layout.h>
#include <qwt_plot_marker.h>
#include <qwt_symbol.h>
#include <qwt_scale_engine.h>
#include <qwt_scale_widget.h>
#include <qwt_scale_div.h>
#include <qwt_color_map.h>
#include <qwt_curve_fitter.h>
#include <algorithm> // for std::lower_bound

#include "CriticalPowerWindow.h"
#include "GcOverlayWidget.h"
#include "RideItem.h"
#include "IntervalItem.h"
#include "LogTimeScaleDraw.h"
#include "RideFile.h"
#include "Season.h"
#include "Settings.h"
#include "LTMCanvasPicker.h"
#include "TimeUtils.h"
#include "Units.h"
#include "Perspective.h"
#include "qwt_spline_curve_fitter.h"
#include "LTMTrend.h"

CPPlot::CPPlot(CriticalPowerWindow *parent, Context *context, bool rangemode) : QwtPlot(parent), parent(parent),

    // model
    model(0), modelVariant(0), fit(0), fitdata(0), modelDecay(false),

    // state
    context(context), bestsCache(NULL), veloCP(0), dateCP(0), dateCV(0.0), sport(""),
    rideSeries(RideFile::watts), criticalSeries(CriticalPowerWindow::CriticalSeriesType::watts),
    isFiltered(false), shadeMode(2),
    shadeIntervals(true), rangemode(rangemode), 
    showTest(true), showBest(true), filterBest(false), showPowerIndex(false), showPercent(false), showHeat(false),
    showEffort(false), showPP(false), showHeatByDate(false), showDelta(false), showDeltaPercent(false),
    plotType(0),
    xAxisLinearOnSpeed(true),

    // curves and plot objects
    rideCurve(NULL), modelCurve(NULL), effortCurve(NULL), heatCurve(NULL), heatAgeCurve(NULL), workModelCurve(NULL), pdModel(NULL), ymax(0)

{
    setAutoFillBackground(true);
    setAxisTitle(QwtAxis::XBottom, tr("Interval Length"));

    // Log scale on x-axis
    ltsd = new LogTimeScaleDraw;
    ltsd->setTickLength(QwtScaleDiv::MajorTick, 3);
    setAxisScaleDraw(QwtAxis::XBottom, ltsd);
    setAxisScaleEngine(QwtAxis::XBottom, new QwtLogScaleEngine);

    // left yAxis scale prettify
    sd = new QwtScaleDraw;
    sd->setTickLength(QwtScaleDiv::MajorTick, 3);
    sd->enableComponent(QwtScaleDraw::Ticks, false);
    sd->enableComponent(QwtScaleDraw::Backbone, false);
    setAxisScaleDraw(QwtAxis::YLeft, sd);
    setAxisTitle(QwtAxis::YLeft, tr("Average Power (watts)"));
    setAxisMaxMinor(QwtAxis::YLeft, 0);
    plotLayout()->setAlignCanvasToScales(true);

    // right yAxis scale prettify
    sd = new QwtScaleDraw;
    sd->setTickLength(QwtScaleDiv::MajorTick, 3);
    sd->enableComponent(QwtScaleDraw::Ticks, false);
    sd->enableComponent(QwtScaleDraw::Backbone, false);
    setAxisScaleDraw(QwtAxis::YRight, sd);
    setAxisTitle(QwtAxis::YRight, tr("Percent of Best"));
    setAxisMaxMinor(QwtAxis::YRight, 0);

    // zoom
    zoomer = new penTooltip(static_cast<QwtPlotCanvas*>(this->canvas()));
    zoomer->setMousePattern(QwtEventPattern::MouseSelect1,
                            Qt::LeftButton, Qt::ShiftModifier);

    // hover
    canvasPicker = new LTMCanvasPicker(this);
    static_cast<QwtPlotCanvas*>(canvas())->setFrameStyle(QFrame::NoFrame);
    connect(canvasPicker, SIGNAL(pointHover(QwtPlotCurve*, int)), this, SLOT(pointHover(QwtPlotCurve*, int)));

    // now color everything we created
    configChanged(CONFIG_APPEARANCE);
}

// set colours mostly
void
CPPlot::configChanged(qint32)
{
    QPalette palette;
    if (rangemode) palette.setBrush(QPalette::Window, QBrush(GColor(CTRENDPLOTBACKGROUND)));
    else palette.setBrush(QPalette::Window, QBrush(GColor(CPLOTBACKGROUND)));
    palette.setColor(QPalette::WindowText, GColor(CPLOTMARKER));
    palette.setColor(QPalette::Text, GColor(CPLOTMARKER));
    setPalette(palette);

    axisWidget(QwtAxis::XBottom)->setPalette(palette);
    axisWidget(QwtAxis::YLeft)->setPalette(palette);
    axisWidget(QwtAxis::YRight)->setPalette(palette);

    if (rangemode) setCanvasBackground(GColor(CTRENDPLOTBACKGROUND));
    else setCanvasBackground(GColor(CPLOTBACKGROUND));
}

// get the fonts and colors right for the axis scales
void
CPPlot::setAxisTitle(int axis, QString label)
{
    // setup the default fonts
    QFont stGiles; // hoho - Chart Font St. Giles ... ok you have to be British to get this joke
    stGiles.fromString(appsettings->value(this, GC_FONT_CHARTLABELS, QFont().toString()).toString());
    stGiles.setPointSize(appsettings->value(NULL, GC_FONT_CHARTLABELS_SIZE, 8).toInt());

    QwtText title(label);
    title.setColor(GColor(CPLOTMARKER));
    title.setFont(stGiles);
    QwtPlot::setAxisFont(axis, stGiles);
    QwtPlot::setAxisTitle(axis, title);
}

// change the date range for the 'bests' curve
void
CPPlot::setDateRange(const QDate &start, const QDate &end, bool stale)
{

    // wipe out current - calculate will reinstate
    QDate istart = (start == QDate()) ? QDate(1900, 1, 1) : start;
    QDate iend = (end == QDate()) ? QDate(3000, 12, 31) : end;

    // check they actually changed, to avoid ridefilecache aggregation
    // which is an expensive function
    if (startDate != istart || endDate != iend || stale) {

        startDate = istart;
        endDate = iend;

        // we need to replot the bests and model curves
        clearCurves(); // clears all bar the ride curve
    }
}

// what series are we plotting ?
void
CPPlot::setSeries(CriticalPowerWindow::CriticalSeriesType criticalSeries)
{
    rideSeries = CriticalPowerWindow::getRideSeries(criticalSeries);
    this->criticalSeries = criticalSeries;

    // we need to set the y axis label to reflect delta
    // comparisons too, or even percent, if that is chosen.
    // first, are we in compare mode ?
    QString prefix = "";
    QString series = "";
    QString units = "";
    QString postfix = "";
    if ((rangemode && context->isCompareDateRanges) || (!rangemode && context->isCompareIntervals)) {
        if (showDelta) {
            prefix = "Delta ";
            if (showDeltaPercent) {
                postfix = "percent";
            }
        }
    }

    enum { linear, inverse, log } scale = log;

    switch (criticalSeries) {

    case CriticalPowerWindow::veloclinicplot:
        series = tr("Veloclinic Plot");
        units = tr("J");
        scale = linear;
        break;

    case CriticalPowerWindow::work:
        series = tr("Total work");
        units = tr("kJ");
        scale = linear;
        break;

    case CriticalPowerWindow::cad:
        series = tr("Cadence");
        units = tr("rpm");
        break;

    case CriticalPowerWindow::hr:
        series = tr("Heartrate");
        units = tr("bpm");
        break;

    case CriticalPowerWindow::wattsd:
        series = tr("Watts delta");
        units = tr("watts/s");
        break;

    case CriticalPowerWindow::cadd:
        series = tr("Cadence delta");
        units = tr("rpm/s");
        break;

    case CriticalPowerWindow::nmd:
        series = tr("Torque delta");
        units = tr("nm/s");
        break;

    case CriticalPowerWindow::hrd:
        series = tr("Heartrate delta");
        units = tr("bpm/s");
        break;

    case CriticalPowerWindow::kphd:
        series = tr("Acceleration");
        units = tr("m/s/s");
        break;

    case CriticalPowerWindow::kph:
        series = tr("Speed");
        units = tr("kph");
        if (xAxisLinearOnSpeed == true) {
            scale = linear;
        } else {
            scale = log;
        }
        break;

    case CriticalPowerWindow::nm:
        series = tr("Pedal Force");
        units = tr("nm");
        break;

    case CriticalPowerWindow::IsoPower:
        series = tr("Iso Power");
        units = tr("watts");
        break;

    case CriticalPowerWindow::aPower:
        series = tr("Altitude Power");
        units = tr("watts");
        break;

    case CriticalPowerWindow::xPower:
        series = tr("xPower");
        units = tr("watts");
        break;

    case CriticalPowerWindow::wattsKg:
        if (GlobalContext::context()->useMetricUnits) {
            series = tr("Watts per kilogram");
            units = tr("w/kg");
        } else {
            series = tr("Watts per lb");
            units = tr("w/lb");
        }
        break;

    case CriticalPowerWindow::aPowerKg:
        if (GlobalContext::context()->useMetricUnits) {
            series = tr("Altitude Power per kilogram");
            units = tr("w/kg");
        } else {
            series = tr("Altitude Power per lb");
            units = tr("w/lb");
        }
        break;

    case CriticalPowerWindow::vam:
        series = tr("VAM");
        units = tr("m/hour");
        break;

    default:
    case CriticalPowerWindow::watts:
        series = tr("Power");
        units = tr("watts");
        break;

    }

    // set scale to match what's needed
    if (scale != log) setAxisScaleEngine(QwtAxis::XBottom, new QwtLinearScaleEngine);
    else setAxisScaleEngine(QwtAxis::XBottom, new QwtLogScaleEngine);

    if (criticalSeries == CriticalPowerWindow::veloclinicplot) {
        sd = new QwtScaleDraw;
        sd->setTickLength(QwtScaleDiv::MajorTick, 3);
        sd->enableComponent(QwtScaleDraw::Ticks, false);
        setAxisScaleDraw(QwtAxis::XBottom, sd);
        setAxisTitle(QwtAxis::XBottom, tr("Power (W)"));
    } else {
        ltsd = new LogTimeScaleDraw;
        ltsd->setTickLength(QwtScaleDiv::MajorTick, 3);
        ltsd->enableComponent(QwtScaleDraw::Ticks, false);
        setAxisScaleDraw(QwtAxis::XBottom, ltsd);
        setAxisTitle(QwtAxis::XBottom, tr("Interval Length"));
    }

    // set axis title
    setAxisTitle(QwtAxis::YLeft, QString ("%1 %2 (%3) %4")
                 .arg(prefix)
                 .arg(series)
                 .arg(units)
                 .arg(postfix));

    // zap the old curves
    clearCurves();
}

void
CPPlot::initModel()
{

    //if (pdModel) {
        //delete pdModel; // fix mem leak
        //pdModel=NULL;
    //}

    switch (model) {

    case 0 : // no model - do nothing
        pdModel = NULL;
        break;
    case 1 : // 2 param
        pdModel = new CP2Model(context);
        break;
    case 2 : // 3 param
        pdModel = new CP3Model(context);
        static_cast<CP3Model*>(pdModel)->modelDecay = modelDecay;
        break;
    case 3 : // extended model
        pdModel = new ExtendedModel(context);
        break;
    case 4 : // multimodel
        pdModel = new MultiModel(context);
        pdModel->setVariant(modelVariant);
        break;
    case 5 : // ward smith
        pdModel = new WSModel(context);
        pdModel->setVariant(modelVariant);
        break;
    }

    if (pdModel) {

        // Model helper
        parent->overlayWidget->setTitle(0, (fit > 1 ? tr("Energy/Time ") : tr("")) + pdModel->name());

        if (fit == 0 || model >= 3) { //!!! always envelope fit the ecp, ward-smith and velo model

            // envelope fit always uses all data
            pdModel->setFit(PDModel::Envelope);
            pdModel->setIntervals(sanI1, sanI2, anI1, anI2, aeI1, aeI2, laeI1, laeI2);
            pdModel->setMinutes(true); // we're minutes here ...
            pdModel->setData(bestsCache->meanMaxArray(criticalSeries == CriticalPowerWindow::work ? RideFile::watts : rideSeries));
            pdModel->fitsummary += " [MMP]";

        } else {
            // LM fit will use filtered data or all data
            pdModel->setFit(fit == 1 ? PDModel::LeastSquares : PDModel::LinearRegression);
            pdModel->setMinutes(true); // ignored by lmfit methods
            //fprintf(stderr, "best filtered to %d points\n", filtertime.count()); fflush(stderr);
            if (fitdata && testpower.count() >= 3) {
                pdModel->setPtData(testpower, testtime);
                pdModel->fitsummary += " [Perf]";
            } else {
                // must have at least 3 points, otherwise drop back to full MMP
                if (filterBest && filtertime.count() >= 3) {
                    pdModel->setPtData(filterpower, filtertime);
                } else {
                    pdModel->setData(bestsCache->meanMaxArray(criticalSeries == CriticalPowerWindow::work ? RideFile::watts : rideSeries));
                }
                pdModel->fitsummary += " [MMP]";
            }
        }

        updateModelHelper();
        parent->setSummary(pdModel->fitsummary);

   } else {

        // Model helper
        parent->overlayWidget->setTitle(0, tr("No Model"));
   }

    #if GC_HAVE_MODEL_LABS
    if (bestsCache->meanMaxArray(rideSeries).count()>0) {

        // Get an eCP Model
        ExtendedCriticalPower *ecp = new ExtendedCriticalPower(context);

        TestModel model;
        QwtPlotCurve* curve;            Q_UNUSED(curve);
        QwtPlotIntervalCurve* icurve;   Q_UNUSED(icurve);
        CpPlotCurve *qcurve;            Q_UNUSED(qcurve);

        //
        // Version 7.3 of Model
        //
        model = ecp->deriveExtendedCP_7_3_Parameters(true, bestsCache, rideSeries, sanI1, sanI2, anI1, anI2, aeI1, aeI2, laeI1, laeI2);

        // model curve
        curve = ecp->getPlotCurveForExtendedCP_7_3(model);
        curve->attach(this);
        modelCurves.append(curve);


        /*curve = ecp->getPlotCurveForExtendedCP_7_3_balance(model, 75);
        curve->attach(this);
        modelCurves.append(curve);

        curve = ecp->getPlotCurveForExtendedCP_7_3_balance(model, 50);
        curve->attach(this);
        modelCurves.append(curve);

        curve = ecp->getPlotCurveForExtendedCP_7_3_balance(model, 25);
        curve->attach(this);
        modelCurves.append(curve);

        curve = ecp->getPlotCurveForExtendedCP_7_3_balance(model, 0);
        curve->attach(this);
        modelCurves.append(curve);*/

        //
        // Version 6.3 of Model
        //
        model = ecp->deriveExtendedCP_6_3_Parameters(true, bestsCache, rideSeries, sanI1, sanI2, anI1, anI2, aeI1, aeI2, laeI1, laeI2);

        // model curve
        curve = ecp->getPlotCurveForExtendedCP_6_3(model);
        curve->attach(this);
        modelCurves.append(curve);

        //
        // Current 5.3 Version of Model
        //
        model = ecp->deriveExtendedCP_5_3_Parameters(true, bestsCache, rideSeries, sanI1, sanI2, anI1, anI2, aeI1, aeI2, laeI1, laeI2);

        // model curve
        curve = ecp->getPlotCurveForExtendedCP_5_3(model);
        curve->attach(this);
        modelCurves.append(curve);

        // Aerobic eneryg
        //icurve = ecp->getPlotCurveForExtendedCP_5_3_CP(model, true, true);
        //icurve->attach(this);
        //modelIntCurves.append(icurve);

        // Anaerobic
        //icurve = ecp->getPlotCurveForExtendedCP_5_3_WPrime(model, true, true, 25);
        //icurve->attach(this);
        //modelIntCurves.append(icurve);

        // ATP/PCr
        //icurve = ecp->getPlotCurveForExtendedCP_5_3_WSecond(model, true, true, 25);
        //icurve->attach(this);
        //modelIntCurves.append(icurve);

        // 10s rolling average curve
        //curve = ecp->getPlotCurveFor10secRollingAverage(bestsCache, rideSeries);
        //curve->attach(this);
        //modelCurves.append(curve);

        // plot quality 
        //qcurve = ecp->getPlotCurveForQualityPoint(bestsCache, rideSeries);
        //qcurve->attach(this);
        //modelCPCurves.append(qcurve);

        // Derived
        //curve = ecp->getPlotCurveForDerived(bestsCache, rideSeries);
        //curve->attach(this);
        //modelCurves.append(curve);
    }
    #endif

}

void
CPPlot::plotLinearWorkModel()
{
    // need to get rid of old one and also be plotting work....
    if (workModelCurve || criticalSeries != CriticalPowerWindow::work) return;

    // normal model estimation, using current settings
    initModel();

    // if we want a model (!)
    if (pdModel) {

        workModelCurve = new QwtPlotCurve("Model");
        if (appsettings->value(this, GC_ANTIALIAS, true).toBool() == true)
            workModelCurve->setRenderHint(QwtPlotItem::RenderAntialiased);

        // prepare two points at 1s and 3600s
        QVector<double> x, work;

        x << 1/60.00f; work << ((pdModel->CP() + pdModel->WPrime())/1000.00f);
        x << 60.00f; work << (((pdModel->CP() * 3600.00) + pdModel->WPrime())/1000.00f);
        workModelCurve->setSamples(x.constData(), work.constData(), 2);

        // curve cosmetics
        QPen pen(GColor(CCP));
        double width = appsettings->value(this, GC_LINEWIDTH, 0.5*dpiXFactor).toDouble();
        pen.setWidth(width);
        if (showBest) pen.setStyle(Qt::DashLine);
        workModelCurve->setPen(pen);
        workModelCurve->attach(this);
    }

    return;

}

// Plot the dashed line model curve according to the parameters
// and will also plot the heat on the curve or below since it is
// related to the model
void
CPPlot::plotModel()
{

    // wipe any temporary model markers
    foreach(QwtPlotMarker *p, cherries) delete p;
    cherries.clear();

    // first lets clear any curves we shouldn't be displaying
    // no model curve if not power !
    if (model == 0 || (rideSeries != RideFile::watts && rideSeries != RideFile::wattsKg && rideSeries != RideFile::aPower && rideSeries != RideFile::aPowerKg && rideSeries != RideFile::kph)) {
        if (modelCurve) {
            modelCurve->detach();
            delete modelCurve;
            modelCurve = NULL;
        }
        return;
    }

    // no heat ?
    if ((rideSeries != RideFile::watts || showHeat == false) && heatCurve) {
        heatCurve->detach();
        delete heatCurve;
        heatCurve = NULL;
    }

    // no heat age ?
    if ((rideSeries != RideFile::watts || showHeatByDate == false) && heatAgeCurve) {
        heatAgeCurve->detach();
        delete heatAgeCurve;
        heatAgeCurve = NULL;
    }

    // we don't want a model
    if (rideSeries != RideFile::wattsKg && rideSeries != RideFile::watts && rideSeries != RideFile::aPowerKg && rideSeries != RideFile::aPower && rideSeries != RideFile::kph) return;

    // we don't have any bests yet?
    if (bestsCache == NULL) return;

    // if you want something you need to wipe the old one first
    if (!modelCurve) {

        // new model please
        initModel();

        if (pdModel) {
            // create curve
            modelCurve = new QwtPlotCurve("Model");
            if (appsettings->value(this, GC_ANTIALIAS, true).toBool() == true)
                modelCurve->setRenderHint(QwtPlotItem::RenderAntialiased);

            // set the point data
            if (criticalSeries == CriticalPowerWindow::veloclinicplot) {
                // Plot the model for the veloclinic plot
                pdModel->setMinutes(false);
                QVector<double> power(static_cast<int>(pdModel->size()));
                QVector<double> wprime(static_cast<int>(pdModel->size()));
                for (int t = 0; t < pdModel->size(); t++) {
                    power[t] = pdModel->y(t+1);
                    wprime[t] = (pdModel->y(t+1)-veloCP) * (pdModel->x(t+1)); // Joules
                }
                modelCurve->setSamples(power.data(), wprime.data(), static_cast<int>(pdModel->size())-1);
            }
            else
                modelCurve->setData(pdModel);

            // plot cherries if there are any
            foreach(QPointF cherry, pdModel->cherries()) {
                QwtSymbol *sym = new QwtSymbol;
                sym->setBrush(QBrush(GColor(CPLOTMARKER)));
                sym->setPen(QPen(GColor(CPLOTMARKER)));
                sym->setStyle(QwtSymbol::XCross);
                sym->setSize(10 *dpiXFactor);

                QwtPlotMarker *cherryp = new QwtPlotMarker();
                cherryp->setSymbol(sym);
                cherryp->setValue(cherry.x()/60.00f, cherry.y());

                // and attach
                cherryp->attach(this);
                cherries << cherryp;
            }

            // curve cosmetics
            QPen pen(GColor(CCP));
            double width = appsettings->value(this, GC_LINEWIDTH, 0.5*dpiXFactor).toDouble();
            pen.setWidth(width);
            if (showBest) pen.setStyle(Qt::DashLine);
            modelCurve->setPen(pen);
            modelCurve->attach(this);
        }
    }

    //
    // HEAT
    //
    // we want a heat curve but don't have one
    if (heatCurve == NULL && showHeat && rideSeries == RideFile::watts && bestsCache && bestsCache->heatMeanMaxArray().count()) {
        // heat curve
        heatCurve = new QwtPlotCurve("heat");

        if (appsettings->value(this, GC_ANTIALIAS, true).toBool() == true) heatCurve->setRenderHint(QwtPlotItem::RenderAntialiased);

        heatCurve->setBrush(QBrush(GColor(CCP).darker(200)));
        heatCurve->setPen(QPen(Qt::NoPen));
        heatCurve->setZ(-1);

        // generate samples
        QVector<double> heat;
        QVector<double> time;

        for (int i=1; i<bestsCache->meanMaxArray(RideFile::watts).count() && i<bestsCache->heatMeanMaxArray().count(); i++) {

            QwtIntervalSample add(i/60.00f, bestsCache->meanMaxArray(RideFile::watts)[i] - bestsCache->heatMeanMaxArray()[i],
                                  bestsCache->meanMaxArray(RideFile::watts)[i]/* + bestsCache->heatMeanMaxArray()[i]*/);
            time << double(i)/60.00f;
            heat << bestsCache->heatMeanMaxArray()[i];
        }

        heatCurve->setSamples(time, heat);
        heatCurve->setYAxis(QwtAxis::YRight);
        setAxisScale(QwtAxis::YRight, 0, 100);  // fine if only heat is shown and percentage Scale will be fixed if shown
        if (showPercent) setAxisTitle(QwtAxis::YRight, tr("Percent of Best / Heat Activities"));
        else setAxisTitle(QwtAxis::YRight, tr("Heat Activities"));
        heatCurve->attach(this);
    }

    setAxisVisible(QwtAxis::YRight, showHeat || ((showPowerIndex||showPercent) && rideCurve));

   // setAxisVisible(QwtAxis::YRight, showHeat || showPercent);

    //
    // HEAT AGE
    //
    // we need a heat by date curve but don't have one
    if (heatAgeCurve == NULL && showHeatByDate && bestsCache) {
        // HeatCurveByDate
        heatAgeCurve = new CpPlotCurve("heat by date");

        if (appsettings->value(this, GC_ANTIALIAS, true).toBool() == true)
            heatAgeCurve->setRenderHint(QwtPlotItem::RenderAntialiased);

        heatAgeCurve->setPenWidth(1);
        QwtLinearColorMap *colorMap = new QwtLinearColorMap(Qt::blue, Qt::red);
        heatAgeCurve->setColorMap(colorMap);

        // generate samples
        QVector<QwtPoint3D> heatByDateSamples;

        for (int i=1; i<bestsCache->meanMaxArray(rideSeries).count(); i++) {
            QDate date = bestsCache->meanMaxDates(rideSeries)[i];
            double heat = 1000*(bestsCache->start.daysTo(bestsCache->end)-date.daysTo(bestsCache->end))/(bestsCache->start.daysTo(bestsCache->end));

            QwtPoint3D add(i/60.00f, bestsCache->meanMaxArray(rideSeries)[i], heat);

            heatByDateSamples << add;

        }
        heatAgeCurve->setSamples(heatByDateSamples);
        heatAgeCurve->attach(this);

    }
    zoomer->setZoomBase(false);

    // make sure that PMax isn't off the top of the chart
    // since ymax is generally only calculated from bests data
    // but with some models and using performance tests only
    // Pmax is often higher than the test values (they're for
    // 3-20 mins typically so well short of pmax).
    if (!showDelta && rideSeries == RideFile::watts && pdModel && pdModel->PMax() > ymax) {
        if (pdModel->PMax() > ymax) setAxisScale(QwtAxis::YLeft, 0, (ymax=pdModel->PMax() * 1.1f));
    }

    // if we're showing the power profile, must be at least 1500
    if (ymax < 20 && showPP && rideSeries == RideFile::wattsKg) {
        setAxisScale(QwtAxis::YLeft, 0, (ymax=20));
    }
    if (ymax < 1500 && showPP && rideSeries == RideFile::watts) {
        setAxisScale(QwtAxis::YLeft, 0, (ymax=1500));
    }
}

void
CPPlot::updateModelHelper()
{
    CriticalPowerWindow *cpw = static_cast<CriticalPowerWindow*>(parent);

    // update the helper widget -- either as watts, w/kg or kph

    if (criticalSeries == CriticalPowerWindow::work || rideSeries == RideFile::watts || rideSeries == RideFile::aPower) {

        // Reset Rank
        cpw->titleRank->setText(tr("Percentile"));

        //WPrime
        cpw->wprimeTitle->setText(tr("W'"));
        if (pdModel->hasWPrime()) {
            cpw->wprimeValue->setText(QString(tr("%1 kJ")).arg(pdModel->WPrime() / 1000.0, 0, 'f', 1));
            cpw->wprimeRank->setText(PowerPercentile::rank(PowerPercentile::abs_w,pdModel->WPrime()));
        } else {
            cpw->wprimeValue->setText("");
            cpw->wprimeRank->setText("");
        }

        //CP
        cpw->cpTitle->setText(tr("CP"));
        cpw->cpValue->setText(QString(tr("%1 w")).arg(pdModel->CP(), 0, 'f', 0));
        cpw->cpRank->setText(PowerPercentile::rank(PowerPercentile::abs_cp,pdModel->CP()));

        // P-MAX and P-MAX ranking
        cpw->pmaxTitle->setText(tr("Pmax"));
        if (pdModel->hasPMax()) {
            cpw->pmaxValue->setText(QString(tr("%1 w")).arg(pdModel->PMax(), 0, 'f', 0));
            cpw->pmaxRank->setText(PowerPercentile::rank(PowerPercentile::abs_pmax, pdModel->PMax()));

        } else  {
            cpw->pmaxValue->setText(tr("n/a"));
            cpw->pmaxRank->setText(tr("n/a"));
        }

        // Endurance Index
        if (pdModel->hasWPrime() && pdModel->WPrime() && pdModel->hasCP() && pdModel->CP()) {
            cpw->eiValue->setText(QString("%1").arg(pdModel->WPrime() / pdModel->CP(), 0, 'f', 0));
        }

    } else if (rideSeries == RideFile::wattsKg || rideSeries == RideFile::aPowerKg) {

        // Reset Rank
        cpw->titleRank->setText(tr("Percentile"));

        //WPrime
        cpw->wprimeTitle->setText(tr("W'"));
        if (pdModel->hasWPrime()) {
            cpw->wprimeValue->setText(QString(tr("%1 J/kg")).arg(pdModel->WPrime(), 0, 'f', 0));
            cpw->wprimeRank->setText(PowerPercentile::rank(PowerPercentile::wpk_w,pdModel->WPrime()));
        } else {
            cpw->wprimeValue->setText(tr("n/a"));
            cpw->wprimeRank->setText(tr("n/a"));
        }

        //CP
        cpw->cpTitle->setText(tr("CP"));
        cpw->cpValue->setText(QString(tr("%1 w/kg")).arg(pdModel->CP(), 0, 'f', 2));
        cpw->cpRank->setText(PowerPercentile::rank(PowerPercentile::wpk_cp, pdModel->CP()));

        // P-MAX and P-MAX ranking
        cpw->pmaxTitle->setText(tr("Pmax"));
        if (pdModel->hasPMax()) {
            cpw->pmaxValue->setText(QString(tr("%1 w/kg")).arg(pdModel->PMax(), 0, 'f', 2));
            cpw->pmaxRank->setText(PowerPercentile::rank(PowerPercentile::wpk_pmax, pdModel->PMax()));

        } else  {
            cpw->pmaxValue->setText(tr("n/a"));
            cpw->pmaxRank->setText(tr("n/a"));
        }

        // Endurance Index
        if (pdModel->hasWPrime() && pdModel->WPrime() && pdModel->hasCP() && pdModel->CP()) {
            cpw->eiValue->setText(QString("%1").arg(pdModel->WPrime() / pdModel->CP(), 0, 'f', 0));
        }

    } else if (rideSeries == RideFile::kph) {

        const PaceZones *zones = (sport == "Run" || sport == "Swim") ? context->athlete->paceZones(sport=="Swim") : NULL;
        // Rank field is reused for pace according to sport
        bool metricPace = zones ? appsettings->value(this, zones->paceSetting(), GlobalContext::context()->useMetricUnits).toBool() : GlobalContext::context()->useMetricUnits;
        cpw->titleRank->setText(zones ? zones->paceUnits(metricPace) : (sport=="Row" ? "min/500m" : "n/a"));

        //DPrime
        cpw->wprimeTitle->setText(tr("D'"));
        if (pdModel->hasWPrime()) {
            cpw->wprimeValue->setText(kmToString(pdModel->WPrime()/1000.0));
            cpw->wprimeRank->setText(QString(tr("%1 %2"))
                            .arg(pdModel->WPrime()/((metricPace || sport=="Row") ? 1.0 : METERS_PER_YARD), 0, 'f', 0)
                            .arg((metricPace  || sport=="Row") ? tr("m") : tr("yd")));
        } else {
            cpw->wprimeValue->setText(tr("n/a"));
            cpw->wprimeRank->setText(tr("n/a"));
        }

        //CV
        cpw->cpTitle->setText(tr("CV"));
        // TODO: Should metric instead depend on the swim/run/default unit settings?
        cpw->cpValue->setText(kphToString(pdModel->CP()));
        cpw->cpRank->setText(zones ? zones->kphToPaceString(pdModel->CP(), metricPace)
                                   : (sport=="Row" ? kphToPace(pdModel->CP()*2, true, false) : "n/a"));

        // V-MAX
        cpw->pmaxTitle->setText(tr("Vmax"));
        if (pdModel->hasPMax()) {
            cpw->pmaxValue->setText(kphToString(pdModel->PMax()));
            cpw->pmaxRank->setText(zones ? zones->kphToPaceString(pdModel->PMax(), metricPace)
                                         : (sport=="Row" ? kphToPace(pdModel->PMax()*2, true, false) : "n/a"));

        } else  {
            cpw->pmaxValue->setText(tr("n/a"));
            cpw->pmaxRank->setText(tr("n/a"));
        }

        // Endurance Index
        if (pdModel->hasWPrime() && pdModel->WPrime() && pdModel->hasCP() && pdModel->CP()) {
            cpw->eiValue->setText(QString("%1").arg(3.6 * pdModel->WPrime() / pdModel->CP(), 0, 'f', 0));
        }

    }
}

// our model for combining a model for delta mode
class DeltaModel : public QwtSyntheticPointData
{
    public:

        double x(unsigned int index) const { return baseline->x(index); }
        double y(double t)  const { return us->y(t) - baseline->y(t); }

        // use the same interval and size as the baseline model
        DeltaModel(PDModel *us, PDModel *baseline) : QwtSyntheticPointData(baseline->size()), us(us), baseline(baseline) {
            setInterval(baseline->interval());
        }

    private:
        PDModel *us, *baseline;
};

// in compare mode we can plot models and compare them...
void
CPPlot::plotModel(QVector<double> vector, QColor plotColor, PDModel *baseline)
{
    // first lets clear any curves we shouldn't be displaying
    // no model curve if not power !
    if (!context->isCompareDateRanges || model == 0 || (rideSeries != RideFile::watts && rideSeries != RideFile::wattsKg && rideSeries != RideFile::kph)) {
        return;
    }

    // we don't want a model
    if (rideSeries != RideFile::wattsKg && rideSeries != RideFile::watts && rideSeries != RideFile::kph) return;

    PDModel *pdmodel = NULL; // synthetic data provider for curve

    // new model please
    switch (model) {

    case 1 : // 2 param
        pdmodel = new CP2Model(context);
        break;
    case 2 : // 3 param
        pdmodel = new CP3Model(context);
        break;
    case 3 : // extended model
        pdmodel = new ExtendedModel(context);
        break;
    case 4 : // multimodel
        pdmodel = new MultiModel(context);
        pdmodel->setVariant(modelVariant);
        break;
    case 5 : // ward smith model
        pdmodel = new WSModel(context);
        pdmodel->setVariant(modelVariant);
        break;
    }

    // set the model and load data
    if (pdmodel) {
        pdmodel->setIntervals(sanI1, sanI2, anI1, anI2, aeI1, aeI2, laeI1, laeI2);
        pdmodel->setMinutes(true); // we're minutes here ...

        if (filterBest) {

        } else pdmodel->setData(vector);
    }

    // create curve
    QwtPlotCurve *curve = new QwtPlotCurve("Model");
    if (appsettings->value(this, GC_ANTIALIAS, true).toBool() == true)
        curve->setRenderHint(QwtPlotItem::RenderAntialiased);

    if (baseline) {

        // doing a delta model
        curve->setData(new DeltaModel(pdmodel, baseline));

    } else {

        // set the point data
        curve->setData(pdmodel);
    }

    // curve cosmetics
    QPen pen(plotColor);
    double width = appsettings->value(this, GC_LINEWIDTH, 0.5*dpiXFactor).toDouble();
    pen.setWidth(width);
    if (showBest) pen.setStyle(Qt::DashLine);
    curve->setPen(pen);
    curve->attach(this);

    intervalCurves.append(curve);
    zoomer->setZoomBase(false);
}

// wipe away all the curves
void
CPPlot::clearCurves()
{
    // bests ridefilecache
    if (bestsCache) {
        delete bestsCache;
        bestsCache = NULL;
    }

    // model curve
    if (modelCurve) {
        delete modelCurve;
        modelCurve = NULL;
    }

    // ride curve
    if (rideCurve) {
        delete rideCurve;
        rideCurve = NULL;
    }

    // efforts curve
    if (effortCurve) {
        delete effortCurve;
        effortCurve = NULL;
    }

    // rainbow curve
    if (bestsCurves.count()) {
        foreach (QwtPlotCurve *curve, bestsCurves) delete curve;
        bestsCurves.clear();
    }

    // rainbow labels
    if (allZoneLabels.size()) {
        foreach (QwtPlotMarker *label, allZoneLabels) delete label;
        allZoneLabels.clear();
    }

    if (profileCurves.count()) {
        foreach (QwtPlotCurve *curve, profileCurves) delete curve;
        profileCurves.clear();
    }

    // heat curves
    if (heatCurve) {
        delete heatCurve;
        heatCurve = NULL;
    }
    if (heatAgeCurve) {
        delete heatAgeCurve;
        heatAgeCurve = NULL;
    }

    if (modelCurves.count()) {
        foreach (QwtPlotCurve *curve, modelCurves) delete curve;
        modelCurves.clear();
    }

    if (modelIntCurves.count()) {
        foreach (QwtPlotIntervalCurve *curve, modelIntCurves) delete curve;
        modelIntCurves.clear();
    }

    if (modelCPCurves.count()) {
        foreach (CpPlotCurve *curve, modelCPCurves) delete curve;
        modelCPCurves.clear();
    }

    if (workModelCurve) {
        delete workModelCurve;
        workModelCurve = NULL;
    }

    // performance test markers
    foreach(QwtPlotMarker *p, performanceTests) delete p;
    performanceTests.clear();

}

// get bests or an empty set if it is null
QVector<double>
CPPlot::getBests()
{
    if (bestsCache) return bestsCache->meanMaxArray(rideSeries);
    else return QVector<double>();
}

// get bests dates or an empty set if it is null
QVector<QDate>
CPPlot::getBestDates()
{
    if (bestsCache) return bestsCache->meanMaxDates(rideSeries);
    else return QVector<QDate>();
}

// for sorting points on x axis
static bool qpointflessthan(const QPointF &s1, const QPointF &s2)
{
            return s1.x() < s2.x();
}

// plot tests if needed
void
CPPlot::plotTests(RideItem *rideitem)
{
    // clear out test data
    testtime.clear();
    testtime.resize(0);
    testpower.clear();
    testpower.resize(0);

    // used to sort
    QVector<QPointF> points;

    // just plot tests as power duration for now, will reiterate to add others later.
    if (rideSeries == RideFile::watts || rideSeries == RideFile::wattsKg ||
        criticalSeries == CriticalPowerWindow::work || rideSeries == RideFile::kph) {

        // rides to search, this one only -or- all in the date range selected?
        QList<RideItem*> rides;

        // honoring chart settings and filters, lets set the list of
        // rides we will search for performance tests...
        FilterSet fs;
        fs.addFilter(parent->searchBox->isFiltered(), SearchFilterBox::matches(context, parent->searchBox->filter())); // chart settings
        fs.addFilter(context->isfiltered, context->filters);
        fs.addFilter(context->ishomefiltered, context->homeFilters);
        if (parent->myPerspective) fs.addFilter(parent->myPerspective->isFiltered(), parent->myPerspective->filterlist(DateRange(startDate,endDate)));
        Specification spec;
        spec.setFilterSet(fs);
        spec.setDateRange(DateRange(startDate, endDate));

        foreach(RideItem *r, context->athlete->rideCache->rides()) {
            // does it match ?
            if ((r->sport == sport) && spec.pass(r))
                rides << r;
        }

        foreach (RideItem *item, rides) {
            foreach (IntervalItem *interval, item->intervals()) {
                if (interval->istest()) {

                    double duration = (interval->stop - interval->start) + 1; // add offset used on log axis
                    double watts = rideSeries == RideFile::kph ?
                                   interval->getForSymbol("average_speed",  GlobalContext::context()->useMetricUnits) :
                                   interval->getForSymbol("average_power",  GlobalContext::context()->useMetricUnits);


                    // ignore where no power present
                    if (watts <= 0) continue;

                    // wpk need weight?
                    if (rideSeries == RideFile::wattsKg) watts /= item->weight;

                    // x and y, we need to sort on x before done
                    points << QPointF(duration, watts);

                    if (showTest) {
                        QwtSymbol *sym = new QwtSymbol;

                        // use interval color user selected
                        sym->setBrush(QBrush(interval->color));
                        sym->setPen(QPen(Qt::NoPen));
                        sym->setStyle(QwtSymbol::Diamond);

                        // make it a really big symbol if from todays ride
                        sym->setSize((12 + (rideitem == item ? 6 : 0))*dpiXFactor);

                        // plotting work, lets convert watts to joules
                        if (criticalSeries == CriticalPowerWindow::work) watts *= duration / 1000.00f;

                        QwtPlotMarker *test = new QwtPlotMarker();
                        test->setSymbol(sym);
                        test->setValue(duration/60.00f, watts);

                        QString desc = QString("%3\n%1 %4%5\n%2").arg(watts,0, 'f', rideSeries == RideFile::watts ? 0 : 2)
                                                             .arg(interval_to_str(duration))
                                                             .arg(interval->name)
                                                             .arg(criticalSeries == CriticalPowerWindow::work ? tr("kJ") : RideFile::unitName(rideSeries, context))
                                                             .arg(criticalSeries == CriticalPowerWindow::kph ? paceString(duration, watts) : "");
                        QwtText text(desc);
                        QFont font; // default
                        font.setPointSize(8);
                        text.setFont(font);
                        text.setRenderFlags((text.renderFlags()&~Qt::AlignCenter)|Qt::AlignLeft);
                        text.setColor(interval->color);
                        test->setLabel(text);
                        test->setLabelAlignment(Qt::AlignTop|Qt::AlignRight);

                        // and attach
                        test->attach(this);
                        performanceTests << test;
                    }
                }
            }
        }

        // now sort the points on x so we can feed to model fit
        std::sort(points.begin(), points.end(), qpointflessthan);
        foreach(QPointF point, points) {
            testtime << point.x() / 60.0f;
            testpower << point.y();
        }
    }
}

// plot the power profile curves
void
CPPlot::plotPowerProfile()
{
    // lots of reasons not to !
    if ((rideSeries != RideFile::watts && rideSeries != RideFile::wattsKg)  || showPP == false || profileCurves.count()) return;

    // plot the power profile curves
    struct PowerProfile *p = rideSeries == RideFile::watts ? &powerProfile : &powerProfileWPK;
    foreach (double percentile, p->percentiles) {

        // for now we don't bother with upper and lower bounds
        //if (percentile > 95 || percentile < 5) continue;

        QwtPlotCurve *curve = new QwtPlotCurve("");
        if (appsettings->value(this, GC_ANTIALIAS, true).toBool() == true) curve->setRenderHint(QwtPlotItem::RenderAntialiased);
        curve->setStyle(QwtPlotCurve::Lines);

        QColor color;
        if (percentile > 95 || percentile < 5) color = GColor(CPLOTGRID);
        else if (percentile < 51 && percentile > 49) {
            color = GColor(CPLOTGRID);
            color.setRed(150);
        } else {
            color = GColor(CPLOTGRID);
            color.setBlue(150);
        }

        QPen gridpen(color);
        gridpen.setWidthF(1.0 * dpiXFactor);
        if (percentile > 51 || percentile < 49) gridpen.setStyle(Qt::DotLine);
        curve->setCurveFitter(new QwtSplineCurveFitter());
        curve->setPen(gridpen);
        curve->setSamples(p->seconds.constData(), p->values.value(percentile).constData(), p->seconds.count());
        curve->attach(this);
        profileCurves << curve;
    }
}

// plot the bests curve and refresh the data if needed too
void
CPPlot::plotBests(RideItem *rideItem)
{
    // we already drew the bests, if you want them again
    // you need to wipe away whats there buddy
    if (bestsCurves.count()) return;

    // do we need to get the cache ?
    if (bestsCache == NULL) {
        // isFiltered and files are filters from CriticalPowerWindow's filter setting
        // we also need to take into account the perspective filter if on trends so
        // we pass isFiltered and files for filterlist to take care of them,
        // but only if rangemode (aka on trends)
        if (rangemode) {
            bestsCache = new RideFileCache(context, startDate, endDate,
                            isFiltered || (parent->myPerspective && parent->myPerspective->isFiltered()),
                            parent->myPerspective ? parent->myPerspective->filterlist(DateRange(startDate,endDate), isFiltered, files) : files,
                            rangemode, rideItem);
        } else {
            bestsCache = new RideFileCache(context, startDate, endDate, isFiltered, files, rangemode, rideItem);
        }
    }

    // how much we got ?
    int maxNonZero = 0;
    if (bestsCache->meanMaxArray(rideSeries).size()) {
        for (int i = 0; i < bestsCache->meanMaxArray(rideSeries).size(); ++i) {
            if (bestsCache->meanMaxArray(rideSeries)[i] > 0) maxNonZero = i;
        }
    }

    // no data dude
    if (maxNonZero == 0) return;

    // lets call the curve drawer
    const double *values = bestsCache->meanMaxArray(rideSeries).constData() + 1;

    // we can only do shading of the bests curve
    // when we have power or speed and the user wants it to
    // be a rainbow curve. Otherwise its gonna be plain
    int shadingCP = 0;
    double shadingRatio = 1.0;
    if ((rideSeries == RideFile::wattsKg || rideSeries == RideFile::watts || rideSeries == RideFile::aPowerKg || rideSeries == RideFile::aPower) && shadeMode) shadingCP = dateCP;
    if ((rideSeries == RideFile::wattsKg || rideSeries == RideFile::aPowerKg) && shadeMode) shadingRatio = context->athlete->getWeight(QDate::currentDate());
    double shadingCV = 0.0;
    if (rideSeries == RideFile::kph && shadeMode) shadingCV = dateCV;

    //For veloclinic plot we need to start by using a 2 parameters model
    if (criticalSeries == CriticalPowerWindow::veloclinicplot) {
        int selectedModel = model;
        if (model == 0)
            model = 1;
        initModel();
        model = selectedModel;
        shadingCP = veloCP;
    }


    // lets setup a time array to the size we want to plot the bests curve
    // and do work at the same time since its used in a few places below
    QVector<double> time(maxNonZero);
    QVector<double> work(maxNonZero);
    QVector<double> wprime(maxNonZero);
    for (int t = 0; t < maxNonZero; t++) {
        time[t] = (t+1.00f) / 60.00f;
        work[t] = values[t] * t / 1000; // kJ not Joules
        if (criticalSeries == CriticalPowerWindow::veloclinicplot) {
            wprime[t] = (values[t]<veloCP?0:(values[t]-veloCP) * time[t] * 60.0); // Joules
        }
    }

    if (showBest) {

        if (shadingCP == 0 && shadingCV == 0.0) {

            // PLAIN CURVE

            // if we're plotting work we need to adjust from
            // power to work from the bests cache, before we
            // set the curve samples.
            //

            // no zones wanted
            QwtPlotCurve *curve = new QwtPlotCurve(tr("Bests"));

            if (appsettings->value(this, GC_ANTIALIAS, true).toBool() == true)
                curve->setRenderHint(QwtPlotItem::RenderAntialiased);

            // lets make it the right colour for the date series
            QPen line;
            QColor fill;
            switch (rideSeries) {

            case RideFile::kphd:
                line.setColor(GColor(CACCELERATION).darker(200));
                fill = (GColor(CACCELERATION));
                break;

            case RideFile::kph:
                line.setColor(GColor(CSPEED).darker(200));
                fill = (GColor(CSPEED));
                break;

            case RideFile::cad:
            case RideFile::cadd:
                line.setColor(GColor(CCADENCE).darker(200));
                fill = (GColor(CCADENCE));
                break;

            case RideFile::nm:
            case RideFile::nmd:
                line.setColor(GColor(CTORQUE).darker(200));
                fill = (GColor(CTORQUE));
                break;

            case RideFile::hr:
            case RideFile::hrd:
                line.setColor(GColor(CHEARTRATE).darker(200));
                fill = (GColor(CHEARTRATE));
                break;

            case RideFile::vam:
                line.setColor(GColor(CALTITUDE).darker(200));
                fill = (GColor(CALTITUDE));
                break;

            default:
            case RideFile::watts:
                line.setColor(GColor(CCP));
                fill = (GColor(CCP));
                break;
            case RideFile::wattsd:
            case RideFile::IsoPower:
            case RideFile::xPower:
                line.setColor(GColor(CPOWER).darker(200));
                fill = (GColor(CPOWER));
                break;
            }

            // when plotting power bests AND a model we draw bests as dots
            // but only if in 'plain' mode .. not doing a rainbow curve.
            if ((criticalSeries == CriticalPowerWindow::work || rideSeries == RideFile::wattsKg || rideSeries == RideFile::watts || rideSeries == RideFile::aPowerKg || rideSeries == RideFile::aPower || rideSeries == RideFile::kph) && model) {

                QwtSymbol *sym = new QwtSymbol;
                sym->setStyle(QwtSymbol::Ellipse);
                if (criticalSeries == CriticalPowerWindow::work && !filterBest)
                    sym->setSize(2*dpiXFactor);
                else
                    sym->setSize((filterBest ? 8 : 4) *dpiXFactor);
                sym->setBrush(QBrush(fill));
                sym->setPen(QPen(fill));
                curve->setSymbol(sym);
                curve->setStyle(QwtPlotCurve::Dots);
            }

            fill.setAlpha(64);
            line.setWidth(appsettings->value(this, GC_LINEWIDTH, 0.5*dpiXFactor).toDouble());

            curve->setPen(line);
            if (criticalSeries == CriticalPowerWindow::work || rideSeries == RideFile::watts || rideSeries == RideFile::wattsKg || rideSeries == RideFile::aPower || rideSeries == RideFile::aPowerKg || rideSeries == RideFile::kph)
                curve->setBrush(Qt::NoBrush);
            else
                curve->setBrush(QBrush(fill));

            if (criticalSeries == CriticalPowerWindow::work && !filterBest)
                curve->setSamples(time, work);
            else if (criticalSeries == CriticalPowerWindow::veloclinicplot)
                curve->setSamples(bestsCache->meanMaxArray(rideSeries).data()+1, wprime.data(), maxNonZero-1);
            else {

                if (filterBest) {

                    // get data to filter
                    QVector<double> t = time;
                    QVector<double> p = bestsCache->meanMaxArray(rideSeries);
                    QVector<QDate> w = bestsCache->meanMaxDates(rideSeries);
                    p.remove(0);
                    w.remove(0);


                    // linear regression of the full data, to help determine
                    // the maximal point on the MMP curve for each day
                    // using brace to set scope and descope temporary variables
                    // as we use a fair few, but not worth making a function
                    double slope=0, intercept=0;
                    {
                        // we want 2m to 20min data (check bounds)
                        int want = p.count() > 1200 ? 1200-121 : p.count()-121;
                        QVector<double> j = bestsCache->meanMaxArray(rideSeries).mid(120, want);
                        QVector<double> ts = t.mid(120, want);

                        // convert time data to seconds (is in minutes)
                        // and power to joules (power x time)
                        for(int i=0; i<j.count(); i++) {
                            ts[i] = ts[i] * 60.0f;
                            j[i] = (j[i] * ts[i]) ;
                        }

                        // LTMTrend does a linear regression for us, lets reuse it
                        LTMTrend regress(ts.data(), j.data(), ts.count());

                        // save away the slope and intercept
                        slope = regress.slope();
                        intercept = regress.intercept();
                    }

                    // filter out efforts on same day that are the furthest
                    // away from a linear regression

                    // the best we found is stored in here
                    struct { int i; double p, t, d, pix; } keep;

                    for(int i=0; i<t.count(); i++) {

                        // reset our holding variable - it will be updated
                        // with the maximal point we want to retain for the
                        // day we are filtering for. Initial means no value
                        // has been set yet, so the first point will set it.
                        if (w[i] != QDate()) {

                            // lets filter all on today, use first one to set the best found so far
                            keep.d = (p[i] * t[i] * 60.0f) - ((slope * t[i] * 60.00f) + intercept);
                            keep.i=i;
                            keep.p=p[i];
                            keep.t=t[i];
                            keep.pix=powerIndex(keep.p, keep.t);

                            // but clear since we iterate beyond
                            if (i>0) { // always keep pmax point
                                p[i]=0;
                                t[i]=0;
                            }

                            // from here to the end of all the points, lets see if there is one further away?
                            for(int x=i+1; x<t.count(); x++) {

                                if (w[x] == w[i]) {

                                    // if its beloe the line multiply distance by -1
                                    double d = (p[x] * t[x] * 60.0f) - ((slope * t[x] * 60.00f) + intercept);
                                    double pix = powerIndex(p[x],t[x]);

                                    // use the regression for shorter durations and 3p for longer
                                    if ((keep.t < 120 && keep.d < d) || (keep.t >= 120 && keep.pix < pix)) {
                                        keep.d = d;
                                        keep.i = x;
                                        keep.p = p[x];
                                        keep.t = t[x];
                                    }

                                    if (x>0) { // always keep pmax point
                                        w[x] = QDate();
                                        p[x] = 0;
                                        t[x] = 0;
                                    }
                                }
                            }

                            // reinstate best we found
                            p[keep.i] = keep.p;
                            t[keep.i] = keep.t;
                        }
                    }
                    // set a series where t > 1
                    filtertime.clear();
                    filtertime.resize(0);
                    filterpower.clear();
                    filterpower.resize(0);
                    QVector<double> filterwork;

                    for(int i=0; i<t.count(); i++) {
                        if (t[i] >0) {
                            filtertime << t[i];
                            filterpower << p[i]; // used to fit as well as plot
                            filterwork << p[i] * (t[i]*60/1000.0f);
                        }
                    }

                    // only show filtered data
                    curve->setSamples(filtertime.data(), criticalSeries == CriticalPowerWindow::work ? filterwork.data() : filterpower.data(), filterpower.count());

                } else {

                    // unfiltered MMP data
                    curve->setSamples(time.data(), bestsCache->meanMaxArray(rideSeries).data()+1, maxNonZero-1);
                }
            }

            curve->attach(this);
            bestsCurves.append(curve);

        } else if (shadingCP > 0) {

            //
            // RAINBOW CURVE We are plotting power AND the user wants a rainbow
            //

            // set zones from shading CP
            QList <int> power_zone;
            int n_zones = context->athlete->zones(sport)->lowsFromCP(&power_zone, (int) int(shadingCP));

            // now run through each zone and create a curve
            int high = maxNonZero - 1;
            int zone = 0;
            while (zone < n_zones && high > 0) {

                // create the curve
                QwtPlotCurve *curve = new QwtPlotCurve("");
                bestsCurves.append(curve);
                curve->attach(this);

                // get range for the curve
                int low = high - 1;
                int nextZone = zone + 1;
                if (nextZone >= power_zone.size())
                    low = 0;
                else {
                    while ((low > 0) && (values[low] < power_zone[nextZone]/shadingRatio))
                        --low;
                }

                // set samples
                if (criticalSeries == CriticalPowerWindow::work) { // this is Energy mode
                    curve->setSamples(time.data() + low, work.data() + low, high - low + 1);
                } else if (criticalSeries == CriticalPowerWindow::veloclinicplot) {
                    curve->setSamples(values + low, wprime.data() + low, high - low + 1);
                } else {
                    curve->setSamples(time.data() + low, values + low, high - low + 1);
                }

                // set the pen color and line width etc
                QColor color = zoneColor(zone, n_zones);
                if (appsettings->value(this, GC_ANTIALIAS, true).toBool() == true)
                    curve->setRenderHint(QwtPlotItem::RenderAntialiased);
                QPen pen(color.darker(200));
                pen.setColor(GColor(CCP)); //XXX color ?
                double width = appsettings->value(this, GC_LINEWIDTH, 0.5*dpiXFactor).toDouble();
                pen.setWidth(width);
                curve->setPen(pen);

                // use a linear gradient
                if (shadeMode && shadingCP) { // 0 value means no shading please - and only if proper value for shadingCP
                    color.setAlpha(128);
                    QColor color1 = color.darker();
                    QLinearGradient linearGradient(0, 0, 0, height());
                    linearGradient.setColorAt(0.0, color);
                    linearGradient.setColorAt(1.0, color1);
                    linearGradient.setSpread(QGradient::PadSpread);
                    curve->setBrush(linearGradient);   // fill below the line
                }

                // now the labels
                if (shadeMode && (criticalSeries != CriticalPowerWindow::work || work[high] > 100.0)) {

                    QwtText text(context->athlete->zones(sport)->getDefaultZoneName(zone));
                    text.setFont(QFont("Helvetica", 20, QFont::Bold));
                    color.setAlpha(255);
                    text.setColor(color);
                    QwtPlotMarker *label_mark = new QwtPlotMarker();

                    // place the text in the geometric mean in time, at a decent power
                    double x, y;
                    if (criticalSeries == CriticalPowerWindow::work) {
                        x = (time[low] + time[high]) / 2;
                        y = (work[low] + work[high]) / 5;
                    } else if (criticalSeries == CriticalPowerWindow::veloclinicplot) {
                        x = (values[low] + values[high]) / 2;
                        if (wprime[high]<1000 && wprime[low]<1000)
                            y = -1000;
                        else
                            y = pdModel->WPrime()/4;
                    } else {
                        x = sqrt(time[low] * time[high]);
                        y = (values[low] + values[high]) / 5;
                    }

                    label_mark->setValue(x, y);
                    label_mark->setLabel(text);
                    label_mark->attach(this);
                    allZoneLabels.append(label_mark);
                }

                high = low;
                ++zone;
            }
        } else if (shadingCV > 0.0) {

            //
            // RAINBOW CURVE We are plotting speed AND the user wants a rainbow
            //

            // set zones from shading CV
            QList <double> pace_zone;
            int n_zones = context->athlete->paceZones(sport=="Swim")->lowsFromCV(&pace_zone, shadingCV);

            // now run through each zone and create a curve
            int high = maxNonZero - 1;
            int zone = 0;
            while (zone < n_zones && high > 0) {

                // create the curve
                QwtPlotCurve *curve = new QwtPlotCurve("");
                bestsCurves.append(curve);
                curve->attach(this);

                // get range for the curve
                int low = high - 1;
                int nextZone = zone + 1;
                if (nextZone >= pace_zone.size())
                    low = 0;
                else {
                    while ((low > 0) && (values[low] < pace_zone[nextZone]))
                        --low;
                }

                // set samples
                curve->setSamples(time.data() + low, values + low, high - low + 1);

                // set the pen color and line width etc
                QColor color = paceZoneColor(zone, n_zones);
                if (appsettings->value(this, GC_ANTIALIAS, true).toBool() == true)
                    curve->setRenderHint(QwtPlotItem::RenderAntialiased);
                QPen pen(color.darker(200));
                pen.setColor(GColor(CCP)); //XXX color ?
                double width = appsettings->value(this, GC_LINEWIDTH, 0.5*dpiXFactor).toDouble();
                pen.setWidth(width);
                curve->setPen(pen);

                // use a linear gradient
                if (shadeMode && shadingCV) { // 0 value means no shading please - and only if proper value for shadingCV
                    color.setAlpha(128);
                    QColor color1 = color.darker();
                    QLinearGradient linearGradient(0, 0, 0, height());
                    linearGradient.setColorAt(0.0, color);
                    linearGradient.setColorAt(1.0, color1);
                    linearGradient.setSpread(QGradient::PadSpread);
                    curve->setBrush(linearGradient);   // fill below the line
                }

                // now the labels
                if (shadeMode) {

                    QwtText text(context->athlete->paceZones(sport=="Swim")->getDefaultZoneName(zone));
                    text.setFont(QFont("Helvetica", 20, QFont::Bold));
                    color.setAlpha(255);
                    text.setColor(color);
                    QwtPlotMarker *label_mark = new QwtPlotMarker();

                    // place the text in the geometric mean in time, at a decent power
                    double x, y;
                    x = sqrt(time[low] * time[high]);
                    y = (values[low] + values[high]) / 5;

                    label_mark->setValue(x, y);
                    label_mark->setLabel(text);
                    label_mark->attach(this);
                    allZoneLabels.append(label_mark);
                }

                high = low;
                ++zone;
            }
        }
    }


    // X-AXIS

    // now sort the axis for the bests curve
    double xmin = 1.0f/60.0f - 0.001f;
    double xmax = time[maxNonZero - 1];

    // truncate at an hour for energy mode
    if (criticalSeries == CriticalPowerWindow::work) xmax = 30.0;

    // not interested in short durations for vam
    if (criticalSeries == CriticalPowerWindow::vam) xmin = 4.993;

    // now set the scale
    QwtScaleDiv div((double)xmin, (double)xmax);

    if (criticalSeries == CriticalPowerWindow::work ||
        (criticalSeries == CriticalPowerWindow::kph && xAxisLinearOnSpeed))
        div.setTicks(QwtScaleDiv::MajorTick, LogTimeScaleDraw::ticksEnergy);
    else if (criticalSeries == CriticalPowerWindow::veloclinicplot)
        div.setTicks(QwtScaleDiv::MajorTick, LogTimeScaleDraw::ticksVeloclinic);
    else
        div.setTicks(QwtScaleDiv::MajorTick, LogTimeScaleDraw::ticks);
    setAxisScaleDiv(QwtAxis::XBottom, div);

    if (criticalSeries == CriticalPowerWindow::veloclinicplot) {
        double xmax;
        xmax = 100 * ceil(values[0] / 100);
        if (xmax == 100) xmax = 5 * ceil(values[0] / 5);

        // set ymax to nearest 100 if power
        int max = xmax * 1.1f;
        max = ((max/100) + 1) * 100;

        setAxisScale(QwtAxis::XBottom, 0, max);
    }

    // Y-AXIS

    double ymax;
    if (criticalSeries == CriticalPowerWindow::work) {
        if (maxNonZero > 1800) ymax = 10 * ceil(work[1799] / 10);
        else ymax = 10 * ceil(work[maxNonZero-1] / 10);

    } else if (criticalSeries == CriticalPowerWindow::vam) {
        double yVam = bestsCache->meanMaxArray(RideFile::vam).value(296);
        ymax = 100 * ceil(yVam / 100); // index of xMin for Time
    } else {
        ymax = 100 * ceil(values[0] / 100);
        if (ymax == 100) ymax = 5 * ceil(values[0] / 5);
    }

    // adjust if for power
    if (rideSeries == RideFile::watts && criticalSeries != CriticalPowerWindow::veloclinicplot) {

        // set ymax to nearest 100 if power
        int max = ymax * 1.1f;
        max = ((max/100) + 1) * 100;
        if (rideSeries == RideFile::watts && showPP && max<1500) max=1500;

        setAxisScale(QwtAxis::YLeft, 0, max);

    } else if (criticalSeries == CriticalPowerWindow::veloclinicplot) {
        setAxisScale(QwtAxis::YLeft, 0, 1.5*pdModel->WPrime());

    } else if (criticalSeries == CriticalPowerWindow::vam) {
        // VAM is very big anyway - so just 5% headroom
        setAxisScale(QwtAxis::YLeft, 0, 1.05*ymax);

    } else if (rideSeries == RideFile::wattsKg && showPP) {
        ymax *= 1.1;
        if (ymax<20) ymax = 20;
        setAxisScale(QwtAxis::YLeft, 0, ymax);

    } else {

        // or just add 10% headroom
        setAxisScale(QwtAxis::YLeft, 0, 1.1*values[0]);
    }
    zoomer->setZoomBase(false);
}

void
CPPlot::plotEfforts()
{
    // only for power, if not already plotted and there are actually some efforts
    if (criticalSeries != CriticalPowerWindow::watts || effortCurve || !showEffort) return;

    // nothing to plot when plotting rides
    if (!rangemode && context->currentRideItem()->intervals(RideFileInterval::EFFORT).count() ==0) return;

    QwtSymbol *sym = new QwtSymbol;
    sym->setStyle(QwtSymbol::Ellipse);
    sym->setSize(dpiXFactor * (rangemode ? 4 : 6));
    QColor col= GColor(CPOWER);
    col.setAlpha(128);
    sym->setBrush(col);
    sym->setPen(QPen(Qt::NoPen));

    // create a curve
    effortCurve = new QwtPlotCurve();
    effortCurve->setSymbol(sym);
    effortCurve->setStyle(QwtPlotCurve::Dots);
    effortCurve->setRenderHint(QwtPlotItem::RenderAntialiased);

    // plot for a range of rides
    if (rangemode) {

        // size for all rides, can increase if needed
        QVector<double> xvals;
        QVector<double> yvals;

        FilterSet fs; // apply filters when selecting intervals
        fs.addFilter(context->isfiltered, context->filters);
        fs.addFilter(context->ishomefiltered, context->homeFilters);
        if (parent->myPerspective) fs.addFilter(parent->myPerspective->isFiltered(), parent->myPerspective->filterlist(DateRange(startDate,endDate)));
        Specification spec;
        spec.setFilterSet(fs);
        spec.setDateRange(DateRange(startDate, endDate));

        int count=0;
        foreach(RideItem *r, context->athlete->rideCache->rides()) {

            // does it match ?
            if (!spec.pass(r)) continue;

            // add the intervals
            foreach(IntervalItem *i, r->intervals(RideFileInterval::EFFORT)) {

                // is it a silly value?
                if (i->getForSymbol("average_power") > 3000) continue;

                xvals << i->getForSymbol("workout_time") / 60.0f;
                yvals << i->getForSymbol("average_power");
                count++;
            }
        }

        if (count) {
            // set the data and attach to the plot
            effortCurve->setSamples(xvals.constData(), yvals.constData(), count);
            effortCurve->attach(this);
        }
    
    } else {

        // plot for the current ride
        QList<IntervalItem *> intervals = context->currentRideItem()->intervals(RideFileInterval::EFFORT);

        // size for all rides, can increase if needed
        QVector<double> xvals(intervals.count());
        QVector<double> yvals(intervals.count());

        for(int i=0; i<intervals.count(); i++) {
            xvals[i] = double(intervals.at(i)->getForSymbol("workout_time")) / 60.0f;
            yvals[i] = double(intervals.at(i)->getForSymbol("average_power"));
        }

        // set the data and attach to the plot
        effortCurve->setSamples(xvals.constData(), yvals.constData(), intervals.count());
        effortCurve->attach(this);
    }
}

void
CPPlot::refreshUpdate(QDate)
{
    // we don't need to lookat the date since we know if the data is incomplete
    if (bestsCache && bestsCache->incomplete && (lastupdate == QTime() || lastupdate.secsTo(QTime::currentTime()) > 5)) {
        lastupdate = QTime::currentTime();
        clearCurves();
        setRide(const_cast<RideItem*>(context->currentRideItem()));
    }

    // what about in compare season mode ?
    if (rangemode && context->isCompareDateRanges && (lastupdate == QTime() || lastupdate.secsTo(QTime::currentTime()) > 5)) {
        lastupdate = QTime::currentTime();
        calculateForDateRanges(context->compareDateRanges);
        return;
    }
}

void
CPPlot::refreshEnd()
{
    refreshUpdate(QDate());
}

// plot the currently selected ride
void
CPPlot::plotRide(RideItem *rideItem)
{
    // currently selected ride wanted ?
    if (!rideItem || rangemode || plotType == 2) return;

    // if its already plotted we don't do it again
    // it is wiped when setRide is called to force
    // a replot
    if (rideCurve) return;

    // there is not data to plot!
    if (rideItem->fileCache()->meanMaxArray(rideSeries).size() == 0) return;

    // check what we do have to plot
    int maxNonZero = 0;
    QVector<double> timeArray(rideItem->fileCache()->meanMaxArray(rideSeries).size());
    for (int i = 0; i < rideItem->fileCache()->meanMaxArray(rideSeries).size(); ++i) {
        timeArray[i] = i / 60.0;
        if (rideItem->fileCache()->meanMaxArray(rideSeries)[i] > 0) maxNonZero = i;
    }

    // do we have nonzero data to plot ?
    if (maxNonZero == 1) return;

    // Right, lets actually plot the ride
    rideCurve = new QwtPlotCurve(rideItem->dateTime.toString(tr("ddd MMM d, yyyy hh:mm")));
    rideCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
    rideCurve->setBrush(QBrush(Qt::NoBrush)); // never filled

    // what color and fill do we have for the ride ?
    // there is a specific colour setting for the "ride curve" on
    // the CP plot, regardless of the series. Its only the bests
    // curve that gets any special colour treatment.
    QPen ridePen;
    ridePen.setColor(GColor(CRIDECP));
    double width = appsettings->value(this, GC_LINEWIDTH, 0.5*dpiXFactor).toDouble();
    ridePen.setWidth(width);
    rideCurve->setPen(ridePen);

    // set the curve samples
    if (criticalSeries == CriticalPowerWindow::work) {

        // WORK

        QVector<double> energyArray(rideItem->fileCache()->meanMaxArray(RideFile::watts).size());
        for (int i = 0; i <= maxNonZero; ++i) {
            energyArray[i] = timeArray[i] * rideItem->fileCache()->meanMaxArray(RideFile::watts)[i] * 60.0 / 1000.0;
        }
        rideCurve->setSamples(timeArray.data() + 1, energyArray.constData() + 1,
                              maxNonZero > 0 ? maxNonZero-1 : 0);

    } else if (criticalSeries == CriticalPowerWindow::veloclinicplot) {

        // Veloclinic plot

        QVector<double> array(rideItem->fileCache()->meanMaxArray(RideFile::watts).size());
        for (int i = 0; i <= maxNonZero; ++i) {
            array[i] =  (rideItem->fileCache()->meanMaxArray(rideSeries)[i]<veloCP?0:(rideItem->fileCache()->meanMaxArray(rideSeries)[i]-veloCP) * timeArray[i] * 60.0);
        }
        rideCurve->setSamples(rideItem->fileCache()->meanMaxArray(rideSeries).constData()  + 1, array.constData() + 1,
                              maxNonZero > 0 ? maxNonZero-1 : 0);
    } else  {

        // ALL OTHER RIDE SERIES

        // AS A PERCENTAGE
        // we want as a percent of best and we do have
        // the bests available
        if (showPercent && bestsCache) {

            QVector<double> samples(timeArray.size());

            // percentify from the cache
            for(int i=0; i <samples.size() && i < rideItem->fileCache()->meanMaxArray(rideSeries).size() &&
                    i <bestsCache->meanMaxArray(rideSeries).size(); i++) {

                samples[i] = rideItem->fileCache()->meanMaxArray(rideSeries)[i] /
                             bestsCache->meanMaxArray(rideSeries)[i] * 100.00f;
            }
            rideCurve->setSamples(timeArray.data() + 1, samples.data() + 1,
                                  maxNonZero > 0 ? maxNonZero-1 : 0);

            // did we get over 100% .. because if so
            // we need to set the maxY on the RHS to reflect that
            int max = rideCurve->maxYValue();
            if (max < 100) max = 100;
            else max = max * 1.05f;
            setAxisScale(QwtAxis::YRight, 0, max); // always 100

            // set the right titles in case both Heat and Percent of best is show
            if (showHeat) setAxisTitle(QwtAxis::YRight, tr("Percent of Best / Heat Activities"));
            else setAxisTitle(QwtAxis::YRight, tr("Percent of Best"));

        } else if (showPowerIndex && bestsCache && (rideSeries == RideFile::wattsKg || rideSeries == RideFile::watts)) {

            // plot as power index if its a power series
            QVector<double> samples(timeArray.size());

            // power index ify from the cache and always use watts regardless
            for(int i=0; i <samples.size() && i < rideItem->fileCache()->meanMaxArray(RideFile::watts).size() &&
                    i <bestsCache->meanMaxArray(rideSeries).size(); i++) {

                samples[i] = powerIndex(rideItem->fileCache()->meanMaxArray(rideSeries)[i], i+1);
            }
            rideCurve->setSamples(timeArray.data() + 1, samples.data() + 1,
                                  maxNonZero > 0 ? maxNonZero-1 : 0);

            // did we get over 100% .. because if so
            // we need to set the maxY on the RHS to reflect that
            int max = rideCurve->maxYValue();
            if (max < 1) max = 1;
            else max = max * 1.05f;
            setAxisScale(QwtAxis::YRight, 0, max); // always 100

            // set the right titles in case both Heat and Percent of best is show
            setAxisTitle(QwtAxis::YRight, tr("Power Index"));

        } else {

            // JUST A NORMAL CURVE
            rideCurve->setYAxis(QwtAxis::YLeft);
            rideCurve->setSamples(timeArray.data() + 1, rideItem->fileCache()->meanMaxArray(rideSeries).constData() + 1,
                                  maxNonZero > 0 ? maxNonZero-1 : 0);

            // Set the YAxis Title if Heat is active
            if (showHeat) setAxisTitle(QwtAxis::YRight, tr("Heat Activities"));
        }
    }

    // which axis should it be on?
    // and also make sure its visible
    rideCurve->setYAxis((showPercent||showPowerIndex) ? QwtAxis::YRight : QwtAxis::YLeft);
    setAxisVisible(QwtAxis::YRight, showPercent || showPowerIndex || showHeat);
    rideCurve->attach(this);

    zoomer->setZoomBase(false);
}

// notified that the user selected a ride
void
CPPlot::setRide(RideItem *rideItem)
{
    // null ride ?
    if (!rideItem) return;

    // Season Compare Mode -- so nothing for us to do
    if (rangemode && context->isCompareDateRanges) return calculateForDateRanges(context->compareDateRanges);

    // Interval Compare Mode -- so go do that instead
    if (!rangemode && context->isCompareIntervals) return calculateForIntervals(context->compareIntervals);

    // clear the old ride curve and cache
    // regardless, as its no longer relevant
    // we may not create new ones but at least
    // we will always remove out of date info
    if (rideCurve) {
        rideCurve->detach();
        delete rideCurve;
        rideCurve = NULL;
    }
    // clear any centile and interval curves
    // since they will be for an old ride
    foreach(QwtPlotCurve *c, centileCurves) {
        c->detach();
        delete c;
    }
    centileCurves.clear();
    foreach(QwtPlotCurve *c, intervalCurves) {
        c->detach();
        delete c;
    }
    intervalCurves.clear();
    if (effortCurve) {
        effortCurve->detach();
        delete effortCurve;
        effortCurve = NULL;
    }

    // MAKE SURE BESTS IS UP TO DATE FIRST AS WE REFERENCE IT
    // first make sure the bests cache is up to date as we may need it
    // if plotting in percentage mode, so get data and plot it now
    // delete if sport changed
    if (!rangemode) {
        setSport(rideItem->sport);
        delete bestsCache;
        bestsCache = NULL;
        clearCurves();
        plotBests(rideItem);
    } else {
        plotBests(NULL);
    }

    // plot the powerprofile
    plotPowerProfile();

    // plot tests (in ride, or across date range)
    plotTests(rangemode ? NULL : rideItem);

    // Plot Sustained Efforts
    plotEfforts();

    // do we actually have something to plot?
    if (rideItem && rideItem->ride() && rideItem->ride()->dataPoints().count()) {

        // NOW PLOT OUR CURVE
        // We are a percent or plain curve
        switch (plotType) {

        case 0 :
            {
                // MEANMAX
                // Plot as normal or percent
                plotRide(rideItem);
                refreshReferenceLines(rideItem);
            }
            break;

        case 1 :
            {
                // CENTILE
                // calculates all the data from the raw ride data, so doesn't need
                // a cache and doesn't make sense to plot reference lines
                plotCentile(rideItem);
            }
            break;

        case 2 :
            {
                // NONE
                // make sure there is no ride curve plotted then
                // NOTE: It was already wiped away at the beginning
                //       of this method so there really is nothing
                //       left to do !
            }
            break;

        }
    }

    // NOW PLOT THE MODEL CURVE
    // it will need to decide if it is relevant etc
    // but we need to make sure we have it

    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // IT MUST ALWAYS BE LAST IN THIS METHOD
    // TO ENSURE MEANMAX AND/ OR FILTERED DATA
    // HAVE BEEN REFRESHED ABOVE
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    plotModel();

    // We also add a line for linear model when plotting work
    plotLinearWorkModel();

    // now replot please
    replot();
}

// the picker hovered over a point on a curve
void
CPPlot::pointHover(QwtPlotCurve *curve, int index)
{
    if (index >= 0) {

        const double xvalue = curve->sample(index).x();
        const double yvalue = curve->sample(index).y();
        QString text, dateStr;
        QString currentRidePercentStr;
        QString units1;
        QString units2;

        // add when to tooltip if its all curve
        if (bestsCurves.contains(curve)) {
            int index = xvalue * 60;
            if (index >= 0 && bestsCache && getBests().count() > index) {
                QDate date = getBestDates()[index];
                dateStr = date.toString(tr("\nddd, dd MMM yyyy"));
            }
        }

        // use the right pace config
        bool metricPace = true;
        if (sport == "Swim") metricPace = appsettings->value(this, GC_SWIMPACE, GlobalContext::context()->useMetricUnits).toBool();
        else if (sport == "Run")  metricPace = appsettings->value(this, GC_PACE, GlobalContext::context()->useMetricUnits).toBool();
        else  metricPace = GlobalContext::context()->useMetricUnits;


        if (criticalSeries == CriticalPowerWindow::veloclinicplot) {
            units1 = RideFile::unitName(rideSeries, context);
        } else {
            units1 = ""; // time --> no units
        }
        
        // pace units for heat curve
        if (curve == heatCurve) {

            units2 = tr("%1 %2").arg(yvalue, 0, 'f', RideFile::decimalsFor(rideSeries))
                                .arg(tr("Activities"));

        } else  if ((((rangemode && context->isCompareDateRanges) || (!rangemode && context->isCompareIntervals)) && showDelta && showDeltaPercent)
                   || (curve == rideCurve && showPercent)) {

            units2 = tr("%1 %2").arg(yvalue, 0, 'f', RideFile::decimalsFor(rideSeries))
                                .arg(tr("%")); // Percent

        } else if (criticalSeries == CriticalPowerWindow::veloclinicplot) {

            units2 = tr("%1 %2").arg(yvalue, 0, 'f', RideFile::decimalsFor(rideSeries))
                                .arg(tr("J")); // Joule

        } else if (criticalSeries == CriticalPowerWindow::work) {

            units2 = tr("%1 %2").arg(yvalue, 0, 'f', 1)
                                .arg(tr("kJ")); // kJoule

        } else if (criticalSeries == CriticalPowerWindow::kph) {

            if (metricPace)  units2 = tr("%1 kph").arg(yvalue, 0, 'f', RideFile::decimalsFor(rideSeries));
            else  units2 = tr("%1 mph").arg(yvalue*MILES_PER_KM, 0, 'f', RideFile::decimalsFor(rideSeries));

        } else {

            // eg: "### watts"
            if (showPercent && curve == rideCurve) units2 = tr("%1 Percent").arg(yvalue, 0, 'f', RideFile::decimalsFor(rideSeries));
            else if (showPowerIndex) units2 = tr("%1 Power Index").arg(yvalue, 0, 'f', RideFile::decimalsFor(rideSeries));
            else units2 = tr("%1 %2").arg(yvalue, 0, 'f', RideFile::decimalsFor(rideSeries))
                                .arg(RideFile::unitName(rideSeries, context));
        }

#if 0
		// for the current ride curve, add a percent of rider's actual best.
        if (!showPercent && curve == rideCurve && index >= 0 && getBests().count() > index) {

			double bestY = getBests()[index];
            if (0 != bestY) {

				// use 0 decimals for the percent.
				currentRidePercentStr = QString("\n%1 %2")
					.arg((yvalue *100)/ bestY, 0, 'f', 0)
					.arg(tr("Percent of Best"));
			}
        }
#endif

        // output the tooltip
        text = QString("%1%2\n%3 %4%5%6")
               .arg(criticalSeries == CriticalPowerWindow::veloclinicplot ?
                    QString("%1").arg(xvalue, 0, 'f', RideFile::decimalsFor(rideSeries))
                    : interval_to_str(60.0*xvalue))
               .arg(units1)
               .arg(units2)
               .arg(currentRidePercentStr)
               .arg(criticalSeries == CriticalPowerWindow::kph ? paceString(xvalue*60.0, yvalue) : "")
               .arg(dateStr);

        // set that text up
        zoomer->setText(text);
        return;
    }
    // no point
    zoomer->setText("");
}

void
CPPlot::exportBests(QString filename)
{
    QFile f(filename);

    if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) return; // couldn't open file

    // do we want to export the model estimate too ?
    bool expmodel = (model && pdModel && (rideSeries == RideFile::wattsKg || rideSeries == RideFile::watts || rideSeries == RideFile::aPowerKg || rideSeries == RideFile::aPower || rideSeries == RideFile::kph));

    // open stream and write header
    QTextStream stream(&f);
    stream << "seconds, value," << (expmodel ? " model, date" : " date") << Qt::endl;

    // output a row for each second
    foreach(QwtPlotCurve *bestsCurve, bestsCurves) {

        // just output for the bests curve
        for (int i=0; i<bestsCurve->data()->size(); i++) {
            const double xvalue = bestsCurve->sample(i).x();
            const double yvalue = bestsCurve->sample(i).y();
            const double modelvalue = expmodel ? pdModel->y(xvalue) : 0;

            int index = xvalue * 60.00f;
            QDate date;
            if (index >= 0 && bestsCache && getBests().count() > index) {
                date = getBestDates()[index];
            }

            // values
            if (expmodel) stream << int(xvalue * 60.00f) << "," << yvalue << "," << modelvalue << "," << date.toString(Qt::ISODate) << Qt::endl;
            else stream << int(xvalue * 60.00f) << "," << yvalue << "," << date.toString(Qt::ISODate) << Qt::endl;
        }
    }

    // and we're done
    f.close();
}

// perspective filter changed, we need to replot with new bests
void
CPPlot::perspectiveFilterChanged()
{
    if (bestsCache) {
        delete bestsCache;
        bestsCache = NULL;
    }
    clearCurves();
}

// no filter
void
CPPlot::clearFilter()
{
    isFiltered = false;
    files.clear();
    delete bestsCache;
    bestsCache = NULL;
    clearCurves();
}

// set a filter
void
CPPlot::setFilter(QStringList list)
{
    isFiltered = true;
    files = list;
    delete bestsCache;
    bestsCache = NULL;
    clearCurves();
}

void
CPPlot::setShowHeat(bool x)
{
    showHeat = x;
    clearCurves();
}

void
CPPlot::setShowPowerIndex(bool x)
{
    showPowerIndex = x;
    showPercent = showPowerIndex ? false : showPercent;
    clearCurves();
}

void
CPPlot::setShowPP(bool x)
{
    showPP = x;
    clearCurves();
}

void
CPPlot::setShowEffort(bool x)
{
    showEffort = x;
    clearCurves();
}

void
CPPlot::setFilterBest(bool x)
{
    filterBest = x;
    clearCurves();
}

void
CPPlot::setShowTest(bool x)
{
    showTest = x;
    clearCurves();
}

void
CPPlot::setShowBest(bool x)
{
    showBest = x;
    clearCurves();
}

void
CPPlot::setShowPercent(bool x)
{
    showPercent = x;
    showPowerIndex = showPercent ? false : showPowerIndex;
}

void
CPPlot::setShowDelta(bool delta, bool percent)
{
    showDelta = delta;
    showDeltaPercent = percent;
    setSeries(this->criticalSeries); // y-axis
}

void
CPPlot::setShowHeatByDate(bool x)
{
    showHeatByDate = x;
    clearCurves();
}


void
CPPlot::setShadeMode(int x)
{
    shadeMode = x;
    clearCurves();
}

void
CPPlot::setShadeIntervals(int x)
{
    shadeIntervals = x;
    clearCurves();
}

void
CPPlot::showXAxisLinear(bool x)
{
    xAxisLinearOnSpeed = x;

    // change to linear/log on critical speed chart only
    if (this->criticalSeries == CriticalPowerWindow::kph) {
        lastupdate = QTime::currentTime();
        clearCurves();
        // redraw
        setSeries(this->criticalSeries);
        setRide(const_cast<RideItem*>(context->currentRideItem()));
        calculateForDateRanges(context->compareDateRanges);
    }
}

// model parameters!
void
CPPlot::setModel(int sanI1, int sanI2, int anI1, int anI2, int aeI1, int aeI2, int laeI1, int laeI2, int model, int variant, int fit, int fitdata, bool decay)
{
    this->anI1 = double(anI1);
    this->anI2 = double(anI2);
    this->aeI1 = double(aeI1);
    this->aeI2 = double(aeI2);

    this->sanI1 = double(sanI1);
    this->sanI2 = double(sanI2);
    this->laeI1 = double(laeI1);
    this->laeI2 = double(laeI2);

    this->model = model;
    this->modelVariant = variant;
    this->fit = fit;
    this->fitdata = fitdata;
    this->modelDecay = decay;

    clearCurves();
}

void
CPPlot::refreshReferenceLines(RideItem *rideItem)
{
    // we only do refs for a specific ride
    if (rangemode) return;

    // wipe existing
    foreach(QwtPlotMarker *referenceLine, referenceLines) {
        referenceLine->detach();
        delete referenceLine;
    }
    referenceLines.clear();

    if (!rideItem) return;
    if (!rideItem->ride()) return;

    // horizontal lines at reference points
    if (rideSeries == RideFile::aPower || rideSeries == RideFile::xPower || rideSeries == RideFile::IsoPower || rideSeries == RideFile::watts  || rideSeries == RideFile::wattsKg || rideSeries == RideFile::aPowerKg) {

        if (rideItem->ride()) {
            foreach(const RideFilePoint *referencePoint, rideItem->ride()->referencePoints()) {

                if (referencePoint->watts != 0) {
                    QwtPlotMarker *referenceLine = new QwtPlotMarker;
                    QPen p;
                    p.setColor(GColor(CPLOTMARKER));
                    double width = appsettings->value(this, GC_LINEWIDTH, 0.5*dpiXFactor).toDouble();
                    p.setWidth(width);
                    p.setStyle(Qt::DashLine);
                    referenceLine->setLinePen(p);
                    referenceLine->setLineStyle(QwtPlotMarker::HLine);
                    referenceLine->setYValue(referencePoint->watts);
                    referenceLine->attach(this);
                    referenceLines.append(referenceLine);
                }
            }
        }
    }
}

// plot mean max, centile or none!
void
CPPlot::setPlotType(int index)
{
    plotType = index;
    clearCurves();
}

// calculate and plot a centile plot
void
CPPlot::plotCentile(RideItem *rideItem)
{
    qDebug() << "calculateCentile";
    QElapsedTimer elapsed;
    elapsed.start();

    qDebug() << "prepare datas ";
    cpintdata data;
    data.rec_int_ms = (int) round(rideItem->ride()->recIntSecs() * 1000.0);
    double lastsecs = 0;
    bool first = true;
    double offset = 0;

    foreach (const RideFilePoint *p, rideItem->ride()->dataPoints()) {

        // get offset to apply on all samples if first sample
        if (first == true) {
            offset = p->secs;
            first = false;
        }

        // drag back to start at 0s
        double psecs = p->secs - offset;

        // fill in any gaps in recording - use same dodgy rounding as before
        int count = (psecs - lastsecs - rideItem->ride()->recIntSecs()) / rideItem->ride()->recIntSecs();

        // gap more than an hour, damn that ride file is a mess
        if (count > 3600) count = 1;

        for(int i=0; i<count; i++) {
            data.points.append(cpintpoint(round(lastsecs+((i+1)*rideItem->ride()->recIntSecs() *1000.0)/1000), 0));
        }

        lastsecs = psecs;

        double secs = round(psecs * 1000.0) / 1000;
        if (secs > 0)  {
            if (round(p->value(RideFile::watts))>1400)
                qDebug() << "append point " <<  round(p->value(RideFile::watts)) ;
            data.points.append(cpintpoint(secs, (int) round(p->value(RideFile::watts))));
        }
    }

    int total_secs = (int) ceil(rideItem->ride()->dataPoints().back()->secs);

    QVector < QVector<double> > ride_centiles(10);
    // Initialisation
    for (int i = 0; i < ride_centiles.size(); ++i) {
        ride_centiles[i] = QVector <double>(total_secs);
    }

    qDebug() << "end prepare datas " << elapsed.elapsed();
    qDebug() << "calcul for first 6min ";

    // loop through the decritized data from top
    // FIRST 6 MINUTES DO BESTS FOR EVERY SECOND
    // WE DO NOT DO THIS FOR IsoPower or xPower SINCE
    // IT IS WELL KNOWN THAT THEY ARE NOT VALID
    // FOR SUCH SHORT DURATIONS AND IT IS VERY
    // CPU INTENSIVE, SO WE DON'T BOTHER

    double samplerate = rideItem->ride()->recIntSecs();

    for (int slice = 1; slice < 360;) {
        int windowsize = slice / samplerate;
        QVector<double> sums(data.points.size()-windowsize+1);

        int index=0;
        double sum=0;

        for (int i=0; i<data.points.size(); i++) {
            sum += data.points[i].value;

            if (i>windowsize-1)
                sum -= data.points[i-windowsize].value;

            if (i>=windowsize-1) {
                sums[index++] = sum/windowsize;
            }

        }
        std::sort(sums.begin(), sums.end());

        qDebug() << "sums (" << slice << ") : " << sums.size() << " max " << sums[sums.size()-1];

        ride_centiles[9][slice] = sums[sums.size()-1];

        for (int i = ride_centiles.size()-1; i > 0; --i) {
            sum = 0;
            int count = 0;

            for (int n = (0.1*i)*sums.size(); n < sums.size()-1 && n < (0.1*(i+1))*sums.size(); ++n) {
                sum += sums[n];
                count++;
            }
            if (sum > 0) {
                double avg = sum / count;
                ride_centiles[i-1][slice]=avg;
            } else {
                ride_centiles[i-1][slice]=ride_centiles[i][slice];
            }
        }

        slice ++;
    }

    qDebug() << "end calcul for first 6min " << elapsed.elapsed();
    qDebug() << "downsampling to 5s after 6min ";

    QVector<double> downsampled(0);

    // moving to 5s samples would INCREASE the work...
    if (rideItem->ride()->recIntSecs() >= 5) {
        samplerate = rideItem->ride()->recIntSecs();
        for (int i=0; i<data.points.size(); i++)
            downsampled.append(data.points[i].value);
    } else {
        // moving to 5s samples is DECREASING the work...
        samplerate = 5;
        // we are downsampling to 5s
        long five=5; // start at 1st 5s sample
        double fivesum=0;

        int fivecount=0;

        for (int i=0; i<data.points.size(); i++) {
            if (data.points[i].secs <= five) {
                fivesum += data.points[i].value;
                fivecount++;
            } else {
                downsampled.append(fivesum / fivecount);
                fivecount = 1;
                fivesum = data.points[i].value;

                five += 5;
            }
        }
    }

    qDebug() << "end downsampling to 5s after 6min " << elapsed.elapsed();
    qDebug() << "calcul for rest of ride ";

    for (int slice = 360; slice < ride_centiles[9].size();) {
        int windowsize = slice / samplerate;
        QVector<double> sums(downsampled.size()-windowsize+2);


        int index=0;
        double sum=0;

        for (int i=0; i<downsampled.size(); i++) {
            sum += downsampled[i];

            if (i>windowsize-1)
                sum -= downsampled[i-windowsize];
            if (i>=windowsize-1)
                sums[index++] = sum / windowsize;

        }
        std::sort(sums.begin(), sums.end());

        qDebug() << "sums (" << slice << ") : " << sums.size() << " max " << sums[sums.size()-1];



        ride_centiles[9][slice] = sums[sums.size()-1];

        for (int i = ride_centiles.size()-1; i > 0; --i) {
            sum = 0;
            int count = 0;

            for (int n = (0.1*i)*sums.size(); n < sums.size() && n < (0.1*(i+1))*sums.size(); ++n) {
                if (sums[n]>0)  {
                    sum += sums[n];
                    count++;
                }
            }
            if (sum > 0) {
                double avg = sum / count;
                ride_centiles[i-1][slice]=avg;
            } else {
                ride_centiles[i-1][slice]=ride_centiles[i][slice];
            }
        }



        // increment interval duration we are going to search
        // for next, gaps increase as duration increases to
        // reduce overall work, since we require far less
        // precision as the ride duration increases
        if (slice < 3600) slice +=20; // 20s up to one hour
        else if (slice < 7200) slice +=60; // 1m up to two hours
        else if (slice < 10800) slice += 300; // 5mins up to three hours
        else slice += 600; // 10mins after that
    }

    qDebug() << "end calcul for rest of ride " << elapsed.elapsed();
    qDebug() << "fill gaps ";

    /*for (int i = 0; i<ride_centiles.size(); i++) {
        double last=0.0;
        for (int j=ride_centiles[i].size()-1; j; j--) {
            if (ride_centiles[i][j] == 0) ride_centiles[i][j]=last;
            else last = ride_centiles[i][j];
        }
    }*/

    for (int i = ride_centiles.size()-1; i>=0; i--) {
        double last=0.0;
        for (int j=0; j<ride_centiles[i].size(); j++) {
            if (ride_centiles[i][j] == 0) ride_centiles[i][j]=last;
            else last = ride_centiles[i][j];
        }
    }

    qDebug() << "end fill gaps " << elapsed.elapsed();
    qDebug() << "plotting ";


    for (int i = 0; i<ride_centiles.size(); i++) {
        int maxNonZero = 0;
        QVector<double> timeArray(ride_centiles[i].size());
        for (int j = 0; j < ride_centiles[i].size(); ++j) {
            timeArray[j] = j / 60.0;
            if (ride_centiles[i][j] > 0) maxNonZero = j;
        }

        if (maxNonZero > 1) {

            QwtPlotCurve *rideCurve = new QwtPlotCurve(tr("%10 %").arg(i+1));
            rideCurve->setRenderHint(QwtPlotItem::RenderAntialiased);

            // red hue to cp curve color
            QColor std = GColor(CRIDECP);
            QPen pen(QColor(250-(i*20),std.green(),std.blue()));
            pen.setStyle(Qt::DashLine); // Qt::SolidLine
            double width = appsettings->value(this, GC_LINEWIDTH, 0.5*dpiXFactor).toDouble();
            pen.setWidth(width);
            rideCurve->setPen(pen);
            rideCurve->attach(this);


            rideCurve->setSamples(timeArray.data() + 1, ride_centiles[i].constData() + 1, maxNonZero - 1);
            centileCurves.append(rideCurve);
        }
    }


    qDebug() << "end plotting " << elapsed.elapsed();
    zoomer->setZoomBase(false);
}

void
CPPlot::calculateForDateRanges(QList<CompareDateRange> compareDateRanges)
{
    if (!rangemode) return;

    // zap old curves
    clearCurves();
    foreach(QwtPlotCurve *c, intervalCurves) {
        c->detach();
        delete c;
    }
    intervalCurves.clear();
    // don't need to zap old models as they
    // are deleted with the curve !

    // If no range
    if (compareDateRanges.count() == 0) {
        replot(); // show cleared plot
        return;
    }

    double ymax = 0;
    double ymin = 0;

    // when delta mode is invoked we compare to a baseline curve
    QVector<double> baseline;
    PDModel *baselineModel = NULL;

    if (showDelta && compareDateRanges.count()) {

        // set the baseline data
        baseline = compareDateRanges[0].rideFileCache()->meanMaxArray(rideSeries);

        if (model && (rideSeries == RideFile::watts || rideSeries == RideFile::wattsKg || rideSeries == RideFile::aPower || rideSeries == RideFile::aPowerKg || rideSeries == RideFile::kph)) {

            // get a model
            switch (model) {
            case 1 : // 2 param
                baselineModel = new CP2Model(context);
                break;
            case 2 : // 3 param
                baselineModel = new CP3Model(context);
                break;
            case 3 : // extended model
                baselineModel = new ExtendedModel(context);
                break;
            case 4 : // multimodel
                baselineModel = new MultiModel(context);
                baselineModel->setVariant(modelVariant);
                break;
            case 5 : // ward smith
                baselineModel = new WSModel(context);
                baselineModel->setVariant(modelVariant);
                break;
            }

            // feed the data into the model
            if (baselineModel) {

                // set the model and load data
                baselineModel->setIntervals(sanI1, sanI2, anI1, anI2, aeI1, aeI2, laeI1, laeI2);
                baselineModel->setMinutes(true); // we're minutes here ...
                baselineModel->setData(baseline);
            }
        }
    }

    double xmin = 1.0f/60.0f - 0.001f;
    double xmax = 0;

    // prepare aggregates
    for (int j = 0; j < compareDateRanges.size(); ++j) {

        if (compareDateRanges[j].isChecked())  {

            RideFileCache *cache = compareDateRanges[j].rideFileCache();

            // create a delta array
            if (showDelta && cache) {

                int n=0;
                QVector<double> deltaArray = cache->meanMaxArray(rideSeries);

                // make a delta to baseline
                for (n=1; n < deltaArray.size() && n < baseline.size(); n++) {
                    // stop when we get to zero!
                    if (deltaArray[n] > 0 && baseline[n] > 0) {

                        if (showDeltaPercent) deltaArray[n] = 100.00f * (double(deltaArray[n]) - double(baseline[n])) / double(baseline[n]); // delta percentage
                        else deltaArray[n] = deltaArray[n] - baseline[n];

                    } else
                        break;
                }
                deltaArray.resize(n-1);

                // now plot using the delta series and NOT the cache
                if (showBest) plotCache(deltaArray, compareDateRanges[j].color);

                // and plot a model too -- its neat to compare them...
                if (rideSeries == RideFile::watts || rideSeries == RideFile::wattsKg || rideSeries == RideFile::aPower || rideSeries == RideFile::aPowerKg || rideSeries == RideFile::kph) {
                    plotModel(cache->meanMaxArray(rideSeries), compareDateRanges[j].color, baselineModel);
                }

                foreach(double v, deltaArray) {
                    if (v > ymax) ymax = v;
                    if (v < ymin) ymin = v;
                }

                // keep track of longest point
                int imax = cache->meanMaxArray(rideSeries).count()/60.00f;
                if (cache && imax > xmax) xmax = imax;

            }

            if (!showDelta && cache->meanMaxArray(rideSeries).size()) {

                // plot the bests if we want them
                if (showBest) plotCache(cache->meanMaxArray(rideSeries), compareDateRanges[j].color);

                // and plot a model too -- its neat to compare them...
                if (rideSeries == RideFile::watts || rideSeries == RideFile::wattsKg || rideSeries == RideFile::aPower || rideSeries == RideFile::aPowerKg || rideSeries == RideFile::kph)
                    plotModel(cache->meanMaxArray(rideSeries), compareDateRanges[j].color, NULL);

                int xCount = 0;
                double vamYMax = 0;
                foreach(double v, cache->meanMaxArray(rideSeries)) {
                    // get the biggest y-Axes value
                    if (v > ymax) ymax = v;

                    // VAM x-Axis starts at 4.993 seconds = x-time array point 296 -
                    // so skip the values upto that point in x-Axis time
                    if (rideSeries == RideFile::vam) {
                       // VAM x-Axis starts at 4.993 seconds = x-time array point 296 -
                       // so skip the values upto that point in x-Axis time
                       if (xCount >= 296) {
                           if (v > vamYMax) vamYMax = v;
                       }
                     }
                     xCount++;
                }
                // overwrite ymax for VAM - if a proper value was found
                if (rideSeries == RideFile::vam && vamYMax > 0) ymax = vamYMax;
            }

            if (!showDelta && showEffort) {

                // show the efforts for each date range
                QwtSymbol *sym = new QwtSymbol;
                sym->setStyle(QwtSymbol::Ellipse);
                sym->setSize(4 *dpiXFactor);
                QColor col= compareDateRanges[j].color;
                col.setAlpha(128);
                sym->setBrush(col);
                sym->setPen(QPen(Qt::NoPen));

                // create a curve
                QwtPlotCurve *effortCurve = new QwtPlotCurve();
                effortCurve->setSymbol(sym);
                effortCurve->setStyle(QwtPlotCurve::Dots);
                effortCurve->setRenderHint(QwtPlotItem::RenderAntialiased);

                intervalCurves << effortCurve;

                // size for all rides, can increase if needed
                QVector<double> xvals;
                QVector<double> yvals;

                int count=0;
                foreach(RideItem *r, compareDateRanges[j].context->athlete->rideCache->rides()) {

                    // does it match ?
                    if (!compareDateRanges[j].specification.pass(r)) continue;

                    // add the intervals
                    foreach(IntervalItem *i, r->intervals(RideFileInterval::EFFORT)) {

                        // is it a silly value?
                        if (i->getForSymbol("average_power") > 3000) continue;

                        xvals << i->getForSymbol("workout_time") / 60.0f;
                        yvals << i->getForSymbol("average_power");
                        count++;
                    }
                }

                if (count) {
                    // set the data and attach to the plot
                    effortCurve->setSamples(xvals.constData(), yvals.constData(), count);
                    effortCurve->attach(this);
                }
            }
        }
    }

    // X-AXIS

    // if max xvalue not set then default to 6 hours
    if (xmax == 0) xmax = 6 * 60;

    // truncate at an hour for energy mode
    if (criticalSeries == CriticalPowerWindow::work) xmax = 60.0;

    // not interested in short durations for vam
    if (criticalSeries == CriticalPowerWindow::vam) xmin = 4.993;

    // now set the scale
    QwtScaleDiv div((double)xmin, (double)xmax);

    if (criticalSeries == CriticalPowerWindow::work ||
        (criticalSeries == CriticalPowerWindow::kph && xAxisLinearOnSpeed))
        div.setTicks(QwtScaleDiv::MajorTick, LogTimeScaleDraw::ticksEnergy);
    else if (criticalSeries == CriticalPowerWindow::veloclinicplot)
        div.setTicks(QwtScaleDiv::MajorTick, LogTimeScaleDraw::ticksVeloclinic);
    else
        div.setTicks(QwtScaleDiv::MajorTick, LogTimeScaleDraw::ticks);
    setAxisScaleDiv(QwtAxis::XBottom, div);

    // Y-AXIS

    if (!showDelta && rideSeries == RideFile::watts) {

        // set ymax to nearest 100 if power
        int max = ymax * 1.1f;
        max = ((max/100) + 1) * 100;

        if (showPP && max < 1500) max=1500;
        setAxisScale(QwtAxis::YLeft, 0, max);

    } else if (showPP && rideSeries == RideFile::wattsKg) {
        ymax *=1.1;
        if (ymax<20) ymax = 20;

        setAxisScale(QwtAxis::YLeft, 0, ymax);
    } else {

        // or just add 10% headroom
        setAxisScale(QwtAxis::YLeft, ymin *1.1, 1.1*ymax);
    }

    plotPowerProfile();

    // phew
    replot();
}

void
CPPlot::calculateForIntervals(QList<CompareInterval> compareIntervals)
{
    if (rangemode) return;

    // Zap what we got
    clearCurves();
    foreach(QwtPlotCurve *c, intervalCurves) {
        c->detach();
        delete c;
    }
    intervalCurves.clear();

    // If no intervals
    if (compareIntervals.count() == 0) {
        replot(); // show cleared plot
        return;
    }

    // set baseline if we're plotting deltas
    QVector<double> baseline;
    if (showDelta && compareIntervals.count()) {

        // set the baseline data
        baseline = compareIntervals[0].rideFileCache()->meanMaxArray(rideSeries);
    }

    ymax = 0;
    double ymin = 0;
    double xmax = 0;
    double xmin = 1.0f/60.0f - 0.001f;

    // prepare aggregates
    for (int i = 0; i < compareIntervals.size(); ++i) {
        CompareInterval &interval = compareIntervals[i];

        if (interval.isChecked())  {

            // no data ?
            if (interval.rideFileCache()->meanMaxArray(rideSeries).count() == 0) return;

            // create a delta array
            if (showDelta) {

                int n=0;
                QVector<double> deltaArray = interval.rideFileCache()->meanMaxArray(rideSeries);

                // make a delta to baseline
                for (n=1; n < deltaArray.size() && n < baseline.size(); n++) {
                    // stop when we get to zero!
                    if (deltaArray[n] > 0 && baseline[n] > 0)
                        if (showDeltaPercent) deltaArray[n] = 100.00f * (double(deltaArray[n]) - double(baseline[n])) / double(baseline[n]); // delta percentage
                        else deltaArray[n] = deltaArray[n] - baseline[n];
                    else
                        break;
                }
                deltaArray.resize(n-1);

                // now plot using the delta series and NOT the cache
                plotCache(deltaArray, interval.color);

                // set x-axis max
                if ((n/60.00f) > xmax) xmax = n/60.00f;

                foreach(double v, deltaArray) {
                    if (v > ymax) ymax = v;
                    if (v < ymin) ymin = v;
                }

            } else {

                // create curve data arrays
                plotCache(interval.rideFileCache()->meanMaxArray(rideSeries), interval.color);

                // whats ymax ?
                int xCount = 0;
                double vamYMax = 0;
                foreach(double v, interval.rideFileCache()->meanMaxArray(rideSeries)) {
                    // get the biggest y-Axes value
                    if (v > ymax) ymax = v;

                    // VAM x-Axis starts at 4.993 seconds = x-time array point 296 -
                    // so skip the values upto that point in x-Axis time
                    if (rideSeries == RideFile::vam) {
                       // VAM x-Axis starts at 4.993 seconds = x-time array point 296 -
                       // so skip the values upto that point in x-Axis time
                       if (xCount >= 296) {
                           if (v > vamYMax) vamYMax = v;
                       }
                     }
                     xCount++;
                }
                // overwrite ymax for VAM - if a proper value was found
                if (rideSeries == RideFile::vam && vamYMax > 0) ymax = vamYMax;

                double mins = interval.rideFileCache()->meanMaxArray(rideSeries).count() / 60.00f;
                if (mins > xmax) xmax = mins;
            }
        }
    }

    // X-AXIS

    // if max xvalue not set then default to 6 hours
    if (xmax == 0) xmax = 6 * 60;

    // truncate at an hour for energy mode
    if (criticalSeries == CriticalPowerWindow::work) xmax = 60.0;

    // not interested in short durations for vam
    if (criticalSeries == CriticalPowerWindow::vam) xmin = 4.993;

    // now set the scale
    QwtScaleDiv div((double)xmin, (double)xmax);

    if (criticalSeries == CriticalPowerWindow::work ||
        (criticalSeries == CriticalPowerWindow::kph && xAxisLinearOnSpeed))
        div.setTicks(QwtScaleDiv::MajorTick, LogTimeScaleDraw::ticksEnergy);
    else if (criticalSeries == CriticalPowerWindow::veloclinicplot)
        div.setTicks(QwtScaleDiv::MajorTick, LogTimeScaleDraw::ticksVeloclinic);
    else
        div.setTicks(QwtScaleDiv::MajorTick, LogTimeScaleDraw::ticks);
    setAxisScaleDiv(QwtAxis::XBottom, div);

    // Y-AXIS

    if (!showDelta && rideSeries == RideFile::watts) {

        // set ymax to nearest 100 if power
        ymax = ymax * 1.1f;
        ymax = ((ymax/100) + 1) * 100;
        if (showPP && ymax<1500) ymax=1500;

        setAxisScale(QwtAxis::YLeft, ymin, ymax);

    } else if (showPP && rideSeries == RideFile::wattsKg) {
        ymax *=1.1;
        if (ymax<20) ymax = 20;

        setAxisScale(QwtAxis::YLeft, 0, ymax);

    } else {

        // or just add 10% headroom
        setAxisScale(QwtAxis::YLeft, ymin *1.1, 1.1*ymax);
    }

    plotPowerProfile();

    replot();
}

void
CPPlot::plotCache(QVector<double> vector, QColor intervalColor)
{
    // we don't shade if we're plotting in compare mode
    bool wantShadeIntervals = false;
    if ((rangemode && !context->isCompareDateRanges) || (!rangemode && !context->isCompareIntervals))
        wantShadeIntervals = shadeIntervals;

    int maxNonZero=0;
    QVector<double>x;
    QVector<double>y;
    for (int i=1; i<vector.count(); i++) {
        x << double(i)/60.00f;
        y << vector[i];
        if (vector[i] < 0 || vector[i] > 0) maxNonZero = i;
    }
    if (maxNonZero == 0) maxNonZero = y.size();

    // create a curve!
    QwtPlotCurve *curve = new QwtPlotCurve();
    if (appsettings->value(this, GC_ANTIALIAS, true).toBool() == true)
        curve->setRenderHint(QwtPlotItem::RenderAntialiased);

    // set its color - based upon index in intervals!
    QPen pen(intervalColor);
    double width = appsettings->value(this, GC_LINEWIDTH, 0.5*dpiXFactor).toDouble();
    pen.setWidth(width);
    //pen.setStyle(Qt::DotLine);
    intervalColor.setAlpha(64);
    QBrush brush = QBrush(intervalColor);
    if (wantShadeIntervals) curve->setBrush(brush);
    else curve->setBrush(Qt::NoBrush);
    curve->setPen(pen);
    curve->setSamples(x.data(), y.data(), maxNonZero-1);

    // attach and register
    curve->attach(this);

    intervalCurves.append(curve);
    zoomer->setZoomBase(false);
}

QString
CPPlot::kphToString(double kph)
{
    if (GlobalContext::context()->useMetricUnits) {
        return tr("%1 kph").arg(kph, 0, 'f', 1);
    } else {
        return tr("%1 mph").arg(kph*MILES_PER_KM, 0, 'f', 1);
    }
}

QString
CPPlot::kmToString(double km)
{
    if (GlobalContext::context()->useMetricUnits) {
        return tr("%1 km").arg(km, 0, 'f', 3);
    } else {
        return tr("%1 mi").arg(km*MILES_PER_KM, 0, 'f', 3);
    }
}

QString
CPPlot::paceString(double secs, double kph)
{
    QString paceStr;
    const PaceZones *zones = context->athlete->paceZones(sport=="Swim");
    const bool metricPace = zones ? appsettings->value(this, zones->paceSetting(), GlobalContext::context()->useMetricUnits).toBool() : GlobalContext::context()->useMetricUnits;

    if (sport == "Run" || sport == "Swim") {

        if (zones) paceStr = QString("\n%1 %2").arg(zones->kphToPaceString(kph, metricPace))
                                               .arg(zones->paceUnits(metricPace));
    } else if (sport == "Row") {

        paceStr = QString("\n%1 %2").arg(kphToPace(kph*2, true, false)).arg(tr("min/500m"));
    }

    const double km = kph*secs/3600.0; // distance in km
    if (sport == "Swim") {

        if (metricPace) paceStr += tr("\n%1 m").arg(1000*km, 0, 'f', 0);
        else paceStr += tr("\n%1 yd").arg(1000*km/METERS_PER_YARD, 0, 'f', 0);

    } else {

        if (metricPace) paceStr += tr("\n%1 km").arg(km, 0, 'f', 3);
        else  paceStr += tr("\n%1 mi").arg(MILES_PER_KM*km, 0, 'f', 3);

    }
    return paceStr;
}
