Files
GoldenCheetah/src/AllPlot.cpp
Mark Liversedge f3af380ff1 Fix interval highlighter when no power
The interval highlighter on the ride plot
uses the same axis as power, but if no
power is present in the activity the axis
is hidden, and therefore so is the interval
highlighter.

This patch will set the interval highlighter
curve axis to one that is in use.
2011-10-17 19:50:31 +01:00

1189 lines
38 KiB
C++

/*
* Copyright (c) 2006 Sean C. Rhea (srhea@srhea.net)
*
* 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 "AllPlot.h"
#include "MainWindow.h"
#include "AllPlotWindow.h"
#include "RideFile.h"
#include "RideItem.h"
#include "IntervalItem.h"
#include "Settings.h"
#include "Units.h"
#include "Zones.h"
#include "Colors.h"
#include <assert.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_plot_layout.h>
#include <qwt_plot_marker.h>
#include <qwt_scale_div.h>
#include <qwt_scale_widget.h>
#include <qwt_valuelist.h>
#include <qwt_text.h>
#include <qwt_legend.h>
#include <qwt_data.h>
#include <QMultiMap>
class IntervalPlotData : public QwtData
{
public:
IntervalPlotData(AllPlot *allPlot, MainWindow *mainWindow) :
allPlot(allPlot), mainWindow(mainWindow) {}
double x(size_t i) const ;
double y(size_t i) const ;
size_t size() const ;
virtual QwtData *copy() const ;
void init() ;
IntervalItem *intervalNum(int n) const;
int intervalCount() const;
AllPlot *allPlot;
MainWindow *mainWindow;
};
// define a background class to handle shading of power zones
// draws power zone bands IF zones are defined and the option
// to draw bonds has been selected
class AllPlotBackground: public QwtPlotItem
{
private:
AllPlot *parent;
public:
AllPlotBackground(AllPlot *_parent)
{
setZ(0.0);
parent = _parent;
}
virtual int rtti() const
{
return QwtPlotItem::Rtti_PlotUserItem;
}
virtual void draw(QPainter *painter,
const QwtScaleMap &, const QwtScaleMap &yMap,
const QRect &rect) const
{
RideItem *rideItem = parent->rideItem;
if (! rideItem)
return;
const Zones *zones = rideItem->zones;
int zone_range = rideItem->zoneRange();
if (parent->shadeZones() && (zone_range >= 0)) {
QList <int> zone_lows = zones->getZoneLows(zone_range);
int num_zones = zone_lows.size();
if (num_zones > 0) {
for (int z = 0; z < num_zones; z ++) {
QRect r = rect;
QColor shading_color = zoneColor(z, num_zones);
shading_color.setHsv(
shading_color.hue(),
shading_color.saturation() / 4,
shading_color.value()
);
r.setBottom(yMap.transform(zone_lows[z]));
if (z + 1 < num_zones)
r.setTop(yMap.transform(zone_lows[z + 1]));
if (r.top() <= r.bottom())
painter->fillRect(r, shading_color);
}
}
} else {
}
}
};
// Zone labels are drawn if power zone bands are enabled, automatically
// at the center of the plot
class AllPlotZoneLabel: public QwtPlotItem
{
private:
AllPlot *parent;
int zone_number;
double watts;
QwtText text;
public:
AllPlotZoneLabel(AllPlot *_parent, int _zone_number)
{
parent = _parent;
zone_number = _zone_number;
RideItem *rideItem = parent->rideItem;
if (! rideItem)
return;
const Zones *zones = rideItem->zones;
int zone_range = rideItem->zoneRange();
// create new zone labels if we're shading
if (parent->shadeZones() && (zone_range >= 0)) {
QList <int> zone_lows = zones->getZoneLows(zone_range);
QList <QString> zone_names = zones->getZoneNames(zone_range);
int num_zones = zone_lows.size();
assert(zone_names.size() == num_zones);
if (zone_number < num_zones) {
watts =
(
(zone_number + 1 < num_zones) ?
0.5 * (zone_lows[zone_number] + zone_lows[zone_number + 1]) :
(
(zone_number > 0) ?
(1.5 * zone_lows[zone_number] - 0.5 * zone_lows[zone_number - 1]) :
2.0 * zone_lows[zone_number]
)
);
text = QwtText(zone_names[zone_number]);
if (_parent->referencePlot == NULL) {
text.setFont(QFont("Helvetica",24, QFont::Bold));
} else {
text.setFont(QFont("Helvetica",12, QFont::Bold));
}
QColor text_color = zoneColor(zone_number, num_zones);
text_color.setAlpha(64);
text.setColor(text_color);
}
}
setZ(1.0 + zone_number / 100.0);
}
virtual int rtti() const
{
return QwtPlotItem::Rtti_PlotUserItem;
}
void draw(QPainter *painter,
const QwtScaleMap &, const QwtScaleMap &yMap,
const QRect &rect) const
{
if (parent->shadeZones()) {
int x = (rect.left() + rect.right()) / 2;
int y = yMap.transform(watts);
// the following code based on source for QwtPlotMarker::draw()
QRect tr(QPoint(0, 0), text.textSize(painter->font()));
tr.moveCenter(QPoint(x, y));
text.draw(painter, tr);
}
}
};
static inline double
max(double a, double b) { if (a > b) return a; else return b; }
AllPlot::AllPlot(AllPlotWindow *parent, MainWindow *mainWindow):
QwtPlot(parent),
rideItem(NULL),
unit(0),
shade_zones(true),
showPowerState(3),
showHrState(Qt::Checked),
showSpeedState(Qt::Checked),
showCadState(Qt::Checked),
showAltState(Qt::Checked),
bydist(false),
parent(parent)
{
setInstanceName("AllPlot");
unit = appsettings->value(this, GC_UNIT);
referencePlot = NULL;
useMetricUnits = (unit.toString() == "Metric");
if (appsettings->value(this, GC_SHADEZONES, true).toBool()==false)
shade_zones = false;
if (smooth < 1) smooth = 1;
// create a background object for shading
bg = new AllPlotBackground(this);
bg->attach(this);
//insertLegend(new QwtLegend(), QwtPlot::BottomLegend);
setCanvasBackground(GColor(CRIDEPLOTBACKGROUND));
canvas()->setFrameStyle(QFrame::NoFrame);
setXTitle();
wattsCurve = new QwtPlotCurve(tr("Power"));
wattsCurve->setYAxis(yLeft);
hrCurve = new QwtPlotCurve(tr("Heart Rate"));
hrCurve->setYAxis(yLeft2);
speedCurve = new QwtPlotCurve(tr("Speed"));
speedCurve->setYAxis(yRight);
cadCurve = new QwtPlotCurve(tr("Cadence"));
cadCurve->setYAxis(yLeft2);
altCurve = new QwtPlotCurve(tr("Altitude"));
// altCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
altCurve->setYAxis(yRight2);
intervalHighlighterCurve = new QwtPlotCurve();
intervalHighlighterCurve->setYAxis(yLeft);
intervalHighlighterCurve->setData(IntervalPlotData(this, mainWindow));
intervalHighlighterCurve->attach(this);
//this->legend()->remove(intervalHighlighterCurve); // don't show in legend
// setup that grid
grid = new QwtPlotGrid();
grid->enableX(true);
grid->enableY(true);
grid->attach(this);
// get rid of nasty blank space on right of the plot
plotLayout()->setAlignCanvasToScales(true);
setAxisMaxMinor(xBottom, 0);
setAxisMaxMinor(yLeft, 0);
setAxisMaxMinor(yLeft2, 0);
setAxisMaxMinor(yRight, 0);
setAxisMaxMinor(yRight2, 0);
configChanged(); // set colors
}
void
AllPlot::configChanged()
{
double width = appsettings->value(this, GC_LINEWIDTH, 2.0).toDouble();
if (appsettings->value(this, GC_ANTIALIAS, false).toBool() == true) {
wattsCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
hrCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
speedCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
cadCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
altCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
intervalHighlighterCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
}
setCanvasBackground(GColor(CRIDEPLOTBACKGROUND));
QPen wattsPen = QPen(GColor(CPOWER));
wattsPen.setWidth(width);
wattsCurve->setPen(wattsPen);
QPen hrPen = QPen(GColor(CHEARTRATE));
hrPen.setWidth(width);
hrCurve->setPen(hrPen);
QPen speedPen = QPen(GColor(CSPEED));
speedPen.setWidth(width);
speedCurve->setPen(speedPen);
QPen cadPen = QPen(GColor(CCADENCE));
cadPen.setWidth(width);
cadCurve->setPen(cadPen);
QPen altPen(GColor(CALTITUDE));
altPen.setWidth(width);
altCurve->setPen(altPen);
QColor brush_color = GColor(CALTITUDEBRUSH);
brush_color.setAlpha(200);
altCurve->setBrush(brush_color); // fill below the line
QPen ihlPen = QPen(GColor(CINTERVALHIGHLIGHTER));
ihlPen.setWidth(width);
intervalHighlighterCurve->setPen(ihlPen);
QColor ihlbrush = QColor(GColor(CINTERVALHIGHLIGHTER));
ihlbrush.setAlpha(64);
intervalHighlighterCurve->setBrush(ihlbrush); // fill below the line
//this->legend()->remove(intervalHighlighterCurve); // don't show in legend
QPen gridPen(GColor(CPLOTGRID));
gridPen.setStyle(Qt::DotLine);
grid->setPen(gridPen);
// curve brushes
if (parent->isPaintBrush()) {
QColor p;
p = wattsCurve->pen().color();
p.setAlpha(64);
wattsCurve->setBrush(QBrush(p));
p = hrCurve->pen().color();
p.setAlpha(64);
hrCurve->setBrush(QBrush(p));
p = speedCurve->pen().color();
p.setAlpha(64);
speedCurve->setBrush(QBrush(p));
p = cadCurve->pen().color();
p.setAlpha(64);
cadCurve->setBrush(QBrush(p));
} else {
wattsCurve->setBrush(Qt::NoBrush);
hrCurve->setBrush(Qt::NoBrush);
speedCurve->setBrush(Qt::NoBrush);
cadCurve->setBrush(Qt::NoBrush);
}
QPalette pal;
// tick draw
QwtScaleDraw *sd = new QwtScaleDraw;
sd->setTickLength(QwtScaleDiv::MajorTick, 3);
setAxisScaleDraw(QwtPlot::xBottom, sd);
sd = new QwtScaleDraw;
sd->setTickLength(QwtScaleDiv::MajorTick, 3);
setAxisScaleDraw(QwtPlot::yLeft, sd);
pal.setColor(QPalette::WindowText, GColor(CPOWER));
pal.setColor(QPalette::Text, GColor(CPOWER));
axisWidget(QwtPlot::yLeft)->setPalette(pal);
sd = new QwtScaleDraw;
sd->setTickLength(QwtScaleDiv::MajorTick, 3);
setAxisScaleDraw(QwtPlot::yLeft2, sd);
pal.setColor(QPalette::WindowText, GColor(CHEARTRATE));
pal.setColor(QPalette::Text, GColor(CHEARTRATE));
axisWidget(QwtPlot::yLeft2)->setPalette(pal);
sd = new QwtScaleDraw;
sd->setTickLength(QwtScaleDiv::MajorTick, 3);
setAxisScaleDraw(QwtPlot::yRight, sd);
pal.setColor(QPalette::WindowText, GColor(CSPEED));
pal.setColor(QPalette::Text, GColor(CSPEED));
axisWidget(QwtPlot::yRight)->setPalette(pal);
sd = new QwtScaleDraw;
sd->setTickLength(QwtScaleDiv::MajorTick, 3);
setAxisScaleDraw(QwtPlot::yRight2, sd);
pal.setColor(QPalette::WindowText, GColor(CALTITUDE));
pal.setColor(QPalette::Text, GColor(CALTITUDE));
axisWidget(QwtPlot::yRight2)->setPalette(pal);
}
struct DataPoint {
double time, hr, watts, speed, cad, alt;
DataPoint(double t, double h, double w, double s, double c, double a) :
time(t), hr(h), watts(w), speed(s), cad(c), alt(a) {}
};
bool AllPlot::shadeZones() const
{
return shade_zones;
}
void
AllPlot::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.setFont(stGiles);
QwtPlot::setAxisFont(axis, stGiles);
QwtPlot::setAxisTitle(axis, title);
}
void AllPlot::refreshZoneLabels()
{
foreach(AllPlotZoneLabel *label, zoneLabels) {
label->detach();
delete label;
}
zoneLabels.clear();
if (rideItem) {
int zone_range = rideItem->zoneRange();
const Zones *zones = rideItem->zones;
// generate labels for existing zones
if (zone_range >= 0) {
int num_zones = zones->numZones(zone_range);
for (int z = 0; z < num_zones; z ++) {
AllPlotZoneLabel *label = new AllPlotZoneLabel(this, z);
label->attach(this);
zoneLabels.append(label);
}
}
}
}
void
AllPlot::recalc()
{
if (referencePlot !=NULL){
return;
}
if (timeArray.empty())
return;
int rideTimeSecs = (int) ceil(timeArray[arrayLength - 1]);
if (rideTimeSecs > 7*24*60*60) {
QwtArray<double> data;
if (!wattsArray.empty())
wattsCurve->setData(data, data);
if (!hrArray.empty())
hrCurve->setData(data, data);
if (!speedArray.empty())
speedCurve->setData(data, data);
if (!cadArray.empty())
cadCurve->setData(data, data);
if (!altArray.empty())
altCurve->setData(data, data);
return;
}
// we should only smooth the curves if smoothed rate is greater than sample rate
if (smooth > rideItem->ride()->recIntSecs()) {
double totalWatts = 0.0;
double totalHr = 0.0;
double totalSpeed = 0.0;
double totalCad = 0.0;
double totalDist = 0.0;
double totalAlt = 0.0;
QList<DataPoint> list;
smoothWatts.resize(rideTimeSecs + 1); //(rideTimeSecs + 1);
smoothHr.resize(rideTimeSecs + 1);
smoothSpeed.resize(rideTimeSecs + 1);
smoothCad.resize(rideTimeSecs + 1);
smoothTime.resize(rideTimeSecs + 1);
smoothDistance.resize(rideTimeSecs + 1);
smoothAltitude.resize(rideTimeSecs + 1);
for (int secs = 0; ((secs < smooth)
&& (secs < rideTimeSecs)); ++secs) {
smoothWatts[secs] = 0.0;
smoothHr[secs] = 0.0;
smoothSpeed[secs] = 0.0;
smoothCad[secs] = 0.0;
smoothTime[secs] = secs / 60.0;
smoothDistance[secs] = 0.0;
smoothAltitude[secs] = 0.0;
}
int i = 0;
for (int secs = smooth; secs <= rideTimeSecs; ++secs) {
while ((i < arrayLength) && (timeArray[i] <= secs)) {
DataPoint dp(timeArray[i],
(!hrArray.empty() ? hrArray[i] : 0),
(!wattsArray.empty() ? wattsArray[i] : 0),
(!speedArray.empty() ? speedArray[i] : 0),
(!cadArray.empty() ? cadArray[i] : 0),
(!altArray.empty() ? altArray[i] : 0));
if (!wattsArray.empty())
totalWatts += wattsArray[i];
if (!hrArray.empty())
totalHr += hrArray[i];
if (!speedArray.empty())
totalSpeed += speedArray[i];
if (!cadArray.empty())
totalCad += cadArray[i];
if (!altArray.empty())
totalAlt += altArray[i];
totalDist = distanceArray[i];
list.append(dp);
++i;
}
while (!list.empty() && (list.front().time < secs - smooth)) {
DataPoint &dp = list.front();
totalWatts -= dp.watts;
totalHr -= dp.hr;
totalSpeed -= dp.speed;
totalCad -= dp.cad;
totalAlt -= dp.alt;
list.removeFirst();
}
// TODO: this is wrong. We should do a weighted average over the
// seconds represented by each point...
if (list.empty()) {
smoothWatts[secs] = 0.0;
smoothHr[secs] = 0.0;
smoothSpeed[secs] = 0.0;
smoothCad[secs] = 0.0;
smoothAltitude[secs] = smoothAltitude[secs - 1];
}
else {
smoothWatts[secs] = totalWatts / list.size();
smoothHr[secs] = totalHr / list.size();
smoothSpeed[secs] = totalSpeed / list.size();
smoothCad[secs] = totalCad / list.size();
smoothAltitude[secs] = totalAlt / list.size();
}
smoothDistance[secs] = totalDist;
smoothTime[secs] = secs / 60.0;
}
} else {
// no smoothing .. just raw data
smoothWatts.resize(0);
smoothHr.resize(0);
smoothSpeed.resize(0);
smoothCad.resize(0);
smoothTime.resize(0);
smoothDistance.resize(0);
smoothAltitude.resize(0);
foreach (RideFilePoint *dp, rideItem->ride()->dataPoints()) {
smoothWatts.append(dp->watts);
smoothHr.append(dp->hr);
smoothSpeed.append(useMetricUnits ? dp->kph : dp->kph * MILES_PER_KM);
smoothCad.append(dp->cad);
smoothTime.append(dp->secs/60);
smoothDistance.append(useMetricUnits ? dp->km : dp->km * MILES_PER_KM);
smoothAltitude.append(useMetricUnits ? dp->alt : dp->alt * FEET_PER_METER);
}
}
QVector<double> &xaxis = bydist ? smoothDistance : smoothTime;
int startingIndex = qMin(smooth, xaxis.count());
int totalPoints = xaxis.count() - startingIndex;
// set curves - we set the intervalHighlighter to whichver is available
if (!wattsArray.empty()) {
wattsCurve->setData(xaxis.data() + startingIndex, smoothWatts.data() + startingIndex, totalPoints);
intervalHighlighterCurve->setYAxis(yLeft);
} if (!hrArray.empty()) {
hrCurve->setData(xaxis.data() + startingIndex, smoothHr.data() + startingIndex, totalPoints);
intervalHighlighterCurve->setYAxis(yLeft2);
} if (!speedArray.empty()) {
speedCurve->setData(xaxis.data() + startingIndex, smoothSpeed.data() + startingIndex, totalPoints);
intervalHighlighterCurve->setYAxis(yRight);
} if (!cadArray.empty()) {
cadCurve->setData(xaxis.data() + startingIndex, smoothCad.data() + startingIndex, totalPoints);
intervalHighlighterCurve->setYAxis(yLeft2);
} if (!altArray.empty()) {
altCurve->setData(xaxis.data() + startingIndex, smoothAltitude.data() + startingIndex, totalPoints);
intervalHighlighterCurve->setYAxis(yRight2);
}
setYMax();
refreshIntervalMarkers();
refreshZoneLabels();
//replot();
}
void
AllPlot::refreshIntervalMarkers()
{
foreach(QwtPlotMarker *mrk, d_mrk) {
mrk->detach();
delete mrk;
}
d_mrk.clear();
QRegExp wkoAuto("^(Peak *[0-9]*(s|min)|Entire workout|Find #[0-9]*) *\\([^)]*\\)$");
if (rideItem->ride()) {
foreach(const RideFileInterval &interval, rideItem->ride()->intervals()) {
// skip WKO autogenerated peak intervals
if (wkoAuto.exactMatch(interval.name))
continue;
QwtPlotMarker *mrk = new QwtPlotMarker;
d_mrk.append(mrk);
mrk->attach(this);
mrk->setLineStyle(QwtPlotMarker::VLine);
mrk->setLabelAlignment(Qt::AlignRight | Qt::AlignTop);
mrk->setLinePen(QPen(GColor(CPLOTMARKER), 0, Qt::DashDotLine));
QwtText text(interval.name);
text.setFont(QFont("Helvetica", 10, QFont::Bold));
text.setColor(GColor(CPLOTMARKER));
if (!bydist)
mrk->setValue(interval.start / 60.0, 0.0);
else
mrk->setValue((useMetricUnits ? 1 : MILES_PER_KM) *
rideItem->ride()->timeToDistance(interval.start), 0.0);
mrk->setLabel(text);
}
}
}
void
AllPlot::setYMax()
{
if (wattsCurve->isVisible()) {
double maxY = (referencePlot == NULL) ? (1.05 * wattsCurve->maxYValue()) :
(1.05 * referencePlot->wattsCurve->maxYValue());
// grid lines for 100 or 25
QwtValueList xytick[QwtScaleDiv::NTickTypes];
for (int i=0;i<maxY;i+=100)
xytick[QwtScaleDiv::MajorTick]<<i;
#if 0
for (int i=0;i<maxY;i+=25)
xytick[QwtScaleDiv::MinorTick]<<i;
#endif
setAxisTitle(yLeft, "Watts");
//setAxisScale(yLeft, 0.0, maxY);
setAxisScaleDiv(QwtPlot::yLeft,QwtScaleDiv(0.0,maxY,xytick));
setAxisLabelRotation(yLeft,270);
setAxisLabelAlignment(yLeft,Qt::AlignVCenter);
}
if (hrCurve->isVisible() || cadCurve->isVisible()) {
double ymax = 0;
QStringList labels;
if (hrCurve->isVisible()) {
labels << "BPM";
if (referencePlot == NULL)
ymax = hrCurve->maxYValue();
else
ymax = referencePlot->hrCurve->maxYValue();
}
if (cadCurve->isVisible()) {
labels << "RPM";
if (referencePlot == NULL)
ymax = qMax(ymax, cadCurve->maxYValue());
else
ymax = qMax(ymax, referencePlot->cadCurve->maxYValue());
}
setAxisTitle(yLeft2, labels.join(" / "));
setAxisScale(yLeft2, 0.0, 1.05 * ymax);
setAxisLabelRotation(yLeft2,270);
setAxisLabelAlignment(yLeft2,Qt::AlignVCenter);
}
if (speedCurve->isVisible()) {
setAxisTitle(yRight, (useMetricUnits ? tr("KPH") : tr("MPH")));
if (referencePlot == NULL)
setAxisScale(yRight, 0.0, 1.05 * speedCurve->maxYValue());
else
setAxisScale(yRight, 0.0, 1.05 * referencePlot->speedCurve->maxYValue());
setAxisLabelRotation(yRight,90);
setAxisLabelAlignment(yRight,Qt::AlignVCenter);
}
if (altCurve->isVisible()) {
setAxisTitle(yRight2, useMetricUnits ? tr("Meters") : tr("Feet"));
double ymin,ymax;
if (referencePlot == NULL) {
ymin = altCurve->minYValue();
ymax = qMax(ymin + 100, 1.05 * altCurve->maxYValue());
} else {
ymin = referencePlot->altCurve->minYValue();
ymax = qMax(ymin + 100, 1.05 * referencePlot->altCurve->maxYValue());
}
setAxisScale(yRight2, ymin, ymax);
setAxisLabelRotation(yRight2,90);
setAxisLabelAlignment(yRight2,Qt::AlignVCenter);
altCurve->setBaseline(ymin);
}
enableAxis(yLeft, wattsCurve->isVisible());
enableAxis(yLeft2, hrCurve->isVisible() || cadCurve->isVisible());
enableAxis(yRight, speedCurve->isVisible());
enableAxis(yRight2, altCurve->isVisible());
}
void
AllPlot::setXTitle()
{
if (bydist)
setAxisTitle(xBottom, tr("Distance ")+QString(unit.toString() == "Metric"?"(km)":"(miles)"));
else
setAxisTitle(xBottom, tr("Time (minutes)"));
}
void
AllPlot::setDataFromPlot(AllPlot *plot, int startidx, int stopidx)
{
if (plot == NULL) {
rideItem = NULL;
return;
}
referencePlot = plot;
// You got to give me some data first!
if (!plot->distanceArray.count() || !plot->timeArray.count()) return;
// reference the plot for data and state
rideItem = plot->rideItem;
bydist = plot->bydist;
arrayLength = stopidx-startidx;
if (bydist) {
startidx = plot->distanceIndex(plot->distanceArray[startidx]);
stopidx = plot->distanceIndex(plot->distanceArray[(stopidx>=plot->distanceArray.size()?plot->distanceArray.size()-1:stopidx)])-1;
} else {
startidx = plot->timeIndex(plot->timeArray[startidx]/60);
stopidx = plot->timeIndex(plot->timeArray[(stopidx>=plot->timeArray.size()?plot->timeArray.size()-1:stopidx)]/60)-1;
}
// make sure indexes are still valid
if (startidx > stopidx || startidx < 0 || stopidx < 0) return;
double *smoothW = &plot->smoothWatts[startidx];
double *smoothT = &plot->smoothTime[startidx];
double *smoothHR = &plot->smoothHr[startidx];
double *smoothS = &plot->smoothSpeed[startidx];
double *smoothC = &plot->smoothCad[startidx];
double *smoothA = &plot->smoothAltitude[startidx];
double *smoothD = &plot->smoothDistance[startidx];
double *xaxis = bydist ? smoothD : smoothT;
// attach appropriate curves
//if (this->legend()) this->legend()->hide();
wattsCurve->detach();
hrCurve->detach();
speedCurve->detach();
cadCurve->detach();
altCurve->detach();
wattsCurve->setVisible(rideItem->ride()->areDataPresent()->watts && showPowerState < 2);
hrCurve->setVisible(rideItem->ride()->areDataPresent()->hr && showHrState == Qt::Checked);
speedCurve->setVisible(rideItem->ride()->areDataPresent()->kph && showSpeedState == Qt::Checked);
cadCurve->setVisible(rideItem->ride()->areDataPresent()->cad && showCadState == Qt::Checked);
altCurve->setVisible(rideItem->ride()->areDataPresent()->alt && showAltState == Qt::Checked);
wattsCurve->setData(xaxis,smoothW,stopidx-startidx);
hrCurve->setData(xaxis, smoothHR,stopidx-startidx);
speedCurve->setData(xaxis, smoothS, stopidx-startidx);
cadCurve->setData(xaxis, smoothC, stopidx-startidx);
altCurve->setData(xaxis, smoothA, stopidx-startidx);
QwtSymbol sym;
sym.setPen(QPen(GColor(CPLOTMARKER)));
if (stopidx-startidx < 150) {
sym.setStyle(QwtSymbol::Ellipse);
sym.setSize(3);
} else {
sym.setStyle(QwtSymbol::NoSymbol);
sym.setSize(0);
}
wattsCurve->setSymbol(sym);
hrCurve->setSymbol(sym);
speedCurve->setSymbol(sym);
cadCurve->setSymbol(sym);
altCurve->setSymbol(sym);
setYMax();
setAxisScale(xBottom, xaxis[0], xaxis[stopidx-startidx-1]);
if (!plot->smoothAltitude.empty()) {
altCurve->attach(this);
intervalHighlighterCurve->setYAxis(yRight2);
}
if (!plot->smoothWatts.empty()) {
wattsCurve->attach(this);
intervalHighlighterCurve->setYAxis(yLeft);
}
if (!plot->smoothHr.empty()) {
hrCurve->attach(this);
intervalHighlighterCurve->setYAxis(yLeft2);
}
if (!plot->smoothSpeed.empty()) {
speedCurve->attach(this);
intervalHighlighterCurve->setYAxis(yRight);
}
if (!plot->smoothCad.empty()) {
cadCurve->attach(this);
intervalHighlighterCurve->setYAxis(yLeft2);
}
refreshIntervalMarkers();
refreshZoneLabels();
//if (this->legend()) this->legend()->show();
//replot();
}
void
AllPlot::setDataFromRide(RideItem *_rideItem)
{
rideItem = _rideItem;
if (_rideItem == NULL) return;
wattsArray.clear();
RideFile *ride = rideItem->ride();
if (ride && ride->deviceType() != QString("Manual CSV")) {
const RideFileDataPresent *dataPresent = ride->areDataPresent();
int npoints = ride->dataPoints().size();
wattsArray.resize(dataPresent->watts ? npoints : 0);
hrArray.resize(dataPresent->hr ? npoints : 0);
speedArray.resize(dataPresent->kph ? npoints : 0);
cadArray.resize(dataPresent->cad ? npoints : 0);
altArray.resize(dataPresent->alt ? npoints : 0);
timeArray.resize(npoints);
distanceArray.resize(npoints);
// attach appropriate curves
wattsCurve->detach();
hrCurve->detach();
speedCurve->detach();
cadCurve->detach();
altCurve->detach();
if (!altArray.empty()) altCurve->attach(this);
if (!wattsArray.empty()) wattsCurve->attach(this);
if (!hrArray.empty()) hrCurve->attach(this);
if (!speedArray.empty()) speedCurve->attach(this);
if (!cadArray.empty()) cadCurve->attach(this);
wattsCurve->setVisible(dataPresent->watts && showPowerState < 2);
hrCurve->setVisible(dataPresent->hr && showHrState == Qt::Checked);
speedCurve->setVisible(dataPresent->kph && showSpeedState == Qt::Checked);
cadCurve->setVisible(dataPresent->cad && showCadState == Qt::Checked);
altCurve->setVisible(dataPresent->alt && showAltState == Qt::Checked);
arrayLength = 0;
foreach (const RideFilePoint *point, ride->dataPoints()) {
// we round the time to nearest 100th of a second
// before adding to the array, to avoid situation
// where 'high precision' time slice is an artefact
// of double precision or slight timing anomalies
// e.g. where realtime gives timestamps like
// 940.002 followed by 940.998 and were previouslt
// both rounded to 940s
//
// NOTE: this rounding mechanism is identical to that
// used by the Ride Editor.
double secs = floor(point->secs);
double msecs = round((point->secs - secs) * 100) * 10;
timeArray[arrayLength] = secs + msecs/1000;
if (!wattsArray.empty())
wattsArray[arrayLength] = max(0, point->watts);
if (!hrArray.empty())
hrArray[arrayLength] = max(0, point->hr);
if (!speedArray.empty())
speedArray[arrayLength] = max(0,
(useMetricUnits
? point->kph
: point->kph * MILES_PER_KM));
if (!cadArray.empty())
cadArray[arrayLength] = max(0, point->cad);
if (!altArray.empty())
altArray[arrayLength] = (useMetricUnits
? point->alt
: point->alt * FEET_PER_METER);
distanceArray[arrayLength] = max(0,
(useMetricUnits
? point->km
: point->km * MILES_PER_KM));
++arrayLength;
}
recalc();
}
else {
//setTitle("no data");
wattsCurve->detach();
hrCurve->detach();
speedCurve->detach();
cadCurve->detach();
altCurve->detach();
foreach(QwtPlotMarker *mrk, d_mrk)
delete mrk;
d_mrk.clear();
}
}
void
AllPlot::showPower(int id)
{
if (showPowerState == id) return;
showPowerState = id;
wattsCurve->setVisible(id < 2);
shade_zones = (id == 0);
setYMax();
if (shade_zones) {
bg->attach(this);
refreshZoneLabels();
} else
bg->detach();
}
void
AllPlot::showHr(int state)
{
showHrState = state;
assert(state != Qt::PartiallyChecked);
hrCurve->setVisible(state == Qt::Checked);
setYMax();
replot();
}
void
AllPlot::showSpeed(int state)
{
showSpeedState = state;
assert(state != Qt::PartiallyChecked);
speedCurve->setVisible(state == Qt::Checked);
setYMax();
replot();
}
void
AllPlot::showCad(int state)
{
showCadState = state;
assert(state != Qt::PartiallyChecked);
cadCurve->setVisible(state == Qt::Checked);
setYMax();
replot();
}
void
AllPlot::showAlt(int state)
{
showAltState = state;
assert(state != Qt::PartiallyChecked);
altCurve->setVisible(state == Qt::Checked);
setYMax();
replot();
}
void
AllPlot::showGrid(int state)
{
assert(state != Qt::PartiallyChecked);
grid->setVisible(state == Qt::Checked);
replot();
}
void
AllPlot::setPaintBrush(int state)
{
if (state) {
QColor p;
p = wattsCurve->pen().color();
p.setAlpha(64);
wattsCurve->setBrush(QBrush(p));
p = hrCurve->pen().color();
p.setAlpha(64);
hrCurve->setBrush(QBrush(p));
p = speedCurve->pen().color();
p.setAlpha(64);
speedCurve->setBrush(QBrush(p));
p = cadCurve->pen().color();
p.setAlpha(64);
cadCurve->setBrush(QBrush(p));
} else {
wattsCurve->setBrush(Qt::NoBrush);
hrCurve->setBrush(Qt::NoBrush);
speedCurve->setBrush(Qt::NoBrush);
cadCurve->setBrush(Qt::NoBrush);
}
replot();
}
void
AllPlot::setSmoothing(int value)
{
smooth = value;
recalc();
}
void
AllPlot::setByDistance(int id)
{
bydist = (id == 1);
setXTitle();
recalc();
}
struct ComparePoints {
bool operator()(const double p1, const double p2) {
return p1 < p2;
}
};
int
AllPlot::timeIndex(double min) const
{
// return index offset for specified time
QVector<double>::const_iterator i = std::lower_bound(
smoothTime.begin(), smoothTime.end(), min, ComparePoints());
if (i == smoothTime.end())
return smoothTime.size();
return i - smoothTime.begin();
}
int
AllPlot::distanceIndex(double km) const
{
// return index offset for specified distance in km
QVector<double>::const_iterator i = std::lower_bound(
smoothDistance.begin(), smoothDistance.end(), km, ComparePoints());
if (i == smoothDistance.end())
return smoothDistance.size();
return i - smoothDistance.begin();
}
/*----------------------------------------------------------------------
* Interval plotting
*--------------------------------------------------------------------*/
/*
* HELPER FUNCTIONS:
* intervalNum - returns a pointer to the nth selected interval
* intervalCount - returns the number of highlighted intervals
*/
// note this is operating on the children of allIntervals and not the
// intervalWidget (QTreeWidget) -- this is why we do not use the
// selectedItems() member. N starts a one not zero.
IntervalItem *IntervalPlotData::intervalNum(int n) const
{
int highlighted=0;
const QTreeWidgetItem *allIntervals = mainWindow->allIntervalItems();
for (int i=0; i<allIntervals->childCount(); i++) {
IntervalItem *current = (IntervalItem *)allIntervals->child(i);
if (current != NULL) {
if (current->isSelected() == true) ++highlighted;
} else {
return NULL;
}
if (highlighted == n) return current;
}
return NULL;
}
// how many intervals selected?
int IntervalPlotData::intervalCount() const
{
int highlighted;
highlighted = 0;
if (mainWindow->allIntervalItems() == NULL) return 0; // not inited yet!
const QTreeWidgetItem *allIntervals = mainWindow->allIntervalItems();
for (int i=0; i<allIntervals->childCount(); i++) {
IntervalItem *current = (IntervalItem *)allIntervals->child(i);
if (current != NULL) {
if (current->isSelected() == true) {
++highlighted;
}
}
}
return highlighted;
}
/*
* INTERVAL HIGHLIGHTING CURVE
* IntervalPlotData - implements the qwtdata interface where
* x,y return point co-ordinates and
* size returns the number of points
*/
// The interval curve data is derived from the intervals that have
// been selected in the MainWindow leftlayout for each selected
// interval we return 4 data points; bottomleft, topleft, topright
// and bottom right.
//
// the points correspond to:
// bottom left = interval start, 0 watts
// top left = interval start, maxwatts
// top right = interval stop, maxwatts
// bottom right = interval stop, 0 watts
//
double IntervalPlotData::x(size_t i) const
{
// for each interval there are four points, which interval is this for?
int interval = i ? i/4 : 0;
interval += 1; // interval numbers start at 1 not ZERO in the utility functions
double multiplier = allPlot->useMetricUnits ? 1 : MILES_PER_KM;
// get the interval
IntervalItem *current = intervalNum(interval);
if (current == NULL) return 0; // out of bounds !?
// which point are we returning?
switch (i%4) {
case 0 : return allPlot->bydist ? multiplier * current->startKM : current->start/60; // bottom left
case 1 : return allPlot->bydist ? multiplier * current->startKM : current->start/60; // top left
case 2 : return allPlot->bydist ? multiplier * current->stopKM : current->stop/60; // bottom right
case 3 : return allPlot->bydist ? multiplier * current->stopKM : current->stop/60; // bottom right
}
return 0; // shouldn't get here, but keeps compiler happy
}
double IntervalPlotData::y(size_t i) const
{
// which point are we returning?
switch (i%4) {
case 0 : return -100; // bottom left
case 1 : return 5000; // top left - set to out of bound value
case 2 : return 5000; // top right - set to out of bound value
case 3 : return -100; // bottom right
}
return 0;
}
size_t IntervalPlotData::size() const { return intervalCount()*4; }
QwtData *IntervalPlotData::copy() const {
return new IntervalPlotData(allPlot, mainWindow);
}
void
AllPlot::pointHover(QwtPlotCurve *curve, int index)
{
if (index >= 0 && curve != intervalHighlighterCurve) {
double yvalue = curve->y(index);
double xvalue = curve->x(index);
// output the tooltip
QString text = QString("%1 %2\n%3 %4")
.arg(yvalue, 0, 'f', 0)
.arg(this->axisTitle(curve->yAxis()).text())
.arg(xvalue, 0, 'f', 2)
.arg(this->axisTitle(curve->xAxis()).text());
// set that text up
tooltip->setText(text);
} else {
// no point
tooltip->setText("");
}
}